Last active
March 31, 2026 23:40
-
-
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…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| 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() |
Author
Author
=================================================================
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
$ 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