github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/crypto/merkle/tree_test.go (about) 1 package merkle 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/hex" 7 "io" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 "github.com/ari-anchor/sei-tendermint/crypto" 14 "github.com/ari-anchor/sei-tendermint/crypto/tmhash" 15 ctest "github.com/ari-anchor/sei-tendermint/internal/libs/test" 16 tmrand "github.com/ari-anchor/sei-tendermint/libs/rand" 17 ) 18 19 type testItem []byte 20 21 func (tI testItem) Hash() []byte { 22 return []byte(tI) 23 } 24 25 func TestHashFromByteSlices(t *testing.T) { 26 testcases := map[string]struct { 27 slices [][]byte 28 expectHash string // in hex format 29 }{ 30 "nil": {nil, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, 31 "empty": {[][]byte{}, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, 32 "single": {[][]byte{{1, 2, 3}}, "054edec1d0211f624fed0cbca9d4f9400b0e491c43742af2c5b0abebf0c990d8"}, 33 "single blank": {[][]byte{{}}, "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"}, 34 "two": {[][]byte{{1, 2, 3}, {4, 5, 6}}, "82e6cfce00453804379b53962939eaa7906b39904be0813fcadd31b100773c4b"}, 35 "many": { 36 [][]byte{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}}, 37 "f326493eceab4f2d9ffbc78c59432a0a005d6ea98392045c74df5d14a113be18", 38 }, 39 } 40 for name, tc := range testcases { 41 tc := tc 42 t.Run(name, func(t *testing.T) { 43 hash := HashFromByteSlices(tc.slices) 44 assert.Equal(t, tc.expectHash, hex.EncodeToString(hash)) 45 }) 46 } 47 } 48 49 func TestProof(t *testing.T) { 50 51 // Try an empty proof first 52 rootHash, proofs := ProofsFromByteSlices([][]byte{}) 53 require.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", hex.EncodeToString(rootHash)) 54 require.Empty(t, proofs) 55 56 total := 100 57 58 items := make([][]byte, total) 59 for i := 0; i < total; i++ { 60 items[i] = testItem(tmrand.Bytes(crypto.HashSize)) 61 } 62 63 rootHash = HashFromByteSlices(items) 64 65 rootHash2, proofs := ProofsFromByteSlices(items) 66 67 require.Equal(t, rootHash, rootHash2, "Unmatched root hashes: %X vs %X", rootHash, rootHash2) 68 69 // For each item, check the trail. 70 for i, item := range items { 71 proof := proofs[i] 72 73 // Check total/index 74 require.EqualValues(t, proof.Index, i, "Unmatched indicies: %d vs %d", proof.Index, i) 75 76 require.EqualValues(t, proof.Total, total, "Unmatched totals: %d vs %d", proof.Total, total) 77 78 // Verify success 79 err := proof.Verify(rootHash, item) 80 require.NoError(t, err, "Verification failed: %v.", err) 81 82 // Trail too long should make it fail 83 origAunts := proof.Aunts 84 proof.Aunts = append(proof.Aunts, tmrand.Bytes(32)) 85 err = proof.Verify(rootHash, item) 86 require.Error(t, err, "Expected verification to fail for wrong trail length") 87 88 proof.Aunts = origAunts 89 90 // Trail too short should make it fail 91 proof.Aunts = proof.Aunts[0 : len(proof.Aunts)-1] 92 err = proof.Verify(rootHash, item) 93 require.Error(t, err, "Expected verification to fail for wrong trail length") 94 95 proof.Aunts = origAunts 96 97 // Mutating the itemHash should make it fail. 98 err = proof.Verify(rootHash, ctest.MutateByteSlice(item)) 99 require.Error(t, err, "Expected verification to fail for mutated leaf hash") 100 101 // Mutating the rootHash should make it fail. 102 err = proof.Verify(ctest.MutateByteSlice(rootHash), item) 103 require.Error(t, err, "Expected verification to fail for mutated root hash") 104 } 105 } 106 107 func TestHashAlternatives(t *testing.T) { 108 109 total := 100 110 111 items := make([][]byte, total) 112 for i := 0; i < total; i++ { 113 items[i] = testItem(tmrand.Bytes(crypto.HashSize)) 114 } 115 116 rootHash1 := HashFromByteSlicesIterative(items) 117 rootHash2 := HashFromByteSlices(items) 118 require.Equal(t, rootHash1, rootHash2, "Unmatched root hashes: %X vs %X", rootHash1, rootHash2) 119 } 120 121 // See https://blog.verichains.io/p/vsa-2022-100-tendermint-forging-membership-proof?utm_source=substack&utm_campaign=post_embed&utm_medium=web 122 // for context 123 func TestForgeEmptyMerkleTreeAttack(t *testing.T) { 124 key := []byte{0x13} 125 value := []byte{0x37} 126 vhash := tmhash.Sum(value) 127 bz := new(bytes.Buffer) 128 _ = EncodeByteSlice(bz, key) 129 _ = EncodeByteSlice(bz, vhash) 130 kvhash := tmhash.Sum(append([]byte{0}, bz.Bytes()...)) 131 op := NewValueOp(key, &Proof{LeafHash: kvhash}) 132 var root []byte 133 err := ProofOperators{op}.Verify(root, "/"+string(key), [][]byte{value}) 134 // Must return error or else the attack would be possible 135 require.NotNil(t, err) 136 } 137 138 func BenchmarkHashAlternatives(b *testing.B) { 139 total := 100 140 141 items := make([][]byte, total) 142 for i := 0; i < total; i++ { 143 items[i] = testItem(tmrand.Bytes(crypto.HashSize)) 144 } 145 146 b.ResetTimer() 147 b.Run("recursive", func(b *testing.B) { 148 for i := 0; i < b.N; i++ { 149 _ = HashFromByteSlices(items) 150 } 151 }) 152 153 b.Run("iterative", func(b *testing.B) { 154 for i := 0; i < b.N; i++ { 155 _ = HashFromByteSlicesIterative(items) 156 } 157 }) 158 } 159 160 func Test_getSplitPoint(t *testing.T) { 161 tests := []struct { 162 length int64 163 want int64 164 }{ 165 {1, 0}, 166 {2, 1}, 167 {3, 2}, 168 {4, 2}, 169 {5, 4}, 170 {10, 8}, 171 {20, 16}, 172 {100, 64}, 173 {255, 128}, 174 {256, 128}, 175 {257, 256}, 176 } 177 for _, tt := range tests { 178 got := getSplitPoint(tt.length) 179 require.EqualValues(t, tt.want, got, "getSplitPoint(%d) = %v, want %v", tt.length, got, tt.want) 180 } 181 } 182 183 func EncodeUvarint(w io.Writer, u uint64) (err error) { 184 var buf [10]byte 185 n := binary.PutUvarint(buf[:], u) 186 _, err = w.Write(buf[0:n]) 187 return 188 } 189 190 func EncodeByteSlice(w io.Writer, bz []byte) (err error) { 191 err = EncodeUvarint(w, uint64(len(bz))) 192 if err != nil { 193 return 194 } 195 _, err = w.Write(bz) 196 return 197 }