Skip to main content

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}