lbcwallet/waddrmgr/address.go
Josh Rickmar 4656a00705 Improve wallet atomicity.
This changes the database access APIs and each of the "manager"
packages (waddrmgr/wstakemgr) so that transactions are opened (only)
by the wallet package and the namespace buckets that each manager
expects to operate on are passed in as parameters.

This helps improve the atomicity situation as it means that many
calls to these APIs can be grouped together into a single
database transaction.

This change does not attempt to completely fix the "half-processed"
block problem.  Mined transactions are still added to the wallet
database under their own database transaction as this is how they are
notified by the consensus JSON-RPC server (as loose transactions,
without the rest of the block that contains them). It will make
updating to a fixed notification model significantly easier, as the
same "manager" APIs can still be used, but grouped into a single
atomic transaction.
2018-05-23 19:38:56 -07:00

603 lines
19 KiB
Go

// Copyright (c) 2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package waddrmgr
import (
"encoding/hex"
"fmt"
"sync"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/internal/zero"
"github.com/btcsuite/btcwallet/walletdb"
)
// AddressType represents the various address types waddrmgr is currently able
// to generate, and maintain.
type AddressType uint8
const (
// PubKeyHash is a regular p2pkh address.
PubKeyHash AddressType = 1 << iota
// NestedWitnessPubKey represents a p2wkh output nested within a p2sh
// output. Using this address type, the wallet can receive funds from
// other wallet's which don't yet recognize the new segwit standard
// output types. Receiving funds to this address maintains the
// scalability, and malleability fixes due to segwit in a backwards
// compatible manner.
NestedWitnessPubKey
// WitnessPubKey represents a p2wkh (pay-to-witness-key-hash) address type.
WitnessPubKey
)
// addressTypeToInternal converts the publicly exported address type to the
// internal address type recognized by the database.
func addressTypeToInternal(t AddressType) addressType {
switch t {
case PubKeyHash:
return adtChain
case NestedWitnessPubKey:
return adtChainNestedWitness
case WitnessPubKey:
return adtChainWitness
}
return adtChain
}
// ManagedAddress is an interface that provides acces to information regarding
// an address managed by an address manager. Concrete implementations of this
// type may provide further fields to provide information specific to that type
// of address.
type ManagedAddress interface {
// Account returns the account the address is associated with.
Account() uint32
// Address returns a btcutil.Address for the backing address.
Address() btcutil.Address
// AddrHash returns the key or script hash related to the address
AddrHash() []byte
// Imported returns true if the backing address was imported instead
// of being part of an address chain.
Imported() bool
// Internal returns true if the backing address was created for internal
// use such as a change output of a transaction.
Internal() bool
// Compressed returns true if the backing address is compressed.
Compressed() bool
// Used returns true if the backing address has been used in a transaction.
Used(ns walletdb.ReadBucket) bool
}
// ManagedPubKeyAddress extends ManagedAddress and additionally provides the
// public and private keys for pubkey-based addresses.
type ManagedPubKeyAddress interface {
ManagedAddress
// PubKey returns the public key associated with the address.
PubKey() *btcec.PublicKey
// ExportPubKey returns the public key associated with the address
// serialized as a hex encoded string.
ExportPubKey() string
// PrivKey returns the private key for the address. It can fail if the
// address manager is watching-only or locked, or the address does not
// have any keys.
PrivKey() (*btcec.PrivateKey, error)
// ExportPrivKey returns the private key associated with the address
// serialized as Wallet Import Format (WIF).
ExportPrivKey() (*btcutil.WIF, error)
// IsNestedWitness returns true if the managed address is an instance of pw2wkh
// nested within p2sh.
IsNestedWitness() bool
// IsWitness returns true if the managed address is a p2wkh address.
IsWitness() bool
}
// ManagedScriptAddress extends ManagedAddress and represents a pay-to-script-hash
// style of bitcoin addresses. It additionally provides information about the
// script.
type ManagedScriptAddress interface {
ManagedAddress
// Script returns the script associated with the address.
Script() ([]byte, error)
}
// managedAddress represents a public key address. It also may or may not have
// the private key associated with the public key.
type managedAddress struct {
manager *Manager
account uint32
address btcutil.Address
imported bool
internal bool
compressed bool
used bool
addrType addressType
pubKey *btcec.PublicKey
privKeyEncrypted []byte
privKeyCT []byte // non-nil if unlocked
privKeyMutex sync.Mutex
}
// Enforce managedAddress satisfies the ManagedPubKeyAddress interface.
var _ ManagedPubKeyAddress = (*managedAddress)(nil)
// unlock decrypts and stores a pointer to the associated private key. It will
// fail if the key is invalid or the encrypted private key is not available.
// The returned clear text private key will always be a copy that may be safely
// used by the caller without worrying about it being zeroed during an address
// lock.
func (a *managedAddress) unlock(key EncryptorDecryptor) ([]byte, error) {
// Protect concurrent access to clear text private key.
a.privKeyMutex.Lock()
defer a.privKeyMutex.Unlock()
if len(a.privKeyCT) == 0 {
privKey, err := key.Decrypt(a.privKeyEncrypted)
if err != nil {
str := fmt.Sprintf("failed to decrypt private key for "+
"%s", a.address)
return nil, managerError(ErrCrypto, str, err)
}
a.privKeyCT = privKey
}
privKeyCopy := make([]byte, len(a.privKeyCT))
copy(privKeyCopy, a.privKeyCT)
return privKeyCopy, nil
}
// lock zeroes the associated clear text private key.
func (a *managedAddress) lock() {
// Zero and nil the clear text private key associated with this
// address.
a.privKeyMutex.Lock()
zero.Bytes(a.privKeyCT)
a.privKeyCT = nil
a.privKeyMutex.Unlock()
}
// Account returns the account number the address is associated with.
//
// This is part of the ManagedAddress interface implementation.
func (a *managedAddress) Account() uint32 {
return a.account
}
// Address returns the btcutil.Address which represents the managed address.
// This will be a pay-to-pubkey-hash address.
//
// This is part of the ManagedAddress interface implementation.
func (a *managedAddress) Address() btcutil.Address {
return a.address
}
// AddrHash returns the public key hash for the address.
//
// This is part of the ManagedAddress interface implementation.
func (a *managedAddress) AddrHash() []byte {
var hash []byte
switch n := a.address.(type) {
case *btcutil.AddressPubKeyHash:
hash = n.Hash160()[:]
case *btcutil.AddressScriptHash:
hash = n.Hash160()[:]
case *btcutil.AddressWitnessPubKeyHash:
hash = n.Hash160()[:]
}
return hash
}
// Imported returns true if the address was imported instead of being part of an
// address chain.
//
// This is part of the ManagedAddress interface implementation.
func (a *managedAddress) Imported() bool {
return a.imported
}
// Internal returns true if the address was created for internal use such as a
// change output of a transaction.
//
// This is part of the ManagedAddress interface implementation.
func (a *managedAddress) Internal() bool {
return a.internal
}
// Compressed returns true if the address is compressed.
//
// This is part of the ManagedAddress interface implementation.
func (a *managedAddress) Compressed() bool {
return a.compressed
}
// Used returns true if the address has been used in a transaction.
//
// This is part of the ManagedAddress interface implementation.
func (a *managedAddress) Used(ns walletdb.ReadBucket) bool {
return a.manager.fetchUsed(ns, a.AddrHash())
}
// PubKey returns the public key associated with the address.
//
// This is part of the ManagedPubKeyAddress interface implementation.
func (a *managedAddress) PubKey() *btcec.PublicKey {
return a.pubKey
}
// pubKeyBytes returns the serialized public key bytes for the managed address
// based on whether or not the managed address is marked as compressed.
func (a *managedAddress) pubKeyBytes() []byte {
if a.compressed {
return a.pubKey.SerializeCompressed()
}
return a.pubKey.SerializeUncompressed()
}
// ExportPubKey returns the public key associated with the address
// serialized as a hex encoded string.
//
// This is part of the ManagedPubKeyAddress interface implementation.
func (a *managedAddress) ExportPubKey() string {
return hex.EncodeToString(a.pubKeyBytes())
}
// PrivKey returns the private key for the address. It can fail if the address
// manager is watching-only or locked, or the address does not have any keys.
//
// This is part of the ManagedPubKeyAddress interface implementation.
func (a *managedAddress) PrivKey() (*btcec.PrivateKey, error) {
// No private keys are available for a watching-only address manager.
if a.manager.watchingOnly {
return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil)
}
a.manager.mtx.Lock()
defer a.manager.mtx.Unlock()
// Account manager must be unlocked to decrypt the private key.
if a.manager.locked {
return nil, managerError(ErrLocked, errLocked, nil)
}
// Decrypt the key as needed. Also, make sure it's a copy since the
// private key stored in memory can be cleared at any time. Otherwise
// the returned private key could be invalidated from under the caller.
privKeyCopy, err := a.unlock(a.manager.cryptoKeyPriv)
if err != nil {
return nil, err
}
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyCopy)
zero.Bytes(privKeyCopy)
return privKey, nil
}
// ExportPrivKey returns the private key associated with the address in Wallet
// Import Format (WIF).
//
// This is part of the ManagedPubKeyAddress interface implementation.
func (a *managedAddress) ExportPrivKey() (*btcutil.WIF, error) {
pk, err := a.PrivKey()
if err != nil {
return nil, err
}
return btcutil.NewWIF(pk, a.manager.chainParams, a.compressed)
}
// IsNestedWitness returns true if the managed address is an instance of pw2wkh
// nested within p2sh.
func (a *managedAddress) IsNestedWitness() bool {
return a.addrType == adtChainNestedWitness
}
// IsWitness returns true if the managed address is a p2wkh address.
func (a *managedAddress) IsWitness() bool {
return a.addrType == adtChainWitness
}
// newManagedAddressWithoutPrivKey returns a new managed address based on the
// passed account, public key, and whether or not the public key should be
// compressed.
func newManagedAddressWithoutPrivKey(m *Manager, account uint32, pubKey *btcec.PublicKey,
compressed bool, addrType addressType) (*managedAddress, error) {
// Create a pay-to-pubkey-hash address from the public key.
var pubKeyHash []byte
if compressed {
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
} else {
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
}
var address btcutil.Address
var err error
switch addrType {
// TODO(roasbeef): only use these types in the db?
case adtChainNestedWitness:
// For this address type we'l generate an address which is
// backwards compatible to Bitcoin nodes running 0.6.0 onwards, but
// allows us to take advantage of segwit's scripting improvments,
// and malleability fixes.
// First, we'll generate a normal p2wkh address from the pubkey hash.
witAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, m.chainParams)
if err != nil {
return nil, err
}
// Next we'll generate the witness program which can be used as a
// pkScript to pay to this generated address.
witnessProgram, err := txscript.PayToAddrScript(witAddr)
if err != nil {
return nil, err
}
// Finally, we'll use the witness program itself as the pre-image
// to a p2sh address. In order to spend, we first use the
// witnessProgram as the sigScript, then present the proper
// <sig, pubkey> pair as the witness.
address, err = btcutil.NewAddressScriptHash(witnessProgram, m.chainParams)
if err != nil {
return nil, err
}
case adtImport:
// TODO(roasbeef): truly proper?
fallthrough
case adtChain:
address, err = btcutil.NewAddressPubKeyHash(pubKeyHash, m.chainParams)
if err != nil {
return nil, err
}
case adtChainWitness:
address, err = btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, m.chainParams)
if err != nil {
return nil, err
}
}
return &managedAddress{
manager: m,
address: address,
account: account,
imported: false,
internal: false,
addrType: addrType,
compressed: compressed,
pubKey: pubKey,
privKeyEncrypted: nil,
privKeyCT: nil,
}, nil
}
// newManagedAddress returns a new managed address based on the passed account,
// private key, and whether or not the public key is compressed. The managed
// address will have access to the private and public keys.
func newManagedAddress(m *Manager, account uint32, privKey *btcec.PrivateKey,
compressed bool, addrType addressType) (*managedAddress, error) {
// Encrypt the private key.
//
// NOTE: The privKeyBytes here are set into the managed address which
// are cleared when locked, so they aren't cleared here.
privKeyBytes := privKey.Serialize()
privKeyEncrypted, err := m.cryptoKeyPriv.Encrypt(privKeyBytes)
if err != nil {
str := "failed to encrypt private key"
return nil, managerError(ErrCrypto, str, err)
}
// Leverage the code to create a managed address without a private key
// and then add the private key to it.
ecPubKey := (*btcec.PublicKey)(&privKey.PublicKey)
managedAddr, err := newManagedAddressWithoutPrivKey(m, account,
ecPubKey, compressed, addrType)
if err != nil {
return nil, err
}
managedAddr.privKeyEncrypted = privKeyEncrypted
managedAddr.privKeyCT = privKeyBytes
return managedAddr, nil
}
// newManagedAddressFromExtKey returns a new managed address based on the passed
// account and extended key. The managed address will have access to the
// private and public keys if the provided extended key is private, otherwise it
// will only have access to the public key.
func newManagedAddressFromExtKey(m *Manager, account uint32,
key *hdkeychain.ExtendedKey, addrType addressType) (*managedAddress, error) {
// Create a new managed address based on the public or private key
// depending on whether the generated key is private.
var managedAddr *managedAddress
if key.IsPrivate() {
privKey, err := key.ECPrivKey()
if err != nil {
return nil, err
}
// Ensure the temp private key big integer is cleared after use.
managedAddr, err = newManagedAddress(m, account, privKey, true,
addrType)
if err != nil {
return nil, err
}
} else {
pubKey, err := key.ECPubKey()
if err != nil {
return nil, err
}
managedAddr, err = newManagedAddressWithoutPrivKey(m, account,
pubKey, true, addrType)
if err != nil {
return nil, err
}
}
return managedAddr, nil
}
// scriptAddress represents a pay-to-script-hash address.
type scriptAddress struct {
manager *Manager
account uint32
address *btcutil.AddressScriptHash
scriptEncrypted []byte
scriptCT []byte
scriptMutex sync.Mutex
used bool
}
// Enforce scriptAddress satisfies the ManagedScriptAddress interface.
var _ ManagedScriptAddress = (*scriptAddress)(nil)
// unlock decrypts and stores the associated script. It will fail if the key is
// invalid or the encrypted script is not available. The returned clear text
// script will always be a copy that may be safely used by the caller without
// worrying about it being zeroed during an address lock.
func (a *scriptAddress) unlock(key EncryptorDecryptor) ([]byte, error) {
// Protect concurrent access to clear text script.
a.scriptMutex.Lock()
defer a.scriptMutex.Unlock()
if len(a.scriptCT) == 0 {
script, err := key.Decrypt(a.scriptEncrypted)
if err != nil {
str := fmt.Sprintf("failed to decrypt script for %s",
a.address)
return nil, managerError(ErrCrypto, str, err)
}
a.scriptCT = script
}
scriptCopy := make([]byte, len(a.scriptCT))
copy(scriptCopy, a.scriptCT)
return scriptCopy, nil
}
// lock zeroes the associated clear text private key.
func (a *scriptAddress) lock() {
// Zero and nil the clear text script associated with this address.
a.scriptMutex.Lock()
zero.Bytes(a.scriptCT)
a.scriptCT = nil
a.scriptMutex.Unlock()
}
// Account returns the account the address is associated with. This will always
// be the ImportedAddrAccount constant for script addresses.
//
// This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) Account() uint32 {
return a.account
}
// Address returns the btcutil.Address which represents the managed address.
// This will be a pay-to-script-hash address.
//
// This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) Address() btcutil.Address {
return a.address
}
// AddrHash returns the script hash for the address.
//
// This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) AddrHash() []byte {
return a.address.Hash160()[:]
}
// Imported always returns true since script addresses are always imported
// addresses and not part of any chain.
//
// This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) Imported() bool {
return true
}
// Internal always returns false since script addresses are always imported
// addresses and not part of any chain in order to be for internal use.
//
// This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) Internal() bool {
return false
}
// Compressed returns false since script addresses are never compressed.
//
// This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) Compressed() bool {
return false
}
// Used returns true if the address has been used in a transaction.
//
// This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) Used(ns walletdb.ReadBucket) bool {
return a.manager.fetchUsed(ns, a.AddrHash())
}
// Script returns the script associated with the address.
//
// This implements the ScriptAddress interface.
func (a *scriptAddress) Script() ([]byte, error) {
// No script is available for a watching-only address manager.
if a.manager.watchingOnly {
return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil)
}
a.manager.mtx.Lock()
defer a.manager.mtx.Unlock()
// Account manager must be unlocked to decrypt the script.
if a.manager.locked {
return nil, managerError(ErrLocked, errLocked, nil)
}
// Decrypt the script as needed. Also, make sure it's a copy since the
// script stored in memory can be cleared at any time. Otherwise,
// the returned script could be invalidated from under the caller.
return a.unlock(a.manager.cryptoKeyScript)
}
// newScriptAddress initializes and returns a new pay-to-script-hash address.
func newScriptAddress(m *Manager, account uint32, scriptHash, scriptEncrypted []byte) (*scriptAddress, error) {
address, err := btcutil.NewAddressScriptHashFromHash(scriptHash,
m.chainParams)
if err != nil {
return nil, err
}
return &scriptAddress{
manager: m,
account: account,
address: address,
scriptEncrypted: scriptEncrypted,
}, nil
}