Blog Logo

16-Aug-2025 ~ 18 min read

Proof Of Work Demo


Proof of Work Demo Application

A demonstration of proof of work (PoW) functionality implemented in Go with a modern web interface. This application shows how PoW can be used to prevent abuse from automated bots and ensure legitimate user requests by requiring computational work before accessing protected resources.

πŸš€ Features

  • Modular PoW System: Abstracted proof of work functionality that can be easily integrated into other applications
  • Interactive Time App: Real-world example where users must solve a computational challenge to get server time
  • Configurable Difficulty: Adjustable challenge difficulty (currently set to 10 leading zero bits)
  • Real-time Progress: Visual feedback during challenge solving with progress bars and statistics
  • Modern UI: Responsive, mobile-friendly interface with real-time updates and status indicators
  • RESTful API: Clean API endpoints for integration and testing
  • HMAC-Signed Challenges: Secure challenge generation with cryptographic signatures to prevent tampering
  • Challenge Expiration: Built-in protection against replay attacks (5-minute expiry)
  • Web Worker Support: Client-side PoW solving using Web Workers for non-blocking computation

πŸ—οΈ Architecture

Backend (Go)

  • PoWVerifier: Core proof of work verification logic with HMAC signature validation
  • Challenge Generation: Timestamp-based challenges with random components and HMAC signatures
  • Hash Verification: SHA-256 based verification with configurable difficulty
  • ProtectedHandler Middleware: Extensible middleware for protecting endpoints with PoW verification
  • Challenge Expiry: Automatic expiration of challenges after 5 minutes
  • HTTP Server: Simple HTTP server serving static files and API endpoints

Frontend (HTML/JavaScript)

  • Challenge Solver: Client-side proof of work computation using Web Workers
  • Progress Tracking: Real-time nonce counting, time elapsed, and hash rate display
  • Error Handling: Comprehensive error handling and user feedback with status messages
  • Responsive Design: Modern, accessible user interface with CSS Grid and Flexbox
  • Status Indicators: Color-coded status messages for different states (info, success, error, warning)

πŸ“ File Structure

proof_of_work_demo/
β”œβ”€β”€ main.go                 # Go server with PoW endpoints and middleware
β”œβ”€β”€ pow.go                  # Core PoW implementation and verification logic
β”œβ”€β”€ static/
β”‚   β”œβ”€β”€ index.html         # Time app with PoW demo interface
β”‚   └── pow.js             # Client-side PoW solving logic with Web Workers
β”œβ”€β”€ Makefile               # Build and run commands
β”œβ”€β”€ go.mod                 # Go module definition
└── README.md              # This file

πŸš€ Quick Start

Prerequisites

  • Go 1.24.0 or later
  • Modern web browser with JavaScript enabled
  • Web Workers support (available in all modern browsers)

Running the Application

  1. Clone and navigate to the project:

    cd proof_of_work_demo
  2. Run the server:

    go run main.go pow.go

    Or use the Makefile:

    make run
  3. Open your browser:

    • Navigate to http://localhost:8082
    • The time app will be displayed with PoW demo functionality

🌐 Available Endpoints

EndpointMethodDescription
/GETTime app with proof of work demo
/get-challengeGETGet a new PoW challenge
/get-timePOSTGet server time (protected by PoW middleware)

πŸ”§ API Usage

1. Get Challenge

curl http://localhost:8082/get-challenge

Response:

{
  "challenge": "1703123456:abc123def456:hmac_signature",
  "difficulty": 10
}

2. Get Server Time (after PoW)

curl -X POST http://localhost:8082/get-time \
  -H "Content-Type: application/json" \
  -d '{
    "challenge": "1703123456:abc123def456:hmac_signature",
    "nonce": 12345,
    "hash": "000abc123def456..."
  }'

Response:

{
  "time": "2023-12-21 15:30:45",
  "timestamp": 1703123445,
  "server_time": "2023-12-21T15:30:45Z",
  "timezone": "Local"
}

🧩 How Proof of Work Works

1. Challenge Generation

  • Server generates a unique challenge string with:
    • Current timestamp
    • Random component (64-bit hex)
    • HMAC-SHA256 signature using a secret key
  • Format: timestamp:random:signature

2. Client Solving

  • Client receives the challenge and difficulty (10 leading zero bits)
  • Computes SHA-256 hashes with different nonces until finding one that meets the difficulty requirement
  • Uses Web Workers for non-blocking computation
  • Provides real-time progress updates

3. Verification

  • Server verifies the solution by:
    • Checking HMAC signature validity
    • Validating challenge timestamp (must be within 5 minutes)
    • Recomputing the hash with the provided nonce
    • Verifying the hash meets the difficulty requirement

4. Resource Access

  • Only after successful verification is the requested resource (server time) provided

Security Features

  • HMAC Signatures: Each challenge is cryptographically signed to prevent tampering
  • Challenge Expiry: Challenges expire after 5 minutes to prevent replay attacks
  • Timestamp Validation: Server validates challenge freshness
  • Random Components: Each challenge includes random data to prevent prediction

Difficulty Configuration

The difficulty is set to 10 bits, meaning the hash must start with 10 leading zero bits. This can be adjusted in the pow.go file:

const difficulty = 10 // Adjust this value to change challenge difficulty

πŸ”’ Security Considerations

  • HMAC Signatures: Challenges are cryptographically signed to prevent forgery
  • Challenge Expiration: Built-in 5-minute expiry prevents replay attacks
  • Secret Key Management: In production, store the secret key securely (not hardcoded)
  • Rate Limiting: Consider implementing rate limiting for challenge requests
  • Session Management: In production, implement proper session validation
  • Difficulty Adjustment: Dynamically adjust difficulty based on server load and attack patterns

πŸš€ Integration Examples

Using the ProtectedHandler Middleware

// Protect any endpoint with PoW verification
http.HandleFunc("/protected-endpoint", ProtectedHandler(yourHandler))

πŸ§ͺ Testing

Manual Testing

  1. Open the app in your browser at http://localhost:8082
  2. Click β€œGet Server Time”
  3. Watch the proof of work solving process with real-time progress
  4. Verify the server time is displayed after successful completion

API Testing

Use tools like curl, Postman, or your browser’s developer tools to test the API endpoints directly.

Automated Testing

The Makefile includes testing instructions:

make test

πŸ”§ Customization

Changing Difficulty

Edit the difficulty constant in pow.go:

const difficulty = 15 // More challenging (15 leading zero bits)

Modifying Challenge Expiry

Update the expiry time in pow.go:

const challengeExpirySeconds = 600 // 10 minutes

Adding New Protected Endpoints

Extend the server by adding new handlers protected with the ProtectedHandler middleware in main.go:

http.HandleFunc("/new-protected-endpoint", ProtectedHandler(yourNewHandler))

πŸ› οΈ Development

Available Make Commands

  • make run - Run the server (kills existing process first)
  • make dev - Run in development mode (kills existing process first)
  • make test - Show testing instructions
  • make build - Build the application
  • make clean - Clean build artifacts
  • make deps - Install dependencies
  • make help - Show available commands
  • make kill-server - Kill server process on port 8082

Server Configuration

  • Port: 8082 (configurable in Makefile via SERVER_PORT variable)
  • Host: localhost
  • Static Files: Served from ./static directory
  • Auto-restart: Make commands automatically kill existing processes

Development Workflow

  1. Make changes to Go files
  2. Use make dev to restart the server
  3. Refresh browser to see changes
  4. Use browser dev tools to debug frontend

πŸ“Š Performance Characteristics

  • Difficulty 10: Typically requires ~1000-2000 hash computations
  • Solving Time: Usually 1-5 seconds on modern devices
  • Hash Rate: Displays real-time hash rate during solving
  • Memory Usage: Minimal memory footprint using Web Workers

🌍 Browser Compatibility

  • Modern Browsers: Chrome, Firefox, Safari, Edge (latest versions)
  • Web Workers: Required for non-blocking PoW solving
  • ES6+ Features: Uses modern JavaScript features
  • Mobile Support: Responsive design works on mobile devices

Note: This is a demonstration application. For production use, ensure proper security measures, error handling, and configuration management are implemented.

Output

Proof of Work Demo Application

/static/index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Time App - Proof of Work Demo</title>
    <style>
      body {
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        max-width: 800px;
        margin: 0 auto;
        padding: 20px;
        background-color: #f5f5f5;
      }

      .container {
        background: white;
        padding: 30px;
        border-radius: 10px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      }

      h1 {
        color: #333;
        text-align: center;
        margin-bottom: 30px;
      }

      .button {
        background: #007bff;
        color: white;
        border: none;
        padding: 15px 30px;
        font-size: 18px;
        border-radius: 5px;
        cursor: pointer;
        transition: background-color 0.3s;
        width: 100%;
        margin-bottom: 20px;
      }

      .button:hover {
        background: #0056b3;
      }

      .button:disabled {
        background: #6c757d;
        cursor: not-allowed;
      }

      .status {
        padding: 15px;
        border-radius: 5px;
        margin: 10px 0;
        font-weight: bold;
      }

      .status.info {
        background: #d1ecf1;
        color: #0c5460;
        border: 1px solid #bee5eb;
      }

      .status.success {
        background: #d4edda;
        color: #155724;
        border: 1px solid #c3e6cb;
      }

      .status.error {
        background: #f8d7da;
        color: #721c24;
        border: 1px solid #f5c6cb;
      }

      .stats {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 15px;
        margin: 20px 0;
      }

      .stat-card {
        background: #f8f9fa;
        padding: 15px;
        border-radius: 5px;
        text-align: center;
      }

      .stat-value {
        font-size: 24px;
        font-weight: bold;
        color: #007bff;
      }

      .stat-label {
        color: #6c757d;
        font-size: 14px;
      }

      .time-display {
        font-size: 32px;
        text-align: center;
        color: #28a745;
        margin: 20px 0;
        font-weight: bold;
      }

      .explanation {
        background: #e7f3ff;
        padding: 20px;
        border-radius: 5px;
        margin: 20px 0;
        border-left: 4px solid #007bff;
      }

      .explanation h3 {
        margin-top: 0;
        color: #0056b3;
      }
    </style>
  </head>

  <body>
    <div class="container">
      <h1>πŸ•’ Time App - Proof of Work Demo</h1>

      <div class="explanation">
        <h3>How it works:</h3>
        <p>
          This app demonstrates proof of work (PoW) to prevent abuse from
          automated bots. When you click "Get Server Time", your browser will:
        </p>
        <ol>
          <li>Request a challenge from the server</li>
          <li>Solve a computational puzzle (proof of work)</li>
          <li>
            Invoke the <strong>getTime</strong> API endpoint along with the
            proof of work solution
          </li>
          <li>Receive the server time only after verification</li>
        </ol>
      </div>

      <button id="getTimeBtn" class="button" onclick="getServerTime()">
        πŸ•’ Get Server Time
      </button>

      <div id="status" class="status info" style="display: none;"></div>

      <div id="stats" class="stats" style="display: none;">
        <div class="stat-card">
          <div id="nonceCount" class="stat-value">0</div>
          <div class="stat-label">Nonces Tried</div>
        </div>
        <div class="stat-card">
          <div id="elapsedTime" class="stat-value">0s</div>
          <div class="stat-label">Time Elapsed</div>
        </div>
      </div>

      <div id="timeDisplay" class="time-display" style="display: none;"></div>
    </div>

    <script src="pow.js"></script>
    <script>
      let startTime;
      let isRunning = false;

      // Access the global pow instance from pow.js
      let powInstance;

      // Wait for pow.js to load and initialize
      function initializePow() {
        if (window.pow) {
          powInstance = window.pow;
          console.log('PoW instance initialized successfully');
        } else {
          console.error('PoW instance not found, retrying...');
          setTimeout(initializePow, 100);
        }
      }

      // Start initialization
      initializePow();

      function showStatus(message, type = 'info') {
        const status = document.getElementById('status');
        status.textContent = message;
        status.className = `status ${type}`;
        status.style.display = 'block';
      }

      function updateStats(nonceCount, elapsedTime) {
        document.getElementById('nonceCount').textContent =
          nonceCount.toLocaleString();
        document.getElementById('elapsedTime').textContent = `${elapsedTime}s`;
      }

      async function getServerTime() {
        if (isRunning) return;

        // Check if pow instance is available
        if (!powInstance) {
          showStatus(
            '❌ Error: PoW system not initialized yet. Please wait a moment and try again.',
            'error'
          );
          return;
        }

        isRunning = true;
        startTime = Date.now();

        const button = document.getElementById('getTimeBtn');
        button.disabled = true;
        button.textContent = '⏳ Solving Challenge...';

        // Reset UI
        document.getElementById('status').style.display = 'none';
        document.getElementById('timeDisplay').style.display = 'none';
        document.getElementById('stats').style.display = 'block';

        try {
          showStatus('πŸ” Requesting challenge from server...', 'info');

          // Solve proof of work using the extracted module
          const result = await initiateProofOfWork();

          // Calculate elapsed time
          const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);

          // Update stats only once when challenge is solved
          updateStats(result.nonce, elapsed);

          showStatus('βœ… Challenge solved! Submitting proof...', 'success');

          // Step 3: Get server time
          const timeRes = await fetch('/get-time', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({
              challenge: result.challenge,
              nonce: result.nonce,
              hash: result.hash,
            }),
          });

          if (!timeRes.ok) {
            throw new Error('Failed to get server time');
          }

          const timeData = await timeRes.json();

          showStatus('πŸŽ‰ Success! Server time retrieved.', 'success');
          document.getElementById('timeDisplay').textContent = timeData.time;
          document.getElementById('timeDisplay').style.display = 'block';
        } catch (error) {
          console.error('Error:', error);
          showStatus(`❌ Error: ${error.message}`, 'error');
        } finally {
          isRunning = false;
          button.disabled = false;
          button.textContent = 'πŸ•’ Get Server Time';
        }
      }
    </script>
  </body>
</html>

/static/pow.js

/**
 * Proof of Work (PoW) Implementation
 * Handles challenge solving, nonce calculation, and hash verification
 */

class ProofOfWork {
  constructor() {
    this.nonceCount = 0;
    this.isRunning = false;
  }

  /**
   * Convert hexadecimal string to binary string
   * @param {string} hex - Hexadecimal string
   * @returns {string} Binary string
   */
  hexToBinary(hex) {
    return hex
      .split('')
      .map(h => parseInt(h, 16).toString(2).padStart(4, '0'))
      .join('');
  }

  /**
   * Generate SHA-256 hash of a message
   * @param {string} message - Message to hash
   * @returns {Promise<string>} Hexadecimal hash string
   */
  async sha256(message) {
    const msgBuffer = new TextEncoder().encode(message);
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
  }

  /**
   * Solve the proof of work challenge
   * @param {string} challenge - Challenge string from server
   * @param {number} difficulty - Difficulty in bits
   * @returns {Promise<Object>} Object containing nonce and hash
   */
  async solveChallenge(challenge, difficulty) {
    let nonce = 0;

    while (this.isRunning) {
      const input = challenge + nonce;
      const hash = await this.sha256(input);
      const binaryHash = this.hexToBinary(hash);

      if (binaryHash.startsWith('0'.repeat(difficulty))) {
        return { nonce, hash };
      }

      nonce++;
      this.nonceCount = nonce;
    }

    throw new Error('Operation cancelled');
  }

  /**
   * Start the proof of work process
   * @param {string} challenge - Challenge string from server
   * @param {number} difficulty - Difficulty in bits
   * @returns {Promise<Object>} Object containing nonce and hash
   */
  async start(challenge, difficulty) {
    if (this.isRunning) {
      throw new Error('POW already running');
    }

    this.isRunning = true;
    this.nonceCount = 0;

    try {
      const result = await this.solveChallenge(challenge, difficulty);
      return result;
    } finally {
      this.isRunning = false;
    }
  }

  /**
   * Stop the proof of work process
   */
  stop() {
    this.isRunning = false;
  }

  /**
   * Check if proof of work is currently running
   * @returns {boolean} True if running, false otherwise
   */
  isActive() {
    return this.isRunning;
  }

  /**
   * Get current nonce count
   * @returns {number} Current nonce count
   */
  getNonceCount() {
    return this.nonceCount;
  }

  /**
   * Reset all counters and state
   */
  reset() {
    this.nonceCount = 0;
    this.isRunning = false;
  }
}

// Create a global instance
const pow = new ProofOfWork();

/**
 * Main function to initiate the proof of work process
 * This is the only function that should be called from the HTML file
 * The function will automatically fetch the challenge from the server
 *
 * @returns {Promise<Object>} Object containing challenge, nonce and hash
 */
async function initiateProofOfWork() {
  // Fetch challenge from server
  const challengeRes = await fetch('/get-challenge');
  if (!challengeRes.ok) {
    throw new Error('Failed to get challenge');
  }

  const { challenge, difficulty } = await challengeRes.json();

  // Start the proof of work process
  const result = await pow.start(challenge, difficulty);

  // Return challenge along with the result
  return {
    challenge: challenge,
    nonce: result.nonce,
    hash: result.hash,
  };
}

// Export for use in other modules (if needed)
if (typeof module !== 'undefined' && module.exports) {
  module.exports = { ProofOfWork, initiateProofOfWork };
}

go.mod

module proof_of_work_demo

go 1.24.0

main.go

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"time"
)

type TimeResponse struct {
	Time        string `json:"time"`
	Timestamp   int64  `json:"timestamp"`
	ServerTime  string `json:"server_time"`
	Timezone    string `json:"timezone"`
}

// getTimeHandler returns the server time
// PoW verification is handled by the ProtectedHandler middleware
func getTimeHandler(w http.ResponseWriter, r *http.Request) {
	// If we reach here, the PoW has already been verified by the middleware
	now := time.Now()
	timeResponse := TimeResponse{
		Time:       now.Format("2006-01-02 15:04:05"),
		Timestamp:  now.Unix(),
		ServerTime: now.Format(time.RFC3339),
		Timezone:   now.Location().String(),
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(timeResponse)
}

func main() {
	rand.Seed(time.Now().UnixNano())

	// Proof of Work endpoints
	http.HandleFunc("/get-challenge", getChallengeHandler)
	http.HandleFunc("/submit", submitHandler)

	// Time endpoint (protected by PoW middleware) - POST only, accepts JSON body
	http.HandleFunc("/get-time", ProtectedHandler(getTimeHandler))

	// Serve static files
	fs := http.FileServer(http.Dir("./static"))

	// Custom handler to serve navigation page as root
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/" {
			// Serve the navigation page for root path
			http.ServeFile(w, r, "./static/index.html")
		} else {
			fmt.Println("Serving static file:", r.URL.Path)
			// Serve other static files normally
			fs.ServeHTTP(w, r)
		}
	})

	fmt.Println("Server running on http://localhost:8082")
	fmt.Println("Available endpoints:")
	fmt.Println("  GET  /              - Navigation page")
	fmt.Println("  GET  /time-app      - Time app with PoW demo")
	fmt.Println("  GET  /index.html    - Original PoW demo")
	fmt.Println("  GET  /get-challenge - Get PoW challenge")
	fmt.Println("  POST /submit        - Submit PoW solution")
	fmt.Println("  POST /get-time      - Get server time (after PoW)")

	log.Fatal(http.ListenAndServe(":8082", nil))
}

Makefile

.PHONY: dev run test clean
SERVER_PORT=8082

kill-server:
	@PID=$(shell lsof -t -i :$(SERVER_PORT)) && \
	if [ ! -z "$$PID" ]; then echo "killing process with PID $$PID"; kill -9 $$PID; fi
# Development mode
dev:
	@make kill-server
	go run main.go pow.go

# Run the server
run:
	@make kill-server
	go run main.go pow.go

# Test the proof of work functionality
test:
	@echo "πŸ§ͺ Testing Proof of Work functionality..."
	@echo "Make sure the server is running first with 'make run'"
	@echo "Then run: python3 test_pow.py"
	@echo ""
	@echo "Or test manually:"
	@echo "1. Open http://localhost:8082 in your browser"
	@echo "2. Click on 'Time App with PoW'"
	@echo "3. Click 'Get Server Time' button"

# Build the application
build:
	go build -o pow-demo main.go

# Clean build artifacts
clean:
	rm -f pow-demo

# Install dependencies (if any)
deps:
	go mod tidy

# Help
help:
	@echo "Available commands:"
	@echo "  make run    - Run the server"
	@echo "  make test   - Show testing instructions"
	@echo "  make build  - Build the application"
	@echo "  make clean  - Clean build artifacts"
	@echo "  make deps   - Install dependencies"
	@echo "  make help   - Show this help message"

pow.go

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const difficulty = 10 // number of leading zero bits required
const challengeExpirySeconds = 300 // 5 minutes
var secretKey = []byte("your-very-secret-key") // You can store this securely elsewhere

type Challenge struct {
	Challenge  string `json:"challenge"`
	Difficulty int    `json:"difficulty"`
}

// ProofOfWork represents a proof of work challenge and solution
type ProofOfWork struct {
	Challenge  string `json:"challenge"`
	Nonce      int    `json:"nonce"`
	Hash       string `json:"hash"`
	Difficulty int    `json:"difficulty"`
}

// PoWVerifier handles proof of work verification
type PoWVerifier struct {
	difficulty int
}

// NewPoWVerifier creates a new proof of work verifier
func NewPoWVerifier(difficulty int) *PoWVerifier {
	return &PoWVerifier{difficulty: difficulty}
}

// GenerateChallenge creates a new challenge for proof of work
func (p *PoWVerifier) GenerateChallenge() string {
	timestamp := time.Now().Unix()
	randomPart := fmt.Sprintf("%x", rand.Int63())
	base := fmt.Sprintf("%d:%s", timestamp, randomPart)

	// Create HMAC signature
	mac := hmac.New(sha256.New, secretKey)
	mac.Write([]byte(base))
	signature := hex.EncodeToString(mac.Sum(nil))

	return fmt.Sprintf("%s:%s", base, signature)
}

// Verify checks if the provided proof of work is valid
func (p *PoWVerifier) Verify(challenge, nonceStr, clientHash string) bool {
	parts := strings.Split(challenge, ":")
	if len(parts) != 3 {
		log.Println("Invalid challenge format")
		return false
	}

	timestampStr := parts[0]
	randomPart := parts[1]
	signature := parts[2]

	// Verify signature
	base := fmt.Sprintf("%s:%s", timestampStr, randomPart)
	mac := hmac.New(sha256.New, secretKey)
	mac.Write([]byte(base))
	expectedSig := hex.EncodeToString(mac.Sum(nil))

	if !hmac.Equal([]byte(expectedSig), []byte(signature)) {
		log.Println("Invalid challenge signature")
		return false
	}

	// Verify timestamp freshness
	timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
	if err != nil {
		log.Println("Invalid timestamp in challenge")
		return false
	}
	if time.Now().Unix()-timestamp > challengeExpirySeconds {
		log.Println("Challenge has expired")
		return false
	}

	// Compute hash
	combined := challenge + nonceStr
	hashBytes := sha256.Sum256([]byte(combined))
	computedHash := hex.EncodeToString(hashBytes[:])

	if computedHash != clientHash {
		log.Printf("Hash mismatch: expected %s, got %s\n", computedHash, clientHash)
		return false
	}

	// Check difficulty
	bin := ""
	for _, c := range hashBytes {
		bin += fmt.Sprintf("%08b", c)
	}
	if !strings.HasPrefix(bin, strings.Repeat("0", p.difficulty)) {
		log.Printf("Hash does not meet difficulty requirement: %s\n", computedHash)
		return false
	}

	return true
}

// GetDifficulty returns the current difficulty setting
func (p *PoWVerifier) GetDifficulty() int {
	return p.difficulty
}

// Global PoW verifier instance
var powVerifier = NewPoWVerifier(difficulty)

// GenerateChallenge creates a new challenge for proof of work
func GenerateChallenge() string {
	return powVerifier.GenerateChallenge()
}

// VerifyPoW checks if the provided proof of work is valid
func VerifyPoW(challenge, nonceStr, clientHash string) bool {
	return powVerifier.Verify(challenge, nonceStr, clientHash)
}

// ProtectedHandler is a middleware that protects any handler with proof of work verification
// It expects the PoW parameters to be in the JSON body of the request
func ProtectedHandler(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// Only allow POST requests
		if r.Method != http.MethodPost {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}

		// Parse JSON body for PoW parameters
		var payload struct {
			Challenge string `json:"challenge"`
			Nonce     int    `json:"nonce"`
			Hash      string `json:"hash"`
		}

		err := json.NewDecoder(r.Body).Decode(&payload)
		if err != nil {
			log.Println("Error decoding JSON:", err)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusBadRequest)
			http.Error(w, "Invalid JSON", http.StatusBadRequest)
			return
		}

		if payload.Challenge == "" || payload.Hash == "" {
			http.Error(w, "Missing PoW parameters", http.StatusBadRequest)
			return
		}

		// Convert nonce to string for verification
		nonceStr := strconv.Itoa(payload.Nonce)

		// Perform PoW verification
		if !VerifyPoW(payload.Challenge, nonceStr, payload.Hash) {
			http.Error(w, "Invalid or expired PoW", http.StatusForbidden)
			return
		}

		// If PoW is valid, proceed to the next handler
		// Note: The request body has been consumed, so the next handler won't be able to read it again
		// In a real application, you might want to store the parsed payload in the request context
		next.ServeHTTP(w, r)
	}
}


func getChallengeHandler(w http.ResponseWriter, r *http.Request) {
	ch := Challenge{
		Challenge:  GenerateChallenge(),
		Difficulty: powVerifier.GetDifficulty(),
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(ch)
}

πŸ“š Further Reading

Output

Proof of Work Demo Application