Skip to content

Commit

Permalink
cli: Add support for storing oasis and ethereum addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
matevz committed Aug 25, 2022
1 parent d55dc3a commit 84f2f6c
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 69 deletions.
55 changes: 34 additions & 21 deletions cli/cmd/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import (
"github.com/oasisprotocol/oasis-sdk/cli/config"
"github.com/oasisprotocol/oasis-sdk/cli/table"
"github.com/oasisprotocol/oasis-sdk/cli/wallet"
walletAddress "github.com/oasisprotocol/oasis-sdk/cli/wallet/address"
walletFile "github.com/oasisprotocol/oasis-sdk/cli/wallet/file"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
)

var (
Expand Down Expand Up @@ -104,7 +104,7 @@ var (
name := args[0]

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

Expand Down Expand Up @@ -189,18 +189,21 @@ var (
cobra.CheckErr(fmt.Errorf("account '%s' already exists", name))
}

// NOTE: We only support importing into the file-based wallet for now.
af, err := wallet.Load(walletFile.Kind)
cobra.CheckErr(err)

// Ask for import kind.
// NOTE: Import is currently supported by file and address account factories.
kindFactory := map[wallet.ImportKind]wallet.Factory{}
var supportedKinds []string
for _, kind := range af.SupportedImportKinds() {
supportedKinds = append(supportedKinds, string(kind))
for _, fk := range []string{walletFile.Kind, walletAddress.Kind} {
f, err := wallet.Load(fk)
cobra.CheckErr(err)
for _, kind := range f.SupportedImportKinds() {
supportedKinds = append(supportedKinds, string(kind))
kindFactory[kind] = f
}
}

// Ask for import kind.
var kindRaw string
err = survey.AskOne(&survey.Select{
err := survey.AskOne(&survey.Select{
Message: "Import kind:",
Options: supportedKinds,
}, &kindRaw)
Expand All @@ -210,6 +213,8 @@ var (
err = kind.UnmarshalText([]byte(kindRaw))
cobra.CheckErr(err)

af := kindFactory[kind]

// Ask for wallet configuration.
afCfg, err := af.GetConfigFromSurvey(&kind)
cobra.CheckErr(err)
Expand All @@ -218,18 +223,24 @@ var (
var answers struct {
Data string
}
questions := []*survey.Question{
{
questions := []*survey.Question{}
if prompt := af.DataPrompt(kind, afCfg); prompt != nil {
questions = append(questions, &survey.Question{
Name: "data",
Prompt: af.DataPrompt(kind, afCfg),
Prompt: prompt,
Validate: af.DataValidator(kind, afCfg),
},
)
}

err = survey.Ask(questions, &answers)
cobra.CheckErr(err)

// Ask for passphrase.
passphrase := common.AskNewPassphrase()
passphrase := ""
if af.RequiresPassphrase() {
passphrase = common.AskNewPassphrase()
}

accCfg := &config.Account{
Kind: af.Kind(),
Expand All @@ -239,7 +250,6 @@ var (
Kind: kind,
Data: answers.Data,
}

err = cfg.Wallet.Import(name, passphrase, accCfg, src)
cobra.CheckErr(err)

Expand All @@ -258,7 +268,7 @@ var (
fmt.Printf("WARNING: Exporting the account will expose secret key material!\n")
acc := common.LoadAccount(config.Global(), name)

showPublicWalletInfo(acc)
showPublicWalletInfo(name, acc)

fmt.Printf("Export:\n")
fmt.Println(acc.UnsafeExport())
Expand Down Expand Up @@ -349,12 +359,15 @@ func (sf *accountEntitySignerFactory) Load(
return sf.signer, nil
}

func showPublicWalletInfo(wallet wallet.Account) {
fmt.Printf("Public Key: %s\n", wallet.Signer().Public())
fmt.Printf("Address: %s\n", wallet.Address())
if wallet.SignatureAddressSpec().Secp256k1Eth != nil {
fmt.Printf("Ethereum address: %s\n", helpers.EthAddressFromPubKey(*wallet.SignatureAddressSpec().Secp256k1Eth))
func showPublicWalletInfo(name string, wallet wallet.Account) {
fmt.Printf("Name: %s\n", name)
if wallet.Signer() != nil {
fmt.Printf("Public Key: %s\n", wallet.Signer().Public())
}
if wallet.EthAddress() != nil {
fmt.Printf("Ethereum address: %s\n", wallet.EthAddress().Hex())
}
fmt.Printf("Native address: %s\n", wallet.Address())
}

func init() {
Expand Down
2 changes: 1 addition & 1 deletion cli/config/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (w *Wallet) Load(name string, passphrase string) (wallet.Account, error) {
return nil, err
}

acc, err := af.Load(name, passphrase, cfg.Config)
acc, err := af.Load(name, passphrase, cfg.Config, cfg.Address)
if err != nil {
return nil, err
}
Expand Down
184 changes: 184 additions & 0 deletions cli/wallet/address/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package address

import (
"fmt"

"github.com/AlecAivazis/survey/v2"
"github.com/mitchellh/mapstructure"
"github.com/oasisprotocol/oasis-sdk/cli/wallet"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"

ethCommon "github.com/ethereum/go-ethereum/common"
coreSignature "github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
)

const (
// Kind is the account kind for the test accounts.
Kind = "address"

// ethAddressCfgKey is an optional key passed in the config files.
ethAddressCfgKey = "eth_address"
)

type accountConfig struct {
EthAddress string `mapstructure:"eth_address,omitempty"`
}

type addressAccount struct {
address types.Address
cfg *accountConfig
}

func newAccount(address types.Address, cfg *accountConfig) (wallet.Account, error) {
return &addressAccount{address: address, cfg: cfg}, nil
}

type addressAccountFactory struct{}

func (a *addressAccountFactory) HasConsensusSigner(cfg map[string]interface{}) bool {
return false
}

func (a *addressAccountFactory) unmarshalConfig(raw map[string]interface{}) (*accountConfig, error) {
if raw == nil {
// No eth_address defined.
return &accountConfig{}, nil
}

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

func (a *addressAccountFactory) Create(name string, passphrase string, cfg map[string]interface{}) (wallet.Account, error) {
return nil, fmt.Errorf("address: create not supported")
}

func (a *addressAccountFactory) Load(_ string, _ string, rawCfg map[string]interface{}, rawAddr string) (wallet.Account, error) {
cfg, err := a.unmarshalConfig(rawCfg)
if err != nil {
return nil, err
}

addr, _, err := helpers.ResolveEthOrOasisAddress(rawAddr)
if err != nil {
return nil, err
}

return newAccount(*addr, cfg)
}

func (a *addressAccountFactory) Remove(name string, cfg map[string]interface{}) error {
return nil
}

func (a *addressAccountFactory) Rename(old, new string, cfg map[string]interface{}) error {
return nil
}

func (a *addressAccountFactory) Import(_ string, _ string, cfg map[string]interface{}, src *wallet.ImportSource) (wallet.Account, error) {
addr, ethAddr, err := helpers.ResolveEthOrOasisAddress(src.Data)
if err != nil {
return nil, err
}

ethAddrHex := ""
if ethAddr != nil {
ethAddrHex = ethAddr.Hex()
cfg[ethAddressCfgKey] = ethAddrHex
}

return newAccount(*addr, &accountConfig{
EthAddress: ethAddrHex,
})
}

func (a *addressAccountFactory) Kind() string {
return Kind
}

func (a *addressAccountFactory) PrettyKind(rawCfg map[string]interface{}) string {
if _, ok := rawCfg[ethAddressCfgKey]; ok {
return "address (ethereum)"
}
return "address (native)"
}

func (a *addressAccountFactory) Flags() *flag.FlagSet {
return nil
}

func (a *addressAccountFactory) GetConfigFromFlags() (map[string]interface{}, error) {
return map[string]interface{}{}, nil
}

func (a *addressAccountFactory) GetConfigFromSurvey(_ *wallet.ImportKind) (map[string]interface{}, error) {
return map[string]interface{}{}, nil
}

func (a *addressAccountFactory) DataPrompt(_ wallet.ImportKind, _ map[string]interface{}) survey.Prompt {
return &survey.Multiline{Message: "Address (bech32 or hex-encoded):"}
}

func (a *addressAccountFactory) DataValidator(_ wallet.ImportKind, _ map[string]interface{}) survey.Validator {
return func(ans interface{}) error {
if addr, _, err := helpers.ResolveEthOrOasisAddress(ans.(string)); addr == nil {
if err != nil {
return err
}
return fmt.Errorf("unsupported address format")
}
return nil
}
}

func (a *addressAccountFactory) RequiresPassphrase() bool {
// Address accounts do not require a passphrase.
return false
}

func (a *addressAccountFactory) SupportedImportKinds() []wallet.ImportKind {
return []wallet.ImportKind{
wallet.ImportKindAddress,
}
}

func (a *addressAccount) ConsensusSigner() coreSignature.Signer {
return nil
}

func (a *addressAccount) Signer() signature.Signer {
return nil
}

func (a *addressAccount) Address() types.Address {
return a.address
}

func (a *addressAccount) EthAddress() *ethCommon.Address {
if a.cfg.EthAddress != "" {
_, ethAddr, err := helpers.ResolveEthOrOasisAddress(a.cfg.EthAddress)
cobra.CheckErr(err)

return ethAddr
}
return nil
}

func (a *addressAccount) SignatureAddressSpec() types.SignatureAddressSpec {
return types.SignatureAddressSpec{}
}

func (a *addressAccount) UnsafeExport() string {
return "N/A"
}

func init() {
wallet.Register(&addressAccountFactory{})
}
18 changes: 17 additions & 1 deletion cli/wallet/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import (
"strings"

"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"
Expand Down Expand Up @@ -373,7 +375,7 @@ func (af *fileAccountFactory) Create(name string, passphrase string, rawCfg map[
return newAccount(state, cfg)
}

func (af *fileAccountFactory) Load(name string, passphrase string, rawCfg map[string]interface{}) (wallet.Account, error) {
func (af *fileAccountFactory) Load(name string, passphrase string, rawCfg map[string]interface{}, _ string) (wallet.Account, error) {
cfg, err := af.unmarshalConfig(rawCfg)
if err != nil {
return nil, err
Expand Down Expand Up @@ -536,6 +538,20 @@ func (a *fileAccount) Address() types.Address {
return types.NewAddress(a.SignatureAddressSpec())
}

func (a *fileAccount) EthAddress() *ethCommon.Address {
switch a.cfg.Algorithm {
case wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSecp256k1Raw:
h := sha3.NewLegacyKeccak256()
untaggedPk, _ := a.Signer().Public().(secp256k1.PublicKey).MarshalBinaryUncompressedUntagged()
h.Write(untaggedPk)
hash := h.Sum(nil)
addr := ethCommon.BytesToAddress(hash[32-20:])
return &addr
}

return nil
}

func (a *fileAccount) SignatureAddressSpec() types.SignatureAddressSpec {
switch a.cfg.Algorithm {
case wallet.AlgorithmEd25519Adr8, wallet.AlgorithmEd25519Raw:
Expand Down
8 changes: 7 additions & 1 deletion cli/wallet/ledger/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/AlecAivazis/survey/v2"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/mitchellh/mapstructure"
flag "github.com/spf13/pflag"

Expand Down Expand Up @@ -109,7 +110,7 @@ func (af *ledgerAccountFactory) Create(name string, passphrase string, rawCfg ma
return newAccount(cfg)
}

func (af *ledgerAccountFactory) Load(name string, passphrase string, rawCfg map[string]interface{}) (wallet.Account, error) {
func (af *ledgerAccountFactory) Load(name string, passphrase string, rawCfg map[string]interface{}, _ string) (wallet.Account, error) {
cfg, err := af.unmarshalConfig(rawCfg)
if err != nil {
return nil, err
Expand Down Expand Up @@ -199,6 +200,11 @@ func (a *ledgerAccount) Address() types.Address {
return types.NewAddress(a.SignatureAddressSpec())
}

func (a *ledgerAccount) EthAddress() *ethCommon.Address {
// secp256k1 accounts are not supported by Ledger yet.
return nil
}

func (a *ledgerAccount) SignatureAddressSpec() types.SignatureAddressSpec {
return types.NewSignatureAddressSpecEd25519(a.signer.Public().(ed25519.PublicKey))
}
Expand Down
Loading

0 comments on commit 84f2f6c

Please sign in to comment.