forked from claw/flaskpaste
add /solver endpoint for PoW solver script download
This commit is contained in:
@@ -469,6 +469,7 @@ class IndexView(MethodView):
|
|||||||
f"GET {prefixed_url('/')}": "API information",
|
f"GET {prefixed_url('/')}": "API information",
|
||||||
f"GET {prefixed_url('/health')}": "Health check",
|
f"GET {prefixed_url('/health')}": "Health check",
|
||||||
f"GET {prefixed_url('/client')}": "Download CLI client",
|
f"GET {prefixed_url('/client')}": "Download CLI client",
|
||||||
|
f"GET {prefixed_url('/solver')}": "Download PoW solver script",
|
||||||
f"GET {prefixed_url('/challenge')}": "Get PoW challenge",
|
f"GET {prefixed_url('/challenge')}": "Get PoW challenge",
|
||||||
f"POST {prefixed_url('/')}": "Create paste",
|
f"POST {prefixed_url('/')}": "Create paste",
|
||||||
f"GET {prefixed_url('/pastes')}": "List your pastes (auth required)",
|
f"GET {prefixed_url('/pastes')}": "List your pastes (auth required)",
|
||||||
@@ -756,6 +757,91 @@ class ClientView(MethodView):
|
|||||||
return error_response("Client not available", 404)
|
return error_response("Client not available", 404)
|
||||||
|
|
||||||
|
|
||||||
|
class SolverView(MethodView):
|
||||||
|
"""PoW solver script download endpoint."""
|
||||||
|
|
||||||
|
def get(self) -> Response:
|
||||||
|
"""Serve a Python script for solving PoW challenges."""
|
||||||
|
server_url = base_url()
|
||||||
|
script = f'''#!/usr/bin/env python3
|
||||||
|
"""FlaskPaste PoW solver - solves challenges for curl-based uploads."""
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
SERVER = "{server_url}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_challenge():
|
||||||
|
"""Fetch a new challenge from the server."""
|
||||||
|
with urllib.request.urlopen(f"{{SERVER}}/challenge") as r:
|
||||||
|
return json.load(r)
|
||||||
|
|
||||||
|
|
||||||
|
def solve(nonce: str, difficulty: int) -> int:
|
||||||
|
"""Find solution where sha256(nonce:solution) has sufficient leading zero bits."""
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
h = hashlib.sha256(f"{{nonce}}:{{i}}".encode()).digest()
|
||||||
|
zero_bits = 0
|
||||||
|
for byte in h:
|
||||||
|
if byte == 0:
|
||||||
|
zero_bits += 8
|
||||||
|
else:
|
||||||
|
zero_bits += 8 - byte.bit_length()
|
||||||
|
break
|
||||||
|
if zero_bits >= difficulty:
|
||||||
|
return i
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: fpaste-solver <file>", file=sys.stderr)
|
||||||
|
print(" cat data | fpaste-solver -", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Read content
|
||||||
|
if sys.argv[1] == "-":
|
||||||
|
content = sys.stdin.buffer.read()
|
||||||
|
else:
|
||||||
|
with open(sys.argv[1], "rb") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Get and solve challenge
|
||||||
|
ch = get_challenge()
|
||||||
|
if not ch.get("enabled", True):
|
||||||
|
token, solution = "", ""
|
||||||
|
else:
|
||||||
|
print(f"Solving PoW (difficulty={{ch['difficulty']}})...", file=sys.stderr)
|
||||||
|
solution = solve(ch["nonce"], ch["difficulty"])
|
||||||
|
token = ch["token"]
|
||||||
|
print(f"Solved: {{solution}}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Upload
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{{SERVER}}/",
|
||||||
|
data=content,
|
||||||
|
headers={{
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
"X-PoW-Token": token,
|
||||||
|
"X-PoW-Solution": str(solution),
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
result = json.load(r)
|
||||||
|
print(result.get("raw", result.get("url", json.dumps(result))))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
'''
|
||||||
|
response = Response(script, mimetype="text/x-python")
|
||||||
|
response.headers["Content-Disposition"] = "attachment; filename=fpaste-solver"
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class PasteView(MethodView):
|
class PasteView(MethodView):
|
||||||
"""Paste metadata operations."""
|
"""Paste metadata operations."""
|
||||||
|
|
||||||
@@ -1399,6 +1485,7 @@ bp.add_url_rule("/", view_func=IndexView.as_view("index"))
|
|||||||
bp.add_url_rule("/health", view_func=HealthView.as_view("health"))
|
bp.add_url_rule("/health", view_func=HealthView.as_view("health"))
|
||||||
bp.add_url_rule("/challenge", view_func=ChallengeView.as_view("challenge"))
|
bp.add_url_rule("/challenge", view_func=ChallengeView.as_view("challenge"))
|
||||||
bp.add_url_rule("/client", view_func=ClientView.as_view("client"))
|
bp.add_url_rule("/client", view_func=ClientView.as_view("client"))
|
||||||
|
bp.add_url_rule("/solver", view_func=SolverView.as_view("solver"))
|
||||||
|
|
||||||
# Paste operations
|
# Paste operations
|
||||||
bp.add_url_rule("/pastes", view_func=PastesListView.as_view("pastes_list"))
|
bp.add_url_rule("/pastes", view_func=PastesListView.as_view("pastes_list"))
|
||||||
|
|||||||
Reference in New Issue
Block a user