144 lines
38 KiB
Markdown
144 lines
38 KiB
Markdown
# The rewrite bound of the post-quantum anchor under the round-free seal model (chain 2800)
|
|
|
|
Date: 2026-10-07 (first form the same morning; this form after two adversarial reviews the same day, section 7 lists what they changed). Scope: Aere Network chain 2800 as it runs today (ten validators, QBFT commit quorum 7, Falcon-512 anchor certificate every 32nd block with a schedule threshold of 7 seals, SLH-DSA-SHA2-128s in addition every 128th block, AIP-22 stages 1 to 3 active, stages 4 and 5 not). Companion machine-checked model: `formal-consensus/anchor_rewrite_bound_roundfree_smt.py` (z3), with its measured output in `formal-consensus/anchor_rewrite_bound_roundfree_smt.RESULT.txt`. This document is the security argument that the model checks; it is written for a reader who does not run our nodes and wants to see where each step comes from.
|
|
|
|
The question it answers, as asked by an external reviewer on 2026-10-07: an attacker who holds every classical validator key and no post-quantum key can rewrite only the tail after the latest anchor, normally up to 32 blocks, with the one-block exception described in the erratum of 2026-10-01; where is the security argument, formal model or reproducible test that establishes this bound under the corrected round-free seal model?
|
|
|
|
## 0. The statement, exactly
|
|
|
|
Against an adversary who holds every secp256k1 ECDSA validator key of chain 2800 and no post-quantum key, a node that enforces the published anchor rules with an effective seal threshold `K_eff >= 1` accepts no alternative history that replaces more than **32 blocks**: the latest anchor block itself and the 31 blocks after it. The latest anchor's **parent** and every block below it are kept. Under the corrected model of the AIP-22 erratum of 2026-10-01 (the post-quantum commit seal carries no consensus round, so an undecided sibling of the anchor's parent may carry honest validators' seals), the bound is **33 blocks**: the anchor's parent can be replaced by that sibling, and nothing below it, because the sibling shares the honest grandparent. The latest anchor's **grandparent** and every block below it are kept. A node that knows the hash of one honest anchor accepts no history that diverges at or below that anchor, whatever its length.
|
|
|
|
The bound is a function of two numbers, and the headline above is their simplest case. `K_eff` is the seal count the node enforces at an anchor: the schedule says 7 since block 20,715,632, a validator enforces 7, and the published from-genesis follower enforces 1 (section 1). `j` is the number of validators whose post-quantum keys the adversary holds. The depth is bounded (32, or 33 with the exception) **if and only if `j < K_eff`**; the exception's extra block costs `K_eff - j` honest seals over the sibling. So on a validator (`K_eff = 7`) the bound survives the loss of six post-quantum keys and falls at the seventh; on the published follower (`K_eff = 1`) it holds against zero post-quantum keys and falls at the first, and one honest seal over a sibling is enough for the 33rd block. The numbers 32 and 33 are for the chain as it runs today (interval 32 since block 20,746,736); inside the dated window in which the interval was 128 (blocks 17,225,968 to 20,746,736, 2026-09-05 to 2026-09-29) the same argument gives 128 and 129.
|
|
|
|
What this is not: it is not post-quantum finality of every block. Between anchors a block is finalized by the ECDSA commit quorum of one round, and that quorum is exactly what this adversary can forge. The defensible terms for chain 2800 today are "post-quantum certified anchors" (every 32nd block carries, under its hash, a certificate of at least seven validators' Falcon-512 seals over its parent) and "post-quantum-authenticated consensus messages" (nine of the ten validators refuse a proposal, prepare, commit or round-change without a valid post-quantum seal; the tenth, the second client, enforces it on commits). "Hybrid consensus" is defensible only with the qualification that the per-block artefact is classical; "post-quantum finality" is not, until AIP-22 stages 4 and 5 and the round-bound seal are active, and this document never uses it for the chain as it is.
|
|
|
|
## 1. The adversary and the victim
|
|
|
|
**Adversary.** Holds the private secp256k1 key of every validator in the set (all ten; after a quantum break of ECDSA, every key whose public key has ever appeared on the wire is such a key). Holds no Falcon-512 and no SLH-DSA key in the headline claim (`j = 0`); sections 4 and 5 say what changes for `j > 0`. Controls what a victim node receives and in what order. Can therefore produce any block whose validity needs only classical material: a header with the ECDSA commit seals of a quorum of one round (Q = 7 of 10), consensus message envelopes, anything a syncing node checks that is signed with secp256k1. Cannot produce one post-quantum seal. Also holds the Foundation's classical registry-writer key, so inside its own tail it can pin a rogue registry epoch and vote new validator ids (AIP-22, Security Considerations, "Registry writer"); what that does and does not buy is in section 2, Fact 2. This is the adversary of AIP-22, Security Considerations ("Threat model"), and of the threat-model page of the documentation. It is **posterior corruption**: the keys are obtained after the honest history was produced, and while that history was produced at most f = 3 validators were Byzantine, so QBFT safety held, every height has one decided block and every proposal extends the decided head (the argument of section 4 uses this; a set that was Byzantine beyond f when it sealed is outside the claim). It is the long-range rewrite, and not "harvest now, decrypt later", which does not apply to signatures.
|
|
|
|
**Victim.** A node that enforces the published rules of section 2 on every header it imports (both clients do; see section 6), with the effective threshold its configuration gives it:
|
|
|
|
- a **validator** (and any node started without the emergency ceiling) enforces the schedule value, `K_eff = 7` since block 20,715,632;
|
|
- the **published from-genesis follower** of the `aere-node` package starts into the genesis validator set (N = 3), where the startup guard refuses to arm a threshold the set cannot reach, so the published configuration sets `aere.pq.anchor.minSealsCeiling=1` and the node enforces `K_eff = 1` at every height (`SPEC.md` 6.4, "The genesis-set threshold problem, and the published answer"; `RUN-A-NODE.md`, "Why the seal-threshold ceiling, for a from-genesis node"). Everything else in R1 and R2 is unchanged for it: the digest under the hash, the acceptable index order, Falcon verification of every carried seal against the height-correct registry epoch, eligibility in the parent's set.
|
|
|
|
In both cases the node verifies seals against registry epochs it **holds** (the published epoch files) and that the chain **pins by hash** (`config.pqRegistryHash` in genesis; the anchor contract's storage slot 0, read from the parent's state, which activates a pending epoch only if the hash matches and fails closed otherwise, AIP-15; the v3 epoch hashes of AIP-22 stage 3). A node whose epochs come from somewhere else is outside the claim (section 5).
|
|
|
|
The victim either (a) holds the honest chain up to some head and will switch to an alternative history only if it is at least as long (QBFT chains carry no work; height decides), or (b) knows, out of band, the height and hash of one recent honest anchor and refuses a chain that does not reach that anchor with that hash (AIP-22, section 4, rule 5: weak subjectivity, named; in the published patch set the option is `aere.pq.checkpoint=<height>:<hash>`, `PqCheckpointRule`, patch 0008, inert unless set). A node that syncs from genesis with no reference point at all is a third case and is addressed in section 5, not covered by the bound.
|
|
|
|
## 2. The facts the bound rests on, and what each of them carries
|
|
|
|
All three are rules a node applies to every header at an anchor height; none is a property of the live network's behaviour. Each is cited to the published code (the patch set of the `aere-node` package, at its commit of 2026-10-05) and to the specification.
|
|
|
|
**Fact 1 (R1): the certificate's digest is under the anchor's hash.** An anchor block carries, in `vanityData`, the 32 bytes that sit inside the hashed pre-image of the header, the digest of the certificate it carries for its parent: `D = keccak256(RLP["AERE-PQ-ANCHOR-2", chainId, parentNumber, parentHash, rlp(certificate)])` for the v2 (scheme-tagged) form in force since block 17,047,600. The rule recomputes `D` from the certificate the header carries and refuses the header if it differs. Consequences: a certificate cannot be stripped, swapped or reordered under a given anchor hash; and, through the parent-hash chain, every later block hash commits to every earlier certificate.
|
|
- Code: `aere-node/patches/0003-aere-pq-anchor.patch`, `PqAnchorDigestRule` (class at patch line 39523; the height and grid gate at 39550; the acceptable index order at 39598, which is "strictly increasing", relaxed to "distinct" only inside the dated disarmed window D4, `PqAnchor.hasAcceptableIndices` at 6980 to 6986; recompute and compare at 39611 to 39615 for v1, at 39657 for the v2 digest). The digest functions: `PqAnchor.anchorDigest` at patch lines 6658 to 6682, `PqAnchorV2.anchorDigestV2` at 9660 to 9680. (Patch 0008 touches this rule only to add AIP-22 forms; the digest check is unchanged.)
|
|
- Specification: `aere-node/SPEC.md` 3.3 ("The two pre-images") and 3.4 ("R1"); AIP-15 ("certificate under the block hash").
|
|
|
|
**Fact 2 (R2): at an anchor height, at least `K_eff` seals from distinct members of the parent's validator set, verified against the registry epoch bound at the parent's height, over a message that names the parent's hash.** The rule reads `required = K_eff = min(schedule K, emergency ceiling)` (7 from the schedule since block 20,715,632; the ceiling, if set, only lowers it, and a configuration whose highest effective threshold over the whole schedule would be below 1 is refused at startup), refuses a certificate with fewer Falcon-512 seals, resolves each seal's index to the validator bound to it in the registry epoch at the parent's height, refuses an index the epoch does not bind, refuses a validator that is not in the parent block's validator set, refuses two indices that resolve to one validator (one validator counts once), and verifies each signature over
|
|
`M(parent) = keccak256(RLP["AERE-PQ-COMMIT-1", chainId, number(parent), hash(parent)])`.
|
|
`M` names the parent's on-chain hash and nothing else: no round, no local state.
|
|
- Code: `aere-node/patches/0003-aere-pq-anchor.patch`, `PqAnchorSealsRule` (class at patch line 39833; gate at 39873; `required = config.minSealsAt(number)` at 39943, the threshold check to 39956; the parent's validator set at 39991; `M` at 40003; registry resolution at the parent's height at 40061; set membership at 40071; distinctness at 40081; verification at 40091; the same for the v2 form at 40127, 40136, 40147, 40158). `minSealsAt` (schedule after the ceiling) at 8260, the ceiling property at 7147, the floor refusal `REFUSAL_MIN_SEALS_FLOOR` at 7264, the startup guard `PqAnchorThresholdGuard` at 9329 (the "threshold above what the set can reach" refusal at 9432). `M` itself: `PqAnchor.commitMessage`, patch lines 6695 to 6715, domain `AERE-PQ-COMMIT-1` at 6544. Patch 0008 (`0008-aere-pq-aip22.patch`) changes this rule in four places, none of them the count, the distinctness or the message (its hunk on the rule is patch lines 15181 to 15493): the dated lapse windows are keyed by chain id; the SLH-DSA scheme is counted as a second requirement on its grid; the keys come from the v3 epoch in force at the parent's height (15405 to 15420); and from AIP-22 stage 3 (block 20,836,228 on chain 2800) the parent's validator set the rule reads is the set translated to post-quantum ids by the vote tally (`VoteTally.translateToIds`, patch line 7389, calling `AerePqIdentity.translateSetAfter`, defined at 2005), so each index resolves to an id rather than a classical address.
|
|
- What the registry-writer key buys the adversary: a rogue epoch pinned in its tail and new ids voted in (one vote per header; AIP-22, Security Considerations, "Registry writer"). What it does not buy: the victim verifies against the epochs it holds, pinned by hash, and an epoch it does not hold is refused fail-closed (AIP-15, "activates the pending registry only if the observed hash matches. Any mismatch fails closed"; `SPEC.md` 3.5; `RUN-A-NODE.md`, the `registries/` row and `aere.falcon.registry.history`; the v3 epochs are each verified by the rotation signatures of the previous keys, which are post-quantum). An adversary who could install an epoch on the victim would be an adversary with post-quantum keys (negative control N2).
|
|
- Specification: `SPEC.md` 3.3, 3.4 ("R2", steps 4 to 9), 3.5, 3.7 and 6.4; AIP-15 (the registry bound to on-chain state); AIP-22 Specification, Notation (`M(B)`), section 3 (identity), and the erratum of 2026-10-01; AIP-21, erratum of 2026-10-01.
|
|
|
|
**Fact 3 (the grid): which heights are anchors is a function of the height alone.** Every 32nd block from 13,014,000 (every 128th from 17,225,968 to 20,746,736; every 32nd again from 20,746,736, which lies on the same grid). At every other height R1 and R2 accept without reading the certificate, and a header there carries none. SLH-DSA-SHA2-128s seals are required in addition on the 128-block grid.
|
|
- Code: the one gate `anchorAppliesAt` read by both rules (patch lines 39550 and 39873; the gate at 8455); the interval schedule in `PqAnchorConfig`.
|
|
- Specification: `SPEC.md` 3.1.
|
|
|
|
**What carries the bound, and what each fact adds.** Stated plainly, because a reader who runs the model with the rules weakened one at a time will see it: the depth bound of sections 3 and 4 rests on R2 with **any** `K_eff >= 1` (one seal the adversary cannot make, over a message that names the parent's hash), the grid, the hash chain, and the fact that honest validators sign `M(b)` only for a block `b` they committed in a QBFT round (plus, for the one-block limit of the exception, QBFT safety while the history was produced). R1 is not what bounds the depth: it is what makes an anchor's **hash** commit to its certificate (model lemma L0b), so that a reference hash pins the certificates below it (section 5) and the public verifier can check a carried certificate against a hash (section 6); without R1 a certificate could be swapped under an unchanged hash, which changes nothing about which parents can be certified. `K_eff = 7` with distinctness is what bounds a **partial** post-quantum compromise: with fewer than seven validators' post-quantum keys the adversary still cannot certify an alternative parent (model P4a), with seven it can (N4a).
|
|
|
|
What the adversary gets from these facts: to make a node accept an anchor block at height `a` over a parent `p` of its choosing, it must present at least `K_eff` Falcon-512 seals, by `K_eff` distinct validators of `p`'s set, over `M(p)`, which names `hash(p)`. With `j = 0` it has no Falcon key. So the only seals it can present are seals honest validators made, and honest validators sign `M(b)` only for a block `b` they committed in a QBFT round. The question "which parents can the adversary certify" is therefore the question "which blocks did honest validators ever seal", and that is the whole argument.
|
|
|
|
## 3. The derivation of 32
|
|
|
|
Let the honest chain be the sequence of blocks the validators decided, with anchors on the grid. Let an alternative history fork at height `F`: its block at `F` differs from the honest block at `F`, and, because the hash of a block covers its parent's hash (Fact 1 extends this to certificates), its block at every height above `F` differs from the honest one as well.
|
|
|
|
Consider the anchor heights `a` with `F + 1 <= a` inside the alternative history. The alternative parent at `a - 1` is a block that differs from the honest block at `a - 1`, so its hash differs, so `M(alternative parent)` differs from `M(honest block at a - 1)`, so no honest validator has ever signed it. (The baseline idealizes "committed" as "decided": honest validators seal only the blocks they decided. Section 4 drops that idealization; it is exactly the exception.) The adversary has no seals over it and cannot make any. By Fact 2, for any `K_eff >= 1`, the node refuses the alternative anchor at `a`. So the alternative history cannot contain any anchor height `a >= F + 1`: it ends at or before `next_anchor(F + 1) - 1`.
|
|
|
|
Now the anchor at `F` itself, if `F` is an anchor height. Its parent at `F - 1` is the honest block: the honest certificate over it exists, is public, and is reusable. The adversary builds its own block at `F` (any content), copies the honest certificate into it, writes the certificate's digest into `vanityData`, and seals the header with the ECDSA commit quorum it holds. R1 accepts (the digest matches the carried certificate), R2 accepts (`K_eff` or more distinct valid seals over `M(honest parent)`), and the header's classical checks accept. **The anchor block itself is inside the exposed tail**, because its certificate is a statement about its parent, not about itself.
|
|
|
|
Put together: the deepest alternative history forks at an anchor height `F = a` and runs to `a + 31`, the block before the next anchor, which it cannot produce. That is `32` blocks: the anchor block and the 31 blocks after it. If `F` is not an anchor height the count is smaller. And if the alternative is to be at least as long as the honest chain, whose latest anchor is `a_last`, its fork point must satisfy `F >= a_last` (otherwise `a_last` would be an anchor inside the alternative history with an alternative parent, which is refused): the latest anchor's parent and everything below it are kept. In the dated window with interval 128 the same steps give 128 (the anchor block and 127 after it).
|
|
|
|
On the two counting conventions a reader will meet in our documents: AIP-22's Security Considerations says "the tail bounded at 31 blocks" (and "a tail of up to 127 blocks" for the interval of its writing), counting the blocks above the anchor; `SPEC.md` 3.1 and the threat-model page say "at most 32 blocks", counting the anchor block too. Both describe the same set; this document counts the anchor block, because it is replaceable. The AIP-22 sentence predates the erratum and does not carry the extra block of section 4.
|
|
|
|
Machine check: properties P1a, P1b and the witness C1a of the z3 model (section 6); for the published follower, P5a; for the dated 128-interval window, P6a and the witness C6c.
|
|
|
|
## 4. The exception of the erratum: 33, and why exactly one more block
|
|
|
|
**What the erratum says.** `M(B)` carries no round. In QBFT an honest validator may commit a block `p` in one round, while a different block `p'` at the same height is decided in a later round (the round-change justification does not need every honest lock to be inside its quorum). Seals over `M(p)` made in different rounds are indistinguishable from each other and from seals over a decided block. Measured in the reference client on 2026-10-01 (test class `AereRoundFreeCertificateTest`, with `AereRoundFreeRestartTest` and `AereRoundFreeCommitTest` beside it): at N = 10, with validators 0 to 8 honest and X = 9 a Byzantine validator holding its own post-quantum key, `p` is committed by validators 0, 1, 2 (and X) in round 0 and by 4, 5, 6 (and X) in round 2, `p'` is decided in round 3, and an anchor over `p` carrying the **six honest seals of rounds 0 and 2 plus X's own** passes R1 and R2. Read exactly: the fixture's adversary holds one post-quantum key (`j = 1`), six honest seals plus its own make `K = 7`, and the fixture's anchor is in the regime of AIP-22 stage 5 (no ECDSA quorum in the header), which chain 2800 has not activated. The errata of AIP-21 and AIP-22 describe the same run as "seven valid Falcon-512 seals over M(p) from rounds 0 and 2"; the seven are six honest and X's. That `K` honest validators can seal an undecided sibling with more round changes is stated in the AIP-22 erratum as derived, not run. So honest validators may have sealed, at some height, an **undecided sibling** of the decided block, with some number `s` of distinct seals; this document, like the model, takes `s` as free in `[0, N - j]`.
|
|
|
|
**How it moves the bound.** Take the latest honest anchor at `a`, its parent `p' = honest block at a - 1`, and suppose honest validators sealed an undecided sibling `p` at height `a - 1`. The adversary now forks one block deeper: at `F = a - 1` it uses `p` itself (it needs no Falcon key for that; `p` is a real block honest validators sealed), then builds an anchor at `a` over `p` with the honest seals over `M(p)` (it needs `K_eff - j` of them: 7 on a validator with `j = 0`, 1 on the published follower), then 31 classical blocks. That is `33` blocks replaced: `p`'s height, the anchor, and 31 after it.
|
|
|
|
**Why not more.** The sibling `p` was proposed in a QBFT round at height `a - 1`, and, QBFT safety having held while the history was produced (section 1), every QBFT proposal at height `n` extends the one block decided at `n - 1`: `p` and `p'` share the parent, the honest block at `a - 2` (the "common grandparent"). So `p` can enter an alternative history only at height `F = a - 1`, where the alternative block's parent is still the honest block. At any height `h > F` the alternative block's parent is itself alternative, so it is neither the honest block nor a sibling honest validators sealed, and no seal over it exists. The next anchor at `a + 32` is therefore refused as in section 3, and the fork point of an alternative at least as long as the honest chain satisfies `F >= a_last - 1`: **the grandparent of the latest anchor and everything below it are kept.** The extra block is bought by the `K_eff - j` sibling seals and by nothing else: with fewer honest seals over the sibling, the bound is 32 again (properties P2c and P4b); and `K_eff` is the value in force at that height (the schedule said 3 from block 13,034,000 and 6 from 14,961,456 before the 7 of today, and the ceiling, where set, lowers it).
|
|
|
|
**Why today's chain needs the whole adversary for this, and a single validator cannot do it.** Below AIP-22 stage 5 the anchor's header is valid only with the ECDSA commit seals of a quorum of one round, Q = 7 distinct keys. The adversary of section 1 holds all ten and can write that header; one validator holds one key and cannot (model statement D1, an arithmetic restatement of Q, not a proof). From stage 5 the anchor's header needs its proposer's Falcon-512 seal, which the adversary of section 1 lacks and a single validator has; that is why the exception matters more for the stages ahead than for the chain as it is, and why AIP-22's finality definition was withdrawn as written.
|
|
|
|
**What closes the exception.** Two things, in two places. First, in the reference client's source there is a decided-head rule that takes an anchor's certificate as evidence that the anchor's **grandparent** was decided, never its parent (the block both siblings share); it is in code, it is not deployed on any network, and it is not part of the published patch set. Second, the remedy the erratum names: a post-quantum commit seal that binds the round, with a certificate of Q seals of one round; a coordinated fork, under conformance work between the two clients, not scheduled on chain 2800. Until the second is active, the bound of this document is 33, and the certificate is evidence that the validators sealed the parent, not that the parent was decided.
|
|
|
|
Machine check: properties P2a, P2b, P2c, the witness C1b and the negative control N3 of the z3 model; the partial-compromise statements P4a, P4b and N4a; the follower's P5b, C5c and N5d.
|
|
|
|
## 5. What the bound does not cover
|
|
|
|
- **A node with no reference point.** A node that syncs from genesis knowing nothing can be served a history that stops before the first enforced anchor, or a 32-block (33-block) tail it has no honest head to compare against; it cannot tell that the honest chain is longer. This is weak subjectivity, named in AIP-22 section 4 rule 5, and the published answer is the one every finality-gadget chain gives: a syncing node is given a recent anchor (height and hash) out of band and refuses a chain that does not reach it with valid certificates. In the published patch set that is `aere.pq.checkpoint=<height>:<hash>` (`PqCheckpointRule`, patch 0008, lines 15494 to 15550, wired at 14634 to 14637; inert unless set). With that reference, every history that diverges at or below the reference anchor is refused, because every hash above the fork differs (properties P3a and P3b), and the rewritable part lies above it. Without it, the negative control P3c shows the divergence is accepted.
|
|
- **History below block 13,014,000.** It carries no certificates of its own. It is covered only through the hash chain from a reference anchor: a rewrite at any height changes every hash above it, so a node with one honest anchor hash detects it (L0a, P3a). The sentence "protected by ECDSA alone, permanently", which `SPEC.md` 3.1 still carries, is weaker than what holds with a reference and wrong without one; the threat-model page was corrected to the reference-point form on 2026-10-05 and the specification should follow it.
|
|
- **The dated windows in which `K_eff` was 0.** The warm-up step 13014000:0, the dated unblock step 17102384:0 (32 blocks on 2026-09-04), and the disarmed window 13,267,824 to 13,268,944 (`SPEC.md` section 8, D4). At those heights the rule accepted anchors with no seal, and the bound does not hold there (negative control N1 is exactly that rule). The threshold-lowered window 13,268,976 to 13,890,544 (D5, one seal) is inside the bound: one seal the adversary cannot make is enough (P5a). All of them are inside the history a reference anchor pins.
|
|
- **The dated 128-interval window.** From 2026-09-05 to 2026-09-29 (blocks 17,225,968 to 20,746,736) the bound was 128, with the exception 129 (P6a, P6b), as the threat-model page and `SPEC.md` 3.1 say.
|
|
- **A node whose effective threshold is 1, against one leaked post-quantum key.** The published from-genesis follower (`K_eff = 1`) keeps the 32/33 bound against the headline adversary, but a single validator's post-quantum key, leaked or misused, makes its rewrite depth unbounded (N5d); a validator at `K_eff = 7` needs seven such keys (N4a). `SPEC.md` 6.4 calls the ceiling "a real weakening of one check on the follower"; this is what that weakening costs, stated in the terms of this document.
|
|
- **A node whose registry epochs come over a classical channel.** The claim assumes the node holds the published epoch files and the chain pins them by hash (section 1). A node that fetches its epochs at sync time over a channel this adversary controls, or runs with a different `registry.history`, is outside the claim; the public verifier run "as a stranger" (section 6) fetches the manifests from our site over TLS and is in the same position for that one input.
|
|
- **The transport layer and node identity.** RLPx is authenticated with secp256k1; this adversary can impersonate a peer. That is how it delivers the alternative history; the bound is about what the victim then accepts.
|
|
- **Classical accounts.** Ordinary secp256k1 accounts, and contracts with classical administrative keys, are outside the consensus layer entirely.
|
|
- **Equivocation and the validator set.** The anchor binds validator behaviour; it is no defence against validators who hold the post-quantum keys (negative control N2: every key; N4a: `K_eff` of them), and signing two conflicting anchors is not slashable. A set that was Byzantine beyond f = 3 while the honest history was produced is outside the claim (section 1).
|
|
- **The second scheme.** SLH-DSA-SHA2-128s seals are required in addition every 128th block. They make a certificate harder to reuse, never easier; the model omits them, which makes its acceptance predicate a necessary condition of the real one, so the bound holds a fortiori.
|
|
- **Whether the exception can be brought about.** It needs honest validators to have sealed the sibling in rounds that did not decide it, at the height of the anchor's parent; the threat-model page states what the adversary must control for that (the order of messages across three round changes at that height). It has been measured in a test fixture with the production rules, with six honest seals and a Byzantine validator's own (section 4), not on any network. The model takes "a sealed sibling with `s` honest seals exists" as a free assumption for every `s` and bounds the rewrite even then.
|
|
|
|
## 6. How to verify
|
|
|
|
**The machine-checked model.** `formal-consensus/anchor_rewrite_bound_roundfree_smt.py` (z3; python 3 with the `z3-solver` package):
|
|
|
|
```
|
|
cd aere-research/formal-consensus
|
|
python anchor_rewrite_bound_roundfree_smt.py # this model alone, about 1 s
|
|
python anchor_rewrite_bound_roundfree_smt.py --plant-bounds # control of the control: must exit 1
|
|
python run_consensus_verification.py # the whole suite, 37 models
|
|
```
|
|
|
|
Convention of the directory: each property is discharged by proving its negation UNSAT, and each proof is paired with a negative control that plants the violation and must come back SAT, so a green result is non-vacuous. Measured 2026-10-07 (Windows, python 3.14.4, z3 4.16.0; the full output is in `anchor_rewrite_bound_roundfree_smt.RESULT.txt`): 28 checks, exit 0, of which **13 proofs** (P1a, P1b, P2a, P2b, P2c, P3a, P3b, P4a, P4b, P5a, P5b, P6a, P6b), **2 lemmas** (L0a, the hash-chain step; L0b, R1 under the hash), **4 configuration witnesses** (C1a the 32-block tail, C1b the 33-block tail forked at the latest anchor's parent, C5c the follower's 33rd block from one sibling seal, C6c the 128-block tail of the dated window), **1 definitional statement** (D1, the ECDSA quorum count, an arithmetic restatement and not counted as a proof) and **8 negative controls** that all fired (N1 no certificate needed, N2 every post-quantum key, N3 no common grandparent, N4a `K_eff` post-quantum keys, N5d one key against the follower, N3a a hash that does not cover the parent, P0c a certificate outside the hash, P3c no reference point). With `--plant-bounds` (every claimed bound one too low) exactly the ten bound proofs come out FAILED and the script exits 1: the bounds are tight. The whole suite the same day: 37 of 37 models, 301 PROVED and 159 CEX-FOUND verdict lines, 0 FAILED, 37 s.
|
|
|
|
The model's own header states its boundary: it is the combinatorics of the acceptance rules over heights, certificates and parent/sibling relations, with keccak and Falcon-512 modelled as injective and unforgeable; it does not re-prove the seal-counting rule (that is `anchor_blocking_quorum_smt.py`), delivery, or the code of either client; it assumes QBFT safety while the honest history was produced and a node that holds its registry epochs; "every anchor in the alternative history is accepted" is checked on the first 40 anchor heights above the fork, a necessary condition that makes the proofs sound and covers every witness of the negative controls (1,000 blocks deep); the headline histories are confined to the regime in force today (fork point above block 20,746,736: interval 32, `K = 7`), with the dated 128-interval window proved separately; and the reference-point property P3a is the hash chain unrolled for 1,280 heights above the fork (z3 does not induct; the induction step is the lemma L0a, and the unrolling shows the composition for 1,280 heights).
|
|
|
|
**The public verifier on the live chain.** `aere-node/tools/verify-anchor.mjs` checks, on recent anchors of chain 2800 from any RPC endpoint and the published key manifests, exactly the facts of section 2: leg 1, the digest in `vanityData` equals the recomputed digest of the carried certificate (Fact 1); leg 3, every seal verifies, in plain JavaScript written from the specification, over `keccak256(RLP["AERE-PQ-COMMIT-1", chainId, parentNumber, parentHash])` against the manifest key of its index (Fact 2, the message); leg 4, the validator bound to each sealing key was in the parent block's validator set, read from the parent's own header, and counts once (Fact 2, the set and distinctness); the threshold schedule it judges by is the chain's (7 since 20,715,632) with the dated windows named; legs 2 and 5 tie the manifests to the validators' own signatures and to the on-chain pin. What it cannot verify is what no header can show: whether the sealed parent was the block a round decided (section 4). Run it as a stranger, from an empty directory, exactly as the documentation page "Verify an anchor" gives it (the tool imports three sibling files):
|
|
|
|
```
|
|
curl -O https://aere.network/tools/verify-anchor.mjs \
|
|
-O https://aere.network/tools/mpt.mjs \
|
|
-O https://aere.network/tools/falcon512.mjs \
|
|
-O https://aere.network/tools/registry-v3.mjs \
|
|
-O https://aere.network/tools/package.json && npm install
|
|
node verify-anchor.mjs
|
|
```
|
|
|
|
**The mechanism of section 3 on both clients, as static vectors.** `aere-research/aips/aip22-conformance/` (kind `classical-fork-31`, from the rehearsal network 28099: a one-validator network, so `K = 1`, where an attacker held that validator's ECDSA key and no post-quantum key; the vectors show the mechanism of R1 and R2, not the threshold of seven): `28099-fork-honest-anchor-2720` (valid, the honest anchor); `28099-fork-anchor-2720-no-certificate` (the fork's own block at the anchor height, refused by both clients because an anchor height carries no certificate); `28099-fork-anchor-2720-honest-certificate` (the same block carrying the honest certificate with the digest re-bound, refused by both clients because the seals sign `M` of the honest parent and do not verify over the fork's parent). The corresponding behaviour, a reader of each client following a classical-only fork of 24 to 26 blocks that stopped at the block before the next anchor and moving to the anchored chain when linked to the honest validator, with the negative control (anchor rule disarmed: the fork crossed the anchor height), is in the AIP-22 stage record of 2026-09-27, measured on the rehearsal network, not on chain 2800.
|
|
|
|
**The exception, reproduced.** `AereRoundFreeCertificateTest` (the anchor over the undecided sibling passes R1 and R2 with six honest Falcon-512 seals from rounds 0 and 2 plus the Byzantine validator's own, and a node that heard round 3 decides the sibling's rival without conflict), `AereRoundFreeRestartTest` (what a restarting node takes as decided from such an anchor) and `AereRoundFreeCommitTest` (a round's quorum assembled from seals of other rounds, under the adversary of section 1 who forges the ECDSA envelope). They reproduce the **exception**, not the bound: they show the certificate rules accept the sibling anchor, which is the premise of section 4. Their source is not yet public: they exercise the decided-head classes of the repair named in section 4, which are not in the published patch set; they will be published with it. Until then a reader has the erratum's description, the public verifier, the model above and the conformance vectors, and should treat the fixture result as our measurement, not as an independently reproducible one.
|
|
|
|
## 7. Divergences found while writing this, stated rather than smoothed
|
|
|
|
- `SPEC.md` 3.1 ends its security paragraph with "History below H remains protected by ECDSA alone, permanently". The threat-model page replaced that sentence on 2026-10-05 with the reference-point statement of section 5; the specification has not yet followed.
|
|
- AIP-22, Security Considerations, "the tail bounded at 31 blocks" (and "up to 127 blocks" for the interval at its writing) counts the blocks above the anchor and predates the erratum; with the anchor block counted and the exception it reads 32 and 33, as in `SPEC.md` 3.1 and the threat-model page.
|
|
- The errata of AIP-21 and AIP-22 describe the fixture run as "seven valid Falcon-512 seals over M(p) from rounds 0 and 2". The seven are six honest seals and the seal of the fixture's Byzantine validator, which holds a post-quantum key; the fixture's anchor is in the stage-5 regime. The claim that `K` honest validators can seal a sibling is, in the AIP-22 erratum's own words, derived and not run. This document says so (section 4); the errata should.
|
|
- The published patch set carries the reference-point option (`aere.pq.checkpoint`, patch 0008), and neither `RUN-A-NODE.md` nor `SPEC.md` documents it; a reader told "give the node a recent anchor" has, from those two pages, no way to do it. The documentation should name it.
|
|
- The first form of this document (the same morning) said the threat-model page's sentence "rewriting anything below an anchor changes that anchor's parent hash" should read "below the anchor's parent". That was wrong, and a reviewer caught it: "below an anchor" is every height under the anchor, the parent included, and rewriting the parent does change the anchor's parent hash; the anchor block itself is not below an anchor, and the page's "at most 32 blocks" counts it. The page's sentence is exact.
|
|
- The first form of this document also named `K = 7` and R1 among "the three facts the bound rests on" and counted two arithmetic statements among the machine-checked proofs; both reviews measured, by planting, that neither `K = 7` nor R1 is what bounds the depth and that those two statements could not fail. Section 2 now says what each fact carries, and section 6 counts proofs, lemmas, witnesses and definitions separately.
|
|
- The two test classes named in AIP-22's erratum as the measurement of the exception are, with the third, not published; the erratum does not say so. This document does.
|
|
|
|
## 8. Reading list
|
|
|
|
- `aere-node/SPEC.md`, sections 3.1, 3.3, 3.4, 3.5, 3.7, 6.4, 7 and 8 (D4, D5).
|
|
- `aere-node/RUN-A-NODE.md`, "The five things you need" (`registries/`), "The configuration a follower passes" and "Why the seal-threshold ceiling, for a from-genesis node".
|
|
- `aere-node/patches/0003-aere-pq-anchor.patch` and `0008-aere-pq-aip22.patch`, the lines cited in sections 2 and 5.
|
|
- `aere-research/aips/AIP-15.md` (the registry bound to on-chain state; the certificate under the block hash).
|
|
- `aere-research/aips/AIP-22.md`: Specification (notation; section 3, identity), section 4 rule 5, Security Considerations ("Threat model", "Registry writer"), stage records of 2026-09-23 and 2026-09-27, Errata (2026-10-01); `AIP-21.md`, Errata (2026-10-01).
|
|
- The documentation's threat-model page ("What the anchor does about it", with the paragraph added 2026-10-01 and the correction of 2026-10-05) and the page "Verify an anchor".
|
|
- `aere-research/formal-consensus/anchor_rewrite_bound_roundfree_smt.py` and its `.RESULT.txt`, `anchor_blocking_quorum_smt.py`, `anchor_viewchange_smt.py` (its note of 2026-10-01), `RULEAZA-TOT.md`.
|
|
- `aere-node/tools/verify-anchor.mjs`; `aere-research/aips/aip22-conformance/` (README, "The classical-only fork (chain 28099)").
|
|
- Azouvi, Danezis, Nikolaenko, "Winkle: Foiling Long-Range Attacks in Proof-of-Stake Systems", IACR 2019/1440, AFT 2020, for the threat (the quantum framing and the anchor are ours).
|