diff --git a/.gitignore b/.gitignore index f55f809..a23c62a 100755 --- a/.gitignore +++ b/.gitignore @@ -109,10 +109,10 @@ example.js # Build directories # ##################### build -#dist +dist docs # Files to ignore # ##################### package-lock.json -/merkletree.js +merkletree.js diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 072cf7b..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,450 +0,0 @@ -/// -interface Options { - /** If set to `true`, an odd node will be duplicated and combined to make a pair to generate the layer hash. */ - duplicateOdd?: boolean; - /** If set to `true`, the leaves will hashed using the set hashing algorithms. */ - hashLeaves?: boolean; - /** If set to `true`, constructs the Merkle Tree using the [Bitcoin Merkle Tree implementation](http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html). Enable it when you need to replicate Bitcoin constructed Merkle Trees. In Bitcoin Merkle Trees, single nodes are combined with themselves, and each output hash is hashed again. */ - isBitcoinTree?: boolean; - /** If set to `true`, the leaves will be sorted. */ - sortLeaves?: boolean; - /** If set to `true`, the hashing pairs will be sorted. */ - sortPairs?: boolean; - /** If set to `true`, the leaves and hashing pairs will be sorted. */ - sort?: boolean; -} -/** - * Class reprensenting a Merkle Tree - * @namespace MerkleTree - */ -export declare class MerkleTree { - private duplicateOdd; - private hashAlgo; - private hashLeaves; - private isBitcoinTree; - private leaves; - private layers; - private sortLeaves; - private sortPairs; - private sort; - /** - * @desc Constructs a Merkle Tree. - * All nodes and leaves are stored as Buffers. - * Lonely leaf nodes are promoted to the next level up without being hashed again. - * @param {Buffer[]} leaves - Array of hashed leaves. Each leaf must be a Buffer. - * @param {Function} hashAlgorithm - Algorithm used for hashing leaves and nodes - * @param {Object} options - Additional options - * @example - *```js - *const MerkleTree = require('merkletreejs') - *const crypto = require('crypto') - * - *function sha256(data) { - * // returns Buffer - * return crypto.createHash('sha256').update(data).digest() - *} - * - *const leaves = ['a', 'b', 'c'].map(x => keccak(x)) - * - *const tree = new MerkleTree(leaves, sha256) - *``` - */ - constructor(leaves: any[], hashAlgorithm?: any, options?: Options); - private _createHashes; - /** - * getLeaves - * @desc Returns array of leaves of Merkle Tree. - * @return {Buffer[]} - * @example - *```js - *const leaves = tree.getLeaves() - *``` - */ - getLeaves(values?: any[]): Buffer[]; - /** - * getHexLeaves - * @desc Returns array of leaves of Merkle Tree as hex strings. - * @return {String[]} - * @example - *```js - *const leaves = tree.getHexLeaves() - *``` - */ - getHexLeaves(): string[]; - /** - * getLayers - * @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root. - * @return {Buffer[]} - * @example - *```js - *const layers = tree.getLayers() - *``` - */ - getLayers(): Buffer[]; - /** - * getHexLayers - * @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root as hex strings. - * @return {String[]} - * @example - *```js - *const layers = tree.getHexLayers() - *``` - */ - getHexLayers(): string[]; - /** - * getLayersFlat - * @desc Returns single flat array of all layers of Merkle Tree, including leaves and root. - * @return {Buffer[]} - * @example - *```js - *const layers = tree.getLayersFlat() - *``` - */ - getLayersFlat(): Buffer[]; - /** - * getHexLayersFlat - * @desc Returns single flat array of all layers of Merkle Tree, including leaves and root as hex string. - * @return {String[]} - * @example - *```js - *const layers = tree.getHexLayersFlat() - *``` - */ - getHexLayersFlat(): string[]; - /** - * getRoot - * @desc Returns the Merkle root hash as a Buffer. - * @return {Buffer} - * @example - *```js - *const root = tree.getRoot() - *``` - */ - getRoot(): Buffer; - /** - * getHexRoot - * @desc Returns the Merkle root hash as a hex string. - * @return {String} - * @example - *```js - *const root = tree.getHexRoot() - *``` - */ - getHexRoot(): string; - /** - * getProof - * @desc Returns the proof for a target leaf. - * @param {Buffer} leaf - Target leaf - * @param {Number} [index] - Target leaf index in leaves array. - * Use if there are leaves containing duplicate data in order to distinguish it. - * @return {Object[]} - Array of objects containing a position property of type string - * with values of 'left' or 'right' and a data property of type Buffer. - * @example - * ```js - *const proof = tree.getProof(leaves[2]) - *``` - * - * @example - *```js - *const leaves = ['a', 'b', 'a'].map(x => keccak(x)) - *const tree = new MerkleTree(leaves, keccak) - *const proof = tree.getProof(leaves[2], 2) - *``` - */ - getProof(leaf: Buffer, index?: number): any[]; - /** - * getHexProof - * @desc Returns the proof for a target leaf as hex strings. - * @param {Buffer} leaf - Target leaf - * @param {Number} [index] - Target leaf index in leaves array. - * Use if there are leaves containing duplicate data in order to distinguish it. - * @return {String[]} - Proof array as hex strings. - * @example - * ```js - *const proof = tree.getHexProof(leaves[2]) - *``` - */ - getHexProof(leaf: Buffer, index?: number): string[]; - /** - * getProofIndices - * @desc Returns the proof indices for given tree indices. - * @param {Number[]} treeIndices - Tree indices - * @param {Number} depth - Tree depth; number of layers. - * @return {Number[]} - Proof indices - * @example - * ```js - *const proofIndices = tree.getProofIndices([2,5,6], 4) - *console.log(proofIndices) // [ 23, 20, 19, 8, 3 ] - *``` - */ - getProofIndices(treeIndices: number[], depth: number): number[]; - /** - * getMultiProof - * @desc Returns the multiproof for given tree indices. - * @param {Number[]} indices - Tree indices. - * @return {Buffer[]} - Multiproofs - * @example - * ```js - *const indices = [2, 5, 6] - *const proof = tree.getMultiProof(indices) - *``` - */ - getMultiProof(tree?: any[], indices?: any[]): Buffer[]; - /** - * getHexMultiProof - * @desc Returns the multiproof for given tree indices as hex strings. - * @param {Number[]} indices - Tree indices. - * @return {String[]} - Multiproofs as hex strings. - * @example - * ```js - *const indices = [2, 5, 6] - *const proof = tree.getHexMultiProof(indices) - *``` - */ - getHexMultiProof(tree: Buffer[], indices: number[]): string[]; - /** - * getProofFlags - * @desc Returns list of booleans where proofs should be used instead of hashing. - * Proof flags are used in the Solidity multiproof verifiers. - * @param {Buffer[]} leaves - * @param {Buffer[]} proofs - * @return {Boolean[]} - Boolean flags - * @example - * ```js - *const indices = [2, 5, 6] - *const proof = tree.getMultiProof(indices) - *const proofFlags = tree.getProofFlags(leaves, proof) - *``` - */ - getProofFlags(leaves: Buffer[], proofs: Buffer[]): boolean[]; - /** - * verify - * @desc Returns true if the proof path (array of hashes) can connect the target node - * to the Merkle root. - * @param {Object[]} proof - Array of proof objects that should connect - * target node to Merkle root. - * @param {Buffer} targetNode - Target node Buffer - * @param {Buffer} root - Merkle root Buffer - * @return {Boolean} - * @example - *```js - *const root = tree.getRoot() - *const proof = tree.getProof(leaves[2]) - *const verified = tree.verify(proof, leaves[2], root) - *``` - */ - verify(proof: any[], targetNode: Buffer, root: Buffer): boolean; - /** - * verifyMultiProof - * @desc Returns true if the multiproofs can connect the leaves to the Merkle root. - * @param {Buffer} root - Merkle tree root - * @param {Number[]} indices - Leave indices - * @param {Buffer[]} leaves - Leaf values at indices. - * @param {Number} depth - Tree depth - * @param {Buffer[]} proof - Multiproofs given indices - * @return {Boolean} - * @example - *```js - *const root = tree.getRoot() - *const treeFlat = tree.getLayersFlat() - *const depth = tree.getDepth() - *const indices = [2, 5, 6] - *const proofLeaves = indices.map(i => leaves[i]) - *const proof = tree.getMultiProof(treeFlat, indices) - *const verified = tree.verifyMultiProof(root, indices, proofLeaves, depth, proof) - *``` - */ - verifyMultiProof(root: Buffer, indices: number[], leaves: Buffer[], depth: number, proof: Buffer[]): boolean; - /** - * getDepth - * @desc Returns the tree depth (number of layers) - * @return {Number} - * @example - *```js - *const depth = tree.getDepth() - *``` - */ - getDepth(): number; - /** - * getLayersAsObject - * @desc Returns the layers as nested objects instead of an array. - * @example - *```js - *const layersObj = tree.getLayersAsObject() - *``` - */ - getLayersAsObject(): any; - /** - * print - * @desc Prints out a visual representation of the merkle tree. - * @example - *```js - *tree.print() - *``` - */ - print(): void; - /** - * toTreeString - * @desc Returns a visual representation of the merkle tree as a string. - * @return {String} - * @example - *```js - *console.log(tree.toTreeString()) - *``` - */ - private _toTreeString; - /** - * toString - * @desc Returns a visual representation of the merkle tree as a string. - * @example - *```js - *console.log(tree.toString()) - *``` - */ - toString(): string; - /** - * getMultiProof - * @desc Returns the multiproof for given tree indices. - * @param {Buffer[]} tree - Tree as a flat array. - * @param {Number[]} indices - Tree indices. - * @return {Buffer[]} - Multiproofs - * - *@example - * ```js - *const flatTree = tree.getLayersFlat() - *const indices = [2, 5, 6] - *const proof = MerkleTree.getMultiProof(flatTree, indices) - *``` - */ - static getMultiProof(tree: Buffer[], indices: number[]): Buffer[]; - /** - * getPairNode - * @desc Returns the node at the index for given layer. - * @param {Buffer[]} layer - Tree layer - * @param {Number} index - Index at layer. - * @return {Buffer} - Node - * - *@example - * ```js - *const node = tree.getPairNode(layer, index) - *``` - */ - private _getPairNode; - /** - * bufferIndexOf - * @desc Returns the first index of which given buffer is found in array. - * @param {Buffer[]} haystack - Array of buffers. - * @param {Buffer} needle - Buffer to find. - * @return {Number} - Index number - * - * @example - * ```js - *const index = tree.bufferIndexOf(haystack, needle) - *``` - */ - private _bufferIndexOf; - /** - * bufferify - * @desc Returns a buffer type for the given value. - * @param {String|Number|Object|Buffer} value - * @return {Buffer} - * - * @example - * ```js - *const buf = MerkleTree.bufferify('0x1234') - *``` - */ - static bufferify(value: any): Buffer; - /** - * isHexString - * @desc Returns true if value is a hex string. - * @param {String} value - * @return {Boolean} - * - * @example - * ```js - *console.log(MerkleTree.isHexString('0x1234')) - *``` - */ - static isHexString(v: string): boolean; - /** - * print - * @desc Prints out a visual representation of the given merkle tree. - * @param {Object} tree - Merkle tree instance. - * @return {String} - * @example - *```js - *MerkleTree.print(tree) - *``` - */ - static print(tree: any): void; - /** - * bufferToHex - * @desc Returns a hex string with 0x prefix for given buffer. - * @param {Buffer} value - * @return {String} - * @example - *```js - *const hexStr = tree.bufferToHex(Buffer.from('A')) - *``` - */ - private _bufferToHex; - /** - * bufferify - * @desc Returns a buffer type for the given value. - * @param {String|Number|Object|Buffer} value - * @return {Buffer} - * - * @example - * ```js - *const buf = MerkleTree.bufferify('0x1234') - *``` - */ - private _bufferify; - /** - * bufferifyFn - * @desc Returns a function that will bufferify the return value. - * @param {Function} - * @return {Function} - * - * @example - * ```js - *const fn = tree.bufferifyFn((value) => sha256(value)) - *``` - */ - private _bufferifyFn; - /** - * isHexString - * @desc Returns true if value is a hex string. - * @param {String} value - * @return {Boolean} - * - * @example - * ```js - *console.log(MerkleTree.isHexString('0x1234')) - *``` - */ - private _isHexString; - /** - * log2 - * @desc Returns the log2 of number. - * @param {Number} value - * @return {Number} - */ - private _log2; - /** - * zip - * @desc Returns true if value is a hex string. - * @param {String[]|Number[]|Buffer[]} a - first array - * @param {String[]|Number[]|Buffer[]} b - second array - * @return {String[][]|Number[][]|Buffer[][]} - * - * @example - * ```js - *const zipped = tree.zip(['a', 'b'],['A', 'B']) - *console.log(zipped) // [ [ 'a', 'A' ], [ 'b', 'B' ] ] - *``` - */ - private _zip; -} -export default MerkleTree; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index b9a070c..0000000 --- a/dist/index.js +++ /dev/null @@ -1,848 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.MerkleTree = void 0; -const buffer_reverse_1 = __importDefault(require("buffer-reverse")); -const crypto_js_1 = __importDefault(require("crypto-js")); -const sha256_1 = __importDefault(require("crypto-js/sha256")); -const treeify_1 = __importDefault(require("treeify")); -/** - * Class reprensenting a Merkle Tree - * @namespace MerkleTree - */ -class MerkleTree { - /** - * @desc Constructs a Merkle Tree. - * All nodes and leaves are stored as Buffers. - * Lonely leaf nodes are promoted to the next level up without being hashed again. - * @param {Buffer[]} leaves - Array of hashed leaves. Each leaf must be a Buffer. - * @param {Function} hashAlgorithm - Algorithm used for hashing leaves and nodes - * @param {Object} options - Additional options - * @example - *```js - *const MerkleTree = require('merkletreejs') - *const crypto = require('crypto') - * - *function sha256(data) { - * // returns Buffer - * return crypto.createHash('sha256').update(data).digest() - *} - * - *const leaves = ['a', 'b', 'c'].map(x => keccak(x)) - * - *const tree = new MerkleTree(leaves, sha256) - *``` - */ - constructor(leaves, hashAlgorithm = sha256_1.default, options = {}) { - this.isBitcoinTree = !!options.isBitcoinTree; - this.hashLeaves = !!options.hashLeaves; - this.sortLeaves = !!options.sortLeaves; - this.sortPairs = !!options.sortPairs; - this.sort = !!options.sort; - if (this.sort) { - this.sortLeaves = true; - this.sortPairs = true; - } - this.duplicateOdd = !!options.duplicateOdd; - this.hashAlgo = this._bufferifyFn(hashAlgorithm); - if (this.hashLeaves) { - leaves = leaves.map(this.hashAlgo); - } - this.leaves = leaves.map(this._bufferify); - if (this.sortLeaves) { - this.leaves = this.leaves.sort(Buffer.compare); - } - this.layers = [this.leaves]; - this._createHashes(this.leaves); - } - _createHashes(nodes) { - while (nodes.length > 1) { - const layerIndex = this.layers.length; - this.layers.push([]); - for (let i = 0; i < nodes.length; i += 2) { - if (i + 1 === nodes.length) { - if (nodes.length % 2 === 1) { - let data = nodes[nodes.length - 1]; - let hash = data; - // is bitcoin tree - if (this.isBitcoinTree) { - // Bitcoin method of duplicating the odd ending nodes - data = Buffer.concat([buffer_reverse_1.default(data), buffer_reverse_1.default(data)]); - hash = this.hashAlgo(data); - hash = buffer_reverse_1.default(this.hashAlgo(hash)); - this.layers[layerIndex].push(hash); - continue; - } - else { - if (!this.duplicateOdd) { - this.layers[layerIndex].push(nodes[i]); - continue; - } - } - } - } - const left = nodes[i]; - let right = i + 1 === nodes.length ? left : nodes[i + 1]; - let data = null; - let combined = null; - if (this.isBitcoinTree) { - combined = [buffer_reverse_1.default(left), buffer_reverse_1.default(right)]; - } - else { - combined = [left, right]; - } - if (this.sortPairs) { - combined.sort(Buffer.compare); - } - data = Buffer.concat(combined); - let hash = this.hashAlgo(data); - // double hash if bitcoin tree - if (this.isBitcoinTree) { - hash = buffer_reverse_1.default(this.hashAlgo(hash)); - } - this.layers[layerIndex].push(hash); - } - nodes = this.layers[layerIndex]; - } - } - /** - * getLeaves - * @desc Returns array of leaves of Merkle Tree. - * @return {Buffer[]} - * @example - *```js - *const leaves = tree.getLeaves() - *``` - */ - getLeaves(values) { - if (Array.isArray(values)) { - if (this.hashLeaves) { - values = values.map(this.hashAlgo); - if (this.sortLeaves) { - values = values.sort(Buffer.compare); - } - } - return this.leaves.filter(x => this._bufferIndexOf(values, x) !== -1); - } - return this.leaves; - } - /** - * getHexLeaves - * @desc Returns array of leaves of Merkle Tree as hex strings. - * @return {String[]} - * @example - *```js - *const leaves = tree.getHexLeaves() - *``` - */ - getHexLeaves() { - return this.leaves.map(x => this._bufferToHex(x)); - } - /** - * getLayers - * @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root. - * @return {Buffer[]} - * @example - *```js - *const layers = tree.getLayers() - *``` - */ - getLayers() { - return this.layers; - } - /** - * getHexLayers - * @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root as hex strings. - * @return {String[]} - * @example - *```js - *const layers = tree.getHexLayers() - *``` - */ - getHexLayers() { - return this.layers.reduce((acc, item, i) => { - if (Array.isArray(item)) { - acc.push(item.map(x => this._bufferToHex(x))); - } - else { - acc.push(item); - } - return acc; - }, []); - } - /** - * getLayersFlat - * @desc Returns single flat array of all layers of Merkle Tree, including leaves and root. - * @return {Buffer[]} - * @example - *```js - *const layers = tree.getLayersFlat() - *``` - */ - getLayersFlat() { - const layers = this.layers.reduce((acc, item, i) => { - if (Array.isArray(item)) { - acc.unshift(...item); - } - else { - acc.unshift(item); - } - return acc; - }, []); - layers.unshift(Buffer.from([0])); - return layers; - } - /** - * getHexLayersFlat - * @desc Returns single flat array of all layers of Merkle Tree, including leaves and root as hex string. - * @return {String[]} - * @example - *```js - *const layers = tree.getHexLayersFlat() - *``` - */ - getHexLayersFlat() { - return this.getLayersFlat().map(x => this._bufferToHex(x)); - } - /** - * getRoot - * @desc Returns the Merkle root hash as a Buffer. - * @return {Buffer} - * @example - *```js - *const root = tree.getRoot() - *``` - */ - getRoot() { - return this.layers[this.layers.length - 1][0] || Buffer.from([]); - } - /** - * getHexRoot - * @desc Returns the Merkle root hash as a hex string. - * @return {String} - * @example - *```js - *const root = tree.getHexRoot() - *``` - */ - getHexRoot() { - return this._bufferToHex(this.getRoot()); - } - /** - * getProof - * @desc Returns the proof for a target leaf. - * @param {Buffer} leaf - Target leaf - * @param {Number} [index] - Target leaf index in leaves array. - * Use if there are leaves containing duplicate data in order to distinguish it. - * @return {Object[]} - Array of objects containing a position property of type string - * with values of 'left' or 'right' and a data property of type Buffer. - * @example - * ```js - *const proof = tree.getProof(leaves[2]) - *``` - * - * @example - *```js - *const leaves = ['a', 'b', 'a'].map(x => keccak(x)) - *const tree = new MerkleTree(leaves, keccak) - *const proof = tree.getProof(leaves[2], 2) - *``` - */ - getProof(leaf, index) { - leaf = this._bufferify(leaf); - const proof = []; - if (typeof index !== 'number') { - index = -1; - for (let i = 0; i < this.leaves.length; i++) { - if (Buffer.compare(leaf, this.leaves[i]) === 0) { - index = i; - } - } - } - if (index <= -1) { - return []; - } - if (this.isBitcoinTree && index === (this.leaves.length - 1)) { - // Proof Generation for Bitcoin Trees - for (let i = 0; i < this.layers.length - 1; i++) { - const layer = this.layers[i]; - const isRightNode = index % 2; - const pairIndex = (isRightNode ? index - 1 : index); - if (pairIndex < layer.length) { - proof.push({ - data: layer[pairIndex] - }); - } - // set index to parent index - index = (index / 2) | 0; - } - return proof; - } - else { - // Proof Generation for Non-Bitcoin Trees - for (let i = 0; i < this.layers.length; i++) { - const layer = this.layers[i]; - const isRightNode = index % 2; - const pairIndex = (isRightNode ? index - 1 : index + 1); - if (pairIndex < layer.length) { - proof.push({ - position: isRightNode ? 'left' : 'right', - data: layer[pairIndex] - }); - } - // set index to parent index - index = (index / 2) | 0; - } - return proof; - } - } - /** - * getHexProof - * @desc Returns the proof for a target leaf as hex strings. - * @param {Buffer} leaf - Target leaf - * @param {Number} [index] - Target leaf index in leaves array. - * Use if there are leaves containing duplicate data in order to distinguish it. - * @return {String[]} - Proof array as hex strings. - * @example - * ```js - *const proof = tree.getHexProof(leaves[2]) - *``` - */ - getHexProof(leaf, index) { - return this.getProof(leaf, index).map(x => this._bufferToHex(x.data)); - } - /** - * getProofIndices - * @desc Returns the proof indices for given tree indices. - * @param {Number[]} treeIndices - Tree indices - * @param {Number} depth - Tree depth; number of layers. - * @return {Number[]} - Proof indices - * @example - * ```js - *const proofIndices = tree.getProofIndices([2,5,6], 4) - *console.log(proofIndices) // [ 23, 20, 19, 8, 3 ] - *``` - */ - getProofIndices(treeIndices, depth) { - const leafCount = Math.pow(2, depth); - let maximalIndices = new Set(); - for (const index of treeIndices) { - let x = leafCount + index; - while (x > 1) { - maximalIndices.add(x ^ 1); - x = (x / 2) | 0; - } - } - const a = treeIndices.map(index => leafCount + index); - const b = Array.from(maximalIndices).sort((a, b) => a - b).reverse(); - maximalIndices = a.concat(b); - const redundantIndices = new Set(); - const proof = []; - for (let index of maximalIndices) { - if (!redundantIndices.has(index)) { - proof.push(index); - while (index > 1) { - redundantIndices.add(index); - if (!redundantIndices.has(index ^ 1)) - break; - index = (index / 2) | 0; - } - } - } - return proof.filter(index => { - return !treeIndices.includes(index - leafCount); - }); - } - /** - * getMultiProof - * @desc Returns the multiproof for given tree indices. - * @param {Number[]} indices - Tree indices. - * @return {Buffer[]} - Multiproofs - * @example - * ```js - *const indices = [2, 5, 6] - *const proof = tree.getMultiProof(indices) - *``` - */ - getMultiProof(tree, indices) { - if (!indices) { - indices = tree; - tree = this.getLayersFlat(); - if (!indices.every(x => typeof x === 'number')) { - let els = indices; - if (this.sortPairs) { - els = els.sort(Buffer.compare); - } - let ids = els.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1); - if (!ids.every((idx) => idx !== -1)) { - throw new Error('Element does not exist in Merkle tree'); - } - const hashes = []; - const proof = []; - let nextIds = []; - for (let i = 0; i < this.layers.length; i++) { - const layer = this.layers[i]; - for (let j = 0; j < ids.length; j++) { - const idx = ids[j]; - const pairElement = this._getPairNode(layer, idx); - hashes.push(layer[idx]); - if (pairElement) { - proof.push(pairElement); - } - nextIds.push((idx / 2) | 0); - } - ids = nextIds.filter((value, i, self) => self.indexOf(value) === i); - nextIds = []; - } - return proof.filter((value) => !hashes.includes(value)); - } - } - return this.getProofIndices(indices, this._log2((tree.length / 2) | 0)).map(index => tree[index]); - } - /** - * getHexMultiProof - * @desc Returns the multiproof for given tree indices as hex strings. - * @param {Number[]} indices - Tree indices. - * @return {String[]} - Multiproofs as hex strings. - * @example - * ```js - *const indices = [2, 5, 6] - *const proof = tree.getHexMultiProof(indices) - *``` - */ - getHexMultiProof(tree, indices) { - return this.getMultiProof(tree, indices).map(this._bufferToHex); - } - /** - * getProofFlags - * @desc Returns list of booleans where proofs should be used instead of hashing. - * Proof flags are used in the Solidity multiproof verifiers. - * @param {Buffer[]} leaves - * @param {Buffer[]} proofs - * @return {Boolean[]} - Boolean flags - * @example - * ```js - *const indices = [2, 5, 6] - *const proof = tree.getMultiProof(indices) - *const proofFlags = tree.getProofFlags(leaves, proof) - *``` - */ - getProofFlags(leaves, proofs) { - let ids = leaves.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1); - if (!ids.every((idx) => idx !== -1)) { - throw new Error('Element does not exist in Merkle tree'); - } - const tested = []; - const flags = []; - for (let index = 0; index < this.layers.length; index++) { - const layer = this.layers[index]; - ids = ids.reduce((ids, idx) => { - const skipped = tested.includes(layer[idx]); - if (!skipped) { - const pairElement = this._getPairNode(layer, idx); - const proofUsed = proofs.includes(layer[idx]) || proofs.includes(pairElement); - pairElement && flags.push(!proofUsed); - tested.push(layer[idx]); - tested.push(pairElement); - } - ids.push((idx / 2) | 0); - return ids; - }, []); - } - return flags; - } - /** - * verify - * @desc Returns true if the proof path (array of hashes) can connect the target node - * to the Merkle root. - * @param {Object[]} proof - Array of proof objects that should connect - * target node to Merkle root. - * @param {Buffer} targetNode - Target node Buffer - * @param {Buffer} root - Merkle root Buffer - * @return {Boolean} - * @example - *```js - *const root = tree.getRoot() - *const proof = tree.getProof(leaves[2]) - *const verified = tree.verify(proof, leaves[2], root) - *``` - */ - verify(proof, targetNode, root) { - let hash = this._bufferify(targetNode); - root = this._bufferify(root); - if (!Array.isArray(proof) || - !proof.length || - !targetNode || - !root) { - return false; - } - for (let i = 0; i < proof.length; i++) { - const node = proof[i]; - let data = null; - let isLeftNode = null; - // NOTE: case for when proof is hex values only - if (typeof node === 'string') { - data = this._bufferify(node); - isLeftNode = true; - } - else { - data = node.data; - isLeftNode = (node.position === 'left'); - } - const buffers = []; - if (this.isBitcoinTree) { - buffers.push(buffer_reverse_1.default(hash)); - buffers[isLeftNode ? 'unshift' : 'push'](buffer_reverse_1.default(data)); - hash = this.hashAlgo(Buffer.concat(buffers)); - hash = buffer_reverse_1.default(this.hashAlgo(hash)); - } - else { - if (this.sortPairs) { - if (Buffer.compare(hash, data) === -1) { - buffers.push(hash, data); - hash = this.hashAlgo(Buffer.concat(buffers)); - } - else { - buffers.push(data, hash); - hash = this.hashAlgo(Buffer.concat(buffers)); - } - } - else { - buffers.push(hash); - buffers[isLeftNode ? 'unshift' : 'push'](data); - hash = this.hashAlgo(Buffer.concat(buffers)); - } - } - } - return Buffer.compare(hash, root) === 0; - } - /** - * verifyMultiProof - * @desc Returns true if the multiproofs can connect the leaves to the Merkle root. - * @param {Buffer} root - Merkle tree root - * @param {Number[]} indices - Leave indices - * @param {Buffer[]} leaves - Leaf values at indices. - * @param {Number} depth - Tree depth - * @param {Buffer[]} proof - Multiproofs given indices - * @return {Boolean} - * @example - *```js - *const root = tree.getRoot() - *const treeFlat = tree.getLayersFlat() - *const depth = tree.getDepth() - *const indices = [2, 5, 6] - *const proofLeaves = indices.map(i => leaves[i]) - *const proof = tree.getMultiProof(treeFlat, indices) - *const verified = tree.verifyMultiProof(root, indices, proofLeaves, depth, proof) - *``` - */ - verifyMultiProof(root, indices, leaves, depth, proof) { - root = this._bufferify(root); - leaves = leaves.map(this._bufferify); - proof = proof.map(this._bufferify); - const tree = {}; - for (const [index, leaf] of this._zip(indices, leaves)) { - tree[(Math.pow(2, depth)) + index] = leaf; - } - for (const [index, proofitem] of this._zip(this.getProofIndices(indices, depth), proof)) { - tree[index] = proofitem; - } - let indexqueue = Object.keys(tree).map(x => +x).sort((a, b) => a - b); - indexqueue = indexqueue.slice(0, indexqueue.length - 1); - let i = 0; - while (i < indexqueue.length) { - const index = indexqueue[i]; - if (index >= 2 && ({}).hasOwnProperty.call(tree, index ^ 1)) { - tree[(index / 2) | 0] = this.hashAlgo(Buffer.concat([tree[index - (index % 2)], tree[index - (index % 2) + 1]])); - indexqueue.push((index / 2) | 0); - } - i += 1; - } - return !indices.length || (({}).hasOwnProperty.call(tree, 1) && tree[1].equals(root)); - } - /** - * getDepth - * @desc Returns the tree depth (number of layers) - * @return {Number} - * @example - *```js - *const depth = tree.getDepth() - *``` - */ - getDepth() { - return this.getLayers().length - 1; - } - /** - * getLayersAsObject - * @desc Returns the layers as nested objects instead of an array. - * @example - *```js - *const layersObj = tree.getLayersAsObject() - *``` - */ - getLayersAsObject() { - const layers = this.getLayers().map((layer) => layer.map((value) => value.toString('hex'))); - const objs = []; - for (let i = 0; i < layers.length; i++) { - const arr = []; - for (let j = 0; j < layers[i].length; j++) { - const obj = { [layers[i][j]]: null }; - if (objs.length) { - obj[layers[i][j]] = {}; - const a = objs.shift(); - const akey = Object.keys(a)[0]; - obj[layers[i][j]][akey] = a[akey]; - if (objs.length) { - const b = objs.shift(); - const bkey = Object.keys(b)[0]; - obj[layers[i][j]][bkey] = b[bkey]; - } - } - arr.push(obj); - } - objs.push(...arr); - } - return objs[0]; - } - /** - * print - * @desc Prints out a visual representation of the merkle tree. - * @example - *```js - *tree.print() - *``` - */ - print() { - MerkleTree.print(this); - } - /** - * toTreeString - * @desc Returns a visual representation of the merkle tree as a string. - * @return {String} - * @example - *```js - *console.log(tree.toTreeString()) - *``` - */ - _toTreeString() { - const obj = this.getLayersAsObject(); - return treeify_1.default.asTree(obj, true); - } - /** - * toString - * @desc Returns a visual representation of the merkle tree as a string. - * @example - *```js - *console.log(tree.toString()) - *``` - */ - toString() { - return this._toTreeString(); - } - /** - * getMultiProof - * @desc Returns the multiproof for given tree indices. - * @param {Buffer[]} tree - Tree as a flat array. - * @param {Number[]} indices - Tree indices. - * @return {Buffer[]} - Multiproofs - * - *@example - * ```js - *const flatTree = tree.getLayersFlat() - *const indices = [2, 5, 6] - *const proof = MerkleTree.getMultiProof(flatTree, indices) - *``` - */ - static getMultiProof(tree, indices) { - const t = new MerkleTree([]); - return t.getMultiProof(tree, indices); - } - /** - * getPairNode - * @desc Returns the node at the index for given layer. - * @param {Buffer[]} layer - Tree layer - * @param {Number} index - Index at layer. - * @return {Buffer} - Node - * - *@example - * ```js - *const node = tree.getPairNode(layer, index) - *``` - */ - _getPairNode(layer, idx) { - const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; - if (pairIdx < layer.length) { - return layer[pairIdx]; - } - else { - return null; - } - } - /** - * bufferIndexOf - * @desc Returns the first index of which given buffer is found in array. - * @param {Buffer[]} haystack - Array of buffers. - * @param {Buffer} needle - Buffer to find. - * @return {Number} - Index number - * - * @example - * ```js - *const index = tree.bufferIndexOf(haystack, needle) - *``` - */ - _bufferIndexOf(array, element) { - for (let i = 0; i < array.length; i++) { - if (element.equals(array[i])) { - return i; - } - } - return -1; - } - /** - * bufferify - * @desc Returns a buffer type for the given value. - * @param {String|Number|Object|Buffer} value - * @return {Buffer} - * - * @example - * ```js - *const buf = MerkleTree.bufferify('0x1234') - *``` - */ - static bufferify(value) { - if (!Buffer.isBuffer(value)) { - // crypto-js support - if (typeof value === 'object' && value.words) { - return Buffer.from(value.toString(crypto_js_1.default.enc.Hex), 'hex'); - } - else if (MerkleTree.isHexString(value)) { - return Buffer.from(value.replace(/^0x/, ''), 'hex'); - } - else if (typeof value === 'string') { - return Buffer.from(value); - } - } - return value; - } - /** - * isHexString - * @desc Returns true if value is a hex string. - * @param {String} value - * @return {Boolean} - * - * @example - * ```js - *console.log(MerkleTree.isHexString('0x1234')) - *``` - */ - static isHexString(v) { - return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v)); - } - /** - * print - * @desc Prints out a visual representation of the given merkle tree. - * @param {Object} tree - Merkle tree instance. - * @return {String} - * @example - *```js - *MerkleTree.print(tree) - *``` - */ - static print(tree) { - console.log(tree.toString()); - } - /** - * bufferToHex - * @desc Returns a hex string with 0x prefix for given buffer. - * @param {Buffer} value - * @return {String} - * @example - *```js - *const hexStr = tree.bufferToHex(Buffer.from('A')) - *``` - */ - _bufferToHex(value) { - return '0x' + value.toString('hex'); - } - /** - * bufferify - * @desc Returns a buffer type for the given value. - * @param {String|Number|Object|Buffer} value - * @return {Buffer} - * - * @example - * ```js - *const buf = MerkleTree.bufferify('0x1234') - *``` - */ - _bufferify(value) { - return MerkleTree.bufferify(value); - } - /** - * bufferifyFn - * @desc Returns a function that will bufferify the return value. - * @param {Function} - * @return {Function} - * - * @example - * ```js - *const fn = tree.bufferifyFn((value) => sha256(value)) - *``` - */ - _bufferifyFn(f) { - return function (value) { - const v = f(value); - if (Buffer.isBuffer(v)) { - return v; - } - if (this._isHexString(v)) { - return Buffer.from(v, 'hex'); - } - // crypto-js support - return Buffer.from(f(crypto_js_1.default.enc.Hex.parse(value.toString('hex'))).toString(crypto_js_1.default.enc.Hex), 'hex'); - }; - } - /** - * isHexString - * @desc Returns true if value is a hex string. - * @param {String} value - * @return {Boolean} - * - * @example - * ```js - *console.log(MerkleTree.isHexString('0x1234')) - *``` - */ - _isHexString(value) { - return MerkleTree.isHexString(value); - } - /** - * log2 - * @desc Returns the log2 of number. - * @param {Number} value - * @return {Number} - */ - _log2(n) { - return n === 1 ? 0 : 1 + this._log2((n / 2) | 0); - } - /** - * zip - * @desc Returns true if value is a hex string. - * @param {String[]|Number[]|Buffer[]} a - first array - * @param {String[]|Number[]|Buffer[]} b - second array - * @return {String[][]|Number[][]|Buffer[][]} - * - * @example - * ```js - *const zipped = tree.zip(['a', 'b'],['A', 'B']) - *console.log(zipped) // [ [ 'a', 'A' ], [ 'b', 'B' ] ] - *``` - */ - _zip(a, b) { - return a.map((e, i) => [e, b[i]]); - } -} -exports.MerkleTree = MerkleTree; -exports.default = MerkleTree; diff --git a/package.json b/package.json index 77d5d1e..385c779 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "merkletreejs", - "version": "0.2.3", + "version": "0.2.4", "description": "Construct Merkle Trees and verify proofs", "main": "dist/index.js", "types": "dist/index.d.ts",