Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SimpleBTC - Bitcoin-Based Banking System Demo

Welcome to the SimpleBTC project documentation!

Project Introduction

SimpleBTC is a Bitcoin banking system demo implemented in Rust, providing a complete implementation of Bitcoin’s core principles and advanced features. This project is not only a learning tool but also a fully functional blockchain system demo.

Core Features

🔐 Complete UTXO Model

  • Unspent Transaction Output (UTXO) management
  • Double-spend prevention mechanism
  • Balance calculation
  • UTXO selection algorithm

⛓️ Blockchain Core Functionality

  • Proof of Work (PoW)
  • Blockchain validation
  • Merkle tree implementation
  • Chain hash structure

💼 Wallet System

  • Key pair generation (simplified)
  • Address generation
  • Digital signatures
  • Transaction creation

📊 Advanced Transaction Features

  • Replace-By-Fee (RBF): Replace unconfirmed transactions to speed up confirmation
  • Time Lock (TimeLock): Term deposits, inheritance planning
  • Multi-Signature (MultiSig): 2-of-3 enterprise wallets, escrow services
  • Transaction Priority: Priority sorting based on fees

🌳 Merkle Tree and SPV

  • Efficient transaction verification
  • Lightweight client support
  • Merkle proof generation and verification

🔧 Engineering Features

  • REST API server (Axum framework)
  • Persistent storage (JSON)
  • Transaction indexer
  • Electron visualization interface

Why Choose SimpleBTC?

  1. Educational Value

    • Deep understanding of Bitcoin principles
    • Learn Rust blockchain development
    • Master cryptographic fundamentals
  2. Complete Implementation

    • Conforms to ACID transaction properties
    • Implements Bitcoin core protocol
    • Includes advanced BIP features
  3. Practical Examples

    • Corporate fund management
    • Escrow transaction services
    • Term deposit systems
  4. Easy to Extend

    • Modular design
    • Clear code structure
    • Detailed comments

Quick Start

# Clone the project
git clone https://github.com/GeoffreyWang1117/SimpleBTC.git
cd SimpleBTC

# Build the project
cargo build --release

# Run the demo
cargo run --bin btc-demo

# Run the REST API server
cargo run --bin btc-server

# Run examples
cargo run --example enterprise_multisig
cargo run --example escrow_service
cargo run --example timelock_savings

System Architecture

SimpleBTC/
├── src/
│   ├── transaction.rs     # Transaction module (UTXO model)
│   ├── block.rs          # Block structure
│   ├── blockchain.rs     # Blockchain core logic
│   ├── wallet.rs         # Wallet management
│   ├── utxo.rs          # UTXO set management
│   ├── merkle.rs        # Merkle tree implementation
│   ├── multisig.rs      # Multi-signature
│   ├── advanced_tx.rs   # RBF, time locks, priority
│   ├── persistence.rs   # Persistent storage
│   └── indexer.rs       # Transaction indexer
├── examples/            # Practical examples
├── frontend/            # Electron GUI
└── docs/               # This documentation

Tech Stack

  • Language: Rust (Edition 2021)
  • Core Libraries:
    • sha2 - SHA256 hashing
    • serde - Serialization
    • rand - Random number generation
  • Web Framework: Axum (async REST API)
  • Frontend: Electron + JavaScript
  • Documentation: mdBook

Learning Path

Beginner: Understanding Basic Concepts

  1. Basic Concepts - UTXO, blocks, hashing
  2. Wallet Management - Creating wallets, sending transactions
  3. Transaction Processing - Transaction structure, validation

Intermediate: Mastering Core Mechanisms

  1. Blockchain Operations - Mining, validation
  2. UTXO Management - UTXO selection, double-spend protection
  3. Merkle Tree - SPV verification

Advanced: Implementing Complex Applications

  1. Multi-Signature - Enterprise wallets
  2. Time Lock - Term deposits
  3. RBF Mechanism - Transaction acceleration

Differences from Bitcoin

SimpleBTC is a simplified educational implementation; the main differences from real Bitcoin are:

FeatureSimpleBTCReal Bitcoin
CryptographySimplified SHA256secp256k1 elliptic curve
SignaturesSimplified validationECDSA signatures
ScriptsSimplified scriptsFull Script language
P2P NetworkNo network layerFull P2P protocol
StorageJSON filesLevelDB database
Difficulty adjustmentFixed difficultyDynamic difficulty adjustment

Project Status

  • ✅ UTXO model
  • ✅ Proof of Work
  • ✅ Merkle tree
  • ✅ Multi-signature
  • ✅ RBF mechanism
  • ✅ Time lock
  • ✅ REST API
  • ✅ GUI interface
  • ✅ Full documentation

Contributing

Contributions of code, documentation, or bug reports are welcome!

See the Contributing Guide for details.

License

This project is licensed under the MIT License.


Let’s start exploring the world of Bitcoin! 🚀

Installation and Configuration

This section will guide you through the installation and basic configuration of SimpleBTC.

System Requirements

Minimum Requirements

  • Operating System: Linux, macOS, or Windows (WSL2)
  • Rust Version: 1.70.0 or higher
  • Memory: At least 2GB RAM
  • Storage: At least 500MB free space
  • Operating System: Linux/macOS
  • Rust Version: Latest stable release
  • Memory: 4GB+ RAM
  • Storage: 1GB+ free space
  • CPU: Multi-core processor (better mining performance)

Installing Rust

If you have not installed Rust yet, visit rust-lang.org or use the following command:

# Linux/macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Verify installation
rustc --version
cargo --version

Cloning the Project

# Clone via HTTPS
git clone https://github.com/GeoffreyWang1117/SimpleBTC.git

# Or clone via SSH
git clone git@github.com:GeoffreyWang1117/SimpleBTC.git

# Enter the project directory
cd SimpleBTC

Building the Project

Development Build

# Fast build (unoptimized, compiles quickly)
cargo build

# Run tests
cargo test

# Run the Demo
cargo run --bin btc-demo

Production Build

# Optimized build (best performance, slower to compile)
cargo build --release

# Run the optimized binary
./target/release/btc-demo
./target/release/btc-server

Running the Examples

SimpleBTC provides three hands-on examples:

# 1. Enterprise multi-signature wallet (2-of-3)
cargo run --example enterprise_multisig

# 2. Escrow service (buyer/seller/arbitrator)
cargo run --example escrow_service

# 3. Time-deposit savings (time lock)
cargo run --example timelock_savings

Starting the REST API Server

# Development mode
cargo run --bin btc-server

# Production mode
cargo run --release --bin btc-server

The server will start at http://localhost:3000

API Endpoints

  • GET /api/blockchain/info - Get blockchain information
  • POST /api/wallet/create - Create a new wallet
  • POST /api/transaction/create - Create a transaction
  • POST /api/mine - Mine a block
  • GET /api/balance/:address - Query balance

Starting the Electron GUI

# Install Node.js dependencies
cd frontend
npm install

# Launch the Electron application
npm start

The GUI provides a visual interface including:

  • Blockchain explorer
  • Wallet management
  • Transaction creation
  • Real-time mining
  • One-click Demo mode

Project Structure

SimpleBTC/
├── src/                    # Source code
│   ├── lib.rs             # Library entry point
│   ├── main.rs            # CLI Demo
│   ├── transaction.rs     # Transaction module
│   ├── block.rs           # Block module
│   ├── blockchain.rs      # Blockchain logic
│   ├── wallet.rs          # Wallet management
│   ├── utxo.rs           # UTXO management
│   ├── merkle.rs         # Merkle tree
│   ├── multisig.rs       # Multi-signature
│   ├── advanced_tx.rs    # Advanced transaction features
│   ├── persistence.rs    # Persistence
│   └── indexer.rs        # Indexer
├── examples/              # Example programs
│   ├── enterprise_multisig.rs
│   ├── escrow_service.rs
│   └── timelock_savings.rs
├── frontend/              # Electron GUI
│   ├── main.js
│   ├── app.js
│   └── index.html
├── docs/                  # Documentation
├── Cargo.toml            # Rust project configuration
└── README.md             # Project description

Configuration Options

Mining Difficulty

Modify in src/blockchain.rs:

#![allow(unused)]
fn main() {
pub fn new() -> Blockchain {
    let mut blockchain = Blockchain {
        difficulty: 3,  // Change here: 3-5 is suitable for demos, 6+ is more secure but slower
        // ...
    }
}
}

Block Reward

#![allow(unused)]
fn main() {
pub fn new() -> Blockchain {
    let mut blockchain = Blockchain {
        mining_reward: 50,  // Modify the mining reward (satoshi)
        // ...
    }
}
}

API Server Port

Modify in src/bin/server.rs:

#![allow(unused)]
fn main() {
let listener = TcpListener::bind("0.0.0.0:3000") // Change port here
    .await
    .unwrap();
}

Frequently Asked Questions

Build Errors

Problem: error: failed to fetch

# Solution: Update the Cargo index
cargo update

Problem: error: linker 'cc' not found

# Ubuntu/Debian
sudo apt-get install build-essential

# macOS (install Xcode command-line tools)
xcode-select --install

Runtime Errors

Problem: Address already in use (os error 98)

# Port 3000 is occupied; kill the process using it or change the port
lsof -ti:3000 | xargs kill

Problem: Mining is too slow

# Lower the difficulty
# Set difficulty: 2 in blockchain.rs

Next Steps

Getting Help

  • GitHub Issues: https://github.com/GeoffreyWang1117/SimpleBTC/issues
  • Project Documentation: this site
  • Rust Community: https://users.rust-lang.org/

Quick Start

This is a 5-minute tutorial that walks you through the core features of SimpleBTC.

Your First Blockchain Program

Create a new Rust project and add the SimpleBTC dependency:

use bitcoin_simulation::{
    blockchain::Blockchain,
    wallet::Wallet,
};

fn main() {
    println!("🚀 SimpleBTC Quick Start\n");

    // 1. Create a blockchain
    let mut blockchain = Blockchain::new();
    println!("✓ Blockchain initialized");

    // 2. Create wallets
    let alice = Wallet::new();
    let bob = Wallet::new();
    println!("✓ Two wallets created");
    println!("  Alice: {}", alice.address);
    println!("  Bob:   {}\n", bob.address);

    // 3. Alice receives initial funds (from the genesis block)
    let tx1 = blockchain.create_transaction(
        &Wallet::from_address("genesis_address".to_string()),
        alice.address.clone(),
        10000,  // 10,000 satoshi
        0,      // No fee (genesis transaction)
    ).unwrap();
    blockchain.add_transaction(tx1).unwrap();
    blockchain.mine_pending_transactions(alice.address.clone()).unwrap();

    println!("💰 Alice's balance: {} satoshi", blockchain.get_balance(&alice.address));

    // 4. Alice sends to Bob
    let tx2 = blockchain.create_transaction(
        &alice,
        bob.address.clone(),
        3000,   // Transfer 3000
        10,     // Fee 10
    ).unwrap();
    blockchain.add_transaction(tx2).unwrap();
    blockchain.mine_pending_transactions(bob.address.clone()).unwrap();

    // 5. View final balances
    println!("\n💼 Final balances:");
    println!("  Alice: {} satoshi", blockchain.get_balance(&alice.address));
    println!("  Bob:   {} satoshi\n", blockchain.get_balance(&bob.address));

    // 6. Validate the blockchain
    if blockchain.is_valid() {
        println!("✅ Blockchain validation passed!");
    }

    // 7. Print blockchain information
    blockchain.print_chain();
}

Expected Output

🚀 SimpleBTC Quick Start

✓ Blockchain initialized
✓ Two wallets created
  Alice: a3f2d8c9e4b7...
  Bob:   b9e4c7d2a3f1...

Block mined: 0003ab4f9c2d...
💰 Alice's balance: 10050 satoshi

Block mined: 0007c3e8d1a9...

💼 Final balances:
  Alice: 6990 satoshi
  Bob:   3060 satoshi

✅ Blockchain validation passed!

Core Concepts at a Glance

1. Blockchain

A blockchain is a chain-linked data structure of blocks, where each block contains multiple transactions.

#![allow(unused)]
fn main() {
let mut blockchain = Blockchain::new();
}

Key methods:

  • create_transaction() - Create a transaction
  • add_transaction() - Add to the pending pool
  • mine_pending_transactions() - Mine and package transactions
  • get_balance() - Query balance
  • is_valid() - Validate the blockchain

2. Wallet

A wallet manages public keys, private keys, and addresses.

#![allow(unused)]
fn main() {
let wallet = Wallet::new();
println!("Address: {}", wallet.address);
println!("Public key: {}", wallet.public_key);
// Keep the private key secret!
}

Key methods:

  • new() - Create a new wallet
  • sign() - Sign data
  • verify_signature() - Verify a signature

3. Transaction

A transaction is the basic unit of value transfer, using the UTXO model.

#![allow(unused)]
fn main() {
let tx = blockchain.create_transaction(
    &sender,         // Sender's wallet
    receiver_addr,   // Receiver's address
    amount,          // Amount (satoshi)
    fee,             // Fee (satoshi)
)?;
}

A transaction contains:

  • Inputs: the UTXOs being spent
  • Outputs: the new UTXOs being created
  • Fee: total inputs - total outputs

4. Mining

Mining is the process of packaging transactions into a block via Proof of Work (PoW).

#![allow(unused)]
fn main() {
blockchain.mine_pending_transactions(miner_address)?;
}

Mining process:

  1. Collect pending transactions
  2. Create a Coinbase transaction (reward + fees)
  3. Compute the Merkle root
  4. Find a hash satisfying the difficulty target (adjust nonce)
  5. Add the block to the chain
  6. Update the UTXO set

Advanced Examples

Multiple Transactions

#![allow(unused)]
fn main() {
// Create multiple transactions
for i in 1..=5 {
    let tx = blockchain.create_transaction(
        &alice,
        bob.address.clone(),
        100 * i,
        i,  // Different fees
    )?;
    blockchain.add_transaction(tx)?;
}

// Package all transactions at once
blockchain.mine_pending_transactions(miner.address.clone())?;
}

Fee-Rate Priority

#![allow(unused)]
fn main() {
// Low-fee transaction
let slow_tx = blockchain.create_transaction(&alice, bob.address.clone(), 1000, 1)?;

// High-fee transaction
let fast_tx = blockchain.create_transaction(&alice, charlie.address.clone(), 1000, 50)?;

blockchain.add_transaction(slow_tx)?;
blockchain.add_transaction(fast_tx)?;

// Miners will prioritize fast_tx (higher fee rate)
blockchain.mine_pending_transactions(miner.address)?;
}

Balance Query

#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&alice.address);
println!("Balance: {} satoshi ({:.8} BTC)", balance, balance as f64 / 100_000_000.0);
}

REST API Usage

Start the API server:

cargo run --bin btc-server

Create a Wallet

curl -X POST http://localhost:3000/api/wallet/create

Response:

{
  "address": "a3f2d8c9e4b7...",
  "public_key": "04f9a...",
  "private_key": "keep your private key safe"
}

Create a Transaction

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
  }'

Query Balance

curl http://localhost:3000/api/balance/alice_address

Mine a Block

curl -X POST http://localhost:3000/api/mine \
  -H "Content-Type: application/json" \
  -d '{
    "miner_address": "miner_address"
  }'

Get Blockchain Information

curl http://localhost:3000/api/blockchain/info

Response:

{
  "chain_length": 3,
  "difficulty": 3,
  "pending_transactions": 2,
  "latest_block": {
    "index": 2,
    "hash": "0003ab4f...",
    "timestamp": 1703001234,
    "transaction_count": 5
  }
}

Electron GUI

Launch the graphical interface:

cd frontend
npm install
npm start

GUI features:

  • 📊 Blockchain Explorer: Visualize all blocks
  • 👛 Wallet Management: Create and import wallets
  • 💸 Send Transactions: Create transactions graphically
  • ⛏️ Mining: Click a button to start mining
  • 🎮 Demo Mode: Run the full demo with one click

Practical Examples

SimpleBTC provides three complete practical examples:

1. Enterprise Multi-Signature Wallet (2-of-3)

cargo run --example enterprise_multisig

What you will learn:

  • Creating a multi-signature address
  • Collecting signatures
  • Enterprise fund management

2. Escrow Service

cargo run --example escrow_service

What you will learn:

  • Buyer/seller transactions
  • Arbitration mechanism
  • Dispute resolution

3. Time-Deposit Savings

cargo run --example timelock_savings

What you will learn:

  • Setting up time locks
  • Expiry checks
  • Enforced saving

Next Steps

Now that you have mastered the basics, you can dive deeper:

  1. Understand the Principles - Core Concepts

    • UTXO model in depth
    • Proof of Work mechanics
    • Merkle tree structure
  2. Core Features - Core Module Guide

    • In-depth wallet usage
    • Advanced transaction features
    • Blockchain operations
  3. Advanced Features - Advanced Functionality

    • Merkle tree and SPV
    • Multi-signature
    • Replace-By-Fee
    • Time locks
  4. API Reference - API Docs

    • Complete API documentation
    • Function signatures
    • Usage examples

Tips

💡 Tips:

  • Mining difficulty 3-4 is suitable for demos; 6+ is closer to reality
  • Higher fees lead to faster transaction confirmation
  • Call is_valid() regularly to verify blockchain integrity
  • Use print_chain() to view detailed information

⚠️ Caution:

  • A lost private key cannot be recovered
  • This project is for learning only — do not use it in production
  • The simplified cryptographic implementation is not as secure as real Bitcoin

Ready to explore further? Continue reading Core Concepts!

Core Concepts

This section explains the fundamental concepts of SimpleBTC and Bitcoin in detail.

The UTXO Model

What is a UTXO?

UTXO (Unspent Transaction Output) is a core concept in Bitcoin. It represents an output from a previous transaction that has not yet been spent.

Account Model vs. UTXO Model:

FeatureAccount Model (Ethereum)UTXO Model (Bitcoin)
Balance storageEach account has a balance fieldComputed from all UTXOs
StateAccount state (balance, nonce)Stateless (only a UTXO set)
TransferA account -100, B account +100Consume A’s UTXO, create new UTXO for B
PrivacyWeaker (same address reused)Stronger (new address each time)
ParallelismWeaker (transactions on same account must be sequential)Stronger (different UTXOs can be processed in parallel)

UTXO Example

Alice has 3 UTXOs:
  UTXO1: 5 BTC (received from Bob)
  UTXO2: 3 BTC (received from Charlie)
  UTXO3: 2 BTC (mining reward)

Alice's total balance: 5 + 3 + 2 = 10 BTC

UTXO Lifecycle

1. Creation
   Transaction output → Added to UTXO set

2. Existence
   UTXO set → Can be queried and spent

3. Spending
   Referenced by a transaction input → Removed from UTXO set

4. New UTXO creation
   Transaction output → New UTXO added to set

Change Mechanism

A UTXO must be spent in full; it cannot be partially spent:

#![allow(unused)]
fn main() {
// Alice wants to send 3 BTC to Bob, but only has one UTXO worth 5 BTC

Inputs:
  - UTXO: 5 BTC (Alice's)

Outputs:
  - Output 1: 3 BTC → Bob
  - Output 2: 1.999 BTC → Alice (change)
  - Fee: 0.001 BTC → Miner (inputs - outputs)
}

Blocks and the Blockchain

Block Structure

┌─────────────────────────────────┐
│        Block Header              │
├─────────────────────────────────┤
│ index: 123                       │ Block height
│ timestamp: 1703001234            │ Timestamp
│ previous_hash: 0x00012ab...     │ Parent block hash
│ merkle_root: 0xabc123...        │ Merkle tree root
│ nonce: 2847563                  │ Proof of Work
│ hash: 0x000034cd...             │ Current block hash
├─────────────────────────────────┤
│        Block Body                │
├─────────────────────────────────┤
│ Transaction 1 (Coinbase)        │ Mining reward
│ Transaction 2                   │ Regular transaction
│ Transaction 3                   │ Regular transaction
│ ...                             │
└─────────────────────────────────┘

Chain Structure

Genesis Block → Block 1 → Block 2 → ... → Latest Block
    ↓              ↓          ↓                  ↓
  hash=A         hash=B     hash=C            hash=Z
  prev=0         prev=A     prev=B            prev=Y

Each block points to its parent block via previous_hash, forming an immutable chain.

Why is it Immutable?

  1. Hash linking: Changing any transaction changes the block’s hash
  2. Cascade invalidation: A changed block hash breaks the previous_hash of all subsequent blocks
  3. Computational cost: Tampering with history requires re-mining all subsequent blocks
  4. Longest chain rule: An attacker would need to outpace the entire network — nearly impossible (except with a 51% attack)

Proof of Work (PoW)

Mining Principle

Find a nonce value such that the block hash satisfies the difficulty requirement:

#![allow(unused)]
fn main() {
target = "000..." // difficulty leading zeros

while hash(block_header + nonce) >= target {
    nonce++;
}
}

Difficulty Example

difficulty = 3 (demo)
target = "000..."

Valid hashes:
  ✅ 0003ab4f9c2d...
  ✅ 000f12e8a3b9...

Invalid hashes:
  ❌ 001a3f2e8d4c...  (only 2 leading zeros)
  ❌ 0123456789ab...  (only 1 leading zero)

Difficulty and Security

DifficultyAverage AttemptsUse Case
116Testing
34,096Demo
51,048,576Small network
10~10¹²Private chain
20~10²⁴Bitcoin-level

Bitcoin’s actual difficulty is approximately 70-80 bits, with the global hash rate in the hundreds of EH/s.

Why is PoW Needed?

  1. Prevent spam attacks: Creating a block requires computational cost
  2. Fair competition: Higher hash power means higher probability of winning
  3. Decentralization: Anyone can participate in mining
  4. Economic incentive: Miners receive rewards (Coinbase + fees)

Merkle Tree

Structure Example

Merkle tree for 4 transactions:

              Root Hash
             /         \
          H(AB)       H(CD)
         /    \       /    \
       H(A)  H(B)  H(C)  H(D)
        ↑     ↑     ↑     ↑
       Tx1   Tx2   Tx3   Tx4

Construction process:

  1. Compute the hash of each transaction (leaf nodes)
  2. Pair them up and compute the parent node hash
  3. Repeat until only one root hash remains
  4. The root hash is stored in the block header

SPV Verification (Lightweight Verification)

No need to download the entire block — only the block header and Merkle proof are needed:

Verify that Tx2 is in the block:

Required:
  - Hash of Tx2
  - Merkle proof: [H(A), H(CD)]
  - Root Hash from the block header

Verification:
  1. Compute H(B) = hash(Tx2)
  2. Compute H(AB) = hash(H(A) + H(B))
  3. Compute Root = hash(H(AB) + H(CD))
  4. Compare computed Root with the Root in the block header

✅ Match → Tx2 is indeed in the block
❌ No match → Tx2 is absent or has been tampered with

Advantages of SPV

  • Lightweight: Only needs the block header (~80 bytes), not the full block (1-2 MB)
  • Fast: O(log n) verification complexity
  • Mobile-friendly: Can run on phone wallets
  • Secure: Protected by PoW; no need to trust a third party

Cryptographic Foundations

Hash Function (SHA256)

Properties:

  • Deterministic: the same input always produces the same output
  • Fast to compute: millisecond-level
  • Irreversible: cannot reverse-engineer the input from the hash
  • Collision-resistant: finding two inputs with the same hash is practically impossible
  • Avalanche effect: a tiny change in input produces a completely different hash

Example:

hash("hello") = 2cf24dba5fb0a30e...
hash("hallo") = d3751d33f9cd5049...  (completely different!)

Digital Signatures (Simplified ECDSA)

Real Bitcoin:

1. Private key (256-bit random number)
   ↓ Elliptic curve operation
2. Public key (elliptic curve point)
   ↓ SHA256 + RIPEMD160
3. Address (Base58 encoded)

SimpleBTC Simplified:

1. Private key (random string)
   ↓ SHA256
2. Public key (hash value)
   ↓ SHA256, first 20 bytes
3. Address (hexadecimal string)

Signature Verification

#![allow(unused)]
fn main() {
// Signing
signature = hash(private_key + data)

// Verification (simplified)
verify(public_key, data, signature) -> bool
}

Real Bitcoin uses the ECDSA algorithm, which is mathematically provably secure.

Transaction Structure

Transaction Anatomy

#![allow(unused)]
fn main() {
Transaction {
    id: "abc123...",           // Transaction hash
    inputs: [                  // Inputs (which UTXOs to spend)
        TxInput {
            txid: "prev_tx",   // Referenced transaction ID
            vout: 0,           // Output index
            signature: "...",  // Signature
            pub_key: "...",   // Public key
        }
    ],
    outputs: [                 // Outputs (which new UTXOs to create)
        TxOutput {
            value: 3000,       // Amount (satoshi)
            pub_key_hash: "bob_address",
        },
        TxOutput {
            value: 6990,       // Change
            pub_key_hash: "alice_address",
        }
    ],
    timestamp: 1703001234,
    fee: 10,                   // Transaction fee
}
}

Transaction Validation

When a miner validates a transaction, it checks:

  1. Valid signature: Each input’s signature is correct
  2. UTXO exists: The referenced UTXO is in the UTXO set
  3. No double-spend: The UTXO has not been spent by another transaction
  4. Sufficient balance: Total inputs ≥ total outputs
  5. Correct format: Conforms to the protocol specification

Coinbase Transaction

The first transaction in every block, used to distribute the mining reward:

#![allow(unused)]
fn main() {
Transaction {
    id: "coinbase_tx",
    inputs: [
        TxInput {
            txid: "",          // Empty (does not reference a UTXO)
            vout: 0,
            signature: "coinbase",
            pub_key: "coinbase",
        }
    ],
    outputs: [
        TxOutput {
            value: 50 + total_fees,  // Reward + fees
            pub_key_hash: "miner_address",
        }
    ],
    fee: 0,
}
}

Consensus Mechanism

Longest Chain Rule

When a fork occurs, the network selects the chain with the most accumulated work:

     Block 3a (PoW difficulty 3)
    /
Block 2
    \
     Block 3b → Block 4b (PoW difficulty 3)

The chain containing Block 4b has greater total difficulty and becomes the main chain. Block 3a is orphaned.

Why the Longest Chain?

  • Work: A longer chain represents more computational investment
  • Majority consensus: Honest nodes always mine on the longest chain
  • Attack difficulty: An attacker would need more than 51% of the total network hash rate

The 6-Confirmation Rule

Your Tx → Block N → N+1 → N+2 → N+3 → N+4 → N+5 → N+6
          0 conf   1 conf 2 conf 3 conf 4 conf 5 conf 6 conf
  • 0 confirmations: May be double-spent (RBF)
  • 1 confirmation: Relatively safe (small payments)
  • 3 confirmations: Safe (medium amounts)
  • 6 confirmations: Very safe (large transfers)

Fee Market

Fee Rate Calculation

fee_rate = fee / transaction_size (sat/byte)

Priority

Miner transaction selection strategy:

#![allow(unused)]
fn main() {
// Sort by fee rate from high to low
transactions.sort_by(|a, b| {
    b.fee_rate().cmp(&a.fee_rate())
});
}

Higher fee-rate transactions are packaged first.

Fee Recommendations

UrgencyFee RateConfirmation Time
Low priority1-5 sat/byteSeveral hours
Medium priority5-20 sat/byte30-60 minutes
High priority20-50 sat/byte10-20 minutes
Urgent50+ sat/byteNext block

Network Parameters

  • Block time: ~10 minutes (maintained via difficulty adjustment)
  • Difficulty adjustment: Every 2,016 blocks (~2 weeks)
  • Halving cycle: Every 210,000 blocks (~4 years)

Economic Parameters

  • Initial reward: 50 BTC
  • Current reward: 3.125 BTC (after the 2024 halving)
  • Total supply: 21 million BTC (never increases)
  • Smallest unit: 1 satoshi = 0.00000001 BTC

Size Limits

  • Block size: 1 MB (legacy) / 4 MB (SegWit)
  • Transaction size: ~250-500 bytes on average
  • Transactions per block: ~2,000-3,000

Next Steps

Now that you understand the core concepts, continue learning:


💡 Self-Quiz: Try answering the following questions to test your understanding:

  1. What is the main difference between the UTXO model and the account model?
  2. Why is the blockchain immutable?
  3. How does the Merkle tree enable SPV verification?
  4. What is the purpose of Proof of Work?
  5. What is the change mechanism?

Architecture Overview

SimpleBTC is a fully-featured educational implementation of the Bitcoin blockchain, written in Rust. This chapter introduces the project’s overall architectural design, module organization, core data flows, and key abstraction layers.


Project Structure

SimpleBTC/
├── src/
│   ├── lib.rs               # Crate entry point, module exports and re-exports
│   ├── main.rs              # Executable entry point (demo / interactive CLI)
│   │
│   │── ── Core Layer ──
│   ├── block.rs             # Block structure + Proof of Work
│   ├── blockchain.rs        # Core blockchain logic (UTXO management, mining, validation)
│   ├── transaction.rs       # Transaction structures (TxInput / TxOutput / Transaction)
│   ├── wallet.rs            # Wallet + secp256k1 key management
│   ├── utxo.rs              # UTXO set (unspent transaction output management)
│   ├── crypto.rs            # Extended cryptography (Bech32, WIF export)
│   │
│   │── ── Advanced Feature Layer ──
│   ├── merkle.rs            # Merkle tree (transaction inclusion proofs)
│   ├── multisig.rs          # Multi-signature (M-of-N)
│   ├── advanced_tx.rs       # Advanced transactions (RBF, TimeLock)
│   ├── mempool.rs           # Memory pool (sorted by fee rate)
│   ├── script.rs            # Bitcoin Script system
│   ├── spv.rs               # SPV lightweight client verification
│   ├── parallel_mining.rs   # Multi-threaded parallel PoW mining
│   ├── network.rs           # P2P network layer
│   │
│   │── ── Infrastructure Layer ──
│   ├── storage.rs           # RocksDB high-performance persistent storage
│   ├── persistence.rs       # Serialization / deserialization helpers
│   ├── config.rs            # Global configuration (difficulty, rewards, etc.)
│   ├── logging.rs           # Structured logging (tracing)
│   ├── security.rs          # Security validation
│   ├── indexer.rs           # Transaction indexer (accelerates address queries)
│   └── error.rs             # Unified error types
│
├── docs/                    # mdBook documentation
├── Cargo.toml
└── README.md

Three-Layer Architecture Model

SimpleBTC’s modules are organized into three responsibility layers:

┌─────────────────────────────────────────────────────────┐
│                     Core Layer                           │
│  block  blockchain  transaction  wallet  utxo  crypto   │
│  ─── Implements Bitcoin's fundamental data structures    │
│      and protocol rules ───                             │
├─────────────────────────────────────────────────────────┤
│                 Advanced Feature Layer                   │
│  merkle  multisig  advanced_tx  mempool  script  spv    │
│  parallel_mining  network                               │
│  ─── Implements Bitcoin's advanced features and         │
│      extended protocols ───                             │
├─────────────────────────────────────────────────────────┤
│                  Infrastructure Layer                    │
│  storage  persistence  config  logging  security        │
│  indexer  error                                         │
│  ─── Provides general-purpose capabilities: storage,    │
│      logging, configuration, etc. ───                   │
└─────────────────────────────────────────────────────────┘

Core Layer Module Details

ModuleFileResponsibility
blockblock.rsDefines the Block struct, containing block header fields (index, timestamp, nonce, merkle_root, previous_hash, hash) and the single-threaded mine_block() method
blockchainblockchain.rsThe Blockchain main struct, coordinating all blockchain operations: genesis block, transaction creation, mempool management, parallel mining, UTXO updates, and chain validation
transactiontransaction.rsThree core structs: TxInput, TxOutput, Transaction; Coinbase transaction construction; ECDSA signature verification
walletwallet.rsWallet struct, uses secp256k1 to generate real key pairs, P2PKH address derivation, ECDSA signing and verification
utxoutxo.rsUTXOSet manages all unspent transaction outputs, supports balance queries and spendable UTXO retrieval
cryptocrypto.rsCryptoWallet extended implementation: Bech32 addresses, WIF private key format import/export

Advanced Feature Layer Module Details

ModuleFileResponsibility
merklemerkle.rsMerkle tree construction and Merkle proof generation/verification (foundation for SPV)
multisigmultisig.rsM-of-N multi-signature scheme
advanced_txadvanced_tx.rsRBF (Replace-By-Fee) fee replacement, TimeLock time-locked transactions
mempoolmempool.rsMemory pool, sorts pending transactions by fee rate (satoshi/byte)
scriptscript.rsBitcoin Script opcode interpreter
spvspv.rsSimple Payment Verification — validates transactions using Merkle proofs without downloading the full chain
parallel_miningparallel_mining.rsParallelMiner: multi-threaded PoW that fully utilizes multi-core CPUs
networknetwork.rsP2P network message propagation layer

Infrastructure Layer Module Details

ModuleFileResponsibility
storagestorage.rsHigh-performance key-value storage based on RocksDB
persistencepersistence.rsBlockchain data serialization and deserialization
configconfig.rsGlobal parameters (mining difficulty, block reward, network parameters, etc.)
logginglogging.rsStructured logging (based on the tracing crate)
securitysecurity.rsAdditional security validation logic
indexerindexer.rsTransactionIndexer: builds an address → transaction ID index to accelerate balance queries
errorerror.rsBitcoinError unified error enum, Result<T> type alias

Core Data Flow

Complete Value Transfer Flow

User initiates a transfer request
       │
       ▼
┌─────────────────────────────────────┐
│  Blockchain::create_transaction()   │
│  1. Find spendable outputs in UTXOSet│
│  2. Sign inputs with Wallet::sign() │
│  3. Construct TxInput + TxOutput    │
│  4. Generate Transaction (with hash ID)│
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  Blockchain::add_transaction()      │
│  1. Transaction::verify() — verify signatures│
│  2. Check UTXO exists + balance sufficient│
│  3. Record pending_spent to prevent double-spend│
│  4. Add to Mempool (sorted by fee rate)│
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  Blockchain::mine_pending_transactions()│
│  1. Retrieve high-fee-rate transactions from Mempool│
│  2. Construct Coinbase transaction (reward + fees)│
│  3. Block::new() computes Merkle Root│
│  4. ParallelMiner multi-threaded PoW│
│  5. Validate all transactions in the block│
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  Atomic UTXO set update             │
│  1. Consume UTXOs referenced by inputs│
│  2. Add outputs as new UTXOs        │
│  3. Clear pending_spent             │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  Block appended to chain            │
│  1. indexer.index_block() — build index│
│  2. chain.push(block)               │
│  3. Remove confirmed transactions from Mempool│
└─────────────────────────────────────┘

Data Structure Relationship Diagram

Blockchain
├── chain: Vec<Block>
│   └── Block
│       ├── index, timestamp, nonce
│       ├── previous_hash → previous block's hash (chain linkage)
│       ├── merkle_root   → computed by MerkleTree
│       ├── hash          → SHA256(index+timestamp+merkle_root+prev+nonce)
│       └── transactions: Vec<Transaction>
│           └── Transaction
│               ├── id      → SHA256(transaction content)
│               ├── inputs: Vec<TxInput>
│               │   └── TxInput {txid, vout, signature, pub_key}
│               └── outputs: Vec<TxOutput>
│                   └── TxOutput {value, pub_key_hash}
│
├── utxo_set: UTXOSet   ← fast balance query and UTXO retrieval
├── mempool: Mempool    ← pending transactions (sorted by fee rate)
├── indexer: TransactionIndexer  ← address → transaction index
└── miner: ParallelMiner         ← multi-threaded PoW

Quick Start

#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};

// 1. Create a blockchain (includes genesis block; genesis wallet receives 10M satoshi initial funds)
let mut blockchain = Blockchain::new();

// 2. Get the pre-funded genesis wallet + create new user wallets
let genesis = Blockchain::genesis_wallet();
let alice = Wallet::new();
let bob = Wallet::new();

// 3. Create transaction: genesis → alice, transfer 1000 satoshi, fee 10
let tx = blockchain.create_transaction(&genesis, alice.address.clone(), 1000, 10)?;
blockchain.add_transaction(tx)?;

// 4. Mine (alice receives the block reward as miner)
blockchain.mine_pending_transactions(alice.address.clone())?;

// 5. Query balance
println!("Alice's balance: {} satoshi", blockchain.get_balance(&alice.address));

// 6. Validate the entire chain's integrity
assert!(blockchain.is_valid());
Ok::<(), String>(())
}

Cryptography Choices

AlgorithmLibraryPurpose
secp256k1 ECDSAsecp256k1 cratePrivate key generation, transaction signing, signature verification
SHA-256bitcoin_hashesBlock hash, transaction hash, address derivation
RIPEMD-160ripemd cratePublic key hash (intermediate step in P2PKH address)
Base58Checkbs58 crateP2PKH address encoding, WIF private key encoding
Bech32bech32 crateNative SegWit addresses
SHA-256dbitcoin_hashesDouble hash (checksum computation)

All cryptographic implementations are compatible with the Bitcoin mainnet — addresses generated by Wallet::genesis() can be used legitimately in the real Bitcoin protocol.


Concurrency Design

Mining (ParallelMiner) is the only module in the project that makes heavy use of multi-threading. The blockchain state itself (the Blockchain struct) follows a single-threaded ownership model; Rust’s borrow checker guarantees data safety at compile time, eliminating the need for runtime lock overhead.

#![allow(unused)]
fn main() {
// Parallel mining: automatically partitions the nonce search space based on CPU core count
self.miner
    .mine_block(&mut block, self.difficulty)
    .map_err(|e| format!("Mining failed: {}", e))?;
}

Error Handling

All public APIs return Result<T, String> or crate::error::Result<T> (i.e., Result<T, BitcoinError>). BitcoinError is a unified enum type covering:

  • PrivateKeyError — key format error
  • Insufficient balance, UTXO not found, signature verification failure, and other domain errors
#![allow(unused)]
fn main() {
use bitcoin_simulation::{BitcoinError, Result};

fn example() -> Result<()> {
    let blockchain = Blockchain::new();
    // ...
    Ok(())
}
}

Wallet Management

At its core, a Bitcoin wallet is a key pair: a private key and a public key. SimpleBTC uses secp256k1 elliptic curve cryptography that is fully compatible with real Bitcoin, implementing two structs: Wallet (the primary wallet) and CryptoWallet (extended wallet, supporting Bech32 and WIF).


Key System Overview

Bitcoin key generation follows a strict one-way derivation chain:

Random number (256 bit)
       │
       ▼  secp256k1 elliptic curve multiplication
    Private key (SecretKey, 32 bytes)
       │
       ▼  scalar multiplication by generator point G
    Public key (PublicKey, 33 bytes compressed format)
       │
       ├─▶ SHA-256 hash
       │          │
       │          ▼  RIPEMD-160 hash
       │      Public key hash (20 bytes)
       │          │
       │          ▼  version prefix 0x00 + double SHA-256 checksum + Base58
       │      P2PKH address (starts with '1')
       │
       └─▶ SHA-256 + RIPEMD-160
                  │
                  ▼  Bech32 encoding (witness v0)
              Bech32 address (starts with 'bc1')

Elliptic curve equation (secp256k1):

y² = x³ + 7  (mod p)
p = 2²⁵⁶ − 2³² − 977  (a very large prime)

Deriving a public key from a private key is a one-way operation — computationally irreversible (discrete logarithm problem).


The Wallet Struct

Wallet is the most commonly used wallet type in the project, defined in src/wallet.rs:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Wallet {
    /// Bitcoin P2PKH address (starts with '1')
    pub address: String,
    /// Compressed public key in hexadecimal (33 bytes = 66 hex characters)
    pub public_key: String,
    /// secp256k1 private key (stored as hexadecimal during serialization, access restricted)
    #[serde(with = "secret_key_serde")]
    private_key: SecretKey,
}
}

Field descriptions:

  • address: P2PKH format address, publicly used — safe to share with others as a receiving address
  • public_key: Compressed public key (33 bytes), used to verify signatures; included in each transaction input
  • private_key: Private key — must be strictly kept secret; possessing the private key is equivalent to owning all funds at the corresponding address

Creating a Wallet

Generating a Random Wallet

#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;

let wallet = Wallet::new();

println!("Address:     {}", wallet.address);           // P2PKH address starting with '1'
println!("Public key:  {}", wallet.public_key);        // 66-character hex
println!("Private key: {}", wallet.private_key_hex()); // 64-character hex (keep secret!)
}

Internal flow of Wallet::new():

#![allow(unused)]
fn main() {
pub fn new() -> Self {
    let secp = Secp256k1::new();
    // Uses a cryptographically secure random number generator (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 }
}
}

Genesis Wallet

The genesis wallet uses a fixed private key 0x01, generating the same address on every startup. This makes it convenient for spending the initial funds from the genesis block during demos:

#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};

// Two equivalent ways to obtain it
let genesis = Blockchain::genesis_wallet();
let genesis2 = Wallet::genesis();

assert_eq!(genesis.address, genesis2.address); // Address is deterministically consistent

// Internal implementation (src/wallet.rs)
pub fn genesis() -> Self {
    Self::from_private_key_hex(
        "0000000000000000000000000000000000000000000000000000000000000001",
    )
    .expect("genesis private key is valid")
}
}

Warning: The genesis wallet’s private key is public — never use it in a production environment.

Recovering a Wallet from a Private Key

#![allow(unused)]
fn main() {
// Recover from a hexadecimal private key
let wallet = Wallet::new();
let hex = wallet.private_key_hex();  // Export private key

let recovered = Wallet::from_private_key_hex(&hex)?;
assert_eq!(wallet.address, recovered.address);  // Address is identical
}

P2PKH Address Derivation

Wallet::pubkey_to_address() implements address generation steps that are identical to real Bitcoin:

#![allow(unused)]
fn main() {
fn pubkey_to_address(public_key: &PublicKey) -> String {
    // Step 1: Serialize compressed public key (33 bytes: 1-byte prefix + 32-byte x coordinate)
    let pubkey_bytes = public_key.serialize();

    // Step 2: SHA-256 hash
    let sha256_hash = sha256::Hash::hash(&pubkey_bytes);

    // Step 3: RIPEMD-160 hash → public key hash (20 bytes)
    let mut ripemd = Ripemd160::new();
    ripemd.update(&sha256_hash[..]);
    let pubkey_hash = ripemd.finalize();

    // Step 4: Add version byte (mainnet = 0x00)
    let mut versioned = vec![0x00];
    versioned.extend_from_slice(&pubkey_hash);  // 21 bytes total

    // Step 5: Double SHA-256, take first 4 bytes as checksum
    let checksum = sha256d::Hash::hash(&versioned);
    versioned.extend_from_slice(&checksum[0..4]);  // 25 bytes total

    // Step 6: Base58 encode → address starting with '1' (~34 characters)
    bs58::encode(versioned).into_string()
}
}

Why RIPEMD-160?

  • Compresses the 33-byte public key to 20 bytes, saving blockchain storage space
  • Even if a quantum computer breaks ECDSA, the attacker would still need to break the hash function

Why Base58 (not Base64)?

  • Removes easily confused characters: 0 (zero), O (uppercase O), I (uppercase i), l (lowercase L)
  • Avoids issues such as spaces being included when double-clicking to copy

Transaction Signing

Wallet::sign() uses the private key to generate an ECDSA signature over data:

#![allow(unused)]
fn main() {
pub fn sign(&self, data: &str) -> String {
    let secp = Secp256k1::new();
    // 1. SHA-256 hash the raw data
    let msg_hash = sha256::Hash::hash(data.as_bytes());
    let message = Message::from_digest(msg_hash.to_byte_array());
    // 2. Generate ECDSA signature using the private key
    let signature = secp.sign_ecdsa(&message, &self.private_key);
    // 3. DER-encode and return as a hex string
    hex::encode(signature.serialize_der())
}
}

In Blockchain::create_transaction(), the signed data is "{txid}{vout}" — the location identifier of the UTXO being referenced:

#![allow(unused)]
fn main() {
// Excerpt from 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);
}
}

This binds each input’s signature to a specific UTXO, preventing the signature from being replayed against other UTXOs.


Signature Verification

Wallet::verify_signature() is a static method — no private key is needed:

#![allow(unused)]
fn main() {
pub fn verify_signature(public_key_hex: &str, data: &str, signature_hex: &str) -> bool {
    // 1. Decode the public key
    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. Decode the DER signature
    let Ok(sig_bytes) = hex::decode(signature_hex) else { return false; };
    let Ok(signature) = Signature::from_der(&sig_bytes) else { return false; };

    // 3. Re-hash the raw data (exactly as during signing)
    let secp = Secp256k1::new();
    let msg_hash = sha256::Hash::hash(data.as_bytes());
    let message = Message::from_digest(msg_hash.to_byte_array());

    // 4. Mathematical verification: check whether the signature was generated by the corresponding private key
    secp.verify_ecdsa(&message, &signature, &public_key).is_ok()
}
}

Complete sign + verify example:

#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;

let wallet = Wallet::new();
let data = "Hello, Bitcoin!";

// Sign
let signature = wallet.sign(data);
println!("Signature: {}", &signature[..32]); // DER-encoded hex

// Verify correct data: should pass
assert!(Wallet::verify_signature(&wallet.public_key, data, &signature));

// Verify tampered data: should fail
assert!(!Wallet::verify_signature(&wallet.public_key, "Tampered!", &signature));

// Verify with wrong public key: should fail
let other = Wallet::new();
assert!(!Wallet::verify_signature(&other.public_key, data, &signature));
}

Extended Wallet: CryptoWallet

CryptoWallet in src/crypto.rs adds more Bitcoin protocol features on top of Wallet:

#![allow(unused)]
fn main() {
use bitcoin_simulation::crypto::CryptoWallet;

let wallet = CryptoWallet::new();

println!("P2PKH address:   {}", wallet.address);           // starts with '1'
println!("Bech32 address:  {}", wallet.bech32_address);    // starts with 'bc1' (SegWit)
println!("Private key hex: {}", wallet.private_key_hex()); // 64 characters
println!("Public key hex:  {}", wallet.public_key_hex());  // 66 characters
}

WIF Private Key Format

WIF (Wallet Import Format) is the standard format for importing and exporting private keys between Bitcoin wallets:

#![allow(unused)]
fn main() {
let wallet = CryptoWallet::new();

// Export as WIF (starts with '5', 'K', or 'L')
let wif = wallet.export_private_key_wif();
println!("WIF: {}", wif);

// Recover wallet from WIF
let imported = CryptoWallet::import_from_wif(&wif)?;
assert_eq!(wallet.address, imported.address);
}

WIF encoding steps:

  1. Add version byte 0x80 (mainnet private key prefix)
  2. Compute double SHA-256 checksum (take first 4 bytes)
  3. Concatenate and Base58-encode

CryptoWallet Signing Interface

CryptoWallet’s signing interface accepts a byte slice, which is more flexible:

#![allow(unused)]
fn main() {
let wallet = CryptoWallet::new();
let message = b"Hello, Bitcoin!";

// Sign (returns a secp256k1::ecdsa::Signature type)
let signature = wallet.sign(message);

// Verify (static method)
assert!(CryptoWallet::verify(message, &signature, &wallet.public_key));
}

Wallet Serialization

Both Wallet and CryptoWallet implement Serialize / Deserialize, with the private key securely stored as a hexadecimal string:

#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;

let wallet = Wallet::new();

// Serialize to JSON
let json = serde_json::to_string(&wallet)?;
// {"address":"1...","public_key":"02...","private_key":"a1b2c3..."}

// Restore from JSON with full signing capability preserved
let restored: Wallet = serde_json::from_str(&json)?;
let sig = restored.sign("test");
assert!(Wallet::verify_signature(&restored.public_key, "test", &sig));
}

Security Recommendations

ItemDescription
Keep private key secretAnyone who obtains the private key can spend all funds at the corresponding address
Do not reuse addressesUse a new address for each payment to protect privacy
Genesis wallet for demo onlyWallet::genesis() uses a public private key — never use it for real funds
Back up your private keyLosing the private key means permanently losing the corresponding funds
Use WIF format for backupsWIF format includes a checksum, which can detect transcription errors

Transaction Processing

One of Bitcoin’s core innovations is the UTXO (Unspent Transaction Output) model. Unlike a bank account balance, there is no concept of an “account balance” in the Bitcoin system — all funds exist in a distributed form across the blockchain as unspent transaction outputs. This chapter provides an in-depth look at SimpleBTC’s transaction structure, UTXO model, and transaction creation and validation mechanisms.


UTXO Model Fundamentals

What is a UTXO

UTXO stands for “Unspent Transaction Output.” Every Bitcoin transaction consumes some existing UTXOs (as inputs) and simultaneously creates new UTXOs (as outputs).

┌──────────────────────────────────────────────────────────────────┐
│ Traditional Account Model (e.g., banking)                         │
│  Alice balance: 100        Bob balance: 0                        │
│  Transfer 30 → Alice: 70, Bob: 30                                │
├──────────────────────────────────────────────────────────────────┤
│ UTXO Model (Bitcoin)                                              │
│  On-chain: UTXO_A (owned by Alice, value 100)                    │
│  Transfer 30:                                                     │
│    Consume: UTXO_A (100)  ← must be consumed in full             │
│    Create:  UTXO_B (30, owned by Bob)   ← transfer amount        │
│             UTXO_C (60, owned by Alice) ← change (100 - 30 - 10 fee)│
└──────────────────────────────────────────────────────────────────┘

Key properties of the UTXO model:

  • Each UTXO can only be spent once (removed from the UTXO set after spending)
  • A UTXO must be consumed in full when spent; any excess is returned to the sender as a “change” output
  • “Balance” = the sum of all UTXO values owned by an address (calculated by UTXOSet)
  • Unspent UTXOs form the “UTXO set” (a Bitcoin full node must maintain approximately 5-10 GB of UTXO set data)

Transaction Data Structures

TxInput (Transaction Input)

A transaction input references an existing UTXO and provides proof of authorization to spend it (a digital signature):

#![allow(unused)]
fn main() {
// src/transaction.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TxInput {
    pub txid: String,       // ID of the referenced transaction (32-byte hash, 64 hex characters)
    pub vout: usize,        // Index of the output within that transaction (0-based)
    pub signature: String,  // ECDSA signature (DER-encoded, hex string)
    pub pub_key: String,    // Sender's compressed public key (33 bytes, 66 hex characters)
}
}

The combination of txid + vout uniquely identifies a UTXO on the blockchain. The signature is generated by the sender’s private key, proving ownership of that UTXO.

TxOutput (Transaction Output)

A transaction output defines the amount the recipient can receive; it is the carrier of a new UTXO:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TxOutput {
    pub value: u64,           // Amount (unit: satoshi; 1 BTC = 10⁸ satoshi)
    pub pub_key_hash: String, // Recipient address (P2PKH) = locking script
}

impl TxOutput {
    pub fn new(value: u64, address: String) -> Self {
        TxOutput { value, pub_key_hash: address }
    }

    /// Check whether this output can be unlocked by a given address
    /// (i.e., whether that address is the recipient of this output)
    pub fn can_be_unlocked_with(&self, address: &str) -> bool {
        self.pub_key_hash == address
    }
}
}

pub_key_hash is the locking script (scriptPubKey) in real Bitcoin; here it is simplified to the recipient’s address.

Transaction

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Transaction {
    pub id: String,              // Transaction ID = SHA-256(transaction content)
    pub inputs: Vec<TxInput>,    // Input list (which UTXOs to consume)
    pub outputs: Vec<TxOutput>,  // Output list (which new UTXOs to create)
    pub timestamp: u64,          // Unix timestamp (seconds)
    pub fee: u64,                // Fee = total inputs - total outputs
}
}

Invariant: total inputs = total outputs + fee


Creating a Regular Transaction

Regular transactions are created via Blockchain::create_transaction(), which automatically handles UTXO selection, change calculation, and signing:

#![allow(unused)]
fn main() {
pub fn create_transaction(
    &self,
    from_wallet: &Wallet,   // Sender's wallet (private key required for signing)
    to_address: String,     // Recipient's address
    amount: u64,            // Transfer amount (satoshi)
    fee: u64,               // Transaction fee (satoshi)
) -> Result<Transaction, String>
}

Internal flow:

#![allow(unused)]
fn main() {
// Excerpt from src/blockchain.rs (simplified)

// 1. Calculate total needed
let total_needed = amount + fee;

// 2. Find sufficient UTXOs in the UTXO set (excluding those already spent by pending transactions)
let spendable = self.utxo_set.find_spendable_outputs_excluding(
    &from_wallet.address,
    total_needed,
    &self.pending_spent,
);
let (accumulated, utxos) = spendable.ok_or_else(|| "Insufficient balance (including fee)".to_string())?;

// 3. Create an input for each selected UTXO and sign it
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. Create outputs (transfer + change)
let mut outputs = Vec::new();
outputs.push(TxOutput::new(amount, to_address));
if accumulated > total_needed {
    // Return change to the sender (minus the fee)
    outputs.push(TxOutput::new(accumulated - total_needed, from_wallet.address.clone()));
}

// 5. Construct the transaction (ID is computed automatically)
Ok(Transaction::new(inputs, outputs, timestamp, fee))
}

Complete usage example:

#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};

let mut blockchain = Blockchain::new();
let genesis = Blockchain::genesis_wallet(); // Pre-funded with 10M satoshi
let alice = Wallet::new();

// genesis transfers 5000 satoshi to alice, with a fee of 100
let tx = blockchain.create_transaction(
    &genesis,
    alice.address.clone(),
    5000,   // Transfer amount
    100,    // Fee
)?;

println!("Transaction ID: {}", tx.id);
println!("Number of inputs: {}", tx.inputs.len());
println!("Number of outputs: {}", tx.outputs.len()); // Usually 2 (transfer + change)
println!("Fee: {} satoshi", tx.fee);
println!("Fee rate: {:.2} sat/byte", tx.fee_rate());
}

Coinbase Transaction

A Coinbase transaction is the first transaction in every block, dedicated to paying the block reward to the miner. Key differences from a regular transaction:

FeatureRegular TransactionCoinbase Transaction
InputsReference existing UTXOsEmpty txid (created from nothing)
OutputsTransfer + changeBlock reward + all fees
SignatureECDSA signatureNo signature required (pub_key = "coinbase")
ValidationAll input signatures verifiedSignature verification skipped (is_coinbase() = true)
#![allow(unused)]
fn main() {
pub fn new_coinbase(to: String, reward: u64, timestamp: u64, total_fees: u64) -> Self {
    // Use an atomic counter to ensure each coinbase transaction ID is unique
    // (similar to BIP34 block height encoding)
    static COINBASE_COUNTER: AtomicU64 = AtomicU64::new(0);
    let nonce = COINBASE_COUNTER.fetch_add(1, Ordering::Relaxed);

    // Output = block reward + all transaction fees
    let tx_out = TxOutput::new(reward + total_fees, to);
    let tx_in = TxInput {
        txid: String::new(),              // Empty txid identifies coinbase
        vout: 0,
        signature: format!("coinbase:{}", nonce),  // Uniqueness field
        pub_key: String::from("coinbase"),
    };
    // ...
}
}

How to identify a Coinbase transaction:

#![allow(unused)]
fn main() {
pub fn is_coinbase(&self) -> bool {
    // Only one input, and that input's txid is empty
    self.inputs.len() == 1 && self.inputs[0].txid.is_empty()
}
}

Transaction Validation

Transaction::verify() validates the ECDSA signatures of all inputs:

#![allow(unused)]
fn main() {
pub fn verify(&self) -> bool {
    // Coinbase transactions do not need signature verification
    if self.is_coinbase() {
        return true;
    }

    // Must have at least one input and one output
    if self.inputs.is_empty() || self.outputs.is_empty() {
        return false;
    }

    // Verify the ECDSA signature of each input
    for input in &self.inputs {
        // The signed data is the location identifier of the UTXO being spent
        let signed_data = format!("{}{}", input.txid, input.vout);
        if !Wallet::verify_signature(&input.pub_key, &signed_data, &input.signature) {
            return false;
        }
    }

    true
}
}

Validation logic explanation:

  • The signed data is "{txid}{vout}", binding the signature to a specific UTXO and preventing signature replay attacks
  • The public key carried in the input (pub_key) is used to verify the signature — full nodes do not need additional lookups
  • Wallet::verify_signature() internally calls secp256k1 to perform real elliptic curve mathematical verification

Complete validation flow when adding a transaction to the blockchain (Blockchain::add_transaction()):

#![allow(unused)]
fn main() {
// 1. ECDSA signature verification
if !transaction.verify() {
    return Err("Transaction verification failed".to_string());
}

// 2. Verify UTXO exists and balance is sufficient
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 does not exist".to_string());
        }
    } else {
        return Err("Referenced transaction does not exist".to_string());
    }
}

let output_sum: u64 = transaction.outputs.iter().map(|o| o.value).sum();
if input_sum < output_sum {
    return Err("Insufficient balance, transaction is invalid".to_string());
}

// 3. Record pending UTXOs (prevent the same UTXO from being double-spent
//    by two pending transactions)
for input in &transaction.inputs {
    self.pending_spent.insert(format!("{}:{}", input.txid, input.vout));
}
}

Fees and Fee Rate

Fee Calculation

fee = total inputs - total outputs

Example: spend a UTXO (100 satoshi), transfer 85, change 5, fee = 100 - 85 - 5 = 10 satoshi

Fee Rate

#![allow(unused)]
fn main() {
pub fn fee_rate(&self) -> f64 {
    let size = self.size();  // Serialized transaction size in bytes
    if size == 0 { return 0.0; }
    self.fee as f64 / size as f64  // Unit: satoshi/byte
}
}

Typical fee rate reference (real Bitcoin — varies with network congestion):

PriorityFee RateConfirmation Time
Low1–5 sat/byteSeveral hours or longer
Medium5–20 sat/byte30–60 minutes
High20–50 sat/byte10–20 minutes
Urgent50+ sat/byteNext block (~10 minutes)

SimpleBTC’s mempool sorts transactions by fee rate, and higher fee-rate transactions are packaged first:

#![allow(unused)]
fn main() {
// Retrieve top transactions sorted by fee rate (Mempool internal logic)
let pending_txs = self.mempool.get_top_transactions(usize::MAX);
}

Transaction Lifecycle

User initiates a transfer request
       │
       ▼
blockchain.create_transaction(&wallet, to, amount, fee)
  → Select UTXOs, generate signatures, construct Transaction
       │
       ▼
blockchain.add_transaction(tx)
  → Verify signatures (ECDSA)
  → Verify UTXO exists + balance sufficient
  → Add to Mempool (sorted by fee rate)
  → Mark spent UTXOs (pending_spent)
       │
       ▼  Waiting to be packaged by a miner
       │
       ▼
blockchain.mine_pending_transactions(miner_address)
  → Retrieve high-priority transactions from Mempool
  → Create Coinbase transaction (reward + fees)
  → Parallel PoW mining
  → Update UTXO set (atomic operation)
  → Block appended to chain, pending_spent cleared
       │
       ▼
Transaction receives 1 confirmation
(each additional subsequent block = +1 confirmation)

Transaction Hash Calculation

#![allow(unused)]
fn main() {
pub fn calculate_hash(&self) -> String {
    // Serialize the transaction to JSON and compute 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())
}
}

Real Bitcoin uses double SHA-256 (SHA256d) with a compact binary serialization format. Here, for educational simplicity, JSON serialization and a single SHA-256 are used instead.


Output Sum Query

#![allow(unused)]
fn main() {
// Get the sum of all output amounts
let output_sum = tx.output_sum();

// Transaction size (in bytes, affects fee calculation)
let size = tx.size();

// Whether this is a Coinbase transaction
let is_cb = tx.is_coinbase();
}

Complete Transaction Example

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();

    // First transaction: genesis → alice, transfer 10000 satoshi
    let tx1 = blockchain.create_transaction(&genesis, alice.address.clone(), 10000, 50)?;
    println!("tx1 id: {}", tx1.id);
    println!("tx1 output count: {}", tx1.outputs.len()); // 2 (transfer + change)
    println!("tx1 fee rate: {:.2} sat/byte", tx1.fee_rate());
    blockchain.add_transaction(tx1)?;

    // Mine to confirm (alice receives the mining reward)
    blockchain.mine_pending_transactions(alice.address.clone())?;

    println!("Alice's balance: {} satoshi", blockchain.get_balance(&alice.address));

    // Second transaction: alice → bob, transfer 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's balance: {} satoshi", blockchain.get_balance(&alice.address));
    println!("Bob's balance:   {} satoshi", blockchain.get_balance(&bob.address));

    // Verify chain integrity
    assert!(blockchain.is_valid());

    Ok(())
}

Blockchain Operations

The blockchain is the core data structure of SimpleBTC — a cryptographically linked sequence of blocks, where each block contains a batch of validated transactions. This chapter covers the Block struct, the creation and management of Blockchain, the proof-of-work mining mechanism, and the chain validation and query interfaces.


Block Structure

Block Data Structure

#![allow(unused)]
fn main() {
// src/block.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
    pub index: u32,                     // Block height (genesis block is 0)
    pub timestamp: u64,                 // Unix timestamp (seconds)
    pub transactions: Vec<Transaction>, // Transaction list (first must be Coinbase)
    pub previous_hash: String,          // Parent block hash (64-character SHA-256 hex)
    pub hash: String,                   // This block's hash (valid value found by mining)
    pub nonce: u64,                     // Proof-of-work counter
    pub merkle_root: String,            // Merkle tree root hash of all transaction IDs
}
}

Block header field details:

FieldBitcoin EquivalentDescription
indexBlock HeightBlock height; genesis block is 0, incremented by 1 for each new block
timestampnTimeBlock creation time (Unix timestamp)
previous_hashhashPrevBlockParent block hash, forming the chain structure
merkle_roothashMerkleRootMerkle tree root of all transactions; a fingerprint of the block’s contents
noncenNonceA random number continuously adjusted during mining
hashBlock HashSHA-256 hash of all block header fields

Chain Structure Diagram

Genesis Block (index=0)     Block 1               Block 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]           │
└──────────────────┘     └──────────────────┘  └──────────────────┘

Why does the chain structure guarantee immutability?

  1. Modify any transaction in block 1 → merkle_root changes
  2. merkle_root changes → block 1’s hash is completely different
  3. Block 2 recorded block 1’s old hash → block 2’s previous_hash no longer matches
  4. Fixing block 2 requires re-mining (recalculating PoW), and the same applies to blocks 3, 4…
  5. An attacker would need to control more than 51% of the total network hashrate to catch up with the honest chain

Block Hash Calculation

The block hash is computed from the key fields of the block header (note: transactions are not hashed directly; the Merkle root is used instead):

#![allow(unused)]
fn main() {
// src/block.rs
pub fn calculate_hash(&self) -> String {
    use sha2::{Digest, Sha256};

    // Concatenate block header fields into a string
    let data = format!(
        "{}{}{}{}{}",
        self.index,
        self.timestamp,
        self.merkle_root,    // ← represents all transaction content
        self.previous_hash,
        self.nonce           // ← this value changes continuously during mining
    );

    let mut hasher = Sha256::new();
    hasher.update(data.as_bytes());
    format!("{:x}", hasher.finalize())
}
}

The role of the Merkle root:

  • Any change to a single transaction will cause the Merkle root to change completely
  • Verifying whether a transaction is included in a block requires only O(log n) hashes (a Merkle proof), rather than downloading all transactions

Creating a Blockchain

Blockchain Struct

#![allow(unused)]
fn main() {
// src/blockchain.rs
pub struct Blockchain {
    pub chain: Vec<Block>,           // Block list (the chain)
    pub difficulty: usize,           // Mining difficulty (number of leading zeros, default 3)
    pub mempool: Mempool,            // Memory pool (pending transactions, sorted by fee rate)
    pub utxo_set: UTXOSet,           // UTXO set (all unspent outputs)
    pub mining_reward: u64,          // Mining reward (default 50 satoshi)
    pub indexer: TransactionIndexer, // Transaction indexer (address → transaction, for fast queries)
    miner: ParallelMiner,            // Parallel PoW miner (private)
    pending_spent: HashSet<String>,  // UTXOs spent by pending transactions (double-spend prevention)
}
}

Initializing the Blockchain

#![allow(unused)]
fn main() {
use bitcoin_simulation::blockchain::Blockchain;

// Create a blockchain (automatically includes the genesis block)
let mut blockchain = Blockchain::new();

println!("Chain length: {}", blockchain.chain.len());     // 1 (genesis block only)
println!("Mining difficulty: {}", blockchain.difficulty);     // 3
println!("Mining reward: {} satoshi", blockchain.mining_reward); // 50
}

Internal flow of 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(),
    };

    // Create and add the genesis block
    let genesis_block = blockchain.create_genesis_block();
    blockchain.indexer.index_block(&genesis_block);
    blockchain.chain.push(genesis_block);

    blockchain
}
}

Genesis Block

The genesis block is the first block in the blockchain (index = 0). What makes it special:

  • previous_hash = "0" (does not reference any parent block)
  • Contains a Coinbase transaction that issues 10,000,000 satoshi of initial funds to the genesis wallet
  • Uses a deterministic genesis wallet (fixed private key 0x01), ensuring the address is the same every time the system starts
#![allow(unused)]
fn main() {
// src/blockchain.rs
fn create_genesis_block(&mut self) -> Block {
    let timestamp = /* current Unix time */;

    // Deterministic genesis wallet (fixed private key, can be spent with a signature)
    let genesis_wallet = Wallet::genesis();
    let coinbase_tx = Transaction::new_coinbase(
        genesis_wallet.address,
        10_000_000,  // Genesis block reward: 10M satoshi
        timestamp,
        0,           // No fee
    );

    // Add the genesis UTXO to the UTXO set
    self.utxo_set.add_transaction(&coinbase_tx);

    // The genesis block's previous_hash is fixed as "0"
    Block::new(0, vec![coinbase_tx], "0".to_string())
}
}

Two equivalent ways to obtain the genesis wallet:

#![allow(unused)]
fn main() {
let genesis = Blockchain::genesis_wallet();  // Static method of Blockchain
let genesis2 = Wallet::genesis();            // Obtained directly from the wallet module
assert_eq!(genesis.address, genesis2.address);
}

Proof of Work (PoW) Mining

Principle

Proof of work requires miners to find a nonce value such that the block hash satisfies the condition “the first N digits are 0”:

difficulty = 3, target hash format: 000xxxxxxxxx...

Since the output of SHA-256 is completely unpredictable, miners can only brute-force the nonce:

nonce=0: hash = "a7f3b2..." → not satisfied (does not start with "000")
nonce=1: hash = "2c91d4..." → not satisfied
...
nonce=38291: hash = "000a4b7c9..." → satisfied! Block mined

On average, 16³ = 4096 attempts are needed (difficulty 3). Real Bitcoin’s difficulty is equivalent to about 20 leading zeros, requiring approximately 2⁸⁰ attempts.

Block::mine_block() (single-threaded)

#![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!("✓ Block mined: {}", self.hash);
}
}

ParallelMiner (multi-threaded)

Blockchain::mine_pending_transactions() uses ParallelMiner instead of the single-threaded mine_block(), making full use of multi-core CPUs:

#![allow(unused)]
fn main() {
// src/blockchain.rs excerpt
self.miner
    .mine_block(&mut block, self.difficulty)
    .map_err(|e| format!("Mining failed: {}", e))?;
}

ParallelMiner splits the nonce space among multiple threads to search in parallel; the first thread to find a valid hash wins.

Difficulty and Adjustment

DifficultyLeading ZerosAverage AttemptsUse Case
11 zero16Very fast testing
22 zeros256Quick demo
33 zeros4,096Default config
44 zeros65,536Performance testing
66 zeros16,777,216Close to real-world

Adding Transactions and Mining

Complete Flow

#![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. Create a transaction
let tx = blockchain.create_transaction(
    &genesis,
    alice.address.clone(),
    5000,   // Transfer 5000 satoshi
    50,     // Fee 50 satoshi
)?;

// 2. Add to mempool (verify signature + UTXO)
blockchain.add_transaction(tx)?;

println!("Mempool transaction count: {}", blockchain.mempool.len()); // 1

// 3. Mine (alice receives the reward as miner)
blockchain.mine_pending_transactions(alice.address.clone())?;

println!("Chain length: {}", blockchain.chain.len()); // 2 (genesis + new block)
println!("Mempool transaction count: {}", blockchain.mempool.len()); // 0 (cleared)
}

Detailed flow of 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("No pending transactions".to_string());
    }

    // 1. Fetch high-fee-rate transactions from the mempool (already sorted)
    let pending_txs = self.mempool.get_top_transactions(usize::MAX);

    // 2. Calculate total fees
    let total_fees: u64 = pending_txs.iter().map(|tx| tx.fee).sum();

    // 3. Create Coinbase transaction (miner reward = block reward + total fees)
    let coinbase_tx = Transaction::new_coinbase(
        miner_address,
        self.mining_reward,  // 50 satoshi
        timestamp,
        total_fees,
    );

    // 4. Assemble the block (Coinbase must be the first transaction)
    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. Parallel PoW mining
    self.miner.mine_block(&mut block, self.difficulty)?;

    // 6. Verify all transaction signatures in the block
    if !block.validate_transactions() {
        return Err("Block contains invalid transactions".to_string());
    }

    // 7. Update UTXO set (consume input UTXOs, create output UTXOs)
    for tx in &block.transactions {
        if !self.utxo_set.process_transaction(tx) {
            return Err("UTXO update failed".to_string());
        }
    }

    // 8. Add block to chain + build index
    self.indexer.index_block(&block);
    self.chain.push(block);

    // 9. Clear mempool and pending_spent
    for tx in &pending_txs {
        let _ = self.mempool.remove_transaction(&tx.id);
    }
    self.pending_spent.clear();

    Ok(())
}
}

Merkle Tree and Transaction Verification

Block::new() automatically builds the Merkle tree and calculates the Merkle root upon creation:

#![allow(unused)]
fn main() {
pub fn new(index: u32, transactions: Vec<Transaction>, previous_hash: String) -> Block {
    // Collect all transaction IDs
    let tx_ids: Vec<String> = transactions.iter().map(|tx| tx.id.clone()).collect();

    // Build Merkle tree, calculate root hash
    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
}
}

Verifying whether a transaction is included in a block (SPV use case):

#![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
    }
}
}

Chain Validation

Blockchain::is_valid() validates the integrity of the chain block by block, starting from block 1 (skipping the genesis block):

#![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. Verify the block's own hash (prevent silent data tampering)
        if current.hash != current.calculate_hash() {
            println!("Block {} has invalid hash", i);
            return false;
        }

        // 2. Verify forward reference (chain link integrity)
        if current.previous_hash != previous.hash {
            println!("Block {} has invalid forward reference", i);
            return false;
        }

        // 3. Verify proof of work (hash leading zeros satisfy difficulty requirement)
        let target = "0".repeat(self.difficulty);
        if current.hash[..self.difficulty] != target {
            println!("Block {} has invalid proof of work", i);
            return false;
        }

        // 4. Verify ECDSA signatures of all transactions in the block
        if !current.validate_transactions() {
            println!("Block {} contains invalid transactions", i);
            return false;
        }
    }
    true
}
}

Validation example:

#![allow(unused)]
fn main() {
let mut blockchain = Blockchain::new();
// ... add transactions, mine ...

// Normal case: should pass
assert!(blockchain.is_valid());

// Simulate tampering (for educational purposes; Rust's borrow rules constrain direct access in practice)
// If someone modifies a transaction in a historical block, is_valid() will return false
}

Balance Query

Balances are calculated via the UTXO set, avoiding a scan of all historical blocks:

#![allow(unused)]
fn main() {
pub fn get_balance(&self, address: &str) -> u64 {
    self.utxo_set.get_balance(address)
}
}

Usage example:

#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&alice.address);
println!("Alice balance: {} satoshi", balance);
println!("Alice balance: {:.8} BTC", balance as f64 / 1e8);
}

Performance advantage of UTXOSet:

Without a UTXO set, querying a balance requires scanning all transactions in all blocks (O(n), where n = total number of transactions). The UTXO set caches all current unspent outputs in memory, making a query an O(1) hash table lookup.


Printing Blockchain Information

Blockchain::print_chain() provides formatted debug output:

#![allow(unused)]
fn main() {
blockchain.print_chain();
}

Sample output:

========== Blockchain Info ==========

--- Block #0 ---
Timestamp: 1711497600
Hash: 000a4b7c9d2e1f3a...
Previous hash: 0
Nonce: 38291
Transaction count: 1
  Transaction #0: f3a1b2c4...
    Type: Coinbase (mining reward)
    Input count: 1
    Output count: 1
      Output 0: 10000000 -> 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2

--- Block #1 ---
Timestamp: 1711497615
Hash: 000d2f8a1b9e4c7f...
Previous hash: 000a4b7c9d2e1f3a...
Nonce: 72481
Transaction count: 2
  Transaction #0: a1b2c3d4...
    Type: Coinbase (mining reward)
    ...
  Transaction #1: e5f6a7b8...
    Fee: 50 satoshi
    Fee rate: 0.23 sat/byte
    Input count: 1
    Output count: 2
      Output 0: 5000 -> 1AliceAddress...
      Output 1: 4994950 -> 1GenesisAddress...

================================

Complete Operation Example

use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};

fn main() -> Result<(), String> {
    // Initialize
    let mut blockchain = Blockchain::new();
    let genesis = Blockchain::genesis_wallet();
    let alice = Wallet::new();
    let bob = Wallet::new();
    let miner = Wallet::new();

    // Round 1: 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!("Block 1 mined");
    println!("Alice balance: {} sat", blockchain.get_balance(&alice.address));
    println!("Miner balance: {} sat", blockchain.get_balance(&miner.address));

    // Round 2: alice → bob (two transactions in the same block)
    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!("\nBlock 2 mined (contains 2 transactions)");
    println!("Chain length: {}", blockchain.chain.len()); // 3
    println!("Alice balance: {} sat", blockchain.get_balance(&alice.address));
    println!("Bob balance:   {} sat", blockchain.get_balance(&bob.address));
    println!("Miner balance: {} sat", blockchain.get_balance(&miner.address));

    // Verify chain integrity
    assert!(blockchain.is_valid(), "Chain validation should pass");
    println!("\nChain validation passed!");

    // Print full chain information
    blockchain.print_chain();

    Ok(())
}

Key Parameter Reference

ParameterDefaultDescription
difficulty3Mining difficulty (number of leading zeros)
mining_reward50Base block reward (satoshi)
Genesis block reward10,000,000Genesis Coinbase amount (satoshi)
Real Bitcoin initial reward5,000,000,00050 BTC (in satoshi)
Real Bitcoin halving interval210,000 blocksApproximately every 4 years
Real Bitcoin target block time10 minutesDifficulty adjusted approximately every 2016 blocks

UTXO Management

UTXO (Unspent Transaction Output) is the core of Bitcoin’s ledger model. Understanding UTXOs is a key step toward mastering how Bitcoin works. This chapter introduces the design and usage of UTXOSet in SimpleBTC.


What is a UTXO?

In the traditional banking system (and in Ethereum’s account model), the system directly records the balance of each account. Bitcoin chose a fundamentally different approach: instead of storing balances, it only stores “unspent outputs”.

Every Bitcoin transaction:

  1. Consumes some previously existing UTXOs (as inputs)
  2. Creates some new UTXOs (as outputs)

Think of Bitcoin like physical cash: you have a 100-unit bill and a 50-unit bill. You want to buy something worth 80 units, so you hand over the entire 100-unit bill and receive 20 units in change. You “spent” the 100-unit UTXO and “created” two new UTXOs — one worth 80 units for the merchant and one worth 20 units as change for yourself.

UTXO Lifecycle

Creation              Existence                  Spending              Destruction
 |                     |                          |                     |
 v                     v                          v                     v
Tx output ──────► UTXO set ──────────────► Referenced by new tx ──────► Removed from set
(packed into block)  (queryable and spendable)   (as input)           (cannot be reused)

UTXO Model vs. Account Model

FeatureUTXO Model (Bitcoin)Account Model (Ethereum)
State storageRecords all unspent outputsRecords the balance of each account
Balance calculationSum all UTXOs belonging to the addressRead the account field directly
PrivacyBetter (each transaction can use a new address)Weaker (fixed address)
Parallel processingNaturally supported (different UTXOs are independent)Requires extra concurrency control
Double-spend preventionA UTXO can only be spent onceControlled via nonce sequence numbers
Complex contractsHarder to implementNatively supported
IntuitivenessRequires understanding the UTXO conceptSimilar to a bank account; intuitive

The source code comments (src/utxo.rs, lines 8–19) provide a concise summary:

#![allow(unused)]
fn main() {
// Account model (Ethereum, etc.):
// - Records the balance of each account
// - Transfer: account A -100, account B +100
// - Simple and intuitive, but hard to process in parallel
//
// UTXO model (Bitcoin):
// - No concept of account balance
// - Only records unspent transaction outputs
// - Transfer: spend A's UTXO, create a new UTXO for B
// - Better privacy and parallelism
}

UTXOSet Data Structure

SimpleBTC uses UTXOSet to manage the set of all unspent outputs across the entire blockchain.

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct UTXOSet {
    // key: txid (transaction ID)
    // value: list of all unspent outputs for that transaction [(output index, output details)]
    utxos: HashMap<String, Vec<(usize, TxOutput)>>,
}
}
  • Key: Transaction ID (txid), a hexadecimal hash string
  • Value: The list of outputs under that transaction that have not yet been spent; each item contains the output’s index within the transaction (vout) and the output’s detailed information (TxOutput)

This data structure makes it very efficient (O(1) hash lookup) to find all available outputs for a given txid, and also makes it easy to remove a specific individual output.


Core API Details

Creating a UTXO Set

#![allow(unused)]
fn main() {
let mut utxo_set = UTXOSet::new();
}

Initializes an empty UTXO set. When the blockchain starts, it processes all transactions block by block from the genesis block to populate it.


Adding Transaction Outputs: add_transaction

#![allow(unused)]
fn main() {
pub fn add_transaction(&mut self, tx: &Transaction)
}

When a new transaction is packed into a block and confirmed, this method is called to add all outputs of that transaction to the UTXO set.

#![allow(unused)]
fn main() {
// Example: a genesis block is mined, and the coinbase reward enters the UTXO set
let coinbase_tx = Transaction::new_coinbase("miner_address".to_string(), 50, 0, 0);
utxo_set.add_transaction(&coinbase_tx);
// Now coinbase_tx.id -> [(0, TxOutput { value: 50, ... })] is in the set
}

Note: add_transaction only adds outputs; it does not process inputs (it does not remove spent UTXOs). For complete transaction processing, use process_transaction.


Removing Spent Outputs: remove_utxo

#![allow(unused)]
fn main() {
pub fn remove_utxo(&mut self, txid: &str, vout: usize)
}

When a UTXO is referenced by an input of a transaction (i.e., it is spent), it must be removed from the set. This is the core mechanism for preventing double spending.

#![allow(unused)]
fn main() {
// The user spent output #0 of txid="abc123"
utxo_set.remove_utxo("abc123", 0);
// Attempting to spend the same UTXO again will fail validation because it is no longer in the set
}

In the implementation, remove_utxo uses retain to keep other unaffected outputs; if all outputs of a transaction have been spent, the entire txid entry is also deleted:

#![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);
        }
    }
}
}

Querying All UTXOs for an Address: find_utxos

#![allow(unused)]
fn main() {
pub fn find_utxos(&self, address: &str) -> Vec<(String, usize, u64)>
}

Iterates over the entire UTXO set and returns all unspent outputs belonging to the specified address. The result format is (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);
}
}

Finding Spendable Outputs: find_spendable_outputs

#![allow(unused)]
fn main() {
pub fn find_spendable_outputs(
    &self,
    address: &str,
    amount: u64,
) -> Option<(u64, Vec<(String, usize)>)>
}

This is the most important API when creating a new transaction. It uses a greedy coin selection algorithm, accumulating UTXOs from the address one by one until the total meets amount.

#![allow(unused)]
fn main() {
// Need to pay 30 satoshis (including fees)
match utxo_set.find_spendable_outputs("alice", 30) {
    Some((accumulated, inputs)) => {
        // accumulated: the actual total selected (may be > 30; the difference is the change)
        // inputs: list of selected UTXOs, each item is (txid, vout)
        let change = accumulated - 30;
        println!("Selected {} UTXOs, change: {} satoshis", inputs.len(), change);
    }
    None => {
        println!("Insufficient balance");
    }
}
}

Change mechanism: If accumulated > amount, the difference must be returned to the sender as a change output. For example, to pay 3 BTC, you select a 5 BTC UTXO, and you need to create a 2 BTC change output (with the fee deducted from it).

UTXO selection strategy comparison:

StrategyDescriptionThis Implementation
Greedy algorithmAccumulate sequentially until the amount is metUsed here
Best matchCombination closest to the target amountReduces change
Smallest UTXO firstPrefer small-denomination UTXOsReduces fragmentation
Largest UTXO firstPrefer large-denomination UTXOsReduces number of inputs

Excluding Already-Pending UTXOs: 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)>)>
}

This is an extended version of find_spendable_outputs. When the same wallet initiates multiple transactions in quick succession, the UTXOs already selected by prior transactions have not yet been confirmed (they are still in the mempool) and cannot be reused. By passing an excluded set (formatted as "txid:vout" strings), these UTXOs already occupied by pending transactions can be skipped.

#![allow(unused)]
fn main() {
// The first transaction used "abc:0"
let mut pending_spent: HashSet<String> = HashSet::new();
pending_spent.insert("abc:0".to_string());

// The second transaction automatically skips "abc:0"
let result = utxo_set.find_spendable_outputs_excluding("alice", 20, &pending_spent);
}

Querying Balance: get_balance

#![allow(unused)]
fn main() {
pub fn get_balance(&self, address: &str) -> u64
}

A Bitcoin “balance” is a computed value, not a stored one. This method internally calls find_utxos and sums up the amounts of all UTXOs belonging to the address.

#![allow(unused)]
fn main() {
let balance = utxo_set.get_balance("alice");
println!("Alice's balance: {} satoshis", balance);
}

Key insight: Querying a balance requires scanning the entire UTXO set (time complexity O(n)). Actual Bitcoin nodes use address indexes to optimize this operation.


Complete Transaction Processing: process_transaction

#![allow(unused)]
fn main() {
pub fn process_transaction(&mut self, tx: &Transaction) -> bool
}

This is the core function for updating UTXO state, and it executes the following steps in order:

  1. Calls tx.verify() to verify the transaction signature
  2. If it is not a coinbase transaction, removes all UTXOs referenced by the inputs
  3. Adds all of the transaction’s outputs to the UTXO set
#![allow(unused)]
fn main() {
// Process a regular transaction
let success = utxo_set.process_transaction(&transfer_tx);
if !success {
    eprintln!("Transaction verification failed; UTXO set was not modified");
}
}

This function has atomic semantics — if verification fails, the UTXO set is not modified, ensuring state consistency.


Double-Spend Prevention Mechanism

Double spending is the core security problem that blockchain must solve. The UTXO model naturally defends against double spending:

Attack flow:
1. Attacker has a UTXO worth 10 BTC (txid="xyz", vout=0)
2. Creates transaction A: spend xyz:0, pay merchant 10 BTC
3. Merchant accepts; transaction A enters the mempool
4. Attacker creates transaction B: also spend xyz:0, pay themselves 10 BTC
5. Attempts to broadcast transaction B

Defense result:
- After transaction A is confirmed, xyz:0 is deleted from the UTXO set
- When transaction B is validated, xyz:0 is not found and is rejected by the node
- Even if transaction A is unconfirmed, the mempool's double-spend detection rejects transaction B at step 3

In SimpleBTC’s Mempool, utxo_index (HashMap<"txid:vout", spending_txid>) records which UTXOs have already been claimed by transactions in the mempool, enabling double-spend detection and rejection of transaction B at step 3.


Complete Usage Example

use bitcoin_simulation::utxo::UTXOSet;
use bitcoin_simulation::transaction::Transaction;

fn main() {
    let mut utxo_set = UTXOSet::new();

    // Step 1: Mine a block, create a coinbase transaction (bitcoin created from scratch)
    let coinbase = Transaction::new_coinbase("alice".to_string(), 50, 0, 0);
    utxo_set.process_transaction(&coinbase);

    // Step 2: Query Alice's balance
    let alice_balance = utxo_set.get_balance("alice");
    println!("Alice balance: {} satoshis", alice_balance); // Output: 50

    // Step 3: Alice transfers 20 satoshis to Bob (fee: 2 satoshis)
    let needed = 22; // 20 for Bob + 2 fee
    if let Some((accumulated, inputs)) = utxo_set.find_spendable_outputs("alice", needed) {
        let change = accumulated - needed;
        println!("Selected {} UTXOs, total: {}, change: {}", inputs.len(), accumulated, change);

        // Build and broadcast the transaction (signature details omitted here)
        // let tx = build_transaction(inputs, "bob", 20, "alice", change, 2);
        // utxo_set.process_transaction(&tx);
    }

    // Step 4: Query Bob's balance
    // println!("Bob balance: {}", utxo_set.get_balance("bob"));
}

Summary

The UTXO model is the cornerstone of Bitcoin’s architecture. UTXOSet ensures ledger security through the following mechanisms:

  • process_transaction: Atomically updates UTXO state (removes inputs first, then adds outputs)
  • remove_utxo: Ensures each UTXO can only be spent once, preventing double spending
  • find_spendable_outputs_excluding: Resolves UTXO conflicts for consecutive transactions by tracking via pending_spent
  • get_balance: Balance is a computed value — the sum of all UTXOs belonging to that address

The next chapter will cover the specific structure of transactions and the signature verification mechanism.

Merkle Tree and SPV Verification

Merkle trees (hash trees) and Simplified Payment Verification (SPV) are the technical foundation for Bitcoin’s lightweight clients. They allow a mobile wallet to securely verify transactions with only a few MB of storage, without needing to download the full blockchain of over 500 GB.


What is a Merkle Tree?

A Merkle tree is a binary hash tree invented by computer scientist Ralph Merkle in 1979. Its core idea is: by recursively hashing data, any quantity of data can be compressed into a fixed-length “fingerprint” (the root hash).

In Bitcoin, all the transactions included in each block are organized into a Merkle tree. The tree’s root hash (Merkle Root) is stored in the block header, protected by proof of work (PoW). Any tampering with the transaction data will cause the root hash to change, invalidating that block and all subsequent blocks.

Tree Structure Diagram (4 transactions)

                    ┌─────────────┐
                    │  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
      (Transaction 1) (Transaction 2) (Transaction 3) (Transaction 4)

Construction Process

Construction follows a bottom-up approach in two phases:

Phase 1: Build the Leaf Layer

The raw data of each transaction is SHA-256 hashed to become a leaf node:

H1 = SHA256(tx1_data)
H2 = SHA256(tx2_data)
H3 = SHA256(tx3_data)
H4 = SHA256(tx4_data)

Odd number handling: If the number of transactions is odd, the last transaction is duplicated to make the layer count even. This is the standard practice specified by the Bitcoin protocol.

Phase 2: Merge Layer by Layer to the Root

Each pair of adjacent nodes’ hashes is concatenated and then hashed to produce the parent node:

H12 = SHA256(H1 + H2)
H34 = SHA256(H3 + H4)
Root = SHA256(H12 + H34)

This process is repeated until only one node remains, which is the Merkle root.


Implementation in SimpleBTC

Node Structure: MerkleNode

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct MerkleNode {
    pub hash: String,                   // The node's hash value
    pub left: Option<Box<MerkleNode>>,  // Left child node (only for internal nodes)
    pub right: Option<Box<MerkleNode>>, // Right child node (only for internal nodes)
}
}
  • Leaf nodes: Both left and right are None; hash is the SHA-256 value of the transaction data
  • Internal nodes: Have left and right child nodes; hash is SHA256(left.hash + right.hash)
  • Root node: The top node of the tree; the final Merkle Root

Two factory methods for creating nodes:

#![allow(unused)]
fn main() {
// Leaf node: hash the raw data directly
let leaf = MerkleNode::new_leaf("tx_data_string");

// Internal node: merge two child nodes
let parent = MerkleNode::new_internal(left_node, right_node);
}

Tree Structure: MerkleTree

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct MerkleTree {
    pub root: Option<MerkleNode>, // Tree root node
    pub leaves: Vec<String>,      // List of original transaction hashes
}
}

Building the Merkle Tree: MerkleTree::new

#![allow(unused)]
fn main() {
pub fn new(transactions: &[String]) -> Self
}

Accepts a list of transaction IDs (strings) and automatically builds the complete Merkle tree:

#![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);
// Output: a 64-character hexadecimal hash string
}

Key steps in the internal implementation:

#![allow(unused)]
fn main() {
// 1. Pad odd count
if !leaves.len().is_multiple_of(2) {
    leaves.push(leaves.last().unwrap().clone());
}

// 2. Build the leaf node layer
let mut nodes: Vec<MerkleNode> = leaves.iter()
    .map(|tx| MerkleNode::new_leaf(tx))
    .collect();

// 3. Merge layer by layer from bottom to top
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(); // Even count already guaranteed
        next_level.push(MerkleNode::new_internal(left, right));
    }
    nodes = next_level;
}
}

Generating a Merkle Proof: get_proof

#![allow(unused)]
fn main() {
pub fn get_proof(&self, tx_hash: &str) -> Option<Vec<String>>
}

Generates a Merkle proof (also called a “Merkle path”) for the specified transaction. This proof contains the hashes of all sibling nodes along the path from that transaction’s leaf node to the root node.

#![allow(unused)]
fn main() {
// Generate a proof for tx1
let proof = tree.get_proof("tx1_hash").unwrap();
// proof = [H2, H34]  ← list of sibling hashes needed for verification
}

Diagram: proof needed to verify tx1

                    ┌────────────┐
                    │    Root    │ ← known (stored in block header)
                    └─────┬──────┘
               ┌──────────┴──────────┐
        ┌──────┴──────┐       ┌──────┴──────┐
        │     H12     │       │ ★ H34 ★    │ ← proof element[1]
        └──────┬──────┘       └─────────────┘
       ┌───────┴───────┐
    ┌──┴──┐       ┌────┴──┐
    │  H1 │       │★ H2 ★│ ← proof element[0]
    └──┬──┘       └───────┘
       │
     [tx1]  ← the transaction to verify (known)

The verifier only needs two hashes [H2, H34] (log₂4 = 2 steps), without needing to know the contents of tx2, tx3, or tx4.


Verifying a Merkle Proof: verify_proof

#![allow(unused)]
fn main() {
pub fn verify_proof(
    tx_hash: &str,      // Hash of the transaction to verify
    proof: &[String],   // Merkle proof (list of sibling hashes)
    root_hash: &str,    // Merkle root from the block header
    index: usize,       // The transaction's index position in the block
) -> bool
}

This is a static method; verification is possible without holding the complete Merkle tree. SPV clients use exactly this method to verify transactions.

#![allow(unused)]
fn main() {
// Known: tx1 is in the block at index 0; Merkle Root comes from the block header
let is_valid = MerkleTree::verify_proof(
    "tx1_hash",
    &proof,      // [H2, H34]
    &root_hash,  // from block header, protected by PoW
    0,           // tx1 is transaction #0
);
println!("Transaction verification result: {}", is_valid); // true
}

Verification algorithm steps (using tx1, index=0 as an example):

Step 1: current_hash = SHA256("tx1_hash")      → get H1
        index=0 (even), H1 is on the left
        combined = H1 + proof[0] (H2)
        current_hash = SHA256(H1 + H2)         → get H12
        index = 0 / 2 = 0

Step 2: index=0 (even), H12 is on the left
        combined = H12 + proof[1] (H34)
        current_hash = SHA256(H12 + H34)       → get the computed Root

Verify: computed Root == merkle_root in the block header?

Source code implementation:

#![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) {
            // Current node is on the left, sibling is on the right
            format!("{}{}", current_hash, sibling_hash)
        } else {
            // Current node is on the right, sibling is on the left
            format!("{}{}", sibling_hash, current_hash)
        };
        current_hash = MerkleNode::hash_data(&combined);
        current_index /= 2;
    }

    current_hash == root_hash
}
}

SPV Light Clients

SPV Concept

SPV (Simplified Payment Verification) was proposed by Satoshi Nakamoto in Section 8 of the Bitcoin whitepaper. Its core idea is: a light client does not need to verify all transactions; it only needs to trust the longest proof-of-work chain and use Merkle proofs to verify transactions relevant to itself.

FeatureFull NodeSPV Node
Storage requirement400+ GB (full blockchain)~5 MB (block headers only)
Bandwidth consumptionFull blocks (1–4 MB/block)Block headers only (80 bytes/block)
Verification scopeAll transactionsOnly transactions relevant to itself
Security levelHighest (fully self-verified)Relies on PoW; trusts miner honesty
Suitable forMining pools, exchanges, full nodesMobile wallets, embedded devices

SPV Implementation in SimpleBTC

Block Header Structure: BlockHeader

SPV clients only download and store block headers, not the transaction body:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockHeader {
    pub height: u32,           // Block height
    pub hash: String,          // Block hash
    pub previous_hash: String, // Previous block hash (ensures chain structure)
    pub merkle_root: String,   // Merkle root (32 bytes, used to verify transactions)
    pub timestamp: u64,        // Timestamp
    pub bits: u32,             // Difficulty target
    pub nonce: u64,            // Proof-of-work nonce
}
}

Each block header is a fixed 80 bytes. Bitcoin currently has approximately 830,000 blocks, so the total size of all block headers is about 66 MB — a tremendous saving compared to the full blockchain of 600+ GB.

SPV Client: SPVClient

#![allow(unused)]
fn main() {
pub struct SPVClient {
    headers: Vec<BlockHeader>,               // Block header chain
    header_index: HashMap<String, BlockHeader>, // hash → header for fast lookup
    verified_transactions: HashMap<String, (String, bool)>, // txid → (block_hash, verified result)
    chain_tip: Option<String>,               // Current latest block hash
    total_work: u64,                         // Accumulated work
}
}

SPV Workflow

Step 1: Sync Block Headers

#![allow(unused)]
fn main() {
use bitcoin_simulation::spv::SPVClient;

let mut client = SPVClient::new();

// Fetch blocks from a full node and extract their headers
let blocks = /* fetched from the P2P network */;
client.sync_from_blocks(&blocks).unwrap();

println!("Synced {} block headers", client.get_height());
println!("Storage used: {} bytes", client.estimate_storage_size());
// 1000 block headers require only 80,000 bytes (about 78 KB)
}

Headers can also be added one at a time:

#![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();
}

The continuity of the block header chain is automatically verified by add_block_header: the new header’s previous_hash must match the hash of the previous header; otherwise it is rejected:

#![allow(unused)]
fn main() {
// Attempting to add a non-continuous block header returns an error
let bad_header = BlockHeader {
    height: 1,
    hash: "block_1".to_string(),
    previous_hash: "wrong_hash".to_string(), // does not match!
    // ...
};
let result = client.add_block_header(bad_header);
assert!(result.is_err()); // rejected
}

Step 2: Verify Transaction Inclusion

When a user receives a payment, they need to verify that this transaction has indeed been packed into a block:

#![allow(unused)]
fn main() {
// Suppose a merchant receives a payment notification: tx_id is at position 0 in block_hash
let tx_id = "payment_tx_hash";
let block_hash = "some_block_hash";

// Request a Merkle proof from a full node (in practice, this is done via P2P protocol)
let proof = vec!["sibling_hash_1".to_string(), "sibling_hash_2".to_string()];
let tx_index = 0; // Position of the transaction in the block

let is_valid = client.verify_transaction(tx_id, &proof, block_hash, tx_index).unwrap();
if is_valid {
    println!("Payment confirmed! Transaction {} is in the block", tx_id);
} else {
    println!("Verification failed; the transaction may not be in that block");
}
}

Step 3: Check Historical Verification Results

#![allow(unused)]
fn main() {
// Check whether a transaction has already passed SPV verification
if let Some(verified) = client.is_transaction_verified(tx_id) {
    if verified {
        println!("This transaction has been verified");
    }
}

// Get SPV statistics
let stats = client.get_stats();
println!("Block header count: {}", stats.header_count);
println!("Storage size: {} bytes", stats.storage_size);
println!("Verified transaction count: {}", stats.verified_tx_count);
}

Complete Example: Build a Tree and Perform SPV Verification

use bitcoin_simulation::merkle::MerkleTree;
use bitcoin_simulation::spv::{SPVClient, BlockHeader};

fn main() {
    // 1. Assume a block contains 4 transactions
    let transactions = vec![
        "tx1".to_string(),
        "tx2".to_string(),
        "tx3".to_string(),
        "tx4".to_string(),
    ];

    // 2. Build the Merkle tree (what a full node does)
    let tree = MerkleTree::new(&transactions);
    let merkle_root = tree.get_root_hash();
    println!("Merkle Root: {}", merkle_root);

    // 3. Generate a proof for tx1 (full node generates it at the SPV client's request)
    let proof = tree.get_proof("tx1").unwrap();
    println!("Merkle proof for tx1 contains {} hashes", proof.len());

    // 4. SPV client verification (knows only the block header and proof, not the other transactions)
    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 verification result: {}", valid); // true

    // 5. Verify directly using the static method (no SPVClient needed)
    let valid2 = MerkleTree::verify_proof("tx1", &proof, &merkle_root, 0);
    println!("Static verification result: {}", valid2); // true
}

Why is SPV Verification Secure?

An attacker cannot forge a Merkle proof for two reasons:

  1. SHA-256 collision resistance: Finding two different inputs that produce the same hash is computationally infeasible (requires approximately 2¹²⁸ hash operations).
  2. PoW protection: The merkle_root is stored in the block header, which is protected by proof of work. To forge a block header containing a fake merkle_root, an attacker would need to redo the mining work for that block and all subsequent blocks, which is computationally extremely difficult (the “longest chain rule”).

The only trust assumption of SPV is: honest miners control more than 51% of the hashrate. As long as this assumption holds, an attacker cannot deceive an SPV client at any practical cost.


Summary

ComponentRole
MerkleNodeBasic unit of the Merkle tree; stores the hash value and references to child nodes
MerkleTree::newBuilds the complete Merkle tree bottom-up from a list of transactions
MerkleTree::get_proofGenerates an O(log n)-sized Merkle proof for a specified transaction
MerkleTree::verify_proofVerifies a transaction using a proof + root hash; O(log n) time complexity
BlockHeaderBlock header; 80 bytes; contains the Merkle Root
SPVClientLight client; downloads only block headers and verifies transactions using Merkle proofs

Multi-Signature (MultiSig)

Multi-signature is an advanced Bitcoin feature that requires M signatures to spend funds controlled by N public keys (M-of-N).

Overview

What is Multi-Signature?

A multi-signature address requires multiple private keys to jointly sign before funds can be spent, rather than a single private key as in traditional setups.

Examples:

  • 2-of-3: Requires any 2 of 3 keys
  • 3-of-5: Requires any 3 of 5 keys
  • 2-of-2: Requires both keys to agree

Why Use MultiSig?

1. Improved Security

  • No single point of failure
  • A stolen private key does not immediately result in fund loss
  • Risk is distributed

2. Distributed Trust

  • Corporate governance: prevents misuse by a single person
  • Escrow services: buyer + seller + arbitrator
  • Joint family management: shared management by spouses

3. Flexibility

  • Different M-N combinations meet different needs
  • Emergency recovery mechanisms can be set up
  • Supports complex business logic

Technical Implementation

MultiSig Address Structure

#![allow(unused)]
fn main() {
pub struct MultiSigAddress {
    pub address: String,            // Multi-sig address (starts with "3")
    pub required_sigs: usize,       // M (number of required signatures)
    pub total_keys: usize,          // N (total number of keys)
    pub public_keys: Vec<String>,   // Public keys of all participants
    pub script: String,             // Locking script
}
}

Creating a Multi-Sig Address

#![allow(unused)]
fn main() {
use bitcoin_simulation::{multisig::MultiSigAddress, wallet::Wallet};

// Create participants
let ceo = Wallet::new();
let cfo = Wallet::new();
let cto = Wallet::new();

// Collect public keys
let public_keys = vec![
    ceo.public_key.clone(),
    cfo.public_key.clone(),
    cto.public_key.clone(),
];

// Create a 2-of-3 multi-sig address
let multisig = MultiSigAddress::new(2, public_keys)?;

println!("Multi-sig address: {}", multisig.address);
println!("Required signatures: {}/{}", multisig.required_sigs, multisig.total_keys);
}

MultiSig Types

SimpleBTC provides preset common multi-sig types:

#![allow(unused)]
fn main() {
use bitcoin_simulation::multisig::MultiSigType;

// 2-of-2: both parties must agree
let two_of_two = MultiSigAddress::from_type(
    MultiSigType::TwoOfTwo,
    vec![alice.public_key, bob.public_key]
)?;

// 2-of-3: any two parties suffice (most common)
let two_of_three = MultiSigAddress::from_type(
    MultiSigType::TwoOfThree,
    vec![party1.public_key, party2.public_key, party3.public_key]
)?;

// 3-of-5: high-security scenarios
let three_of_five = MultiSigAddress::from_type(
    MultiSigType::ThreeOfFive,
    vec![pk1, pk2, pk3, pk4, pk5]
)?;
}

Use Cases

Use Case 1: Corporate Financial Management

Requirement: Company funds require joint approval from multiple executives

Solution: 2-of-3 MultiSig (CEO + CFO + CTO)

#![allow(unused)]
fn main() {
fn setup_corporate_wallet() -> Result<MultiSigAddress, String> {
    // 1. Create executive wallets
    let ceo = Wallet::new();
    let cfo = Wallet::new();
    let cto = Wallet::new();

    println!("=== Corporate Multi-Sig Wallet ===");
    println!("CEO: {}", &ceo.address[..16]);
    println!("CFO: {}", &cfo.address[..16]);
    println!("CTO: {}", &cto.address[..16]);

    // 2. Create multi-sig address
    let company_wallet = MultiSigAddress::new(
        2,  // Requires 2 signatures
        vec![
            ceo.public_key.clone(),
            cfo.public_key.clone(),
            cto.public_key.clone(),
        ]
    )?;

    println!("\nCompany multi-sig address: {}", company_wallet.address);
    println!("Rule: any 2 executives can authorize a transfer\n");

    Ok(company_wallet)
}

// Transfer scenario
fn corporate_payment(
    multisig: &MultiSigAddress,
    ceo: &Wallet,
    cfo: &Wallet,
    recipient: &str,
    amount: u64
) -> Result<(), String> {
    println!("Transferring {} satoshi to {}", amount, &recipient[..16]);

    // 1. CEO signs
    let ceo_sig = ceo.sign(&format!("{}{}", multisig.address, amount));
    println!("✓ CEO has signed");

    // 2. CFO signs
    let cfo_sig = cfo.sign(&format!("{}{}", multisig.address, amount));
    println!("✓ CFO has signed");

    // 3. Collect signatures
    let signatures = vec![ceo_sig, cfo_sig];

    // 4. Verify signature count
    if signatures.len() >= multisig.required_sigs {
        println!("✅ Signature count meets requirement; transaction can be executed");
        // Create and broadcast transaction...
        Ok(())
    } else {
        Err("Insufficient signatures".to_string())
    }
}
}

Advantages:

  • ✅ Prevents misuse of funds by a single person
  • ✅ CFO + CTO can still operate when CEO is traveling
  • ✅ Even if one person is compromised, funds remain safe

Use Case 2: Escrow Service

Requirement: Buyer and seller do not trust each other; a third-party arbitrator is needed

Solution: 2-of-3 MultiSig (buyer + seller + arbitrator)

#![allow(unused)]
fn main() {
fn escrow_service() -> Result<(), String> {
    // Participants
    let buyer = Wallet::new();
    let seller = Wallet::new();
    let arbitrator = Wallet::new();

    println!("=== Escrow Service ===");
    println!("Buyer: {}", &buyer.address[..16]);
    println!("Seller: {}", &seller.address[..16]);
    println!("Arbitrator: {}", &arbitrator.address[..16]);

    // Create escrow multi-sig address
    let escrow = MultiSigAddress::new(
        2,
        vec![
            buyer.public_key.clone(),
            seller.public_key.clone(),
            arbitrator.public_key.clone(),
        ]
    )?;

    println!("\nEscrow address: {}", escrow.address);

    // Scenario 1: Normal transaction (buyer + seller)
    println!("\n--- Scenario 1: Transaction completed smoothly ---");
    println!("Buyer received goods; satisfied");
    println!("Buyer signs: ✓");
    println!("Seller signs: ✓");
    println!("✅ 2/3 signatures; funds released to seller");

    // Scenario 2: Dispute (buyer + arbitrator or seller + arbitrator)
    println!("\n--- Scenario 2: Dispute arises ---");
    println!("Buyer: goods are defective");
    println!("Seller: goods are fine");
    println!("Arbitrator investigates...");
    println!("Arbitrator: buyer is right");
    println!("Buyer signs: ✓");
    println!("Arbitrator signs: ✓");
    println!("✅ 2/3 signatures; funds refunded to buyer");

    Ok(())
}
}

Advantages:

  • ✅ Buyer protection: refund if goods don’t match description
  • ✅ Seller protection: funds released automatically for normal transactions
  • ✅ Fair: arbitrator cannot control funds alone

Use Case 3: Personal Asset Protection

Requirement: Prevent loss due to a single private key being lost or stolen

Solution: 2-of-3 MultiSig (primary key + backup key + custodian key)

#![allow(unused)]
fn main() {
fn personal_security_setup() -> Result<(), String> {
    // Key assignment
    let main_key = Wallet::new();      // Daily use
    let backup_key = Wallet::new();    // Safe deposit box
    let custodian_key = Wallet::new(); // Lawyer / trust company

    println!("=== Personal Asset Protection ===");
    println!("Primary key (daily): {}", &main_key.address[..16]);
    println!("Backup key (safe): {}", &backup_key.address[..16]);
    println!("Custodian key (lawyer): {}", &custodian_key.address[..16]);

    let secure_wallet = MultiSigAddress::new(
        2,
        vec![
            main_key.public_key,
            backup_key.public_key,
            custodian_key.public_key,
        ]
    )?;

    println!("\nSecure wallet: {}", secure_wallet.address);

    // Usage scenarios
    println!("\n--- Usage Scenarios ---");
    println!("Daily transfers: primary key + backup key");
    println!("Primary key lost: backup key + custodian key");
    println!("Theft risk: requires 2 keys; single key theft poses no risk");

    Ok(())
}
}

Use Case 4: Cold-Hot Wallet Combination

Requirement: Security for large storage + convenience for small amounts

Solution: 2-of-3 (hot wallet + cold wallet 1 + cold wallet 2)

#![allow(unused)]
fn main() {
fn cold_hot_wallet_setup() -> Result<(), String> {
    let hot_wallet = Wallet::new();    // Online device
    let cold_wallet_1 = Wallet::new(); // Hardware wallet 1
    let cold_wallet_2 = Wallet::new(); // Paper wallet

    println!("=== Cold-Hot Wallet Combination ===");
    println!("Hot wallet (phone): {}", &hot_wallet.address[..16]);
    println!("Cold wallet 1 (Ledger): {}", &cold_wallet_1.address[..16]);
    println!("Cold wallet 2 (paper wallet): {}", &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!("\nVault address: {}", vault.address);

    println!("\n--- Usage Strategy ---");
    println!("Daily small amounts: hot wallet + cold wallet 1 (convenient)");
    println!("Large transfers: cold wallet 1 + cold wallet 2 (most secure)");
    println!("Hot wallet hacked: still requires cold wallet cooperation; funds safe");

    Ok(())
}
}

Advanced Usage

TimeLock + MultiSig

Combining time locks for inheritance planning:

#![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();

    // Normal: 2-of-2 (owner + heir; protects privacy)
    let normal_multisig = MultiSigAddress::new(
        2,
        vec![owner.public_key.clone(), heir.public_key.clone()]
    )?;

    // Time lock: 1 year later
    let one_year = 365 * 24 * 60 * 60;
    let unlock_time = current_timestamp() + one_year;
    let timelock = TimeLock::new_time_based(unlock_time);

    println!("=== Inheritance Plan ===");
    println!("Normal period: requires owner + heir (2-of-2)");
    println!("After 1 year: heir can operate independently");

    // Or use 3-of-3, downgraded to 2-of-3 after 1 year
    let emergency_multisig = MultiSigAddress::new(
        2,  // Only 2 required after 1 year
        vec![owner.public_key, heir.public_key, lawyer.public_key]
    )?;

    Ok(())
}
}

Hierarchical MultiSig

Multi-level multi-sig structure for large organizations:

#![allow(unused)]
fn main() {
// Board of directors: 5-of-9
let board = MultiSigAddress::new(5, board_members)?;

// Executive committee: 3-of-5
let exec_committee = MultiSigAddress::new(3, executives)?;

// Petty cash: 2-of-3
let petty_cash = MultiSigAddress::new(2, managers)?;

println!("Permission levels:");
println!("< 10 BTC: manager level 2-of-3");
println!("10-100 BTC: executive level 3-of-5");
println!("> 100 BTC: board level 5-of-9");
}

Security Considerations

⚠️ Important Notes

  1. Key Management

    • Store keys in dispersed locations; do not keep them together
    • Use hardware wallets for cold keys
    • Regularly test backup recovery
  2. Choosing M

    • M too small: security is reduced
    • M too large: usability is reduced
    • Recommended: M = (N+1)/2 or N-1
  3. Choosing N

    • N=2: simple but has a single point of failure
    • N=3: balances security and convenience (most common)
    • N=5+: high security but complex
  4. Choosing Participants

    • Geographically dispersed
    • Trusted but mutually independent
    • Have emergency contact information

Best Practices

#![allow(unused)]
fn main() {
// ✅ Good practice
let multisig = MultiSigAddress::new(
    2,  // Reasonable M value
    vec![key1, key2, key3]  // 3 independent keys
)?;

// Dispersed storage
// key1 -> mobile hot wallet
// key2 -> hardware wallet (safe)
// key3 -> paper wallet (bank safe deposit box)

// ❌ Bad practice
// All keys stored on the same computer
// M=N (loses fault tolerance)
// Multiple keys derived from the same mnemonic
}

Complete Example

#![allow(unused)]
fn main() {
use bitcoin_simulation::{
    blockchain::Blockchain,
    wallet::Wallet,
    multisig::MultiSigAddress,
};

fn complete_multisig_demo() -> Result<(), String> {
    let mut blockchain = Blockchain::new();

    // Create participants
    let alice = Wallet::new();
    let bob = Wallet::new();
    let charlie = Wallet::new();

    // Create 2-of-3 multi-sig
    let multisig = MultiSigAddress::new(
        2,
        vec![
            alice.public_key.clone(),
            bob.public_key.clone(),
            charlie.public_key.clone(),
        ]
    )?;

    println!("Multi-sig address: {}", multisig.address);

    // 1. Deposit funds
    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!("Multi-sig balance: {}", blockchain.get_balance(&multisig.address));

    // 2. Multi-sig transfer (requires 2 signatures)
    let recipient = Wallet::new();

    // Alice signs
    let alice_sig = alice.sign(&format!("{}{}",
        multisig.address, recipient.address));

    // Bob signs
    let bob_sig = bob.sign(&format!("{}{}",
        multisig.address, recipient.address));

    // Verify signatures
    println!("\nCollecting signatures:");
    println!("Alice: ✓");
    println!("Bob: ✓");

    if vec![alice_sig, bob_sig].len() >= multisig.required_sigs {
        println!("✅ Signatures meet requirement; transfer can proceed");

        // Create transfer transaction
        // Note: actual implementation requires multi-sig transaction building logic
        println!("Transaction created and broadcast");
    }

    Ok(())
}
}

References


Next: TimeLock Tutorial | RBF Mechanism

Back to Advanced Features

Replace-By-Fee (RBF) Mechanism

RBF (Replace-By-Fee) is a mechanism proposed in BIP125 that allows users to replace unconfirmed transactions.

Overview

What is RBF?

RBF allows a sender to replace an unconfirmed transaction with a new transaction carrying a higher fee.

Scenario:

1. Alice sends transaction A: 1 BTC → Bob, fee 1 sat/byte
2. Network congestion; transaction A remains unconfirmed for a long time
3. Alice sends transaction B: 1 BTC → Bob, fee 50 sat/byte
4. Miners prioritize packing transaction B (higher fee)
5. Transaction A is discarded

Why is RBF Needed?

  1. Speed up confirmation

    • Initial fee estimate was inaccurate
    • Network suddenly became congested
    • Urgent transaction needs fast confirmation
  2. Cancel a transaction

    • Sent to the wrong address
    • Changed your mind
    • “Cancel” by sending to yourself
  3. Batch optimization

    • Initial transaction includes some recipients
    • Add more recipients later
    • Save on total fees

Technical Implementation

RBF Signaling

nSequence field:

#![allow(unused)]
fn main() {
// Enable RBF
input.sequence = 0xFFFFFFFD;  // < 0xFFFFFFFE

// Disable RBF (final transaction)
input.sequence = 0xFFFFFFFF;
}

BIP125 Rules

A replacement transaction must satisfy:

  1. Higher fee

    #![allow(unused)]
    fn main() {
    new_tx.fee > original_tx.fee
    }
  2. Spends the same UTXOs

    #![allow(unused)]
    fn main() {
    new_tx.inputs == original_tx.inputs
    }
  3. Fee increment

    #![allow(unused)]
    fn main() {
    new_tx.fee >= original_tx.fee + min_relay_fee
    }
  4. Does not introduce new unconfirmed UTXOs


RBFManager Implementation

Data Structure

#![allow(unused)]
fn main() {
pub struct RBFManager {
    replaceable_txs: Vec<String>,  // List of replaceable transaction IDs
}
}

Methods

new

#![allow(unused)]
fn main() {
pub fn new() -> Self
}

Creates a new RBF manager.

mark_replaceable

#![allow(unused)]
fn main() {
pub fn mark_replaceable(&mut self, txid: String)
}

Marks a transaction as replaceable.

Example:

#![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
}

Checks whether a transaction is replaceable.

replace_transaction

#![allow(unused)]
fn main() {
pub fn replace_transaction(
    &mut self,
    original_txid: &str,
    new_tx: Transaction
) -> Result<(), String>
}

Replaces the original transaction with a new one.

Validation:

  1. The original transaction must be replaceable
  2. The new transaction has a higher fee
  3. The new transaction is valid

Use Cases

Use Case 1: Speeding Up Confirmation

#![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();

    // Initialize balance
    setup_balance(&mut blockchain, &alice, 10000)?;

    println!("=== RBF Transaction Acceleration Demo ===\n");

    // 1. Create a low-fee transaction
    println!("--- Step 1: Send a low-fee transaction ---");
    let slow_tx = blockchain.create_transaction(
        &alice,
        bob.address.clone(),
        1000,
        1,  // Low fee: 1 sat
    )?;

    println!("Original transaction:");
    println!("  ID: {}", &slow_tx.id[..16]);
    println!("  Amount: 1000 sat");
    println!("  Fee: 1 sat");
    println!("  Fee rate: {:.2} sat/byte\n", slow_tx.fee_rate());

    blockchain.add_transaction(slow_tx.clone())?;
    rbf.mark_replaceable(slow_tx.id.clone());

    // 2. Network congestion; transaction remains unconfirmed for a long time
    println!("--- Step 2: Network congestion ---");
    println!("⏰ Waiting for confirmation...");
    println!("⏰ Still unconfirmed after 10 minutes");
    println!("⚠️  Fee too low; need to accelerate\n");

    // 3. Create a high-fee replacement transaction
    println!("--- Step 3: Create replacement transaction (higher fee) ---");
    let fast_tx = blockchain.create_transaction(
        &alice,
        bob.address.clone(),
        1000,
        50,  // High fee: 50 sat
    )?;

    println!("Replacement transaction:");
    println!("  ID: {}", &fast_tx.id[..16]);
    println!("  Amount: 1000 sat");
    println!("  Fee: 50 sat (50x)");
    println!("  Fee rate: {:.2} sat/byte\n", fast_tx.fee_rate());

    // 4. Validate and replace
    if rbf.is_replaceable(&slow_tx.id) {
        if fast_tx.fee > slow_tx.fee {
            println!("✓ RBF conditions met:");
            println!("  New fee ({}) > Original fee ({})", fast_tx.fee, slow_tx.fee);

            // Remove the original transaction from the pending pool
            blockchain.pending_transactions.retain(|tx| tx.id != slow_tx.id);

            // Add the new transaction
            blockchain.add_transaction(fast_tx)?;

            println!("✓ Transaction replaced\n");
        }
    }

    // 5. Mine and confirm
    println!("--- Step 4: Miner packs (prioritizes high fee rate) ---");
    blockchain.mine_pending_transactions(alice.address.clone())?;

    println!("✓ Transaction confirmed");
    println!("  Bob balance: {} sat", blockchain.get_balance(&bob.address));

    Ok(())
}
}

Output:

=== RBF Transaction Acceleration Demo ===

--- Step 1: Send a low-fee transaction ---
Original transaction:
  ID: abc123...
  Amount: 1000 sat
  Fee: 1 sat
  Fee rate: 0.01 sat/byte

--- Step 2: Network congestion ---
⏰ Waiting for confirmation...
⏰ Still unconfirmed after 10 minutes
⚠️  Fee too low; need to accelerate

--- Step 3: Create replacement transaction (higher fee) ---
Replacement transaction:
  ID: def456...
  Amount: 1000 sat
  Fee: 50 sat (50x)
  Fee rate: 0.50 sat/byte

✓ RBF conditions met:
  New fee (50) > Original fee (1)
✓ Transaction replaced

--- Step 4: Miner packs (prioritizes high fee rate) ---
✓ Transaction confirmed
  Bob balance: 1000 sat

Use Case 2: Canceling a Transaction

#![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;  // Wrong address

    setup_balance(&mut blockchain, &alice, 10000)?;

    println!("=== RBF Transaction Cancellation Demo ===\n");

    // 1. Sent to the wrong address
    println!("--- Error: sent to wrong address ---");
    let wrong_tx = blockchain.create_transaction(
        &alice,
        wrong_addr.clone(),
        5000,
        10,
    )?;

    println!("Erroneous transaction:");
    println!("  Recipient: {} (wrong!)", &wrong_addr[..16]);
    println!("  Amount: 5000 sat\n");

    blockchain.add_transaction(wrong_tx.clone())?;
    rbf.mark_replaceable(wrong_tx.id.clone());

    // 2. Error discovered; attempt to cancel
    println!("--- Error discovered; attempting to cancel ---");
    println!("Strategy: send to yourself with a higher fee\n");

    // 3. Create a "cancel" transaction (send to yourself)
    let cancel_tx = blockchain.create_transaction(
        &alice,
        alice.address.clone(),  // Send to yourself
        4950,  // Slightly less (fee deducted)
        50,    // Higher fee
    )?;

    println!("Cancellation transaction:");
    println!("  Recipient: {} (yourself)", &alice.address[..16]);
    println!("  Amount: 4950 sat");
    println!("  Fee: 50 sat (5x)\n");

    // 4. Replace
    if cancel_tx.fee > wrong_tx.fee {
        blockchain.pending_transactions.retain(|tx| tx.id != wrong_tx.id);
        blockchain.add_transaction(cancel_tx)?;
        println!("✓ Transaction cancelled (actually replaced)\n");
    }

    // 5. Confirm
    blockchain.mine_pending_transactions(alice.address.clone())?;

    println!("✓ Funds returned");
    println!("  Alice balance: {} sat", blockchain.get_balance(&alice.address));
    println!("  Wrong address balance: {} sat", blockchain.get_balance(&wrong_addr));

    Ok(())
}
}

Use Case 3: Batch Payment Optimization

#![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 Batch Payment Optimization ===\n");

    // 1. Initial payment (2 recipients)
    println!("--- Initial batch payment (2 recipients) ---");
    let mut outputs = vec![
        TxOutput::new(1000, recipients[0].address.clone()),
        TxOutput::new(2000, recipients[1].address.clone()),
    ];

    // Create transaction... (simplified)
    println!("Payment:");
    println!("  Recipient 1: 1000 sat");
    println!("  Recipient 2: 2000 sat");
    println!("  Fee: 10 sat\n");

    // 2. Add more recipients
    println!("--- Add more recipients (RBF extension) ---");
    outputs.push(TxOutput::new(3000, recipients[2].address.clone()));
    outputs.push(TxOutput::new(4000, recipients[3].address.clone()));

    println!("Newly added:");
    println!("  Recipient 3: 3000 sat");
    println!("  Recipient 4: 4000 sat");
    println!("  Fee: 15 sat (only 5 sat more!)\n");

    println!("Advantages:");
    println!("  ✓ 4 transactions merged into 1");
    println!("  ✓ Fee savings (4×10 - 15 = 25 sat)");
    println!("  ✓ Block space saved");

    Ok(())
}
}

Security Considerations

⚠️ Zero-Confirmation Transaction Risk

Problem: RBF makes zero-confirmation transactions insecure

#![allow(unused)]
fn main() {
// Attack scenario
// 1. Attacker: Alice → merchant Bob (1 BTC, low fee)
//    Merchant sees the transaction and ships the goods

// 2. Attacker replaces: Alice → Alice (1 BTC, high fee)
//    Funds return to attacker; merchant suffers a loss

// Defense: wait for confirmation
if confirmations < 1 {
    println!("⚠️ Warning: zero-confirmation transactions are unsafe (RBF risk)");
    println!("Recommendation: wait for at least 1 confirmation");
}
}

Merchant Recommendations

#![allow(unused)]
fn main() {
fn accept_payment(tx: &Transaction) -> bool {
    // 1. Check whether RBF is enabled
    if is_rbf_enabled(tx) {
        println!("⚠️ Transaction has RBF enabled");

        // Option A: Reject zero-confirmation
        println!("Waiting for confirmation...");
        return false;

        // Option B: Require a higher fee
        if tx.fee_rate() < 50.0 {
            println!("Fee too low; requires >= 50 sat/byte");
            return false;
        }
    }

    // 2. Wait for sufficient confirmations
    let confirmations = get_confirmations(tx);
    if confirmations < 1 {
        return false;
    }

    true
}
}

RBF vs CPFP

Child-Pays-For-Parent (CPFP)

CPFP: A child transaction pays for the parent transaction’s fee

Parent transaction: Alice → Bob (low fee)
  ↓
Child transaction: Bob → Charlie (high fee)

Miners will pack them together to collect the higher fee

Comparison

FeatureRBFCPFP
OperatorSenderReceiver
MechanismReplace transactionChild transaction pulls parent
Fee paid bySenderReceiver
ComplexitySimpleSlightly more complex
Use caseSender acceleratesReceiver accelerates

Best Practices

1. When to Use RBF

#![allow(unused)]
fn main() {
// ✅ Suitable scenarios for RBF
if network_congested && !urgent {
    // Send with a low fee first; accelerate when needed
    create_rbf_transaction(fee_low);
}

// ❌ Unsuitable scenarios for RBF
if urgent || large_amount {
    // Send with a high fee directly
    create_transaction(fee_high);
}
}

2. Fee Strategy

#![allow(unused)]
fn main() {
fn calculate_replacement_fee(original_fee: u64) -> u64 {
    // Increase by at least 50% of the original fee
    let min_increase = original_fee / 2;

    // Or reach the current recommended fee rate
    let recommended = get_recommended_fee_rate() * tx_size;

    max(original_fee + min_increase, recommended)
}
}

3. User Notification

#![allow(unused)]
fn main() {
fn notify_replacement(original_tx: &Transaction, new_tx: &Transaction) {
    println!("📢 Transaction has been replaced:");
    println!("  Original transaction: {}", &original_tx.id[..16]);
    println!("  New transaction: {}", &new_tx.id[..16]);
    println!("  Original fee: {} sat", original_tx.fee);
    println!("  New fee: {} sat", new_tx.fee);
    println!("  Increase: +{} sat", new_tx.fee - original_tx.fee);
}
}

Implementation Example

Complete RBF Transaction Flow

#![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. Initialize
    setup_balance(&mut blockchain, &alice, 10000)?;

    // 2. Create a replaceable transaction
    let tx1 = blockchain.create_transaction(&alice, bob.address.clone(), 1000, 5)?;
    blockchain.add_transaction(tx1.clone())?;
    rbf.mark_replaceable(tx1.id.clone());

    println!("✓ Original transaction created (fee: 5 sat)");

    // 3. Monitor transaction status
    std::thread::sleep(std::time::Duration::from_secs(30));

    if !is_confirmed(&blockchain, &tx1.id) {
        println!("⚠️ Still unconfirmed after 30 seconds; preparing to accelerate...");

        // 4. Create replacement transaction
        let tx2 = blockchain.create_transaction(&alice, bob.address, 1000, 50)?;

        // 5. Validate RBF rules
        if rbf.can_replace(&tx1, &tx2) {
            // 6. Execute replacement
            blockchain.pending_transactions.retain(|tx| tx.id != tx1.id);
            blockchain.add_transaction(tx2.clone())?;

            println!("✓ Transaction replaced (fee: 50 sat)");

            // 7. Confirm
            blockchain.mine_pending_transactions(alice.address)?;
            println!("✓ New transaction confirmed");
        }
    }

    Ok(())
}
}

References


Summary: RBF is a powerful tool, but be mindful of zero-confirmation transaction risks. Merchants should wait for confirmation; users should use it judiciously.

Back to Advanced Features

Time Lock (TimeLock / nLockTime)

Time locks are an important Bitcoin feature that prevent a transaction from being confirmed before a specific time or block height.

Overview

What is a Time Lock?

A time lock allows a transaction to be used only at some future point in time, implementing a “deferred payment” feature.

Example:

Alice creates a transaction: 1 BTC → Bob
Time lock: January 1, 2025

Before January 1, 2025:
  ❌ Transaction cannot be confirmed
  ❌ Miners refuse to pack it

After January 1, 2025:
  ✓ Transaction can be confirmed
  ✓ Miners can pack it

Two Types

1. Unix Timestamp-Based

#![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);
}

Characteristics:

  • Unit: seconds
  • Suitable for precise time control
  • Affected by system time

Use cases:

  • Salary payment (1st of each month)
  • Bond maturity (fixed date)
  • Term deposit (3/6/12 months)

2. Block Height-Based

#![allow(unused)]
fn main() {
// locktime < 500,000,000
let unlock_height = 800000;  // Block #800,000
let timelock = TimeLock::new_block_based(unlock_height);
}

Characteristics:

  • Unit: blocks
  • More precise (approximately 10 minutes per block)
  • Not affected by system time

Use cases:

  • More precise time control
  • Avoid timestamp manipulation
  • Smart contract triggers

Time estimation:

1 block  ≈ 10 minutes
6 blocks ≈ 1 hour
144 blocks ≈ 1 day
1008 blocks ≈ 1 week
4032 blocks ≈ 1 month

TimeLock Implementation

Data Structure

#![allow(unused)]
fn main() {
pub struct TimeLock {
    pub locktime: u64,         // Lock time / height
    pub is_block_height: bool, // true: block height, false: timestamp
}
}

Methods

new_time_based

#![allow(unused)]
fn main() {
pub fn new_time_based(timestamp: u64) -> Self
}

Creates a time-based time lock.

Parameters:

  • timestamp - Unix timestamp (seconds)

Example:

#![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();

// Unlock after 3 months
let three_months = 90 * 24 * 3600;
let unlock_time = current_time + three_months;
let timelock = TimeLock::new_time_based(unlock_time);

println!("Locked until: {}", format_timestamp(unlock_time));
}

new_block_based

#![allow(unused)]
fn main() {
pub fn new_block_based(block_height: u64) -> Self
}

Creates a block height-based time lock.

Parameters:

  • block_height - Target block height

Example:

#![allow(unused)]
fn main() {
let current_height = blockchain.chain.len() as u64;

// Unlock after 1000 blocks (approximately 1 week)
let unlock_height = current_height + 1000;
let timelock = TimeLock::new_block_based(unlock_height);

println!("Locked until block #{}", unlock_height);
}

is_mature

#![allow(unused)]
fn main() {
pub fn is_mature(&self, current_time: u64, current_height: u64) -> bool
}

Checks whether the time lock has expired.

Parameters:

  • current_time - Current Unix timestamp
  • current_height - Current block height

Return value:

  • true - Expired; can be used
  • false - Not yet expired; still locked

Example:

#![allow(unused)]
fn main() {
let timelock = TimeLock::new_time_based(unlock_time);

if timelock.is_mature(current_time, 0) {
    println!("✓ Expired; can be spent");
} else {
    let remaining = unlock_time - current_time;
    println!("🔒 Still locked; {} seconds remaining", remaining);
}
}

Use Cases

Use Case 1: Term Deposit

#![allow(unused)]
fn main() {
fn savings_account_demo() -> Result<(), String> {
    println!("=== Term Deposit Demo ===\n");

    let alice = Wallet::new();
    let mut blockchain = Blockchain::new();

    // Initialize balance
    setup_balance(&mut blockchain, &alice, 100000)?;

    let current_time = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();

    // Product 1: 3-month term deposit
    println!("--- Product A: 3-Month Term Deposit ---");
    let three_months = 90 * 24 * 3600;
    let maturity_3m = current_time + three_months;
    let deposit_3m = TimeLock::new_time_based(maturity_3m);

    println!("Deposit amount: 30,000 sat");
    println!("Term: 3 months");
    println!("Maturity date: {}", format_date(maturity_3m));
    println!("Annual interest rate: 3%\n");

    // Product 2: 1-year term deposit
    println!("--- Product B: 1-Year Term Deposit ---");
    let one_year = 365 * 24 * 3600;
    let maturity_1y = current_time + one_year;
    let deposit_1y = TimeLock::new_time_based(maturity_1y);

    println!("Deposit amount: 50,000 sat");
    println!("Term: 1 year");
    println!("Maturity date: {}", format_date(maturity_1y));
    println!("Annual interest rate: 5%\n");

    // Check maturity status
    println!("--- Current Status Check ---");
    println!("Current time: {}", format_date(current_time));

    if deposit_3m.is_mature(current_time, 0) {
        println!("✓ 3-month deposit has matured; can be withdrawn");
        let interest = 30000 * 3 / 100 / 4;  // Quarterly interest
        println!("  Principal + interest: {} sat", 30000 + interest);
    } else {
        let days_left = (maturity_3m - current_time) / 86400;
        println!("🔒 3-month deposit is locked");
        println!("  Remaining: {} days", days_left);
    }

    if deposit_1y.is_mature(current_time, 0) {
        println!("✓ 1-year deposit has matured; can be withdrawn");
        let interest = 50000 * 5 / 100;  // Annual interest
        println!("  Principal + interest: {} sat", 50000 + interest);
    } else {
        let days_left = (maturity_1y - current_time) / 86400;
        println!("🔒 1-year deposit is locked");
        println!("  Remaining: {} days", days_left);
    }

    Ok(())
}
}

Output:

=== Term Deposit Demo ===

--- Product A: 3-Month Term Deposit ---
Deposit amount: 30,000 sat
Term: 3 months
Maturity date: 2025-03-15 00:00:00
Annual interest rate: 3%

--- Product B: 1-Year Term Deposit ---
Deposit amount: 50,000 sat
Term: 1 year
Maturity date: 2025-12-15 00:00:00
Annual interest rate: 5%

--- Current Status Check ---
Current time: 2024-12-15 00:00:00
🔒 3-month deposit is locked
  Remaining: 90 days
🔒 1-year deposit is locked
  Remaining: 365 days

Use Case 2: Inheritance Planning

#![allow(unused)]
fn main() {
fn inheritance_planning() -> Result<(), String> {
    println!("=== Inheritance Plan ===\n");

    let owner = Wallet::new();
    let heir = Wallet::new();
    let lawyer = Wallet::new();

    println!("Participants:");
    println!("  Owner: {}", &owner.address[..16]);
    println!("  Heir: {}", &heir.address[..16]);
    println!("  Lawyer: {}\n", &lawyer.address[..16]);

    let current_time = current_timestamp();

    // Plan: after 1 year of inactivity, assets automatically transfer to the heir
    println!("--- Plan Design ---");
    println!("Normal situation:");
    println!("  Requires: owner + heir (2-of-2)");
    println!("  Protects privacy; prevents unilateral transfer\n");

    println!("Emergency situation (after 1 year):");
    println!("  Owner is unreachable or deceased");
    println!("  Time lock has expired");
    println!("  Heir can operate independently\n");

    // Create time lock transaction
    let one_year = 365 * 24 * 3600;
    let inheritance_time = current_time + one_year;
    let timelock = TimeLock::new_time_based(inheritance_time);

    println!("--- Time Lock Configuration ---");
    println!("Trigger time: {}", format_date(inheritance_time));
    println!("Trigger condition: no transaction signed by owner within 1 year\n");

    // Periodic check (performed by lawyer)
    println!("--- Periodic Check ---");
    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!("✓ Time lock triggered");
            println!("✓ Inheritance process started");
            println!("✓ Assets can be transferred to heir");
        }
    } else {
        let days_remaining = (one_year - inactive_period) / 86400;
        println!("🔒 Normal status");
        println!("{} days until inheritance trigger", days_remaining);
    }

    Ok(())
}
}

Use Case 3: Salary Payment

#![allow(unused)]
fn main() {
fn salary_payment_system() -> Result<(), String> {
    println!("=== Salary Payment System ===\n");

    let company = Wallet::new();
    let employees: Vec<_> = (0..5)
        .map(|i| (format!("Employee {}", i+1), Wallet::new()))
        .collect();

    let current_time = current_timestamp();

    println!("Company address: {}", &company.address[..16]);
    println!("Number of employees: {}\n", employees.len());

    // Pay salary on the 1st of each month
    println!("--- Salary Payment Schedule ---");

    for month in 1..=3 {
        // Calculate the timestamp for the 1st of the next month
        let payment_date = calculate_first_day_of_month(current_time, month);
        let timelock = TimeLock::new_time_based(payment_date);

        println!("Month {} salary:", month);
        println!("  Payment date: {}", format_date(payment_date));

        if timelock.is_mature(current_time, 0) {
            println!("  Status: ✓ Ready to pay");

            for (name, wallet) in &employees {
                println!("    {} → {} sat", name, 10000);
            }
        } else {
            let days_until = (payment_date - current_time) / 86400;
            println!("  Status: 🔒 Locked");
            println!("  Countdown: {} days", days_until);
        }
        println!();
    }

    println!("--- Advantages ---");
    println!("✓ Automated payment");
    println!("✓ Cannot be diverted early");
    println!("✓ Employees have predictable income");
    println!("✓ Reduced administrative costs");

    Ok(())
}
}

Use Case 4: Crowdfunding Refund

#![allow(unused)]
fn main() {
fn crowdfunding_refund() -> Result<(), String> {
    println!("=== Crowdfunding Refund Mechanism ===\n");

    let project_owner = Wallet::new();
    let backers: Vec<_> = (0..10).map(|_| Wallet::new()).collect();

    let current_time = current_timestamp();

    println!("--- Crowdfunding Project ---");
    println!("Target amount: 1,000,000 sat");
    println!("Currently raised: 500,000 sat");
    println!("Deadline: 30 days from now\n");

    // If the target is not met after 30 days, automatically refund
    let deadline = current_time + 30 * 86400;
    let refund_timelock = TimeLock::new_time_based(deadline);

    println!("--- Refund Time Lock ---");
    println!("Trigger condition: target not met after 30 days");
    println!("Trigger time: {}", format_date(deadline));
    println!("Refund method: automatically returned to backers\n");

    // Check status
    if refund_timelock.is_mature(current_time, 0) {
        println!("--- Project failed; executing refund ---");
        for (i, backer) in backers.iter().enumerate() {
            println!("✓ Refund to backer #{}: {} sat", i+1, 50000);
        }
    } else {
        let days_left = (deadline - current_time) / 86400;
        println!("--- Crowdfunding in progress ---");
        println!("Time remaining: {} days", days_left);
        println!("Still needed: 500,000 sat");
    }

    Ok(())
}
}

Advanced Usage

TimeLock + MultiSig

Combining multi-sig for more complex logic:

#![allow(unused)]
fn main() {
fn timelock_multisig_combination() -> Result<(), String> {
    let owner = Wallet::new();
    let heir = Wallet::new();
    let lawyer = Wallet::new();

    // Normal: 2-of-2 (owner + heir)
    let normal_multisig = MultiSigAddress::new(
        2,
        vec![owner.public_key.clone(), heir.public_key.clone()]
    )?;

    // After 1 year: 2-of-3 (any two parties)
    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!("=== TimeLock + MultiSig Combination ===");
    println!("\nNormal period (first year):");
    println!("  Multi-sig address: {}", &normal_multisig.address[..16]);
    println!("  Requirement: owner + heir (2-of-2)");

    println!("\nEmergency period (after one year):");
    println!("  Multi-sig address: {}", &emergency_multisig.address[..16]);
    println!("  Requirement: any two parties (2-of-3)");
    println!("  Possible combinations:");
    println!("    - owner + heir");
    println!("    - owner + lawyer");
    println!("    - heir + lawyer");

    Ok(())
}
}

Technical Details

nLockTime Field

In actual Bitcoin transactions:

#![allow(unused)]
fn main() {
struct Transaction {
    version: u32,
    inputs: Vec<TxInput>,
    outputs: Vec<TxOutput>,
    locktime: u32,  // Time lock field
}
}

Rules:

if locktime < 500,000,000:
    # Block height mode
    if current_block_height >= locktime:
        can confirm
    else:
        reject

else:
    # Timestamp mode
    if current_timestamp >= locktime:
        can confirm
    else:
        reject

nSequence and Time Locks

For a time lock to be enabled, nSequence must be < 0xFFFFFFFF:

#![allow(unused)]
fn main() {
// Enable time lock
input.sequence = 0xFFFFFFFD;

// Disable time lock (final transaction)
input.sequence = 0xFFFFFFFF;
}

Security Considerations

1. Timestamp Manipulation

Problem: Miners may manipulate block timestamps

Constraints:

  • Timestamp cannot be earlier than the median of the previous 11 blocks
  • Cannot be more than 2 hours later than the current time

Recommendation: Using block height is more reliable

2. Emergencies

Problem: A time lock cannot be cancelled

Solutions:

#![allow(unused)]
fn main() {
// Solution 1: Use RBF to replace before expiry
if !timelock.is_mature(...) && need_cancel {
    replace_with_non_locked_tx();
}

// Solution 2: Double spend (before expiry)
create_alternative_tx_without_timelock();
}

3. Key Loss

Problem: Key lost before expiry

Recommendation:

  • Use multi-sig to reduce risk
  • Back up keys
  • Set up recovery mechanisms

Relationship to CLTV/CSV

CheckLockTimeVerify (CLTV)

Introduced in BIP65:

OP_CLTV opcode
Locks a single UTXO
More flexible

nLockTime vs CLTV:

nLockTime: locks the entire transaction
CLTV: locks a single output (more flexible)

CheckSequenceVerify (CSV)

Introduced in BIP112:

OP_CSV opcode
Relative time lock
Calculated from the time the UTXO was created

Best Practices

1. Choose the Right Type

#![allow(unused)]
fn main() {
// Exact date: use timestamp
let birthday = to_timestamp("2025-01-01");
let timelock = TimeLock::new_time_based(birthday);

// Relative delay: use block height
let blocks_1week = 1008;  // About 1 week
let timelock = TimeLock::new_block_based(current_height + blocks_1week);
}

2. User-Friendly Time Display

#![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!("✓ Unlocked");
    } else {
        if timelock.is_block_height {
            let blocks_left = timelock.locktime - current_height;
            let hours = blocks_left * 10 / 60;  // Approximately 10 minutes per block
            println!("🔒 Locked; {} blocks remaining (approximately {} hours)", blocks_left, hours);
        } else {
            let seconds_left = timelock.locktime - current_time;
            let days = seconds_left / 86400;
            println!("🔒 Locked; {} days remaining", days);
        }
    }
}
}

3. Testing Time Locks

#![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);

        // Not yet expired
        assert!(!timelock.is_mature(current, 0));

        // Expired
        assert!(timelock.is_mature(future + 1, 0));
    }
}
}

References


Summary: Time locks are a key technology for implementing deferred payments and smart contracts. Used appropriately, they enable a wide range of applications such as term deposits, inheritance planning, and salary payments.

Back to Advanced Features

Transaction Priority

When the network is congested, thousands of pending transactions may accumulate in the mempool. Miners can only pack approximately 1 MB of data into each block, so a priority mechanism is needed to decide which transactions are confirmed first. This chapter covers how transaction priority is calculated in SimpleBTC, the mempool sorting logic, and the fee recommendation strategy.


Core Concept: Fee Rate

Fee rate is the most important metric for measuring transaction priority:

Fee Rate (sat/byte) = Fee (satoshi) / Transaction Size (bytes)

Miners preferentially select high-fee-rate transactions to pack into blocks, because doing so maximizes fee income for the same block space.

Why Use Fee Rate Instead of Absolute Fee?

A complex transaction with 10 inputs may pay a fee of 1000 sat but occupies 900 bytes, giving a fee rate of about 1.1 sat/byte. A simple transaction with only 1 input pays 200 sat but occupies only 192 bytes, giving a fee rate of about 1.04 sat/byte. From the miner’s perspective, both are roughly equivalent. If only the absolute fee is considered, the former would be incorrectly prioritized, wasting block space.


Mempool Structure

SimpleBTC’s Mempool uses a dual-index structure to support efficient priority sorting:

#![allow(unused)]
fn main() {
pub struct Mempool {
    // Primary storage: txid → mempool entry
    transactions: HashMap<String, MempoolEntry>,

    // Fee rate index: fee_rate → set of txids (BTreeMap is automatically sorted; efficient iteration)
    fee_index: BTreeMap<ordered_float::NotNan<f64>, HashSet<String>>,

    // UTXO index: used for double-spend detection
    utxo_index: HashMap<String, String>,

    // Capacity control
    max_size: usize,       // Maximum bytes (default 300 MB)
    current_size: usize,   // Currently used bytes
    min_fee_rate: f64,     // Minimum accepted fee rate (default 1.0 sat/byte)
    max_age: u64,          // Maximum retention time (default 72 hours)
}
}

BTreeMap (a balanced binary search tree) is the key: it automatically sorts by fee rate, making the operation “get the top N transactions by fee rate” a simple reverse iteration from the tail, with time complexity O(N).

Mempool Entry: MempoolEntry

#![allow(unused)]
fn main() {
pub struct MempoolEntry {
    pub transaction: Transaction,  // Full transaction data
    pub added_time: u64,           // Time added (Unix timestamp)
    pub size: usize,               // Estimated byte size
    pub fee_rate: f64,             // Calculated fee rate (sat/byte)
    pub replaceable: bool,         // Whether RBF replacement is supported
}
}

The fee rate is calculated immediately when a MempoolEntry is created and cached, avoiding repeated calculations:

#![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
        };
        // ...
    }
}
}

Transaction Size Estimation

SimpleBTC uses a simplified formula to estimate transaction byte size:

#![allow(unused)]
fn main() {
fn estimate_tx_size(&self, tx: &Transaction) -> usize {
    let base = 10;               // Fixed overhead (version number, lock time, etc.)
    let inputs_size = tx.inputs.len() * 148;   // About 148 bytes per input
    let outputs_size = tx.outputs.len() * 34;  // About 34 bytes per output
    base + inputs_size + outputs_size
}
}

Real Bitcoin transaction size reference (native SegWit, P2WPKH format):

Transaction TypeInputsOutputsEstimated Size
Simple transfer12~192 bytes
Consolidate multiple UTXOs52~898 bytes
Batch payment110~388 bytes

Transaction Addition and Validation Flow

When mempool.add_transaction(tx) is called, the following checks are executed in order internally:

Transaction arrives at mempool
      │
      ▼
① Already exists? ──Yes──► Reject (duplicate transaction)
      │No
      ▼
② Basic security validation (format, signature, etc.)
      │
      ▼
③ Double-spend detection: has any input already been spent by another mempool transaction?
      │
      ├─ Yes, and old transaction supports RBF and new fee is higher ──► Trigger RBF replacement; continue
      │
      └─ Yes, but RBF conditions not met ──► Reject (double-spend attack)
      │No
      ▼
④ Estimate size; calculate fee rate
      │
      ▼
⑤ Fee rate ≥ min_fee_rate? ──No──► Reject (fee rate too low)
      │Yes
      ▼
⑥ Mempool full? ──Yes──► Trigger eviction of low-fee-rate transactions
      │
      ▼
⑦ Add to transactions, fee_index, utxo_index
      │
      ▼
     Success
#![allow(unused)]
fn main() {
// Example: add a transaction to the mempool
let mut mempool = Mempool::default(); // 300 MB limit, 1 sat/byte minimum fee rate

let tx = Transaction::new(inputs, outputs, 0, 200); // 200 sat fee
match mempool.add_transaction(tx) {
    Ok(()) => println!("Transaction entered mempool"),
    Err(e) => println!("Rejected reason: {}", e),
}
}

Priority Sorting and Block Packing

Get Top N by Fee Rate: get_top_transactions

#![allow(unused)]
fn main() {
pub fn get_top_transactions(&self, max_count: usize) -> Vec<Transaction>
}

By reverse-iterating fee_index (BTreeMap from high to low), the highest-fee-rate transactions are quickly retrieved:

#![allow(unused)]
fn main() {
// Iterate from high fee rate to low
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() {
// Usage: miner wants to preview the top 10 best transactions
let top_txs = mempool.get_top_transactions(10);
for tx in &top_txs {
    println!("txid: {}, fee: {} sat", tx.id, tx.fee);
}
}

Pack by Block Size Limit: get_transactions_for_block

#![allow(unused)]
fn main() {
pub fn get_transactions_for_block(&self, max_size: usize) -> Vec<Transaction>
}

A more practical block-packing function. It also selects transactions from high fee rate to low, but additionally checks that the accumulated size does not exceed max_size bytes:

#![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() {
// Usage: pack transactions for a new block (Bitcoin block limit is about 1 MB = 1,000,000 bytes)
let block_txs = mempool.get_transactions_for_block(1_000_000);
println!("Selected {} transactions for packing", block_txs.len());
}

Composite Priority Score

SimpleBTC provides TxPriorityCalculator in src/advanced_tx.rs, implementing more refined priority calculation.

Basic Fee Rate Calculation

#![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 fee, transaction size 192 bytes
let fee_rate = TxPriorityCalculator::calculate_fee_rate(200, 192);
println!("Fee rate: {:.2} sat/byte", fee_rate); // ~1.04 sat/byte
}

Coin Age Priority

In early Bitcoin (before SegWit), “coin age” was also considered: the UTXO’s value multiplied by the number of blocks it has been waiting, divided by the transaction size:

#![allow(unused)]
fn main() {
/// Priority = (input value × input confirmations) / transaction size
pub fn calculate_priority(
    input_value: u64,  // Total value of inputs (satoshi)
    input_age: u32,    // Number of blocks the input UTXO has been confirmed
    tx_size: usize,
) -> f64 {
    (input_value as f64 * input_age as f64) / tx_size as f64
}
}
#![allow(unused)]
fn main() {
// Example: input value 1 BTC = 100,000,000 sat, confirmed 100 blocks, transaction size 200 bytes
let priority = TxPriorityCalculator::calculate_priority(100_000_000, 100, 200);
println!("Coin age priority: {:.0}", priority); // 50,000,000
}

Historical context: Bitcoin Core removed coin-age-based free transaction priority in version 0.12 (2016), because low-fee transactions significantly slowed down block packing. In the modern network, fee rate is the only priority metric that actually matters.

Composite Score Formula: 70% Fee Rate + 30% Coin Age

#![allow(unused)]
fn main() {
/// Composite score = fee_rate × 0.7 + priority × 0.001 × 0.3
pub fn calculate_score(fee_rate: f64, priority: f64) -> f64 {
    fee_rate * 0.7 + priority * 0.001 * 0.3
}
}

Design rationale of this weighted formula:

  • 70% weight for fee rate: ensures miner revenue maximization; high-fee-rate transactions still take priority
  • 30% weight for coin age (scaled by 0.001): gives a “bonus” to long-waiting transactions, preventing low-fee-rate old UTXOs from never being confirmed
#![allow(unused)]
fn main() {
// Full scoring example
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!("Composite score: {:.4}", score);
// score = 2.5 * 0.7 + 2_500_000 * 0.001 * 0.3 = 1.75 + 750 = 751.75
}

Fee Recommendations

TxPriorityCalculator::recommend_fee returns a suggested fee based on urgency:

#![allow(unused)]
fn main() {
pub enum FeeUrgency {
    Low,    // Low priority: confirmed within a few hours
    Medium, // Medium priority: confirmed within 30–60 minutes
    High,   // High priority: 10–20 minutes (approximately 1–2 blocks)
    Urgent, // Urgent: next block (highest priority)
}

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};

// Estimate the suggested fee for a standard transaction (1 input, 2 outputs)
let tx_size = 10 + 1 * 148 + 2 * 34; // = 226 bytes

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!("Low priority:    {} sat ({} sat/byte)", low_fee,    1);  // 226 sat
println!("Medium priority: {} sat ({} sat/byte)", medium_fee, 5);  // 1130 sat
println!("High priority:   {} sat ({} sat/byte)", high_fee,   20); // 4520 sat
println!("Urgent:          {} sat ({} sat/byte)", urgent_fee, 50); // 11300 sat
}

Real Bitcoin network fee rate reference (2024 data, BTC/USD = $60,000):

UrgencyTypical Fee RateApproximate USD (226-byte transaction)
Low1–3 sat/byte$0.14 – $0.41
Medium5–15 sat/byte$0.68 – $2.03
High20–50 sat/byte$2.71 – $6.78
Urgent50–200 sat/byte$6.78 – $27.1

Note: Actual fee rates are heavily influenced by network congestion. At the peak of the 2017 bull market, some users paid fees exceeding $50 for fast confirmation.


Low-Fee-Rate Transaction Eviction

When the mempool reaches its capacity limit, it automatically evicts the lowest-fee-rate transactions to free up space:

#![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();

    // Iterate from [low fee rate to high fee rate] (BTreeMap forward iteration)
    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; }
    }

    // Execute eviction
    for txid in &to_remove {
        self.remove_transaction(txid)?;
    }
    Ok(())
}
}

This design ensures the mempool always maintains the subset of “highest-fee-rate” transactions; low-fee-rate transactions are naturally eliminated through competition.

#![allow(unused)]
fn main() {
// Create a very small mempool to demonstrate eviction behavior
let mut mempool = Mempool::new(1000, 1.0); // Only 1 KB capacity

// Add multiple transactions; when over 1 KB, low-fee-rate ones are evicted
for i in 1..=10 {
    let tx = create_tx_with_fee(i * 100); // fee: 100, 200, ..., 1000
    let _ = mempool.add_transaction(tx); // Low-fee-rate ones may be evicted
}
}

Expired Transaction Cleanup

By default, transactions that have been waiting in the mempool for more than 72 hours are cleared:

#![allow(unused)]
fn main() {
// Call periodically (e.g., once per hour)
let expired_count = mempool.clear_expired();
if expired_count > 0 {
    println!("Cleared {} expired transactions", expired_count);
}
}

Mempool Statistics

#![allow(unused)]
fn main() {
let stats = mempool.get_stats();
println!("Pending transaction count: {}", stats.tx_count);
println!("Mempool size:              {} / {} bytes", stats.total_size, stats.max_size);
println!("Total pending fees:        {} sat", stats.total_fees);
println!("Average fee rate:          {:.2} sat/byte", stats.avg_fee_rate);
println!("Minimum accepted fee rate: {:.2} sat/byte", stats.min_fee_rate);
}

Replace-By-Fee (RBF)

RBF (BIP125) allows users to replace an old transaction in the mempool with a new one carrying a higher fee. In SimpleBTC, RBFManager in advanced_tx.rs manages replaceable transactions:

#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::RBFManager;

let mut rbf = RBFManager::new();

// Mark a transaction as replaceable (set sequence < 0xFFFFFFFE when sending)
rbf.mark_replaceable("original_tx_id");

// Later, replace the old transaction with a new one bearing a higher fee
let can_replace = rbf.can_replace(&old_tx, &new_tx);
match can_replace {
    Ok(()) => println!("RBF replacement successful"),
    Err(reason) => println!("Replacement rejected: {}", reason),
}
}

RBF replacement conditions (verified by can_replace):

  1. The old transaction must have been marked as replaceable (replaceable = true)
  2. The number of inputs in the new and old transactions must be the same, referencing the same UTXOs
  3. The new transaction’s fee must be strictly higher than the old transaction’s
  4. The fee increment must be at least the size of the old transaction (approximately 1 sat/byte)

Summary

SimpleBTC’s transaction priority system consists of three layers:

LayerComponentRole
Mempool sortingMempool + BTreeMap<fee_rate>Automatically maintains a sorted queue by fee rate
Priority calculationTxPriorityCalculatorFee rate, coin age, composite score
Fee recommendationFeeUrgency + recommend_feeRecommends a reasonable fee by urgency level

Core formula recap:

Fee Rate (sat/byte)  = fee / transaction size
Composite Score      = fee_rate × 0.7 + coin_age_priority × 0.001 × 0.3
Recommended Fee      = transaction size × sat_per_byte (select 1/5/20/50 by urgency)

Enterprise Multisig Wallet in Practice

This example demonstrates how to use 2-of-3 multisig to manage corporate funds.

Scenario Description

A technology company needs to manage its bitcoin assets with the following requirements:

  • Three executives (CEO, CFO, CTO) each hold one key
  • Any two executives can authorize a transfer
  • Prevent misuse or loss of control by a single person
  • Normal operations continue even if one executive is unavailable

Run the Example

cargo run --example enterprise_multisig

Code Walkthrough

1. Initialization

use bitcoin_simulation::{
    blockchain::Blockchain,
    wallet::Wallet,
    multisig::MultiSigAddress,
};

fn main() -> Result<(), String> {
    println!("=== Enterprise Multisig Wallet Demo ===\n");

    // Create the blockchain
    let mut blockchain = Blockchain::new();

    // Create wallets for the three executives
    let ceo = Wallet::new();
    let cfo = Wallet::new();
    let cto = Wallet::new();

    println!("Created executive wallets:");
    println!("  CEO: {}", &ceo.address[..20]);
    println!("  CFO: {}", &cfo.address[..20]);
    println!("  CTO: {}\n", &cto.address[..20]);

2. Create the Multisig Address

#![allow(unused)]
fn main() {
    // Create a 2-of-3 multisig address
    let company_multisig = MultiSigAddress::new(
        2,  // requires 2 signatures
        vec![
            ceo.public_key.clone(),
            cfo.public_key.clone(),
            cto.public_key.clone(),
        ]
    ).expect("Failed to create multisig address");

    println!("Company multisig address created:");
    println!("  Address: {}", &company_multisig.address[..20]);
    println!("  Type: {}-of-{} multisig",
        company_multisig.required_sigs,
        company_multisig.total_keys);
    println!("  Rule: any 2 executives can authorize a transfer\n");
}

Key points:

  • required_sigs = 2: requires 2 signatures
  • total_keys = 3: 3 total keys
  • Any combination of two executives works: CEO+CFO, CEO+CTO, or CFO+CTO

3. Fund the Address

#![allow(unused)]
fn main() {
    // Fund the company multisig address
    println!("--- Scenario 1: Company receives investment ---");

    let investor = Wallet::new();
    println!("Investor address: {}\n", &investor.address[..20]);

    // Create a funding transaction (from the genesis address)
    let funding_tx = blockchain.create_transaction(
        &Wallet::from_address("genesis_address".to_string()),
        company_multisig.address.clone(),
        100000,  // 100,000 satoshi
        0,
    )?;

    blockchain.add_transaction(funding_tx)?;
    blockchain.mine_pending_transactions(investor.address.clone())?;

    let company_balance = blockchain.get_balance(&company_multisig.address);
    println!("Funding complete");
    println!("  Company account balance: {} satoshi\n", company_balance);
}

4. Scenario Demo: Normal Expenditure

#![allow(unused)]
fn main() {
    println!("--- Scenario 2: Normal payment (approved by CEO + CFO) ---");

    let supplier = Wallet::new();
    println!("Supplier address: {}\n", &supplier.address[..20]);

    // Simulate the multisig flow
    let payment_amount = 30000;
    let payment_data = format!("{}{}", company_multisig.address, supplier.address);

    // Step 1: CEO signs
    let ceo_signature = ceo.sign(&payment_data);
    println!("CEO has approved and signed");

    // Step 2: CFO signs
    let cfo_signature = cfo.sign(&payment_data);
    println!("CFO has approved and signed");

    // Step 3: Verify signature count
    let signatures = vec![ceo_signature, cfo_signature];

    if signatures.len() >= company_multisig.required_sigs {
        println!("Signature count satisfies requirement (2/3)");
        println!("Transaction can be executed\n");

        // Execute the transfer
        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!("Payment complete");
        println!("  Amount paid: {} satoshi", payment_amount);
        println!("  Company balance: {} satoshi\n",
            blockchain.get_balance(&company_multisig.address));
    }
}

Workflow:

  1. CEO initiates a payment request
  2. CEO signs with their private key
  3. CFO reviews and signs
  4. The system verifies the signature count (2 ≥ required 2)
  5. The transfer is executed

5. Scenario Demo: CEO Unavailable

#![allow(unused)]
fn main() {
    println!("--- Scenario 3: Emergency payment while CEO is traveling (CFO + CTO) ---");

    let emergency_vendor = Wallet::new();
    println!("Emergency vendor: {}\n", &emergency_vendor.address[..20]);

    let emergency_amount = 20000;
    let emergency_data = format!("{}{}",
        company_multisig.address, emergency_vendor.address);

    println!("CEO is traveling and cannot be reached");
    println!("CFO and CTO decide to approve the emergency payment\n");

    // CFO signs
    let cfo_sig = cfo.sign(&emergency_data);
    println!("CFO has signed");

    // CTO signs
    let cto_sig = cto.sign(&emergency_data);
    println!("CTO has signed");

    let emergency_sigs = vec![cfo_sig, cto_sig];

    if emergency_sigs.len() >= company_multisig.required_sigs {
        println!("Signatures satisfy requirement (2/3)");
        println!("Business continues normally even without the CEO\n");

        // Execute the transfer
        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!("Emergency payment complete");
        println!("  Final balance: {} satoshi\n",
            blockchain.get_balance(&company_multisig.address));
    }

    Ok(())
}
}

Sample Output

=== Enterprise Multisig Wallet Demo ===

Created executive wallets:
  CEO: a3f2d8c9e4b7f1a8...
  CFO: b9e4c7d2a3f1e8b6...
  CTO: c8f1e9d3b4a7c2e5...

Company multisig address created:
  Address: 3Mf2d8c9e4b7f1a8...
  Type: 2-of-3 multisig
  Rule: any 2 executives can authorize a transfer

--- Scenario 1: Company receives investment ---
Investor address: d7c2e8f3a9b1d4c6...

Block mined: 0003ab4f9c2d...
Funding complete
  Company account balance: 100000 satoshi

--- Scenario 2: Normal payment (approved by CEO + CFO) ---
Supplier address: e6d1f8c2b9a3e7d4...

CEO has approved and signed
CFO has approved and signed
Signature count satisfies requirement (2/3)
Transaction can be executed

Block mined: 0007c3e8d1a9...
Payment complete
  Amount paid: 30000 satoshi
  Company balance: 69900 satoshi

--- Scenario 3: Emergency payment while CEO is traveling (CFO + CTO) ---
Emergency vendor: f5e2d9c3a8b7f1e6...

CEO is traveling and cannot be reached
CFO and CTO decide to approve the emergency payment

CFO has signed
CTO has signed
Signatures satisfy requirement (2/3)
Business continues normally even without the CEO

Block mined: 000ab7e4f2c8...
Emergency payment complete
  Final balance: 49800 satoshi

Business Value

1. Security

Traditional Single-SigEnterprise Multisig
CEO’s private key stolen → all funds lostRequires 2 keys; theft of one is harmless
Single point of failureDistributed risk
High risk of insider fraudRequires two people to collude

2. Business Continuity

ScenarioTraditional ApproachMultisig Approach
CEO on vacationBusiness haltsCFO+CTO continue operations
Executive resignsMust transfer all fundsReplace one key
Emergency paymentKey holder unavailableAny 2 people can approve

3. Compliance

Audit trail:
- Every transaction requires 2 signatures
- Clear record of who approved what
- Satisfies internal control requirements
- Meets financial audit standards

Extended Solutions

Tiered Authorization

#![allow(unused)]
fn main() {
// Small amounts: manager level 2-of-3
if amount < 10000 {
    let managers_multisig = MultiSigAddress::new(2, manager_keys)?;
}

// Medium amounts: executive level 2-of-3
else if amount < 100000 {
    let exec_multisig = MultiSigAddress::new(2, exec_keys)?;
}

// Large amounts: board 5-of-9
else {
    let board_multisig = MultiSigAddress::new(5, board_keys)?;
}
}

Timelock Protection

#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::TimeLock;

// Large transfers require a 24-hour delay
let timelock = TimeLock::new_time_based(
    current_time() + 24 * 3600
);

// Can be cancelled during the delay period
// Protects against coerced transfers
}

Emergency Recovery

#![allow(unused)]
fn main() {
// Normal: 2-of-3
let normal_multisig = MultiSigAddress::new(
    2,
    vec![ceo_key, cfo_key, cto_key]
)?;

// Emergency (2 keys lost): attorney-custodied recovery key
let recovery_multisig = MultiSigAddress::new(
    1,
    vec![lawyer_key]  // requires legal documentation
)?;
}

Implementation Recommendations

1. Key Management

CEO key:
  - Primary: mobile hot wallet (daily signing)
  - Backup: hardware wallet (safe)

CFO key:
  - Primary: desktop hot wallet (office)
  - Backup: paper wallet (bank safe deposit box)

CTO key:
  - Primary: hardware wallet (carried personally)
  - Backup: encrypted USB drive (offsite storage)

2. Operating Procedure

1. Initiator creates transfer request
2. Initiator signs
3. Notify the second approver
4. Second approver reviews and signs
5. System automatically verifies signature count
6. Execute transaction and notify everyone
7. Record audit log

3. Security Checklist

  • Keys stored in separate locations
  • Regularly test the recovery process
  • Back up all keys
  • Set amount thresholds
  • Enable transaction notifications
  • Regularly audit transaction records
  • Prepare a contingency plan for key loss
  • Train all key holders

Summary

The enterprise multisig wallet achieves the following through a 2-of-3 mechanism:

Security — no single point of failure Flexibility — any two people can approve Continuity — operations continue even if one person is absent Compliance — satisfies internal controls Transparency — all operations are traceable

This is a best practice for enterprises managing digital assets.


View full source code

Escrow Service in Practice

This example demonstrates how to implement a Bitcoin escrow service using 2-of-3 multisig.

Scenario Description

In e-commerce transactions, buyers and sellers don’t trust each other and need a third-party escrow:

  • Buyer’s concern: pays but the seller doesn’t ship
  • Seller’s concern: ships but the buyer doesn’t pay
  • Solution: funds held in a 2-of-3 multisig address

Participants:

  • Buyer
  • Seller
  • Arbitrator

Rules:

  • Normal transaction: Buyer + Seller sign → funds go to Seller
  • Dispute resolution: Buyer/Seller + Arbitrator → outcome per arbitration ruling

Run the Example

cargo run --example escrow_service

Code Walkthrough

1. Initialize Participants

use bitcoin_simulation::{
    blockchain::Blockchain,
    wallet::Wallet,
    multisig::MultiSigAddress,
};

fn main() -> Result<(), String> {
    println!("=== Bitcoin Escrow Service Demo ===\n");

    // Create the blockchain
    let mut blockchain = Blockchain::new();

    // Create participant wallets
    let buyer = Wallet::new();
    let seller = Wallet::new();
    let arbitrator = Wallet::new();

    println!("Participants created:");
    println!("  Buyer:      {}", &buyer.address[..20]);
    println!("  Seller:     {}", &seller.address[..20]);
    println!("  Arbitrator: {}\n", &arbitrator.address[..20]);

2. Create the Escrow Multisig Address

#![allow(unused)]
fn main() {
    // Create a 2-of-3 escrow multisig address
    let escrow_multisig = MultiSigAddress::new(
        2,  // requires 2 signatures
        vec![
            buyer.public_key.clone(),
            seller.public_key.clone(),
            arbitrator.public_key.clone(),
        ]
    ).expect("Failed to create multisig address");

    println!("Escrow address created:");
    println!("  Address: {}", &escrow_multisig.address[..20]);
    println!("  Type: {}-of-{} multisig",
        escrow_multisig.required_sigs,
        escrow_multisig.total_keys);
    println!("  Rule: any 2 parties can sign");
    println!("  Possible combinations:");
    println!("    - Buyer + Seller (normal transaction)");
    println!("    - Buyer + Arbitrator (refund to buyer)");
    println!("    - Seller + Arbitrator (payment to seller)\n");
}

Key points:

  • 2-of-3 ensures no single party has unilateral control
  • In normal cases, buyer and seller resolve directly
  • The arbitrator intervenes only in disputes

3. Buyer Deposits Funds

#![allow(unused)]
fn main() {
    println!("--- Scenario 1: Buyer deposits escrow funds ---");

    // Buyer receives initial funds
    let funding_tx = blockchain.create_transaction(
        &Wallet::from_address("genesis_address".to_string()),
        buyer.address.clone(),
        100000,  // 100,000 satoshi
        0,
    )?;

    blockchain.add_transaction(funding_tx)?;
    blockchain.mine_pending_transactions(buyer.address.clone())?;

    let buyer_initial = blockchain.get_balance(&buyer.address);
    println!("Buyer balance: {} sat\n", buyer_initial);

    // Buyer transfers payment to the escrow address
    let escrow_amount = 50000;  // 50,000 sat
    println!("Item price: {} sat", escrow_amount);
    println!("Buyer is transferring payment to the escrow address...\n");

    let deposit_tx = blockchain.create_transaction(
        &buyer,
        escrow_multisig.address.clone(),
        escrow_amount,
        100,  // fee
    )?;

    blockchain.add_transaction(deposit_tx)?;
    blockchain.mine_pending_transactions(buyer.address.clone())?;

    let escrow_balance = blockchain.get_balance(&escrow_multisig.address);
    println!("Funds escrowed");
    println!("  Escrow amount: {} sat", escrow_balance);
    println!("  Buyer balance: {} sat\n", blockchain.get_balance(&buyer.address));
}

Flow:

  1. Buyer obtains initial funds
  2. Buyer transfers payment to the escrow address
  3. Funds are locked in the multisig address
  4. Seller sees the successful escrow and ships

4. Scenario A: Normal Transaction Completed

#![allow(unused)]
fn main() {
    println!("--- Scenario 2A: Normal transaction (buyer satisfied) ---");
    println!("Seller has shipped");
    println!("Buyer received the item and is satisfied\n");

    // Both buyer and seller sign to release funds to the seller
    let payment_amount = escrow_balance - 50;  // deduct fee
    let payment_data = format!("{}{}{}",
        escrow_multisig.address,
        seller.address,
        payment_amount);

    println!("Signing process:");
    // Buyer signs
    let buyer_signature = buyer.sign(&payment_data);
    println!("  Buyer has signed (confirming receipt)");

    // Seller signs
    let seller_signature = seller.sign(&payment_data);
    println!("  Seller has signed (accepting payment)");

    // Verify signature count
    let signatures = vec![buyer_signature, seller_signature];

    if signatures.len() >= escrow_multisig.required_sigs {
        println!("\nSignatures satisfy requirement (2/3)");
        println!("Releasing funds to seller\n");

        // Create the payment transaction
        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!("=== Transaction Complete ===");
        println!("Seller balance: {} sat", blockchain.get_balance(&seller.address));
        println!("Escrow balance: {} sat", blockchain.get_balance(&escrow_multisig.address));
    }
}

Normal flow:

  1. Seller ships
  2. Buyer confirms receipt
  3. Buyer signs (confirming satisfaction)
  4. Seller signs (accepting payment)
  5. 2 signatures satisfy the requirement
  6. Funds released to seller

5. Scenario B: Dispute Resolution

#![allow(unused)]
fn main() {
    println!("\n--- Scenario 2B: Dispute resolution (item has a problem) ---");

    // Re-create the scenario (hypothetical)
    let escrow_multisig_dispute = MultiSigAddress::new(
        2,
        vec![
            buyer.public_key.clone(),
            seller.public_key.clone(),
            arbitrator.public_key,
        ]
    )?;

    println!("Buyer: item doesn't match the description, requesting a refund");
    println!("Seller: item is fine, refusing refund");
    println!("Arbitrator intervenes to investigate...\n");

    println!("Arbitration ruling:");
    println!("  Investigation confirms the item has a problem");
    println!("  Decision: refund the buyer\n");

    // Buyer + Arbitrator sign
    let refund_data = format!("{}{}{}",
        escrow_multisig_dispute.address,
        buyer.address,
        payment_amount);

    println!("Signing process:");
    let buyer_sig_dispute = buyer.sign(&refund_data);
    println!("  Buyer has signed (agreeing to refund)");

    let arbitrator_sig = arbitrator.sign(&refund_data);
    println!("  Arbitrator has signed (executing ruling)");

    let dispute_sigs = vec![buyer_sig_dispute, arbitrator_sig];

    if dispute_sigs.len() >= escrow_multisig_dispute.required_sigs {
        println!("\nSignatures satisfy requirement (2/3)");
        println!("Executing refund\n");

        println!("=== Dispute Resolved ===");
        println!("Refund to buyer: {} sat", payment_amount);
        println!("Arbitration fee: 50 sat (deducted from escrow)");
    }

    Ok(())
}
}

Dispute flow:

  1. Buyer reports a problem with the item
  2. Seller refuses a refund
  3. Arbitrator investigates
  4. Arbitrator issues a ruling
  5. Buyer + Arbitrator sign
  6. Funds returned to buyer

Sample Output

=== Bitcoin Escrow Service Demo ===

Participants created:
  Buyer:      a3f2d8c9e4b7f1a8...
  Seller:     b9e4c7d2a3f1e8b6...
  Arbitrator: c8f1e9d3b4a7c2e5...

Escrow address created:
  Address: 3Mf2d8c9e4b7f1a8...
  Type: 2-of-3 multisig
  Rule: any 2 parties can sign
  Possible combinations:
    - Buyer + Seller (normal transaction)
    - Buyer + Arbitrator (refund to buyer)
    - Seller + Arbitrator (payment to seller)

--- Scenario 1: Buyer deposits escrow funds ---
Buyer balance: 100000 sat

Item price: 50000 sat
Buyer is transferring payment to the escrow address...

Funds escrowed
  Escrow amount: 50000 sat
  Buyer balance: 49900 sat

--- Scenario 2A: Normal transaction (buyer satisfied) ---
Seller has shipped
Buyer received the item and is satisfied

Signing process:
  Buyer has signed (confirming receipt)
  Seller has signed (accepting payment)

Signatures satisfy requirement (2/3)
Releasing funds to seller

=== Transaction Complete ===
Seller balance: 49950 sat
Escrow balance: 0 sat

--- Scenario 2B: Dispute resolution (item has a problem) ---
Buyer: item doesn't match the description, requesting a refund
Seller: item is fine, refusing refund
Arbitrator intervenes to investigate...

Arbitration ruling:
  Investigation confirms the item has a problem
  Decision: refund the buyer

Signing process:
  Buyer has signed (agreeing to refund)
  Arbitrator has signed (executing ruling)

Signatures satisfy requirement (2/3)
Executing refund

=== Dispute Resolved ===
Refund to buyer: 49950 sat
Arbitration fee: 50 sat (deducted from escrow)

Business Value

1. Buyer Protection

Traditional TransactionEscrow Service
Pay and seller doesn’t shipFunds escrowed; released only after shipping
Item doesn’t match; no refundArbitrator can rule for a refund
No recourse for disputesArbitration mechanism protects rights

2. Seller Protection

Traditional TransactionEscrow Service
Ships but buyer refuses to payPayment escrowed; ship normally
Malicious refund requestsArbitrator makes a fair judgment
No guaranteed paymentPayment certainty

3. Fairness

Buyer alone cannot withdraw funds (needs seller or arbitrator)
Seller alone cannot withdraw funds (needs buyer or arbitrator)
Arbitrator alone cannot withdraw funds (needs buyer or seller)

→ Three-party checks and balances — fair and impartial

Extended Solutions

1. Automated Arbitration

#![allow(unused)]
fn main() {
struct AutoArbitration {
    logistics_tracking: bool,
    photo_evidence: Vec<String>,
    chat_records: Vec<Message>,
}

fn auto_judge(evidence: &AutoArbitration) -> Decision {
    if evidence.logistics_tracking && evidence.photo_evidence.len() > 3 {
        Decision::RefundBuyer  // automatic refund
    } else {
        Decision::ManualReview  // human review
    }
}
}

2. Staged Release

#![allow(unused)]
fn main() {
// Stage 1: shipping confirmed — release 50%
// Stage 2: receipt confirmed — release remaining 50%

let stage1 = escrow_amount / 2;
let stage2 = escrow_amount - stage1;

// Seller provides tracking number → release stage1
// Buyer confirms receipt → release stage2
}

3. Timelock Protection

#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::TimeLock;

// If no dispute within 7 days, automatically release to seller
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. Multi-Tier Arbitration

#![allow(unused)]
fn main() {
// Level 1 arbitration: standard arbitrator
// Level 2 arbitration: senior arbitrator
// Level 3 arbitration: arbitration committee (3-of-5)

let appeals_committee = MultiSigAddress::new(
    3,
    vec![arbitrator1, arbitrator2, arbitrator3, arbitrator4, arbitrator5]
)?;
}

Arbitrator Mechanism

Selection Criteria

Good reputation (track record)
Subject matter expertise (product category)
Neutral and impartial (no conflicts of interest)
Responsive (within 24 hours)

Arbitration Fees

#![allow(unused)]
fn main() {
let arbitration_fee = match dispute_complexity {
    Simple => 50,      // 0.1%
    Medium => 100,     // 0.2%
    Complex => 500,    // 1%
};

// Deducted from the escrow amount
let net_amount = escrow_amount - arbitration_fee;
}

Arbitration Process

1. Buyer or seller initiates dispute
2. Submit evidence (photos, chat records)
3. Arbitrator reviews (within 3 business days)
4. Issue a ruling
5. Execute ruling (sign)
6. Collect arbitration fee

Security Considerations

1. Arbitrator Collusion

Risk: arbitrator colludes with buyer or seller

Protection:

#![allow(unused)]
fn main() {
// Arbitrator must post collateral
let arbitrator_deposit = 100000;

// Collusion detected → slash collateral
if collusion_detected {
    slash_deposit(&arbitrator);
    ban_arbitrator(&arbitrator);
}

// Multiple arbitrators vote
let arbitrators = vec![arb1, arb2, arb3];
let decision = majority_vote(&arbitrators);
}

2. Evidence Fabrication

Protection:

#![allow(unused)]
fn main() {
// Logistics information on-chain
blockchain.add_tracking_info(tracking_number);

// Photo hash on-chain (tamper-proof)
let photo_hash = hash_photo(photo);
blockchain.add_evidence_hash(photo_hash);

// Timestamp proof
let timestamp = blockchain.get_block_time();
}

3. Malicious Delays

Protection:

#![allow(unused)]
fn main() {
// Set arbitration deadline
let deadline = current_time + 7 * 86400;  // 7 days

if current_time > deadline && no_decision {
    // Timeout → automatic refund
    refund_to_buyer();
}
}

Implementation Recommendations

1. Tech Stack

Frontend: web interface to display escrow flow
Backend: SimpleBTC + database
Storage: evidence storage (IPFS)
Notifications: email/SMS alerts

2. User Flow

Buyer:
  1. Browse items
  2. Place order and transfer payment to escrow
  3. Wait for seller to ship
  4. Receive item and confirm
  5. Sign to release funds

Seller:
  1. Wait for buyer to deposit escrow funds
  2. Ship upon seeing successful escrow
  3. Provide tracking number
  4. Wait for buyer to confirm
  5. Sign to receive payment

3. Fee Structure

Platform fee: 1%
Arbitration fee: 0.1–1% (in disputes)
Blockchain transaction fee: dynamic (50–200 sat)

Comparison with Traditional Solutions

vs. Alipay Escrow

FeatureSimpleBTC EscrowAlipay Escrow
DecentralizedYesNo (centralized)
Censorship resistanceYesNo (can be censored)
Cross-border paymentsYesRestricted
FeeLow (0.1–1%)Higher (1–3%)
PrivacyBetterWorse

vs. PayPal Disputes

FeatureSimpleBTC EscrowPayPal
Dispute resolutionArbitratorPlatform support
TransparencyOn-chain, publicly verifiableBlack-box process
IrreversibilityYesNo (can freeze accounts)

Summary

The escrow service achieves the following through 2-of-3 multisig:

Buyer protection — refunds available if item doesn’t match Seller protection — guaranteed receipt of payment Fair arbitration — impartial third-party ruling Decentralized — no need to trust a central platform Transparent — on-chain records are publicly accessible

This is an ideal solution for e-commerce, freelancing, and cross-border trade.


View full source code

Time Deposit System

This example demonstrates how to use the TimeLock feature to implement a time deposit system. Users can choose from different terms (3 months or 1 year); funds cannot be withdrawn before maturity, and principal plus interest can be collected upon maturity.

Business Scenario

Pain points of traditional time deposits:

  • Early withdrawal requires bank approval
  • Interest calculation is opaque
  • Requires trusting a bank
  • Manual action needed at maturity

Blockchain solution:

  • Smart contract executes automatically — no approval needed
  • Interest rules written into code — fully transparent
  • Technically impossible to withdraw before maturity (trustless)
  • Automatically unlocks at maturity

System Architecture

Product Design

ProductTermAnnual RateMinimum AmountMaturity
Short-Term Saver3 months3%1,000 satoshiAutomatic unlock
Steady Earner1 year5%5,000 satoshiAutomatic unlock

Time Calculation

#![allow(unused)]
fn main() {
// 3-month term (approximately 13 weeks, 91 days)
const BLOCKS_PER_3_MONTHS: u64 = 13 * 7 * 144;  // 13,104 blocks

// 1-year term (approximately 52 weeks, 365 days)
const BLOCKS_PER_YEAR: u64 = 52 * 7 * 144;      // 52,416 blocks

// Note: Bitcoin averages one block every 10 minutes, approximately 144 blocks per day
}

Complete Implementation

The following is the complete time deposit system code:

use bitcoin_simulation::{
    blockchain::Blockchain,
    wallet::Wallet,
    advanced_tx::{TimeLock, TimeLockType},
};

fn main() -> Result<(), String> {
    println!("=== Time Deposit System Demo ===\n");

    // Initialize the blockchain
    let mut blockchain = Blockchain::new();

    // Create a user wallet
    let user = Wallet::new();
    println!("User address: {}...{}", &user.address[..16], &user.address[36..]);

    // Give the user initial funds
    setup_balance(&mut blockchain, &user, 20000)?;
    println!("Initial balance: {} satoshi\n", blockchain.get_balance(&user.address));

    // Scenario 1: 3-month time deposit
    println!("--- Scenario 1: 3-month time deposit (3% annual rate) ---");
    let amount_3m = 5000;
    let rate_3m = 0.03;
    let blocks_3m = 13 * 7 * 144;  // 13 weeks

    // Calculate 3-month interest
    let interest_3m = (amount_3m as f64 * rate_3m * 3.0 / 12.0) as u64;
    let total_3m = amount_3m + interest_3m;

    println!("Deposit amount: {} satoshi", amount_3m);
    println!("Expected interest: {} satoshi (3 months @ 3%)", interest_3m);
    println!("Total at maturity: {} satoshi", total_3m);
    println!("Lock period (blocks): {}", blocks_3m);

    // Create the 3-month term
    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(),  // returned to the depositor at maturity
        total_3m,              // principal + interest
        10,
    )?;

    blockchain.add_transaction(deposit_tx_3m.clone())?;
    blockchain.mine_pending_transactions(user.address.clone())?;

    println!("3-month deposit created successfully");
    println!("Transaction ID: {}...{}\n", &deposit_tx_3m.id[..16], &deposit_tx_3m.id[56..]);

    // Scenario 2: 1-year time deposit
    println!("--- Scenario 2: 1-year time deposit (5% annual rate) ---");
    let amount_1y = 10000;
    let rate_1y = 0.05;
    let blocks_1y = 52 * 7 * 144;  // 52 weeks

    // Calculate 1-year interest
    let interest_1y = (amount_1y as f64 * rate_1y) as u64;
    let total_1y = amount_1y + interest_1y;

    println!("Deposit amount: {} satoshi", amount_1y);
    println!("Expected interest: {} satoshi (1 year @ 5%)", interest_1y);
    println!("Total at maturity: {} satoshi", total_1y);
    println!("Lock period (blocks): {}", blocks_1y);

    // Create the 1-year term
    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-year deposit created successfully");
    println!("Transaction ID: {}...{}\n", &deposit_tx_1y.id[..16], &deposit_tx_1y.id[56..]);

    // Show current balance
    let current_balance = blockchain.get_balance(&user.address);
    println!("Available balance: {} satoshi", current_balance);
    println!("Total in time deposits: {} satoshi (locked)\n", amount_3m + amount_1y);

    // Scenario 3: Attempt early withdrawal (should fail)
    println!("--- Scenario 3: Attempting early withdrawal ---");
    println!("Current block height: {}", blockchain.chain.len());
    println!("3-month deposit unlock height: {}", blockchain.chain.len() as u64 + blocks_3m);

    match timelock_3m.is_spendable(&blockchain) {
        true => println!("ERROR: deposit not yet matured but withdrawal is possible!"),
        false => println!("Correct: deposit not yet matured, funds are locked"),
    }

    // Scenario 4: Simulate passage of time (mine to 3 months later)
    println!("\n--- Scenario 4: 3-month deposit matures ---");
    println!("Simulating mining {} blocks...", blocks_3m);

    // Fast-simulate mining
    for _ in 0..blocks_3m {
        blockchain.mine_pending_transactions(user.address.clone())?;
    }

    println!("Current block height: {}", blockchain.chain.len());

    // Check whether withdrawal is possible
    if timelock_3m.is_spendable(&blockchain) {
        println!("3-month deposit has matured, withdrawal is available");

        // Collect principal + interest
        println!("Withdrawal amount: {} satoshi (principal {} + interest {})",
                 total_3m, amount_3m, interest_3m);

        let final_balance = blockchain.get_balance(&user.address);
        println!("Balance after receipt: {} satoshi", final_balance);
    } else {
        println!("ERROR: deposit has matured but withdrawal is unavailable");
    }

    // Scenario 5: 1-year deposit not yet matured
    println!("\n--- Scenario 5: 1-year deposit status ---");
    println!("Current block height: {}", blockchain.chain.len());
    println!("1-year deposit unlock height: {}", blockchain.chain.len() as u64 + blocks_1y - blocks_3m);

    match timelock_1y.is_spendable(&blockchain) {
        true => println!("1-year deposit has matured, withdrawal is available"),
        false => {
            let remaining = blocks_1y - blocks_3m;
            println!("1-year deposit has not yet matured; {} blocks remaining (approximately {} days)",
                     remaining, remaining / 144);
        }
    }

    println!("\n=== Demo Complete ===");

    Ok(())
}

// Helper function: initialize balance
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(())
}

Code Walkthrough

1. Product Parameter Definitions

#![allow(unused)]
fn main() {
// Short-Term Saver: 3-month term
let amount_3m = 5000;              // deposit amount
let rate_3m = 0.03;                // 3% annual rate
let blocks_3m = 13 * 7 * 144;      // 3 months = 13 weeks = 13,104 blocks

// Calculate interest: principal × annual rate × time (months/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
}

Why block height instead of timestamp?

  • More precise: block height is a discrete integer with no ambiguity
  • More reliable: timestamps can be manipulated by miners (±2 hours)
  • More consistent: the entire network has uniform consensus on block height

2. Creating the TimeLock Term

#![allow(unused)]
fn main() {
// Create a timelock: current height + lock period
let timelock_3m = TimeLock::new(
    TimeLockType::BlockHeight(
        blockchain.chain.len() as u64 + blocks_3m
    )
);
}

Key points:

  • blockchain.chain.len() = current block height
  • + blocks_3m = unlock block height
  • Before the unlock height, the transaction cannot be spent

3. Creating the Time Deposit Transaction

#![allow(unused)]
fn main() {
let deposit_tx_3m = timelock_3m.create_timelocked_transaction(
    &mut blockchain,
    &user,                      // depositor
    user.address.clone(),       // returned to the depositor at maturity
    total_3m,                   // principal + interest
    10,                         // fee
)?;
}

Transaction flow:

User balance → [timelocked transaction] → UTXO pool (locked state)
                        ↓
                 (spendable only at maturity)
                        ↓
               User balance (principal + interest)

4. Maturity Check

#![allow(unused)]
fn main() {
if timelock_3m.is_spendable(&blockchain) {
    // withdrawal available
} else {
    // not yet matured
}
}

Check logic:

#![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) => {
            // Compare with current timestamp
            current_timestamp() >= *time
        }
    }
}
}

Sample Output

$ cargo run --example timelock_savings

=== Time Deposit System Demo ===

User address: a3f2d8c9e4b7f1a8...c4e7d9b2a5c
Initial balance: 20000 satoshi

--- Scenario 1: 3-month time deposit (3% annual rate) ---
Deposit amount: 5000 satoshi
Expected interest: 37 satoshi (3 months @ 3%)
Total at maturity: 5037 satoshi
Lock period (blocks): 13104
3-month deposit created successfully
Transaction ID: d4f7a9e2b5c8f1a3...b5c8f1a3d4f7

--- Scenario 2: 1-year time deposit (5% annual rate) ---
Deposit amount: 10000 satoshi
Expected interest: 500 satoshi (1 year @ 5%)
Total at maturity: 10500 satoshi
Lock period (blocks): 52416
1-year deposit created successfully
Transaction ID: e5g8b0f3c6d9g2b4...c6d9g2b4e5g8

Available balance: 4960 satoshi
Total in time deposits: 15000 satoshi (locked)

--- Scenario 3: Attempting early withdrawal ---
Current block height: 4
3-month deposit unlock height: 13108
Correct: deposit not yet matured, funds are locked

--- Scenario 4: 3-month deposit matures ---
Simulating mining 13104 blocks...
Current block height: 13108
3-month deposit has matured, withdrawal is available
Withdrawal amount: 5037 satoshi (principal 5000 + interest 37)
Balance after receipt: 10497 satoshi

--- Scenario 5: 1-year deposit status ---
Current block height: 13108
1-year deposit unlock height: 52420
1-year deposit has not yet matured; 39312 blocks remaining (approximately 273 days)

=== Demo Complete ===

Business Value

Value to Users

FeatureTraditional Bank Term DepositBlockchain Term DepositAdvantage
Rate transparencyBank decidesCode is openFully transparent
Forced savingsCan withdraw earlyTechnically lockedTruly forced
Interest guaranteeBank’s promiseSmart contractAuto-executes
Maturity actionMust go to bankAutomatic unlockNo action needed
Trust costHigh (trust the bank)Low (trust the code)Decentralized

Return Comparison (assuming 10,000 satoshi deposited)

ProductTermRateTotal at MaturityEarnings
Demand deposit0.3%10,03030
Short-Term Saver3 months3%10,07575
Steady Earner1 year5%10,500500

Calculation formula:

Total at maturity = principal × (1 + annual rate × term in years)

3 months: 10000 × (1 + 0.03 × 0.25) = 10075
1 year:   10000 × (1 + 0.05 × 1.0)  = 10500

Extended Solutions

1. Laddered Deposits

#![allow(unused)]
fn main() {
struct LadderDeposit {
    amount: u64,
    start_height: u64,
    periods: Vec<(u64, f64)>,  // (term blocks, rate)
}

impl LadderDeposit {
    // Create a laddered deposit: stagger maturity dates
    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 months, 3%
                (26 * 7 * 144, 0.04),   // 6 months, 4%
                (39 * 7 * 144, 0.045),  // 9 months, 4.5%
                (52 * 7 * 144, 0.05),   // 12 months, 5%
            ],
        }
    }
}

// Benefits:
// - One tranche matures every 3 months, maintaining liquidity
// - Average rate higher than a single short-term deposit
// - Reduces interest rate fluctuation risk
}

2. Auto-Renewal

#![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;

            // Calculate this period's principal + interest
            let interest = (total as f64 * self.rate
                          * (self.term_blocks as f64 / 52416.0)) as u64;
            total += interest;

            // Create the renewal transaction
            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)
    }
}

// Usage example:
let auto_deposit = AutoRenewDeposit {
    principal: 10000,
    term_blocks: 13 * 7 * 144,  // 3 months
    rate: 0.03,
    max_renewals: 4,  // auto-renew 4 times = 1 year
};

// Automatically creates 4 deposits, renewing every 3 months
let txs = auto_deposit.create_auto_renew(&mut blockchain, &user)?;
}

3. Capital-Protected Floating Yield

#![allow(unused)]
fn main() {
struct FloatingDeposit {
    principal: u64,
    min_rate: f64,      // capital protection rate
    bonus_rate: f64,    // bonus rate
    target_blocks: u64, // target block count
}

impl FloatingDeposit {
    pub fn calculate_interest(&self, blockchain: &Blockchain) -> u64 {
        let actual_blocks = blockchain.chain.len() as u64;

        // Base interest (capital protection)
        let base = (self.principal as f64 * self.min_rate) as u64;

        // Bonus interest (based on actual holding time)
        if actual_blocks >= self.target_blocks {
            let bonus = (self.principal as f64 * self.bonus_rate) as u64;
            base + bonus
        } else {
            base
        }
    }
}

// Usage example:
let floating = FloatingDeposit {
    principal: 10000,
    min_rate: 0.03,    // 3% guaranteed
    bonus_rate: 0.02,  // additional 2% bonus
    target_blocks: 52 * 7 * 144,  // bonus requires holding for 1 year
};

// Under 1 year: 3% interest = 300 satoshi
// 1 full year:  5% interest = 500 satoshi
}

4. Early Redemption (with Penalty)

#![allow(unused)]
fn main() {
struct EarlyWithdraw {
    deposit_tx: Transaction,
    lock_height: u64,
    penalty_rate: f64,  // penalty rate
}

impl EarlyWithdraw {
    pub fn withdraw_early(
        &self,
        blockchain: &mut Blockchain,
        wallet: &Wallet,
    ) -> Result<Transaction, String> {
        let current = blockchain.chain.len() as u64;

        // Check whether this is an early redemption
        if current >= self.lock_height {
            return Err("Deposit has matured; please use normal withdrawal".to_string());
        }

        // Calculate penalty
        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);

        // Create early redemption transaction (requires admin signature)
        let tx = blockchain.create_transaction(
            wallet,
            wallet.address.clone(),
            actual_amount,
            10,
        )?;

        println!("Early redemption: principal {}, penalty {}, received {}",
                 principal, penalty, actual_amount);

        Ok(tx)
    }
}

// Usage example:
// User deposits 10000 for 1 year, withdraws early after 6 months
// Penalty 5% = 500 satoshi
// Receives 9500 satoshi (loses 500)
}

Security Considerations

1. Interest Funding Source

#![allow(unused)]
fn main() {
// Incorrect: creating interest out of thin air
let interest = 100;
let total = principal + interest;  // where does the interest come from?

// Correct: interest paid from a reserve pool
struct DepositPool {
    reserves: u64,  // reserve funds
}

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("Insufficient pool balance".to_string());
        }

        self.reserves -= interest;
        Ok(interest)
    }
}
}

2. Timestamp Manipulation Attack

Attack scenario: miner manipulates the timestamp to cause early maturity

Defense:

#![allow(unused)]
fn main() {
// Use block height, not timestamp
TimeLockType::BlockHeight(height)  // recommended

// Avoid using timestamps (easily manipulated)
TimeLockType::Timestamp(time)      // not recommended
}

3. Reentrancy Attack

#![allow(unused)]
fn main() {
// Incorrect: transfer first, then update state
fn withdraw(&mut self) {
    self.transfer(user, amount);  // transfer first
    self.balance = 0;             // update later (may be reentered)
}

// Correct: update state first, then transfer (checks-effects-interactions pattern)
fn withdraw(&mut self) {
    let amount = self.balance;    // check
    self.balance = 0;             // effect
    self.transfer(user, amount);  // interaction
}
}

4. Integer Overflow

#![allow(unused)]
fn main() {
// Incorrect: may overflow
let total = principal + interest;  // u64 overflow risk

// Correct: use checked_add
let total = principal.checked_add(interest)
    .ok_or("Arithmetic overflow")?;
}

Implementation Recommendations

Technical Considerations

  1. Adequate testing

    #![allow(unused)]
    fn main() {
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_interest_calculation() { /* ... */ }
    
        #[test]
        fn test_early_withdraw_penalty() { /* ... */ }
    
        #[test]
        fn test_timelock_enforcement() { /* ... */ }
    }
    }
  2. Code audit

    • Verify interest calculation formulas are correct
    • Verify that the timelock is reliable
    • Verify that the funding source is clearly defined
    • Handle edge cases
  3. Monitoring and alerts

    #![allow(unused)]
    fn main() {
    // Monitor key metrics
    - Reserve pool balance warning (< 10%)
    - Matured deposits not yet collected (> 1 month)
    - Abnormal early redemption frequency
    }

Business Considerations

  1. Risk disclosures

    Time Deposit Risk Notice:
    1. Funds will be locked and cannot be withdrawn before maturity
    2. Interest is paid from the reserve pool and carries payment risk
    3. Smart contracts may contain unknown vulnerabilities
    4. Blockchain transactions are irreversible; proceed with care
    
  2. User education

    • Provide a sandbox demo environment for users to practice
    • Offer detailed operation guides
    • Explain the differences from traditional banking
    • Emphasize the importance of private key custody
  3. Product iteration

    • Collect user feedback
    • Analyze maturity data
    • Optimize interest rate strategy
    • Add more product types

Real-World Applications

DeFi Time Deposit Protocols

Compound: lending protocol, deposits accrue interest automatically

User deposits ETH → receives cETH (interest-bearing token)
Interest rate floats with the market → withdrawable at any time

Anchor Protocol: fixed-rate deposits (Terra ecosystem)

Deposit UST → fixed ~20% APY
Interest comes from lending markets and staking rewards

Alchemix: self-repaying loans

Deposit DAI → borrow alUSD (50% LTV)
Interest automatically repays the loan → no repayments needed

Comparison with SimpleBTC

FeatureSimpleBTC Term DepositDeFi Term Deposit
TimelockHard lock (nLockTime)Soft lock (contract)
RateFixedUsually floating
LiquidityWithdraw only at maturityEarly withdrawal available (with penalty)
Interest sourceReserve poolLending/staking
RiskTimelock riskSmart contract risk

FAQ

Q1: Where does the interest for time deposits come from?

A: SimpleBTC interest is for demo purposes. In real applications, interest may come from:

  • Reserve pool funds
  • Interest spreads in lending markets
  • Distribution of mining rewards
  • Return of transaction fees
  • Protocol token issuance

Q2: Can I withdraw early?

A: SimpleBTC uses nLockTime hard locking, so early withdrawal is technically impossible. Real applications can be designed with:

  • Early redemption with a penalty (5–10% fee)
  • NFT collateral borrowing (keeps the term deposit running)
  • Secondary market transfer (sell at a discount)

Q3: What if I forget to collect after maturity?

A: UTXOs are permanently valid and can be collected at any time. However, note that:

  • No additional interest accrues after maturity
  • Set maturity reminders
  • Auto-renewal can be implemented

Q4: How is the timelock period calculated?

A:

Block height method (recommended):
- 3 months ≈ 13,104 blocks (91 days × 144 blocks/day)
- 1 year   ≈ 52,416 blocks (365 days × 144 blocks/day)

Timestamp method (not recommended):
- 3 months = current timestamp + 7,862,400 seconds
- 1 year   = current timestamp + 31,536,000 seconds

References


Back to Examples | Next Example: Enterprise Multisig

Core Module API

The SimpleBTC core module provides fundamental Bitcoin blockchain functionality.

Module List

Transaction Module

Block Module

  • Block API - Block structure, proof of work, Merkle root

Blockchain Module

Wallet Module

UTXO Module

  • UTXO API - UTXO set, balance queries, double-spend protection

Quick Index

Common Functions

Create a Wallet

#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;
let wallet = Wallet::new();
}

Create a Transaction

#![allow(unused)]
fn main() {
let tx = blockchain.create_transaction(
    &from_wallet,
    to_address,
    amount,
    fee
)?;
}

Mine

#![allow(unused)]
fn main() {
blockchain.mine_pending_transactions(miner_address)?;
}

Query Balance

#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&address);
}

Data Flow

1. Create Wallet
   Wallet::new() → generate key pair → obtain address

2. Create Transaction
   Select UTXOs → build inputs/outputs → sign → verify

3. Add Transaction
   Validate transaction → add to pending pool → wait to be mined

4. Mine
   Collect transactions → create Coinbase → compute Merkle root → PoW → update UTXOs

5. Query
   Traverse UTXO set → accumulate balance

Type Definitions

Core Types

#![allow(unused)]
fn main() {
// Amount unit: satoshi
type Amount = u64;  // 1 BTC = 100,000,000 satoshi

// Address: 40-character hexadecimal
type Address = String;

// Hash: 64-character hexadecimal
type Hash = String;

// Unix timestamp (seconds)
type Timestamp = u64;
}

Error Types

#![allow(unused)]
fn main() {
// All APIs return Result<T, String>
type ApiResult<T> = Result<T, String>;

// Common error messages
"Insufficient balance (including fee)"
"UTXO does not exist"
"Transaction validation failed"
"Referenced transaction does not exist"
"No pending transactions"
}

Usage Patterns

Basic Pattern

use bitcoin_simulation::{
    blockchain::Blockchain,
    wallet::Wallet,
};

fn main() -> Result<(), String> {
    // 1. Initialize
    let mut blockchain = Blockchain::new();
    let wallet = Wallet::new();

    // 2. Operate
    let tx = blockchain.create_transaction(...)?;
    blockchain.add_transaction(tx)?;
    blockchain.mine_pending_transactions(...)?;

    // 3. Query
    let balance = blockchain.get_balance(&wallet.address);

    Ok(())
}

Error Handling Pattern

#![allow(unused)]
fn main() {
match blockchain.create_transaction(&alice, bob_addr, 1000, 10) {
    Ok(tx) => {
        blockchain.add_transaction(tx)?;
        println!("✓ Transaction successful");
    }
    Err(e) => {
        eprintln!("✗ Error: {}", e);
        // Handle error...
    }
}
}

Performance Considerations

UTXO Queries

  • Time complexity: O(n), where n is the total number of UTXOs
  • Recommendation: Use index optimization (see indexer.rs)

Mining

  • Time complexity: O(2^difficulty)
  • Recommendation: Difficulty 3–4 is suitable for demos; real applications require higher values

Blockchain Validation

  • Time complexity: O(n*m), where n is the block count and m is the average transaction count
  • Recommendation: Validate periodically rather than after every operation

Thread Safety

⚠️ Note: The current implementation is not thread-safe.

For concurrent access:

#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};

let blockchain = Arc::new(Mutex::new(Blockchain::new()));

// In different threads
let blockchain = blockchain.clone();
let mut bc = blockchain.lock().unwrap();
bc.create_transaction(...)?;
}

Next Steps


Back to Documentation Home

Transaction API

The transaction module provides a complete implementation of the Bitcoin UTXO model.

Data Structures

TxInput

A transaction input that references a previously unspent output (UTXO).

#![allow(unused)]
fn main() {
pub struct TxInput {
    pub txid: String,        // ID of the referenced transaction
    pub vout: usize,         // Output index
    pub signature: String,   // Digital signature
    pub pub_key: String,     // Public key
}
}

Methods:

new

#![allow(unused)]
fn main() {
pub fn new(
    txid: String,
    vout: usize,
    signature: String,
    pub_key: String
) -> Self
}

Creates a new transaction input.

Parameters:

  • txid - ID of the referenced transaction
  • vout - Output index number
  • signature - Signature generated with the private key
  • pub_key - Corresponding public key

Example:

#![allow(unused)]
fn main() {
let input = TxInput::new(
    "abc123...".to_string(),
    0,
    wallet.sign("data"),
    wallet.public_key.clone()
);
}

TxOutput

A transaction output representing an unspent amount (UTXO).

#![allow(unused)]
fn main() {
pub struct TxOutput {
    pub value: u64,              // Amount (satoshi)
    pub pub_key_hash: String,    // Recipient address
}
}

Methods:

new

#![allow(unused)]
fn main() {
pub fn new(value: u64, address: String) -> Self
}

Creates a new transaction output.

Parameters:

  • value - Output amount (satoshi)
  • address - Recipient address

Example:

#![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
}

Checks whether this output can be unlocked by the specified address.

Parameters:

  • address - The address to check

Return value:

  • true - Address matches
  • false - Address does not match

Example:

#![allow(unused)]
fn main() {
if output.can_be_unlocked_with(&alice.address) {
    println!("Alice can spend this output");
}
}

Transaction

The complete transaction structure.

#![allow(unused)]
fn main() {
pub struct Transaction {
    pub id: String,                 // Transaction ID
    pub inputs: Vec<TxInput>,       // List of inputs
    pub outputs: Vec<TxOutput>,     // List of outputs
    pub timestamp: u64,             // Unix timestamp
    pub fee: u64,                   // Transaction fee
}
}

Methods:

new

#![allow(unused)]
fn main() {
pub fn new(
    inputs: Vec<TxInput>,
    outputs: Vec<TxOutput>,
    timestamp: u64,
    fee: u64
) -> Self
}

Creates a new transaction.

Parameters:

  • inputs - List of transaction inputs
  • outputs - List of transaction outputs
  • timestamp - Unix timestamp
  • fee - Transaction fee (satoshi)

Return value:

  • A newly created transaction instance with an automatically computed ID

Example:

#![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
}

Creates a Coinbase transaction (mining reward).

Parameters:

  • to - Miner address
  • reward - Block reward (excluding fees)
  • timestamp - Unix timestamp
  • total_fees - Sum of all transaction fees in the block

Return value:

  • Coinbase transaction instance

Example:

#![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
}

Computes the transaction hash (transaction ID).

Return value:

  • 64-character hexadecimal hash string

Notes:

  • Uses the SHA256 algorithm
  • Includes all transaction data (inputs, outputs, timestamp, fee)
  • Any change in data will produce a completely different hash

Example:

#![allow(unused)]
fn main() {
let tx_id = tx.calculate_hash();
println!("Transaction ID: {}", tx_id);
}

is_coinbase

#![allow(unused)]
fn main() {
pub fn is_coinbase(&self) -> bool
}

Checks whether this is a Coinbase transaction.

Return value:

  • true - Coinbase transaction
  • false - Regular transaction

Criteria:

  • Exactly one input
  • That input’s txid is empty

Example:

#![allow(unused)]
fn main() {
if tx.is_coinbase() {
    println!("This is a mining reward transaction");
} else {
    println!("This is a regular transaction");
}
}

verify

#![allow(unused)]
fn main() {
pub fn verify(&self) -> bool
}

Validates the transaction (simplified version).

Validation items:

  1. Coinbase transactions are always valid
  2. Checks for at least one input and one output
  3. Checks that the signature and public key are non-empty

Return value:

  • true - Transaction is valid
  • false - Transaction is invalid

Note: Real Bitcoin also requires:

  • ECDSA signature correctness
  • UTXO existence
  • Amount balance
  • Script execution

Example:

#![allow(unused)]
fn main() {
if tx.verify() {
    blockchain.add_transaction(tx)?;
} else {
    return Err("Invalid transaction".to_string());
}
}

size

#![allow(unused)]
fn main() {
pub fn size(&self) -> usize
}

Calculates the transaction size in bytes.

Return value:

  • Byte size of the transaction

Use cases:

  • Computing fee rate (sat/byte)
  • Estimating block space usage
  • Fee estimation

Example:

#![allow(unused)]
fn main() {
let size = tx.size();
println!("Transaction size: {} bytes", size);
}

fee_rate

#![allow(unused)]
fn main() {
pub fn fee_rate(&self) -> f64
}

Calculates the transaction fee rate (satoshi/byte).

Return value:

  • Fee rate (sat/byte)

Formula:

fee_rate = fee / size

Fee rate reference:

  • 1–5 sat/byte: Low priority
  • 5–20 sat/byte: Medium priority
  • 20–50 sat/byte: High priority
  • 50+ sat/byte: Urgent

Example:

#![allow(unused)]
fn main() {
let rate = tx.fee_rate();
println!("Fee rate: {:.2} sat/byte", rate);

if rate < 5.0 {
    println!("Warning: Low fee rate, confirmation may be slow");
}
}

output_sum

#![allow(unused)]
fn main() {
pub fn output_sum(&self) -> u64
}

Gets the total amount of all outputs.

Return value:

  • Total output amount (satoshi)

Use cases:

  • Verifying transaction balance
  • Computing the actual fee

Formula:

fee = input_sum - output_sum

Example:

#![allow(unused)]
fn main() {
let output_total = tx.output_sum();
let fee = input_total - output_total;
println!("Fee: {}", fee);
}

Usage Examples

Creating a Simple Transaction

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 receives initial funds
    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 sends to Bob
    let tx = blockchain.create_transaction(
        &alice,
        bob.address.clone(),
        3000,  // amount
        10,    // fee
    )?;

    // View transaction details
    println!("Transaction ID: {}", tx.id);
    println!("Number of inputs: {}", tx.inputs.len());
    println!("Number of outputs: {}", tx.outputs.len());
    println!("Fee: {}", tx.fee);
    println!("Fee rate: {:.2} sat/byte", tx.fee_rate());

    // Add to the blockchain
    blockchain.add_transaction(tx)?;
    blockchain.mine_pending_transactions(bob.address)?;

    Ok(())
}

Batch Transactions

#![allow(unused)]
fn main() {
// Create multiple transactions to test fee priority
let transactions = vec![
    (bob.address.clone(), 1000, 1),   // Low fee
    (charlie.address.clone(), 2000, 50), // High fee
    (david.address.clone(), 3000, 5), // Medium fee
];

for (to, amount, fee) in transactions {
    let tx = blockchain.create_transaction(&alice, to, amount, fee)?;
    blockchain.add_transaction(tx)?;
}

// Mining sorts transactions by fee rate, highest first
blockchain.mine_pending_transactions(miner.address)?;
}

Manually Building a Transaction

#![allow(unused)]
fn main() {
use std::time::{SystemTime, UNIX_EPOCH};
use bitcoin_simulation::transaction::{Transaction, TxInput, TxOutput};

// 1. Create input (requires knowledge of the prior UTXO)
let input = TxInput::new(
    "previous_tx_id".to_string(),
    0,  // vout
    alice.sign("tx_data"),
    alice.public_key.clone(),
);

// 2. Create outputs
let output1 = TxOutput::new(3000, bob.address);     // To Bob
let output2 = TxOutput::new(6990, alice.address);   // Change

// 3. Assemble transaction
let timestamp = SystemTime::now()
    .duration_since(UNIX_EPOCH)
    .unwrap()
    .as_secs();

let tx = Transaction::new(
    vec![input],
    vec![output1, output2],
    timestamp,
    10,  // fee
);

// 4. Verify and add
if tx.verify() {
    blockchain.add_transaction(tx)?;
}
}

Error Handling

#![allow(unused)]
fn main() {
match blockchain.create_transaction(&alice, bob.address, 1000, 10) {
    Ok(tx) => {
        println!("✓ Transaction created successfully");
        blockchain.add_transaction(tx)?;
    }
    Err(e) => {
        eprintln!("❌ Transaction creation failed: {}", e);
        // Common errors:
        // - "Insufficient balance (including fee)"
        // - "UTXO does not exist"
        // - "Referenced transaction does not exist"
    }
}
}

Best Practices

1. Setting Fees

#![allow(unused)]
fn main() {
// Set fee based on urgency
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 Selection

#![allow(unused)]
fn main() {
// Prefer small UTXOs to avoid fragmentation
let utxos = blockchain.utxo_set.find_spendable_outputs(&address, amount)?;
println!("Used {} UTXOs", utxos.1.len());
}

3. Transaction Validation

#![allow(unused)]
fn main() {
// Validate immediately after creating a transaction
let tx = Transaction::new(...);
assert!(tx.verify(), "Transaction validation failed");
assert!(tx.fee_rate() >= 1.0, "Fee rate too low");
}

References


Back to API Index

Block API

Block is the fundamental unit of the blockchain, defined in src/block.rs. Each block contains a batch of confirmed transactions and is linked to the previous block via a hash chain, together forming an immutable ledger.


Block Struct

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
    pub index: u32,                     // Block height (index); genesis block is 0
    pub timestamp: u64,                 // Unix timestamp (seconds)
    pub transactions: Vec<Transaction>, // Transaction list (first must be a Coinbase transaction)
    pub previous_hash: String,          // Parent block hash (SHA256, 64-character hex)
    pub hash: String,                   // Current block hash (found through mining)
    pub nonce: u64,                     // Proof-of-work nonce (adjusted during mining)
    pub merkle_root: String,            // Merkle tree root hash of all transactions
}
}

Field Descriptions

FieldTypeDescription
indexu32Block height. Genesis block is 0; each subsequent block increments by 1.
timestampu64Unix timestamp (seconds) when the block was created. Automatically set by SystemTime::now().
transactionsVec<Transaction>List of transactions in the block. The first must be a Coinbase transaction (miner reward).
previous_hashStringSHA256 hash (64-character hex) of the parent block. For the genesis block, this field is "0".
hashStringSHA256 hash of the current block. Computed by calculate_hash(), continuously updated during mining until the difficulty requirement is met.
nonceu64Proof-of-work nonce. Miners increment nonce to find a hash satisfying the difficulty target.
merkle_rootStringMerkle tree root hash of all transactions in the block. Any tampered transaction will change this value.

Chain Structure

Genesis Block (index=0)  ->   Block 1         ->   Block 2
prev: "0"                     prev: abc123...       prev: def456...
hash: abc123...               hash: def456...       hash: ghi789...

Because each block’s hash depends on previous_hash and all transactions (via merkle_root), modifying any historical block requires recomputing the hash of all subsequent blocks — computationally infeasible.


Methods

Block::new

Creates a new block. Automatically sets the timestamp and computes the Merkle root, but nonce starts at 0 and hash is the initial computed value (does not yet satisfy mining difficulty).

#![allow(unused)]
fn main() {
pub fn new(
    index: u32,
    transactions: Vec<Transaction>,
    previous_hash: String,
) -> Block
}

Parameters:

  • index — Height of the new block.
  • transactions — List of transactions to include in the block (first should be a Coinbase transaction).
  • previous_hash — Hash string of the parent block.

Return value: An initialized Block instance (mining not yet complete).

Internal flow:

  1. Obtain the current Unix timestamp.
  2. Build a MerkleTree from the id list of transactions and compute merkle_root.
  3. Construct the block with nonce = 0 and call calculate_hash() to get the initial 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 #{}: {}", block.index, block.hash);
}

Block::calculate_hash

Computes the SHA256 hash of the block. The hash input includes index, timestamp, merkle_root, previous_hash, and nonce.

#![allow(unused)]
fn main() {
pub fn calculate_hash(&self) -> String
}

Return value: 64-character lowercase hexadecimal SHA256 hash string.

Hash input format:

"{index}{timestamp}{merkle_root}{previous_hash}{nonce}"

Using merkle_root rather than the full transaction data keeps the block header lightweight (approximately 80 bytes) while ensuring the integrity of all transaction content.

#![allow(unused)]
fn main() {
let mut block = Block::new(1, transactions, prev_hash);
// Recompute hash after modifying nonce (core mining logic)
block.nonce += 1;
block.hash = block.calculate_hash();
println!("New hash: {}", block.hash);
}

Block::validate_transactions

Validates the signature validity of all transactions in the block. Calls each transaction’s verify() method in sequence.

#![allow(unused)]
fn main() {
pub fn validate_transactions(&self) -> bool
}

Return value:

  • true — All transaction signatures are valid.
  • false — At least one invalid transaction exists.
#![allow(unused)]
fn main() {
let block = Block::new(1, transactions, prev_hash);

if block.validate_transactions() {
    println!("All transactions valid, can be added to chain");
} else {
    println!("Block contains invalid transactions, rejected");
}
}

Note: This method only validates signatures, not UTXO balances. Balance validation is handled at the Blockchain layer.


Block::verify_transaction_inclusion

Verifies whether a specific transaction is included in this block using a Merkle proof. This is the core functionality of SPV (Simplified Payment Verification), requiring no traversal of all transactions — time complexity is O(log n).

#![allow(unused)]
fn main() {
pub fn verify_transaction_inclusion(
    &self,
    tx_id: &str,
    index: usize,
) -> bool
}

Parameters:

  • tx_id — The transaction ID (hash string) to verify.
  • index — The position index of the transaction in the block’s transaction list (starting from 0).

Return value:

  • true — The transaction is indeed included in this block and the Merkle proof is valid.
  • false — The transaction is not in this block, or the proof is invalid.

Internal flow:

  1. Reconstruct the block’s MerkleTree.
  2. Call get_proof(tx_id) to generate a Merkle proof.
  3. Call MerkleTree::verify_proof() to verify the proof against merkle_root.
#![allow(unused)]
fn main() {
let tx_id = "abc123def456...";
let tx_index = 2; // Position of the transaction in the block

if block.verify_transaction_inclusion(tx_id, tx_index) {
    println!("Transaction confirmed in block #{}", block.index);
} else {
    println!("Transaction is not in this block");
}
}

Block::mine_block

Proof-of-Work mining. Continuously increments nonce and recomputes the hash until the hash prefix satisfies the difficulty requirement (i.e., starts with difficulty number of '0' characters).

#![allow(unused)]
fn main() {
pub fn mine_block(&mut self, difficulty: usize)
}

Parameters:

  • difficulty — Mining difficulty: the number of leading '0' characters required in the hash.

Side effects: Modifies self.nonce and self.hash until a valid hash is found.

#![allow(unused)]
fn main() {
let mut block = Block::new(1, transactions, prev_hash);
println!("Starting mining, difficulty: 4");
block.mine_block(4); // Hash must start with "0000"
println!("Mining complete: {}", block.hash);
println!("Nonce used: {}", block.nonce);
// Example output: 0000a3f7c2...
}

About difficulty: Bitcoin mainnet’s current difficulty is roughly equivalent to about 20 leading '0' characters in the hash (requiring approximately 2^80 hash calculations). This project uses smaller difficulty values (such as 2–4) for demonstration purposes.


Complete Usage Example

use simplebtc::block::Block;
use simplebtc::transaction::Transaction;
use simplebtc::wallet::Wallet;

fn main() {
    // 1. Create miner wallet
    let miner = Wallet::new();

    // 2. Create Coinbase transaction (miner reward)
    let coinbase = Transaction::new_coinbase(&miner.address, 3_125_000);

    // 3. Create a regular transfer transaction
    let alice = Wallet::new();
    let bob = Wallet::new();
    let transfer = Transaction::new(&alice, &bob.address, 50_000, 500);

    // 4. Pack into a block (assuming parent hash is known)
    let prev_hash = "0000abc123...".to_string();
    let mut block = Block::new(1, vec![coinbase, transfer], prev_hash);

    // 5. Mine (proof of work)
    block.mine_block(3); // Difficulty 3: hash starts with "000"

    // 6. Validate the block
    assert!(block.validate_transactions(), "Block transactions invalid");
    println!("Block hash: {}", block.hash);
    println!("Merkle root: {}", block.merkle_root);
    println!("Nonce: {}", block.nonce);

    // 7. SPV verification: is a transaction included in this block?
    let included = block.verify_transaction_inclusion(&block.transactions[0].id.clone(), 0);
    println!("Coinbase transaction included: {}", included);
}

Immutability Principle

Attacker attempts to modify a transaction in Block 1:

  Modify transaction
      ↓
  Transaction hash changes
      ↓
  Merkle Root changes
      ↓
  Block 1's Hash changes
      ↓
  Block 2's previous_hash no longer matches
      ↓
  Hashes of Blocks 2, 3, 4... all become invalid
      ↓
  Attacker must re-mine all subsequent blocks (computationally infeasible)

This is the mathematical guarantee of blockchain “immutability.”


  • MerkleTree — Merkle tree implementation, used to compute merkle_root and generate SPV proofs.
  • Transaction — Transaction struct, the core data of a Block.
  • Blockchain — Manages the blockchain, calls mine_block(), and maintains chain state.

Blockchain API

The blockchain module is the core of SimpleBTC, managing the entire state and operations of the blockchain.

Data Structures

Blockchain

#![allow(unused)]
fn main() {
pub struct Blockchain {
    pub chain: Vec<Block>,                      // The blockchain (list of blocks)
    pub difficulty: usize,                      // Mining difficulty
    pub pending_transactions: Vec<Transaction>, // Pending transaction pool
    pub utxo_set: UTXOSet,                     // UTXO set
    pub mining_reward: u64,                    // Mining reward
    pub indexer: TransactionIndexer,           // Transaction indexer
}
}

Methods

Initialization

new

#![allow(unused)]
fn main() {
pub fn new() -> Blockchain
}

Creates a new blockchain, automatically creating the genesis block.

Initial parameters:

  • difficulty: 3 - Mining difficulty (3 leading zeros)
  • mining_reward: 50 - Block reward (50 satoshi)
  • Genesis block contains 100 satoshi sent to genesis_address

Return value: New blockchain instance

Example:

#![allow(unused)]
fn main() {
let mut blockchain = Blockchain::new();
println!("Blockchain initialized, current height: {}", blockchain.chain.len());
}

Transaction Management

create_transaction

#![allow(unused)]
fn main() {
pub fn create_transaction(
    &self,
    from_wallet: &Wallet,
    to_address: String,
    amount: u64,
    fee: u64,
) -> Result<Transaction, String>
}

Creates a new transaction. Automatically selects UTXOs, builds inputs/outputs, and adds signatures.

Parameters:

  • from_wallet - Sender’s wallet (requires private key for signing)
  • to_address - Recipient address
  • amount - Transfer amount (satoshi)
  • fee - Transaction fee (satoshi)

Return value:

  • Ok(Transaction) - Transaction created successfully
  • Err(String) - Error message

Error cases:

  • "Insufficient balance (including fee)" - Not enough UTXOs
  • "UTXO does not exist" - Referenced UTXO has already been spent
  • "Referenced transaction does not exist" - Data inconsistency

Workflow:

  1. Find available UTXOs for the sender
  2. Select sufficient UTXOs (greedy algorithm)
  3. Create transaction inputs (including signatures)
  4. Create transaction outputs (recipient + change)
  5. Compute transaction ID

Example:

#![allow(unused)]
fn main() {
// Basic usage
let tx = blockchain.create_transaction(
    &alice,
    bob.address.clone(),
    5000,  // transfer 5000 satoshi
    10,    // fee 10 satoshi
)?;

blockchain.add_transaction(tx)?;

// Check balance
let balance = blockchain.get_balance(&alice.address);
if balance < amount + fee {
    return Err("Insufficient balance".to_string());
}

// Batch creation
for i in 1..=10 {
    let tx = blockchain.create_transaction(
        &alice,
        recipients[i].clone(),
        1000,
        i as u64,  // different fees
    )?;
    blockchain.add_transaction(tx)?;
}
}

add_transaction

#![allow(unused)]
fn main() {
pub fn add_transaction(&mut self, transaction: Transaction) -> Result<(), String>
}

Adds a transaction to the pending pool, waiting to be mined.

Parameters:

  • transaction - The transaction to add

Validation items:

  1. ✅ Transaction format is correct (verify())
  2. ✅ UTXOs referenced by inputs exist
  3. ✅ Signatures are valid
  4. ✅ Total inputs ≥ total outputs

Return value:

  • Ok(()) - Added successfully
  • Err(String) - Reason for validation failure

Example:

#![allow(unused)]
fn main() {
let tx = blockchain.create_transaction(&alice, bob.address, 1000, 5)?;

match blockchain.add_transaction(tx) {
    Ok(_) => println!("✓ Transaction added to pending pool"),
    Err(e) => eprintln!("✗ Invalid transaction: {}", e),
}

// View number of pending transactions
println!("Pending: {} transactions", blockchain.pending_transactions.len());
}

Mining

mine_pending_transactions

#![allow(unused)]
fn main() {
pub fn mine_pending_transactions(
    &mut self,
    miner_address: String
) -> Result<(), String>
}

Mining: packs pending transactions into a new block.

Parameters:

  • miner_address - Miner address (receives reward)

Mining flow:

  1. Check if there are pending transactions
  2. Sort by fee rate, highest to lowest
  3. Compute total fees
  4. Create Coinbase transaction (reward + fees)
  5. Build Merkle tree
  6. Proof of work (adjust nonce to find a valid hash)
  7. Validate all transactions in the block
  8. Update UTXO set (atomic operation)
  9. Add block to the chain
  10. Clear the pending pool

Return value:

  • Ok(()) - Mining successful
  • Err(String) - Error message

Error cases:

  • "No pending transactions" - Pending pool is empty
  • "Block contains invalid transactions" - Transaction validation failed
  • "UTXO update failed" - Data inconsistency

Performance:

  • Difficulty 3: ~0.001–0.1 seconds
  • Difficulty 4: ~0.01–1 second
  • Difficulty 5: ~0.1–10 seconds
  • Difficulty 6+: Several seconds to several minutes

Example:

#![allow(unused)]
fn main() {
// Basic mining
blockchain.mine_pending_transactions(miner.address.clone())?;

// Mining loop (similar to a real miner)
loop {
    if blockchain.pending_transactions.is_empty() {
        println!("Waiting for new transactions...");
        std::thread::sleep(Duration::from_secs(1));
        continue;
    }

    println!("Starting mining...");
    let start = Instant::now();

    blockchain.mine_pending_transactions(miner.address.clone())?;

    let duration = start.elapsed();
    println!("✓ Mining successful! Time elapsed: {:?}", duration);

    // Check reward
    let reward = blockchain.get_balance(&miner.address);
    println!("Miner balance: {} satoshi", reward);
}
}

Query Operations

get_balance

#![allow(unused)]
fn main() {
pub fn get_balance(&self, address: &str) -> u64
}

Queries the balance of an address.

Parameters:

  • address - The address to query

Return value: Balance (satoshi)

Computation method: Traverse the UTXO set and sum all UTXOs for the address

Example:

#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&alice.address);
println!("Balance: {} satoshi", balance);
println!("Balance: {:.8} BTC", balance as f64 / 100_000_000.0);

// Batch query
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
}

Validates the integrity of the entire blockchain.

Validation items:

  1. ✅ Each block’s hash is correct
  2. ✅ Forward references are correct (previous_hash links)
  3. ✅ Proof of work is valid (hash satisfies difficulty)
  4. ✅ All transactions are valid

Return value:

  • true - Blockchain is complete and valid
  • false - Tampering or error detected

Use cases:

  • Periodic integrity checks
  • Validation after syncing nodes
  • Detection of tampering attacks

Example:

#![allow(unused)]
fn main() {
// Periodic validation
if !blockchain.is_valid() {
    panic!("❌ Blockchain has been tampered with!");
}

// Detailed validation log
for (i, block) in blockchain.chain.iter().enumerate() {
    if block.hash != block.calculate_hash() {
        eprintln!("Block {} has invalid hash", i);
    }
    if !block.validate_transactions() {
        eprintln!("Block {} contains invalid transactions", i);
    }
}

if blockchain.is_valid() {
    println!("✅ Blockchain validation passed");
}
}
#![allow(unused)]
fn main() {
pub fn print_chain(&self)
}

Prints detailed blockchain information (for debugging).

Output includes:

  • Block index, timestamp, hash
  • Previous block hash
  • Nonce value
  • Transaction list (ID, type, fee, inputs/outputs)

Example:

#![allow(unused)]
fn main() {
blockchain.print_chain();

// Example output:
// ========== Blockchain Info ==========
//
// --- Block #0 ---
// Timestamp: 1703001234
// Hash: 0003ab4f9c2d...
// Previous hash: 0
// Nonce: 1247
// Transaction count: 1
//   Transaction #0: abc123...
//     Type: Coinbase (mining reward)
//     Inputs: 1
//     Outputs: 1
//       Output 0: 100 -> genesis_address
// ...
}

Usage Examples

Complete Blockchain Demo

use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};

fn main() -> Result<(), String> {
    println!("=== SimpleBTC Blockchain Demo ===\n");

    // 1. Initialize
    let mut blockchain = Blockchain::new();
    println!("✓ Blockchain created (genesis block)\n");

    // 2. Create participants
    let alice = Wallet::new();
    let bob = Wallet::new();
    let miner = Wallet::new();

    println!("✓ Created 3 wallets");
    println!("  Alice: {}", &alice.address[..16]);
    println!("  Bob:   {}", &bob.address[..16]);
    println!("  Miner: {}\n", &miner.address[..16]);

    // 3. Alice receives initial funds
    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!("✓ Block #1 mined");
    println!("  Alice balance: {}\n", blockchain.get_balance(&alice.address));

    // 4. Multiple transactions
    println!("Creating 5 transactions (different fees)...");
    for i in 1..=5 {
        let tx = blockchain.create_transaction(
            &alice,
            bob.address.clone(),
            100 * i,
            i as u64,  // incrementing fees
        )?;
        blockchain.add_transaction(tx)?;
        println!("  Transaction #{}: {} sat, fee rate: {} sat/byte",
            i, 100 * i, i);
    }

    // 5. Mine (transactions sorted by fee rate)
    println!("\nStarting mining...");
    blockchain.mine_pending_transactions(miner.address.clone())?;
    println!("✓ Block #2 mined\n");

    // 6. Final balances
    println!("=== Final Balances ===");
    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. Validate blockchain
    println!("\n=== Validate Blockchain ===");
    if blockchain.is_valid() {
        println!("✅ Blockchain integrity validation passed");
    } else {
        println!("❌ Blockchain validation failed");
    }

    // 8. Print details
    println!("\n=== Blockchain Details ===");
    blockchain.print_chain();

    Ok(())
}

Fee Priority Demo

#![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();

    // Initialize Alice's balance
    setup_balance(&mut blockchain, &alice, 10000)?;

    // Create transactions with different fee rates
    let txs = vec![
        ("Slow", 1000, 1),   // 1 sat/byte
        ("Fast", 1000, 50),  // 50 sat/byte
        ("Medium", 1000, 10),  // 10 sat/byte
    ];

    println!("Adding transactions:");
    for (i, (name, amount, fee)) in txs.iter().enumerate() {
        let tx = blockchain.create_transaction(
            &alice,
            recipients[i].address.clone(),
            *amount,
            *fee,
        )?;
        println!("  {}: {} sat, fee rate {} sat/byte", name, amount, fee);
        blockchain.add_transaction(tx)?;
    }

    println!("\nMining (auto-sorted by fee rate)...");
    blockchain.mine_pending_transactions(recipients[0].address.clone())?;

    // View transaction order in the latest block
    let latest_block = blockchain.chain.last().unwrap();
    println!("\nTransaction order in block:");
    for (i, tx) in latest_block.transactions.iter().skip(1).enumerate() {
        println!("  #{}: fee rate {:.2} sat/byte",
            i + 1, tx.fee_rate());
    }

    Ok(())
}
}

Monitoring Blockchain State

#![allow(unused)]
fn main() {
fn blockchain_monitor(blockchain: &Blockchain) {
    println!("=== Blockchain State ===");
    println!("Block height: {}", blockchain.chain.len());
    println!("Difficulty: {} ({} leading zeros)",
        blockchain.difficulty, blockchain.difficulty);
    println!("Mining reward: {} satoshi", blockchain.mining_reward);
    println!("Pending transactions: {}", blockchain.pending_transactions.len());

    // UTXO statistics
    let total_utxos = blockchain.utxo_set.utxos
        .values()
        .map(|v| v.len())
        .sum::<usize>();
    println!("Total UTXOs: {}", total_utxos);

    // Latest block info
    if let Some(latest) = blockchain.chain.last() {
        println!("\nLatest block:");
        println!("  Hash: {}", &latest.hash[..16]);
        println!("  Transactions: {}", latest.transactions.len());
        println!("  Merkle root: {}", &latest.merkle_root[..16]);
    }
}
}

Configuration Recommendations

Mining Difficulty

#![allow(unused)]
fn main() {
// Demo environment
blockchain.difficulty = 3;  // Fast (milliseconds)

// Test environment
blockchain.difficulty = 4;  // Moderate (seconds)

// Production environment
blockchain.difficulty = 6;  // Secure (minutes)
}

Block Reward

#![allow(unused)]
fn main() {
// Bitcoin-style (gradual halving)
let halving_interval = 210000;
let halvings = blockchain.chain.len() / halving_interval;
blockchain.mining_reward = 50 >> halvings;  // 50, 25, 12.5, ...
}

Performance Optimization

1. UTXO Indexing

Use indexer to speed up queries:

#![allow(unused)]
fn main() {
// Find all transactions for an address
let txs = blockchain.indexer.get_transactions_by_address(&address);

// Find a specific transaction
let tx = blockchain.indexer.get_transaction(&txid);
}

2. Batch Operations

#![allow(unused)]
fn main() {
// Batch add transactions
for tx in transactions {
    blockchain.add_transaction(tx)?;
}
// Mine all at once
blockchain.mine_pending_transactions(miner.address)?;
}

3. Parallel Validation

#![allow(unused)]
fn main() {
use rayon::prelude::*;

// Parallel validation of all transactions (requires rayon dependency)
let all_valid = blockchain.pending_transactions
    .par_iter()
    .all(|tx| tx.verify());
}

References


Back to API Index

Wallet API

The wallet module is responsible for managing key pairs, addresses, and signatures.

Data Structures

Wallet

#![allow(unused)]
fn main() {
pub struct Wallet {
    pub address: String,        // Wallet address (public key hash)
    pub private_key: String,    // Private key
    pub public_key: String,     // Public key
}
}

Methods

Creating a Wallet

new

#![allow(unused)]
fn main() {
pub fn new() -> Self
}

Creates a new wallet, automatically generating a key pair and address.

Key generation flow:

  1. Generate a random private key (64-character hexadecimal)
  2. Derive the public key from the private key (SHA256)
  3. Derive the address from the public key hash (first 40 characters)

Return value: New wallet instance

Security notes:

  • ⚠️ The private key must be kept secret
  • ⚠️ A lost private key cannot be recovered
  • ⚠️ Back up to a secure location

Example:

#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;

// Create a new wallet
let wallet = Wallet::new();

println!("Address: {}", wallet.address);
println!("Public key: {}", wallet.public_key);
// Never print or share the private key!

// Multiple wallets
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
}

Creates a wallet from a known address (for demonstration only).

Note:

  • This generates a new random key pair
  • The keys do not correspond to the address
  • For testing and demo purposes only

Parameters:

  • address - The specified address string

Return value: Wallet instance (keys are newly generated)

Example:

#![allow(unused)]
fn main() {
// Used for demonstrating the genesis address
let genesis = Wallet::from_address("genesis_address".to_string());

// In real applications, restore from private key
// let wallet = Wallet::from_private_key(private_key);
}

Signing Operations

sign

#![allow(unused)]
fn main() {
pub fn sign(&self, data: &str) -> String
}

Signs data with the private key.

Signing process (simplified):

signature = SHA256(private_key + data)

Real Bitcoin uses ECDSA:

1. Double SHA256 the data
2. Generate signature using private key and secp256k1 curve
3. Signature contains two parts: r and s

Parameters:

  • data - Data to sign (usually transaction data)

Return value: Signature string (64-character hexadecimal)

Use cases:

  • Proving ownership of the private key
  • Authorizing transactions
  • Preventing transaction tampering

Example:

#![allow(unused)]
fn main() {
let wallet = Wallet::new();

// Sign transaction data
let tx_data = "send 100 BTC to Bob";
let signature = wallet.sign(tx_data);

println!("Signature: {}", signature);

// Use in a transaction
let input = TxInput::new(
    prev_txid,
    vout,
    wallet.sign(&tx_data),  // signature
    wallet.public_key.clone(),
);
}

verify_signature (static method)

#![allow(unused)]
fn main() {
pub fn verify_signature(
    public_key: &str,
    data: &str,
    signature: &str
) -> bool
}

Verifies whether a signature is valid (simplified version).

Verification process (simplified):

  • Checks that the public key and signature are non-empty

Real Bitcoin uses ECDSA verification:

  1. Recover the public key from the signature
  2. Verify the public key matches
  3. Verify the mathematical correctness of the signature

Parameters:

  • public_key - Signer’s public key
  • data - Original data
  • signature - Signature

Return value:

  • true - Signature is valid
  • false - Signature is invalid

Example:

#![allow(unused)]
fn main() {
let wallet = Wallet::new();
let data = "transaction data";
let signature = wallet.sign(data);

// Verify signature
if Wallet::verify_signature(&wallet.public_key, data, &signature) {
    println!("✓ Signature valid");
} else {
    println!("✗ Signature invalid");
}

// Used in transaction validation
for input in transaction.inputs {
    if !Wallet::verify_signature(&input.pub_key, &tx_data, &input.signature) {
        return Err("Signature verification failed");
    }
}
}

Address Formats

SimpleBTC Address

Format: 40-character hexadecimal string
Example: a3f2d8c9e4b7f1a89c2d5e8f3b6a1c4e7d9b2a5c

Real Bitcoin Addresses

P2PKH (starts with 1)

1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa

Generation process:

Public key → SHA256 → RIPEMD160 → Add version → Checksum → Base58 encoding

P2SH (starts with 3)

3J98t1WpEZ73CNmYviecrnyiWrnqRhWNLy

Use case: Multisig, script addresses

Bech32 (starts with bc1)

bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4

Advantage: SegWit address, lower fees


Key Management

Private Key Security

Best practices:

#![allow(unused)]
fn main() {
// ✅ Good practice
let wallet = Wallet::new();

// Encrypt and store the private key
let encrypted = encrypt_private_key(&wallet.private_key, password);
save_to_secure_storage(&encrypted);

// Clear memory immediately after use
drop(wallet);

// Back up to multiple locations
backup_to_hardware_wallet(&wallet.private_key);
backup_to_paper(&wallet.private_key);
backup_to_encrypted_usb(&wallet.private_key);
}
#![allow(unused)]
fn main() {
// ❌ Bad practice
println!("Private key: {}", wallet.private_key);  // Never print
save_to_file(&wallet.private_key);        // Plaintext storage
send_via_email(&wallet.private_key);      // Network transmission
}

Key Recovery

#![allow(unused)]
fn main() {
// Recover wallet from private key (needs implementation)
fn recover_wallet(private_key: &str) -> Wallet {
    // 1. Validate private key format
    // 2. Derive public key from private key
    // 3. Generate address from public key
    // 4. Return wallet instance
}

// Using mnemonic phrase (BIP39 standard, needs implementation)
fn from_mnemonic(words: &str) -> Wallet {
    // Mnemonic → seed → master private key → derived keys
}
}

Use Cases

Case 1: Basic Transfer

#![allow(unused)]
fn main() {
use bitcoin_simulation::{blockchain::Blockchain, wallet::Wallet};

fn basic_transfer() -> Result<(), String> {
    let mut blockchain = Blockchain::new();

    // Create participants
    let alice = Wallet::new();
    let bob = Wallet::new();

    // Alice receives initial funds
    setup_balance(&mut blockchain, &alice, 10000)?;

    // Alice transfers to 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())?;

    // Check balances
    println!("Alice: {}", blockchain.get_balance(&alice.address));
    println!("Bob: {}", blockchain.get_balance(&bob.address));

    Ok(())
}
}

Case 2: Batch Wallet Creation

#![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!("Wallet #{}: {}", i, &wallet.address[..16]);
        wallets.push(wallet);
    }

    wallets
}

// Usage
let users = create_wallet_pool(100);  // Create 100 wallets
}

Case 3: Wallet Import/Export

#![allow(unused)]
fn main() {
use serde_json;

// Export wallet (encrypted)
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)
}

// Import wallet
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)
}

// Usage
let wallet = Wallet::new();
let backup = export_wallet(&wallet, "strong_password")?;
save_to_file("wallet_backup.enc", &backup)?;

// Restore
let backup_data = read_from_file("wallet_backup.enc")?;
let recovered = import_wallet(&backup_data, "strong_password")?;
}

Case 4: Multisig Wallet Integration

#![allow(unused)]
fn main() {
use bitcoin_simulation::multisig::MultiSigAddress;

fn create_multisig_wallet() -> Result<MultiSigAddress, String> {
    // Create participant wallets
    let alice = Wallet::new();
    let bob = Wallet::new();
    let charlie = Wallet::new();

    // Collect public keys
    let public_keys = vec![
        alice.public_key,
        bob.public_key,
        charlie.public_key,
    ];

    // Create 2-of-3 multisig address
    let multisig = MultiSigAddress::new(2, public_keys)?;

    println!("Multisig address: {}", multisig.address);

    Ok(multisig)
}
}

Differences from Real Bitcoin

FeatureSimpleBTCReal Bitcoin
Private key generationRandom string256-bit random number
Public key derivationSHA256secp256k1 elliptic curve
Address format40-character hexBase58/Bech32 encoding
Signing algorithmSHA256ECDSA
Signature verificationSimplified checkFull mathematical verification

Real Bitcoin flow:

Private key (256 bits)
  ↓ secp256k1
Public key (33/65 bytes)
  ↓ SHA256 + RIPEMD160
Public key hash (20 bytes)
  ↓ Version + checksum + Base58
Address (25-34 chars)

Security Recommendations

1. Private Key Protection

#![allow(unused)]
fn main() {
// Use OS keyring
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. Multiple Backups

  • ✅ Paper wallet (fireproof and waterproof)
  • ✅ Hardware wallet (Ledger, Trezor)
  • ✅ Encrypted USB drive (offsite storage)
  • ✅ Secret sharing (Shamir’s Secret Sharing)

3. Regular Audits

#![allow(unused)]
fn main() {
fn audit_wallets(wallets: &[Wallet]) {
    for (i, wallet) in wallets.iter().enumerate() {
        println!("Wallet #{}", i);
        println!("  Address: {}", wallet.address);
        println!("  Public key present: {}", !wallet.public_key.is_empty());
        println!("  Private key present: {}", !wallet.private_key.is_empty());

        // Test signing
        let test_sig = wallet.sign("test");
        assert!(Wallet::verify_signature(
            &wallet.public_key,
            "test",
            &test_sig
        ));
    }
}
}

Frequently Asked Questions

Q: How do I recover a lost wallet?

A: You can only recover from a backed-up private key. If the private key is lost, the bitcoin is permanently lost.

Q: Can I derive a private key from an address?

A: No. Addresses are one-way hashes, computationally irreversible.

Q: Can one private key generate multiple addresses?

A: Hierarchical Deterministic Wallets (HD Wallet, BIP32) can derive multiple key pairs from a single seed.

Q: How do I know if a wallet has been compromised?

A: Monitor transactions on the blockchain. If unauthorized transactions appear, the private key has been leaked.


References


Back to API Index

UTXO API

The UTXO module manages all unspent transaction outputs and is the core of Bitcoin’s accounting model.

Data Structures

UTXOSet

#![allow(unused)]
fn main() {
pub struct UTXOSet {
    // key: txid (transaction ID)
    // value: Vec<(vout_index, TxOutput)>
    utxos: HashMap<String, Vec<(usize, TxOutput)>>,
}
}

Internal structure:

HashMap {
    "tx1": [(0, Output{value: 100, addr: "alice"}),
            (1, Output{value: 50, addr: "bob"})],
    "tx2": [(0, Output{value: 200, addr: "charlie"})],
}

Core Concepts

UTXO Model vs. Account Model

Account Model (Ethereum)

Account balances:
  Alice: 100 BTC
  Bob: 50 BTC

Transfer: Alice → Bob (30 BTC)
  Alice: 70 BTC  (-30)
  Bob: 80 BTC    (+30)

UTXO Model (Bitcoin)

UTXO set:
  tx1:0 → 100 BTC (Alice)
  tx1:1 → 50 BTC (Bob)

Transfer: Alice → Bob (30 BTC)
  Spend: tx1:0 (100 BTC)
  Create:
    tx2:0 → 30 BTC (Bob)
    tx2:1 → 70 BTC (Alice, change)

New UTXO set:
  tx1:1 → 50 BTC (Bob)
  tx2:0 → 30 BTC (Bob)
  tx2:1 → 70 BTC (Alice)

Advantages of the UTXO Model

  1. Better privacy

    • Can use a new address each time
    • Harder to trace fund flows
  2. Parallel processing

    • Different UTXOs can be validated concurrently
    • No account locking issues
  3. Simplified validation

    • Only need to check UTXO existence
    • No need for account history

Methods

Initialization

new

#![allow(unused)]
fn main() {
pub fn new() -> Self
}

Creates an empty UTXO set.

Example:

#![allow(unused)]
fn main() {
use bitcoin_simulation::utxo::UTXOSet;

let mut utxo_set = UTXOSet::new();
}

UTXO Management

add_transaction

#![allow(unused)]
fn main() {
pub fn add_transaction(&mut self, tx: &Transaction)
}

Adds all outputs of a transaction to the UTXO set.

Process:

  1. Iterate over all outputs of the transaction
  2. Mark each output as unspent
  3. Add to the UTXO set

Parameters:

  • tx - The transaction to add

Note: Only adds outputs, does not process inputs

Example:

#![allow(unused)]
fn main() {
let tx = Transaction::new(...);
utxo_set.add_transaction(&tx);

// Now all of tx's outputs can be spent
}

remove_utxo

#![allow(unused)]
fn main() {
pub fn remove_utxo(&mut self, txid: &str, vout: usize)
}

Removes a spent UTXO.

Double-spend protection:

  • A UTXO can only be spent once
  • Removed from the set immediately after spending
  • A second reference will fail

Parameters:

  • txid - Transaction ID
  • vout - Output index

Example:

#![allow(unused)]
fn main() {
// Spend a UTXO
utxo_set.remove_utxo("tx1", 0);

// Attempt to spend again (fails)
// UTXO does not exist
}

process_transaction

#![allow(unused)]
fn main() {
pub fn process_transaction(&mut self, tx: &Transaction) -> bool
}

Fully processes a transaction (removes inputs, adds outputs).

ACID properties:

Atomicity:

#![allow(unused)]
fn main() {
// Either fully succeeds or fully fails
if !tx.verify() {
    return false;  // No modifications made
}
// All processed
}

Consistency:

#![allow(unused)]
fn main() {
// Before and after processing: input_sum = output_sum + fee
assert_eq!(input_sum, output_sum + fee);
}

Steps:

  1. Validate the transaction
  2. Remove UTXOs referenced by inputs
  3. Add newly created outputs

Parameters:

  • tx - The transaction to process

Return value:

  • true - Processing successful
  • false - Transaction is invalid

Example:

#![allow(unused)]
fn main() {
let tx = blockchain.create_transaction(&alice, bob.address, 1000, 10)?;

if utxo_set.process_transaction(&tx) {
    println!("✓ UTXO updated successfully");
} else {
    println!("✗ Invalid transaction");
}
}

Query Operations

find_utxos

#![allow(unused)]
fn main() {
pub fn find_utxos(&self, address: &str) -> Vec<(String, usize, u64)>
}

Finds all UTXOs for an address.

Return format: Vec<(txid, vout, value)>

Parameters:

  • address - The address to query

Return value: List of UTXOs

Example:

#![allow(unused)]
fn main() {
let utxos = utxo_set.find_utxos(&alice.address);

println!("Alice's UTXOs:");
for (txid, vout, value) in utxos {
    println!("  {}:{} → {} sat", &txid[..8], vout, value);
}

// Output:
// Alice's UTXOs:
//   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)>)>
}

Finds a combination of UTXOs usable for payment.

UTXO selection strategies:

  1. Greedy algorithm (current implementation):

    #![allow(unused)]
    fn main() {
    accumulated = 0
    for utxo in utxos:
        accumulated += utxo.value
        if accumulated >= amount:
            return utxos
    }
  2. Optimal match (possible optimization):

    • Select the combination closest in total to the target
    • Reduces change, saving on fees
  3. Smallest UTXO first:

    • Prefer small UTXOs
    • Avoids UTXO fragmentation

Parameters:

  • address - Sender address
  • amount - Required amount (including fee)

Return value:

  • Some((accumulated, utxo_list)) - Sufficient UTXOs found
    • accumulated: Accumulated amount
    • utxo_list: Selected UTXO list
  • None - Insufficient balance

Example:

#![allow(unused)]
fn main() {
// Need 1000 sat (including fee)
let result = utxo_set.find_spendable_outputs(&alice.address, 1000);

match result {
    Some((accumulated, utxos)) => {
        println!("✓ Found sufficient UTXOs");
        println!("  Accumulated amount: {} sat", accumulated);
        println!("  UTXOs used: {}", utxos.len());
        println!("  Change: {} sat", accumulated - 1000);
    }
    None => {
        println!("✗ Insufficient balance");
    }
}
}

get_balance

#![allow(unused)]
fn main() {
pub fn get_balance(&self, address: &str) -> u64
}

Calculates the total balance for an address.

Computation method:

#![allow(unused)]
fn main() {
balance = sum(all_utxos.value)
}

Parameters:

  • address - The address to query

Return value: Balance (satoshi)

Example:

#![allow(unused)]
fn main() {
let balance = utxo_set.get_balance(&alice.address);
println!("Balance: {} satoshi", balance);
println!("Balance: {:.8} BTC", balance as f64 / 100_000_000.0);

// Batch query
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);
}
}

Use Cases

Case 1: Selecting UTXOs When Creating a Transaction

#![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. Find available UTXOs
    let result = utxo_set.find_spendable_outputs(&from.address, total_needed);

    let (accumulated, utxo_refs) = result.ok_or("Insufficient balance")?;

    // 2. Build inputs
    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. Build outputs
    let mut outputs = vec![
        TxOutput::new(amount, to.to_string()),  // To recipient
    ];

    // 4. Change
    if accumulated > total_needed {
        outputs.push(TxOutput::new(
            accumulated - total_needed,
            from.address.clone()
        ));
    }

    // 5. Create transaction
    Ok(Transaction::new(inputs, outputs, current_timestamp(), fee))
}
}

Case 2: Detailed Balance Query

#![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!("=== Balance Details ===");
    println!("Address: {}", &address[..20]);
    println!("Total balance: {} sat ({:.8} BTC)", total, total as f64 / 1e8);
    println!("UTXO count: {}", utxos.len());
    println!("\nUTXO list:");

    for (i, (txid, vout, value)) in utxos.iter().enumerate() {
        println!("  #{}: {}:{} → {} sat",
            i + 1, &txid[..8], vout, value);
    }

    // Statistics
    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!("\nStatistics:");
        println!("  Average: {} sat", avg);
        println!("  Maximum: {} sat", max);
        println!("  Minimum: {} sat", min);
    }
}
}

Case 3: UTXO Consolidation

#![allow(unused)]
fn main() {
fn consolidate_utxos(
    blockchain: &mut Blockchain,
    wallet: &Wallet
) -> Result<(), String> {
    let utxos = blockchain.utxo_set.find_utxos(&wallet.address);

    // If too many UTXOs (>50), consolidate into 1
    if utxos.len() > 50 {
        println!("Starting UTXO consolidation...");
        println!("  Current UTXO count: {}", utxos.len());

        // Create a self-to-self transaction consolidating all UTXOs
        let total = blockchain.get_balance(&wallet.address);
        let fee = 100;  // Fixed fee

        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!("✓ Consolidation complete");
        println!("  New UTXO count: {}", new_utxos.len());
    }

    Ok(())
}
}

Case 4: UTXO Audit

#![allow(unused)]
fn main() {
fn audit_utxo_set(utxo_set: &UTXOSet, blockchain: &Blockchain) -> bool {
    println!("=== UTXO Audit ===");

    // 1. Count total UTXOs
    let total_utxos: usize = utxo_set.utxos.values()
        .map(|v| v.len())
        .sum();
    println!("Total UTXO count: {}", total_utxos);

    // 2. Sum total value
    let mut total_value = 0u64;
    for outputs in utxo_set.utxos.values() {
        for (_, output) in outputs {
            total_value += output.value;
        }
    }
    println!("Total value: {} sat", total_value);

    // 3. Validate each UTXO
    let mut valid = true;
    for (txid, outputs) in &utxo_set.utxos {
        // Verify the transaction exists on the blockchain
        let tx_exists = blockchain.chain.iter()
            .any(|block| block.transactions.iter()
                .any(|tx| &tx.id == txid));

        if !tx_exists {
            println!("✗ Warning: UTXO references non-existent transaction {}", txid);
            valid = false;
        }
    }

    if valid {
        println!("✓ UTXO set is complete");
    }

    valid
}
}

Performance Optimization

1. Index Optimization

#![allow(unused)]
fn main() {
// Create an index for addresses
pub struct IndexedUTXOSet {
    utxos: HashMap<String, Vec<(usize, TxOutput)>>,
    // New: address index
    address_index: HashMap<String, Vec<(String, usize)>>,
}

impl IndexedUTXOSet {
    pub fn find_utxos(&self, address: &str) -> Vec<(String, usize, u64)> {
        // O(1) lookup instead of 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. Batch Operations

#![allow(unused)]
fn main() {
// Process transactions in batch
pub fn process_transactions(&mut self, txs: &[Transaction]) -> bool {
    // 1. Validate all transactions
    for tx in txs {
        if !tx.verify() {
            return false;
        }
    }

    // 2. Batch update UTXOs
    for tx in txs {
        // Remove inputs
        if !tx.is_coinbase() {
            for input in &tx.inputs {
                self.remove_utxo(&input.txid, input.vout);
            }
        }

        // Add outputs
        self.add_transaction(tx);
    }

    true
}
}

3. Balance Caching

#![allow(unused)]
fn main() {
pub struct CachedUTXOSet {
    utxos: HashMap<String, Vec<(usize, TxOutput)>>,
    balance_cache: HashMap<String, u64>,  // Balance cache
}

impl CachedUTXOSet {
    pub fn get_balance(&mut self, address: &str) -> u64 {
        // Check cache
        if let Some(balance) = self.balance_cache.get(address) {
            return *balance;
        }

        // Compute and cache
        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);
    }
}
}

Comparison with Ethereum Account Model

FeatureUTXO Model (Bitcoin)Account Model (Ethereum)
StateStateless (only UTXO set)Stateful (account balances, nonce)
BalanceComputed value (UTXO sum)Stored value (directly stored)
TransferSpend UTXOs, create new UTXOsAccount balance increases/decreases
PrivacyBetter (can use new address)Worse (reuse of addresses)
ParallelismEasy to validate in parallelRequires sequential processing (nonce)
ComplexityComplex transaction constructionSimple transactions
Smart contractsLimited (Script)Flexible (EVM)

Frequently Asked Questions

Q: Why use the UTXO model?

A:

  • ✅ Better privacy (new address each time)
  • ✅ Parallel validation (different UTXOs are independent)
  • ✅ Simplified validation logic
  • ✅ Natural double-spend prevention

Q: Will UTXOs keep accumulating?

A: Yes. Solutions:

  • UTXO consolidation (merge multiple small UTXOs)
  • Higher transaction fees (limit junk UTXOs)
  • UTXO commitments (reduce storage)

Q: How do I prevent UTXO fragmentation?

A:

#![allow(unused)]
fn main() {
// Consolidate periodically
if utxos.len() > threshold {
    consolidate_utxos();
}

// Prioritize small UTXOs
utxos.sort_by_key(|u| u.value);  // Smallest first
}

Q: What if UTXOs are lost?

A: As long as you have the private key, you can rebuild the UTXO set from the blockchain:

#![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
}
}

References


Back to API Index

Advanced Modules

SimpleBTC’s advanced modules build a complete set of Bitcoin protocol features on top of the core blockchain functionality. These modules work together to cover the full stack — from data integrity verification to complex multi-party signatures, and from lightweight payment verification to script language execution.


Module Overview

ModuleSource FileCore Functionality
Merkle Treesrc/merkle.rsData integrity verification, SPV proof generation and verification
Multisigsrc/multisig.rsM-of-N multi-party signature addresses and transaction construction
Advanced Transactionssrc/advanced_tx.rsRBF replacement mechanism, timelocks, fee estimation
Mempoolsrc/mempool.rsUnconfirmed transaction management and priority ordering
Script Enginesrc/script.rsBitcoin Script subset interpretation and execution
SPVsrc/spv.rsLightweight payment verification client

Merkle Tree

Source file: src/merkle.rs | Documentation: Merkle API

The Merkle tree is the foundation of blockchain data integrity. SimpleBTC uses SHA256 to build a binary hash tree, aggregating all transaction hashes in a block into a single 32-byte merkle_root stored in the block header.

The core value lies in supporting SPV (Simplified Payment Verification): a light wallet does not need to download the full block (1–2 MB); it only needs to obtain the block header (80 bytes) and an O(log n) hash path to cryptographically prove that a transaction has been confirmed.

#![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();

// Generate and verify an SPV proof
let proof = tree.get_proof("tx1_hash").unwrap();
let valid = MerkleTree::verify_proof("tx1_hash", &proof, &root, 0);
}

The Merkle tree is called internally by Block::new() to compute merkle_root, and is also used by Block::verify_transaction_inclusion() for SPV verification.


Multisig

Source file: src/multisig.rs | Documentation: MultiSig API

Multisig implements Bitcoin’s M-of-N signature scheme: N participants each hold an ECDSA key pair, and at least M of them must sign to authorize fund movement. This is the core mechanism in the Bitcoin protocol for implementing distributed control and risk distribution.

Typical use cases include: 2-of-3 corporate fund management (preventing single-person embezzlement), 2-of-3 third-party escrow (buyer-seller-arbitrator), and personal multi-device backup (recovery is still possible even if the primary key is lost).

#![allow(unused)]
fn main() {
use simplebtc::multisig::{MultiSigAddress, MultiSigTxBuilder};
use simplebtc::wallet::Wallet;

// Create a 2-of-3 multisig address
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();

// Collect signatures (any two participants can sign)
let mut builder = MultiSigTxBuilder::new(ms_addr);
builder.add_signature(&w1, "transaction data").unwrap();
builder.add_signature(&w2, "transaction data").unwrap();
assert!(builder.is_complete());
}

Multisig addresses start with "3" (corresponding to Bitcoin’s P2SH address format), generated via script hash, and support up to 15 participating keys.


Advanced Transactions

Source file: src/advanced_tx.rs | Documentation: Advanced TX API

The advanced transaction module provides three key features that address real engineering problems in the Bitcoin network:

RBF (Replace-By-Fee): Allows users to replace an unconfirmed transaction with a new one bearing a higher fee, thereby accelerating confirmation or canceling an erroneous transaction. RBFManager maintains a list of replaceable transactions and enforces replacement validation rules (same inputs, higher fee, increment meets minimum requirement).

TimeLock: Restricts a transaction from being mined before a specified time or block height. Supports two types: Unix timestamp-based (new_time_based) and block height-based (new_height_based). Commonly used for time deposits, inheritance, smart contracts, and similar scenarios.

TxPriorityCalculator: Recommends a reasonable fee based on transaction size and urgency (Low/Medium/High/Urgent), and calculates a composite priority score (70% fee rate weight + 30% priority weight).

#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{AdvancedTxBuilder, TimeLock, RBFManager, TxPriorityCalculator, FeeUrgency};

// Combined RBF + timelock usage
let timelock = TimeLock::new_height_based(850_000);
let builder = AdvancedTxBuilder::new()
    .with_rbf()
    .with_timelock(timelock);

// Recommended fee
let fee = TxPriorityCalculator::recommend_fee(250, FeeUrgency::High); // 250-byte transaction
println!("Recommended fee: {} satoshi", fee); // ~5000 satoshi
}

Mempool

Source file: src/mempool.rs

The mempool (Memory Pool) stores all transactions that have been broadcast but not yet packed into a block. Miners select transactions from the mempool by priority to build new blocks.

Main responsibilities:

  • Receive and temporarily store broadcast transactions
  • Sort by fee rate, offering high-value transactions first to miners
  • Detect and reject double-spend attempts
  • Handle transaction replacement in conjunction with the RBF mechanism
  • Remove packed transactions from the pool after block confirmation
#![allow(unused)]
fn main() {
use simplebtc::mempool::Mempool;

let mut pool = Mempool::new();
pool.add_transaction(tx);
let pending = pool.get_pending_transactions(10); // Get the 10 highest-fee transactions
}

Script Engine

Source file: src/script.rs

Bitcoin Script is a simple stack-based scripting language used to define the spending conditions for transactions. SimpleBTC implements the core subset of Script, supporting the most common transaction types.

Supported script types:

  • P2PKH (Pay-to-Public-Key-Hash): The most common ordinary address transaction; lock script format is OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG.
  • P2SH (Pay-to-Script-Hash): The basis for multisig and complex contracts; addresses starting with "3".
  • OP_RETURN: Writes arbitrary non-spendable data on-chain (up to 80 bytes).

The script engine provides the underlying support for the multisig module: the script field of MultiSigAddress stores a simplified Script locking script.


SPV (Simplified Payment Verification)

Source file: src/spv.rs

SPV simulates the operation of Bitcoin light wallets: validating the legitimacy of transactions without downloading the full blockchain. This is critical for resource-constrained devices (mobile phones, embedded systems).

SPV verification flow:

  1. Download only block headers (each approximately 80 bytes, all headers approximately 60 MB)
  2. Verify the proof of work (PoW) of the block headers
  3. Request the Merkle proof for the target transaction (a few hundred bytes)
  4. Execute MerkleTree::verify_proof() locally
Full node mode: Download full blockchain (~500 GB) → Full local verification
SPV mode:       Download block headers (~60 MB) + Merkle proof (few KB) → O(log n) verification

The SPV module is deeply integrated with the Merkle module, relying on MerkleTree::get_proof() and MerkleTree::verify_proof() for lightweight verification.


Module Dependencies

Core Modules
├── Transaction (src/transaction.rs)
├── Wallet      (src/wallet.rs)
└── Block       (src/block.rs)
        │
        ▼
Advanced Modules (built on top of core modules)
├── Merkle      ← Used internally by Block (computing merkle_root and SPV proofs)
├── MultiSig    ← Depends on Wallet (ECDSA signing) + Script (locking scripts)
├── AdvancedTx  ← Depends on Transaction (RBF replacement validation)
├── Mempool     ← Depends on Transaction + AdvancedTx (RBF support)
├── Script      ← Foundation for MultiSig and SPV
└── SPV         ← Depends on Merkle (proof verification) + Block (block headers)

Quick Navigation

Merkle API

The Merkle tree (hash tree) is implemented in src/merkle.rs and is the core data structure for blockchain data integrity verification. SimpleBTC uses SHA256 to build a binary Merkle tree, aggregating all transaction hashes in a block into a single root hash (merkle_root) stored in the block header.


Data Structures

MerkleNode Struct

A single node in the Merkle tree, which can be a leaf node (corresponding to one transaction) or an internal node (corresponding to the hash of its child node hashes).

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct MerkleNode {
    pub hash: String,                   // SHA256 hash of the node (64-character hex)
    pub left: Option<Box<MerkleNode>>,  // Left child node (None for leaf nodes)
    pub right: Option<Box<MerkleNode>>, // Right child node (None for leaf nodes)
}
}
FieldTypeDescription
hashStringNode hash. For leaf nodes: SHA256(transaction ID); for internal nodes: SHA256(left_hash + right_hash).
leftOption<Box<MerkleNode>>Left child node. None for leaf nodes.
rightOption<Box<MerkleNode>>Right child node. None for leaf nodes.

MerkleNode Methods

#![allow(unused)]
fn main() {
// Create a leaf node from raw data (computes SHA256 hash)
pub fn new_leaf(data: &str) -> Self

// Create an internal node from two child nodes (hash = SHA256(left_hash + right_hash))
pub fn new_internal(left: MerkleNode, right: MerkleNode) -> Self
}

MerkleTree Struct

The complete Merkle tree, holding the root node and the original leaf data list.

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct MerkleTree {
    pub root: Option<MerkleNode>, // Root node (None when transaction list is empty)
    pub leaves: Vec<String>,      // Original leaf data list (transaction ID list)
}
}
FieldTypeDescription
rootOption<MerkleNode>The root node of the tree. None when input is empty.
leavesVec<String>The original transaction ID list passed in during construction (unhashed).

Tree Structure Illustration

Example with 4 transactions:

              Root
             /    \
           H12    H34
          /  \   /  \
        H1  H2  H3  H4
        │    │   │   │
       tx1  tx2 tx3 tx4

Where:
  H1  = SHA256(tx1)
  H2  = SHA256(tx2)
  H12 = SHA256(H1 + H2)
  H34 = SHA256(H3 + H4)
  Root = SHA256(H12 + H34)

Handling odd number of transactions: If a layer has an odd number of nodes, the last node is duplicated to form a pair (e.g., with 3 transactions, tx3 is duplicated as tx3’).


Methods

MerkleTree::new

Builds a complete Merkle tree from a list of transaction IDs. Uses a bottom-up approach to build layer by layer; time complexity O(n).

#![allow(unused)]
fn main() {
pub fn new(transactions: &[String]) -> Self
}

Parameters:

  • transactions — A slice of transaction IDs (or arbitrary strings). Can be empty, in which case root is None.

Return value: A fully constructed MerkleTree instance.

#![allow(unused)]
fn main() {
use simplebtc::merkle::MerkleTree;

// Build tree from transaction ID list
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);

// Handle empty transaction list
let empty_tree = MerkleTree::new(&[]);
assert!(empty_tree.root.is_none());
}

MerkleTree::get_root_hash

Gets the root hash string of the Merkle tree. This value is stored in the merkle_root field of the block header.

#![allow(unused)]
fn main() {
pub fn get_root_hash(&self) -> String
}

Return value:

  • 64-character lowercase hexadecimal SHA256 hash string (when tree is non-empty).
  • Empty string "" (when tree is empty, i.e., root is None).
#![allow(unused)]
fn main() {
let tree = MerkleTree::new(&tx_ids);
let root_hash = tree.get_root_hash();
println!("Merkle root: {}", root_hash);
// Output: a3f7c2e1b4d9...(64-character hex)

// Compare with the value stored in the block
assert_eq!(root_hash, block.merkle_root);
}

MerkleTree::get_proof

Generates a Merkle proof (SPV proof) for a specified transaction. The proof is a set of sibling node hashes; an SPV client uses these hashes to reconstruct the root hash layer by layer from the leaf, without accessing the full block.

#![allow(unused)]
fn main() {
pub fn get_proof(&self, tx_hash: &str) -> Option<Vec<String>>
}

Parameters:

  • tx_hash — The transaction ID to generate a proof for (must exist in self.leaves).

Return value:

  • Some(Vec<String>) — List of sibling node hashes needed for the proof, ordered from the leaf layer to the root layer.
  • None — Transaction ID does not exist in this Merkle tree.

Proof size: For a block with n transactions, the proof contains ceil(log2(n)) hashes, each 32 bytes. For example, a block with 2000 transactions requires only approximately 352 bytes of proof (11 hashes).

#![allow(unused)]
fn main() {
let tree = MerkleTree::new(&tx_ids);

match tree.get_proof("tx_hash_1") {
    Some(proof) => {
        println!("Proof contains {} sibling hashes", proof.len());
        for (i, hash) in proof.iter().enumerate() {
            println!("  Layer {}: {}", i, &hash[..16]);
        }
    }
    None => println!("Transaction does not exist in this Merkle tree"),
}
}

MerkleTree::verify_proof

Verifies a Merkle proof (static method). This is the core function of SPV lightweight verification: using the sibling hashes in the proof, it computes upward layer by layer from the leaf node, verifying whether the final result matches the merkle_root in the block header.

#![allow(unused)]
fn main() {
pub fn verify_proof(
    tx_hash: &str,
    proof: &[String],
    root_hash: &str,
    index: usize,
) -> bool
}

Parameters:

  • tx_hash — The transaction ID string to verify (raw value, not hashed).
  • proof — List of sibling node hashes generated by get_proof().
  • root_hash — The merkle_root value stored in the block header.
  • index — The position index of the transaction in the block’s transaction list (starting from 0), used to determine left/right merge order.

Return value:

  • true — Proof is valid; the transaction is indeed included in the corresponding block.
  • false — Proof is invalid; the transaction is not in this block, or data has been tampered with.

Verification algorithm:

Input: tx_hash, proof = [sibling_0, sibling_1, ...], root_hash, index

Steps:
  current = SHA256(tx_hash)
  For each sibling_hash in proof:
    If index is even (current node is on the left):
      current = SHA256(current + sibling_hash)
    If index is odd (current node is on the right):
      current = SHA256(sibling_hash + current)
    index = index / 2

Final: current == root_hash → verification passes
#![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();

// Generate proof
let proof = tree.get_proof("tx1").expect("Transaction exists");

// Verify proof (index=0, tx1 is the first transaction)
let is_valid = MerkleTree::verify_proof("tx1", &proof, &root, 0);
assert!(is_valid, "SPV proof verification failed");
println!("Transaction tx1 confirmed included in block");

// Tamper test: proof becomes invalid after modifying transaction content
let tampered = MerkleTree::verify_proof("tx1_TAMPERED", &proof, &root, 0);
assert!(!tampered, "Proof should be invalid after tampering");
}

Complete Usage Examples

Example 1: Integration with a Block

use simplebtc::block::Block;
use simplebtc::merkle::MerkleTree;
use simplebtc::transaction::Transaction;
use simplebtc::wallet::Wallet;

fn main() {
    // Simulate packing 4 transactions
    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 internally builds a MerkleTree and computes merkle_root
    let block = Block::new(1, transactions, "000000abc...".to_string());
    println!("Merkle root: {}", block.merkle_root);

    // SPV verification: is tx[2] in this block?
    let tx_id = block.transactions[2].id.clone();
    let included = block.verify_transaction_inclusion(&tx_id, 2);
    println!("Transaction included in block: {}", included);
}

Example 2: Using MerkleTree Standalone

#![allow(unused)]
fn main() {
use simplebtc::merkle::MerkleTree;

fn spv_demo() {
    // Full node builds complete Merkle tree
    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!("Merkle root for 8 transactions: {}", root);

    // Generate SPV proof for tx #5 (index=4)
    let target_tx = "transaction_0005";
    let proof = tree.get_proof(target_tx).expect("Transaction exists");
    println!("Proof size: {} hashes (log2(8)=3 layers)", proof.len());

    // SPV client verification (only needs root + proof, not full transaction list)
    let verified = MerkleTree::verify_proof(target_tx, &proof, &root, 4);
    println!("SPV verification result: {}", verified);
}
}

Example 3: Detecting Data Tampering

#![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();

    // Simulate attacker modifying 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 roots are completely different; tampering is immediately detected
    assert_ne!(original_root, tampered_root);
    println!("Original root:  {}", &original_root[..16]);
    println!("Tampered root:  {}", &tampered_root[..16]);
    println!("Tamper detected: root hash has changed");
}
}

Security Notes

Why can Merkle proofs be trusted?

For an attacker to forge a valid Merkle proof, they would need to:

  1. Find a SHA256 hash collision (computational complexity approximately 2^128 — currently infeasible); or
  2. Re-mine the block (changing merkle_root changes the block hash, requiring redo of the proof of work).

Therefore, as long as an SPV client can obtain block headers protected by honest proof of work, the security of a Merkle proof is equivalent to that of a full node.

Block timestamp variance: Miners’ block timestamps are allowed to differ by approximately 2 hours, but this does not affect the security of Merkle verification (the Merkle tree does not depend on timestamps).


  • BlockBlock::new() internally calls MerkleTree::new() to compute merkle_root; Block::verify_transaction_inclusion() uses get_proof() and verify_proof().
  • Advanced Modules — The SPV module (src/spv.rs) is built on the Merkle API to implement a lightweight client.

MultiSig API

Multi-Signature (MultiSig) is implemented in src/multisig.rs and supports Bitcoin’s M-of-N signature scheme: out of N participants, at least M valid ECDSA signatures are required to authorize fund movement.


Core Concepts

M-of-N signatures: N participants each hold a key pair; any M of them can authorize a transaction. Common combinations:

TypeMeaningTypical Use Case
2-of-2Both must agreeJoint accounts, partnerships
2-of-3Any two of three agreeCorporate fund management, escrow services
3-of-5Any three of five agreeLarge institutions, board decisions

Address format: Multisig addresses start with "3", corresponding to Bitcoin’s P2SH (Pay-to-Script-Hash) format.


MultiSigAddress Struct

Represents an M-of-N multisig address and its configuration.

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiSigAddress {
    pub address: String,          // Multisig address ("3"-prefixed, P2SH format)
    pub required_sigs: usize,     // Required number of signatures M
    pub total_keys: usize,        // Total number of keys N
    pub public_keys: Vec<String>, // Public key list for all participants
    pub script: String,           // Locking script (simplified Script code)
}
}

Field Descriptions

FieldTypeDescription
addressStringMultisig address starting with "3", generated by taking 42 characters of the SHA256 hash of the locking script.
required_sigsusizeM value: minimum number of valid signatures required to spend funds.
total_keysusizeN value: total number of participants (equals public_keys.len()).
public_keysVec<String>Public key list of all participants (hex-encoded).
scriptStringLocking script in the format OP_{M}{pubkeys...}OP_CHECKMULTISIG.

MultiSigAddress Methods

MultiSigAddress::new

Creates an M-of-N multisig address. Validates parameter legality, then generates the locking script and the corresponding P2SH address.

#![allow(unused)]
fn main() {
pub fn new(
    required_sigs: usize,
    public_keys: Vec<String>,
) -> Result<Self, String>
}

Parameters:

  • required_sigs — Required number of signatures M; must satisfy 1 <= M <= N.
  • public_keys — List of all participants’ public keys; length equals N, maximum 15.

Return value:

  • Ok(MultiSigAddress) — Created successfully.
  • Err(String) — Invalid parameters; error reasons include:
    • "Invalid signature requirement"required_sigs == 0 or required_sigs > total_keys.
    • "Maximum 15 keys supported"public_keys.len() > 15 (Bitcoin protocol limit).
#![allow(unused)]
fn main() {
use simplebtc::multisig::MultiSigAddress;
use simplebtc::wallet::Wallet;

// Create three participant wallets
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(),
];

// Create 2-of-3 multisig address
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: {}", multisig.address);
println!("Locking script: {}", multisig.script);

// Parameter validation error examples
let result = MultiSigAddress::new(0, vec!["key1".to_string()]);
assert!(result.is_err()); // required_sigs cannot be 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

Quickly checks whether the number of signatures meets the requirement (does not verify signature content, only validates count).

#![allow(unused)]
fn main() {
pub fn verify_signatures(&self, signatures: &[String]) -> bool
}

Parameters:

  • signatures — List of signatures.

Return value: 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!("Signature count meets requirement: {}", count_ok); // true (2 >= 2)
}

MultiSigAddress::verify_signatures_with_data

Full signature verification: not only checks count, but also uses ECDSA to verify whether each signature was produced by one of the keys in public_keys.

#![allow(unused)]
fn main() {
pub fn verify_signatures_with_data(
    &self,
    signatures: &[String],
    data: &str,
) -> bool
}

Parameters:

  • signatures — List of ECDSA signatures in hex DER encoding.
  • data — The original data string that was signed (typically a transaction ID or transaction digest).

Return value:

  • true — Number of valid signatures >= required_sigs. Each signature matches at most one public key (preventing the same signature from being counted twice).
  • false — Insufficient valid signatures, or signatures do not correspond to any key in public_keys.
#![allow(unused)]
fn main() {
// Sign transaction data with wallet1 and wallet2
let data = "Transaction digest: Alice transfers 0.01 BTC to Bob";
let sig1 = wallet1.sign(data);
let sig2 = wallet2.sign(data);

let valid = multisig.verify_signatures_with_data(
    &[sig1, sig2],
    data,
);
println!("ECDSA verification passed: {}", valid);
}

MultiSigTxBuilder Struct

The multisig transaction builder is responsible for collecting signatures and confirming whether the M-of-N requirement is met. Built-in duplicate signature detection.

#![allow(unused)]
fn main() {
pub struct MultiSigTxBuilder {
    pub multisig_address: MultiSigAddress,
    pub signatures: Vec<String>,
    // signed_keys: HashMap<String, bool>  // Private field, prevents same public key from signing twice
}
}
FieldTypeDescription
multisig_addressMultiSigAddressAssociated multisig address configuration (includes M, N, public key list).
signaturesVec<String>List of collected valid ECDSA signatures.

MultiSigTxBuilder Methods

MultiSigTxBuilder::new

Creates a multisig transaction builder associated with the specified MultiSigAddress.

#![allow(unused)]
fn main() {
pub fn new(multisig_address: MultiSigAddress) -> Self
}

Parameters:

  • multisig_address — An already-created MultiSigAddress instance.
#![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

Adds an ECDSA signature from a participant. Internally validates automatically:

  1. The wallet’s public key must be in multisig_address.public_keys.
  2. The wallet cannot sign twice (prevents the same person from signing twice to fake M signatures).
#![allow(unused)]
fn main() {
pub fn add_signature(
    &mut self,
    wallet: &Wallet,
    data: &str,
) -> Result<(), String>
}

Parameters:

  • wallet — The participant’s Wallet instance (used to call wallet.sign(data) to generate a signature).
  • data — Data to sign (typically a transaction digest or transaction ID).

Return value:

  • Ok(()) — Signature added successfully.
  • Err(String) — Error reasons:
    • "This wallet is not in the multisig address" — The wallet’s public key is not in the public_keys list.
    • "This wallet has already signed" — The wallet has previously signed.
#![allow(unused)]
fn main() {
let mut builder = MultiSigTxBuilder::new(multisig.clone());
let data = "transfer_tx_hash_abc123";

// wallet1 signs successfully
builder.add_signature(&wallet1, data).unwrap();
assert_eq!(builder.signatures.len(), 1);

// wallet1 cannot sign again
let err = builder.add_signature(&wallet1, data);
assert!(err.is_err());
println!("Duplicate signature error: {}", err.unwrap_err()); // "This wallet has already signed"

// A wallet not in the multisig address cannot sign
let outsider = Wallet::new();
let err = builder.add_signature(&outsider, data);
assert!(err.is_err());
println!("External wallet error: {}", err.unwrap_err()); // "This wallet is not in the multisig address"
}

MultiSigTxBuilder::is_complete

Checks whether enough signatures have been collected (signatures.len() >= required_sigs).

#![allow(unused)]
fn main() {
pub fn is_complete(&self) -> bool
}

Return value: true means the M-of-N requirement is satisfied and the transaction can be broadcast.

#![allow(unused)]
fn main() {
let mut builder = MultiSigTxBuilder::new(multisig);
assert!(!builder.is_complete()); // 0 signatures

builder.add_signature(&wallet1, data).unwrap();
assert!(!builder.is_complete()); // 1 signature, not enough yet (needs 2)

builder.add_signature(&wallet2, data).unwrap();
assert!(builder.is_complete());  // 2 signatures, satisfies 2-of-3
println!("Multisig complete, transaction can be broadcast");
}

MultiSigTxBuilder::get_signatures

Gets a copy of all collected signatures.

#![allow(unused)]
fn main() {
pub fn get_signatures(&self) -> Vec<String>
}

Return value: Vec<String> — Clone of the signature list (does not affect the builder’s internal state).

#![allow(unused)]
fn main() {
let sigs = builder.get_signatures();
println!("Collected {} signatures", sigs.len());
for (i, sig) in sigs.iter().enumerate() {
    println!("  Signature {}: {}...", i + 1, &sig[..16]);
}
}

MultiSigType Enum (Convenience API)

Provides quick creation for common multisig types.

#![allow(unused)]
fn main() {
pub enum MultiSigType {
    TwoOfTwo,    // 2-of-2
    TwoOfThree,  // 2-of-3 (most common)
    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 address: {}", ms_addr.address);

// Returns an error if the number of wallets does not match
let err = MultiSigType::ThreeOfFive.create_address(&wallets); // Requires 5 wallets
assert!(err.is_err());
}

Complete Usage Examples

Scenario 1: Corporate Fund Management (2-of-3)

#![allow(unused)]
fn main() {
use simplebtc::multisig::{MultiSigAddress, MultiSigTxBuilder};
use simplebtc::wallet::Wallet;

fn corporate_treasury() {
    // Three company executives each hold one key
    let ceo = Wallet::new();
    let cfo = Wallet::new();
    let cto = Wallet::new();

    // Create 2-of-3 corporate multisig address
    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!("Corporate treasury address: {}", treasury.address);

    // Initiate a payment (requires CEO + CFO signatures)
    let tx_data = "Pay 10 BTC to supplier ABC";
    let mut builder = MultiSigTxBuilder::new(treasury);

    builder.add_signature(&ceo, tx_data).unwrap();
    println!("CEO has signed, waiting for second authorization...");

    builder.add_signature(&cfo, tx_data).unwrap();
    println!("CFO has signed");

    if builder.is_complete() {
        let signatures = builder.get_signatures();
        println!("Transaction authorized, signature count: {}", signatures.len());
        // Here, attach signatures to the transaction and broadcast to the network
    }
}
}

Scenario 2: Third-Party Escrow (Buyer-Seller-Arbitrator)

#![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: normally buyer+seller; in a dispute, either party + arbitrator
    let escrow = MultiSigAddress::new(2, pub_keys).unwrap();
    println!("Escrow address: {}", escrow.address);

    let release_tx = "Release escrow funds to seller address";

    // Normal flow: buyer confirms receipt; buyer+seller sign to release funds
    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!("Funds released normally to seller");

    // Dispute flow: arbitrator intervenes; seller+arbitrator sign to release funds
    let dispute_tx = "Dispute ruling: refund to buyer";
    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!("Arbitration complete, funds returned to buyer");
}
}

Scenario 3: Full Signature Verification Flow

#![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 = "Transfer 1 BTC";

    // Collect signatures
    let sig1 = w1.sign(tx_data);
    let sig2 = w2.sign(tx_data);
    let sigs = vec![sig1, sig2];

    // Full ECDSA verification (verifies signatures were produced by keys in public_keys)
    let valid = ms.verify_signatures_with_data(&sigs, tx_data);
    println!("ECDSA multisig verification: {}", valid);

    // Quick count check (does not verify content)
    let count_ok = ms.verify_signatures(&sigs);
    println!("Signature count satisfied: {}", count_ok);
}
}

Limitations and Notes

  • Maximum keys: Due to Bitcoin script limits, N is at most 15 (returns an error if exceeded).
  • Duplicate signature prevention: MultiSigTxBuilder internally maintains a set of already-signed public keys; calling add_signature twice with the same wallet returns an error.
  • Signature order: Verification does not require signatures to be in the same order as the public keys; any M valid signatures will do.
  • Zero-confirmation risk: A multisig transaction is still unconfirmed before being mined. For important transactions, wait for at least 1 block confirmation.

  • Wallet — Provides sign() and verify_signature() methods, the foundation of multisig ECDSA operations.
  • AdvancedTxBuilder — Can be combined with timelocks to implement advanced scenarios such as “reduced multisig requirement after time expiry.”
  • Advanced Modules Overview — Understand multisig’s place in SimpleBTC’s overall architecture.

Advanced TX API

The advanced transaction module is implemented in src/advanced_tx.rs and provides three key mechanisms addressing real engineering problems in the Bitcoin network: RBF (Replace-By-Fee) (allowing acceleration or cancellation of unconfirmed transactions), TimeLock (restricting transactions from being confirmed before a specified time or block), and TxPriorityCalculator (recommending reasonable fees and calculating transaction priority).


RBFManager — Replace-By-Fee Manager

RBF (Replace-By-Fee), defined in BIP125, allows users to replace an unconfirmed transaction in the mempool with a new one bearing a higher fee, thereby accelerating confirmation or canceling an erroneous transaction.

Struct Definition

#![allow(unused)]
fn main() {
pub struct RBFManager {
    // replaceable_txs: Vec<String>  // Private field, stores list of replaceable transaction IDs
}
}

Methods

RBFManager::new

Creates a new RBF manager instance (replaceable transaction list is empty).

#![allow(unused)]
fn main() {
pub fn new() -> Self
}

RBFManager::mark_replaceable

Marks the specified transaction as supporting RBF replacement. Idempotent operation; marking the same transaction multiple times has no side effects.

#![allow(unused)]
fn main() {
pub fn mark_replaceable(&mut self, tx_id: &str)
}

Parameters:

  • tx_id — Transaction ID to mark as replaceable.

In the Bitcoin protocol, a transaction indicates RBF support by setting its nSequence field to a value less than 0xFFFFFFFE. AdvancedTxBuilder::with_rbf() automatically sets sequence to 0xFFFFFFFD.

RBFManager::is_replaceable

Checks whether a transaction has been marked as replaceable.

#![allow(unused)]
fn main() {
pub fn is_replaceable(&self, tx_id: &str) -> bool
}

RBFManager::can_replace

Validates whether a new transaction can legitimately replace the old one. Performs full RBF rule checks:

#![allow(unused)]
fn main() {
pub fn can_replace(
    &self,
    old_tx: &Transaction,
    new_tx: &Transaction,
) -> Result<(), String>
}

Validation rules (in order):

  1. Replaceability check: old_tx.id must be in the replaceable list; otherwise returns "Original transaction does not support RBF".
  2. Same inputs: Both transactions’ input lists have the same length, and corresponding inputs have identical txid and vout (must spend the same UTXOs); otherwise returns "Must spend the same UTXOs".
  3. Higher fee: new_tx.fee > old_tx.fee; otherwise returns "New transaction fee({}) must be higher than old transaction({})".
  4. Sufficient increment: fee_increase >= old_tx.size() (simplified rule: fee increment must be at least as many satoshi as the old transaction’s byte count), preventing low-cost spam replacement attacks.

Return value:

  • Ok(()) — Replacement is legitimate; the new transaction can be broadcast.
  • Err(String) — Specific validation failure reason.

RBFManager::remove_confirmed

Removes a confirmed transaction from the replaceable list.

#![allow(unused)]
fn main() {
pub fn remove_confirmed(&mut self, tx_id: &str)
}

RBFManager Usage Example

#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::RBFManager;

let mut rbf = RBFManager::new();

// Mark original transaction as supporting RBF
rbf.mark_replaceable("original_tx_001");
assert!(rbf.is_replaceable("original_tx_001"));
assert!(!rbf.is_replaceable("other_tx_002"));

// Validate whether replacement is legitimate
match rbf.can_replace(&old_tx, &new_tx) {
    Ok(()) => println!("Replacement legitimate, broadcast new transaction"),
    Err(e) => println!("Replacement rejected: {}", e),
}

// Remove record after transaction is confirmed
rbf.remove_confirmed("original_tx_001");
assert!(!rbf.is_replaceable("original_tx_001"));
}

TimeLock — Timelock

Timelocks restrict a transaction from being mined by miners before a specific time or block height. They are a foundational primitive for implementing advanced scenarios such as time deposits, inheritance, smart contracts, and more.

Struct Definition

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeLock {
    pub locktime: u64,         // Lock value: Unix timestamp (seconds) or block height
    pub is_block_height: bool, // true: block-height-based; false: timestamp-based
}
}
FieldTypeDescription
locktimeu64Lock value. When is_block_height = true, this is the block height; otherwise it is a Unix timestamp (seconds).
is_block_heightboolLock type flag. Corresponds to Bitcoin protocol: locktime < 500_000_000 = block height; >= 500_000_000 = timestamp.

Methods

TimeLock::new_time_based

Creates a Unix timestamp-based timelock. The transaction cannot be confirmed before timestamp seconds.

#![allow(unused)]
fn main() {
pub fn new_time_based(timestamp: u64) -> Self
}

Parameters:

  • timestamp — Unix timestamp (seconds) for unlock time. For example, 1767225600 represents 2026-01-01 00:00:00 UTC.

TimeLock::new_height_based

Creates a block-height-based timelock. The transaction cannot be confirmed until the blockchain reaches the specified height.

#![allow(unused)]
fn main() {
pub fn new_height_based(height: u64) -> Self
}

Parameters:

  • height — Block height required for unlock. For example, 900_000 represents approximately mid-2027 (based on approximately 10 minutes per block).

TimeLock::is_mature

Checks whether the timelock has expired (is ready to be spent).

#![allow(unused)]
fn main() {
pub fn is_mature(&self, current_time: u64, current_height: u32) -> bool
}

Parameters:

  • current_time — Current Unix timestamp (seconds).
  • current_height — Current blockchain height.

Return value:

  • Time-based: current_time >= self.locktime
  • Block-height-based: current_height as u64 >= self.locktime

TimeLock::remaining

Gets how much time (seconds) or how many blocks remain until unlock.

#![allow(unused)]
fn main() {
pub fn remaining(&self, current_time: u64, current_height: u32) -> i64
}

Return value: Remaining seconds or block count. A negative value indicates the lock time has already passed (expired).

TimeLock Usage Example

#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::TimeLock;

// Timestamp-based: lock until 2026-01-01 00:00:00 UTC
let time_lock = TimeLock::new_time_based(1_767_225_600);
let now = 1_740_000_000u64; // Current time (2025)
println!("Timelock expired: {}", time_lock.is_mature(now, 0)); // false
println!("Remaining until unlock: {} seconds", time_lock.remaining(now, 0));

// Block-height-based: lock until block 900,000
let block_lock = TimeLock::new_height_based(900_000);
let current_height = 850_000u32; // Current block height
println!("Block lock expired: {}", block_lock.is_mature(0, current_height)); // false
println!("Remaining until unlock: {} blocks", block_lock.remaining(0, current_height)); // 50000

// An already-expired timelock
let expired_lock = TimeLock::new_height_based(800_000);
println!("Expired: {}", expired_lock.is_mature(0, 850_000)); // true
println!("Remaining (negative = already past): {}", expired_lock.remaining(0, 850_000)); // -50000
}

AdvancedTxBuilder — Advanced Transaction Builder

AdvancedTxBuilder is a builder (Builder Pattern) for configuring advanced transaction options (RBF support and timelocks) and generating the corresponding sequence field value.

Struct Definition

#![allow(unused)]
fn main() {
pub struct AdvancedTxBuilder {
    pub enable_rbf: bool,
    pub timelock: Option<TimeLock>,
    pub sequence: u32,
}
}
FieldTypeDescription
enable_rbfboolWhether RBF support is enabled. true after with_rbf().
timelockOption<TimeLock>Associated timelock configuration. Some(TimeLock) after with_timelock().
sequenceu32Transaction input sequence number, encoding RBF and timelock state: 0xFFFFFFFF (default/no feature), 0xFFFFFFFD (RBF), 0x00000000 (timelock).

Methods

AdvancedTxBuilder::new

Creates a default builder. RBF and timelock are disabled by default; sequence = 0xFFFFFFFF.

#![allow(unused)]
fn main() {
pub fn new() -> Self
}

AdvancedTxBuilder::with_rbf

Enables RBF support. Sets enable_rbf to true and sequence to 0xFFFFFFFD (less than 0xFFFFFFFE, compliant with BIP125).

#![allow(unused)]
fn main() {
pub fn with_rbf(mut self) -> Self
}

Return value: Self (supports method chaining).

AdvancedTxBuilder::with_timelock

Sets a timelock. Sets timelock to Some(timelock) and sequence to 0 (enables nLockTime mechanism).

#![allow(unused)]
fn main() {
pub fn with_timelock(mut self, timelock: TimeLock) -> Self
}

Parameters:

  • timelock — The TimeLock instance to associate.

Return value: Self (supports method chaining).

AdvancedTxBuilder::get_sequence

Gets the final sequence field value, which should be written to the transaction input’s nSequence field.

#![allow(unused)]
fn main() {
pub fn get_sequence(&self) -> u32
}

AdvancedTxBuilder::supports_rbf

Checks whether the current configuration supports RBF (sequence < 0xFFFFFFFE).

#![allow(unused)]
fn main() {
pub fn supports_rbf(&self) -> bool
}

AdvancedTxBuilder Usage Example

#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{AdvancedTxBuilder, TimeLock};

// Enable RBF only
let rbf_builder = AdvancedTxBuilder::new()
    .with_rbf();
println!("sequence: 0x{:08X}", rbf_builder.get_sequence()); // 0xFFFFFFFD
println!("Supports RBF: {}", rbf_builder.supports_rbf()); // true

// Enable timelock only (lock until block 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 + timelock combined (with_timelock overrides sequence to 0)
let combined = AdvancedTxBuilder::new()
    .with_rbf()
    .with_timelock(TimeLock::new_time_based(1_800_000_000));
println!("Timelock: {:?}", combined.timelock);
println!("sequence: 0x{:08X}", combined.get_sequence()); // 0x00000000

// Default builder (no advanced features)
let default_builder = AdvancedTxBuilder::new();
println!("sequence: 0x{:08X}", default_builder.get_sequence()); // 0xFFFFFFFF
println!("Supports RBF: {}", default_builder.supports_rbf()); // false
}

TxPriorityCalculator — Transaction Priority Calculator

TxPriorityCalculator is a stateless utility class (all methods are associated functions) for calculating transaction priority scores and recommending reasonable fees. Miners use priority scores to decide which mempool transactions to pack first.

Struct Definition

#![allow(unused)]
fn main() {
pub struct TxPriorityCalculator;
}

FeeUrgency — Fee Urgency Level

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy)]
pub enum FeeUrgency {
    Low,    // Low priority: 1 sat/byte, confirmation within hours
    Medium, // Medium priority: 5 sat/byte, 30–60 minute confirmation
    High,   // High priority: 20 sat/byte, 10–20 minutes (approximately 1–2 blocks)
    Urgent, // Urgent: 50 sat/byte, next block (approximately 10 minutes)
}
}
Enum ValueFee RateExpected Confirmation TimeTypical Use Case
Low1 sat/byteHours to daysNon-urgent transfers, low network fee periods
Medium5 sat/byte30–60 minutesEveryday transactions, normal confirmation speed
High20 sat/byte10–20 minutesTime-sensitive transactions (Lightning Network channel opening)
Urgent50 sat/byte~10 minutes (next block)Urgent payments, exchange withdrawals

Methods

TxPriorityCalculator::calculate_priority

Calculates the traditional priority score based on UTXO value and age.

Formula: priority = (input_value × input_age) / tx_size

#![allow(unused)]
fn main() {
pub fn calculate_priority(
    input_value: u64, // Total value of input UTXOs (satoshi)
    input_age: u32,   // Age of input UTXOs (number of confirmations)
    tx_size: usize,   // Transaction size (bytes)
) -> f64
}

Older (larger age) and higher-value UTXOs have higher priority. Returns 0.0 when tx_size = 0 (prevents division by zero).

TxPriorityCalculator::calculate_fee_rate

Calculates the transaction fee rate (sat/byte).

Formula: fee_rate = fee / size

#![allow(unused)]
fn main() {
pub fn calculate_fee_rate(
    fee: u64,    // Fee (satoshi)
    size: usize, // Transaction size (bytes)
) -> f64
}

Returns 0.0 when size = 0.

TxPriorityCalculator::calculate_score

Calculates a composite score (miner sorting basis).

Formula: score = fee_rate × 0.7 + priority × 0.001 × 0.3

#![allow(unused)]
fn main() {
pub fn calculate_score(fee_rate: f64, priority: f64) -> f64
}

Weight distribution: 70% fee-rate-based, 30% UTXO-priority-based. Transactions with higher fee rates get higher composite scores and are more likely to be selected by miners.

TxPriorityCalculator::recommend_fee

Recommends a fee (satoshi) based on transaction size and urgency.

#![allow(unused)]
fn main() {
pub fn recommend_fee(
    tx_size: usize,    // Transaction size (bytes)
    urgency: FeeUrgency, // Fee urgency level
) -> u64
}

Return value: (tx_size × sat_per_byte) as u64 truncated.

TxPriorityCalculator Usage Example

#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{TxPriorityCalculator, FeeUrgency};

// A standard Bitcoin transaction is approximately 250 bytes (1 input + 2 outputs)
let tx_size = 250usize;

// Recommended fee for each urgency level
println!("Low priority:  {} sat", TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Low));
// 250 sat
println!("Medium priority:  {} sat", TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Medium));
// 1250 sat
println!("High priority:  {} sat", TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::High));
// 5000 sat
println!("Urgent:      {} sat", TxPriorityCalculator::recommend_fee(tx_size, FeeUrgency::Urgent));
// 12500 sat

// Calculate fee rate for an existing transaction
let actual_fee = 2000u64; // Actual fee
let fee_rate = TxPriorityCalculator::calculate_fee_rate(actual_fee, tx_size);
println!("Actual fee rate: {:.1} sat/byte", fee_rate); // 8.0 sat/byte

// Calculate UTXO priority (hold 1 BTC, confirmed for 100 blocks, 250-byte transaction)
let priority = TxPriorityCalculator::calculate_priority(
    100_000_000, // 1 BTC = 100,000,000 satoshi
    100,         // 100 blocks of age
    tx_size,
);
println!("Priority score: {:.0}", priority); // 40,000,000

// Composite score (miner sorting basis)
let score = TxPriorityCalculator::calculate_score(fee_rate, priority);
println!("Composite score: {:.2}", score);
}

Complete Usage Examples

Scenario 1: Accelerating an Unconfirmed Transaction with RBF

#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{RBFManager, AdvancedTxBuilder, TxPriorityCalculator, FeeUrgency};

fn accelerate_tx_example() {
    let mut rbf = RBFManager::new();

    // 1. Send the original transaction (low fee, supports RBF)
    let builder = AdvancedTxBuilder::new().with_rbf();
    println!("RBF sequence: 0x{:08X}", builder.get_sequence()); // 0xFFFFFFFD

    // Simulate transaction sent but still unconfirmed after 30 minutes
    // ... create and broadcast original transaction original_tx ...
    rbf.mark_replaceable("original_tx_id_001");

    // 2. Network congested, need to increase fee
    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!("Original fee: {} sat -> New fee: {} sat", old_fee, new_fee);

    // 3. Validate replacement rules
    // can_replace checks: same inputs, higher fee, sufficient increment
    // match rbf.can_replace(&old_tx, &new_tx) {
    //     Ok(()) => { /* broadcast new transaction */ }
    //     Err(e) => println!("Replacement rejected: {}", e),
    // }

    // 4. Clean up after old transaction is confirmed (or replaced)
    rbf.remove_confirmed("original_tx_id_001");
}
}

Scenario 2: Time Deposit with Timelock

#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{AdvancedTxBuilder, TimeLock};

fn savings_timelock() {
    // Lock until block height 950,000 (approximately 2028)
    let unlock_height = 950_000u64;
    let timelock = TimeLock::new_height_based(unlock_height);

    let builder = AdvancedTxBuilder::new()
        .with_timelock(timelock.clone());

    println!("Transaction sequence: 0x{:08X}", builder.get_sequence()); // 0x00000000
    println!("Timelock enabled: {}", builder.timelock.is_some());

    // Check if funds can currently be used
    let current_height = 870_000u32;
    if timelock.is_mature(0, current_height) {
        println!("Funds unlocked, can be used");
    } else {
        let remaining = timelock.remaining(0, current_height);
        println!("Need to wait {} more blocks (approximately {} days)",
            remaining,
            remaining * 10 / 60 / 24); // Approximate days
    }
}
}

Scenario 3: Fee Strategy Analysis

#![allow(unused)]
fn main() {
use simplebtc::advanced_tx::{TxPriorityCalculator, FeeUrgency};

fn fee_strategy_analysis() {
    let tx_sizes = vec![
        (125,  "Simple payment (1 input, 1 output)"),
        (250,  "Standard transaction (1 input, 2 outputs)"),
        (500,  "Batch payment (multiple inputs and outputs)"),
        (1000, "Large transaction (common before SegWit)"),
    ];

    println!("{:<40} {:>10} {:>10} {:>10} {:>10}",
        "Transaction Type", "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),
        );
    }

    // Composite score comparison
    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!("\nTransaction A (old UTXO, low fee rate) composite score: {:.2}", TxPriorityCalculator::calculate_score(fee_rate_a, priority_a));
    println!("Transaction B (new UTXO, high fee rate) composite score: {:.2}", TxPriorityCalculator::calculate_score(fee_rate_b, priority_b));
}
}

sequence Field Value Reference

sequence ValueMeaning
0xFFFFFFFFDefault value; RBF and timelock not enabled
0xFFFFFFFEDoes not support RBF, but allows nLockTime
0xFFFFFFFDSupports RBF (BIP125 standard value)
0x00000000Enables timelock (nLockTime takes effect)

  • Mempool — The mempool uses RBFManager for transaction replacement and TxPriorityCalculator to sort pending transactions.
  • MultiSig — Multisig and timelocks can be combined to implement scenarios such as “M-of-N required before expiry, reduced to 1-of-N after.”
  • Advanced Modules Overview — View the full advanced module dependency graph.

REST API

SimpleBTC provides a complete RESTful API, allowing interaction with the blockchain system over HTTP. This page describes all endpoints, request and response formats, and cURL call examples in detail.


Basic Information

ItemDescription
Base URLhttp://localhost:3000
ProtocolHTTP/1.1
Data FormatJSON
CORSAll origins allowed (*)
AuthenticationNone (demo version)

Starting the Server

# Development mode
cargo run --bin server

# Release mode (better performance)
cargo run --release --bin server

Server output on startup:

  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)

Common Response Format

All endpoints use a unified JSON response structure:

Success Response

{
  "success": true,
  "data": { },
  "error": null
}

Error Response

{
  "success": false,
  "data": null,
  "error": "Error description"
}

HTTP Status Codes

Status CodeDescription
200Request successful
400Parameter error or business logic failure

Endpoint Summary

MethodPathDescription
GET/Returns embedded Web UI
GET/api/blockchain/infoGet blockchain status information
GET/api/blockchain/chainGet full blockchain data
GET/api/blockchain/validateValidate blockchain integrity
POST/api/wallet/createCreate a new wallet
GET/api/wallet/balance/:addressQuery address balance
POST/api/transaction/createCreate a transfer transaction
POST/api/mineMine (pack pending transactions)

Endpoint Details

GET /

Returns the embedded Web UI page (HTML). Suitable for opening directly in a browser.

Example

curl http://localhost:3000/

GET /api/blockchain/info

Gets the current blockchain state, including height, difficulty, number of pending transactions, mining reward, and genesis address.

Response Fields

FieldTypeDescription
heightnumberBlockchain height (total blocks included)
difficultynumberCurrent mining difficulty (number of leading zeros in hash)
pending_transactionsnumberNumber of unconfirmed transactions in the mempool
mining_rewardnumberBlock mining reward (satoshi)
genesis_addressstringGenesis wallet address (pre-funded with 100 BTC)

Response Example

{
  "success": true,
  "data": {
    "height": 3,
    "difficulty": 4,
    "pending_transactions": 1,
    "mining_reward": 5000,
    "genesis_address": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
  },
  "error": null
}

cURL Example

curl http://localhost:3000/api/blockchain/info

GET /api/blockchain/chain

Gets the full blockchain data, including all fields and transaction lists for each block.

Block Fields

FieldTypeDescription
indexnumberBlock index (starting from 0)
timestampnumberBlock timestamp (milliseconds)
transactionsarrayTransaction list
previous_hashstringPrevious block hash
hashstringThis block’s hash
noncenumberProof-of-work nonce
merkle_rootstringMerkle root of transactions
difficultynumberBlock difficulty

Transaction Fields

FieldTypeDescription
idstringTransaction ID (hash)
inputsarrayTransaction input list (UTXO references)
outputsarrayTransaction output list (recipients and amounts)
timestampnumberTransaction timestamp

Response Example

{
  "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 Example

# Get full blockchain (with jq for formatted output)
curl http://localhost:3000/api/blockchain/chain | jq

GET /api/blockchain/validate

Validates the integrity of the blockchain, checking whether the hash chain and proof of work for all blocks are valid.

Response Example (Validation Passed)

{
  "success": true,
  "data": "Blockchain is valid",
  "error": null
}

Response Example (Validation Failed)

{
  "success": false,
  "data": "Blockchain is invalid",
  "error": null
}

cURL Example

curl http://localhost:3000/api/blockchain/validate

POST /api/wallet/create

Generates a new secp256k1 key pair on the server side, returning the wallet address and public key. The private key is saved in server memory for subsequent ECDSA signing of transactions.

Note: The private key is not returned via the API. The wallet address created this way can be directly used to receive transfers and initiate transactions.

Request Body

None required.

Response Fields

FieldTypeDescription
addressstringWallet address (40-character hex)
public_keystringCompressed public key (secp256k1)

Response Example

{
  "success": true,
  "data": {
    "address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a",
    "public_key": "04d8c9e4b7f1a89c2d5e8f3b6a1c4e7d9b2a5c8f1a3d6e9b4c7f0a3d6e9b4c7f0"
  },
  "error": null
}

cURL Example

curl -X POST http://localhost:3000/api/wallet/create

GET /api/wallet/balance/:address

Queries the current balance of a specified wallet address; balance is computed from the UTXO set.

Path Parameters

ParameterTypeRequiredDescription
addressstringYesWallet address (40-character hex)

Response Fields

FieldTypeDescription
addressstringThe queried wallet address
balancenumberBalance (satoshi; 1 BTC = 100,000,000 satoshi)

Response Example

{
  "success": true,
  "data": {
    "address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a",
    "balance": 10000000000
  },
  "error": null
}

cURL Example

ADDRESS="3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a"
curl http://localhost:3000/api/wallet/balance/$ADDRESS

POST /api/transaction/create

Creates a transfer transaction, signs it with the sender’s private key using secp256k1 ECDSA, and adds it to the mempool to await mining confirmation.

Prerequisite: The sender address must be a wallet created via /api/wallet/create (the server must hold its private key to sign).

Request Body

{
  "from_address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a",
  "to_address":   "7c1e5b9f4a2d8e3c6f0b5a9d2e7f4c1b8a3d6e9f",
  "amount": 5000,
  "fee": 10
}

Request Parameters

ParameterTypeRequiredDescription
from_addressstringYesSender wallet address
to_addressstringYesRecipient wallet address
amountnumberYesTransfer amount (satoshi)
feenumberYesTransaction fee (satoshi, goes to the miner)

Success Response

{
  "success": true,
  "data": "Transaction created: txabc123def456...",
  "error": null
}

Error Response Example

{
  "success": false,
  "data": null,
  "error": "Wallet not found: 3f8a.... Please first create a wallet via /api/wallet/create."
}

Common Errors

ErrorCauseResolution
Wallet not foundSender address not created on this serverCall /api/wallet/create first
Insufficient balanceBalance < amount + feeReduce amount, or mine to receive a reward first

cURL Example

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

Performs proof-of-work mining, packing all pending transactions in the mempool into a new block, and distributing the block reward to the miner address.

Note: Mining is a CPU-intensive operation that may take several seconds depending on the current difficulty. The mining reward (mining_reward) and all transaction fees go to the miner address; the balance change is visible after the next query.

Request Body

{
  "miner_address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a"
}

Request Parameters

ParameterTypeRequiredDescription
miner_addressstringYesMiner wallet address (receives reward)

Success Response

{
  "success": true,
  "data": "Block mined! Height: 4",
  "error": null
}

Error Response Example

{
  "success": false,
  "data": null,
  "error": "No pending transactions"
}

cURL Example

curl -X POST http://localhost:3000/api/mine \
  -H "Content-Type: application/json" \
  -d '{"miner_address": "3f8a2d1c9e4b7f0a5c8d2e6f1b4a9c3d7e5f2b8a"}'

Quick Start: Complete Workflow

The following example shows the complete flow from creating a wallet to completing a transfer.

Step 1: Get the Genesis Address

The genesis wallet is pre-funded with 100 BTC (10,000,000,000 satoshi); its address can be obtained from blockchain/info.

# Get genesis address
curl -s http://localhost:3000/api/blockchain/info | jq '.data.genesis_address'
# Example output: "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"

GENESIS="a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"

Step 2: Create a New Wallet

# Create wallet, save address
WALLET=$(curl -s -X POST http://localhost:3000/api/wallet/create)
echo $WALLET | jq

MY_ADDR=$(echo $WALLET | jq -r '.data.address')
echo "My address: $MY_ADDR"

Step 3: Transfer from Genesis Address to New Wallet

# Genesis address transfers 10,000 satoshi to new wallet
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
  }"

Step 4: Mine to Confirm Transaction

# Use new wallet as miner address (also receives mining reward)
curl -X POST http://localhost:3000/api/mine \
  -H "Content-Type: application/json" \
  -d "{\"miner_address\": \"$MY_ADDR\"}"

Step 5: Query Balance

# Query new wallet balance (should include transfer amount + mining reward)
curl http://localhost:3000/api/wallet/balance/$MY_ADDR | jq

Step 6: Validate the Blockchain

# Confirm blockchain data is complete
curl http://localhost:3000/api/blockchain/validate | jq

Complete Script

#!/bin/bash
BASE="http://localhost:3000"

echo "=== SimpleBTC Quick Demo ==="

# 1. Get genesis address
GENESIS=$(curl -s $BASE/api/blockchain/info | jq -r '.data.genesis_address')
echo "Genesis address: $GENESIS"

# 2. Create new wallet
MY_ADDR=$(curl -s -X POST $BASE/api/wallet/create | jq -r '.data.address')
echo "New wallet address: $MY_ADDR"

# 3. Create transaction (genesis address → new wallet, transfer 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 "Transaction: $(echo $TX | jq -r '.data')"

# 4. Mine to confirm
MINE=$(curl -s -X POST $BASE/api/mine \
  -H "Content-Type: application/json" \
  -d "{\"miner_address\":\"$MY_ADDR\"}")
echo "Mining: $(echo $MINE | jq -r '.data')"

# 5. Query balance
BAL=$(curl -s $BASE/api/wallet/balance/$MY_ADDR | jq '.data.balance')
echo "Balance: $BAL satoshi"

# 6. Validate blockchain
VALID=$(curl -s $BASE/api/blockchain/validate | jq -r '.data')
echo "Validation: $VALID"

Unit Notes

All amount fields in SimpleBTC use satoshi as the unit (consistent with Bitcoin):

UnitConversion
1 BTC100,000,000 satoshi
1 mBTC100,000 satoshi
1 satoshiMinimum unit, indivisible

The genesis wallet’s pre-funded balance is 10,000,000,000 satoshi (100 BTC). The default mining reward is 5000 satoshi.

Glossary

Core terminology related to Bitcoin and blockchain.

A

Address

A unique identifier used to receive bitcoin. Generated from a public key through hashing and encoding.

Example: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa

Types:

  • P2PKH (starts with 1): legacy address
  • P2SH (starts with 3): script/multisig address
  • Bech32 (starts with bc1): SegWit address

ACID

Four properties of database transactions:

  • Atomicity: all-or-nothing execution
  • Consistency: maintains a consistent data state
  • Isolation: concurrent transactions do not interfere with each other
  • Durability: committed changes are persisted permanently

SimpleBTC transaction processing conforms to ACID properties.


B

Block

A data structure containing a list of transactions, linked by hashes to form a blockchain.

Contains:

  • Block header (index, timestamp, hash, previous_hash, merkle_root, nonce)
  • Transaction list (the first is the Coinbase transaction)

Size: approximately 1–4 MB in Bitcoin


Blockchain

A chronologically linked sequence of blocks, made tamper-resistant through cryptography.

Properties:

  • Decentralized
  • Tamper-resistant
  • Transparent and verifiable
  • Trustless (no third party required)

Block Height

The position of a block in the chain. The genesis block has height 0.

Example: If the blockchain contains 100 blocks, the latest block has height 99.


BIP (Bitcoin Improvement Proposal)

A proposal for improving the Bitcoin protocol.

Important BIPs:

  • BIP11: M-of-N multisig
  • BIP16: P2SH (Pay-to-Script-Hash)
  • BIP32: Hierarchical Deterministic Wallets
  • BIP39: Mnemonic phrases
  • BIP125: RBF (Replace-By-Fee)

C

Coinbase Transaction

The first transaction in a block, used to pay the mining reward to the miner.

Characteristics:

  • No valid inputs (does not spend UTXOs)
  • Creates new bitcoin
  • Includes block reward + transaction fees

Example:

#![allow(unused)]
fn main() {
Transaction::new_coinbase(
    miner_address,
    50,           // block reward
    timestamp,
    total_fees,   // sum of fees
)
}

Cold Wallet

A wallet that stores private keys offline, not connected to the internet.

Types:

  • Hardware wallets (Ledger, Trezor)
  • Paper wallets
  • Air-gapped computers

Advantage: Extremely high security Disadvantage: Inconvenient to use


Confirmation

The number of times a transaction has been included in a block and subsequently extended by additional blocks.

Confirmation counts:

  • 0 confirmations: in the mempool, not yet mined
  • 1 confirmation: included in a block
  • 6 confirmations: very secure (Bitcoin standard)

Time: approximately 10 minutes per confirmation in Bitcoin


D

Difficulty

The computational difficulty of mining, which determines how hard it is to find a valid block hash.

SimpleBTC:

#![allow(unused)]
fn main() {
blockchain.difficulty = 3;  // 3 leading zeros
}

Bitcoin: dynamically adjusted every 2016 blocks (approximately 2 weeks), targeting a 10-minute block time.


Double Spending

An attack that attempts to spend the same bitcoin twice.

Defense mechanisms:

  1. UTXO model (each UTXO can only be spent once)
  2. Block confirmations (almost impossible after 6 confirmations)
  3. Proof of Work (requires 51% hash power to rewrite history)

E

ECDSA (Elliptic Curve Digital Signature Algorithm)

The digital signature scheme used by Bitcoin.

Curve: secp256k1

Flow:

Private key → ECDSA → Public key → Hash → Address

SimpleBTC uses a simplified SHA256-based signature.


F

Fee

The amount paid to miners as an incentive to include transactions in a block.

Calculation:

Fee = total inputs − total outputs

Fee rate:

Fee rate = fee / transaction size (sat/byte)

Recommendations:

  • Low: 1–5 sat/byte
  • Medium: 10–20 sat/byte
  • High: 50+ sat/byte

Fork

A situation where the blockchain has multiple valid branches.

Types:

  • Temporary fork: two miners produce a block simultaneously; resolved by the longest-chain rule
  • Hard fork: protocol-incompatible upgrade (e.g., BCH)
  • Soft fork: backward-compatible upgrade (e.g., SegWit)

G

Genesis Block

The first block in the blockchain, with index 0.

Bitcoin genesis block:

  • Date: January 3, 2009
  • Reward: 50 BTC (unspendable)
  • Message: “The Times 03/Jan/2009 Chancellor on brink of second bailout for banks”

SimpleBTC:

#![allow(unused)]
fn main() {
fn create_genesis_block() {
    // Create the block at index 0
    // previous_hash = "0"
}
}

H

Hash

A function that converts arbitrary data into a fixed-length string.

Bitcoin uses:

  • SHA256 (transaction IDs, block hashes)
  • RIPEMD160 (address generation)

Properties:

  • Deterministic
  • One-way (preimage resistant)
  • Collision resistant
  • Avalanche effect

Example:

SHA256("hello") = 2cf24dba5fb0a30e...

Hash Rate

The number of hash computations performed per second.

Units:

  • 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

Bitcoin network: approximately 300+ EH/s


Hot Wallet

A wallet connected to the internet, convenient for everyday use.

Types:

  • Mobile wallets
  • Desktop wallets
  • Web wallets

Advantage: Convenient to use Disadvantage: Lower security


M

Merkle Tree

A binary hash tree of transactions; the root hash is stored in the block header.

Structure:

        Root
       /    \
     H12    H34
    /  \   /  \
   H1  H2 H3  H4

Uses:

  • SPV lightweight verification
  • Proving a transaction exists in a block
  • O(log n) verification complexity

Mining

The process of creating new blocks through proof of work.

Steps:

  1. Collect pending transactions
  2. Create the Coinbase transaction
  3. Compute the Merkle root
  4. Adjust the nonce to find a valid hash
  5. Broadcast the block

Reward: block reward + transaction fees


Multisig (M-of-N)

An address that requires M signatures (out of N total keys) to spend funds.

Examples:

  • 2-of-3: CEO + CFO + CTO, any two suffice
  • 3-of-5: a board of 5, requiring 3 to agree

Address: starts with “3” (P2SH)


N

Node

A computer running Bitcoin client software.

Types:

  • Full node: stores the complete blockchain, validates all transactions
  • Light node: stores only block headers, uses SPV verification
  • Miner node: a full node that participates in mining

Nonce

A number adjusted during mining to change the block hash.

Purpose: proof of work

#![allow(unused)]
fn main() {
while hash(block_data + nonce) >= target {
    nonce++;  // keep trying
}
}

P

P2P (Peer-to-Peer)

A network where nodes communicate directly with each other, without a central server.

Bitcoin network:

  • Decentralized
  • Censorship-resistant
  • No single point of failure

P2PKH (Pay-to-Public-Key-Hash)

The traditional Bitcoin address type, starting with “1”.

Flow:

Public key → SHA256 → RIPEMD160 → Base58 → Address

P2SH (Pay-to-Script-Hash)

Pay-to-script-hash, used for advanced features like multisig; addresses start with “3”.

Advantages:

  • Supports complex scripts
  • Hides script details
  • Fee is borne by the recipient

Private Key

A secret number used to sign transactions.

Properties:

  • A 256-bit random number
  • Owning the private key = owning the bitcoin
  • Cannot be recovered if lost

Protection:

  • Never share it
  • Store it encrypted
  • Keep multiple backups

Proof of Work (PoW)

Bitcoin’s consensus mechanism.

Principle: Finding a hash that satisfies the difficulty target requires a large amount of computation.

Purpose:

  • Prevent spam attacks
  • Decentralized consensus
  • Extremely high cost for a 51% attack

Public Key

A publicly shareable number derived from a private key, used to generate addresses and verify signatures.

Derivation:

Private key → elliptic curve operation → Public key → Hash → Address

R

RBF (Replace-By-Fee)

A mechanism that allows replacing an unconfirmed transaction (BIP125).

Uses:

  • Speed up a transaction (by increasing the fee)
  • Cancel a transaction
  • Batch optimization

Marker: nSequence < 0xFFFFFFFE


S

Satoshi (sat)

The smallest unit of bitcoin.

1 BTC = 100,000,000 satoshi
1 sat = 0.00000001 BTC

Named after: Bitcoin’s creator, Satoshi Nakamoto


Script

Bitcoin’s scripting language, which defines spending conditions.

Opcodes:

  • OP_DUP
  • OP_HASH160
  • OP_EQUALVERIFY
  • OP_CHECKSIG
  • OP_CHECKMULTISIG

SimpleBTC uses a simplified version.


SPV (Simplified Payment Verification)

Lightweight verification that does not require downloading the full blockchain.

Principle: Uses Merkle proofs to verify transactions.

Advantages:

  • Only needs block headers (~80 bytes each)
  • Suitable for mobile wallets
  • O(log n) verification

T

Timelock (nLockTime)

Restricts a transaction from being confirmed before a specific time.

Types:

  • Timestamp-based (≥ 500,000,000)
  • Block height-based (< 500,000,000)

Applications:

  • Time deposits
  • Inheritance
  • Payroll disbursement

Transaction (TX)

The basic unit of value transfer.

Contains:

  • Inputs (which UTXOs to spend)
  • Outputs (which new UTXOs to create)
  • Timestamp
  • Fee

U

UTXO (Unspent Transaction Output)

An unspent transaction output, representing bitcoin that can be spent.

Lifecycle:

  1. Created (as a transaction output)
  2. Exists (in the UTXO set)
  3. Spent (referenced by a transaction input)
  4. Removed (deleted from the UTXO set)

Balance: the sum of all UTXOs


W

Wallet

Software that manages private keys, public keys, and addresses.

Types:

  • Hot wallet (online)
  • Cold wallet (offline)
  • Hardware wallet
  • Paper wallet

Functions:

  • Generate key pairs
  • Create addresses
  • Sign transactions
  • Query balances

Numbers

51% Attack

An attack in which an attacker controls more than 50% of the network’s hash power, enabling rewriting of blockchain history.

Consequences:

  • Double-spend attacks
  • Blocking transaction confirmations

Defense: Bitcoin’s hash rate is so large that the cost of such an attack is prohibitively high.


6 Confirmations

The standard number of confirmations for a Bitcoin transaction to be considered secure.

Time: approximately 60 minutes (6 blocks × 10 minutes)

Reason: After 6 blocks, rewriting history is virtually impossible.


Reference Resources


Back to Documentation Home | Basic Concepts

Bitcoin Principles

This appendix is a standalone educational article for readers who want to understand the underlying principles of Bitcoin. No programming background is required, though some familiarity with cryptography and distributed systems will be helpful.


Introduction

On October 31, 2008, a person using the pseudonym “Satoshi Nakamoto” published a 9-page paper on a cryptography mailing list: Bitcoin: A Peer-to-Peer Electronic Cash System. The paper proposed a revolutionary answer to the question: How can two strangers transfer value without a trusted third party (such as a bank)?

Bitcoin’s answer rests on four core technical pillars: hash functions, public-key cryptography, proof of work, and the blockchain data structure. This chapter introduces each of these principles in turn.


I. Hash Functions and SHA-256

What Is a Hash Function?

A hash function is a mathematical function that maps data of arbitrary length to a fixed-length “digest.” Bitcoin uses SHA-256 (Secure Hash Algorithm 256-bit), which always produces a 256-bit (32-byte) output, typically represented as 64 hexadecimal characters.

SHA-256("Hello, Bitcoin!") =
  a3b5c7d2e1f0... (64 hexadecimal characters)

SHA-256("Hello, Bitcoin.")  =
  f9e8d7c6b5a4... (completely different hash)

Four Key Properties of Hash Functions

1. Deterministic The same input always produces the same output. There is no randomness.

2. Avalanche Effect A tiny change in the input (even a single bit) causes a large, unpredictable change in the output. This ensures the hash is highly sensitive to even minor modifications.

3. One-Way (Preimage Resistance) It is computationally infeasible to reverse-engineer the original input from a hash output. Even knowing the SHA-256 output, recovering the input by brute force within the lifetime of the universe is impossible (the search space is 2²⁵⁶).

4. Collision Resistance It is computationally infeasible to find two different inputs that produce the same output (a hash collision). This is the foundation of the Bitcoin blockchain’s tamper resistance.

SHA-256 Applications in Bitcoin

Bitcoin uses SHA-256 (or double SHA-256, i.e., SHA-256(SHA-256(data))) in several places:

ApplicationHash MethodPurpose
Block hashSHA-256(SHA-256(block header))Unique block identifier, links the blockchain
Transaction IDSHA-256(SHA-256(transaction data))Unique transaction identifier
Address generationRIPEMD-160(SHA-256(public key))Derives a Bitcoin address from a public key
Merkle treeSHA-256(SHA-256(node concatenation))Efficient verification of a transaction set
MiningSHA-256(SHA-256(block header))Finds a nonce satisfying the difficulty target

Why Double SHA-256?

Bitcoin uses SHA-256 twice rather than once, primarily to defend against length extension attacks. SHA-256’s mathematical structure has a weakness: knowing SHA-256(M), one can compute SHA-256(M || X) (for arbitrary appended data X) without knowing M. Double SHA-256 eliminates this security concern.


II. Public-Key Cryptography

Symmetric vs. Asymmetric Encryption

Traditional symmetric encryption (such as AES) uses the same key for both encryption and decryption. The problem is that if Alice wants to send an encrypted message to Bob, she first needs to securely transmit the key to Bob — but if a secure channel already exists for that, why not use it directly for the message?

Asymmetric encryption (public-key cryptography) solves this “key distribution problem.” Each user has two keys:

  • Public Key: can be shared openly with anyone
  • Private Key: must be kept strictly secret and never shared

Their relationship is: a public key can be derived from a private key, but a private key cannot be reverse-engineered from a public key.

Private key (a random 256-bit number)
    │
    ▼ (one-way, irreversible)
Public key (a point on an elliptic curve)
    │
    ▼ (one-way, irreversible)
Bitcoin address (hash of the public key)

Elliptic Curve Cryptography (ECC) and secp256k1

Bitcoin uses the Elliptic Curve Digital Signature Algorithm (ECDSA) with a specific curve called secp256k1. This curve is defined by the equation:

y² = x³ + 7  (over the finite field Fp, where p = 2²⁵⁶ − 2³² − 977)

The security of elliptic curve cryptography is based on the Elliptic Curve Discrete Logarithm Problem (ECDLP): given a generator point G on the curve and a point P = k·G, it is computationally infeasible to recover the integer k from P.

Why secp256k1 rather than the more common secp256r1 (NIST P-256)?

Satoshi Nakamoto chose secp256k1 parameters that were derived from a deterministic formula rather than generated randomly, making it less likely to contain a backdoor inserted by the NSA — a concern that is debated but worth considering in the cryptography community. The coefficients of secp256k1 are very simple (a=0, b=7), with no complex “seemingly random” parameters, providing greater transparency.

Key generation process:

1. Generate a private key: randomly select a 256-bit integer k (1 ≤ k ≤ n−1, where n is the curve order)
   Private key = random number k (typically from a cryptographically secure random number generator)

2. Generate a public key: compute elliptic curve point multiplication
   Public key = k × G (G is the standard base point of secp256k1)
   Note: point multiplication is a special operation defined on the elliptic curve, not ordinary multiplication

3. Generate an address (simplified):
   Address = RIPEMD-160(SHA-256(public key)) + checksum

The randomness of the private key is critical. Documented theft cases show that private keys generated using weak random number generators (such as timestamps) have been brute-forced. Truly secure private keys come from the operating system’s cryptographic random number interface (e.g., /dev/urandom on Linux).


III. Digital Signatures

The Role of Signatures

Digital signatures solve the most fundamental problem in Bitcoin: How do you prove you have the right to spend some funds without revealing your private key?

By analogy with the real world: you sign a cheque and the bank verifies the signature is yours. A digital signature is the cryptographic equivalent of this process, but more secure — it does not rely on the visual appearance of the signature (which can be forged), but on a cryptographic proof that is mathematically impossible to forge.

The ECDSA Signing Process

Signing:

Input: message M (hash of transaction data), private key k
Output: signature (r, s)

Steps:
1. Generate a random number r_rand (must be different for each signature!)
2. Compute the curve point R = r_rand × G
3. r = R.x mod n (take the x-coordinate of R)
4. s = r_rand⁻¹ × (hash(M) + k × r) mod n

Verification:

Input: message M, signature (r, s), public key P = k × G
Output: valid / invalid

Steps:
1. u1 = hash(M) × s⁻¹ mod n
2. u2 = r × s⁻¹ mod n
3. Compute point Q = u1 × G + u2 × P
4. Verify Q.x mod n == r

The verification process uses only the public key and does not require the private key. This means anyone can verify a signature, but only the holder of the private key can create a valid signature.

Critical Security Requirement: The Random Number Must Not Be Reused

The random number r_rand in the signing algorithm must be unique and unpredictable for each signature. In 2013, the ECDSA implementation in the PlayStation 3 was broken because it used a fixed random number, leading to private key exposure. Similar cases have occurred in Bitcoin history.

Modern implementations (including Bitcoin Core) use RFC 6979, which deterministically generates the random number from the private key and message, completely eliminating the risk of random number reuse.


IV. Proof-of-Work Consensus

The Byzantine Generals Problem

In a distributed system, how can consensus be reached if nodes may send incorrect information (whether due to malice or failure)? This is known as the Byzantine Generals Problem, formally introduced by Lamport, Shostak, and Pease in 1982.

The classical conclusion: in a traditional message-passing model, if there are f Byzantine nodes, at least 3f+1 total nodes are required to tolerate the faults. However, this result has a prerequisite: communication costs are negligible.

Satoshi Nakamoto’s insight was that in Bitcoin, speaking on the network requires a real physical cost (electricity), which fundamentally changes the game-theoretic equilibrium.

Proof of Work

Proof of Work requires miners to find a special number (nonce) such that the hash of the block header satisfies a specific condition (beginning with a certain number of zeros):

Target: SHA-256(SHA-256(block header)) < target value

Equivalently: the block header hash begins with `difficulty` leading zeros

Example (difficulty=4):
0000a3f7d2e1b5c8...  ← valid (starts with 4 zeros)
0001a3f7d2e1b5c8...  ← invalid (4th character is not 0)

Miners repeatedly modify the nonce and recompute the hash until a valid value is found:

while SHA-256(SHA-256(block header || nonce)) >= target:
    nonce += 1  // try the next number

Once found, broadcast this block to the entire network

Difficulty adjustment: Bitcoin automatically adjusts the difficulty every 2016 blocks (approximately two weeks), targeting an average block time of 10 minutes. If hash power increases, the difficulty rises; if hash power decreases, the difficulty falls.

Why Does PoW Prevent Double Spending?

Suppose an attacker attempts to double-spend:

  1. Sends transaction A to a merchant (paying 10 BTC)
  2. The merchant waits for N block confirmations before shipping
  3. The attacker secretly mines on a separate chain, creating blocks containing transaction B (sending the 10 BTC back to themselves)

The attacker needs to secretly outpace the honest miners who have already mined N blocks, producing a longer chain. If the attacker controls a fraction α of the hash power (α < 0.5), their probability of success decreases exponentially as N increases. Satoshi Nakamoto proved in the whitepaper that:

P(success) ≈ (α / (1−α))^N

When α = 0.3 (30% hash power) and N = 6 (6 confirmations, approximately 1 hour):

P(success) ≈ (0.3/0.7)^6 = (0.4286)^6 ≈ 0.0006 = 0.06%

This is the mathematical basis for Bitcoin’s “6 confirmations” rule.

PoW vs. Other Consensus Mechanisms

Consensus MechanismRepresentative ProjectsAdvantagesDisadvantages
Proof of Work (PoW)Bitcoin, LitecoinNo need to trust participants, Sybil attack resistantHigh energy consumption, slow block time
Proof of Stake (PoS)Ethereum 2.0, CardanoLow energy consumption, good scalabilityInitial distribution fairness issues, “nothing-at-stake” problem
Delegated Proof of Stake (DPoS)EOS, TronHigh throughputCentralization risk, 21 nodes
Practical Byzantine Fault Tolerance (PBFT)HyperledgerEfficient, has finalitySuitable only for consortium chains with known participants

V. Blockchain Data Structure

Block Structure

Each block consists of two parts:

Block header (80 bytes):

Version      (4 bytes)  - protocol version
Previous hash(32 bytes) - links this block to the previous one
Merkle root  (32 bytes) - root hash of the Merkle tree of all transactions
Timestamp    (4 bytes)  - Unix timestamp
Difficulty   (4 bytes)  - current mining difficulty (compact format)
Nonce        (4 bytes)  - value adjusted by the miner

Block body (variable size):

Transaction count (varint)
Transaction list  [Transaction...]
  ├── Transaction 1 (coinbase, miner reward)
  ├── Transaction 2
  └── ...

Chain Structure and Tamper Resistance

The “chain” in blockchain comes from each block header containing the hash of the previous block:

Block 0 (genesis)              Block 1                     Block 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)      │
└──────────────────┘    └──────────────────┘    └──────────────────┘

If an attacker modifies a transaction in Block 1:

  1. Block 1’s Merkle root changes
  2. Block 1’s block header hash changes
  3. Block 2’s prev_hash field no longer matches Block 1’s new hash
  4. The attacker must redo the proof of work to compute Block 2’s nonce
  5. This invalidates Block 3, requiring it to be mined again…
  6. The attacker must recompute the PoW for every block from the tampered one to the chain tip

Because honest miners are continuously extending the chain, the attacker would need to complete this work faster than the entire network’s hash power. Under the assumption of 51% honest hash power, this is an impossible task.

The UTXO Set: The “State” of the Blockchain

The full blockchain records all historical transactions, but validating new transactions only requires knowing “which outputs are currently unspent” — the UTXO set (Unspent Transaction Output Set).

The UTXO set is the “ledger state” obtained by sequentially processing all transactions starting from the genesis block. As of 2024, the Bitcoin UTXO set contains approximately 110 million entries, occupying about 5–6 GB of memory, far smaller than the full blockchain at 600+ GB.

Blockchain (historical record, ~600 GB)
    ↓ Full node processes sequentially
UTXO set (current state, ~5 GB)
    ↓ Query
Validate whether new transactions are valid

VI. Decentralization and Network Security

P2P Network

Bitcoin nodes connect to each other through a peer-to-peer (P2P) network, with no central server. Each node establishes connections to dozens of peers, forming a small-world network.

New transactions propagate through the network via a Gossip protocol:

  1. Node A creates a transaction and broadcasts it to connected nodes
  2. Each node that receives the transaction validates it, then forwards it to its own connected nodes
  3. The transaction spreads to most nodes worldwide within seconds

Economic Analysis of a 51% Attack

Attacking the Bitcoin network requires controlling more than 50% of the global hash rate. As of 2024, Bitcoin’s global hash rate is approximately 600 EH/s. Purchasing or renting 50% of that hash power would require billions of dollars in hardware investment, plus ongoing electricity costs.

More critically, there is the economic incentive problem of the attack:

  • Potential gain from a successful attack: double-spending in a large transaction, possibly defrauding an exchange
  • Cost of the attack: Bitcoin’s price collapses, and the attacker’s bitcoin holdings and mining equipment drop sharply in value
  • Conclusion: a rational economic actor would prefer to use their hash power to mine honestly (approximately $20 million per day in revenue) rather than launch an attack with limited upside and extreme risk

This economically self-reinforcing security is the elegance of Bitcoin’s design — security automatically strengthens as the network’s value grows.

Node Types and Network Roles

Node TypeDescriptionTypical Use Case
Full nodeStores the full blockchain, independently validates all rulesExchanges, node operators
Pruned nodeRetains only recent blocks, saves disk spaceHome users
SPV nodeDownloads only block headers, lightweight verificationMobile wallets
Mining pool nodeCoordinates large numbers of miners, allocates hash powerMining farm operators
Lightning Network nodeManages payment channels, enables instant micropaymentsMerchants, everyday payments

Further Reading and References

The following are important references for a deeper understanding of Bitcoin’s technical principles:

Foundational Papers

  1. Nakamoto, S. (2008). Bitcoin: A Peer-to-Peer Electronic Cash System. https://bitcoin.org/bitcoin.pdf The Bitcoin whitepaper, 9 pages, covering all core concepts. Essential reading.

  2. Merkle, R. C. (1979). Secrecy, Authentication, and Public Key Systems. Stanford University doctoral dissertation, the original paper on Merkle trees.

  3. Lamport, L., Shostak, R., & Pease, M. (1982). The Byzantine Generals Problem. ACM Transactions on Programming Languages and Systems. The classical formal description of the distributed consensus problem.

  4. Back, A. (2002). Hashcash – A Denial of Service Counter-Measure. http://www.hashcash.org/papers/hashcash.pdf The predecessor to Bitcoin’s PoW mechanism, originally designed to prevent email spam.

  5. Dai, W. (1998). b-money. http://www.weidai.com/bmoney.txt An early decentralized digital currency proposal cited by Satoshi Nakamoto.

Cryptography Fundamentals

  1. Johnson, D., Menezes, A., & Vanstone, S. (2001). The Elliptic Curve Digital Signature Algorithm (ECDSA). International Journal of Information Security. The authoritative technical specification for ECDSA.

  2. Pornin, T. (2013). RFC 6979: Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA). IETF Request for Comments. The standard for deterministic signature nonce generation, eliminating the nonce reuse vulnerability.

  3. National Institute of Standards and Technology. (2015). FIPS PUB 180-4: Secure Hash Standard. The official specification document for SHA-256.

Books

  1. Antonopoulos, A. M. (2017). Mastering Bitcoin: Programming the Open Blockchain (2nd ed.). O’Reilly Media. The most comprehensive introductory book on Bitcoin technology; the open-source version is freely available.

  2. Song, J. (2019). Programming Bitcoin. O’Reilly Media. Implements the Bitcoin protocol from scratch in Python; ideal for hands-on learning.

  3. Narayanan, A., Bonneau, J., Felten, E., Miller, A., & Goldfeder, S. (2016). Bitcoin and Cryptocurrency Technologies. Princeton University Press. Free PDF available, a comprehensive academic analysis.

Advanced Resources

  1. Bitcoin Improvement Proposals (BIPs). https://github.com/bitcoin/bips All improvement proposals for the Bitcoin protocol, including SegWit (BIP141), RBF (BIP125), HD Wallets (BIP32), and more.

  2. Bitcoin Core source code. https://github.com/bitcoin/bitcoin The reference implementation of Bitcoin, written in C++, approximately 150,000 lines of code.


Summary: Bitcoin’s Five-Layer Architecture

Layer 5: Economic Incentive Layer
         PoW rewards (block reward + fees) → drives honest miner behavior
              ↑
Layer 4: Consensus Layer
         Longest-chain rule + PoW difficulty adjustment → network-wide agreement on "which chain is correct"
              ↑
Layer 3: Data Structure Layer
         Blockchain (chained blocks) + Merkle tree → tamper-resistant transaction history
              ↑
Layer 2: Transaction Layer
         UTXO model + Script system → defines the rules for value transfer
              ↑
Layer 1: Cryptography Layer
         SHA-256 (integrity) + ECDSA (authentication) → trustless mathematical guarantees

Bitcoin’s revolution does not lie in any single technical innovation — SHA-256, elliptic curve cryptography, P2P networks, hash chains, and PoW all existed before. Satoshi Nakamoto’s genius was in combining these known technologies in a specific way to create a self-consistent, game-theoretically stable decentralized monetary system.

The SimpleBTC project implements a teaching version of this system, helping you understand how each layer works through runnable code. Reading this article alongside the source code is recommended, combining theory with practice.

Frequently Asked Questions (FAQ)

Installation and Configuration

Q: How do I install SimpleBTC?

A:

# 1. Make sure Rust is installed
rustc --version

# 2. Clone the project
git clone https://github.com/GeoffreyWang1117/SimpleBTC.git
cd SimpleBTC

# 3. Build
cargo build --release

# 4. Run
cargo run --bin btc-demo

See the Installation Guide for details.


Q: I get a “linker ‘cc’ not found” error when compiling.

A: You need to install a C compiler:

# Ubuntu/Debian
sudo apt-get install build-essential

# macOS
xcode-select --install

# Windows
# Install Visual Studio Build Tools

Q: How do I change the mining difficulty?

A: Edit src/blockchain.rs:

#![allow(unused)]
fn main() {
pub fn new() -> Blockchain {
    Blockchain {
        difficulty: 3,  // change this value
        // 3-4 is good for demos
        // 5-6 is more secure but slower
        // ...
    }
}
}

Basic Concepts

Q: What is UTXO? Why not use account balances?

A: UTXO (Unspent Transaction Output) is a core Bitcoin concept.

Account model (Ethereum):

Alice account: 100 BTC
After transfer:
Alice: 70 BTC
Bob: 30 BTC

UTXO model (Bitcoin):

Alice has UTXOs: [50 BTC, 30 BTC, 20 BTC]
Transferring 30 BTC to Bob:
  - Consume the 50 BTC UTXO
  - Create 30 BTC output for Bob
  - Create 20 BTC change output for Alice (50 - 30)

Advantages:

  • Better privacy (use a new address each time)
  • Parallel processing (different UTXOs can be handled concurrently)
  • Simpler validation logic

See Basic Concepts - UTXO for details.


Q: Why do transactions need fees?

A: Transaction fees serve several purposes:

  1. Prevent spam attacks — sending a transaction has a cost
  2. Incentivize miners — miners prioritize transactions with higher fee rates
  3. Resource allocation — when the network is congested, those willing to pay more get priority

Fee calculation:

#![allow(unused)]
fn main() {
fee = total inputs - total outputs

// Example
inputs: 100 satoshi
outputs: 90 satoshi
fee: 10 satoshi
}

Fee rate recommendations:

  • 1–5 sat/byte: low priority (several hours)
  • 10–20 sat/byte: medium priority (30–60 minutes)
  • 50+ sat/byte: high priority (next block)

Q: What is Proof of Work (PoW)? Why is mining necessary?

A: PoW is Bitcoin’s consensus mechanism.

Mining process:

#![allow(unused)]
fn main() {
target = "000..."  // difficulty requirement

while hash(block_data + nonce) >= target {
    nonce++;  // keep trying
}
// Found a valid nonce; the block is accepted
}

Why it is needed:

  • Prevents spam blocks (creating a block requires computational cost)
  • Decentralized consensus (hash power as votes)
  • Extremely high cost for a 51% attack (requires more than half the global hash power)

Difficulty and time:

  • Difficulty 3: milliseconds (demo)
  • Difficulty 10: seconds (private chain)
  • Difficulty 20: minutes (Bitcoin scale)

See Basic Concepts - PoW for details.


Usage Questions

Q: How do I create a wallet?

A:

#![allow(unused)]
fn main() {
use bitcoin_simulation::wallet::Wallet;

// Create a new wallet
let wallet = Wallet::new();

println!("Address: {}", wallet.address);
println!("Public key: {}", wallet.public_key);
// Keep the private key secret!
}

Important:

  • Losing the private key = permanent loss of bitcoin
  • Exposing the private key = bitcoin theft
  • Back up the private key to a secure location

Q: How do I send a transfer?

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. Create the transaction
let tx = blockchain.create_transaction(
    &alice,           // sender
    bob.address,      // recipient
    1000,            // amount (satoshi)
    10,              // fee
)?;

// 2. Add to the pending pool
blockchain.add_transaction(tx)?;

// 3. Mine to confirm
blockchain.mine_pending_transactions(miner.address)?;
}

Q: What do I do if my balance is insufficient?

A: Check the following:

  1. Query balance:
#![allow(unused)]
fn main() {
let balance = blockchain.get_balance(&address);
println!("Balance: {}", balance);
}
  1. Ensure you have UTXOs:
#![allow(unused)]
fn main() {
let utxos = blockchain.utxo_set.find_utxos(&address);
println!("UTXO count: {}", utxos.len());
}
  1. Check that the fee is included:
#![allow(unused)]
fn main() {
let total_needed = amount + fee;
if balance < total_needed {
    return Err("Insufficient balance (including fee)");
}
}
  1. Wait for transaction confirmation: A recently sent transaction must be confirmed by mining before its outputs can be used.

Q: What if a transaction remains unconfirmed for a long time?

A: Possible causes and solutions:

Cause 1: Fee too low

#![allow(unused)]
fn main() {
// Increase the fee
let tx = blockchain.create_transaction(
    &alice,
    bob.address,
    1000,
    50,  // higher fee
)?;
}

Cause 2: No miner is mining

# Mine manually
cargo run --bin btc-demo
# Or in code
blockchain.mine_pending_transactions(miner.address)?;

Cause 3: Transaction is invalid

#![allow(unused)]
fn main() {
// Validate the transaction
if !tx.verify() {
    println!("Transaction invalid, check:");
    println!("- Whether the input UTXOs exist");
    println!("- Whether the signature is correct");
    println!("- Whether the balance is sufficient");
}
}

Use RBF to speed up:

#![allow(unused)]
fn main() {
// Create a replacement transaction with a higher fee rate
let faster_tx = blockchain.create_transaction(
    &alice,
    bob.address,
    1000,
    100,  // higher fee
)?;
}

Advanced Features

Q: How do I use multisig?

A:

#![allow(unused)]
fn main() {
use bitcoin_simulation::multisig::MultiSigAddress;

// 1. Create participant wallets
let alice = Wallet::new();
let bob = Wallet::new();
let charlie = Wallet::new();

// 2. Create a 2-of-3 multisig address
let multisig = MultiSigAddress::new(
    2,  // requires 2 signatures
    vec![
        alice.public_key,
        bob.public_key,
        charlie.public_key,
    ]
)?;

// 3. Send funds to the multisig address
let tx = blockchain.create_transaction(
    &funder,
    multisig.address.clone(),
    10000,
    0,
)?;

// 4. Spend from the multisig address (requires 2 signatures)
let alice_sig = alice.sign(&payment_data);
let bob_sig = bob.sign(&payment_data);

if vec![alice_sig, bob_sig].len() >= multisig.required_sigs {
    // Execute the transaction
}
}

See the Multisig Tutorial for details.


Q: What is a Merkle tree? What is it used for?

A: A Merkle tree is a hash tree of transactions, stored in the block header.

Structure:

        Root Hash
       /         \
     H(AB)      H(CD)
    /    \      /    \
  H(A)  H(B)  H(C)  H(D)
   tx1   tx2   tx3   tx4

Uses:

  1. SPV verification — light wallets do not need to download the full block
#![allow(unused)]
fn main() {
// Only needs block header + Merkle proof
let proof = merkle_tree.get_proof(&tx_hash)?;
let valid = MerkleTree::verify_proof(
    &tx_hash,
    &proof,
    &block.merkle_root,
    tx_index
);
}
  1. Data integrity — any change to a transaction changes the root hash

  2. Efficient verification — O(log n) complexity

See the Merkle Tree Tutorial for details.


Q: What is a timelock? How do I use one?

A: A timelock restricts a transaction from being confirmed before a specific time.

Two types:

  1. Timestamp-based:
#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::TimeLock;

// Unlock after 3 months
let three_months = 90 * 24 * 3600;
let unlock_time = current_time + three_months;
let timelock = TimeLock::new_time_based(unlock_time);

// Check if it has matured
if timelock.is_mature(current_time, 0) {
    println!("Matured, funds can be spent");
}
}
  1. Block height-based:
#![allow(unused)]
fn main() {
// Unlock after block 100,000
let timelock = TimeLock::new_block_based(100000);

if timelock.is_mature(current_time, current_block_height) {
    println!("Block height reached");
}
}

Use cases:

  • Time deposits
  • Inheritance
  • Payroll disbursement
  • Project vesting periods

See the Timelock Tutorial for details.


Development Questions

Q: How do I integrate SimpleBTC into my project?

A: SimpleBTC can be used as a library:

# Cargo.toml
[dependencies]
bitcoin_simulation = { path = "../SimpleBTC" }
#![allow(unused)]
fn main() {
// In your code
use bitcoin_simulation::{
    blockchain::Blockchain,
    wallet::Wallet,
};

fn my_app() {
    let blockchain = Blockchain::new();
    // ... your business logic
}
}

Q: How do I use the REST API?

A:

Start the server:

cargo run --bin btc-server
# Server runs at http://localhost:3000

API call examples:

# Create a wallet
curl -X POST http://localhost:3000/api/wallet/create

# Create a transaction
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
  }'

# Query balance
curl http://localhost:3000/api/balance/alice_address

# Mine
curl -X POST http://localhost:3000/api/mine \
  -H "Content-Type: application/json" \
  -d '{"miner_address": "miner_address"}'

See the REST API documentation for details.


Q: How do I run tests?

A:

# Run all tests
cargo test

# Run a specific test
cargo test test_blockchain

# Show output
cargo test -- --nocapture

# Run examples
cargo run --example enterprise_multisig
cargo run --example escrow_service
cargo run --example timelock_savings

Q: How do I deploy the documentation site?

A:

Local preview:

cd docs
mdbook serve --open

GitHub Pages deployment:

# Build
mdbook build

# Deploy to the gh-pages branch
# See docs/README.md

Docker deployment:

FROM nginx:alpine
COPY docs/book /usr/share/nginx/html
EXPOSE 80

See the Documentation Deployment Guide for details.


Performance Questions

Q: Mining is too slow. What can I do?

A: Adjust the difficulty:

#![allow(unused)]
fn main() {
// In blockchain.rs
blockchain.difficulty = 3;  // lower the difficulty
// 3: milliseconds
// 4: seconds
// 5: several seconds
// 6+: may be very slow
}

Or use Release mode:

cargo run --release --bin btc-demo
# Release mode is much faster than Debug

Q: Balance queries are slow. How can I speed them up?

A: Use the built-in indexer:

#![allow(unused)]
fn main() {
// SimpleBTC has a built-in indexer
let txs = blockchain.indexer.get_transactions_by_address(&address);

// Or cache balances
let balance_cache: HashMap<String, u64> = HashMap::new();
}

Security Questions

Q: Is SimpleBTC secure? Can it be used in production?

A: SimpleBTC is an educational project and is not recommended for production use.

Differences from real Bitcoin:

  • Simplified cryptography (SHA256 instead of ECDSA)
  • No P2P network layer
  • Simplified script system
  • No full SPV implementation
  • JSON storage (should use LevelDB)

Requirements for production use:

  • Implement full secp256k1 elliptic curve
  • Implement ECDSA signature verification
  • Add a P2P network protocol
  • Use a professional database
  • Full Script engine
  • Security audit

Q: How do I protect my private key?

A: Private key security recommendations:

  1. Never share the private key
  2. Multiple backups:
    • Paper wallet (fire- and water-resistant)
    • Hardware wallet
    • Encrypted USB drive
  3. Distributed storage:
    • Home safe
    • Bank safe deposit box
    • Offsite backup
  4. Use multisig:
    • 2-of-3 reduces single-point-of-failure risk
  5. Regularly test recovery

Other Questions

Q: What are the differences between SimpleBTC and real Bitcoin?

A:

FeatureSimpleBTCReal Bitcoin
CryptographySHA256 (simplified)secp256k1 ECDSA
ConsensusPoW (simplified)PoW (full)
ScriptSimplifiedFull Script language
NetworkNoneP2P network
StorageJSONLevelDB
Difficulty adjustmentFixedEvery 2016 blocks

Value of SimpleBTC:

  • Learn Bitcoin principles
  • Understand the UTXO model
  • Practice blockchain development
  • Rapid prototype validation

Q: How do I contribute code?

A:

  1. Fork the project
  2. Create a feature branch
  3. Submit a Pull Request
  4. Wait for review

See the Contributing Guide for details.


Q: What do I do if I find a bug?

A:

  1. Open an issue on GitHub: https://github.com/GeoffreyWang1117/SimpleBTC/issues

  2. Include the following information:

    • Operating system
    • Rust version
    • Error message
    • Steps to reproduce
    • Relevant code

Q: Where can I get help?

A:

  • Documentation: this site
  • GitHub Issues: report problems and suggestions
  • Rust community: https://users.rust-lang.org/
  • Bitcoin whitepaper: https://bitcoin.org/bitcoin.pdf

More Resources


Didn’t find your question? Open an issue on GitHub

Contributing Guide

Thank you for your interest in the SimpleBTC project! All forms of contribution are welcome.

Ways to Contribute

1. Report Bugs

Submit a bug report on GitHub Issues.

Include the following information:

  • Operating system and version
  • Rust version (rustc --version)
  • Error message
  • Steps to reproduce
  • Relevant code snippets

2. Suggest Features

Submit a feature request in Issues, explaining:

  • Feature description
  • Use case
  • Implementation ideas (optional)

3. Contribute Code

  1. Fork the project
# Fork on GitHub
# Clone your fork
git clone https://github.com/YOUR_USERNAME/SimpleBTC.git
cd SimpleBTC
  1. Create a feature branch
git checkout -b feature/your-feature-name
  1. Write code

    • Follow the Rust style guide
    • Add tests
    • Update documentation
  2. Submit a Pull Request

git add .
git commit -m "Add: your feature description"
git push origin feature/your-feature-name

Create a Pull Request on GitHub.

4. Improve Documentation

Documentation matters just as much as code!

  • Fix errors
  • Add examples
  • Improve explanations
  • Translate documentation

Development Guide

Code Style

# Format code
cargo fmt

# Run lint checks
cargo clippy

Testing

# Run all tests
cargo test

# Add tests
#[cfg(test)]
mod tests {
    #[test]
    fn test_something() {
        // ...
    }
}

Documentation Comments

#![allow(unused)]
fn main() {
/// Brief description of the function
///
/// Detailed explanation...
///
/// # Parameters
/// * `param1` - parameter description
///
/// # Return Value
/// Description of the return value
///
/// # Examples
/// \```
/// let result = function(arg);
/// \```
pub fn function(param1: Type) -> ReturnType {
    // ...
}
}

Pull Request Checklist

Before submitting a PR, ensure:

  • Code is formatted with cargo fmt
  • Code passes cargo clippy checks
  • All tests pass with cargo test
  • Necessary tests have been added
  • Relevant documentation has been updated
  • Commit messages are clear and descriptive

Community Guidelines

  • Be friendly and respectful
  • Keep discussions constructive
  • Welcome newcomers
  • Stay focused on technical topics

License

Code contributed to this project will be released under the project’s MIT license.


Thank you for your contribution!