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}