diff --git a/extensions/envelope/crypto.go b/extensions/envelope/crypto.go new file mode 100644 index 0000000..d485634 --- /dev/null +++ b/extensions/envelope/crypto.go @@ -0,0 +1,52 @@ +package envelope + +import ( + "crypto/ed25519" + "crypto/rand" + "crypto/sha256" + "fmt" +) + +type Crypto interface { + GenerateKey() (publicKey, privateKey []byte, err error) + Hash([]byte) []byte + Sign(hash, privateKey []byte) ([]byte, error) + Verify([]byte) bool + PublicKey(privateKey []byte) ([]byte, error) +} + +func NewEd25519() *Ed25519 { + return &Ed25519{} +} + +type Ed25519 struct{} + +func (ed *Ed25519) GenerateKey() (publicKey, privateKey []byte, err error) { + publicKey, privateKey, err = ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, err + } + return publicKey, privateKey, nil +} + +func (ed *Ed25519) Sign(hash, privateKey []byte) (signature []byte, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("sign: %v", r) + } + }() + return ed25519.Sign(privateKey, hash), nil +} + +func (ed *Ed25519) Hash(msg []byte) []byte { + h := sha256.Sum256(msg) + return h[:] +} + +func (ed *Ed25519) Verify([]byte) bool { + return false +} + +func (ed *Ed25519) PublicKey(privateKey []byte) ([]byte, error) { + return ed25519.PrivateKey(privateKey).Public().(ed25519.PublicKey), nil +} diff --git a/extensions/envelope/envelope.pb.go b/extensions/envelope/envelope.pb.go index b8551cd..837a5fc 100644 --- a/extensions/envelope/envelope.pb.go +++ b/extensions/envelope/envelope.pb.go @@ -35,9 +35,10 @@ type Envelope struct { HashFunc string `protobuf:"bytes,5,opt,name=hash_func,json=hashFunc,proto3" json:"hash_func,omitempty"` // function used for hashing Deadline *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=deadline,proto3" json:"deadline,omitempty"` // signature is not valid after deadline (EIP-2612) // channel + chaincode + method are used as domain separator to prevent replay attack from other domains (EIP-2612) - Channel string `protobuf:"bytes,7,opt,name=channel,proto3" json:"channel,omitempty"` - Chaincode string `protobuf:"bytes,8,opt,name=chaincode,proto3" json:"chaincode,omitempty"` - Method string `protobuf:"bytes,9,opt,name=method,proto3" json:"method,omitempty"` + Channel string `protobuf:"bytes,7,opt,name=channel,proto3" json:"channel,omitempty"` + Chaincode string `protobuf:"bytes,8,opt,name=chaincode,proto3" json:"chaincode,omitempty"` + Method string `protobuf:"bytes,9,opt,name=method,proto3" json:"method,omitempty"` + SignatureAlg string `protobuf:"bytes,10,opt,name=signature_alg,json=signatureAlg,proto3" json:"signature_alg,omitempty"` } func (x *Envelope) Reset() { @@ -135,6 +136,13 @@ func (x *Envelope) GetMethod() string { return "" } +func (x *Envelope) GetSignatureAlg() string { + if x != nil { + return x.SignatureAlg + } + return "" +} + var File_envelope_envelope_proto protoreflect.FileDescriptor var file_envelope_envelope_proto_rawDesc = []byte{ @@ -146,7 +154,7 @@ var file_envelope_envelope_proto_rawDesc = []byte{ 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0xa4, 0x02, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0xc9, 0x02, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, @@ -164,11 +172,13 @@ var file_envelope_envelope_proto_rawDesc = []byte{ 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x79, 0x70, 0x65, 0x72, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, - 0x2d, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x63, 0x63, 0x6b, 0x69, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, - 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x5f, 0x61, 0x6c, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x41, 0x6c, 0x67, 0x42, 0x37, 0x5a, 0x35, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x79, 0x70, 0x65, 0x72, 0x6c, + 0x65, 0x64, 0x67, 0x65, 0x72, 0x2d, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x63, 0x63, 0x6b, 0x69, 0x74, + 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x65, 0x6e, 0x76, 0x65, + 0x6c, 0x6f, 0x70, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/extensions/envelope/envelope.proto b/extensions/envelope/envelope.proto index c0a8cc3..382d6a2 100644 --- a/extensions/envelope/envelope.proto +++ b/extensions/envelope/envelope.proto @@ -18,4 +18,5 @@ message Envelope { string channel = 7; string chaincode = 8; string method = 9; + string signature_alg = 10; } \ No newline at end of file diff --git a/extensions/envelope/envelope_test.go b/extensions/envelope/envelope_test.go index 43d11f7..0368e1f 100644 --- a/extensions/envelope/envelope_test.go +++ b/extensions/envelope/envelope_test.go @@ -10,7 +10,7 @@ import ( "github.com/btcsuite/btcutil/base58" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/timestamppb" e "github.com/hyperledger-labs/cckit/extensions/envelope" "github.com/hyperledger-labs/cckit/extensions/envelope/testdata" @@ -38,12 +38,15 @@ var ( payload = []byte(`{"symbol":"GLD","decimals":"8","name":"Gold digital asset","type":"DM","underlying_asset":"gold","issuer_id":"GLDINC"}`) ) -var _ = Describe(`Envelop`, func() { +var _ = Describe(`Envelop with Ed25519`, func() { + + ed25519 := e.NewEd25519() + signer := e.NewSigner(ed25519) Describe("Signature methods", func() { It("Allow to create keys", func() { - publicKey, privateKey, err := e.CreateKeys() + publicKey, privateKey, err := ed25519.GenerateKey() Expect(err).NotTo(HaveOccurred()) Expect(len(publicKey)).To(Equal(32)) Expect(len(privateKey)).To(Equal(64)) @@ -59,34 +62,35 @@ var _ = Describe(`Envelop`, func() { }) It("Allow to create signature", func() { - _, privateKey, _ := e.CreateKeys() - _, sig := e.CreateSig(payload, e.CreateNonce(), channel, chaincode, methodInvoke, deadline.String(), privateKey) + _, privateKey, _ := ed25519.GenerateKey() + sig, err := signer.Sign(payload, e.CreateNonce(), channel, chaincode, methodInvoke, deadline.String(), privateKey) + Expect(err).NotTo(HaveOccurred()) Expect(len(sig)).To(Equal(64)) }) It("Allow to check valid signature", func() { nonce := e.CreateNonce() - publicKey, privateKey, _ := e.CreateKeys() - _, sig := e.CreateSig(payload, nonce, channel, chaincode, methodInvoke, deadline.String(), privateKey) - err := e.CheckSig(payload, nonce, channel, chaincode, methodInvoke, deadline.String(), publicKey, sig) + publicKey, privateKey, _ := ed25519.GenerateKey() + sig, err := signer.Sign(payload, nonce, channel, chaincode, methodInvoke, deadline.String(), privateKey) + Expect(err).NotTo(HaveOccurred()) + err = signer.CheckSignature(payload, nonce, channel, chaincode, methodInvoke, deadline.String(), publicKey, sig) Expect(err).NotTo(HaveOccurred()) }) It("Disallow to check signature with invalid payload", func() { nonce := e.CreateNonce() - publicKey, privateKey, _ := e.CreateKeys() - _, sig := e.CreateSig(payload, nonce, channel, chaincode, methodInvoke, deadline.String(), privateKey) + publicKey, privateKey, _ := ed25519.GenerateKey() + sig, _ := signer.Sign(payload, nonce, channel, chaincode, methodInvoke, deadline.String(), privateKey) invalidPayload := []byte("invalid payload") - err := e.CheckSig(invalidPayload, nonce, channel, chaincode, methodInvoke, deadline.String(), publicKey, sig) + err := signer.CheckSignature(invalidPayload, nonce, channel, chaincode, methodInvoke, deadline.String(), publicKey, sig) Expect(err).Should(MatchError(e.ErrSignatureCheckFailed)) }) - }) Describe("Handle base64 envelop", func() { It("Allow to parse base64 envelop", func() { - _, envelope := createEnvelope(payload, channel, chaincode, methodInvoke, deadline) + _, envelope := createEnvelope(signer, payload, channel, chaincode, methodInvoke, deadline) jj, _ := json.Marshal(envelope) b64 := base64.StdEncoding.EncodeToString(jj) bb, err := e.DecodeEnvelope([]byte(b64)) @@ -99,18 +103,18 @@ var _ = Describe(`Envelop`, func() { Describe("Signature verification", func() { It("Allow to verify valid signature", func() { - serializedEnvelope, _ := createEnvelope(payload, channel, chaincode, methodInvoke, deadline) + serializedEnvelope, _ := createEnvelope(signer, payload, channel, chaincode, methodInvoke, deadline) - envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(chaincode)).WithChannel(channel) + envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(signer, chaincode)).WithChannel(channel) resp := envelopCC.Invoke(methodInvoke, payload, serializedEnvelope) Expect(resp.Status).To(BeNumerically("==", 200)) }) It("Allow to verify valid signature without deadline", func() { - serializedEnvelope, _ := createEnvelope(payload, channel, chaincode, methodInvoke) + serializedEnvelope, _ := createEnvelope(signer, payload, channel, chaincode, methodInvoke) - envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(chaincode)).WithChannel(channel) + envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(signer, chaincode)).WithChannel(channel) resp := envelopCC.Invoke(methodInvoke, payload, serializedEnvelope) Expect(resp.Status).To(BeNumerically("==", 200)) @@ -122,7 +126,7 @@ var _ = Describe(`Envelop`, func() { decodedEnvelope, err := e.DecodeEnvelope([]byte(b64Envelope)) Expect(err).NotTo(HaveOccurred()) - envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(chaincode)).WithChannel(channel) + envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(signer, chaincode)).WithChannel(channel) resp := envelopCC.Invoke(methodInvoke, payload, decodedEnvelope) Expect(resp.Status).To(BeNumerically("==", 200)) @@ -134,16 +138,16 @@ var _ = Describe(`Envelop`, func() { decodedEnvelope, err := e.DecodeEnvelope([]byte(b64Envelope)) Expect(err).NotTo(HaveOccurred()) - envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(chaincode)).WithChannel(channel) + envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(signer, chaincode)).WithChannel(channel) resp := envelopCC.Invoke(methodInvoke, payload, decodedEnvelope) Expect(resp.Status).To(BeNumerically("==", 200)) }) It("Disallow to verify signature with invalid payload", func() { - serializedEnvelope, _ := createEnvelope(payload, channel, chaincode, methodInvoke, deadline) + serializedEnvelope, _ := createEnvelope(signer, payload, channel, chaincode, methodInvoke, deadline) - envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(chaincode)).WithChannel(channel) + envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(signer, chaincode)).WithChannel(channel) invalidPayload := []byte("invalid payload") resp := envelopCC.Invoke(methodInvoke, invalidPayload, serializedEnvelope) @@ -151,25 +155,25 @@ var _ = Describe(`Envelop`, func() { }) It("Disallow to verify signature with invalid method", func() { - serializedEnvelope, _ := createEnvelope(payload, channel, chaincode, "invalid method", deadline) + serializedEnvelope, _ := createEnvelope(signer, payload, channel, chaincode, "invalid method", deadline) - envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(chaincode)).WithChannel(channel) + envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(signer, chaincode)).WithChannel(channel) resp := envelopCC.Invoke(methodInvoke, payload, serializedEnvelope) Expect(resp.Status).To(BeNumerically("==", 500)) }) It("Disallow to verify signature with invalid channel", func() { - serializedEnvelope, _ := createEnvelope(payload, "invalid channel", chaincode, methodInvoke, deadline) + serializedEnvelope, _ := createEnvelope(signer, payload, "invalid channel", chaincode, methodInvoke, deadline) - envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(chaincode)).WithChannel(channel) + envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(signer, chaincode)).WithChannel(channel) resp := envelopCC.Invoke(methodInvoke, payload, serializedEnvelope) Expect(resp.Status).To(BeNumerically("==", 500)) }) It("Don't check signature for query method", func() { - envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(chaincode)).WithChannel(channel) + envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(signer, chaincode)).WithChannel(channel) resp := envelopCC.Query(methodQuery, payload) Expect(resp.Status).To(BeNumerically("==", 200)) @@ -179,12 +183,12 @@ var _ = Describe(`Envelop`, func() { Describe("Nonce verification (replay attack)", func() { It("Disallow to execute tx with the same parameters (nonce, payload, pubkey)", func() { - envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(chaincode)).WithChannel(channel) + envelopCC = testcc.NewMockStub(chaincode, testdata.NewEnvelopCC(signer, chaincode)).WithChannel(channel) - publicKey, privateKey, _ := e.CreateKeys() + publicKey, privateKey, _ := ed25519.GenerateKey() nonce := "thesamenonce" - hashToSign := e.Hash(payload, nonce, channel, chaincode, methodInvoke, deadline.AsTime().Format(e.TimeLayout), []byte(publicKey)) - _, sig := e.CreateSig(payload, nonce, channel, chaincode, methodInvoke, deadline.AsTime().Format(e.TimeLayout), privateKey) + hashToSign := signer.Hash(payload, nonce, channel, chaincode, methodInvoke, deadline.AsTime().Format(e.TimeLayout), publicKey) + sig, _ := signer.Sign(payload, nonce, channel, chaincode, methodInvoke, deadline.AsTime().Format(e.TimeLayout), privateKey) envelope := &e.Envelope{ PublicKey: base58.Encode([]byte(publicKey)), Signature: base58.Encode(sig), @@ -209,8 +213,8 @@ var _ = Describe(`Envelop`, func() { }) -func createEnvelope(payload []byte, channel, chaincode, method string, deadline ...*timestamppb.Timestamp) ([]byte, *e.Envelope) { - publicKey, privateKey, _ := e.CreateKeys() +func createEnvelope(signer *e.Signer, payload []byte, channel, chaincode, method string, deadline ...*timestamppb.Timestamp) ([]byte, *e.Envelope) { + publicKey, privateKey, _ := e.NewEd25519().GenerateKey() nonce := e.CreateNonce() envelope := &e.Envelope{ @@ -226,10 +230,10 @@ func createEnvelope(payload []byte, channel, chaincode, method string, deadline envelope.Deadline = deadline[0] formatDeadline = envelope.Deadline.AsTime().Format(e.TimeLayout) } - hashToSign := e.Hash(payload, nonce, channel, chaincode, method, formatDeadline, publicKey) - envelope.HashToSign = base58.Encode(hashToSign[:]) + hashToSign := signer.Hash(payload, nonce, channel, chaincode, method, formatDeadline, publicKey) + envelope.HashToSign = base58.Encode(hashToSign) - _, sig := e.CreateSig(payload, nonce, channel, chaincode, method, formatDeadline, privateKey) + sig, _ := signer.Sign(payload, nonce, channel, chaincode, method, formatDeadline, privateKey) envelope.Signature = base58.Encode(sig) serializedEnvelope, _ := serialize.PreferJSONSerializer.ToBytesFrom(envelope) diff --git a/extensions/envelope/middleware.go b/extensions/envelope/middleware.go index ba2d392..fdeada7 100644 --- a/extensions/envelope/middleware.go +++ b/extensions/envelope/middleware.go @@ -6,8 +6,9 @@ import ( "time" "github.com/btcsuite/btcutil/base58" - "github.com/hyperledger-labs/cckit/router" "go.uber.org/zap" + + "github.com/hyperledger-labs/cckit/router" ) const ( @@ -26,68 +27,68 @@ const ( ) // Verify is a middleware for checking signature in envelop -func Verify() router.MiddlewareFunc { +func Verify(signer *Signer) router.MiddlewareFunc { return func(next router.HandlerFunc, pos ...int) router.HandlerFunc { - return func(c router.Context) (interface{}, error) { - if c.Handler().Type == invokeType { - iArgs := c.GetArgs() + return func(ctx router.Context) (interface{}, error) { + if ctx.Handler().Type == invokeType { + iArgs := ctx.GetArgs() if string(iArgs[methodNamePos]) != initType { if len(iArgs) == 2 { - c.Logger().Sugar().Error(ErrSignatureNotFound) + ctx.Logger().Sugar().Error(ErrSignatureNotFound) return nil, ErrSignatureNotFound } else { var ( e *Envelope err error ) - if e, err = verifyEnvelope(c, iArgs[methodNamePos], iArgs[payloadPos], iArgs[envelopePos]); err != nil { + if e, err = verifyEnvelope(ctx, signer, iArgs[methodNamePos], iArgs[payloadPos], iArgs[envelopePos]); err != nil { return nil, err } // store correct pubkey in context - c.SetParam(PubKey, e.PublicKey) + ctx.SetParam(PubKey, e.PublicKey) } } } - return next(c) + return next(ctx) } } } -func verifyEnvelope(c router.Context, method, payload, envlp []byte) (*Envelope, error) { +func verifyEnvelope(ctx router.Context, signer *Signer, method, payload, envlp []byte) (*Envelope, error) { // parse json envelope format (json is original format for envelope from frontend) - data, err := c.Serializer().FromBytesTo(envlp, &Envelope{}) + data, err := ctx.Serializer().FromBytesTo(envlp, &Envelope{}) if err != nil { - c.Logger().Error(`convert from bytes failed:`, zap.Error(err)) + ctx.Logger().Error(`convert from bytes failed:`, zap.Error(err)) return nil, err } envelope := data.(*Envelope) if envelope.Deadline.AsTime().Unix() != 0 { if envelope.Deadline.AsTime().Unix() < time.Now().Unix() { - c.Logger().Sugar().Error(ErrDeadlineExpired) + ctx.Logger().Sugar().Error(ErrDeadlineExpired) return nil, ErrDeadlineExpired } } // check method and channel names because envelope can only be used once for channel+chaincode+method combination if string(method) != envelope.Method { - c.Logger().Sugar().Error(ErrInvalidMethod) + ctx.Logger().Sugar().Error(ErrInvalidMethod) return nil, ErrInvalidMethod } - if c.Stub().GetChannelID() != envelope.Channel { - c.Logger().Sugar().Error(ErrInvalidChannel) + if ctx.Stub().GetChannelID() != envelope.Channel { + ctx.Logger().Sugar().Error(ErrInvalidChannel) return nil, ErrInvalidChannel } // replay attack check txHash := txNonceKey(payload, envelope.Nonce, envelope.Channel, envelope.Chaincode, envelope.Method, envelope.PublicKey) - key, err := c.Stub().CreateCompositeKey(nonceObjectType, []string{txHash}) + key, err := ctx.Stub().CreateCompositeKey(nonceObjectType, []string{txHash}) if err != nil { return nil, err } - bb, err := c.Stub().GetState(key) + bb, err := ctx.Stub().GetState(key) if bb == nil && err == nil { - if err := c.Stub().PutState(key, []byte{'0'}); err != nil { + if err := ctx.Stub().PutState(key, []byte{'0'}); err != nil { return nil, err } // convert public key and sig from base58 @@ -98,13 +99,13 @@ func verifyEnvelope(c router.Context, method, payload, envlp []byte) (*Envelope, if envelope.Deadline != nil { deadline = envelope.Deadline.AsTime().Format(TimeLayout) } - if err := CheckSig(payload, envelope.Nonce, envelope.Channel, envelope.Chaincode, envelope.Method, deadline, pubkey, sig); err != nil { - c.Logger().Error(ErrCheckSignatureFailed.Error(), zap.String("payload", string(payload)), zap.Any("envelope", envelope)) + if err := signer.CheckSignature(payload, envelope.Nonce, envelope.Channel, envelope.Chaincode, envelope.Method, deadline, pubkey, sig); err != nil { + ctx.Logger().Error(ErrCheckSignatureFailed.Error(), zap.String("payload", string(payload)), zap.Any("envelope", envelope)) //c.Logger().Sugar().Error(ErrCheckSignatureFailed) return nil, ErrCheckSignatureFailed } } else { - c.Logger().Sugar().Error(ErrTxAlreadyExecuted) + ctx.Logger().Sugar().Error(ErrTxAlreadyExecuted) return nil, ErrTxAlreadyExecuted } return envelope, nil diff --git a/extensions/envelope/signature.go b/extensions/envelope/signature.go deleted file mode 100644 index 4a9312a..0000000 --- a/extensions/envelope/signature.go +++ /dev/null @@ -1,56 +0,0 @@ -package envelope - -import ( - "crypto/ed25519" - "crypto/rand" - "crypto/sha256" - "strconv" - "strings" - "time" - - "github.com/btcsuite/btcutil/base58" -) - -func CreateKeys() (ed25519.PublicKey, ed25519.PrivateKey, error) { - publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - return nil, nil, err - } - return publicKey, privateKey, nil -} - -func CreateNonce() string { - return strconv.Itoa(int(time.Now().Unix())) -} - -func Hash(payload []byte, nonce, channel, chaincode, method, deadline string, pubkey []byte) [32]byte { - bb := append(removeSpacesBetweenCommaAndQuotes(payload), nonce...) // resolve the unclear json serialization behavior in protojson package - bb = append(bb, channel...) - bb = append(bb, chaincode...) - bb = append(bb, method...) - bb = append(bb, deadline...) - b58Pubkey := base58.Encode(pubkey) - bb = append(bb, b58Pubkey...) - return sha256.Sum256(bb) -} - -func CreateSig(payload []byte, nonce, channel, chaincode, method, deadline string, privateKey []byte) ([]byte, []byte) { - pubKey := ed25519.PrivateKey(privateKey).Public() - hashed := Hash(payload, nonce, channel, chaincode, method, deadline, []byte(pubKey.(ed25519.PublicKey))) - sig := ed25519.Sign(privateKey, hashed[:]) - return pubKey.(ed25519.PublicKey), sig -} - -func CheckSig(payload []byte, nonce, channel, chaincode, method, deadline string, pubKey []byte, sig []byte) error { - hashed := Hash(payload, nonce, channel, chaincode, method, deadline, pubKey) - if !ed25519.Verify(pubKey, hashed[:], sig) { - return ErrCheckSignatureFailed - } - return nil -} - -func removeSpacesBetweenCommaAndQuotes(s []byte) []byte { - removed := strings.ReplaceAll(string(s), `", "`, `","`) - removed = strings.ReplaceAll(removed, `"}, {"`, `"},{"`) - return []byte(strings.ReplaceAll(removed, `], "`, `],"`)) -} diff --git a/extensions/envelope/signer.go b/extensions/envelope/signer.go new file mode 100644 index 0000000..ddd1851 --- /dev/null +++ b/extensions/envelope/signer.go @@ -0,0 +1,59 @@ +package envelope + +import ( + "crypto/ed25519" + "fmt" + "strconv" + "strings" + "time" + + "github.com/btcsuite/btcutil/base58" +) + +type Signer struct { + crypto Crypto +} + +func NewSigner(crypto Crypto) *Signer { + return &Signer{crypto: crypto} +} + +func removeSpacesBetweenCommaAndQuotes(s []byte) []byte { + removed := strings.ReplaceAll(string(s), `", "`, `","`) + removed = strings.ReplaceAll(removed, `"}, {"`, `"},{"`) + return []byte(strings.ReplaceAll(removed, `], "`, `],"`)) +} + +func CreateNonce() string { + return strconv.Itoa(int(time.Now().Unix())) +} + +func (b *Signer) Hash(payload []byte, nonce, channel, chaincode, method, deadline string, pubkey []byte) []byte { + bb := append(removeSpacesBetweenCommaAndQuotes(payload), nonce...) // resolve the unclear json serialization behavior in protojson package + bb = append(bb, channel...) + bb = append(bb, chaincode...) + bb = append(bb, method...) + bb = append(bb, deadline...) + b58Pubkey := base58.Encode(pubkey) + bb = append(bb, b58Pubkey...) + return b.crypto.Hash(bb) +} + +func (b *Signer) Sign( + payload []byte, nonce, channel, chaincode, method, deadline string, privateKey []byte) ([]byte, error) { + pubKey, err := b.crypto.PublicKey(privateKey) + if err != nil { + return nil, fmt.Errorf(`extract public key: %w`, err) + } + + hashed := b.Hash(payload, nonce, channel, chaincode, method, deadline, pubKey) + return b.crypto.Sign(hashed, privateKey) +} + +func (b *Signer) CheckSignature(payload []byte, nonce, channel, chaincode, method, deadline string, pubKey []byte, sig []byte) error { + hashed := b.Hash(payload, nonce, channel, chaincode, method, deadline, pubKey) + if !ed25519.Verify(pubKey, hashed[:], sig) { + return ErrCheckSignatureFailed + } + return nil +} diff --git a/extensions/envelope/testdata/cc_envelope.go b/extensions/envelope/testdata/cc_envelope.go index f7d5930..57722b2 100755 --- a/extensions/envelope/testdata/cc_envelope.go +++ b/extensions/envelope/testdata/cc_envelope.go @@ -10,8 +10,8 @@ import ( type EnvelopCC struct { } -func NewEnvelopCC(chaincodeName string) *router.Chaincode { - r := router.New(chaincodeName, router.WithSerializer(serialize.PreferJSONSerializer)).Use(envelope.Verify()) +func NewEnvelopCC(signer *envelope.Signer, chaincodeName string) *router.Chaincode { + r := router.New(chaincodeName, router.WithSerializer(serialize.PreferJSONSerializer)).Use(envelope.Verify(signer)) r.Invoke("invokeWithEnvelope", func(c router.Context) (interface{}, error) { return nil, nil