From d2bf6633e10630232e216d04e51c134243b9a127 Mon Sep 17 00:00:00 2001 From: Moonyongjung Date: Wed, 6 Sep 2023 10:09:56 +0900 Subject: [PATCH 1/2] fix: handle keyring when create multisig transaction --- client/tx.go | 155 ++++++++++++++++++++++++++++------------- client/tx_handle.go | 30 ++++++++ types/core_auth_msg.go | 5 +- util/client.go | 23 +++++- 4 files changed, 158 insertions(+), 55 deletions(-) diff --git a/client/tx.go b/client/tx.go index 811df96..dd0fe4e 100644 --- a/client/tx.go +++ b/client/tx.go @@ -3,6 +3,8 @@ package client import ( "encoding/base64" "encoding/json" + "os" + "path/filepath" mevm "github.com/xpladev/xpla.go/core/evm" "github.com/xpladev/xpla.go/key" @@ -10,8 +12,8 @@ import ( "github.com/xpladev/xpla.go/types/errors" "github.com/xpladev/xpla.go/util" - cmclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/crypto/keyring" kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/crypto/types/multisig" @@ -26,22 +28,14 @@ import ( // Options required for create and sign are stored in the xpla client and reflected when the values of those options exist. // Create and sign transaction must be needed in order to send transaction to the chain. func (xplac *XplaClient) CreateAndSignTx() ([]byte, error) { + var err error if xplac.GetErr() != nil { return nil, xplac.GetErr() } - if xplac.GetAccountNumber() == "" || xplac.GetSequence() == "" { - if xplac.GetLcdURL() == "" && xplac.GetGrpcUrl() == "" { - xplac.WithAccountNumber(util.FromUint64ToString(types.DefaultAccNum)) - xplac.WithSequence(util.FromUint64ToString(types.DefaultAccSeq)) - } else { - account, err := xplac.LoadAccount(sdk.AccAddress(xplac.GetPrivateKey().PubKey().Address())) - if err != nil { - return nil, err - } - xplac.WithAccountNumber(util.FromUint64ToString(account.GetAccountNumber())) - xplac.WithSequence(util.FromUint64ToString(account.GetSequence())) - } + xplac, err = GetAccNumAndSeq(xplac) + if err != nil { + return nil, err } if xplac.GetGasAdjustment() == "" { @@ -166,6 +160,8 @@ func (xplac *XplaClient) CreateUnsignedTx() ([]byte, error) { // Sign created unsigned transaction. func (xplac *XplaClient) SignTx(signTxMsg types.SignTxMsg) ([]byte, error) { + var err error + if xplac.GetErr() != nil { return nil, xplac.GetErr() } @@ -174,11 +170,20 @@ func (xplac *XplaClient) SignTx(signTxMsg types.SignTxMsg) ([]byte, error) { return nil, util.LogErr(errors.ErrNotSatisfiedOptions, "need sign tx message of xpla client's option") } + if !signTxMsg.Offline { + xplac, err = GetAccNumAndSeq(xplac) + if err != nil { + return nil, err + } + } + clientCtx, err := util.NewClient() if err != nil { return nil, err } - err = clientCtx.Keyring.ImportPrivKey(types.XplaToolDefaultName, key.EncryptArmorPrivKey(xplac.GetPrivateKey(), key.DefaultEncryptPassphrase), key.DefaultEncryptPassphrase) + + fromName := types.XplaToolDefaultName + err = clientCtx.Keyring.ImportPrivKey(fromName, key.EncryptArmorPrivKey(xplac.GetPrivateKey(), key.DefaultEncryptPassphrase), key.DefaultEncryptPassphrase) if err != nil { return nil, util.LogErr(errors.ErrKeyNotFound, err) } @@ -198,26 +203,36 @@ func (xplac *XplaClient) SignTx(signTxMsg types.SignTxMsg) ([]byte, error) { signatureOnly := signTxMsg.SignatureOnly multisig := signTxMsg.MultisigAddress - from := signTxMsg.FromAddress - generateOnly := false - offline := true - - _, fromName, _, err := cmclient.GetFromFields(txFactory.Keybase(), from, generateOnly) - if err != nil { - return nil, util.LogErr(errors.ErrParse, err) - } - if multisig != "" { multisigAddr, err := sdk.AccAddressFromBech32(multisig) if err != nil { - multisigAddr, _, _, err = cmclient.GetFromFields(txFactory.Keybase(), multisig, generateOnly) + return nil, util.LogErr(errors.ErrParse, err) + } + + multisigAccNum := uint64(types.DefaultAccNum) + multisigAccSeq := uint64(types.DefaultAccSeq) + if !signTxMsg.Offline { + if xplac.GetLcdURL() == "" && xplac.GetGrpcUrl() == "" { + return nil, util.LogErr(errors.ErrInvalidRequest, "need LCD or gRPC URL when not offline mode") + } + signerAcc, err := xplac.LoadAccount(multisigAddr) if err != nil { return nil, util.LogErr(errors.ErrParse, err) } + multisigAccNum = signerAcc.GetAccountNumber() + multisigAccSeq = signerAcc.GetSequence() } - err = authclient.SignTxWithSignerAddress( - txFactory, clientCtx, multisigAddr, fromName, txBuilder, offline, signTxMsg.Overwrite, - ) + + txFactory = txFactory.WithSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON). + WithChainID(xplac.GetChainId()). + WithAccountNumber(multisigAccNum). + WithSequence(multisigAccSeq) + + if !isTxSigner(multisigAddr, txBuilder.GetTx().GetSigners()) { + return nil, util.LogErr(errors.ErrParse, err) + } + + err = tx.Sign(txFactory, fromName, txBuilder, signTxMsg.Overwrite) if err != nil { return nil, util.LogErr(errors.ErrParse, err) } @@ -228,7 +243,6 @@ func (xplac *XplaClient) SignTx(signTxMsg types.SignTxMsg) ([]byte, error) { xplac.WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) } - privs := []cryptotypes.PrivKey{xplac.GetPrivateKey()} accNumU64, err := util.FromStringToUint64(xplac.GetAccountNumber()) if err != nil { return nil, err @@ -237,6 +251,8 @@ func (xplac *XplaClient) SignTx(signTxMsg types.SignTxMsg) ([]byte, error) { if err != nil { return nil, err } + + privs := []cryptotypes.PrivKey{xplac.GetPrivateKey()} accNums := []uint64{accNumU64} accSeqs := []uint64{accSeqU64} @@ -292,6 +308,30 @@ func (xplac *XplaClient) MultiSign(txMultiSignMsg types.TxMultiSignMsg) ([]byte, return nil, err } + if txMultiSignMsg.KeyringBackend != keyring.BackendFile && + txMultiSignMsg.KeyringBackend != keyring.BackendMemory && + txMultiSignMsg.KeyringBackend != keyring.BackendTest { + return nil, util.LogErr(errors.ErrParse, "invalid keyring backend, must be "+util.BackendFile+", "+util.BackendTest+" or "+util.BackendMemory) + } + + keyringPath := txMultiSignMsg.KeyringPath + if (txMultiSignMsg.KeyringBackend == keyring.BackendFile || + txMultiSignMsg.KeyringBackend == keyring.BackendTest) && txMultiSignMsg.KeyringPath == "" { + userHomeDir, err := os.UserHomeDir() + if err != nil { + return nil, util.LogErr(errors.ErrParse, err) + } + + keyringPath = filepath.Join(userHomeDir, ".xpla") + } + + newKeyring, err := util.NewKeyring(txMultiSignMsg.KeyringBackend, keyringPath) + if err != nil { + return nil, util.LogErr(errors.ErrParse, err) + } + + clientCtx = clientCtx.WithKeyring(newKeyring) + parseTx, err := authclient.ReadTxFromFile(clientCtx, txMultiSignMsg.FileName) if err != nil { return nil, util.LogErr(errors.ErrParse, err) @@ -301,7 +341,8 @@ func (xplac *XplaClient) MultiSign(txMultiSignMsg types.TxMultiSignMsg) ([]byte, if txFactory.SignMode() == signing.SignMode_SIGN_MODE_UNSPECIFIED { txFactory = txFactory.WithSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) } - txFactory = txFactory.WithChainID(xplac.GetChainId()). + txFactory = txFactory. + WithChainID(xplac.GetChainId()). WithAccountNumber(uint64(types.DefaultAccNum)). WithSequence(uint64(types.DefaultAccSeq)) @@ -318,13 +359,17 @@ func (xplac *XplaClient) MultiSign(txMultiSignMsg types.TxMultiSignMsg) ([]byte, multisigPub := multisigInfo.GetPubKey().(*kmultisig.LegacyAminoPubKey) multisigSig := multisig.NewMultisig(len(multisigPub.PubKeys)) - clientCtx = clientCtx.WithOffline(txMultiSignMsg.Offline) - if !clientCtx.Offline { - accnum, seq, err := clientCtx.AccountRetriever.GetAccountNumberSequence(clientCtx, multisigInfo.GetAddress()) + if !txMultiSignMsg.Offline { + if xplac.GetLcdURL() == "" && xplac.GetGrpcUrl() == "" { + return nil, util.LogErr(errors.ErrInvalidRequest, "need LCD or gRPC URL when not offline mode") + } + multisigAccount, err := xplac.LoadAccount(multisigInfo.GetAddress()) if err != nil { return nil, util.LogErr(errors.ErrParse, err) } - txFactory = txFactory.WithAccountNumber(accnum).WithSequence(seq) + txFactory = txFactory. + WithAccountNumber(multisigAccount.GetAccountNumber()). + WithSequence(multisigAccount.GetSequence()) } for _, sigFile := range txMultiSignMsg.SignatureFiles { @@ -333,20 +378,31 @@ func (xplac *XplaClient) MultiSign(txMultiSignMsg types.TxMultiSignMsg) ([]byte, return nil, util.LogErr(errors.ErrFailedToUnmarshal, err) } - signingData := authsigning.SignerData{ - ChainID: txFactory.ChainID(), - AccountNumber: txFactory.AccountNumber(), - Sequence: txFactory.Sequence(), - } - for _, sig := range sigs { + data, ok := sig.Data.(*signing.SingleSignatureData) + if !ok { + return nil, util.LogErr(errors.ErrParse, "signature data is not single signature") + } + + if data.SignMode != signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { + continue + } + + addr, err := sdk.AccAddressFromHex(sig.PubKey.Address().String()) + if err != nil { + return nil, util.LogErr(errors.ErrParse, err) + } + + signingData := authsigning.SignerData{ + ChainID: txFactory.ChainID(), + AccountNumber: txFactory.AccountNumber(), + Sequence: txFactory.Sequence(), + } + err = authsigning.VerifySignature(sig.PubKey, signingData, sig.Data, txCfg.SignModeHandler(), txBuilder.GetTx()) if err != nil { - addr, err := sdk.AccAddressFromHex(sig.PubKey.Address().String()) - if err != nil { - return nil, util.LogErr(errors.ErrParse, err) - } - return nil, util.LogErr(errors.ErrInvalidRequest, "couldn't verify signature for address", addr) + + return nil, util.LogErr(errors.ErrInvalidRequest, "couldn't verify signature for address", addr.String()) } if err := multisig.AddSignatureV2(multisigSig, sig, multisigPub.GetPubKeys()); err != nil { @@ -392,14 +448,13 @@ func (xplac *XplaClient) MultiSign(txMultiSignMsg types.TxMultiSignMsg) ([]byte, } } - if txMultiSignMsg.OutputDocument == "" { - return json, nil + if xplac.GetOutputDocument() != "" { + err = util.SaveJsonPretty(json, xplac.GetOutputDocument()) + if err != nil { + return nil, err + } } - err = util.SaveJsonPretty(json, xplac.GetOutputDocument()) - if err != nil { - return nil, err - } return json, nil } diff --git a/client/tx_handle.go b/client/tx_handle.go index 4f6a7de..8a214cd 100644 --- a/client/tx_handle.go +++ b/client/tx_handle.go @@ -1,6 +1,7 @@ package client import ( + "bytes" "crypto/ecdsa" "math/big" "os" @@ -425,3 +426,32 @@ func getGasLimitFeeAmount(xplac *XplaClient, builder cmclient.TxBuilder) (string return gasLimit, feeAmount, nil } + +// check user = signer +func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool { + for _, s := range signers { + if bytes.Equal(user.Bytes(), s.Bytes()) { + return true + } + } + + return false +} + +// Get account number and sequence +func GetAccNumAndSeq(xplac *XplaClient) (*XplaClient, error) { + if xplac.GetAccountNumber() == "" || xplac.GetSequence() == "" { + if xplac.GetLcdURL() == "" && xplac.GetGrpcUrl() == "" { + xplac.WithAccountNumber(util.FromUint64ToString(types.DefaultAccNum)) + xplac.WithSequence(util.FromUint64ToString(types.DefaultAccSeq)) + } else { + account, err := xplac.LoadAccount(sdk.AccAddress(xplac.GetPrivateKey().PubKey().Address())) + if err != nil { + return nil, err + } + xplac.WithAccountNumber(util.FromUint64ToString(account.GetAccountNumber())) + xplac.WithSequence(util.FromUint64ToString(account.GetSequence())) + } + } + return xplac, nil +} diff --git a/types/core_auth_msg.go b/types/core_auth_msg.go index 53c42ee..b543cab 100644 --- a/types/core_auth_msg.go +++ b/types/core_auth_msg.go @@ -18,9 +18,9 @@ type SignTxMsg struct { UnsignedFileName string SignatureOnly bool MultisigAddress string - FromAddress string Overwrite bool Amino bool + Offline bool } type TxMultiSignMsg struct { @@ -29,9 +29,10 @@ type TxMultiSignMsg struct { FromName string Offline bool SignatureFiles []string - OutputDocument string SignatureOnly bool Amino bool + KeyringPath string + KeyringBackend string } type QueryAccAddressMsg struct { diff --git a/util/client.go b/util/client.go index 2f30da9..a1663ba 100644 --- a/util/client.go +++ b/util/client.go @@ -22,6 +22,7 @@ import ( const ( BackendFile = "file" BackendMemory = "memory" + BackendTest = "test" ) // Provide cosmos sdk client. @@ -82,7 +83,8 @@ func NewEvmClient(evmRpcUrl string, ctx context.Context) (*EvmClient, error) { // Provide cosmos sdk keyring func NewKeyring(backendType string, keyringPath string) (keyring.Keyring, error) { - if backendType == BackendMemory { + switch { + case backendType == BackendMemory: k, err := keyring.New( types.XplaToolDefaultName, keyring.BackendMemory, @@ -96,7 +98,7 @@ func NewKeyring(backendType string, keyringPath string) (keyring.Keyring, error) return k, nil - } else if backendType == BackendFile { + case backendType == BackendFile: k, err := keyring.New( types.XplaToolDefaultName, keyring.BackendFile, @@ -109,7 +111,22 @@ func NewKeyring(backendType string, keyringPath string) (keyring.Keyring, error) } return k, nil - } else { + + case backendType == BackendTest: + k, err := keyring.New( + types.XplaToolDefaultName, + keyring.BackendTest, + keyringPath, + nil, + hd.EthSecp256k1Option(), + ) + if err != nil { + return nil, LogErr(errors.ErrKeyNotFound, err) + } + + return k, nil + + default: return nil, LogErr(errors.ErrInvalidMsgType, "invalid keyring backend type") } } From 10d30bfb17ad0a29639e16008a20e1e6976af6a0 Mon Sep 17 00:00:00 2001 From: Moonyongjung Date: Wed, 6 Sep 2023 10:11:05 +0900 Subject: [PATCH 2/2] test: add multisig transaction test --- client/msg_tx_test.go | 146 ++++++++++++++++++++++++++++++++++++++++++ client/tx_test.go | 2 - 2 files changed, 146 insertions(+), 2 deletions(-) diff --git a/client/msg_tx_test.go b/client/msg_tx_test.go index 10f5a14..293ad67 100644 --- a/client/msg_tx_test.go +++ b/client/msg_tx_test.go @@ -1,6 +1,11 @@ package client_test import ( + "path/filepath" + + "github.com/evmos/ethermint/crypto/hd" + "github.com/xpladev/xpla.go/client" + "github.com/xpladev/xpla.go/key" "github.com/xpladev/xpla.go/types" "github.com/xpladev/xpla.go/util/testutil" @@ -18,6 +23,9 @@ import ( mupgrade "github.com/xpladev/xpla.go/core/upgrade" mwasm "github.com/xpladev/xpla.go/core/wasm" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -808,3 +816,141 @@ func (s *ClientTestSuite) TestWasmTx() { s.Require().NoError(err) s.Require().Equal(testutil.WasmMigrateTxTemplates, string(wasmMigrateJsonTxbytes)) } + +func (s *ClientTestSuite) TestMultiSignature() { + s.xplac.WithURL(s.apis[0]) + rootDir := s.network.Validators[0].Dir + key1 := s.accounts[0] + key2 := s.accounts[1] + + key1Name := "key1" + key2Name := "key2" + multiKeyName := "multiKey" + + // gen multisig account + kb, err := keyring.New(types.XplaToolDefaultName, keyring.BackendTest, rootDir, nil, hd.EthSecp256k1Option()) + s.Require().NoError(err) + + err = kb.ImportPrivKey( + key1Name, + key.EncryptArmorPrivKey(key1.PrivKey, key.DefaultEncryptPassphrase), + key.DefaultEncryptPassphrase, + ) + s.Require().NoError(err) + + err = kb.ImportPrivKey( + key2Name, + key.EncryptArmorPrivKey(key2.PrivKey, key.DefaultEncryptPassphrase), + key.DefaultEncryptPassphrase, + ) + s.Require().NoError(err) + + var pks []cryptotypes.PubKey + multisigThreshold := 2 + + k1, err := kb.Key(key1Name) + s.Require().NoError(err) + pks = append(pks, k1.GetPubKey()) + + k2, err := kb.Key(key2Name) + s.Require().NoError(err) + pks = append(pks, k2.GetPubKey()) + + multiKeyInfo, err := kb.SaveMultisig(multiKeyName, multisig.NewLegacyAminoPubKey(multisigThreshold, pks)) + s.Require().NoError(err) + + // send coin to multisig account + s.xplac.WithPrivateKey(key2.PrivKey) + + bankSendMsg := types.BankSendMsg{ + FromAddress: key2.Address.String(), + ToAddress: multiKeyInfo.GetAddress().String(), + Amount: "10000000000axpla", + } + bankSendTxbytes, err := s.xplac.BankSend(bankSendMsg).CreateAndSignTx() + s.Require().NoError(err) + + _, err = s.xplac.Broadcast(bankSendTxbytes) + s.Require().NoError(err) + s.Require().NoError(s.network.WaitForNextBlock()) + + // create unsigned tx + unsignedTxPath := filepath.Join(rootDir, "unsignedTx.json") + s.xplac.WithOutputDocument(unsignedTxPath) + + bankSendMsg2 := types.BankSendMsg{ + FromAddress: multiKeyInfo.GetAddress().String(), + ToAddress: key1.Address.String(), + Amount: "10", + } + + _, err = s.xplac.BankSend(bankSendMsg2).CreateUnsignedTx() + s.Require().NoError(err) + + // create signature of key1 + s.xplac.WithPrivateKey(key1.PrivKey) + signature1Path := filepath.Join(rootDir, "signature1.json") + s.xplac.WithOutputDocument(signature1Path) + + signTxMsg1 := types.SignTxMsg{ + UnsignedFileName: unsignedTxPath, + SignatureOnly: true, + MultisigAddress: multiKeyInfo.GetAddress().String(), + Overwrite: false, + Amino: false, + Offline: false, + } + _, err = s.xplac.SignTx(signTxMsg1) + s.Require().NoError(err) + + // create signature of key2 + s.xplac.WithPrivateKey(key2.PrivKey) + signature2Path := filepath.Join(rootDir, "signature2.json") + s.xplac.WithOutputDocument(signature2Path) + + signTxMsg2 := types.SignTxMsg{ + UnsignedFileName: unsignedTxPath, + SignatureOnly: true, + MultisigAddress: multiKeyInfo.GetAddress().String(), + Overwrite: false, + Amino: false, + Offline: false, + } + _, err = s.xplac.SignTx(signTxMsg2) + s.Require().NoError(err) + + // create multisigned transaction + s.xplac.WithOutputDocument("") + txMultiSignMsg := types.TxMultiSignMsg{ + FileName: unsignedTxPath, + GenerateOnly: true, + FromName: multiKeyInfo.GetName(), + Offline: false, + SignatureFiles: []string{ + signature1Path, + signature2Path, + }, + KeyringBackend: "test", + KeyringPath: rootDir, + } + multiSignTx, err := s.xplac.MultiSign(txMultiSignMsg) + s.Require().NoError(err) + + // broadcast test + sdkTx, err := s.xplac.GetEncoding().TxConfig.TxJSONDecoder()(multiSignTx) + s.Require().NoError(err) + txBytes, err := s.xplac.GetEncoding().TxConfig.TxEncoder()(sdkTx) + s.Require().NoError(err) + + _, err = s.xplac.Broadcast(txBytes) + + // generate error insufficient funds + // multisig tx is normal + s.Require().Error(err) + s.Require().Equal( + `code 8 : tx failed - [with code 5 : 10000000000axpla is smaller than 133715200000000000axpla: insufficient funds: insufficient funds]`, + err.Error(), + ) + + s.xplac = client.ResetXplac(s.xplac) +} diff --git a/client/tx_test.go b/client/tx_test.go index 73af6fe..280ad4d 100644 --- a/client/tx_test.go +++ b/client/tx_test.go @@ -110,7 +110,6 @@ func (suite *TestSuite) TestSimulateSignTx() { UnsignedFileName: unsignedTxPath, SignatureOnly: false, MultisigAddress: "", - FromAddress: from.Address.String(), Overwrite: false, Amino: false, } @@ -158,7 +157,6 @@ func (suite *TestSuite) TestSimulateSignatureOnly() { UnsignedFileName: unsignedTxPath, SignatureOnly: true, MultisigAddress: "", - FromAddress: acc.Address.String(), Overwrite: false, Amino: false, }