Skip to content

Instantly share code, notes, and snippets.

@sphinxid
Last active June 7, 2023 06:21
Show Gist options
  • Save sphinxid/610be4302400a3c8dda95421ae0b256c to your computer and use it in GitHub Desktop.
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…
import hashlib
import hmac
import time
import secrets
import random
# Secret Key for HMAC
SECRET_KEY = b"SayaOrangPalingGantengSedunia" # please change it to something else lol.
# Challenge (this could be any piece of data)
CHALLENGE = hashlib.sha3_256(str(secrets.randbits(64)).encode()).hexdigest()
# Difficulty (number of leading zeroes required in the hash)
DIFFICULTY = 5
# Maximum time difference (in seconds)
TIME_DIFF = 300
# Nonce Range
NONCE_RANGE = 10**10
# The function server_set_challenge creates a unique string and a timestamp.
# It concatenates the unique string with the challenge and outputs the challenge, unique string, and timestamp together.
# The server instructs the client to find a nonce (a random value) that, when hashed with the challenge + unique string,
# results in a hash with a specific number of leading zeros (as per the "difficulty" level).
def server_set_challenge():
unique_str = secrets.token_hex(16)
timestamp = int(time.time())
challenge_with_unique_str = CHALLENGE + unique_str
digest = hmac.new(SECRET_KEY, str(challenge_with_unique_str + str(timestamp)).encode("utf-8"), hashlib.sha3_256).hexdigest()
print(f"Server: The challenge is '{CHALLENGE}', you need to find a nonce such that the SHA3-256 hash of the challenge + unique string, and nonce together has at least {DIFFICULTY} leading zeroes.")
return challenge_with_unique_str, timestamp, digest
# The function client_solve_challenge tries to solve the challenge. It begins by initializing a nonce within a given range.
# Then, it starts a loop where it concatenates the challenge and the nonce, hashes this data, and checks if the hash meets
# the difficulty level. If it does, the client has solved the challenge and outputs the nonce along with the time taken.
# If not, the nonce is incremented and the process repeats until a solution is found.
def client_solve_challenge(challenge, server_timestamp):
print("Client: Solving challenge...")
challenge_with_timestamp = challenge + str(server_timestamp)
start_time = time.time()
nonce = random.randint(0, NONCE_RANGE)
while True:
# Concatenate the challenge and nonce
data = challenge_with_timestamp + str(nonce)
# Compute the SHA3-256 hash of the data
hash_result = hashlib.sha3_256(data.encode()).hexdigest()
# Check if the hash has the required number of leading zeroes
if hash_result[:DIFFICULTY] == "0" * DIFFICULTY:
end_time = time.time()
print(f"hash result -> {hash_result}")
print(f"client timestamp -> {start_time}")
print(f"server timestamp -> {server_timestamp}")
print(f"Client: Challenge solved in {end_time - start_time} seconds, the nonce is {nonce}")
return nonce, start_time
# If the hash does not have the required number of leading zeroes, increment the nonce and try again
nonce = (nonce + 1) % NONCE_RANGE
# The function server_verify_solution checks the client's solution. It hashes the challenge and nonce again,
# and checks if the resultant hash meets the difficulty level and whether the solution was found within a specific
# time limit. If both conditions are met, the server processes the request and outputs a confirmation message.
# Otherwise, it rejects the request.
def server_verify_solution(challenge, nonce, client_timestamp, server_timestamp, digest):
##
#challenge = "3b9e9d95c105f3ca9e1ec56fdc6c15ba3a91b0b53e1ac90f105f01283a05de17"
#nonce = 1588079050
#client_timestamp = server_timestamp + 5000
##
data = challenge + str(server_timestamp) + str(nonce)
hash_result = hashlib.sha3_256(data.encode()).hexdigest()
original_digest = hmac.new(SECRET_KEY, str(challenge + str(server_timestamp)).encode("utf-8"),
hashlib.sha3_256).hexdigest()
# Check if the solution was found within the time limit
current_timestamp = int(time.time())
if (digest != original_digest):
print("Server: challenge has been tampered.")
elif (current_timestamp - client_timestamp > TIME_DIFF):
print("Server: Solution is too old, request rejected")
elif hash_result[:DIFFICULTY] == "0" * DIFFICULTY:
print("Server: Solution is correct, processing request...")
# Simulate request processing
time.sleep(1)
print("Server: Request processed")
else:
print("Server: Incorrect solution")
# The function simulate_api_request encapsulates the whole process.
# The server sets a challenge, the client solves it, and then the server verifies the solution.
def simulate_api_request():
challenge, server_timestamp, digest = server_set_challenge()
nonce, client_timestamp = client_solve_challenge(challenge, server_timestamp)
server_verify_solution(challenge, nonce, client_timestamp, server_timestamp, digest)
def main():
simulate_api_request()
if __name__ == "__main__":
main()
@sphinxid
Copy link
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

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