Add multiproof support

This commit is contained in:
Miguel Mota 2020-05-31 23:31:19 -07:00
parent b36c6f31a5
commit cea9442939
7 changed files with 1184 additions and 248 deletions

6
.eslintrc Normal file
View File

@ -0,0 +1,6 @@
{
"extends": "standard",
"rules": {
}
}

72
dist/index.d.ts vendored
View File

@ -1,6 +1,9 @@
/// <reference types="node" />
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`, an odd node will not have a pair generating the layer hash. */
singleOdd?: 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. */
@ -22,6 +25,7 @@ declare type TLayer = any;
*/
export declare class MerkleTree {
duplicateOdd: boolean;
singleOdd: boolean;
hashAlgo: (value: TValue) => THashAlgo;
hashLeaves: boolean;
isBitcoinTree: boolean;
@ -63,10 +67,20 @@ export declare class MerkleTree {
*const leaves = tree.getLeaves()
*```
*/
getLeaves(): any[];
getLeaves(data?: any[]): any[];
/**
* 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 array of all layers of Merkle Tree, including leaves and root.
* @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root.
* @return {Buffer[]}
* @example
*```js
@ -74,6 +88,36 @@ export declare class MerkleTree {
*```
*/
getLayers(): any[];
/**
* 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(): any;
/**
* 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(): any;
/**
* 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(): any;
/**
* getRoot
* @desc Returns the Merkle root hash as a Buffer.
@ -84,6 +128,15 @@ export declare class MerkleTree {
*```
*/
getRoot(): any;
/**
* getHexRoot
* @desc Returns the Merkle root hash as a hex string.
* @return {String}
* @example
*```js
*const root = tree.getHexRoot()
*```
*/
getHexRoot(): string;
/**
* getProof
@ -106,6 +159,12 @@ export declare class MerkleTree {
*```
*/
getProof(leaf: any, index?: any): any[];
getProofIndices(treeIndices: any, depth: any): any[];
getMultiProof(tree: any, indices: any): any[];
getHexMultiProof(tree: any, indices: any): string[];
bufIndexOf(arr: any, el: any): number;
getProofFlags(els: any, proofs: any): any[];
getPairElement(idx: any, layer: any): any;
getHexProof(leaf: any, index?: any): string[];
/**
* verify
@ -124,11 +183,20 @@ export declare class MerkleTree {
*```
*/
verify(proof: any, targetNode: any, root: any): boolean;
verifyMultiProof(root: any, indices: any, leaves: any, depth: any, proof: any): any;
getDepth(): number;
getLayersAsObject(): any;
print(): void;
toTreeString(): any;
toString(): any;
static bufferify(x: any): any;
static isHexStr(v: any): boolean;
static print(tree: any): void;
_bufferToHex(value: Buffer): string;
_bufferify(x: any): any;
_bufferifyFn(f: any): (x: any) => Buffer;
_isHexStr(v: any): boolean;
_log2(x: any): any;
_zip(a: any, b: any): any;
}
export default MerkleTree;

434
dist/index.js vendored
View File

@ -1,4 +1,35 @@
"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spread = (this && this.__spread) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
exports.__esModule = true;
var reverse = require("buffer-reverse");
var CryptoJS = require("crypto-js");
@ -42,11 +73,12 @@ var MerkleTree = /** @class */ (function () {
this.sortPairs = true;
}
this.duplicateOdd = !!options.duplicateOdd;
this.hashAlgo = bufferifyFn(hashAlgorithm);
this.singleOdd = !!options.singleOdd;
this.hashAlgo = this._bufferifyFn(hashAlgorithm);
if (this.hashLeaves) {
leaves = leaves.map(this.hashAlgo);
}
this.leaves = leaves.map(bufferify);
this.leaves = leaves.map(this._bufferify);
if (this.sortLeaves) {
this.leaves = this.leaves.sort(Buffer.compare);
}
@ -73,7 +105,7 @@ var MerkleTree = /** @class */ (function () {
continue;
}
else {
if (!this.duplicateOdd) {
if (!this.duplicateOdd && !this.singleOdd) {
this.layers[layerIndex].push(nodes[i]);
continue;
}
@ -81,14 +113,28 @@ var MerkleTree = /** @class */ (function () {
}
}
var left = nodes[i];
var right = i + 1 == nodes.length ? left : nodes[i + 1];
var right = i + 1 === nodes.length ? left : nodes[i + 1];
var data = null;
var combined = null;
if (this.isBitcoinTree) {
combined = [reverse(left), reverse(right)];
}
else {
combined = [left, right];
if (this.singleOdd) {
right = nodes[i + 1];
if (!left) {
combined = [right];
}
else if (!right) {
combined = [left];
}
else {
combined = [left, right];
}
}
else {
combined = [left, right];
}
}
if (this.sortPairs) {
combined.sort(Buffer.compare);
@ -113,12 +159,35 @@ var MerkleTree = /** @class */ (function () {
*const leaves = tree.getLeaves()
*```
*/
MerkleTree.prototype.getLeaves = function () {
MerkleTree.prototype.getLeaves = function (data) {
var _this = this;
if (Array.isArray(data)) {
if (this.hashLeaves) {
data = data.map(this.hashAlgo);
if (this.sortLeaves) {
data = data.sort(Buffer.compare);
}
}
return this.leaves.filter(function (x) { return _this.bufIndexOf(data, 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()
*```
*/
MerkleTree.prototype.getHexLeaves = function () {
var _this = this;
return this.leaves.map(function (x) { return _this._bufferToHex(x); });
};
/**
* getLayers
* @desc Returns array of all layers of Merkle Tree, including leaves and root.
* @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root.
* @return {Buffer[]}
* @example
*```js
@ -128,6 +197,62 @@ var MerkleTree = /** @class */ (function () {
MerkleTree.prototype.getLayers = function () {
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()
*```
*/
MerkleTree.prototype.getHexLayers = function () {
var _this = this;
return this.layers.reduce(function (acc, item, i) {
if (Array.isArray(item)) {
acc.push(item.map(function (x) { return _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()
*```
*/
MerkleTree.prototype.getLayersFlat = function () {
var layers = this.layers.reduce(function (acc, item, i) {
if (Array.isArray(item)) {
acc.unshift.apply(acc, __spread(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()
*```
*/
MerkleTree.prototype.getHexLayersFlat = function () {
var _this = this;
return this.getLayersFlat().map(function (x) { return _this._bufferToHex(x); });
};
/**
* getRoot
* @desc Returns the Merkle root hash as a Buffer.
@ -140,9 +265,17 @@ var MerkleTree = /** @class */ (function () {
MerkleTree.prototype.getRoot = function () {
return this.layers[this.layers.length - 1][0] || Buffer.from([]);
};
// TODO: documentation
/**
* getHexRoot
* @desc Returns the Merkle root hash as a hex string.
* @return {String}
* @example
*```js
*const root = tree.getHexRoot()
*```
*/
MerkleTree.prototype.getHexRoot = function () {
return bufferToHex(this.getRoot());
return this._bufferToHex(this.getRoot());
};
/**
* getProof
@ -165,7 +298,7 @@ var MerkleTree = /** @class */ (function () {
*```
*/
MerkleTree.prototype.getProof = function (leaf, index) {
leaf = bufferify(leaf);
leaf = this._bufferify(leaf);
var proof = [];
if (typeof index !== 'number') {
index = -1;
@ -213,8 +346,150 @@ var MerkleTree = /** @class */ (function () {
}
};
// TODO: documentation
MerkleTree.prototype.getProofIndices = function (treeIndices, depth) {
var e_1, _a, e_2, _b;
var leafCount = Math.pow(2, depth);
var maximalIndices = new Set();
try {
for (var treeIndices_1 = __values(treeIndices), treeIndices_1_1 = treeIndices_1.next(); !treeIndices_1_1.done; treeIndices_1_1 = treeIndices_1.next()) {
var index = treeIndices_1_1.value;
var x = leafCount + index;
while (x > 1) {
maximalIndices.add(x ^ 1);
x = (x / 2) | 0;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (treeIndices_1_1 && !treeIndices_1_1.done && (_a = treeIndices_1["return"])) _a.call(treeIndices_1);
}
finally { if (e_1) throw e_1.error; }
}
var a = treeIndices.map(function (index) { return leafCount + index; });
var b = Array.from(maximalIndices).sort(function (a, b) { return a - b; }).reverse();
maximalIndices = a.concat(b);
var redundantIndices = new Set();
var proof = [];
try {
for (var maximalIndices_1 = __values(maximalIndices), maximalIndices_1_1 = maximalIndices_1.next(); !maximalIndices_1_1.done; maximalIndices_1_1 = maximalIndices_1.next()) {
var index = maximalIndices_1_1.value;
if (!redundantIndices.has(index)) {
proof.push(index);
while (index > 1) {
redundantIndices.add(index);
if (!redundantIndices.has(index ^ 1))
break;
index = (index / 2) | 0;
}
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (maximalIndices_1_1 && !maximalIndices_1_1.done && (_b = maximalIndices_1["return"])) _b.call(maximalIndices_1);
}
finally { if (e_2) throw e_2.error; }
}
return proof.filter(function (index) {
return !treeIndices.includes(index - leafCount);
});
};
// TODO: documentation
MerkleTree.prototype.getMultiProof = function (tree, indices) {
var _this = this;
if (!indices) {
indices = tree;
tree = this.getLayersFlat();
if (!indices.every(function (x) { return typeof x === 'number'; })) {
var els = indices;
if (this.sortPairs) {
els = els.sort(Buffer.compare);
}
var ids = els.map(function (el) { return _this.bufIndexOf(_this.leaves, el); }).sort(function (a, b) { return a === b ? 0 : a > b ? 1 : -1; });
if (!ids.every(function (idx) { return idx !== -1; })) {
throw new Error('Element does not exist in Merkle tree');
}
var hashes_1 = [];
var proof = [];
var nextIds = [];
for (var i = 0; i < this.layers.length; i++) {
var layer = this.layers[i];
for (var j = 0; j < ids.length; j++) {
var idx = ids[j];
var pairElement = this.getPairElement(idx, layer);
hashes_1.push(layer[idx]);
if (pairElement) {
proof.push(pairElement);
}
nextIds.push((idx / 2) | 0);
}
ids = nextIds.filter(function (value, i, self) { return self.indexOf(value) === i; });
nextIds = [];
}
return proof.filter(function (value) { return !hashes_1.includes(value); });
}
}
return this.getProofIndices(indices, this._log2((tree.length / 2) | 0)).map(function (index) { return tree[index]; });
};
// TODO: documentation
MerkleTree.prototype.getHexMultiProof = function (tree, indices) {
return this.getMultiProof(tree, indices).map(this._bufferToHex);
};
// TODO: documentation
MerkleTree.prototype.bufIndexOf = function (arr, el) {
for (var i = 0; i < arr.length; i++) {
if (el.equals(arr[i])) {
return i;
}
}
return -1;
};
// TODO: documentation
MerkleTree.prototype.getProofFlags = function (els, proofs) {
var _this = this;
var ids = els.map(function (el) { return _this.bufIndexOf(_this.leaves, el); }).sort(function (a, b) { return a === b ? 0 : a > b ? 1 : -1; });
if (!ids.every(function (idx) { return idx !== -1; })) {
throw new Error('Element does not exist in Merkle tree');
}
var tested = [];
var flags = [];
var _loop_1 = function (index) {
var layer = this_1.layers[index];
ids = ids.reduce(function (ids, idx) {
var skipped = tested.includes(layer[idx]);
if (!skipped) {
var pairElement = _this.getPairElement(idx, layer);
var 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;
}, []);
};
var this_1 = this;
for (var index = 0; index < this.layers.length; index++) {
_loop_1(index);
}
return flags;
};
MerkleTree.prototype.getPairElement = function (idx, layer) {
var pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1;
if (pairIdx < layer.length) {
return layer[pairIdx];
}
else {
return null;
}
};
// TODO: documentation
MerkleTree.prototype.getHexProof = function (leaf, index) {
return this.getProof(leaf, index).map(function (x) { return bufferToHex(x.data); });
var _this = this;
return this.getProof(leaf, index).map(function (x) { return _this._bufferToHex(x.data); });
};
/**
* verify
@ -233,8 +508,8 @@ var MerkleTree = /** @class */ (function () {
*```
*/
MerkleTree.prototype.verify = function (proof, targetNode, root) {
var hash = bufferify(targetNode);
root = bufferify(root);
var hash = this._bufferify(targetNode);
root = this._bufferify(root);
if (!Array.isArray(proof) ||
!proof.length ||
!targetNode ||
@ -247,7 +522,7 @@ var MerkleTree = /** @class */ (function () {
var isLeftNode = null;
// NOTE: case for when proof is hex values only
if (typeof node === 'string') {
data = bufferify(node);
data = this._bufferify(node);
isLeftNode = true;
}
else {
@ -282,6 +557,56 @@ var MerkleTree = /** @class */ (function () {
return Buffer.compare(hash, root) === 0;
};
// TODO: documentation
MerkleTree.prototype.verifyMultiProof = function (root, indices, leaves, depth, proof) {
var e_3, _a, e_4, _b;
root = this._bufferify(root);
leaves = leaves.map(this._bufferify);
proof = proof.map(this._bufferify);
var tree = {};
try {
for (var _c = __values(this._zip(indices, leaves)), _d = _c.next(); !_d.done; _d = _c.next()) {
var _e = __read(_d.value, 2), index = _e[0], leaf = _e[1];
tree[(Math.pow(2, depth)) + index] = leaf;
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (_d && !_d.done && (_a = _c["return"])) _a.call(_c);
}
finally { if (e_3) throw e_3.error; }
}
try {
for (var _f = __values(this._zip(this.getProofIndices(indices, depth), proof)), _g = _f.next(); !_g.done; _g = _f.next()) {
var _h = __read(_g.value, 2), index = _h[0], proofitem = _h[1];
tree[index] = proofitem;
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (_g && !_g.done && (_b = _f["return"])) _b.call(_f);
}
finally { if (e_4) throw e_4.error; }
}
var indexqueue = Object.keys(tree).map(function (x) { return +x; }).sort(function (a, b) { return a - b; });
indexqueue = indexqueue.slice(0, indexqueue.length - 1);
var i = 0;
while (i < indexqueue.length) {
var 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));
};
// TODO: documentation
MerkleTree.prototype.getDepth = function () {
return this.getLayers().length - 1;
};
// TODO: documentation
MerkleTree.prototype.getLayersAsObject = function () {
var _a;
var layers = this.getLayers().map(function (x) { return x.map(function (x) { return x.toString('hex'); }); });
@ -303,7 +628,7 @@ var MerkleTree = /** @class */ (function () {
}
arr.push(obj);
}
objs.push.apply(objs, arr);
objs.push.apply(objs, __spread(arr));
}
return objs[0];
};
@ -322,47 +647,56 @@ var MerkleTree = /** @class */ (function () {
};
// TODO: documentation
MerkleTree.bufferify = function (x) {
return bufferify(x);
if (!Buffer.isBuffer(x)) {
// crypto-js support
if (typeof x === 'object' && x.words) {
return Buffer.from(x.toString(CryptoJS.enc.Hex), 'hex');
}
else if (MerkleTree.isHexStr(x)) {
return Buffer.from(x.replace(/^0x/, ''), 'hex');
}
else if (typeof x === 'string') {
return Buffer.from(x);
}
}
return x;
};
MerkleTree.isHexStr = function (v) {
return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v));
};
// TODO: documentation
MerkleTree.print = function (tree) {
console.log(tree.toString());
};
MerkleTree.prototype._bufferToHex = function (value) {
return '0x' + value.toString('hex');
};
MerkleTree.prototype._bufferify = function (x) {
return MerkleTree.bufferify(x);
};
MerkleTree.prototype._bufferifyFn = function (f) {
return function (x) {
var v = f(x);
if (Buffer.isBuffer(v)) {
return v;
}
if (this._isHexStr(v)) {
return Buffer.from(v, 'hex');
}
// crypto-js support
return Buffer.from(f(CryptoJS.enc.Hex.parse(x.toString('hex'))).toString(CryptoJS.enc.Hex), 'hex');
};
};
MerkleTree.prototype._isHexStr = function (v) {
return MerkleTree.isHexStr(v);
};
MerkleTree.prototype._log2 = function (x) {
return x === 1 ? 0 : 1 + this._log2((x / 2) | 0);
};
MerkleTree.prototype._zip = function (a, b) {
return a.map(function (e, i) { return [e, b[i]]; });
};
return MerkleTree;
}());
exports.MerkleTree = MerkleTree;
function bufferToHex(value) {
return '0x' + value.toString('hex');
}
function bufferify(x) {
if (!Buffer.isBuffer(x)) {
// crypto-js support
if (typeof x === 'object' && x.words) {
return Buffer.from(x.toString(CryptoJS.enc.Hex), 'hex');
}
else if (isHexStr(x)) {
return Buffer.from(x.replace(/^0x/, ''), 'hex');
}
else if (typeof x === 'string') {
return Buffer.from(x);
}
}
return x;
}
function bufferifyFn(f) {
return function (x) {
var v = f(x);
if (Buffer.isBuffer(v)) {
return v;
}
if (isHexStr(v)) {
return Buffer.from(v, 'hex');
}
// crypto-js support
return Buffer.from(f(CryptoJS.enc.Hex.parse(x.toString('hex'))).toString(CryptoJS.enc.Hex), 'hex');
};
}
function isHexStr(v) {
return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v));
}
exports["default"] = MerkleTree;

424
index.ts
View File

@ -5,6 +5,8 @@ import * as treeify from 'treeify'
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`, an odd node will not have a pair generating the layer hash. */
singleOdd?: 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. */
@ -28,6 +30,7 @@ type TLayer = any
*/
export class MerkleTree {
duplicateOdd: boolean
singleOdd: boolean
hashAlgo: (value: TValue) => THashAlgo
hashLeaves: boolean
isBitcoinTree: boolean
@ -59,7 +62,7 @@ export class MerkleTree {
*const tree = new MerkleTree(leaves, sha256)
*```
*/
constructor(leaves, hashAlgorithm, options: Options = {}) {
constructor (leaves, hashAlgorithm, options: Options = {}) {
this.isBitcoinTree = !!options.isBitcoinTree
this.hashLeaves = !!options.hashLeaves
this.sortLeaves = !!options.sortLeaves
@ -72,12 +75,14 @@ export class MerkleTree {
}
this.duplicateOdd = !!options.duplicateOdd
this.hashAlgo = bufferifyFn(hashAlgorithm)
this.singleOdd = !!options.singleOdd
this.hashAlgo = this._bufferifyFn(hashAlgorithm)
if (this.hashLeaves) {
leaves = leaves.map(this.hashAlgo)
}
this.leaves = leaves.map(bufferify)
this.leaves = leaves.map(this._bufferify)
if (this.sortLeaves) {
this.leaves = this.leaves.sort(Buffer.compare)
}
@ -87,15 +92,13 @@ export class MerkleTree {
}
// TODO: documentation
createHashes(nodes) {
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]
@ -111,24 +114,34 @@ export class MerkleTree {
this.layers[layerIndex].push(hash)
continue
} else {
if (!this.duplicateOdd) {
if (!this.duplicateOdd && !this.singleOdd) {
this.layers[layerIndex].push(nodes[i])
continue
}
}
}
}
const left = nodes[i]
const right = i + 1 == nodes.length ? left : nodes[i + 1];
let right = i + 1 === nodes.length ? left : nodes[i + 1]
let data = null
let combined = null
if (this.isBitcoinTree) {
combined = [reverse(left), reverse(right)]
} else {
combined = [left, right]
if (this.singleOdd) {
right = nodes[i + 1]
if (!left) {
combined = [right]
} else if (!right) {
combined = [left]
} else {
combined = [left, right]
}
} else {
combined = [left, right]
}
}
if (this.sortPairs) {
@ -160,23 +173,106 @@ export class MerkleTree {
*const leaves = tree.getLeaves()
*```
*/
getLeaves() {
getLeaves (data?: any[]) {
if (Array.isArray(data)) {
if (this.hashLeaves) {
data = data.map(this.hashAlgo)
if (this.sortLeaves) {
data = data.sort(Buffer.compare)
}
}
return this.leaves.filter(x => this.bufIndexOf(data, 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 array of all layers of Merkle Tree, including leaves and root.
* @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root.
* @return {Buffer[]}
* @example
*```js
*const layers = tree.getLayers()
*```
*/
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.
@ -186,13 +282,21 @@ export class MerkleTree {
*const root = tree.getRoot()
*```
*/
getRoot() {
getRoot () {
return this.layers[this.layers.length - 1][0] || Buffer.from([])
}
// TODO: documentation
getHexRoot() {
return bufferToHex(this.getRoot())
/**
* 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())
}
/**
@ -215,8 +319,8 @@ export class MerkleTree {
*const proof = tree.getProof(leaves[2], 2)
*```
*/
getProof(leaf, index?) {
leaf = bufferify(leaf)
getProof (leaf, index?) {
leaf = this._bufferify(leaf)
const proof = []
if (typeof index !== 'number') {
@ -253,7 +357,6 @@ export class MerkleTree {
return proof
} else {
// Proof Generation for Non-Bitcoin Trees
for (let i = 0; i < this.layers.length; i++) {
@ -270,7 +373,6 @@ export class MerkleTree {
// set index to parent index
index = (index / 2) | 0
}
return proof
@ -278,8 +380,143 @@ export class MerkleTree {
}
// TODO: documentation
getHexProof(leaf, index?) {
return this.getProof(leaf, index).map(x => bufferToHex(x.data))
getProofIndices (treeIndices, depth) {
const leafCount = 2 ** depth
let maximalIndices :any = 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: any, b: any) => 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 as number ^ 1)) break
index = (index as number / 2) | 0
}
}
}
return proof.filter(index => {
return !treeIndices.includes(index - leafCount)
})
}
// TODO: documentation
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.bufIndexOf(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.getPairElement(idx, layer)
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])
}
// TODO: documentation
getHexMultiProof (tree, indices) {
return this.getMultiProof(tree, indices).map(this._bufferToHex)
}
// TODO: documentation
bufIndexOf (arr, el) {
for (let i = 0; i < arr.length; i++) {
if (el.equals(arr[i])) {
return i
}
}
return -1
}
// TODO: documentation
getProofFlags (els, proofs) {
let ids = els.map((el) => this.bufIndexOf(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.getPairElement(idx, layer)
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
}
getPairElement (idx, layer) {
const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1
if (pairIdx < layer.length) {
return layer[pairIdx]
} else {
return null
}
}
// TODO: documentation
getHexProof (leaf, index?) {
return this.getProof(leaf, index).map(x => this._bufferToHex(x.data))
}
/**
@ -298,9 +535,9 @@ export class MerkleTree {
*const verified = tree.verify(proof, leaves[2], root)
*```
*/
verify(proof, targetNode, root) {
let hash = bufferify(targetNode)
root = bufferify(root)
verify (proof, targetNode, root) {
let hash = this._bufferify(targetNode)
root = this._bufferify(root)
if (!Array.isArray(proof) ||
!proof.length ||
@ -316,7 +553,7 @@ export class MerkleTree {
// NOTE: case for when proof is hex values only
if (typeof node === 'string') {
data = bufferify(node)
data = this._bufferify(node)
isLeftNode = true
} else {
data = node.data
@ -332,20 +569,19 @@ export class MerkleTree {
hash = this.hashAlgo(Buffer.concat(buffers))
hash = reverse(this.hashAlgo(hash))
} else {
if (this.sortPairs) {
if (Buffer.compare(hash, data) === -1) {
buffers.push(hash, data)
hash = this.hashAlgo(Buffer.concat(buffers));
hash = this.hashAlgo(Buffer.concat(buffers))
} else {
buffers.push(data, hash)
hash = this.hashAlgo(Buffer.concat(buffers));
hash = this.hashAlgo(Buffer.concat(buffers))
}
} else {
buffers.push(hash);
buffers[isLeftNode ? 'unshift' : 'push'](data);
hash = this.hashAlgo(Buffer.concat(buffers));
buffers.push(hash)
buffers[isLeftNode ? 'unshift' : 'push'](data)
hash = this.hashAlgo(Buffer.concat(buffers))
}
}
}
@ -354,7 +590,39 @@ export class MerkleTree {
}
// TODO: documentation
getLayersAsObject() {
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[(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))
}
// TODO: documentation
getDepth () {
return this.getLayers().length - 1
}
// TODO: documentation
getLayersAsObject () {
const layers = this.getLayers().map(x => x.map(x => x.toString('hex')))
const objs = []
for (let i = 0; i < layers.length; i++) {
@ -383,69 +651,81 @@ export class MerkleTree {
}
// TODO: documentation
print() {
print () {
MerkleTree.print(this)
}
// TODO: documentation
toTreeString() {
toTreeString () {
const obj = this.getLayersAsObject()
return treeify.asTree(obj, true)
}
// TODO: documentation
toString() {
toString () {
return this.toTreeString()
}
// TODO: documentation
static bufferify(x) {
return bufferify(x)
static bufferify (x) {
if (!Buffer.isBuffer(x)) {
// crypto-js support
if (typeof x === 'object' && x.words) {
return Buffer.from(x.toString(CryptoJS.enc.Hex), 'hex')
} else if (MerkleTree.isHexStr(x)) {
return Buffer.from(x.replace(/^0x/, ''), 'hex')
} else if (typeof x === 'string') {
return Buffer.from(x)
}
}
return x
}
static isHexStr (v) {
return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v))
}
// TODO: documentation
static print(tree) {
static print (tree) {
console.log(tree.toString())
}
}
function bufferToHex(value: Buffer) {
return '0x' + value.toString('hex')
}
_bufferToHex (value: Buffer) {
return '0x' + value.toString('hex')
}
function bufferify(x) {
if (!Buffer.isBuffer(x)) {
// crypto-js support
if (typeof x === 'object' && x.words) {
return Buffer.from(x.toString(CryptoJS.enc.Hex), 'hex')
} else if (isHexStr(x)) {
return Buffer.from(x.replace(/^0x/, ''), 'hex')
} else if (typeof x === 'string') {
return Buffer.from(x)
_bufferify (x) {
return MerkleTree.bufferify(x)
}
_bufferifyFn (f) {
return function (x) {
const v = f(x)
if (Buffer.isBuffer(v)) {
return v
}
if (this._isHexStr(v)) {
return Buffer.from(v, 'hex')
}
// crypto-js support
return Buffer.from(f(CryptoJS.enc.Hex.parse(x.toString('hex'))).toString(CryptoJS.enc.Hex), 'hex')
}
}
return x
}
function bufferifyFn(f) {
return function (x) {
const v = f(x)
if (Buffer.isBuffer(v)) {
return v
}
if (isHexStr(v)) {
return Buffer.from(v, 'hex')
}
// crypto-js support
return Buffer.from(f(CryptoJS.enc.Hex.parse(x.toString('hex'))).toString(CryptoJS.enc.Hex), 'hex')
_isHexStr (v) {
return MerkleTree.isHexStr(v)
}
}
function isHexStr(v) {
return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v))
_log2 (x) {
return x === 1 ? 0 : 1 + this._log2((x / 2) | 0)
}
_zip (a, b) {
return a.map((e, i) => [e, b[i]])
}
}
export default MerkleTree

View File

@ -4,10 +4,13 @@
"description": "Construct Merkle Trees and verify proofs",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"files": [
"dist"
],
"scripts": {
"test": "tape test/*.js",
"build": "rm -rf dist/ && tsc",
"lint": "standardx --fix index.ts test/*.js",
"docs:md": "rm -rf docs/ && typedoc --theme markdown index.ts --out docs --mdEngine github --mdDocusaurus --mdHideSources"
},
"repository": {
@ -39,9 +42,13 @@
"homepage": "https://github.com/miguelmota/merkletreejs",
"devDependencies": {
"@types/node": "^11.12.1",
"@typescript-eslint/eslint-plugin": "^3.0.2",
"@typescript-eslint/parser": "^3.0.2",
"crypto": "0.0.3",
"ethereumjs-util": "6.1.0",
"sha1": "^1.1.1",
"standard": "^14.3.4",
"standardx": "^5.0.0",
"tape": "^4.9.2",
"typedoc": "^0.14.2",
"typedoc-plugin-markdown": "^1.2.1",
@ -56,5 +63,15 @@
"is-buffer": "^2.0.3",
"merkle-lib": "^2.0.10",
"treeify": "^1.1.0"
},
"eslintConfig": {
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
},
"standardx": {
"parser": "@typescript-eslint/parser",
"plugins": [ "@typescript-eslint/eslint-plugin" ]
}
}

View File

@ -1,5 +1,7 @@
/* eslint camelcase: 0 */
const test = require('tape')
const {keccak} = require('ethereumjs-util')
const { keccak } = require('ethereumjs-util')
const crypto = require('crypto')
const CryptoJS = require('crypto-js')
const SHA256 = require('crypto-js/sha256')
@ -24,7 +26,7 @@ test('sha256 with keccak leaves with duplicate odd option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(x => keccak(x))
const tree = new MerkleTree(leaves, sha256, {duplicateOdd: true})
const tree = new MerkleTree(leaves, sha256, { duplicateOdd: true })
const root = 'bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1'
t.equal(tree.getRoot().toString('hex'), root)
@ -44,7 +46,7 @@ test('sha256 with sort pairs option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(x => sha256(x))
const tree = new MerkleTree(leaves, sha256, {sortPairs: true})
const tree = new MerkleTree(leaves, sha256, { sortPairs: true })
const root = 'a30ba95a1a5dc397fe45ea20105363b08d682b864a28f4940419a29349a28325'
t.equal(tree.getRoot().toString('hex'), root)
@ -54,7 +56,7 @@ test('keccak with sort leaves and sort pairs option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].map(x => keccak(x))
const tree = new MerkleTree(leaves, keccak, {sortLeaves: true, sortPairs: true})
const tree = new MerkleTree(leaves, keccak, { sortLeaves: true, sortPairs: true })
const root = '60219f87561939610b484575e45c6e81156a53b86d7cd16640d930d14f21758e'
t.equal(tree.getRoot().toString('hex'), root)
@ -64,7 +66,7 @@ test('keccak with sort option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].map(x => keccak(x))
const tree = new MerkleTree(leaves, keccak, {sort: true})
const tree = new MerkleTree(leaves, keccak, { sort: true })
const root = '60219f87561939610b484575e45c6e81156a53b86d7cd16640d930d14f21758e'
t.equal(tree.getRoot().toString('hex'), root)
@ -74,7 +76,7 @@ test('sha256 with sha256 leaves and sort pairs option and duplicate odd option',
t.plan(1)
const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(x => sha256(x))
const tree = new MerkleTree(leaves, sha256, {sortPairs: true, duplicateOdd: true})
const tree = new MerkleTree(leaves, sha256, { sortPairs: true, duplicateOdd: true })
const root = 'a5260b2a7ec31584e5d5689a5628c2b3d949e2397334fd71c107478e5f887eaf'
t.equal(tree.getRoot().toString('hex'), root)
@ -84,7 +86,7 @@ test('sha256 with hash leaves option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c', 'd', 'e', 'f']
const tree = new MerkleTree(leaves, sha256, {hashLeaves: true})
const tree = new MerkleTree(leaves, sha256, { hashLeaves: true })
const root = '1f7379539707bcaea00564168d1d4d626b09b73f8a2a365234c62d763f854da2'
t.equal(tree.getRoot().toString('hex'), root)
@ -94,7 +96,7 @@ test('sha256 with hash leaves option and duplicate odd option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c', 'd', 'e', 'f']
const tree = new MerkleTree(leaves, sha256, {hashLeaves: true, duplicateOdd: true})
const tree = new MerkleTree(leaves, sha256, { hashLeaves: true, duplicateOdd: true })
const root = '44205acec5156114821f1f71d87c72e0de395633cd1589def6d4444cc79f8103'
t.equal(tree.getRoot().toString('hex'), root)
@ -114,7 +116,7 @@ test('crypto-js - sha256 with keccak leaves and duplicate odd option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(x => keccak(x))
const tree = new MerkleTree(leaves, SHA256, {duplicateOdd: true})
const tree = new MerkleTree(leaves, SHA256, { duplicateOdd: true })
const root = 'bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1'
t.equal(tree.getRoot().toString('hex'), root)
@ -124,7 +126,7 @@ test('crypto-js - SHA256 with SHA256 leaves and duplicate odd option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(SHA256)
const tree = new MerkleTree(leaves, SHA256, {duplicateOdd: true})
const tree = new MerkleTree(leaves, SHA256, { duplicateOdd: true })
const root = 'd31a37ef6ac14a2db1470c4316beb5592e6afd4465022339adafda76a18ffabe'
t.equal(tree.getRoot().toString('hex'), root)
@ -145,7 +147,7 @@ test('crypto-js - SHA256 with keccak leaves and duplicate odd option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(x => keccak(x))
const tree = new MerkleTree(leaves, SHA256, {duplicateOdd: true})
const tree = new MerkleTree(leaves, SHA256, { duplicateOdd: true })
const root = 'bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1'
t.equal(tree.getRoot().toString('hex'), root)
@ -211,7 +213,7 @@ test('solidity keccak [keccak-256] with duplicate odd option', t => {
t.deepEqual(leaves.map(x => x.toString('hex')), [a_hash, b_hash, c_hash])
const tree = new MerkleTree(leaves, keccak, {duplicateOdd: true})
const tree = new MerkleTree(leaves, keccak, { duplicateOdd: true })
const layers = tree.getLayers().slice(1) // no leaves
const layer_1 = keccak(Buffer.concat([leaves[0], leaves[1]])).toString('hex')
const layer_2 = keccak(Buffer.concat([leaves[2], leaves[2]])).toString('hex')
@ -277,110 +279,110 @@ test('sha-256 with option.isBitcoinTree', t => {
* http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html
*/
const txHashes = [
"00baf6626abc2df808da36a518c69f09b0d2ed0a79421ccfde4f559d2e42128b",
"91c5e9f288437262f218c60f986e8bc10fb35ab3b9f6de477ff0eb554da89dea",
"46685c94b82b84fa05b6a0f36de6ff46475520113d5cb8c6fb060e043a0dbc5c",
"ba7ed2544c78ad793ef5bb0ebe0b1c62e8eb9404691165ffcb08662d1733d7a8",
"b8dc1b7b7ed847c3595e7b02dbd7372aa221756b718c5f2943c75654faf48589",
"25074ef168a061fcc8663b4554a31b617683abc33b72d2e2834f9329c93f8214",
"0fb8e311bffffadc6dc4928d7da9e142951d3ba726c8bde2cf1489b62fb9ebc5",
"c67c79204e681c8bb453195db8ca7d61d4692f0098514ca198ccfd1b59dbcee3",
"bd27570a6cbd8ad026bfdb8909fdae9321788f0643dea195f39cd84a60a1901b",
"41a06e53ffc5108358ddcec05b029763d714ae9f33c5403735e8dee78027fe74",
"cc2696b44cb07612c316f24c07092956f7d8b6e0d48f758572e0d611d1da6fb9",
"8fc508772c60ace7bfeb3f5f3a507659285ea6f351ac0474a0a9710c7673d4fd",
"62fed508c095446d971580099f976428fc069f32e966a40a991953b798b28684",
"928eadbc39196b95147416eedf6f635dcff818916da65419904df8fde977d5db",
"b137e685df7c1dffe031fb966a0923bb5d0e56f381e730bc01c6d5244cfe47c1",
"b92207cee1f9e0bfbd797b05a738fab9de9c799b74f54f6b922f20bd5ec23dd6",
"29d6f37ada0481375b6903c6480a81f8deaf2dcdba03411ed9e8d3e5684d02dd",
"48158deb116e4fd0429fbbbae61e8e68cb6d0e0c4465ff9a6a990037f88c489c",
"be64ea86960864cc0a0236bbb11f232faf5b19ae6e2c85518628f5fae37ec1ca",
"081363552e9fff7461f1fc6663e1abd0fb2dd1c54931e177479a18c4c26260e8",
"eb87c25dd2b2537b1ff3dbabc420e422e2a801f1bededa6fa49ef7980feaef70",
"339e16fcc11deb61ccb548239270af43f5ad34c321416bada4b8d66467b1c697",
"4ad6417a3a04179482ed2e4b7251c396e38841c6fba8d2ce9543337ab7c93c02",
"c28a45cded020bf424b400ffc9cb6f2f85601934f18c34a4f78283247192056a",
"882037cc9e3ee6ddc2d3eba86b7ca163533b5d3cbb16eaa38696bb0a2ea1137e",
"179bb936305b46bb0a9df330f8701984c725a60e063ad5892fa97461570b5c04",
"9517c585d1578cb327b7988f38e1a15c663955ea288a2292b40d27f232fbb980",
"2c7e07d0cf42e5520bcbfe2f5ef63761a9ab9d7ccb00ea346195eae030f3b86f",
"534f631fc42ae2d309670e01c7a0890e4bfb65bae798522ca14df09c81b09734",
"104643385619adb848593eb668a8066d1f32650edf35e74b0fc3306cb6719448",
"87ac990808239c768182a752f4f71cd98558397072883c7e137efb49d22b9231",
"9b3e2f1c47d59a444e9b6dc725f0ac6baf160d22f3a9d399434e5e65b14eccb0",
"fbe123066ae5add633a542f151663db4eb5a7053e388faadb40240671ae1b09b",
"1dd07e92e20b3cb9208af040031f7cfc4efd46cc31ec27be20a1047965a42849",
"2709bb9ed27353c1fd76b9240cab7576a44de68945e256ad44b2cb8d849a8060",
"d0174db2c712573432a7869c1508f371f3a1058aeedddc1b53a7e04d7c56c725",
"b4a16f724cddb8f77ddf3d2146a12c4be13d503885eaba3518a03da005009f62",
"2aa706d75decbe57745e01d46f9f5d30a08dedaf3288cee14cc4948e3684e1d4",
"ee49c5f6a5129ccaf2abebbc1d6d07a402a600af6221476b89aafaa683ca95b7",
"bea1011c77874845e9b4c876ed2ceebd530d428dd4a564ad003d9211d40bb091",
"f1e88ffc2b1de2aa4827002f06943ce5468735f7433f960bf01e75885b9f832b",
"19247d017e002fb9143d1a89eb921222a94f8a3d0faaf2e05b0f594989edc4c4",
"13f714ff62ee7d26b6d69ca980c141ebc54e9f71d2697083fe6c5efc1b02bd0f",
"0c78cbb8246572f015fbdc53dc9798fa54d1119ec77c1f07ac310bcbcc40dbf8",
"4bcde0ef92a6d24a2be7be50ac5e5299d776df2e6229ba5d475c2491da94f255",
"0cfd7d1058502730cf0b2ffa880c78ef534651e06832b5d87c0d7eb84eac5b0c",
"3a168f794d6e0c614429ad874317cc4cd67a8177214880ff6ea1704d29228c2f",
"f9a555d817334397b402518d6fd959dc73d981ee7f5fe67969b63974ebbef127",
"24b52691f66eaed4ce391a473902e309018257c98b9f02aaa33b399c9e6f3168",
"a37b5e623dc26a180d9e2c9510d06885b014e86e533adb63ec40511e10b55046",
"9dbaeb485e51d9e25a5621dc46e0bc0aaf51fb26be5acc4e370b96f62c469b80",
"a6431d3d39f6c38c5df48405090752cab03bfdf5c77cf881b18a946807fba74a",
"faa77e309f125373acf19855dd496fffe2f74962e545420844557a3adc7ebc11",
"3523f52543ecfea2f78486dc91550fad0e6467d46d9d9c82ca63b2e0230bfa71",
"a0583e358e42d77d18d1fd0533ff0a65615fc3b3112061ef92f168a00bf640c1",
"42ae900888d5e5dde59c8e3d06e13db9e84ef05d27726d4b67fd00c50cd9406a",
"154940777d3ff78f592ef02790131a59263c36b4958bbc836f9a767ea1a9f178",
"6a0337de6ac75eecf748306e8ebc5bfe5c811a1481ae50f6956a9e7f26a679f5",
"c99530c2148e09688d0b88795625943371183bf1f5d56c7446c6ed51ea133589",
"626421dbe8ad6a0fd0d622d5dd3308a1cdc00b98575a41a91fe01a439e6f40bd",
"b2f3a559f605a158cc395126c3cf394a7e92a53b7514c75157e1dc43a6c7f93e",
"dffe06d1bea81f2a01c76786404bb867258f9e68013bf25454097ce935090738",
"0860159ec7a2a51ce107c182a988c40b4bc2057a734354a1219b6c65e72640ed",
"a405ff1bb51846b1867acc0b0da17f6f9616e592a0a7ff5ef3297c1ecfd60911",
"a7d451924263284765f6343bca8a21b79b89ebfe611c7355dd88e0ec1c29e232",
"41c758d08a4d3fe4d90645711589b832a2cd54dd25bd5b66e463e5d389a53aff",
"a05c1a93a521fa5dbc1790cfbb808893453a428a65f2c6b2d51249fbb12db309",
"90997920aa9786e10f513cfdd14e294feee6739cee1ab61b3fb1e3f42e7a915d",
"99fcb9cb62c20a3135484a70bd3f73983f8f3b7b26266dad34f3993958a7642c",
"e05f9a668b37e5f78bd3b9d047f29f92b33a87f11dd48390410006f858188b7b",
"56dbc65895f7992da4a6985e7edba4d1c00879f1b28442c644c8a07658ceab27",
"5e9004fe262b829563d0804656ba68b1de1690401f08a1915273230d8c902fc0",
"1ea9ed3717523c5e304b7a7ac8058a87fb4f3fed8c6004769f226c9bb67e79c5",
"f0f1a4c009b3f1b2729e89898e2f5c0fcdc312edea5df884a9c897cb90e4c566",
"b5bb4ddf04863e6a60f33cb96c20dac8175d3bae55f335781503143c97a50e43",
"f14cc97a20c6f627b4b78301352ae35463bc359362589cd178a06c0fa90850b7",
"628801c8f614015c0fa0ccb2768cccc3e7b9d41ceed06071ce2534d31f7236d6",
"3be1013c8f8da150e2195408093153b55b08b037fd92db8bb5e803f4c2538aae",
"c9e1f8777685f54ba65c4e02915fd649ee1edcbf9c77ddf584b943d27efb86c3",
"4274e92ed3bd02eb101baa5fb8ff7b96236830762d08273749fbb5166db8ab0b",
"aa84c955bea04c7cee8f5bbbec97d25930fcaca363eed1b8cad37b931556d3e3",
"d6a29c948677fb1f71aaf16debc3d071a4dd349458eb9e056dce3a000ff853da",
"ba84bdb3d78367ca365016ac4bff9269576eb010f874c2967af73e0de5638de0",
"1546c79951e3b541bc64d1957b565b7a2850fc87192c7b374aee6cfc69b9805e",
"f119227d492ebe27fe9aae321980802454dfa64b2691efbe796c5075d5b07f62",
"b8cf13d64818b32f96bbb585998b1bc9505f6a94055488e5a71fee9479c6f2a9",
"1aaf459705b6afef2d7b83e3f181f1af55be0813daf55edce104cc59abc28ed7",
"61ac185c8f520b5e3134953dc52ff292a40e1e96b088dab259558a9d240ec02f",
"2da96e3154d7ec2329f787b73cb8a436b92d64cf3cc28e920d073279ea73b5f8",
"1c4d72ce733b971b9ec4e24f37d733355f6f2ea635cc67ffb3e22748484df446",
"2a6f89769f3272ac8c7a36a42a57627eca6b260ab2c76d8046a27d44d4034893",
"f8d11df51a2cc113698ebf39a958fe81179d7d973d2044322771c0fe63f4d7c9",
"f2287f17a4fa232dca5715c24a92f7112402a8101b9a7b276fb8c8f617376b90",
"bb5ee510a4fda29cae30c97e7eee80569d3ec3598465f2d7e0674c395e0256e9",
"647ab8c84365620d60f2523505d14bd230b5e650c96dee48be47770063ee7461",
"34b06018fcc33ba6ebb01198d785b0629fbdc5d1948f688059158f053093f08b",
"ff58b258dab0d7f36a2908e6c75229ce308d34806289c912a1a5f39a5aa71f9f",
"232fc124803668a9f23b1c3bcb1134274303f5c0e1b0e27c9b6c7db59f0e2a4d",
"27a0797cc5b042ba4c11e72a9555d13a67f00161550b32ede0511718b22dbc2c",
'00baf6626abc2df808da36a518c69f09b0d2ed0a79421ccfde4f559d2e42128b',
'91c5e9f288437262f218c60f986e8bc10fb35ab3b9f6de477ff0eb554da89dea',
'46685c94b82b84fa05b6a0f36de6ff46475520113d5cb8c6fb060e043a0dbc5c',
'ba7ed2544c78ad793ef5bb0ebe0b1c62e8eb9404691165ffcb08662d1733d7a8',
'b8dc1b7b7ed847c3595e7b02dbd7372aa221756b718c5f2943c75654faf48589',
'25074ef168a061fcc8663b4554a31b617683abc33b72d2e2834f9329c93f8214',
'0fb8e311bffffadc6dc4928d7da9e142951d3ba726c8bde2cf1489b62fb9ebc5',
'c67c79204e681c8bb453195db8ca7d61d4692f0098514ca198ccfd1b59dbcee3',
'bd27570a6cbd8ad026bfdb8909fdae9321788f0643dea195f39cd84a60a1901b',
'41a06e53ffc5108358ddcec05b029763d714ae9f33c5403735e8dee78027fe74',
'cc2696b44cb07612c316f24c07092956f7d8b6e0d48f758572e0d611d1da6fb9',
'8fc508772c60ace7bfeb3f5f3a507659285ea6f351ac0474a0a9710c7673d4fd',
'62fed508c095446d971580099f976428fc069f32e966a40a991953b798b28684',
'928eadbc39196b95147416eedf6f635dcff818916da65419904df8fde977d5db',
'b137e685df7c1dffe031fb966a0923bb5d0e56f381e730bc01c6d5244cfe47c1',
'b92207cee1f9e0bfbd797b05a738fab9de9c799b74f54f6b922f20bd5ec23dd6',
'29d6f37ada0481375b6903c6480a81f8deaf2dcdba03411ed9e8d3e5684d02dd',
'48158deb116e4fd0429fbbbae61e8e68cb6d0e0c4465ff9a6a990037f88c489c',
'be64ea86960864cc0a0236bbb11f232faf5b19ae6e2c85518628f5fae37ec1ca',
'081363552e9fff7461f1fc6663e1abd0fb2dd1c54931e177479a18c4c26260e8',
'eb87c25dd2b2537b1ff3dbabc420e422e2a801f1bededa6fa49ef7980feaef70',
'339e16fcc11deb61ccb548239270af43f5ad34c321416bada4b8d66467b1c697',
'4ad6417a3a04179482ed2e4b7251c396e38841c6fba8d2ce9543337ab7c93c02',
'c28a45cded020bf424b400ffc9cb6f2f85601934f18c34a4f78283247192056a',
'882037cc9e3ee6ddc2d3eba86b7ca163533b5d3cbb16eaa38696bb0a2ea1137e',
'179bb936305b46bb0a9df330f8701984c725a60e063ad5892fa97461570b5c04',
'9517c585d1578cb327b7988f38e1a15c663955ea288a2292b40d27f232fbb980',
'2c7e07d0cf42e5520bcbfe2f5ef63761a9ab9d7ccb00ea346195eae030f3b86f',
'534f631fc42ae2d309670e01c7a0890e4bfb65bae798522ca14df09c81b09734',
'104643385619adb848593eb668a8066d1f32650edf35e74b0fc3306cb6719448',
'87ac990808239c768182a752f4f71cd98558397072883c7e137efb49d22b9231',
'9b3e2f1c47d59a444e9b6dc725f0ac6baf160d22f3a9d399434e5e65b14eccb0',
'fbe123066ae5add633a542f151663db4eb5a7053e388faadb40240671ae1b09b',
'1dd07e92e20b3cb9208af040031f7cfc4efd46cc31ec27be20a1047965a42849',
'2709bb9ed27353c1fd76b9240cab7576a44de68945e256ad44b2cb8d849a8060',
'd0174db2c712573432a7869c1508f371f3a1058aeedddc1b53a7e04d7c56c725',
'b4a16f724cddb8f77ddf3d2146a12c4be13d503885eaba3518a03da005009f62',
'2aa706d75decbe57745e01d46f9f5d30a08dedaf3288cee14cc4948e3684e1d4',
'ee49c5f6a5129ccaf2abebbc1d6d07a402a600af6221476b89aafaa683ca95b7',
'bea1011c77874845e9b4c876ed2ceebd530d428dd4a564ad003d9211d40bb091',
'f1e88ffc2b1de2aa4827002f06943ce5468735f7433f960bf01e75885b9f832b',
'19247d017e002fb9143d1a89eb921222a94f8a3d0faaf2e05b0f594989edc4c4',
'13f714ff62ee7d26b6d69ca980c141ebc54e9f71d2697083fe6c5efc1b02bd0f',
'0c78cbb8246572f015fbdc53dc9798fa54d1119ec77c1f07ac310bcbcc40dbf8',
'4bcde0ef92a6d24a2be7be50ac5e5299d776df2e6229ba5d475c2491da94f255',
'0cfd7d1058502730cf0b2ffa880c78ef534651e06832b5d87c0d7eb84eac5b0c',
'3a168f794d6e0c614429ad874317cc4cd67a8177214880ff6ea1704d29228c2f',
'f9a555d817334397b402518d6fd959dc73d981ee7f5fe67969b63974ebbef127',
'24b52691f66eaed4ce391a473902e309018257c98b9f02aaa33b399c9e6f3168',
'a37b5e623dc26a180d9e2c9510d06885b014e86e533adb63ec40511e10b55046',
'9dbaeb485e51d9e25a5621dc46e0bc0aaf51fb26be5acc4e370b96f62c469b80',
'a6431d3d39f6c38c5df48405090752cab03bfdf5c77cf881b18a946807fba74a',
'faa77e309f125373acf19855dd496fffe2f74962e545420844557a3adc7ebc11',
'3523f52543ecfea2f78486dc91550fad0e6467d46d9d9c82ca63b2e0230bfa71',
'a0583e358e42d77d18d1fd0533ff0a65615fc3b3112061ef92f168a00bf640c1',
'42ae900888d5e5dde59c8e3d06e13db9e84ef05d27726d4b67fd00c50cd9406a',
'154940777d3ff78f592ef02790131a59263c36b4958bbc836f9a767ea1a9f178',
'6a0337de6ac75eecf748306e8ebc5bfe5c811a1481ae50f6956a9e7f26a679f5',
'c99530c2148e09688d0b88795625943371183bf1f5d56c7446c6ed51ea133589',
'626421dbe8ad6a0fd0d622d5dd3308a1cdc00b98575a41a91fe01a439e6f40bd',
'b2f3a559f605a158cc395126c3cf394a7e92a53b7514c75157e1dc43a6c7f93e',
'dffe06d1bea81f2a01c76786404bb867258f9e68013bf25454097ce935090738',
'0860159ec7a2a51ce107c182a988c40b4bc2057a734354a1219b6c65e72640ed',
'a405ff1bb51846b1867acc0b0da17f6f9616e592a0a7ff5ef3297c1ecfd60911',
'a7d451924263284765f6343bca8a21b79b89ebfe611c7355dd88e0ec1c29e232',
'41c758d08a4d3fe4d90645711589b832a2cd54dd25bd5b66e463e5d389a53aff',
'a05c1a93a521fa5dbc1790cfbb808893453a428a65f2c6b2d51249fbb12db309',
'90997920aa9786e10f513cfdd14e294feee6739cee1ab61b3fb1e3f42e7a915d',
'99fcb9cb62c20a3135484a70bd3f73983f8f3b7b26266dad34f3993958a7642c',
'e05f9a668b37e5f78bd3b9d047f29f92b33a87f11dd48390410006f858188b7b',
'56dbc65895f7992da4a6985e7edba4d1c00879f1b28442c644c8a07658ceab27',
'5e9004fe262b829563d0804656ba68b1de1690401f08a1915273230d8c902fc0',
'1ea9ed3717523c5e304b7a7ac8058a87fb4f3fed8c6004769f226c9bb67e79c5',
'f0f1a4c009b3f1b2729e89898e2f5c0fcdc312edea5df884a9c897cb90e4c566',
'b5bb4ddf04863e6a60f33cb96c20dac8175d3bae55f335781503143c97a50e43',
'f14cc97a20c6f627b4b78301352ae35463bc359362589cd178a06c0fa90850b7',
'628801c8f614015c0fa0ccb2768cccc3e7b9d41ceed06071ce2534d31f7236d6',
'3be1013c8f8da150e2195408093153b55b08b037fd92db8bb5e803f4c2538aae',
'c9e1f8777685f54ba65c4e02915fd649ee1edcbf9c77ddf584b943d27efb86c3',
'4274e92ed3bd02eb101baa5fb8ff7b96236830762d08273749fbb5166db8ab0b',
'aa84c955bea04c7cee8f5bbbec97d25930fcaca363eed1b8cad37b931556d3e3',
'd6a29c948677fb1f71aaf16debc3d071a4dd349458eb9e056dce3a000ff853da',
'ba84bdb3d78367ca365016ac4bff9269576eb010f874c2967af73e0de5638de0',
'1546c79951e3b541bc64d1957b565b7a2850fc87192c7b374aee6cfc69b9805e',
'f119227d492ebe27fe9aae321980802454dfa64b2691efbe796c5075d5b07f62',
'b8cf13d64818b32f96bbb585998b1bc9505f6a94055488e5a71fee9479c6f2a9',
'1aaf459705b6afef2d7b83e3f181f1af55be0813daf55edce104cc59abc28ed7',
'61ac185c8f520b5e3134953dc52ff292a40e1e96b088dab259558a9d240ec02f',
'2da96e3154d7ec2329f787b73cb8a436b92d64cf3cc28e920d073279ea73b5f8',
'1c4d72ce733b971b9ec4e24f37d733355f6f2ea635cc67ffb3e22748484df446',
'2a6f89769f3272ac8c7a36a42a57627eca6b260ab2c76d8046a27d44d4034893',
'f8d11df51a2cc113698ebf39a958fe81179d7d973d2044322771c0fe63f4d7c9',
'f2287f17a4fa232dca5715c24a92f7112402a8101b9a7b276fb8c8f617376b90',
'bb5ee510a4fda29cae30c97e7eee80569d3ec3598465f2d7e0674c395e0256e9',
'647ab8c84365620d60f2523505d14bd230b5e650c96dee48be47770063ee7461',
'34b06018fcc33ba6ebb01198d785b0629fbdc5d1948f688059158f053093f08b',
'ff58b258dab0d7f36a2908e6c75229ce308d34806289c912a1a5f39a5aa71f9f',
'232fc124803668a9f23b1c3bcb1134274303f5c0e1b0e27c9b6c7db59f0e2a4d',
'27a0797cc5b042ba4c11e72a9555d13a67f00161550b32ede0511718b22dbc2c'
]
const leaves = txHashes.map(x => Buffer.from(x, 'hex'))
const tree = new MerkleTree(leaves, sha256, {isBitcoinTree: true})
const tree = new MerkleTree(leaves, sha256, { isBitcoinTree: true })
const root = Buffer.from('871714dcbae6c8193a2bb9b2a69fe1c0440399f38d94b3a0f1b447275a29978a', 'hex')
t.equal(tree.getRoot().toString('hex'), root.toString('hex'))
@ -422,6 +424,18 @@ test.skip('sha256 - 1,000,000 leaves', t => {
t.equal(tree.getRoot().toString('hex'), root)
})
test('sha256 getHexLeaves', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(x => keccak(x))
const tree = new MerkleTree(leaves, sha256)
t.deepEqual(tree.getHexLeaves(), [
'0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb',
'0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510',
'0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2'
])
})
test('crypto-js SHA3 leaves SHA256 hash algo', t => {
t.plan(2)
@ -455,12 +469,12 @@ test('sha1', t => {
'd89f84d948796605a413e196f40bce1d6294175d',
'32f04c7f572bf75a266268c6f4d8c92731dc3b7f',
'b80b52d80f5fe940ac2c987044bc439e4218ac94',
'1553c75a1d637961827f4904a0955e57915d8310',
'1553c75a1d637961827f4904a0955e57915d8310'
]
const tree = new MerkleTree(leaves, sha1, {
sortLeaves: true,
sortPairs: true,
sortPairs: true
})
const root = tree.getHexRoot()
@ -470,12 +484,36 @@ test('sha1', t => {
t.equal(tree.verify(proof, leaf, root), true)
})
test('sha56 getHexLayers', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(x => keccak(x))
const tree = new MerkleTree(leaves, sha256)
const layers = tree.getHexLayers()
t.deepEqual(layers, [
[
'0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb',
'0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510',
'0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2'
],
[
'0x176f0f307632fdd5831875eb709e2f68d770b102262998b214ddeb3f04164ae1',
'0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2'
],
[
'0x311d2e46f49b15fff8b746b74ad57f2cc9e0d9939fda94387141a2d3fdf187ae'
]
])
})
test('getLayersAsObject', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(x => keccak(x))
const tree = new MerkleTree(leaves, sha256)
const obj = tree.getLayersAsObject()
t.deepEqual(obj, {
'311d2e46f49b15fff8b746b74ad57f2cc9e0d9939fda94387141a2d3fdf187ae': {
'0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2': {
@ -483,7 +521,7 @@ test('getLayersAsObject', t => {
},
'176f0f307632fdd5831875eb709e2f68d770b102262998b214ddeb3f04164ae1': {
'3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb': null,
'b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510': null
b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510: null
}
}
})
@ -493,22 +531,38 @@ test('getLayersAsObject with duplicate odd option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(x => keccak(x))
const tree = new MerkleTree(leaves, sha256, {duplicateOdd: true})
const tree = new MerkleTree(leaves, sha256, { duplicateOdd: true })
const obj = tree.getLayersAsObject()
t.deepEqual(obj, {
"bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1": {
"176f0f307632fdd5831875eb709e2f68d770b102262998b214ddeb3f04164ae1": {
"3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb": null,
"b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510": null
bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1: {
'176f0f307632fdd5831875eb709e2f68d770b102262998b214ddeb3f04164ae1': {
'3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb': null,
b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510: null
},
"43e061172b1177f25d0f156b2d2ed11728006fade8e167ff3d1b9dbc979a3358": {
"0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2": null
'43e061172b1177f25d0f156b2d2ed11728006fade8e167ff3d1b9dbc979a3358': {
'0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2': null
}
}
})
})
test.skip('sha256 getHexLayersFlat', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(x => keccak(x))
const tree = new MerkleTree(leaves, sha256)
const layers = tree.getLayersFlat()
t.deepEqual(layers, [
'3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb',
'b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510',
'0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2',
'176f0f307632fdd5831875eb709e2f68d770b102262998b214ddeb3f04164ae1',
'0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2',
'311d2e46f49b15fff8b746b74ad57f2cc9e0d9939fda94387141a2d3fdf187ae'
])
})
test('print', t => {
t.plan(1)
@ -529,7 +583,7 @@ test('print with duplicate odd option', t => {
t.plan(1)
const leaves = ['a', 'b', 'c'].map(x => keccak(x))
const tree = new MerkleTree(leaves, sha256, {duplicateOdd: true})
const tree = new MerkleTree(leaves, sha256, { duplicateOdd: true })
t.equal(tree.toString(),
`└─ bcdd0f60308db788712205115fe4273bfda49fa0925611fee765a63df9ab96a1
@ -540,3 +594,179 @@ test('print with duplicate odd option', t => {
0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2
`)
})
test('sha256 getMultiProof', t => {
t.plan(3)
const leaves = Array(16).fill(0).map((x, i) => {
const b = Buffer.alloc(32)
b.writeUIntLE(i, 31, 1)
return b
})
const tree = new MerkleTree(leaves, sha256)
const root = tree.getHexRoot()
t.equal(root, '0xc1ebc5b83154907160d73863bdae7eb86fe1888495a83cb8daadb1603b8aeaf5')
const i = 100
const indices = Array(16).fill(0).map((x, j) => j).filter(j => (i >> j) % 2 === 1)
const proof = tree.getMultiProof(indices)
t.deepEqual(proof.map(x => x.toString('hex')), [
'0000000000000000000000000000000000000000000000000000000000000007',
'0000000000000000000000000000000000000000000000000000000000000004',
'0000000000000000000000000000000000000000000000000000000000000003',
'90f4b39548df55ad6187a1d20d731ecee78c545b94afd16f42ef7592d99cd365',
'6b41dc65d0df2bb4b8c2f1fe48e85b7ba8f9b876113ec410fe612bae61a19eaf'
])
const depth = tree.getDepth()
const tLeaves = indices.map(i => leaves[i])
t.equal(tree.verifyMultiProof(root, indices, tLeaves, depth, proof), true)
})
test('sha256 getMultiProof using tree array', t => {
t.plan(4)
const leaves = Array(16).fill(0).map((x, i) => {
const b = Buffer.alloc(32)
b.writeUIntLE(i, 31, 1)
return b
})
const tree = new MerkleTree(leaves, sha256)
const root = tree.getHexRoot()
t.equal(root, '0xc1ebc5b83154907160d73863bdae7eb86fe1888495a83cb8daadb1603b8aeaf5')
const treeFlat = tree.getLayersFlat()
const i = 100
const indices = Array(16).fill(0).map((x, j) => j).filter(j => (i >> j) % 2 === 1)
t.deepEqual(treeFlat.map(x => x.toString('hex')), [
'00',
'c1ebc5b83154907160d73863bdae7eb86fe1888495a83cb8daadb1603b8aeaf5',
'32e0fe0539aeca5782542f7232d32185eebec2e6dc258177456dcdebbbf18f8b',
'6b41dc65d0df2bb4b8c2f1fe48e85b7ba8f9b876113ec410fe612bae61a19eaf',
'd1d79b01cacccaa40e6a3825db8ff96f38858092ea71689ce31dc6df3cf31df2',
'564ad5882547f199bbe82f40f4e5ddbd3836859c2e5caf3ff5b0989caf614af0',
'5ef82769b7a6ea72894663fd3ed0736e7084beb596edeac229d4ae598d19aa2b',
'446c09a38396a1f51846548cc4a87bddd73bdcce9cf86c65f9ac6c153a92a562',
'90f4b39548df55ad6187a1d20d731ecee78c545b94afd16f42ef7592d99cd365',
'a774c351cf3882b36b2c541586b0b59c6dfd119ae831ef3c6b2e269f7a6be220',
'b023893dc819559da606f81778f566532801f428570db5fd3d3088491bb292c2',
'277752261a13c9c81c37ea894efe5dc3af8b7ffecbedce75d0ec09862e74a2cb',
'4c1f763d4f27669f41e511f15268e6396a66f7750c6128d68210a1dfbeb0cbc3',
'856b7aa12bb3a7fb591ac8cfb8051bb89c4b57cb7faf852fdc5d226076423d04',
'719cf2e4468492fde20372b704eca4a67397bba60d7513bb08f199887636027f',
'cec448f4862209fa531a43a65ec850a6327f02f518ef55f4d347510a5cf5d1cf',
'0000000000000000000000000000000000000000000000000000000000000000',
'0000000000000000000000000000000000000000000000000000000000000001',
'0000000000000000000000000000000000000000000000000000000000000002',
'0000000000000000000000000000000000000000000000000000000000000003',
'0000000000000000000000000000000000000000000000000000000000000004',
'0000000000000000000000000000000000000000000000000000000000000005',
'0000000000000000000000000000000000000000000000000000000000000006',
'0000000000000000000000000000000000000000000000000000000000000007',
'0000000000000000000000000000000000000000000000000000000000000008',
'0000000000000000000000000000000000000000000000000000000000000009',
'000000000000000000000000000000000000000000000000000000000000000a',
'000000000000000000000000000000000000000000000000000000000000000b',
'000000000000000000000000000000000000000000000000000000000000000c',
'000000000000000000000000000000000000000000000000000000000000000d',
'000000000000000000000000000000000000000000000000000000000000000e',
'000000000000000000000000000000000000000000000000000000000000000f'
])
const proof = tree.getMultiProof(treeFlat, indices)
t.deepEqual(proof.map(x => x.toString('hex')), [
'0000000000000000000000000000000000000000000000000000000000000007',
'0000000000000000000000000000000000000000000000000000000000000004',
'0000000000000000000000000000000000000000000000000000000000000003',
'90f4b39548df55ad6187a1d20d731ecee78c545b94afd16f42ef7592d99cd365',
'6b41dc65d0df2bb4b8c2f1fe48e85b7ba8f9b876113ec410fe612bae61a19eaf'
])
const depth = tree.getDepth()
const tRoot = treeFlat[1]
const tLeaves = indices.map(i => leaves[i])
t.equal(tree.verifyMultiProof(tRoot, indices, tLeaves, depth, proof), true)
})
test('sha256 getMultiProof', t => {
t.plan(3)
const leaves = Array(16).fill(0).map((x, i) => {
const b = Buffer.alloc(32)
b.writeUIntLE(i, 31, 1)
return b
})
const tree = new MerkleTree(leaves, sha256, { sortPairs: true, singleOdd: true })
const root = tree.getHexRoot()
t.equal(root, '0xc336787ec1a678cbbb3391bd0146e0c11e62dac39f84fe9a404a882094c00273')
const treeFlat = tree.getLayersFlat()
const indices = [0, 1]
const proof = tree.getMultiProof(treeFlat, indices)
const tLeaves = indices.map(i => leaves[i])
t.deepEqual(proof.map(x => x.toString('hex')), [
'a774c351cf3882b36b2c541586b0b59c6dfd119ae831ef3c6b2e269f7a6be220',
'de44f8fbe261b8ed52c606670f9c73e428a997908df7452806d6123829d50adc',
'44f76d663f391971ba8bd22469061c70aa63c176bc29b651336db7587b6f5a6c'
])
const proofFlags = tree.getProofFlags(tLeaves, proof)
t.deepEqual(proofFlags, [
true,
false,
false,
false
])
})
test('sha256 getMultiProof - statusim', t => {
t.plan(5)
const elements = ['a', 'b', 'c', 'd', 'e', 'f']
const tree = new MerkleTree(elements, keccak, {
hashLeaves: true,
sortLeaves: true,
sortPairs: true
})
t.equal(tree.getHexRoot(), '0x1b404f199ea828ec5771fb30139c222d8417a82175fefad5cd42bc3a189bd8d5')
t.deepEqual(tree.getHexLeaves(), [
'0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2',
'0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb',
'0xa8982c89d80987fb9a510e25981ee9170206be21af3c8e0eb312ef1d3382e761',
'0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510',
'0xd1e8aeb79500496ef3dc2e57ba746a8315d048b7a664a2bf948db4fa91960483',
'0xf1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3'
])
const leaves = tree.getLeaves(['d', 'a'])
const proof = tree.getMultiProof(leaves)
const proofFlags = tree.getProofFlags(leaves, proof)
t.deepEqual(leaves.map(x => x.toString('hex')), [
'3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb',
'f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3'
])
t.deepEqual(tree.getHexMultiProof(leaves), [
'0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2',
'0xd1e8aeb79500496ef3dc2e57ba746a8315d048b7a664a2bf948db4fa91960483',
'0x434d51cfeb80272378f4c3a8fd2824561c2cad9fce556ea600d46f20550976a6'
])
t.deepEqual(proofFlags, [false, false, false, true])
})

View File

@ -3,6 +3,7 @@
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"downlevelIteration": true,
"types": ["node"],
"baseUrl": "./",
"paths": {