From b6301dcd602ee28b0dee5077e43272561dff1782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matev=C5=BE=20Jekovec?= Date: Mon, 2 Oct 2023 13:17:00 +0200 Subject: [PATCH] wallet: Export secp256k1 private key for BIP44 accounts --- cmd/common/wallet.go | 37 ++++++++++++++++++++++++++--------- cmd/wallet.go | 14 ++++++++++--- wallet/file/file.go | 13 ++++++++++-- wallet/file/secp256k1.go | 10 +++++----- wallet/file/secp256k1_test.go | 4 ++-- wallet/test/test.go | 5 ----- 6 files changed, 57 insertions(+), 26 deletions(-) diff --git a/cmd/common/wallet.go b/cmd/common/wallet.go index ffa24be3..67db5276 100644 --- a/cmd/common/wallet.go +++ b/cmd/common/wallet.go @@ -28,14 +28,8 @@ func LoadAccount(cfg *config.Config, name string) wallet.Account { return acc } - // Early check for whether the account exists so that we don't ask for passphrase first. - var ( - acfg *config.Account - exists bool - ) - if acfg, exists = cfg.Wallet.All[name]; !exists { - cobra.CheckErr(fmt.Errorf("account '%s' does not exist in the wallet", name)) - } + acfg, err := LoadAccountConfig(cfg, name) + cobra.CheckErr(err) af, err := acfg.LoadFactory() cobra.CheckErr(err) @@ -55,6 +49,20 @@ func LoadAccount(cfg *config.Config, name string) wallet.Account { return acc } +// LoadAccountConfig loads the config instance of the given named account. +func LoadAccountConfig(cfg *config.Config, name string) (*config.Account, error) { + if testName := helpers.ParseTestAccountAddress(name); testName != "" { + return LoadTestAccountConfig(testName) + } + + // Early check for whether the account exists so that we don't ask for passphrase first. + if acfg, exists := cfg.Wallet.All[name]; exists { + return acfg, nil + } + + return nil, fmt.Errorf("account '%s' does not exist in the wallet", name) +} + // LoadTestAccount loads the given named test account. func LoadTestAccount(name string) (wallet.Account, error) { if testKey, ok := testing.TestAccounts[name]; ok { @@ -70,9 +78,20 @@ func LoadTestAccountConfig(name string) (*config.Account, error) { return nil, err } + kind := "" + if testAcc.SignatureAddressSpec().Ed25519 != nil { + kind = wallet.AlgorithmEd25519Raw + } else if testAcc.SignatureAddressSpec().Secp256k1Eth != nil { + kind = wallet.AlgorithmSecp256k1Raw + } else if testAcc.SignatureAddressSpec().Sr25519 != nil { + kind = wallet.AlgorithmSr25519Raw + } else { + return nil, fmt.Errorf("unrecognized kind for account %s", name) + } + return &config.Account{ Description: "", - Kind: test.Kind, + Kind: kind, Address: testAcc.Address().String(), Config: nil, }, nil diff --git a/cmd/wallet.go b/cmd/wallet.go index 5641c6f4..fb4d3f6e 100644 --- a/cmd/wallet.go +++ b/cmd/wallet.go @@ -108,7 +108,8 @@ var ( name := args[0] acc := common.LoadAccount(config.Global(), name) - showPublicWalletInfo(name, acc) + accCfg, _ := common.LoadAccountConfig(config.Global(), name) + showPublicWalletInfo(name, acc, accCfg) }, } @@ -268,8 +269,9 @@ var ( fmt.Printf("WARNING: Exporting the account will expose secret key material!\n") acc := common.LoadAccount(config.Global(), name) + accCfg, _ := common.LoadAccountConfig(config.Global(), name) - showPublicWalletInfo(name, acc) + showPublicWalletInfo(name, acc, accCfg) fmt.Printf("Export:\n") fmt.Println(acc.UnsafeExport()) @@ -360,8 +362,14 @@ func (sf *accountEntitySignerFactory) Load( return sf.signer, nil } -func showPublicWalletInfo(name string, wallet wallet.Account) { +func showPublicWalletInfo(name string, wallet wallet.Account, accCfg *config.Account) { + kind := "" + if accCfg != nil { + kind = accCfg.PrettyKind() + } + fmt.Printf("Name: %s\n", name) + fmt.Printf("Kind: %s\n", kind) if signer := wallet.Signer(); signer != nil { fmt.Printf("Public Key: %s\n", signer.Public()) } diff --git a/wallet/file/file.go b/wallet/file/file.go index 6eea1e7c..5aa1b60a 100644 --- a/wallet/file/file.go +++ b/wallet/file/file.go @@ -504,7 +504,7 @@ func newAccount(state *secretState, cfg *accountConfig) (wallet.Account, error) }, nil case wallet.AlgorithmSecp256k1Bip44: // For Secp256k1-BIP-44 use the BIP-44 derivation scheme. - signer, err := Secp256k1FromMnemonic(state.Data, cfg.Number) + signer, _, err := Secp256k1FromMnemonic(state.Data, cfg.Number) if err != nil { return nil, fmt.Errorf("failed to initialize signer: %w", err) } @@ -605,7 +605,16 @@ func (a *fileAccount) SignatureAddressSpec() types.SignatureAddressSpec { } func (a *fileAccount) UnsafeExport() string { - return a.state.Data + mnemonicOrKey := a.state.Data + + // Derive the corresponding private key of the mnemonic for convenience. + switch a.cfg.Algorithm { + case wallet.AlgorithmSecp256k1Bip44: + _, pk, _ := Secp256k1FromMnemonic(a.state.Data, a.cfg.Number) + mnemonicOrKey += "\n" + hex.EncodeToString(pk) + } + + return mnemonicOrKey } func init() { diff --git a/wallet/file/secp256k1.go b/wallet/file/secp256k1.go index 79fa1d80..0ee331a1 100644 --- a/wallet/file/secp256k1.go +++ b/wallet/file/secp256k1.go @@ -20,21 +20,21 @@ const ( ) // Secp256k1FromMnemonic derives a signer using BIP-44 from given mnemonic. -func Secp256k1FromMnemonic(mnemonic string, number uint32) (sdkSignature.Signer, error) { +func Secp256k1FromMnemonic(mnemonic string, number uint32) (sdkSignature.Signer, []byte, error) { wallet, err := hdwallet.NewFromMnemonic(mnemonic) if err != nil { - return nil, fmt.Errorf("failed to parse mnemonic: %w", err) + return nil, nil, fmt.Errorf("failed to parse mnemonic: %w", err) } path := hdwallet.MustParseDerivationPath(fmt.Sprintf(Bip44DerivationPath, number)) account, err := wallet.Derive(path, false) if err != nil { - return nil, fmt.Errorf("failed to derive key from mnemonic: %w", err) + return nil, nil, fmt.Errorf("failed to derive key from mnemonic: %w", err) } pk, err := wallet.PrivateKeyBytes(account) if err != nil { - return nil, fmt.Errorf("failed to obtain generated private key: %w", err) + return nil, nil, fmt.Errorf("failed to obtain generated private key: %w", err) } - return secp256k1.NewSigner(pk), nil + return secp256k1.NewSigner(pk), pk, nil } // Secp256k1FromHex creates a signer from given hex-encoded private key. diff --git a/wallet/file/secp256k1_test.go b/wallet/file/secp256k1_test.go index 7d48d5b4..975f8d47 100644 --- a/wallet/file/secp256k1_test.go +++ b/wallet/file/secp256k1_test.go @@ -37,11 +37,11 @@ var mnemonics = []struct { func TestSecp256k1FromMnemonic(t *testing.T) { for _, m := range mnemonics { if m.valid { - signer, err := Secp256k1FromMnemonic(m.mnemonic, m.num) + signer, _, err := Secp256k1FromMnemonic(m.mnemonic, m.num) require.NoError(t, err) require.Equal(t, m.pubkey, signer.Public().String()) } else { - _, err := Secp256k1FromMnemonic(m.mnemonic, 0) + _, _, err := Secp256k1FromMnemonic(m.mnemonic, 0) require.Error(t, err) } } diff --git a/wallet/test/test.go b/wallet/test/test.go index 2d1d418a..b804baa8 100644 --- a/wallet/test/test.go +++ b/wallet/test/test.go @@ -14,11 +14,6 @@ import ( "github.com/oasisprotocol/cli/wallet" ) -const ( - // Kind is the account kind for the test accounts. - Kind = "test" -) - type testAccount struct { testKey testing.TestKey }