diff --git a/clients/errors.go b/clients/errors.go index d095dcec..c4652e8c 100644 --- a/clients/errors.go +++ b/clients/errors.go @@ -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") ) diff --git a/clients/ethereum/client.go b/clients/ethereum/client.go index dfea49d7..2268b42d 100644 --- a/clients/ethereum/client.go +++ b/clients/ethereum/client.go @@ -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" @@ -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 @@ -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 @@ -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, @@ -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 @@ -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 @@ -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 @@ -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 } @@ -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 } @@ -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 } @@ -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 } diff --git a/clients/ethereum/client_test.go b/clients/ethereum/client_test.go index fca14a04..a13922c7 100644 --- a/clients/ethereum/client_test.go +++ b/clients/ethereum/client_test.go @@ -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" @@ -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) @@ -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 @@ -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) { @@ -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) { @@ -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) @@ -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) @@ -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) diff --git a/clients/ethereum/cryptoHandler.go b/clients/ethereum/cryptoHandler.go new file mode 100644 index 00000000..0cc05199 --- /dev/null +++ b/clients/ethereum/cryptoHandler.go @@ -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 +} diff --git a/clients/ethereum/cryptoHandler_test.go b/clients/ethereum/cryptoHandler_test.go new file mode 100644 index 00000000..79d8c99f --- /dev/null +++ b/clients/ethereum/cryptoHandler_test.go @@ -0,0 +1,102 @@ +package ethereum + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func TestNewCryptoHandler(t *testing.T) { + t.Parallel() + + t.Run("invalid file should error", func(t *testing.T) { + t.Parallel() + + handler, err := NewCryptoHandler("missing file") + assert.Nil(t, handler) + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "open missing file: no such file or directory") + }) + t.Run("invalid private key file", func(t *testing.T) { + t.Parallel() + + handler, err := NewCryptoHandler("./testdata/nok-ethereum-key") + assert.Nil(t, handler) + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "invalid hex data for private key") + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + handler, err := NewCryptoHandler("./testdata/ok-ethereum-key") + assert.NotNil(t, handler) + assert.Nil(t, err) + }) +} + +func TestCryptoHandler_IsInterfaceNil(t *testing.T) { + t.Parallel() + + var instance *cryptoHandler + assert.True(t, instance.IsInterfaceNil()) + + instance = &cryptoHandler{} + assert.False(t, instance.IsInterfaceNil()) +} + +func TestCryptoHandler_Sign(t *testing.T) { + t.Parallel() + + t.Run("test 1", func(t *testing.T) { + expectedSig := "b556014dd984183e4662dc3204e522a5a92093fd6f64bb2da9c1b66b8d5ad12d774e05728b83c76bf09bb91af93ede4118f59aa949c7d02c86051dd0fa140c9900" + msgHash := common.HexToHash("c99286352d865e33f1747761cbd440a7906b9bd8a5261cb6909e5ba18dd19b08") + + handler, _ := NewCryptoHandler("./testdata/ok-ethereum-key") + sig, err := handler.Sign(msgHash) + assert.Nil(t, err) + assert.Equal(t, expectedSig, hex.EncodeToString(sig)) + }) + t.Run("test 2", func(t *testing.T) { + expectedSig := "9abff5ecad356a82855f3ecc816cad5d19315ab812f1affeed7f8020accf01127d4c41ed56ff1b3053b64957a19aa1c6fd7dd1b5aa53065b0df231f517bfe89f01" + msgHash := common.HexToHash("c99286352d865e33f1747761cbd440a7906b9bd8a5261cb6909e5ba18dd19b09") + + handler, _ := NewCryptoHandler("./testdata/ok-ethereum-key") + sig, err := handler.Sign(msgHash) + assert.Nil(t, err) + assert.Equal(t, expectedSig, hex.EncodeToString(sig)) + }) +} + +func TestCryptoHandler_GetAddress(t *testing.T) { + t.Parallel() + + handler, _ := NewCryptoHandler("./testdata/ok-ethereum-key") + expectedAddress := common.HexToAddress("0x3FE464Ac5aa562F7948322F92020F2b668D543d8") + + assert.Equal(t, expectedAddress, handler.GetAddress()) +} + +func TestCryptoHandler_CreateKeyedTransactor(t *testing.T) { + t.Parallel() + + t.Run("nil chain ID should error", func(t *testing.T) { + t.Parallel() + + handler, _ := NewCryptoHandler("./testdata/ok-ethereum-key") + opts, err := handler.CreateKeyedTransactor(nil) + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "no chain id specified") + assert.Nil(t, opts) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + handler, _ := NewCryptoHandler("./testdata/ok-ethereum-key") + opts, err := handler.CreateKeyedTransactor(big.NewInt(1)) + assert.Nil(t, err) + assert.NotNil(t, opts) + }) +} diff --git a/clients/ethereum/interface.go b/clients/ethereum/interface.go index 51d82a66..d6a2ca9d 100644 --- a/clients/ethereum/interface.go +++ b/clients/ethereum/interface.go @@ -73,3 +73,11 @@ type erc20ContractWrapper interface { BalanceOf(ctx context.Context, account common.Address) (*big.Int, error) IsInterfaceNil() bool } + +// CryptoHandler defines the operations for a component that expose some crypto primitives +type CryptoHandler interface { + Sign(msgHash common.Hash) ([]byte, error) + GetAddress() common.Address + CreateKeyedTransactor(chainId *big.Int) (*bind.TransactOpts, error) + IsInterfaceNil() bool +} diff --git a/clients/ethereum/testdata/nok-ethereum-key b/clients/ethereum/testdata/nok-ethereum-key new file mode 100644 index 00000000..3c506c9c --- /dev/null +++ b/clients/ethereum/testdata/nok-ethereum-key @@ -0,0 +1 @@ +9bb971db41e3815a669a71c3f1bcb24e0b81f21e04bf11faa7a34b9b40e7cfb diff --git a/clients/ethereum/testdata/ok-ethereum-key b/clients/ethereum/testdata/ok-ethereum-key new file mode 100644 index 00000000..5675c4b6 --- /dev/null +++ b/clients/ethereum/testdata/ok-ethereum-key @@ -0,0 +1 @@ +9bb971db41e3815a669a71c3f1bcb24e0b81f21e04bf11faa7a34b9b40e7cfb1 diff --git a/cmd/migration/flags.go b/cmd/migration/flags.go index ee96b554..c8eba2ff 100644 --- a/cmd/migration/flags.go +++ b/cmd/migration/flags.go @@ -1,6 +1,8 @@ package main import ( + "path" + "github.com/multiversx/mx-bridge-eth-go/config" logger "github.com/multiversx/mx-chain-logger-go" "github.com/urfave/cli" @@ -23,13 +25,18 @@ var ( } mode = cli.StringFlag{ Name: "mode", - Usage: "This flag specifies the operation mode. Usage: generate, sign or execute", - Value: generateMode, + Usage: "This flag specifies the operation mode. Usage: sign or execute", + Value: signMode, } migrationJsonFile = cli.StringFlag{ Name: "migration-file", - Usage: "The input or output .json file containing the migration data", - Value: "config/migration.json", + Usage: "The output .json file containing the migration data", + Value: path.Join(configPath, "migration-"+timestampPlaceholder+".json"), + } + signatureJsonFile = cli.StringFlag{ + Name: "signature-file", + Usage: "The output .json file containing the signature data", + Value: path.Join(configPath, publicKeyPlaceholder+"-"+timestampPlaceholder+".json"), } newSafeAddress = cli.StringFlag{ Name: "new-safe-address", @@ -44,6 +51,7 @@ func getFlags() []cli.Flag { configurationFile, mode, migrationJsonFile, + signatureJsonFile, newSafeAddress, } } diff --git a/cmd/migration/main.go b/cmd/migration/main.go index 273452e0..c3e66baa 100644 --- a/cmd/migration/main.go +++ b/cmd/migration/main.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "encoding/hex" "encoding/json" "fmt" "os" @@ -27,10 +28,12 @@ import ( ) const ( - filePathPlaceholder = "[path]" - generateMode = "generate" - signMode = "sign" - executeMode = "execute" + filePathPlaceholder = "[path]" + signMode = "sign" + executeMode = "execute" + configPath = "config" + timestampPlaceholder = "[timestamp]" + publicKeyPlaceholder = "[public-key]" ) var log = logger.GetOrCreate("main") @@ -75,10 +78,8 @@ func execute(ctx *cli.Context) error { operationMode := strings.ToLower(ctx.GlobalString(mode.Name)) switch operationMode { - case generateMode: - return generate(ctx, cfg) case signMode: - //TODO: implement + return generateAndSign(ctx, cfg) case executeMode: //TODO: implement } @@ -86,7 +87,7 @@ func execute(ctx *cli.Context) error { return fmt.Errorf("unknown execution mode: %s", operationMode) } -func generate(ctx *cli.Context, cfg config.MigrationToolConfig) error { +func generateAndSign(ctx *cli.Context, cfg config.MigrationToolConfig) error { argsProxy := blockchain.ArgsProxy{ ProxyURL: cfg.MultiversX.NetworkAddress, SameScState: false, @@ -173,10 +174,44 @@ func generate(ctx *cli.Context, cfg config.MigrationToolConfig) error { return err } + cryptoHandler, err := ethereumClient.NewCryptoHandler(cfg.Eth.PrivateKeyFile) + if err != nil { + return err + } + + signature, err := cryptoHandler.Sign(batchInfo.MessageHash) + if err != nil { + return err + } + log.Info(string(val)) + log.Info("Batch signed", + "public key", cryptoHandler.GetAddress().String(), + "message hash", batchInfo.MessageHash.String(), + "signature", signature) jsonFilename := ctx.GlobalString(migrationJsonFile.Name) - return os.WriteFile(jsonFilename, val, os.ModePerm) + jsonFilename = applyTimestamp(jsonFilename) + err = os.WriteFile(jsonFilename, val, os.ModePerm) + if err != nil { + return err + } + + sigInfo := ðereum.SignatureInfo{ + PublicKey: cryptoHandler.GetAddress().String(), + MessageHash: batchInfo.MessageHash.String(), + Signature: hex.EncodeToString(signature), + } + + sigFilename := ctx.GlobalString(signatureJsonFile.Name) + sigFilename = applyTimestamp(sigFilename) + sigFilename = applyPublicKey(sigFilename, sigInfo.PublicKey) + val, err = json.MarshalIndent(sigInfo, "", " ") + if err != nil { + return err + } + + return os.WriteFile(sigFilename, val, os.ModePerm) } func loadConfig(filepath string) (config.MigrationToolConfig, error) { @@ -188,3 +223,14 @@ func loadConfig(filepath string) (config.MigrationToolConfig, error) { return cfg, nil } + +func applyTimestamp(input string) string { + actualTimestamp := time.Now().Format("2006-01-02T15-04-05") + actualTimestamp = strings.Replace(actualTimestamp, "T", "-", 1) + + return strings.Replace(input, timestampPlaceholder, actualTimestamp, 1) +} + +func applyPublicKey(input string, publickey string) string { + return strings.Replace(input, publicKeyPlaceholder, publickey, 1) +} diff --git a/executors/ethereum/common.go b/executors/ethereum/common.go index 7732975d..8805664d 100644 --- a/executors/ethereum/common.go +++ b/executors/ethereum/common.go @@ -21,5 +21,13 @@ type BatchInfo struct { OldSafeContractAddress string `json:"OldSafeContractAddress"` NewSafeContractAddress string `json:"NewSafeContractAddress"` BatchID uint64 `json:"BatchID"` + MessageHash common.Hash `json:"MessageHash"` DepositsInfo []*DepositInfo `json:"DepositsInfo"` } + +// SignatureInfo is the struct holding signature info +type SignatureInfo struct { + PublicKey string `json:"PublicKey"` + MessageHash string `json:"MessageHash"` + Signature string `json:"Signature"` +} diff --git a/executors/ethereum/migrationBatchCreator.go b/executors/ethereum/migrationBatchCreator.go index d5bae823..1589a301 100644 --- a/executors/ethereum/migrationBatchCreator.go +++ b/executors/ethereum/migrationBatchCreator.go @@ -7,6 +7,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/multiversx/mx-bridge-eth-go/clients/ethereum" + "github.com/multiversx/mx-bridge-eth-go/core/batchProcessor" "github.com/multiversx/mx-chain-core-go/core/check" ) @@ -130,5 +132,33 @@ func (creator *migrationBatchCreator) assembleBatchInfo(batchesCount uint64, dep batchInfo.DepositsInfo = append(batchInfo.DepositsInfo, deposit) } + var err error + batchInfo.MessageHash, err = creator.computeMessageHash(batchInfo) + if err != nil { + return nil, err + } + return batchInfo, nil } + +func (creator *migrationBatchCreator) computeMessageHash(batch *BatchInfo) (common.Hash, error) { + tokens := make([]common.Address, 0, len(batch.DepositsInfo)) + recipients := make([]common.Address, 0, len(batch.DepositsInfo)) + amounts := make([]*big.Int, 0, len(batch.DepositsInfo)) + nonces := make([]*big.Int, 0, len(batch.DepositsInfo)) + for _, deposit := range batch.DepositsInfo { + tokens = append(tokens, deposit.ContractAddress) + recipients = append(recipients, common.HexToAddress(batch.NewSafeContractAddress)) + amounts = append(amounts, deposit.Amount) + nonces = append(nonces, big.NewInt(0).SetUint64(deposit.DepositNonce)) + } + + args := &batchProcessor.ArgListsBatch{ + EthTokens: tokens, + Recipients: recipients, + Amounts: amounts, + Nonces: nonces, + } + + return ethereum.GenerateMessageHash(args, batch.BatchID) +} diff --git a/executors/ethereum/migrationBatchCreator_test.go b/executors/ethereum/migrationBatchCreator_test.go index 348a5235..a8c09f25 100644 --- a/executors/ethereum/migrationBatchCreator_test.go +++ b/executors/ethereum/migrationBatchCreator_test.go @@ -208,6 +208,7 @@ func TestMigrationBatchCreator_CreateBatchInfo(t *testing.T) { OldSafeContractAddress: safeContractAddress.String(), NewSafeContractAddress: newSafeContractAddress.String(), BatchID: 2245, + MessageHash: common.HexToHash("0x93915c0bea665553dfc85ec3cdf4b883100929f22d6cbbfc44db2f0ee71b3b56"), DepositsInfo: []*DepositInfo{ { DepositNonce: 40, diff --git a/factory/errors.go b/factory/errors.go index be86f098..ddfb0dce 100644 --- a/factory/errors.go +++ b/factory/errors.go @@ -9,7 +9,6 @@ var ( errNilStatusStorer = errors.New("nil status storer") errNilErc20ContractsHolder = errors.New("nil ERC20 contracts holder") errMissingConfig = errors.New("missing config") - errPublicKeyCast = errors.New("error casting public key to ECDSA") errInvalidValue = errors.New("invalid value") errNilMetricsHolder = errors.New("nil metrics holder") errNilStatusHandler = errors.New("nil status handler") diff --git a/factory/ethMultiversXBridgeComponents.go b/factory/ethMultiversXBridgeComponents.go index e25e0927..8bbc5f98 100644 --- a/factory/ethMultiversXBridgeComponents.go +++ b/factory/ethMultiversXBridgeComponents.go @@ -2,15 +2,12 @@ package factory import ( "context" - "crypto/ecdsa" "fmt" "io" - "os" "sync" "time" "github.com/ethereum/go-ethereum/common" - ethCrypto "github.com/ethereum/go-ethereum/crypto" "github.com/multiversx/mx-bridge-eth-go/bridges/ethMultiversX" "github.com/multiversx/mx-bridge-eth-go/bridges/ethMultiversX/disabled" "github.com/multiversx/mx-bridge-eth-go/bridges/ethMultiversX/steps/ethToMultiversX" @@ -366,22 +363,12 @@ func (components *ethMultiversXBridgeComponents) createEthereumClient(args ArgsE return err } - privateKeyBytes, err := os.ReadFile(ethereumConfigs.PrivateKeyFile) - if err != nil { - return err - } - privateKeyString := converters.TrimWhiteSpaceCharacters(string(privateKeyBytes)) - privateKey, err := ethCrypto.HexToECDSA(privateKeyString) + cryptoHandler, err := ethereum.NewCryptoHandler(ethereumConfigs.PrivateKeyFile) if err != nil { return err } - publicKey := privateKey.Public() - publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) - if !ok { - return errPublicKeyCast - } - components.ethereumRelayerAddress = ethCrypto.PubkeyToAddress(*publicKeyECDSA) + components.ethereumRelayerAddress = cryptoHandler.GetAddress() tokensMapper, err := mappers.NewErc20ToMultiversXMapper(components.mxDataGetter) if err != nil { @@ -404,7 +391,7 @@ func (components *ethMultiversXBridgeComponents) createEthereumClient(args ArgsE Log: core.NewLoggerWithIdentifier(logger.GetOrCreate(ethClientLogId), ethClientLogId), AddressConverter: components.addressConverter, Broadcaster: components.broadcaster, - PrivateKey: privateKey, + CryptoHandler: cryptoHandler, TokensMapper: tokensMapper, SignatureHolder: signaturesHolder, SafeContractAddress: safeContractAddress, diff --git a/testsCommon/bridge/cryptoHandlerStub.go b/testsCommon/bridge/cryptoHandlerStub.go new file mode 100644 index 00000000..262ce7ff --- /dev/null +++ b/testsCommon/bridge/cryptoHandlerStub.go @@ -0,0 +1,48 @@ +package bridge + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +// CryptoHandlerStub - +type CryptoHandlerStub struct { + SignCalled func(msgHash common.Hash) ([]byte, error) + GetAddressCalled func() common.Address + CreateKeyedTransactorCalled func(chainId *big.Int) (*bind.TransactOpts, error) +} + +// Sign - +func (stub *CryptoHandlerStub) Sign(msgHash common.Hash) ([]byte, error) { + if stub.SignCalled != nil { + return stub.SignCalled(msgHash) + } + + return make([]byte, 0), nil +} + +// GetAddress - +func (stub *CryptoHandlerStub) GetAddress() common.Address { + if stub.GetAddressCalled != nil { + return stub.GetAddressCalled() + } + + return common.BytesToAddress(make([]byte, 0)) +} + +// CreateKeyedTransactor - +func (stub *CryptoHandlerStub) CreateKeyedTransactor(chainId *big.Int) (*bind.TransactOpts, error) { + if stub.CreateKeyedTransactorCalled != nil { + return stub.CreateKeyedTransactorCalled(chainId) + } + + return nil, fmt.Errorf("not implemented") +} + +// IsInterfaceNil - +func (stub *CryptoHandlerStub) IsInterfaceNil() bool { + return stub == nil +}