603 lines
19 KiB
Go
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/roasbeef/btcd/btcec"
|
|
"github.com/roasbeef/btcd/txscript"
|
|
"github.com/roasbeef/btcutil"
|
|
"github.com/roasbeef/btcutil/hdkeychain"
|
|
"github.com/roasbeef/btcwallet/internal/zero"
|
|
"github.com/roasbeef/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
|
|
}
|