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:
parent
9eb48cb6ab
commit
759741dccc
3 changed files with 417 additions and 79 deletions
|
@ -47,6 +47,10 @@ const (
|
|||
// 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
|
||||
|
@ -54,7 +58,8 @@ const (
|
|||
// type may provide further fields to provide information specific to that type
|
||||
// of address.
|
||||
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
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// keys, the first value will be set to false to indicate that we don't know
|
||||
// exactly how the key was derived.
|
||||
|
@ -502,8 +507,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 +518,11 @@ type scriptAddress struct {
|
|||
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
|
||||
// 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 +544,7 @@ func (a *scriptAddress) unlock(key EncryptorDecryptor) ([]byte, error) {
|
|||
}
|
||||
|
||||
// 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.
|
||||
a.scriptMutex.Lock()
|
||||
zero.Bytes(a.scriptClearText)
|
||||
|
@ -553,10 +556,32 @@ 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
|
||||
}
|
||||
|
||||
// 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
|
||||
//
|
||||
|
@ -580,22 +605,6 @@ func (a *scriptAddress) AddrHash() []byte {
|
|||
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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// This implements the ScriptAddress interface.
|
||||
// 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() {
|
||||
|
@ -633,6 +642,9 @@ func (a *scriptAddress) Script() ([]byte, error) {
|
|||
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) {
|
||||
|
@ -645,9 +657,131 @@ 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package waddrmgr
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -83,16 +84,17 @@ const (
|
|||
// expectedAddr is used to house the expected return values from a managed
|
||||
// address. Not all fields for used for all managed address types.
|
||||
type expectedAddr struct {
|
||||
address string
|
||||
addressHash []byte
|
||||
internal bool
|
||||
compressed bool
|
||||
imported bool
|
||||
pubKey []byte
|
||||
privKey []byte
|
||||
privKeyWIF string
|
||||
script []byte
|
||||
derivationInfo DerivationPath
|
||||
address string
|
||||
addressHash []byte
|
||||
internal bool
|
||||
compressed bool
|
||||
imported bool
|
||||
pubKey []byte
|
||||
privKey []byte
|
||||
privKeyWIF string
|
||||
script []byte
|
||||
derivationInfo DerivationPath
|
||||
scriptNotSecret bool
|
||||
}
|
||||
|
||||
// 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.
|
||||
gotScript, err := gotAddr.Script()
|
||||
switch {
|
||||
case tc.watchingOnly:
|
||||
case tc.watchingOnly && !wantAddr.scriptNotSecret:
|
||||
// Confirm expected watching-only error.
|
||||
testName := fmt.Sprintf("%s Script", prefix)
|
||||
if !checkManagerError(tc.t, testName, err, ErrWatchingOnly) {
|
||||
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 {
|
||||
tc.t.Errorf("%s Script: unexpected error - got %v",
|
||||
prefix, err)
|
||||
|
@ -260,6 +265,7 @@ func testManagedScriptAddress(tc *testContext, prefix string,
|
|||
"want %x", prefix, gotScript, wantAddr.script)
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
// Confirm expected locked error.
|
||||
testName := fmt.Sprintf("%s Script", prefix)
|
||||
|
@ -883,10 +889,13 @@ func testImportPrivateKey(tc *testContext) bool {
|
|||
// with the manager locked.
|
||||
func testImportScript(tc *testContext) bool {
|
||||
tests := []struct {
|
||||
name string
|
||||
in []byte
|
||||
blockstamp BlockStamp
|
||||
expected expectedAddr
|
||||
name string
|
||||
in []byte
|
||||
isWitness bool
|
||||
witnessVersion byte
|
||||
isSecretScript bool
|
||||
blockstamp BlockStamp
|
||||
expected expectedAddr
|
||||
}{
|
||||
{
|
||||
name: "p2sh uncompressed pubkey",
|
||||
|
@ -924,6 +933,64 @@ func testImportScript(tc *testContext) bool {
|
|||
// 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
|
||||
|
@ -955,7 +1022,18 @@ func testImportScript(tc *testContext) bool {
|
|||
err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
|
||||
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
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
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -979,8 +1057,28 @@ func testImportScript(tc *testContext) bool {
|
|||
|
||||
// Use the Address API to retrieve each of the expected
|
||||
// new addresses and ensure they're accurate.
|
||||
utilAddr, err := btcutil.NewAddressScriptHash(test.in,
|
||||
chainParams)
|
||||
var (
|
||||
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 {
|
||||
tc.t.Errorf("%s NewAddressScriptHash #%d (%s): "+
|
||||
"unexpected error: %v", prefix, i,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package waddrmgr
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
@ -789,7 +790,9 @@ func (s *ScopedKeyManager) importedAddressRowToManaged(row *dbImportedAddressRow
|
|||
|
||||
// scriptAddressRowToManaged returns a new managed address based on script
|
||||
// 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.
|
||||
scriptHash, err := s.rootManager.cryptoKeyPub.Decrypt(row.encryptedHash)
|
||||
if err != nil {
|
||||
|
@ -800,6 +803,24 @@ func (s *ScopedKeyManager) scriptAddressRowToManaged(row *dbScriptAddressRow) (M
|
|||
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
|
||||
// address data loaded from the database. It will automatically select the
|
||||
// appropriate type.
|
||||
|
@ -817,6 +838,9 @@ func (s *ScopedKeyManager) rowInterfaceToManaged(ns walletdb.ReadBucket,
|
|||
|
||||
case *dbScriptAddressRow:
|
||||
return s.scriptAddressRowToManaged(row)
|
||||
|
||||
case *dbWitnessScriptAddressRow:
|
||||
return s.witnessScriptAddressRowToManaged(row)
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("unsupported address type %T", rowInterface)
|
||||
|
@ -2029,16 +2053,63 @@ func (s *ScopedKeyManager) toImportedPublicManagedAddress(
|
|||
func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket,
|
||||
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()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
scriptHash := btcutil.Hash160(script)
|
||||
alreadyExists := s.existsAddress(ns, scriptHash)
|
||||
if alreadyExists {
|
||||
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)
|
||||
}
|
||||
|
||||
// Encrypt the script for storage in database using the crypto script
|
||||
// key when not a watching-only address manager.
|
||||
var encryptedScript []byte
|
||||
if !s.rootManager.WatchOnly() {
|
||||
encryptedScript, err = s.rootManager.cryptoKeyScript.Encrypt(
|
||||
script,
|
||||
)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("failed to encrypt script for %x",
|
||||
scriptHash)
|
||||
return nil, managerError(ErrCrypto, str, err)
|
||||
}
|
||||
// If a key isn't considered to be "secret", we encrypt it with the
|
||||
// public key, so we can create script addresses that also work in
|
||||
// watch-only mode.
|
||||
cryptoKey := s.rootManager.cryptoKeyScript
|
||||
if !isSecretScript {
|
||||
cryptoKey = s.rootManager.cryptoKeyPub
|
||||
}
|
||||
|
||||
// Encrypt the script for storage in database using the selected crypto
|
||||
// key.
|
||||
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
|
||||
|
@ -2080,10 +2154,20 @@ func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket,
|
|||
|
||||
// Save the new imported address to the db and update start block (if
|
||||
// needed) in a single transaction.
|
||||
err = putScriptAddress(
|
||||
ns, &s.scope, scriptHash, ImportedAddrAccount, ssNone,
|
||||
encryptedHash, encryptedScript,
|
||||
)
|
||||
switch addrType {
|
||||
case WitnessScript:
|
||||
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 {
|
||||
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
|
||||
// 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(
|
||||
s, ImportedAddrAccount, scriptHash, encryptedScript,
|
||||
var (
|
||||
managedAddr ManagedScriptAddress
|
||||
baseScriptAddr *baseScriptAddress
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !s.rootManager.WatchOnly() {
|
||||
scriptAddr.scriptClearText = make([]byte, len(script))
|
||||
copy(scriptAddr.scriptClearText, script)
|
||||
switch addrType {
|
||||
case WitnessScript:
|
||||
witnessAddr, err := newWitnessScriptAddress(
|
||||
s, ImportedAddrAccount, scriptHash, encryptedScript,
|
||||
witnessVersion, isSecretScript,
|
||||
)
|
||||
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
|
||||
// return it.
|
||||
s.addrs[addrKey(scriptHash)] = scriptAddr
|
||||
return scriptAddr, nil
|
||||
s.addrs[addrKey(scriptHash)] = managedAddr
|
||||
return managedAddr, nil
|
||||
}
|
||||
|
||||
// lookupAccount loads account number stored in the manager for the given
|
||||
|
|
Loading…
Reference in a new issue