Skip to main content

rhdl_dsig_scalable_core/
slice_factory.rs

1//! A slice's key factory: one complete one-time key every 68 cycles.
2//!
3//! This is the hardware form of [`slice_model::model_key`](crate::slice_model::model_key):
4//! it issues exactly the requests listed there, in the order fixed by the
5//! [`slice_schedule`](crate::slice_schedule), and writes every chain node to
6//! the slice's cache.
7//!
8//! # The story so far
9//!
10//! DSig signs a message with a *one-time key*: a W-OTS+ key with Winternitz
11//! parameter `w = 4`. Such a key is 68 hash chains (64 message digits plus 4
12//! checksum digits). Each chain starts at an 18-byte secret and is hashed
13//! three times, so it has four positions, 0 to 3. Position 3 is the public
14//! chain end. When we sign, each message digit tells us which position of its
15//! chain to reveal. Making keys ahead of time, and storing all four positions,
16//! turns signing into "look up 68 nodes and copy them out".
17//!
18//! A one-time key must never sign twice. So every key gets a fresh number
19//! (`batch`, `index`), and its secrets are derived from that number. 128 keys
20//! form a *batch*; the hash of each key's chain ends is a leaf of a Merkle
21//! tree, and one Ed25519 signature certifies the batch root. A finished DSig
22//! signature is 1,642 bytes.
23//!
24//! This module is the slice's key maker. Its job, every 68 clock cycles at the
25//! 205 MHz design clock, is:
26//!
27//! 1. derive the key's secret seed from `(epoch, batch, index)` (BLAKE3);
28//! 2. derive two nonces from the seed: the public chain salt `pk_nonce` and the
29//!    private message nonce;
30//! 3. expand the seed into 68 × 18 secret bytes (a BLAKE3 *XOF*, an
31//!    "extendable output", read as 20 blocks of 64 bytes);
32//! 4. push the 68 chain secrets through the chain engine, one per cycle, and
33//!    write all four positions of each chain into the cache;
34//! 5. hash `pk_nonce` and the 68 chain ends into the key's `pk_hash` (the
35//!    *endpoint commitment*, 20 BLAKE3 blocks plus one parent);
36//! 6. report the finished key ([`KeyDone`]) so the subtree unit can use
37//!    `pk_hash` as a Merkle leaf.
38//!
39//! # The reasoning behind the shape
40//!
41//! Step 4 sets the pace: the chain engine ([`crate::slice_chain`]) accepts one
42//! chain per cycle, and a key has 68 chains, so a new key must start every 68
43//! cycles. Steps 1, 2, 3 and 5 need 44 BLAKE3 compressions per key
44//! (1 seed + 2 nonces + 20 XOF + 20 commitment blocks + 1 parent), and two
45//! more slots per period are lent to outsiders (see below): 46 in all. A
46//! dedicated hash core per
47//! step would cost far too many LUTs, so they all share **one** pipelined
48//! BLAKE3 lane that accepts one compression per cycle. 46 of every 68 cycles
49//! are therefore busy; the rest are idle.
50//!
51//! Sharing creates two problems:
52//!
53//! * **Who uses the lane in this cycle?** Several keys are in flight at once
54//!   (a key's work spans several 68-cycle periods). Instead of an arbiter, we
55//!   use a fixed *modulo schedule* ([`crate::slice_schedule`]): every op of a
56//!   key has a fixed offset from that key's start, and no two offsets are
57//!   equal modulo 68. So the cycle number modulo 68 (the *residue*) alone says
58//!   which op runs. The table lookup is all the control logic there is.
59//! * **Where does each in-flight key keep its half-done state?** Each key in
60//!   flight owns a *context*: one entry in a small ring of RAMs indexed by the
61//!   key's number modulo [`CONTEXTS`]. The seed, nonces and BLAKE3 chaining
62//!   values of a key live in its context until the key is done.
63//!
64//! An op's *lag* is how many 68-cycle periods after its key's start it runs
65//! (negative for ops that run before chain 0). From the current period and an
66//! op's lag, the factory knows which key, and thus which context, the op
67//! belongs to: `context = (period − lag) mod CONTEXTS`.
68//!
69//! # Picture
70//!
71//! ```text
72//!               residue r = cycle mod 68 ──▶ timetable ──▶ (op, key context)
73//!                                                │
74//!   prefix_cv, epoch, batch ──┐                  ▼
75//!   context RAMs (seed, CVs) ─┼──▶ request mux ──▶ BLAKE3 lane (28 stages, II=1)
76//!   commitment words ─────────┘                          │ result + tag
77//!         ▲                                              ▼
78//!         │                       write back into the key's context RAMs
79//!   packer ◀── chain ends ◀── chain engine ◀── XOF window ◀── XOF RAM
80//!                                │
81//!                                └──▶ cache writes (all four positions)
82//! ```
83//!
84//! Read the picture from the top. A counter gives the residue; the timetable
85//! ([`SLOTS`]) turns it into an op and a lag; the lag picks the key context.
86//! The request mux builds that op's 64-byte BLAKE3 block from the run
87//! configuration and the context RAMs, and the lane hashes it. 28 cycles
88//! later the result comes out with its *tag* (a 32-bit label that says which
89//! op and which context it belongs to) and is written back into that
90//! context. XOF results go to a separate XOF RAM. From there, a two-block
91//! *window* feeds 18-byte secrets into the chain engine, one per cycle. Chain
92//! nodes go to the cache, and chain ends go through the packer into the
93//! commitment RAM. Later commitment ops read them back as BLAKE3 blocks.
94//!
95//! # Where the data lives
96//!
97//! | Storage | Width × depth | Written by | Read by |
98//! |---|---|---|---|
99//! | `contexts.seed` | 8 words × [`CONTEXTS`] | `Seed` result | nonce and XOF requests |
100//! | `contexts.pkn_feed` | 16 bytes × [`CONTEXTS`] | `PkNonce` result | chain-salt latch (residue 66/67) |
101//! | `contexts.pkn_meta` | 16 bytes × [`CONTEXTS`] | `PkNonce` result | [`KeyDone`] metadata |
102//! | `contexts.nonce` | 16 bytes × [`CONTEXTS`] | `Nonce` result | [`KeyDone`] metadata |
103//! | `contexts.cv0` / `cv1` | 8 words × [`CONTEXTS`] | chunk-0 / chunk-1 results | next block of that chunk, parent |
104//! | `xof_ram` | 16 words × 64 | `Xof` results | chain window |
105//! | `commit_ram` | 64 bytes × (32 × [`CONTEXTS`]) | packer | chunk requests |
106//!
107//! `pk_nonce` is stored twice because two different readers need it at
108//! different addresses in the same cycle, and each RAM has one read port.
109//!
110//! # Cycle contract
111//!
112//! Key `k` is decided (real or bubble) [`KEY_LEAD`] periods before its chain
113//! 0 enters the chain engine at residue 0 of period `k`. Each op of key `k`
114//! issues at cycle `68k + offset`, the offsets coming from
115//! [`SCHEDULE`]. Every op is two registered steps:
116//!
117//! 1. **decode** (cycle `y − 1`): look up residue `y mod 68`, compute the key's
118//!    context index, present context-RAM read addresses, register the op;
119//! 2. **issue** (cycle `y`): assemble the request from the RAM outputs and
120//!    feed the lane.
121//!
122//! Results leave the lane 28 cycles after issue, are registered once, and are
123//! written into the key's context RAM. They are readable by an op issuing
124//! [`use_delay`](crate::slice_schedule::Schedule::use_delay) cycles after the
125//! producer, which the schedule guarantees.
126//!
127//! ```text
128//!   cycle:   y−1        y          y+1 … y+27     y+28          y+29
129//!            decode     issue      lane stages    lane output   result register
130//!            residue    request ─▶ BLAKE3 lane ─▶ (q.lane)   ─▶ (q.data.result)
131//!            lookup,    enters                                  ─▶ context-RAM write
132//!            RAM addr   lane
133//!
134//!   An op that consumes this result issues use_delay = 28 + 4 = 32 cycles
135//!   after y, once the written value can be read back.
136//! ```
137//!
138//! # How a key moves through periods
139//!
140//! One *period* is 68 cycles. Key `k`'s chain 0 enters the chain engine at
141//! residue 0 of period `k`. Its preparation ops (seed, nonces, XOF) run in
142//! earlier periods, its commitment ops in later ones. Meanwhile other keys
143//! do their own ops in the same periods, on other residues:
144//!
145//! ```text
146//!   period:      k−KEY_LEAD   …      k−1           k              k+1    …
147//!   key k:       decided      seed, nonces, XOF    chains 0..67   commitment, parent → KeyDone
148//!   key k+1:                  decided  …           seed, XOF …    chains 0..67  …
149//! ```
150//!
151//! The ring of [`CONTEXTS`] contexts must be larger than the number of
152//! periods a key spans, so a context is never reused while its key still has
153//! ops pending; compile-time assertions below check this.
154//!
155//! The Merkle and message slots carry requests from outside (the subtree unit
156//! and the signer). A request offered on [`SliceFactoryInput::merkle`] or
157//! [`SliceFactoryInput::message`] is taken at that slot's residue, signalled
158//! by `merkle_taken` or `message_taken`, and its 16 result words return on
159//! [`SliceFactoryOutput::slot_result`] with the caller's tag.
160//!
161//! # Safety rules carried by this block
162//!
163//! * Each real key gets a fresh `(batch, index)` from a monotonic counter; a
164//!   key index is never issued twice within a run, and reset ends the run.
165//! * Bubble keys issue nothing: their slots are idle.
166//! * The secret prefix CV is an input, never stored beyond the run's config.
167//!
168//! # Neighbours
169//!
170//! * [`crate::slice_schedule`] computes the timetable this block follows.
171//! * [`crate::slice_chain`] is the chain engine fed one chain per cycle.
172//! * [`crate::chain_lane`] picks the chain hash (Haraka-512 by default) and
173//!   with it the chain engine's latency, which shapes [`END_DELAY`],
174//!   [`PACK_HISTORY`] and [`CTX_BITS`].
175//! * [`crate::packer`] turns 18-byte chain ends into 64-byte commitment words.
176//! * [`crate::blake3_core`] supplies the shared BLAKE3 lane.
177//! * [`crate::slice_model`] is the software model the tests compare against.
178//! * [`crate::slice`] wires this factory to the cache, subtree unit and signer.
179
180use crate::blake3_core::{
181    CHUNK_END, CHUNK_START, CompressionInput, DERIVE_KEY_MATERIAL, IV, InlineCompressionPipeline,
182    PARENT, PIPELINE_STAGES, ROOT, SINGLE_BLOCK_ROOT,
183};
184use crate::packer::{COMMITMENT_PREFIX, ElementPacker, PackerInput};
185use crate::slice_chain::{SLICE_CHAIN_LATENCY, SliceChainEngine, SliceChainInput};
186use crate::slice_model::{NONCE_PREFIX, PK_NONCE_PREFIX};
187use crate::slice_schedule::{
188    CHUNK0_BLOCKS, KEY_PERIOD, OPS, Op, Schedule, XOF_BLOCKS, first_chain_of_xof, op_at, solve,
189};
190use crate::{CHAINS, ELEMENT_BYTES, KEY_ONE, KEY_ZERO, KEYS_PER_SLICE, KeyIndex, SUBTREE_LEVELS};
191use rhdl::prelude::*;
192use rhdl_fpga::core::{
193    dff::DFF,
194    ram::synchronous::{In as RamIn, SyncBRAM, Write as RamWrite},
195};
196
197/// Cycles from a chain's issue until its commitment word is readable.
198///
199/// The chain engine takes [`SLICE_CHAIN_LATENCY`] cycles (three chain-hash
200/// lanes in series); two more cycles cover the registers on the way from the
201/// engine output through the packer into the commitment RAM. Haraka: 3 × 20 + 2 = 62. BLAKE3: 3 × 28 + 2 = 86.
202/// SHA-512: 3 × 82 + 2 = 248. The schedule solver needs this number to know
203/// when a commitment block's chain ends are all present.
204pub const END_DELAY: i32 = SLICE_CHAIN_LATENCY as i32 + 2;
205/// Earlier keys' `pk_nonce`s kept for the commitment prefix.
206///
207/// A key's chain ends leave the chain engine [`END_DELAY`] cycles after its
208/// chains start. With Haraka (62 cycles) that is still the key's own period;
209/// with SHA-512 chains (248 cycles) three periods later, when newer keys are
210/// already being fed. The packer picks the nonce by the key's age.
211///
212/// The formula counts how many whole periods the chain engine spans, plus
213/// one. Haraka: 62 / 68 + 1 = 1. BLAKE3: 86 / 68 + 1 = 2. SHA-512:
214/// 248 / 68 + 1 = 4. Each entry is one 16-byte register, so this is cheap.
215pub const PACK_HISTORY: usize = END_DELAY as usize / KEY_PERIOD + 1;
216/// The timetable this factory implements.
217///
218/// Computed at compile time by [`solve`] from the BLAKE3 lane latency
219/// ([`PIPELINE_STAGES`] = 28) and [`END_DELAY`]. The hardware never runs the
220/// solver; it only reads the resulting tables ([`SLOTS`], [`WINDOW_LOADS`]).
221pub const SCHEDULE: Schedule = solve(PIPELINE_STAGES as i32, END_DELAY);
222/// Bits of a key-context index (and of an op lag). The SHA-512 chain lanes
223/// (82 cycles each) stretch one key over more 68-cycle periods, so that profile
224/// needs 32 contexts; Haraka and BLAKE3 need 16.
225///
226/// A *context* is the slot of per-key state (seed, nonces, chaining values,
227/// commitment words) that a key owns while it is in flight. More contexts
228/// cost deeper (but still small) RAMs, so we use the fewest that fit.
229pub const CTX_BITS: usize = if cfg!(dsig_chain = "sha512") { 5 } else { 4 };
230/// Key contexts held at once; must exceed the span of op lags.
231///
232/// Key number `k` uses context `k mod CONTEXTS`. If a key's ops spanned
233/// `CONTEXTS` periods or more, a newer key would overwrite its state.
234pub const CONTEXTS: usize = 1 << CTX_BITS;
235/// The last key number of a slice's batch share.
236const LAST_KEY: KeyIndex = bits((KEYS_PER_SLICE - 1) as u128);
237/// A key-context index (wraps modulo [`CONTEXTS`]).
238///
239/// [`CTX_BITS`] wide. Because the arithmetic wraps, "period minus lag" gives
240/// the right context even across the counter's wrap-around.
241pub type Ctx = Bits<CTX_BITS>;
242/// Commitment-RAM address: context and word.
243///
244/// [`CTX_BITS`] + 5 bits: `ctx << 5 | word`. The low 5 bits select one of 32
245/// word slots per context (20 are used: 16 for chunk 0, 4 for chunk 1).
246pub type CommitAddress = Bits<{ CTX_BITS + 5 }>;
247/// `1` as a [`Ctx`].
248///
249/// RHDL kernels cannot convert integers on the fly, so commonly used values
250/// are prepared as typed constants.
251pub const CTX_ONE: Ctx = bits(1);
252/// [`KEY_LEAD`] as a [`Ctx`].
253pub const CTX_LEAD: Ctx = bits(KEY_LEAD as u128);
254/// [`LAG_BIAS`] as a [`Ctx`].
255pub const CTX_BIAS: Ctx = bits(LAG_BIAS as u128);
256/// Every context index as a [`Ctx`] (kernels compare against these).
257///
258/// `CTX_INDEX[i] == i`. A kernel that needs "the ring entry at a run-time
259/// index" loops over all `i` and compares, which synthesizes to a plain
260/// multiplexer ([`ring_entry_kernel`]).
261pub const CTX_INDEX: [Ctx; CONTEXTS] = ctx_index();
262
263/// Builds [`CTX_INDEX`] at compile time: entry `k` holds the value `k`.
264const fn ctx_index() -> [Ctx; CONTEXTS] {
265    // Start from zeros, then write each index.
266    let mut out = [bits(0); CONTEXTS];
267    let mut k = 0;
268    while k < CONTEXTS {
269        out[k] = bits(k as u128);
270        k += 1;
271    }
272    out
273}
274/// Bits of a cache slot number.
275///
276/// 9 bits name 512 slots ([`CACHE_SLOTS`]); a slot holds one whole key.
277pub const SLOT_BITS: usize = 9;
278/// One-time keys the slice cache can hold (ring size): 32 batches of 16.
279///
280/// Why 32 batches: a key cannot be spent until its batch certificate returns.
281/// The Ed25519 core reaches one signature per ~758 cycles only with ~32
282/// requests in flight, each taking ~25,000 cycles, so roughly 25 batches per
283/// slice wait in the cache at the full rate. See [`sustained`](crate::sustained), section 4a.
284pub const CACHE_SLOTS: usize = 1 << SLOT_BITS;
285/// Rows of each cache bank: keys packed densely, `CHAINS` rows per key.
286///
287/// 512 × 68 = 34,816 rows. One row holds one chain position (18 bytes).
288pub const CACHE_ROWS: usize = CACHE_SLOTS * CHAINS;
289/// Bits of a cache-bank row address.
290///
291/// 16 bits reach 65,536 rows, enough for [`CACHE_ROWS`] = 34,816.
292pub const CACHE_ADDRESS_BITS: usize = 16;
293
294/// Dense cache row of `(slot, chain)`: `slot × 68 + chain`.
295///
296/// `68 = 64 + 4`, so this is two shifts and two adds. A power-of-two layout
297/// (`slot << 7 | chain`) would waste 60 of every 128 rows and need 1,024
298/// UltraRAMs for 512 keys per slice, more than the U280's 960.
299///
300/// Inputs: `slot` (9 bits, 0..512) and `chain` (7 bits, 0..68). Output: the
301/// 16-bit row address. Purely combinational: the result is ready in the same
302/// cycle.
303#[kernel]
304pub fn cache_address_kernel(slot: b9, chain: b7) -> b16 {
305    // Widen the slot first so the shifts do not lose high bits.
306    let s: b16 = slot.resize();
307    // slot × 64 + slot × 4 + chain = slot × 68 + chain.
308    (s << 6) + (s << 2) + chain.resize::<16>()
309}
310
311/// Op codes in the residue table.
312///
313/// Each residue of the 68-cycle timetable runs one kind of op. These small
314/// integers (4 bits in hardware, see [`Slot::kind`]) name the kinds. They are
315/// `usize` because RHDL kernels accept `usize` constants and turn them into
316/// bit patterns where they are compared.
317pub mod code {
318    /// No op this cycle.
319    pub const IDLE: usize = 0;
320    /// Seed derivation.
321    pub const SEED: usize = 1;
322    /// Public chain salt.
323    pub const PK_NONCE: usize = 2;
324    /// Private message nonce.
325    pub const NONCE: usize = 3;
326    /// XOF block (argument = block).
327    pub const XOF: usize = 4;
328    /// Commitment chunk-0 block (argument = block).
329    pub const CHUNK0: usize = 5;
330    /// Commitment chunk-1 block (argument = block within chunk).
331    pub const CHUNK1: usize = 6;
332    /// Commitment parent, yielding `pk_hash`.
333    pub const PARENT: usize = 7;
334    /// External Merkle-subtree request.
335    pub const MERKLE: usize = 8;
336    /// External message-digit request.
337    pub const MESSAGE: usize = 9;
338}
339
340/// The *lag* of an op: how many whole 68-cycle periods after its key's
341/// chain-0 cycle `T` it issues.
342///
343/// `offset` is the op's issue cycle relative to `T` (from [`SCHEDULE`]). A
344/// negative offset gives a negative lag (the op runs in an earlier period).
345/// `div_euclid` rounds toward minus infinity, so offset −1 has lag −1, not 0.
346const fn lag_of(offset: i32) -> i32 {
347    offset.div_euclid(KEY_PERIOD as i32)
348}
349
350/// Smallest and largest op lag (period of an op relative to its key's period).
351///
352/// The difference tells how many periods one key spans, which bounds how many
353/// contexts must exist ([`CONTEXTS`]) and when a key must be decided
354/// ([`KEY_LEAD`]).
355const fn lag_range() -> (i32, i32) {
356    // Start with impossible extremes; every op tightens them.
357    let mut low = i32::MAX;
358    let mut high = i32::MIN;
359    let mut index = 0;
360    while index < OPS {
361        // Lag of op number `index` in the timetable.
362        let lag = lag_of(SCHEDULE.offsets[index]);
363        if lag < low {
364            low = lag;
365        }
366        if lag > high {
367            high = lag;
368        }
369        index += 1;
370    }
371    (low, high)
372}
373
374/// Bias added to lags so they fit an unsigned [`CTX_BITS`]-bit field.
375///
376/// Lags can be negative (preparation ops run before chain 0), but a hardware
377/// field is unsigned. The timetable stores `lag + LAG_BIAS`, and the decode
378/// step adds [`CTX_BIAS`] back in, so the bias cancels:
379/// `period + BIAS − (lag + BIAS) = period − lag`. A compile-time assertion
380/// below checks that every biased lag fits.
381pub const LAG_BIAS: i32 = 4;
382/// Periods between deciding key `k` and its chain 0: one before its first op.
383///
384/// A key must be decided (real key or bubble) before its earliest op decodes,
385/// because decode reads the key's [`KeyEntry::real`] flag. The earliest op has
386/// lag `lag_range().0` (negative); deciding one period earlier than that is
387/// `1 − lag_range().0` periods before chain 0.
388pub const KEY_LEAD: i32 = 1 - lag_range().0;
389
390/// [`LAG_BIAS`] as a `usize`, usable inside RHDL kernels.
391pub const LAG_BIAS_U: usize = LAG_BIAS as usize;
392/// [`KEY_LEAD`] as a `usize`, usable inside RHDL kernels.
393pub const KEY_LEAD_U: usize = KEY_LEAD as usize;
394
395// RHDL kernels accept only RHDL-typed or `usize` constants, so every BLAKE3
396// constant the request mux needs is converted here, once.
397/// Converts the BLAKE3 initial vector [`IV`] (`u32` words) into RHDL `b32`
398/// words, at compile time.
399const fn iv_bits() -> [b32; 8] {
400    let mut out = [b32(0); 8];
401    let mut w = 0;
402    while w < 8 {
403        out[w] = b32(IV[w] as u128);
404        w += 1;
405    }
406    out
407}
408/// BLAKE3 IV words.
409///
410/// The chaining value a BLAKE3 hash starts from. Used for every request that
411/// is the first block of a hash (nonces, XOF, first commitment blocks, parent,
412/// Merkle, message). The seed request starts from the secret prefix CV instead.
413pub const IV_BITS: [b32; 8] = iv_bits();
414/// Flags of the seed request.
415///
416/// BLAKE3 "derive key" mode: the 20-byte material block is the whole (last,
417/// root) block of a hash keyed by the secret prefix CV.
418pub const FLAGS_SEED: b32 = b32((CHUNK_END | ROOT | DERIVE_KEY_MATERIAL) as u128);
419/// Flags of a complete single-block root hash.
420///
421/// The default for a request: one block that is both the start and the end of
422/// its chunk and the root of the hash.
423pub const FLAGS_SINGLE: b32 = b32(SINGLE_BLOCK_ROOT as u128);
424/// Flag marking a chunk's first block.
425pub const FLAG_START: b32 = b32(CHUNK_START as u128);
426/// Flag marking a chunk's last block.
427pub const FLAG_END: b32 = b32(CHUNK_END as u128);
428/// Flags of the commitment parent.
429///
430/// The commitment is 1,240 bytes, so it spans two BLAKE3 chunks; a parent
431/// node marked root joins their chaining values into `pk_hash`.
432pub const FLAGS_PARENT: b32 = b32((PARENT | ROOT) as u128);
433/// `CHUNK_END | ROOT`: profile 2's message tail block.
434pub const FLAGS_END_ROOT: b32 = b32((CHUNK_END | ROOT) as u128);
435/// Profile 2 (`DSIG_MESSAGE_BYTES=64`): the message digest is two blocks.
436pub const TWO_BLOCK_MESSAGE: bool = crate::MESSAGE_BYTES == 64;
437/// `pk_nonce` derivation prefix word.
438///
439/// Placed in word 0 of the nonce request so the two nonces derived from the
440/// same seed differ (domain separation).
441pub const PK_NONCE_WORD: b32 = b32(PK_NONCE_PREFIX as u128);
442/// Message-nonce derivation prefix word.
443pub const NONCE_WORD: b32 = b32(NONCE_PREFIX as u128);
444
445// Every biased lag fits the unsigned CTX_BITS-bit field of `Slot::lag`.
446const _: () = assert!(lag_range().0 + LAG_BIAS >= 0 && lag_range().1 + LAG_BIAS < CONTEXTS as i32);
447// From its decision to its last op, a key spans fewer than CONTEXTS periods,
448// so no newer key can reuse its context while it still has work pending.
449const _: () = assert!(lag_range().1 - lag_range().0 + KEY_LEAD < CONTEXTS as i32);
450
451/// Maps a schedule [`Op`] to its `(code, argument)` pair for the hardware
452/// table. The argument is the block number for XOF and chunk ops, else 0.
453const fn code_of(op: Op) -> (usize, usize) {
454    match op {
455        Op::Seed => (code::SEED, 0),
456        Op::PkNonce => (code::PK_NONCE, 0),
457        Op::Nonce => (code::NONCE, 0),
458        Op::Xof(n) => (code::XOF, n as usize),
459        Op::Chunk0(n) => (code::CHUNK0, n as usize),
460        Op::Chunk1(n) => (code::CHUNK1, n as usize),
461        Op::Parent => (code::PARENT, 0),
462        Op::Merkle => (code::MERKLE, 0),
463        Op::Message => (code::MESSAGE, 0),
464        // Profile 2's tail residue: the same slot code, argument 1.
465        Op::MessageTail => (code::MESSAGE, 1),
466    }
467}
468
469/// One timetable row in RHDL types.
470///
471/// The decode step reads one `Slot` per cycle, chosen by the residue. It says
472/// *what* to do (`kind`, `arg`) and *for which key* (`lag`, turned into a
473/// context by subtracting it from the current period).
474#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
475pub struct Slot {
476    /// [`code`] of the op at this residue.
477    ///
478    /// 4 bits: values 0 ([`code::IDLE`]) to 9 ([`code::MESSAGE`]).
479    pub kind: b4,
480    /// Op argument (block number).
481    ///
482    /// 5 bits: XOF block 0..20, chunk-0 block 0..16 or chunk-1 block 0..4.
483    pub arg: b5,
484    /// Lag of the op plus [`LAG_BIAS`].
485    ///
486    /// [`CTX_BITS`] wide, so it can be subtracted directly from a [`Ctx`].
487    pub lag: Ctx,
488}
489
490/// Builds [`SLOTS`]: for each residue `r`, the op the schedule places there
491/// (or an idle row), converted into RHDL types. Runs at compile time.
492const fn slot_table() -> [Slot; KEY_PERIOD] {
493    // Every residue starts idle (kind 0).
494    let mut table = [Slot {
495        kind: b4(0),
496        arg: b5(0),
497        lag: bits(0),
498    }; KEY_PERIOD];
499    let mut r = 0;
500    while r < KEY_PERIOD {
501        // `index` is the op number the solver placed at residue `r`, if any.
502        if let Some(index) = SCHEDULE.residue[r] {
503            let (kind, arg) = code_of(op_at(index as usize));
504            // Store the lag biased, so it is never negative.
505            let lag = lag_of(SCHEDULE.offsets[index as usize]) + LAG_BIAS;
506            table[r] = Slot {
507                kind: b4(kind as u128),
508                arg: b5(arg as u128),
509                lag: bits(lag as u128),
510            };
511        }
512        r += 1;
513    }
514    table
515}
516
517/// The residue table as RHDL constants.
518///
519/// 68 rows, one per residue. In hardware this is a small ROM (a constant
520/// lookup), selected by the residue counter.
521pub const SLOTS: [Slot; KEY_PERIOD] = slot_table();
522
523/// XOF window load: which block (if any) to *address* at each residue.
524///
525/// Block `t` is loaded into the window at chain `first_chain(t) − 1`, so its
526/// RAM read is addressed one cycle earlier. Block 0 is addressed at residue 66
527/// of the previous period (for the next key).
528///
529/// Why a window: chain `i` needs XOF bytes `18i..18i+18`, which may straddle
530/// two 64-byte blocks. Keeping the current and the next block in two
531/// registers (indexed by block parity) lets every chain pick its 18 bytes
532/// with a fixed multiplexer ([`pick_secret_kernel`]), and the XOF RAM needs
533/// only one read per block.
534///
535/// ```text
536///   residue:  first_chain(t) − 2    first_chain(t) − 1     first_chain(t)
537///             address xof_ram       RAM data → window[t%2]  chain reads window
538/// ```
539#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
540pub struct WindowLoad {
541    /// A block read is addressed this residue.
542    pub active: bool,
543    /// The block number.
544    ///
545    /// 5 bits: 0..[`XOF_BLOCKS`] (20).
546    pub block: b5,
547    /// The block belongs to the key that starts next period.
548    ///
549    /// Only block 0 has this set: its read happens two cycles before chain 0,
550    /// which is at the end of the previous period.
551    pub next_key: bool,
552}
553
554/// Builds [`WINDOW_LOADS`] at compile time: for each XOF block, mark the
555/// residue at which its RAM read is addressed.
556const fn window_table() -> [WindowLoad; KEY_PERIOD] {
557    // All residues start with no load.
558    let mut table = [WindowLoad {
559        active: false,
560        block: b5(0),
561        next_key: false,
562    }; KEY_PERIOD];
563    let mut t = 0;
564    while t < XOF_BLOCKS {
565        // Address two cycles before the first chain that needs block `t`:
566        // one cycle for the RAM read, one for the window register.
567        let address = first_chain_of_xof(t) - 2;
568        // A negative cycle falls in the previous period (block 0 only).
569        let next_key = address < 0;
570        table[address.rem_euclid(KEY_PERIOD as i32) as usize] = WindowLoad {
571            active: true,
572            block: b5(t as u128),
573            next_key,
574        };
575        t += 1;
576    }
577    table
578}
579
580/// Window-load table as RHDL constants.
581///
582/// 68 rows, indexed by the residue of the current cycle.
583pub const WINDOW_LOADS: [WindowLoad; KEY_PERIOD] = window_table();
584
585/// Where chain `i`'s 18 secret bytes sit in the two-block XOF window.
586///
587/// Chain `i` starts at XOF byte `18i`. That byte lies in block `18i / 64` at
588/// offset `18i mod 64`. Because 18 is even, every offset is even, which halves
589/// the multiplexer in [`pick_secret_kernel`].
590#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
591pub struct ChainPick {
592    /// Parity of the block holding the chain's first byte.
593    ///
594    /// The window stores even blocks in `window[0]` and odd ones in
595    /// `window[1]`; this says which register is the "low" (earlier) block.
596    pub low_parity: bool,
597    /// Byte offset of the first byte within that block (even, 0..64).
598    ///
599    /// 6 bits.
600    pub offset: b6,
601}
602
603/// Builds [`CHAIN_PICKS`] at compile time: entry `i` locates chain `i`'s
604/// secret. The table has [`KEY_PERIOD`] entries because it is indexed by
605/// residue, and residue `i` feeds chain `i`.
606const fn pick_table() -> [ChainPick; KEY_PERIOD] {
607    let mut table = [ChainPick {
608        low_parity: false,
609        offset: b6(0),
610    }; KEY_PERIOD];
611    let mut i = 0;
612    while i < CHAINS {
613        // First XOF byte of chain `i`.
614        let start = ELEMENT_BYTES * i;
615        table[i] = ChainPick {
616            low_parity: (start / 64) % 2 == 1,
617            offset: b6((start % 64) as u128),
618        };
619        i += 1;
620    }
621    table
622}
623
624/// Chain-pick table as RHDL constants.
625///
626/// Indexed by residue; only the first [`CHAINS`] (= all 68) entries are
627/// meaningful.
628pub const CHAIN_PICKS: [ChainPick; KEY_PERIOD] = pick_table();
629
630/// Run configuration; hold stable while `run` is set.
631///
632/// Written by the host before a run. Together, `epoch`, the batch id and the
633/// key index make every key's number unique, and the seed is derived from
634/// that number. This is how the design guarantees fresh secrets per key.
635#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
636pub struct FactoryConfig {
637    /// Secret derive-key prefix CV ([`slice_model::prefix_cv`](crate::slice_model::prefix_cv)).
638    ///
639    /// Eight 32-bit words: the BLAKE3 chaining value after hashing the secret
640    /// key-derivation key. The seed request starts from it, so seeds cannot be
641    /// predicted without the secret.
642    pub prefix_cv: [b32; 8],
643    /// Durably allocated epoch.
644    ///
645    /// 64 bits. The host persists it before use, so a restart never repeats an
646    /// epoch, and therefore never repeats a key number.
647    pub epoch: b64,
648    /// First batch id of this run.
649    ///
650    /// 64 bits. Batch `b` of the run has id `first_batch + b`.
651    pub first_batch: b64,
652    /// Batches to make (0 = no limit).
653    ///
654    /// 32 bits. When [`FactoryControl::next_batch`] reaches it, no new key is
655    /// admitted.
656    pub batches: b32,
657    /// This slice's number; key index within a batch is
658    /// `KEYS_PER_SLICE·slice + j`.
659    ///
660    /// 3 bits: up to 8 slices.
661    pub slice: b3,
662}
663
664/// A request for one of the two externally fed slots.
665///
666/// The subtree unit and the signer also need single-block BLAKE3 hashes. The
667/// timetable reserves one residue per period for each of them, so they can
668/// borrow the factory's lane instead of building their own.
669#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
670pub struct SlotRequest {
671    /// Hold true until taken.
672    ///
673    /// The request is taken only at its slot's residue, so the caller may wait
674    /// up to one period (68 cycles).
675    pub valid: bool,
676    /// The 64-byte block as words (single-block root hash, IV key).
677    pub block: [b32; 16],
678    /// Profile 2's message digest only (`false` otherwise): this is the
679    /// tail block (48 bytes, `CHUNK_END | ROOT`, chained from [`Self::cv`]);
680    /// when `false` a profile-2 message block is the first one
681    /// (`CHUNK_START`, not a root, its CV returned).
682    pub tail: bool,
683    /// The chaining value of a tail block.
684    pub cv: [b32; 8],
685    /// Returned with the result.
686    ///
687    /// 16 bits chosen by the caller, so it can match results to requests.
688    pub tag: b16,
689}
690
691/// Factory inputs.
692#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
693pub struct SliceFactoryInput {
694    /// Configuration, stable while running.
695    pub config: FactoryConfig,
696    /// Allow new keys to be started.
697    ///
698    /// Sampled once per period, at residue 0. Clearing it turns later keys
699    /// into bubbles; keys already admitted finish normally.
700    pub run: bool,
701    /// The cache has room for one more key.
702    ///
703    /// Also sampled at residue 0. Without room, the key becomes a bubble
704    /// instead of overwriting a cached key that may not be spent yet.
705    pub space: bool,
706    /// Merkle-subtree request.
707    pub merkle: SlotRequest,
708    /// Message-digit request.
709    pub message: SlotRequest,
710}
711
712/// Four positions of one chain for the cache.
713///
714/// One write per cycle while a real key's chains leave the chain engine:
715/// 68 writes per key, one per chain.
716#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
717pub struct CacheWrite {
718    /// A write this cycle.
719    pub valid: bool,
720    /// Cache slot of the key.
721    ///
722    /// 9 bits (0..[`CACHE_SLOTS`]).
723    pub slot: b9,
724    /// Chain number.
725    ///
726    /// 7 bits (0..68).
727    pub chain: b7,
728    /// Positions 0..=3.
729    ///
730    /// Four 18-byte nodes: the secret, the two intermediate nodes and the
731    /// public chain end. All four are stored so signing needs no hashing.
732    pub nodes: [[b8; ELEMENT_BYTES]; 4],
733}
734
735/// One finished key's public and private metadata.
736///
737/// Emitted once per real key, the cycle after its parent result leaves the
738/// BLAKE3 lane. By then all 68 chains are already in the cache.
739#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
740pub struct KeyDone {
741    /// A key finished this cycle.
742    pub valid: bool,
743    /// Cache slot holding its nodes.
744    pub slot: b9,
745    /// Batch number relative to `first_batch`.
746    pub batch: b32,
747    /// Position `j` of the key within this slice's share of the batch
748    /// (`0..KEYS_PER_SLICE`).
749    pub j: KeyIndex,
750    /// Endpoint commitment (Merkle leaf).
751    ///
752    /// 32 bytes: BLAKE3 of `pk_nonce ‖ end₀ ‖ … ‖ end₆₇`.
753    pub pk_hash: [b8; 32],
754    /// Public chain salt.
755    ///
756    /// 16 bytes. A verifier needs it to recompute the chains, so it goes into
757    /// the signature.
758    pub pk_nonce: [b8; 16],
759    /// Private message nonce.
760    ///
761    /// 16 bytes, kept by the signer until the key is used.
762    pub nonce: [b8; 16],
763}
764
765/// Result of a Merkle or message slot request.
766#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
767pub struct SlotResult {
768    /// A result this cycle.
769    pub valid: bool,
770    /// True for a message result, false for Merkle.
771    pub message: bool,
772    /// All sixteen output words.
773    ///
774    /// The full 512-bit compression output; callers keep what they need.
775    pub words: [b32; 16],
776    /// The request's tag.
777    pub tag: b16,
778}
779
780/// Factory outputs.
781#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
782pub struct SliceFactoryOutput {
783    /// Chain nodes for the cache.
784    pub cache_write: CacheWrite,
785    /// Finished key metadata.
786    pub key_done: KeyDone,
787    /// Merkle/message results.
788    pub slot_result: SlotResult,
789    /// The Merkle request was taken this cycle.
790    ///
791    /// The caller drops or replaces its request on the next cycle.
792    pub merkle_taken: bool,
793    /// The message request was taken this cycle.
794    pub message_taken: bool,
795    /// Real keys started so far.
796    ///
797    /// A 32-bit counter for status and tests; bubbles are not counted.
798    pub keys_started: b32,
799}
800
801/// Per-key bookkeeping, one entry per context.
802///
803/// Written at admission (residue 0, [`KEY_LEAD`] periods before chain 0) and
804/// read by every op of the key. Small enough to keep in registers, so any
805/// stage can read any entry in the same cycle.
806#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
807pub struct KeyEntry {
808    /// The key is real (not a bubble).
809    ///
810    /// A *bubble* is a period in which no key is made; its ops do nothing.
811    pub real: bool,
812    /// Cache slot.
813    pub slot: b9,
814    /// Batch relative to the run's first batch.
815    pub batch: b32,
816    /// Position within this slice's share of the batch.
817    pub j: KeyIndex,
818}
819
820/// A decoded op waiting to issue next cycle.
821///
822/// The output of the decode step (cycle `y − 1`), registered so that the
823/// issue step (cycle `y`) sees it together with the context-RAM outputs.
824#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
825pub struct Decoded {
826    /// [`code`] value.
827    pub kind: b4,
828    /// Op argument.
829    pub arg: b5,
830    /// Key context.
831    pub ctx: Ctx,
832    /// The op should issue (real key or valid external request).
833    pub live: bool,
834}
835
836/// Resettable control state.
837///
838/// Everything here returns to zero on reset. That kills all in-flight work:
839/// the ring forgets every key, so their remaining ops become idle. Keys cut
840/// off this way are never used. Reset ends the run; the host starts any new
841/// run with a fresh batch range, so no key number repeats.
842#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
843pub struct FactoryControl {
844    /// Residue of the *current* cycle.
845    ///
846    /// 7 bits, counts 0..=67 and wraps.
847    pub residue: b7,
848    /// Low bits of the current period.
849    ///
850    /// Increments when the residue wraps. Only the low [`CTX_BITS`] bits are
851    /// kept, which is exactly the context index of the key whose chains
852    /// run in this period.
853    pub period: Ctx,
854    /// Key contexts.
855    ///
856    /// One [`KeyEntry`] per context, indexed by key number mod [`CONTEXTS`].
857    pub ring: [KeyEntry; CONTEXTS],
858    /// Next cache slot to hand out.
859    ///
860    /// Wraps after 511; the `space` input stops admission before a slot still
861    /// in use would be handed out again.
862    pub next_slot: b9,
863    /// Next `j` within the slice's share of a batch.
864    pub next_j: KeyIndex,
865    /// Batch currently being filled, relative.
866    pub next_batch: b32,
867    /// Real keys started.
868    pub started: b32,
869    /// Op decoded last cycle.
870    pub decoded: Decoded,
871    /// A window load was addressed last cycle (load the data now).
872    pub window_pending: bool,
873    /// Parity of the pending window block.
874    pub window_parity: bool,
875    /// Latch the next key's `pk_nonce` from the RAM output now.
876    pub latch_pending: bool,
877    /// Result register holds a valid result.
878    pub result_valid: bool,
879    /// Commitment packer context for the key whose ends are streaming.
880    pub pack_ctx: Ctx,
881    /// Next commitment word index.
882    ///
883    /// 5 bits, 0..20, reset to 0 after the packer's last word of a key.
884    pub pack_word: b5,
885}
886
887/// Resetless datapath state.
888///
889/// Wide data registers without reset: reset logic on hundreds of bits costs
890/// area, and these values are only used when a matching valid flag in
891/// [`FactoryControl`] says so.
892#[derive(Clone, Copy, Debug, PartialEq, Digital)]
893pub struct FactoryData {
894    /// Two-block XOF window, indexed by block parity.
895    ///
896    /// `window[0]` holds the latest even block, `window[1]` the latest odd
897    /// block, 64 bytes each.
898    pub window: [[b8; 64]; 2],
899    /// Current key's `pk_nonce` for chain salting and the commitment prefix.
900    pub pk_nonce: [b8; 16],
901    /// The [`PACK_HISTORY`] keys before it, newest first.
902    ///
903    /// Needed because chain ends of a key can emerge after newer keys have
904    /// started feeding (see [`PACK_HISTORY`]).
905    pub older_nonces: [[b8; 16]; PACK_HISTORY],
906    /// Registered lane result.
907    ///
908    /// Sixteen 32-bit words: the full BLAKE3 compression output.
909    pub result: [b32; 16],
910    /// Tag of the registered result.
911    ///
912    /// Layout from [`lane_tag_kernel`]: kind in bits 0..4, argument in bits
913    /// 4..9, context from bit 9, external tag in bits 16..32.
914    pub result_tag: b32,
915}
916
917/// The six small per-key context RAMs, grouped into one child.
918///
919/// RHDL's `Synchronous` derive stores child state in a tuple, and Rust only
920/// compares tuples of up to twelve elements, so a parent can have at most
921/// eleven direct children. Grouping these LUT RAMs keeps the factory legal.
922///
923/// Each RAM has [`CONTEXTS`] entries, one per key context, a registered read
924/// (data appears the cycle after the address) and one write port.
925#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
926pub struct ContextRams {
927    /// The key's 32-byte one-time seed, as eight words.
928    seed: SyncBRAM<[b32; 8], CTX_BITS>,
929    /// `pk_nonce` copy for the chain-salt latch.
930    pkn_feed: SyncBRAM<[b8; 16], CTX_BITS>,
931    /// `pk_nonce` copy for the finished-key metadata.
932    pkn_meta: SyncBRAM<[b8; 16], CTX_BITS>,
933    /// The private message nonce.
934    nonce: SyncBRAM<[b8; 16], CTX_BITS>,
935    /// Running chaining value of commitment chunk 0.
936    cv0: SyncBRAM<[b32; 8], CTX_BITS>,
937    /// Running chaining value of commitment chunk 1.
938    cv1: SyncBRAM<[b32; 8], CTX_BITS>,
939}
940
941/// Six empty RAMs; their contents are don't-care until written.
942impl Default for ContextRams {
943    /// Builds the six RAMs with default (unloaded) contents.
944    fn default() -> Self {
945        Self {
946            seed: SyncBRAM::default(),
947            pkn_feed: SyncBRAM::default(),
948            pkn_meta: SyncBRAM::default(),
949            nonce: SyncBRAM::default(),
950            cv0: SyncBRAM::default(),
951            cv1: SyncBRAM::default(),
952        }
953    }
954}
955
956/// Ports of [`ContextRams`], one simple-dual-port interface each.
957///
958/// "Simple dual port" means one read address and one independent write port
959/// per RAM, both usable in the same cycle.
960#[derive(Clone, Copy, Debug, PartialEq, Digital)]
961pub struct ContextRamsIn {
962    /// One-time seed, written by `Seed`, read by nonce and XOF ops.
963    pub seed: RamIn<[b32; 8], CTX_BITS>,
964    /// `pk_nonce` copy read when latching the next key's salt.
965    pub pkn_feed: RamIn<[b8; 16], CTX_BITS>,
966    /// `pk_nonce` copy read when a key finishes.
967    pub pkn_meta: RamIn<[b8; 16], CTX_BITS>,
968    /// Private nonce, read when a key finishes.
969    pub nonce: RamIn<[b8; 16], CTX_BITS>,
970    /// Chunk-0 chaining value.
971    pub cv0: RamIn<[b32; 8], CTX_BITS>,
972    /// Chunk-1 chaining value.
973    pub cv1: RamIn<[b32; 8], CTX_BITS>,
974}
975
976/// Registered read data of [`ContextRams`].
977///
978/// Each field is the entry addressed on the *previous* cycle.
979#[derive(Clone, Copy, Debug, PartialEq, Digital)]
980pub struct ContextRamsOut {
981    /// Seed words.
982    pub seed: [b32; 8],
983    /// `pk_nonce` for the salt latch.
984    pub pkn_feed: [b8; 16],
985    /// `pk_nonce` for key metadata.
986    pub pkn_meta: [b8; 16],
987    /// Private nonce.
988    pub nonce: [b8; 16],
989    /// Chunk-0 chaining value.
990    pub cv0: [b32; 8],
991    /// Chunk-1 chaining value.
992    pub cv1: [b32; 8],
993}
994
995/// Connects [`ContextRams`] to its port types and wiring kernel.
996impl SynchronousIO for ContextRams {
997    /// All six RAMs' read addresses and write ports.
998    type I = ContextRamsIn;
999    /// All six RAMs' registered read data.
1000    type O = ContextRamsOut;
1001    /// The pass-through wiring.
1002    type Kernel = context_rams_kernel;
1003}
1004
1005/// Pure wiring between the grouped RAM ports and their children.
1006///
1007/// Inputs go straight to the child RAMs (`D`), and the RAMs' read data goes
1008/// straight out (`Q` to output). No logic and no extra cycle: this block only
1009/// exists to group six children under one name.
1010#[kernel]
1011pub fn context_rams_kernel(
1012    _clock_reset: ClockReset,
1013    input: ContextRamsIn,
1014    q: ContextRamsQ,
1015) -> (ContextRamsOut, ContextRamsD) {
1016    (
1017        ContextRamsOut {
1018            seed: q.seed,
1019            pkn_feed: q.pkn_feed,
1020            pkn_meta: q.pkn_meta,
1021            nonce: q.nonce,
1022            cv0: q.cv0,
1023            cv1: q.cv1,
1024        },
1025        ContextRamsD {
1026            seed: input.seed,
1027            pkn_feed: input.pkn_feed,
1028            pkn_meta: input.pkn_meta,
1029            nonce: input.nonce,
1030            cv0: input.cv0,
1031            cv1: input.cv1,
1032        },
1033    )
1034}
1035
1036/// The factory: one BLAKE3 lane, one chain engine and their bookkeeping.
1037///
1038/// `TABLE_MASK` is the chain-lane area knob, passed through to
1039/// [`SliceChainEngine`]: it chooses which AES rounds of the Haraka lane use
1040/// table ROMs. See [`crate::chain_step`].
1041#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
1042pub struct SliceFactory<const TABLE_MASK: usize> {
1043    /// The shared BLAKE3 compression pipeline: one request per cycle,
1044    /// [`PIPELINE_STAGES`] (28) cycles latency.
1045    lane: InlineCompressionPipeline,
1046    /// The chain engine: one chain in per cycle, four positions out
1047    /// [`SLICE_CHAIN_LATENCY`] cycles later.
1048    chains: SliceChainEngine<TABLE_MASK>,
1049    /// Packs `pk_nonce` and 68 chain ends (18 bytes each) into 64-byte
1050    /// commitment words.
1051    packer: ElementPacker<COMMITMENT_PREFIX>,
1052    /// The six per-key context RAMs.
1053    contexts: ContextRams,
1054    /// XOF blocks, 16 words each. 64 entries addressed `parity << 5 | block`:
1055    /// two keys' worth (the key feeding chains and the next one), selected by
1056    /// the low bit of the key's context.
1057    xof_ram: SyncBRAM<[b32; 16], 6>,
1058    /// Commitment words, 64 bytes each, addressed `ctx << 5 | word`
1059    /// ([`CommitAddress`]).
1060    commit_ram: SyncBRAM<[b8; 64], { CTX_BITS + 5 }>,
1061    /// Counters, the key ring and pipeline flags; cleared by reset.
1062    control: DFF<FactoryControl>,
1063    /// Wide data registers; not reset.
1064    data: DFF<FactoryData>,
1065}
1066
1067/// A factory at power-up: control state zeroed, data registers don't-care.
1068impl<const TABLE_MASK: usize> Default for SliceFactory<TABLE_MASK> {
1069    /// Builds every child in its default state.
1070    fn default() -> Self {
1071        Self {
1072            lane: InlineCompressionPipeline,
1073            chains: SliceChainEngine::default(),
1074            packer: ElementPacker::default(),
1075            contexts: ContextRams::default(),
1076            xof_ram: SyncBRAM::default(),
1077            commit_ram: SyncBRAM::default(),
1078            control: DFF::new(FactoryControl::default()),
1079            data: DFF::resetless(),
1080        }
1081    }
1082}
1083
1084/// Connects [`SliceFactory`] to its port types and wiring kernel.
1085impl<const TABLE_MASK: usize> SynchronousIO for SliceFactory<TABLE_MASK> {
1086    /// Run control, configuration and the two external slot requests.
1087    type I = SliceFactoryInput;
1088    /// Cache writes, finished keys and slot results.
1089    type O = SliceFactoryOutput;
1090    /// The factory's wiring, [`slice_factory_kernel`].
1091    type Kernel = slice_factory_kernel<TABLE_MASK>;
1092}
1093
1094/// Little-endian bytes to words.
1095///
1096/// Input: 64 bytes. Output: 16 words of 32 bits, word `w` made of bytes
1097/// `4w..4w+4` with byte `4w` as the lowest. BLAKE3 reads its message block
1098/// this way. Pure wiring: no logic gates, no cycles.
1099#[allow(clippy::needless_range_loop)]
1100#[kernel]
1101pub fn words_from_bytes_kernel(bytes: [b8; 64]) -> [b32; 16] {
1102    let mut words = [b32(0); 16];
1103    for w in 0..16 {
1104        // Byte 4w is the least significant byte of word w.
1105        words[w] = bytes[4 * w].resize::<32>()
1106            | (bytes[4 * w + 1].resize::<32>() << 8)
1107            | (bytes[4 * w + 2].resize::<32>() << 16)
1108            | (bytes[4 * w + 3].resize::<32>() << 24);
1109    }
1110    words
1111}
1112
1113/// Words to little-endian bytes.
1114///
1115/// The inverse of [`words_from_bytes_kernel`]: 16 words in, 64 bytes out.
1116/// Used to turn BLAKE3 outputs into the byte strings the protocol defines.
1117#[allow(clippy::needless_range_loop)]
1118#[kernel]
1119pub fn bytes_from_words_kernel(words: [b32; 16]) -> [b8; 64] {
1120    let mut bytes = [b8(0); 64];
1121    for w in 0..16 {
1122        bytes[4 * w] = words[w].resize();
1123        bytes[4 * w + 1] = (words[w] >> 8).resize();
1124        bytes[4 * w + 2] = (words[w] >> 16).resize();
1125        bytes[4 * w + 3] = (words[w] >> 24).resize();
1126    }
1127    bytes
1128}
1129
1130/// Pick a chain's 18 secret bytes from the two-block window.
1131///
1132/// Inputs: `low` (the block holding the secret's first byte), `high` (the
1133/// block after it) and `offset` (even byte offset 0..62 within `low`).
1134/// Output: the 18 bytes starting at `offset` in `low ‖ high`. Combinational.
1135///
1136/// Why written as an OR over 32 cases: RHDL turns a dynamic array index into
1137/// whole-array copies, which is very large. Comparing `offset` against each
1138/// of the 32 possible even values with constant indices instead gives a
1139/// 32-way select per output byte (see the RHDL notes in [`crate::packer`]).
1140#[allow(clippy::needless_range_loop)]
1141#[kernel]
1142pub fn pick_secret_kernel(low: [b8; 64], high: [b8; 64], offset: b6) -> [b8; ELEMENT_BYTES] {
1143    // The two blocks side by side, so a secret that crosses the block
1144    // boundary is one contiguous run of bytes.
1145    let mut joined = [b8(0); 128];
1146    for byte in 0..64 {
1147        joined[byte] = low[byte];
1148        joined[64 + byte] = high[byte];
1149    }
1150    let mut secret = [b8(0); ELEMENT_BYTES];
1151    for k in 0..ELEMENT_BYTES {
1152        // Byte k of the secret: exactly one case below matches, the others
1153        // contribute zero.
1154        let mut value = b8(0);
1155        for f in 0..32 {
1156            // Case f: the secret starts at byte 2f.
1157            if offset == b6((2 * f) as u128) {
1158                value = value | joined[2 * f + k];
1159            }
1160        }
1161        secret[k] = value;
1162    }
1163    secret
1164}
1165
1166/// Pack (op, ctx) into a lane tag; slot requests keep their 16-bit tag above.
1167///
1168/// The BLAKE3 lane carries a 32-bit tag beside each request and returns it
1169/// with the result. 28 cycles later, the tag alone tells the result path what
1170/// the result is and where to write it.
1171///
1172/// ```text
1173///   bit:  31            16 15   9+CTX_BITS  9 8     4 3    0
1174///         [ external tag ] [ 0 ] [   ctx    ] [  arg  ] [kind]
1175/// ```
1176#[kernel]
1177pub fn lane_tag_kernel(kind: b4, arg: b5, ctx: Ctx, external: b16) -> b32 {
1178    kind.resize::<32>()
1179        | (arg.resize::<32>() << 4)
1180        | (ctx.resize::<32>() << 9)
1181        | (external.resize::<32>() << 16)
1182}
1183
1184/// Read one ring entry by dynamic context index.
1185///
1186/// Inputs: the whole ring and a context index. Output: `ring[ctx]`. Written
1187/// as a compare-and-select loop for the same reason as
1188/// [`pick_secret_kernel`]: a [`CONTEXTS`]-way multiplexer instead of array
1189/// copies. Combinational.
1190#[kernel]
1191pub fn ring_entry_kernel(ring: [KeyEntry; CONTEXTS], ctx: Ctx) -> KeyEntry {
1192    let mut entry = KeyEntry::default();
1193    for index in 0..CONTEXTS {
1194        // Exactly one index matches.
1195        if ctx == CTX_INDEX[index] {
1196            entry = ring[index];
1197        }
1198    }
1199    entry
1200}
1201
1202/// The factory's wiring.
1203///
1204/// Like every RHDL kernel, this runs once per clock: it reads the current
1205/// register and child outputs (`q`) and the inputs, and returns this cycle's
1206/// outputs and the next values for every register and child input (`d`).
1207///
1208/// It does six jobs each cycle, in the sections marked below:
1209///
1210/// | Section | What happens this cycle |
1211/// |---|---|
1212/// | time | advance the residue (0..=67) and period counters |
1213/// | key admission | at residue 0, decide key `period + KEY_LEAD`: real or bubble |
1214/// | decode (y − 1) | look up next cycle's op, find its context, address the RAMs |
1215/// | issue (y) | build the BLAKE3 request of the op decoded last cycle |
1216/// | result path | register the lane's output; route it to a RAM or an output |
1217/// | chain feed | feed chain `residue` of the key whose period this is |
1218/// | chain results → cache | write four positions per chain; pack chain ends |
1219/// | RAM ports | collect every RAM's read address and write port |
1220///
1221/// Inputs: [`SliceFactoryInput`]. Outputs: [`SliceFactoryOutput`]; the
1222/// cache write appears [`SLICE_CHAIN_LATENCY`] cycles after its chain was
1223/// fed, [`KeyDone`] one cycle after the key's parent result leaves the lane.
1224#[allow(clippy::too_many_lines, clippy::needless_range_loop)]
1225#[kernel]
1226pub fn slice_factory_kernel<const TABLE_MASK: usize>(
1227    clock_reset: ClockReset,
1228    input: SliceFactoryInput,
1229    q: SliceFactoryQ<TABLE_MASK>,
1230) -> (SliceFactoryOutput, SliceFactoryD<TABLE_MASK>) {
1231    // True during reset: every valid flag below is forced low.
1232    let resetting = clock_reset.reset.any();
1233    // Short name for the run configuration.
1234    let cfg = input.config;
1235    // Next-state copies: start from the current state and change fields.
1236    let mut control = q.control;
1237    let mut data = q.data;
1238
1239    // ---------------------------------------------------------------- time
1240    // Residue of this cycle (0..=67) and of the next one.
1241    let residue = q.control.residue;
1242    let next_residue = if residue == b7((KEY_PERIOD - 1) as u128) {
1243        b7(0)
1244    } else {
1245        residue + b7(1)
1246    };
1247    // Period of the *next* cycle (decode looks one cycle ahead).
1248    let next_period = if residue == b7((KEY_PERIOD - 1) as u128) {
1249        q.control.period + CTX_ONE
1250    } else {
1251        q.control.period
1252    };
1253    control.residue = next_residue;
1254    control.period = next_period;
1255
1256    // ------------------------------------------------------- key admission
1257    // At residue 0 of period p, decide key p + KEY_LEAD.
1258    if residue == b7(0) {
1259        // Context of the key being decided (key number mod CONTEXTS).
1260        let decided: Ctx = q.control.period + CTX_LEAD;
1261        // The run asked for a fixed number of batches and they are all made.
1262        let limit_reached = cfg.batches != b32(0) && q.control.next_batch >= cfg.batches;
1263        // No key starts until every UltraRAM S-box is loaded (see
1264        // `aes_table::SboxLoader`); instantly true with no UltraRAM boxes.
1265        let real = input.run && input.space && !limit_reached && q.chains.ready;
1266        // A bubble gets the default entry (real = false).
1267        let mut entry = KeyEntry::default();
1268        if real {
1269            // A real key takes the next slot and the next fresh index. These
1270            // counters only move forward, so no index is handed out twice.
1271            entry = KeyEntry {
1272                real: true,
1273                slot: q.control.next_slot,
1274                batch: q.control.next_batch,
1275                j: q.control.next_j,
1276            };
1277            control.next_slot = q.control.next_slot + b9(1);
1278            control.started = q.control.started + b32(1);
1279            control.next_j = q.control.next_j + KEY_ONE;
1280            // After the slice's last key of a batch, move to the next batch.
1281            if q.control.next_j == LAST_KEY {
1282                control.next_j = KEY_ZERO;
1283                control.next_batch = q.control.next_batch + b32(1);
1284            }
1285        }
1286        // Store the entry in the decided key's context.
1287        for index in 0..CONTEXTS {
1288            if decided == CTX_INDEX[index] {
1289                control.ring[index] = entry;
1290            }
1291        }
1292    }
1293
1294    // ----------------------------------------------------- decode (y − 1)
1295    // The op that issues next cycle: the table row of next cycle's residue.
1296    let mut slot = Slot::default();
1297    for r in 0..KEY_PERIOD {
1298        if next_residue == b7(r as u128) {
1299            slot = SLOTS[r];
1300        }
1301    }
1302    // Context of the key this op belongs to: (period − lag) mod CONTEXTS.
1303    // (The table's lag carries LAG_BIAS, which CTX_BIAS cancels.)
1304    let ctx: Ctx = next_period + CTX_BIAS - slot.lag;
1305    // Bookkeeping of that key: is it real?
1306    let owner = ring_entry_kernel(q.control.ring, ctx);
1307    // External slots run only when their caller has a request waiting;
1308    // key ops run only for real keys. Idle residues never run.
1309    let is_merkle = slot.kind == b4(code::MERKLE as u128);
1310    let is_message = slot.kind == b4(code::MESSAGE as u128);
1311    let live = if is_merkle {
1312        input.merkle.valid
1313    } else if is_message {
1314        // Each message residue takes only its own kind of request: argument
1315        // 0 first blocks, argument 1 (profile 2's tail residue) tails. A
1316        // first block taken at the tail residue would burn a second key in
1317        // the same period while its tail had nowhere to go.
1318        input.message.valid && (input.message.tail == (slot.arg == b5(1)))
1319    } else {
1320        slot.kind != b4(code::IDLE as u128) && owner.real
1321    };
1322    control.decoded = Decoded {
1323        kind: slot.kind,
1324        arg: slot.arg,
1325        ctx,
1326        live,
1327    };
1328    // Context-RAM read addresses for the op issuing next cycle.
1329    // Commitment word to read: chunk-0 block n is word n, chunk-1 block n
1330    // is word 16 + n.
1331    let word_index: b5 = if slot.kind == b4(code::CHUNK1 as u128) {
1332        slot.arg + b5(CHUNK0_BLOCKS as u128)
1333    } else {
1334        slot.arg
1335    };
1336    let ctx_wide: CommitAddress = ctx.resize();
1337    let word_wide: CommitAddress = word_index.resize();
1338    // Address = ctx << 5 | word (see `CommitAddress`).
1339    let commit_address: CommitAddress = (ctx_wide << 5) | word_wide;
1340
1341    // ------------------------------------------------------- issue (y)
1342    // The op decoded last cycle; the RAM data it asked for is ready now.
1343    let op = q.control.decoded;
1344    let issuing = op.live && !resetting;
1345    // Its key's bookkeeping (batch and j for the seed request).
1346    let entry = ring_entry_kernel(q.control.ring, op.ctx);
1347    // The key's seed, read from its context.
1348    let seed = q.contexts.seed;
1349    // Default request: a single-block root hash starting from the IV, with a
1350    // full 64-byte block of zeros. Each op kind below changes what it needs.
1351    let mut request = CompressionInput {
1352        cv: IV_BITS,
1353        block: [b32(0); 16],
1354        counter_low: b32(0),
1355        counter_high: b32(0),
1356        block_len: b32(64),
1357        flags: FLAGS_SINGLE,
1358        tag: lane_tag_kernel(op.kind, op.arg, op.ctx, b16(0)),
1359        valid: issuing,
1360    };
1361    if op.kind == b4(code::SEED as u128) {
1362        // Seed: derive-key hash, keyed by the secret prefix CV, of the
1363        // 20-byte material epoch ‖ batch id ‖ key index (little-endian).
1364        // Key index within the batch = slice << SUBTREE_LEVELS | j.
1365        let index: b32 =
1366            (cfg.slice.resize::<32>() << (SUBTREE_LEVELS as u128)) | entry.j.resize::<32>();
1367        // Absolute batch id.
1368        let batch_id = cfg.first_batch + entry.batch.resize::<64>();
1369        request.cv = cfg.prefix_cv;
1370        request.block[0] = cfg.epoch.resize();
1371        request.block[1] = (cfg.epoch >> 32).resize();
1372        request.block[2] = batch_id.resize();
1373        request.block[3] = (batch_id >> 32).resize();
1374        request.block[4] = index;
1375        request.block_len = b32(20);
1376        request.flags = FLAGS_SEED;
1377    } else if op.kind == b4(code::PK_NONCE as u128) || op.kind == b4(code::NONCE as u128) {
1378        // Nonces: hash of a 4-byte prefix word and the 32-byte seed (36 bytes).
1379        // The prefix word separates the public and the private nonce.
1380        request.block[0] = if op.kind == b4(code::PK_NONCE as u128) {
1381            PK_NONCE_WORD
1382        } else {
1383            NONCE_WORD
1384        };
1385        for w in 0..8 {
1386            request.block[1 + w] = seed[w];
1387        }
1388        request.block_len = b32(36);
1389    } else if op.kind == b4(code::XOF as u128) {
1390        // XOF block n: hash of the 32-byte seed with the BLAKE3 block counter
1391        // set to n, so each n gives 64 new bytes of the stream.
1392        for w in 0..8 {
1393            request.block[w] = seed[w];
1394        }
1395        request.counter_low = op.arg.resize();
1396        request.block_len = b32(32);
1397    } else if op.kind == b4(code::CHUNK0 as u128) || op.kind == b4(code::CHUNK1 as u128) {
1398        // Commitment blocks. Chunk 0 has blocks 0..16, chunk 1 blocks 0..4.
1399        let chunk1 = op.kind == b4(code::CHUNK1 as u128);
1400        // First block of a chunk starts from the IV; later blocks continue
1401        // from the chunk's chaining value stored in the context.
1402        let first = op.arg == b5(0);
1403        let last = if chunk1 {
1404            op.arg == b5(3)
1405        } else {
1406            op.arg == b5((CHUNK0_BLOCKS - 1) as u128)
1407        };
1408        if !first {
1409            request.cv = if chunk1 {
1410                q.contexts.cv1
1411            } else {
1412                q.contexts.cv0
1413            };
1414        }
1415        // The 64-byte commitment word, read from the commitment RAM.
1416        request.block = words_from_bytes_kernel(q.commit_ram);
1417        // BLAKE3's chunk counter: 0 for chunk 0, 1 for chunk 1.
1418        request.counter_low = if chunk1 { b32(1) } else { b32(0) };
1419        // The commitment is 1,240 = 19 × 64 + 24 bytes: the last block is short.
1420        request.block_len = if chunk1 && last { b32(24) } else { b32(64) };
1421        // Not a root here: the parent below is the root.
1422        let mut flags = b32(0);
1423        if first {
1424            flags = flags | FLAG_START;
1425        }
1426        if last {
1427            flags = flags | FLAG_END;
1428        }
1429        request.flags = flags;
1430    } else if op.kind == b4(code::PARENT as u128) {
1431        // Parent: the two chunk chaining values side by side form the block.
1432        for w in 0..8 {
1433            request.block[w] = q.contexts.cv0[w];
1434            request.block[8 + w] = q.contexts.cv1[w];
1435        }
1436        request.flags = FLAGS_PARENT;
1437    } else if op.kind == b4(code::MERKLE as u128) {
1438        // External slots: the caller supplies the whole block and its tag.
1439        request.block = input.merkle.block;
1440        request.tag = lane_tag_kernel(op.kind, op.arg, op.ctx, input.merkle.tag);
1441    } else if op.kind == b4(code::MESSAGE as u128) {
1442        request.block = input.message.block;
1443        request.tag = lane_tag_kernel(op.kind, op.arg, op.ctx, input.message.tag);
1444        if TWO_BLOCK_MESSAGE {
1445            if input.message.tail {
1446                request.cv = input.message.cv;
1447                request.block_len = b32(48);
1448                request.flags = FLAGS_END_ROOT;
1449            } else {
1450                request.flags = FLAG_START;
1451            }
1452        }
1453    }
1454    // Tell the callers their request entered the lane this cycle.
1455    let merkle_taken = issuing && op.kind == b4(code::MERKLE as u128);
1456    let message_taken = issuing && op.kind == b4(code::MESSAGE as u128);
1457
1458    // ------------------------------------------------------ result path
1459    // Lane output → result register (this cycle) → RAM writes (next cycle).
1460    control.result_valid = q.lane.valid && !resetting;
1461    data.result = q.lane.words;
1462    data.result_tag = q.lane.tag;
1463    // Unpack the registered result's tag: what op, which argument, which key.
1464    let r_kind: b4 = q.data.result_tag.resize();
1465    let r_ctx: Ctx = (q.data.result_tag >> 9).resize();
1466    let r_arg: b5 = (q.data.result_tag >> 4).resize();
1467    let r_valid = q.control.result_valid;
1468    let result = q.data.result;
1469    let result_bytes = bytes_from_words_kernel(result);
1470    // Nonces are the first 16 bytes of their hash output.
1471    let mut first16 = [b8(0); 16];
1472    for byte in 0..16 {
1473        first16[byte] = result_bytes[byte];
1474    }
1475    // Seeds and chaining values are the first 8 words.
1476    let mut cv8 = [b32(0); 8];
1477    for w in 0..8 {
1478        cv8[w] = result[w];
1479    }
1480    // Meta reads for a parent result are addressed when it leaves the lane.
1481    // (So `pkn_meta` and `nonce` output that key's values next cycle, when
1482    // the parent result sits in the result register.)
1483    let lane_ctx: Ctx = (q.lane.tag >> 9).resize();
1484    // Bookkeeping of the result's key.
1485    let r_entry = ring_entry_kernel(q.control.ring, r_ctx);
1486    // pk_hash is the first 32 bytes of the parent output.
1487    let mut pk_hash = [b8(0); 32];
1488    for byte in 0..32 {
1489        pk_hash[byte] = result_bytes[byte];
1490    }
1491    // A parent result means the key is complete.
1492    let key_done = KeyDone {
1493        valid: r_valid && r_kind == b4(code::PARENT as u128),
1494        slot: r_entry.slot,
1495        batch: r_entry.batch,
1496        j: r_entry.j,
1497        pk_hash,
1498        pk_nonce: q.contexts.pkn_meta,
1499        nonce: q.contexts.nonce,
1500    };
1501    // Merkle and message results go back to their callers, with the
1502    // caller's 16-bit tag from the top half of the lane tag.
1503    let slot_result = SlotResult {
1504        valid: r_valid
1505            && (r_kind == b4(code::MERKLE as u128) || r_kind == b4(code::MESSAGE as u128)),
1506        message: r_kind == b4(code::MESSAGE as u128),
1507        words: result,
1508        tag: (q.data.result_tag >> 16).resize(),
1509    };
1510
1511    // ---------------------------------------------------- chain feed (T+i)
1512    // Window loads: address at residue first_chain(t) − 2, data next cycle.
1513    // This residue's row of the window-load table.
1514    let mut load = WindowLoad::default();
1515    for r in 0..KEY_PERIOD {
1516        if residue == b7(r as u128) {
1517            load = WINDOW_LOADS[r];
1518        }
1519    }
1520    // Which key the block belongs to: this period's key, or (block 0) the
1521    // next period's key.
1522    let feed_period: Ctx = if load.next_key {
1523        q.control.period + CTX_ONE
1524    } else {
1525        q.control.period
1526    };
1527    // XOF parity storage: (key parity, block).
1528    let xof_read: b6 = (feed_period.resize::<6>() & b6(1)) << 5 | load.block.resize::<6>();
1529    // Remember to capture the RAM data next cycle, and into which half.
1530    control.window_pending = load.active;
1531    control.window_parity = (load.block & b5(1)) == b5(1);
1532    // The read addressed last cycle has arrived: store it in the window.
1533    if q.control.window_pending {
1534        let block = bytes_from_words_kernel(q.xof_ram);
1535        if q.control.window_parity {
1536            data.window[1] = block;
1537        } else {
1538            data.window[0] = block;
1539        }
1540    }
1541    // pk_nonce of the next key: address at residue 66, latch at 67.
1542    control.latch_pending = residue == b7((KEY_PERIOD - 2) as u128);
1543    // At residue 67: the current pk_nonce becomes history (shift by one,
1544    // oldest dropped), and the next key's pk_nonce becomes current, ready
1545    // for its chain 0 at residue 0.
1546    if q.control.latch_pending {
1547        data.older_nonces[0] = q.data.pk_nonce;
1548        for k in 1..PACK_HISTORY {
1549            data.older_nonces[k] = q.data.older_nonces[k - 1];
1550        }
1551        data.pk_nonce = q.contexts.pkn_feed;
1552    }
1553    // Where this cycle's chain secret sits in the window.
1554    let mut pick = ChainPick::default();
1555    for r in 0..KEY_PERIOD {
1556        if residue == b7(r as u128) {
1557            pick = CHAIN_PICKS[r];
1558        }
1559    }
1560    // Order the two window halves as (block with the first byte, next block).
1561    let (low, high) = if pick.low_parity {
1562        (q.data.window[1], q.data.window[0])
1563    } else {
1564        (q.data.window[0], q.data.window[1])
1565    };
1566    // The key whose chains run in this period is the key numbered `period`.
1567    let feed_ctx = q.control.period;
1568    let feed_entry = ring_entry_kernel(q.control.ring, feed_ctx);
1569    // At residue i we feed chain i (KEY_PERIOD = CHAINS = 68).
1570    let chain_index: b7 = residue;
1571    // Bubbles feed nothing.
1572    let feeding = feed_entry.real && residue < b7(CHAINS as u128) && !resetting;
1573    // Chain tag: chain in bits 0..7, cache slot in bits 7..16, context from
1574    // bit 16. The engine returns it unchanged with the finished chain.
1575    let chain_tag: b32 = chain_index.resize::<32>()
1576        | (feed_entry.slot.resize::<32>() << 7)
1577        | (feed_ctx.resize::<32>() << 16);
1578
1579    // --------------------------------------------- chain results → cache
1580    // A finished chain from the engine, and its unpacked tag.
1581    let out = q.chains;
1582    let out_chain: b7 = out.tag.resize();
1583    let out_slot: b9 = (out.tag >> 7).resize();
1584    let out_ctx: Ctx = (out.tag >> 16).resize();
1585    let cache_write = CacheWrite {
1586        valid: out.valid,
1587        slot: out_slot,
1588        chain: out_chain,
1589        nodes: out.nodes,
1590    };
1591    // Chain 0 of a key starts a new commitment stream; remember its context
1592    // so the packer's words land in that key's part of the commitment RAM.
1593    let starting = out.valid && out_chain == b7(0);
1594    if starting {
1595        control.pack_ctx = out_ctx;
1596    }
1597    // The emerging key's pk_nonce: current, or `age` keys back.
1598    // age 0 = this period's key (current pk_nonce); age k + 1 =
1599    // older_nonces[k]. With a long chain hash, ends emerge periods late.
1600    let age: Ctx = q.control.period - out_ctx;
1601    let mut pack_nonce = q.data.pk_nonce;
1602    for k in 0..PACK_HISTORY {
1603        if age == CTX_INDEX[k + 1] {
1604            pack_nonce = q.data.older_nonces[k];
1605        }
1606    }
1607    // The packer's prefix: pk_nonce in the first 16 bytes, rest unused.
1608    let mut prefix = [b8(0); 64];
1609    for byte in 0..16 {
1610        prefix[byte] = pack_nonce[byte];
1611    }
1612    // Packer words → commitment RAM at (ctx, word).
1613    let packed = q.packer;
1614    let pack_ctx_wide: CommitAddress = q.control.pack_ctx.resize();
1615    let pack_word_wide: CommitAddress = q.control.pack_word.resize();
1616    let pack_address: CommitAddress = (pack_ctx_wide << 5) | pack_word_wide;
1617    // Advance the word index; wrap to 0 after the key's last word.
1618    if packed.valid {
1619        control.pack_word = if packed.last {
1620            b5(0)
1621        } else {
1622            q.control.pack_word + b5(1)
1623        };
1624    }
1625
1626    // --------------------------------------------------------- RAM ports
1627    // Each result kind is written to its own RAM, at its key's context.
1628    let write_seed = r_valid && r_kind == b4(code::SEED as u128);
1629    let write_pkn = r_valid && r_kind == b4(code::PK_NONCE as u128);
1630    let write_nonce = r_valid && r_kind == b4(code::NONCE as u128);
1631    let write_xof = r_valid && r_kind == b4(code::XOF as u128);
1632    let write_cv0 = r_valid && r_kind == b4(code::CHUNK0 as u128);
1633    let write_cv1 = r_valid && r_kind == b4(code::CHUNK1 as u128);
1634    // XOF RAM address: key parity in bit 5, block number in bits 0..5.
1635    let xof_write: b6 = (r_ctx.resize::<6>() & b6(1)) << 5 | r_arg.resize::<6>();
1636
1637    // Next-cycle inputs of every child and register.
1638    let d = SliceFactoryD::<TABLE_MASK> {
1639        // The request built in the issue section.
1640        lane: request,
1641        // This cycle's chain: its secret from the window, the key's salt.
1642        chains: SliceChainInput {
1643            secret: pick_secret_kernel(low, high, pick.offset),
1644            pk_nonce: q.data.pk_nonce,
1645            index: chain_index,
1646            tag: chain_tag,
1647            valid: feeding,
1648        },
1649        // Only the chain end (position 3) goes into the commitment.
1650        packer: PackerInput {
1651            prefix,
1652            start: starting,
1653            node: out.nodes[3],
1654            valid: out.valid,
1655        },
1656        // Reads use the decoded op's context (`ctx`) except where noted;
1657        // writes use the result's context (`r_ctx`).
1658        contexts: ContextRamsIn {
1659            seed: RamIn::<[b32; 8], CTX_BITS> {
1660                read_addr: ctx,
1661                write: RamWrite::<[b32; 8], CTX_BITS> {
1662                    addr: r_ctx,
1663                    value: cv8,
1664                    enable: write_seed,
1665                },
1666            },
1667            // Always reads the next key's pk_nonce, for the latch at 67.
1668            pkn_feed: RamIn::<[b8; 16], CTX_BITS> {
1669                read_addr: q.control.period + CTX_ONE,
1670                write: RamWrite::<[b8; 16], CTX_BITS> {
1671                    addr: r_ctx,
1672                    value: first16,
1673                    enable: write_pkn,
1674                },
1675            },
1676            // Reads follow the lane output, for the KeyDone metadata.
1677            pkn_meta: RamIn::<[b8; 16], CTX_BITS> {
1678                read_addr: lane_ctx,
1679                write: RamWrite::<[b8; 16], CTX_BITS> {
1680                    addr: r_ctx,
1681                    value: first16,
1682                    enable: write_pkn,
1683                },
1684            },
1685            nonce: RamIn::<[b8; 16], CTX_BITS> {
1686                read_addr: lane_ctx,
1687                write: RamWrite::<[b8; 16], CTX_BITS> {
1688                    addr: r_ctx,
1689                    value: first16,
1690                    enable: write_nonce,
1691                },
1692            },
1693            cv0: RamIn::<[b32; 8], CTX_BITS> {
1694                read_addr: ctx,
1695                write: RamWrite::<[b32; 8], CTX_BITS> {
1696                    addr: r_ctx,
1697                    value: cv8,
1698                    enable: write_cv0,
1699                },
1700            },
1701            cv1: RamIn::<[b32; 8], CTX_BITS> {
1702                read_addr: ctx,
1703                write: RamWrite::<[b32; 8], CTX_BITS> {
1704                    addr: r_ctx,
1705                    value: cv8,
1706                    enable: write_cv1,
1707                },
1708            },
1709        },
1710        // XOF RAM: window reads, XOF result writes.
1711        xof_ram: RamIn::<[b32; 16], 6> {
1712            read_addr: xof_read,
1713            write: RamWrite::<[b32; 16], 6> {
1714                addr: xof_write,
1715                value: result,
1716                enable: write_xof,
1717            },
1718        },
1719        // Commitment RAM: chunk-op reads, packer writes.
1720        commit_ram: RamIn::<[b8; 64], { CTX_BITS + 5 }> {
1721            read_addr: commit_address,
1722            write: RamWrite::<[b8; 64], { CTX_BITS + 5 }> {
1723                addr: pack_address,
1724                value: packed.word,
1725                enable: packed.valid,
1726            },
1727        },
1728        control,
1729        data,
1730    };
1731    (
1732        SliceFactoryOutput {
1733            cache_write,
1734            key_done,
1735            slot_result,
1736            merkle_taken,
1737            message_taken,
1738            keys_started: q.control.started,
1739        },
1740        d,
1741    )
1742}