Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signing #331

Merged
merged 5 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading