Today we continue chapter 10 about Script, P2SH, and Miniscript.
A policy language is a way to express your intentions. It’s easier than writing a Miniscript directly, let alone writing Bitcoin Script directly. A compiler then does the hard work.
Our earlier example of a poor man’s multisig was actually found this way. Starting with a policy and(pk(KEY_A),pk(KEY_B))
, the compiler produced and_v(v:pk(KEY_A),pk(KEY_B))
, which is equivalent to the script <KEY_A> OP_CHECKSIGVERIFY <KEY_B> OP_CHECKSIG
. It turns out this actually produces a lower fee transaction than <KEY_A> <KEY_B> 2 OP_CHECKMULTISIG
. This is the kind of optimization a human might overlook, which is what compilers are good for.
Basically, you write a policy language, which is like a higher-level programming language, which the compiler turns into low level op codes. These are instructions like the ones we described above for popping things off the stack and duplicating them. Miniscript also lives at that very low level, even if it’s slightly more readable and a lot safer. It’s the compiler’s job to take a high level language like Policy Language and turn into the most efficient low-level code.
In the case of multisig, you might say, “I just want two out of two signatures. I don’t care how you do that.” The compiler knows there are multiple ways to execute the intention. And then, the question is, which of them will be picked? The answer to that depends on the transaction weight and the fees that might be involved.
However, you can also tell the compiler, “OK, I think most of the time it’s condition A, but only 10 percent of the time it’s condition B.” The compiler would then calculate the fee for condition A, multiply by 9, add the fee for condition B and divide the total by 10 in order to get the average expected fee. It can optimize for typical use cases, worst case scenarios, all these things, and it then spits out a Miniscript which can then be transpiled to Bitcoin Script.^[The technical term for going from Miniscript to Script — or for transforming source code from any language into another similar one — is transpiling, which can basically be done in two directions. So you can go from Miniscript to Script, or from Script to Miniscript, but you can’t trivially go back to a policy language. However, using automated analysis tools, you can often still figure out what policy language was used to produce a given piece of Miniscript.]
With Taproot (see chapter @sec:taproot_basics), rather than splitting different conditions using and / or, they can be split into a Merkle tree of scripts. You don’t have to worry about how to build the Merkle tree, as the compiler takes care of that. In principle, each leaf can also contain and / or statements. Does it make sense to do that? Or is it better to stick to one condition per leaf? Who knows? A future Miniscript compiler can just try all permutations and decide what’s optimal.