Skip to content

Commit bfb0367

Browse files
darosiorsipa
andcommitted
Miniscript support in output descriptors
Miniscript descriptors are defined under P2WSH context (either `wsh()` or `sh(wsh())`). Only sane Miniscripts are accepted, as insane ones (although valid by type) can have surprising behaviour with regard to malleability guarantees and resources limitations. As Miniscript descriptors are longer and more complex than "legacy" descriptors, care was taken in error reporting to help a user determine for what reason a provided Miniscript is insane. Co-authored-by: Pieter Wuille <[email protected]>
1 parent 4a08288 commit bfb0367

File tree

2 files changed

+259
-26
lines changed

2 files changed

+259
-26
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) {

0 commit comments

Comments
 (0)