Bitcoin Transactions

Table of Contents

1. Tx 简介

交易(Transaction,简称 Tx)是比特币系统的重要组成部分。而块(Block)就是将这些基础单元打包装箱,并链在一起。巨大算力保障了块的安全,也就保障了单个交易的安全。

可以把 Tx 简单地理解为转账,图 1 是一个简单的交易示意图,它描述了 Alice 向 Bob 转账的场景。这个 Tx 中,Input 是 Alice 及其付出金额,而 Output 是 Bob 和 Alice 及其收到金额。为什么 Output 中也有 Alice 呢?这是因为, 如果不考虑交易手续费,则交易 Input 和 Output 的金额应该相等(一般来说,Input 会略大于 Output,差额是交易的手续费)。 比如,Alice 手头有一个 10 元钱,想转账 3 元钱给 Bob,则输出中还会包含转账 7 元给自己的条目。

bitcoin_tx_common_example.png

Figure 1: Common Transaction

2 是多个 Input,一个 Output 的 Tx 场景。

bitcoin_tx_aggregating_example.png

Figure 2: Transaction aggregating funds

3 是一个 Input,很多个 Output 的 Tx 场景。

bitcoin_tx_distributing_example.png

Figure 3: Transaction distributing funds

2. Tx 细节

4 是通过 Block explorer 展示 Tx 信息的例子(假设场景为 Alice 使用了 0.1 BTC,其中的 0.015 BTC 转账给了 Bob 用于购买咖啡,而 0.0845 BTC 退给了自己,0.0005 BTC 是转账手续费,由打包者获得)。这个 Tx 在主网上真实存在,参考 Tx 0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2

bitcoin_tx_high_level.png

Figure 4: Alice's transaction to Bob's Cafe

4 是很 high level 的交易信息。使用 bitcoin-cli 子命令 getrawtransactiondecoderawtransaction ,可以得到上面 Tx 的底层结构看起来像下面这样:

{
    "version": 1,
    "locktime": 0,
    "vin": [
        {
            "txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
            "vout": 0,
            "scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
            "sequence": 4294967295
        }
    ],
    "vout": [
        {
            "value": 0.01500000,
            "scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
        },
        {
            "value": 0.08450000,
            "scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
        }
    ]
}

可以看到 Transaction 中有 Input 和 Output(分别是上面的 vin 和 vout)。在上面信息中,为什么一眼看不出 Alice 和 Bob 的地址,也看不出 Alice 所花费的 0.1 BTC 呢?不着急,后文将对上面信息进行说明。

3. Unspent Transaction Output (UTXO)

Tx 输出(Transaction Output)是比特币交易的基本组成部分。

比特币网络中 Tx 输出有两个重要信息:地址(公钥 Hash)和 value(比特币)。如果 Tx 的输出没有出现在其它 Tx 的输入中,则这个 Tx 的输出就称为 Unspent Transaction Output (UTXO)。系统中所有 UTXO 之和就是比特币总和,把和某个地址关联的所有 UTXO 相加,就是这个地址的“余额”。

谁拥有 UTXO 中公钥对应的私钥,谁就可以使用(即花费)这个 UTXO。创建普通 Tx,建构 Tx 输入时,需要指定一个可用的 UTXO。如前面例子中 Tx 的输入为:

    "vin": [
        {
            "txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
            "vout": 0,
            "scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
            "sequence": 4294967295
        }
    ]

它表示这个 Tx 所花费的 UTXO 来自于另外一个 Tx(其 id 为 7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18)的第 0 个输出(一个 Tx 的输出可以有多个,索引从 0 开始编号),我们可以从历史 Tx 中查找出这个 UTXO 的 value(比如为 0.1),所以这个 Tx 中 Alice 花费了 0.1 BTC,数值 0.1 不需要显式地写在 Tx 中,而是通过查找 UTXO 信息来得到的。

一旦这个 Tx 被提交,那么 Tx(7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18)的第 0 个输出就不再是 UTXO 了。

这个 Tx 中,输出有两个条目,如下所示:

    "vout": [
        {
            "value": 0.01500000,
            "scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
        },
        {
            "value": 0.08450000,
            "scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
        }
    ]

这两个条目刚开始都是 UTXO,直到有另外的 Tx 把它们作为输入。

3.1. UTXO 实例

下面假设一个这样的场景:张三挖到 12.5 枚比特币。过了几天,他把其中 2.5 枚支付给李四。又过了几天,他和李四各出资 2.5 比特币凑成 5 比特币付给王五。

上面场景,在比特币中产生的 Tx 如图 5 所示。交易 #3001 中的两个输出是目前系统中的 UTXO。

bitcoin_tx_utxo.jpg

Figure 5: UTXO 实例

本节的例子摘自:其实并没有什么比特币,只有UTXO

4. Tx Output

每一笔比特币交易都会创造输出,并被比特币账簿记录下来。几乎所有的输出,除了一个例外(OP_RETURN,参考节 10.3),都能创造称为 UTXO 的比特币块,然后被整个网络识别,供所有者在未来交易中使用。

UTXO 集中的 UTXO 被每一个全节点比特币客户端追踪。新的交易从 UTXO 集中消耗(花费)一个或多个输出。

Output 包含两部分:

  1. 一定量的比特币,面值为“聪”(satoshis),是最小的比特币单位;
  2. 确定花费输出所需条件的加密难题(cryptographic puzzle),也被称为锁定脚本(locking script), 见证脚本(witness script), 或脚本公钥(scriptPubKey)。

5. Tx Input

交易输入标识哪个 UTXO(通过引用)将被消费,并通过解锁脚本(Unlocking Script)提供所有权证明。

要构建一个交易,一个钱包从它控制的 UTXO 中选择足够的面值来支付请求的付款。有时一个 UTXO 就足够,有时候需要不止一个。对于用于付款的每个 UTXO,钱包将创建一个指向该 UTXO 的输入,使用解锁脚本解锁它。

Input 包含四个元素:

  1. 一个交易 ID,引用包含将要消费的 UTXO 的交易;
  2. 一个输出索引(vout),用于标识来自该交易的哪个 UTXO 被引用(第一个为零);
  3. 一个 scriptSig(Unlocking Script),满足 UTXO 的消费条件,解锁用于支出;
  4. 一个序列号。

6. Tx Script(Unlocking Script 和 Locking Script)

比特币的交易验证引擎依赖于两类脚本(Script)来验证比特币交易:Output 中的“锁定脚本”(Locking Script)和 Input 中的“解锁脚本”(Unlocking Script)。

Locking Script 是一个放置在输出上面的花费条件:它指定了今后花费这笔输出必须要满足的条件。由于 Locking Script 往往含有一个公钥或比特币地址(公钥哈希值),在历史上它曾被称为脚本公钥 scriptPubKey。在大多数比特币应用程序中,我们所称的“锁定脚本”将以 scriptPubKey 的形式出现在源代码中。您还将看到被称为见证脚本(witness script)的锁定脚本,或者更普遍称为加密难题(cryptographic puzzle)。 这些术语在不同的抽象层次都代表同样的东西。

Unlocking Script 是这样一个脚本,它“解决”或满足由 Locking Script 放置在输出上的条件,并允许使用输出。解锁脚本是每一笔比特币交易输入的一部分,而且往往含有一个由用户的比特币钱包(通过用户的私钥)生成的数字签名。由于解锁脚本常常包含一个数字签名,因此它曾被称作脚本签名 scriptSig。在大多数比特币应用的源代码中,scriptSig 便是我们所说的 Unlocking Script。需要说明的是“并非所有的 Unlocking Script 都一定包含签名”。

比特币中的 Script 是一种简单的基于栈的语言,该语言比较简单,没有循环等结构,它不是图灵完备的。这里以 Pay-to-Public-Key-Hash(P2PKH)类型的脚本为例进行说明。

Output 中的 scriptPubKey,主要包含转账的目标地址(公钥的 Hash)。而 Input 中的 scriptSig 主要包含签名和公钥(这样,我们可以利用公钥验证签名,从而确定用户身份)。如图 6 所示。

bitcoin_tx_script.png

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

6.1. Tx 的验证

打包节点验证 Tx 的合法性时,需要验证每个 Input。以主网上 Tx 0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2 为例:

{
    "version": 1,
    "locktime": 0,
    "vin": [
        {
            "txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
            "vout": 0,
            "scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
            "sequence": 4294967295
        }
    ],
    "vout": [
        {
            "value": 0.01500000,
            "scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
        },
        {
            "value": 0.08450000,
            "scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
        }
    ]
}

这个交易只有一个 Input。首先通过 Input 的 txid 和 vout 找到该 Input 所引用的 UTXO(也就是 tx 7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18 的首个 Output),并找到这个 Output 的 scriptPubKey,它为:

76a9147f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a888ac

反编译上面脚本,就是:

OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG

然后, 在栈上执行脚本,先执行 Input 的 Unlocking Script(scriptSig),再执行这个 Input 所引用的 UTXO 中的 Locking Script(scriptPubKey),如果最后栈中内容为 TRUE,则认为这个 Input 是合法的,如果所有的 Input 合法则整个 Tx 通过验证。 具体执行细节如图 7 所示。

bitcoin_tx_script_example.gif

Figure 7: Evaluating a script for a P2PKH transaction

7. Tx 手续费

交易手续费没有直接体现在 Tx 中,它等于 Tx 输入之和减去 Tx 输出之和。钱包客户端一般可以调整交易手续费。交易手续费不是强制的,但打包节点会优先打包手续费高的 Tx,且没有手续费的 Tx 很可能不会被打包。

8. Tx 中 Locktime 字段

在 Tx 中有个 locktime 字段,它表示这个 Tx 在 locktime 指定的时间之前不能被节点打包到网络中。也就是说, 节点验证 Tx 时,一旦发现 Tx 的 locktime 是个“未来的时间点”,则会拒绝这个 Tx。

locktime 既可以使用 Block Number,也可以使用 UNIX Timestamp,如表 1 所示。

Table 1: Tx 中 Locktime 的含义
Value Description
0 Not locked
< 500000000 Block number at which this transaction is unlocked
>= 500000000 UNIX timestamp at which this transaction is unlocked

不过,当 Tx 的 sequence 字段为 0xffffffff (被称为 final sequence)时,节点将忽略 locktime 的限制。

注:BTC 要达到 500000000 的区块高度,还需要 9000 多年;而目前的 UNIX Timestamp 早已经超过了 500000000,所以小于 500000000 时表示区块高度,大于等于 500000000 时表示 UNIX Timestamp 在可见的未来不会产生歧义。

9. Tx 序列化和广播

Tx 由钱包创建,钱包将序列化后 Tx 发送给一些节点。如果某个节点(命名为 A)收到一个新的 Tx,则节点 A 会把这个 Tx 发送给它连接的所有节点,这种传播方式称为 Flooding。 挖矿节点会验证 Tx 的合法性,并在成功挖到矿后,把合法的 Tx 打包为块。

9.1. Tx 的序列化格式

9.2. Txid 的生成规则

Txid 由 [nVersion][txins][txouts][nLockTime] 这 4 部分内容,进行 2 次 SHA256 运算后得到。 注:引入隔离见证(参见节 10.5 )后,Txid 的生成规则没有变化,仍然是 [nVersion][txins][txouts][nLockTime] 这 4 部分内容(不包含 witness),进行 2 次 SHA256 运算后得到。

对于图 8 中的例子,这 4 部分内容分别为:

01000000
01484d40d45b9ea0d652fca8258ab7caa42541eb52975857f96fb50cd732c8b481000000008a47304402202cb265bf10707bf49346c3515dd3d16fc454618c58ec0a0ff448a676c54ff71302206c6624d762a1fcef4618284ead8f08678ac05b13c84235f1654e6ad168233e8201410414e301b2328f17442c0b8310d787bf3d8a404cfbd0704f135b6ad4b2d3ee751310f981926e53a6e8c39bd7d3fefd576c543cce493cbac06388f2651d1aacbfcdffffffff
0162640100000000001976a914c8e90996c7c6080ee06284600c684ed904d14c5c88ac
00000000

把它们拼接在一起,再进行 2 次 SHA256 运算后,可以得到 Txid 为 ea0f54946769db29d5f0e6e2eb0bab8726c43ea406f1d9abacc0e73d085f283f,再转换一下字节序,得到 3f285f083de7c0acabd9f106a43ec42687ab0bebe2e6f0d529db696794540fea ,这是主网上一个真实的 Tx。

下面是计算 Txid 的 python 代码:

#!/usr/bin/env python3
from hashlib import sha256

txHex = bytes.fromhex("0100000001484d40d45b9ea0d652fca8258ab7caa42541eb52975857f96fb50cd732c8b481000000008a47304402202cb265bf10707bf49346c3515dd3d16fc454618c58ec0a0ff448a676c54ff71302206c6624d762a1fcef4618284ead8f08678ac05b13c84235f1654e6ad168233e8201410414e301b2328f17442c0b8310d787bf3d8a404cfbd0704f135b6ad4b2d3ee751310f981926e53a6e8c39bd7d3fefd576c543cce493cbac06388f2651d1aacbfcdffffffff0162640100000000001976a914c8e90996c7c6080ee06284600c684ed904d14c5c88ac00000000")
txId = sha256(sha256(txHex).digest()).digest()

print("Internal Form Hash:", txId.hex())        # ea0f54946769db29d5f0e6e2eb0bab8726c43ea406f1d9abacc0e73d085f283f
print("RPC Form Hash:     ", txId[::-1].hex())  # 3f285f083de7c0acabd9f106a43ec42687ab0bebe2e6f0d529db696794540fea

参考:https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Transaction_ID

10. 高级 Script

10.1. 多重签名

多重签名脚本设置了一个条件,脚本中记录了 N 个公钥,必须至少提供其中的 M 个签名才能解锁资金。这也称为 M/N 方案,其中 N 是密钥的总数,M 是验证必须的签名数。例如,2/3 的多重签名是三个公钥被列为潜在签名人,其中至少两个必须用于签名才能创建有效的使用资金的交易。

设置 M/N 多重签名条件的 Locking Script 的一般形式是:

M <Public Key 1> <Public Key 2> ... <Public Key N> N CHECKMULTISIG

M 是花费输出所需的签名的数量的底限,N 是公钥的总数。 设置 2/3 多重签名条件的锁定脚本如下所示:

2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

在 Unlocking Script 中只需要提供 A/B/C 中任意 2 个签名;指令 CHECKMULTISIG 使用 3 个公钥中的任意 2 个去验证签名,如果通过就返回 True。

 <Signature B> <Signature C>  2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
|<----Unlocking Script----->| |<--------------------Locking Script------------------------>|

不过,由于 CHECKMULTISIG 指令实现时出了一个 Bug,导致 Unlocking Script 不得不在前面多加一个 0:

 0 <Signature B> <Signature C>  2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
|<------Unlocking Script----->| |<--------------------Locking Script------------------------>|

10.2. P2SH(Pay-to-Script-Hash)

支付脚本哈希 P2SH(Pay-to-Script-Hash)是 2012 年推出的一种功能强大的新型交易,它大大简化了复杂交易脚本的使用。为了解释 P2SH 的必要性,让我们看一个实际的例子。

假设迪拜的电子产品进口商 Mohammed 对所有客户付款(会计术语称为“应收账款”)都使用多重签名脚本。基于多重签名方案,客户支付的任何款项都会被锁定,必须至少 2 个签名才能解锁,一个来自 Mohammed,另一个来自其合伙人或拥有备份密钥的律师。这样的多重签名机制能提升公司治理管控,同时也能有效防范盗窃、挪用和丢失。

最终的 Locking Script 会非常长:

2 <Mohammed's Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 OP_CHECKMULTISIG

客户付款给 Mohammed 时,需要设置交易的 Output 的 Locking Script 字段为上面内容,这样 Mohammed 才能花费这个 UXTO。

这在操作起来多有不便。 Mohammed 必须在客户付款前将上面的脚本发送给每一位客户,而每一位客户也必须使用专用的能创建自定义交易脚本的比特币钱包软件,每位客户还得学会如何利用自定义脚本来创建交易。 此外,由于脚本可能包含特别长的公钥,最终的交易脚本可能是最初交易脚本长度的 5 倍之多。超大的交易还将给客户造成费用负担。最后,这样一个大交易脚本将一直记录在所有节点内存的 UTXO 集中,直到该笔资金被使用。所有这些都使得这种复杂锁定脚本在实践中变得困难重重。

P2SH 正是为了解决这一实际难题而被引入的,它使复杂脚本的使用能与直接向比特币地址支付一样简单。使用 P2SH 支付,复杂的锁定脚本被其电子指纹(加密哈希)所取代。当随后出现的一笔交易试图花费这个 UTXO 时,除了解锁脚本外,它还必须包含与哈希匹配的脚本。简单地说, P2SH 意味着“支付给匹配这个哈希的脚本,这个脚本将在以后花费这个 UXTO 时提供。”

在 P2SH 交易中,Locking Script 被哈希值取代,称为兑换脚本(Redeem Script),因为它在兑换时提交给系统,而不是作为锁定脚本。表 2 显示了不带 P2SH 的脚本,表 3 显示用 P2SH 编码的相同脚本。

Table 2: 非 P2SH 脚本
Item Value
Locking Script 2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 CHECKMULTISIG
Unlocking Script Sig1 Sig2
Table 3: 用 P2SH 编码的相同脚本
Item Value
Redeem Script 2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 CHECKMULTISIG
Locking Script HASH160 <20-byte hash of redeem script> EQUAL
Unlocking Script Sig1 Sig2 < redeem script >

显然,表 3 中的 Locking Script 更加简单。

下面以具体的例子来介绍 Redeem Script 哈希的计算过程:

echo \
2 \
[04C16B8698A9ABF84250A7C3EA7EEDEF9897D1C8C6ADF47F06CF73370D74DCCA01CDCA79DCC5C395D7EEC6984D83F1F50C900A24DD47F569FD4193AF5DE762C587] \
[04A2192968D8655D6A935BEAF2CA23E3FB87A3495E7AF308EDF08DAC3C1FCBFC2C75B4B0F4D0B1B70CD2423657738C0C2B1D5CE65C97D78D0E34224858008E8B49] \
[047E63248B75DB7379BE9CDA8CE5751D16485F431E46117B9D0C1837C9D5737812F393DA7D4420D7E1A9162F0279CFC10F1E8E8F3020DECDBC3C0DD389D9977965] \
[0421D65CBD7149B255382ED7F78E946580657EE6FDA162A187543A9D85BAAA93A4AB3A8F044DADA618D087227440645ABE8A35DA8C5B73997AD343BE5C2AFD94A5] \
[043752580AFA1ECED3C68D446BCAB69AC0BA7DF50D56231BE0AABF1FDEEC78A6A45E394BA29A1EDF518C022DD618DA774D207D137AAB59E0B000EB7ED238F4D800] \
5 CHECKMULTISIG \
| bx script-encode | bx sha256 | bx ripemd160
54c557e07dde5bb6cb791c7a540e0a4796f5e97e

Mohammed 的客户在付款时指定 Locking Script 为下面内容即可:

HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL

不过,Mohammed 在花费客户支付的款项时,比以前复杂一些,因为这时要指定 Unlocking Script 为:

<Sig1> <Sig2> < redeem script >

也就是:

<Sig1> <Sig2> < 2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 CHECKMULTISIG >

两个脚本(解锁脚本和锁定脚本)经由两步实现组合。首先,检测兑换脚本(redeem script)的哈希与锁定脚本中指定的哈希是否匹配:

< 2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 CHECKMULTISIG > HASH160 <20-byte hash of redeem script> EQUAL

如果兑换脚本的哈希是匹配的,那么下一步解锁脚本会自行执行:

<Sig1> <Sig2> 2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 CHECKMULTISIG

如果返回 TRUE,这个交易就验证通过。

10.2.1. P2SH 地址

接着讨论前面的例子,Mohammed 并不直接把 54c557e07dde5bb6cb791c7a540e0a4796f5e97e 告诉其客户,而是对其进行 Base58Check 编码后再告诉客户。

由于 P2SH 地址采用 5 作为版本前缀,这导致基于 Base58Check 编码的地址以“3”开头。

例如,54c557e07dde5bb6cb791c7a540e0a4796f5e97e 编码后会得到地址 39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw:

echo \
'54c557e07dde5bb6cb791c7a540e0a4796f5e97e'\
 | bx address-encode -v 5
39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw

现在,Mohammed 可以把这个地址发送给他的客户,他们可以采用任意比特币钱包进行简单支付,就像是比特币地址一样。前缀“3”暗示客户这是一种特殊类型的地址,一种对应于脚本而不是公钥的地址,但它作为付款方式与比特币地址完全相同。

支持 P2SH 地址的钱包,会先从地址 39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw 中解码出 54c557e07dde5bb6cb791c7a540e0a4796f5e97e,然后在付款时指定 Locking Script 为下面内容:

OP_HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e OP_EQUAL

注:有时,P2SH 地址(以 3 开头的地址)被称为“多签地址”,这其实是不严谨的。“多签”仅仅是 P2SH 地址的一个应用而已,3 开头的地址也可能不是“多签地址”。

10.2.1.1. P2SH 实例

主网上,Tx 6c4f4def4a7c7d21958c0a70ecdf620f4ee9490eabf94d8a2ee2f6e20c9ae4fa 共有 2 个 Output,第 2 个 Output 是转移 0.00265676 BTC 到 P2SH 地址 33yjjAMJUZbzFYz1PLaV58QsjxsyFa1wqj,其指定的 Locking Script 为:

OP_HASH160 1919cd901fe5fe9744a32f86d6520b84eac8b997 OP_EQUAL

10.2.2. P2SH 的优点

与在 Locking Script 中直接指定复杂脚本相比,P2SH 特性提供了以下好处:

  1. 在交易输出中,复杂脚本由简短哈希取代,使得交易代码变短。
  2. 脚本被编译为地址,支付发送者及其比特币钱包都不需要复杂工程就可以执行 P2SH。
  3. P2SH 将构建脚本的负担转移至接收方,而非发送者。
  4. P2SH 将长脚本数据的存储负担从输出方(既存储在区块链,又存储在 UTXO 集),转移至输入方(只存储在区块链中)。
  5. P2SH 将长脚本数据的存储负担从当前(支付时)转移至未来(花费时)。
  6. P2SH 将长脚本的交易费成本从发送方转移至接收方,接收方必须包含长的兑换脚本才能使用该笔资金。

10.3. OP_RETURN(非支付数据)

比特币的潜在用途超越了支付领域。许多开发者试图充分发挥交易脚本语言的安全性和弹性优势,将其运用于数字公证服务、股票证书和智能合约等领域。使用比特币的脚本语言来实现这些目的的早期尝试,包括创建交易输出,把数据记录在区块链上,例如,以这样的方式记录文件的数字指纹,任何人可以通过引用该交易来建立该文件特定日期的存在证明。

运用比特币的区块链技术存储与比特币支付不相关数据的做法是一个有争议的话题。许多开发者认为其有滥用的嫌疑,因而试图予以阻止。另一些开发者则将之视为区块链技术强大功能的有力证明,认为应该给予大力支持。那些反对包含非支付数据的人辩称这将导致“区块链膨胀”,增加运行的全节点的磁盘存储成本,承担了区块链不应该携带的数据。而且,此类交易创建了不能花费的 UTXO,使用目标比特币地址作为 20 字节的自由格式字段。因为比特币地址只是被当作数据使用,并不对应于私钥,所以会导致 UTXO 永远不能用于交易,因而是“伪支付”。这些交易永远不会被花费,所以永远不会从 UTXO 集中删除,会导致 UTXO 数据库的大小永远增加“膨胀”。

在 0.9 版的 Bitcoin Core 客户端上,通过采用 RETURN 操作符最终实现了妥协。 RETURN 允许开发者在交易输出上增加 80 字节的非支付数据。 然后,与伪支付不同,RETURN 创造了一种明确的可验证不可消费型输出,此类数据无需存储于 UTXO 集。 RETURN 输出被记录在区块链上,它们会消耗磁盘空间,也会导致区块链规模的增加,但它们不存储在 UTXO 集中,因此也不会使得 UTXO 内存池膨胀,更不会增加全节点昂贵的内存代价。

RETURN 脚本的样式:

RETURN <data>

“data”部分被限制为 80 字节,且多表示为哈希值,如同 SHA256 算法输出一样(不过值是 32 字节)。许多应用都在其前面加上前缀以方便识别。例如, 这家网站 Proof of Existence 的数字公证服务使用 8 字节前缀 DOCTIOND,16 进制 ASCII 编码为 44×4F 43 50 50 4F 4F 46。

请记住,并不存在对应于 RETURN 的解锁脚本,也就不能花费 RETURN 的输出。RETURN 的关键点就是锁定的输出不能花费,因此它不需要被保存在 UTXO 集中供未来消费,RETURN 是可验证但是不可花费的。RETURN 常为一个金额为 0 比特币的输出, 因为分配到该输出的比特币都会永久消失。假如一笔 RETURN 被作为一笔交易的输入,脚本验证引擎将会阻止验证脚本的执行,将标记交易为无效。执行 RETURN 本质上导致脚本“返回”FALSE 并停止执行。如果你不小心将 RETURN 的输出作为另一笔交易的输入,则该交易是无效的。

Tx 8bae12b5f4c088d940733dcd1455efc6a3a69cf9340e17a981286d3778615684 中第 0 个 Output 就是一个 RETURN 操作符。

10.4. 复杂的脚本示例

我们的例子使用了迪拜一家公司所有者 Mohammed 的故事,他们主营进出口业务。

在这个例子中,Mohammed 希望用灵活的规则建立公司资本账户。他创建的方案需要使用时间锁设置不同级别的授权。 多重签名计划的参与者是 Mohammed,和他的两个合伙人 Saeed 和 Zaira,以及他们的公司律师 Abdul。三个合伙人根据多数规则作出决定,也就是三人中的两人必须同意才可以。然而,如果他们的密钥出现问题,他们希望他们的律师能够用三个合伙人中任何一人的签名收回资金。最后,如果所有的合伙人一段时间临时都联系不上或不能工作,他们希望律师能够直接接管该帐户。

下面是 Mohammed 设计的上实现上述目的的脚本:

 1: IF
 2:   IF
 3:     2
 4:   ELSE
 5:     <30 days> CHECKSEQUENCEVERIFY DROP
 6:     <Abdul the Lawyer's Pubkey> CHECKSIGVERIFY
 7:     1
 8:   ENDIF
 9:   <Mohammed's Pubkey> <Saeed's Pubkey> <Zaira's Pubkey> 3 CHECKMULTISIG
10: ELSE
11:   <90 days> CHECKSEQUENCEVERIFY DROP
12:   <Abdul the Lawyer's Pubkey> CHECKSIG
13: ENDIF

这里仅说明可以通过脚本构造一些复杂的逻辑,不对其进行一一说明。

10.5. 隔离见证(Segregated Witness)

“隔离见证”(Segregated Witness,或缩写为 segwit)是比特币的一次大的升级,于 2017 年在主网上激活。

在密码学中,术语“见证(Witness)”用于描述解决密码难题的方案。在比特币环境中,见证是指能够满足对 UTXO 施加的条件并解锁该 UTXO 以供花费的任何解决方案。术语“见证”是“Unlocking Script”或“scriptSig”的更一般的术语。

隔离见证,指的是把见证数据(即 scriptSig 中的内容)从 Transaction 信息里抽离出来,单独存放。 如图 9(摘自:https://www.buybitcoinworldwide.com/segwit/ )所示。

bitcoin_tx_segwit.png

Figure 9: 隔离见证

不使用隔离见证时,Transaction 中包含了见证数据(scriptSig),也就是说见证数据(scriptSig)会影响 txid(txid 是 Transaction 信息进行哈希运算后得到的一个唯一 id);使用隔离见证后,Transaction 中不再包含了见证数据(scriptSig 总是为空),见证数据在另外的字段(witness)中(而这个字段不参与 txid 的计算)。

10.5.1. 隔离见证的好处

10.5.1.1. 消除了交易延展性(Transaction Malleability)

隔离见证的最大好处之一是消除了“Transaction Malleability”(一般翻译成“交易延展性”,也有人把它翻译为“交易可锻性”)。

下面介绍一下 Transaction Malleability 是什么,以及为什么它会带来问题。

“Transaction Malleability”是指在 Transaction 被比特币网络确认之前,txid 可能被别人(攻击者)修改。 举例来说,A 提交了一个 Transaction(其功能是转移一个 BTC 给 B),记为 tx1,这个交易的 Input 的 scriptSig 字段中包含了 A 的签名。这个 Transation 在打包确认前,攻击者稍微调整一下 scriptSig,比如增加无关紧要的指令(或者把签名换个 S 值,注:不改变 R 值时,每个 ECDSA 签名都有两个有效的 S 值,参考 https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures ),只要不影响到这个交易信息的签名验证就行,这时会得到一个新 txid(因为 scriptSig 参与了 txid 的生成),记为 tx2。现在 tx1 和 tx2 都在等待打包确认状态。如果恰好 tx2 被先打包(这很少出现,但可以伪造更多的 tx3,tx4 来增加被先打包的概率),那么 tx1 在验证时就会失败。因为交易 Input 中引用的 UXTO 已经被 tx2 花费了。这样,A 成功转移了一个 BTC 给 B,但 txid 却不是 A 当初提交的那个 tx1 了。当然如要 tx1 被先打包,那么 tx2 会验证失败,这里 txid 还是 A 当初提交的那个 tx1。

Transaction Malleability 为什么会带来问题呢?我们以例子来说明,假设 A 开了一个交易所,而 B 是该交易所的用户。B 以前存入了 100 个 BTC,现在向 A 提出提币申请,A 向 B 转移 100 个 BTC,记下 txid 为 tx1,B 利用 Transaction Malleability,伪造了另外一个交易 tx2。这时恰好 tx2 被先打包了,tx1 失败。B 对 A 说“我没有收到 100 个 BTC”,A 去查询他当时提交的 txid(即 tx1),发现确实 tx1 没有打包,然后再发起一个新交易,向 B 转移 100 BTC。这里,其实 A 已经向 B 转移了 200 BTC。

当然,这种情况是可以避免的。如果 A 不根据 txid 来判断是否转账成功,而是查询它钱包地址的余额变化,那么他是可以发现已经转移过 100 个 BTC 给用户 B 了(只是 txid 变了而已)。

比特币交易平台 Mt.Gox,曾经由于 Transaction Malleability 而损失了比特币,参见 Bitcoin Transaction Malleability and MtGox

采用隔离见证时,由于 scriptSig 信息总是为空,见证数据保存在另外的字段(witness)中,修改见证数据,也不会对 txid 造成变化,所以消除了 Transaction Malleability。

10.5.1.2. 优化了网络和存储

见证数据通常在交易的总大小中占了很大比重。更复杂的脚本,如用于多签或支付通道的脚本体积会非常大。在某些情况下,这些脚本会占到交易数据的大多数(超过 75%)。通过将见证数据从交易中移出,隔离见证提高了比特币的可扩展性。节点可以在验证签名后删减见证数据。见证数据不再需要发送到所有节点,也不需要被所有节点存储在磁盘上。

10.5.1.3. 其它好处

除了前面介绍的两个好处外,隔离见证还有其它一些好处,如:

  • 签名验证优化:隔离见证升级签名函数(CHECKSIG,CHECKONSIGG 等),以减少算法的计算复杂度。在引入隔离见证之前,用于生成签名的算法需要大量与交易大小成比例的哈希操作。相对于签名操作的数量数据哈希计算复杂度增加到 O(n^2),给验证签名的所有节点带来了巨大的计算负担。有了隔离见证,算法的复杂度降低到 O(n)。
  • 离线签名改进:隔离见证签名包含由签名的哈希中的每个输入引用的值(金额)。以前,离线签名设备(如硬件钱包)必须在签名交易之前验证每个输入的金额。这通常是通过流式传输大量先前作为输入引用的交易的数据来实现的。由于金额现在是已签名的提交哈希的一部分,因此离线设备不需要以前的交易。如果金额不匹配(由被入侵的在线系统篡改),签名将是无效的。

10.5.2. 隔离见证 Output 和交易示例:P2WPKH

先回顾一下 P2PKH 的 Output(参见节 6.1)。Alice 创建一笔交易,付给 Bob 一杯咖啡的费用。该笔交易构建了一个价值 0.015BTC 的 P2PKH 输出(Bob 可用来花费),该 Output 的 Locking Script 看起来像这样:

OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG

如果通过隔离见证,Alice 将会创建一个“支付给见证公钥哈希” Pay-to-Witness-Public-Key-Hash(P2WPKH)脚本,看起来是这样的:

0 ab68025513c3dbd2f7b92a94e0581f5d50f654e7

显然,隔离见证 Output 的 Locking Script 比 P2PKH 的要简单得多。它包含两个值,第一个数字(0)是一个版本号,第二部分(20 字节)相当于一个 Locking Script,被称为见证程序 witness program。这 20 字节的见证程序就像是 P2PKH 脚本中的公钥哈希值一样。

现在,再看看 Bob 用来去花费这个 UTXO 对应的交易。对于原始脚本(非隔离见证),Bob 的交易必须在交易输入中包含签名。下面的解码交易显示要花费 P2PKH 输出时需要提供的信息:

[...]
"vin" : [
"txid": "0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2",
"vout": 0,
"scriptSig": "<Bob’s scriptSig>",
]
[...]

但是,要花费隔离见证输出,这个交易输入中没有签名。相反,Bob 的交易只有空的 scriptSig 和交易本身之外的隔离见证。下面的解码交易显示要花费 P2WPKH 输出时需要提供的信息:

[...]
"vin": [
"txid": "0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2",
"vout": 0,
"scriptSig": "",
]
[...]
"witness": "<Bob' Signature> <Bob's PublicKey>"
[...]

主网上 P2WPKH 交易的实例,可参考:https://www.blockchain.com/btc/tx/ec9f03d79de1b408a2880e77b7be67c149ddb5e89c5b8c5a648fe29f4524d959

10.5.3. 隔离见证 Output 和交易示例:P2WSH

第二种类型的验证程序对应“支付给脚本哈希”(Pay-to-Script-Hash , P2SH)脚本。节 10.2 中见过这类脚本。例子中,Mohammed 的公司使用 P2SH 来表示一个多重签名脚本。对 Mohammed’s 的公司的支付被编码成一个这样的锁定脚本:

OP_HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL

这个 P2SH 脚本引用了一个兑换脚本的哈希值,该脚本定义了一个花费资金的“2/3”的多重签名要求。为了花费该输出,Mohammed 的公司需提供兑现脚本(其哈希值与 P2SH 输出中的脚本哈希值匹配)和满足兑现脚本所必需的签名,所有这些都在交易输入中。下面的解码交易显示了要花费 P2SH 输出时需要提供的信息:

[...]
"vin" : [
"txid": "abcdef12345...",
"vout": 0,
"scriptSig": "<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 CHECKMULTISIG>",
]

现在,让我们看看整个示例如何升级成为隔离见证。如果 Mohammed 的客户使用的是兼容隔离见证的钱包,他们就能创建一个 P2WSH 输出进行付款,如下:

0 9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73

就像 P2WPKH 的例子一样,你可以看到,隔离见证等效脚本要简单得多,省略了 P2SH 脚本中的各种脚本操作符。而且,隔离见证程序仅包含两个推送到堆栈的值:一个见证版本(0),另一个为 32 字节的兑换脚本(Redeem Script)的哈希值。

Mohammed 的公司可以通过提供正确的兑换脚本和足够的签名满足花费 P2WSH 输出。兑换脚本和签名都将作为见证数据的一部分被隔离在消费交易之外。在交易输入内部,Mohammed 的钱包会放一个空的 scriptSig。下面的解码交易显示要花费 P2WSH 输出时需要提供的信息:

[...]
"vin" : [
"txid": "abcdef12345...",
"vout": 0,
"scriptSig": "",
]
[...]
"witness": "<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 CHECKMULTISIG>"
[...]

主网上 P2WSH 交易的实例,可参考:https://www.blockchain.com/btc/tx/897cea35c5db6c27fd48b66115d9bb848e543b4aae9b7a701286f1b11a798f7f

10.5.4. P2WPKH/P2WSH 地址(Bech32)

P2WPKH/P2WSH 地址没有采用 Base58Check 编码了,而是采用的 Bech32 编码,在 BIP 0173 中定义。主网上它们总是以 “bc1” 开头。

为什么不再使用 Base58 编码呢?主要是 Base58 编码有下面不足:

  • Base58 needs a lot of space in QR codes, as it cannot use the alphanumeric mode.
  • The mixed case in base58 makes it inconvenient to reliably write down, type on mobile keyboards, or read out loud.
  • The double SHA256 checksum is slow and has no error-detection guarantees.
  • Most of the research on error-detecting codes only applies to character-set sizes that are a prime power, which 58 is not.
  • Base58 decoding is complicated and relatively slow.

Bech32 编码由 3 部分组成:

  1. The human-readable part。BTC 主网上固定为 bc,BTC 测试网上固定为 tb。
  2. 分隔符。固定为 1。
  3. The data part。不区分大小写,在 alphanumeric 中去除了 4 个字符:"1", "b", "i", 和 "o"。

下面是几个 Bech32 编码的例子:

Mainnet P2WPKH: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
Testnet P2WPKH: tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx
Mainnet P2WSH: bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3
Testnet P2WSH: tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7

不同语言 Bech32 的实现可参考:https://en.bitcoin.it/wiki/BIP_0173#Reference_implementations

11. 参考

Mastering Bitcoin, 2nd Edition

Author: cig01

Created: <2018-05-27 Sun>

Last updated: <2021-01-09 Sat>

Creator: Emacs 27.1 (Org mode 9.4)