From 7618921124aab628e823fbf0bf61ca1d4b64fe12 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Thu, 16 Nov 2023 16:50:38 +0000 Subject: [PATCH 1/5] Initial --- yarn-project/circuits.js/package.json | 1 + yarn-project/circuits.js/src/abis/abis.ts | 15 ++++++-- yarn-project/merkle-tree/package.json | 1 - yarn-project/merkle-tree/src/index.ts | 1 + .../src/merkle_tree_root_calculator.test.ts | 29 +++++++++++++++ .../src/merkle_tree_root_calculator.ts | 37 +++++++++++++++++++ yarn-project/yarn.lock | 2 +- 7 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 yarn-project/merkle-tree/src/merkle_tree_root_calculator.test.ts create mode 100644 yarn-project/merkle-tree/src/merkle_tree_root_calculator.ts diff --git a/yarn-project/circuits.js/package.json b/yarn-project/circuits.js/package.json index 120d66964e5..ccf987111c7 100644 --- a/yarn-project/circuits.js/package.json +++ b/yarn-project/circuits.js/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "@aztec/foundation": "workspace:^", + "@aztec/merkle-tree": "workspace:^", "@msgpack/msgpack": "^3.0.0-beta2", "@noble/curves": "^1.0.0", "@types/lodash.camelcase": "^4.3.7", diff --git a/yarn-project/circuits.js/src/abis/abis.ts b/yarn-project/circuits.js/src/abis/abis.ts index 88911dd1c67..31e575a26d8 100644 --- a/yarn-project/circuits.js/src/abis/abis.ts +++ b/yarn-project/circuits.js/src/abis/abis.ts @@ -2,6 +2,7 @@ import { padArrayEnd } from '@aztec/foundation/collection'; import { keccak, pedersenHash } from '@aztec/foundation/crypto'; import { numToUInt32BE } from '@aztec/foundation/serialize'; import { IWasmModule } from '@aztec/foundation/wasm'; +import { MerkleTreeRootCalculator } from '@aztec/merkle-tree'; import { Buffer } from 'buffer'; import chunk from 'lodash.chunk'; @@ -11,6 +12,7 @@ import { CompleteAddress, ContractDeploymentData, FUNCTION_SELECTOR_NUM_BYTES, + FUNCTION_TREE_HEIGHT, Fr, FunctionData, FunctionLeafPreimage, @@ -23,7 +25,7 @@ import { TxContext, TxRequest, } from '../index.js'; -import { boolToBuffer, serializeBufferArrayToVector } from '../utils/serialize.js'; +import { boolToBuffer } from '../utils/serialize.js'; /** * Synchronously calls a wasm function. @@ -109,6 +111,13 @@ export function computeFunctionLeaf(fnLeaf: FunctionLeafPreimage): Fr { ); } +const functionTreeRootCalculator = new MerkleTreeRootCalculator( + FUNCTION_TREE_HEIGHT, + // The "zero leaf" of the function tree is the hash of 5 zero fields. + // TODO: Why can we not just use a zero field as the zero leaf? Complicates things perhaps unnecessarily? + pedersenHash(new Array(5).fill(Buffer.alloc(32))), +); + /** * Computes a function tree root from function leaves. * @param wasm - A module providing low-level wasm access. @@ -116,9 +125,7 @@ export function computeFunctionLeaf(fnLeaf: FunctionLeafPreimage): Fr { * @returns The function tree root. */ export function computeFunctionTreeRoot(wasm: IWasmModule, fnLeaves: Fr[]) { - const inputVector = serializeBufferArrayToVector(fnLeaves.map(fr => fr.toBuffer())); - const result = wasmSyncCall(wasm, 'abis__compute_function_tree_root', inputVector, 32); - return Fr.fromBuffer(result); + return Fr.fromBuffer(functionTreeRootCalculator.computeTreeRoot(fnLeaves.map(fr => fr.toBuffer()))); } /** diff --git a/yarn-project/merkle-tree/package.json b/yarn-project/merkle-tree/package.json index 6378561fc38..f35d957b9f4 100644 --- a/yarn-project/merkle-tree/package.json +++ b/yarn-project/merkle-tree/package.json @@ -32,7 +32,6 @@ "testTimeout": 15000 }, "dependencies": { - "@aztec/circuits.js": "workspace:^", "@aztec/foundation": "workspace:^", "@aztec/types": "workspace:^", "levelup": "^5.1.1", diff --git a/yarn-project/merkle-tree/src/index.ts b/yarn-project/merkle-tree/src/index.ts index de19e295bfd..80508404e50 100644 --- a/yarn-project/merkle-tree/src/index.ts +++ b/yarn-project/merkle-tree/src/index.ts @@ -6,6 +6,7 @@ export * from './pedersen.js'; export * from './sparse_tree/sparse_tree.js'; export * from './standard_indexed_tree/standard_indexed_tree.js'; export * from './standard_tree/standard_tree.js'; +export * from './merkle_tree_root_calculator.js'; export { INITIAL_LEAF } from './tree_base.js'; export { newTree } from './new_tree.js'; export { loadTree } from './load_tree.js'; diff --git a/yarn-project/merkle-tree/src/merkle_tree_root_calculator.test.ts b/yarn-project/merkle-tree/src/merkle_tree_root_calculator.test.ts new file mode 100644 index 00000000000..419d3492a89 --- /dev/null +++ b/yarn-project/merkle-tree/src/merkle_tree_root_calculator.test.ts @@ -0,0 +1,29 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { MerkleTreeRootCalculator } from './merkle_tree_root_calculator.js'; + +describe('merkle tree root calculator', () => { + it('should correctly handle no leaves', () => { + // Height of 3 is 8 leaves. + const calculator = new MerkleTreeRootCalculator(4); + const expected = calculator.computeTreeRoot(new Array(8).fill(new Fr(0)).map(fr => fr.toBuffer())); + expect(calculator.computeTreeRoot()).toEqual(expected); + }); + + it('should correctly leverage zero hashes', () => { + const calculator = new MerkleTreeRootCalculator(4); + const leaves = Array.from({ length: 4 }).map((_, i) => new Fr(i).toBuffer()); + const padded = [...leaves, ...new Array(4).fill(Buffer.alloc(32))]; + const expected = calculator.computeTreeRoot(padded); + expect(calculator.computeTreeRoot(leaves)).toEqual(expected); + }); + + it('should correctly handle non default zero leaf', () => { + const zeroLeaf = new Fr(666).toBuffer(); + const calculator = new MerkleTreeRootCalculator(4, zeroLeaf); + const leaves = Array.from({ length: 4 }).map((_, i) => new Fr(i).toBuffer()); + const padded = [...leaves, ...new Array(4).fill(zeroLeaf)]; + const expected = calculator.computeTreeRoot(padded); + expect(calculator.computeTreeRoot(leaves)).toEqual(expected); + }); +}); diff --git a/yarn-project/merkle-tree/src/merkle_tree_root_calculator.ts b/yarn-project/merkle-tree/src/merkle_tree_root_calculator.ts new file mode 100644 index 00000000000..d591bf99367 --- /dev/null +++ b/yarn-project/merkle-tree/src/merkle_tree_root_calculator.ts @@ -0,0 +1,37 @@ +import { pedersenHash } from '@aztec/foundation/crypto'; + +/** + * Calculates the root of a merkle tree. + */ +export class MerkleTreeRootCalculator { + private zeroHashes: Buffer[]; + + constructor(private height: number, zeroLeaf = Buffer.alloc(32)) { + this.zeroHashes = Array.from({ length: height }).reduce( + (acc: Buffer[], _, i) => [...acc, pedersenHash([acc[i], acc[i]])], + [zeroLeaf], + ); + } + + computeTreeRoot(leaves: Buffer[] = []) { + if (this.height > this.zeroHashes.length - 1) { + throw new Error('Height greater than computed zeroHashes.'); + } + + if (leaves.length === 0) { + return this.zeroHashes[this.zeroHashes.length - 1]; + } + + for (let i = 0; i < this.height; ++i) { + let j = 0; + for (; j < leaves.length / 2; ++j) { + const l = leaves[j * 2]; + const r = leaves[j * 2 + 1] || this.zeroHashes[i]; + leaves[j] = pedersenHash([l, r]); + } + leaves = leaves.slice(0, j); + } + + return leaves[0]; + } +} diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index ddc9fd7a35f..6e6812a7ef0 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -338,6 +338,7 @@ __metadata: resolution: "@aztec/circuits.js@workspace:circuits.js" dependencies: "@aztec/foundation": "workspace:^" + "@aztec/merkle-tree": "workspace:^" "@jest/globals": ^29.5.0 "@msgpack/msgpack": ^3.0.0-beta2 "@noble/curves": ^1.0.0 @@ -579,7 +580,6 @@ __metadata: version: 0.0.0-use.local resolution: "@aztec/merkle-tree@workspace:merkle-tree" dependencies: - "@aztec/circuits.js": "workspace:^" "@aztec/foundation": "workspace:^" "@aztec/types": "workspace:^" "@jest/globals": ^29.5.0 From 5909fc6c4167f09020d588130e5e71a952551dd6 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Thu, 16 Nov 2023 17:18:46 +0000 Subject: [PATCH 2/5] Circuits can't depend on merkle-tree, everything depends on it via types :/ --- yarn-project/circuits.js/src/abis/abis.test.ts | 2 ++ yarn-project/circuits.js/src/abis/abis.ts | 17 ++++++++--------- .../abis}/merkle_tree_root_calculator.test.ts | 0 .../src/abis}/merkle_tree_root_calculator.ts | 0 yarn-project/merkle-tree/src/index.ts | 1 - yarn-project/merkle-tree/tsconfig.json | 3 --- 6 files changed, 10 insertions(+), 13 deletions(-) rename yarn-project/{merkle-tree/src => circuits.js/src/abis}/merkle_tree_root_calculator.test.ts (100%) rename yarn-project/{merkle-tree/src => circuits.js/src/abis}/merkle_tree_root_calculator.ts (100%) diff --git a/yarn-project/circuits.js/src/abis/abis.test.ts b/yarn-project/circuits.js/src/abis/abis.test.ts index 79413a36c10..5ac6049553c 100644 --- a/yarn-project/circuits.js/src/abis/abis.test.ts +++ b/yarn-project/circuits.js/src/abis/abis.test.ts @@ -1,3 +1,5 @@ +import { Timer } from '@aztec/foundation/timer'; + import times from 'lodash.times'; import { diff --git a/yarn-project/circuits.js/src/abis/abis.ts b/yarn-project/circuits.js/src/abis/abis.ts index 31e575a26d8..19141b074b9 100644 --- a/yarn-project/circuits.js/src/abis/abis.ts +++ b/yarn-project/circuits.js/src/abis/abis.ts @@ -2,7 +2,6 @@ import { padArrayEnd } from '@aztec/foundation/collection'; import { keccak, pedersenHash } from '@aztec/foundation/crypto'; import { numToUInt32BE } from '@aztec/foundation/serialize'; import { IWasmModule } from '@aztec/foundation/wasm'; -import { MerkleTreeRootCalculator } from '@aztec/merkle-tree'; import { Buffer } from 'buffer'; import chunk from 'lodash.chunk'; @@ -25,7 +24,8 @@ import { TxContext, TxRequest, } from '../index.js'; -import { boolToBuffer } from '../utils/serialize.js'; +import { boolToBuffer, serializeBufferArrayToVector } from '../utils/serialize.js'; +import { MerkleTreeRootCalculator } from './merkle_tree_root_calculator.js'; /** * Synchronously calls a wasm function. @@ -111,12 +111,10 @@ export function computeFunctionLeaf(fnLeaf: FunctionLeafPreimage): Fr { ); } -const functionTreeRootCalculator = new MerkleTreeRootCalculator( - FUNCTION_TREE_HEIGHT, - // The "zero leaf" of the function tree is the hash of 5 zero fields. - // TODO: Why can we not just use a zero field as the zero leaf? Complicates things perhaps unnecessarily? - pedersenHash(new Array(5).fill(Buffer.alloc(32))), -); +// The "zero leaf" of the function tree is the hash of 5 zero fields. +// TODO: Why can we not just use a zero field as the zero leaf? Complicates things perhaps unnecessarily? +const functionTreeZeroLeaf = pedersenHash(new Array(5).fill(Buffer.alloc(32))); +const functionTreeRootCalculator = new MerkleTreeRootCalculator(FUNCTION_TREE_HEIGHT, functionTreeZeroLeaf); /** * Computes a function tree root from function leaves. @@ -125,7 +123,8 @@ const functionTreeRootCalculator = new MerkleTreeRootCalculator( * @returns The function tree root. */ export function computeFunctionTreeRoot(wasm: IWasmModule, fnLeaves: Fr[]) { - return Fr.fromBuffer(functionTreeRootCalculator.computeTreeRoot(fnLeaves.map(fr => fr.toBuffer()))); + const leaves = fnLeaves.map(fr => fr.toBuffer()); + return Fr.fromBuffer(functionTreeRootCalculator.computeTreeRoot(leaves)); } /** diff --git a/yarn-project/merkle-tree/src/merkle_tree_root_calculator.test.ts b/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.test.ts similarity index 100% rename from yarn-project/merkle-tree/src/merkle_tree_root_calculator.test.ts rename to yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.test.ts diff --git a/yarn-project/merkle-tree/src/merkle_tree_root_calculator.ts b/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.ts similarity index 100% rename from yarn-project/merkle-tree/src/merkle_tree_root_calculator.ts rename to yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.ts diff --git a/yarn-project/merkle-tree/src/index.ts b/yarn-project/merkle-tree/src/index.ts index 80508404e50..de19e295bfd 100644 --- a/yarn-project/merkle-tree/src/index.ts +++ b/yarn-project/merkle-tree/src/index.ts @@ -6,7 +6,6 @@ export * from './pedersen.js'; export * from './sparse_tree/sparse_tree.js'; export * from './standard_indexed_tree/standard_indexed_tree.js'; export * from './standard_tree/standard_tree.js'; -export * from './merkle_tree_root_calculator.js'; export { INITIAL_LEAF } from './tree_base.js'; export { newTree } from './new_tree.js'; export { loadTree } from './load_tree.js'; diff --git a/yarn-project/merkle-tree/tsconfig.json b/yarn-project/merkle-tree/tsconfig.json index 1820488d409..831130c7c84 100644 --- a/yarn-project/merkle-tree/tsconfig.json +++ b/yarn-project/merkle-tree/tsconfig.json @@ -6,9 +6,6 @@ "tsBuildInfoFile": ".tsbuildinfo" }, "references": [ - { - "path": "../circuits.js" - }, { "path": "../foundation" }, From b5ec805543b9e66d9fa4304c8be1ed3919b441b7 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Thu, 16 Nov 2023 17:20:14 +0000 Subject: [PATCH 3/5] Fix. --- yarn-project/circuits.js/package.json | 1 - yarn-project/circuits.js/src/abis/abis.test.ts | 2 -- yarn-project/yarn.lock | 1 - 3 files changed, 4 deletions(-) diff --git a/yarn-project/circuits.js/package.json b/yarn-project/circuits.js/package.json index ccf987111c7..120d66964e5 100644 --- a/yarn-project/circuits.js/package.json +++ b/yarn-project/circuits.js/package.json @@ -40,7 +40,6 @@ }, "dependencies": { "@aztec/foundation": "workspace:^", - "@aztec/merkle-tree": "workspace:^", "@msgpack/msgpack": "^3.0.0-beta2", "@noble/curves": "^1.0.0", "@types/lodash.camelcase": "^4.3.7", diff --git a/yarn-project/circuits.js/src/abis/abis.test.ts b/yarn-project/circuits.js/src/abis/abis.test.ts index 5ac6049553c..79413a36c10 100644 --- a/yarn-project/circuits.js/src/abis/abis.test.ts +++ b/yarn-project/circuits.js/src/abis/abis.test.ts @@ -1,5 +1,3 @@ -import { Timer } from '@aztec/foundation/timer'; - import times from 'lodash.times'; import { diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 6e6812a7ef0..463d5afc30d 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -338,7 +338,6 @@ __metadata: resolution: "@aztec/circuits.js@workspace:circuits.js" dependencies: "@aztec/foundation": "workspace:^" - "@aztec/merkle-tree": "workspace:^" "@jest/globals": ^29.5.0 "@msgpack/msgpack": ^3.0.0-beta2 "@noble/curves": ^1.0.0 From e5acea66ed6ad241de80f665ac3a686034d09bb2 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Thu, 16 Nov 2023 17:37:12 +0000 Subject: [PATCH 4/5] Fix. --- yarn-project/circuits.js/src/abis/abis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/circuits.js/src/abis/abis.ts b/yarn-project/circuits.js/src/abis/abis.ts index 19141b074b9..bd9740cc1a0 100644 --- a/yarn-project/circuits.js/src/abis/abis.ts +++ b/yarn-project/circuits.js/src/abis/abis.ts @@ -24,7 +24,7 @@ import { TxContext, TxRequest, } from '../index.js'; -import { boolToBuffer, serializeBufferArrayToVector } from '../utils/serialize.js'; +import { boolToBuffer } from '../utils/serialize.js'; import { MerkleTreeRootCalculator } from './merkle_tree_root_calculator.js'; /** From 463ba0aeccc8cb7c3506455138d1e3da8b54ce97 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Thu, 16 Nov 2023 18:02:22 +0000 Subject: [PATCH 5/5] Remove redundent height check. Use odd number of leaves for safety in test. --- .../src/abis/merkle_tree_root_calculator.test.ts | 12 +++++++----- .../src/abis/merkle_tree_root_calculator.ts | 4 ---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.test.ts b/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.test.ts index 419d3492a89..58966728949 100644 --- a/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.test.ts +++ b/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.test.ts @@ -12,17 +12,19 @@ describe('merkle tree root calculator', () => { it('should correctly leverage zero hashes', () => { const calculator = new MerkleTreeRootCalculator(4); - const leaves = Array.from({ length: 4 }).map((_, i) => new Fr(i).toBuffer()); - const padded = [...leaves, ...new Array(4).fill(Buffer.alloc(32))]; + const leaves = Array.from({ length: 5 }).map((_, i) => new Fr(i).toBuffer()); + const padded = [...leaves, ...new Array(3).fill(Buffer.alloc(32))]; const expected = calculator.computeTreeRoot(padded); - expect(calculator.computeTreeRoot(leaves)).toEqual(expected); + const result = calculator.computeTreeRoot(leaves); + expect(result).not.toBeUndefined(); + expect(result).toEqual(expected); }); it('should correctly handle non default zero leaf', () => { const zeroLeaf = new Fr(666).toBuffer(); const calculator = new MerkleTreeRootCalculator(4, zeroLeaf); - const leaves = Array.from({ length: 4 }).map((_, i) => new Fr(i).toBuffer()); - const padded = [...leaves, ...new Array(4).fill(zeroLeaf)]; + const leaves = Array.from({ length: 5 }).map((_, i) => new Fr(i).toBuffer()); + const padded = [...leaves, ...new Array(3).fill(zeroLeaf)]; const expected = calculator.computeTreeRoot(padded); expect(calculator.computeTreeRoot(leaves)).toEqual(expected); }); diff --git a/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.ts b/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.ts index d591bf99367..904eec35776 100644 --- a/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.ts +++ b/yarn-project/circuits.js/src/abis/merkle_tree_root_calculator.ts @@ -14,10 +14,6 @@ export class MerkleTreeRootCalculator { } computeTreeRoot(leaves: Buffer[] = []) { - if (this.height > this.zeroHashes.length - 1) { - throw new Error('Height greater than computed zeroHashes.'); - } - if (leaves.length === 0) { return this.zeroHashes[this.zeroHashes.length - 1]; }