在前面的比特币源码分析中,我们有意忽略了签名机制部分的介绍。 但比特币的签名机制远非我们想象的那么简单,正如这篇博客Bitcoin contracts所言:
Bitcoin achieves high flexibility due to three features: 1. scripts — unlocking funds in transactions is done using a simple scripting language, 2. signature hash flags — indicate which parts of the transaction are signed, 3. sequence numbers and lock time — mark transactions as not valid until specified time.
换句话说,正是因为不同签名格式(signature hash flags
)的存在,才使得比特币的编程功能更加灵活多变。 接下来的三篇博客即从原理和源码两个角度来分析比特币的签名机制。 本篇博客先介绍基础知识,第二篇博客介绍签名流程的基本原理,第三篇博客从btcd
源码层面讲解签名流程。
源码分析所针对的btcd
版本为: ed77733ec07dfc8a513741138419b8d9d3de9d2d
I. 签名流程简述
A. 签名的数目
我们知道一笔交易可能包含若干笔input
和若干笔output
。针对其中的每一笔input
,都需要构造一个签名。也就是说,签名是逐input
构建的。有多少个input
,就要构建多少个签名。 以P2PKH
类型的锁定脚本为例,其格式为
DUP HASH160 <public key hash> EQUALVERIFY CHECKSIG
为花费该交易,需要在input
中构建如下格式的解锁脚本:
<sig> <public key>
其中<sig>即为签名。
B. 签名的对象
构建签名的过程一般包含两个阶段:1)构建用于签名的消息(message
);2)使用私钥对该消息进行签名。 后者不是我们这里关注的问题,我们主要关注这个message
是由哪些内容组成的。 回顾博客开头,其中提到的"signature hash flags"即是用来指定交易的哪些部分用来组成message
.
II. 签名类型介绍
A. 基本类型
“签名类型”,更准确地称为Signature Hash Types,主要包括4种基本类型:SIGHASH_ALL
, SIGHASH_NONE
, SIGHASH_SINGLE
, SIGHASH_ANYONECANPAY
. 4种类型的简述如下表所示:
基本类型 | 简单描述 | 用途 |
---|---|---|
SIGHASH_ALL |
message 中包含所有的output |
默认类型,使用最为广泛 |
SIGHASH_NONE |
message 中不包含任何output |
允许任何人通过构建output 来花费这笔钱 |
SIGHASH_SINGLE |
message 中包含特定 (对应input 的) output |
保证自己的output 不被篡改,但允许其他人的output 被改动 |
SIGHASH_ANYONECANPAY |
message 中包含当前input |
允许任何人构建input ,也即允许任何人往交易里面输入金额 |
容易发现,前三者 (SIGHASH_ALL
, SIGHASH_NONE
, SIGHASH_SINGLE
)主要是用来对output
进行限制,而SIGHASH_ANYONECANPAY
是对input
进行限制。在任何一次签名中,需要指定前三者中的一项,(可选择地)搭配使用SIGHASH_ANYONECANPAY
进行修饰。
B. 组合类型
因此,组合后的签名类型可能出现下面两大类(无修饰类和有修饰类)6种情况:
分类 | 组合类型 | 简单描述 | 用途 |
---|---|---|---|
无修饰类 | SIGHASH_ALL |
message 中包含所有的input 和output |
默认类型,使用最为广泛 |
无修饰类 | SIGHASH_NONE |
message 中包含所有的input ,不包含任何output |
允许任何人通过构建output 来花费这笔钱 |
无修饰类 | SIGHASH_SINGLE |
message 中包含所有input 和特定 (对应input 的) output |
保证自己的output 不被篡改,但允许其他人的output 被改动 |
有修饰类 | SIGHASH_ALL | SIGHASH_ANYONECANPAY |
message 中包含所有output 和当前input |
常被用来做资金“众筹” |
有修饰类 | SIGHASH_NONE | SIGHASH_ANYONECANPAY |
message 中只包含当前input ,不包含任何output |
允许任何人输入金额,也允许任何人花费金额 |
有修饰类 | SIGHASH_SINGLE | SIGHASH_ANYONECANPAY |
message 中只包含当前input ,和对应的output |
允许任何人输入金额,也保证对应的output 不被篡改 |
容易看出,无修饰类的message
是包含了所有input
;而有修饰类的message
只包含了当前input
. 更详细的内容,可以参考比特币开发指南.
C. 源码&字节码分析
1. 定义
btcd
中和签名基本类型相关的定义如下所示: 1
2
3
4
5
6
7
8
9
10
11
12
13
14// script.go
type SigHashType uint32
const (
SigHashOld SigHashType = 0x0
SigHashAll SigHashType = 0x1
SigHashNone SigHashType = 0x2
SigHashSingle SigHashType = 0x3
SigHashAnyOneCanPay SigHashType = 0x80
// sigHashMask defines the number of bits of the hash type which is used
// to identify which outputs are signed.
sigHashMask = 0x1f
)SigHashOld
是弃用的类型了,其和SigHashAll
功能完全相同。 可见四种基本类型都只占用一个字节。SIGHASH_ALL
, SIGHASH_NONE
, SIGHASH_SINGLE
可与SigHashAnyOneCanPay
进行位操作,从而实现组合类型。不同组合类型和字节码如下所示: |分类| 组合类型 | 字节码 | |---|---|---| |无修饰类|SIGHASH_ALL
|0x01
| |无修饰类|SIGHASH_NONE
| 0x02
| |无修饰类|SIGHASH_SINGLE
| 0x03
| |有修饰类|SIGHASH_ALL | SIGHASH_ANYONECANPAY
| 0x81
| |有修饰类| SIGHASH_NONE | SIGHASH_ANYONECANPAY
| 0x82
| |有修饰类| SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
| 0x83
|
2. 举例
我们以比特币主网上的交易737ec67db90553cf2c3fda6e241e7c4d759ee7636d877f7c24017ecbd62b5792
为例,介绍一下签名类型字节码在交易中的位置。 从比特币浏览器BTC.com中容易查询到该交易的输入输出和输入脚本分别如下所示:
其中每一笔输入的输入脚本都是用来解锁一笔
P2PKH
输出的,且签名类型都是SIGHASH_ALL | SIGHASH_ANYONECANPAY
. 以第一笔input为例,其输入脚本可被解析如下:
字节码 | 含义 |
---|---|
47 | OP_PUSHDATA47 |
30 | 表示DER 序列的开始 |
44 | 序列的长度(68字节) |
02 | 表示接下来是一个整数值 |
20 | 表示接下来这个整数的长度 |
207ab184b288275c6466075d724a9f632487b6996489d49c8148cf30f9d5fc1a | DER 编码中的R 值 |
02 | 表示接下来是一个整数值 |
20 | 表示接下来这个整数的长度 |
518b62471a25f8da86d0184de5b0496d4c2b272115c84fa58ea32e568b53053d | DER 编码中的S 值 |
81 | 表示签名类型 |
21 | OP_PUSHDATA21 |
032153889b1813175f337f24944ff632a6b9a78b63e09ff8c6a569f5d5d429cf97 | 公钥 |
关于签名数据的解析,可以结合博客理解比特币的raw transaction (1) P2PKH类型输出和电子书《Mastering Bitcoin (2nd Edition)》Serialization of signatures (DER)部分,深入理解。 重点关注表格中加粗的“81”,其表示当前的签名类型是SIGHASH_ALL | SIGHASH_ANYONECANPAY
.