Skip to main content

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}