0

EDIT: Pieter Wuille gave an answer (look at the comments) to the last situation (with OP_CHECKSIG). OP_CHECKSIG is pushing 0x00.

E.g. OP_0 pushes the empty vector, which represents the number 0 (to numeric opcodesk).



Probably the lowest-IQ question ever, but what does empty vector represent in Bitcoin? Is it a single byte 0x00 (value 0) or is it literally nothing ("air") or maybe both depending on the situation?

In BIP342, the following is written for OP_IF and OF_NOTIF :

Consensus-enforced MINIMALIF The MINIMALIF rules, which are only a standardness rule in P2WSH, are consensus enforced in tapscript. This means that the input argument to the OP_IF and OP_NOTIF opcodes must be either exactly 0 (the empty vector) or exactly 1 (the one-byte vector with value 1).

I assume that here empty vector represents literally byte 0x00 (value 0) on the stack, thus it is not an "air" (nothing). I have that assumption because if the empty vector here represents an "air" (nothing) then it would require totally empty stack to perform these opcodes.

In the same BIP, for signature opcodes (OP_CHECKSIGADD, OP_CHECKSIGVERIFY and OP_CHECKSIG) and signature check it says that if the signature is an empty vector then different behaviors are performed than in pre-Taproot. I assume that here empty vector literally represents an "air", i.e. the witness item with length 0.

At the end, in the BIP342, it writes that if the signature is an empty vector ("air"; witness item with the length 0), then OP_CHECKSIG will push an empty vector onto the stack and execution continues with the next opcode. I assume that the empty vector in the case of opcodes (OP_CHECKSIG in this case) also represent byte 0x00 (value 0), since there is no point to push literally nothing ("air") onto the stack.

  1. Am I right or wrong? Does empty vector actually represents the (3) option from the first paragraph, thus in case of stack it represents 0x00 and in case of witness stack it represents item with the length of 0? If not, can you tell me what the empty vector represents.
  2. Would a witness item of length 1 and value 0x00 also be considered an empty vector so that the signature check also would not be performed in the case of Tapscript?

1 Answer 1

4

I think we first need to distinguish between the script stack as an abstract concept as it exists during script execution, and the initial stack serialization in the transaction input witness.

The stack, during execution, is a vector of vectors of bytes. For example, we could have a stack that consists of 3 vectors, the first being empty, the second being a single 0 byte, and the third consisting of 4 0xba bytes. We'll denote that as:

{{}, {0x00}, {0xba, 0xba, 0xba, 0xba}}

These would be serialized in the transaction input witness as {0x00, 0x01, 0x00, 0x04, 0xba, 0xba, 0xba, 0xba}. So when you're asking about 0x00, it depends on whether you're taking about the stack elements, or their serialization. An empty stack element is empty, length 0, no contents - but its serialization is a 0x00 byte.

Executing an OP_0 (encoded as 0x00) pushes onto the stack a empty byte vector, so starting from the previous example, we'd get:

{{}, {0x00}, {0xba, 0xba, 0xba, 0xba}, {}}

The script OP_3 (encoded as 0x53) pushes the 1-byte stack element {0x03} onto the stack:

{{}, {0x00}, {0xba, 0xba, 0xba, 0xba}, {}, {0x03}}

On the other hand, the script <0x00> (encoded itself as {0x01, 0x00} pushes the 1-byte value {0x00}:

{{}, {0x00}, {0xba, 0xba, 0xba, 0xba}, {}, {0x03}, {0x00}}

For the purpose of OP_CHECKSIG and OP_CHECKSIGADD, a signature equal to {} is special: this is the only "invalid" signature allowed. Any other signature stack element which doesn't actually verify correctly causes the script execution to abort immediately. An {} signature element simply causes a 0 (= {}) to be pushed onto the stack for OP_CHECKSIG, and no increment to occur for OP_CHECKSIGADD.

These rules do not apply to the stack element {0x00}, or any other value. The stack element has to be {} exactly. But again, that stack element can appear by being present in the witness stack, where it'd be serialized as {0x00}, or can be pushed using OP_0 inside script (which is encoded as 0x00 as well).

13
  • Wow, what an answer, amazing. Have 2 questions. 1) An {} signature element simply causes a 0 (= {}) to be pushed onto the stack for OP_CHECKSIG..., is it more accurate to say empty vector instead of 0 since 0 can also mean to push literally zero like {0x00}? 2) But again, that stack element can appear by being present in the witness stack, where it'd be serialized as {0x00} by this you mean that the witness item is of length 0?
    – Cosmos
    Nov 25, 2023 at 3:06
  • 2
    (1) They're equivalent; an empty vector is how in the script language the number 0 is represented (as it's also what OP_0 pushed), so they can be used interchangeably. (2) Yes, the serialization of a 0-length stack element is the byte 0x00. (3) Indeed, empty stack elements are very much not ignored. The witness stack serialization {0x00, 0x00, 0x00} indeed represents an initial stack of 3 empty vectors. Nov 25, 2023 at 3:56
  • 1
    Whenever a stack element is treated as a boolean (e.g for OP_BOOLAND), any byte vector that contains a nonzero byte is treated as "true", while others are treated as "false" (including {}, {0x00}, {0x00, 0x00}, ...) But when we say an opcode "pushes false", it is always {} because the shortest encoding is preferred. Nov 25, 2023 at 13:05
  • 1
    That's an error. Anything that needs more stack elements than are available causes script execution to abort and treat the transaction as invalid. Nov 25, 2023 at 13:16
  • 1
    Indeed, the MINIMALIF rule (consensus in tapscript, a standardness rule for other scripts) requires the input to an OP_IF or OP_NOTIF to be exactly {}, or exactly {0x01}. Nov 25, 2023 at 14:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.