aere-research/formal-consensus/CONSENSUS-VERIFICATION-2026-07-12.md
Aere Network 4a0b48588c Initial public release
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.
2026-07-20 01:02:30 +03:00

17 KiB
Raw Blame History

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 ≥ 2qN validators (inclusionexclusion), 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 a ceil(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 the ceil(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 ≤ f Falcon-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 (N2 ≥ q) against 2 simultaneous Falcon faults; N=7 is (f=2, N2=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 (N1)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 via qbft_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.
  • NEGATIVE CONTROLS (CEX-FOUND): dropping the ≤ f bound, or lowering the quorum to ceil(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 of f+1 consecutive rounds contains an honest proposer (≤ f faulty cannot cover f+1 distinct proposers; f+1 ≤ N checked).
  • L-COMMIT — UNBOUNDED + per-N witnesses (PROVED / sat): an honest proposer's round commits because the honest set alone Nf ≥ quorum reaches PREPARE+COMMIT quorum.
  • NEGATIVE CONTROL (CEX-FOUND): with f+1 faulty, a whole f+1-round window can be all-faulty proposers → no guaranteed progress — the ≤ f bound 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 extraData is 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 ND1 (buggy) or ND (fixed). At the boundary D=f and optimal size N=3f+1: alive A = Nf = 2f+1 = quorum(N). So the buggy count is A1 = 2f = quorum1 (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 Nf1 < 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.