Skip to main content

rhdl_dsig_scalable_core/ubft/
signer.rs

1//! The signing half of [`super::UbftEngine`]: a stream in front of the
2//! batch engine's slices and certificate unit.
3//!
4//! # What changes against [`crate::engine`]
5//!
6//! | Batch engine | Here |
7//! |---|---|
8//! | [`crate::msg_reader`] reads messages from HBM and deals message `n` to slice `n mod SLICES` | the **dealer** takes one request beat at a time and gives it to the first idle slice |
9//! | one [`crate::hbm_writer`] per slice writes frames to HBM | the **arbiter** sends whole frames, one slice at a time, on one stream |
10//! | the run ends after `count` frames | the session ends after `sign_count` frames |
11//!
12//! The slices, the certificate unit and every register on the links between
13//! them ([`crate::relay`]) are the batch engine's, unchanged.
14//!
15//! # The dealer
16//!
17//! Each slice has a one-message buffer. The request stream is ready while
18//! any buffer is empty; a request goes to the first empty buffer at or after
19//! a rotating pointer, so a lone request never waits behind a busy slice.
20//! A buffer empties when the slice's message link takes it.
21//!
22//! # The certificate announce
23//!
24//! Each batch certificate (the 64-byte commitment and its Ed25519
25//! signature) is copied, as the certificate unit hands it to slice 0, to the
26//! `m_axis_cert` stream: commitment beat, then signature beat (`tlast`).
27//! uBFT sends it to the peers ahead of the batch's signatures, so their
28//! verifiers check it before the first one arrives (see
29//! [`super::verifier`]). The copy is best-effort: if the previous one is
30//! still waiting, the new one is skipped and that batch's first record
31//! simply checks its certificate itself.
32//!
33//! # The arbiter
34//!
35//! A frame is 26 beats. The arbiter picks a slice whose beat pipe offers a
36//! beat, sends that slice's beats until the one with `last`, then picks
37//! again (round robin), so frames never interleave on the stream.
38
39use super::{Axis512, Latency, latency_kernel};
40use crate::cert_unit::{CertConfig, CertInput, CertUnit, SLICES};
41use crate::ed25519::ScalarWords;
42use crate::engine::{SliceControl, SliceReport};
43use crate::relay::{Delay, PipeIn, RelayPipe, ToggleLink};
44use crate::slice::{Slice, SliceInput};
45use crate::slice_factory::{FactoryConfig, TWO_BLOCK_MESSAGE};
46use crate::slice_signer::{Certificate, FrameBeat, Message};
47use rhdl::core::ReplicatedSynchronous;
48use rhdl::prelude::*;
49use rhdl_fpga::core::dff::DFF;
50
51/// The session's signing configuration (host arguments).
52#[derive(Clone, Copy, Debug, PartialEq, Digital)]
53pub struct SignConfig {
54    /// Derive-key prefix CV (secret).
55    pub prefix_cv: [b32; 8],
56    /// Epoch.
57    pub epoch: b64,
58    /// First batch id.
59    pub first_batch: b64,
60    /// Batches to make.
61    pub batches: b32,
62    /// Ed25519 seed as four little-endian words (secret).
63    pub seed: [b64; 4],
64}
65
66/// Signer inputs.
67#[derive(Clone, Copy, Debug, PartialEq, Digital)]
68pub struct SignerIn {
69    /// The configuration (static while running).
70    pub config: SignConfig,
71    /// The session runs.
72    pub run: bool,
73    /// The free-running tick counter.
74    pub ticks: b64,
75    /// `s_axis_sign`.
76    pub request: Axis512,
77    /// `m_axis_frame` `tready`.
78    pub frame_ready: bool,
79    /// `m_axis_cert` `tready`.
80    pub cert_ready: bool,
81}
82
83/// Signer outputs.
84#[derive(Clone, Copy, Debug, PartialEq, Digital)]
85pub struct SignerOut {
86    /// `s_axis_sign` `tready`.
87    pub request_ready: bool,
88    /// `m_axis_frame`.
89    pub frame: Axis512,
90    /// `m_axis_cert`: the announce of each new batch certificate.
91    pub cert: Axis512,
92    /// Certificates announced.
93    pub announced: b32,
94    /// Requests accepted.
95    pub taken: b32,
96    /// Frames sent.
97    pub signed: b32,
98    /// Batches certified.
99    pub certified: b32,
100    /// The Ed25519 public key derived from the seed.
101    pub public_key: ScalarWords,
102    /// A slice fault.
103    pub slice_fault: bool,
104    /// A certificate unit fault.
105    pub cert_fault: bool,
106    /// Isolated-request latency.
107    pub latency: Latency,
108}
109
110/// Dealer, arbiter and counters.
111#[derive(Clone, Copy, Debug, PartialEq, Digital)]
112#[doc(hidden)]
113pub struct SignerState {
114    /// Each slice's one-message buffer.
115    pending: [Message; SLICES],
116    /// Rotating dealer pointer.
117    next: b3,
118    /// The arbiter is sending `owner`'s frame.
119    locked: bool,
120    /// Whose frame is on the stream.
121    owner: b3,
122    /// Rotating arbiter pointer.
123    next_owner: b3,
124    /// Requests accepted.
125    taken: b32,
126    /// Frames sent.
127    signed: b32,
128    /// Requests accepted whose frame has not left.
129    in_flight: b32,
130    /// Latency statistics.
131    latency: Latency,
132    /// An announce is waiting to leave.
133    ann_valid: bool,
134    /// Its second (signature) beat is next.
135    ann_second: bool,
136    /// The announced commitment.
137    ann_commitment: [b8; 64],
138    /// The announced signature.
139    ann_signature: [b8; 64],
140    /// Certificates announced.
141    announced: b32,
142}
143
144/// Profile 2's signed context in the sign beat (bytes 8..56; 0 in profile 1).
145const CONTEXT_BYTES: usize = if TWO_BLOCK_MESSAGE { 48 } else { 0 };
146
147impl Default for SignerState {
148    /// Empty buffers, nothing in flight.
149    fn default() -> Self {
150        Self {
151            pending: [Message {
152                valid: false,
153                bytes: [b8(0); crate::MESSAGE_BYTES],
154                index: b64(0),
155            }; SLICES],
156            next: b3(0),
157            locked: false,
158            owner: b3(0),
159            next_owner: b3(0),
160            taken: b32(0),
161            signed: b32(0),
162            in_flight: b32(0),
163            latency: Latency::default(),
164            ann_valid: false,
165            ann_second: false,
166            ann_commitment: [b8(0); 64],
167            ann_signature: [b8(0); 64],
168            announced: b32(0),
169        }
170    }
171}
172
173/// The signing half.
174#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
175pub struct UbftSigner<const TABLE_MASK: usize> {
176    /// Dealer, arbiter, counters.
177    state: DFF<SignerState>,
178    /// The shared Merkle-top and Ed25519 certificate unit.
179    cert: CertUnit,
180    /// The slices.
181    slices: ReplicatedSynchronous<Slice<TABLE_MASK>, SLICES>,
182    /// Frame beats, slice → arbiter.
183    beats: [RelayPipe<FrameBeat>; SLICES],
184    /// Messages, dealer → slice.
185    messages: [ToggleLink<Message>; SLICES],
186    /// Certificates, cert unit → slice.
187    certificates: [ToggleLink<Certificate>; SLICES],
188    /// Configuration, `run` and root credit, signer → slice.
189    to_slice: [Delay<SliceControl>; SLICES],
190    /// Root pulses, status and fault, slice → signer.
191    from_slice: [Delay<SliceReport>; SLICES],
192}
193
194impl<const TABLE_MASK: usize> Default for UbftSigner<TABLE_MASK> {
195    /// Power-on: nothing made, nothing buffered.
196    fn default() -> Self {
197        Self {
198            state: DFF::new(SignerState::default()),
199            cert: CertUnit::default(),
200            slices: ReplicatedSynchronous::new(Slice::default()),
201            beats: core::array::from_fn(|_| RelayPipe::default()),
202            messages: core::array::from_fn(|_| ToggleLink::default()),
203            certificates: core::array::from_fn(|_| ToggleLink::default()),
204            to_slice: core::array::from_fn(|_| Delay::default()),
205            from_slice: core::array::from_fn(|_| Delay::default()),
206        }
207    }
208}
209
210impl<const TABLE_MASK: usize> SynchronousIO for UbftSigner<TABLE_MASK> {
211    type I = SignerIn;
212    type O = SignerOut;
213    type Kernel = ubft_signer_kernel<TABLE_MASK>;
214}
215
216/// The signer's logic.
217#[allow(clippy::needless_range_loop, clippy::too_many_lines)]
218#[kernel]
219pub fn ubft_signer_kernel<const TABLE_MASK: usize>(
220    _clock_reset: ClockReset,
221    input: SignerIn,
222    q: UbftSignerQ<TABLE_MASK>,
223) -> (SignerOut, UbftSignerD<TABLE_MASK>) {
224    let mut s = q.state;
225    let run = input.run;
226
227    // ------------------------------------------------ dealer
228    // A buffer whose message the link took is empty again.
229    let mut pending = q.state.pending;
230    let mut message_taken = [false; SLICES];
231    for i in 0..SLICES {
232        message_taken[i] = q.state.pending[i].valid && q.messages[i].ready;
233        if message_taken[i] {
234            pending[i].valid = false;
235        }
236    }
237    // The first empty buffer at or after `next`, else the first empty one.
238    let mut pick_found = false;
239    let mut pick = b3(0);
240    for j in 0..SLICES {
241        let i = SLICES - 1 - j;
242        if !q.state.pending[i].valid && b3(i as u128) >= q.state.next {
243            pick_found = true;
244            pick = b3(i as u128);
245        }
246    }
247    if !pick_found {
248        for j in 0..SLICES {
249            let i = SLICES - 1 - j;
250            if !q.state.pending[i].valid {
251                pick_found = true;
252                pick = b3(i as u128);
253            }
254        }
255    }
256    let request_ready = run && pick_found;
257    let accept = request_ready && input.request.valid;
258    if accept {
259        let d = input.request.data;
260        // Profile 1: message bytes 0..16, tag 16..24. Profile 2 (sign layout
261        // v2, as Ed25519's): tag 0..8, the 48-byte context 8..56, signed
262        // zero-extended to 64 bytes.
263        let mut bytes = [b8(0); crate::MESSAGE_BYTES];
264        let mut index = b64(0);
265        if TWO_BLOCK_MESSAGE {
266            for k in 0..CONTEXT_BYTES {
267                bytes[k] = d[8 + k];
268            }
269            for k in 0..8 {
270                index = index | (d[k].resize::<64>() << ((8 * k) as u128));
271            }
272        } else {
273            for k in 0..16 {
274                bytes[k] = d[k];
275            }
276            for k in 0..8 {
277                index = index | (d[16 + k].resize::<64>() << ((8 * k) as u128));
278            }
279        }
280        for i in 0..SLICES {
281            if pick == b3(i as u128) {
282                pending[i] = Message {
283                    valid: true,
284                    bytes,
285                    index,
286                };
287            }
288        }
289        s.next = if pick == b3((SLICES - 1) as u128) { b3(0) } else { pick + b3(1) };
290        s.taken = q.state.taken + b32(1);
291    }
292    s.pending = pending;
293
294    // ------------------------------------------------ arbiter
295    let mut frame = Axis512 {
296        valid: false,
297        data: [b8(0); 64],
298        last: false,
299    };
300    let mut beat_taken = [false; SLICES];
301    let mut sent_last = false;
302    if q.state.locked {
303        for i in 0..SLICES {
304            if q.state.owner == b3(i as u128) {
305                frame = Axis512 {
306                    valid: q.beats[i].valid,
307                    data: q.beats[i].data.data,
308                    last: q.beats[i].data.last,
309                };
310                beat_taken[i] = q.beats[i].valid && input.frame_ready;
311            }
312        }
313        if frame.valid && input.frame_ready && frame.last {
314            sent_last = true;
315            s.locked = false;
316            s.signed = q.state.signed + b32(1);
317        }
318    } else {
319        let mut found = false;
320        let mut owner = b3(0);
321        for j in 0..SLICES {
322            let i = SLICES - 1 - j;
323            if q.beats[i].valid && b3(i as u128) >= q.state.next_owner {
324                found = true;
325                owner = b3(i as u128);
326            }
327        }
328        if !found {
329            for j in 0..SLICES {
330                let i = SLICES - 1 - j;
331                if q.beats[i].valid {
332                    found = true;
333                    owner = b3(i as u128);
334                }
335            }
336        }
337        if found {
338            s.locked = true;
339            s.owner = owner;
340            s.next_owner = if owner == b3((SLICES - 1) as u128) { b3(0) } else { owner + b3(1) };
341        }
342    }
343
344    // ------------------------------------------------ latency
345    let begin = accept && q.state.in_flight == b32(0);
346    let end = sent_last && q.state.in_flight == b32(1);
347    let abandon = accept && q.state.in_flight != b32(0);
348    s.latency = latency_kernel(q.state.latency, input.ticks, begin, end, abandon);
349    s.in_flight = q.state.in_flight + (if accept { b32(1) } else { b32(0) }) - (if sent_last { b32(1) } else { b32(0) });
350
351    // ------------------------------------------------ certificate announce
352    let cert_out = Axis512 {
353        valid: q.state.ann_valid,
354        data: if q.state.ann_second { q.state.ann_signature } else { q.state.ann_commitment },
355        last: q.state.ann_second,
356    };
357    if q.state.ann_valid && input.cert_ready {
358        if q.state.ann_second {
359            s.ann_valid = false;
360            s.ann_second = false;
361            s.announced = q.state.announced + b32(1);
362        } else {
363            s.ann_second = true;
364        }
365    }
366    // A certificate slice 0 takes this cycle is copied, unless one waits.
367    let fresh = q.cert.certificates[0].valid && q.certificates[0].ready;
368    if fresh && !q.state.ann_valid {
369        s.ann_valid = true;
370        s.ann_second = false;
371        s.ann_commitment = q.cert.certificates[0].commitment;
372        s.ann_signature = q.cert.certificates[0].signature;
373    }
374
375    // ------------------------------------------------ slices and links
376    let cfg = input.config;
377    let mut roots = [q.from_slice[0].root; SLICES];
378    let mut cert_taken = [false; SLICES];
379    let mut slice_fault = false;
380    for i in 0..SLICES {
381        roots[i] = q.from_slice[i].root;
382        cert_taken[i] = q.cert.certificates[i].valid && q.certificates[i].ready;
383        slice_fault = slice_fault || q.from_slice[i].fault;
384    }
385    let mut to_slice = [SliceControl {
386        config: FactoryConfig {
387            prefix_cv: cfg.prefix_cv,
388            epoch: cfg.epoch,
389            first_batch: cfg.first_batch,
390            batches: cfg.batches,
391            slice: b3(0),
392        },
393        run,
394        roots_popped: q.cert.roots_popped,
395    }; SLICES];
396    let mut slices = [SliceInput {
397        config: q.to_slice[0].config,
398        run: q.to_slice[0].run,
399        message: q.messages[0].data,
400        certificate: q.certificates[0].data,
401        beat_ready: q.beats[0].ready,
402        roots_popped: q.to_slice[0].roots_popped,
403    }; SLICES];
404    let mut beats = [PipeIn::<FrameBeat> {
405        data: q.slices[0].beat,
406        valid: q.slices[0].beat.valid,
407        taken: beat_taken[0],
408    }; SLICES];
409    let mut messages = [PipeIn::<Message> {
410        data: q.state.pending[0],
411        valid: q.state.pending[0].valid,
412        taken: q.slices[0].message_taken,
413    }; SLICES];
414    let mut certificates = [PipeIn::<Certificate> {
415        data: q.cert.certificates[0],
416        valid: q.cert.certificates[0].valid,
417        taken: q.slices[0].certificate_taken,
418    }; SLICES];
419    let mut from_slice = [SliceReport {
420        root: q.slices[0].root,
421        keys_started: q.slices[0].keys_started,
422        fault: q.slices[0].fault,
423    }; SLICES];
424    for i in 0..SLICES {
425        to_slice[i].config.slice = b3(i as u128);
426        slices[i] = SliceInput {
427            config: q.to_slice[i].config,
428            run: q.to_slice[i].run,
429            message: q.messages[i].data,
430            certificate: q.certificates[i].data,
431            beat_ready: q.beats[i].ready,
432            roots_popped: q.to_slice[i].roots_popped,
433        };
434        slices[i].message.valid = q.messages[i].valid;
435        slices[i].certificate.valid = q.certificates[i].valid;
436        beats[i] = PipeIn::<FrameBeat> {
437            data: q.slices[i].beat,
438            valid: q.slices[i].beat.valid,
439            taken: beat_taken[i],
440        };
441        messages[i] = PipeIn::<Message> {
442            data: q.state.pending[i],
443            valid: q.state.pending[i].valid,
444            taken: q.slices[i].message_taken,
445        };
446        certificates[i] = PipeIn::<Certificate> {
447            data: q.cert.certificates[i],
448            valid: q.cert.certificates[i].valid,
449            taken: q.slices[i].certificate_taken,
450        };
451        from_slice[i] = SliceReport {
452            root: q.slices[i].root,
453            keys_started: q.slices[i].keys_started,
454            fault: q.slices[i].fault,
455        };
456    }
457    let d = UbftSignerD::<TABLE_MASK> {
458        state: s,
459        cert: CertInput {
460            config: CertConfig {
461                epoch: cfg.epoch,
462                first_batch: cfg.first_batch,
463                key_seed: ScalarWords { words: cfg.seed },
464                run,
465            },
466            roots,
467            taken: cert_taken,
468        },
469        slices,
470        beats,
471        messages,
472        certificates,
473        to_slice,
474        from_slice,
475    };
476    (
477        SignerOut {
478            request_ready,
479            frame,
480            cert: cert_out,
481            announced: q.state.announced,
482            taken: q.state.taken,
483            signed: q.state.signed,
484            certified: q.cert.certified,
485            public_key: q.cert.public_key,
486            slice_fault,
487            cert_fault: q.cert.fault,
488            latency: q.state.latency,
489        },
490        d,
491    )
492}