The PR branch HEAD was ec72f35 at the time of this review club meeting.
Notes
Scope
This is a 2-part Review Club. Since we haven’t covered Miniscript before, we first take some time to get familiar with the general concepts before diving into the actual PR.
In the first part, we’ll disregard output descriptors and look at Miniscript in general and some of the changes this PR introduces. We’ll focus on the first 6 commits from “miniscript: remove a workaround for a GCC 4.8 bug” to “miniscript: split ValidSatisfactions from IsSane”.
In the second part, we’ll look at the Miniscript output descriptor implementation. We’ll focus on the last 9 commits from “miniscript: tiny doc fixups” to “qa: functional test Miniscript watchonly support”.
Some of the questions refer to changes introduced in the PR’s predecessor #24147 which introduced the bulk of the Miniscript logic into Bitcoin Core, so it may be helpful to review that PR too. It also contains a more detailed overview of the various PRs involved in merging Miniscript into the Bitcoin Core codebase.
Introduction
Miniscript is a language for writing (a subset of) Bitcoin Scripts in a structured way, enabling analysis, composition, generic signing and more. It is not to be confused with the policy language on top of Miniscript which looks similar to Miniscript, but is out of scope for this PR. Andrew Poelstra has a helpful video on “Getting Started with Miniscript”.
Output script descriptors are strings that contain all the information necessary to allow a wallet or other program to track payments made to or spent from a particular script or set of related scripts (i.e. an address or a set of related addresses such as in an HD wallet).
Descriptors combine well with Miniscript in allowing a wallet to handle tracking and signing for a larger variety of scripts. Since Bitcoin Core 23.0 descriptor wallets have become the default wallet type.
Which type of analysis enabled by Miniscript would be helpful for which use case or application?
What would be a valid Miniscript for a spending policy that unconditionally locks the UTXO for 21 blocks, and then requires a 2-of-3 multisig from Alice, Bob, or Carol? (Note: the Miniscript homepage and https://min.sc/ have easy-to-use tooling available to construct Miniscripts)
What does it mean when a node is “sane” or “valid”? Do they mean the same thing?
What does it mean for an expression to be non-malleably satisfiable? After SegWit, why do we still need to worry about malleability?
Why does Compare now use a non-recursive algorithm, whereas previously the Node::operator== operator was recursive? What is the largest size that queue in Compare can ever grow?
How do we keep track of a Node’s type and type properties? Why don’t we just declare them as regular class members? Can we instantiate a Node with multiple type properties at once?
<stickies-v> welcome everyone! This and next week we're looking at #24148 (https://bitcoincore.reviews/24148) which introduces Miniscript support for Output Descriptors. Today we're focusing on general Miniscript concepts, and some of the changes introduced in #24148.
<paul_c> I was at BTC 2022 and attended an Open Source Stage session Gloria was a part of. I learned a lot from that panel and just wanted to reach out to say hi.
<stickies-v> disclaimer: I picked tis PR just because I'm excited about the potential Miniscript brings, but I'm still relatively new to this codebase - so please keep me honest and I welcome all of your input/corrections :-)
<stickies-v> alright not too much code review so good to start with a few general concept questions first then, it's important to understand what miniscript is about
<stickies-v> starting off with the first question to get creatie about use cases: which type of analysis enabled by Miniscript would be helpful for which use case or application?
<darosior> For instance, the analysis of the maximum witness size is helpful for "second layer" protocols to assign fee bumping reserves, since they can then estimate the worst case size of the transaction (if not signed with exotic sighash types).
<theStack> OliverOffing: +1, also thought about that (i think smaller is pretty much always better, in order to save fees, independently of the conrete usecase)
<stickies-v> OliverOffing: yeah absolutely, one of the (hand-crafted) transaction templates used on LN (I believe it's the commitment tx?) was found to be slightly suboptimal thanks to Miniscript analysis
<darosior> OliverOffing: in general, since Miniscript is only a subset of Script some policies tend to be more optimizable "by hand". But then you lose all the guarantees given by Miniscript for just a few witness units. :)
<darosior> But it did happen that the policy compiler found more optimal Script (for instance IIRC in the anchor output proposal for Lightning one of the Scripts was found using the policy compiler)
<stickies-v> personally I think composition is really interesting, where multiple parties (e.g. in an advanced kind of multi-sig) can provide complex subexpressions without everyone having to understand the other party's spending conditions
<darosior> __gotcha: to expand, Script may sometimes have surprising behaviour and a Script that look to do something might actually not behave this way in all cases
<darosior> __gotcha: from the website, "consensus sound: It is not possible to construct a witness that is consensus valid for a Script unless the spending conditions are met. Since standardness rules permit only a subset of consensus-valid satisfactions (by definition), this property also implies standardness soundness. "
<darosior> __gotcha: then to not lock yourself out of your funds you also want completeness "consensus and standardness complete: Assuming the resource limits listed in the previous section are not violated and there is no timelock mixing, for every set of met conditions that are permitted by the semantics, a witness can be constructed that passes Bitcoin's
<stickies-v> what would be a valid Miniscript for a spending policy that unconditionally locks the UTXO for 21 blocks, and then requires a 2-of-3 multisig from Alice, Bob, or Carol? (See https://bitcoin.sipa.be/miniscript or https://min.sc/)
<stickies-v> michaelfolkson: it was (intentionally) a bit of a trick question, but that's *policy* you posted instead of Miniscript. We use both in the discussion here, but just wanted to highlight that there is a difference. Does everyone understand the difference?
<darosior> __gotcha: just to be clear Miniscript doesn't "compile" to Script (maybe the word works but it can lead to confusion), each Miniscript fragment maps to a specific Script
<theStack> michaelfolkson: which compiler did you use? i found it interesting that your 3-of-3 multisig condition was transformed into two and_v operations
<stickies-v> OliverOffing: a Miniscript expression is essentially a tree (see https://miniscript.fun for a visual). Each fragment in the tree is a node
<OliverOffing> i'd guess that sane means that the arguments passed to the fragment match what the fragment type expects (in terms of number of args and types)
<OliverOffing> Found this on StackExchange: "We use the term valid for any correctly typed Miniscript. And we use the term safe for any sane Miniscript, ie one whose satisfaction isn't malleable, which requires a key for any spending path, etc."
<stickies-v> __gotcha: that's a very fair question, the code (especially header files) is always a good place to look for definitions and documentation etc
<theStack> darosior: thanks! obviously min.sc is not using the latest version of rust-miniscript; at least it doesn't transform n-of-n thresholds into and_v operations
<sipa> __gotcha: The rust implementation was written by people who wanted a rust implementation. The C++ implementation was written by people who wanted a C++ implementation.
<michaelfolkson> sipa: Ohh in the cases where they can't be separated? Surely if one Miniscript is superior that is a (minor) flaw of one of the compilers?
<OliverOffing> darosior: I understand most resource limitation points there, but what is this one? "Anything but pk(key) (P2PK), pkh(key) (P2PKH), and multi(k,...) up to n=3 is invalid by standardness (bare)."
<theStack> is there any plan to implement the equivalent of an "inline assembler" expression, e.g. something like "bare_script(OP_FOO OP_BAR...)"? (not that i can think of a good use-case, just a random thought :D)
<stickies-v> I guess to summarize sanity, it needs to be valid, consensus and standardness-compliant (e.g. number of operations and script size), have non-malleable solutions, not mix different timelock units (block/time), and not have duplicate keys
<sipa> There isn't one that does script assembly... could be added, but I doubt that's very useful. Being able to do fancy things through miniscript is much more usable.
<stickies-v> alright time for the next question, we've already spoken about malleability a bit. What does it mean for an expression to be non-malleably satisfiable? After SegWit, why do we still need to worry about malleability?
<theStack> my naive answer to the second questions would be: pre-segwit spending conditions are still valid (and very likely will always be), so we can't just ignore them?
<michaelfolkson> SegWit didn't resolve all forms of malleability. Just signature malleability. If there are different possible witnesses with a complex script malleability is still possible
<michaelfolkson> stickies-v: Why should we care? Hmm if a second layer protocol relied on knowing wtxid? Auditability for how Bitcoin were spent from a complex script?
<stickies-v> oh I missed __gotcha 's answer, yes exactly having a different witness can affect transaction size, and since the absolute fee amount is fixed (that part is not malleable), that would afffect the tx's fee rate - and thus it's ability to get propagated and priority to get mined into a block, which can be problematic
<darosior> What if a transaction spends a Miniscript which contains a hash using another path? It needs to "dissatisfy" this hash. Once this transaction is broadcast, what can a node on the network do if they want to be a pain?
<darosior> And then for instance the first node i'm broadcasting it to can just take my transaction and send a different version to all nodes on the network
<OliverOffing> does "non-malleably satisfiable" perhaps mean that there's no way to use a same witness data to construct a dissatisfaction of one of the fragments?
<stickies-v> to make it specific, I think an example of a policy of which the Miniscript does not have a satisfaction that's guaranteed to be non-malleable is `or(and(older(21), pk(A)), thresh(2, pk(A), pk(B)))`
<darosior> This will increase the bandwidth usage of compact relay for everyone, since the miner will mine a transaction that is not exactly the same as every node has in its mempool
<stickies-v> thank you all for bringing your A game today. Unfortunately we're out of time for this session, but there's more Miniscript joy next week. Same place, same time! Thank you again to darosior and sipa for guiding us all through this.