@@ -2,12 +2,16 @@ import assert from 'assert/strict';
2
2
import { HashZero as zero } from '@ethersproject/constants' ;
3
3
import { keccak256 } from '@ethersproject/keccak256' ;
4
4
import { SimpleMerkleTree } from './simple' ;
5
+ import { BytesLike , HexString , concat , compare } from './bytes' ;
6
+
7
+ const reverseHashPair = ( a : BytesLike , b : BytesLike ) : HexString => keccak256 ( concat ( [ a , b ] . sort ( compare ) . reverse ( ) ) ) ;
5
8
6
9
describe ( 'simple merkle tree' , ( ) => {
7
10
for ( const opts of [
8
11
{ } ,
9
12
{ sortLeaves : true } ,
10
13
{ sortLeaves : false } ,
14
+ { hashPair : reverseHashPair } ,
11
15
] ) {
12
16
describe ( `with options '${ JSON . stringify ( opts ) } '` , ( ) => {
13
17
const leaves = 'abcdef' . split ( '' ) . map ( c => keccak256 ( Buffer . from ( c ) ) ) ;
@@ -28,7 +32,11 @@ describe('simple merkle tree', () => {
28
32
29
33
assert ( tree . verify ( id , proof1 ) ) ;
30
34
assert ( tree . verify ( leaf , proof1 ) ) ;
31
- assert ( SimpleMerkleTree . verify ( tree . root , leaf , proof1 ) ) ;
35
+ if ( opts . hashPair ) {
36
+ assert ( SimpleMerkleTree . verify ( tree . root , leaf , proof1 , opts . hashPair ) ) ;
37
+ } else {
38
+ assert ( SimpleMerkleTree . verify ( tree . root , leaf , proof1 ) ) ;
39
+ }
32
40
}
33
41
} ) ;
34
42
@@ -37,7 +45,11 @@ describe('simple merkle tree', () => {
37
45
const invalidProof = otherTree . getProof ( leaf ) ;
38
46
39
47
assert ( ! tree . verify ( leaf , invalidProof ) ) ;
40
- assert ( ! SimpleMerkleTree . verify ( tree . root , leaf , invalidProof ) ) ;
48
+ if ( opts . hashPair ) {
49
+ assert ( ! SimpleMerkleTree . verify ( tree . root , leaf , invalidProof , opts . hashPair ) ) ;
50
+ } else {
51
+ assert ( ! SimpleMerkleTree . verify ( tree . root , leaf , invalidProof ) ) ;
52
+ }
41
53
} ) ;
42
54
43
55
it ( 'generates valid multiproofs' , ( ) => {
@@ -48,53 +60,80 @@ describe('simple merkle tree', () => {
48
60
assert . deepEqual ( proof1 , proof2 ) ;
49
61
50
62
assert ( tree . verifyMultiProof ( proof1 ) ) ;
51
- assert ( SimpleMerkleTree . verifyMultiProof ( tree . root , proof1 ) ) ;
63
+ if ( opts . hashPair ) {
64
+ assert ( SimpleMerkleTree . verifyMultiProof ( tree . root , proof1 , opts . hashPair ) ) ;
65
+ } else {
66
+ assert ( SimpleMerkleTree . verifyMultiProof ( tree . root , proof1 ) ) ;
67
+ }
52
68
}
53
69
} ) ;
54
70
55
71
it ( 'rejects invalid multiproofs' , ( ) => {
56
72
const multiProof = otherTree . getMultiProof ( leaves . slice ( 0 , 3 ) ) ;
57
73
58
74
assert ( ! tree . verifyMultiProof ( multiProof ) ) ;
59
- assert ( ! SimpleMerkleTree . verifyMultiProof ( tree . root , multiProof ) ) ;
75
+ if ( opts . hashPair ) {
76
+ assert ( ! SimpleMerkleTree . verifyMultiProof ( tree . root , multiProof , opts . hashPair ) ) ;
77
+ } else {
78
+ assert ( ! SimpleMerkleTree . verifyMultiProof ( tree . root , multiProof ) ) ;
79
+ }
60
80
} ) ;
61
81
62
82
it ( 'renders tree representation' , ( ) => {
63
- assert . equal (
64
- tree . render ( ) ,
65
- opts . sortLeaves == false
66
- ? [
67
- "0) 0x9012f1e18a87790d2e01faace75aaaca38e53df437cdce2c0552464dda4af49c" ,
68
- "├─ 1) 0x68203f90e9d07dc5859259d7536e87a6ba9d345f2552b5b9de2999ddce9ce1bf" ,
69
- "│ ├─ 3) 0xd253a52d4cb00de2895e85f2529e2976e6aaaa5c18106b68ab66813e14415669" ,
70
- "│ │ ├─ 7) 0xf1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3" ,
71
- "│ │ └─ 8) 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2" ,
72
- "│ └─ 4) 0x805b21d846b189efaeb0377d6bb0d201b3872a363e607c25088f025b0c6ae1f8" ,
73
- "│ ├─ 9) 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" ,
74
- "│ └─ 10) 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb" ,
75
- "└─ 2) 0xf0b49bb4b0d9396e0315755ceafaa280707b32e75e6c9053f5cdf2679dcd5c6a" ,
76
- " ├─ 5) 0xd1e8aeb79500496ef3dc2e57ba746a8315d048b7a664a2bf948db4fa91960483" ,
77
- " └─ 6) 0xa8982c89d80987fb9a510e25981ee9170206be21af3c8e0eb312ef1d3382e761" ,
78
- ] . join ( "\n" )
79
- : [
80
- "0) 0x1b404f199ea828ec5771fb30139c222d8417a82175fefad5cd42bc3a189bd8d5" ,
81
- "├─ 1) 0xec554bdfb01d31fa838d0830339b0e6e8a70e0d55a8f172ffa8bebbf8e8d5ba0" ,
82
- "│ ├─ 3) 0x434d51cfeb80272378f4c3a8fd2824561c2cad9fce556ea600d46f20550976a6" ,
83
- "│ │ ├─ 7) 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" ,
84
- "│ │ └─ 8) 0xa8982c89d80987fb9a510e25981ee9170206be21af3c8e0eb312ef1d3382e761" ,
85
- "│ └─ 4) 0x7dea550f679f3caab547cbbc5ee1a4c978c8c039b572ba00af1baa6481b88360" ,
86
- "│ ├─ 9) 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb" ,
87
- "│ └─ 10) 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2" ,
88
- "└─ 2) 0xaf46af0745b433e1d5bed9a04b1fdf4002f67a733c20db2fca5b2af6120d9bcb" ,
89
- " ├─ 5) 0xf1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3" ,
90
- " └─ 6) 0xd1e8aeb79500496ef3dc2e57ba746a8315d048b7a664a2bf948db4fa91960483" ,
91
- ] . join ( "\n" ) ,
92
- ) ;
83
+ const expected = (
84
+ // standard hash + unsorted
85
+ ! opts . hashPair && opts . sortLeaves === false
86
+ ? [
87
+ "0) 0x9012f1e18a87790d2e01faace75aaaca38e53df437cdce2c0552464dda4af49c" ,
88
+ "├─ 1) 0x68203f90e9d07dc5859259d7536e87a6ba9d345f2552b5b9de2999ddce9ce1bf" ,
89
+ "│ ├─ 3) 0xd253a52d4cb00de2895e85f2529e2976e6aaaa5c18106b68ab66813e14415669" ,
90
+ "│ │ ├─ 7) 0xf1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3" ,
91
+ "│ │ └─ 8) 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2" ,
92
+ "│ └─ 4) 0x805b21d846b189efaeb0377d6bb0d201b3872a363e607c25088f025b0c6ae1f8" ,
93
+ "│ ├─ 9) 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" ,
94
+ "│ └─ 10) 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb" ,
95
+ "└─ 2) 0xf0b49bb4b0d9396e0315755ceafaa280707b32e75e6c9053f5cdf2679dcd5c6a" ,
96
+ " ├─ 5) 0xd1e8aeb79500496ef3dc2e57ba746a8315d048b7a664a2bf948db4fa91960483" ,
97
+ " └─ 6) 0xa8982c89d80987fb9a510e25981ee9170206be21af3c8e0eb312ef1d3382e761" ,
98
+ ]
99
+ // sortLeaves = true | undefined --- standard hash + sorted
100
+ : ! opts . hashPair
101
+ ? [
102
+ "0) 0x1b404f199ea828ec5771fb30139c222d8417a82175fefad5cd42bc3a189bd8d5" ,
103
+ "├─ 1) 0xec554bdfb01d31fa838d0830339b0e6e8a70e0d55a8f172ffa8bebbf8e8d5ba0" ,
104
+ "│ ├─ 3) 0x434d51cfeb80272378f4c3a8fd2824561c2cad9fce556ea600d46f20550976a6" ,
105
+ "│ │ ├─ 7) 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" ,
106
+ "│ │ └─ 8) 0xa8982c89d80987fb9a510e25981ee9170206be21af3c8e0eb312ef1d3382e761" ,
107
+ "│ └─ 4) 0x7dea550f679f3caab547cbbc5ee1a4c978c8c039b572ba00af1baa6481b88360" ,
108
+ "│ ├─ 9) 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb" ,
109
+ "│ └─ 10) 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2" ,
110
+ "└─ 2) 0xaf46af0745b433e1d5bed9a04b1fdf4002f67a733c20db2fca5b2af6120d9bcb" ,
111
+ " ├─ 5) 0xf1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3" ,
112
+ " └─ 6) 0xd1e8aeb79500496ef3dc2e57ba746a8315d048b7a664a2bf948db4fa91960483" ,
113
+ ]
114
+ // non standard hash
115
+ : [
116
+ "0) 0x8f0a1adb058c628fa4ce2e7bd26024180b888fec77087d4e5ee6890746e9c6ec" ,
117
+ "├─ 1) 0xb9f5a6bc1b75fadcd9765163dfc8d4865d1608337a2a310ff51fecb431faaee4" ,
118
+ "│ ├─ 3) 0x37d657e93dfbae50b18241610418794b51124af5ca872f1b56c08490cb2905ac" ,
119
+ "│ │ ├─ 7) 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" ,
120
+ "│ │ └─ 8) 0xa8982c89d80987fb9a510e25981ee9170206be21af3c8e0eb312ef1d3382e761" ,
121
+ "│ └─ 4) 0xed90ef72e95e6692b91b020dc6cb5c4db9dc149a496799c4318fa8075960c48e" ,
122
+ "│ ├─ 9) 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb" ,
123
+ "│ └─ 10) 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2" ,
124
+ "└─ 2) 0x138c55cca8f6430d75b6bbcea643a7afa8ee74c22643ad76723ecafd4fcd21d4" ,
125
+ " ├─ 5) 0xf1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3" ,
126
+ " └─ 6) 0xd1e8aeb79500496ef3dc2e57ba746a8315d048b7a664a2bf948db4fa91960483" ,
127
+ ]
128
+ ) . join ( "\n" ) ;
129
+
130
+ assert . equal ( tree . render ( ) , expected ) ;
93
131
} ) ;
94
132
95
133
it ( 'dump and load' , ( ) => {
96
- const recoveredTree = SimpleMerkleTree . load ( tree . dump ( ) ) ;
97
-
134
+ const recoveredTree = opts . hashPair
135
+ ? SimpleMerkleTree . load ( tree . dump ( ) , opts . hashPair )
136
+ : SimpleMerkleTree . load ( tree . dump ( ) ) ;
98
137
recoveredTree . validate ( ) ;
99
138
assert . deepEqual ( tree , recoveredTree ) ;
100
139
} ) ;
@@ -124,6 +163,20 @@ describe('simple merkle tree', () => {
124
163
) ;
125
164
} ) ;
126
165
166
+ it ( 'reject standard tree dump with a custom hash' , ( ) => {
167
+ assert . throws (
168
+ ( ) => SimpleMerkleTree . load ( { format : 'standard-v1' } as any , reverseHashPair ) ,
169
+ / ^ E r r o r : F o r m a t ' s t a n d a r d - v 1 ' d o e s n o t s u p p o r t c u s t o m h a s h i n g f u n c t i o n s $ / ,
170
+ ) ;
171
+ } ) ;
172
+
173
+ it ( 'reject custom tree dump without a custom hash' , ( ) => {
174
+ assert . throws (
175
+ ( ) => SimpleMerkleTree . load ( { format : 'custom-v1' } as any ) ,
176
+ / ^ E r r o r : F o r m a t ' c u s t o m - v 1 ' r e q u i r e s a h a s h i n g f u n c t i o n $ / ,
177
+ ) ;
178
+ } ) ;
179
+
127
180
it ( 'reject malformed tree dump' , ( ) => {
128
181
const loadedTree1 = SimpleMerkleTree . load ( {
129
182
format : 'standard-v1' ,
0 commit comments