merge bitcoin#15024: Allow specific private keys to be derived from descriptor

This commit is contained in:
Kittywhiskers Van Gogh 2022-09-19 02:25:36 +05:30
parent 94bcbf588d
commit 5df05449be
4 changed files with 71 additions and 13 deletions

View File

@ -166,6 +166,9 @@ struct PubkeyProvider
/** Get the descriptor string form including private data (if available in arg). */ /** Get the descriptor string form including private data (if available in arg). */
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0; 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 class OriginPubkeyProvider final : public PubkeyProvider
@ -197,6 +200,10 @@ public:
ret = "[" + OriginString() + "]" + std::move(sub); ret = "[" + OriginString() + "]" + std::move(sub);
return true; 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. */ /** An object representing a parsed constant public key in a descriptor. */
@ -224,6 +231,10 @@ public:
ret = EncodeSecret(key); ret = EncodeSecret(key);
return true; return true;
} }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
{
return arg.GetKey(m_pubkey.GetID(), key);
}
}; };
enum class DeriveType { enum class DeriveType {
@ -268,14 +279,9 @@ public:
{ {
if (key) { if (key) {
if (IsHardened()) { if (IsHardened()) {
CExtKey extkey; CKey priv_key;
if (!GetExtKey(arg, extkey)) return false; if (!GetPrivKey(pos, arg, priv_key)) return false;
for (auto entry : m_path) { *key = priv_key.GetPubKey();
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;
} else { } else {
// TODO: optimize by caching // TODO: optimize by caching
CExtPubKey extkey = m_extkey; CExtPubKey extkey = m_extkey;
@ -314,6 +320,18 @@ public:
} }
return true; 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. */ /** Base class for all Descriptor implementations. */
@ -468,6 +486,20 @@ public:
Span<const unsigned char> span = MakeSpan(cache); Span<const unsigned char> span = MakeSpan(cache);
return ExpandHelper(pos, DUMMY_SIGNING_PROVIDER, &span, output_scripts, out, nullptr) && span.size() == 0; 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_subdescriptor_arg) {
FlatSigningProvider subprovider;
m_subdescriptor_arg->ExpandPrivate(pos, provider, subprovider);
out = Merge(out, subprovider);
}
}
}; };
/** Construct a vector with one element, which is moved into it. */ /** Construct a vector with one element, which is moved into it. */

View File

@ -63,6 +63,14 @@ struct Descriptor {
* @param[out] out: Scripts and public keys necessary for solving the expanded scriptPubKeys (may be equal to `provider`). * @param[out] out: Scripts and public keys necessary for solving the expanded scriptPubKeys (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; 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`. /** Parse a `descriptor` string. Included private keys are put in `out`.

View File

@ -1258,8 +1258,7 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
// Expand all descriptors to get public keys and scripts. // Expand all descriptors to get public keys and scripts, and private keys if available.
// TODO: get private keys from descriptors too
for (int i = range_start; i <= range_end; ++i) { for (int i = range_start; i <= range_end; ++i) {
FlatSigningProvider out_keys; FlatSigningProvider out_keys;
std::vector<CScript> scripts_temp; std::vector<CScript> scripts_temp;
@ -1273,7 +1272,10 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
import_data.import_scripts.emplace(x.second); 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.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()); import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
} }

View File

@ -448,13 +448,12 @@ class ImportMultiTest(BitcoinTestFramework):
error_code=-8, error_code=-8,
error_message='Descriptor is ranged, please specify the range') 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.log.info("Should import the ranged descriptor with specified range as solvable")
self.test_importmulti({"desc": descsum_create(desc), self.test_importmulti({"desc": descsum_create(desc),
"timestamp": "now", "timestamp": "now",
"range": 1}, "range": 1},
success=True, success=True)
warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": -1}, self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": -1},
success=False, error_code=-8, error_message='End of range is too high') success=False, error_code=-8, error_message='End of range is too high')
@ -471,6 +470,23 @@ class ImportMultiTest(BitcoinTestFramework):
self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]}, self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]},
success=False, error_code=-8, error_message='Range is too large') success=False, error_code=-8, error_message='Range is too large')
# Test importing a descriptor containing a WIF private key
wif_priv = "cTT3BvHnd51YJf8fkdr2XvZTQRRUZruWhRvRyQY1raVFg5Lvam2A"
address = "ySWABbcNKyHUgBb1ffhpuETuis9jsdR3aq"
desc = "sh(pkh(" + 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 # Test importing of a P2PKH address via descriptor
key = get_key(self.nodes[0]) key = get_key(self.nodes[0])
self.log.info("Should import a p2pkh address from descriptor") self.log.info("Should import a p2pkh address from descriptor")