最近のプロジェクトでは、特定の情報を迅速に検証するためにブロックチェーンを使用する必要があります。毎日の情報量が非常に多いため、ブロックチェーンを直接使用して情報を保存するのは現実的ではありません。そのため、マークル ツリーのリーフ ノードを使用します。まず受信情報をハッシュし、次にノード ハッシュを使用してマークル ツリーを構築し、マークル ルートを取得します。最後に、以前のブロック ハッシュ、タイムスタンプ、その他の情報とともにハッシュ化および暗号化されて、現在のブロックが取得されます。 。
データが攻撃され、改ざんされた場合:
マークル ツリー構築アルゴリズム:
マークル ツリーの基本構造を設定します。左右の子、データ ドメイン、リーフ ポイントかどうか、および対応する情報の主キーを含みます。
public class MerKleTreeNode {
private MerKleTreeNode lChild;
private MerKleTreeNode rChild;
private String data;
private Integer isLeaf;
private Integer infoId;
}
まずリーフノードのハッシュを構築します
for(Map<String,String> map : list){
MerKleTreeNode node = new MerKleTreeNode(null,null,BlockHashAlgoUtils.encodeDataBySHA_256(map),1,-1);
res.add(node);
}
Merkle バイナリ ツリーは、そのペアワイズ ハッシュを使用して完全なバイナリ ツリーを構築し、その後、完全なバイナリ ツリーの特性 (シリアル番号の配置特性、木の高さの式、層あたりのノード数の式) などを使用できるためです。上では、すべてが完全なバイナリ ツリーに関連付けられており、バイナリ ツリーの特性により、MerkleTreeNode への高速アクセスが実現されます。完全なバイナリ ツリーを構築したいので、最初にリーフ ノードを使用して数値を見つけます。その数値を X, < とします。 a i= 3>X はリーフ ノードの数より大きくなければならず、2 の累乗 (1、2、4、8...) であり、X は大きすぎてはなりません。大きすぎるとスペースが無駄になってしまいます。できるだけリーフ ノードの総数に近づける必要があります。
if((size & (size -1))!=0){
int n = size;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
n = n >= MAXIMUM ? MAXIMUM : n + 1;
//计算差距
int distance = n - size;
for(int i =size-distance;i < size; i++){
//复制对称数据,凑齐merkle树所有节点
data.add(data.get(i));
}
size = n;
}
上記の例は次のとおりです。
init: 0001101011011001
>>>1: 0001101010000100 | 0000110101000010 = 000111111000110
````````(符号なし右シフトの類似)
====>>> 0001111111111111
res = 0001111111111111 + 1 = 0010000000000000 = 2^14
高次右シフトの特性を利用して、実際には入力された数値を符号なし右シフトで 1 に変更し、最後に By + を渡すことになります。 1 回だけ 1 を取得すると、元の数値の次に高いビットに 1 が得られ、残りの位置は 2 進法により 0 になります。これで、正確な X を取得できるようになります。
次のステップは、高さ、シリアル番号、各レイヤーが所有するノード ツリーを計算することです
サイズのバイナリ形式を取得します。その長さはマークル ツリーの高さになります。つまり、 バイナリの長さです。
int height = Integer.toBinaryString(size).length();
int idxStart = (total +1) >> 1;
int total = (int)(Math.pow(2,height)-1);
最終的にマークル ツリーを構築します
idxStart は、ノードの各層の最初の葉ノードのシーケンス番号 (左から数えて) を計算するために使用されます (ルート ノードは最初から配置されます) 1) から
while(height-1>0){
/**
* 总节点数 2^n-1
* 每一层的最左节点序号
*/
total = (int)(Math.pow(2,height-1)-1);
idxStart = (total +1) >> 1;
/**
* 逐层构建,new temp(list) 作为新一层的数据赋值到data(list)上,循环
*/
ArrayList<MerKleTreeNode> temp = new ArrayList<>();
for(int j = 0; j < size ; j+=2){
MerKleTreeNode lChild = data.get(j);
MerKleTreeNode rChild = data.get(j+1);
String hash = BlockHashAlgoUtils.encodeDataBySHA_256(lChild.getData() + rChild.getData());
MerKleTreeNode node = new MerKleTreeNode(lChild, rChild, hash,0,-1);
temp.add(node);
//输出非叶节点的序号
System.out.println(idxStart++);
}
//temp 下个循环进行新建回收,赋值data
data = temp;
height--;
//size长度每次减半
size = size >> 1;
}
次のステップは SPV 検証です。配列の保存は表示が不便であるため、Guwo はまずデータをデータベースに保存し、インデックス フィールドを使用して配列の添字を保存します。
SPV クエリを実行するには、最初に SPV 検証パスを取得することが最も重要です (つまり、次の図に示すように、ハッシュのペアの残りの半分を固定要素として使用する必要があります)。下の図の黒丸)、または完全なバイナリ ツリーに基づいて、メッセージが配置されているインデックスのシリアル番号特性に基づいて、すべての spv 検証パス ノードのインデックスを迅速に取得します
while(idx>1){
/**
* if 是偶数左节点,取他的右兄弟节点
* else 取左节点
* 完成后向父节点移动
*/
if((idx&1) == 0){
list.add(idx+1);
}else{
list.add(idx-1);
}
idx = idx >> 1;
}
/**
* 1也要加入进去
*/
list.add(idx);
//根据集合在list集合中的数据去数据库中查询所有的节点
........(查库操作)
}
次のステップでは、ハッシュ ブランチを再度構築して MerkleRoot を取り戻し、それをブロックチェーンにアップロードされた merkleROOT と比較して、情報が再度変更されたかどうかを判断します。
if(CollectionUtils.isEmpty(checkProofs)){
return res;
}
/**
* 从集合中取出一个,任意一个所属的区块和链都应相同
*/
Integer blockIndex = checkProofs.get(0).getBlockIndex();
MerkleNode merkleRoot = getMerkleRoot(blockIndex);
if(Objects.isNull(merkleRoot)){
return res;
}
/**
* 与区块链上的block比对Merkle Root
*/
Blockchain blockchain = blockchainMapper.selectByPrimaryKey(blockIndex);
if(!blockchain.getBlockMerkle().equals(merkleRoot.getHash())){
return res;
}
/**
* 与merkle树上的验证路径比对
* mapper降序处理
* 通过奇偶判断左右孩子
* 跑到最后一层即可,不用继续计算
*/
Info info = infoMapper.selectByPrimaryKey(infoId);
String hash = BlockHashAlgoUtils.encodeDataBySHA_256(info.toString());
for(int i = checkProofs.size()-1;i>0;i--){
MerkleNode node2 = checkProofs.get(i);
if((node2.getMerkleNodeIndex()&1)==0){
hash = BlockHashAlgoUtils.encodeDataBySHA_256(node2.getHash()+hash);
}else{
hash = BlockHashAlgoUtils.encodeDataBySHA_256(hash + node2.getHash());
}
}
/**
* 新生成的与merkle root再次比对
*/
if(!blockchain.getBlockMerkle().equals(hash)){
return res;
}
一般に、Merkle + SPV を使用する利点は、大量の情報の中の特定の情報が変更されているかどうかを確認する必要がある場合、すべてのノードの情報を知る必要がなく、知っているだけで済むことです。ある小さなブランチのノードを検証する必要があるが、必要なノードはlog(n+1)だけで十分である 桁が大きい場合、節約されるスペースはかなり大きくなり、ユーザーはそれほど大きなコストを負担する必要はない必要なのはブロックチェーンの先頭、データだけです。
残りはブロックチェーンの特性です。MerkleRoot はブロックチェーンに保存されているため、対応する情報は基本的にその信頼性を保証できます (ハッシュの衝突は依然として発生する可能性があります。使用されるハッシュ アルゴリズムを参照してください)