mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
133 lines
5.4 KiB
Markdown
133 lines
5.4 KiB
Markdown
# Merkle Mountain Ranges
|
||
|
||
*Read this in other languages: [English](mmr.md), [简体中文](mmr_ZH-CN.md), [Korean](mmr_KR.md)*
|
||
|
||
## 结构
|
||
|
||
Merkle Mountain Ranges [1] 是 Merkle trees [2] 的替代品。
|
||
后者依赖于完美二叉树,前者既可以看作是由许多完美二叉树组成的列表或是看作是一个右上角被截断的二叉树。
|
||
Merkle Mountain Ranges(MMR)是 append-only 的:
|
||
元素自左向右添加,如果有 2 个孩子节点,则立即添加一个父节点,并将相应的 range 填满。
|
||
|
||
这里展示了一个 range,其中有 11 个叶子,总大小为 19(个节点),其中每个节点都按照插入顺序编号被标注。
|
||
|
||
```
|
||
高度
|
||
|
||
3 14
|
||
/ \
|
||
/ \
|
||
/ \
|
||
/ \
|
||
2 6 13
|
||
/ \ / \
|
||
1 2 5 9 12 17
|
||
/ \ / \ / \ / \ / \
|
||
0 0 1 3 4 7 8 10 11 15 16 18
|
||
```
|
||
|
||
这个 range 可以以平面列表的方式展现出来,此处存储了每个节点在其插入位置的高度:
|
||
|
||
```
|
||
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
||
0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 0 0 1 0
|
||
```
|
||
|
||
我们可以轻松地从其大小(19)来对这个结构做一个全面的描述。
|
||
同时还带来了另一个好处,可以很容易地使用快速二进制操作在 MMR 中导航。
|
||
给定一个节点的位置 `n`,我们可以计算它的高度、父节点、兄弟节点等等。
|
||
|
||
## Hashing 与 Bagging
|
||
|
||
就像 Merkle trees 一样,MMR 中的父节点的值由其两个孩子节点 hash 得到。
|
||
Grin 自始至终使用 Blake2b 哈希函数,在进行 hashing 之前会预先设置节点在 MMR 中的位置以此来避免冲突。
|
||
所以对于索引 `n` 处的叶子 `l` 存储的数据 `D`(在输出的情况下,数据是其 Pedersen 承诺,例如),我们有:
|
||
|
||
```
|
||
Node(l) = Blake2b(n | D)
|
||
```
|
||
|
||
对于任何处于索引 `m` 的父节点 `p`:
|
||
|
||
```
|
||
Node(p) = Blake2b(m | Node(left_child(p)) | Node(right_child(p)))
|
||
```
|
||
|
||
与 Merkle tree 相反,MMR 通常在构造时没有单独的根所以我们需要一种方法来计算一个(不然就违背了使用哈希树的本意)。
|
||
由于某些原因,此过程称为 “bagging the peaks” 详见 [1] 中描述。
|
||
|
||
首先,我们确定 MMR 所有的峰;在这里我们将定义这样一种方法。
|
||
首先编写另一个小示例 MMR,但将其索引用表示二进制表示出来(而不是十进制),从 1 开始:
|
||
|
||
```
|
||
高度
|
||
|
||
2 111
|
||
/ \
|
||
1 11 110 1010
|
||
/ \ / \ / \
|
||
0 1 10 100 101 1000 1001 1011
|
||
```
|
||
|
||
这个 MMR 有 11 个节点,它所有的峰分别位于索引 111(7),1010(10)和 1011(11)处。
|
||
我们首先会注意到最左边的第一个峰以二进制表示时,总是最高的并且其所有位 “都是 1”。
|
||
因此该最高峰的位置总是遵循 `2^n - 1` 的规律且处于这个位置的峰一定是 MMR 内部最大的峰(其位置索引值小于 MMR 的大小即节点个数)。
|
||
我们针对大小为 11 的 MMR 进行迭代处理:
|
||
|
||
```
|
||
2^0 - 1 = 0, and 0 < 11
|
||
2^1 - 1 = 1, and 1 < 11
|
||
2^2 - 1 = 3, and 3 < 11
|
||
2^3 - 1 = 7, and 7 < 11
|
||
2^4 - 1 = 15, and 15 is not < 11
|
||
```
|
||
|
||
(这个过程可以通过非迭代地计算表示为 `2^(大小的二进制对数 + 1) - 1`
|
||
|
||
因此,第一个峰为 7。要找到下一个峰,我们需要 “跳” 到它的右边的兄弟节点。
|
||
如果该节点不在 MMR 中(确实不在),走到它的左孩子节点。
|
||
如果该孩子节点也不在 MMR 中,继续跟进到其左孩子节点直到发现这个节点存在于我们的 MMR 中。
|
||
一旦找到下一个峰,继续重复该过程,直到到达最后一个节点。
|
||
|
||
所有这些操作都非常的简单。高度 `h` 下跳至节点的右兄弟节点只需要将 `2^(h+1) - 1` 加到其索引上。
|
||
减去 `2^h` 即可跳到它的左孩子节点。
|
||
|
||
最后,一旦知道了峰的所有位置,就对这些峰进行 “bagging” 操作,使用 MMR 的总大小作为前缀从右侧开始迭代地对其进行哈希处理。
|
||
对于一个大小为 N 具有 3 个峰 p1,p2 和 p3 的 MMR,我们最终得到的最高峰为:
|
||
|
||
```
|
||
P = Blake2b(N | Blake2b(N | Node(p3) | Node(p2)) | Node(p1))
|
||
```
|
||
|
||
## 修剪
|
||
|
||
在 Grin 中,有很多经过哈希并存储在 MMR 中的数据最终是可以被删除的。
|
||
发生这种情况时,那些相应的叶子节点的哈希的存在变得不必要并且这些哈希可以被删除。
|
||
当删除掉足够多的叶子后,它们的父节点的存在也变得不必要,因此,我们可以通过删除它的叶子来修剪 MMR 的一大部分。
|
||
|
||
修剪 MMR 依赖于一个简单的迭代过程。首先将 `X` 初始化为我们希望修剪的那个叶子。
|
||
|
||
1. 修剪 `X`。
|
||
2. 如果 `X` 有兄弟姐妹,则在此处停止。
|
||
3. 如果 `X` 没有兄弟姐妹,则将 `X` 的父亲节点指定为 `X`。
|
||
|
||
为了可视化结果,从我们的第一个 MMR 示例开始,然后删除叶子 [0、3、4、8、16] 得到以下修剪过后的 MMR:
|
||
|
||
```
|
||
高度
|
||
|
||
3 14
|
||
/ \
|
||
/ \
|
||
/ \
|
||
/ \
|
||
2 6 13
|
||
/ / \
|
||
1 2 9 12 17
|
||
\ / / \ /
|
||
0 1 7 10 11 15 18
|
||
```
|
||
|
||
[1] Peter Todd, [merkle-mountain-range](https://github.com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.md)
|
||
|
||
[2] [Wikipedia, Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree)
|