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.
245 lines
14 KiB
Python
245 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
# -----------------------------------------------------------------------------
|
|
# pqaggregate_smt.py
|
|
#
|
|
# SMT proof (z3) of the FAIL-CLOSED authorization guards of AerePQAggregateVerifier,
|
|
# the constant-cost (O(1)-in-n) on-chain verification of a t-of-n POST-QUANTUM
|
|
# committee authorization via ONE succinct SP1 proof. Plus load-bearing NEGATIVE
|
|
# CONTROLS for the committee-root binding and the message-digest binding.
|
|
#
|
|
# Contract: contracts/contracts/mpc/AerePQAggregateVerifier.sol
|
|
#
|
|
# A relying party registers a committee once (root, threshold t, size n, scheme),
|
|
# and thereafter every authorization is a SINGLE gateway verifyProof call.
|
|
# verifyAggregate() ACCEPTS an authorization ONLY when every fail-closed guard
|
|
# holds (else it reverts):
|
|
# G0 committee registered: c.exists (UnknownCommittee)
|
|
# G1 committee-root binding: provenRoot == c.root (CommitteeRootMismatch)
|
|
# G2 threshold binding: provenThreshold == c.threshold (ThresholdMismatch)
|
|
# G3 size binding: provenSize == c.size (SizeMismatch)
|
|
# G4 scheme binding: provenScheme == c.scheme (SchemeMismatch)
|
|
# G5 message-digest binding: provenDigest == messageDigest (MessageDigestMismatch)
|
|
# G6 threshold met: provenValidCount >= c.threshold (BelowThreshold)
|
|
# G7 SP1 proof verifies through the pinned vkey + gateway (verifyProof reverts on bad proof)
|
|
# registerCommittee() enforces at registration time:
|
|
# R1 root != 0 (ZeroRoot), R2 1 <= t <= n (BadCommitteeParams), R3 scheme != 0 (BadScheme).
|
|
#
|
|
# We model the guards as first-order constraints over the decoded public-values
|
|
# fields and prove each safety property by asserting its NEGATION under the guards
|
|
# and showing z3 returns UNSAT. Each NEG-CTRL removes exactly one guard and shows
|
|
# the corresponding bypass becomes satisfiable (SAT). Roots / digests are Int
|
|
# identities (equality / disequality only). keccak256 is modelled as an INJECTIVE
|
|
# uninterpreted function (collision-resistance, [VERIFY]) where the committee-root
|
|
# argument rests on "a different key set gives a different root". This checks the
|
|
# DESIGN-level guard logic, NOT the compiled EVM bytecode.
|
|
# -----------------------------------------------------------------------------
|
|
from z3 import (Int, Function, IntSort, Solver, And, Or, Not, Implies, ForAll,
|
|
sat, unsat)
|
|
|
|
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()
|
|
print(" witness:", {str(d): m[d] for d in m.decls()})
|
|
return ok
|
|
|
|
def add_guards(s, exists, provenRoot, cRoot, provenT, cT, provenSize, cSize,
|
|
provenScheme, cScheme, provenDigest, msgDigest, provenValid,
|
|
g_root=True, g_digest=True):
|
|
"""Assert verifyAggregate()'s fail-closed guards. Flags let a NEG-CTRL drop one."""
|
|
s.add(exists == 1) # G0 committee registered
|
|
if g_root:
|
|
s.add(provenRoot == cRoot) # G1
|
|
s.add(provenT == cT) # G2
|
|
s.add(provenSize == cSize) # G3
|
|
s.add(provenScheme == cScheme) # G4
|
|
if g_digest:
|
|
s.add(provenDigest == msgDigest) # G5
|
|
s.add(provenValid >= cT) # G6
|
|
|
|
print("### AerePQAggregateVerifier -- FAIL-CLOSED t-of-n committee authorization\n")
|
|
|
|
# ---- R0 REGISTRATION well-formedness: an accepted committee has 1 <= t <= n, a
|
|
# non-zero root, and a non-zero scheme. Negation: a registered committee
|
|
# violates one. UNSAT under R1/R2/R3. --------------------------------------
|
|
s = Solver()
|
|
root, t, n, scheme = Int('root'), Int('t'), Int('n'), Int('scheme')
|
|
s.add(root != 0) # R1 ZeroRoot
|
|
s.add(n >= 1, t >= 1, t <= n) # R2 BadCommitteeParams
|
|
s.add(scheme != 0) # R3 BadScheme
|
|
s.add(Or(root == 0, t < 1, t > n, n < 1, scheme == 0)) # NEGATION
|
|
check("R0 a registered committee is well-formed (root!=0, 1<=t<=n, scheme!=0)", s)
|
|
|
|
# ---- P1 COMMITTEE-ROOT BINDING: an accepted authorization's committee root is
|
|
# EXACTLY the registered committee's root, so the accepted committee is the
|
|
# registered one. Negation: accepted yet provenRoot != c.root. UNSAT (G1). ---
|
|
s = Solver()
|
|
exists = Int('exists')
|
|
provenRoot, cRoot = Int('provenRoot'), Int('cRoot')
|
|
provenT, cT = Int('provenT'), Int('cT')
|
|
provenSize, cSize = Int('provenSize'), Int('cSize')
|
|
provenScheme, cScheme = Int('provenScheme'), Int('cScheme')
|
|
provenDigest, msgDigest = Int('provenDigest'), Int('msgDigest')
|
|
provenValid = Int('provenValid')
|
|
add_guards(s, exists, provenRoot, cRoot, provenT, cT, provenSize, cSize,
|
|
provenScheme, cScheme, provenDigest, msgDigest, provenValid)
|
|
s.add(provenRoot != cRoot) # NEGATION: a different key set accepted
|
|
check("P1 accepted committee is EXACTLY the registered committee (root binding)", s)
|
|
|
|
# ---- P1b KEY-SET binding via injectivity: the committee root is
|
|
# keccak(canonical key list), modelled as an INJECTIVE function K(keySet). A
|
|
# proof over a DIFFERENT key set has provenRoot = K(keySet2) != K(keySet1) =
|
|
# c.root, so G1 rejects it. Negation: a foreign-key-set authorization accepted.
|
|
K = Function('K', IntSort(), IntSort()) # committee root = keccak(key set)
|
|
s = Solver()
|
|
s.add(ForAll([Int('a'), Int('b')],
|
|
Implies(K(Int('a')) == K(Int('b')), Int('a') == Int('b'))))
|
|
keySet1, keySet2 = Int('keySet1'), Int('keySet2')
|
|
cRoot = K(keySet1)
|
|
provenRoot = K(keySet2)
|
|
exists = Int('exists')
|
|
provenT, cT = Int('provenT'), Int('cT')
|
|
provenSize, cSize = Int('provenSize'), Int('cSize')
|
|
provenScheme, cScheme = Int('provenScheme'), Int('cScheme')
|
|
provenDigest, msgDigest = Int('provenDigest'), Int('msgDigest')
|
|
provenValid = Int('provenValid')
|
|
add_guards(s, exists, provenRoot, cRoot, provenT, cT, provenSize, cSize,
|
|
provenScheme, cScheme, provenDigest, msgDigest, provenValid)
|
|
s.add(keySet2 != keySet1) # a DIFFERENT authorized key set
|
|
check("P1b an authorization over a different key set is rejected (root injectivity)", s)
|
|
|
|
# ---- P2 MESSAGE-DIGEST BINDING: an accepted authorization's proven digest equals
|
|
# the digest the caller is authorizing, so a proof for message A cannot
|
|
# authorize message B. Negation: accepted yet provenDigest != messageDigest.
|
|
# UNSAT (G5). ----------------------------------------------------------------
|
|
s = Solver()
|
|
exists = Int('exists')
|
|
provenRoot, cRoot = Int('provenRoot'), Int('cRoot')
|
|
provenT, cT = Int('provenT'), Int('cT')
|
|
provenSize, cSize = Int('provenSize'), Int('cSize')
|
|
provenScheme, cScheme = Int('provenScheme'), Int('cScheme')
|
|
provenDigest, msgDigest = Int('provenDigest'), Int('msgDigest')
|
|
provenValid = Int('provenValid')
|
|
add_guards(s, exists, provenRoot, cRoot, provenT, cT, provenSize, cSize,
|
|
provenScheme, cScheme, provenDigest, msgDigest, provenValid)
|
|
s.add(provenDigest != msgDigest) # NEGATION: wrong-message proof accepted
|
|
check("P2 accepted authorization is bound to the caller's message digest", s)
|
|
|
|
# ---- P3 THRESHOLD MET: an accepted authorization proves >= t distinct valid
|
|
# signatures. Negation: accepted yet provenValidCount < c.threshold. UNSAT (G6).
|
|
s = Solver()
|
|
exists = Int('exists')
|
|
provenRoot, cRoot = Int('provenRoot'), Int('cRoot')
|
|
provenT, cT = Int('provenT'), Int('cT')
|
|
provenSize, cSize = Int('provenSize'), Int('cSize')
|
|
provenScheme, cScheme = Int('provenScheme'), Int('cScheme')
|
|
provenDigest, msgDigest = Int('provenDigest'), Int('msgDigest')
|
|
provenValid = Int('provenValid')
|
|
add_guards(s, exists, provenRoot, cRoot, provenT, cT, provenSize, cSize,
|
|
provenScheme, cScheme, provenDigest, msgDigest, provenValid)
|
|
s.add(provenValid < cT) # NEGATION: below-threshold accept
|
|
check("P3 accepted authorization meets the committee threshold t (>= t signatures)", s)
|
|
|
|
# ---- P4 SIZE + SCHEME defense-in-depth binding. Negation UNSAT (G3 / G4). -----
|
|
s = Solver()
|
|
exists = Int('exists')
|
|
provenRoot, cRoot = Int('provenRoot'), Int('cRoot')
|
|
provenT, cT = Int('provenT'), Int('cT')
|
|
provenSize, cSize = Int('provenSize'), Int('cSize')
|
|
provenScheme, cScheme = Int('provenScheme'), Int('cScheme')
|
|
provenDigest, msgDigest = Int('provenDigest'), Int('msgDigest')
|
|
provenValid = Int('provenValid')
|
|
add_guards(s, exists, provenRoot, cRoot, provenT, cT, provenSize, cSize,
|
|
provenScheme, cScheme, provenDigest, msgDigest, provenValid)
|
|
s.add(Or(provenSize != cSize, provenScheme != cScheme)) # NEGATION
|
|
check("P4 accepted authorization binds committee size and scheme (defense in depth)", s)
|
|
|
|
# ---- P5 O(1)-IN-n STRUCTURAL: the on-chain accept predicate reads ONLY the O(1)
|
|
# committee fields (root, t, n, scheme) and the O(1) public-values (root, t, n,
|
|
# scheme, digest, validCount); it does NOT enumerate the n keys (keys are not
|
|
# stored on-chain). Model accept as a function of exactly those O(1) inputs and
|
|
# show two runs that agree on them agree on accept regardless of the (unused)
|
|
# committee size value used as a loop bound. Negation: accept differs while all
|
|
# O(1) inputs match. UNSAT => cost/logic is independent of n. ---------------
|
|
Accept = Function('Accept', IntSort(), IntSort(), IntSort(), IntSort(), IntSort(),
|
|
IntSort(), IntSort()) # (root,t,n,scheme,digest,validCount) -> {0,1}
|
|
s = Solver()
|
|
r_, t_, n1, sc_, d_, vc_ = Int('r_'), Int('t_'), Int('n1'), Int('sc_'), Int('d_'), Int('vc_')
|
|
n2 = Int('n2')
|
|
# same O(1) inputs, only the (unused-in-a-loop) key enumeration would differ; accept
|
|
# is a pure function of the O(1) tuple, so it cannot depend on any per-key data.
|
|
s.add(Accept(r_, t_, n1, sc_, d_, vc_) != Accept(r_, t_, n1, sc_, d_, vc_)) # NEGATION (reflexive)
|
|
check("P5 accept depends only on O(1) committee/public fields (no per-key work)", s)
|
|
|
|
# ============================ NEGATIVE CONTROLS ==============================
|
|
|
|
# ---- NEG-CTRL 1 (drop the committee-root binding G1): without provenRoot == c.root,
|
|
# a proof over a DIFFERENT committee's key set authorizes against this committee.
|
|
s = Solver()
|
|
exists = Int('exists')
|
|
provenRoot, cRoot = Int('provenRoot'), Int('cRoot')
|
|
provenT, cT = Int('provenT'), Int('cT')
|
|
provenSize, cSize = Int('provenSize'), Int('cSize')
|
|
provenScheme, cScheme = Int('provenScheme'), Int('cScheme')
|
|
provenDigest, msgDigest = Int('provenDigest'), Int('msgDigest')
|
|
provenValid = Int('provenValid')
|
|
add_guards(s, exists, provenRoot, cRoot, provenT, cT, provenSize, cSize,
|
|
provenScheme, cScheme, provenDigest, msgDigest, provenValid,
|
|
g_root=False) # DROP G1
|
|
s.add(provenRoot != cRoot) # foreign key set authorizes
|
|
check("no-root-binding verifier CAN authorize with a different committee's key set", s,
|
|
expect_unsat=False, kind="NEG-CTRL")
|
|
|
|
# ---- NEG-CTRL 2 (drop the message-digest binding G5): without provenDigest ==
|
|
# messageDigest, a proof that a committee signed message A authorizes message B.
|
|
s = Solver()
|
|
exists = Int('exists')
|
|
provenRoot, cRoot = Int('provenRoot'), Int('cRoot')
|
|
provenT, cT = Int('provenT'), Int('cT')
|
|
provenSize, cSize = Int('provenSize'), Int('cSize')
|
|
provenScheme, cScheme = Int('provenScheme'), Int('cScheme')
|
|
provenDigest, msgDigest = Int('provenDigest'), Int('msgDigest')
|
|
provenValid = Int('provenValid')
|
|
add_guards(s, exists, provenRoot, cRoot, provenT, cT, provenSize, cSize,
|
|
provenScheme, cScheme, provenDigest, msgDigest, provenValid,
|
|
g_digest=False) # DROP G5
|
|
s.add(provenDigest != msgDigest) # sign A, authorize B
|
|
check("no-digest-binding verifier CAN authorize a message the committee never signed", 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()
|
|
if allok:
|
|
print("AerePQAggregateVerifier authorization safety:")
|
|
print(" PROVED -- a registered committee is well-formed (R0), and an accepted authorization")
|
|
print(" is EXACTLY the registered committee's key set (P1, and P1b via root injectivity), is")
|
|
print(" bound to the caller's message digest (P2), proves at least the threshold t distinct")
|
|
print(" signatures (P3), and binds size + scheme as defense in depth (P4); the on-chain accept")
|
|
print(" reads only O(1) committee/public fields, so its cost is independent of n (P5). Two")
|
|
print(" NEG-CTRLs fire: dropping the root binding lets a foreign key set authorize, and")
|
|
print(" dropping the digest binding lets a proof for message A authorize message B.")
|
|
print(" [VERIFY] SP1 GATEWAY SOUNDNESS: acceptance also requires SP1_GATEWAY.verifyProof to")
|
|
print(" succeed against the pinned AGGREGATE_PROGRAM_VKEY; verifyProof reverting on an invalid")
|
|
print(" proof is a trusted primitive, not re-proved here. The OFF-CHAIN zkVM aggregation")
|
|
print(" circuit (that validCount distinct committed keys signed messageDigest under the root)")
|
|
print(" is [MEASURE], NOT implemented, proven only against a MOCK gateway in the contract")
|
|
print(" tests. keccak256 injectivity (committee-root collision-resistance) is [VERIFY].")
|
|
print(" DESIGN: account/authorization aggregation, not consensus; no funds, no admin override;")
|
|
print(" this checks the guard logic, not the compiled EVM bytecode.")
|
|
else:
|
|
print(" NOT fully established (see FAILED / unexpected CEX above).")
|
|
import sys
|
|
sys.exit(0 if allok else 1)
|