Skip to main content

rhdl_dsig_scalable_core/ubft/
mod.rs

1//! DSig as the signature service of a uBFT replica (feature `ubft`).
2//!
3//! # The problem
4//!
5//! [uBFT](https://github.com/YiJiang0908/ubft_new) is a consensus engine that
6//! runs entirely on an Alveo U280: a Vitis HLS state machine (`ubft`) that
7//! talks to its peers through a RoCE stack and keeps its mailboxes in HBM.
8//! On its slow path every replica signs a 64-bit summary of the slot, sends
9//! the signature to every peer, and verifies the shares it receives until
10//! it holds `2f + 1`. It does both with the Vitis Security Library's
11//! Ed25519 (`xf::security::eddsaEd25519`), called inline: the replica's
12//! whole state machine stops for every signature and every verification,
13//! and that signer is the routed design's worst path (−2.866 ns), which
14//! pulls the shared kernel clock from 300 MHz down to 161.3 MHz.
15//!
16//! The rest of this crate signs and verifies in *batches*: the host fills
17//! HBM, starts a kernel, and reads frames or verdicts back when it is done.
18//! A consensus engine cannot work that way. It needs a service that sits
19//! next to it on the chip, takes one request at a time on a stream, and
20//! answers in microseconds.
21//!
22//! # The idea
23//!
24//! [`UbftEngine`] is that service: the same slices, certificate unit, verify
25//! core and Ed25519 verify core as the batch kernels, with AXI4-Stream ports
26//! where those have HBM masters.
27//!
28//! ```text
29//!   uBFT (HLS) ──sign request──▶ ┌──────────────── UbftEngine ───────────────┐
30//!              ◀──26-beat frame── │ UbftSigner: dealer ▶ slices ▶ arbiter     │
31//!                                 │             cert unit (Ed25519 signer)    │
32//!   uBFT (HLS) ──verify request─▶ │ UbftVerifier: framing ▶ verify core ▶ tags│
33//!              ◀──verdict word─── │ Ed25519 verify core, 8 trusted key slots  │
34//!                                 └──────── AXI4-Lite: keys, counters ────────┘
35//! ```
36//!
37//! * **Signing.** A request is one 64-byte beat: the 16-byte message and an
38//!   8-byte tag. The dealer hands it to the first idle slice; the slice
39//!   signs it with a one-time key it made in advance, and the frame (26
40//!   beats, the format of [`crate::slice_signer`], the tag in the message
41//!   index field) leaves on the frame stream. Keys are made all the time in
42//!   the background, so a request never waits for key generation.
43//! * **Verifying.** A request is a header beat (the signer's key slot and a
44//!   32-bit tag) followed by the 26 beats of the frame exactly as the
45//!   signer produced it. The verdict word comes back in request order.
46//! * **Peers.** uBFT has one signer per replica, so the verifier trusts up to
47//!   [`KEY_SLOTS`] Ed25519 keys at once (replicas `N ≤ 9`). The certificate
48//!   cache keeps the key slot with every entry (see
49//!   [`crate::verify::certs`]): a certificate checked for one replica never
50//!   answers for another.
51//!
52//! # Streams
53//!
54//! | Port | Direction | Beat | Content |
55//! |---|---|---|---|
56//! | `s_axis_sign` | in | 512 bits | bytes 0..16 message, 16..24 tag (LE64) |
57//! | `m_axis_frame` | out | 512 bits, `tlast` on beat 25 | the frame: 1,642-byte signature, tag at 1642..1650 |
58//! | `s_axis_verify` | in | 512 bits, `tlast` on beat 26 | beat 0: byte 0 key slot (4 bits), bytes 4..8 tag (LE32); beats 1..26: the frame |
59//! | `m_axis_verdict` | out | 64 bits | bits 0..8 verdict ([`crate::verify`]), 8..12 key slot, 32..64 tag |
60//! | `m_axis_cert` | out | 512 bits, `tlast` on beat 1 | this signer's new batch certificate: commitment, then Ed25519 signature |
61//! | `s_axis_cert` | in | 512 bits, `tlast` on beat 2 | a peer's announced certificate: header (byte 0 key slot), commitment, signature |
62//!
63//! The certificate streams are optional: uBFT forwards `m_axis_cert` to
64//! its peers and feeds theirs into `s_axis_cert`, so a batch's certificate
65//! is checked before its first signature arrives (a cache miss otherwise
66//! costs one Ed25519 check, ~52,670 cycles). Leave `s_axis_cert` idle and
67//! `m_axis_cert` drained to skip it.
68//!
69//! A verdict of `0x80` means valid; any other value has bit 7 set and names
70//! the failed checks.
71//!
72//! # Register map (32-bit words)
73//!
74//! | Offset | Name | Access |
75//! |---|---|---|
76//! | `0x000` | `AP_CTRL`: bit 0 `ap_start`, 1 `ap_done` (clear on read), 2 `ap_idle`, 3 `ap_ready` | RW |
77//! | `0x004`/`0x008`/`0x00c` | `GIE` / `IER` / `ISR` | RW |
78//! | `0x010` | arg `sign_count`: signatures this session (u32) | W |
79//! | `0x014` | arg `verify_count`: verifications this session (u32) | W |
80//! | `0x018` | arg `batches`: key batches to make (u32, 128 keys each) | W |
81//! | `0x020` | arg `epoch` (u64) | W |
82//! | `0x028` | arg `first_batch` (u64) | W |
83//! | `0x030 + 4w` | args `cv0..cv7`: derive-key prefix CV (secret) | W |
84//! | `0x050 + 4w` | args `seed0..seed7`: Ed25519 seed (secret) | W |
85//! | `0x070` | arg `key_mask`: trusted key slots in use (bits 0..8) | W |
86//! | `0x080 + 0x20·s + 4w` | args `key{s}_{w}`: slot `s`'s Ed25519 public key, bytes `4w..4w+4` LE | W |
87//! | `0x180 + 8·s` | args `epoch{s}` (u64, lo then hi): slot `s`'s signer's epoch this session | W |
88//! | `0x1c0` | arg `epoch_mask`: slots whose epoch is enforced (bits 0..8; see [`verifier`], session epochs) | W |
89//! | `0x200` | `MAGIC` = [`MAGIC`] ("DSU1") | R |
90//! | `0x204` | `ABI` = [`ABI_VERSION`] | R |
91//! | `0x208` | `SHAPE`: slices bits 0..8, table mask 8..24, key slots 24..28 | R |
92//! | `0x20c` | `FAULT`: bit 0 slice, 1 certificate unit, 2 Ed25519 verify core, 3 a trusted key rejected, 4 verify stream framing | R |
93//! | `0x210`/`0x214` | `TICKS` lo (snapshots hi) / hi | R |
94//! | `0x218`/`0x21c` | `START_TICK` lo/hi | R |
95//! | `0x220`/`0x224` | `DONE_TICK` lo/hi | R |
96//! | `0x228`/`0x22c` | `KEY_TICK` lo/hi: the trusted keys finished loading | R |
97//! | `0x230` | `SIGNED`: frames sent | R |
98//! | `0x234` | `SIGN_TAKEN`: sign requests accepted | R |
99//! | `0x238` | `VERIFIED`: verdicts sent | R |
100//! | `0x23c` | `VALID`: verdicts that were `0x80` | R |
101//! | `0x240` | `CERT_CHECKS`: Ed25519 verifications issued | R |
102//! | `0x244` | `CERT_FAILS` | R |
103//! | `0x248` | `CERTIFIED`: batches the signer certified | R |
104//! | `0x24c` | `ANNOUNCED`: certificates sent on `m_axis_cert` | R |
105//! | `0x250 + 4i` | sign latency: `N`, `LAST`, `MIN`, `MAX`, `SUM` lo, `SUM` hi | R |
106//! | `0x270 + 4i` | verify latency: same six words | R |
107//! | `0x2a0 + 4w` | `PUBLIC_KEY`: the signer's Ed25519 public key (8 × u32) | R |
108//!
109//! # How latency is measured
110//!
111//! The kernel times requests on its own clock. A request that arrives while
112//! no other request of its kind is in flight starts a measurement: the tick
113//! of its first beat's handshake. The measurement ends on the handshake of
114//! its answer's last beat, if no second request arrived meanwhile. So `N`
115//! counts *isolated* requests, and `LAST`/`MIN`/`MAX`/`SUM` are their
116//! latencies in cycles, request beat to answer beat, with nothing else in
117//! the service. Loaded latency is not measured here.
118//!
119//! # The rules from the batch kernels still hold
120//!
121//! One session per reset: the kernel never restarts, the host gives every
122//! session a fresh namespace (epoch, batch range, master seed), and the
123//! secrets have no read path. A one-time key is burned before its frame
124//! leaves, so a stalled frame stream can never cause a key to sign twice.
125
126pub mod card;
127pub mod engine;
128pub mod probe;
129pub mod signer;
130pub mod verifier;
131
132pub use card::{CardInput, CardOutput, UbftCard};
133pub use engine::{CfgWrite, UbftEngine, UbftInput, UbftOutput};
134pub use probe::UbftProbe;
135pub use signer::UbftSigner;
136pub use verifier::UbftVerifier;
137
138use rhdl::prelude::*;
139
140/// `MAGIC` register value ("DSU1" in little-endian ASCII).
141pub const MAGIC: b32 = b32(0x3155_5344);
142/// Register-map version.
143pub const ABI_VERSION: b32 = b32(0x0001_0001);
144/// Trusted Ed25519 keys the verifier holds at once (the verify core's key
145/// slots): replicas `N ≤ KEY_SLOTS + 1`.
146pub const KEY_SLOTS: usize = 8;
147/// Verify requests that may be in flight (the verify core's contexts).
148pub const VERIFY_IN_FLIGHT: usize = 64;
149/// Beats of a verify request: the header and the 26-beat frame.
150pub const VERIFY_REQUEST_BEATS: usize = 27;
151
152/// Offsets of the host arguments.
153pub const ARG_SIGN_COUNT: usize = 0x010;
154/// `verify_count`.
155pub const ARG_VERIFY_COUNT: usize = 0x014;
156/// `batches`.
157pub const ARG_BATCHES: usize = 0x018;
158/// `epoch` (u64).
159pub const ARG_EPOCH: usize = 0x020;
160/// `first_batch` (u64).
161pub const ARG_FIRST_BATCH: usize = 0x028;
162/// `cv0`.
163pub const ARG_CV: usize = 0x030;
164/// `seed0`.
165pub const ARG_SEED: usize = 0x050;
166/// `key_mask`.
167pub const ARG_KEY_MASK: usize = 0x070;
168/// `key0_0`; slot `s`, word `w` at `+ 0x20·s + 4w`.
169pub const ARG_KEYS: usize = 0x080;
170/// `epoch{s}` (slot 0's low word; high word at +4, slots at +8).
171pub const ARG_EPOCHS: usize = 0x180;
172/// `epoch_mask`.
173pub const ARG_EPOCH_MASK: usize = 0x1c0;
174/// Status registers.
175pub const REG_MAGIC: usize = 0x200;
176/// `ABI`.
177pub const REG_ABI: usize = 0x204;
178/// `SHAPE`.
179pub const REG_SHAPE: usize = 0x208;
180/// `FAULT`.
181pub const REG_FAULT: usize = 0x20c;
182/// `TICKS` lo.
183pub const REG_TICKS: usize = 0x210;
184/// `START_TICK` lo.
185pub const REG_START_TICK: usize = 0x218;
186/// `DONE_TICK` lo.
187pub const REG_DONE_TICK: usize = 0x220;
188/// `KEY_TICK` lo.
189pub const REG_KEY_TICK: usize = 0x228;
190/// `SIGNED`.
191pub const REG_SIGNED: usize = 0x230;
192/// `SIGN_TAKEN`.
193pub const REG_SIGN_TAKEN: usize = 0x234;
194/// `VERIFIED`.
195pub const REG_VERIFIED: usize = 0x238;
196/// `VALID`.
197pub const REG_VALID: usize = 0x23c;
198/// `CERT_CHECKS`.
199pub const REG_CERT_CHECKS: usize = 0x240;
200/// `CERT_FAILS`.
201pub const REG_CERT_FAILS: usize = 0x244;
202/// `CERTIFIED`.
203pub const REG_CERTIFIED: usize = 0x248;
204/// `ANNOUNCED`.
205pub const REG_ANNOUNCED: usize = 0x24c;
206/// Sign latency block (six words).
207pub const REG_SIGN_LATENCY: usize = 0x250;
208/// Verify latency block (six words).
209pub const REG_VERIFY_LATENCY: usize = 0x270;
210/// `PUBLIC_KEY` words.
211pub const REG_PUBLIC_KEY: usize = 0x2a0;
212
213/// One 512-bit stream beat (`tvalid`, `tdata`, `tlast`; `tready` travels
214/// the other way as a plain `bool`).
215#[derive(Clone, Copy, Debug, PartialEq, Digital)]
216pub struct Axis512 {
217    /// `tvalid`.
218    pub valid: bool,
219    /// `tdata`, byte 0 in bits 0..8.
220    pub data: [b8; 64],
221    /// `tlast`.
222    pub last: bool,
223}
224
225impl Axis512 {
226    /// No beat.
227    #[must_use]
228    pub fn idle() -> Self {
229        Self {
230            valid: false,
231            data: [b8(0); 64],
232            last: false,
233        }
234    }
235}
236
237/// One 64-bit stream beat (the verdict stream).
238#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
239pub struct Axis64 {
240    /// `tvalid`.
241    pub valid: bool,
242    /// `tdata`.
243    pub data: b64,
244}
245
246/// Isolated-request latency statistics, in cycles.
247#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
248pub struct Latency {
249    /// A measurement is open.
250    pub open: bool,
251    /// Its start tick.
252    pub start: b64,
253    /// Measurements taken.
254    pub n: b32,
255    /// The last one.
256    pub last: b32,
257    /// The smallest.
258    pub min: b32,
259    /// The largest.
260    pub max: b32,
261    /// Their sum.
262    pub sum: b64,
263}
264
265/// Opens or closes a latency measurement.
266///
267/// * `begin`: a request handshake happened while `in_flight` (before it)
268///   was zero.
269/// * `end`: an answer's last handshake happened while `in_flight` (before
270///   it) was one.
271/// * `abandon`: a second request arrived while one was measured.
272#[kernel]
273pub fn latency_kernel(l: Latency, ticks: b64, begin: bool, end: bool, abandon: bool) -> Latency {
274    let mut n = l;
275    if end && l.open {
276        let sample: b32 = (ticks - l.start).resize();
277        n.open = false;
278        n.n = l.n + b32(1);
279        n.last = sample;
280        n.min = if l.n == b32(0) || sample < l.min { sample } else { l.min };
281        n.max = if sample > l.max { sample } else { l.max };
282        n.sum = l.sum + sample.resize::<64>();
283    }
284    if abandon {
285        n.open = false;
286    }
287    if begin {
288        n.open = true;
289        n.start = ticks;
290    }
291    n
292}