diff --git a/cmd/subcommands/keys.go b/cmd/subcommands/keys.go index 304bed3..da8ea62 100644 --- a/cmd/subcommands/keys.go +++ b/cmd/subcommands/keys.go @@ -36,6 +36,8 @@ var ( blsFilePath string blsShardID uint32 blsCount uint32 + coinType uint32 // coin type used for key path derivation BIP-44 (1023 default for Harmony; 60 for Ethereum or for Metamask mnemonics) + keyIndex uint32 ppPrompt = fmt.Sprintf( "prompt for passphrase, otherwise use default passphrase: \"`%s`\"", c.DefaultPassphrase, ) @@ -214,7 +216,11 @@ func keysSub() []*cobra.Command { if !bip39.IsMnemonicValid(m) { return mnemonic.InvalidMnemonic } + acc.Mnemonic = m + acc.CoinType = &coinType + acc.HdIndexNumber = &keyIndex + if err := account.CreateNewLocalAccount(&acc); err != nil { return err } @@ -226,6 +232,8 @@ func keysSub() []*cobra.Command { } cmdRecoverMnemonic.Flags().BoolVar(&userProvidesPassphrase, "passphrase", false, ppPrompt) cmdRecoverMnemonic.Flags().StringVar(&passphraseFilePath, "passphrase-file", "", "path to a file containing the passphrase") + cmdRecoverMnemonic.Flags().Uint32Var(&coinType, "coin-type", 1023, "coin type used for key path derivation (1023 default for Harmony; 60 for Ethereum or for Metamask mnemonics)") + cmdRecoverMnemonic.Flags().Uint32Var(&keyIndex, "index", 0, "index of the key recovered from the provided mnemonic") cmdImportKS := &cobra.Command{ Use: "import-ks [ACCOUNT_NAME]", diff --git a/pkg/account/creation.go b/pkg/account/creation.go index 4832c80..9710693 100644 --- a/pkg/account/creation.go +++ b/pkg/account/creation.go @@ -18,6 +18,7 @@ type Creation struct { Mnemonic string HdAccountNumber *uint32 HdIndexNumber *uint32 + CoinType *uint32 } func New() string { @@ -34,8 +35,18 @@ func CreateNewLocalAccount(candidate *Creation) error { if candidate.Mnemonic == "" { candidate.Mnemonic = mnemonic.Generate() } - // Hardcoded index of 0 here. - private, _ := keys.FromMnemonicSeedAndPassphrase(candidate.Mnemonic, 0) + + index := uint32(0) + if candidate.HdIndexNumber != nil { + index = *candidate.HdIndexNumber + } + + coinType := uint32(1023) + if candidate.CoinType != nil { + coinType = *candidate.CoinType + } + + private, _ := keys.FromMnemonicSeedAndPassphrase(candidate.Mnemonic, int(index), int(coinType)) _, err := ks.ImportECDSA(private.ToECDSA(), candidate.Passphrase) if err != nil { return err diff --git a/pkg/keys/mnemonic.go b/pkg/keys/mnemonic.go index 04d208a..a69ac1f 100644 --- a/pkg/keys/mnemonic.go +++ b/pkg/keys/mnemonic.go @@ -11,13 +11,13 @@ import ( // FromMnemonicSeedAndPassphrase mimics the Harmony JS sdk in deriving the // private, public key pair from the mnemonic, its index, and empty string password. // Note that an index k would be the k-th key generated using the same mnemonic. -func FromMnemonicSeedAndPassphrase(mnemonic string, index int) (*secp256k1.PrivateKey, *secp256k1.PublicKey) { +func FromMnemonicSeedAndPassphrase(mnemonic string, index int, coinType int) (*secp256k1.PrivateKey, *secp256k1.PublicKey) { seed := bip39.NewSeed(mnemonic, "") master, ch := hd.ComputeMastersFromSeed(seed) private, _ := hd.DerivePrivateKeyForPath( master, ch, - fmt.Sprintf("44'/1023'/0'/0/%d", index), + fmt.Sprintf("44'/%d'/0'/0/%d", coinType, index), ) return secp256k1.PrivKeyFromBytes(secp256k1.S256(), private[:]) diff --git a/pkg/keys/mnemonic_test.go b/pkg/keys/mnemonic_test.go index 965b150..07f07b9 100644 --- a/pkg/keys/mnemonic_test.go +++ b/pkg/keys/mnemonic_test.go @@ -9,10 +9,11 @@ const ( index = 0 publicKey = "0x030b64624e60c6e6758711fdf00f7e873f40a22e647a6918ba73807fab194d09ba" privateKey = "0xda4bc68857640103942ce7dd22a9fcdb96f3cfe0380254e81352a94ac8262ed2" + coinType = 1023 ) func TestMnemonic(t *testing.T) { - private, public := FromMnemonicSeedAndPassphrase(phrase, index) + private, public := FromMnemonicSeedAndPassphrase(phrase, index, coinType) sk, pkCompressed := func() (string, string) { dump := EncodeHex(private, public) return dump.PrivateKey, dump.PublicKeyCompressed