比特币源码分析,交易背后的核心逻辑与实现机制
摘要:比特币作为首个去中心化数字货币,其核心魅力不仅在于价格波动,更在于底层技术架构的颠覆性创新,而交易作为比特币价值流转的基本单元,其设计与实现直接决定了系统的安全性、透明性和去中心化特性,本文将从比特币...
比特币作为首个去中心化数字货币,其核心魅力不仅在于价格波动,更在于底层技术架构的颠覆性创新,而交易作为比特币价值流转的基本单元,其设计与实现直接决定了系统的安全性、透明性和去中心化特性,本文将从比特币源码出发,深入分析交易的数据结构、生成、验证及全网传播的全流程,揭示其“代码即法律”的技术内核。
交易数据结构:从“转账请求”到“数字凭证”
比特币交易的本质是数字所有权转移,其核心数据结构定义在src/primitives/transaction.h中,主要由版本号、输入列表、输出列表、锁定时间四部分组成。
交易版本(nVersion)
标识交易兼容的协议版本,如当前主流的2(支持隔离见证等新特性),允许未来协议升级时向后兼容。
交易输入(vin)
输入是“花费”已有UTXO(Unspent Transaction Output,未花费交易输出)的引用,结构如下:
class CTxIn {
public:
COutPoint prevout; // 前一笔交易的输出索引(交易ID+输出序号)
CScript scriptSig; // 解锁脚本(签名+公钥,用于证明所有权)
uint32_t nSequence; // 序列号(用于相对锁定时间,如RBF替换)
};
- prevout:明确指出“花谁的钱”(如交易ID
a3b2...的第1个输出); - scriptSig:签名脚本,包含签名和公钥,通过脚本验证逻辑证明“花的是自己的钱”;
- nSequence:默认
0xffffffff,可设置为小于该值的数,用于实现“相对锁定时间”(如锁定未来N个区块后才可花费)。
交易输出(vout)
输出是“转账给谁”的指令,结构如下:
class CTxOut {
public:
int64_t nValue; // 转账金额(单位:satoshi,1 BTC = 1e8 satoshi)
CScript scriptPubKey; // 锁定脚本(定义接收方如何花费)
};
- nValue:精确到satoshi的金额,避免浮点数精度问题;
- scriptPubKey:锁定脚本,定义“谁能花这笔钱”,标准P2PKH(Pay-to-Public-Key-Hash)脚本为
OP_DUP OP_HASH160 <接收方公钥哈希> OP_EQUALVERIFY OP_CHECKSIG,要求提供签名和公钥通过验证。
锁定时间(nLockTime)
交易生效的“截止时间”,可以是区块高度(≤500000000)或UNIX时间戳(>500000000),若未到锁定时间,交易不会被矿工打包。
交易生成:从“用户操作”到“数字签名”
用户发起交易时,需完成“输入选择-签名-广播”三步,核心逻辑在src/wallet/transaction.cpp中实现。
UTXO收集与输入构建
比特币采用UTXO模型而非账户模型,用户需从自己拥有的UTXO池中挑选足够金额作为输入,用户有3个UTXO(分别10、5、2 BTC),需支付8 BTC时,可能选择10 BTC的UTXO,并生成2 BTC的找零输出。
// 伪代码:UTXO选择算法(贪心策略)
std::vector<COutput> SelectUTXOs(const std::vector<COutput>& utxo_pool, int64_t target_amount) {
std::vector<COutput> selected;
int64_t total = 0;
for (const auto& utxo : utxo_pool) {
if (total >= target_amount) break;
selected.push_back(utxo);
total += utxo.txout.nValue;
}
return selected;
}
签名:所有权的数学证明
输入的scriptSig需通过scriptPubKey的验证,这一过程依赖椭圆曲线数字签名算法(ECDSA),签名步骤如下:
- 构造签名脚本模板:将
scriptPubKey的公钥哈希替换为占位符,生成“半签名”交易; - 哈希交易:对交易进行双重SHA256哈希(RPEPE_HASH,确保签名内容不可篡改);
- 生成签名:用用户私钥对哈希值进行ECDSA签名,得到
signature; - 填充scriptSig:将
signature和用户公钥填入scriptSig,完成输入解锁。// 伪代码:ECDSA签名过程 std::vector<unsigned char> Sign(const CScript& scriptPubKey, const CKey& priv_key) { uint256 hash = SignatureHash(scriptPubKey, tx_to_sign, 0, SIGHASH_ALL); return priv_key.Sign(hash); }
广播交易
签名完成后,交易通过P2P网络(src/net/net_processing.cpp)广播给相邻节点,节点验证通过后继续扩散,最终进入内存池(mempool)等待打包。
交易验证:从“节点检查”到“共识确认”
交易验证是比特币安全的核心,分为节点级验证和矿工级验证,规则定义在src/script/interpreter.cpp和src/validation.cpp中。
基础验证(节点级)
节点收到交易后,首先检查:
- 语法正确性:版本号、输入输出数量、脚本格式等是否符合协议;
- 输入有效性:
prevout对应的UTXO是否存在且未被花费; - 金额有效性:输入总金额≥输出总金额(防止凭空创造比特币);
- 脚本验证:执行输入的
scriptSig与输出的scriptPubKey组成的脚本,返回true则验证通过。
脚本验证是核心中的核心,采用基于堆栈的虚拟机执行:
// 伪代码:脚本验证逻辑
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& tx, unsigned int nIn) {
CScriptInterpreter interpreter;
std::vector<unsigned char> stack;
if (!interpreter.Execute(scriptSig, scriptPubKey, tx, nIn, stack)) return false;
return stack.empty() || CastToBool(stack.back());
}
例如P2PKH脚本的执行流程:
scriptSig(签名+公钥)入栈;OP_DUP:复制栈顶元素(公钥);OP_HASH160:对公钥哈希得到RIPEMD160(SHA256(公钥));- 与
scriptPubKey中的公钥哈希比较,OP_EQUALVERIFY确保一致; OP_CHECKSIG:用签名和公钥验证ECDSA签名有效性。
矿工验证与共识打包
矿工从内存池中选择交易打包进区块时,需额外验证:
- 交易费合理性:输入总金额-输出总金额=矿工费,需符合网络默认费率;
- 依赖关系:若交易A依赖交易B的输出,则B必须先被打包(避免双花);
- 隔离见证(SegWit):对于 witness 数据的交易(版本≥2),需验证 witness 脚本(定义在
src/script/script.cpp),将签名数据从交易数据中分离,提升扩容性和安全性。
交易生命周期:从“创建”到“确认”
一次完整的交易需经历“创建-广播-验证-打包-确认”的全流程:
- 创建:用户通过钱包软件生成交易,完成UTXO选择和签名;
- 广播:交易通过P2P网络传播,节点验证后加入内存池;
- 打包:矿工选择高费率交易打包进区块,通过工作量量证明(PoW)竞争记账权;
- 确认:区块链接入主链后,交易被“确认”,每个后续区块增加1个确认数(通常6个确认视为最终)。
源码中的安全设计:防篡改与防双花
比特币交易的安全性源码设计体现在多个细节:
- UTXO模型:每个输出只能被花费一次,通过
prevout引用避免双花; - 脚本系统:支持复杂的锁定逻辑(如多重签名、时间锁),实现可编程性;
- 序列号与RBF:
nSequence支持“Replace-by-Fee”(RBF),允许用户用更高费率替换未确认交易,避免网络拥堵; - 隔离见证:将签名数据分离
