Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function pedersenCompress(wasm: BarretenbergWasm, lhs: Uint8Array, rhs: U
// If not done already, precompute constants.
wasm.call('pedersen__init');
if (lhs.length !== 32 || rhs.length !== 32) {
throw new Error('lhs and rhs must be equal to 32 bytes');
throw new Error(`Pedersen lhs and rhs inputs must be 32 bytes (got ${lhs.length} and ${rhs.length} respectively)`);
}
wasm.writeMemory(0, lhs);
wasm.writeMemory(32, rhs);
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/circuits.js/src/structs/base_rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export class NullifierLeafPreimage {
toBuffer() {
return serializeToBuffer(this.leafValue, this.nextValue, this.nextIndex);
}

static empty() {
return new NullifierLeafPreimage(Fr.ZERO, Fr.ZERO, 0);
}
}

export class AppendOnlyTreeSnapshot {
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/circuits.js/src/structs/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ export class MembershipWitness<N extends number> {
.map(() => Fr.ZERO);
return new MembershipWitness<N>(pathSize, leafIndex, arr);
}

static fromBufferArray(leafIndex: number, siblingPath: Buffer[]) {
return new MembershipWitness(
siblingPath.length,
leafIndex,
siblingPath.map(x => Fr.fromBuffer(x)),
);
}
}

export class AggregationObject {
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/circuits.js/src/utils/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ export function toFriendlyJSON(obj: object): string {
return '0x' + Buffer.from(value.data).toString('hex');
} else if (typeof value === 'bigint') {
return value.toString();
} else if ((value as { toFriendlyJSON: () => string }).toFriendlyJSON) {
return value.toFriendlyJSON();
} else {
return value;
}
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/foundation/src/bigint-buffer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,7 @@ export function toBufferLE(num: bigint, width: number): Buffer {
*/
export function toBufferBE(num: bigint, width: number): Buffer {
const hex = num.toString(16);
return Buffer.from(hex.padStart(width * 2, '0').slice(0, width * 2), 'hex');
const buffer = Buffer.from(hex.padStart(width * 2, '0').slice(0, width * 2), 'hex');
if (buffer.length > width) throw new Error(`Number ${num.toString(16)} does not fit in ${width}`);
return buffer;
}
24 changes: 10 additions & 14 deletions yarn-project/merkle-tree/src/indexed_tree/indexed_tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ const createFromName = async (levelUp: levelup.LevelUp, hasher: Hasher, name: st
};

const createIndexedTreeLeaf = (value: number, nextIndex: number, nextValue: number) => {
return Buffer.concat([
toBufferBE(BigInt(value), 32),
toBufferBE(BigInt(nextIndex), 32),
toBufferBE(BigInt(nextValue), 32),
]);
return [toBufferBE(BigInt(value), 32), toBufferBE(BigInt(nextIndex), 32), toBufferBE(BigInt(nextValue), 32)];
};

const verifyCommittedState = async (
Expand Down Expand Up @@ -58,7 +54,7 @@ describe('IndexedMerkleTreeSpecific', () => {
* nextVal 0 0 0 0 0 0 0 0.
*/

const zeroTreeLeafHash = pedersen.hashToField(createIndexedTreeLeaf(0, 0, 0));
const zeroTreeLeafHash = pedersen.compressInputs(createIndexedTreeLeaf(0, 0, 0));
const level1ZeroHash = pedersen.compress(zeroTreeLeafHash, zeroTreeLeafHash);
const level2ZeroHash = pedersen.compress(level1ZeroHash, level1ZeroHash);
let root = pedersen.compress(level2ZeroHash, level2ZeroHash);
Expand All @@ -82,8 +78,8 @@ describe('IndexedMerkleTreeSpecific', () => {
* nextIdx 1 0 0 0 0 0 0 0
* nextVal 30 0 0 0 0 0 0 0.
*/
let index0Hash = pedersen.hashToField(createIndexedTreeLeaf(0, 1, 30));
let index1Hash = pedersen.hashToField(createIndexedTreeLeaf(30, 0, 0));
let index0Hash = pedersen.compressInputs(createIndexedTreeLeaf(0, 1, 30));
let index1Hash = pedersen.compressInputs(createIndexedTreeLeaf(30, 0, 0));
let e10 = pedersen.compress(index0Hash, index1Hash);
let e20 = pedersen.compress(e10, level1ZeroHash);
root = pedersen.compress(e20, level2ZeroHash);
Expand All @@ -106,8 +102,8 @@ describe('IndexedMerkleTreeSpecific', () => {
* nextIdx 2 0 1 0 0 0 0 0
* nextVal 10 0 30 0 0 0 0 0.
*/
index0Hash = pedersen.hashToField(createIndexedTreeLeaf(0, 2, 10));
let index2Hash = pedersen.hashToField(createIndexedTreeLeaf(10, 1, 30));
index0Hash = pedersen.compressInputs(createIndexedTreeLeaf(0, 2, 10));
let index2Hash = pedersen.compressInputs(createIndexedTreeLeaf(10, 1, 30));
e10 = pedersen.compress(index0Hash, index1Hash);
let e11 = pedersen.compress(index2Hash, zeroTreeLeafHash);
e20 = pedersen.compress(e10, e11);
Expand All @@ -132,8 +128,8 @@ describe('IndexedMerkleTreeSpecific', () => {
* nextVal 10 0 20 30 0 0 0 0.
*/
e10 = pedersen.compress(index0Hash, index1Hash);
index2Hash = pedersen.hashToField(createIndexedTreeLeaf(10, 3, 20));
const index3Hash = pedersen.hashToField(createIndexedTreeLeaf(20, 1, 30));
index2Hash = pedersen.compressInputs(createIndexedTreeLeaf(10, 3, 20));
const index3Hash = pedersen.compressInputs(createIndexedTreeLeaf(20, 1, 30));
e11 = pedersen.compress(index2Hash, index3Hash);
e20 = pedersen.compress(e10, e11);
root = pedersen.compress(e20, level2ZeroHash);
Expand All @@ -156,8 +152,8 @@ describe('IndexedMerkleTreeSpecific', () => {
* nextIdx 2 4 3 1 0 0 0 0
* nextVal 10 50 20 30 0 0 0 0.
*/
index1Hash = pedersen.hashToField(createIndexedTreeLeaf(30, 4, 50));
const index4Hash = pedersen.hashToField(createIndexedTreeLeaf(50, 0, 0));
index1Hash = pedersen.compressInputs(createIndexedTreeLeaf(30, 4, 50));
const index4Hash = pedersen.compressInputs(createIndexedTreeLeaf(50, 0, 0));
e10 = pedersen.compress(index0Hash, index1Hash);
e20 = pedersen.compress(e10, e11);
const e12 = pedersen.compress(index4Hash, zeroTreeLeafHash);
Expand Down
62 changes: 55 additions & 7 deletions yarn-project/merkle-tree/src/indexed_tree/indexed_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@ const encodeTreeValue = (leafData: LeafData) => {
return Buffer.concat([valueAsBuffer, indexAsBuffer, nextValueAsBuffer]);
};

// TODO: Check which version of hash we need to match the cpp implementation
Comment thread
spalladino marked this conversation as resolved.
const hashEncodedTreeValue = (leaf: LeafData, hasher: Hasher) => {
return hasher.hashToField(
Buffer.concat([leaf.value, leaf.nextIndex, leaf.nextValue].map(val => toBufferBE(val, 32))),
);
return hasher.compressInputs([leaf.value, leaf.nextIndex, leaf.nextValue].map(val => toBufferBE(val, 32)));
};

Expand Down Expand Up @@ -74,12 +70,19 @@ export class IndexedTree implements MerkleTree {
* @param hasher - A hasher used to compute hash paths.
* @param name - A name of the tree.
* @param depth - A depth of the tree.
* @param prefilledSize - {optional} A number of leaves that are prefilled with values.
* @returns A promise with the new Merkle tree.
*/
public static async new(db: LevelUp, hasher: Hasher, name: string, depth: number): Promise<IndexedTree> {
public static async new(
db: LevelUp,
hasher: Hasher,
name: string,
depth: number,
prefilledSize = 0,
): Promise<IndexedTree> {
const underlying = await StandardMerkleTree.new(db, hasher, name, depth, hashEncodedTreeValue(initialLeaf, hasher));
const tree = new IndexedTree(underlying, hasher, db);
await tree.init();
await tree.init(prefilledSize);
return tree;
}

Expand All @@ -97,6 +100,14 @@ export class IndexedTree implements MerkleTree {
return tree;
}

/**
* Returns an empty leaf of the tree.
* @returns An empty leaf.
*/
static initialLeaf(): LeafData {
return initialLeaf;
}

/**
* Returns the root of the tree.
* @returns The root of the tree.
Expand All @@ -105,6 +116,14 @@ export class IndexedTree implements MerkleTree {
return this.underlying.getRoot(includeUncommitted);
}

/**
* Returns the depth of the tree.
* @returns The depth of the tree.
*/
public getDepth(): number {
return this.underlying.getDepth();
}

/**
* Returns the number of leaves in the tree.
* @returns The number of leaves in the tree.
Expand Down Expand Up @@ -152,13 +171,38 @@ export class IndexedTree implements MerkleTree {
return await this.underlying.getSiblingPath(index, includeUncommitted);
}

/**
* Exposes the underlying tree's update leaf method
* @param leaf - The hash to set at the leaf
* @param index - The index of the element
*/
public async updateLeaf(leaf: LeafData, index: bigint): Promise<void> {
this.cachedLeaves[Number(index)] = leaf;
const encodedLeaf = hashEncodedTreeValue(leaf, this.hasher);
await this.underlying.updateLeaf(encodedLeaf, index);
}

/**
* Special case which will force append zero into the tree by increasing its size
*/
private appendZero(): void {
this.underlying.forceAppendEmptyLeaf();
}

/**
* Appends the given leaf to the tree.
* @param leaf - The leaf to append.
* @returns Empty promise.
*/
private async appendLeaf(leaf: Buffer): Promise<void> {
const newValue = toBigIntBE(leaf);

// Special case when appending zero
if (newValue === 0n) {
this.appendZero();
return;
}

const indexOfPrevious = this.findIndexOfPreviousValue(newValue, true);
const previousLeafCopy = this.getLatestLeafDataCopy(indexOfPrevious.index, true);
if (previousLeafCopy === undefined) {
Expand Down Expand Up @@ -228,9 +272,13 @@ export class IndexedTree implements MerkleTree {
/**
* Saves the initial leaf to this object and saves it to a database.
*/
private async init() {
private async init(initialSize = 1) {
this.leaves.push(initialLeaf);
await this.underlying.appendLeaves([hashEncodedTreeValue(initialLeaf, this.hasher)]);

for (let i = 1; i < initialSize; i++) {
await this.appendLeaf(Buffer.from([i]));
Comment thread
spalladino marked this conversation as resolved.
}
await this.commit();
}

Expand Down
11 changes: 11 additions & 0 deletions yarn-project/merkle-tree/src/merkle_tree.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LeafData } from './index.js';
import { SiblingPath } from './sibling_path/sibling_path.js';

/**
Expand Down Expand Up @@ -35,6 +36,16 @@ export interface MerkleTree extends SiblingPathSource {
* Commit pending updates to the tree
*/
commit(): Promise<void>;
/**
* Updates a leaf at a given index in the tree
* @param leaf The leaf value to be updated
* @param index The leaf to be updated
*/
updateLeaf(leaf: Buffer | LeafData, index: bigint): Promise<void>;
Comment thread
Maddiaa0 marked this conversation as resolved.
/**
* Returns the depth of the tree
*/
getDepth(): number;
Comment thread
spalladino marked this conversation as resolved.
/**
* Rollback pending update to the tree
*/
Expand Down
7 changes: 7 additions & 0 deletions yarn-project/merkle-tree/src/standard_tree/standard_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ export class StandardMerkleTree implements MerkleTree {
}
}

/**
* Force increase the size of the tree
*/
public forceAppendEmptyLeaf() {
this.cachedSize = (this.cachedSize ?? this.size) + 1n;
}

/**
* Commits the changes to the database.
* @returns Empty promise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe('sequencer/circuit_block_builder', () => {

expect(l2Block.number).toEqual(blockNumber);
expect(proof).toEqual(emptyProof);
});
}, 20000);

it('builds an L2 block with empty txs using wasm circuits', async () => {
const simulator = new WasmCircuitSimulator(wasm);
Expand Down Expand Up @@ -197,7 +197,7 @@ describe('sequencer/circuit_block_builder', () => {
expect(contractTreeAfter.root).not.toEqual(contractTreeBefore.root);
expect(contractTreeAfter.root).toEqual(await expectsDb.getTreeInfo(MerkleTreeId.CONTRACT_TREE).then(t => t.root));
expect(contractTreeAfter.size).toEqual(4n);
});
}, 10000);
});

// Test subject class that exposes internal functions for testing
Expand Down
Loading