1. 引言
Covenants契约是一种允许introspection自省的结构:
- 交易output可以对花费其的交易施加条件(超出特定的"必须提供自身的有效签名和特定的公钥")。
Rusty Russell 之前研究过Covenants: Examining ScriptPubkeys in Bitcoin Script,但契约想要强化的另一个有用的事情是金额。很容易实现相等,但需考虑允许合并输入的情况:
- 也许第一个output必须是第一个和第二个输入的总和。
问题是比特币脚本处理的是带符号的补码值,而 31 位限制了最多 21.47483648 个比特币。但是,使用OP_MULTISHA256
或OP_CAT
,可以处理全额。
2. 令人烦恼的金额问题
使用OP_TXHASH
,可在stack上获得 SHA256(输入金额)
和 SHA256(输出金额)
。由于这涉及哈希,因此除了相等之外,无法评估数字,因此在没有完全完整契约Covenants in Bitcoin: A Useful Review Taxonomy的其他情况下:
- 需要让用户提供实际值到witness stack上,
- 然后测试这些值来确定想要的条件,
- 然后确保它们与OP_TXHASH交易中的内容相符。
Rusty Russell通常反对这种向后的形式(只需给stack上的值!),但无论如何都无法原生使用源自 OP_TX 的64 位值(建议push两个值,尽管其有点ugly)。
3. 比特币脚本可以处理的价值形式
2100万个 BTC 略低于 2^51 聪。
将这些位分成一组堆栈值:
- lower:低24位
- upper:高位(27,但可最多允许到 31)
将此元组称为"脚本友好对"(Script-friendly pair, SFP) 形式。请注意,stack上的所有脚本数字均以little-endian表示,并带有符号位(最后一个字节为 0x80)。不幸的是,这是一种令人讨厌的格式。
4. 将SFP转换为 8 字节 Little-Endian 值
下面的代码采用positive CScriptNum,并生成2个stack值,可以将这2个stack连接起来形成一个 4 字节无符号值:
# !UNTESTED CODE!
# Stack (top to bottom): lower, upper
OP_SWAP
# Generate required prefix to append to stack value to make it 4 bytes long.
OP_SIZE
OP_DUP
OP_NOTIF
# 0 -> 00000000
OP_DROP
4 OP_PUSHDATA1 0x00 0x00 0x00 0x00
OP_ELSE
OP_DUP
1 OP_EQUAL OP_IF
# Single byte: prepend 0x00 0x00 0x00
OP_DROP
3 OP_PUSHDATA1 0x00 0x00 0x00
OP_ELSE
OP_DUP
2 OP_EQUAL OP_IF
# Two bytes: prepend 0x00 0x00
2 OP_PUSHDATA1 0x00 0x00
OP_ELSE
3 OP_EQUAL OP_IF
# Three bytes: prepend 0x00
1 OP_PUSHDATA1 0x00
OP_ELSE
# Prepend nothing.
0
OP_ENDIF
OP_ENDIF
OP_ENDIF
OP_ENDIF
OP_SWAP
# Stack (top to bottom): upper, pad, lower
这46个字节处理upper。现在lower是 0 到 16777215 之间的 CScriptNum,想要生成两个stack值,可将它们连接起来形成一个 3 字节无符号值。此时必须删除四字节情况下的零填充:
# !UNTESTED CODE!
# Stack (top to bottom): upper, pad, lower
OP_ROT
# Generate required prefix to append to stack value to make it 3 bytes long.
OP_SIZE
OP_DUP
OP_NOTIF
# 0 -> 000000
OP_DROP
3 OP_PUSHDATA1 0x00 0x00 0x00
OP_ELSE
OP_DUP
1 OP_EQUAL OP_IF
# Single byte: prepend 0x00 0x00
OP_DROP
2 OP_PUSHDATA1 0x00 0x00
OP_ELSE
OP_DUP
2 OP_EQUAL OP_IF
# Two bytes. Now maybe final byte is 0x00 simply so it doesn't
# appear negative, but we don't care.
1 OP_PUSHDATA1 0x00
OP_ELSE
# Three bytes: empty append below
3 OP_EQUAL OP_NOTIF
# Four bytes, e.g. 0xff 0xff 0xff 0x00
# Convert to three byte version: negate and add 2^23
# => 0xff 0xff 0xff
OP_NEG
4 OP_PUSHDATA1 0x00 0x00 0x80 0x00
OP_ADD
OP_ENDIF
# Prepend nothing.
0
OP_ENDIF
OP_ENDIF
OP_ENDIF
OP_SWAP
# Stack (top to bottom): lower, pad, upper, pad
可稍微优化这 47 个字节。
现在使用OP_MULTISHA256(或OP_CAT 3 次 和OP_SHA256) 将它们连接起来形成一个 8 字节的little-endian数,以便与OP_TXHASH所使用的格式进行比较。
基本上,95 个字节用于将元组与哈希值进行比较。
5. 添加两个SFP
编写一些代码来添加两个格式良好的SFP!
# !UNTESTED CODE!
# Stack (top to bottom): a_lower, a_upper, b_lower, b_upper
OP_ROT
OP_ADD
OP_DUP
4 OP_PUSHDATA1 0x00 0x00 0x00 0x01
OP_GREATERTHANOREQUAL
OP_IF
# lower overflow, bump upper.
# FIXME: We can OP_TUCK this constant above!
4 OP_PUSHDATA1 0x00 0x00 0x00 0x01
OP_SUB
OP_SWAP
OP_1ADD
OP_ELSE
OP_SWAP
OP_ENDIF
# Stack now: a_upper(w/carry), lower_sum, b_upper.
OP_ROT
OP_ADD
OP_SWAP
# Stack now: lower_sum, upper_sum
请注意,这 26 个字节不会检查upper是否溢出:若处理已验证的金额,甚至可以在可能之前添加 16 次(当然,对于不同的金额,这是不可能的)。不过,可在最后的OP_SWAP前,添加OP_DUP 0 OP_GREATERTHANOREQUAL OP_VERIFY
。
6. 检查SFP
上面的代码假设格式良好的pairs,但由于这些pairs源自witness stack,因此需要有一个routine来检查某pair是否格式良好:
# !UNTESTED CODE!
# Stack: lower, upper
OP_DUP
# lower must be 0 - 0xFFFFFF inclusive
0
4 OP_PUSHDATA1 0xFF 0xFF 0xFF 0x00
OP_WITHIN
OP_VERIFY
OP_OVER
# upper must be 0 - 0x7FFFFFF inclusive
0
4 OP_PUSHDATA1 0xFF 0xFF 0xFF 0x07
OP_WITHIN
OP_VERIFY
这确保了范围都在规格范围内:没有负数,没有巨大的数字。
7. 总结
虽然这表明OP_CAT/OP_MULTISHA256
足以处理脚本中的比特币金额,但其大小(约 250 字节以验证两个输入等于一个输出)为优化提供了相当引人注目的案例。
值得注意的是,这就是 Liquid 选择在比特币脚本中添加以下 64 位操作码的原因:
- OP_ADD64 ,
- OP_SUB64,
- OP_MUL64,
- OP_DIV64,
- OP_NEG64,
- OP_LESSTHAN64,
- OP_LESSTHANOREQUAL64,
- OP_GREATERTHAN64,
- OP_GREATERTHANOREQUAL64。
【Liquid团队还重新启用了bitwise操作码(OP_XOR等)以使其能够很好地工作。还实现了OP_SCRIPTNUMTOLE64、OP_LE64TOSCRIPTNUM和OP_LE32TOLE64进行转换。】
在Rusty Russell之前的文章中,建议OP_LESS适用于任意值,但不适用于这些值,因为endian字节序是错误的!至少,需要添加OP_LESSTHAN64、OP_ADD64和OP_NEG64来允许 64 位比较、加法和减法。
但是,仅使用OP_CAT或OP_MULTISHA256,就可以处理金额。只是不够优美!
参考资料
[1] Blockstream团队 Rusty Russell 2023年10月22日博客 Covenants: Dealing with Amounts in Bitcoin Script