MiniTapscript: port Miniscript to Tapscript (descriptors)

Host: josibake  -  PR author: darosior

The PR branch HEAD was 6e3b37b at the time of this review club meeting.


Background and prior work

Miniscript is a language for writing (a subset of) Bitcoin Scripts in a structured way, enabling analysis, composition, generic signing and more. Miniscript support in Bitcoin Core has been an ongoing effort, with watch-only support for Miniscript in P2WSH descriptors added in v24.0.1 and signing support to be added in the upcoming v25. For background:

  • gives an overview of the Miniscript language
  • #24147 provides the rationale and initial backbone needed for Miniscript support in Bitcoin Core
  • #24148 adds watch-only support for Miniscript descriptors (see previous review club #24148)
  • #24149 adds signing support for Miniscript descriptors


While Tapscript shares most operations with legacy Bitcoin Script, there are a few notable differences. As such, TapMiniscript has been a project to support Tapscript in Miniscript. For background:

  • This gist contains the initial design and discussion on supporting Miniscript in Tapscript
  • #134 adds Tapscript support in Miniscript

Putting it all together

Putting it all together, this PR adds support for Tapscript in Miniscript in Bitcoin Core and makes Miniscript available inside tr() descriptors, both with watching and signing support.


  1. Did you review the PR? Concept ACK, approach ACK, tested ACK, or NACK? What was your review approach?

  2. In your own words, what are some of the differences between Segwit v0 Script (Bitcoin Script with the Segwit Soft Fork rules) and Tapscript rules that may be relevant to Miniscript? Why?

  3. What is Tapscript?

  4. This PR adds a multi_a fragment for Tapscript. How is this different than the existing multi fragment? Does it have the same properties as multi? Why?

  5. In Miniscript, we have type modifiers, which guarantee additional properties for an expression. 866284d makes the wrapper β€œd:” have the β€œu” property under Tapscript.
    • What is the β€œd:” wrapper and the β€œu” type modifier?
    • Why is it that we can make d: have the u property here? Why not in non-Tapscript Miniscript?
  6. This PR adds some logic for statically ensuring no spending path exceeds the stack size at execution time:
    • Why does this matter for Tapscript?
    • What’s the approach taken by this PR? What are the pros/cons? (hint: efdd154)
    • Can you think of an alternative approach to ensure no spending path exceeds the stack size?
  7. In e81635c, the scripts are optionally padded during fuzzing. Why?

  8. What is the most significant change to the descriptor logic in this PR (hint: 08db38a). Why is it needed?

Meeting Log

  117:00 <josie> #startmeeting
  217:00 <stickies-v> hi
  317:00 <kevkevin> Hello
  417:00 <josie> hi all, today we are reviewing
  517:00 <brunoerg> hi
  617:00 <abubakar> hi
  717:00 <effexzi> Hi every1
  817:01 <josie> any first timers for the PR review club?
  917:01 <DaveBeer> hi
 1017:01 <turkycat> yep, first timer here
 1117:01 <Alex66> hi, yap first time
 1217:02 <josie> turkycat, Alex66: welcome!
 1317:04 <turkycat> ty
 1417:05 <josie> just as a quick reminder: no need to ask about asking a question. if you have a question, jump in!
 1517:05 <josie> first question: did you get a chance to review the PR? give a y for yes or an n for no
 1617:05 <kevkevin> y
 1717:06 <abubakar> y
 1817:06 <Alex66> n
 1917:06 <Eppie> y
 2017:06 <josie> if you did get a chance to review the PR, what did was your approach? did you ACK/NACK?
 2117:06 <stickies-v> n, mostly looked at the notes/questions
 2217:07 <kevkevin> I just visually read the code and got to understand miniscript.cpp and miniscript.h concept ACK
 2317:07 <LarryRuane> hi
 2417:07 <abubakar> I am running the test currently concept ACK
 2517:07 <josie> (or even if you didn't fully review, what do you think regarding the concept and the approach)
 2617:08 <LarryRuane> yes concept ACK also from me but this is an area I'm not familiar with at all
 2717:09 <stickies-v> taproot enables much more complex scripting, so big concept ack for adding miniscript compatibility
 2817:09 <brunoerg> concept ACK
 2917:10 <turkycat> I'm comfortable with the code changes and understand them- descriptors in general though not so much. some of the concepts of the descriptors are still fuzzy to me after reading the doc on them. hoping to pick up more today
 3017:11 <LarryRuane> I have a very basic question (sorry), but this already mentions Tapscript, so what is this PR for? I'm unclear on what is already done and what more is needed
 3117:11 <josie> cool! for the next question, I think it makes more sense to start with 3: what is tapscript?
 3217:11 <josie> turkycat: descriptors are a really big topic!
 3317:12 <stickies-v> LarryRuane: the website was only updated very recently:
 3417:12 <abubakar> tapscript is a modified version of bitcoin script which removes and adds some opcodes like checksigadd
 3517:12 <LarryRuane> stickies-v: thanks
 3617:13 <stickies-v> bitcoin core currently is not yet able to parse tapscript miniscript, so the website is just describing the spec already
 3717:13 <brunoerg> tapscript is a scripting language used in Bitcoin which can use Schnorr Signatures and other things
 3817:13 <kevkevin> tapscript is a scripting language that utilizes the upgrades from the taproot update
 3917:14 <josie> abubakar: correct!
 4017:14 <LarryRuane> The interactive aspect of that website is really cool and amazing
 4117:15 <abubakar> only v1 scrippubkeys can use tapscript i think
 4217:16 <josie> yep, more specifically, tapscript is built of segwit v0 script
 4317:16 <turkycat> abubakar v1 as in segwit v1 (taproot) as apposed to segwit v0?
 4417:16 <josie> s/built of/built on/
 4517:17 <josie> turkeycat: yep! more importantly, segwit v1 introduced the concept of a leaf version. tapscript is defined as leaf version 0
 4617:18 <abubakar> turkycat: yes segwit v0 uses the bitcoin script
 4717:18 <josie> a few of you have already mentioned some differences, but can you summarize in your own words some differences between Segwit v0 Script and Tapscript?
 4817:18 <turkycat> yep just needed to clarify that there aren't two versions of something else in context
 4917:19 <LarryRuane> ah so there can later be leaf versions 1, 2, ..., all within segwit v1
 5017:19 <josie> LarryRuane: the miniscript website is awesome! I reference it constantly
 5117:20 <turkycat> segwit v0 supports pubkeyhash and script hash operations via witness data. taproot allows for a merkle tree of different script leaves that can be revealed at spending time or satisfied with a composite signature of a single key
 5217:20 <josie> LarryRuane: that's right! in fact, you could create a TapTree where each leaf has it's own leaf version
 5317:21 <brunoerg> turkycat: MAST, right?
 5417:21 <stickies-v> josie: OP_(NOT)IF is now only allowed (consensus) to have 0 or 1 as its argument, whereas in v0 e.g. 4 would also evaluate as true (this is also referred to as MINIMALIF)
 5517:21 <abubakar> multisig is now different in tapscript because of the new checksigadd opcode
 5617:22 <josie> turkycat: close. I'd say it's more correct to say that segwit v1 introduces the merkle tree of scripts (with leaf versions) and tapscript is defined as leaf version 0
 5717:22 <turkycat> brunoerg - I had to google "MAST", but I think so. All I know is that with a single merkle root you can have an unbalanced trie of script leaves
 5817:23 <josie> stickies-v: yep, this is an important one! previously minimalif was only a policy rule, whereas in tapscript it is now a consensus rule
 5917:23 <turkycat> josie ACK. re-read your question and realized I didn't parse correctly on the first go
 6017:23 <turkycat> ty
 6117:24 <josie> abubakar: yep, tapscript adds a new op_code checksigadd. what about the old multisig opcodes from legacy and segwit v0?
 6217:24 <kevkevin> isnt the old multisig op code CHECKMULTISIG?
 6317:25 <abubakar> its removed i think, i might be wrong on this
 6417:25 <josie> kevkevin: yep! CHECKMULTISIG (and CHECKMULTISIGVERIFY)
 6517:25 <stickies-v> kevkevin: yes, OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY were used up until segwit v0 - what's the question?
 6617:26 <abubakar> kevkevin: yes
 6717:26 <stickies-v> oh I see, you were just listing them. sorry
 6817:26 <josie> abubakar: you are correct, CHECKMULTISIG and CHECKMULTISIGVERIFY are removed from tapscript
 6917:28 <josie> what about limits in segwit v0 vs tapscript? any changes here?
 7017:28 <stickies-v> mmm I don't think they're removed? just made to behave like OP_RETURN, pretty much?
 7117:28 <stickies-v> as in - they fail script termination
 7217:28 <stickies-v> *script execution
 7317:29 <abubakar> stickies-v: +1
 7417:29 <abubakar> josie: thanks
 7517:29 <josie> stickies-v: that's a great point, actually! you are correct: they now behave like OP_RETURN
 7617:30 <josie> so instead of saying removed, the more correct terminology would be "disabled"
 7717:31 <brunoerg> Sorry couldn't get it, what do you mean by "disabled"?
 7817:31 <turkycat> extending Script requires the redefining of the numbered NO_OP codes (like NO_OP1), where I could make a WAG that tapscript can be extensible without requiring to always be backwards compatible? by adding a new version I mean
 7917:33 <turkycat> segwit v0 still relies on 'legacy' Script, so now we could theoretically create all sorts of new capabilities with new versions?
 8017:33 <stickies-v> brunoerg: if during script execution one of those opcodes is encountered, script termination fails. if the opcodes are in a branch that's not executed, they're ignored
 8117:33 <brunoerg> ok, I think I got it, disabled means they are still present in the language but they're no longer executable
 8217:34 <brunoerg> stickies-v: cool, thanks
 8317:34 <stickies-v> (i don't know why i keep saying script termination instead of script execution, sorry)
 8417:35 <josie> brunoerg, stickies-v: that's my understanding as well. I'm not sure about this, but perhaps it also means those OP_CODE numbers are still reserved? meaning they cant be redefined to mean something else in the future
 8517:36 <brunoerg> yes, I think they're still reserved to keep backwards compatible
 8617:37 <josie> turkycat: actually, one of the key features of tapscript is that it allows adding new op_codes using OP_SUCCESS, instead of NO_OP
 8717:37 <brunoerg> so older scripts can still be decoded correctly
 8817:37 <abubakar> josie: is it limit to the size of the items in stack during execution of the script
 8917:38 <turkycat> @josie cool, added
 9017:38 <turkycat> tapscript walkthrough to reading list
 9117:39 <josie> abubakar: the stack element size, and the element count limit are the same, but the non-push opcodes limit of 201 per script was removed and the maximum script size limit of 10,000 bytes was removed
 9217:40 <josie> okay, moving on to question 4: the PR adds a `multi_a` fragment for Tapscript. how is this different than the existing `multi` fragment?
 9317:41 <josie> does `multi_a` have the same properties as `multi`? why/why not?
 9417:41 <kevkevin> is the difference multi_a uses checksigadd while multi uses checkmultisig
 9517:41 <kevkevin> they have the same properties tho?
 9617:42 <stickies-v> `multi` always consumes at least one stack element (because of the off-by-one bug, i think?), `multi_a` does not
 9717:43 <kevkevin> ooh ok didn't know that bit of info
 9817:44 <abubakar> kevkevin: +1 multi output has the checkmultisig opcode which was removed, the fragment output script differs
 9917:45 <josie> kevkevin, abubakar: tapscript definitely uses checksigadd, but I suppose we could have reused the same multi fragment and recognized that it was being used in a tapscript context. but this would only be possible if the multi fragment had the same properties under both p2wsh and tapscript
10017:46 <kevkevin> ohh is it because the tapscript inputs are 32 bytes and p2wsh 33 not too sure on this one
10117:47 <josie> stickies-v: multi does have the n property, which means it must always consume at least one stack element. good point about the off-by-one tho, I'm not sure if this is the reason
10217:49 <josie> let me rephrase the question a bit: the `multi` fragment has the `n` type modifier, which means it must always consume at least one stack element. `multi_a` does not have the `n` type modifier, which means the top of the stack can be the empty vector. any ideas as to why?
10317:51 <stickies-v> if we pass `0` as the first argument to `multi_a` it would be a 0-0 multisig, so nothing gets popped off the stack?
10417:51 <abubakar> because of the 0 prefix
10517:53 <josie> stickies-v: instead of a 0-0, think about how CHECKSIGADD would be used for a 2-3 multisig. how many parameters does CSA expect?
10617:53 <turkycat> maybe because it still requires n items to satisfy the NUMEQUAL at the end, but if we're using, say 2-of-3 multisig then we only need 2 valid signatures? idk feels like a shaky guess but there doesn't appear to be a 'k' value on `multi_a`
10717:54 <turkycat> oops, yes there is- at the end before NUMEQUAL
10817:54 <josie> turkycat: bingo! CSA expects N sigs, but only k are needed for the NUMEQUAL. so n - k of the sigs will be the empty vector
10917:55 <turkycat> score 1 for the WAGs
11017:56 <josie> CHECKMULTISIG had you pass both k and n, so it would be expecting exactly k sigs, non of which were empty
11117:57 <josie> we are pretty close to time, so instead of going into the next Q (which is a pretty big one), are there any questions about stuff we've talked about so far?
11217:57 <josie> also, any interest in finishing the remaining questions in a follow-up review club? there's some pretty good stuff in the remaining questions :)
11317:58 <LarryRuane> I would say yes to follow-up, and I'll try to be better prepared!
11417:58 <kevkevin> yup I'd say yes to a follow up aswell
11517:59 <josie> cool, follow-up it is! probably should have started with less questions because this is a really big topic! what we are seeing in this PR is the tip of the iceberg
11617:59 <brunoerg> yea, a follow-up would be great
11717:59 <abubakar> +1 would like a part 2 :)
11818:00 <turkycat> yea, I'll be able to read the new items on my list before then
11918:00 <stickies-v> josie: contrary to OP_CHECKMULTISIG(VERIFY), CSA always operates on a single signature
12018:00 <josie> stickies-v: yep! this is a big improvement over CMS(V)'s brute force approach
12118:01 <stickies-v> I still think that the only way for `multi_a` to not pop anything off the stack is for it to be `multi_a(0)`?
12218:01 <josie> okay, thanks everyone for attending and keep your eyes out for a follow-up!
12318:01 <stickies-v> (i.e. k is 0 as per the first argument and n is 0 because we don't pass any further keys)
12418:01 <josie> #endmeeting