diff --git a/crypto/merkle/hash.go b/crypto/merkle/hash.go deleted file mode 100644 index e5f7318..0000000 --- a/crypto/merkle/hash.go +++ /dev/null @@ -1,26 +0,0 @@ -package merkle - -import ( - "github.com/cometbft/cometbft/crypto/tmhash" -) - -// TODO: make these have a large predefined capacity -var ( - leafPrefix = []byte{0} - innerPrefix = []byte{1} -) - -// returns tmhash() -func emptyHash() []byte { - return tmhash.Sum([]byte{}) -} - -// returns tmhash(0x00 || leaf) -func leafHash(leaf []byte) []byte { - return tmhash.Sum(append(leafPrefix, leaf...)) -} - -// returns tmhash(0x01 || left || right) -func innerHash(left []byte, right []byte) []byte { - return tmhash.Sum(append(innerPrefix, append(left, right...)...)) -} diff --git a/go.work b/go.work new file mode 100644 index 0000000..b9dc24f --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.21.5 + +use ( + . + ./merkle +) \ No newline at end of file diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..c5b581a --- /dev/null +++ b/go.work.sum @@ -0,0 +1,3 @@ +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= diff --git a/crypto/merkle/README.md b/merkle/README.md similarity index 67% rename from crypto/merkle/README.md rename to merkle/README.md index 16b1abb..00d6124 100644 --- a/crypto/merkle/README.md +++ b/merkle/README.md @@ -1,4 +1,6 @@ # Merkle Tree -For smaller static data structures that don't require immutable snapshots or mutability; +For smaller static data structures that don't require immutable snapshots or mutability; for instance the transactions and validation signatures of a block can be hashed using this simple merkle tree logic. + +This is a forked copy of github.com/cometbft/cometbft/crypto/merkle diff --git a/crypto/merkle/doc.go b/merkle/doc.go similarity index 100% rename from crypto/merkle/doc.go rename to merkle/doc.go diff --git a/merkle/go.mod b/merkle/go.mod new file mode 100644 index 0000000..3acc43d --- /dev/null +++ b/merkle/go.mod @@ -0,0 +1,15 @@ +module github.com/celestiaorg/go-square/merkle + +go 1.21.5 + +require ( + github.com/stretchr/testify v1.8.4 + google.golang.org/protobuf v1.31.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/merkle/go.sum b/merkle/go.sum new file mode 100644 index 0000000..0d44549 --- /dev/null +++ b/merkle/go.sum @@ -0,0 +1,18 @@ +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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/merkle/hash.go b/merkle/hash.go new file mode 100644 index 0000000..973629e --- /dev/null +++ b/merkle/hash.go @@ -0,0 +1,31 @@ +package merkle + +import ( + "crypto/sha256" +) + +// TODO: make these have a large predefined capacity +var ( + leafPrefix = []byte{0} + innerPrefix = []byte{1} +) + +// returns empty sha256 hash +func emptyHash() []byte { + return hash([]byte{}) +} + +// returns sha256(0x00 || leaf) +func leafHash(leaf []byte) []byte { + return hash(append(leafPrefix, leaf...)) +} + +// returns sha256(0x01 || left || right) +func innerHash(left []byte, right []byte) []byte { + return hash(append(innerPrefix, append(left, right...)...)) +} + +func hash(bz []byte) []byte { + h := sha256.Sum256(bz) + return h[:] +} diff --git a/crypto/merkle/proof.go b/merkle/proof.go similarity index 94% rename from crypto/merkle/proof.go rename to merkle/proof.go index 08dc2c2..2491b47 100644 --- a/crypto/merkle/proof.go +++ b/merkle/proof.go @@ -2,11 +2,11 @@ package merkle import ( "bytes" + "crypto/sha256" "errors" "fmt" - "github.com/cometbft/cometbft/crypto/tmhash" - cmtcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + wire "github.com/celestiaorg/go-square/merkle/proto/gen/merkle/v1" ) const ( @@ -117,25 +117,25 @@ func (sp *Proof) ValidateBasic() error { if sp.Index < 0 { return errors.New("negative Index") } - if len(sp.LeafHash) != tmhash.Size { - return fmt.Errorf("expected LeafHash size to be %d, got %d", tmhash.Size, len(sp.LeafHash)) + if len(sp.LeafHash) != sha256.Size { + return fmt.Errorf("expected LeafHash size to be %d, got %d", sha256.Size, len(sp.LeafHash)) } if len(sp.Aunts) > MaxAunts { return fmt.Errorf("expected no more than %d aunts, got %d", MaxAunts, len(sp.Aunts)) } for i, auntHash := range sp.Aunts { - if len(auntHash) != tmhash.Size { - return fmt.Errorf("expected Aunts#%d size to be %d, got %d", i, tmhash.Size, len(auntHash)) + if len(auntHash) != sha256.Size { + return fmt.Errorf("expected Aunts#%d size to be %d, got %d", i, sha256.Size, len(auntHash)) } } return nil } -func (sp *Proof) ToProto() *cmtcrypto.Proof { +func (sp *Proof) ToProto() *wire.Proof { if sp == nil { return nil } - pb := new(cmtcrypto.Proof) + pb := new(wire.Proof) pb.Total = sp.Total pb.Index = sp.Index @@ -145,7 +145,7 @@ func (sp *Proof) ToProto() *cmtcrypto.Proof { return pb } -func ProofFromProto(pb *cmtcrypto.Proof) (*Proof, error) { +func ProofFromProto(pb *wire.Proof) (*Proof, error) { if pb == nil { return nil, errors.New("nil proof") } diff --git a/crypto/merkle/proof_key_path.go b/merkle/proof_key_path.go similarity index 100% rename from crypto/merkle/proof_key_path.go rename to merkle/proof_key_path.go diff --git a/crypto/merkle/proof_key_path_test.go b/merkle/proof_key_path_test.go similarity index 100% rename from crypto/merkle/proof_key_path_test.go rename to merkle/proof_key_path_test.go diff --git a/crypto/merkle/proof_op.go b/merkle/proof_op.go similarity index 84% rename from crypto/merkle/proof_op.go rename to merkle/proof_op.go index 36fdc06..122b600 100644 --- a/crypto/merkle/proof_op.go +++ b/merkle/proof_op.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - cmtcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + wire "github.com/celestiaorg/go-square/merkle/proto/gen/merkle/v1" ) //---------------------------------------- @@ -21,7 +21,7 @@ import ( type ProofOperator interface { Run([][]byte) ([][]byte, error) GetKey() []byte - ProofOp() cmtcrypto.ProofOp + ProofOp() wire.ProofOp } //---------------------------------------- @@ -101,7 +101,7 @@ func (poz ProofOperators) VerifyFromKeys(root []byte, keys [][]byte, args [][]by //---------------------------------------- // ProofRuntime - main entrypoint -type OpDecoder func(cmtcrypto.ProofOp) (ProofOperator, error) +type OpDecoder func(*wire.ProofOp) (ProofOperator, error) type ProofRuntime struct { decoders map[string]OpDecoder @@ -121,15 +121,18 @@ func (prt *ProofRuntime) RegisterOpDecoder(typ string, dec OpDecoder) { prt.decoders[typ] = dec } -func (prt *ProofRuntime) Decode(pop cmtcrypto.ProofOp) (ProofOperator, error) { +func (prt *ProofRuntime) Decode(pop *wire.ProofOp) (ProofOperator, error) { decoder := prt.decoders[pop.Type] if decoder == nil { return nil, fmt.Errorf("unrecognized proof type %v", pop.Type) } + if pop == nil { + return nil, errors.New("nil ProofOp") + } return decoder(pop) } -func (prt *ProofRuntime) DecodeProof(proof *cmtcrypto.ProofOps) (ProofOperators, error) { +func (prt *ProofRuntime) DecodeProof(proof *wire.ProofOps) (ProofOperators, error) { poz := make(ProofOperators, 0, len(proof.Ops)) for _, pop := range proof.Ops { operator, err := prt.Decode(pop) @@ -141,21 +144,21 @@ func (prt *ProofRuntime) DecodeProof(proof *cmtcrypto.ProofOps) (ProofOperators, return poz, nil } -func (prt *ProofRuntime) VerifyValue(proof *cmtcrypto.ProofOps, root []byte, keypath string, value []byte) (err error) { +func (prt *ProofRuntime) VerifyValue(proof *wire.ProofOps, root []byte, keypath string, value []byte) (err error) { return prt.Verify(proof, root, keypath, [][]byte{value}) } -func (prt *ProofRuntime) VerifyValueFromKeys(proof *cmtcrypto.ProofOps, root []byte, keys [][]byte, value []byte) (err error) { +func (prt *ProofRuntime) VerifyValueFromKeys(proof *wire.ProofOps, root []byte, keys [][]byte, value []byte) (err error) { return prt.VerifyFromKeys(proof, root, keys, [][]byte{value}) } // TODO In the long run we'll need a method of classification of ops, // whether existence or absence or perhaps a third? -func (prt *ProofRuntime) VerifyAbsence(proof *cmtcrypto.ProofOps, root []byte, keypath string) (err error) { +func (prt *ProofRuntime) VerifyAbsence(proof *wire.ProofOps, root []byte, keypath string) (err error) { return prt.Verify(proof, root, keypath, nil) } -func (prt *ProofRuntime) Verify(proof *cmtcrypto.ProofOps, root []byte, keypath string, args [][]byte) (err error) { +func (prt *ProofRuntime) Verify(proof *wire.ProofOps, root []byte, keypath string, args [][]byte) (err error) { poz, err := prt.DecodeProof(proof) if err != nil { return fmt.Errorf("decoding proof: %w", err) @@ -166,7 +169,7 @@ func (prt *ProofRuntime) Verify(proof *cmtcrypto.ProofOps, root []byte, keypath // VerifyFromKeys performs the same verification logic as the normal Verify // method, except it does not perform any processing on the keypath. This is // useful when using keys that have split or escape points as a part of the key. -func (prt *ProofRuntime) VerifyFromKeys(proof *cmtcrypto.ProofOps, root []byte, keys [][]byte, args [][]byte) (err error) { +func (prt *ProofRuntime) VerifyFromKeys(proof *wire.ProofOps, root []byte, keys [][]byte, args [][]byte) (err error) { poz, err := prt.DecodeProof(proof) if err != nil { return fmt.Errorf("decoding proof: %w", err) diff --git a/crypto/merkle/proof_test.go b/merkle/proof_test.go similarity index 95% rename from crypto/merkle/proof_test.go rename to merkle/proof_test.go index 07af062..abaea7d 100644 --- a/crypto/merkle/proof_test.go +++ b/merkle/proof_test.go @@ -6,11 +6,10 @@ import ( "fmt" "testing" + wire "github.com/celestiaorg/go-square/merkle/proto/gen/merkle/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/crypto/tmhash" - cmtcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + "google.golang.org/protobuf/proto" ) const ProofOpDomino = "test:domino" @@ -31,18 +30,18 @@ func NewDominoOp(key, input, output string) DominoOp { } } -func (dop DominoOp) ProofOp() cmtcrypto.ProofOp { - dopb := cmtcrypto.DominoOp{ +func (dop DominoOp) ProofOp() wire.ProofOp { + dopb := &wire.DominoOp{ Key: dop.key, Input: dop.Input, Output: dop.Output, } - bz, err := dopb.Marshal() + bz, err := proto.Marshal(dopb) if err != nil { panic(err) } - return cmtcrypto.ProofOp{ + return wire.ProofOp{ Type: ProofOpDomino, Key: []byte(dop.key), Data: bz, @@ -270,11 +269,11 @@ func TestVsa2022_100(t *testing.T) { // a fake key-value pair and its hash key := []byte{0x13} value := []byte{0x37} - vhash := tmhash.Sum(value) + vhash := hash(value) bz := new(bytes.Buffer) _ = encodeByteSlice(bz, key) _ = encodeByteSlice(bz, vhash) - kvhash := tmhash.Sum(append([]byte{0}, bz.Bytes()...)) + kvhash := hash(append([]byte{0}, bz.Bytes()...)) // the malicious `op` op := NewValueOp( diff --git a/crypto/merkle/proof_value.go b/merkle/proof_value.go similarity index 82% rename from crypto/merkle/proof_value.go rename to merkle/proof_value.go index 5cc188c..5ef8933 100644 --- a/crypto/merkle/proof_value.go +++ b/merkle/proof_value.go @@ -2,10 +2,11 @@ package merkle import ( "bytes" + "crypto/sha256" "fmt" - "github.com/cometbft/cometbft/crypto/tmhash" - cmtcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + wire "github.com/celestiaorg/go-square/merkle/proto/gen/merkle/v1" + "google.golang.org/protobuf/proto" ) const ProofOpValue = "simple:v" @@ -37,12 +38,12 @@ func NewValueOp(key []byte, proof *Proof) ValueOp { } } -func ValueOpDecoder(pop cmtcrypto.ProofOp) (ProofOperator, error) { +func ValueOpDecoder(pop *wire.ProofOp) (ProofOperator, error) { if pop.Type != ProofOpValue { return nil, fmt.Errorf("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpValue) } - var pbop cmtcrypto.ValueOp // a bit strange as we'll discard this, but it works. - err := pbop.Unmarshal(pop.Data) + var pbop wire.ValueOp // a bit strange as we'll discard this, but it works. + err := proto.Unmarshal(pop.Data, &pbop) if err != nil { return nil, fmt.Errorf("decoding ProofOp.Data into ValueOp: %w", err) } @@ -54,16 +55,16 @@ func ValueOpDecoder(pop cmtcrypto.ProofOp) (ProofOperator, error) { return NewValueOp(pop.Key, sp), nil } -func (op ValueOp) ProofOp() cmtcrypto.ProofOp { - pbval := cmtcrypto.ValueOp{ +func (op ValueOp) ProofOp() wire.ProofOp { + pbval := &wire.ValueOp{ Key: op.key, Proof: op.Proof.ToProto(), } - bz, err := pbval.Marshal() + bz, err := proto.Marshal(pbval) if err != nil { panic(err) } - return cmtcrypto.ProofOp{ + return wire.ProofOp{ Type: ProofOpValue, Key: op.key, Data: bz, @@ -79,7 +80,7 @@ func (op ValueOp) Run(args [][]byte) ([][]byte, error) { return nil, fmt.Errorf("expected 1 arg, got %v", len(args)) } value := args[0] - hasher := tmhash.New() + hasher := sha256.New() hasher.Write(value) vhash := hasher.Sum(nil) diff --git a/merkle/proto/buf.gen.yaml b/merkle/proto/buf.gen.yaml new file mode 100644 index 0000000..d887b47 --- /dev/null +++ b/merkle/proto/buf.gen.yaml @@ -0,0 +1,6 @@ +version: v1 +plugins: + - plugin: buf.build/protocolbuffers/go + out: gen + opt: + - paths=source_relative \ No newline at end of file diff --git a/merkle/proto/gen/merkle/v1/proof.pb.go b/merkle/proto/gen/merkle/v1/proof.pb.go new file mode 100644 index 0000000..f26aa80 --- /dev/null +++ b/merkle/proto/gen/merkle/v1/proof.pb.go @@ -0,0 +1,478 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: merkle/v1/proof.proto + +package merkle + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Proof struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + Index int64 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` + LeafHash []byte `protobuf:"bytes,3,opt,name=leaf_hash,json=leafHash,proto3" json:"leaf_hash,omitempty"` + Aunts [][]byte `protobuf:"bytes,4,rep,name=aunts,proto3" json:"aunts,omitempty"` +} + +func (x *Proof) Reset() { + *x = Proof{} + if protoimpl.UnsafeEnabled { + mi := &file_merkle_v1_proof_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Proof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Proof) ProtoMessage() {} + +func (x *Proof) ProtoReflect() protoreflect.Message { + mi := &file_merkle_v1_proof_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Proof.ProtoReflect.Descriptor instead. +func (*Proof) Descriptor() ([]byte, []int) { + return file_merkle_v1_proof_proto_rawDescGZIP(), []int{0} +} + +func (x *Proof) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *Proof) GetIndex() int64 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *Proof) GetLeafHash() []byte { + if x != nil { + return x.LeafHash + } + return nil +} + +func (x *Proof) GetAunts() [][]byte { + if x != nil { + return x.Aunts + } + return nil +} + +type ValueOp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Encoded in ProofOp.Key. + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // To encode in ProofOp.Data + Proof *Proof `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` +} + +func (x *ValueOp) Reset() { + *x = ValueOp{} + if protoimpl.UnsafeEnabled { + mi := &file_merkle_v1_proof_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ValueOp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValueOp) ProtoMessage() {} + +func (x *ValueOp) ProtoReflect() protoreflect.Message { + mi := &file_merkle_v1_proof_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValueOp.ProtoReflect.Descriptor instead. +func (*ValueOp) Descriptor() ([]byte, []int) { + return file_merkle_v1_proof_proto_rawDescGZIP(), []int{1} +} + +func (x *ValueOp) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *ValueOp) GetProof() *Proof { + if x != nil { + return x.Proof + } + return nil +} + +type DominoOp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Input string `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` + Output string `protobuf:"bytes,3,opt,name=output,proto3" json:"output,omitempty"` +} + +func (x *DominoOp) Reset() { + *x = DominoOp{} + if protoimpl.UnsafeEnabled { + mi := &file_merkle_v1_proof_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DominoOp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DominoOp) ProtoMessage() {} + +func (x *DominoOp) ProtoReflect() protoreflect.Message { + mi := &file_merkle_v1_proof_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DominoOp.ProtoReflect.Descriptor instead. +func (*DominoOp) Descriptor() ([]byte, []int) { + return file_merkle_v1_proof_proto_rawDescGZIP(), []int{2} +} + +func (x *DominoOp) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *DominoOp) GetInput() string { + if x != nil { + return x.Input + } + return "" +} + +func (x *DominoOp) GetOutput() string { + if x != nil { + return x.Output + } + return "" +} + +// ProofOp defines an operation used for calculating Merkle root +// The data could be arbitrary format, providing necessary data +// for example neighbouring node hash +type ProofOp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *ProofOp) Reset() { + *x = ProofOp{} + if protoimpl.UnsafeEnabled { + mi := &file_merkle_v1_proof_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProofOp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProofOp) ProtoMessage() {} + +func (x *ProofOp) ProtoReflect() protoreflect.Message { + mi := &file_merkle_v1_proof_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProofOp.ProtoReflect.Descriptor instead. +func (*ProofOp) Descriptor() ([]byte, []int) { + return file_merkle_v1_proof_proto_rawDescGZIP(), []int{3} +} + +func (x *ProofOp) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ProofOp) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *ProofOp) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +// ProofOps is Merkle proof defined by the list of ProofOps +type ProofOps struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ops []*ProofOp `protobuf:"bytes,1,rep,name=ops,proto3" json:"ops,omitempty"` +} + +func (x *ProofOps) Reset() { + *x = ProofOps{} + if protoimpl.UnsafeEnabled { + mi := &file_merkle_v1_proof_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProofOps) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProofOps) ProtoMessage() {} + +func (x *ProofOps) ProtoReflect() protoreflect.Message { + mi := &file_merkle_v1_proof_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProofOps.ProtoReflect.Descriptor instead. +func (*ProofOps) Descriptor() ([]byte, []int) { + return file_merkle_v1_proof_proto_rawDescGZIP(), []int{4} +} + +func (x *ProofOps) GetOps() []*ProofOp { + if x != nil { + return x.Ops + } + return nil +} + +var File_merkle_v1_proof_proto protoreflect.FileDescriptor + +var file_merkle_v1_proof_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6f, + 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x2e, + 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x22, 0x66, 0x0a, 0x05, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x6c, + 0x65, 0x61, 0x66, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, + 0x6c, 0x65, 0x61, 0x66, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x75, 0x6e, 0x74, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x61, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x47, + 0x0a, 0x07, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x70, + 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x71, 0x75, + 0x61, 0x72, 0x65, 0x2e, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x4a, 0x0a, 0x08, 0x44, 0x6f, 0x6d, 0x69, 0x6e, + 0x6f, 0x4f, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x22, 0x43, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x4f, 0x70, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x34, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x4f, 0x70, 0x73, 0x12, 0x28, 0x0a, 0x03, 0x6f, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x2e, 0x6d, 0x65, 0x72, 0x6b, 0x6c, + 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, 0x42, 0x29, + 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x65, 0x6c, + 0x65, 0x73, 0x74, 0x69, 0x61, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x6f, 0x2d, 0x73, 0x71, 0x75, 0x61, + 0x72, 0x65, 0x2f, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_merkle_v1_proof_proto_rawDescOnce sync.Once + file_merkle_v1_proof_proto_rawDescData = file_merkle_v1_proof_proto_rawDesc +) + +func file_merkle_v1_proof_proto_rawDescGZIP() []byte { + file_merkle_v1_proof_proto_rawDescOnce.Do(func() { + file_merkle_v1_proof_proto_rawDescData = protoimpl.X.CompressGZIP(file_merkle_v1_proof_proto_rawDescData) + }) + return file_merkle_v1_proof_proto_rawDescData +} + +var file_merkle_v1_proof_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_merkle_v1_proof_proto_goTypes = []interface{}{ + (*Proof)(nil), // 0: square.merkle.Proof + (*ValueOp)(nil), // 1: square.merkle.ValueOp + (*DominoOp)(nil), // 2: square.merkle.DominoOp + (*ProofOp)(nil), // 3: square.merkle.ProofOp + (*ProofOps)(nil), // 4: square.merkle.ProofOps +} +var file_merkle_v1_proof_proto_depIdxs = []int32{ + 0, // 0: square.merkle.ValueOp.proof:type_name -> square.merkle.Proof + 3, // 1: square.merkle.ProofOps.ops:type_name -> square.merkle.ProofOp + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_merkle_v1_proof_proto_init() } +func file_merkle_v1_proof_proto_init() { + if File_merkle_v1_proof_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_merkle_v1_proof_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Proof); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_merkle_v1_proof_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ValueOp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_merkle_v1_proof_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DominoOp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_merkle_v1_proof_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProofOp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_merkle_v1_proof_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProofOps); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_merkle_v1_proof_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_merkle_v1_proof_proto_goTypes, + DependencyIndexes: file_merkle_v1_proof_proto_depIdxs, + MessageInfos: file_merkle_v1_proof_proto_msgTypes, + }.Build() + File_merkle_v1_proof_proto = out.File + file_merkle_v1_proof_proto_rawDesc = nil + file_merkle_v1_proof_proto_goTypes = nil + file_merkle_v1_proof_proto_depIdxs = nil +} diff --git a/merkle/proto/merkle/v1/proof.proto b/merkle/proto/merkle/v1/proof.proto new file mode 100644 index 0000000..47fe221 --- /dev/null +++ b/merkle/proto/merkle/v1/proof.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; +package square.merkle; + +option go_package = "github.com/celestiaorg/go-square/merkle"; + +message Proof { + int64 total = 1; + int64 index = 2; + bytes leaf_hash = 3; + repeated bytes aunts = 4; +} + +message ValueOp { + // Encoded in ProofOp.Key. + bytes key = 1; + + // To encode in ProofOp.Data + Proof proof = 2; +} + +message DominoOp { + string key = 1; + string input = 2; + string output = 3; +} + +// ProofOp defines an operation used for calculating Merkle root +// The data could be arbitrary format, providing necessary data +// for example neighbouring node hash +message ProofOp { + string type = 1; + bytes key = 2; + bytes data = 3; +} + +// ProofOps is Merkle proof defined by the list of ProofOps +message ProofOps { + repeated ProofOp ops = 1; +} diff --git a/crypto/merkle/rfc6962_test.go b/merkle/rfc6962_test.go similarity index 95% rename from crypto/merkle/rfc6962_test.go rename to merkle/rfc6962_test.go index dd0c817..ae4b54a 100644 --- a/crypto/merkle/rfc6962_test.go +++ b/merkle/rfc6962_test.go @@ -17,10 +17,9 @@ package merkle // and consequently fall under the above license. import ( "bytes" + "crypto/sha256" "encoding/hex" "testing" - - "github.com/cometbft/cometbft/crypto/tmhash" ) func TestRFC6962Hasher(t *testing.T) { @@ -39,7 +38,7 @@ func TestRFC6962Hasher(t *testing.T) { // echo -n '' | sha256sum { desc: "RFC6962 Empty Tree", - want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"[:tmhash.Size*2], + want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"[:sha256.Size*2], got: emptyTreeHash, }, @@ -47,19 +46,19 @@ func TestRFC6962Hasher(t *testing.T) { // echo -n 00 | xxd -r -p | sha256sum { desc: "RFC6962 Empty Leaf", - want: "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"[:tmhash.Size*2], + want: "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"[:sha256.Size*2], got: emptyLeafHash, }, // echo -n 004C313233343536 | xxd -r -p | sha256sum { desc: "RFC6962 Leaf", - want: "395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56"[:tmhash.Size*2], + want: "395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56"[:sha256.Size*2], got: leafHash, }, // echo -n 014E3132334E343536 | xxd -r -p | sha256sum { desc: "RFC6962 Node", - want: "aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb"[:tmhash.Size*2], + want: "aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb"[:sha256.Size*2], got: innerHash([]byte("N123"), []byte("N456")), }, } { diff --git a/crypto/merkle/tree.go b/merkle/tree.go similarity index 100% rename from crypto/merkle/tree.go rename to merkle/tree.go diff --git a/crypto/merkle/tree_test.go b/merkle/tree_test.go similarity index 79% rename from crypto/merkle/tree_test.go rename to merkle/tree_test.go index 4b90186..78c1783 100644 --- a/crypto/merkle/tree_test.go +++ b/merkle/tree_test.go @@ -1,16 +1,14 @@ package merkle import ( + crand "crypto/rand" + "crypto/sha256" "encoding/hex" + "math/rand" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - cmtrand "github.com/cometbft/cometbft/libs/rand" - . "github.com/cometbft/cometbft/libs/test" - - "github.com/cometbft/cometbft/crypto/tmhash" ) type testItem []byte @@ -54,7 +52,7 @@ func TestProof(t *testing.T) { items := make([][]byte, total) for i := 0; i < total; i++ { - items[i] = testItem(cmtrand.Bytes(tmhash.Size)) + items[i] = testItem(randBytes(sha256.Size)) } rootHash = HashFromByteSlices(items) @@ -78,7 +76,7 @@ func TestProof(t *testing.T) { // Trail too long should make it fail origAunts := proof.Aunts - proof.Aunts = append(proof.Aunts, cmtrand.Bytes(32)) + proof.Aunts = append(proof.Aunts, randBytes(32)) err = proof.Verify(rootHash, item) require.Error(t, err, "Expected verification to fail for wrong trail length") @@ -92,11 +90,11 @@ func TestProof(t *testing.T) { proof.Aunts = origAunts // Mutating the itemHash should make it fail. - err = proof.Verify(rootHash, MutateByteSlice(item)) + err = proof.Verify(rootHash, mutateByteSlice(item)) require.Error(t, err, "Expected verification to fail for mutated leaf hash") // Mutating the rootHash should make it fail. - err = proof.Verify(MutateByteSlice(rootHash), item) + err = proof.Verify(mutateByteSlice(rootHash), item) require.Error(t, err, "Expected verification to fail for mutated root hash") } } @@ -107,7 +105,7 @@ func TestHashAlternatives(t *testing.T) { items := make([][]byte, total) for i := 0; i < total; i++ { - items[i] = testItem(cmtrand.Bytes(tmhash.Size)) + items[i] = testItem(randBytes(sha256.Size)) } rootHash1 := HashFromByteSlicesIterative(items) @@ -120,7 +118,7 @@ func BenchmarkHashAlternatives(b *testing.B) { items := make([][]byte, total) for i := 0; i < total; i++ { - items[i] = testItem(cmtrand.Bytes(tmhash.Size)) + items[i] = testItem(randBytes(sha256.Size)) } b.ResetTimer() @@ -159,3 +157,32 @@ func Test_getSplitPoint(t *testing.T) { require.EqualValues(t, tt.want, got, "getSplitPoint(%d) = %v, want %v", tt.length, got, tt.want) } } + +func randBytes(size int) []byte { + b := make([]byte, size) + _, _ = crand.Read(b) + return b +} + +// Contract: !bytes.Equal(input, output) && len(input) >= len(output) +func mutateByteSlice(bytez []byte) []byte { + // If bytez is empty, panic + if len(bytez) == 0 { + panic("Cannot mutate an empty bytez") + } + + // Copy bytez + mBytez := make([]byte, len(bytez)) + copy(mBytez, bytez) + bytez = mBytez + + // Try a random mutation + switch rand.Int() % 2 { + case 0: // Mutate a single byte + bytez[rand.Int()%len(bytez)] += byte(rand.Int()%255 + 1) + case 1: // Remove an arbitrary byte + pos := rand.Int() % len(bytez) + bytez = append(bytez[:pos], bytez[pos+1:]...) + } + return bytez +} diff --git a/crypto/merkle/types.go b/merkle/types.go similarity index 100% rename from crypto/merkle/types.go rename to merkle/types.go