Skip to main content

rhdl_dsig_scalable_core/ubft/
engine.rs

1//! [`UbftEngine`]: the register file, the trusted-key loader and the
2//! Ed25519 verify core around [`UbftSigner`] and [`UbftVerifier`].
3//!
4//! The register map and the streams are in [`super`].
5//!
6//! # Trusted keys
7//!
8//! The host writes up to [`KEY_SLOTS`] Ed25519 public keys (one per peer
9//! replica) and `key_mask` before start. After start the loader puts each
10//! masked slot's key into the verify core, one at a time (about 108,000
11//! cycles each for the `1,1,1,0` core), and opens the verify stream once
12//! every masked slot has settled. A key that does not decode sets fault bit
13//! 3; records naming that slot then fail their certificate check.
14//!
15//! # Certificate requests
16//!
17//! The verify core asks for one Ed25519 check per new batch certificate.
18//! The request is held in registers in front of the Ed25519 core (its input
19//! logic is deep; see [`crate::verify::engine`]), with the public key of the
20//! request's key slot.
21
22use super::signer::{SignConfig, SignerIn, UbftSigner};
23use super::verifier::{UbftVerifier, VerifierIn};
24use super::*;
25use crate::axi4::{AxiLiteIn, AxiLiteOut};
26use crate::cert_unit::SLICES;
27use crate::engine::set_half_kernel;
28use crate::verify::certs::CertResult;
29use crate::verify::engine::{CertCore, words64_kernel};
30use rhdl::prelude::*;
31use rhdl_ed25519_live::sha_ring::{ShaDone, ShaRingOut};
32use rhdl_ed25519_live::verify_core::VerifyIn;
33use rhdl_ed25519_live::wire::{ScalarWords, VerifyRequest, Words512};
34use rhdl_fpga::core::dff::DFF;
35
36/// A register write from a component that embeds the engine (the same
37/// offsets and rules as an AXI-Lite write; no response).
38#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
39pub struct CfgWrite {
40    /// A write this cycle (taken unless an AXI-Lite write is decoded in the
41    /// same cycle; see [`UbftOutput::cfg_ready`]).
42    pub valid: bool,
43    /// Register offset.
44    pub addr: b12,
45    /// Value.
46    pub data: b32,
47}
48
49/// Kernel inputs.
50#[derive(Clone, Copy, Debug, PartialEq, Digital)]
51pub struct UbftInput {
52    /// Register writes from an embedding component (`valid: false` in the
53    /// Vitis kernel).
54    pub cfg: CfgWrite,
55    /// Host control channel.
56    pub axi: AxiLiteIn,
57    /// `s_axis_sign`.
58    pub sign: Axis512,
59    /// `m_axis_frame` `tready`.
60    pub frame_ready: bool,
61    /// `s_axis_verify`.
62    pub verify: Axis512,
63    /// `m_axis_verdict` `tready`.
64    pub verdict_ready: bool,
65    /// `s_axis_cert`: a peer's announced certificate.
66    pub cert_in: Axis512,
67    /// `m_axis_cert` `tready`.
68    pub cert_out_ready: bool,
69}
70
71/// Kernel outputs.
72#[derive(Clone, Copy, Debug, PartialEq, Digital)]
73pub struct UbftOutput {
74    /// Host control channel.
75    pub axi: AxiLiteOut,
76    /// `s_axis_sign` `tready`.
77    pub sign_ready: bool,
78    /// `m_axis_frame`.
79    pub frame: Axis512,
80    /// `s_axis_verify` `tready`.
81    pub verify_ready: bool,
82    /// `m_axis_verdict`.
83    pub verdict: Axis64,
84    /// `m_axis_cert`: this signer's certificate announce.
85    pub cert_out: Axis512,
86    /// `s_axis_cert` `tready`.
87    pub cert_in_ready: bool,
88    /// The service runs and its trusted keys are loaded.
89    pub ready: bool,
90    /// Any `FAULT` bit is set.
91    pub fault: bool,
92    /// A `cfg` write presented this cycle is taken.
93    pub cfg_ready: bool,
94}
95
96/// Host arguments.
97#[derive(Clone, Copy, Debug, PartialEq, Digital)]
98#[doc(hidden)]
99pub struct UbftArgs {
100    sign_count: b32,
101    verify_count: b32,
102    batches: b32,
103    epoch: b64,
104    first_batch: b64,
105    prefix_cv: [b32; 8],
106    seed: [b32; 8],
107    key_mask: b8,
108    keys: [[b32; 8]; KEY_SLOTS],
109    epochs: [b64; KEY_SLOTS],
110    epoch_mask: b8,
111}
112
113/// The request held in front of the Ed25519 core.
114#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
115#[doc(hidden)]
116pub struct EdHeld {
117    message: [b64; 8],
118    signature: [b64; 8],
119    public_key: [b64; 4],
120    tag: b16,
121}
122
123/// Register file, run state, key loader, the held Ed25519 request.
124#[derive(Clone, Copy, Debug, PartialEq, Digital)]
125#[doc(hidden)]
126pub struct UbftControl {
127    args: UbftArgs,
128    running: bool,
129    done: bool,
130    done_unread: bool,
131    gie: bool,
132    ier: bool,
133    isr: bool,
134    ticks: b64,
135    start_tick: b64,
136    done_tick: b64,
137    key_tick: b64,
138    ticks_hi: b32,
139    /// Key loader: the slot being considered (`KEY_SLOTS` = all done).
140    kl_slot: b5,
141    /// Key loader: a load was issued for `kl_slot` and has not settled.
142    kl_wait: bool,
143    /// Key loader: pulse `load_key_valid` this cycle.
144    kl_pulse: bool,
145    /// Every masked slot settled.
146    keys_settled: bool,
147    /// A trusted key did not decode.
148    key_bad: bool,
149    aw_addr: b12,
150    aw_have: bool,
151    w_data: b32,
152    w_have: bool,
153    b_valid: bool,
154    r_valid: bool,
155    r_data: b32,
156    /// `ed_request` holds a request the Ed25519 core has not taken.
157    ed_valid: bool,
158    ed_request: EdHeld,
159    ed_slot: b4,
160}
161
162impl Default for UbftControl {
163    /// IDLE, zero arguments.
164    fn default() -> Self {
165        Self {
166            args: UbftArgs {
167                sign_count: b32(0),
168                verify_count: b32(0),
169                batches: b32(0),
170                epoch: b64(0),
171                first_batch: b64(0),
172                prefix_cv: [b32(0); 8],
173                seed: [b32(0); 8],
174                key_mask: b8(0),
175                keys: [[b32(0); 8]; KEY_SLOTS],
176                epochs: [b64(0); KEY_SLOTS],
177                epoch_mask: b8(0),
178            },
179            running: false,
180            done: false,
181            done_unread: false,
182            gie: false,
183            ier: false,
184            isr: false,
185            ticks: b64(0),
186            start_tick: b64(0),
187            done_tick: b64(0),
188            key_tick: b64(0),
189            ticks_hi: b32(0),
190            kl_slot: b5(0),
191            kl_wait: false,
192            kl_pulse: false,
193            keys_settled: false,
194            key_bad: false,
195            aw_addr: b12(0),
196            aw_have: false,
197            w_data: b32(0),
198            w_have: false,
199            b_valid: false,
200            r_valid: false,
201            r_data: b32(0),
202            ed_valid: false,
203            ed_request: EdHeld::default(),
204            ed_slot: b4(0),
205        }
206    }
207}
208
209/// The uBFT signature service kernel.
210#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
211pub struct UbftEngine<const TABLE_MASK: usize> {
212    /// Registers, run state, key loader.
213    control: DFF<UbftControl>,
214    /// Signing.
215    signer: UbftSigner<TABLE_MASK>,
216    /// Verifying.
217    verifier: UbftVerifier<TABLE_MASK>,
218    /// Strict Ed25519 verification of peers' batch certificates.
219    ed: CertCore,
220}
221
222impl<const TABLE_MASK: usize> Default for UbftEngine<TABLE_MASK> {
223    /// Power-on: IDLE, no keys.
224    fn default() -> Self {
225        Self {
226            control: DFF::new(UbftControl::default()),
227            signer: UbftSigner::default(),
228            verifier: UbftVerifier::default(),
229            ed: CertCore::default(),
230        }
231    }
232}
233
234impl<const TABLE_MASK: usize> SynchronousIO for UbftEngine<TABLE_MASK> {
235    type I = UbftInput;
236    type O = UbftOutput;
237    type Kernel = ubft_engine_kernel<TABLE_MASK>;
238}
239
240/// `SHAPE`: slices, table mask, key slots.
241const SHAPE_BASE: usize = SLICES | (KEY_SLOTS << 24);
242
243/// One register word of a latency block (`i` = 0 `N`, 1 `LAST`, 2 `MIN`,
244/// 3 `MAX`, 4 `SUM` lo, 5 `SUM` hi).
245#[kernel]
246pub fn latency_word_kernel(l: Latency, i: b3) -> b32 {
247    if i == b3(0) {
248        l.n
249    } else if i == b3(1) {
250        l.last
251    } else if i == b3(2) {
252        l.min
253    } else if i == b3(3) {
254        l.max
255    } else if i == b3(4) {
256        l.sum.resize()
257    } else {
258        (l.sum >> 32).resize()
259    }
260}
261
262/// The kernel's logic.
263#[allow(clippy::needless_range_loop, clippy::too_many_lines)]
264#[kernel]
265pub fn ubft_engine_kernel<const TABLE_MASK: usize>(
266    clock_reset: ClockReset,
267    input: UbftInput,
268    q: UbftEngineQ<TABLE_MASK>,
269) -> (UbftOutput, UbftEngineD<TABLE_MASK>) {
270    let resetting = clock_reset.reset.any();
271    let mut c = q.control;
272    let args = q.control.args;
273    c.ticks = q.control.ticks + b64(1);
274    let ed = q.ed;
275    let sig = q.signer;
276    let ver = q.verifier;
277    c.kl_pulse = false;
278
279    // ------------------------------------------------ progress and faults
280    let fault_bits: b32 = (if sig.slice_fault { b32(1) } else { b32(0) })
281        | (if sig.cert_fault { b32(2) } else { b32(0) })
282        | (if ed.internal_error { b32(4) } else { b32(0) })
283        | (if q.control.key_bad { b32(8) } else { b32(0) })
284        | (if ver.framing_fault { b32(16) } else { b32(0) });
285    let settled_start = q.control.ticks - q.control.start_tick > b64(16);
286    if q.control.running
287        && settled_start
288        && q.control.keys_settled
289        && sig.signed == args.sign_count
290        && ver.verified == args.verify_count
291    {
292        c.running = false;
293        c.done = true;
294        c.done_unread = true;
295        c.isr = true;
296        c.done_tick = q.control.ticks;
297    }
298
299    // ------------------------------------------------ trusted keys
300    // Slot `kl_slot` (0..8): skip unmasked slots, load masked ones one at a
301    // time, and wait for each to settle.
302    let mut kl_key = [b64(0); 4];
303    let mut kl_masked = false;
304    for s in 0..KEY_SLOTS {
305        if q.control.kl_slot == b5(s as u128) {
306            kl_masked = (args.key_mask & (b8(1) << b8(s as u128))) != b8(0);
307            for w in 0..4 {
308                kl_key[w] = args.keys[s][2 * w].resize::<64>() | (args.keys[s][2 * w + 1].resize::<64>() << 32);
309            }
310        }
311    }
312    let mut kl_state = b2(0);
313    for s in 0..KEY_SLOTS {
314        if q.control.kl_slot == b5(s as u128) {
315            kl_state = ed.key_state[s];
316        }
317    }
318    let loading = q.control.running && !q.control.keys_settled && !resetting;
319    if loading {
320        if q.control.kl_slot == b5(KEY_SLOTS as u128) {
321            c.keys_settled = true;
322            c.key_tick = q.control.ticks;
323        } else if !kl_masked {
324            c.kl_slot = q.control.kl_slot + b5(1);
325        } else if !q.control.kl_wait {
326            if ed.load_key_ready && !q.control.kl_pulse && !q.control.ed_valid {
327                c.kl_pulse = true;
328                c.kl_wait = true;
329                c.ed_request.public_key = kl_key;
330            }
331        } else if !q.control.kl_pulse && (kl_state == b2(2) || kl_state == b2(3)) {
332            c.kl_wait = false;
333            c.kl_slot = q.control.kl_slot + b5(1);
334            if kl_state == b2(3) {
335                c.key_bad = true;
336            }
337        }
338    }
339
340    // ------------------------------------------------ certificate requests
341    if q.control.ed_valid && ed.ready {
342        c.ed_valid = false;
343    }
344    let cr = ver.cert_request;
345    let issue = cr.valid && !q.control.ed_valid && q.control.keys_settled && !resetting;
346    if issue {
347        let mut key = [b64(0); 4];
348        for s in 0..KEY_SLOTS {
349            if cr.slot == b4(s as u128) {
350                for w in 0..4 {
351                    key[w] = args.keys[s][2 * w].resize::<64>() | (args.keys[s][2 * w + 1].resize::<64>() << 32);
352                }
353            }
354        }
355        c.ed_valid = true;
356        c.ed_slot = cr.slot;
357        c.ed_request = EdHeld {
358            message: words64_kernel(cr.commitment),
359            signature: words64_kernel(cr.signature),
360            public_key: key,
361            tag: cr.entry.resize(),
362        };
363    }
364
365    // ------------------------------------------------ AXI-Lite write
366    let axi = input.axi;
367    if axi.aw_valid && !q.control.aw_have {
368        c.aw_addr = axi.aw_addr;
369        c.aw_have = true;
370    }
371    if axi.w_valid && !q.control.w_have {
372        c.w_data = axi.w_data;
373        c.w_have = true;
374    }
375    if q.control.b_valid && axi.b_ready {
376        c.b_valid = false;
377    }
378    // A register write comes from AXI-Lite (address and data latched, the
379    // previous response gone) or, when AXI-Lite has none, from the `cfg`
380    // port of a component that embeds the engine. Both decode alike.
381    let axi_write = q.control.aw_have && q.control.w_have && !q.control.b_valid;
382    let cfg_write = input.cfg.valid && !axi_write;
383    if axi_write || cfg_write {
384        let a = if axi_write { q.control.aw_addr } else { input.cfg.addr };
385        let v = if axi_write { q.control.w_data } else { input.cfg.data };
386        if axi_write {
387            c.aw_have = false;
388            c.w_have = false;
389            c.b_valid = true;
390        }
391        if a == b12(0x000) && (v & b32(1)) == b32(1) && !q.control.running && !q.control.done {
392            c.running = true;
393            c.start_tick = q.control.ticks;
394        }
395        if a == b12(0x004) {
396            c.gie = (v & b32(1)) == b32(1);
397        }
398        if a == b12(0x008) {
399            c.ier = (v & b32(1)) == b32(1);
400        }
401        if a == b12(0x00c) && (v & b32(1)) == b32(1) {
402            c.isr = !q.control.isr;
403        }
404        if !q.control.running && !q.control.done {
405            if a == b12(ARG_SIGN_COUNT as u128) {
406                c.args.sign_count = v;
407            }
408            if a == b12(ARG_VERIFY_COUNT as u128) {
409                c.args.verify_count = v;
410            }
411            if a == b12(ARG_BATCHES as u128) {
412                c.args.batches = v;
413            }
414            if a == b12(ARG_EPOCH as u128) || a == b12((ARG_EPOCH + 4) as u128) {
415                c.args.epoch = set_half_kernel(args.epoch, v, a == b12((ARG_EPOCH + 4) as u128));
416            }
417            if a == b12(ARG_FIRST_BATCH as u128) || a == b12((ARG_FIRST_BATCH + 4) as u128) {
418                c.args.first_batch = set_half_kernel(args.first_batch, v, a == b12((ARG_FIRST_BATCH + 4) as u128));
419            }
420            for w in 0..8 {
421                if a == b12((ARG_CV + 4 * w) as u128) {
422                    c.args.prefix_cv[w] = v;
423                }
424                if a == b12((ARG_SEED + 4 * w) as u128) {
425                    c.args.seed[w] = v;
426                }
427            }
428            if a == b12(ARG_KEY_MASK as u128) {
429                c.args.key_mask = v.resize();
430            }
431            for s in 0..KEY_SLOTS {
432                for w in 0..8 {
433                    if a == b12((ARG_KEYS + 0x20 * s + 4 * w) as u128) {
434                        c.args.keys[s][w] = v;
435                    }
436                }
437                if a == b12((ARG_EPOCHS + 8 * s) as u128) {
438                    c.args.epochs[s] = (q.control.args.epochs[s] & (b64(0xffff_ffff) << 32)) | v.resize::<64>();
439                }
440                if a == b12((ARG_EPOCHS + 8 * s + 4) as u128) {
441                    c.args.epochs[s] = (q.control.args.epochs[s] & b64(0xffff_ffff)) | (v.resize::<64>() << 32);
442                }
443            }
444            if a == b12(ARG_EPOCH_MASK as u128) {
445                c.args.epoch_mask = v.resize();
446            }
447        }
448    }
449
450    // ------------------------------------------------ AXI-Lite read
451    if q.control.r_valid && axi.r_ready {
452        c.r_valid = false;
453    }
454    let ar_ready = !q.control.r_valid;
455    if axi.ar_valid && ar_ready {
456        let a = axi.ar_addr;
457        let mut value = b32(0);
458        if a == b12(0x000) {
459            value = (if q.control.running { b32(1) } else { b32(0) })
460                | (if q.control.done_unread { b32(2) } else { b32(0) })
461                | (if !q.control.running { b32(4) } else { b32(0) })
462                | (if q.control.done_unread { b32(8) } else { b32(0) });
463            c.done_unread = false;
464        }
465        if a == b12(0x004) {
466            value = if q.control.gie { b32(1) } else { b32(0) };
467        }
468        if a == b12(0x008) {
469            value = if q.control.ier { b32(1) } else { b32(0) };
470        }
471        if a == b12(0x00c) {
472            value = if q.control.isr { b32(1) } else { b32(0) };
473        }
474        if a == b12(REG_MAGIC as u128) {
475            value = MAGIC;
476        }
477        if a == b12(REG_ABI as u128) {
478            value = ABI_VERSION;
479        }
480        if a == b12(REG_SHAPE as u128) {
481            value = b32((SHAPE_BASE | (TABLE_MASK << 8)) as u128);
482        }
483        if a == b12(REG_FAULT as u128) {
484            value = fault_bits;
485        }
486        if a == b12(REG_TICKS as u128) {
487            value = q.control.ticks.resize();
488            c.ticks_hi = (q.control.ticks >> 32).resize();
489        }
490        if a == b12((REG_TICKS + 4) as u128) {
491            value = q.control.ticks_hi;
492        }
493        if a == b12(REG_START_TICK as u128) {
494            value = q.control.start_tick.resize();
495        }
496        if a == b12((REG_START_TICK + 4) as u128) {
497            value = (q.control.start_tick >> 32).resize();
498        }
499        if a == b12(REG_DONE_TICK as u128) {
500            value = q.control.done_tick.resize();
501        }
502        if a == b12((REG_DONE_TICK + 4) as u128) {
503            value = (q.control.done_tick >> 32).resize();
504        }
505        if a == b12(REG_KEY_TICK as u128) {
506            value = q.control.key_tick.resize();
507        }
508        if a == b12((REG_KEY_TICK + 4) as u128) {
509            value = (q.control.key_tick >> 32).resize();
510        }
511        if a == b12(REG_SIGNED as u128) {
512            value = sig.signed;
513        }
514        if a == b12(REG_SIGN_TAKEN as u128) {
515            value = sig.taken;
516        }
517        if a == b12(REG_VERIFIED as u128) {
518            value = ver.verified;
519        }
520        if a == b12(REG_VALID as u128) {
521            value = ver.valid;
522        }
523        if a == b12(REG_CERT_CHECKS as u128) {
524            value = ver.cert_checks;
525        }
526        if a == b12(REG_CERT_FAILS as u128) {
527            value = ver.cert_fails;
528        }
529        if a == b12(REG_CERTIFIED as u128) {
530            value = sig.certified;
531        }
532        if a == b12(REG_ANNOUNCED as u128) {
533            value = sig.announced;
534        }
535        for i in 0..6 {
536            if a == b12((REG_SIGN_LATENCY + 4 * i) as u128) {
537                value = latency_word_kernel(sig.latency, b3(i as u128));
538            }
539            if a == b12((REG_VERIFY_LATENCY + 4 * i) as u128) {
540                value = latency_word_kernel(ver.latency, b3(i as u128));
541            }
542        }
543        for w in 0..4 {
544            if a == b12((REG_PUBLIC_KEY + 8 * w) as u128) {
545                value = sig.public_key.words[w].resize();
546            }
547            if a == b12((REG_PUBLIC_KEY + 8 * w + 4) as u128) {
548                value = (sig.public_key.words[w] >> 32).resize();
549            }
550        }
551        c.r_valid = true;
552        c.r_data = value;
553    }
554
555    // ------------------------------------------------ children
556    let mut seed_words = [b64(0); 4];
557    for w in 0..4 {
558        seed_words[w] = args.seed[2 * w].resize::<64>() | (args.seed[2 * w + 1].resize::<64>() << 32);
559    }
560    let held = q.control.ed_request;
561    let d = UbftEngineD::<TABLE_MASK> {
562        control: c,
563        signer: SignerIn {
564            config: SignConfig {
565                prefix_cv: args.prefix_cv,
566                epoch: args.epoch,
567                first_batch: args.first_batch,
568                batches: args.batches,
569                seed: seed_words,
570            },
571            run: q.control.running,
572            ticks: q.control.ticks,
573            request: input.sign,
574            frame_ready: input.frame_ready,
575            cert_ready: input.cert_out_ready,
576        },
577        verifier: VerifierIn {
578            open: q.control.running && q.control.keys_settled,
579            ticks: q.control.ticks,
580            request: input.verify,
581            verdict_ready: input.verdict_ready,
582            cert: input.cert_in,
583            epochs: q.control.args.epochs,
584            epoch_mask: q.control.args.epoch_mask,
585            cert_taken: issue,
586            cert_result: CertResult {
587                valid: ed.valid,
588                entry: ed.tag.resize(),
589                ok: ed.ok,
590            },
591        },
592        ed: VerifyIn {
593            valid: q.control.ed_valid,
594            request: VerifyRequest {
595                message: Words512 { words: held.message },
596                public_key: ScalarWords { words: held.public_key },
597                signature: Words512 { words: held.signature },
598                tag: held.tag,
599            },
600            load_key_valid: q.control.kl_pulse,
601            load_key: ScalarWords { words: held.public_key },
602            load_slot: q.control.kl_slot.resize(),
603            sha_ext: ShaRingOut {
604                ready: false,
605                done: ShaDone {
606                    valid: false,
607                    tag: b16(0),
608                    state: [b64(0); 8],
609                },
610            },
611        },
612    };
613    (
614        UbftOutput {
615            axi: AxiLiteOut {
616                aw_ready: !q.control.aw_have,
617                w_ready: !q.control.w_have,
618                b_valid: q.control.b_valid,
619                b_resp: b2(0),
620                ar_ready,
621                r_valid: q.control.r_valid,
622                r_data: q.control.r_data,
623                r_resp: b2(0),
624                interrupt: q.control.gie && q.control.ier && q.control.isr,
625            },
626            sign_ready: sig.request_ready,
627            frame: sig.frame,
628            verify_ready: ver.request_ready,
629            verdict: ver.verdict,
630            cert_out: sig.cert,
631            cert_in_ready: ver.cert_ready,
632            ready: q.control.running && q.control.keys_settled,
633            fault: fault_bits != b32(0),
634            cfg_ready: !axi_write,
635        },
636        d,
637    )
638}
639
640const _: () = assert!(KEY_SLOTS <= rhdl_ed25519_live::machine::KEY_SLOTS, "more trusted keys than the Ed25519 verify core has slots");