// 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/lbryio/lbcd/btcec" "github.com/lbryio/lbcd/txscript" btcutil "github.com/lbryio/lbcutil" "github.com/lbryio/lbcutil/hdkeychain" "github.com/lbryio/lbcwallet/internal/zero" "github.com/lbryio/lbcwallet/walletdb" ) // AddressType represents the various address types waddrmgr is currently able // to generate, and maintain. // // NOTE: These MUST be stable as they're used for scope address schema // recognition within the database. type AddressType uint8 const ( // PubKeyHash is a regular p2pkh address. PubKeyHash AddressType = iota // Script reprints a raw script address. Script // RawPubKey is just raw public key to be used within scripts, This // type indicates that a scoped manager with this address type // shouldn't be consulted during historical rescans. RawPubKey // 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 // WitnessScript represents a p2wsh (pay-to-witness-script-hash) address // type. WitnessScript ) // 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 { // InternalAccount returns the internal account the address is // associated with. InternalAccount() 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 // AddrType returns the address type of the managed address. This can // be used to quickly discern the address type without further // processing AddrType() AddressType } // 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) // DerivationInfo contains the information required to derive the key // that backs the address via traditional methods from the HD root. For // imported keys, the first value will be set to false to indicate that // we don't know exactly how the key was derived. DerivationInfo() (KeyScope, DerivationPath, 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 *ScopedKeyManager derivationPath DerivationPath address btcutil.Address imported bool internal bool compressed bool addrType AddressType pubKey *btcec.PublicKey privKeyEncrypted []byte // nil if part of watch-only account 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 the address belongs to a watch-only account, the encrypted private // key won't be present, so we'll return an error. if len(a.privKeyEncrypted) == 0 { return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil) } 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() } // InternalAccount returns the internal account number the address is associated // with. // // This is part of the ManagedAddress interface implementation. func (a *managedAddress) InternalAccount() uint32 { return a.derivationPath.InternalAccount } // AddrType returns the address type of the managed address. This can be used // to quickly discern the address type without further processing // // This is part of the ManagedAddress interface implementation. func (a *managedAddress) AddrType() AddressType { return a.addrType } // 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.rootManager.WatchOnly() { 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.rootManager.IsLocked() { 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.rootManager.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.rootManager.chainParams, a.compressed) } // DerivationInfo contains the information required to derive the key that // backs the address via traditional methods from the HD root. For imported // keys, the first value will be set to false to indicate that we don't know // exactly how the key was derived. // // This is part of the ManagedPubKeyAddress interface implementation. func (a *managedAddress) DerivationInfo() (KeyScope, DerivationPath, bool) { var ( scope KeyScope path DerivationPath ) // If this key is imported, then we can't return any information as we // don't know precisely how the key was derived. if a.imported { return scope, path, false } return a.manager.Scope(), a.derivationPath, true } // 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 *ScopedKeyManager, derivationPath DerivationPath, 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 { case NestedWitnessPubKey: // 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 improvements, // and malleability fixes. // First, we'll generate a normal p2wkh address from the pubkey hash. witAddr, err := btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, m.rootManager.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.rootManager.chainParams, ) if err != nil { return nil, err } case PubKeyHash: address, err = btcutil.NewAddressPubKeyHash( pubKeyHash, m.rootManager.chainParams, ) if err != nil { return nil, err } case WitnessPubKey: address, err = btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, m.rootManager.chainParams, ) if err != nil { return nil, err } } return &managedAddress{ manager: m, address: address, derivationPath: derivationPath, 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(s *ScopedKeyManager, derivationPath DerivationPath, 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 := s.rootManager.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( s, derivationPath, 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(s *ScopedKeyManager, derivationPath DerivationPath, 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( s, derivationPath, privKey, true, addrType, ) if err != nil { return nil, err } } else { pubKey, err := key.ECPubKey() if err != nil { return nil, err } managedAddr, err = newManagedAddressWithoutPrivKey( s, derivationPath, pubKey, true, addrType, ) if err != nil { return nil, err } } return managedAddr, nil } // baseScriptAddress represents the common fields of a pay-to-script-hash and // a pay-to-witness-script-hash address. type baseScriptAddress struct { manager *ScopedKeyManager account uint32 address *btcutil.AddressScriptHash scriptEncrypted []byte scriptClearText []byte scriptMutex sync.Mutex } // 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 *baseScriptAddress) unlock(key EncryptorDecryptor) ([]byte, error) { // Protect concurrent access to clear text script. a.scriptMutex.Lock() defer a.scriptMutex.Unlock() if len(a.scriptClearText) == 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.scriptClearText = script } scriptCopy := make([]byte, len(a.scriptClearText)) copy(scriptCopy, a.scriptClearText) return scriptCopy, nil } // lock zeroes the associated clear text script. func (a *baseScriptAddress) lock() { // Zero and nil the clear text script associated with this address. a.scriptMutex.Lock() zero.Bytes(a.scriptClearText) a.scriptClearText = nil a.scriptMutex.Unlock() } // InternalAccount 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 *baseScriptAddress) InternalAccount() uint32 { return a.account } // 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 *baseScriptAddress) 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 *baseScriptAddress) Internal() bool { return false } // scriptAddress represents a pay-to-script-hash address. type scriptAddress struct { baseScriptAddress address *btcutil.AddressScriptHash } // AddrType returns the address type of the managed address. This can be used // to quickly discern the address type without further processing // // This is part of the ManagedAddress interface implementation. func (a *scriptAddress) AddrType() AddressType { return Script } // 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()[:] } // 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 is part of the ManagedAddress interface implementation. func (a *scriptAddress) Script() ([]byte, error) { // No script is available for a watching-only address manager. if a.manager.rootManager.WatchOnly() { 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.rootManager.IsLocked() { 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.rootManager.cryptoKeyScript) } // Enforce scriptAddress satisfies the ManagedScriptAddress interface. var _ ManagedScriptAddress = (*scriptAddress)(nil) // newScriptAddress initializes and returns a new pay-to-script-hash address. func newScriptAddress(m *ScopedKeyManager, account uint32, scriptHash, scriptEncrypted []byte) (*scriptAddress, error) { address, err := btcutil.NewAddressScriptHashFromHash( scriptHash, m.rootManager.chainParams, ) if err != nil { return nil, err } return &scriptAddress{ baseScriptAddress: baseScriptAddress{ manager: m, account: account, scriptEncrypted: scriptEncrypted, }, address: address, }, nil } // witnessScriptAddress represents a pay-to-witness-script-hash address. type witnessScriptAddress struct { baseScriptAddress address btcutil.Address // witnessVersion is the version of the witness script. witnessVersion byte // isSecretScript denotes whether the script is considered to be "secret" // and encrypted with the script encryption key or "public" and // therefore only encrypted with the public encryption key. isSecretScript bool } // AddrType returns the address type of the managed address. This can be used // to quickly discern the address type without further processing // // This is part of the ManagedAddress interface implementation. func (a *witnessScriptAddress) AddrType() AddressType { return WitnessScript } // Address returns the btcutil.Address which represents the managed address. // This will be a pay-to-witness-script-hash address. // // This is part of the ManagedAddress interface implementation. func (a *witnessScriptAddress) Address() btcutil.Address { return a.address } // AddrHash returns the script hash for the address. // // This is part of the ManagedAddress interface implementation. func (a *witnessScriptAddress) AddrHash() []byte { return a.address.ScriptAddress() } // Compressed returns true since witness script addresses are always compressed. // // This is part of the ManagedAddress interface implementation. func (a *witnessScriptAddress) Compressed() bool { return true } // Used returns true if the address has been used in a transaction. // // This is part of the ManagedAddress interface implementation. func (a *witnessScriptAddress) Used(ns walletdb.ReadBucket) bool { return a.manager.fetchUsed(ns, a.AddrHash()) } // Script returns the script associated with the address. // // This is part of the ManagedAddress interface implementation. func (a *witnessScriptAddress) Script() ([]byte, error) { // No script is available for a watching-only address manager. if a.isSecretScript && a.manager.rootManager.WatchOnly() { 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.isSecretScript && a.manager.rootManager.IsLocked() { return nil, managerError(ErrLocked, errLocked, nil) } cryptoKey := a.manager.rootManager.cryptoKeyScript if !a.isSecretScript { cryptoKey = a.manager.rootManager.cryptoKeyPub } // 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(cryptoKey) } // Enforce witnessScriptAddress satisfies the ManagedScriptAddress interface. var _ ManagedScriptAddress = (*witnessScriptAddress)(nil) // newWitnessScriptAddress initializes and returns a new // pay-to-witness-script-hash address. func newWitnessScriptAddress(m *ScopedKeyManager, account uint32, scriptHash, scriptEncrypted []byte, witnessVersion byte, isSecretScript bool) (*witnessScriptAddress, error) { var ( address btcutil.Address err error ) switch witnessVersion { case 0x00: address, err = btcutil.NewAddressWitnessScriptHash( scriptHash, m.rootManager.chainParams, ) case 0x01: address, err = btcutil.NewAddressTaproot( scriptHash, m.rootManager.chainParams, ) } if err != nil { return nil, err } return &witnessScriptAddress{ baseScriptAddress: baseScriptAddress{ manager: m, account: account, scriptEncrypted: scriptEncrypted, }, address: address, witnessVersion: witnessVersion, isSecretScript: isSecretScript, }, nil }