UNPKG

merkletreejs

Version:
1,314 lines (1,313 loc) 46.9 kB
"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_1 = require("buffer"); const buffer_reverse_1 = __importDefault(require("buffer-reverse")); const sha256_1 = __importDefault(require("crypto-js/sha256")); const treeify_1 = __importDefault(require("treeify")); const Base_1 = __importDefault(require("./Base")); /** * Class reprensenting a Merkle Tree * @namespace MerkleTree */ class MerkleTree extends Base_1.default { /** * @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} hashFunction - Hash function to use 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(value => keccak(value)) * *const tree = new MerkleTree(leaves, sha256) *``` */ constructor(leaves, hashFn = sha256_1.default, options = {}) { super(); this.duplicateOdd = false; this.hashLeaves = false; this.isBitcoinTree = false; this.leaves = []; this.layers = []; this.sortLeaves = false; this.sortPairs = false; this.sort = false; this.fillDefaultHash = null; this.complete = false; if (options.complete) { if (options.isBitcoinTree) { throw new Error('option "complete" is incompatible with "isBitcoinTree"'); } if (options.duplicateOdd) { throw new Error('option "complete" is incompatible with "duplicateOdd"'); } } this.isBitcoinTree = !!options.isBitcoinTree; this.hashLeaves = !!options.hashLeaves; this.sortLeaves = !!options.sortLeaves; this.sortPairs = !!options.sortPairs; this.complete = !!options.complete; if (options.fillDefaultHash) { if (typeof options.fillDefaultHash === 'function') { this.fillDefaultHash = options.fillDefaultHash; } else if (buffer_1.Buffer.isBuffer(options.fillDefaultHash) || typeof options.fillDefaultHash === 'string') { this.fillDefaultHash = (idx, hashFn) => options.fillDefaultHash; } else { throw new Error('method "fillDefaultHash" must be a function, Buffer, or string'); } } this.sort = !!options.sort; if (this.sort) { this.sortLeaves = true; this.sortPairs = true; } this.duplicateOdd = !!options.duplicateOdd; if (options.concatenator) { this.concatenator = options.concatenator; } else { this.concatenator = buffer_1.Buffer.concat; } if (typeof hashFn !== 'function') { throw new Error('hashFn must be a function'); } this.hashFn = this.bufferifyFn(hashFn); this.processLeaves(leaves); } getOptions() { var _a, _b; return { complete: this.complete, isBitcoinTree: this.isBitcoinTree, hashLeaves: this.hashLeaves, sortLeaves: this.sortLeaves, sortPairs: this.sortPairs, sort: this.sort, fillDefaultHash: (_b = (_a = this.fillDefaultHash) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : null, duplicateOdd: this.duplicateOdd }; } processLeaves(leaves) { if (this.hashLeaves) { leaves = leaves.map(this.hashFn); } this.leaves = leaves.map(this.bufferify); if (this.sortLeaves) { this.leaves = this.leaves.sort(buffer_1.Buffer.compare); } if (this.fillDefaultHash) { for (let i = this.leaves.length; i < Math.pow(2, Math.ceil(Math.log2(this.leaves.length))); i++) { this.leaves.push(this.bufferify(this.fillDefaultHash(i, this.hashFn))); } } this.createHashes(this.leaves); } createHashes(nodes) { this.layers = [nodes]; while (nodes.length > 1) { const layerIndex = this.layers.length; this.layers.push([]); const layerLimit = this.complete && layerIndex === 1 && !Number.isInteger(Math.log2(nodes.length)) ? (2 * nodes.length) - (Math.pow(2, Math.ceil(Math.log2(nodes.length)))) : nodes.length; for (let i = 0; i < nodes.length; i += 2) { if (i >= layerLimit) { this.layers[layerIndex].push(...nodes.slice(layerLimit)); break; } else if (i + 1 === nodes.length) { if (nodes.length % 2 === 1) { const data = nodes[nodes.length - 1]; let hash = data; // is bitcoin tree if (this.isBitcoinTree) { // Bitcoin method of duplicating the odd ending nodes hash = this.hashFn(this.concatenator([buffer_reverse_1.default(data), buffer_reverse_1.default(data)])); hash = buffer_reverse_1.default(this.hashFn(hash)); this.layers[layerIndex].push(hash); continue; } else { if (this.duplicateOdd) { // continue with creating layer } else { // push copy of hash and continue iteration this.layers[layerIndex].push(nodes[i]); continue; } } } } const left = nodes[i]; const right = i + 1 === nodes.length ? left : nodes[i + 1]; 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_1.Buffer.compare); } let hash = this.hashFn(this.concatenator(combined)); // double hash if bitcoin tree if (this.isBitcoinTree) { hash = buffer_reverse_1.default(this.hashFn(hash)); } this.layers[layerIndex].push(hash); } nodes = this.layers[layerIndex]; } } /** * addLeaf * @desc Adds a leaf to the tree and re-calculates layers. * @param {String|Buffer} - Leaf * @param {Boolean} - Set to true if the leaf should be hashed before being added to tree. * @example *```js *tree.addLeaf(newLeaf) *``` */ addLeaf(leaf, shouldHash = false) { if (shouldHash) { leaf = this.hashFn(leaf); } this.processLeaves(this.leaves.concat(leaf)); } /** * addLeaves * @desc Adds multiple leaves to the tree and re-calculates layers. * @param {String[]|Buffer[]} - Array of leaves * @param {Boolean} - Set to true if the leaves should be hashed before being added to tree. * @example *```js *tree.addLeaves(newLeaves) *``` */ addLeaves(leaves, shouldHash = false) { if (shouldHash) { leaves = leaves.map(this.hashFn); } this.processLeaves(this.leaves.concat(leaves)); } /** * 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.hashFn); if (this.sortLeaves) { values = values.sort(buffer_1.Buffer.compare); } } return this.leaves.filter(leaf => this.bufferIndexOf(values, leaf, this.sortLeaves) !== -1); } return this.leaves; } removeLeaf(index) { if (!this.isValidLeafIndex(index)) { throw new Error(`"${index}" is not a valid leaf index. Expected to be [0, ${this.getLeafCount() - 1}]`); } const result = this.leaves.splice(index, 1); this.processLeaves(this.leaves); return result[0]; } updateLeaf(index, value, shouldHash = false) { if (!this.isValidLeafIndex(index)) { throw new Error(`"${index}" is not a valid leaf index. Expected to be [0, ${this.getLeafCount() - 1}]`); } if (shouldHash) value = this.hashFn(value); this.leaves[index] = value; this.processLeaves(this.leaves); } /** * getLeaf * @desc Returns the leaf at the given index. * @param {Number} - Index number * @return {Buffer} * @example *```js *const leaf = tree.getLeaf(1) *``` */ getLeaf(index) { if (index < 0 || index > this.leaves.length - 1) { return buffer_1.Buffer.from([]); } return this.leaves[index]; } /** * getLeafIndex * @desc Returns the index of the given leaf, or -1 if the leaf is not found. * @param {String|Buffer} - Target leaf * @return {number} * @example *```js *const leaf = Buffer.from('abc') *const index = tree.getLeafIndex(leaf) *``` */ getLeafIndex(target) { target = this.bufferify(target); const leaves = this.getLeaves(); for (let i = 0; i < leaves.length; i++) { const leaf = leaves[i]; if (leaf.equals(target)) { return i; } } return -1; } /** * getLeafCount * @desc Returns the total number of leaves. * @return {number} * @example *```js *const count = tree.getLeafCount() *``` */ getLeafCount() { return this.leaves.length; } /** * 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(leaf => this.bufferToHex(leaf)); } /** * marshalLeaves * @desc Returns array of leaves of Merkle Tree as a JSON string. * @param {String[]|Buffer[]} - Merkle tree leaves * @return {String} - List of leaves as JSON string * @example *```js *const jsonStr = MerkleTree.marshalLeaves(leaves) *``` */ static marshalLeaves(leaves) { return JSON.stringify(leaves.map(leaf => MerkleTree.bufferToHex(leaf)), null, 2); } /** * unmarshalLeaves * @desc Returns array of leaves of Merkle Tree as a Buffers. * @param {String|Object} - JSON stringified leaves * @return {Buffer[]} - Unmarshalled list of leaves * @example *```js *const leaves = MerkleTree.unmarshalLeaves(jsonStr) *``` */ static unmarshalLeaves(jsonStr) { let parsed = null; if (typeof jsonStr === 'string') { parsed = JSON.parse(jsonStr); } else if (jsonStr instanceof Object) { parsed = jsonStr; } else { throw new Error('Expected type of string or object'); } if (!parsed) { return []; } if (!Array.isArray(parsed)) { throw new Error('Expected JSON string to be array'); } return parsed.map(MerkleTree.bufferify); } /** * 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) => { if (Array.isArray(item)) { acc.push(item.map(layer => this.bufferToHex(layer))); } 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) => { if (Array.isArray(item)) { acc.unshift(...item); } else { acc.unshift(item); } return acc; }, []); layers.unshift(buffer_1.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(layer => this.bufferToHex(layer)); } /** * getLayerCount * @desc Returns the total number of layers. * @return {number} * @example *```js *const count = tree.getLayerCount() *``` */ getLayerCount() { return this.getLayers().length; } /** * getRoot * @desc Returns the Merkle root hash as a Buffer. * @return {Buffer} * @example *```js *const root = tree.getRoot() *``` */ getRoot() { if (this.layers.length === 0) { return buffer_1.Buffer.from([]); } return this.layers[this.layers.length - 1][0] || buffer_1.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(value => keccak(value)) *const tree = new MerkleTree(leaves, keccak) *const proof = tree.getProof(leaves[2], 2) *``` */ getProof(leaf, index) { if (typeof leaf === 'undefined') { throw new Error('leaf is required'); } leaf = this.bufferify(leaf); const proof = []; if (!Number.isInteger(index)) { index = -1; for (let i = 0; i < this.leaves.length; i++) { if (buffer_1.Buffer.compare(leaf, this.leaves[i]) === 0) { index = i; } } } if (index <= -1) { return []; } for (let i = 0; i < this.layers.length; i++) { const layer = this.layers[i]; const isRightNode = index % 2; const pairIndex = (isRightNode ? index - 1 : this.isBitcoinTree && index === layer.length - 1 && i < this.layers.length - 1 // Proof Generation for Bitcoin Trees ? index // Proof Generation for Non-Bitcoin Trees : 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(item => this.bufferToHex(item.data)); } /** * getProofs * @desc Returns the proofs for all leaves. * @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 for all leaves. * @example * ```js *const proofs = tree.getProofs() *``` * * @example *```js *const leaves = ['a', 'b', 'a'].map(value => keccak(value)) *const tree = new MerkleTree(leaves, keccak) *const proofs = tree.getProofs() *``` */ getProofs() { const proof = []; const proofs = []; this.getProofsDFS(this.layers.length - 1, 0, proof, proofs); return proofs; } /** * getProofsDFS * @desc Get all proofs through single traverse * @param {Number} currentLayer - Current layer index in traverse. * @param {Number} index - Current tarvese node index in traverse. * @param {Object[]} proof - Proof chain for single leaf. * @param {Object[]} proofs - Proofs for all leaves * @example * ```js *const layers = tree.getLayers() *const index = 0; *let proof = []; *let proofs = []; *const proof = tree.getProofsDFS(layers, index, proof, proofs) *``` */ getProofsDFS(currentLayer, index, proof, proofs) { const isRightNode = index % 2; if (currentLayer === -1) { if (!isRightNode) proofs.push([...proof].reverse()); return; } if (index >= this.layers[currentLayer].length) return; const layer = this.layers[currentLayer]; const pairIndex = isRightNode ? index - 1 : index + 1; let pushed = false; if (pairIndex < layer.length) { pushed = true; proof.push({ position: isRightNode ? 'left' : 'right', data: layer[pairIndex] }); } const leftchildIndex = index * 2; const rightchildIndex = index * 2 + 1; this.getProofsDFS(currentLayer - 1, leftchildIndex, proof, proofs); this.getProofsDFS(currentLayer - 1, rightchildIndex, proof, proofs); if (pushed) proof.splice(proof.length - 1, 1); } /** * getHexProofs * @desc Returns the proofs for all leaves as hex strings. * @return {String[]} - Proofs array as hex strings. * @example * ```js *const proofs = tree.getHexProofs() *``` */ getHexProofs() { return this.getProofs().map(item => this.bufferToHex(item.data)); } /** * getPositionalHexProof * @desc Returns the proof for a target leaf as hex strings and the position in binary (left == 0). * @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 | number)[][]} - Proof array as hex strings. position at index 0 * @example * ```js *const proof = tree.getPositionalHexProof(leaves[2]) *``` */ getPositionalHexProof(leaf, index) { return this.getProof(leaf, index).map(item => { return [ item.position === 'left' ? 0 : 1, this.bufferToHex(item.data) ]; }); } /** * marshalProof * @desc Returns proof array as JSON string. * @param {String[]|Object[]} proof - Merkle tree proof array * @return {String} - Proof array as JSON string. * @example * ```js *const jsonStr = MerkleTree.marshalProof(proof) *``` */ static marshalProof(proof) { const json = proof.map(item => { if (typeof item === 'string') { return item; } if (buffer_1.Buffer.isBuffer(item)) { return MerkleTree.bufferToHex(item); } return { position: item.position, data: MerkleTree.bufferToHex(item.data) }; }); return JSON.stringify(json, null, 2); } /** * unmarshalProof * @desc Returns the proof for a target leaf as a list of Buffers. * @param {String|Object} - Merkle tree leaves * @return {String|Object} - Marshalled proof * @example * ```js *const proof = MerkleTree.unmarshalProof(jsonStr) *``` */ static unmarshalProof(jsonStr) { let parsed = null; if (typeof jsonStr === 'string') { parsed = JSON.parse(jsonStr); } else if (jsonStr instanceof Object) { parsed = jsonStr; } else { throw new Error('Expected type of string or object'); } if (!parsed) { return []; } if (!Array.isArray(parsed)) { throw new Error('Expected JSON string to be array'); } return parsed.map(item => { if (typeof item === 'string') { return MerkleTree.bufferify(item); } else if (item instanceof Object) { return { position: item.position, data: MerkleTree.bufferify(item.data) }; } else { throw new Error('Expected item to be of type string or object'); } }); } static marshalTree(tree) { const root = tree.getHexRoot(); const leaves = tree.leaves.map(leaf => MerkleTree.bufferToHex(leaf)); const layers = tree.getHexLayers(); const options = tree.getOptions(); return JSON.stringify({ options, root, layers, leaves }, null, 2); } static unmarshalTree(jsonStr, hashFn = sha256_1.default, options = {}) { let parsed = null; if (typeof jsonStr === 'string') { parsed = JSON.parse(jsonStr); } else if (jsonStr instanceof Object) { parsed = jsonStr; } else { throw new Error('Expected type of string or object'); } if (!parsed) { throw new Error('could not parse json'); } options = Object.assign({}, parsed.options || {}, options); return new MerkleTree(parsed.leaves, hashFn, options); } /** * 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); }); } getProofIndicesForUnevenTree(sortedLeafIndices, leavesCount) { const depth = Math.ceil(Math.log2(leavesCount)); const unevenLayers = []; for (let index = 0; index < depth; index++) { const unevenLayer = leavesCount % 2 !== 0; if (unevenLayer) { unevenLayers.push({ index, leavesCount }); } leavesCount = Math.ceil(leavesCount / 2); } const proofIndices = []; let layerNodes = sortedLeafIndices; for (let layerIndex = 0; layerIndex < depth; layerIndex++) { const siblingIndices = layerNodes.map((index) => { if (index % 2 === 0) { return index + 1; } return index - 1; }); let proofNodeIndices = siblingIndices.filter((index) => !layerNodes.includes(index)); const unevenLayer = unevenLayers.find(({ index }) => index === layerIndex); if (unevenLayer && layerNodes.includes(unevenLayer.leavesCount - 1)) { proofNodeIndices = proofNodeIndices.slice(0, -1); } proofIndices.push(proofNodeIndices); layerNodes = [...new Set(layerNodes.map((index) => { if (index % 2 === 0) { return index / 2; } if (index % 2 === 0) { return (index + 1) / 2; } return (index - 1) / 2; }))]; } return proofIndices; } /** * 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 (!this.complete) { console.warn('Warning: For correct multiProofs it\'s strongly recommended to set complete: true'); } if (!indices) { indices = tree; tree = this.getLayersFlat(); } const isUneven = this.isUnevenTree(); if (isUneven) { if (indices.every(Number.isInteger)) { return this.getMultiProofForUnevenTree(indices); } } if (!indices.every(Number.isInteger)) { let els = indices; if (this.sortPairs) { els = els.sort(buffer_1.Buffer.compare); } let ids = els.map((el) => this.bufferIndexOf(this.leaves, el, this.sortLeaves)).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, Math.log2((tree.length / 2) | 0)).map(index => tree[index]); } getMultiProofForUnevenTree(tree, indices) { if (!indices) { indices = tree; tree = this.getLayers(); } let proofHashes = []; let currentLayerIndices = indices; for (const treeLayer of tree) { const siblings = []; for (const index of currentLayerIndices) { if (index % 2 === 0) { const idx = index + 1; if (!currentLayerIndices.includes(idx)) { if (treeLayer[idx]) { siblings.push(treeLayer[idx]); continue; } } } const idx = index - 1; if (!currentLayerIndices.includes(idx)) { if (treeLayer[idx]) { siblings.push(treeLayer[idx]); continue; } } } proofHashes = proofHashes.concat(siblings); const uniqueIndices = new Set(); for (const index of currentLayerIndices) { if (index % 2 === 0) { uniqueIndices.add(index / 2); continue; } if (index % 2 === 0) { uniqueIndices.add((index + 1) / 2); continue; } uniqueIndices.add((index - 1) / 2); } currentLayerIndices = Array.from(uniqueIndices); } return proofHashes; } /** * 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((x) => this.bufferToHex(x)); } /** * getProofFlags * @desc Returns list of booleans where proofs should be used instead of hashing. * Proof flags are used in the Solidity multiproof verifiers. * @param {Number[]|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) { if (!Array.isArray(leaves) || leaves.length <= 0) { throw new Error('Invalid Inputs!'); } let ids; if (leaves.every(Number.isInteger)) { ids = [...leaves].sort((a, b) => a === b ? 0 : a > b ? 1 : -1); // Indices where passed } else { ids = leaves.map((el) => this.bufferIndexOf(this.leaves, el, this.sortLeaves)).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 proofBufs = proofs.map(item => this.bufferify(item)); 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 = this.bufferArrayIncludes(proofBufs, layer[idx]) || this.bufferArrayIncludes(proofBufs, 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) || !targetNode || !root) { return false; } for (let i = 0; i < proof.length; i++) { const node = proof[i]; let data = null; let isLeftNode = null; // case for when proof is hex values only if (typeof node === 'string') { data = this.bufferify(node); isLeftNode = true; } else if (Array.isArray(node)) { isLeftNode = (node[0] === 0); data = this.bufferify(node[1]); } else if (buffer_1.Buffer.isBuffer(node)) { data = node; isLeftNode = true; } else if (node instanceof Object) { data = this.bufferify(node.data); isLeftNode = (node.position === 'left'); } else { throw new Error('Expected node to be of type string or object'); } const buffers = []; if (this.isBitcoinTree) { buffers.push(buffer_reverse_1.default(hash)); buffers[isLeftNode ? 'unshift' : 'push'](buffer_reverse_1.default(data)); hash = this.hashFn(this.concatenator(buffers)); hash = buffer_reverse_1.default(this.hashFn(hash)); } else { if (this.sortPairs) { if (buffer_1.Buffer.compare(hash, data) === -1) { buffers.push(hash, data); hash = this.hashFn(this.concatenator(buffers)); } else { buffers.push(data, hash); hash = this.hashFn(this.concatenator(buffers)); } } else { buffers.push(hash); buffers[isLeftNode ? 'unshift' : 'push'](data); hash = this.hashFn(this.concatenator(buffers)); } } } return buffer_1.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[]} proofIndices - Leave indices for proof * @param {Buffer[]} proofLeaves - Leaf values at indices for proof * @param {Number} leavesCount - Count of original leaves * @param {Buffer[]} proof - Multiproofs given indices * @return {Boolean} * @example *```js *const leaves = tree.getLeaves() *const root = tree.getRoot() *const treeFlat = tree.getLayersFlat() *const leavesCount = leaves.length *const proofIndices = [2, 5, 6] *const proofLeaves = proofIndices.map(i => leaves[i]) *const proof = tree.getMultiProof(treeFlat, indices) *const verified = tree.verifyMultiProof(root, proofIndices, proofLeaves, leavesCount, proof) *``` */ verifyMultiProof(root, proofIndices, proofLeaves, leavesCount, proof) { const isUneven = this.isUnevenTree(); if (isUneven) { // TODO: combine these functions and simplify return this.verifyMultiProofForUnevenTree(root, proofIndices, proofLeaves, leavesCount, proof); } const depth = Math.ceil(Math.log2(leavesCount)); root = this.bufferify(root); proofLeaves = proofLeaves.map(leaf => this.bufferify(leaf)); proof = proof.map(leaf => this.bufferify(leaf)); const tree = {}; for (const [index, leaf] of this.zip(proofIndices, proofLeaves)) { tree[(Math.pow(2, depth)) + index] = leaf; } for (const [index, proofitem] of this.zip(this.getProofIndices(proofIndices, depth), proof)) { tree[index] = proofitem; } let indexqueue = Object.keys(tree).map(value => +value).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)) { let pair = [tree[index - (index % 2)], tree[index - (index % 2) + 1]]; if (this.sortPairs) { pair = pair.sort(buffer_1.Buffer.compare); } const hash = pair[1] ? this.hashFn(this.concatenator(pair)) : pair[0]; tree[(index / 2) | 0] = hash; indexqueue.push((index / 2) | 0); } i += 1; } return !proofIndices.length || (({}).hasOwnProperty.call(tree, 1) && tree[1].equals(root)); } verifyMultiProofWithFlags(root, leaves, proofs, proofFlag) { root = this.bufferify(root); leaves = leaves.map(this.bufferify); proofs = proofs.map(this.bufferify); const leavesLen = leaves.length; const totalHashes = proofFlag.length; const hashes = []; let leafPos = 0; let hashPos = 0; let proofPos = 0; for (let i = 0; i < totalHashes; i++) { const bufA = proofFlag[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) : proofs[proofPos++]; const bufB = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; const buffers = [bufA, bufB].sort(buffer_1.Buffer.compare); hashes[i] = this.hashFn(this.concatenator(buffers)); } return buffer_1.Buffer.compare(hashes[totalHashes - 1], root) === 0; } verifyMultiProofForUnevenTree(root, indices, leaves, leavesCount, proof) { root = this.bufferify(root); leaves = leaves.map(leaf => this.bufferify(leaf)); proof = proof.map(leaf => this.bufferify(leaf)); const computedRoot = this.calculateRootForUnevenTree(indices, leaves, leavesCount, proof); return root.equals(computedRoot); } /** * 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) => this.bufferToHex(value, false))); 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]; } /** * 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 * @param {Function} hashFunction - Hash function for hashing leaves and nodes * @param {Object} options - Additional options * @return {Boolean} * @example *```js *const verified = MerkleTree.verify(proof, leaf, root, sha256, options) *``` */ static verify(proof, targetNode, root, hashFn = sha256_1.default, options = {}) { const tree = new MerkleTree([], hashFn, options); return tree.verify(proof, targetNode, root); } /** * 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); } /** * resetTree * @desc Resets the tree by clearing the leaves and layers. * @example *```js *tree.resetTree() *``` */ resetTree() { this.leaves = []; this.layers = []; } /** * 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; } } /** * 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(); } isUnevenTree(treeLayers) { const depth = (treeLayers === null || treeLayers === void 0 ? void 0 : treeLayers.length) || this.getDepth(); return !this.isPowOf2(depth); } isPowOf2(v) { return v && !(v & (v - 1)); } isValidLeafIndex(idx) { return idx >= 0 && idx < this.getLeafCount(); } calculateRootForUnevenTree(leafIndices, leafHashes, totalLeavesCount, proofHashes) { const leafTuples = this.zip(leafIndices, leafHashes).sort(([indexA], [indexB]) => indexA - indexB); const leafTupleIndices = leafTuples.map(([index]) => index); const proofIndices = this.getProofIndicesForUnevenTree(leafTupleIndices, totalLeavesCount); let nextSliceStart = 0; const proofTuplesByLayers = []; for (let i = 0; i < proofIndices.length; i++) { const indices = proofIndices[i]; const sliceStart = nextSliceStart; nextSliceStart += indices.length; proofTuplesByLayers[i] = this.zip(indices, proofHashes.slice(sliceStart, nextSliceStart)); } const tree = [leafTuples]; for (let layerIndex = 0; layerIndex < proofTuplesByLayers.length; layerIndex++) { const currentLayer = proofTuplesByLayers[layerIndex].concat(tree[layerIndex]).sort(([indexA], [indexB]) => indexA - indexB) .map(([, hash]) => hash); const s = tree[layerIndex].map(([layerIndex]) => layerIndex); const parentIndices = [...new Set(s.map((index) => { if (index % 2 === 0) { return index / 2; } if (index % 2 === 0) { return (index + 1) / 2; } return (index - 1) / 2; }))]; const parentLayer = []; for (let i = 0; i < parentIndices.length; i++) { const parentNodeTreeIndex = parentIndices[i]; const bufA = currentLayer[i * 2]; const bufB = currentLayer[i * 2 + 1]; const hash = bufB ? this.hashFn(this.concatenator([bufA, bufB])) : bufA; parentLayer.push([parentNodeTreeIndex, hash]); } tree.push(parentLayer); } return tree[tree.length - 1][0][1]; } } exports.MerkleTree = MerkleTree; if (typeof window !== 'undefined') { ; window.MerkleTree = MerkleTree; } exports.default = MerkleTree;