From e8265eca41313ea5544a84ff5e0a784142c6a837 Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Mon, 6 Jan 2014 12:24:29 -0500 Subject: [PATCH] Switch to new btcutil Address encoding/decoding API. --- account.go | 77 ++++++------ accountstore.go | 3 +- cmdmgr.go | 64 +++++++--- createtx.go | 41 +++---- createtx_test.go | 9 +- tx/tx.go | 15 +-- wallet/wallet.go | 267 +++++++++++++++++------------------------- wallet/wallet_test.go | 1 + 8 files changed, 223 insertions(+), 254 deletions(-) diff --git a/account.go b/account.go index 9049216..c8237ed 100644 --- a/account.go +++ b/account.go @@ -134,7 +134,7 @@ func (a *Account) Rollback(height int32, hash *btcwire.ShaHash) { // a given address. Assumming correct TxStore usage, this will return true iff // there are any transactions with outputs to this address in the blockchain or // the btcd mempool. -func (a *Account) AddressUsed(pkHash []byte) bool { +func (a *Account) AddressUsed(addr btcutil.Address) bool { // This can be optimized by recording this data as it is read when // opening an account, and keeping it up to date each time a new // received tx arrives. @@ -142,6 +142,8 @@ func (a *Account) AddressUsed(pkHash []byte) bool { a.TxStore.RLock() defer a.TxStore.RUnlock() + pkHash := addr.ScriptAddress() + for i := range a.TxStore.s { rtx, ok := a.TxStore.s[i].(*tx.RecvTx) if !ok { @@ -193,7 +195,7 @@ func (a *Account) CalculateBalance(confirms int) float64 { // a UTXO must be in a block. If confirmations is 1 or greater, // the balance will be calculated based on how many how many blocks // include a UTXO. -func (a *Account) CalculateAddressBalance(pubkeyHash []byte, confirms int) float64 { +func (a *Account) CalculateAddressBalance(addr *btcutil.AddressPubKeyHash, confirms int) float64 { var bal uint64 // Measured in satoshi bs, err := GetCurBlock() @@ -206,7 +208,7 @@ func (a *Account) CalculateAddressBalance(pubkeyHash []byte, confirms int) float // Utxos not yet in blocks (height -1) should only be // added if confirmations is 0. if confirms == 0 || (u.Height != -1 && int(bs.Height-u.Height+1) >= confirms) { - if bytes.Equal(pubkeyHash, u.AddrHash[:]) { + if bytes.Equal(addr.ScriptAddress(), u.AddrHash[:]) { bal += u.Amt } } @@ -219,22 +221,17 @@ func (a *Account) CalculateAddressBalance(pubkeyHash []byte, confirms int) float // from an account. If the address has already been used (there is at least // one transaction spending to it in the blockchain or btcd mempool), the next // chained address is returned. -func (a *Account) CurrentAddress() (string, error) { +func (a *Account) CurrentAddress() (btcutil.Address, error) { a.mtx.RLock() - addr, err := a.Wallet.LastChainedAddress() + addr := a.Wallet.LastChainedAddress() a.mtx.RUnlock() - if err != nil { - return "", err - } - // Get next chained address if the last one has already been used. - pkHash, _, _ := btcutil.DecodeAddress(addr) - if a.AddressUsed(pkHash) { - addr, err = a.NewAddress() + if a.AddressUsed(addr) { + return a.NewAddress() } - return addr, err + return addr, nil } // ListTransactions returns a slice of maps with details about a recorded @@ -337,8 +334,8 @@ func (a *Account) ListAllTransactions() ([]map[string]interface{}, error) { return txInfoList, nil } -// DumpPrivKeys returns the WIF-encoded private keys for all addresses -// non-watching addresses in a wallets. +// DumpPrivKeys returns the WIF-encoded private keys for all addresses with +// private keys in a wallet. func (a *Account) DumpPrivKeys() ([]string, error) { a.mtx.RLock() defer a.mtx.RUnlock() @@ -346,13 +343,13 @@ func (a *Account) DumpPrivKeys() ([]string, error) { // Iterate over each active address, appending the private // key to privkeys. var privkeys []string - for _, addr := range a.ActiveAddresses() { - key, err := a.AddressKey(addr.Address) + for addr, info := range a.ActiveAddresses() { + key, err := a.AddressKey(addr) if err != nil { return nil, err } encKey, err := btcutil.EncodePrivateKey(key.D.Bytes(), - a.Net(), addr.Compressed) + a.Net(), info.Compressed) if err != nil { return nil, err } @@ -364,19 +361,19 @@ func (a *Account) DumpPrivKeys() ([]string, error) { // DumpWIFPrivateKey returns the WIF encoded private key for a // single wallet address. -func (a *Account) DumpWIFPrivateKey(address string) (string, error) { +func (a *Account) DumpWIFPrivateKey(addr btcutil.Address) (string, error) { a.mtx.RLock() defer a.mtx.RUnlock() // Get private key from wallet if it exists. - key, err := a.AddressKey(address) + key, err := a.AddressKey(addr) if err != nil { return "", err } // Get address info. This is needed to determine whether // the pubkey is compressed or not. - info, err := a.AddressInfo(address) + info, err := a.AddressInfo(addr) if err != nil { return "", err } @@ -573,8 +570,8 @@ func (a *Account) SortedActivePaymentAddresses() []string { infos := a.SortedActiveAddresses() addrs := make([]string, len(infos)) - for i, addr := range infos { - addrs[i] = addr.Address + for i, info := range infos { + addrs[i] = info.Address.EncodeAddress() } return addrs @@ -590,29 +587,31 @@ func (a *Account) ActivePaymentAddresses() map[string]struct{} { addrs := make(map[string]struct{}, len(infos)) for _, info := range infos { - addrs[info.Address] = struct{}{} + addrs[info.Address.EncodeAddress()] = struct{}{} } return addrs } // NewAddress returns a new payment address for an account. -func (a *Account) NewAddress() (string, error) { +func (a *Account) NewAddress() (btcutil.Address, error) { a.mtx.Lock() // Get current block's height and hash. bs, err := GetCurBlock() if err != nil { - return "", err + a.mtx.Unlock() + return nil, err } // Get next address from wallet. addr, err := a.NextChainedAddress(&bs) if err != nil { - return "", err + a.mtx.Unlock() + return nil, err } - // Write updated wallet to disk. + // Immediately write updated wallet to disk. a.dirty = true a.mtx.Unlock() if err = a.writeDirtyToDisk(); err != nil { @@ -620,7 +619,7 @@ func (a *Account) NewAddress() (string, error) { } // Mark this new address as belonging to this account. - MarkAddressForAccount(addr, a.Name()) + MarkAddressForAccount(addr.EncodeAddress(), a.Name()) // Request updates from btcd for new transactions sent to this address. a.ReqNewTxsForAddress(addr) @@ -630,15 +629,21 @@ func (a *Account) NewAddress() (string, error) { // ReqNewTxsForAddress sends a message to btcd to request tx updates // for addr for each new block that is added to the blockchain. -func (a *Account) ReqNewTxsForAddress(addr string) { - log.Debugf("Requesting notifications of TXs sending to address %v", addr) +func (a *Account) ReqNewTxsForAddress(addr btcutil.Address) { + // Only support P2PKH addresses currently. + apkh, ok := addr.(*btcutil.AddressPubKeyHash) + if !ok { + return + } + + log.Debugf("Requesting notifications of TXs sending to address %v", apkh) a.mtx.RLock() n := a.NewBlockTxJSONID a.mtx.RUnlock() cmd := btcws.NewNotifyNewTXsCmd(fmt.Sprintf("btcwallet(%d)", n), - []string{addr}) + []string{apkh.EncodeAddress()}) mcmd, err := cmd.MarshalJSON() if err != nil { log.Errorf("cannot request transaction notifications: %v", err) @@ -719,12 +724,12 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo } return false } - receiver, ok := v["receiver"].(string) + receiverStr, ok := v["receiver"].(string) if !ok { log.Error("Tx Handler: Unspecified receiver.") return false } - receiverHash, _, err := btcutil.DecodeAddress(receiver) + receiver, err := btcutil.DecodeAddr(receiverStr) if err != nil { log.Errorf("Tx Handler: receiver address can not be decoded: %v", err) return false @@ -810,7 +815,7 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo BlockIndex: blockIndex, BlockTime: blockTime, Amount: int64(amt), - ReceiverHash: receiverHash, + ReceiverHash: receiver.ScriptAddress(), } // For transactions originating from this wallet, the sent tx history should @@ -863,7 +868,7 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo } copy(u.Out.Hash[:], txID[:]) u.Out.Index = uint32(txOutIndex) - copy(u.AddrHash[:], receiverHash) + copy(u.AddrHash[:], receiver.ScriptAddress()) copy(u.BlockHash[:], blockHash[:]) a.UtxoStore.Lock() a.UtxoStore.s.Insert(u) diff --git a/accountstore.go b/accountstore.go index 348457c..600082f 100644 --- a/accountstore.go +++ b/accountstore.go @@ -20,6 +20,7 @@ import ( "bytes" "errors" "fmt" + "github.com/conformal/btcutil" "github.com/conformal/btcwallet/tx" "github.com/conformal/btcwallet/wallet" "github.com/conformal/btcwire" @@ -247,7 +248,7 @@ func (store *AccountStore) DumpKeys() ([]string, error) { // DumpWIFPrivateKey searches through all accounts for the bitcoin // payment address addr and returns the WIF-encdoded private key. -func (store *AccountStore) DumpWIFPrivateKey(addr string) (string, error) { +func (store *AccountStore) DumpWIFPrivateKey(addr btcutil.Address) (string, error) { store.Lock() defer store.Unlock() diff --git a/cmdmgr.go b/cmdmgr.go index 908535d..20698cd 100644 --- a/cmdmgr.go +++ b/cmdmgr.go @@ -233,7 +233,13 @@ func DumpPrivKey(frontend chan []byte, icmd btcjson.Cmd) { return } - switch key, err := accountstore.DumpWIFPrivateKey(cmd.Address); err { + addr, err := btcutil.DecodeAddr(cmd.Address) + if err != nil { + ReplyError(frontend, cmd.Id(), &btcjson.ErrInvalidAddressOrKey) + return + } + + switch key, err := accountstore.DumpWIFPrivateKey(addr); err { case nil: // Key was found. ReplySuccess(frontend, cmd.Id(), key) @@ -349,8 +355,24 @@ func GetAccount(frontend chan []byte, icmd btcjson.Cmd) { } // Is address valid? - _, net, err := btcutil.DecodeAddress(cmd.Address) - if err != nil || net != cfg.Net() { + addr, err := btcutil.DecodeAddr(cmd.Address) + if err != nil { + ReplyError(frontend, cmd.Id(), &btcjson.ErrInvalidAddressOrKey) + return + } + var net btcwire.BitcoinNet + switch a := addr.(type) { + case *btcutil.AddressPubKeyHash: + net = a.Net() + + case *btcutil.AddressScriptHash: + net = a.Net() + + default: + ReplyError(frontend, cmd.Id(), &btcjson.ErrInvalidAddressOrKey) + return + } + if net != cfg.Net() { ReplyError(frontend, cmd.Id(), &btcjson.ErrInvalidAddressOrKey) return } @@ -429,8 +451,13 @@ func GetAddressBalance(frontend chan []byte, icmd btcjson.Cmd) { } // Is address valid? - pkhash, net, err := btcutil.DecodeAddress(cmd.Address) - if err != nil || net != cfg.Net() { + addr, err := btcutil.DecodeAddr(cmd.Address) + if err != nil { + ReplyError(frontend, cmd.Id(), &btcjson.ErrInvalidAddressOrKey) + return + } + apkh, ok := addr.(*btcutil.AddressPubKeyHash) + if !ok || apkh.Net() != cfg.Net() { ReplyError(frontend, cmd.Id(), &btcjson.ErrInvalidAddressOrKey) return } @@ -455,7 +482,7 @@ func GetAddressBalance(frontend chan []byte, icmd btcjson.Cmd) { return } - bal := a.CalculateAddressBalance(pkhash, int(cmd.Minconf)) + bal := a.CalculateAddressBalance(apkh, int(cmd.Minconf)) ReplySuccess(frontend, cmd.Id(), bal) } @@ -712,19 +739,20 @@ func ListAddressTransactions(frontend chan []byte, icmd btcjson.Cmd) { return } - // Parse hash160s out of addresses. + // Decode addresses. pkHashMap := make(map[string]struct{}) - for _, addr := range cmd.Addresses { - pkHash, net, err := btcutil.DecodeAddress(addr) - if err != nil || net != cfg.Net() { - e := &btcjson.Error{ - Code: btcjson.ErrInvalidParams.Code, - Message: "invalid address", - } - ReplyError(frontend, cmd.Id(), e) + for _, addrStr := range cmd.Addresses { + addr, err := btcutil.DecodeAddr(addrStr) + if err != nil { + ReplyError(frontend, cmd.Id(), &btcjson.ErrInvalidAddressOrKey) return } - pkHashMap[string(pkHash)] = struct{}{} + apkh, ok := addr.(*btcutil.AddressPubKeyHash) + if !ok || apkh.Net() != cfg.Net() { + ReplyError(frontend, cmd.Id(), &btcjson.ErrInvalidAddressOrKey) + return + } + pkHashMap[string(addr.ScriptAddress())] = struct{}{} } txList, err := a.ListAddressTransactions(pkHashMap) @@ -863,7 +891,7 @@ func SendFrom(frontend chan []byte, icmd btcjson.Cmd) { // If a change address was added, mark wallet as dirty, sync to disk, // and request updates for change address. - if len(createdTx.changeAddr) != 0 { + if createdTx.changeAddr != nil { a.dirty = true if err := a.writeDirtyToDisk(); err != nil { log.Errorf("cannot write dirty wallet: %v", err) @@ -955,7 +983,7 @@ func SendMany(frontend chan []byte, icmd btcjson.Cmd) { // If a change address was added, mark wallet as dirty, sync to disk, // and request updates for change address. - if len(createdTx.changeAddr) != 0 { + if createdTx.changeAddr != nil { a.dirty = true if err := a.writeDirtyToDisk(); err != nil { log.Errorf("cannot write dirty wallet: %v", err) diff --git a/createtx.go b/createtx.go index abf1012..c2b4c8c 100644 --- a/createtx.go +++ b/createtx.go @@ -71,7 +71,7 @@ type CreatedTx struct { outputs []tx.Pair btcspent int64 fee int64 - changeAddr string + changeAddr *btcutil.AddressPubKeyHash changeUtxo *tx.Utxo } @@ -181,14 +181,14 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er outputs := make([]tx.Pair, 0, len(pairs)+1) // Add outputs to new tx. - for addr, amt := range pairs { - addr160, _, err := btcutil.DecodeAddress(addr) + for addrStr, amt := range pairs { + addr, err := btcutil.DecodeAddr(addrStr) if err != nil { return nil, fmt.Errorf("cannot decode address: %s", err) } - // Spend amt to addr160 - pkScript, err := btcscript.PayToPubKeyHashScript(addr160) + // Add output to spend amt to addr. + pkScript, err := btcscript.PayToAddrScript(addr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } @@ -198,7 +198,7 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er // Create amount, address pair and add to outputs. out := tx.Pair{ Amount: amt, - PubkeyHash: addr160, + PubkeyHash: addr.ScriptAddress(), } outputs = append(outputs, out) } @@ -216,8 +216,7 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er // These are nil/zeroed until a change address is needed, and reused // again in case a change utxo has already been chosen. - var changeAddrHash []byte - var changeAddr string + var changeAddr *btcutil.AddressPubKeyHash var btcspent int64 var selectedInputs []*tx.Utxo @@ -245,23 +244,18 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er // Create a new address to spend leftover outputs to. // Get a new change address if one has not already been found. - if changeAddrHash == nil { + if changeAddr == nil { changeAddr, err = a.NextChainedAddress(&bs) if err != nil { return nil, fmt.Errorf("failed to get next address: %s", err) } // Mark change address as belonging to this account. - MarkAddressForAccount(changeAddr, a.Name()) - - changeAddrHash, _, err = btcutil.DecodeAddress(changeAddr) - if err != nil { - return nil, fmt.Errorf("cannot decode new address: %s", err) - } + MarkAddressForAccount(changeAddr.EncodeAddress(), a.Name()) } // Spend change. - pkScript, err := btcscript.PayToPubKeyHashScript(changeAddrHash) + pkScript, err := btcscript.PayToAddrScript(changeAddr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } @@ -278,7 +272,7 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er Height: -1, Subscript: pkScript, } - copy(changeUtxo.AddrHash[:], changeAddrHash) + copy(changeUtxo.AddrHash[:], changeAddr.ScriptAddress()) } // Selected unspent outputs become new transaction's inputs. @@ -286,18 +280,17 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er msgtx.AddTxIn(btcwire.NewTxIn((*btcwire.OutPoint)(&ip.Out), nil)) } for i, ip := range inputs { - addrstr, err := btcutil.EncodeAddress(ip.AddrHash[:], + // Error is ignored as the length and network checks can never fail + // for these inputs. + addr, _ := btcutil.NewAddressPubKeyHash(ip.AddrHash[:], a.Wallet.Net()) - if err != nil { - return nil, err - } - privkey, err := a.AddressKey(addrstr) + privkey, err := a.AddressKey(addr) if err == wallet.ErrWalletLocked { return nil, wallet.ErrWalletLocked } else if err != nil { return nil, fmt.Errorf("cannot get address key: %v", err) } - ai, err := a.AddressInfo(addrstr) + ai, err := a.AddressInfo(addr) if err != nil { return nil, fmt.Errorf("cannot get address info: %v", err) } @@ -326,7 +319,7 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er // Add change to outputs. out := tx.Pair{ Amount: int64(change), - PubkeyHash: changeAddrHash, + PubkeyHash: changeAddr.ScriptAddress(), Change: true, } outputs = append(outputs, out) diff --git a/createtx_test.go b/createtx_test.go index f7700cb..23f1e49 100644 --- a/createtx_test.go +++ b/createtx_test.go @@ -89,18 +89,13 @@ func TestFakeTxs(t *testing.T) { t.Errorf("Cannot get next address: %s", err) return } - addr160, _, err := btcutil.DecodeAddress(addr) - if err != nil { - t.Errorf("Cannot decode address: %s", err) - return - } - copy(utxo.AddrHash[:], addr160) + copy(utxo.AddrHash[:], addr.ScriptAddress()) ophash := (btcwire.ShaHash)([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}) out := btcwire.NewOutPoint(&ophash, 0) utxo.Out = tx.OutPoint(*out) - ss, err := btcscript.PayToPubKeyHashScript(addr160) + ss, err := btcscript.PayToAddrScript(addr) if err != nil { t.Errorf("Could not create utxo PkScript: %s", err) return diff --git a/tx/tx.go b/tx/tx.go index 9b58aaa..1f5ac3e 100644 --- a/tx/tx.go +++ b/tx/tx.go @@ -1004,9 +1004,10 @@ func (tx *RecvTx) WriteTo(w io.Writer) (n int64, err error) { func (tx *RecvTx) TxInfo(account string, curheight int32, net btcwire.BitcoinNet) map[string]interface{} { - address, err := btcutil.EncodeAddress(tx.ReceiverHash, net) - if err != nil { - address = "Unknown" + address := "Unknown" + addr, err := btcutil.NewAddressPubKeyHash(tx.ReceiverHash, net) + if err == nil { + address = addr.String() } txInfo := map[string]interface{}{ @@ -1137,10 +1138,10 @@ func (tx *SendTx) TxInfo(account string, curheight int32, blockHashStr := blockHash.String() for i, pair := range tx.Receivers { - // EncodeAddress cannot error with these inputs. - address, err := btcutil.EncodeAddress(pair.PubkeyHash, net) - if err != nil { - address = "Unknown" + address := "Unknown" + addr, err := btcutil.NewAddressPubKeyHash(pair.PubkeyHash, net) + if err == nil { + address = addr.String() } info := map[string]interface{}{ "account": account, diff --git a/wallet/wallet.go b/wallet/wallet.go index 862b0b1..6df605a 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -32,7 +32,7 @@ import ( "github.com/conformal/btcec" "github.com/conformal/btcutil" "github.com/conformal/btcwire" - "hash" + "github.com/davecgh/go-spew/spew" "io" "math/big" "sync" @@ -111,27 +111,6 @@ func binaryWrite(w io.Writer, order binary.ByteOrder, data interface{}) (n int64 return int64(written), err } -// Calculate the hash of hasher over buf. -func calcHash(buf []byte, hasher hash.Hash) []byte { - hasher.Write(buf) - return hasher.Sum(nil) -} - -// calculate hash160 which is ripemd160(sha256(data)) -func calcHash160(buf []byte) []byte { - return calcHash(calcHash(buf, sha256.New()), ripemd160.New()) -} - -// calculate hash256 which is sha256(sha256(data)) -func calcHash256(buf []byte) []byte { - return calcHash(calcHash(buf, sha256.New()), sha256.New()) -} - -// calculate sha512(data) -func calcSha512(buf []byte) []byte { - return calcHash(buf, sha512.New()) -} - // pubkeyFromPrivkey creates an encoded pubkey based on a // 32-byte privkey. The returned pubkey is 33 bytes if compressed, // or 65 bytes if uncompressed. @@ -154,11 +133,11 @@ func keyOneIter(passphrase, salt []byte, memReqts uint64) []byte { lutbl := make([]byte, memReqts) // Seed for lookup table - seed := calcSha512(saltedpass) - copy(lutbl[:sha512.Size], seed) + seed := sha512.Sum512(saltedpass) + copy(lutbl[:sha512.Size], seed[:]) for nByte := 0; nByte < (int(memReqts) - sha512.Size); nByte += sha512.Size { - hash := calcSha512(lutbl[nByte : nByte+sha512.Size]) + hash := sha512.Sum512(lutbl[nByte : nByte+sha512.Size]) copy(lutbl[nByte+sha512.Size:nByte+2*sha512.Size], hash[:]) } @@ -180,7 +159,7 @@ func keyOneIter(passphrase, salt []byte, memReqts uint64) []byte { } // Save new hash to x - hash := calcSha512(x) + hash := sha512.Sum512(x) copy(x, hash[:]) } @@ -230,7 +209,7 @@ func ChainedPrivKey(privkey, pubkey, chaincode []byte) ([]byte, error) { } xorbytes := make([]byte, 32) - chainMod := calcHash256(pubkey) + chainMod := sha256.Sum256(pubkey) for i := range xorbytes { xorbytes[i] = chainMod[i] ^ chaincode[i] } @@ -450,7 +429,6 @@ func (v *varEntries) ReadFrom(r io.Reader) (n int64, err error) { } } -type addressHashKey string type transactionHashKey string type comment []byte @@ -472,8 +450,8 @@ type Wallet struct { // root address and the appended entries. recent recentBlocks - addrMap map[addressHashKey]*btcAddress - addrCommentMap map[addressHashKey]comment + addrMap map[btcutil.AddressPubKeyHash]*btcAddress + addrCommentMap map[btcutil.AddressPubKeyHash]comment txCommentMap map[transactionHashKey]comment // These are not serialized. @@ -481,7 +459,7 @@ type Wallet struct { sync.Mutex key []byte } - chainIdxMap map[int64]addressHashKey + chainIdxMap map[int64]*btcutil.AddressPubKeyHash importedAddrs []*btcAddress lastChainIdx int64 } @@ -490,9 +468,7 @@ type Wallet struct { // desc's binary representation must not exceed 32 and 256 bytes, // respectively. All address private keys are encrypted with passphrase. // The wallet is returned unlocked. -func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet, - createdAt *BlockStamp) (*Wallet, error) { - +func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet, createdAt *BlockStamp) (*Wallet, error) { // Check sizes of inputs. if len([]byte(name)) > 32 { return nil, errors.New("name exceeds 32 byte maximum size") @@ -501,6 +477,11 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet, return nil, errors.New("desc exceeds 256 byte maximum size") } + // Check for a valid network. + if !(net == btcwire.MainNet || net == btcwire.TestNet3) { + return nil, errors.New("wallets must use mainnet or testnet3") + } + // Randomly-generate rootkey and chaincode. rootkey, chaincode := make([]byte, 32), make([]byte, 32) if _, err := rand.Read(rootkey); err != nil { @@ -550,18 +531,18 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet, &createdAt.Hash, }, }, - addrMap: make(map[addressHashKey]*btcAddress), - addrCommentMap: make(map[addressHashKey]comment), + addrMap: make(map[btcutil.AddressPubKeyHash]*btcAddress), + addrCommentMap: make(map[btcutil.AddressPubKeyHash]comment), txCommentMap: make(map[transactionHashKey]comment), - chainIdxMap: make(map[int64]addressHashKey), + chainIdxMap: make(map[int64]*btcutil.AddressPubKeyHash), lastChainIdx: rootKeyChainIdx, } copy(w.name[:], []byte(name)) copy(w.desc[:], []byte(desc)) // Add root address to maps. - w.addrMap[addressHashKey(w.keyGenerator.pubKeyHash[:])] = &w.keyGenerator - w.chainIdxMap[rootKeyChainIdx] = addressHashKey(w.keyGenerator.pubKeyHash[:]) + w.addrMap[*w.keyGenerator.address(net)] = &w.keyGenerator + w.chainIdxMap[rootKeyChainIdx] = w.keyGenerator.address(net) // Fill keypool. if err := w.extendKeypool(nKeypoolIncrement, aeskey, createdAt); err != nil { @@ -589,9 +570,9 @@ func (w *Wallet) Name() string { func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) { var read int64 - w.addrMap = make(map[addressHashKey]*btcAddress) - w.addrCommentMap = make(map[addressHashKey]comment) - w.chainIdxMap = make(map[int64]addressHashKey) + w.addrMap = make(map[btcutil.AddressPubKeyHash]*btcAddress) + w.addrCommentMap = make(map[btcutil.AddressPubKeyHash]comment) + w.chainIdxMap = make(map[int64]*btcutil.AddressPubKeyHash) w.txCommentMap = make(map[transactionHashKey]comment) var id [8]byte @@ -640,29 +621,29 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) { } // Add root address to address map. - rootAddrKey := addressHashKey(w.keyGenerator.pubKeyHash[:]) - w.addrMap[rootAddrKey] = &w.keyGenerator - w.chainIdxMap[rootKeyChainIdx] = rootAddrKey + rootAddr := w.keyGenerator.address(w.net) + w.addrMap[*rootAddr] = &w.keyGenerator + w.chainIdxMap[rootKeyChainIdx] = rootAddr // Fill unserializied fields. wts := ([]io.WriterTo)(appendedEntries) for _, wt := range wts { switch e := wt.(type) { case *addrEntry: - addrKey := addressHashKey(e.pubKeyHash160[:]) - w.addrMap[addrKey] = &e.addr + addr := e.addr.address(w.net) + w.addrMap[*addr] = &e.addr if e.addr.chainIndex == importedKeyChainIdx { w.importedAddrs = append(w.importedAddrs, &e.addr) } else { - w.chainIdxMap[e.addr.chainIndex] = addrKey + w.chainIdxMap[e.addr.chainIndex] = addr if w.lastChainIdx < e.addr.chainIndex { w.lastChainIdx = e.addr.chainIndex } } case *addrCommentEntry: - addrKey := addressHashKey(e.pubKeyHash160[:]) - w.addrCommentMap[addrKey] = comment(e.comment) + addr := e.address(w.net) + w.addrCommentMap[*addr] = comment(e.comment) case *txCommentEntry: txKey := transactionHashKey(e.txHash[:]) @@ -682,26 +663,26 @@ func (w *Wallet) WriteTo(wtr io.Writer) (n int64, err error) { var wts []io.WriterTo var chainedAddrs = make([]io.WriterTo, len(w.chainIdxMap)-1) var importedAddrs []io.WriterTo - for hash, addr := range w.addrMap { + for addr, btcAddr := range w.addrMap { e := &addrEntry{ - addr: *addr, + addr: *btcAddr, } - copy(e.pubKeyHash160[:], []byte(hash)) - if addr.chainIndex >= 0 { + copy(e.pubKeyHash160[:], addr.ScriptAddress()) + if btcAddr.chainIndex >= 0 { // Chained addresses are sorted. This is // kind of nice but probably isn't necessary. - chainedAddrs[addr.chainIndex] = e - } else if addr.chainIndex == importedKeyChainIdx { + chainedAddrs[btcAddr.chainIndex] = e + } else if btcAddr.chainIndex == importedKeyChainIdx { // No order for imported addresses. importedAddrs = append(importedAddrs, e) } } wts = append(chainedAddrs, importedAddrs...) - for hash, comment := range w.addrCommentMap { + for addr, comment := range w.addrCommentMap { e := &addrCommentEntry{ comment: []byte(comment), } - copy(e.pubKeyHash160[:], []byte(hash)) + copy(e.pubKeyHash160[:], addr.ScriptAddress()) wts = append(wts, e) } for hash, comment := range w.txCommentMap { @@ -814,7 +795,7 @@ func (w *Wallet) Version() (string, int) { // NextChainedAddress attempts to get the next chained address, // refilling the keypool if necessary. -func (w *Wallet) NextChainedAddress(bs *BlockStamp) (string, error) { +func (w *Wallet) NextChainedAddress(bs *BlockStamp) (*btcutil.AddressPubKeyHash, error) { // Attempt to get address hash of next chained address. next160, ok := w.chainIdxMap[w.highestUsed+1] if !ok { @@ -823,61 +804,50 @@ func (w *Wallet) NextChainedAddress(bs *BlockStamp) (string, error) { w.secret.Lock() if len(w.secret.key) != 32 { w.secret.Unlock() - return "", ErrWalletLocked + return nil, ErrWalletLocked } copy(aeskey, w.secret.key) w.secret.Unlock() err := w.extendKeypool(nKeypoolIncrement, aeskey, bs) if err != nil { - return "", err + return nil, err } next160, ok = w.chainIdxMap[w.highestUsed+1] if !ok { - return "", errors.New("chain index map inproperly updated") + return nil, errors.New("chain index map inproperly updated") } } // Look up address. - addr, ok := w.addrMap[next160] + addr, ok := w.addrMap[*next160] if !ok { - return "", errors.New("cannot find generated address") + return nil, errors.New("cannot find generated address") } w.highestUsed++ // Create and return payment address for address hash. - return addr.paymentAddress(w.net) + return addr.address(w.net), nil } // LastChainedAddress returns the most recently requested chained // address from calling NextChainedAddress, or the root address if // no chained addresses have been requested. -func (w *Wallet) LastChainedAddress() (string, error) { - // Lookup pubkey hash for last used chained address. - pkHash, ok := w.chainIdxMap[w.highestUsed] - if !ok { - return "", errors.New("chain index references unknown address") - } - - // Lookup address with this pubkey hash. - addr, ok := w.addrMap[pkHash] - if !ok { - return "", errors.New("cannot find address by pubkey hash") - } - - // Create and return payment address from serialized pubkey. - return addr.paymentAddress(w.net) +func (w *Wallet) LastChainedAddress() btcutil.Address { + return w.chainIdxMap[w.highestUsed] } // extendKeypool grows the keypool by n addresses. func (w *Wallet) extendKeypool(n uint, aeskey []byte, bs *BlockStamp) error { // Get last chained address. New chained addresses will be // chained off of this address's chaincode and private key. - addrKey := w.chainIdxMap[w.lastChainIdx] - addr, ok := w.addrMap[addrKey] + a := w.chainIdxMap[w.lastChainIdx] + addr, ok := w.addrMap[*a] if !ok { + spew.Dump(a) + spew.Dump(w.addrMap) return errors.New("expected last chained address not found") } privkey, err := addr.unlock(aeskey) @@ -903,10 +873,10 @@ func (w *Wallet) extendKeypool(n uint, aeskey []byte, bs *BlockStamp) error { if err = newaddr.encrypt(aeskey); err != nil { return err } - addrKey := addressHashKey(newaddr.pubKeyHash[:]) - w.addrMap[addrKey] = newaddr + a := newaddr.address(w.net) + w.addrMap[*a] = newaddr newaddr.chainIndex = addr.chainIndex + 1 - w.chainIdxMap[newaddr.chainIndex] = addrKey + w.chainIdxMap[newaddr.chainIndex] = a w.lastChainIdx++ // armory does this.. but all the chaincodes are equal so why // not use the root's? @@ -917,40 +887,22 @@ func (w *Wallet) extendKeypool(n uint, aeskey []byte, bs *BlockStamp) error { return nil } -// addrHashForAddress decodes and returns the address hash for a -// payment address string, performing some basic sanity checking that it -// matches the Bitcoin network used by the wallet. -func (w *Wallet) addrHashForAddress(addr string) ([]byte, error) { - addr160, net, err := btcutil.DecodeAddress(addr) - if err != nil { - return nil, err - } - - // Return error if address is for the wrong Bitcoin network. - switch { - case net == btcutil.MainNetAddr && w.net != btcwire.MainNet: - fallthrough - case net == btcutil.TestNetAddr && w.net != btcwire.TestNet: - return nil, ErrNetworkMismatch - } - - return addr160, nil -} - // AddressKey returns the private key for a payment address stored // in a wallet. This can fail if the payment address is for a different // Bitcoin network than what this wallet uses, the address is not // contained in the wallet, the address does not include a public and // private key, or if the wallet is locked. -func (w *Wallet) AddressKey(addr string) (key *ecdsa.PrivateKey, err error) { - // Get address hash for payment address string. - addr160, err := w.addrHashForAddress(addr) - if err != nil { - return nil, err +func (w *Wallet) AddressKey(a btcutil.Address) (key *ecdsa.PrivateKey, err error) { + // 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. - btcaddr, ok := w.addrMap[addressHashKey(addr160)] + btcaddr, ok := w.addrMap[*addr] if !ok { return nil, ErrAddressNotFound } @@ -996,17 +948,19 @@ func (w *Wallet) AddressKey(addr string) (key *ecdsa.PrivateKey, err error) { } // AddressInfo returns an AddressInfo structure for an address in a wallet. -func (w *Wallet) AddressInfo(addr string) (*AddressInfo, error) { - // Get address hash for addr. - addr160, err := w.addrHashForAddress(addr) - if err != nil { - return nil, err +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. + addr, ok := a.(*btcutil.AddressPubKeyHash) + if !ok { + return nil, errors.New("unsupported address") } // Look up address by address hash. - btcaddr, ok := w.addrMap[addressHashKey(addr160)] + btcaddr, ok := w.addrMap[*addr] if !ok { - return nil, errors.New("address not in wallet") + return nil, ErrAddressNotFound } return btcaddr.info(w.net) @@ -1116,11 +1070,8 @@ func (w *Wallet) SetBetterEarliestBlockHeight(height int32) { // ImportPrivateKey creates a new encrypted btcAddress with a // user-provided private key and adds it to the wallet. If the // import is successful, the payment address string is returned. -func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, - bs *BlockStamp) (string, error) { - - // The wallet's secret will be zeroed on lock, so make a local - // copy. +func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, bs *BlockStamp) (string, error) { + // The wallet's secret will be zeroed on lock, so make a local copy. w.secret.Lock() if len(w.secret.key) != 32 { w.secret.Unlock() @@ -1131,32 +1082,28 @@ func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, w.secret.Unlock() // Create new address with this private key. - addr, err := newBtcAddress(privkey, nil, bs, compressed) + btcaddr, err := newBtcAddress(privkey, nil, bs, compressed) if err != nil { return "", err } - addr.chainIndex = importedKeyChainIdx + btcaddr.chainIndex = importedKeyChainIdx // Encrypt imported address with the derived AES key. - if err = addr.encrypt(localSecret); err != nil { - return "", err - } - - // Create payment address string. If this fails, return an error - // before adding the address to the wallet. - addr160 := addr.pubKeyHash[:] - addrstr, err := btcutil.EncodeAddress(addr160, w.Net()) - if err != nil { + if err = btcaddr.encrypt(localSecret); err != nil { return "", err } // Add address to wallet's bookkeeping structures. Adding to // the map will result in the imported address being serialized // on the next WriteTo call. - w.addrMap[addressHashKey(addr160)] = addr - w.importedAddrs = append(w.importedAddrs, addr) + w.addrMap[*btcaddr.address(w.net)] = btcaddr + w.importedAddrs = append(w.importedAddrs, btcaddr) - return addrstr, nil + // Create and return encoded payment address string. Error is + // ignored as the length of the pubkey hash and net will always + // be valid. + addr, _ := btcutil.NewAddressPubKeyHash(btcaddr.pubKeyHash[:], w.Net()) + return addr.String(), nil } // CreateDate returns the Unix time of the wallet creation time. This @@ -1169,7 +1116,7 @@ func (w *Wallet) CreateDate() int64 { // AddressInfo holds information regarding an address needed to manage // a complete wallet. type AddressInfo struct { - Address string + btcutil.Address AddrHash string Compressed bool FirstBlock int32 @@ -1185,12 +1132,8 @@ func (w *Wallet) SortedActiveAddresses() []*AddressInfo { addrs := make([]*AddressInfo, 0, w.highestUsed+int64(len(w.importedAddrs))+1) for i := int64(rootKeyChainIdx); i <= w.highestUsed; i++ { - addr160, ok := w.chainIdxMap[i] - if !ok { - return addrs - } - addr := w.addrMap[addr160] - info, err := addr.info(w.Net()) + a := w.chainIdxMap[i] + info, err := w.addrMap[*a].info(w.Net()) if err == nil { addrs = append(addrs, info) } @@ -1207,15 +1150,11 @@ 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[string]*AddressInfo { - addrs := make(map[string]*AddressInfo) +func (w *Wallet) ActiveAddresses() map[btcutil.Address]*AddressInfo { + addrs := make(map[btcutil.Address]*AddressInfo) for i := int64(rootKeyChainIdx); i <= w.highestUsed; i++ { - addr160, ok := w.chainIdxMap[i] - if !ok { - return addrs - } - addr := w.addrMap[addr160] - info, err := addr.info(w.Net()) + a := w.chainIdxMap[i] + info, err := w.addrMap[*a].info(w.Net()) if err == nil { addrs[info.Address] = info } @@ -1676,7 +1615,7 @@ func newBtcAddress(privkey, iv []byte, bs *BlockStamp, compressed bool) (addr *b addr.privKeyCT.key = privkey copy(addr.initVector[:], iv) addr.pubKey = pubkeyFromPrivkey(privkey, compressed) - copy(addr.pubKeyHash[:], calcHash160(addr.pubKey)) + copy(addr.pubKeyHash[:], btcutil.Hash160(addr.pubKey)) return addr, nil } @@ -1932,19 +1871,18 @@ func (a *btcAddress) changeEncryptionKey(oldkey, newkey []byte) error { return errors.New("unimplemented") } -// paymentAddress returns a human readable payment address string for -// an address. -func (a *btcAddress) paymentAddress(net btcwire.BitcoinNet) (string, error) { - return btcutil.EncodeAddress(a.pubKeyHash[:], net) +// address returns a btcutil.AddressPubKeyHash for a btcAddress. +func (a *btcAddress) address(net btcwire.BitcoinNet) *btcutil.AddressPubKeyHash { + // error is not returned because the hash will always be 20 + // bytes, and net is assumed to be valid. + addr, _ := btcutil.NewAddressPubKeyHash(a.pubKeyHash[:], net) + return addr } // info returns information about a btcAddress stored in a AddressInfo // struct. func (a *btcAddress) info(net btcwire.BitcoinNet) (*AddressInfo, error) { - address, err := a.paymentAddress(net) - if err != nil { - return nil, err - } + address := a.address(net) return &AddressInfo{ Address: address, @@ -2124,6 +2062,13 @@ type addrCommentEntry struct { comment []byte } +func (e *addrCommentEntry) address(net btcwire.BitcoinNet) *btcutil.AddressPubKeyHash { + // error is not returned because the hash will always be 20 + // bytes, and net is assumed to be valid. + addr, _ := btcutil.NewAddressPubKeyHash(e.pubKeyHash160[:], net) + return addr +} + func (e *addrCommentEntry) WriteTo(w io.Writer) (n int64, err error) { var written int64 diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go index f5c9639..c0cc021 100644 --- a/wallet/wallet_test.go +++ b/wallet/wallet_test.go @@ -90,6 +90,7 @@ func TestWalletCreationSerialization(t *testing.T) { []byte("banana"), btcwire.MainNet, createdAt) if err != nil { t.Error("Error creating new wallet: " + err.Error()) + return } file, err := os.Create("newwallet.bin")