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.
251miniscript::Availability SatHASH256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { return SatHash(hash, preimage, ChallengeType::HASH256); }252miniscript::Availability SatHASH160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { return SatHash(hash, preimage, ChallengeType::HASH160); }253};255/** Mocking signature/timelock checker.256*257* It holds a pointer to a Satisfier object, to determine which timelocks are supposed to be available.258*/259class TestSignatureChecker : public BaseSignatureChecker {260const Satisfier& ctx;262public:263TestSignatureChecker(const Satisfier& in_ctx LIFETIMEBOUND) : ctx(in_ctx) {}265bool CheckECDSASignature(const std::vector<unsigned char>& sig, const std::vector<unsigned char>& pubkey, const CScript& scriptcode, SigVersion sigversion) const override {266CPubKey pk(pubkey);267if (!pk.IsValid()) return false;268// Instead of actually running signature validation, check if the signature matches the precomputed one for this key.269auto it = g_testdata->signatures.find(pk);270if (it == g_testdata->signatures.end()) return false;271return sig == it->second;272}274bool CheckSchnorrSignature(std::span<const unsigned char> sig, std::span<const unsigned char> pubkey, SigVersion,275ScriptExecutionData&, ScriptError*) const override {276XOnlyPubKey pk{pubkey};277auto it = g_testdata->schnorr_signatures.find(pk);278if (it == g_testdata->schnorr_signatures.end()) return false;279return std::ranges::equal(sig, it->second);280}282bool CheckLockTime(const CScriptNum& locktime) const override {283// Delegate to Satisfier.284return ctx.CheckAfter(locktime.GetInt64());285}287bool CheckSequence(const CScriptNum& sequence) const override {288// Delegate to Satisfier.289return ctx.CheckOlder(sequence.GetInt64());290}291};293using Fragment = miniscript::Fragment;294using miniscript::operator""_mst;295using Node = miniscript::Node<CPubKey>;297/** Compute all challenges (pubkeys, hashes, timelocks) that occur in a given Miniscript. */298std::set<Challenge> FindChallenges(const Node& root)299{300std::set<Challenge> chal;302for (std::vector stack{&root}; !stack.empty();) {303const auto* ref{stack.back()};304stack.pop_back();306for (const auto& key : ref->Keys()) {307chal.emplace(ChallengeType::PK, ChallengeNumber(key));308}309switch (ref->Fragment()) {310case Fragment::OLDER: chal.emplace(ChallengeType::OLDER, ref->K()); break;311case Fragment::AFTER: chal.emplace(ChallengeType::AFTER, ref->K()); break;312case Fragment::SHA256: chal.emplace(ChallengeType::SHA256, ChallengeNumber(ref->Data())); break;313case Fragment::RIPEMD160: chal.emplace(ChallengeType::RIPEMD160, ChallengeNumber(ref->Data())); break;314case Fragment::HASH256: chal.emplace(ChallengeType::HASH256, ChallengeNumber(ref->Data())); break;315case Fragment::HASH160: chal.emplace(ChallengeType::HASH160, ChallengeNumber(ref->Data())); break;316default: break;317}318for (const auto& sub : ref->Subs()) {319stack.push_back(&sub);320}321}322return chal;323}325//! The spk for this script under the given context. If it's a Taproot output also record the spend data.326CScript ScriptPubKey(miniscript::MiniscriptContext ctx, const CScript& script, TaprootBuilder& builder)327{328if (!miniscript::IsTapscript(ctx)) return CScript() << OP_0 << WitnessV0ScriptHash(script);330// For Taproot outputs we always use a tree with a single script and a dummy internal key.331builder.Add(0, script, TAPROOT_LEAF_TAPSCRIPT);332builder.Finalize(XOnlyPubKey::NUMS_H);333return GetScriptForDestination(builder.GetOutput());334}336//! Fill the witness with the data additional to the script satisfaction.337void SatisfactionToWitness(miniscript::MiniscriptContext ctx, CScriptWitness& witness, const CScript& script, TaprootBuilder& builder) {338// For P2WSH, it's only the witness script.339witness.stack.emplace_back(script.begin(), script.end());340if (!miniscript::IsTapscript(ctx)) return;341// For Tapscript we also need the control block.342witness.stack.push_back(*builder.GetSpendData().scripts.begin()->second.begin());343}345struct MiniScriptTest : BasicTestingSetup {346/** Run random satisfaction tests. */347void TestSatisfy(const KeyConverter& converter, const Node& node)348{349auto script = node.ToScript(converter);350const auto challenges{FindChallenges(node)}; // Find all challenges in the generated miniscript.351std::vector<Challenge> challist(challenges.begin(), challenges.end());352for (int iter = 0; iter < 3; ++iter) {353std::shuffle(challist.begin(), challist.end(), m_rng);354Satisfier satisfier(converter.MsContext());355TestSignatureChecker checker(satisfier);356bool prev_mal_success = false, prev_nonmal_success = false;357// Go over all challenges involved in this miniscript in random order.358for (int add = -1; add < (int)challist.size(); ++add) {359if (add >= 0) satisfier.supported.insert(challist[add]); // The first iteration does not add anything361// Get the ScriptPubKey for this script, filling spend data if it's Taproot.362TaprootBuilder builder;363const CScript script_pubkey{ScriptPubKey(converter.MsContext(), script, builder)};365// Run malleable satisfaction algorithm.366CScriptWitness witness_mal;367const bool mal_success = node.Satisfy(satisfier, witness_mal.stack, false) == miniscript::Availability::YES;368SatisfactionToWitness(converter.MsContext(), witness_mal, script, builder);370// Run non-malleable satisfaction algorithm.371CScriptWitness witness_nonmal;372const bool nonmal_success = node.Satisfy(satisfier, witness_nonmal.stack, true) == miniscript::Availability::YES;373// Compute witness size (excluding script push, control block, and witness count encoding).374const uint64_t wit_size{GetSerializeSize(witness_nonmal.stack) - GetSizeOfCompactSize(witness_nonmal.stack.size())};375SatisfactionToWitness(converter.MsContext(), witness_nonmal, script, builder);377if (nonmal_success) {378// Non-malleable satisfactions are bounded by the satisfaction size plus:379// - For P2WSH spends, the witness script380// - For Tapscript spends, both the witness script and the control block381const size_t max_stack_size{*node.GetStackSize() + 1 + miniscript::IsTapscript(converter.MsContext())};382BOOST_CHECK(witness_nonmal.stack.size() <= max_stack_size);383// If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it.384BOOST_CHECK(mal_success);385BOOST_CHECK(witness_nonmal.stack == witness_mal.stack);386assert(wit_size <= *node.GetWitnessSize());388// Test non-malleable satisfaction.389ScriptError serror;390bool res = VerifyScript(CScript(), script_pubkey, &witness_nonmal, STANDARD_SCRIPT_VERIFY_FLAGS, checker, &serror);391// Non-malleable satisfactions are guaranteed to be valid if ValidSatisfactions().392if (node.ValidSatisfactions()) BOOST_CHECK(res);393// More detailed: non-malleable satisfactions must be valid, or could fail with ops count error (if CheckOpsLimit failed),394// or with a stack size error (if CheckStackSize check fails).395BOOST_CHECK(res ||396(!node.CheckOpsLimit() && serror == ScriptError::SCRIPT_ERR_OP_COUNT) ||397(!node.CheckStackSize() && serror == ScriptError::SCRIPT_ERR_STACK_SIZE));398}400if (mal_success && (!nonmal_success || witness_mal.stack != witness_nonmal.stack)) {401// Test malleable satisfaction only if it's different from the non-malleable one.402ScriptError serror;403bool res = VerifyScript(CScript(), script_pubkey, &witness_mal, STANDARD_SCRIPT_VERIFY_FLAGS, checker, &serror);404// Malleable satisfactions are not guaranteed to be valid under any conditions, but they can only405// fail due to stack or ops limits.406BOOST_CHECK(res || serror == ScriptError::SCRIPT_ERR_OP_COUNT || serror == ScriptError::SCRIPT_ERR_STACK_SIZE);407}409if (node.IsSane()) {410// For sane nodes, the two algorithms behave identically.411BOOST_CHECK_EQUAL(mal_success, nonmal_success);412}414// Adding more satisfied conditions can never remove our ability to produce a satisfaction.415BOOST_CHECK(mal_success >= prev_mal_success);416// For nonmalleable solutions this is only true if the added condition is PK;417// for other conditions, adding one may make an valid satisfaction become malleable. If the script418// is sane, this cannot happen however.419if (node.IsSane() || add < 0 || challist[add].first == ChallengeType::PK) {420BOOST_CHECK(nonmal_success >= prev_nonmal_success);421}422// Remember results for the next added challenge.423prev_mal_success = mal_success;424prev_nonmal_success = nonmal_success;425}427bool satisfiable = node.IsSatisfiable([](const Node&) { return true; });428// If the miniscript was satisfiable at all, a satisfaction must be found after all conditions are added.429BOOST_CHECK_EQUAL(prev_mal_success, satisfiable);430// If the miniscript is sane and satisfiable, a nonmalleable satisfaction must eventually be found.431if (node.IsSane()) BOOST_CHECK_EQUAL(prev_nonmal_success, satisfiable);432}433}435enum TestMode : int {436//! Invalid under any context437TESTMODE_INVALID = 0,438//! Valid under any context unless overridden439TESTMODE_VALID = 1,440TESTMODE_NONMAL = 2,441TESTMODE_NEEDSIG = 4,442TESTMODE_TIMELOCKMIX = 8,443//! Invalid only under P2WSH context444TESTMODE_P2WSH_INVALID = 16,445//! Invalid only under Tapscript context446TESTMODE_TAPSCRIPT_INVALID = 32,447};449void Test(const std::string& ms, const std::string& hexscript, int mode, const KeyConverter& converter,450int opslimit = -1, int stacklimit = -1, std::optional<uint32_t> max_wit_size = std::nullopt,451std::optional<uint32_t> stack_exec = {})452{453auto node = miniscript::FromString(ms, converter);454const bool is_tapscript{miniscript::IsTapscript(converter.MsContext())};455if (mode == TESTMODE_INVALID || ((mode & TESTMODE_P2WSH_INVALID) && !is_tapscript) || ((mode & TESTMODE_TAPSCRIPT_INVALID) && is_tapscript)) {456BOOST_CHECK_MESSAGE(!node || !node->IsValid(), "Unexpectedly valid: " + ms);457} else {458BOOST_CHECK_MESSAGE(node, "Unparseable: " + ms);459BOOST_CHECK_MESSAGE(node->IsValid(), "Invalid: " + ms);460BOOST_CHECK_MESSAGE(node->IsValidTopLevel(), "Invalid top level: " + ms);461auto computed_script = node->ToScript(converter);462BOOST_CHECK_MESSAGE(node->ScriptSize() == computed_script.size(), "Script size mismatch: " + ms);463if (hexscript != "?") BOOST_CHECK_MESSAGE(HexStr(computed_script) == hexscript, "Script mismatch: " + ms + " (" + HexStr(computed_script) + " vs " + hexscript + ")");464BOOST_CHECK_MESSAGE(node->IsNonMalleable() == !!(mode & TESTMODE_NONMAL), "Malleability mismatch: " + ms);465BOOST_CHECK_MESSAGE(node->NeedsSignature() == !!(mode & TESTMODE_NEEDSIG), "Signature necessity mismatch: " + ms);466BOOST_CHECK_MESSAGE((node->GetType() << "k"_mst) == !(mode & TESTMODE_TIMELOCKMIX), "Timelock mix mismatch: " + ms);467auto inferred_miniscript = miniscript::FromScript(computed_script, converter);468BOOST_CHECK_MESSAGE(inferred_miniscript, "Cannot infer miniscript from script: " + ms);469BOOST_CHECK_MESSAGE(inferred_miniscript->ToScript(converter) == computed_script, "Roundtrip failure: miniscript->script != miniscript->script->miniscript->script: " + ms);470if (opslimit != -1) BOOST_CHECK_MESSAGE((int)*node->GetOps() == opslimit, "Ops limit mismatch: " << ms << " (" << *node->GetOps() << " vs " << opslimit << ")");471if (stacklimit != -1) BOOST_CHECK_MESSAGE((int)*node->GetStackSize() == stacklimit, "Stack limit mismatch: " << ms << " (" << *node->GetStackSize() << " vs " << stacklimit << ")");472if (max_wit_size) BOOST_CHECK_MESSAGE(*node->GetWitnessSize() == *max_wit_size, "Witness size limit mismatch: " << ms << " (" << *node->GetWitnessSize() << " vs " << *max_wit_size << ")");473if (stack_exec) BOOST_CHECK_MESSAGE(*node->GetExecStackSize() == *stack_exec, "Stack execution limit mismatch: " << ms << " (" << *node->GetExecStackSize() << " vs " << *stack_exec << ")");474TestSatisfy(converter, *node);475}476}478void Test(const std::string& ms, const std::string& hexscript, const std::string& hextapscript, int mode,479int opslimit, int stacklimit, std::optional<uint32_t> max_wit_size,480std::optional<uint32_t> max_tap_wit_size,481std::optional<uint32_t> stack_exec)482{483KeyConverter wsh_converter(miniscript::MiniscriptContext::P2WSH);484Test(ms, hexscript, mode, wsh_converter, opslimit, stacklimit, max_wit_size, stack_exec);485KeyConverter tap_converter(miniscript::MiniscriptContext::TAPSCRIPT);486Test(ms, hextapscript == "=" ? hexscript : hextapscript, mode, tap_converter, opslimit, stacklimit, max_tap_wit_size, stack_exec);487}489void Test(const std::string& ms, const std::string& hexscript, const std::string& hextapscript, int mode)490{491Test(ms, hexscript, hextapscript, mode,492/*opslimit=*/-1, /*stacklimit=*/-1,493/*max_wit_size=*/std::nullopt, /*max_tap_wit_size=*/std::nullopt, /*stack_exec=*/std::nullopt);494}495}; // struct MiniScriptTest497} // namespace499BOOST_FIXTURE_TEST_SUITE(miniscript_tests, MiniScriptTest)
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.