diff --git a/go.mod b/go.mod index 4eb58ef1..717511f1 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 + github.com/davecgh/go-spew v1.1.1 golang.org/x/sync v0.1.0 ) diff --git a/go.sum b/go.sum index 91a7301b..04aa9135 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,7 @@ github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5U github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= diff --git a/proof_test.go b/proof_test.go index 4a2bf454..9dc97c3a 100644 --- a/proof_test.go +++ b/proof_test.go @@ -36,6 +36,19 @@ import ( "github.com/crate-crypto/go-ipa/common" ) +func TestProofEmptyTree(t *testing.T) { + t.Parallel() + + root := New() + root.Commit() + + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, nil) + cfg := GetConfig() + if ok, err := VerifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { + t.Fatalf("could not verify verkle proof: %s", ToDot(root)) + } +} + func TestProofVerifyTwoLeaves(t *testing.T) { t.Parallel() diff --git a/tree_test.go b/tree_test.go index 099f0012..fb49f622 100644 --- a/tree_test.go +++ b/tree_test.go @@ -32,12 +32,17 @@ import ( "encoding/hex" "errors" "fmt" + "io" mRand "math/rand" + "reflect" "runtime" "sort" "strings" "testing" + "testing/quick" "time" + + "github.com/davecgh/go-spew/spew" ) // a 32 byte value, as expected in the tree structure @@ -1649,3 +1654,154 @@ func TestLeafNodeInsert(t *testing.T) { t.Fatalf("hash should not be nil") } } + +type randTest []randTestStep + +type randTestStep struct { + op int + key []byte // for opInsert, opDelete, opGet + value []byte // for opInsert + err error // for debugging +} + +const ( + opInsert = iota + opDelete + opGet + opHash + opCommit + opProve + numOps +) + +// Generate implements the quick.Generator interface from testing/quick +// to generate random test cases. +func (randTest) Generate(r *mRand.Rand, size int) reflect.Value { + var finishedFn = func() bool { + if size == 0 { + return true + } + size-- + return false + } + return reflect.ValueOf(generateSteps(finishedFn, r)) +} + +func generateSteps(finished func() bool, r io.Reader) randTest { + var allKeys [][]byte + var tmp = []byte{0} + genKey := func() []byte { + _, err := r.Read(tmp) + if err != nil { + panic(err) + } + + // first 2 operations always create a new key, then 10% of the time + // we create a new key or return an existing key otherwise. + if len(allKeys) < 2 || tmp[0]%100 > 90 { + // new key + key := make([]byte, 32) + _, err := r.Read(key) + if err != nil { + panic(err) + } + + allKeys = append(allKeys, key) + return key + } + // use existing key + idx := int(tmp[0]) % len(allKeys) + return allKeys[idx] + } + var steps randTest + for !finished() { + _, err := r.Read(tmp) + if err != nil { + panic(err) + } + + step := randTestStep{op: int(tmp[0]) % numOps} + switch step.op { + case opInsert: + step.key = genKey() + step.value = make([]byte, 32) + _, err := r.Read(step.value) + if err != nil { + panic(err) + } + case opGet, opDelete, opProve: + step.key = genKey() + } + steps = append(steps, step) + } + return steps +} + +// runRandTestBool coerces error to boolean, for use in quick.Check +func runRandTestBool(rt randTest) bool { + return runRandTest(rt) == nil +} + +func runRandTest(rt randTest) error { + var ( + root = New() + keys = [][]byte{} + values = make(map[string]string) + cfg = GetConfig() + ) + for i, step := range rt { + switch step.op { + case opInsert: + if err := root.Insert(step.key, step.value, nil); err != nil { + rt[i].err = err + } + keys = append(keys, step.key) + values[string(step.key)] = string(step.value) + case opDelete: + if _, err := root.Delete(step.key, nil); err != nil { + rt[i].err = err + } + delete(values, string(step.key)) + case opGet: + v, err := root.Get(step.key, nil) + want := values[string(step.key)] + if string(v) != want { + rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x, err %v", step.key, v, want, err) + } + case opProve: + if len(keys) == 0 { + continue + } + root.Commit() + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, keys, nil) + if ok, err := VerifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { + rt[i].err = fmt.Errorf("could not verify verkle proof: %s, err %v", ToDot(root), err) + } + // TODO: reconsider if we should avoid returning pointers in Hash() and Commit() + case opHash: + if hash := root.Hash(); hash == nil { + rt[i].err = fmt.Errorf("hash is nil") + } + case opCommit: + if comm := root.Commit(); comm == nil { + rt[i].err = fmt.Errorf("commit is nil") + } + } + // Abort the test on error. + if rt[i].err != nil { + return rt[i].err + } + } + return nil +} + +func TestRandom(t *testing.T) { + t.Parallel() + + if err := quick.Check(runRandTestBool, nil); err != nil { + if cerr, ok := err.(*quick.CheckError); ok { + t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In)) + } + t.Fatal(err) + } +}