mirror of
https://github.com/LBRYFoundation/lbcwallet.git
synced 2025-08-23 17:47:29 +00:00
Add function+tests for exporting a watching wallet.
This change introduces a new function to export a wallet in memory to a watching wallet. Watching wallets allow to watch for balance changes and transactions to wallet addresses while only storing the public parts of a wallet (no private keys). New addresses created by the watching wallet will use pubkey address chaining and will allow to receive funds to an indefinite number of new addresses, and create the private keys for said addresses from the non-watching wallet later. The actual exporting of a watching wallet to a file (triggered by an RPC request) is not yet implemented. While here, fix an issue found by new test code for the chained address code which incorrectly set the starting index of addresses in the chain needing private keys to be created.
This commit is contained in:
parent
8952fc5acf
commit
effd810e54
3 changed files with 310 additions and 38 deletions
|
@ -424,6 +424,7 @@ func (a *Account) ImportWIFPrivateKey(wif string, bs *wallet.BlockStamp) (string
|
||||||
a.mtx.Unlock()
|
a.mtx.Unlock()
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
addrStr := addr.String()
|
||||||
|
|
||||||
// Immediately write dirty wallet to disk.
|
// Immediately write dirty wallet to disk.
|
||||||
//
|
//
|
||||||
|
@ -438,12 +439,12 @@ func (a *Account) ImportWIFPrivateKey(wif string, bs *wallet.BlockStamp) (string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Associate the imported address with this account.
|
// Associate the imported address with this account.
|
||||||
MarkAddressForAccount(addr, a.Name())
|
MarkAddressForAccount(addrStr, a.Name())
|
||||||
|
|
||||||
log.Infof("Imported payment address %v", addr)
|
log.Infof("Imported payment address %v", addrStr)
|
||||||
|
|
||||||
// Return the payment address string of the imported private key.
|
// Return the payment address string of the imported private key.
|
||||||
return addr, nil
|
return addrStr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track requests btcd to send notifications of new transactions for
|
// Track requests btcd to send notifications of new transactions for
|
||||||
|
|
168
wallet/wallet.go
168
wallet/wallet.go
|
@ -59,6 +59,7 @@ var (
|
||||||
ErrMalformedEntry = errors.New("malformed entry")
|
ErrMalformedEntry = errors.New("malformed entry")
|
||||||
ErrNetworkMismatch = errors.New("network mismatch")
|
ErrNetworkMismatch = errors.New("network mismatch")
|
||||||
ErrWalletDoesNotExist = errors.New("non-existant wallet")
|
ErrWalletDoesNotExist = errors.New("non-existant wallet")
|
||||||
|
ErrWalletIsWatchingOnly = errors.New("wallet is watching-only")
|
||||||
ErrWalletLocked = errors.New("wallet is locked")
|
ErrWalletLocked = errors.New("wallet is locked")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -681,7 +682,10 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
// If the private keys have not ben created yet, mark the
|
// If the private keys have not ben created yet, mark the
|
||||||
// earliest so all can be created on next wallet unlock.
|
// earliest so all can be created on next wallet unlock.
|
||||||
if e.addr.flags.createPrivKeyNextUnlock {
|
if e.addr.flags.createPrivKeyNextUnlock {
|
||||||
if w.missingKeysStart < e.addr.chainIndex {
|
switch {
|
||||||
|
case w.missingKeysStart == 0:
|
||||||
|
fallthrough
|
||||||
|
case e.addr.chainIndex < w.missingKeysStart:
|
||||||
w.missingKeysStart = e.addr.chainIndex
|
w.missingKeysStart = e.addr.chainIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -781,6 +785,10 @@ func (w *Wallet) WriteTo(wtr io.Writer) (n int64, err error) {
|
||||||
// addresses created while the wallet was locked without private
|
// addresses created while the wallet was locked without private
|
||||||
// keys are created at this time.
|
// keys are created at this time.
|
||||||
func (w *Wallet) Unlock(passphrase []byte) error {
|
func (w *Wallet) Unlock(passphrase []byte) error {
|
||||||
|
if w.flags.watchingOnly {
|
||||||
|
return ErrWalletIsWatchingOnly
|
||||||
|
}
|
||||||
|
|
||||||
// Derive key from KDF parameters and passphrase.
|
// Derive key from KDF parameters and passphrase.
|
||||||
key := Key(passphrase, &w.kdfParams)
|
key := Key(passphrase, &w.kdfParams)
|
||||||
|
|
||||||
|
@ -798,6 +806,10 @@ func (w *Wallet) Unlock(passphrase []byte) error {
|
||||||
// Lock performs a best try effort to remove and zero all secret keys
|
// Lock performs a best try effort to remove and zero all secret keys
|
||||||
// associated with the wallet.
|
// associated with the wallet.
|
||||||
func (w *Wallet) Lock() (err error) {
|
func (w *Wallet) Lock() (err error) {
|
||||||
|
if w.flags.watchingOnly {
|
||||||
|
return ErrWalletIsWatchingOnly
|
||||||
|
}
|
||||||
|
|
||||||
// Remove clear text passphrase from wallet.
|
// Remove clear text passphrase from wallet.
|
||||||
if len(w.secret) != 32 {
|
if len(w.secret) != 32 {
|
||||||
err = ErrWalletLocked
|
err = ErrWalletLocked
|
||||||
|
@ -872,7 +884,7 @@ func (w *Wallet) NextChainedAddress(bs *BlockStamp,
|
||||||
// LastChainedAddress returns the most recently requested chained
|
// LastChainedAddress returns the most recently requested chained
|
||||||
// address from calling NextChainedAddress, or the root address if
|
// address from calling NextChainedAddress, or the root address if
|
||||||
// no chained addresses have been requested.
|
// no chained addresses have been requested.
|
||||||
func (w *Wallet) LastChainedAddress() btcutil.Address {
|
func (w *Wallet) LastChainedAddress() *btcutil.AddressPubKeyHash {
|
||||||
return w.chainIdxMap[w.highestUsed]
|
return w.chainIdxMap[w.highestUsed]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1018,6 +1030,11 @@ func (w *Wallet) createMissingPrivateKeys() error {
|
||||||
// contained in the wallet, the address does not include a public and
|
// contained in the wallet, the address does not include a public and
|
||||||
// private key, or if the wallet is locked.
|
// private key, or if the wallet is locked.
|
||||||
func (w *Wallet) AddressKey(a btcutil.Address) (key *ecdsa.PrivateKey, err error) {
|
func (w *Wallet) AddressKey(a btcutil.Address) (key *ecdsa.PrivateKey, err error) {
|
||||||
|
// Watching-only wallets do not contain private keys.
|
||||||
|
if w.flags.watchingOnly {
|
||||||
|
return nil, ErrWalletIsWatchingOnly
|
||||||
|
}
|
||||||
|
|
||||||
// Currently, only P2PKH addresses are supported. This should
|
// Currently, only P2PKH addresses are supported. This should
|
||||||
// be extended to a switch-case statement when support for other
|
// be extended to a switch-case statement when support for other
|
||||||
// addresses are added.
|
// addresses are added.
|
||||||
|
@ -1187,36 +1204,39 @@ func (w *Wallet) SetBetterEarliestBlockHeight(height int32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportPrivateKey creates a new encrypted btcAddress with a
|
// ImportPrivateKey creates a new encrypted btcAddress with a
|
||||||
// user-provided private key and adds it to the wallet. If the
|
// user-provided private key and adds it to the wallet.
|
||||||
// import is successful, the payment address string is returned.
|
func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, bs *BlockStamp) (*btcutil.AddressPubKeyHash, error) {
|
||||||
func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, bs *BlockStamp) (string, error) {
|
if w.flags.watchingOnly {
|
||||||
|
return nil, ErrWalletIsWatchingOnly
|
||||||
|
}
|
||||||
|
|
||||||
// First, must check that the key being imported will not result
|
// First, must check that the key being imported will not result
|
||||||
// in a duplicate address.
|
// in a duplicate address.
|
||||||
pkh := btcutil.Hash160(pubkeyFromPrivkey(privkey, compressed))
|
pkh := btcutil.Hash160(pubkeyFromPrivkey(privkey, compressed))
|
||||||
// Will always be valid inputs so omit error check.
|
// Will always be valid inputs so omit error check.
|
||||||
apkh, err := btcutil.NewAddressPubKeyHash(pkh, w.Net())
|
apkh, err := btcutil.NewAddressPubKeyHash(pkh, w.Net())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
if _, ok := w.addrMap[*apkh]; ok {
|
if _, ok := w.addrMap[*apkh]; ok {
|
||||||
return "", ErrDuplicate
|
return nil, ErrDuplicate
|
||||||
}
|
}
|
||||||
|
|
||||||
// The wallet must be unlocked to encrypt the imported private key.
|
// The wallet must be unlocked to encrypt the imported private key.
|
||||||
if len(w.secret) != 32 {
|
if len(w.secret) != 32 {
|
||||||
return "", ErrWalletLocked
|
return nil, ErrWalletLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new address with this private key.
|
// Create new address with this private key.
|
||||||
btcaddr, err := newBtcAddress(privkey, nil, bs, compressed)
|
btcaddr, err := newBtcAddress(privkey, nil, bs, compressed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
btcaddr.chainIndex = importedKeyChainIdx
|
btcaddr.chainIndex = importedKeyChainIdx
|
||||||
|
|
||||||
// Encrypt imported address with the derived AES key.
|
// Encrypt imported address with the derived AES key.
|
||||||
if err = btcaddr.encrypt(w.secret); err != nil {
|
if err = btcaddr.encrypt(w.secret); err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add address to wallet's bookkeeping structures. Adding to
|
// Add address to wallet's bookkeeping structures. Adding to
|
||||||
|
@ -1225,11 +1245,8 @@ func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, bs *BlockStam
|
||||||
w.addrMap[*btcaddr.address(w.net)] = btcaddr
|
w.addrMap[*btcaddr.address(w.net)] = btcaddr
|
||||||
w.importedAddrs = append(w.importedAddrs, btcaddr)
|
w.importedAddrs = append(w.importedAddrs, btcaddr)
|
||||||
|
|
||||||
// Create and return encoded payment address string. Error is
|
// Create and return address.
|
||||||
// ignored as the length of the pubkey hash and net will always
|
return btcutil.NewAddressPubKeyHash(btcaddr.pubKeyHash[:], w.Net())
|
||||||
// be valid.
|
|
||||||
addr, _ := btcutil.NewAddressPubKeyHash(btcaddr.pubKeyHash[:], w.Net())
|
|
||||||
return addr.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDate returns the Unix time of the wallet creation time. This
|
// CreateDate returns the Unix time of the wallet creation time. This
|
||||||
|
@ -1239,6 +1256,71 @@ func (w *Wallet) CreateDate() int64 {
|
||||||
return w.createDate
|
return w.createDate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExportWatchingWallet creates and returns a new wallet with the same
|
||||||
|
// addresses in w, but as a watching-only wallet without any private keys.
|
||||||
|
// New addresses created by the watching wallet will match the new addresses
|
||||||
|
// created the original wallet (thanks to public key address chaining), but
|
||||||
|
// will be missing the associated private keys.
|
||||||
|
func (w *Wallet) ExportWatchingWallet() (*Wallet, error) {
|
||||||
|
// Don't continue if wallet is already a watching-only wallet.
|
||||||
|
if w.flags.watchingOnly {
|
||||||
|
return nil, ErrWalletIsWatchingOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy members of w into a new wallet, but mark as watching-only and
|
||||||
|
// do not include any private keys.
|
||||||
|
ww := &Wallet{
|
||||||
|
net: w.net,
|
||||||
|
flags: walletFlags{
|
||||||
|
useEncryption: false,
|
||||||
|
watchingOnly: true,
|
||||||
|
},
|
||||||
|
uniqID: w.uniqID,
|
||||||
|
name: w.name,
|
||||||
|
desc: w.desc,
|
||||||
|
createDate: w.createDate,
|
||||||
|
highestUsed: w.highestUsed,
|
||||||
|
keyGenerator: *w.keyGenerator.watchingCopy(),
|
||||||
|
recent: recentBlocks{
|
||||||
|
lastHeight: w.recent.lastHeight,
|
||||||
|
},
|
||||||
|
|
||||||
|
addrMap: make(map[btcutil.AddressPubKeyHash]*btcAddress),
|
||||||
|
addrCommentMap: make(map[btcutil.AddressPubKeyHash]comment),
|
||||||
|
txCommentMap: make(map[transactionHashKey]comment),
|
||||||
|
|
||||||
|
chainIdxMap: make(map[int64]*btcutil.AddressPubKeyHash),
|
||||||
|
lastChainIdx: w.lastChainIdx,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.recent.hashes) != 0 {
|
||||||
|
ww.recent.hashes = make([]*btcwire.ShaHash, 0, len(w.recent.hashes))
|
||||||
|
for _, hash := range w.recent.hashes {
|
||||||
|
var hashCpy btcwire.ShaHash
|
||||||
|
copy(hashCpy[:], hash[:])
|
||||||
|
ww.recent.hashes = append(ww.recent.hashes, &hashCpy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for apkh, addr := range w.addrMap {
|
||||||
|
apkhCopy := apkh
|
||||||
|
ww.chainIdxMap[addr.chainIndex] = &apkhCopy
|
||||||
|
ww.addrMap[apkhCopy] = addr.watchingCopy()
|
||||||
|
}
|
||||||
|
for apkh, cmt := range w.addrCommentMap {
|
||||||
|
cmtCopy := make(comment, len(cmt))
|
||||||
|
copy(cmtCopy, cmt)
|
||||||
|
ww.addrCommentMap[apkh] = cmtCopy
|
||||||
|
}
|
||||||
|
if len(w.importedAddrs) != 0 {
|
||||||
|
ww.importedAddrs = make([]*btcAddress, 0, len(w.importedAddrs))
|
||||||
|
for _, addr := range w.importedAddrs {
|
||||||
|
ww.importedAddrs = append(ww.importedAddrs, addr.watchingCopy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ww, nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddressInfo holds information regarding an address needed to manage
|
// AddressInfo holds information regarding an address needed to manage
|
||||||
// a complete wallet.
|
// a complete wallet.
|
||||||
type AddressInfo struct {
|
type AddressInfo struct {
|
||||||
|
@ -1299,30 +1381,36 @@ type walletFlags struct {
|
||||||
watchingOnly bool
|
watchingOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wf *walletFlags) ReadFrom(r io.Reader) (n int64, err error) {
|
func (wf *walletFlags) ReadFrom(r io.Reader) (int64, error) {
|
||||||
raw := make([]byte, 8)
|
var b [8]byte
|
||||||
n, err = binaryRead(r, binary.LittleEndian, raw)
|
n, err := r.Read(b[:])
|
||||||
wf.useEncryption = raw[0] != 0
|
if err != nil {
|
||||||
wf.watchingOnly = raw[1] != 0
|
return int64(n), err
|
||||||
return n, err
|
}
|
||||||
|
|
||||||
|
wf.useEncryption = b[0]&(1<<0) != 0
|
||||||
|
wf.watchingOnly = b[0]&(1<<1) != 0
|
||||||
|
|
||||||
|
return int64(n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wf *walletFlags) WriteTo(w io.Writer) (n int64, err error) {
|
func (wf *walletFlags) WriteTo(w io.Writer) (int64, error) {
|
||||||
raw := make([]byte, 8)
|
var b [8]byte
|
||||||
if wf.useEncryption {
|
if wf.useEncryption {
|
||||||
raw[0] = 1
|
b[0] |= 1 << 0
|
||||||
}
|
}
|
||||||
if wf.watchingOnly {
|
if wf.watchingOnly {
|
||||||
raw[1] = 1
|
b[0] |= 1 << 1
|
||||||
}
|
}
|
||||||
return binaryWrite(w, binary.LittleEndian, raw)
|
n, err := w.Write(b[:])
|
||||||
|
return int64(n), err
|
||||||
}
|
}
|
||||||
|
|
||||||
type addrFlags struct {
|
type addrFlags struct {
|
||||||
hasPrivKey bool
|
hasPrivKey bool
|
||||||
hasPubKey bool
|
hasPubKey bool
|
||||||
encrypted bool
|
encrypted bool
|
||||||
createPrivKeyNextUnlock bool // unimplemented in btcwallet
|
createPrivKeyNextUnlock bool
|
||||||
compressed bool
|
compressed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1640,7 +1728,7 @@ type btcAddress struct {
|
||||||
flags addrFlags
|
flags addrFlags
|
||||||
chaincode [32]byte
|
chaincode [32]byte
|
||||||
chainIndex int64
|
chainIndex int64
|
||||||
chainDepth int64 // currently unused (will use when extending a locked wallet)
|
chainDepth int64 // unused
|
||||||
initVector [16]byte
|
initVector [16]byte
|
||||||
privKey [32]byte
|
privKey [32]byte
|
||||||
pubKey publicKey
|
pubKey publicKey
|
||||||
|
@ -2053,6 +2141,30 @@ func (a *btcAddress) info(net btcwire.BitcoinNet) (*AddressInfo, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// watchingCopy creates a copy of an address without a private key.
|
||||||
|
// This is used to fill a watching a wallet with addresses from a
|
||||||
|
// normal wallet.
|
||||||
|
func (a *btcAddress) watchingCopy() *btcAddress {
|
||||||
|
return &btcAddress{
|
||||||
|
pubKeyHash: a.pubKeyHash,
|
||||||
|
flags: addrFlags{
|
||||||
|
hasPrivKey: false,
|
||||||
|
hasPubKey: a.flags.hasPubKey,
|
||||||
|
encrypted: false,
|
||||||
|
createPrivKeyNextUnlock: false,
|
||||||
|
compressed: a.flags.compressed,
|
||||||
|
},
|
||||||
|
chaincode: a.chaincode,
|
||||||
|
chainIndex: a.chainIndex,
|
||||||
|
chainDepth: a.chainDepth,
|
||||||
|
pubKey: a.pubKey,
|
||||||
|
firstSeen: a.firstSeen,
|
||||||
|
lastSeen: a.lastSeen,
|
||||||
|
firstBlock: a.firstBlock,
|
||||||
|
lastBlock: a.lastBlock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func walletHash(b []byte) uint32 {
|
func walletHash(b []byte) uint32 {
|
||||||
sum := btcwire.DoubleSha256(b)
|
sum := btcwire.DoubleSha256(b)
|
||||||
return binary.LittleEndian.Uint32(sum)
|
return binary.LittleEndian.Uint32(sum)
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/conformal/btcec"
|
"github.com/conformal/btcec"
|
||||||
|
"github.com/conformal/btcutil"
|
||||||
"github.com/conformal/btcwire"
|
"github.com/conformal/btcwire"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -462,3 +463,161 @@ func TestWalletPubkeyChaining(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWatchingWalletExport(t *testing.T) {
|
||||||
|
const keypoolSize = 10
|
||||||
|
createdAt := &BlockStamp{}
|
||||||
|
w, err := NewWallet("banana wallet", "A wallet for testing.",
|
||||||
|
[]byte("banana"), btcwire.MainNet, createdAt, keypoolSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error creating new wallet: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maintain a set of the active addresses in the wallet.
|
||||||
|
activeAddrs := make(map[btcutil.AddressPubKeyHash]struct{})
|
||||||
|
|
||||||
|
// Add root address.
|
||||||
|
activeAddrs[*w.LastChainedAddress()] = struct{}{}
|
||||||
|
|
||||||
|
// Get as many new active addresses as necessary to deplete the keypool.
|
||||||
|
// This is done as we will want to test that new addresses created by
|
||||||
|
// the watching wallet do not pull from previous public keys in the
|
||||||
|
// original keypool.
|
||||||
|
for i := 0; i < keypoolSize; i++ {
|
||||||
|
apkh, err := w.NextChainedAddress(createdAt, keypoolSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to get next address: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
activeAddrs[*apkh] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create watching wallet from w.
|
||||||
|
ww, err := w.ExportWatchingWallet()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Could not create watching wallet: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify correctness of wallet flags.
|
||||||
|
if ww.flags.useEncryption {
|
||||||
|
t.Errorf("Watching wallet marked as using encryption (but nothing to encrypt).")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ww.flags.watchingOnly {
|
||||||
|
t.Errorf("Wallet should be watching-only but is not marked so.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that all flags are set as expected.
|
||||||
|
if ww.keyGenerator.flags.encrypted {
|
||||||
|
t.Errorf("Watching root address should not be encrypted (nothing to encrypt)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ww.keyGenerator.flags.hasPrivKey {
|
||||||
|
t.Errorf("Watching root address marked as having a private key.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ww.keyGenerator.flags.hasPubKey {
|
||||||
|
t.Errorf("Watching root address marked as missing a public key.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ww.keyGenerator.flags.createPrivKeyNextUnlock {
|
||||||
|
t.Errorf("Watching root address marked as needing a private key to be generated later.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for apkh, addr := range ww.addrMap {
|
||||||
|
if addr.flags.encrypted {
|
||||||
|
t.Errorf("Chained address should not be encrypted (nothing to encrypt)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ww.keyGenerator.flags.hasPrivKey {
|
||||||
|
t.Errorf("Chained address marked as having a private key.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ww.keyGenerator.flags.hasPubKey {
|
||||||
|
t.Errorf("Chained address marked as missing a public key.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ww.keyGenerator.flags.createPrivKeyNextUnlock {
|
||||||
|
t.Errorf("Chained address marked as needing a private key to be generated later.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := activeAddrs[apkh]; !ok {
|
||||||
|
t.Errorf("Address from watching wallet not found in original wallet.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(activeAddrs, apkh)
|
||||||
|
}
|
||||||
|
if len(activeAddrs) != 0 {
|
||||||
|
t.Errorf("%v address(es) were not exported to watching wallet.", len(activeAddrs))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the new addresses created by each wallet match. The
|
||||||
|
// original wallet is unlocked so the keypool is refilled and chained
|
||||||
|
// addresses use the previous' privkey, not pubkey.
|
||||||
|
if err := w.Unlock([]byte("banana")); err != nil {
|
||||||
|
t.Errorf("Unlocking original wallet failed: %v", err)
|
||||||
|
}
|
||||||
|
for i := 0; i < keypoolSize; i++ {
|
||||||
|
addr, err := w.NextChainedAddress(createdAt, keypoolSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot get next chained address for original wallet: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
waddr, err := ww.NextChainedAddress(createdAt, keypoolSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot get next chained address for watching wallet: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if addr.String() != waddr.String() {
|
||||||
|
t.Errorf("Next addresses for each wallet do not match eachother.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test (de)serialization of watching wallet.
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
_, err = ww.WriteTo(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot write watching wallet: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ww2 := new(Wallet)
|
||||||
|
_, err = ww2.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot read watching wallet: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that (de)serialized watching wallet matches the exported wallet.
|
||||||
|
if !reflect.DeepEqual(ww, ww2) {
|
||||||
|
t.Error("Exported and read-in watching wallets do not match.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that nonsensical functions fail with correct error.
|
||||||
|
if err := ww.Lock(); err != ErrWalletIsWatchingOnly {
|
||||||
|
t.Errorf("Nonsensical func Lock returned no or incorrect error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ww.Unlock([]byte("banana")); err != ErrWalletIsWatchingOnly {
|
||||||
|
t.Errorf("Nonsensical func Unlock returned no or incorrect error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := ww.AddressKey(w.keyGenerator.address(ww.net)); err != ErrWalletIsWatchingOnly {
|
||||||
|
t.Errorf("Nonsensical func AddressKey returned no or incorrect error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := ww.ExportWatchingWallet(); err != ErrWalletIsWatchingOnly {
|
||||||
|
t.Errorf("Nonsensical func ExportWatchingWallet returned no or incorrect error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := ww.ImportPrivateKey(make([]byte, 32), true, createdAt); err != ErrWalletIsWatchingOnly {
|
||||||
|
t.Errorf("Nonsensical func ImportPrivateKey returned no or incorrect error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue