aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/script/descriptor.cpp48
-rw-r--r--src/script/descriptor.h8
-rw-r--r--src/wallet/rpcdump.cpp6
-rwxr-xr-xtest/functional/wallet_importmulti.py28
4 files changed, 75 insertions, 15 deletions
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 9be87fabb0..50119ba184 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -164,6 +164,9 @@ struct PubkeyProvider
/** Get the descriptor string form including private data (if available in arg). */
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0;
+
+ /** Derive a private key, if private data is available in arg. */
+ virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0;
};
class OriginPubkeyProvider final : public PubkeyProvider
@@ -195,6 +198,10 @@ public:
ret = "[" + OriginString() + "]" + std::move(sub);
return true;
}
+ bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
+ {
+ return m_provider->GetPrivKey(pos, arg, key);
+ }
};
/** An object representing a parsed constant public key in a descriptor. */
@@ -222,6 +229,10 @@ public:
ret = EncodeSecret(key);
return true;
}
+ bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
+ {
+ return arg.GetKey(m_pubkey.GetID(), key);
+ }
};
enum class DeriveType {
@@ -266,14 +277,9 @@ public:
{
if (key) {
if (IsHardened()) {
- CExtKey extkey;
- if (!GetExtKey(arg, extkey)) return false;
- for (auto entry : m_path) {
- extkey.Derive(extkey, entry);
- }
- if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
- if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL);
- *key = extkey.Neuter().pubkey;
+ CKey priv_key;
+ if (!GetPrivKey(pos, arg, priv_key)) return false;
+ *key = priv_key.GetPubKey();
} else {
// TODO: optimize by caching
CExtPubKey extkey = m_extkey;
@@ -312,6 +318,18 @@ public:
}
return true;
}
+ bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
+ {
+ CExtKey extkey;
+ if (!GetExtKey(arg, extkey)) return false;
+ for (auto entry : m_path) {
+ extkey.Derive(extkey, entry);
+ }
+ if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
+ if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL);
+ key = extkey.key;
+ return true;
+ }
};
/** Base class for all Descriptor implementations. */
@@ -462,6 +480,20 @@ public:
Span<const unsigned char> span = MakeSpan(cache);
return ExpandHelper(pos, DUMMY_SIGNING_PROVIDER, &span, output_scripts, out, nullptr) && span.size() == 0;
}
+
+ void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const final
+ {
+ for (const auto& p : m_pubkey_args) {
+ CKey key;
+ if (!p->GetPrivKey(pos, provider, key)) continue;
+ out.keys.emplace(key.GetPubKey().GetID(), key);
+ }
+ if (m_script_arg) {
+ FlatSigningProvider subprovider;
+ m_script_arg->ExpandPrivate(pos, provider, subprovider);
+ out = Merge(out, subprovider);
+ }
+ }
};
/** Construct a vector with one element, which is moved into it. */
diff --git a/src/script/descriptor.h b/src/script/descriptor.h
index 907a102284..af7ae229ca 100644
--- a/src/script/descriptor.h
+++ b/src/script/descriptor.h
@@ -60,6 +60,14 @@ struct Descriptor {
* out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider).
*/
virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0;
+
+ /** Expand the private key for a descriptor at a specified position, if possible.
+ *
+ * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored.
+ * provider: the provider to query for the private keys.
+ * out: any private keys available for the specified pos will be placed here.
+ */
+ virtual void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const = 0;
};
/** Parse a descriptor string. Included private keys are put in out.
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 2f2fe33ba4..3112dca9f5 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1165,8 +1165,7 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
- // Expand all descriptors to get public keys and scripts.
- // TODO: get private keys from descriptors too
+ // Expand all descriptors to get public keys and scripts, and private keys if available.
for (int i = range_start; i <= range_end; ++i) {
FlatSigningProvider out_keys;
std::vector<CScript> scripts_temp;
@@ -1180,7 +1179,10 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
import_data.import_scripts.emplace(x.second);
}
+ parsed_desc->ExpandPrivate(i, keys, out_keys);
+
std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
+ std::copy(out_keys.keys.begin(), out_keys.keys.end(), std::inserter(privkey_map, privkey_map.end()));
import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
}
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index 7d652a7825..e19c7919a9 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -571,6 +571,7 @@ class ImportMultiTest(BitcoinTestFramework):
# Test ranged descriptor fails if range is not specified
xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg"
addresses = ["2N7yv4p8G8yEaPddJxY41kPihnWvs39qCMf", "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA"] # hdkeypath=m/0'/0'/0' and 1'
+ addresses += ["bcrt1qrd3n235cj2czsfmsuvqqpr3lu6lg0ju7scl8gn", "bcrt1qfqeppuvj0ww98r6qghmdkj70tv8qpchehegrg8"] # wpkh subscripts corresponding to the above addresses
desc = "sh(wpkh(" + xpriv + "/0'/0'/*'" + "))"
self.log.info("Ranged descriptor import should fail without a specified range")
self.test_importmulti({"desc": descsum_create(desc),
@@ -579,17 +580,17 @@ class ImportMultiTest(BitcoinTestFramework):
error_code=-8,
error_message='Descriptor is ranged, please specify the range')
- # Test importing of a ranged descriptor without keys
+ # Test importing of a ranged descriptor with xpriv
self.log.info("Should import the ranged descriptor with specified range as solvable")
self.test_importmulti({"desc": descsum_create(desc),
"timestamp": "now",
"range": 1},
- success=True,
- warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
+ success=True)
for address in addresses:
test_address(self.nodes[1],
- key.p2sh_p2wpkh_addr,
- solvable=True)
+ address,
+ solvable=True,
+ ismine=True)
self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": -1},
success=False, error_code=-8, error_message='End of range is too high')
@@ -606,6 +607,23 @@ class ImportMultiTest(BitcoinTestFramework):
self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]},
success=False, error_code=-8, error_message='Range is too large')
+ # Test importing a descriptor containing a WIF private key
+ wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh"
+ address = "2MuhcG52uHPknxDgmGPsV18jSHFBnnRgjPg"
+ desc = "sh(wpkh(" + wif_priv + "))"
+ self.log.info("Should import a descriptor with a WIF private key as spendable")
+ self.test_importmulti({"desc": descsum_create(desc),
+ "timestamp": "now"},
+ success=True)
+ test_address(self.nodes[1],
+ address,
+ solvable=True,
+ ismine=True)
+
+ # dump the private key to ensure it matches what was imported
+ privkey = self.nodes[1].dumpprivkey(address)
+ assert_equal(privkey, wif_priv)
+
# Test importing of a P2PKH address via descriptor
key = get_key(self.nodes[0])
self.log.info("Should import a p2pkh address from descriptor")