From df31e308396fa65a025f57a41b6ac5b2a8139527 Mon Sep 17 00:00:00 2001 From: "Owain G. Ainsworth" Date: Thu, 6 Mar 2014 00:34:44 +0000 Subject: [PATCH] Make AddressInfo an interface. Shortly we will add new types of address, so make AddressInfo an interface, with concrete types providing address-specific information. Adapt existing code to this new status quo. --- account.go | 11 ++--- createtx.go | 2 +- rpcserver.go | 13 ++++-- wallet/wallet.go | 101 ++++++++++++++++++++++++++++++++---------- wallet/wallet_test.go | 11 +++-- 5 files changed, 100 insertions(+), 38 deletions(-) diff --git a/account.go b/account.go index ad69f21..628f0a7 100644 --- a/account.go +++ b/account.go @@ -315,7 +315,7 @@ func (a *Account) DumpPrivKeys() ([]string, error) { return nil, err } encKey, err := btcutil.EncodePrivateKey(key.D.Bytes(), - a.Wallet.Net(), info.Compressed) + a.Wallet.Net(), info.Compressed()) if err != nil { return nil, err } @@ -342,7 +342,8 @@ func (a *Account) DumpWIFPrivateKey(addr btcutil.Address) (string, error) { } // Return WIF-encoding of the private key. - return btcutil.EncodePrivateKey(key.D.Bytes(), a.Net(), info.Compressed) + return btcutil.EncodePrivateKey(key.D.Bytes(), a.Net(), + info.Compressed()) } // ImportPrivateKey imports a private key to the account's wallet and @@ -497,7 +498,7 @@ func (a *Account) SortedActivePaymentAddresses() []string { addrs := make([]string, len(infos)) for i, info := range infos { - addrs[i] = info.Address.EncodeAddress() + addrs[i] = info.Address().EncodeAddress() } return addrs @@ -510,7 +511,7 @@ func (a *Account) ActivePaymentAddresses() map[string]struct{} { addrs := make(map[string]struct{}, len(infos)) for _, info := range infos { - addrs[info.Address.EncodeAddress()] = struct{}{} + addrs[info.Address().EncodeAddress()] = struct{}{} } return addrs @@ -595,7 +596,7 @@ func (a *Account) RecoverAddresses(n int) error { m[addrs[i].EncodeAddress()] = struct{}{} } go func(addrs map[string]struct{}) { - jsonErr := Rescan(CurrentServerConn(), lastInfo.FirstBlock, addrs) + jsonErr := Rescan(CurrentServerConn(), lastInfo.FirstBlock(), addrs) if jsonErr != nil { log.Errorf("Rescanning for recovered addresses failed: %v", jsonErr.Message) diff --git a/createtx.go b/createtx.go index 08fcbcf..4f9e900 100644 --- a/createtx.go +++ b/createtx.go @@ -257,7 +257,7 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er sigscript, err := btcscript.SignatureScript(msgtx, i, input.PkScript(), btcscript.SigHashAll, privkey, - ai.Compressed) + ai.Compressed()) if err != nil { return nil, fmt.Errorf("cannot create sigscript: %s", err) } diff --git a/rpcserver.go b/rpcserver.go index 10a1f86..0cdb266 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1493,7 +1493,7 @@ func SignMessage(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { fullmsg := "Bitcoin Signed Message:\n" + cmd.Message sigbytes, err := btcec.SignCompact(btcec.S256(), privkey, - btcwire.DoubleSha256([]byte(fullmsg)), ainfo.Compressed) + btcwire.DoubleSha256([]byte(fullmsg)), ainfo.Compressed()) if err != nil { return nil, &btcjson.Error{ Code: btcjson.ErrWallet.Code, @@ -1600,13 +1600,18 @@ func ValidateAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { result["ismine"] = true result["account"] = account + switch info := ainfo.(type) { + case *wallet.AddressPubKeyInfo: + result["compressed"] = info.Compressed() + result["pubkey"] = info.Pubkey + default: + } + // TODO(oga) when we handle different types of addresses then // we will need to check here and only provide the script, // hexsript and list of addresses. // if scripthash, the pubkey if pubkey/pubkeyhash, etc. // for now we only support p2pkh so is irrelavent - result["compressed"] = ainfo.Compressed - result["pubkey"] = ainfo.Pubkey } else { result["ismine"] = false } @@ -1681,7 +1686,7 @@ func VerifyMessage(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { // Return boolean if keys match. return (pk.X.Cmp(privkey.X) == 0 && pk.Y.Cmp(privkey.Y) == 0 && - ainfo.Compressed == wasCompressed), nil + ainfo.Compressed() == wasCompressed), nil } // WalletIsLocked handles the walletislocked extension request by diff --git a/wallet/wallet.go b/wallet/wallet.go index 4619d41..df46649 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1165,7 +1165,7 @@ func (w *Wallet) AddressKey(a btcutil.Address) (key *ecdsa.PrivateKey, err error } // AddressInfo returns an AddressInfo structure for an address in a wallet. -func (w *Wallet) AddressInfo(a btcutil.Address) (*AddressInfo, error) { +func (w *Wallet) AddressInfo(a btcutil.Address) (AddressInfo, error) { // Currently, only P2PKH addresses are supported. This should // be extended to a switch-case statement when support for other // addresses are added. @@ -1404,24 +1404,77 @@ func (w *Wallet) ExportWatchingWallet() (*Wallet, error) { return ww, nil } -// AddressInfo holds information regarding an address needed to manage -// a complete wallet. -type AddressInfo struct { - btcutil.Address - AddrHash string - Compressed bool - FirstBlock int32 - Imported bool +// AddressInfo is an interface that provides acces to information regarding an +// address managed by a wallet. Concrete implementations of this type may +// provide further fields to provide information specific to that type of +// address. +type AddressInfo interface { + // Address returns a btcutil.Address for the backing address. + Address() btcutil.Address + // FirstBlock returns the first block an address could be in. + FirstBlock() int32 + // Compressed returns true if the backing address was imported instead + // of being part of an address chain. + Imported() bool + // Compressed returns true if the backing address was created for a + // change output of a transaction. + Change() bool + // Compressed returns true if the backing address is compressed. + Compressed() bool +} + +// AddressPubKeyInfo implements AddressInfo and additionally provides the +// pubkey for a pubkey-based address. +type AddressPubKeyInfo struct { + address btcutil.Address + addrHash string + compressed bool + firstBlock int32 + imported bool Pubkey string - Change bool + change bool +} + +// Address returns the pub key address, implementing AddressInfo. +func (ai *AddressPubKeyInfo) Address() btcutil.Address { + return ai.address +} + +// AddrHash returns the pub key hash, implementing AddressInfo. +func (ai *AddressPubKeyInfo) AddrHash() string { + return ai.addrHash +} + +// FirstBlock returns the first block the address is seen in, implementing +// AddressInfo. +func (ai *AddressPubKeyInfo) FirstBlock() int32 { + return ai.firstBlock +} + +// Imported returns the pub if the address was imported, or a chained address, +// implementing AddressInfo. +func (ai *AddressPubKeyInfo) Imported() bool { + return ai.imported +} + +// AddrHash returns true if the address was created as a change address, +// implementing AddressInfo. +func (ai *AddressPubKeyInfo) Change() bool { + return ai.change +} + +// AddrHash returns true if the address backing key is compressed, +// implementing AddressInfo. +func (ai *AddressPubKeyInfo) Compressed() bool { + return ai.compressed } // SortedActiveAddresses returns all wallet addresses that have been // requested to be generated. These do not include unused addresses in // the key pool. Use this when ordered addresses are needed. Otherwise, // ActiveAddresses is preferred. -func (w *Wallet) SortedActiveAddresses() []*AddressInfo { - addrs := make([]*AddressInfo, 0, +func (w *Wallet) SortedActiveAddresses() []AddressInfo { + addrs := make([]AddressInfo, 0, w.highestUsed+int64(len(w.importedAddrs))+1) for i := int64(rootKeyChainIdx); i <= w.highestUsed; i++ { a := w.chainIdxMap[i] @@ -1442,19 +1495,19 @@ func (w *Wallet) SortedActiveAddresses() []*AddressInfo { // ActiveAddresses returns a map between active payment addresses // and their full info. These do not include unused addresses in the // key pool. If addresses must be sorted, use SortedActiveAddresses. -func (w *Wallet) ActiveAddresses() map[btcutil.Address]*AddressInfo { - addrs := make(map[btcutil.Address]*AddressInfo) +func (w *Wallet) ActiveAddresses() map[btcutil.Address]AddressInfo { + addrs := make(map[btcutil.Address]AddressInfo) for i := int64(rootKeyChainIdx); i <= w.highestUsed; i++ { a := w.chainIdxMap[i] info, err := w.addrMap[*a].info(w.Net()) if err == nil { - addrs[info.Address] = info + addrs[info.Address()] = info } } for _, addr := range w.importedAddrs { info, err := addr.info(w.Net()) if err == nil { - addrs[info.Address] = info + addrs[info.Address()] = info } } return addrs @@ -2273,17 +2326,17 @@ func (a *btcAddress) address(net btcwire.BitcoinNet) *btcutil.AddressPubKeyHash // info returns information about a btcAddress stored in a AddressInfo // struct. -func (a *btcAddress) info(net btcwire.BitcoinNet) (*AddressInfo, error) { +func (a *btcAddress) info(net btcwire.BitcoinNet) (AddressInfo, error) { address := a.address(net) - return &AddressInfo{ - Address: address, - AddrHash: string(a.pubKeyHash[:]), - Compressed: a.flags.compressed, - FirstBlock: a.firstBlock, - Imported: a.chainIndex == importedKeyChainIdx, + return &AddressPubKeyInfo{ + address: address, + addrHash: string(a.pubKeyHash[:]), + compressed: a.flags.compressed, + firstBlock: a.firstBlock, + imported: a.chainIndex == importedKeyChainIdx, Pubkey: hex.EncodeToString(a.pubKey), - Change: a.flags.change, + change: a.flags.change, }, nil } diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go index 91ab154..a0e9d2e 100644 --- a/wallet/wallet_test.go +++ b/wallet/wallet_test.go @@ -347,12 +347,14 @@ func TestWalletPubkeyChaining(t *testing.T) { t.Errorf("Failed to get info about address without private key: %v", err) return } + + pkinfo := info.(*AddressPubKeyInfo) // sanity checks - if !info.Compressed { + if !info.Compressed() { t.Errorf("Pubkey should be compressed.") return } - if info.Imported { + if info.Imported() { t.Errorf("Should not be marked as imported.") return } @@ -419,7 +421,7 @@ func TestWalletPubkeyChaining(t *testing.T) { t.Errorf("Unable to sign hash with the created private key: %v", err) return } - pubKeyStr, _ := hex.DecodeString(info.Pubkey) + pubKeyStr, _ := hex.DecodeString(pkinfo.Pubkey) pubKey, err := btcec.ParsePubKey(pubKeyStr, btcec.S256()) ok := ecdsa.Verify(pubKey, hash, r, s) if !ok { @@ -442,6 +444,7 @@ func TestWalletPubkeyChaining(t *testing.T) { t.Errorf("Couldn't get info about the next address in the chain: %v", err) return } + nextPkInfo := nextInfo.(*AddressPubKeyInfo) nextKey, err := w.AddressKey(nextAddr) if err != nil { t.Errorf("Couldn't get private key for the next address in the chain: %v", err) @@ -455,7 +458,7 @@ func TestWalletPubkeyChaining(t *testing.T) { t.Errorf("Unable to sign hash with the created private key: %v", err) return } - pubKeyStr, _ = hex.DecodeString(nextInfo.Pubkey) + pubKeyStr, _ = hex.DecodeString(nextPkInfo.Pubkey) pubKey, err = btcec.ParsePubKey(pubKeyStr, btcec.S256()) ok = ecdsa.Verify(pubKey, hash, r, s) if !ok {