Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Experimental Merkle Tree Implementation #343

Merged
merged 52 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
67997d5
feat: add merkle_tree file
ymekuria Aug 8, 2022
2204d63
feature: merkletree class boilerplate
ymekuria Aug 8, 2022
d204778
feat: declare node and zeroes
ymekuria Aug 8, 2022
dd036a2
feat: merkletree constructor
ymekuria Aug 9, 2022
c23d0fd
add getnode method
ymekuria Aug 9, 2022
831dedd
feat: getroot method
ymekuria Aug 9, 2022
de9b382
feat: add setnode merkle tree method
ymekuria Aug 9, 2022
06cb4dc
feat: add set leaf method
ymekuria Aug 9, 2022
6315b1a
feat: getWitness method
ymekuria Aug 9, 2022
78140a7
feat: validate method
ymekuria Aug 9, 2022
7903276
feat: add fill method
ymekuria Aug 10, 2022
fb2f8cc
chore: create merkle_tree test file
ymekuria Aug 10, 2022
74a037d
feat: add beforeall and afterall hooks
ymekuria Aug 10, 2022
b971174
test: root of empty tree size 1
ymekuria Aug 10, 2022
2fa6a65
test: root is correct
ymekuria Aug 10, 2022
447b06f
test: builds correct tree
ymekuria Aug 10, 2022
a65108d
test: tree height of 128
ymekuria Aug 10, 2022
d5f065f
test: tree height of 256
ymekuria Aug 10, 2022
2fa1777
feat: add merkletree to experimental namespace
ymekuria Aug 10, 2022
04ca2fc
fix: update tests with experimental namespace
ymekuria Aug 11, 2022
00a0320
refactor: add comments
ymekuria Aug 11, 2022
83e6693
make CircuitValue support `super()` w/o arguments
mitschabaude Aug 11, 2022
bfcea03
refactor sudoku example to confirm CircuitValue changes are non-breaking
mitschabaude Aug 11, 2022
7a44142
feat: create merkle witness class
ymekuria Aug 11, 2022
605c5f7
feat: addd constructor
ymekuria Aug 11, 2022
1a0357f
feat: calculateroot merklewitness method
ymekuria Aug 11, 2022
aa0eaa4
feat: calculateindex method
ymekuria Aug 11, 2022
4012fc1
feat: add merklewitness to experimental namespace
ymekuria Aug 11, 2022
db313f2
update changelog
mitschabaude Aug 11, 2022
19322e3
move exports to top
mitschabaude Aug 11, 2022
b1d882d
Merge branch 'main' into feature/merkle-tree
mitschabaude Aug 11, 2022
8b0c939
temp: add merkle tree example
Trivo25 Aug 11, 2022
3b58850
Merge branch 'feature/merkle-tree' of https://github.com/o1-labs/snar…
Trivo25 Aug 11, 2022
dfe1ee0
rm debug
Trivo25 Aug 11, 2022
c7ab3f0
tweak namespace to work with classes
mitschabaude Aug 11, 2022
ee54e61
code golf
mitschabaude Aug 11, 2022
b8fb1c1
add sceptical comments
mitschabaude Aug 11, 2022
f143a7d
Merge branch 'release/0.5' into feature/merkle-tree
mitschabaude Aug 11, 2022
7919125
fix MerkleWitness constructor
mitschabaude Aug 11, 2022
6006431
add leafCount getter
Trivo25 Aug 11, 2022
fbf272a
add range check
Trivo25 Aug 11, 2022
b7f598d
Merge branch 'feature/merkle-tree' of https://github.com/o1-labs/snar…
Trivo25 Aug 11, 2022
b67f59d
fix CircuitValue.toJSON
mitschabaude Aug 11, 2022
f5562c5
Merge branch 'feature/merkle-tree' of https://github.com/o1-labs/snar…
mitschabaude Aug 11, 2022
076449a
tweak leafCount
mitschabaude Aug 11, 2022
f83b7dd
finish example
Trivo25 Aug 11, 2022
83d0f66
dynamic merkle witness
mitschabaude Aug 11, 2022
48ed1f4
Merge branch 'feature/merkle-tree' of https://github.com/o1-labs/snar…
mitschabaude Aug 11, 2022
6f02581
fix
mitschabaude Aug 11, 2022
973d096
add test for MerkleWitness
mitschabaude Aug 11, 2022
4649c5b
make example work
mitschabaude Aug 11, 2022
eaaa83e
make example create proof, clean up
mitschabaude Aug 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
} from './lib/circuit_value';
import { jsLayout, asFieldsAndAux } from './snarky/types';
import { packToFields } from './lib/hash';
import { MerkleTree, MerkleWitness } from './lib/merkle_tree';
export { Experimental };

/**
Expand All @@ -76,6 +77,8 @@ const Experimental = {
jsLayout,
asFieldsAndAux,
packToFields,
MerkleTree,
MerkleWitness,
};
namespace Experimental {
export type AsFieldsAndAux<T, TJson> = AsFieldsAndAux_<T, TJson>;
Expand Down
61 changes: 61 additions & 0 deletions src/lib/merkle_tree.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
isReady,
shutdown,
Poseidon,
Field,
Experimental,
} from '../../dist/server';

describe('Merkle Tree', () => {
beforeAll(async () => {
await isReady;
});
afterAll(async () => {
setTimeout(shutdown, 0);
});

it('root of empty tree of size 1', () => {
const tree = new Experimental.MerkleTree(1);
expect(tree.getRoot().toString()).toEqual(Field(0).toString());
});

it('root is correct', () => {
const tree = new Experimental.MerkleTree(2);
tree.setLeaf(0n, Field(1));
tree.setLeaf(1n, Field(2));
expect(tree.getRoot().toString()).toEqual(
Poseidon.hash([Field(1), Field(2)]).toString()
);
});

it('builds correct tree', () => {
const tree = new Experimental.MerkleTree(4);
tree.setLeaf(0n, Field(1));
tree.setLeaf(1n, Field(2));
tree.setLeaf(2n, Field(3));
expect(tree.validate(0n)).toBe(true);
expect(tree.validate(1n)).toBe(true);
expect(tree.validate(2n)).toBe(true);
expect(tree.validate(3n)).toBe(true);
});

it('tree of height 128', () => {
const tree = new Experimental.MerkleTree(128);

const index = 2n ** 64n;
expect(tree.validate(index)).toBe(true);

tree.setLeaf(index, Field(1));
expect(tree.validate(index)).toBe(true);
});

it('tree of height 256', () => {
const tree = new Experimental.MerkleTree(256);

const index = 2n ** 128n;
expect(tree.validate(index)).toBe(true);

tree.setLeaf(index, Field(1));
expect(tree.validate(index)).toBe(true);
});
});
115 changes: 115 additions & 0 deletions src/lib/merkle_tree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Circuit, CircuitValue, arrayProp } from './circuit_value';
import { Poseidon } from './hash';
import { Bool, Field } from './core';

export type Witness = {
isLeft: boolean;
sibling: Field;
}[];

/**
* Levels are indexed from leafs (level 0) to root (level N - 1).
*/
export class MerkleTree {
private nodes: Record<number, Record<string, Field>> = {};
private zeroes: Field[];

constructor(public readonly height: number) {
this.zeroes = [Field(0)];
for (let i = 1; i < height; i++) {
this.zeroes.push(Poseidon.hash([this.zeroes[i - 1], this.zeroes[i - 1]]));
}
}

getNode(level: number, index: bigint): Field {
return this.nodes[level]?.[index.toString()] ?? this.zeroes[level];
}

getRoot(): Field {
return this.getNode(this.height - 1, 0n);
}

private setNode(level: number, index: bigint, value: Field) {
(this.nodes[level] ??= {})[index.toString()] = value;
}

setLeaf(index: bigint, leaf: Field) {
this.setNode(0, index, leaf);
let currIndex = index;
for (let level = 1; level < this.height; level++) {
currIndex = 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]));
}
}

getWitness(index: bigint): Witness {
const witness = [];
for (let level = 0; level < this.height - 1; level++) {
const isLeft = index % 2n === 0n;
witness.push({
isLeft,
sibling: this.getNode(level, isLeft ? index + 1n : index - 1n),
});
index = index / 2n;
}
return witness;
}

validate(index: bigint): boolean {
const path = this.getWitness(index);
let hash = this.getNode(0, index);
for (const node of path) {
hash = Poseidon.hash(
node.isLeft ? [hash, node.sibling] : [node.sibling, hash]
);
}

return hash.toString() === this.getRoot().toString();
}

fill(leaves: Field[]) {
leaves.forEach((value, index) => {
this.setLeaf(BigInt(index), value);
});
}
}

export class MerkleWitness extends CircuitValue {
@arrayProp(Field, 255) path: Field[];
mitschabaude marked this conversation as resolved.
Show resolved Hide resolved
@arrayProp(Bool, 255) isLeft: Bool[];

constructor(witness: Witness) {
super(witness);

this.path = witness.map((item) => item.sibling);
this.isLeft = witness.map((item) => Bool(item.isLeft));
}

calculateRoot(leaf: Field): Field {
let hash = leaf;

for (let i = 1; i < 256; ++i) {
const left = Circuit.if(this.isLeft[i - 1], hash, this.path[i - 1]);
const right = Circuit.if(this.isLeft[i - 1], this.path[i - 1], hash);
hash = Poseidon.hash([left, right]);
}

return hash;
}

calculateIndex(): Field {
let powerOfTwo = Field(1);
let index = Field(0);

for (let i = 1; i < 256; ++i) {
index = Circuit.if(this.isLeft[i - 1], index, index.add(powerOfTwo));
mitschabaude marked this conversation as resolved.
Show resolved Hide resolved
powerOfTwo = powerOfTwo.mul(2);
}

return index;
}
}