Skip to main content

rhdl_ed25519_scalable_core/ubft/
service.rs

1//! The service: its ports, state and kernel ([`UbftEd25519`]).
2
3use super::*;
4
5/// Kernel inputs.
6#[derive(Clone, Copy, Debug, PartialEq, Digital)]
7pub struct UbftIn {
8    /// Host control channel.
9    pub axi: AxiLiteIn,
10    /// `s_axis_sign`.
11    pub sign: Axis512,
12    /// `m_axis_sig` `tready`.
13    pub sig_ready: bool,
14    /// `s_axis_verify`.
15    pub verify: Axis512,
16    /// `m_axis_verdict` `tready`.
17    pub verdict_ready: bool,
18}
19
20/// Kernel outputs.
21#[derive(Clone, Copy, Debug, PartialEq, Digital)]
22pub struct UbftOut {
23    /// Host control channel.
24    pub axi: AxiLiteOut,
25    /// `s_axis_sign` `tready`.
26    pub sign_ready: bool,
27    /// `m_axis_sig`.
28    pub sig: Axis512,
29    /// `s_axis_verify` `tready`.
30    pub verify_ready: bool,
31    /// `m_axis_verdict`.
32    pub verdict: Axis64,
33}
34
35/// A finished signature waiting for `m_axis_sig`.
36#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
37#[doc(hidden)]
38pub struct SigDone {
39    /// Tag-table index.
40    idx: b4,
41    /// `R‖S`.
42    signature: [b64; 8],
43}
44
45/// A finished verification waiting for `m_axis_verdict`.
46#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
47#[doc(hidden)]
48pub struct VerdictDone {
49    /// Tag-table index.
50    idx: b4,
51    /// `VERIFY_*` code (0 when valid).
52    error: b8,
53}
54
55/// Host arguments.
56#[derive(Clone, Copy, Debug, PartialEq, Digital)]
57#[doc(hidden)]
58pub struct UbftArgs {
59    sign_count: b32,
60    verify_count: b32,
61    seed: [b32; 8],
62    key_mask: b16,
63    keys: [[b32; 8]; KEY_SLOTS],
64}
65
66/// Register file, run state, key loading, stream state and tag tables.
67#[derive(Clone, Copy, Debug, PartialEq, Digital)]
68#[doc(hidden)]
69pub struct UbftControl {
70    args: UbftArgs,
71    running: bool,
72    done: bool,
73    done_unread: bool,
74    gie: bool,
75    ier: bool,
76    isr: bool,
77    ticks: b64,
78    start_tick: b64,
79    done_tick: b64,
80    key_tick: b64,
81    ticks_hi: b32,
82    /// The signer's seed was offered.
83    seed_sent: bool,
84    /// Key loader: the slot being considered (16 = all done).
85    kl_slot: b5,
86    /// Key loader: a load was issued for `kl_slot` and has not settled.
87    kl_wait: bool,
88    /// Key loader: pulse `load_key_valid` this cycle.
89    kl_pulse: bool,
90    /// Every masked slot settled.
91    keys_settled: bool,
92    /// The signer's key is cached and the trusted keys settled.
93    keys_ticked: bool,
94    /// A trusted key did not decode.
95    key_bad: bool,
96    aw_addr: b12,
97    aw_have: bool,
98    w_data: b32,
99    w_have: bool,
100    b_valid: bool,
101    r_valid: bool,
102    r_data: b32,
103    /// Sign tag table: free indices and each index's user tag.
104    sign_free: b16,
105    sign_tag: [b64; 16],
106    /// Sign requests taken and not yet answered.
107    sign_in_flight: b5,
108    /// `m_axis_sig` is on beat 1 (the tag) of the head signature.
109    sig_beat: bool,
110    /// Verify tag table: free indices, each index's tag and slot.
111    verify_free: b16,
112    verify_tag: [b32; 16],
113    verify_slot: [b4; 16],
114    /// Verify requests taken and not yet answered.
115    verify_in_flight: b5,
116    /// `s_axis_verify` beat being received (0 header, 1 message, 2 `R‖S`).
117    vin_beat: b2,
118    /// The assembled request waits for the verify core.
119    vin_full: bool,
120    vin_slot: b4,
121    vin_tag: b32,
122    vin_message: [b64; 8],
123    vin_signature: [b64; 8],
124    /// A verify request's `tlast` was not on beat 2.
125    framing_fault: bool,
126    signed: b32,
127    sign_taken: b32,
128    verified: b32,
129    valid: b32,
130    sign_latency: Latency,
131    verify_latency: Latency,
132}
133
134impl Default for UbftControl {
135    /// IDLE, zero arguments, every tag free.
136    fn default() -> Self {
137        Self {
138            args: UbftArgs {
139                sign_count: b32(0),
140                verify_count: b32(0),
141                seed: [b32(0); 8],
142                key_mask: b16(0),
143                keys: [[b32(0); 8]; KEY_SLOTS],
144            },
145            running: false,
146            done: false,
147            done_unread: false,
148            gie: false,
149            ier: false,
150            isr: false,
151            ticks: b64(0),
152            start_tick: b64(0),
153            done_tick: b64(0),
154            key_tick: b64(0),
155            ticks_hi: b32(0),
156            seed_sent: false,
157            kl_slot: b5(0),
158            kl_wait: false,
159            kl_pulse: false,
160            keys_settled: false,
161            keys_ticked: false,
162            key_bad: false,
163            aw_addr: b12(0),
164            aw_have: false,
165            w_data: b32(0),
166            w_have: false,
167            b_valid: false,
168            r_valid: false,
169            r_data: b32(0),
170            sign_free: b16(0xffff),
171            sign_tag: [b64(0); 16],
172            sign_in_flight: b5(0),
173            sig_beat: false,
174            verify_free: b16(0xffff),
175            verify_tag: [b32(0); 16],
176            verify_slot: [b4(0); 16],
177            verify_in_flight: b5(0),
178            vin_beat: b2(0),
179            vin_full: false,
180            vin_slot: b4(0),
181            vin_tag: b32(0),
182            vin_message: [b64(0); 8],
183            vin_signature: [b64(0); 8],
184            framing_fault: false,
185            signed: b32(0),
186            sign_taken: b32(0),
187            verified: b32(0),
188            valid: b32(0),
189            sign_latency: Latency::default(),
190            verify_latency: Latency::default(),
191        }
192    }
193}
194
195/// The uBFT Ed25519 service kernel: a signer with `NFB` fixed-base
196/// machines and a verifier with `NV` verify machines (one SHA-512 ring and
197/// one scalar ring each). uBFT signs once per consensus slot and verifies
198/// at most `N − 1` shares, so `UbftEd25519<1, 1>` is the usual build.
199#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
200pub struct UbftEd25519<const NFB: usize, const NV: usize> {
201    /// Registers, run state, tag tables.
202    control: DFF<UbftControl>,
203    /// Cached-key signer (fused: no compress machine).
204    signer: Ed25519SignCore<NFB, 1, 1, 0>,
205    /// Known-key verifier, 16 key slots.
206    verifier: KnownKeyVerifyCore<NV, 1, 1, 0>,
207    /// Signatures waiting for `m_axis_sig` (up to 31; at most 16 by the tag
208    /// table).
209    sig_fifo: SyncFIFO<SigDone, 5>,
210    /// Verdicts waiting for `m_axis_verdict`.
211    verdict_fifo: SyncFIFO<VerdictDone, 5>,
212}
213
214impl<const NFB: usize, const NV: usize> Default for UbftEd25519<NFB, NV> {
215    /// Power-on: IDLE, no keys.
216    fn default() -> Self {
217        Self {
218            control: DFF::new(UbftControl::default()),
219            signer: Ed25519SignCore::default(),
220            verifier: KnownKeyVerifyCore::default(),
221            sig_fifo: SyncFIFO::default(),
222            verdict_fifo: SyncFIFO::default(),
223        }
224    }
225}
226
227impl<const NFB: usize, const NV: usize> SynchronousIO for UbftEd25519<NFB, NV> {
228    type I = UbftIn;
229    type O = UbftOut;
230    type Kernel = ubft_ed25519_kernel<NFB, NV>;
231}
232
233/// The kernel's logic.
234#[allow(clippy::needless_range_loop, clippy::too_many_lines)]
235#[kernel]
236pub fn ubft_ed25519_kernel<const NFB: usize, const NV: usize>(
237    clock_reset: ClockReset,
238    input: UbftIn,
239    q: UbftEd25519Q<NFB, NV>,
240) -> (UbftOut, UbftEd25519D<NFB, NV>) {
241    let resetting = clock_reset.reset.any();
242    let r = q.control;
243    let args = q.control.args;
244    let mut c = q.control;
245    c.ticks = r.ticks + b64(1);
246    c.kl_pulse = false;
247    let sig = q.signer;
248    let ver = q.verifier;
249
250    // ------------------------------------------------ progress and faults
251    let fault_bits: b32 = (if sig.internal_error { b32(1) } else { b32(0) })
252        | (if ver.internal_error { b32(4) } else { b32(0) })
253        | (if r.key_bad { b32(8) } else { b32(0) })
254        | (if r.framing_fault { b32(16) } else { b32(0) });
255    let settled_start = r.ticks - r.start_tick > b64(16);
256    if r.running && settled_start && r.keys_ticked && r.signed == args.sign_count && r.verified == args.verify_count {
257        c.running = false;
258        c.done = true;
259        c.done_unread = true;
260        c.isr = true;
261        c.done_tick = r.ticks;
262    }
263
264    // ------------------------------------------------ the signer's key
265    let mut seed_words = [b64(0); 4];
266    for w in 0..4 {
267        seed_words[w] = args.seed[2 * w].resize::<64>() | (args.seed[2 * w + 1].resize::<64>() << 32);
268    }
269    let seed_offer = r.running && !r.seed_sent && sig.load_key_ready && !resetting;
270    if seed_offer {
271        c.seed_sent = true;
272    }
273
274    // ------------------------------------------------ trusted keys
275    // Slot `kl_slot` (0..16): skip unmasked slots, load masked ones one at a
276    // time, and wait for each to settle.
277    let mut kl_key = [b64(0); 4];
278    let mut kl_masked = false;
279    let mut kl_state = b2(0);
280    for s in 0..16 {
281        if r.kl_slot == b5(s as u128) {
282            kl_masked = (args.key_mask & (b16(1) << b16(s as u128))) != b16(0);
283            kl_state = ver.key_state[s];
284            for w in 0..4 {
285                kl_key[w] = args.keys[s][2 * w].resize::<64>() | (args.keys[s][2 * w + 1].resize::<64>() << 32);
286            }
287        }
288    }
289    if r.running && !r.keys_settled && !resetting {
290        if r.kl_slot == b5(16) {
291            c.keys_settled = true;
292        } else if !kl_masked {
293            c.kl_slot = r.kl_slot + b5(1);
294        } else if !r.kl_wait {
295            if ver.load_key_ready && !r.kl_pulse {
296                c.kl_pulse = true;
297                c.kl_wait = true;
298            }
299        } else if !r.kl_pulse && (kl_state == b2(2) || kl_state == b2(3)) {
300            c.kl_wait = false;
301            c.kl_slot = r.kl_slot + b5(1);
302            if kl_state == b2(3) {
303                c.key_bad = true;
304            }
305        }
306    }
307    if r.running && r.keys_settled && sig.key_loaded && !r.keys_ticked {
308        c.keys_ticked = true;
309        c.key_tick = r.ticks;
310    }
311
312    // ------------------------------------------------ s_axis_sign
313    let sign_open = r.running && r.keys_ticked && !resetting;
314    let sign_ready = sign_open && sig.ready && r.sign_free != b16(0);
315    let sign_take = sign_ready && input.sign.valid;
316    // A sign request is exactly one beat: a beat without `last` is
317    // consumed, never signed, and raises the framing fault (R-10).
318    let sign_req = sign_take && input.sign.last;
319    if sign_take && !input.sign.last {
320        c.framing_fault = true;
321    }
322    let sign_idx = lowest_free(r.sign_free);
323    // Layout v2 (ABI 2): bytes 0..8 tag, 8..56 the message, signed as
324    // those 48 bytes zero-extended to 64.
325    let words_in = beat_words(input.sign.data);
326    let message = sign_message(words_in);
327    if sign_req {
328        c.sign_free = c.sign_free & !(b16(1) << sign_idx.resize::<16>());
329        c.sign_tag[sign_idx] = words_in[0];
330        c.sign_taken = r.sign_taken + b32(1);
331    }
332
333    // ------------------------------------------------ m_axis_sig
334    let sig_head = q.sig_fifo.data;
335    let mut sig_out = Axis512 { valid: false, data: [b8(0); 64], last: false };
336    let mut sig_pop = false;
337    let mut sig_done = SigDone { idx: b4(0), signature: [b64(0); 8] };
338    match sig_head {
339        Some(h) => {
340            sig_done = h;
341            sig_out.valid = true;
342            if r.sig_beat {
343                let mut tag_words = [b64(0); 8];
344                tag_words[0] = r.sign_tag[h.idx];
345                sig_out.data = words_beat(tag_words);
346                sig_out.last = true;
347            } else {
348                sig_out.data = words_beat(h.signature);
349            }
350        }
351        None => {}
352    }
353    if sig_out.valid && input.sig_ready {
354        if r.sig_beat {
355            sig_pop = true;
356            c.sig_beat = false;
357            c.signed = r.signed + b32(1);
358        } else {
359            c.sig_beat = true;
360        }
361    }
362    if sig_pop {
363        c.sign_free = c.sign_free | (b16(1) << sig_done.idx.resize::<16>());
364    }
365    let sign_in_flight = r.sign_in_flight + (if sign_req { b5(1) } else { b5(0) }) - (if sig_pop { b5(1) } else { b5(0) });
366    c.sign_in_flight = sign_in_flight;
367    c.sign_latency = latency_kernel(
368        r.sign_latency,
369        r.ticks,
370        sign_req && r.sign_in_flight == b5(0),
371        sig_pop && r.sign_in_flight == b5(1),
372        sign_req && r.sign_in_flight != b5(0),
373    );
374
375    // ------------------------------------------------ s_axis_verify
376    let verify_open = r.running && r.keys_ticked && !resetting;
377    let verify_ready = verify_open && !r.vin_full;
378    let beat = input.verify;
379    let vtake = verify_ready && beat.valid;
380    let beat_w = beat_words(beat.data);
381    let mut verify_first = false;
382    if vtake {
383        if r.vin_beat == b2(0) {
384            c.vin_slot = beat.data[0].resize();
385            c.vin_tag = (beat_w[0] >> 32).resize();
386            c.vin_beat = b2(1);
387            verify_first = true;
388        } else if r.vin_beat == b2(1) {
389            c.vin_message = beat_w;
390            c.vin_beat = b2(2);
391        } else {
392            c.vin_signature = beat_w;
393            c.vin_beat = b2(0);
394            c.vin_full = true;
395        }
396        if beat.last != (r.vin_beat == b2(2)) {
397            c.framing_fault = true;
398        }
399    }
400
401    // Issue the assembled request with its slot's public key. The core
402    // matches the key by its bytes; an unloaded slot answers
403    // VERIFY_UNKNOWN_KEY.
404    let mut slot_key = [b64(0); 4];
405    for s in 0..16 {
406        if r.vin_slot == b4(s as u128) {
407            for w in 0..4 {
408                slot_key[w] = args.keys[s][2 * w].resize::<64>() | (args.keys[s][2 * w + 1].resize::<64>() << 32);
409            }
410        }
411    }
412    let verify_idx = lowest_free(r.verify_free);
413    let verify_issue = r.vin_full && ver.ready && r.verify_free != b16(0) && !r.kl_pulse && !resetting;
414    if verify_issue {
415        c.vin_full = false;
416        c.verify_free = c.verify_free & !(b16(1) << verify_idx.resize::<16>());
417        c.verify_tag[verify_idx] = r.vin_tag;
418        c.verify_slot[verify_idx] = r.vin_slot;
419    }
420
421    // ------------------------------------------------ m_axis_verdict
422    let verdict_head = q.verdict_fifo.data;
423    let mut verdict_out = Axis64 { valid: false, data: b64(0) };
424    let mut verdict_pop = false;
425    let mut vd = VerdictDone { idx: b4(0), error: b8(0) };
426    match verdict_head {
427        Some(h) => {
428            vd = h;
429            verdict_out.valid = true;
430            verdict_out.data = (b64(0x80) | h.error.resize::<64>())
431                | (r.verify_slot[h.idx].resize::<64>() << b64(8))
432                | (r.verify_tag[h.idx].resize::<64>() << b64(32));
433        }
434        None => {}
435    }
436    if verdict_out.valid && input.verdict_ready {
437        verdict_pop = true;
438        c.verify_free = c.verify_free | (b16(1) << vd.idx.resize::<16>());
439        c.verified = r.verified + b32(1);
440        if vd.error == b8(0) {
441            c.valid = r.valid + b32(1);
442        }
443    }
444    let verify_in_flight =
445        r.verify_in_flight + (if verify_first { b5(1) } else { b5(0) }) - (if verdict_pop { b5(1) } else { b5(0) });
446    c.verify_in_flight = verify_in_flight;
447    c.verify_latency = latency_kernel(
448        r.verify_latency,
449        r.ticks,
450        verify_first && r.verify_in_flight == b5(0),
451        verdict_pop && r.verify_in_flight == b5(1),
452        verify_first && r.verify_in_flight != b5(0),
453    );
454
455    // ------------------------------------------------ AXI-Lite write
456    let axi = input.axi;
457    if axi.aw_valid && !r.aw_have {
458        c.aw_addr = axi.aw_addr;
459        c.aw_have = true;
460    }
461    if axi.w_valid && !r.w_have {
462        c.w_data = axi.w_data;
463        c.w_have = true;
464    }
465    if r.b_valid && axi.b_ready {
466        c.b_valid = false;
467    }
468    if r.aw_have && r.w_have && !r.b_valid {
469        let a = r.aw_addr;
470        let v = r.w_data;
471        c.aw_have = false;
472        c.w_have = false;
473        c.b_valid = true;
474        if a == b12(0x000) && (v & b32(1)) == b32(1) && !r.running && !r.done {
475            c.running = true;
476            c.start_tick = r.ticks;
477        }
478        if a == b12(0x004) {
479            c.gie = (v & b32(1)) == b32(1);
480        }
481        if a == b12(0x008) {
482            c.ier = (v & b32(1)) == b32(1);
483        }
484        if a == b12(0x00c) && (v & b32(1)) == b32(1) {
485            c.isr = !r.isr;
486        }
487        if !r.running && !r.done {
488            if a == b12(ARG_SIGN_COUNT as u128) {
489                c.args.sign_count = v;
490            }
491            if a == b12(ARG_VERIFY_COUNT as u128) {
492                c.args.verify_count = v;
493            }
494            for w in 0..8 {
495                if a == b12((ARG_SEED + 4 * w) as u128) {
496                    c.args.seed[w] = v;
497                }
498            }
499            if a == b12(ARG_KEY_MASK as u128) {
500                c.args.key_mask = v.resize();
501            }
502            for s in 0..16 {
503                for w in 0..8 {
504                    if a == b12((ARG_KEYS + 0x20 * s + 4 * w) as u128) {
505                        c.args.keys[s][w] = v;
506                    }
507                }
508            }
509        }
510    }
511
512    // ------------------------------------------------ AXI-Lite read
513    if r.r_valid && axi.r_ready {
514        c.r_valid = false;
515    }
516    let ar_ready = !r.r_valid;
517    if axi.ar_valid && ar_ready {
518        let a = axi.ar_addr;
519        let mut value = b32(0);
520        if a == b12(0x000) {
521            value = (if r.running { b32(1) } else { b32(0) })
522                | (if r.done_unread { b32(2) } else { b32(0) })
523                | (if !r.running { b32(4) } else { b32(0) })
524                | (if r.done_unread { b32(8) } else { b32(0) });
525            c.done_unread = false;
526        }
527        if a == b12(0x004) {
528            value = if r.gie { b32(1) } else { b32(0) };
529        }
530        if a == b12(0x008) {
531            value = if r.ier { b32(1) } else { b32(0) };
532        }
533        if a == b12(0x00c) {
534            value = if r.isr { b32(1) } else { b32(0) };
535        }
536        if a == b12(REG_MAGIC as u128) {
537            value = MAGIC;
538        }
539        if a == b12(REG_ABI as u128) {
540            value = ABI_VERSION;
541        }
542        if a == b12(REG_SHAPE as u128) {
543            value = b32((NFB | (NV << 8) | (16 << 24)) as u128);
544        }
545        if a == b12(REG_FAULT as u128) {
546            value = fault_bits;
547        }
548        if a == b12(REG_TICKS as u128) {
549            value = r.ticks.resize();
550            c.ticks_hi = (r.ticks >> 32).resize();
551        }
552        if a == b12((REG_TICKS + 4) as u128) {
553            value = r.ticks_hi;
554        }
555        if a == b12(REG_START_TICK as u128) {
556            value = r.start_tick.resize();
557        }
558        if a == b12((REG_START_TICK + 4) as u128) {
559            value = (r.start_tick >> 32).resize();
560        }
561        if a == b12(REG_DONE_TICK as u128) {
562            value = r.done_tick.resize();
563        }
564        if a == b12((REG_DONE_TICK + 4) as u128) {
565            value = (r.done_tick >> 32).resize();
566        }
567        if a == b12(REG_KEY_TICK as u128) {
568            value = r.key_tick.resize();
569        }
570        if a == b12((REG_KEY_TICK + 4) as u128) {
571            value = (r.key_tick >> 32).resize();
572        }
573        if a == b12(REG_SIGNED as u128) {
574            value = r.signed;
575        }
576        if a == b12(REG_SIGN_TAKEN as u128) {
577            value = r.sign_taken;
578        }
579        if a == b12(REG_VERIFIED as u128) {
580            value = r.verified;
581        }
582        if a == b12(REG_VALID as u128) {
583            value = r.valid;
584        }
585        for i in 0..6 {
586            if a == b12((REG_SIGN_LATENCY + 4 * i) as u128) {
587                value = latency_word(r.sign_latency, b3(i as u128));
588            }
589            if a == b12((REG_VERIFY_LATENCY + 4 * i) as u128) {
590                value = latency_word(r.verify_latency, b3(i as u128));
591            }
592        }
593        for w in 0..4 {
594            if a == b12((REG_PUBLIC_KEY + 8 * w) as u128) {
595                value = sig.public_key.words[w].resize();
596            }
597            if a == b12((REG_PUBLIC_KEY + 8 * w + 4) as u128) {
598                value = (sig.public_key.words[w] >> 32).resize();
599            }
600        }
601        c.r_valid = true;
602        c.r_data = value;
603    }
604
605    // ------------------------------------------------ children
606    let mut d = UbftEd25519D::<NFB, NV>::dont_care();
607    d.control = c;
608    d.signer = CachedSignIn {
609        load_key_valid: seed_offer,
610        key_seed: ScalarWords { words: seed_words },
611        clear_key: false,
612        valid: sign_req,
613        request: SignRequest { message: Words512 { words: message }, tag: sign_idx.resize() },
614    };
615    d.verifier = VerifyIn {
616        valid: verify_issue,
617        request: VerifyRequest {
618            message: Words512 { words: r.vin_message },
619            public_key: ScalarWords { words: slot_key },
620            signature: Words512 { words: r.vin_signature },
621            tag: verify_idx.resize(),
622        },
623        load_key_valid: r.kl_pulse,
624        load_key: ScalarWords { words: kl_key },
625        load_slot: r.kl_slot.resize(),
626        sha_ext: ShaRingOut { ready: false, done: ShaDone { valid: false, tag: b16(0), state: [b64(0); 8] } },
627    };
628    d.sig_fifo = FifoIn::<SigDone> {
629        data: if sig.valid { Some(SigDone { idx: sig.tag.resize(), signature: sig.signature.words }) } else { None },
630        next: sig_pop,
631    };
632    d.verdict_fifo = FifoIn::<VerdictDone> {
633        data: if ver.valid { Some(VerdictDone { idx: ver.tag.resize(), error: ver.error }) } else { None },
634        next: verdict_pop,
635    };
636    let out = UbftOut {
637        axi: AxiLiteOut {
638            aw_ready: !r.aw_have,
639            w_ready: !r.w_have,
640            b_valid: r.b_valid,
641            b_resp: b2(0),
642            ar_ready,
643            r_valid: r.r_valid,
644            r_data: r.r_data,
645            r_resp: b2(0),
646            interrupt: r.gie && r.ier && r.isr,
647        },
648        sign_ready,
649        sig: sig_out,
650        verify_ready,
651        verdict: verdict_out,
652    };
653    (out, d)
654}