The full revision history is available in a separate branch (https://github.com/rustyrussell/bips/tree/guilt/varops): this is a clean one to submit for merge.
BIP440: Varops Budget for Script Runtime Constraint, BIP441: Restoration of disabled Script (tapleaf 0xc2) #2118
pull rustyrussell wants to merge 3 commits into bitcoin:master from rustyrussell:varops-bips changing 4 files +1083 −0-
rustyrussell commented at 7:03 AM on March 8, 2026: contributor
- jonatack added the label New BIP on Mar 8, 2026
-
jmoik commented at 12:20 PM on March 9, 2026: none
to fix the CI we should add
Toom = "Toom"to the[default.extend-words]section in.typos.toml. -
in bip-unknown-script-restoration.mediawiki:55 in c40bbe2d5b
50 | + 51 | +There needs to be some limit on memory usage, to avoid a memory-based denial of service. 52 | + 53 | +Putting the entire transaction on the stack is a foreseeable use case, hence using the block size (4MB) as a limit makes sense. However, allowing 4MB stack elements is a significant increase in memory requirements, so a total limit of twice that many bytes (8MB) is introduced. Many stack operations require making at least one copy, so this allows such use. 54 | + 55 | +Putting all outputs or inputs from the transaction on the stack as separate elements requires as much stack capacity as there are inputs or outputs. The smallest possible input is 34 bytes (allowing almost 26,411 inputs), and the smallest possible output is 9 bytes (allowing almost 111,111 outputs). However, empty outputs are rare and not economically interesting. Thus we consider smallest non-OP_RETURN standard output script, which is P2WPKH at 22 bytes, giving a minimum output size of 31 bytes, allowing 32,258 outputs in a maximally-sized transaction.
murchandamus commented at 11:58 PM on March 13, 2026:How did you get to 34 bytes for the smallest possible input? As far as I’m aware, the smallest possible input is 41 bytes. (32+4 for the outpoint, 4 for sequence, 1 for input script length).
rustyrussell commented at 1:33 AM on March 23, 2026:Good catch, fixed!
in bip-unknown-script-restoration.mediawiki:47 in c40bbe2d5b
42 | + 43 | +Validation of a script fails if: 44 | +- It exceeds the remaining varops budget for the transaction. 45 | +- Any stack element exceeds 4,000,000 bytes. 46 | +- The total size of all stack (and altstack) elements exceeds 8,000,000 bytes. 47 | +- The number of stack elements (including altstack elements) exceeds 32768.
murchandamus commented at 11:59 PM on March 13, 2026:consistency nit
- The number of stack elements (including altstack elements) exceeds 32,768.
rustyrussell commented at 1:34 AM on March 23, 2026:Fair. There's a 1000 to fix too.
in bip-unknown-script-restoration.mediawiki:81 in c40bbe2d5b
76 | +2. OP_CHECKSEQUENCEVERIFY 77 | +3. OP_VERIFY 78 | +4. OP_PICK 79 | +5. OP_ROLL 80 | +6. OP_IFDUP 81 | +7. OP_CHECKSIGADD
murchandamus commented at 12:08 AM on March 14, 2026:These are currently all rendered on a single line, but I assume you wanted to produce a numbered list:
# OP_CHECKLOCKTIMEVERIFY # OP_CHECKSEQUENCEVERIFY # OP_VERIFY # OP_PICK # OP_ROLL # OP_IFDUP # OP_CHECKSIGADD
rustyrussell commented at 1:38 AM on March 23, 2026:Thanks! Turns out I used Markdown formatting in a few places. I toyed with the idea of converting to md, but I think it would just confuse existing reviewers.
in bip-unknown-script-restoration.mediawiki:87 in c40bbe2d5b
82 | + 83 | +These opcodes are redefined in v2 Tapscript to write numbers to the stack as minimal-length little-endian values (instead of CScriptNum): 84 | + 85 | +1. OP_CHECKSIGADD 86 | +2. OP_DEPTH 87 | +3. OP_SIZE
murchandamus commented at 12:09 AM on March 14, 2026:Rendered as a single line. See above.
in bip-0441.mediawiki:201 in c40bbe2d5b outdated
196 | +|OTHER + ZEROING 197 | +| 198 | +# Pop B off the stack. 199 | +# Pop A off the stack. 200 | +# If B is longer than A, swap B and A. 201 | +# For each byte in A (the longer operand): bitwise AND it with the equivalent byte in B (or 0 if past end of B)
murchandamus commented at 12:18 AM on March 14, 2026:I was surprised that you align different lengths in the front instead of the back. I would have expected padding in the front instead of the back for bitwise AND.
jmoik commented at 10:20 AM on March 14, 2026:For bitwise AND on little-endian encoded values you pad the shorter one in the back, if you would pad it in the front you would change its value. x & 0 = 0 zeroes out the high bytes which is what you would expect when ANDing with a narrower value.
murchandamus commented at 3:50 PM on March 14, 2026:From what I understand, the endianness is relevant to how the data is serialized on chain. I don’t see why endianness would be or should be relevant to the stack operations and find this approach to describing the opcodes needlessly confusing. The opcodes operate on numbers, not on serialized data, so describing the operations abstractly would seem more useful to me.
rustyrussell commented at 1:10 AM on March 23, 2026:You are confused. Endianness absolutely applies to stack values. It's just that with so many opcodes disabled, it's hard to tell. Once you can alter at a bit level, it becomes salient.
When considering the costs of operations, the serialized data is the key, not (usually!) the number. Adding 1 to 0 could be cheap, or could be expensive (if either number is serialized with 2MB of 0 at the end).
rustyrussell commented at 1:42 AM on March 23, 2026:I added a comment in the rationale:
All opcodes are described in exact (painstaking) byte-by-byte operations, so that their varops budget can be easily derived. Note that this level of detail is unnecessary to users of script, only being of interest to implementers.
murchandamus commented at 10:04 PM on March 23, 2026:I see, I expected from title and abstract that this document would be directed at users of script, and that the second document would cover budgets and how costs were selected directed at the implementers.
in bip-unknown-script-restoration.mediawiki:214 in c40bbe2d5b
209 | +| 210 | +# Pop B off the stack. 211 | +# Pop A off the stack. 212 | +# If B is longer than A, swap B and A. 213 | +# For each byte in B (the shorter operand): bitwise OR it with the equivalent byte in A. 214 | +# Push A onto the stack.
murchandamus commented at 12:28 AM on March 14, 2026:Maybe this is obvious to other readers, but I assume that you mean that the result of the bitwise OR of A and B plus the tail of A are pushed to the stack? If so, this could be expressed more clearly.
rustyrussell commented at 1:14 AM on March 23, 2026:Line 213 should say explicitly that it's altering A.
rustyrussell commented at 1:46 AM on March 23, 2026:Here's the diff:
@@ -210,7 +212,7 @@ OP_LEFT only needs to read its OFFSET operand (truncation is free), whereas OP_R # Pop B off the stack. # Pop A off the stack. # If B is longer than A, swap B and A. -# For each byte in B (the shorter operand): bitwise OR it with the equivalent byte in A. +# For each byte in B (the shorter operand): bitwise OR it into the equivalent byte in A (altering A). # Push A onto the stack. |- |OP_XOR @@ -222,13 +224,13 @@ OP_LEFT only needs to read its OFFSET operand (truncation is free), whereas OP_R # Pop B off the stack. # Pop A off the stack. # If B is longer than A, swap B and A. -# For each byte in B (the shorter operand): exclusive OR it with the equivalent byte in A. +# For each byte in B (the shorter operand): exclusive OR it into the equivalent byte in A (altering A). # Push A onto the stack. |} =====Rationale===== -OP_AND, OP_OR and OP_XOR are assumed to fold the results into the longer of the two operands. This is an OTHER operation (i.e. cost is 2 per byte), but OP_AND needs to do this until one operand is exhausted, and then zero the rest (ZEROING, cost 1 per byte). OP_OR and OP_XOR can stop as soon as the shorter operand is exhausted. +OP_AND, OP_OR and OP_XOR are assumed to fold the results into the longer of the two operands. This is an OTHER operation (i.e. cost is 2 per byte), but OP_AND needs to do this until one operand is exhausted, and then zero the rest (ZEROING, cost 1 per byte). OP_OR and OP_XOR can stop processing the operands as soon as the shorter operand is exhausted. ====Bitshift Opcodes====in bip-unknown-script-restoration.mediawiki:226 in c40bbe2d5b
221 | +| 222 | +# Pop B off the stack. 223 | +# Pop A off the stack. 224 | +# If B is longer than A, swap B and A. 225 | +# For each byte in B (the shorter operand): exclusive OR it with the equivalent byte in A. 226 | +# Push A onto the stack.
murchandamus commented at 12:29 AM on March 14, 2026:I assume you mean that the result of the XOR concatenated with the remaining bytes of A is pushed to the stack, but if so (or not so), it’s not clear.
rustyrussell commented at 1:14 AM on March 23, 2026:Yes, same problem as above.
in bip-unknown-script-restoration.mediawiki:231 in c40bbe2d5b
226 | +# Push A onto the stack. 227 | +|} 228 | + 229 | +=====Rationale===== 230 | + 231 | +OP_AND, OP_OR and OP_XOR are assumed to fold the results into the longer of the two operands. This is an OTHER operation (i.e. cost is 2 per byte), but OP_AND needs to do this until one operand is exhausted, and then zero the rest (ZEROING, cost 1 per byte). OP_OR and OP_XOR can stop as soon as the shorter operand is exhausted.
murchandamus commented at 12:29 AM on March 14, 2026:…can stop because the remaining bits of the longer operand will not be changed by the operation? But are they preserved or not?
rustyrussell commented at 1:15 AM on March 23, 2026:They are. They pop, fold, push. It's the folding step that can terminate early.
in bip-0441.mediawiki:254 in c40bbe2d5b outdated
249 | +|LENGTHCONV + ZEROING + COPYING. If BITS % 8 != 0, + OTHER. 250 | +| 251 | +# Pop BITS off the stack. 252 | +# Pop A off the stack. 253 | +# If A shifted by value(BITS) would exceed the individual stack limit, fail. 254 | +# If value(BITS) % 8 == 0: simply prepend value(BITS) / 8 zeroes to A.
murchandamus commented at 12:40 AM on March 14, 2026:Uh. “prepend”? Isn’t an upshift usually bit shifting to the left in binary representation? Is this description based on endianness? I would have expected that endianness is just relevant to how numbers are read and written to the stack, but when describing the bit operations we would still operate on them in the binary representation. I.e. wouldn’t an upshift append zeroes on the right?
If I’m not stumbling completely in the dark here, could you please clarify whether you are describing the operations based on the little endian numbers or in binary representation?
jmoik commented at 10:46 AM on March 14, 2026:Yes these descriptions are definitely based on endianness, I think it would be confusing to use the "wrong" endianness in the descriptions here, so we prepend to upshift because the most significant byte is on the right.
But I agree that the current state is also confusing and it should clarify that this is not describing the more common binary representation with MSB on the left, but the layout of the actual byte vectors.
murchandamus commented at 3:59 PM on March 14, 2026:Thanks, glad to hear that my issue was heard, but I think the underlying issue here is that this description mixes serialization format and stack operations. Just because the data is encoded little-endian in the serialization format doesn’t mean that implementers would handle the data that way after reading it. Describing the operations abstractly on basis of them just being numbers (or on binary for bitwise operations) seems much preferable, as long as the explanation makes clear how the specification is meant to be read.
rustyrussell commented at 1:21 AM on March 23, 2026:No, that would completely defeat the point. We need to specify it to this level for our model of costs. There are no invisible steps.
If an implementation reads it as a number and converts it to its preferred endian, they have to do a lot of work to determine what the additional costs of that step are, because it's not costed here. Perhaps they can do an analysis and show that they do not meaningfully exceed the budget doing so.
It's absolutely possible to have a higher-level description, and move the exact description of each opcode to an appendix. But mainly that's obvious?
murchandamus commented at 8:14 PM on March 23, 2026:My misunderstanding was that you are not describing the opcodes in this document for users of script, but that you’re defining them to explain the cost model. This was not obvious to me from title and abstract.
in bip-0441.mediawiki:298 in c40bbe2d5b outdated
293 | +|length(A) * 7 294 | +|OTHER + COPYING 295 | +| 296 | +# Pop A off the stack. 297 | +# Shift each byte in A 1 bit to the left (increasing values, equivalent to C's << operator), tracking the last non-zero value. 298 | +# If the final byte overflows, append a single 1 byte.
murchandamus commented at 12:49 AM on March 14, 2026:I’m still confused by these descriptions. Why are we even talking about bytes at all?
I think I understand what you mean, but the way it’s described seems confusing to me.
in bip-0441.mediawiki:323 in c40bbe2d5b outdated
318 | +| 319 | +# Pop B off the stack. 320 | +# Pop A off the stack. 321 | +# Calculate the varops cost of the operation: if it exceeds the remaining budget, fail. 322 | +# Allocate an all-zero vector R of length equal to length(A) + length(B). 323 | +# For each word in A, multiply it by B and add it into the vector R, offset by the word offset in A.
murchandamus commented at 1:01 AM on March 14, 2026:It seems to me that this description is mixing stack operation descriptions with implementation details. I haven’t read the second document yet, but I thought the other one was dealing with the Varops costs?
I would have e.g., expected the definition of
OP_MULto be something along the lines of:| Word | Opcode | Hex | Input | Output | Description | | OP_MUL | 149 | 0x97 | [a, b] | [a×b] | Pops two elements off the stack, multiplies them, pushes result to the stack |
I’m not sure why the opcode descriptions would need to mention bytes at all—even if the implementation of the stack operation would then obviously be much smarter about how the bytes are modified to achieve the result efficiently.
rustyrussell commented at 1:24 AM on March 23, 2026:I think you're missing the varops cost entirely?
murchandamus commented at 8:44 PM on March 23, 2026:My first impression from title “Restoration of disabled script functionality” and (previous) abstract was that this document would be about describing the restored opcodes behavior. I expected that the second document would be about the budget and the costs?
It was not clear to me that this document’s main purpose was to explain the assigned costs for the reintroduced opcodes rather than the functionality of the opcodes themselves. On second read and with your recent commentary, it seems to me now that explaining the behavior of the opcodes themselves is at most a secondary goal of the document or assumed to be known to the readers already.
in bip-0441.mediawiki:338 in c40bbe2d5b outdated
333 | +# Pop B off the stack. 334 | +# Pop A off the stack. 335 | +# Calculate the varops cost of the operation: if it exceeds the remaining budget, fail. 336 | +# If B is empty or all zeroes, fail. 337 | +# Perform division as per Knuth's The Art of Computer Programming v2 page 272, Algorithm D "Division of non-negative integers". 338 | +# Trim trailing zeroes off the quotient.
murchandamus commented at 1:08 AM on March 14, 2026:I’m again confused by trailing zeroes vs leading zeroes. Please clarify how we should be thinking about numbers.
rustyrussell commented at 1:25 AM on March 23, 2026:Because MUL and DIV normalize the results, you need to do that step. This has a cost associated with it, so must be described.
rustyrussell commented at 1:50 AM on March 23, 2026:You didn't get to it (perhaps we should hoist this higher?) but it is spelled out in it its own section:
===Normalization of Results===
Note that only arithmetic operations (those which treat operands as numbers) normalize their results: bit operations do not. Thus operations such as "0 OP_ADD" and "2 OP_MUL" will never result in a top stack entry with a trailing zero byte, but "0 OP_OR" and "1 OP_UPSHIFT" may.
To be clear, the following operations are arithmetic and will normalize their results:
- OP_1ADD
- OP_1SUB
- OP_2MUL
- OP_2DIV
- OP_ADD
- OP_SUB
- OP_MUL
- OP_DIV
- OP_MOD
- OP_MIN
- OP_MAX
murchandamus commented at 1:15 AM on March 14, 2026: memberI only made it to the Multiply and Divide Opcodes in this first read. I suspect that I’m missing something about how you are describing the opcodes. Please see my comments for details.
murchandamus added the label PR Author action required on Mar 14, 2026rustyrussell force-pushed on Mar 23, 2026rustyrussell force-pushed on Mar 23, 2026rustyrussell force-pushed on Mar 23, 2026rustyrussell commented at 4:22 AM on March 23, 2026: contributor@murchandamus thankyou for your detailed review. Here's the comment on the fixup commit which I hope addresses it:
Great feedback from Murch: 1. Lots of markdown instead of mediawiki formatting fixed. 2. Input count max fixed (explanatory only, but still a good fix). 3. Justification for detailed by-byte nature of descriptions added to global Rationale. 4. Note on truncation moved earlier, and expanded. 5. OP_OR/OP_XOR clarified to make it clear that A is altered "in-place". 6. Clarification on early termination (and thus cost) of OR and XOR. 7. nee is spelled "née" (Julian suggested replacing it, but let's try to keep some culture alive!). 8. Murch is now thanked in the thanks section. In particular, Murch's confusion was enlightening. I had not appreciated that the reduction in script capability (removing all bitops and limiting operand size) made endian of stack objects very much an *implementation detail*, thus these descriptions are battling with people's head-canon of how Bitcoin's Stack Is Just Numbers. Mere words can only do so much to address this issue, but I've tried, and at least I'm now aware of this.in bip-unknown-script-restoration.mediawiki:71 in 7d29c1909c outdated
66 | +* OP_NEGATE = OP_SUCCESS143 67 | +* OP_ABS = OP_SUCCESS144<ref>Anthony Towns suggested this could become an opcode which normalized the value on the top of the stack by truncating any trailing zeroes.</ref> 68 | + 69 | +====Rationale==== 70 | + 71 | +Negative numbers are not natively supported in v2 Tapscript. Arbitrary precision makes them difficult to manipulate and negative values are not used meaningfully in bitcoin transactions.
murchandamus commented at 7:24 PM on March 23, 2026:We have so many concepts that are “v2”, perhaps consider giving your script variant a more distinct name.
rustyrussell commented at 12:29 AM on March 24, 2026:Good point! It's only marginally better than "New Tapscript" :)
Since Julian chose Tapleaf 0xC2, we can use "0xC2 Tapscript" explicitly.
in bip-unknown-script-restoration.mediawiki:77 in 7d29c1909c outdated
72 | + 73 | +===Arbitrary-length Values, Endianness, and Normalization of Results=== 74 | + 75 | +The restoration of bit operations means that the little-endianness of stack values is once more exposed to the Script author, if they mix them with arithmetic operations. The restoration of arbitrary-length values additionally exposes the endianness to the implementation authors (who cannot simply load stack entries into registers), and requires explicit consideration when considering varops costs of operations.<ref>For example, removing trailing bytes from a stack element is almost free, whereas removing bytes from the front involves copying all remaining bytes.</ref> 76 | + 77 | +Note that only arithmetic operations (those which treat operands as numbers) normalize their results: bit operations do not. Thus operations such as "0 OP_ADD" and "2 OP_MUL" will never result in a top stack entry with a trailing zero byte, but "0 OP_OR" and "1 OP_UPSHIFT" may.<ref>The original Bitcoin implementation had a similar operational split, but OP_LSHIFT and OP_RSHIFT did normalize, which was almost a requirement given that they also preserved the sign of the shifted operand</ref>
murchandamus commented at 7:28 PM on March 23, 2026:Was the cost of removing trailing bytes the reason for not normalizing results of bit operations? Otherwise, it might be interesting to explain this design decision in the Rationale.
rustyrussell commented at 12:47 AM on March 24, 2026:This made me think! Actually, the question is more "why do arithmetic operators do it?", and that's mainly tradition. It saves a bit of budget with chained operations but nobody should care. It allows canonical comparison, i.e. "" is 0, but that's also a bit obscure.
Here's what I ended up with:
<ref>Such non-arithmetic operations can used to operate on values such as preimages or (with introspection) parts of transactions, where truncation of zeros would be unexpected. One could argue that even arithmetic operators should not normalize, but that would be a gratuitous and surprising change. Note that "0 OP_ADD" can always be used to cheaply normalize the top stack element.</ref>
in bip-unknown-script-restoration.mediawiki:4 in 7d29c1909c outdated
0 | @@ -0,0 +1,595 @@ 1 | +<pre> 2 | + BIP: ? 3 | + Layer: Consensus (soft fork) 4 | + Title: Restoration of disabled script functionality (Tapscript v2)
murchandamus commented at 7:30 PM on March 23, 2026:Nit: title is over 50 characters.
in bip-unknown-script-restoration.mediawiki:119 in 7d29c1909c outdated
114 | + 115 | + ## If the execution results in anything but exactly one element on the stack which evaluates to true with <code>CastToBool()</code>, fail. 116 | + 117 | +Now: 118 | + 119 | + ## If the execution results in anything but exactly one element on the stack which contains one or more non-zero bytes, fail.
murchandamus commented at 7:31 PM on March 23, 2026:Formatting nit: this cuts off text in the default view, perhaps use different formatting here.
<img width="1046" height="222" alt="Image" src="https://github.com/user-attachments/assets/bd688e5b-44d5-4948-ad49-1940652f0912" />
in bip-unknown-script-restoration.mediawiki:182 in 7d29c1909c outdated
177 | +|length(OFFSET) * 2 + value of OFFSET * 3 178 | +|LENGTHCONV + COPYING 179 | +| 180 | +# Pop OFFSET off the stack. 181 | +# Pop A off the stack. 182 | +# Copy value(OFFSET) bytes from offset length(A) - value(OFFSET) to offset 0, if value(OFFSET) is less than length(A).
murchandamus commented at 7:36 PM on March 23, 2026:After the tail is copied to the front, don’t you still need to truncate?
# Copy value(OFFSET) bytes from offset length(A) - value(OFFSET) to offset 0, if value(OFFSET) is less than length(A). # Truncate A to length value(OFFSET)What happens if A is shorter than OFFSET?
rustyrussell commented at 12:53 AM on March 24, 2026:Clarified:
- If value(OFFSET) is less than length(A), copy value(OFFSET) bytes from offset length(A) - value(OFFSET) to offset 0 in A, and truncate A to length(A) - value(OFFSET). Otherwise truncate A to length 0.
This copies the original code:
case OP_LEFT: case OP_RIGHT: { // (in size -- out) if (stack.size() < 2) return false; valtype& vch = stacktop(-2); int nSize = CBigNum(stacktop(-1)).getint(); if (nSize < 0) return false; if (nSize > vch.size()) nSize = vch.size(); if (opcode == OP_LEFT) vch.erase(vch.begin() + nSize, vch.end()); else vch.erase(vch.begin(), vch.end() - nSize); stack.pop_back(); } break;in bip-unknown-script-restoration.mediawiki:319 in 7d29c1909c outdated
314 | +|1 315 | +|length(A) * 7 316 | +|OTHER + COPYING 317 | +| 318 | +# Pop A off the stack. 319 | +# Shift each byte in A 1 bit to the left (increasing values, equivalent to C's << operator), tracking the last non-zero value.
murchandamus commented at 8:59 PM on March 23, 2026:I assume that it is considered obvious that any overflowing bit is pushed to next next byte. However, if that is obvious, why does the specification of OP_ADD below explicitly mention “For each byte in B, add it and previous overflow into the equivalent byte in A, remembering next overflow.”? Should that also be mentioned here on OP_2MUL and OP_2DIV?
rustyrussell commented at 1:07 AM on March 24, 2026:Yeah, these are less completely described than the OP_ADD case. But the overflow has huge impact on costs, because of the worst case (copying the entire thing to add one byte at the end), so it does need to be explicitly mentioned.
How's this?
# Shift each byte in A 1 bit to the left (increasing values, equivalent to C's << operator), overflowing into the next byte.
And for OP_2DIV:
# Shift each byte in A 1 bit to the right (decreasing values, equivalent to C's >> operator), taking the top bit from the next byte, and tracking the last non-zero value.
in bip-0441.mediawiki:321 in 7d29c1909c outdated
316 | +|OTHER + COPYING 317 | +| 318 | +# Pop A off the stack. 319 | +# Shift each byte in A 1 bit to the left (increasing values, equivalent to C's << operator), tracking the last non-zero value. 320 | +# If the final byte overflows, append a single 1 byte. 321 | +# Otherwise, truncate A at the last non-zero byte.
murchandamus commented at 9:07 PM on March 23, 2026:Is the idea that A could have started with one or more trailing zero bytes? I don’t see how A could ever gain a trailing zero byte from OP_2MUL, since only a left-shifted 1 would overflow to the next byte, which is explicitly described.
rustyrussell commented at 1:12 AM on March 24, 2026:Exactly, A does not have to be normalized on input.
in bip-unknown-script-restoration.mediawiki:332 in 7d29c1909c outdated
327 | +|length(A) * 4 328 | +|OTHER 329 | +| 330 | +# Pop A off the stack. 331 | +# Shift each byte 1 bit to the right (decreasing values, equivalent to C's >> operator), tracking the last non-zero value. 332 | +# Truncate A at the last non-zero byte.
murchandamus commented at 9:19 PM on March 23, 2026:OP_DOWNSHIFT explicitly mentions that the excess downshifted bits are truncated, should that also be mentioned here?
rustyrussell commented at 1:10 AM on March 24, 2026:There's no truncation, since it's only 1 bit (except for post-normalization) With DOWNSHIFT, if it's more than 7 bits, there's truncation. That's why they're described differently.
in bip-unknown-script-restoration.mediawiki:519 in 7d29c1909c outdated
514 | +* OP_WITHIN 515 | +* OP_SHA256 516 | +* OP_HASH160 517 | +* OP_HASH256 518 | + 519 | +Those with costs not defined here have a cost of 0 (they do not operate on variable-length stack objects).
murchandamus commented at 9:35 PM on March 23, 2026:Does “those” refer to items from this list that are not defined in the second document or to additional unnamed opcodes?
Any opcodes not mentioned in this document or the preceding list have a cost of 0 (they do not operate on variable-length stack objects).in bip-unknown-script-restoration.mediawiki:547 in 7d29c1909c outdated
542 | +* Steven Roose 543 | +* FIXME: your name here! 544 | + 545 | +==Appendix A: Cost Model Calculations for Multiply and Divide== 546 | + 547 | +Multiplication and division require multiple passes over the operands, meaning a cost proportional to the square of the lengths involved, and the word size used for that iteration makes a difference. We assume 8 bytes (64 bits) at a time are evaluated, and the ability to multiply two 64-bit numbers and receive a 128-bit result, and divide a 128-bit number by a 64 bit number to receive a 128 bit quotient and remainder. This is true on modern 64-bit CPUs (sometimes using multiple instructions).
murchandamus commented at 9:36 PM on March 23, 2026:Multiplication and division require multiple passes over the operands, meaning a cost proportional to the square of the lengths involved, and the word size used for that iteration makes a difference. We assume 8 bytes (64 bits) at a time are evaluated, and the ability to multiply two 64-bit numbers and receive a 128-bit result, and divide a 128-bit number by a 64-bit number to receive a 128-bit quotient and remainder. This is true on modern 64-bit CPUs (sometimes using multiple instructions).in bip-unknown-varops-budget.mediawiki:46 in 7d29c1909c outdated
41 | +- We assume that memory allocation and deallocation overhead is negligible. 42 | +- We do not consider the cost of the script interpretation itself, which is necessarily limited by block size. 43 | +- We assume implementations use simple linear arrays/vectors of contiguous memory. 44 | +- We assume implementations use linear accesses to stack data (perhaps multiple times): random accesses would require an extension to the model. 45 | +- We assume object size is limited to the entire transaction (4,000,000 bytes, worst-case). 46 | +- Costs are based on the worst-case behavior of each opcode.
murchandamus commented at 10:15 PM on March 23, 2026:* We assume that the manipulation of the stack vector itself (e.g. OP_DROP) is negligible (with the exception of OP_ROLL) * We assume that memory allocation and deallocation overhead is negligible. * We do not consider the cost of the script interpretation itself, which is necessarily limited by block size. * We assume implementations use simple linear arrays/vectors of contiguous memory. * We assume implementations use linear accesses to stack data (perhaps multiple times): random accesses would require an extension to the model. * We assume object size is limited to the entire transaction (4,000,000 bytes, worst-case). * Costs are based on the worst-case behavior of each opcode.in bip-unknown-varops-budget.mediawiki:64 in 7d29c1909c outdated
59 | + 60 | +As each block can contain 80,000 Schnorr signature checks, we used this as a reasonable upper bound for maximally slow block processing. 61 | + 62 | +To estimate a conservative maximum runtime for each opcode, we consider scripts with two constraints: 63 | +1. the script size is limited by the existing weight limit of 4,000,000 units and 64 | +2. the script can only consume the varops budget of a whole block: 10,000 * 4,000,000 (40b).
murchandamus commented at 10:18 PM on March 23, 2026:# the script size is limited by the existing weight limit of 4,000,000 units and # the script can only consume the varops budget of a whole block: 10,000 * 4,000,000 (40b).in bip-unknown-varops-budget.mediawiki:70 in 7d29c1909c
65 | + 66 | +The script is assumed to execute in a single thread and acts on initial stack elements that are not included in the limits for conservatism. 67 | + 68 | +Ideally, on each platform we tested, the worst case time for each opcode would be no worse than the Schnorr signature upper bound: i.e. the block would get no slower. And while CHECKSIG can be batched and/or done in parallel, it also involves hashing, which is not taken into account here (the worst-case being significantly slower than the signature validations themselves). 69 | + 70 | +The signature cost is simply carried across from the existing [[bip-0342.mediawiki|BIP-342]] limit: 50 weight units allow you one signature. Since each transaction gets varops budget for the entire transaction (not just the current input's witness), and each input has at least 40 bytes (160 weight), this is actually slightly more generous than the sigops budget (which was 50 + witness weight), but still limits the entire block to 80,000 signatures.
murchandamus commented at 10:20 PM on March 23, 2026:The signature cost is simply carried across from the existing [[bip-0342.mediawiki|BIP-342]] limit: 50 weight units allow you one signature. Since each transaction gets varops budget for the entire transaction (not just the current input's witness), and each input has at least 41 bytes (164 weight), this is actually slightly more generous than the sigops budget (which was 50 + witness weight), but still limits the entire block to 80,000 signatures.
rustyrussell commented at 3:14 AM on March 24, 2026:I'm tempted to ignore this one for being too pedantic, so then when other people notice it I can claim even more pedantically that the existing text is technically correct!
Or, y'know, I could just admit I'm wrong.
in bip-unknown-varops-budget.mediawiki:105 in 7d29c1909c outdated
100 | +1. Signature operations. 101 | +2. Hashing operations. 102 | +3. OP_ROLL, which does a large-scale stack movement. 103 | +4. Fast operations: comparing bytes, comparing bytes against zero, and zeroing bytes. Empirically, these have been shown to be well-optimized. 104 | +5. Copying bytes: slightly more expensive than fast operations due to memory allocation overhead. 105 | +6. Everything else.
murchandamus commented at 10:24 PM on March 23, 2026:Use pound sign for numbered list
in bip-unknown-varops-budget.mediawiki:114 in 7d29c1909c outdated
109 | +1. Signature operations cost 500,000 (10,000 * 50) units each, this resembles the cost of the existing sigops budget. 110 | +2. Hashing costs 50 units per byte hashed. 111 | +3. OP_ROLL costs an additional 48 units (24 bytes per std::vector * 2 units per byte) per stack entry moved (i.e. the value of its operand). 112 | +4. Fast operations cost 2 units per byte output. 113 | +5. Copying operations cost 3 units per byte output. 114 | +6. Other operations cost 4 units per byte output.
murchandamus commented at 10:24 PM on March 23, 2026:Pound sign for numbered list
in bip-unknown-varops-budget.mediawiki:332 in 7d29c1909c outdated
327 | + https://github.com/jmoik/bitcoin/tree/gsr 328 | + 329 | +==Changelog== 330 | + 331 | +0.1.0: 2025-09-27: first public posting 332 | +0.2.0: 2025-02-21: increase in cost for hashing and copying based on benchmark results.
murchandamus commented at 10:39 PM on March 23, 2026:Suggesting an unnumbered list as it line breaks as currently formatted.
BIP3 specifies that the Changelog is listed most recent version first.
I assume that you meant 2026 for version 0.2.0, or it would predate version 0.1.0.
- 0.2.0 (2026-02-21): increase in cost for hashing and copying based on benchmark results. - 0.1.0 (2025-09-27): first public postingin bip-unknown-script-restoration.mediawiki:12 in 7d29c1909c outdated
7 | + Status: Draft 8 | + Type: Specification 9 | + Assigned: ? 10 | + License: BSD-3-Clause 11 | + Discussion: https://groups.google.com/g/bitcoindev/c/GisTcPb8Jco/m/8znWcWwKAQAJ 12 | + Version: 0.1.0
murchandamus commented at 10:41 PM on March 23, 2026:No Changelog present.
murchandamus commented at 10:55 PM on March 23, 2026: memberThanks, the additional explanation and additions to the abstract helped.
My first impression from the document titles was that "Restoration of disabled script functionality (Tapscript v2)" would be focused on explaining the functionality of the restored opcodes (and perhaps only maybe mention costs), whereas "Varops Budget For Script Runtime Constraint" would expound on the budget and how the costs were found. It seemed to me that you were splitting the content for users of script from the content for the implementers.
In hindsight, I should have first read "Varops Budget For Script Runtime Constraint", I think "Restoration of disabled script functionality (Tapscript v2)" would have made more sense to me after that. Either way, the title "Restoration of disabled script functionality (Tapscript v2)" does not set the expectation that the main focus of the document would be to provide Rationale for the costs of the restored opcodes.
murchandamus added the label Needs number assignment on Mar 23, 2026in bip-unknown-varops-budget.mediawiki:345 in 7d29c1909c outdated
340 | +- Brandon Black (aka Reardencode) 341 | +- John Light 342 | +- Jonas Nick 343 | +- Rijndael (aka rot13maxi) 344 | +- Steven Roose 345 | +- FIXME: your name here!
murchandamus commented at 10:59 PM on March 23, 2026:Asterisks for unnumbered list here as well.
rustyrussell commented at 12:22 AM on March 24, 2026: contributorThanks, the additional explanation and additions to the abstract helped.
My first impression from the document titles was that "Restoration of disabled script functionality (Tapscript v2)" would be focused on explaining the functionality of the restored opcodes (and perhaps only maybe mention costs), whereas "Varops Budget For Script Runtime Constraint" would expound on the budget and how the costs were found. It seemed to me that you were splitting the content for users of script from the content for the implementers.
Yes, the split is kind of awkward :(
In hindsight, I should have first read "Varops Budget For Script Runtime Constraint", I think "Restoration of disabled script functionality (Tapscript v2)" would have made more sense to me after that. Either way, the title "Restoration of disabled script functionality (Tapscript v2)" does not set the expectation that the main focus of the document would be to provide Rationale for the costs of the restored opcodes.
If we were to put restoration first, that BIP would become trivial. But, also, kinda useless? The varops budget BIP would then have to re-describe the restored opcodes in precise detail, in order to derive their costs.
I guess there are three parts:
- The concepts of varops, such as it limits and factors for different kinds of work opcodes can do.
- The restoration of script: removing length limits, making numbers unsigned, restoring old opcodes (presumably with a mention that this is deeply unsafe without the new varops).
- The reference-style listing and derivation of varops costs for every opcode.
Happy to do this. If so, would number 3 be a separate BIP, or a giant appendix? I think I'm too close to it to see it through fresh eyes, so I'll take your advice here? Help!
murchandamus commented at 1:18 AM on March 24, 2026: memberI think my confusion was mostly caused by reading the documents in the order they appear in this PR instead of the intended order. When they’re assigned numbers, putting them in the order of recommended reading should mostly resolve that. The "Restoration" BIP should then also have a Requires header along the lines of “Requires: Varops BIP" in the Preamble to clarify.
After that, the split into three documents does not seem necessary.
Perhaps some tweaks to the tables would be able to address both parts of the audience. (This is a spontaneous suggestion, please feel free to reject or amend as makes sense to you.)
I’m thinking the following might help:
- Required Stack Elements is a bit of a waste of space. Replace it with a column "Inputs", "Operands", or "Input Stack". This could communicate the count of operands and save you the "Pop x off the stack" lines in the Definition.
- I would refer to the number as the opcode, so maybe rename "Opcode" to "Operation" or "Word" and "value" to "Opcode".
- Add a column "Description" that gives a very short summary of what the opcode does. Between the Operands column and Description, the functionality should be clear enough to the Script users that read the BIP.
- Then perhaps move the Varops Cost and Varops Reason to the end, to illustrate that they follow from the Definition.
<img width="1273" height="905" alt="image" src="https://github.com/user-attachments/assets/f390afbd-8808-439a-80a0-15e1d745551d" />
That would make the columns:
| Operation | Opcode | Inputs | Description | Definition | Varops Cost | Varops Reason |
rustyrussell force-pushed on Mar 24, 2026rustyrussell force-pushed on Mar 24, 2026in bip-unknown-script-restoration.mediawiki:143 in 1093247ffd
138 | +! Varops Cost 139 | +! Varops Reason 140 | +|- 141 | +|OP_CAT 142 | +|126 143 | +|[A B]
murchandamus commented at 9:10 PM on March 24, 2026:Sorry, I didn’t account for another fun aspect of MediaWiki syntax when I made my suggestion: square brackets are used to signify external links, and therefore these stack depictions show only the first "word" in the brackets:
<img width="1051" height="878" alt="Image" src="https://github.com/user-attachments/assets/b74c9892-32df-47c7-926e-a557cab09849" />
Either of these should work:
|\[A B\]or
|<nowiki>[A B]</nowiki>
rustyrussell commented at 1:40 AM on March 25, 2026:FML. I thought about omitting the [ altogether, but it does seem more idiomatic. Good catch!
in bip-unknown-script-restoration.mediawiki:126 in 1093247ffd
121 | + 122 | +===Enabled Opcodes=== 123 | + 124 | +Fifteen opcodes that were removed in v0.3.1 are re-enabled in 0xC2 Tapscript. 125 | + 126 | +If there are fewer than the required number of stack elements, these opcodes fail validation. These are popped off the stack in right-to-left order, i.e. [A B] means pop B off the stack, then pop A off the stack.
murchandamus commented at 9:27 PM on March 24, 2026:I thought I was going crazy, when I looked at the rendered document
<img width="616" height="161" alt="Image" src="https://github.com/user-attachments/assets/d1d3d924-c0ac-4c1e-8e91-2258ee964f9b" />
and then the MediaWiki source code, but alas, just another MediaWiki quirk: single square brackets are used to signify external links. To render them as text, they need to be escaped. I think either of these should work:
If there are fewer than the required number of stack elements, these opcodes fail validation. These are popped off the stack in right-to-left order, i.e. \[A B\] means pop B off the stack, then pop A off the stack.If there are fewer than the required number of stack elements, these opcodes fail validation. These are popped off the stack in right-to-left order, i.e. <nowiki>[A B]</nowiki> means pop B off the stack, then pop A off the stack.
A bit late to tell you now, but we accept submissions either in MediaWiki or in Markdown format! ;-)
rustyrussell commented at 1:48 AM on March 25, 2026:Yes, I read that when I read BIP 3, but mediawiki was the norm when I started. As you can tell, I'm more familiar with md. And tables are a md extension, since it's not really a standard. GH supports them so maybe that's moot?
Happy to redo it, if it makes people happy...
murchandamus commented at 5:10 AM on March 25, 2026:Nah, just if Markdown is your preference, you can do the next one in Markdown. And yeah, tables work on GitHub with Markdown.
in bip-0441.mediawiki:182 in 1093247ffd outdated
177 | +|[A OFFSET] 178 | +|Extract the right bytes of A, from OFFSET onwards 179 | +| 180 | +# Pop operands off the stack. 181 | +# If value(OFFSET) is less than length(A), copy value(OFFSET) bytes from offset length(A) - value(OFFSET) to offset 0 in A, and truncate A to length(A) - value(OFFSET). Otherwise truncate A to length 0. 182 | +# Push A onto the stack.
murchandamus commented at 9:46 PM on March 24, 2026:This Description and Definition appear to be at odds.
Let’s say that A:
0x00112233DEADBEEFand OFFSET is 5. Per the Description I would expect:A: 0x00112233DEADBEEF OFFSET 5: ^ OUTPUT: 0xADBEEFI.e., three bytes starting with the byte at position 5 are copied to the front.
Per the Definition: copy 5 bytes from offset 8-5 = 3 to offset 0:
A: 0x00112233DEADBEEF ↦ 0x33DEADBEEFADBEEFTruncate to length(A)-value(OFFSET) (3):
↦ 0x33DEADI guess this should read:
|Extract the right bytes of A, from OFFSET onwards | # Pop operands off the stack. # If value(OFFSET) is less than length(A), copy length(A) - value(OFFSET) bytes from offset value(OFFSET) to offset 0 in A, and truncate A to length(A) - value(OFFSET). Otherwise truncate A to length 0. # Push A onto the stack.
rustyrussell commented at 2:02 AM on March 25, 2026:This correct: great catch. Doesn't alter the cost, fortunately.
in bip-unknown-script-restoration.mediawiki:219 in 1093247ffd
214 | +|length(A) * 4 215 | +|OTHER 216 | +|- 217 | +|OP_AND 218 | +|132 219 | +|[A B]
murchandamus commented at 9:47 PM on March 24, 2026:|\[A B\]and below
murchandamus commented at 9:50 PM on March 24, 2026: memberLooks good, except for one more MediaWiki quirk and a potential issue in OP_RIGHT.
in bip-unknown-script-restoration.mediawiki:78 in 1093247ffd
73 | + 74 | +===Arbitrary-length Values, Endianness, and Normalization of Results=== 75 | + 76 | +The restoration of bit operations means that the little-endianness of stack values is once more exposed to the Script author, if they mix them with arithmetic operations. The restoration of arbitrary-length values additionally exposes the endianness to the implementation authors (who cannot simply load stack entries into registers), and requires explicit consideration when considering varops costs of operations.<ref>For example, removing trailing bytes from a stack element is almost free, whereas removing bytes from the front involves copying all remaining bytes.</ref> 77 | + 78 | +Note that only arithmetic operations (those which treat operands as numbers) normalize their results: bit and byte operations do not.<ref>Such non-arithmetic operations can used to operate on values such as preimages or (with introspection) parts of transactions, where truncation of zeros would be unexpected. One could argue that even arithmetic operators should not normalize, but that would be a gratuitous and surprising change. Note that "0 OP_ADD" can always be used to cheaply normalize the top stack element.</ref> Thus operations such as "0 OP_ADD" and "2 OP_MUL" will never result in a top stack entry with a trailing zero byte, but "0 OP_OR" and "1 OP_UPSHIFT" may.<ref>The original Bitcoin implementation had a similar operational split, but OP_LSHIFT and OP_RSHIFT did normalize, which was almost a requirement given that they also preserved the sign of the shifted operand</ref>
murchandamus commented at 9:55 PM on March 24, 2026:Note that only arithmetic operations (those which treat operands as numbers) normalize their results: bit and byte operations do not.<ref>Such non-arithmetic operations be can used to operate on values such as preimages or (with introspection) parts of transactions, where truncation of zeros would be unexpected. One could argue that even arithmetic operators should not normalize, but that would be a gratuitous and surprising change. Note that "0 OP_ADD" can always be used to cheaply normalize the top stack element.</ref> Thus operations such as "0 OP_ADD" and "2 OP_MUL" will never result in a top stack entry with a trailing zero byte, but "0 OP_OR" and "1 OP_UPSHIFT" may.<ref>The original Bitcoin implementation had a similar operational split, but OP_LSHIFT and OP_RSHIFT did normalize, which was almost a requirement given that they also preserved the sign of the shifted operand</ref>
rustyrussell commented at 2:03 AM on March 25, 2026:I get the idea, I think you be the wrong position in though!
in bip-unknown-script-restoration.mediawiki:120 in 1093247ffd
115 | + 116 | +## If the execution results in anything but exactly one element on the stack which evaluates to true with <code>CastToBool()</code>, fail. 117 | + 118 | +Now: 119 | + 120 | +## If the execution results in anything but exactly one element on the stack which contains one or more non-zero bytes, fail.
murchandamus commented at 10:01 PM on March 24, 2026:This is now rendered as a numbered list. Perhaps indenting plus italicization would meet your expectation?
Previously: : ''If the execution results in anything but exactly one element on the stack which evaluates to true with <code>CastToBool()</code>, fail.'' Now: : ''If the execution results in anything but exactly one element on the stack which contains one or more non-zero bytes, fail.''
rustyrussell commented at 1:54 AM on March 25, 2026:I really want to label it 4 (ii), to make it clear. I have open-coded that.
BTW https://www.mediawiki.org/wiki/Help:Formatting implies that
:for indent is evil, so I'll avoid.in bip-unknown-script-restoration.mediawiki:336 in 1093247ffd
331 | +|142 332 | +|[A] 333 | +|Divide A by 2 334 | +| 335 | +# Pop operands off the stack. 336 | +# Shift each byte in A 1 bit to the right (decreasing values, equivalent to C's >> operator), taking the top bit from the next byte, and tracking the last non-zero value.
murchandamus commented at 10:08 PM on March 24, 2026:This sentence is ambiguous. I think you mean that it’s setting the new value of this byte’s top bit from the next byte’s bottom bit, but it could be read as taking the next byte’s top bit.
# Shift each byte in A 1 bit to the right (decreasing values, equivalent to C's >> operator), taking the next byte’s bottom bit as the value of the top bit, and tracking the last non-zero value.
rustyrussell commented at 4:03 AM on March 25, 2026:Gotta love little endian. I always have to use a whiteboard: eg. value is 1 + 256*3, so 769:
00000001 00000011Divide by 2 is 384:
10000000 00000001So, yes, you're right.
in bip-unknown-script-restoration.mediawiki:561 in 1093247ffd
556 | +* Steven Roose 557 | +* FIXME: your name here! 558 | + 559 | +==Appendix A: Cost Model Calculations for Multiply and Divide== 560 | + 561 | +Multiplication and division require multiple passes over the operands, meaning a cost proportional to the square of the lengths involved, and the word size used for that iteration makes a difference. We assume 8 bytes (64 bits) at a time are evaluated, and the ability to multiply two 64-bit numbers and receive a 128-bit result, and divide a 128-bit number by a 64-bit number to receive a 128 bit quotient and remainder. This is true on modern 64-bit CPUs (sometimes using multiple instructions).
murchandamus commented at 10:10 PM on March 24, 2026:I think
:s/128 bit quotient/128-bit quotient/got lost?Multiplication and division require multiple passes over the operands, meaning a cost proportional to the square of the lengths involved, and the word size used for that iteration makes a difference. We assume 8 bytes (64 bits) at a time are evaluated, and the ability to multiply two 64-bit numbers and receive a 128-bit result, and divide a 128-bit number by a 64-bit number to receive a 128-bit quotient and remainder. This is true on modern 64-bit CPUs (sometimes using multiple instructions).(No action necessary as these documents are already fairly mature, but I find that also in text documents limiting lines to at most 120 characters makes diffs and review comments more compact and easier to parse, and I usually recommend that to authors in the BIPs repository when they can still expect a number of revisions. See e.g., https://sembr.org/)
rustyrussell commented at 3:28 AM on March 25, 2026:I made the final fixup simply "wrap at 78 outside tables".
I think ease of review is important.
murchandamus commented at 10:37 PM on March 24, 2026: memberNoticed a few more minor things when scanning the changes from the last version I reviewed.
rustyrussell commented at 4:14 AM on March 25, 2026: contributorNote: I noticed that we used the marker ARITH on some costs. This is not present in the varops BIP: I'm consulting with @jmoik now to see if this is a simple omission, or requires deeper change :(
in bip-unknown-script-restoration.mediawiki:116 in d228cb266d
112 | @@ -113,17 +113,17 @@ In addition, the [[bip-0342.mediawiki#specification|BIP-342 success requirement] 113 | 114 | Previously: 115 | 116 | -## If the execution results in anything but exactly one element on the stack which evaluates to true with <code>CastToBool()</code>, fail. 117 | +``4. (ii) If the execution results in anything but exactly one element on the stack which evaluates to true with <code>CastToBool()</code>, fail.``
murchandamus commented at 9:14 PM on March 25, 2026:In case you didn’t notice, your commit message mentions italicization, but that would be two single-quotes <code>''</code>, not two backticks <code>``</code>.
<img width="996" height="163" alt="Image" src="https://github.com/user-attachments/assets/f27366af-686b-4f3b-9fdb-11101121d2d9" />
in bip-unknown-script-restoration.mediawiki:154 in d228cb266d
150 | @@ -151,7 +151,7 @@ See [[bip-unknown-varops-budget.mediawiki|BIP-varops]] for the meaning of the an 151 | |- 152 | |OP_SUBSTR 153 | |127 154 | -|[A BEGIN LEN] 155 | +|<nowiki>[A BEGIN LEN]</nowiki>
murchandamus commented at 9:18 PM on March 25, 2026:I’m feeling a bit silly to talk so much about formatting, but I was just looking at the document, and given that the right element in the square brackets is the "top"" of the stack, maybe line breaks in the Input Stack column are sub-optimal:
<img width="310" height="448" alt="Image" src="https://github.com/user-attachments/assets/c41d6f61-ed04-455f-b228-da47ba1c6485" />
Using
<pre>works, but formats it as code (which may be acceptable), using an actual non-breakable space character (U+00A0) does work without changing the appearance at preventing the line break: https://github.com/murchandamus/bips/commit/254f4b56536d00694cea9cd61a454bf68b7d888d, rendered file: https://github.com/murchandamus/bips/blob/254f4b56536d00694cea9cd61a454bf68b7d888d/bip-unknown-script-restoration.mediawiki#splice-opcodes
rustyrussell commented at 10:45 PM on March 25, 2026:No, this matters. Sure, I never read the formatted version, preferring plain text, but I know others will. Thanks for doing this investigation!
murchandamus force-pushed on Mar 25, 2026murchandamus commented at 9:29 PM on March 25, 2026: memberMy apologies, @rustyrussell. I was intending to experiment on my own branch whether there is an easy way to prevent line breaks in the Input Stack column, but I pushed the commit to your branch instead of my repository. I’ve reverted the change.
murchandamus commented at 9:50 PM on March 25, 2026: memberThanks for the line breaking. Got a couple more format nits for you. Please feel free to ignore.
Also, let’s refer to the Grand Script Restoration BIPs as BIP 440 and BIP 441. I assume you’ll want to make it BIP440: Varops, and BIP441: Restoration, but please feel free to do it the other way around, if I misunderstood.
Could you please rename the files, set the BIP number and Assigned date in the Preamble headers, and add table entries to the table in the README.mediawiki?
murchandamus renamed this:Varops: Two BIPs for Script Restoration: varops calculations and tapleaf version (0xc2).
BIP440,441—Varops: Two BIPs for Script Restoration: varops calculations and tapleaf version (0xc2).
on Mar 25, 2026murchandamus removed the label Needs number assignment on Mar 25, 2026in bip-unknown-varops-budget.mediawiki:332 in 4256c69b5a
327 | + https://github.com/jmoik/bitcoin/tree/gsr 328 | + 329 | +==Changelog== 330 | + 331 | +- 0.2.0: 2026-02-21: increase in cost for hashing and copying based on benchmark results. 332 | +- 0.1.0: 2025-09-27: first public posting
murchandamus commented at 11:43 PM on March 25, 2026:* 0.2.0: 2026-02-21: increase in cost for hashing and copying based on benchmark results. * 0.1.0: 2025-09-27: first public postingrustyrussell commented at 3:30 AM on March 27, 2026: contributorOK, I shall also rebase to squash the bazillion fixes, if that's OK? I will leave the final change (which actually was a change in how OP_MUL costs are calculated) as a separate commit though.
rustyrussell force-pushed on Mar 27, 2026rustyrussell commented at 4:01 AM on March 27, 2026: contributorI smooshed all the fixes, here's the diff from the last commit I pushed which you reviewed:
$ git diff 4256c69b5ade17e36e0cb30c07a33cf47125188a diff --git a/bip-unknown-script-restoration.mediawiki b/bip-unknown-script-restoration.mediawiki index 9e01eae6..56691843 100644 --- a/bip-unknown-script-restoration.mediawiki +++ b/bip-unknown-script-restoration.mediawiki @@ -624,7 +624,8 @@ Work in progress: ==Changelog== -- 0.1.0: 2025-09-27: first public posting +* 0.2.0: 2025-02-21: change costs to match those in varops budget +* 0.1.0: 2025-09-27: first public posting ==Thanks== diff --git a/bip-unknown-varops-budget.mediawiki b/bip-unknown-varops-budget.mediawiki index c74c05be..bfc927b3 100644 --- a/bip-unknown-varops-budget.mediawiki +++ b/bip-unknown-varops-budget.mediawiki @@ -111,6 +111,7 @@ Each class then has the following costs. # OP_ROLL costs an additional 48 units (24 bytes per std::vector * 2 units per byte) per stack entry moved (i.e. the value of its operand). # Fast operations cost 2 units per byte output. # Copying operations cost 3 units per byte output. +# Arithmetic operations (which don't pipeline as well due to the overflow between words) cost 6 units per byte output. # Other operations cost 4 units per byte output. ===Variable Opcode Budget=== @@ -127,6 +128,8 @@ We use the following annotations to indicate the derivation for each opcode: : Copying bytes: cost = 3 per byte copied. ;LENGTHCONV : Converting an operand to a length value, including verifying that trailing bytes are zero: cost = 2 per byte examined. +;ARITH +: Arithmetic operations which have carry operations: cost = 6 per byte examined. ;SIGCHECK : Checking a signature is a flat cost: cost = 500,000. ;HASH @@ -328,8 +331,8 @@ Work in progress: ==Changelog== -- 0.2.0: 2026-02-21: increase in cost for hashing and copying based on benchmark results. -- 0.1.0: 2025-09-27: first public posting +* 0.2.0: 2026-02-21: increase in cost for hashing and copying based on benchmark results. +* 0.1.0: 2025-09-27: first public posting ==Thanks==Sorry, I missed the nbsp; change. But it's horrible, because it makes the text unreadable. Let me experiment with putting a nbsp in the title as a workaround.
rustyrussell force-pushed on Mar 27, 2026rustyrussell force-pushed on Mar 27, 2026in bip-0441.mediawiki:203 in 9341e57157
198 | +! Varops Cost 199 | +! Varops Reason 200 | +|- 201 | +|OP_CAT 202 | +|126 203 | +|<nowiki>[A B]</nowiki>
murchandamus commented at 5:17 AM on March 27, 2026:I think we crossed a wire here. I had tried and discarded the idea of using an HTML code (
) in the<nowiki>but that looked terrible. I had then recommended on using a UTF-8 non-breakable space character instead (U+00a0). Like so (space in suggestion is actually a non-breakable space):|<nowiki>[A B]</nowiki>Preventing a linebreak in the title seems like a good idea, but it seems to be a bit unreliable:
<img width="128" height="476" alt="Image" src="https://github.com/user-attachments/assets/4f2a8d8e-847f-44e7-83b6-cdeb583266ec" />
in bip-0441.mediawiki:214 in 9341e57157
209 | +|(length(A) + length(B)) * 3 210 | +|COPYING 211 | +|- 212 | +|OP_SUBSTR 213 | +|127 214 | +|<nowiki>[A BEGIN LEN]</nowiki>
murchandamus commented at 5:22 AM on March 27, 2026:I think we crossed a wire. I had tried and discarded the idea of using an HTML code (
) in the<nowiki>because it looked terrible (like you ;)). I had then recommended on using a UTF-8 non-breakable space character instead (U+00a0). As follows (the spaces in my suggestion are actually a non-breakable space UTF-8 characters (U+00a0)):|<nowiki>[A BEGIN LEN]</nowiki>Preventing a linebreak in the title sounds like a good idea, but it seems to be a bit unreliable for the longer stacks:
<img width="1065" height="523" alt="Image" src="https://github.com/user-attachments/assets/dd765417-a37b-4464-be76-3dd696928526" />
in bip-0440.mediawiki:9 in 9341e57157
0 | @@ -0,0 +1,353 @@ 1 | +<pre> 2 | + BIP: 440 3 | + Layer: Consensus (soft fork) 4 | + Title: Varops Budget For Script Runtime Constraint 5 | + Authors: Rusty Russell <rusty@rustcorp.com.au> 6 | + Julian Moik <julianmoik@gmail.com> 7 | + Status: Draft 8 | + Type: Specification 9 | + Assigned: 2026-03-27
murchandamus commented at 5:24 AM on March 27, 2026:You time-traveler. It’s still the 26th here. 😁
Assignment was at 2026-03-25–21:50Z, so that should please read:
Assigned: 2026-03-25in bip-0441.mediawiki:9 in 9341e57157
0 | @@ -0,0 +1,715 @@ 1 | +<pre> 2 | + BIP: 441 3 | + Layer: Consensus (soft fork) 4 | + Title: Restoration of disabled script (Tapleaf 0xC2) 5 | + Authors: Rusty Russell <rusty@rustcorp.com.au> 6 | + Julian Moik <julianmoik@gmail.com> 7 | + Status: Draft 8 | + Type: Specification 9 | + Assigned: 2026-03-27
murchandamus commented at 5:30 AM on March 27, 2026:Assignment was at 2026-03-25–21:50Z, so that should please read:
Assigned: 2026-03-25murchandamus renamed this:BIP440,441—Varops: Two BIPs for Script Restoration: varops calculations and tapleaf version (0xc2).
BIP440: Varops Budget for Script Runtime Constraint, BIP441: Restoration of disabled Script (tapleaf 0xc2)
on Mar 27, 2026murchandamus commented at 5:32 AM on March 27, 2026: memberThanks for the summary. Squash was a good call, changes look good, except that the Assigned date should be 2026-03-25 (see inline comment).
Sorry, I missed the nbsp; change. But it's horrible, because it makes the text unreadable. Let me experiment with putting a nbsp in the title as a workaround.
Wondering whether we crossed a wire here. I had tried using the non-breakable space HTML code in the commit that I accidentally pushed to your branch, but in
<nowiki>it was written out which looked terrible (as you seem to have discovered as well). Using the non-breakable space UTF-8 character (U+00A0) looked fine in the render and code (unless you have your editor set up to show it?), so that was what I recommended.Otherwise looks good. Thanks for the quick turn-around, pleasure to work with you. :)
977342a943Varops: Two BIPs for Script Restoration: varops calculations and tapleaf version (0xc2).
Special thanks to Murch for teaching me mediawiki, and so much great formatting and clarity advice. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
32035058b4script restoration: fix MUL cost to account to round up B to word boundary.
Julian points out that the implementation does this, which improves accuracy for the case of small B (since the term is multiplied: for normal OP_ADD etc we don't bother, since the difference is very bounded). Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
78e7562de3BIP 440, 441: official numbers, into README.mediawiki and renamed.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
rustyrussell commented at 4:05 AM on March 29, 2026: contributorOK, fixed to use literal
NO-BREAK SPACEnot , which is much better. And fixed dates. Squashed into three neat commits. Here's the overall diff since last review:diff --git a/bip-0440.mediawiki b/bip-0440.mediawiki index a8d58895..409b5a0f 100644 --- a/bip-0440.mediawiki +++ b/bip-0440.mediawiki @@ -6,7 +6,7 @@ Julian Moik <julianmoik@gmail.com> Status: Draft Type: Specification - Assigned: 2026-03-27 + Assigned: 2026-03-25 License: BSD-3-Clause Discussion: https://groups.google.com/g/bitcoindev/c/GisTcPb8Jco/m/8znWcWwKAQAJ https://delvingbitcoin.org/t/benchmarking-bitcoin-script-evaluation-for-the-varops-budget-great-script-restoration/2094 diff --git a/bip-0441.mediawiki b/bip-0441.mediawiki index 6caf21b5..f658bcef 100644 --- a/bip-0441.mediawiki +++ b/bip-0441.mediawiki @@ -6,7 +6,7 @@ Julian Moik <julianmoik@gmail.com> Status: Draft Type: Specification - Assigned: 2026-03-27 + Assigned: 2026-03-25 License: BSD-3-Clause Discussion: https://groups.google.com/g/bitcoindev/c/GisTcPb8Jco/m/8znWcWwKAQAJ Version: 0.2.1 @@ -181,7 +181,7 @@ Fifteen opcodes that were removed in v0.3.1 are re-enabled in 0xC2 Tapscript. If there are fewer than the required number of stack elements, these opcodes fail validation. These are popped off the stack in right-to-left order, -i.e. <nowiki>[A B]</nowiki> means pop B off the stack, then pop A off the +i.e. <nowiki>[A B]</nowiki> means pop B off the stack, then pop A off the stack. See [[bip-0440.mediawiki|BIP440]] for the meaning of the @@ -192,7 +192,7 @@ annotations in the varops cost field. {| ! Mnemonic ! Opcode -! Input Stack +! Input Stack ! Description ! Definition ! Varops Cost @@ -200,7 +200,7 @@ annotations in the varops cost field. |- |OP_CAT |126 -|<nowiki>[A B]</nowiki> +|<nowiki>[A B]</nowiki> |Append B to A | # Pop operands off the stack. @@ -211,7 +211,7 @@ annotations in the varops cost field. |- |OP_SUBSTR |127 -|<nowiki>[A BEGIN LEN]</nowiki> +|<nowiki>[A BEGIN LEN]</nowiki> |Extract bytes BEGIN through BEGIN+LEN of A | # Pop operands off the stack. @@ -223,7 +223,7 @@ annotations in the varops cost field. |- |OP_LEFT |128 -|<nowiki>[A OFFSET]</nowiki> +|<nowiki>[A OFFSET]</nowiki> |Extract the left OFFSET bytes of A | # Pop operands off the stack. @@ -234,7 +234,7 @@ annotations in the varops cost field. |- |OP_RIGHT |129 -|<nowiki>[A OFFSET]</nowiki> +|<nowiki>[A OFFSET]</nowiki> |Extract the right bytes of A, from OFFSET onwards | # Pop operands off the stack. @@ -259,7 +259,7 @@ OP_RIGHT must copy the bytes, which depends on the OFFSET value. {| ! Mnemonic ! Opcode -! Input Stack +! Input Stack ! Description ! Definition ! Varops Cost @@ -278,7 +278,7 @@ OP_RIGHT must copy the bytes, which depends on the OFFSET value. |- |OP_AND |132 -|<nowiki>[A B]</nowiki> +|<nowiki>[A B]</nowiki> |Binary AND of A and B | # Pop operands off the stack. @@ -290,7 +290,7 @@ OP_RIGHT must copy the bytes, which depends on the OFFSET value. |- |OP_OR |133 -|<nowiki>[A B]</nowiki> +|<nowiki>[A B]</nowiki> |Binary OR of A and B | # Pop operands off the stack. @@ -302,7 +302,7 @@ OP_RIGHT must copy the bytes, which depends on the OFFSET value. |- |OP_XOR |134 -|<nowiki>[A B]</nowiki> +|<nowiki>[A B]</nowiki> |Binary exclusive-OR of A and B | # Pop operands off the stack. @@ -331,7 +331,7 @@ OP_DOWNSHIFT (née OP_RSHIFT). {| ! Mnemonic ! Opcode -! Input Stack +! Input Stack ! Description ! Definition ! Varops Cost @@ -339,7 +339,7 @@ OP_DOWNSHIFT (née OP_RSHIFT). |- |OP_UPSHIFT |152 -|<nowiki>[A BITS]</nowiki> +|<nowiki>[A BITS]</nowiki> |Move bits of A right by BITS (numerically increase) | # Pop operands off the stack. @@ -352,7 +352,7 @@ OP_DOWNSHIFT (née OP_RSHIFT). |- |OP_DOWNSHIFT |153 -|<nowiki>[A BITS]</nowiki> +|<nowiki>[A BITS]</nowiki> |Move bits of A left by BITS (numerically decrease) | # Pop operands off the stack. @@ -385,7 +385,7 @@ routine as OP_DOWNSHIFT. {| ! Mnemonic ! Opcode -! Input Stack +! Input Stack ! Description ! Definition ! Varops Cost @@ -418,7 +418,7 @@ routine as OP_DOWNSHIFT. |- |OP_MUL |149 -|<nowiki>[A B]</nowiki> +|<nowiki>[A B]</nowiki> |Multiply A by B | # Pop operands off the stack. @@ -432,7 +432,7 @@ routine as OP_DOWNSHIFT. |- |OP_DIV |150 -|<nowiki>[A B]</nowiki> +|<nowiki>[A B]</nowiki> |Divide A by (non-zero) B | # Pop operands off the stack. @@ -446,7 +446,7 @@ routine as OP_DOWNSHIFT. |- |OP_MOD |151 -|<nowiki>[A B]</nowiki> +|<nowiki>[A B]</nowiki> |Replace A with remainder when A divided by (non-zero) B | # Pop operands off the stack. @@ -476,7 +476,7 @@ The opcodes OP_ADD, OP_SUB, OP_1ADD and OP_1SUB are redefined in 0xC2 Tapscript {| ! Mnemonic ! Opcode -! Input Stack +! Input Stack ! Description ! Definition ! Varops Cost @@ -484,7 +484,7 @@ The opcodes OP_ADD, OP_SUB, OP_1ADD and OP_1SUB are redefined in 0xC2 Tapscript |- |OP_ADD |147 -|<nowiki>[A B]</nowiki> +|<nowiki>[A B]</nowiki> |Add A and B | # Pop operands off the stack. @@ -509,7 +509,7 @@ The opcodes OP_ADD, OP_SUB, OP_1ADD and OP_1SUB are redefined in 0xC2 Tapscript |- |OP_SUB |148 -|<nowiki>[A B]</nowiki> +|<nowiki>[A B]</nowiki> |Subtract B from A where B is <= A | # Pop operands off the stack.rustyrussell force-pushed on Mar 29, 2026murchandamus removed the label PR Author action required on Mar 29, 2026murchandamus commented at 4:56 AM on March 29, 2026: memberLGTM. I’m gonna leave it open for a few more days to see if more review substantiates. I’ll be afk for a week, will then revisit.
What’s the next step from your perspective? Is there still have planned work, are you waiting for any expected reviewers, or is it ready for publication from your side?
rustyrussell commented at 2:42 AM on March 30, 2026: contributorLGTM. I’m gonna leave it open for a few more days to see if more review substantiates. I’ll be afk for a week, will then revisit.
That seems wise: we're not suddenly in a hurry!
What’s the next step from your perspective? Is there still have planned work, are you waiting for any expected reviewers, or is it ready for publication from your side?
I am not expecting more reviewers, but of course I'm hoping that once it's merged we can get some more eyes on it. More feedback is always welcome!
murchandamus merged this on Apr 7, 2026murchandamus closed this on Apr 7, 2026murchandamus commented at 3:39 PM on April 7, 2026: memberSplendid. It’s published.
ContributorsLabels
github-metadata-mirror
This is a metadata mirror of the GitHub repository bitcoin/bips. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-14 15:10 UTC
This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me