2020-06-02 14:29:33 +08:00
import reverse from 'buffer-reverse'
import CryptoJS from 'crypto-js'
import treeify from 'treeify'
2017-07-22 15:31:30 +08:00
2019-06-07 15:45:17 +08:00
interface Options {
2019-06-07 16:00:36 +08:00
/** If set to `true`, an odd node will be duplicated and combined to make a pair to generate the layer hash. */
2020-02-14 05:25:52 +08:00
duplicateOdd? : boolean
2020-06-01 14:31:19 +08:00
/** If set to `true`, an odd node will not have a pair generating the layer hash. */
singleOdd? : boolean
2019-06-07 16:00:36 +08:00
/** If set to `true`, the leaves will hashed using the set hashing algorithms. */
2020-02-14 05:25:52 +08:00
hashLeaves? : boolean
2019-06-07 16:00:36 +08:00
/** 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. */
2020-02-14 05:25:52 +08:00
isBitcoinTree? : boolean
2019-06-08 06:15:16 +08:00
/** If set to `true`, the leaves will be sorted. */
2020-02-14 05:25:52 +08:00
sortLeaves? : boolean
2019-06-08 06:15:16 +08:00
/** If set to `true`, the hashing pairs will be sorted. */
2020-02-14 05:25:52 +08:00
sortPairs? : boolean
2019-06-17 08:46:12 +08:00
/** If set to `true`, the leaves and hashing pairs will be sorted. */
2020-02-14 05:25:52 +08:00
sort? : boolean
2019-06-07 15:45:17 +08:00
}
2020-02-14 05:25:52 +08:00
type THashAlgo = any
type TValue = any
type TLeaf = any
type TLayer = any
2019-06-07 16:00:36 +08:00
2017-07-22 15:31:30 +08:00
/ * *
* Class reprensenting a Merkle Tree
* @namespace MerkleTree
* /
2019-03-30 10:53:32 +08:00
export class MerkleTree {
2019-06-07 15:45:17 +08:00
duplicateOdd : boolean
2020-06-01 14:31:19 +08:00
singleOdd : boolean
2020-02-14 05:25:52 +08:00
hashAlgo : ( value : TValue ) = > THashAlgo
2019-06-07 15:29:42 +08:00
hashLeaves : boolean
2019-03-30 10:53:32 +08:00
isBitcoinTree : boolean
2020-02-14 05:25:52 +08:00
leaves : TLeaf [ ]
layers : TLayer [ ]
2019-06-08 06:15:16 +08:00
sortLeaves : boolean
sortPairs : boolean
2019-06-17 08:46:12 +08:00
sort : boolean
2019-03-30 10:53:32 +08:00
2017-07-22 15:31:30 +08:00
/ * *
* @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 .
2017-07-22 16:05:52 +08:00
* @param { Buffer [ ] } leaves - Array of hashed leaves . Each leaf must be a Buffer .
2017-07-22 15:38:06 +08:00
* @param { Function } hashAlgorithm - Algorithm used for hashing leaves and nodes
2017-07-22 15:31:30 +08:00
* @param { Object } options - Additional options
* @example
2019-06-07 15:45:17 +08:00
* ` ` ` js
* const MerkleTree = require ( 'merkletreejs' )
* const crypto = require ( 'crypto' )
2017-07-22 15:31:30 +08:00
*
2019-06-07 15:45:17 +08:00
* function sha256 ( data ) {
* // returns Buffer
* return crypto . createHash ( 'sha256' ) . update ( data ) . digest ( )
* }
2017-07-22 15:31:30 +08:00
*
2019-06-30 14:39:53 +08:00
* const leaves = [ 'a' , 'b' , 'c' ] . map ( x = > keccak ( x ) )
2017-07-22 15:31:30 +08:00
*
2019-06-07 15:45:17 +08:00
* const tree = new MerkleTree ( leaves , sha256 )
* ` ` `
2017-07-22 15:31:30 +08:00
* /
2020-06-01 14:31:19 +08:00
constructor ( leaves , hashAlgorithm , options : Options = { } ) {
2019-06-07 15:29:42 +08:00
this . isBitcoinTree = ! ! options . isBitcoinTree
this . hashLeaves = ! ! options . hashLeaves
2019-06-08 06:15:16 +08:00
this . sortLeaves = ! ! options . sortLeaves
this . sortPairs = ! ! options . sortPairs
2019-06-17 08:46:12 +08:00
this . sort = ! ! options . sort
if ( this . sort ) {
this . sortLeaves = true
this . sortPairs = true
}
2019-06-07 15:29:42 +08:00
this . duplicateOdd = ! ! options . duplicateOdd
2020-06-01 14:31:19 +08:00
this . singleOdd = ! ! options . singleOdd
this . hashAlgo = this . _bufferifyFn ( hashAlgorithm )
2019-06-07 15:29:42 +08:00
if ( this . hashLeaves ) {
leaves = leaves . map ( this . hashAlgo )
}
2020-06-01 14:31:19 +08:00
this . leaves = leaves . map ( this . _bufferify )
2019-06-08 06:15:16 +08:00
if ( this . sortLeaves ) {
this . leaves = this . leaves . sort ( Buffer . compare )
}
2018-10-27 04:09:38 +08:00
this . layers = [ this . leaves ]
2017-07-22 15:31:30 +08:00
this . createHashes ( this . leaves )
}
2018-12-10 11:57:31 +08:00
// TODO: documentation
2020-06-01 14:31:19 +08:00
createHashes ( nodes ) {
2018-10-26 13:04:51 +08:00
while ( nodes . length > 1 ) {
2018-08-20 04:14:03 +08:00
const layerIndex = this . layers . length
2017-07-22 15:31:30 +08:00
2018-08-20 04:14:03 +08:00
this . layers . push ( [ ] )
2017-07-22 15:31:30 +08:00
2019-06-07 15:29:42 +08:00
for ( let i = 0 ; i < nodes . length ; i += 2 ) {
2019-06-30 14:39:53 +08:00
if ( i + 1 === nodes . length ) {
2019-06-07 15:29:42 +08:00
if ( nodes . length % 2 === 1 ) {
2019-06-30 14:39:53 +08:00
let data = nodes [ nodes . length - 1 ]
2019-06-07 15:29:42 +08:00
let hash = data
// is bitcoin tree
if ( this . isBitcoinTree ) {
// Bitcoin method of duplicating the odd ending nodes
data = Buffer . concat ( [ reverse ( data ) , reverse ( data ) ] )
hash = this . hashAlgo ( data )
hash = reverse ( this . hashAlgo ( hash ) )
this . layers [ layerIndex ] . push ( hash )
continue
} else {
2020-06-01 14:31:19 +08:00
if ( ! this . duplicateOdd && ! this . singleOdd ) {
2019-06-07 15:29:42 +08:00
this . layers [ layerIndex ] . push ( nodes [ i ] )
continue
}
}
}
}
2018-08-20 04:14:03 +08:00
const left = nodes [ i ]
2020-06-01 14:31:19 +08:00
let right = i + 1 === nodes . length ? left : nodes [ i + 1 ]
2018-08-20 04:14:03 +08:00
let data = null
2019-06-08 06:15:16 +08:00
let combined = null
2018-08-20 04:14:03 +08:00
if ( this . isBitcoinTree ) {
2019-06-08 06:15:16 +08:00
combined = [ reverse ( left ) , reverse ( right ) ]
2018-08-20 04:14:03 +08:00
} else {
2020-06-01 14:31:19 +08:00
if ( this . singleOdd ) {
right = nodes [ i + 1 ]
if ( ! left ) {
combined = [ right ]
} else if ( ! right ) {
combined = [ left ]
} else {
combined = [ left , right ]
}
} else {
combined = [ left , right ]
}
2019-06-08 06:15:16 +08:00
}
2019-06-07 15:29:42 +08:00
2019-06-08 06:15:16 +08:00
if ( this . sortPairs ) {
combined . sort ( Buffer . compare )
2018-08-20 04:14:03 +08:00
}
2017-07-22 15:31:30 +08:00
2019-06-08 06:15:16 +08:00
data = Buffer . concat ( combined )
2018-08-20 04:14:03 +08:00
let hash = this . hashAlgo ( data )
2017-07-22 15:31:30 +08:00
2018-08-20 04:14:03 +08:00
// double hash if bitcoin tree
if ( this . isBitcoinTree ) {
hash = reverse ( this . hashAlgo ( hash ) )
}
this . layers [ layerIndex ] . push ( hash )
2017-07-22 15:31:30 +08:00
}
2018-08-20 04:14:03 +08:00
nodes = this . layers [ layerIndex ]
2017-07-22 15:31:30 +08:00
}
}
/ * *
* getLeaves
2017-07-22 16:05:52 +08:00
* @desc Returns array of leaves of Merkle Tree .
* @return { Buffer [ ] }
2017-07-22 15:31:30 +08:00
* @example
2019-06-07 15:45:17 +08:00
* ` ` ` js
* const leaves = tree . getLeaves ( )
* ` ` `
2017-07-22 15:31:30 +08:00
* /
2020-06-01 14:31:19 +08:00
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 )
}
2017-07-22 15:31:30 +08:00
return this . leaves
}
2020-06-01 14:31:19 +08:00
/ * *
* 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 ) )
}
2017-07-22 15:31:30 +08:00
/ * *
* getLayers
2020-06-01 14:31:19 +08:00
* @desc Returns multi - dimensional array of all layers of Merkle Tree , including leaves and root .
2017-07-22 16:05:52 +08:00
* @return { Buffer [ ] }
2017-07-22 15:31:30 +08:00
* @example
2019-06-07 15:45:17 +08:00
* ` ` ` js
* const layers = tree . getLayers ( )
* ` ` `
2017-07-22 15:31:30 +08:00
* /
2020-06-01 14:31:19 +08:00
getLayers ( ) {
2017-07-22 15:31:30 +08:00
return this . layers
}
2020-06-01 14:31:19 +08:00
/ * *
* 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 ) )
}
2017-07-22 15:31:30 +08:00
/ * *
* getRoot
2017-07-22 16:05:52 +08:00
* @desc Returns the Merkle root hash as a Buffer .
* @return { Buffer }
2017-07-22 15:31:30 +08:00
* @example
2019-06-07 15:45:17 +08:00
* ` ` ` js
* const root = tree . getRoot ( )
* ` ` `
2017-07-22 15:31:30 +08:00
* /
2020-06-01 14:31:19 +08:00
getRoot ( ) {
2019-06-30 14:39:53 +08:00
return this . layers [ this . layers . length - 1 ] [ 0 ] || Buffer . from ( [ ] )
2017-07-22 15:31:30 +08:00
}
2020-06-01 14:31:19 +08:00
/ * *
* 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 ( ) )
2019-06-08 08:41:48 +08:00
}
2017-07-22 15:31:30 +08:00
/ * *
* 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 .
2018-09-18 06:03:35 +08:00
* @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 .
2019-06-07 15:45:17 +08:00
* @example
* ` ` ` js
* const proof = tree . getProof ( leaves [ 2 ] )
* ` ` `
2017-07-22 15:31:30 +08:00
*
* @example
2019-06-07 15:45:17 +08:00
* ` ` ` js
2019-06-30 14:39:53 +08:00
* const leaves = [ 'a' , 'b' , 'a' ] . map ( x = > keccak ( x ) )
* const tree = new MerkleTree ( leaves , keccak )
2019-06-07 15:45:17 +08:00
* const proof = tree . getProof ( leaves [ 2 ] , 2 )
* ` ` `
2017-07-22 15:31:30 +08:00
* /
2020-06-01 14:31:19 +08:00
getProof ( leaf , index ? ) {
leaf = this . _bufferify ( leaf )
2018-10-30 04:58:49 +08:00
const proof = [ ]
2017-07-22 15:31:30 +08:00
if ( typeof index !== 'number' ) {
index = - 1
for ( let i = 0 ; i < this . leaves . length ; i ++ ) {
if ( Buffer . compare ( leaf , this . leaves [ i ] ) === 0 ) {
index = i
}
}
}
if ( index <= - 1 ) {
return [ ]
}
2018-07-11 07:31:09 +08:00
if ( this . isBitcoinTree && index === ( this . leaves . length - 1 ) ) {
2018-07-11 05:29:30 +08:00
// Proof Generation for Bitcoin Trees
2017-07-22 15:31:30 +08:00
2018-07-11 05:29:30 +08:00
for ( let i = 0 ; i < this . layers . length - 1 ; i ++ ) {
const layer = this . layers [ i ]
const isRightNode = index % 2
2019-06-30 14:39:53 +08:00
const pairIndex = ( isRightNode ? index - 1 : index )
2018-07-11 05:29:30 +08:00
if ( pairIndex < layer . length ) {
proof . push ( {
data : layer [ pairIndex ]
} )
}
// set index to parent index
2019-06-30 14:39:53 +08:00
index = ( index / 2 ) | 0
2019-06-07 15:29:42 +08:00
}
2018-07-11 05:29:30 +08:00
2019-06-07 15:29:42 +08:00
return proof
} else {
// Proof Generation for Non-Bitcoin Trees
2018-07-11 05:29:30 +08:00
2019-06-07 15:29:42 +08:00
for ( let i = 0 ; i < this . layers . length ; i ++ ) {
const layer = this . layers [ i ]
const isRightNode = index % 2
const pairIndex = ( isRightNode ? index - 1 : index + 1 )
2018-07-11 05:29:30 +08:00
2019-06-07 15:29:42 +08:00
if ( pairIndex < layer . length ) {
proof . push ( {
2019-06-30 14:39:53 +08:00
position : isRightNode ? 'left' : 'right' ,
2019-06-07 15:29:42 +08:00
data : layer [ pairIndex ]
} )
}
2018-07-11 05:29:30 +08:00
2019-06-07 15:29:42 +08:00
// set index to parent index
2019-06-30 14:39:53 +08:00
index = ( index / 2 ) | 0
2019-06-07 15:29:42 +08:00
}
2018-07-11 05:29:30 +08:00
2019-06-07 15:29:42 +08:00
return proof
}
}
2017-07-22 15:31:30 +08:00
2019-06-08 08:41:48 +08:00
// TODO: documentation
2020-06-01 14:31:19 +08:00
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 ) )
2019-06-08 08:41:48 +08:00
}
2017-07-22 15:31:30 +08:00
/ * *
* verify
* @desc Returns true if the proof path ( array of hashes ) can connect the target node
* to the Merkle root .
2018-09-18 06:03:35 +08:00
* @param { Object [ ] } proof - Array of proof objects that should connect
2017-07-22 15:31:30 +08:00
* target node to Merkle root .
* @param { Buffer } targetNode - Target node Buffer
* @param { Buffer } root - Merkle root Buffer
* @return { Boolean }
* @example
2019-06-07 15:45:17 +08:00
* ` ` ` js
* const root = tree . getRoot ( )
* const proof = tree . getProof ( leaves [ 2 ] )
* const verified = tree . verify ( proof , leaves [ 2 ] , root )
* ` ` `
2017-07-22 15:31:30 +08:00
* /
2020-06-01 14:31:19 +08:00
verify ( proof , targetNode , root ) {
let hash = this . _bufferify ( targetNode )
root = this . _bufferify ( root )
2017-07-22 15:31:30 +08:00
if ( ! Array . isArray ( proof ) ||
2019-06-07 15:29:42 +08:00
! proof . length ||
! targetNode ||
! root ) {
2017-07-22 15:31:30 +08:00
return false
}
for ( let i = 0 ; i < proof . length ; i ++ ) {
const node = proof [ i ]
2019-06-17 08:14:53 +08:00
let data = null
let isLeftNode = null
// NOTE: case for when proof is hex values only
if ( typeof node === 'string' ) {
2020-06-01 14:31:19 +08:00
data = this . _bufferify ( node )
2019-06-17 08:14:53 +08:00
isLeftNode = true
} else {
data = node . data
isLeftNode = ( node . position === 'left' )
}
2017-07-22 15:31:30 +08:00
const buffers = [ ]
if ( this . isBitcoinTree ) {
buffers . push ( reverse ( hash ) )
2019-06-17 08:14:53 +08:00
buffers [ isLeftNode ? 'unshift' : 'push' ] ( reverse ( data ) )
2017-07-22 15:31:30 +08:00
hash = this . hashAlgo ( Buffer . concat ( buffers ) )
hash = reverse ( this . hashAlgo ( hash ) )
} else {
2019-06-08 08:29:26 +08:00
if ( this . sortPairs ) {
2019-06-17 08:14:53 +08:00
if ( Buffer . compare ( hash , data ) === - 1 ) {
buffers . push ( hash , data )
2020-06-01 14:31:19 +08:00
hash = this . hashAlgo ( Buffer . concat ( buffers ) )
2019-06-08 08:29:26 +08:00
} else {
2019-06-17 08:14:53 +08:00
buffers . push ( data , hash )
2020-06-01 14:31:19 +08:00
hash = this . hashAlgo ( Buffer . concat ( buffers ) )
2019-06-08 08:29:26 +08:00
}
} else {
2020-06-01 14:31:19 +08:00
buffers . push ( hash )
buffers [ isLeftNode ? 'unshift' : 'push' ] ( data )
hash = this . hashAlgo ( Buffer . concat ( buffers ) )
2019-06-08 08:29:26 +08:00
}
2017-07-22 15:31:30 +08:00
}
}
return Buffer . compare ( hash , root ) === 0
}
2018-10-27 04:09:38 +08:00
2018-12-10 11:57:31 +08:00
// TODO: documentation
2020-06-01 14:31:19 +08:00
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 ( ) {
2018-12-10 11:57:31 +08:00
const layers = this . getLayers ( ) . map ( x = > x . map ( x = > x . toString ( 'hex' ) ) )
2018-12-10 11:10:43 +08:00
const objs = [ ]
for ( let i = 0 ; i < layers . length ; i ++ ) {
const arr = [ ]
for ( let j = 0 ; j < layers [ i ] . length ; j ++ ) {
2018-12-10 11:57:31 +08:00
const obj = { [ layers [ i ] [ j ] ] : null }
2018-12-10 11:10:43 +08:00
if ( objs . length ) {
2018-12-10 11:57:31 +08:00
obj [ layers [ i ] [ j ] ] = { }
2018-12-10 11:10:43 +08:00
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 )
}
2018-12-10 11:57:31 +08:00
return objs [ 0 ]
}
// TODO: documentation
2020-06-01 14:31:19 +08:00
print ( ) {
2018-12-10 11:57:31 +08:00
MerkleTree . print ( this )
}
// TODO: documentation
2020-06-01 14:31:19 +08:00
toTreeString ( ) {
2018-12-10 11:57:31 +08:00
const obj = this . getLayersAsObject ( )
return treeify . asTree ( obj , true )
}
// TODO: documentation
2020-06-01 14:31:19 +08:00
toString ( ) {
2018-12-10 11:57:31 +08:00
return this . toTreeString ( )
}
// TODO: documentation
2020-06-01 14:31:19 +08:00
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 ) )
2018-12-10 11:57:31 +08:00
}
2018-12-10 11:10:43 +08:00
2018-12-10 11:57:31 +08:00
// TODO: documentation
2020-06-01 14:31:19 +08:00
static print ( tree ) {
2018-12-10 11:57:31 +08:00
console . log ( tree . toString ( ) )
2018-12-10 11:10:43 +08:00
}
2017-07-22 15:31:30 +08:00
2020-06-01 14:31:19 +08:00
_bufferToHex ( value : Buffer ) {
return '0x' + value . toString ( 'hex' )
}
2019-06-08 08:41:48 +08:00
2020-06-01 14:31:19 +08:00
_bufferify ( x ) {
return MerkleTree . bufferify ( x )
2018-10-27 03:42:14 +08:00
}
2020-06-01 14:31:19 +08:00
_bufferifyFn ( f ) {
return function ( x ) {
const v = f ( x )
if ( Buffer . isBuffer ( v ) ) {
return v
}
2018-10-27 03:42:14 +08:00
2020-06-01 14:31:19 +08:00
if ( this . _isHexStr ( v ) ) {
return Buffer . from ( v , 'hex' )
}
2018-08-03 08:46:06 +08:00
2020-06-01 14:31:19 +08:00
// crypto-js support
return Buffer . from ( f ( CryptoJS . enc . Hex . parse ( x . toString ( 'hex' ) ) ) . toString ( CryptoJS . enc . Hex ) , 'hex' )
2019-06-17 08:14:53 +08:00
}
2020-06-01 14:31:19 +08:00
}
2019-06-17 08:14:53 +08:00
2020-06-01 14:31:19 +08:00
_isHexStr ( v ) {
return MerkleTree . isHexStr ( v )
}
_log2 ( x ) {
return x === 1 ? 0 : 1 + this . _log2 ( ( x / 2 ) | 0 )
2018-08-03 08:46:06 +08:00
}
2020-06-01 14:31:19 +08:00
_zip ( a , b ) {
return a . map ( ( e , i ) = > [ e , b [ i ] ] )
}
2018-10-27 03:42:14 +08:00
}
2019-03-30 10:53:32 +08:00
export default MerkleTree