waddrmgr: add support for nested+regular witness key hash addresses

This commit introduces two new address types to the waddrmgr. The first
address type is the native p2wkh (pay-to-witness-key-hash) output type
introduced as part of BIP0141 and the segwit soft-fork. The second
address type is a p2wkh output nested *within* a regular p2sh output.
This second address allows older wallets which are not yet aware of the
new segwit output types to transparently pay to a wallet which does
support them. Additionally, using this nested p2wkh output the wallet
gains both the space+transaction fee savings, as well as the
malleability fixes.

Both address types have been implemented as special cases of the
ManagedPubKeyAddress since they share several traits, only
differentiating in the signing mechanism needed, and the concrete
implementation of btcutil.Address returned by the address.

Two new `addressType` constants have been added to waddrmgr’s db in
order to properly serialize and deserialize the new address types.
This commit is contained in:
Olaoluwa Osuntokun 2016-04-25 12:30:38 -07:00
parent a2ff118b25
commit cb225e2add
4 changed files with 208 additions and 46 deletions

View file

@ -10,11 +10,47 @@ import (
"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"
)
// 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
@ -64,6 +100,13 @@ type ManagedPubKeyAddress interface {
// 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
@ -81,11 +124,12 @@ type ManagedScriptAddress interface {
type managedAddress struct {
manager *Manager
account uint32
address *btcutil.AddressPubKeyHash
address btcutil.Address
imported bool
internal bool
compressed bool
used bool
addrType addressType
pubKey *btcec.PublicKey
privKeyEncrypted []byte
privKeyCT []byte // non-nil if unlocked
@ -150,7 +194,18 @@ func (a *managedAddress) Address() btcutil.Address {
//
// This is part of the ManagedAddress interface implementation.
func (a *managedAddress) AddrHash() []byte {
return a.address.Hash160()[:]
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
@ -251,10 +306,23 @@ func (a *managedAddress) ExportPrivKey() (*btcutil.WIF, error) {
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) (*managedAddress, error) {
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 {
@ -262,17 +330,58 @@ func newManagedAddressWithoutPrivKey(m *Manager, account uint32, pubKey *btcec.P
} else {
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
}
address, err := btcutil.NewAddressPubKeyHash(pubKeyHash, m.chainParams)
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 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,
@ -283,7 +392,9 @@ func newManagedAddressWithoutPrivKey(m *Manager, account uint32, pubKey *btcec.P
// 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) (*managedAddress, error) {
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
@ -299,7 +410,7 @@ func newManagedAddress(m *Manager, account uint32, privKey *btcec.PrivateKey, co
// and then add the private key to it.
ecPubKey := (*btcec.PublicKey)(&privKey.PublicKey)
managedAddr, err := newManagedAddressWithoutPrivKey(m, account,
ecPubKey, compressed)
ecPubKey, compressed, addrType)
if err != nil {
return nil, err
}
@ -313,7 +424,9 @@ func newManagedAddress(m *Manager, account uint32, privKey *btcec.PrivateKey, co
// 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) (*managedAddress, error) {
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
@ -324,8 +437,8 @@ func newManagedAddressFromExtKey(m *Manager, account uint32, key *hdkeychain.Ext
}
// Ensure the temp private key big integer is cleared after use.
managedAddr, err = newManagedAddress(m, account, privKey, true)
zero.BigInt(privKey.D)
managedAddr, err = newManagedAddress(m, account, privKey, true,
addrType)
if err != nil {
return nil, err
}
@ -336,7 +449,7 @@ func newManagedAddressFromExtKey(m *Manager, account uint32, key *hdkeychain.Ext
}
managedAddr, err = newManagedAddressWithoutPrivKey(m, account,
pubKey, true)
pubKey, true, addrType)
if err != nil {
return nil, err
}
@ -412,8 +525,6 @@ func (a *scriptAddress) Address() btcutil.Address {
// AddrHash returns the script hash for the address.
//
// This is part of the ManagedAddress interface implementation.
//
// This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) AddrHash() []byte {
return a.address.Hash160()[:]
}

View file

@ -68,6 +68,8 @@ const (
adtChain addressType = 0 // not iota as they need to be stable for db
adtImport addressType = 1
adtScript addressType = 2
adtChainWitness addressType = 3
adtChainNestedWitness addressType = 4
)
// accountType represents a type of address stored in the database.
@ -990,6 +992,10 @@ func fetchAddressByHash(tx walletdb.Tx, addrHash []byte) (interface{}, error) {
}
switch row.addrType {
case adtChainWitness:
fallthrough
case adtChainNestedWitness:
fallthrough
case adtChain:
return deserializeChainedAddress(row)
case adtImport:
@ -1058,10 +1064,10 @@ func putAddress(tx walletdb.Tx, addressID []byte, row *dbAddressRow) error {
// putChainedAddress stores the provided chained address information to the
// database.
func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32,
status syncStatus, branch, index uint32) error {
status syncStatus, branch, index uint32, addrType addressType) error {
addrRow := dbAddressRow{
addrType: adtChain,
addrType: addrType,
account: account,
addTime: uint64(time.Now().Unix()),
syncStatus: status,
@ -1079,7 +1085,6 @@ func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32,
// Deserialize the account row.
row, err := deserializeAccountRow(accountID, serializedAccount)
if err != nil {
return err
}

View file

@ -168,7 +168,7 @@ type AccountProperties struct {
// managed address when the address manager is unlocked. See the deriveOnUnlock
// field in the Manager struct for more details on how this is used.
type unlockDeriveInfo struct {
managedAddr *managedAddress
managedAddr ManagedAddress
branch uint32
index uint32
}
@ -387,11 +387,13 @@ func (m *Manager) Close() {
// The passed derivedKey is zeroed after the new address is created.
//
// This function MUST be called with the manager lock held for writes.
func (m *Manager) keyToManaged(derivedKey *hdkeychain.ExtendedKey, account, branch, index uint32) (ManagedAddress, error) {
func (m *Manager) keyToManaged(derivedKey *hdkeychain.ExtendedKey,
addrType addressType, account, branch, index uint32) (ManagedAddress, error) {
// Create a new managed address based on the public or private key
// depending on whether the passed key is private. Also, zero the
// key after creating the managed address from it.
ma, err := newManagedAddressFromExtKey(m, account, derivedKey)
ma, err := newManagedAddressFromExtKey(m, account, derivedKey, addrType)
defer derivedKey.Zero()
if err != nil {
return nil, err
@ -525,7 +527,9 @@ func (m *Manager) loadAccountInfo(account uint32) (*accountInfo, error) {
if err != nil {
return nil, err
}
lastExtAddr, err := m.keyToManaged(lastExtKey, account, branch, index)
// TODO(roasbeef): default type??
lastExtAddr, err := m.keyToManaged(lastExtKey, adtChainWitness,
account, branch, index)
if err != nil {
return nil, err
}
@ -540,7 +544,8 @@ func (m *Manager) loadAccountInfo(account uint32) (*accountInfo, error) {
if err != nil {
return nil, err
}
lastIntAddr, err := m.keyToManaged(lastIntKey, account, branch, index)
lastIntAddr, err := m.keyToManaged(lastIntKey, adtChainWitness,
account, branch, index)
if err != nil {
return nil, err
}
@ -630,7 +635,7 @@ func (m *Manager) chainAddressRowToManaged(row *dbChainAddressRow) (ManagedAddre
return nil, err
}
return m.keyToManaged(addressKey, row.account, row.branch, row.index)
return m.keyToManaged(addressKey, row.addrType, row.account, row.branch, row.index)
}
// importedAddressRowToManaged returns a new managed address based on imported
@ -651,7 +656,7 @@ func (m *Manager) importedAddressRowToManaged(row *dbImportedAddressRow) (Manage
compressed := len(pubBytes) == btcec.PubKeyBytesLenCompressed
ma, err := newManagedAddressWithoutPrivKey(m, row.account, pubKey,
compressed)
compressed, row.addrType)
if err != nil {
return nil, err
}
@ -745,6 +750,8 @@ func (m *Manager) Address(address btcutil.Address) (ManagedAddress, error) {
address = pka.AddressPubKeyHash()
}
// TODO(roasbeef): also need to distinguish p2wkh from p2pkh
// Return the address from cache if it's available.
//
// NOTE: Not using a defer on the lock here since a write lock is
@ -1143,13 +1150,15 @@ func (m *Manager) ImportPrivateKey(wif *btcutil.WIF, bs *BlockStamp) (ManagedPub
// Create a new managed address based on the imported address.
var managedAddr *managedAddress
// TODO(roasbeef): default type? need to watch for all
if !m.watchingOnly {
managedAddr, err = newManagedAddress(m, ImportedAddrAccount,
wif.PrivKey, wif.CompressPubKey)
wif.PrivKey, wif.CompressPubKey, adtChain)
} else {
pubKey := (*btcec.PublicKey)(&wif.PrivKey.PublicKey)
managedAddr, err = newManagedAddressWithoutPrivKey(m,
ImportedAddrAccount, pubKey, wif.CompressPubKey)
ImportedAddrAccount, pubKey, wif.CompressPubKey,
adtChain)
}
if err != nil {
return nil, err
@ -1403,7 +1412,8 @@ func (m *Manager) Unlock(passphrase []byte) error {
// Derive any private keys that are pending due to them being created
// while the address manager was locked.
for _, info := range m.deriveOnUnlock {
addressKey, err := m.deriveKeyFromPath(info.managedAddr.account,
// TODO(roasbeef): addr type here?
addressKey, err := m.deriveKeyFromPath(info.managedAddr.Account(),
info.branch, info.index, true)
if err != nil {
m.lock()
@ -1425,8 +1435,14 @@ func (m *Manager) Unlock(passphrase []byte) error {
"address %s", info.managedAddr.Address())
return managerError(ErrCrypto, str, err)
}
info.managedAddr.privKeyEncrypted = privKeyEncrypted
info.managedAddr.privKeyCT = privKeyBytes
// TODO(roasbeef): don't need to do anythign further?
switch a := info.managedAddr.(type) {
case *managedAddress:
a.privKeyEncrypted = privKeyEncrypted
a.privKeyCT = privKeyBytes
case *scriptAddress:
}
// Avoid re-deriving this key on subsequent unlocks.
m.deriveOnUnlock[0] = nil
@ -1478,7 +1494,9 @@ func (m *Manager) ChainParams() *chaincfg.Params {
// branch indicated by the internal flag.
//
// This function MUST be called with the manager lock held for writes.
func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bool) ([]ManagedAddress, error) {
func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bool,
addrType addressType) ([]ManagedAddress, error) {
// The next address can only be generated for accounts that have already
// been created.
acctInfo, err := m.loadAccountInfo(account)
@ -1552,14 +1570,16 @@ func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bo
// Create a new managed address based on the public or private
// key depending on whether the generated key is private. Also,
// zero the next key after creating the managed address from it.
managedAddr, err := newManagedAddressFromExtKey(m, account, nextKey)
nextKey.Zero()
addr, err := newManagedAddressFromExtKey(m, account, nextKey, addrType)
if err != nil {
return nil, err
}
if internal {
managedAddr.internal = true
addr.internal = true
}
managedAddr := addr
nextKey.Zero()
info := unlockDeriveInfo{
managedAddr: managedAddr,
branch: branchNum,
@ -1573,12 +1593,30 @@ func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bo
err = m.namespace.Update(func(tx walletdb.Tx) error {
for _, info := range addressInfo {
ma := info.managedAddr
// TODO(roasbeef): need to distinguish between p2pkh and p2wkh
addressID := ma.Address().ScriptAddress()
switch a := ma.(type) {
case *managedAddress:
err := putChainedAddress(tx, addressID, account, ssFull,
info.branch, info.index)
info.branch, info.index, addrType)
if err != nil {
return err
}
case *scriptAddress: // TODO(roasbeef): no longer needed?
encryptedHash, err := m.cryptoKeyPub.Encrypt(a.AddrHash())
if err != nil {
str := fmt.Sprintf("failed to encrypt script hash %x",
a.AddrHash())
return managerError(ErrCrypto, str, err)
}
err = putScriptAddress(tx, a.AddrHash(), ImportedAddrAccount,
ssNone, encryptedHash, a.scriptEncrypted)
if err != nil {
return err
}
}
}
return nil
@ -1620,7 +1658,9 @@ func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bo
// NextExternalAddresses returns the specified number of next chained addresses
// that are intended for external use from the address manager.
func (m *Manager) NextExternalAddresses(account uint32, numAddresses uint32) ([]ManagedAddress, error) {
func (m *Manager) NextExternalAddresses(account uint32, numAddresses uint32,
addrType AddressType) ([]ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
@ -1630,12 +1670,15 @@ func (m *Manager) NextExternalAddresses(account uint32, numAddresses uint32) ([]
m.mtx.Lock()
defer m.mtx.Unlock()
return m.nextAddresses(account, numAddresses, false)
t := addressTypeToInternal(addrType)
return m.nextAddresses(account, numAddresses, false, t)
}
// NextInternalAddresses returns the specified number of next chained addresses
// that are intended for internal use such as change from the address manager.
func (m *Manager) NextInternalAddresses(account uint32, numAddresses uint32) ([]ManagedAddress, error) {
func (m *Manager) NextInternalAddresses(account uint32, numAddresses uint32,
addrType AddressType) ([]ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
@ -1645,7 +1688,8 @@ func (m *Manager) NextInternalAddresses(account uint32, numAddresses uint32) ([]
m.mtx.Lock()
defer m.mtx.Unlock()
return m.nextAddresses(account, numAddresses, true)
t := addressTypeToInternal(addrType)
return m.nextAddresses(account, numAddresses, true, t)
}
// LastExternalAddress returns the most recently requested chained external

View file

@ -294,7 +294,8 @@ func testExternalAddresses(tc *testContext) bool {
if tc.create {
prefix := prefix + " NextExternalAddresses"
var err error
addrs, err = tc.manager.NextExternalAddresses(tc.account, 5)
addrs, err = tc.manager.NextExternalAddresses(tc.account, 5,
waddrmgr.PubKeyHash)
if err != nil {
tc.t.Errorf("%s: unexpected error: %v", prefix, err)
return false
@ -421,7 +422,8 @@ func testInternalAddresses(tc *testContext) bool {
if tc.create {
prefix := prefix + " NextInternalAddress"
var err error
addrs, err = tc.manager.NextInternalAddresses(tc.account, 5)
addrs, err = tc.manager.NextInternalAddresses(tc.account, 5,
waddrmgr.PubKeyHash)
if err != nil {
tc.t.Errorf("%s: unexpected error: %v", prefix, err)
return false