Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1001}1002assert(stack.size() == 1);1003assert(stack[0].GetStaticOps() == ops);1004assert(stack[0].ScriptSize() == scriptsize);1005stack[0].DuplicateKeyCheck(KEY_COMP);1006return std::move(stack[0]);1007}1009//! The spk for this script under the given context. If it's a Taproot output also record the spend data.1010CScript ScriptPubKey(MsCtx ctx, const CScript& script, TaprootBuilder& builder)1011{1012if (!miniscript::IsTapscript(ctx)) return CScript() << OP_0 << WitnessV0ScriptHash(script);1014// For Taproot outputs we always use a tree with a single script and a dummy internal key.1015builder.Add(0, script, TAPROOT_LEAF_TAPSCRIPT);1016builder.Finalize(XOnlyPubKey::NUMS_H);1017return GetScriptForDestination(builder.GetOutput());1018}1020//! Fill the witness with the data additional to the script satisfaction.1021void SatisfactionToWitness(MsCtx ctx, CScriptWitness& witness, const CScript& script, TaprootBuilder& builder) {1022// For P2WSH, it's only the witness script.1023witness.stack.emplace_back(script.begin(), script.end());1024if (!miniscript::IsTapscript(ctx)) return;1025// For Tapscript we also need the control block.1026witness.stack.push_back(*builder.GetSpendData().scripts.begin()->second.begin());1027}1029/** Perform various applicable tests on a miniscript Node. */1030void TestNode(const MsCtx script_ctx, const std::optional<Node>& node, FuzzedDataProvider& provider)1031{1032if (!node) return;1034// Check that it roundtrips to text representation1035const ParserContext parser_ctx{script_ctx};1036std::optional<std::string> str{node->ToString(parser_ctx)};1037assert(str);1038auto parsed = miniscript::FromString(*str, parser_ctx);1039assert(parsed);1040assert(*parsed == *node);1042// Check consistency between script size estimation and real size.1043auto script = node->ToScript(parser_ctx);1044assert(node->ScriptSize() == script.size());1046// Check consistency of "x" property with the script (type K is excluded, because it can end1047// with a push of a key, which could match these opcodes).1048if (!(node->GetType() << "K"_mst)) {1049bool ends_in_verify = !(node->GetType() << "x"_mst);1050assert(ends_in_verify == (script.back() == OP_CHECKSIG || script.back() == OP_CHECKMULTISIG || script.back() == OP_EQUAL || script.back() == OP_NUMEQUAL));1051}1053// The rest of the checks only apply when testing a valid top-level script.1054if (!node->IsValidTopLevel()) return;1056// Check roundtrip to script1057auto decoded = miniscript::FromScript(script, parser_ctx);1058assert(decoded);1059// Note we can't use *decoded == *node because the miniscript representation may differ, so we check that:1060// - The script corresponding to that decoded form matches exactly1061// - The type matches exactly1062assert(decoded->ToScript(parser_ctx) == script);1063assert(decoded->GetType() == node->GetType());1065// Optionally pad the script or the witness in order to increase the sensitivity of the tests of1066// the resources limits logic.1067CScriptWitness witness_mal, witness_nonmal;1068if (provider.ConsumeBool()) {1069// Under P2WSH, optionally pad the script with OP_NOPs to max op the ops limit of the constructed script.1070// This makes the script obviously not actually miniscript-compatible anymore, but the1071// signatures constructed in this test don't commit to the script anyway, so the same1072// miniscript satisfier will work. This increases the sensitivity of the test to the ops1073// counting logic being too low, especially for simple scripts.1074// Do this optionally because we're not solely interested in cases where the number of ops is1075// maximal.1076// Do not pad more than what would cause MAX_STANDARD_P2WSH_SCRIPT_SIZE to be reached, however,1077// as that also invalidates scripts.1078const auto node_ops{node->GetOps()};1079if (!IsTapscript(script_ctx) && node_ops && *node_ops < MAX_OPS_PER_SCRIPT1080&& node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) {1081int add = std::min<int>(1082MAX_OPS_PER_SCRIPT - *node_ops,1083MAX_STANDARD_P2WSH_SCRIPT_SIZE - node->ScriptSize());1084for (int i = 0; i < add; ++i) script.push_back(OP_NOP);1085}1087// Under Tapscript, optionally pad the stack up to the limit minus the calculated maximum execution stack1088// size to assert a Miniscript would never add more elements to the stack during execution than anticipated.1089const auto node_exec_ss{node->GetExecStackSize()};1090if (miniscript::IsTapscript(script_ctx) && node_exec_ss && *node_exec_ss < MAX_STACK_SIZE) {1091unsigned add{(unsigned)MAX_STACK_SIZE - *node_exec_ss};1092witness_mal.stack.resize(add);1093witness_nonmal.stack.resize(add);1094script.reserve(add);1095for (unsigned i = 0; i < add; ++i) script.push_back(OP_NIP);1096}1097}1099const SatisfierContext satisfier_ctx{script_ctx};1101// Get the ScriptPubKey for this script, filling spend data if it's Taproot.1102TaprootBuilder builder;1103const CScript script_pubkey{ScriptPubKey(script_ctx, script, builder)};1105// Run malleable satisfaction algorithm.1106std::vector<std::vector<unsigned char>> stack_mal;1107const bool mal_success = node->Satisfy(satisfier_ctx, stack_mal, false) == miniscript::Availability::YES;1109// Run non-malleable satisfaction algorithm.1110std::vector<std::vector<unsigned char>> stack_nonmal;1111const bool nonmal_success = node->Satisfy(satisfier_ctx, stack_nonmal, true) == miniscript::Availability::YES;1113if (nonmal_success) {1114// Non-malleable satisfactions are bounded by the satisfaction size plus:1115// - For P2WSH spends, the witness script1116// - For Tapscript spends, both the witness script and the control block1117const size_t max_stack_size{*node->GetStackSize() + 1 + miniscript::IsTapscript(script_ctx)};1118assert(stack_nonmal.size() <= max_stack_size);1119// If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it.1120assert(mal_success);1121assert(stack_nonmal == stack_mal);1122// Compute witness size (excluding script push, control block, and witness count encoding).1123const uint64_t wit_size{GetSerializeSize(stack_nonmal) - GetSizeOfCompactSize(stack_nonmal.size())};1124assert(wit_size <= *node->GetWitnessSize());1126// Test non-malleable satisfaction.1127witness_nonmal.stack.insert(witness_nonmal.stack.end(), std::make_move_iterator(stack_nonmal.begin()), std::make_move_iterator(stack_nonmal.end()));1128SatisfactionToWitness(script_ctx, witness_nonmal, script, builder);1129ScriptError serror;1130bool res = VerifyScript(DUMMY_SCRIPTSIG, script_pubkey, &witness_nonmal, STANDARD_SCRIPT_VERIFY_FLAGS, CHECKER_CTX, &serror);1131// Non-malleable satisfactions are guaranteed to be valid if ValidSatisfactions().1132if (node->ValidSatisfactions()) assert(res);1133// More detailed: non-malleable satisfactions must be valid, or could fail with ops count error (if CheckOpsLimit failed),1134// or with a stack size error (if CheckStackSize check failed).1135assert(res ||1136(!node->CheckOpsLimit() && serror == ScriptError::SCRIPT_ERR_OP_COUNT) ||1137(!node->CheckStackSize() && serror == ScriptError::SCRIPT_ERR_STACK_SIZE));1138}1140if (mal_success && (!nonmal_success || witness_mal.stack != witness_nonmal.stack)) {1141// Test malleable satisfaction only if it's different from the non-malleable one.1142witness_mal.stack.insert(witness_mal.stack.end(), std::make_move_iterator(stack_mal.begin()), std::make_move_iterator(stack_mal.end()));1143SatisfactionToWitness(script_ctx, witness_mal, script, builder);1144ScriptError serror;1145bool res = VerifyScript(DUMMY_SCRIPTSIG, script_pubkey, &witness_mal, STANDARD_SCRIPT_VERIFY_FLAGS, CHECKER_CTX, &serror);1146// Malleable satisfactions are not guaranteed to be valid under any conditions, but they can only1147// fail due to stack or ops limits.1148assert(res || serror == ScriptError::SCRIPT_ERR_OP_COUNT || serror == ScriptError::SCRIPT_ERR_STACK_SIZE);1149}1151if (node->IsSane()) {1152// For sane nodes, the two algorithms behave identically.1153assert(mal_success == nonmal_success);1154}1156// Verify that if a node is policy-satisfiable, the malleable satisfaction1157// algorithm succeeds. Given that under IsSane() both satisfactions1158// are identical, this implies that for such nodes, the non-malleable1159// satisfaction will also match the expected policy.1160const auto is_key_satisfiable = [script_ctx](const CPubKey& pubkey) -> bool {1161auto sig_ptr{TEST_DATA.GetSig(script_ctx, pubkey)};1162return sig_ptr != nullptr && sig_ptr->second;1163};1164bool satisfiable = node->IsSatisfiable([&](const Node& node) -> bool {1165switch (node.Fragment()) {1166case Fragment::PK_K:1167case Fragment::PK_H:1168return is_key_satisfiable(node.Keys()[0]);1169case Fragment::MULTI:1170case Fragment::MULTI_A: {1171size_t sats = std::ranges::count_if(node.Keys(), [&](const auto& key) {1172return size_t(is_key_satisfiable(key));1173});1174return sats >= node.K();1175}1176case Fragment::OLDER:1177case Fragment::AFTER:1178return node.K() & 1;1179case Fragment::SHA256:1180return TEST_DATA.sha256_preimages.contains(node.Data());1181case Fragment::HASH256:1182return TEST_DATA.hash256_preimages.contains(node.Data());1183case Fragment::RIPEMD160:1184return TEST_DATA.ripemd160_preimages.contains(node.Data());1185case Fragment::HASH160:1186return TEST_DATA.hash160_preimages.contains(node.Data());1187default:1188assert(false);1189}1190return false;1191});1192assert(mal_success == satisfiable);1193}1195} // namespace1197void FuzzInit()1198{1199static ECC_Context ecc_context{};1200TEST_DATA.Init();1201}1203void FuzzInitSmart()1204{1205FuzzInit();1206SMARTINFO.Init();1207}1209/** Fuzz target that runs TestNode on nodes generated using ConsumeNodeStable. */1210FUZZ_TARGET(miniscript_stable, .init = FuzzInit)1211{1212// Run it under both P2WSH and Tapscript contexts.1213for (const auto script_ctx: {MsCtx::P2WSH, MsCtx::TAPSCRIPT}) {1214FuzzedDataProvider provider(buffer.data(), buffer.size());1215TestNode(script_ctx, GenNode(script_ctx, [&](Type needed_type) {1216return ConsumeNodeStable(script_ctx, provider, needed_type);1217}, ""_mst), provider);1218}1219}1221/** Fuzz target that runs TestNode on nodes generated using ConsumeNodeSmart. */1222FUZZ_TARGET(miniscript_smart, .init = FuzzInitSmart)1223{1224/** The set of types we aim to construct nodes for. Together they cover all. */1225static constexpr std::array<Type, 4> BASE_TYPES{"B"_mst, "V"_mst, "K"_mst, "W"_mst};1227FuzzedDataProvider provider(buffer.data(), buffer.size());1228const auto script_ctx{(MsCtx)provider.ConsumeBool()};1229TestNode(script_ctx, GenNode(script_ctx, [&](Type needed_type) {1230return ConsumeNodeSmart(script_ctx, provider, needed_type);1231}, PickValue(provider, BASE_TYPES), true), provider);1232}1234/* Fuzz tests that test parsing from a string, and roundtripping via string. */1235FUZZ_TARGET(miniscript_string, .init = FuzzInit)1236{1237constexpr auto is_too_expensive{[](std::span<const uint8_t> buf) { return HasTooManySubFrag(buf) || HasTooManyWrappers(buf); }};1239if (buffer.empty()) return;1240FuzzedDataProvider provider(buffer.data(), buffer.size());1241auto str = provider.ConsumeBytesAsString(provider.remaining_bytes() - 1);1242if (is_too_expensive(MakeUCharSpan(str))) return;1243const ParserContext parser_ctx{(MsCtx)provider.ConsumeBool()};1244auto parsed = miniscript::FromString(str, parser_ctx);1245if (!parsed) return;1247const auto str2 = parsed->ToString(parser_ctx);1248assert(str2);1249auto parsed2 = miniscript::FromString(*str2, parser_ctx);1250assert(parsed2);
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.