Skip to content

Commit 85b601e

Browse files
committed
Merge bitcoin/bitcoin#24148: Miniscript support in Output Descriptors
ffc79b8 qa: functional test Miniscript watchonly support (Antoine Poinsot) bfb0367 Miniscript support in output descriptors (Antoine Poinsot) 4a08288 qa: better error reporting on descriptor parsing error (Antoine Poinsot) d25d58b miniscript: add a helper to find the first insane sub with no child (Antoine Poinsot) c38c7c5 miniscript: don't check for top level validity at parsing time (Antoine Poinsot) Pull request description: This adds Miniscript support for Output Descriptors without any signing logic (yet). See the OP of #24147 for a description of Miniscript and a rationale of having it in Bitcoin Core. On its own, this PR adds "watchonly" support for Miniscript descriptors in the descriptor wallet. A follow-up adds signing support. A minified corpus of Miniscript Descriptors for the `descriptor_parse` fuzz target is available at bitcoin-core/qa-assets#92. The Miniscript descriptors used in the unit tests here and in #24149 were cross-tested against the Rust implementation at https://github.com/rust-bitcoin/rust-miniscript. This PR contains code and insights from Pieter Wuille. ACKs for top commit: Sjors: re-utACK ffc79b8 achow101: ACK ffc79b8 w0xlt: reACK bitcoin/bitcoin@ffc79b8 Tree-SHA512: 02d919d38bb626d3c557eca3680ce71117739fa161b7a92cfdb6c9c432ed88870b1ed127ba24248574c40c7428217d7e9bdd986fd8cd7c51fae8c776e1271fb9
2 parents 02ede4f + ffc79b8 commit 85b601e

File tree

6 files changed

+401
-36
lines changed

6 files changed

+401
-36
lines changed

src/script/descriptor.cpp

Lines changed: 236 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <key_io.h>
88
#include <pubkey.h>
9+
#include <script/miniscript.h>
910
#include <script/script.h>
1011
#include <script/standard.h>
1112

@@ -161,6 +162,20 @@ struct PubkeyProvider
161162

162163
virtual ~PubkeyProvider() = default;
163164

165+
/** Compare two public keys represented by this provider.
166+
* Used by the Miniscript descriptors to check for duplicate keys in the script.
167+
*/
168+
bool operator<(PubkeyProvider& other) const {
169+
CPubKey a, b;
170+
SigningProvider dummy;
171+
KeyOriginInfo dummy_info;
172+
173+
GetPubKey(0, dummy, a, dummy_info);
174+
other.GetPubKey(0, dummy, b, dummy_info);
175+
176+
return a < b;
177+
}
178+
164179
/** Derive a public key.
165180
* read_cache is the cache to read keys from (if not nullptr)
166181
* write_cache is the cache to write keys to (if not nullptr)
@@ -493,12 +508,12 @@ class BIP32PubkeyProvider final : public PubkeyProvider
493508
/** Base class for all Descriptor implementations. */
494509
class DescriptorImpl : public Descriptor
495510
{
496-
//! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for Multisig).
511+
protected:
512+
//! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for WSH and Multisig).
497513
const std::vector<std::unique_ptr<PubkeyProvider>> m_pubkey_args;
498514
//! The string name of the descriptor function.
499515
const std::string m_name;
500516

501-
protected:
502517
//! The sub-descriptor arguments (empty for everything but SH and WSH).
503518
//! In doc/descriptors.m this is referred to as SCRIPT expressions sh(SCRIPT)
504519
//! and wsh(SCRIPT), and distinct from KEY expressions and ADDR expressions.
@@ -563,7 +578,7 @@ class DescriptorImpl : public Descriptor
563578
return true;
564579
}
565580

566-
bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, const DescriptorCache* cache = nullptr) const
581+
virtual bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, const DescriptorCache* cache = nullptr) const
567582
{
568583
std::string extra = ToStringExtra();
569584
size_t pos = extra.size() > 0 ? 1 : 0;
@@ -917,6 +932,89 @@ class TRDescriptor final : public DescriptorImpl
917932
bool IsSingleType() const final { return true; }
918933
};
919934

935+
/* We instantiate Miniscript here with a simple integer as key type.
936+
* The value of these key integers are an index in the
937+
* DescriptorImpl::m_pubkey_args vector.
938+
*/
939+
940+
/**
941+
* The context for converting a Miniscript descriptor into a Script.
942+
*/
943+
class ScriptMaker {
944+
//! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args).
945+
const std::vector<CPubKey>& m_keys;
946+
947+
public:
948+
ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND) : m_keys(keys) {}
949+
950+
std::vector<unsigned char> ToPKBytes(uint32_t key) const {
951+
return {m_keys[key].begin(), m_keys[key].end()};
952+
}
953+
954+
std::vector<unsigned char> ToPKHBytes(uint32_t key) const {
955+
auto id = m_keys[key].GetID();
956+
return {id.begin(), id.end()};
957+
}
958+
};
959+
960+
/**
961+
* The context for converting a Miniscript descriptor to its textual form.
962+
*/
963+
class StringMaker {
964+
//! To convert private keys for private descriptors.
965+
const SigningProvider* m_arg;
966+
//! Keys contained in the Miniscript (a reference to DescriptorImpl::m_pubkey_args).
967+
const std::vector<std::unique_ptr<PubkeyProvider>>& m_pubkeys;
968+
//! Whether to serialize keys as private or public.
969+
bool m_private;
970+
971+
public:
972+
StringMaker(const SigningProvider* arg LIFETIMEBOUND, const std::vector<std::unique_ptr<PubkeyProvider>>& pubkeys LIFETIMEBOUND, bool priv)
973+
: m_arg(arg), m_pubkeys(pubkeys), m_private(priv) {}
974+
975+
std::optional<std::string> ToString(uint32_t key) const
976+
{
977+
std::string ret;
978+
if (m_private) {
979+
if (!m_pubkeys[key]->ToPrivateString(*m_arg, ret)) return {};
980+
} else {
981+
ret = m_pubkeys[key]->ToString();
982+
}
983+
return ret;
984+
}
985+
};
986+
987+
class MiniscriptDescriptor final : public DescriptorImpl
988+
{
989+
private:
990+
miniscript::NodeRef<uint32_t> m_node;
991+
992+
protected:
993+
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts,
994+
FlatSigningProvider& provider) const override
995+
{
996+
for (const auto& key : keys) provider.pubkeys.emplace(key.GetID(), key);
997+
return Vector(m_node->ToScript(ScriptMaker(keys)));
998+
}
999+
1000+
public:
1001+
MiniscriptDescriptor(std::vector<std::unique_ptr<PubkeyProvider>> providers, miniscript::NodeRef<uint32_t> node)
1002+
: DescriptorImpl(std::move(providers), "?"), m_node(std::move(node)) {}
1003+
1004+
bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type,
1005+
const DescriptorCache* cache = nullptr) const override
1006+
{
1007+
if (const auto res = m_node->ToString(StringMaker(arg, m_pubkey_args, type == StringType::PRIVATE))) {
1008+
out = *res;
1009+
return true;
1010+
}
1011+
return false;
1012+
}
1013+
1014+
bool IsSolvable() const override { return false; } // For now, mark these descriptors as non-solvable (as we don't have signing logic for them).
1015+
bool IsSingleType() const final { return true; }
1016+
};
1017+
9201018
////////////////////////////////////////////////////////////////////////////
9211019
// Parser //
9221020
////////////////////////////////////////////////////////////////////////////
@@ -1058,6 +1156,94 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
10581156
return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider));
10591157
}
10601158

1159+
std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
1160+
{
1161+
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false);
1162+
KeyOriginInfo info;
1163+
if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
1164+
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
1165+
}
1166+
return key_provider;
1167+
}
1168+
1169+
std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider)
1170+
{
1171+
unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02};
1172+
std::copy(xkey.begin(), xkey.end(), full_key + 1);
1173+
CPubKey pubkey(full_key);
1174+
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true);
1175+
KeyOriginInfo info;
1176+
if (provider.GetKeyOriginByXOnly(xkey, info)) {
1177+
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
1178+
}
1179+
return key_provider;
1180+
}
1181+
1182+
/**
1183+
* The context for parsing a Miniscript descriptor (either from Script or from its textual representation).
1184+
*/
1185+
struct KeyParser {
1186+
//! The Key type is an index in DescriptorImpl::m_pubkey_args
1187+
using Key = uint32_t;
1188+
//! Must not be nullptr if parsing from string.
1189+
FlatSigningProvider* m_out;
1190+
//! Must not be nullptr if parsing from Script.
1191+
const SigningProvider* m_in;
1192+
//! List of keys contained in the Miniscript.
1193+
mutable std::vector<std::unique_ptr<PubkeyProvider>> m_keys;
1194+
//! Used to detect key parsing errors within a Miniscript.
1195+
mutable std::string m_key_parsing_error;
1196+
1197+
KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND) : m_out(out), m_in(in) {}
1198+
1199+
bool KeyCompare(const Key& a, const Key& b) const {
1200+
return *m_keys.at(a) < *m_keys.at(b);
1201+
}
1202+
1203+
template<typename I> std::optional<Key> FromString(I begin, I end) const
1204+
{
1205+
assert(m_out);
1206+
Key key = m_keys.size();
1207+
auto pk = ParsePubkey(key, {&*begin, &*end}, ParseScriptContext::P2WSH, *m_out, m_key_parsing_error);
1208+
if (!pk) return {};
1209+
m_keys.push_back(std::move(pk));
1210+
return key;
1211+
}
1212+
1213+
std::optional<std::string> ToString(const Key& key) const
1214+
{
1215+
return m_keys.at(key)->ToString();
1216+
}
1217+
1218+
template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const
1219+
{
1220+
assert(m_in);
1221+
CPubKey pubkey(begin, end);
1222+
if (pubkey.IsValid()) {
1223+
Key key = m_keys.size();
1224+
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
1225+
return key;
1226+
}
1227+
return {};
1228+
}
1229+
1230+
template<typename I> std::optional<Key> FromPKHBytes(I begin, I end) const
1231+
{
1232+
assert(end - begin == 20);
1233+
assert(m_in);
1234+
uint160 hash;
1235+
std::copy(begin, end, hash.begin());
1236+
CKeyID keyid(hash);
1237+
CPubKey pubkey;
1238+
if (m_in->GetPubKey(keyid, pubkey)) {
1239+
Key key = m_keys.size();
1240+
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
1241+
return key;
1242+
}
1243+
return {};
1244+
}
1245+
};
1246+
10611247
/** Parse a script in a particular context. */
10621248
std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
10631249
{
@@ -1279,6 +1465,45 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
12791465
error = "Can only have raw() at top level";
12801466
return nullptr;
12811467
}
1468+
// Process miniscript expressions.
1469+
{
1470+
KeyParser parser(&out, nullptr);
1471+
auto node = miniscript::FromString(std::string(expr.begin(), expr.end()), parser);
1472+
if (node) {
1473+
if (ctx != ParseScriptContext::P2WSH) {
1474+
error = "Miniscript expressions can only be used in wsh";
1475+
return nullptr;
1476+
}
1477+
if (parser.m_key_parsing_error != "") {
1478+
error = std::move(parser.m_key_parsing_error);
1479+
return nullptr;
1480+
}
1481+
if (!node->IsSane()) {
1482+
// Try to find the first insane sub for better error reporting.
1483+
auto insane_node = node.get();
1484+
if (const auto sub = node->FindInsaneSub()) insane_node = sub;
1485+
if (const auto str = insane_node->ToString(parser)) error = *str;
1486+
if (!insane_node->IsValid()) {
1487+
error += " is invalid";
1488+
} else {
1489+
error += " is not sane";
1490+
if (!insane_node->IsNonMalleable()) {
1491+
error += ": malleable witnesses exist";
1492+
} else if (insane_node == node.get() && !insane_node->NeedsSignature()) {
1493+
error += ": witnesses without signature exist";
1494+
} else if (!insane_node->CheckTimeLocksMix()) {
1495+
error += ": contains mixes of timelocks expressed in blocks and seconds";
1496+
} else if (!insane_node->CheckDuplicateKey()) {
1497+
error += ": contains duplicate public keys";
1498+
} else if (!insane_node->ValidSatisfactions()) {
1499+
error += ": needs witnesses that may exceed resource limits";
1500+
}
1501+
}
1502+
return nullptr;
1503+
}
1504+
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));
1505+
}
1506+
}
12821507
if (ctx == ParseScriptContext::P2SH) {
12831508
error = "A function is needed within P2SH";
12841509
return nullptr;
@@ -1290,29 +1515,6 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
12901515
return nullptr;
12911516
}
12921517

1293-
std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
1294-
{
1295-
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false);
1296-
KeyOriginInfo info;
1297-
if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
1298-
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
1299-
}
1300-
return key_provider;
1301-
}
1302-
1303-
std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider)
1304-
{
1305-
unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02};
1306-
std::copy(xkey.begin(), xkey.end(), full_key + 1);
1307-
CPubKey pubkey(full_key);
1308-
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true);
1309-
KeyOriginInfo info;
1310-
if (provider.GetKeyOriginByXOnly(xkey, info)) {
1311-
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
1312-
}
1313-
return key_provider;
1314-
}
1315-
13161518
std::unique_ptr<DescriptorImpl> InferMultiA(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider)
13171519
{
13181520
auto match = MatchMultiA(script);
@@ -1426,6 +1628,14 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
14261628
}
14271629
}
14281630

1631+
if (ctx == ParseScriptContext::P2WSH) {
1632+
KeyParser parser(nullptr, &provider);
1633+
auto node = miniscript::FromScript(script, parser);
1634+
if (node && node->IsSane()) {
1635+
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));
1636+
}
1637+
}
1638+
14291639
CTxDestination dest;
14301640
if (ExtractDestination(script, dest)) {
14311641
if (GetScriptForDestination(dest) == script) {

src/script/miniscript.h

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,21 @@ struct Node {
429429
));
430430
}
431431

432+
/** Like TreeEval, but without downfn or State type.
433+
* upfn takes (const Node&, Span<Result>) and returns Result. */
434+
template<typename Result, typename UpFn>
435+
Result TreeEval(UpFn upfn) const
436+
{
437+
struct DummyState {};
438+
return std::move(*TreeEvalMaybe<Result>(DummyState{},
439+
[](DummyState, const Node&, size_t) { return DummyState{}; },
440+
[&upfn](DummyState, const Node& node, Span<Result> subs) {
441+
Result res{upfn(node, subs)};
442+
return std::optional<Result>(std::move(res));
443+
}
444+
));
445+
}
446+
432447
/** Compare two miniscript subtrees, using a non-recursive algorithm. */
433448
friend int Compare(const Node<Key>& node1, const Node<Key>& node2)
434449
{
@@ -818,6 +833,15 @@ struct Node {
818833
//! Return the expression type.
819834
Type GetType() const { return typ; }
820835

836+
//! Find an insane subnode which has no insane children. Nullptr if there is none.
837+
const Node* FindInsaneSub() const {
838+
return TreeEval<const Node*>([](const Node& node, Span<const Node*> subs) -> const Node* {
839+
for (auto& sub: subs) if (sub) return sub;
840+
if (!node.IsSaneSubexpression()) return &node;
841+
return nullptr;
842+
});
843+
}
844+
821845
//! Check whether this node is valid at all.
822846
bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; }
823847

@@ -953,7 +977,11 @@ void BuildBack(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>>& construct
953977
}
954978
}
955979

956-
//! Parse a miniscript from its textual descriptor form.
980+
/**
981+
* Parse a miniscript from its textual descriptor form.
982+
* This does not check whether the script is valid, let alone sane. The caller is expected to use
983+
* the `IsValidTopLevel()` and `IsSaneTopLevel()` to check for these properties on the node.
984+
*/
957985
template<typename Key, typename Ctx>
958986
inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
959987
{
@@ -1255,9 +1283,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
12551283
// Sanity checks on the produced miniscript
12561284
assert(constructed.size() == 1);
12571285
if (in.size() > 0) return {};
1258-
const NodeRef<Key> tl_node = std::move(constructed.front());
1259-
if (!tl_node->IsValidTopLevel()) return {};
1260-
return tl_node;
1286+
return std::move(constructed.front());
12611287
}
12621288

12631289
/** Decode a script into opcode/push pairs.

0 commit comments

Comments
 (0)