#!/usr/bin/env python3 """Seeded RL-v3 Grover experiment. Classical simulations, NOT quantum hardware. Run: python3 -B grover_rl_test.py One worker; no installs; new, exclusive JSON output only. Never edits reports. Table-oracle agreement verifies Grover mechanics, not cryptographic security. The optional reversible circuit uses clean compute/copy/uncompute and retains old state; it does not assume the RL state transition is invertible. """ import os import sys sys.dont_write_bytecode = True for _name in ('OPENBLAS_NUM_THREADS', 'OMP_NUM_THREADS', 'MKL_NUM_THREADS', 'VECLIB_MAXIMUM_THREADS', 'NUMEXPR_NUM_THREADS'): os.environ[_name] = '1' os.environ['RL_VERSION'] = 'v3' import argparse from collections import Counter from datetime import datetime, timezone import hashlib import json import math from pathlib import Path import platform import random import subprocess import time import numpy as np from phase3_sat_attacks import digest_w, step from refracting_light_v3 import digest, unfolded ROOT = Path(__file__).resolve().parent def vector_checks(): data = json.loads((ROOT / 'refracting-light-v3-test-vectors.json').read_text()) for v in data['vectors']: m = bytes.fromhex(v['message_hex']) assert digest_w(m, 64) == digest(m) == int(v['folded128'], 16) assert unfolded(m) == int(v['unfolded512'], 16) v = data['nonce_vector'] m = bytes.fromhex(v['message_hex']) + v['nonce'].to_bytes(8, 'big') assert digest_w(m, 64) == digest(m) == int(v['folded128'], 16) return dict(claim='MEASURED', ordinary_vectors=len(data['vectors']), nonce_vectors=1, passed=True) def leading_zero_bits(value): n = int.from_bytes(value, 'big') return 8*len(value) - n.bit_length() if n else 8*len(value) def librl_crosscheck(seed, n_in, cli_path): """Compare the built fixed-width C++ library with both Python v3 references.""" cli = Path(cli_path) if not cli.is_absolute(): cli = ROOT / cli if not cli.is_file(): raise FileNotFoundError(f'rl_cli not found: {cli}; build the release preset first') rng = random.Random(seed ^ 0x4c4942524c) width = (n_in + 7) // 8 messages = [j.to_bytes(width, 'big') for j in range(1 << n_in)] vectors = json.loads((ROOT / 'refracting-light-v3-test-vectors.json').read_text()) messages += [bytes.fromhex(v['message_hex']) for v in vectors['vectors']] nv = vectors['nonce_vector'] messages.append(bytes.fromhex(nv['message_hex']) + nv['nonce'].to_bytes(8, 'big')) messages += [rng.randbytes(n) for n in (0, 1, 2, 3, 4, 11, 12, 32, 44, 48, 55, 64, 120, 128)] scan = json.loads((ROOT / 'e46/reports/wo08-20261007/full-scan.json').read_text()) stamp_records = scan['commits'] for row in stamp_records: messages.append(b'E46-commit-pow\0' + bytes.fromhex(row['commitment']) + row['nonce'].to_bytes(8, 'little')) proc = subprocess.run([str(cli), 'v3', 'one'], input=''.join(m.hex() + '\n' for m in messages), text=True, capture_output=True, check=True) lines = proc.stdout.splitlines() assert len(lines) == len(messages), (len(lines), len(messages), proc.stderr) for index, (message, line) in enumerate(zip(messages, lines)): parts = line.split() assert len(parts) == 2, (index, line) cpp = int(parts[0], 16) assert cpp == digest_w(message, 64) == digest(message), (index, message.hex()) for row in stamp_records: raw = bytes.fromhex(row['hash']) assert leading_zero_bits(raw) >= 18 message = b'E46-commit-pow\0' + bytes.fromhex(row['commitment']) + row['nonce'].to_bytes(8, 'little') assert digest(message) == int(row['hash'], 16) return dict(claim='MEASURED', library='C++ e46::rl::rl_v3_128 via rl_cli', word_width=64, exhaustive_inputs=1 << n_in, additional_messages=len(messages)-(1 << n_in), wo08_commitment_stamp_messages=len(stamp_records), python_references=['phase3_sat_attacks.digest_w(w=64)', 'refracting_light_v3.digest'], mismatches=0, passed=True) def grover_curve(marked): N, M = len(marked), int(marked.sum()) theta = math.asin(math.sqrt(M / N)) asym = math.pi / 4 * math.sqrt(N / M) if M else None exact = max(0, math.pi / (4 * theta) - .5) if M else None k_first = math.floor(exact + .5) if M else None end = 2 * math.ceil(asym) if M else 4 psi = np.full(N, 1 / math.sqrt(N), dtype=np.float64) rows = [] for k in range(end + 1): p = float(np.sum(psi[marked] ** 2)) theory = math.sin((2 * k + 1) * theta) ** 2 norm_error = abs(float(np.dot(psi, psi)) - 1) assert abs(p - theory) < 1e-10 and norm_error < 1e-10 rows.append(dict(k=k, measured=p, theory=theory, norm_error=norm_error)) psi[marked] *= -1 # table phase oracle psi = 2 * float(np.mean(psi)) - psi # diffusion peak = max(r['measured'] for r in rows) best = next(r['k'] for r in rows if abs(r['measured'] - peak) < 1e-12) if M and M != N: # The integer nearest the first continuous peak need not be the # highest sampled point across two oscillation periods: later peaks # can round closer to their continuous maxima. Every point is checked # against the exact formula above; retain the measured global maximum. assert 0 <= k_first < len(rows) return dict(M=M, theta=theta, k_asymptotic=asym, k_first_peak_real=exact, k_opt_theory=k_first, k_best_measured=best, peak_measured=peak, peak_theory=max(r['theory'] for r in rows), max_probability_error=max(abs(r['measured']-r['theory']) for r in rows), curve=rows) def small_tests(seed, n_in): rng = random.Random(seed) results = [] N = 1 << n_in messages = [j.to_bytes((n_in + 7)//8, 'big') for j in range(N)] for w in (2, 3, 4): start = time.perf_counter() table = np.array([digest_w(m, w) for m in messages], dtype=np.uint16) # Recompute independently of the stored table. Check every output bit # and every selected equality predicate over the entire input domain. reference = np.array([digest_w(m, w) for m in messages], dtype=np.uint16) assert np.array_equal(table, reference) trials = [] for target in rng.sample(range(1 << (2*w)), 5): marked = table == target assert np.array_equal(marked, reference == target) probe = np.arange(N, dtype=np.float64) + .25 saved = probe.copy() probe[marked] *= -1 assert np.array_equal(probe, np.where(reference == target, -saved, saved)) probe[marked] *= -1 assert np.array_equal(probe, saved) result = grover_curve(marked) result.update(target=target, oracle_inputs_checked=N) trials.append(result) result = dict(claim='MEASURED', w=w, output_bits=2*w, input_qubits=n_in, N=N, targets=trials, oracle='table oracle, not a gate-level circuit', output_histogram=np.bincount(table, minlength=1 << (2*w)).tolist(), seconds=time.perf_counter()-start, passed=True) results.append(result) print(f'MEASURED w={w}: {N} inputs, five targets, all curves passed; ' f'{result["seconds"]:.2f}s', flush=True) # Boundary cases exercise simulator handling independently of RL. controls = {str(m): grover_curve(np.arange(16) < m) for m in (0, 1, 8, 16)} return results, controls def rad_and_stamp_toy_tests(seed, n_in): """Python-only toy prefix oracles; curves test Grover mechanics, not RL security.""" rng = random.Random(seed ^ 0x5241445354414d50) N = 1 << n_in messages = [j.to_bytes((n_in + 7)//8, 'big') for j in range(N)] rows = [] for w in (2, 3, 4): output_bits = 2*w table = np.array([digest_w(m, w) for m in messages], dtype=np.uint16) # Given-Rad toy task: fix one candidate's prefix and find another # candidate with the same prefix. This is not a bounded-domain cost model. given = rng.randrange(N) z_rad = w prefix = int(table[given]) >> (output_bits-z_rad) rad_marked = (table >> (output_bits-z_rad)) == prefix rad_marked[given] = False rad = grover_curve(rad_marked) rad.update(given_index=given, target_prefix=prefix, z=z_rad) # Toy commitment-stamp task: find a zero-prefix hash. The production # 18-bit estimate is reported separately; these widths are only controls. z_stamp = min(3, output_bits) stamp_marked = (table >> (output_bits-z_stamp)) == 0 stamp = grover_curve(stamp_marked) stamp.update(target_prefix=0, z=z_stamp) rows.append(dict(claim='MEASURED', w=w, output_bits=output_bits, N=N, oracle='Python-only width-scaled table oracle', given_rad_prefix=rad, toy_zero_prefix_stamp=stamp)) print(f'MEASURED toy Rad/stamp oracles w={w}: ' f'Rad M={rad["M"]}, stamp M={stamp["M"]}', flush=True) return rows def wo14_cost_estimates(): def rad_cost(z): expected_pairs = (2**20)*(2**20-1) / 2**(z+1) return dict(z=z, bht_query_scale=2**(z/3), fixed_prefix_grover_scale=2**(z/2), fixed_prefix_grover_first_peak=(math.pi/4)*2**(z/2), ideal_expected_pairs_per_bounded_puzzle=expected_pairs, poisson_probability_of_any_pair=1-math.exp(-expected_pairs)) return dict(claim='ESTIMATE', model='ideal random-function oracle queries; no quantum hardware or circuit costs', z_base=41, base=rad_cost(41), collision_trigger_z=48, collision_trigger=rad_cost(48), commit_stamp=dict(z=18, grover_query_scale=2**9, grover_first_peak=(math.pi/4)*2**9, predicate='18 leading zero bits in the 55-byte commitment stamp hash'), limitations=['BHT scale is asymptotic and conditional on the ideal collision model; the bounded S=2^20 puzzle can contain no pair.', 'The fixed-prefix Grover scale assumes a generic search domain with marked density 2^-Z; it is not the cost of searching an already-known WO-08 Rad pair within its bounded candidate set.', 'The stamp estimate excludes oracle synthesis, error correction, restarts and wall-clock conversion.']) # Circuit wires are nonnegative integers; -1 and -2 mean constant 0 and 1. ZERO, ONE = -1, -2 class Circuit: """Explicit reversible X/CNOT/Toffoli list, fresh zero-target arithmetic.""" def __init__(self, inputs): self.inputs = self.n = inputs self.gates = [] def fresh(self): r = self.n self.n += 1 return r def xor(self, a, b): if a == ZERO: return b if b == ZERO: return a if a == b: return ZERO if a == ONE and b == ONE: return ZERO t = self.fresh() for x in (a, b): self.gates.append((t,) if x == ONE else (x, t)) return t def both(self, a, b): if ZERO in (a, b): return ZERO if a == ONE: return b if b == ONE or a == b: return a t = self.fresh() self.gates.append((a, b, t)) return t def const(self, n, width): return [ONE if (n >> j) & 1 else ZERO for j in range(width)] def add(self, a, b): carry, out = ZERO, [] for j, (x, y) in enumerate(zip(a, b)): p = self.xor(x, y) out.append(self.xor(p, carry)) if j + 1 < len(a): carry = self.xor(self.both(x, y), self.both(p, carry)) return out def select_constants(self, control, yes, no, width): a, b = self.const(yes, width), self.const(no, width) return [self.xor(y, self.both(control, self.xor(x, y))) for x, y in zip(a, b)] def clean(self, outputs): forward = list(self.gates) copied = [] for x in outputs: t = self.fresh() copied.append(t) if x != ZERO: self.gates.append((t,) if x == ONE else (x, t)) self.gates.extend(reversed(forward)) return copied def counts(self): counts = Counter(len(g) for g in self.gates) levels = [0] * self.n for g in self.gates: level = 1 + max(levels[x] for x in g) for x in g: levels[x] = level return dict(qubits=self.n, input_qubits=self.inputs, X=counts[1], CNOT=counts[2], Toffoli=counts[3], T_count_convention_7_per_Toffoli=7*counts[3], logical_gate_depth=max(levels), T_depth_conservative_bound=7*max(levels)) def evaluate(self, inputs, ones=1): wires = list(inputs) + [0] * (self.n - len(inputs)) for g in self.gates: if len(g) == 1: wires[g[0]] ^= ones elif len(g) == 2: wires[g[1]] ^= wires[g[0]] else: wires[g[2]] ^= wires[g[0]] & wires[g[1]] return wires def rotate(a, n): n %= len(a) return a[-n:] + a[:-n] if n else a[:] def lane(c, x, v, xn, vn, bit, i, l, w): W = w + 16 assert 0 <= i < 600 and w >= 2 # For these counters, signed W-bit z and clamp cannot overflow. assert 275*((1 << w)-1) + 16*(i+4) + 16 < 1 << (W-1) ext = lambda a: a + [ZERO] * (W-len(a)) sh = lambda a, n: ([ZERO]*n + a)[:len(a)] xe, ve = ext(x), ext(v) z = c.add(c.add(xe, sh(xe, 8)), c.add(ve, sh(ve, 4))) z = c.add(z, c.select_constants(bit, 16*(i+l+1), -16*(i+l+1), W)) z = c.add(z, ext(rotate(xn, 11+7*l))) zero = ONE for t in z: zero = c.both(zero, c.xor(t, ONE)) # OR(sign, is_zero) = a XOR b XOR (a AND b). neg = c.xor(c.xor(z[-1], zero), c.both(z[-1], zero)) oz = c.add(z, c.select_constants(neg, -16, 15, W)) r = oz[:w] # Arithmetic floor division; sign extend when extracting above W. q = (oz[w:] + [oz[-1]]*w)[:w] vv = c.add(v, sh(v, 16)) for a in (q, c.select_constants(bit, 16, -16, w), c.const(i+l+1, w), vn): vv = c.add(vv, a) vv = [c.xor(a,b) for a,b in zip(rotate(vv,13+8*l), r)] xx = [c.xor(a,b) for a,b in zip(r, rotate(vv,(i+11*l)%(w-1)+1))] return xx + vv def step_circuit(w, i): c = Circuit(8*w+1) xs = [list(range(l*w,(l+1)*w)) for l in range(4)] vs = [list(range((l+4)*w,(l+5)*w)) for l in range(4)] outputs = [] for l in range(4): outputs += lane(c, xs[l], vs[l], xs[(l+1)%4], vs[(l+1)%4], 8*w, i, l, w) outputs = c.clean(outputs) return c, outputs def packed_planes(values, bits): return [int.from_bytes(np.packbits(((values >> j) & 1).astype(np.uint8), bitorder='little').tobytes(), 'little') for j in range(bits)] def circuit_tests(seed): start = time.perf_counter() w, n = 4, 1 << 17 indices = np.arange(n, dtype=np.uint32) planes = packed_planes(indices, 17) ones = (1 << n)-1 cases = [] # Each lane depends on only 4 words and 1 bit. Exhaust these local domains # to cover all 2^33 full-step inputs compositionally at each fixed counter. for i in (0, 64, 599): for l in range(4): c = Circuit(17) out = lane(c, list(range(4)), list(range(4,8)), list(range(8,12)), list(range(12,16)), 16, i, l, w) out = c.clean(out) actual = c.evaluate(planes, ones) expected = np.empty(n, dtype=np.uint16) for j in range(n): xs, vs = [0]*4, [0]*4 xs[l], vs[l] = j & 15, (j >> 4) & 15 xs[(l+1)%4], vs[(l+1)%4] = (j >> 8) & 15, (j >> 12) & 15 nx, nv = step(xs, vs, j >> 16, i, w) expected[j] = nx[l] | (nv[l] << 4) assert [actual[x] for x in out] == packed_planes(expected, 8) assert actual[:17] == planes assert all(actual[j] == 0 for j in range(17, out[0])) cases.append(dict(i=i, lane=l, assignments=n, passed=True)) print(f'MEASURED reversible w=4, i={i}: all four lane domains passed', flush=True) rng = random.Random(seed ^ 0x524c) resources = [] for w in (4,64): for i in (0,64,599): c, outputs = step_circuit(w, i) samples = [([rng.getrandbits(w) for _ in range(4)], [rng.getrandbits(w) for _ in range(4)], rng.randrange(2)) for _ in range(32)] # Include negative/zero clamp inputs, maxima and carry boundaries; # random 64-bit states almost never enter the negative branch. samples += [([a]*4, [b]*4, bit) for a in (0,1,(1<> j)&1 for v in xs+vs for j in range(w)] + [bit] wires = c.evaluate(inputs) nx, nv = step(xs, vs, bit, i, w) expected = [v for pair in zip(nx,nv) for v in pair] actual = [sum(wires[outputs[k*w+j]] << j for j in range(w)) for k in range(8)] assert expected == actual and wires[:c.inputs] == inputs assert not any(wires[c.inputs:outputs[0]]) resources.append(dict(w=w, i=i, **c.counts(), full_step_random_cases=32, full_step_boundary_cases=18)) return dict(claim='MEASURED', model='reversible X/CNOT/Toffoli Boolean circuit; classical basis-state simulation', exhaustive_lane_checks=cases, full_step_counts=resources, full_step_exhaustive_counters=[0,64,599], scope='Compositional exhaustive w=4 verification at three counters, not all counters. w=64 sampled only. No quantum hardware.', seconds=time.perf_counter()-start) def resource_estimate(): # Synthesize every counter at w=64. Gate counting is not a quantum run. rows = [] for i in range(600): c, _ = step_circuit(64, i) rows.append(c.counts()) forward_t = sum(r['T_count_convention_7_per_Toffoli'] for r in rows) forward_depth = sum(r['logical_gate_depth'] for r in rows) scratch = max(r['qubits'] - r['input_qubits'] - 512 for r in rows) iterations = math.pi / 4 * 2**64 # A deliberately loose complete RL-only envelope. Fixed-length RS encoding # is affine over bits: at most one X and 352 CNOTs per record bit. # Comparator: compute 128-bit AND into 127 clean ancillas, Z, uncompute. # Diffuser on 256 variable key bits: analogous 255-ancilla AND ladder. # These surrounding stages are budgeted algebraically, not gate-simulated. tag_bytes = len(b'E46-address\0') input_bytes = tag_bytes + 32 message_bits = 8 * input_bytes assert input_bytes == 44 and 8*(8+6*math.ceil(input_bytes/4))+8 == 600 encoding_depth_bound = 600 * (message_bits+1) comparator_toffoli = 2*127 diffuser_toffoli = 2*255 phase_t = 2*forward_t + 7*comparator_toffoli iteration_t = phase_t + 7*diffuser_toffoli phase_depth = 2*forward_depth + 2*encoding_depth_bound + 2*512 + 257 + 2 iteration_depth = phase_depth + 515 qubit_bound = 601*512 + scratch + 1 + message_bits + 600 + 128 + 255 return dict(claim='ESTIMATE', scope='Unoptimized logical RL-only oracle envelope; NOT a complete concatenated-address oracle or physical machine estimate', domain_tag_bytes=tag_bytes, input_bytes=input_bytes, steps=600, architecture='Retain 601 state registers; clean each out-of-place step; reuse arithmetic scratch; reverse the entire history after marking.', per_counter_counts=rows, forward_hash_step_T_count=forward_t, phase_oracle_step_T_count=2*forward_t, phase_oracle_step_logical_depth_serial_steps=2*forward_depth, phase_oracle_step_T_depth_conservative_bound=14*forward_depth, step_history_plus_scratch_qubits=601*512+scratch+1, step_subtotal_excludes='Message and record encoding, folding, target comparator and diffusion.', all_estimates_exclude='SHAKE256, public-key generation, routing, error correction and magic-state factories.', grover_iterations_ideal_RL128=iterations, full_search_step_T_count=iterations*2*forward_t, full_search_step_logical_depth=iterations*2*forward_depth, full_search_step_T_depth_bound=iterations*14*forward_depth, rl_only_envelope=dict(claim='ESTIMATE', scope='Arithmetic circuits synthesized; encoding/comparator/diffusion budgeted but NOT integrated or simulated.', variable_input_bits=256, fixed_tag_bits=8*tag_bytes, encoding_forward_X_CNOT_depth_bound=encoding_depth_bound, comparator_Toffoli=comparator_toffoli, diffuser_Toffoli=diffuser_toffoli, logical_qubits_bound=qubit_bound, phase_oracle_T_count=phase_t, iteration_including_diffuser_T_count=iteration_t, phase_oracle_logical_depth_bound=phase_depth, iteration_including_diffuser_logical_depth_bound=iteration_depth, iteration_T_depth_bound=14*forward_depth+7*(comparator_toffoli+diffuser_toffoli), full_search_T_count=iterations*iteration_t, full_search_logical_depth_bound=iterations*iteration_depth), complete_concatenated_address_oracle_logical_qubits=None, complete_concatenated_address_oracle_T_count=None, complete_concatenated_address_oracle_depth=None, ideal_joint_256_iterations=math.pi/4*2**128, caveat='Ideal random-function estimates are hypotheses for RL; concatenation alone does not prove 128-bit quantum preimage security.') def main(): ap = argparse.ArgumentParser(description=__doc__) ap.add_argument('--seed', type=int, default=20261007) ap.add_argument('--input-qubits', type=int, choices=range(8,13), default=12) ap.add_argument('--rl-cli', default='build/release/rl_cli') ap.add_argument('--skip-circuit', action='store_true') args = ap.parse_args() start = time.perf_counter() names = ['grover_rl_test.py','phase3_sat_attacks.py','refracting_light_v3.py', 'refracting-light-v3-test-vectors.json','REFRACTING-LIGHT-SPEC.md', 'CHAIN-DESIGN.md','e46/include/e46/rl.hpp','e46/src/rl.cpp', 'e46/tools/rl_cli.cpp'] provenance = {name:hashlib.shake_256((ROOT/name).read_bytes()).hexdigest(32) for name in names} result = dict(experiment='RL-v3-Grover-2026-10', claim='MEASURED', hardware='CLASSICAL CPU SIMULATION ONLY', seed=args.seed, workers=1, python=platform.python_version(), numpy=np.__version__, provenance_shake256_256=provenance, encoding='Unsigned integer in fixed-width big-endian bytes; unused high bits zero', limitations=['Table oracle does not test structural quantum attacks.', 'Toy w<=16 loses the 65537 multiplier modulo the word size.', 'w=2 retains reference seeds 5 and 7; these exceed a two-bit word before the first step.']) result['vectors'] = vector_checks() result['librl_crosscheck'] = librl_crosscheck(args.seed,args.input_qubits,args.rl_cli) result['widths'], result['simulator_boundary_controls'] = small_tests(args.seed,args.input_qubits) result['rad_and_stamp_toy_tests'] = rad_and_stamp_toy_tests(args.seed,args.input_qubits) result['wo14_quantum_costs'] = wo14_cost_estimates() if not args.skip_circuit: result['circuit'] = circuit_tests(args.seed) result['resources'] = resource_estimate() else: result['circuit'] = dict(status='NOT RUN') result['resources'] = dict(status='NOT RUN') result['seconds'] = time.perf_counter()-start result['completed_utc'] = datetime.now(timezone.utc).isoformat() result['passed'] = True out = ROOT/'phase3-runs'/('grover-'+datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')+'.json') with out.open('x') as f: json.dump(result,f,indent=2,allow_nan=False); f.write('\n') print(f'MEASURED all checks passed in {result["seconds"]:.2f}s\nEvidence: {out}',flush=True) if __name__ == '__main__': main()