diff --git a/plugins/recover/client/cli/commands.go b/plugins/recover/client/cli/commands.go new file mode 100644 index 000000000..73be057dd --- /dev/null +++ b/plugins/recover/client/cli/commands.go @@ -0,0 +1,22 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/spf13/cobra" +) + +func AddCommands(cmd *cobra.Command, cdc *codec.Codec) { + airdropCmd := &cobra.Command{ + Use: "recover", + Short: "recover commands", + } + + airdropCmd.AddCommand( + client.PostCommands( + SignTokenRecoverRequestCmd(cdc), + )..., + ) + + cmd.AddCommand(airdropCmd) +} diff --git a/plugins/recover/client/cli/tx.go b/plugins/recover/client/cli/tx.go new file mode 100644 index 000000000..c09ab720e --- /dev/null +++ b/plugins/recover/client/cli/tx.go @@ -0,0 +1,107 @@ +package cli + +import ( + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/tendermint/tendermint/crypto/tmhash" + cmn "github.com/tendermint/tendermint/libs/common" + + airdrop "github.com/bnb-chain/node/plugins/recover" +) + +const ( + flagAmount = "amount" + flagTokenSymbol = "token-symbol" + flagRecipient = "recipient" +) + +func SignTokenRecoverRequestCmd(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "sign-token-recover-request", + Short: "get token recover request sign data", + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + amount := viper.GetInt64(flagAmount) + tokenSymbol := viper.GetString(flagTokenSymbol) + recipient := viper.GetString(flagRecipient) + msg := airdrop.NewTokenRecoverRequestMsg(tokenSymbol, uint64(amount), common.HexToAddress(recipient).Hex()) + + sdkErr := msg.ValidateBasic() + if sdkErr != nil { + return fmt.Errorf("%v", sdkErr.Data()) + } + return SignAndPrint(cliCtx, txBldr, msg) + }, + } + + cmd.Flags().String(flagTokenSymbol, "", "owner token symbol") + cmd.Flags().Int64(flagAmount, 0, "amount of token") + cmd.Flags().String(flagRecipient, "", "bsc recipient address") + + return cmd +} + +func SignAndPrint(ctx context.CLIContext, builder authtxb.TxBuilder, msg sdk.Msg) error { + name, err := ctx.GetFromName() + if err != nil { + return err + } + + passphrase, err := keys.GetPassphrase(name) + if err != nil { + return err + } + + // build and sign the transaction + stdMsg, err := builder.Build([]sdk.Msg{msg}) + if err != nil { + return err + } + txBytes, err := builder.Sign(name, passphrase, stdMsg) + if err != nil { + return err + } + + var tx auth.StdTx + if err = builder.Codec.UnmarshalBinaryLengthPrefixed(txBytes, &tx); err == nil { + json, err := builder.Codec.MarshalJSON(tx) + if err == nil { + fmt.Printf("TX JSON: %s\n", json) + } + } + hexBytes := make([]byte, len(txBytes)*2) + hex.Encode(hexBytes, txBytes) + txHash := cmn.HexBytes(tmhash.Sum(txBytes)).String() + fmt.Printf("Transaction hash: %s, Transaction hex: %s\n", txHash, hexBytes) + + fmt.Println("Sign Message: ", string(stdMsg.Bytes())) + fmt.Println("Sign Message Hash: ", hex.EncodeToString(crypto.Sha256(stdMsg.Bytes()))) + sig := tx.GetSignatures()[0] + fmt.Printf("Signature: %s\n", hex.EncodeToString(sig.Signature)) + var originPubKey secp256k1.PubKeySecp256k1 + err = builder.Codec.UnmarshalBinaryBare(sig.PubKey.Bytes(), &originPubKey) + if err != nil { + return err + } + fmt.Printf("PubKey: %s\n", hex.EncodeToString(originPubKey)) + return nil +} diff --git a/plugins/recover/msg.go b/plugins/recover/msg.go new file mode 100644 index 000000000..7c87b3b2f --- /dev/null +++ b/plugins/recover/msg.go @@ -0,0 +1,96 @@ +package recover + +import ( + "encoding/hex" + "encoding/json" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" +) + +const ( + Route = "recover" + MsgType = "request_token" +) + +var _ sdk.Msg = TokenRecoverRequest{} + +func NewTokenRecoverRequestMsg(tokenSymbol string, amount uint64, recipient string) TokenRecoverRequest { + return TokenRecoverRequest{ + TokenSymbol: tokenSymbol, + Amount: amount, + Recipient: recipient, + } +} + +func newTokenRecoverRequestSignData(tokenSymbol string, amount uint64, recipient string) tokenRecoverRequestSignData { + var tokenSymbolBytes [32]byte + copy(tokenSymbolBytes[:], []byte(tokenSymbol)) + + return tokenRecoverRequestSignData{ + TokenSymbol: hex.EncodeToString(tokenSymbolBytes[:]), + Amount: hex.EncodeToString(big.NewInt(int64(amount)).FillBytes(make([]byte, 32))), + Recipient: recipient, + } +} + +type tokenRecoverRequestSignData struct { + TokenSymbol string `json:"token_symbol"` // hex string(32 bytes) + Amount string `json:"amount"` // hex string(32 bytes) + Recipient string `json:"recipient"` // eth address(20 bytes) +} + +type TokenRecoverRequest struct { + TokenSymbol string `json:"token_symbol"` + Amount uint64 `json:"amount"` + Recipient string `json:"recipient"` // eth address +} + +// GetInvolvedAddresses implements types.Msg. +func (msg TokenRecoverRequest) GetInvolvedAddresses() []sdk.AccAddress { + return msg.GetSigners() +} + +// GetSignBytes implements types.Msg. +func (msg TokenRecoverRequest) GetSignBytes() []byte { + b, err := json.Marshal(newTokenRecoverRequestSignData(msg.TokenSymbol, msg.Amount, msg.Recipient)) + if err != nil { + panic(err) + } + return b +} + +// GetSigners implements types.Msg. +func (m TokenRecoverRequest) GetSigners() []sdk.AccAddress { + // This is not a real on-chain transaction + // We can get signer from the public key. + return []sdk.AccAddress{} +} + +// Route implements types.Msg. +func (TokenRecoverRequest) Route() string { + return Route +} + +// Type implements types.Msg. +func (TokenRecoverRequest) Type() string { + return MsgType +} + +// ValidateBasic implements types.Msg. +func (msg TokenRecoverRequest) ValidateBasic() sdk.Error { + if msg.TokenSymbol == "" { + return sdk.ErrUnknownRequest("Invalid token symbol") + } + + if msg.Amount == 0 { + return sdk.ErrUnknownRequest("Invalid amount, should be greater than 0") + } + + if !common.IsHexAddress(msg.Recipient) { + return sdk.ErrInvalidAddress("Invalid recipient address") + } + + return nil +} diff --git a/plugins/recover/wire.go b/plugins/recover/wire.go new file mode 100644 index 000000000..0624360a5 --- /dev/null +++ b/plugins/recover/wire.go @@ -0,0 +1,10 @@ +package recover + +import ( + "github.com/bnb-chain/node/wire" +) + +// Register concrete types on wire codec +func RegisterWire(cdc *wire.Codec) { + cdc.RegisterConcrete(TokenRecoverRequest{}, "recover/TokenRecoverRequest", nil) +}