MiniTapscript: port Miniscript to Tapscript (Part 2) (descriptors)

Host: josibake  -  PR author: darosior

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


See notes from the first part of this review club.


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

  2. 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?
  3. 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?
  4. In e81635c, the scripts are optionally padded during fuzzing. Why?

  5. 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 <josie> hi!
  317:02 <kevkevin> hi
  417:02 <josie> planning to review <a href='' target='blank'></a> , which is a follow up to last weeks PR review club:
  517:03 <josie> although, it's awfully quiet in here...
  617:03 <abubakr> hi
  717:03 <glozow> hi
  817:05 <brunoerg> hi
  917:05 <josie> i recognize most of the names from last time, but I'll ask any way: did everyone here get a chance to attend part 1?
 1017:05 <josie> and if so, have you had a chance to review the questions for part 2?
 1117:06 <abubakar> yes
 1217:06 <kevkevin> yup was in part 1 didnt get too much of a chance to look at part 2's questions
 1317:08 <LarryRuane> Hi
 1417:09 <josie> cool, let's start with question 2: In Miniscript, we have type modifiers, which guarantee additional properties for an expression. commit 866284d makes the wrapper β€œd:” have the β€œu” property under Tapscript
 1517:10 <josie> first off, what are the "d:" wrapper and "u" fragment?
 1617:11 <abubakar> from miniscript website "d" is a dissatisfiable: does not include a signature or a hash preimage cannot rely on timelocks for being satisfied
 1717:12 <stickies-v> hi
 1817:12 <abubakar> "u" when satisfied the expression will have 1 on the stack
 1917:13 <josie> also - I typed that incorrectly: that should be "what is the 'u' type modifier"
 2017:13 <stickies-v> abubakar: perhaps confusingly, there's a difference between `d` the property and `d:` the wrapper
 2117:13 <stickies-v> as in - they're entirely unrelated
 2217:14 <abubakar> stickies-v: ohthanks
 2317:15 <josie> abubakar: I made the same mistake when writing the questions! it is confusingly named
 2417:15 <stickies-v> properties/type modifiers describe attributes of fragments (expressions), e.g. the `z` property says that a fragment with that property always consumes exactly 0 stack elements
 2517:15 <stickies-v> wrappers however take a fragment and add some script before/after it
 2617:16 <josie> stickies-v: that's a great explanation on the difference between wrappers and type modifiers, thanks!
 2717:17 <stickies-v> the `d:X` wrapper takes fragment X, and adds `DUP IF` before and `ENDIF` after the fragment, so you end up with `DUP IF [X] ENDIF`
 2817:19 <abubakar> stickies-v: thanks
 2917:19 <josie> what about the type modifier, "u"?
 3017:20 <stickies-v> fragments with type modifier `u` always put `1` on the stack when satisfied
 3117:20 <abubakar> i think its the one I reffered to earlier
 3217:20 <stickies-v> ah yes exactly you did!
 3317:21 <josie> abubakar: sorry, i missed this, but yes! when satisfied, expressions with the u property leave exactly 1 on the stack
 3417:22 <josie> so leading into the second part: why is okay for us to say the d:X fragment has the u property under tapscript?
 3517:22 <abubakar> It is called unit because of the 1 it leaves right?
 3617:23 <josie> abubakar: that's how I understood it: unit as in exactly 1
 3717:25 <abubakar> josie: okay
 3817:26 <stickies-v> before taproot, OP_IF would evaluate to true for any value > 0, whereas in taproot we require the argument to be either 0/1. Which means that in case of true, the argument is definitely 1. Because the `d:` wrapper duplicates the argument (OP_DUP), we can state that in case of satisfaction this fragment will put 1 on the stack
 3917:27 <josie> stickies-v: "OP_IF would evaluate true for any value > 0" .. was this true for _any_ transaction?
 4017:29 <stickies-v> hmmmm, i thought so but it sounds like i'm missing something?
 4117:30 <josie> perhaps a better way to phrase my question: was there anything pre-tapscript that would have prevented "regular" users from constructing transactions that used this OP_IF behavior?
 4217:31 <instagibbs> MINIMALIF was a standardness check for p2wsh iirc
 4317:32 <josie> instagibbs: bingo! the main problem here is miners are not bound by standardness, only validity
 4417:33 <josie> I don't have a link to it, but darosier has shown some nasty examples where miners could exploit this to steal funds in scripts that use thresholds
 4517:34 <josie> so if people remember from last week, MINIMALIF was added as a consensus rule in tapscript, which means it is now safe to say that d:X fragments can have the u property
 4617:34 <josie> moving on to question 3: "This PR adds some logic for statically ensuring no spending path exceeds the stack size at execution time"
 4717:35 <josie> why does this matter for tapscript?
 4817:36 <kevkevin> didn't CHECKMULTISIG have a limit of 20, and CHECKSIGADD removed that limit so now we're bound by the stack size limit, not sure if I'm remembering correctly
 4917:37 <instagibbs> miniscript does more than 20 signatures fine I think. BIP342 introduces "It is extended to also apply to the size of initial stack" which means it can't exceed limits ever and succeed. Not sure that answers it
 5017:37 <instagibbs> see
 5117:37 <abubakar> because someone might use a script that add elements that are larger than stack size, standarness were lifted
 5217:39 <stickies-v> miniscript also checks if a script is sane, which (amongst others) also means that it is consensus valid and standard - this helps a lot with analysis of scripts. for example, you may not care about the entire script, just that you have a certain spending path to which you control the keys. but if it then turns out that that spending path is actually not spendable (because of stack size limit), that's a problem
 5317:39 <josie> kevkevin: CHECKMULTISIG did have a limit, but I think its more correct to say CHECKMULTISIG was disabled in tapscript and CHECKSIGADD was added, and CHECKSIGADD doesn't have a limit (aside from the stack size limit)
 5417:41 <kevkevin> josie: ok ya that makes more sense, thanks
 5517:41 <josie> stickies-v: great point re: miniscript. I think tapscript's decision to remove some of the "arbitrary" limits from before makes being able to reason about sanity much more straightforward
 5617:43 <josie> moving on the the second part of the question: "What’s the approach taken by this PR? What are the pros/cons?"
 5717:44 <josie> by approach, we are referring to how this PR ensures no spending path exceeds the stack size limit at execution time
 5817:50 <josie> for this one, the commit message is pretty helpful
 5917:53 <josie> haha the silence is pretty deafening on this one (which admittedly is a pretty in the weeds question), so lets finish with the last question about descriptors
 6017:53 <josie> What is the most significant change to the descriptor logic in this PR?
 6117:54 <stickies-v> sorry josie I didn't review this far ahead so just looking at the commit now - if you wouldn't mind summarizing your answer i'd appreciate that?
 6217:54 <stickies-v> on the approach and pros/cons
 6317:58 <josie> sure! basically, it tracks how the stack will be affected after the script executes, and also how many items will be pushed on the stack during execution. the second part is the part I don't fully understand, but it also checks the maximum witness size for a fragment and takes the lesser. regarding pros and cons, it mentions this being a conservative approach because the max
 6417:58 <josie> witness might not always happen along with the max stack size
 6517:59 <stickies-v> i think we also distinguish between satisfactions and dissatisfactions, right?
 6617:59 <josie> as a con, you could say since this is a conservative approximation, which might not let you do a script that you would otherwise be able to do with more precise accounting
 6718:00 <josie> stickies-v: I believe so, but this where my knowledge of script execution gets fuzzy
 6818:01 <josie> we'll stop here. thanks everyone for attending part 2! this is a really dense topic so I appreciate everyone muscling through
 6918:01 <josie> #endmeeting
 7020:58 <sipa> @josie kevkevin Arguably, CHECKMULTISIG has no more or no less a limit (20) than CHECKSIGADD has a limit (1).
 7120:59 <sipa> The point is that multi maps to just one CHECKMULTISIG, and thus inherits the 20 limit.
 7220:59 <sipa> While multi_a maps to many CHECKSIGADD opcodes, so it is not bound to its 1 limit.
 7321:00 <sipa> We could arguably define a multi() fragment that intelligently maps to multiple CHECKMULTISIG opcodes, and that wouldn't be bound by the 20 limit either, but that's just hiding a lot of complexity that can equally well be captured at a higher level (forcing the miniscript to combine multiple multi() fragments explicitly).