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  }