Skip to main content

rhdl_dsig_scalable_core/ubft/
verifier.rs

1//! The verifying half of [`super::UbftEngine`]: one verify core behind a
2//! request stream.
3//!
4//! # A request
5//!
6//! ```text
7//!   beat 0      header: byte 0 key slot (0..16), bytes 4..8 tag (LE32)
8//!   beats 1..26 the 26-beat frame, exactly as the signer sent it (tlast on 26)
9//! ```
10//!
11//! The header goes into a tag FIFO; the frame's beats go to the
12//! [`VerifyCore`](crate::verify::datapath::VerifyCore) with the key slot
13//! beside beat 0 (the certificate cache needs it there). The core answers in
14//! request order, so the verdict's tag is always the FIFO's head.
15//!
16//! At most [`VERIFY_IN_FLIGHT`] requests are inside at once (the core's
17//! contexts); a header waits while that many are.
18//!
19//! # Session epochs
20//!
21//! A one-time key's frame names its signer's epoch in the certificate. The
22//! host sets each key slot's epoch for this session (`EPOCH{s}`) and the
23//! slots to enforce (`EPOCH_MASK`); a record from any other epoch of that
24//! signer gets FORMAT. Without it, a frame signed in an earlier session
25//! under the same certificate key would still verify.
26//!
27//! # Announced certificates
28//!
29//! A second stream, `s_axis_cert`, takes a peer's batch certificate ahead
30//! of its records: a header beat (byte 0 key slot), the 64-byte
31//! commitment, the 64-byte signature (`tlast`). The verify core looks it up
32//! between records (a warm lookup, see [`crate::verify::certs`]) and starts
33//! its Ed25519 check, so the batch's first record finds it checked.
34//!
35//! A frame whose 26th beat does not carry `tlast`, or that ends early, sets
36//! the framing fault bit. The adapter keeps counting 26 beats, so it stays
37//! aligned with what the core received; the host should treat the session
38//! as broken.
39
40use super::{Axis64, Axis512, KEY_SLOTS, Latency, VERIFY_IN_FLIGHT, latency_kernel};
41use crate::verify::RECORD_BEATS;
42use crate::verify::certs::{CertRequest, CertResult};
43use crate::verify::datapath::{Beat, CertWarm, CoreInput, VerifyCore};
44use rhdl::prelude::*;
45use rhdl_fpga::core::{
46    dff::DFF,
47    ram::synchronous::{In as RamIn, SyncBRAM, Write as RamWrite},
48};
49
50/// The frame's last beat number (25).
51const LAST_BEAT: b5 = b5((RECORD_BEATS - 1) as u128);
52/// The in-flight limit.
53const IN_FLIGHT_LIMIT: b7 = b7(VERIFY_IN_FLIGHT as u128);
54
55/// Verifier inputs.
56#[derive(Clone, Copy, Debug, PartialEq, Digital)]
57pub struct VerifierIn {
58    /// The session runs and the trusted keys are loaded.
59    pub open: bool,
60    /// The free-running tick counter.
61    pub ticks: b64,
62    /// `s_axis_verify`.
63    pub request: Axis512,
64    /// `m_axis_verdict` `tready`.
65    pub verdict_ready: bool,
66    /// `s_axis_cert`: an announced certificate.
67    pub cert: Axis512,
68    /// The Ed25519 core took the presented certificate request.
69    pub cert_taken: bool,
70    /// An Ed25519 answer.
71    pub cert_result: CertResult,
72    /// Each key slot's epoch this session (`EPOCH{s}`).
73    pub epochs: [b64; KEY_SLOTS],
74    /// Key slots whose epoch is enforced (`EPOCH_MASK`).
75    pub epoch_mask: b8,
76}
77
78/// Verifier outputs.
79#[derive(Clone, Copy, Debug, PartialEq, Digital)]
80pub struct VerifierOut {
81    /// `s_axis_verify` `tready`.
82    pub request_ready: bool,
83    /// `s_axis_cert` `tready`.
84    pub cert_ready: bool,
85    /// `m_axis_verdict`.
86    pub verdict: Axis64,
87    /// A certificate check for the Ed25519 core.
88    pub cert_request: CertRequest,
89    /// Verdicts sent.
90    pub verified: b32,
91    /// Verdicts that were valid.
92    pub valid: b32,
93    /// Certificate checks issued.
94    pub cert_checks: b32,
95    /// Certificate checks failed.
96    pub cert_fails: b32,
97    /// A framing error on the request stream (sticky).
98    pub framing_fault: bool,
99    /// Isolated-request latency.
100    pub latency: Latency,
101}
102
103/// Framing, the tag FIFO pointers, the verdict register, the announced
104/// certificate and counters.
105#[derive(Clone, Copy, Debug, PartialEq, Digital)]
106#[doc(hidden)]
107pub struct VerifierState {
108    /// The announced certificate being collected or presented.
109    warm: CertWarm,
110    /// Its beats taken so far (0 header, 1 commitment, 2 signature).
111    warm_beat: b2,
112    /// The next beat is a frame beat (else a header).
113    in_frame: bool,
114    /// Frame beats taken of the current request.
115    beat: b5,
116    /// The current request's key slot.
117    slot: b4,
118    /// Tag FIFO write pointer.
119    tail: b6,
120    /// Tag FIFO read pointer.
121    head: b6,
122    /// Requests inside (headers taken, verdicts not sent).
123    in_flight: b7,
124    /// The verdict register holds a verdict.
125    out_valid: bool,
126    /// The verdict word.
127    out_data: b64,
128    /// Verdicts sent.
129    verified: b32,
130    /// Valid verdicts sent.
131    valid: b32,
132    /// Framing fault.
133    framing_fault: bool,
134    /// Latency statistics.
135    latency: Latency,
136}
137
138/// The verifying half.
139#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
140pub struct UbftVerifier<const TABLE_MASK: usize> {
141    /// Framing, FIFO pointers, counters.
142    state: DFF<VerifierState>,
143    /// Header words (`slot << 32 | tag`), in request order.
144    tags: SyncBRAM<b40, 6>,
145    /// The verify core.
146    core: VerifyCore<TABLE_MASK>,
147}
148
149impl Default for VerifierState {
150    /// Nothing in flight.
151    fn default() -> Self {
152        Self {
153            warm: CertWarm::none(),
154            warm_beat: b2(0),
155            in_frame: false,
156            beat: b5(0),
157            slot: b4(0),
158            tail: b6(0),
159            head: b6(0),
160            in_flight: b7(0),
161            out_valid: false,
162            out_data: b64(0),
163            verified: b32(0),
164            valid: b32(0),
165            framing_fault: false,
166            latency: Latency::default(),
167        }
168    }
169}
170
171impl<const TABLE_MASK: usize> Default for UbftVerifier<TABLE_MASK> {
172    /// Nothing in flight.
173    fn default() -> Self {
174        Self {
175            state: DFF::new(VerifierState::default()),
176            tags: SyncBRAM::default(),
177            core: VerifyCore::default(),
178        }
179    }
180}
181
182impl<const TABLE_MASK: usize> SynchronousIO for UbftVerifier<TABLE_MASK> {
183    type I = VerifierIn;
184    type O = VerifierOut;
185    type Kernel = ubft_verifier_kernel<TABLE_MASK>;
186}
187
188/// The verifier's logic.
189#[allow(clippy::needless_range_loop)]
190#[kernel]
191pub fn ubft_verifier_kernel<const TABLE_MASK: usize>(
192    _clock_reset: ClockReset,
193    input: VerifierIn,
194    q: UbftVerifierQ<TABLE_MASK>,
195) -> (VerifierOut, UbftVerifierD<TABLE_MASK>) {
196    let mut s = q.state;
197    let req = input.request;
198
199    // ------------------------------------------------ request framing
200    let header_ready = input.open && q.state.in_flight < IN_FLIGHT_LIMIT;
201    let request_ready = if q.state.in_frame { q.core.beat_ready } else { header_ready };
202    let take = req.valid && request_ready;
203    let header = take && !q.state.in_frame;
204    let frame_beat = take && q.state.in_frame;
205    let mut write = RamWrite::<b40, 6> {
206        addr: q.state.tail,
207        value: b40(0),
208        enable: false,
209    };
210    if header {
211        let slot: b4 = (req.data[0] & b8(15)).resize();
212        let tag: b40 = req.data[4].resize::<40>()
213            | (req.data[5].resize::<40>() << 8)
214            | (req.data[6].resize::<40>() << 16)
215            | (req.data[7].resize::<40>() << 24);
216        write = RamWrite::<b40, 6> {
217            addr: q.state.tail,
218            value: tag | (slot.resize::<40>() << 32),
219            enable: true,
220        };
221        s.tail = q.state.tail + b6(1);
222        s.slot = slot;
223        s.in_frame = true;
224        s.beat = b5(0);
225        if req.last {
226            s.framing_fault = true;
227        }
228    }
229    if frame_beat {
230        let last_beat = q.state.beat == LAST_BEAT;
231        if req.last != last_beat {
232            s.framing_fault = true;
233        }
234        if last_beat {
235            s.in_frame = false;
236        } else {
237            s.beat = q.state.beat + b5(1);
238        }
239    }
240
241    // ------------------------------------------------ verdicts
242    // The core hands a verdict over when the register is empty; the tag is
243    // the FIFO's head, read every cycle at `head`.
244    if q.state.out_valid && input.verdict_ready {
245        s.out_valid = false;
246        s.verified = q.state.verified + b32(1);
247        if (q.state.out_data & b64(0xff)) == b64(0x80) {
248            s.valid = q.state.valid + b32(1);
249        }
250    }
251    let core_verdict = q.core.verdict.valid && !q.state.out_valid;
252    if core_verdict {
253        let head_word = q.tags;
254        let slot: b64 = (head_word >> 32).resize();
255        let tag: b64 = (head_word & b40(0xffff_ffff)).resize();
256        s.out_valid = true;
257        s.out_data = q.core.verdict.byte.resize::<64>() | (slot << 8) | (tag << 32);
258        s.head = q.state.head + b6(1);
259    }
260    let sent = q.state.out_valid && input.verdict_ready;
261    s.in_flight = q.state.in_flight + (if header { b7(1) } else { b7(0) }) - (if sent { b7(1) } else { b7(0) });
262
263    // ------------------------------------------------ announced certificates
264    // Collect header, commitment, signature; present them to the core
265    // until it takes them.
266    let cert = input.cert;
267    let cert_ready = !q.state.warm.valid;
268    if cert.valid && cert_ready {
269        if q.state.warm_beat == b2(0) {
270            s.warm.slot = (cert.data[0] & b8(15)).resize();
271            s.warm_beat = b2(1);
272        } else if q.state.warm_beat == b2(1) {
273            s.warm.commitment = cert.data;
274            s.warm_beat = b2(2);
275        } else {
276            s.warm.signature = cert.data;
277            s.warm.valid = true;
278            s.warm_beat = b2(0);
279            if !cert.last {
280                s.framing_fault = true;
281            }
282        }
283    }
284    if q.core.warm_taken {
285        s.warm.valid = false;
286    }
287
288    // ------------------------------------------------ latency
289    let begin = header && q.state.in_flight == b7(0);
290    let end = sent && q.state.in_flight == b7(1);
291    let abandon = header && q.state.in_flight != b7(0);
292    s.latency = latency_kernel(q.state.latency, input.ticks, begin, end, abandon);
293
294    // The record's signer's epoch this session, if the host set one.
295    let mut expected_epoch = b64(0);
296    let mut epoch_checked = false;
297    for k in 0..KEY_SLOTS {
298        if q.state.slot == b4(k as u128) {
299            expected_epoch = input.epochs[k];
300            epoch_checked = (input.epoch_mask & (b8(1) << b8(k as u128))) != b8(0);
301        }
302    }
303
304    let d = UbftVerifierD::<TABLE_MASK> {
305        state: s,
306        tags: RamIn::<b40, 6> {
307            read_addr: s.head,
308            write,
309        },
310        core: CoreInput {
311            beat: Beat {
312                valid: req.valid && q.state.in_frame,
313                data: req.data,
314            },
315            slot: q.state.slot,
316            check_epoch: epoch_checked,
317            expected_epoch,
318            warm: q.state.warm,
319            verdict_ready: !q.state.out_valid,
320            cert_taken: input.cert_taken,
321            cert_result: input.cert_result,
322        },
323    };
324    (
325        VerifierOut {
326            request_ready,
327            cert_ready,
328            verdict: Axis64 {
329                valid: q.state.out_valid,
330                data: q.state.out_data,
331            },
332            cert_request: q.core.cert_request,
333            verified: q.state.verified,
334            valid: q.state.valid,
335            cert_checks: q.core.cert_checks,
336            cert_fails: q.core.cert_fails,
337            framing_fault: q.state.framing_fault,
338            latency: q.state.latency,
339        },
340        d,
341    )
342}