coz_rs/alg.rs
1//! Algorithm definitions for Coz cryptographic operations.
2//!
3//! This module defines the sealed [`Algorithm`] trait and marker types for
4//! each supported algorithm: [`ES256`], [`ES384`], [`ES512`], and [`Ed25519`].
5
6use digest::Digest;
7use sha2::{Sha256, Sha384, Sha512};
8
9// ============================================================================
10// Sealed trait pattern - prevents external implementations
11// ============================================================================
12
13mod private {
14 pub trait Sealed {}
15}
16
17// ============================================================================
18// Hash Algorithm Enum
19// ============================================================================
20
21/// Runtime hash algorithm selector.
22///
23/// This enum represents the hash algorithms used by Coz signing algorithms.
24/// Multiple signing algorithms may map to the same hash algorithm
25/// (e.g., ES512 and Ed25519 both use SHA-512).
26///
27/// # Relationship with [`Algorithm`] trait
28///
29/// Each [`Algorithm`] implementation defines both:
30/// - `type Hasher: Digest` — compile-time hasher type for generic code
31/// - `const HASH_ALG: HashAlg` — runtime-accessible hash algorithm identifier
32///
33/// This ensures the compile-time and runtime representations stay in sync.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
35pub enum HashAlg {
36 /// SHA-256: 32-byte (256-bit) digest.
37 Sha256,
38 /// SHA-384: 48-byte (384-bit) digest.
39 Sha384,
40 /// SHA-512: 64-byte (512-bit) digest.
41 Sha512,
42}
43
44impl HashAlg {
45 /// Get the digest size in bytes.
46 #[must_use]
47 pub const fn digest_size(self) -> usize {
48 match self {
49 Self::Sha256 => 32,
50 Self::Sha384 => 48,
51 Self::Sha512 => 64,
52 }
53 }
54
55 /// Hash bytes using this algorithm.
56 #[must_use]
57 pub fn hash_bytes(self, data: &[u8]) -> Vec<u8> {
58 match self {
59 Self::Sha256 => Sha256::digest(data).to_vec(),
60 Self::Sha384 => Sha384::digest(data).to_vec(),
61 Self::Sha512 => Sha512::digest(data).to_vec(),
62 }
63 }
64}
65
66impl std::fmt::Display for HashAlg {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 Self::Sha256 => write!(f, "SHA-256"),
70 Self::Sha384 => write!(f, "SHA-384"),
71 Self::Sha512 => write!(f, "SHA-512"),
72 }
73 }
74}
75
76// ============================================================================
77// Algorithm trait
78// ============================================================================
79
80/// Trait for Coz-supported cryptographic algorithms.
81///
82/// This trait is sealed and cannot be implemented outside this crate.
83/// It provides associated types and constants for each algorithm.
84pub trait Algorithm: private::Sealed + Sized + 'static {
85 /// Algorithm name as it appears in JSON (e.g., "ES256").
86 const NAME: &'static str;
87
88 /// The runtime hash algorithm identifier.
89 ///
90 /// This must match the `Hasher` type (e.g., if `Hasher = Sha256`,
91 /// then `HASH_ALG = HashAlg::Sha256`).
92 const HASH_ALG: HashAlg;
93
94 /// Signature size in bytes.
95 const SIG_SIZE: usize;
96
97 /// Public key size in bytes.
98 const PUB_SIZE: usize;
99
100 /// Private key size in bytes.
101 const PRV_SIZE: usize;
102
103 /// The hashing algorithm used for digests (compile-time type).
104 type Hasher: Digest + Clone;
105}
106
107// ============================================================================
108// ECDSA Algorithms
109// ============================================================================
110
111/// ES256: ECDSA using P-256 and SHA-256.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub struct ES256;
114
115impl private::Sealed for ES256 {}
116
117impl Algorithm for ES256 {
118 type Hasher = Sha256;
119
120 const NAME: &'static str = "ES256";
121 const HASH_ALG: HashAlg = HashAlg::Sha256;
122 // Uncompressed X || Y (without 0x04 prefix)
123 const PRV_SIZE: usize = 32;
124 const PUB_SIZE: usize = 64;
125 const SIG_SIZE: usize = 64;
126}
127
128/// ES384: ECDSA using P-384 and SHA-384.
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub struct ES384;
131
132impl private::Sealed for ES384 {}
133
134impl Algorithm for ES384 {
135 type Hasher = Sha384;
136
137 const NAME: &'static str = "ES384";
138 const HASH_ALG: HashAlg = HashAlg::Sha384;
139 const PRV_SIZE: usize = 48;
140 const PUB_SIZE: usize = 96;
141 const SIG_SIZE: usize = 96;
142}
143
144/// ES512: ECDSA using P-521 and SHA-512.
145///
146/// Note: P-521 uses 66-byte components (521 bits rounded up to 528 bits = 66 bytes).
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub struct ES512;
149
150impl private::Sealed for ES512 {}
151
152impl Algorithm for ES512 {
153 type Hasher = Sha512;
154
155 const NAME: &'static str = "ES512";
156 const HASH_ALG: HashAlg = HashAlg::Sha512;
157 // 66 * 2
158 const PRV_SIZE: usize = 66;
159 // 66 * 2
160 const PUB_SIZE: usize = 132;
161 const SIG_SIZE: usize = 132;
162}
163
164// ============================================================================
165// EdDSA Algorithms
166// ============================================================================
167
168/// Ed25519: EdDSA using Curve25519.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub struct Ed25519;
171
172impl private::Sealed for Ed25519 {}
173
174impl Algorithm for Ed25519 {
175 type Hasher = Sha512;
176
177 const NAME: &'static str = "Ed25519";
178 const HASH_ALG: HashAlg = HashAlg::Sha512;
179 const PRV_SIZE: usize = 32;
180 const PUB_SIZE: usize = 32;
181 const SIG_SIZE: usize = 64; // Ed25519 internally uses SHA-512
182}
183
184// ============================================================================
185// Runtime Algorithm Selector
186// ============================================================================
187
188/// Runtime algorithm selector for type-safe dispatch.
189///
190/// Use this enum when the algorithm is determined at runtime (e.g., from user
191/// input or configuration). It provides a single parse point via [`Alg::from_str`]
192/// and methods that delegate to the generic [`Algorithm`] implementations.
193///
194/// # Example
195///
196/// ```ignore
197/// use coz::Alg;
198///
199/// let alg = Alg::from_str("ES256").expect("valid algorithm");
200/// assert_eq!(alg.name(), "ES256");
201/// ```
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
203pub enum Alg {
204 /// ES256: ECDSA using P-256 and SHA-256.
205 ES256,
206 /// ES384: ECDSA using P-384 and SHA-384.
207 ES384,
208 /// ES512: ECDSA using P-521 and SHA-512.
209 ES512,
210 /// Ed25519: EdDSA using Curve25519.
211 Ed25519,
212}
213
214impl Alg {
215 /// Parse algorithm name from string.
216 ///
217 /// Returns `None` if the algorithm is not recognized.
218 ///
219 /// # Example
220 ///
221 /// ```ignore
222 /// use coz::Alg;
223 ///
224 /// assert_eq!(Alg::from_str("ES256"), Some(Alg::ES256));
225 /// assert_eq!(Alg::from_str("unknown"), None);
226 /// ```
227 #[must_use]
228 #[allow(clippy::should_implement_trait)]
229 pub fn from_str(s: &str) -> Option<Self> {
230 match s {
231 "ES256" => Some(Self::ES256),
232 "ES384" => Some(Self::ES384),
233 "ES512" => Some(Self::ES512),
234 "Ed25519" => Some(Self::Ed25519),
235 _ => None,
236 }
237 }
238
239 /// Get the algorithm name as a static string.
240 ///
241 /// This returns the same value as `Algorithm::NAME` for the corresponding type.
242 #[must_use]
243 pub const fn name(self) -> &'static str {
244 match self {
245 Self::ES256 => ES256::NAME,
246 Self::ES384 => ES384::NAME,
247 Self::ES512 => ES512::NAME,
248 Self::Ed25519 => Ed25519::NAME,
249 }
250 }
251
252 /// Get the public key size in bytes.
253 #[must_use]
254 pub const fn pub_size(self) -> usize {
255 match self {
256 Self::ES256 => ES256::PUB_SIZE,
257 Self::ES384 => ES384::PUB_SIZE,
258 Self::ES512 => ES512::PUB_SIZE,
259 Self::Ed25519 => Ed25519::PUB_SIZE,
260 }
261 }
262
263 /// Get the private key size in bytes.
264 #[must_use]
265 pub const fn prv_size(self) -> usize {
266 match self {
267 Self::ES256 => ES256::PRV_SIZE,
268 Self::ES384 => ES384::PRV_SIZE,
269 Self::ES512 => ES512::PRV_SIZE,
270 Self::Ed25519 => Ed25519::PRV_SIZE,
271 }
272 }
273
274 /// Get the hash algorithm used by this signing algorithm.
275 ///
276 /// Multiple signing algorithms may share the same hash algorithm
277 /// (e.g., ES512 and Ed25519 both use SHA-512).
278 #[must_use]
279 pub const fn hash_alg(self) -> HashAlg {
280 match self {
281 Self::ES256 => ES256::HASH_ALG,
282 Self::ES384 => ES384::HASH_ALG,
283 Self::ES512 => ES512::HASH_ALG,
284 Self::Ed25519 => Ed25519::HASH_ALG,
285 }
286 }
287}
288
289impl std::fmt::Display for Alg {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 write!(f, "{}", self.name())
292 }
293}
294
295// ============================================================================
296// Tests
297// ============================================================================
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn algorithm_names() {
305 assert_eq!(ES256::NAME, "ES256");
306 assert_eq!(ES384::NAME, "ES384");
307 assert_eq!(ES512::NAME, "ES512");
308 assert_eq!(Ed25519::NAME, "Ed25519");
309 }
310
311 #[test]
312 fn es256_sizes() {
313 assert_eq!(ES256::SIG_SIZE, 64);
314 assert_eq!(ES256::PUB_SIZE, 64);
315 assert_eq!(ES256::PRV_SIZE, 32);
316 }
317
318 #[test]
319 fn es384_sizes() {
320 assert_eq!(ES384::SIG_SIZE, 96);
321 assert_eq!(ES384::PUB_SIZE, 96);
322 assert_eq!(ES384::PRV_SIZE, 48);
323 }
324
325 #[test]
326 fn es512_sizes() {
327 // P-521: 521 bits = 66 bytes per component
328 assert_eq!(ES512::SIG_SIZE, 132);
329 assert_eq!(ES512::PUB_SIZE, 132);
330 assert_eq!(ES512::PRV_SIZE, 66);
331 }
332
333 #[test]
334 fn ed25519_sizes() {
335 assert_eq!(Ed25519::SIG_SIZE, 64);
336 assert_eq!(Ed25519::PUB_SIZE, 32);
337 assert_eq!(Ed25519::PRV_SIZE, 32);
338 }
339}