简单介绍 MerkleTree 和 Delta merkle proof.
在实际应用中, 除了证明默克尔树中存在某一特定叶子节点外(merkle proof), 我们通常还希望证明将树中的特定叶子节点从一个值修改为另一个值的结果(delta merkle proof).
Delta merkle proof 可以证明:
注意, 在变更前后的证明中, 兄弟节点是相同的! 即 merkle path(witness) 是相同的.
因此, delta merkle proof
包括:
const map = new MerkleMap();
const root1 = map.getRoot();
const witness1 = map.getWitness(Field(1));
const [calcroot1, key1] = witness1.computeRootAndKey(Field(0));
Bool.and(calcroot1.equals(root1), key1.equals(Field(1))).assertTrue('Member validation failed');
map.set(Field(1), Field(10086));
const root2 = map.getRoot();
const witness2 = map.getWitness(Field(1));
const [calcroot2, key2] = witness2.computeRootAndKey(Field(10086));
Bool.and(calcroot2.equals(root2), key2.equals(Field(1))).assertTrue('Member validation failed');
root1.assertNotEquals(root2);
witness1.assertEquals(witness2);
map.set(Field(1), Field(10010));
const root3 = map.getRoot();
const witness3 = map.getWitness(Field(1));
const [calcroot3, key3] = witness3.computeRootAndKey(Field(10010));
Bool.and(calcroot3.equals(root3), key3.equals(Field(1))).assertTrue('Member validation failed');
root2.assertNotEquals(root3);
witness1.assertEquals(witness3);
nodes 是每层节点的 map, 一共 height 层. 更新的叶子节点对应的每一层节点会存放到 nodes 中. zeros 是每层全是 zeros leaves 的 hash 的数组.
class MerkleTree {
nodes: Record<number, Record<string, Field>> = {};
zeroes: Field[];
constructor(public readonly height: number) {
this.zeroes = new Array(height);
this.zeroes[0] = Field(0);
for (let i = 1; i < height; i += 1) {
this.zeroes[i] = Poseidon.hash([this.zeroes[i - 1], this.zeroes[i - 1]]);
}
}
}
getNode
返回某一层, 某个索引的 value, 如果不存在(为未更新的 zero hash), 则从 zeros[level]
中查找.
?
: 可选链操作符, 允许在访问对象的深层次属性时, 如果中间某个属性为 null
或者 undefined
, 表达式就会短路返回 undefined
, 而不会抛出错误.??
: 空值合并操作符, 用于在左侧的值为 null
或者 undefined
时, 返回右侧的值. getNode(level: number, index: bigint): Field {
return this.nodes[level]?.[index.toString()] ?? this.zeroes[level];
}
获取 merkletree root.
getRoot(): Field {
return this.getNode(this.height - 1, 0n);
}
参考 getNode
.
private setNode(level: number, index: bigint, value: Field) {
(this.nodes[level] ??= {})[index.toString()] = value;
}
沿着要更新的 Leaf
的路径更新路径上的节点值. 共更新 height
个节点.
setLeaf(index: bigint, leaf: Field) {
if (index >= this.leafCount) {
throw new Error(
`index ${index} is out of range for ${this.leafCount} leaves.`
);
}
this.setNode(0, index, leaf);
let currIndex = index;
for (let level = 1; level < this.height; level++) {
currIndex /= 2n;
const left = this.getNode(level - 1, currIndex * 2n);
const right = this.getNode(level - 1, currIndex * 2n + 1n);
this.setNode(level, currIndex, Poseidon.hash([left, right]));
}
}
获取 index
叶子节点对应的 merkle path.
getWitness(index: bigint): Witness {
if (index >= this.leafCount) {
throw new Error(
`index ${index} is out of range for ${this.leafCount} leaves.`
);
}
const witness = [];
for (let level = 0; level < this.height - 1; level++) {
const isLeft = index % 2n === 0n;
const sibling = this.getNode(level, isLeft ? index + 1n : index - 1n);
witness.push({ isLeft, sibling });
index /= 2n;
}
return witness;
}
class BaseMerkleWitness extends CircuitValue {
static height: number;
path: Field[];
isLeft: Bool[];
height(): number {
return (this.constructor as any).height;
}
constructor(witness: Witness) {
super();
let height = witness.length + 1;
if (height !== this.height()) {
throw Error(
`Length of witness ${height}-1 doesn't match static tree height ${this.height()}.`
);
}
this.path = witness.map((item) => item.sibling);
this.isLeft = witness.map((item) => Bool(item.isLeft));
}
}
根据输入的叶子节点的 value 和 merkle path 计算 merkletree root.
calculateRoot(leaf: Field): Field {
let hash = leaf;
let n = this.height();
for (let i = 1; i < n; ++i) {
let isLeft = this.isLeft[i - 1];
const [left, right] = conditionalSwap(isLeft, hash, this.path[i - 1]);
hash = Poseidon.hash([left, right]);
}
return hash;
}
确保 key 为 254 位以避免冲突
class MerkleMap {
tree: MerkleTree;
constructor() {
this.tree = new MerkleTree(256);
}
}
注意: 确保 key 为 254 位以避免冲突, 因为 Pasta curve 域模数小于 2 的 255 次方. 关于 Pasta 和 Vesta 曲线参考另外一篇文章 https://learnblockchain.cn/article/10083.
同 MerkleTree setLeaf, 只不过要用 _keyToIndex
处理 index.
同 MerkleTree getWitness, 只不过要用 _keyToIndex
处理 index.
同 BaseMerkleWitness.
class MerkleMapWitness extends CircuitValue {
@arrayProp(Bool, 255) isLefts: Bool[];
@arrayProp(Field, 255) siblings: Field[];
constructor(isLefts: Bool[], siblings: Field[]) {
super();
this.isLefts = isLefts;
this.siblings = siblings;
}
同 BaseMerkleWitness computeRoot. 由于限制 key 为 254 位, 这里还会返回 key.
computeRootAndKey(value: Field) {
// Check that the computed key is less than 2^254, in order to avoid collisions since the Pasta field modulus is smaller than 2^255
this.isLefts[0].assertTrue();
let hash = value;
const isLeft = this.isLefts;
const siblings = this.siblings;
let key = Field(0);
for (let i = 0; i < 255; i++) {
const left = Provable.if(isLeft[i], hash, siblings[i]);
const right = Provable.if(isLeft[i], siblings[i], hash);
hash = Poseidon.hash([left, right]);
const bit = Provable.if(isLeft[i], Field(0), Field(1));
key = key.mul(2).add(bit);
}
return [hash, key];
}
}
Dynamic-length list which is represented as a single hash
Supported operations are () and () and some variants thereof.
A Merkle list is generic over its element types, so before using it you must create a subclass for your element type:
class MyList extends MerkleList.create(MyType) {}
// now use it
let list = MyList.empty();
list.push(new MyType(...));
let element = list.pop();
todo
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!