btcd源码解析 —— 签名机制(1) —— 基础知识

1. 写在前面

在前面的比特币源码分析中,我们有意忽略了签名机制部分的介绍。
但比特币的签名机制远非我们想象的那么简单,正如这篇博客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

2. 签名流程简述

2.1. 签名的数目

我们知道一笔交易可能包含若干笔input和若干笔output。针对其中的每一笔input,都需要构造一个签名。也就是说,签名是逐input构建的。有多少个input,就要构建多少个签名。
P2PKH类型的锁定脚本为例,其格式为

DUP HASH160 EQUALVERIFY CHECKSIG

为花费该交易,需要在input中构建如下格式的解锁脚本:

<sig> <public key>

其中即为签名。

2.2. 签名的对象

构建签名的过程一般包含两个阶段:1)构建用于签名的消息(message);2)使用私钥对该消息进行签名。
后者不是我们这里关注的问题,我们主要关注这个message是由哪些内容组成的。
回顾第1章节,其中提到的"signature hash flags"即是用来指定交易的哪些部分用来组成message.

3. 签名类型介绍

3.1. 基本类型

“签名类型”,更准确地称为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进行修饰。

3.2. 组合类型

因此,组合后的签名类型可能出现下面两大类(无修饰类和有修饰类)6种情况:

分类 组合类型 简单描述 用途
无修饰类 SIGHASH_ALL message中包含所有的inputoutput 默认类型,使用最为广泛
无修饰类 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.
更详细的内容,可以参考比特币开发指南.

3.3. 源码&字节码分析

3.3.1. 定义

btcd中和签名基本类型相关的定义如下所示:

// 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

3.3.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.

发布了51 篇原创文章 · 获赞 23 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/u014633283/article/details/104676657