diff --git a/lib/BaseTree.d.ts b/lib/BaseTree.d.ts index 6f5fbb4..fcf6a89 100644 --- a/lib/BaseTree.d.ts +++ b/lib/BaseTree.d.ts @@ -1,4 +1,4 @@ -import { Element, HashFunction, ProofPath } from './'; +import { Element, HashFunction, ProofPath, MultiProofPath } from './'; export declare class BaseTree { levels: number; protected _hashFn: HashFunction; @@ -37,6 +37,40 @@ export declare class BaseTree { * @returns {{pathElements: Object[], pathIndex: number[]}} An object containing adjacent elements and left-right index */ path(index: number): ProofPath; + /** + * Return the indices for the next layer in the multiPath calculation + * @param {number} indices A list of leaf indices + * @returns {number[]} the new list of indices + */ + static nextLayerMultiPathIndices(indices: number[]): number[]; + /** + * Get merkle path to a list of leaves + * @param {number} indices A list of leaf indices to generate path for + * @returns {{pathElements: Element[], leafIndices: number[]}} An object containing adjacent elements and leaves indices + */ + multiPath(indices: number[]): MultiProofPath; + /** + * Verifies a merkle proof + * @param {Element} root the root of the merkle tree + * @param {number} levels the number of levels of the tree + * @param {HashFunction} hashFn hash function + * @param {Element} leaf the leaf to be verified + * @param {Element[]} pathElements adjacent path elements + * @param {number[]} pathIndices left-right indices + * @returns {Boolean} whether the proof is valid for the given root + */ + static verifyProof(root: Element, levels: number, hashFn: HashFunction, leaf: Element, pathElements: Element[], pathIndices: number[]): boolean; + /** + * Verifies a merkle multiproof + * @param {Element} root the root of the merkle tree + * @param {number} levels the number of levels of the tree + * @param {HashFunction} hashFn hash function + * @param {Element[]} leaves the list of leaves to be verified + * @param {Element[]} pathElements multiproof path elements + * @param {number[]} leafIndices multiproof leaf indices + * @returns {Boolean} whether the proof is valid for the given root + */ + static verifyMultiProof(root: Element, levels: number, hashFn: HashFunction, leaves: Element[], pathElements: Element[], leafIndices: number[]): boolean; protected _buildZeros(): void; protected _processNodes(nodes: Element[], layerIndex: number): any[]; protected _processUpdate(index: number): void; diff --git a/lib/BaseTree.js b/lib/BaseTree.js index 5d4a33b..4499b3b 100644 --- a/lib/BaseTree.js +++ b/lib/BaseTree.js @@ -117,6 +117,126 @@ class BaseTree { pathRoot: this.root, }; } + /** + * Return the indices for the next layer in the multiPath calculation + * @param {number} indices A list of leaf indices + * @returns {number[]} the new list of indices + */ + static nextLayerMultiPathIndices(indices) { + const nextIndices = new Set(); + for (let i = 0; i < indices.length; i++) { + nextIndices.add(indices[i] >> 1); + } + return [...nextIndices]; + } + /** + * Get merkle path to a list of leaves + * @param {number} indices A list of leaf indices to generate path for + * @returns {{pathElements: Element[], leafIndices: number[]}} An object containing adjacent elements and leaves indices + */ + multiPath(indices) { + let pathElements = []; + let layerIndices = indices; + for (let level = 0; level < this.levels; level++) { + // find whether there is a neighbor idx that is not in layerIndices + const proofElements = layerIndices.reduce((elements, idx) => { + const leafIndex = idx ^ 1; + if (!layerIndices.includes(leafIndex)) { + if (leafIndex < this._layers[level].length) { + elements.push(this._layers[level][leafIndex]); + } + else { + elements.push(this._zeros[level]); + } + } + return elements; + }, []); + pathElements = pathElements.concat(proofElements); + layerIndices = BaseTree.nextLayerMultiPathIndices(layerIndices); + } + return { + pathElements, + leafIndices: indices, + pathRoot: this.root, + }; + } + /** + * Verifies a merkle proof + * @param {Element} root the root of the merkle tree + * @param {number} levels the number of levels of the tree + * @param {HashFunction} hashFn hash function + * @param {Element} leaf the leaf to be verified + * @param {Element[]} pathElements adjacent path elements + * @param {number[]} pathIndices left-right indices + * @returns {Boolean} whether the proof is valid for the given root + */ + static verifyProof(root, levels, hashFn, leaf, pathElements, pathIndices) { + const layerProofs = []; + for (let level = 0; level < levels; level++) { + let elem = level == 0 ? leaf : layerProofs[level - 1]; + if (pathIndices[level] == 0) { + layerProofs[level] = hashFn(elem, pathElements[level]); + } + else { + layerProofs[level] = hashFn(pathElements[level], elem); + } + } + return root === layerProofs[levels - 1]; + } + /** + * Verifies a merkle multiproof + * @param {Element} root the root of the merkle tree + * @param {number} levels the number of levels of the tree + * @param {HashFunction} hashFn hash function + * @param {Element[]} leaves the list of leaves to be verified + * @param {Element[]} pathElements multiproof path elements + * @param {number[]} leafIndices multiproof leaf indices + * @returns {Boolean} whether the proof is valid for the given root + */ + static verifyMultiProof(root, levels, hashFn, leaves, pathElements, leafIndices) { + let layerElements = leaves; + let layerIndices = leafIndices; + const proofElements = pathElements; + const layerProofs = []; + for (let level = 0; level < levels; level++) { + for (let i = 0; i < layerIndices.length; i++) { + let layerHash; + const elIndex = layerIndices[i]; + const leafIndex = elIndex ^ 1; + if (layerIndices.includes(leafIndex)) { + if (elIndex % 2 === 0) { + layerHash = hashFn(layerElements[0], layerElements[1]); + } + else { + layerHash = hashFn(layerElements[1], layerElements[0]); + } + layerElements.splice(0, 2); // remove 1st and 2nd element + i++; // skip next idx + layerProofs.push(layerHash); + } + else { + if (elIndex % 2 === 0) { + layerHash = hashFn(layerElements[0], proofElements[0]); + } + else { + layerHash = hashFn(proofElements[0], layerElements[0]); + } + layerElements.shift(); // remove 1st element + layerProofs.push(layerHash); + if (proofElements.shift() === undefined) { + break; + } + } + } + layerIndices = BaseTree.nextLayerMultiPathIndices(layerIndices); + layerElements = layerProofs; + if (proofElements.length == 0 && layerElements.length == 2) { + layerProofs[0] = hashFn(layerProofs[0], layerProofs[1]); + break; + } + } + return root === layerProofs[0]; + } _buildZeros() { this._zeros = [this.zeroElement]; for (let i = 1; i <= this.levels; i++) { diff --git a/lib/FixedMerkleTree.d.ts b/lib/FixedMerkleTree.d.ts index c255a18..c250297 100644 --- a/lib/FixedMerkleTree.d.ts +++ b/lib/FixedMerkleTree.d.ts @@ -1,4 +1,4 @@ -import { Element, HashFunction, MerkleTreeOptions, ProofPath, SerializedTreeState, TreeEdge, TreeSlice } from './'; +import { Element, HashFunction, MerkleTreeOptions, ProofPath, MultiProofPath, SerializedTreeState, TreeEdge, TreeSlice } from './'; import { BaseTree } from './BaseTree'; export default class MerkleTree extends BaseTree { constructor(levels: number, elements?: Element[], { hashFunction, zeroElement, }?: MerkleTreeOptions); @@ -10,6 +10,7 @@ export default class MerkleTree extends BaseTree { bulkInsert(elements: Element[]): void; indexOf(element: Element, comparator?: (arg0: T, arg1: T) => boolean): number; proof(element: Element): ProofPath; + multiProof(elements: Element[]): MultiProofPath; getTreeEdge(edgeIndex: number): TreeEdge; /** * 🪓 diff --git a/lib/FixedMerkleTree.js b/lib/FixedMerkleTree.js index 14c9de7..8ccc9b5 100644 --- a/lib/FixedMerkleTree.js +++ b/lib/FixedMerkleTree.js @@ -59,6 +59,13 @@ class MerkleTree extends BaseTree_1.BaseTree { const index = this.indexOf(element); return this.path(index); } + multiProof(elements) { + const indexes = []; + for (let i = 0; i < elements.length; i++) { + indexes.push(this.indexOf(elements[i])); + } + return this.multiPath(indexes); + } getTreeEdge(edgeIndex) { const edgeElement = this._layers[0][edgeIndex]; if (edgeElement === undefined) { diff --git a/lib/index.d.ts b/lib/index.d.ts index b1c2abc..06ca5f6 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -29,6 +29,11 @@ export declare type ProofPath = { pathPositions: number[]; pathRoot: Element; }; +export declare type MultiProofPath = { + pathElements: Element[]; + leafIndices: number[]; + pathRoot: Element; +}; export declare type TreeEdge = { edgeElement: Element; edgePath: ProofPath;