Pushdata 的“最小推送”规则

在 BSV 脚本中,将数据 0x01 送到栈上,似乎有两种做法:

  1. 直接使用操作码 OP_1,对应的脚本为 { 0x51 }。根据定义,OP_1 会将 0x01 送到栈顶
  2. 使用 pushdata 的方式,对应的脚本为 { 0x01 0x01 }。第一个 0x01 表示接下来的 1 字节数据(第二个 0x01)会被送到栈上

实际上,[2] 是错误的,因为它违反了“最小推送”(minimal push)规则。

  • 推送空的字节序列,必须使用操作码 OP_0,即 { 0x00 }
  • 推送 0x01 ~ 0x10,必须使用操作码 OP_1 ~ OP_16,即 { 0x51 } ~ { 0x60 }
  • 推送 0x81,必须使用操作码 OP_1NEGATE,即 { 0x4f }
  • 推送其他数据,根据定义使用对应的 pushdata 操作码

这个规则很简单,可以用一句话概括:针对 18 种要压栈的数据,特定的操作码让我们能在脚本里用更短的指令(1 字节)表达动作,不再需要使用 pushdata 的方式(2 字节)。

如果违反该规则,节点会报错:

64: non-mandatory-script-verify-flag (Data push larger than necessary)

请注意,推送 0x00 不在这 18 个特例范围内,所以正确的脚本应该是 { 0x01 0x00 }。

另一个要注意的点是,在 OP_RETURN 输出里推送数据不受此规则限制,因为 OP_RETURN 操作码之后的脚本不会被执行。

特别感谢 @xhliu 的指点和帮助,其他参考资料如下:

代码实现

def encode_pushdata(pushdata: bytes, minimal_push: bool = True) -> bytes:
"""encode pushdata with proper opcode
https://github.com/bitcoin-sv/bitcoin-sv/blob/v1.0.10/src/script/interpreter.cpp#L310-L337
:param pushdata: bytes you want to push onto the stack in bitcoin script
:param minimal_push: if True then push data following the minimal push rule
"""
if minimal_push:
if pushdata == b'':
return OP.OP_0
if len(pushdata) == 1 and 1 <= pushdata[0] <= 16:
return bytes([OP.OP_1[0] + pushdata[0] - 1])
if len(pushdata) == 1 and pushdata[0] == 0x81:
return OP.OP_1NEGATE
else:
# non-minimal push requires pushdata != b''
assert pushdata, 'empty pushdata'
return get_pushdata_code(len(pushdata)) + pushdata