Skip to content

Commit

Permalink
wallet: Derive private key on wallet export
Browse files Browse the repository at this point in the history
  • Loading branch information
matevz committed Dec 22, 2023
1 parent 69677ae commit 1803dfd
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 123 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
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)
}
}
}
90 changes: 46 additions & 44 deletions wallet/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@ import (

"github.com/AlecAivazis/survey/v2"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/mitchellh/mapstructure"
flag "github.com/spf13/pflag"
bip39 "github.com/tyler-smith/go-bip39"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/sha3"

"github.com/oasisprotocol/deoxysii"
"github.com/oasisprotocol/oasis-core/go/common/crypto/sakg"
coreSignature "github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/ed25519"
Expand Down Expand Up @@ -58,11 +56,6 @@ func SupportedAlgorithmsForImport(kind *wallet.ImportKind) []string {
}
}

type accountConfig struct {
Algorithm string `mapstructure:"algorithm"`
Number uint32 `mapstructure:"number,omitempty"`
}

type secretState struct {
// Algorithm is the cryptographic algorithm used by the account.
Algorithm string `json:"algorithm"`
Expand Down Expand Up @@ -184,8 +177,8 @@ func (af *fileAccountFactory) Kind() string {
}

func (af *fileAccountFactory) PrettyKind(rawCfg map[string]interface{}) string {
cfg, err := af.unmarshalConfig(rawCfg)
if err != nil {
var cfg wallet.AccountConfig
if err := cfg.Unmarshal(rawCfg); err != nil {
return ""
}

Expand Down Expand Up @@ -249,8 +242,8 @@ func (af *fileAccountFactory) DataPrompt(kind wallet.ImportKind, rawCfg map[stri
case wallet.ImportKindMnemonic:
return &survey.Multiline{Message: "Mnemonic:"}
case wallet.ImportKindPrivateKey:
cfg, err := af.unmarshalConfig(rawCfg)
if err != nil {
var cfg wallet.AccountConfig
if err := cfg.Unmarshal(rawCfg); err != nil {
return nil
}
switch cfg.Algorithm {
Expand All @@ -273,8 +266,8 @@ func (af *fileAccountFactory) DataValidator(kind wallet.ImportKind, rawCfg map[s
switch kind {
case wallet.ImportKindMnemonic:
case wallet.ImportKindPrivateKey:
cfg, err := af.unmarshalConfig(rawCfg)
if err != nil {
var cfg wallet.AccountConfig
if err := cfg.Unmarshal(rawCfg); err != nil {
return nil
}
switch cfg.Algorithm {
Expand Down Expand Up @@ -319,8 +312,8 @@ func (af *fileAccountFactory) SupportedImportKinds() []wallet.ImportKind {
}

func (af *fileAccountFactory) HasConsensusSigner(rawCfg map[string]interface{}) bool {
cfg, err := af.unmarshalConfig(rawCfg)
if err != nil {
var cfg wallet.AccountConfig
if err := cfg.Unmarshal(rawCfg); err != nil {
return false
}

Expand All @@ -331,21 +324,9 @@ func (af *fileAccountFactory) HasConsensusSigner(rawCfg map[string]interface{})
return false
}

func (af *fileAccountFactory) unmarshalConfig(raw map[string]interface{}) (*accountConfig, error) {
if raw == nil {
return nil, fmt.Errorf("missing configuration")
}

var cfg accountConfig
if err := mapstructure.Decode(raw, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}

func (af *fileAccountFactory) Create(name string, passphrase string, rawCfg map[string]interface{}) (wallet.Account, error) {
cfg, err := af.unmarshalConfig(rawCfg)
if err != nil {
var cfg wallet.AccountConfig
if err := cfg.Unmarshal(rawCfg); err != nil {
return nil, err
}

Expand Down Expand Up @@ -378,7 +359,7 @@ func (af *fileAccountFactory) Create(name string, passphrase string, rawCfg map[
}

// Create a proper account based on the chosen algorithm.
return newAccount(state, cfg)
return newAccount(state, &cfg)
}

// Migrate migrates the given wallet config entry to the latest version and returns true, if any changes were needed.
Expand All @@ -387,8 +368,8 @@ func (af *fileAccountFactory) Migrate(_ map[string]interface{}) bool {
}

func (af *fileAccountFactory) Load(name string, passphrase string, rawCfg map[string]interface{}) (wallet.Account, error) {
cfg, err := af.unmarshalConfig(rawCfg)
if err != nil {
var cfg wallet.AccountConfig
if err := cfg.Unmarshal(rawCfg); err != nil {
return nil, err
}

Expand All @@ -408,7 +389,7 @@ func (af *fileAccountFactory) Load(name string, passphrase string, rawCfg map[st
return nil, fmt.Errorf("failed to open account state (maybe incorrect passphrase?)")
}

return newAccount(state, cfg)
return newAccount(state, &cfg)
}

func (af *fileAccountFactory) Remove(name string, _ map[string]interface{}) error {
Expand All @@ -420,8 +401,8 @@ func (af *fileAccountFactory) Rename(old, new string, _ map[string]interface{})
}

func (af *fileAccountFactory) Import(name string, passphrase string, rawCfg map[string]interface{}, src *wallet.ImportSource) (wallet.Account, error) {
cfg, err := af.unmarshalConfig(rawCfg)
if err != nil {
var cfg wallet.AccountConfig
if err := cfg.Unmarshal(rawCfg); err != nil {
return nil, err
}

Expand Down Expand Up @@ -449,7 +430,7 @@ func (af *fileAccountFactory) Import(name string, passphrase string, rawCfg map[
}

// Create a proper account based on the chosen algorithm.
acc, err := newAccount(&state, cfg)
acc, err := newAccount(&state, &cfg)
if err != nil {
return nil, err
}
Expand All @@ -471,24 +452,24 @@ func (af *fileAccountFactory) Import(name string, passphrase string, rawCfg map[
}

type fileAccount struct {
cfg *accountConfig
cfg *wallet.AccountConfig
state *secretState
signer signature.Signer
}

func newAccount(state *secretState, cfg *accountConfig) (wallet.Account, error) {
func newAccount(state *secretState, cfg *wallet.AccountConfig) (wallet.Account, error) {
switch state.Algorithm {
case wallet.AlgorithmEd25519Adr8:
// For Ed25519 use the ADR 0008 derivation scheme.
signer, _, err := sakg.GetAccountSigner(state.Data, "", cfg.Number)
signer, _, err := Ed25519FromMnemonic(state.Data, cfg.Number)
if err != nil {
return nil, fmt.Errorf("failed to derive signer: %w", err)
}

return &fileAccount{
cfg: cfg,
state: state,
signer: ed25519.WrapSigner(signer),
signer: signer,
}, nil
case wallet.AlgorithmEd25519Raw:
// For Ed25519-Raw use the raw private key.
Expand All @@ -504,7 +485,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 All @@ -527,7 +508,7 @@ func newAccount(state *secretState, cfg *accountConfig) (wallet.Account, error)
}, nil
case wallet.AlgorithmSr25519Adr8:
// For Sr25519 use the ADR 0008 derivation scheme.
signer, err := Sr25519FromMnemonic(state.Data, cfg.Number)
signer, _, err := Sr25519FromMnemonic(state.Data, cfg.Number)
if err != nil {
return nil, fmt.Errorf("failed to initialize signer: %w", err)
}
Expand Down Expand Up @@ -604,8 +585,29 @@ func (a *fileAccount) SignatureAddressSpec() types.SignatureAddressSpec {
}
}

func (a *fileAccount) UnsafeExport() string {
return a.state.Data
func (a *fileAccount) UnsafeExport() (string, string) {
switch a.cfg.Algorithm {
case wallet.AlgorithmEd25519Raw, wallet.AlgorithmSecp256k1Raw, wallet.AlgorithmSr25519Raw:
return a.state.Data, ""
}

mnemonic := a.state.Data

// For convenience derive the corresponding private key of the mnemonic.
key := ""
switch a.cfg.Algorithm {
case wallet.AlgorithmEd25519Adr8:
_, sk, _ := Ed25519FromMnemonic(a.state.Data, a.cfg.Number)
key = base64.StdEncoding.EncodeToString(sk)
case wallet.AlgorithmSecp256k1Bip44:
_, sk, _ := Secp256k1FromMnemonic(a.state.Data, a.cfg.Number)
key = hex.EncodeToString(sk)
case wallet.AlgorithmSr25519Adr8:
_, sk, _ := Sr25519FromMnemonic(a.state.Data, a.cfg.Number)
key = base64.StdEncoding.EncodeToString(sk)
}

return key, mnemonic
}

func init() {
Expand Down
Loading

0 comments on commit 1803dfd

Please sign in to comment.