Skip to content

Add support to Merkle Multiproof #4

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
36 changes: 35 additions & 1 deletion lib/BaseTree.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Element, HashFunction, ProofPath } from './';
import { Element, HashFunction, ProofPath, MultiProofPath } from './';
export declare class BaseTree {
levels: number;
protected _hashFn: HashFunction<Element>;
Expand Down Expand Up @@ -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<Element>} 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<Element>, 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<Element>} 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<Element>, leaves: Element[], pathElements: Element[], leafIndices: number[]): boolean;
protected _buildZeros(): void;
protected _processNodes(nodes: Element[], layerIndex: number): any[];
protected _processUpdate(index: number): void;
Expand Down
120 changes: 120 additions & 0 deletions lib/BaseTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Element>} 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<Element>} 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++) {
Expand Down
3 changes: 2 additions & 1 deletion lib/FixedMerkleTree.d.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -10,6 +10,7 @@ export default class MerkleTree extends BaseTree {
bulkInsert(elements: Element[]): void;
indexOf(element: Element, comparator?: <T>(arg0: T, arg1: T) => boolean): number;
proof(element: Element): ProofPath;
multiProof(elements: Element[]): MultiProofPath;
getTreeEdge(edgeIndex: number): TreeEdge;
/**
* 🪓
Expand Down
7 changes: 7 additions & 0 deletions lib/FixedMerkleTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading