Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
131 lines
9.7 KiB
Markdown
131 lines
9.7 KiB
Markdown
# spec-formal-final: z3 SMT coverage for the new Aere Network contract suite
|
|
|
|
Prepared 2026-07-19. Environment: Python 3.14.4, z3-solver 4.16.0, Windows 11.
|
|
All four new models follow the corpus discipline exactly: each safety property is proved by asserting
|
|
NOT-property under the real Solidity guards (z3 returns UNSAT), paired with a firing negative control
|
|
that removes one guard and returns SAT (non-vacuity). Real verdicts only. No contract was modified.
|
|
|
|
## PART 1: new z3 SMT models (4 added, all PROVED, all negative controls fired)
|
|
|
|
### a. ap2_mandate_smt.py (AereAP2MandateVerifier) -- 9 PROVED / 3 CEX-FOUND, exit 0
|
|
Load-bearing invariant modelled: CAP-SOLVENCY, `INV(mh) := 0 <= spentUnder[mh] <= maxAmount`.
|
|
Proved (UNSAT under the real guards):
|
|
- base case (fresh mandate spent == 0) satisfies INV;
|
|
- authorizeSpend preserves INV inductively (cumulative spend stays within the cap);
|
|
- two sequential guarded draws can never exceed the cap (the requested windowed-cumulative property);
|
|
- authorizeSpend is monotone (consumed cap is never refunded / reduced, so a replay cannot free cap);
|
|
- an expired mandate authorizes nothing (write requires now <= periodEnd);
|
|
- a not-yet-valid mandate authorizes nothing (write requires now >= periodStart);
|
|
- an accepted authorization has a well-formed window (periodEnd >= periodStart);
|
|
- L3 executor gate (post-fix): with SETTLEMENT_EXECUTOR configured, only that executor can consume
|
|
the cap (the open replay path is blocked);
|
|
- a zero-amount draw is rejected.
|
|
Negative controls (SAT, all fired): drop the cap check -> authorizes over-cap; drop the expiry check
|
|
-> authorizes an expired mandate; drop the L3 executor gate -> a non-executor drains a configured
|
|
mandate's cap. All three requested controls present (cap, expiry, plus the L3 gate control).
|
|
Caveat / [VERIFY]: the Falcon-512 validity bit is the live precompile 0x0AE1 (trusted primitive, not
|
|
re-proved); mandateHash chain/contract binding and keccak256 injectivity are [VERIFY]; the contract
|
|
holds NO funds (cap is accounting, the AERE402 rail moves value); unbounded-Int design abstraction,
|
|
DESIGN-level not bytecode. The open path (executor == 0) is verification/test only per the contract's
|
|
documented L3; the model proves the configured executor closes the vector.
|
|
|
|
### b. reputation_gate_smt.py (AereReputationRegistry8004, M1 fix) -- 5 PROVED / 2 CEX-FOUND, exit 0
|
|
Invariant modelled: M1 FORWARD-SAFETY, a forwarded attestation happens => (author authorized) AND
|
|
(delta in {-1,0,+1}).
|
|
Proved (UNSAT): only an authorized feedback author can move a score (G0, the M1 gate); a forwarded
|
|
delta is clamped to {-1,0,+1} (G2, no arbitrary value set); the composite M1 property (a score move
|
|
requires an authorized author AND a clamped unit delta); feedback requires a registered agent (G1);
|
|
forwarding requires the adapter to be a live attestor (G3).
|
|
Negative controls (SAT, both fired): drop the author gate -> anyone drives an agent's score (this
|
|
reproduces the exact M1 bug); drop the delta clamp -> an arbitrary reputation delta is forwarded.
|
|
Honest caveat: this is the lightest of the four; its authorization half overlaps the Hardhat test
|
|
"gates giveFeedback to owner-authorized feedback authors (M1)". The z3 value-add is the composite
|
|
invariant plus the firing non-vacuity control, and the delta-clamp arithmetic bound. The live
|
|
AereAIReputation computes the score FROM the delta; the model bounds only what the adapter forwards
|
|
(authorized + clamped), not the live contract's score formula. Included because the task explicitly
|
|
requested it AND the clamp bound + firing M1 control make it non-vacuous, not coverage-theater.
|
|
|
|
### c1. vectorstore_smt.py (AereVectorStore) -- 8 PROVED / 3 CEX-FOUND, exit 0
|
|
Invariants modelled: VERSION MONOTONICITY, APPEND-ONLY history, retrieval-against-a-committed-version,
|
|
one-shot receipt.
|
|
Proved (UNSAT): a commit increments version by exactly one (version = s.version + 1); version strictly
|
|
increases; commitCount strictly increases; the version history is append-only (a committed root can
|
|
never be rewritten); every attestation names a committed version 1..current (CV); vector-inclusion is
|
|
only checked against a committed version (CV2); a priced receipt is consumed at most once (R1); only
|
|
the bound provider may consume a receipt (R2, the L2 fix).
|
|
Negative controls (SAT, all fired): drop the version bound -> attest against an uncommitted version;
|
|
drop append-only -> overwrite a committed root; drop the one-shot guard -> double-spend one paid
|
|
receipt.
|
|
Caveat / [VERIFY]: post-quantum commit AUTHENTICITY is the live precompile 0x0AE1 (fail-closed,
|
|
trusted); the AERE402 facilitator settlement in paidQuery is an external trusted rail (this model
|
|
covers the receipt STATE machine, not the token movement); Merkle inclusion rests on keccak256 / OZ
|
|
MerkleProof soundness. This satisfies the task's "check AereVectorStore version monotonicity" item.
|
|
|
|
### c2. bitstring_status_smt.py (AereBitstringStatusList) -- 6 PROVED / 2 CEX-FOUND, exit 0
|
|
Invariants modelled: REVOCATION-BIT MONOTONICITY (terminal), no silent no-op, epoch monotonicity,
|
|
bit-frame isolation.
|
|
Proved (UNSAT): on a Revocation list a status bit is monotone, the only enabled transition is 0 -> 1
|
|
(RM), so a revoked credential can never be un-revoked; an enabled revocation transition always starts
|
|
from an unset bit (RM2); every recorded mutation is a real change (NS); only the controller can write
|
|
(CTRL); the epoch strictly increases on every mutation (EP, the freshness anchor); setting one bit
|
|
leaves every other credential's bit unchanged (FR, frame isolation).
|
|
Negative controls (SAT, both fired): drop the terminal guard -> a Revocation list clears a bit
|
|
(un-revoke); drop the bit mask -> flip a neighbouring credential's bit while setting one.
|
|
Caveat: Suspension lists are intentionally reversible, so the monotonicity property is asserted only
|
|
for Revocation lists. The zk non-revocation path (verifyNonRevocation + SP1 circuit) is [MEASURE]
|
|
(circuit pending, fail-closed off until configured); its codeless-verifier L1 guard and fail-closed
|
|
binding are covered by the aere-pq-screen Hardhat tests, not modelled here. This satisfies the task's
|
|
"AereBitstringStatusList status-bit monotonicity of revocation" item.
|
|
|
|
## Contracts deliberately NOT modelled (honesty over coverage-theater)
|
|
Per the task's own guidance, these have key properties that are access-control / fail-closed /
|
|
precompile-return-handling, already covered by their Hardhat tests (and, where applicable, Halmos
|
|
symbolic). A z3 arithmetic model would be vacuous:
|
|
- AereIdentityRegistry8004: register is gated to the agent's current controller; resolve/getAgent are
|
|
pass-through reads. Pure access-control. Covered by test/erc8004-adapters.test.js.
|
|
- AereValidationRegistry8004: respondValidation is fail-closed PQC-return handling (reverts on an
|
|
invalid Falcon-512 / ML-DSA-44 proof) plus a one-shot Pending -> Completed status and a request
|
|
nonce used only for id derivation. Its structural invariants (fail-closed verify, record-once,
|
|
monotone nonce) are already the exact shapes proved by recovery_registry_smt and credential_
|
|
registry_smt, and it is covered by erc8004-adapters.test.js. Not forced.
|
|
- AereRandomnessBeaconV2: precompile-return-handling / fail-closed (task explicitly lists it).
|
|
- AerePQAggregateModule and the L1 fix on AerePQAggregateVerifier: the t-of-n authorization is already
|
|
covered by pqaggregate_smt.py; the L1 fix itself is a codeless-gateway constructor guard (fail-
|
|
closed), covered by the AerePQAggregate.test.js "rejects a code-less SP1 gateway at construction"
|
|
test. Not arithmetic.
|
|
- The other L1 codeless-verifier constructor guards (AereComputeMarketV3, AereBitstringStatusList):
|
|
fail-closed constructor guards covered by the AereComputeMarketV3 and aere-pq-screen Hardhat tests;
|
|
not arithmetic. (ComputeMarketV3's escrow arithmetic is already modelled by computemarket_smt.py;
|
|
BitstringStatusList's real arithmetic is the revocation monotonicity modelled above, not the L1
|
|
guard.)
|
|
Also, within the modelled contracts, the precompile-return / access-control halves (AP2 Falcon
|
|
verification, VectorStore L2 provider-binding) are noted as test/precompile-trust covered; the z3
|
|
models only the genuine arithmetic/state halves.
|
|
|
|
## PART 2: canonical runner new totals and REPRODUCE.md
|
|
All four new models wired into aerenew/formal-consensus/run_consensus_verification.py CONTRACT_MODELS
|
|
(existing 15 entries kept intact). Fresh canonical run on 2026-07-19:
|
|
|
|
models run: 19 passed: 19 (8 consensus + 11 contract)
|
|
verdict lines: 156 PROVED 89 CEX-FOUND 0 FAILED
|
|
RESULT: ALL MODELS PASS (proofs PROVED, negative controls fired) exit 0
|
|
|
|
Delta from the previous 15-model run (128 PROVED / 79 CEX-FOUND): +28 PROVED, +10 CEX-FOUND, exactly
|
|
the four new models' contribution (ap2_mandate 9/3, reputation_gate 5/2, vectorstore 8/3,
|
|
bitstring_status 6/2).
|
|
|
|
REPRODUCE.md section 3b updated to the real fresh count: "19 models: 8 consensus + 11 contract",
|
|
the tally "156 PROVED, 89 CEX-FOUND, 0 FAILED", the four newly-added models named with their
|
|
one-line invariants and per-model verdict-line contributions, and the [VERIFY] boundary note extended
|
|
to the ERC-8004 mandate / reputation adapters (the model bounds the adapter's guard logic and
|
|
accounting, not the AERE402 rail's token movement or the live reputation's score formula). No stale
|
|
"15 models" or "128 PROVED" counts remain; no em-dashes or "--" in the prose.
|
|
|
|
## Files added / changed
|
|
- NEW aerenew/formal-consensus/ap2_mandate_smt.py
|
|
- NEW aerenew/formal-consensus/reputation_gate_smt.py
|
|
- NEW aerenew/formal-consensus/vectorstore_smt.py
|
|
- NEW aerenew/formal-consensus/bitstring_status_smt.py
|
|
- EDIT aerenew/formal-consensus/run_consensus_verification.py (4 entries appended to CONTRACT_MODELS)
|
|
- EDIT aerenew/REPRODUCE.md (section 3b: 15 -> 19 models, 128/79 -> 156/89, named the new models)
|