From 1db10b00332b12e2a7f26af54796499c02dc4bf7 Mon Sep 17 00:00:00 2001 From: Frank Yang Date: Thu, 30 May 2019 20:46:38 +0800 Subject: [PATCH] Configurable Bip44 CoinType & HdPath for SDK users (#4300) Closes: #4144 --- .pending/features/sdk/4144-Configurable-Be | 1 + crypto/keys/hd/hdpath.go | 12 +++-------- crypto/keys/hd/hdpath_test.go | 11 +++++++--- crypto/keys/keybase.go | 9 +++++--- crypto/keys/keybase_test.go | 6 +++--- crypto/keys/lazy_keybase_test.go | 2 +- crypto/keys/types_test.go | 2 +- crypto/ledger_mock.go | 2 +- crypto/ledger_test.go | 12 +++++------ types/address.go | 7 +++++++ types/config.go | 24 +++++++++++++++++++++- 11 files changed, 60 insertions(+), 28 deletions(-) create mode 100644 .pending/features/sdk/4144-Configurable-Be diff --git a/.pending/features/sdk/4144-Configurable-Be b/.pending/features/sdk/4144-Configurable-Be new file mode 100644 index 0000000000..fedddc0e13 --- /dev/null +++ b/.pending/features/sdk/4144-Configurable-Be @@ -0,0 +1 @@ +#4144 Allow for configurable BIP44 HD path and coin type. diff --git a/crypto/keys/hd/hdpath.go b/crypto/keys/hd/hdpath.go index 5c1ad0359b..94ca286966 100644 --- a/crypto/keys/hd/hdpath.go +++ b/crypto/keys/hd/hdpath.go @@ -25,12 +25,6 @@ import ( "github.com/btcsuite/btcd/btcec" ) -// BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser. -const ( - BIP44Prefix = "44'/118'/" - FullFundraiserPath = BIP44Prefix + "0'/0/0" -) - // BIP44Params wraps BIP 44 params (5 level BIP 32 path). // To receive a canonical string representation ala // m / purpose' / coinType' / account' / change / addressIndex @@ -130,10 +124,10 @@ func isHardened(field string) bool { } // NewFundraiserParams creates a BIP 44 parameter object from the params: -// m / 44' / 118' / account' / 0 / address_index +// m / 44' / coinType' / account' / 0 / address_index // The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser. -func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params { - return NewParams(44, 118, account, false, addressIdx) +func NewFundraiserParams(account, coinType, addressIdx uint32) *BIP44Params { + return NewParams(44, coinType, account, false, addressIdx) } // DerivationPath returns the BIP44 fields as an array. diff --git a/crypto/keys/hd/hdpath_test.go b/crypto/keys/hd/hdpath_test.go index b0089603e9..e375094cb1 100644 --- a/crypto/keys/hd/hdpath_test.go +++ b/crypto/keys/hd/hdpath_test.go @@ -5,6 +5,8 @@ import ( "fmt" "testing" + "github.com/cosmos/cosmos-sdk/types" + bip39 "github.com/cosmos/go-bip39" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,11 +31,14 @@ func ExampleStringifyPathParams() { } func TestStringifyFundraiserPathParams(t *testing.T) { - path := NewFundraiserParams(4, 22) + path := NewFundraiserParams(4, types.CoinType, 22) require.Equal(t, "44'/118'/4'/0/22", path.String()) - path = NewFundraiserParams(4, 57) + path = NewFundraiserParams(4, types.CoinType, 57) require.Equal(t, "44'/118'/4'/0/57", path.String()) + + path = NewFundraiserParams(4, 12345, 57) + require.Equal(t, "44'/12345'/4'/0/57", path.String()) } func TestPathToArray(t *testing.T) { @@ -105,7 +110,7 @@ func ExampleSomeBIP32TestVecs() { fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)") fmt.Println() // cosmos - priv, err := DerivePrivateKeyForPath(master, ch, FullFundraiserPath) + priv, err := DerivePrivateKeyForPath(master, ch, types.FullFundraiserPath) if err != nil { fmt.Println("INVALID") } else { diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index a72b43238a..8d25aa64dc 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -115,13 +115,15 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string } seed := bip39.NewSeed(mnemonic, DefaultBIP39Passphrase) - info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) + fullFundraiserPath := types.GetConfig().GetFullFundraiserPath() + info, err = kb.persistDerivedKey(seed, passwd, name, fullFundraiserPath) return } // CreateAccount converts a mnemonic to a private key and persists it, encrypted with the given password. func (kb dbKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) { - hdPath := hd.NewFundraiserParams(account, index) + coinType := types.GetConfig().GetCoinType() + hdPath := hd.NewFundraiserParams(account, coinType, index) return kb.Derive(name, mnemonic, bip39Passwd, encryptPasswd, *hdPath) } @@ -142,7 +144,8 @@ func (kb dbKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, acco return nil, ErrUnsupportedSigningAlgo } - hdPath := hd.NewFundraiserParams(account, index) + coinType := types.GetConfig().GetCoinType() + hdPath := hd.NewFundraiserParams(account, coinType, index) priv, _, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath, hrp) if err != nil { return nil, err diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 67b1f1c0ae..7670d8afd3 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -383,7 +383,7 @@ func TestSeedPhrase(t *testing.T) { require.NotNil(t, err) // let us re-create it from the mnemonic-phrase - params := *hd.NewFundraiserParams(0, 0) + params := *hd.NewFundraiserParams(0, sdk.CoinType, 0) newInfo, err := cstore.Derive(n2, mnemonic, DefaultBIP39Passphrase, p2, params) require.NoError(t, err) require.Equal(t, n2, newInfo.GetName()) @@ -406,8 +406,8 @@ func ExampleNew() { // return info here just like in List fmt.Println(bob.GetName()) } - cstore.CreateMnemonic("Alice", English, "secret", sec) - cstore.CreateMnemonic("Carl", English, "mitm", sec) + _, _, _ = cstore.CreateMnemonic("Alice", English, "secret", sec) + _, _, _ = cstore.CreateMnemonic("Carl", English, "mitm", sec) info, _ := cstore.List() for _, i := range info { fmt.Println(i.GetName()) diff --git a/crypto/keys/lazy_keybase_test.go b/crypto/keys/lazy_keybase_test.go index face5edef2..8ad9f1c64d 100644 --- a/crypto/keys/lazy_keybase_test.go +++ b/crypto/keys/lazy_keybase_test.go @@ -335,7 +335,7 @@ func TestLazySeedPhrase(t *testing.T) { require.NotNil(t, err) // let us re-create it from the mnemonic-phrase - params := *hd.NewFundraiserParams(0, 0) + params := *hd.NewFundraiserParams(0, sdk.CoinType, 0) newInfo, err := kb.Derive(n2, mnemonic, DefaultBIP39Passphrase, p2, params) require.NoError(t, err) require.Equal(t, n2, newInfo.GetName()) diff --git a/crypto/keys/types_test.go b/crypto/keys/types_test.go index 6475eb8907..feb4131c2d 100644 --- a/crypto/keys/types_test.go +++ b/crypto/keys/types_test.go @@ -19,7 +19,7 @@ func Test_writeReadLedgerInfo(t *testing.T) { lInfo := ledgerInfo{ "some_name", tmpKey, - *hd.NewFundraiserParams(5, 1)} + *hd.NewFundraiserParams(5, types.CoinType, 1)} assert.Equal(t, TypeLedger, lInfo.GetType()) path, err := lInfo.GetPath() diff --git a/crypto/ledger_mock.go b/crypto/ledger_mock.go index cf68cd404c..8d2509bbe3 100644 --- a/crypto/ledger_mock.go +++ b/crypto/ledger_mock.go @@ -41,7 +41,7 @@ func (mock LedgerSECP256K1Mock) GetPublicKeySECP256K1(derivationPath []uint32) ( if derivationPath[0] != 44 { return nil, errors.New("Invalid derivation path") } - if derivationPath[1] != 118 { + if derivationPath[1] != types.CoinType { return nil, errors.New("Invalid derivation path") } diff --git a/crypto/ledger_test.go b/crypto/ledger_test.go index d7f30eefcc..5a3f639e0d 100644 --- a/crypto/ledger_test.go +++ b/crypto/ledger_test.go @@ -24,7 +24,7 @@ func TestLedgerErrorHandling(t *testing.T) { } func TestPublicKeyUnsafe(t *testing.T) { - path := *hd.NewFundraiserParams(0, 0) + path := *hd.NewFundraiserParams(0, sdk.CoinType, 0) priv, err := NewPrivKeyLedgerSecp256k1Unsafe(path) require.Nil(t, err, "%s", err) require.NotNil(t, priv) @@ -63,7 +63,7 @@ func TestPublicKeyUnsafeHDPath(t *testing.T) { // Check with device for i := uint32(0); i < 10; i++ { - path := *hd.NewFundraiserParams(0, i) + path := *hd.NewFundraiserParams(0, sdk.CoinType, i) fmt.Printf("Checking keys at %v\n", path) priv, err := NewPrivKeyLedgerSecp256k1Unsafe(path) @@ -99,7 +99,7 @@ func TestPublicKeyUnsafeHDPath(t *testing.T) { } func TestPublicKeySafe(t *testing.T) { - path := *hd.NewFundraiserParams(0, 0) + path := *hd.NewFundraiserParams(0, sdk.CoinType, 0) priv, addr, err := NewPrivKeyLedgerSecp256k1(path, "cosmos") require.Nil(t, err, "%s", err) @@ -154,7 +154,7 @@ func TestPublicKeyHDPath(t *testing.T) { // Check with device for i := uint32(0); i < 10; i++ { - path := *hd.NewFundraiserParams(0, i) + path := *hd.NewFundraiserParams(0, sdk.CoinType, i) fmt.Printf("Checking keys at %v\n", path) priv, addr, err := NewPrivKeyLedgerSecp256k1(path, "cosmos") @@ -208,7 +208,7 @@ func TestSignaturesHD(t *testing.T) { for account := uint32(0); account < 100; account += 30 { msg := getFakeTx(account) - path := *hd.NewFundraiserParams(account, account/5) + path := *hd.NewFundraiserParams(account, sdk.CoinType, account/5) fmt.Printf("Checking signature at %v --- PLEASE REVIEW AND ACCEPT IN THE DEVICE\n", path) priv, err := NewPrivKeyLedgerSecp256k1Unsafe(path) @@ -225,7 +225,7 @@ func TestSignaturesHD(t *testing.T) { func TestRealLedgerSecp256k1(t *testing.T) { msg := getFakeTx(50) - path := *hd.NewFundraiserParams(0, 0) + path := *hd.NewFundraiserParams(0, sdk.CoinType, 0) priv, err := NewPrivKeyLedgerSecp256k1Unsafe(path) require.Nil(t, err, "%s", err) diff --git a/types/address.go b/types/address.go index 08ff7916f0..7fa6074332 100644 --- a/types/address.go +++ b/types/address.go @@ -20,6 +20,13 @@ const ( // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address Bech32MainPrefix = "cosmos" + // Atom in https://github.com/satoshilabs/slips/blob/master/slip-0044.md + CoinType = 118 + + // BIP44Prefix is the parts of the BIP32 HD path that are fixed by + // what we used during the fundraiser. + FullFundraiserPath = "44'/118'/0'/0/0" + // PrefixAccount is the prefix for account keys PrefixAccount = "acc" // PrefixValidator is the prefix for validator keys diff --git a/types/config.go b/types/config.go index dae512d6fe..96d12276cc 100644 --- a/types/config.go +++ b/types/config.go @@ -10,6 +10,8 @@ type Config struct { mtx sync.RWMutex sealed bool bech32AddressPrefix map[string]string + coinType uint32 + fullFundraiserPath string txEncoder TxEncoder addressVerifier func([]byte) error } @@ -26,7 +28,9 @@ var ( "validator_pub": Bech32PrefixValPub, "consensus_pub": Bech32PrefixConsPub, }, - txEncoder: nil, + coinType: CoinType, + fullFundraiserPath: FullFundraiserPath, + txEncoder: nil, } ) @@ -81,6 +85,16 @@ func (config *Config) SetAddressVerifier(addressVerifier func([]byte) error) { config.addressVerifier = addressVerifier } +func (config *Config) SetCoinType(coinType uint32) { + config.assertNotSealed() + config.coinType = coinType +} + +func (config *Config) SetFullFundraiserPath(fullFundraiserPath string) { + config.assertNotSealed() + config.fullFundraiserPath = fullFundraiserPath +} + // Seal seals the config such that the config state could not be modified further func (config *Config) Seal() *Config { config.mtx.Lock() @@ -129,3 +143,11 @@ func (config *Config) GetTxEncoder() TxEncoder { func (config *Config) GetAddressVerifier() func([]byte) error { return config.addressVerifier } + +func (config *Config) GetCoinType() uint32 { + return config.coinType +} + +func (config *Config) GetFullFundraiserPath() string { + return config.fullFundraiserPath +}