Merkle树的生成和SPV验证信息的策略
Posted white and white
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Merkle树的生成和SPV验证信息的策略相关的知识,希望对你有一定的参考价值。
最近项目需要用到区块链来对某些信息进行快速校验,且由于每天的信息量都是很大的,因此直接使用区块链来保存信息是不可行的,故使用Merkle树的叶子节点先对传入的信息进行一次hash加密,紧接着利用结点两两hash构造Merkle树得到Merkle Root 最后与前区块hash,时间戳等信息一起再hash加密得到当前区块信息。
当数据被攻击篡改:
Merkle树构造算法:
设置Merkle树的基本架构,包括左右孩子,数据域,是否叶子几点和对应的信息主键
public class MerKleTreeNode
private MerKleTreeNode lChild;
private MerKleTreeNode rChild;
private String data;
private Integer isLeaf;
private Integer infoId;
先对叶子节点进行构造hash
for(Map<String,String> map : list)
MerKleTreeNode node = new MerKleTreeNode(null,null,BlockHashAlgoUtils.encodeDataBySHA_256(map),1,-1);
res.add(node);
由于Merkle二叉树的话可以利用他两两hash的缘故,构造一颗满二叉树,再利用满二叉树的特性(序号编排特点,树高公式,每层节点数公式)等等一切与满二叉树的特点,可以达到快速的存取MerkleTreeNode。既然要构造满二叉树,我们可以先利用叶子节点,通过求出一个数,设该数为 X,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,最后在通过一次+1 ,既可以在原数的下一个最高位上得到一个1,且其余的位置因为2进制的缘故变为了0,这下就可以得到一个准确地 X
接着就是计算一些高度啊,序号,以及每层拥有的节点树
获取size的二进制形式,它的长度就是merkle树的高度,也就是该二进制的长度
int height = Integer.toBinaryString(size).length();
int idxStart = (total +1) >> 1;
int total = (int)(Math.pow(2,height)-1);
最后构造Merkle树
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验证,由于数组存储不方便展示,顾我把数据先行存放在数据库中,利用index字段存储数组的下标。
进行SPV查询,最重要的就是需要先获取SPV验证路径(即需要对两两hash的另一半作为固定的因素,如下图黑圈所示),还是根据满二叉树的序号特点,通过计算下标
如图所示,很明显,左节点都是偶数,右节点是奇数,根结点是1,可以利用这个特性,根据传入消息所在的index快速得到所有的spv验证路径节点的index
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集合中的数据去数据库中查询所有的节点
........(查库操作)
接着就是再一次构造hash分支重新得到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保存在区块链中,所以对应的信息可基本可以保障他的可靠性(仍有可能发生hash碰撞的因素,看利用的hash算法)
以上是关于Merkle树的生成和SPV验证信息的策略的主要内容,如果未能解决你的问题,请参考以下文章