Skip to content

Commit 56f30d7

Browse files
authored
Merge pull request #30 from akifoq/main
More flexible merkle proof generation
2 parents 1c99f32 + cd92a5d commit 56f30d7

5 files changed

+82
-47
lines changed

src/boc/cell/exoticMerkleProof.ts

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import { BitReader } from "../BitReader";
1010
import { BitString } from "../BitString";
1111
import { Cell } from "../Cell";
12+
import { beginCell } from '../Builder';
1213

1314
export function exoticMerkleProof(bits: BitString, refs: Cell[]) {
1415
const reader = new BitReader(bits);
@@ -48,4 +49,13 @@ export function exoticMerkleProof(bits: BitString, refs: Cell[]) {
4849
proofDepth,
4950
proofHash
5051
};
52+
}
53+
54+
export function convertToMerkleProof(c: Cell): Cell {
55+
return beginCell()
56+
.storeUint(3, 8)
57+
.storeBuffer(c.hash(0))
58+
.storeUint(c.depth(0), 16)
59+
.storeRef(c)
60+
.endCell({ exotic: true });
5161
}

src/dict/Dictionary.spec.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,22 @@ describe('Dictionary', () => {
121121
d.set(4, 44);
122122
d.set(5, 55);
123123

124+
const dictHash = beginCell().storeDictDirect(d).endCell().hash();
124125
for (let k = 1; k <= 5; k++) {
125-
const proof = d.generateMerkleProof(k);
126+
const proof = d.generateMerkleProof([k]);
126127
Cell.fromBoc(proof.toBoc());
127128
expect(exoticMerkleProof(proof.bits, proof.refs).proofHash).toEqual(
128-
Buffer.from(
129-
'ee41b86bd71f8224ebd01848b4daf4cd46d3bfb3e119d8b865ce7c2802511de3',
130-
'hex'
131-
)
129+
dictHash
130+
);
131+
132+
// todo: parse the pruned dictionary and check the presence of the keys
133+
}
134+
135+
for (let k = 1; k <= 3; k++) {
136+
const proof = d.generateMerkleProof([k, k + 1, k + 2]);
137+
Cell.fromBoc(proof.toBoc());
138+
expect(exoticMerkleProof(proof.bits, proof.refs).proofHash).toEqual(
139+
dictHash
132140
);
133141
}
134142
});

src/dict/Dictionary.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Cell } from "../boc/Cell";
1212
import { Slice } from "../boc/Slice";
1313
import { BitString } from "../boc/BitString";
1414
import { Maybe } from "../utils/maybe";
15-
import { generateMerkleProof } from "./generateMerkleProof";
15+
import { generateMerkleProof, generateMerkleProofDirect } from "./generateMerkleProof";
1616
import { generateMerkleUpdate } from "./generateMerkleUpdate";
1717
import { parseDict } from "./parseDict";
1818
import { serializeDict } from "./serializeDict";
@@ -392,8 +392,23 @@ export class Dictionary<K extends DictionaryKeyTypes, V> {
392392
serializeDict(prepared, resolvedKey.bits, resolvedValue.serialize, builder);
393393
}
394394

395-
generateMerkleProof(key: K): Cell {
396-
return generateMerkleProof(this, key, this._key!)
395+
/**
396+
* Generate merkle proof for multiple keys in the dictionary
397+
* @param keys an array of the keys
398+
* @returns generated merkle proof cell
399+
*/
400+
generateMerkleProof(keys: K[]): Cell {
401+
return generateMerkleProof(this, keys, this._key!)
402+
}
403+
404+
/**
405+
* Low level method for generating pruned dictionary directly.
406+
* The result can be used as a part of a bigger merkle proof
407+
* @param keys an array of the keys
408+
* @returns cell that contains the pruned dictionary
409+
*/
410+
generateMerkleProofDirect(keys: K[]): Cell {
411+
return generateMerkleProofDirect(this, keys, this._key!)
397412
}
398413

399414
generateMerkleUpdate(key: K, newValue: V): Cell {

src/dict/generateMerkleProof.ts

+39-37
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Cell } from '../boc/Cell';
33
import { Slice } from '../boc/Slice';
44
import { DictionaryKeyTypes, Dictionary, DictionaryKey } from './Dictionary';
55
import { readUnaryLength } from './utils/readUnaryLength';
6+
import { convertToMerkleProof } from '../boc/cell/exoticMerkleProof';
67

78
function convertToPrunedBranch(c: Cell): Cell {
89
return beginCell()
@@ -13,24 +14,20 @@ function convertToPrunedBranch(c: Cell): Cell {
1314
.endCell({ exotic: true });
1415
}
1516

16-
function convertToMerkleProof(c: Cell): Cell {
17-
return beginCell()
18-
.storeUint(3, 8)
19-
.storeBuffer(c.hash(0))
20-
.storeUint(c.depth(0), 16)
21-
.storeRef(c)
22-
.endCell({ exotic: true });
23-
}
24-
2517
function doGenerateMerkleProof(
2618
prefix: string,
2719
slice: Slice,
2820
n: number,
29-
key: string
21+
keys: string[]
3022
): Cell {
3123
// Reading label
3224
const originalCell = slice.asCell();
3325

26+
if (keys.length == 0) {
27+
// no keys to prove, prune the whole subdict
28+
return convertToPrunedBranch(originalCell);
29+
}
30+
3431
let lb0 = slice.loadBit() ? 1 : 0;
3532
let prefixLength = 0;
3633
let pp = prefix;
@@ -71,28 +68,26 @@ function doGenerateMerkleProof(
7168
let right = sl.loadRef();
7269
// NOTE: Left and right branches are implicitly contain prefixes '0' and '1'
7370
if (!left.isExotic) {
74-
if (pp + '0' === key.slice(0, pp.length + 1)) {
75-
left = doGenerateMerkleProof(
76-
pp + '0',
77-
left.beginParse(),
78-
n - prefixLength - 1,
79-
key
80-
);
81-
} else {
82-
left = convertToPrunedBranch(left);
83-
}
71+
const leftKeys = keys.filter((key) => {
72+
return pp + '0' === key.slice(0, pp.length + 1);
73+
});
74+
left = doGenerateMerkleProof(
75+
pp + '0',
76+
left.beginParse(),
77+
n - prefixLength - 1,
78+
leftKeys
79+
);
8480
}
8581
if (!right.isExotic) {
86-
if (pp + '1' === key.slice(0, pp.length + 1)) {
87-
right = doGenerateMerkleProof(
88-
pp + '1',
89-
right.beginParse(),
90-
n - prefixLength - 1,
91-
key
92-
);
93-
} else {
94-
right = convertToPrunedBranch(right);
95-
}
82+
const rightKeys = keys.filter((key) => {
83+
return pp + '1' === key.slice(0, pp.length + 1);
84+
});
85+
right = doGenerateMerkleProof(
86+
pp + '1',
87+
right.beginParse(),
88+
n - prefixLength - 1,
89+
rightKeys
90+
);
9691
}
9792

9893
return beginCell()
@@ -103,18 +98,25 @@ function doGenerateMerkleProof(
10398
}
10499
}
105100

106-
export function generateMerkleProof<K extends DictionaryKeyTypes, V>(
101+
export function generateMerkleProofDirect<K extends DictionaryKeyTypes, V>(
107102
dict: Dictionary<K, V>,
108-
key: K,
103+
keys: K[],
109104
keyObject: DictionaryKey<K>
110105
): Cell {
111-
const s = beginCell().storeDictDirect(dict).endCell().beginParse();
112-
return convertToMerkleProof(
113-
doGenerateMerkleProof(
106+
keys = keys.filter((key) => dict.has(key));
107+
const s = beginCell().storeDictDirect(dict).asSlice();
108+
return doGenerateMerkleProof(
114109
'',
115110
s,
116111
keyObject.bits,
117-
keyObject.serialize(key).toString(2).padStart(keyObject.bits, '0')
118-
)
112+
keys.map((key) => keyObject.serialize(key).toString(2).padStart(keyObject.bits, '0'))
119113
);
120114
}
115+
116+
export function generateMerkleProof<K extends DictionaryKeyTypes, V>(
117+
dict: Dictionary<K, V>,
118+
keys: K[],
119+
keyObject: DictionaryKey<K>
120+
): Cell {
121+
return convertToMerkleProof(generateMerkleProofDirect(dict, keys, keyObject));
122+
}

src/dict/generateMerkleUpdate.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export function generateMerkleUpdate<K extends DictionaryKeyTypes, V>(
2121
keyObject: DictionaryKey<K>,
2222
newValue: V
2323
): Cell {
24-
const oldProof = generateMerkleProof(dict, key, keyObject).refs[0];
24+
const oldProof = generateMerkleProof(dict, [key], keyObject).refs[0];
2525
dict.set(key, newValue);
26-
const newProof = generateMerkleProof(dict, key, keyObject).refs[0];
26+
const newProof = generateMerkleProof(dict, [key], keyObject).refs[0];
2727
return convertToMerkleUpdate(oldProof, newProof);
2828
}

0 commit comments

Comments
 (0)