Skip to content

Instantly share code, notes, and snippets.

@sphinxid
Last active March 31, 2026 23:40
Show Gist options
  • Select an option

  • Save sphinxid/610be4302400a3c8dda95421ae0b256c to your computer and use it in GitHub Desktop.

Select an option

Save sphinxid/610be4302400a3c8dda95421ae0b256c to your computer and use it in GitHub Desktop.
This code simulates a client-server interaction using a Proof-of-Work (PoW) system. In such systems, the client must solve a computational challenge set by the server in order to have its request processed. This particular code makes use of SHA3-256 hashing and requires the hash of a unique challenge and a nonce to have a specific number of lead…
"""
Proof-of-Work (PoW) API Client-Server Simulation
=================================================
This module simulates a simple API spam-protection mechanism using Proof-of-Work (PoW).
How it works:
1. SERVER issues a challenge: a unique token (random hash + random suffix) signed
with an HMAC to prevent tampering, along with a timestamp.
2. CLIENT must find a nonce (an arbitrary number) such that:
SHA3-256(challenge_token + server_timestamp + nonce)
starts with DIFFICULTY leading zero hex characters. This requires brute-force
iteration, making spam/bot requests computationally expensive.
3. SERVER verifies three things before processing the request:
a. The challenge was not tampered with (HMAC signature check).
b. The solution was submitted within the allowed time window (replay guard).
c. The nonce actually produces a valid hash (PoW verification).
"""
import hashlib
import hmac
import random
import secrets
import time
from dataclasses import dataclass
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
# Secret key used to sign and verify challenges via HMAC.
# In production, load this from a secure vault or environment variable.
SECRET_KEY = b"SayaOrangPalingGantengSedunia"
# Number of leading zero hex characters required in a valid hash.
# Each extra zero roughly multiplies the expected work by 16.
DIFFICULTY = 5
# Maximum age (in seconds) a submitted solution is allowed to be.
# Prevents replay attacks where an attacker reuses an old valid nonce.
MAX_SOLUTION_AGE_SECONDS = 300
# Upper bound of the nonce search space (~10 billion).
# Starting from a random offset within this range reduces collision risk
# when multiple clients solve the same challenge concurrently.
NONCE_RANGE = 10**10
# ---------------------------------------------------------------------------
# Data structure
# ---------------------------------------------------------------------------
@dataclass
class ChallengePacket:
"""
Bundles all data the server sends to the client for a PoW challenge.
Attributes:
challenge_token : The puzzle string the client must embed in its hash.
Composed of a random hash + a random unique suffix to
prevent pre-computation (rainbow table) attacks.
server_timestamp: Unix timestamp of when the challenge was issued.
Used to enforce the time window during verification.
hmac_digest : HMAC-SHA3-256 signature over (challenge_token + server_timestamp).
Lets the server detect any tampering with the packet.
"""
challenge_token: str
server_timestamp: int
hmac_digest: str
# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------
def _random_hash() -> str:
"""Returns a SHA3-256 hex digest of a cryptographically random 64-bit number."""
return hashlib.sha3_256(str(secrets.randbits(64)).encode()).hexdigest()
def _sign(challenge_token: str, timestamp: int) -> str:
"""
Creates an HMAC-SHA3-256 signature over the challenge token and timestamp.
The server calls this both when issuing and when verifying a challenge,
so any modification to the packet will produce a mismatched digest.
"""
message = f"{challenge_token}{timestamp}".encode()
return hmac.new(SECRET_KEY, message, hashlib.sha3_256).hexdigest()
# ---------------------------------------------------------------------------
# Server
# ---------------------------------------------------------------------------
def server_issue_challenge() -> ChallengePacket:
"""
[SERVER] Generate and sign a new PoW challenge, then send it to the client.
The challenge_token is built from:
- A random hash : ensures uniqueness and unpredictability.
- A random suffix: prevents two simultaneous challenges from sharing a token.
Returns a ChallengePacket the server would transmit to the client.
"""
random_hash = _random_hash()
unique_suffix = secrets.token_hex(16) # 16 random bytes → 32 hex chars
challenge_token = random_hash + unique_suffix # final puzzle string
server_timestamp = int(time.time())
hmac_digest = _sign(challenge_token, server_timestamp)
print(
f"[Server] Challenge issued.\n"
f" Token : {challenge_token}\n"
f" Goal : Find a nonce so that\n"
f" SHA3-256(token + server_timestamp + nonce)\n"
f" starts with {DIFFICULTY} leading zero(s)."
)
return ChallengePacket(challenge_token, server_timestamp, hmac_digest)
def server_verify_solution(packet: ChallengePacket, nonce: int, client_timestamp: int) -> bool:
"""
[SERVER] Verify the client's PoW solution before processing the API request.
Verification steps (fail-fast order):
1. HMAC check — reject if the challenge packet was tampered with.
2. Time check — reject if the solution is older than MAX_SOLUTION_AGE_SECONDS.
3. PoW check — reject if the nonce does not produce a valid hash.
Args:
packet : The original ChallengePacket issued by the server.
nonce : The nonce the client found.
client_timestamp: Unix timestamp recorded when the client started solving.
Returns True if all checks pass, False otherwise.
"""
current_time = int(time.time())
# Step 1: Re-derive the HMAC and compare using a timing-safe comparison.
# hmac.compare_digest prevents timing side-channel attacks.
expected_digest = _sign(packet.challenge_token, packet.server_timestamp)
if not hmac.compare_digest(expected_digest, packet.hmac_digest):
print("[Server] REJECTED: HMAC mismatch — challenge packet may have been tampered with.")
return False
# Step 2: Check that the solution is not too old.
solution_age = current_time - client_timestamp
if solution_age > MAX_SOLUTION_AGE_SECONDS:
print(
f"[Server] REJECTED: Solution is {solution_age}s old "
f"(limit: {MAX_SOLUTION_AGE_SECONDS}s)."
)
return False
# Step 3: Recompute the hash with the claimed nonce and check leading zeroes.
data = f"{packet.challenge_token}{packet.server_timestamp}{nonce}"
hash_result = hashlib.sha3_256(data.encode()).hexdigest()
if hash_result.startswith("0" * DIFFICULTY):
print(f"[Server] ACCEPTED: nonce={nonce}, hash={hash_result}")
print("[Server] Processing API request...")
time.sleep(1) # Simulate actual request handling time
print("[Server] Request completed successfully.")
return True
print(f"[Server] REJECTED: Hash '{hash_result}' does not meet difficulty={DIFFICULTY}.")
return False
# ---------------------------------------------------------------------------
# Client
# ---------------------------------------------------------------------------
def client_solve_challenge(packet: ChallengePacket) -> tuple[int, int]:
"""
[CLIENT] Brute-force a nonce that satisfies the server's PoW difficulty.
The client iterates nonce values (wrapping at NONCE_RANGE) and checks
whether SHA3-256(challenge_token + server_timestamp + nonce) starts with
the required number of zero hex characters.
Starting from a random offset within NONCE_RANGE reduces the chance of
two parallel clients doing identical work.
Args:
packet: The ChallengePacket received from the server.
Returns (nonce, client_timestamp):
nonce : The valid nonce found.
client_timestamp: Unix timestamp of when solving began (sent to server
so it can enforce the time window).
"""
print(f"[Client] Solving challenge (difficulty={DIFFICULTY})...")
client_timestamp = int(time.time()) # Record start time before the loop
start_time = time.time()
nonce = 0
while nonce <= NONCE_RANGE:
# Build the candidate string and hash it
data = f"{packet.challenge_token}{packet.server_timestamp}{nonce}"
hash_result = hashlib.sha3_256(data.encode()).hexdigest()
# A valid nonce produces a hash with DIFFICULTY leading zero hex chars
if hash_result.startswith("0" * DIFFICULTY):
elapsed = time.time() - start_time
print(
f"[Client] Solved in {elapsed:.2f}s\n"
f" Nonce : {nonce}\n"
f" Hash : {hash_result}"
)
return nonce, client_timestamp
# Advance to the next nonce
nonce += 1
# ---------------------------------------------------------------------------
# Simulation entry point
# ---------------------------------------------------------------------------
def simulate_api_request() -> None:
"""
Runs a full end-to-end PoW-protected API request simulation:
Phase 1 — Server issues a challenge.
Phase 2 — Client solves the challenge.
Phase 3 — Server verifies the solution and processes the request.
"""
print("=" * 65)
print(" Proof-of-Work API Simulation")
print("=" * 65)
# Phase 1: Server creates and signs a challenge
packet = server_issue_challenge()
print()
# Phase 2: Client brute-forces a valid nonce
nonce, client_timestamp = client_solve_challenge(packet)
print()
# Phase 3: Server validates and (if accepted) handles the request
success = server_verify_solution(packet, nonce, client_timestamp)
print()
print(f" Final result: {'✓ SUCCESS' if success else '✗ FAILED'}")
print("=" * 65)
def main() -> None:
simulate_api_request()
if __name__ == "__main__":
main()
@sphinxid
Copy link
Copy Markdown
Author

sphinxid commented Jun 6, 2023

$ python3.10 pow04.py
Server: The challenge is '45834afa64a3eb05a15e01c11feeee05f6dc1db3bae17d7dc15eb55b947de79e', you need to find a nonce such that the SHA3-256 hash of the challenge + unique string, and nonce together has at least 5 leading zeroes.
Client: Solving challenge...
hash result -> 000005faf2176f73d9ff080893587183ef6cf6ed1ef307f930d547dcf5221dc8
client timestamp -> 1686118872.015199
server timestamp -> 1686118872
Client: Challenge solved in 1.4372360706329346 seconds, the nonce is 6410631986
Server: Solution is correct, processing request...
Server: Request processed

@sphinxid
Copy link
Copy Markdown
Author

sphinxid commented Mar 31, 2026

=================================================================
  Proof-of-Work API Simulation
=================================================================
[Server] Challenge issued.
         Token : 02013d4d6758fc10f16afff53ee2e238fc70cffa2c729ada3ec15330f2695ddb8cdf5d0a71c353e32cfc86c9e32a28de
         Goal  : Find a nonce so that
                 SHA3-256(token + server_timestamp + nonce)
                 starts with 5 leading zero(s).

[Client] Solving challenge (difficulty=5)...
[Client] Solved in 1.74s
         Nonce : 983142
         Hash  : 0000030c064df05df8c66575ac546cfecccb40cd807647329bf600c9d7fe4d0d

[Server] ACCEPTED: nonce=983142, hash=0000030c064df05df8c66575ac546cfecccb40cd807647329bf600c9d7fe4d0d
[Server] Processing API request...
[Server] Request completed successfully.

  Final result: ✓ SUCCESS
=================================================================

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment