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.
192 lines
11 KiB
Markdown
192 lines
11 KiB
Markdown
# Formal-verification extension: new fund-flow contracts (z3 SMT + full regression)
|
|
|
|
Date: 2026-07-19
|
|
Scope: three new money contracts for Aere Network, modelled in z3 in the exact style of the
|
|
existing consensus/contract SMT corpus, plus a full regression of the new-contract test suite to
|
|
confirm the recent security fixes (M1 giveFeedback gate, L1 codeless-gateway guards, L2 VectorStore
|
|
receipt binding) broke nothing.
|
|
|
|
Honesty posture (same caveat as the rest of the corpus): each SMT script checks the DESIGN-level
|
|
accounting/guard logic over the modelled transitions with unbounded Ints, NOT the compiled EVM
|
|
bytecode. A model counts as non-vacuous only because a NEGATIVE CONTROL (one guard removed) fires a
|
|
real counterexample. Trusted external primitives (SP1 gateway proof soundness, the Falcon-512
|
|
precompile at 0x0AE1, SafeERC20 whole-tx revert) are marked [VERIFY] and are not re-proved here.
|
|
|
|
All verdicts below are the REAL z3 output and the REAL mocha output, reproduced verbatim from the runs.
|
|
|
|
---
|
|
|
|
## PART 1: new z3 SMT models
|
|
|
|
All three scripts live in `aerenew/formal-consensus/` (they join the existing corpus next to
|
|
`spokepool_smt.py`, `settlementhub_smt.py`, `threshold_account_smt.py`). Run each with
|
|
`python <script>.py` (z3-solver 4.16.0, confirmed installed). Each exits 0 only if every PROOF is
|
|
PROVED and every NEGATIVE CONTROL fired.
|
|
|
|
### A. computemarket_smt.py -> AereComputeMarketV3, ESCROW SOLVENCY
|
|
|
|
Contract: `aerenew/contracts/contracts/depin/AereComputeMarketV3.sol`
|
|
Invariant (the contract's own `isSolvent`): per asset T, `bal[T] >= totalLiabilities[T]` and
|
|
`totalLiabilities[T] >= 0`, where a reward is escrowed in its own `token` and EVERY provider/challenger
|
|
bond is native AERE (address(0)). So native liabilities carry native-token rewards PLUS all bonds; an
|
|
ERC-20 asset carries only its own rewards. No fee, no owner, no sweep in this contract, so each op's
|
|
balance delta equals its liability delta on the same asset.
|
|
|
|
VERDICT: PROVED (16 proofs UNSAT), 3 NEGATIVE CONTROLS fired (SAT). Script exit 0.
|
|
|
|
Proofs (all z3=unsat, inductive across the real transitions):
|
|
- base case (empty contract) satisfies INV
|
|
- postJob(native) / postJob(ERC20, received==reward guard) preserve INV
|
|
- claimJob (bond escrowed to native), cancelJob (refund + uncommit) preserve INV
|
|
- submitResult (status-only, no fund move) preserves INV
|
|
- settle (acceptResult / finalize / submitResultZK): reward leg on token asset, bond-return leg on
|
|
native, AND the combined leg when token==native, all preserve INV
|
|
- reclaimExpired (reward + bond leave together) preserves INV
|
|
- dispute (challenger bond escrowed) preserves INV
|
|
- resolveDispute BOTH branches (providerWon / not) preserve INV on token and native legs
|
|
- no double-count: an ERC20-reward + native-bond settle keeps BOTH assets solvent, reward touched
|
|
only on its asset, bond only on native
|
|
- double-pay: a second payout after the terminal status is set is guard-infeasible
|
|
- ZK_VERIFIED: paying without a valid proof is impossible (settlement sits strictly behind the
|
|
`ZK_VERIFIER.verifyProof` gate, which reverts on an invalid proof)
|
|
|
|
Negative controls (all z3=sat, load-bearing):
|
|
- BUGGY postJob (commit full reward but receive less, i.e. no received==reward check) CAN under-back
|
|
the escrow [witness: reward=1, received=0]
|
|
- BUGGY claim (native bond mis-posted to the ERC-20 reward asset) CAN under-back that asset
|
|
[witness: bond=1, balE=0, liabE=0]
|
|
- BUGGY ZK settle (no verifyProof gate) CAN pay without a valid proof [witness: pay_zk=1, proofValid=0]
|
|
|
|
[VERIFY] SP1 gateway proof soundness and the Falcon-512 precompile at 0x0AE1 are trusted primitives,
|
|
not re-proved by this SMT model.
|
|
|
|
### B. destinationsettler_smt.py -> AereDestinationSettler, NO-ARBITRARY-RECIPIENT + ATOMICITY
|
|
|
|
Contract: `aerenew/contracts/contracts/intents/AereDestinationSettler.sol`
|
|
Guards modelled from the real `fill()`: G1 declOrderId==orderId, G2 declRecipient!=0, G3
|
|
declOutputToken!=0, G4 declOutputAmount!=0, G5 fills[orderId].filler==0 (unfilled), G6 delivered token
|
|
== declared token AND delivered recipient == declared recipient AND delivered amount >= declared amount.
|
|
|
|
VERDICT: PROVED (5 proofs UNSAT), 4 NEGATIVE CONTROLS fired (SAT). Script exit 0.
|
|
|
|
Proofs:
|
|
- P1 a successful fill can deliver ONLY to the order's declared recipient (an attacker address is
|
|
impossible)
|
|
- P1b the transfer target equals the declared recipient AND is non-zero
|
|
- P2 a second fill of an already-filled order is guard-infeasible (AlreadyFilled), so a solver is
|
|
repaid at most once per intent
|
|
- P2b the fill is bound to the arg orderId (G1), so no cross-slot record can dodge the duplicate check
|
|
- P3 an output that mismatches the declared token OR is short is rejected (OutputMismatch)
|
|
|
|
Negative controls:
|
|
- no-recipient-check settler CAN deliver to an arbitrary attacker [witness: declRecipient=1,
|
|
attacker=2, deliveredRecipient=2]
|
|
- no-AlreadyFilled settler CAN double-fill one order (double repayment)
|
|
- no-ZeroRecipient settler CAN record a delivery to address(0)
|
|
- no-OutputMismatch settler CAN record a wrong-token delivery [witness: declOutputToken=1,
|
|
deliveredToken=2]
|
|
|
|
[VERIFY] Delivery ATOMICITY (effects-before-interaction record, then a single SafeERC20 pull
|
|
solver->recipient that reverts the whole tx on failure, under nonReentrant) is an EVM-revert property
|
|
asserted structurally, not by this arithmetic model. [MEASURE] Origin-chain repayment is gated by the
|
|
validator-set-anchored finality+inclusion proof and is out of scope here.
|
|
|
|
### C. migrator_smt.py -> AereAccountMigrator, ONLY-DESTINATION + ATOMICITY
|
|
|
|
Contract: `aerenew/contracts/contracts/pqc/AereAccountMigrator.sol`
|
|
Guards modelled from the real `_migrate()`: H1 destination!=0, H2 length match, H3 each token/amount
|
|
!=0, H4 moveNative => msg.value!=0, H5 !moveNative => msg.value==0 (StrayNative, the no-strand guard),
|
|
H6 every ERC-20 leg is safeTransferFrom(caller, destination, amount), H7 native leg forwards the full
|
|
msg.value to destination and reverts on failure.
|
|
|
|
VERDICT: PROVED (5 proofs UNSAT), 4 NEGATIVE CONTROLS fired (SAT). Script exit 0.
|
|
|
|
Proofs:
|
|
- P1 every ERC-20 leg delivers ONLY to the caller's non-zero `destination` (no third-party target)
|
|
- P2 no custody: sum(pulled from caller) == sum(delivered to destination), migrator ERC-20 delta == 0
|
|
- P3 atomicity: no partial move, all legs commit or none (whole-tx revert)
|
|
- P4 no stranded native: migrator native delta == 0 (StrayNative guard + full forward)
|
|
- P5 migrateToPqcAccount honors a pinned expectedDestination (AddressMismatch guard)
|
|
|
|
Negative controls:
|
|
- BUGGY skim (a fee f>0 diverted to a third party) CAN leak funds off `destination` [witness:
|
|
amount0=2, fee=1, destGets=1, feeCollectorGets=1]
|
|
- BUGGY per-leg try/catch (continue past a failed leg instead of whole-tx revert) CAN leave a PARTIAL
|
|
move, i.e. a half-drained old account [witness: leg_ok_0=true, leg_ok_1=false]
|
|
- BUGGY no-StrayNative migrator CAN strand native value unrecoverably (no sweep exists) [witness:
|
|
msgValue=1]
|
|
- BUGGY no-ZeroDestination migrator CAN sweep to address(0) (burn) [witness: destination=0]
|
|
|
|
[VERIFY] Whole-tx atomicity rests on SafeERC20 reverting on a false/failing ERC-20 and on
|
|
nonReentrant; P3 models its consequence (all-or-nothing) rather than the opcode semantics.
|
|
|
|
### Part 1 summary table
|
|
|
|
| Model | File | Proofs (UNSAT) | Neg-controls (SAT) | Control fired? | Exit |
|
|
|-------|------|----------------|--------------------|----------------|------|
|
|
| AereComputeMarketV3 escrow solvency | computemarket_smt.py | 16 / 16 PROVED | 3 / 3 | YES | 0 |
|
|
| AereDestinationSettler no-arb-recipient + atomicity | destinationsettler_smt.py | 5 / 5 PROVED | 4 / 4 | YES | 0 |
|
|
| AereAccountMigrator only-destination + atomicity | migrator_smt.py | 5 / 5 PROVED | 4 / 4 | YES | 0 |
|
|
|
|
Every model is non-vacuous: each has at least one firing negative control. No z3 timeouts, no FAILED
|
|
lines. The out-of-decidable-scope pieces (EVM whole-tx revert atomicity, SP1/Falcon primitive
|
|
soundness, origin-chain repayment) are marked [VERIFY]/[MEASURE] honestly and are not claimed as proved.
|
|
|
|
---
|
|
|
|
## PART 2: full regression of the new-contract test suite
|
|
|
|
`npx hardhat compile` -> GREEN ("Nothing to compile", cache valid, exit 0).
|
|
|
|
Per-file `npx hardhat test test/<file>.test.js` (RandomnessBeacon run with
|
|
`--config hardhat.config.prague.js` so the EIP-2537 BLS12-381 pairing path actually executes):
|
|
|
|
| # | Test file | Result | Exit |
|
|
|---|-----------|--------|------|
|
|
| 1 | AereRecoveryRegistry.test.js | 10 passing | 0 |
|
|
| 2 | erc8004-adapters.test.js | 19 passing | 0 |
|
|
| 3 | AerePQAggregate.test.js | 18 passing | 0 |
|
|
| 4 | aere-pq-screen.test.js | 13 passing | 0 |
|
|
| 5 | destination-settler.test.js | 4 passing | 0 |
|
|
| 6 | AereRandomnessBeaconV2.test.js (prague config) | 15 passing, 1 pending | 0 |
|
|
| 7 | AereComputeMarketV3.test.js | 14 passing | 0 |
|
|
| 8 | aere-vector-store.test.js | 8 passing | 0 |
|
|
| 9 | account-migrator.test.js | 9 passing | 0 |
|
|
| 10 | AerePQFinalityCertificate.test.js | 12 passing | 0 |
|
|
|
|
GRAND TOTAL: 122 passing, 0 failing, 1 pending (all 10 files exit 0).
|
|
|
|
Notes:
|
|
- 0 failing across all 10 logs (verified by a grep for "failing": NONE).
|
|
- The 1 pending is intentional and honest: under the prague config EIP-2537 IS present, so the
|
|
"without EIP-2537, a genuine sig is also rejected" fail-closed-ABSENCE check is not applicable and is
|
|
skipped. The 15 passing include the real on-chain BLS12-381 pairing verification of genuine drand
|
|
quicknet vectors.
|
|
- destination-settler.test.js legitimately has 4 tests (valid fill, duplicate reverts, output mismatch
|
|
reverts, zero recipient reverts), the complete run, matching the properties modelled in Part 1.
|
|
|
|
The recent security fixes (M1 giveFeedback gate in the ERC-8004 adapters, L1 codeless-gateway guards,
|
|
L2 VectorStore receipt binding) introduced NO regression: erc8004-adapters (19), aere-vector-store (8),
|
|
and the whole suite stay green.
|
|
|
|
---
|
|
|
|
## Flags / honest caveats
|
|
|
|
- SMT models check DESIGN-level math/guards over the modelled transitions with unbounded Ints, NOT the
|
|
compiled EVM bytecode. Same boundary as the existing corpus.
|
|
- [VERIFY] items not proved by SMT: SP1 gateway proof soundness, Falcon-512 precompile (0x0AE1)
|
|
soundness, SafeERC20 whole-tx revert / nonReentrant atomicity.
|
|
- [MEASURE] AereDestinationSettler origin-chain repayment (off-chain SP1 prover + second chain) is out
|
|
of scope for both the SMT model and the unit test.
|
|
- 1 pending test in the regression is a deliberately-skipped not-applicable fail-closed-absence check
|
|
under the prague EVM, not a masked failure.
|
|
- No contract was modified. Only the three new SMT scripts were added.
|
|
```
|
|
|
|
Files added:
|
|
aerenew/formal-consensus/computemarket_smt.py
|
|
aerenew/formal-consensus/destinationsettler_smt.py
|
|
aerenew/formal-consensus/migrator_smt.py
|
|
```
|