Merkle Tree,也叫哈希树,是由Ralph Merkle于1979年提出申请的专利。它是一种用做快速归纳和校验大规模数据完整性的树形数据结构。
它具有以下特点:
它是一种树,大多数是二叉树,也可以是多叉树,具有树结构的所有特点。
Merkle Tree的叶子节点是数据块的哈希。
Merkle Tree的非叶子节点的哈希值是根据它下面所有叶子节点的值哈希计算得到,如下图所示。
备注:如果最开始叶子节点是奇数个,可以复制最后一个叶子节点,凑成偶数个。
可以发现,只要存储的叶子节点数据有任何的变动,就会逐级向上传递到相应的父节点,最终使得Merkle树的根节点哈希值发生变化。
3.Merkle树的应用
从用户A在购买商品时通过比特币支付,并声称自己已经转了1BTC给商家,到商家验证支付有效(SPV验证),这个过程是怎样的呢?
SPV验证
用户A在购买商品时通过比特币支付,并声称自己已经转了1BTC给商家,到商家验证支付有效(SPV验证),这个过程是怎样的呢?
第一步:SPV节点如果只关心某个支付到自己比特币地址的交易,则可以通过建立布隆过滤器(布隆过滤器是一种基于哈希的高效查找结构,能够快速确定某个元素是否在一个集合内)限制只接收含有目标比特币地址的交易。
第二步:一旦比特币网络中其他当节点探测到某个交易符合SPV节点设置的布隆过滤器条件时,其它节点将以Merkleblock消息的形式发送该区块,Merkleblock消息包含区块头和一条连接目标交易与Merkle根的Merkle路径。
第三步:接下来,SPV节点需要验证交易,需要做2个检查,分别是:交易的存在性检查和交易是否重花的检查。
第四步:SPV节点通过该Merkle路径找到跟该交易相关的区块,并验证对应区块中是否存在目标交易。SPV节点所收到的Merkleblock数据量通常少于1KB,只有一个完整区块(大约1MB)大小的千分之一左右。
第五步:现在通过Merkle Path Proof,SPV节点确认了交易确实存在于区块链中,但是这个还是无法保证这笔交易(Transaction)的Input(引用的上一笔UTXO)没有被重花(双重支付)。这时候SPV节点通过去看这笔交易所在区块之后的区块个数,Block个数越多说明该区块被全网更多节点共识,一般来说,一笔交易所属区块之后的区块个数达到6个时,就说明这笔交易是被大家核准过(达成共识)的,没有重花,而且被篡改的可能性也很低
典型应用
- 快速比较大量数据:当两个默克尔树根相同时,则意味着所代表的数据必然相同(哈希算法决定的)。
- 快速定位修改:例如上例中,如果 D1 中数据被修改,会影响到Hash0-0,Hash0 和 Root。因此,沿着 Root --> 0 --> 0-0,可以快速定位到发生改变的 D1;
- 零知识证明:例如如何证明某个数据(D0……D3)中包括给定内容 D0,很简单,构造一个默克尔树,公布 N0,N1,N4,Root,D0 拥有者可以很容易检测 D0 存在,但不知道其它内容。
- 数据完整性校验:git 版本控制系统,ZFS/IPFS 分布式文件系统以及 BT 下载,都是通过 Merkle Tree 来进行完整性校验的。
- 在分布式存储系统中的应用:
为了保持数据一致,分布系统间数据需要同步,如果对机器上所有数据都进行比对的话,数据传输量就会很大,从而造成“网络拥挤”。为了解决这个问题,可以在每台机器上构造一棵 Merkle Tree,这样,在两台机器间进行数据比对时,从 Merkle Tree 的根节点开始进行比对,如果根节点一样,则表示两个副本目前是一致的,不再需要任何处理;如果不一样,则沿着hash值不同的节点路径查询,很快就能定位到数据不一致的叶节点,只用把不一致的数据同步即可,这样大大节省了比对时间以及数据的传输量。
相对于 Hash List,MT的明显的一个好处是可以单独拿出一个分支来(作为一个小树)对部分数据进行校验,这个很多使用场合就带来了哈希列表所不能比拟的方便和高效。正是源于这些优点,MT常用于分布式系统或分布式存储中。
more..
- Merkle Tree 在数字加密货币中首先应用于比特币(BTC),以太坊(Etherum)采用了改进的 Merkle Patricia Tree (MPT) 。
默克尔证明(Merkle Proof)
- 比特币钱包用 Merkle Tree 的机制来作 百分百准备金证明
- 比特币SPV中 如何利用默克尔树证明某个交易是否存在
- EOS.IO 通过默克尔证明 实现区块链间通信
4.eos中如何构建默克尔树
我们知道在eos中最重要的因素无非区块(block)、事物(transaction)、动作(action),通过阅读源码我们会发现,在每一次transaction执行的过程中都会对transaction和action进行默克尔树的构建,我们来一步步看一下。
当transaction被打包到区块中之后会有一个区块信息的确认,即:finalize_block,我们来看:
1void finalize_block()
2 {
3 //该方法开始对一些资源信息进行确认操作
4 //我们接下来加了部分日志打印,方便观察对比
5 std::string strPending = "";
6 strPending = fc::json::to_string(*pending->_pending_block_state);
7 dlog("contorller before set action merkle:${state}", ("state", strPending));
8 set_action_merkle();
9 strPending = fc::json::to_string(*pending->_pending_block_state);
10 dlog("contorller after set action and before set trx merkle:${state}", ("state", strPending));
11 set_trx_merkle();
12 strPending = fc::json::to_string(*pending->_pending_block_state);
13 dlog("contorller after set trx merkle:${state}", ("state", strPending));
14
15 auto p = pending->_pending_block_state;
16 p->id = p->header.id();
17
18 create_block_summary(p->id);
19
20 } FC_CAPTURE_AND_RETHROW() }
在这里面我们看到了set_action_merkle以及set_trx_merkle对action以及transaction进行默克尔树的构建,继续来看:
1 void set_action_merkle() {
2 vector<digest_type> action_digests;
3 dlog("contorller set_action_merkle size:${size}", ("size", pending->_actions.size()));
4 action_digests.reserve( pending->_actions.size() );
5 for( const auto& a : pending->_actions )
6 action_digests.emplace_back( a.digest() );
7
8 pending->_pending_block_state->header.action_mroot = merkle( move(action_digests) );
9 }
10
11 void set_trx_merkle() {
12 vector<digest_type> trx_digests;
13 const auto& trxs = pending->_pending_block_state->block->transactions;
14 dlog("contorller set_trx_merkle size:${size}", ("size", trxs.size()));
15 trx_digests.reserve( trxs.size() );
16 for( const auto& a : trxs )
17 trx_digests.emplace_back( a.digest() );
18
19 pending->_pending_block_state->header.transaction_mroot = merkle( move(trx_digests) );
20 }
不管是set_action_merkle还是set_trx_merkle最后都调用了Merkle方法,即先获取action或者transaction的摘要信息,然后进行默克尔树的构建,我们来看Merkle函数,在Merkle.cpp中:
1digest_type merkle(vector<digest_type> ids) {
2 if( 0 == ids.size() ) { return digest_type(); }
3
4 while( ids.size() > 1 ) {
5 if( ids.size() % 2 )
6 ids.push_back(ids.back());
7
8 for (int i = 0; i < ids.size() / 2; i++) {
9 ids[i] = digest_type::hash(make_canonical_pair(ids[2 * i], ids[(2 * i) + 1]));
10 }
11
12 ids.resize(ids.size() / 2);
13 }
针对武功个数是奇数还是偶数分别进行了hash,最终形成了默克尔树的构建,我这里以创建用户名为例,根据日志的打印对其进行跟踪对比结果,先执行命令行如下:
1cleos create account eosio merkletest yourpubkey yourpubkey
在构建默克尔树之前和之后的对比结果如下:
1//构建默克尔树之前
2 "header": {
3 "timestamp": "2018-10-10T12:32:30.500",
4 "producer": "eosio",
5 "confirmed": 0,
6 "previous": "0000014735eeda35fd8c2e303ceda4ff8fd5c67fbb032187c75eeb319daec123",
7 "transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
8 "action_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
9 "schedule_version": 0,
10 "header_extensions": [],
11 "producer_signature": "SIG_K1_111111111111111111111111111111111111111111111111111111111111111116uk5ne"
12 }
13//构建默克尔树之后
14"header": {
15 "timestamp": "2018-10-10T12:32:30.500",
16 "producer": "eosio",
17 "confirmed": 0,
18 "previous": "0000014735eeda35fd8c2e303ceda4ff8fd5c67fbb032187c75eeb319daec123",
19 "transaction_mroot": "12399917b8b8a3d7eb05aa58dd87aec2bbbda139e770627332da9e6f5efd7d88",
20 "action_mroot": "2b4c51c20fb35268628e8f2c5171549fa5bcb947079ed82a7f2d38c7481f8c4e",
21 "schedule_version": 0,
22 "header_extensions": [],
23 "producer_signature": "SIG_K1_111111111111111111111111111111111111111111111111111111111111111116uk5ne"
24 }
通过对比可以发现transaction_mroot及action_mroot发生了相应的变化,至此eos中构建默克尔树的流程也已经完成。