Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wallet export: Derive a private key for mnemonic accounts #137

Merged
merged 1 commit into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 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,11 +78,23 @@ func LoadTestAccountConfig(name string) (*config.Account, error) {
return nil, err
}

alg := ""
switch {
case testAcc.SignatureAddressSpec().Ed25519 != nil:
alg = wallet.AlgorithmEd25519Raw
case testAcc.SignatureAddressSpec().Secp256k1Eth != nil:
alg = wallet.AlgorithmSecp256k1Raw
case testAcc.SignatureAddressSpec().Sr25519 != nil:
alg = wallet.AlgorithmSr25519Raw
default:
return nil, fmt.Errorf("unrecognized algorithm for test account %s", name)
}

return &config.Account{
Description: "",
Kind: test.Kind,
Address: testAcc.Address().String(),
Config: nil,
Config: map[string]interface{}{"algorithm": alg},
}, nil
}

Expand Down
18 changes: 15 additions & 3 deletions cmd/wallet/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,22 @@ var exportCmd = &cobra.Command{

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())
key, mnemonic := acc.UnsafeExport()
if mnemonic != "" {
fmt.Printf("Secret mnemonic:\n")
fmt.Println(mnemonic)
if key != "" {
fmt.Printf("Derived secret key for account number %d:\n", accCfg.Config["number"])
fmt.Println(key)
}
}
if mnemonic == "" && key != "" {
fmt.Printf("Secret key:\n")
fmt.Println(key)
}
},
}
11 changes: 9 additions & 2 deletions cmd/wallet/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@ var showCmd = &cobra.Command{
name := args[0]

acc := common.LoadAccount(config.Global(), name)
showPublicWalletInfo(name, acc)
accCfg, _ := common.LoadAccountConfig(config.Global(), name)
showPublicWalletInfo(name, acc, accCfg)
},
}

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
2 changes: 1 addition & 1 deletion examples/wallet/export-ledger.out.static
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
WARNING: Exporting the account will expose secret key material!
Name: lenny
Kind: ledger (secp256k1-bip44:3)
Public Key: AhhT2TUkEZ7rMasLBvHcsGj4SUO7Iw36ELEpL0evZDV1
Ethereum address: 0x95e5e3C1BDD92cd4A0c14c62480DB5867946281D
Native address: oasis1qrmw4rhvp8ksj3yx6p2ftnkz864muc3re5jlgall
Export:
5 changes: 4 additions & 1 deletion examples/wallet/export-secp256k1-bip44.out.static
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ WARNING: Exporting the account will expose secret key material!
Unlock your account.
? Passphrase:
Name: eugene
Kind: file (secp256k1-bip44:0)
Public Key: ArEjDxsPfDvfeLlity4mjGzy8E/nI4umiC8vYQh+eh/c
Ethereum address: 0xBd16C6bF701a01DF1B5C11B14860b6bDbE776669
Native address: oasis1qrvzxld9rz83wv92lvnkpmr30c77kj2tvg0pednz
Export:
Secret mnemonic:
man ankle mystery favorite tone number ice west spare marriage control lucky life together neither
Derived secret key for account number 0:
c559cad1e71e0db1b3a657f47ca7a618bfb6a51a7294df72bcfca57aded5377e
3 changes: 2 additions & 1 deletion examples/wallet/export-secp256k1-raw.out.static
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ WARNING: Exporting the account will expose secret key material!
Unlock your account.
? Passphrase:
Name: emma
Kind: file (secp256k1-raw)
Public Key: Az8B2UpSUET0E3n9XMzr+HBvviQKcRvz6C6bJtRFWNYG
Ethereum address: 0xeEbE22411f579682F6f9D68f4C19B3581bCb576b
Native address: oasis1qph93wnfw8shu04pqyarvtjy4lytz3hp0c7tqnqh
Export:
Secret key:
4811ebbe4f29f32a758f6f7bad39deb97ea67f07350637e31c75795dc679262a
5 changes: 4 additions & 1 deletion examples/wallet/export.out.static
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ WARNING: Exporting the account will expose secret key material!
Unlock your account.
? Passphrase:
Name: oscar
Kind: file (ed25519-adr8:0)
Public Key: Bx6gOixnxy15tCs09ua5DcKyX9uo2Forb32O6Hyjoc8=
Native address: oasis1qp87hflmelnpqhzcqcw8rhzakq4elj7jzv090p3e
Export:
Secret mnemonic:
promote easily runway junior saddle gold flip believe wet example amount believe habit mixed pistol lemon increase moon rail mail fiction miss clip asset
Derived secret key for account number 0:
LHOUUJgVquTdi/3DVsS4caW4jQcvuFgl1Oag6BwlNvwHHqA6LGfHLXm0KzT25rkNwrJf26jYWitvfY7ofKOhzw==
1 change: 1 addition & 0 deletions examples/wallet/show-ledger.out.static
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Name: logan
Kind: ledger (ed25519-legacy:0)
Public Key: l+cuboPsOeuY1+kYlROrpmKgiiELmXSw9xl0WEg8cWE=
Native address: oasis1qpl4axynedmdrrgrg7dpw3yxc4a8crevr5dkuksl
1 change: 1 addition & 0 deletions examples/wallet/show-secp256k1.out.static
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Unlock your account.
? Passphrase:
Name: eugene
Kind: file (secp256k1-bip44:0)
Public Key: ArEjDxsPfDvfeLlity4mjGzy8E/nI4umiC8vYQh+eh/c
Ethereum address: 0xBd16C6bF701a01DF1B5C11B14860b6bDbE776669
Native address: oasis1qrvzxld9rz83wv92lvnkpmr30c77kj2tvg0pednz
1 change: 1 addition & 0 deletions examples/wallet/show.out.static
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Unlock your account.
? Passphrase:
Name: oscar
Kind: file (ed25519-adr8:0)
Public Key: Bx6gOixnxy15tCs09ua5DcKyX9uo2Forb32O6Hyjoc8=
Native address: oasis1qp87hflmelnpqhzcqcw8rhzakq4elj7jzv090p3e
14 changes: 14 additions & 0 deletions wallet/file/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@ package file

import (
"encoding/base64"
"fmt"

"github.com/oasisprotocol/curve25519-voi/primitives/ed25519"
"github.com/oasisprotocol/oasis-core/go/common/crypto/sakg"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
sdkSignature "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature"
ed255192 "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/ed25519"
)

// Ed25519FromMnemonic derives a signer using ADR-8 from given mnemonic.
func Ed25519FromMnemonic(mnemonic string, number uint32) (sdkSignature.Signer, []byte, error) {
signer, _, err := sakg.GetAccountSigner(mnemonic, "", number)
if err != nil {
return nil, nil, fmt.Errorf("failed to derive key from mnemonic: %w", err)
}

return ed255192.WrapSigner(signer), signer.(signature.UnsafeSigner).UnsafeBytes(), nil
}

// ed25519rawSigner is an in-memory signer that allows deserialization of raw ed25519 keys for use
// in imported accounts that don't use ADR 0008.
type ed25519rawSigner struct {
Expand Down
35 changes: 35 additions & 0 deletions wallet/file/ed25519_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package file //nolint: dupl

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestEd25519FromMnemonic(t *testing.T) {
mnemonics := []struct {
mnemonic string
num uint32
pubkey string
valid bool
}{
{mnemonic: "equip will roof matter pink blind book anxiety banner elbow sun young", num: 0, pubkey: "RWAfdhrxfbpQJDUp5ilzLxxY0I/92qhJEjhUBHVynYU=", valid: true},
{mnemonic: "equip will roof matter pink blind book anxiety banner elbow sun young", num: 1, pubkey: "J+0Eo8Dc7GWRwAHk6jB9ZcvXEsuQ2Fq3cDw17uB6d90=", valid: true},
{mnemonic: "equip will roof matter pink blind book anxiety banner elbow sun young", num: 2, pubkey: "GUVqPwzz9MxebOUt71fZK7PFplH6liayRs/sB6vChyQ=", valid: true},
{mnemonic: "equip will roof matter pink blind book anxiety banner elbow sun young", num: 3, pubkey: "klSQRiFP20cpv3pu5KO70PRjxHasyTOyx8zghFCavuQ=", valid: true},
{mnemonic: "actorr want explain gravity body drill bike update mask wool tell seven", pubkey: "", valid: false},
{mnemonic: "actor want explain gravity body drill bike update mask wool tell", pubkey: "", valid: false},
{mnemonic: "", pubkey: "", valid: false},
}

for _, m := range mnemonics {
if m.valid {
signer, _, err := Ed25519FromMnemonic(m.mnemonic, m.num)
require.NoError(t, err)
require.Equal(t, m.pubkey, signer.Public().String())
} else {
_, _, err := Ed25519FromMnemonic(m.mnemonic, 0)
require.Error(t, err)
}
}
}
Loading