rhdl_ed25519_scalable_core/ubft.rs
1//! Ed25519 as the signature service of a uBFT replica (feature `ubft`).
2//!
3//! # The problem
4//!
5//! [uBFT](https://github.com/YiJiang0908/ubft_new) runs consensus entirely
6//! on an Alveo U280. On its slow path every replica signs a 64-bit summary
7//! of the slot, sends the signature to every peer, and verifies the shares
8//! it receives until it holds `2f + 1`. It calls the Vitis Security
9//! Library's Ed25519 inline, so the whole state machine stops for every
10//! signature and every verification. That signer is the routed design's
11//! worst path (−2.866 ns), which pulls the kernel clock from 300 MHz down
12//! to 161.3 MHz. It also hashes only one byte of the summary: the calls pass
13//! a length of 1.
14//!
15//! # The idea
16//!
17//! [`UbftEd25519`] is a service that sits next to uBFT on the chip. It takes
18//! one request at a time on a stream and answers in microseconds, with the
19//! same streams and header layout as DSig's service (`rhdl_dsig_scalable_core`
20//! `ubft`), so uBFT picks a scheme at compile time with the same HLS stream
21//! calls. Inside are this crate's cached-key signer and known-key verifier.
22//!
23//! ```text
24//! uBFT ──sign request──▶ ┌────────────── UbftEd25519 ──────────────┐
25//! ◀──R‖S, tag────── │ Ed25519SignCore (cached key) │
26//! uBFT ──verify request─▶│ KnownKeyVerifyCore, 16 trusted key slots│
27//! ◀──verdict word── └──── AXI4-Lite: seed, keys, counters ────┘
28//! ```
29//!
30//! * **Signing.** A request is one 64-byte beat: an 8-byte tag, then a
31//! 48-byte message (layout v2, [`ABI_VERSION`] 2). The signed message is
32//! those 48 bytes zero-extended to 64 bytes, an ordinary RFC 8032
33//! signature that any Ed25519 library checks. rhdl_ubft puts a domain
34//! magic, a role, a session id and its `(view, slot, k, m)` there, so a
35//! share from another run or another role does not verify. (v1 signed
36//! bytes 0..16 and carried the tag at 16..24.)
37//! The answer is two beats: `R‖S` (the 64 bytes uBFT mails to its peers),
38//! then the tag.
39//! * **Verifying.** A request is three beats: a header (the signer's key
40//! slot and a 32-bit tag), the 64-byte message as it was signed, and
41//! `R‖S`. The verdict word comes back with the slot and the tag.
42//! * **Peers.** One key per replica, so the verifier trusts up to
43//! [`KEY_SLOTS`] = 16 keys (`N ≤ 17` replicas; uBFT has `N ≤ 12`).
44//! * **Order.** Answers can come back out of order. Match them by tag.
45//!
46//! # Streams
47//!
48//! | Port | Direction | Beat | Content |
49//! |---|---|---|---|
50//! | `s_axis_sign` | in | 512 bits | bytes 0..8 tag (LE64), 8..56 message |
51//! | `m_axis_sig` | out | 512 bits, `tlast` on beat 1 | beat 0: `R‖S`; beat 1: bytes 0..8 tag |
52//! | `s_axis_verify` | in | 512 bits, `tlast` on beat 2 | beat 0: byte 0 bits 0..4 key slot, bytes 4..8 tag (LE32); beat 1: message; beat 2: `R‖S` |
53//! | `m_axis_verdict` | out | 64 bits | bits 0..8 verdict, 8..12 key slot, 32..64 tag |
54//!
55//! A verdict of `0x80` means valid. Otherwise it is `0x80 | code`, with a
56//! `VERIFY_*` code of [`crate::verify_core`]: 7 means the slot's key is not
57//! loaded, 6 that the equation failed.
58//!
59//! # Register map (32-bit words)
60//!
61//! | Offset | Name | Access |
62//! |---|---|---|
63//! | `0x000` | `AP_CTRL`: bit 0 `ap_start`, 1 `ap_done` (clear on read), 2 `ap_idle`, 3 `ap_ready` | RW |
64//! | `0x004`/`0x008`/`0x00c` | `GIE` / `IER` / `ISR` | RW |
65//! | `0x010` | arg `sign_count`: signatures this session | W |
66//! | `0x014` | arg `verify_count`: verifications this session | W |
67//! | `0x050 + 4w` | args `seed0..seed7`: the signer's Ed25519 seed (secret) | W |
68//! | `0x070` | arg `key_mask`: trusted key slots in use (bits 0..16) | W |
69//! | `0x200` | `MAGIC` = [`MAGIC`] ("ESU1") | R |
70//! | `0x204` | `ABI` = [`ABI_VERSION`] | R |
71//! | `0x208` | `SHAPE`: fixed-base machines bits 0..8, verify machines 8..16, key slots 24..29 | R |
72//! | `0x20c` | `FAULT`: bit 0 signer, 2 verify core, 3 a trusted key rejected, 4 verify stream framing | R |
73//! | `0x210`/`0x214` | `TICKS` lo (snapshots hi) / hi | R |
74//! | `0x218`/`0x21c` | `START_TICK` lo/hi | R |
75//! | `0x220`/`0x224` | `DONE_TICK` lo/hi | R |
76//! | `0x228`/`0x22c` | `KEY_TICK` lo/hi: the signer's key and the trusted keys are loaded | R |
77//! | `0x230` | `SIGNED`: signatures sent | R |
78//! | `0x234` | `SIGN_TAKEN`: sign requests accepted | R |
79//! | `0x238` | `VERIFIED`: verdicts sent | R |
80//! | `0x23c` | `VALID`: verdicts that were `0x80` | R |
81//! | `0x250 + 4i` | sign latency: `N`, `LAST`, `MIN`, `MAX`, `SUM` lo, `SUM` hi | R |
82//! | `0x270 + 4i` | verify latency: same six words | R |
83//! | `0x2a0 + 4w` | `PUBLIC_KEY`: the signer's Ed25519 public key (8 × u32) | R |
84//! | `0x400 + 0x20·s + 4w` | args `key{s}_{w}`: slot `s`'s public key, bytes `4w..4w+4` LE | W |
85//!
86//! The words up to `0x2c0` sit where DSig's service has them. The trusted
87//! keys moved to `0x400`: 16 slots at DSig's `0x080` would run into the
88//! status words at `0x200`.
89//!
90//! # How latency is measured
91//!
92//! As in DSig's service: the kernel times *isolated* requests on its own
93//! clock, from the request's first handshake to the answer's last, when no
94//! other request of the same kind is in flight. Loaded latency is not
95//! measured here.
96//!
97//! # Session rules
98//!
99//! The host writes the seed, the keys, `key_mask` and the counts, then sets
100//! `ap_start`. The signer expands its seed and the loader puts each masked
101//! key into the verifier, about 105,000 cycles each. The sign stream opens
102//! once the signer's key is cached and the verify stream once every masked
103//! slot has settled. The session is done when `sign_count` signatures and
104//! `verify_count` verdicts have left. The seed has no read path.
105
106use rhdl::prelude::*;
107use rhdl_fpga::core::dff::DFF;
108use rhdl_fpga::fifo::synchronous::{In as FifoIn, SyncFIFO};
109
110use crate::{
111 ed25519_sign_core::Ed25519SignCore,
112 machine::KEY_SLOTS,
113 sha_ring::{ShaDone, ShaRingOut},
114 verify_core::{KnownKeyVerifyCore, VerifyIn},
115 wire::{CachedSignIn, ScalarWords, SignRequest, VerifyRequest, Words512},
116};
117
118const _: () = assert!(KEY_SLOTS == 16, "the uBFT service is built with 16 key slots");
119
120/// `MAGIC` register value ("ESU1" in little-endian ASCII).
121pub const MAGIC: b32 = b32(0x3155_5345);
122/// Register-map version.
123pub const ABI_VERSION: b32 = b32(0x0002_0000);
124/// Requests of each kind the service holds at once (in a core or waiting
125/// on an output stream): the tag tables' size.
126pub const IN_FLIGHT: usize = 16;
127
128/// `sign_count`.
129pub const ARG_SIGN_COUNT: usize = 0x010;
130/// `verify_count`.
131pub const ARG_VERIFY_COUNT: usize = 0x014;
132/// `seed0`.
133pub const ARG_SEED: usize = 0x050;
134/// `key_mask`.
135pub const ARG_KEY_MASK: usize = 0x070;
136/// `key0_0`; slot `s`, word `w` at `+ 0x20·s + 4w`.
137pub const ARG_KEYS: usize = 0x400;
138/// `MAGIC`.
139pub const REG_MAGIC: usize = 0x200;
140/// `ABI`.
141pub const REG_ABI: usize = 0x204;
142/// `SHAPE`.
143pub const REG_SHAPE: usize = 0x208;
144/// `FAULT`.
145pub const REG_FAULT: usize = 0x20c;
146/// `TICKS` lo.
147pub const REG_TICKS: usize = 0x210;
148/// `START_TICK` lo.
149pub const REG_START_TICK: usize = 0x218;
150/// `DONE_TICK` lo.
151pub const REG_DONE_TICK: usize = 0x220;
152/// `KEY_TICK` lo.
153pub const REG_KEY_TICK: usize = 0x228;
154/// `SIGNED`.
155pub const REG_SIGNED: usize = 0x230;
156/// `SIGN_TAKEN`.
157pub const REG_SIGN_TAKEN: usize = 0x234;
158/// `VERIFIED`.
159pub const REG_VERIFIED: usize = 0x238;
160/// `VALID`.
161pub const REG_VALID: usize = 0x23c;
162/// Sign latency block (six words).
163pub const REG_SIGN_LATENCY: usize = 0x250;
164/// Verify latency block (six words).
165pub const REG_VERIFY_LATENCY: usize = 0x270;
166/// `PUBLIC_KEY` words.
167pub const REG_PUBLIC_KEY: usize = 0x2a0;
168
169// The service in four files: its port types, the latency counters, beat
170// packing, and the service itself. Everything is re-exported here.
171mod ports;
172mod latency;
173mod beats;
174mod service;
175
176pub use ports::*;
177pub use latency::*;
178pub use beats::*;
179pub use service::*;
180
181#[cfg(test)]
182mod tests;