Skip to content

Commit 8ff9489

Browse files
committed
descriptor: Tapscript-specific Miniscript key serialization / parsing
64-hex-characters public keys are valid in Miniscript key expressions within a Tapscript context. Keys under a Tapscript context always serialize as 32-bytes x-only public keys (and that's what get hashed by OP_HASH160 on the stack too).
1 parent 5e76f3f commit 8ff9489

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

src/script/descriptor.cpp

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,16 +1114,33 @@ class TRDescriptor final : public DescriptorImpl
11141114
class ScriptMaker {
11151115
//! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args).
11161116
const std::vector<CPubKey>& m_keys;
1117+
//! The script context we're operating within (Tapscript or P2WSH).
1118+
const miniscript::MiniscriptContext m_script_ctx;
1119+
1120+
//! Get the ripemd160(sha256()) hash of this key.
1121+
//! Any key that is valid in a descriptor serializes as 32 bytes within a Tapscript context. So we
1122+
//! must not hash the sign-bit byte in this case.
1123+
uint160 GetHash160(uint32_t key) const {
1124+
if (miniscript::IsTapscript(m_script_ctx)) {
1125+
return Hash160(XOnlyPubKey{m_keys[key]});
1126+
}
1127+
return m_keys[key].GetID();
1128+
}
11171129

11181130
public:
1119-
ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND) : m_keys(keys) {}
1131+
ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND, const miniscript::MiniscriptContext script_ctx) : m_keys(keys), m_script_ctx{script_ctx} {}
11201132

11211133
std::vector<unsigned char> ToPKBytes(uint32_t key) const {
1122-
return {m_keys[key].begin(), m_keys[key].end()};
1134+
// In Tapscript keys always serialize as x-only, whether an x-only key was used in the descriptor or not.
1135+
if (!miniscript::IsTapscript(m_script_ctx)) {
1136+
return {m_keys[key].begin(), m_keys[key].end()};
1137+
}
1138+
const XOnlyPubKey xonly_pubkey{m_keys[key]};
1139+
return {xonly_pubkey.begin(), xonly_pubkey.end()};
11231140
}
11241141

11251142
std::vector<unsigned char> ToPKHBytes(uint32_t key) const {
1126-
auto id = m_keys[key].GetID();
1143+
auto id = GetHash160(key);
11271144
return {id.begin(), id.end()};
11281145
}
11291146
};
@@ -1165,7 +1182,7 @@ class MiniscriptDescriptor final : public DescriptorImpl
11651182
FlatSigningProvider& provider) const override
11661183
{
11671184
for (const auto& key : keys) provider.pubkeys.emplace(key.GetID(), key);
1168-
return Vector(m_node->ToScript(ScriptMaker(keys)));
1185+
return Vector(m_node->ToScript(ScriptMaker(keys, m_node->GetMsCtx())));
11691186
}
11701187

11711188
public:
@@ -1434,11 +1451,19 @@ struct KeyParser {
14341451
return *m_keys.at(a) < *m_keys.at(b);
14351452
}
14361453

1454+
ParseScriptContext ParseContext() const {
1455+
switch (m_script_ctx) {
1456+
case miniscript::MiniscriptContext::P2WSH: return ParseScriptContext::P2WSH;
1457+
case miniscript::MiniscriptContext::TAPSCRIPT: return ParseScriptContext::P2TR;
1458+
}
1459+
assert(false);
1460+
}
1461+
14371462
template<typename I> std::optional<Key> FromString(I begin, I end) const
14381463
{
14391464
assert(m_out);
14401465
Key key = m_keys.size();
1441-
auto pk = ParsePubkey(key, {&*begin, &*end}, ParseScriptContext::P2WSH, *m_out, m_key_parsing_error);
1466+
auto pk = ParsePubkey(key, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error);
14421467
if (!pk) return {};
14431468
m_keys.push_back(std::move(pk));
14441469
return key;
@@ -1452,11 +1477,18 @@ struct KeyParser {
14521477
template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const
14531478
{
14541479
assert(m_in);
1455-
CPubKey pubkey(begin, end);
1456-
if (pubkey.IsValidNonHybrid()) {
1457-
Key key = m_keys.size();
1458-
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
1480+
Key key = m_keys.size();
1481+
if (miniscript::IsTapscript(m_script_ctx) && end - begin == 32) {
1482+
XOnlyPubKey pubkey;
1483+
std::copy(begin, end, pubkey.begin());
1484+
m_keys.push_back(InferPubkey(pubkey.GetEvenCorrespondingCPubKey(), ParseContext(), *m_in));
14591485
return key;
1486+
} else if (!miniscript::IsTapscript(m_script_ctx)) {
1487+
CPubKey pubkey{begin, end};
1488+
if (pubkey.IsValidNonHybrid()) {
1489+
m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in));
1490+
return key;
1491+
}
14601492
}
14611493
return {};
14621494
}
@@ -1471,7 +1503,7 @@ struct KeyParser {
14711503
CPubKey pubkey;
14721504
if (m_in->GetPubKey(keyid, pubkey)) {
14731505
Key key = m_keys.size();
1474-
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
1506+
m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in));
14751507
return key;
14761508
}
14771509
return {};

src/script/miniscript.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,9 @@ struct Node {
15161516
//! Return the expression type.
15171517
Type GetType() const { return typ; }
15181518

1519+
//! Return the script context for this node.
1520+
MiniscriptContext GetMsCtx() const { return m_script_ctx; }
1521+
15191522
//! Find an insane subnode which has no insane children. Nullptr if there is none.
15201523
const Node* FindInsaneSub() const {
15211524
return TreeEval<const Node*>([](const Node& node, Span<const Node*> subs) -> const Node* {

0 commit comments

Comments
 (0)