dcrypt_sign/ecdsa/p224/
mod.rs

1//! ECDSA implementation for NIST P-224 curve
2//!
3//! This implementation follows FIPS 186-4: Digital Signature Standard (DSS)
4//! and SP 800-56A Rev. 3: Recommendation for Pair-Wise Key-Establishment Schemes
5//! Using Discrete Logarithm Cryptography. SHA-224 is used as the hash function.
6
7use crate::ecdsa::common::SignatureComponents;
8use dcrypt_algorithms::ec::p224 as ec;
9use dcrypt_algorithms::hash::sha2::{Sha224, Sha224Algorithm}; // Import Sha224Algorithm for BLOCK_SIZE
10use dcrypt_algorithms::hash::{HashAlgorithm, HashFunction}; // Import HashAlgorithm for BLOCK_SIZE
11use dcrypt_algorithms::mac::hmac::Hmac;
12use dcrypt_api::{error::Error as ApiError, Result as ApiResult, Signature as SignatureTrait};
13use dcrypt_internal::constant_time::ct_eq;
14use rand::{CryptoRng, RngCore};
15use zeroize::Zeroize; // ZeroizeOnDrop is implicitly handled by ec::Scalar's own Drop
16
17/// ECDSA signature scheme using NIST P-224 curve (secp224r1)
18pub struct EcdsaP224;
19
20/// P-224 public key in uncompressed format (0x04 || X || Y)
21#[derive(Clone, Zeroize)]
22pub struct EcdsaP224PublicKey(pub [u8; ec::P224_POINT_UNCOMPRESSED_SIZE]);
23
24/// P-224 secret key
25#[derive(Clone)]
26pub struct EcdsaP224SecretKey {
27    raw: ec::Scalar,
28    bytes: [u8; ec::P224_SCALAR_SIZE],
29}
30
31impl Zeroize for EcdsaP224SecretKey {
32    fn zeroize(&mut self) {
33        self.bytes.zeroize();
34        // self.raw is an ec::Scalar, which implements ZeroizeOnDrop.
35    }
36}
37
38impl Drop for EcdsaP224SecretKey {
39    fn drop(&mut self) {
40        self.zeroize();
41    }
42}
43
44/// P-224 signature encoded in ASN.1 DER format
45#[derive(Clone)]
46pub struct EcdsaP224Signature(pub Vec<u8>);
47
48// AsRef/AsMut implementations
49impl AsRef<[u8]> for EcdsaP224PublicKey {
50    fn as_ref(&self) -> &[u8] {
51        &self.0
52    }
53}
54impl AsMut<[u8]> for EcdsaP224PublicKey {
55    fn as_mut(&mut self) -> &mut [u8] {
56        &mut self.0
57    }
58}
59impl AsRef<[u8]> for EcdsaP224SecretKey {
60    fn as_ref(&self) -> &[u8] {
61        &self.bytes
62    }
63}
64// REMOVED: AsMut<[u8]> for EcdsaP224SecretKey to prevent direct mutation of secret key bytes
65
66impl AsRef<[u8]> for EcdsaP224Signature {
67    fn as_ref(&self) -> &[u8] {
68        &self.0
69    }
70}
71impl AsMut<[u8]> for EcdsaP224Signature {
72    fn as_mut(&mut self) -> &mut [u8] {
73        &mut self.0
74    }
75}
76
77impl SignatureTrait for EcdsaP224 {
78    type PublicKey = EcdsaP224PublicKey;
79    type SecretKey = EcdsaP224SecretKey;
80    type SignatureData = EcdsaP224Signature;
81    type KeyPair = (Self::PublicKey, Self::SecretKey);
82
83    fn name() -> &'static str {
84        "ECDSA-P224"
85    }
86
87    fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
88        let (sk_scalar, pk_point) = ec::generate_keypair(rng).map_err(ApiError::from)?;
89
90        let sk_bytes: [u8; ec::P224_SCALAR_SIZE] = sk_scalar.serialize();
91
92        if sk_scalar.is_zero() {
93            return Err(ApiError::InvalidParameter {
94                context: "ECDSA-P224 keypair",
95                #[cfg(feature = "std")]
96                message: "Generated secret key is zero".to_string(),
97            });
98        }
99
100        let secret_key = EcdsaP224SecretKey {
101            raw: sk_scalar,
102            bytes: sk_bytes,
103        };
104        let public_key = EcdsaP224PublicKey(pk_point.serialize_uncompressed());
105        Ok((public_key, secret_key))
106    }
107
108    fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
109        keypair.0.clone()
110    }
111    fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
112        keypair.1.clone()
113    }
114
115    fn sign(message: &[u8], secret_key: &Self::SecretKey) -> ApiResult<Self::SignatureData> {
116        let mut hasher = Sha224::new();
117        hasher.update(message).map_err(ApiError::from)?;
118        let hash_output = hasher.finalize().map_err(ApiError::from)?;
119
120        let mut z_bytes_fixed_size = [0u8; ec::P224_SCALAR_SIZE];
121        z_bytes_fixed_size.copy_from_slice(hash_output.as_ref());
122        let z = reduce_bytes_to_scalar_p224(&z_bytes_fixed_size)?;
123
124        let d = secret_key.raw.clone();
125        let mut rng = rand::thread_rng();
126
127        loop {
128            let k = deterministic_k_hedged_p224(&d, &z, &mut rng);
129
130            let kg = ec::scalar_mult_base_g(&k).map_err(ApiError::from)?;
131
132            if kg.is_identity() {
133                continue;
134            }
135            let r_bytes = kg.x_coordinate_bytes();
136
137            let r = match reduce_bytes_to_scalar_p224(&r_bytes) {
138                Ok(scalar) if !scalar.is_zero() => scalar,
139                _ => continue,
140            };
141
142            let k_inv = k.inv_mod_n().map_err(ApiError::from)?;
143            let rd = r.mul_mod_n(&d).map_err(ApiError::from)?;
144            let z_plus_rd = z.add_mod_n(&rd).map_err(ApiError::from)?;
145            let s = k_inv.mul_mod_n(&z_plus_rd).map_err(ApiError::from)?;
146
147            if s.is_zero() {
148                continue;
149            }
150
151            let sig_comps = SignatureComponents {
152                r: r.serialize().to_vec(),
153                s: s.serialize().to_vec(),
154            };
155            return Ok(EcdsaP224Signature(sig_comps.to_der()));
156        }
157    }
158
159    fn verify(
160        message: &[u8],
161        signature: &Self::SignatureData,
162        public_key: &Self::PublicKey,
163    ) -> ApiResult<()> {
164        let sig_comps = SignatureComponents::from_der(&signature.0)?;
165
166        if sig_comps.r.len() > ec::P224_SCALAR_SIZE || sig_comps.s.len() > ec::P224_SCALAR_SIZE {
167            return Err(ApiError::InvalidSignature {
168                context: "ECDSA-P224 verify",
169                #[cfg(feature = "std")]
170                message: "Invalid signature component size".to_string(),
171            });
172        }
173
174        let mut r_bytes = [0u8; ec::P224_SCALAR_SIZE];
175        let mut s_bytes = [0u8; ec::P224_SCALAR_SIZE];
176        let r_offset = ec::P224_SCALAR_SIZE.saturating_sub(sig_comps.r.len());
177        let s_offset = ec::P224_SCALAR_SIZE.saturating_sub(sig_comps.s.len());
178        r_bytes[r_offset..].copy_from_slice(&sig_comps.r);
179        s_bytes[s_offset..].copy_from_slice(&sig_comps.s);
180
181        let r = ec::Scalar::new(r_bytes).map_err(|_| ApiError::InvalidSignature {
182            context: "ECDSA-P224 verify",
183            #[cfg(feature = "std")]
184            message: "Invalid r component".to_string(),
185        })?;
186        let s = ec::Scalar::new(s_bytes).map_err(|_| ApiError::InvalidSignature {
187            context: "ECDSA-P224 verify",
188            #[cfg(feature = "std")]
189            message: "Invalid s component".to_string(),
190        })?;
191
192        let mut hasher = Sha224::new();
193        hasher.update(message).map_err(ApiError::from)?;
194        let hash_output = hasher.finalize().map_err(ApiError::from)?;
195
196        let mut z_bytes_fixed_size = [0u8; ec::P224_SCALAR_SIZE];
197        z_bytes_fixed_size.copy_from_slice(hash_output.as_ref());
198        let z = reduce_bytes_to_scalar_p224(&z_bytes_fixed_size)?;
199
200        let s_inv = s.inv_mod_n().map_err(ApiError::from)?;
201        let u1 = z.mul_mod_n(&s_inv).map_err(ApiError::from)?;
202        let u2 = r.mul_mod_n(&s_inv).map_err(ApiError::from)?;
203
204        let q_point = ec::Point::deserialize_uncompressed(&public_key.0).map_err(ApiError::from)?;
205
206        if q_point.is_identity() {
207            return Err(ApiError::InvalidKey {
208                context: "ECDSA-P224 verify",
209                #[cfg(feature = "std")]
210                message: "Public key is the point at infinity".to_string(),
211            });
212        }
213
214        let u1g = ec::scalar_mult_base_g(&u1).map_err(ApiError::from)?;
215        let u2q = ec::scalar_mult(&u2, &q_point).map_err(ApiError::from)?;
216
217        let point = u1g.add(&u2q);
218
219        if point.is_identity() {
220            return Err(ApiError::InvalidSignature {
221                context: "ECDSA-P224 verify",
222                #[cfg(feature = "std")]
223                message: "Verification point is identity".to_string(),
224            });
225        }
226
227        let x1_bytes = point.x_coordinate_bytes();
228        let v = reduce_bytes_to_scalar_p224(&x1_bytes)?;
229
230        if !ct_eq(r.serialize(), v.serialize()) {
231            return Err(ApiError::InvalidSignature {
232                context: "ECDSA-P224 verify",
233                #[cfg(feature = "std")]
234                message: "Signature verification failed (r != v)".to_string(),
235            });
236        }
237        Ok(())
238    }
239}
240
241fn deterministic_k_hedged_p224<R: RngCore + CryptoRng>(
242    d: &ec::Scalar,
243    z: &ec::Scalar,
244    rng: &mut R,
245) -> ec::Scalar {
246    use zeroize::Zeroize;
247    let hash_len = Sha224Algorithm::OUTPUT_SIZE; // 28 bytes
248
249    let mut rbuf = [0u8; ec::P224_SCALAR_SIZE];
250    rng.fill_bytes(&mut rbuf[..hash_len]);
251
252    let mut v_hmac_block = [0x01u8; Sha224Algorithm::BLOCK_SIZE];
253    let mut k_hmac_block = [0x00u8; Sha224Algorithm::BLOCK_SIZE];
254
255    // Step C
256    {
257        let mut mac = Hmac::<Sha224>::new(&k_hmac_block).unwrap();
258        mac.update(&v_hmac_block).unwrap();
259        mac.update(&[0x00]).unwrap();
260        mac.update(&d.serialize()).unwrap();
261        mac.update(&z.serialize()).unwrap();
262        mac.update(&rbuf[..hash_len]).unwrap();
263        let mac_res = mac.finalize().unwrap();
264        k_hmac_block[..hash_len].copy_from_slice(&mac_res);
265        k_hmac_block[hash_len..].fill(0);
266    }
267    // Step D
268    let v_new_res = Hmac::<Sha224>::mac(&k_hmac_block, &v_hmac_block).unwrap();
269    v_hmac_block[..hash_len].copy_from_slice(&v_new_res);
270    v_hmac_block[hash_len..].fill(0);
271    // Step E
272    {
273        let mut mac = Hmac::<Sha224>::new(&k_hmac_block).unwrap();
274        mac.update(&v_hmac_block).unwrap();
275        mac.update(&[0x01]).unwrap();
276        mac.update(&d.serialize()).unwrap();
277        mac.update(&z.serialize()).unwrap();
278        mac.update(&rbuf[..hash_len]).unwrap();
279        let mac_res = mac.finalize().unwrap();
280        k_hmac_block[..hash_len].copy_from_slice(&mac_res);
281        k_hmac_block[hash_len..].fill(0);
282    }
283    // Step F
284    let v_new_res = Hmac::<Sha224>::mac(&k_hmac_block, &v_hmac_block).unwrap();
285    v_hmac_block[..hash_len].copy_from_slice(&v_new_res);
286    v_hmac_block[hash_len..].fill(0);
287
288    // Step G/H
289    loop {
290        let v_new_res = Hmac::<Sha224>::mac(&k_hmac_block, &v_hmac_block).unwrap();
291        v_hmac_block[..hash_len].copy_from_slice(&v_new_res);
292        v_hmac_block[hash_len..].fill(0);
293
294        let mut candidate_scalar_bytes = [0u8; ec::P224_SCALAR_SIZE];
295        candidate_scalar_bytes.copy_from_slice(&v_hmac_block[..ec::P224_SCALAR_SIZE]);
296
297        // Attempt to create a scalar. ec::Scalar::new() will perform reduction and check for zero.
298        if let Ok(candidate) = ec::Scalar::new(candidate_scalar_bytes) {
299            // If candidate is valid (non-zero and < n), return it.
300            rbuf.zeroize();
301            return candidate;
302        }
303
304        // Retry path (step H): update K and V if candidate was invalid (e.g., zero after reduction)
305        let mut mac = Hmac::<Sha224>::new(&k_hmac_block).unwrap();
306        mac.update(&v_hmac_block).unwrap();
307        mac.update(&[0x00]).unwrap();
308        let mac_res = mac.finalize().unwrap();
309        k_hmac_block[..hash_len].copy_from_slice(&mac_res);
310        k_hmac_block[hash_len..].fill(0);
311
312        let v_new_res = Hmac::<Sha224>::mac(&k_hmac_block, &v_hmac_block).unwrap();
313        v_hmac_block[..hash_len].copy_from_slice(&v_new_res);
314        v_hmac_block[hash_len..].fill(0);
315    }
316}
317
318fn reduce_bytes_to_scalar_p224(bytes: &[u8; ec::P224_SCALAR_SIZE]) -> ApiResult<ec::Scalar> {
319    ec::Scalar::new(*bytes).map_err(|algo_err| {
320        match algo_err {
321            dcrypt_algorithms::error::Error::Parameter {
322                ref name,
323                ref reason,
324            } if name.as_ref() == "P-224 Scalar"
325                && reason.as_ref().contains("Scalar cannot be zero") =>
326            {
327                ApiError::InvalidSignature {
328                    context: "ECDSA-P224 scalar reduction",
329                    #[cfg(feature = "std")]
330                    message: "Computed scalar component is zero or invalid".to_string(),
331                }
332            }
333            // Catch-all for other algo errors, converting them directly to ApiError
334            _ => ApiError::from(algo_err),
335        }
336    })
337}
338
339#[cfg(test)]
340mod tests;