Proof of Work (PoW)

Understanding and implementing Proof of Work consensus mechanism.

Advanced⏱️ 50 min📚 Prerequisites: 1

Proof of Work (PoW)

Proof of Work is a consensus mechanism where miners compete to solve a cryptographic puzzle. The first to solve it gets to create the next block.

How PoW Works

  1. Mining: Miners try different nonce values
  2. Hashing: Calculate hash of (block data + nonce)
  3. Difficulty: Hash must be below target (start with zeros)
  4. Success: When hash meets difficulty, block is valid
  5. Reward: Miner receives block reward

Difficulty Target

The difficulty determines how many leading zeros the hash must have:

RUST
// Hash must start with N zeros
// Difficulty 1: hash starts with "0"
// Difficulty 4: hash starts with "0000"

Mining Process

RUST
struct Block {
    index: u64,
    data: String,
    previous_hash: String,
    nonce: u64,  // Number used once
    hash: String,
}

fn mine_block(mut block: Block, difficulty: usize) -> Block {
    let prefix = "0".repeat(difficulty);
    
    loop {
        block.nonce += 1;
        block.hash = calculate_hash(&block);
        
        if block.hash.starts_with(&prefix) {
            return block;  // Found valid hash!
        }
    }
}

Bitcoin's PoW

  • Uses SHA-256 double hashing
  • Difficulty adjusts every 2016 blocks
  • Target: ~10 minutes per block
  • Energy intensive but secure

Advantages

  • Security: Expensive to attack (51% attack)
  • Decentralized: Anyone can mine
  • Proven: Bitcoin has been secure for years

Disadvantages

  • Energy consumption: Very high
  • Centralization: Mining pools dominate
  • Slow: 10 minutes per block (Bitcoin)

Alternative: Proof of Stake

Many newer blockchains use PoS instead of PoW for better efficiency.

Code Examples

Mining Process

Simple Proof of Work mining

RUST
struct Block {
    index: u64,
    data: String,
    previous_hash: String,
    nonce: u64,
    hash: String,
}
fn calculate_hash(block: &Block) -> String {
    let data = format!("{}{}{}{}",
                      block.index,
                      block.data,
                      block.previous_hash,
                      block.nonce);
    format!("hash_{}", data)
}
fn mine_block(mut block: Block, difficulty: usize) -> Block {
    let prefix = "0".repeat(difficulty);
    println!("Mining block... (difficulty: {} zeros)", difficulty);
    loop {
        block.nonce += 1;
        block.hash = calculate_hash(&block);
        if block.hash.starts_with(&prefix) {
            println!("Block mined! Nonce: {}, Hash: {}", block.nonce, block.hash);
            return block;
        }
        if block.nonce > 10000 {
            println!("Stopping after 10000 attempts");
            break;
        }
    }
    block
}
fn main() {
    let block = Block {
        index: 1,
        data: String::from("Transaction data"),
        previous_hash: String::from("prev_hash"),
        nonce: 0,
        hash: String::from(""),
    };
    let mined = mine_block(block, 2);
    println!("Final hash: {}", mined.hash);
}

Explanation:

Mining involves trying different nonce values until the hash meets the difficulty requirement. This requires computational work, hence 'Proof of Work'.

Difficulty Adjustment

Understanding difficulty in PoW

RUST
fn check_difficulty(hash: &str, difficulty: usize) -> bool {
    let prefix = "0".repeat(difficulty);
    hash.starts_with(&prefix)
}
fn main() {
    let hashes = vec![
        "0abc123",
        "00def456",
        "000ghi789",
        "abc123",
    ];
    for hash in hashes {
        println!("Hash: {}", hash);
        println!("  Difficulty 1: {}", check_difficulty(hash, 1));
        println!("  Difficulty 2: {}", check_difficulty(hash, 2));
        println!("  Difficulty 3: {}", check_difficulty(hash, 3));
        println!();
    }
}

Explanation:

Difficulty determines how many leading zeros the hash must have. Higher difficulty = more zeros = harder to find = more secure but slower.

Exercises

Mine a Block

Implement a simple mining function that finds a nonce!

Medium

Starter Code:

RUST
struct Block {
    data: String,
    nonce: u64,
    hash: String,
}
fn calculate_hash(data: &str, nonce: u64) -> String {
    format!("hash_{}_{}", data, nonce)
}
fn main() {
    let mut block = Block {
        data: String::from("test"),
        nonce: 0,
        hash: String::from(""),
    };
}