diff --git a/internal/rpchelp/helpdescs_en_US.go b/internal/rpchelp/helpdescs_en_US.go index daa7fab..4c64c67 100644 --- a/internal/rpchelp/helpdescs_en_US.go +++ b/internal/rpchelp/helpdescs_en_US.go @@ -422,12 +422,6 @@ var helpDescsEnUS = map[string]string{ "The wallet must be unlocked for this request to succeed.", "createnewaccount-account": "Name of the new account", - // ExportWatchingWalletCmd help. - "exportwatchingwallet--synopsis": "Creates and returns a duplicate of the wallet database without any private keys to be used as a watching-only wallet.", - "exportwatchingwallet-account": "Unused (must be unset or \"*\")", - "exportwatchingwallet-download": "Unused", - "exportwatchingwallet--result0": "The watching-only database encoded as a base64 string", - // GetBestBlockCmd help. "getbestblock--synopsis": "Returns the hash and height of the newest block in the best chain that wallet has finished syncing with.", diff --git a/internal/rpchelp/methods.go b/internal/rpchelp/methods.go index d9fbf1b..7e2c136 100644 --- a/internal/rpchelp/methods.go +++ b/internal/rpchelp/methods.go @@ -63,7 +63,6 @@ var Methods = []struct { {"walletpassphrase", nil}, {"walletpassphrasechange", nil}, {"createnewaccount", nil}, - {"exportwatchingwallet", returnsString}, {"getbestblock", []interface{}{(*btcjson.GetBestBlockResult)(nil)}}, {"getunconfirmedbalance", returnsNumber}, {"listaddresstransactions", returnsLTRArray}, diff --git a/rpc/legacyrpc/methods.go b/rpc/legacyrpc/methods.go index 98cacda..fd7925f 100644 --- a/rpc/legacyrpc/methods.go +++ b/rpc/legacyrpc/methods.go @@ -506,9 +506,6 @@ func getAddressInfo(icmd interface{}, w *wallet.Wallet) (interface{}, error) { // just break out now if there is an error. script, err := ma.Script() if err != nil { - if waddrmgr.IsError(err, waddrmgr.ErrWatchingOnly) { - result.IsWatchOnly = true - } break } hexScript := hex.EncodeToString(script) @@ -2054,8 +2051,7 @@ func walletIsLocked(icmd interface{}, w *wallet.Wallet) (interface{}, error) { } // walletLock handles a walletlock request by locking the all account -// wallets, returning an error if any wallet is not encrypted (for example, -// a watching-only wallet). +// wallets, returning an error if any wallet is not encrypted. func walletLock(icmd interface{}, w *wallet.Wallet) (interface{}, error) { w.Lock() return nil, nil diff --git a/rpc/legacyrpc/rpcserverhelp.go b/rpc/legacyrpc/rpcserverhelp.go index e79690a..02472a3 100644 --- a/rpc/legacyrpc/rpcserverhelp.go +++ b/rpc/legacyrpc/rpcserverhelp.go @@ -43,7 +43,6 @@ func helpDescsEnUS() map[string]string { "walletpassphrase": "walletpassphrase \"passphrase\" timeout\n\nUnlock the wallet.\n\nArguments:\n1. passphrase (string, required) The wallet passphrase\n2. timeout (numeric, required) The number of seconds to wait before the wallet automatically locks\n\nResult:\nNothing\n", "walletpassphrasechange": "walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n\nChange the wallet passphrase.\n\nArguments:\n1. oldpassphrase (string, required) The old wallet passphrase\n2. newpassphrase (string, required) The new wallet passphrase\n\nResult:\nNothing\n", "createnewaccount": "createnewaccount \"account\"\n\nCreates a new account.\nThe wallet must be unlocked for this request to succeed.\n\nArguments:\n1. account (string, required) Name of the new account\n\nResult:\nNothing\n", - "exportwatchingwallet": "exportwatchingwallet (\"account\" download=false)\n\nCreates and returns a duplicate of the wallet database without any private keys to be used as a watching-only wallet.\n\nArguments:\n1. account (string, optional) Unused (must be unset or \"*\")\n2. download (boolean, optional, default=false) Unused\n\nResult:\n\"value\" (string) The watching-only database encoded as a base64 string\n", "getbestblock": "getbestblock\n\nReturns the hash and height of the newest block in the best chain that wallet has finished syncing with.\n\nArguments:\nNone\n\nResult:\n{\n \"hash\": \"value\", (string) The hash of the block\n \"height\": n, (numeric) The blockchain height of the block\n} \n", "getunconfirmedbalance": "getunconfirmedbalance (\"account\")\n\nCalculates the unspent output value of all unmined transaction outputs for an account.\n\nArguments:\n1. account (string, optional) The account to query the unconfirmed balance for (default=\"default\")\n\nResult:\nn.nnn (numeric) Total amount of all unmined unspent outputs of the account valued in bitcoin.\n", "listaddresstransactions": "listaddresstransactions [\"address\",...] (\"account\")\n\nReturns a JSON array of objects containing verbose details for wallet transactions pertaining some addresses.\n\nArguments:\n1. addresses (array of string, required) Addresses to filter transaction results by\n2. account (string, optional) Unused (must be unset or \"*\")\n\nResult:\n[{\n \"abandoned\": true|false, (boolean) Unset\n \"account\": \"value\", (string) DEPRECATED -- Unset\n \"address\": \"value\", (string) Payment address for a transaction output\n \"amount\": n.nnn, (numeric) The value of the transaction output valued in bitcoin\n \"bip125-replaceable\": \"value\", (string) Unset\n \"blockhash\": \"value\", (string) The hash of the block this transaction is mined in, or the empty string if unmined\n \"blockheight\": n, (numeric) The block height containing the transaction.\n \"blockindex\": n, (numeric) Unset\n \"blocktime\": n, (numeric) The Unix time of the block header this transaction is mined in, or 0 if unmined\n \"category\": \"value\", (string) The kind of transaction: \"send\" for sent transactions, \"immature\" for immature coinbase outputs, \"generate\" for mature coinbase outputs, or \"recv\" for all other received outputs. Note: A single output may be included multiple times under different categories\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction\n \"fee\": n.nnn, (numeric) The total input value minus the total output value for sent transactions\n \"generated\": true|false, (boolean) Whether the transaction output is a coinbase output\n \"involveswatchonly\": true|false, (boolean) Unset\n \"label\": \"value\", (string) A comment for the address/transaction, if any\n \"time\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"timereceived\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"trusted\": true|false, (boolean) Unset\n \"txid\": \"value\", (string) The hash of the transaction\n \"vout\": n, (numeric) The transaction output index\n \"walletconflicts\": [\"value\",...], (array of string) Unset\n \"comment\": \"value\", (string) Unset\n \"otheraccount\": \"value\", (string) Unset\n},...]\n", @@ -57,4 +56,4 @@ var localeHelpDescs = map[string]func() map[string]string{ "en_US": helpDescsEnUS, } -var requestUsages = "addmultisigaddress nrequired [\"key\",...] (\"account\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetaddressinfo \"address\"\ngetbalance (\"account\" minconf=1)\ngetbestblockhash\ngetblockcount\ngetinfo\ngetnewaddress (\"account\" \"addresstype\")\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nkeypoolrefill (newsize=100)\nlistaccounts (minconf=1)\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"txid\":\"value\",\"vout\":n},...]\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\ncreatenewaccount \"account\"\nexportwatchingwallet (\"account\" download=false)\ngetbestblock\ngetunconfirmedbalance (\"account\")\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nrenameaccount \"oldaccount\" \"newaccount\"\nwalletislocked" +var requestUsages = "addmultisigaddress nrequired [\"key\",...] (\"account\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetaddressinfo \"address\"\ngetbalance (\"account\" minconf=1)\ngetbestblockhash\ngetblockcount\ngetinfo\ngetnewaddress (\"account\" \"addresstype\")\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nkeypoolrefill (newsize=100)\nlistaccounts (minconf=1)\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"txid\":\"value\",\"vout\":n},...]\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\ncreatenewaccount \"account\"\ngetbestblock\ngetunconfirmedbalance (\"account\")\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nrenameaccount \"oldaccount\" \"newaccount\"\nwalletislocked" diff --git a/waddrmgr/address.go b/waddrmgr/address.go index 6c71f00..4d667b9 100644 --- a/waddrmgr/address.go +++ b/waddrmgr/address.go @@ -101,8 +101,7 @@ type ManagedPubKeyAddress interface { ExportPubKey() string // PrivKey returns the private key for the address. It can fail if the - // address manager is watching-only or locked, or the address does not - // have any keys. + // address manager is locked, or the address does not have any keys. PrivKey() (*btcec.PrivateKey, error) // ExportPrivKey returns the private key associated with the address @@ -155,12 +154,6 @@ func (a *managedAddress) unlock(key EncryptorDecryptor) ([]byte, error) { a.privKeyMutex.Lock() defer a.privKeyMutex.Unlock() - // If the address belongs to a watch-only account, the encrypted private - // key won't be present, so we'll return an error. - if len(a.privKeyEncrypted) == 0 { - return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil) - } - if len(a.privKeyCT) == 0 { privKey, err := key.Decrypt(a.privKeyEncrypted) if err != nil { @@ -284,14 +277,10 @@ func (a *managedAddress) ExportPubKey() string { } // PrivKey returns the private key for the address. It can fail if the address -// manager is watching-only or locked, or the address does not have any keys. +// manager is locked, or the address does not have any keys. // // This is part of the ManagedPubKeyAddress interface implementation. func (a *managedAddress) PrivKey() (*btcec.PrivateKey, error) { - // No private keys are available for a watching-only address manager. - if a.manager.rootManager.WatchOnly() { - return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil) - } a.manager.mtx.Lock() defer a.manager.mtx.Unlock() @@ -623,10 +612,6 @@ func (a *scriptAddress) Used(ns walletdb.ReadBucket) bool { // // This is part of the ManagedAddress interface implementation. func (a *scriptAddress) Script() ([]byte, error) { - // No script is available for a watching-only address manager. - if a.manager.rootManager.WatchOnly() { - return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil) - } a.manager.mtx.Lock() defer a.manager.mtx.Unlock() @@ -721,10 +706,6 @@ func (a *witnessScriptAddress) Used(ns walletdb.ReadBucket) bool { // // This is part of the ManagedAddress interface implementation. func (a *witnessScriptAddress) Script() ([]byte, error) { - // No script is available for a watching-only address manager. - if a.isSecretScript && a.manager.rootManager.WatchOnly() { - return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil) - } a.manager.mtx.Lock() defer a.manager.mtx.Unlock() diff --git a/waddrmgr/db.go b/waddrmgr/db.go index d4ccf29..2372b8b 100644 --- a/waddrmgr/db.go +++ b/waddrmgr/db.go @@ -6,7 +6,6 @@ package waddrmgr import ( - "bytes" "crypto/sha256" "encoding/binary" "errors" @@ -85,12 +84,6 @@ const ( // database. This is an account that re-uses the key derivation schema // of BIP0044-like accounts. accountDefault accountType = 0 // not iota as they need to be stable - - // accountWatchOnly is the account type used for storing watch-only - // accounts within the database. This is an account that re-uses the key - // derivation schema of BIP0044-like accounts and does not store private - // keys. - accountWatchOnly accountType = 1 ) // dbAccountRow houses information stored about an account in the database. @@ -110,18 +103,6 @@ type dbDefaultAccountRow struct { name string } -// dbWatchOnlyAccountRow houses additional information stored about a watch-only -// account in the databse. -type dbWatchOnlyAccountRow struct { - dbAccountRow - pubKeyEncrypted []byte - masterKeyFingerprint uint32 - nextExternalIndex uint32 - nextInternalIndex uint32 - name string - addrSchema *ScopeAddrSchema -} - // dbAddressRow houses common information stored about an address in the // database. type dbAddressRow struct { @@ -298,7 +279,6 @@ var ( cryptoPrivKeyName = []byte("cpriv") cryptoPubKeyName = []byte("cpub") cryptoScriptKeyName = []byte("cscript") - watchingOnlyName = []byte("watchonly") // Sync related key names (sync bucket). syncedToName = []byte("syncedto") @@ -439,9 +419,7 @@ func putManagerVersion(ns walletdb.ReadWriteBucket, version uint32) error { } // fetchMasterKeyParams loads the master key parameters needed to derive them -// (when given the correct user-supplied passphrase) from the database. Either -// returned value can be nil, but in practice only the private key params will -// be nil for a watching-only database. +// (when given the correct user-supplied passphrase) from the database. func fetchMasterKeyParams(ns walletdb.ReadBucket) ([]byte, []byte, error) { bucket := ns.NestedReadBucket(mainBucketName) @@ -604,8 +582,7 @@ func fetchMasterHDKeys(ns walletdb.ReadBucket) ([]byte, []byte) { // fetchCryptoKeys loads the encrypted crypto keys which are in turn used to // protect the extended keys, imported keys, and scripts. Any of the returned -// values can be nil, but in practice only the crypto private and script keys -// will be nil for a watching-only database. +// values can be nil func fetchCryptoKeys(ns walletdb.ReadBucket) ([]byte, []byte, []byte, error) { bucket := ns.NestedReadBucket(mainBucketName) @@ -672,35 +649,6 @@ func putCryptoKeys(ns walletdb.ReadWriteBucket, pubKeyEncrypted, privKeyEncrypte return nil } -// fetchWatchingOnly loads the watching-only flag from the database. -func fetchWatchingOnly(ns walletdb.ReadBucket) (bool, error) { - bucket := ns.NestedReadBucket(mainBucketName) - - buf := bucket.Get(watchingOnlyName) - if len(buf) != 1 { - str := "malformed watching-only flag stored in database" - return false, managerError(ErrDatabase, str, nil) - } - - return buf[0] != 0, nil -} - -// putWatchingOnly stores the watching-only flag to the database. -func putWatchingOnly(ns walletdb.ReadWriteBucket, watchingOnly bool) error { - bucket := ns.NestedReadWriteBucket(mainBucketName) - - var encoded byte - if watchingOnly { - encoded = 1 - } - - if err := bucket.Put(watchingOnlyName, []byte{encoded}); err != nil { - str := "failed to store watching only flag" - return managerError(ErrDatabase, str, err) - } - return nil -} - // deserializeAccountRow deserializes the passed serialized account information. // This is used as a common base for the various account types to deserialize // the common parts. @@ -817,159 +765,6 @@ func serializeDefaultAccountRow(encryptedPubKey, encryptedPrivKey []byte, return rawData } -// deserializeWatchOnlyAccountRow deserializes the raw data from the passed -// account row as a watch-only account. -func deserializeWatchOnlyAccountRow(accountID []byte, - row *dbAccountRow) (*dbWatchOnlyAccountRow, error) { - - // The serialized BIP0044 watch-only account raw data format is: - // - // - // - // 4 bytes encrypted pubkey len + encrypted pubkey + 4 bytes master key - // fingerprint + 4 bytes next external index + 4 bytes next internal - // index + 4 bytes name len + name + 1 byte addr schema exists + 2 bytes - // addr schema (if exists) - - // Given the above, the length of the entry must be at a minimum - // the constant value sizes. - if len(row.rawData) < 21 { - str := fmt.Sprintf("malformed serialized watch-only account "+ - "for key %x", accountID) - return nil, managerError(ErrDatabase, str, nil) - } - - retRow := dbWatchOnlyAccountRow{ - dbAccountRow: *row, - } - r := bytes.NewReader(row.rawData) - - var pubLen uint32 - err := binary.Read(r, binary.LittleEndian, &pubLen) - if err != nil { - return nil, err - } - retRow.pubKeyEncrypted = make([]byte, pubLen) - err = binary.Read(r, binary.LittleEndian, &retRow.pubKeyEncrypted) - if err != nil { - return nil, err - } - - err = binary.Read(r, binary.LittleEndian, &retRow.masterKeyFingerprint) - if err != nil { - return nil, err - } - - err = binary.Read(r, binary.LittleEndian, &retRow.nextExternalIndex) - if err != nil { - return nil, err - } - err = binary.Read(r, binary.LittleEndian, &retRow.nextInternalIndex) - if err != nil { - return nil, err - } - - var nameLen uint32 - err = binary.Read(r, binary.LittleEndian, &nameLen) - if err != nil { - return nil, err - } - name := make([]byte, nameLen) - err = binary.Read(r, binary.LittleEndian, &name) - if err != nil { - return nil, err - } - retRow.name = string(name) - - var addrSchemaExists bool - err = binary.Read(r, binary.LittleEndian, &addrSchemaExists) - if err != nil { - return nil, err - } - if addrSchemaExists { - var addrSchemaBytes [2]byte - err = binary.Read(r, binary.LittleEndian, &addrSchemaBytes) - if err != nil { - return nil, err - } - retRow.addrSchema = scopeSchemaFromBytes(addrSchemaBytes[:]) - } - - return &retRow, nil -} - -// serializeWatchOnlyAccountRow returns the serialization of the raw data field -// for a watch-only account. -func serializeWatchOnlyAccountRow(encryptedPubKey []byte, masterKeyFingerprint, - nextExternalIndex, nextInternalIndex uint32, name string, - addrSchema *ScopeAddrSchema) ([]byte, error) { - - // The serialized BIP0044 account raw data format is: - // - // - // - // 4 bytes encrypted pubkey len + encrypted pubkey + 4 bytes master key - // fingerprint + 4 bytes next external index + 4 bytes next internal - // index + 4 bytes name len + name + 1 byte addr schema exists + 2 bytes - // addr schema (if exists) - pubLen := uint32(len(encryptedPubKey)) - nameLen := uint32(len(name)) - - addrSchemaExists := addrSchema != nil - var addrSchemaBytes []byte - if addrSchemaExists { - addrSchemaBytes = scopeSchemaToBytes(addrSchema) - } - - bufLen := 21 + pubLen + nameLen + uint32(len(addrSchemaBytes)) - buf := bytes.NewBuffer(make([]byte, 0, bufLen)) - - err := binary.Write(buf, binary.LittleEndian, pubLen) - if err != nil { - return nil, err - } - err = binary.Write(buf, binary.LittleEndian, encryptedPubKey) - if err != nil { - return nil, err - } - - err = binary.Write(buf, binary.LittleEndian, masterKeyFingerprint) - if err != nil { - return nil, err - } - - err = binary.Write(buf, binary.LittleEndian, nextExternalIndex) - if err != nil { - return nil, err - } - err = binary.Write(buf, binary.LittleEndian, nextInternalIndex) - if err != nil { - return nil, err - } - - err = binary.Write(buf, binary.LittleEndian, nameLen) - if err != nil { - return nil, err - } - err = binary.Write(buf, binary.LittleEndian, []byte(name)) - if err != nil { - return nil, err - } - - err = binary.Write(buf, binary.LittleEndian, addrSchemaExists) - if err != nil { - return nil, err - } - if addrSchemaExists { - err = binary.Write(buf, binary.LittleEndian, addrSchemaBytes) - if err != nil { - return nil, err - } - } - - return buf.Bytes(), nil -} - // forEachKeyScope calls the given function for each known manager scope // within the set of scopes known by the root manager. func forEachKeyScope(ns walletdb.ReadBucket, fn func(KeyScope) error) error { @@ -1108,8 +903,6 @@ func fetchAccountInfo(ns walletdb.ReadBucket, scope *KeyScope, switch row.acctType { case accountDefault: return deserializeDefaultAccountRow(accountID, row) - case accountWatchOnly: - return deserializeWatchOnlyAccountRow(accountID, row) } str := fmt.Sprintf("unsupported account type '%d'", row.acctType) @@ -1270,30 +1063,6 @@ func putDefaultAccountInfo(ns walletdb.ReadWriteBucket, scope *KeyScope, return putAccountInfo(ns, scope, account, &acctRow, name) } -// putWatchOnlyAccountInfo stores the provided watch-only account information to -// the database. -func putWatchOnlyAccountInfo(ns walletdb.ReadWriteBucket, scope *KeyScope, - account uint32, encryptedPubKey []byte, masterKeyFingerprint, - nextExternalIndex, nextInternalIndex uint32, name string, - addrSchema *ScopeAddrSchema) error { - - rawData, err := serializeWatchOnlyAccountRow( - encryptedPubKey, masterKeyFingerprint, nextExternalIndex, - nextInternalIndex, name, addrSchema, - ) - if err != nil { - return err - } - - // TODO(roasbeef): pass scope bucket directly?? - - acctRow := dbAccountRow{ - acctType: accountWatchOnly, - rawData: rawData, - } - return putAccountInfo(ns, scope, account, &acctRow, name) -} - // putAccountInfo stores the provided account information to the database. func putAccountInfo(ns walletdb.ReadWriteBucket, scope *KeyScope, account uint32, acctRow *dbAccountRow, name string) error { @@ -1763,32 +1532,6 @@ func putChainedAddress(ns walletdb.ReadWriteBucket, scope *KeyScope, arow.pubKeyEncrypted, arow.privKeyEncrypted, nextExternalIndex, nextInternalIndex, arow.name, ) - - case accountWatchOnly: - arow, err := deserializeWatchOnlyAccountRow(accountID, row) - if err != nil { - return err - } - - // Increment the appropriate next index depending on whether the - // branch is internal or external. - nextExternalIndex := arow.nextExternalIndex - nextInternalIndex := arow.nextInternalIndex - if branch == InternalBranch { - nextInternalIndex = index + 1 - } else { - nextExternalIndex = index + 1 - } - - // Reserialize the account with the updated index and store it. - row.rawData, err = serializeWatchOnlyAccountRow( - arow.pubKeyEncrypted, arow.masterKeyFingerprint, - nextExternalIndex, nextInternalIndex, arow.name, - arow.addrSchema, - ) - if err != nil { - return err - } } err = bucket.Put(accountID, serializeAccountRow(row)) @@ -1982,12 +1725,6 @@ func forEachActiveAddress(ns walletdb.ReadBucket, scope *KeyScope, } // deletePrivateKeys removes all private key material from the database. -// -// NOTE: Care should be taken when calling this function. It is primarily -// intended for use in converting to a watching-only copy. Removing the private -// keys from the main database without also marking it watching-only will result -// in an unusable database. It will also make any imported scripts and private -// keys unrecoverable unless there is a backup copy available. func deletePrivateKeys(ns walletdb.ReadWriteBucket) error { bucket := ns.NestedReadWriteBucket(mainBucketName) @@ -2058,9 +1795,6 @@ func deletePrivateKeys(ns walletdb.ReadWriteBucket) error { str := "failed to delete account private key" return managerError(ErrDatabase, str, err) } - - // Watch-only accounts don't contain any private keys. - case accountWatchOnly: } return nil diff --git a/waddrmgr/error.go b/waddrmgr/error.go index f360c55..2afe3c8 100644 --- a/waddrmgr/error.go +++ b/waddrmgr/error.go @@ -29,10 +29,6 @@ var ( // errLocked is the common error description used for the ErrLocked // error code. errLocked = "address manager is locked" - - // errWatchingOnly is the common error description used for the - // ErrWatchingOnly error code. - errWatchingOnly = "address manager is watching-only" ) // ErrorCode identifies a kind of error. @@ -86,11 +82,6 @@ const ( // manager to be unlocked, was requested on a locked account manager. ErrLocked - // ErrWatchingOnly indicates that an operation, which requires the - // account manager to have access to private data, was requested on - // a watching-only account manager. - ErrWatchingOnly - // ErrInvalidAccount indicates that the requested account is not valid. ErrInvalidAccount @@ -157,7 +148,6 @@ var errorCodeStrings = map[ErrorCode]string{ ErrCoinTypeTooHigh: "ErrCoinTypeTooHigh", ErrAccountNumTooHigh: "ErrAccountNumTooHigh", ErrLocked: "ErrLocked", - ErrWatchingOnly: "ErrWatchingOnly", ErrInvalidAccount: "ErrInvalidAccount", ErrAddressNotFound: "ErrAddressNotFound", ErrAccountNotFound: "ErrAccountNotFound", diff --git a/waddrmgr/error_test.go b/waddrmgr/error_test.go index 8e0ed14..4dbe65a 100644 --- a/waddrmgr/error_test.go +++ b/waddrmgr/error_test.go @@ -28,7 +28,6 @@ func TestErrorCodeStringer(t *testing.T) { {waddrmgr.ErrCoinTypeTooHigh, "ErrCoinTypeTooHigh"}, {waddrmgr.ErrAccountNumTooHigh, "ErrAccountNumTooHigh"}, {waddrmgr.ErrLocked, "ErrLocked"}, - {waddrmgr.ErrWatchingOnly, "ErrWatchingOnly"}, {waddrmgr.ErrInvalidAccount, "ErrInvalidAccount"}, {waddrmgr.ErrAddressNotFound, "ErrAddressNotFound"}, {waddrmgr.ErrAccountNotFound, "ErrAccountNotFound"}, diff --git a/waddrmgr/manager.go b/waddrmgr/manager.go index 3800d9b..b6efbc0 100644 --- a/waddrmgr/manager.go +++ b/waddrmgr/manager.go @@ -42,16 +42,6 @@ const ( // ImportedAddrAccountName is the name of the imported account. ImportedAddrAccountName = "imported" - // ImportedWatchonlyAddrAccount is the account number to use for all - // imported watchonly addresses, such as public keys and addresses. - // This is useful since normal accounts are derived from the root - // hierarchical deterministic key and imported addresses do not fit - // into that model. - ImportedWatchonlyAddrAccount = hdkeychain.HardenedKeyStart - 2 // 2^31 - 2 - - // ImportedWatchonlyAddrAccountName is the name of the imported watchonly account. - ImportedWatchonlyAddrAccountName = "imported-watchonly" - // DefaultAccountNum is the number of the default account. DefaultAccountNum = 0 @@ -221,10 +211,6 @@ type AccountProperties struct { // KeyScope is the key scope the account belongs to. KeyScope KeyScope - // IsWatchOnly indicates whether the is set up as watch-only, i.e., it - // doesn't contain any private key information. - IsWatchOnly bool - // AddrSchema, if non-nil, specifies an address schema override for // address generation only applicable to the account. AddrSchema *ScopeAddrSchema @@ -352,12 +338,11 @@ type Manager struct { externalAddrSchemas map[AddressType][]KeyScope internalAddrSchemas map[AddressType][]KeyScope - syncState syncState - watchingOnly bool - birthday time.Time - locked bool - closed bool - chainParams *chaincfg.Params + syncState syncState + birthday time.Time + locked bool + closed bool + chainParams *chaincfg.Params // masterKeyPub is the secret key used to secure the cryptoKeyPub key // and masterKeyPriv is the secret key used to secure the cryptoKeyPriv @@ -397,46 +382,6 @@ type Manager struct { hashedPrivPassphrase [sha512.Size]byte } -// WatchOnly returns true if the root manager is in watch only mode, and false -// otherwise. -func (m *Manager) WatchOnly() bool { - m.mtx.RLock() - defer m.mtx.RUnlock() - - return m.watchOnly() -} - -// watchOnly returns true if the root manager is in watch only mode, and false -// otherwise. -// -// NOTE: This method requires the Manager's lock to be held. -func (m *Manager) watchOnly() bool { - return m.watchingOnly -} - -// IsWatchOnlyAccount determines if the account with the given key scope is set -// up as watch-only. -func (m *Manager) IsWatchOnlyAccount(ns walletdb.ReadBucket, keyScope KeyScope, - account uint32) (bool, error) { - - if m.WatchOnly() { - return true, nil - } - - if account == ImportedAddrAccount { - return false, nil - } - if account == ImportedWatchonlyAddrAccount { - return true, nil - } - - scopedMgr, err := m.FetchScopedKeyManager(keyScope) - if err != nil { - return false, err - } - return scopedMgr.IsWatchOnlyAccount(ns, account) -} - // lock performs a best try effort to remove and zero all secret keys associated // with the address manager. // @@ -497,7 +442,7 @@ func (m *Manager) Close() { } // Attempt to clear private key material from memory. - if !m.watchingOnly && !m.locked { + if !m.locked { m.lock() } @@ -525,51 +470,50 @@ func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket, defer m.mtx.Unlock() var rootPriv *hdkeychain.ExtendedKey - if !m.watchingOnly { - // If the manager is locked, then we can't create a new scoped - // manager. - if m.locked { - return nil, managerError(ErrLocked, errLocked, nil) - } + // If the manager is locked, then we can't create a new scoped + // manager. + if m.locked { + return nil, managerError(ErrLocked, errLocked, nil) + } - // Now that we know the manager is unlocked, we'll need to - // fetch the root master HD private key. This is required as - // we'll be attempting the following derivation: - // m/purpose'/cointype' - // - // Note that the path to the coin type is requires hardened - // derivation, therefore this can only be done if the wallet's - // root key hasn't been neutered. - masterRootPrivEnc, _ := fetchMasterHDKeys(ns) + // Now that we know the manager is unlocked, we'll need to + // fetch the root master HD private key. This is required as + // we'll be attempting the following derivation: + // m/purpose'/cointype' + // + // Note that the path to the coin type is requires hardened + // derivation, therefore this can only be done if the wallet's + // root key hasn't been neutered. + masterRootPrivEnc, _ := fetchMasterHDKeys(ns) - // If the master root private key isn't found within the - // database, but we need to bail here as we can't create the - // cointype key without the master root private key. - if masterRootPrivEnc == nil { - return nil, managerError(ErrWatchingOnly, "", nil) - } + // If the master root private key isn't found within the + // database, but we need to bail here as we can't create the + // cointype key without the master root private key. + if masterRootPrivEnc == nil { + str := fmt.Sprintf("no master root private key found") + return nil, managerError(ErrKeyChain, str, nil) + } - // Before we can derive any new scoped managers using this - // key, we'll need to fully decrypt it. - serializedMasterRootPriv, err := - m.cryptoKeyPriv.Decrypt(masterRootPrivEnc) - if err != nil { - str := fmt.Sprintf("failed to decrypt master root " + - "serialized private key") - return nil, managerError(ErrLocked, str, err) - } + // Before we can derive any new scoped managers using this + // key, we'll need to fully decrypt it. + serializedMasterRootPriv, err := + m.cryptoKeyPriv.Decrypt(masterRootPrivEnc) + if err != nil { + str := fmt.Sprintf("failed to decrypt master root " + + "serialized private key") + return nil, managerError(ErrLocked, str, err) + } - // Now that we know the root priv is within the database, - // we'll decode it into a usable object. - rootPriv, err = hdkeychain.NewKeyFromString( - string(serializedMasterRootPriv), - ) - zero.Bytes(serializedMasterRootPriv) - if err != nil { - str := fmt.Sprintf("failed to create master extended " + - "private key") - return nil, managerError(ErrKeyChain, str, err) - } + // Now that we know the root priv is within the database, + // we'll decode it into a usable object. + rootPriv, err = hdkeychain.NewKeyFromString( + string(serializedMasterRootPriv), + ) + zero.Bytes(serializedMasterRootPriv) + if err != nil { + str := fmt.Sprintf("failed to create master extended " + + "private key") + return nil, managerError(ErrKeyChain, str, err) } // Now that we have the root private key, we'll fetch the scope bucket @@ -591,21 +535,19 @@ func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket, } scopeKey := scopeToBytes(&scope) schemaBytes := scopeSchemaToBytes(&addrSchema) - err := scopeSchemas.Put(scopeKey[:], schemaBytes) + err = scopeSchemas.Put(scopeKey[:], schemaBytes) if err != nil { return nil, err } - if !m.watchingOnly { - // With the database state created, we'll now derive the - // cointype key using the master HD private key, then encrypt - // it along with the first account using our crypto keys. - err = createManagerKeyScope( - ns, scope, rootPriv, m.cryptoKeyPub, m.cryptoKeyPriv, - ) - if err != nil { - return nil, err - } + // With the database state created, we'll now derive the + // cointype key using the master HD private key, then encrypt + // it along with the first account using our crypto keys. + err = createManagerKeyScope( + ns, scope, rootPriv, m.cryptoKeyPub, m.cryptoKeyPriv, + ) + if err != nil { + return nil, err } // Finally, we'll register this new scoped manager with the root @@ -886,19 +828,13 @@ func (m *Manager) ChainParams() *chaincfg.Params { } // ChangePassphrase changes either the public or private passphrase to the -// provided value depending on the private flag. In order to change the -// private password, the address manager must not be watching-only. The new -// passphrase keys are derived using the scrypt parameters in the options, so -// changing the passphrase may be used to bump the computational difficulty -// needed to brute force the passphrase. +// provided value depending on the private flag. The new passphrase keys are +// derived using the scrypt parameters in the options, so changing the +// passphrase may be used to bump the computational difficulty needed to brute +// force the passphrase. func (m *Manager) ChangePassphrase(ns walletdb.ReadWriteBucket, oldPassphrase, newPassphrase []byte, private bool, config *ScryptOptions) error { - // No private passphrase to change for a watching-only address manager. - if private && m.watchingOnly { - return managerError(ErrWatchingOnly, errWatchingOnly, nil) - } - m.mtx.Lock() defer m.mtx.Unlock() @@ -1045,91 +981,6 @@ func (m *Manager) ChangePassphrase(ns walletdb.ReadWriteBucket, oldPassphrase, return nil } -// ConvertToWatchingOnly converts the current address manager to a locked -// watching-only address manager. -// -// WARNING: This function removes private keys from the existing address manager -// which means they will no longer be available. Typically the caller will make -// a copy of the existing wallet database and modify the copy since otherwise it -// would mean permanent loss of any imported private keys and scripts. -// -// Executing this function on a manager that is already watching-only will have -// no effect. -func (m *Manager) ConvertToWatchingOnly(ns walletdb.ReadWriteBucket) error { - m.mtx.Lock() - defer m.mtx.Unlock() - - // Exit now if the manager is already watching-only. - if m.watchingOnly { - return nil - } - - var err error - - // Remove all private key material and mark the new database as - // watching only. - if err := deletePrivateKeys(ns); err != nil { - return maybeConvertDbError(err) - } - - err = putWatchingOnly(ns, true) - if err != nil { - return maybeConvertDbError(err) - } - - // Lock the manager to remove all clear text private key material from - // memory if needed. - if !m.locked { - m.lock() - } - - // This section clears and removes the encrypted private key material - // that is ordinarily used to unlock the manager. Since the the manager - // is being converted to watching-only, the encrypted private key - // material is no longer needed. - - // Clear and remove all of the encrypted acount private keys. - for _, manager := range m.scopedManagers { - for _, acctInfo := range manager.acctInfo { - zero.Bytes(acctInfo.acctKeyEncrypted) - acctInfo.acctKeyEncrypted = nil - } - } - - // Clear and remove encrypted private keys and encrypted scripts from - // all address entries. - for _, manager := range m.scopedManagers { - for _, ma := range manager.addrs { - switch addr := ma.(type) { - case *managedAddress: - zero.Bytes(addr.privKeyEncrypted) - addr.privKeyEncrypted = nil - case *scriptAddress: - zero.Bytes(addr.scriptEncrypted) - addr.scriptEncrypted = nil - } - } - } - - // Clear and remove encrypted private and script crypto keys. - zero.Bytes(m.cryptoKeyScriptEncrypted) - m.cryptoKeyScriptEncrypted = nil - m.cryptoKeyScript = nil - zero.Bytes(m.cryptoKeyPrivEncrypted) - m.cryptoKeyPrivEncrypted = nil - m.cryptoKeyPriv = nil - - // The master private key is derived from a passphrase when the manager - // is unlocked, so there is no encrypted version to zero. However, - // it is no longer needed, so nil it. - m.masterKeyPriv = nil - - // Mark the manager watching-only. - m.watchingOnly = true - return nil - -} - // IsLocked returns whether or not the address managed is locked. When it is // unlocked, the decryption key needed to decrypt private keys used for signing // is in memory. @@ -1151,14 +1002,7 @@ func (m *Manager) isLocked() bool { // Lock performs a best try effort to remove and zero all secret keys associated // with the address manager. -// -// This function will return an error if invoked on a watching-only address -// manager. func (m *Manager) Lock() error { - // A watching-only address manager can't be locked. - if m.watchingOnly { - return managerError(ErrWatchingOnly, errWatchingOnly, nil) - } m.mtx.Lock() defer m.mtx.Unlock() @@ -1177,14 +1021,7 @@ func (m *Manager) Lock() error { // is stored in memory until the address manager is locked. Any failures that // occur during this function will result in the address manager being locked, // even if it was already unlocked prior to calling this function. -// -// This function will return an error if invoked on a watching-only address -// manager. func (m *Manager) Unlock(ns walletdb.ReadBucket, passphrase []byte) error { - // A watching-only address manager can't be unlocked. - if m.watchingOnly { - return managerError(ErrWatchingOnly, errWatchingOnly, nil) - } m.mtx.Lock() defer m.mtx.Unlock() @@ -1337,7 +1174,7 @@ func (m *Manager) LookupAccount(ns walletdb.ReadBucket, name string) (KeyScope, func (m *Manager) selectCryptoKey(keyType CryptoKeyType) (EncryptorDecryptor, error) { if keyType == CKTPrivate || keyType == CKTScript { // The manager must be unlocked to work with the private keys. - if m.locked || m.watchingOnly { + if m.locked { return nil, managerError(ErrLocked, errLocked, nil) } } @@ -1401,7 +1238,7 @@ func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey, masterKeyPriv *snacl.SecretKey, cryptoKeyPub EncryptorDecryptor, cryptoKeyPrivEncrypted, cryptoKeyScriptEncrypted []byte, syncInfo *syncState, birthday time.Time, privPassphraseSalt [saltSize]byte, - scopedManagers map[KeyScope]*ScopedKeyManager, watchingOnly bool) *Manager { + scopedManagers map[KeyScope]*ScopedKeyManager) *Manager { m := &Manager{ chainParams: chainParams, @@ -1419,7 +1256,6 @@ func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey, scopedManagers: scopedManagers, externalAddrSchemas: make(map[AddressType][]KeyScope), internalAddrSchemas: make(map[AddressType][]KeyScope), - watchingOnly: watchingOnly, } for _, sMgr := range m.scopedManagers { @@ -1546,12 +1382,6 @@ func loadManager(ns walletdb.ReadBucket, pubPassphrase []byte, return nil, managerError(ErrUpgrade, str, nil) } - // Load whether or not the manager is watching-only from the db. - watchingOnly, err := fetchWatchingOnly(ns) - if err != nil { - return nil, maybeConvertDbError(err) - } - // Load the master key params from the db. masterKeyPubParams, masterKeyPrivParams, err := fetchMasterKeyParams(ns) if err != nil { @@ -1579,15 +1409,13 @@ func loadManager(ns walletdb.ReadBucket, pubPassphrase []byte, return nil, maybeConvertDbError(err) } - // When not a watching-only manager, set the master private key params, - // but don't derive it now since the manager starts off locked. + // Set the master private key params, but don't derive it now since the + // manager starts off locked. var masterKeyPriv snacl.SecretKey - if !watchingOnly { - err := masterKeyPriv.Unmarshal(masterKeyPrivParams) - if err != nil { - str := "failed to unmarshal master private key" - return nil, managerError(ErrCrypto, str, err) - } + err = masterKeyPriv.Unmarshal(masterKeyPrivParams) + if err != nil { + str := "failed to unmarshal master private key" + return nil, managerError(ErrCrypto, str, err) } // Derive the master public key using the serialized params and provided @@ -1654,8 +1482,7 @@ func loadManager(ns walletdb.ReadBucket, pubPassphrase []byte, mgr := newManager( chainParams, &masterKeyPub, &masterKeyPriv, cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo, - birthday, privPassphraseSalt, scopedManagers, watchingOnly, - ) + birthday, privPassphraseSalt, scopedManagers) for _, scopedManager := range scopedManagers { scopedManager.rootManager = mgr @@ -1797,11 +1624,6 @@ func createManagerKeyScope(ns walletdb.ReadWriteBucket, // derived. This allows all chained addresses in the address manager // to be recovered by using the same seed. // -// If the provided seed value is nil the address manager will be -// created in watchingOnly mode in which case no default accounts or -// scoped managers are created - it is up to the caller to create a -// new one with NewAccountWatchingOnly and NewScopedKeyManager. -// // All private and public keys and information are protected by secret // keys derived from the provided private and public passphrases. The // public passphrase is required on subsequent opens of the address @@ -1820,9 +1642,6 @@ func Create(ns walletdb.ReadWriteBucket, rootKey *hdkeychain.ExtendedKey, chainParams *chaincfg.Params, config *ScryptOptions, birthday time.Time) error { - // If the seed argument is nil we create in watchingOnly mode. - isWatchingOnly := rootKey == nil - // Return an error if the manager has already been created in // the given database namespace. exists := managerExists(ns) @@ -1831,17 +1650,13 @@ func Create(ns walletdb.ReadWriteBucket, rootKey *hdkeychain.ExtendedKey, } // Ensure the private passphrase is not empty. - if !isWatchingOnly && len(privPassphrase) == 0 { + if len(privPassphrase) == 0 { str := "private passphrase may not be empty" return managerError(ErrEmptyPassphrase, str, nil) } // Perform the initial bucket creation and database namespace setup. - defaultScopes := map[KeyScope]ScopeAddrSchema{} - if !isWatchingOnly { - defaultScopes = ScopeAddrMap - } - if err := createManagerNS(ns, defaultScopes); err != nil { + if err := createManagerNS(ns, ScopeAddrMap); err != nil { return maybeConvertDbError(err) } @@ -1890,92 +1705,90 @@ func Create(ns walletdb.ReadWriteBucket, rootKey *hdkeychain.ExtendedKey, var masterKeyPriv *snacl.SecretKey var cryptoKeyPrivEnc []byte var cryptoKeyScriptEnc []byte - if !isWatchingOnly { - masterKeyPriv, err = newSecretKey(&privPassphrase, config) - if err != nil { - str := "failed to master private key" - return managerError(ErrCrypto, str, err) - } - defer masterKeyPriv.Zero() - - // Generate the private passphrase salt. This is used when - // hashing passwords to detect whether an unlock can be - // avoided when the manager is already unlocked. - var privPassphraseSalt [saltSize]byte - _, err = rand.Read(privPassphraseSalt[:]) - if err != nil { - str := "failed to read random source for passphrase salt" - return managerError(ErrCrypto, str, err) - } - - cryptoKeyPriv, err := newCryptoKey() - if err != nil { - str := "failed to generate crypto private key" - return managerError(ErrCrypto, str, err) - } - defer cryptoKeyPriv.Zero() - cryptoKeyScript, err := newCryptoKey() - if err != nil { - str := "failed to generate crypto script key" - return managerError(ErrCrypto, str, err) - } - defer cryptoKeyScript.Zero() - - cryptoKeyPrivEnc, err = - masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes()) - if err != nil { - str := "failed to encrypt crypto private key" - return managerError(ErrCrypto, str, err) - } - cryptoKeyScriptEnc, err = - masterKeyPriv.Encrypt(cryptoKeyScript.Bytes()) - if err != nil { - str := "failed to encrypt crypto script key" - return managerError(ErrCrypto, str, err) - } - - // Generate the BIP0044 HD key structure to ensure the - // provided seed can generate the required structure with no - // issues. - rootPubKey, err := rootKey.Neuter() - if err != nil { - str := "failed to neuter master extended key" - return managerError(ErrKeyChain, str, err) - } - - // Next, for each registers default manager scope, we'll - // create the hardened cointype key for it, as well as the - // first default account. - for _, defaultScope := range DefaultKeyScopes { - err := createManagerKeyScope( - ns, defaultScope, rootKey, cryptoKeyPub, cryptoKeyPriv, - ) - if err != nil { - return maybeConvertDbError(err) - } - } - - // Before we proceed, we'll also store the root master private - // key within the database in an encrypted format. This is - // required as in the future, we may need to create additional - // scoped key managers. - masterHDPrivKeyEnc, err := - cryptoKeyPriv.Encrypt([]byte(rootKey.String())) - if err != nil { - return maybeConvertDbError(err) - } - masterHDPubKeyEnc, err := - cryptoKeyPub.Encrypt([]byte(rootPubKey.String())) - if err != nil { - return maybeConvertDbError(err) - } - err = putMasterHDKeys(ns, masterHDPrivKeyEnc, masterHDPubKeyEnc) - if err != nil { - return maybeConvertDbError(err) - } - - privParams = masterKeyPriv.Marshal() + masterKeyPriv, err = newSecretKey(&privPassphrase, config) + if err != nil { + str := "failed to master private key" + return managerError(ErrCrypto, str, err) } + defer masterKeyPriv.Zero() + + // Generate the private passphrase salt. This is used when + // hashing passwords to detect whether an unlock can be + // avoided when the manager is already unlocked. + var privPassphraseSalt [saltSize]byte + _, err = rand.Read(privPassphraseSalt[:]) + if err != nil { + str := "failed to read random source for passphrase salt" + return managerError(ErrCrypto, str, err) + } + + cryptoKeyPriv, err := newCryptoKey() + if err != nil { + str := "failed to generate crypto private key" + return managerError(ErrCrypto, str, err) + } + defer cryptoKeyPriv.Zero() + cryptoKeyScript, err := newCryptoKey() + if err != nil { + str := "failed to generate crypto script key" + return managerError(ErrCrypto, str, err) + } + defer cryptoKeyScript.Zero() + + cryptoKeyPrivEnc, err = + masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes()) + if err != nil { + str := "failed to encrypt crypto private key" + return managerError(ErrCrypto, str, err) + } + cryptoKeyScriptEnc, err = + masterKeyPriv.Encrypt(cryptoKeyScript.Bytes()) + if err != nil { + str := "failed to encrypt crypto script key" + return managerError(ErrCrypto, str, err) + } + + // Generate the BIP0044 HD key structure to ensure the + // provided seed can generate the required structure with no + // issues. + rootPubKey, err := rootKey.Neuter() + if err != nil { + str := "failed to neuter master extended key" + return managerError(ErrKeyChain, str, err) + } + + // Next, for each registers default manager scope, we'll + // create the hardened cointype key for it, as well as the + // first default account. + for _, defaultScope := range DefaultKeyScopes { + err := createManagerKeyScope( + ns, defaultScope, rootKey, cryptoKeyPub, cryptoKeyPriv, + ) + if err != nil { + return maybeConvertDbError(err) + } + } + + // Before we proceed, we'll also store the root master private + // key within the database in an encrypted format. This is + // required as in the future, we may need to create additional + // scoped key managers. + masterHDPrivKeyEnc, err := + cryptoKeyPriv.Encrypt([]byte(rootKey.String())) + if err != nil { + return maybeConvertDbError(err) + } + masterHDPubKeyEnc, err := + cryptoKeyPub.Encrypt([]byte(rootPubKey.String())) + if err != nil { + return maybeConvertDbError(err) + } + err = putMasterHDKeys(ns, masterHDPrivKeyEnc, masterHDPubKeyEnc) + if err != nil { + return maybeConvertDbError(err) + } + + privParams = masterKeyPriv.Marshal() // Save the master key params to the database. err = putMasterKeyParams(ns, pubParams, privParams) @@ -1990,13 +1803,6 @@ func Create(ns walletdb.ReadWriteBucket, rootKey *hdkeychain.ExtendedKey, return maybeConvertDbError(err) } - // Save the watching-only mode of the address manager to the - // database. - err = putWatchingOnly(ns, isWatchingOnly) - if err != nil { - return maybeConvertDbError(err) - } - // Save the initial synced to state. err = PutSyncedTo(ns, &syncInfo.syncedTo) if err != nil { diff --git a/waddrmgr/manager_test.go b/waddrmgr/manager_test.go index faa2b0c..73b8881 100644 --- a/waddrmgr/manager_test.go +++ b/waddrmgr/manager_test.go @@ -10,7 +10,6 @@ import ( "encoding/hex" "errors" "fmt" - "os" "reflect" "testing" "time" @@ -70,7 +69,6 @@ type testContext struct { internalAccount uint32 create bool unlocked bool - watchingOnly bool } // addrType is the type of address being tested @@ -162,12 +160,6 @@ func testManagedPubKeyAddress(tc *testContext, prefix string, // for the expected error when the manager is locked. gotPrivKey, err := gotAddr.PrivKey() switch { - case tc.watchingOnly: - // Confirm expected watching-only error. - testName := fmt.Sprintf("%s PrivKey", prefix) - if !checkManagerError(tc.t, testName, err, ErrWatchingOnly) { - return false - } case tc.unlocked: if err != nil { tc.t.Errorf("%s PrivKey: unexpected error - got %v", @@ -194,12 +186,6 @@ func testManagedPubKeyAddress(tc *testContext, prefix string, // the manager is locked. gotWIF, err := gotAddr.ExportPrivKey() switch { - case tc.watchingOnly: - // Confirm expected watching-only error. - testName := fmt.Sprintf("%s ExportPrivKey", prefix) - if !checkManagerError(tc.t, testName, err, ErrWatchingOnly) { - return false - } case tc.unlocked: if err != nil { tc.t.Errorf("%s ExportPrivKey: unexpected error - "+ @@ -245,13 +231,6 @@ func testManagedScriptAddress(tc *testContext, prefix string, // the expected error when the manager is locked. gotScript, err := gotAddr.Script() switch { - case tc.watchingOnly && !wantAddr.scriptNotSecret: - // Confirm expected watching-only error. - testName := fmt.Sprintf("%s Script", prefix) - if !checkManagerError(tc.t, testName, err, ErrWatchingOnly) { - return false - } - // Either the manger is unlocked or the script is not considered to // be secret and is encrypted with the public key. case tc.unlocked || wantAddr.scriptNotSecret: @@ -448,13 +427,6 @@ func testExternalAddresses(tc *testContext) bool { return false } - // Everything after this point involves retesting with an unlocked - // address manager which is not possible for watching-only mode, so - // just exit now in that case. - if tc.watchingOnly { - return true - } - // Unlock the manager and retest all of the addresses to ensure the // private information is valid as well. err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error { @@ -485,24 +457,19 @@ func testExternalAddresses(tc *testContext) bool { // retrieved by Address, and that they work properly when the manager is locked // and unlocked. func testInternalAddresses(tc *testContext) bool { - // When the address manager is not in watching-only mode, unlocked it - // first to ensure that address generation works correctly when the - // address manager is unlocked and then locked later. These tests - // reverse the order done in the external tests which starts with a - // locked manager and unlock it afterwards. - if !tc.watchingOnly { - // Unlock the manager and retest all of the addresses to ensure the - // private information is valid as well. - err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error { - ns := tx.ReadBucket(waddrmgrNamespaceKey) - return tc.rootManager.Unlock(ns, privPassphrase) - }) - if err != nil { - tc.t.Errorf("Unlock: unexpected error: %v", err) - return false - } - tc.unlocked = true + // These tests reverse the order done in the external tests which starts + // with a locked manager and unlock it afterwards. + // Unlock the manager and retest all of the addresses to ensure the + // private information is valid as well. + err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(waddrmgrNamespaceKey) + return tc.rootManager.Unlock(ns, privPassphrase) + }) + if err != nil { + tc.t.Errorf("Unlock: unexpected error: %v", err) + return false } + tc.unlocked = true prefix := testNamePrefix(tc) + " testInternalAddresses" var addrs []ManagedAddress @@ -597,24 +564,10 @@ func testInternalAddresses(tc *testContext) bool { return true } - // The address manager could either be locked or unlocked here depending - // on whether or not it's a watching-only manager. When it's unlocked, - // this will test both the public and private address data are accurate. - // When it's locked, it must be watching-only, so only the public - // address information is tested and the private functions are checked - // to ensure they return the expected ErrWatchingOnly error. if !testResults() { return false } - // Everything after this point involves locking the address manager and - // retesting the addresses with a locked manager. However, for - // watching-only mode, this has already happened, so just exit now in - // that case. - if tc.watchingOnly { - return true - } - // Lock the manager and retest all of the addresses to ensure the // public information remains valid and the private functions return // the expected error. @@ -639,55 +592,38 @@ func testLocking(tc *testContext) bool { return false } - // Locking an already lock manager should return an error. The error - // should be ErrLocked or ErrWatchingOnly depending on the type of the - // address manager. + // Locking an already lock manager should return an error. err := tc.rootManager.Lock() wantErrCode := ErrLocked - if tc.watchingOnly { - wantErrCode = ErrWatchingOnly - } if !checkManagerError(tc.t, "Lock", err, wantErrCode) { return false } // Ensure unlocking with the correct passphrase doesn't return any // unexpected errors and the manager properly reports it is unlocked. - // Since watching-only address managers can't be unlocked, also ensure - // the correct error for that case. err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error { ns := tx.ReadBucket(waddrmgrNamespaceKey) return tc.rootManager.Unlock(ns, privPassphrase) }) - if tc.watchingOnly { - if !checkManagerError(tc.t, "Unlock", err, ErrWatchingOnly) { - return false - } - } else if err != nil { + if err != nil { tc.t.Errorf("Unlock: unexpected error: %v", err) return false } - if !tc.watchingOnly && tc.rootManager.IsLocked() { + if tc.rootManager.IsLocked() { tc.t.Error("IsLocked: returned true on unlocked manager") return false } - // Unlocking the manager again is allowed. Since watching-only address - // managers can't be unlocked, also ensure the correct error for that - // case. + // Unlocking the manager again is allowed. err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error { ns := tx.ReadBucket(waddrmgrNamespaceKey) return tc.rootManager.Unlock(ns, privPassphrase) }) - if tc.watchingOnly { - if !checkManagerError(tc.t, "Unlock2", err, ErrWatchingOnly) { - return false - } - } else if err != nil { + if err != nil { tc.t.Errorf("Unlock: unexpected error: %v", err) return false } - if !tc.watchingOnly && tc.rootManager.IsLocked() { + if tc.rootManager.IsLocked() { tc.t.Error("IsLocked: returned true on unlocked manager") return false } @@ -699,9 +635,6 @@ func testLocking(tc *testContext) bool { return tc.rootManager.Unlock(ns, []byte("invalidpassphrase")) }) wantErrCode = ErrWrongPassphrase - if tc.watchingOnly { - wantErrCode = ErrWatchingOnly - } if !checkManagerError(tc.t, "Unlock", err, wantErrCode) { return false } @@ -760,19 +693,16 @@ func testImportPrivateKey(tc *testContext) bool { }, } - // The manager must be unlocked to import a private key, however a - // watching-only manager can't be unlocked. - if !tc.watchingOnly { - err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error { - ns := tx.ReadBucket(waddrmgrNamespaceKey) - return tc.rootManager.Unlock(ns, privPassphrase) - }) - if err != nil { - tc.t.Errorf("Unlock: unexpected error: %v", err) - return false - } - tc.unlocked = true + // The manager must be unlocked to import a private key. + err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(waddrmgrNamespaceKey) + return tc.rootManager.Unlock(ns, privPassphrase) + }) + if err != nil { + tc.t.Errorf("Unlock: unexpected error: %v", err) + return false } + tc.unlocked = true // Only import the private keys when in the create phase of testing. tc.internalAccount = ImportedAddrAccount @@ -853,24 +783,10 @@ func testImportPrivateKey(tc *testContext) bool { return !failed } - // The address manager could either be locked or unlocked here depending - // on whether or not it's a watching-only manager. When it's unlocked, - // this will test both the public and private address data are accurate. - // When it's locked, it must be watching-only, so only the public - // address information is tested and the private functions are checked - // to ensure they return the expected ErrWatchingOnly error. if !testResults() { return false } - // Everything after this point involves locking the address manager and - // retesting the addresses with a locked manager. However, for - // watching-only mode, this has already happened, so just exit now in - // that case. - if tc.watchingOnly { - return true - } - // Lock the manager and retest all of the addresses to ensure the // private information returns the expected error. if err := tc.rootManager.Lock(); err != nil { @@ -994,19 +910,16 @@ func testImportScript(tc *testContext) bool { } // The manager must be unlocked to import a private key and also for - // testing private data. However, a watching-only manager can't be - // unlocked. - if !tc.watchingOnly { - err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error { - ns := tx.ReadBucket(waddrmgrNamespaceKey) - return tc.rootManager.Unlock(ns, privPassphrase) - }) - if err != nil { - tc.t.Errorf("Unlock: unexpected error: %v", err) - return false - } - tc.unlocked = true + // testing private data. + err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(waddrmgrNamespaceKey) + return tc.rootManager.Unlock(ns, privPassphrase) + }) + if err != nil { + tc.t.Errorf("Unlock: unexpected error: %v", err) + return false } + tc.unlocked = true // Only import the scripts when in the create phase of testing. tc.internalAccount = ImportedAddrAccount @@ -1110,24 +1023,10 @@ func testImportScript(tc *testContext) bool { return !failed } - // The address manager could either be locked or unlocked here depending - // on whether or not it's a watching-only manager. When it's unlocked, - // this will test both the public and private address data are accurate. - // When it's locked, it must be watching-only, so only the public - // address information is tested and the private functions are checked - // to ensure they return the expected ErrWatchingOnly error. if !testResults() { return false } - // Everything after this point involves locking the address manager and - // retesting the addresses with a locked manager. However, for - // watching-only mode, this has already happened, so just exit now in - // that case. - if tc.watchingOnly { - return true - } - // Lock the manager and retest all of the addresses to ensure the // private information returns the expected error. if err := tc.rootManager.Lock(); err != nil { @@ -1286,9 +1185,6 @@ func testChangePassphrase(tc *testContext) bool { return false } - // Attempt to change private passphrase with invalid old passphrase. - // The error should be ErrWrongPassphrase or ErrWatchingOnly depending - // on the type of the address manager. testName = pfx + "ChangePassphrase (private) with invalid old passphrase" err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) @@ -1297,21 +1193,10 @@ func testChangePassphrase(tc *testContext) bool { ) }) wantErrCode := ErrWrongPassphrase - if tc.watchingOnly { - wantErrCode = ErrWatchingOnly - } if !checkManagerError(tc.t, testName, err, wantErrCode) { return false } - // Everything after this point involves testing that the private - // passphrase for the address manager can be changed successfully. - // This is not possible for watching-only mode, so just exit now in that - // case. - if tc.watchingOnly { - return true - } - // Change the private passphrase. testName = pfx + "ChangePassphrase (private)" err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { @@ -1368,22 +1253,6 @@ func testChangePassphrase(tc *testContext) bool { // testNewAccount tests the new account creation func of the address manager works // as expected. func testNewAccount(tc *testContext) bool { - if tc.watchingOnly { - // Creating new accounts in watching-only mode should return ErrWatchingOnly - err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { - ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - _, err := tc.manager.NewAccount(ns, "test") - return err - }) - if !checkManagerError( - tc.t, "Create account in watching-only mode", err, - ErrWatchingOnly, - ) { - tc.manager.Close() - return false - } - return true - } // Creating new accounts when wallet is locked should return ErrLocked err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) @@ -1711,148 +1580,26 @@ func testForEachAccountAddress(tc *testContext) bool { // testManagerAPI tests the functions provided by the Manager API as well as // the ManagedAddress, ManagedPubKeyAddress, and ManagedScriptAddress // interfaces. -func testManagerAPI(tc *testContext, caseCreatedWatchingOnly bool) { - if !caseCreatedWatchingOnly { - // Test API for normal create (w/ seed) case. - testLocking(tc) - testExternalAddresses(tc) - testInternalAddresses(tc) - testImportPrivateKey(tc) - testImportScript(tc) - testMarkUsed(tc, true) - testChangePassphrase(tc) +func testManagerAPI(tc *testContext) { + // Test API for normal create (w/ seed) case. + testLocking(tc) + testExternalAddresses(tc) + testInternalAddresses(tc) + testImportPrivateKey(tc) + testImportScript(tc) + testMarkUsed(tc, true) + testChangePassphrase(tc) - // Reset default account - tc.internalAccount = 0 - testNewAccount(tc) - testLookupAccount(tc) - testForEachAccount(tc) - testForEachAccountAddress(tc) + // Reset default account + tc.internalAccount = 0 + testNewAccount(tc) + testLookupAccount(tc) + testForEachAccount(tc) + testForEachAccountAddress(tc) - // Rename account 1 "acct-create" - tc.internalAccount = 1 - testRenameAccount(tc) - } else { - // Test API for created watch-only case. - testExternalAddresses(tc) - testInternalAddresses(tc) - testMarkUsed(tc, false) - testChangePassphrase(tc) - - testNewAccount(tc) - expectedAccounts := map[string]uint32{ - defaultAccountName: DefaultAccountNum, - } - testLookupExpectedAccount(tc, expectedAccounts, 0) - //testForEachAccount(tc) - testForEachAccountAddress(tc) - } -} - -// testConvertWatchingOnly tests various facets of a watching-only address -// manager such as running the full set of API tests against a newly converted -// copy as well as when it is opened from an existing namespace. -func testConvertWatchingOnly(tc *testContext) bool { - // These tests check the case where the manager was not initially - // created watch-only, but converted to watch only ... - - // Make a copy of the current database so the copy can be converted to - // watching only. - woMgrName := "mgrtestwo.bin" - _ = os.Remove(woMgrName) - fi, err := os.OpenFile(woMgrName, os.O_CREATE|os.O_RDWR, 0600) - if err != nil { - tc.t.Errorf("%v", err) - return false - } - if err := tc.db.Copy(fi); err != nil { - fi.Close() - tc.t.Errorf("%v", err) - return false - } - fi.Close() - defer os.Remove(woMgrName) - - // Open the new database copy and get the address manager namespace. - db, err := walletdb.Open("bdb", woMgrName, true, defaultDBTimeout) - if err != nil { - tc.t.Errorf("openDbNamespace: unexpected error: %v", err) - return false - } - defer db.Close() - - // Open the manager using the namespace and convert it to watching-only. - var mgr *Manager - err = walletdb.View(db, func(tx walletdb.ReadTx) error { - ns := tx.ReadBucket(waddrmgrNamespaceKey) - var err error - mgr, err = Open(ns, pubPassphrase, &chaincfg.MainNetParams) - return err - }) - if err != nil { - tc.t.Errorf("%v", err) - return false - } - err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { - ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - return mgr.ConvertToWatchingOnly(ns) - }) - if err != nil { - tc.t.Errorf("%v", err) - return false - } - - // Run all of the manager API tests against the converted manager and - // close it. We'll also retrieve the default scope (BIP0044) from the - // manager in order to use. - scopedMgr, err := mgr.FetchScopedKeyManager(KeyScopeBIP0044) - if err != nil { - tc.t.Errorf("unable to fetch bip 44 scope %v", err) - return false - } - testManagerAPI(&testContext{ - t: tc.t, - caseName: tc.caseName, - db: db, - rootManager: mgr, - manager: scopedMgr, - internalAccount: 0, - create: false, - watchingOnly: true, - }, false) - mgr.Close() - - // Open the watching-only manager and run all the tests again. - err = walletdb.View(db, func(tx walletdb.ReadTx) error { - ns := tx.ReadBucket(waddrmgrNamespaceKey) - var err error - mgr, err = Open(ns, pubPassphrase, &chaincfg.MainNetParams) - return err - }) - if err != nil { - tc.t.Errorf("Open Watching-Only: unexpected error: %v", err) - return false - } - defer mgr.Close() - - scopedMgr, err = mgr.FetchScopedKeyManager(KeyScopeBIP0044) - if err != nil { - tc.t.Errorf("unable to fetch bip 44 scope %v", err) - return false - } - - testManagerAPI(&testContext{ - t: tc.t, - caseName: tc.caseName, - db: db, - rootManager: mgr, - manager: scopedMgr, - internalAccount: 0, - create: false, - watchingOnly: true, - }, false) - - return true + // Rename account 1 "acct-create" + tc.internalAccount = 1 + testRenameAccount(tc) } // testSync tests various facets of setting the manager sync state. @@ -1914,57 +1661,43 @@ func testSync(tc *testContext) bool { // much of the testing involves having specific state. func _TestManager(t *testing.T) { tests := []struct { - name string - createdWatchingOnly bool - rootKey *hdkeychain.ExtendedKey - privPassphrase []byte + name string + rootKey *hdkeychain.ExtendedKey + privPassphrase []byte }{ { - name: "created with seed", - createdWatchingOnly: false, - rootKey: rootKey, - privPassphrase: privPassphrase, - }, - { - name: "created watch-only", - createdWatchingOnly: true, - rootKey: nil, - privPassphrase: nil, + name: "created with seed", + rootKey: rootKey, + privPassphrase: privPassphrase, }, } for _, test := range tests { // Need to wrap in a call so the defers work correctly. - testManagerCase( - t, test.name, test.createdWatchingOnly, - test.privPassphrase, test.rootKey, - ) + testManagerCase(t, test.name, test.privPassphrase, test.rootKey) } } func testManagerCase(t *testing.T, caseName string, - caseCreatedWatchingOnly bool, casePrivPassphrase []byte, - caseKey *hdkeychain.ExtendedKey) { + casePrivPassphrase []byte, caseKey *hdkeychain.ExtendedKey) { teardown, db := emptyDB(t) defer teardown() - if !caseCreatedWatchingOnly { - // Open manager that does not exist to ensure the expected error is - // returned. - err := walletdb.View(db, func(tx walletdb.ReadTx) error { - ns := tx.ReadBucket(waddrmgrNamespaceKey) - _, err := Open(ns, pubPassphrase, &chaincfg.MainNetParams) - return err - }) - if !checkManagerError(t, "Open non-existent", err, ErrNoExist) { - return - } + // Open manager that does not exist to ensure the expected error is + // returned. + err := walletdb.View(db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(waddrmgrNamespaceKey) + _, err := Open(ns, pubPassphrase, &chaincfg.MainNetParams) + return err + }) + if !checkManagerError(t, "Open non-existent", err, ErrNoExist) { + return } // Create a new manager. var mgr *Manager - err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns, err := tx.CreateTopLevelBucket(waddrmgrNamespaceKey) if err != nil { return err @@ -1981,10 +1714,6 @@ func testManagerCase(t *testing.T, caseName string, return err } - if caseCreatedWatchingOnly { - _, err = mgr.NewScopedKeyManager( - ns, KeyScopeBIP0044, ScopeAddrMap[KeyScopeBIP0044]) - } return err }) if err != nil { @@ -2015,32 +1744,6 @@ func testManagerCase(t *testing.T, caseName string, t.Fatalf("(%s) unable to fetch default scope: %v", caseName, err) } - if caseCreatedWatchingOnly { - accountKey := deriveTestAccountKey(t) - if accountKey == nil { - return - } - - acctKeyPub, err := accountKey.Neuter() - if err != nil { - t.Errorf("(%s) Neuter: unexpected error: %v", caseName, err) - return - } - - // Create the default account - err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { - ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - _, err = scopedMgr.NewAccountWatchingOnly( - ns, defaultAccountName, acctKeyPub, 0, nil, - ) - return err - }) - if err != nil { - t.Errorf("NewAccountWatchingOnly: unexpected error: %v", err) - return - } - } - // Run all of the manager API tests in create mode and close the // manager after they've completed testManagerAPI(&testContext{ @@ -2051,8 +1754,7 @@ func testManagerCase(t *testing.T, caseName string, rootManager: mgr, internalAccount: 0, create: true, - watchingOnly: caseCreatedWatchingOnly, - }, caseCreatedWatchingOnly) + }) mgr.Close() // Open the manager and run all the tests again in open mode which @@ -2081,29 +1783,20 @@ func testManagerCase(t *testing.T, caseName string, rootManager: mgr, internalAccount: 0, create: false, - watchingOnly: caseCreatedWatchingOnly, - } - testManagerAPI(tc, caseCreatedWatchingOnly) - - if !caseCreatedWatchingOnly { - // Now that the address manager has been tested in both the newly - // created and opened modes, test a watching-only version. - testConvertWatchingOnly(tc) } + testManagerAPI(tc) // Ensure that the manager sync state functionality works as expected. testSync(tc) - if !caseCreatedWatchingOnly { - // Unlock the manager so it can be closed with it unlocked to ensure - // it works without issue. - err = walletdb.View(db, func(tx walletdb.ReadTx) error { - ns := tx.ReadBucket(waddrmgrNamespaceKey) - return mgr.Unlock(ns, casePrivPassphrase) - }) - if err != nil { - t.Errorf("Unlock: unexpected error: %v", err) - } + // Unlock the manager so it can be closed with it unlocked to ensure + // it works without issue. + err = walletdb.View(db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(waddrmgrNamespaceKey) + return mgr.Unlock(ns, casePrivPassphrase) + }) + if err != nil { + t.Errorf("Unlock: unexpected error: %v", err) } } @@ -2683,142 +2376,6 @@ func TestNewRawAccount(t *testing.T) { testNewRawAccount(t, mgr, db, accountNum, scopedMgr) } -// TestNewRawAccountWatchingOnly tests that callers are able to -// properly create, and use watching-only raw accounts created with -// only an account number, and not a string which is eventually mapped -// to an account number. -func TestNewRawAccountWatchingOnly(t *testing.T) { - - teardown, db := emptyDB(t) - defer teardown() - - t.Parallel() - - // We'll start the test by creating a new root manager that will be - // used for the duration of the test. - var mgr *Manager - err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { - ns, err := tx.CreateTopLevelBucket(waddrmgrNamespaceKey) - if err != nil { - return err - } - err = Create( - ns, nil, pubPassphrase, nil, - &chaincfg.MainNetParams, fastScrypt, time.Time{}, - ) - if err != nil { - return err - } - mgr, err = Open(ns, pubPassphrase, &chaincfg.MainNetParams) - if err != nil { - return err - } - - _, err = mgr.NewScopedKeyManager( - ns, KeyScopeBIP0044, ScopeAddrMap[KeyScopeBIP0044]) - return err - }) - if err != nil { - t.Fatalf("create/open: unexpected error: %v", err) - } - defer mgr.Close() - - // Now that we have the manager created, we'll fetch one of the default - // scopes for usage within this test. - scopedMgr, err := mgr.FetchScopedKeyManager(KeyScopeBIP0044) - if err != nil { - t.Fatalf("unable to fetch scope %v: %v", KeyScopeBIP0044, err) - } - - accountKey := deriveTestAccountKey(t) - if accountKey == nil { - return - } - - // With the scoped manager retrieved, we'll attempt to create a new raw - // account by number. - const accountNum = 1000 - err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { - ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - return scopedMgr.NewRawAccountWatchingOnly( - ns, accountNum, accountKey, 0, nil, - ) - }) - if err != nil { - t.Fatalf("unable to create new account: %v", err) - } - - testNewRawAccount(t, mgr, db, accountNum, scopedMgr) -} - -// TestNewRawAccountHybrid is similar to TestNewRawAccountWatchingOnly -// except that the manager is created normally with a seed. This test -// shows that watch-only accounts can be added to managers with -// non-watch-only accounts. -func TestNewRawAccountHybrid(t *testing.T) { - - teardown, db := emptyDB(t) - defer teardown() - - t.Parallel() - - // We'll start the test by creating a new root manager that will be - // used for the duration of the test. - var mgr *Manager - err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { - ns, err := tx.CreateTopLevelBucket(waddrmgrNamespaceKey) - if err != nil { - return err - } - err = Create( - ns, rootKey, pubPassphrase, privPassphrase, - &chaincfg.MainNetParams, fastScrypt, time.Time{}, - ) - if err != nil { - return err - } - mgr, err = Open(ns, pubPassphrase, &chaincfg.MainNetParams) - return err - }) - if err != nil { - t.Fatalf("create/open: unexpected error: %v", err) - } - defer mgr.Close() - - // Now that we have the manager created, we'll fetch one of the default - // scopes for usage within this test. - scopedMgr, err := mgr.FetchScopedKeyManager(KeyScopeBIP0044) - if err != nil { - t.Fatalf("unable to fetch scope %v: %v", KeyScopeBIP0044, err) - } - - accountKey := deriveTestAccountKey(t) - if accountKey == nil { - return - } - - acctKeyPub, err := accountKey.Neuter() - if err != nil { - t.Errorf("Neuter: unexpected error: %v", err) - return - } - - // With the scoped manager retrieved, we'll attempt to create a new raw - // account by number. - const accountNum = 1000 - err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { - ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - return scopedMgr.NewRawAccountWatchingOnly( - ns, accountNum, acctKeyPub, 0, nil, - ) - }) - if err != nil { - t.Fatalf("unable to create new account: %v", err) - } - - testNewRawAccount(t, mgr, db, accountNum, scopedMgr) -} - func testNewRawAccount(t *testing.T, _ *Manager, db walletdb.DB, accountNum uint32, scopedMgr *ScopedKeyManager) { // With the account created, we should be able to derive new addresses diff --git a/waddrmgr/scoped_manager.go b/waddrmgr/scoped_manager.go index 7ec1e3f..2db3aee 100644 --- a/waddrmgr/scoped_manager.go +++ b/waddrmgr/scoped_manager.go @@ -393,10 +393,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket, return hdkeychain.NewKeyFromString(string(serializedKey)) } - // The wallet will only contain private keys for default accounts if the - // wallet's not set up as watch-only and it's been unlocked. - watchOnly := s.rootManager.watchOnly() - hasPrivateKey := !s.rootManager.isLocked() && !watchOnly + hasPrivateKey := !s.rootManager.isLocked() // Create the new account info with the known information. The rest of // the fields are filled out below. @@ -435,29 +432,6 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket, } } - case *dbWatchOnlyAccountRow: - acctInfo = &accountInfo{ - acctName: row.name, - acctType: row.acctType, - nextExternalIndex: row.nextExternalIndex, - nextInternalIndex: row.nextInternalIndex, - addrSchema: row.addrSchema, - masterKeyFingerprint: row.masterKeyFingerprint, - } - - // Use the crypto public key to decrypt the account public - // extended key. - acctInfo.acctKeyPub, err = decryptKey( - s.rootManager.cryptoKeyPub, row.pubKeyEncrypted, - ) - if err != nil { - str := fmt.Sprintf("failed to decrypt public key for "+ - "account %d", account) - return nil, managerError(ErrCrypto, str, err) - } - - hasPrivateKey = false - default: str := fmt.Sprintf("unsupported account type %T", row) return nil, managerError(ErrDatabase, str, nil) @@ -546,8 +520,6 @@ func (s *ScopedKeyManager) AccountProperties(ns walletdb.ReadBucket, props.InternalKeyCount = acctInfo.nextInternalIndex props.AccountPubKey = acctInfo.acctKeyPub props.MasterKeyFingerprint = acctInfo.masterKeyFingerprint - props.IsWatchOnly = s.rootManager.WatchOnly() || - acctInfo.acctKeyPriv == nil props.AddrSchema = acctInfo.addrSchema // Export the account public key with the correct version @@ -574,7 +546,6 @@ func (s *ScopedKeyManager) AccountProperties(ns walletdb.ReadBucket, } } else { props.AccountName = ImportedAddrAccountName // reserved, nonchangable - props.IsWatchOnly = s.rootManager.WatchOnly() // Could be more efficient if this was tracked by the db. var importedKeyCount uint32 @@ -642,8 +613,7 @@ func (s *ScopedKeyManager) DeriveFromKeyPathCache( ) } - watchOnly := s.rootManager.WatchOnly() - private := !s.rootManager.IsLocked() && !watchOnly + private := !s.rootManager.IsLocked() // Now that we have the account information, we can derive the key // directly. @@ -684,8 +654,7 @@ func (s *ScopedKeyManager) DeriveFromKeyPath(ns walletdb.ReadBucket, s.mtx.Lock() defer s.mtx.Unlock() - watchOnly := s.rootManager.WatchOnly() - private := !s.rootManager.IsLocked() && !watchOnly + private := !s.rootManager.IsLocked() addrKey, _, _, err := s.deriveKeyFromPath( ns, kp.InternalAccount, kp.Branch, kp.Index, private, @@ -739,7 +708,7 @@ func (s *ScopedKeyManager) chainAddressRowToManaged(ns walletdb.ReadBucket, // Since the manger's mutex is assumed to held when invoking this // function, we use the internal isLocked to avoid a deadlock. - private := !s.rootManager.isLocked() && !s.rootManager.watchOnly() + private := !s.rootManager.isLocked() addressKey, acctKey, masterKeyFingerprint, err := s.deriveKeyFromPath( ns, row.account, row.branch, row.index, private, @@ -981,8 +950,7 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket, // Choose the account key to used based on whether the address manager // is locked. acctKey := acctInfo.acctKeyPub - watchOnly := s.rootManager.WatchOnly() || len(acctInfo.acctKeyEncrypted) == 0 - if !s.rootManager.IsLocked() && !watchOnly { + if !s.rootManager.IsLocked() { acctKey = acctInfo.acctKeyPriv } @@ -1138,7 +1106,7 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket, // Add the new managed address to the list of addresses // that need their private keys derived when the // address manager is next unlocked. - if s.rootManager.isLocked() && !watchOnly { + if s.rootManager.isLocked() { s.deriveOnUnlock = append(s.deriveOnUnlock, info) } } @@ -1178,8 +1146,7 @@ func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket, // Choose the account key to used based on whether the address manager // is locked. acctKey := acctInfo.acctKeyPub - watchOnly := s.rootManager.WatchOnly() || acctInfo.acctKeyPriv != nil - if !s.rootManager.IsLocked() && !watchOnly { + if !s.rootManager.IsLocked() { acctKey = acctInfo.acctKeyPriv } @@ -1328,7 +1295,7 @@ func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket, // Add the new managed address to the list of addresses that // need their private keys derived when the address manager is // next unlocked. - if s.rootManager.IsLocked() && !watchOnly { + if s.rootManager.IsLocked() { s.deriveOnUnlock = append(s.deriveOnUnlock, info) } } @@ -1489,9 +1456,6 @@ func (s *ScopedKeyManager) LastInternalAddress(ns walletdb.ReadBucket, // number *directly*, rather than taking a string name for the account, then // mapping that to the next highest account number. func (s *ScopedKeyManager) NewRawAccount(ns walletdb.ReadWriteBucket, number uint32) error { - if s.rootManager.WatchOnly() { - return managerError(ErrWatchingOnly, errWatchingOnly, nil) - } s.mtx.Lock() defer s.mtx.Unlock() @@ -1507,45 +1471,12 @@ func (s *ScopedKeyManager) NewRawAccount(ns walletdb.ReadWriteBucket, number uin return s.newAccount(ns, number, name) } -// NewRawAccountWatchingOnly creates a new watching only account for the scoped -// manager. This method differs from the NewAccountWatchingOnly method in that -// this method takes the account number *directly*, rather than taking a string -// name for the account, then mapping that to the next highest account number. -// -// The master key fingerprint denotes the fingerprint of the root key -// corresponding to the account public key (also known as the key with -// derivation path m/). This may be required by some hardware wallets for proper -// identification and signing. -// -// An optional address schema may also be provided to override the -// ScopedKeyManager's address schema. This will affect all addresses derived -// from the account. -func (s *ScopedKeyManager) NewRawAccountWatchingOnly( - ns walletdb.ReadWriteBucket, number uint32, - pubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32, - addrSchema *ScopeAddrSchema) error { - - s.mtx.Lock() - defer s.mtx.Unlock() - - // As this is an ad hoc account that may not follow our normal linear - // derivation, we'll create a new name for this account based off of - // the account number. - name := fmt.Sprintf("act:%v", number) - return s.newAccountWatchingOnly( - ns, number, name, pubKey, masterKeyFingerprint, addrSchema, - ) -} - // NewAccount creates and returns a new account stored in the manager based on // the given account name. If an account with the same name already exists, // ErrDuplicateAccount will be returned. Since creating a new account requires // access to the cointype keys (from which extended account keys are derived), // it requires the manager to be unlocked. func (s *ScopedKeyManager) NewAccount(ns walletdb.ReadWriteBucket, name string) (uint32, error) { - if s.rootManager.WatchOnly() { - return 0, managerError(ErrWatchingOnly, errWatchingOnly, nil) - } s.mtx.Lock() defer s.mtx.Unlock() @@ -1654,95 +1585,6 @@ func (s *ScopedKeyManager) newAccount(ns walletdb.ReadWriteBucket, return putLastAccount(ns, &s.scope, account) } -// NewAccountWatchingOnly is similar to NewAccount, but for watch-only wallets. -// -// The master key fingerprint denotes the fingerprint of the root key -// corresponding to the account public key (also known as the key with -// derivation path m/). This may be required by some hardware wallets for proper -// identification and signing. -// -// An optional address schema may also be provided to override the -// ScopedKeyManager's address schema. This will affect all addresses derived -// from the account. -func (s *ScopedKeyManager) NewAccountWatchingOnly(ns walletdb.ReadWriteBucket, - name string, pubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32, - addrSchema *ScopeAddrSchema) (uint32, error) { - - s.mtx.Lock() - defer s.mtx.Unlock() - - // Fetch latest account, and create a new account in the same - // transaction Fetch the latest account number to generate the next - // account number - account, err := fetchLastAccount(ns, &s.scope) - if err != nil { - return 0, err - } - account++ - - // With the name validated, we'll create a new account for the new - // contiguous account. - err = s.newAccountWatchingOnly( - ns, account, name, pubKey, masterKeyFingerprint, addrSchema, - ) - if err != nil { - return 0, err - } - - return account, nil -} - -// newAccountWatchingOnly is similar to newAccount, but for watching-only wallets. -// -// The master key fingerprint denotes the fingerprint of the root key -// corresponding to the account public key (also known as the key with -// derivation path m/). This may be required by some hardware wallets for proper -// identification and signing. -// -// An optional address schema may also be provided to override the -// ScopedKeyManager's address schema. This will affect all addresses derived -// from the account. -// -// NOTE: This function MUST be called with the manager lock held for writes. -func (s *ScopedKeyManager) newAccountWatchingOnly(ns walletdb.ReadWriteBucket, - account uint32, name string, pubKey *hdkeychain.ExtendedKey, - masterKeyFingerprint uint32, addrSchema *ScopeAddrSchema) error { - - // Validate the account name. - if err := ValidateAccountName(name); err != nil { - return err - } - - // Check that account with the same name does not exist - _, err := s.lookupAccount(ns, name) - if err == nil { - str := fmt.Sprintf("account with the same name already exists") - return managerError(ErrDuplicateAccount, str, err) - } - - // Encrypt the default account keys with the associated crypto keys. - acctPubEnc, err := s.rootManager.cryptoKeyPub.Encrypt( - []byte(pubKey.String()), - ) - if err != nil { - str := "failed to encrypt public key for account" - return managerError(ErrCrypto, str, err) - } - - // We have the encrypted account extended keys, so save them to the - // database - err = putWatchOnlyAccountInfo( - ns, &s.scope, account, acctPubEnc, masterKeyFingerprint, 0, 0, - name, addrSchema, - ) - if err != nil { - return err - } - - // Save last account metadata - return putLastAccount(ns, &s.scope, account) -} - // RenameAccount renames an account stored in the manager based on the given // account number with the given name. If an account with the same name // already exists, ErrDuplicateAccount will be returned. @@ -1796,21 +1638,6 @@ func (s *ScopedKeyManager) RenameAccount(ns walletdb.ReadWriteBucket, return err } - case *dbWatchOnlyAccountRow: - // Remove the old name key from the account name index. - if err = deleteAccountNameIndex(ns, &s.scope, row.name); err != nil { - return err - } - - err = putWatchOnlyAccountInfo( - ns, &s.scope, account, row.pubKeyEncrypted, - row.masterKeyFingerprint, row.nextExternalIndex, - row.nextInternalIndex, name, row.addrSchema, - ) - if err != nil { - return err - } - default: str := fmt.Sprintf("unsupported account type %T", row) return managerError(ErrDatabase, str, nil) @@ -1834,14 +1661,8 @@ func (s *ScopedKeyManager) RenameAccount(ns walletdb.ReadWriteBucket, // All imported addresses will be part of the account defined by the // ImportedAddrAccount constant. // -// NOTE: When the address manager is watching-only, the private key itself will -// not be stored or available since it is private data. Instead, only the -// public key will be stored. This means it is paramount the private key is -// kept elsewhere as the watching-only address manager will NOT ever have access -// to it. -// -// This function will return an error if the address manager is locked and not -// watching-only, or not for the same network as the key trying to be imported. +// This function will return an error if the address manager is locked, or +// not for the same network as the key trying to be imported. // It will also return an error if the address already exists. Any other // errors returned are generally unexpected. func (s *ScopedKeyManager) ImportPrivateKey(ns walletdb.ReadWriteBucket, @@ -1860,25 +1681,23 @@ func (s *ScopedKeyManager) ImportPrivateKey(ns walletdb.ReadWriteBucket, defer s.mtx.Unlock() // The manager must be unlocked to encrypt the imported private key. - if s.rootManager.IsLocked() && !s.rootManager.WatchOnly() { + if s.rootManager.IsLocked() { return nil, managerError(ErrLocked, errLocked, nil) } - // Encrypt the private key when not a watching-only address manager. + // Encrypt the private key. var encryptedPrivKey []byte - if !s.rootManager.WatchOnly() { - privKeyBytes := wif.PrivKey.Serialize() - var err error - encryptedPrivKey, err = s.rootManager.cryptoKeyPriv.Encrypt(privKeyBytes) - zero.Bytes(privKeyBytes) - if err != nil { - str := fmt.Sprintf("failed to encrypt private key for %x", - wif.PrivKey.PubKey().SerializeCompressed()) - return nil, managerError(ErrCrypto, str, err) - } + privKeyBytes := wif.PrivKey.Serialize() + var err error + encryptedPrivKey, err = s.rootManager.cryptoKeyPriv.Encrypt(privKeyBytes) + zero.Bytes(privKeyBytes) + if err != nil { + str := fmt.Sprintf("failed to encrypt private key for %x", + wif.PrivKey.PubKey().SerializeCompressed()) + return nil, managerError(ErrCrypto, str, err) } - err := s.importPublicKey( + err = s.importPublicKey( ns, wif.SerializePubKey(), encryptedPrivKey, s.addrSchema.ExternalAddrType, bs, ) @@ -1887,11 +1706,7 @@ func (s *ScopedKeyManager) ImportPrivateKey(ns walletdb.ReadWriteBucket, } // Create a new managed address based on the imported address. - if !s.rootManager.WatchOnly() { - return s.toImportedPrivateManagedAddress(wif) - } - pubKey := (*btcec.PublicKey)(&wif.PrivKey.PublicKey) - return s.toImportedPublicManagedAddress(pubKey, wif.CompressPubKey) + return s.toImportedPrivateManagedAddress(wif) } // ImportPublicKey imports a public key into the address manager. @@ -2051,12 +1866,8 @@ func (s *ScopedKeyManager) toImportedPublicManagedAddress( // All imported script addresses will be part of the account defined by the // ImportedAddrAccount constant. // -// When the address manager is watching-only, the script itself will not be -// stored or available since it is considered private data. -// -// This function will return an error if the address manager is locked and not -// watching-only, or the address already exists. Any other errors returned are -// generally unexpected. +// This function will return an error if the address manager is locked, or the +// address already exists. Any other errors returned are generally unexpected. func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket, script []byte, bs *BlockStamp) (ManagedScriptAddress, error) { @@ -2069,12 +1880,8 @@ func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket, // All imported script addresses will be part of the account defined by the // ImportedAddrAccount constant. // -// When the address manager is watching-only, the script itself will not be -// stored or available since it is considered private data. -// -// This function will return an error if the address manager is locked and not -// watching-only, or the address already exists. Any other errors returned are -// generally unexpected. +// This function will return an error if the address manager is locked, or the +// address already exists. Any other errors returned are generally unexpected. func (s *ScopedKeyManager) ImportWitnessScript(ns walletdb.ReadWriteBucket, script []byte, bs *BlockStamp, witnessVersion byte, isSecretScript bool) (ManagedScriptAddress, error) { @@ -2099,13 +1906,6 @@ func (s *ScopedKeyManager) importScriptAddress(ns walletdb.ReadWriteBucket, return nil, managerError(ErrLocked, errLocked, nil) } - // A secret script can only be used with a non-watch only manager. If - // a wallet is watch-only then the script must be encrypted with the - // public encryption key. - if isSecretScript && s.rootManager.WatchOnly() { - return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil) - } - // Witness script addresses use a SHA256. var scriptHash []byte switch addrType { @@ -2124,8 +1924,8 @@ func (s *ScopedKeyManager) importScriptAddress(ns walletdb.ReadWriteBucket, return nil, managerError(ErrDuplicateAddress, str, nil) } - // Encrypt the script hash using the crypto public key so it is - // accessible when the address manager is locked or watching-only. + // Encrypt the script hash using the crypto public key so it not + // accessible when the address manager is locked. encryptedHash, err := s.rootManager.cryptoKeyPub.Encrypt(scriptHash) if err != nil { str := fmt.Sprintf("failed to encrypt script hash %x", @@ -2195,9 +1995,9 @@ func (s *ScopedKeyManager) importScriptAddress(ns walletdb.ReadWriteBucket, } // Create a new managed address based on the imported script. Also, - // when not a watching-only address manager, make a copy of the script - // since it will be cleared on lock and the script the caller passed - // should not be cleared out from under the caller. + // make a copy of the script since it will be cleared on lock and the + // script the caller passed // should not be cleared out from under the + // caller. var ( managedAddr ManagedScriptAddress baseScriptAddr *baseScriptAddress @@ -2390,22 +2190,6 @@ func (s *ScopedKeyManager) ForEachInternalActiveAddress(ns walletdb.ReadBucket, return nil } -// IsWatchOnlyAccount determines if the given account belonging to this scoped -// manager is set up as watch-only. -func (s *ScopedKeyManager) IsWatchOnlyAccount(ns walletdb.ReadBucket, - account uint32) (bool, error) { - - s.mtx.Lock() - defer s.mtx.Unlock() - - acctInfo, err := s.loadAccountInfo(ns, account) - if err != nil { - return false, err - } - - return acctInfo.acctKeyPriv == nil, nil -} - // cloneKeyWithVersion clones an extended key to use the version corresponding // to the manager's key scope. This should only be used for non-watch-only // accounts as they are stored within the database using the legacy BIP-0044 diff --git a/wallet/createtx.go b/wallet/createtx.go index 363c2fd..7c22ae2 100644 --- a/wallet/createtx.go +++ b/wallet/createtx.go @@ -197,40 +197,18 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut, keyScope *waddrmgr.KeyScope, return walletdb.ErrDryRunRollBack } - // Before committing the transaction, we'll sign our inputs. If - // the inputs are part of a watch-only account, there's no - // private key information stored, so we'll skip signing such. - var watchOnly bool - if keyScope == nil { - // If a key scope wasn't specified, then coin selection - // was performed from the default wallet accounts - // (NP2WKH, P2WKH), so any key scope provided doesn't - // impact the result of this call. - watchOnly, err = w.Manager.IsWatchOnlyAccount( - addrmgrNs, waddrmgr.KeyScopeBIP0084, account, - ) - } else { - watchOnly, err = w.Manager.IsWatchOnlyAccount( - addrmgrNs, *keyScope, account, - ) - } + err = tx.AddAllInputScripts( + secretSource{w.Manager, addrmgrNs}, + ) if err != nil { return err } - if !watchOnly { - err = tx.AddAllInputScripts( - secretSource{w.Manager, addrmgrNs}, - ) - if err != nil { - return err - } - err = validateMsgTx( - tx.Tx, tx.PrevScripts, tx.PrevInputValues, - ) - if err != nil { - return err - } + err = validateMsgTx( + tx.Tx, tx.PrevScripts, tx.PrevInputValues, + ) + if err != nil { + return err } if tx.ChangeIndex >= 0 && account == waddrmgr.ImportedAddrAccount { diff --git a/wallet/example_test.go b/wallet/example_test.go index b1e77ad..7400800 100644 --- a/wallet/example_test.go +++ b/wallet/example_test.go @@ -8,8 +8,6 @@ import ( "github.com/lbryio/lbcd/chaincfg" "github.com/lbryio/lbcutil/hdkeychain" - "github.com/lbryio/lbcwallet/waddrmgr" - "github.com/lbryio/lbcwallet/walletdb" ) // defaultDBTimeout specifies the timeout value when opening the wallet @@ -53,48 +51,3 @@ func testWallet(t *testing.T) (*Wallet, func()) { return w, cleanup } - -// testWalletWatchingOnly creates a test watch only wallet and unlocks it. -func testWalletWatchingOnly(t *testing.T) (*Wallet, func()) { - // Set up a wallet. - dir, err := ioutil.TempDir("", "test_wallet_watch_only") - if err != nil { - t.Fatalf("Failed to create db dir: %v", err) - } - - cleanup := func() { - if err := os.RemoveAll(dir); err != nil { - t.Fatalf("could not cleanup test: %v", err) - } - } - - pubPass := []byte("hello") - loader := NewLoader( - &chaincfg.TestNet3Params, dir, true, defaultDBTimeout, 250, - ) - w, err := loader.CreateNewWatchingOnlyWallet(pubPass, time.Now()) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } - chainClient := &mockChainClient{} - w.chainClient = chainClient - - err = walletdb.Update(w.Database(), func(tx walletdb.ReadWriteTx) error { - ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - for scope, schema := range waddrmgr.ScopeAddrMap { - _, err := w.Manager.NewScopedKeyManager( - ns, scope, schema, - ) - if err != nil { - return err - } - } - - return nil - }) - if err != nil { - t.Fatalf("unable to create default scopes: %v", err) - } - - return w, cleanup -} diff --git a/wallet/import.go b/wallet/import.go index e700640..7ba8991 100644 --- a/wallet/import.go +++ b/wallet/import.go @@ -268,12 +268,14 @@ func (w *Wallet) importAccountScope(ns walletdb.ReadWriteBucket, name string, } } - account, err := scopedMgr.NewAccountWatchingOnly( - ns, name, accountPubKey, masterKeyFingerprint, addrSchema, - ) - if err != nil { - return nil, err - } + // FIXME + // account, err := scopedMgr.NewAccountWatchingOnly( + // ns, name, accountPubKey, masterKeyFingerprint, addrSchema, + // ) + // if err != nil { + // return nil, err + // } + var account uint32 return scopedMgr.AccountProperties(ns, account) } diff --git a/wallet/import_test.go b/wallet/import_test.go index 28a4551..cbc17aa 100644 --- a/wallet/import_test.go +++ b/wallet/import_test.go @@ -137,23 +137,12 @@ func _TestImportAccount(t *testing.T) { w, cleanup := testWallet(t) defer cleanup() - testImportAccount(t, w, tc, false, tc.name) - }) - - name := tc.name + " watch-only" - t.Run(name, func(t *testing.T) { - t.Parallel() - - w, cleanup := testWalletWatchingOnly(t) - defer cleanup() - - testImportAccount(t, w, tc, true, name) + testImportAccount(t, w, tc, tc.name) }) } } -func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool, - name string) { +func testImportAccount(t *testing.T, w *Wallet, tc *testCase, name string) { // First derive the master public key of the account we want to import. root, err := hdkeychain.NewKeyFromString(tc.masterPriv) @@ -208,9 +197,6 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool, // If the wallet is watch only, there is no default account and our // imported account will be index 0. firstAccountIndex := uint32(1) - if watchOnly { - firstAccountIndex = 0 - } // We should have 3 additional accounts now. acctResult, err := w.Accounts(tc.expectedScope) @@ -220,7 +206,6 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool, // Validate the state of the accounts. require.Equal(t, firstAccountIndex, acct1.AccountNumber) require.Equal(t, name+"1", acct1.AccountName) - require.Equal(t, true, acct1.IsWatchOnly) require.Equal(t, root.ParentFingerprint(), acct1.MasterKeyFingerprint) require.NotNil(t, acct1.AccountPubKey) require.Equal(t, acct1Pub.String(), acct1.AccountPubKey.String()) @@ -230,7 +215,6 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool, require.Equal(t, firstAccountIndex+1, acct2.AccountNumber) require.Equal(t, name+"2", acct2.AccountName) - require.Equal(t, true, acct2.IsWatchOnly) require.Equal(t, root.ParentFingerprint(), acct2.MasterKeyFingerprint) require.NotNil(t, acct2.AccountPubKey) require.Equal(t, acct2Pub.String(), acct2.AccountPubKey.String()) @@ -255,7 +239,6 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool, // Make sure we can't get private keys for the imported accounts. _, err = w.DumpWIFPrivateKey(intAddr) - require.True(t, waddrmgr.IsError(err, waddrmgr.ErrWatchingOnly)) // Get the address info for the single key we imported. switch tc.addrType { diff --git a/wallet/loader.go b/wallet/loader.go index cd73ae9..693c7df 100644 --- a/wallet/loader.go +++ b/wallet/loader.go @@ -168,9 +168,7 @@ func (l *Loader) CreateNewWallet(pubPassphrase, privPassphrase, seed []byte, } } - return l.createNewWallet( - pubPassphrase, privPassphrase, rootKey, bday, false, - ) + return l.createNewWallet(pubPassphrase, privPassphrase, rootKey, bday) } // CreateNewWalletExtendedKey creates a new wallet from an extended master root @@ -180,25 +178,11 @@ func (l *Loader) CreateNewWallet(pubPassphrase, privPassphrase, seed []byte, func (l *Loader) CreateNewWalletExtendedKey(pubPassphrase, privPassphrase []byte, rootKey *hdkeychain.ExtendedKey, bday time.Time) (*Wallet, error) { - return l.createNewWallet( - pubPassphrase, privPassphrase, rootKey, bday, false, - ) -} - -// CreateNewWatchingOnlyWallet creates a new wallet using the provided -// public passphrase. No seed or private passphrase may be provided -// since the wallet is watching-only. -func (l *Loader) CreateNewWatchingOnlyWallet(pubPassphrase []byte, - bday time.Time) (*Wallet, error) { - - return l.createNewWallet( - pubPassphrase, nil, nil, bday, true, - ) + return l.createNewWallet(pubPassphrase, privPassphrase, rootKey, bday) } func (l *Loader) createNewWallet(pubPassphrase, privPassphrase []byte, - rootKey *hdkeychain.ExtendedKey, bday time.Time, - isWatchingOnly bool) (*Wallet, error) { + rootKey *hdkeychain.ExtendedKey, bday time.Time) (*Wallet, error) { defer l.mu.Unlock() l.mu.Lock() @@ -232,22 +216,12 @@ func (l *Loader) createNewWallet(pubPassphrase, privPassphrase []byte, } // Initialize the newly created database for the wallet before opening. - if isWatchingOnly { - err := CreateWatchingOnlyWithCallback( - l.db, pubPassphrase, l.chainParams, bday, - l.walletCreated, - ) - if err != nil { - return nil, err - } - } else { - err := CreateWithCallback( - l.db, pubPassphrase, privPassphrase, rootKey, - l.chainParams, bday, l.walletCreated, - ) - if err != nil { - return nil, err - } + err = CreateWithCallback( + l.db, pubPassphrase, privPassphrase, rootKey, + l.chainParams, bday, l.walletCreated, + ) + if err != nil { + return nil, err } // Open the newly-created wallet. diff --git a/wallet/psbt.go b/wallet/psbt.go index 4d12813..2ce4da5 100644 --- a/wallet/psbt.go +++ b/wallet/psbt.go @@ -330,37 +330,6 @@ func (w *Wallet) FinalizePsbt(keyScope *waddrmgr.KeyScope, account uint32, } } - // Finally, if the input doesn't belong to a watch-only account, - // then we'll sign it as is, and populate the input with the - // witness and sigScript (if needed). - watchOnly := false - err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { - ns := tx.ReadBucket(waddrmgrNamespaceKey) - var err error - if keyScope == nil { - // If a key scope wasn't specified, then coin - // selection was performed from the default - // wallet accounts (NP2WKH, P2WKH), so any key - // scope provided doesn't impact the result of - // this call. - watchOnly, err = w.Manager.IsWatchOnlyAccount( - ns, waddrmgr.KeyScopeBIP0084, account, - ) - } else { - watchOnly, err = w.Manager.IsWatchOnlyAccount( - ns, *keyScope, account, - ) - } - return err - }) - if err != nil { - return fmt.Errorf("unable to determine if account is "+ - "watch-only: %v", err) - } - if watchOnly { - continue - } - witness, sigScript, err := w.ComputeInputScript( tx, signOutput, idx, sigHashes, in.SighashType, nil, ) diff --git a/wallet/wallet.go b/wallet/wallet.go index 51b738e..732a61e 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1177,16 +1177,14 @@ out: // private key material, we need to prevent it from // doing so while we are assembling the transaction. release := func() {} - if !w.Manager.WatchOnly() { - heldUnlock, err := w.holdUnlock() - if err != nil { - txr.resp <- createTxResponse{nil, err} - continue - } - - release = heldUnlock.release + heldUnlock, err := w.holdUnlock() + if err != nil { + txr.resp <- createTxResponse{nil, err} + continue } + release = heldUnlock.release + tx, err := w.txToOutputs( txr.outputs, txr.keyScope, txr.account, txr.minconf, txr.feeSatPerKB, @@ -2640,10 +2638,11 @@ func (w *Wallet) ListUnspent(minconf, maxconf int32, // // TODO: Each case will need updates when watch-only addrs // is added. For P2PK, P2PKH, and P2SH, the address must be - // looked up and not be watching-only. For multisig, all - // pubkeys must belong to the manager with the associated - // private key (currently it only checks whether the pubkey - // exists, since the private key is required at the moment). + // looked up. + // For multisig, all pubkeys must belong to the manager with + // the associated private key (currently it only checks whether + // the pubkey exists, since the private key is required at the + // moment). var spendable bool scSwitch: switch sc { @@ -3199,13 +3198,6 @@ func (w *Wallet) SendOutputs(outputs []*wire.TxOut, keyScope *waddrmgr.KeyScope, return nil, err } - // If our wallet is read-only, we'll get a transaction with coins - // selected but no witness data. In such a case we need to inform our - // caller that they'll actually need to go ahead and sign the TX. - if w.Manager.WatchOnly() { - return createdTx.Tx, ErrTxUnsigned - } - txHash, err := w.reliablyPublishTransaction(createdTx.Tx, label) if err != nil { return nil, err @@ -3695,19 +3687,7 @@ func CreateWithCallback(db walletdb.DB, pubPass, privPass []byte, birthday time.Time, cb func(walletdb.ReadWriteTx) error) error { return create( - db, pubPass, privPass, rootKey, params, birthday, false, cb, - ) -} - -// CreateWatchingOnlyWithCallback is the same as CreateWatchingOnly with an -// added callback that will be called in the same transaction the wallet -// structure is initialized. -func CreateWatchingOnlyWithCallback(db walletdb.DB, pubPass []byte, - params *chaincfg.Params, birthday time.Time, - cb func(walletdb.ReadWriteTx) error) error { - - return create( - db, pubPass, nil, nil, params, birthday, true, cb, + db, pubPass, privPass, rootKey, params, birthday, cb, ) } @@ -3719,31 +3699,16 @@ func Create(db walletdb.DB, pubPass, privPass []byte, birthday time.Time) error { return create( - db, pubPass, privPass, rootKey, params, birthday, false, nil, + db, pubPass, privPass, rootKey, params, birthday, nil, ) } - -// CreateWatchingOnly creates an new watch-only wallet, writing it to -// an empty database. No root key can be provided as this wallet will be -// watching only. Likewise no private passphrase may be provided -// either. -func CreateWatchingOnly(db walletdb.DB, pubPass []byte, - params *chaincfg.Params, birthday time.Time) error { - - return create( - db, pubPass, nil, nil, params, birthday, true, nil, - ) -} - func create(db walletdb.DB, pubPass, privPass []byte, rootKey *hdkeychain.ExtendedKey, params *chaincfg.Params, - birthday time.Time, isWatchingOnly bool, + birthday time.Time, cb func(walletdb.ReadWriteTx) error) error { // If no root key was provided, we create one now from a random seed. - // But only if this is not a watching-only wallet where the accounts are - // created individually from their xpubs. - if !isWatchingOnly && rootKey == nil { + if rootKey == nil { hdSeed, err := hdkeychain.GenerateSeed( hdkeychain.RecommendedSeedLen, ) @@ -3760,7 +3725,7 @@ func create(db walletdb.DB, pubPass, privPass []byte, } // We need a private key if this isn't a watching only wallet. - if !isWatchingOnly && rootKey != nil && !rootKey.IsPrivate() { + if rootKey != nil && !rootKey.IsPrivate() { return fmt.Errorf("need extended private key for wallet that " + "is not watching only") } diff --git a/wallet/watchingonly_test.go b/wallet/watchingonly_test.go deleted file mode 100644 index 9c07a75..0000000 --- a/wallet/watchingonly_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2018 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package wallet - -import ( - "io/ioutil" - "os" - "testing" - "time" - - "github.com/lbryio/lbcd/chaincfg" - _ "github.com/lbryio/lbcwallet/walletdb/bdb" -) - -// TestCreateWatchingOnly checks that we can construct a watching-only -// wallet. -func TestCreateWatchingOnly(t *testing.T) { - // Set up a wallet. - dir, err := ioutil.TempDir("", "watchingonly_test") - if err != nil { - t.Fatalf("Failed to create db dir: %v", err) - } - defer os.RemoveAll(dir) - - pubPass := []byte("hello") - - loader := NewLoader( - &chaincfg.TestNet3Params, dir, true, defaultDBTimeout, 250, - ) - _, err = loader.CreateNewWatchingOnlyWallet(pubPass, time.Now()) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } -}