From cb225e2adde152d1c7246ab874a557b05b3a3cce Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 25 Apr 2016 12:30:38 -0700 Subject: [PATCH] waddrmgr: add support for nested+regular witness key hash addresses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- waddrmgr/address.go | 139 +++++++++++++++++++++++++++++++++++---- waddrmgr/db.go | 17 +++-- waddrmgr/manager.go | 92 +++++++++++++++++++------- waddrmgr/manager_test.go | 6 +- 4 files changed, 208 insertions(+), 46 deletions(-) diff --git a/waddrmgr/address.go b/waddrmgr/address.go index cefdd15..6f53be8 100644 --- a/waddrmgr/address.go +++ b/waddrmgr/address.go @@ -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,9 +330,49 @@ func newManagedAddressWithoutPrivKey(m *Manager, account uint32, pubKey *btcec.P } else { pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed()) } - address, err := btcutil.NewAddressPubKeyHash(pubKeyHash, m.chainParams) - if err != nil { - return nil, err + + 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 + // 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{ @@ -273,6 +381,7 @@ func newManagedAddressWithoutPrivKey(m *Manager, account uint32, pubKey *btcec.P 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()[:] } diff --git a/waddrmgr/db.go b/waddrmgr/db.go index c5cbd64..6ccbc1a 100644 --- a/waddrmgr/db.go +++ b/waddrmgr/db.go @@ -65,9 +65,11 @@ type addressType uint8 // These constants define the various supported address types. const ( - adtChain addressType = 0 // not iota as they need to be stable for db - adtImport addressType = 1 - adtScript addressType = 2 + 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 } diff --git a/waddrmgr/manager.go b/waddrmgr/manager.go index 7795438..0a9b386 100644 --- a/waddrmgr/manager.go +++ b/waddrmgr/manager.go @@ -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,11 +1593,29 @@ 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() - err := putChainedAddress(tx, addressID, account, ssFull, - info.branch, info.index) - if err != nil { - return err + + switch a := ma.(type) { + case *managedAddress: + err := putChainedAddress(tx, addressID, account, ssFull, + 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 + } } } @@ -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 diff --git a/waddrmgr/manager_test.go b/waddrmgr/manager_test.go index 5810304..c9b025f 100644 --- a/waddrmgr/manager_test.go +++ b/waddrmgr/manager_test.go @@ -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