Raising the 520 byte push limit & 201 operation limit

I think there are two current limits which most constrain the functionality of the Bitcoin Cash VM. They are closely related, and any changes to them must be tested together.

What work needs to be done before these limits may be relaxed?

520 Byte Push Limit

This limit prevents items longer than 520 bytes from being pushed, OP_NUM2BINed, or OP_CATed on to the stack. In the C++ implementation, the constant is called MAX_SCRIPT_ELEMENT_SIZE. The limit was present as a magic number in the earliest versions of Bitcoin.

Increasing this limit has the potential to:

  • make hashing operations much more expensive
  • increase VM memory usage (OP_DUP OP_CAT OP_DUP OP_CAT [...])
  • magnify other pathological constructions, especially by allowing larger P2SH scripts

If we can reduce or eliminate these potential bottlenecks/DOS vectors, it will allow:

  • larger P2SH scripts – constraining scripts to 520 bytes prevents a huge variety of more complex use cases (including many useful CashToken-based public covenants)
  • more efficient P2SH scripts – some data is more efficient to include in the P2SH script itself, but the 520 byte limit forces authors to design contracts to pull and validate this data from the unlocking bytecode. By allowing larger P2SH scripts, this overhead can be avoided.
  • larger hash preimages – many OP_CHECKDATASIG use cases require inspecting the contents of a signed message; the 520 byte limit also limits the size of these messages. (This could be worked around using merkle trees, but at the cost of much more expensive hashing.) In particular, CashToken proofs must include their parent transactions, so this limit also sets the upper-bound for CashToken transaction sizes.

I think we should consider making this limit the same as the current MAX_SCRIPT_SIZE limit: 10,000 bytes.

This would effectively eliminate it as a roadblock to complex use cases, while still serving its purpose for DOS prevention.

Some things I think need to be done before this is possible:

What other DOS attacks are possible? What else could raising this limit break?

201 Operation Limit

This limit invalidates scripts which use more than 201 non-push opcodes. In the Satoshi implementation, the constant is called MAX_OPS_PER_SCRIPT.

This one is more straight-forward – more operations allows for more complex scripts.

What are some concerning constructions here?

  • A full block of transactions with <n> OP_HASH160 OP_HASH160 OP_HASH160 [...x201], where n is incremented for every script (preventing caches from being useful) – increasing the limit reduces the “transaction overhead” in this block, allowing a few more pathological transactions to be included

To raise this limit, I think we need:

  • some hashing-specific operation limit

Can anyone offer any other attacks which are magnified by raising these limits?

Also, if anyone has links to any analyses of relevant DOS attacks, it would be great if we could collect those here.

5 Likes

I see Core PR#16902 has been backported to ABC, I’ll look into backporting it to BCHN too.

2 Likes

Hi, the developer of BlockUpload.io here,

I would vote NACK on this change unless OP_RETURN size limit is proportionally increased. The website uses 1-of-8 multisig in a P2SH where there is one real public key and the rest are fake 65-byte data pushes. OP_RETURN size limit was upgraded to 220 because DexX7 the Omni developer calculated that it was more feasible to use the P2SH method than OP_RETURN if the limit was less than 220 bytes. We should keep the limits proportional or OP_RETURN will only be used by those can’t afford changing the code.

It should be adjusted so that OP_RETURN is always more feasible (because that’s its purpose) otherwise we can expect everyone to push lots of data to move to P2SH multisig.

5 Likes

Thanks for the heads up! I had totally forgotten the origin of the 220 byte OP_RETURN limit. Here’s the commit in BCHN. Is there any more BCH-specific discussion we can link to here?

I don’t remember ever digging into this, and think I may be misunderstanding the analysis in the linked CIP6 document:

What is requiring the un-hashed data to be directly included in the 520 byte P2SH redeem script?

Is it not possible to include only a hash of the data in the redeem script (to protect the data from being modified before the TX is mined), then provide the un-hashed data before it in the unlocking bytecode?

If I’m not mistaken, the 1650 byte limit on unlocking bytecode (MAX_TX_IN_SCRIPT_SIG_SIZE) should give you well over ~1.5KB of space to commit data, and the 520 byte push limit only affects how many chunks that data must be divided into.

1 Like

You’re right - multisig doesn’t matter, as presented in CIP6.

That’s new to me, I’ll experiment with that idea and report back.

Could you please give a link to a source code where MAX_TX_IN_SCRIPT_SIG_SIZE is defined?

It’s in policy.h in most C++ implementations. :+1:

I e-mailed Dexx about it:

Hi Dexx7,

I’m the developer of a tool used to store files in the blockchain (blockupload) and
I would be grateful if you could share you opinion on something:

Jason Dreyzehner and I were discussing (link to this webpage)
about the most efficient way of pushing data. My website used Class B transaction, multsig-in-P2SH,
which I thought of as efficient. We were wondering if the following would work, and why Omni doesn’t use the following (in P2SH):

ScriptPubKey: OP_HASH160 <hash> OP_EQUALVERIFY 1 <fake public keys> <real public key> 7 OP_CHECKMULTISIG(VERIFY OP_DEPTH OP_0 OP_EQUAL)
ScriptSig: <1600 byte data push> <real signature>

The parts in parentheses are optional, but desirable malleability checks.

The scriptsig’s length limit can be found here: bitcoin/policy.cpp at 4a540683ec40393d6369da1a9e02e45614db936d · bitcoin/bitcoin · GitHub

Jason thought of this way as strictly superior than class B or even CIP-6 (link that you posted) that seems to be the current best. This seems unbelievably efficient to me. I was wondering whether you had any experience with an upload method similar to this, or why Omni decided not to use this?

Thank you,
MCCCS

OK, the 220 byte limit is no longer relevant since you just invented a second way that makes P2SH pushing 4x as feasible or 1.25x as CIP-6.
His answer:

Hi MCCCS,

thanks for reaching out. First of all, the proposal to raise the
OP_RETURN limit was not merged in Bitcoin Core, but it’s actually in
Bitcoin Cash.

Have you tested this script? I’m very curious. It’s an interesting
approach. :slight_smile:

In Omni, the bare-multisig approach is a relict from the past and we
haven’t added a new way so far.

Cheers

Me:

I agree that this seems promising. I’ll try to upload something that
uses this method. I haven’t tried this script yet.

If this works, then the OP_RETURN limit needs to be QUADRUPLED to stay competitive
your graph at https://github.com/bitcoin/bitcoin/issues/12033 would become archaic.
It seems to be a nice candidate for class D transaction format.

I’ll report back, once I try this in a few weeks.

Thanks,
MCCCS

This is so exciting, although it’s off-topic to this discussion.

1 Like

I backported this to BCHN, see here:

2 Likes

Bitcoin Unlimited is also porting this: Port core 16902 O(1): OP_IF/NOTIF/ELSE/ENDIF script implementation (!2428) · Merge Requests · bitcoinunlimited / BCHUnlimited · GitLab

1 Like

I’m separately pushing for allowing multiple OP_RETURNs in a single transaction. Talk thus far was that the aggregate size of all OP_RETURNs would need to stay within the present 220 byte limit. Just running the question by you all to make sure that support for multiple OP_RETURNs won’t break any of this work. Any comments/feedback?

2 Likes

Nope, I don’t think there will be any interaction with these two changes. :+1:

Want to start a new topic on allowing multiple OP_RETURNs? I’d love to see that happen.

In the past, I think a lot of resistance stemmed from the perception that OP_RETURN is a temporary hack in need of replacement by some formal “data” field in a new transaction format. (I think that misunderstands the transaction format, and OP_RETURN is actually an ideal solution within the TX model, e.g. SIGHASH_SINGLE.) So I think the strongest remaining concern is probably about the “fee structure” of adding extra data to the blockchain.

One conservative option might be to allow a total of N bytes across all OP_RETURN outputs, where the full, serialized size of the OP_RETURN output is counted. Also, I think we’ve just realized that the 220 byte limit was selected partially by mistake. It might be a good idea to select a new N based on the info above.

3 Likes

FYI the multiple OP_RETURN proposal is here: Multiple OP_RETURNS - This time for real!.

2 Likes

I think that once this suggestion is expanded into a full chip, it makes sense to make a note on both chips of how they interact with eachother.

My understanding is that the multiple opreturn chip would change how the limit is counted, but would not touch the actual number. This chip, on the other hand, could consider changing the actual number, but should not have any impact on how it’s counted.

Therefor, this chip will be the one to face the resistance of “more data on chain”, while the multiple opreturn remains a question about compatibility and interoperability between opreturn based protocols.

2 Likes

Sounds great :rocket:

I think that would still be misplaced – raising the 520 byte push limit has no meanigful impact on data storage potential, since the MAX_TX_IN_SCRIPT_SIG_SIZE size (1650 bytes) is the primary VM-level limiter right now.

And even that is not a serious limiter – its just the threshold at which people will need to break larger “uploads” into multiple inputs (adding overhead of a few bytes per input). The first meaningful limit is MAX_STANDARD_TX_SIZE, which is 100,000 bytes. After that, MAX_TX_SIZE (if they have access to a mining pool) is 1 MB.

The TX size limits are really the first layer to place meaningful costs on “chain upload” applications, since applications have to start breaking “uploads” apart across multiple transactions. While possible, it’s usually not worth it – with the infrastructure and overhead the application would need to link transaction data together, it’s more worthwhile (and saves a lot of TX fees) to just serve the files directly, committing to hashes on chain.

So beyond the 100 KB transaction size relay/policy limit, we’re just dealing with applications that want absolute censorship resistance and are willing to pay a serious premium for it. That price is currently 1 satoshi/byte, i.e. the price of 1 BCH per 100 MB of chain space. All these other limits just place slight inefficiencies on those base sizes by requiring a few extra bytes here and there.

If anyone is seriously concerned about that price, we need to address it directly, not fiddle with inconsequential properties of the VM.

2 Likes

The P2SH scripts solve a very specific problem and have some benefits for some usecases. The specific problem is that it makes non-standard scripts fit in the payment protocol that is “address based”.
The added benefit is that in some cases the delayed exposing of a script is nice.

As we hopefully will soon get more attention to expanding payment protocols featuresets, one of the most often cited wishes is that we stop assuming an address and the payment protocol will allow the receiver to simply send over the outputs they want to include in the final transaction. Sending a full output (or more than one) enables many more usecases.

Most important such an approach allows us to use non-templated scripts in outputs in many more cases. Because p2sh already allows this implicitly, I doubt we’ll have a lot of problems getting this changed in the nodes policy to enable this, and open many more features.

This also paves the way to longer scripts that live in the tx-output and thus no longer have any problems with push limits since they no longer are pushed as a whole.

1 Like

I’m concerned that discussion of raising the size limit and relating that to multiple OP_RETURNs might cause resistance to multiple OP_RETURNs. I understand that this isn’t entirely rational but I’d be willing to bet that lesser informed people or people who just never want a single extra byte be allowed and view multiple OP_RETURN as a lever by which it might win consensus will just attack everything associated with it. My preference is that we leave discussion of tx size limits til after the May fork and that it be carried forward without reference to the multiple OP_RETURN CHIP.

1 Like

I would argue that this would be irresponsible, and that showing that there has been proper research into the topic and that as many of the edge-cases as possible has been taken into account is somewhat of a strict requirement.

Particulary so when you’re aiming to get something accepted on a very short time-frame. Avoiding the discussion in order to retain “popularity” is a strong warning signal to me, and one that - quite frankly - makes me want it to be excluded for may2021.

You’re reading me wrong. Your own comment on Mar 1 claims that these are independent issues and I quite agree - it’s important to keep them as independent issues. But when you present both at the same time it’s going to be a natural reaction to tie them together which would be a mistake.

Understand that we’re talking about two dimensions here: First, the formal specification dimension, which I think all of us here are comfortable with and can be dealt with in a completely rational manner, secondly, the human consensus aspect of getting people to agree with changes that impact them yet have all kinds of different agendas and don’t have either the time or the capacity to fully appreciate all the details of the first dimension.

The concern I’m talking about here is focused on the second dimension. And we ignore it to our great peril. Now I would immediately change my mind and take on the fight if there was a compelling reason under the first dimension to increase transaction sizes immediately. But I haven’t experienced this (and it was explicitly something I was investigating in my own research) and I haven’t seen the case made for it. My personal opinion is that increasing tx sizes is inevitable - once a compelling case that demonstrates it’s necessary for BCH’s progress. I’ll be the first to push forward for it. Just let’s not get ahead of ourselves and certainly don’t accidentally trip up other progress to compound the error.

1 Like

But there is a proposal (this thread) where there is an actual discussion abour raising the op_return sizes (the talk about how it was chosen in the first place, and if it should be increased as part of this proposal) and that does play out differently depending on how multiple OP_RETURN support is implemented.

Acknowledging that and making sure that the stakeholders are made aware of this is how we get the information required to take good decisions.

You said:

I’m concerned that discussion of raising the size limit and relating that to multiple OP_RETURNs might cause resistance to multiple OP_RETURNs. I understand that this isn’t entirely rational […]

The multiple OP_RETURN chip has an impact on how this is handled. The impact is positive, so there shouldn’t be a concern about this - it should be brought up and as many edge cases as possible should be considered by as many stakeholders as possible.

lesser informed people or people who just never want a single extra byte be allowed and view multiple OP_RETURN as a lever by which it might win consensus will just attack everything associated with it

Your role here is not to convince the lesser informed people to like or dislike your proposal. You should make sure that those impacted by the change have their needs considered and that the information necessary to take good decisions is publicly available and that there’s enough time to get edge-cases and details get properly worked out.

My preference is that we leave discussion of tx size limits til after the May fork

This is perfectly fine, but paired with it comes the natural conclusion that the multiple OP_RETURN chip should also wait until after the May 2021 fork.

I don’t see why you find this problematic - In my view you should just add a statement somewhere in the multiple opreturn chip to document how accepting the multiple op_return chip would interact with a future change to the opreturn byte limits: namely that by changing to an aggregate across op_return outputs, any changes to the limit would have the intended effect of restricting the correct amount of bytes, as chosen by such an upgrade.