Skip to content
This repository was archived by the owner on May 9, 2024. It is now read-only.

Commit 76d0d57

Browse files
authored
sign transactions using a signer interface instead of a private key (#328)
* sign transactions using a signer interface instead of a private key * add mocks * remove PrivateKey method from secp256k1.Keypair
1 parent e65c87a commit 76d0d57

File tree

15 files changed

+174
-54
lines changed

15 files changed

+174
-54
lines changed

chains/evm/calls/evmclient/evm-client.go

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@ package evmclient
22

33
import (
44
"context"
5-
"crypto/ecdsa"
65
"encoding/json"
76
"errors"
87
"fmt"
98
"math/big"
109
"sync"
1110
"time"
1211

13-
"github.com/ChainSafe/chainbridge-core/crypto/secp256k1"
14-
1512
"github.com/ethereum/go-ethereum"
1613
"github.com/ethereum/go-ethereum/common"
1714
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -24,24 +21,31 @@ import (
2421

2522
type EVMClient struct {
2623
*ethclient.Client
27-
kp *secp256k1.Keypair
24+
signer Signer
2825
gethClient *gethclient.Client
2926
rpClient *rpc.Client
3027
nonce *big.Int
3128
nonceLock sync.Mutex
3229
}
3330

31+
type Signer interface {
32+
CommonAddress() common.Address
33+
34+
// Sign calculates an ECDSA signature.
35+
// The produced signature must be in the [R || S || V] format where V is 0 or 1.
36+
Sign(digestHash []byte) ([]byte, error)
37+
}
38+
3439
type CommonTransaction interface {
3540
// Hash returns the transaction hash.
3641
Hash() common.Hash
3742

38-
// RawWithSignature Returns signed transaction by provided private key
39-
RawWithSignature(key *ecdsa.PrivateKey, domainID *big.Int) ([]byte, error)
43+
// RawWithSignature Returns signed transaction by provided signer
44+
RawWithSignature(signer Signer, domainID *big.Int) ([]byte, error)
4045
}
4146

42-
// NewEVMClient creates a client for EVMChain with provided
43-
// private key.
44-
func NewEVMClient(url string, privateKey *ecdsa.PrivateKey) (*EVMClient, error) {
47+
// NewEVMClient creates a client for EVMChain with provided signer
48+
func NewEVMClient(url string, signer Signer) (*EVMClient, error) {
4549
rpcClient, err := rpc.DialContext(context.TODO(), url)
4650
if err != nil {
4751
return nil, err
@@ -50,7 +54,7 @@ func NewEVMClient(url string, privateKey *ecdsa.PrivateKey) (*EVMClient, error)
5054
c.Client = ethclient.NewClient(rpcClient)
5155
c.gethClient = gethclient.New(rpcClient)
5256
c.rpClient = rpcClient
53-
c.kp = secp256k1.NewKeypair(*privateKey)
57+
c.signer = signer
5458
return c, nil
5559
}
5660

@@ -160,17 +164,17 @@ func (c *EVMClient) PendingCallContract(ctx context.Context, callArgs map[string
160164
}
161165

162166
func (c *EVMClient) From() common.Address {
163-
return c.kp.CommonAddress()
167+
return c.signer.CommonAddress()
164168
}
165169

166170
func (c *EVMClient) SignAndSendTransaction(ctx context.Context, tx CommonTransaction) (common.Hash, error) {
167171
id, err := c.ChainID(ctx)
168172
if err != nil {
169-
//panic(err)
173+
// panic(err)
170174
// Probably chain does not support chainID eg. CELO
171175
id = nil
172176
}
173-
rawTx, err := tx.RawWithSignature(c.kp.PrivateKey(), id)
177+
rawTx, err := tx.RawWithSignature(c.signer, id)
174178
if err != nil {
175179
return common.Hash{}, err
176180
}
@@ -182,7 +186,7 @@ func (c *EVMClient) SignAndSendTransaction(ctx context.Context, tx CommonTransac
182186
}
183187

184188
func (c *EVMClient) RelayerAddress() common.Address {
185-
return c.kp.CommonAddress()
189+
return c.signer.CommonAddress()
186190
}
187191

188192
func (c *EVMClient) LockNonce() {
@@ -197,7 +201,7 @@ func (c *EVMClient) UnsafeNonce() (*big.Int, error) {
197201
var err error
198202
for i := 0; i <= 10; i++ {
199203
if c.nonce == nil {
200-
nonce, err := c.PendingNonceAt(context.Background(), c.kp.CommonAddress())
204+
nonce, err := c.PendingNonceAt(context.Background(), c.signer.CommonAddress())
201205
if err != nil {
202206
time.Sleep(1 * time.Second)
203207
continue

chains/evm/calls/evmtransaction/evm-tx.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package evmtransaction
22

33
import (
4-
"crypto/ecdsa"
5-
"github.com/ChainSafe/chainbridge-core/chains/evm/calls/evmclient"
4+
"context"
65
"math/big"
76

7+
"github.com/ChainSafe/chainbridge-core/chains/evm/calls/evmclient"
8+
89
"github.com/ethereum/go-ethereum/accounts/abi/bind"
910
"github.com/ethereum/go-ethereum/common"
1011
"github.com/ethereum/go-ethereum/core/types"
11-
"github.com/ethereum/go-ethereum/crypto"
1212
)
1313

1414
type TX struct {
@@ -19,12 +19,12 @@ type TX struct {
1919
// but return raw byte representation of transaction to be compatible and interchangeable between different go-ethereum forks
2020
// WithSignature returns a new transaction with the given signature.
2121
// This signature needs to be in the [R || S || V] format where V is 0 or 1.
22-
func (a *TX) RawWithSignature(key *ecdsa.PrivateKey, domainID *big.Int) ([]byte, error) {
23-
opts, err := bind.NewKeyedTransactorWithChainID(key, domainID)
22+
func (a *TX) RawWithSignature(signer evmclient.Signer, domainID *big.Int) ([]byte, error) {
23+
opts, err := newTransactorWithChainID(signer, domainID)
2424
if err != nil {
2525
return nil, err
2626
}
27-
tx, err := opts.Signer(crypto.PubkeyToAddress(key.PublicKey), a.tx)
27+
tx, err := opts.Signer(signer.CommonAddress(), a.tx)
2828
if err != nil {
2929
return nil, err
3030
}
@@ -76,3 +76,29 @@ func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit
7676
func (a *TX) Hash() common.Hash {
7777
return a.tx.Hash()
7878
}
79+
80+
// newTransactorWithChainID is a utility method to easily create a transaction signer
81+
// for an evmclient.Signer.
82+
// Mostly copies bind.NewKeyedTransactorWithChainID but sings with the provided signer
83+
// instead of a privateKey
84+
func newTransactorWithChainID(s evmclient.Signer, chainID *big.Int) (*bind.TransactOpts, error) {
85+
keyAddr := s.CommonAddress()
86+
if chainID == nil {
87+
return nil, bind.ErrNoChainID
88+
}
89+
signer := types.LatestSignerForChainID(chainID)
90+
return &bind.TransactOpts{
91+
From: keyAddr,
92+
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
93+
if address != keyAddr {
94+
return nil, bind.ErrNotAuthorized
95+
}
96+
signature, err := s.Sign(signer.Hash(tx).Bytes())
97+
if err != nil {
98+
return nil, err
99+
}
100+
return tx.WithSignature(signer, signature)
101+
},
102+
Context: context.Background(),
103+
}, nil
104+
}

chains/evm/calls/evmtransaction/evm-tx_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import (
77
evmgaspricer "github.com/ChainSafe/chainbridge-core/chains/evm/calls/evmgaspricer"
88
mock_evmgaspricer "github.com/ChainSafe/chainbridge-core/chains/evm/calls/evmgaspricer/mock"
99

10-
"github.com/ChainSafe/chainbridge-core/keystore"
1110
"github.com/ethereum/go-ethereum/core/types"
1211

12+
"github.com/ChainSafe/chainbridge-core/keystore"
13+
1314
"github.com/ethereum/go-ethereum/common"
1415

1516
"github.com/golang/mock/gomock"
@@ -43,7 +44,7 @@ func (s *EVMTxTestSuite) TestNewTransactionWithStaticGasPricer() {
4344
s.Nil(err)
4445
tx, err := txFabric(1, &common.Address{}, big.NewInt(0), 10000, gp, []byte{})
4546
s.Nil(err)
46-
rawTx, err := tx.RawWithSignature(aliceKp.PrivateKey(), big.NewInt(420))
47+
rawTx, err := tx.RawWithSignature(aliceKp, big.NewInt(420))
4748
s.Nil(err)
4849
txt := types.Transaction{}
4950
err = txt.UnmarshalBinary(rawTx)
@@ -60,7 +61,7 @@ func (s *EVMTxTestSuite) TestNewTransactionWithLondonGasPricer() {
6061
s.Nil(err)
6162
tx, err := txFabric(1, &common.Address{}, big.NewInt(0), 10000, gp, []byte{})
6263
s.Nil(err)
63-
rawTx, err := tx.RawWithSignature(aliceKp.PrivateKey(), big.NewInt(420))
64+
rawTx, err := tx.RawWithSignature(aliceKp, big.NewInt(420))
6465
s.Nil(err)
6566
txt := types.Transaction{}
6667
err = txt.UnmarshalBinary(rawTx)

chains/evm/calls/transactor/itx/itx.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"math/big"
77

88
"github.com/ChainSafe/chainbridge-core/chains/evm/calls/transactor"
9-
"github.com/ChainSafe/chainbridge-core/crypto/secp256k1"
109
"github.com/ethereum/go-ethereum/accounts/abi"
1110
"github.com/ethereum/go-ethereum/common"
1211
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -56,17 +55,22 @@ type Forwarder interface {
5655
ForwarderData(to *common.Address, data []byte, opts transactor.TransactOptions) ([]byte, error)
5756
}
5857

58+
type Signer interface {
59+
CommonAddress() common.Address
60+
Sign(digestHash []byte) ([]byte, error)
61+
}
62+
5963
type ITXTransactor struct {
6064
forwarder Forwarder
6165
relayCaller RelayCaller
62-
kp *secp256k1.Keypair
66+
signer Signer
6367
}
6468

65-
func NewITXTransactor(relayCaller RelayCaller, forwarder Forwarder, kp *secp256k1.Keypair) *ITXTransactor {
69+
func NewITXTransactor(relayCaller RelayCaller, forwarder Forwarder, signer Signer) *ITXTransactor {
6670
return &ITXTransactor{
6771
relayCaller: relayCaller,
6872
forwarder: forwarder,
69-
kp: kp,
73+
signer: signer,
7074
}
7175
}
7276

@@ -138,7 +142,7 @@ func (itx *ITXTransactor) signRelayTx(tx *RelayTx) (*SignedRelayTx, error) {
138142
txID := crypto.Keccak256Hash(packed)
139143
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(txID), string(txID.Bytes()))
140144
hash := crypto.Keccak256Hash([]byte(msg))
141-
sig, err := crypto.Sign(hash.Bytes(), itx.kp.PrivateKey())
145+
sig, err := itx.signer.Sign(hash.Bytes())
142146
if err != nil {
143147
return nil, err
144148
}

chains/evm/calls/transactor/itx/minimalForwarder.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77

88
"github.com/ChainSafe/chainbridge-core/chains/evm/calls/contracts/forwarder"
99
"github.com/ChainSafe/chainbridge-core/chains/evm/calls/transactor"
10-
"github.com/ChainSafe/chainbridge-core/crypto/secp256k1"
10+
1111
"github.com/ethereum/go-ethereum/common"
1212
"github.com/ethereum/go-ethereum/common/math"
1313
"github.com/ethereum/go-ethereum/crypto"
@@ -27,7 +27,7 @@ type NonceStorer interface {
2727
}
2828

2929
type MinimalForwarder struct {
30-
kp *secp256k1.Keypair
30+
signer Signer
3131
nonce *big.Int
3232
nonceLock sync.Mutex
3333
chainID *big.Int
@@ -36,10 +36,10 @@ type MinimalForwarder struct {
3636
}
3737

3838
// NewMinimalForwarder creates an instance of MinimalForwarder
39-
func NewMinimalForwarder(chainID *big.Int, kp *secp256k1.Keypair, forwarderContract ForwarderContract, nonceStore NonceStorer) *MinimalForwarder {
39+
func NewMinimalForwarder(chainID *big.Int, signer Signer, forwarderContract ForwarderContract, nonceStore NonceStorer) *MinimalForwarder {
4040
return &MinimalForwarder{
4141
chainID: chainID,
42-
kp: kp,
42+
signer: signer,
4343
forwarderContract: forwarderContract,
4444
nonceStore: nonceStore,
4545
}
@@ -73,8 +73,7 @@ func (c *MinimalForwarder) UnsafeNonce() (*big.Int, error) {
7373
if err != nil {
7474
return nil, err
7575
}
76-
77-
from := common.HexToAddress(c.kp.Address())
76+
from := c.signer.CommonAddress()
7877
contractNonce, err := c.forwarderContract.GetNonce(from)
7978
if err != nil {
8079
return nil, err
@@ -110,7 +109,7 @@ func (c *MinimalForwarder) ChainId() *big.Int {
110109

111110
// ForwarderData returns ABI packed and signed byte data for a forwarded transaction
112111
func (c *MinimalForwarder) ForwarderData(to *common.Address, data []byte, opts transactor.TransactOptions) ([]byte, error) {
113-
from := c.kp.Address()
112+
from := c.signer.CommonAddress().Hex()
114113
forwarderHash, err := c.typedHash(
115114
from,
116115
to.String(),
@@ -124,7 +123,7 @@ func (c *MinimalForwarder) ForwarderData(to *common.Address, data []byte, opts t
124123
return nil, err
125124
}
126125

127-
sig, err := crypto.Sign(forwarderHash, c.kp.PrivateKey())
126+
sig, err := c.signer.Sign(forwarderHash)
128127
if err != nil {
129128
return nil, err
130129
}

chains/evm/calls/transactor/itx/mock/itx.go

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chains/evm/cli/deploy/deploy.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package deploy
22

33
import (
4-
"encoding/hex"
54
"errors"
65
"fmt"
76
"math/big"
@@ -20,7 +19,6 @@ import (
2019
"github.com/ChainSafe/chainbridge-core/chains/evm/cli/logger"
2120
"github.com/ChainSafe/chainbridge-core/chains/evm/cli/utils"
2221
"github.com/ethereum/go-ethereum/common"
23-
"github.com/ethereum/go-ethereum/crypto"
2422
"github.com/rs/zerolog/log"
2523
"github.com/spf13/cobra"
2624
)
@@ -170,9 +168,9 @@ func DeployCLI(cmd *cobra.Command, args []string, txFabric calls.TxFabric, gasPr
170168
}
171169

172170
log.Debug().Msgf("url: %s gas limit: %v gas price: %v", url, gasLimit, gasPrice)
173-
log.Debug().Msgf("SENDER Private key 0x%s", hex.EncodeToString(crypto.FromECDSA(senderKeyPair.PrivateKey())))
171+
log.Debug().Msgf("SENDER Address %s", senderKeyPair.CommonAddress().Hex())
174172

175-
ethClient, err := evmclient.NewEVMClient(url, senderKeyPair.PrivateKey())
173+
ethClient, err := evmclient.NewEVMClient(url, senderKeyPair)
176174
if err != nil {
177175
log.Error().Err(fmt.Errorf("ethereum client error: %v", err)).Msg("error initializing new EVM client")
178176
return err

chains/evm/cli/initialize/initialize.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ func InitializeClient(
1818
url string,
1919
senderKeyPair *secp256k1.Keypair,
2020
) (*evmclient.EVMClient, error) {
21-
ethClient, err := evmclient.NewEVMClient(
22-
url, senderKeyPair.PrivateKey())
21+
ethClient, err := evmclient.NewEVMClient(url, senderKeyPair)
2322
if err != nil {
2423
log.Error().Err(fmt.Errorf("eth client initialization error: %v", err))
2524
return nil, err

0 commit comments

Comments
 (0)