diff --git a/randonautentropy/rndo.py b/randonautentropy/rndo.py index 71632d4..4d0fddb 100644 --- a/randonautentropy/rndo.py +++ b/randonautentropy/rndo.py @@ -1,4 +1,8 @@ import requests +import random +import secrets +import base64 +import os try: from urllib.parse import urlencode @@ -11,33 +15,101 @@ requests.packages.urllib3.disable_warnings(InsecureRequestWarning) URL = 'https://qrng.randonautica.com/api/json/' typeS = { - 'int32' : 'randint32', - 'hex16' : 'randhex', - 'uniform' : 'randuniform', + 'int32' : 'randint32', + 'hex16' : 'randhex', + 'uniform': 'randuniform', 'normal' : 'randnormal', 'base64' : 'randbase64' } DEVICE_ID = 'QWR70154' -def get(length=10, type='hex16'): +def get(length=10, type='hex16', timeout=1.5): """ - Fetch data from the Randonautica Quantum Random Numbers JSON API - - length (int): length of bytes to get from the QRNG when `type=hex16` - type (string): type of random number data to fetch. - Valid choices are `hex16`, `int32`, `uniform`, `normal`, `base64` + Fetch data from the Randonautica Quantum Random Numbers JSON API. + Falls back to local randomness if the API is unavailable. + length (int): number of values/bytes to get + type (str): one of {'hex16','int32','uniform','normal','base64'} + timeout (float): HTTP timeout in seconds """ - if type not in typeS.keys(): - raise Exception("type must be one of %s" % typeS.keys()) + if type not in typeS: + raise Exception("type must be one of %s" % list(typeS.keys())) + + # Try QRNG first url = URL + typeS[type] + '?' + urlencode({ 'device_id': DEVICE_ID, 'length': length, }) - data = __get_json(url) - return data + try: + data = __get_json(url, timeout=timeout) + # Validate expected shape minimally + if isinstance(data, dict) and "data" in data and isinstance(data["data"], list): + return data + # If shape unexpected, fall back + except Exception: + pass + # Fallback path: generate locally with best available primitives + return _fallback_response(type=type, length=length) -def __get_json(url): - return requests.get(url, verify=False).json() \ No newline at end of file +def __get_json(url, timeout=1.5): + resp = requests.get(url, verify=False, timeout=timeout) + resp.raise_for_status() + return resp.json() + +def _fallback_response(type: str, length: int): + """ + Produce a response object that mirrors the QRNG API: + { + "type": , + "length": , + "data": [...], + "success": true, + "source": "fallback" + } + """ + if type == 'uniform': + # Uniform floats in [0.0, 1.0) + data = [random.random() for _ in range(length)] + + elif type == 'normal': + # Standard normal N(0,1) + data = [random.gauss(0.0, 1.0) for _ in range(length)] + + elif type == 'int32': + # 32-bit signed ints (match typical randint32 range: [-2^31, 2^31-1]) + # secrets is preferable to random for stronger entropy. + data = [] + for _ in range(length): + u = secrets.randbits(32) + # Convert to signed 32-bit + if u & (1 << 31): + u = u - (1 << 32) + data.append(u) + + elif type == 'hex16': + # Hex string of length*2 bytes? The API name suggests 16-bit hex bytes. + # Historically this endpoint returns hex bytes; emulate by returning a single hex string + # of 2*length characters (each byte -> 2 hex chars). If API returns list, adapt as needed. + # Here we return a list of hex byte strings to match "data": [...] + data = [secrets.token_hex(1) for _ in range(length)] + + elif type == 'base64': + # Return base64-encoded random bytes; match as a list of base64 strings + data = [] + for _ in range(length): + b = os.urandom(16) # 16 bytes per element; adjust if your API uses a different size + data.append(base64.b64encode(b).decode('ascii')) + + else: + # Should never hit due to earlier check + data = [] + + return { + "type": type, + "length": length, + "data": data, + "success": True, + "source": "pseudo" + } \ No newline at end of file diff --git a/setup.py b/setup.py index 8270716..3b0045d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open("README.md", "r") as fh: setuptools.setup( name="randonautentropy", - version="1.0.2", + version="1.0.3", author="randogoth", author_email="randogoth@posteo.org", description="Python API for interacting with The Randonautica Quantum Random Number Generator",