Skip to content

Commit

Permalink
feat: support secp256k1 key (#187)
Browse files Browse the repository at this point in the history
* feat: support secp256k1 key

* support ed25519 key (#191)

* support ed25519 key

* chore: use Signer instead of PrivateKey

* change signature type to string in API

* change `Transaction.Sender` to `[]byte`

* chore: run linter

* chore: update func docs
  • Loading branch information
Yaiba authored and brennanjl committed Feb 26, 2024
1 parent 3326825 commit e205cba
Show file tree
Hide file tree
Showing 31 changed files with 926 additions and 171 deletions.
19 changes: 12 additions & 7 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
3 changes: 1 addition & 2 deletions api/openapi-spec/api/v1/api.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -768,8 +768,7 @@
"format": "byte"
},
"signature_type": {
"type": "integer",
"format": "int32"
"type": "string"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion cmd/kwil-cli/cmds/common/roundtripper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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 == "" {
Expand Down
2 changes: 1 addition & 1 deletion cmd/kwil-cli/cmds/configure/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
2 changes: 1 addition & 1 deletion cmd/kwil-cli/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
34 changes: 0 additions & 34 deletions internal/app/kwild/client/client.go

This file was deleted.

2 changes: 1 addition & 1 deletion internal/app/kwild/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 13 additions & 3 deletions internal/controller/grpc/txsvc/v1/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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
}
Expand Down
9 changes: 2 additions & 7 deletions internal/controller/grpc/txsvc/v1/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -45,7 +40,7 @@ func convertTransaction(incoming *txpb.Transaction) (*transactions.Transaction,
Fee: bigFee,
Salt: incoming.Body.Salt,
},
Sender: sender,
Sender: incoming.Sender,
}, nil
}

Expand All @@ -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
}
Expand Down
12 changes: 6 additions & 6 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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)
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/client/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/client/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
67 changes: 57 additions & 10 deletions pkg/crypto/crypto.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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)
}
}
Loading

0 comments on commit e205cba

Please sign in to comment.