Prepare for adding other types of wallet addresses other than pkhash

Add a walletAddress interface to handle the differences betweent he
different types. Stop using btcutil.AddressPubKeyHash everywhere and just use
the standard address.
This commit is contained in:
Owain G. Ainsworth 2014-03-11 01:28:40 +00:00
parent df31e30839
commit 59fb904dc7
2 changed files with 201 additions and 125 deletions

View file

@ -473,9 +473,14 @@ func (v *varEntries) ReadFrom(r io.Reader) (n int64, err error) {
} }
} }
type addressKey string
type transactionHashKey string type transactionHashKey string
type comment []byte type comment []byte
func getAddressKey(addr btcutil.Address) addressKey {
return addressKey(addr.ScriptAddress())
}
// Wallet represents an btcwallet wallet in memory. It implements // Wallet represents an btcwallet wallet in memory. It implements
// the io.ReaderFrom and io.WriterTo interfaces to read from and // the io.ReaderFrom and io.WriterTo interfaces to read from and
// write to any type of byte streams, including files. // write to any type of byte streams, including files.
@ -494,15 +499,15 @@ type Wallet struct {
// root address and the appended entries. // root address and the appended entries.
recent recentBlocks recent recentBlocks
addrMap map[btcutil.AddressPubKeyHash]*btcAddress addrMap map[addressKey]walletAddress
addrCommentMap map[btcutil.AddressPubKeyHash]comment addrCommentMap map[addressKey]comment
txCommentMap map[transactionHashKey]comment txCommentMap map[transactionHashKey]comment
// The rest of the fields in this struct are not serialized. // The rest of the fields in this struct are not serialized.
passphrase []byte passphrase []byte
secret []byte secret []byte
chainIdxMap map[int64]*btcutil.AddressPubKeyHash chainIdxMap map[int64]btcutil.Address
importedAddrs []*btcAddress importedAddrs []walletAddress
lastChainIdx int64 lastChainIdx int64
missingKeysStart int64 missingKeysStart int64
} }
@ -575,10 +580,10 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet,
&createdAt.Hash, &createdAt.Hash,
}, },
}, },
addrMap: make(map[btcutil.AddressPubKeyHash]*btcAddress), addrMap: make(map[addressKey]walletAddress),
addrCommentMap: make(map[btcutil.AddressPubKeyHash]comment), addrCommentMap: make(map[addressKey]comment),
txCommentMap: make(map[transactionHashKey]comment), txCommentMap: make(map[transactionHashKey]comment),
chainIdxMap: make(map[int64]*btcutil.AddressPubKeyHash), chainIdxMap: make(map[int64]btcutil.Address),
lastChainIdx: rootKeyChainIdx, lastChainIdx: rootKeyChainIdx,
secret: aeskey, secret: aeskey,
} }
@ -586,8 +591,9 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet,
copy(w.desc[:], []byte(desc)) copy(w.desc[:], []byte(desc))
// Add root address to maps. // Add root address to maps.
w.addrMap[*w.keyGenerator.address(net)] = &w.keyGenerator rootAddr := w.keyGenerator.address(w.net)
w.chainIdxMap[rootKeyChainIdx] = w.keyGenerator.address(net) w.addrMap[getAddressKey(rootAddr)] = &w.keyGenerator
w.chainIdxMap[rootKeyChainIdx] = rootAddr
// Fill keypool. // Fill keypool.
if err := w.extendKeypool(keypoolSize, createdAt); err != nil { if err := w.extendKeypool(keypoolSize, createdAt); err != nil {
@ -620,9 +626,9 @@ func (w *Wallet) Name() string {
func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) { func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
var read int64 var read int64
w.addrMap = make(map[btcutil.AddressPubKeyHash]*btcAddress) w.addrMap = make(map[addressKey]walletAddress)
w.addrCommentMap = make(map[btcutil.AddressPubKeyHash]comment) w.addrCommentMap = make(map[addressKey]comment)
w.chainIdxMap = make(map[int64]*btcutil.AddressPubKeyHash) w.chainIdxMap = make(map[int64]btcutil.Address)
w.txCommentMap = make(map[transactionHashKey]comment) w.txCommentMap = make(map[transactionHashKey]comment)
var id [8]byte var id [8]byte
@ -671,7 +677,7 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
// Add root address to address map. // Add root address to address map.
rootAddr := w.keyGenerator.address(w.net) rootAddr := w.keyGenerator.address(w.net)
w.addrMap[*rootAddr] = &w.keyGenerator w.addrMap[getAddressKey(rootAddr)] = &w.keyGenerator
w.chainIdxMap[rootKeyChainIdx] = rootAddr w.chainIdxMap[rootKeyChainIdx] = rootAddr
// Fill unserializied fields. // Fill unserializied fields.
@ -680,8 +686,8 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
switch e := wt.(type) { switch e := wt.(type) {
case *addrEntry: case *addrEntry:
addr := e.addr.address(w.net) addr := e.addr.address(w.net)
w.addrMap[*addr] = &e.addr w.addrMap[getAddressKey(addr)] = &e.addr
if e.addr.chainIndex == importedKeyChainIdx { if e.addr.imported() {
w.importedAddrs = append(w.importedAddrs, &e.addr) w.importedAddrs = append(w.importedAddrs, &e.addr)
} else { } else {
w.chainIdxMap[e.addr.chainIndex] = addr w.chainIdxMap[e.addr.chainIndex] = addr
@ -703,7 +709,8 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
case *addrCommentEntry: case *addrCommentEntry:
addr := e.address(w.net) addr := e.address(w.net)
w.addrCommentMap[*addr] = comment(e.comment) w.addrCommentMap[getAddressKey(addr)] =
comment(e.comment)
case *txCommentEntry: case *txCommentEntry:
txKey := transactionHashKey(e.txHash[:]) txKey := transactionHashKey(e.txHash[:])
@ -723,18 +730,21 @@ func (w *Wallet) WriteTo(wtr io.Writer) (n int64, err error) {
var wts []io.WriterTo var wts []io.WriterTo
var chainedAddrs = make([]io.WriterTo, len(w.chainIdxMap)-1) var chainedAddrs = make([]io.WriterTo, len(w.chainIdxMap)-1)
var importedAddrs []io.WriterTo var importedAddrs []io.WriterTo
for addr, btcAddr := range w.addrMap { for _, wAddr := range w.addrMap {
switch btcAddr := wAddr.(type) {
case *btcAddress:
e := &addrEntry{ e := &addrEntry{
addr: *btcAddr, addr: *btcAddr,
} }
copy(e.pubKeyHash160[:], addr.ScriptAddress()) copy(e.pubKeyHash160[:], btcAddr.pubKeyHash[:])
if btcAddr.chainIndex >= 0 { if btcAddr.imported() {
// No order for imported addresses.
importedAddrs = append(importedAddrs, e)
} else if btcAddr.chainIndex >= 0 {
// Chained addresses are sorted. This is // Chained addresses are sorted. This is
// kind of nice but probably isn't necessary. // kind of nice but probably isn't necessary.
chainedAddrs[btcAddr.chainIndex] = e chainedAddrs[btcAddr.chainIndex] = e
} else if btcAddr.chainIndex == importedKeyChainIdx { }
// No order for imported addresses.
importedAddrs = append(importedAddrs, e)
} }
} }
wts = append(chainedAddrs, importedAddrs...) wts = append(chainedAddrs, importedAddrs...)
@ -742,7 +752,9 @@ func (w *Wallet) WriteTo(wtr io.Writer) (n int64, err error) {
e := &addrCommentEntry{ e := &addrCommentEntry{
comment: []byte(comment), comment: []byte(comment),
} }
copy(e.pubKeyHash160[:], addr.ScriptAddress()) // addresskey is the address hash as a string, we can cast it
// safely (though a little distasteful).
copy(e.pubKeyHash160[:], []byte(addr))
wts = append(wts, e) wts = append(wts, e)
} }
for hash, comment := range w.txCommentMap { for hash, comment := range w.txCommentMap {
@ -868,7 +880,13 @@ func (w *Wallet) ChangePassphrase(new []byte) error {
oldkey := w.secret oldkey := w.secret
newkey := Key(new, &w.kdfParams) newkey := Key(new, &w.kdfParams)
for _, a := range w.addrMap { for _, wa := range w.addrMap {
// Only btcAddresses curently have private keys.
a, ok := wa.(*btcAddress)
if !ok {
continue
}
if err := a.changeEncryptionKey(oldkey, newkey); err != nil { if err := a.changeEncryptionKey(oldkey, newkey); err != nil {
return err return err
} }
@ -902,7 +920,7 @@ func (w *Wallet) IsLocked() bool {
// is used. If not and the wallet is unlocked, the keypool is extended. // is used. If not and the wallet is unlocked, the keypool is extended.
// If locked, a new address's pubkey is chained off the last pubkey // If locked, a new address's pubkey is chained off the last pubkey
// and added to the wallet. // and added to the wallet.
func (w *Wallet) NextChainedAddress(bs *BlockStamp, keypoolSize uint) (*btcutil.AddressPubKeyHash, error) { func (w *Wallet) NextChainedAddress(bs *BlockStamp, keypoolSize uint) (btcutil.Address, error) {
addr, err := w.nextChainedAddress(bs, keypoolSize) addr, err := w.nextChainedAddress(bs, keypoolSize)
if err != nil { if err != nil {
return nil, err return nil, err
@ -912,7 +930,7 @@ func (w *Wallet) NextChainedAddress(bs *BlockStamp, keypoolSize uint) (*btcutil.
return addr.address(w.net), nil return addr.address(w.net), nil
} }
func (w *Wallet) ChangeAddress(bs *BlockStamp, keypoolSize uint) (*btcutil.AddressPubKeyHash, error) { func (w *Wallet) ChangeAddress(bs *BlockStamp, keypoolSize uint) (btcutil.Address, error) {
addr, err := w.nextChainedAddress(bs, keypoolSize) addr, err := w.nextChainedAddress(bs, keypoolSize)
if err != nil { if err != nil {
return nil, err return nil, err
@ -948,20 +966,25 @@ func (w *Wallet) nextChainedAddress(bs *BlockStamp, keypoolSize uint) (*btcAddre
} }
// Look up address. // Look up address.
addr, ok := w.addrMap[*nextAPKH] addr, ok := w.addrMap[getAddressKey(nextAPKH)]
if !ok { if !ok {
return nil, errors.New("cannot find generated address") return nil, errors.New("cannot find generated address")
} }
btcAddr, ok := addr.(*btcAddress)
if !ok {
return nil, errors.New("found non-pubkey chained address")
}
w.highestUsed++ w.highestUsed++
return addr, nil return btcAddr, nil
} }
// 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.AddressPubKeyHash { func (w *Wallet) LastChainedAddress() btcutil.Address {
return w.chainIdxMap[w.highestUsed] return w.chainIdxMap[w.highestUsed]
} }
@ -970,17 +993,22 @@ func (w *Wallet) extendKeypool(n uint, bs *BlockStamp) error {
// Get last chained address. New chained addresses will be // Get last chained address. New chained addresses will be
// chained off of this address's chaincode and private key. // chained off of this address's chaincode and private key.
a := w.chainIdxMap[w.lastChainIdx] a := w.chainIdxMap[w.lastChainIdx]
addr, ok := w.addrMap[*a] waddr, ok := w.addrMap[getAddressKey(a)]
if !ok { if !ok {
return errors.New("expected last chained address not found") return errors.New("expected last chained address not found")
} }
if len(w.secret) != 32 { if len(w.secret) != 32 {
return ErrWalletLocked return ErrWalletLocked
} }
privkey, err := addr.unlock(w.secret) privkey, err := waddr.unlock(w.secret)
if err != nil { if err != nil {
return err return err
} }
addr, ok := waddr.(*btcAddress)
if !ok {
return errors.New("found non-pubkey chained address")
}
cc := addr.chaincode[:] cc := addr.chaincode[:]
// Create n encrypted addresses and add each to the wallet's // Create n encrypted addresses and add each to the wallet's
@ -1001,10 +1029,11 @@ func (w *Wallet) extendKeypool(n uint, bs *BlockStamp) error {
return err return err
} }
a := newaddr.address(w.net) a := newaddr.address(w.net)
w.addrMap[*a] = newaddr w.addrMap[getAddressKey(a)] = newaddr
newaddr.chainIndex = addr.chainIndex + 1 newaddr.chainIndex = addr.chainIndex + 1
w.chainIdxMap[newaddr.chainIndex] = a w.chainIdxMap[newaddr.chainIndex] = a
w.lastChainIdx++ w.lastChainIdx++
// armory does this.. but all the chaincodes are equal so why // armory does this.. but all the chaincodes are equal so why
// not use the root's? // not use the root's?
copy(newaddr.chaincode[:], cc) copy(newaddr.chaincode[:], cc)
@ -1021,11 +1050,16 @@ func (w *Wallet) extendKeypool(n uint, bs *BlockStamp) error {
// not be called unless the keypool has been depleted. // not be called unless the keypool has been depleted.
func (w *Wallet) extendLockedWallet(bs *BlockStamp) error { func (w *Wallet) extendLockedWallet(bs *BlockStamp) error {
a := w.chainIdxMap[w.lastChainIdx] a := w.chainIdxMap[w.lastChainIdx]
addr, ok := w.addrMap[*a] waddr, ok := w.addrMap[getAddressKey(a)]
if !ok { if !ok {
return errors.New("expected last chained address not found") return errors.New("expected last chained address not found")
} }
addr, ok := waddr.(*btcAddress)
if !ok {
return errors.New("found non-pubkey chained address")
}
cc := addr.chaincode[:] cc := addr.chaincode[:]
prevPubkey := addr.pubKey prevPubkey := addr.pubKey
@ -1038,7 +1072,7 @@ func (w *Wallet) extendLockedWallet(bs *BlockStamp) error {
return err return err
} }
a = newaddr.address(w.net) a = newaddr.address(w.net)
w.addrMap[*a] = newaddr w.addrMap[getAddressKey(a)] = newaddr
newaddr.chainIndex = addr.chainIndex + 1 newaddr.chainIndex = addr.chainIndex + 1
w.chainIdxMap[newaddr.chainIndex] = a w.chainIdxMap[newaddr.chainIndex] = a
w.lastChainIdx++ w.lastChainIdx++
@ -1062,10 +1096,16 @@ func (w *Wallet) createMissingPrivateKeys() error {
if !ok { if !ok {
return errors.New("missing previous chained address") return errors.New("missing previous chained address")
} }
prevAddr := w.addrMap[*apkh] prevWAddr := w.addrMap[getAddressKey(apkh)]
if len(w.secret) != 32 { if len(w.secret) != 32 {
return ErrWalletLocked return ErrWalletLocked
} }
prevAddr, ok := prevWAddr.(*btcAddress)
if !ok {
return errors.New("found non-pubkey chained address")
}
prevPrivKey, err := prevAddr.unlock(w.secret) prevPrivKey, err := prevAddr.unlock(w.secret)
if err != nil { if err != nil {
return err return err
@ -1086,7 +1126,11 @@ func (w *Wallet) createMissingPrivateKeys() error {
// Finished. // Finished.
break break
} }
addr := w.addrMap[*apkh] waddr := w.addrMap[getAddressKey(apkh)]
addr, ok := waddr.(*btcAddress)
if !ok {
return errors.New("found non-pubkey chained address")
}
addr.privKeyCT = ithPrivKey addr.privKeyCT = ithPrivKey
if err := addr.encrypt(w.secret); err != nil { if err := addr.encrypt(w.secret); err != nil {
// Avoid bug: see comment for VersUnsetNeedsPrivkeyFlag. // Avoid bug: see comment for VersUnsetNeedsPrivkeyFlag.
@ -1116,22 +1160,17 @@ func (w *Wallet) AddressKey(a btcutil.Address) (key *ecdsa.PrivateKey, err error
return nil, ErrWalletIsWatchingOnly return nil, ErrWalletIsWatchingOnly
} }
// Currently, only P2PKH addresses are supported. This should
// be extended to a switch-case statement when support for other
// addresses are added.
addr, ok := a.(*btcutil.AddressPubKeyHash)
if !ok {
return nil, errors.New("unsupported address")
}
// Lookup address from map. // Lookup address from map.
btcaddr, ok := w.addrMap[*addr] waddr, ok := w.addrMap[getAddressKey(a)]
if !ok { if !ok {
return nil, ErrAddressNotFound return nil, ErrAddressNotFound
} }
// Both the pubkey and encrypted privkey must be recorded to return switch btcaddr := waddr.(type) {
// the private key. Error if neither are saved. case *btcAddress:
// Both the pubkey and encrypted privkey must be recorded to
// return the private key. Error if neither are saved.
if !btcaddr.flags.hasPubKey { if !btcaddr.flags.hasPubKey {
return nil, errors.New("no public key for address") return nil, errors.New("no public key for address")
} }
@ -1150,9 +1189,9 @@ func (w *Wallet) AddressKey(a btcutil.Address) (key *ecdsa.PrivateKey, err error
return nil, ErrWalletLocked return nil, ErrWalletLocked
} }
// Unlock address with wallet secret. unlock returns a copy of the // Unlock address with wallet secret. unlock returns a copy of
// clear text private key, and may be used safely even during an address // the clear text private key, and may be used safely even
// lock. // during an address lock.
privKeyCT, err := btcaddr.unlock(w.secret) privKeyCT, err := btcaddr.unlock(w.secret)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1162,6 +1201,9 @@ func (w *Wallet) AddressKey(a btcutil.Address) (key *ecdsa.PrivateKey, err error
PublicKey: *pubkey, PublicKey: *pubkey,
D: new(big.Int).SetBytes(privKeyCT), D: new(big.Int).SetBytes(privKeyCT),
}, nil }, nil
default:
return nil, errors.New("unsupported address type")
}
} }
// AddressInfo returns an AddressInfo structure for an address in a wallet. // AddressInfo returns an AddressInfo structure for an address in a wallet.
@ -1175,7 +1217,7 @@ func (w *Wallet) AddressInfo(a btcutil.Address) (AddressInfo, error) {
} }
// Look up address by address hash. // Look up address by address hash.
btcaddr, ok := w.addrMap[*addr] btcaddr, ok := w.addrMap[getAddressKey(addr)]
if !ok { if !ok {
return nil, ErrAddressNotFound return nil, ErrAddressNotFound
} }
@ -1261,8 +1303,9 @@ func (w *Wallet) EarliestBlockHeight() int32 {
// Imported keys will be the only ones that may have an earlier // Imported keys will be the only ones that may have an earlier
// blockchain height. Check each and set the returned height // blockchain height. Check each and set the returned height
for _, addr := range w.importedAddrs { for _, addr := range w.importedAddrs {
if addr.firstBlock < height { aheight := addr.FirstBlockHeight()
height = addr.firstBlock if aheight < height {
height = aheight
// Can't go any lower than 0. // Can't go any lower than 0.
if height == 0 { if height == 0 {
@ -1286,7 +1329,7 @@ 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. // user-provided private key and adds it to the wallet.
func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, bs *BlockStamp) (*btcutil.AddressPubKeyHash, error) { func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, bs *BlockStamp) (btcutil.Address, error) {
if w.flags.watchingOnly { if w.flags.watchingOnly {
return nil, ErrWalletIsWatchingOnly return nil, ErrWalletIsWatchingOnly
} }
@ -1294,12 +1337,7 @@ func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, bs *BlockStam
// 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. if _, ok := w.addrMap[addressKey(pkh)]; ok {
apkh, err := btcutil.NewAddressPubKeyHash(pkh, w.Net())
if err != nil {
return nil, err
}
if _, ok := w.addrMap[*apkh]; ok {
return nil, ErrDuplicate return nil, ErrDuplicate
} }
@ -1320,14 +1358,15 @@ func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, bs *BlockStam
return nil, err return nil, err
} }
addr := btcaddr.address(w.Net())
// Add address to wallet's bookkeeping structures. Adding to // Add address to wallet's bookkeeping structures. Adding to
// the map will result in the imported address being serialized // the map will result in the imported address being serialized
// on the next WriteTo call. // on the next WriteTo call.
w.addrMap[*btcaddr.address(w.net)] = btcaddr w.addrMap[getAddressKey(addr)] = btcaddr
w.importedAddrs = append(w.importedAddrs, btcaddr) w.importedAddrs = append(w.importedAddrs, btcaddr)
// Create and return address. // Create and return address.
return btcutil.NewAddressPubKeyHash(btcaddr.pubKeyHash[:], w.Net()) return addr, nil
} }
// CreateDate returns the Unix time of the wallet creation time. This // CreateDate returns the Unix time of the wallet creation time. This
@ -1348,6 +1387,9 @@ func (w *Wallet) ExportWatchingWallet() (*Wallet, error) {
return nil, ErrWalletIsWatchingOnly return nil, ErrWalletIsWatchingOnly
} }
kgwc := w.keyGenerator.watchingCopy()
kgwcba := kgwc.(*btcAddress)
// Copy members of w into a new wallet, but mark as watching-only and // Copy members of w into a new wallet, but mark as watching-only and
// do not include any private keys. // do not include any private keys.
ww := &Wallet{ ww := &Wallet{
@ -1361,16 +1403,16 @@ func (w *Wallet) ExportWatchingWallet() (*Wallet, error) {
desc: w.desc, desc: w.desc,
createDate: w.createDate, createDate: w.createDate,
highestUsed: w.highestUsed, highestUsed: w.highestUsed,
keyGenerator: *w.keyGenerator.watchingCopy(), keyGenerator: *kgwcba,
recent: recentBlocks{ recent: recentBlocks{
lastHeight: w.recent.lastHeight, lastHeight: w.recent.lastHeight,
}, },
addrMap: make(map[btcutil.AddressPubKeyHash]*btcAddress), addrMap: make(map[addressKey]walletAddress),
addrCommentMap: make(map[btcutil.AddressPubKeyHash]comment), addrCommentMap: make(map[addressKey]comment),
txCommentMap: make(map[transactionHashKey]comment), txCommentMap: make(map[transactionHashKey]comment),
chainIdxMap: make(map[int64]*btcutil.AddressPubKeyHash), chainIdxMap: make(map[int64]btcutil.Address),
lastChainIdx: w.lastChainIdx, lastChainIdx: w.lastChainIdx,
} }
@ -1383,10 +1425,14 @@ func (w *Wallet) ExportWatchingWallet() (*Wallet, error) {
} }
} }
for apkh, addr := range w.addrMap { for apkh, addr := range w.addrMap {
apkhCopy := apkh if !addr.imported() {
if addr.chainIndex != importedKeyChainIdx { // Must be a btcAddress if !imported.
ww.chainIdxMap[addr.chainIndex] = &apkhCopy btcAddr := addr.(*btcAddress)
ww.chainIdxMap[btcAddr.chainIndex] =
addr.address(w.net)
} }
apkhCopy := apkh
ww.addrMap[apkhCopy] = addr.watchingCopy() ww.addrMap[apkhCopy] = addr.watchingCopy()
} }
for apkh, cmt := range w.addrCommentMap { for apkh, cmt := range w.addrCommentMap {
@ -1395,7 +1441,8 @@ func (w *Wallet) ExportWatchingWallet() (*Wallet, error) {
ww.addrCommentMap[apkh] = cmtCopy ww.addrCommentMap[apkh] = cmtCopy
} }
if len(w.importedAddrs) != 0 { if len(w.importedAddrs) != 0 {
ww.importedAddrs = make([]*btcAddress, 0, len(w.importedAddrs)) ww.importedAddrs = make([]walletAddress, 0,
len(w.importedAddrs))
for _, addr := range w.importedAddrs { for _, addr := range w.importedAddrs {
ww.importedAddrs = append(ww.importedAddrs, addr.watchingCopy()) ww.importedAddrs = append(ww.importedAddrs, addr.watchingCopy())
} }
@ -1478,7 +1525,7 @@ func (w *Wallet) SortedActiveAddresses() []AddressInfo {
w.highestUsed+int64(len(w.importedAddrs))+1) w.highestUsed+int64(len(w.importedAddrs))+1)
for i := int64(rootKeyChainIdx); i <= w.highestUsed; i++ { for i := int64(rootKeyChainIdx); i <= w.highestUsed; i++ {
a := w.chainIdxMap[i] a := w.chainIdxMap[i]
info, err := w.addrMap[*a].info(w.Net()) info, err := w.addrMap[getAddressKey(a)].info(w.Net())
if err == nil { if err == nil {
addrs = append(addrs, info) addrs = append(addrs, info)
} }
@ -1499,7 +1546,7 @@ func (w *Wallet) ActiveAddresses() map[btcutil.Address]AddressInfo {
addrs := make(map[btcutil.Address]AddressInfo) addrs := make(map[btcutil.Address]AddressInfo)
for i := int64(rootKeyChainIdx); i <= w.highestUsed; i++ { for i := int64(rootKeyChainIdx); i <= w.highestUsed; i++ {
a := w.chainIdxMap[i] a := w.chainIdxMap[i]
info, err := w.addrMap[*a].info(w.Net()) info, err := w.addrMap[getAddressKey(a)].info(w.Net())
if err == nil { if err == nil {
addrs[info.Address()] = info addrs[info.Address()] = info
} }
@ -1526,8 +1573,8 @@ func (w *Wallet) ExtendActiveAddresses(n int, keypoolSize uint) ([]btcutil.Addre
return nil, errors.New("n is not positive") return nil, errors.New("n is not positive")
} }
last := w.addrMap[*w.chainIdxMap[w.highestUsed]] last := w.addrMap[getAddressKey(w.chainIdxMap[w.highestUsed])]
bs := &BlockStamp{Height: last.firstBlock} bs := &BlockStamp{Height: last.FirstBlockHeight()}
addrs := make([]btcutil.Address, 0, n) addrs := make([]btcutil.Address, 0, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
@ -1892,6 +1939,21 @@ func (u *unusedSpace) WriteTo(w io.Writer) (int64, error) {
return written + int64(n), err return written + int64(n), err
} }
type walletAddress interface {
io.ReaderFrom
io.WriterTo
encrypt(key []byte) error
lock() error
unlock(key []byte) (privKeyCT []byte, err error)
changeEncryptionKey(oldkey, newkey []byte) error
verifyKeypairs() error
address(net btcwire.BitcoinNet) btcutil.Address
info(net btcwire.BitcoinNet) (AddressInfo, error)
watchingCopy() walletAddress
FirstBlockHeight() int32
imported() bool
}
type btcAddress struct { type btcAddress struct {
pubKeyHash [ripemd160.Size]byte pubKeyHash [ripemd160.Size]byte
flags addrFlags flags addrFlags
@ -2317,7 +2379,7 @@ func (a *btcAddress) changeEncryptionKey(oldkey, newkey []byte) error {
} }
// address returns a btcutil.AddressPubKeyHash for a btcAddress. // address returns a btcutil.AddressPubKeyHash for a btcAddress.
func (a *btcAddress) address(net btcwire.BitcoinNet) *btcutil.AddressPubKeyHash { func (a *btcAddress) address(net btcwire.BitcoinNet) btcutil.Address {
// error is not returned because the hash will always be 20 // error is not returned because the hash will always be 20
// bytes, and net is assumed to be valid. // bytes, and net is assumed to be valid.
addr, _ := btcutil.NewAddressPubKeyHash(a.pubKeyHash[:], net) addr, _ := btcutil.NewAddressPubKeyHash(a.pubKeyHash[:], net)
@ -2334,7 +2396,7 @@ func (a *btcAddress) info(net btcwire.BitcoinNet) (AddressInfo, error) {
addrHash: string(a.pubKeyHash[:]), addrHash: string(a.pubKeyHash[:]),
compressed: a.flags.compressed, compressed: a.flags.compressed,
firstBlock: a.firstBlock, firstBlock: a.firstBlock,
imported: a.chainIndex == importedKeyChainIdx, imported: a.imported(),
Pubkey: hex.EncodeToString(a.pubKey), Pubkey: hex.EncodeToString(a.pubKey),
change: a.flags.change, change: a.flags.change,
}, nil }, nil
@ -2343,7 +2405,7 @@ func (a *btcAddress) info(net btcwire.BitcoinNet) (AddressInfo, error) {
// watchingCopy creates a copy of an address without a private key. // watchingCopy creates a copy of an address without a private key.
// This is used to fill a watching a wallet with addresses from a // This is used to fill a watching a wallet with addresses from a
// normal wallet. // normal wallet.
func (a *btcAddress) watchingCopy() *btcAddress { func (a *btcAddress) watchingCopy() walletAddress {
return &btcAddress{ return &btcAddress{
pubKeyHash: a.pubKeyHash, pubKeyHash: a.pubKeyHash,
flags: addrFlags{ flags: addrFlags{
@ -2365,6 +2427,14 @@ func (a *btcAddress) watchingCopy() *btcAddress {
} }
} }
func (a *btcAddress) FirstBlockHeight() int32 {
return a.firstBlock
}
func (a *btcAddress) imported() bool {
return a.chainIndex == importedKeyChainIdx
}
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)

View file

@ -489,10 +489,10 @@ func TestWatchingWalletExport(t *testing.T) {
} }
// Maintain a set of the active addresses in the wallet. // Maintain a set of the active addresses in the wallet.
activeAddrs := make(map[btcutil.AddressPubKeyHash]struct{}) activeAddrs := make(map[addressKey]struct{})
// Add root address. // Add root address.
activeAddrs[*w.LastChainedAddress()] = struct{}{} activeAddrs[getAddressKey(w.LastChainedAddress())] = struct{}{}
// Get as many new active addresses as necessary to deplete the keypool. // 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 // This is done as we will want to test that new addresses created by
@ -504,7 +504,7 @@ func TestWatchingWalletExport(t *testing.T) {
t.Errorf("unable to get next address: %v", err) t.Errorf("unable to get next address: %v", err)
return return
} }
activeAddrs[*apkh] = struct{}{} activeAddrs[getAddressKey(apkh)] = struct{}{}
} }
// Create watching wallet from w. // Create watching wallet from w.
@ -541,23 +541,29 @@ func TestWatchingWalletExport(t *testing.T) {
t.Errorf("Watching root address marked as needing a private key to be generated later.") t.Errorf("Watching root address marked as needing a private key to be generated later.")
return return
} }
for apkh, addr := range ww.addrMap { for apkh, waddr := range ww.addrMap {
switch addr := waddr.(type) {
case *btcAddress:
if addr.flags.encrypted { if addr.flags.encrypted {
t.Errorf("Chained address should not be encrypted (nothing to encrypt)") t.Errorf("Chained address should not be encrypted (nothing to encrypt)")
return return
} }
if ww.keyGenerator.flags.hasPrivKey { if addr.flags.hasPrivKey {
t.Errorf("Chained address marked as having a private key.") t.Errorf("Chained address marked as having a private key.")
return return
} }
if !ww.keyGenerator.flags.hasPubKey { if !addr.flags.hasPubKey {
t.Errorf("Chained address marked as missing a public key.") t.Errorf("Chained address marked as missing a public key.")
return return
} }
if ww.keyGenerator.flags.createPrivKeyNextUnlock { if addr.flags.createPrivKeyNextUnlock {
t.Errorf("Chained address marked as needing a private key to be generated later.") t.Errorf("Chained address marked as needing a private key to be generated later.")
return return
} }
default:
t.Errorf("Chained address unknown type!")
return
}
if _, ok := activeAddrs[apkh]; !ok { if _, ok := activeAddrs[apkh]; !ok {
t.Errorf("Address from watching wallet not found in original wallet.") t.Errorf("Address from watching wallet not found in original wallet.")
@ -587,7 +593,7 @@ func TestWatchingWalletExport(t *testing.T) {
t.Errorf("Cannot get next chained address for watching wallet: %v", err) t.Errorf("Cannot get next chained address for watching wallet: %v", err)
return return
} }
if addr.String() != waddr.String() { if addr.EncodeAddress() != waddr.EncodeAddress() {
t.Errorf("Next addresses for each wallet do not match eachother.") t.Errorf("Next addresses for each wallet do not match eachother.")
return return
} }