Skip to content

Commit 84d1044

Browse files
author
Dan Volkov
authored
Merge pull request #14 from Gusarich/patch2
add `generateMerkleProof` and `generateMerkleUpdate` functions for dictionaries
2 parents 562fb15 + 210ddf3 commit 84d1044

7 files changed

+4982
-6844
lines changed

src/dict/Dictionary.spec.ts

+51
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import { beginCell, Builder } from "../boc/Builder";
1010
import { Cell } from "../boc/Cell";
11+
import { exoticMerkleProof } from "../boc/cell/exoticMerkleProof";
12+
import { exoticMerkleUpdate } from "../boc/cell/exoticMerkleUpdate";
1113
import { Dictionary } from "./Dictionary";
1214
import fs from 'fs';
1315

@@ -83,4 +85,53 @@ describe('Dictionary', () => {
8385
// console.warn(oracles);
8486
}
8587
});
88+
89+
it('should generate merkle proofs', () => {
90+
let d = Dictionary.empty(
91+
Dictionary.Keys.Uint(8),
92+
Dictionary.Values.Uint(32)
93+
);
94+
d.set(1, 11);
95+
d.set(2, 22);
96+
d.set(3, 33);
97+
d.set(4, 44);
98+
d.set(5, 55);
99+
100+
for (let k = 1; k <= 5; k++) {
101+
const proof = d.generateMerkleProof(k);
102+
Cell.fromBoc(proof.toBoc());
103+
expect(exoticMerkleProof(proof.bits, proof.refs).proofHash).toEqual(
104+
Buffer.from(
105+
'ee41b86bd71f8224ebd01848b4daf4cd46d3bfb3e119d8b865ce7c2802511de3',
106+
'hex'
107+
)
108+
);
109+
}
110+
});
111+
112+
it('should generate merkle updates', () => {
113+
let d = Dictionary.empty(
114+
Dictionary.Keys.Uint(8),
115+
Dictionary.Values.Uint(32)
116+
);
117+
d.set(1, 11);
118+
d.set(2, 22);
119+
d.set(3, 33);
120+
d.set(4, 44);
121+
d.set(5, 55);
122+
123+
for (let k = 1; k <= 5; k++) {
124+
const update = d.generateMerkleUpdate(k, d.get(k)! * 2);
125+
Cell.fromBoc(update.toBoc());
126+
expect(
127+
exoticMerkleUpdate(update.bits, update.refs).proofHash1
128+
).toEqual(
129+
Buffer.from(
130+
'ee41b86bd71f8224ebd01848b4daf4cd46d3bfb3e119d8b865ce7c2802511de3',
131+
'hex'
132+
)
133+
);
134+
d.set(k, Math.floor(d.get(k)! / 2));
135+
}
136+
});
86137
});

src/dict/Dictionary.ts

+10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { beginCell, Builder } from "../boc/Builder";
1111
import { Cell } from "../boc/Cell";
1212
import { Slice } from "../boc/Slice";
1313
import { Maybe } from "../utils/maybe";
14+
import { generateMerkleProof } from "./generateMerkleProof";
15+
import { generateMerkleUpdate } from "./generateMerkleUpdate";
1416
import { parseDict } from "./parseDict";
1517
import { serializeDict } from "./serializeDict";
1618
import { deserializeInternalKey, serializeInternalKey } from "./utils/internalKeySerializer";
@@ -364,6 +366,14 @@ export class Dictionary<K extends DictionaryKeyTypes, V> {
364366
// Store
365367
serializeDict(prepared, resolvedKey.bits, resolvedValue.serialize, builder);
366368
}
369+
370+
generateMerkleProof(key: K): Cell {
371+
return generateMerkleProof(this, key, this._key!)
372+
}
373+
374+
generateMerkleUpdate(key: K, newValue: V): Cell {
375+
return generateMerkleUpdate(this, key, this._key!, newValue);
376+
}
367377
}
368378

369379
//

src/dict/generateMerkleProof.ts

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { beginCell } from '../boc/Builder';
2+
import { Cell } from '../boc/Cell';
3+
import { Slice } from '../boc/Slice';
4+
import { DictionaryKeyTypes, Dictionary, DictionaryKey } from './Dictionary';
5+
import { readUnaryLength } from './utils/readUnaryLength';
6+
7+
function convertToPrunedBranch(c: Cell): Cell {
8+
return new Cell({
9+
exotic: true,
10+
bits: beginCell()
11+
.storeUint(1, 8)
12+
.storeUint(1, 8)
13+
.storeBuffer(c.hash(0))
14+
.storeUint(c.depth(0), 16)
15+
.endCell()
16+
.beginParse()
17+
.loadBits(288),
18+
});
19+
}
20+
21+
function convertToMerkleProof(c: Cell): Cell {
22+
return new Cell({
23+
exotic: true,
24+
bits: beginCell()
25+
.storeUint(3, 8)
26+
.storeBuffer(c.hash(0))
27+
.storeUint(c.depth(0), 16)
28+
.endCell()
29+
.beginParse()
30+
.loadBits(280),
31+
refs: [c],
32+
});
33+
}
34+
35+
function doGenerateMerkleProof(
36+
prefix: string,
37+
slice: Slice,
38+
n: number,
39+
key: string
40+
): Cell {
41+
// Reading label
42+
const originalCell = slice.asCell();
43+
44+
let lb0 = slice.loadBit() ? 1 : 0;
45+
let prefixLength = 0;
46+
let pp = prefix;
47+
48+
if (lb0 === 0) {
49+
// Short label detected
50+
51+
// Read
52+
prefixLength = readUnaryLength(slice);
53+
54+
// Read prefix
55+
for (let i = 0; i < prefixLength; i++) {
56+
pp += slice.loadBit() ? '1' : '0';
57+
}
58+
} else {
59+
let lb1 = slice.loadBit() ? 1 : 0;
60+
if (lb1 === 0) {
61+
// Long label detected
62+
prefixLength = slice.loadUint(Math.ceil(Math.log2(n + 1)));
63+
for (let i = 0; i < prefixLength; i++) {
64+
pp += slice.loadBit() ? '1' : '0';
65+
}
66+
} else {
67+
// Same label detected
68+
let bit = slice.loadBit() ? '1' : '0';
69+
prefixLength = slice.loadUint(Math.ceil(Math.log2(n + 1)));
70+
for (let i = 0; i < prefixLength; i++) {
71+
pp += bit;
72+
}
73+
}
74+
}
75+
76+
if (n - prefixLength === 0) {
77+
return originalCell;
78+
} else {
79+
let sl = originalCell.beginParse();
80+
let left = sl.loadRef();
81+
let right = sl.loadRef();
82+
// NOTE: Left and right branches are implicitly contain prefixes '0' and '1'
83+
if (!left.isExotic) {
84+
if (pp + '0' === key.slice(0, pp.length + 1)) {
85+
left = doGenerateMerkleProof(
86+
pp + '0',
87+
left.beginParse(),
88+
n - prefixLength - 1,
89+
key
90+
);
91+
} else {
92+
left = convertToPrunedBranch(left);
93+
}
94+
}
95+
if (!right.isExotic) {
96+
if (pp + '1' === key.slice(0, pp.length + 1)) {
97+
right = doGenerateMerkleProof(
98+
pp + '1',
99+
right.beginParse(),
100+
n - prefixLength - 1,
101+
key
102+
);
103+
} else {
104+
right = convertToPrunedBranch(right);
105+
}
106+
}
107+
108+
return beginCell()
109+
.storeSlice(sl)
110+
.storeRef(left)
111+
.storeRef(right)
112+
.endCell();
113+
}
114+
}
115+
116+
export function generateMerkleProof<K extends DictionaryKeyTypes, V>(
117+
dict: Dictionary<K, V>,
118+
key: K,
119+
keyObject: DictionaryKey<K>
120+
): Cell {
121+
const s = beginCell().storeDictDirect(dict).endCell().beginParse();
122+
return convertToMerkleProof(
123+
doGenerateMerkleProof(
124+
'',
125+
s,
126+
keyObject.bits,
127+
keyObject.serialize(key).toString(2).padStart(keyObject.bits, '0')
128+
)
129+
);
130+
}

src/dict/generateMerkleUpdate.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { beginCell } from '../boc/Builder';
2+
import { Cell } from '../boc/Cell';
3+
import { DictionaryKeyTypes, Dictionary, DictionaryKey } from './Dictionary';
4+
import { generateMerkleProof } from './generateMerkleProof';
5+
6+
function convertToMerkleUpdate(c1: Cell, c2: Cell): Cell {
7+
return new Cell({
8+
exotic: true,
9+
bits: beginCell()
10+
.storeUint(4, 8)
11+
.storeBuffer(c1.hash(0))
12+
.storeBuffer(c2.hash(0))
13+
.storeUint(c1.depth(0), 16)
14+
.storeUint(c2.depth(0), 16)
15+
.endCell()
16+
.beginParse()
17+
.loadBits(552),
18+
refs: [c1, c2],
19+
});
20+
}
21+
22+
export function generateMerkleUpdate<K extends DictionaryKeyTypes, V>(
23+
dict: Dictionary<K, V>,
24+
key: K,
25+
keyObject: DictionaryKey<K>,
26+
newValue: V
27+
): Cell {
28+
const oldProof = generateMerkleProof(dict, key, keyObject).refs[0];
29+
dict.set(key, newValue);
30+
const newProof = generateMerkleProof(dict, key, keyObject).refs[0];
31+
return convertToMerkleUpdate(oldProof, newProof);
32+
}

src/dict/utils/readUnaryLength.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Slice } from '../../boc/Slice';
2+
3+
export function readUnaryLength(slice: Slice) {
4+
let res = 0;
5+
while (slice.loadBit()) {
6+
res++;
7+
}
8+
return res;
9+
}

src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export { exoticMerkleProof } from './boc/cell/exoticMerkleProof';
3232
export { exoticMerkleUpdate } from './boc/cell/exoticMerkleUpdate';
3333
export { exoticPruned } from './boc/cell/exoticPruned';
3434

35+
// Merkle trees
36+
export { generateMerkleProof } from './dict/generateMerkleProof'
37+
export { generateMerkleUpdate } from './dict/generateMerkleUpdate'
38+
3539
// Tuples
3640
export { Tuple, TupleItem, TupleItemNull, TupleItemInt, TupleItemNaN, TupleItemCell, TupleItemSlice, TupleItemBuilder } from './tuple/tuple';
3741
export { parseTuple, serializeTuple } from './tuple/tuple';

0 commit comments

Comments
 (0)