aere-research/formal-consensus/reputation_gate_smt.py
Aere Network 6cb0140fae Republished from a clean root: the compiled artifact is gone from history, and the local line of work joins the sanitized public line
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.
2026-08-15 13:52:14 +03:00

176 lines
10 KiB
Python

#!/usr/bin/env python3
# -----------------------------------------------------------------------------
# reputation_gate_smt.py
#
# SMT proof (z3) of the M1-FIX authorization + delta-clamp invariant of
# AereReputationRegistry8004, the ERC-8004 Reputation adapter over the LIVE
# AereAIReputation. Plus load-bearing NEGATIVE CONTROLS for the author gate (the
# M1 bug: anyone could drive a score) and for the delta clamp.
#
# Contract: contracts/contracts/erc8004/AereReputationRegistry8004.sol
#
# Because every caller of this adapter collapses into ONE on-chain attestor identity
# (the adapter) on the live AereAIReputation, an UNGATED write path (the pre-fix M1
# state) would let ANY address inflate or tank any agent's reputation. The M1 fix
# gates giveFeedback to an owner-curated allowlist AND keeps the existing delta clamp,
# so a forwarded attestation is authorized AND a bounded unit step, never an arbitrary
# value set. giveFeedback fail-closes on each guard (else revert, forwards nothing):
# G0 author gate (M1 fix): isAuthorizedFeedbackAuthor[msg.sender] (NotAuthorizedFeedbackAuthor)
# G1 agent registered: IDENTITY.isRegistered(agentId) (AgentNotRegistered)
# G2 delta clamp: -1 <= delta <= 1 (InvalidDelta)
# G3 adapter is attestor: REPUTATION.isAttestor(this) (AdapterNotAttestor)
# then forwards REPUTATION.attest(operator, key, delta, ...).
#
# FORWARD-SAFETY INVARIANT (the M1 target):
# a forwarded attestation happens => (author is authorized) AND (delta in {-1,0,+1})
#
# So the ONLY way this adapter moves an agent's live reputation is an authorized
# author submitting a clamped unit delta; the owner curates WHO may write but can
# neither set a reputation value directly nor exceed the clamp every author is held
# to, and moves no funds.
#
# Method: model the fail-closed guards as first-order constraints and prove each
# property by asserting its NEGATION under the guards (UNSAT). Each NEG-CTRL removes
# exactly one guard and shows the corresponding attack becomes SAT.
#
# HONEST SCOPE / OVERLAP. The authorization half of this property is also exercised
# directly by the Hardhat test "gates giveFeedback to owner-authorized feedback
# authors (M1)" in test/erc8004-adapters.test.js. The z3 value-add here is (a) proving
# the COMPOSITE invariant (authorized AND unit-clamped) is the only way a forward can
# occur, and (b) the firing negative control that reproduces the exact M1 "anyone
# drives the score" bug, showing the gate is load-bearing. The live AereAIReputation
# computes the actual score FROM the delta; this model bounds only what the adapter
# forwards (an authorized, clamped delta), not the live contract's internal score
# formula. Application-layer, no funds, consensus classical ECDSA. DESIGN-level logic,
# NOT the compiled EVM bytecode.
# -----------------------------------------------------------------------------
from z3 import Int, Bool, Solver, And, Or, Not, Implies, 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 clamped(delta):
"""InvalidDelta guard: delta in {-1, 0, +1}."""
return And(delta >= -1, delta <= 1)
def forward_ok(authorized, registered, delta, adapterIsAttestor):
"""giveFeedback's fail-closed guards G0..G3. A forwarded attest happens iff all hold."""
return And(authorized, registered, clamped(delta), adapterIsAttestor)
print("### AereReputationRegistry8004 -- M1 AUTHOR GATE + DELTA CLAMP (forward-safety)\n")
# ---- G0 AUTHORIZATION REQUIRED (M1 fix): a forwarded attestation requires an
# authorized author. Negation: a forward happens with an unauthorized caller. UNSAT.
s = Solver()
authorized, registered, adapterIsAttestor = Bool('authorized'), Bool('registered'), Bool('adapterIsAttestor')
delta = Int('delta')
s.add(forward_ok(authorized, registered, delta, adapterIsAttestor))
s.add(Not(authorized)) # NEGATION: unauthorized caller moved a score
check("G0 only an authorized feedback author can move a score (M1 gate)", s)
# ---- G2 DELTA CLAMP: a forwarded attestation carries delta in {-1,0,+1}, so no
# author (nor the owner) can forge an arbitrary reputation move. Negation: a
# forward happens with an out-of-range delta. UNSAT.
s = Solver()
authorized, registered, adapterIsAttestor = Bool('authorized'), Bool('registered'), Bool('adapterIsAttestor')
delta = Int('delta')
s.add(forward_ok(authorized, registered, delta, adapterIsAttestor))
s.add(Or(delta < -1, delta > 1)) # NEGATION: arbitrary delta forwarded
check("G2 a forwarded delta is clamped to {-1,0,+1} (no arbitrary value set)", s)
# ---- M1 COMPOSITE forward-safety: any score move through the adapter is BOTH by an
# authorized author AND a clamped unit step. Negation: a forward happens yet the
# author is unauthorized OR the delta is out of range. UNSAT.
s = Solver()
authorized, registered, adapterIsAttestor = Bool('authorized'), Bool('registered'), Bool('adapterIsAttestor')
delta = Int('delta')
s.add(forward_ok(authorized, registered, delta, adapterIsAttestor))
s.add(Or(Not(authorized), delta < -1, delta > 1)) # NEGATION
check("M1 a score move requires (authorized author) AND (clamped unit delta)", s)
# ---- G1 REGISTERED AGENT required (fail-closed): a forward requires a registered
# agentId. Negation: a forward about an unregistered agent. UNSAT.
s = Solver()
authorized, registered, adapterIsAttestor = Bool('authorized'), Bool('registered'), Bool('adapterIsAttestor')
delta = Int('delta')
s.add(forward_ok(authorized, registered, delta, adapterIsAttestor))
s.add(Not(registered)) # NEGATION: feedback about an unknown agent
check("G1 feedback requires a registered agent (fail-closed)", s)
# ---- G3 ADAPTER-IS-ATTESTOR required (fail-closed): a forward requires this adapter
# to be an authorized attestor on the live reputation. Negation: forward without
# the adapter being an attestor. UNSAT.
s = Solver()
authorized, registered, adapterIsAttestor = Bool('authorized'), Bool('registered'), Bool('adapterIsAttestor')
delta = Int('delta')
s.add(forward_ok(authorized, registered, delta, adapterIsAttestor))
s.add(Not(adapterIsAttestor)) # NEGATION: writes through a non-attestor adapter
check("G3 forwarding requires the adapter to be a live attestor (fail-closed)", s)
# ============================ NEGATIVE CONTROLS ==============================
# ---- NEG-CTRL 1 (drop the author gate -> the M1 bug): an UNGATED giveFeedback (the
# pre-fix state) lets ANY caller drive an agent's score. Since every caller
# collapses into the single adapter attestor, an unauthorized address writes.
s = Solver()
authorized, registered, adapterIsAttestor = Bool('authorized'), Bool('registered'), Bool('adapterIsAttestor')
delta = Int('delta')
# BUG: the G0 author gate is removed; a forward needs only registered + clamp + attestor.
buggy_forward = And(registered, clamped(delta), adapterIsAttestor)
s.add(buggy_forward)
s.add(Not(authorized)) # an UNauthorized caller still drives the score
check("no-author-gate adapter CAN let anyone drive an agent's score (reproduces M1)", s,
expect_unsat=False, kind="NEG-CTRL")
# ---- NEG-CTRL 2 (drop the delta clamp): a BUGGY adapter without the {-1,0,+1} clamp
# forwards an arbitrary delta, letting even an authorized author forge a large
# reputation swing in one call. z3 finds the out-of-range delta.
s = Solver()
authorized, registered, adapterIsAttestor = Bool('authorized'), Bool('registered'), Bool('adapterIsAttestor')
delta = Int('delta')
# BUG: the G2 clamp is removed; a forward needs only author + registered + attestor.
buggy_forward = And(authorized, registered, adapterIsAttestor)
s.add(buggy_forward)
s.add(delta > 1) # an arbitrary large delta is forwarded
check("no-delta-clamp adapter CAN forward an arbitrary reputation delta", 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("AereReputationRegistry8004 M1 forward-safety:")
print(" PROVED -- a forwarded attestation requires an authorized feedback author (G0, the M1")
print(" fix), a delta clamped to {-1,0,+1} (G2, no arbitrary value set), a registered agent")
print(" (G1) and the adapter being a live attestor (G3); the composite M1 property holds: the")
print(" ONLY way to move an agent's score through the adapter is an authorized author with a")
print(" clamped unit delta. Two NEG-CTRLs fire: dropping the author gate reproduces the exact")
print(" M1 bug (anyone drives the score), and dropping the clamp lets an arbitrary delta")
print(" through, so both guards are load-bearing.")
print(" OVERLAP / SCOPE (honest): the authorization half is also covered directly by the")
print(" Hardhat test 'gates giveFeedback to owner-authorized feedback authors (M1)'; the z3")
print(" value-add is the composite invariant and the firing non-vacuity control. The owner")
print(" curates WHO may write but cannot set a reputation value or exceed the clamp. The live")
print(" AereAIReputation computes the score FROM the delta; this model bounds what the adapter")
print(" forwards (authorized + clamped), not the live contract's score formula. Application-")
print(" layer, no funds, consensus classical ECDSA. DESIGN-level guard logic, not the bytecode.")
else:
print(" NOT fully established (see FAILED / unexpected CEX above).")
import sys
sys.exit(0 if allok else 1)