Merge #14477: Add ability to convert solvability info to descriptor

109699dd33 Add release notes (Pieter Wuille)
b65326b562 Add matching descriptors to scantxoutset output + tests (Pieter Wuille)
16203d5df7 Add descriptors to listunspent and getaddressinfo + tests (Pieter Wuille)
9b2a25b13f Add tests for InferDescriptor and Descriptor::IsSolvable (Pieter Wuille)
225bf3e3b0 Add Descriptor::IsSolvable() to distinguish addr/raw from others (Pieter Wuille)
4d78bd93b5 Add support for inferring descriptors from scripts (Pieter Wuille)

Pull request description:

  This PR adds functionality to convert a script to a descriptor, given a `SigningProvider` with the relevant information about public keys and redeemscripts/witnessscripts.

  The feature is exposed in `listunspent`, `getaddressinfo`, and `scantxoutset` whenever these calls are applied to solvable outputs/addresses.

  This is not very useful on its own, though when we add RPCs to import descriptors, or sign PSBTs using descriptors, these strings become a compact and standalone way of conveying everything necessary to sign an output (excluding private keys).

  Unit tests and rudimentary RPC tests are included (more relevant tests can be added once RPCs support descriptors).

  Fixes #14503.

Tree-SHA512: cb36b84a3e0200375b7e06a98c7e750cfaf95cf5de132cad59f7ec3cbd201f739427de0dc108f515be7aca203652089fbf5f24ed283d4553bddf23a3224ab31f
This commit is contained in:
Pieter Wuille
2018-11-27 12:14:33 -08:00
9 changed files with 213 additions and 2 deletions

View File

@@ -211,6 +211,7 @@ public:
AddressDescriptor(CTxDestination destination) : m_destination(std::move(destination)) {}
bool IsRange() const override { return false; }
bool IsSolvable() const override { return false; }
std::string ToString() const override { return "addr(" + EncodeDestination(m_destination) + ")"; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override { out = ToString(); return true; }
bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
@@ -229,6 +230,7 @@ public:
RawDescriptor(CScript script) : m_script(std::move(script)) {}
bool IsRange() const override { return false; }
bool IsSolvable() const override { return false; }
std::string ToString() const override { return "raw(" + HexStr(m_script.begin(), m_script.end()) + ")"; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override { out = ToString(); return true; }
bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
@@ -249,6 +251,7 @@ public:
SingleKeyDescriptor(std::unique_ptr<PubkeyProvider> prov, const std::function<CScript(const CPubKey&)>& fn, const std::string& name) : m_script_fn(fn), m_fn_name(name), m_provider(std::move(prov)) {}
bool IsRange() const override { return m_provider->IsRange(); }
bool IsSolvable() const override { return true; }
std::string ToString() const override { return m_fn_name + "(" + m_provider->ToString() + ")"; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
{
@@ -290,6 +293,8 @@ public:
return false;
}
bool IsSolvable() const override { return true; }
std::string ToString() const override
{
std::string ret = strprintf("multi(%i", m_threshold);
@@ -343,6 +348,7 @@ public:
ConvertorDescriptor(std::unique_ptr<Descriptor> descriptor, const std::function<CScript(const CScript&)>& fn, const std::string& name) : m_convert_fn(fn), m_fn_name(name), m_descriptor(std::move(descriptor)) {}
bool IsRange() const override { return m_descriptor->IsRange(); }
bool IsSolvable() const override { return m_descriptor->IsSolvable(); }
std::string ToString() const override { return m_fn_name + "(" + m_descriptor->ToString() + ")"; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
{
@@ -377,6 +383,7 @@ public:
ComboDescriptor(std::unique_ptr<PubkeyProvider> provider) : m_provider(std::move(provider)) {}
bool IsRange() const override { return m_provider->IsRange(); }
bool IsSolvable() const override { return true; }
std::string ToString() const override { return "combo(" + m_provider->ToString() + ")"; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
{
@@ -625,6 +632,80 @@ std::unique_ptr<Descriptor> ParseScript(Span<const char>& sp, ParseScriptContext
return nullptr;
}
std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
{
std::unique_ptr<PubkeyProvider> key_provider = MakeUnique<ConstPubkeyProvider>(pubkey);
KeyOriginInfo info;
if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
return MakeUnique<OriginPubkeyProvider>(std::move(info), std::move(key_provider));
}
return key_provider;
}
std::unique_ptr<Descriptor> InferScript(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider)
{
std::vector<std::vector<unsigned char>> data;
txnouttype txntype = Solver(script, data);
if (txntype == TX_PUBKEY) {
CPubKey pubkey(data[0].begin(), data[0].end());
if (pubkey.IsValid()) {
return MakeUnique<SingleKeyDescriptor>(InferPubkey(pubkey, ctx, provider), P2PKGetScript, "pk");
}
}
if (txntype == TX_PUBKEYHASH) {
uint160 hash(data[0]);
CKeyID keyid(hash);
CPubKey pubkey;
if (provider.GetPubKey(keyid, pubkey)) {
return MakeUnique<SingleKeyDescriptor>(InferPubkey(pubkey, ctx, provider), P2PKHGetScript, "pkh");
}
}
if (txntype == TX_WITNESS_V0_KEYHASH && ctx != ParseScriptContext::P2WSH) {
uint160 hash(data[0]);
CKeyID keyid(hash);
CPubKey pubkey;
if (provider.GetPubKey(keyid, pubkey)) {
return MakeUnique<SingleKeyDescriptor>(InferPubkey(pubkey, ctx, provider), P2WPKHGetScript, "wpkh");
}
}
if (txntype == TX_MULTISIG) {
std::vector<std::unique_ptr<PubkeyProvider>> providers;
for (size_t i = 1; i + 1 < data.size(); ++i) {
CPubKey pubkey(data[i].begin(), data[i].end());
providers.push_back(InferPubkey(pubkey, ctx, provider));
}
return MakeUnique<MultisigDescriptor>((int)data[0][0], std::move(providers));
}
if (txntype == TX_SCRIPTHASH && ctx == ParseScriptContext::TOP) {
uint160 hash(data[0]);
CScriptID scriptid(hash);
CScript subscript;
if (provider.GetCScript(scriptid, subscript)) {
auto sub = InferScript(subscript, ParseScriptContext::P2SH, provider);
if (sub) return MakeUnique<ConvertorDescriptor>(std::move(sub), ConvertP2SH, "sh");
}
}
if (txntype == TX_WITNESS_V0_SCRIPTHASH && ctx != ParseScriptContext::P2WSH) {
CScriptID scriptid;
CRIPEMD160().Write(data[0].data(), data[0].size()).Finalize(scriptid.begin());
CScript subscript;
if (provider.GetCScript(scriptid, subscript)) {
auto sub = InferScript(subscript, ParseScriptContext::P2WSH, provider);
if (sub) return MakeUnique<ConvertorDescriptor>(std::move(sub), ConvertP2WSH, "wsh");
}
}
CTxDestination dest;
if (ExtractDestination(script, dest)) {
if (GetScriptForDestination(dest) == script) {
return MakeUnique<AddressDescriptor>(std::move(dest));
}
}
return MakeUnique<RawDescriptor>(script);
}
} // namespace
std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out)
@@ -634,3 +715,8 @@ std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProv
if (sp.size() == 0 && ret) return ret;
return nullptr;
}
std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const SigningProvider& provider)
{
return InferScript(script, ParseScriptContext::TOP, provider);
}