Frontend Labs
⋯

Day 104 — Proof-of-Work & Invisible Challenges

Learning objective Issue and verify SHA256 PoW challenges; gate session cookies on completion
Key packages crypto/sha256, crypto/rand, crypto/hmac, net/http, encoding/hex
Security context PoW imposes computational cost on bots while allowing human users through (they solve it in JS)

Key Concepts

PoW mechanics:

  • Server issues {challenge: random_hex, difficulty: 16, expires: unix_timestamp}
  • Client finds nonce such that SHA256(challenge + nonce) has ≥ 16 leading zero bits
  • Expected attempts: 2^16 = 65,536 (takes ~65ms on modern CPU)
  • Difficulty=20 → ~1 million attempts → 1 second — too slow for human-facing challenges

Session cookie issuance: After PoW solved, issue signed cookie. Subsequent requests verify cookie rather than re-challenging.

package bot

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

type PowSystem struct {
    secret     []byte
    difficulty int
    cookieName string
    expiry     time.Duration
}

func NewPowSystem(secret []byte, difficulty int) *PowSystem {
    return &PowSystem{
        secret:     secret,
        difficulty: difficulty,
        cookieName: "_bot_challenge",
        expiry:     30 * time.Minute,
    }
}

// ChallengeHandler issues a PoW challenge
func (ps *PowSystem) ChallengeHandler(w http.ResponseWriter, r *http.Request) {
    raw := make([]byte, 16)
    rand.Read(raw)
    expires := time.Now().Add(5 * time.Minute).Unix()

    // HMAC-sign the challenge + expiry
    mac := hmac.New(sha256.New, ps.secret)
    mac.Write(raw)
    mac.Write([]byte(strconv.FormatInt(expires, 10)))
    sig := hex.EncodeToString(mac.Sum(nil)[:8])

    challengeHex := hex.EncodeToString(raw)

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "challenge":  challengeHex + "." + sig, // embed sig for verification
        "difficulty": ps.difficulty,
        "expires":    expires,
    })
}

// VerifyHandler validates a solved PoW and issues a session cookie
func (ps *PowSystem) VerifyHandler(w http.ResponseWriter, r *http.Request) {
    var req struct {
        Challenge string `json:"challenge"`
        Nonce     string `json:"nonce"`
    }
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, `{"error":"invalid_request"}`, http.StatusBadRequest)
        return
    }

    parts := strings.SplitN(req.Challenge, ".", 2)
    if len(parts) != 2 {
        http.Error(w, `{"error":"invalid_challenge"}`, http.StatusBadRequest)
        return
    }
    challengeHex, storedSig := parts[0], parts[1]

    challengeBytes, err := hex.DecodeString(challengeHex)
    if err != nil {
        http.Error(w, `{"error":"invalid_challenge"}`, http.StatusBadRequest)
        return
    }

    // Verify HMAC (simplified — in production include expiry in MAC)
    mac := hmac.New(sha256.New, ps.secret)
    mac.Write(challengeBytes)
    expectedSig := hex.EncodeToString(mac.Sum(nil)[:8])
    if !hmac.Equal([]byte(storedSig), []byte(expectedSig)) {
        http.Error(w, `{"error":"invalid_challenge"}`, http.StatusBadRequest)
        return
    }

    // Verify nonce
    nonce, err := hex.DecodeString(req.Nonce)
    if err != nil {
        http.Error(w, `{"error":"invalid_nonce"}`, http.StatusBadRequest)
        return
    }

    h := sha256.New()
    h.Write(challengeBytes)
    h.Write(nonce)
    hash := h.Sum(nil)
    if leadingZeroBits(hash) < ps.difficulty {
        http.Error(w, `{"error":"insufficient_work"}`, http.StatusBadRequest)
        return
    }

    // Issue session cookie
    sessionToken := ps.issueSessionToken()
    http.SetCookie(w, &http.Cookie{
        Name:     ps.cookieName,
        Value:    sessionToken,
        Path:     "/",
        Expires:  time.Now().Add(ps.expiry),
        HttpOnly: true,
        Secure:   true,
        SameSite: http.SameSiteStrictMode,
    })
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"status": "verified"})
}

func (ps *PowSystem) issueSessionToken() string {
    raw := make([]byte, 16)
    rand.Read(raw)
    mac := hmac.New(sha256.New, ps.secret)
    mac.Write(raw)
    mac.Write([]byte(strconv.FormatInt(time.Now().Unix(), 10)))
    return hex.EncodeToString(raw) + "." + hex.EncodeToString(mac.Sum(nil)[:8])
}

// HasValidSession checks if request has a valid PoW-issued session cookie
func (ps *PowSystem) HasValidSession(r *http.Request) bool {
    cookie, err := r.Cookie(ps.cookieName)
    if err != nil {
        return false
    }
    parts := strings.SplitN(cookie.Value, ".", 2)
    if len(parts) != 2 {
        return false
    }
    // In production: verify HMAC and check expiry
    // For brevity: just check format
    _, err1 := hex.DecodeString(parts[0])
    _, err2 := hex.DecodeString(parts[1])
    return err1 == nil && err2 == nil
}

Generated from week14.md · 8 Oct 2026 16:36