rhdl_ed25519_scalable_core/wire.rs
1//! Port and data types at the signer boundary.
2//!
3//! These are plain [`Digital`] structs: a 512-bit message/signature, a
4//! 256-bit scalar or key, a signing request with its tag, a verification
5//! request, and the input and output ports of
6//! [`crate::ed25519_sign_core::Ed25519SignCore`]. Their bit layouts match the
7//! original `rhdl_ed25519_fast_core` types of the same names, so a design
8//! wired to that crate's signer can switch to this one without changing its
9//! port wiring.
10//!
11//! Every multi-word value is little-endian twice over: word zero is the
12//! least significant 64 bits, and within a word byte zero is the least
13//! significant byte. That is the byte order of RFC 8032 encodings, so a
14//! 32- or 64-byte buffer maps onto the words with no reordering.
15
16use rhdl::prelude::*;
17
18/// A scalar or 32-byte value as four little-endian 64-bit words.
19///
20/// Used for the secret seed, the nonce prefix, the compressed public key and
21/// any other 256-bit quantity that crosses the boundary.
22#[derive(Copy, Clone, Debug, PartialEq, Digital)]
23pub struct ScalarWords {
24 /// Little-endian words; word zero carries bits 63 through 0.
25 pub words: [b64; 4],
26}
27
28/// A 512-bit value represented as little-endian 64-bit words.
29///
30/// Holds either a 64-byte message or a 64-byte `R || S` signature.
31#[derive(Copy, Clone, Debug, PartialEq, Digital)]
32pub struct Words512 {
33 /// Little-endian words; word zero carries bits 63 through 0.
34 pub words: [b64; 8],
35}
36
37/// Host-side conversions between byte arrays and [`Words512`], plus a zero
38/// constructor for reset values.
39impl Words512 {
40 /// Pack a host byte array without hashing or changing the message.
41 ///
42 /// Byte `8 * i + j` becomes byte `j` of word `i`, so the packed value is
43 /// the array read as one little-endian 512-bit integer.
44 ///
45 /// This adapter is for drivers and testbenches, not a hardware kernel.
46 /// ```
47 /// use rhdl_ed25519_scalable_core::Words512;
48 /// let message = [0x42; 64];
49 /// assert_eq!(Words512::from_le_bytes(message).to_le_bytes(), message);
50 /// ```
51 pub fn from_le_bytes(bytes: [u8; 64]) -> Self {
52 Self {
53 // Word i is bytes 8i..8i+8 read as a little-endian u64.
54 words: std::array::from_fn(|i| {
55 b64(u64::from_le_bytes(bytes[i * 8..i * 8 + 8].try_into().unwrap()) as u128)
56 }),
57 }
58 }
59
60 /// Unpack a message or signature returned by the hardware boundary.
61 ///
62 /// Inverse of [`Self::from_le_bytes`]; for a signature the result is the
63 /// 64-byte RFC 8032 `R || S` encoding.
64 pub fn to_le_bytes(self) -> [u8; 64] {
65 let mut bytes = [0u8; 64];
66 // Each word fills its 8-byte slice, least significant byte first.
67 for (i, word) in self.words.iter().enumerate() {
68 bytes[i * 8..i * 8 + 8].copy_from_slice(&(word.raw() as u64).to_le_bytes());
69 }
70 bytes
71 }
72
73 /// Construct zero.
74 ///
75 /// Used for register reset values and idle pipeline stages.
76 pub fn zero() -> Self {
77 Self { words: [b64(0); 8] }
78 }
79}
80
81/// Signing request accepted by the cached-key fixed-64 datapath.
82///
83/// The key is not part of the request: it is loaded once and cached inside
84/// the signer, and every request is signed with it.
85#[derive(Copy, Clone, Debug, PartialEq, Digital)]
86pub struct SignRequest {
87 /// Exactly 64 message bytes, packed little-endian by 64-bit word. The
88 /// signer hashes these bytes as given; there is no other length.
89 pub message: Words512,
90 /// Opaque transaction tag. The signer stores it and returns it with the
91 /// signature without interpreting it, so a caller can match
92 /// out-of-order completions to its requests.
93 pub tag: b16,
94}
95
96/// Signer input port (the `CachedSignIn` layout shared with the original
97/// cached-key signer, so either core can sit behind the same wiring).
98#[derive(Copy, Clone, Debug, PartialEq, Digital)]
99pub struct CachedSignIn {
100 /// Loads and expands a key seed when [`CachedSignOut::load_key_ready`] is
101 /// high in the same cycle; ignored otherwise.
102 pub load_key_valid: bool,
103 /// Secret 32-byte Ed25519 seed, sampled in the cycle the load is accepted.
104 pub key_seed: ScalarWords,
105 /// Explicitly clears the cached expanded key at the next clock edge.
106 /// In-flight requests are not cancelled, so drain them first.
107 pub clear_key: bool,
108 /// Marks [`Self::request`] valid. The request is accepted in a cycle
109 /// where both this and [`CachedSignOut::ready`] are high.
110 pub valid: bool,
111 /// Fixed-64-byte signing request.
112 pub request: SignRequest,
113}
114
115/// Signer output port (the `CachedSignOut` layout).
116#[derive(Copy, Clone, Debug, PartialEq, Digital)]
117pub struct CachedSignOut {
118 /// High when a seed can be accepted: the signer is idle and no key is
119 /// loaded.
120 pub load_key_ready: bool,
121 /// High after hardware key expansion and public-key derivation complete.
122 pub key_loaded: bool,
123 /// Cached compressed public key `A` (valid while [`Self::key_loaded`]).
124 pub public_key: ScalarWords,
125 /// High when a signing request can be accepted this cycle. It can drop
126 /// for single cycles even with room (for example while an internal
127 /// challenge hash takes the SHA-512 port), so a source should offer
128 /// whenever it is high.
129 pub ready: bool,
130 /// High for one cycle when [`Self::signature`] and [`Self::tag`] are valid.
131 /// There is no output backpressure.
132 pub valid: bool,
133 /// `R || S` signature, packed in little-endian words: words 0..4 are `R`,
134 /// words 4..8 are `S`.
135 pub signature: Words512,
136 /// Tag of the completed request, as given in [`SignRequest::tag`].
137 /// Completions can be out of order.
138 pub tag: b16,
139 /// Number of occupied signing contexts (requests in flight), saturated at
140 /// 255.
141 pub occupancy: b8,
142 /// Sticky scheduler overflow or invariant failure.
143 pub internal_error: bool,
144}
145
146/// Verification request: exactly 64 message bytes, a public key and a
147/// signature (the `VerifyRequest` layout of `rhdl_ed25519_fast_core`).
148#[derive(Copy, Clone, Debug, PartialEq, Digital)]
149pub struct VerifyRequest {
150 /// Exactly 64 message bytes.
151 pub message: Words512,
152 /// Compressed public key `A`.
153 pub public_key: ScalarWords,
154 /// `R || S`, packed as eight little-endian words.
155 pub signature: Words512,
156 /// Opaque transaction tag, returned with the result.
157 pub tag: b16,
158}