Skip to main content

rhdl_dsig_scalable_core/
slice_signer.rs

1//! A slice's signer: one complete 1,642-byte signature every 68 cycles.
2//!
3//! # Where this fits
4//!
5//! By the time a message reaches the signer, almost all the work of signing
6//! it is already done. The [key factory](crate::slice_factory) made the
7//! one-time key in advance and wrote its chain nodes to the
8//! [key cache](crate::cache_bank). The [subtree builder](crate::slice_merkle)
9//! hashed the key into its batch's Merkle tree. The shared
10//! [cert unit](crate::cert_unit) signed the batch root with Ed25519. What is
11//! left is to pick a key, hash the message, look up the right nodes, and
12//! write it all out in the wire format. That is this module.
13//!
14//! The [`slice_factory`](crate::slice_factory) fills the cache with keys; this
15//! block spends them. For every message it:
16//!
17//! 1. **burns** the slice's next certified key (before anything else, so a
18//!    lost frame can never lead to key reuse) and asks the factory's message
19//!    slot for `BLAKE3(pk_hash ‖ nonce ‖ message)`;
20//! 2. turns the 64-byte XOF into 68 base-4 **digits** (64 message digits and
21//!    a 4-digit complement checksum, as in
22//!    [`reference::digits_from_xof`](crate::reference::digits_from_xof));
23//! 3. **prepares** the frame: reads four Merkle siblings, writes the six
24//!    header beats, and computes the 34 header bytes that share a beat with
25//!    the first node;
26//! 4. **streams** the frame: one cached node per cycle, each read from the
27//!    cache bank its digit selects, packed into beats.
28//!
29//! Preparation of frame *n + 1* overlaps the streaming of frame *n*, so in
30//! steady state a slice finishes one frame every 68 cycles: exactly one key per
31//! [`KEY_PERIOD`](crate::slice_schedule::KEY_PERIOD), matching the factory.
32//!
33//! ```text
34//!  message ─▶ [burn+hash] ─▶ factory slot ─▶ [digits] ─▶ [prep] ─▶ [stream] ─▶ frame buffer ─▶ writer
35//!                │                                         │           │        (4 frames)
36//!          key metadata RAM ◀── key_done          proof port   cache banks
37//!          certificate FIFO ◀── shared top
38//! ```
39//!
40//! (The "four Merkle siblings" are the 8-slice count, [`SUBTREE_LEVELS`]; the
41//! other [`TOP_LEVELS`] siblings arrive inside the certificate.)
42//!
43//! # Why the stages overlap
44//!
45//! Streaming is the only stage that has to take 68 cycles (one node per
46//! chain, one cache read per cycle). Everything else is shorter, so it is
47//! done for the *next* frame while the current one streams. A work item moves
48//! through a chain of holding registers in [`SignerControl`]:
49//!
50//! ```text
51//!   queue[0..2] ──▶ prep ──▶ ready ──▶ stream ──▶ cache reads ──▶ packer ──▶ frame buffer ──▶ drain
52//!   (burned,        (siblings, (prepared, (68 node    (token rides  (beats      (4 slots of     (26 beats
53//!    digits soon)    header)    waiting)   addresses)  with data)    6..25)      32 beats)       to HBM)
54//!
55//!   time ─▶     |──── frame n streams (68) ────|──── frame n+1 streams (68) ────|
56//!               |hash n+1|prep n+1| (waits)     |hash n+2|prep n+2| (waits)
57//! ```
58//!
59//! A new stream may begin addressing on the cycle right after the previous
60//! stream addressed chain 67, so streams run back to back with no gap.
61//!
62//! # Frame layout (26 × 64-byte beats = 1,664 bytes)
63//!
64//! Bytes `0..1642` are exactly
65//! [`DsigSignature::to_bytes`](crate::reference::DsigSignature::to_bytes):
66//!
67//! | Bytes | Field |
68//! |---|---|
69//! | 0..64 | certificate commitment (`DSIGFP01` envelope) |
70//! | 64..128 | Ed25519 signature |
71//! | 128..130 | key index within the batch, LE16 |
72//! | 130..162 | `pk_hash` |
73//! | 162..178 | `pk_nonce` |
74//! | 178..194 | private nonce |
75//! | 194..418 | seven 32-byte Merkle siblings, bottom-up |
76//! | 418..1642 | 68 revealed 18-byte nodes |
77//!
78//! Bytes `1642..1650` carry the message index (LE64) so the host can match a
79//! frame to its message; the remaining padding is zero.
80//!
81//! The same layout seen as beats, and who writes each one:
82//!
83//! ```text
84//!   beat 0       commitment                                  ← preparation (from certificate)
85//!   beat 1       Ed25519 signature                           ← preparation (from certificate)
86//!   beats 2..5   frame bytes 128..384 (index, pk_hash, …)    ← preparation (header_tail_kernel)
87//!   beat 6       bytes 384..418 (34 B) │ node 0 │ node 1 …    ← packer (34 B are its prefix)
88//!   beats 7..24  nodes                                       ← packer
89//!   beat 25      … node 67 │ msg index (B 42..50) │ zeros     ← packer + index patch
90//!   beats 26..31 unused (each frame slot is 32 beats)
91//! ```
92//!
93//! # Ordering rules
94//!
95//! * Keys are spent in cache-slot order, which is the order the factory made
96//!   them, so "key `k` is finished" is the counter test `done > consumed`.
97//! * A key is eligible only once its batch certificate is present.
98//! * A certificate is retired after the last frame that needs it (this slice's
99//!   `j = 15` key of that batch) has been prepared.
100//!
101//! # The key-reuse rule
102//!
103//! A one-time key must never sign twice. The signer increments `consumed` (the
104//! burn) in the same cycle the factory accepts the message hash, before any
105//! node is read. `consumed` only moves forward and the factory may not
106//! overwrite a slot until its key is burned (see [`crate::slice`]). So even if
107//! a frame is later lost, delayed or the run is reset, the key it used is
108//! gone for good.
109
110use crate::cache_bank::CACHE_READ_LATENCY;
111use crate::packer::{ElementPacker, PackerInput, SIGNATURE_PREFIX};
112use crate::slice_factory::{
113    KeyDone, SlotRequest, SlotResult, TWO_BLOCK_MESSAGE, bytes_from_words_kernel, cache_address_kernel,
114    words_from_bytes_kernel,
115};
116use crate::slice_merkle::{node_address_kernel, ring_of_kernel};
117use crate::{CHAINS, ELEMENT_BYTES, KEY_BITS, KEY_ONE, KEYS_PER_SLICE, KeyIndex, MESSAGE_BYTES, SUBTREE_LEVELS, TOP_LEVELS};
118use rhdl::prelude::*;
119use rhdl_fpga::core::{
120    dff::DFF,
121    ram::synchronous::{In as RamIn, SyncBRAM, Write as RamWrite},
122};
123
124/// Beats per frame.
125///
126/// 26 × 64 = 1,664 bytes: the 1,642-byte signature, the 8-byte message index
127/// and zero padding.
128pub const FRAME_BEATS: usize = 26;
129/// Frame slots in the frame buffer.
130///
131/// Four slots let one frame be prepared, one be packed and finished frames
132/// wait for the HBM writer, all at the same time.
133pub const FRAME_SLOTS: usize = 4;
134/// Header beats written by preparation.
135///
136/// Beats 0..6 (frame bytes 0..384). Beat 6 onward comes from the packer.
137pub const HEADER_BEATS: usize = 6;
138
139/// One batch certificate as delivered by the shared top.
140#[derive(Clone, Copy, Debug, PartialEq, Digital)]
141pub struct Certificate {
142    /// Certificate present.
143    pub valid: bool,
144    /// Batch number relative to the run's first batch.
145    pub batch: b32,
146    /// The 64-byte signed envelope.
147    pub commitment: [b8; 64],
148    /// The Ed25519 signature over it.
149    pub signature: [b8; 64],
150    /// Merkle siblings above this slice's subtree, lowest level first
151    /// ([`TOP_LEVELS`] of them; levels 4, 5 and 6 in the 8-slice build).
152    pub top: [[b8; 32]; TOP_LEVELS],
153}
154
155/// A message to sign.
156#[derive(Clone, Copy, Debug, PartialEq, Digital)]
157pub struct Message {
158    /// Message present; hold until `message_taken`.
159    pub valid: bool,
160    /// The message bytes (16, or 64 in profile 2; [`crate::MESSAGE_BYTES`]).
161    pub bytes: [b8; MESSAGE_BYTES],
162    /// Host-assigned index, written into the frame padding.
163    pub index: b64,
164}
165
166impl Default for Message {
167    /// No message.
168    fn default() -> Self {
169        Self {
170            valid: false,
171            bytes: [b8(0); MESSAGE_BYTES],
172            index: b64(0),
173        }
174    }
175}
176
177/// Message bytes past the first 16 (profile 2: 48, hashed as the digest's
178/// tail block; profile 1: none).
179const TAIL_BYTES: usize = MESSAGE_BYTES - 16;
180
181/// Signer inputs.
182#[derive(Clone, Copy, Debug, PartialEq, Digital)]
183pub struct SignerInput {
184    /// This slice's number (key index = `16·slice + j`).
185    pub slice: b3,
186    /// Next message.
187    pub message: Message,
188    /// Finished keys from the factory.
189    pub key_done: KeyDone,
190    /// Certificate offered by the shared top.
191    pub certificate: Certificate,
192    /// The factory took our message request.
193    pub message_taken: bool,
194    /// Factory slot results (message ones are ours).
195    pub slot_result: SlotResult,
196    /// Cache bank read data, positions 0..=3, for the address presented [`CACHE_READ_LATENCY`] cycles ago.
197    ///
198    /// The banks answer [`CACHE_READ_LATENCY`] cycles after `cache_address`
199    /// was presented; the `reads` token line in [`SignerControl`] keeps track
200    /// of which read each node belongs to.
201    pub cache_data: [[b8; ELEMENT_BYTES]; 4],
202    /// Subtree proof-port data for the address presented on the previous cycle.
203    pub proof_data: [b8; 32],
204    /// The writer accepts the presented frame beat.
205    pub beat_ready: bool,
206}
207
208/// One frame beat for the HBM writer.
209#[derive(Clone, Copy, Debug, PartialEq, Digital)]
210pub struct FrameBeat {
211    /// Beat present.
212    pub valid: bool,
213    /// The 64 bytes.
214    pub data: [b8; 64],
215    /// Final beat of a frame.
216    pub last: bool,
217}
218
219/// Signer outputs.
220#[derive(Clone, Copy, Debug, PartialEq, Digital)]
221pub struct SignerOutput {
222    /// Message request for the factory slot.
223    pub request: SlotRequest,
224    /// The current message was consumed (its key is burned).
225    pub message_taken: bool,
226    /// The certificate on the input was accepted this cycle.
227    pub certificate_taken: bool,
228    /// Cache read address `slot × 68 + chain` ([`cache_address_kernel`]).
229    pub cache_address: b16,
230    /// Subtree proof read address.
231    pub proof_address: b10,
232    /// Frame beat to the writer.
233    pub beat: FrameBeat,
234    /// Keys burned so far (the factory's cache-space accounting).
235    pub consumed: b32,
236    /// Frames fully handed to the writer.
237    pub frames_out: b32,
238}
239
240/// Per-key metadata held until the key is spent.
241///
242/// Stored in the signer's metadata RAM at the key's cache slot when the
243/// factory reports `key_done`; read back when the key is burned. 32 + 16 +
244/// 16 bytes plus batch and position.
245#[derive(Clone, Copy, Debug, PartialEq, Digital)]
246pub struct KeyMeta {
247    /// Endpoint commitment.
248    pub pk_hash: [b8; 32],
249    /// Public salt.
250    pub pk_nonce: [b8; 16],
251    /// Private nonce.
252    pub nonce: [b8; 16],
253    /// Relative batch.
254    pub batch: b32,
255    /// Position within the slice's batch share (`0..KEYS_PER_SLICE`).
256    pub j: KeyIndex,
257}
258
259/// A burned key travelling through digits, preparation and streaming.
260#[derive(Clone, Copy, Debug, PartialEq, Digital)]
261pub struct Work {
262    /// Entry in use.
263    pub valid: bool,
264    /// Digits have arrived.
265    pub digits_ready: bool,
266    /// Cache slot of the key.
267    pub slot: b9,
268    /// Key metadata.
269    pub meta: KeyMeta,
270    /// Message index.
271    pub index: b64,
272    /// 68 base-4 digits.
273    pub digits: [b2; CHAINS],
274    /// Frame-buffer slot (assigned by preparation).
275    pub frame: b2,
276    /// Header bytes 384..418 that precede node 0 in beat 6.
277    ///
278    /// Only the first [`SIGNATURE_PREFIX`] = 34 bytes are used; the array is
279    /// 64 bytes because that is the packer's prefix input width.
280    pub prefix: [b8; 64],
281}
282
283/// Everything needed to use one cache read when its data arrives.
284///
285/// The stream may already be addressing the next frame by then, so the
286/// frame slot and message index ride along with the read.
287#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
288pub struct NodeRead {
289    /// A real read (not a bubble).
290    pub valid: bool,
291    /// The read is chain 0, the first node of its frame.
292    pub first: bool,
293    /// Which of the four stored positions this signature reveals.
294    pub digit: b2,
295    /// Frame slot the node belongs to.
296    pub frame: b2,
297    /// Message index of that frame.
298    pub index: b64,
299}
300
301/// Index of the oldest read token.
302///
303/// The token at this index was issued [`CACHE_READ_LATENCY`] cycles ago, so
304/// its node is on the cache output now.
305const READ_LAST: usize = CACHE_READ_LATENCY - 1;
306
307/// Resettable control.
308#[derive(Clone, Copy, Debug, PartialEq, Digital)]
309pub struct SignerControl {
310    /// Keys finished by the factory.
311    pub done: b32,
312    /// Keys burned.
313    pub consumed: b32,
314    /// The metadata RAM output holds key `consumed`.
315    pub meta_ok: bool,
316    /// A message hash is in flight.
317    pub hashing: bool,
318    /// Profile 2: the first digest block returned; the tail is to be offered.
319    pub tail_pending: bool,
320    /// Profile 2: the tail block is in flight (its result is the digest).
321    pub in_tail: bool,
322    /// Profile 2: the tail block (message bytes 16..64, zero-padded).
323    pub tail_block: [b32; 16],
324    /// Profile 2: the first block's chaining value.
325    pub tail_cv: [b32; 8],
326    /// Certificates held (0..=2).
327    pub certs: b2,
328    /// Burned keys awaiting digits/preparation, oldest first.
329    pub queue: [Work; 2],
330    /// Preparation step: 0 idle, 1..=4 sibling reads, 5..=10 header writes.
331    ///
332    /// The counts are for the 8-slice build: steps `1..=SUBTREE_LEVELS` read
333    /// siblings, then [`HEADER_BEATS`] steps write the header.
334    pub prep_step: b4,
335    /// The work item being prepared.
336    pub prep: Work,
337    /// Prepared, waiting to stream.
338    pub ready: Work,
339    /// The work item being streamed.
340    pub stream: Work,
341    /// Next chain to address while streaming (68 = finished addressing).
342    pub chain: b7,
343    /// Cache reads in flight, newest at index 0. A read issued this cycle
344    /// reaches the end of this line exactly when its node leaves the cache
345    /// (`CACHE_READ_LATENCY` cycles), so the token travels with its data.
346    pub reads: [NodeRead; CACHE_READ_LATENCY],
347    /// Packer beats written for the frame being packed.
348    pub packed: b5,
349    /// Frame slot the packer writes into.
350    pub pack_frame: b2,
351    /// Message index of the frame being packed.
352    pub pack_index: b64,
353    /// Frame slots in use (preparing, streaming, complete or draining).
354    pub frames_used: b3,
355    /// Next frame slot to allocate.
356    pub next_frame: b2,
357    /// Completed frames not yet drained.
358    pub complete: b3,
359    /// Frame slot being drained.
360    pub drain_frame: b2,
361    /// Next beat to drain.
362    pub drain_beat: b5,
363    /// A drain read is outstanding.
364    pub drain_pending: bool,
365    /// The outstanding drain read is the frame's last beat.
366    pub drain_last: bool,
367    /// The output register holds an undelivered beat.
368    pub beat_valid: bool,
369    /// Frame-buffer address of the outstanding drain read (held while stalled).
370    pub drain_read: b7,
371    /// Frames fully drained.
372    pub frames_out: b32,
373}
374
375/// Resetless datapath registers.
376///
377/// Wide data that needs no reset value: nothing reads it until a control
378/// flag in [`SignerControl`] says it is valid. Leaving it out of reset saves
379/// reset routing.
380#[derive(Clone, Copy, Debug, PartialEq, Digital)]
381pub struct SignerData {
382    /// Certificate FIFO (index 0 oldest).
383    pub certs: [Certificate; 2],
384    /// Subtree siblings of the frame being prepared.
385    pub siblings: [[b8; 32]; SUBTREE_LEVELS],
386    /// The beat presented to the writer.
387    pub beat: [b8; 64],
388    /// The presented beat is a frame's last.
389    pub beat_last: bool,
390}
391
392/// The slice signer.
393///
394/// Two RAMs, the frame packer and two big registers. All the control lives in
395/// [`slice_signer_kernel`].
396#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
397pub struct SliceSigner {
398    /// [`KeyMeta`] of every cached key, indexed by cache slot (512 entries).
399    meta_ram: SyncBRAM<KeyMeta, 9>,
400    /// Frame buffer: [`FRAME_SLOTS`] slots × 32 beats of 64 bytes
401    /// (address `frame << 5 | beat`, see [`frame_address_kernel`]).
402    frames: SyncBRAM<[b8; 64], 7>,
403    /// Lays the 34-byte header tail and 68 revealed nodes into beats 6..25.
404    packer: ElementPacker<SIGNATURE_PREFIX>,
405    /// Counters, queues, stage registers (reset to idle).
406    control: DFF<SignerControl>,
407    /// Certificates, siblings and the output beat (no reset).
408    data: DFF<SignerData>,
409}
410
411/// An empty queue entry. `meta` is don't-care while `valid` is low.
412///
413/// Host-side helper for [`Default`]; kernels use [`cleared_work`].
414fn idle_work() -> Work {
415    cleared_work(Work {
416        valid: false,
417        digits_ready: false,
418        slot: b9(0),
419        meta: KeyMeta::dont_care(),
420        index: b64(0),
421        digits: [b2(0); CHAINS],
422        frame: b2(0),
423        prefix: [b8(0); 64],
424    })
425}
426
427/// Kernel-side `idle_work`: clears every field except the don't-care
428/// `meta`, which it keeps from `stale` (kernels cannot call `dont_care()`).
429///
430/// `meta` is never read while `valid` is low, so whatever value it keeps is
431/// harmless.
432#[kernel]
433pub fn cleared_work(stale: Work) -> Work {
434    Work {
435        valid: false,
436        digits_ready: false,
437        slot: b9(0),
438        meta: stale.meta,
439        index: b64(0),
440        digits: [b2(0); CHAINS],
441        frame: b2(0),
442        prefix: [b8(0); 64],
443    }
444}
445
446impl Default for SliceSigner {
447    /// No keys known, nothing burned, every stage idle, no certificates.
448    ///
449    /// `chain` starts at 68 ("finished addressing") so the idle stream does
450    /// not issue reads.
451    fn default() -> Self {
452        let idle = idle_work();
453        Self {
454            meta_ram: SyncBRAM::default(),
455            frames: SyncBRAM::default(),
456            packer: ElementPacker::default(),
457            control: DFF::new(SignerControl {
458                done: b32(0),
459                consumed: b32(0),
460                meta_ok: false,
461                hashing: false,
462                tail_pending: false,
463                in_tail: false,
464                tail_block: [b32(0); 16],
465                tail_cv: [b32(0); 8],
466                certs: b2(0),
467                queue: [idle; 2],
468                prep_step: b4(0),
469                prep: idle,
470                ready: idle,
471                stream: idle,
472                chain: b7(CHAINS as u128),
473                reads: [NodeRead::default(); CACHE_READ_LATENCY],
474                packed: b5(0),
475                pack_frame: b2(0),
476                pack_index: b64(0),
477                frames_used: b3(0),
478                next_frame: b2(0),
479                complete: b3(0),
480                drain_frame: b2(0),
481                drain_beat: b5(0),
482                drain_pending: false,
483                drain_last: false,
484                beat_valid: false,
485                drain_read: b7(0),
486                frames_out: b32(0),
487            }),
488            data: DFF::resetless(),
489        }
490    }
491}
492
493/// Input: messages, keys, certificates, cache and proof data, writer
494/// back-pressure. Output: hash requests, cache/proof addresses, frame beats.
495impl SynchronousIO for SliceSigner {
496    type I = SignerInput;
497    type O = SignerOutput;
498    type Kernel = slice_signer_kernel;
499}
500
501/// 68 digits from the 64-byte message XOF (low two bits, then checksum).
502///
503/// * Digits 0..64: the low two bits of each XOF byte (values 0..=3).
504/// * Digits 64..68: the checksum `Σ (3 − d)` over those 64 digits, written
505///   in base 4, low digit first. The maximum is 64 × 3 = 192, so it fits in
506///   8 bits and four base-4 digits.
507///
508/// The checksum is what makes W-OTS+ safe: raising any message digit (which a
509/// forger could do by hashing further along a chain) lowers the checksum, and
510/// a lower checksum digit would need a chain to be walked *backwards*, which
511/// is infeasible. Combinational.
512#[allow(clippy::needless_range_loop)]
513#[kernel]
514pub fn digits_kernel(xof: [b8; 64]) -> [b2; CHAINS] {
515    let mut digits = [b2(0); CHAINS];
516    // Running sum of (3 − digit); 8 bits is enough (at most 192).
517    let mut checksum = b8(0);
518    for i in 0..64 {
519        let d: b2 = xof[i].resize();
520        digits[i] = d;
521        checksum = checksum + (b8(3) - d.resize::<8>());
522    }
523    // The checksum's four base-4 digits, least significant first.
524    digits[64] = checksum.resize();
525    digits[65] = (checksum >> 2).resize();
526    digits[66] = (checksum >> 4).resize();
527    digits[67] = (checksum >> 6).resize();
528    digits
529}
530
531/// The 290 header bytes after the signature (frame bytes 128..418).
532///
533/// Output index `i` is frame byte `128 + i`:
534///
535/// | `h[..]` | Frame bytes | Field |
536/// |---|---|---|
537/// | 0..2 | 128..130 | `key_index`, LE16 |
538/// | 2..34 | 130..162 | `pk_hash` |
539/// | 34..50 | 162..178 | `pk_nonce` |
540/// | 50..66 | 178..194 | private nonce |
541/// | 66..290 | 194..418 | 7 siblings: `siblings` (subtree), then `top` (cert unit) |
542///
543/// Bytes 0..256 fill header beats 2..5; bytes 256..290 become the packer
544/// prefix. Combinational.
545#[allow(clippy::needless_range_loop)]
546#[kernel]
547pub fn header_tail_kernel(
548    key_index: b16,
549    meta: KeyMeta,
550    siblings: [[b8; 32]; SUBTREE_LEVELS],
551    top: [[b8; 32]; TOP_LEVELS],
552) -> [b8; 290] {
553    let mut h = [b8(0); 290];
554    // Key index within the batch, little-endian.
555    h[0] = key_index.resize();
556    h[1] = (key_index >> 8).resize();
557    for b in 0..32 {
558        h[2 + b] = meta.pk_hash[b];
559    }
560    for b in 0..16 {
561        h[34 + b] = meta.pk_nonce[b];
562        h[50 + b] = meta.nonce[b];
563    }
564    // The 7 proof siblings, lowest level first: the subtree's own, then the
565    // certificate unit's. Their byte offsets do not depend on the slice count.
566    for level in 0..SUBTREE_LEVELS {
567        for b in 0..32 {
568            h[66 + 32 * level + b] = siblings[level][b];
569        }
570    }
571    for level in 0..TOP_LEVELS {
572        for b in 0..32 {
573            h[66 + 32 * (SUBTREE_LEVELS + level) + b] = top[level][b];
574        }
575    }
576    h
577}
578
579/// Subtree sibling address for `level` of key `j` in batch ring `ring`: the
580/// node next to `j`'s ancestor at that level.
581///
582/// The ancestor of leaf `j` at `level` has index `j >> level`; its sibling
583/// differs only in the lowest bit, hence `^ 1`. Combinational.
584#[kernel]
585pub fn sibling_address_kernel(ring: b5, j: KeyIndex, level: b3) -> b10 {
586    // Index of the sibling within its level.
587    let index: KeyIndex = (j >> level.resize::<KEY_BITS>()) ^ KEY_ONE;
588    node_address_kernel(ring, level, index)
589}
590
591/// Preparation step of the last sibling read (steps `1..=` this).
592const LAST_READ_STEP: b4 = b4(SUBTREE_LEVELS as u128);
593/// Preparation step of header beat 0.
594const HEADER_STEP: b4 = b4((SUBTREE_LEVELS + 1) as u128);
595/// The last `j` of a batch: preparing it retires the batch's certificate.
596const LAST_J: KeyIndex = bits((KEYS_PER_SLICE - 1) as u128);
597
598/// Frame-buffer address of beat `beat` in frame slot `frame`.
599///
600/// `frame << 5 | beat`: each slot spans 32 beat addresses (26 used), so the
601/// address is a concatenation rather than a multiply. Combinational.
602#[kernel]
603pub fn frame_address_kernel(frame: b2, beat: b5) -> b7 {
604    (frame.resize::<7>() << 5) | beat.resize::<7>()
605}
606
607/// The signer's wiring.
608///
609/// One kernel runs every stage each cycle, in this order (later sections
610/// may override decisions of earlier ones, so the order matters):
611///
612/// 1. keys and certificates: count finished keys, accept a certificate;
613/// 2. **\[1\] burn + hash**: offer the message hash; burn the key if taken;
614/// 3. **\[2\] digits**: turn a returning message hash into 68 digits;
615/// 4. **\[4\] stream**: address the next chain, follow read tokens, feed the
616///    packer and write packed beats (the packer has priority on the frame
617///    buffer's write port);
618/// 5. **\[3\] prepare**: sibling reads, then header beats when the write port
619///    is free;
620/// 6. frame completion and drain to the HBM writer;
621/// 7. certificate FIFO retire/accept.
622///
623/// Timing: the metadata RAM, proof port and frame buffer answer one cycle
624/// after their address; the cache answers [`CACHE_READ_LATENCY`] cycles
625/// after. The packer adds one cycle.
626#[allow(clippy::too_many_lines, clippy::needless_range_loop)]
627#[kernel]
628pub fn slice_signer_kernel(
629    clock_reset: ClockReset,
630    input: SignerInput,
631    q: SliceSignerQ,
632) -> (SignerOutput, SliceSignerD) {
633    let resetting = clock_reset.reset.any();
634    // Next-state copies of the two registers; edited below, stored at the end.
635    let mut c = q.control;
636    let mut data = q.data;
637
638    // ============================================= keys and certificates
639    if input.key_done.valid {
640        c.done = q.control.done + b32(1);
641    }
642    // Cache slot of the next key to burn: `consumed mod 512`.
643    let next_slot: b9 = q.control.consumed.resize();
644    // Metadata RAM output (valid for key `consumed` when `meta_ok`).
645    let meta = q.meta_ram;
646    // The factory has finished at least one key we have not burned.
647    let key_finished = q.control.done > q.control.consumed;
648    // Accept an offered certificate whenever the 2-entry FIFO has room.
649    let cert_taken = input.certificate.valid && q.control.certs != b2(2);
650    if cert_taken {
651        if q.control.certs == b2(0) {
652            data.certs[0] = input.certificate;
653        } else {
654            data.certs[1] = input.certificate;
655        }
656    }
657
658    // ============================================= [1] burn + hash
659    // The next key's batch certificate is in the FIFO (entry 0 or 1).
660    let cert_match = (q.control.certs != b2(0) && q.data.certs[0].batch == meta.batch)
661        || (q.control.certs == b2(2) && q.data.certs[1].batch == meta.batch);
662    // Offer the message hash only when: metadata is valid, the key is
663    // certified, the work queue has room, no other hash is outstanding, a
664    // message is waiting, and we are not in reset.
665    // Profile 2: a new first block may go while the previous tail is in
666    // flight (`in_tail`), but not while a tail still waits to be offered.
667    let can_hash = q.control.meta_ok
668        && cert_match
669        && !q.control.queue[1].valid
670        && !q.control.hashing
671        && !(TWO_BLOCK_MESSAGE && q.control.tail_pending)
672        && input.message.valid
673        && !resetting;
674    // The 64-byte hash input: pk_hash ‖ private nonce ‖ 16-byte message.
675    let mut block = [b8(0); 64];
676    for b in 0..32 {
677        block[b] = meta.pk_hash[b];
678    }
679    for b in 0..16 {
680        block[32 + b] = meta.nonce[b];
681        block[48 + b] = input.message.bytes[b];
682    }
683    // Profile 2: the tail block, once the first block's CV is back.
684    let offer_tail = TWO_BLOCK_MESSAGE && q.control.tail_pending && !resetting;
685    let request = if offer_tail {
686        SlotRequest {
687            valid: true,
688            block: q.control.tail_block,
689            // Bit 0 marks a tail: results return in issue order, and a
690            // first block may be in the lane behind a tail.
691            tag: b16(1),
692            tail: true,
693            cv: q.control.tail_cv,
694        }
695    } else {
696        SlotRequest {
697            valid: can_hash,
698            block: words_from_bytes_kernel(block),
699            tag: b16(0),
700            tail: false,
701            cv: [b32(0); 8],
702        }
703    };
704    if offer_tail && input.message_taken {
705        c.tail_pending = false;
706        c.in_tail = true;
707    }
708    // The factory accepted the hash this cycle: the key is burned now.
709    let taken = can_hash && !offer_tail && input.message_taken;
710    if TWO_BLOCK_MESSAGE && taken {
711        // Latch the tail now: the message is consumed with the burn.
712        let mut tail = [b8(0); 64];
713        for b in 0..TAIL_BYTES {
714            tail[b] = input.message.bytes[16 + b];
715        }
716        c.tail_block = words_from_bytes_kernel(tail);
717    }
718    // The RAM output is trustworthy one cycle after a stable address whose key
719    // was already written; a burn changes the address.
720    c.meta_ok = key_finished && !taken;
721    // Working copy of the 2-entry work queue.
722    let mut queue = q.control.queue;
723    if taken {
724        // The burn: from here on this key can never be selected again.
725        c.consumed = q.control.consumed + b32(1);
726        c.hashing = true;
727        // New work item for the burned key; digits come later.
728        let mut entry = cleared_work(q.control.queue[1]);
729        entry.valid = true;
730        entry.slot = next_slot;
731        entry.meta = meta;
732        entry.index = input.message.index;
733        if q.control.queue[0].valid {
734            queue[1] = entry;
735        } else {
736            queue[0] = entry;
737        }
738    }
739
740    // ============================================= [2] digits
741    // The message hash returned: attach its digits to the oldest entry that
742    // is still waiting for them.
743    // Profile 2: a first block's result is a CV for its tail, not digits
744    // (a tail's result carries tag bit 0).
745    let tail_result = (input.slot_result.tag & b16(1)) == b16(1);
746    let first_of_two = TWO_BLOCK_MESSAGE && !tail_result;
747    if input.slot_result.valid && input.slot_result.message && first_of_two {
748        for w in 0..8 {
749            c.tail_cv[w] = input.slot_result.words[w];
750        }
751        c.tail_pending = true;
752        c.hashing = false;
753    }
754    if input.slot_result.valid && input.slot_result.message && !first_of_two {
755        let digits = digits_kernel(bytes_from_words_kernel(input.slot_result.words));
756        // Profile 2: `hashing` tracks the *next* message's first block, which
757        // may already be in the lane behind this tail; only its own result
758        // clears it.
759        if !TWO_BLOCK_MESSAGE {
760            c.hashing = false;
761        }
762        c.in_tail = false;
763        if q.control.queue[0].valid && !q.control.queue[0].digits_ready {
764            queue[0].digits = digits;
765            queue[0].digits_ready = true;
766        } else {
767            queue[1].digits = digits;
768            queue[1].digits_ready = true;
769        }
770    }
771
772    // ============================================= [4] stream (before prep:
773    // the packer has write priority on the frame buffer)
774    // The frame being streamed, and this cycle's cache read (if any).
775    let stream = q.control.stream;
776    let mut cache_address = b16(0);
777    let mut issued = NodeRead::default();
778    if stream.valid && q.control.chain < b7(CHAINS as u128) {
779        cache_address = cache_address_kernel(stream.slot, q.control.chain);
780        // Digit of the chain being read (constant-index loop for RHDL).
781        let mut digit = b2(0);
782        for i in 0..CHAINS {
783            if q.control.chain == b7(i as u128) {
784                digit = stream.digits[i];
785            }
786        }
787        issued = NodeRead {
788            valid: true,
789            first: q.control.chain == b7(0),
790            digit,
791            frame: stream.frame,
792            index: stream.index,
793        };
794        c.chain = q.control.chain + b7(1);
795    }
796    // Shift the read tokens one step; the new token enters at index 0.
797    c.reads[0] = issued;
798    for k in 1..CACHE_READ_LATENCY {
799        c.reads[k] = q.control.reads[k - 1];
800    }
801    // The oldest token's node is on the cache output this cycle.
802    let arriving = q.control.reads[READ_LAST];
803    // All four banks were read at the same row; keep the one at `digit`.
804    let mut node = input.cache_data[0];
805    for p in 0..4 {
806        if arriving.digit == b2(p as u128) {
807            node = input.cache_data[p];
808        }
809    }
810    let node_valid = arriving.valid;
811    // Node 0 of a frame: tells the packer to start a new stream.
812    let node_start = arriving.valid && arriving.first;
813    // The last node is addressed at chain 67; the next prepared frame may
814    // start addressing on the very next cycle, so streams run back to back.
815    let last_address = stream.valid && q.control.chain == b7((CHAINS - 1) as u128);
816    let finished = stream.valid && q.control.chain == b7(CHAINS as u128);
817    let mut next_stream = stream;
818    if finished {
819        next_stream.valid = false;
820    }
821    // The stream register can take the `ready` frame next cycle.
822    let stream_free = !stream.valid || last_address || finished;
823    let mut ready = q.control.ready;
824    if stream_free && q.control.ready.valid {
825        next_stream = q.control.ready;
826        ready.valid = false;
827        c.chain = b7(0);
828    }
829    c.stream = next_stream;
830
831    // Packed beats → frame buffer (priority write).
832    // The packer's registered output word (beat 6 + `packed` of the frame).
833    let packed = q.packer;
834    // The frame buffer's single write port for this cycle; the packer claims
835    // it first, preparation uses it only if it is still free.
836    let mut write = false;
837    let mut write_address = b7(0);
838    let mut write_value = packed.word;
839    let mut frame_complete = false;
840    if packed.valid {
841        write = true;
842        write_address = frame_address_kernel(
843            q.control.pack_frame,
844            q.control.packed + b5(HEADER_BEATS as u128),
845        );
846        c.packed = q.control.packed + b5(1);
847        if packed.last {
848            // Final beat: patch the message index into bytes 42..50 (frame
849            // bytes 1642..1650), just after node 67.
850            for b in 0..8 {
851                write_value[42 + b] = (q.control.pack_index >> b64(8 * b as u128)).resize();
852            }
853            frame_complete = true;
854        }
855    }
856
857    // A new frame's node 0 enters the packer: later packed beats belong to it.
858    // (This cycle's packed write above used the previous frame's registers.)
859    if node_start {
860        c.pack_frame = arriving.frame;
861        c.pack_index = arriving.index;
862        c.packed = b5(0);
863    }
864
865    // ============================================= [3] prepare
866    // The frame being prepared and its batch's subtree ring entry.
867    let prep = q.control.prep;
868    let ring = ring_of_kernel(prep.meta.batch);
869    let mut proof_address = b10(0);
870    let mut prep_next = prep;
871    // Set when this cycle finishes preparing the batch's last key.
872    let mut retire = false;
873    let step = q.control.prep_step;
874    // Start preparing when idle, a work item has its digits, the `ready`
875    // register is empty and a frame slot is free.
876    let can_start_prep = step == b4(0)
877        && q.control.queue[0].valid
878        && q.control.queue[0].digits_ready
879        && !q.control.ready.valid
880        && q.control.frames_used != b3(FRAME_SLOTS as u128);
881    let mut frames_used = q.control.frames_used;
882    if can_start_prep {
883        // Move the oldest work item into `prep`, give it a frame slot, and
884        // issue the level-0 sibling read.
885        prep_next = q.control.queue[0];
886        prep_next.frame = q.control.next_frame;
887        queue[0] = queue[1];
888        queue[1] = cleared_work(q.control.queue[1]);
889        c.next_frame = q.control.next_frame + b2(1);
890        frames_used = frames_used + b3(1);
891        proof_address = sibling_address_kernel(
892            ring_of_kernel(q.control.queue[0].meta.batch),
893            q.control.queue[0].meta.j,
894            b3(0),
895        );
896        c.prep_step = b4(1);
897    } else if step >= b4(1) && step <= LAST_READ_STEP {
898        // Sibling of `level` arrives from the proof port (read last cycle).
899        let level: b3 = (step - b4(1)).resize();
900        for l in 0..SUBTREE_LEVELS {
901            if level == b3(l as u128) {
902                data.siblings[l] = input.proof_data;
903            }
904        }
905        // Issue the next level's read, unless this was the last one.
906        if step < LAST_READ_STEP {
907            proof_address = sibling_address_kernel(ring, prep.meta.j, level + b3(1));
908        }
909        c.prep_step = step + b4(1);
910    } else if step >= HEADER_STEP {
911        // Header beat (step − HEADER_STEP); waits whenever the packer owns the port.
912        let beat: b5 = (step - HEADER_STEP).resize();
913        // This key's batch certificate (it is entry 0 or entry 1).
914        let cert = if q.data.certs[0].batch == prep.meta.batch {
915            q.data.certs[0]
916        } else {
917            q.data.certs[1]
918        };
919        // Key index within the whole batch: `slice · KEYS_PER_SLICE + j`.
920        let key_index: b16 =
921            (input.slice.resize::<16>() << (SUBTREE_LEVELS as u128)) | prep.meta.j.resize::<16>();
922        // Frame bytes 128..418.
923        let tail = header_tail_kernel(key_index, prep.meta, q.data.siblings, cert.top);
924        if !write {
925            write = true;
926            write_address = frame_address_kernel(prep.frame, beat);
927            if beat == b5(0) {
928                write_value = cert.commitment;
929            } else if beat == b5(1) {
930                write_value = cert.signature;
931            } else {
932                // Beats 2..5: consecutive 64-byte pieces of `tail`.
933                let mut value = [b8(0); 64];
934                for h in 2..HEADER_BEATS {
935                    if beat == b5(h as u128) {
936                        for b in 0..64 {
937                            value[b] = tail[64 * (h - 2) + b];
938                        }
939                    }
940                }
941                write_value = value;
942            }
943            if beat == b5((HEADER_BEATS - 1) as u128) {
944                // Last header beat: save the 34-byte packer prefix, hand the
945                // item to `ready`, and go idle.
946                for b in 0..SIGNATURE_PREFIX {
947                    prep_next.prefix[b] = tail[256 + b];
948                }
949                prep_next.valid = false;
950                ready = prep;
951                ready.prefix = prep_next.prefix;
952                c.prep_step = b4(0);
953                retire = prep.meta.j == LAST_J;
954            } else {
955                c.prep_step = step + b4(1);
956            }
957        }
958    }
959    c.prep = prep_next;
960    c.ready = ready;
961    c.queue = queue;
962
963    // ============================================= frame completion + drain
964    // Frames whose last packed beat is written, waiting to be drained.
965    let mut complete = q.control.complete;
966    if frame_complete {
967        complete = complete + b3(1);
968    }
969    // The writer took the presented beat.
970    if q.control.beat_valid && input.beat_ready {
971        c.beat_valid = false;
972    }
973    // The output register is empty, or is being emptied this cycle.
974    let presenting_free = !q.control.beat_valid || input.beat_ready;
975    // The drain read issued last cycle has its data on `q.frames`: move it
976    // into the output register as soon as that is free.
977    if q.control.drain_pending && presenting_free {
978        data.beat = q.frames;
979        data.beat_last = q.control.drain_last;
980        c.beat_valid = true;
981        c.drain_pending = false;
982        if q.control.drain_last {
983            c.frames_out = q.control.frames_out + b32(1);
984        }
985    }
986    let drain_address = frame_address_kernel(q.control.drain_frame, q.control.drain_beat);
987    // Issue the next drain read if a frame is complete and the pending read
988    // (if any) is leaving this cycle.
989    let can_drain = q.control.complete != b3(0) && (!q.control.drain_pending || presenting_free);
990    // While stalled, keep re-reading the same address so the RAM output
991    // stays stable.
992    let mut read_address = q.control.drain_read;
993    if can_drain {
994        read_address = drain_address;
995        c.drain_read = drain_address;
996        c.drain_pending = true;
997        c.drain_last = q.control.drain_beat == b5((FRAME_BEATS - 1) as u128);
998        if q.control.drain_beat == b5((FRAME_BEATS - 1) as u128) {
999            // Last beat read: the frame slot is free for a new frame.
1000            c.drain_beat = b5(0);
1001            c.drain_frame = q.control.drain_frame + b2(1);
1002            complete = complete - b3(1);
1003            frames_used = frames_used - b3(1);
1004        } else {
1005            c.drain_beat = q.control.drain_beat + b5(1);
1006        }
1007    }
1008    c.complete = complete;
1009    c.frames_used = frames_used;
1010
1011    // Certificate FIFO: retire the oldest, then accept a new one.
1012    // `certs` is the FIFO's next fill level (0..=2).
1013    let mut certs = q.control.certs;
1014    if retire {
1015        data.certs[0] = q.data.certs[1];
1016        certs = certs - b2(1);
1017        if cert_taken {
1018            // Accepted this cycle into the slot that is shifting down.
1019            if q.control.certs == b2(1) {
1020                data.certs[0] = input.certificate;
1021            } else {
1022                data.certs[1] = input.certificate;
1023            }
1024        }
1025    }
1026    if cert_taken {
1027        certs = certs + b2(1);
1028    }
1029    c.certs = certs;
1030
1031    let d = SliceSignerD {
1032        // Read the next key's metadata (one ahead after a burn); write each
1033        // finished key's metadata at its cache slot.
1034        meta_ram: RamIn::<KeyMeta, 9> {
1035            read_addr: if taken { next_slot + b9(1) } else { next_slot },
1036            write: RamWrite::<KeyMeta, 9> {
1037                addr: input.key_done.slot,
1038                value: KeyMeta {
1039                    pk_hash: input.key_done.pk_hash,
1040                    pk_nonce: input.key_done.pk_nonce,
1041                    nonce: input.key_done.nonce,
1042                    batch: input.key_done.batch,
1043                    j: input.key_done.j,
1044                },
1045                enable: input.key_done.valid,
1046            },
1047        },
1048        // Frame buffer: drain reads; packer or header writes.
1049        frames: RamIn::<[b8; 64], 7> {
1050            read_addr: read_address,
1051            write: RamWrite::<[b8; 64], 7> {
1052                addr: write_address,
1053                value: write_value,
1054                enable: write,
1055            },
1056        },
1057        // The arriving node, with the streamed frame's 34-byte prefix.
1058        packer: PackerInput {
1059            prefix: q.control.stream.prefix,
1060            start: node_start,
1061            node,
1062            valid: node_valid,
1063        },
1064        control: c,
1065        data,
1066    };
1067    (
1068        SignerOutput {
1069            request,
1070            message_taken: taken,
1071            certificate_taken: cert_taken,
1072            cache_address,
1073            proof_address,
1074            beat: FrameBeat {
1075                valid: q.control.beat_valid && !resetting,
1076                data: q.data.beat,
1077                last: q.data.beat_last,
1078            },
1079            consumed: q.control.consumed,
1080            frames_out: q.control.frames_out,
1081        },
1082        d,
1083    )
1084}