11import { test , testProp , fc } from '@fast-check/ava' ;
22import { HashZero as zero } from '@ethersproject/constants' ;
3+ import { keccak256 } from '@ethersproject/keccak256' ;
34import { SimpleMerkleTree } from './simple' ;
5+ import { BytesLike , HexString , concat , compare } from './bytes' ;
6+
7+ const reverseNodeHash = ( a : BytesLike , b : BytesLike ) : HexString => keccak256 ( concat ( [ a , b ] . sort ( compare ) . reverse ( ) ) ) ;
8+ const otherNodeHash = ( a : BytesLike , b : BytesLike ) : HexString => keccak256 ( reverseNodeHash ( a , b ) ) ; // double hash
9+
410import { toHex } from './bytes' ;
511import { InvalidArgumentError , InvariantError } from './utils/errors' ;
612
713const leaf = fc . uint8Array ( { minLength : 32 , maxLength : 32 } ) . map ( toHex ) ;
814const leaves = fc . array ( leaf , { minLength : 1 } ) ;
9- const options = fc . record ( { sortLeaves : fc . oneof ( fc . constant ( undefined ) , fc . boolean ( ) ) } ) ;
15+ const options = fc . record ( {
16+ sortLeaves : fc . oneof ( fc . constant ( undefined ) , fc . boolean ( ) ) ,
17+ nodeHash : fc . oneof ( fc . constant ( undefined ) , fc . constant ( reverseNodeHash ) ) ,
18+ } ) ;
1019
11- const tree = fc . tuple ( leaves , options ) . map ( ( [ leaves , options ] ) => SimpleMerkleTree . of ( leaves , options ) ) ;
20+ const tree = fc
21+ . tuple ( leaves , options )
22+ . chain ( ( [ leaves , options ] ) => fc . tuple ( fc . constant ( SimpleMerkleTree . of ( leaves , options ) ) , fc . constant ( options ) ) ) ;
1223const treeAndLeaf = fc . tuple ( leaves , options ) . chain ( ( [ leaves , options ] ) =>
1324 fc . tuple (
1425 fc . constant ( SimpleMerkleTree . of ( leaves , options ) ) ,
26+ fc . constant ( options ) ,
1527 fc . nat ( { max : leaves . length - 1 } ) . map ( index => ( { value : leaves [ index ] ! , index } ) ) ,
1628 ) ,
1729) ;
1830const treeAndLeaves = fc . tuple ( leaves , options ) . chain ( ( [ leaves , options ] ) =>
1931 fc . tuple (
2032 fc . constant ( SimpleMerkleTree . of ( leaves , options ) ) ,
33+ fc . constant ( options ) ,
2134 fc
2235 . uniqueArray ( fc . nat ( { max : leaves . length - 1 } ) )
2336 . map ( indices => indices . map ( index => ( { value : leaves [ index ] ! , index } ) ) ) ,
@@ -26,48 +39,64 @@ const treeAndLeaves = fc.tuple(leaves, options).chain(([leaves, options]) =>
2639
2740fc . configureGlobal ( { numRuns : process . env . CI ? 10000 : 100 } ) ;
2841
29- testProp ( 'generates a valid tree' , [ tree ] , ( t , tree ) => {
42+ testProp ( 'generates a valid tree' , [ tree ] , ( t , [ tree ] ) => {
3043 t . notThrows ( ( ) => tree . validate ( ) ) ;
3144} ) ;
3245
33- testProp ( 'generates valid single proofs for all leaves' , [ treeAndLeaf ] , ( t , [ tree , { value : leaf , index } ] ) => {
34- const proof1 = tree . getProof ( index ) ;
35- const proof2 = tree . getProof ( leaf ) ;
36-
37- t . deepEqual ( proof1 , proof2 ) ;
38- t . true ( tree . verify ( index , proof1 ) ) ;
39- t . true ( tree . verify ( leaf , proof1 ) ) ;
40- t . true ( SimpleMerkleTree . verify ( tree . root , leaf , proof1 ) ) ;
41- } ) ;
46+ testProp (
47+ 'generates valid single proofs for all leaves' ,
48+ [ treeAndLeaf ] ,
49+ ( t , [ tree , options , { value : leaf , index } ] ) => {
50+ const proof1 = tree . getProof ( index ) ;
51+ const proof2 = tree . getProof ( leaf ) ;
52+
53+ t . deepEqual ( proof1 , proof2 ) ;
54+ t . true ( tree . verify ( index , proof1 ) ) ;
55+ t . true ( tree . verify ( leaf , proof1 ) ) ;
56+ t . true ( SimpleMerkleTree . verify ( tree . root , leaf , proof1 , options . nodeHash ) ) ;
57+ } ,
58+ ) ;
4259
43- testProp ( 'rejects invalid proofs' , [ treeAndLeaf , tree ] , ( t , [ tree , { value : leaf } ] , otherTree ) => {
44- const proof = tree . getProof ( leaf ) ;
45- t . false ( otherTree . verify ( leaf , proof ) ) ;
46- t . false ( SimpleMerkleTree . verify ( otherTree . root , leaf , proof ) ) ;
47- } ) ;
60+ testProp (
61+ 'rejects invalid proofs' ,
62+ [ treeAndLeaf , tree ] ,
63+ ( t , [ tree , options , { value : leaf } ] , [ otherTree , otherOptions ] ) => {
64+ const proof = tree . getProof ( leaf ) ;
65+ t . false ( otherTree . verify ( leaf , proof ) ) ;
66+ t . false ( SimpleMerkleTree . verify ( otherTree . root , leaf , proof , options . nodeHash ) ) ;
67+ t . false ( SimpleMerkleTree . verify ( otherTree . root , leaf , proof , otherOptions . nodeHash ) ) ;
68+ } ,
69+ ) ;
4870
49- testProp ( 'generates valid multiproofs' , [ treeAndLeaves ] , ( t , [ tree , indices ] ) => {
71+ testProp ( 'generates valid multiproofs' , [ treeAndLeaves ] , ( t , [ tree , options , indices ] ) => {
5072 const proof1 = tree . getMultiProof ( indices . map ( e => e . index ) ) ;
5173 const proof2 = tree . getMultiProof ( indices . map ( e => e . value ) ) ;
5274
5375 t . deepEqual ( proof1 , proof2 ) ;
5476 t . true ( tree . verifyMultiProof ( proof1 ) ) ;
55- t . true ( SimpleMerkleTree . verifyMultiProof ( tree . root , proof1 ) ) ;
77+ t . true ( SimpleMerkleTree . verifyMultiProof ( tree . root , proof1 , options . nodeHash ) ) ;
5678} ) ;
5779
58- testProp ( 'rejects invalid multiproofs' , [ treeAndLeaves , tree ] , ( t , [ tree , indices ] , otherTree ) => {
59- const multiProof = tree . getMultiProof ( indices . map ( e => e . index ) ) ;
60-
61- t . false ( otherTree . verifyMultiProof ( multiProof ) ) ;
62- t . false ( SimpleMerkleTree . verifyMultiProof ( otherTree . root , multiProof ) ) ;
63- } ) ;
80+ testProp (
81+ 'rejects invalid multiproofs' ,
82+ [ treeAndLeaves , tree ] ,
83+ ( t , [ tree , options , indices ] , [ otherTree , otherOptions ] ) => {
84+ const multiProof = tree . getMultiProof ( indices . map ( e => e . index ) ) ;
85+
86+ t . false ( otherTree . verifyMultiProof ( multiProof ) ) ;
87+ t . false ( SimpleMerkleTree . verifyMultiProof ( otherTree . root , multiProof , options . nodeHash ) ) ;
88+ t . false ( SimpleMerkleTree . verifyMultiProof ( otherTree . root , multiProof , otherOptions . nodeHash ) ) ;
89+ } ,
90+ ) ;
6491
6592testProp (
6693 'renders tree representation' ,
6794 [ leaves ] ,
6895 ( t , leaves ) => {
6996 t . snapshot ( SimpleMerkleTree . of ( leaves , { sortLeaves : true } ) . render ( ) ) ;
7097 t . snapshot ( SimpleMerkleTree . of ( leaves , { sortLeaves : false } ) . render ( ) ) ;
98+ t . snapshot ( SimpleMerkleTree . of ( leaves , { sortLeaves : true , nodeHash : reverseNodeHash } ) . render ( ) ) ;
99+ t . snapshot ( SimpleMerkleTree . of ( leaves , { sortLeaves : false , nodeHash : reverseNodeHash } ) . render ( ) ) ;
71100 } ,
72101 { numRuns : 1 , seed : 0 } ,
73102) ;
@@ -78,24 +107,34 @@ testProp(
78107 ( t , leaves ) => {
79108 t . snapshot ( SimpleMerkleTree . of ( leaves , { sortLeaves : true } ) . dump ( ) ) ;
80109 t . snapshot ( SimpleMerkleTree . of ( leaves , { sortLeaves : false } ) . dump ( ) ) ;
110+ t . snapshot ( SimpleMerkleTree . of ( leaves , { sortLeaves : true , nodeHash : reverseNodeHash } ) . dump ( ) ) ;
111+ t . snapshot ( SimpleMerkleTree . of ( leaves , { sortLeaves : false , nodeHash : reverseNodeHash } ) . dump ( ) ) ;
81112 } ,
82113 { numRuns : 1 , seed : 0 } ,
83114) ;
84115
85- testProp ( 'dump and load' , [ tree ] , ( t , tree ) => {
86- const recoveredTree = SimpleMerkleTree . load ( tree . dump ( ) ) ;
87- recoveredTree . validate ( ) ;
116+ testProp ( 'dump and load' , [ tree ] , ( t , [ tree , options ] ) => {
117+ const dump = tree . dump ( ) ;
118+ const recoveredTree = SimpleMerkleTree . load ( dump , options . nodeHash ) ;
119+ recoveredTree . validate ( ) ; // already done in load
88120
121+ t . is ( dump . hash , options . nodeHash ? 'custom' : undefined ) ;
89122 t . is ( tree . root , recoveredTree . root ) ;
90123 t . is ( tree . render ( ) , recoveredTree . render ( ) ) ;
91124 t . deepEqual ( tree . entries ( ) , recoveredTree . entries ( ) ) ;
92125 t . deepEqual ( tree . dump ( ) , recoveredTree . dump ( ) ) ;
93126} ) ;
94127
95- testProp ( 'reject out of bounds value index' , [ tree ] , ( t , tree ) => {
128+ testProp ( 'reject out of bounds value index' , [ tree ] , ( t , [ tree ] ) => {
96129 t . throws ( ( ) => tree . getProof ( - 1 ) , new InvalidArgumentError ( 'Index out of bounds' ) ) ;
97130} ) ;
98131
132+ // We need at least 2 leaves for internal node hashing to come into play
133+ testProp ( 'reject loading dump with wrong node hash' , [ fc . array ( leaf , { minLength : 2 } ) ] , ( t , leaves ) => {
134+ const dump = SimpleMerkleTree . of ( leaves , { nodeHash : reverseNodeHash } ) . dump ( ) ;
135+ t . throws ( ( ) => SimpleMerkleTree . load ( dump , otherNodeHash ) , new InvariantError ( 'Merkle tree is invalid' ) ) ;
136+ } ) ;
137+
99138test ( 'reject invalid leaf size' , t => {
100139 const invalidLeaf = '0x000000000000000000000000000000000000000000000000000000000000000000' ;
101140 t . throws ( ( ) => SimpleMerkleTree . of ( [ invalidLeaf ] ) , {
@@ -116,22 +155,28 @@ test('reject unrecognized tree dump', t => {
116155} ) ;
117156
118157test ( 'reject malformed tree dump' , t => {
119- const loadedTree1 = SimpleMerkleTree . load ( {
120- format : 'simple-v1' ,
121- tree : [ zero ] ,
122- values : [
123- {
124- value : '0x0000000000000000000000000000000000000000000000000000000000000001' ,
125- treeIndex : 0 ,
126- } ,
127- ] ,
128- } ) ;
129- t . throws ( ( ) => loadedTree1 . getProof ( 0 ) , new InvariantError ( 'Merkle tree does not contain the expected value' ) ) ;
158+ t . throws (
159+ ( ) =>
160+ SimpleMerkleTree . load ( {
161+ format : 'simple-v1' ,
162+ tree : [ zero ] ,
163+ values : [
164+ {
165+ value : '0x0000000000000000000000000000000000000000000000000000000000000001' ,
166+ treeIndex : 0 ,
167+ } ,
168+ ] ,
169+ } ) ,
170+ new InvariantError ( 'Merkle tree does not contain the expected value' ) ,
171+ ) ;
130172
131- const loadedTree2 = SimpleMerkleTree . load ( {
132- format : 'simple-v1' ,
133- tree : [ zero , zero , zero ] ,
134- values : [ { value : zero , treeIndex : 2 } ] ,
135- } ) ;
136- t . throws ( ( ) => loadedTree2 . getProof ( 0 ) , new InvariantError ( 'Unable to prove value' ) ) ;
173+ t . throws (
174+ ( ) =>
175+ SimpleMerkleTree . load ( {
176+ format : 'simple-v1' ,
177+ tree : [ zero , zero , zero ] ,
178+ values : [ { value : zero , treeIndex : 2 } ] ,
179+ } ) ,
180+ new InvariantError ( 'Merkle tree is invalid' ) ,
181+ ) ;
137182} ) ;
0 commit comments