Add Merkle Mountain Range (MMR) tree class

This commit is contained in:
Miguel Mota 2021-03-29 15:01:46 -07:00
parent ffd68dde08
commit 8deaa7fc53
No known key found for this signature in database
GPG Key ID: 67EC1161588A00F9
6 changed files with 667 additions and 215 deletions

View File

@ -65,9 +65,11 @@
"node": ">= 7.6.0"
},
"dependencies": {
"bignumber.js": "^9.0.1",
"buffer-reverse": "^1.0.1",
"crypto-js": "^3.1.9-1",
"treeify": "^1.1.0"
"treeify": "^1.1.0",
"web3-utils": "^1.3.4"
},
"eslintConfig": {
"rules": {

212
src/Base.ts Normal file
View File

@ -0,0 +1,212 @@
import CryptoJS from 'crypto-js'
export class Base {
/**
* print
* @desc Prints out a visual representation of the merkle tree.
* @example
*```js
*tree.print()
*```
*/
print ():void {
Base.print(this)
}
/**
* bufferIndexOf
* @desc Returns the first index of which given buffer is found in array.
* @param {Buffer[]} haystack - Array of buffers.
* @param {Buffer} needle - Buffer to find.
* @return {Number} - Index number
*
* @example
* ```js
*const index = tree.bufferIndexOf(haystack, needle)
*```
*/
protected _bufferIndexOf (array: Buffer[], element: Buffer):number {
for (let i = 0; i < array.length; i++) {
if (element.equals(array[i])) {
return i
}
}
return -1
}
/**
* bufferify
* @desc Returns a buffer type for the given value.
* @param {String|Number|Object|Buffer} value
* @return {Buffer}
*
* @example
* ```js
*const buf = MerkleTree.bufferify('0x1234')
*```
*/
static bufferify (value: any):Buffer {
if (!Buffer.isBuffer(value)) {
// crypto-js support
if (typeof value === 'object' && value.words) {
return Buffer.from(value.toString(CryptoJS.enc.Hex), 'hex')
} else if (Base.isHexString(value)) {
return Buffer.from(value.replace(/^0x/, ''), 'hex')
} else if (typeof value === 'string') {
return Buffer.from(value)
} else if (typeof value === 'number') {
let s = value.toString()
if (s.length % 2) {
s = `0${s}`
}
return Buffer.from(s, 'hex')
}
}
return value
}
/**
* isHexString
* @desc Returns true if value is a hex string.
* @param {String} value
* @return {Boolean}
*
* @example
* ```js
*console.log(MerkleTree.isHexString('0x1234'))
*```
*/
static isHexString (v: string):boolean {
return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v))
}
/**
* print
* @desc Prints out a visual representation of the given merkle tree.
* @param {Object} tree - Merkle tree instance.
* @return {String}
* @example
*```js
*MerkleTree.print(tree)
*```
*/
static print (tree: any):void {
console.log(tree.toString())
}
/**
* bufferToHex
* @desc Returns a hex string with 0x prefix for given buffer.
* @param {Buffer} value
* @return {String}
* @example
*```js
*const hexStr = tree.bufferToHex(Buffer.from('A'))
*```
*/
bufferToHex (value: Buffer, withPrefix: boolean = true):string {
return Base.bufferToHex(value, withPrefix)
}
/**
* bufferToHex
* @desc Returns a hex string with 0x prefix for given buffer.
* @param {Buffer} value
* @return {String}
* @example
*```js
*const hexStr = MerkleTree.bufferToHex(Buffer.from('A'))
*```
*/
static bufferToHex (value: Buffer, withPrefix: boolean = true):string {
return `${withPrefix ? '0x' : ''}${(value || Buffer.alloc(0)).toString('hex')}`
}
/**
* bufferify
* @desc Returns a buffer type for the given value.
* @param {String|Number|Object|Buffer} value
* @return {Buffer}
*
* @example
* ```js
*const buf = tree.bufferify('0x1234')
*```
*/
bufferify (value: any):Buffer {
return Base.bufferify(value)
}
/**
* bufferifyFn
* @desc Returns a function that will bufferify the return value.
* @param {Function}
* @return {Function}
*
* @example
* ```js
*const fn = tree.bufferifyFn((value) => sha256(value))
*```
*/
protected _bufferifyFn (f: any):any {
return function (value: any) {
const v = f(value)
if (Buffer.isBuffer(v)) {
return v
}
if (this._isHexString(v)) {
return Buffer.from(v, 'hex')
}
// crypto-js support
return Buffer.from(f(CryptoJS.enc.Hex.parse(value.toString('hex'))).toString(CryptoJS.enc.Hex), 'hex')
}
}
/**
* isHexString
* @desc Returns true if value is a hex string.
* @param {String} value
* @return {Boolean}
*
* @example
* ```js
*console.log(MerkleTree.isHexString('0x1234'))
*```
*/
protected _isHexString (value: string):boolean {
return Base.isHexString(value)
}
/**
* log2
* @desc Returns the log2 of number.
* @param {Number} value
* @return {Number}
*/
protected _log2 (n: number):number {
return n === 1 ? 0 : 1 + this._log2((n / 2) | 0)
}
/**
* zip
* @desc Returns true if value is a hex string.
* @param {String[]|Number[]|Buffer[]} a - first array
* @param {String[]|Number[]|Buffer[]} b - second array
* @return {String[][]|Number[][]|Buffer[][]}
*
* @example
* ```js
*const zipped = tree.zip(['a', 'b'],['A', 'B'])
*console.log(zipped) // [ [ 'a', 'A' ], [ 'b', 'B' ] ]
*```
*/
protected _zip (a: any[], b: any[]):any[][] {
return a.map((e, i) => [e, b[i]])
}
}
export default Base

348
src/MerkleMountainRange.ts Normal file
View File

@ -0,0 +1,348 @@
import Base from './Base'
import SHA256 from 'crypto-js/sha256'
// @credit: https://github.com/wanseob/solidity-mmr
export class MerkleMountainRange extends Base {
root: Buffer = Buffer.alloc(0)
size: number = 0
width: number = 0
hashes: any = {}
data: any = {}
hashLeafFn: any
peakBaggingFn: any
hashBranchFn: any
private hashAlgo: any
constructor (hashAlgorithm = SHA256, leaves: any[] = [], hashLeafFn?: any, peakBaggingFn?: any, hashBranchFn?: any) {
super()
leaves = leaves.map(this.bufferify)
this.hashAlgo = this._bufferifyFn(hashAlgorithm)
this.hashLeafFn = hashLeafFn
this.peakBaggingFn = peakBaggingFn
this.hashBranchFn = hashBranchFn
for (const leaf of leaves) {
this.append(leaf)
}
}
append (data: any) {
data = this.bufferify(data)
const dataHash = this.hashAlgo(data)
if (this.bufferToHex(this.hashAlgo(this.data[dataHash])) !== this.bufferToHex(dataHash)) {
this.data[this.bufferToHex(dataHash)] = data
}
const leaf = this.hashLeaf(this.size + 1, dataHash)
this.hashes[this.size + 1] = leaf
this.width += 1
// find peaks for enlarged tree
const peakIndexes = this.getPeakIndexes(this.width)
// the right most peak's value is the new size of the updated tree
this.size = this.getSize(this.width)
// starting from the left-most peak, get all peak hashes
const peaks = []
for (let i = 0; i < peakIndexes.length; i++) {
peaks[i] = this._getOrCreateNode(peakIndexes[i])
}
// update the tree root hash
this.root = this.peakBagging(this.width, peaks)
}
hashLeaf (index: number, dataHash: any) {
if (this.hashLeafFn) {
return this.bufferify(this.hashLeafFn(index, dataHash))
}
return this.hashAlgo(Buffer.concat([this.bufferify(index), this.bufferify(dataHash)]))
}
hashBranch (index: number, left: any, right: any): any {
if (this.hashBranchFn) {
return this.bufferify(this.hashBranchFn(index, left, right))
}
return this.hashAlgo(Buffer.concat([this.bufferify(index), this.bufferify(left), this.bufferify(right)]))
}
getPeaks () {
const peakIndexes = this.getPeakIndexes(this.width)
const peaks = []
for (let i = 0; i < peakIndexes.length; i++) {
peaks[i] = this.hashes[peakIndexes[i]]
}
return peaks
}
getLeafIndex (width: number) {
if (width % 2 === 1) {
return this.getSize(width)
}
return this.getSize(width - 1) + 1
}
getPeakIndexes (width: number): number[] {
const numPeaks = this.numOfPeaks(width)
const peakIndexes = []
let count = 0
let size = 0
for (let i = 255; i > 0; i--) {
if ((width & (1 << (i - 1))) !== 0) {
// peak exists
size = size + (1 << i) - 1
peakIndexes[count++] = size
if (peakIndexes.length >= numPeaks) {
break
}
}
}
if (count !== peakIndexes.length) {
throw new Error('Invalid bit calculation')
}
return peakIndexes
}
numOfPeaks (width: number): number {
let bits = width
let num = 0
while (bits > 0) {
if (bits % 2 === 1) {
num++
}
bits = bits >> 1
}
return num
}
peakBagging (width: number, peaks: any[]): any {
const size = this.getSize(width)
if (this.numOfPeaks(width) !== peaks.length) {
throw new Error('Received invalid number of peaks')
}
if (this.peakBaggingFn) {
return this.bufferify(this.peakBaggingFn(size, peaks))
}
return this.hashAlgo(Buffer.concat([this.bufferify(size), ...peaks.map(this.bufferify)]))
}
getSize (width: number): number {
return (width << 1) - this.numOfPeaks(width)
}
getRoot (): any {
return this.root
}
getHexRoot (): any {
return this.bufferToHex(this.getRoot())
}
getNode (index: number): any {
return this.hashes[index]
}
mountainHeight (size: number): number {
let height = 1
while (1 << height <= size + height) {
height++
}
return height - 1
}
heightAt (index: number): number {
let reducedIndex = index
let peakIndex = 0
let height = 0
// if an index has a left mountain then subtract the mountain
while (reducedIndex > peakIndex) {
reducedIndex -= (1 << height) - 1
height = this.mountainHeight(reducedIndex)
peakIndex = (1 << height) - 1
}
// index is on the right slope
return height - (peakIndex - reducedIndex)
}
isLeaf (index: number): boolean {
return this.heightAt(index) === 1
}
getChildren (index: number) {
const left = index - (1 << (this.heightAt(index) - 1))
const right = index - 1
if (left === right) {
throw new Error('Not a parent')
}
return [left, right]
}
_getOrCreateNode (index: number) {
if (index > this.size) {
throw new Error('Out of range')
}
if (!this.hashes[index]) {
const [leftIndex, rightIndex] = this.getChildren(index)
const leftHash = this._getOrCreateNode(leftIndex)
const rightHash = this._getOrCreateNode(rightIndex)
this.hashes[index] = this.hashBranch(index, leftHash, rightHash)
}
return this.hashes[index]
}
getMerkleProof (index: number) {
if (index >= this.size) {
throw new Error('Out of range')
}
if (!this.isLeaf(index)) {
throw new Error('Not a leaf')
}
const root = this.root
const width = this.width
// find all peaks for bagging
const peaks = this.getPeakIndexes(this.width)
const peakBagging = []
let cursor = 0
for (let i = 0; i < peaks.length; i++) {
// collect the hash of all peaks
peakBagging[i] = this.hashes[peaks[i]]
// find the peak which includes the target index
if (peaks[i] >= index && cursor === 0) {
cursor = peaks[i]
}
}
let left = 0
let right = 0
// get hashes of the siblings in the mountain which the index belgons to.
// it moves the cursor from the summit of the mountain down to the target index
let height = this.heightAt(cursor)
const siblings = []
while (cursor !== index) {
height--
([left, right] = this.getChildren(cursor))
// move the cursor down to the left size or right size
cursor = index <= left ? left : right
// remaining node is the sibling
siblings[height - 1] = this.hashes[index < left ? right : left]
}
return {
root,
width,
peakBagging,
siblings
}
}
verify (root: any, width: number, index: number, value: any, peaks: any[], siblings: any[]) {
const size = this.getSize(width)
if (size < index) {
throw new Error('Index is out of range')
}
// check the root equals the peak bagging hash
if (!root.equals(this.peakBagging(width, peaks))) {
throw new Error('Invalid root hash from the peaks')
}
// find the mountain where the target index belongs to
let cursor = 0
let targetPeak
const peakIndexes = this.getPeakIndexes(width)
for (let i = 0; i < peakIndexes.length; i++) {
if (peakIndexes[i] >= index) {
targetPeak = peaks[i]
cursor = peakIndexes[i]
break
}
}
if (!targetPeak) {
throw new Error('target not found')
}
// find the path climbing down
let height = siblings.length + 1
const path = new Array(height)
let left = 0
let right = 0
while (height > 0) {
// record the current cursor and climb down
path[--height] = cursor
if (cursor === index) {
// on the leaf node. Stop climbing down
break
} else {
// on the parent node. Go left or right
([left, right] = this.getChildren(cursor))
cursor = index > left ? right : left
continue
}
}
// calculate the summit hash climbing up again
let node
while (height < path.length) {
// move cursor
cursor = path[height]
if (height === 0) {
// cusor is on the leaf
node = this.hashLeaf(cursor, this.hashAlgo(value))
} else if (cursor - 1 === path[height - 1]) {
// cursor is on a parent and a siblings is on the left
node = this.hashBranch(cursor, siblings[height - 1], node)
} else {
// cursor is on a parent and a siblings is on the right
node = this.hashBranch(cursor, node, siblings[height - 1])
}
// climb up
height++
}
// computed hash value of the summit should equal to the target peak hash
if (!node.equals(targetPeak)) {
throw new Error('hashed peak is invalid')
}
return true
}
peaksToPeakMap (width: number, peaks: any[]) {
throw new Error('not implemented')
}
peakMapToPeaks (width: number, peakMap: any) {
throw new Error('not implemented')
}
peakUpdate (width: number, prevPeakMap: any, itemHash: any) {
throw new Error('not implemented')
}
rollUp (root: any, width: number, peaks: any[], itemHashes: any[]) {
throw new Error('not implemented')
}
}
export default MerkleMountainRange

View File

@ -1,6 +1,6 @@
import reverse from 'buffer-reverse'
import CryptoJS from 'crypto-js'
import SHA256 from 'crypto-js/sha256'
import Base from './Base'
import treeify from 'treeify'
export interface Options {
@ -27,7 +27,7 @@ type TLayer = any
* Class reprensenting a Merkle Tree
* @namespace MerkleTree
*/
export class MerkleTree {
export class MerkleTree extends Base {
private duplicateOdd: boolean
private hashAlgo: (value: TValue) => THashAlgo
private hashLeaves: boolean
@ -61,6 +61,7 @@ export class MerkleTree {
*```
*/
constructor (leaves: any[], hashAlgorithm = SHA256, options: Options = {}) {
super()
this.isBitcoinTree = !!options.isBitcoinTree
this.hashLeaves = !!options.hashLeaves
this.sortLeaves = !!options.sortLeaves
@ -861,44 +862,6 @@ export class MerkleTree {
return objs[0]
}
/**
* print
* @desc Prints out a visual representation of the merkle tree.
* @example
*```js
*tree.print()
*```
*/
print ():void {
MerkleTree.print(this)
}
/**
* toTreeString
* @desc Returns a visual representation of the merkle tree as a string.
* @return {String}
* @example
*```js
*console.log(tree.toTreeString())
*```
*/
private _toTreeString ():string {
const obj = this.getLayersAsObject()
return treeify.asTree(obj, true)
}
/**
* toString
* @desc Returns a visual representation of the merkle tree as a string.
* @example
*```js
*console.log(tree.toString())
*```
*/
toString ():string {
return this._toTreeString()
}
/**
* getMultiProof
* @desc Returns the multiproof for given tree indices.
@ -941,192 +904,29 @@ export class MerkleTree {
}
/**
* bufferIndexOf
* @desc Returns the first index of which given buffer is found in array.
* @param {Buffer[]} haystack - Array of buffers.
* @param {Buffer} needle - Buffer to find.
* @return {Number} - Index number
*
* @example
* ```js
*const index = tree.bufferIndexOf(haystack, needle)
*```
*/
private _bufferIndexOf (array: Buffer[], element: Buffer):number {
for (let i = 0; i < array.length; i++) {
if (element.equals(array[i])) {
return i
}
}
return -1
}
/**
* bufferify
* @desc Returns a buffer type for the given value.
* @param {String|Number|Object|Buffer} value
* @return {Buffer}
*
* @example
* ```js
*const buf = MerkleTree.bufferify('0x1234')
*```
*/
static bufferify (value: any):Buffer {
if (!Buffer.isBuffer(value)) {
// crypto-js support
if (typeof value === 'object' && value.words) {
return Buffer.from(value.toString(CryptoJS.enc.Hex), 'hex')
} else if (MerkleTree.isHexString(value)) {
return Buffer.from(value.replace(/^0x/, ''), 'hex')
} else if (typeof value === 'string') {
return Buffer.from(value)
}
}
return value
}
/**
* isHexString
* @desc Returns true if value is a hex string.
* @param {String} value
* @return {Boolean}
*
* @example
* ```js
*console.log(MerkleTree.isHexString('0x1234'))
*```
*/
static isHexString (v: string):boolean {
return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v))
}
/**
* print
* @desc Prints out a visual representation of the given merkle tree.
* @param {Object} tree - Merkle tree instance.
* toTreeString
* @desc Returns a visual representation of the merkle tree as a string.
* @return {String}
* @example
*```js
*MerkleTree.print(tree)
*console.log(tree.toTreeString())
*```
*/
static print (tree: any):void {
console.log(tree.toString())
protected _toTreeString ():string {
const obj = this.getLayersAsObject()
return treeify.asTree(obj, true)
}
/**
* bufferToHex
* @desc Returns a hex string with 0x prefix for given buffer.
* @param {Buffer} value
* @return {String}
* toString
* @desc Returns a visual representation of the merkle tree as a string.
* @example
*```js
*const hexStr = tree.bufferToHex(Buffer.from('A'))
*console.log(tree.toString())
*```
*/
bufferToHex (value: Buffer, withPrefix: boolean = true):string {
return MerkleTree.bufferToHex(value, withPrefix)
}
/**
* bufferToHex
* @desc Returns a hex string with 0x prefix for given buffer.
* @param {Buffer} value
* @return {String}
* @example
*```js
*const hexStr = MerkleTree.bufferToHex(Buffer.from('A'))
*```
*/
static bufferToHex (value: Buffer, withPrefix: boolean = true):string {
return `${withPrefix ? '0x' : ''}${value.toString('hex')}`
}
/**
* bufferify
* @desc Returns a buffer type for the given value.
* @param {String|Number|Object|Buffer} value
* @return {Buffer}
*
* @example
* ```js
*const buf = tree.bufferify('0x1234')
*```
*/
bufferify (value: any):Buffer {
return MerkleTree.bufferify(value)
}
/**
* bufferifyFn
* @desc Returns a function that will bufferify the return value.
* @param {Function}
* @return {Function}
*
* @example
* ```js
*const fn = tree.bufferifyFn((value) => sha256(value))
*```
*/
private _bufferifyFn (f: any):any {
return function (value: any) {
const v = f(value)
if (Buffer.isBuffer(v)) {
return v
}
if (this._isHexString(v)) {
return Buffer.from(v, 'hex')
}
// crypto-js support
return Buffer.from(f(CryptoJS.enc.Hex.parse(value.toString('hex'))).toString(CryptoJS.enc.Hex), 'hex')
}
}
/**
* isHexString
* @desc Returns true if value is a hex string.
* @param {String} value
* @return {Boolean}
*
* @example
* ```js
*console.log(MerkleTree.isHexString('0x1234'))
*```
*/
private _isHexString (value: string):boolean {
return MerkleTree.isHexString(value)
}
/**
* log2
* @desc Returns the log2 of number.
* @param {Number} value
* @return {Number}
*/
private _log2 (n: number):number {
return n === 1 ? 0 : 1 + this._log2((n / 2) | 0)
}
/**
* zip
* @desc Returns true if value is a hex string.
* @param {String[]|Number[]|Buffer[]} a - first array
* @param {String[]|Number[]|Buffer[]} b - second array
* @return {String[][]|Number[][]|Buffer[][]}
*
* @example
* ```js
*const zipped = tree.zip(['a', 'b'],['A', 'B'])
*console.log(zipped) // [ [ 'a', 'A' ], [ 'b', 'B' ] ]
*```
*/
private _zip (a: any[], b: any[]):any[][] {
return a.map((e, i) => [e, b[i]])
toString ():string {
return this._toTreeString()
}
}

View File

@ -1,3 +1,4 @@
import MerkleTree from './MerkleTree'
export { MerkleTree }
export { MerkleMountainRange } from './MerkleMountainRange'
export default MerkleTree

View File

@ -0,0 +1,89 @@
/* eslint camelcase: 0 */
const test = require('tape')
const { keccak } = require('ethereumjs-util')
const { MerkleMountainRange } = require('../')
const { soliditySha3 } = require('web3-utils')
test('merkle mountain range', t => {
t.plan(6)
const leaves = [
'0xf6c5123c17fe0d9c8fc675504ffa8cc8f1613c185d16fc8099ffab31cc0f39b8',
'0x6579ce25f7cc6797b1386af5acc4ff8acc9f8054b65dad5fb0c3b0a32c6c4610',
'0x6423808868aca9b46c7ccf2d060ff079cb8cf252f87abe767ea3bdce6c933d0c',
'0xf0f21a35fc749991e54d26c2a7e6ae3509667bc0d897ac7bcf6a462d1bbe7996',
'0x2db0abbe3ef9894b15b7e5e25b538b68ce19812792e3245746cb16c6cfc14345',
'0xfcd354a2e527cb6040e15b1f04aa6df4eb5cd7f250ead21809ef16a348beca6b',
'0xe747a507a27368d45b0e9df1b6cc2cff173e3f739daff1e8bbdd7159263ba8ad',
'0x07d438f3191418363c43a92bcf0555a8b2cf5451d6854932259ede6096c59ed4',
'0xd49690a48a2c2a6772092af6d6eec62cc4285a88997cae9fa4835bf0835c2b4d',
'0x50b25445c2139a3cec3c593b49273f1f47af0eb7d9d9ed37667ef5673d163bb9',
'0x22a6e30c867f4109491e90313ebd0b1438bd8b559cbe85ee9239aa8902a67e74',
'0x1b70b853c66181ccf5d149c263c7cf5e1123d6655bcc0f668c9957db1d98e038',
'0x527efa47060e8eead056eb3b2b7c20821bf6e246828fdd835f3ccc16946568b1',
'0x22a33bbf9177edebe708656bcbe42b7cf077965d2d5fe268dff8878654db06d6',
'0x02c4f6983e8616f6b3e600b3c8b768e71755220a04173b8281440c6b8eb91cd9',
'0x3ed1f8b63a683529477f8813c0955ae058048b9a4fd9a706454f73f86baf977f',
'0x70153ddc9ab0028e922b6b2f9eb319a9a6f1ae37866e7f9771f2866dfbecbc1d',
'0x4c26cfe12f5b2e8b2d992d99c9359a261d0bf50450acb10a5060231b5d41c31c',
'0x86da6c8cc46b60f9eec99353c42e144525a202880db5c877d6d55ee0568c66e4',
'0x1b6a474c3e33a28104dd0e75484414b9d35e93c878823bc3b4d9fb5a5109e9eb'
]
const hashLeaf = (index, dataHash) => {
const hash = soliditySha3({ t: 'uint256', v: index }, { t: 'bytes32', v: '0x' + dataHash.toString('hex') })
return hash
}
const peakBagging = (size, peaks) => {
// keccak256(abi.encodePacked(size, keccak256(abi.encodePacked(size, peaks))));
const a = [{ t: 'uint256', v: size }, ...peaks.map(x => ({ t: 'bytes32', v: x.toString('hex') }))]
const x = soliditySha3(...a)
const b = [{ t: 'uint256', v: size }, { t: 'bytes32', v: x.toString('hex') }]
const res = soliditySha3(...b)
return res
// return keccak256(abi.encodePacked(size, keccak256(abi.encodePacked(size, peaks))));
}
const hashBranch = (index, left, right) => {
const args = []
if (index !== undefined) {
args.push({ t: 'uint256', v: index })
}
if (left !== undefined) {
args.push({ t: 'bytes32', v: '0x' + left.toString('hex') })
}
if (right !== undefined) {
args.push({ t: 'bytes32', v: '0x' + right.toString('hex') })
}
const hash = soliditySha3(...args)
return hash
}
const tree = new MerkleMountainRange(keccak, leaves, hashLeaf, peakBagging, hashBranch)
const root = tree.getHexRoot()
const proof = tree.getMerkleProof(2)
t.equal(proof.root.toString('hex'), '5d6caccebed1b6720659b4a6c022b77d7ed5706ece976aaf44b7cf8e34650eb7')
t.equal(proof.width, leaves.length)
t.deepEqual(proof.peakBagging.map(x => x.toString('hex')), [
'4965af1a91b3f41c5440e5fd5b529aae6fbf28c7c04a4fc151be321fabe4a784',
'581f3f6a99cd8f276fd07845f619d1a271d763dd4ab049c7f8ac98602da58b7f'
])
t.deepEqual(proof.siblings.map(x => x.toString('hex')), [
'd3185c77a90c0b4f44e9c38468ce303924345c756d19f5ee342a1043f308d606',
'fc5341e2e1127e54327c793c2b930ca9ca93cd4db16e234b73d04fdb11fa9df7',
'db3261e4e8d9dcfcc6a4973172a13338a013fb455a7a10ca90bfcc49699688b1',
'37bc386efc898c6c36b1c2e5a754dafd614398abf146904bcea607af5068605c'
])
t.equal(root, '0x5d6caccebed1b6720659b4a6c022b77d7ed5706ece976aaf44b7cf8e34650eb7')
const leaf = leaves[1]
const index = 2
const verified = tree.verify(proof.root, proof.width, index, leaf, proof.peakBagging, proof.siblings)
t.equal(verified, true)
})