Skip to content

Commit 99faf26

Browse files
committed
Merge branch 'keystore-prompt' into staging-watchonly
2 parents 240c34e + c232335 commit 99faf26

File tree

27 files changed

+593
-77
lines changed

27 files changed

+593
-77
lines changed

appveyor.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ environment:
44
nodejs_version: "18"
55
matrix:
66
- QT: C:\Qt\5.15.2\msvc2019_64
7-
GOROOT: C:\go119
7+
GOROOT: C:\go120
88
GOPATH: C:\gopath\
99
PLATFORM: amd64
1010
COMPILER: msvc
1111

1212
# https://www.appveyor.com/docs/windows-images-software/#golang
1313
# If you change this, also change the GOROOT variable above to point to the right installation folder.
14-
stack: go 1.19 # should be 1.20, but that is not available yet, see https://www.appveyor.com/docs/windows-images-software/#golang
14+
stack: go 1.20
1515

1616
install:
1717
- ps: Install-Product node $env:nodejs_version

backend/accounts.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"math"
2121
"sort"
2222
"strings"
23+
"time"
2324

2425
"github.com/btcsuite/btcd/btcutil/hdkeychain"
2526
"github.com/digitalbitbox/bitbox-wallet-app/backend/accounts"
@@ -551,7 +552,66 @@ func (backend *Backend) createAndAddAccount(coin coinpkg.Coin, persistedConfig *
551552
Config: persistedConfig,
552553
DBFolder: backend.arguments.CacheDirectoryPath(),
553554
NotesFolder: backend.arguments.NotesDirectoryPath(),
554-
Keystore: backend.keystore,
555+
ConnectKeystore: func() (keystore.Keystore, error) {
556+
type data struct {
557+
Type string `json:"typ"`
558+
KeystoreName string `json:"keystoreName"`
559+
ErrorCode string `json:"errorCode"`
560+
ErrorMessage string `json:"errorMessage"`
561+
}
562+
accountRootFingerprint, err := persistedConfig.SigningConfigurations.RootFingerprint()
563+
if err != nil {
564+
return nil, err
565+
}
566+
keystoreName := ""
567+
persistedKeystore, err := backend.config.AccountsConfig().LookupKeystore(accountRootFingerprint)
568+
if err == nil {
569+
keystoreName = persistedKeystore.Name
570+
}
571+
backend.Notify(observable.Event{
572+
Subject: "connect-keystore",
573+
Action: action.Replace,
574+
Object: data{
575+
Type: "connect",
576+
KeystoreName: keystoreName,
577+
},
578+
})
579+
ks, err := backend.connectKeystore.connect(
580+
backend.Keystore(),
581+
accountRootFingerprint,
582+
20*time.Minute,
583+
)
584+
switch {
585+
case errp.Cause(err) == errReplaced:
586+
// If a previous connect-keystore request is in progress, the previous request is
587+
// failed, but we don't dismiss the prompt, as the new prompt has already been shown
588+
// by the above "connect" notification.y
589+
case err == nil || errp.Cause(err) == errUserAbort:
590+
// Dismiss prompt after success or upon user abort.
591+
592+
backend.Notify(observable.Event{
593+
Subject: "connect-keystore",
594+
Action: action.Replace,
595+
Object: nil,
596+
})
597+
default:
598+
// Display error to user.
599+
errorCode := ""
600+
if errp.Cause(err) == errWrongKeystore {
601+
errorCode = "wrongKeystore"
602+
}
603+
backend.Notify(observable.Event{
604+
Subject: "connect-keystore",
605+
Action: action.Replace,
606+
Object: data{
607+
Type: "error",
608+
ErrorCode: errorCode,
609+
ErrorMessage: err.Error(),
610+
},
611+
})
612+
}
613+
return ks, err
614+
},
555615
OnEvent: func(event accountsTypes.Event) {
556616
backend.events <- AccountEvent{
557617
Type: "account", Code: persistedConfig.Code,

backend/accounts/baseaccount.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type AccountConfig struct {
4545
DBFolder string
4646
// NotesFolder is the folder where the transaction notes are stored. Full path.
4747
NotesFolder string
48-
Keystore keystore.Keystore
48+
ConnectKeystore func() (keystore.Keystore, error)
4949
OnEvent func(types.Event)
5050
RateUpdater *rates.RateUpdater
5151
GetNotifier func(signing.Configurations) Notifier

backend/accounts/baseaccount_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ func TestBaseAccount(t *testing.T) {
6565
},
6666
DBFolder: test.TstTempDir("baseaccount_test_dbfolder"),
6767
NotesFolder: test.TstTempDir("baseaccount_test_notesfolder"),
68-
Keystore: nil,
6968
OnEvent: func(event types.Event) { events <- event },
7069
RateUpdater: nil,
7170
GetNotifier: nil,

backend/backend.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ type Backend struct {
164164
accounts accountsList
165165
// keystore is nil if no keystore is connected.
166166
keystore keystore.Keystore
167-
aopp AOPP
167+
168+
connectKeystore connectKeystore
169+
170+
aopp AOPP
168171

169172
// makeBtcAccount creates a BTC account. In production this is `btc.NewAccount`, but can be
170173
// overridden in unit tests for mocking.
@@ -505,7 +508,11 @@ func (backend *Backend) Keystore() keystore.Keystore {
505508
func (backend *Backend) registerKeystore(keystore keystore.Keystore) {
506509
defer backend.accountsAndKeystoreLock.Lock()()
507510
// Only for logging, if there is an error we continue anyway.
508-
fingerprint, _ := keystore.RootFingerprint()
511+
fingerprint, err := keystore.RootFingerprint()
512+
if err != nil {
513+
backend.log.WithError(err).Error("could not retrieve keystore fingerprint")
514+
return
515+
}
509516
log := backend.log.WithField("rootFingerprint", fingerprint)
510517
log.Info("registering keystore")
511518
backend.keystore = keystore
@@ -515,19 +522,10 @@ func (backend *Backend) registerKeystore(keystore keystore.Keystore) {
515522
})
516523

517524
belongsToKeystore := func(account *config.Account) bool {
518-
fingerprint, err := keystore.RootFingerprint()
519-
if err != nil {
520-
log.WithError(err).Error("Could not retrieve root fingerprint")
521-
return false
522-
}
523525
return account.SigningConfigurations.ContainsRootFingerprint(fingerprint)
524526
}
525527

526528
persistKeystore := func(accountsConfig *config.AccountsConfig) error {
527-
fingerprint, err := keystore.RootFingerprint()
528-
if err != nil {
529-
return errp.WithMessage(err, "could not retrieve root fingerprint")
530-
}
531529
keystoreName, err := keystore.Name()
532530
if err != nil {
533531
return errp.WithMessage(err, "could not retrieve keystore name")
@@ -538,7 +536,7 @@ func (backend *Backend) registerKeystore(keystore keystore.Keystore) {
538536
return nil
539537
}
540538

541-
err := backend.config.ModifyAccountsConfig(func(accountsConfig *config.AccountsConfig) error {
539+
err = backend.config.ModifyAccountsConfig(func(accountsConfig *config.AccountsConfig) error {
542540
// Persist keystore with its name in the config.
543541
if err := persistKeystore(accountsConfig); err != nil {
544542
log.WithError(err).Error("Could not persist keystore")
@@ -559,6 +557,8 @@ func (backend *Backend) registerKeystore(keystore keystore.Keystore) {
559557
backend.initAccounts(false)
560558

561559
backend.aoppKeystoreRegistered()
560+
561+
backend.connectKeystore.onConnect(backend.keystore)
562562
}
563563

564564
// DeregisterKeystore removes the registered keystore.
@@ -784,3 +784,8 @@ func (backend *Backend) GetAccountFromCode(code string) (accounts.Interface, err
784784

785785
return acct, nil
786786
}
787+
788+
// CancelConnectKeystore cancels a pending keystore connection request if one exists.
789+
func (backend *Backend) CancelConnectKeystore() {
790+
backend.connectKeystore.cancel(errUserAbort)
791+
}

backend/coins/btc/account.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,12 @@ func (account *Account) VerifyAddress(addressID string) (bool, error) {
721721
return false, errp.New("account must be initialized")
722722
}
723723
account.Synchronizer.WaitSynchronized()
724+
725+
keystore, err := account.Config().ConnectKeystore()
726+
if err != nil {
727+
return false, err
728+
}
729+
724730
scriptHashHex := blockchain.ScriptHashHex(addressID)
725731
var address *addresses.AccountAddress
726732
for _, subacc := range account.subaccounts {
@@ -732,19 +738,23 @@ func (account *Account) VerifyAddress(addressID string) (bool, error) {
732738
if address == nil {
733739
return false, errp.New("unknown address not found")
734740
}
735-
canVerifyAddress, _, err := account.Config().Keystore.CanVerifyAddress(account.Coin())
741+
canVerifyAddress, _, err := keystore.CanVerifyAddress(account.Coin())
736742
if err != nil {
737743
return false, err
738744
}
739745
if canVerifyAddress {
740-
return true, account.Config().Keystore.VerifyAddress(address.Configuration, account.Coin())
746+
return true, keystore.VerifyAddress(address.Configuration, account.Coin())
741747
}
742748
return false, nil
743749
}
744750

745751
// CanVerifyAddresses wraps Keystores().CanVerifyAddresses(), see that function for documentation.
746752
func (account *Account) CanVerifyAddresses() (bool, bool, error) {
747-
return account.Config().Keystore.CanVerifyAddress(account.Coin())
753+
keystore, err := account.Config().ConnectKeystore()
754+
if err != nil {
755+
return false, false, err
756+
}
757+
return keystore.CanVerifyAddress(account.Coin())
748758
}
749759

750760
type byValue struct {
@@ -799,7 +809,11 @@ func (account *Account) VerifyExtendedPublicKey(signingConfigIndex int) (bool, e
799809
return false, errp.New("account not initialized")
800810
}
801811

802-
keystore := account.Config().Keystore
812+
keystore, err := account.Config().ConnectKeystore()
813+
if err != nil {
814+
return false, err
815+
}
816+
803817
if keystore.CanVerifyExtendedPublicKey() {
804818
return true, keystore.VerifyExtendedPublicKey(
805819
account.Coin(),

backend/coins/btc/account_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ func TestAccount(t *testing.T) {
7373
SigningConfigurations: signingConfigurations,
7474
},
7575
DBFolder: dbFolder,
76-
Keystore: nil,
7776
OnEvent: func(accountsTypes.Event) {},
7877
RateUpdater: nil,
7978
GetNotifier: func(signing.Configurations) accounts.Notifier { return nil },

backend/coins/btc/handlers/handlers.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package handlers
1717

1818
import (
19+
"context"
1920
"encoding/json"
2021
"fmt"
2122
"math/big"
@@ -69,6 +70,7 @@ func NewHandlers(
6970
handleFunc("/has-secure-output", handlers.ensureAccountInitialized(handlers.getHasSecureOutput)).Methods("GET")
7071
handleFunc("/propose-tx-note", handlers.ensureAccountInitialized(handlers.postProposeTxNote)).Methods("POST")
7172
handleFunc("/notes/tx", handlers.ensureAccountInitialized(handlers.postSetTxNote)).Methods("POST")
73+
handleFunc("/connect-keystore", handlers.ensureAccountInitialized(handlers.postConnectKeystore)).Methods("POST")
7274
return handlers
7375
}
7476

@@ -541,6 +543,10 @@ func (handlers *Handlers) postVerifyExtendedPublicKey(r *http.Request) (interfac
541543
}, nil
542544
}
543545
canVerify, err := btcAccount.VerifyExtendedPublicKey(input.SigningConfigIndex)
546+
// User canceled keystore connect prompt - no special action or message needed in the frontend.
547+
if errp.Cause(err) == context.Canceled {
548+
return result{Success: true}, nil
549+
}
544550
if err != nil {
545551
return result{Success: false, ErrorMessage: err.Error()}, nil
546552
}
@@ -584,3 +590,12 @@ func (handlers *Handlers) postSetTxNote(r *http.Request) (interface{}, error) {
584590

585591
return nil, handlers.account.SetTxNote(args.InternalTxID, args.Note)
586592
}
593+
594+
func (handlers *Handlers) postConnectKeystore(r *http.Request) (interface{}, error) {
595+
type response struct {
596+
Success bool `json:"success"`
597+
}
598+
599+
_, err := handlers.account.Config().ConnectKeystore()
600+
return response{Success: err == nil}, nil
601+
}

backend/coins/btc/sign.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ func (account *Account) signTransaction(
6363
FormatUnit: account.coin.formatUnit,
6464
}
6565

66-
if err := account.Config().Keystore.SignTransaction(proposedTransaction); err != nil {
66+
keystore, err := account.Config().ConnectKeystore()
67+
if err != nil {
68+
return err
69+
}
70+
if err := keystore.SignTransaction(proposedTransaction); err != nil {
6771
return err
6872
}
6973

backend/coins/eth/account.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -642,8 +642,13 @@ func (account *Account) SendTx() error {
642642
return errp.New("No active tx proposal")
643643
}
644644

645+
keystore, err := account.Config().ConnectKeystore()
646+
if err != nil {
647+
return err
648+
}
649+
645650
account.log.Info("Signing and sending transaction")
646-
if err := account.Config().Keystore.SignTransaction(txProposal); err != nil {
651+
if err := keystore.SignTransaction(txProposal); err != nil {
647652
return err
648653
}
649654
// By experience, at least with the Etherscan backend, this can succeed and still the
@@ -802,17 +807,26 @@ func (account *Account) VerifyAddress(addressID string) (bool, error) {
802807
if !account.isInitialized() {
803808
return false, errp.New("account must be initialized")
804809
}
805-
canVerifyAddress, _, err := account.Config().Keystore.CanVerifyAddress(account.Coin())
810+
keystore, err := account.Config().ConnectKeystore()
811+
if err != nil {
812+
return false, err
813+
}
814+
canVerifyAddress, _, err := keystore.CanVerifyAddress(account.Coin())
806815
if err != nil {
807816
return false, err
808817
}
809818
if canVerifyAddress {
810-
return true, account.Config().Keystore.VerifyAddress(account.signingConfiguration, account.Coin())
819+
return true, keystore.VerifyAddress(account.signingConfiguration, account.Coin())
811820
}
812821
return false, nil
813822
}
814823

815824
// CanVerifyAddresses implements accounts.Interface.
816825
func (account *Account) CanVerifyAddresses() (bool, bool, error) {
817-
return account.Config().Keystore.CanVerifyAddress(account.Coin())
826+
keystore, err := account.Config().ConnectKeystore()
827+
if err != nil {
828+
return false, false, err
829+
}
830+
831+
return keystore.CanVerifyAddress(account.Coin())
818832
}

0 commit comments

Comments
 (0)