Merge pull request #4187 from kittywhiskers/scantxout

merge #12196, #13697: Add scantxoutset RPC method
This commit is contained in:
PastaPastaPasta 2021-07-25 19:10:11 -05:00 committed by GitHub
commit edf0552c0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1143 additions and 2 deletions

View File

@ -239,6 +239,7 @@ BITCOIN_CORE_H = \
rpc/util.h \
saltedhasher.h \
scheduler.h \
script/descriptor.h \
script/ismine.h \
script/sigcache.h \
script/sign.h \
@ -576,6 +577,7 @@ libdash_common_a_SOURCES = \
protocol.cpp \
saltedhasher.cpp \
scheduler.cpp \
script/descriptor.cpp \
script/ismine.cpp \
script/sign.cpp \
script/standard.cpp \

View File

@ -60,6 +60,7 @@ BITCOIN_TESTS =\
test/cuckoocache_tests.cpp \
test/denialofservice_tests.cpp \
test/dip0020opcodes_tests.cpp \
test/descriptor_tests.cpp \
test/evo_deterministicmns_tests.cpp \
test/evo_simplifiedmns_tests.cpp \
test/fs_tests.cpp \

View File

@ -7,18 +7,22 @@
#include <rpc/blockchain.h>
#include <amount.h>
#include <base58.h>
#include <chain.h>
#include <chainparams.h>
#include <checkpoints.h>
#include <coins.h>
#include <node/coinstats.h>
#include <core_io.h>
#include <consensus/validation.h>
#include <key_io.h>
#include <validation.h>
#include <index/txindex.h>
#include <policy/feerate.h>
#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>
@ -36,6 +40,7 @@
#include <llmq/quorums_chainlocks.h>
#include <llmq/quorums_instantsend.h>
#include <assert.h>
#include <stdint.h>
#include <univalue.h>
@ -2273,6 +2278,217 @@ static UniValue savemempool(const JSONRPCRequest& request)
return NullUniValue;
}
//! Search for a given set of pubkey scripts
bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results) {
scan_progress = 0;
count = 0;
while (cursor->Valid()) {
COutPoint key;
Coin coin;
if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false;
if (++count % 8192 == 0) {
boost::this_thread::interruption_point();
if (should_abort) {
// allow to abort the scan via the abort reference
return false;
}
}
if (count % 256 == 0) {
// update progress reference every 256 item
uint32_t high = 0x100 * *key.hash.begin() + *(key.hash.begin() + 1);
scan_progress = (int)(high * 100.0 / 65536.0 + 0.5);
}
if (needles.count(coin.out.scriptPubKey)) {
out_results.emplace(key, coin);
}
cursor->Next();
}
scan_progress = 100;
return true;
}
/** RAII object to prevent concurrency issue when scanning the txout set */
static std::mutex g_utxosetscan;
static std::atomic<int> g_scan_progress;
static std::atomic<bool> g_scan_in_progress;
static std::atomic<bool> g_should_abort_scan;
class CoinsViewScanReserver
{
private:
bool m_could_reserve;
public:
explicit CoinsViewScanReserver() : m_could_reserve(false) {}
bool reserve() {
assert (!m_could_reserve);
std::lock_guard<std::mutex> lock(g_utxosetscan);
if (g_scan_in_progress) {
return false;
}
g_scan_in_progress = true;
m_could_reserve = true;
return true;
}
~CoinsViewScanReserver() {
if (m_could_reserve) {
std::lock_guard<std::mutex> lock(g_utxosetscan);
g_scan_in_progress = 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"
"\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 and P2PKH 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, 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"
"\nResult:\n"
"{\n"
" \"unspents\": [\n"
" {\n"
" \"txid\" : \"transactionid\", (string) The transaction id\n"
" \"vout\": n, (numeric) the vout value\n"
" \"scriptPubKey\" : \"script\", (string) the script key\n"
" \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n"
" \"height\" : n, (numeric) Height of the unspent transaction output\n"
" }\n"
" ,...], \n"
" \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n"
"]\n"
);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR});
UniValue result(UniValue::VOBJ);
if (request.params[0].get_str() == "status") {
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// no scan in progress
return NullUniValue;
}
result.pushKV("progress", g_scan_progress);
return result;
} else if (request.params[0].get_str() == "abort") {
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// reserve was possible which means no scan was running
return false;
}
// set the abort flag
g_should_abort_scan = true;
return true;
} else if (request.params[0].get_str() == "start") {
CoinsViewScanReserver reserver;
if (!reserver.reserve()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
}
std::set<CScript> needles;
CAmount total_in = 0;
// loop through the scan objects
for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
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");
}
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));
}
needles.insert(scripts.begin(), scripts.end());
}
}
// Scan the unspent transaction output set for inputs
UniValue unspents(UniValue::VARR);
std::vector<CTxOut> input_txos;
std::map<COutPoint, Coin> coins;
g_should_abort_scan = false;
g_scan_progress = 0;
int64_t count = 0;
std::unique_ptr<CCoinsViewCursor> pcursor;
{
LOCK(cs_main);
FlushStateToDisk();
pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor());
assert(pcursor);
}
bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins);
result.pushKV("success", res);
result.pushKV("searched_items", count);
for (const auto& it : coins) {
const COutPoint& outpoint = it.first;
const Coin& coin = it.second;
const CTxOut& txo = coin.out;
input_txos.push_back(txo);
total_in += txo.nValue;
UniValue unspent(UniValue::VOBJ);
unspent.pushKV("txid", outpoint.hash.GetHex());
unspent.pushKV("vout", (int32_t)outpoint.n);
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
unspent.pushKV("amount", ValueFromAmount(txo.nValue));
unspent.pushKV("height", (int32_t)coin.nHeight);
unspents.push_back(unspent);
}
result.pushKV("unspents", unspents);
result.pushKV("total_amount", ValueFromAmount(total_in));
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command");
}
return result;
}
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
@ -2303,6 +2519,7 @@ static const CRPCCommand commands[] =
{ "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} },
{ "blockchain", "preciousblock", &preciousblock, {"blockhash"} },
{ "blockchain", "scantxoutset", &scantxoutset, {"action", "scanobjects"} },
/* Not shown in help */
{ "hidden", "invalidateblock", &invalidateblock, {"blockhash"} },

View File

@ -97,6 +97,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendmany", 6, "use_is" },
{ "sendmany", 7, "use_cj" },
{ "sendmany", 8, "conf_target" },
{ "scantxoutset", 1, "scanobjects" },
{ "addmultisigaddress", 0, "nrequired" },
{ "addmultisigaddress", 1, "keys" },
{ "createmultisig", 0, "nrequired" },

547
src/script/descriptor.cpp Normal file
View File

@ -0,0 +1,547 @@
// 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/system.h>
#include <util/memory.h>
#include <util/strencodings.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); }
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) + ")"; }
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) 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); }
/** 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) 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)); }
/** 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);
}
return true;
}
};
////////////////////////////////////////////////////////////////////////////
// Parser //
////////////////////////////////////////////////////////////////////////////
enum class ParseScriptContext {
TOP,
P2SH
};
/** 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::P2SH, out);
if (!pubkey) return nullptr;
return MakeUnique<SingleKeyDescriptor>(std::move(pubkey), P2PKGetScript, "pk");
}
if (Func("pkh", expr)) {
auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2SH, 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::P2SH, 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::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::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;
}

99
src/script/descriptor.h Normal file
View File

@ -0,0 +1,99 @@
// 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.
// * sh(S): Pay-to-script-hash (P2SH) output for script S
// * combo(P): combination of P2PK and P2PKH 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

View File

@ -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) {}
@ -389,6 +388,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(32, 32);
@ -410,7 +421,6 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script)
return false;
}
bool PartiallySignedTransaction::IsNull() const
{
return !tx && inputs.empty() && outputs.empty() && unknown.empty();
@ -552,3 +562,19 @@ bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& inf
if (m_hide_origin) return false;
return m_provider->GetKeyOrigin(keyid, info);
}
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;
}

View File

@ -54,6 +54,19 @@ public:
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
};
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:

View File

@ -0,0 +1,145 @@
// 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_dash.h>
#include <boost/test/unit_test.hpp>
#include <script/descriptor.h>
#include <util/strencodings.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.
std::unique_ptr<Descriptor> parse_priv = Parse(MaybeUseHInsteadOfApostrophy(prv), keys_priv);
std::unique_ptr<Descriptor> 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]));
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("pk(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}});
Check("pkh(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}});
Check("combo(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}});
// Basic single-key uncompressed
Check("pk(7sH936MDoVPFVk2VoaCM5yW8P3BfPyffnZECyaHrZwfLgWpS13e)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}});
Check("pkh(7sH936MDoVPFVk2VoaCM5yW8P3BfPyffnZECyaHrZwfLgWpS13e)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}});
Check("combo(7sH936MDoVPFVk2VoaCM5yW8P3BfPyffnZECyaHrZwfLgWpS13e)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}});
// Some unconventional single-key constructions
Check("sh(pk(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}});
Check("sh(pkh(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}});
// Versions with BIP32 derivations
Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}});
Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}});
Check("combo(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac"}});
Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac"}});
CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)"); // BIP 32 path element overflow
// Multisig constructions
Check("multi(1,XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2,7sH936MDoVPFVk2VoaCM5yW8P3BfPyffnZECyaHrZwfLgWpS13e)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}});
Check("sh(multi(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}});
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(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2SH needs a script, not a key
CheckUnparsable("sh(sh(pk(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)))", "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2SH inside P2SH
CheckUnparsable("sh(combo(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2))", "sh(combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))"); // Old must be top level
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,89 @@
#!/usr/bin/env python3
# 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.
"""Test the scantxoutset rpc call."""
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, Decimal
import shutil
import os
class ScantxoutsetTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def run_test(self):
self.log.info("Mining blocks...")
self.nodes[0].generate(110)
addr1 = self.nodes[0].getnewaddress("")
pubk1 = self.nodes[0].getaddressinfo(addr1)['pubkey']
addr2 = self.nodes[0].getnewaddress("")
pubk2 = self.nodes[0].getaddressinfo(addr2)['pubkey']
addr3 = self.nodes[0].getnewaddress("")
pubk3 = self.nodes[0].getaddressinfo(addr3)['pubkey']
self.nodes[0].sendtoaddress(addr1, 0.001)
self.nodes[0].sendtoaddress(addr2, 0.002)
self.nodes[0].sendtoaddress(addr3, 0.004)
#send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK
self.nodes[0].sendtoaddress("yR5yZLjevw5kX3UxGiQN1g96LXGJni2wSS", 0.008) # (m/0'/0'/0')
self.nodes[0].sendtoaddress("yPcxzaQekxTjaJVSaZ58r22o37H8moWPK2", 0.016) # (m/0'/0'/1')
self.nodes[0].sendtoaddress("yhv7iRHSx4SgyvCPmkm6Js8gTuJTtJH9ec", 0.032) # (m/0'/0'/1500')
self.nodes[0].sendtoaddress("yWEdyyKVNbmaiXHkg3LVPqgoXpMA3S6Xt7", 0.064) # (m/0'/0'/0)
self.nodes[0].sendtoaddress("yTGAdq8sSHJ1QrcqSUaMHs8RMj3Bqz3bkb", 0.128) # (m/0'/0'/1)
self.nodes[0].sendtoaddress("yRTNkmjXjhatND4Dv3V4GwBzYJQ4o9ukQr", 0.256) # (m/0'/0'/1500)
self.nodes[0].sendtoaddress("yPwUp9Vwmr4zE6rSuZg3TBxeyRerdRAbNd", 0.512) # (m/1/1/0')
self.nodes[0].sendtoaddress("yLapNU3bG8E8JNGXRhZbRHHDifrqTucGcg", 1.024) # (m/1/1/1')
self.nodes[0].sendtoaddress("yUhbAKf7AcTC5sPXb2dkABKm3FYENqdzv2", 2.048) # (m/1/1/1500')
self.nodes[0].sendtoaddress("yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM", 4.096) # (m/1/1/0)
self.nodes[0].sendtoaddress("ydccVGNV2EcEouAxbbgdu8pi8gkdaqkiav", 8.192) # (m/1/1/1)
self.nodes[0].sendtoaddress("yVCdQxPXJ3SrtTLv8FuLXDNaynz6kmjPNq", 16.384) # (m/1/1/1500)
self.nodes[0].generate(1)
self.log.info("Stop node, remove wallet, mine again some blocks...")
self.stop_node(0)
shutil.rmtree(os.path.join(self.nodes[0].datadir, "regtest", 'wallets'))
self.start_node(0)
self.nodes[0].generate(110)
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", [ "pkh(" + pubk1 + ")", "pkh(" + pubk2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.007"))
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(" + addr1 + ")", "addr(" + addr2 + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007"))
assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "addr(" + addr3 + ")"])['total_amount'], Decimal("0.007"))
assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.007"))
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()

View File

@ -193,6 +193,7 @@ BASE_SCRIPTS = [
'p2p_unrequested_blocks.py',
'feature_asmap.py',
'feature_includeconf.py',
'rpc_scantxoutset.py',
'feature_logging.py',
'p2p_node_network_limited.py',
'p2p_permissions.py',