"""Helpers for OpenAI Sentinel proof-of-work tokens.""" from __future__ import annotations import base64 import hashlib import json import random import time import uuid from datetime import datetime, timedelta, timezone from typing import Sequence DEFAULT_SENTINEL_DIFF = "0fffff" DEFAULT_MAX_ITERATIONS = 500_000 _SCREEN_SIGNATURES = (3000, 3120, 4000, 4160) _LANGUAGE_SIGNATURE = "en-US,es-US,en,es" _NAVIGATOR_KEYS = ("location", "ontransitionend", "onprogress") _WINDOW_KEYS = ("window", "document", "navigator") class SentinelPOWError(RuntimeError): """Raised when a Sentinel proof-of-work token cannot be solved.""" def _format_browser_time() -> str: """Match the browser-style timestamp used by public Sentinel solvers.""" browser_now = datetime.now(timezone(timedelta(hours=-5))) return browser_now.strftime("%a %b %d %Y %H:%M:%S") + " GMT-0500 (Eastern Standard Time)" def build_sentinel_config(user_agent: str) -> list: """Build a browser-like fingerprint payload for the Sentinel PoW solver.""" perf_ms = time.perf_counter() * 1000 epoch_ms = (time.time() * 1000) - perf_ms return [ random.choice(_SCREEN_SIGNATURES), _format_browser_time(), 4294705152, 0, user_agent, "", "", "en-US", _LANGUAGE_SIGNATURE, 0, random.choice(_NAVIGATOR_KEYS), "location", random.choice(_WINDOW_KEYS), perf_ms, str(uuid.uuid4()), "", 8, epoch_ms, ] def _encode_pow_payload(config: Sequence[object], nonce: int) -> bytes: prefix = (json.dumps(config[:3], separators=(",", ":"), ensure_ascii=False)[:-1] + ",").encode("utf-8") middle = ( "," + json.dumps(config[4:9], separators=(",", ":"), ensure_ascii=False)[1:-1] + "," ).encode("utf-8") suffix = ("," + json.dumps(config[10:], separators=(",", ":"), ensure_ascii=False)[1:]).encode("utf-8") body = prefix + str(nonce).encode("ascii") + middle + str(nonce >> 1).encode("ascii") + suffix return base64.b64encode(body) def solve_sentinel_pow( seed: str, difficulty: str, config: Sequence[object], max_iterations: int = DEFAULT_MAX_ITERATIONS, ) -> str: """Solve the Sentinel PoW challenge and return the base64 payload.""" seed_bytes = seed.encode("utf-8") target = bytes.fromhex(difficulty) prefix_length = len(target) for nonce in range(max_iterations): encoded = _encode_pow_payload(config, nonce) digest = hashlib.sha3_512(seed_bytes + encoded).digest() if digest[:prefix_length] <= target: return encoded.decode("ascii") raise SentinelPOWError(f"failed to solve sentinel pow after {max_iterations} attempts") def build_sentinel_pow_token( user_agent: str, difficulty: str = DEFAULT_SENTINEL_DIFF, max_iterations: int = DEFAULT_MAX_ITERATIONS, ) -> str: """Build the `p` token required by the Sentinel request endpoint.""" config = build_sentinel_config(user_agent) seed = format(random.random()) solution = solve_sentinel_pow(seed, difficulty, config, max_iterations=max_iterations) return f"gAAAAAC{solution}"