Skip to content

Commit

Permalink
Merge pull request #137 from oasisprotocol/matevz/feature/walletinfo
Browse files Browse the repository at this point in the history
wallet export: Derive a private key for mnemonic accounts
  • Loading branch information
matevz authored Jan 4, 2024
2 parents 038c8b9 + e0f3f92 commit def2114
Show file tree
Hide file tree
Showing 20 changed files with 318 additions and 128 deletions.
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

0 comments on commit def2114

Please sign in to comment.