Proof of Work Explained

If you’ve heard of Bitcoin or blockchain, you’ve probably heard of Proof of Work (PoW) — the mechanism that makes blockchains secure and decentralized. But how does it actually work?

In this article, we'll explain PoW in simple terms and implement it in Go. You’ll see how it prevents spam and fraud in distributed systems and why it's the engine behind cryptocurrencies like Bitcoin.

What Is Proof of Work?

Proof of Work is a computational puzzle. It’s hard to solve, but easy to verify.

The idea is to add a new block to the blockchain, you must find a value (called a nonce) that makes the block’s hash start with a certain number of zeros.

This process is called mining. It:

  • Slows down block creation
  • Requires real-world computational effort (i.e., work)
  • Makes attacking the chain costly
A Simple Example

Let’s say we want a block’s SHA-256 hash to start with two zeros, like 00ab3f.... You can’t guess it directly. So you:

  • Try a nonce (number)
  • Hash the block + nonce
  • Check if the hash starts with 00
  • Repeat until you succeed

That’s Proof of Work!

Implementing PoW in Go

See the following section.

Go example

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "strconv"
    "strings"
    "time"
)

type Block struct {
    Index     int
    Timestamp string
    Data      string
    PrevHash  string
    Hash      string
    Nonce     int
}

func calculateHash(block Block) string {
    record := strconv.Itoa(block.Index) + block.Timestamp + block.Data + block.PrevHash + strconv.Itoa(block.Nonce)
    h := sha256.New()
    h.Write([]byte(record))
    return hex.EncodeToString(h.Sum(nil))
}

func mineBlock(block *Block, difficulty int) {
    target := strings.Repeat("0", difficulty)
    for {
        hash := calculateHash(*block)
        if strings.HasPrefix(hash, target) {
            block.Hash = hash
            return
        }
        block.Nonce++
    }
}

func createGenesisBlock(difficulty int) Block {
    block := Block{
        Index:     0,
        Timestamp: time.Now().String(),
        Data:      "Genesis Block",
        PrevHash:  "",
    }
    mineBlock(█, difficulty)
    return block
}

func generateNextBlock(prevBlock Block, data string, difficulty int) Block {
    block := Block{
        Index:     prevBlock.Index + 1,
        Timestamp: time.Now().String(),
        Data:      data,
        PrevHash:  prevBlock.Hash,
    }
    mineBlock(█, difficulty)
    return block
}

func main() {
    var blockchain []Block
    difficulty := 4 // Adjust for faster or slower mining

    genesis := createGenesisBlock(difficulty)
    blockchain = append(blockchain, genesis)

    for i := 1; i <= 3; i++ {
        fmt.Printf("Mining block #%d...\n", i)
        newBlock := generateNextBlock(blockchain[len(blockchain)-1], fmt.Sprintf("Block %d data", i), difficulty)
        blockchain = append(blockchain, newBlock)
    }

    for _, block := range blockchain {
        fmt.Printf("\nIndex: %d\nNonce: %d\nHash: %s\nPrevHash: %s\n", block.Index, block.Nonce, block.Hash, block.PrevHash)
    }
}
What Did We Build?

Each block is mined until its hash matches the required difficulty.

The Nonce is incremented until the hash is valid.

This simulates how Bitcoin and others ensure blocks aren’t created instantly or maliciously.

Why It Matters

Makes it hard to add blocks (security)

Makes it easy to verify blocks (speed)

Makes attacking the blockchain expensive (protection)