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
-
Clone and navigate to the project:
cd proof_of_work_demo
-
Run the server:
go run main.go pow.go
Or use the Makefile:
make run
-
Open your browser:
- Navigate to
http://localhost:8082
- The time app will be displayed with PoW demo functionality
- Navigate to
π Available Endpoints
Endpoint | Method | Description |
---|---|---|
/ | GET | Time app with proof of work demo |
/get-challenge | GET | Get a new PoW challenge |
/get-time | POST | Get 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
- Open the app in your browser at
http://localhost:8082
- Click βGet Server Timeβ
- Watch the proof of work solving process with real-time progress
- 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 instructionsmake build
- Build the applicationmake clean
- Clean build artifactsmake deps
- Install dependenciesmake help
- Show available commandsmake 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
- Make changes to Go files
- Use
make dev
to restart the server - Refresh browser to see changes
- 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
/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)
}