From dafb7c4c66f351bd129af5b76af327ba4887dcc7 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Tue, 12 Oct 2021 11:42:13 +0300 Subject: [PATCH] Add support for pay to witness script address --- waddrmgr/address.go | 103 +++++++++++++++++++++++++++++++------ waddrmgr/scoped_manager.go | 35 +++++++++++-- 2 files changed, 119 insertions(+), 19 deletions(-) diff --git a/waddrmgr/address.go b/waddrmgr/address.go index 45df8e05f4..3a868ebb86 100644 --- a/waddrmgr/address.go +++ b/waddrmgr/address.go @@ -119,6 +119,8 @@ type ManagedScriptAddress interface { // Script returns the script associated with the address. Script() ([]byte, error) + + BaseScriptAddress() *baseScriptAddress } // managedAddress represents a public key address. It also may or may not have @@ -502,8 +504,9 @@ func newManagedAddressFromExtKey(s *ScopedKeyManager, return managedAddr, nil } -// scriptAddress represents a pay-to-script-hash address. -type scriptAddress struct { +// 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 @@ -512,14 +515,35 @@ type scriptAddress struct { scriptMutex sync.Mutex } +// BaseScriptAddress returns the common part of a pay-to-script-hash and +// a pay-to-witness-script-hash address. +func (a *baseScriptAddress) BaseScriptAddress() *baseScriptAddress { + return a +} + +// scriptAddress represents a pay-to-script-hash address. +type scriptAddress struct { + baseScriptAddress + address *btcutil.AddressScriptHash +} + // Enforce scriptAddress satisfies the ManagedScriptAddress interface. var _ ManagedScriptAddress = (*scriptAddress)(nil) +// witnessScriptAddress represents a pay-to-witness-script-hash address. +type witnessScriptAddress struct { + baseScriptAddress + address *btcutil.AddressWitnessScriptHash +} + +// Enforce witnessScriptAddress satisfies the ManagedScriptAddress interface. +var _ ManagedScriptAddress = (*witnessScriptAddress)(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) { +func (a *baseScriptAddress) unlock(key EncryptorDecryptor) ([]byte, error) { // Protect concurrent access to clear text script. a.scriptMutex.Lock() defer a.scriptMutex.Unlock() @@ -541,7 +565,7 @@ func (a *scriptAddress) unlock(key EncryptorDecryptor) ([]byte, error) { } // lock zeroes the associated clear text private key. -func (a *scriptAddress) lock() { +func (a *baseScriptAddress) lock() { // Zero and nil the clear text script associated with this address. a.scriptMutex.Lock() zero.Bytes(a.scriptCT) @@ -553,7 +577,7 @@ func (a *scriptAddress) lock() { // always be the ImportedAddrAccount constant for script addresses. // // This is part of the ManagedAddress interface implementation. -func (a *scriptAddress) InternalAccount() uint32 { +func (a *baseScriptAddress) InternalAccount() uint32 { return a.account } @@ -561,7 +585,7 @@ func (a *scriptAddress) InternalAccount() uint32 { // to quickly discern the address type without further processing // // This is part of the ManagedAddress interface implementation. -func (a *scriptAddress) AddrType() AddressType { +func (a *baseScriptAddress) AddrType() AddressType { return Script } @@ -573,6 +597,14 @@ func (a *scriptAddress) Address() btcutil.Address { return a.address } +// 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. @@ -580,11 +612,18 @@ func (a *scriptAddress) AddrHash() []byte { return a.address.Hash160()[:] } +// 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() +} + // 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 { +func (a *baseScriptAddress) Imported() bool { return true } @@ -592,14 +631,14 @@ func (a *scriptAddress) Imported() bool { // 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 { +func (a *baseScriptAddress) 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 { +func (a *baseScriptAddress) Compressed() bool { return false } @@ -610,10 +649,17 @@ func (a *scriptAddress) Used(ns walletdb.ReadBucket) bool { return a.manager.fetchUsed(ns, a.AddrHash()) } +// 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 implements the ScriptAddress interface. -func (a *scriptAddress) Script() ([]byte, error) { +func (a *baseScriptAddress) Script() ([]byte, error) { // No script is available for a watching-only address manager. if a.manager.rootManager.WatchOnly() { return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil) @@ -633,9 +679,12 @@ func (a *scriptAddress) Script() ([]byte, error) { return a.unlock(a.manager.rootManager.cryptoKeyScript) } +type newBaseScriptAddress func(m *ScopedKeyManager, account uint32, scriptHash, + scriptEncrypted []byte) (ManagedScriptAddress, error) + // newScriptAddress initializes and returns a new pay-to-script-hash address. func newScriptAddress(m *ScopedKeyManager, account uint32, scriptHash, - scriptEncrypted []byte) (*scriptAddress, error) { + scriptEncrypted []byte) (ManagedScriptAddress, error) { address, err := btcutil.NewAddressScriptHashFromHash( scriptHash, m.rootManager.chainParams, @@ -645,9 +694,33 @@ func newScriptAddress(m *ScopedKeyManager, account uint32, scriptHash, } return &scriptAddress{ - manager: m, - account: account, - address: address, - scriptEncrypted: scriptEncrypted, + baseScriptAddress: baseScriptAddress{ + manager: m, + account: account, + scriptEncrypted: scriptEncrypted, + }, + address: address, + }, nil +} + +// newWitnessScriptAddress initializes and returns a new +// pay-to-script-hash address. +func newWitnessScriptAddress(m *ScopedKeyManager, account uint32, scriptHash, + scriptEncrypted []byte) (ManagedScriptAddress, error) { + + address, err := btcutil.NewAddressWitnessScriptHash( + scriptHash, m.rootManager.chainParams, + ) + if err != nil { + return nil, err + } + + return &witnessScriptAddress{ + baseScriptAddress: baseScriptAddress{ + manager: m, + account: account, + scriptEncrypted: scriptEncrypted, + }, + address: address, }, nil } diff --git a/waddrmgr/scoped_manager.go b/waddrmgr/scoped_manager.go index 6d02c31514..9fb671ce27 100644 --- a/waddrmgr/scoped_manager.go +++ b/waddrmgr/scoped_manager.go @@ -796,7 +796,11 @@ func (s *ScopedKeyManager) scriptAddressRowToManaged(row *dbScriptAddressRow) (M str := "failed to decrypt imported script hash" return nil, managerError(ErrCrypto, str, err) } - + if len(scriptHash) == 32 { + return newWitnessScriptAddress(s, row.account, scriptHash, + row.encryptedScript, + ) + } return newScriptAddress(s, row.account, scriptHash, row.encryptedScript) } @@ -2028,6 +2032,29 @@ func (s *ScopedKeyManager) toImportedPublicManagedAddress( // generally unexpected. func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket, script []byte, bs *BlockStamp) (ManagedScriptAddress, error) { + return s.baseImportScript(ns, script, bs, newScriptAddress) +} + +// ImportWitnessScript imports a user-provided script into the address manager. +// The imported script will act as a pay-to-witness-script-hash address. +// +// All imported script addresses will be part of the account defined by the +// ImportedAddrAccount constant. +// +// When the address manager is watching-only, the script itself will not be +// stored or available since it is considered private data. +// +// This function will return an error if the address manager is locked and not +// watching-only, or the address already exists. Any other errors returned are +// generally unexpected. +func (s *ScopedKeyManager) ImportWitnessScript(ns walletdb.ReadWriteBucket, + script []byte, bs *BlockStamp) (ManagedScriptAddress, error) { + return s.baseImportScript(ns, script, bs, newWitnessScriptAddress) +} + +func (s *ScopedKeyManager) baseImportScript(ns walletdb.ReadWriteBucket, + script []byte, bs *BlockStamp, + newFunc newBaseScriptAddress) (ManagedScriptAddress, error) { s.mtx.Lock() defer s.mtx.Unlock() @@ -2107,15 +2134,15 @@ func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket, // when not a watching-only address manager, make a copy of the script // since it will be cleared on lock and the script the caller passed // should not be cleared out from under the caller. - scriptAddr, err := newScriptAddress( + scriptAddr, err := newFunc( s, ImportedAddrAccount, scriptHash, encryptedScript, ) if err != nil { return nil, err } if !s.rootManager.WatchOnly() { - scriptAddr.scriptCT = make([]byte, len(script)) - copy(scriptAddr.scriptCT, script) + scriptAddr.BaseScriptAddress().scriptCT = make([]byte, len(script)) + copy(scriptAddr.BaseScriptAddress().scriptCT, script) } // Add the new managed address to the cache of recent addresses and