SimpleBTC - 基于比特币的银行系统Demo
欢迎来到SimpleBTC项目文档!
项目简介
SimpleBTC是一个用Rust实现的比特币银行系统演示项目,完整实现了比特币的核心原理和高级特性。本项目不仅是一个学习工具,也是一个功能完备的区块链系统demo。
核心特性
🔐 完整的UTXO模型
- 未花费交易输出(UTXO)管理
- 防双花机制
- 余额计算
- UTXO选择算法
⛓️ 区块链核心功能
- 工作量证明(Proof of Work)
- 区块链验证
- Merkle树实现
- 链式哈希结构
💼 钱包系统
- 密钥对生成(简化版)
- 地址生成
- 数字签名
- 交易创建
📊 高级交易特性
- Replace-By-Fee (RBF): 替换未确认交易,加速确认
- 时间锁(TimeLock): 定期存款、遗产继承
- 多重签名(MultiSig): 2-of-3企业钱包、托管服务
- 交易优先级: 基于手续费的优先排序
🌳 Merkle树与SPV
- 高效的交易验证
- 轻量级客户端支持
- Merkle证明生成与验证
🔧 工程特性
- REST API服务器(Axum框架)
- 持久化存储(JSON)
- 交易索引器
- Electron可视化界面
为什么选择SimpleBTC?
-
教育价值
- 深入理解比特币原理
- 学习Rust区块链开发
- 掌握密码学基础知识
-
完整实现
- 符合ACID事务特性
- 实现比特币核心协议
- 包含高级BIP特性
-
实战案例
- 企业资金管理
- 托管交易服务
- 定期存款系统
-
易于扩展
- 模块化设计
- 清晰的代码结构
- 详细的中文注释
快速开始
# 克隆项目
git clone https://github.com/GeoffreyWang1117/SimpleBTC.git
cd SimpleBTC
# 编译项目
cargo build --release
# 运行Demo
cargo run --bin btc-demo
# 运行REST API服务器
cargo run --bin btc-server
# 运行示例
cargo run --example enterprise_multisig
cargo run --example escrow_service
cargo run --example timelock_savings
系统架构
SimpleBTC/
├── src/
│ ├── transaction.rs # 交易模块(UTXO模型)
│ ├── block.rs # 区块结构
│ ├── blockchain.rs # 区块链核心逻辑
│ ├── wallet.rs # 钱包管理
│ ├── utxo.rs # UTXO集合管理
│ ├── merkle.rs # Merkle树实现
│ ├── multisig.rs # 多重签名
│ ├── advanced_tx.rs # RBF、时间锁、优先级
│ ├── persistence.rs # 持久化存储
│ └── indexer.rs # 交易索引
├── examples/ # 实战案例
├── frontend/ # Electron GUI
└── docs/ # 本文档
技术栈
- 语言: Rust (Edition 2021)
- 核心库:
- sha2 - SHA256哈希
- serde - 序列化
- rand - 随机数生成
- Web框架: Axum (异步REST API)
- 前端: Electron + JavaScript
- 文档: mdBook
学习路径
初级:理解基础概念
中级:掌握核心机制
高级:实现复杂应用
与比特币的差异
SimpleBTC是教育性质的简化实现,与真实比特币的主要差异:
| 特性 | SimpleBTC | 真实比特币 |
|---|---|---|
| 密码学 | 简化的SHA256 | secp256k1椭圆曲线 |
| 签名 | 简化验证 | ECDSA签名 |
| 脚本 | 简化脚本 | 完整Script语言 |
| P2P网络 | 无网络层 | 完整P2P协议 |
| 存储 | JSON文件 | LevelDB数据库 |
| 难度调整 | 固定难度 | 动态难度调整 |
项目状态
- ✅ UTXO模型
- ✅ 工作量证明
- ✅ Merkle树
- ✅ 多重签名
- ✅ RBF机制
- ✅ 时间锁
- ✅ REST API
- ✅ GUI界面
- ✅ 完整文档
贡献
欢迎贡献代码、文档或报告问题!
详见贡献指南
许可证
本项目采用MIT许可证
让我们开始探索比特币的世界吧! 🚀
安装与配置
本章节将指导您完成SimpleBTC的安装和基本配置。
系统要求
最低要求
- 操作系统: Linux, macOS, 或 Windows (WSL2)
- Rust版本: 1.70.0 或更高
- 内存: 至少 2GB RAM
- 存储: 至少 500MB 可用空间
推荐配置
- 操作系统: Linux/macOS
- Rust版本: 最新稳定版
- 内存: 4GB+ RAM
- 存储: 1GB+ 可用空间
- CPU: 多核处理器(挖矿性能更好)
安装Rust
如果您还没有安装Rust,请访问rust-lang.org或使用以下命令:
# Linux/macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 验证安装
rustc --version
cargo --version
克隆项目
# 使用HTTPS克隆
git clone https://github.com/GeoffreyWang1117/SimpleBTC.git
# 或使用SSH
git clone git@github.com:GeoffreyWang1117/SimpleBTC.git
# 进入项目目录
cd SimpleBTC
编译项目
开发模式编译
# 快速编译(未优化,编译快)
cargo build
# 运行测试
cargo test
# 运行Demo
cargo run --bin btc-demo
生产模式编译
# 优化编译(性能最佳,编译慢)
cargo build --release
# 运行优化后的程序
./target/release/btc-demo
./target/release/btc-server
运行示例
SimpleBTC提供了三个实战示例:
# 1. 企业多签钱包(2-of-3)
cargo run --example enterprise_multisig
# 2. 托管服务(买家/卖家/仲裁员)
cargo run --example escrow_service
# 3. 定期存款(时间锁)
cargo run --example timelock_savings
启动REST API服务器
# 开发模式
cargo run --bin btc-server
# 生产模式
cargo run --release --bin btc-server
服务器将在 http://localhost:3000 启动
API端点
GET /api/blockchain/info- 获取区块链信息POST /api/wallet/create- 创建新钱包POST /api/transaction/create- 创建交易POST /api/mine- 挖矿GET /api/balance/:address- 查询余额
启动Electron GUI
# 安装Node.js依赖
cd frontend
npm install
# 启动Electron应用
npm start
GUI提供了可视化界面,包括:
- 区块链浏览器
- 钱包管理
- 交易创建
- 实时挖矿
- 一键Demo模式
项目结构
SimpleBTC/
├── src/ # 源代码
│ ├── lib.rs # 库入口
│ ├── main.rs # CLI Demo
│ ├── transaction.rs # 交易模块
│ ├── block.rs # 区块模块
│ ├── blockchain.rs # 区块链逻辑
│ ├── wallet.rs # 钱包管理
│ ├── utxo.rs # UTXO管理
│ ├── merkle.rs # Merkle树
│ ├── multisig.rs # 多重签名
│ ├── advanced_tx.rs # 高级交易特性
│ ├── persistence.rs # 持久化
│ └── indexer.rs # 索引器
├── examples/ # 示例程序
│ ├── enterprise_multisig.rs
│ ├── escrow_service.rs
│ └── timelock_savings.rs
├── frontend/ # Electron GUI
│ ├── main.js
│ ├── app.js
│ └── index.html
├── docs/ # 文档
├── Cargo.toml # Rust项目配置
└── README.md # 项目说明
配置选项
挖矿难度
在 src/blockchain.rs 中修改:
#![allow(unused)]
fn main() {
pub fn new() -> Blockchain {
let mut blockchain = Blockchain {
difficulty: 3, // 修改这里:3-5适合演示,6+更安全但慢
// ...
}
}
}
区块奖励
#![allow(unused)]
fn main() {
pub fn new() -> Blockchain {
let mut blockchain = Blockchain {
mining_reward: 50, // 修改挖矿奖励(satoshi)
// ...
}
}
}
API服务器端口
在 src/bin/server.rs 中修改:
#![allow(unused)]
fn main() {
let listener = TcpListener::bind("0.0.0.0:3000") // 修改端口
.await
.unwrap();
}
常见问题
编译错误
问题: error: failed to fetch
# 解决方案:更新Cargo索引
cargo update
问题: error: linker 'cc' not found
# Ubuntu/Debian
sudo apt-get install build-essential
# macOS (安装Xcode命令行工具)
xcode-select --install
运行时错误
问题: Address already in use (os error 98)
# 端口3000被占用,杀死占用进程或修改端口
lsof -ti:3000 | xargs kill
问题: 挖矿太慢
# 降低难度
# 在 blockchain.rs 中设置 difficulty: 2
下一步
获取帮助
- GitHub Issues: https://github.com/GeoffreyWang1117/SimpleBTC/issues
- 项目文档: 本站
- Rust社区: https://users.rust-lang.org/
快速入门
这是一个5分钟的快速教程,带您体验SimpleBTC的核心功能。
第一个区块链程序
创建一个新的Rust项目并添加SimpleBTC依赖:
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
};
fn main() {
println!("🚀 SimpleBTC 快速入门\n");
// 1. 创建区块链
let mut blockchain = Blockchain::new();
println!("✓ 区块链已初始化");
// 2. 创建钱包
let alice = Wallet::new();
let bob = Wallet::new();
println!("✓ 创建了两个钱包");
println!(" Alice: {}", alice.address);
println!(" Bob: {}\n", bob.address);
// 3. Alice获得初始资金(从创世区块)
let tx1 = blockchain.create_transaction(
&Wallet::from_address("genesis_address".to_string()),
alice.address.clone(),
10000, // 1万 satoshi
0, // 无手续费(创世交易)
).unwrap();
blockchain.add_transaction(tx1).unwrap();
blockchain.mine_pending_transactions(alice.address.clone()).unwrap();
println!("💰 Alice的余额: {} satoshi", blockchain.get_balance(&alice.address));
// 4. Alice向Bob转账
let tx2 = blockchain.create_transaction(
&alice,
bob.address.clone(),
3000, // 转账3000
10, // 手续费10
).unwrap();
blockchain.add_transaction(tx2).unwrap();
blockchain.mine_pending_transactions(bob.address.clone()).unwrap();
// 5. 查看最终余额
println!("\n💼 最终余额:");
println!(" Alice: {} satoshi", blockchain.get_balance(&alice.address));
println!(" Bob: {} satoshi\n", blockchain.get_balance(&bob.address));
// 6. 验证区块链
if blockchain.is_valid() {
println!("✅ 区块链验证通过!");
}
// 7. 打印区块链信息
blockchain.print_chain();
}
运行结果
🚀 SimpleBTC 快速入门
✓ 区块链已初始化
✓ 创建了两个钱包
Alice: a3f2d8c9e4b7...
Bob: b9e4c7d2a3f1...
区块已挖出: 0003ab4f9c2d...
💰 Alice的余额: 10050 satoshi
区块已挖出: 0007c3e8d1a9...
💼 最终余额:
Alice: 6990 satoshi
Bob: 3060 satoshi
✅ 区块链验证通过!
核心概念速览
1. 区块链(Blockchain)
区块链是区块的链式数据结构,每个区块包含多笔交易。
#![allow(unused)]
fn main() {
let mut blockchain = Blockchain::new();
}
关键方法:
create_transaction()- 创建交易add_transaction()- 添加到待处理池mine_pending_transactions()- 挖矿打包交易get_balance()- 查询余额is_valid()- 验证区块链
2. 钱包(Wallet)
钱包管理公钥、私钥和地址。
#![allow(unused)]
fn main() {
let wallet = Wallet::new();
println!("地址: {}", wallet.address);
println!("公钥: {}", wallet.public_key);
// 私钥应保密!
}
关键方法:
new()- 创建新钱包sign()- 签名数据verify_signature()- 验证签名
3. 交易(Transaction)
交易是价值转移的基本单位,使用UTXO模型。
#![allow(unused)]
fn main() {
let tx = blockchain.create_transaction(
&sender, // 发送者钱包
receiver_addr, // 接收者地址
amount, // 金额(satoshi)
fee, // 手续费(satoshi)
)?;
}
交易包含:
- 输入(Inputs): 花费的UTXO
- 输出(Outputs): 创建的新UTXO
- 手续费: 输入总额 - 输出总额
4. 挖矿(Mining)
挖矿是通过工作量证明(PoW)将交易打包成区块。
#![allow(unused)]
fn main() {
blockchain.mine_pending_transactions(miner_address)?;
}
挖矿过程:
- 收集待处理交易
- 创建Coinbase交易(奖励+手续费)
- 计算Merkle根
- 找到满足难度的哈希(调整nonce)
- 将区块添加到链上
- 更新UTXO集合
进阶示例
多笔交易
#![allow(unused)]
fn main() {
// 创建多笔交易
for i in 1..=5 {
let tx = blockchain.create_transaction(
&alice,
bob.address.clone(),
100 * i,
i, // 不同的手续费
)?;
blockchain.add_transaction(tx)?;
}
// 一次性打包所有交易
blockchain.mine_pending_transactions(miner.address.clone())?;
}
交易费率优先级
#![allow(unused)]
fn main() {
// 低手续费交易
let slow_tx = blockchain.create_transaction(&alice, bob.address.clone(), 1000, 1)?;
// 高手续费交易
let fast_tx = blockchain.create_transaction(&alice, charlie.address.clone(), 1000, 50)?;
blockchain.add_transaction(slow_tx)?;
blockchain.add_transaction(fast_tx)?;
// 矿工会优先打包fast_tx(更高费率)
blockchain.mine_pending_transactions(miner.address)?;
}
余额查询
#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&alice.address);
println!("余额: {} satoshi ({:.8} BTC)", balance, balance as f64 / 100_000_000.0);
}
REST API 使用
启动API服务器:
cargo run --bin btc-server
创建钱包
curl -X POST http://localhost:3000/api/wallet/create
响应:
{
"address": "a3f2d8c9e4b7...",
"public_key": "04f9a...",
"private_key": "私钥请妥善保管"
}
创建交易
curl -X POST http://localhost:3000/api/transaction/create \
-H "Content-Type: application/json" \
-d '{
"from": "alice_address",
"to": "bob_address",
"amount": 5000,
"fee": 10
}'
查询余额
curl http://localhost:3000/api/balance/alice_address
挖矿
curl -X POST http://localhost:3000/api/mine \
-H "Content-Type: application/json" \
-d '{
"miner_address": "miner_address"
}'
查看区块链信息
curl http://localhost:3000/api/blockchain/info
响应:
{
"chain_length": 3,
"difficulty": 3,
"pending_transactions": 2,
"latest_block": {
"index": 2,
"hash": "0003ab4f...",
"timestamp": 1703001234,
"transaction_count": 5
}
}
Electron GUI
启动图形界面:
cd frontend
npm install
npm start
GUI功能:
- 📊 区块链浏览器: 可视化查看所有区块
- 👛 钱包管理: 创建、导入钱包
- 💸 发送交易: 图形化创建交易
- ⛏️ 挖矿: 点击按钮开始挖矿
- 🎮 Demo模式: 一键运行完整演示
实战案例
SimpleBTC提供了三个完整的实战案例:
1. 企业多签钱包(2-of-3)
cargo run --example enterprise_multisig
学习内容:
- 创建多签地址
- 收集签名
- 企业资金管理
2. 托管服务
cargo run --example escrow_service
学习内容:
- 买卖双方交易
- 仲裁机制
- 争议解决
3. 定期存款
cargo run --example timelock_savings
学习内容:
- 时间锁设置
- 到期检查
- 强制储蓄
下一步学习
现在您已经掌握了基础使用,可以继续深入学习:
-
理解原理 - 基本概念
- UTXO模型详解
- 工作量证明原理
- Merkle树结构
-
核心功能 - 核心模块指南
- 钱包深入使用
- 交易高级特性
- 区块链操作
-
高级特性 - 高级功能
- Merkle树与SPV
- 多重签名
- Replace-By-Fee
- 时间锁
-
API文档 - API参考
- 完整的API文档
- 函数签名
- 使用示例
小贴士
💡 提示:
- 挖矿难度3-4适合演示,6+更接近真实
- 手续费越高,交易越快被确认
- 定期调用
is_valid()验证区块链完整性 - 使用
print_chain()查看详细信息
⚠️ 注意:
- 私钥一旦丢失无法恢复
- 本项目仅用于学习,不要用于生产
- 简化的密码学实现不如真实比特币安全
准备好深入探索了吗?继续阅读基本概念!
基本概念
本章节详细解释SimpleBTC和比特币的核心概念。
UTXO模型
什么是UTXO?
UTXO (Unspent Transaction Output) 即“未花费的交易输出“,是比特币的核心概念。
账户模型 vs UTXO模型对比:
| 特性 | 账户模型(以太坊) | UTXO模型(比特币) |
|---|---|---|
| 余额存储 | 每个账户有余额字段 | 由所有UTXO计算得出 |
| 状态 | 账户状态(余额、nonce) | 无状态(只有UTXO集合) |
| 转账 | A账户-100,B账户+100 | 消费A的UTXO,创建B的新UTXO |
| 隐私性 | 较差(同一地址重复使用) | 较好(每次可用新地址) |
| 并行性 | 较差(同账户交易需串行) | 较好(不同UTXO可并行) |
UTXO示例
Alice有3个UTXO:
UTXO1: 5 BTC(从Bob收到)
UTXO2: 3 BTC(从Charlie收到)
UTXO3: 2 BTC(挖矿奖励)
Alice的总余额: 5 + 3 + 2 = 10 BTC
UTXO的生命周期
1. 创建
交易输出 → 加入UTXO集合
2. 存在
UTXO集合 → 可被查询和使用
3. 花费
交易输入引用 → 从UTXO集合移除
4. 新UTXO创建
交易输出 → 新的UTXO加入集合
找零机制
UTXO必须完整花费,无法部分花费:
#![allow(unused)]
fn main() {
// Alice要给Bob转3 BTC,但只有一个5 BTC的UTXO
输入:
- UTXO: 5 BTC(Alice的)
输出:
- 输出1: 3 BTC → Bob
- 输出2: 1.999 BTC → Alice(找零)
- 手续费: 0.001 BTC → 矿工(输入-输出)
}
区块与区块链
区块结构
┌─────────────────────────────────┐
│ 区块头 (Block Header) │
├─────────────────────────────────┤
│ index: 123 │ 区块高度
│ timestamp: 1703001234 │ 时间戳
│ previous_hash: 0x00012ab... │ 父区块哈希
│ merkle_root: 0xabc123... │ Merkle树根
│ nonce: 2847563 │ 工作量证明
│ hash: 0x000034cd... │ 当前区块哈希
├─────────────────────────────────┤
│ 区块体 (Block Body) │
├─────────────────────────────────┤
│ Transaction 1 (Coinbase) │ 挖矿奖励
│ Transaction 2 │ 普通交易
│ Transaction 3 │ 普通交易
│ ... │
└─────────────────────────────────┘
链式结构
Genesis Block → Block 1 → Block 2 → ... → Latest Block
↓ ↓ ↓ ↓
hash=A hash=B hash=C hash=Z
prev=0 prev=A prev=B prev=Y
每个区块通过previous_hash指向父区块,形成不可篡改的链。
为什么不可篡改?
- 哈希链接: 改变任何交易会改变区块哈希
- 后续失效: 区块哈希改变会破坏所有后续区块的
previous_hash - 计算成本: 要篡改历史,必须重新挖所有后续区块
- 最长链: 攻击者需要比全网更快,几乎不可能(51%攻击除外)
工作量证明(Proof of Work)
挖矿原理
找到一个nonce值,使得区块哈希满足难度要求:
#![allow(unused)]
fn main() {
target = "000..." // difficulty个前导0
while hash(block_header + nonce) >= target {
nonce++;
}
}
难度示例
difficulty = 3 (demo)
target = "000..."
有效哈希:
✅ 0003ab4f9c2d...
✅ 000f12e8a3b9...
无效哈希:
❌ 001a3f2e8d4c... (只有2个0)
❌ 0123456789ab... (只有1个0)
难度与安全性
| 难度 | 平均尝试次数 | 适用场景 |
|---|---|---|
| 1 | 16次 | 测试 |
| 3 | 4,096次 | Demo演示 |
| 5 | 1,048,576次 | 小型网络 |
| 10 | ~10¹² 次 | 私有链 |
| 20 | ~10²⁴ 次 | 比特币级别 |
比特币实际难度约70-80位,全网算力数百EH/s。
为什么需要PoW?
- 防止垃圾攻击: 创建区块需要计算成本
- 公平竞争: 算力越大,获胜概率越高
- 去中心化: 任何人都可以参与挖矿
- 经济激励: 矿工获得奖励(Coinbase + 手续费)
Merkle树
结构示例
4笔交易的Merkle树:
Root Hash
/ \
H(AB) H(CD)
/ \ / \
H(A) H(B) H(C) H(D)
↑ ↑ ↑ ↑
Tx1 Tx2 Tx3 Tx4
构建过程:
- 对每笔交易计算哈希(叶子节点)
- 两两配对,计算父节点哈希
- 重复直到只剩一个根哈希
- 根哈希存储在区块头
SPV验证(轻量级验证)
不下载整个区块,只下载区块头和Merkle证明:
验证Tx2在区块中:
需要:
- Tx2的哈希
- Merkle证明: [H(A), H(CD)]
- 区块头中的Root Hash
验证:
1. 计算 H(B) = hash(Tx2)
2. 计算 H(AB) = hash(H(A) + H(B))
3. 计算 Root = hash(H(AB) + H(CD))
4. 对比计算的Root与区块头中的Root
✅ 匹配 → Tx2确实在区块中
❌ 不匹配 → Tx2不在或被篡改
SPV的优势
- 轻量: 只需区块头(~80字节),不需完整区块(1-2MB)
- 快速: O(log n)验证复杂度
- 移动友好: 手机钱包可以运行
- 安全: 依赖PoW保护,无需信任第三方
密码学基础
哈希函数(SHA256)
特性:
- 确定性:相同输入总是产生相同输出
- 快速计算:毫秒级
- 不可逆:无法从哈希反推原文
- 抗碰撞:找到两个相同哈希的输入几乎不可能
- 雪崩效应:输入微小变化导致哈希完全不同
示例:
hash("hello") = 2cf24dba5fb0a30e...
hash("hallo") = d3751d33f9cd5049... (完全不同!)
数字签名(ECDSA简化版)
真实比特币:
1. 私钥(256位随机数)
↓ 椭圆曲线运算
2. 公钥(椭圆曲线点)
↓ SHA256 + RIPEMD160
3. 地址(Base58编码)
SimpleBTC简化:
1. 私钥(随机字符串)
↓ SHA256
2. 公钥(哈希值)
↓ SHA256取前20字节
3. 地址(十六进制字符串)
签名验证
#![allow(unused)]
fn main() {
// 签名
signature = hash(private_key + data)
// 验证(简化版)
verify(public_key, data, signature) -> bool
}
真实比特币使用ECDSA算法,数学上可证明安全。
交易结构
交易剖析
#![allow(unused)]
fn main() {
Transaction {
id: "abc123...", // 交易哈希
inputs: [ // 输入(花费哪些UTXO)
TxInput {
txid: "prev_tx", // 引用的交易ID
vout: 0, // 输出索引
signature: "...", // 签名
pub_key: "...", // 公钥
}
],
outputs: [ // 输出(创建哪些UTXO)
TxOutput {
value: 3000, // 金额(satoshi)
pub_key_hash: "bob_address",
},
TxOutput {
value: 6990, // 找零
pub_key_hash: "alice_address",
}
],
timestamp: 1703001234,
fee: 10, // 手续费
}
}
交易验证
矿工验证交易时检查:
- ✅ 签名有效: 每个输入的签名正确
- ✅ UTXO存在: 引用的UTXO在UTXO集合中
- ✅ 未双花: UTXO没有被其他交易花费
- ✅ 余额充足: 输入总额 ≥ 输出总额
- ✅ 格式正确: 符合协议规范
Coinbase交易
每个区块的第一笔交易,用于发放挖矿奖励:
#![allow(unused)]
fn main() {
Transaction {
id: "coinbase_tx",
inputs: [
TxInput {
txid: "", // 空(不引用UTXO)
vout: 0,
signature: "coinbase",
pub_key: "coinbase",
}
],
outputs: [
TxOutput {
value: 50 + total_fees, // 奖励 + 手续费
pub_key_hash: "miner_address",
}
],
fee: 0,
}
}
共识机制
最长链规则
当出现分叉时,网络选择工作量最大的链:
Block 3a (PoW难度3)
/
Block 2
\
Block 3b → Block 4b (PoW难度3)
Block 4b所在的链总难度更高,成为主链。Block 3a被孤立。
为什么是最长链?
- 工作量: 长链代表更多计算投入
- 多数共识: 诚实节点总是挖最长链
- 攻击难度: 攻击者需要超过全网51%算力
6个确认规则
Your Tx → Block N → N+1 → N+2 → N+3 → N+4 → N+5 → N+6
0确认 1确认 2确认 3确认 4确认 5确认 6确认
- 0确认:可能被双花(RBF)
- 1确认:较安全(小额支付)
- 3确认:安全(中等金额)
- 6确认:非常安全(大额转账)
手续费市场
费率计算
fee_rate = fee / transaction_size (sat/byte)
优先级
矿工选择交易的策略:
#![allow(unused)]
fn main() {
// 按费率从高到低排序
transactions.sort_by(|a, b| {
b.fee_rate().cmp(&a.fee_rate())
});
}
高费率交易优先打包。
手续费推荐
| 紧急程度 | 费率 | 确认时间 |
|---|---|---|
| 低优先级 | 1-5 sat/byte | 数小时 |
| 中优先级 | 5-20 sat/byte | 30-60分钟 |
| 高优先级 | 20-50 sat/byte | 10-20分钟 |
| 紧急 | 50+ sat/byte | 下一个区块 |
网络参数
时间相关
- 出块时间: 约10分钟(通过难度调整维持)
- 难度调整: 每2016个区块(约2周)
- 减半周期: 每210,000个区块(约4年)
经济参数
- 初始奖励: 50 BTC
- 当前奖励: 3.125 BTC(2024年减半后)
- 总供应量: 2100万BTC(永不增发)
- 最小单位: 1 satoshi = 0.00000001 BTC
大小限制
- 区块大小: 1 MB (原始) / 4 MB (SegWit)
- 交易大小: 平均250-500字节
- 每区块交易数: 约2000-3000笔
下一步
现在您已经理解了核心概念,可以继续学习:
💡 小测验:尝试回答以下问题检验您的理解:
- UTXO模型与账户模型的主要区别是什么?
- 为什么区块链是不可篡改的?
- Merkle树如何实现SPV验证?
- 工作量证明的目的是什么?
- 什么是找零机制?
架构概览
SimpleBTC 是一个功能完整的比特币区块链教育实现,用 Rust 编写。本章介绍项目的整体架构设计、模块组织方式、核心数据流以及关键抽象层。
项目结构
SimpleBTC/
├── src/
│ ├── lib.rs # Crate 入口,模块导出与重新导出
│ ├── main.rs # 可执行入口(演示 / 交互式 CLI)
│ │
│ │── ── 核心层 (Core) ──
│ ├── block.rs # 区块结构 + 工作量证明
│ ├── blockchain.rs # 区块链核心逻辑(UTXO 管理、挖矿、验证)
│ ├── transaction.rs # 交易结构(TxInput / TxOutput / Transaction)
│ ├── wallet.rs # 钱包 + secp256k1 密钥管理
│ ├── utxo.rs # UTXO 集合(未花费交易输出管理)
│ ├── crypto.rs # 扩展密码学(Bech32、WIF 导出)
│ │
│ │── ── 高级特性层 (Advanced) ──
│ ├── merkle.rs # Merkle 树(交易包含证明)
│ ├── multisig.rs # 多重签名(M-of-N)
│ ├── advanced_tx.rs # 高级交易(RBF、TimeLock)
│ ├── mempool.rs # 内存池(按费率排序)
│ ├── script.rs # Bitcoin Script 脚本系统
│ ├── spv.rs # SPV 轻客户端验证
│ ├── parallel_mining.rs # 多线程并行 PoW 挖矿
│ ├── network.rs # P2P 网络层
│ │
│ │── ── 基础设施层 (Infrastructure) ──
│ ├── storage.rs # RocksDB 高性能持久化存储
│ ├── persistence.rs # 序列化 / 反序列化辅助
│ ├── config.rs # 全局配置(难度、奖励等)
│ ├── logging.rs # 结构化日志(tracing)
│ ├── security.rs # 安全验证
│ ├── indexer.rs # 交易索引器(加速地址查询)
│ └── error.rs # 统一错误类型
│
├── docs/ # mdBook 文档
├── Cargo.toml
└── README.md
三层架构模型
SimpleBTC 的模块按职责分为三个层次:
┌─────────────────────────────────────────────────────────┐
│ 核心层 (Core) │
│ block blockchain transaction wallet utxo crypto │
│ ─── 实现比特币的基本数据结构和协议规则 ─── │
├─────────────────────────────────────────────────────────┤
│ 高级特性层 (Advanced) │
│ merkle multisig advanced_tx mempool script spv │
│ parallel_mining network │
│ ─── 实现比特币的高级功能与扩展协议 ─── │
├─────────────────────────────────────────────────────────┤
│ 基础设施层 (Infrastructure) │
│ storage persistence config logging security │
│ indexer error │
│ ─── 提供存储、日志、配置等通用基础能力 ─── │
└─────────────────────────────────────────────────────────┘
核心层模块详解
| 模块 | 文件 | 职责 |
|---|---|---|
block | block.rs | 定义 Block 结构体,包含区块头字段(index、timestamp、nonce、merkle_root、previous_hash、hash)以及单线程 mine_block() 方法 |
blockchain | blockchain.rs | Blockchain 主结构体,统筹区块链全部操作:创世区块、交易创建、内存池管理、并行挖矿、UTXO 更新、链验证 |
transaction | transaction.rs | TxInput、TxOutput、Transaction 三个核心结构体;Coinbase 交易构造;ECDSA 签名验证 |
wallet | wallet.rs | Wallet 结构体,使用 secp256k1 生成真实密钥对,P2PKH 地址推导,ECDSA 签名与验证 |
utxo | utxo.rs | UTXOSet 管理所有未花费交易输出,支持余额查询、可花费 UTXO 检索 |
crypto | crypto.rs | CryptoWallet 扩展实现:Bech32 地址、WIF 私钥格式导入导出 |
高级特性层模块详解
| 模块 | 文件 | 职责 |
|---|---|---|
merkle | merkle.rs | Merkle 树构建与 Merkle 证明生成/验证(SPV 的基础) |
multisig | multisig.rs | M-of-N 多重签名方案 |
advanced_tx | advanced_tx.rs | RBF(Replace-By-Fee)费用替换、TimeLock 时间锁交易 |
mempool | mempool.rs | 内存池,按费率(satoshi/byte)优先级排序待确认交易 |
script | script.rs | Bitcoin Script 操作码解释器 |
spv | spv.rs | 简单支付验证,使用 Merkle 证明在不下载完整链的情况下验证交易 |
parallel_mining | parallel_mining.rs | ParallelMiner:多线程 PoW,充分利用多核 CPU |
network | network.rs | P2P 网络消息传播层 |
基础设施层模块详解
| 模块 | 文件 | 职责 |
|---|---|---|
storage | storage.rs | 基于 RocksDB 的高性能键值存储 |
persistence | persistence.rs | 区块链数据序列化与反序列化 |
config | config.rs | 全局参数(挖矿难度、区块奖励、网络参数等) |
logging | logging.rs | 结构化日志(基于 tracing crate) |
security | security.rs | 额外的安全验证逻辑 |
indexer | indexer.rs | TransactionIndexer:为地址 → 交易ID 建立索引,加速余额查询 |
error | error.rs | BitcoinError 统一错误枚举,Result<T> 类型别名 |
核心数据流
完整的价值转移流程
用户创建交易请求
│
▼
┌─────────────────────────────────────┐
│ Blockchain::create_transaction() │
│ 1. 在 UTXOSet 中查找可花费输出 │
│ 2. 用 Wallet::sign() 对输入签名 │
│ 3. 构造 TxInput + TxOutput │
│ 4. 生成 Transaction(含哈希 ID) │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Blockchain::add_transaction() │
│ 1. Transaction::verify() 验证签名 │
│ 2. 检查 UTXO 存在 + 余额足够 │
│ 3. 记录 pending_spent 防双花 │
│ 4. 添加到 Mempool(按费率排序) │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Blockchain::mine_pending_transactions() │
│ 1. 从 Mempool 取出高费率交易 │
│ 2. 构造 Coinbase 交易(奖励+费用) │
│ 3. Block::new() 计算 Merkle Root │
│ 4. ParallelMiner 多线程 PoW 挖矿 │
│ 5. 验证区块中所有交易 │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ UTXO 集合原子更新 │
│ 1. 消费输入中引用的 UTXO │
│ 2. 将输出添加为新 UTXO │
│ 3. 清空 pending_spent │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 区块上链 │
│ 1. indexer.index_block() 建索引 │
│ 2. chain.push(block) │
│ 3. 从 Mempool 删除已确认交易 │
└─────────────────────────────────────┘
数据结构关系图
Blockchain
├── chain: Vec<Block>
│ └── Block
│ ├── index, timestamp, nonce
│ ├── previous_hash → 上一区块 hash(链式连接)
│ ├── merkle_root → MerkleTree 计算
│ ├── hash → SHA256(index+timestamp+merkle_root+prev+nonce)
│ └── transactions: Vec<Transaction>
│ └── Transaction
│ ├── id → SHA256(交易内容)
│ ├── inputs: Vec<TxInput>
│ │ └── TxInput {txid, vout, signature, pub_key}
│ └── outputs: Vec<TxOutput>
│ └── TxOutput {value, pub_key_hash}
│
├── utxo_set: UTXOSet ← 快速余额查询与 UTXO 检索
├── mempool: Mempool ← 待确认交易(按费率排序)
├── indexer: TransactionIndexer ← 地址→交易索引
└── miner: ParallelMiner ← 多线程 PoW
快速开始
#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};
// 1. 创建区块链(含创世区块,创世钱包获得 10M satoshi 初始资金)
let mut blockchain = Blockchain::new();
// 2. 获取预置资金的创世钱包 + 创建新用户钱包
let genesis = Blockchain::genesis_wallet();
let alice = Wallet::new();
let bob = Wallet::new();
// 3. 创建交易:genesis → alice,转账 1000 satoshi,手续费 10
let tx = blockchain.create_transaction(&genesis, alice.address.clone(), 1000, 10)?;
blockchain.add_transaction(tx)?;
// 4. 挖矿(alice 作为矿工地址接收奖励)
blockchain.mine_pending_transactions(alice.address.clone())?;
// 5. 查询余额
println!("Alice 余额: {} satoshi", blockchain.get_balance(&alice.address));
// 6. 验证整个链的完整性
assert!(blockchain.is_valid());
Ok::<(), String>(())
}
密码学选型
| 算法 | 库 | 用途 |
|---|---|---|
| secp256k1 ECDSA | secp256k1 crate | 私钥生成、交易签名、签名验证 |
| SHA-256 | bitcoin_hashes | 区块哈希、交易哈希、地址推导 |
| RIPEMD-160 | ripemd crate | 公钥哈希(P2PKH 地址中间步骤) |
| Base58Check | bs58 crate | P2PKH 地址编码、WIF 私钥编码 |
| Bech32 | bech32 crate | 原生隔离见证(SegWit)地址 |
| SHA-256d | bitcoin_hashes | 双重哈希(校验和计算) |
所有密码学实现均与比特币主网兼容——Wallet::genesis() 生成的地址可以在真实比特币协议中合法使用。
并发设计
挖矿(ParallelMiner)是项目中唯一大量使用多线程的模块。区块链状态本身(Blockchain 结构体)采用单线程所有权模型,通过 Rust 借用检查器在编译期保证数据安全,无需运行时锁开销。
#![allow(unused)]
fn main() {
// 并行挖矿:根据 CPU 核心数自动分配 nonce 搜索范围
self.miner
.mine_block(&mut block, self.difficulty)
.map_err(|e| format!("挖矿失败: {}", e))?;
}
错误处理
所有公共 API 返回 Result<T, String> 或 crate::error::Result<T>(即 Result<T, BitcoinError>)。BitcoinError 是统一的枚举类型,覆盖:
PrivateKeyError— 密钥格式错误- 余额不足、UTXO 不存在、签名验证失败等业务错误
#![allow(unused)]
fn main() {
use bitcoin_simulation::{BitcoinError, Result};
fn example() -> Result<()> {
let blockchain = Blockchain::new();
// ...
Ok(())
}
}
钱包管理
比特币钱包的本质是一对密钥:私钥和公钥。SimpleBTC 使用与真实比特币完全兼容的 secp256k1 椭圆曲线密码学,实现了 Wallet(主钱包)和 CryptoWallet(扩展钱包,支持 Bech32 和 WIF)两个结构体。
密钥体系概览
比特币的密钥生成遵循严格的单向推导链:
随机数(256 bit)
│
▼ secp256k1 椭圆曲线乘法
私钥 (SecretKey, 32 字节)
│
▼ G 点标量乘
公钥 (PublicKey, 33 字节压缩格式)
│
├─▶ SHA-256 哈希
│ │
│ ▼ RIPEMD-160 哈希
│ 公钥哈希 (20 字节)
│ │
│ ▼ 版本前缀 0x00 + 双 SHA-256 校验和 + Base58
│ P2PKH 地址(以 '1' 开头)
│
└─▶ SHA-256 + RIPEMD-160
│
▼ Bech32 编码(witness v0)
Bech32 地址(以 'bc1' 开头)
椭圆曲线方程(secp256k1):
y² = x³ + 7 (mod p)
p = 2²⁵⁶ − 2³² − 977 (一个巨大的素数)
私钥到公钥的推导是单向的,在计算上不可逆(离散对数难题)。
Wallet 结构体
Wallet 是项目中最常用的钱包类型,定义于 src/wallet.rs:
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Wallet {
/// 比特币 P2PKH 地址(以 '1' 开头)
pub address: String,
/// 压缩公钥的十六进制表示(33 字节 = 66 hex 字符)
pub public_key: String,
/// secp256k1 私钥(序列化时以十六进制存储,访问受限)
#[serde(with = "secret_key_serde")]
private_key: SecretKey,
}
}
字段说明:
address:P2PKH 格式地址,公开使用,可安全分享给他人作为收款地址public_key:压缩公钥(33 字节),用于验证签名,包含在每个交易输入中private_key:私钥,必须严格保密,拥有私钥等同于拥有对应地址的所有资金
创建钱包
生成随机钱包
#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;
let wallet = Wallet::new();
println!("地址: {}", wallet.address); // 以 '1' 开头的 P2PKH 地址
println!("公钥: {}", wallet.public_key); // 66 字符十六进制
println!("私钥: {}", wallet.private_key_hex()); // 64 字符十六进制(保密!)
}
Wallet::new() 内部流程:
#![allow(unused)]
fn main() {
pub fn new() -> Self {
let secp = Secp256k1::new();
// 使用密码学安全的随机数生成器(OsRng)
let (secret_key, public_key) = secp.generate_keypair(&mut rand::thread_rng());
let address = Self::pubkey_to_address(&public_key);
let public_key_hex = hex::encode(public_key.serialize());
Wallet { address, public_key: public_key_hex, private_key: secret_key }
}
}
创世钱包
创世钱包使用固定的私钥 0x01,每次启动都生成相同的地址,方便演示时花费创世区块中的初始资金:
#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};
// 两种等价的获取方式
let genesis = Blockchain::genesis_wallet();
let genesis2 = Wallet::genesis();
assert_eq!(genesis.address, genesis2.address); // 地址确定性一致
// 内部实现(src/wallet.rs)
pub fn genesis() -> Self {
Self::from_private_key_hex(
"0000000000000000000000000000000000000000000000000000000000000001",
)
.expect("genesis private key is valid")
}
}
警告: 创世钱包的私钥是公开的,切勿在生产环境中使用。
从私钥恢复钱包
#![allow(unused)]
fn main() {
// 从十六进制私钥恢复
let wallet = Wallet::new();
let hex = wallet.private_key_hex(); // 导出私钥
let recovered = Wallet::from_private_key_hex(&hex)?;
assert_eq!(wallet.address, recovered.address); // 地址完全一致
}
P2PKH 地址推导
Wallet::pubkey_to_address() 实现了与真实比特币一致的地址生成步骤:
#![allow(unused)]
fn main() {
fn pubkey_to_address(public_key: &PublicKey) -> String {
// 步骤 1:压缩公钥序列化(33 字节:1 字节前缀 + 32 字节 x 坐标)
let pubkey_bytes = public_key.serialize();
// 步骤 2:SHA-256 哈希
let sha256_hash = sha256::Hash::hash(&pubkey_bytes);
// 步骤 3:RIPEMD-160 哈希 → 公钥哈希(20 字节)
let mut ripemd = Ripemd160::new();
ripemd.update(&sha256_hash[..]);
let pubkey_hash = ripemd.finalize();
// 步骤 4:添加版本字节(主网 = 0x00)
let mut versioned = vec![0x00];
versioned.extend_from_slice(&pubkey_hash); // 总共 21 字节
// 步骤 5:双 SHA-256 取前 4 字节作为校验和
let checksum = sha256d::Hash::hash(&versioned);
versioned.extend_from_slice(&checksum[0..4]); // 总共 25 字节
// 步骤 6:Base58 编码 → 以 '1' 开头的地址(约 34 字符)
bs58::encode(versioned).into_string()
}
}
为什么用 RIPEMD-160?
- 将 33 字节公钥压缩为 20 字节,节省区块链存储空间
- 即使量子计算机破解了 ECDSA,攻击者仍需额外破解哈希函数
为什么用 Base58(而非 Base64)?
- 去掉了容易混淆的字符:0(零)、O(大写 O)、I(大写 i)、l(小写 L)
- 避免双击复制时包含空格等问题
交易签名
Wallet::sign() 使用私钥对数据生成 ECDSA 签名:
#![allow(unused)]
fn main() {
pub fn sign(&self, data: &str) -> String {
let secp = Secp256k1::new();
// 1. 对原始数据进行 SHA-256 哈希
let msg_hash = sha256::Hash::hash(data.as_bytes());
let message = Message::from_digest(msg_hash.to_byte_array());
// 2. 使用私钥生成 ECDSA 签名
let signature = secp.sign_ecdsa(&message, &self.private_key);
// 3. DER 编码后返回十六进制字符串
hex::encode(signature.serialize_der())
}
}
在 Blockchain::create_transaction() 中,签名的数据是 "{txid}{vout}",即被引用 UTXO 的位置标识:
#![allow(unused)]
fn main() {
// src/blockchain.rs 节选
for (txid, vout) in utxos {
let signature = from_wallet.sign(&format!("{}{}", txid, vout));
let input = TxInput::new(txid, vout, signature, from_wallet.public_key.clone());
inputs.push(input);
}
}
这样每个输入的签名都绑定到具体的 UTXO,防止签名被重放到其他 UTXO 上。
签名验证
Wallet::verify_signature() 是静态方法,不需要持有私钥:
#![allow(unused)]
fn main() {
pub fn verify_signature(public_key_hex: &str, data: &str, signature_hex: &str) -> bool {
// 1. 解码公钥
let Ok(pubkey_bytes) = hex::decode(public_key_hex) else { return false; };
let Ok(public_key) = PublicKey::from_slice(&pubkey_bytes) else { return false; };
// 2. 解码 DER 签名
let Ok(sig_bytes) = hex::decode(signature_hex) else { return false; };
let Ok(signature) = Signature::from_der(&sig_bytes) else { return false; };
// 3. 重新哈希原始数据(与签名时完全一致)
let secp = Secp256k1::new();
let msg_hash = sha256::Hash::hash(data.as_bytes());
let message = Message::from_digest(msg_hash.to_byte_array());
// 4. 数学验证:检查签名是否由对应私钥生成
secp.verify_ecdsa(&message, &signature, &public_key).is_ok()
}
}
完整的签名 + 验证示例:
#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;
let wallet = Wallet::new();
let data = "Hello, Bitcoin!";
// 签名
let signature = wallet.sign(data);
println!("签名: {}", &signature[..32]); // DER 编码的十六进制
// 验证正确数据:应通过
assert!(Wallet::verify_signature(&wallet.public_key, data, &signature));
// 验证篡改数据:应失败
assert!(!Wallet::verify_signature(&wallet.public_key, "Tampered!", &signature));
// 用错误公钥验证:应失败
let other = Wallet::new();
assert!(!Wallet::verify_signature(&other.public_key, data, &signature));
}
扩展钱包:CryptoWallet
src/crypto.rs 中的 CryptoWallet 在 Wallet 基础上增加了更多比特币协议功能:
#![allow(unused)]
fn main() {
use bitcoin_simulation::crypto::CryptoWallet;
let wallet = CryptoWallet::new();
println!("P2PKH 地址: {}", wallet.address); // 以 '1' 开头
println!("Bech32 地址: {}", wallet.bech32_address); // 以 'bc1' 开头(SegWit)
println!("私钥十六进制: {}", wallet.private_key_hex()); // 64 字符
println!("公钥十六进制: {}", wallet.public_key_hex()); // 66 字符
}
WIF 私钥格式
WIF(Wallet Import Format)是比特币钱包之间导入导出私钥的标准格式:
#![allow(unused)]
fn main() {
let wallet = CryptoWallet::new();
// 导出为 WIF(以 '5'、'K' 或 'L' 开头)
let wif = wallet.export_private_key_wif();
println!("WIF: {}", wif);
// 从 WIF 恢复钱包
let imported = CryptoWallet::import_from_wif(&wif)?;
assert_eq!(wallet.address, imported.address);
}
WIF 格式的编码步骤:
- 添加版本字节
0x80(主网私钥前缀) - 计算双 SHA-256 校验和(取前 4 字节)
- 拼接后进行 Base58 编码
CryptoWallet 签名接口
CryptoWallet 的签名接口接受字节切片,更灵活:
#![allow(unused)]
fn main() {
let wallet = CryptoWallet::new();
let message = b"Hello, Bitcoin!";
// 签名(返回 secp256k1::ecdsa::Signature 类型)
let signature = wallet.sign(message);
// 验证(静态方法)
assert!(CryptoWallet::verify(message, &signature, &wallet.public_key));
}
钱包序列化
Wallet 和 CryptoWallet 均实现了 Serialize / Deserialize,私钥以十六进制字符串安全存储:
#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;
let wallet = Wallet::new();
// 序列化为 JSON
let json = serde_json::to_string(&wallet)?;
// {"address":"1...","public_key":"02...","private_key":"a1b2c3..."}
// 从 JSON 恢复,签名能力完整保留
let restored: Wallet = serde_json::from_str(&json)?;
let sig = restored.sign("test");
assert!(Wallet::verify_signature(&restored.public_key, "test", &sig));
}
安全建议
| 注意事项 | 说明 |
|---|---|
| 私钥保密 | 任何人获得私钥即可花费对应地址的全部资金 |
| 不要重用地址 | 每次收款使用新地址,保护隐私 |
| 创世钱包仅用于演示 | Wallet::genesis() 使用公开私钥,绝不能用于真实资金 |
| 备份私钥 | 丢失私钥意味着永久失去对应资金 |
| 使用 WIF 格式备份 | WIF 格式含校验和,可检测录入错误 |
交易处理
比特币的核心创新之一是 UTXO(Unspent Transaction Output)模型。与银行账户余额不同,比特币系统中不存在“账户余额“这一概念——所有资金都以未花费交易输出的形式分散存在于区块链中。本章深入介绍 SimpleBTC 的交易结构、UTXO 模型、交易创建和验证机制。
UTXO 模型基础
什么是 UTXO
UTXO 是“Unspent Transaction Output“(未花费交易输出)的缩写。每笔比特币交易消费若干现有 UTXO(作为输入),同时创造若干新 UTXO(作为输出)。
┌──────────────────────────────────────────────────────────────────┐
│ 传统账户模型(如银行) │
│ Alice 余额: 100 Bob 余额: 0 │
│ 转账 30 → Alice: 70, Bob: 30 │
├──────────────────────────────────────────────────────────────────┤
│ UTXO 模型(比特币) │
│ 区块链上存在:UTXO_A (属于 Alice, 值 100) │
│ 转账 30: │
│ 消费:UTXO_A (100) ← 必须整体消费,不能部分花费 │
│ 创造:UTXO_B (30, 属于 Bob) ← 转账金额 │
│ UTXO_C (60, 属于 Alice) ← 找零(100 - 30 - 10 手续费) │
└──────────────────────────────────────────────────────────────────┘
UTXO 模型的关键特性:
- 每个 UTXO 只能被花费一次(花费后从 UTXO 集合中移除)
- 花费时必须消费完整的 UTXO,多余的部分以“找零“输出返还给发送者
- “余额”= 某地址拥有的所有 UTXO 价值之和(由
UTXOSet计算) - 没有被花费的 UTXO 形成“UTXO 集“(比特币全节点需要维护约 5-10 GB 的 UTXO 集)
交易数据结构
TxInput(交易输入)
交易输入引用一个现有的 UTXO,并提供花费它的授权证明(数字签名):
#![allow(unused)]
fn main() {
// src/transaction.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TxInput {
pub txid: String, // 被引用交易的 ID(32 字节哈希,64 hex 字符)
pub vout: usize, // 该交易中第几个输出(从 0 开始)
pub signature: String, // ECDSA 签名(DER 编码,hex 字符串)
pub pub_key: String, // 发送者的压缩公钥(33 字节,66 hex 字符)
}
}
txid + vout 的组合唯一定位区块链上的某个 UTXO。signature 由发送者的私钥生成,证明其对该 UTXO 的所有权。
TxOutput(交易输出)
交易输出定义了接收方可获得的金额,是新 UTXO 的载体:
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TxOutput {
pub value: u64, // 金额(单位:satoshi,1 BTC = 10⁸ satoshi)
pub pub_key_hash: String, // 接收者地址(P2PKH)= 锁定脚本
}
impl TxOutput {
pub fn new(value: u64, address: String) -> Self {
TxOutput { value, pub_key_hash: address }
}
/// 检查是否可以被某地址解锁(即该地址是否为此输出的接收者)
pub fn can_be_unlocked_with(&self, address: &str) -> bool {
self.pub_key_hash == address
}
}
}
pub_key_hash 在真实比特币中是锁定脚本(locking script / scriptPubKey),这里简化为接收者地址。
Transaction(交易)
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Transaction {
pub id: String, // 交易 ID = SHA-256(交易内容)
pub inputs: Vec<TxInput>, // 输入列表(消费哪些 UTXO)
pub outputs: Vec<TxOutput>, // 输出列表(创建哪些新 UTXO)
pub timestamp: u64, // Unix 时间戳(秒)
pub fee: u64, // 手续费 = 输入总额 - 输出总额
}
}
不变量: 输入总额 = 输出总额 + 手续费
创建普通交易
普通交易通过 Blockchain::create_transaction() 创建,该方法会自动处理 UTXO 选择、找零计算和签名:
#![allow(unused)]
fn main() {
pub fn create_transaction(
&self,
from_wallet: &Wallet, // 发送者钱包(需要私钥签名)
to_address: String, // 接收者地址
amount: u64, // 转账金额(satoshi)
fee: u64, // 手续费(satoshi)
) -> Result<Transaction, String>
}
内部流程:
#![allow(unused)]
fn main() {
// src/blockchain.rs 节选(简化展示)
// 1. 计算总需求
let total_needed = amount + fee;
// 2. 从 UTXO 集合中查找足够的 UTXO(排除已被待确认交易花费的)
let spendable = self.utxo_set.find_spendable_outputs_excluding(
&from_wallet.address,
total_needed,
&self.pending_spent,
);
let (accumulated, utxos) = spendable.ok_or_else(|| "余额不足(包括交易费)".to_string())?;
// 3. 对每个选中的 UTXO 创建输入并签名
let mut inputs = Vec::new();
for (txid, vout) in utxos {
let signature = from_wallet.sign(&format!("{}{}", txid, vout));
let input = TxInput::new(txid, vout, signature, from_wallet.public_key.clone());
inputs.push(input);
}
// 4. 创建输出(转账 + 找零)
let mut outputs = Vec::new();
outputs.push(TxOutput::new(amount, to_address));
if accumulated > total_needed {
// 找零返还给发送者(扣除手续费)
outputs.push(TxOutput::new(accumulated - total_needed, from_wallet.address.clone()));
}
// 5. 构造交易(自动计算 ID)
Ok(Transaction::new(inputs, outputs, timestamp, fee))
}
完整使用示例:
#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};
let mut blockchain = Blockchain::new();
let genesis = Blockchain::genesis_wallet(); // 预置资金 10M satoshi
let alice = Wallet::new();
// genesis 向 alice 转账 5000 satoshi,手续费 100
let tx = blockchain.create_transaction(
&genesis,
alice.address.clone(),
5000, // 转账金额
100, // 手续费
)?;
println!("交易 ID: {}", tx.id);
println!("输入数量: {}", tx.inputs.len());
println!("输出数量: {}", tx.outputs.len()); // 通常为 2(转账 + 找零)
println!("手续费: {} satoshi", tx.fee);
println!("费率: {:.2} sat/byte", tx.fee_rate());
}
Coinbase 交易
Coinbase 交易是每个区块的第一笔交易,专门用于向矿工发放区块奖励。它与普通交易的关键区别:
| 特性 | 普通交易 | Coinbase 交易 |
|---|---|---|
| 输入 | 引用现有 UTXO | 空 txid(凭空创建) |
| 输出 | 转账 + 找零 | 区块奖励 + 所有手续费 |
| 签名 | ECDSA 签名 | 无需签名(pub_key = "coinbase") |
| 验证 | 验证所有输入签名 | 跳过签名验证(is_coinbase() = true) |
#![allow(unused)]
fn main() {
pub fn new_coinbase(to: String, reward: u64, timestamp: u64, total_fees: u64) -> Self {
// 使用原子计数器保证每个 coinbase 交易 ID 唯一(类似 BIP34 区块高度编码)
static COINBASE_COUNTER: AtomicU64 = AtomicU64::new(0);
let nonce = COINBASE_COUNTER.fetch_add(1, Ordering::Relaxed);
// 输出 = 区块奖励 + 所有交易手续费
let tx_out = TxOutput::new(reward + total_fees, to);
let tx_in = TxInput {
txid: String::new(), // 空 txid 标识 coinbase
vout: 0,
signature: format!("coinbase:{}", nonce), // 唯一性字段
pub_key: String::from("coinbase"),
};
// ...
}
}
Coinbase 交易识别方式:
#![allow(unused)]
fn main() {
pub fn is_coinbase(&self) -> bool {
// 只有一个输入,且该输入的 txid 为空
self.inputs.len() == 1 && self.inputs[0].txid.is_empty()
}
}
交易验证
Transaction::verify() 验证所有输入的 ECDSA 签名:
#![allow(unused)]
fn main() {
pub fn verify(&self) -> bool {
// Coinbase 交易无需验证签名
if self.is_coinbase() {
return true;
}
// 必须有输入和输出
if self.inputs.is_empty() || self.outputs.is_empty() {
return false;
}
// 对每个输入验证 ECDSA 签名
for input in &self.inputs {
// 签名的原始数据:被花费 UTXO 的位置标识
let signed_data = format!("{}{}", input.txid, input.vout);
if !Wallet::verify_signature(&input.pub_key, &signed_data, &input.signature) {
return false;
}
}
true
}
}
验证逻辑说明:
- 签名数据为
"{txid}{vout}",将签名绑定到具体的 UTXO,防止签名重放攻击 - 使用输入中携带的公钥(
pub_key)验证签名,全节点无需额外查询 Wallet::verify_signature()内部调用 secp256k1 执行真实的椭圆曲线数学验证
添加交易到区块链时的完整验证流程(Blockchain::add_transaction()):
#![allow(unused)]
fn main() {
// 1. ECDSA 签名验证
if !transaction.verify() {
return Err("交易验证失败".to_string());
}
// 2. 验证 UTXO 存在且余额充足
let mut input_sum = 0u64;
for input in &transaction.inputs {
if let Some(outputs) = self.find_transaction_outputs(&input.txid) {
if let Some((_, output)) = outputs.iter().find(|(idx, _)| *idx == input.vout) {
input_sum += output.value;
} else {
return Err("UTXO 不存在".to_string());
}
} else {
return Err("引用的交易不存在".to_string());
}
}
let output_sum: u64 = transaction.outputs.iter().map(|o| o.value).sum();
if input_sum < output_sum {
return Err("余额不足,交易无效".to_string());
}
// 3. 记录待确认 UTXO(防止同一 UTXO 被两笔待确认交易双花)
for input in &transaction.inputs {
self.pending_spent.insert(format!("{}:{}", input.txid, input.vout));
}
}
手续费与费率
手续费计算
手续费 = 输入总额 - 输出总额
例如:花费 UTXO (100 satoshi),转账 85,找零 5,手续费 = 100 - 85 - 5 = 10 satoshi
费率
#![allow(unused)]
fn main() {
pub fn fee_rate(&self) -> f64 {
let size = self.size(); // 交易序列化后的字节大小
if size == 0 { return 0.0; }
self.fee as f64 / size as f64 // 单位:satoshi/byte
}
}
典型费率参考(真实比特币,随网络拥堵波动):
| 优先级 | 费率 | 确认时间 |
|---|---|---|
| 低 | 1–5 sat/byte | 数小时甚至更长 |
| 中 | 5–20 sat/byte | 30–60 分钟 |
| 高 | 20–50 sat/byte | 10–20 分钟 |
| 紧急 | 50+ sat/byte | 下一个区块(约 10 分钟) |
SimpleBTC 的内存池按费率排序,高费率交易优先被打包:
#![allow(unused)]
fn main() {
// 获取按费率排序的顶部交易(Mempool 内部逻辑)
let pending_txs = self.mempool.get_top_transactions(usize::MAX);
}
交易生命周期
用户发起转账请求
│
▼
blockchain.create_transaction(&wallet, to, amount, fee)
→ 选择 UTXO,生成签名,构造 Transaction
│
▼
blockchain.add_transaction(tx)
→ 验证签名(ECDSA)
→ 验证 UTXO 存在 + 余额充足
→ 加入 Mempool(按费率排序)
→ 标记已花费 UTXO(pending_spent)
│
▼ 等待矿工打包
│
▼
blockchain.mine_pending_transactions(miner_address)
→ 从 Mempool 取出高优先级交易
→ 创建 Coinbase 交易(奖励 + 手续费)
→ 并行 PoW 挖矿
→ 更新 UTXO 集合(原子操作)
→ 区块上链,清空 pending_spent
│
▼
交易获得 1 次确认
(每增加一个后续区块 = +1 次确认)
交易哈希计算
#![allow(unused)]
fn main() {
pub fn calculate_hash(&self) -> String {
// 将交易序列化为 JSON,计算 SHA-256
let tx_data = serde_json::to_string(&self).unwrap_or_default();
let mut hasher = Sha256::new();
hasher.update(tx_data.as_bytes());
format!("{:x}", hasher.finalize())
}
}
真实比特币使用双重 SHA-256(SHA256d),并且序列化格式为紧凑二进制格式;这里为了教学简化为 JSON + 单次 SHA-256。
输出总额查询
#![allow(unused)]
fn main() {
// 获取所有输出的金额之和
let output_sum = tx.output_sum();
// 交易大小(字节数,影响手续费计算)
let size = tx.size();
// 是否为 Coinbase 交易
let is_cb = tx.is_coinbase();
}
完整交易示例
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};
fn main() -> Result<(), String> {
let mut blockchain = Blockchain::new();
let genesis = Blockchain::genesis_wallet();
let alice = Wallet::new();
let bob = Wallet::new();
// 第一笔:genesis → alice,转账 10000 satoshi
let tx1 = blockchain.create_transaction(&genesis, alice.address.clone(), 10000, 50)?;
println!("tx1 id: {}", tx1.id);
println!("tx1 输出数: {}", tx1.outputs.len()); // 2(转账 + 找零)
println!("tx1 费率: {:.2} sat/byte", tx1.fee_rate());
blockchain.add_transaction(tx1)?;
// 挖矿确认(alice 收矿工奖励)
blockchain.mine_pending_transactions(alice.address.clone())?;
println!("Alice 余额: {} satoshi", blockchain.get_balance(&alice.address));
// 第二笔:alice → bob,转账 3000 satoshi
let tx2 = blockchain.create_transaction(&alice, bob.address.clone(), 3000, 100)?;
blockchain.add_transaction(tx2)?;
blockchain.mine_pending_transactions(bob.address.clone())?;
println!("Alice 余额: {} satoshi", blockchain.get_balance(&alice.address));
println!("Bob 余额: {} satoshi", blockchain.get_balance(&bob.address));
// 验证链完整性
assert!(blockchain.is_valid());
Ok(())
}
区块链操作
区块链是 SimpleBTC 的核心数据结构——一个以密码学方式链接的区块序列,每个区块包含一批经过验证的交易。本章介绍 Block 结构体、Blockchain 的创建与管理、工作量证明挖矿机制,以及链的验证与查询接口。
区块结构
Block 数据结构
#![allow(unused)]
fn main() {
// src/block.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
pub index: u32, // 区块高度(创世区块为 0)
pub timestamp: u64, // Unix 时间戳(秒)
pub transactions: Vec<Transaction>, // 交易列表(第一笔必须是 Coinbase)
pub previous_hash: String, // 父区块哈希(64 字符 SHA-256 hex)
pub hash: String, // 本区块哈希(挖矿找到的有效值)
pub nonce: u64, // 工作量证明计数器
pub merkle_root: String, // 所有交易 ID 的 Merkle 树根哈希
}
}
区块头字段详解:
| 字段 | 比特币对应字段 | 说明 |
|---|---|---|
index | Block Height | 区块高度,创世区块为 0,每新增一块加 1 |
timestamp | nTime | 区块创建时间(Unix 时间戳) |
previous_hash | hashPrevBlock | 父区块哈希,形成链式结构 |
merkle_root | hashMerkleRoot | 所有交易的 Merkle 树根,代表区块内容的指纹 |
nonce | nNonce | 挖矿时不断调整的随机数 |
hash | Block Hash | 区块头所有字段的 SHA-256 哈希 |
链式结构示意图
创世区块 (index=0) 区块 1 区块 2
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ prev: "0" │◄────│ prev: abc...hash │◄─│ prev: def...hash │
│ hash: abc... │ │ hash: def... │ │ hash: ghi... │
│ nonce: 38291 │ │ nonce: 72481 │ │ nonce: 19374 │
│ merkle: xyz... │ │ merkle: pqr... │ │ merkle: stu... │
│ [Coinbase TX] │ │ [Coinbase TX] │ │ [Coinbase TX] │
│ │ │ [TX_1] │ │ [TX_3] │
│ │ │ [TX_2] │ │ [TX_4] │
└──────────────────┘ └──────────────────┘ └──────────────────┘
为什么链式结构保证不可篡改?
- 修改区块 1 中的任意交易 →
merkle_root变化 merkle_root变化 → 区块 1 的hash完全不同- 区块 2 记录了区块 1 的旧
hash→ 区块 2 的previous_hash不再匹配 - 修复区块 2 需要重新挖矿(重算 PoW),区块 3、4… 同理
- 攻击者需要掌握全网 51% 以上算力才能追上诚实链
区块哈希计算
区块哈希由区块头的关键字段计算得出(注意:不直接哈希交易列表,而是使用 Merkle 根):
#![allow(unused)]
fn main() {
// src/block.rs
pub fn calculate_hash(&self) -> String {
use sha2::{Digest, Sha256};
// 将区块头字段拼接为字符串
let data = format!(
"{}{}{}{}{}",
self.index,
self.timestamp,
self.merkle_root, // ← 代表全部交易内容
self.previous_hash,
self.nonce // ← 挖矿时不断改变这个值
);
let mut hasher = Sha256::new();
hasher.update(data.as_bytes());
format!("{:x}", hasher.finalize())
}
}
Merkle 根的作用:
- 任何单笔交易的改动都会导致 Merkle 根完全变化
- 验证交易是否在区块中只需 O(log n) 次哈希(Merkle 证明),而非下载全部交易
创建区块链
Blockchain 结构体
#![allow(unused)]
fn main() {
// src/blockchain.rs
pub struct Blockchain {
pub chain: Vec<Block>, // 区块列表(链)
pub difficulty: usize, // 挖矿难度(前导 0 的个数,默认 3)
pub mempool: Mempool, // 内存池(待确认交易,按费率排序)
pub utxo_set: UTXOSet, // UTXO 集合(所有未花费输出)
pub mining_reward: u64, // 挖矿奖励(默认 50 satoshi)
pub indexer: TransactionIndexer, // 交易索引(地址→交易,加速查询)
miner: ParallelMiner, // 并行 PoW 挖矿器(私有)
pending_spent: HashSet<String>, // 已被待确认交易花费的 UTXO(防双花)
}
}
初始化区块链
#![allow(unused)]
fn main() {
use bitcoin_simulation::blockchain::Blockchain;
// 创建区块链(自动包含创世区块)
let mut blockchain = Blockchain::new();
println!("链长度: {}", blockchain.chain.len()); // 1(仅创世区块)
println!("挖矿难度: {}", blockchain.difficulty); // 3
println!("挖矿奖励: {} satoshi", blockchain.mining_reward); // 50
}
Blockchain::new() 内部流程:
#![allow(unused)]
fn main() {
pub fn new() -> Blockchain {
let mempool = Mempool::new_permissive();
let mut blockchain = Blockchain {
chain: vec![],
difficulty: 3,
mempool,
utxo_set: UTXOSet::new(),
mining_reward: 50,
indexer: TransactionIndexer::new(),
miner: ParallelMiner::default(),
pending_spent: HashSet::new(),
};
// 创建并添加创世区块
let genesis_block = blockchain.create_genesis_block();
blockchain.indexer.index_block(&genesis_block);
blockchain.chain.push(genesis_block);
blockchain
}
}
创世区块
创世区块(Genesis Block)是区块链的第一个区块(index = 0)。它的特殊之处:
previous_hash="0"(不引用任何父区块)- 包含一个 Coinbase 交易,向创世钱包发放 10,000,000 satoshi 初始资金
- 使用确定性创世钱包(固定私钥
0x01),保证每次启动地址一致
#![allow(unused)]
fn main() {
// src/blockchain.rs
fn create_genesis_block(&mut self) -> Block {
let timestamp = /* 当前 Unix 时间 */;
// 确定性创世钱包(固定私钥,可被签名花费)
let genesis_wallet = Wallet::genesis();
let coinbase_tx = Transaction::new_coinbase(
genesis_wallet.address,
10_000_000, // 创世区块奖励:10M satoshi
timestamp,
0, // 无手续费
);
// 将创世 UTXO 加入 UTXO 集合
self.utxo_set.add_transaction(&coinbase_tx);
// 创世区块的 previous_hash 固定为 "0"
Block::new(0, vec![coinbase_tx], "0".to_string())
}
}
获取创世钱包的两种等价方式:
#![allow(unused)]
fn main() {
let genesis = Blockchain::genesis_wallet(); // Blockchain 的静态方法
let genesis2 = Wallet::genesis(); // 直接从 wallet 模块获取
assert_eq!(genesis.address, genesis2.address);
}
工作量证明(PoW)挖矿
原理
工作量证明要求矿工找到一个 nonce 值,使得区块哈希满足“前 N 位为 0“的条件:
difficulty = 3,目标哈希格式:000xxxxxxxxx...
由于 SHA-256 的输出完全不可预测,矿工只能穷举 nonce:
nonce=0: hash = "a7f3b2..." → 不满足(不以 "000" 开头)
nonce=1: hash = "2c91d4..." → 不满足
...
nonce=38291: hash = "000a4b7c9..." → 满足!区块挖出
平均需要尝试 16³ = 4096 次(难度 3)。真实比特币难度相当于约 20 个前导 0,需要约 2⁸⁰ 次尝试。
Block::mine_block()(单线程)
#![allow(unused)]
fn main() {
// src/block.rs
pub fn mine_block(&mut self, difficulty: usize) {
let target = "0".repeat(difficulty);
while self.hash[..difficulty] != target {
self.nonce += 1;
self.hash = self.calculate_hash();
}
println!("✓ 区块已挖出: {}", self.hash);
}
}
ParallelMiner(多线程)
Blockchain::mine_pending_transactions() 使用 ParallelMiner 而非单线程 mine_block(),充分利用多核 CPU:
#![allow(unused)]
fn main() {
// src/blockchain.rs 节选
self.miner
.mine_block(&mut block, self.difficulty)
.map_err(|e| format!("挖矿失败: {}", e))?;
}
ParallelMiner 将 nonce 空间分割给多个线程并行搜索,第一个找到有效哈希的线程获胜。
难度与调整
| 难度值 | 前导零数 | 平均尝试次数 | 适用场景 |
|---|---|---|---|
| 1 | 1 个 0 | 16 次 | 极快测试 |
| 2 | 2 个 0 | 256 次 | 快速演示 |
| 3 | 3 个 0 | 4,096 次 | 默认配置 |
| 4 | 4 个 0 | 65,536 次 | 性能测试 |
| 6 | 6 个 0 | 16,777,216 次 | 接近真实 |
添加交易与挖矿
完整流程
#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};
let mut blockchain = Blockchain::new();
let genesis = Blockchain::genesis_wallet();
let alice = Wallet::new();
// 1. 创建交易
let tx = blockchain.create_transaction(
&genesis,
alice.address.clone(),
5000, // 转账 5000 satoshi
50, // 手续费 50 satoshi
)?;
// 2. 添加到内存池(验证签名 + UTXO)
blockchain.add_transaction(tx)?;
println!("内存池交易数: {}", blockchain.mempool.len()); // 1
// 3. 挖矿(alice 作为矿工接收奖励)
blockchain.mine_pending_transactions(alice.address.clone())?;
println!("链长度: {}", blockchain.chain.len()); // 2(创世 + 新区块)
println!("内存池交易数: {}", blockchain.mempool.len()); // 0(已清空)
}
mine_pending_transactions() 详细流程
#![allow(unused)]
fn main() {
pub fn mine_pending_transactions(&mut self, miner_address: String) -> Result<(), String> {
if self.mempool.is_empty() {
return Err("没有待处理的交易".to_string());
}
// 1. 从内存池取出高费率交易(已排序)
let pending_txs = self.mempool.get_top_transactions(usize::MAX);
// 2. 计算总手续费
let total_fees: u64 = pending_txs.iter().map(|tx| tx.fee).sum();
// 3. 创建 Coinbase 交易(矿工奖励 = 区块奖励 + 总手续费)
let coinbase_tx = Transaction::new_coinbase(
miner_address,
self.mining_reward, // 50 satoshi
timestamp,
total_fees,
);
// 4. 组装区块(Coinbase 必须是第一笔交易)
let mut transactions = vec![coinbase_tx];
transactions.extend(pending_txs.iter().cloned());
let previous_hash = self.chain.last().unwrap().hash.clone();
let mut block = Block::new(self.chain.len() as u32, transactions, previous_hash);
// 5. 并行 PoW 挖矿
self.miner.mine_block(&mut block, self.difficulty)?;
// 6. 验证区块中所有交易签名
if !block.validate_transactions() {
return Err("区块包含无效交易".to_string());
}
// 7. 更新 UTXO 集合(消费输入 UTXO,创建输出 UTXO)
for tx in &block.transactions {
if !self.utxo_set.process_transaction(tx) {
return Err("UTXO 更新失败".to_string());
}
}
// 8. 区块上链 + 建索引
self.indexer.index_block(&block);
self.chain.push(block);
// 9. 清理内存池和 pending_spent
for tx in &pending_txs {
let _ = self.mempool.remove_transaction(&tx.id);
}
self.pending_spent.clear();
Ok(())
}
}
Merkle 树与交易验证
Block::new() 在创建时自动构建 Merkle 树并计算 Merkle 根:
#![allow(unused)]
fn main() {
pub fn new(index: u32, transactions: Vec<Transaction>, previous_hash: String) -> Block {
// 收集所有交易 ID
let tx_ids: Vec<String> = transactions.iter().map(|tx| tx.id.clone()).collect();
// 构建 Merkle 树,计算根哈希
let merkle_tree = MerkleTree::new(&tx_ids);
let merkle_root = merkle_tree.get_root_hash();
let mut block = Block {
index, timestamp, transactions, previous_hash,
hash: String::new(), nonce: 0, merkle_root,
};
block.hash = block.calculate_hash();
block
}
}
验证某笔交易是否包含在区块中(SPV 使用场景):
#![allow(unused)]
fn main() {
// src/block.rs
pub fn verify_transaction_inclusion(&self, tx_id: &str, index: usize) -> bool {
let tx_ids: Vec<String> = self.transactions.iter().map(|tx| tx.id.clone()).collect();
let merkle_tree = MerkleTree::new(&tx_ids);
if let Some(proof) = merkle_tree.get_proof(tx_id) {
MerkleTree::verify_proof(tx_id, &proof, &self.merkle_root, index)
} else {
false
}
}
}
链验证
Blockchain::is_valid() 从第 1 块(跳过创世块)开始逐块验证链的完整性:
#![allow(unused)]
fn main() {
pub fn is_valid(&self) -> bool {
for i in 1..self.chain.len() {
let current = &self.chain[i];
let previous = &self.chain[i - 1];
// 1. 验证区块自身哈希正确性(防止数据被静默篡改)
if current.hash != current.calculate_hash() {
println!("区块 {} 哈希无效", i);
return false;
}
// 2. 验证前向引用(链式连接完整性)
if current.previous_hash != previous.hash {
println!("区块 {} 的前向引用无效", i);
return false;
}
// 3. 验证工作量证明(哈希前导零满足难度要求)
let target = "0".repeat(self.difficulty);
if current.hash[..self.difficulty] != target {
println!("区块 {} 工作量证明无效", i);
return false;
}
// 4. 验证区块中所有交易的 ECDSA 签名
if !current.validate_transactions() {
println!("区块 {} 包含无效交易", i);
return false;
}
}
true
}
}
验证示例:
#![allow(unused)]
fn main() {
let mut blockchain = Blockchain::new();
// ... 添加交易,挖矿 ...
// 正常情况:应通过
assert!(blockchain.is_valid());
// 模拟篡改(教学用途,实际中 Rust 借用规则会约束直接访问)
// 如果有人修改了历史区块的交易,is_valid() 将返回 false
}
余额查询
余额通过 UTXO 集合计算,避免扫描全部历史区块:
#![allow(unused)]
fn main() {
pub fn get_balance(&self, address: &str) -> u64 {
self.utxo_set.get_balance(address)
}
}
使用示例:
#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&alice.address);
println!("Alice 余额: {} satoshi", balance);
println!("Alice 余额: {:.8} BTC", balance as f64 / 1e8);
}
UTXOSet 的性能优势:
不使用 UTXO 集合时,查询余额需要扫描全部区块的全部交易(O(n),n = 总交易数)。UTXO 集合将当前所有未花费输出缓存在内存中,查询变为 O(1) 的哈希表查找。
打印区块链信息
Blockchain::print_chain() 提供格式化的调试输出:
#![allow(unused)]
fn main() {
blockchain.print_chain();
}
输出示例:
========== 区块链信息 ==========
--- 区块 #0 ---
时间戳: 1711497600
哈希: 000a4b7c9d2e1f3a...
前一个哈希: 0
Nonce: 38291
交易数量: 1
交易 #0: f3a1b2c4...
类型: Coinbase(挖矿奖励)
输入数: 1
输出数: 1
输出 0: 10000000 -> 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2
--- 区块 #1 ---
时间戳: 1711497615
哈希: 000d2f8a1b9e4c7f...
前一个哈希: 000a4b7c9d2e1f3a...
Nonce: 72481
交易数量: 2
交易 #0: a1b2c3d4...
类型: Coinbase(挖矿奖励)
...
交易 #1: e5f6a7b8...
交易费: 50 satoshi
费率: 0.23 sat/byte
输入数: 1
输出数: 2
输出 0: 5000 -> 1AliceAddress...
输出 1: 4994950 -> 1GenesisAddress...
================================
完整操作示例
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};
fn main() -> Result<(), String> {
// 初始化
let mut blockchain = Blockchain::new();
let genesis = Blockchain::genesis_wallet();
let alice = Wallet::new();
let bob = Wallet::new();
let miner = Wallet::new();
// 第一轮:genesis → alice
let tx1 = blockchain.create_transaction(&genesis, alice.address.clone(), 100_000, 100)?;
blockchain.add_transaction(tx1)?;
blockchain.mine_pending_transactions(miner.address.clone())?;
println!("区块 1 已挖出");
println!("Alice 余额: {} sat", blockchain.get_balance(&alice.address));
println!("矿工余额: {} sat", blockchain.get_balance(&miner.address));
// 第二轮:alice → bob(两笔交易在同一区块)
let tx2 = blockchain.create_transaction(&alice, bob.address.clone(), 30_000, 200)?;
let tx3 = blockchain.create_transaction(&alice, miner.address.clone(), 20_000, 150)?;
blockchain.add_transaction(tx2)?;
blockchain.add_transaction(tx3)?;
blockchain.mine_pending_transactions(miner.address.clone())?;
println!("\n区块 2 已挖出(含 2 笔交易)");
println!("链长度: {}", blockchain.chain.len()); // 3
println!("Alice 余额: {} sat", blockchain.get_balance(&alice.address));
println!("Bob 余额: {} sat", blockchain.get_balance(&bob.address));
println!("矿工余额: {} sat", blockchain.get_balance(&miner.address));
// 验证链完整性
assert!(blockchain.is_valid(), "链验证应通过");
println!("\n链验证通过!");
// 打印完整链信息
blockchain.print_chain();
Ok(())
}
关键参数参考
| 参数 | 默认值 | 说明 |
|---|---|---|
difficulty | 3 | 挖矿难度(前导 0 的个数) |
mining_reward | 50 | 区块基础奖励(satoshi) |
| 创世区块奖励 | 10,000,000 | 创世 Coinbase 金额(satoshi) |
| 真实比特币初始奖励 | 5,000,000,000 | 50 BTC(satoshi 单位) |
| 真实比特币减半周期 | 210,000 区块 | 约 4 年 |
| 真实比特币目标出块时间 | 10 分钟 | 约每 2016 块调整一次难度 |
UTXO管理
UTXO(Unspent Transaction Output,未花费的交易输出)是比特币账本模型的核心。理解UTXO是掌握比特币工作原理的关键一步。本章介绍SimpleBTC中UTXOSet的设计与使用。
什么是UTXO?
在传统银行体系(以及以太坊账户模型)中,系统直接记录每个账户的余额数字。比特币选择了一种截然不同的方式:不存储余额,只存储“未花费的输出“。
每一笔比特币交易都会:
- 消费若干个此前已存在的UTXO(作为输入)
- 创建若干个新的UTXO(作为输出)
把比特币想象成现金纸币:你手里有一张100元和一张50元。你去买一件80元的商品,你需要把100元纸币整张交出去,找回20元。你“花费“了100元这个UTXO,同时“创建“了两个新UTXO——一个价值80元给商家,一个价值20元的找零给自己。
UTXO的生命周期
创建 存在 花费 销毁
| | | |
v v v v
交易输出 ──────► UTXO集合 ──────────────► 被新交易引用 ──────► 从集合移除
(打包进区块) (可被查询和花费) (作为输入) (不可再用)
UTXO模型 vs 账户模型
| 特性 | UTXO模型(比特币) | 账户模型(以太坊) |
|---|---|---|
| 状态存储 | 记录所有未花费输出 | 记录每个账户的余额 |
| 余额计算 | 扫描所有属于该地址的UTXO求和 | 直接读取账户字段 |
| 隐私性 | 较好(每笔交易可换地址) | 较弱(地址固定) |
| 并行处理 | 天然支持(不同UTXO互不依赖) | 需要额外的并发控制 |
| 双花防止 | UTXO只能被消费一次 | 通过nonce序号控制 |
| 复杂合约 | 较难实现 | 原生支持 |
| 直观性 | 需要理解UTXO概念 | 类似银行账户,直观 |
源码注释(src/utxo.rs第8–19行)对此有精炼的总结:
#![allow(unused)]
fn main() {
// 账户模型(以太坊等):
// - 记录每个账户的余额
// - 转账:A账户-100,B账户+100
// - 简单直观,但难以并行处理
//
// UTXO模型(比特币):
// - 没有账户余额概念
// - 只记录未花费的交易输出
// - 转账:消费A的UTXO,创建给B的新UTXO
// - 更好的隐私性和并行性
}
UTXOSet数据结构
SimpleBTC使用UTXOSet来管理整个区块链的未花费输出集合。
#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct UTXOSet {
// key: txid(交易ID)
// value: 该交易所有未花费的输出列表 [(输出索引, 输出详情)]
utxos: HashMap<String, Vec<(usize, TxOutput)>>,
}
}
- 键(key):交易ID(txid),是一个十六进制哈希字符串
- 值(value):该交易下还未被花费的输出列表,每项包含输出在交易中的索引(
vout)和输出的详细信息(TxOutput)
这种结构设计使得按txid查找某笔交易的所有可用输出非常高效(O(1)哈希查找),同时也能方便地移除指定的单个输出。
核心API详解
创建UTXO集合
#![allow(unused)]
fn main() {
let mut utxo_set = UTXOSet::new();
}
初始化一个空的UTXO集合。区块链启动时从创世区块开始,逐块处理所有交易来填充它。
添加交易输出:add_transaction
#![allow(unused)]
fn main() {
pub fn add_transaction(&mut self, tx: &Transaction)
}
当一笔新交易被打包进区块并确认时,调用此方法将该交易的所有输出加入UTXO集合。
#![allow(unused)]
fn main() {
// 示例:挖到创世区块,coinbase奖励进入UTXO集合
let coinbase_tx = Transaction::new_coinbase("miner_address".to_string(), 50, 0, 0);
utxo_set.add_transaction(&coinbase_tx);
// 现在 coinbase_tx.id -> [(0, TxOutput { value: 50, ... })] 在集合中
}
注意:
add_transaction只添加输出,不处理输入(不移除被花费的UTXO)。完整的交易处理应使用process_transaction。
移除已花费输出:remove_utxo
#![allow(unused)]
fn main() {
pub fn remove_utxo(&mut self, txid: &str, vout: usize)
}
当一个UTXO被某笔交易的输入引用(即被花费)时,必须将其从集合中移除。这是防止双重花费的核心机制。
#![allow(unused)]
fn main() {
// 用户花费了 txid="abc123" 的第0号输出
utxo_set.remove_utxo("abc123", 0);
// 之后再试图花费同一个UTXO,因为它已不在集合中,验证会失败
}
实现上,remove_utxo使用retain保留其他未受影响的输出,如果某笔交易的所有输出都被花费了,则将整个txid条目一并删除:
#![allow(unused)]
fn main() {
pub fn remove_utxo(&mut self, txid: &str, vout: usize) {
if let Some(outputs) = self.utxos.get_mut(txid) {
outputs.retain(|(index, _)| *index != vout);
if outputs.is_empty() {
self.utxos.remove(txid);
}
}
}
}
查询地址的所有UTXO:find_utxos
#![allow(unused)]
fn main() {
pub fn find_utxos(&self, address: &str) -> Vec<(String, usize, u64)>
}
遍历整个UTXO集合,返回属于指定地址的所有未花费输出,结果格式为(txid, vout, value)。
#![allow(unused)]
fn main() {
let utxos = utxo_set.find_utxos("alice_address");
for (txid, vout, value) in &utxos {
println!("UTXO: {}:{} = {} satoshis", txid, vout, value);
}
}
查找可用UTXO:find_spendable_outputs
#![allow(unused)]
fn main() {
pub fn find_spendable_outputs(
&self,
address: &str,
amount: u64,
) -> Option<(u64, Vec<(String, usize)>)>
}
这是创建新交易时最重要的API。它使用贪心算法(Greedy Coin Selection),从该地址的UTXO中逐个累加,直到总额满足amount为止。
#![allow(unused)]
fn main() {
// 要支付 30 satoshis(含手续费)
match utxo_set.find_spendable_outputs("alice", 30) {
Some((accumulated, inputs)) => {
// accumulated: 实际选出的总金额(可能 > 30,差额作为找零)
// inputs: 选中的 UTXO 列表,每项为 (txid, vout)
let change = accumulated - 30;
println!("选中 {} 个UTXO,找零: {} satoshis", inputs.len(), change);
}
None => {
println!("余额不足");
}
}
}
找零机制:若accumulated > amount,差额需要作为找零输出返回给发送者。例如,要支付3 BTC,选了5 BTC的UTXO,需要创建一个2 BTC的找零输出(手续费从中扣除)。
UTXO选择策略对比:
| 策略 | 说明 | 本实现 |
|---|---|---|
| 贪心算法 | 顺序累加直到满足金额 | ✓ 使用此策略 |
| 最优匹配 | 最接近目标金额的组合 | 减少找零 |
| 最小UTXO优先 | 优先用小额UTXO | 减少碎片化 |
| 最大UTXO优先 | 优先用大额UTXO | 减少输入数量 |
排除已待确认UTXO:find_spendable_outputs_excluding
#![allow(unused)]
fn main() {
pub fn find_spendable_outputs_excluding(
&self,
address: &str,
amount: u64,
excluded: &HashSet<String>,
) -> Option<(u64, Vec<(String, usize)>)>
}
这是find_spendable_outputs的扩展版本。当同一个钱包在短时间内连续发起多笔交易时,先前交易已选用的UTXO尚未被确认(仍在内存池中),但已不可再用。通过传入excluded集合(格式为"txid:vout"字符串),可以跳过这些已被待确认交易占用的UTXO。
#![allow(unused)]
fn main() {
// 第一笔交易选用了 "abc:0"
let mut pending_spent: HashSet<String> = HashSet::new();
pending_spent.insert("abc:0".to_string());
// 第二笔交易自动跳过 "abc:0"
let result = utxo_set.find_spendable_outputs_excluding("alice", 20, &pending_spent);
}
查询余额:get_balance
#![allow(unused)]
fn main() {
pub fn get_balance(&self, address: &str) -> u64
}
比特币的“余额“是一个计算值,而非存储值。此方法内部调用find_utxos,将所有属于该地址的UTXO金额求和。
#![allow(unused)]
fn main() {
let balance = utxo_set.get_balance("alice");
println!("Alice的余额: {} satoshis", balance);
}
重要认知:查询余额需要扫描整个UTXO集合(时间复杂度O(n)),比特币实际节点通过地址索引来优化此操作。
完整交易处理:process_transaction
#![allow(unused)]
fn main() {
pub fn process_transaction(&mut self, tx: &Transaction) -> bool
}
这是UTXO状态更新的核心函数,按顺序执行:
- 调用
tx.verify()验证交易签名 - 若非coinbase交易,移除所有输入引用的UTXO
- 将交易的所有输出加入UTXO集合
#![allow(unused)]
fn main() {
// 处理一笔普通交易
let success = utxo_set.process_transaction(&transfer_tx);
if !success {
eprintln!("交易验证失败,UTXO集合未变更");
}
}
此函数具备原子性语义——验证失败时不会修改UTXO集合,保证了状态一致性。
双花防止机制
双重花费(Double Spend)是区块链需要解决的核心安全问题。UTXO模型天然防御双花:
攻击流程:
1. 攻击者有一个价值10 BTC的UTXO(txid="xyz", vout=0)
2. 创建交易A:花费 xyz:0,付给商家10 BTC
3. 商家接受,交易A进入内存池
4. 攻击者创建交易B:同样花费 xyz:0,付给自己10 BTC
5. 尝试广播交易B
防御结果:
- 交易A确认后,xyz:0 从UTXO集合删除
- 交易B验证时找不到 xyz:0,被节点拒绝
- 即使交易A未确认,内存池的双花检测也会拒绝交易B
在SimpleBTC的Mempool中,utxo_index(HashMap<"txid:vout", spending_txid>)记录了哪些UTXO已被内存池中的交易占用,从而在第3步就能检测到双花并拒绝交易B。
完整使用示例
use bitcoin_simulation::utxo::UTXOSet;
use bitcoin_simulation::transaction::Transaction;
fn main() {
let mut utxo_set = UTXOSet::new();
// 步骤1:挖矿,创建coinbase交易(凭空产生比特币)
let coinbase = Transaction::new_coinbase("alice".to_string(), 50, 0, 0);
utxo_set.process_transaction(&coinbase);
// 步骤2:查询Alice的余额
let alice_balance = utxo_set.get_balance("alice");
println!("Alice余额: {} satoshis", alice_balance); // 输出: 50
// 步骤3:Alice向Bob转账20 satoshis(手续费2 satoshis)
let needed = 22; // 20给Bob + 2手续费
if let Some((accumulated, inputs)) = utxo_set.find_spendable_outputs("alice", needed) {
let change = accumulated - needed;
println!("选用 {} 个UTXO,总额: {},找零: {}", inputs.len(), accumulated, change);
// 构建并广播交易(此处省略签名细节)
// let tx = build_transaction(inputs, "bob", 20, "alice", change, 2);
// utxo_set.process_transaction(&tx);
}
// 步骤4:查询Bob的余额
// println!("Bob余额: {}", utxo_set.get_balance("bob"));
}
小结
UTXO模型是比特币架构的基石。UTXOSet通过以下机制保证账本安全:
process_transaction:原子性地更新UTXO状态(先删输入,再增输出)remove_utxo:确保每个UTXO只能被消费一次,防止双花find_spendable_outputs_excluding:通过pending_spent跟踪,解决连续交易的UTXO冲突get_balance:余额是计算值,是所有属于该地址的UTXO的求和
下一章将介绍交易的具体结构和签名验证机制。
Merkle树与SPV验证
Merkle树(哈希树)和简化支付验证(SPV)是比特币实现轻量级客户端的技术基础。它们使手机钱包只需几MB存储就能安全验证交易,而不需要下载超过500GB的完整区块链。
什么是Merkle树?
Merkle树是一种二叉哈希树,由计算机科学家Ralph Merkle于1979年发明。其核心思想是:通过递归地对数据做哈希,最终将任意数量的数据压缩成一个固定长度的“指纹“(根哈希)。
在比特币中,每个区块包含的所有交易会被组织成一棵Merkle树。树的根哈希(Merkle Root)存储在区块头中,受工作量证明(PoW)保护。任何对交易数据的篡改都会导致根哈希发生变化,进而使该区块及其后所有区块失效。
树结构图示(4笔交易的情形)
┌─────────────┐
│ Root Hash │
│ hash(H12+H34)│
└──────┬──────┘
┌───────┴───────┐
┌──────┴──────┐ ┌─────┴──────┐
│ H12 │ │ H34 │
│ hash(H1+H2) │ │ hash(H3+H4)│
└──────┬──────┘ └─────┬──────┘
┌────────┴───┐ ┌─────┴───┐
┌──┴──┐ ┌───┴──┐ ┌───┴──┐ ┌──┴───┐
│ H1 │ │ H2 │ │ H3 │ │ H4 │
│hash │ │ hash │ │ hash │ │ hash │
│(tx1)│ │(tx2) │ │(tx3) │ │(tx4) │
└──┬──┘ └──┬───┘ └──┬───┘ └──┬───┘
│ │ │ │
tx1 tx2 tx3 tx4
(交易1) (交易2) (交易3) (交易4)
构建过程
构建遵循自底向上的原则,分两个阶段:
第一阶段:构建叶子层
每笔交易的原始数据经SHA-256哈希后,成为一个叶子节点:
H1 = SHA256(tx1_data)
H2 = SHA256(tx2_data)
H3 = SHA256(tx3_data)
H4 = SHA256(tx4_data)
奇数处理:若交易数量为奇数,则复制最后一笔交易,使层数变为偶数。这是比特币协议规定的标准做法。
第二阶段:逐层合并至根
每两个相邻节点的哈希值拼接后再哈希,得到父节点:
H12 = SHA256(H1 + H2)
H34 = SHA256(H3 + H4)
Root = SHA256(H12 + H34)
重复此过程,直到只剩一个节点,即为Merkle根。
SimpleBTC中的实现
节点结构:MerkleNode
#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct MerkleNode {
pub hash: String, // 节点的哈希值
pub left: Option<Box<MerkleNode>>, // 左子节点(内部节点才有)
pub right: Option<Box<MerkleNode>>, // 右子节点(内部节点才有)
}
}
- 叶子节点:
left和right均为None,hash为交易数据的SHA-256值 - 内部节点:有左右子节点,
hash为SHA256(left.hash + right.hash) - 根节点:树的顶部节点,是最终的Merkle Root
创建节点的两个工厂方法:
#![allow(unused)]
fn main() {
// 叶子节点:直接哈希原始数据
let leaf = MerkleNode::new_leaf("tx_data_string");
// 内部节点:合并两个子节点
let parent = MerkleNode::new_internal(left_node, right_node);
}
树结构:MerkleTree
#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct MerkleTree {
pub root: Option<MerkleNode>, // 树根节点
pub leaves: Vec<String>, // 原始交易哈希列表
}
}
构建Merkle树:MerkleTree::new
#![allow(unused)]
fn main() {
pub fn new(transactions: &[String]) -> Self
}
接受一个交易ID(字符串)列表,自动构建完整的Merkle树:
#![allow(unused)]
fn main() {
use bitcoin_simulation::merkle::MerkleTree;
let txs = vec![
"tx1_hash".to_string(),
"tx2_hash".to_string(),
"tx3_hash".to_string(),
"tx4_hash".to_string(),
];
let tree = MerkleTree::new(&txs);
let root = tree.get_root_hash();
println!("Merkle Root: {}", root);
// 输出:一个64字符的十六进制哈希字符串
}
内部实现的关键步骤:
#![allow(unused)]
fn main() {
// 1. 奇数补齐
if !leaves.len().is_multiple_of(2) {
leaves.push(leaves.last().unwrap().clone());
}
// 2. 构建叶子节点层
let mut nodes: Vec<MerkleNode> = leaves.iter()
.map(|tx| MerkleNode::new_leaf(tx))
.collect();
// 3. 自底向上逐层合并
while nodes.len() > 1 {
let mut next_level = Vec::new();
for i in (0..nodes.len()).step_by(2) {
let left = nodes[i].clone();
let right = nodes[i + 1].clone(); // 已保证偶数
next_level.push(MerkleNode::new_internal(left, right));
}
nodes = next_level;
}
}
生成Merkle证明:get_proof
#![allow(unused)]
fn main() {
pub fn get_proof(&self, tx_hash: &str) -> Option<Vec<String>>
}
为指定交易生成一个Merkle证明(Merkle Proof),也称为“Merkle路径“。这个证明包含从该交易的叶子节点到根节点路径上的所有兄弟节点哈希。
#![allow(unused)]
fn main() {
// 为 tx1 生成证明
let proof = tree.get_proof("tx1_hash").unwrap();
// proof = [H2, H34] ← 验证时需要用到的兄弟哈希列表
}
图示:验证tx1需要的证明
┌────────────┐
│ Root │ ← 已知(存在区块头中)
└─────┬──────┘
┌──────────┴──────────┐
┌──────┴──────┐ ┌──────┴──────┐
│ H12 │ │ ★ H34 ★ │ ← 证明元素[1]
└──────┬──────┘ └─────────────┘
┌───────┴───────┐
┌──┴──┐ ┌────┴──┐
│ H1 │ │★ H2 ★│ ← 证明元素[0]
└──┬──┘ └───────┘
│
[tx1] ← 要验证的交易(已知)
验证者只需[H2, H34]两个哈希(log₂4 = 2步),而不需要知道tx2、tx3、tx4的内容。
验证Merkle证明:verify_proof
#![allow(unused)]
fn main() {
pub fn verify_proof(
tx_hash: &str, // 要验证的交易哈希
proof: &[String], // Merkle证明(兄弟哈希列表)
root_hash: &str, // 区块头中的Merkle根
index: usize, // 该交易在区块中的索引位置
) -> bool
}
这是一个静态方法,无需持有完整的Merkle树就可以验证。SPV客户端正是通过此方法来验证交易。
#![allow(unused)]
fn main() {
// 已知:tx1在区块中,索引为0,Merkle Root来自区块头
let is_valid = MerkleTree::verify_proof(
"tx1_hash",
&proof, // [H2, H34]
&root_hash, // 来自区块头,受PoW保护
0, // tx1是第0号交易
);
println!("交易验证结果: {}", is_valid); // true
}
验证算法步骤(以tx1,index=0为例):
第1步:current_hash = SHA256("tx1_hash") → 得到 H1
index=0(偶数),H1在左边
combined = H1 + proof[0](H2)
current_hash = SHA256(H1 + H2) → 得到 H12
index = 0 / 2 = 0
第2步:index=0(偶数),H12在左边
combined = H12 + proof[1](H34)
current_hash = SHA256(H12 + H34) → 得到计算出的Root
验证:计算出的Root == 区块头中的merkle_root ?
源码实现:
#![allow(unused)]
fn main() {
pub fn verify_proof(tx_hash: &str, proof: &[String], root_hash: &str, index: usize) -> bool {
let mut current_hash = MerkleNode::hash_data(tx_hash);
let mut current_index = index;
for sibling_hash in proof {
let combined = if current_index.is_multiple_of(2) {
// 当前节点在左边,兄弟在右边
format!("{}{}", current_hash, sibling_hash)
} else {
// 当前节点在右边,兄弟在左边
format!("{}{}", sibling_hash, current_hash)
};
current_hash = MerkleNode::hash_data(&combined);
current_index /= 2;
}
current_hash == root_hash
}
}
SPV轻客户端
SPV概念
SPV(Simplified Payment Verification,简化支付验证)由中本聪在比特币白皮书第8节中提出。其核心思想是:轻客户端不需要验证所有交易,只需信任最长工作量证明链,并使用Merkle证明验证与自己相关的交易。
| 特性 | 全节点 | SPV节点 |
|---|---|---|
| 存储需求 | 400+ GB(完整区块链) | ~5 MB(仅区块头) |
| 带宽消耗 | 完整区块(1-4 MB/块) | 仅区块头(80字节/块) |
| 验证范围 | 所有交易 | 仅与自己相关的交易 |
| 安全级别 | 最高(完全自主验证) | 依赖PoW,信任矿工诚实 |
| 适用场景 | 矿池、交易所、全节点 | 移动钱包、嵌入式设备 |
SimpleBTC中的SPV实现
区块头结构:BlockHeader
SPV客户端只下载并存储区块头,不下载交易体:
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockHeader {
pub height: u32, // 区块高度
pub hash: String, // 区块哈希
pub previous_hash: String, // 前一个区块哈希(保证链式结构)
pub merkle_root: String, // Merkle根(32字节,用于验证交易)
pub timestamp: u64, // 时间戳
pub bits: u32, // 难度目标
pub nonce: u64, // 工作量证明随机数
}
}
每个区块头固定80字节。比特币目前约有83万个区块,区块头总大小约66 MB——相比完整区块链的600+ GB,这是极大的节省。
SPV客户端:SPVClient
#![allow(unused)]
fn main() {
pub struct SPVClient {
headers: Vec<BlockHeader>, // 区块头链
header_index: HashMap<String, BlockHeader>, // hash → header 快速查找
verified_transactions: HashMap<String, (String, bool)>, // txid → (block_hash, 验证结果)
chain_tip: Option<String>, // 当前最新区块哈希
total_work: u64, // 累积工作量
}
}
SPV工作流程
第一步:同步区块头
#![allow(unused)]
fn main() {
use bitcoin_simulation::spv::SPVClient;
let mut client = SPVClient::new();
// 从全节点获取区块并提取区块头
let blocks = /* 从P2P网络获取 */;
client.sync_from_blocks(&blocks).unwrap();
println!("已同步 {} 个区块头", client.get_height());
println!("存储占用: {} 字节", client.estimate_storage_size());
// 1000个区块头只需 80,000 字节(约78 KB)
}
也可以逐块添加:
#![allow(unused)]
fn main() {
use bitcoin_simulation::spv::BlockHeader;
let header = BlockHeader {
height: 0,
hash: "genesis_hash".to_string(),
previous_hash: "0000...".to_string(),
merkle_root: "merkle_root_hash".to_string(),
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 2083236893,
};
client.add_block_header(header).unwrap();
}
区块头链的连续性由add_block_header自动验证:新区块头的previous_hash必须与上一个区块头的hash匹配,否则拒绝添加:
#![allow(unused)]
fn main() {
// 尝试添加不连续的区块头会返回错误
let bad_header = BlockHeader {
height: 1,
hash: "block_1".to_string(),
previous_hash: "wrong_hash".to_string(), // 不匹配!
// ...
};
let result = client.add_block_header(bad_header);
assert!(result.is_err()); // 被拒绝
}
第二步:验证交易包含性
当用户收到一笔付款,需要验证这笔交易确实被打包进了某个区块:
#![allow(unused)]
fn main() {
// 假设商家收到付款通知:tx_id 在 block_hash 的第0号位置
let tx_id = "payment_tx_hash";
let block_hash = "some_block_hash";
// 向全节点请求Merkle证明(实际应通过P2P协议请求)
let proof = vec!["sibling_hash_1".to_string(), "sibling_hash_2".to_string()];
let tx_index = 0; // 交易在区块中的位置
let is_valid = client.verify_transaction(tx_id, &proof, block_hash, tx_index).unwrap();
if is_valid {
println!("付款已确认!交易 {} 在区块中", tx_id);
} else {
println!("验证失败,交易可能不在该区块中");
}
}
第三步:检查历史验证结果
#![allow(unused)]
fn main() {
// 检查某笔交易是否已通过SPV验证
if let Some(verified) = client.is_transaction_verified(tx_id) {
if verified {
println!("该交易已验证");
}
}
// 获取SPV统计信息
let stats = client.get_stats();
println!("区块头数量: {}", stats.header_count);
println!("存储大小: {} 字节", stats.storage_size);
println!("已验证交易数: {}", stats.verified_tx_count);
}
完整示例:构建树并做SPV验证
use bitcoin_simulation::merkle::MerkleTree;
use bitcoin_simulation::spv::{SPVClient, BlockHeader};
fn main() {
// 1. 假设某区块包含4笔交易
let transactions = vec![
"tx1".to_string(),
"tx2".to_string(),
"tx3".to_string(),
"tx4".to_string(),
];
// 2. 构建Merkle树(全节点做的事)
let tree = MerkleTree::new(&transactions);
let merkle_root = tree.get_root_hash();
println!("Merkle Root: {}", merkle_root);
// 3. 为tx1生成证明(全节点应SPV客户端请求生成)
let proof = tree.get_proof("tx1").unwrap();
println!("tx1的Merkle证明包含 {} 个哈希", proof.len());
// 4. SPV客户端验证(只知道区块头和证明,不知道其他交易)
let mut spv = SPVClient::new();
let header = BlockHeader {
height: 0,
hash: "block_0".to_string(),
previous_hash: "0".to_string(),
merkle_root: merkle_root.clone(),
timestamp: 1700000000,
bits: 0,
nonce: 42,
};
spv.add_block_header(header).unwrap();
let valid = spv.verify_transaction("tx1", &proof, "block_0", 0).unwrap();
println!("SPV验证结果: {}", valid); // true
// 5. 直接使用静态方法验证(不需要SPVClient)
let valid2 = MerkleTree::verify_proof("tx1", &proof, &merkle_root, 0);
println!("静态验证结果: {}", valid2); // true
}
为什么SPV验证是安全的?
攻击者无法伪造Merkle证明,原因有两点:
- SHA-256抗碰撞性:要找到两个不同的输入产生相同哈希,在计算上不可行(需要约2¹²⁸次哈希运算)。
- PoW保护:
merkle_root存储在区块头中,而区块头受工作量证明保护。若要伪造一个包含虚假merkle_root的区块头,攻击者需要重新完成该区块及其后所有区块的挖矿工作,这在算力上极难实现(“最长链规则”)。
SPV的唯一信任假设是:诚实矿工控制的算力超过51%。在这个假设成立的前提下,攻击者无法以实际可行的成本欺骗SPV客户端。
小结
| 组件 | 作用 |
|---|---|
MerkleNode | Merkle树的基本单元,存储哈希值和子节点引用 |
MerkleTree::new | 从交易列表自底向上构建完整Merkle树 |
MerkleTree::get_proof | 为指定交易生成O(log n)大小的Merkle证明 |
MerkleTree::verify_proof | 用证明+根哈希验证交易,O(log n)时间复杂度 |
BlockHeader | 区块头,80字节,包含Merkle Root |
SPVClient | 轻客户端,仅下载区块头并使用Merkle证明验证交易 |
多重签名(MultiSig)
多重签名是比特币的高级功能,要求M个签名才能花费N个公钥控制的资金(M-of-N)。
概述
什么是多重签名?
多重签名地址需要多个私钥共同签名才能花费资金,而不是传统的单一私钥。
示例:
- 2-of-3: 需要3个密钥中的任意2个
- 3-of-5: 需要5个密钥中的任意3个
- 2-of-2: 需要2个密钥都同意
为什么需要多签?
1. 安全性提升
- 没有单点故障
- 私钥被盗不会立即丢失资金
- 分散风险
2. 信任分散
- 企业治理:防止单人滥用
- 托管服务:买卖双方 + 仲裁员
- 家庭共管:夫妻共同管理
3. 灵活性
- 不同的M-N组合满足不同需求
- 可以设置紧急恢复机制
- 支持复杂的业务逻辑
技术实现
多签地址结构
#![allow(unused)]
fn main() {
pub struct MultiSigAddress {
pub address: String, // 多签地址(以"3"开头)
pub required_sigs: usize, // M(需要的签名数)
pub total_keys: usize, // N(总密钥数)
pub public_keys: Vec<String>, // 所有参与者公钥
pub script: String, // 锁定脚本
}
}
创建多签地址
#![allow(unused)]
fn main() {
use bitcoin_simulation::{multisig::MultiSigAddress, wallet::Wallet};
// 创建参与者
let ceo = Wallet::new();
let cfo = Wallet::new();
let cto = Wallet::new();
// 收集公钥
let public_keys = vec![
ceo.public_key.clone(),
cfo.public_key.clone(),
cto.public_key.clone(),
];
// 创建2-of-3多签地址
let multisig = MultiSigAddress::new(2, public_keys)?;
println!("多签地址: {}", multisig.address);
println!("需要签名: {}/{}", multisig.required_sigs, multisig.total_keys);
}
多签类型
SimpleBTC提供了预设的常用多签类型:
#![allow(unused)]
fn main() {
use bitcoin_simulation::multisig::MultiSigType;
// 2-of-2: 双方都必须同意
let two_of_two = MultiSigAddress::from_type(
MultiSigType::TwoOfTwo,
vec![alice.public_key, bob.public_key]
)?;
// 2-of-3: 任意两方即可(最常用)
let two_of_three = MultiSigAddress::from_type(
MultiSigType::TwoOfThree,
vec![party1.public_key, party2.public_key, party3.public_key]
)?;
// 3-of-5: 高安全性场景
let three_of_five = MultiSigAddress::from_type(
MultiSigType::ThreeOfFive,
vec![pk1, pk2, pk3, pk4, pk5]
)?;
}
应用场景
场景1: 企业财务管理
需求: 公司资金需要多个高管共同批准
方案: 2-of-3多签(CEO + CFO + CTO)
#![allow(unused)]
fn main() {
fn setup_corporate_wallet() -> Result<MultiSigAddress, String> {
// 1. 创建高管钱包
let ceo = Wallet::new();
let cfo = Wallet::new();
let cto = Wallet::new();
println!("=== 企业多签钱包 ===");
println!("CEO: {}", &ceo.address[..16]);
println!("CFO: {}", &cfo.address[..16]);
println!("CTO: {}", &cto.address[..16]);
// 2. 创建多签地址
let company_wallet = MultiSigAddress::new(
2, // 需要2个签名
vec![
ceo.public_key.clone(),
cfo.public_key.clone(),
cto.public_key.clone(),
]
)?;
println!("\n公司多签地址: {}", company_wallet.address);
println!("规则: 任意2位高管签名即可转账\n");
Ok(company_wallet)
}
// 转账场景
fn corporate_payment(
multisig: &MultiSigAddress,
ceo: &Wallet,
cfo: &Wallet,
recipient: &str,
amount: u64
) -> Result<(), String> {
println!("转账 {} satoshi 给 {}", amount, &recipient[..16]);
// 1. CEO签名
let ceo_sig = ceo.sign(&format!("{}{}", multisig.address, amount));
println!("✓ CEO已签名");
// 2. CFO签名
let cfo_sig = cfo.sign(&format!("{}{}", multisig.address, amount));
println!("✓ CFO已签名");
// 3. 收集签名
let signatures = vec![ceo_sig, cfo_sig];
// 4. 验证签名数量
if signatures.len() >= multisig.required_sigs {
println!("✅ 签名数量满足要求,交易可以执行");
// 创建并广播交易...
Ok(())
} else {
Err("签名不足".to_string())
}
}
}
优势:
- ✅ 防止单人滥用资金
- ✅ CEO出差时,CFO+CTO仍可运作
- ✅ 任何一人被攻击,资金仍安全
场景2: 托管交易
需求: 买卖双方不信任对方,需要第三方仲裁
方案: 2-of-3多签(买家 + 卖家 + 仲裁员)
#![allow(unused)]
fn main() {
fn escrow_service() -> Result<(), String> {
// 参与方
let buyer = Wallet::new();
let seller = Wallet::new();
let arbitrator = Wallet::new();
println!("=== 托管服务 ===");
println!("买家: {}", &buyer.address[..16]);
println!("卖家: {}", &seller.address[..16]);
println!("仲裁员: {}", &arbitrator.address[..16]);
// 创建托管多签地址
let escrow = MultiSigAddress::new(
2,
vec![
buyer.public_key.clone(),
seller.public_key.clone(),
arbitrator.public_key.clone(),
]
)?;
println!("\n托管地址: {}", escrow.address);
// 情况1: 正常交易(买家 + 卖家)
println!("\n--- 场景1: 交易顺利完成 ---");
println!("买家收到货物,满意");
println!("买家签名: ✓");
println!("卖家签名: ✓");
println!("✅ 2/3签名,资金释放给卖家");
// 情况2: 争议(买家 + 仲裁员 或 卖家 + 仲裁员)
println!("\n--- 场景2: 发生争议 ---");
println!("买家: 货物有问题");
println!("卖家: 货物没问题");
println!("仲裁员介入调查...");
println!("仲裁员: 买家有理");
println!("买家签名: ✓");
println!("仲裁员签名: ✓");
println!("✅ 2/3签名,资金退还给买家");
Ok(())
}
}
优势:
- ✅ 买家保护:货不对版可退款
- ✅ 卖家保护:正常交易自动放款
- ✅ 公平:仲裁员无法单独控制资金
场景3: 个人资产保护
需求: 防止单一私钥丢失或被盗
方案: 2-of-3多签(主密钥 + 备份密钥 + 托管密钥)
#![allow(unused)]
fn main() {
fn personal_security_setup() -> Result<(), String> {
// 密钥分配
let main_key = Wallet::new(); // 日常使用
let backup_key = Wallet::new(); // 保险柜
let custodian_key = Wallet::new(); // 律师/信托公司
println!("=== 个人资产保护 ===");
println!("主密钥(日常): {}", &main_key.address[..16]);
println!("备份密钥(保险柜): {}", &backup_key.address[..16]);
println!("托管密钥(律师): {}", &custodian_key.address[..16]);
let secure_wallet = MultiSigAddress::new(
2,
vec![
main_key.public_key,
backup_key.public_key,
custodian_key.public_key,
]
)?;
println!("\n安全钱包: {}", secure_wallet.address);
// 使用场景
println!("\n--- 使用场景 ---");
println!("日常转账: 主密钥 + 备份密钥");
println!("主密钥丢失: 备份密钥 + 托管密钥");
println!("被盗风险: 需要2个密钥,单个被盗无风险");
Ok(())
}
}
场景4: 冷热钱包组合
需求: 大额存储安全 + 小额使用便利
方案: 2-of-3(热钱包 + 冷钱包1 + 冷钱包2)
#![allow(unused)]
fn main() {
fn cold_hot_wallet_setup() -> Result<(), String> {
let hot_wallet = Wallet::new(); // 联网设备
let cold_wallet_1 = Wallet::new(); // 硬件钱包1
let cold_wallet_2 = Wallet::new(); // 纸钱包
println!("=== 冷热钱包组合 ===");
println!("热钱包(手机): {}", &hot_wallet.address[..16]);
println!("冷钱包1(Ledger): {}", &cold_wallet_1.address[..16]);
println!("冷钱包2(纸钱包): {}", &cold_wallet_2.address[..16]);
let vault = MultiSigAddress::new(
2,
vec![
hot_wallet.public_key,
cold_wallet_1.public_key,
cold_wallet_2.public_key,
]
)?;
println!("\n金库地址: {}", vault.address);
println!("\n--- 使用策略 ---");
println!("日常小额: 热钱包 + 冷钱包1(方便)");
println!("大额转账: 冷钱包1 + 冷钱包2(最安全)");
println!("热钱包被黑: 仍需冷钱包配合,资金安全");
Ok(())
}
}
高级用法
时间锁 + 多签
结合时间锁实现遗产继承:
#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::TimeLock;
fn inheritance_setup() -> Result<(), String> {
let owner = Wallet::new();
let heir = Wallet::new();
let lawyer = Wallet::new();
// 正常情况:2-of-2(本人 + 继承人,保护隐私)
let normal_multisig = MultiSigAddress::new(
2,
vec![owner.public_key.clone(), heir.public_key.clone()]
)?;
// 时间锁设置:1年后
let one_year = 365 * 24 * 60 * 60;
let unlock_time = current_timestamp() + one_year;
let timelock = TimeLock::new_time_based(unlock_time);
println!("=== 遗产继承方案 ===");
println!("正常时期: 需要本人 + 继承人(2-of-2)");
println!("1年后: 自动变为继承人可独立操作");
// 或使用3-of-3,1年后降级为2-of-3
let emergency_multisig = MultiSigAddress::new(
2, // 1年后只需2个
vec![owner.public_key, heir.public_key, lawyer.public_key]
)?;
Ok(())
}
}
分层多签
大型组织的多层多签结构:
#![allow(unused)]
fn main() {
// 董事会多签: 5-of-9
let board = MultiSigAddress::new(5, board_members)?;
// 执行委员会多签: 3-of-5
let exec_committee = MultiSigAddress::new(3, executives)?;
// 小额快速多签: 2-of-3
let petty_cash = MultiSigAddress::new(2, managers)?;
println!("权限分级:");
println!("< 10 BTC: 经理级 2-of-3");
println!("10-100 BTC: 高管级 3-of-5");
println!("> 100 BTC: 董事会 5-of-9");
}
安全考虑
⚠️ 注意事项
-
密钥管理
- 分散存储,不要放在一起
- 使用硬件钱包存储冷密钥
- 定期测试备份恢复
-
M值选择
- M太小:安全性降低
- M太大:可用性降低
- 推荐:M = (N+1)/2 或 N-1
-
N值选择
- N=2: 简单但单点故障
- N=3: 平衡安全与便利(最常用)
- N=5+: 高安全但复杂
-
参与者选择
- 地理分散
- 信任但相互独立
- 有紧急联系方式
最佳实践
#![allow(unused)]
fn main() {
// ✅ 好的实践
let multisig = MultiSigAddress::new(
2, // 合理的M值
vec![key1, key2, key3] // 3个独立密钥
)?;
// 分散存储
// key1 -> 手机热钱包
// key2 -> 硬件钱包(保险柜)
// key3 -> 纸钱包(银行保险箱)
// ❌ 不好的实践
// 所有密钥存在同一台电脑
// M=N(失去容错能力)
// 使用同一个助记词派生多个密钥
}
完整示例
#![allow(unused)]
fn main() {
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
multisig::MultiSigAddress,
};
fn complete_multisig_demo() -> Result<(), String> {
let mut blockchain = Blockchain::new();
// 创建参与者
let alice = Wallet::new();
let bob = Wallet::new();
let charlie = Wallet::new();
// 创建2-of-3多签
let multisig = MultiSigAddress::new(
2,
vec![
alice.public_key.clone(),
bob.public_key.clone(),
charlie.public_key.clone(),
]
)?;
println!("多签地址: {}", multisig.address);
// 1. 存入资金
let funding_tx = blockchain.create_transaction(
&Wallet::from_address("funder".to_string()),
multisig.address.clone(),
10000,
0,
)?;
blockchain.add_transaction(funding_tx)?;
blockchain.mine_pending_transactions(alice.address.clone())?;
println!("多签余额: {}", blockchain.get_balance(&multisig.address));
// 2. 多签转账(需要2个签名)
let recipient = Wallet::new();
// Alice签名
let alice_sig = alice.sign(&format!("{}{}",
multisig.address, recipient.address));
// Bob签名
let bob_sig = bob.sign(&format!("{}{}",
multisig.address, recipient.address));
// 验证签名
println!("\n收集签名:");
println!("Alice: ✓");
println!("Bob: ✓");
if vec![alice_sig, bob_sig].len() >= multisig.required_sigs {
println!("✅ 签名满足要求,可以转账");
// 创建转账交易
// 注意:实际实现需要多签交易构建逻辑
println!("交易已创建并广播");
}
Ok(())
}
}
参考资料
Replace-By-Fee (RBF) 机制
RBF(替换手续费)是BIP125提出的机制,允许用户替换未确认的交易。
概述
什么是RBF?
RBF允许发送者在交易未确认前,用更高手续费的交易替换原交易。
场景:
1. Alice发送交易A:1 BTC → Bob,手续费 1 sat/byte
2. 网络拥堵,交易A长时间未确认
3. Alice发送交易B:1 BTC → Bob,手续费 50 sat/byte
4. 矿工优先打包交易B(手续费更高)
5. 交易A被丢弃
为什么需要RBF?
-
加速确认
- 初始手续费估计不准
- 网络突然拥堵
- 紧急交易需要快速确认
-
取消交易
- 发送到错误地址
- 改变主意
- 通过发送给自己实现“取消“
-
批量优化
- 初始交易包含部分收款人
- 后续添加更多收款人
- 节省总手续费
技术实现
RBF标记
nSequence字段:
#![allow(unused)]
fn main() {
// 启用RBF
input.sequence = 0xFFFFFFFD; // < 0xFFFFFFFE
// 禁用RBF(最终交易)
input.sequence = 0xFFFFFFFF;
}
BIP125规则
替换交易必须满足:
-
更高手续费
#![allow(unused)] fn main() { new_tx.fee > original_tx.fee } -
花费相同UTXO
#![allow(unused)] fn main() { new_tx.inputs == original_tx.inputs } -
费率增量
#![allow(unused)] fn main() { new_tx.fee >= original_tx.fee + min_relay_fee } -
不引入新的未确认UTXO
RBFManager实现
数据结构
#![allow(unused)]
fn main() {
pub struct RBFManager {
replaceable_txs: Vec<String>, // 可替换交易ID列表
}
}
方法
new
#![allow(unused)]
fn main() {
pub fn new() -> Self
}
创建新的RBF管理器。
mark_replaceable
#![allow(unused)]
fn main() {
pub fn mark_replaceable(&mut self, txid: String)
}
标记交易为可替换。
示例:
#![allow(unused)]
fn main() {
let mut rbf = RBFManager::new();
rbf.mark_replaceable(tx.id.clone());
}
is_replaceable
#![allow(unused)]
fn main() {
pub fn is_replaceable(&self, txid: &str) -> bool
}
检查交易是否可替换。
replace_transaction
#![allow(unused)]
fn main() {
pub fn replace_transaction(
&mut self,
original_txid: &str,
new_tx: Transaction
) -> Result<(), String>
}
用新交易替换原交易。
验证:
- 原交易必须可替换
- 新交易手续费更高
- 新交易有效
使用场景
场景1: 加速确认
#![allow(unused)]
fn main() {
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
advanced_tx::RBFManager,
};
fn speed_up_transaction() -> Result<(), String> {
let mut blockchain = Blockchain::new();
let mut rbf = RBFManager::new();
let alice = Wallet::new();
let bob = Wallet::new();
// 初始化余额
setup_balance(&mut blockchain, &alice, 10000)?;
println!("=== RBF加速交易演示 ===\n");
// 1. 创建低手续费交易
println!("--- 步骤1: 发送低手续费交易 ---");
let slow_tx = blockchain.create_transaction(
&alice,
bob.address.clone(),
1000,
1, // 低手续费:1 sat
)?;
println!("原始交易:");
println!(" ID: {}", &slow_tx.id[..16]);
println!(" 金额: 1000 sat");
println!(" 手续费: 1 sat");
println!(" 费率: {:.2} sat/byte\n", slow_tx.fee_rate());
blockchain.add_transaction(slow_tx.clone())?;
rbf.mark_replaceable(slow_tx.id.clone());
// 2. 网络拥堵,交易长时间未确认
println!("--- 步骤2: 网络拥堵 ---");
println!("⏰ 等待确认...");
println!("⏰ 10分钟后仍未确认");
println!("⚠️ 手续费太低,需要加速\n");
// 3. 创建高手续费替换交易
println!("--- 步骤3: 创建替换交易(更高手续费)---");
let fast_tx = blockchain.create_transaction(
&alice,
bob.address.clone(),
1000,
50, // 高手续费:50 sat
)?;
println!("替换交易:");
println!(" ID: {}", &fast_tx.id[..16]);
println!(" 金额: 1000 sat");
println!(" 手续费: 50 sat (50x)");
println!(" 费率: {:.2} sat/byte\n", fast_tx.fee_rate());
// 4. 验证并替换
if rbf.is_replaceable(&slow_tx.id) {
if fast_tx.fee > slow_tx.fee {
println!("✓ 满足RBF条件:");
println!(" 新手续费({}) > 原手续费({})", fast_tx.fee, slow_tx.fee);
// 从待处理池移除原交易
blockchain.pending_transactions.retain(|tx| tx.id != slow_tx.id);
// 添加新交易
blockchain.add_transaction(fast_tx)?;
println!("✓ 交易已替换\n");
}
}
// 5. 挖矿确认
println!("--- 步骤4: 矿工打包(优先高费率)---");
blockchain.mine_pending_transactions(alice.address.clone())?;
println!("✓ 交易已确认");
println!(" Bob余额: {} sat", blockchain.get_balance(&bob.address));
Ok(())
}
}
输出:
=== RBF加速交易演示 ===
--- 步骤1: 发送低手续费交易 ---
原始交易:
ID: abc123...
金额: 1000 sat
手续费: 1 sat
费率: 0.01 sat/byte
--- 步骤2: 网络拥堵 ---
⏰ 等待确认...
⏰ 10分钟后仍未确认
⚠️ 手续费太低,需要加速
--- 步骤3: 创建替换交易(更高手续费)---
替换交易:
ID: def456...
金额: 1000 sat
手续费: 50 sat (50x)
费率: 0.50 sat/byte
✓ 满足RBF条件:
新手续费(50) > 原手续费(1)
✓ 交易已替换
--- 步骤4: 矿工打包(优先高费率)---
✓ 交易已确认
Bob余额: 1000 sat
场景2: 取消交易
#![allow(unused)]
fn main() {
fn cancel_transaction() -> Result<(), String> {
let mut blockchain = Blockchain::new();
let mut rbf = RBFManager::new();
let alice = Wallet::new();
let wrong_addr = Wallet::new().address; // 错误地址
setup_balance(&mut blockchain, &alice, 10000)?;
println!("=== RBF取消交易演示 ===\n");
// 1. 发送到错误地址
println!("--- 错误:发送到错误地址 ---");
let wrong_tx = blockchain.create_transaction(
&alice,
wrong_addr.clone(),
5000,
10,
)?;
println!("错误交易:");
println!(" 收款人: {} (错误!)", &wrong_addr[..16]);
println!(" 金额: 5000 sat\n");
blockchain.add_transaction(wrong_tx.clone())?;
rbf.mark_replaceable(wrong_tx.id.clone());
// 2. 发现错误,取消交易
println!("--- 发现错误,尝试取消 ---");
println!("策略: 用更高手续费发送给自己\n");
// 3. 创建"取消"交易(发送给自己)
let cancel_tx = blockchain.create_transaction(
&alice,
alice.address.clone(), // 发给自己
4950, // 金额略少(扣除手续费)
50, // 更高手续费
)?;
println!("取消交易:");
println!(" 收款人: {} (自己)", &alice.address[..16]);
println!(" 金额: 4950 sat");
println!(" 手续费: 50 sat (5x)\n");
// 4. 替换
if cancel_tx.fee > wrong_tx.fee {
blockchain.pending_transactions.retain(|tx| tx.id != wrong_tx.id);
blockchain.add_transaction(cancel_tx)?;
println!("✓ 交易已取消(实际是替换)\n");
}
// 5. 确认
blockchain.mine_pending_transactions(alice.address.clone())?;
println!("✓ 资金已返回");
println!(" Alice余额: {} sat", blockchain.get_balance(&alice.address));
println!(" 错误地址余额: {} sat", blockchain.get_balance(&wrong_addr));
Ok(())
}
}
场景3: 批量支付优化
#![allow(unused)]
fn main() {
fn batch_payment_optimization() -> Result<(), String> {
let mut blockchain = Blockchain::new();
let mut rbf = RBFManager::new();
let alice = Wallet::new();
let recipients: Vec<_> = (0..5).map(|_| Wallet::new()).collect();
setup_balance(&mut blockchain, &alice, 100000)?;
println!("=== RBF批量支付优化 ===\n");
// 1. 初始支付(2个收款人)
println!("--- 初始批量支付(2个收款人)---");
let mut outputs = vec![
TxOutput::new(1000, recipients[0].address.clone()),
TxOutput::new(2000, recipients[1].address.clone()),
];
// 创建交易...(简化)
println!("支付:");
println!(" 收款人1: 1000 sat");
println!(" 收款人2: 2000 sat");
println!(" 手续费: 10 sat\n");
// 2. 添加更多收款人
println!("--- 添加更多收款人(RBF扩展)---");
outputs.push(TxOutput::new(3000, recipients[2].address.clone()));
outputs.push(TxOutput::new(4000, recipients[3].address.clone()));
println!("新增:");
println!(" 收款人3: 3000 sat");
println!(" 收款人4: 4000 sat");
println!(" 手续费: 15 sat (只增加5 sat!)\n");
println!("优势:");
println!(" ✓ 4笔交易合并为1笔");
println!(" ✓ 节省手续费 (4×10 - 15 = 25 sat)");
println!(" ✓ 节省区块空间");
Ok(())
}
}
安全考虑
⚠️ 零确认交易风险
问题: RBF使零确认交易不安全
#![allow(unused)]
fn main() {
// 攻击场景
// 1. 攻击者:Alice → 商家Bob (1 BTC, 低手续费)
// 商家看到交易,发货
// 2. 攻击者替换:Alice → Alice (1 BTC, 高手续费)
// 资金返回自己,商家损失
// 防御:等待确认
if confirmations < 1 {
println!("⚠️ 警告:零确认交易不安全(RBF风险)");
println!("建议:等待至少1个确认");
}
}
商家建议
#![allow(unused)]
fn main() {
fn accept_payment(tx: &Transaction) -> bool {
// 1. 检查是否启用RBF
if is_rbf_enabled(tx) {
println!("⚠️ 交易启用了RBF");
// 选项A: 拒绝零确认
println!("等待确认中...");
return false;
// 选项B: 要求更高手续费
if tx.fee_rate() < 50.0 {
println!("手续费太低,需要 >= 50 sat/byte");
return false;
}
}
// 2. 等待足够确认
let confirmations = get_confirmations(tx);
if confirmations < 1 {
return false;
}
true
}
}
RBF vs CPFP
Child-Pays-For-Parent (CPFP)
CPFP: 子交易支付父交易的手续费
父交易: Alice → Bob (低手续费)
↓
子交易: Bob → Charlie (高手续费)
矿工会一起打包以获得高手续费
对比
| 特性 | RBF | CPFP |
|---|---|---|
| 操作者 | 发送者 | 接收者 |
| 机制 | 替换交易 | 子交易拉动 |
| 手续费 | 发送者支付 | 接收者支付 |
| 复杂度 | 简单 | 稍复杂 |
| 适用场景 | 发送者加速 | 接收者加速 |
最佳实践
1. 何时使用RBF
#![allow(unused)]
fn main() {
// ✅ 适合RBF的场景
if network_congested && !urgent {
// 先发低手续费,需要时再加速
create_rbf_transaction(fee_low);
}
// ❌ 不适合RBF的场景
if urgent || large_amount {
// 直接发高手续费
create_transaction(fee_high);
}
}
2. 手续费策略
#![allow(unused)]
fn main() {
fn calculate_replacement_fee(original_fee: u64) -> u64 {
// 至少增加原费用的50%
let min_increase = original_fee / 2;
// 或达到当前推荐费率
let recommended = get_recommended_fee_rate() * tx_size;
max(original_fee + min_increase, recommended)
}
}
3. 用户通知
#![allow(unused)]
fn main() {
fn notify_replacement(original_tx: &Transaction, new_tx: &Transaction) {
println!("📢 交易已被替换:");
println!(" 原交易: {}", &original_tx.id[..16]);
println!(" 新交易: {}", &new_tx.id[..16]);
println!(" 原手续费: {} sat", original_tx.fee);
println!(" 新手续费: {} sat", new_tx.fee);
println!(" 增加: +{} sat", new_tx.fee - original_tx.fee);
}
}
实现示例
完整的RBF交易流程
#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::RBFManager;
fn rbf_complete_example() -> Result<(), String> {
let mut blockchain = Blockchain::new();
let mut rbf = RBFManager::new();
let alice = Wallet::new();
let bob = Wallet::new();
// 1. 初始化
setup_balance(&mut blockchain, &alice, 10000)?;
// 2. 创建可替换交易
let tx1 = blockchain.create_transaction(&alice, bob.address.clone(), 1000, 5)?;
blockchain.add_transaction(tx1.clone())?;
rbf.mark_replaceable(tx1.id.clone());
println!("✓ 原交易已创建(手续费: 5 sat)");
// 3. 监控交易状态
std::thread::sleep(std::time::Duration::from_secs(30));
if !is_confirmed(&blockchain, &tx1.id) {
println!("⚠️ 30秒后仍未确认,准备加速...");
// 4. 创建替换交易
let tx2 = blockchain.create_transaction(&alice, bob.address, 1000, 50)?;
// 5. 验证RBF规则
if rbf.can_replace(&tx1, &tx2) {
// 6. 执行替换
blockchain.pending_transactions.retain(|tx| tx.id != tx1.id);
blockchain.add_transaction(tx2.clone())?;
println!("✓ 交易已替换(手续费: 50 sat)");
// 7. 确认
blockchain.mine_pending_transactions(alice.address)?;
println!("✓ 新交易已确认");
}
}
Ok(())
}
}
参考资料
总结: RBF是强大的工具,但要注意零确认交易风险。商家应等待确认,用户应合理使用。
时间锁(TimeLock / nLockTime)
时间锁是比特币的重要特性,限制交易在特定时间或区块高度之前不能被确认。
概述
什么是时间锁?
时间锁让交易在未来某个时间点才能被使用,实现“延迟支付“功能。
示例:
Alice创建交易: 1 BTC → Bob
时间锁: 2025年1月1日
在2025年1月1日之前:
❌ 交易无法被确认
❌ 矿工拒绝打包
2025年1月1日之后:
✓ 交易可以被确认
✓ 矿工可以打包
两种类型
1. 基于Unix时间戳
#![allow(unused)]
fn main() {
// locktime >= 500,000,000
let unlock_time = 1735689600; // 2025-01-01 00:00:00
let timelock = TimeLock::new_time_based(unlock_time);
}
特点:
- 以秒为单位
- 适合精确时间控制
- 受系统时间影响
使用场景:
- 工资发放(每月1号)
- 债券到期(固定日期)
- 定期存款(3/6/12个月)
2. 基于区块高度
#![allow(unused)]
fn main() {
// locktime < 500,000,000
let unlock_height = 800000; // 第800,000个区块
let timelock = TimeLock::new_block_based(unlock_height);
}
特点:
- 以区块为单位
- 更精确(约10分钟/块)
- 不受系统时间影响
使用场景:
- 更精确的时间控制
- 避免时间戳操纵
- 智能合约触发
时间估算:
1块 ≈ 10分钟
6块 ≈ 1小时
144块 ≈ 1天
1008块 ≈ 1周
4032块 ≈ 1月
TimeLock实现
数据结构
#![allow(unused)]
fn main() {
pub struct TimeLock {
pub locktime: u64, // 锁定时间/高度
pub is_block_height: bool, // true: 区块高度, false: 时间戳
}
}
方法
new_time_based
#![allow(unused)]
fn main() {
pub fn new_time_based(timestamp: u64) -> Self
}
创建基于时间的时间锁。
参数:
timestamp- Unix时间戳(秒)
示例:
#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::TimeLock;
use std::time::{SystemTime, UNIX_EPOCH};
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// 3个月后解锁
let three_months = 90 * 24 * 3600;
let unlock_time = current_time + three_months;
let timelock = TimeLock::new_time_based(unlock_time);
println!("锁定至: {}", format_timestamp(unlock_time));
}
new_block_based
#![allow(unused)]
fn main() {
pub fn new_block_based(block_height: u64) -> Self
}
创建基于区块高度的时间锁。
参数:
block_height- 目标区块高度
示例:
#![allow(unused)]
fn main() {
let current_height = blockchain.chain.len() as u64;
// 1000个区块后解锁(约1周)
let unlock_height = current_height + 1000;
let timelock = TimeLock::new_block_based(unlock_height);
println!("锁定至区块 #{}", unlock_height);
}
is_mature
#![allow(unused)]
fn main() {
pub fn is_mature(&self, current_time: u64, current_height: u64) -> bool
}
检查时间锁是否已到期。
参数:
current_time- 当前Unix时间戳current_height- 当前区块高度
返回值:
true- 已到期,可以使用false- 未到期,仍被锁定
示例:
#![allow(unused)]
fn main() {
let timelock = TimeLock::new_time_based(unlock_time);
if timelock.is_mature(current_time, 0) {
println!("✓ 已到期,可以花费");
} else {
let remaining = unlock_time - current_time;
println!("🔒 仍被锁定,剩余 {} 秒", remaining);
}
}
应用场景
场景1: 定期存款
#![allow(unused)]
fn main() {
fn savings_account_demo() -> Result<(), String> {
println!("=== 定期存款演示 ===\n");
let alice = Wallet::new();
let mut blockchain = Blockchain::new();
// 初始化余额
setup_balance(&mut blockchain, &alice, 100000)?;
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// 产品1: 3个月定存
println!("--- 产品A: 3个月定期存款 ---");
let three_months = 90 * 24 * 3600;
let maturity_3m = current_time + three_months;
let deposit_3m = TimeLock::new_time_based(maturity_3m);
println!("存款金额: 30,000 sat");
println!("期限: 3个月");
println!("到期日: {}", format_date(maturity_3m));
println!("年化利率: 3%\n");
// 产品2: 1年定存
println!("--- 产品B: 1年定期存款 ---");
let one_year = 365 * 24 * 3600;
let maturity_1y = current_time + one_year;
let deposit_1y = TimeLock::new_time_based(maturity_1y);
println!("存款金额: 50,000 sat");
println!("期限: 1年");
println!("到期日: {}", format_date(maturity_1y));
println!("年化利率: 5%\n");
// 检查到期状态
println!("--- 当前状态检查 ---");
println!("当前时间: {}", format_date(current_time));
if deposit_3m.is_mature(current_time, 0) {
println!("✓ 3个月定存已到期,可提取");
let interest = 30000 * 3 / 100 / 4; // 季度利息
println!(" 本息: {} sat", 30000 + interest);
} else {
let days_left = (maturity_3m - current_time) / 86400;
println!("🔒 3个月定存锁定中");
println!(" 剩余: {} 天", days_left);
}
if deposit_1y.is_mature(current_time, 0) {
println!("✓ 1年定存已到期,可提取");
let interest = 50000 * 5 / 100; // 年利息
println!(" 本息: {} sat", 50000 + interest);
} else {
let days_left = (maturity_1y - current_time) / 86400;
println!("🔒 1年定存锁定中");
println!(" 剩余: {} 天", days_left);
}
Ok(())
}
}
输出:
=== 定期存款演示 ===
--- 产品A: 3个月定期存款 ---
存款金额: 30,000 sat
期限: 3个月
到期日: 2025-03-15 00:00:00
年化利率: 3%
--- 产品B: 1年定期存款 ---
存款金额: 50,000 sat
期限: 1年
到期日: 2025-12-15 00:00:00
年化利率: 5%
--- 当前状态检查 ---
当前时间: 2024-12-15 00:00:00
🔒 3个月定存锁定中
剩余: 90 天
🔒 1年定存锁定中
剩余: 365 天
场景2: 遗产继承
#![allow(unused)]
fn main() {
fn inheritance_planning() -> Result<(), String> {
println!("=== 遗产继承方案 ===\n");
let owner = Wallet::new();
let heir = Wallet::new();
let lawyer = Wallet::new();
println!("参与方:");
println!(" 所有人: {}", &owner.address[..16]);
println!(" 继承人: {}", &heir.address[..16]);
println!(" 律师: {}\n", &lawyer.address[..16]);
let current_time = current_timestamp();
// 方案: 1年无活动后,资产自动转给继承人
println!("--- 方案设计 ---");
println!("正常情况:");
println!(" 需要: 所有人 + 继承人 (2-of-2)");
println!(" 保护隐私,防止单方面转移\n");
println!("紧急情况(1年后):");
println!(" 所有人失联或去世");
println!(" 时间锁到期");
println!(" 继承人可独立操作\n");
// 创建时间锁交易
let one_year = 365 * 24 * 3600;
let inheritance_time = current_time + one_year;
let timelock = TimeLock::new_time_based(inheritance_time);
println!("--- 时间锁配置 ---");
println!("触发时间: {}", format_date(inheritance_time));
println!("触发条件: 1年内无所有人签名的交易\n");
// 定期检查(由律师执行)
println!("--- 定期检查 ---");
let last_activity = current_time;
let inactive_period = current_time - last_activity;
if inactive_period > one_year {
if timelock.is_mature(current_time, 0) {
println!("✓ 时间锁已触发");
println!("✓ 继承程序启动");
println!("✓ 资产可转移给继承人");
}
} else {
let days_remaining = (one_year - inactive_period) / 86400;
println!("🔒 正常状态");
println!("距离继承触发还有 {} 天", days_remaining);
}
Ok(())
}
}
场景3: 工资发放
#![allow(unused)]
fn main() {
fn salary_payment_system() -> Result<(), String> {
println!("=== 工资发放系统 ===\n");
let company = Wallet::new();
let employees: Vec<_> = (0..5)
.map(|i| (format!("员工{}", i+1), Wallet::new()))
.collect();
let current_time = current_timestamp();
println!("公司地址: {}", &company.address[..16]);
println!("员工数量: {}\n", employees.len());
// 每月1号发放工资
println!("--- 工资发放计划 ---");
for month in 1..=3 {
// 计算下个月1号的时间戳
let payment_date = calculate_first_day_of_month(current_time, month);
let timelock = TimeLock::new_time_based(payment_date);
println!("第{}月工资:", month);
println!(" 发放日期: {}", format_date(payment_date));
if timelock.is_mature(current_time, 0) {
println!(" 状态: ✓ 可发放");
for (name, wallet) in &employees {
println!(" {} → {} sat", name, 10000);
}
} else {
let days_until = (payment_date - current_time) / 86400;
println!(" 状态: 🔒 锁定中");
println!(" 倒计时: {} 天", days_until);
}
println!();
}
println!("--- 优势 ---");
println!("✓ 自动化发放");
println!("✓ 无法提前挪用");
println!("✓ 员工可预期收入");
println!("✓ 降低管理成本");
Ok(())
}
}
场景4: 众筹退款
#![allow(unused)]
fn main() {
fn crowdfunding_refund() -> Result<(), String> {
println!("=== 众筹退款机制 ===\n");
let project_owner = Wallet::new();
let backers: Vec<_> = (0..10).map(|_| Wallet::new()).collect();
let current_time = current_timestamp();
println!("--- 众筹项目 ---");
println!("目标金额: 1,000,000 sat");
println!("当前筹集: 500,000 sat");
println!("截止日期: 30天后\n");
// 30天后如果未达标,自动退款
let deadline = current_time + 30 * 86400;
let refund_timelock = TimeLock::new_time_based(deadline);
println!("--- 退款时间锁 ---");
println!("触发条件: 30天后未达标");
println!("触发时间: {}", format_date(deadline));
println!("退款方式: 自动返还支持者\n");
// 检查状态
if refund_timelock.is_mature(current_time, 0) {
println!("--- 项目失败,执行退款 ---");
for (i, backer) in backers.iter().enumerate() {
println!("✓ 退款给支持者#{}: {} sat", i+1, 50000);
}
} else {
let days_left = (deadline - current_time) / 86400;
println!("--- 众筹进行中 ---");
println!("剩余时间: {} 天", days_left);
println!("仍需筹集: 500,000 sat");
}
Ok(())
}
}
高级用法
时间锁 + 多签
结合多签实现更复杂的逻辑:
#![allow(unused)]
fn main() {
fn timelock_multisig_combination() -> Result<(), String> {
let owner = Wallet::new();
let heir = Wallet::new();
let lawyer = Wallet::new();
// 正常:2-of-2(所有人 + 继承人)
let normal_multisig = MultiSigAddress::new(
2,
vec![owner.public_key.clone(), heir.public_key.clone()]
)?;
// 1年后:2-of-3(任意两人)
let emergency_multisig = MultiSigAddress::new(
2,
vec![owner.public_key, heir.public_key, lawyer.public_key]
)?;
let one_year = 365 * 24 * 3600;
let timelock = TimeLock::new_time_based(current_timestamp() + one_year);
println!("=== 时间锁 + 多签组合 ===");
println!("\n正常时期(第一年):");
println!(" 多签地址: {}", &normal_multisig.address[..16]);
println!(" 要求: 所有人 + 继承人 (2-of-2)");
println!("\n紧急时期(一年后):");
println!(" 多签地址: {}", &emergency_multisig.address[..16]);
println!(" 要求: 任意两人 (2-of-3)");
println!(" 可能组合:");
println!(" - 所有人 + 继承人");
println!(" - 所有人 + 律师");
println!(" - 继承人 + 律师");
Ok(())
}
}
技术细节
nLockTime字段
在实际比特币交易中:
#![allow(unused)]
fn main() {
struct Transaction {
version: u32,
inputs: Vec<TxInput>,
outputs: Vec<TxOutput>,
locktime: u32, // 时间锁字段
}
}
规则:
if locktime < 500,000,000:
# 区块高度模式
if current_block_height >= locktime:
可以确认
else:
拒绝
else:
# 时间戳模式
if current_timestamp >= locktime:
可以确认
else:
拒绝
nSequence与时间锁
要启用时间锁,nSequence必须 < 0xFFFFFFFF:
#![allow(unused)]
fn main() {
// 启用时间锁
input.sequence = 0xFFFFFFFD;
// 禁用时间锁(最终交易)
input.sequence = 0xFFFFFFFF;
}
安全考虑
1. 时间戳操纵
问题: 矿工可能操纵区块时间戳
限制:
- 时间戳不能早于前11个区块的中位数
- 不能晚于当前时间2小时以上
建议: 使用区块高度更可靠
2. 紧急情况
问题: 时间锁无法取消
解决方案:
#![allow(unused)]
fn main() {
// 方案1: 使用RBF在到期前替换
if !timelock.is_mature(...) && need_cancel {
replace_with_non_locked_tx();
}
// 方案2: 双重支出(到期前)
create_alternative_tx_without_timelock();
}
3. 密钥丢失
问题: 到期前密钥丢失
建议:
- 使用多签降低风险
- 备份密钥
- 设置恢复机制
与CLTV/CSV的关系
CheckLockTimeVerify (CLTV)
BIP65引入:
OP_CLTV操作码
锁定单个UTXO
更灵活
nLockTime vs CLTV:
nLockTime: 锁定整个交易
CLTV: 锁定单个输出(更灵活)
CheckSequenceVerify (CSV)
BIP112引入:
OP_CSV操作码
相对时间锁
从UTXO创建时间开始计算
最佳实践
1. 选择正确的类型
#![allow(unused)]
fn main() {
// 精确日期:使用时间戳
let birthday = to_timestamp("2025-01-01");
let timelock = TimeLock::new_time_based(birthday);
// 相对延迟:使用区块高度
let blocks_1week = 1008; // 约1周
let timelock = TimeLock::new_block_based(current_height + blocks_1week);
}
2. 用户友好的时间显示
#![allow(unused)]
fn main() {
fn display_timelock_status(timelock: &TimeLock, current_time: u64, current_height: u64) {
if timelock.is_mature(current_time, current_height) {
println!("✓ 已解锁");
} else {
if timelock.is_block_height {
let blocks_left = timelock.locktime - current_height;
let hours = blocks_left * 10 / 60; // 约10分钟/块
println!("🔒 锁定中,还需 {} 个区块 (约{}小时)", blocks_left, hours);
} else {
let seconds_left = timelock.locktime - current_time;
let days = seconds_left / 86400;
println!("🔒 锁定中,还需 {} 天", days);
}
}
}
}
3. 测试时间锁
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
#[test]
fn test_timelock() {
let current = 1000000;
let future = 2000000;
let timelock = TimeLock::new_time_based(future);
// 未到期
assert!(!timelock.is_mature(current, 0));
// 已到期
assert!(timelock.is_mature(future + 1, 0));
}
}
}
参考资料
总结: 时间锁是实现延迟支付、智能合约的关键技术。合理使用可以实现定期存款、遗产继承、工资发放等多种应用。
交易优先级
当网络拥堵时,内存池(Mempool)中可能积压数千笔待确认交易。矿工每次只能打包约1MB数据进入区块,因此需要一套优先级机制来决定哪些交易先被确认。本章介绍SimpleBTC中交易优先级的计算方式、内存池排序逻辑以及手续费推荐策略。
核心概念:费率(Fee Rate)
费率(Fee Rate)是衡量交易优先级最重要的指标:
费率(sat/byte)= 手续费(satoshi)/ 交易大小(bytes)
矿工优先选择费率高的交易打包进区块,因为这样在相同区块空间下能获得最多手续费收益。
为什么用费率而不是绝对手续费?
一笔包含10个输入的复杂交易可能支付1000 sat手续费,但它占用900字节,费率约1.1 sat/byte。一笔只有1个输入的简单交易支付200 sat,仅占用192字节,费率约1.04 sat/byte。两者从矿工利益角度基本相当。若只看绝对手续费,会错误地优先选择前者,浪费区块空间。
内存池结构
SimpleBTC的Mempool使用双索引结构来支持高效的优先级排序:
#![allow(unused)]
fn main() {
pub struct Mempool {
// 主存储:txid → 内存池条目
transactions: HashMap<String, MempoolEntry>,
// 费率索引:fee_rate → txid集合(BTreeMap自动有序,高效迭代)
fee_index: BTreeMap<ordered_float::NotNan<f64>, HashSet<String>>,
// UTXO索引:用于双花检测
utxo_index: HashMap<String, String>,
// 容量控制
max_size: usize, // 最大字节数(默认300MB)
current_size: usize, // 当前已用字节数
min_fee_rate: f64, // 最低接受费率(默认1.0 sat/byte)
max_age: u64, // 最长保留时间(默认72小时)
}
}
BTreeMap(平衡二叉搜索树)是关键:它按费率自动排序,使得“取费率最高的N笔交易“操作只需从尾部反向迭代,时间复杂度为O(N)。
内存池条目:MempoolEntry
#![allow(unused)]
fn main() {
pub struct MempoolEntry {
pub transaction: Transaction, // 完整交易数据
pub added_time: u64, // 加入时间(Unix时间戳)
pub size: usize, // 估算的字节大小
pub fee_rate: f64, // 计算出的费率(sat/byte)
pub replaceable: bool, // 是否支持RBF替换
}
}
费率在创建MempoolEntry时立即计算并缓存,避免重复计算:
#![allow(unused)]
fn main() {
impl MempoolEntry {
pub fn new(transaction: Transaction, size: usize) -> Self {
let fee_rate = if size > 0 {
transaction.fee as f64 / size as f64
} else {
0.0
};
// ...
}
}
}
交易大小估算
SimpleBTC使用简化的公式估算交易字节大小:
#![allow(unused)]
fn main() {
fn estimate_tx_size(&self, tx: &Transaction) -> usize {
let base = 10; // 固定开销(版本号、锁定时间等)
let inputs_size = tx.inputs.len() * 148; // 每个输入约148字节
let outputs_size = tx.outputs.len() * 34; // 每个输出约34字节
base + inputs_size + outputs_size
}
}
实际比特币交易大小参考(原生SegWit,P2WPKH格式):
| 交易类型 | 输入数 | 输出数 | 估算大小 |
|---|---|---|---|
| 简单转账 | 1 | 2 | 约192字节 |
| 合并多个UTXO | 5 | 2 | 约898字节 |
| 批量付款 | 1 | 10 | 约388字节 |
交易添加与验证流程
调用mempool.add_transaction(tx)时,内部按顺序执行以下检查:
交易到达内存池
│
▼
① 是否已存在? ──是──► 拒绝(重复交易)
│否
▼
② 基本安全验证(格式、签名等)
│
▼
③ 双花检测:是否有输入已被其他内存池交易花费?
│
├─ 是,且旧交易支持RBF且新费用更高 ──► 触发RBF替换,继续
│
└─ 是,但不满足RBF条件 ──► 拒绝(双花攻击)
│否
▼
④ 估算大小,计算费率
│
▼
⑤ 费率 ≥ min_fee_rate? ──否──► 拒绝(费率过低)
│是
▼
⑥ 内存池是否已满? ──是──► 触发淘汰低费率交易
│
▼
⑦ 添加到 transactions、fee_index、utxo_index
│
▼
成功
#![allow(unused)]
fn main() {
// 示例:添加一笔交易到内存池
let mut mempool = Mempool::default(); // 300MB限制,1 sat/byte最低费率
let tx = Transaction::new(inputs, outputs, 0, 200); // 200 sat手续费
match mempool.add_transaction(tx) {
Ok(()) => println!("交易已进入内存池"),
Err(e) => println!("拒绝原因: {}", e),
}
}
优先级排序与区块打包
按费率获取前N笔:get_top_transactions
#![allow(unused)]
fn main() {
pub fn get_top_transactions(&self, max_count: usize) -> Vec<Transaction>
}
通过反向迭代fee_index(BTreeMap从大到小),快速取出费率最高的交易:
#![allow(unused)]
fn main() {
// 从高费率到低费率遍历
for (_fee_rate, txids) in self.fee_index.iter().rev() {
for txid in txids {
if let Some(entry) = self.transactions.get(txid) {
result.push(entry.transaction.clone());
if result.len() >= max_count {
return result;
}
}
}
}
}
#![allow(unused)]
fn main() {
// 用法:矿工想预览最优质的10笔交易
let top_txs = mempool.get_top_transactions(10);
for tx in &top_txs {
println!("txid: {}, fee: {} sat", tx.id, tx.fee);
}
}
按区块大小限制打包:get_transactions_for_block
#![allow(unused)]
fn main() {
pub fn get_transactions_for_block(&self, max_size: usize) -> Vec<Transaction>
}
更实用的区块打包函数。同样按费率从高到低选取,但额外检查累积大小不超过max_size字节:
#![allow(unused)]
fn main() {
pub fn get_transactions_for_block(&self, max_size: usize) -> Vec<Transaction> {
let mut result = Vec::new();
let mut total_size = 0;
for (_fee_rate, txids) in self.fee_index.iter().rev() {
for txid in txids {
if let Some(entry) = self.transactions.get(txid) {
if total_size + entry.size <= max_size {
result.push(entry.transaction.clone());
total_size += entry.size;
}
}
}
}
result
}
}
#![allow(unused)]
fn main() {
// 用法:为新区块打包交易(比特币区块限制约1MB = 1_000_000字节)
let block_txs = mempool.get_transactions_for_block(1_000_000);
println!("选中 {} 笔交易用于打包", block_txs.len());
}
综合优先级评分
SimpleBTC在src/advanced_tx.rs中提供了TxPriorityCalculator,实现了更精细的优先级计算。
基础费率计算
#![allow(unused)]
fn main() {
pub fn calculate_fee_rate(fee: u64, size: usize) -> f64 {
if size == 0 { return 0.0; }
fee as f64 / size as f64
}
}
#![allow(unused)]
fn main() {
// 200 sat手续费,交易大小192字节
let fee_rate = TxPriorityCalculator::calculate_fee_rate(200, 192);
println!("费率: {:.2} sat/byte", fee_rate); // 约1.04 sat/byte
}
硬币年龄优先级
比特币早期(SegWit之前)也考虑“硬币年龄“(Coin Age):UTXO的价值乘以其等待的区块数,除以交易大小:
#![allow(unused)]
fn main() {
/// 优先级 = (输入价值 × 输入确认数) / 交易大小
pub fn calculate_priority(
input_value: u64, // 输入的总价值(satoshi)
input_age: u32, // 输入UTXO已确认的区块数
tx_size: usize,
) -> f64 {
(input_value as f64 * input_age as f64) / tx_size as f64
}
}
#![allow(unused)]
fn main() {
// 示例:输入价值1 BTC = 100_000_000 sat,已确认100个区块,交易大小200字节
let priority = TxPriorityCalculator::calculate_priority(100_000_000, 100, 200);
println!("硬币年龄优先级: {:.0}", priority); // 50_000_000
}
历史背景:比特币核心在0.12版本(2016年)移除了基于硬币年龄的免费交易优先级,因为低手续费交易严重拖慢区块打包速度。现代网络中,费率是唯一实际起作用的优先级指标。
综合评分公式:70% 费率 + 30% 硬币年龄
#![allow(unused)]
fn main() {
/// 综合评分 = 费率 × 0.7 + 优先级 × 0.001 × 0.3
pub fn calculate_score(fee_rate: f64, priority: f64) -> f64 {
fee_rate * 0.7 + priority * 0.001 * 0.3
}
}
这个加权公式的设计思路:
- 70%的权重给费率:保证矿工利益最大化,高费率交易仍然优先
- 30%的权重给硬币年龄(乘以0.001缩放系数):给长期等待的交易一个“加分“,避免低费率旧UTXO永久无法确认
#![allow(unused)]
fn main() {
// 完整评分示例
let fee_rate = TxPriorityCalculator::calculate_fee_rate(500, 200); // 2.5 sat/byte
let priority = TxPriorityCalculator::calculate_priority(50_000_000, 10, 200); // 2_500_000
let score = TxPriorityCalculator::calculate_score(fee_rate, priority);
println!("综合分数: {:.4}", score);
// score = 2.5 * 0.7 + 2_500_000 * 0.001 * 0.3 = 1.75 + 750 = 751.75
}
手续费推荐
TxPriorityCalculator::recommend_fee根据紧急程度返回建议手续费:
#![allow(unused)]
fn main() {
pub enum FeeUrgency {
Low, // 低优先级:几小时内确认
Medium, // 中优先级:30-60分钟确认
High, // 高优先级:10-20分钟(约1-2个区块)
Urgent, // 紧急:下一个区块(最高优先级)
}
pub fn recommend_fee(tx_size: usize, urgency: FeeUrgency) -> u64 {
let sat_per_byte = match urgency {
FeeUrgency::Low => 1.0, // 1 sat/byte
FeeUrgency::Medium => 5.0, // 5 sat/byte
FeeUrgency::High => 20.0, // 20 sat/byte
FeeUrgency::Urgent => 50.0, // 50 sat/byte
};
(tx_size as f64 * sat_per_byte) as u64
}
}
#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::{TxPriorityCalculator, FeeUrgency};
// 估算一笔标准交易(1输入2输出)的建议手续费
let tx_size = 10 + 1 * 148 + 2 * 34; // = 226 字节
let low_fee = TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Low);
let medium_fee = TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Medium);
let high_fee = TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::High);
let urgent_fee = TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Urgent);
println!("低优先级: {} sat ({} sat/byte)", low_fee, 1); // 226 sat
println!("中优先级: {} sat ({} sat/byte)", medium_fee, 5); // 1130 sat
println!("高优先级: {} sat ({} sat/byte)", high_fee, 20); // 4520 sat
println!("紧急: {} sat ({} sat/byte)", urgent_fee, 50); // 11300 sat
}
实际比特币网络费率参考(2024年数据,BTC/USD = 60,000$):
| 紧急程度 | 典型费率 | 约合美元(226字节交易) |
|---|---|---|
| 低 | 1-3 sat/byte | $0.14 - $0.41 |
| 中 | 5-15 sat/byte | $0.68 - $2.03 |
| 高 | 20-50 sat/byte | $2.71 - $6.78 |
| 紧急 | 50-200 sat/byte | $6.78 - $27.1 |
注意:实际费率受网络拥堵影响极大。2017年牛市高峰期,部分用户支付了超过50美元的手续费才能快速确认。
低费率交易淘汰机制
当内存池达到容量上限时,会自动淘汰费率最低的交易以腾出空间:
#![allow(unused)]
fn main() {
fn evict_low_fee_transactions(&mut self, needed_size: usize) -> Result<()> {
let mut freed_size = 0;
let mut to_remove = Vec::new();
// 从【低费率到高费率】遍历(BTreeMap正向迭代)
for (_fee_rate, txids) in self.fee_index.iter() {
for txid in txids {
if let Some(entry) = self.transactions.get(txid) {
to_remove.push(txid.clone());
freed_size += entry.size;
if freed_size >= needed_size {
break;
}
}
}
if freed_size >= needed_size { break; }
}
// 执行淘汰
for txid in &to_remove {
self.remove_transaction(txid)?;
}
Ok(())
}
}
这个设计保证了内存池始终维护着“费率最高“的交易子集,低费率交易在竞争中被自然淘汰。
#![allow(unused)]
fn main() {
// 创建一个容量极小的内存池来演示淘汰行为
let mut mempool = Mempool::new(1000, 1.0); // 仅1KB容量
// 添加多笔交易,当超过1KB时,低费率的会被淘汰
for i in 1..=10 {
let tx = create_tx_with_fee(i * 100); // fee: 100, 200, ..., 1000
let _ = mempool.add_transaction(tx); // 低费率的可能被淘汰
}
}
过期交易清理
默认情况下,在内存池中等待超过72小时的交易会被清理:
#![allow(unused)]
fn main() {
// 定期调用(例如每小时一次)
let expired_count = mempool.clear_expired();
if expired_count > 0 {
println!("清理了 {} 笔过期交易", expired_count);
}
}
内存池统计信息
#![allow(unused)]
fn main() {
let stats = mempool.get_stats();
println!("待确认交易数: {}", stats.tx_count);
println!("内存池大小: {} / {} bytes", stats.total_size, stats.max_size);
println!("总待收手续费: {} sat", stats.total_fees);
println!("平均费率: {:.2} sat/byte", stats.avg_fee_rate);
println!("最低接受费率: {:.2} sat/byte", stats.min_fee_rate);
}
Replace-By-Fee(RBF)
RBF(BIP125)允许用户用更高手续费的新交易替换内存池中的旧交易。在SimpleBTC中,advanced_tx.rs的RBFManager管理可替换交易:
#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::RBFManager;
let mut rbf = RBFManager::new();
// 标记交易为可替换(发送时设置sequence < 0xFFFFFFFE)
rbf.mark_replaceable("original_tx_id");
// 稍后,以更高费用的新交易替换旧交易
let can_replace = rbf.can_replace(&old_tx, &new_tx);
match can_replace {
Ok(()) => println!("RBF替换成功"),
Err(reason) => println!("替换被拒绝: {}", reason),
}
}
RBF替换条件(由can_replace验证):
- 旧交易必须已标记为可替换(
replaceable = true) - 新旧交易的输入数量必须相同,且引用相同的UTXO
- 新交易手续费必须严格高于旧交易
- 手续费增量至少为旧交易大小(约1 sat/byte)
小结
SimpleBTC的交易优先级系统由三个层次构成:
| 层次 | 组件 | 作用 |
|---|---|---|
| 内存池排序 | Mempool + BTreeMap<fee_rate> | 按费率自动维护有序队列 |
| 优先级计算 | TxPriorityCalculator | 费率、硬币年龄、综合评分 |
| 手续费推荐 | FeeUrgency + recommend_fee | 按紧急程度推荐合理费用 |
核心公式回顾:
费率(sat/byte)= 手续费 / 交易大小
综合评分 = 费率 × 0.7 + 硬币年龄优先级 × 0.001 × 0.3
推荐手续费 = 交易大小 × sat_per_byte(按紧急程度选取1/5/20/50)
企业多签钱包实战
本案例演示如何使用2-of-3多签管理企业资金。
场景描述
某科技公司需要管理公司的比特币资产,要求:
- 三位高管(CEO、CFO、CTO)各持一个密钥
- 任意两位高管同意即可转账
- 防止单人滥用或失控
- 某位高管不在也能正常运作
运行示例
cargo run --example enterprise_multisig
代码详解
1. 初始化
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
multisig::MultiSigAddress,
};
fn main() -> Result<(), String> {
println!("=== 企业多签钱包演示 ===\n");
// 创建区块链
let mut blockchain = Blockchain::new();
// 创建三位高管的钱包
let ceo = Wallet::new();
let cfo = Wallet::new();
let cto = Wallet::new();
println!("✓ 创建企业高管钱包:");
println!(" CEO: {}", &ceo.address[..20]);
println!(" CFO: {}", &cfo.address[..20]);
println!(" CTO: {}\n", &cto.address[..20]);
2. 创建多签地址
#![allow(unused)]
fn main() {
// 创建2-of-3多签地址
let company_multisig = MultiSigAddress::new(
2, // 需要2个签名
vec![
ceo.public_key.clone(),
cfo.public_key.clone(),
cto.public_key.clone(),
]
).expect("创建多签地址失败");
println!("✓ 公司多签地址已创建:");
println!(" 地址: {}", &company_multisig.address[..20]);
println!(" 类型: {}-of-{} 多签",
company_multisig.required_sigs,
company_multisig.total_keys);
println!(" 规则: 任意2位高管签名即可转账\n");
}
关键点:
required_sigs = 2: 需要2个签名total_keys = 3: 共3个密钥- 任意两位高管的组合都可以:CEO+CFO、CEO+CTO、CFO+CTO
3. 注入初始资金
#![allow(unused)]
fn main() {
// 为公司多签地址注入资金
println!("--- 场景1: 公司获得融资 ---");
let investor = Wallet::new();
println!("投资人地址: {}\n", &investor.address[..20]);
// 创建融资交易(从创世地址)
let funding_tx = blockchain.create_transaction(
&Wallet::from_address("genesis_address".to_string()),
company_multisig.address.clone(),
100000, // 10万 satoshi
0,
)?;
blockchain.add_transaction(funding_tx)?;
blockchain.mine_pending_transactions(investor.address.clone())?;
let company_balance = blockchain.get_balance(&company_multisig.address);
println!("✓ 融资完成");
println!(" 公司账户余额: {} satoshi\n", company_balance);
}
4. 场景演示:正常支出
#![allow(unused)]
fn main() {
println!("--- 场景2: 正常支出(CEO + CFO批准)---");
let supplier = Wallet::new();
println!("供应商地址: {}\n", &supplier.address[..20]);
// 模拟多签流程
let payment_amount = 30000;
let payment_data = format!("{}{}", company_multisig.address, supplier.address);
// 步骤1: CEO签名
let ceo_signature = ceo.sign(&payment_data);
println!("✓ CEO已审批并签名");
// 步骤2: CFO签名
let cfo_signature = cfo.sign(&payment_data);
println!("✓ CFO已审批并签名");
// 步骤3: 验证签名数量
let signatures = vec![ceo_signature, cfo_signature];
if signatures.len() >= company_multisig.required_sigs {
println!("✓ 签名数量满足要求 (2/3)");
println!("✓ 交易可以执行\n");
// 实际转账
let payment_tx = blockchain.create_transaction(
&Wallet::from_address(company_multisig.address.clone()),
supplier.address.clone(),
payment_amount,
100,
)?;
blockchain.add_transaction(payment_tx)?;
blockchain.mine_pending_transactions(ceo.address.clone())?;
println!("✓ 支付完成");
println!(" 支付金额: {} satoshi", payment_amount);
println!(" 公司余额: {} satoshi\n",
blockchain.get_balance(&company_multisig.address));
}
}
工作流程:
- CEO发起支付请求
- CEO使用私钥签名
- CFO审核并签名
- 系统验证签名数量(2个 ≥ 要求的2个)
- 执行转账
5. 场景演示:CEO不在场
#![allow(unused)]
fn main() {
println!("--- 场景3: CEO出差期间的紧急支出(CFO + CTO)---");
let emergency_vendor = Wallet::new();
println!("紧急供应商: {}\n", &emergency_vendor.address[..20]);
let emergency_amount = 20000;
let emergency_data = format!("{}{}",
company_multisig.address, emergency_vendor.address);
println!("CEO正在出差,无法联系");
println!("CFO和CTO决定批准紧急支出\n");
// CFO签名
let cfo_sig = cfo.sign(&emergency_data);
println!("✓ CFO已签名");
// CTO签名
let cto_sig = cto.sign(&emergency_data);
println!("✓ CTO已签名");
let emergency_sigs = vec![cfo_sig, cto_sig];
if emergency_sigs.len() >= company_multisig.required_sigs {
println!("✓ 签名满足要求 (2/3)");
println!("✓ 即使CEO不在,业务仍可正常运作\n");
// 执行转账
let emergency_tx = blockchain.create_transaction(
&Wallet::from_address(company_multisig.address.clone()),
emergency_vendor.address,
emergency_amount,
100,
)?;
blockchain.add_transaction(emergency_tx)?;
blockchain.mine_pending_transactions(cfo.address.clone())?;
println!("✓ 紧急支付完成");
println!(" 最终余额: {} satoshi\n",
blockchain.get_balance(&company_multisig.address));
}
Ok(())
}
}
输出示例
=== 企业多签钱包演示 ===
✓ 创建企业高管钱包:
CEO: a3f2d8c9e4b7f1a8...
CFO: b9e4c7d2a3f1e8b6...
CTO: c8f1e9d3b4a7c2e5...
✓ 公司多签地址已创建:
地址: 3Mf2d8c9e4b7f1a8...
类型: 2-of-3 多签
规则: 任意2位高管签名即可转账
--- 场景1: 公司获得融资 ---
投资人地址: d7c2e8f3a9b1d4c6...
区块已挖出: 0003ab4f9c2d...
✓ 融资完成
公司账户余额: 100000 satoshi
--- 场景2: 正常支出(CEO + CFO批准)---
供应商地址: e6d1f8c2b9a3e7d4...
✓ CEO已审批并签名
✓ CFO已审批并签名
✓ 签名数量满足要求 (2/3)
✓ 交易可以执行
区块已挖出: 0007c3e8d1a9...
✓ 支付完成
支付金额: 30000 satoshi
公司余额: 69900 satoshi
--- 场景3: CEO出差期间的紧急支出(CFO + CTO)---
紧急供应商: f5e2d9c3a8b7f1e6...
CEO正在出差,无法联系
CFO和CTO决定批准紧急支出
✓ CFO已签名
✓ CTO已签名
✓ 签名满足要求 (2/3)
✓ 即使CEO不在,业务仍可正常运作
区块已挖出: 000ab7e4f2c8...
✓ 紧急支付完成
最终余额: 49800 satoshi
业务价值
1. 安全性
| 传统单签 | 企业多签 |
|---|---|
| ❌ CEO私钥被盗,全部资金丢失 | ✅ 需要2个密钥,单个被盗无风险 |
| ❌ 单点故障 | ✅ 分散风险 |
| ❌ 内部舞弊风险高 | ✅ 需要两人合谋才可能 |
2. 业务连续性
| 场景 | 传统方案 | 多签方案 |
|---|---|---|
| CEO休假 | ❌ 业务暂停 | ✅ CFO+CTO继续运作 |
| 高管离职 | ❌ 需要全部转移资金 | ✅ 更换一个密钥即可 |
| 紧急支出 | ❌ 找不到唯一的密钥持有人 | ✅ 任意2人即可批准 |
3. 合规性
审计追踪:
- 每笔交易需要2个签名
- 明确记录谁批准了什么
- 符合内部控制要求
- 满足财务审计标准
扩展方案
分级授权
#![allow(unused)]
fn main() {
// 小额:经理级 2-of-3
if amount < 10000 {
let managers_multisig = MultiSigAddress::new(2, manager_keys)?;
}
// 中额:高管级 2-of-3
else if amount < 100000 {
let exec_multisig = MultiSigAddress::new(2, exec_keys)?;
}
// 大额:董事会 5-of-9
else {
let board_multisig = MultiSigAddress::new(5, board_keys)?;
}
}
时间锁保护
#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::TimeLock;
// 大额转账需要24小时延迟
let timelock = TimeLock::new_time_based(
current_time() + 24 * 3600
);
// 延迟期间可以取消
// 防止胁迫转账
}
紧急恢复
#![allow(unused)]
fn main() {
// 正常:2-of-3
let normal_multisig = MultiSigAddress::new(
2,
vec![ceo_key, cfo_key, cto_key]
)?;
// 紧急(2个密钥丢失):律师托管的恢复密钥
let recovery_multisig = MultiSigAddress::new(
1,
vec![lawyer_key] // 需要法律文件证明
)?;
}
实施建议
1. 密钥管理
CEO密钥:
- 主密钥:手机热钱包(日常签名)
- 备份:硬件钱包(保险柜)
CFO密钥:
- 主密钥:电脑热钱包(办公室)
- 备份:纸钱包(银行保险箱)
CTO密钥:
- 主密钥:硬件钱包(随身携带)
- 备份:加密U盘(异地存储)
2. 操作流程
1. 发起人创建转账申请
2. 发起人签名
3. 通知第二审批人
4. 第二审批人审核并签名
5. 系统自动验证签名数量
6. 执行交易并通知所有人
7. 记录审计日志
3. 安全检查清单
- 密钥分散存储
- 定期测试恢复流程
- 备份所有密钥
- 设置金额阈值
- 启用交易通知
- 定期审计交易记录
- 制定密钥丢失应急预案
- 培训所有密钥持有人
相关资源
总结
企业多签钱包通过2-of-3机制实现了:
✅ 安全性 - 没有单点故障 ✅ 灵活性 - 任意两人可批准 ✅ 连续性 - 某人不在仍可运作 ✅ 合规性 - 符合内部控制 ✅ 透明性 - 所有操作可追溯
是企业管理数字资产的最佳实践!
托管服务实战
本案例演示如何使用2-of-3多签实现比特币托管服务。
场景描述
在电商交易中,买卖双方互不信任,需要第三方托管:
- 买家担心:付款后卖家不发货
- 卖家担心:发货后买家不付款
- 解决方案:资金托管在2-of-3多签地址
参与方:
- 买家(Buyer)
- 卖家(Seller)
- 仲裁员(Arbitrator)
规则:
- 正常交易:买家 + 卖家签名 → 资金给卖家
- 争议处理:买家/卖家 + 仲裁员 → 按仲裁结果
运行示例
cargo run --example escrow_service
代码详解
1. 初始化参与方
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
multisig::MultiSigAddress,
};
fn main() -> Result<(), String> {
println!("=== 比特币托管服务演示 ===\n");
// 创建区块链
let mut blockchain = Blockchain::new();
// 创建参与方钱包
let buyer = Wallet::new();
let seller = Wallet::new();
let arbitrator = Wallet::new();
println!("✓ 参与方已创建:");
println!(" 买家: {}", &buyer.address[..20]);
println!(" 卖家: {}", &seller.address[..20]);
println!(" 仲裁员: {}\n", &arbitrator.address[..20]);
2. 创建托管多签地址
#![allow(unused)]
fn main() {
// 创建2-of-3托管多签地址
let escrow_multisig = MultiSigAddress::new(
2, // 需要2个签名
vec![
buyer.public_key.clone(),
seller.public_key.clone(),
arbitrator.public_key.clone(),
]
).expect("创建多签地址失败");
println!("✓ 托管地址已创建:");
println!(" 地址: {}", &escrow_multisig.address[..20]);
println!(" 类型: {}-of-{} 多签",
escrow_multisig.required_sigs,
escrow_multisig.total_keys);
println!(" 规则: 任意2方签名即可");
println!(" 可能组合:");
println!(" - 买家 + 卖家(正常交易)");
println!(" - 买家 + 仲裁员(买家退款)");
println!(" - 卖家 + 仲裁员(卖家收款)\n");
}
关键点:
- 2-of-3确保没有单方控制
- 正常情况买卖双方自行解决
- 争议时仲裁员介入
3. 买家存入资金
#![allow(unused)]
fn main() {
println!("--- 场景1: 买家存入托管资金 ---");
// 买家获得初始资金
let funding_tx = blockchain.create_transaction(
&Wallet::from_address("genesis_address".to_string()),
buyer.address.clone(),
100000, // 10万 satoshi
0,
)?;
blockchain.add_transaction(funding_tx)?;
blockchain.mine_pending_transactions(buyer.address.clone())?;
let buyer_initial = blockchain.get_balance(&buyer.address);
println!("买家余额: {} sat\n", buyer_initial);
// 买家将货款转入托管地址
let escrow_amount = 50000; // 5万 sat
println!("商品价格: {} sat", escrow_amount);
println!("买家将货款转入托管地址...\n");
let deposit_tx = blockchain.create_transaction(
&buyer,
escrow_multisig.address.clone(),
escrow_amount,
100, // 手续费
)?;
blockchain.add_transaction(deposit_tx)?;
blockchain.mine_pending_transactions(buyer.address.clone())?;
let escrow_balance = blockchain.get_balance(&escrow_multisig.address);
println!("✓ 资金已托管");
println!(" 托管金额: {} sat", escrow_balance);
println!(" 买家余额: {} sat\n", blockchain.get_balance(&buyer.address));
}
流程:
- 买家先获得资金
- 买家将货款转入托管地址
- 资金被锁定在多签地址中
- 卖家看到托管成功后发货
4. 场景A:正常交易完成
#![allow(unused)]
fn main() {
println!("--- 场景2A: 正常交易(买家满意)---");
println!("卖家已发货");
println!("买家收到货物,确认满意\n");
// 买家和卖家都签名,释放资金给卖家
let payment_amount = escrow_balance - 50; // 扣除手续费
let payment_data = format!("{}{}{}",
escrow_multisig.address,
seller.address,
payment_amount);
println!("签名过程:");
// 买家签名
let buyer_signature = buyer.sign(&payment_data);
println!(" ✓ 买家已签名(确认收货)");
// 卖家签名
let seller_signature = seller.sign(&payment_data);
println!(" ✓ 卖家已签名(同意收款)");
// 验证签名数量
let signatures = vec![buyer_signature, seller_signature];
if signatures.len() >= escrow_multisig.required_sigs {
println!("\n✓ 签名满足要求 (2/3)");
println!("✓ 释放资金给卖家\n");
// 创建支付交易
let payment_tx = blockchain.create_transaction(
&Wallet::from_address(escrow_multisig.address.clone()),
seller.address.clone(),
payment_amount,
50,
)?;
blockchain.add_transaction(payment_tx)?;
blockchain.mine_pending_transactions(seller.address.clone())?;
println!("=== 交易完成 ===");
println!("卖家余额: {} sat", blockchain.get_balance(&seller.address));
println!("托管余额: {} sat", blockchain.get_balance(&escrow_multisig.address));
}
}
正常流程:
- 卖家发货
- 买家收货确认
- 买家签名(确认满意)
- 卖家签名(同意收款)
- 2个签名满足要求
- 资金释放给卖家
5. 场景B:争议处理
#![allow(unused)]
fn main() {
println!("\n--- 场景2B: 争议处理(货物有问题)---");
// 重新创建场景(假设)
let escrow_multisig_dispute = MultiSigAddress::new(
2,
vec![
buyer.public_key.clone(),
seller.public_key.clone(),
arbitrator.public_key,
]
)?;
println!("买家: 货物与描述不符,要求退款");
println!("卖家: 货物没问题,拒绝退款");
println!("仲裁员介入调查...\n");
println!("仲裁结果:");
println!(" 经核实,货物确实存在问题");
println!(" 判决:退款给买家\n");
// 买家 + 仲裁员签名
let refund_data = format!("{}{}{}",
escrow_multisig_dispute.address,
buyer.address,
payment_amount);
println!("签名过程:");
let buyer_sig_dispute = buyer.sign(&refund_data);
println!(" ✓ 买家签名(同意退款)");
let arbitrator_sig = arbitrator.sign(&refund_data);
println!(" ✓ 仲裁员签名(执行判决)");
let dispute_sigs = vec![buyer_sig_dispute, arbitrator_sig];
if dispute_sigs.len() >= escrow_multisig_dispute.required_sigs {
println!("\n✓ 签名满足要求 (2/3)");
println!("✓ 执行退款\n");
println!("=== 争议解决 ===");
println!("退款给买家: {} sat", payment_amount);
println!("仲裁费: 50 sat(从托管金扣除)");
}
Ok(())
}
}
争议流程:
- 买家投诉货物问题
- 卖家拒绝退款
- 仲裁员介入调查
- 仲裁员做出判决
- 买家 + 仲裁员签名
- 资金退还买家
输出示例
=== 比特币托管服务演示 ===
✓ 参与方已创建:
买家: a3f2d8c9e4b7f1a8...
卖家: b9e4c7d2a3f1e8b6...
仲裁员: c8f1e9d3b4a7c2e5...
✓ 托管地址已创建:
地址: 3Mf2d8c9e4b7f1a8...
类型: 2-of-3 多签
规则: 任意2方签名即可
可能组合:
- 买家 + 卖家(正常交易)
- 买家 + 仲裁员(买家退款)
- 卖家 + 仲裁员(卖家收款)
--- 场景1: 买家存入托管资金 ---
买家余额: 100000 sat
商品价格: 50000 sat
买家将货款转入托管地址...
✓ 资金已托管
托管金额: 50000 sat
买家余额: 49900 sat
--- 场景2A: 正常交易(买家满意)---
卖家已发货
买家收到货物,确认满意
签名过程:
✓ 买家已签名(确认收货)
✓ 卖家已签名(同意收款)
✓ 签名满足要求 (2/3)
✓ 释放资金给卖家
=== 交易完成 ===
卖家余额: 49950 sat
托管余额: 0 sat
--- 场景2B: 争议处理(货物有问题)---
买家: 货物与描述不符,要求退款
卖家: 货物没问题,拒绝退款
仲裁员介入调查...
仲裁结果:
经核实,货物确实存在问题
判决:退款给买家
签名过程:
✓ 买家签名(同意退款)
✓ 仲裁员签名(执行判决)
✓ 签名满足要求 (2/3)
✓ 执行退款
=== 争议解决 ===
退款给买家: 49950 sat
仲裁费: 50 sat(从托管金扣除)
业务价值
1. 买家保护
| 传统交易 | 托管服务 |
|---|---|
| ❌ 付款后卖家不发货 | ✓ 资金托管,发货后才释放 |
| ❌ 货不对版无法退款 | ✓ 仲裁员判定可退款 |
| ❌ 纠纷无处申诉 | ✓ 仲裁机制保护权益 |
2. 卖家保护
| 传统交易 | 托管服务 |
|---|---|
| ❌ 发货后买家拒付 | ✓ 货款已托管,正常发货即可 |
| ❌ 恶意退款 | ✓ 仲裁员公正判断 |
| ❌ 无担保风险 | ✓ 资金确定性 |
3. 公平性
买家单独无法取走资金(需要卖家或仲裁员)
卖家单独无法取走资金(需要买家或仲裁员)
仲裁员单独无法取走资金(需要买卖双方之一)
→ 三方制衡,公平公正
扩展方案
1. 自动仲裁
#![allow(unused)]
fn main() {
struct AutoArbitration {
物流跟踪: bool,
照片证据: Vec<String>,
聊天记录: Vec<Message>,
}
fn auto_judge(evidence: &AutoArbitration) -> Decision {
if evidence.物流跟踪 && evidence.照片证据.len() > 3 {
Decision::RefundBuyer // 自动退款
} else {
Decision::ManualReview // 人工审核
}
}
}
2. 分阶段释放
#![allow(unused)]
fn main() {
// 阶段1: 发货确认 - 释放50%
// 阶段2: 收货确认 - 释放剩余50%
let stage1 = escrow_amount / 2;
let stage2 = escrow_amount - stage1;
// 卖家提供物流单号 → 释放stage1
// 买家确认收货 → 释放stage2
}
3. 时间锁保护
#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::TimeLock;
// 7天内无争议,自动释放给卖家
let seven_days = 7 * 24 * 3600;
let auto_release = TimeLock::new_time_based(current_time + seven_days);
if auto_release.is_mature(...) && no_dispute {
release_to_seller();
}
}
4. 多层仲裁
#![allow(unused)]
fn main() {
// 一级仲裁: 普通仲裁员
// 二级仲裁: 资深仲裁员
// 三级仲裁: 仲裁委员会 (3-of-5)
let appeals_committee = MultiSigAddress::new(
3,
vec![arbitrator1, arbitrator2, arbitrator3, arbitrator4, arbitrator5]
)?;
}
仲裁员机制
选择标准
✓ 信誉良好(历史记录)
✓ 专业知识(商品类别)
✓ 中立公正(无利益冲突)
✓ 响应及时(24小时内)
仲裁费用
#![allow(unused)]
fn main() {
let arbitration_fee = match dispute_complexity {
Simple => 50, // 0.1%
Medium => 100, // 0.2%
Complex => 500, // 1%
};
// 从托管金扣除
let net_amount = escrow_amount - arbitration_fee;
}
仲裁流程
1. 买家/卖家发起争议
2. 提交证据(照片、聊天记录)
3. 仲裁员审核(3个工作日内)
4. 做出判决
5. 执行判决(签名)
6. 收取仲裁费
安全考虑
1. 仲裁员串通
风险: 仲裁员与买家/卖家串通
防护:
#![allow(unused)]
fn main() {
// 仲裁员需要质押
let arbitrator_deposit = 100000;
// 串通被发现,扣除质押
if collusion_detected {
slash_deposit(&arbitrator);
ban_arbitrator(&arbitrator);
}
// 多个仲裁员投票
let arbitrators = vec![arb1, arb2, arb3];
let decision = majority_vote(&arbitrators);
}
2. 证据造假
防护:
#![allow(unused)]
fn main() {
// 物流信息上链
blockchain.add_tracking_info(tracking_number);
// 照片哈希上链(防篡改)
let photo_hash = hash_photo(photo);
blockchain.add_evidence_hash(photo_hash);
// 时间戳证明
let timestamp = blockchain.get_block_time();
}
3. 恶意拖延
防护:
#![allow(unused)]
fn main() {
// 设置仲裁时限
let deadline = current_time + 7 * 86400; // 7天
if current_time > deadline && no_decision {
// 超时自动退款
refund_to_buyer();
}
}
实施建议
1. 技术栈
前端: Web界面展示托管流程
后端: SimpleBTC + 数据库
存储: 证据存储(IPFS)
通知: 邮件/短信提醒
2. 用户流程
买家:
1. 浏览商品
2. 下单并将货款转入托管
3. 等待卖家发货
4. 收货并确认
5. 签名释放资金
卖家:
1. 等待买家托管资金
2. 看到托管成功后发货
3. 提供物流单号
4. 等待买家确认
5. 签名收款
3. 费用结构
平台手续费: 1%
仲裁费: 0.1-1%(争议时)
区块链手续费: 动态(50-200 sat)
对比传统方案
vs 支付宝担保交易
| 特性 | SimpleBTC托管 | 支付宝担保 |
|---|---|---|
| 去中心化 | ✓ | ❌ 中心化 |
| 审查抗性 | ✓ | ❌ 可被审查 |
| 跨境支付 | ✓ | ❌ 受限 |
| 手续费 | 低(0.1-1%) | 较高(1-3%) |
| 隐私性 | 较好 | 较差 |
vs PayPal争议
| 特性 | SimpleBTC托管 | PayPal |
|---|---|---|
| 争议解决 | 仲裁员 | 平台客服 |
| 透明度 | 链上可查 | 黑箱操作 |
| 不可逆 | ✓ | ❌ 可冻结账户 |
相关资源
总结
托管服务通过2-of-3多签实现了:
✅ 买家保护 - 货不对版可退款 ✅ 卖家保护 - 货款确定到账 ✅ 公平仲裁 - 第三方公正判决 ✅ 去中心化 - 无需信任中心平台 ✅ 透明可查 - 链上记录公开
是电商、自由职业、跨境贸易的理想解决方案!
定期存款系统
本案例演示如何使用TimeLock功能实现一个定期存款系统,用户可以选择不同的存期(3个月或1年),在到期前资金无法取出,到期后可领取本金和利息。
业务场景
传统定期存款的痛点:
- 提前支取需要银行审批
- 利息计算不透明
- 需要信任银行
- 到期后需要手动操作
区块链解决方案:
- ✅ 智能合约自动执行,无需审批
- ✅ 利息规则写入代码,完全透明
- ✅ 到期前技术上无法提取(去信任)
- ✅ 到期自动解锁
系统架构
产品设计
| 产品 | 存期 | 年化利率 | 最低金额 | 到期方式 |
|---|---|---|---|---|
| 短期宝 | 3个月 | 3% | 1000 satoshi | 自动解锁 |
| 稳健盈 | 1年 | 5% | 5000 satoshi | 自动解锁 |
时间计算
#![allow(unused)]
fn main() {
// 3个月定期(约13周,91天)
const BLOCKS_PER_3_MONTHS: u64 = 13 * 7 * 144; // 13,104区块
// 1年定期(约52周,365天)
const BLOCKS_PER_YEAR: u64 = 52 * 7 * 144; // 52,416区块
// 注:比特币平均每10分钟一个区块,每天约144个区块
}
完整实现
以下是完整的定期存款系统代码:
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
advanced_tx::{TimeLock, TimeLockType},
};
fn main() -> Result<(), String> {
println!("=== 定期存款系统演示 ===\n");
// 初始化区块链
let mut blockchain = Blockchain::new();
// 创建用户钱包
let user = Wallet::new();
println!("用户地址: {}...{}", &user.address[..16], &user.address[36..]);
// 给用户初始资金
setup_balance(&mut blockchain, &user, 20000)?;
println!("初始余额: {} satoshi\n", blockchain.get_balance(&user.address));
// 场景1: 3个月定期存款
println!("--- 场景1: 3个月定期存款 (3%年化利率) ---");
let amount_3m = 5000;
let rate_3m = 0.03;
let blocks_3m = 13 * 7 * 144; // 13周
// 计算3个月利息
let interest_3m = (amount_3m as f64 * rate_3m * 3.0 / 12.0) as u64;
let total_3m = amount_3m + interest_3m;
println!("存入金额: {} satoshi", amount_3m);
println!("预期利息: {} satoshi (3个月 @ 3%)", interest_3m);
println!("到期总额: {} satoshi", total_3m);
println!("锁定区块数: {}", blocks_3m);
// 创建3个月定期
let timelock_3m = TimeLock::new(
TimeLockType::BlockHeight(blockchain.chain.len() as u64 + blocks_3m)
);
let deposit_tx_3m = timelock_3m.create_timelocked_transaction(
&mut blockchain,
&user,
user.address.clone(), // 到期后返回给自己
total_3m, // 本金+利息
10,
)?;
blockchain.add_transaction(deposit_tx_3m.clone())?;
blockchain.mine_pending_transactions(user.address.clone())?;
println!("✓ 3个月定期创建成功");
println!("交易ID: {}...{}\n", &deposit_tx_3m.id[..16], &deposit_tx_3m.id[56..]);
// 场景2: 1年定期存款
println!("--- 场景2: 1年定期存款 (5%年化利率) ---");
let amount_1y = 10000;
let rate_1y = 0.05;
let blocks_1y = 52 * 7 * 144; // 52周
// 计算1年利息
let interest_1y = (amount_1y as f64 * rate_1y) as u64;
let total_1y = amount_1y + interest_1y;
println!("存入金额: {} satoshi", amount_1y);
println!("预期利息: {} satoshi (1年 @ 5%)", interest_1y);
println!("到期总额: {} satoshi", total_1y);
println!("锁定区块数: {}", blocks_1y);
// 创建1年定期
let timelock_1y = TimeLock::new(
TimeLockType::BlockHeight(blockchain.chain.len() as u64 + blocks_1y)
);
let deposit_tx_1y = timelock_1y.create_timelocked_transaction(
&mut blockchain,
&user,
user.address.clone(),
total_1y,
10,
)?;
blockchain.add_transaction(deposit_tx_1y.clone())?;
blockchain.mine_pending_transactions(user.address.clone())?;
println!("✓ 1年定期创建成功");
println!("交易ID: {}...{}\n", &deposit_tx_1y.id[..16], &deposit_tx_1y.id[56..]);
// 显示当前余额
let current_balance = blockchain.get_balance(&user.address);
println!("剩余可用余额: {} satoshi", current_balance);
println!("定期存款总额: {} satoshi (锁定中)\n", amount_3m + amount_1y);
// 场景3: 尝试提前取款(应该失败)
println!("--- 场景3: 尝试提前取款 ---");
println!("当前区块高度: {}", blockchain.chain.len());
println!("3个月定期解锁高度: {}", blockchain.chain.len() as u64 + blocks_3m);
match timelock_3m.is_spendable(&blockchain) {
true => println!("❌ 错误:定期未到期却可以取款!"),
false => println!("✓ 正确:定期未到期,资金已锁定"),
}
// 场景4: 模拟时间流逝(挖矿到3个月后)
println!("\n--- 场景4: 3个月后到期 ---");
println!("模拟挖矿 {} 个区块...", blocks_3m);
// 快速模拟挖矿
for _ in 0..blocks_3m {
blockchain.mine_pending_transactions(user.address.clone())?;
}
println!("当前区块高度: {}", blockchain.chain.len());
// 检查是否可以取款
if timelock_3m.is_spendable(&blockchain) {
println!("✓ 3个月定期已到期,可以取款");
// 领取本金+利息
println!("领取金额: {} satoshi (本金 {} + 利息 {})",
total_3m, amount_3m, interest_3m);
let final_balance = blockchain.get_balance(&user.address);
println!("到账后余额: {} satoshi", final_balance);
} else {
println!("❌ 错误:定期已到期但无法取款");
}
// 场景5: 1年定期还未到期
println!("\n--- 场景5: 1年定期状态 ---");
println!("当前区块高度: {}", blockchain.chain.len());
println!("1年定期解锁高度: {}", blockchain.chain.len() as u64 + blocks_1y - blocks_3m);
match timelock_1y.is_spendable(&blockchain) {
true => println!("✓ 1年定期已到期,可以取款"),
false => {
let remaining = blocks_1y - blocks_3m;
println!("✓ 1年定期还未到期,还需 {} 个区块 (约 {} 天)",
remaining, remaining / 144);
}
}
println!("\n=== 演示完成 ===");
Ok(())
}
// 辅助函数:初始化余额
fn setup_balance(
blockchain: &mut Blockchain,
wallet: &Wallet,
amount: u64
) -> Result<(), String> {
let genesis = Wallet::from_address("genesis".to_string());
let tx = blockchain.create_transaction(
&genesis,
wallet.address.clone(),
amount,
0,
)?;
blockchain.add_transaction(tx)?;
blockchain.mine_pending_transactions(wallet.address.clone())?;
Ok(())
}
代码详解
1. 产品参数定义
#![allow(unused)]
fn main() {
// 短期宝:3个月定期
let amount_3m = 5000; // 存款金额
let rate_3m = 0.03; // 3%年化利率
let blocks_3m = 13 * 7 * 144; // 3个月 = 13周 = 13,104区块
// 计算利息:本金 × 年利率 × 时间(月/12)
let interest_3m = (amount_3m as f64 * rate_3m * 3.0 / 12.0) as u64;
// interest_3m = 5000 × 0.03 × 0.25 = 37.5 ≈ 37 satoshi
}
为什么用区块高度而非时间戳?
- 更精确:区块高度是离散的整数,不会有歧义
- 更可靠:时间戳可能被矿工操纵(±2小时)
- 更一致:全网对区块高度有统一共识
2. 创建时间锁定期
#![allow(unused)]
fn main() {
// 创建时间锁:当前高度 + 锁定期
let timelock_3m = TimeLock::new(
TimeLockType::BlockHeight(
blockchain.chain.len() as u64 + blocks_3m
)
);
}
关键点:
blockchain.chain.len()= 当前区块高度+ blocks_3m= 到期区块高度- 在到期高度之前,交易无法被花费
3. 创建定期存款交易
#![allow(unused)]
fn main() {
let deposit_tx_3m = timelock_3m.create_timelocked_transaction(
&mut blockchain,
&user, // 存款人
user.address.clone(), // 到期后返回给存款人
total_3m, // 本金 + 利息
10, // 手续费
)?;
}
交易流程:
用户余额 → [时间锁定交易] → UTXO池(锁定状态)
↓
(到期后才能花费)
↓
用户余额(本金+利息)
4. 到期检查
#![allow(unused)]
fn main() {
if timelock_3m.is_spendable(&blockchain) {
// 可以取款
} else {
// 还未到期
}
}
检查逻辑:
#![allow(unused)]
fn main() {
pub fn is_spendable(&self, blockchain: &Blockchain) -> bool {
match &self.lock_type {
TimeLockType::BlockHeight(height) => {
blockchain.chain.len() as u64 >= *height
},
TimeLockType::Timestamp(time) => {
// 使用当前时间戳比较
current_timestamp() >= *time
}
}
}
}
运行效果
$ cargo run --example timelock_savings
=== 定期存款系统演示 ===
用户地址: a3f2d8c9e4b7f1a8...c4e7d9b2a5c
初始余额: 20000 satoshi
--- 场景1: 3个月定期存款 (3%年化利率) ---
存入金额: 5000 satoshi
预期利息: 37 satoshi (3个月 @ 3%)
到期总额: 5037 satoshi
锁定区块数: 13104
✓ 3个月定期创建成功
交易ID: d4f7a9e2b5c8f1a3...b5c8f1a3d4f7
--- 场景2: 1年定期存款 (5%年化利率) ---
存入金额: 10000 satoshi
预期利息: 500 satoshi (1年 @ 5%)
到期总额: 10500 satoshi
锁定区块数: 52416
✓ 1年定期创建成功
交易ID: e5g8b0f3c6d9g2b4...c6d9g2b4e5g8
剩余可用余额: 4960 satoshi
定期存款总额: 15000 satoshi (锁定中)
--- 场景3: 尝试提前取款 ---
当前区块高度: 4
3个月定期解锁高度: 13108
✓ 正确:定期未到期,资金已锁定
--- 场景4: 3个月后到期 ---
模拟挖矿 13104 个区块...
当前区块高度: 13108
✓ 3个月定期已到期,可以取款
领取金额: 5037 satoshi (本金 5000 + 利息 37)
到账后余额: 10497 satoshi
--- 场景5: 1年定期状态 ---
当前区块高度: 13108
1年定期解锁高度: 52420
✓ 1年定期还未到期,还需 39312 个区块 (约 273 天)
=== 演示完成 ===
业务价值
对用户的价值
| 特性 | 传统银行定期 | 区块链定期 | 优势 |
|---|---|---|---|
| 利率透明 | ❌ 银行说了算 | ✅ 代码公开 | 完全透明 |
| 强制储蓄 | ⚠️ 可提前支取 | ✅ 技术锁定 | 真正强制 |
| 利息保障 | ⚠️ 银行承诺 | ✅ 智能合约 | 自动执行 |
| 到期操作 | ❌ 需要去银行 | ✅ 自动解锁 | 无需操作 |
| 信任成本 | 高(需要信任银行) | 低(信任代码) | 去中心化 |
收益对比(假设存入10000 satoshi)
| 产品 | 期限 | 利率 | 到期本息 | 收益 |
|---|---|---|---|---|
| 活期存款 | - | 0.3% | 10030 | 30 |
| 短期宝 | 3个月 | 3% | 10075 | 75 |
| 稳健盈 | 1年 | 5% | 10500 | 500 |
计算公式:
到期本息 = 本金 × (1 + 年利率 × 存期年数)
3个月: 10000 × (1 + 0.03 × 0.25) = 10075
1年: 10000 × (1 + 0.05 × 1.0) = 10500
扩展方案
1. 阶梯式定期
#![allow(unused)]
fn main() {
struct LadderDeposit {
amount: u64,
start_height: u64,
periods: Vec<(u64, f64)>, // (期限区块数, 利率)
}
impl LadderDeposit {
// 创建阶梯式定期:分散到期时间
pub fn new(total: u64, blockchain: &Blockchain) -> Self {
let per_amount = total / 4;
let current = blockchain.chain.len() as u64;
LadderDeposit {
amount: per_amount,
start_height: current,
periods: vec![
(13 * 7 * 144, 0.03), // 3个月,3%
(26 * 7 * 144, 0.04), // 6个月,4%
(39 * 7 * 144, 0.045), // 9个月,4.5%
(52 * 7 * 144, 0.05), // 12个月,5%
],
}
}
}
// 好处:
// - 每3个月有一笔到期,保持流动性
// - 平均利率高于单一短期
// - 降低利率波动风险
}
2. 自动续存
#![allow(unused)]
fn main() {
struct AutoRenewDeposit {
principal: u64,
term_blocks: u64,
rate: f64,
max_renewals: u32,
}
impl AutoRenewDeposit {
pub fn create_auto_renew(
&self,
blockchain: &mut Blockchain,
wallet: &Wallet,
) -> Result<Vec<Transaction>, String> {
let mut transactions = Vec::new();
let mut total = self.principal;
for i in 0..self.max_renewals {
let lock_height = blockchain.chain.len() as u64
+ (i as u64 + 1) * self.term_blocks;
// 计算本期本息
let interest = (total as f64 * self.rate
* (self.term_blocks as f64 / 52416.0)) as u64;
total += interest;
// 创建续存交易
let timelock = TimeLock::new(
TimeLockType::BlockHeight(lock_height)
);
let tx = timelock.create_timelocked_transaction(
blockchain,
wallet,
wallet.address.clone(),
total,
10,
)?;
transactions.push(tx);
}
Ok(transactions)
}
}
// 使用示例:
let auto_deposit = AutoRenewDeposit {
principal: 10000,
term_blocks: 13 * 7 * 144, // 3个月
rate: 0.03,
max_renewals: 4, // 自动续存4次 = 1年
};
// 自动创建4笔定期,每3个月自动续存一次
let txs = auto_deposit.create_auto_renew(&mut blockchain, &user)?;
}
3. 保本浮动收益
#![allow(unused)]
fn main() {
struct FloatingDeposit {
principal: u64,
min_rate: f64, // 保本利率
bonus_rate: f64, // 奖励利率
target_blocks: u64, // 目标区块数
}
impl FloatingDeposit {
pub fn calculate_interest(&self, blockchain: &Blockchain) -> u64 {
let actual_blocks = blockchain.chain.len() as u64;
// 基础利息(保本)
let base = (self.principal as f64 * self.min_rate) as u64;
// 奖励利息(根据实际持有时间)
if actual_blocks >= self.target_blocks {
let bonus = (self.principal as f64 * self.bonus_rate) as u64;
base + bonus
} else {
base
}
}
}
// 使用示例:
let floating = FloatingDeposit {
principal: 10000,
min_rate: 0.03, // 3%保本
bonus_rate: 0.02, // 额外2%奖励
target_blocks: 52 * 7 * 144, // 持有1年才有奖励
};
// 未满1年:3%利息 = 300 satoshi
// 满1年: 5%利息 = 500 satoshi
}
4. 提前赎回(罚息)
#![allow(unused)]
fn main() {
struct EarlyWithdraw {
deposit_tx: Transaction,
lock_height: u64,
penalty_rate: f64, // 罚息比例
}
impl EarlyWithdraw {
pub fn withdraw_early(
&self,
blockchain: &mut Blockchain,
wallet: &Wallet,
) -> Result<Transaction, String> {
let current = blockchain.chain.len() as u64;
// 检查是否提前赎回
if current >= self.lock_height {
return Err("已到期,请正常取款".to_string());
}
// 计算罚息
let principal = self.deposit_tx.outputs[0].value;
let penalty = (principal as f64 * self.penalty_rate) as u64;
let actual_amount = principal.saturating_sub(penalty);
// 创建提前赎回交易(需要管理员签名)
let tx = blockchain.create_transaction(
wallet,
wallet.address.clone(),
actual_amount,
10,
)?;
println!("提前赎回:本金 {}, 罚息 {}, 实得 {}",
principal, penalty, actual_amount);
Ok(tx)
}
}
// 使用示例:
// 用户存入10000,期限1年,提前6个月取出
// 罚息5% = 500 satoshi
// 实得9500 satoshi(损失500)
}
安全考虑
1. 利息资金来源
#![allow(unused)]
fn main() {
// ❌ 错误:凭空创造利息
let interest = 100;
let total = principal + interest; // 利息从哪来?
// ✅ 正确:利息从资金池支付
struct DepositPool {
reserves: u64, // 准备金
}
impl DepositPool {
pub fn pay_interest(&mut self, principal: u64, rate: f64) -> Result<u64, String> {
let interest = (principal as f64 * rate) as u64;
if self.reserves < interest {
return Err("资金池余额不足".to_string());
}
self.reserves -= interest;
Ok(interest)
}
}
}
2. 时间操纵攻击
攻击场景:矿工操纵时间戳,使定期提前到期
防御措施:
#![allow(unused)]
fn main() {
// ✅ 使用区块高度而非时间戳
TimeLockType::BlockHeight(height) // 推荐
// ⚠️ 避免使用时间戳(容易被操纵)
TimeLockType::Timestamp(time) // 不安全
}
3. 重入攻击
#![allow(unused)]
fn main() {
// ❌ 错误:先转账再更新状态
fn withdraw(&mut self) {
self.transfer(user, amount); // 先转账
self.balance = 0; // 后更新(可能被重入)
}
// ✅ 正确:先更新状态再转账(检查-生效-交互模式)
fn withdraw(&mut self) {
let amount = self.balance; // 检查
self.balance = 0; // 生效
self.transfer(user, amount); // 交互
}
}
4. 整数溢出
#![allow(unused)]
fn main() {
// ❌ 错误:可能溢出
let total = principal + interest; // u64溢出风险
// ✅ 正确:使用checked_add
let total = principal.checked_add(interest)
.ok_or("计算溢出")?;
}
实施建议
技术层面
-
测试充分性
#![allow(unused)] fn main() { #[cfg(test)] mod tests { #[test] fn test_interest_calculation() { /* ... */ } #[test] fn test_early_withdraw_penalty() { /* ... */ } #[test] fn test_timelock_enforcement() { /* ... */ } } } -
代码审计
- 利息计算公式是否正确
- 时间锁定是否可靠
- 资金来源是否明确
- 边界条件是否处理
-
监控告警
#![allow(unused)] fn main() { // 监控关键指标 - 资金池余额预警(< 10%) - 到期未领取定期(> 1个月) - 异常提前赎回频率 }
业务层面
-
风险提示
⚠️ 定期存款风险提示: 1. 资金将被锁定,到期前无法取出 2. 利息由资金池支付,存在支付风险 3. 智能合约可能存在未知漏洞 4. 区块链不可逆,操作需谨慎 -
用户教育
- 演示沙盒环境供用户练习
- 提供详细的操作指南
- 说明与传统银行的区别
- 强调私钥保管的重要性
-
产品迭代
- 收集用户反馈
- 分析到期数据
- 优化利率策略
- 增加产品种类
真实应用
DeFi定期存款协议
Compound: 借贷协议,存款自动生息
用户存入 ETH → 获得 cETH(计息代币)
利率随市场浮动 → 随时可取
Anchor Protocol: 固定利率存款(Terra生态)
存入 UST → 固定 ~20% APY
利息来自借贷市场和质押奖励
Alchemix: 自偿还贷款
存入 DAI → 借出 alUSD(50% LTV)
利息自动偿还贷款 → 无需还款
与SimpleBTC的对比
| 特性 | SimpleBTC定期 | DeFi定期 |
|---|---|---|
| 时间锁 | 硬锁定(nLockTime) | 软锁定(合约) |
| 利率 | 固定利率 | 通常浮动 |
| 流动性 | 到期才能取 | 可提前取(罚息) |
| 利息来源 | 资金池 | 借贷/质押 |
| 风险 | 时间锁风险 | 智能合约风险 |
常见问题
Q1: 定期存款的利息从哪来?
A: SimpleBTC的利息是演示性质的,实际应用中利息可能来自:
- 资金池的储备金
- 借贷市场的利差
- 矿工奖励的分配
- 交易手续费的返还
- 协议代币的增发
Q2: 可以提前取款吗?
A: SimpleBTC使用nLockTime硬锁定,技术上无法提前取款。实际应用可以设计:
- 罚息提前赎回(5-10%罚金)
- NFT质押借款(保持定期继续)
- 二级市场转让(折价卖给接盘侠)
Q3: 如果到期后忘记领取怎么办?
A: UTXO永久有效,任何时候都可以领取。但要注意:
- 逾期不会额外生息
- 建议设置到期提醒
- 可以实现自动续存
Q4: 时间锁定期怎么计算?
A:
区块高度法(推荐):
- 3个月 ≈ 13,104 区块 (91天 × 144区块/天)
- 1年 ≈ 52,416 区块 (365天 × 144区块/天)
时间戳法(不推荐):
- 3个月 = 当前时间戳 + 7,862,400 秒
- 1年 = 当前时间戳 + 31,536,000 秒
参考资料
- TimeLock详细教程 - 时间锁原理和用法
- Transaction API - 交易创建
- Blockchain API - 区块链操作
- Compound Finance - DeFi借贷协议
- BIP65 - CHECKLOCKTIMEVERIFY
核心模块 API
SimpleBTC核心模块提供了比特币区块链的基本功能。
模块列表
交易模块
- Transaction API - 交易结构、UTXO模型、手续费计算
区块模块
- Block API - 区块结构、工作量证明、Merkle根
区块链模块
- Blockchain API - 区块链管理、挖矿、验证
钱包模块
- Wallet API - 密钥生成、签名、地址
UTXO模块
- UTXO API - UTXO集合、余额查询、双花防护
快速索引
常用函数
创建钱包
#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;
let wallet = Wallet::new();
}
创建交易
#![allow(unused)]
fn main() {
let tx = blockchain.create_transaction(
&from_wallet,
to_address,
amount,
fee
)?;
}
挖矿
#![allow(unused)]
fn main() {
blockchain.mine_pending_transactions(miner_address)?;
}
查询余额
#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&address);
}
数据流程
1. 创建钱包
Wallet::new() → 生成密钥对 → 得到地址
2. 创建交易
选择UTXO → 构建输入输出 → 签名 → 验证
3. 添加交易
验证交易 → 加入待处理池 → 等待打包
4. 挖矿
收集交易 → 创建Coinbase → 计算Merkle根 → PoW → 更新UTXO
5. 查询
遍历UTXO集合 → 累加余额
类型定义
核心类型
#![allow(unused)]
fn main() {
// 金额单位:satoshi
type Amount = u64; // 1 BTC = 100,000,000 satoshi
// 地址:40字符十六进制
type Address = String;
// 哈希:64字符十六进制
type Hash = String;
// Unix时间戳(秒)
type Timestamp = u64;
}
错误类型
#![allow(unused)]
fn main() {
// 所有API使用 Result<T, String> 返回
type ApiResult<T> = Result<T, String>;
// 常见错误消息
"余额不足(包括手续费)"
"UTXO不存在"
"交易验证失败"
"引用的交易不存在"
"没有待处理的交易"
}
使用模式
基础模式
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
};
fn main() -> Result<(), String> {
// 1. 初始化
let mut blockchain = Blockchain::new();
let wallet = Wallet::new();
// 2. 操作
let tx = blockchain.create_transaction(...)?;
blockchain.add_transaction(tx)?;
blockchain.mine_pending_transactions(...)?;
// 3. 查询
let balance = blockchain.get_balance(&wallet.address);
Ok(())
}
错误处理模式
#![allow(unused)]
fn main() {
match blockchain.create_transaction(&alice, bob_addr, 1000, 10) {
Ok(tx) => {
blockchain.add_transaction(tx)?;
println!("✓ 交易成功");
}
Err(e) => {
eprintln!("✗ 错误: {}", e);
// 处理错误...
}
}
}
性能考虑
UTXO查询
- 时间复杂度:O(n),n为UTXO总数
- 建议:使用索引优化(见
indexer.rs)
挖矿
- 时间复杂度:O(2^difficulty)
- 建议:难度3-4适合demo,实际应用需更高
区块链验证
- 时间复杂度:O(n*m),n为区块数,m为平均交易数
- 建议:定期验证,而非每次操作后验证
线程安全
⚠️ 注意:当前实现不是线程安全的。
如需并发访问:
#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
let blockchain = Arc::new(Mutex::new(Blockchain::new()));
// 在不同线程中
let blockchain = blockchain.clone();
let mut bc = blockchain.lock().unwrap();
bc.create_transaction(...)?;
}
下一步
Transaction API
交易模块提供了比特币UTXO模型的完整实现。
数据结构
TxInput
交易输入,引用之前的未花费输出(UTXO)。
#![allow(unused)]
fn main() {
pub struct TxInput {
pub txid: String, // 被引用的交易ID
pub vout: usize, // 输出索引
pub signature: String, // 数字签名
pub pub_key: String, // 公钥
}
}
方法:
new
#![allow(unused)]
fn main() {
pub fn new(
txid: String,
vout: usize,
signature: String,
pub_key: String
) -> Self
}
创建新的交易输入。
参数:
txid- 被引用的交易IDvout- 输出索引号signature- 使用私钥生成的签名pub_key- 对应的公钥
示例:
#![allow(unused)]
fn main() {
let input = TxInput::new(
"abc123...".to_string(),
0,
wallet.sign("data"),
wallet.public_key.clone()
);
}
TxOutput
交易输出,代表一笔未花费的金额(UTXO)。
#![allow(unused)]
fn main() {
pub struct TxOutput {
pub value: u64, // 金额(satoshi)
pub pub_key_hash: String, // 接收者地址
}
}
方法:
new
#![allow(unused)]
fn main() {
pub fn new(value: u64, address: String) -> Self
}
创建新的交易输出。
参数:
value- 输出金额(satoshi)address- 接收者地址
示例:
#![allow(unused)]
fn main() {
let output = TxOutput::new(5000, bob_address);
}
can_be_unlocked_with
#![allow(unused)]
fn main() {
pub fn can_be_unlocked_with(&self, address: &str) -> bool
}
检查是否可以被指定地址解锁。
参数:
address- 要检查的地址
返回值:
true- 地址匹配false- 地址不匹配
示例:
#![allow(unused)]
fn main() {
if output.can_be_unlocked_with(&alice.address) {
println!("Alice可以花费这个输出");
}
}
Transaction
完整的交易结构。
#![allow(unused)]
fn main() {
pub struct Transaction {
pub id: String, // 交易ID
pub inputs: Vec<TxInput>, // 输入列表
pub outputs: Vec<TxOutput>, // 输出列表
pub timestamp: u64, // Unix时间戳
pub fee: u64, // 手续费
}
}
方法:
new
#![allow(unused)]
fn main() {
pub fn new(
inputs: Vec<TxInput>,
outputs: Vec<TxOutput>,
timestamp: u64,
fee: u64
) -> Self
}
创建新交易。
参数:
inputs- 交易输入列表outputs- 交易输出列表timestamp- Unix时间戳fee- 手续费(satoshi)
返回值:
- 新创建的交易实例,ID已自动计算
示例:
#![allow(unused)]
fn main() {
let tx = Transaction::new(
vec![input1, input2],
vec![output1, output2],
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
10
);
}
new_coinbase
#![allow(unused)]
fn main() {
pub fn new_coinbase(
to: String,
reward: u64,
timestamp: u64,
total_fees: u64
) -> Self
}
创建Coinbase交易(挖矿奖励)。
参数:
to- 矿工地址reward- 区块奖励(不含手续费)timestamp- Unix时间戳total_fees- 区块内所有交易的手续费总和
返回值:
- Coinbase交易实例
示例:
#![allow(unused)]
fn main() {
let coinbase = Transaction::new_coinbase(
miner.address,
50,
timestamp,
total_fees
);
}
calculate_hash
#![allow(unused)]
fn main() {
pub fn calculate_hash(&self) -> String
}
计算交易哈希(交易ID)。
返回值:
- 64字符的十六进制哈希字符串
说明:
- 使用SHA256算法
- 包含所有交易数据(输入、输出、时间戳、手续费)
- 任何数据改变都会导致完全不同的哈希
示例:
#![allow(unused)]
fn main() {
let tx_id = tx.calculate_hash();
println!("交易ID: {}", tx_id);
}
is_coinbase
#![allow(unused)]
fn main() {
pub fn is_coinbase(&self) -> bool
}
检查是否为Coinbase交易。
返回值:
true- Coinbase交易false- 普通交易
判断标准:
- 只有一个输入
- 该输入的txid为空
示例:
#![allow(unused)]
fn main() {
if tx.is_coinbase() {
println!("这是挖矿奖励交易");
} else {
println!("这是普通交易");
}
}
verify
#![allow(unused)]
fn main() {
pub fn verify(&self) -> bool
}
验证交易有效性(简化版)。
验证项:
- Coinbase交易总是有效
- 检查是否有输入和输出
- 检查签名和公钥非空
返回值:
true- 交易有效false- 交易无效
注意: 实际比特币还需验证:
- ECDSA签名正确性
- UTXO存在性
- 金额平衡
- 脚本执行
示例:
#![allow(unused)]
fn main() {
if tx.verify() {
blockchain.add_transaction(tx)?;
} else {
return Err("无效交易".to_string());
}
}
size
#![allow(unused)]
fn main() {
pub fn size(&self) -> usize
}
计算交易大小(字节)。
返回值:
- 交易的字节大小
用途:
- 计算手续费率(sat/byte)
- 评估区块空间占用
- 手续费估算
示例:
#![allow(unused)]
fn main() {
let size = tx.size();
println!("交易大小: {} 字节", size);
}
fee_rate
#![allow(unused)]
fn main() {
pub fn fee_rate(&self) -> f64
}
计算交易费率(satoshi/byte)。
返回值:
- 费率(sat/byte)
公式:
fee_rate = fee / size
费率参考:
- 1-5 sat/byte: 低优先级
- 5-20 sat/byte: 中优先级
- 20-50 sat/byte: 高优先级
- 50+ sat/byte: 紧急
示例:
#![allow(unused)]
fn main() {
let rate = tx.fee_rate();
println!("费率: {:.2} sat/byte", rate);
if rate < 5.0 {
println!("警告:费率较低,确认可能较慢");
}
}
output_sum
#![allow(unused)]
fn main() {
pub fn output_sum(&self) -> u64
}
获取所有输出的总金额。
返回值:
- 输出总额(satoshi)
用途:
- 验证交易平衡
- 计算实际手续费
公式:
fee = input_sum - output_sum
示例:
#![allow(unused)]
fn main() {
let output_total = tx.output_sum();
let fee = input_total - output_total;
println!("手续费: {}", fee);
}
使用示例
创建简单交易
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
};
fn main() -> Result<(), String> {
let mut blockchain = Blockchain::new();
let alice = Wallet::new();
let bob = Wallet::new();
// Alice获得初始资金
let init_tx = blockchain.create_transaction(
&Wallet::from_address("genesis".to_string()),
alice.address.clone(),
10000,
0,
)?;
blockchain.add_transaction(init_tx)?;
blockchain.mine_pending_transactions(alice.address.clone())?;
// Alice向Bob转账
let tx = blockchain.create_transaction(
&alice,
bob.address.clone(),
3000, // 金额
10, // 手续费
)?;
// 查看交易详情
println!("交易ID: {}", tx.id);
println!("输入数: {}", tx.inputs.len());
println!("输出数: {}", tx.outputs.len());
println!("手续费: {}", tx.fee);
println!("费率: {:.2} sat/byte", tx.fee_rate());
// 添加到区块链
blockchain.add_transaction(tx)?;
blockchain.mine_pending_transactions(bob.address)?;
Ok(())
}
批量交易
#![allow(unused)]
fn main() {
// 创建多笔交易,测试手续费优先级
let transactions = vec![
(bob.address.clone(), 1000, 1), // 低手续费
(charlie.address.clone(), 2000, 50), // 高手续费
(david.address.clone(), 3000, 5), // 中等手续费
];
for (to, amount, fee) in transactions {
let tx = blockchain.create_transaction(&alice, to, amount, fee)?;
blockchain.add_transaction(tx)?;
}
// 挖矿时会按费率从高到低排序
blockchain.mine_pending_transactions(miner.address)?;
}
手动构建交易
#![allow(unused)]
fn main() {
use std::time::{SystemTime, UNIX_EPOCH};
use bitcoin_simulation::transaction::{Transaction, TxInput, TxOutput};
// 1. 创建输入(需要知道之前的UTXO)
let input = TxInput::new(
"previous_tx_id".to_string(),
0, // vout
alice.sign("tx_data"),
alice.public_key.clone(),
);
// 2. 创建输出
let output1 = TxOutput::new(3000, bob.address); // 给Bob
let output2 = TxOutput::new(6990, alice.address); // 找零
// 3. 组装交易
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let tx = Transaction::new(
vec![input],
vec![output1, output2],
timestamp,
10, // 手续费
);
// 4. 验证和添加
if tx.verify() {
blockchain.add_transaction(tx)?;
}
}
错误处理
#![allow(unused)]
fn main() {
match blockchain.create_transaction(&alice, bob.address, 1000, 10) {
Ok(tx) => {
println!("✓ 交易创建成功");
blockchain.add_transaction(tx)?;
}
Err(e) => {
eprintln!("❌ 交易创建失败: {}", e);
// 常见错误:
// - "余额不足(包括手续费)"
// - "UTXO不存在"
// - "引用的交易不存在"
}
}
}
最佳实践
1. 手续费设置
#![allow(unused)]
fn main() {
// 根据紧急程度设置手续费
let size = estimate_tx_size(inputs_count, outputs_count);
let fee = match urgency {
Urgency::Low => size * 1, // 1 sat/byte
Urgency::Medium => size * 10, // 10 sat/byte
Urgency::High => size * 50, // 50 sat/byte
};
}
2. UTXO选择
#![allow(unused)]
fn main() {
// 优先使用小额UTXO,避免碎片化
let utxos = blockchain.utxo_set.find_spendable_outputs(&address, amount)?;
println!("使用了 {} 个UTXO", utxos.1.len());
}
3. 交易验证
#![allow(unused)]
fn main() {
// 创建交易后立即验证
let tx = Transaction::new(...);
assert!(tx.verify(), "交易验证失败");
assert!(tx.fee_rate() >= 1.0, "手续费率过低");
}
参考
- Blockchain API - 区块链交易管理
- UTXO API - UTXO集合操作
- Wallet API - 钱包和签名
Block API
Block 是区块链的基本组成单位,定义在 src/block.rs 中。每个区块包含一批已确认的交易,并通过哈希链与前一个区块相连,共同构成不可篡改的账本。
Block 结构体
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
pub index: u32, // 区块高度(索引),创世区块为 0
pub timestamp: u64, // Unix 时间戳(秒)
pub transactions: Vec<Transaction>, // 交易列表(第一笔必须是 Coinbase 交易)
pub previous_hash: String, // 父区块哈希(SHA256,64 字符十六进制)
pub hash: String, // 当前区块哈希(通过挖矿找到)
pub nonce: u64, // 工作量证明的随机数(挖矿时调整此值)
pub merkle_root: String, // 交易 Merkle 树根哈希
}
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
index | u32 | 区块高度。创世区块为 0,之后每个区块递增 1。 |
timestamp | u64 | 区块创建时的 Unix 时间戳(秒)。由 SystemTime::now() 自动填写。 |
transactions | Vec<Transaction> | 区块包含的交易列表。第一笔必须是 Coinbase 交易(矿工奖励)。 |
previous_hash | String | 父区块的 SHA256 哈希(64 字符十六进制)。创世区块此字段为 "0"。 |
hash | String | 当前区块的 SHA256 哈希。由 calculate_hash() 计算,在挖矿过程中持续更新直到满足难度要求。 |
nonce | u64 | 工作量证明的随机数。矿工通过递增 nonce 来寻找满足难度条件的哈希。 |
merkle_root | String | 区块内所有交易的 Merkle 树根哈希。任何交易被篡改都会导致此值改变。 |
区块链式结构
创世区块 (index=0) -> 区块 1 -> 区块 2
prev: "0" prev: abc123... prev: def456...
hash: abc123... hash: def456... hash: ghi789...
由于每个区块的 hash 依赖于 previous_hash,以及所有交易(通过 merkle_root),任何历史区块的修改都需要重新计算其后所有区块的哈希,这在计算上是不可行的。
方法
Block::new
创建一个新区块。自动设置时间戳并计算 Merkle 根,但 nonce 初始为 0,hash 为初始计算值(尚未满足挖矿难度要求)。
#![allow(unused)]
fn main() {
pub fn new(
index: u32,
transactions: Vec<Transaction>,
previous_hash: String,
) -> Block
}
参数:
index— 新区块的高度。transactions— 要打包进区块的交易列表(第一笔应为 Coinbase 交易)。previous_hash— 父区块的哈希字符串。
返回值: 初始化好的 Block 实例(尚未完成挖矿)。
内部流程:
- 获取当前 Unix 时间戳。
- 从
transactions的id列表构建MerkleTree,计算merkle_root。 - 以
nonce = 0构建区块并调用calculate_hash()得到初始哈希。
#![allow(unused)]
fn main() {
use simplebtc::block::Block;
use simplebtc::transaction::Transaction;
let coinbase = Transaction::new_coinbase("miner_address", 3125000); // 3.125 BTC(satoshi)
let block = Block::new(1, vec![coinbase], "abc123...".to_string());
println!("区块 #{}: {}", block.index, block.hash);
}
Block::calculate_hash
计算区块的 SHA256 哈希值。哈希输入包含 index、timestamp、merkle_root、previous_hash 和 nonce。
#![allow(unused)]
fn main() {
pub fn calculate_hash(&self) -> String
}
返回值: 64 字符的小写十六进制 SHA256 哈希字符串。
哈希输入格式:
"{index}{timestamp}{merkle_root}{previous_hash}{nonce}"
使用 merkle_root 而非完整交易数据,使区块头保持轻量(约 80 字节),同时保证所有交易内容的完整性。
#![allow(unused)]
fn main() {
let mut block = Block::new(1, transactions, prev_hash);
// 修改 nonce 后重新计算哈希(挖矿核心逻辑)
block.nonce += 1;
block.hash = block.calculate_hash();
println!("新哈希: {}", block.hash);
}
Block::validate_transactions
验证区块中所有交易的签名有效性。依次调用每笔交易的 verify() 方法。
#![allow(unused)]
fn main() {
pub fn validate_transactions(&self) -> bool
}
返回值:
true— 所有交易签名均有效。false— 存在至少一笔无效交易。
#![allow(unused)]
fn main() {
let block = Block::new(1, transactions, prev_hash);
if block.validate_transactions() {
println!("所有交易有效,可以上链");
} else {
println!("区块包含无效交易,拒绝");
}
}
注意: 此方法仅验证签名,不验证 UTXO 余额。余额验证由
Blockchain层负责。
Block::verify_transaction_inclusion
使用 Merkle 证明验证某笔交易是否确实包含在该区块中。这是 SPV(简化支付验证)的核心功能,无需遍历所有交易,时间复杂度为 O(log n)。
#![allow(unused)]
fn main() {
pub fn verify_transaction_inclusion(
&self,
tx_id: &str,
index: usize,
) -> bool
}
参数:
tx_id— 要验证的交易 ID(哈希字符串)。index— 该交易在区块交易列表中的位置索引(从 0 开始)。
返回值:
true— 交易确实包含在该区块中,且 Merkle 证明有效。false— 交易不在该区块中,或证明无效。
内部流程:
- 重建区块的
MerkleTree。 - 调用
get_proof(tx_id)生成 Merkle 证明。 - 调用
MerkleTree::verify_proof()验证证明与merkle_root是否匹配。
#![allow(unused)]
fn main() {
let tx_id = "abc123def456...";
let tx_index = 2; // 该交易在区块中的位置
if block.verify_transaction_inclusion(tx_id, tx_index) {
println!("交易已确认包含在第 {} 个区块中", block.index);
} else {
println!("交易不在此区块中");
}
}
Block::mine_block
工作量证明(Proof of Work)挖矿。不断递增 nonce 并重新计算哈希,直到哈希前缀满足难度要求(即以 difficulty 个 '0' 开头)。
#![allow(unused)]
fn main() {
pub fn mine_block(&mut self, difficulty: usize)
}
参数:
difficulty— 挖矿难度,即哈希前缀需要的'0'个数。
副作用: 修改 self.nonce 和 self.hash,直到找到有效哈希。
#![allow(unused)]
fn main() {
let mut block = Block::new(1, transactions, prev_hash);
println!("开始挖矿,难度: 4");
block.mine_block(4); // 哈希必须以 "0000" 开头
println!("挖矿完成: {}", block.hash);
println!("使用 nonce: {}", block.nonce);
// 输出示例: 0000a3f7c2...
}
关于难度: 比特币主网当前难度约等效于哈希前缀约 20 个
'0'(需要约 2^80 次哈希计算)。本项目使用较小难度值(如 2-4)以便演示。
完整使用示例
use simplebtc::block::Block;
use simplebtc::transaction::Transaction;
use simplebtc::wallet::Wallet;
fn main() {
// 1. 创建矿工钱包
let miner = Wallet::new();
// 2. 创建 Coinbase 交易(矿工奖励)
let coinbase = Transaction::new_coinbase(&miner.address, 3_125_000);
// 3. 创建普通转账交易
let alice = Wallet::new();
let bob = Wallet::new();
let transfer = Transaction::new(&alice, &bob.address, 50_000, 500);
// 4. 打包区块(假设父块哈希已知)
let prev_hash = "0000abc123...".to_string();
let mut block = Block::new(1, vec![coinbase, transfer], prev_hash);
// 5. 挖矿(工作量证明)
block.mine_block(3); // 难度 3:哈希以 "000" 开头
// 6. 验证区块
assert!(block.validate_transactions(), "区块交易无效");
println!("区块哈希: {}", block.hash);
println!("Merkle 根: {}", block.merkle_root);
println!("Nonce: {}", block.nonce);
// 7. SPV 验证:某交易是否在此区块中
let included = block.verify_transaction_inclusion(&block.transactions[0].id.clone(), 0);
println!("Coinbase 交易已包含: {}", included);
}
不可篡改性原理
攻击者尝试修改区块 1 的某笔交易:
修改交易
↓
交易哈希改变
↓
Merkle Root 改变
↓
区块 1 的 Hash 改变
↓
区块 2 的 previous_hash 不匹配
↓
区块 2、3、4... 的 Hash 全部失效
↓
攻击者需要重新挖所有后续区块(计算上不可行)
这就是区块链“不可篡改性“的数学保证。
相关模块
MerkleTree— Merkle 树实现,用于计算merkle_root和生成 SPV 证明。Transaction— 交易结构体,Block 的核心数据。Blockchain— 管理区块链,调用mine_block()并维护链状态。
Blockchain API
区块链模块是SimpleBTC的核心,管理整个区块链的状态和操作。
数据结构
Blockchain
#![allow(unused)]
fn main() {
pub struct Blockchain {
pub chain: Vec<Block>, // 区块链(区块列表)
pub difficulty: usize, // 挖矿难度
pub pending_transactions: Vec<Transaction>, // 待处理交易池
pub utxo_set: UTXOSet, // UTXO集合
pub mining_reward: u64, // 挖矿奖励
pub indexer: TransactionIndexer, // 交易索引器
}
}
方法
初始化
new
#![allow(unused)]
fn main() {
pub fn new() -> Blockchain
}
创建新的区块链,自动创建创世区块。
初始参数:
difficulty: 3- 挖矿难度(3个前导0)mining_reward: 50- 区块奖励(50 satoshi)- 创世区块包含100 satoshi发送给
genesis_address
返回值: 新的区块链实例
示例:
#![allow(unused)]
fn main() {
let mut blockchain = Blockchain::new();
println!("区块链已初始化,当前高度: {}", blockchain.chain.len());
}
交易管理
create_transaction
#![allow(unused)]
fn main() {
pub fn create_transaction(
&self,
from_wallet: &Wallet,
to_address: String,
amount: u64,
fee: u64,
) -> Result<Transaction, String>
}
创建新交易。自动选择UTXO、构建输入输出、添加签名。
参数:
from_wallet- 发送者钱包(需要私钥签名)to_address- 接收者地址amount- 转账金额(satoshi)fee- 手续费(satoshi)
返回值:
Ok(Transaction)- 交易创建成功Err(String)- 错误信息
错误情况:
"余额不足(包括手续费)"- 没有足够的UTXO"UTXO不存在"- 引用的UTXO已被花费"引用的交易不存在"- 数据不一致
工作流程:
- 查找发送者的可用UTXO
- 选择足够的UTXO(贪心算法)
- 创建交易输入(包含签名)
- 创建交易输出(接收者 + 找零)
- 计算交易ID
示例:
#![allow(unused)]
fn main() {
// 基本用法
let tx = blockchain.create_transaction(
&alice,
bob.address.clone(),
5000, // 转5000 satoshi
10, // 手续费10 satoshi
)?;
blockchain.add_transaction(tx)?;
// 检查余额
let balance = blockchain.get_balance(&alice.address);
if balance < amount + fee {
return Err("余额不足".to_string());
}
// 批量创建
for i in 1..=10 {
let tx = blockchain.create_transaction(
&alice,
recipients[i].clone(),
1000,
i as u64, // 不同的手续费
)?;
blockchain.add_transaction(tx)?;
}
}
add_transaction
#![allow(unused)]
fn main() {
pub fn add_transaction(&mut self, transaction: Transaction) -> Result<(), String>
}
将交易添加到待处理池,等待被打包。
参数:
transaction- 要添加的交易
验证项:
- ✅ 交易格式正确(verify())
- ✅ 输入引用的UTXO存在
- ✅ 签名有效
- ✅ 输入总额 ≥ 输出总额
返回值:
Ok(())- 添加成功Err(String)- 验证失败原因
示例:
#![allow(unused)]
fn main() {
let tx = blockchain.create_transaction(&alice, bob.address, 1000, 5)?;
match blockchain.add_transaction(tx) {
Ok(_) => println!("✓ 交易已添加到待处理池"),
Err(e) => eprintln!("✗ 交易无效: {}", e),
}
// 查看待处理交易数量
println!("待处理: {} 笔", blockchain.pending_transactions.len());
}
挖矿
mine_pending_transactions
#![allow(unused)]
fn main() {
pub fn mine_pending_transactions(
&mut self,
miner_address: String
) -> Result<(), String>
}
挖矿:将待处理交易打包成新区块。
参数:
miner_address- 矿工地址(接收奖励)
挖矿流程:
- 检查是否有待处理交易
- 按手续费率从高到低排序
- 计算总手续费
- 创建Coinbase交易(奖励 + 手续费)
- 构建Merkle树
- 工作量证明(调整nonce找到有效哈希)
- 验证区块中所有交易
- 更新UTXO集合(原子操作)
- 将区块添加到链上
- 清空待处理池
返回值:
Ok(())- 挖矿成功Err(String)- 错误信息
错误情况:
"没有待处理的交易"- 待处理池为空"区块包含无效交易"- 交易验证失败"UTXO更新失败"- 数据不一致
性能:
- 难度3: 约0.001-0.1秒
- 难度4: 约0.01-1秒
- 难度5: 约0.1-10秒
- 难度6+: 数秒到数分钟
示例:
#![allow(unused)]
fn main() {
// 基本挖矿
blockchain.mine_pending_transactions(miner.address.clone())?;
// 挖矿循环(类似真实矿工)
loop {
if blockchain.pending_transactions.is_empty() {
println!("等待新交易...");
std::thread::sleep(Duration::from_secs(1));
continue;
}
println!("开始挖矿...");
let start = Instant::now();
blockchain.mine_pending_transactions(miner.address.clone())?;
let duration = start.elapsed();
println!("✓ 挖矿成功! 耗时: {:?}", duration);
// 查看奖励
let reward = blockchain.get_balance(&miner.address);
println!("矿工余额: {} satoshi", reward);
}
}
查询操作
get_balance
#![allow(unused)]
fn main() {
pub fn get_balance(&self, address: &str) -> u64
}
查询地址余额。
参数:
address- 要查询的地址
返回值: 余额(satoshi)
计算方式: 遍历UTXO集合,累加该地址的所有UTXO
示例:
#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&alice.address);
println!("余额: {} satoshi", balance);
println!("余额: {:.8} BTC", balance as f64 / 100_000_000.0);
// 批量查询
let addresses = vec![alice.address, bob.address, charlie.address];
for addr in addresses {
let bal = blockchain.get_balance(&addr);
println!("{}: {}", &addr[..10], bal);
}
}
is_valid
#![allow(unused)]
fn main() {
pub fn is_valid(&self) -> bool
}
验证整个区块链的完整性。
验证项:
- ✅ 每个区块的哈希正确
- ✅ 前向引用正确(previous_hash链接)
- ✅ 工作量证明有效(哈希满足难度)
- ✅ 所有交易有效
返回值:
true- 区块链完整有效false- 发现篡改或错误
用途:
- 定期完整性检查
- 同步节点后验证
- 检测篡改攻击
示例:
#![allow(unused)]
fn main() {
// 定期验证
if !blockchain.is_valid() {
panic!("❌ 区块链已被篡改!");
}
// 详细验证日志
for (i, block) in blockchain.chain.iter().enumerate() {
if block.hash != block.calculate_hash() {
eprintln!("区块 {} 哈希无效", i);
}
if !block.validate_transactions() {
eprintln!("区块 {} 包含无效交易", i);
}
}
if blockchain.is_valid() {
println!("✅ 区块链验证通过");
}
}
print_chain
#![allow(unused)]
fn main() {
pub fn print_chain(&self)
}
打印区块链详细信息(调试用)。
输出内容:
- 区块索引、时间戳、哈希
- 前一个区块哈希
- Nonce值
- 交易列表(ID、类型、手续费、输入输出)
示例:
#![allow(unused)]
fn main() {
blockchain.print_chain();
// 输出示例:
// ========== 区块链信息 ==========
//
// --- 区块 #0 ---
// 时间戳: 1703001234
// 哈希: 0003ab4f9c2d...
// 前一个哈希: 0
// Nonce: 1247
// 交易数量: 1
// 交易 #0: abc123...
// 类型: Coinbase(挖矿奖励)
// 输入数: 1
// 输出数: 1
// 输出 0: 100 -> genesis_address
// ...
}
使用示例
完整的区块链演示
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};
fn main() -> Result<(), String> {
println!("=== SimpleBTC区块链演示 ===\n");
// 1. 初始化
let mut blockchain = Blockchain::new();
println!("✓ 区块链已创建(创世区块)\n");
// 2. 创建参与者
let alice = Wallet::new();
let bob = Wallet::new();
let miner = Wallet::new();
println!("✓ 创建了3个钱包");
println!(" Alice: {}", &alice.address[..16]);
println!(" Bob: {}", &bob.address[..16]);
println!(" Miner: {}\n", &miner.address[..16]);
// 3. Alice获得初始资金
let init_tx = blockchain.create_transaction(
&Wallet::from_address("genesis_address".to_string()),
alice.address.clone(),
10000,
0,
)?;
blockchain.add_transaction(init_tx)?;
blockchain.mine_pending_transactions(miner.address.clone())?;
println!("✓ 区块 #1 已挖出");
println!(" Alice余额: {}\n", blockchain.get_balance(&alice.address));
// 4. 多笔交易
println!("创建5笔交易(不同手续费)...");
for i in 1..=5 {
let tx = blockchain.create_transaction(
&alice,
bob.address.clone(),
100 * i,
i as u64, // 手续费递增
)?;
blockchain.add_transaction(tx)?;
println!(" 交易 #{}: {} sat, 费率: {} sat/byte",
i, 100 * i, i);
}
// 5. 挖矿(交易按费率排序)
println!("\n开始挖矿...");
blockchain.mine_pending_transactions(miner.address.clone())?;
println!("✓ 区块 #2 已挖出\n");
// 6. 最终余额
println!("=== 最终余额 ===");
println!("Alice: {} satoshi", blockchain.get_balance(&alice.address));
println!("Bob: {} satoshi", blockchain.get_balance(&bob.address));
println!("Miner: {} satoshi", blockchain.get_balance(&miner.address));
// 7. 验证区块链
println!("\n=== 验证区块链 ===");
if blockchain.is_valid() {
println!("✅ 区块链完整性验证通过");
} else {
println!("❌ 区块链验证失败");
}
// 8. 打印详细信息
println!("\n=== 区块链详情 ===");
blockchain.print_chain();
Ok(())
}
手续费优先级演示
#![allow(unused)]
fn main() {
fn fee_priority_demo() -> Result<(), String> {
let mut blockchain = Blockchain::new();
let alice = Wallet::new();
let recipients: Vec<_> = (0..3).map(|_| Wallet::new()).collect();
// 初始化Alice余额
setup_balance(&mut blockchain, &alice, 10000)?;
// 创建不同费率的交易
let txs = vec![
("慢速", 1000, 1), // 1 sat/byte
("快速", 1000, 50), // 50 sat/byte
("中速", 1000, 10), // 10 sat/byte
];
println!("添加交易:");
for (i, (name, amount, fee)) in txs.iter().enumerate() {
let tx = blockchain.create_transaction(
&alice,
recipients[i].address.clone(),
*amount,
*fee,
)?;
println!(" {}: {} sat, 费率 {} sat/byte", name, amount, fee);
blockchain.add_transaction(tx)?;
}
println!("\n挖矿(自动按费率排序)...");
blockchain.mine_pending_transactions(recipients[0].address.clone())?;
// 查看最新区块的交易顺序
let latest_block = blockchain.chain.last().unwrap();
println!("\n区块中的交易顺序:");
for (i, tx) in latest_block.transactions.iter().skip(1).enumerate() {
println!(" #{}: 费率 {:.2} sat/byte",
i + 1, tx.fee_rate());
}
Ok(())
}
}
监控区块链状态
#![allow(unused)]
fn main() {
fn blockchain_monitor(blockchain: &Blockchain) {
println!("=== 区块链状态 ===");
println!("区块高度: {}", blockchain.chain.len());
println!("难度: {} ({}个前导0)",
blockchain.difficulty, blockchain.difficulty);
println!("挖矿奖励: {} satoshi", blockchain.mining_reward);
println!("待处理交易: {} 笔", blockchain.pending_transactions.len());
// UTXO统计
let total_utxos = blockchain.utxo_set.utxos
.values()
.map(|v| v.len())
.sum::<usize>();
println!("UTXO总数: {}", total_utxos);
// 最新区块信息
if let Some(latest) = blockchain.chain.last() {
println!("\n最新区块:");
println!(" 哈希: {}", &latest.hash[..16]);
println!(" 交易数: {}", latest.transactions.len());
println!(" Merkle根: {}", &latest.merkle_root[..16]);
}
}
}
配置建议
挖矿难度
#![allow(unused)]
fn main() {
// 演示环境
blockchain.difficulty = 3; // 快速(毫秒级)
// 测试环境
blockchain.difficulty = 4; // 适中(秒级)
// 生产环境
blockchain.difficulty = 6; // 安全(分钟级)
}
区块奖励
#![allow(unused)]
fn main() {
// 比特币风格(逐步减半)
let halving_interval = 210000;
let halvings = blockchain.chain.len() / halving_interval;
blockchain.mining_reward = 50 >> halvings; // 50, 25, 12.5, ...
}
性能优化
1. UTXO索引
使用indexer加速查询:
#![allow(unused)]
fn main() {
// 查找地址的所有交易
let txs = blockchain.indexer.get_transactions_by_address(&address);
// 查找特定交易
let tx = blockchain.indexer.get_transaction(&txid);
}
2. 批量操作
#![allow(unused)]
fn main() {
// 批量添加交易
for tx in transactions {
blockchain.add_transaction(tx)?;
}
// 一次性挖矿
blockchain.mine_pending_transactions(miner.address)?;
}
3. 并行验证
#![allow(unused)]
fn main() {
use rayon::prelude::*;
// 并行验证所有交易(需要添加rayon依赖)
let all_valid = blockchain.pending_transactions
.par_iter()
.all(|tx| tx.verify());
}
参考
Wallet API
钱包模块负责管理密钥对、地址和签名。
数据结构
Wallet
#![allow(unused)]
fn main() {
pub struct Wallet {
pub address: String, // 钱包地址(公钥哈希)
pub private_key: String, // 私钥
pub public_key: String, // 公钥
}
}
方法
创建钱包
new
#![allow(unused)]
fn main() {
pub fn new() -> Self
}
创建新钱包,自动生成密钥对和地址。
密钥生成流程:
- 生成随机私钥(64字符十六进制)
- 从私钥派生公钥(SHA256)
- 从公钥哈希得到地址(取前40字符)
返回值: 新的钱包实例
安全提示:
- ⚠️ 私钥必须保密
- ⚠️ 私钥丢失无法恢复
- ⚠️ 建议备份到安全位置
示例:
#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;
// 创建新钱包
let wallet = Wallet::new();
println!("地址: {}", wallet.address);
println!("公钥: {}", wallet.public_key);
// 私钥不要打印或分享!
// 多个钱包
let alice = Wallet::new();
let bob = Wallet::new();
let charlie = Wallet::new();
}
from_address
#![allow(unused)]
fn main() {
pub fn from_address(address: String) -> Self
}
从已知地址创建钱包(仅用于演示)。
注意:
- 这会生成新的随机密钥对
- 密钥与地址不对应
- 仅用于测试和演示
参数:
address- 指定的地址字符串
返回值: 钱包实例(密钥是新生成的)
示例:
#![allow(unused)]
fn main() {
// 用于演示创世地址
let genesis = Wallet::from_address("genesis_address".to_string());
// 实际应用中应该从私钥恢复
// let wallet = Wallet::from_private_key(private_key);
}
签名操作
sign
#![allow(unused)]
fn main() {
pub fn sign(&self, data: &str) -> String
}
使用私钥签名数据。
签名过程(简化版):
signature = SHA256(private_key + data)
实际比特币使用ECDSA:
1. 对数据进行双重SHA256
2. 使用私钥和secp256k1曲线生成签名
3. 签名包含r和s两部分
参数:
data- 要签名的数据(通常是交易数据)
返回值: 签名字符串(64字符十六进制)
用途:
- 证明拥有私钥
- 授权交易
- 防止交易被篡改
示例:
#![allow(unused)]
fn main() {
let wallet = Wallet::new();
// 签名交易数据
let tx_data = "send 100 BTC to Bob";
let signature = wallet.sign(tx_data);
println!("签名: {}", signature);
// 在交易中使用
let input = TxInput::new(
prev_txid,
vout,
wallet.sign(&tx_data), // 签名
wallet.public_key.clone(),
);
}
verify_signature (静态方法)
#![allow(unused)]
fn main() {
pub fn verify_signature(
public_key: &str,
data: &str,
signature: &str
) -> bool
}
验证签名是否有效(简化版)。
验证过程(简化版):
- 检查公钥和签名非空
实际比特币使用ECDSA验证:
- 从签名恢复公钥
- 验证公钥匹配
- 验证签名数学正确性
参数:
public_key- 签名者的公钥data- 原始数据signature- 签名
返回值:
true- 签名有效false- 签名无效
示例:
#![allow(unused)]
fn main() {
let wallet = Wallet::new();
let data = "transaction data";
let signature = wallet.sign(data);
// 验证签名
if Wallet::verify_signature(&wallet.public_key, data, &signature) {
println!("✓ 签名有效");
} else {
println!("✗ 签名无效");
}
// 在交易验证中使用
for input in transaction.inputs {
if !Wallet::verify_signature(&input.pub_key, &tx_data, &input.signature) {
return Err("签名验证失败");
}
}
}
地址格式
SimpleBTC地址
格式: 40字符十六进制字符串
示例: a3f2d8c9e4b7f1a89c2d5e8f3b6a1c4e7d9b2a5c
真实比特币地址
P2PKH(以1开头)
1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
生成过程:
公钥 → SHA256 → RIPEMD160 → 添加版本 → 校验和 → Base58编码
P2SH(以3开头)
3J98t1WpEZ73CNmYviecrnyiWrnqRhWNLy
用途: 多签、脚本地址
Bech32(以bc1开头)
bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
优势: SegWit地址,手续费更低
密钥管理
私钥安全
最佳实践:
#![allow(unused)]
fn main() {
// ✅ 好的做法
let wallet = Wallet::new();
// 加密存储私钥
let encrypted = encrypt_private_key(&wallet.private_key, password);
save_to_secure_storage(&encrypted);
// 使用后立即清除内存
drop(wallet);
// 备份到多个位置
backup_to_hardware_wallet(&wallet.private_key);
backup_to_paper(&wallet.private_key);
backup_to_encrypted_usb(&wallet.private_key);
}
#![allow(unused)]
fn main() {
// ❌ 不好的做法
println!("私钥: {}", wallet.private_key); // 永不打印
save_to_file(&wallet.private_key); // 明文存储
send_via_email(&wallet.private_key); // 网络传输
}
密钥恢复
#![allow(unused)]
fn main() {
// 从私钥恢复钱包(需要实现)
fn recover_wallet(private_key: &str) -> Wallet {
// 1. 验证私钥格式
// 2. 从私钥派生公钥
// 3. 从公钥生成地址
// 4. 返回钱包实例
}
// 使用助记词(BIP39标准,需要实现)
fn from_mnemonic(words: &str) -> Wallet {
// 助记词 → 种子 → 主私钥 → 派生密钥
}
}
使用场景
场景1: 基本转账
#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};
fn basic_transfer() -> Result<(), String> {
let mut blockchain = Blockchain::new();
// 创建参与者
let alice = Wallet::new();
let bob = Wallet::new();
// Alice获得初始资金
setup_balance(&mut blockchain, &alice, 10000)?;
// Alice向Bob转账
let tx = blockchain.create_transaction(
&alice, // from_wallet
bob.address.clone(),
5000, // amount
10, // fee
)?;
blockchain.add_transaction(tx)?;
blockchain.mine_pending_transactions(alice.address.clone())?;
// 查看余额
println!("Alice: {}", blockchain.get_balance(&alice.address));
println!("Bob: {}", blockchain.get_balance(&bob.address));
Ok(())
}
}
场景2: 批量创建钱包
#![allow(unused)]
fn main() {
fn create_wallet_pool(count: usize) -> Vec<Wallet> {
let mut wallets = Vec::new();
for i in 0..count {
let wallet = Wallet::new();
println!("钱包 #{}: {}", i, &wallet.address[..16]);
wallets.push(wallet);
}
wallets
}
// 使用
let users = create_wallet_pool(100); // 创建100个钱包
}
场景3: 钱包导入导出
#![allow(unused)]
fn main() {
use serde_json;
// 导出钱包(加密)
fn export_wallet(wallet: &Wallet, password: &str) -> Result<String, String> {
let wallet_json = serde_json::to_string(wallet)?;
let encrypted = encrypt(&wallet_json, password);
Ok(encrypted)
}
// 导入钱包
fn import_wallet(encrypted_data: &str, password: &str) -> Result<Wallet, String> {
let decrypted = decrypt(encrypted_data, password)?;
let wallet: Wallet = serde_json::from_str(&decrypted)?;
Ok(wallet)
}
// 使用
let wallet = Wallet::new();
let backup = export_wallet(&wallet, "strong_password")?;
save_to_file("wallet_backup.enc", &backup)?;
// 恢复
let backup_data = read_from_file("wallet_backup.enc")?;
let recovered = import_wallet(&backup_data, "strong_password")?;
}
场景4: 多签钱包集成
#![allow(unused)]
fn main() {
use bitcoin_simulation::multisig::MultiSigAddress;
fn create_multisig_wallet() -> Result<MultiSigAddress, String> {
// 创建参与者钱包
let alice = Wallet::new();
let bob = Wallet::new();
let charlie = Wallet::new();
// 收集公钥
let public_keys = vec![
alice.public_key,
bob.public_key,
charlie.public_key,
];
// 创建2-of-3多签地址
let multisig = MultiSigAddress::new(2, public_keys)?;
println!("多签地址: {}", multisig.address);
Ok(multisig)
}
}
与真实比特币的差异
| 特性 | SimpleBTC | 真实比特币 |
|---|---|---|
| 私钥生成 | 随机字符串 | 256位随机数 |
| 公钥推导 | SHA256 | secp256k1椭圆曲线 |
| 地址格式 | 40字符十六进制 | Base58/Bech32编码 |
| 签名算法 | SHA256 | ECDSA |
| 签名验证 | 简化检查 | 完整数学验证 |
真实比特币流程:
私钥(256 bits)
↓ secp256k1
公钥(33/65 bytes)
↓ SHA256 + RIPEMD160
公钥哈希(20 bytes)
↓ 版本 + 校验和 + Base58
地址(25-34 chars)
安全建议
1. 私钥保护
#![allow(unused)]
fn main() {
// 使用操作系统密钥环
use keyring::Entry;
fn store_private_key(address: &str, private_key: &str) -> Result<(), String> {
let entry = Entry::new("SimpleBTC", address)?;
entry.set_password(private_key)?;
Ok(())
}
fn retrieve_private_key(address: &str) -> Result<String, String> {
let entry = Entry::new("SimpleBTC", address)?;
let private_key = entry.get_password()?;
Ok(private_key)
}
}
2. 多重备份
- ✅ 纸钱包(防火防水)
- ✅ 硬件钱包(Ledger, Trezor)
- ✅ 加密U盘(异地存储)
- ✅ 分片存储(Shamir秘密共享)
3. 定期审计
#![allow(unused)]
fn main() {
fn audit_wallets(wallets: &[Wallet]) {
for (i, wallet) in wallets.iter().enumerate() {
println!("钱包 #{}", i);
println!(" 地址: {}", wallet.address);
println!(" 公钥存在: {}", !wallet.public_key.is_empty());
println!(" 私钥存在: {}", !wallet.private_key.is_empty());
// 测试签名
let test_sig = wallet.sign("test");
assert!(Wallet::verify_signature(
&wallet.public_key,
"test",
&test_sig
));
}
}
}
常见问题
Q: 如何恢复丢失的钱包?
A: 只能从备份的私钥恢复。如果私钥丢失,比特币永久丢失。
Q: 可以从地址推导私钥吗?
A: 不可以。地址是单向哈希,计算上不可逆。
Q: 一个私钥可以生成多个地址吗?
A: 分层确定性钱包(HD Wallet, BIP32)可以从一个种子派生多个密钥对。
Q: 如何知道钱包是否被盗用?
A: 监控区块链上的交易记录,如果出现未授权的交易,说明私钥泄露。
参考
- Transaction API - 使用钱包创建交易
- MultiSig API - 多签钱包
- 快速入门 - 钱包使用教程
- BIP32 - HD Wallets
- BIP39 - 助记词
UTXO API
UTXO模块管理所有未花费的交易输出,是比特币账户模型的核心。
数据结构
UTXOSet
#![allow(unused)]
fn main() {
pub struct UTXOSet {
// key: txid (交易ID)
// value: Vec<(vout_index, TxOutput)>
utxos: HashMap<String, Vec<(usize, TxOutput)>>,
}
}
内部结构:
HashMap {
"tx1": [(0, Output{value: 100, addr: "alice"}),
(1, Output{value: 50, addr: "bob"})],
"tx2": [(0, Output{value: 200, addr: "charlie"})],
}
核心概念
UTXO模型 vs 账户模型
账户模型(以太坊)
账户余额:
Alice: 100 BTC
Bob: 50 BTC
转账: Alice → Bob (30 BTC)
Alice: 70 BTC (-30)
Bob: 80 BTC (+30)
UTXO模型(比特币)
UTXO集合:
tx1:0 → 100 BTC (Alice)
tx1:1 → 50 BTC (Bob)
转账: Alice → Bob (30 BTC)
消费: tx1:0 (100 BTC)
创建:
tx2:0 → 30 BTC (Bob)
tx2:1 → 70 BTC (Alice, 找零)
新UTXO集合:
tx1:1 → 50 BTC (Bob)
tx2:0 → 30 BTC (Bob)
tx2:1 → 70 BTC (Alice)
UTXO的优势
-
更好的隐私
- 每次可用新地址
- 难以追踪资金流向
-
并行处理
- 不同UTXO可并发验证
- 无账户锁定问题
-
简化验证
- 只需检查UTXO存在
- 无需账户历史
方法
初始化
new
#![allow(unused)]
fn main() {
pub fn new() -> Self
}
创建空的UTXO集合。
示例:
#![allow(unused)]
fn main() {
use bitcoin_simulation::utxo::UTXOSet;
let mut utxo_set = UTXOSet::new();
}
UTXO管理
add_transaction
#![allow(unused)]
fn main() {
pub fn add_transaction(&mut self, tx: &Transaction)
}
将交易的所有输出添加到UTXO集合。
过程:
- 遍历交易的所有输出
- 将每个输出标记为未花费
- 添加到UTXO集合
参数:
tx- 要添加的交易
注意: 只添加输出,不处理输入
示例:
#![allow(unused)]
fn main() {
let tx = Transaction::new(...);
utxo_set.add_transaction(&tx);
// 现在tx的所有输出都可以被花费
}
remove_utxo
#![allow(unused)]
fn main() {
pub fn remove_utxo(&mut self, txid: &str, vout: usize)
}
移除已花费的UTXO。
双花防护:
- UTXO只能花费一次
- 花费后立即从集合移除
- 二次引用会失败
参数:
txid- 交易IDvout- 输出索引
示例:
#![allow(unused)]
fn main() {
// 花费UTXO
utxo_set.remove_utxo("tx1", 0);
// 再次尝试花费(失败)
// UTXO不存在
}
process_transaction
#![allow(unused)]
fn main() {
pub fn process_transaction(&mut self, tx: &Transaction) -> bool
}
完整处理交易(移除输入,添加输出)。
ACID特性:
Atomicity(原子性):
#![allow(unused)]
fn main() {
// 要么完全成功,要么完全失败
if !tx.verify() {
return false; // 不进行任何修改
}
// 全部处理
}
Consistency(一致性):
#![allow(unused)]
fn main() {
// 处理前后,输入总额 = 输出总额 + 手续费
assert_eq!(input_sum, output_sum + fee);
}
步骤:
- 验证交易有效性
- 移除输入引用的UTXO
- 添加新创建的输出
参数:
tx- 要处理的交易
返回值:
true- 处理成功false- 交易无效
示例:
#![allow(unused)]
fn main() {
let tx = blockchain.create_transaction(&alice, bob.address, 1000, 10)?;
if utxo_set.process_transaction(&tx) {
println!("✓ UTXO更新成功");
} else {
println!("✗ 交易无效");
}
}
查询操作
find_utxos
#![allow(unused)]
fn main() {
pub fn find_utxos(&self, address: &str) -> Vec<(String, usize, u64)>
}
查找地址的所有UTXO。
返回格式: Vec<(txid, vout, value)>
参数:
address- 要查询的地址
返回值: UTXO列表
示例:
#![allow(unused)]
fn main() {
let utxos = utxo_set.find_utxos(&alice.address);
println!("Alice的UTXO:");
for (txid, vout, value) in utxos {
println!(" {}:{} → {} sat", &txid[..8], vout, value);
}
// 输出:
// Alice的UTXO:
// tx1:0 → 5000 sat
// tx2:1 → 3000 sat
// tx5:0 → 2000 sat
}
find_spendable_outputs
#![allow(unused)]
fn main() {
pub fn find_spendable_outputs(
&self,
address: &str,
amount: u64
) -> Option<(u64, Vec<(String, usize)>)>
}
查找可用于支付的UTXO组合。
UTXO选择策略:
-
贪心算法(当前实现):
#![allow(unused)] fn main() { accumulated = 0 for utxo in utxos: accumulated += utxo.value if accumulated >= amount: return utxos } -
最优匹配(可优化):
- 选择总额最接近目标的组合
- 减少找零,节省手续费
-
最小UTXO优先:
- 优先使用小额UTXO
- 避免UTXO碎片化
参数:
address- 发送者地址amount- 需要的金额(包括手续费)
返回值:
Some((accumulated, utxo_list))- 找到足够UTXOaccumulated: 累积金额utxo_list: 选中的UTXO列表
None- 余额不足
示例:
#![allow(unused)]
fn main() {
// 需要1000 sat(含手续费)
let result = utxo_set.find_spendable_outputs(&alice.address, 1000);
match result {
Some((accumulated, utxos)) => {
println!("✓ 找到足够的UTXO");
println!(" 累积金额: {} sat", accumulated);
println!(" 使用UTXO数: {}", utxos.len());
println!(" 找零: {} sat", accumulated - 1000);
}
None => {
println!("✗ 余额不足");
}
}
}
get_balance
#![allow(unused)]
fn main() {
pub fn get_balance(&self, address: &str) -> u64
}
计算地址的总余额。
计算方式:
#![allow(unused)]
fn main() {
balance = sum(all_utxos.value)
}
参数:
address- 要查询的地址
返回值: 余额(satoshi)
示例:
#![allow(unused)]
fn main() {
let balance = utxo_set.get_balance(&alice.address);
println!("余额: {} satoshi", balance);
println!("余额: {:.8} BTC", balance as f64 / 100_000_000.0);
// 批量查询
let addresses = vec![alice.address, bob.address, charlie.address];
for addr in addresses {
let bal = utxo_set.get_balance(&addr);
println!("{}: {} sat", &addr[..10], bal);
}
}
使用场景
场景1: 创建交易时选择UTXO
#![allow(unused)]
fn main() {
fn create_payment(
utxo_set: &UTXOSet,
from: &Wallet,
to: &str,
amount: u64,
fee: u64
) -> Result<Transaction, String> {
let total_needed = amount + fee;
// 1. 查找可用UTXO
let result = utxo_set.find_spendable_outputs(&from.address, total_needed);
let (accumulated, utxo_refs) = result.ok_or("余额不足")?;
// 2. 构建输入
let mut inputs = Vec::new();
for (txid, vout) in utxo_refs {
let signature = from.sign(&format!("{}{}", txid, vout));
inputs.push(TxInput::new(txid, vout, signature, from.public_key.clone()));
}
// 3. 构建输出
let mut outputs = vec![
TxOutput::new(amount, to.to_string()), // 给接收者
];
// 4. 找零
if accumulated > total_needed {
outputs.push(TxOutput::new(
accumulated - total_needed,
from.address.clone()
));
}
// 5. 创建交易
Ok(Transaction::new(inputs, outputs, current_timestamp(), fee))
}
}
场景2: 查询余额详情
#![allow(unused)]
fn main() {
fn balance_breakdown(utxo_set: &UTXOSet, address: &str) {
let utxos = utxo_set.find_utxos(address);
let total = utxo_set.get_balance(address);
println!("=== 余额详情 ===");
println!("地址: {}", &address[..20]);
println!("总余额: {} sat ({:.8} BTC)", total, total as f64 / 1e8);
println!("UTXO数量: {}", utxos.len());
println!("\nUTXO列表:");
for (i, (txid, vout, value)) in utxos.iter().enumerate() {
println!(" #{}: {}:{} → {} sat",
i + 1, &txid[..8], vout, value);
}
// 统计
if !utxos.is_empty() {
let avg = total / utxos.len() as u64;
let max = utxos.iter().map(|(_, _, v)| v).max().unwrap();
let min = utxos.iter().map(|(_, _, v)| v).min().unwrap();
println!("\n统计:");
println!(" 平均: {} sat", avg);
println!(" 最大: {} sat", max);
println!(" 最小: {} sat", min);
}
}
}
场景3: UTXO碎片整理
#![allow(unused)]
fn main() {
fn consolidate_utxos(
blockchain: &mut Blockchain,
wallet: &Wallet
) -> Result<(), String> {
let utxos = blockchain.utxo_set.find_utxos(&wallet.address);
// 如果UTXO太多(>50个),整理成1个
if utxos.len() > 50 {
println!("开始整理UTXO...");
println!(" 当前UTXO数: {}", utxos.len());
// 创建自己给自己的交易,整理所有UTXO
let total = blockchain.get_balance(&wallet.address);
let fee = 100; // 固定手续费
let tx = blockchain.create_transaction(
wallet,
wallet.address.clone(),
total - fee,
fee,
)?;
blockchain.add_transaction(tx)?;
blockchain.mine_pending_transactions(wallet.address.clone())?;
let new_utxos = blockchain.utxo_set.find_utxos(&wallet.address);
println!("✓ 整理完成");
println!(" 新UTXO数: {}", new_utxos.len());
}
Ok(())
}
}
场景4: UTXO审计
#![allow(unused)]
fn main() {
fn audit_utxo_set(utxo_set: &UTXOSet, blockchain: &Blockchain) -> bool {
println!("=== UTXO审计 ===");
// 1. 统计UTXO总数
let total_utxos: usize = utxo_set.utxos.values()
.map(|v| v.len())
.sum();
println!("总UTXO数: {}", total_utxos);
// 2. 统计总价值
let mut total_value = 0u64;
for outputs in utxo_set.utxos.values() {
for (_, output) in outputs {
total_value += output.value;
}
}
println!("总价值: {} sat", total_value);
// 3. 验证每个UTXO
let mut valid = true;
for (txid, outputs) in &utxo_set.utxos {
// 验证交易存在于区块链
let tx_exists = blockchain.chain.iter()
.any(|block| block.transactions.iter()
.any(|tx| &tx.id == txid));
if !tx_exists {
println!("✗ 警告: UTXO引用不存在的交易 {}", txid);
valid = false;
}
}
if valid {
println!("✓ UTXO集合完整");
}
valid
}
}
性能优化
1. 索引优化
#![allow(unused)]
fn main() {
// 为地址创建索引
pub struct IndexedUTXOSet {
utxos: HashMap<String, Vec<(usize, TxOutput)>>,
// 新增:地址索引
address_index: HashMap<String, Vec<(String, usize)>>,
}
impl IndexedUTXOSet {
pub fn find_utxos(&self, address: &str) -> Vec<(String, usize, u64)> {
// O(1) 查找而不是 O(n)
if let Some(refs) = self.address_index.get(address) {
refs.iter()
.filter_map(|(txid, vout)| {
self.utxos.get(txid)
.and_then(|outputs| outputs.iter()
.find(|(idx, _)| idx == vout)
.map(|(_, output)| (txid.clone(), *vout, output.value))
)
})
.collect()
} else {
vec![]
}
}
}
}
2. 批量操作
#![allow(unused)]
fn main() {
// 批量处理交易
pub fn process_transactions(&mut self, txs: &[Transaction]) -> bool {
// 1. 验证所有交易
for tx in txs {
if !tx.verify() {
return false;
}
}
// 2. 批量更新UTXO
for tx in txs {
// 移除输入
if !tx.is_coinbase() {
for input in &tx.inputs {
self.remove_utxo(&input.txid, input.vout);
}
}
// 添加输出
self.add_transaction(tx);
}
true
}
}
3. 缓存余额
#![allow(unused)]
fn main() {
pub struct CachedUTXOSet {
utxos: HashMap<String, Vec<(usize, TxOutput)>>,
balance_cache: HashMap<String, u64>, // 余额缓存
}
impl CachedUTXOSet {
pub fn get_balance(&mut self, address: &str) -> u64 {
// 检查缓存
if let Some(balance) = self.balance_cache.get(address) {
return *balance;
}
// 计算并缓存
let balance = self.calculate_balance(address);
self.balance_cache.insert(address.to_string(), balance);
balance
}
fn invalidate_cache(&mut self, address: &str) {
self.balance_cache.remove(address);
}
}
}
与以太坊账户模型对比
| 特性 | UTXO模型(比特币) | 账户模型(以太坊) |
|---|---|---|
| 状态 | 无状态(只有UTXO集合) | 有状态(账户余额、nonce) |
| 余额 | 计算值(UTXO总和) | 存储值(直接存储) |
| 转账 | 消费UTXO,创建新UTXO | 账户余额增减 |
| 隐私 | 较好(可用新地址) | 较差(重复使用地址) |
| 并行 | 易于并行验证 | 需要顺序处理(nonce) |
| 复杂度 | 交易构建复杂 | 交易简单 |
| 智能合约 | 有限(Script) | 灵活(EVM) |
常见问题
Q: 为什么要用UTXO模型?
A:
- ✅ 更好的隐私(每次用新地址)
- ✅ 并行验证(不同UTXO独立)
- ✅ 简化的验证逻辑
- ✅ 防双花机制天然
Q: UTXO会越来越多吗?
A: 是的。解决方案:
- UTXO整理(将多个小UTXO合并)
- 提高交易费(限制垃圾UTXO)
- UTXO承诺(减少存储)
Q: 如何防止UTXO碎片化?
A:
#![allow(unused)]
fn main() {
// 定期整理
if utxos.len() > threshold {
consolidate_utxos();
}
// 优先使用小UTXO
utxos.sort_by_key(|u| u.value); // 小的优先
}
Q: UTXO丢失怎么办?
A: 只要有私钥,可以从区块链重建UTXO集合:
#![allow(unused)]
fn main() {
fn rebuild_utxo_set(blockchain: &Blockchain, address: &str) -> UTXOSet {
let mut utxo_set = UTXOSet::new();
for block in &blockchain.chain {
for tx in &block.transactions {
utxo_set.process_transaction(tx);
}
}
utxo_set
}
}
参考
- Transaction API - UTXO的创建和消费
- Blockchain API - UTXO集合管理
- 基本概念 - UTXO模型
- 比特币白皮书
高级模块
SimpleBTC 的高级模块在核心区块链功能之上构建了一套完整的比特币协议特性实现。这些模块相互协作,覆盖了从数据完整性验证到复杂多方签名、从轻量级支付验证到脚本语言执行的完整功能栈。
模块概览
| 模块 | 源文件 | 核心功能 |
|---|---|---|
| Merkle 树 | src/merkle.rs | 数据完整性验证、SPV 证明生成与验证 |
| 多重签名 | src/multisig.rs | M-of-N 多方签名地址与交易构建 |
| 高级交易 | src/advanced_tx.rs | RBF 替换机制、时间锁、手续费估算 |
| 内存池 | src/mempool.rs | 未确认交易管理与优先级排序 |
| 脚本引擎 | src/script.rs | Bitcoin Script 子集解释执行 |
| SPV | src/spv.rs | 轻量级支付验证客户端 |
Merkle 树
源文件: src/merkle.rs | 文档: Merkle API
Merkle 树是区块链数据完整性的基础。SimpleBTC 使用 SHA256 构建二叉哈希树,将区块内所有交易哈希汇聚为单个 32 字节的 merkle_root 存储在区块头中。
核心价值在于支持 SPV(简化支付验证):轻钱包无需下载完整区块(1-2 MB),只需获取区块头(80 字节)和 O(log n) 条哈希路径,即可以密码学方式证明某笔交易已被打包确认。
#![allow(unused)]
fn main() {
use simplebtc::merkle::MerkleTree;
let tx_ids = vec!["tx1_hash".to_string(), "tx2_hash".to_string()];
let tree = MerkleTree::new(&tx_ids);
let root = tree.get_root_hash();
// 生成并验证 SPV 证明
let proof = tree.get_proof("tx1_hash").unwrap();
let valid = MerkleTree::verify_proof("tx1_hash", &proof, &root, 0);
}
Merkle 树被 Block::new() 内部调用以计算 merkle_root,也被 Block::verify_transaction_inclusion() 用于 SPV 验证。
多重签名
源文件: src/multisig.rs | 文档: MultiSig API
多重签名(MultiSig)实现了 Bitcoin 的 M-of-N 签名方案:N 个参与方各持一个 ECDSA 密钥对,需要其中至少 M 个人签名才能动用资金。这是比特币协议中实现分布式控制与风险分散的核心机制。
典型应用场景包括:2-of-3 企业资金管理(防止单人挪用)、2-of-3 第三方托管(买家-卖家-仲裁员)、个人多设备备份(主密钥丢失仍可恢复)。
#![allow(unused)]
fn main() {
use simplebtc::multisig::{MultiSigAddress, MultiSigTxBuilder};
use simplebtc::wallet::Wallet;
// 创建 2-of-3 多签地址
let (w1, w2, w3) = (Wallet::new(), Wallet::new(), Wallet::new());
let pub_keys = vec![w1.public_key.clone(), w2.public_key.clone(), w3.public_key.clone()];
let ms_addr = MultiSigAddress::new(2, pub_keys).unwrap();
// 收集签名(任意两人签名即可)
let mut builder = MultiSigTxBuilder::new(ms_addr);
builder.add_signature(&w1, "交易数据").unwrap();
builder.add_signature(&w2, "交易数据").unwrap();
assert!(builder.is_complete());
}
多签地址以 "3" 开头(对应比特币的 P2SH 地址格式),通过脚本哈希生成,最多支持 15 个参与密钥。
高级交易
源文件: src/advanced_tx.rs | 文档: Advanced TX API
高级交易模块提供三项关键功能,解决比特币网络中的实际工程问题:
RBF(Replace-By-Fee): 允许用户用更高手续费的新交易替换尚未确认的旧交易,从而加速确认或取消错误交易。RBFManager 维护可替换交易列表并执行替换验证规则(输入相同、手续费更高、增量满足最低要求)。
TimeLock(时间锁): 限制交易在指定时间或区块高度之前无法被矿工打包。支持两种类型:基于 Unix 时间戳(new_time_based)和基于区块高度(new_height_based)。常用于定期存款、遗产继承、智能合约等场景。
TxPriorityCalculator(手续费计算器): 根据交易大小和紧急程度(Low/Medium/High/Urgent)推荐合理手续费,并计算综合优先级分数(70% 费率权重 + 30% 优先级权重)。
#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{AdvancedTxBuilder, TimeLock, RBFManager, TxPriorityCalculator, FeeUrgency};
// RBF + 时间锁组合使用
let timelock = TimeLock::new_height_based(850_000);
let builder = AdvancedTxBuilder::new()
.with_rbf()
.with_timelock(timelock);
// 推荐手续费
let fee = TxPriorityCalculator::recommend_fee(250, FeeUrgency::High); // 250 字节交易
println!("推荐手续费: {} satoshi", fee); // ~5000 satoshi
}
内存池(Mempool)
源文件: src/mempool.rs
内存池(Memory Pool,简称 Mempool)存储所有已广播但尚未打包进区块的交易。矿工从内存池中按优先级选取交易来构建新区块。
主要职责:
- 接收并暂存广播的交易
- 按手续费率排序,为矿工提供高价值交易优先选择
- 检测并拒绝双花(Double Spend)尝试
- 配合 RBF 机制处理交易替换
- 区块确认后从池中移除已打包交易
#![allow(unused)]
fn main() {
use simplebtc::mempool::Mempool;
let mut pool = Mempool::new();
pool.add_transaction(tx);
let pending = pool.get_pending_transactions(10); // 获取手续费最高的 10 笔
}
脚本引擎(Script)
源文件: src/script.rs
Bitcoin Script 是一种基于栈的简单脚本语言,用于定义交易的花费条件。SimpleBTC 实现了 Script 的核心子集,支持最常见的交易类型。
支持的脚本类型:
- P2PKH(Pay-to-Public-Key-Hash):最常见的普通地址交易,锁定脚本格式为
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG。 - P2SH(Pay-to-Script-Hash):多签和复杂合约的基础,以
"3"开头的地址。 - OP_RETURN:在链上写入不可花费的任意数据(最多 80 字节)。
脚本引擎为多重签名模块提供底层支撑:MultiSigAddress 的 script 字段存储的就是简化版的 Script 锁定脚本。
SPV(简化支付验证)
源文件: src/spv.rs
SPV 模拟比特币轻钱包的工作方式:在不下载完整区块链的情况下验证交易的合法性。这对于资源受限的设备(手机、嵌入式系统)至关重要。
SPV 验证流程:
- 仅下载区块头(每个约 80 字节,所有区块头约 60 MB)
- 验证区块头的工作量证明(PoW)
- 请求目标交易的 Merkle 证明(约几百字节)
- 本地执行
MerkleTree::verify_proof()验证
全节点模式:下载全部区块链 (~500 GB) → 本地完整验证
SPV 模式: 下载区块头 (~60 MB) + Merkle 证明 (几 KB) → O(log n) 验证
SPV 模块与 Merkle 模块深度集成,依赖 MerkleTree::get_proof() 和 MerkleTree::verify_proof() 实现轻量验证。
模块依赖关系
核心模块
├── Transaction (src/transaction.rs)
├── Wallet (src/wallet.rs)
└── Block (src/block.rs)
│
▼
高级模块(构建在核心模块之上)
├── Merkle ← Block 内部使用(计算 merkle_root 和 SPV 证明)
├── MultiSig ← 依赖 Wallet(ECDSA 签名)+ Script(锁定脚本)
├── AdvancedTx ← 依赖 Transaction(RBF 替换验证)
├── Mempool ← 依赖 Transaction + AdvancedTx(RBF 支持)
├── Script ← MultiSig 和 SPV 的基础
└── SPV ← 依赖 Merkle(证明验证)+ Block(区块头)
快速导航
- Merkle API — Merkle 树数据结构与 SPV 证明
- MultiSig API — M-of-N 多重签名
- Advanced TX API — RBF、时间锁、手续费计算
- Block API — 区块结构与挖矿
- Transaction API — 交易构建与验证
- Wallet API — 钱包与密钥管理
Merkle API
Merkle 树(哈希树)实现在 src/merkle.rs 中,是区块链数据完整性验证的核心数据结构。SimpleBTC 使用 SHA256 构建二叉 Merkle 树,将区块内所有交易汇聚为单个根哈希(merkle_root),存储在区块头中。
数据结构
MerkleNode 结构体
Merkle 树的单个节点,可以是叶子节点(对应一笔交易)或内部节点(对应子节点哈希的哈希)。
#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct MerkleNode {
pub hash: String, // 节点的 SHA256 哈希值(64 字符十六进制)
pub left: Option<Box<MerkleNode>>, // 左子节点(叶子节点为 None)
pub right: Option<Box<MerkleNode>>, // 右子节点(叶子节点为 None)
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
hash | String | 节点哈希。叶子节点为 SHA256(交易ID);内部节点为 SHA256(左哈希 + 右哈希)。 |
left | Option<Box<MerkleNode>> | 左子节点。叶子节点为 None。 |
right | Option<Box<MerkleNode>> | 右子节点。叶子节点为 None。 |
MerkleNode 方法
#![allow(unused)]
fn main() {
// 从原始数据创建叶子节点(计算 SHA256 哈希)
pub fn new_leaf(data: &str) -> Self
// 从两个子节点创建内部节点(哈希 = SHA256(左哈希 + 右哈希))
pub fn new_internal(left: MerkleNode, right: MerkleNode) -> Self
}
MerkleTree 结构体
完整的 Merkle 树,持有根节点和原始叶子数据列表。
#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct MerkleTree {
pub root: Option<MerkleNode>, // 树根节点(空交易列表时为 None)
pub leaves: Vec<String>, // 原始叶子数据列表(交易 ID 列表)
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
root | Option<MerkleNode> | 树的根节点。输入为空时为 None。 |
leaves | Vec<String> | 构建时传入的原始交易 ID 列表(未哈希)。 |
树的结构示意
以 4 笔交易为例:
Root
/ \
H12 H34
/ \ / \
H1 H2 H3 H4
│ │ │ │
tx1 tx2 tx3 tx4
其中:
H1 = SHA256(tx1)
H2 = SHA256(tx2)
H12 = SHA256(H1 + H2)
H34 = SHA256(H3 + H4)
Root = SHA256(H12 + H34)
奇数交易处理: 若某层节点数为奇数,最后一个节点被复制配对(例如 3 笔交易时,tx3 被复制为 tx3’)。
方法
MerkleTree::new
从交易 ID 列表构建完整的 Merkle 树。采用自底向上的方式逐层构建,时间复杂度 O(n)。
#![allow(unused)]
fn main() {
pub fn new(transactions: &[String]) -> Self
}
参数:
transactions— 交易 ID(或任意字符串)的切片。可以为空,此时root为None。
返回值: 构建完成的 MerkleTree 实例。
#![allow(unused)]
fn main() {
use simplebtc::merkle::MerkleTree;
// 从交易 ID 列表构建树
let tx_ids = vec![
"tx_hash_1".to_string(),
"tx_hash_2".to_string(),
"tx_hash_3".to_string(),
"tx_hash_4".to_string(),
];
let tree = MerkleTree::new(&tx_ids);
// 处理空交易列表
let empty_tree = MerkleTree::new(&[]);
assert!(empty_tree.root.is_none());
}
MerkleTree::get_root_hash
获取 Merkle 树的根哈希字符串。这个值存储在区块头的 merkle_root 字段中。
#![allow(unused)]
fn main() {
pub fn get_root_hash(&self) -> String
}
返回值:
- 64 字符的小写十六进制 SHA256 哈希字符串(树非空时)。
- 空字符串
""(树为空时,即root为None)。
#![allow(unused)]
fn main() {
let tree = MerkleTree::new(&tx_ids);
let root_hash = tree.get_root_hash();
println!("Merkle 根: {}", root_hash);
// 输出: a3f7c2e1b4d9...(64 字符十六进制)
// 与区块中存储的值比较
assert_eq!(root_hash, block.merkle_root);
}
MerkleTree::get_proof
为指定交易生成 Merkle 证明(SPV 证明)。证明是一组兄弟节点哈希,SPV 客户端利用这些哈希从叶子逐层向上重建根哈希,无需访问完整区块。
#![allow(unused)]
fn main() {
pub fn get_proof(&self, tx_hash: &str) -> Option<Vec<String>>
}
参数:
tx_hash— 要生成证明的交易 ID(必须存在于self.leaves中)。
返回值:
Some(Vec<String>)— 证明所需的兄弟节点哈希列表,按从叶子层到根层的顺序排列。None— 交易 ID 不存在于该 Merkle 树中。
证明大小: 对于包含 n 笔交易的区块,证明包含 ceil(log2(n)) 个哈希,每个 32 字节。例如 2000 笔交易的区块,证明仅约 352 字节(11 个哈希)。
#![allow(unused)]
fn main() {
let tree = MerkleTree::new(&tx_ids);
match tree.get_proof("tx_hash_1") {
Some(proof) => {
println!("证明包含 {} 个兄弟哈希", proof.len());
for (i, hash) in proof.iter().enumerate() {
println!(" 层 {}: {}", i, &hash[..16]);
}
}
None => println!("交易不存在于此 Merkle 树"),
}
}
MerkleTree::verify_proof
验证 Merkle 证明(静态方法)。这是 SPV 轻量级验证的核心函数:利用证明中的兄弟哈希,从叶子节点逐层向上计算,验证最终结果是否与区块头中的 merkle_root 匹配。
#![allow(unused)]
fn main() {
pub fn verify_proof(
tx_hash: &str,
proof: &[String],
root_hash: &str,
index: usize,
) -> bool
}
参数:
tx_hash— 要验证的交易 ID 字符串(原始值,非哈希)。proof— 由get_proof()生成的兄弟节点哈希列表。root_hash— 区块头中存储的merkle_root值。index— 交易在区块交易列表中的位置索引(从 0 开始),用于确定左右合并顺序。
返回值:
true— 证明有效,该交易确实包含在对应区块中。false— 证明无效,交易不在该区块中,或数据被篡改。
验证算法:
输入: tx_hash, proof = [sibling_0, sibling_1, ...], root_hash, index
步骤:
current = SHA256(tx_hash)
对于 proof 中的每个 sibling_hash:
如果 index 为偶数(当前节点在左):
current = SHA256(current + sibling_hash)
如果 index 为奇数(当前节点在右):
current = SHA256(sibling_hash + current)
index = index / 2
最终: current == root_hash → 验证通过
#![allow(unused)]
fn main() {
use simplebtc::merkle::MerkleTree;
let tx_ids = vec![
"tx1".to_string(),
"tx2".to_string(),
"tx3".to_string(),
"tx4".to_string(),
];
let tree = MerkleTree::new(&tx_ids);
let root = tree.get_root_hash();
// 生成证明
let proof = tree.get_proof("tx1").expect("交易存在");
// 验证证明(index=0,tx1 是第一笔交易)
let is_valid = MerkleTree::verify_proof("tx1", &proof, &root, 0);
assert!(is_valid, "SPV 证明验证失败");
println!("交易 tx1 已确认包含在区块中");
// 篡改测试:修改交易内容后证明失效
let tampered = MerkleTree::verify_proof("tx1_TAMPERED", &proof, &root, 0);
assert!(!tampered, "篡改后证明应当失效");
}
完整使用示例
示例一:与区块集成
use simplebtc::block::Block;
use simplebtc::merkle::MerkleTree;
use simplebtc::transaction::Transaction;
use simplebtc::wallet::Wallet;
fn main() {
// 模拟打包 4 笔交易
let miner = Wallet::new();
let alice = Wallet::new();
let bob = Wallet::new();
let transactions = vec![
Transaction::new_coinbase(&miner.address, 3_125_000),
Transaction::new(&alice, &bob.address, 100_000, 1_000),
Transaction::new(&alice, &miner.address, 50_000, 500),
Transaction::new(&bob, &alice.address, 20_000, 200),
];
// Block::new 内部自动构建 MerkleTree 并计算 merkle_root
let block = Block::new(1, transactions, "000000abc...".to_string());
println!("Merkle 根: {}", block.merkle_root);
// SPV 验证:tx[2] 是否在此区块中
let tx_id = block.transactions[2].id.clone();
let included = block.verify_transaction_inclusion(&tx_id, 2);
println!("交易已包含在区块中: {}", included);
}
示例二:独立使用 MerkleTree
#![allow(unused)]
fn main() {
use simplebtc::merkle::MerkleTree;
fn spv_demo() {
// 全节点构建完整 Merkle 树
let tx_ids: Vec<String> = (1..=8)
.map(|i| format!("transaction_{:04}", i))
.collect();
let tree = MerkleTree::new(&tx_ids);
let root = tree.get_root_hash();
println!("8 笔交易的 Merkle 根: {}", root);
// 为 tx #5 (index=4) 生成 SPV 证明
let target_tx = "transaction_0005";
let proof = tree.get_proof(target_tx).expect("交易存在");
println!("证明大小: {} 个哈希(log2(8)=3 层)", proof.len());
// SPV 客户端验证(仅需 root + proof,不需要完整交易列表)
let verified = MerkleTree::verify_proof(target_tx, &proof, &root, 4);
println!("SPV 验证结果: {}", verified);
}
}
示例三:检测数据篡改
#![allow(unused)]
fn main() {
use simplebtc::merkle::MerkleTree;
fn tamper_detection() {
let original = vec!["tx_a".to_string(), "tx_b".to_string(), "tx_c".to_string()];
let tree = MerkleTree::new(&original);
let original_root = tree.get_root_hash();
// 模拟攻击者修改了 tx_b
let mut tampered = original.clone();
tampered[1] = "tx_b_MALICIOUS".to_string();
let tampered_tree = MerkleTree::new(&tampered);
let tampered_root = tampered_tree.get_root_hash();
// Merkle 根完全不同,篡改立即被检测到
assert_ne!(original_root, tampered_root);
println!("原始根: {}", &original_root[..16]);
println!("篡改后根: {}", &tampered_root[..16]);
println!("篡改检测成功:根哈希已改变");
}
}
安全性说明
为什么 Merkle 证明可以信任?
攻击者若要伪造一个合法的 Merkle 证明,需要:
- 找到一个 SHA256 哈希碰撞(计算复杂度约 2^128,当前技术无法实现);或
- 重新挖矿(改变
merkle_root会改变区块哈希,需要重新完成工作量证明)。
因此,只要 SPV 客户端能获取到由诚实的工作量证明保护的区块头,Merkle 证明的安全性就与完整节点等价。
区块时间误差: 矿工的区块时间戳允许有约 2 小时的误差,但这不影响 Merkle 验证的安全性(Merkle 树不依赖时间戳)。
相关模块
Block—Block::new()内部调用MerkleTree::new()计算merkle_root;Block::verify_transaction_inclusion()使用get_proof()和verify_proof()。- 高级模块 — SPV 模块 (
src/spv.rs) 基于 Merkle API 实现轻量级客户端。
MultiSig API
多重签名(Multi-Signature,简称 MultiSig)实现在 src/multisig.rs 中,支持 Bitcoin 的 M-of-N 签名方案:从 N 个参与方中需要至少 M 个人提供有效的 ECDSA 签名,资金才能被动用。
核心概念
M-of-N 签名: N 个参与方各持一个密钥对,任意 M 个人签名即可授权交易。常见组合:
| 类型 | 含义 | 典型场景 |
|---|---|---|
| 2-of-2 | 两人必须全部同意 | 联合账户、合伙企业 |
| 2-of-3 | 三人中任意两人同意 | 企业资金管理、托管服务 |
| 3-of-5 | 五人中任意三人同意 | 大型机构资金、董事会决策 |
地址格式: 多签地址以 "3" 开头,对应比特币的 P2SH(Pay-to-Script-Hash)格式。
MultiSigAddress 结构体
表示一个 M-of-N 多重签名地址及其配置。
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiSigAddress {
pub address: String, // 多签地址("3" 开头,P2SH 格式)
pub required_sigs: usize, // 需要的签名数量 M
pub total_keys: usize, // 总密钥数量 N
pub public_keys: Vec<String>, // 所有参与方的公钥列表
pub script: String, // 锁定脚本(简化的 Script 代码)
}
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
address | String | 多签地址,以 "3" 开头,由锁定脚本的 SHA256 哈希截取 42 字符生成。 |
required_sigs | usize | M 值,即花费资金所需的最少有效签名数。 |
total_keys | usize | N 值,即参与方总数(等于 public_keys.len())。 |
public_keys | Vec<String> | 所有参与方钱包的公钥列表(十六进制编码)。 |
script | String | 锁定脚本,格式为 OP_{M}{pubkeys...}OP_CHECKMULTISIG。 |
MultiSigAddress 方法
MultiSigAddress::new
创建一个 M-of-N 多重签名地址。验证参数合法性后生成锁定脚本和对应的 P2SH 地址。
#![allow(unused)]
fn main() {
pub fn new(
required_sigs: usize,
public_keys: Vec<String>,
) -> Result<Self, String>
}
参数:
required_sigs— 所需签名数 M,必须满足1 <= M <= N。public_keys— 所有参与方的公钥列表,长度即为 N,最多 15 个。
返回值:
Ok(MultiSigAddress)— 创建成功。Err(String)— 参数非法,错误原因包括:"无效的签名要求"—required_sigs == 0或required_sigs > total_keys。"最多支持15个密钥"—public_keys.len() > 15(比特币协议限制)。
#![allow(unused)]
fn main() {
use simplebtc::multisig::MultiSigAddress;
use simplebtc::wallet::Wallet;
// 创建三个参与方钱包
let wallet1 = Wallet::new();
let wallet2 = Wallet::new();
let wallet3 = Wallet::new();
let public_keys = vec![
wallet1.public_key.clone(),
wallet2.public_key.clone(),
wallet3.public_key.clone(),
];
// 创建 2-of-3 多签地址
let multisig = MultiSigAddress::new(2, public_keys).unwrap();
assert!(multisig.address.starts_with('3'));
assert_eq!(multisig.required_sigs, 2);
assert_eq!(multisig.total_keys, 3);
println!("多签地址: {}", multisig.address);
println!("锁定脚本: {}", multisig.script);
// 参数校验错误示例
let result = MultiSigAddress::new(0, vec!["key1".to_string()]);
assert!(result.is_err()); // required_sigs 不能为 0
let result = MultiSigAddress::new(3, vec!["key1".to_string(), "key2".to_string()]);
assert!(result.is_err()); // required_sigs(3) > total_keys(2)
}
MultiSigAddress::verify_signatures
快速检查签名数量是否满足要求(不验证签名内容,仅校验数量)。
#![allow(unused)]
fn main() {
pub fn verify_signatures(&self, signatures: &[String]) -> bool
}
参数:
signatures— 签名列表。
返回值: signatures.len() >= self.required_sigs。
#![allow(unused)]
fn main() {
let sigs = vec!["sig1".to_string(), "sig2".to_string()];
let count_ok = multisig.verify_signatures(&sigs);
println!("签名数量满足要求: {}", count_ok); // true(2 >= 2)
}
MultiSigAddress::verify_signatures_with_data
完整的签名验证:不仅检查数量,还用 ECDSA 验证每个签名是否由 public_keys 中的某个密钥产生。
#![allow(unused)]
fn main() {
pub fn verify_signatures_with_data(
&self,
signatures: &[String],
data: &str,
) -> bool
}
参数:
signatures— 十六进制 DER 编码的 ECDSA 签名列表。data— 被签名的原始数据字符串(通常是交易 ID 或交易摘要)。
返回值:
true— 有效签名数量 >=required_sigs。每个签名最多匹配一个公钥(防止同一签名重复计数)。false— 有效签名数量不足,或签名不对应public_keys中的任何公钥。
#![allow(unused)]
fn main() {
// 用 wallet1 和 wallet2 对交易数据签名
let data = "交易摘要:Alice 转账 0.01 BTC 给 Bob";
let sig1 = wallet1.sign(data);
let sig2 = wallet2.sign(data);
let valid = multisig.verify_signatures_with_data(
&[sig1, sig2],
data,
);
println!("ECDSA 验证通过: {}", valid);
}
MultiSigTxBuilder 结构体
多重签名交易构建器,负责收集签名并确认是否满足 M-of-N 要求。内置防重复签名检测。
#![allow(unused)]
fn main() {
pub struct MultiSigTxBuilder {
pub multisig_address: MultiSigAddress,
pub signatures: Vec<String>,
// signed_keys: HashMap<String, bool> // 私有字段,防止同一公钥重复签名
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
multisig_address | MultiSigAddress | 关联的多签地址配置(含 M、N、公钥列表)。 |
signatures | Vec<String> | 已收集的有效 ECDSA 签名列表。 |
MultiSigTxBuilder 方法
MultiSigTxBuilder::new
创建多签交易构建器,与指定的 MultiSigAddress 关联。
#![allow(unused)]
fn main() {
pub fn new(multisig_address: MultiSigAddress) -> Self
}
参数:
multisig_address— 已创建的MultiSigAddress实例。
#![allow(unused)]
fn main() {
use simplebtc::multisig::MultiSigTxBuilder;
let builder = MultiSigTxBuilder::new(multisig);
assert_eq!(builder.signatures.len(), 0);
assert!(!builder.is_complete());
}
MultiSigTxBuilder::add_signature
添加一个参与方的 ECDSA 签名。内部自动验证:
- 该钱包的公钥必须在
multisig_address.public_keys中。 - 该钱包不能重复签名(防止同一人签名两次来伪造 M 个签名)。
#![allow(unused)]
fn main() {
pub fn add_signature(
&mut self,
wallet: &Wallet,
data: &str,
) -> Result<(), String>
}
参数:
wallet— 参与方的Wallet实例(用于调用wallet.sign(data)生成签名)。data— 要签名的数据(通常为交易摘要或交易 ID)。
返回值:
Ok(())— 签名添加成功。Err(String)— 错误原因:"此钱包不在多签地址中"— 该钱包的公钥不在public_keys列表中。"此钱包已签名"— 该钱包之前已经签过名。
#![allow(unused)]
fn main() {
let mut builder = MultiSigTxBuilder::new(multisig.clone());
let data = "transfer_tx_hash_abc123";
// wallet1 签名成功
builder.add_signature(&wallet1, data).unwrap();
assert_eq!(builder.signatures.len(), 1);
// wallet1 不能重复签名
let err = builder.add_signature(&wallet1, data);
assert!(err.is_err());
println!("重复签名错误: {}", err.unwrap_err()); // "此钱包已签名"
// 不在多签地址中的钱包无法签名
let outsider = Wallet::new();
let err = builder.add_signature(&outsider, data);
assert!(err.is_err());
println!("外部钱包错误: {}", err.unwrap_err()); // "此钱包不在多签地址中"
}
MultiSigTxBuilder::is_complete
检查是否已收集到足够的签名(signatures.len() >= required_sigs)。
#![allow(unused)]
fn main() {
pub fn is_complete(&self) -> bool
}
返回值: true 表示已满足 M-of-N 要求,可以广播交易。
#![allow(unused)]
fn main() {
let mut builder = MultiSigTxBuilder::new(multisig);
assert!(!builder.is_complete()); // 0 个签名
builder.add_signature(&wallet1, data).unwrap();
assert!(!builder.is_complete()); // 1 个签名,还不够(需要 2)
builder.add_signature(&wallet2, data).unwrap();
assert!(builder.is_complete()); // 2 个签名,满足 2-of-3
println!("多签已完成,可以广播交易");
}
MultiSigTxBuilder::get_signatures
获取所有已收集签名的副本列表。
#![allow(unused)]
fn main() {
pub fn get_signatures(&self) -> Vec<String>
}
返回值: Vec<String> — 签名列表的克隆(不影响构建器内部状态)。
#![allow(unused)]
fn main() {
let sigs = builder.get_signatures();
println!("已收集 {} 个签名", sigs.len());
for (i, sig) in sigs.iter().enumerate() {
println!(" 签名 {}: {}...", i + 1, &sig[..16]);
}
}
MultiSigType 枚举(便捷 API)
提供常见多签类型的快速创建方式。
#![allow(unused)]
fn main() {
pub enum MultiSigType {
TwoOfTwo, // 2-of-2
TwoOfThree, // 2-of-3(最常用)
ThreeOfFive, // 3-of-5
}
impl MultiSigType {
pub fn create_address(&self, wallets: &[Wallet]) -> Result<MultiSigAddress, String>
}
}
#![allow(unused)]
fn main() {
use simplebtc::multisig::MultiSigType;
use simplebtc::wallet::Wallet;
let wallets: Vec<Wallet> = (0..3).map(|_| Wallet::new()).collect();
let ms_addr = MultiSigType::TwoOfThree.create_address(&wallets).unwrap();
println!("2-of-3 地址: {}", ms_addr.address);
// 如果钱包数量不匹配会返回错误
let err = MultiSigType::ThreeOfFive.create_address(&wallets); // 需要 5 个钱包
assert!(err.is_err());
}
完整使用示例
场景一:企业资金管理(2-of-3)
#![allow(unused)]
fn main() {
use simplebtc::multisig::{MultiSigAddress, MultiSigTxBuilder};
use simplebtc::wallet::Wallet;
fn corporate_treasury() {
// 公司三个高管各持一个密钥
let ceo = Wallet::new();
let cfo = Wallet::new();
let cto = Wallet::new();
// 创建 2-of-3 企业多签地址
let pub_keys = vec![
ceo.public_key.clone(),
cfo.public_key.clone(),
cto.public_key.clone(),
];
let treasury = MultiSigAddress::new(2, pub_keys).unwrap();
println!("企业金库地址: {}", treasury.address);
// 发起一笔支付(需要 CEO + CFO 共同签名)
let tx_data = "支付 10 BTC 给供应商 ABC";
let mut builder = MultiSigTxBuilder::new(treasury);
builder.add_signature(&ceo, tx_data).unwrap();
println!("CEO 已签名,等待第二个授权...");
builder.add_signature(&cfo, tx_data).unwrap();
println!("CFO 已签名");
if builder.is_complete() {
let signatures = builder.get_signatures();
println!("交易已完成授权,签名数: {}", signatures.len());
// 此处将 signatures 附加到交易并广播到网络
}
}
}
场景二:第三方托管(买家-卖家-仲裁员)
#![allow(unused)]
fn main() {
fn escrow_service() {
let buyer = Wallet::new();
let seller = Wallet::new();
let arbiter = Wallet::new();
let pub_keys = vec![
buyer.public_key.clone(),
seller.public_key.clone(),
arbiter.public_key.clone(),
];
// 2-of-3:正常情况买家+卖家,争议时任一方+仲裁员
let escrow = MultiSigAddress::new(2, pub_keys).unwrap();
println!("托管地址: {}", escrow.address);
let release_tx = "释放托管资金至卖家地址";
// 正常流程:买家确认收货,买家+卖家共同签名释放资金
let mut builder = MultiSigTxBuilder::new(escrow.clone());
builder.add_signature(&buyer, release_tx).unwrap();
builder.add_signature(&seller, release_tx).unwrap();
assert!(builder.is_complete());
println!("资金正常释放给卖家");
// 争议流程:仲裁员介入,卖家+仲裁员签名释放资金
let dispute_tx = "争议裁定:退款给买家";
let mut dispute_builder = MultiSigTxBuilder::new(escrow);
dispute_builder.add_signature(&buyer, dispute_tx).unwrap();
dispute_builder.add_signature(&arbiter, dispute_tx).unwrap();
assert!(dispute_builder.is_complete());
println!("仲裁完成,资金退还买家");
}
}
场景三:完整签名验证流程
#![allow(unused)]
fn main() {
fn full_verification() {
let w1 = Wallet::new();
let w2 = Wallet::new();
let pub_keys = vec![w1.public_key.clone(), w2.public_key.clone()];
let ms = MultiSigAddress::new(2, pub_keys).unwrap(); // 2-of-2
let tx_data = "转账 1 BTC";
// 收集签名
let sig1 = w1.sign(tx_data);
let sig2 = w2.sign(tx_data);
let sigs = vec![sig1, sig2];
// 完整 ECDSA 验证(验证签名是否由 public_keys 中的密钥产生)
let valid = ms.verify_signatures_with_data(&sigs, tx_data);
println!("ECDSA 多签验证: {}", valid);
// 快速数量检查(不验证内容)
let count_ok = ms.verify_signatures(&sigs);
println!("签名数量满足: {}", count_ok);
}
}
限制与注意事项
- 最大密钥数: 由于比特币脚本限制,N 最大为 15(超出返回错误)。
- 防重复签名:
MultiSigTxBuilder内部维护已签名公钥集合,同一钱包调用add_signature两次会返回错误。 - 签名顺序: 验证时不要求签名顺序与公钥顺序一致,任意 M 个有效签名即可。
- 零确认风险: 多签交易在被矿工打包前仍属于未确认状态,重要交易应等待至少 1 个区块确认。
相关模块
Wallet— 提供sign()和verify_signature()方法,是多签 ECDSA 操作的基础。AdvancedTxBuilder— 可与时间锁结合,实现“时间到期后多签要求降低“等高级场景。- 高级模块概览 — 了解多签在 SimpleBTC 整体架构中的位置。
Advanced TX API
高级交易模块实现在 src/advanced_tx.rs 中,提供三项解决比特币网络实际工程问题的关键机制:RBF 替换手续费(允许加速或取消未确认交易)、TimeLock 时间锁(限制交易在指定时间/区块前无法确认)以及 TxPriorityCalculator 手续费计算器(推荐合理手续费并计算交易优先级)。
RBFManager — Replace-By-Fee 管理器
RBF(Replace-By-Fee)是 BIP125 定义的机制,允许用户用手续费更高的新交易替换内存池中尚未确认的旧交易,从而加速确认或取消错误交易。
结构体定义
#![allow(unused)]
fn main() {
pub struct RBFManager {
// replaceable_txs: Vec<String> // 私有字段,存储可替换的交易 ID 列表
}
}
方法
RBFManager::new
创建新的 RBF 管理器实例(可替换交易列表为空)。
#![allow(unused)]
fn main() {
pub fn new() -> Self
}
RBFManager::mark_replaceable
将指定交易标记为支持 RBF 替换。幂等操作,重复标记同一交易不会产生副作用。
#![allow(unused)]
fn main() {
pub fn mark_replaceable(&mut self, tx_id: &str)
}
参数:
tx_id— 要标记为可替换的交易 ID。
在比特币协议中,通过将交易的 nSequence 字段设置为小于 0xFFFFFFFE 的值来表示支持 RBF。AdvancedTxBuilder::with_rbf() 会自动将 sequence 设为 0xFFFFFFFD。
RBFManager::is_replaceable
检查某笔交易是否已被标记为可替换。
#![allow(unused)]
fn main() {
pub fn is_replaceable(&self, tx_id: &str) -> bool
}
RBFManager::can_replace
验证新交易是否可以合法替换旧交易。执行完整的 RBF 规则校验:
#![allow(unused)]
fn main() {
pub fn can_replace(
&self,
old_tx: &Transaction,
new_tx: &Transaction,
) -> Result<(), String>
}
验证规则(按顺序):
- 可替换性检查:
old_tx.id必须在可替换列表中,否则返回"原交易不支持RBF"。 - 输入相同: 两笔交易的输入列表长度相同,且对应输入的
txid和vout完全一致(必须花费相同的 UTXO),否则返回"必须花费相同的UTXO"。 - 手续费更高:
new_tx.fee > old_tx.fee,否则返回"新交易手续费({})必须高于旧交易({})"。 - 增量足够:
fee_increase >= old_tx.size()(简化规则:手续费增量至少为旧交易字节数个 satoshi),防止低成本的垃圾替换攻击。
返回值:
Ok(())— 替换合法,可以广播新交易。Err(String)— 具体的验证失败原因。
RBFManager::remove_confirmed
将已被区块打包确认的交易从可替换列表中移除。
#![allow(unused)]
fn main() {
pub fn remove_confirmed(&mut self, tx_id: &str)
}
RBFManager 使用示例
#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::RBFManager;
let mut rbf = RBFManager::new();
// 标记原始交易支持 RBF
rbf.mark_replaceable("original_tx_001");
assert!(rbf.is_replaceable("original_tx_001"));
assert!(!rbf.is_replaceable("other_tx_002"));
// 验证替换是否合法
match rbf.can_replace(&old_tx, &new_tx) {
Ok(()) => println!("替换合法,广播新交易"),
Err(e) => println!("替换被拒绝: {}", e),
}
// 交易确认后移除记录
rbf.remove_confirmed("original_tx_001");
assert!(!rbf.is_replaceable("original_tx_001"));
}
TimeLock — 时间锁
时间锁限制交易在特定时间或区块高度之前无法被矿工打包,是实现定期存款、遗产继承、智能合约等高级场景的基础原语。
结构体定义
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeLock {
pub locktime: u64, // 锁定值:Unix 时间戳(秒)或区块高度
pub is_block_height: bool, // true: 基于区块高度;false: 基于时间戳
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
locktime | u64 | 锁定值。is_block_height = true 时为区块高度,否则为 Unix 时间戳(秒)。 |
is_block_height | bool | 锁定类型标志。对应比特币协议中 locktime < 500_000_000 为区块高度,>= 500_000_000 为时间戳。 |
方法
TimeLock::new_time_based
创建基于 Unix 时间戳的时间锁。交易在 timestamp 秒之前无法被确认。
#![allow(unused)]
fn main() {
pub fn new_time_based(timestamp: u64) -> Self
}
参数:
timestamp— 解锁时间的 Unix 时间戳(秒)。例如1767225600表示 2026-01-01 00:00:00 UTC。
TimeLock::new_height_based
创建基于区块高度的时间锁。交易在区块链达到指定高度之前无法被确认。
#![allow(unused)]
fn main() {
pub fn new_height_based(height: u64) -> Self
}
参数:
height— 解锁所需的区块高度。例如900_000表示约 2027 年中(基于约 10 分钟/区块估算)。
TimeLock::is_mature
检查时间锁是否已到期(可以使用)。
#![allow(unused)]
fn main() {
pub fn is_mature(&self, current_time: u64, current_height: u32) -> bool
}
参数:
current_time— 当前 Unix 时间戳(秒)。current_height— 当前区块链高度。
返回值:
- 基于时间:
current_time >= self.locktime - 基于区块高度:
current_height as u64 >= self.locktime
TimeLock::remaining
获取距离解锁还剩多少时间(秒)或区块数。
#![allow(unused)]
fn main() {
pub fn remaining(&self, current_time: u64, current_height: u32) -> i64
}
返回值: 剩余秒数或区块数。负值表示已超过锁定时间(已到期)。
TimeLock 使用示例
#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::TimeLock;
// 基于时间戳:锁定至 2026-01-01 00:00:00 UTC
let time_lock = TimeLock::new_time_based(1_767_225_600);
let now = 1_740_000_000u64; // 当前时间(2025年)
println!("时间锁已到期: {}", time_lock.is_mature(now, 0)); // false
println!("距解锁剩余: {} 秒", time_lock.remaining(now, 0));
// 基于区块高度:锁定至第 90 万个区块
let block_lock = TimeLock::new_height_based(900_000);
let current_height = 850_000u32; // 当前区块高度
println!("区块锁已到期: {}", block_lock.is_mature(0, current_height)); // false
println!("距解锁剩余: {} 个区块", block_lock.remaining(0, current_height)); // 50000
// 已到期的时间锁
let expired_lock = TimeLock::new_height_based(800_000);
println!("已到期: {}", expired_lock.is_mature(0, 850_000)); // true
println!("剩余(负数表示已过期): {}", expired_lock.remaining(0, 850_000)); // -50000
}
AdvancedTxBuilder — 高级交易构建器
AdvancedTxBuilder 是一个构建器(Builder Pattern),用于配置交易的高级选项(RBF 支持和时间锁),并生成对应的 sequence 字段值。
结构体定义
#![allow(unused)]
fn main() {
pub struct AdvancedTxBuilder {
pub enable_rbf: bool,
pub timelock: Option<TimeLock>,
pub sequence: u32,
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
enable_rbf | bool | 是否启用 RBF 支持。with_rbf() 后为 true。 |
timelock | Option<TimeLock> | 关联的时间锁配置。with_timelock() 后为 Some(TimeLock)。 |
sequence | u32 | 交易输入的序列号,编码了 RBF 和时间锁状态:0xFFFFFFFF(默认/无功能)、0xFFFFFFFD(RBF)、0x00000000(时间锁)。 |
方法
AdvancedTxBuilder::new
创建默认构建器。默认不启用 RBF 和时间锁,sequence = 0xFFFFFFFF。
#![allow(unused)]
fn main() {
pub fn new() -> Self
}
AdvancedTxBuilder::with_rbf
启用 RBF 支持。将 enable_rbf 设为 true,sequence 设为 0xFFFFFFFD(小于 0xFFFFFFFE,符合 BIP125 规范)。
#![allow(unused)]
fn main() {
pub fn with_rbf(mut self) -> Self
}
返回值: Self(支持链式调用)。
AdvancedTxBuilder::with_timelock
设置时间锁。将 timelock 设为 Some(timelock),sequence 设为 0(启用 nLockTime 机制)。
#![allow(unused)]
fn main() {
pub fn with_timelock(mut self, timelock: TimeLock) -> Self
}
参数:
timelock— 要关联的TimeLock实例。
返回值: Self(支持链式调用)。
AdvancedTxBuilder::get_sequence
获取最终的 sequence 字段值,应写入交易输入的 nSequence 字段。
#![allow(unused)]
fn main() {
pub fn get_sequence(&self) -> u32
}
AdvancedTxBuilder::supports_rbf
检查当前配置是否支持 RBF(sequence < 0xFFFFFFFE)。
#![allow(unused)]
fn main() {
pub fn supports_rbf(&self) -> bool
}
AdvancedTxBuilder 使用示例
#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{AdvancedTxBuilder, TimeLock};
// 仅启用 RBF
let rbf_builder = AdvancedTxBuilder::new()
.with_rbf();
println!("sequence: 0x{:08X}", rbf_builder.get_sequence()); // 0xFFFFFFFD
println!("支持 RBF: {}", rbf_builder.supports_rbf()); // true
// 仅启用时间锁(锁定至第 900,000 个区块)
let timelock = TimeLock::new_height_based(900_000);
let timelock_builder = AdvancedTxBuilder::new()
.with_timelock(timelock);
println!("sequence: 0x{:08X}", timelock_builder.get_sequence()); // 0x00000000
// RBF + 时间锁组合(with_timelock 会覆盖 sequence 为 0)
let combined = AdvancedTxBuilder::new()
.with_rbf()
.with_timelock(TimeLock::new_time_based(1_800_000_000));
println!("时间锁: {:?}", combined.timelock);
println!("sequence: 0x{:08X}", combined.get_sequence()); // 0x00000000
// 默认构建器(无高级功能)
let default_builder = AdvancedTxBuilder::new();
println!("sequence: 0x{:08X}", default_builder.get_sequence()); // 0xFFFFFFFF
println!("支持 RBF: {}", default_builder.supports_rbf()); // false
}
TxPriorityCalculator — 交易优先级计算器
TxPriorityCalculator 是一个无状态工具类(所有方法均为关联函数),用于计算交易优先级分数和推荐合理手续费。矿工使用优先级分数决定先打包哪些内存池中的交易。
结构体定义
#![allow(unused)]
fn main() {
pub struct TxPriorityCalculator;
}
FeeUrgency — 手续费紧急程度
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy)]
pub enum FeeUrgency {
Low, // 低优先级:1 sat/byte,几小时内确认
Medium, // 中优先级:5 sat/byte,30-60 分钟确认
High, // 高优先级:20 sat/byte,10-20 分钟(约 1-2 个区块)
Urgent, // 紧急:50 sat/byte,下一个区块(约 10 分钟)
}
}
| 枚举值 | 费率 | 预期确认时间 | 典型场景 |
|---|---|---|---|
Low | 1 sat/byte | 数小时至数天 | 非紧急转账、低网络费用时段 |
Medium | 5 sat/byte | 30-60 分钟 | 日常交易、普通确认速度 |
High | 20 sat/byte | 10-20 分钟 | 时间敏感交易(闪电网络开通) |
Urgent | 50 sat/byte | ~10 分钟(下一区块) | 紧急支付、交易所提现 |
方法
TxPriorityCalculator::calculate_priority
计算基于 UTXO 价值和年龄的传统优先级分数。
公式: priority = (input_value × input_age) / tx_size
#![allow(unused)]
fn main() {
pub fn calculate_priority(
input_value: u64, // 输入 UTXO 总价值(satoshi)
input_age: u32, // 输入 UTXO 的年龄(确认区块数)
tx_size: usize, // 交易大小(字节)
) -> f64
}
较老(age 大)且价值较高的 UTXO 的优先级更高。tx_size = 0 时返回 0.0(防止除零)。
TxPriorityCalculator::calculate_fee_rate
计算交易的手续费率(sat/byte)。
公式: fee_rate = fee / size
#![allow(unused)]
fn main() {
pub fn calculate_fee_rate(
fee: u64, // 手续费(satoshi)
size: usize, // 交易大小(字节)
) -> f64
}
size = 0 时返回 0.0。
TxPriorityCalculator::calculate_score
计算综合评分(矿工排序依据)。
公式: score = fee_rate × 0.7 + priority × 0.001 × 0.3
#![allow(unused)]
fn main() {
pub fn calculate_score(fee_rate: f64, priority: f64) -> f64
}
权重分配:70% 基于费率,30% 基于 UTXO 优先级。高费率的交易综合得分更高,更容易被矿工选中。
TxPriorityCalculator::recommend_fee
根据交易大小和紧急程度推荐手续费(satoshi)。
#![allow(unused)]
fn main() {
pub fn recommend_fee(
tx_size: usize, // 交易大小(字节)
urgency: FeeUrgency, // 手续费紧急程度
) -> u64
}
返回值: (tx_size × sat_per_byte) as u64 向下取整。
TxPriorityCalculator 使用示例
#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{TxPriorityCalculator, FeeUrgency};
// 标准比特币交易约 250 字节(1 输入 + 2 输出)
let tx_size = 250usize;
// 推荐各紧急程度的手续费
println!("低优先级: {} sat", TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Low));
// 250 sat
println!("中优先级: {} sat", TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Medium));
// 1250 sat
println!("高优先级: {} sat", TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::High));
// 5000 sat
println!("紧急: {} sat", TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Urgent));
// 12500 sat
// 计算现有交易的费率
let actual_fee = 2000u64; // 实际手续费
let fee_rate = TxPriorityCalculator::calculate_fee_rate(actual_fee, tx_size);
println!("实际费率: {:.1} sat/byte", fee_rate); // 8.0 sat/byte
// 计算 UTXO 优先级(持有 1 BTC、已确认 100 个区块、250 字节交易)
let priority = TxPriorityCalculator::calculate_priority(
100_000_000, // 1 BTC = 100,000,000 satoshi
100, // 100 个区块年龄
tx_size,
);
println!("优先级分数: {:.0}", priority); // 40,000,000
// 综合评分(矿工排序依据)
let score = TxPriorityCalculator::calculate_score(fee_rate, priority);
println!("综合评分: {:.2}", score);
}
完整使用示例
场景一:使用 RBF 加速未确认交易
#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{RBFManager, AdvancedTxBuilder, TxPriorityCalculator, FeeUrgency};
fn accelerate_tx_example() {
let mut rbf = RBFManager::new();
// 1. 发送原始交易(低手续费,支持 RBF)
let builder = AdvancedTxBuilder::new().with_rbf();
println!("RBF sequence: 0x{:08X}", builder.get_sequence()); // 0xFFFFFFFD
// 模拟交易被发送,但 30 分钟后仍未确认
// ... 创建并广播原始交易 original_tx ...
rbf.mark_replaceable("original_tx_id_001");
// 2. 网络拥堵,需要提高手续费
let tx_size = 250usize;
let old_fee = TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Low);
let new_fee = TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::High);
println!("原始手续费: {} sat -> 新手续费: {} sat", old_fee, new_fee);
// 3. 验证替换规则
// can_replace 会检查:输入相同、新费更高、增量足够
// match rbf.can_replace(&old_tx, &new_tx) {
// Ok(()) => { /* 广播新交易 */ }
// Err(e) => println!("替换被拒绝: {}", e),
// }
// 4. 旧交易确认(或被替换后)清理
rbf.remove_confirmed("original_tx_id_001");
}
}
场景二:定期存款时间锁
#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{AdvancedTxBuilder, TimeLock};
fn savings_timelock() {
// 锁定至区块高度 950,000(约 2028 年)
let unlock_height = 950_000u64;
let timelock = TimeLock::new_height_based(unlock_height);
let builder = AdvancedTxBuilder::new()
.with_timelock(timelock.clone());
println!("交易 sequence: 0x{:08X}", builder.get_sequence()); // 0x00000000
println!("启用时间锁: {}", builder.timelock.is_some());
// 检查当前是否可以动用资金
let current_height = 870_000u32;
if timelock.is_mature(0, current_height) {
println!("资金已解锁,可以使用");
} else {
let remaining = timelock.remaining(0, current_height);
println!("还需等待 {} 个区块(约 {} 天)",
remaining,
remaining * 10 / 60 / 24); // 约算天数
}
}
}
场景三:手续费策略分析
#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{TxPriorityCalculator, FeeUrgency};
fn fee_strategy_analysis() {
let tx_sizes = vec![
(125, "简单支付(1 输入 1 输出)"),
(250, "标准交易(1 输入 2 输出)"),
(500, "批量支付(多输入多输出)"),
(1000, "大型交易(SegWit 之前常见)"),
];
println!("{:<40} {:>10} {:>10} {:>10} {:>10}",
"交易类型", "Low", "Medium", "High", "Urgent");
println!("{}", "-".repeat(80));
for (size, desc) in &tx_sizes {
println!("{:<40} {:>10} {:>10} {:>10} {:>10}",
desc,
TxPriorityCalculator::recommend_fee(*size, FeeUrgency::Low),
TxPriorityCalculator::recommend_fee(*size, FeeUrgency::Medium),
TxPriorityCalculator::recommend_fee(*size, FeeUrgency::High),
TxPriorityCalculator::recommend_fee(*size, FeeUrgency::Urgent),
);
}
// 综合评分比较
let fee_rate_a = TxPriorityCalculator::calculate_fee_rate(500, 250); // 2 sat/byte
let fee_rate_b = TxPriorityCalculator::calculate_fee_rate(5000, 250); // 20 sat/byte
let priority_a = TxPriorityCalculator::calculate_priority(10_000_000, 50, 250);
let priority_b = TxPriorityCalculator::calculate_priority(100_000, 1, 250);
println!("\n交易A(老 UTXO,低费率)综合分: {:.2}", TxPriorityCalculator::calculate_score(fee_rate_a, priority_a));
println!("交易B(新 UTXO,高费率)综合分: {:.2}", TxPriorityCalculator::calculate_score(fee_rate_b, priority_b));
}
}
sequence 字段值对照
| sequence 值 | 含义 |
|---|---|
0xFFFFFFFF | 默认值,不启用 RBF 和时间锁 |
0xFFFFFFFE | 不支持 RBF,但允许 nLockTime |
0xFFFFFFFD | 支持 RBF(BIP125 标准值) |
0x00000000 | 启用时间锁(nLockTime 生效) |
相关模块
Mempool— 内存池使用RBFManager处理交易替换,使用TxPriorityCalculator排序待打包交易。MultiSig— 多签与时间锁可组合,实现“时间到期前需要 M-of-N,之后降为 1-of-N“等场景。- 高级模块概览 — 查看完整的高级模块依赖图。
REST API
SimpleBTC 提供完整的 RESTful API,允许通过 HTTP 与区块链系统进行交互。本页面详细描述所有端点、请求与响应格式,以及 cURL 调用示例。
基本信息
| 项目 | 说明 |
|---|---|
| 基础 URL | http://localhost:3000 |
| 协议 | HTTP/1.1 |
| 数据格式 | JSON |
| CORS | 允许所有来源(*) |
| 认证 | 无(演示版本) |
启动服务器
# 开发模式
cargo run --bin server
# Release 模式(性能更好)
cargo run --release --bin server
服务器启动后输出:
SimpleBTC Server v1.0
=====================
Web UI: http://localhost:3000
API: http://localhost:3000/api/blockchain/info
Genesis: <genesis_address> (pre-funded with 100 BTC)
Crypto: secp256k1 ECDSA (real Bitcoin signatures)
通用响应格式
所有端点均使用统一的 JSON 响应结构:
成功响应
{
"success": true,
"data": { },
"error": null
}
错误响应
{
"success": false,
"data": null,
"error": "错误描述信息"
}
HTTP 状态码
| 状态码 | 说明 |
|---|---|
| 200 | 请求成功 |
| 400 | 参数错误或业务逻辑失败 |
端点一览
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | / | 返回内嵌 Web UI |
| GET | /api/blockchain/info | 获取区块链状态信息 |
| GET | /api/blockchain/chain | 获取完整区块链数据 |
| GET | /api/blockchain/validate | 验证区块链完整性 |
| POST | /api/wallet/create | 创建新钱包 |
| GET | /api/wallet/balance/:address | 查询地址余额 |
| POST | /api/transaction/create | 创建转账交易 |
| POST | /api/mine | 挖矿(打包待确认交易) |
端点详解
GET /
返回内嵌的 Web UI 页面(HTML)。适合在浏览器中直接打开。
示例
curl http://localhost:3000/
GET /api/blockchain/info
获取区块链当前状态,包括高度、难度、待确认交易数量、挖矿奖励和创世地址。
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
height | number | 区块链高度(已包含区块总数) |
difficulty | number | 当前挖矿难度(哈希前导零个数) |
pending_transactions | number | 内存池中待确认交易数 |
mining_reward | number | 挖矿区块奖励(satoshi) |
genesis_address | string | 创世钱包地址(预置 100 BTC) |
响应示例
{
"success": true,
"data": {
"height": 3,
"difficulty": 4,
"pending_transactions": 1,
"mining_reward": 5000,
"genesis_address": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
},
"error": null
}
cURL 示例
curl http://localhost:3000/api/blockchain/info
GET /api/blockchain/chain
获取完整的区块链数据,包含每个区块的所有字段和交易列表。
区块字段
| 字段 | 类型 | 说明 |
|---|---|---|
index | number | 区块索引(从 0 开始) |
timestamp | number | 区块时间戳(毫秒) |
transactions | array | 交易列表 |
previous_hash | string | 前一区块哈希 |
hash | string | 本区块哈希 |
nonce | number | 工作量证明随机数 |
merkle_root | string | 交易的 Merkle 根 |
difficulty | number | 区块难度 |
交易字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 交易 ID(哈希) |
inputs | array | 交易输入列表(UTXO 引用) |
outputs | array | 交易输出列表(接收方与金额) |
timestamp | number | 交易时间戳 |
响应示例
{
"success": true,
"data": [
{
"index": 0,
"timestamp": 1700000000000,
"transactions": [],
"previous_hash": "0",
"hash": "0000abcdef...",
"nonce": 12345,
"merkle_root": "abc123...",
"difficulty": 4
},
{
"index": 1,
"timestamp": 1700000060000,
"transactions": [
{
"id": "txabc123...",
"inputs": [],
"outputs": [
{ "address": "a1b2c3...", "amount": 5000 }
],
"timestamp": 1700000059000
}
],
"previous_hash": "0000abcdef...",
"hash": "0000fedcba...",
"nonce": 67890,
"merkle_root": "def456...",
"difficulty": 4
}
],
"error": null
}
cURL 示例
# 获取完整区块链(配合 jq 格式化输出)
curl http://localhost:3000/api/blockchain/chain | jq
GET /api/blockchain/validate
验证区块链的完整性,检查所有区块的哈希链与工作量证明是否有效。
响应示例(验证通过)
{
"success": true,
"data": "Blockchain is valid",
"error": null
}
响应示例(验证失败)
{
"success": false,
"data": "Blockchain is invalid",
"error": null
}
cURL 示例
curl http://localhost:3000/api/blockchain/validate
POST /api/wallet/create
在服务器端生成新的 secp256k1 密钥对,返回钱包地址和公钥。私钥保存在服务器内存中,用于后续对交易进行 ECDSA 签名。
注意:私钥不会通过 API 返回。创建后的钱包地址可直接用于接收转账和发起交易。
请求体
无需请求体。
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
address | string | 钱包地址(40 字符十六进制) |
public_key | string | 压缩公钥(secp256k1) |
响应示例
{
"success": true,
"data": {
"address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a",
"public_key": "04d8c9e4b7f1a89c2d5e8f3b6a1c4e7d9b2a5c8f1a3d6e9b4c7f0a3d6e9b4c7f0"
},
"error": null
}
cURL 示例
curl -X POST http://localhost:3000/api/wallet/create
GET /api/wallet/balance/:address
查询指定钱包地址的当前余额,余额由 UTXO 集合计算得出。
路径参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
address | string | 是 | 钱包地址(40 字符十六进制) |
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
address | string | 查询的钱包地址 |
balance | number | 余额(satoshi,1 BTC = 100,000,000 satoshi) |
响应示例
{
"success": true,
"data": {
"address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a",
"balance": 10000000000
},
"error": null
}
cURL 示例
ADDRESS="3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a"
curl http://localhost:3000/api/wallet/balance/$ADDRESS
POST /api/transaction/create
创建一笔转账交易,使用发送方的私钥进行 secp256k1 ECDSA 签名,然后加入内存池等待挖矿确认。
前提:发送方地址必须是通过
/api/wallet/create创建的钱包(服务器持有其私钥才能签名)。
请求体
{
"from_address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a",
"to_address": "7c1e5b9f4a2d8e3c6f0b5a9d2e7f4c1b8a3d6e9f",
"amount": 5000,
"fee": 10
}
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
from_address | string | 是 | 发送方钱包地址 |
to_address | string | 是 | 接收方钱包地址 |
amount | number | 是 | 转账金额(satoshi) |
fee | number | 是 | 交易手续费(satoshi,归矿工所有) |
成功响应
{
"success": true,
"data": "Transaction created: txabc123def456...",
"error": null
}
错误响应示例
{
"success": false,
"data": null,
"error": "钱包未找到: 3f8a...。请先通过 /api/wallet/create 创建钱包。"
}
常见错误
| 错误 | 原因 | 解决方法 |
|---|---|---|
| 钱包未找到 | 发送方地址未在此服务器创建 | 先调用 /api/wallet/create |
| 余额不足 | 余额 < amount + fee | 减少金额,或先挖矿获得奖励 |
cURL 示例
curl -X POST http://localhost:3000/api/transaction/create \
-H "Content-Type: application/json" \
-d '{
"from_address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a",
"to_address": "7c1e5b9f4a2d8e3c6f0b5a9d2e7f4c1b8a3d6e9f",
"amount": 5000,
"fee": 10
}'
POST /api/mine
执行工作量证明挖矿,将内存池中所有待确认交易打包进新区块,并向矿工地址发放区块奖励。
注意:挖矿是 CPU 密集操作,根据当前难度可能耗时数秒。挖矿奖励(mining_reward)和所有交易手续费均归矿工地址所有,下一次挖矿后可查询到余额变化。
请求体
{
"miner_address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a"
}
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
miner_address | string | 是 | 矿工钱包地址(接收奖励) |
成功响应
{
"success": true,
"data": "Block mined! Height: 4",
"error": null
}
错误响应示例
{
"success": false,
"data": null,
"error": "No pending transactions"
}
cURL 示例
curl -X POST http://localhost:3000/api/mine \
-H "Content-Type: application/json" \
-d '{"miner_address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a"}'
快速入门:完整工作流
以下示例展示从创建钱包到完成转账的完整流程。
第一步:查看创世地址
创世钱包预置了 100 BTC(10,000,000,000 satoshi),可从 blockchain/info 中获取其地址。
# 获取创世地址
curl -s http://localhost:3000/api/blockchain/info | jq '.data.genesis_address'
# 示例输出: "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
GENESIS="a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
第二步:创建新钱包
# 创建钱包,保存地址
WALLET=$(curl -s -X POST http://localhost:3000/api/wallet/create)
echo $WALLET | jq
MY_ADDR=$(echo $WALLET | jq -r '.data.address')
echo "我的地址: $MY_ADDR"
第三步:从创世地址转账给新钱包
# 创世地址向新钱包转账 10,000 satoshi
curl -X POST http://localhost:3000/api/transaction/create \
-H "Content-Type: application/json" \
-d "{
\"from_address\": \"$GENESIS\",
\"to_address\": \"$MY_ADDR\",
\"amount\": 10000,
\"fee\": 100
}"
第四步:挖矿以确认交易
# 使用新钱包作为矿工地址(同时获得挖矿奖励)
curl -X POST http://localhost:3000/api/mine \
-H "Content-Type: application/json" \
-d "{\"miner_address\": \"$MY_ADDR\"}"
第五步:查询余额
# 查询新钱包余额(应包含转账金额 + 挖矿奖励)
curl http://localhost:3000/api/wallet/balance/$MY_ADDR | jq
第六步:验证区块链
# 确认区块链数据完整
curl http://localhost:3000/api/blockchain/validate | jq
完整脚本
#!/bin/bash
BASE="http://localhost:3000"
echo "=== SimpleBTC 快速体验 ==="
# 1. 获取创世地址
GENESIS=$(curl -s $BASE/api/blockchain/info | jq -r '.data.genesis_address')
echo "创世地址: $GENESIS"
# 2. 创建新钱包
MY_ADDR=$(curl -s -X POST $BASE/api/wallet/create | jq -r '.data.address')
echo "新钱包地址: $MY_ADDR"
# 3. 创建交易(创世地址 → 新钱包,转账 10000 satoshi)
TX=$(curl -s -X POST $BASE/api/transaction/create \
-H "Content-Type: application/json" \
-d "{\"from_address\":\"$GENESIS\",\"to_address\":\"$MY_ADDR\",\"amount\":10000,\"fee\":100}")
echo "交易: $(echo $TX | jq -r '.data')"
# 4. 挖矿确认
MINE=$(curl -s -X POST $BASE/api/mine \
-H "Content-Type: application/json" \
-d "{\"miner_address\":\"$MY_ADDR\"}")
echo "挖矿: $(echo $MINE | jq -r '.data')"
# 5. 查询余额
BAL=$(curl -s $BASE/api/wallet/balance/$MY_ADDR | jq '.data.balance')
echo "余额: $BAL satoshi"
# 6. 验证区块链
VALID=$(curl -s $BASE/api/blockchain/validate | jq -r '.data')
echo "验证: $VALID"
单位说明
SimpleBTC 所有金额字段均以 satoshi 为单位(与 Bitcoin 保持一致):
| 单位 | 换算 |
|---|---|
| 1 BTC | 100,000,000 satoshi |
| 1 mBTC | 100,000 satoshi |
| 1 satoshi | 最小单位,不可再分 |
创世钱包预置余额为 10,000,000,000 satoshi(100 BTC)。挖矿奖励默认为 5000 satoshi。
术语表
比特币和区块链相关的核心术语解释。
A
Address(地址)
用于接收比特币的唯一标识符。由公钥通过哈希和编码生成。
示例: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
类型:
- P2PKH(以1开头):传统地址
- P2SH(以3开头):脚本/多签地址
- Bech32(以bc1开头):SegWit地址
ACID
数据库事务的四个特性:
- Atomicity(原子性): 全部成功或全部失败
- Consistency(一致性): 保持数据一致状态
- Isolation(隔离性): 并发事务互不影响
- Durability(持久性): 已提交的持久保存
SimpleBTC的交易处理符合ACID特性。
B
Block(区块)
包含交易列表的数据结构,通过哈希链接形成区块链。
包含:
- 区块头(index, timestamp, hash, previous_hash, merkle_root, nonce)
- 交易列表(第一笔是Coinbase)
大小: 比特币约1-4MB
Blockchain(区块链)
按时间顺序链接的区块序列,通过密码学确保不可篡改。
特性:
- 去中心化
- 不可篡改
- 透明可验证
- 无需信任第三方
Block Height(区块高度)
区块在链中的位置。创世区块高度为0。
示例: 当前区块链有100个区块,最新区块高度为99。
BIP (Bitcoin Improvement Proposal)
比特币改进提案,用于提出比特币协议的改进。
重要BIP:
- BIP11: M-of-N多签
- BIP16: P2SH(脚本哈希支付)
- BIP32: 分层确定性钱包
- BIP39: 助记词
- BIP125: RBF(替换手续费)
C
Coinbase Transaction
区块的第一笔交易,用于向矿工发放奖励。
特点:
- 无有效输入(不消费UTXO)
- 创造新的比特币
- 包含区块奖励 + 交易手续费
示例:
#![allow(unused)]
fn main() {
Transaction::new_coinbase(
miner_address,
50, // 区块奖励
timestamp,
total_fees, // 手续费总和
)
}
Cold Wallet(冷钱包)
离线存储私钥的钱包,不连接互联网。
类型:
- 硬件钱包(Ledger, Trezor)
- 纸钱包
- 气隙计算机
优势: 极高的安全性 劣势: 使用不便
Confirmation(确认)
交易被包含在区块中并被后续区块延续的次数。
确认数:
- 0确认:在待处理池,未打包
- 1确认:已打包到区块
- 6确认:非常安全(比特币标准)
时间: 比特币约10分钟/确认
D
Difficulty(难度)
挖矿的计算难度,决定了找到有效区块哈希的难度。
SimpleBTC:
#![allow(unused)]
fn main() {
blockchain.difficulty = 3; // 3个前导0
}
比特币: 动态调整,每2016个区块(约2周)调整一次,目标是保持10分钟出块时间。
Double Spending(双花)
试图将同一笔比特币花费两次的攻击。
防御机制:
- UTXO模型(每个UTXO只能花费一次)
- 区块确认(6确认后几乎不可能)
- 工作量证明(需要51%算力重写历史)
E
ECDSA (Elliptic Curve Digital Signature Algorithm)
椭圆曲线数字签名算法,比特币使用的签名方案。
曲线: secp256k1
过程:
私钥 → ECDSA → 公钥 → Hash → 地址
SimpleBTC使用简化的SHA256签名。
F
Fee(手续费)
支付给矿工的费用,激励其打包交易。
计算:
手续费 = 输入总额 - 输出总额
费率:
费率 = 手续费 / 交易大小(sat/byte)
推荐:
- 低: 1-5 sat/byte
- 中: 10-20 sat/byte
- 高: 50+ sat/byte
Fork(分叉)
区块链出现多个有效分支。
类型:
- 暂时性分叉: 两个矿工同时挖出区块,最长链原则解决
- 硬分叉: 协议不兼容升级(如BCH)
- 软分叉: 向后兼容升级(如SegWit)
G
Genesis Block(创世区块)
区块链的第一个区块,索引为0。
比特币创世区块:
- 日期: 2009年1月3日
- 奖励: 50 BTC(无法花费)
- 消息: “The Times 03/Jan/2009 Chancellor on brink of second bailout for banks”
SimpleBTC:
#![allow(unused)]
fn main() {
fn create_genesis_block() {
// 创建索引为0的区块
// previous_hash = "0"
}
}
H
Hash(哈希)
将任意数据转换为固定长度字符串的函数。
比特币使用:
- SHA256(交易ID、区块哈希)
- RIPEMD160(地址生成)
特性:
- 确定性
- 单向性
- 抗碰撞
- 雪崩效应
示例:
SHA256("hello") = 2cf24dba5fb0a30e...
Hash Rate(算力)
每秒计算哈希的次数。
单位:
- H/s (hashes per second)
- KH/s = 1,000 H/s
- MH/s = 1,000,000 H/s
- GH/s = 1,000,000,000 H/s
- TH/s = 1,000,000,000,000 H/s
- EH/s = 1,000,000,000,000,000,000 H/s
比特币全网: 约300+ EH/s
Hot Wallet(热钱包)
连接互联网的钱包,便于日常使用。
类型:
- 手机钱包
- 桌面钱包
- Web钱包
优势: 使用方便 劣势: 安全性较低
M
Merkle Tree(Merkle树)
交易的二叉哈希树,根哈希存储在区块头。
结构:
Root
/ \
H12 H34
/ \ / \
H1 H2 H3 H4
用途:
- SPV轻量级验证
- 证明交易存在于区块中
- O(log n)验证复杂度
Mining(挖矿)
通过工作量证明创建新区块的过程。
步骤:
- 收集待处理交易
- 创建Coinbase交易
- 计算Merkle根
- 调整nonce寻找有效哈希
- 广播区块
奖励: 区块奖励 + 交易手续费
Multisig (M-of-N)
需要M个签名(共N个密钥)才能花费的地址。
示例:
- 2-of-3: CEO + CFO + CTO,任意2人即可
- 3-of-5: 董事会5人,需要3人同意
地址: 以“3“开头(P2SH)
N
Node(节点)
运行比特币客户端软件的计算机。
类型:
- 全节点: 存储完整区块链,验证所有交易
- 轻节点: 只存储区块头,使用SPV验证
- 矿工节点: 参与挖矿的全节点
Nonce
挖矿时调整的随机数,用于改变区块哈希。
作用: 工作量证明
#![allow(unused)]
fn main() {
while hash(block_data + nonce) >= target {
nonce++; // 不断尝试
}
}
P
P2P (Peer-to-Peer)
点对点网络,节点之间直接通信,无需中心服务器。
比特币网络:
- 去中心化
- 抗审查
- 无单点故障
P2PKH (Pay-to-Public-Key-Hash)
传统的比特币地址类型,以“1“开头。
流程:
公钥 → SHA256 → RIPEMD160 → Base58 → 地址
P2SH (Pay-to-Script-Hash)
脚本哈希支付,用于多签等高级功能,以“3“开头。
优势:
- 支持复杂脚本
- 隐藏脚本细节
- 费用由接收方承担
Private Key(私钥)
用于签名交易的秘密数字。
特性:
- 256位随机数
- 拥有私钥 = 拥有比特币
- 丢失不可恢复
保护:
- 永不分享
- 加密存储
- 多重备份
Proof of Work (PoW)
工作量证明,比特币的共识机制。
原理: 找到满足难度的哈希值需要大量计算
目的:
- 防止垃圾攻击
- 去中心化共识
- 51%攻击成本极高
Public Key(公钥)
从私钥派生的公开数字,用于生成地址和验证签名。
推导:
私钥 → 椭圆曲线运算 → 公钥 → 哈希 → 地址
R
RBF (Replace-By-Fee)
允许替换未确认交易的机制(BIP125)。
用途:
- 加速交易(提高手续费)
- 取消交易
- 批量优化
标记: nSequence < 0xFFFFFFFE
S
Satoshi (sat)
比特币的最小单位。
1 BTC = 100,000,000 satoshi
1 sat = 0.00000001 BTC
命名: 以比特币创始人Satoshi Nakamoto命名
Script
比特币的脚本语言,定义花费条件。
操作码:
- OP_DUP
- OP_HASH160
- OP_EQUALVERIFY
- OP_CHECKSIG
- OP_CHECKMULTISIG
SimpleBTC使用简化版本。
SPV (Simplified Payment Verification)
轻量级验证,无需下载完整区块链。
原理: 使用Merkle证明验证交易
优势:
- 只需区块头(~80字节)
- 手机钱包可用
- O(log n)验证
T
Timelock (nLockTime)
限制交易在特定时间前不能被确认。
类型:
- 时间戳(≥ 500,000,000)
- 区块高度(< 500,000,000)
应用:
- 定期存款
- 遗产继承
- 工资发放
Transaction (TX)
价值转移的基本单位。
包含:
- 输入(花费哪些UTXO)
- 输出(创建哪些新UTXO)
- 时间戳
- 手续费
U
UTXO (Unspent Transaction Output)
未花费的交易输出,代表可以被花费的比特币。
生命周期:
- 创建(交易输出)
- 存在(UTXO集合)
- 花费(交易输入引用)
- 移除(从UTXO集合删除)
余额: 所有UTXO的总和
W
Wallet(钱包)
管理私钥、公钥和地址的软件。
类型:
- 热钱包(联网)
- 冷钱包(离线)
- 硬件钱包
- 纸钱包
功能:
- 生成密钥对
- 创建地址
- 签名交易
- 查询余额
数字
51% Attack
攻击者控制超过50%算力,可以重写区块链历史。
后果:
- 双花攻击
- 阻止交易确认
防御: 比特币算力太大,攻击成本极高
6 Confirmations
比特币交易的标准安全确认数。
时间: 约60分钟(6个区块 × 10分钟)
原因: 6个区块后,重写历史几乎不可能
参考资源
比特币原理
本附录是独立的教育性文章,面向希望理解比特币底层原理的读者。阅读本章无需任何编程基础,但对密码学和分布式系统有一定了解将有所帮助。
引言
2008年10月31日,一位化名“中本聪“(Satoshi Nakamoto)的人在一个密码学邮件列表中发布了一篇9页的论文:《比特币:一种点对点的电子现金系统》。这篇论文提出了一个革命性的问题的解答:如何在没有可信第三方(如银行)的情况下,实现两个陌生人之间的价值转移?
比特币的答案建立在四个核心技术支柱之上:哈希函数、公钥密码学、工作量证明和区块链数据结构。本章将逐一介绍这些原理。
一、哈希函数与SHA-256
什么是哈希函数?
哈希函数(Hash Function)是一种将任意长度的数据映射为固定长度“摘要“的数学函数。比特币使用的是SHA-256(Secure Hash Algorithm 256-bit),输出始终为256位(32字节,通常以64个十六进制字符表示)。
SHA-256("Hello, Bitcoin!") =
a3b5c7d2e1f0... (64个十六进制字符)
SHA-256("Hello, Bitcoin.") =
f9e8d7c6b5a4... (完全不同的哈希)
哈希函数的四个关键属性
1. 确定性(Deterministic) 相同的输入永远产生相同的输出。没有随机性。
2. 雪崩效应(Avalanche Effect) 输入的微小变化(哪怕只改变一个比特)会导致输出发生巨大且不可预测的变化。这确保了哈希值对内容的微小修改高度敏感。
3. 单向性(One-Way / Preimage Resistance) 从哈希值反推原始输入在计算上不可行。即使知道SHA-256的输出,也无法在宇宙寿命内通过暴力枚举恢复输入(搜索空间为2²⁵⁶)。
4. 抗碰撞性(Collision Resistance) 找到两个不同的输入产生相同输出(哈希碰撞)在计算上不可行。这是比特币区块链不可篡改性的基础。
比特币中的SHA-256应用
比特币在多个地方使用SHA-256(或双重SHA-256,即SHA-256(SHA-256(data))):
| 应用场景 | 哈希方式 | 用途 |
|---|---|---|
| 区块哈希 | SHA-256(SHA-256(区块头)) | 区块的唯一标识,连接区块链 |
| 交易ID | SHA-256(SHA-256(交易数据)) | 交易的唯一标识 |
| 地址生成 | RIPEMD-160(SHA-256(公钥)) | 从公钥生成比特币地址 |
| Merkle树 | SHA-256(SHA-256(节点拼接)) | 高效验证交易集合 |
| 挖矿 | SHA-256(SHA-256(区块头)) | 找到满足难度要求的nonce |
为什么双重SHA-256?
比特币使用两次SHA-256而非一次,主要是为了对抗长度扩展攻击(Length Extension Attack)。SHA-256的数学结构存在一个弱点:知道SHA-256(M)后,可以在不知道M的情况下计算SHA-256(M || X)(X是任意附加数据)。双重SHA-256消除了这个安全隐患。
二、公钥密码学
对称加密 vs 非对称加密
传统的对称加密(如AES)使用同一把密钥加密和解密。问题在于:Alice要给Bob加密发消息,需要先安全地把密钥传给Bob——但如果有安全信道传密钥,为什么不直接用这个信道传消息呢?
非对称加密(公钥密码学)解决了这个“密钥分发问题“。每个用户有两把密钥:
- 公钥(Public Key):可以公开分享给任何人
- 私钥(Private Key):必须严格保密,绝不分享
它们的关系是:从私钥可以推导出公钥,但从公钥无法反推私钥。
私钥(随机的256位数)
│
▼ (单向,不可逆)
公钥(椭圆曲线上的点)
│
▼ (单向,不可逆)
比特币地址(公钥哈希)
椭圆曲线密码学(ECC)与secp256k1
比特币使用椭圆曲线数字签名算法(ECDSA),具体使用名为secp256k1的椭圆曲线。这条曲线由方程定义:
y² = x³ + 7 (在有限域 Fp 上,p = 2²⁵⁶ - 2³² - 977)
椭圆曲线密码学的安全性基于椭圆曲线离散对数问题(ECDLP):给定曲线上的点G(生成元)和点P = k·G,从P反推整数k在计算上不可行。
为什么选择secp256k1而不是更常见的secp256r1(NIST P-256)?
中本聪选择secp256k1的参数并非随机生成,而是来自确定性公式,这使得它不太可能被美国国家安全局(NSA)预置后门——这在密码学社区是一个有争议但值得考虑的担忧。secp256k1的系数非常简单(a=0, b=7),没有复杂的“看似随机“的参数,透明度更高。
密钥生成过程:
1. 生成私钥:随机选取一个256位整数 k(1 ≤ k ≤ n-1,n是曲线阶数)
私钥 = 随机数 k(通常从加密安全的随机数生成器获得)
2. 生成公钥:计算椭圆曲线上的点乘法
公钥 = k × G(G是secp256k1的标准基点)
注意:点乘是椭圆曲线上定义的特殊运算,不是普通乘法
3. 生成地址(简化版):
地址 = RIPEMD-160(SHA-256(公钥)) + 校验和
私钥的随机性至关重要。有据可查的盗币案例显示,使用弱随机数生成器(如时间戳)的私钥曾被暴力破解。真正安全的私钥来自操作系统的密码学随机数接口(如Linux的/dev/urandom)。
三、数字签名
签名的作用
数字签名解决了比特币中最核心的问题:如何证明你有权花费某笔钱,同时不透露你的私钥?
类比现实世界:你在支票上签名,银行验证签名是否为你本人所写。数字签名是这一过程的密码学等价物,但更安全——它不依赖签名的视觉外观(可被仿造),而依赖数学上无法伪造的密码学证明。
ECDSA签名过程
签名(Signing):
输入:消息M(交易数据的哈希)、私钥 k
输出:签名 (r, s)
步骤:
1. 生成随机数 r_rand(每次签名都必须不同!)
2. 计算曲线点 R = r_rand × G
3. r = R.x mod n(取R的x坐标)
4. s = r_rand⁻¹ × (hash(M) + k × r) mod n
验证(Verification):
输入:消息M、签名 (r, s)、公钥 P = k × G
输出:有效 / 无效
步骤:
1. u1 = hash(M) × s⁻¹ mod n
2. u2 = r × s⁻¹ mod n
3. 计算点 Q = u1 × G + u2 × P
4. 验证 Q.x mod n == r
验证过程只使用公钥,不需要私钥。这意味着任何人都可以验证签名,但只有持有私钥的人才能创建有效签名。
关键安全要求:随机数不可重用
签名算法中的随机数r_rand每次签名必须唯一且不可预测。2013年,PlayStation 3的ECDSA实现因使用了固定的随机数而被破解,导致私钥泄露。比特币历史上也有类似案例。
现代实现(包括比特币核心)使用RFC 6979,从私钥和消息确定性地生成随机数,彻底消除了随机数重用的风险。
四、工作量证明共识
拜占庭将军问题
在分布式系统中,如果节点可能发送错误信息(恶意或故障),如何达成一致?这被称为拜占庭将军问题(Byzantine Generals Problem),由Lamport、Shostak和Pease于1982年正式提出。
经典结论:在传统消息传递模型中,若有f个拜占庭节点,需要至少3f+1个总节点才能容忍故障。然而,这个结论有个前提:通信成本可忽略。
中本聪的洞见在于:在比特币中,加入网络发言需要付出真实的物理成本(电力),这从根本上改变了博弈论均衡。
工作量证明(Proof of Work)
工作量证明要求矿工找到一个特殊的数(nonce),使得区块头的哈希值满足特定条件(以若干个0开头):
目标:SHA-256(SHA-256(区块头)) < 目标值
等价地:区块头哈希以difficulty个0开头
示例(difficulty=4):
0000a3f7d2e1b5c8... ← 有效(以4个0开头)
0001a3f7d2e1b5c8... ← 无效(第4位不是0)
矿工反复修改nonce并重新计算哈希,直到找到满足条件的值:
while SHA-256(SHA-256(块头 || nonce)) >= 目标值:
nonce += 1 // 尝试下一个随机数
找到后广播这个区块给全网
难度调整:比特币每2016个区块(约两周)自动调整难度,目标是使平均出块时间维持在10分钟。若算力增加,难度上升;若算力减少,难度下降。
PoW为什么能防止双花?
假设攻击者试图双花:
- 向商家发送交易A(支付10 BTC)
- 商家等待N个区块确认后发货
- 攻击者秘密在另一条链上挖矿,创建包含交易B(把10 BTC转回自己)的区块
攻击者需要在诚实矿工已挖了N个区块的情况下,悄悄挖出更长的链来超越诚实链。若攻击者掌握的算力比例为α(α < 0.5),其成功概率随N的增加指数衰减。中本聪在白皮书中证明:
P(成功) ≈ (α / (1-α))^N
当α = 0.3(30%算力)、N = 6(6个确认,约1小时)时:
P(成功) ≈ (0.3/0.7)^6 = (0.4286)^6 ≈ 0.0006 = 0.06%
这就是比特币“6个确认“规则的数学依据。
PoW vs 其他共识机制
| 共识机制 | 代表项目 | 优点 | 缺点 |
|---|---|---|---|
| 工作量证明(PoW) | 比特币、莱特币 | 无需信任参与者,抗女巫攻击 | 能耗高,出块慢 |
| 权益证明(PoS) | 以太坊2.0、Cardano | 能耗低,可扩展性好 | 初始分发公平性问题,“无利害关系“问题 |
| 委托权益证明(DPoS) | EOS、Tron | 高吞吐量 | 中心化风险,21个节点 |
| 实用拜占庭容错(PBFT) | Hyperledger | 高效,有最终确定性 | 仅适合已知参与者的联盟链 |
五、区块链数据结构
区块的结构
每个区块由两部分组成:
区块头(80字节):
版本号 (4字节) - 协议版本
前块哈希 (32字节) - 将此区块与前一个区块链接
Merkle根 (32字节) - 所有交易的Merkle树根哈希
时间戳 (4字节) - Unix时间戳
难度目标 (4字节) - 当前挖矿难度(compact格式)
随机数Nonce (4字节) - 矿工调整的值
区块体(可变大小):
交易计数 (varint)
交易列表 [Transaction...]
├── 交易1(coinbase,矿工奖励)
├── 交易2
└── ...
链式结构与不可篡改性
区块链的“链“来自于每个区块头包含前一个区块的哈希:
区块0(创世区块) 区块1 区块2
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ prev_hash: 0000 │ │ prev_hash: H(B0) │ │ prev_hash: H(B1) │
│ merkle_root: ... │◄───│ merkle_root: ... │◄───│ merkle_root: ... │
│ nonce: 2083236893│ │ nonce: 12345678 │ │ nonce: 87654321 │
│ hash: H(B0) │ │ hash: H(B1) │ │ hash: H(B2) │
└──────────────────┘ └──────────────────┘ └──────────────────┘
如果攻击者修改了区块1中的某笔交易:
- 区块1的Merkle根会改变
- 区块1的区块头哈希会改变
- 区块2的
prev_hash字段不再匹配区块1的新哈希 - 攻击者必须重新挖矿计算区块2的nonce
- 这会导致区块3也失效,需要重新挖矿……
- 攻击者需要重新计算从被篡改区块到链尾的所有区块的PoW
由于诚实矿工在持续延长链,攻击者需要以超越全网算力的速度完成这项工作。在51%诚实算力的假设下,这是不可能完成的任务。
UTXO集:区块链的“状态“
完整区块链记录了所有历史交易,但验证新交易只需要知道“当前哪些输出还未被花费“——即UTXO集(Unspent Transaction Output Set)。
UTXO集是从创世区块开始,顺序处理所有交易后得到的“账本状态“。截至2024年,比特币UTXO集包含约1.1亿个条目,占用约5-6 GB内存,比600+ GB的完整区块链小得多。
区块链(历史记录,约600 GB)
↓ 全节点顺序处理
UTXO集(当前状态,约5 GB)
↓ 查询
验证新交易是否有效
六、去中心化与网络安全
P2P网络
比特币节点通过点对点(P2P)网络相互连接,没有中心服务器。每个节点与几十个对等节点建立连接,形成一个弱小世界网络(Small World Network)。
新交易通过Gossip协议在网络中传播:
- 节点A创建交易,广播给连接的节点
- 每个收到交易的节点验证后,转发给自己的连接节点
- 交易在数秒内扩散至全球大多数节点
51%攻击的经济学分析
攻击比特币网络需要控制超过50%的全网算力。截至2024年,比特币全网算力约为600 EH/s(每秒60京次哈希计算)。购买或租用50%的算力需要数十亿美元的硬件投入,加上持续的电力消耗。
更关键的是攻击的经济激励问题:
- 成功攻击所能获得的收益:双花一次大额交易,可能欺骗交易所
- 攻击的代价:比特币价格崩溃,攻击者持有的比特币和矿机价值暴跌
- 结论:理性的经济行为者更愿意用算力诚实挖矿(每天约2000万美元收益),而非发动收益有限、风险极大的攻击
这种经济上的自我强化安全性是比特币设计的精妙之处——安全性随着网络价值的增加而自动增强。
节点类型与网络分工
| 节点类型 | 说明 | 典型场景 |
|---|---|---|
| 全节点 | 存储完整区块链,独立验证所有规则 | 交易所、节点运营商 |
| 修剪节点 | 仅保留近期区块,节省磁盘 | 家庭用户 |
| SPV节点 | 仅下载区块头,轻量验证 | 手机钱包 |
| 矿池节点 | 协调大量矿工,分配算力 | 矿场运营商 |
| 闪电网络节点 | 管理支付通道,实现即时小额支付 | 商户、日常支付 |
深入阅读与参考文献
以下是深入理解比特币技术原理的重要文献:
基础论文
-
Nakamoto, S. (2008). Bitcoin: A Peer-to-Peer Electronic Cash System. https://bitcoin.org/bitcoin.pdf 比特币白皮书,9页,涵盖所有核心概念。必读。
-
Merkle, R. C. (1979). Secrecy, Authentication, and Public Key Systems. 斯坦福大学博士论文,Merkle树的原始论文。
-
Lamport, L., Shostak, R., & Pease, M. (1982). The Byzantine Generals Problem. ACM Transactions on Programming Languages and Systems. 分布式共识问题的经典形式化描述。
-
Back, A. (2002). Hashcash – A Denial of Service Counter-Measure. http://www.hashcash.org/papers/hashcash.pdf 比特币PoW机制的前身,最初设计用于防止垃圾邮件。
-
Dai, W. (1998). b-money. http://www.weidai.com/bmoney.txt 中本聪引用的早期去中心化数字货币方案。
密码学基础
-
Johnson, D., Menezes, A., & Vanstone, S. (2001). The Elliptic Curve Digital Signature Algorithm (ECDSA). International Journal of Information Security. ECDSA的权威技术规范。
-
Pornin, T. (2013). RFC 6979: Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA). IETF Request for Comments. 确定性签名随机数生成的标准,消除了随机数重用漏洞。
-
National Institute of Standards and Technology. (2015). FIPS PUB 180-4: Secure Hash Standard. SHA-256的官方规范文档。
书籍
-
Antonopoulos, A. M. (2017). Mastering Bitcoin: Programming the Open Blockchain (2nd ed.). O’Reilly Media. 比特币技术最全面的入门书,开源版本免费可读。
-
Song, J. (2019). Programming Bitcoin. O’Reilly Media. 从零开始用Python实现比特币协议,适合动手学习。
-
Narayanan, A., Bonneau, J., Felten, E., Miller, A., & Goldfeder, S. (2016). Bitcoin and Cryptocurrency Technologies. Princeton University Press. 免费PDF版本,学术视角的全面分析。
进阶资源
-
Bitcoin Improvement Proposals (BIPs). https://github.com/bitcoin/bips 比特币协议的所有改进提案,包括SegWit(BIP141)、RBF(BIP125)、HD钱包(BIP32)等。
-
Bitcoin Core源代码. https://github.com/bitcoin/bitcoin 比特币的参考实现,C++编写,约15万行代码。
总结:比特币的五层架构
Layer 5: 经济激励层
PoW奖励(区块奖励 + 手续费)→ 驱动矿工诚实行为
↑
Layer 4: 共识层
最长链规则 + PoW难度调整 → 全网对"哪条链是正确的"达成共识
↑
Layer 3: 数据结构层
区块链(链式区块)+ Merkle树 → 不可篡改的交易历史
↑
Layer 2: 交易层
UTXO模型 + 脚本系统 → 定义价值转移的规则
↑
Layer 1: 密码学层
SHA-256(完整性)+ ECDSA(身份认证)→ 无需信任的数学保证
比特币的革命性不在于任何单一技术创新——SHA-256、椭圆曲线密码、P2P网络、哈希链、PoW此前都已存在。中本聪的天才在于将这些已知技术以特定方式组合,创造出一个自洽的、在博弈论上稳定的去中心化货币系统。
SimpleBTC项目实现了这个系统的教学版本,帮助你通过可运行的代码理解每一层的工作原理。建议结合源代码阅读本文,将理论与实践相结合。
常见问题(FAQ)
安装和配置
Q: 如何安装SimpleBTC?
A:
# 1. 确保已安装Rust
rustc --version
# 2. 克隆项目
git clone https://github.com/GeoffreyWang1117/SimpleBTC.git
cd SimpleBTC
# 3. 编译
cargo build --release
# 4. 运行
cargo run --bin btc-demo
详见安装指南
Q: 编译时报错 “linker ‘cc’ not found”
A: 需要安装C编译器:
# Ubuntu/Debian
sudo apt-get install build-essential
# macOS
xcode-select --install
# Windows
# 安装Visual Studio Build Tools
Q: 如何修改挖矿难度?
A: 在 src/blockchain.rs 中修改:
#![allow(unused)]
fn main() {
pub fn new() -> Blockchain {
Blockchain {
difficulty: 3, // 修改这个值
// 3-4 适合演示
// 5-6 更安全但慢
// ...
}
}
}
基础概念
Q: 什么是UTXO?为什么不是账户余额?
A: UTXO(Unspent Transaction Output)是比特币的核心概念。
账户模型(以太坊):
Alice账户: 100 BTC
转账后:
Alice: 70 BTC
Bob: 30 BTC
UTXO模型(比特币):
Alice有UTXO: [50 BTC, 30 BTC, 20 BTC]
转账30 BTC给Bob:
- 消费50 BTC的UTXO
- 创建30 BTC给Bob
- 创建20 BTC找零给Alice(50-30)
优势:
- ✅ 更好的隐私性(每次用新地址)
- ✅ 并行处理(不同UTXO可并发)
- ✅ 更简单的验证逻辑
Q: 为什么交易需要手续费?
A: 手续费的作用:
- 防止垃圾攻击 - 发送交易有成本
- 激励矿工 - 矿工优先打包高费率交易
- 资源分配 - 网络拥堵时,愿意支付更高费用的优先
手续费计算:
#![allow(unused)]
fn main() {
手续费 = 输入总额 - 输出总额
// 示例
输入: 100 satoshi
输出: 90 satoshi
手续费: 10 satoshi
}
费率建议:
- 1-5 sat/byte: 低优先级(数小时)
- 10-20 sat/byte: 中优先级(30-60分钟)
- 50+ sat/byte: 高优先级(下一个区块)
Q: 什么是工作量证明(PoW)?为什么需要挖矿?
A: PoW是比特币的共识机制。
挖矿过程:
#![allow(unused)]
fn main() {
target = "000..." // 难度要求
while hash(block_data + nonce) >= target {
nonce++; // 不断尝试
}
// 找到有效nonce,区块被接受
}
为什么需要:
- 防止垃圾区块(创建区块需要计算成本)
- 去中心化共识(算力投票)
- 51%攻击成本极高(需要超过全网一半算力)
难度与时间:
- 难度3: 毫秒级(demo)
- 难度10: 秒级(私有链)
- 难度20: 分钟级(比特币级别)
使用问题
Q: 如何创建钱包?
A:
#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;
// 创建新钱包
let wallet = Wallet::new();
println!("地址: {}", wallet.address);
println!("公钥: {}", wallet.public_key);
// 私钥要保密!
}
重要:
- 私钥丢失 = 比特币永久丢失
- 私钥泄露 = 比特币被盗
- 建议备份私钥到安全的地方
Q: 如何转账?
A:
#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};
let mut blockchain = Blockchain::new();
let alice = Wallet::new();
let bob = Wallet::new();
// 1. 创建交易
let tx = blockchain.create_transaction(
&alice, // 发送者
bob.address, // 接收者
1000, // 金额(satoshi)
10, // 手续费
)?;
// 2. 添加到待处理池
blockchain.add_transaction(tx)?;
// 3. 挖矿确认
blockchain.mine_pending_transactions(miner.address)?;
}
Q: 余额不足怎么办?
A: 检查以下几点:
- 查询余额:
#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&address);
println!("余额: {}", balance);
}
- 确保有UTXO:
#![allow(unused)]
fn main() {
let utxos = blockchain.utxo_set.find_utxos(&address);
println!("UTXO数量: {}", utxos.len());
}
- 检查是否包含手续费:
#![allow(unused)]
fn main() {
let total_needed = amount + fee;
if balance < total_needed {
return Err("余额不足(包括手续费)");
}
}
- 等待交易确认: 刚发送的交易需要挖矿确认后才能使用。
Q: 交易长时间未确认怎么办?
A: 可能原因和解决方案:
原因1:手续费太低
#![allow(unused)]
fn main() {
// 提高手续费
let tx = blockchain.create_transaction(
&alice,
bob.address,
1000,
50, // 提高手续费
)?;
}
原因2:没有矿工挖矿
# 手动挖矿
cargo run --bin btc-demo
# 或在代码中
blockchain.mine_pending_transactions(miner.address)?;
原因3:交易无效
#![allow(unused)]
fn main() {
// 验证交易
if !tx.verify() {
println!("交易无效,检查:");
println!("- 输入UTXO是否存在");
println!("- 签名是否正确");
println!("- 余额是否足够");
}
}
使用RBF加速:
#![allow(unused)]
fn main() {
// 创建更高费率的替换交易
let faster_tx = blockchain.create_transaction(
&alice,
bob.address,
1000,
100, // 更高的手续费
)?;
}
高级功能
Q: 如何使用多重签名?
A:
#![allow(unused)]
fn main() {
use bitcoin_simulation::multisig::MultiSigAddress;
// 1. 创建参与者钱包
let alice = Wallet::new();
let bob = Wallet::new();
let charlie = Wallet::new();
// 2. 创建2-of-3多签地址
let multisig = MultiSigAddress::new(
2, // 需要2个签名
vec![
alice.public_key,
bob.public_key,
charlie.public_key,
]
)?;
// 3. 向多签地址发送资金
let tx = blockchain.create_transaction(
&funder,
multisig.address.clone(),
10000,
0,
)?;
// 4. 从多签地址支出(需要2个签名)
let alice_sig = alice.sign(&payment_data);
let bob_sig = bob.sign(&payment_data);
if vec![alice_sig, bob_sig].len() >= multisig.required_sigs {
// 执行交易
}
}
详见多重签名教程
Q: 什么是Merkle树?有什么用?
A: Merkle树是交易的哈希树,存储在区块头中。
结构:
Root Hash
/ \
H(AB) H(CD)
/ \ / \
H(A) H(B) H(C) H(D)
tx1 tx2 tx3 tx4
用途:
- SPV验证 - 轻钱包无需下载完整区块
#![allow(unused)]
fn main() {
// 只需区块头 + Merkle证明
let proof = merkle_tree.get_proof(&tx_hash)?;
let valid = MerkleTree::verify_proof(
&tx_hash,
&proof,
&block.merkle_root,
tx_index
);
}
-
数据完整性 - 任何交易改变都会改变根哈希
-
高效验证 - O(log n)复杂度
Q: 什么是时间锁?如何使用?
A: 时间锁限制交易在特定时间前不能被确认。
两种类型:
- 基于时间戳:
#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::TimeLock;
// 3个月后解锁
let three_months = 90 * 24 * 3600;
let unlock_time = current_time + three_months;
let timelock = TimeLock::new_time_based(unlock_time);
// 检查是否到期
if timelock.is_mature(current_time, 0) {
println!("已到期,可以使用");
}
}
- 基于区块高度:
#![allow(unused)]
fn main() {
// 在第100,000个区块后解锁
let timelock = TimeLock::new_block_based(100000);
if timelock.is_mature(current_time, current_block_height) {
println!("区块高度已达到");
}
}
应用场景:
- 定期存款
- 遗产继承
- 工资发放
- 项目锁定期
详见时间锁教程
开发问题
Q: 如何集成SimpleBTC到我的项目?
A: SimpleBTC可以作为库使用:
# Cargo.toml
[dependencies]
bitcoin_simulation = { path = "../SimpleBTC" }
#![allow(unused)]
fn main() {
// 在你的代码中
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
};
fn my_app() {
let blockchain = Blockchain::new();
// ... 你的业务逻辑
}
}
Q: 如何使用REST API?
A:
启动服务器:
cargo run --bin btc-server
# 服务器运行在 http://localhost:3000
API调用示例:
# 创建钱包
curl -X POST http://localhost:3000/api/wallet/create
# 创建交易
curl -X POST http://localhost:3000/api/transaction/create \
-H "Content-Type: application/json" \
-d '{
"from": "alice_address",
"to": "bob_address",
"amount": 1000,
"fee": 10
}'
# 查询余额
curl http://localhost:3000/api/balance/alice_address
# 挖矿
curl -X POST http://localhost:3000/api/mine \
-H "Content-Type: application/json" \
-d '{"miner_address": "miner_address"}'
Q: 如何运行测试?
A:
# 运行所有测试
cargo test
# 运行特定测试
cargo test test_blockchain
# 显示输出
cargo test -- --nocapture
# 运行示例
cargo run --example enterprise_multisig
cargo run --example escrow_service
cargo run --example timelock_savings
Q: 如何部署文档网站?
A:
本地预览:
cd docs
mdbook serve --open
GitHub Pages部署:
# 构建
mdbook build
# 部署到gh-pages分支
# 详见 docs/README.md
Docker部署:
FROM nginx:alpine
COPY docs/book /usr/share/nginx/html
EXPOSE 80
详见文档部署指南
性能问题
Q: 挖矿太慢怎么办?
A: 调整难度:
#![allow(unused)]
fn main() {
// 在 blockchain.rs 中
blockchain.difficulty = 3; // 降低难度
// 3: 毫秒级
// 4: 秒级
// 5: 数秒
// 6+: 可能很慢
}
或者使用Release模式:
cargo run --release --bin btc-demo
# Release模式比Debug快很多
Q: 余额查询很慢?
A: 使用索引器加速:
#![allow(unused)]
fn main() {
// SimpleBTC已经内置了索引器
let txs = blockchain.indexer.get_transactions_by_address(&address);
// 或者缓存余额
let balance_cache: HashMap<String, u64> = HashMap::new();
}
安全问题
Q: SimpleBTC安全吗?可以用于生产吗?
A: ⚠️ SimpleBTC是教育项目,不建议用于生产!
与真实比特币的差异:
- ❌ 简化的密码学(SHA256代替ECDSA)
- ❌ 无P2P网络层
- ❌ 简化的脚本系统
- ❌ 无完整的SPV实现
- ❌ JSON存储(应该用LevelDB)
用于生产需要:
- 实现完整的secp256k1椭圆曲线
- 实现ECDSA签名验证
- 添加P2P网络协议
- 使用专业的数据库
- 完整的Script脚本引擎
- 经过安全审计
Q: 如何保护私钥?
A: 私钥安全建议:
- 永不分享私钥
- 多重备份:
- 纸钱包(防火防水)
- 硬件钱包
- 加密U盘
- 分散存储:
- 家中保险柜
- 银行保险箱
- 异地备份
- 使用多签:
- 2-of-3减少单点风险
- 定期测试恢复
其他问题
Q: SimpleBTC与真实比特币的区别?
A:
| 特性 | SimpleBTC | 真实比特币 |
|---|---|---|
| 密码学 | SHA256(简化) | secp256k1 ECDSA |
| 共识 | PoW(简化) | PoW(完整) |
| 脚本 | 简化 | 完整Script语言 |
| 网络 | 无 | P2P网络 |
| 存储 | JSON | LevelDB |
| 难度调整 | 固定 | 每2016区块调整 |
SimpleBTC的价值:
- ✅ 学习比特币原理
- ✅ 理解UTXO模型
- ✅ 实践区块链开发
- ✅ 快速原型验证
Q: 如何贡献代码?
A:
- Fork项目
- 创建功能分支
- 提交Pull Request
- 等待Review
详见贡献指南
Q: 遇到Bug怎么办?
A:
-
在GitHub提Issue: https://github.com/GeoffreyWang1117/SimpleBTC/issues
-
提供以下信息:
- 操作系统
- Rust版本
- 错误信息
- 复现步骤
- 相关代码
Q: 在哪里获取帮助?
A:
- 📖 文档: 本站
- 💬 GitHub Issues: 报告问题和建议
- 📚 Rust社区: https://users.rust-lang.org/
- 📖 比特币白皮书: https://bitcoin.org/bitcoin.pdf
更多资源
没找到你的问题? 在GitHub提Issue
贡献指南
感谢您对SimpleBTC项目的关注!我们欢迎各种形式的贡献。
贡献方式
1. 报告Bug
在GitHub Issues提交Bug报告。
包含以下信息:
- 操作系统和版本
- Rust版本(
rustc --version) - 错误信息
- 复现步骤
- 相关代码片段
2. 提出功能建议
在Issues中提交功能请求,说明:
- 功能描述
- 使用场景
- 实现思路(可选)
3. 贡献代码
- Fork项目
# 在GitHub上Fork
# 克隆你的Fork
git clone https://github.com/YOUR_USERNAME/SimpleBTC.git
cd SimpleBTC
- 创建功能分支
git checkout -b feature/your-feature-name
-
编写代码
- 遵循Rust风格指南
- 添加测试
- 更新文档
-
提交Pull Request
git add .
git commit -m "Add: your feature description"
git push origin feature/your-feature-name
在GitHub上创建Pull Request。
4. 改进文档
文档同样重要!
- 修正错误
- 添加示例
- 改进说明
- 翻译文档
开发指南
代码风格
# 格式化代码
cargo fmt
# 检查lint
cargo clippy
测试
# 运行所有测试
cargo test
# 添加测试
#[cfg(test)]
mod tests {
#[test]
fn test_something() {
// ...
}
}
文档注释
#![allow(unused)]
fn main() {
/// 函数的简短描述
///
/// 详细说明...
///
/// # 参数
/// * `param1` - 参数说明
///
/// # 返回值
/// 返回值说明
///
/// # 示例
/// \```
/// let result = function(arg);
/// \```
pub fn function(param1: Type) -> ReturnType {
// ...
}
}
Pull Request检查清单
提交PR前确保:
- 代码通过
cargo fmt格式化 - 代码通过
cargo clippy检查 - 所有测试通过
cargo test - 添加了必要的测试
- 更新了相关文档
- 提交信息清晰明确
社区准则
- 友好尊重
- 建设性讨论
- 欢迎新手
- 专注技术
License
贡献的代码将采用项目的MIT许可证。
感谢您的贡献!🎉