You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Context
A-1351 (#24515) fixed the honest-proposer and relay path for
non-canonical yParity attestation signatures, but a malicious selected
proposer can still hand-craft `propose()` calldata carrying a
non-proposer signature slot with `v ∈ {0,1}` (or an all-zero `(r,s,v)`
in a bitmap-marked signature slot). L1 accepts this at propose time (it
only recovers the proposer's own slot) and stores the attestations hash
over the raw bytes. Afterwards, either:
- the slot recovers to the correct member, so honest nodes deem the
checkpoint valid and nobody invalidates it — but epoch proving reverts
at `ECDSA.recover` (which rejects `v ∉ {27,28}`), a silent stall; or
- the slot is detected as invalid, but the invalidation repack via
`packAttestations` is not byte-faithful, so the recomputed hash diverges
from the stored one and `InvalidateLib` reverts — wedging the chain
until prune.
This is the malicious-proposer sibling of A-1351; it survives that fix.
Tracks aztec-claude#650.
## Approach
TS-only — no L1 changes, since this targets a fresh deployment. Two
parts:
- **Detection** — `getAttestationInfoFromPayload` no longer passes
`allowYParityAsV: true`, so TS signature validity matches L1's
`ECDSA.recover`. A non-canonical slot is now classified as an invalid
attestation, so honest nodes detect the bad checkpoint instead of
silently accepting it.
- **Byte-faithful invalidation** — the raw packed
`CommitteeAttestations` tuple from the checkpoint calldata is threaded
verbatim (as `verbatimAttestations`) through the negative
`ValidateCheckpointResult` into `buildInvalidateCheckpointRequest`,
which submits those exact bytes rather than repacking. With the original
bytes, `invalidateBadAttestation` reproduces the stored hash and L1's
`tryRecover` returns `address(0) ≠ member`, so invalidation succeeds.
`verbatimAttestations` is a required field on the negative result and is
serialized unconditionally — a repack fallback is intentionally absent,
since silently repacking would re-introduce the wedge.
The gossip sender-recovery path (`recoverCoordinationSigner`)
deliberately stays lenient (`allowYParityAsV`) so an honest node can
still attribute a yParity-encoded message and canonicalize it on
ingress; only the on-chain checkpoint validation is made strict. A
comment documents the split so it is not accidentally unified.
## Tests
An e2e regression under the invalidation suite
(`invalidate_block.parallel.test.ts`, "proposer invalidates checkpoint
with a yParity attestation slot") demonstrates the attack red→green: a
malicious proposer lands a checkpoint whose non-proposer attestation
slots carry raw yParity bytes, and the honest next proposer invalidates
it — which only succeeds when the invalidation submits the verbatim
calldata bytes (a repack diverges from the on-chain `attestationsHash`
and reverts). Plus unit coverage for the strict attestation-info
classification, the byte-faithful passthrough, and the result
round-trip.
An attester-side variant (a committee member emitting yParity
attestations) is not separately tested: the honest proposer's
`orderAttestations` normalization (A-1351) canonicalizes the byte before
the L1 bundle, so it never reaches L1 and produces the same on-chain
outcome as — and is subsumed by — the malicious-proposer case above.
Fixes A-1401
0 commit comments