aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am2
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/rpc/blockchain.cpp168
-rw-r--r--src/script/descriptor.cpp566
-rw-r--r--src/script/descriptor.h102
-rw-r--r--src/script/sign.cpp30
-rw-r--r--src/script/sign.h13
-rw-r--r--src/span.h20
-rw-r--r--src/test/descriptor_tests.cpp163
-rwxr-xr-xtest/functional/rpc_scantxoutset.py60
10 files changed, 994 insertions, 131 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 60ecf07a59..d1693fa85c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -158,6 +158,7 @@ BITCOIN_CORE_H = \
rpc/register.h \
rpc/util.h \
scheduler.h \
+ script/descriptor.h \
script/ismine.h \
script/sigcache.h \
script/sign.h \
@@ -387,6 +388,7 @@ libbitcoin_common_a_SOURCES = \
policy/feerate.cpp \
protocol.cpp \
scheduler.cpp \
+ script/descriptor.cpp \
script/ismine.cpp \
script/sign.cpp \
script/standard.cpp \
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 02f2063504..6f401636f5 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -47,6 +47,7 @@ BITCOIN_TESTS =\
test/crypto_tests.cpp \
test/cuckoocache_tests.cpp \
test/denialofservice_tests.cpp \
+ test/descriptor_tests.cpp \
test/getarg_tests.cpp \
test/hash_tests.cpp \
test/key_io_tests.cpp \
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 012e3e3ac1..46dec4ca6e 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -20,6 +20,7 @@
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
+#include <script/descriptor.h>
#include <streams.h>
#include <sync.h>
#include <txdb.h>
@@ -1984,67 +1985,38 @@ public:
}
};
-static const char *g_default_scantxoutset_script_types[] = { "P2PKH", "P2SH_P2WPKH", "P2WPKH" };
-
-enum class OutputScriptType {
- UNKNOWN,
- P2PK,
- P2PKH,
- P2SH_P2WPKH,
- P2WPKH
-};
-
-static inline OutputScriptType GetOutputScriptTypeFromString(const std::string& outputtype)
-{
- if (outputtype == "P2PK") return OutputScriptType::P2PK;
- else if (outputtype == "P2PKH") return OutputScriptType::P2PKH;
- else if (outputtype == "P2SH_P2WPKH") return OutputScriptType::P2SH_P2WPKH;
- else if (outputtype == "P2WPKH") return OutputScriptType::P2WPKH;
- else return OutputScriptType::UNKNOWN;
-}
-
-CTxDestination GetDestinationForKey(const CPubKey& key, OutputScriptType type)
-{
- switch (type) {
- case OutputScriptType::P2PKH: return key.GetID();
- case OutputScriptType::P2SH_P2WPKH:
- case OutputScriptType::P2WPKH: {
- if (!key.IsCompressed()) return key.GetID();
- CTxDestination witdest = WitnessV0KeyHash(key.GetID());
- if (type == OutputScriptType::P2SH_P2WPKH) {
- CScript witprog = GetScriptForDestination(witdest);
- return CScriptID(witprog);
- } else {
- return witdest;
- }
- }
- default: assert(false);
- }
-}
-
UniValue scantxoutset(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw std::runtime_error(
"scantxoutset <action> ( <scanobjects> )\n"
- "\nScans the unspent transaction output set for possible entries that matches common scripts of given public keys.\n"
- "Using addresses as scanobjects will _not_ detect unspent P2PK txouts\n"
+ "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n"
+ "\nScans the unspent transaction output set for entries that match certain output descriptors.\n"
+ "Examples of output descriptors are:\n"
+ " addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n"
+ " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
+ " combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n"
+ " pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
+ " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
+ "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
+ "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
+ "unhardened or hardened child keys.\n"
+ "In the latter case, a range needs to be specified by below if different from 1000.\n"
+ "For more information on output descriptors, see the documentation at TODO\n"
"\nArguments:\n"
"1. \"action\" (string, required) The action to execute\n"
" \"start\" for starting a scan\n"
" \"abort\" for aborting the current scan (returns true when abort was successful)\n"
" \"status\" for progress report (in %) of the current scan\n"
- "2. \"scanobjects\" (array, optional) Array of scan objects (only one object type per scan object allowed)\n"
- " [\n"
- " { \"address\" : \"<address>\" }, (string, optional) Bitcoin address\n"
- " { \"script\" : \"<scriptPubKey>\" }, (string, optional) HEX encoded script (scriptPubKey)\n"
- " { \"pubkey\" : (object, optional) Public key\n"
- " {\n"
- " \"pubkey\" : \"<pubkey\">, (string, required) HEX encoded public key\n"
- " \"script_types\" : [ ... ], (array, optional) Array of script-types to derive from the pubkey (possible values: \"P2PK\", \"P2PKH\", \"P2SH-P2WPKH\", \"P2WPKH\")\n"
- " }\n"
+ "2. \"scanobjects\" (array, required) Array of scan objects\n"
+ " [ Every scan object is either a string descriptor or an object:\n"
+ " \"descriptor\", (string, optional) An output descriptor\n"
+ " { (object, optional) An object with output descriptor and metadata\n"
+ " \"desc\": \"descriptor\", (string, required) An output descriptor\n"
+ " \"range\": n, (numeric, optional) Up to what child index HD chains should be explored (default: 1000)\n"
" },\n"
- " ]\n"
+ " ...\n"
+ " ]\n"
"\nResult:\n"
"{\n"
" \"unspents\": [\n"
@@ -2090,79 +2062,35 @@ UniValue scantxoutset(const JSONRPCRequest& request)
// loop through the scan objects
for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
- if (!scanobject.isObject()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scan object");
- }
- UniValue address_uni = find_value(scanobject, "address");
- UniValue pubkey_uni = find_value(scanobject, "pubkey");
- UniValue script_uni = find_value(scanobject, "script");
-
- // make sure only one object type is present
- if (1 != !address_uni.isNull() + !pubkey_uni.isNull() + !script_uni.isNull()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Only one object type is allowed per scan object");
- } else if (!address_uni.isNull() && !address_uni.isStr()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"address\" must contain a single string as value");
- } else if (!pubkey_uni.isNull() && !pubkey_uni.isObject()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"pubkey\" must contain an object as value");
- } else if (!script_uni.isNull() && !script_uni.isStr()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"script\" must contain a single string as value");
- } else if (address_uni.isStr()) {
- // type: address
- // decode destination and derive the scriptPubKey
- // add the script to the scan containers
- CTxDestination dest = DecodeDestination(address_uni.get_str());
- if (!IsValidDestination(dest)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
- }
- CScript script = GetScriptForDestination(dest);
- assert(!script.empty());
- needles.insert(script);
- } else if (pubkey_uni.isObject()) {
- // type: pubkey
- // derive script(s) according to the script_type parameter
- UniValue script_types_uni = find_value(pubkey_uni, "script_types");
- UniValue pubkeydata_uni = find_value(pubkey_uni, "pubkey");
-
- // check the script types and use the default if not provided
- if (!script_types_uni.isNull() && !script_types_uni.isArray()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "script_types must be an array");
- } else if (script_types_uni.isNull()) {
- // use the default script types
- script_types_uni = UniValue(UniValue::VARR);
- for (const char *t : g_default_scantxoutset_script_types) {
- script_types_uni.push_back(t);
- }
- }
-
- // check the acctual pubkey
- if (!pubkeydata_uni.isStr() || !IsHex(pubkeydata_uni.get_str())) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Public key must be hex encoded");
- }
- CPubKey pubkey(ParseHexV(pubkeydata_uni, "pubkey"));
- if (!pubkey.IsFullyValid()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key");
+ std::string desc_str;
+ int range = 1000;
+ if (scanobject.isStr()) {
+ desc_str = scanobject.get_str();
+ } else if (scanobject.isObject()) {
+ UniValue desc_uni = find_value(scanobject, "desc");
+ if (desc_uni.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor needs to be provided in scan object");
+ desc_str = desc_uni.get_str();
+ UniValue range_uni = find_value(scanobject, "range");
+ if (!range_uni.isNull()) {
+ range = range_uni.get_int();
+ if (range < 0 || range > 1000000) throw JSONRPCError(RPC_INVALID_PARAMETER, "range out of range");
}
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object");
+ }
- // loop through the script types and derive the script
- for (const UniValue& script_type_uni : script_types_uni.get_array().getValues()) {
- OutputScriptType script_type = GetOutputScriptTypeFromString(script_type_uni.get_str());
- if (script_type == OutputScriptType::UNKNOWN) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid script type");
- CScript script;
- if (script_type == OutputScriptType::P2PK) {
- // support legacy P2PK scripts
- script << ToByteVector(pubkey) << OP_CHECKSIG;
- } else {
- script = GetScriptForDestination(GetDestinationForKey(pubkey, script_type));
- }
- assert(!script.empty());
- needles.insert(script);
+ FlatSigningProvider provider;
+ auto desc = Parse(desc_str, provider);
+ if (!desc) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor '%s'", desc_str));
+ }
+ if (!desc->IsRange()) range = 0;
+ for (int i = 0; i <= range; ++i) {
+ std::vector<CScript> scripts;
+ if (!desc->Expand(i, provider, scripts, provider)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
}
- } else if (script_uni.isStr()) {
- // type: script
- // check and add the script to the scan containers (needles array)
- CScript script(ParseHexV(script_uni, "script"));
- // TODO: check script: max length, has OP, is unspenable etc.
- needles.insert(script);
+ needles.insert(scripts.begin(), scripts.end());
}
}
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
new file mode 100644
index 0000000000..f366b99ec3
--- /dev/null
+++ b/src/script/descriptor.cpp
@@ -0,0 +1,566 @@
+// Copyright (c) 2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <script/descriptor.h>
+
+#include <key_io.h>
+#include <pubkey.h>
+#include <script/script.h>
+#include <script/standard.h>
+
+#include <span.h>
+#include <util.h>
+#include <utilstrencodings.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////
+// Internal representation //
+////////////////////////////////////////////////////////////////////////////
+
+typedef std::vector<uint32_t> KeyPath;
+
+std::string FormatKeyPath(const KeyPath& path)
+{
+ std::string ret;
+ for (auto i : path) {
+ ret += strprintf("/%i", (i << 1) >> 1);
+ if (i >> 31) ret += '\'';
+ }
+ return ret;
+}
+
+/** Interface for public key objects in descriptors. */
+struct PubkeyProvider
+{
+ virtual ~PubkeyProvider() = default;
+
+ /** Derive a public key. */
+ virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const = 0;
+
+ /** Whether this represent multiple public keys at different positions. */
+ virtual bool IsRange() const = 0;
+
+ /** Get the size of the generated public key(s) in bytes (33 or 65). */
+ virtual size_t GetSize() const = 0;
+
+ /** Get the descriptor string form. */
+ virtual std::string ToString() const = 0;
+
+ /** Get the descriptor string form including private data (if available in arg). */
+ virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0;
+};
+
+/** An object representing a parsed constant public key in a descriptor. */
+class ConstPubkeyProvider final : public PubkeyProvider
+{
+ CPubKey m_pubkey;
+
+public:
+ ConstPubkeyProvider(const CPubKey& pubkey) : m_pubkey(pubkey) {}
+ bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const override
+ {
+ out = m_pubkey;
+ return true;
+ }
+ bool IsRange() const override { return false; }
+ size_t GetSize() const override { return m_pubkey.size(); }
+ std::string ToString() const override { return HexStr(m_pubkey.begin(), m_pubkey.end()); }
+ bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
+ {
+ CKey key;
+ if (!arg.GetKey(m_pubkey.GetID(), key)) return false;
+ ret = EncodeSecret(key);
+ return true;
+ }
+};
+
+enum class DeriveType {
+ NO,
+ UNHARDENED,
+ HARDENED,
+};
+
+/** An object representing a parsed extended public key in a descriptor. */
+class BIP32PubkeyProvider final : public PubkeyProvider
+{
+ CExtPubKey m_extkey;
+ KeyPath m_path;
+ DeriveType m_derive;
+
+ bool GetExtKey(const SigningProvider& arg, CExtKey& ret) const
+ {
+ CKey key;
+ if (!arg.GetKey(m_extkey.pubkey.GetID(), key)) return false;
+ ret.nDepth = m_extkey.nDepth;
+ std::copy(m_extkey.vchFingerprint, m_extkey.vchFingerprint + 4, ret.vchFingerprint);
+ ret.nChild = m_extkey.nChild;
+ ret.chaincode = m_extkey.chaincode;
+ ret.key = key;
+ return true;
+ }
+
+ bool IsHardened() const
+ {
+ if (m_derive == DeriveType::HARDENED) return true;
+ for (auto entry : m_path) {
+ if (entry >> 31) return true;
+ }
+ return false;
+ }
+
+public:
+ BIP32PubkeyProvider(const CExtPubKey& extkey, KeyPath path, DeriveType derive) : m_extkey(extkey), m_path(std::move(path)), m_derive(derive) {}
+ bool IsRange() const override { return m_derive != DeriveType::NO; }
+ size_t GetSize() const override { return 33; }
+ bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const override
+ {
+ if (IsHardened()) {
+ CExtKey key;
+ if (!GetExtKey(arg, key)) return false;
+ for (auto entry : m_path) {
+ key.Derive(key, entry);
+ }
+ if (m_derive == DeriveType::UNHARDENED) key.Derive(key, pos);
+ if (m_derive == DeriveType::HARDENED) key.Derive(key, pos | 0x80000000UL);
+ out = key.Neuter().pubkey;
+ } else {
+ // TODO: optimize by caching
+ CExtPubKey key = m_extkey;
+ for (auto entry : m_path) {
+ key.Derive(key, entry);
+ }
+ if (m_derive == DeriveType::UNHARDENED) key.Derive(key, pos);
+ assert(m_derive != DeriveType::HARDENED);
+ out = key.pubkey;
+ }
+ return true;
+ }
+ std::string ToString() const override
+ {
+ std::string ret = EncodeExtPubKey(m_extkey) + FormatKeyPath(m_path);
+ if (IsRange()) {
+ ret += "/*";
+ if (m_derive == DeriveType::HARDENED) ret += '\'';
+ }
+ return ret;
+ }
+ bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
+ {
+ CExtKey key;
+ if (!GetExtKey(arg, key)) return false;
+ out = EncodeExtKey(key) + FormatKeyPath(m_path);
+ if (IsRange()) {
+ out += "/*";
+ if (m_derive == DeriveType::HARDENED) out += '\'';
+ }
+ return true;
+ }
+};
+
+/** A parsed addr(A) descriptor. */
+class AddressDescriptor final : public Descriptor
+{
+ CTxDestination m_destination;
+
+public:
+ AddressDescriptor(CTxDestination destination) : m_destination(std::move(destination)) {}
+
+ bool IsRange() 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
+ {
+ output_scripts = std::vector<CScript>{GetScriptForDestination(m_destination)};
+ return true;
+ }
+};
+
+/** A parsed raw(H) descriptor. */
+class RawDescriptor final : public Descriptor
+{
+ CScript m_script;
+
+public:
+ RawDescriptor(CScript script) : m_script(std::move(script)) {}
+
+ bool IsRange() 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
+ {
+ output_scripts = std::vector<CScript>{m_script};
+ return true;
+ }
+};
+
+/** A parsed pk(P), pkh(P), or wpkh(P) descriptor. */
+class SingleKeyDescriptor final : public Descriptor
+{
+ const std::function<CScript(const CPubKey&)> m_script_fn;
+ const std::string m_fn_name;
+ std::unique_ptr<PubkeyProvider> m_provider;
+
+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(); }
+ std::string ToString() const override { return m_fn_name + "(" + m_provider->ToString() + ")"; }
+ bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
+ {
+ std::string ret;
+ if (!m_provider->ToPrivateString(arg, ret)) return false;
+ out = m_fn_name + "(" + std::move(ret) + ")";
+ return true;
+ }
+ bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
+ {
+ CPubKey key;
+ if (!m_provider->GetPubKey(pos, arg, key)) return false;
+ output_scripts = std::vector<CScript>{m_script_fn(key)};
+ out.pubkeys.emplace(key.GetID(), std::move(key));
+ return true;
+ }
+};
+
+CScript P2PKHGetScript(const CPubKey& pubkey) { return GetScriptForDestination(pubkey.GetID()); }
+CScript P2PKGetScript(const CPubKey& pubkey) { return GetScriptForRawPubKey(pubkey); }
+CScript P2WPKHGetScript(const CPubKey& pubkey) { return GetScriptForDestination(WitnessV0KeyHash(pubkey.GetID())); }
+
+/** A parsed multi(...) descriptor. */
+class MultisigDescriptor : public Descriptor
+{
+ int m_threshold;
+ std::vector<std::unique_ptr<PubkeyProvider>> m_providers;
+
+public:
+ MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers) : m_threshold(threshold), m_providers(std::move(providers)) {}
+
+ bool IsRange() const override
+ {
+ for (const auto& p : m_providers) {
+ if (p->IsRange()) return true;
+ }
+ return false;
+ }
+
+ std::string ToString() const override
+ {
+ std::string ret = strprintf("multi(%i", m_threshold);
+ for (const auto& p : m_providers) {
+ ret += "," + p->ToString();
+ }
+ return std::move(ret) + ")";
+ }
+
+ bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
+ {
+ std::string ret = strprintf("multi(%i", m_threshold);
+ for (const auto& p : m_providers) {
+ std::string sub;
+ if (!p->ToPrivateString(arg, sub)) return false;
+ ret += "," + std::move(sub);
+ }
+ out = std::move(ret) + ")";
+ return true;
+ }
+
+ bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
+ {
+ std::vector<CPubKey> pubkeys;
+ pubkeys.reserve(m_providers.size());
+ for (const auto& p : m_providers) {
+ CPubKey key;
+ if (!p->GetPubKey(pos, arg, key)) return false;
+ pubkeys.push_back(key);
+ }
+ for (const CPubKey& key : pubkeys) {
+ out.pubkeys.emplace(key.GetID(), std::move(key));
+ }
+ output_scripts = std::vector<CScript>{GetScriptForMultisig(m_threshold, pubkeys)};
+ return true;
+ }
+};
+
+/** A parsed sh(S) or wsh(S) descriptor. */
+class ConvertorDescriptor : public Descriptor
+{
+ const std::function<CScript(const CScript&)> m_convert_fn;
+ const std::string m_fn_name;
+ std::unique_ptr<Descriptor> m_descriptor;
+
+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(); }
+ std::string ToString() const override { return m_fn_name + "(" + m_descriptor->ToString() + ")"; }
+ bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
+ {
+ std::string ret;
+ if (!m_descriptor->ToPrivateString(arg, ret)) return false;
+ out = m_fn_name + "(" + std::move(ret) + ")";
+ return true;
+ }
+ bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
+ {
+ std::vector<CScript> sub;
+ if (!m_descriptor->Expand(pos, arg, sub, out)) return false;
+ output_scripts.clear();
+ for (const auto& script : sub) {
+ CScriptID id(script);
+ out.scripts.emplace(CScriptID(script), script);
+ output_scripts.push_back(m_convert_fn(script));
+ }
+ return true;
+ }
+};
+
+CScript ConvertP2SH(const CScript& script) { return GetScriptForDestination(CScriptID(script)); }
+CScript ConvertP2WSH(const CScript& script) { return GetScriptForDestination(WitnessV0ScriptHash(script)); }
+
+/** A parsed combo(P) descriptor. */
+class ComboDescriptor final : public Descriptor
+{
+ std::unique_ptr<PubkeyProvider> m_provider;
+
+public:
+ ComboDescriptor(std::unique_ptr<PubkeyProvider> provider) : m_provider(std::move(provider)) {}
+
+ bool IsRange() const override { return m_provider->IsRange(); }
+ std::string ToString() const override { return "combo(" + m_provider->ToString() + ")"; }
+ bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
+ {
+ std::string ret;
+ if (!m_provider->ToPrivateString(arg, ret)) return false;
+ out = "combo(" + std::move(ret) + ")";
+ return true;
+ }
+ bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
+ {
+ CPubKey key;
+ if (!m_provider->GetPubKey(pos, arg, key)) return false;
+ CKeyID keyid = key.GetID();
+ {
+ CScript p2pk = GetScriptForRawPubKey(key);
+ CScript p2pkh = GetScriptForDestination(keyid);
+ output_scripts = std::vector<CScript>{std::move(p2pk), std::move(p2pkh)};
+ out.pubkeys.emplace(keyid, key);
+ }
+ if (key.IsCompressed()) {
+ CScript p2wpkh = GetScriptForDestination(WitnessV0KeyHash(keyid));
+ CScriptID p2wpkh_id(p2wpkh);
+ CScript p2sh_p2wpkh = GetScriptForDestination(p2wpkh_id);
+ out.scripts.emplace(p2wpkh_id, p2wpkh);
+ output_scripts.push_back(std::move(p2wpkh));
+ output_scripts.push_back(std::move(p2sh_p2wpkh));
+ }
+ return true;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////
+// Parser //
+////////////////////////////////////////////////////////////////////////////
+
+enum class ParseScriptContext {
+ TOP,
+ P2SH,
+ P2WSH,
+};
+
+/** Parse a constant. If succesful, sp is updated to skip the constant and return true. */
+bool Const(const std::string& str, Span<const char>& sp)
+{
+ if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) {
+ sp = sp.subspan(str.size());
+ return true;
+ }
+ return false;
+}
+
+/** Parse a function call. If succesful, sp is updated to be the function's argument(s). */
+bool Func(const std::string& str, Span<const char>& sp)
+{
+ if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) {
+ sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2);
+ return true;
+ }
+ return false;
+}
+
+/** Return the expression that sp begins with, and update sp to skip it. */
+Span<const char> Expr(Span<const char>& sp)
+{
+ int level = 0;
+ auto it = sp.begin();
+ while (it != sp.end()) {
+ if (*it == '(') {
+ ++level;
+ } else if (level && *it == ')') {
+ --level;
+ } else if (level == 0 && (*it == ')' || *it == ',')) {
+ break;
+ }
+ ++it;
+ }
+ Span<const char> ret = sp.first(it - sp.begin());
+ sp = sp.subspan(it - sp.begin());
+ return ret;
+}
+
+/** Split a string on every instance of sep, returning a vector. */
+std::vector<Span<const char>> Split(const Span<const char>& sp, char sep)
+{
+ std::vector<Span<const char>> ret;
+ auto it = sp.begin();
+ auto start = it;
+ while (it != sp.end()) {
+ if (*it == sep) {
+ ret.emplace_back(start, it);
+ start = it + 1;
+ }
+ ++it;
+ }
+ ret.emplace_back(start, it);
+ return ret;
+}
+
+/** Parse a key path, being passed a split list of elements (the first element is ignored). */
+bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out)
+{
+ for (size_t i = 1; i < split.size(); ++i) {
+ Span<const char> elem = split[i];
+ bool hardened = false;
+ if (elem.size() > 0 && (elem[elem.size() - 1] == '\'' || elem[elem.size() - 1] == 'h')) {
+ elem = elem.first(elem.size() - 1);
+ hardened = true;
+ }
+ uint32_t p;
+ if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p) || p > 0x7FFFFFFFUL) return false;
+ out.push_back(p | (((uint32_t)hardened) << 31));
+ }
+ return true;
+}
+
+std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out)
+{
+ auto split = Split(sp, '/');
+ std::string str(split[0].begin(), split[0].end());
+ if (split.size() == 1) {
+ if (IsHex(str)) {
+ std::vector<unsigned char> data = ParseHex(str);
+ CPubKey pubkey(data);
+ if (pubkey.IsFullyValid() && (permit_uncompressed || pubkey.IsCompressed())) return MakeUnique<ConstPubkeyProvider>(pubkey);
+ }
+ CKey key = DecodeSecret(str);
+ if (key.IsValid() && (permit_uncompressed || key.IsCompressed())) {
+ CPubKey pubkey = key.GetPubKey();
+ out.keys.emplace(pubkey.GetID(), key);
+ return MakeUnique<ConstPubkeyProvider>(pubkey);
+ }
+ }
+ CExtKey extkey = DecodeExtKey(str);
+ CExtPubKey extpubkey = DecodeExtPubKey(str);
+ if (!extkey.key.IsValid() && !extpubkey.pubkey.IsValid()) return nullptr;
+ KeyPath path;
+ DeriveType type = DeriveType::NO;
+ if (split.back() == MakeSpan("*").first(1)) {
+ split.pop_back();
+ type = DeriveType::UNHARDENED;
+ } else if (split.back() == MakeSpan("*'").first(2) || split.back() == MakeSpan("*h").first(2)) {
+ split.pop_back();
+ type = DeriveType::HARDENED;
+ }
+ if (!ParseKeyPath(split, path)) return nullptr;
+ if (extkey.key.IsValid()) {
+ extpubkey = extkey.Neuter();
+ out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key);
+ }
+ return MakeUnique<BIP32PubkeyProvider>(extpubkey, std::move(path), type);
+}
+
+/** Parse a script in a particular context. */
+std::unique_ptr<Descriptor> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out)
+{
+ auto expr = Expr(sp);
+ if (Func("pk", expr)) {
+ auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2WSH, out);
+ if (!pubkey) return nullptr;
+ return MakeUnique<SingleKeyDescriptor>(std::move(pubkey), P2PKGetScript, "pk");
+ }
+ if (Func("pkh", expr)) {
+ auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2WSH, out);
+ if (!pubkey) return nullptr;
+ return MakeUnique<SingleKeyDescriptor>(std::move(pubkey), P2PKHGetScript, "pkh");
+ }
+ if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
+ auto pubkey = ParsePubkey(expr, true, out);
+ if (!pubkey) return nullptr;
+ return MakeUnique<ComboDescriptor>(std::move(pubkey));
+ }
+ if (Func("multi", expr)) {
+ auto threshold = Expr(expr);
+ uint32_t thres;
+ std::vector<std::unique_ptr<PubkeyProvider>> providers;
+ if (!ParseUInt32(std::string(threshold.begin(), threshold.end()), &thres)) return nullptr;
+ size_t script_size = 0;
+ while (expr.size()) {
+ if (!Const(",", expr)) return nullptr;
+ auto arg = Expr(expr);
+ auto pk = ParsePubkey(arg, ctx != ParseScriptContext::P2WSH, out);
+ if (!pk) return nullptr;
+ script_size += pk->GetSize() + 1;
+ providers.emplace_back(std::move(pk));
+ }
+ if (providers.size() < 1 || providers.size() > 16 || thres < 1 || thres > providers.size()) return nullptr;
+ if (ctx == ParseScriptContext::TOP) {
+ if (providers.size() > 3) return nullptr; // Not more than 3 pubkeys for raw multisig
+ }
+ if (ctx == ParseScriptContext::P2SH) {
+ if (script_size + 3 > 520) return nullptr; // Enforce P2SH script size limit
+ }
+ return MakeUnique<MultisigDescriptor>(thres, std::move(providers));
+ }
+ if (ctx != ParseScriptContext::P2WSH && Func("wpkh", expr)) {
+ auto pubkey = ParsePubkey(expr, false, out);
+ if (!pubkey) return nullptr;
+ return MakeUnique<SingleKeyDescriptor>(std::move(pubkey), P2WPKHGetScript, "wpkh");
+ }
+ if (ctx == ParseScriptContext::TOP && Func("sh", expr)) {
+ auto desc = ParseScript(expr, ParseScriptContext::P2SH, out);
+ if (!desc || expr.size()) return nullptr;
+ return MakeUnique<ConvertorDescriptor>(std::move(desc), ConvertP2SH, "sh");
+ }
+ if (ctx != ParseScriptContext::P2WSH && Func("wsh", expr)) {
+ auto desc = ParseScript(expr, ParseScriptContext::P2WSH, out);
+ if (!desc || expr.size()) return nullptr;
+ return MakeUnique<ConvertorDescriptor>(std::move(desc), ConvertP2WSH, "wsh");
+ }
+ if (ctx == ParseScriptContext::TOP && Func("addr", expr)) {
+ CTxDestination dest = DecodeDestination(std::string(expr.begin(), expr.end()));
+ if (!IsValidDestination(dest)) return nullptr;
+ return MakeUnique<AddressDescriptor>(std::move(dest));
+ }
+ if (ctx == ParseScriptContext::TOP && Func("raw", expr)) {
+ std::string str(expr.begin(), expr.end());
+ if (!IsHex(str)) return nullptr;
+ auto bytes = ParseHex(str);
+ return MakeUnique<RawDescriptor>(CScript(bytes.begin(), bytes.end()));
+ }
+ return nullptr;
+}
+
+} // namespace
+
+std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out)
+{
+ Span<const char> sp(descriptor.data(), descriptor.size());
+ auto ret = ParseScript(sp, ParseScriptContext::TOP, out);
+ if (sp.size() == 0 && ret) return ret;
+ return nullptr;
+}
diff --git a/src/script/descriptor.h b/src/script/descriptor.h
new file mode 100644
index 0000000000..e079c72e92
--- /dev/null
+++ b/src/script/descriptor.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_SCRIPT_DESCRIPTOR_H
+#define BITCOIN_SCRIPT_DESCRIPTOR_H
+
+#include <script/script.h>
+#include <script/sign.h>
+
+#include <vector>
+
+// Descriptors are strings that describe a set of scriptPubKeys, together with
+// all information necessary to solve them. By combining all information into
+// one, they avoid the need to separately import keys and scripts.
+//
+// Descriptors may be ranged, which occurs when the public keys inside are
+// specified in the form of HD chains (xpubs).
+//
+// Descriptors always represent public information - public keys and scripts -
+// but in cases where private keys need to be conveyed along with a descriptor,
+// they can be included inside by changing public keys to private keys (WIF
+// format), and changing xpubs by xprvs.
+//
+// 1. Examples
+//
+// A P2PK descriptor with a fixed public key:
+// - pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)
+//
+// A P2SH-P2WSH-P2PKH descriptor with a fixed public key:
+// - sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))
+//
+// A bare 1-of-2 multisig descriptor:
+// - multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)
+//
+// A chain of P2PKH outputs (this needs the corresponding private key to derive):
+// - pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2/*)
+//
+// 2. Grammar description:
+//
+// X: xpub or xprv encoded extended key
+// I: decimal encoded integer
+// H: Hex encoded byte array
+// A: Address in P2PKH, P2SH, or Bech32 encoding
+//
+// S (Scripts):
+// * pk(P): Pay-to-pubkey (P2PK) output for public key P.
+// * pkh(P): Pay-to-pubkey-hash (P2PKH) output for public key P.
+// * wpkh(P): Pay-to-witness-pubkey-hash (P2WPKH) output for public key P.
+// * sh(S): Pay-to-script-hash (P2SH) output for script S
+// * wsh(S): Pay-to-witness-script-hash (P2WSH) output for script S
+// * combo(P): combination of P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH for public key P.
+// * multi(I,L): k-of-n multisig for given public keys
+// * addr(A): Output to address
+// * raw(H): scriptPubKey with raw bytes
+//
+// P (Public keys):
+// * H: fixed public key (or WIF-encoded private key)
+// * E: extended public key
+// * E/*: (ranged) all unhardened direct children of an extended public key
+// * E/*': (ranged) all hardened direct children of an extended public key
+//
+// L (Comma-separated lists of public keys):
+// * P
+// * L,P
+//
+// E (Extended public keys):
+// * X
+// * E/I: unhardened child
+// * E/I': hardened child
+// * E/Ih: hardened child (alternative notation)
+//
+// The top level is S.
+
+/** Interface for parsed descriptor objects. */
+struct Descriptor {
+ virtual ~Descriptor() = default;
+
+ /** Whether the expansion of this descriptor depends on the position. */
+ virtual bool IsRange() const = 0;
+
+ /** Convert the descriptor back to a string, undoing parsing. */
+ virtual std::string ToString() const = 0;
+
+ /** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */
+ virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;
+
+ /** Expand a descriptor at a specified position.
+ *
+ * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored.
+ * provider: the provider to query for private keys in case of hardened derivation.
+ * output_script: the expanded scriptPubKeys will be put here.
+ * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider).
+ */
+ virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0;
+};
+
+/** Parse a descriptor string. Included private keys are put in out. Returns nullptr if parsing fails. */
+std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out);
+
+#endif // BITCOIN_SCRIPT_DESCRIPTOR_H
+
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index d10b1c4fd7..fa09adbaf8 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -11,7 +11,6 @@
#include <script/standard.h>
#include <uint256.h>
-
typedef std::vector<unsigned char> valtype;
MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {}
@@ -437,6 +436,18 @@ public:
return true;
}
};
+
+template<typename M, typename K, typename V>
+bool LookupHelper(const M& map, const K& key, V& value)
+{
+ auto it = map.find(key);
+ if (it != map.end()) {
+ value = it->second;
+ return true;
+ }
+ return false;
+}
+
}
const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR = DummySignatureCreator();
@@ -460,7 +471,6 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script)
return false;
}
-
bool PartiallySignedTransaction::IsNull() const
{
return !tx && inputs.empty() && outputs.empty() && unknown.empty();
@@ -618,3 +628,19 @@ bool PublicOnlySigningProvider::GetPubKey(const CKeyID &address, CPubKey& pubkey
{
return m_provider->GetPubKey(address, pubkey);
}
+
+bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); }
+bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); }
+bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); }
+
+FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b)
+{
+ FlatSigningProvider ret;
+ ret.scripts = a.scripts;
+ ret.scripts.insert(b.scripts.begin(), b.scripts.end());
+ ret.pubkeys = a.pubkeys;
+ ret.pubkeys.insert(b.pubkeys.begin(), b.pubkeys.end());
+ ret.keys = a.keys;
+ ret.keys.insert(b.keys.begin(), b.keys.end());
+ return ret;
+}
diff --git a/src/script/sign.h b/src/script/sign.h
index d12d0b5874..96ef59fbe8 100644
--- a/src/script/sign.h
+++ b/src/script/sign.h
@@ -43,6 +43,19 @@ public:
bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const;
};
+struct FlatSigningProvider final : public SigningProvider
+{
+ std::map<CScriptID, CScript> scripts;
+ std::map<CKeyID, CPubKey> pubkeys;
+ std::map<CKeyID, CKey> keys;
+
+ bool GetCScript(const CScriptID& scriptid, CScript& script) const override;
+ bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
+ bool GetKey(const CKeyID& keyid, CKey& key) const override;
+};
+
+FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b);
+
/** Interface for signature creators. */
class BaseSignatureCreator {
public:
diff --git a/src/span.h b/src/span.h
index 707fc21918..77de059fa6 100644
--- a/src/span.h
+++ b/src/span.h
@@ -7,6 +7,7 @@
#include <type_traits>
#include <cstddef>
+#include <algorithm>
/** A Span is an object that can refer to a contiguous sequence of objects.
*
@@ -21,9 +22,25 @@ class Span
public:
constexpr Span() noexcept : m_data(nullptr), m_size(0) {}
constexpr Span(C* data, std::ptrdiff_t size) noexcept : m_data(data), m_size(size) {}
+ constexpr Span(C* data, C* end) noexcept : m_data(data), m_size(end - data) {}
constexpr C* data() const noexcept { return m_data; }
+ constexpr C* begin() const noexcept { return m_data; }
+ constexpr C* end() const noexcept { return m_data + m_size; }
constexpr std::ptrdiff_t size() const noexcept { return m_size; }
+ constexpr C& operator[](std::ptrdiff_t pos) const noexcept { return m_data[pos]; }
+
+ constexpr Span<C> subspan(std::ptrdiff_t offset) const noexcept { return Span<C>(m_data + offset, m_size - offset); }
+ constexpr Span<C> subspan(std::ptrdiff_t offset, std::ptrdiff_t count) const noexcept { return Span<C>(m_data + offset, count); }
+ constexpr Span<C> first(std::ptrdiff_t count) const noexcept { return Span<C>(m_data, count); }
+ constexpr Span<C> last(std::ptrdiff_t count) const noexcept { return Span<C>(m_data + m_size - count, count); }
+
+ friend constexpr bool operator==(const Span& a, const Span& b) noexcept { return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); }
+ friend constexpr bool operator!=(const Span& a, const Span& b) noexcept { return !(a == b); }
+ friend constexpr bool operator<(const Span& a, const Span& b) noexcept { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); }
+ friend constexpr bool operator<=(const Span& a, const Span& b) noexcept { return !(b < a); }
+ friend constexpr bool operator>(const Span& a, const Span& b) noexcept { return (b < a); }
+ friend constexpr bool operator>=(const Span& a, const Span& b) noexcept { return !(a < b); }
};
/** Create a span to a container exposing data() and size().
@@ -34,6 +51,9 @@ public:
*
* std::span will have a constructor that implements this functionality directly.
*/
+template<typename A, int N>
+constexpr Span<A> MakeSpan(A (&a)[N]) { return Span<A>(a, N); }
+
template<typename V>
constexpr Span<typename std::remove_pointer<decltype(std::declval<V>().data())>::type> MakeSpan(V& v) { return Span<typename std::remove_pointer<decltype(std::declval<V>().data())>::type>(v.data(), v.size()); }
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
new file mode 100644
index 0000000000..f189222be8
--- /dev/null
+++ b/src/test/descriptor_tests.cpp
@@ -0,0 +1,163 @@
+// Copyright (c) 2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <vector>
+#include <string>
+#include <script/sign.h>
+#include <script/standard.h>
+#include <test/test_bitcoin.h>
+#include <boost/test/unit_test.hpp>
+#include <script/descriptor.h>
+#include <utilstrencodings.h>
+
+namespace {
+
+void CheckUnparsable(const std::string& prv, const std::string& pub)
+{
+ FlatSigningProvider keys_priv, keys_pub;
+ auto parse_priv = Parse(prv, keys_priv);
+ auto parse_pub = Parse(pub, keys_pub);
+ BOOST_CHECK(!parse_priv);
+ BOOST_CHECK(!parse_pub);
+}
+
+constexpr int DEFAULT = 0;
+constexpr int RANGE = 1; // Expected to be ranged descriptor
+constexpr int HARDENED = 2; // Derivation needs access to private keys
+constexpr int UNSOLVABLE = 4; // This descriptor is not expected to be solvable
+constexpr int SIGNABLE = 8; // We can sign with this descriptor (this is not true when actual BIP32 derivation is used, as that's not integrated in our signing code)
+
+std::string MaybeUseHInsteadOfApostrophy(std::string ret)
+{
+ if (InsecureRandBool()) {
+ while (true) {
+ auto it = ret.find("'");
+ if (it != std::string::npos) {
+ ret[it] = 'h';
+ } else {
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts)
+{
+ FlatSigningProvider keys_priv, keys_pub;
+
+ // Check that parsing succeeds.
+ auto parse_priv = Parse(MaybeUseHInsteadOfApostrophy(prv), keys_priv);
+ auto parse_pub = Parse(MaybeUseHInsteadOfApostrophy(pub), keys_pub);
+ BOOST_CHECK(parse_priv);
+ BOOST_CHECK(parse_pub);
+
+ // Check private keys are extracted from the private version but not the public one.
+ BOOST_CHECK(keys_priv.keys.size());
+ BOOST_CHECK(!keys_pub.keys.size());
+
+ // Check that both versions serialize back to the public version.
+ std::string pub1 = parse_priv->ToString();
+ std::string pub2 = parse_priv->ToString();
+ BOOST_CHECK_EQUAL(pub, pub1);
+ BOOST_CHECK_EQUAL(pub, pub2);
+
+ // Check that both can be serialized with private key back to the private version, but not without private key.
+ std::string prv1, prv2;
+ BOOST_CHECK(parse_priv->ToPrivateString(keys_priv, prv1));
+ BOOST_CHECK_EQUAL(prv, prv1);
+ BOOST_CHECK(!parse_priv->ToPrivateString(keys_pub, prv1));
+ BOOST_CHECK(parse_pub->ToPrivateString(keys_priv, prv1));
+ BOOST_CHECK_EQUAL(prv, prv1);
+ BOOST_CHECK(!parse_pub->ToPrivateString(keys_pub, prv1));
+
+ // Check whether IsRange on both returns the expected result
+ BOOST_CHECK_EQUAL(parse_pub->IsRange(), (flags & RANGE) != 0);
+ BOOST_CHECK_EQUAL(parse_priv->IsRange(), (flags & RANGE) != 0);
+
+
+ // Is not ranged descriptor, only a single result is expected.
+ if (!(flags & RANGE)) assert(scripts.size() == 1);
+
+ size_t max = (flags & RANGE) ? scripts.size() : 3;
+ for (size_t i = 0; i < max; ++i) {
+ const auto& ref = scripts[(flags & RANGE) ? i : 0];
+ for (int t = 0; t < 2; ++t) {
+ FlatSigningProvider key_provider = (flags & HARDENED) ? keys_priv : keys_pub;
+ FlatSigningProvider script_provider;
+ std::vector<CScript> spks;
+ BOOST_CHECK((t ? parse_priv : parse_pub)->Expand(i, key_provider, spks, script_provider));
+ BOOST_CHECK_EQUAL(spks.size(), ref.size());
+ for (size_t n = 0; n < spks.size(); ++n) {
+ BOOST_CHECK_EQUAL(ref[n], HexStr(spks[n].begin(), spks[n].end()));
+ BOOST_CHECK_EQUAL(IsSolvable(Merge(key_provider, script_provider), spks[n]), (flags & UNSOLVABLE) == 0);
+
+ if (flags & SIGNABLE) {
+ CMutableTransaction spend;
+ spend.vin.resize(1);
+ spend.vout.resize(1);
+ BOOST_CHECK_MESSAGE(SignSignature(Merge(keys_priv, script_provider), spks[n], spend, 0, 1, SIGHASH_ALL), prv);
+ }
+ }
+
+ }
+ }
+}
+
+}
+
+BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(descriptor_test)
+{
+ // Basic single-key compressed
+ Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}});
+ Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}});
+ Check("pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}});
+ Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}});
+ Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}});
+
+ // Basic single-key uncompressed
+ Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}});
+ Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}});
+ Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}});
+ CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)"); // No uncompressed keys in witness
+ CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))"); // No uncompressed keys in witness
+ CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))"); // No uncompressed keys in witness
+
+ // Some unconventional single-key constructions
+ Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}});
+ Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}});
+ Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}});
+ Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}});
+ Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}});
+ Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}});
+
+ // Versions with BIP32 derivations
+ Check("combo(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}});
+ Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}});
+ Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}});
+ Check("wpkh(xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh(xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}});
+ Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}});
+ Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}});
+ CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)"); // BIP 32 path element overflow
+
+ // Multisig constructions
+ Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}});
+ Check("sh(multi(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}});
+ Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}});
+ Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}});
+ CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))"); // P2SH does not fit 16 compressed pubkeys in a redeemscript
+
+ // Check for invalid nesting of structures
+ CheckUnparsable("sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2SH needs a script, not a key
+ CheckUnparsable("sh(combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))"); // Old must be top level
+ CheckUnparsable("wsh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wsh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2WSH needs a script, not a key
+ CheckUnparsable("wsh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))"); // Cannot embed witness inside witness
+ CheckUnparsable("wsh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2SH inside P2WSH
+ CheckUnparsable("sh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2SH inside P2SH
+ CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2WSH inside P2WSH
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py
index ce5d4da9e7..11c35b9f08 100755
--- a/test/functional/rpc_scantxoutset.py
+++ b/test/functional/rpc_scantxoutset.py
@@ -23,9 +23,25 @@ class ScantxoutsetTest(BitcoinTestFramework):
pubk2 = self.nodes[0].getaddressinfo(addr_LEGACY)['pubkey']
addr_BECH32 = self.nodes[0].getnewaddress("", "bech32")
pubk3 = self.nodes[0].getaddressinfo(addr_BECH32)['pubkey']
- self.nodes[0].sendtoaddress(addr_P2SH_SEGWIT, 1)
- self.nodes[0].sendtoaddress(addr_LEGACY, 2)
- self.nodes[0].sendtoaddress(addr_BECH32, 3)
+ self.nodes[0].sendtoaddress(addr_P2SH_SEGWIT, 0.001)
+ self.nodes[0].sendtoaddress(addr_LEGACY, 0.002)
+ self.nodes[0].sendtoaddress(addr_BECH32, 0.004)
+
+ #send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK
+ self.nodes[0].sendtoaddress("mkHV1C6JLheLoUSSZYk7x3FH5tnx9bu7yc", 0.008) # (m/0'/0'/0')
+ self.nodes[0].sendtoaddress("mipUSRmJAj2KrjSvsPQtnP8ynUon7FhpCR", 0.016) # (m/0'/0'/1')
+ self.nodes[0].sendtoaddress("n37dAGe6Mq1HGM9t4b6rFEEsDGq7Fcgfqg", 0.032) # (m/0'/0'/1500')
+ self.nodes[0].sendtoaddress("mqS9Rpg8nNLAzxFExsgFLCnzHBsoQ3PRM6", 0.064) # (m/0'/0'/0)
+ self.nodes[0].sendtoaddress("mnTg5gVWr3rbhHaKjJv7EEEc76ZqHgSj4S", 0.128) # (m/0'/0'/1)
+ self.nodes[0].sendtoaddress("mketCd6B9U9Uee1iCsppDJJBHfvi6U6ukC", 0.256) # (m/0'/0'/1500)
+ self.nodes[0].sendtoaddress("mj8zFzrbBcdaWXowCQ1oPZ4qioBVzLzAp7", 0.512) # (m/1/1/0')
+ self.nodes[0].sendtoaddress("mfnKpKQEftniaoE1iXuMMePQU3PUpcNisA", 1.024) # (m/1/1/1')
+ self.nodes[0].sendtoaddress("mou6cB1kaP1nNJM1sryW6YRwnd4shTbXYQ", 2.048) # (m/1/1/1500')
+ self.nodes[0].sendtoaddress("mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", 4.096) # (m/1/1/0)
+ self.nodes[0].sendtoaddress("mxp7w7j8S1Aq6L8StS2PqVvtt4HGxXEvdy", 8.192) # (m/1/1/1)
+ self.nodes[0].sendtoaddress("mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq", 16.384) # (m/1/1/1500)
+
+
self.nodes[0].generate(1)
self.log.info("Stop node, remove wallet, mine again some blocks...")
@@ -36,13 +52,39 @@ class ScantxoutsetTest(BitcoinTestFramework):
self.restart_node(0, ['-nowallet'])
self.log.info("Test if we have found the non HD unspent outputs.")
- assert_equal(self.nodes[0].scantxoutset("start", [ {"pubkey": {"pubkey": pubk1}}, {"pubkey": {"pubkey": pubk2}}, {"pubkey": {"pubkey": pubk3}}])['total_amount'], 6)
- assert_equal(self.nodes[0].scantxoutset("start", [ {"address": addr_P2SH_SEGWIT}, {"address": addr_LEGACY}, {"address": addr_BECH32}])['total_amount'], 6)
- assert_equal(self.nodes[0].scantxoutset("start", [ {"address": addr_P2SH_SEGWIT}, {"address": addr_LEGACY}, {"pubkey": {"pubkey": pubk3}} ])['total_amount'], 6)
+ assert_equal(self.nodes[0].scantxoutset("start", [ "pkh(" + pubk1 + ")", "pkh(" + pubk2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.002"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "wpkh(" + pubk1 + ")", "wpkh(" + pubk2 + ")", "wpkh(" + pubk3 + ")"])['total_amount'], Decimal("0.004"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "sh(wpkh(" + pubk1 + "))", "sh(wpkh(" + pubk2 + "))", "sh(wpkh(" + pubk3 + "))"])['total_amount'], Decimal("0.001"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(" + pubk1 + ")", "combo(" + pubk2 + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "addr(" + addr_BECH32 + ")"])['total_amount'], Decimal("0.007"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007"))
- self.log.info("Test invalid parameters.")
- assert_raises_rpc_error(-8, 'Scanobject "pubkey" must contain an object as value', self.nodes[0].scantxoutset, "start", [ {"pubkey": pubk1}]) #missing pubkey object
- assert_raises_rpc_error(-8, 'Scanobject "address" must contain a single string as value', self.nodes[0].scantxoutset, "start", [ {"address": {"address": addr_P2SH_SEGWIT}}]) #invalid object for address object
+ self.log.info("Test extended key derivation.")
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/0h)"])['total_amount'], Decimal("0.008"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/1h)"])['total_amount'], Decimal("0.016"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500')"])['total_amount'], Decimal("0.032"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/0)"])['total_amount'], Decimal("0.064"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/1)"])['total_amount'], Decimal("0.128"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500)"])['total_amount'], Decimal("0.256"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*h)", "range": 1499}])['total_amount'], Decimal("0.024"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/*h)", "range": 1500}])['total_amount'], Decimal("0.056"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])['total_amount'], Decimal("0.192"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*)", "range": 1500}])['total_amount'], Decimal("0.448"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0')"])['total_amount'], Decimal("0.512"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1')"])['total_amount'], Decimal("1.024"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500h)"])['total_amount'], Decimal("2.048"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])['total_amount'], Decimal("4.096"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1)"])['total_amount'], Decimal("8.192"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500)"])['total_amount'], Decimal("16.384"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"])['total_amount'], Decimal("4.096"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8.192"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1500)"])['total_amount'], Decimal("16.384"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1499}])['total_amount'], Decimal("1.536"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1500}])['total_amount'], Decimal("3.584"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
if __name__ == '__main__':
ScantxoutsetTest().main()