Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
script/descriptor.cpp
script/miniscript.cpp
script/parsing.cpp
script/policy.cpp
script/sign.cpp
script/signingprovider.cpp
script/solver.cpp
Expand Down
80 changes: 80 additions & 0 deletions src/script/descriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <pubkey.h>
#include <script/miniscript.h>
#include <script/parsing.h>
#include <script/policy.h>
#include <script/script.h>
#include <script/signingprovider.h>
#include <script/solver.h>
Expand Down Expand Up @@ -1941,6 +1942,85 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
error = "Can only have addr() at top level";
return {};
}
if (ctx == ParseScriptContext::TOP && Func("compile_tr", expr)) {
auto arg = Expr(expr);
auto internal_keys = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error);
if (internal_keys.empty()) {
error = strprintf("compile_tr(): %s", error);
return {};
}
++key_exp_index;
if (!Const(",", expr)) {
error = "compile_tr(): expected ',' after internal key";
return {};
}

KeyParser parser(/*out=*/&out, /*in=*/nullptr, /*ctx=*/miniscript::MiniscriptContext::TAPSCRIPT, key_exp_index);
auto pol = policy::ParsePolicy<uint32_t>(expr, parser);
if (!pol || !expr.empty()) {
error = "compile_tr(): invalid policy expression";
if (!parser.m_key_parsing_error.empty()) error = strprintf("compile_tr(): %s", parser.m_key_parsing_error);
return {};
}

auto result = policy::CompileTrNative<uint32_t>(*pol);
if (!result) {
error = "compile_tr(): policy compilation failed (too many leaves or unsupported policy)";
return {};
}

auto& [scripts, depths] = *result;

size_t max_providers_len = internal_keys.size();
if (!parser.m_keys.empty()) {
max_providers_len = std::max(max_providers_len, std::max_element(parser.m_keys.begin(), parser.m_keys.end(),
[](const std::vector<std::unique_ptr<PubkeyProvider>>& a, const std::vector<std::unique_ptr<PubkeyProvider>>& b) {
return a.size() < b.size();
})->size());
}

for (auto& vec : parser.m_keys) {
if (vec.size() == 1) {
for (size_t j = 1; j < max_providers_len; ++j) {
vec.emplace_back(vec.at(0)->Clone());
}
} else if (vec.size() != max_providers_len) {
error = "compile_tr(): multipath derivation paths have mismatched lengths";
return {};
}
}

if (internal_keys.size() > 1 && internal_keys.size() != max_providers_len) {
error = "compile_tr(): multipath internal key mismatches multipath subscripts lengths";
return {};
}
while (internal_keys.size() < max_providers_len) {
internal_keys.emplace_back(internal_keys.at(0)->Clone());
}

key_exp_index += parser.m_keys.size();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is key_exp_index used? (I've searched throughout the parser and I don't see it ever used.)

assert(TaprootBuilder::ValidDepths(depths));

for (size_t mp = 0; mp < max_providers_len; ++mp) {
std::vector<std::unique_ptr<DescriptorImpl>> descs;
descs.reserve(scripts.size());
for (size_t i = 0; i < scripts.size(); ++i) {
std::vector<std::unique_ptr<PubkeyProvider>> leaf_keys;
leaf_keys.reserve(parser.m_keys.size());
for (auto& key_vec : parser.m_keys) {
leaf_keys.push_back(key_vec.at(mp)->Clone());
}
descs.push_back(std::make_unique<MiniscriptDescriptor>(
std::move(leaf_keys), (mp == max_providers_len - 1) ? std::move(scripts[i]) : scripts[i]->Clone()));
}
ret.emplace_back(std::make_unique<TRDescriptor>(std::move(internal_keys.at(mp)), std::move(descs), depths));
}
return ret;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where can I find design material on this?


} else if (Func("compile_tr", expr)) {
error = "Can only have compile_tr at top level";
return {};
}
if (ctx == ParseScriptContext::TOP && Func("tr", expr)) {
auto arg = Expr(expr);
auto internal_keys = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error);
Expand Down
22 changes: 22 additions & 0 deletions src/script/miniscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -1642,6 +1642,28 @@ struct Node {
//! Check whether this node is safe as a script on its own.
bool IsSane() const { return IsValidTopLevel() && IsSaneSubexpression() && NeedsSignature(); }

//! Check whether this node or any sub-node contains OP_IF/NOTIF-emitting fragments
//! (WRAP_D, WRAP_J, ANDOR, OR_C, OR_D, OR_I). In Tapscript, these should be replaced
//! by separate Taptree leaves for better privacy and efficiency.
bool HasBranchingOpcodes(size_t depth = 0) const {
if (depth > 100) return true;
switch (fragment) {
case Fragment::WRAP_D:
case Fragment::WRAP_J:
case Fragment::ANDOR:
case Fragment::OR_C:
case Fragment::OR_D:
case Fragment::OR_I:
return true;
default:
break;
}
for (const auto& sub : subs) {
if (sub->HasBranchingOpcodes(depth + 1)) return true;
}
return false;
}

//! Produce a witness for this script, if possible and given the information available in the context.
//! The non-malleable satisfaction is guaranteed to be valid if it exists, and ValidSatisfaction()
//! is true. If IsSane() holds, this satisfaction is guaranteed to succeed in case the node's
Expand Down
5 changes: 5 additions & 0 deletions src/script/policy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) 2026 The Bitcoin Knots developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <script/policy.h>
Loading
Loading