// ----------------------------------------------------------------------------- // FalconQuorum.qnt // // FORMAL MODEL of the AERE post-fork Falcon quorum-certificate consensus rule. // // Models the DESIGN described in consensus-pqc/QUORUM-DESIGN.md and implemented // by FalconSealValidationRule (besu-consensus-pqc-quorum.patch): // // * There are N QBFT validators, each holding a Falcon-512 keypair. // * When a validator commits a block it ALSO Falcon-signs the same commit hash // and gossips the seal (validatorIndex, signature). // * A post-fork ("blocking") block is VALID iff its embedded certificate carries // >= quorum(N) DISTINCT valid Falcon seals from registered validators over the // correct commit hash. (Java: calculateRequiredValidatorQuorum(N) = ceil(2N/3).) // * ECDSA committed seals still govern in parallel; this model isolates the // ADDED Falcon-quorum gate — the new safety/liveness surface. // // Adversary model (Falcon layer): // * An HONEST validator signs at most one block per height and only produces // seals that verify -> it can be counted toward at most ONE of two conflicting // blocks. // * A FALCON-FAULTY validator is one whose seal does NOT count toward an honest // quorum: either it emits a signature that fails verification (registry // mismatch / crash / correlated impl defect) OR it equivocates. A faulty // node that equivocates CAN place a valid seal on BOTH conflicting blocks // (it holds its own key), so it counts toward both. // // This is a check of the DESIGN's combinatorics, NOT of the Besu Java code. // It never asserts anything about mainnet consensus (chain 2800 consensus is // classical ECDSA QBFT; the Falcon quorum is not activated there). // // Besu quorum: quorum(N) = ceil(2N/3) (fastDivCeiling(2N,3)) // BFT fault bound: f(N) = floor((N-1)/3) // ----------------------------------------------------------------------------- module falcon_quorum { // -------- configuration -------- const N: int // number of validators (instantiated to 4, 5, 7 below) // Besu's quorum: ceil(2N/3) = floor((2N+2)/3) for the integers we use. pure def quorum(n: int): int = (2 * n + 2) / 3 // Standard BFT fault bound f = floor((N-1)/3). pure def faultBound(n: int): int = (n - 1) / 3 // Validator index set 0..N-1. pure def validators(n: int): Set[int] = 0.to(n - 1) // -------- state -------- // signA / signB : the DISTINCT valid-seal index sets that end up embedded in // the certificate of two (possibly conflicting) candidate // blocks A and B at the SAME height. // faulty : the Falcon-faulty validator set chosen by the adversary. var signA: Set[int] var signB: Set[int] var faulty: Set[int] val honest: Set[int] = validators(N).exclude(faulty) // A block's certificate meets the blocking rule iff it carries >= quorum seals. val certifiedA: bool = signA.size() >= quorum(N) val certifiedB: bool = signB.size() >= quorum(N) // Well-formedness of a certificate assignment under the adversary model: // - every seal index is a real validator; // - an HONEST validator never appears on both conflicting blocks (no equivocation) // and never appears unless it actually signed (captured by membership); // - FAULTY validators may appear on either/both (equivocation allowed). val wellFormed: bool = signA.subseteq(validators(N)) and signB.subseteq(validators(N)) and faulty.subseteq(validators(N)) and honest.forall(h => not(signA.contains(h) and signB.contains(h))) // Are A and B conflicting? Two distinct candidate blocks at one height. // We treat them as conflicting whenever both certificates are non-empty and // the seal sets differ in a way only possible for two different blocks. // For the safety theorem we conservatively assume A != B (distinct blocks). val conflicting: bool = true // -------- adversary: pick faulty, signA, signB nondeterministically -------- action init = all { nondet fs = validators(N).powerset().oneOf() nondet sa = validators(N).powerset().oneOf() nondet sb = validators(N).powerset().oneOf() all { faulty' = fs, signA' = sa, signB' = sb, } } action step = all { faulty' = faulty, signA' = signA, signB' = signB, } // -------- SAFETY -------- // Under the BFT assumption (<= f faulty), no two conflicting blocks can both // reach a Falcon quorum. Honest signers split disjointly; faulty (<= f) may // double-sign. So |signA| + |signB| <= |honest| + 2|faulty| = N + |faulty|. // Both >= quorum requires 2*quorum <= N + |faulty| <= N + f, which is false // for N in {4,5,7}. Hence the AND is impossible => safety. val bftAssumption: bool = faulty.size() <= faultBound(N) // The invariant to check (must hold in ALL reachable states): val safety: bool = (wellFormed and bftAssumption and conflicting) implies not(certifiedA and certifiedB) // Non-vacuity / assumption is load-bearing: DROP the <= f bound and safety CAN // break. Apalache REFUTES this and returns a state where > f equivocating // faulty validators put valid seals on BOTH conflicting blocks so each reaches // quorum. This proves the safety theorem genuinely depends on <= f faults. val safetyNoAssumption: bool = (wellFormed and conflicting) implies not(certifiedA and certifiedB) // -------- LIVENESS / no-halt boundary -------- // With exactly f Falcon-faulty validators, the honest set (N - f) can still // form a quorum. This is a CONFIG theorem (state-independent): N - f >= Q. val livenessAtF: bool = (N - faultBound(N)) >= quorum(N) // With f+1 Falcon-faulty validators, the honest set is BELOW quorum, so no // post-fork block can be certified -> the chain HALTS. This is exactly why // N >= 7 is needed to tolerate 2 faults. val haltAtFplus1: bool = (N - (faultBound(N) + 1)) < quorum(N) // Sanity invariant that always holds (used by `quint run` to confirm the // config theorems for this N): val configTheorems: bool = livenessAtF and haltAtFplus1 // LIVENESS invariant (Apalache VERIFIES this holds): when at most f validators // are Falcon-faulty and every honest validator signs the single canonical // block A, the block IS certified -> the chain does NOT halt. val livenessInv: bool = (wellFormed and bftAssumption and signA == honest and signB == Set()) implies certifiedA // HALT WITNESS (Apalache REFUTES this -> prints the counterexample state): // this predicate CLAIMS "an (f+1)-fault halt configuration never occurs". // Apalache finds it DOES occur: with f+1 faulty and every honest validator // signing block A, |honest| = N-(f+1) < quorum, so A is NOT certified. // That refuting state is exactly the f+1-fault halt. This is the reason // N>=7 is required to tolerate 2 faults. val noHaltAtFplus1: bool = not(wellFormed and faulty.size() == faultBound(N) + 1 and signA == honest and signB == Set() and not(certifiedA)) } // Concrete instances ----------------------------------------------------------- module n4 { import falcon_quorum(N = 4).* } module n5 { import falcon_quorum(N = 5).* } module n7 { import falcon_quorum(N = 7).* }