Skip to content

Commit

Permalink
Merge pull request #30 from akifoq/main
Browse files Browse the repository at this point in the history
More flexible merkle proof generation
  • Loading branch information
dvlkv authored May 2, 2024
2 parents 1c99f32 + cd92a5d commit 56f30d7
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 47 deletions.
10 changes: 10 additions & 0 deletions src/boc/cell/exoticMerkleProof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { BitReader } from "../BitReader";
import { BitString } from "../BitString";
import { Cell } from "../Cell";
import { beginCell } from '../Builder';

export function exoticMerkleProof(bits: BitString, refs: Cell[]) {
const reader = new BitReader(bits);
Expand Down Expand Up @@ -48,4 +49,13 @@ export function exoticMerkleProof(bits: BitString, refs: Cell[]) {
proofDepth,
proofHash
};
}

export function convertToMerkleProof(c: Cell): Cell {
return beginCell()
.storeUint(3, 8)
.storeBuffer(c.hash(0))
.storeUint(c.depth(0), 16)
.storeRef(c)
.endCell({ exotic: true });
}
18 changes: 13 additions & 5 deletions src/dict/Dictionary.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,22 @@ describe('Dictionary', () => {
d.set(4, 44);
d.set(5, 55);

const dictHash = beginCell().storeDictDirect(d).endCell().hash();
for (let k = 1; k <= 5; k++) {
const proof = d.generateMerkleProof(k);
const proof = d.generateMerkleProof([k]);
Cell.fromBoc(proof.toBoc());
expect(exoticMerkleProof(proof.bits, proof.refs).proofHash).toEqual(
Buffer.from(
'ee41b86bd71f8224ebd01848b4daf4cd46d3bfb3e119d8b865ce7c2802511de3',
'hex'
)
dictHash
);

// todo: parse the pruned dictionary and check the presence of the keys
}

for (let k = 1; k <= 3; k++) {
const proof = d.generateMerkleProof([k, k + 1, k + 2]);
Cell.fromBoc(proof.toBoc());
expect(exoticMerkleProof(proof.bits, proof.refs).proofHash).toEqual(
dictHash
);
}
});
Expand Down
21 changes: 18 additions & 3 deletions src/dict/Dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Cell } from "../boc/Cell";
import { Slice } from "../boc/Slice";
import { BitString } from "../boc/BitString";
import { Maybe } from "../utils/maybe";
import { generateMerkleProof } from "./generateMerkleProof";
import { generateMerkleProof, generateMerkleProofDirect } from "./generateMerkleProof";
import { generateMerkleUpdate } from "./generateMerkleUpdate";
import { parseDict } from "./parseDict";
import { serializeDict } from "./serializeDict";
Expand Down Expand Up @@ -392,8 +392,23 @@ export class Dictionary<K extends DictionaryKeyTypes, V> {
serializeDict(prepared, resolvedKey.bits, resolvedValue.serialize, builder);
}

generateMerkleProof(key: K): Cell {
return generateMerkleProof(this, key, this._key!)
/**
* Generate merkle proof for multiple keys in the dictionary
* @param keys an array of the keys
* @returns generated merkle proof cell
*/
generateMerkleProof(keys: K[]): Cell {
return generateMerkleProof(this, keys, this._key!)
}

/**
* Low level method for generating pruned dictionary directly.
* The result can be used as a part of a bigger merkle proof
* @param keys an array of the keys
* @returns cell that contains the pruned dictionary
*/
generateMerkleProofDirect(keys: K[]): Cell {
return generateMerkleProofDirect(this, keys, this._key!)
}

generateMerkleUpdate(key: K, newValue: V): Cell {
Expand Down
76 changes: 39 additions & 37 deletions src/dict/generateMerkleProof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Cell } from '../boc/Cell';
import { Slice } from '../boc/Slice';
import { DictionaryKeyTypes, Dictionary, DictionaryKey } from './Dictionary';
import { readUnaryLength } from './utils/readUnaryLength';
import { convertToMerkleProof } from '../boc/cell/exoticMerkleProof';

function convertToPrunedBranch(c: Cell): Cell {
return beginCell()
Expand All @@ -13,24 +14,20 @@ function convertToPrunedBranch(c: Cell): Cell {
.endCell({ exotic: true });
}

function convertToMerkleProof(c: Cell): Cell {
return beginCell()
.storeUint(3, 8)
.storeBuffer(c.hash(0))
.storeUint(c.depth(0), 16)
.storeRef(c)
.endCell({ exotic: true });
}

function doGenerateMerkleProof(
prefix: string,
slice: Slice,
n: number,
key: string
keys: string[]
): Cell {
// Reading label
const originalCell = slice.asCell();

if (keys.length == 0) {
// no keys to prove, prune the whole subdict
return convertToPrunedBranch(originalCell);
}

let lb0 = slice.loadBit() ? 1 : 0;
let prefixLength = 0;
let pp = prefix;
Expand Down Expand Up @@ -71,28 +68,26 @@ function doGenerateMerkleProof(
let right = sl.loadRef();
// NOTE: Left and right branches are implicitly contain prefixes '0' and '1'
if (!left.isExotic) {
if (pp + '0' === key.slice(0, pp.length + 1)) {
left = doGenerateMerkleProof(
pp + '0',
left.beginParse(),
n - prefixLength - 1,
key
);
} else {
left = convertToPrunedBranch(left);
}
const leftKeys = keys.filter((key) => {
return pp + '0' === key.slice(0, pp.length + 1);
});
left = doGenerateMerkleProof(
pp + '0',
left.beginParse(),
n - prefixLength - 1,
leftKeys
);
}
if (!right.isExotic) {
if (pp + '1' === key.slice(0, pp.length + 1)) {
right = doGenerateMerkleProof(
pp + '1',
right.beginParse(),
n - prefixLength - 1,
key
);
} else {
right = convertToPrunedBranch(right);
}
const rightKeys = keys.filter((key) => {
return pp + '1' === key.slice(0, pp.length + 1);
});
right = doGenerateMerkleProof(
pp + '1',
right.beginParse(),
n - prefixLength - 1,
rightKeys
);
}

return beginCell()
Expand All @@ -103,18 +98,25 @@ function doGenerateMerkleProof(
}
}

export function generateMerkleProof<K extends DictionaryKeyTypes, V>(
export function generateMerkleProofDirect<K extends DictionaryKeyTypes, V>(
dict: Dictionary<K, V>,
key: K,
keys: K[],
keyObject: DictionaryKey<K>
): Cell {
const s = beginCell().storeDictDirect(dict).endCell().beginParse();
return convertToMerkleProof(
doGenerateMerkleProof(
keys = keys.filter((key) => dict.has(key));
const s = beginCell().storeDictDirect(dict).asSlice();
return doGenerateMerkleProof(
'',
s,
keyObject.bits,
keyObject.serialize(key).toString(2).padStart(keyObject.bits, '0')
)
keys.map((key) => keyObject.serialize(key).toString(2).padStart(keyObject.bits, '0'))
);
}

export function generateMerkleProof<K extends DictionaryKeyTypes, V>(
dict: Dictionary<K, V>,
keys: K[],
keyObject: DictionaryKey<K>
): Cell {
return convertToMerkleProof(generateMerkleProofDirect(dict, keys, keyObject));
}
4 changes: 2 additions & 2 deletions src/dict/generateMerkleUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export function generateMerkleUpdate<K extends DictionaryKeyTypes, V>(
keyObject: DictionaryKey<K>,
newValue: V
): Cell {
const oldProof = generateMerkleProof(dict, key, keyObject).refs[0];
const oldProof = generateMerkleProof(dict, [key], keyObject).refs[0];
dict.set(key, newValue);
const newProof = generateMerkleProof(dict, key, keyObject).refs[0];
const newProof = generateMerkleProof(dict, [key], keyObject).refs[0];
return convertToMerkleUpdate(oldProof, newProof);
}

0 comments on commit 56f30d7

Please sign in to comment.