Skip to content

Commit

Permalink
Merge pull request #331 from multiversx/signing
Browse files Browse the repository at this point in the history
Signing
  • Loading branch information
iulianpascalau authored Sep 5, 2024
2 parents 55fd6db + a08de87 commit afe1fb1
Show file tree
Hide file tree
Showing 16 changed files with 417 additions and 77 deletions.
3 changes: 3 additions & 0 deletions clients/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,7 @@ var (

// ErrNoPendingBatchAvailable signals that no pending batch is available
ErrNoPendingBatchAvailable = errors.New("no pending batch available")

// ErrNilCryptoHandler signals that a nil crypto handler was provided
ErrNilCryptoHandler = errors.New("nil crypto handler")
)
40 changes: 15 additions & 25 deletions clients/ethereum/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package ethereum

import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"
"sync"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/multiversx/mx-bridge-eth-go/clients"
Expand All @@ -34,7 +32,7 @@ type ArgsEthereumClient struct {
Log chainCore.Logger
AddressConverter core.AddressConverter
Broadcaster Broadcaster
PrivateKey *ecdsa.PrivateKey
CryptoHandler CryptoHandler
TokensMapper TokensMapper
SignatureHolder SignaturesHolder
SafeContractAddress common.Address
Expand All @@ -52,8 +50,7 @@ type client struct {
log chainCore.Logger
addressConverter core.AddressConverter
broadcaster Broadcaster
privateKey *ecdsa.PrivateKey
publicKey *ecdsa.PublicKey
cryptoHandler CryptoHandler
tokensMapper TokensMapper
signatureHolder SignaturesHolder
safeContractAddress common.Address
Expand All @@ -76,20 +73,13 @@ func NewEthereumClient(args ArgsEthereumClient) (*client, error) {
return nil, err
}

publicKey := args.PrivateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return nil, errPublicKeyCast
}

c := &client{
clientWrapper: args.ClientWrapper,
erc20ContractsHandler: args.Erc20ContractsHandler,
log: args.Log,
addressConverter: args.AddressConverter,
broadcaster: args.Broadcaster,
privateKey: args.PrivateKey,
publicKey: publicKeyECDSA,
cryptoHandler: args.CryptoHandler,
tokensMapper: args.TokensMapper,
signatureHolder: args.SignatureHolder,
safeContractAddress: args.SafeContractAddress,
Expand All @@ -102,7 +92,7 @@ func NewEthereumClient(args ArgsEthereumClient) (*client, error) {
}

c.log.Info("NewEthereumClient",
"relayer address", crypto.PubkeyToAddress(*publicKeyECDSA),
"relayer address", c.cryptoHandler.GetAddress(),
"safe contract address", c.safeContractAddress.String())

return c, err
Expand All @@ -124,8 +114,8 @@ func checkArgs(args ArgsEthereumClient) error {
if check.IfNil(args.Broadcaster) {
return errNilBroadcaster
}
if args.PrivateKey == nil {
return clients.ErrNilPrivateKey
if check.IfNil(args.CryptoHandler) {
return clients.ErrNilCryptoHandler
}
if check.IfNil(args.TokensMapper) {
return clients.ErrNilTokensMapper
Expand Down Expand Up @@ -256,7 +246,7 @@ func (c *client) WasExecuted(ctx context.Context, batchID uint64) (bool, error)

// BroadcastSignatureForMessageHash will send the signature for the provided message hash
func (c *client) BroadcastSignatureForMessageHash(msgHash common.Hash) {
signature, err := crypto.Sign(msgHash.Bytes(), c.privateKey)
signature, err := c.cryptoHandler.Sign(msgHash)
if err != nil {
c.log.Error("error generating signature", "msh hash", msgHash, "error", err)
return
Expand All @@ -267,6 +257,11 @@ func (c *client) BroadcastSignatureForMessageHash(msgHash common.Hash) {

// GenerateMessageHash will generate the message hash based on the provided batch
func (c *client) GenerateMessageHash(batch *batchProcessor.ArgListsBatch, batchId uint64) (common.Hash, error) {
return GenerateMessageHash(batch, batchId)
}

// GenerateMessageHash will generate the message hash based on the provided batch
func GenerateMessageHash(batch *batchProcessor.ArgListsBatch, batchId uint64) (common.Hash, error) {
if batch == nil {
return common.Hash{}, clients.ErrNilBatch
}
Expand Down Expand Up @@ -336,9 +331,7 @@ func (c *client) ExecuteTransfer(
return "", fmt.Errorf("%w in client.ExecuteTransfer", clients.ErrMultisigContractPaused)
}

fromAddress := crypto.PubkeyToAddress(*c.publicKey)

nonce, err := c.getNonce(ctx, fromAddress)
nonce, err := c.getNonce(ctx, c.cryptoHandler.GetAddress())
if err != nil {
return "", err
}
Expand All @@ -348,7 +341,7 @@ func (c *client) ExecuteTransfer(
return "", err
}

auth, err := bind.NewKeyedTransactorWithChainID(c.privateKey, chainId)
auth, err := c.cryptoHandler.CreateKeyedTransactor(chainId)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -496,10 +489,7 @@ func (c *client) WhitelistedTokens(ctx context.Context, token common.Address) (b
}

func (c *client) checkRelayerFundsForFee(ctx context.Context, transferFee *big.Int) error {

ethereumRelayerAddress := crypto.PubkeyToAddress(*c.publicKey)

existingBalance, err := c.clientWrapper.BalanceAt(ctx, ethereumRelayerAddress, nil)
existingBalance, err := c.clientWrapper.BalanceAt(ctx, c.cryptoHandler.GetAddress(), nil)
if err != nil {
return err
}
Expand Down
89 changes: 67 additions & 22 deletions clients/ethereum/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/multiversx/mx-bridge-eth-go/clients"
"github.com/multiversx/mx-bridge-eth-go/clients/ethereum/contract"
bridgeCore "github.com/multiversx/mx-bridge-eth-go/core"
Expand All @@ -33,8 +32,6 @@ var expectedRecipients = []common.Address{common.BytesToAddress([]byte("to1")),
var expectedNonces = []*big.Int{big.NewInt(10), big.NewInt(30)}

func createMockEthereumClientArgs() ArgsEthereumClient {
sk, _ := crypto.HexToECDSA("9bb971db41e3815a669a71c3f1bcb24e0b81f21e04bf11faa7a34b9b40e7cfb1")

addressConverter, err := converters.NewAddressConverter()
if err != nil {
panic(err)
Expand All @@ -46,7 +43,7 @@ func createMockEthereumClientArgs() ArgsEthereumClient {
Log: logger.GetOrCreate("test"),
AddressConverter: addressConverter,
Broadcaster: &testsCommon.BroadcasterStub{},
PrivateKey: sk,
CryptoHandler: &bridgeTests.CryptoHandlerStub{},
TokensMapper: &bridgeTests.TokensMapperStub{
ConvertTokenCalled: func(ctx context.Context, sourceBytes []byte) ([]byte, error) {
return append([]byte("ERC20"), sourceBytes...), nil
Expand Down Expand Up @@ -137,12 +134,12 @@ func TestNewEthereumClient(t *testing.T) {
assert.Equal(t, errNilBroadcaster, err)
assert.True(t, check.IfNil(c))
})
t.Run("nil private key", func(t *testing.T) {
t.Run("nil crypto handler", func(t *testing.T) {
args := createMockEthereumClientArgs()
args.PrivateKey = nil
args.CryptoHandler = nil
c, err := NewEthereumClient(args)

assert.Equal(t, clients.ErrNilPrivateKey, err)
assert.Equal(t, clients.ErrNilCryptoHandler, err)
assert.True(t, check.IfNil(c))
})
t.Run("nil tokens mapper", func(t *testing.T) {
Expand Down Expand Up @@ -475,23 +472,54 @@ func TestClient_GenerateMessageHash(t *testing.T) {
func TestClient_BroadcastSignatureForMessageHash(t *testing.T) {
t.Parallel()

expectedSig := "b556014dd984183e4662dc3204e522a5a92093fd6f64bb2da9c1b66b8d5ad12d774e05728b83c76bf09bb91af93ede4118f59aa949c7d02c86051dd0fa140c9900"
broadcastCalled := false
t.Run("sign failed should not broadcast", func(t *testing.T) {
t.Parallel()

hash := common.HexToHash("c99286352d865e33f1747761cbd440a7906b9bd8a5261cb6909e5ba18dd19b08")
args := createMockEthereumClientArgs()
args.Broadcaster = &testsCommon.BroadcasterStub{
BroadcastSignatureCalled: func(signature []byte, messageHash []byte) {
assert.Equal(t, hash.Bytes(), messageHash)
assert.Equal(t, expectedSig, hex.EncodeToString(signature))
broadcastCalled = true
},
}
expectedError := errors.New("expected error")
hash := common.HexToHash("hash")
args := createMockEthereumClientArgs()
args.Broadcaster = &testsCommon.BroadcasterStub{
BroadcastSignatureCalled: func(signature []byte, messageHash []byte) {
assert.Fail(t, "should have not called bradcast")
},
}
args.CryptoHandler = &bridgeTests.CryptoHandlerStub{
SignCalled: func(msgHash common.Hash) ([]byte, error) {
assert.Equal(t, hash.Bytes(), msgHash.Bytes())
return nil, expectedError
},
}

c, _ := NewEthereumClient(args)
c.BroadcastSignatureForMessageHash(hash)
c, _ := NewEthereumClient(args)
c.BroadcastSignatureForMessageHash(hash)
})
t.Run("should work", func(t *testing.T) {
t.Parallel()

expectedSig := "expected sig"
broadcastCalled := false

hash := common.HexToHash("hash")
args := createMockEthereumClientArgs()
args.Broadcaster = &testsCommon.BroadcasterStub{
BroadcastSignatureCalled: func(signature []byte, messageHash []byte) {
assert.Equal(t, hash.Bytes(), messageHash)
assert.Equal(t, expectedSig, string(signature))
broadcastCalled = true
},
}
args.CryptoHandler = &bridgeTests.CryptoHandlerStub{
SignCalled: func(msgHash common.Hash) ([]byte, error) {
assert.Equal(t, hash.Bytes(), msgHash.Bytes())
return []byte(expectedSig), nil
},
}

assert.True(t, broadcastCalled)
c, _ := NewEthereumClient(args)
c.BroadcastSignatureForMessageHash(hash)

assert.True(t, broadcastCalled)
})
}

func TestClient_WasExecuted(t *testing.T) {
Expand All @@ -517,6 +545,11 @@ func TestClient_ExecuteTransfer(t *testing.T) {
t.Parallel()

args := createMockEthereumClientArgs()
args.CryptoHandler = &bridgeTests.CryptoHandlerStub{
CreateKeyedTransactorCalled: func(chainId *big.Int) (*bind.TransactOpts, error) {
return &bind.TransactOpts{}, nil
},
}
batch := createMockTransferBatch()
argLists := batchProcessor.ExtractListMvxToEth(batch)
signatures := make([][]byte, 10)
Expand Down Expand Up @@ -589,6 +622,18 @@ func TestClient_ExecuteTransfer(t *testing.T) {
assert.Equal(t, "", hash)
assert.True(t, errors.Is(err, expectedErr))
})
t.Run("create keyed transactor fails", func(t *testing.T) {
expectedErr := errors.New("expected error create keyed transactor")
c, _ := NewEthereumClient(args)
c.cryptoHandler = &bridgeTests.CryptoHandlerStub{
CreateKeyedTransactorCalled: func(chainId *big.Int) (*bind.TransactOpts, error) {
return nil, expectedErr
},
}
hash, err := c.ExecuteTransfer(context.Background(), common.Hash{}, argLists, batch.ID, 10)
assert.Equal(t, "", hash)
assert.True(t, errors.Is(err, expectedErr))
})
t.Run("get current gas price fails", func(t *testing.T) {
expectedErr := errors.New("expected error get current gas price")
c, _ := NewEthereumClient(args)
Expand All @@ -599,7 +644,7 @@ func TestClient_ExecuteTransfer(t *testing.T) {
}
hash, err := c.ExecuteTransfer(context.Background(), common.Hash{}, argLists, batch.ID, 10)
assert.Equal(t, "", hash)
assert.True(t, errors.Is(err, expectedErr))
assert.ErrorIs(t, err, expectedErr)
})
t.Run("not enough quorum", func(t *testing.T) {
c, _ := NewEthereumClient(args)
Expand Down
63 changes: 63 additions & 0 deletions clients/ethereum/cryptoHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ethereum

import (
"crypto/ecdsa"
"math/big"
"os"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/multiversx/mx-bridge-eth-go/core/converters"
)

type cryptoHandler struct {
privateKey *ecdsa.PrivateKey
publicKey *ecdsa.PublicKey
address common.Address
}

// NewCryptoHandler creates a new instance of type cryptoHandler able to sign messages and provide the containing public key
func NewCryptoHandler(privateKeyFilename string) (*cryptoHandler, error) {
privateKeyBytes, err := os.ReadFile(privateKeyFilename)
if err != nil {
return nil, err
}
privateKeyString := converters.TrimWhiteSpaceCharacters(string(privateKeyBytes))
privateKey, err := ethCrypto.HexToECDSA(privateKeyString)
if err != nil {
return nil, err
}

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return nil, errPublicKeyCast
}

return &cryptoHandler{
privateKey: privateKey,
publicKey: publicKeyECDSA,
address: ethCrypto.PubkeyToAddress(*publicKeyECDSA),
}, nil
}

// Sign signs the provided message hash with the containing private key
func (handler *cryptoHandler) Sign(msgHash common.Hash) ([]byte, error) {
return ethCrypto.Sign(msgHash.Bytes(), handler.privateKey)
}

// GetAddress returns the corresponding address of the containing public key
func (handler *cryptoHandler) GetAddress() common.Address {
return handler.address
}

// CreateKeyedTransactor creates a keyed transactor used to create transactions on Ethereum chain
func (handler *cryptoHandler) CreateKeyedTransactor(chainId *big.Int) (*bind.TransactOpts, error) {
return bind.NewKeyedTransactorWithChainID(handler.privateKey, chainId)
}

// IsInterfaceNil returns true if there is no value under the interface
func (handler *cryptoHandler) IsInterfaceNil() bool {
return handler == nil
}
Loading

0 comments on commit afe1fb1

Please sign in to comment.