Skip to main content

rhdl_ed25519_fast_core/
protocol.rs

1//! Stable typed protocol for the fixed-64 signing and verification core.
2
3use rhdl::prelude::*;
4
5use crate::scalar::ScalarWords;
6
7/// A 512-bit value represented as little-endian 64-bit words.
8#[derive(Copy, Clone, Debug, PartialEq, Digital)]
9pub struct Words512 {
10    /// Little-endian words; word zero carries bits 63 through 0.
11    pub words: [b64; 8],
12}
13
14impl Words512 {
15    /// Pack a host byte array without hashing or changing the message.
16    ///
17    /// This adapter is for drivers and testbenches, not a hardware kernel.
18    /// ```
19    /// use rhdl_ed25519_fast_core::Words512;
20    /// let message = [0x42; 64];
21    /// assert_eq!(Words512::from_le_bytes(message).to_le_bytes(), message);
22    /// ```
23    pub fn from_le_bytes(bytes: [u8; 64]) -> Self {
24        Self {
25            words: std::array::from_fn(|i| {
26                b64(u64::from_le_bytes(bytes[i * 8..i * 8 + 8].try_into().unwrap()) as u128)
27            }),
28        }
29    }
30
31    /// Unpack a message or signature returned by the hardware boundary.
32    pub fn to_le_bytes(self) -> [u8; 64] {
33        let mut bytes = [0u8; 64];
34        for (i, word) in self.words.iter().enumerate() {
35            bytes[i * 8..i * 8 + 8].copy_from_slice(&(word.raw() as u64).to_le_bytes());
36        }
37        bytes
38    }
39
40    /// Construct zero.
41    pub fn zero() -> Self {
42        Self { words: [b64(0); 8] }
43    }
44}
45
46/// Signing request accepted by the cached-key fixed-64 datapath.
47#[derive(Copy, Clone, Debug, PartialEq, Digital)]
48pub struct SignRequest {
49    /// Exactly 64 message bytes, packed little-endian by 64-bit word.
50    pub message: Words512,
51    /// Opaque transaction tag.
52    pub tag: b16,
53}
54
55/// Strict-verification request accepted by the fixed-64 datapath.
56#[derive(Copy, Clone, Debug, PartialEq, Digital)]
57pub struct VerifyRequest {
58    /// Exactly 64 message bytes.
59    pub message: Words512,
60    /// Canonical compressed public key.
61    pub public_key: ScalarWords,
62    /// `R || S`, packed as eight little-endian words.
63    pub signature: Words512,
64    /// Opaque transaction tag.
65    pub tag: b16,
66}
67
68/// Construct an inactive, all-zero strict-verification request.
69pub fn zero_verify_request() -> VerifyRequest {
70    VerifyRequest {
71        message: Words512::zero(),
72        public_key: ScalarWords { words: [b64(0); 4] },
73        signature: Words512::zero(),
74        tag: b16(0),
75    }
76}
77
78/// A retired sign or strict-verification operation.
79#[derive(Copy, Clone, Debug, PartialEq, Digital)]
80pub struct CoreResult {
81    /// Marks this result valid for the current cycle.
82    pub valid: bool,
83    /// Opaque request tag.
84    pub tag: b16,
85    /// Signature for signing results; zero for verification results.
86    pub signature: Words512,
87    /// Distinguishes verification from signing results.
88    pub is_verify: bool,
89    /// Strict-verification decision.
90    pub verify_ok: bool,
91    /// Stable error code; zero means no error.
92    pub error: b8,
93}
94
95/// Public input contract of the fixed-64 black box.
96#[derive(Copy, Clone, Debug, PartialEq, Digital)]
97pub struct Fast64CoreIn {
98    /// Loads and expands a key seed when `load_key_ready` is asserted.
99    pub load_key_valid: bool,
100    /// Secret 32-byte seed.
101    pub key_seed: ScalarWords,
102    /// Explicitly zeroizes cached key state.
103    pub clear_key: bool,
104    /// Marks [`Self::sign`] valid.
105    pub sign_valid: bool,
106    /// Cached-key signing request.
107    pub sign: SignRequest,
108    /// Marks [`Self::verify`] valid.
109    pub verify_valid: bool,
110    /// Strict-verification request.
111    pub verify: VerifyRequest,
112}
113
114/// Public output contract of the fixed-64 black box.
115#[derive(Copy, Clone, Debug, PartialEq, Digital)]
116pub struct Fast64CoreOut {
117    /// Key-load channel readiness.
118    pub load_key_ready: bool,
119    /// Whether a cached expanded key is available.
120    pub key_loaded: bool,
121    /// Public key corresponding to the cached seed.
122    pub cached_public_key: ScalarWords,
123    /// Signing-channel readiness.
124    pub sign_ready: bool,
125    /// Strict-verification-channel readiness.
126    pub verify_ready: bool,
127    /// Retired operation.
128    pub result: CoreResult,
129    /// Sticky internal invariant failure.
130    pub internal_error: bool,
131}
132
133/// Return an inactive black-box result.
134pub fn idle_result() -> CoreResult {
135    CoreResult {
136        valid: false,
137        tag: b16(0),
138        signature: Words512::zero(),
139        is_verify: false,
140        verify_ok: false,
141        error: b8(0),
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn packed_black_box_abi_is_stable() {
151        assert_eq!(Words512::BITS, 512);
152        assert_eq!(ScalarWords::BITS, 256);
153        assert_eq!(SignRequest::BITS, 528);
154        assert_eq!(VerifyRequest::BITS, 1296);
155        assert_eq!(Fast64CoreIn::BITS, 2084);
156        assert_eq!(CoreResult::BITS, 539);
157        assert_eq!(Fast64CoreOut::BITS, 800);
158    }
159}