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.
198 lines
11 KiB
Python
198 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
# -----------------------------------------------------------------------------
|
|
# agentdid_lifecycle_smt.py
|
|
#
|
|
# SMT proof (z3) of the ROOT-LIFECYCLE and CONTROLLER-AUTHORITY invariants of
|
|
# AereAgentDID, the LIVE (mainnet chain 2800, deployed 2026-07-12) PQC-rooted DID for
|
|
# AI agents, at 0xce641d7d7C10553D82b06B7C21d423550e7522C5.
|
|
#
|
|
# Contract: contracts/contracts/pqc/AereAgentDID.sol
|
|
#
|
|
# This COMPLEMENTS formal-consensus/agentdid_session_smt.py (which proved the
|
|
# cumulative spend cap C1, the general liveness gate C2, the anti-replay nonce C3, and
|
|
# terminal expiry C4). Here we prove the two properties the build task calls out that
|
|
# the session model did not make explicit:
|
|
# D1 A session DIES the moment its Falcon ROOT is ROTATED or REVOKED (the liveness
|
|
# check re-reads the registry on every call; a non-ACTIVE / non-Falcon root makes
|
|
# _isSessionLive false, so authorize / isSessionValid / remainingSpend all fail).
|
|
# D2 ONLY THE CONTROLLER MUTATES the DID/session LIFECYCLE:
|
|
# - createAgent requires msg.sender == the registry owner of the root key;
|
|
# - revokeSession requires msg.sender == the CURRENT registry owner of the root;
|
|
# - spend is recorded ONLY with a valid session-key ECDSA signature
|
|
# (signer == sessionAddr), never by an arbitrary caller.
|
|
# D3 A session cannot OUTLIVE its expiry nor EXCEED its cap (restated jointly here as
|
|
# the containment envelope, cross-checking the session model).
|
|
#
|
|
# Style: PROVED = negation UNSAT under the exact Solidity guards; each paired with a
|
|
# firing NEGATIVE CONTROL.
|
|
#
|
|
# ASSUMPTIONS (bound every PROVED):
|
|
# A1. ecrecover is a sound signature oracle (returns sessionAddr ONLY for a genuine
|
|
# session-key signature over the exact digest). Out of scope; same class as the
|
|
# halmos precompile-oracle assumption. The gating proofs hold for BOTH branches.
|
|
# A2. The registry read (getKey) reflects the root key's TRUE current status; the
|
|
# contract re-reads it on every liveness check (no stale cache) -- this is the
|
|
# fact D1 exploits and we model it directly.
|
|
# A3. Design model (unbounded Int); the halmos suite is the bytecode layer.
|
|
# -----------------------------------------------------------------------------
|
|
from z3 import Int, Bool, Solver, And, Or, Not, Implies, If, sat, unsat
|
|
|
|
# registry statuses / schemes (as AereAgentDID reads them)
|
|
REG_ACTIVE = 1
|
|
FALCON512, FALCON1024 = 1, 2
|
|
|
|
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("### AereAgentDID -- root-lifecycle kill-switch + controller-only mutation\n")
|
|
|
|
# _isSessionLive(s): exists AND !revoked AND now<=expiry AND rootStatus==ACTIVE AND
|
|
# rootScheme in {Falcon512, Falcon1024}
|
|
def is_live(exists, revoked, now, expiry, root_status, root_scheme):
|
|
return And(exists,
|
|
Not(revoked),
|
|
now <= expiry,
|
|
root_status == REG_ACTIVE,
|
|
Or(root_scheme == FALCON512, root_scheme == FALCON1024))
|
|
|
|
# ---- D1a : a ROTATED root (registry status != ACTIVE) kills the session ----------
|
|
# When the Falcon root is rotated, its registry status becomes ROTATED (2), not ACTIVE.
|
|
s = Solver()
|
|
now, expiry, root_scheme, root_status = Int('now'), Int('expiry'), Int('rootScheme'), Int('rootStatus')
|
|
s.add(root_status != REG_ACTIVE) # rotated (=2) or revoked (=3): any non-ACTIVE
|
|
# a session that is otherwise perfectly fine (exists, not revoked, not expired, Falcon):
|
|
s.add(is_live(True, False, now, expiry, root_status, FALCON512))
|
|
check("D1a a non-ACTIVE (rotated/revoked) root makes the session non-live (kill-switch)", s)
|
|
|
|
# ---- D1b : if the root is no longer a FALCON scheme, the session is dead ----------
|
|
s = Solver()
|
|
now, expiry, root_scheme = Int('now'), Int('expiry'), Int('rootScheme')
|
|
s.add(root_scheme != FALCON512, root_scheme != FALCON1024) # not a Falcon root anymore
|
|
s.add(is_live(True, False, now, expiry, REG_ACTIVE, root_scheme))
|
|
check("D1b a non-Falcon root scheme makes the session non-live", s)
|
|
|
|
# ---- D1c : authorize() cannot record spend when the root is not ACTIVE Falcon -----
|
|
# authorize requires _isSessionLive BEFORE touching spent/nonce. Model: reachedEffects
|
|
# implies live; with a rotated root, live is false, so no spend is recorded.
|
|
s = Solver()
|
|
root_status = Int('rootStatus'); reachedEffects = Bool('reachedEffects')
|
|
now, expiry = Int('now'), Int('expiry')
|
|
live = is_live(True, False, now, expiry, root_status, FALCON512)
|
|
s.add(reachedEffects == live) # the _isSessionLive guard
|
|
s.add(root_status != REG_ACTIVE) # root rotated/revoked
|
|
s.add(reachedEffects) # claim spend was still recorded
|
|
check("D1c authorize() records no spend after the root is rotated/revoked", s)
|
|
|
|
# ---- NEG-CTRL D1: a session that CACHES root-active at issuance (never re-reads)
|
|
# keeps authorizing after the root is revoked. z3 finds the live-after-revoke state.
|
|
s = Solver()
|
|
cached_active_at_issue = Bool('cachedActiveAtIssue')
|
|
root_status_now = Int('rootStatusNow'); now, expiry = Int('now'), Int('expiry')
|
|
s.add(cached_active_at_issue == True) # BUG: liveness uses the cached flag
|
|
buggy_live = And(True, Not(False), now <= expiry, cached_active_at_issue)
|
|
s.add(root_status_now != REG_ACTIVE) # root has since been revoked
|
|
s.add(buggy_live) # session still 'live' -> can spend
|
|
check("NEG-CTRL a cached root-active flag keeps a session live after the root is revoked", s,
|
|
expect_unsat=False, kind="NEG-CTRL")
|
|
|
|
# ---- D2a : revokeSession is CONTROLLER-ONLY -------------------------------------
|
|
# require msg.sender == _currentController(root) (the registry owner of the root key).
|
|
s = Solver()
|
|
sender, controller = Int('msgSender'), Int('controller')
|
|
s.add(sender != controller) # a non-controller caller
|
|
reached_revoke_effect = (sender == controller) # the guard: only equal passes
|
|
s.add(reached_revoke_effect) # claim the non-controller revoked
|
|
check("D2a revokeSession is controller-only (non-controller cannot mutate session state)", s)
|
|
|
|
# ---- D2b : createAgent is ROOT-OWNER-ONLY ---------------------------------------
|
|
# require msg.sender == rootOwner (the registry owner who proved Falcon possession).
|
|
s = Solver()
|
|
sender, root_owner = Int('msgSender'), Int('rootOwner')
|
|
s.add(sender != root_owner)
|
|
reached_create = (sender == root_owner)
|
|
s.add(reached_create) # claim a non-owner created the agent
|
|
check("D2b createAgent is root-owner-only (only the Falcon key holder creates the DID)", s)
|
|
|
|
# ---- D2c : spend is recorded ONLY with a valid session-key signature -------------
|
|
# authorize: signer = ecrecover(digest, sig); require signer != 0 AND signer == sessionAddr.
|
|
# Model: spend recorded => sig valid for the session key. A caller without the session
|
|
# key (sigValid == False) cannot record spend.
|
|
s = Solver()
|
|
sig_valid = Bool('sigValidForSessionKey')
|
|
reached_spend = Bool('reachedSpend')
|
|
s.add(reached_spend == sig_valid) # the ecrecover==sessionAddr guard
|
|
s.add(Not(sig_valid)) # caller lacks the session key
|
|
s.add(reached_spend) # claim spend was recorded anyway
|
|
check("D2c spend is recorded only with a valid session-key signature (no key -> no spend)", s)
|
|
|
|
# ---- NEG-CTRL D2: dropping the controller check lets ANYONE revoke a session ------
|
|
s = Solver()
|
|
sender, controller = Int('msgSender'), Int('controller')
|
|
s.add(sender != controller)
|
|
buggy_reached = True # BUG: no controller gate
|
|
s.add(buggy_reached)
|
|
check("NEG-CTRL removing the controller gate lets anyone revoke a session", s,
|
|
expect_unsat=False, kind="NEG-CTRL")
|
|
|
|
# ---- NEG-CTRL D2c: dropping the ecrecover==sessionAddr check lets anyone spend -----
|
|
s = Solver()
|
|
sig_valid = Bool('sigValidForSessionKey'); reached_spend = Bool('reachedSpend')
|
|
s.add(reached_spend == True) # BUG: effects reached unconditionally
|
|
s.add(Not(sig_valid)) # caller has no valid session signature
|
|
s.add(reached_spend)
|
|
check("NEG-CTRL removing the session-signature check lets anyone record spend", s,
|
|
expect_unsat=False, kind="NEG-CTRL")
|
|
|
|
# ---- D3 : containment envelope (joint restate) -----------------------------------
|
|
# A session can neither outlive expiry (now>expiry => dead) nor exceed cap
|
|
# (spent<=cap inductive). Both directions in one check as a sanity cross-tie.
|
|
s = Solver()
|
|
now, expiry, spent, cap, amount = Int('now'), Int('expiry'), Int('spent'), Int('cap'), Int('amount')
|
|
s.add(spent >= 0, spent <= cap, amount >= 0)
|
|
# authorize success guards: live (now<=expiry) AND cumulative cap
|
|
s.add(now <= expiry, spent + amount >= spent, spent + amount <= cap)
|
|
spent2 = spent + amount
|
|
# negate the joint post-condition: spent2<=cap AND (we did not act past expiry)
|
|
s.add(Not(And(spent2 <= cap, now <= expiry)))
|
|
check("D3 containment: a successful action stays within cap AND within expiry", s)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
print("\n=== SUMMARY ===")
|
|
allok = True
|
|
for name, tag, ok, kind in results:
|
|
print(f" {tag:9} [{kind}] {name}")
|
|
allok = allok and ok
|
|
print()
|
|
print("AereAgentDID (root lifecycle + controller authority):")
|
|
if allok:
|
|
print(" PROVED (under A1 ecrecover-soundness + A2 live registry read): a session dies")
|
|
print(" the instant its Falcon root is ROTATED or REVOKED, or ceases to be a Falcon")
|
|
print(" scheme (D1a/b/c -- authorize records nothing thereafter); the DID/session")
|
|
print(" lifecycle is mutated ONLY by the proper authority -- createAgent by the root")
|
|
print(" owner, revokeSession by the current controller, spend only by a valid session")
|
|
print(" signature (D2a/b/c); and the cap+expiry containment envelope holds (D3). Four")
|
|
print(" NEG-CTRLs fire (cached-root-active, missing controller gate, missing signature")
|
|
print(" gate), confirming the live registry read and each authority gate are")
|
|
print(" load-bearing. Complements agentdid_session_smt.py (spend cap / anti-replay).")
|
|
else:
|
|
print(" NOT fully established (see FAILED / unexpected result above).")
|
|
import sys
|
|
sys.exit(0 if allok else 1)
|