rhdl_dsig_scalable_core/cert_unit.rs
1//! The engine's only shared logic: the Merkle top and the Ed25519 certificate.
2//!
3//! # The problem
4//!
5//! Every batch of 128 one-time keys needs one *certificate*: an Ed25519
6//! signature over the batch's Merkle root, so that a verifier who trusts the
7//! signer's Ed25519 public key can trust all 128 keys. The keys of a batch are
8//! spread over all the slices, and each slice can only hash its own
9//! [subtree](crate::slice_merkle). Somebody has to join the subtree roots into
10//! the batch root and get it signed. That is this unit, and it is the only
11//! piece of logic all slices share.
12//!
13//! ```text
14//! slice 0 ─ subtree root r0 ─┐ ┌─▶ slice 0: certificate
15//! slice 1 ─ subtree root r1 ─┤ Merkle top ──▶ Ed25519 ──▶ ├─▶ slice 1: certificate
16//! … ├─▶ (this unit) (this unit) │ …
17//! slice 7 ─ subtree root r7 ─┘ └─▶ slice 7: certificate
18//! ```
19//!
20//! # Joining the subtrees
21//!
22//! Each [slice](crate::slice) builds one subtree of every batch (see
23//! [`KEYS_PER_SLICE`](crate::KEYS_PER_SLICE)). This unit joins the
24//! [`SLICES`](crate::SLICES) subtree roots into the batch root, using the
25//! classic array ("heap") layout of a binary tree:
26//!
27//! ```text
28//! tree[1] root (index 1 is the root)
29//! tree[2], tree[3] ┌──┴──┐ parent i = H(tree[2i] ‖ tree[2i+1])
30//! … children of i are 2i and 2i + 1
31//! tree[SLICES + s] slice roots r0 … r(SLICES−1)
32//! ```
33//!
34//! For the 8-slice build that is 4 + 2 + 1 compressions (levels 5, 6, 7); for
35//! 2 slices it is a single one. They run one tree level at a time.
36//!
37//! ```text
38//! 8-slice heap, by index
39//! [1] batch root
40//! [2] [3]
41//! [4] [5] [6] [7]
42//! [8] [9] [10] [11] [12] [13] [14] [15]
43//! r0 r1 r2 r3 r4 r5 r6 r7
44//! ```
45//!
46//! The heap layout makes "my sibling" and "my parent" plain bit operations
47//! (`i ^ 1`, `i >> 1`), which is what hardware wants.
48//!
49//! # Signing and handing out
50//!
51//! ```text
52//! envelope DSIGFP01 ‖ 1 ‖ 128 ‖ epoch ‖ batch ‖ root ──▶ Ed25519 ──▶ signature
53//! ```
54//!
55//! Every slice then receives the same commitment and signature, plus the
56//! [`TOP_LEVELS`](crate::TOP_LEVELS) siblings above *its* subtree: at level
57//! `t`, `tree[((SLICES + s) >> t) ^ 1]` (the other child of its ancestor).
58//! With the siblings inside its own subtree, a signature carries the full
59//! 7-sibling inclusion proof.
60//!
61//! # Why it is pipelined
62//!
63//! The Ed25519 core is barrel-threaded: one signature takes about 25,000
64//! cycles from request to result, but with ~32 requests in flight it
65//! completes one every ~895 cycles (the vendored `Ed25519SignCoreSmall`; the
66//! earlier 1,1,1,0 profile managed ~758). One certificate covers a batch of
67//! 128 keys, so 30M signatures/s at 205 MHz needs one every
68//! 128 × 205 M / 30 M ≈ 875 cycles: right at the core's rate, which is why
69//! this unit must keep many batches in the core at once:
70//!
71//! ```text
72//! roots ─▶ Merkle front ──request(tag = batch)──▶ Ed25519 core (≤ 32 in flight)
73//! │ ~100 cycles/batch │ results, any order
74//! ▼ ▼
75//! records[batch mod 32] signatures[tag mod 32], done bit
76//! (8 roots, 6 inner nodes, envelope) │
77//! └──────────────┬──────────────────────────┘
78//! ▼
79//! distribution, strictly in batch order ──▶ eight slices
80//! ```
81//!
82//! (*Barrel-threaded* means the core interleaves many independent signatures
83//! through one datapath, so it is slow per request but fast in total. The
84//! planner, [`crate::config`], uses the same measured ~895 cycles per
85//! signature with ≥ 32 in flight.)
86//!
87//! The front never waits for a signature, only for a free ring entry. A
88//! completion is accepted only if its tag names a requested, not yet completed
89//! batch; anything else is a sticky fault (the core's contract: reject unknown
90//! or reused tags). [`IN_FLIGHT`] = 32 matches both the core's credits and the
91//! slices' 32-batch key caches. `busy_cycles` counts cycles with any batch
92//! inside the unit.
93//!
94//! # Tags and in-order delivery
95//!
96//! Each request carries its relative batch number as a *tag*, and the core
97//! returns the tag with the signature. Results may come back out of order, so
98//! the unit files each signature by `tag mod 32` and sets a done bit. The
99//! slices, however, spend keys strictly in order, so certificates must leave
100//! in order. Distribution therefore waits for the done bit of the oldest
101//! undistributed batch, `dist`, even if younger batches are already done.
102//!
103//! ```text
104//! ring entry: 0 1 2 3 4 … dist = 1, front = 5
105//! batch: - b1 b2 b3 b4 (b0 already distributed)
106//! done bit: - 0 1 1 0 b2, b3 signed early: they wait
107//! ▲ distribution waits here until b1's signature returns
108//! accepted tag = a batch in [dist, front) whose done bit is still 0
109//! ```
110//!
111//! Each slice offers at most two subtree roots ahead (the root queue of each slice holds two);
112//! roots arrive in batch order because each slice finishes its keys in order.
113
114use crate::TOP_LEVELS;
115use crate::blake3_core::{CompressionInput, InlineCompressionPipeline, SINGLE_BLOCK_ROOT};
116use crate::certificate::batch_commitment_words;
117use crate::ed25519::{CertificateSigner, CertificateSignerInput, ScalarWords};
118use crate::slice_factory::{IV_BITS, bytes_from_words_kernel, words_from_bytes_kernel};
119use crate::slice_merkle::SubtreeRoot;
120use crate::slice_signer::Certificate;
121use rhdl::prelude::*;
122use rhdl_fpga::core::{
123 dff::DFF,
124 ram::synchronous::{In as RamIn, SyncBRAM, Write as RamWrite},
125};
126
127/// Slices joined by this unit (the crate-wide area knob).
128pub use crate::SLICES;
129/// Heap slots of the Merkle top: index 0 unused, `1..SLICES` inner nodes,
130/// `SLICES..2·SLICES` slice roots.
131pub const TREE_NODES: usize = 2 * SLICES;
132/// Heap index of the first node of the bottom inner level.
133///
134/// That level has `SLICES / 2` nodes, at heap indices `SLICES/2 .. SLICES`.
135const BOTTOM_LEVEL: b4 = b4((SLICES / 2) as u128);
136
137/// Flags for a 64-byte single-block root hash.
138pub const FLAGS_SINGLE_BLOCK: b32 = b32(SINGLE_BLOCK_ROOT as u128);
139
140/// Configuration, stable while running.
141#[derive(Clone, Copy, Debug, PartialEq, Digital)]
142pub struct CertConfig {
143 /// Durably allocated epoch.
144 pub epoch: b64,
145 /// Batch id of relative batch 0.
146 pub first_batch: b64,
147 /// Secret Ed25519 seed.
148 pub key_seed: ScalarWords,
149 /// Load the key and certify batches.
150 pub run: bool,
151}
152
153/// Inputs from the slices.
154#[derive(Clone, Copy, Debug, PartialEq, Digital)]
155pub struct CertInput {
156 /// Configuration.
157 pub config: CertConfig,
158 /// Subtree-root pulses, one per slice.
159 pub roots: [SubtreeRoot; SLICES],
160 /// Each slice accepted the offered certificate this cycle.
161 pub taken: [bool; SLICES],
162}
163
164/// Outputs to the slices.
165#[derive(Clone, Copy, Debug, PartialEq, Digital)]
166pub struct CertOutput {
167 /// Certificate offered to each slice (held until taken).
168 pub certificates: [Certificate; SLICES],
169 /// Ed25519 public key once loaded.
170 pub public_key: ScalarWords,
171 /// The Ed25519 key is loaded.
172 pub key_loaded: bool,
173 /// Batches fully certified and distributed.
174 pub certified: b32,
175 /// Batches whose subtree roots have left the root queues (= batches
176 /// sent to the Ed25519 core). Slices use it as root credit: a slice may
177 /// work on batch `b` only while `b < roots_popped + ROOT_QUEUE`, so no
178 /// root ever arrives at a full queue.
179 pub roots_popped: b32,
180 /// Cycles spent with a batch in progress (Merkle, signing, distribution).
181 pub busy_cycles: b64,
182 /// A root arrived for a full queue, or the signer reported an error.
183 pub fault: bool,
184}
185
186/// Phase of the Merkle front (one batch at a time, about 100 cycles each).
187#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
188pub enum Phase {
189 /// Waiting for every slice's root of the next batch.
190 #[default]
191 Idle,
192 /// Compressions of one tree level in flight (see `CertControl::level`).
193 Hashing,
194 /// Offering the signing request until the core accepts it.
195 Sign,
196}
197
198/// Batches that may be between "signature requested" and "distributed".
199///
200/// Equal to the Ed25519 core's admission credits (32 for profile 1,1,1,0) and
201/// to the slices' cache depth in batches, so neither side is the bottleneck.
202pub const IN_FLIGHT: usize = 32;
203
204/// Subtree roots each slice may have waiting in the cert unit's root queue.
205///
206/// The queue is two deep per slice. Before this credit existed, a slice
207/// offered roots as fast as it made keys (up to 8 batches ahead), so while
208/// the Ed25519 core was still loading its key a third root met a full queue,
209/// was dropped and set the sticky fault. The first card run stopped at 6 of
210/// 8 batches that way (2026-10-14). Slices now wait for credit
211/// ([`CertOutput::roots_popped`]).
212pub const ROOT_QUEUE: usize = 2;
213
214/// Batches the front may have outstanding: [`IN_FLIGHT`], or 1 with the
215/// measurement-only `cert-serial` feature, which reproduces the earlier
216/// one-batch-at-a-time unit so its cost can be measured (see the crate page,
217/// "How it got fast").
218pub const MAX_OUTSTANDING: usize = if cfg!(feature = "cert-serial") { 1 } else { IN_FLIGHT };
219
220/// Everything about one batch except its signature, kept until distribution.
221///
222/// Written into the `records` RAM at `batch mod 32` when the signing request
223/// is issued, read back when the batch is distributed.
224#[derive(Clone, Copy, Debug, PartialEq, Digital)]
225pub struct CertRecord {
226 /// The batch's Merkle top in heap layout (slice roots included).
227 pub tree: [[b8; 32]; TREE_NODES],
228 /// Signed envelope bytes.
229 pub commitment: [b8; 64],
230}
231
232/// Resettable control.
233#[derive(Clone, Copy, Debug, PartialEq, Digital)]
234pub struct CertControl {
235 /// Merkle front phase.
236 pub phase: Phase,
237 /// Roots queued per slice (0..=2).
238 pub queued: [b2; SLICES],
239 /// Heap index of the first node of the level being hashed; that level
240 /// has exactly this many nodes (`SLICES/2`, …, 2, 1).
241 pub level: b4,
242 /// Compressions issued in the current level.
243 pub issued: b4,
244 /// Results returned in the current level.
245 pub returned: b4,
246 /// The key-load request was accepted.
247 pub key_requested: bool,
248 /// Relative batch in the Merkle front (= batches requested so far).
249 pub front: b32,
250 /// Relative batch next to distribute (= batches certified so far).
251 pub dist: b32,
252 /// Signature received, per ring entry `batch mod IN_FLIGHT`.
253 pub done: [bool; IN_FLIGHT],
254 /// Distribution step: 0 wait for signature, 1 RAM read, 2 offering.
255 pub stage: b2,
256 /// Slices that took the current certificate.
257 pub delivered: [bool; SLICES],
258 /// Busy-cycle counter.
259 pub busy: b64,
260 /// Sticky fault.
261 pub fault: bool,
262}
263
264/// Resetless datapath of the Merkle front.
265#[derive(Clone, Copy, Debug, PartialEq, Digital)]
266pub struct CertData {
267 /// Two queued roots per slice, oldest in index 0.
268 pub roots: [[[b8; 32]; 2]; SLICES],
269 /// Merkle top of the batch in the front (heap layout).
270 pub tree: [[b8; 32]; TREE_NODES],
271 /// Batch root.
272 pub root: [b8; 32],
273 /// Signed envelope bytes.
274 pub commitment: [b8; 64],
275}
276
277/// The shared certificate unit.
278///
279/// One BLAKE3 lane for the Merkle top, the Ed25519 signer, two 32-entry
280/// rings (records and signatures) and the control/data registers.
281#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
282pub struct CertUnit {
283 /// Pipelined BLAKE3 compression for the Merkle top; results come back
284 /// with the heap index as tag.
285 lane: InlineCompressionPipeline,
286 /// The Ed25519 certificate signer (vendored RHDL core, see
287 /// [`crate::ed25519`]).
288 signer: CertificateSigner,
289 /// Per-batch Merkle top and envelope, indexed by `batch mod 32`.
290 records: SyncBRAM<CertRecord, 5>,
291 /// Per-batch Ed25519 signature, indexed by `tag mod 32`.
292 signatures: SyncBRAM<[b8; 64], 5>,
293 /// Phases, counters, done bits (reset to idle).
294 control: DFF<CertControl>,
295 /// Queued roots and the front's tree (no reset).
296 data: DFF<CertData>,
297}
298
299impl Default for CertUnit {
300 /// Idle front, nothing requested or distributed, key not loaded.
301 fn default() -> Self {
302 Self {
303 lane: InlineCompressionPipeline,
304 signer: CertificateSigner::default(),
305 records: SyncBRAM::default(),
306 signatures: SyncBRAM::default(),
307 control: DFF::new(CertControl {
308 phase: Phase::Idle,
309 queued: [b2(0); SLICES],
310 level: b4(0),
311 issued: b4(0),
312 returned: b4(0),
313 key_requested: false,
314 front: b32(0),
315 dist: b32(0),
316 done: [false; IN_FLIGHT],
317 stage: b2(0),
318 delivered: [false; SLICES],
319 busy: b64(0),
320 fault: false,
321 }),
322 data: DFF::resetless(),
323 }
324 }
325}
326
327/// Input: subtree roots, slice acknowledgements and configuration. Output:
328/// one certificate offer per slice, plus status.
329impl SynchronousIO for CertUnit {
330 type I = CertInput;
331 type O = CertOutput;
332 type Kernel = cert_unit_kernel;
333}
334
335/// Little-endian bytes of eight 64-bit words.
336///
337/// Turns the Ed25519 core's word-oriented envelope and signature into the
338/// 64 bytes that go into the frame. Combinational.
339#[allow(clippy::needless_range_loop)]
340#[kernel]
341pub fn words64_to_bytes_kernel(words: [b64; 8]) -> [b8; 64] {
342 let mut bytes = [b8(0); 64];
343 for w in 0..8 {
344 for b in 0..8 {
345 bytes[8 * w + b] = (words[w] >> (8 * b as u128)).resize();
346 }
347 }
348 bytes
349}
350
351/// A single-block BLAKE3 root hash of `left ‖ right`.
352///
353/// Builds the lane request for one Merkle-top node: IV key, 64-byte block,
354/// counter 0, single-block root flags. `tag` (the heap index) comes back
355/// with the result; `valid` says whether the request is real. Combinational.
356#[allow(clippy::needless_range_loop)]
357#[kernel]
358pub fn pair_request_kernel(
359 left: [b8; 32],
360 right: [b8; 32],
361 tag: b32,
362 valid: bool,
363) -> CompressionInput {
364 let mut bytes = [b8(0); 64];
365 for b in 0..32 {
366 bytes[b] = left[b];
367 bytes[32 + b] = right[b];
368 }
369 CompressionInput {
370 cv: IV_BITS,
371 block: words_from_bytes_kernel(bytes),
372 counter_low: b32(0),
373 counter_high: b32(0),
374 block_len: b32(64),
375 flags: FLAGS_SINGLE_BLOCK,
376 tag,
377 valid,
378 }
379}
380
381/// The certificate unit's wiring.
382///
383/// Four independent machines share this kernel, connected only through
384/// counters and the two RAMs:
385///
386/// 1. **root queues**: up to two roots per slice;
387/// 2. **Merkle front** (`phase`): Idle → Hashing (one level at a time, one
388/// compression per cycle) → Sign; one batch at a time;
389/// 3. **Ed25519 issue and completion**: request with `tag = front`, file
390/// results by tag, check every tag;
391/// 4. **distribution** (`stage`): wait for the oldest batch's signature,
392/// read both RAMs, offer to all slices until each has taken it.
393///
394/// Timing: the RAMs answer one cycle after their address; the lane and the
395/// Ed25519 core return results many cycles later, tagged.
396#[allow(clippy::too_many_lines, clippy::needless_range_loop)]
397#[kernel]
398pub fn cert_unit_kernel(
399 clock_reset: ClockReset,
400 input: CertInput,
401 q: CertUnitQ,
402) -> (CertOutput, CertUnitD) {
403 let resetting = clock_reset.reset.any();
404 // Next-state copies of the registers.
405 let mut c = q.control;
406 let mut data = q.data;
407 let cfg = input.config;
408
409 // ---------------------------------------------------- root queues
410 // Append each arriving root to its slice's 2-entry queue; a third root
411 // would overwrite one still needed, so it is a fault instead.
412 let mut queued = q.control.queued;
413 for s in 0..SLICES {
414 if input.roots[s].valid {
415 if queued[s] == b2(0) {
416 data.roots[s][0] = input.roots[s].root;
417 queued[s] = b2(1);
418 } else if queued[s] == b2(1) {
419 data.roots[s][1] = input.roots[s].root;
420 queued[s] = b2(2);
421 } else {
422 c.fault = true;
423 }
424 }
425 }
426 // Every slice has delivered its root of the next batch.
427 let mut all_ready = true;
428 for s in 0..SLICES {
429 if q.control.queued[s] == b2(0) {
430 all_ready = false;
431 }
432 }
433
434 // ---------------------------------------------------- Merkle top
435 // The lane's current result: tag = heap index, first 32 bytes = node.
436 let result = q.lane;
437 let r_index: b4 = result.tag.resize();
438 let r_bytes = bytes_from_words_kernel(result.words);
439 let mut r_node = [b8(0); 32];
440 for b in 0..32 {
441 r_node[b] = r_bytes[b];
442 }
443 let phase = q.control.phase;
444 // Batches requested from Ed25519 but not yet distributed.
445 let outstanding = q.control.front - q.control.dist;
446 if phase != Phase::Idle || outstanding != b32(0) {
447 c.busy = q.control.busy + b64(1);
448 }
449 // Node `n = level + issued` of the current level reads its two children.
450 let n = q.control.level + q.control.issued;
451 // Children 2n and 2n + 1 (constant-index loop for RHDL).
452 let mut left = q.data.tree[0];
453 let mut right = q.data.tree[0];
454 for k in 1..SLICES {
455 if n == b4(k as u128) {
456 left = q.data.tree[2 * k];
457 right = q.data.tree[2 * k + 1];
458 }
459 }
460 // The lane request for node n; only sent if `valid` is set below.
461 let mut lane = pair_request_kernel(left, right, n.resize(), false);
462 // Set when this batch's roots are no longer needed (request issued).
463 let mut pop_roots = false;
464 if phase == Phase::Idle {
465 if all_ready && cfg.run {
466 // Copy the oldest root of every slice into the heap's leaves.
467 for s in 0..SLICES {
468 data.tree[SLICES + s] = q.data.roots[s][0];
469 }
470 c.phase = Phase::Hashing;
471 c.level = BOTTOM_LEVEL;
472 c.issued = b4(0);
473 c.returned = b4(0);
474 }
475 } else if phase == Phase::Hashing {
476 // Issue one compression per cycle until the whole level is issued.
477 if q.control.issued < q.control.level {
478 lane.valid = true;
479 c.issued = q.control.issued + b4(1);
480 }
481 // Store each result at its heap index.
482 if result.valid {
483 for k in 1..SLICES {
484 if r_index == b4(k as u128) {
485 data.tree[k] = r_node;
486 }
487 }
488 c.returned = q.control.returned + b4(1);
489 // Level complete: either it was the root, or move one level up.
490 if q.control.returned + b4(1) == q.control.level {
491 if q.control.level == b4(1) {
492 // tree[1] is the batch root.
493 data.root = r_node;
494 let batch_id = cfg.first_batch + q.control.front.resize::<64>();
495 data.commitment = words64_to_bytes_kernel(batch_commitment_words(
496 cfg.epoch, batch_id, r_node,
497 ));
498 c.phase = Phase::Sign;
499 } else {
500 c.level = q.control.level >> 1;
501 c.issued = b4(0);
502 c.returned = b4(0);
503 }
504 }
505 }
506 }
507
508 // ---------------------------------------------------- Ed25519 issue
509 // The core keeps ~32 requests in flight and may finish them out of
510 // order, so the front never waits for a signature: it only needs a free
511 // ring entry (fewer than IN_FLIGHT batches undistributed).
512 let signed = q.signer;
513 // Load the secret key once, the first time the core is ready for it.
514 let load_key = cfg.run && !q.control.key_requested && signed.load_key_ready;
515 if load_key {
516 c.key_requested = true;
517 }
518 // A ring entry is free for one more batch.
519 let ring_free = outstanding < b32(MAX_OUTSTANDING as u128);
520 // Offer the signing request; `issued` is the handshake completing.
521 let requesting = phase == Phase::Sign && signed.key_loaded && ring_free && !resetting;
522 let issued = requesting && signed.ready;
523 // Ring entry of the batch in the front: `front mod 32`.
524 let front_entry: b5 = q.control.front.resize();
525 let record = CertRecord {
526 tree: q.data.tree,
527 commitment: q.data.commitment,
528 };
529 if issued {
530 // The record is written at `front_entry` (below); the front is free
531 // for the next batch.
532 c.phase = Phase::Idle;
533 c.front = q.control.front + b32(1);
534 pop_roots = true;
535 }
536 if signed.internal_error {
537 c.fault = true;
538 }
539
540 // ---------------------------------------------------- Ed25519 completion
541 // Accept a tag only if it names a requested, not yet completed batch.
542 // Ring entry the returned signature belongs to.
543 let done_entry: b5 = signed.tag.resize();
544 // Distance from the oldest undistributed batch (wraps, so "too old" and
545 // "never requested" both come out ≥ outstanding).
546 let age: b16 = signed.tag - q.control.dist.resize::<16>();
547 // The entry's done bit is already set: a duplicate result.
548 let mut already = false;
549 for e in 0..IN_FLIGHT {
550 if done_entry == b5(e as u128) && q.control.done[e] {
551 already = true;
552 }
553 }
554 let completion_ok = age < outstanding.resize::<16>() && !already;
555 let completing = signed.valid && completion_ok && !resetting;
556 if signed.valid && !completion_ok {
557 c.fault = true;
558 }
559 if completing {
560 for e in 0..IN_FLIGHT {
561 if done_entry == b5(e as u128) {
562 c.done[e] = true;
563 }
564 }
565 }
566
567 // ---------------------------------------------------- distribution
568 // Strictly in batch order. Both RAMs are always read at `dist mod 32`;
569 // their registered outputs stay stable while that entry is offered.
570 let dist_entry: b5 = q.control.dist.resize();
571 // The oldest undistributed batch has its signature.
572 let mut head_done = false;
573 for e in 0..IN_FLIGHT {
574 if dist_entry == b5(e as u128) && q.control.done[e] {
575 head_done = true;
576 }
577 }
578 let stage = q.control.stage;
579 if stage == b2(0) && head_done {
580 c.stage = b2(1);
581 } else if stage == b2(1) {
582 // RAM outputs are valid from the next cycle; nobody has it yet.
583 c.stage = b2(2);
584 c.delivered = [false; SLICES];
585 }
586 let offering = stage == b2(2);
587 // The record of batch `dist` (tree + envelope) from the records RAM.
588 let head = q.records;
589 // The same certificate for every slice; only `valid` and `top` differ.
590 let mut certificates = [Certificate {
591 valid: false,
592 batch: q.control.dist,
593 commitment: head.commitment,
594 signature: q.signatures,
595 top: [[b8(0); 32]; TOP_LEVELS],
596 }; SLICES];
597 for s in 0..SLICES {
598 certificates[s].valid = offering && !q.control.delivered[s] && !resetting;
599 // Sibling at top level t: the other child of slice s's ancestor.
600 for t in 0..TOP_LEVELS {
601 certificates[s].top[t] = head.tree[((SLICES + s) >> t) ^ 1];
602 }
603 }
604 if offering {
605 // Track who has taken it; finish when every slice has.
606 let mut all = true;
607 for s in 0..SLICES {
608 let got = q.control.delivered[s] || input.taken[s];
609 c.delivered[s] = got;
610 if !got {
611 all = false;
612 }
613 }
614 if all {
615 // Batch distributed: free its ring entry for a new batch.
616 c.stage = b2(0);
617 c.dist = q.control.dist + b32(1);
618 for e in 0..IN_FLIGHT {
619 if dist_entry == b5(e as u128) {
620 c.done[e] = false;
621 }
622 }
623 }
624 }
625 // Drop each slice's oldest root (the one just requested).
626 if pop_roots {
627 for s in 0..SLICES {
628 data.roots[s][0] = data.roots[s][1];
629 queued[s] = queued[s] - b2(1);
630 }
631 }
632 c.queued = queued;
633
634 // Absolute batch id of the batch in the front.
635 let batch_id = cfg.first_batch + q.control.front.resize::<64>();
636 let d = CertUnitD {
637 lane,
638 // Signing request for the front batch, tagged with its relative
639 // batch number.
640 signer: CertificateSignerInput {
641 load_key_valid: load_key,
642 key_seed: cfg.key_seed,
643 clear_key: false,
644 valid: requesting,
645 epoch: cfg.epoch,
646 batch_id,
647 root: q.data.root,
648 tag: q.control.front.resize(),
649 },
650 // Write the front's record on issue; read the distribution head.
651 records: RamIn::<CertRecord, 5> {
652 read_addr: dist_entry,
653 write: RamWrite::<CertRecord, 5> {
654 addr: front_entry,
655 value: record,
656 enable: issued,
657 },
658 },
659 // File each accepted signature by its tag; read the distribution head.
660 signatures: RamIn::<[b8; 64], 5> {
661 read_addr: dist_entry,
662 write: RamWrite::<[b8; 64], 5> {
663 addr: done_entry,
664 value: words64_to_bytes_kernel(signed.signature.words),
665 enable: completing,
666 },
667 },
668 control: c,
669 data,
670 };
671 (
672 CertOutput {
673 certificates,
674 public_key: signed.public_key,
675 key_loaded: signed.key_loaded,
676 certified: q.control.dist,
677 roots_popped: q.control.front,
678 busy_cycles: q.control.busy,
679 fault: q.control.fault,
680 },
681 d,
682 )
683}