Skip to content

Commit

Permalink
wallet: Export secp256k1 private key for BIP44 accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
matevz committed Oct 2, 2023
1 parent d35c41e commit b6301dc
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 26 deletions.
37 changes: 28 additions & 9 deletions cmd/common/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand All @@ -70,9 +78,20 @@ func LoadTestAccountConfig(name string) (*config.Account, error) {
return nil, err
}

kind := ""
if testAcc.SignatureAddressSpec().Ed25519 != nil {

Check failure on line 82 in cmd/common/wallet.go

View workflow job for this annotation

GitHub Actions / lint

ifElseChain: rewrite if-else to switch statement (gocritic)
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
Expand Down
14 changes: 11 additions & 3 deletions cmd/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
}

Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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 := "<unknown>"
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())
}
Expand Down
13 changes: 11 additions & 2 deletions wallet/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {

Check failure on line 611 in wallet/file/file.go

View workflow job for this annotation

GitHub Actions / lint

singleCaseSwitch: should rewrite switch statement to if statement (gocritic)
case wallet.AlgorithmSecp256k1Bip44:
_, pk, _ := Secp256k1FromMnemonic(a.state.Data, a.cfg.Number)
mnemonicOrKey += "\n" + hex.EncodeToString(pk)
}

return mnemonicOrKey
}

func init() {
Expand Down
10 changes: 5 additions & 5 deletions wallet/file/secp256k1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions wallet/file/secp256k1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
5 changes: 0 additions & 5 deletions wallet/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit b6301dc

Please sign in to comment.