DC-CRYPTO-10
DC derived enforcedThe RED signing shell must evolve the operator KES signing key to the requested KES period before signing, using the existing deterministic Sum6KES update primitive. It must fail closed if the requested period is before the key start, beyond the key lifetime, or cannot be reached by sequential evolution. The slot->KES-period gate (CoordinatorState::kes_period_for_slot) supplying that requested period MUST return the RELATIVE evolution index anchored at the op-cert's start period (absolute_period - kes_start_period), valid only inside the op-cert's covered window [kes_start_period, kes_start_period + kes_max_period] -- NEVER the raw absolute period (which would exceed the key lifetime and refuse every real-chain slot).
- Source
docs/clusters/PHASE4-N-AC/cluster.md; docs/evidence/c1-genesis-rehearsal-reproduction-README.md (item-4 C1 re-run finding)
- Cluster
- CL-PRODUCER-KEYS
- Introduced in
- PHASE4-N-AC
- Authority surface
- RED producer-shell KES key evolution before signing + the slot->KES-period gate that supplies the requested relative evolution index
Enforcement trace
Code
Tests 5
- crates/ade_runtime/src/producer/producer_shell.rs::tests::shell_kes_sign_header_advancing_evolves_then_signs
- crates/ade_runtime/src/producer/producer_shell.rs::tests::shell_kes_sign_header_advancing_at_current_period_signs
- crates/ade_runtime/src/producer/producer_shell.rs::tests::shell_kes_sign_header_advancing_backwards_fails_closed
- crates/ade_runtime/src/producer/producer_shell.rs::tests::shell_kes_sign_header_advancing_beyond_lifetime_fails_closed
- crates/ade_runtime/src/producer/coordinator.rs::tests::kes_period_for_slot_anchors_relative_to_opcert_start_period
Cross-references
Strengthened in
Evidence notes
Declared at cluster scoping (2026-06-06, user-confirmed). Surfaced by the item-4 C1 re-run: the C1 genesis rehearsal stopped reproducing once the private net aged past one KES period (slot ~249659 / slotsPerKESPeriod 129600 -> KES period 1), because the forge's only real KES sign (produce_mode.rs:891 kes_sign_header) requires kes.current_period() == requested and nothing evolved the minted-at-period-0 key forward -> KesPeriodNotCurrent{requested:1,current:0} on every leader slot (succeeded=0; the follower KeepAlive-timed-out waiting for a block). NOT a regression from N-U/AA/AB (the wire/serve path handshook + found the chain-sync intersection). Fix (S1): add ProducerShell::kes_sign_header_advancing (kes_advance_to(period) then kes_sign_header) and wire the :891 site to it; kes_advance_to -> kes_update is already idempotent at the current period and fail-closed (EvolutionBackwards on backwards, EvolutionExhausted beyond SUM6_MAX_PERIOD=63 / unreachable). Signing stays RED; the Sum6KES algorithm + KES verifier + forge eligibility + wire rules are unchanged. Enforced at S1 with ci/ci_check_kes_evolution_before_sign.sh + 4 shell tests + the live C1 re-run in KES period 1 (acceptance #3, non-promotable rehearsal). CLOSED PHASE4-N-AC (2026-06-06); both gating reviews PASS (no HIGH+). Live proof: with the fix Ade forged 3 period-1 blocks + self-accepted them, and the real cardano-node DOWNLOADED the period-1 header with NO KES/parse rejection (pre-fix: failed=5/succeeded=0, KesPeriodNotCurrent). Genesis-window finding (recorded honestly): slotsPerKESPeriod = 129600 == genesis density window 3k/f = 129600, so KES period 1 begins exactly when the genesis window closes — a from-genesis rehearsal can never show forge-at-period-1 AND follower-adopt simultaneously; the period-1 follower rejection is CandidateTooSparse (genesis-density-window limit, KES-INDEPENDENT), not a KES rejection. Cross-period end-to-end forge->adopt is proven on the C2 tip path (dense current tip, no genesis window), NOT a net reset (which returns to period 0). Two pre-existing fail-closed INFO items handed to C2: (1) kes_advance_to zeroes the key on a failed advance (std::mem::replace before kes_update) -- unreachable via the forge (kes_period_in_window + kes_period_for_slot bound it), fail-safe (a zero key signs nothing the peer accepts); (2) the opcert window upper bound opcert_start+63 can diverge from the absolute Sum6KES ceiling 63 when opcert_start>0 (real preprod opcerts) -- still fail-closed (EvolutionExhausted), but the C2 config must derive kes_max_period from the absolute ceiling, not opcert_start+63. STRENGTHENED 2026-06-18 (KES-PERIOD-GATE-OPCERT-ANCHOR -- resolves the handed-off INFO item #2): the gate CoordinatorState::kes_period_for_slot computed the ABSOLUTE KES period (slot/slotsPerKESPeriod) and rejected it when > kes_max_period (= genesis maxKESEvolutions = 62), IGNORING the opcert start period -- so on any real chain (preview live slot ~115092757, absolute period 888) it returned None (888 > 62) for EVERY slot. The forge-tick (node_lifecycle ForgeTick) then SKIPPED the forge body BEFORE the DC-NODE-15 catch-up gate and recorded the generic NoTipAvailable, masking the KES-gate failure as no_tip_available / initial_catchup_required and SILENTLY BLOCKING ALL C2-PREVIEW-BA02 block production (epoch 1331 AND 1332). Fix: kes_period_for_slot now returns the RELATIVE evolution (absolute_period - opcert.kes_start_period), valid only inside [kes_start_period, kes_start_period + kes_max_period] -- the SAME relative index ProducerShell::init / Sum6KES sign_kes / the header period field use (the raw key evolution index is NEVER the absolute period, per OP-OPS-04). A from-genesis opcert (kes_start_period=0) is behaviour-identical (all prior tests use start=0, stay green). Proven: kes_period_for_slot_anchors_relative_to_opcert_start_period (opcert start 885 -> the live epoch-1332 slot yields relative evolution 3, was None) + 127 producer tests + forge_succeeds + node_sync green; LIVE on C2-PREVIEW store7 vs cardano-node-preview the forge-tick now passes the KES gate (kes=Some(3) on every tick, was None) and reaches the catch-up gate -- the one-at-a-time wire-pump follow-lag (admission ~25 blocks behind, 0 diverged) is now the next, SEPARATE blocker (the catch-up gate stays NotCaughtUp).