github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/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/celestia-core/crypto/tmhash"
    13  	cmtcrypto "github.com/badrootd/celestia-core/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 TestProofOperatorsFromKeys(t *testing.T) {
   139  	var err error
   140  
   141  	// ProofRuntime setup
   142  	// TODO test this somehow.
   143  
   144  	// ProofOperators setup
   145  	op1 := NewDominoOp("KEY1", "INPUT1", "INPUT2")
   146  	op2 := NewDominoOp("KEY%2", "INPUT2", "INPUT3")
   147  	op3 := NewDominoOp("", "INPUT3", "INPUT4")
   148  	op4 := NewDominoOp("KEY/4", "INPUT4", "OUTPUT4")
   149  
   150  	// add characters to the keys that would otherwise result in bad keypath if
   151  	// processed
   152  	keys1 := [][]byte{bz("KEY/4"), bz("KEY%2"), bz("KEY1")}
   153  	badkeys1 := [][]byte{bz("WrongKey"), bz("KEY%2"), bz("KEY1")}
   154  	keys2 := [][]byte{bz("KEY3"), bz("KEY%2"), bz("KEY1")}
   155  	keys3 := [][]byte{bz("KEY2"), bz("KEY1")}
   156  
   157  	// Good
   158  	popz := ProofOperators([]ProofOperator{op1, op2, op3, op4})
   159  	err = popz.VerifyFromKeys(bz("OUTPUT4"), keys1, [][]byte{bz("INPUT1")})
   160  	assert.NoError(t, err)
   161  
   162  	// BAD INPUT
   163  	err = popz.VerifyFromKeys(bz("OUTPUT4"), keys1, [][]byte{bz("INPUT1_WRONG")})
   164  	assert.Error(t, err)
   165  
   166  	// BAD KEY 1
   167  	err = popz.VerifyFromKeys(bz("OUTPUT4"), keys2, [][]byte{bz("INPUT1")})
   168  	assert.Error(t, err)
   169  
   170  	// BAD KEY 2
   171  	err = popz.VerifyFromKeys(bz("OUTPUT4"), badkeys1, [][]byte{bz("INPUT1")})
   172  	assert.Error(t, err)
   173  
   174  	// BAD KEY 5
   175  	err = popz.VerifyFromKeys(bz("OUTPUT4"), keys3, [][]byte{bz("INPUT1")})
   176  	assert.Error(t, err)
   177  
   178  	// BAD OUTPUT 1
   179  	err = popz.VerifyFromKeys(bz("OUTPUT4_WRONG"), keys1, [][]byte{bz("INPUT1")})
   180  	assert.Error(t, err)
   181  
   182  	// BAD OUTPUT 2
   183  	err = popz.VerifyFromKeys(bz(""), keys1, [][]byte{bz("INPUT1")})
   184  	assert.Error(t, err)
   185  
   186  	// BAD POPZ 1
   187  	popz = []ProofOperator{op1, op2, op4}
   188  	err = popz.VerifyFromKeys(bz("OUTPUT4"), keys1, [][]byte{bz("INPUT1")})
   189  	assert.Error(t, err)
   190  
   191  	// BAD POPZ 2
   192  	popz = []ProofOperator{op4, op3, op2, op1}
   193  	err = popz.VerifyFromKeys(bz("OUTPUT4"), keys1, [][]byte{bz("INPUT1")})
   194  	assert.Error(t, err)
   195  
   196  	// BAD POPZ 3
   197  	popz = []ProofOperator{}
   198  	err = popz.VerifyFromKeys(bz("OUTPUT4"), keys1, [][]byte{bz("INPUT1")})
   199  	assert.Error(t, err)
   200  }
   201  
   202  func bz(s string) []byte {
   203  	return []byte(s)
   204  }
   205  
   206  func TestProofValidateBasic(t *testing.T) {
   207  	testCases := []struct {
   208  		testName      string
   209  		malleateProof func(*Proof)
   210  		errStr        string
   211  	}{
   212  		{"Good", func(sp *Proof) {}, ""},
   213  		{"Negative Total", func(sp *Proof) { sp.Total = -1 }, "negative Total"},
   214  		{"Negative Index", func(sp *Proof) { sp.Index = -1 }, "negative Index"},
   215  		{"Invalid LeafHash", func(sp *Proof) { sp.LeafHash = make([]byte, 10) },
   216  			"expected LeafHash size to be 32, got 10"},
   217  		{"Too many Aunts", func(sp *Proof) { sp.Aunts = make([][]byte, MaxAunts+1) },
   218  			"expected no more than 100 aunts, got 101"},
   219  		{"Invalid Aunt", func(sp *Proof) { sp.Aunts[0] = make([]byte, 10) },
   220  			"expected Aunts#0 size to be 32, got 10"},
   221  	}
   222  
   223  	for _, tc := range testCases {
   224  		tc := tc
   225  		t.Run(tc.testName, func(t *testing.T) {
   226  			_, proofs := ProofsFromByteSlices([][]byte{
   227  				[]byte("apple"),
   228  				[]byte("watermelon"),
   229  				[]byte("kiwi"),
   230  			})
   231  			tc.malleateProof(proofs[0])
   232  			err := proofs[0].ValidateBasic()
   233  			if tc.errStr != "" {
   234  				assert.Contains(t, err.Error(), tc.errStr)
   235  			}
   236  		})
   237  	}
   238  }
   239  func TestVoteProtobuf(t *testing.T) {
   240  
   241  	_, proofs := ProofsFromByteSlices([][]byte{
   242  		[]byte("apple"),
   243  		[]byte("watermelon"),
   244  		[]byte("kiwi"),
   245  	})
   246  	testCases := []struct {
   247  		testName string
   248  		v1       *Proof
   249  		expPass  bool
   250  	}{
   251  		{"empty proof", &Proof{}, false},
   252  		{"failure nil", nil, false},
   253  		{"success", proofs[0], true},
   254  	}
   255  	for _, tc := range testCases {
   256  		pb := tc.v1.ToProto()
   257  
   258  		v, err := ProofFromProto(pb)
   259  		if tc.expPass {
   260  			require.NoError(t, err)
   261  			require.Equal(t, tc.v1, v, tc.testName)
   262  		} else {
   263  			require.Error(t, err)
   264  		}
   265  	}
   266  }
   267  
   268  // TestVsa2022_100 verifies https://blog.verichains.io/p/vsa-2022-100-tendermint-forging-membership-proof
   269  func TestVsa2022_100(t *testing.T) {
   270  	// a fake key-value pair and its hash
   271  	key := []byte{0x13}
   272  	value := []byte{0x37}
   273  	vhash := tmhash.Sum(value)
   274  	bz := new(bytes.Buffer)
   275  	_ = encodeByteSlice(bz, key)
   276  	_ = encodeByteSlice(bz, vhash)
   277  	kvhash := tmhash.Sum(append([]byte{0}, bz.Bytes()...))
   278  
   279  	// the malicious `op`
   280  	op := NewValueOp(
   281  		key,
   282  		&Proof{LeafHash: kvhash},
   283  	)
   284  
   285  	// the nil root
   286  	var root []byte
   287  
   288  	assert.NotNil(t, ProofOperators{op}.Verify(root, "/"+string(key), [][]byte{value}))
   289  }