Multi-byte opcodes

115 views
Skip to first unread message

Weikeng Chen

unread,
Nov 15, 2024, 9:00:53 PM (6 days ago) Nov 15
to Bitcoin Development Mailing List
I think we need a way to allow more opcodes without taking up the rest of the NOPs.

This is related to a point from Murch (https://groups.google.com/g/bitcoindev/c/usHmnXDuJQc/m/hhtvAjSdCgAJ) that the reasoning of "its' compatible, why not" for adding CHECKSIGFROMSTACK(VERIFY/ADD) is not solid because when we add a new opcode, we usually have to give up a NOP. We do not have many NOPs left.

We can, however, solve that by allowing multi-byte opcodes.

Say, for example, we can have:
    OP_OP { 0x1521 }
which will set the current opcode to be the one with the assigned number 0x1521.

Another idea is maybe OP_OP takes a stack element as the opcode.
    { 0x1521 } OP_OP

We can enforce some sort of minimal rule, or not do so, to allow more flexible use of existing opcodes.

This, of course, runs at a cost as this opcode needs three bytes in total to represent, but since the existing opcodes already take care of most of the basic functionalities that we expect users to use very frequently, the new opcodes that we want to add are likely those that complete something important and are going to be used only a few times in a script.

Similarly, we can require that multi-byte opcodes that have not been enabled my result in OP_SUCCESS.

OP_OP is not the best name as it could be confusing. OP_SETOP, OP_NEXT, etc could be taken into consideration.

The result of this is that we can worry less about whether it is worthy of a NOP to do an opcode, but focus on if the opcode has enough use cases to support it.

I feel that someone must have brought this up before (but it is a little bit hard to find the history in this mailing list at this moment).

What do people think?

Thanks,
Weikeng

Garlo Nicon

unread,
Nov 18, 2024, 1:36:29 PM (3 days ago) Nov 18
to Bitcoin Development Mailing List
> I think we need a way to allow more opcodes without taking up the rest of the NOPs.

It is already possible since Taproot. For example: OP_CHECKSIGADD was added, without replacing any OP_NOP.


> I feel that someone must have brought this up before (but it is a little bit hard to find the history in this mailing list at this moment).

Satoshi added OP_SINGLEBYTE_END, set to 0xf0, and OP_DOUBLEBYTE_BEGIN, set to 0xf000. It was removed later, but it can be reintroduced in a similar way, if needed. See source code for version 0.1.0 for more details.

Brandon Black

unread,
Nov 18, 2024, 1:36:42 PM (3 days ago) Nov 18
to Weikeng Chen, Bitcoin Development Mailing List
Hi Weikeng, thanks for your thoughts on this!

> We can, however, solve that by allowing multi-byte opcodes.
>
> Say, for example, we can have:
> OP_OP { 0x1521 }
> which will set the current opcode to be the one with the assigned number
> 0x1521.
>
> Another idea is maybe OP_OP takes a stack element as the opcode.
> { 0x1521 } OP_OP

Another option that works for many cases is to have opcode families
where an argument is augmented with flags to determine the behavior. We
can consider this to already be the case for OP_CHECKSIG* where the
signature determines the behavior of the hashing portion of the opcode.

This is also how OP_CHECKTEMPLATEVERIFY is designed, and how
OP_CHECKSIGFROMSTACKVERIFY as currently spec'd in the PR is designed.
CTV and CSFSV only constrain 32-byte first arguments, but not other
lengths leaving open extensions using any other length, including using
other lengths of either opcode as OP_OP, or as variants on CTV and CSFSV
respectively.

The benefit of this approach is that it doesn't "waste" the length byte
only to specify the opcode behavior, but enables it to do double duty as
specifying the total length of the first argument including both flags
and data.

Best,

--Brandon

Ethan Heilman

unread,
Nov 18, 2024, 2:26:58 PM (3 days ago) Nov 18
to Brandon Black, Weikeng Chen, Bitcoin Development Mailing List
Couldn't we add opcodes contexts via the script version?

By context I mean an opcode that loads a 1 to N byte map rewriting the mapping from opcode number to what instruction that number signifies. This should let you have an infinite number of instructions. You could change the context multiple times in a script as a cost of only two or three bytes.



--
You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/Zzt2OCE6Aj9H3DiY%40console.

moonsettler

unread,
Nov 19, 2024, 12:36:30 PM (2 days ago) Nov 19
to Brandon Black, Weikeng Chen, Bitcoin Development Mailing List
Hi All,

I think we should discuss back-porting tapscript instead of coming up with a further divergent set of opcodes.

To sum up earlier discussion with Brandon, we could:

* Use a single upgradeable NOP for OP_TAPSCRIPTVERIFY
* <s1> .. <sn> | <faux-control-block> <tapscript> CTAPV
* Isolated execution environment
* Gets the entire stack below the top 2 elements
* Fails if it fails, does nothing if internal script succeeds.
* Require the last opcode executed by the script interpreter to be a push opcode, fails otherwise

The faux control block can be just a few bytes mainly for tapscript version.

BR,
moonsettler

Sent with Proton Mail secure email.

Brandon Black

unread,
Nov 19, 2024, 3:24:35 PM (2 days ago) Nov 19
to moonsettler, Weikeng Chen, Bitcoin Development Mailing List
On 2024-11-19 (Tue) at 16:38:21 +0000, moonsettler wrote:
> I think we should discuss back-porting tapscript instead of coming up with a further divergent set of opcodes.
>
> To sum up earlier discussion with Brandon, we could:
>
> * Use a single upgradeable NOP for OP_TAPSCRIPTVERIFY
> * <s1> .. <sn> | <faux-control-block> <tapscript> CTAPV
> * Isolated execution environment
> * Gets the entire stack below the top 2 elements
> * Fails if it fails, does nothing if internal script succeeds.
> * Require the last opcode executed by the script interpreter to be a push opcode, fails otherwise
>
> The faux control block can be just a few bytes mainly for tapscript version.

Few additional points that came up in discussing this with moon earlier:

* Compared to enabling tapscript without a key spend path (i.e.
potential for quantum resistance) via a new witnessv1 program length
or a new witness version, this approach will be similar in weight.
* The biggest challenge with this approach that I came up with is the
need to clean up the stack after execution (to satisfy witness v0
clean stack).
* To keep it simple, it probably makes sense to make it a 1-primary
argument opcode where the argument is `<tapscript||version>` to
prevent the version from coming from spend-time witness elements.

Best,

--Brandon
Reply all
Reply to author
Forward
0 new messages