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.
216 lines
13 KiB
Python
216 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
# -----------------------------------------------------------------------------
|
|
# destinationsettler_smt.py
|
|
#
|
|
# SMT proof (z3) of NO-ARBITRARY-RECIPIENT + AT-MOST-ONCE (no duplicate fill) +
|
|
# OUTPUT-MATCH for AereDestinationSettler, the ERC-7683 filling-side settler, with
|
|
# load-bearing NEGATIVE CONTROLS for each guard.
|
|
#
|
|
# Contract: contracts/contracts/intents/AereDestinationSettler.sol
|
|
#
|
|
# The settler's fill() delivers an order's promised output on AERE and records it
|
|
# so the origin chain can later repay the solver. The safety-critical guarantees
|
|
# (fail-closed) are: a fill can only deliver to the order's DECLARED recipient
|
|
# (never an attacker address), a duplicate fill is impossible (a solver is repaid
|
|
# at most once per intent), and an output that does not satisfy the declared leg
|
|
# (wrong token / wrong recipient / short amount) is rejected.
|
|
#
|
|
# We model fill()'s guards as first-order constraints over the decoded fields and
|
|
# prove the properties by asserting each property's NEGATION under the guards and
|
|
# showing z3 returns UNSAT (no counterexample). Each NEG-CTRL removes exactly one
|
|
# guard and shows the corresponding attack becomes satisfiable (SAT).
|
|
#
|
|
# Addresses / token ids are modelled as Ints (identity only; the proof needs
|
|
# equality/disequality, not byte layout). This checks the DESIGN-level guard
|
|
# logic, NOT the EVM bytecode.
|
|
#
|
|
# Decoded fields (from fill()):
|
|
# originData -> (declOrderId, declOutputToken, declOutputAmount, declRecipient)
|
|
# fillerData -> (deliveredToken, deliveredAmount, deliveredRecipient, repayAddr)
|
|
# orderId = the standard's canonical id argument
|
|
#
|
|
# Guards enforced by fill() (all must hold or it reverts, no state change):
|
|
# G1 declOrderId == orderId (OrderIdMismatch)
|
|
# G2 declRecipient != 0 (ZeroRecipient)
|
|
# G3 declOutputToken != 0 (ZeroAddress)
|
|
# G4 declOutputAmount != 0 (ZeroAmount)
|
|
# G5 fills[orderId].filler == 0 (unfilled) (AlreadyFilled)
|
|
# G6 deliveredToken == declOutputToken AND
|
|
# deliveredRecipient == declRecipient AND
|
|
# deliveredAmount >= declOutputAmount (OutputMismatch)
|
|
# On success: record fills[orderId], then safeTransferFrom(solver -> deliveredRecipient).
|
|
# -----------------------------------------------------------------------------
|
|
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 guards(s, orderId, declOrderId, declRecipient, declOutputToken, declOutputAmount,
|
|
deliveredToken, deliveredRecipient, deliveredAmount, alreadyFiller,
|
|
include_g1=True, include_g2=True, include_g6=True):
|
|
"""Assert fill()'s guards. Flags let a NEG-CTRL drop exactly one guard."""
|
|
if include_g1:
|
|
s.add(declOrderId == orderId) # G1
|
|
if include_g2:
|
|
s.add(declRecipient != 0) # G2 (ZeroRecipient)
|
|
s.add(declOutputToken != 0) # G3
|
|
s.add(declOutputAmount >= 1) # G4
|
|
s.add(alreadyFiller == 0) # G5 (unfilled)
|
|
if include_g6:
|
|
s.add(deliveredToken == declOutputToken) # G6a
|
|
s.add(deliveredRecipient == declRecipient) # G6b
|
|
s.add(deliveredAmount >= declOutputAmount) # G6c
|
|
|
|
print("### AereDestinationSettler -- NO-ARBITRARY-RECIPIENT + AT-MOST-ONCE + OUTPUT-MATCH\n")
|
|
|
|
# ---- P1 NO-ARBITRARY-RECIPIENT: a successful fill delivers ONLY to the order's
|
|
# declared recipient. Negation: fill succeeds yet the funds go to some other
|
|
# address `attacker` (attacker != declRecipient). Under the guards, UNSAT. --
|
|
s = Solver()
|
|
orderId, declOrderId = Int('orderId'), Int('declOrderId')
|
|
declRecipient, declOutputToken, declOutputAmount = Int('declRecipient'), Int('declOutputToken'), Int('declOutputAmount')
|
|
deliveredToken, deliveredRecipient, deliveredAmount = Int('deliveredToken'), Int('deliveredRecipient'), Int('deliveredAmount')
|
|
alreadyFiller, attacker = Int('alreadyFiller'), Int('attacker')
|
|
guards(s, orderId, declOrderId, declRecipient, declOutputToken, declOutputAmount,
|
|
deliveredToken, deliveredRecipient, deliveredAmount, alreadyFiller)
|
|
# The tokens are transferred to `deliveredRecipient` (the safeTransferFrom `to`).
|
|
# NEGATION: that recipient is an attacker distinct from the order's declared one.
|
|
s.add(attacker != declRecipient, deliveredRecipient == attacker)
|
|
check("P1 fill can only deliver to the DECLARED recipient (no arbitrary recipient)", s)
|
|
|
|
# ---- P1b the transfer destination is provably the declared recipient AND non-zero
|
|
s = Solver()
|
|
declRecipient, deliveredRecipient = Int('declRecipient'), Int('deliveredRecipient')
|
|
declOrderId, orderId = Int('declOrderId'), Int('orderId')
|
|
declOutputToken, declOutputAmount = Int('declOutputToken'), Int('declOutputAmount')
|
|
deliveredToken, deliveredAmount, alreadyFiller = Int('deliveredToken'), Int('deliveredAmount'), Int('alreadyFiller')
|
|
guards(s, orderId, declOrderId, declRecipient, declOutputToken, declOutputAmount,
|
|
deliveredToken, deliveredRecipient, deliveredAmount, alreadyFiller)
|
|
# NEGATION: the actual transfer target is zero OR differs from the declared recipient.
|
|
s.add(Or(deliveredRecipient == 0, deliveredRecipient != declRecipient))
|
|
check("P1b transfer target == declared recipient AND is non-zero", s)
|
|
|
|
# ---- P2 AT-MOST-ONCE: an order already filled cannot be filled again. The G5
|
|
# guard rejects fills[orderId].filler != 0. Model the double-fill directly:
|
|
# a second fill requires filler==0 but the slot is already set (filler!=0). -
|
|
s = Solver()
|
|
firstFiller = Int('firstFiller')
|
|
s.add(firstFiller != 0) # order already filled by someone (slot occupied)
|
|
s.add(firstFiller == 0) # ... yet the AlreadyFilled guard demands "unfilled"
|
|
check("P2 second fill of an already-filled order is guard-infeasible (AlreadyFilled)", s)
|
|
|
|
# ---- P2b the recorded slot is EXACTLY the arg orderId (G1 binds declOrderId to
|
|
# the id argument), so a fill can never be recorded under a different order's
|
|
# slot to dodge the duplicate check. Negation: recorded under a slot whose
|
|
# declared id differs from the arg. UNSAT under G1. -----------------------
|
|
s = Solver()
|
|
orderId, declOrderId = Int('orderId'), Int('declOrderId')
|
|
s.add(declOrderId == orderId) # G1
|
|
s.add(declOrderId != orderId) # NEGATION
|
|
check("P2b fill is bound to the arg orderId (no cross-slot record, G1)", s)
|
|
|
|
# ---- P3 OUTPUT-MATCH: a recorded fill satisfies the declared output exactly on
|
|
# token+recipient and is >= on amount. Negation: a fill succeeds yet delivers
|
|
# the wrong token OR short amount. Under G6, UNSAT. -----------------------
|
|
s = Solver()
|
|
orderId, declOrderId = Int('orderId'), Int('declOrderId')
|
|
declRecipient, declOutputToken, declOutputAmount = Int('declRecipient'), Int('declOutputToken'), Int('declOutputAmount')
|
|
deliveredToken, deliveredRecipient, deliveredAmount = Int('deliveredToken'), Int('deliveredRecipient'), Int('deliveredAmount')
|
|
alreadyFiller = Int('alreadyFiller')
|
|
guards(s, orderId, declOrderId, declRecipient, declOutputToken, declOutputAmount,
|
|
deliveredToken, deliveredRecipient, deliveredAmount, alreadyFiller)
|
|
# NEGATION: wrong token OR under-delivered amount slipped through.
|
|
s.add(Or(deliveredToken != declOutputToken, deliveredAmount < declOutputAmount))
|
|
check("P3 output-mismatch (wrong token OR short amount) is rejected", s)
|
|
|
|
# ============================ NEGATIVE CONTROLS ==============================
|
|
|
|
# ---- NEG-CTRL 1 (recipient guard G6b removed): without deliveredRecipient ==
|
|
# declRecipient, a solver can deliver the output to an arbitrary attacker.
|
|
s = Solver()
|
|
orderId, declOrderId = Int('orderId'), Int('declOrderId')
|
|
declRecipient, declOutputToken, declOutputAmount = Int('declRecipient'), Int('declOutputToken'), Int('declOutputAmount')
|
|
deliveredToken, deliveredRecipient, deliveredAmount = Int('deliveredToken'), Int('deliveredRecipient'), Int('deliveredAmount')
|
|
alreadyFiller, attacker = Int('alreadyFiller'), Int('attacker')
|
|
guards(s, orderId, declOrderId, declRecipient, declOutputToken, declOutputAmount,
|
|
deliveredToken, deliveredRecipient, deliveredAmount, alreadyFiller,
|
|
include_g6=False) # drop the whole output-match block...
|
|
s.add(deliveredToken == declOutputToken, deliveredAmount >= declOutputAmount) # ...keep token+amount
|
|
s.add(attacker != declRecipient, deliveredRecipient == attacker) # but NOT recipient
|
|
check("no-recipient-check settler CAN deliver to an arbitrary attacker", s,
|
|
expect_unsat=False, kind="NEG-CTRL")
|
|
|
|
# ---- NEG-CTRL 2 (duplicate guard G5 removed): without AlreadyFilled, a second
|
|
# fill of the same order succeeds -> the solver is repaid twice per intent. -
|
|
s = Solver()
|
|
firstFiller, secondFiller = Int('firstFiller'), Int('secondFiller')
|
|
s.add(firstFiller != 0, secondFiller != 0) # order already filled; a 2nd fill also runs
|
|
# No G5 guard tying the 2nd fill to "unfilled" -> both fills are recorded.
|
|
check("no-AlreadyFilled settler CAN double-fill one order (double repayment)", s,
|
|
expect_unsat=False, kind="NEG-CTRL")
|
|
|
|
# ---- NEG-CTRL 3 (zero-recipient guard G2 removed): without ZeroRecipient, an
|
|
# order can be "delivered" to address(0) (funds burned / undeliverable). ----
|
|
s = Solver()
|
|
orderId, declOrderId = Int('orderId'), Int('declOrderId')
|
|
declRecipient, declOutputToken, declOutputAmount = Int('declRecipient'), Int('declOutputToken'), Int('declOutputAmount')
|
|
deliveredToken, deliveredRecipient, deliveredAmount = Int('deliveredToken'), Int('deliveredRecipient'), Int('deliveredAmount')
|
|
alreadyFiller = Int('alreadyFiller')
|
|
guards(s, orderId, declOrderId, declRecipient, declOutputToken, declOutputAmount,
|
|
deliveredToken, deliveredRecipient, deliveredAmount, alreadyFiller,
|
|
include_g2=False) # drop ZeroRecipient
|
|
s.add(declRecipient == 0, deliveredRecipient == 0)
|
|
check("no-ZeroRecipient settler CAN record a delivery to address(0)", s,
|
|
expect_unsat=False, kind="NEG-CTRL")
|
|
|
|
# ---- NEG-CTRL 4 (output-match guard G6 removed): without OutputMismatch, a solver
|
|
# delivers a WORTHLESS/wrong token yet records a valid fill (still repaid). --
|
|
s = Solver()
|
|
orderId, declOrderId = Int('orderId'), Int('declOrderId')
|
|
declRecipient, declOutputToken, declOutputAmount = Int('declRecipient'), Int('declOutputToken'), Int('declOutputAmount')
|
|
deliveredToken, deliveredRecipient, deliveredAmount = Int('deliveredToken'), Int('deliveredRecipient'), Int('deliveredAmount')
|
|
alreadyFiller = Int('alreadyFiller')
|
|
guards(s, orderId, declOrderId, declRecipient, declOutputToken, declOutputAmount,
|
|
deliveredToken, deliveredRecipient, deliveredAmount, alreadyFiller,
|
|
include_g6=False)
|
|
s.add(deliveredToken != declOutputToken) # wrong token delivered, recorded anyway
|
|
check("no-OutputMismatch settler CAN record a wrong-token delivery", 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("AereDestinationSettler fill() safety:")
|
|
print(" PROVED -- under the four fail-closed guards, a fill can deliver ONLY to the order's")
|
|
print(" declared (non-zero) recipient (P1/P1b), is bound to the arg orderId and can be")
|
|
print(" recorded at most once (P2/P2b), and an output that mismatches the declared token or")
|
|
print(" is short is rejected (P3). Four NEG-CTRLs fire: dropping the recipient-match, the")
|
|
print(" AlreadyFilled, the ZeroRecipient, or the output-match guard each opens a real attack")
|
|
print(" (arbitrary recipient / double repayment / burn-to-zero / wrong-token repayment).")
|
|
print(" [VERIFY] ATOMICITY of the delivery (effects-before-interaction record, then a single")
|
|
print(" SafeERC20 pull solver->recipient that reverts the WHOLE tx on failure, under")
|
|
print(" nonReentrant) is an EVM-revert/whole-tx property, asserted structurally not by this")
|
|
print(" arithmetic model. Repayment on the ORIGIN chain is gated by the validator-set-anchored")
|
|
print(" finality+inclusion proof and is out of scope here [MEASURE].")
|
|
print(" BOUNDARY: this checks the DESIGN-level 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)
|