深入比特币源码,交易构造与验证的核心机制
摘要:比特币作为首个成功的去中心化数字货币,其核心魅力在于通过密码学原理和分布式共识实现了点对点的价值转移,而这一切的基础,便是“交易”(Transaction),本文将基于比特币核心源码,深入剖析比特币交...
比特币作为首个成功的去中心化数字货币,其核心魅力在于通过密码学原理和分布式共识实现了点对点的价值转移,而这一切的基础,便是“交易”(Transaction),本文将基于比特币核心源码,深入剖析比特币交易的构造、验证流程及其核心数据结构,揭示其在去中心化网络中安全、可靠运作的底层逻辑。
比特币交易的本质
在比特币网络中,交易是指一次价值的转移,它本质上是一组数据结构,包含了输入(Inputs)和输出(Outputs),以及控制交易如何被创建和验证的脚本(Scripts),交易由网络节点广播,由矿工打包进区块,并通过共识机制最终确认,每一笔比特币的“花费”,都可以追溯到其最初在创世交易或某笔交易中产生的“未花费输出”(Unspent Transaction Output, UTXO)。
比特币交易的核心数据结构
要理解比特币交易,首先需要掌握其核心数据结构,在比特币核心源码(如src/primitives/transaction.h)中,一个交易主要由以下几个部分组成:
-
版本号 (Version):
一个4字节的无符号整数,用于标识交易的版本,不同版本的交易可能支持不同的特性或规则,例如SegWit引入后,版本号也与之相关。
-
标记 (Marker) 和 标志 (Flag) (可选,仅SegWit交易):
- 在
src/primitives/transaction.h中,CTransaction类包含nVersion和nLockTime,以及一个CTransaction的联合体(union),其中包含了CTxIn和CTxOut的序列,对于SegWit交易,会有一个witness字段(见证数据)。 marker(当前固定为0x00)和flag(当前固定为0x01)用于在不改变旧节点验证逻辑的情况下,识别出该交易是SegWit交易,并将其见证数据与交易本身分离存储。
- 在
-
输入列表 (TxIn - CTxIn):
-
每个交易输入引用之前一笔交易的输出(即UTXO),证明发送者有权花费这些比特币。
-
在
src/primitives/transaction.h中,CTxIn结构体定义如下:class CTxIn { public: COutPoint prevout; // 引用的前一笔交易的输出点(哈希+索引) CScript scriptSig; // 解锁脚本(签名和公钥等) uint32_t nSequence; // 序列号,用于相对锁定时间等 // 构造函数、序列化/反序列化方法等 };prevout:包含prevout.hash(被引用交易的哈希)和prevout.n(被引用在该交易中的输出索引)。scriptSig:签名脚本,也称为解锁脚本,它提供了满足被引用UTXO输出脚本(锁定脚本)的条件的数据,例如签名和公钥。nSequence:最初用于实现交易替换(Replace-by-Fee, RBF)和相对锁定时间,在SegWit后其作用有所限制。
-
-
输出列表 (TxOut - CTxOut):
-
每个交易输出定义了接收方将获得的比特币数量以及接收方需要满足何种条件才能花费这些比特币。
-
在
src/primitives/transaction.h中,CTxOut结构体定义如下:class CTxOut { public: CAmount nValue; // 输出的金额(以聪为单位,1 BTC = 100,000,000 聪) CScript scriptPubKey; // 锁定脚本,定义了花费条件 // 构造函数、序列化/反序列化方法等 };nValue:明确指定了输出的金额。scriptPubKey:锁定脚本,也称为脚本公钥,它规定了未来花费这笔UTXO必须满足的条件,例如提供与某个公钥匹配的签名(P2PKH),或满足更复杂的脚本条件(P2SH, SegWit等)。
-
-
锁定时间 (nLockTime):
- 一个4字节的无符号整数,表示该交易最早可以被矿工打包进区块的时间或区块高度,如果当前时间或区块高度小于
nLockTime,则交易不会被确认,它可以与nSequence结合实现相对锁定时间。
- 一个4字节的无符号整数,表示该交易最早可以被矿工打包进区块的时间或区块高度,如果当前时间或区块高度小于
-
见证数据 (witness - 可选,仅SegWit交易):
- 对于SegWit交易,见证数据被单独存储,它是一个
CTxWitness对象,包含每个输入对应的见证脚本(如签名和公钥)。 - 见证数据的引入将签名数据从交易本身分离,使得交易ID(txid)不再包含签名信息,从而解决了签名延展性问题,并提升了区块空间的利用效率。
- 对于SegWit交易,见证数据被单独存储,它是一个
交易的构造流程
构造一笔比特币交易通常包括以下步骤:
-
确定输入(选择UTXO):
发送方需要从自己的UTXO集合中选择足够金额的未花费输出作为交易输入,这通常通过查询比特币的UTXO集实现。
-
确定输出(指定接收方和金额):
发送方指定接收方的地址(或直接指定脚本)以及要发送的金额,需要注意的是,所有输入的总和必须大于或等于所有输出的总和,差额作为矿工费。
-
创建锁定脚本 (scriptPubKey):
根据接收方的地址类型(如P2PKH, P2SH-P2WPKH, P2TR等),生成对应的锁定脚本,并将其放入每个输出中。
-
创建解锁脚本 (scriptSig) 和 见证数据 (witness):
- 对于每个输入,发送方需要使用其私钥对交易数据进行签名,生成解锁脚本(对于传统交易)或见证数据(对于SegWit交易)。
- 签名过程通常使用哈希算法(如SHA256+RIPEMD160)对交易数据进行哈希,然后对哈希值进行签名(如ECDSA签名),签名中会包含签名算法、签名本身以及可能需要的公钥。
-
组装交易:
将版本号、输入列表、输出列表、锁定时间以及见证数据(如果是SegWit交易)按照比特币网络协议规定的格式组装成完整的交易数据。
-
广播交易:
将构造好的交易序列化后,广播到比特币网络,由其他节点进行验证和传播。
交易的验证流程
交易验证是比特币网络安全的核心,当节点收到一笔交易时,会执行一系列严格的验证步骤(源码主要在src/main.cpp、src/validation.cpp、src/script/interpreter.cpp等中):
-
语法验证:
检查交易数据格式是否正确,如字段长度是否合法,序列化/反序列化是否成功等。
-
输入验证:
- 输入非空:交易输入列表不能为空。
- 引用UTXO存在:每个输入引用的
prevout必须在UTXO集中存在且未被花费。 - 签名验证:这是最关键的一步,对于每个输入,节点会执行脚本验证:
- 将输入的
scriptSig(和见证数据,如果存在)与被引用UTXO的scriptPubKey组合成一个完整的脚本。 - 按照比特币脚本解释器的规则执行该脚本,脚本通常包含一系列操作码(OPCODES),如签名验证(OP_CHECKSIG, OP_CHECKSIGVERIFY)、哈希(OP_HASH160)、条件判断(OP_IF, OP_ELSE)等。
- 脚本执行结果必须为“真”(True),否则交易无效,签名验证确保了只有拥有对应私钥的人才能花费UTXO。
- 将输入的
-
输出验证:
- 输出非空:交易输出列表不能为空。
- 金额非负:每个输出的金额
nValue必须大于等于0。 - 金额溢出检查:确保所有输出的金额总和不会超过
CAmount类型的最大值,且不会导致整数溢出。
-
锁定时间验证:
- 检查交易的
nLockTime是否满足,如果nLockTime大于0,则当前时间(或区块高度)必须大于等于nLockTime,或者交易中所有输入的nSequence都小于SEQUENCE_LOCKTIME_DISABLE(即0x80000000,表示不启用相对锁定时间)。
- 检查交易的
-
共识规则检查:
交易必须符合
