Skip to main content

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::{
8    bits2octets, is_canonical_nonzero_scalar, is_high_s, Rfc6979, SignatureComponents,
9};
10use alloc::vec::Vec;
11use dcrypt_algorithms::ec::p224 as ec;
12use dcrypt_algorithms::hash::sha2::Sha224;
13use dcrypt_algorithms::hash::HashFunction;
14use dcrypt_api::{
15    error::Error as ApiError, Result as ApiResult, Signature as SignatureTrait, ZeroizingBytes,
16};
17use dcrypt_common::SecretBuffer;
18use dcrypt_internal::{
19    constant_time::ct_eq, zeroizing_bytes_from_slice, CryptoRng, RngCore, Zeroize, ZeroizeOnDrop,
20    Zeroizing,
21};
22use dcrypt_params::traditional::ecdsa::NIST_P224;
23
24/// ECDSA signature scheme using NIST P-224 curve (secp224r1)
25pub struct EcdsaP224;
26
27/// P-224 public key in uncompressed format (0x04 || X || Y)
28#[derive(Clone)]
29pub struct EcdsaP224PublicKey(pub(crate) [u8; ec::P224_POINT_UNCOMPRESSED_SIZE]);
30
31/// P-224 secret key
32#[derive(Clone)]
33pub struct EcdsaP224SecretKey {
34    raw: ec::Scalar,
35    bytes: SecretBuffer<{ ec::P224_SCALAR_SIZE }>,
36}
37
38impl Zeroize for EcdsaP224SecretKey {
39    fn zeroize(&mut self) {
40        self.raw.zeroize();
41        self.bytes.zeroize();
42    }
43}
44
45impl Drop for EcdsaP224SecretKey {
46    fn drop(&mut self) {
47        self.zeroize();
48    }
49}
50
51impl ZeroizeOnDrop for EcdsaP224SecretKey {}
52
53/// P-224 signature encoded in ASN.1 DER format
54#[derive(Clone)]
55pub struct EcdsaP224Signature(pub(crate) Vec<u8>);
56
57// AsRef/AsMut implementations
58impl AsRef<[u8]> for EcdsaP224PublicKey {
59    fn as_ref(&self) -> &[u8] {
60        &self.0
61    }
62}
63impl AsMut<[u8]> for EcdsaP224PublicKey {
64    fn as_mut(&mut self) -> &mut [u8] {
65        &mut self.0
66    }
67}
68impl AsRef<[u8]> for EcdsaP224SecretKey {
69    fn as_ref(&self) -> &[u8] {
70        self.bytes.as_ref()
71    }
72}
73// REMOVED: AsMut<[u8]> for EcdsaP224SecretKey to prevent direct mutation of secret key bytes
74
75impl AsRef<[u8]> for EcdsaP224Signature {
76    fn as_ref(&self) -> &[u8] {
77        &self.0
78    }
79}
80impl AsMut<[u8]> for EcdsaP224Signature {
81    fn as_mut(&mut self) -> &mut [u8] {
82        &mut self.0
83    }
84}
85
86impl EcdsaP224PublicKey {
87    /// Parse an uncompressed P-224 public key with on-curve validation.
88    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
89        let point = ec::Point::deserialize_uncompressed(bytes).map_err(ApiError::from)?;
90        if point.is_identity() {
91            return Err(ApiError::InvalidParameter {
92                context: "ECDSA-P224 public key",
93                #[cfg(feature = "std")]
94                message: "Identity is not a valid ECDSA public key".to_string(),
95            });
96        }
97        Ok(Self(point.serialize_uncompressed()))
98    }
99
100    /// Return the SEC1 uncompressed encoding.
101    pub fn to_bytes(&self) -> &[u8] {
102        &self.0
103    }
104}
105
106impl EcdsaP224SecretKey {
107    /// Parse a canonical, nonzero P-224 secret scalar.
108    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
109        let raw = ec::Scalar::deserialize(bytes).map_err(ApiError::from)?;
110        let serialized = raw.serialize();
111        Ok(Self {
112            raw,
113            bytes: serialized,
114        })
115    }
116
117    /// Export the secret scalar in a zeroizing buffer.
118    pub fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
119        zeroizing_bytes_from_slice(self.bytes.as_ref())
120    }
121}
122
123impl EcdsaP224Signature {
124    /// Parse a strictly encoded ASN.1 DER ECDSA signature.
125    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
126        SignatureComponents::from_der(bytes)?;
127        Ok(Self(bytes.to_vec()))
128    }
129
130    /// Return the DER encoding.
131    pub fn to_bytes(&self) -> &[u8] {
132        &self.0
133    }
134}
135
136impl SignatureTrait for EcdsaP224 {
137    type PublicKey = EcdsaP224PublicKey;
138    type SecretKey = EcdsaP224SecretKey;
139    type SignatureData = EcdsaP224Signature;
140    type KeyPair = (Self::PublicKey, Self::SecretKey);
141
142    fn name() -> &'static str {
143        "ECDSA-P224"
144    }
145
146    fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
147        let (sk_scalar, pk_point) = ec::generate_keypair(rng).map_err(ApiError::from)?;
148
149        let sk_bytes = sk_scalar.serialize();
150
151        if sk_scalar.is_zero() {
152            return Err(ApiError::InvalidParameter {
153                context: "ECDSA-P224 keypair",
154                #[cfg(feature = "std")]
155                message: "Generated secret key is zero".to_string(),
156            });
157        }
158
159        let secret_key = EcdsaP224SecretKey {
160            raw: sk_scalar,
161            bytes: sk_bytes,
162        };
163        let public_key = EcdsaP224PublicKey(pk_point.serialize_uncompressed());
164        Ok((public_key, secret_key))
165    }
166
167    fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
168        keypair.0.clone()
169    }
170    fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
171        keypair.1.clone()
172    }
173
174    fn sign(message: &[u8], secret_key: &Self::SecretKey) -> ApiResult<Self::SignatureData> {
175        let mut hasher = Sha224::new();
176        hasher.update(message).map_err(ApiError::from)?;
177        let hash_output = hasher.finalize().map_err(ApiError::from)?;
178
179        let z_octets = bits2octets(hash_output.as_ref(), &NIST_P224.n, 224)?;
180        let z_bytes: [u8; ec::P224_SCALAR_SIZE] =
181            (&z_octets[..])
182                .try_into()
183                .map_err(|_| ApiError::InvalidLength {
184                    context: "ECDSA-P224 hash conversion",
185                    expected: ec::P224_SCALAR_SIZE,
186                    actual: z_octets.len(),
187                })?;
188        let z = ec::Scalar::from_bytes_reduced(z_bytes);
189
190        let d = secret_key.raw.clone();
191        let mut d_bytes = d.serialize();
192        let nonces_result =
193            Rfc6979::<Sha224>::new(d_bytes.as_ref(), hash_output.as_ref(), &NIST_P224.n, 224);
194        d_bytes.zeroize();
195        let mut nonces = nonces_result?;
196
197        loop {
198            let mut nonce = nonces.next_nonce()?;
199            let mut nonce_bytes: [u8; ec::P224_SCALAR_SIZE] =
200                (&nonce[..])
201                    .try_into()
202                    .map_err(|_| ApiError::InvalidLength {
203                        context: "ECDSA-P224 nonce",
204                        expected: ec::P224_SCALAR_SIZE,
205                        actual: nonce.len(),
206                    })?;
207            nonce.zeroize();
208            let scalar = ec::Scalar::new(nonce_bytes).map_err(ApiError::from);
209            nonce_bytes.zeroize();
210            let k = scalar?;
211
212            let kg = ec::scalar_mult_base_g(&k).map_err(ApiError::from)?;
213
214            if kg.is_identity() {
215                continue;
216            }
217            let r_bytes = Zeroizing::new(kg.x_coordinate_bytes());
218
219            let r = match reduce_bytes_to_scalar_p224(&r_bytes) {
220                Ok(scalar) if !scalar.is_zero() => scalar,
221                _ => continue,
222            };
223
224            let k_inv = k.inv_mod_n().map_err(ApiError::from)?;
225            let rd = r.mul_mod_n(&d).map_err(ApiError::from)?;
226            let z_plus_rd = z.add_mod_n(&rd).map_err(ApiError::from)?;
227            let mut s = k_inv.mul_mod_n(&z_plus_rd).map_err(ApiError::from)?;
228
229            if s.is_zero() {
230                continue;
231            }
232            if is_high_s(s.serialize().as_ref(), &NIST_P224.n) {
233                s = s.negate();
234            }
235
236            let sig_comps = SignatureComponents {
237                r: r.serialize().to_vec(),
238                s: s.serialize().to_vec(),
239            };
240            return Ok(EcdsaP224Signature(sig_comps.to_der()));
241        }
242    }
243
244    fn verify(
245        message: &[u8],
246        signature: &Self::SignatureData,
247        public_key: &Self::PublicKey,
248    ) -> ApiResult<()> {
249        let sig_comps = SignatureComponents::from_der(&signature.0)?;
250
251        if sig_comps.r.len() > ec::P224_SCALAR_SIZE || sig_comps.s.len() > ec::P224_SCALAR_SIZE {
252            return Err(ApiError::InvalidSignature {
253                context: "ECDSA-P224 verify",
254                #[cfg(feature = "std")]
255                message: "Invalid signature component size".to_string(),
256            });
257        }
258
259        let mut r_bytes = [0u8; ec::P224_SCALAR_SIZE];
260        let mut s_bytes = [0u8; ec::P224_SCALAR_SIZE];
261        let r_offset = ec::P224_SCALAR_SIZE.saturating_sub(sig_comps.r.len());
262        let s_offset = ec::P224_SCALAR_SIZE.saturating_sub(sig_comps.s.len());
263        r_bytes[r_offset..].copy_from_slice(&sig_comps.r);
264        s_bytes[s_offset..].copy_from_slice(&sig_comps.s);
265
266        if !is_canonical_nonzero_scalar(&r_bytes, &NIST_P224.n)
267            || !is_canonical_nonzero_scalar(&s_bytes, &NIST_P224.n)
268        {
269            return Err(ApiError::InvalidSignature {
270                context: "ECDSA-P224 verify",
271                #[cfg(feature = "std")]
272                message: "signature components must be canonical integers in [1, n-1]".to_string(),
273            });
274        }
275
276        let r = ec::Scalar::new(r_bytes).map_err(|_| ApiError::InvalidSignature {
277            context: "ECDSA-P224 verify",
278            #[cfg(feature = "std")]
279            message: "Invalid r component".to_string(),
280        })?;
281        let s = ec::Scalar::new(s_bytes).map_err(|_| ApiError::InvalidSignature {
282            context: "ECDSA-P224 verify",
283            #[cfg(feature = "std")]
284            message: "Invalid s component".to_string(),
285        })?;
286        if is_high_s(s.serialize().as_ref(), &NIST_P224.n) {
287            return Err(ApiError::InvalidSignature {
288                context: "ECDSA-P224 verify",
289                #[cfg(feature = "std")]
290                message: "high-s signatures are non-canonical".to_string(),
291            });
292        }
293
294        let mut hasher = Sha224::new();
295        hasher.update(message).map_err(ApiError::from)?;
296        let hash_output = hasher.finalize().map_err(ApiError::from)?;
297
298        let z_octets = bits2octets(hash_output.as_ref(), &NIST_P224.n, 224)?;
299        let z_bytes: [u8; ec::P224_SCALAR_SIZE] =
300            (&z_octets[..])
301                .try_into()
302                .map_err(|_| ApiError::InvalidLength {
303                    context: "ECDSA-P224 hash conversion",
304                    expected: ec::P224_SCALAR_SIZE,
305                    actual: z_octets.len(),
306                })?;
307        let z = ec::Scalar::from_bytes_reduced(z_bytes);
308
309        let s_inv = s.inv_mod_n().map_err(ApiError::from)?;
310        let u1 = z.mul_mod_n(&s_inv).map_err(ApiError::from)?;
311        let u2 = r.mul_mod_n(&s_inv).map_err(ApiError::from)?;
312
313        let q_point = ec::Point::deserialize_uncompressed(&public_key.0).map_err(ApiError::from)?;
314
315        if q_point.is_identity() {
316            return Err(ApiError::InvalidKey {
317                context: "ECDSA-P224 verify",
318                #[cfg(feature = "std")]
319                message: "Public key is the point at infinity".to_string(),
320            });
321        }
322
323        let u1g = ec::scalar_mult_base_g(&u1).map_err(ApiError::from)?;
324        let u2q = ec::scalar_mult(&u2, &q_point).map_err(ApiError::from)?;
325
326        let point = u1g.add(&u2q);
327
328        if point.is_identity() {
329            return Err(ApiError::InvalidSignature {
330                context: "ECDSA-P224 verify",
331                #[cfg(feature = "std")]
332                message: "Verification point is identity".to_string(),
333            });
334        }
335
336        let x1_bytes = point.x_coordinate_bytes();
337        let v = reduce_bytes_to_scalar_p224(&x1_bytes)?;
338
339        if !ct_eq(r.serialize(), v.serialize()) {
340            return Err(ApiError::InvalidSignature {
341                context: "ECDSA-P224 verify",
342                #[cfg(feature = "std")]
343                message: "Signature verification failed (r != v)".to_string(),
344            });
345        }
346        Ok(())
347    }
348}
349
350fn reduce_bytes_to_scalar_p224(bytes: &[u8; ec::P224_SCALAR_SIZE]) -> ApiResult<ec::Scalar> {
351    Ok(ec::Scalar::from_bytes_reduced(*bytes))
352}
353
354#[cfg(test)]
355mod tests;