merkletreejs
Version:
Construct Merkle Trees and verify proofs
1,314 lines (1,313 loc) • 46.9 kB
JavaScript
"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;