diff --git a/pkg/keys/v2/core_keystore.go b/pkg/keys/v2/core_keystore.go new file mode 100644 index 0000000000..0bf207843d --- /dev/null +++ b/pkg/keys/v2/core_keystore.go @@ -0,0 +1,74 @@ +package keys + +import ( + "context" + "errors" + + "github.com/smartcontractkit/chainlink-common/keystore" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" +) + +var _ core.Keystore = &TxKeyCoreKeystore{} + +type TxKeyCoreKeystore struct { + ks keystore.Keystore + cache map[string]string +} + +// NewTxKeyCoreKeystore creates a new CoreKeystore for transaction keys. +// This wrapper is required for using TxKeys with the txm +// which requires address based lookups. +func NewTxKeyCoreKeystore(ks keystore.Keystore) *TxKeyCoreKeystore { + return &TxKeyCoreKeystore{ + ks: ks, + cache: make(map[string]string), + } +} + +func (s *TxKeyCoreKeystore) Accounts(ctx context.Context) ([]string, error) { + keys, err := GetTxKeys(ctx, s.ks, []string{}) + if err != nil { + return nil, err + } + accounts := make([]string, 0, len(keys)) + for _, key := range keys { + accounts = append(accounts, key.Address().String()) + } + return accounts, nil +} + +func (s *TxKeyCoreKeystore) Sign(ctx context.Context, account string, data []byte) ([]byte, error) { + if addr, ok := s.cache[account]; ok { + resp, err := s.ks.Sign(ctx, keystore.SignRequest{ + KeyName: addr, + Data: data, + }) + if err != nil { + return nil, err + } + return resp.Signature, nil + } + // Otherwise do the first time lookup to find the key by address. + keys, err := GetTxKeys(ctx, s.ks, []string{}) + if err != nil { + return nil, err + } + if len(keys) == 0 { + return nil, errors.New("no keys found") + } + for _, key := range keys { + if key.Address().String() == account { + s.cache[account] = key.KeyPath().String() + resp, err := key.SignRaw(ctx, SignRawDataRequest{Data: data}) + if err != nil { + return nil, err + } + return resp.Signature, nil + } + } + return nil, errors.New("key not found") +} + +func (s *TxKeyCoreKeystore) Decrypt(ctx context.Context, account string, data []byte) ([]byte, error) { + return nil, errors.New("not implemented") +} diff --git a/pkg/keys/v2/core_keystore_test.go b/pkg/keys/v2/core_keystore_test.go new file mode 100644 index 0000000000..4eecae651b --- /dev/null +++ b/pkg/keys/v2/core_keystore_test.go @@ -0,0 +1,64 @@ +package keys + +import ( + "context" + "os" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/smartcontractkit/chainlink-common/keystore" + "github.com/stretchr/testify/require" +) + +func TestCoreKeystore(t *testing.T) { + ctx := context.Background() + tmpfile, err := os.CreateTemp("", "keystore.json") + require.NoError(t, err) + defer os.Remove(tmpfile.Name()) + ks, err := keystore.LoadKeystore(ctx, keystore.NewFileStorage(tmpfile.Name()), "password") + require.NoError(t, err) + + coreKs := NewTxKeyCoreKeystore(ks) + accounts, err := coreKs.Accounts(ctx) + require.NoError(t, err) + require.Len(t, accounts, 0) + + txKey, err := CreateTxKey(ks, "key1") + require.NoError(t, err) + + keys, err := ks.GetKeys(ctx, keystore.GetKeysRequest{ + KeyNames: []string{txKey.KeyPath().String()}, + }) + require.NoError(t, err) + require.Len(t, keys.Keys, 1) + + data := crypto.Keccak256([]byte("data")) + signature, err := coreKs.Sign(ctx, txKey.Address().String(), data) + require.NoError(t, err) + require.NotNil(t, signature) + + resp, err := ks.Verify(ctx, keystore.VerifyRequest{ + KeyType: keystore.ECDSA_S256, + PublicKey: keys.Keys[0].KeyInfo.PublicKey, + Data: data, + Signature: signature, + }) + require.NoError(t, err) + require.True(t, resp.Valid) + + // Make sure the cache populated. + require.Equal(t, txKey.KeyPath().String(), coreKs.cache[txKey.Address().String()]) + + // Sign again with cached. + signature, err = coreKs.Sign(ctx, txKey.Address().String(), data) + require.NoError(t, err) + require.NotNil(t, signature) + resp, err = ks.Verify(ctx, keystore.VerifyRequest{ + KeyType: keystore.ECDSA_S256, + PublicKey: keys.Keys[0].KeyInfo.PublicKey, + Data: data, + Signature: signature, + }) + require.NoError(t, err) + require.True(t, resp.Valid) +} diff --git a/pkg/keys/v2/evm_tx.go b/pkg/keys/v2/evm_tx.go index b77169531f..92f03f5353 100644 --- a/pkg/keys/v2/evm_tx.go +++ b/pkg/keys/v2/evm_tx.go @@ -38,6 +38,16 @@ type SignTxResponse struct { Tx *gethtypes.Transaction } +// SignRawDataRequest contains the request to sign raw data. +type SignRawDataRequest struct { + Data []byte +} + +// SignRawDataResponse contains the signed raw data. +type SignRawDataResponse struct { + Signature []byte +} + // KeyPath returns the key path for this transaction key. func (k *TxKey) KeyPath() keystore.KeyPath { return k.keyPath @@ -70,6 +80,18 @@ func (k *TxKey) SignTx(ctx context.Context, req SignTxRequest) (SignTxResponse, return SignTxResponse{Tx: req.Tx}, nil } +func (k *TxKey) SignRaw(ctx context.Context, req SignRawDataRequest) (SignRawDataResponse, error) { + signReq := keystore.SignRequest{ + KeyName: k.keyPath.String(), + Data: req.Data, + } + signResp, err := k.ks.Sign(ctx, signReq) + if err != nil { + return SignRawDataResponse{}, err + } + return SignRawDataResponse{Signature: signResp.Signature}, nil +} + // GetTransactOpts returns transaction options for this key. func (k *TxKey) GetTransactOpts(ctx context.Context, chainID *big.Int) (*bind.TransactOpts, error) { if chainID == nil {