Rust Elective 02: ブロックチェーン - 分散台帳の基礎
課題説明
概要
ブロックチェーンは、暗号技術を用いた分散型の改ざん防止台帳システムです。本課題では、Rustを使って基本的なブロックチェーンを実装し、暗号学的ハッシュ、Proof of Work、P2Pネットワークの基礎を学びます。
背景と動機
ブロックチェーンの重要性:
- 信頼の分散化: 中央機関なしでデータの整合性を保証
- 改ざん耐性: 暗号学的ハッシュチェーンによる保護
- 透明性: すべての取引履歴が公開され検証可能
- 分散合意: ネットワーク参加者間での合意形成
Rustの優位性:
- メモリ安全性(セキュリティが重要)
- 高性能な暗号演算
- 並行処理に強い(P2Pネットワーク)
- 型システムによるバグ防止
- ブロック構造:
課題要件
以下の機能を持つブロックチェーンシステムを実装してください:
- チェーン管理:
- Proof of Work:
- トランザクション:
- P2Pネットワーク基礎:
制約条件
- SHA-256ハッシュを使用すること
- Proof of Workは調整可能な難易度を持つこと
- チェーンの整合性を常に検証すること
- エラーハンドリングを適切に実装すること
- CLIインターフェースを提供すること
---
想定解答
プロジェクト構造
rust_blockchain/
├── Cargo.toml
├── src/
│ ├── main.rs
│ ├── lib.rs
│ ├── block.rs
│ ├── blockchain.rs
│ ├── transaction.rs
│ ├── pow.rs
│ ├── network.rs
│ └── cli.rs
├── tests/
│ ├── block_tests.rs
│ ├── blockchain_tests.rs
│ └── integration_tests.rs
└── README.md
Cargo.toml
[package]
name = "rust_blockchain"
version = "0.1.0"
edition = "2021"
[dependencies]
sha2 = "0.10"
hex = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4"
clap = { version = "4.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
bincode = "1.3"
[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "mining_benchmark"
harness = false
src/block.rs
use chrono::Utc;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::transaction::Transaction;
/// ブロック構造体
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
pub index: u64,
pub timestamp: i64,
pub transactions: Vec<Transaction>,
pub previous_hash: String,
pub hash: String,
pub nonce: u64,
pub difficulty: usize,
}
impl Block {
/// 新しいブロックを作成
pub fn new(
index: u64,
transactions: Vec<Transaction>,
previous_hash: String,
difficulty: usize,
) -> Self {
let timestamp = Utc::now().timestamp();
let mut block = Block {
index,
timestamp,
transactions,
previous_hash,
hash: String::new(),
nonce: 0,
difficulty,
};
// 初期ハッシュを計算(マイニング前)
block.hash = block.calculate_hash();
block
}
/// ジェネシスブロック(最初のブロック)を作成
pub fn genesis() -> Self {
Block::new(
0,
vec![Transaction::coinbase("genesis".to_string(), 50.0)],
"0".repeat(64),
4,
)
}
/// ブロックのハッシュを計算
pub fn calculate_hash(&self) -> String {
let block_data = format!(
"{}{}{}{}{}{}",
self.index,
self.timestamp,
serde_json::to_string(&self.transactions).unwrap(),
self.previous_hash,
self.nonce,
self.difficulty
);
let mut hasher = Sha256::new();
hasher.update(block_data.as_bytes());
format!("{:x}", hasher.finalize())
}
/// ブロックをマイニング(Proof of Work)
pub fn mine(&mut self) -> u64 {
let target = "0".repeat(self.difficulty);
let start_time = Utc::now().timestamp_millis();
println!(
"Mining block {} with difficulty {}...",
self.index, self.difficulty
);
loop {
self.hash = self.calculate_hash();
if self.hash.starts_with(&target) {
let end_time = Utc::now().timestamp_millis();
let duration = end_time - start_time;
println!(
"Block mined! Hash: {}, Nonce: {}, Time: {}ms",
self.hash, self.nonce, duration
);
return self.nonce;
}
self.nonce += 1;
// デバッグ用:10万回ごとに進捗表示
if self.nonce % 100_000 == 0 {
println!("Tried {} nonces...", self.nonce);
}
}
}
/// ブロックが有効か検証
pub fn is_valid(&self) -> bool {
// ハッシュが正しく計算されているか
if self.hash != self.calculate_hash() {
println!("Invalid hash for block {}", self.index);
return false;
}
// Proof of Workの条件を満たしているか
let target = "0".repeat(self.difficulty);
if !self.hash.starts_with(&target) {
println!("Invalid proof of work for block {}", self.index);
return false;
}
// すべてのトランザクションが有効か
for tx in &self.transactions {
if !tx.is_valid() {
println!("Invalid transaction in block {}", self.index);
return false;
}
}
true
}
/// ブロック内のトランザクション数を返す
pub fn transaction_count(&self) -> usize {
self.transactions.len()
}
/// ブロックのサイズ(バイト)を推定
pub fn size(&self) -> usize {
bincode::serialize(self).unwrap().len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_genesis_block() {
let genesis = Block::genesis();
assert_eq!(genesis.index, 0);
assert_eq!(genesis.previous_hash, "0".repeat(64));
assert!(genesis.is_valid());
}
#[test]
fn test_hash_calculation() {
let block = Block::genesis();
let hash1 = block.calculate_hash();
let hash2 = block.calculate_hash();
// 同じブロックは同じハッシュを生成
assert_eq!(hash1, hash2);
// ハッシュは64文字の16進数
assert_eq!(hash1.len(), 64);
}
#[test]
fn test_mining() {
let mut block = Block::new(
1,
vec![Transaction::coinbase("miner".to_string(), 50.0)],
"previous".to_string(),
2, // 低い難易度でテスト
);
block.mine();
// マイニング後、ハッシュは "00" で始まるはず
assert!(block.hash.starts_with("00"));
assert!(block.is_valid());
}
}
src/transaction.rs
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
/// トランザクション構造体
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Transaction {
pub from: String,
pub to: String,
pub amount: f64,
pub timestamp: i64,
pub signature: Option<String>,
}
impl Transaction {
/// 新しいトランザクションを作成
pub fn new(from: String, to: String, amount: f64) -> Self {
Transaction {
from,
to,
amount,
timestamp: chrono::Utc::now().timestamp(),
signature: None,
}
}
/// コインベーストランザクション(マイニング報酬)
pub fn coinbase(to: String, amount: f64) -> Self {
Transaction {
from: "COINBASE".to_string(),
to,
amount,
timestamp: chrono::Utc::now().timestamp(),
signature: None,
}
}
/// トランザクションのハッシュを計算
pub fn hash(&self) -> String {
let data = format!(
"{}{}{}{}",
self.from, self.to, self.amount, self.timestamp
);
let mut hasher = Sha256::new();
hasher.update(data.as_bytes());
format!("{:x}", hasher.finalize())
}
/// トランザクションが有効か検証
pub fn is_valid(&self) -> bool {
// 金額が正か
if self.amount <= 0.0 {
return false;
}
// コインベーストランザクションは常に有効
if self.from == "COINBASE" {
return true;
}
// 送信者と受信者が異なるか
if self.from == self.to {
return false;
}
// 実際のシステムでは署名の検証も行う
true
}
}
/// トランザクションプール
#[derive(Debug, Clone)]
pub struct TransactionPool {
transactions: Vec<Transaction>,
}
impl TransactionPool {
pub fn new() -> Self {
TransactionPool {
transactions: Vec::new(),
}
}
/// トランザクションを追加
pub fn add_transaction(&mut self, tx: Transaction) -> Result<(), String> {
if !tx.is_valid() {
return Err("Invalid transaction".to_string());
}
self.transactions.push(tx);
Ok(())
}
/// 保留中のトランザクションを取得
pub fn get_pending_transactions(&self, limit: usize) -> Vec<Transaction> {
self.transactions.iter().take(limit).cloned().collect()
}
/// トランザクションをクリア
pub fn clear(&mut self) {
self.transactions.clear();
}
/// プール内のトランザクション数
pub fn len(&self) -> usize {
self.transactions.len()
}
pub fn is_empty(&self) -> bool {
self.transactions.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_creation() {
let tx = Transaction::new("Alice".to_string(), "Bob".to_string(), 10.0);
assert_eq!(tx.from, "Alice");
assert_eq!(tx.to, "Bob");
assert_eq!(tx.amount, 10.0);
}
#[test]
fn test_coinbase_transaction() {
let tx = Transaction::coinbase("Miner".to_string(), 50.0);
assert_eq!(tx.from, "COINBASE");
assert!(tx.is_valid());
}
#[test]
fn test_invalid_transaction() {
let tx = Transaction::new("Alice".to_string(), "Bob".to_string(), -10.0);
assert!(!tx.is_valid());
}
#[test]
fn test_transaction_pool() {
let mut pool = TransactionPool::new();
let tx1 = Transaction::new("Alice".to_string(), "Bob".to_string(), 10.0);
let tx2 = Transaction::new("Bob".to_string(), "Charlie".to_string(), 5.0);
assert!(pool.add_transaction(tx1).is_ok());
assert!(pool.add_transaction(tx2).is_ok());
assert_eq!(pool.len(), 2);
let pending = pool.get_pending_transactions(1);
assert_eq!(pending.len(), 1);
}
}
src/blockchain.rs
use crate::block::Block;
use crate::transaction::{Transaction, TransactionPool};
use serde::{Deserialize, Serialize};
/// ブロックチェーン構造体
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Blockchain {
pub chain: Vec<Block>,
pub difficulty: usize,
pub mining_reward: f64,
#[serde(skip)]
pub transaction_pool: TransactionPool,
}
impl Blockchain {
/// 新しいブロックチェーンを作成
pub fn new(difficulty: usize, mining_reward: f64) -> Self {
let mut blockchain = Blockchain {
chain: Vec::new(),
difficulty,
mining_reward,
transaction_pool: TransactionPool::new(),
};
// ジェネシスブロックを作成
let genesis = Block::genesis();
blockchain.chain.push(genesis);
blockchain
}
/// 最後のブロックを取得
pub fn last_block(&self) -> &Block {
self.chain.last().unwrap()
}
/// 新しいブロックを追加
pub fn add_block(&mut self, mut block: Block) -> Result<(), String> {
// ブロックのインデックスと前のハッシュを検証
let last_block = self.last_block();
if block.index != last_block.index + 1 {
return Err("Invalid block index".to_string());
}
if block.previous_hash != last_block.hash {
return Err("Invalid previous hash".to_string());
}
// ブロックをマイニング
block.mine();
// ブロックが有効か検証
if !block.is_valid() {
return Err("Invalid block".to_string());
}
self.chain.push(block);
Ok(())
}
/// 保留中のトランザクションをマイニング
pub fn mine_pending_transactions(&mut self, mining_reward_address: String) -> Result<(), String> {
let mut transactions = self.transaction_pool.get_pending_transactions(10);
// マイニング報酬を追加
transactions.push(Transaction::coinbase(
mining_reward_address,
self.mining_reward,
));
// 新しいブロックを作成
let last_block = self.last_block();
let new_block = Block::new(
last_block.index + 1,
transactions,
last_block.hash.clone(),
self.difficulty,
);
// ブロックを追加
self.add_block(new_block)?;
// トランザクションプールをクリア
self.transaction_pool.clear();
Ok(())
}
/// トランザクションを追加
pub fn add_transaction(&mut self, transaction: Transaction) -> Result<(), String> {
self.transaction_pool.add_transaction(transaction)
}
/// アドレスの残高を取得
pub fn get_balance(&self, address: &str) -> f64 {
let mut balance = 0.0;
for block in &self.chain {
for tx in &block.transactions {
if tx.to == address {
balance += tx.amount;
}
if tx.from == address {
balance -= tx.amount;
}
}
}
balance
}
/// チェーン全体の整合性を検証
pub fn is_valid(&self) -> bool {
// ジェネシスブロックを検証
if self.chain.is_empty() {
return false;
}
// 各ブロックを検証
for i in 1..self.chain.len() {
let current_block = &self.chain[i];
let previous_block = &self.chain[i - 1];
// ブロック自体が有効か
if !current_block.is_valid() {
println!("Block {} is invalid", i);
return false;
}
// 前のブロックへの参照が正しいか
if current_block.previous_hash != previous_block.hash {
println!("Block {} has invalid previous hash", i);
return false;
}
// インデックスが連続しているか
if current_block.index != previous_block.index + 1 {
println!("Block {} has invalid index", i);
return false;
}
}
true
}
/// チェーンの統計情報を取得
pub fn get_stats(&self) -> BlockchainStats {
let total_blocks = self.chain.len();
let total_transactions: usize = self.chain.iter().map(|b| b.transaction_count()).sum();
let total_size: usize = self.chain.iter().map(|b| b.size()).sum();
BlockchainStats {
total_blocks,
total_transactions,
total_size,
difficulty: self.difficulty,
}
}
/// チェーンをJSON形式で保存
pub fn save_to_file(&self, path: &str) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(self)?;
std::fs::write(path, json)?;
Ok(())
}
/// JSON形式からチェーンを読み込み
pub fn load_from_file(path: &str) -> std::io::Result<Self> {
let json = std::fs::read_to_string(path)?;
let blockchain: Blockchain = serde_json::from_str(&json)?;
Ok(blockchain)
}
}
#[derive(Debug)]
pub struct BlockchainStats {
pub total_blocks: usize,
pub total_transactions: usize,
pub total_size: usize,
pub difficulty: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blockchain_creation() {
let blockchain = Blockchain::new(2, 50.0);
assert_eq!(blockchain.chain.len(), 1);
assert!(blockchain.is_valid());
}
#[test]
fn test_add_transaction_and_mine() {
let mut blockchain = Blockchain::new(2, 50.0);
let tx = Transaction::new("Alice".to_string(), "Bob".to_string(), 10.0);
assert!(blockchain.add_transaction(tx).is_ok());
assert!(blockchain.mine_pending_transactions("Miner".to_string()).is_ok());
assert_eq!(blockchain.chain.len(), 2);
assert!(blockchain.is_valid());
}
#[test]
fn test_balance_calculation() {
let mut blockchain = Blockchain::new(2, 50.0);
blockchain
.add_transaction(Transaction::new(
"Alice".to_string(),
"Bob".to_string(),
10.0,
))
.unwrap();
blockchain
.mine_pending_transactions("Miner".to_string())
.unwrap();
// Miner should have mining reward
assert_eq!(blockchain.get_balance("Miner"), 50.0);
// Bob should have 10
assert_eq!(blockchain.get_balance("Bob"), 10.0);
// Alice should have -10 (実際のシステムでは残高チェックが必要)
assert_eq!(blockchain.get_balance("Alice"), -10.0);
}
#[test]
fn test_chain_validation() {
let mut blockchain = Blockchain::new(2, 50.0);
// 正常なブロックを追加
blockchain
.add_transaction(Transaction::new(
"Alice".to_string(),
"Bob".to_string(),
5.0,
))
.unwrap();
blockchain
.mine_pending_transactions("Miner".to_string())
.unwrap();
assert!(blockchain.is_valid());
// ブロックを改ざん
blockchain.chain[1].transactions[0].amount = 100.0;
blockchain.chain[1].hash = blockchain.chain[1].calculate_hash();
// 改ざんが検出されるはず
assert!(!blockchain.is_valid());
}
}
src/main.rs
use clap::{Parser, Subcommand};
use rust_blockchain::{Blockchain, Transaction};
use std::path::Path;
#[derive(Parser)]
#[command(name = "rust_blockchain")]
#[command(about = "A simple blockchain implementation in Rust", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// 新しいブロックチェーンを作成
Init {
#[arg(short, long, default_value_t = 4)]
difficulty: usize,
#[arg(short, long, default_value_t = 50.0)]
reward: f64,
},
/// トランザクションを追加
Transaction {
#[arg(short, long)]
from: String,
#[arg(short, long)]
to: String,
#[arg(short, long)]
amount: f64,
},
/// 保留中のトランザクションをマイニング
Mine {
#[arg(short, long)]
address: String,
},
/// アドレスの残高を表示
Balance {
#[arg(short, long)]
address: String,
},
/// チェーンの統計情報を表示
Stats,
/// チェーン全体を表示
Print,
/// チェーンの整合性を検証
Validate,
}
const BLOCKCHAIN_FILE: &str = "blockchain.json";
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Init { difficulty, reward } => {
let blockchain = Blockchain::new(difficulty, reward);
blockchain.save_to_file(BLOCKCHAIN_FILE).unwrap();
println!("Blockchain initialized with difficulty {} and reward {}", difficulty, reward);
}
Commands::Transaction { from, to, amount } => {
let mut blockchain = load_blockchain();
let tx = Transaction::new(from.clone(), to.clone(), amount);
match blockchain.add_transaction(tx) {
Ok(_) => {
blockchain.save_to_file(BLOCKCHAIN_FILE).unwrap();
println!("Transaction added: {} -> {} ({})", from, to, amount);
}
Err(e) => println!("Error: {}", e),
}
}
Commands::Mine { address } => {
let mut blockchain = load_blockchain();
println!("Mining block...");
match blockchain.mine_pending_transactions(address.clone()) {
Ok(_) => {
blockchain.save_to_file(BLOCKCHAIN_FILE).unwrap();
println!("Block mined successfully! Reward sent to {}", address);
}
Err(e) => println!("Error: {}", e),
}
}
Commands::Balance { address } => {
let blockchain = load_blockchain();
let balance = blockchain.get_balance(&address);
println!("Balance of {}: {}", address, balance);
}
Commands::Stats => {
let blockchain = load_blockchain();
let stats = blockchain.get_stats();
println!("=== Blockchain Statistics ===");
println!("Total Blocks: {}", stats.total_blocks);
println!("Total Transactions: {}", stats.total_transactions);
println!("Total Size: {} bytes", stats.total_size);
println!("Difficulty: {}", stats.difficulty);
}
Commands::Print => {
let blockchain = load_blockchain();
for (i, block) in blockchain.chain.iter().enumerate() {
println!("\n=== Block {} ===", i);
println!("Hash: {}", block.hash);
println!("Previous Hash: {}", block.previous_hash);
println!("Timestamp: {}", block.timestamp);
println!("Nonce: {}", block.nonce);
println!("Transactions: {}", block.transaction_count());
for (j, tx) in block.transactions.iter().enumerate() {
println!(" TX {}: {} -> {} ({})", j, tx.from, tx.to, tx.amount);
}
}
}
Commands::Validate => {
let blockchain = load_blockchain();
if blockchain.is_valid() {
println!("✓ Blockchain is valid!");
} else {
println!("✗ Blockchain is invalid!");
}
}
}
}
fn load_blockchain() -> Blockchain {
if Path::new(BLOCKCHAIN_FILE).exists() {
Blockchain::load_from_file(BLOCKCHAIN_FILE).unwrap()
} else {
println!("No blockchain found. Initializing new blockchain...");
let blockchain = Blockchain::new(4, 50.0);
blockchain.save_to_file(BLOCKCHAIN_FILE).unwrap();
blockchain
}
}
---
解説
実装のポイント
1. ブロック構造とハッシュチェーン
改ざん防止の仕組み:
pub fn calculate_hash(&self) -> String {
let block_data = format!(
"{}{}{}{}{}{}",
self.index, self.timestamp, transactions,
self.previous_hash, self.nonce, self.difficulty
);
// SHA-256ハッシュを計算
}
- 各ブロックは前のブロックのハッシュを含む
- データを変更するとハッシュが変わり、チェーンが壊れる
- 過去のブロックを改ざんすると以降すべてのブロックが無効化
2. Proof of Work(作業証明)
マイニングアルゴリズム:
pub fn mine(&mut self) -> u64 {
let target = "0".repeat(self.difficulty);
loop {
self.hash = self.calculate_hash();
if self.hash.starts_with(&target) {
return self.nonce;
}
self.nonce += 1;
}
}
- 特定の条件を満たすハッシュを見つける計算パズル
- 難易度が高いほど時間がかかる(セキュリティ向上)
- 見つけるのは困難、検証は容易(非対称性)
3. トランザクション管理
UTXO モデルの簡易版:
pub fn get_balance(&self, address: &str) -> f64 {
let mut balance = 0.0;
for block in &self.chain {
for tx in &block.transactions {
if tx.to == address { balance += tx.amount; }
if tx.from == address { balance -= tx.amount; }
}
}
balance
}
- すべてのトランザクションを走査して残高を計算
- 実際のビットコインはUTXOセットで効率化
- 二重支払いの防止が重要
4. チェーンの検証
整合性チェック:
pub fn is_valid(&self) -> bool {
for i in 1..self.chain.len() {
let current = &self.chain[i];
let previous = &self.chain[i - 1];
if !current.is_valid() { return false; }
if current.previous_hash != previous.hash { return false; }
}
true
}
- 各ブロックのPoWを検証
- ハッシュチェーンの連続性を確認
- トランザクションの妥当性をチェック
- シンプルなトランザクションモデル: デジタル署名やUTXOは省略し、基本概念に集中
- 調整可能な難易度: 実験とデモのため、難易度を柔軟に設定可能
- CLIインターフェース: コマンドラインで直感的に操作できる
- 永続化: JSON形式でチェーンをファイルに保存
- デジタル署名: 楕円曲線暗号(ECDSA)による署名検証
- マークルツリー: トランザクションの効率的な検証
- PoS(Proof of Stake): エネルギー効率の良い合意アルゴリズム
- スマートコントラクト: 実行可能なコード on chain
- 暗号学的ハッシュ:
設計判断
代替案
---
学習の意図
習得する概念
- 分散合意:
- トランザクション:
- P2Pネットワーク:
CSの基礎との関連
データ構造:
- 連結リスト(ブロックチェーン)
- ハッシュテーブル(UTXO管理)
- マークルツリー(バイナリツリー)
アルゴリズム:
- ハッシュ関数の応用
- 全数探索(マイニング)
- 最長パス問題(チェーン選択)
分散システム:
- CAP定理
- ビザンチン将軍問題
- 合意アルゴリズム
暗号学:
- 公開鍵暗号
- デジタル署名
- ハッシュ関数
---
テスト方法
単体テスト
cargo test
統合テスト
# 1. 新しいチェーンを作成
cargo run -- init --difficulty 3 --reward 50
# 2. トランザクションを追加
cargo run -- transaction --from Alice --to Bob --amount 10
cargo run -- transaction --from Bob --to Charlie --amount 5
# 3. マイニング
cargo run -- mine --address Miner
# 4. 残高確認
cargo run -- balance --address Miner
cargo run -- balance --address Bob
# 5. チェーン表示
cargo run -- print
# 6. 検証
cargo run -- validate
# 7. 統計情報
cargo run -- stats
パフォーマンステスト
# 異なる難易度でマイニング時間を計測
time cargo run --release -- init --difficulty 4
time cargo run --release -- init --difficulty 5
time cargo run --release -- init --difficulty 6
---
評価基準
必須要件(60点)
標準要件(30点)
発展要件(10点)
ボーナス(+10点)
---