Building a Bitcoin P2PKH Tx by Hand

Table of Contents

1. 背景介绍

直接使用 BTC 钱包往外转一笔帐是很简单的,这个过程隐藏了很多的细节。为了更细致地了解 BTC Tx 的细节,本文将介绍如何“纯手工地”构造一个 Pay-to-Pub-Key Hash (P2PKH) Tx。

本文大部分内容都是在讲构造 Tx 的细节,在阅读本文之前,读者需要对 BTC Tx、UTXO、P2PKH 等概念比较熟悉。

1.1. 任务描述

在比特币主网的 Tx 8ab9dd72d22337458af8285c70e7e65916893f8691daf6fcfb9dde70a39fdf10 中,往地址 1MThTn1XmUYcUtBs7GPdUBB7cmZWr2BPft 转入了 0.00001000 BTC。本文将介绍如何构造一个新 Tx,其功能是把其中 0.00000552 BTC 转给另一个地址 1DRVinkHyDWruJHGyqFSGbzsLbYDmGiiTs(两个数量的差值 0.00000448 BTC 就是矿工费用),这个新 Tx 目前也已经在主网上存在了,参见 Tx c6e81c4a315d7eed40cb32b2558f4b142d37959f7e8eb3eb5c43fdf2931cca42 ,本文就是介绍如何一步步地手工构造该 Tx。

1.2. 演示帐户

为了更完整地演示过程,下面交待一下地址 1MThTn1XmUYcUtBs7GPdUBB7cmZWr2BPft 对应的私钥和公钥:

Private Key (Hex): ab6f3e0f87efdd79d99be7caa9f91c2bcca32be13e6b5f9affd4d84894279e2c
Private Key (WIF): 5K7neTxmszArk8yxRgtrkPy21GpfkHfMr13bZkuUSknEKV1k2wn
Compressed Public Key: 02b6b6509ee40cb6947af267d3cd7da306dbb4fb2691fc30c1e76ede0b3e533980
Uncompressed Public Key: 04b6b6509ee40cb6947af267d3cd7da306dbb4fb2691fc30c1e76ede0b3e533980c87e8a4d382f80b2338fcb15460af964428662aa144e02e52047637e7900b18c
Uncompressed Address: 1MThTn1XmUYcUtBs7GPdUBB7cmZWr2BPft

2. Tx 序列化格式

调用 RPC 接口 sendrawtransaction 把序列化后的 Tx 提交给节点,这就是向 BTC 网络提交 Tx 的方法。序化化后的 Tx 是什么格式呢?本节将介绍它。

2.1. Tx Raw Format

序列化后的 Tx 是一串 Hex String,下面就是我们本文最终想要构造出来的序列化后 Tx:

010000000110df9fa370de9dfbfcf6da91863f891659e6e7705c28f88a453723d272ddb98a000000008a47304402205aaaa23d7ba69eb6d233cd31ab242d09b6ab1a325b8e406e55d9e5575832734102204eec8e7669241249b4f597d9dfe34a9a25cc61f0f55f1c63b8f22df47371836d014104b6b6509ee40cb6947af267d3cd7da306dbb4fb2691fc30c1e76ede0b3e533980c87e8a4d382f80b2338fcb15460af964428662aa144e02e52047637e7900b18cfeffffff0128020000000000001976a914884377972c0df175fc1ef878a490d701c0fd195e88ac18090800

上面的数据可以分解为下面这种更清晰的表达方式:

+--------------------------------+-------------------------------------------------+
| Version                        | 01 00 00 00                                     |
+--------------------------------+-------------------------------------------------+
| Number of inputs               | 01                                              |
+--------+-----------------------+-------------------------------------------------+
|        | Previous output hash  | 10 df 9f a3 70 de 9d fb fc f6 da 91 86 3f 89 16 |
|        | (reversed)            | 59 e6 e7 70 5c 28 f8 8a 45 37 23 d2 72 dd b9 8a |
|        +-----------------------+-------------------------------------------------+
|        | Previous output index | 00 00 00 00                                     |
|        +-----------------------+-------------------------------------------------+
|        | Script length         | 8a                                              |
|        +-----------------------+-------------------------------------------------+
|        |                       | 47 30 44 02 20 5a aa a2 3d 7b a6 9e b6 d2 33 cd |
|        |                       | 31 ab 24 2d 09 b6 ab 1a 32 5b 8e 40 6e 55 d9 e5 |
| Input  |                       | 57 58 32 73 41 02 20 4e ec 8e 76 69 24 12 49 b4 |
|        | ScriptSig             | f5 97 d9 df e3 4a 9a 25 cc 61 f0 f5 5f 1c 63 b8 |
|        | (Unlocking Script)    | f2 2d f4 73 71 83 6d 01 41 04 b6 b6 50 9e e4 0c |
|        |                       | b6 94 7a f2 67 d3 cd 7d a3 06 db b4 fb 26 91 fc |
|        |                       | 30 c1 e7 6e de 0b 3e 53 39 80 c8 7e 8a 4d 38 2f |
|        |                       | 80 b2 33 8f cb 15 46 0a f9 64 42 86 62 aa 14 4e |
|        |                       | 02 e5 20 47 63 7e 79 00 b1 8c                   |
|        +-----------------------+-------------------------------------------------+
|        | Sequence              | fe ff ff ff                                     |
+--------+-----------------------+-------------------------------------------------+
| Number of outputs              | 01                                              |
+--------+-----------------------+-------------------------------------------------+
|        | Value                 | 28 02 00 00 00 00 00 00                         |
|        +-----------------------+-------------------------------------------------+
| Output | Script length         | 19                                              |
|        +-----------------------+-------------------------------------------------+
|        | ScriptPubKey          | 76 a9 14 88 43 77 97 2c 0d f1 75 fc 1e f8 78 a4 |
|        | (Locking Script)      | 90 d7 01 c0 fd 19 5e 88 ac                      |
+--------+-----------------------+-------------------------------------------------+
| Locktime                       | 18 09 08 00                                     |
+--------------------------------+-------------------------------------------------+

下面介绍一下每个字段的含义:

  1. Version,表示 Tx 版本号,占用 4 字节,目前有两个可选值 1/2。当 Tx 版本号为 2 时表示使用 BIP68 定义的 Relative Locktime。参考:https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format
  2. Number of inputs,表示后面 Input 数组的元素个数,这个例子中是 1;Input 数组中记录着这个 Tx 中所花费的 UTXO;
  3. Previous output hash,花费的 UTXO 来哪自哪个交易,它按字节反转才是 Block Explorer 中的表示形式,所以这个例子表示的就是要花费 Tx 8ab9dd72d22337458af8285c70e7e65916893f8691daf6fcfb9dde70a39fdf10 中的某个 UTXO;
  4. Previous output index,某个 Tx 可能有多个 UTXO,这个索引用于指定花费 Tx 中的哪个 UTXO;
  5. Script length,表示后面的 Unlocking Script 的长度,这个例子中 0x8a(即十进制的 138);
  6. ScriptSig(Unlocking Script),表示解锁这个 UTXO 所需要的脚本;
  7. Sequence,序列号,占用 4 字节。如果不使用 Replace-by-Fee 且想启用 Locktime(后面会介绍)时,往往设置为 0xfffffffe,细节参考 BIP125
  8. Number of outputs,表示后面 Output 数组的元素个数,这个例子中是 1;Output 数组中记录着 Input 中的 UTXO 将如何分配;
  9. Value,表示转移多少 UTXO;这个例子中是 0x0228(即十进制的 552);
  10. Script length,表示后面的 Locking Script 的长度,这个例子中 0x19(即十进制的 25);
  11. ScriptPubKey(Locking Script),锁定脚本,设置解锁这个新的 UTXO 的条件;
  12. Locktime,表示锁定时间,当所有 Input 的 Sequence 都为 0xffffffff 时这个字段会被忽略。这个例子中是 0x00080918(即十进制的 526616),表示这个 Tx 在区块高度 526616 后才可以被打包。Locktime 为 0x00000000 表示不锁定,即可马上打包。

除了“Unlocking Script”和“Locking Script”外,其它字段都交待了具体的含义,后文将介绍如何构造这里的“Unlocking Script”和“Locking Script”。

参考:
https://medium.com/fcats-blockchain-incubator/understanding-the-bitcoin-blockchain-header-a2b0db06b515
https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format
https://en.bitcoin.it/wiki/Protocol_documentation#tx

2.1.1. Sequence

Tx 中 Sequence 含义如表 1 所示。

Table 1: Tx 中 Sequence 含义
Value Relative Locktime (BIP68) Replace-by-Fee (BIP125) Absolute Locktime
0x00000001 <= Sequence <= 0x7fffffff 启用 显式启用 启用
0x80000000 <= Sequence <= 0xfffffffd 不启用 显式启用 启用
Sequence = 0xfffffffe 不启用 不显式启用 启用
Sequence = 0xffffffff 不启用 不显式启用 不启用

注:Sequence 是应用在 tx 的某个 Input 上的,而 Locktime 则是应用在整个 tx 上的。 当 Tx 每个 Input 的 Sequence 全部为 0xffffffff 时,整个 Tx 的 Locktime 才会被忽略。

当 Tx 中 Sequence 字段小于 0xfffffffe 时(往往为 0xfffffffd),表示显式启用 Replace-by-Fee。那什么是 Replace-by-Fee 呢?

A Bitcoin transaction can be designated as RBF in order to allow the sender to replace this transaction with another similar transaction which pays a higher fee. This mechanism exists to allow users to respond if the network becomes congested and fees rise unexpectedly. If a user sends a transaction with a low fee, and finds that it is taking too long to confirm, the user can raise the fee they pay to confirm their transaction faster.

上面描述摘自:https://river.com/learn/terms/r/replace-by-fee-rbf/ 。除“显式启用 RBF”的 Tx 外,还有“继承启用 RBF”的 Tx。如果 Tx 的“未打包确认的父 Tx”中,有“显式启用 RBF”的 Tx,则当前 Tx 是“继承启用 RBF”的。

2.1.2. Locktime

Tx 中 Locktime 含义如表 2 所示。

Table 2: Tx 中 Locktime 含义
Value 说明
Locktime = 0 Tx 可马上写入 block 中
Locktime < 500000000 Locktime 含义是块高度,在这个块高度之后才 Tx 可以被写入到 block 中
Locktime >= 500000000 Locktime 含义是 Unix 时间戳,在这个时间戳之后 Tx 才可以被写入到 block 中
2.1.2.1. CLTV(BIP65)和 CSV(BIP112)

前面提到了,通过 Locktime 可以设置让 Tx 经过一段时间后才能打包到 block 中。有没有办法实现:Tx 可以马上打包,但新 UTXO 经过一定时间后才可以被消费呢?答案是有的,而且有两种方案。

方案一、使用绝对时间锁 OP_CHECKLOCKTIMEVERIFY(CLTV),锁定脚本的例子:

<绝对超时时间> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <Alice's pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

|<----- 相对 P2PKH,增加的脚本(CLTV)------>|

只有当前时间超过 <绝对超时时间> (其格式和 Locktime 一样)后,Alice 才可以成功解锁。详情可参考:BIP65

方案二、使用相对时间锁 OP_CHECKSEQUENCEVERIFY(CSV),锁定脚本的例子:

IF
    2 <Alice's pubkey> <Bob's pubkey> <Escrow's pubkey> 3 CHECKMULTISIG
ELSE
    "30d" CHECKSEQUENCEVERIFY DROP
    <Alice's pubkey> CHECKSIG
ENDIF

这个脚本表示 Alice/Bob/Escrow 任意两个人配合可以马上解锁脚本,而 30 天后,Alice 可以一个人单独解锁脚本。详情可参考:BIP112

2.1.2.2. 时间锁总结

目前,Bitcoin 一共有 4 种类型的时间锁,如表 3 所示。

Table 3: Bitcoin 4 种类型的时间锁
类型 说明
Timelock, Sequence (>= 0x80000000) 限制 Tx 打包上链的绝对时间锁
Timelock, Sequence (<= 0x7fffffff) 限制 Tx 打包上链的相对时间锁
CLTV 限制 UTXO 消费的绝对时间锁
CSV 限制 UTXO 消费的相对时间锁

参考:
https://en.bitcoin.it/wiki/Timelock
https://academy.bit2me.com/en/que-es-timelock/

2.2. Unlocking Script

这个例子中,Unlocking Script 用于解锁 Tx 8ab9dd72d22337458af8285c70e7e65916893f8691daf6fcfb9dde70a39fdf10 中的第 0 个 UTXO,我们先分析一下这个 UTXO 的 Locking Script,从区块浏览器中获取后,摘抄如下:

76a914e06f1b3b23fe35386c31e5c6dc43b4fc4c025c3988ac

上面是个典型的 P2PKH Locking Script:

OP_DUP
OP_HASH160
e06f1b3b23fe35386c31e5c6dc43b4fc4c025c39
OP_EQUALVERIFY
OP_CHECKSIG

为了解锁它,我们要构造相应的 Unlocking Script,如图 1 所示。

bitcoin_tx_script.png

Figure 1: Combining scriptSig and scriptPubKey to evaluate a transaction script

具体来说,这个例子中 Unlocking Script 的格式如下:

+----------------------+-------------------------------------------------+
| PUSHDATA Opcode      | 47                                              |
+-----------+----------+-------------------------------------------------+
|           | Sequence | 30                                              |
|           +----------+-------------------------------------------------+
|           | Length   | 44                                              |
|           +----------+-------------------------------------------------+
|           | Integer  | 02                                              |
|           +----------+-------------------------------------------------+
|           |   Length | 20                                              |
| Signature +----------+-------------------------------------------------+
| (DER)     |   R      | 5a aa a2 3d 7b a6 9e b6 d2 33 cd 31 ab 24 2d 09 |
|           |          | b6 ab 1a 32 5b 8e 40 6e 55 d9 e5 57 58 32 73 41 |
|           +----------+-------------------------------------------------+
|           | Integer  | 02                                              |
|           +----------+-------------------------------------------------+
|           |   Length | 20                                              |
|           +----------+-------------------------------------------------+
|           |   S      | 4e ec 8e 76 69 24 12 49 b4 f5 97 d9 df e3 4a 9a |
|           |          | 25 cc 61 f0 f5 5f 1c 63 b8 f2 2d f4 73 71 83 6d |
+-----------+----------+-------------------------------------------------+
| Sig Hashtype         | 01                                              |
+----------------------+-------------------------------------------------+
| PUSHDATA Opcode      | 41                                              |
+---------------+------+-------------------------------------------------+
|               | type | 04                                              |
|               +------+-------------------------------------------------+
|               | X    | b6 b6 50 9e e4 0c b6 94 7a f2 67 d3 cd 7d a3 06 |
| Public Key    |      | db b4 fb 26 91 fc 30 c1 e7 6e de 0b 3e 53 39 80 |
|               +------+-------------------------------------------------+
|               | Y    | c8 7e 8a 4d 38 2f 80 b2 33 8f cb 15 46 0a f9 64 |
|               |      | 42 86 62 aa 14 4e 02 e5 20 47 63 7e 79 00 b1 8c |
+---------------+------+-------------------------------------------------+

上面这个数据由“签名”和“公钥”两部分组成。其中 2 个 PUSHDATA Opcode 都表示后面数据的长度,比如第一个 PUSHDATA 0x47(十进制 71)表示 PUSH 后面 71 个字节数据(即后面的 Signature 加上 Sig Hashtype)到执行栈,第二个 PUSHDATA 0x41(十进制 65)表示 PUSH 后面的 65 个字节数据(即公钥,参考 1.2)到执行栈。

2.2.1. Signature Hash Types

BTC 支持的 Signature Hash Types 如表 4 所示。

Table 4: BTC Signature Hash Types
Sig Hash Types
SIGHASH_ALL 0x01
SIGHASH_NONE 0x02
SIGHASH_SINGLE 0x03
SIGHASH_ALL | SIGHASH_ANYONECANPAY 0x81
SIGHASH_NONE | SIGHASH_ANYONECANPAY 0x82
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY 0x83
SIGHASH_ALL | SIGHASH_ANYPREVOUT 0x41
SIGHASH_NONE | SIGHASH_ANYPREVOUT 0x42
SIGHASH_SINGLE | SIGHASH_ANYPREVOUT 0x43
SIGHASH_ALL | SIGHASH_ANYPREVOUTANYSCRIPT 0xc1
SIGHASH_NONE | SIGHASH_ANYPREVOUTANYSCRIPT 0xc2
SIGHASH_SINGLE | SIGHASH_ANYPREVOUTANYSCRIPT 0xc3

SIGHASH_ALL 使用最广泛,普通转账一般都是 SIGHASH_ALL。

关于 Sig Hashtype 字段详细说明可参考:
https://raghavsood.com/blog/2018/06/10/bitcoin-signature-types-sighash
https://github.com/libbitcoin/libbitcoin-system/wiki/Sighash-and-TX-Signing
https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki

2.2.2. 计算 ECDSA 签名

上面数据中 Signature 中大部分字段都来自于标准的 DER 格式,我们重点介绍一下如何计算 ECDSA 签名中的 r 值和 s 值。这个例子中,只有一个 Input,所以只需要进行一次签名。

首先,准备“待签名数据”。待签名数据由下表组成:

+--------------------------------+-------------------------------------------------+
| Version                        | 01 00 00 00                                     |
+--------------------------------+-------------------------------------------------+
| Number of inputs               | 01                                              |
+--------+-----------------------+-------------------------------------------------+
|        | Previous output hash  | 10 df 9f a3 70 de 9d fb fc f6 da 91 86 3f 89 16 |
|        | (reversed)            | 59 e6 e7 70 5c 28 f8 8a 45 37 23 d2 72 dd b9 8a |
|        +-----------------------+-------------------------------------------------+
|        | Previous output index | 00 00 00 00                                     |
|        +-----------------------+-------------------------------------------------+
| Input  | Script length         | 19                                              | \
|        +-----------------------+-------------------------------------------------+  \
|        | Locking Script        | 76 a9 14 e0 6f 1b 3b 23 fe 35 38 6c 31 e5 c6 dc |  / 和最终的 Tx 的第一个不同点
|        |                       | 43 b4 fc 4c 02 5c 39 88 ac                      | /
|        +-----------------------+-------------------------------------------------+
|        | Sequence              | fe ff ff ff                                     |
+--------+-----------------------+-------------------------------------------------+
| Number of outputs              | 01                                              |
+--------+-----------------------+-------------------------------------------------+
|        | Value                 | 28 02 00 00 00 00 00 00                         |
|        +-----------------------+-------------------------------------------------+
| Output | Script length         | 19                                              |
|        +-----------------------+-------------------------------------------------+
|        | ScriptPubKey          | 76 a9 14 88 43 77 97 2c 0d f1 75 fc 1e f8 78 a4 |
|        | (Locking Script)      | 90 d7 01 c0 fd 19 5e 88 ac                      |
+--------+-----------------------+-------------------------------------------------+
| Locktime                       | 18 09 08 00                                     |
+--------------------------------+-------------------------------------------------+
| Sig Hashtype                   | 01 00 00 00                                     | // 和最终的 Tx 的第二个不同点
+--------------------------------+-------------------------------------------------+

这和最终的 Tx 数据有两点不同:

  1. Input 中的 Unlocking Script 改为 UTXO 的 Locking Script,也就是 8ab9dd72d22337458af8285c70e7e65916893f8691daf6fcfb9dde70a39fdf10 中第 0 个 Output 的 Locking Script,即 76a914e06f1b3b23fe35386c31e5c6dc43b4fc4c025c3988ac;
  2. 在数据的最后,增加 4 字节的 Sig Hashtype。

BTC 规定上面数据的 SHA256 哈希才是 ECDSA 签名的输入消息(也就是待签名数据),由于 ECDSA 签名算法要求在“输入消息的哈希”上进行运算,所以实际上上面数据进行了两次哈希运算。

2.2.2.1. Python 实现

下面是对前面例子生成 ECDSA 签名数据的 Python 实现:

#!/usr/bin/env python3

import hashlib
from ecdsa import SECP256k1, SigningKey, VerifyingKey

priv_key_hex = 'ab6f3e0f87efdd79d99be7caa9f91c2bcca32be13e6b5f9affd4d84894279e2c'
priv_key = int(priv_key_hex, 16)

txData = bytes.fromhex('010000000110df9fa370de9dfbfcf6da91863f891659e6e7705c28f88a453723d272ddb98a000000001976a914e06f1b3b23fe35386c31e5c6dc43b4fc4c025c3988acfeffffff0128020000000000001976a914884377972c0df175fc1ef878a490d701c0fd195e88ac1809080001000000')
msg: bytes = hashlib.sha256(txData).digest()  # 5d159615a85bf06a61c7d6889b52f2588a4e64b855ae70a268e7b0489608fce2

sk: SigningKey = SigningKey.from_secret_exponent(priv_key, curve=SECP256k1, hashfunc=hashlib.sha256)
deterministic_signature: bytes = sk.sign_deterministic(msg)  # RFC6979

print("Signing the message:    ", msg.hex())  # 5d159615a85bf06a61c7d6889b52f2588a4e64b855ae70a268e7b0489608fce2
print("RFC6979 signature (r):  ", deterministic_signature.hex()[0:64])  # 5aaaa23d7ba69eb6d233cd31ab242d09b6ab1a325b8e406e55d9e55758327341
print("RFC6979 signature (s):  ", deterministic_signature.hex()[64:])   # b113718996dbedb64b0a6826201cb56494e27af5b9e983d806e030985cc4bdd4

vk: VerifyingKey = sk.verifying_key
print("Public key (compressed):", vk.to_string('compressed').hex())     # 02b6b6509ee40cb6947af267d3cd7da306dbb4fb2691fc30c1e76ede0b3e533980
assert vk.verify(deterministic_signature, msg)

我们得到了 ECDSA 签名数据:

r = 5aaaa23d7ba69eb6d233cd31ab242d09b6ab1a325b8e406e55d9e55758327341
s = b113718996dbedb64b0a6826201cb56494e27af5b9e983d806e030985cc4bdd4

BIP62 中规定,如果 s 值大于 0x7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 5D576E73 57A4501D DFE92F46 681B20A0 ,则要拿 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141 减去 s 才是最终的 s(这样调整后,不会影响 ECDSA 签名,它仍然是个有效的 ECDSA 签名,参考 https://bitcoin.stackexchange.com/questions/83408/in-ecdsa-why-is-r-%E2%88%92s-mod-n-complementary-to-r-s )。由于前面求出的 s 正好大于这里提到的阈值,也就是要相应地调整:

s = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141 - b1137189 96dbedb6 4b0a6826 201cb564 94e27af5 b9e983d8 06e03098 5cc4bdd4
  = 4eec8e76 69241249 b4f597d9 dfe34a9a 25cc61f0 f55f1c63 b8f22df4 7371836d

最终,我们得到了:

r = 5aaaa23d7ba69eb6d233cd31ab242d09b6ab1a325b8e406e55d9e55758327341
s = 4eec8e7669241249b4f597d9dfe34a9a25cc61f0f55f1c63b8f22df47371836d

这和节 2.2 中的交待的签名数据的 r 和 s 是一致的。

2.2.2.2. ECDSA 签名数据的长度

上面例子中,签名长度(包含后面的 Sig Hashtype)是 71 字节,其实这不一定是固定的。

ANS.1 编码规则(DER)中使用的是有符号整数(Signed),而 ECDSA 中的 r 值和 s 值是无符号整数(Unsigned),因此当 r 值(或 s 值)的第一个比特位为 1 时,会被 DER 的 Parser 错误地认为是“负数”,导致错误。为了解决这个问题,有一种解决方案:一旦发现 r 值(或 s 值)的第一个比特位为 1,则在数值前加上了一个 0x00 字节(称为 padding),让无符号整数被编码为一个正整数。这样,r 值(或 s 值)将占据 33 字节的长度,从而导致签名的长度不是固定的。

ECDSA 签名数据各部分所占长度:

 6  byte  | DER encoding overhead
 32 byte  | r-value
(1  byte) | r-value padding (if needed)
 32 byte  | S-value
(1  byte) | S-value padding (if needed)
 1  byte  | Signature Hash

具体来说,ECDSA 签名长度可以有下面几种情况:

  1. 71 字节,r 值和 s 值都没有进行 padding;
  2. 72 字节,r 值和 s 值其中有一个进行了 padding;
  3. 73 字节,r 值和 s 值都进行了 padding。

2 是 ECDSA 签名长度为 73 字节时的情况(即 r 值和 s 值都进行了 padding)。

btc_ecdsa_der.png

Figure 2: ECDSA 签名长度为 73 字节时的情况

BIP62 提出了总是产生第一个比特位为 0 的 s 值的方案,在 https://github.com/bitcoin/bitcoin/pull/13666 中实现了总是产生第一个比特位为 0 的 r 值的方案,这样,我们可以总是产生长度为 71 字节的 ECDSA 签名了。不过,并不是所有 BTC Client 都会这样做,链上 ECDSA 签名长度的分布情况可参考:https://transactionfee.info/charts/bitcoin-script-ecdsa-length/

参考:Evolution of the signature size in Bitcoin

2.3. Locking Script

在节 2.1 中提到 Locking Script 如下所示:

+--------+-----------------------+-------------------------------------------------+
|        | Value                 | 28 02 00 00 00 00 00 00                         |
|        +-----------------------+-------------------------------------------------+
| Output | Script length         | 19                                              |
|        +-----------------------+-------------------------------------------------+
|        | ScriptPubKey          | 76 a9 14 88 43 77 97 2c 0d f1 75 fc 1e f8 78 a4 |
|        | (Locking Script)      | 90 d7 01 c0 fd 19 5e 88 ac                      |
+--------+-----------------------+-------------------------------------------------+

里面的 Locking Script 的格式如下:

+----------------+-------------------------------------------------+
| OP_DUP         | 76                                              |
+----------------+-------------------------------------------------+
| OP_HASH160     | a9                                              |
+----------------+-------------------------------------------------+
| PUSHDATA       | 14                                              |
+----------------+-------------------------------------------------+
| PubKHash       | 88 43 77 97 2c 0d f1 75 fc 1e f8 78 a4 90 d7 01 |
|                | c0 fd 19 5e                                     |
+----------------+-------------------------------------------------+
| OP_EQUALVERIFY | 88                                              |
+----------------+-------------------------------------------------+
| OP_CHECKSIG    | ac                                              |
+----------------+-------------------------------------------------+

PubKHash 是从目标帐户(1DRVinkHyDWruJHGyqFSGbzsLbYDmGiiTs)的公钥的 SHA256 哈希,再 RIPEMD-160 哈希得到。

这可以从目标地址 1DRVinkHyDWruJHGyqFSGbzsLbYDmGiiTs 推导出来。1DRVinkHyDWruJHGyqFSGbzsLbYDmGiiTs 是 Base58Check 编码,把它解码后得到:

00884377972c0df175fc1ef878a490d701c0fd195edc621d32

去掉前面的 version(00)和后面的 checksum(dc621d32),就是 PubKHash(公钥的 SHA256 哈希,再 RIPEMD-160 哈希)。

至此,我们已经交待了 Raw Tx 的每个字节是如果构造的。

3. 其它例子(多个 Input)

前面介绍的例子中,只有一个 Input。当存在多个 Input 时,需要多次签名,细节可参考:https://github.com/libbitcoin/libbitcoin-system/wiki/Sighash-and-TX-Signing

BTC 主网上的 Tx 92616e432f7807bf6d93252fcdca7efb139e56f7aa34835b50670ebc6a4b5649 是个具有多个 Input 的 Tx,它的 Input 数量为 2,Output 数量为 1。

这个 Tx 对应的序列化后的已签名数据(可以直接广播,不过已经广播过了):

01000000022628a02cab1c7bdc2959015c5033489202a8b0bcfccb3c64f466dd4b1218638f000000008b4830450221008d4ecab2a74461cabcd3e5398ca6752594fbd758c3d10ff3a50c5bf45e460a4502206e7607d2f9ff8fa40489c041fdbbcaecfe723caabf28de9451a2be67d8705d1901410426f2ad4968ef7c5412a2cfac85c902f613b7406962477c931d25ddfb2957c494e70f832ac8dd50740c64297038c722542005b0c5191061e444c793b62d9b2aceffffffff2628a02cab1c7bdc2959015c5033489202a8b0bcfccb3c64f466dd4b1218638f010000008a4730440220184f1d7e9cfe7539cd40cae04e71dc21ce05de958620294cb2d490d45356bfc102203f4ae4afe33b93d1cc96c356735d7a4d3b90cfe62cab0d0cf71fd2f7718e99df0141043c8564e9fa9e9530699ffe7c5dd75077698ff96f3786e96bdbb210a109147cedacfdaa784eb4ba538556e8dd70455ba3627fe902f152940d70ffdd55acdb82a9ffffffff0143170600000000001976a9144614b4066faf3ef831a20186f76381c25dd6ea8288ac00000000

这个 Tx 中两个 Input 涉及的地址的私钥分别为:

Private Key (Hex): cbac84458fcfbb39f87fca7ab9a9ef2f76812a6f999a75dfa25dbcbb0ee3eb6f
Uncompressed Address: 1PFw45xp5JUcLZfDQnMpto6yJpcjRLqrJ8

Private Key (Hex): 7d37c1a74d3b87d3994ac6db65b4f298f64a8ed6144edfdb2cacea70cf3070af
Uncompressed Address: 19u4WSjpp19yoAK9kdRyY9HJ7ad2S8s1E4

可以利用这两个私钥重建出上面提到的“序列化后的已签名数据”。

这个例子来自于:http://edgecase.net/articles/bitcoin_transaction_test_set

Author: cig01

Created: <2020-07-26 Sun>

Last updated: <2021-01-09 Sat>

Creator: Emacs 27.1 (Org mode 9.4)