Skip to content
This repository was archived by the owner on Nov 30, 2021. It is now read-only.

Commit 72fc3ca

Browse files
authored
Transaction signing and encoding (#95)
* WIP setting up evm tx command and updating emint keys output * Fix linting issue * Wip restructuring to allow for ethereum signing and encoding * WIP setting up keybase and context to use Ethermint keys * Fixed encoding and decoding of emint keys * Adds command for generating explicit ethereum tx * Fixed evm route for handling tx * Fixed tx and msg encoding which allows transactions to be sent * Added relevant documentation for changes and cleaned up code * Added documentation and indicators why code was overriden
1 parent 4c29c48 commit 72fc3ca

File tree

20 files changed

+1075
-71
lines changed

20 files changed

+1075
-71
lines changed

app/ante.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func validateEthTxCheckTx(
231231
// validate sender/signature
232232
signer, err := ethTxMsg.VerifySig(chainID)
233233
if err != nil {
234-
return sdk.ErrUnauthorized("signature verification failed").Result()
234+
return sdk.ErrUnauthorized(fmt.Sprintf("signature verification failed: %s", err)).Result()
235235
}
236236

237237
// validate account (nonce and balance checks)

cmd/emintcli/main.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import (
1313
sdk "github.com/cosmos/cosmos-sdk/types"
1414
emintkeys "github.com/cosmos/ethermint/keys"
1515

16+
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
17+
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
18+
1619
emintapp "github.com/cosmos/ethermint/app"
1720
"github.com/spf13/cobra"
1821
"github.com/spf13/viper"
@@ -48,7 +51,7 @@ func main() {
4851
sdkrpc.StatusCommand(),
4952
client.ConfigCmd(emintapp.DefaultCLIHome),
5053
queryCmd(cdc),
51-
// TODO: Set up tx command
54+
txCmd(cdc),
5255
// TODO: Set up rest routes (if included, different from web3 api)
5356
rpc.Web3RpcCmd(cdc),
5457
client.LineBreak,
@@ -83,6 +86,28 @@ func queryCmd(cdc *amino.Codec) *cobra.Command {
8386
return queryCmd
8487
}
8588

89+
func txCmd(cdc *amino.Codec) *cobra.Command {
90+
txCmd := &cobra.Command{
91+
Use: "tx",
92+
Short: "Transactions subcommands",
93+
}
94+
95+
txCmd.AddCommand(
96+
bankcmd.SendTxCmd(cdc),
97+
client.LineBreak,
98+
authcmd.GetSignCommand(cdc),
99+
client.LineBreak,
100+
authcmd.GetBroadcastCommand(cdc),
101+
authcmd.GetEncodeCommand(cdc),
102+
client.LineBreak,
103+
)
104+
105+
// add modules' tx commands
106+
emintapp.ModuleBasics.AddTxCommands(txCmd, cdc)
107+
108+
return txCmd
109+
}
110+
86111
func initConfig(cmd *cobra.Command) error {
87112
home, err := cmd.PersistentFlags().GetString(cli.HomeFlag)
88113
if err != nil {

crypto/codec.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@ import (
66

77
var cryptoCodec = codec.New()
88

9+
const (
10+
// Amino encoding names
11+
PrivKeyAminoName = "crypto/PrivKeySecp256k1"
12+
PubKeyAminoName = "crypto/PubKeySecp256k1"
13+
)
14+
915
func init() {
1016
RegisterCodec(cryptoCodec)
1117
}
1218

1319
// RegisterCodec registers all the necessary types with amino for the given
1420
// codec.
1521
func RegisterCodec(cdc *codec.Codec) {
16-
cdc.RegisterConcrete(PubKeySecp256k1{}, "crypto/PubKeySecp256k1", nil)
17-
cdc.RegisterConcrete(PrivKeySecp256k1{}, "crypto/PrivKeySecp256k1", nil)
22+
cdc.RegisterConcrete(PubKeySecp256k1{}, PubKeyAminoName, nil)
23+
24+
cdc.RegisterConcrete(PrivKeySecp256k1{}, PrivKeyAminoName, nil)
1825
}

crypto/encoding/amino.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package encoding
2+
3+
import (
4+
emintcrypto "github.com/cosmos/ethermint/crypto"
5+
amino "github.com/tendermint/go-amino"
6+
tmcrypto "github.com/tendermint/tendermint/crypto"
7+
)
8+
9+
var cdc = amino.NewCodec()
10+
11+
func init() {
12+
RegisterAmino(cdc)
13+
}
14+
15+
// RegisterAmino registers all crypto related types in the given (amino) codec.
16+
func RegisterAmino(cdc *amino.Codec) {
17+
// These are all written here instead of
18+
cdc.RegisterInterface((*tmcrypto.PubKey)(nil), nil)
19+
cdc.RegisterConcrete(emintcrypto.PubKeySecp256k1{}, emintcrypto.PubKeyAminoName, nil)
20+
21+
cdc.RegisterInterface((*tmcrypto.PrivKey)(nil), nil)
22+
cdc.RegisterConcrete(emintcrypto.PrivKeySecp256k1{}, emintcrypto.PrivKeyAminoName, nil)
23+
}
24+
25+
// PrivKeyFromBytes unmarshalls emint private key from encoded bytes
26+
func PrivKeyFromBytes(privKeyBytes []byte) (privKey tmcrypto.PrivKey, err error) {
27+
err = cdc.UnmarshalBinaryBare(privKeyBytes, &privKey)
28+
return
29+
}
30+
31+
// PubKeyFromBytes unmarshalls emint public key from encoded bytes
32+
func PubKeyFromBytes(pubKeyBytes []byte) (pubKey tmcrypto.PubKey, err error) {
33+
err = cdc.UnmarshalBinaryBare(pubKeyBytes, &pubKey)
34+
return
35+
}

crypto/encoding/amino_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package encoding
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
emintcrypto "github.com/cosmos/ethermint/crypto"
9+
)
10+
11+
func TestKeyEncodingDecoding(t *testing.T) {
12+
// Priv Key encoding and decoding
13+
privKey, err := emintcrypto.GenerateKey()
14+
require.NoError(t, err)
15+
privBytes := privKey.Bytes()
16+
17+
decodedPriv, err := PrivKeyFromBytes(privBytes)
18+
require.NoError(t, err)
19+
require.Equal(t, privKey, decodedPriv)
20+
21+
// Pub key encoding and decoding
22+
pubKey := privKey.PubKey()
23+
pubBytes := pubKey.Bytes()
24+
25+
decodedPub, err := PubKeyFromBytes(pubBytes)
26+
require.NoError(t, err)
27+
require.Equal(t, pubKey, decodedPub)
28+
}

crypto/keys/keybase.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
1414
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
1515
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
16-
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
16+
"github.com/cosmos/ethermint/crypto/keys/mintkey"
1717
"github.com/cosmos/cosmos-sdk/types"
1818

1919
bip39 "github.com/cosmos/go-bip39"

crypto/keys/mintkey/mintkey.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package mintkey
2+
3+
import (
4+
"encoding/hex"
5+
"fmt"
6+
7+
"github.com/tendermint/crypto/bcrypt"
8+
9+
emintEncoding "github.com/cosmos/ethermint/crypto/encoding"
10+
"github.com/tendermint/tendermint/crypto"
11+
"github.com/tendermint/tendermint/crypto/armor"
12+
13+
"github.com/tendermint/tendermint/crypto/xsalsa20symmetric"
14+
15+
cmn "github.com/tendermint/tendermint/libs/common"
16+
17+
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
18+
)
19+
20+
const (
21+
blockTypePrivKey = "ETHERMINT PRIVATE KEY"
22+
blockTypeKeyInfo = "ETHERMINT KEY INFO"
23+
blockTypePubKey = "ETHERMINT PUBLIC KEY"
24+
)
25+
26+
// Make bcrypt security parameter var, so it can be changed within the lcd test
27+
// Making the bcrypt security parameter a var shouldn't be a security issue:
28+
// One can't verify an invalid key by maliciously changing the bcrypt
29+
// parameter during a runtime vulnerability. The main security
30+
// threat this then exposes would be something that changes this during
31+
// runtime before the user creates their key. This vulnerability must
32+
// succeed to update this to that same value before every subsequent call
33+
// to the keys command in future startups / or the attacker must get access
34+
// to the filesystem. However, with a similar threat model (changing
35+
// variables in runtime), one can cause the user to sign a different tx
36+
// than what they see, which is a significantly cheaper attack then breaking
37+
// a bcrypt hash. (Recall that the nonce still exists to break rainbow tables)
38+
// For further notes on security parameter choice, see README.md
39+
var BcryptSecurityParameter = 12
40+
41+
//-----------------------------------------------------------------
42+
// add armor
43+
44+
// Armor the InfoBytes
45+
func ArmorInfoBytes(bz []byte) string {
46+
return armorBytes(bz, blockTypeKeyInfo)
47+
}
48+
49+
// Armor the PubKeyBytes
50+
func ArmorPubKeyBytes(bz []byte) string {
51+
return armorBytes(bz, blockTypePubKey)
52+
}
53+
54+
func armorBytes(bz []byte, blockType string) string {
55+
header := map[string]string{
56+
"type": "Info",
57+
"version": "0.0.0",
58+
}
59+
return armor.EncodeArmor(blockType, header, bz)
60+
}
61+
62+
//-----------------------------------------------------------------
63+
// remove armor
64+
65+
// Unarmor the InfoBytes
66+
func UnarmorInfoBytes(armorStr string) (bz []byte, err error) {
67+
return unarmorBytes(armorStr, blockTypeKeyInfo)
68+
}
69+
70+
// Unarmor the PubKeyBytes
71+
func UnarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
72+
return unarmorBytes(armorStr, blockTypePubKey)
73+
}
74+
75+
func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
76+
bType, header, bz, err := armor.DecodeArmor(armorStr)
77+
if err != nil {
78+
return
79+
}
80+
if bType != blockType {
81+
err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType)
82+
return
83+
}
84+
if header["version"] != "0.0.0" {
85+
err = fmt.Errorf("Unrecognized version: %v", header["version"])
86+
return
87+
}
88+
return
89+
}
90+
91+
//-----------------------------------------------------------------
92+
// encrypt/decrypt with armor
93+
94+
// Encrypt and armor the private key.
95+
func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
96+
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
97+
header := map[string]string{
98+
"kdf": "bcrypt",
99+
"salt": fmt.Sprintf("%X", saltBytes),
100+
}
101+
armorStr := armor.EncodeArmor(blockTypePrivKey, header, encBytes)
102+
return armorStr
103+
}
104+
105+
// encrypt the given privKey with the passphrase using a randomly
106+
// generated salt and the xsalsa20 cipher. returns the salt and the
107+
// encrypted priv key.
108+
func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
109+
saltBytes = crypto.CRandBytes(16)
110+
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
111+
if err != nil {
112+
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
113+
}
114+
key = crypto.Sha256(key) // get 32 bytes
115+
privKeyBytes := privKey.Bytes()
116+
return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
117+
}
118+
119+
// Unarmor and decrypt the private key.
120+
func UnarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
121+
var privKey crypto.PrivKey
122+
blockType, header, encBytes, err := armor.DecodeArmor(armorStr)
123+
if err != nil {
124+
return privKey, err
125+
}
126+
if blockType != blockTypePrivKey {
127+
return privKey, fmt.Errorf("Unrecognized armor type: %v", blockType)
128+
}
129+
if header["kdf"] != "bcrypt" {
130+
return privKey, fmt.Errorf("Unrecognized KDF type: %v", header["KDF"])
131+
}
132+
if header["salt"] == "" {
133+
return privKey, fmt.Errorf("Missing salt bytes")
134+
}
135+
saltBytes, err := hex.DecodeString(header["salt"])
136+
if err != nil {
137+
return privKey, fmt.Errorf("Error decoding salt: %v", err.Error())
138+
}
139+
privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase)
140+
return privKey, err
141+
}
142+
143+
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
144+
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
145+
if err != nil {
146+
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
147+
}
148+
key = crypto.Sha256(key) // Get 32 bytes
149+
privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key)
150+
if err != nil && err.Error() == "Ciphertext decryption failed" {
151+
return privKey, keyerror.NewErrWrongPassword()
152+
} else if err != nil {
153+
return privKey, err
154+
}
155+
privKey, err = emintEncoding.PrivKeyFromBytes(privKeyBytes)
156+
return privKey, err
157+
}

0 commit comments

Comments
 (0)