作者:Raghav Sood
来源:https://raghavsood.com/blog/2018/06/10/bitcoin-signature-types-sighash
原文出版于 2018 年。
经常使用比特币,你可能会觉得签名是稀松平常的事。每次要发送比特币时,我就签个名,不就这么简单吗?它不就是用来保证不经我的许可,就没用人能动用我的比特币的吗?
这些话说得都对,但也都有点过度简化了。实际上,比特币的交易有多种签名方式,就是所谓的 “SIGHASH”。SIGHASH 是一个编码在交易的签名部分的字段。这许多种签名方式不仅使得用户能够花费比特币,还能协助免信任的销售、多方协作等更有创意的交易形式。本文就想分享一些关于这些签名方式、以及如何运用它们的知识。
我们以一笔单个输入、单个输入的 P2PKH(给公钥哈希值支付)为例子,是从本文撰写之时最近的一个区块选出来的:e251decc598148363669ae64cf0ec56f6049a0adc1eb42ac850f949aff0e6e55 。
这笔交易的签名数据总计 106 字节(包括 PUSH 操作码)。你可以通过 bitcoind 软件的 decoderawtransaction
方法把签名信息拉取下来,就在 vin>scriptSig
键值对中。
PUSH(0x47) # 71 bytes
304402203ff7162d6635246dbf59b7fa9e72e3023e959a73b1fbc51edbaaa5a8dbc6d2f
70220776e2fa5740df01cc0ac47bda713e87fc59044960122ba45abb11c949655c58401
PUSH(0x21) # 33 bytes
03bc3c9134f5a5e3f08287d175d7e43368f72cb93a2e6cbb801b5e90d1ed628e60
第一个 PUSH 后面的是实际的签名,而第二个 PUSH 后面的是地址 18w59Xd5ZXPsnx9A7buNgqmAvUNqn3cmUJ
的公钥,也即本交易的发送者的公钥。
比特币使用 DER 编码的签名,所以我们再把签名分解一下:
30 # DER 序列标签
44 # 序列的长度 0x44 (68) 字节
02 # 整数元素
20 # 元素长度 0x20 (32) 字节
3ff7162d6635246dbf59b7fa9e72e3023e959a73b1fbc51edbaaa5a8dbc6d2f7 # ECDSA r 值
02 # 整数元素
20 # 元素长度 0x20 (32) 字节
776e2fa5740df01cc0ac47bda713e87fc59044960122ba45abb11c949655c584 # ECDSA s 值
# DER 编码结束
01
在 DER 编码结束后(但仍属于签名数据)的 1 个字节,就是 SIGHASH
标签。
这个标签的值可以在 bitcoin core 软件代码库的 interpreter.h 文件中发现:
/** 签名的类型/标签 */
enum
{
SIGHASH_ALL = 1,
SIGHASH_NONE = 2,
SIGHASH_SINGLE = 3,
SIGHASH_ANYONECANPAY = 0x80,
};
虽然定义了 4 个值,但 SIGHASH_ANYONECANPAY
(英文字面意思为:任何人都可以 花)需要与上面的三个使用按位运算符 &
结合起来(在这里就相当于加号),所以总共有 6 种可能的值。
SIGHASH_ALL
(0x01
)—— 是我所知所有面向消费者的钱包软件的默认签名方式。它签名的对象包含所有的输入和输出,所以交易的数据有一丝一毫的改变,都会使签名无效(这个签名只能使这一个具体的交易生效)。它本质上就是说:“我只同意以这些输入和这些输出的组合转移我的比特币”。SIGHASH_NONE
(0x02
)—— 该方式下,签名的对象仅包括所有的输入,但不包含任何输出。所以,它的授权含义是:“我同意(使用这些资金)参与这样一笔交易,结果怎么样我不是特别在乎”。听起来似乎很不安全,而且不应该用在单输入的交易中。不过,我们后面会解释为什么要存在这样的签名方式。SIGHASH_SINGLE
(0x03
)—— 这种签名的对象包含所有的输入,以及一个相应的输出。这个输出应该跟你的签名放在同一个索引位置(即,如果你的输入是在 vin 0,那你要签名的输出也必须是 vout 0 位置的)。相当于是说:“我同意使用所有这些输入参与这笔交易,只要这么多钱是打到这个地址的”。SIGHASH_ALL | SIGHASH_ANYONECANPAY
(0x81
)—— 类似于SIGHASH_ALL
,也是签名所有的输出。不过,它只签名其中的一个输入。相当于是说:“我同意参与这笔交易,只要这些受益人能收到这些款项。至于谁愿意给这笔交易提供赞助(额外的输入),我没所谓”。SIGHASH_NONE | SIGHASH_ANYONECANPAY
(0x82
)—— 类似于SIGHASH_NONE
,但只为一个输入(这个签名所在的输入)提供许可。它本质上是说:“我愿意发送这笔比特币,不问缘由。实际上我甚至不在乎这笔钱是不是通过这笔交易来发送。这里是一个签名,表示任何包含了这个签名的交易都可以花费这笔资金”。SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
(0x83
)—— 类似于SIGHASH_SINGLE
,签名只针对会包含这个签名的输入,以及对应的输出。意思是:“我就是想移动这么多比特币到这个输出中,我不介意有没有人愿意跟我参与同一笔交易,也不介意交易有没有别的输出”。
现在,你肯定会好奇,为什么需要这么多签名类型。那我们就来举一些它们的用途的例子:
SIGHASH_ALL
—— 这个就很显然了,就是我们每天都在用的。花费特定的输入来创建特定的输出。SIGHASH_NONE
—— 这种方法更令人困惑。从事实上来看,这就像是你要把钱都烧掉一样,因为你没有签名任何输出。实际上,如果你创建一笔只有一个输入的交易,并以SIGHASH_NONE
签名,打包这笔交易的矿工可以直接改变输出,转到一个自己控制的地址。从设计上来看,它主要是用在多方贡献输入的情景中。那时候,这样的一个签名就是在说:“我同意使用我的这些资金,前提是所有其他人也都同意使用他们的”。也就是说,预计要有某个签名者使用SIGHASH_ALL
来保护这笔交易所有的输出、把资金发到一个大家都同意的输出集合中。SIGHASH_SINGLE
——可以用来处理这样一种情形:只有其他人正在发起一笔交易,你才愿意参与转账。本质上,你是同意转移一定数量的 BTC 到某个特定的输出中,前提是其他输入的参与者也愿意转移他们的 BTC。比如说,你和你的朋友要一起向某人支付 1.5 btc,你需要支付 1 btc。但是,你的朋友只有 1 btc 面额的输出。那么,你就可以使用SIGHASH_SINGLE
来创建一笔交易,把自己的 1 btc 作为输入,并把 1.5 btc 的输入安排给你们的支付对象。你的朋友可以(在添加 1 btc 输入之余)添加一个找零输出,并使用SIGHASH_ALL
或SIGHASH_SINGLE
(如果其找零输出可以与其输入使用同一个索引号的话)来完成这笔交易。SIGHASH_ALL | SIGHASH_ANYONECANPAY
—— 这个也是非常直接的。你本质上是在同意加入一笔交易,前提是一系列的接收者可以收到特定数量的比特币。这种情形跟我们在SIGHASH_SINGLE
中列举的有点像,只是其余的输入是任意的,而输出是固定的。SIGHASH_NONE | SIGHASH_ANYONECANPAY
—— 本质上是在高调宣传:“我的比特币就在这,想拿就拿”。它完全没有提供任何保障,所以可以用作燃烧证明(proof of burn)。这样签名的交易允许任何人添加输入;其它任何交易也可以使用如此签名的输入,产生任意的输出。创建这样的签名时,你是完全承诺愿意把这些比特币交出去,不带任何条件。SIGHASH_NONE
至少还包含了一个强制条件:给这笔交易提供了输出的其他人也必须把 BTC 交出去。SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
—— 这个组合非常有趣,允许我们实现链上的互换(比如染色币)。我觉得 Alex Mzrahi 提出的这个例子精彩绝伦,无有出其右者。本质上,这中签名方式使得 Alice(控制着 100 个 XX 币的人)可以签名一个输入,授权转移 100 个 XX 币,并要求向某个她控制的地址转移 1 btc。然后,她就可以将这笔不完整的、无效的交易(因为这笔交易没有面额 1 btc 的输入)发布出去。任何愿意拿 1 btc 购买 100 XX币的人都可以为这笔交易加入一个大于 1 btc 的输入,以及一个拿走 100 XX 币的输出(Alice 的输出只要求了比特币),以及找零输出(如果有需要的话)。这样它就成了一笔完整的交易,可以广播到网络中执行这场免信任的互换。
比特币上的签名类型,我们已经简要介绍完了。最后一点提醒。很有趣的是,SIGHASH_SINGLE
的实现中有一个 bug,首次披露是在 2012 年。如果被签名的输入的索引号超过了输出的数量,所生成的签名会是有效的,但不绑定任何输入。所以,最终的签名将不仅可以用于花费你正在签名的输出,还能用来花费这个地址上的任何资金!几天前,有人开启了一个代码更新(bitcoin#13360)来让这样的交易变成非标准化的,也正是这个 PR 促使我研究比特币的签名类型是如何工作的。
(完)