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