Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for pay to witness script address #625

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
103 changes: 88 additions & 15 deletions waddrmgr/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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)
Expand All @@ -553,15 +577,15 @@ 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
}

// 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 {
func (a *baseScriptAddress) AddrType() AddressType {
return Script
}

Expand All @@ -573,33 +597,48 @@ 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.
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
}

// 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 *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
}

Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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
}
35 changes: 31 additions & 4 deletions waddrmgr/scoped_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above about this sentence. Or maybe we can add a switch to decide whether this is considered to be that private? So the user of the library can choose what crypto key to use? cc @Roasbeef

// 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()
Expand Down Expand Up @@ -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
Expand Down