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}