github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/crypto/merkle/proof_test.go (about)

     1  package merkle
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/badrootd/nibiru-cometbft/crypto/tmhash"
    13  	cmtcrypto "github.com/badrootd/nibiru-cometbft/proto/tendermint/crypto"
    14  )
    15  
    16  const ProofOpDomino = "test:domino"
    17  
    18  // Expects given input, produces given output.
    19  // Like the game dominos.
    20  type DominoOp struct {
    21  	key    string // unexported, may be empty
    22  	Input  string
    23  	Output string
    24  }
    25  
    26  func NewDominoOp(key, input, output string) DominoOp {
    27  	return DominoOp{
    28  		key:    key,
    29  		Input:  input,
    30  		Output: output,
    31  	}
    32  }
    33  
    34  func (dop DominoOp) ProofOp() cmtcrypto.ProofOp {
    35  	dopb := cmtcrypto.DominoOp{
    36  		Key:    dop.key,
    37  		Input:  dop.Input,
    38  		Output: dop.Output,
    39  	}
    40  	bz, err := dopb.Marshal()
    41  	if err != nil {
    42  		panic(err)
    43  	}
    44  
    45  	return cmtcrypto.ProofOp{
    46  		Type: ProofOpDomino,
    47  		Key:  []byte(dop.key),
    48  		Data: bz,
    49  	}
    50  }
    51  
    52  func (dop DominoOp) Run(input [][]byte) (output [][]byte, err error) {
    53  	if len(input) != 1 {
    54  		return nil, errors.New("expected input of length 1")
    55  	}
    56  	if string(input[0]) != dop.Input {
    57  		return nil, fmt.Errorf("expected input %v, got %v",
    58  			dop.Input, string(input[0]))
    59  	}
    60  	return [][]byte{[]byte(dop.Output)}, nil
    61  }
    62  
    63  func (dop DominoOp) GetKey() []byte {
    64  	return []byte(dop.key)
    65  }
    66  
    67  //----------------------------------------
    68  
    69  func TestProofOperators(t *testing.T) {
    70  	var err error
    71  
    72  	// ProofRuntime setup
    73  	// TODO test this somehow.
    74  
    75  	// ProofOperators setup
    76  	op1 := NewDominoOp("KEY1", "INPUT1", "INPUT2")
    77  	op2 := NewDominoOp("KEY2", "INPUT2", "INPUT3")
    78  	op3 := NewDominoOp("", "INPUT3", "INPUT4")
    79  	op4 := NewDominoOp("KEY4", "INPUT4", "OUTPUT4")
    80  
    81  	// Good
    82  	popz := ProofOperators([]ProofOperator{op1, op2, op3, op4})
    83  	err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")})
    84  	assert.Nil(t, err)
    85  	err = popz.VerifyValue(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", bz("INPUT1"))
    86  	assert.Nil(t, err)
    87  
    88  	// BAD INPUT
    89  	err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1_WRONG")})
    90  	assert.NotNil(t, err)
    91  	err = popz.VerifyValue(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", bz("INPUT1_WRONG"))
    92  	assert.NotNil(t, err)
    93  
    94  	// BAD KEY 1
    95  	err = popz.Verify(bz("OUTPUT4"), "/KEY3/KEY2/KEY1", [][]byte{bz("INPUT1")})
    96  	assert.NotNil(t, err)
    97  
    98  	// BAD KEY 2
    99  	err = popz.Verify(bz("OUTPUT4"), "KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")})
   100  	assert.NotNil(t, err)
   101  
   102  	// BAD KEY 3
   103  	err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1/", [][]byte{bz("INPUT1")})
   104  	assert.NotNil(t, err)
   105  
   106  	// BAD KEY 4
   107  	err = popz.Verify(bz("OUTPUT4"), "//KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")})
   108  	assert.NotNil(t, err)
   109  
   110  	// BAD KEY 5
   111  	err = popz.Verify(bz("OUTPUT4"), "/KEY2/KEY1", [][]byte{bz("INPUT1")})
   112  	assert.NotNil(t, err)
   113  
   114  	// BAD OUTPUT 1
   115  	err = popz.Verify(bz("OUTPUT4_WRONG"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")})
   116  	assert.NotNil(t, err)
   117  
   118  	// BAD OUTPUT 2
   119  	err = popz.Verify(bz(""), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")})
   120  	assert.NotNil(t, err)
   121  
   122  	// BAD POPZ 1
   123  	popz = []ProofOperator{op1, op2, op4}
   124  	err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")})
   125  	assert.NotNil(t, err)
   126  
   127  	// BAD POPZ 2
   128  	popz = []ProofOperator{op4, op3, op2, op1}
   129  	err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")})
   130  	assert.NotNil(t, err)
   131  
   132  	// BAD POPZ 3
   133  	popz = []ProofOperator{}
   134  	err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")})
   135  	assert.NotNil(t, err)
   136  }
   137  
   138  func bz(s string) []byte {
   139  	return []byte(s)
   140  }
   141  
   142  func TestProofValidateBasic(t *testing.T) {
   143  	testCases := []struct {
   144  		testName      string
   145  		malleateProof func(*Proof)
   146  		errStr        string
   147  	}{
   148  		{"Good", func(sp *Proof) {}, ""},
   149  		{"Negative Total", func(sp *Proof) { sp.Total = -1 }, "negative Total"},
   150  		{"Negative Index", func(sp *Proof) { sp.Index = -1 }, "negative Index"},
   151  		{"Invalid LeafHash", func(sp *Proof) { sp.LeafHash = make([]byte, 10) },
   152  			"expected LeafHash size to be 32, got 10"},
   153  		{"Too many Aunts", func(sp *Proof) { sp.Aunts = make([][]byte, MaxAunts+1) },
   154  			"expected no more than 100 aunts, got 101"},
   155  		{"Invalid Aunt", func(sp *Proof) { sp.Aunts[0] = make([]byte, 10) },
   156  			"expected Aunts#0 size to be 32, got 10"},
   157  	}
   158  
   159  	for _, tc := range testCases {
   160  		tc := tc
   161  		t.Run(tc.testName, func(t *testing.T) {
   162  			_, proofs := ProofsFromByteSlices([][]byte{
   163  				[]byte("apple"),
   164  				[]byte("watermelon"),
   165  				[]byte("kiwi"),
   166  			})
   167  			tc.malleateProof(proofs[0])
   168  			err := proofs[0].ValidateBasic()
   169  			if tc.errStr != "" {
   170  				assert.Contains(t, err.Error(), tc.errStr)
   171  			}
   172  		})
   173  	}
   174  }
   175  func TestVoteProtobuf(t *testing.T) {
   176  
   177  	_, proofs := ProofsFromByteSlices([][]byte{
   178  		[]byte("apple"),
   179  		[]byte("watermelon"),
   180  		[]byte("kiwi"),
   181  	})
   182  	testCases := []struct {
   183  		testName string
   184  		v1       *Proof
   185  		expPass  bool
   186  	}{
   187  		{"empty proof", &Proof{}, false},
   188  		{"failure nil", nil, false},
   189  		{"success", proofs[0], true},
   190  	}
   191  	for _, tc := range testCases {
   192  		pb := tc.v1.ToProto()
   193  
   194  		v, err := ProofFromProto(pb)
   195  		if tc.expPass {
   196  			require.NoError(t, err)
   197  			require.Equal(t, tc.v1, v, tc.testName)
   198  		} else {
   199  			require.Error(t, err)
   200  		}
   201  	}
   202  }
   203  
   204  // TestVsa2022_100 verifies https://blog.verichains.io/p/vsa-2022-100-tendermint-forging-membership-proof
   205  func TestVsa2022_100(t *testing.T) {
   206  	// a fake key-value pair and its hash
   207  	key := []byte{0x13}
   208  	value := []byte{0x37}
   209  	vhash := tmhash.Sum(value)
   210  	bz := new(bytes.Buffer)
   211  	_ = encodeByteSlice(bz, key)
   212  	_ = encodeByteSlice(bz, vhash)
   213  	kvhash := tmhash.Sum(append([]byte{0}, bz.Bytes()...))
   214  
   215  	// the malicious `op`
   216  	op := NewValueOp(
   217  		key,
   218  		&Proof{LeafHash: kvhash},
   219  	)
   220  
   221  	// the nil root
   222  	var root []byte
   223  
   224  	assert.NotNil(t, ProofOperators{op}.Verify(root, "/"+string(key), [][]byte{value}))
   225  }