waddrmgr: add support for pay to witness script address

With this commit we refactor the existing script address into a
baseScriptAddress struct and then add a new witnessScriptAddress type
that manages a pay-to-witness-script address.
This commit is contained in:
Yaacov Akiba Slama 2021-12-02 15:10:24 +01:00 committed by Roy Lee
parent 9eb48cb6ab
commit 759741dccc
3 changed files with 417 additions and 79 deletions

View file

@ -47,6 +47,10 @@ const (
// WitnessPubKey represents a p2wkh (pay-to-witness-key-hash) address // WitnessPubKey represents a p2wkh (pay-to-witness-key-hash) address
// type. // type.
WitnessPubKey WitnessPubKey
// WitnessScript represents a p2wsh (pay-to-witness-script-hash) address
// type.
WitnessScript
) )
// ManagedAddress is an interface that provides acces to information regarding // ManagedAddress is an interface that provides acces to information regarding
@ -54,7 +58,8 @@ const (
// type may provide further fields to provide information specific to that type // type may provide further fields to provide information specific to that type
// of address. // of address.
type ManagedAddress interface { type ManagedAddress interface {
// Account returns the internal account the address is associated with. // InternalAccount returns the internal account the address is
// associated with.
InternalAccount() uint32 InternalAccount() uint32
// Address returns a btcutil.Address for the backing address. // Address returns a btcutil.Address for the backing address.
@ -322,7 +327,7 @@ func (a *managedAddress) ExportPrivKey() (*btcutil.WIF, error) {
return btcutil.NewWIF(pk, a.manager.rootManager.chainParams, a.compressed) return btcutil.NewWIF(pk, a.manager.rootManager.chainParams, a.compressed)
} }
// Derivationinfo contains the information required to derive the key that // DerivationInfo contains the information required to derive the key that
// backs the address via traditional methods from the HD root. For imported // 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 // keys, the first value will be set to false to indicate that we don't know
// exactly how the key was derived. // exactly how the key was derived.
@ -502,8 +507,9 @@ func newManagedAddressFromExtKey(s *ScopedKeyManager,
return managedAddr, nil return managedAddr, nil
} }
// scriptAddress represents a pay-to-script-hash address. // baseScriptAddress represents the common fields of a pay-to-script-hash and
type scriptAddress struct { // a pay-to-witness-script-hash address.
type baseScriptAddress struct {
manager *ScopedKeyManager manager *ScopedKeyManager
account uint32 account uint32
address *btcutil.AddressScriptHash address *btcutil.AddressScriptHash
@ -512,14 +518,11 @@ type scriptAddress struct {
scriptMutex sync.Mutex scriptMutex sync.Mutex
} }
// Enforce scriptAddress satisfies the ManagedScriptAddress interface.
var _ ManagedScriptAddress = (*scriptAddress)(nil)
// unlock decrypts and stores the associated script. It will fail if the key is // 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 // 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 // script will always be a copy that may be safely used by the caller without
// worrying about it being zeroed during an address lock. // 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. // Protect concurrent access to clear text script.
a.scriptMutex.Lock() a.scriptMutex.Lock()
defer a.scriptMutex.Unlock() defer a.scriptMutex.Unlock()
@ -541,7 +544,7 @@ func (a *scriptAddress) unlock(key EncryptorDecryptor) ([]byte, error) {
} }
// lock zeroes the associated clear text script. // lock zeroes the associated clear text script.
func (a *scriptAddress) lock() { func (a *baseScriptAddress) lock() {
// Zero and nil the clear text script associated with this address. // Zero and nil the clear text script associated with this address.
a.scriptMutex.Lock() a.scriptMutex.Lock()
zero.Bytes(a.scriptClearText) zero.Bytes(a.scriptClearText)
@ -553,10 +556,32 @@ func (a *scriptAddress) lock() {
// always be the ImportedAddrAccount constant for script addresses. // always be the ImportedAddrAccount constant for script addresses.
// //
// This is part of the ManagedAddress interface implementation. // This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) InternalAccount() uint32 { func (a *baseScriptAddress) InternalAccount() uint32 {
return a.account 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 // AddrType returns the address type of the managed address. This can be used
// to quickly discern the address type without further processing // to quickly discern the address type without further processing
// //
@ -580,22 +605,6 @@ func (a *scriptAddress) AddrHash() []byte {
return a.address.Hash160()[:] return a.address.Hash160()[:]
} }
// 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 {
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 {
return false
}
// Compressed returns false since script addresses are never compressed. // Compressed returns false since script addresses are never compressed.
// //
// This is part of the ManagedAddress interface implementation. // This is part of the ManagedAddress interface implementation.
@ -612,7 +621,7 @@ func (a *scriptAddress) Used(ns walletdb.ReadBucket) bool {
// Script returns the script associated with the address. // Script returns the script associated with the address.
// //
// This implements the ScriptAddress interface. // This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) Script() ([]byte, error) { func (a *scriptAddress) Script() ([]byte, error) {
// No script is available for a watching-only address manager. // No script is available for a watching-only address manager.
if a.manager.rootManager.WatchOnly() { if a.manager.rootManager.WatchOnly() {
@ -633,6 +642,9 @@ func (a *scriptAddress) Script() ([]byte, error) {
return a.unlock(a.manager.rootManager.cryptoKeyScript) 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. // newScriptAddress initializes and returns a new pay-to-script-hash address.
func newScriptAddress(m *ScopedKeyManager, account uint32, scriptHash, func newScriptAddress(m *ScopedKeyManager, account uint32, scriptHash,
scriptEncrypted []byte) (*scriptAddress, error) { scriptEncrypted []byte) (*scriptAddress, error) {
@ -645,9 +657,131 @@ func newScriptAddress(m *ScopedKeyManager, account uint32, scriptHash,
} }
return &scriptAddress{ return &scriptAddress{
manager: m, baseScriptAddress: baseScriptAddress{
account: account, manager: m,
address: address, account: account,
scriptEncrypted: scriptEncrypted, 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 }, nil
} }

View file

@ -6,6 +6,7 @@ package waddrmgr
import ( import (
"bytes" "bytes"
"crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
@ -83,16 +84,17 @@ const (
// expectedAddr is used to house the expected return values from a managed // expectedAddr is used to house the expected return values from a managed
// address. Not all fields for used for all managed address types. // address. Not all fields for used for all managed address types.
type expectedAddr struct { type expectedAddr struct {
address string address string
addressHash []byte addressHash []byte
internal bool internal bool
compressed bool compressed bool
imported bool imported bool
pubKey []byte pubKey []byte
privKey []byte privKey []byte
privKeyWIF string privKeyWIF string
script []byte script []byte
derivationInfo DerivationPath derivationInfo DerivationPath
scriptNotSecret bool
} }
// testNamePrefix is a helper to return a prefix to show for test errors based // testNamePrefix is a helper to return a prefix to show for test errors based
@ -243,13 +245,16 @@ func testManagedScriptAddress(tc *testContext, prefix string,
// the expected error when the manager is locked. // the expected error when the manager is locked.
gotScript, err := gotAddr.Script() gotScript, err := gotAddr.Script()
switch { switch {
case tc.watchingOnly: case tc.watchingOnly && !wantAddr.scriptNotSecret:
// Confirm expected watching-only error. // Confirm expected watching-only error.
testName := fmt.Sprintf("%s Script", prefix) testName := fmt.Sprintf("%s Script", prefix)
if !checkManagerError(tc.t, testName, err, ErrWatchingOnly) { if !checkManagerError(tc.t, testName, err, ErrWatchingOnly) {
return false return false
} }
case tc.unlocked:
// Either the manger is unlocked or the script is not considered to
// be secret and is encrypted with the public key.
case tc.unlocked || wantAddr.scriptNotSecret:
if err != nil { if err != nil {
tc.t.Errorf("%s Script: unexpected error - got %v", tc.t.Errorf("%s Script: unexpected error - got %v",
prefix, err) prefix, err)
@ -260,6 +265,7 @@ func testManagedScriptAddress(tc *testContext, prefix string,
"want %x", prefix, gotScript, wantAddr.script) "want %x", prefix, gotScript, wantAddr.script)
return false return false
} }
default: default:
// Confirm expected locked error. // Confirm expected locked error.
testName := fmt.Sprintf("%s Script", prefix) testName := fmt.Sprintf("%s Script", prefix)
@ -883,10 +889,13 @@ func testImportPrivateKey(tc *testContext) bool {
// with the manager locked. // with the manager locked.
func testImportScript(tc *testContext) bool { func testImportScript(tc *testContext) bool {
tests := []struct { tests := []struct {
name string name string
in []byte in []byte
blockstamp BlockStamp isWitness bool
expected expectedAddr witnessVersion byte
isSecretScript bool
blockstamp BlockStamp
expected expectedAddr
}{ }{
{ {
name: "p2sh uncompressed pubkey", name: "p2sh uncompressed pubkey",
@ -924,6 +933,64 @@ func testImportScript(tc *testContext) bool {
// script is set to the in field during tests. // script is set to the in field during tests.
}, },
}, },
{
name: "p2wsh multisig",
isWitness: true,
witnessVersion: 0,
isSecretScript: true,
in: hexToBytes("52210305a662958b547fe25a71cd28fc7ef1c2" +
"ad4a79b12f34fc40137824b88e61199d21038552c09d9" +
"a709c8cbba6e472307d3f8383f46181895a76e01e258f" +
"09033b4a7821029dd72aba87324af59508380f9564d34" +
"b0f7b20d864d186e7d0428c9ea241c61653ae"),
expected: expectedAddr{
address: "bc1q0jljr70qchwtk3ag0w3gyg9mjhg4c95xr7h8ezhvdrfgppcpz4esfdl9an",
addressHash: hexToBytes("7cbf21f9e0c5dcbb47a87ba28220bb95d15c16861fae7c8aec68d28087011573"),
internal: false,
imported: true,
compressed: true,
// script is set to the in field during tests.
},
},
{
name: "p2wsh multisig as watch-only address",
isWitness: true,
witnessVersion: 0,
isSecretScript: false,
in: hexToBytes("52210305a662958b547fe25a71cd28fc7ef1c2" +
"ad4a79b12f34fc40137824b88e61199d21038552c09d9" +
"a709c8cbba6e472307d3f8383f46181895a76e01e258f" +
"09033b4a7821024794b65a83e9ba415096e59abc4d4d1" +
"1710968e52bf5eec56fe0e5bdb3d3ec0e53ae"),
expected: expectedAddr{
address: "bc1q3a79gkjulrsgp864yskp4d5zmwm49xsdrfwvdypkqtlpj7spd3fqrl5nes",
addressHash: hexToBytes("8f7c545a5cf8e0809f55242c1ab682dbb7529a0d1a5cc6903602fe197a016c52"),
internal: false,
imported: true,
compressed: true,
scriptNotSecret: true,
// script is set to the in field during tests.
},
},
{
name: "p2tr multisig",
isWitness: true,
witnessVersion: 1,
isSecretScript: true,
in: hexToBytes("52210305a662958b547fe25a71cd28fc7ef1c2" +
"ad4a79b12f34fc40137824b88e61199d21038552c09d9" +
"a709c8cbba6e472307d3f8383f46181895a76e01e258f" +
"09033b4a78210205ad9a838cff17d79fee2841bec72e9" +
"9b6fd4e62fd9214fcf845b1cf8438062053ae"),
expected: expectedAddr{
address: "bc1pc57jdm7kcnufnc339fvy2caflj6lkfeqasdfghftl7dd77dfpresqu7vep",
addressHash: hexToBytes("c53d26efd6c4f899e2312a584563a9fcb5fb2720ec1a945d2bff9adf79a908f3"),
internal: false,
imported: true,
compressed: true,
// script is set to the in field during tests.
},
},
} }
// The manager must be unlocked to import a private key and also for // The manager must be unlocked to import a private key and also for
@ -955,7 +1022,18 @@ func testImportScript(tc *testContext) bool {
err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
var err error var err error
addr, err = tc.manager.ImportScript(ns, test.in, &test.blockstamp)
if test.isWitness {
addr, err = tc.manager.ImportWitnessScript(
ns, test.in, &test.blockstamp,
test.witnessVersion,
test.isSecretScript,
)
} else {
addr, err = tc.manager.ImportScript(
ns, test.in, &test.blockstamp,
)
}
return err return err
}) })
if err != nil { if err != nil {
@ -979,8 +1057,28 @@ func testImportScript(tc *testContext) bool {
// Use the Address API to retrieve each of the expected // Use the Address API to retrieve each of the expected
// new addresses and ensure they're accurate. // new addresses and ensure they're accurate.
utilAddr, err := btcutil.NewAddressScriptHash(test.in, var (
chainParams) utilAddr btcutil.Address
err error
)
switch {
case test.isWitness && test.witnessVersion == 0:
scriptHash := sha256.Sum256(test.in)
utilAddr, err = btcutil.NewAddressWitnessScriptHash(
scriptHash[:], chainParams,
)
case test.isWitness && test.witnessVersion == 1:
scriptHash := sha256.Sum256(test.in)
utilAddr, err = btcutil.NewAddressTaproot(
scriptHash[:], chainParams,
)
default:
utilAddr, err = btcutil.NewAddressScriptHash(
test.in, chainParams,
)
}
if err != nil { if err != nil {
tc.t.Errorf("%s NewAddressScriptHash #%d (%s): "+ tc.t.Errorf("%s NewAddressScriptHash #%d (%s): "+
"unexpected error: %v", prefix, i, "unexpected error: %v", prefix, i,

View file

@ -1,6 +1,7 @@
package waddrmgr package waddrmgr
import ( import (
"crypto/sha256"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"sync" "sync"
@ -789,7 +790,9 @@ func (s *ScopedKeyManager) importedAddressRowToManaged(row *dbImportedAddressRow
// scriptAddressRowToManaged returns a new managed address based on script // scriptAddressRowToManaged returns a new managed address based on script
// address data loaded from the database. // address data loaded from the database.
func (s *ScopedKeyManager) scriptAddressRowToManaged(row *dbScriptAddressRow) (ManagedAddress, error) { func (s *ScopedKeyManager) scriptAddressRowToManaged(
row *dbScriptAddressRow) (ManagedAddress, error) {
// Use the crypto public key to decrypt the imported script hash. // Use the crypto public key to decrypt the imported script hash.
scriptHash, err := s.rootManager.cryptoKeyPub.Decrypt(row.encryptedHash) scriptHash, err := s.rootManager.cryptoKeyPub.Decrypt(row.encryptedHash)
if err != nil { if err != nil {
@ -800,6 +803,24 @@ func (s *ScopedKeyManager) scriptAddressRowToManaged(row *dbScriptAddressRow) (M
return newScriptAddress(s, row.account, scriptHash, row.encryptedScript) return newScriptAddress(s, row.account, scriptHash, row.encryptedScript)
} }
// witnessScriptAddressRowToManaged returns a new managed address based on
// witness script address data loaded from the database.
func (s *ScopedKeyManager) witnessScriptAddressRowToManaged(
row *dbWitnessScriptAddressRow) (ManagedAddress, error) {
// Use the crypto public key to decrypt the imported script hash.
scriptHash, err := s.rootManager.cryptoKeyPub.Decrypt(row.encryptedHash)
if err != nil {
str := "failed to decrypt imported witness script hash"
return nil, managerError(ErrCrypto, str, err)
}
return newWitnessScriptAddress(
s, row.account, scriptHash, row.encryptedScript,
row.witnessVersion, row.isSecretScript,
)
}
// rowInterfaceToManaged returns a new managed address based on the given // rowInterfaceToManaged returns a new managed address based on the given
// address data loaded from the database. It will automatically select the // address data loaded from the database. It will automatically select the
// appropriate type. // appropriate type.
@ -817,6 +838,9 @@ func (s *ScopedKeyManager) rowInterfaceToManaged(ns walletdb.ReadBucket,
case *dbScriptAddressRow: case *dbScriptAddressRow:
return s.scriptAddressRowToManaged(row) return s.scriptAddressRowToManaged(row)
case *dbWitnessScriptAddressRow:
return s.witnessScriptAddressRowToManaged(row)
} }
str := fmt.Sprintf("unsupported address type %T", rowInterface) str := fmt.Sprintf("unsupported address type %T", rowInterface)
@ -2029,16 +2053,63 @@ func (s *ScopedKeyManager) toImportedPublicManagedAddress(
func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket, func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket,
script []byte, bs *BlockStamp) (ManagedScriptAddress, error) { script []byte, bs *BlockStamp) (ManagedScriptAddress, error) {
return s.importScriptAddress(ns, script, bs, Script, 0, true)
}
// 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, witnessVersion byte,
isSecretScript bool) (ManagedScriptAddress, error) {
return s.importScriptAddress(
ns, script, bs, WitnessScript, witnessVersion, isSecretScript,
)
}
// importScriptAddress imports a new pay-to-script or pay-to-witness-script
// address.
func (s *ScopedKeyManager) importScriptAddress(ns walletdb.ReadWriteBucket,
script []byte, bs *BlockStamp, addrType AddressType,
witnessVersion byte, isSecretScript bool) (ManagedScriptAddress,
error) {
s.mtx.Lock() s.mtx.Lock()
defer s.mtx.Unlock() defer s.mtx.Unlock()
// The manager must be unlocked to encrypt the imported script. // The manager must be unlocked to encrypt the imported script.
if s.rootManager.IsLocked() { if isSecretScript && s.rootManager.IsLocked() {
return nil, managerError(ErrLocked, errLocked, nil) return nil, managerError(ErrLocked, errLocked, nil)
} }
// A secret script can only be used with a non-watch only manager. If
// a wallet is watch-only then the script must be encrypted with the
// public encryption key.
if isSecretScript && s.rootManager.WatchOnly() {
return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil)
}
// Witness script addresses use a SHA256.
var scriptHash []byte
switch addrType {
case WitnessScript:
digest := sha256.Sum256(script)
scriptHash = digest[:]
default:
scriptHash = btcutil.Hash160(script)
}
// Prevent duplicates. // Prevent duplicates.
scriptHash := btcutil.Hash160(script)
alreadyExists := s.existsAddress(ns, scriptHash) alreadyExists := s.existsAddress(ns, scriptHash)
if alreadyExists { if alreadyExists {
str := fmt.Sprintf("address for script hash %x already exists", str := fmt.Sprintf("address for script hash %x already exists",
@ -2055,18 +2126,21 @@ func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket,
return nil, managerError(ErrCrypto, str, err) return nil, managerError(ErrCrypto, str, err)
} }
// Encrypt the script for storage in database using the crypto script // If a key isn't considered to be "secret", we encrypt it with the
// key when not a watching-only address manager. // public key, so we can create script addresses that also work in
var encryptedScript []byte // watch-only mode.
if !s.rootManager.WatchOnly() { cryptoKey := s.rootManager.cryptoKeyScript
encryptedScript, err = s.rootManager.cryptoKeyScript.Encrypt( if !isSecretScript {
script, cryptoKey = s.rootManager.cryptoKeyPub
) }
if err != nil {
str := fmt.Sprintf("failed to encrypt script for %x", // Encrypt the script for storage in database using the selected crypto
scriptHash) // key.
return nil, managerError(ErrCrypto, str, err) encryptedScript, err := cryptoKey.Encrypt(script)
} if err != nil {
str := fmt.Sprintf("failed to encrypt script for %x",
scriptHash)
return nil, managerError(ErrCrypto, str, err)
} }
// The start block needs to be updated when the newly imported address // The start block needs to be updated when the newly imported address
@ -2080,10 +2154,20 @@ func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket,
// Save the new imported address to the db and update start block (if // Save the new imported address to the db and update start block (if
// needed) in a single transaction. // needed) in a single transaction.
err = putScriptAddress( switch addrType {
ns, &s.scope, scriptHash, ImportedAddrAccount, ssNone, case WitnessScript:
encryptedHash, encryptedScript, err = putWitnessScriptAddress(
) ns, &s.scope, scriptHash, ImportedAddrAccount, ssNone,
witnessVersion, isSecretScript, encryptedHash,
encryptedScript,
)
default:
err = putScriptAddress(
ns, &s.scope, scriptHash, ImportedAddrAccount, ssNone,
encryptedHash, encryptedScript,
)
}
if err != nil { if err != nil {
return nil, maybeConvertDbError(err) return nil, maybeConvertDbError(err)
} }
@ -2107,21 +2191,43 @@ func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket,
// when not a watching-only address manager, make a copy of the script // 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 // since it will be cleared on lock and the script the caller passed
// should not be cleared out from under the caller. // should not be cleared out from under the caller.
scriptAddr, err := newScriptAddress( var (
s, ImportedAddrAccount, scriptHash, encryptedScript, managedAddr ManagedScriptAddress
baseScriptAddr *baseScriptAddress
) )
if err != nil { switch addrType {
return nil, err case WitnessScript:
} witnessAddr, err := newWitnessScriptAddress(
if !s.rootManager.WatchOnly() { s, ImportedAddrAccount, scriptHash, encryptedScript,
scriptAddr.scriptClearText = make([]byte, len(script)) witnessVersion, isSecretScript,
copy(scriptAddr.scriptClearText, script) )
if err != nil {
return nil, err
}
managedAddr = witnessAddr
baseScriptAddr = &witnessAddr.baseScriptAddress
default:
scriptAddr, err := newScriptAddress(
s, ImportedAddrAccount, scriptHash, encryptedScript,
)
if err != nil {
return nil, err
}
managedAddr = scriptAddr
baseScriptAddr = &scriptAddr.baseScriptAddress
} }
// Even if the script is secret, we are currently unlocked, so we keep a
// clear text copy of the script around to avoid decrypting it on each
// access.
baseScriptAddr.scriptClearText = make([]byte, len(script))
copy(baseScriptAddr.scriptClearText, script)
// Add the new managed address to the cache of recent addresses and // Add the new managed address to the cache of recent addresses and
// return it. // return it.
s.addrs[addrKey(scriptHash)] = scriptAddr s.addrs[addrKey(scriptHash)] = managedAddr
return scriptAddr, nil return managedAddr, nil
} }
// lookupAccount loads account number stored in the manager for the given // lookupAccount loads account number stored in the manager for the given