github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/iavl/proof_forgery_test.go (about) 1 package iavl_test 2 3 import ( 4 "encoding/hex" 5 "math/rand" 6 "strings" 7 "testing" 8 9 "github.com/stretchr/testify/require" 10 11 "github.com/gnolang/gno/tm2/pkg/crypto/tmhash" 12 "github.com/gnolang/gno/tm2/pkg/db/memdb" 13 "github.com/gnolang/gno/tm2/pkg/iavl" 14 ) 15 16 func TestProofForgery(t *testing.T) { 17 t.Parallel() 18 19 source := rand.NewSource(0) 20 r := rand.New(source) 21 cacheSize := 0 22 tree := iavl.NewMutableTree(memdb.NewMemDB(), cacheSize) 23 24 // two keys only 25 keys := []byte{0x11, 0x32} 26 values := make([][]byte, len(keys)) 27 // make random values and insert into tree 28 for i, ikey := range keys { 29 key := []byte{ikey} 30 v := r.Intn(255) 31 values[i] = []byte{byte(v)} 32 tree.Set(key, values[i]) 33 } 34 35 // get root 36 root := tree.WorkingHash() 37 // use the rightmost kv pair in the tree so the inner nodes will populate left 38 k := []byte{keys[1]} 39 v := values[1] 40 41 val, proof, err := tree.GetWithProof(k) 42 require.NoError(t, err) 43 44 err = proof.Verify(root) 45 require.NoError(t, err) 46 err = proof.VerifyItem(k, val) 47 require.NoError(t, err) 48 49 // ------------------- FORGE PROOF ------------------- 50 51 forgedPayloadBytes := decodeHex(t, "0xabcd") 52 forgedValueHash := tmhash.Sum(forgedPayloadBytes) 53 // make a forgery of the proof by adding: 54 // - a new leaf node to the right 55 // - an empty inner node 56 // - a right entry in the path 57 _, proof2, _ := tree.GetWithProof(k) 58 forgedNode := proof2.Leaves[0] 59 forgedNode.Key = []byte{0xFF} 60 forgedNode.ValueHash = forgedValueHash 61 proof2.Leaves = append(proof2.Leaves, forgedNode) 62 proof2.InnerNodes = append(proof2.InnerNodes, iavl.PathToLeaf{}) 63 // figure out what hash we need via https://twitter.com/samczsun/status/1578181160345034752 64 proof2.LeftPath[0].Right = decodeHex(t, "82C36CED85E914DAE8FDF6DD11FD5833121AA425711EB126C470CE28FF6623D5") 65 66 rootHashValid := proof.ComputeRootHash() 67 verifyErr := proof.Verify(rootHashValid) 68 require.NoError(t, verifyErr, "should verify") 69 70 // forged proofs now should make ComputeRootHash() and Verify() panic 71 var rootHashForged []byte 72 require.Panics(t, func() { rootHashForged = proof2.ComputeRootHash() }, "ComputeRootHash must panic if both left and right are set") 73 require.Panics(t, func() { proof2.Verify(rootHashForged) }, "forged proof should not verify") 74 require.Panics(t, func() { proof2.Verify(rootHashValid) }, "verify (tentatively forged) proof2 two fails with valid proof") 75 76 { 77 // legit node verifies against legit proof (expected) 78 verifyErr = proof.VerifyItem(k, v) 79 require.NoError(t, verifyErr, "valid proof should verify") 80 // forged node fails to verify against legit proof (expected) 81 verifyErr = proof.VerifyItem(forgedNode.Key, forgedPayloadBytes) 82 require.Error(t, verifyErr, "forged proof should fail to verify") 83 } 84 { 85 // legit node fails to verify against forged proof (expected) 86 verifyErr = proof2.VerifyItem(k, v) 87 require.Error(t, verifyErr, "valid proof should verify, but has a forged sister node") 88 89 // forged node fails to verify against forged proof (previously this succeeded!) 90 verifyErr = proof2.VerifyItem(forgedNode.Key, forgedPayloadBytes) 91 require.Error(t, verifyErr, "forged proof should fail verify") 92 } 93 } 94 95 func decodeHex(t *testing.T, str string) []byte { 96 t.Helper() 97 if strings.HasPrefix(str, "0x") { 98 str = str[2:] 99 } 100 b, err := hex.DecodeString(str) 101 if err != nil { 102 t.Fatalf("unable to decode string, %v", err) 103 } 104 return b 105 }