Skip to content

Commit

Permalink
add feature to export wallet seed after initial backup
Browse files Browse the repository at this point in the history
Signed-off-by: Philemon Ukane <[email protected]>
  • Loading branch information
ukane-philemon committed May 7, 2024
1 parent d34f1f2 commit a581dde
Show file tree
Hide file tree
Showing 16 changed files with 99 additions and 14 deletions.
13 changes: 12 additions & 1 deletion libwallet/assets/btc/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func RestoreWallet(seedMnemonic string, pass *sharedW.AuthInfo, params *sharedW.
// It validates the network type passed by fetching the chain parameters
// associated with it for the BTC asset. It then generates the BTC loader interface
// that is passed to be used upstream while loading the existing the wallet in the
// shared wallet implemenation.
// shared wallet implementation.
// Immediately loading the existing wallet is complete, the function to safely
// cancel network sync is set. There after returning the loaded wallet's interface.
func LoadExisting(w *sharedW.Wallet, params *sharedW.InitParams) (sharedW.Asset, error) {
Expand All @@ -238,6 +238,17 @@ func LoadExisting(w *sharedW.Wallet, params *sharedW.InitParams) (sharedW.Asset,
txAndBlockNotificationListeners: make(map[string]*sharedW.TxAndBlockNotificationListener),
}

// w.EncryptedSeed was previously deleted after verification. Existing
// wallets created before the change to allow viewing wallet seed in-app
// should still behave normal but they can no longer view their seed.
if len(w.EncryptedSeed) == 0 && !w.IsBackedUp {
w.IsBackedUp = true
if err := params.DB.Save(w); err != nil {
log.Errorf("DB.Save error: %v", err)
return nil, errors.New("failed to update wallet back up state")
}
}

err = btcWallet.Prepare(ldr, params)
if err != nil {
return nil, err
Expand Down
12 changes: 12 additions & 0 deletions libwallet/assets/dcr/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dcr

import (
"context"
"errors"
"path/filepath"
"sync"

Expand Down Expand Up @@ -237,6 +238,17 @@ func LoadExisting(w *sharedW.Wallet, params *sharedW.InitParams) (sharedW.Asset,
dbMutex: &dbMutex,
}

// w.EncryptedSeed was previously deleted after verification. Existing
// wallets created before the change to allow viewing wallet seed in-app
// should still behave normal but they can no longer view their seed.
if len(w.EncryptedSeed) == 0 && !w.IsBackedUp {
w.IsBackedUp = true
if err := params.DB.Save(w); err != nil {
log.Errorf("DB.Save error: %v", err)
return nil, errors.New("failed to update wallet back up state")
}
}

err = dcrWallet.Prepare(ldr, params)
if err != nil {
return nil, err
Expand Down
11 changes: 11 additions & 0 deletions libwallet/assets/ltc/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ func LoadExisting(w *sharedW.Wallet, params *sharedW.InitParams) (sharedW.Asset,
txAndBlockNotificationListeners: make(map[string]*sharedW.TxAndBlockNotificationListener),
}

// w.EncryptedSeed was previously deleted after verification. Existing
// wallets created before the change to allow viewing wallet seed in-app
// should still behave normal but they can no longer view their seed.
if len(w.EncryptedSeed) == 0 && !w.IsBackedUp {
w.IsBackedUp = true
if err := params.DB.Save(w); err != nil {
log.Errorf("DB.Save error: %v", err)
return nil, errors.New("failed to update wallet back up state")
}
}

err = ltcWallet.Prepare(ldr, params)
if err != nil {
return nil, err
Expand Down
3 changes: 2 additions & 1 deletion libwallet/assets/wallet/asset_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ type Asset interface {

RootDir() string
DataDir() string
GetEncryptedSeed() string
HasWalletSeed() bool
IsWalletBackedUp() bool
IsConnectedToNetwork() bool
NetType() utils.NetworkType
ToAmount(v int64) AssetAmount
Expand Down
22 changes: 19 additions & 3 deletions libwallet/assets/wallet/wallet_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Wallet struct {
logDir string

EncryptedSeed []byte
IsBackedUp bool
IsRestored bool
HasDiscoveredAccounts bool
PrivatePassphraseType int32
Expand Down Expand Up @@ -211,7 +212,7 @@ func (wallet *Wallet) RootDir() string {
}

// SetNetType is used to set the net type if it doesn't exist. This method is
// used before the actual wallet is loaded otherwise once loaded the nettype
// used before the actual wallet is loaded otherwise once loaded the netType
// can't be altered. This method help create the full method with the unique
// path for the folder structure for the testnet data dirs.
func (wallet *Wallet) SetNetType(netType utils.NetworkType) {
Expand Down Expand Up @@ -247,10 +248,16 @@ func (wallet *Wallet) Internal() *loader.LoadedWallets {
return lw
}

func (wallet *Wallet) GetEncryptedSeed() string {
func (wallet *Wallet) IsWalletBackedUp() bool {
wallet.mu.RLock()
defer wallet.mu.RUnlock()
return string(wallet.EncryptedSeed)
return wallet.IsBackedUp
}

func (wallet *Wallet) HasWalletSeed() bool {
wallet.mu.RLock()
defer wallet.mu.RUnlock()
return len(wallet.EncryptedSeed) > 0
}

func (wallet *Wallet) GetWalletID() int {
Expand Down Expand Up @@ -462,6 +469,14 @@ func (wallet *Wallet) createWatchingOnlyWallet(extendedPublicKey string) error {
func RestoreWallet(seedMnemonic string, pass *AuthInfo, loader loader.AssetLoader,
params *InitParams, assetType utils.AssetType,
) (*Wallet, error) {
// Ensure the encrypted seeds are available before creating wallet so we can
// return early.
encryptedSeed, err := encryptWalletSeed([]byte(pass.PrivatePass), seedMnemonic)
if err != nil {
log.Errorf("wallet.createWallet: error encrypting wallet seed: %v", err)
return nil, err
}

wallet := &Wallet{
Name: pass.Name,
PrivatePassphraseType: pass.PrivatePassType,
Expand All @@ -470,6 +485,7 @@ func RestoreWallet(seedMnemonic string, pass *AuthInfo, loader loader.AssetLoade
rootDir: params.RootDir,
logDir: params.LogDir,

EncryptedSeed: encryptedSeed,
IsRestored: true,
HasDiscoveredAccounts: false,
Type: assetType,
Expand Down
14 changes: 11 additions & 3 deletions libwallet/assets/wallet/wallet_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,29 @@ func (wallet *Wallet) batchDbTransaction(dbOp func(node storm.Node) error) (err
// DecryptSeed decrypts wallet.EncryptedSeed using privatePassphrase
func (wallet *Wallet) DecryptSeed(privatePassphrase string) (string, error) {
if wallet.EncryptedSeed == nil {
return "", errors.New(utils.ErrInvalid)
return "", errors.New(utils.ErrNoSeed)
}

return decryptWalletSeed([]byte(privatePassphrase), wallet.EncryptedSeed)
}

// VerifySeedForWallet compares seedMnemonic with the decrypted wallet.EncryptedSeed and clears wallet.EncryptedSeed if they match.
// VerifySeedForWallet compares seedMnemonic with the decrypted
// wallet.EncryptedSeed.
func (wallet *Wallet) VerifySeedForWallet(seedMnemonic, privpass string) (bool, error) {
wallet.mu.RLock()
defer wallet.mu.RUnlock()

if wallet.IsBackedUp {
return true, nil // return early
}

decryptedSeed, err := decryptWalletSeed([]byte(privpass), wallet.EncryptedSeed)
if err != nil {
return false, err
}

if decryptedSeed == seedMnemonic {
wallet.EncryptedSeed = nil
wallet.IsBackedUp = true
return true, utils.TranslateError(wallet.db.Save(wallet))
}

Expand Down
6 changes: 6 additions & 0 deletions libwallet/internal/loader/dcr/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ func (l *dcrLoader) CreateNewWallet(ctx context.Context, params *loader.CreateWa
defer l.mu.Unlock()
l.mu.Lock()

defer func() {
for i := range params.Seed {
params.Seed[i] = 0
}
}()

if l.wallet != nil {
return nil, errors.E(op, errors.Exist, "wallet already opened")
}
Expand Down
1 change: 1 addition & 0 deletions libwallet/utils/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const (
ErrNoMixableOutput = "err_no_mixable_output"
ErrInvalidVoteBit = "err_invalid_vote_bit"
ErrNotSynced = "err_not_synced"
ErrNoSeed = "no_seed"
)

var (
Expand Down
4 changes: 2 additions & 2 deletions ui/page/components/wallet_sync_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (wsi *WalletSyncInfo) WalletInfoLayout(gtx C) D {
layout.Rigid(wsi.syncStatusSection),
}

if len(wsi.wallet.GetEncryptedSeed()) > 0 {
if !wsi.wallet.IsWalletBackedUp() {
items = append(items, layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return layout.E.Layout(gtx, wsi.toBackup.Layout)
Expand Down Expand Up @@ -160,7 +160,7 @@ func (wsi *WalletSyncInfo) walletNameAndBackupInfo(gtx C) D {
})
}))

if len(wsi.wallet.GetEncryptedSeed()) > 0 {
if !wsi.wallet.IsWalletBackedUp() {
items = append(items, layout.Rigid(func(gtx C) D {
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(wsi.Theme.Icons.RedAlert.Layout20dp),
Expand Down
1 change: 1 addition & 0 deletions ui/page/root/home_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func (hp *HomePage) OnNavigatedTo() {
}

// Reload the window whenever there is an exchange rate update.
hp.AssetsManager.RateSource.RemoveRateListener(HomePageID)
rateListener := &ext.RateListener{
OnRateUpdated: hp.CalculateAssetsUSDBalance,
}
Expand Down
1 change: 1 addition & 0 deletions ui/page/root/overview_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,7 @@ func (pg *OverviewPage) listenForMixerNotifications() {
}

// Reload the window whenever there is an exchange rate update.
pg.AssetsManager.RateSource.RemoveRateListener(OverviewPageID)
rateListener := &ext.RateListener{
OnRateUpdated: func() {
go pg.updateAssetsUSDBalance()
Expand Down
2 changes: 1 addition & 1 deletion ui/page/root/wallet_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ func (pg *WalletSelectorPage) layoutSyncStatus(gtx C, item *walletWithBalance) D
}),
}

if len(item.wallet.GetEncryptedSeed()) > 0 {
if !item.wallet.IsWalletBackedUp() {
widgets = append(widgets,
layout.Rigid(func(gtx C) D {
return layout.Inset{
Expand Down
4 changes: 2 additions & 2 deletions ui/page/wallet/single_wallet_main_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (swmp *SingleWalletMasterPage) OnNavigatedTo() {
// reset the checkbox
swmp.checkBox.CheckBox.Value = false

needBackup := swmp.selectedWallet.GetEncryptedSeed() != ""
needBackup := !swmp.selectedWallet.IsWalletBackedUp()

if swmp.CurrentPage() == nil {
swmp.Display(info.NewInfoPage(swmp.Load, swmp.selectedWallet)) // TODO: Should pagestack have a start page? YES!
Expand Down Expand Up @@ -410,7 +410,7 @@ func (swmp *SingleWalletMasterPage) OnNavigatedFrom() {
// The encrypted seed exists by default and is cleared after wallet is backed up.
// Activate the modal requesting the user to backup their current wallet on
// every wallet open request until the encrypted seed is cleared (backup happens).
if swmp.selectedWallet.GetEncryptedSeed() != "" {
if !swmp.selectedWallet.IsWalletBackedUp() {
swmp.selectedWallet.SaveUserConfigValue(sharedW.SeedBackupNotificationConfigKey, false)
}

Expand Down
17 changes: 16 additions & 1 deletion ui/page/wallet/wallet_settings_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/crypto-power/cryptopower/ui/modal"
"github.com/crypto-power/cryptopower/ui/page/components"
"github.com/crypto-power/cryptopower/ui/page/security"
"github.com/crypto-power/cryptopower/ui/page/seedbackup"
s "github.com/crypto-power/cryptopower/ui/page/settings"
"github.com/crypto-power/cryptopower/ui/utils"
"github.com/crypto-power/cryptopower/ui/values"
Expand Down Expand Up @@ -52,7 +53,7 @@ type SettingsPage struct {

pageContainer *widget.List

changePass, rescan *cryptomaterial.Clickable
changePass, viewSeed, rescan *cryptomaterial.Clickable
changeAccount, checklog, checkStats *cryptomaterial.Clickable
changeWalletName, addAccount, deleteWallet *cryptomaterial.Clickable
verifyMessage, validateAddr, signMessage *cryptomaterial.Clickable
Expand All @@ -76,6 +77,7 @@ func NewSettingsPage(l *load.Load, wallet sharedW.Asset, walletCallbackFunc func
GenericPageModal: app.NewGenericPageModal(WalletSettingsPageID),
wallet: wallet,
changePass: l.Theme.NewClickable(false),
viewSeed: l.Theme.NewClickable(false),
rescan: l.Theme.NewClickable(false),
setGapLimit: l.Theme.NewClickable(false),
changeAccount: l.Theme.NewClickable(false),
Expand Down Expand Up @@ -180,6 +182,12 @@ func (pg *SettingsPage) generalSection() layout.Widget {
return layout.Inset{}.Layout(gtx, pg.sectionContent(pg.changePass, values.String(values.StrSpendingPassword)))
}),
layout.Rigid(pg.sectionContent(pg.changeWalletName, values.String(values.StrRenameWalletSheetTitle))),
layout.Rigid(func(gtx C) D {
if !pg.wallet.IsWalletBackedUp() || !pg.wallet.HasWalletSeed() {
return D{}
}
return layout.Inset{}.Layout(gtx, pg.sectionContent(pg.viewSeed, values.String(values.StrExportWalletSeed)))
}),
layout.Rigid(func(gtx C) D {
if pg.wallet.GetAssetType() == libutils.DCRWalletAsset {
return pg.subSection(gtx, values.String(values.StrUnconfirmedFunds), pg.spendUnconfirmed.Layout)
Expand Down Expand Up @@ -651,6 +659,13 @@ func (pg *SettingsPage) HandleUserInteractions() {
break
}

for pg.viewSeed.Clicked() {
currentPage := pg.ParentWindow().CurrentPageID()
pg.ParentWindow().Display(seedbackup.NewBackupInstructionsPage(pg.Load, pg.wallet, func(load *load.Load, navigator app.WindowNavigator) {
navigator.ClosePagesAfter(currentPage)
}))
}

if pg.rescan.Clicked() {
go func() {
info := modal.NewCustomModal(pg.Load).
Expand Down
1 change: 1 addition & 0 deletions ui/values/localizable/en.go
Original file line number Diff line number Diff line change
Expand Up @@ -936,4 +936,5 @@ const EN = `
"updateAPI" = "Check for Update API"
"newUpdateText" = "A new update, %s, is available"
"updateAvailable" = "Update Available"
"exportWalletSeed" = "Export Wallet Seed"
`
1 change: 1 addition & 0 deletions ui/values/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -1045,4 +1045,5 @@ const (
StrUpdateAPI = "updateAPI"
StrNewUpdateText = "newUpdateText"
StrUpdateAvailable = "updateAvailable"
StrExportWalletSeed = "exportWalletSeed"
)

0 comments on commit a581dde

Please sign in to comment.