The public history carried kat/__pycache__/mlkem768_reference.cpython-314.pyc, a compiled Python artifact embedding the operator's absolute local path. Text secret scanners do not read compiled binaries, which is exactly how it slipped through, and removing it from the tip would have left it reachable through the old root commits. So this repository is republished from a single clean root. This root also carries, from the previously unpublished line of work: - corrected LICENSE year, LICENSING.md, VERIFY-POLICY.md, and CITATIONS-UNRESOLVED.md remeasured 2026-08-11 (101 paths, README aligned) - O-018: run_consensus_verification.py ran 19 of 29 models and reported PASS; it now runs all 29, and computemarket_smt.py gains resolveByTimeout / reclaimUnsettled cases plus a negative control - O-006: the word 'audited' removed from next to Bouncy Castle, twice, after a concurrent edit resurrected it - O-014: prior art named and dated - Algorand's native falcon_verify shipped about ten months before AERE's precompiles; the primacy claim is withdrawn where it was implied - bench/ scripts parametrized so they actually run for an outsider (the earlier textual sanitization left $STAGING unexpanded inside Python strings) - AIP-2/AIP-3 errata with measured figures, spec remeasurements at 2026-08-01, and the spec-zk-stack retractions (owner is an operational key, not the Foundation; 'maximally sound' withdrawn; aggregator V1 deprecated) The redacted bench-host environment files from the sanitized line are kept exactly as published; the unredacted local variants are not carried.
164 lines
7.6 KiB
Python
164 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
# -----------------------------------------------------------------------------
|
|
# cryptoregistry_smt.py
|
|
#
|
|
# SMT RE-CONFIRMATION (z3) of the AereCryptoRegistry governance/routing invariants,
|
|
# the LIVE (mainnet chain 2800) crypto-agility registry at
|
|
# 0xaE6fC596bb3eCcbf5c5D02D67B0Ef065b3Afbaa5
|
|
#
|
|
# Contract: contracts/contracts/pqc/AereCryptoRegistry.sol
|
|
#
|
|
# These are the SAME properties the halmos bytecode suite proves
|
|
# (contracts/test/formal/AereCryptoRegistry.symbolic.t.sol, R1-R3), re-confirmed here
|
|
# at the DESIGN level with z3 so the whole PQC set has one runnable, dependency-light
|
|
# proof (halmos/forge are not installed in this environment; z3 4.16 is). This does NOT
|
|
# replace the bytecode-level halmos check -- it complements it, exactly like the other
|
|
# formal-consensus/*.py models.
|
|
#
|
|
# TARGET INVARIANTS:
|
|
# R1 FAIL-CLOSED: verify() returns false for an UNKNOWN or REVOKED id (and for a
|
|
# hash-only row, isSignature==false), BEFORE routing to any verifier -- for any
|
|
# pubkey/message/signature, regardless of what the precompile would return.
|
|
# R3 resolveActive returns ONLY an ACTIVE id and TERMINATES within the bounded walk
|
|
# (returns ACTIVE or reverts, even on a cycle).
|
|
#
|
|
# NEG-CTRLs fire for each. ASSUMPTIONS: A2 (status gate is evaluated before the
|
|
# staticcall -- true in the source), design model not bytecode (A3).
|
|
# -----------------------------------------------------------------------------
|
|
from z3 import (Int, Bool, Array, IntSort, Select, Solver, And, Or, Not, If,
|
|
Distinct, sat, unsat)
|
|
|
|
UNKNOWN, ACTIVE, DEPRECATED, REVOKED = 0, 1, 2, 3
|
|
N = 6
|
|
|
|
results = []
|
|
def check(name, s, expect_unsat=True, kind="PROOF"):
|
|
r = s.check()
|
|
if expect_unsat:
|
|
ok = (r == unsat); tag = "PROVED" if ok else "FAILED"
|
|
else:
|
|
ok = (r == sat); tag = "CEX-FOUND" if ok else "FAILED"
|
|
results.append((name, tag, ok, kind))
|
|
print(f"[{tag:9}] ({kind}) {name}: z3={r} (expected {'unsat' if expect_unsat else 'sat'})")
|
|
if r == sat:
|
|
m = s.model()
|
|
wit = {}
|
|
for d in m.decls():
|
|
try: wit[str(d)] = m[d].as_long()
|
|
except Exception:
|
|
try: wit[str(d)] = bool(m[d])
|
|
except Exception: wit[str(d)] = "?"
|
|
print(" witness:", wit)
|
|
return ok
|
|
|
|
print("### AereCryptoRegistry -- re-confirm R1 fail-closed + R3 resolveActive (z3 design layer)\n")
|
|
|
|
# verify() gate, faithful to the source order:
|
|
# if !exists return false
|
|
# if !isSignature return false
|
|
# if status in {UNKNOWN, REVOKED} return false (ACTIVE / DEPRECATED proceed)
|
|
# ... length checks ... then staticcall -> precompile bool
|
|
def verify_model(exists, is_sig, status, lengths_ok, precompile_says):
|
|
return And(exists, is_sig,
|
|
Not(Or(status == UNKNOWN, status == REVOKED)),
|
|
lengths_ok, precompile_says)
|
|
|
|
# ---- R1a : a REVOKED id never verifies (any inputs / any precompile output) -------
|
|
s = Solver()
|
|
is_sig, lengths_ok, precompile = Bool('isSig'), Bool('lengthsOk'), Bool('precompileSaysValid')
|
|
s.add(verify_model(True, is_sig, REVOKED, lengths_ok, precompile))
|
|
check("R1a REVOKED id never verifies (fail-closed before routing)", s)
|
|
|
|
# ---- R1b : an UNKNOWN id never verifies ------------------------------------------
|
|
s = Solver()
|
|
is_sig, lengths_ok, precompile = Bool('isSig'), Bool('lengthsOk'), Bool('precompileSaysValid')
|
|
s.add(verify_model(False, is_sig, UNKNOWN, lengths_ok, precompile)) # !exists
|
|
check("R1b UNKNOWN / non-existent id never verifies", s)
|
|
|
|
# ---- R1c : a hash-only row (isSignature==false, e.g. SHAKE256) never verifies -----
|
|
s = Solver()
|
|
lengths_ok, precompile = Bool('lengthsOk'), Bool('precompileSaysValid')
|
|
s.add(verify_model(True, False, ACTIVE, lengths_ok, precompile)) # isSignature == false
|
|
check("R1c hash-only ACTIVE row (SHAKE256) never verifies as a signature", s)
|
|
|
|
# sanity: an ACTIVE signature row CAN verify when the precompile accepts (not vacuous)
|
|
s = Solver()
|
|
s.add(verify_model(True, True, ACTIVE, True, True))
|
|
check("R1-sanity an ACTIVE signature row verifies when lengths ok + precompile accepts", s,
|
|
expect_unsat=False, kind="SANITY")
|
|
|
|
# ---- NEG-CTRL R1: a verify that routes to the precompile BEFORE the status gate
|
|
# would let a REVOKED row verify. ---------------------------------------------
|
|
s = Solver()
|
|
precompile = Bool('precompileSaysValid')
|
|
buggy = precompile # BUG: no status gate
|
|
s.add(buggy, precompile == True) # revoked row, precompile happens to accept
|
|
check("NEG-CTRL a verify() that skips the status gate lets a REVOKED row verify", s,
|
|
expect_unsat=False, kind="NEG-CTRL")
|
|
|
|
# ---- R3 : resolveActive returns ONLY an ACTIVE id, and terminates -----------------
|
|
status = Array('status', IntSort(), IntSort())
|
|
successor = Array('successor', IntSort(), IntSort())
|
|
exists = Array('exists', IntSort(), IntSort())
|
|
|
|
def _walk(id_expr, hop):
|
|
if hop > N:
|
|
return (True, id_expr) # SuccessorCycle revert
|
|
ex = Select(exists, id_expr); st = Select(status, id_expr); nx = Select(successor, id_expr)
|
|
adv_rev, adv_id = _walk(nx, hop + 1)
|
|
rev = If(Not(ex == 1), True, If(st == ACTIVE, False, If(nx == 0, True, adv_rev)))
|
|
rid = If(Not(ex == 1), id_expr, If(st == ACTIVE, id_expr, If(nx == 0, id_expr, adv_id)))
|
|
return (rev, rid)
|
|
|
|
s = Solver()
|
|
startId = Int('startId')
|
|
rev, resolved = _walk(startId, 0)
|
|
s.add(Not(rev)) # returned a value
|
|
s.add(Select(status, resolved) != ACTIVE) # claim it is non-ACTIVE
|
|
check("R3a resolveActive returns ONLY an ACTIVE id", s)
|
|
|
|
s = Solver()
|
|
startId = Int('startId2')
|
|
rev, resolved = _walk(startId, 0)
|
|
s.add(Not(Or(rev, Not(rev)))) # totality: always halts
|
|
check("R3b resolveActive is total within the loop bound (always ACTIVE or revert)", s)
|
|
|
|
# pigeonhole: no acyclic non-ACTIVE walk of length _count+1 (cycle revert is sound)
|
|
s = Solver()
|
|
vs = [Int(f'v{i}') for i in range(N + 1)]
|
|
for v in vs:
|
|
s.add(v >= 1, v <= N)
|
|
s.add(Distinct(*vs))
|
|
check("R3c pigeonhole: no acyclic successor walk of length _count+1 over _count rows", s)
|
|
|
|
# ---- NEG-CTRL R3: a fixed too-small loop bound (K < _count) can return before ACTIVE
|
|
# or miss a reachable ACTIVE row -> unsound. Demonstrate a 1-hop resolver that
|
|
# returns a non-ACTIVE id. -----------------------------------------------------
|
|
s = Solver()
|
|
st0 = Int('status0')
|
|
s.add(st0 != ACTIVE)
|
|
buggy_resolved_status = st0 # BUG: returns row 0 without walking
|
|
s.add(buggy_resolved_status != ACTIVE)
|
|
check("NEG-CTRL a resolver that returns the start row unconditionally yields a non-ACTIVE id", s,
|
|
expect_unsat=False, kind="NEG-CTRL")
|
|
|
|
# ------------------------------------------------------------------------------
|
|
print("\n=== SUMMARY ===")
|
|
allok = True
|
|
for name, tag, ok, kind in results:
|
|
print(f" {tag:9} [{kind}] {name}")
|
|
allok = allok and ok
|
|
print()
|
|
print("AereCryptoRegistry (design-level re-confirmation of the halmos R1/R3 suite):")
|
|
if allok:
|
|
print(" RE-CONFIRMED: verify() is fail-closed for UNKNOWN / REVOKED / hash-only rows")
|
|
print(" before any routing (R1a/b/c, any precompile output); resolveActive returns")
|
|
print(" ONLY an ACTIVE id and always terminates within the _count-bounded walk")
|
|
print(" (R3a/b/c pigeonhole). NEG-CTRLs confirm the status gate and the bounded walk")
|
|
print(" are load-bearing. The authoritative BYTECODE proof remains the halmos suite")
|
|
print(" in contracts/test/formal/AereCryptoRegistry.symbolic.t.sol (R1-R3).")
|
|
else:
|
|
print(" NOT fully established (see FAILED / unexpected result above).")
|
|
import sys
|
|
sys.exit(0 if allok else 1)
|