Add private key import and export support.

This adds the necessary bits for handling importing addresses for the
wallet file format, as well as implementing the importprivkey and
dumpprivkey RPC requests.

Initial code by dhill.
This commit is contained in:
Josh Rickmar 2013-11-19 20:18:11 -05:00
parent 0bd877237f
commit 00fe439670
4 changed files with 417 additions and 74 deletions

View file

@ -18,6 +18,7 @@ package main
import (
"bytes"
"errors"
"fmt"
"github.com/conformal/btcjson"
"github.com/conformal/btcutil"
@ -75,8 +76,8 @@ func NewAccountStore() *AccountStore {
// TODO(jrick): This must also roll back the UTXO and TX stores, and notify
// all wallets of new account balances.
func (s *AccountStore) Rollback(height int32, hash *btcwire.ShaHash) {
for _, w := range s.m {
w.Rollback(height, hash)
for _, a := range s.m {
a.Rollback(height, hash)
}
}
@ -127,6 +128,94 @@ func (a *Account) CalculateBalance(confirms int) float64 {
return float64(bal) / float64(btcutil.SatoshiPerBitcoin)
}
// DumpPrivKeys returns the WIF-encoded private keys for all addresses
// non-watching addresses in a wallets.
func (a *Account) DumpPrivKeys() ([]string, error) {
a.mtx.RLock()
defer a.mtx.RUnlock()
// Iterate over each active address, appending the private
// key to privkeys.
var privkeys []string
for _, addr := range a.GetActiveAddresses() {
key, err := a.GetAddressKey(addr.Address)
if err != nil {
return nil, err
}
encKey, err := btcutil.EncodePrivateKey(key.D.Bytes(),
a.Net(), addr.Compressed)
if err != nil {
return nil, err
}
privkeys = append(privkeys, encKey)
}
return privkeys, nil
}
// DumpWIFPrivateKey returns the WIF encoded private key for a
// single wallet address.
func (a *Account) DumpWIFPrivateKey(address string) (string, error) {
a.mtx.RLock()
defer a.mtx.RUnlock()
// Get private key from wallet if it exists.
key, err := a.GetAddressKey(address)
if err != nil {
return "", err
}
// Get address info. This is needed to determine whether
// the pubkey is compressed or not.
info, err := a.GetAddressInfo(address)
if err != nil {
return "", err
}
// Return WIF-encoding of the private key.
return btcutil.EncodePrivateKey(key.D.Bytes(), a.Net(), info.Compressed)
}
// ImportWIFPrivateKey takes a WIF encoded private key and adds it to the
// wallet. If the import is successful, the payment address string is
// returned.
func (a *Account) ImportWIFPrivateKey(wif, label string,
bs *wallet.BlockStamp) (string, error) {
// Decode WIF private key and perform sanity checking.
privkey, net, compressed, err := btcutil.DecodePrivateKey(wif)
if err != nil {
return "", err
}
if net != a.Net() {
return "", errors.New("wrong network")
}
// Attempt to import private key into wallet.
a.mtx.Lock()
addr, err := a.ImportPrivateKey(privkey, compressed, bs)
if err != nil {
a.mtx.Unlock()
return "", err
}
// Immediately write dirty wallet to disk.
//
// TODO(jrick): change writeDirtyToDisk to not grab the writer lock.
// Don't want to let another goroutine waiting on the mutex to grab
// the mutex before it is written to disk.
a.dirty = true
a.mtx.Unlock()
if err := a.writeDirtyToDisk(); err != nil {
log.Errorf("cannot write dirty wallet: %v", err)
}
log.Infof("Imported payment address %v", addr)
// Return the payment address string of the imported private key.
return addr, nil
}
// Track requests btcd to send notifications of new transactions for
// each address stored in a wallet and sets up a new reply handler for
// these notifications.
@ -158,18 +247,18 @@ func (a *Account) Track() {
a.UtxoStore.RUnlock()
}
// RescanToBestBlock requests btcd to rescan the blockchain for new
// transactions to all wallet addresses. This is needed for making
// btcwallet catch up to a long-running btcd process, as otherwise
// RescanActiveAddresse requests btcd to rescan the blockchain for new
// transactions to all active wallet addresses. This is needed for
// catching btcwallet up to a long-running btcd process, as otherwise
// it would have missed notifications as blocks are attached to the
// main chain.
func (a *Account) RescanToBestBlock() {
func (a *Account) RescanActiveAddresses() {
// Determine the block to begin the rescan from.
beginBlock := int32(0)
if a.fullRescan {
// Need to perform a complete rescan since the wallet creation
// block.
beginBlock = a.CreatedAt()
beginBlock = a.EarliestBlockHeight()
log.Debugf("Rescanning account '%v' for new transactions since block height %v",
a.name, beginBlock)
} else {
@ -184,9 +273,17 @@ func (a *Account) RescanToBestBlock() {
beginBlock = bs.Height + 1
}
// Rescan active addresses starting at the determined block height.
a.RescanAddresses(beginBlock, a.ActivePaymentAddresses())
}
// RescanAddresses requests btcd to rescan a set of addresses. This
// is needed when, for example, importing private key(s), where btcwallet
// is synced with btcd for all but several address.
func (a *Account) RescanAddresses(beginBlock int32, addrs map[string]struct{}) {
n := <-NewJSONID
cmd, err := btcws.NewRescanCmd(fmt.Sprintf("btcwallet(%v)", n),
beginBlock, a.ActivePaymentAddresses())
beginBlock, addrs)
if err != nil {
log.Errorf("cannot create rescan request: %v", err)
return

153
cmdmgr.go
View file

@ -39,9 +39,12 @@ type cmdHandler func(chan []byte, btcjson.Cmd)
var rpcHandlers = map[string]cmdHandler{
// Standard bitcoind methods
"dumpprivkey": DumpPrivKey,
"dumpwallet": DumpWallet,
"getaddressesbyaccount": GetAddressesByAccount,
"getbalance": GetBalance,
"getnewaddress": GetNewAddress,
"importprivkey": ImportPrivKey,
"listaccounts": ListAccounts,
"sendfrom": SendFrom,
"sendmany": SendMany,
@ -59,10 +62,11 @@ var wsHandlers = map[string]cmdHandler{
"walletislocked": WalletIsLocked,
}
// ProcessFrontendMsg checks the message sent from a frontend. If the
// message method is one that must be handled by btcwallet, the request
// is processed here. Otherwise, the message is sent to btcd.
func ProcessFrontendMsg(frontend chan []byte, msg []byte, ws bool) {
// ProcessRequest checks the requests sent from a frontend. If the
// request method is one that must be handled by btcwallet, the
// request is processed here. Otherwise, the request is sent to btcd
// and btcd's reply is routed back to the frontend.
func ProcessRequest(frontend chan []byte, msg []byte, ws bool) {
// Parse marshaled command and check
cmd, err := btcjson.ParseMarshaledCmd(msg)
if err != nil {
@ -131,7 +135,7 @@ func RouteID(origID, routeID interface{}) string {
return fmt.Sprintf("btcwallet(%v)-%v", routeID, origID)
}
// ReplyError creates and marshalls a btcjson.Reply with the error e,
// ReplyError creates and marshals a btcjson.Reply with the error e,
// sending the reply to a frontend reply channel.
func ReplyError(frontend chan []byte, id interface{}, e *btcjson.Error) {
// Create a Reply with a non-nil error to marshal.
@ -146,7 +150,7 @@ func ReplyError(frontend chan []byte, id interface{}, e *btcjson.Error) {
}
}
// ReplySuccess creates and marshalls a btcjson.Reply with the result r,
// ReplySuccess creates and marshals a btcjson.Reply with the result r,
// sending the reply to a frontend reply channel.
func ReplySuccess(frontend chan []byte, id interface{}, result interface{}) {
// Create a Reply with a non-nil result to marshal.
@ -161,6 +165,90 @@ func ReplySuccess(frontend chan []byte, id interface{}, result interface{}) {
}
}
// DumpPrivKey replies to a dumpprivkey request with the private
// key for a single address, or an appropiate error if the wallet
// is locked.
func DumpPrivKey(frontend chan []byte, icmd btcjson.Cmd) {
// Type assert icmd to access parameters.
cmd, ok := icmd.(*btcjson.DumpPrivKeyCmd)
if !ok {
ReplyError(frontend, icmd.Id(), &btcjson.ErrInternal)
return
}
// Iterate over all accounts, returning the key if it is found
// in any wallet.
for _, a := range accounts.m {
switch key, err := a.DumpWIFPrivateKey(cmd.Address); err {
case wallet.ErrAddressNotFound:
// Move on to the next account.
continue
case wallet.ErrWalletLocked:
// Address was found, but the private key isn't
// accessible.
ReplyError(frontend, cmd.Id(), &btcjson.ErrWalletUnlockNeeded)
return
case nil:
// Key was found.
ReplySuccess(frontend, cmd.Id(), key)
return
default: // all other non-nil errors
e := &btcjson.Error{
Code: btcjson.ErrWallet.Code,
Message: err.Error(),
}
ReplyError(frontend, cmd.Id(), e)
return
}
}
// If this is reached, all accounts have been checked, but none
// have they address.
e := &btcjson.Error{
Code: btcjson.ErrWallet.Code,
Message: "Address does not refer to a key",
}
ReplyError(frontend, cmd.Id(), e)
}
// DumpWallet replies to a dumpwallet request with all private keys
// in a wallet, or an appropiate error if the wallet is locked.
func DumpWallet(frontend chan []byte, icmd btcjson.Cmd) {
// Type assert icmd to access parameters.
cmd, ok := icmd.(*btcjson.DumpWalletCmd)
if !ok {
ReplyError(frontend, icmd.Id(), &btcjson.ErrInternal)
return
}
// Iterate over all accounts, appending the private keys
// for each.
var keys []string
for _, a := range accounts.m {
switch walletKeys, err := a.DumpPrivKeys(); err {
case wallet.ErrWalletLocked:
ReplyError(frontend, cmd.Id(), &btcjson.ErrWalletUnlockNeeded)
return
case nil:
keys = append(keys, walletKeys...)
default: // any other non-nil error
e := &btcjson.Error{
Code: btcjson.ErrWallet.Code,
Message: err.Error(),
}
ReplyError(frontend, cmd.Id(), e)
}
}
// Reply with sorted WIF encoded private keys
ReplySuccess(frontend, cmd.Id(), keys)
}
// GetAddressesByAccount replies to a getaddressesbyaccount request with
// all addresses for an account, or an error if the requested account does
// not exist.
@ -213,6 +301,59 @@ func GetBalances(frontend chan []byte, cmd btcjson.Cmd) {
NotifyBalances(frontend)
}
// ImportPrivKey replies to an importprivkey request by parsing
// a WIF-encoded private key and adding it to an account.
func ImportPrivKey(frontend chan []byte, icmd btcjson.Cmd) {
// Type assert icmd to access parameters.
cmd, ok := icmd.(*btcjson.ImportPrivKeyCmd)
if !ok {
ReplyError(frontend, icmd.Id(), &btcjson.ErrInternal)
return
}
// Check that the account specified in the requests exists.
// Yes, Label is the account name.
a, ok := accounts.m[cmd.Label]
if !ok {
ReplyError(frontend, cmd.Id(),
&btcjson.ErrWalletInvalidAccountName)
return
}
// Create a blockstamp for when this address first appeared.
// Because the importprivatekey RPC call does not allow
// specifying when the address first appeared, we must make
// a worst case guess.
bs := &wallet.BlockStamp{Height: 0}
// Attempt importing the private key, replying with an appropiate
// error if the import was unsuccesful.
addr, err := a.ImportWIFPrivateKey(cmd.PrivKey, cmd.Label, bs)
switch {
case err == wallet.ErrWalletLocked:
ReplyError(frontend, cmd.Id(), &btcjson.ErrWalletUnlockNeeded)
return
case err != nil:
e := &btcjson.Error{
Code: btcjson.ErrWallet.Code,
Message: err.Error(),
}
ReplyError(frontend, cmd.Id(), e)
return
}
if cmd.Rescan {
addrs := map[string]struct{}{
addr: struct{}{},
}
a.RescanAddresses(bs.Height, addrs)
}
// If the import was successful, reply with nil.
ReplySuccess(frontend, cmd.Id(), nil)
}
// NotifyBalances notifies an attached frontend of the current confirmed
// and unconfirmed account balances.
//

View file

@ -142,7 +142,7 @@ func (s *server) handleRPCRequest(w http.ResponseWriter, r *http.Request) {
close(done)
}()
ProcessFrontendMsg(frontend, body, false)
ProcessRequest(frontend, body, false)
<-done
}
@ -258,7 +258,7 @@ func frontendSendRecv(ws *websocket.Conn) {
return
}
// Handle request here.
go ProcessFrontendMsg(frontendNotification, m, true)
go ProcessRequest(frontendNotification, m, true)
case ntfn, _ := <-frontendNotification:
if err := websocket.Message.Send(ws, ntfn); err != nil {
// Frontend disconnected.
@ -703,7 +703,7 @@ func BtcdHandshake(ws *websocket.Conn) {
// catch up.
for _, a := range accounts.m {
a.RescanToBestBlock()
a.RescanActiveAddresses()
}
// Begin tracking wallets against this btcd instance.

View file

@ -248,7 +248,7 @@ func ChainedPrivKey(privkey, pubkey, chaincode []byte) ([]byte, error) {
type varEntries []io.WriterTo
func (v *varEntries) WriteTo(w io.Writer) (n int64, err error) {
ss := ([]io.WriterTo)(*v)
ss := []io.WriterTo(*v)
var written int64
for _, s := range ss {
@ -266,7 +266,7 @@ func (v *varEntries) ReadFrom(r io.Reader) (n int64, err error) {
// Remove any previous entries.
*v = nil
wts := ([]io.WriterTo)(*v)
wts := []io.WriterTo(*v)
// Keep reading entries until an EOF is reached.
for {
@ -326,7 +326,6 @@ type comment []byte
// Wallet represents an btcd/Armory wallet in memory. It
// implements the io.ReaderFrom and io.WriterTo interfaces to read
// from and write to any type of byte streams, including files.
// TODO(jrick) remove as many more magic numbers as possible.
type Wallet struct {
version uint32
net btcwire.BitcoinNet
@ -353,8 +352,9 @@ type Wallet struct {
sync.Mutex
key []byte
}
chainIdxMap map[int64]addressHashKey
lastChainIdx int64
chainIdxMap map[int64]addressHashKey
importedAddrs []*btcAddress
lastChainIdx int64
}
// UnusedWalletBytes specifies the number of actually unused bytes
@ -372,7 +372,9 @@ const UnusedWalletBytes = 1024 - 4 - btcwire.HashSize
// 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")
@ -420,7 +422,7 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet, cre
watchingOnly: false,
},
createDate: time.Now().Unix(),
highestUsed: -1,
highestUsed: rootKeyChainIdx,
kdfParams: *kdfp,
keyGenerator: *root,
syncedBlockHeight: createdAt.Height,
@ -436,7 +438,7 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet, cre
// Add root address to maps.
w.addrMap[addressHashKey(w.keyGenerator.pubKeyHash[:])] = &w.keyGenerator
w.chainIdxMap[w.keyGenerator.chainIndex] = addressHashKey(w.keyGenerator.pubKeyHash[:])
w.chainIdxMap[rootKeyChainIdx] = addressHashKey(w.keyGenerator.pubKeyHash[:])
// Pre-generate encrypted addresses and add to maps.
addr := &w.keyGenerator
@ -449,16 +451,17 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet, cre
if err != nil {
return nil, err
}
newaddr, err := newBtcAddress(privkey, nil, createdAt)
newaddr, err := newBtcAddress(privkey, nil, createdAt, true)
if err != nil {
return nil, err
}
if err = newaddr.encrypt(aeskey); err != nil {
return nil, err
}
w.addrMap[addressHashKey(newaddr.pubKeyHash[:])] = newaddr
addrKey := addressHashKey(newaddr.pubKeyHash[:])
w.addrMap[addrKey] = newaddr
newaddr.chainIndex = addr.chainIndex + 1
w.chainIdxMap[newaddr.chainIndex] = addressHashKey(newaddr.pubKeyHash[:])
w.chainIdxMap[newaddr.chainIndex] = addrKey
copy(newaddr.chaincode[:], cc) // armory does this.. but why?
addr = newaddr
}
@ -523,27 +526,35 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
return n, errors.New("unknown file ID")
}
// Add root address to address map
w.addrMap[addressHashKey(w.keyGenerator.pubKeyHash[:])] = &w.keyGenerator
w.chainIdxMap[w.keyGenerator.chainIndex] = addressHashKey(w.keyGenerator.pubKeyHash[:])
// Add root address to address map.
rootAddrKey := addressHashKey(w.keyGenerator.pubKeyHash[:])
w.addrMap[rootAddrKey] = &w.keyGenerator
w.chainIdxMap[rootKeyChainIdx] = rootAddrKey
// Fill unserializied fields.
wts := ([]io.WriterTo)(appendedEntries)
for _, wt := range wts {
switch wt.(type) {
switch e := wt.(type) {
case *addrEntry:
e := wt.(*addrEntry)
w.addrMap[addressHashKey(e.pubKeyHash160[:])] = &e.addr
w.chainIdxMap[e.addr.chainIndex] = addressHashKey(e.pubKeyHash160[:])
if w.lastChainIdx < e.addr.chainIndex {
w.lastChainIdx = e.addr.chainIndex
addrKey := addressHashKey(e.pubKeyHash160[:])
w.addrMap[addrKey] = &e.addr
if e.addr.chainIndex == importedKeyChainIdx {
w.importedAddrs = append(w.importedAddrs, &e.addr)
} else {
w.chainIdxMap[e.addr.chainIndex] = addrKey
if w.lastChainIdx < e.addr.chainIndex {
w.lastChainIdx = e.addr.chainIndex
}
}
case *addrCommentEntry:
e := wt.(*addrCommentEntry)
w.addrCommentMap[addressHashKey(e.pubKeyHash160[:])] = comment(e.comment)
addrKey := addressHashKey(e.pubKeyHash160[:])
w.addrCommentMap[addrKey] = comment(e.comment)
case *txCommentEntry:
e := wt.(*txCommentEntry)
w.txCommentMap[transactionHashKey(e.txHash[:])] = comment(e.comment)
txKey := transactionHashKey(e.txHash[:])
w.txCommentMap[txKey] = comment(e.comment)
default:
return n, errors.New("unknown appended entry")
}
@ -555,16 +566,24 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
// WriteTo serializes a Wallet and writes it to a io.Writer,
// returning the number of bytes written and any errors encountered.
func (w *Wallet) WriteTo(wtr io.Writer) (n int64, err error) {
wts := make([]io.WriterTo, len(w.addrMap)-1)
var wts []io.WriterTo
var chainedAddrs = make([]io.WriterTo, len(w.chainIdxMap)-1)
var importedAddrs []io.WriterTo
for hash, addr := range w.addrMap {
if addr.chainIndex != -1 { // ignore root address
e := addrEntry{
addr: *addr,
}
copy(e.pubKeyHash160[:], []byte(hash))
wts[addr.chainIndex] = &e
e := &addrEntry{
addr: *addr,
}
copy(e.pubKeyHash160[:], []byte(hash))
if addr.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 {
// No order for imported addresses.
importedAddrs = append(importedAddrs, e)
}
}
wts = append(chainedAddrs, importedAddrs...)
for hash, comment := range w.addrCommentMap {
e := &addrCommentEntry{
comment: []byte(comment),
@ -689,8 +708,8 @@ func (w *Wallet) Version() (string, int) {
// and will return an empty string if the address pool has run out.
func (w *Wallet) NextUnusedAddress() (string, error) {
// Attempt to get address hash of next chained address.
next160, err := w.addr160ForIdx(w.highestUsed + 1)
if err != nil {
next160, ok := w.chainIdxMap[w.highestUsed+1]
if !ok {
// TODO(jrick): Re-fill key pool.
return "", errors.New("cannot find generated address")
} else {
@ -823,18 +842,75 @@ func (w *Wallet) SyncedWith() *BlockStamp {
}
}
// CreatedAt returns the height of the blockchain at the time of wallet
// creation. This is needed when performaing a full rescan to prevent
// unnecessary rescanning before wallet addresses first appeared.
func (w *Wallet) CreatedAt() int32 {
return w.keyGenerator.firstBlock
// EarliestBlockHeight returns the height of the blockchain for when any
// wallet address first appeared. This will usually be the block height
// at the time of wallet creation, unless a private key with an earlier
// block height was imported into the wallet. This is needed when
// performing a full rescan to prevent unnecessary rescanning before
// wallet addresses first appeared.
func (w *Wallet) EarliestBlockHeight() int32 {
height := w.keyGenerator.firstBlock
// Imported keys will be the only ones that may have an earlier
// blockchain height. Check each and set the returned height
for _, addr := range w.importedAddrs {
if addr.firstBlock < height {
height = addr.firstBlock
// Can't go any lower than 0.
if height == 0 {
break
}
}
}
return height
}
func (w *Wallet) addr160ForIdx(idx int64) (addressHashKey, error) {
if idx > w.lastChainIdx {
return "", errors.New("chain index out of range")
// 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.
w.secret.Lock()
if len(w.secret.key) != 32 {
w.secret.Unlock()
return "", ErrWalletLocked
}
return w.chainIdxMap[idx], nil
localSecret := make([]byte, 32)
copy(localSecret, w.secret.key)
w.secret.Unlock()
// Create new address with this private key.
addr, err := newBtcAddress(privkey, nil, bs, compressed)
if err != nil {
return "", err
}
addr.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 {
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)
return addrstr, nil
}
// AddressInfo holds information regarding an address needed to manage
@ -842,8 +918,9 @@ func (w *Wallet) addr160ForIdx(idx int64) (addressHashKey, error) {
type AddressInfo struct {
Address string
AddrHash string
FirstBlock int32
Compressed bool
FirstBlock int32
Imported bool
}
// GetSortedActiveAddresses returns all wallet addresses that have been
@ -851,10 +928,11 @@ type AddressInfo struct {
// the key pool. Use this when ordered addresses are needed. Otherwise,
// GetActiveAddresses is preferred.
func (w *Wallet) GetSortedActiveAddresses() []*AddressInfo {
addrs := make([]*AddressInfo, 0, w.highestUsed+1)
for i := int64(-1); i <= w.highestUsed; i++ {
addr160, err := w.addr160ForIdx(i)
if err != nil {
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]
@ -863,6 +941,12 @@ func (w *Wallet) GetSortedActiveAddresses() []*AddressInfo {
addrs = append(addrs, info)
}
}
for _, addr := range w.importedAddrs {
info, err := addr.info(w.Net())
if err == nil {
addrs = append(addrs, info)
}
}
return addrs
}
@ -871,9 +955,9 @@ func (w *Wallet) GetSortedActiveAddresses() []*AddressInfo {
// key pool. If addresses must be sorted, use GetSortedActiveAddresses.
func (w *Wallet) GetActiveAddresses() map[string]*AddressInfo {
addrs := make(map[string]*AddressInfo)
for i := int64(-1); i <= w.highestUsed; i++ {
addr160, err := w.addr160ForIdx(i)
if err != nil {
for i := int64(rootKeyChainIdx); i <= w.highestUsed; i++ {
addr160, ok := w.chainIdxMap[i]
if !ok {
return addrs
}
addr := w.addrMap[addr160]
@ -882,6 +966,12 @@ func (w *Wallet) GetActiveAddresses() map[string]*AddressInfo {
addrs[info.Address] = info
}
}
for _, addr := range w.importedAddrs {
info, err := addr.info(w.Net())
if err == nil {
addrs[info.Address] = info
}
}
return addrs
}
@ -988,6 +1078,16 @@ type btcAddress struct {
}
}
const (
// Root address has a chain index of -1. Each subsequent
// chained address increments the index.
rootKeyChainIdx = -1
// Imported private keys are not part of the chain, and have a
// special index of -2.
importedKeyChainIdx = -2
)
const (
pubkeyCompressed byte = 0x2
pubkeyUncompressed byte = 0x4
@ -1039,7 +1139,7 @@ func (k *publicKey) WriteTo(w io.Writer) (n int64, err error) {
// newBtcAddress initializes and returns a new address. privkey must
// be 32 bytes. iv must be 16 bytes, or nil (in which case it is
// randomly generated).
func newBtcAddress(privkey, iv []byte, bs *BlockStamp) (addr *btcAddress, err error) {
func newBtcAddress(privkey, iv []byte, bs *BlockStamp, compressed bool) (addr *btcAddress, err error) {
if len(privkey) != 32 {
return nil, errors.New("private key is not 32 bytes")
}
@ -1056,7 +1156,7 @@ func newBtcAddress(privkey, iv []byte, bs *BlockStamp) (addr *btcAddress, err er
flags: addrFlags{
hasPrivKey: true,
hasPubKey: true,
compressed: true,
compressed: compressed,
},
firstSeen: time.Now().Unix(),
firstBlock: bs.Height,
@ -1072,18 +1172,22 @@ func newBtcAddress(privkey, iv []byte, bs *BlockStamp) (addr *btcAddress, err er
// newRootBtcAddress generates a new address, also setting the
// chaincode and chain index to represent this address as a root
// address.
func newRootBtcAddress(privKey, iv, chaincode []byte, bs *BlockStamp) (addr *btcAddress, err error) {
func newRootBtcAddress(privKey, iv, chaincode []byte,
bs *BlockStamp) (addr *btcAddress, err error) {
if len(chaincode) != 32 {
return nil, errors.New("chaincode is not 32 bytes")
}
addr, err = newBtcAddress(privKey, iv, bs)
// Create new btcAddress with provided inputs. This will
// always use a compressed pubkey.
addr, err = newBtcAddress(privKey, iv, bs, true)
if err != nil {
return nil, err
}
copy(addr.chaincode[:], chaincode)
addr.chainIndex = -1
addr.chainIndex = rootKeyChainIdx
return addr, err
}
@ -1299,8 +1403,9 @@ func (a *btcAddress) info(net btcwire.BitcoinNet) (*AddressInfo, error) {
return &AddressInfo{
Address: address,
AddrHash: string(a.pubKeyHash[:]),
FirstBlock: a.firstBlock,
Compressed: a.flags.compressed,
FirstBlock: a.firstBlock,
Imported: a.chainIndex == importedKeyChainIdx,
}, nil
}