diff --git a/Taskfile.yml b/Taskfile.yml index 7a6d76c57..d21d5b23e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -104,7 +104,7 @@ tasks: - task: build:docker vars: {VARIANT: 'shell'} cmds: - - go test ./test/acceptance/ -timeout 12h -dev -v + - task dev:up:nb dev:up:nb: desc: Start the dev environment without rebuilding docker image @@ -117,7 +117,7 @@ tasks: - task: build:docker vars: { VARIANT: 'shell' } cmds: - - go test ./test/integration/ -timeout 12h -dev -v + - task dev:testnet:up:nb dev:testnet:up:nb: desc: Start the dev environment(with testnet) without rebuilding docker image @@ -127,11 +127,10 @@ tasks: # ************ test ************ test:act: desc: Run acceptance tests - # deps: - # - task: build:docker - # vars: { VARIANT: 'shell' } + deps: + - task: build:docker cmds: - - go test ./test/acceptance/ -v + - task test:act:nb test:act:nb: desc: Run acceptance tests without building docker image @@ -147,6 +146,12 @@ tasks: test:it: desc: Run integration tests - #deps: [ build:docker ] + deps: + - task: build:docker + cmds: + - task test:it:nb + + test:it:nb: + desc: Run integration tests cmds: - go test -count=1 -timeout 0 ./test/integration/ -v diff --git a/api/openapi-spec/api/v1/api.swagger.json b/api/openapi-spec/api/v1/api.swagger.json index 3fbf8ae6c..b61e7d83f 100644 --- a/api/openapi-spec/api/v1/api.swagger.json +++ b/api/openapi-spec/api/v1/api.swagger.json @@ -768,8 +768,7 @@ "format": "byte" }, "signature_type": { - "type": "integer", - "format": "int32" + "type": "string" } } }, diff --git a/cmd/kwil-cli/cmds/common/roundtripper.go b/cmd/kwil-cli/cmds/common/roundtripper.go index 37566f13a..9bad0fbaa 100644 --- a/cmd/kwil-cli/cmds/common/roundtripper.go +++ b/cmd/kwil-cli/cmds/common/roundtripper.go @@ -9,6 +9,7 @@ import ( ) const ( + // this is a wire flag // WithoutPrivateKey is a flag that can be passed to DialClient to indicate that the client should not use the private key in the config WithoutPrivateKey uint8 = 1 << iota ) @@ -31,7 +32,7 @@ func DialClient(ctx context.Context, flags uint8, fn RoundTripper) error { return fmt.Errorf("private key not provided") } - options = append(options, client.WithPrivateKey(conf.PrivateKey)) + options = append(options, client.WithSigner(conf.PrivateKey.Signer())) } if conf.GrpcURL == "" { diff --git a/cmd/kwil-cli/cmds/configure/configure.go b/cmd/kwil-cli/cmds/configure/configure.go index cec9e8bc8..8a2fc54ad 100644 --- a/cmd/kwil-cli/cmds/configure/configure.go +++ b/cmd/kwil-cli/cmds/configure/configure.go @@ -85,7 +85,7 @@ func promptPrivateKey(conf *config.KwilCliConfig) error { return nil } - pk, err := crypto.PrivateKeyFromHex(res) + pk, err := crypto.Secp256k1PrivateKeyFromHex(res) if err != nil { fmt.Println(`invalid private key. key could not be converted to hex. received: `, res) promptAskAgain := &common.Prompter{ diff --git a/cmd/kwil-cli/config/config.go b/cmd/kwil-cli/config/config.go index d4e64bbda..2186f9ef3 100644 --- a/cmd/kwil-cli/config/config.go +++ b/cmd/kwil-cli/config/config.go @@ -41,7 +41,7 @@ func (c *kwilCliPersistedConfig) toKwilCliConfig() (*KwilCliConfig, error) { ClientChainRPCURL: c.ClientChainRPCURL, } - privateKey, err := crypto.PrivateKeyFromHex(c.PrivateKey) + privateKey, err := crypto.Secp256k1PrivateKeyFromHex(c.PrivateKey) if err != nil { return kwilConfig, nil } diff --git a/internal/app/kwild/client/client.go b/internal/app/kwild/client/client.go deleted file mode 100644 index 82d6bc883..000000000 --- a/internal/app/kwild/client/client.go +++ /dev/null @@ -1,34 +0,0 @@ -package client - -import ( - "context" - - "github.com/kwilteam/kwil-db/pkg/client" - "github.com/kwilteam/kwil-db/pkg/crypto" -) - -type KwildConfig struct { - PrivateKey string - GrpcURL string - ClientChainRPCURL string -} - -func NewClient(ctx context.Context, cfg *KwildConfig) (*client.Client, error) { - options := []client.ClientOpt{} - if cfg.ClientChainRPCURL != "" { - options = append(options, client.WithCometBftUrl(cfg.ClientChainRPCURL)) - } - if cfg.PrivateKey != "" { - key, err := crypto.PrivateKeyFromHex(cfg.PrivateKey) - if err != nil { - return nil, err - } - - options = append(options, client.WithPrivateKey(key)) - } - clt, err := client.New(ctx, cfg.GrpcURL, options...) - if err != nil { - return nil, err - } - return clt, nil -} diff --git a/internal/app/kwild/driver.go b/internal/app/kwild/driver.go index a4fcb20a8..8f28240f2 100644 --- a/internal/app/kwild/driver.go +++ b/internal/app/kwild/driver.go @@ -43,7 +43,7 @@ func NewKwildDriver(clt *client.Client, opts ...GrpcDriverOpt) *KwildDriver { } func (d *KwildDriver) GetUserAddress() string { - return d.clt.PrivateKey.PubKey().Address().String() + return d.clt.Signer.PubKey().Address().String() } // TODO: this likely needs to change; the old Kwild driver is not compatible, since deploy, drop, and execute are asynchronous diff --git a/internal/controller/grpc/txsvc/v1/call.go b/internal/controller/grpc/txsvc/v1/call.go index f6bc3df18..773c7c203 100644 --- a/internal/controller/grpc/txsvc/v1/call.go +++ b/internal/controller/grpc/txsvc/v1/call.go @@ -19,7 +19,7 @@ func (s *Service) Call(ctx context.Context, req *txpb.CallRequest) (*txpb.CallRe return nil, status.Errorf(codes.InvalidArgument, "failed to convert action call: %s", err.Error()) } - if msg.Sender == nil { + if msg.Sender != nil { err = msg.Verify() if err != nil { return nil, status.Errorf(codes.InvalidArgument, "failed to verify signed message: %s", err.Error()) @@ -53,12 +53,22 @@ func convertActionCall(req *txpb.CallRequest) (*transactions.ActionCall, *transa return nil, nil, err } - sender, err := crypto.PublicKeyFromBytes(req.Sender) + if req.GetSignature() == nil { + return &actionPayload, &transactions.SignedMessage{ + Signature: nil, + Message: nil, + Sender: nil, + }, nil + } + + convSignature, err := convertSignature(req.Signature) if err != nil { return nil, nil, err } - convSignature, err := convertSignature(req.Signature) + // TODO: not sure should we parse PublicKey based on Signature.Type or not + + sender, err := crypto.PublicKeyFromBytes(convSignature.KeyType(), req.Sender) if err != nil { return nil, nil, err } diff --git a/internal/controller/grpc/txsvc/v1/convert.go b/internal/controller/grpc/txsvc/v1/convert.go index 1f2aa2916..02ca6664d 100644 --- a/internal/controller/grpc/txsvc/v1/convert.go +++ b/internal/controller/grpc/txsvc/v1/convert.go @@ -21,11 +21,6 @@ func convertTransaction(incoming *txpb.Transaction) (*transactions.Transaction, return nil, fmt.Errorf("transaction signature cannot be nil") } - sender, err := crypto.PublicKeyFromBytes(incoming.Sender) - if err != nil { - return nil, fmt.Errorf("invalid sender public key: %s", err.Error()) - } - convSignature, err := convertSignature(incoming.Signature) if err != nil { return nil, err @@ -45,7 +40,7 @@ func convertTransaction(incoming *txpb.Transaction) (*transactions.Transaction, Fee: bigFee, Salt: incoming.Body.Salt, }, - Sender: sender, + Sender: incoming.Sender, }, nil } @@ -62,7 +57,7 @@ func convertSignature(sig *txpb.Signature) (*crypto.Signature, error) { }, nil } - sigType := crypto.SignatureType(sig.SignatureType) + sigType := crypto.SignatureLookUp(sig.SignatureType) if err := sigType.IsValid(); err != nil { return nil, err } diff --git a/pkg/client/client.go b/pkg/client/client.go index 9819c0ec2..382f807b1 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -24,7 +24,7 @@ type Client struct { client *grpcClient.Client CometBftClient *rpchttp.HTTP datasets map[string]*transactions.Schema - PrivateKey crypto.PrivateKey + Signer crypto.Signer cometBftRpcUrl string } @@ -164,7 +164,7 @@ func (c *Client) CallAction(ctx context.Context, dbid string, action string, inp } var signedMsg *transactions.SignedMessage - shouldSign, err := shouldAuthenticate(c.PrivateKey, callOpts.forceAuthenticated) + shouldSign, err := shouldAuthenticate(c.Signer, callOpts.forceAuthenticated) if err != nil { return nil, err } @@ -175,7 +175,7 @@ func (c *Client) CallAction(ctx context.Context, dbid string, action string, inp } if shouldSign { - err = msg.Sign(c.PrivateKey) + err = msg.Sign(c.Signer) if err != nil { return nil, fmt.Errorf("failed to create signed message: %w", err) @@ -188,20 +188,20 @@ func (c *Client) CallAction(ctx context.Context, dbid string, action string, inp // shouldAuthenticate decides whether the client should authenticate or not // if enforced is not nil, it will be used instead of the default value // otherwise, if the private key is not nil, it will authenticate -func shouldAuthenticate(privateKey crypto.PrivateKey, enforced *bool) (bool, error) { +func shouldAuthenticate(signer crypto.Signer, enforced *bool) (bool, error) { if enforced != nil { if !*enforced { return false, nil } - if privateKey == nil { + if signer == nil { return false, fmt.Errorf("private key is nil, but authentication is enforced") } return true, nil } - return privateKey != nil, nil + return signer != nil, nil } func DecodeOutputs(bts []byte) ([]map[string]any, error) { diff --git a/pkg/client/opts.go b/pkg/client/opts.go index e228eb476..a232fe99f 100644 --- a/pkg/client/opts.go +++ b/pkg/client/opts.go @@ -4,9 +4,9 @@ import "github.com/kwilteam/kwil-db/pkg/crypto" type ClientOpt func(*Client) -func WithPrivateKey(key crypto.PrivateKey) ClientOpt { +func WithSigner(signer crypto.Signer) ClientOpt { return func(c *Client) { - c.PrivateKey = key + c.Signer = signer } } diff --git a/pkg/client/tx.go b/pkg/client/tx.go index 78051afb2..02c620dbb 100644 --- a/pkg/client/tx.go +++ b/pkg/client/tx.go @@ -11,7 +11,7 @@ import ( // Transaction signed by the client func (c *Client) newTx(ctx context.Context, data transactions.Payload) (*transactions.Transaction, error) { - if c.PrivateKey == nil { + if c.Signer == nil { return nil, fmt.Errorf("private key is nil") } @@ -46,7 +46,7 @@ func (c *Client) newTx(ctx context.Context, data transactions.Payload) (*transac tx.Body.Fee = price // sign transaction - err = tx.Sign(c.PrivateKey) + err = tx.Sign(c.Signer) if err != nil { return nil, fmt.Errorf("failed to sign transaction: %w", err) } @@ -122,9 +122,9 @@ func (c *Client) NewNodeTx(ctx context.Context, payloadType transactions.Payload } func (c *Client) getAddress() (string, error) { - if c.PrivateKey == nil { + if c.Signer == nil { return "", fmt.Errorf("private key is nil") } - return c.PrivateKey.PubKey().Address().String(), nil + return c.Signer.PubKey().Address().String(), nil } diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go index d4c59e850..2f157b21a 100644 --- a/pkg/crypto/crypto.go +++ b/pkg/crypto/crypto.go @@ -1,12 +1,18 @@ package crypto import ( - "crypto/ecdsa" + "crypto/ed25519" c256 "crypto/sha256" c512 "crypto/sha512" "encoding/hex" + "fmt" - ec "github.com/ethereum/go-ethereum/crypto" + ethCrypto "github.com/ethereum/go-ethereum/crypto" +) + +var ( + errInvalidPrivateKey = fmt.Errorf("invalid private key") + errInvalidPublicKey = fmt.Errorf("invalid public key") ) // Sha384 returns the sha384 hash of the data. @@ -40,18 +46,59 @@ func Sha256Hex(data []byte) string { return hex.EncodeToString(Sha256(data)) } -func PublicKeyFromBytes(key []byte) (PublicKey, error) { - panic("not implemented") +func Secp256k1PublicKeyFromBytes(key []byte) (*Secp256k1PublicKey, error) { + pk, err := ethCrypto.UnmarshalPubkey(key) + if err != nil { + return nil, err + } + return &Secp256k1PublicKey{publicKey: pk}, nil +} + +func Secp256k1PrivateKeyFromHex(key string) (*Secp256k1PrivateKey, error) { + pk, err := ethCrypto.HexToECDSA(key) + if err != nil { + return nil, err + } + return &Secp256k1PrivateKey{key: pk}, nil +} + +func Ed25519PrivateKeyFromHex(key string) (*Ed25519PrivateKey, error) { + if len(key) != ed25519.PrivateKeySize*2 { + return nil, errInvalidPrivateKey + } + + pkBytes, err := hex.DecodeString(key) + if err != nil { + return nil, err + } + return &Ed25519PrivateKey{key: pkBytes}, nil } -func PrivateKeyFromHex(h string) (PrivateKey, error) { - panic("not implemented") +func Ed25519PublicKeyFromBytes(key []byte) (*Ed25519PublicKey, error) { + if len(key) != ed25519.PublicKeySize { + return nil, errInvalidPublicKey + } + return &Ed25519PublicKey{key: key}, nil } -func PrivateKeyFromBytes(key []byte) (PrivateKey, error) { - panic("not implemented") +func PrivateKeyFromHex(keyType KeyType, key string) (PrivateKey, error) { + switch keyType { + case Secp256k1: + return Secp256k1PrivateKeyFromHex(key) + case Ed25519: + return Ed25519PrivateKeyFromHex(key) + default: + return nil, fmt.Errorf("invalid key type: %s", keyType) + } } -func ECDSAFromHex(hex string) (*ecdsa.PrivateKey, error) { - return ec.HexToECDSA(hex) +func PublicKeyFromBytes(keyType KeyType, key []byte) (PublicKey, error) { + switch keyType { + case Secp256k1: + return Secp256k1PublicKeyFromBytes(key) + case Ed25519: + return Ed25519PublicKeyFromBytes(key) + default: + return nil, fmt.Errorf("invalid key type %s", keyType) + } } diff --git a/pkg/crypto/crypto_test.go b/pkg/crypto/crypto_test.go new file mode 100644 index 000000000..1e1d7d3a3 --- /dev/null +++ b/pkg/crypto/crypto_test.go @@ -0,0 +1,78 @@ +package crypto_test + +import ( + "reflect" + "testing" + + "github.com/kwilteam/kwil-db/pkg/crypto" +) + +func TestPrivateKeyFromHex(t *testing.T) { + type args struct { + key string + } + tests := []struct { + name string + args args + want crypto.KeyType + wantErr bool + }{ + { + name: "empty secp256k1 private key", + args: args{ + key: "", + }, + want: crypto.Secp256k1, + wantErr: true, + }, + { + name: "secp256k1", + args: args{ + key: "f1aa5a7966c3863ccde3047f6a1e266cdc0c76b399e256b8fede92b1c69e4f4e", + }, + want: crypto.Secp256k1, + wantErr: false, + }, + { + name: "secp256k1 invalid key", + args: args{ + key: "f1aa5a7966c3863ccde3047f6a1e266cdc0c76b399e256b8fede92b1c69e4f", + }, + want: crypto.Secp256k1, + wantErr: true, + }, + { + name: "empty ed25519 private key", + args: args{ + key: "", + }, + want: crypto.Ed25519, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var pk crypto.PrivateKey + var err error + switch tt.want { + case crypto.Secp256k1: + pk, err = crypto.Secp256k1PrivateKeyFromHex(tt.args.key) + case crypto.Ed25519: + pk, err = crypto.Ed25519PrivateKeyFromHex(tt.args.key) + } + + if (err != nil) != tt.wantErr { + t.Errorf("PrivateKeyFromHex() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.wantErr { + return + } + + if !reflect.DeepEqual(pk.Type(), tt.want) { + t.Errorf("PrivateKeyFromHex() got = %v, want %v", pk.Type(), tt.want) + } + }) + } +} diff --git a/pkg/crypto/ed25519.go b/pkg/crypto/ed25519.go index 64a974eea..138a36065 100644 --- a/pkg/crypto/ed25519.go +++ b/pkg/crypto/ed25519.go @@ -1,57 +1,98 @@ package crypto +import ( + "crypto/ed25519" + "encoding/hex" +) + const Ed25519 KeyType = "Ed25519" type Ed25519PrivateKey struct { - privateKey []byte + key ed25519.PrivateKey +} + +func (pv *Ed25519PrivateKey) Bytes() []byte { + return pv.key +} + +func (pv *Ed25519PrivateKey) PubKey() PublicKey { + publicKey := make([]byte, ed25519.PublicKeySize) + copy(publicKey, pv.key[32:]) + return &Ed25519PublicKey{ + key: publicKey, + } } -func (s *Ed25519PrivateKey) Bytes() []byte { - return s.privateKey +func (pv *Ed25519PrivateKey) Hex() string { + return hex.EncodeToString(pv.Bytes()) } -func (s *Ed25519PrivateKey) PubKey() PublicKey { - panic("implement me") +// SignMsg signs the given message(not hashed). ed25519 is kind special that hashing is took care already. +// Implements the Signer interface. +func (pv *Ed25519PrivateKey) SignMsg(msg []byte) (*Signature, error) { + sig, err := pv.Sign(msg) + if err != nil { + return nil, err + } + return &Signature{ + Signature: sig, + Type: SIGNATURE_TYPE_ED25519, + }, nil } -func (s *Ed25519PrivateKey) Sign(msg []byte) (*Signature, error) { - panic("implement me") +// Sign signs the given message(not hashed). This is only to keep the interface consistent. +func (pv *Ed25519PrivateKey) Sign(msg []byte) ([]byte, error) { + return ed25519.Sign(pv.key, msg), nil } -func (s *Ed25519PrivateKey) Type() KeyType { +func (pv *Ed25519PrivateKey) Signer() Signer { + return pv +} + +func (pv *Ed25519PrivateKey) Type() KeyType { return Ed25519 } type Ed25519PublicKey struct { + key ed25519.PublicKey } -func (s *Ed25519PublicKey) Address() Address { - panic("implement me") +func (pub *Ed25519PublicKey) Address() Address { + return Ed25519Address(pub.key[:20]) } -func (s *Ed25519PublicKey) Bytes() []byte { - panic("implement me") +func (pub *Ed25519PublicKey) Bytes() []byte { + return pub.key } -func (s *Ed25519PublicKey) Type() KeyType { +func (pub *Ed25519PublicKey) Type() KeyType { return Ed25519 } -func (s *Ed25519PublicKey) Verify(sig *Signature, data []byte) error { - panic("implement me") -} +// Verify verifies the given signature against the given message(not hashed). +func (pub *Ed25519PublicKey) Verify(sig []byte, msg []byte) error { + if len(sig) != ed25519.SignatureSize { + return errInvalidSignature + } -type Ed25519Address struct { + ok := ed25519.Verify(pub.key, msg, sig) + if !ok { + return errVerifySignatureFailed + } + return nil } -func (s *Ed25519Address) Bytes() []byte { - panic("implement me") +type Ed25519Address [20]byte + +func (s Ed25519Address) Bytes() []byte { + return s[:] } -func (s *Ed25519Address) Type() KeyType { +func (s Ed25519Address) Type() KeyType { return Ed25519 } -func (s *Ed25519Address) String() string { - panic("implement me") +func (s Ed25519Address) String() string { + // TODO: need an address format + return hex.EncodeToString(s.Bytes()) } diff --git a/pkg/crypto/ed25519_test.go b/pkg/crypto/ed25519_test.go new file mode 100644 index 000000000..5483839c4 --- /dev/null +++ b/pkg/crypto/ed25519_test.go @@ -0,0 +1,107 @@ +package crypto_test + +import ( + "encoding/hex" + "testing" + + "github.com/kwilteam/kwil-db/pkg/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEd25519PrivateKey_Sign(t *testing.T) { + key := "7c67e60fce0c403ff40193a3128e5f3d8c2139aed36d76d7b5f1e70ec19c43f00aa611bf555596912bc6f9a9f169f8785918e7bab9924001895798ff13f05842" + pk, err := crypto.Ed25519PrivateKeyFromHex(key) + require.NoError(t, err, "error parse private key") + + msg := []byte("foo") + + sig, err := pk.Sign(msg) + require.NoError(t, err, "error sign") + + expectSignature := "59b2db2d1e4ce6f8771453cfc78d1f943723528f00fa14adf574600f15c601d591fa2ba29c94d9ed694db324f9e8671bdfbcba4b8e10f6a8733682fa3d115f0c" + assert.Equal(t, expectSignature, hex.EncodeToString(sig), "unexpect signature") +} + +func TestEd25519PrivateKey_SignMsg(t *testing.T) { + key := "7c67e60fce0c403ff40193a3128e5f3d8c2139aed36d76d7b5f1e70ec19c43f00aa611bf555596912bc6f9a9f169f8785918e7bab9924001895798ff13f05842" + pk, err := crypto.Ed25519PrivateKeyFromHex(key) + require.NoError(t, err, "error parse private key") + + msg := []byte("foo") + + sig, err := pk.SignMsg(msg) + require.NoError(t, err, "error sign") + + expectSignature := "59b2db2d1e4ce6f8771453cfc78d1f943723528f00fa14adf574600f15c601d591fa2ba29c94d9ed694db324f9e8671bdfbcba4b8e10f6a8733682fa3d115f0c" + expectSignatureBytes, _ := hex.DecodeString(expectSignature) + + expectSig := &crypto.Signature{ + Signature: expectSignatureBytes, + Type: crypto.SIGNATURE_TYPE_ED25519, + } + + assert.EqualValues(t, expectSig, sig, "unexpect signature") +} + +func TestEd25519PublicKey_Verify(t *testing.T) { + key := "0aa611bf555596912bc6f9a9f169f8785918e7bab9924001895798ff13f05842" + keyBytes, err := hex.DecodeString(key) + require.NoError(t, err, "error decode public key") + + pubKey, err := crypto.Ed25519PublicKeyFromBytes(keyBytes) + require.NoError(t, err, "error parse public key") + + msg := []byte("foo") + sig := "59b2db2d1e4ce6f8771453cfc78d1f943723528f00fa14adf574600f15c601d591fa2ba29c94d9ed694db324f9e8671bdfbcba4b8e10f6a8733682fa3d115f0c" + sigBytes, _ := hex.DecodeString(sig) + + tests := []struct { + name string + msg []byte + sigBytes []byte + wantErr string // internal error message + }{ + { + name: "verify success", + msg: msg, + sigBytes: sigBytes, + wantErr: "", + }, + { + name: "invalid signature length", + msg: msg, + sigBytes: sigBytes[1:], + wantErr: "invalid signature", + }, + { + name: "wrong signature", + msg: []byte("bar"), + sigBytes: sigBytes, + wantErr: "verify signature failed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := pubKey.Verify(tt.sigBytes, tt.msg) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + } else { + assert.Nil(t, err, "expect no error") + } + }) + } +} + +func TestEd25519PublicKey_Address(t *testing.T) { + key := "0aa611bf555596912bc6f9a9f169f8785918e7bab9924001895798ff13f05842" + keyBytes, err := hex.DecodeString(key) + require.NoError(t, err, "error decode public key") + + pubKey, err := crypto.Ed25519PublicKeyFromBytes(keyBytes) + require.NoError(t, err, "error parse public key") + + eq := pubKey.Address().String() == "0aa611bf555596912bc6f9a9f169f8785918e7ba" + assert.True(t, eq, "mismatch address") +} diff --git a/pkg/crypto/keys.go b/pkg/crypto/keys.go index 32aaab104..1ed5fae8c 100644 --- a/pkg/crypto/keys.go +++ b/pkg/crypto/keys.go @@ -2,18 +2,23 @@ package crypto type KeyType string +const UnknownKeyType KeyType = "unknown" + type PrivateKey interface { Bytes() []byte Type() KeyType - Sign(msg []byte) (*Signature, error) + // Sign generate signature on data. Data could be hashed or not, depends on the implementation + Sign(data []byte) ([]byte, error) PubKey() PublicKey Hex() string + Signer() Signer } type PublicKey interface { Bytes() []byte Type() KeyType - Verify(sig *Signature, data []byte) error + // Verify verify signature against data. Data could be hashed or not, depends on the implementation + Verify(sig []byte, data []byte) error Address() Address } diff --git a/pkg/crypto/secp256k1.go b/pkg/crypto/secp256k1.go index 8d3d1ec6a..1f7dc9421 100644 --- a/pkg/crypto/secp256k1.go +++ b/pkg/crypto/secp256k1.go @@ -2,7 +2,9 @@ package crypto import ( "crypto/ecdsa" + "encoding/hex" + ethAccount "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" ethCrypto "github.com/ethereum/go-ethereum/crypto" ) @@ -10,25 +12,48 @@ import ( const Secp256k1 KeyType = "secp256k1" type Secp256k1PrivateKey struct { - privateKey *ecdsa.PrivateKey + key *ecdsa.PrivateKey } -func (s *Secp256k1PrivateKey) Bytes() []byte { - return ethCrypto.FromECDSA(s.privateKey) +func (pv *Secp256k1PrivateKey) Bytes() []byte { + return ethCrypto.FromECDSA(pv.key) } -func (s *Secp256k1PrivateKey) PubKey() PublicKey { +func (pv *Secp256k1PrivateKey) Hex() string { + return hex.EncodeToString(pv.Bytes()) +} + +func (pv *Secp256k1PrivateKey) PubKey() PublicKey { return &Secp256k1PublicKey{ - publicKey: &s.privateKey.PublicKey, + publicKey: &pv.key.PublicKey, + } +} + +// SignMsg signs the given message(not hashed) according to EIP-191 personal_sign. +// This is default signature type for sec256k1. +// Implements the Signer interface. +func (pv *Secp256k1PrivateKey) SignMsg(msg []byte) (*Signature, error) { + hash := ethAccount.TextHash(msg) + sig, err := pv.Sign(hash) + if err != nil { + return nil, err } + return &Signature{ + Signature: sig, + Type: SIGNATURE_TYPE_SECP256K1_PERSONAL, + }, nil } -func (s *Secp256k1PrivateKey) Sign(msg []byte) (*Signature, error) { - // TODO: implement - panic("TODO") +// Sign signs the given hash directly utilizing go-ethereum's Sign function. +func (pv *Secp256k1PrivateKey) Sign(hash []byte) ([]byte, error) { + return ethCrypto.Sign(hash, pv.key) } -func (s *Secp256k1PrivateKey) Type() KeyType { +func (pv *Secp256k1PrivateKey) Signer() Signer { + return pv +} + +func (pv *Secp256k1PrivateKey) Type() KeyType { return Secp256k1 } @@ -36,37 +61,47 @@ type Secp256k1PublicKey struct { publicKey *ecdsa.PublicKey } -func (s *Secp256k1PublicKey) Address() Address { +func (pub *Secp256k1PublicKey) Address() Address { return &Secp256k1Address{ - address: ethCrypto.PubkeyToAddress(*s.publicKey), + address: ethCrypto.PubkeyToAddress(*pub.publicKey), } } -func (s *Secp256k1PublicKey) Bytes() []byte { - return ethCrypto.FromECDSAPub(s.publicKey) +func (pub *Secp256k1PublicKey) Bytes() []byte { + return ethCrypto.FromECDSAPub(pub.publicKey) } -func (s *Secp256k1PublicKey) Type() KeyType { +func (pub *Secp256k1PublicKey) Type() KeyType { return Secp256k1 } -func (s *Secp256k1PublicKey) Verify(sig *Signature, data []byte) error { - // TODO: implement - panic("TODO") +// Verify verifies the signature against the given hash. +// e.g. this verify able to verify multi-signature-schema like personal_sign, eip712, cometbft, etc. +func (pub *Secp256k1PublicKey) Verify(sig []byte, hash []byte) error { + if len(sig) != 64 { + return errInvalidSignature + } + + // signature should have the 64 byte [R || S] format + if !ethCrypto.VerifySignature(pub.Bytes(), hash, sig) { + return errVerifySignatureFailed + } + + return nil } type Secp256k1Address struct { address common.Address } -func (s *Secp256k1Address) Bytes() []byte { - return s.address.Bytes() +func (addr *Secp256k1Address) Bytes() []byte { + return addr.address.Bytes() } -func (s *Secp256k1Address) Type() KeyType { +func (addr *Secp256k1Address) Type() KeyType { return Secp256k1 } -func (s *Secp256k1Address) String() string { - return s.address.String() +func (addr *Secp256k1Address) String() string { + return addr.address.Hex() } diff --git a/pkg/crypto/secp256k1_test.go b/pkg/crypto/secp256k1_test.go new file mode 100644 index 000000000..4ea1b0e32 --- /dev/null +++ b/pkg/crypto/secp256k1_test.go @@ -0,0 +1,103 @@ +package crypto + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSecp256k1PrivateKey_Sign(t *testing.T) { + key := "f1aa5a7966c3863ccde3047f6a1e266cdc0c76b399e256b8fede92b1c69e4f4e" + pk, err := Secp256k1PrivateKeyFromHex(key) + require.NoError(t, err, "error parse private key") + + msg := []byte("foo") + hash := Sha256(msg) + + sig, err := pk.Sign(hash) + require.NoError(t, err, "error sign") + + expectSig := "19a4aced02d5b9142b4f622b06442b1904445e16bd25409e6b0ff357ccc021d001d0e7824654b695b4b6e0991cb7507f487b82be4b2ed713d1e3e2cbc3d2518d01" + require.Equal(t, SIGNATURE_SECP256K1_PERSONAL_LENGTH, len(sig), "invalid signature length") + require.EqualValues(t, hex.EncodeToString(sig), expectSig, "invalid signature") +} + +func TestSecp256k1PrivateKey_SignMsg(t *testing.T) { + key := "f1aa5a7966c3863ccde3047f6a1e266cdc0c76b399e256b8fede92b1c69e4f4e" + pk, err := Secp256k1PrivateKeyFromHex(key) + require.NoError(t, err, "error parse private key") + + msg := []byte("foo") + + sig, err := pk.SignMsg(msg) + require.NoError(t, err, "error sign msg") + + expectSignature := "cb3fed7f6ff36e59054c04a831b215e514052753ee353e6fe31d4b4ef736acd6155127db555d3006ba14fcb4c79bbad56c8e63b81a9896319bb053a9e253475800" + expectSignatureBytes, _ := hex.DecodeString(expectSignature) + + expectSig := &Signature{ + Signature: expectSignatureBytes, + Type: SIGNATURE_TYPE_SECP256K1_PERSONAL, + } + + assert.EqualValues(t, expectSig, sig, "unexpect signature") +} + +func TestSecp256k1PublicKey_Verify(t *testing.T) { + key := "04812bef44f6e7b2a19c0b01c2dca5e54ba1935a1890ffdcb93abd0c534b209c21e4f6176823fef493f7b5afaa456f31d5293363d8f801c540ebcc061812890cba" + keyBytes, err := hex.DecodeString(key) + require.NoError(t, err, "error decode key") + + pubKey, err := Secp256k1PublicKeyFromBytes(keyBytes) + require.NoError(t, err, "error parse public key") + + msg := []byte("foo") + hash := Sha256(msg) + + sig := "19a4aced02d5b9142b4f622b06442b1904445e16bd25409e6b0ff357ccc021d001d0e7824654b695b4b6e0991cb7507f487b82be4b2ed713d1e3e2cbc3d2518d01" + sigBytes, _ := hex.DecodeString(sig) + require.Equal(t, SIGNATURE_SECP256K1_PERSONAL_LENGTH, len(sigBytes), "invalid signature length") + + tests := []struct { + name string + sigBytes []byte + wantErr error + }{ + { + name: "verify success", + sigBytes: sigBytes[:len(sigBytes)-1], + wantErr: nil, + }, + { + name: "invalid signature length", + sigBytes: sigBytes, + wantErr: errInvalidSignature, + }, + { + name: "wrong signature", + sigBytes: sigBytes[1:], + wantErr: errVerifySignatureFailed, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := pubKey.Verify(tt.sigBytes, hash) + assert.ErrorIs(t, err, tt.wantErr) + }) + } +} + +func TestSecp256k1PublicKey_Address(t *testing.T) { + key := "04812bef44f6e7b2a19c0b01c2dca5e54ba1935a1890ffdcb93abd0c534b209c21e4f6176823fef493f7b5afaa456f31d5293363d8f801c540ebcc061812890cba" + keyBytes, err := hex.DecodeString(key) + require.NoError(t, err, "error decode key") + + pubKey, err := Secp256k1PublicKeyFromBytes(keyBytes) + require.NoError(t, err, "error parse public key") + + eq := pubKey.Address().String() == "0xc89D42189f0450C2b2c3c61f58Ec5d628176A1E7" + require.True(t, eq, "mismatch address") +} diff --git a/pkg/crypto/signature.go b/pkg/crypto/signature.go index 3aebcf010..f3da95e8c 100644 --- a/pkg/crypto/signature.go +++ b/pkg/crypto/signature.go @@ -2,6 +2,9 @@ package crypto import ( "fmt" + "strings" + + ethAccount "github.com/ethereum/go-ethereum/accounts" ) type SignatureType int32 @@ -9,14 +12,51 @@ type SignatureType int32 const ( SIGNATURE_TYPE_INVALID SignatureType = iota SIGNATURE_TYPE_EMPTY + SIGNATURE_TYPE_SECP256K1_COMETBFT SIGNATURE_TYPE_ED25519 + SIGNATURE_TYPE_SECP256K1_PERSONAL // ethereum EIP-191 personal_sign END_SIGNATURE_TYPE ) +const ( + SIGNATURE_SECP256K1_COMETBFT_LENGTH = 64 + SIGNATURE_SECP256K1_PERSONAL_LENGTH = 65 + SIGNATURE_ED25519_LENGTH = 64 +) + +var SignatureTypeNames = [...]string{ + "invalid", + "empty", + "secp256k1_ct", + "ed25519", + "secp256k1_ep", + "invalid", +} + +var SignatureTypeFromName = map[string]SignatureType{ + "secp256k1_ct": SIGNATURE_TYPE_SECP256K1_COMETBFT, // secp256k1 cometbft + "ed25519": SIGNATURE_TYPE_ED25519, // ed25519 standard + "secp256k1_ep": SIGNATURE_TYPE_SECP256K1_PERSONAL, // secp256k1 ethereum personal_sign +} + +var ( + errInvalidSignature = fmt.Errorf("invalid signature") + errVerifySignatureFailed = fmt.Errorf("verify signature failed") + errNotSupportedSignatureType = fmt.Errorf("not supported signature type") +) + +func SignatureLookUp(name string) SignatureType { + name = strings.ToLower(name) + if t, ok := SignatureTypeFromName[name]; ok { + return t + } + return SIGNATURE_TYPE_INVALID +} + // IsValid returns an error if the signature type is invalid. -func (s *SignatureType) IsValid() error { - if *s < SIGNATURE_TYPE_INVALID || *s >= END_SIGNATURE_TYPE { - return fmt.Errorf("invalid signature type '%d'", *s) +func (s SignatureType) IsValid() error { + if s <= SIGNATURE_TYPE_INVALID || s >= END_SIGNATURE_TYPE { + return fmt.Errorf("%w: %s", errNotSupportedSignatureType, s.String()) } return nil } @@ -26,13 +66,59 @@ func (s SignatureType) Int32() int32 { return int32(s) } +func (s SignatureType) KeyType() KeyType { + switch s { + case SIGNATURE_TYPE_SECP256K1_COMETBFT, SIGNATURE_TYPE_SECP256K1_PERSONAL: + return Secp256k1 + case SIGNATURE_TYPE_ED25519: + return Ed25519 + default: + return UnknownKeyType + } +} + +func (s SignatureType) String() string { + if s <= SIGNATURE_TYPE_INVALID || s >= END_SIGNATURE_TYPE { + return "invalid" + } + return SignatureTypeNames[s] +} + // Signature is a cryptographic signature. type Signature struct { Signature []byte `json:"signature_bytes"` Type SignatureType `json:"signature_type"` } +func (s *Signature) KeyType() KeyType { + return s.Type.KeyType() +} + // Verify verifies the signature against the given public key and data. -func (s *Signature) Verify(publicKey PublicKey, data []byte) error { - return publicKey.Verify(s, data) +func (s *Signature) Verify(publicKey PublicKey, msg []byte) error { + switch s.Type { + case SIGNATURE_TYPE_SECP256K1_PERSONAL: + if len(s.Signature) != SIGNATURE_SECP256K1_PERSONAL_LENGTH { + return errInvalidSignature + } + hash := ethAccount.TextHash(msg) + // Remove recovery ID + sig := s.Signature[:len(s.Signature)-1] + return publicKey.Verify(sig, hash) + case SIGNATURE_TYPE_SECP256K1_COMETBFT: + if len(s.Signature) != SIGNATURE_SECP256K1_COMETBFT_LENGTH { + return errInvalidSignature + } + // cometbft using sha256 and 64 bytes signature(no recovery ID 'v') + hash := Sha256(msg) + return publicKey.Verify(s.Signature, hash) + case SIGNATURE_TYPE_ED25519: + if len(s.Signature) != SIGNATURE_ED25519_LENGTH { + return errInvalidSignature + } + // hash(sha512) is handled by downstream library + return publicKey.Verify(s.Signature, msg) + default: + return fmt.Errorf("%w: %d", errNotSupportedSignatureType, s.Type) + } } diff --git a/pkg/crypto/signature_test.go b/pkg/crypto/signature_test.go new file mode 100644 index 000000000..3f59b4698 --- /dev/null +++ b/pkg/crypto/signature_test.go @@ -0,0 +1,178 @@ +package crypto + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSignature_Verify(t *testing.T) { + msg := []byte("foo") + anotherMsg := []byte("bar") + + // secp256k1 + secp256k1PubKeyHex := "04812bef44f6e7b2a19c0b01c2dca5e54ba1935a1890ffdcb93abd0c534b209c21e4f6176823fef493f7b5afaa456f31d5293363d8f801c540ebcc061812890cba" + secp256k1PubKeyBytes, _ := hex.DecodeString(secp256k1PubKeyHex) + secp256k1PublicKey, _ := Secp256k1PublicKeyFromBytes(secp256k1PubKeyBytes) + + personalSignSig := "cb3fed7f6ff36e59054c04a831b215e514052753ee353e6fe31d4b4ef736acd6155127db555d3006ba14fcb4c79bbad56c8e63b81a9896319bb053a9e253475800" + personalSignSigBytes, _ := hex.DecodeString(personalSignSig) + + cometbftSecp256k1Sig := "19a4aced02d5b9142b4f622b06442b1904445e16bd25409e6b0ff357ccc021d001d0e7824654b695b4b6e0991cb7507f487b82be4b2ed713d1e3e2cbc3d2518d" + cometbftSecp256k1SigBytes, _ := hex.DecodeString(cometbftSecp256k1Sig) + + // ed25519 + ed25519PubKeyHex := "0aa611bf555596912bc6f9a9f169f8785918e7bab9924001895798ff13f05842" + ed25519PubKeyBytes, _ := hex.DecodeString(ed25519PubKeyHex) + ed25519PublicKey, _ := Ed25519PublicKeyFromBytes(ed25519PubKeyBytes) + ed25519Sig := "59b2db2d1e4ce6f8771453cfc78d1f943723528f00fa14adf574600f15c601d591fa2ba29c94d9ed694db324f9e8671bdfbcba4b8e10f6a8733682fa3d115f0c" + ed25519SigBytes, _ := hex.DecodeString(ed25519Sig) + + type fields struct { + Signature []byte + Type SignatureType + } + type args struct { + publicKey PublicKey + msg []byte + } + + tests := []struct { + name string + fields fields + args args + wantErr error + }{ + { + name: "test secp256k1 personal_sign", + fields: fields{ + Signature: personalSignSigBytes, + Type: SIGNATURE_TYPE_SECP256K1_PERSONAL, + }, + args: args{ + publicKey: secp256k1PublicKey, + msg: msg, + }, + wantErr: nil, + }, + { + name: "test secp256k1 personal_sign invalid signature", + fields: fields{ + Signature: personalSignSigBytes[1:], + Type: SIGNATURE_TYPE_SECP256K1_PERSONAL, + }, + args: args{ + publicKey: secp256k1PublicKey, + msg: msg, + }, + wantErr: errInvalidSignature, + }, + { + name: "test secp256k1 personal_sign wrong signature", + fields: fields{ + Signature: personalSignSigBytes, + Type: SIGNATURE_TYPE_SECP256K1_PERSONAL, + }, + args: args{ + publicKey: secp256k1PublicKey, + msg: anotherMsg, + }, + wantErr: errVerifySignatureFailed, + }, + { + name: "test secp256k1 cometbft", + fields: fields{ + Signature: cometbftSecp256k1SigBytes, + Type: SIGNATURE_TYPE_SECP256K1_COMETBFT, + }, + args: args{ + publicKey: secp256k1PublicKey, + msg: msg, + }, + wantErr: nil, + }, + { + name: "test secp256k1 cometbft invalid signature", + fields: fields{ + Signature: cometbftSecp256k1SigBytes[1:], + Type: SIGNATURE_TYPE_SECP256K1_COMETBFT, + }, + args: args{ + publicKey: secp256k1PublicKey, + msg: msg, + }, + wantErr: errInvalidSignature, + }, + { + name: "test secp256k1 cometbft wrong signature", + fields: fields{ + Signature: cometbftSecp256k1SigBytes, + Type: SIGNATURE_TYPE_SECP256K1_COMETBFT, + }, + args: args{ + publicKey: secp256k1PublicKey, + msg: anotherMsg, + }, + wantErr: errVerifySignatureFailed, + }, + { + name: "ed25519", + fields: fields{ + Signature: ed25519SigBytes, + Type: SIGNATURE_TYPE_ED25519, + }, + args: args{ + publicKey: ed25519PublicKey, + msg: msg, + }, + wantErr: nil, + }, + { + name: "ed25519 invalid signature", + fields: fields{ + Signature: ed25519SigBytes[1:], + Type: SIGNATURE_TYPE_ED25519, + }, + args: args{ + publicKey: ed25519PublicKey, + msg: msg, + }, + wantErr: errInvalidSignature, + }, + { + name: "ed25519 wrong signature", + fields: fields{ + Signature: ed25519SigBytes, + Type: SIGNATURE_TYPE_ED25519, + }, + args: args{ + publicKey: ed25519PublicKey, + msg: anotherMsg, + }, + wantErr: errVerifySignatureFailed, + }, + { + name: "unsupported signature type", + fields: fields{ + Signature: nil, + Type: SIGNATURE_TYPE_INVALID, + }, + args: args{ + publicKey: secp256k1PublicKey, + msg: msg, + }, + wantErr: errNotSupportedSignatureType, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Signature{ + Signature: tt.fields.Signature, + Type: tt.fields.Type, + } + err := s.Verify(tt.args.publicKey, tt.args.msg) + assert.ErrorIs(t, err, tt.wantErr) + }) + } +} diff --git a/pkg/crypto/signer.go b/pkg/crypto/signer.go new file mode 100644 index 000000000..8547b029f --- /dev/null +++ b/pkg/crypto/signer.go @@ -0,0 +1,26 @@ +package crypto + +type Signer interface { + SignMsg(msg []byte) (*Signature, error) + PubKey() PublicKey +} + +type ComebftSecp256k1Signer struct { + key *Secp256k1PrivateKey +} + +func (c *ComebftSecp256k1Signer) PublicKey() PublicKey { + return c.key.PubKey() +} + +func (c *ComebftSecp256k1Signer) SignMsg(msg []byte) (*Signature, error) { + hash := Sha256(msg) + sig, err := c.key.Sign(hash) + if err != nil { + return nil, err + } + return &Signature{ + Signature: sig[:len(sig)-1], + Type: SIGNATURE_TYPE_SECP256K1_COMETBFT, + }, nil +} diff --git a/pkg/crypto/signer_test.go b/pkg/crypto/signer_test.go new file mode 100644 index 000000000..1bf1c295f --- /dev/null +++ b/pkg/crypto/signer_test.go @@ -0,0 +1,31 @@ +package crypto + +import ( + "encoding/hex" + "testing" + + "github.com/cometbft/cometbft/crypto/secp256k1" + "github.com/stretchr/testify/assert" +) + +func TestComebftSecp256k1Signer_SignMsg(t *testing.T) { + msg := []byte("foo") + + pvKeyHex := "f1aa5a7966c3863ccde3047f6a1e266cdc0c76b399e256b8fede92b1c69e4f4e" + sigHex := "19a4aced02d5b9142b4f622b06442b1904445e16bd25409e6b0ff357ccc021d001d0e7824654b695b4b6e0991cb7507f487b82be4b2ed713d1e3e2cbc3d2518d" + + pvKeyBytes, _ := hex.DecodeString(pvKeyHex) + cometBftSecp256k1Key := secp256k1.PrivKey(pvKeyBytes) + cometBfgSecp256k1Sig, err := cometBftSecp256k1Key.Sign(msg) + assert.NoError(t, err, "error signing message") + assert.Equal(t, sigHex, hex.EncodeToString(cometBfgSecp256k1Sig), "signature mismatch") + + // use the cometbft signer to sign the message + kwilCometBftKey, _ := Secp256k1PrivateKeyFromHex(pvKeyHex) + cometBfgSigner := &ComebftSecp256k1Signer{ + key: kwilCometBftKey, + } + kwilCometBftKeySig, err := cometBfgSigner.SignMsg(msg) + assert.NoError(t, err, "error signing message") + assert.Equal(t, sigHex, hex.EncodeToString(kwilCometBftKeySig.Signature), "signature mismatch") +} diff --git a/pkg/grpc/client/v1/convert.go b/pkg/grpc/client/v1/convert.go index 185ab096d..e610e3c4c 100644 --- a/pkg/grpc/client/v1/convert.go +++ b/pkg/grpc/client/v1/convert.go @@ -19,7 +19,7 @@ func convertTx(incoming *transactions.Transaction) *txpb.Transaction { Salt: incoming.Body.Salt, }, Signature: convertActionSignature(incoming.Signature), - Sender: incoming.Sender.Bytes(), + Sender: incoming.Sender, } } @@ -30,7 +30,7 @@ func convertActionSignature(oldSig *crypto.Signature) *txpb.Signature { newSig := &txpb.Signature{ SignatureBytes: oldSig.Signature, - SignatureType: oldSig.Type.Int32(), + SignatureType: oldSig.Type.String(), } return newSig diff --git a/pkg/modules/datasets/execution.go b/pkg/modules/datasets/execution.go index 8837ce5ad..6ccfd2dc4 100644 --- a/pkg/modules/datasets/execution.go +++ b/pkg/modules/datasets/execution.go @@ -59,7 +59,13 @@ func (u *DatasetModule) Drop(ctx context.Context, dbid string, tx *transactions. return failure(tx, price, err) } - err = u.engine.DropDataset(ctx, tx.Sender.Address().String(), dbid) + senderPubKey, err := tx.GetSenderPubKey() + // NOTE: This should never happen, since the transaction is already validated + if err != nil { + return failure(tx, price, fmt.Errorf("failed to parse sender: %w", err)) + } + + err = u.engine.DropDataset(ctx, senderPubKey.Address().String(), dbid) if err != nil { return failure(tx, price, fmt.Errorf("failed to drop dataset: %w", err)) } @@ -83,8 +89,14 @@ func (u *DatasetModule) Execute(ctx context.Context, dbid string, action string, return failure(tx, price, err) } + senderPubKey, err := tx.GetSenderPubKey() + // NOTE: This should never happen, since the transaction is already validated + if err != nil { + return failure(tx, price, fmt.Errorf("failed to parse sender: %w", err)) + } + _, err = u.engine.Execute(ctx, dbid, action, args, - engine.WithCaller(tx.Sender.Address().String()), + engine.WithCaller(senderPubKey.Address().String()), ) if err != nil { return failure(tx, price, fmt.Errorf("failed to execute action: %w", err)) @@ -95,13 +107,18 @@ func (u *DatasetModule) Execute(ctx context.Context, dbid string, action string, // compareAndSpend compares the calculated price to the transaction's fee, and spends the price if the fee is sufficient. func (u *DatasetModule) compareAndSpend(ctx context.Context, price *big.Int, tx *transactions.Transaction) error { - if tx.Body.Fee.Cmp(price) < 0 { return fmt.Errorf(`%w: fee %s is less than price %s`, ErrInsufficientFee, tx.Body.Fee.String(), price.String()) } + senderPubKey, err := tx.GetSenderPubKey() + // NOTE: This should never happen, since the transaction is already validated + if err != nil { + return fmt.Errorf("failed to parse sender: %w", err) + } + return u.accountStore.Spend(ctx, &balances.Spend{ - AccountAddress: tx.Sender.Address().String(), + AccountAddress: senderPubKey.Address().String(), Amount: price, Nonce: int64(tx.Body.Nonce), }) diff --git a/pkg/transactions/message.go b/pkg/transactions/message.go index 8f1de68bd..daedfab74 100644 --- a/pkg/transactions/message.go +++ b/pkg/transactions/message.go @@ -36,16 +36,16 @@ type SignedMessage struct { // it to the message in the signature. // It then uses the public key in the signature to verify the signature. func (s *SignedMessage) Verify() error { - return s.Sender.Verify(s.Signature, s.Message) + return s.Signature.Verify(s.Sender, s.Message) } // Sign signs a message with a private key. -func (s *SignedMessage) Sign(privateKey crypto.PrivateKey) error { - signature, err := privateKey.Sign(s.Message) +func (s *SignedMessage) Sign(signer crypto.Signer) error { + signature, err := signer.SignMsg(s.Message) if err != nil { return err } s.Signature = signature - s.Sender = privateKey.PubKey() + s.Sender = signer.PubKey() return nil } diff --git a/pkg/transactions/transaction.go b/pkg/transactions/transaction.go index 5ffabc24c..ca87fc0ac 100644 --- a/pkg/transactions/transaction.go +++ b/pkg/transactions/transaction.go @@ -43,12 +43,16 @@ type Transaction struct { // Sender is the public key of the sender // It is not included in the signature - Sender crypto.PublicKey + Sender []byte // hash of the transaction that is signed. it is kept here as a cache hash []byte } +func (t *Transaction) GetSenderPubKey() (crypto.PublicKey, error) { + return crypto.PublicKeyFromBytes(t.Signature.KeyType(), t.Sender) +} + // Verify verifies the signature of the transaction func (t *Transaction) Verify() error { data, err := t.Body.MarshalBinary() @@ -56,23 +60,28 @@ func (t *Transaction) Verify() error { return err } - return t.Sender.Verify(t.Signature, data) + var pubKey crypto.PublicKey + pubKey, err = crypto.PublicKeyFromBytes(t.Signature.KeyType(), t.Sender) + if err != nil { + return err + } + + return t.Signature.Verify(pubKey, data) } -func (t *Transaction) Sign(privateKey crypto.PrivateKey) error { +func (t *Transaction) Sign(signer crypto.Signer) error { data, err := t.Body.MarshalBinary() if err != nil { return err } - // TODO: we need to figure out if we hash the payload before or after signing - signature, err := privateKey.Sign(data) + signature, err := signer.SignMsg(data) if err != nil { return err } t.Signature = signature - t.Sender = privateKey.PubKey() + t.Sender = signer.PubKey().Bytes() return nil } diff --git a/proto b/proto index c9de21008..1c2f19128 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit c9de21008bf3a44896bf2924d7037a5492f76141 +Subproject commit 1c2f19128cb333578647a5cb2d26c18d77a34a8b diff --git a/test/acceptance/helper.go b/test/acceptance/helper.go index 1ca79cf87..2063633ec 100644 --- a/test/acceptance/helper.go +++ b/test/acceptance/helper.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "testing" + "time" "github.com/kwilteam/kwil-db/internal/app/kwild" "github.com/kwilteam/kwil-db/internal/pkg/nodecfg" @@ -18,6 +19,8 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) +const DefaultContainerWaitTimeout = 10 * time.Second + type ActTestCfg struct { GWEndpoint string // gateway endpoint GrpcEndpoint string @@ -30,8 +33,8 @@ type ActTestCfg struct { AliceRawPK string // Alice is the owner BobRawPK string - AlicePK crypto.PrivateKey - BobPk crypto.PrivateKey + AlicePK crypto.Signer + BobPk crypto.Signer } func (e *ActTestCfg) AliceAddr() string { @@ -103,13 +106,13 @@ func (r *ActHelper) LoadConfig() { } var err error - cfg.AlicePK, err = crypto.PrivateKeyFromHex(cfg.AliceRawPK) + cfg.AlicePK, err = crypto.Secp256k1PrivateKeyFromHex(cfg.AliceRawPK) require.NoError(r.t, err, "invalid alice private key") //if err != nil { // return nil, fmt.Errorf("invalid alice private key: %v", err) //} - cfg.BobPk, err = crypto.PrivateKeyFromHex(cfg.BobRawPK) + cfg.BobPk, err = crypto.Secp256k1PrivateKeyFromHex(cfg.BobRawPK) require.NoError(r.t, err, "invalid bob private key") r.cfg = cfg @@ -145,11 +148,6 @@ func (r *ActHelper) runDockerCompose(ctx context.Context) { dc, err := compose.NewDockerCompose(r.cfg.DockerComposeFile) require.NoError(r.t, err, "failed to create docker compose object for single kwild node") - err = dc. - WithEnv(envs). - WaitForService("kwild", wait.NewLogStrategy("grpc server started")). - WaitForService("ext", wait.NewLogStrategy("listening on")). - Up(ctx) r.teardown = append(r.teardown, func() { r.t.Logf("teardown docker compose") @@ -160,6 +158,17 @@ func (r *ActHelper) runDockerCompose(ctx context.Context) { r.Teardown() }) + err = dc. + WithEnv(envs). + WaitForService( + "ext", + wait.NewLogStrategy("listening on").WithStartupTimeout(DefaultContainerWaitTimeout)). + WaitForService( + "kwild", + wait.NewLogStrategy("grpc server started").WithStartupTimeout(DefaultContainerWaitTimeout)). + Up(ctx) + r.t.Log("docker compose up") + require.NoError(r.t, err, "failed to start kwild node") } @@ -178,7 +187,7 @@ func (r *ActHelper) Teardown() { func (r *ActHelper) GetAliceDriver(ctx context.Context) KwilAcceptanceDriver { kwilClt, err := client.New(ctx, r.cfg.GrpcEndpoint, - client.WithPrivateKey(r.cfg.AlicePK), + client.WithSigner(r.cfg.AlicePK), client.WithCometBftUrl(r.cfg.ChainEndpoint), ) require.NoError(r.t, err, "failed to create kwil client") @@ -188,7 +197,7 @@ func (r *ActHelper) GetAliceDriver(ctx context.Context) KwilAcceptanceDriver { func (r *ActHelper) GetBobDriver(ctx context.Context) KwilAcceptanceDriver { kwilClt, err := client.New(ctx, r.cfg.GrpcEndpoint, - client.WithPrivateKey(r.cfg.BobPk), + client.WithSigner(r.cfg.BobPk), client.WithCometBftUrl(r.cfg.ChainEndpoint), ) require.NoError(r.t, err, "failed to create kwil client") diff --git a/test/integration/helper.go b/test/integration/helper.go index 59af5f455..7d86ffc2d 100644 --- a/test/integration/helper.go +++ b/test/integration/helper.go @@ -6,6 +6,7 @@ import ( "os" "strconv" "testing" + "time" "github.com/kwilteam/kwil-db/internal/app/kwild" "github.com/kwilteam/kwil-db/internal/pkg/nodecfg" @@ -21,6 +22,8 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) +const DefaultContainerWaitTimeout = 10 * time.Second + type IntTestConfig struct { acceptance.ActTestCfg @@ -31,7 +34,7 @@ type IntTestConfig struct { //// this can be used to simulate several "wallets" in the same test //func newGRPCClient(ctx context.Context, t *testing.T, cfg *IntTestConfig) KwilIntDriver { // kwilClt, err := client.New(ctx, cfg.GrpcEndpoint, -// client.WithPrivateKey(cfg.AlicePK), +// client.WithSigner(cfg.AlicePK), // client.WithCometBftUrl(cfg.ChainEndpoint), // ) // require.NoError(t, err, "failed to create kwil client") @@ -87,10 +90,10 @@ func (r *IntHelper) LoadConfig() { cfg.NValidator, err = strconv.Atoi(nodeNum) require.NoError(r.t, err, "invalid node number") - cfg.AlicePK, err = crypto.PrivateKeyFromHex(cfg.AliceRawPK) + cfg.AlicePK, err = crypto.Secp256k1PrivateKeyFromHex(cfg.AliceRawPK) require.NoError(r.t, err, "invalid alice private key") - cfg.BobPk, err = crypto.PrivateKeyFromHex(cfg.BobRawPK) + cfg.BobPk, err = crypto.Secp256k1PrivateKeyFromHex(cfg.BobRawPK) require.NoError(r.t, err, "invalid bob private key") r.cfg = cfg @@ -134,13 +137,6 @@ func (r *IntHelper) runDockerCompose(ctx context.Context) { dc, err := compose.NewDockerCompose(r.cfg.DockerComposeFile) require.NoError(r.t, err, "failed to create docker compose object for kwild cluster") - err = dc. - WithEnv(envs). - WaitForService("ext1", wait.NewLogStrategy("listening on")). - WaitForService("k1", wait.NewLogStrategy("grpc server started")). - WaitForService("k2", wait.NewLogStrategy("grpc server started")). - WaitForService("k3", wait.NewLogStrategy("grpc server started")). - Up(ctx) r.teardown = append(r.teardown, func() { r.t.Log("teardown docker compose") @@ -151,6 +147,19 @@ func (r *IntHelper) runDockerCompose(ctx context.Context) { r.Teardown() }) + err = dc. + WithEnv(envs). + WaitForService("ext1", + wait.NewLogStrategy("listening on").WithStartupTimeout(DefaultContainerWaitTimeout)). + WaitForService("k1", + wait.NewLogStrategy("grpc server started").WithStartupTimeout(DefaultContainerWaitTimeout)). + WaitForService("k2", + wait.NewLogStrategy("grpc server started").WithStartupTimeout(DefaultContainerWaitTimeout)). + WaitForService("k3", + wait.NewLogStrategy("grpc server started").WithStartupTimeout(DefaultContainerWaitTimeout)). + Up(ctx) + r.t.Log("docker compose up") + require.NoError(r.t, err, "failed to start kwild cluster") serviceNames := dc.Services() @@ -190,7 +199,7 @@ func (r *IntHelper) getDriver(ctx context.Context, ctr *testcontainers.DockerCon r.t.Logf("nodeURL: %s gatewayURL: %s for container name: %s", nodeURL, gatewayURL, name) kwilClt, err := client.New(ctx, r.cfg.GrpcEndpoint, - client.WithPrivateKey(r.cfg.AlicePK), + client.WithSigner(r.cfg.AlicePK), client.WithCometBftUrl(r.cfg.ChainEndpoint), ) require.NoError(r.t, err, "failed to create kwil client") diff --git a/test/integration/kwild_test.go b/test/integration/kwild_test.go index 7bc78ad8b..240dd4d60 100644 --- a/test/integration/kwild_test.go +++ b/test/integration/kwild_test.go @@ -14,8 +14,6 @@ import ( "github.com/kwilteam/kwil-db/test/specifications" ) -// @yaiba remove unused remote? -var remote = flag.Bool("remote", false, "run tests against remote environment") //nolint:unused var dev = flag.Bool("dev", false, "run for development purpose (no tests)") func TestKwildDatabaseIntegration(t *testing.T) {