2019-03-30 10:53:32 +08:00
import * as reverse from 'buffer-reverse'
import * as CryptoJS from 'crypto-js'
import * as 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. */
2019-06-07 15:45:17 +08:00
duplicateOdd : boolean
2019-06-07 16:00:36 +08:00
/** If set to `true`, the leaves will hashed using the set hashing algorithms. */
2019-06-07 15:45:17 +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. */
2019-06-07 15:45:17 +08:00
isBitcoinTree : boolean
2019-06-08 06:15:16 +08:00
/** If set to `true`, the leaves will be sorted. */
sortLeaves : boolean
/** If set to `true`, the hashing pairs will be sorted. */
sortPairs : boolean
2019-06-07 15:45:17 +08:00
}
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
hashAlgo : ( value :any ) = > any
2019-06-07 15:29:42 +08:00
hashLeaves : boolean
2019-03-30 10:53:32 +08:00
isBitcoinTree : boolean
2019-06-07 15:45:17 +08:00
leaves : any [ ]
layers : any [ ]
2019-06-08 06:15:16 +08:00
sortLeaves : boolean
sortPairs : 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-07 15:45:17 +08:00
* const leaves = [ 'a' , 'b' , 'c' ] . map ( x = > sha3 ( 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
* /
2019-06-07 15:45:17 +08:00
constructor ( leaves , hashAlgorithm , options :Options = { } as any ) {
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-07 15:29:42 +08:00
this . duplicateOdd = ! ! options . duplicateOdd
2018-10-27 03:42:14 +08:00
this . hashAlgo = bufferifyFn ( hashAlgorithm )
2019-06-07 15:29:42 +08:00
if ( this . hashLeaves ) {
leaves = leaves . map ( this . hashAlgo )
}
2018-10-27 03:42:14 +08:00
this . leaves = leaves . map ( 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
2017-07-22 15:31:30 +08:00
createHashes ( nodes ) {
2018-10-26 13:04:51 +08:00
while ( nodes . length > 1 ) {
2017-07-22 15:31:30 +08:00
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 ) {
if ( i + 1 === nodes . length ) {
if ( nodes . length % 2 === 1 ) {
let data = nodes [ nodes . length - 1 ]
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 {
if ( ! this . duplicateOdd ) {
this . layers [ layerIndex ] . push ( nodes [ i ] )
continue
}
}
}
}
2018-08-20 04:14:03 +08:00
const left = nodes [ i ]
2019-06-07 15:29:42 +08:00
const 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 {
2019-06-08 06:15:16 +08:00
combined = [ left , right ]
}
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
* /
getLeaves() {
return this . leaves
}
/ * *
* getLayers
2017-07-22 16:05:52 +08:00
* @desc Returns array of all layers of Merkle Tree , including leaves and root .
* @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
* /
getLayers() {
return this . layers
}
/ * *
* 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
* /
getRoot() {
2018-10-26 13:04:51 +08:00
return this . layers [ this . layers . length - 1 ] [ 0 ] || Buffer . from ( [ ] )
2017-07-22 15:31:30 +08:00
}
2019-06-08 08:41:48 +08:00
// TODO: documentation
getHexRoot() {
return bufferToHex ( this . getRoot ( ) )
}
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
* const leaves = [ 'a' , 'b' , 'a' ] . map ( x = > sha3 ( x ) )
* const tree = new MerkleTree ( leaves , sha3 )
* const proof = tree . getProof ( leaves [ 2 ] , 2 )
* ` ` `
2017-07-22 15:31:30 +08:00
* /
2019-03-30 10:53:32 +08:00
getProof ( leaf , index ? ) {
2018-10-30 04:58:49 +08:00
leaf = bufferify ( leaf )
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
2018-07-11 07:31:09 +08:00
const pairIndex = ( isRightNode ? index - 1 : index )
2019-06-08 08:29:26 +08:00
const position = isRightNode ? 'left' : 'right'
2018-07-11 05:29:30 +08:00
if ( pairIndex < layer . length ) {
proof . push ( {
data : layer [ pairIndex ]
} )
}
// set index to parent index
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 {
2018-07-11 05:29:30 +08:00
2019-06-07 15:29:42 +08:00
// 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 ( {
position : isRightNode ? 'left' : 'right' ,
data : layer [ pairIndex ]
} )
}
2018-07-11 05:29:30 +08:00
2019-06-07 15:29:42 +08:00
// set index to parent index
index = ( index / 2 ) | 0
2018-07-11 05:29:30 +08:00
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
getHexProof ( leaf , index ? ) {
return this . getProof ( leaf , index ) . map ( x = > bufferToHex ( x . data ) )
}
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
* /
verify ( proof , targetNode , root ) {
2018-10-27 04:09:38 +08:00
let hash = bufferify ( targetNode )
2018-12-10 11:10:43 +08:00
root = 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' ) {
data = bufferify ( node )
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 ) )
2018-07-11 07:31:09 +08:00
2017-07-22 15:31:30 +08:00
} 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 )
2019-06-08 08:29:26 +08:00
hash = this . hashAlgo ( Buffer . concat ( buffers ) ) ;
} else {
2019-06-17 08:14:53 +08:00
buffers . push ( data , hash )
2019-06-08 08:29:26 +08:00
hash = this . hashAlgo ( Buffer . concat ( buffers ) ) ;
}
} else {
buffers . push ( hash ) ;
2019-06-17 08:14:53 +08:00
buffers [ isLeftNode ? 'unshift' : 'push' ] ( data ) ;
2019-06-08 08:29:26 +08:00
hash = this . hashAlgo ( Buffer . concat ( buffers ) ) ;
}
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
getLayersAsObject() {
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
print() {
MerkleTree . print ( this )
}
// TODO: documentation
toTreeString() {
const obj = this . getLayersAsObject ( )
return treeify . asTree ( obj , true )
}
// TODO: documentation
toString() {
return this . toTreeString ( )
}
// TODO: documentation
static bufferify ( x ) {
return bufferify ( x )
}
2018-12-10 11:10:43 +08:00
2018-12-10 11:57:31 +08:00
// TODO: documentation
static print ( tree ) {
console . log ( tree . toString ( ) )
2018-12-10 11:10:43 +08:00
}
2017-07-22 15:31:30 +08:00
}
2019-06-08 08:41:48 +08:00
function bufferToHex ( value :Buffer ) {
return '0x' + value . toString ( 'hex' )
}
2018-10-27 03:42:14 +08:00
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 ) ) {
2019-06-17 07:47:38 +08:00
return Buffer . from ( x . replace ( /^0x/ , '' ) , 'hex' )
2018-10-27 03:42:14 +08:00
} else if ( typeof x === 'string' ) {
return Buffer . from ( x )
}
}
return x
}
function bufferifyFn ( f ) {
2018-08-03 08:46:06 +08:00
return function ( x ) {
const v = f ( x )
if ( Buffer . isBuffer ( v ) ) {
return v
}
2019-06-17 08:14:53 +08:00
if ( isHexStr ( v ) ) {
return Buffer . from ( v , 'hex' )
}
2018-08-03 08:46:06 +08:00
// crypto-js support
return Buffer . from ( f ( CryptoJS . enc . Hex . parse ( x . toString ( 'hex' ) ) ) . toString ( CryptoJS . enc . Hex ) , 'hex' )
}
}
2018-12-10 11:57:31 +08:00
function isHexStr ( v ) {
2018-10-27 03:42:14 +08:00
return ( typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/ . test ( v ) )
}
2019-03-30 10:53:32 +08:00
export default MerkleTree