签名过程主要是对交易进行签名,故首先需要了解BTC的交易结构组成。
一、普通交易结构
字节 | 字段 | 描述 |
---|---|---|
4 | 版本 | 明确这笔交易参照的规则 |
1-9 | 输入计数器 | 包含的交易输入数量 |
不定 | 输入【见普通交易输入】 | 一个或多个交易输入 |
1-9 | 输出计数器 | 包含的交易输出数量 |
不定 | 输出【见普通交易输出】 | 一个或多个交易输出 |
4 | 锁定时间 | 一个区块号或UNIX时间戳 |
普通交易输入
字节 | 字段 | 描述 |
---|---|---|
32 | 交易哈希值 | 指向被花费的UTXO所在的交易的哈希指针 |
4 | 输出索引 | 被花费的UTXO的索引号,第一个是0 |
1-9 | 解锁脚本大小 | 用字节表示的后面的解锁脚本长度 |
不定 | 解锁脚本 | 满足UTXO解锁脚本条件的脚本 |
4 | 序列号 | 目前未被使用的交易替换功能,设为0xFFFFFFFF |
普通交易输出
字节 | 字段 | 描述 |
---|---|---|
8 | 总量 | 用聪表示的比特币值 |
1-9 | 锁定脚本大小 | 用字节表示的后面的锁定脚本长度 |
不定 | 锁定脚本 | 一个定义了支付输出所需条件的脚本 |
二、构建原始交易RawTransaction
现在假设有一个地址S_ADD_001,该地址收到了两笔转账,一笔交易ID为TX001,金额0.4BTC,另一笔交易ID为TX002,金额1.1BTC,这两笔收入都是在其交易Output的第0条,也就是Index=0。现在我们想要做一笔1.2BTC的转账[收款人的地址R_ADD_001],然后给一定的手续费后,找零到原地址,所以我们会构建一笔交易,该交易有2Input和2Output。构造如下:
既有的UTXO输出结构
UTXO |
TxHash:TX001, OutIndex:0, Amount:0.4BTC, PkScript:【锁定脚本 L1】 OP_DUP OP_HASH160 PUSHDATA(20)S_ADD_001 OP_EQUALVERIFY OP_CHECKSIG |
TxHash:TX002, OutIndex:0, Amount:1.1BTC, PkScript:【锁定脚本】 OP_DUP OP_HASH160 PUSHDATA(20)S_ADD_001 OP_EQUALVERIFY OP_CHECKSIG |
锁定脚本的S_ADD_001位置,往往是公钥或者公钥的HASH值【BTC地址】
构造原始交易[命名为T]:
input | output |
PreviousOutPoint={ TxHash:TX001, OutIndex:0} SignatureScript =NULL,Sequence =0xFFFFFFFF | Value=29910240 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)S_ADD_001OP_EQUALVERIFY OP_CHECKSIG |
PreviousOutPoint={ TxHash:TX002, OutIndex:0} SignatureScript =NULL,Sequence =0xFFFFFFFF | Value=120000000 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)R_ADD_002 OP_EQUALVERIFY OP_CHECKSIG |
三、签名过程
在比特币中,对交易T的签名流程是这样的:
1.查找交易T对应的UTXO
2.获得该UTXO对应的锁定脚本
3.复制该交易T对象,并在复制副本中将该Input的解锁脚本字段的值设置为对应的锁定脚本
4.清除其他Input的解锁脚本字段
5.对这个改造后的交易对象计算SHA-256值,
6.使用私钥对SHA-256值进行签名。
四、示例
现在需要用私钥,对该交易进行签名,因为有2个Input,所以需要进行签名2次,每个签名的原理是一样的,以第一个Input例来说明。
1、交易T复制一个副本,并改为如下形式,该副本命名为T_C
input | output |
PreviousOutPoint={ TxHash:TX001, OutIndex:0} SignatureScript =OP_DUP OP_HASH160 PUSHDATA(20) S_ADD_001 OP_EQUALVERIFY OP_CHECKSIG,Sequence =0xFFFFFFFF | Value=29910240 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)S_ADD_001OP_EQUALVERIFY OP_CHECKSIG |
PreviousOutPoint={ TxHash:TX002, OutIndex:0} SignatureScript =NULL,Sequence =0xFFFFFFFF | Value=120000000 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)R_ADD_002 OP_EQUALVERIFY OP_CHECKSIG |
绿色背景内容来自交易TX001的UTXO上的锁定脚本值,数据直接拷贝过来
2、对T_C序列化后做SHA_256得M值【256位】
3、用私钥对M进行签名,得签名S1
4、对输入1签名之后结果如下
input | output |
PreviousOutPoint={ TxHash:TX001, OutIndex:0} SignatureScript = PUSHDATA(72)[签名S1] PUSHDATA(33)[公钥值] ,Sequence =0xFFFFFFFF | Value=29910240 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)S_ADD_001OP_EQUALVERIFY OP_CHECKSIG |
PreviousOutPoint={ TxHash:TX002, OutIndex:0} SignatureScript =NULL,Sequence =0xFFFFFFFF | Value=120000000 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)R_ADD_002 OP_EQUALVERIFY OP_CHECKSIG |
5、对输入2进行签名
同样的道理,制造一个交易的副本,然后把第一个Input的SignatureScript清空,然后给第二个Input的SignatureScript赋值:
input | output |
PreviousOutPoint={ TxHash:TX001, OutIndex:0} SignatureScript =NULL,Sequence =0xFFFFFFFF | Value=29910240 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)S_ADD_001OP_EQUALVERIFY OP_CHECKSIG |
PreviousOutPoint={ TxHash:TX002, OutIndex:0} SignatureScript =OP_DUP OP_HASH160 PUSHDATA(20) S_ADD_001 OP_EQUALVERIFY OP_CHECKSIG,Sequence =0xFFFFFFFF | Value=120000000 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)R_ADD_002 OP_EQUALVERIFY OP_CHECKSIG |
显然这个副本与第一个签名时的数据【数据位置不同,同时可能锁定脚本可能不同】是不一样的,所以签名结果也不一样。设输入2的签名为S2
6、组装完整交易:
将S2签名和公钥再放回原始交易中,就变成需要的完整签名的交易:
PreviousOutPoint={ TxHash:TX001, OutIndex:0} SignatureScript = PUSHDATA(72)[签名S1] PUSHDATA(33)[公钥值] ,Sequence =0xFFFFFFFF | Value=29910240 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)S_ADD_001OP_EQUALVERIFY OP_CHECKSIG |
PreviousOutPoint={ TxHash:TX002, OutIndex:0} SignatureScript =PUSHDATA(72)[签名S2] PUSHDATA(33)[公钥值],Sequence =0xFFFFFFFF | Value=120000000 PkScript= OP_DUP OP_HASH160 PUSHDATA(20)R_ADD_002 OP_EQUALVERIFY OP_CHECKSIG |
接下来就可以通过P2P网络发送该交易,并最终被矿工打包确认。