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.
17 KiB
AERE consensus — machine-checked SAFETY + LIVENESS verification (with Falcon quorum extension)
Date: 2026-07-12 · Scope: AERE QBFT / IBFT 2.0 (Hyperledger Besu, chain 2800) consensus and its staged Falcon-512 quorum-certificate extension (log-only + blocking).
This is a credibility artifact for the audit. Everything below actually runs and is re-runnable in seconds. No mainnet node or key was touched to produce it.
1. Tool used
Primary: z3 (SMT), version 4.16.0, Python 3.14.4 bindings, on Windows (MSYS2/MINGW64).
The models follow the established formal-consensus/*_smt.py convention: each property is discharged
by proving its negation UNSAT under the exact protocol guards, and every proof is paired with a
firing NEGATIVE CONTROL (a deliberately broken variant whose violation is SAT) so the results
are demonstrably non-vacuous.
Why z3 and not TLA+/TLC: TLC/Apalache need a JVM. Java is not installed on this workstation, so
TLC/Apalache could not be set up cleanly here (the task permits z3/SMT in that case). z3 4.16.0 is
present and the surrounding formal-consensus/ directory is already an SMT corpus, so z3 is the
rigorous core.
Secondary cross-check: Quint 0.32.0 randomized simulator over the pre-existing FalconQuorum.qnt
(the Apalache-targeted TLA+‑family spec of the Falcon quorum combinatorics). Quint's symbolic
backend is Apalache (JVM, absent), so this is randomized sampling, not a proof — it is reported
only as an independent sanity cross-check of the z3 results.
2. Files (all under aerenew/formal-consensus/)
| File | What it verifies |
|---|---|
qbft_safety_smt.py |
SAFETY / agreement — no two different blocks committed at one height, <= f Byzantine equivocation. L1 intersection lemma (all N) + L2 one-round + L3 cross-round (IBFT 2.0 locking). |
falcon_logonly_noop_smt.py |
Falcon LOG-ONLY no-op — the certificate changes neither any block hash nor the committable set. |
falcon_blocking_smt.py |
Falcon BLOCKING — safety under <= f Falcon faults, liveness at the fault bound, and the machine-checked reason N ≥ 7 is required. |
qbft_liveness_smt.py |
LIVENESS under partial synchrony (post-GST round-change progress). |
qbft_prepare_counting_smt.py |
2026-07-14 second-client bug + fix — Besu's explicit-Prepare quorum rule ⇒ a proposer omitting its self-Prepare deadlocks at D=f; the fix restores liveness (all N) and is safety-neutral. |
qbft_digest_keyed_smt.py |
2026-07-14 Finding-A shipped fix: DIGEST-KEYED vote tally: prepare/commit tallies keyed by digest so prepares cast for an old proposal digest D0 can never be counted toward a conflicting D1 at the same (height,round). Removes the unjustified-quorum / cross-client fork; negative control fires on the flat-set bug. |
qbft_locking_smt.py |
2026-07-14 Findings D/E: IBFT 2.0 round-change locking with the two engine roles (PROPOSER re-proposes the highest prepared value; ACCEPTOR refuses an unjustified round>0 proposal) as explicit variables mapping 1:1 onto the C# fix. Proves the second client MUST enforce acceptor-side locking before it can produce. |
run_consensus_verification.py |
one-command runner for all seven SMT models (single PASS/FAIL). |
FalconQuorum.qnt |
pre-existing Quint/Apalache spec (secondary randomized cross-check). |
3. What was proven — exact configs and results
Besu formulas used throughout: quorum(N) = ceil(2N/3) (fastDivCeiling(2N,3)), BFT fault bound
f(N) = floor((N-1)/3). For N=4→q=3,f=1 · N=5→q=4,f=1 · N=7→q=5,f=2.
3.1 SAFETY (agreement) — qbft_safety_smt.py → all PROVED, all neg-controls fired
- L1 quorum-intersection lemma — UNBOUNDED in N (PROVED, unsat): for every N≥1,
2·quorum(N) − N ≥ f(N)+1. Since any two quorums overlap in≥ 2q−Nvalidators (inclusion–exclusion), any two commit quorums share > f validators, hence ≥ 1 honest validator. This is a genuine all-N proof (linear integer arithmetic / Presburger, decidable). - L2 one-round agreement — bounded, N ∈ {4,5,7,10,13,16} (PROVED, unsat): explicit per-validator
COMMIT with an equivocating Byzantine adversary (faulty may double-sign both blocks),
|faulty| ≤ f. Two distinct blocks cannot both reach aceil(2N/3)COMMIT quorum. - L3 cross-round agreement — bounded, N ∈ {4,5,7,10,13}, R=3 rounds (PROVED, unsat): faithful per-validator model of IBFT 2.0 locking / round-change justification (honest validators only PREPARE the highest previously-prepared value; the lock is sound because a round-change quorum intersects every earlier prepare-quorum in an honest node — L1). Even across round changes, no two rounds commit different values.
- NEGATIVE CONTROLS (all CEX-FOUND / sat): lowering the quorum to
ceil(N/2)breaks L1 (intersection can be ≤ f, witness N=2) and L2 (two blocks both "commit" at N=4); dropping the lock breaks L3 (a later round commits a different value). This proves theceil(2N/3)quorum and the locking rule are load-bearing — the model genuinely catches unsafety.
3.2 Falcon LOG-ONLY no-op — falcon_logonly_noop_smt.py → all PROVED, both neg-controls fired
Under A2 (keccak injective), with the certificate excluded from every hashed pre-image and never gating finality before the fork:
- N1 (PROVED): the block hash is invariant to the Falcon certificate (cert is outside the pre-image → two headers differing only in the cert hash identically).
- N2 / N3 (PROVED): committability, and the whole set of committable blocks, are identical with the log-only cert enabled vs. the baseline (no Falcon layer) — a strict no-op.
- NEGATIVE CONTROLS (CEX-FOUND): a pre-image that includes the cert makes the hash cert-dependent; a log-only rule that gates on the cert changes the committable set. So the exclusion and the always-true gate are load-bearing. This is exactly the mainnet (chain 2800) posture: log-only, additive, non-gating, zero halt risk.
3.3 Falcon BLOCKING — falcon_blocking_smt.py → all PROVED/expected, neg-controls fired
- B-SAFETY — bounded, N ∈ {4,5,7} (PROVED, unsat): in blocking mode
≤ fFalcon-key-faulty validators (allowed to equivocate — a valid own-key seal on both conflicting blocks) can never certify two conflicting blocks. - B-LIVENESS at the fault bound — UNBOUNDED (PROVED, unsat):
N − f(N) ≥ quorum(N)for all N, so the honest+correct set alone can always form a certificate. - THRESHOLD — why N ≥ 7 (machine-checked):
- T1 (PROVED): no N ∈ {4,5,6} is both safe (
f≥2) and live (N−2 ≥ q) against 2 simultaneous Falcon faults; N=7 is (f=2, N−2=5 ≥ q=5). N=7 is the smallest validator set that survives 2 Falcon faults safely and live-ly. - T2: the single-fault liveness margin
(N−1)−quorum(N)is 0 exactly at N ∈ {4,5} (proved unsat that it is nonzero) and ≥ 1 at N ≥ 6 (config). At the proved zero-margin sizes N ∈ {4,5}, one Falcon fault would force all remaining honest validators to sign every block, which is why the set was grown past 5. The live mainnet size is N=7 (verified on-chain 2026-07-16 viaqbft_getValidatorsByBlockNumber; f=2, quorum 5), the next BFT-optimal 3f+1, which has positive single-fault margin and tolerates 2 faults. - T3 (STALL, reachable/sat): at N=5 with 2 Falcon-faulty the correct set (3) is below quorum (4) → no certificate can form → post-fork chain stalls. At N=7 the same 2 faults still leave quorum (5 = 5) → no stall (PROVED unsat that it stalls). This is precisely why blocking is gated on N ≥ 7 + re-genesis.
- T1 (PROVED): no N ∈ {4,5,6} is both safe (
- NEGATIVE CONTROLS (CEX-FOUND): dropping the
≤ fbound, or lowering the quorum toceil(N/2), lets two conflicting blocks both be certified — the assumption and the quorum are load-bearing.
3.4 LIVENESS under partial synchrony — qbft_liveness_smt.py → all PROVED/expected, neg-ctrl fired
Premise (assumed, not proven — it is the protocol's own premise): partial synchrony / post-GST message delivery within an (adaptively growing) round timeout. Asynchronous liveness is impossible (FLP); we do not claim it.
- L-PROGRESS — bounded, N ∈ {4,5,7} (PROVED, unsat): with round-robin
proposer(r)=r mod N, any window off+1consecutive rounds contains an honest proposer (≤ ffaulty cannot coverf+1distinct proposers;f+1 ≤ Nchecked). - L-COMMIT — UNBOUNDED + per-N witnesses (PROVED / sat): an honest proposer's round commits
because the honest set alone
N−f ≥ quorumreaches PREPARE+COMMIT quorum. - NEGATIVE CONTROL (CEX-FOUND): with
f+1faulty, a wholef+1-round window can be all-faulty proposers → no guaranteed progress — the≤ fbound is load-bearing for liveness too.
3.5 Secondary cross-check — Quint FalconQuorum.qnt (randomized, not a proof)
safety: no violation over 20k random samples on n4/n5/n7.safetyNoAssumption(drop≤ f): violation found — matches z3 neg-control.configTheorems(livenessAtF ∧ haltAtFplus1): no violation on n4/n5/n7.noHaltAtFplus1(the f+1 halt witness): violation found on n4/n5/n7 — the stall is reachable.
4. Honest boundary (what this does and does NOT establish)
- Bounded vs. unbounded. L1, B-LIVENESS, L-COMMIT and the threshold arithmetic are unbounded (proved for all N via decidable linear integer arithmetic). L2, L3, B-SAFETY, L-PROGRESS are bounded model checks over the stated finite configs (N ∈ {4,5,7}; L3 uses R=3 rounds). A bounded model check is a decision procedure over that finite configuration, not a proof for all N or all-length executions. Stated plainly and deliberately.
- Partial synchrony is assumed, not proven — liveness holds only post-GST (standard for QBFT/IBFT and every partially-synchronous BFT protocol; FLP forbids asynchronous liveness).
- Design combinatorics, not the Besu Java bytecode. These models check the QBFT/IBFT 2.0 and
Falcon-certificate design math (message quorums, locking, certificate counting). They do not
execute or symbolically analyze the compiled Besu code. The complementary evidence for the Java
layer is the consensus unit-test suite and the isolated-testnet runs recorded in
../consensus-pqc/QUORUM-DESIGN.md. - Mainnet consensus is classical ECDSA QBFT at N=7, with NO Falcon layer of any kind. Chain 2800
QBFT is signed with classical ECDSA (live set N=7, f=2, quorum 5, verified on-chain); it is
not post-quantum. There is no Falcon certificate, not even log-only, in any mainnet header
(verified: mainnet
extraDatais byte-identical to upstream Besu, with no Falcon list). The Falcon log-only no-op (§3.2) and blocking mode (§3.3) are isolated-testnet R&D (chainIds 220744 / 330855, N=4), never live on chain 2800; blocking additionally requires N ≥ 7 + a re-genesis carrying a Falcon anchor. Nothing here claims mainnet consensus is post-quantum, and nothing here is live on mainnet. - Cryptographic soundness of Falcon-512 is out of scope (covered by NIST KATs + the live PQC
precompile /
eth_call, not by these combinatorial models). B-SAFETY assumes a faulty node can at most equivocate with its own key; it cannot forge another validator's seal.
4b. The 2026-07-14 second-client interop bug + fix — machine-checked (qbft_prepare_counting_smt.py)
Added 2026-07-14. During the second-execution-client work (a patched Nethermind QBFT block producer interoperating with Besu on an isolated testnet), a real interop defect was found in the field, fixed, and confirmed by adversarial soaks (0 forks; Besu-parity throughput after the fix). This model turns that bug + fix into a re-runnable formal artifact and proves the fix is safe.
The defect. Besu tallies the PREPARE quorum from explicit Prepare messages and does not
count the Proposal as an implicit self-Prepare from the proposer. The Nethermind proposer emitted a
Proposal but no explicit self-Prepare, leaving every block it proposed one Prepare short of
quorum → never committed → the chain stalled. The fix: the proposer now also broadcasts an
explicit self-Prepare (round 0 and round>0) in AereQbftLiveEngine.cs.
Why it bit only at the fault boundary (the arithmetic the model proves). With D validators down
and the proposer alive, explicit Prepares available are N−D−1 (buggy) or N−D (fixed). At the
boundary D=f and optimal size N=3f+1: alive A = N−f = 2f+1 = quorum(N). So the buggy count
is A−1 = 2f = quorum−1 (one short → deadlock even with every alive validator honest), while the
fixed count is A = 2f+1 = quorum (commits). For D<f the buggy rule still clears quorum — which
is exactly why the defect only surfaced under the kill-2 (f=2) adversarial soak, never in the healthy
set.
| Property | Result |
|---|---|
| P1 — buggy rule ⇒ guaranteed stall at D=f (N=3f+1 ∈ {4,7,10,13}): with all alive validators honest and preparing, the explicit-Prepare count cannot reach quorum | PROVED (unsat) |
| P1-neg — fixed rule, same config, IS committable (proposer self-Prepares → quorum reachable) | CEX-FOUND (sat) — the missing self-Prepare is the whole cause |
P2 — fixed rule ⇒ liveness restored for ALL N (unbounded Presburger): N − f(N) ≥ quorum(N) at the boundary |
PROVED (unsat) |
P2-neg — buggy count N−f−1 < quorum(N) for some N (a real deadlock exists) |
CEX-FOUND (sat) — self-Prepare is load-bearing |
| P3 — fixed rule PRESERVES SAFETY (N ∈ {4,7,10,13}): the proposer's one extra honest Prepare (to its single proposed value) cannot make two distinct values both reach quorum under ≤ f equivocating Byzantine | PROVED (unsat) |
| P3-neg — lowering quorum to ceil(N/2) breaks safety (two values both commit) | CEX-FOUND (sat) — the ceil(2N/3) threshold, unchanged by the fix, is what carries safety |
16 checks: 8 PROVED, 8 negative controls fired. So the fix restores liveness for all N and is safety-neutral (safety rests on the quorum threshold, invariant to whether the proposer's Prepare is implicit or explicit). This is consistent with, and explains, the empirical result: 0 forks throughout and Besu-parity throughput once the self-Prepare lands. Honest boundary: P1/P3 are bounded per-N checks, P2 an unbounded identity; this models the QBFT Prepare-tally design combinatorics, not the Besu/Nethermind bytecode.
5. How to re-run
From aerenew/formal-consensus/:
# one command: runs all seven models, prints a single PASS/FAIL, exit 0 on success
python run_consensus_verification.py
# or individually (each prints PROVED / CEX-FOUND lines and exits 0 on success)
python qbft_safety_smt.py
python falcon_logonly_noop_smt.py
python falcon_blocking_smt.py
python qbft_liveness_smt.py
python qbft_prepare_counting_smt.py # 2026-07-14 second-client bug + fix
python qbft_digest_keyed_smt.py # 2026-07-14 Finding-A shipped fix (digest-keyed tally)
python qbft_locking_smt.py # 2026-07-14 Findings D/E (engine-role IBFT locking)
# optional secondary cross-check (randomized simulation, needs quint; NOT a proof)
quint run FalconQuorum.qnt --main=n7 --invariant=safety --max-samples=20000 --max-steps=1
quint run FalconQuorum.qnt --main=n7 --invariant=safetyNoAssumption --max-samples=20000 --max-steps=1 # expect violation
quint run FalconQuorum.qnt --main=n5 --invariant=noHaltAtFplus1 --max-samples=20000 --max-steps=1 # expect violation (halt witness)
Requirements: pip install z3-solver (proved with z3 4.16.0); optional npm i -g @informalsystems/quint
(0.32.0). To reproduce the TLA+/Apalache symbolic path instead, run FalconQuorum.qnt through
quint verify on a box with a JVM (e.g. the infra box) — the arithmetic and the results are identical.
6. One-line verdict
QBFT/IBFT 2.0 agreement (no two different blocks at one height under ≤ f equivocating Byzantine
validators) is machine-checked — unbounded via quorum intersection and bounded across N ∈ {4,5,7,10,13,16}
including cross-round locking; liveness holds under partial synchrony; the Falcon log-only
certificate is a proven strict no-op (mainnet posture, zero halt risk); Falcon blocking is
safe and live at the fault bound and its N ≥ 7 requirement is machine-checked. The 2026-07-14
second-client interop bug (Besu counts only explicit Prepares → a proposer that omits its self-Prepare
deadlocks at exactly the D=f boundary) and its fix (proposer broadcasts an explicit self-Prepare)
are machine-checked: the fix restores liveness for all N and is safety-neutral. Every proof is
backed by a firing negative control. Boundaries (bounded model-check, partial-synchrony assumption,
design-not-bytecode, mainnet-is-classical-ECDSA-log-only) are stated honestly.