Skip to content

Commit

Permalink
Configurable Bip44 CoinType & HdPath for SDK users (cosmos#4300)
Browse files Browse the repository at this point in the history
Closes: cosmos#4144
  • Loading branch information
Frank Yang authored and Yun committed Jun 17, 2019
1 parent 2fe1aff commit 97338f9
Show file tree
Hide file tree
Showing 11 changed files with 60 additions and 28 deletions.
1 change: 1 addition & 0 deletions .pending/features/sdk/4144-Configurable-Be
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#4144 Allow for configurable BIP44 HD path and coin type.
12 changes: 3 additions & 9 deletions crypto/keys/hd/hdpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
11 changes: 8 additions & 3 deletions crypto/keys/hd/hdpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 6 additions & 3 deletions crypto/keys/keybase.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions crypto/keys/keybase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,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())
Expand All @@ -405,8 +405,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())
Expand Down
2 changes: 1 addition & 1 deletion crypto/keys/lazy_keybase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion crypto/keys/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion crypto/ledger_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,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")
}

Expand Down
12 changes: 6 additions & 6 deletions crypto/ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,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)
Expand Down Expand Up @@ -61,7 +61,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)
Expand Down Expand Up @@ -97,7 +97,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)
Expand Down Expand Up @@ -152,7 +152,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")
Expand Down Expand Up @@ -206,7 +206,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)
Expand All @@ -223,7 +223,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)

Expand Down
7 changes: 7 additions & 0 deletions types/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 23 additions & 1 deletion types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type Config struct {
mtx sync.RWMutex
sealed bool
bech32AddressPrefix map[string]string
coinType uint32
fullFundraiserPath string
txEncoder TxEncoder
}

Expand All @@ -25,7 +27,9 @@ var (
"validator_pub": Bech32PrefixValPub,
"consensus_pub": Bech32PrefixConsPub,
},
txEncoder: nil,
coinType: CoinType,
fullFundraiserPath: FullFundraiserPath,
txEncoder: nil,
}
)

Expand Down Expand Up @@ -73,6 +77,16 @@ func (config *Config) SetTxEncoder(encoder TxEncoder) {
config.txEncoder = encoder
}

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()
Expand Down Expand Up @@ -116,3 +130,11 @@ func (config *Config) GetBech32ConsensusPubPrefix() string {
func (config *Config) GetTxEncoder() TxEncoder {
return config.txEncoder
}

func (config *Config) GetCoinType() uint32 {
return config.coinType
}

func (config *Config) GetFullFundraiserPath() string {
return config.fullFundraiserPath
}

0 comments on commit 97338f9

Please sign in to comment.