dcrypt_sign/ecdsa/p384/
mod.rs

1//! ECDSA implementation for NIST P-384 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
6
7use crate::ecdsa::common::SignatureComponents;
8use dcrypt_algorithms::ec::p384 as ec;
9use dcrypt_algorithms::hash::sha2::Sha384;
10use dcrypt_algorithms::hash::HashFunction;
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;
16
17/// ECDSA signature scheme using NIST P-384 curve (secp384r1)
18///
19/// Implements ECDSA as specified in FIPS 186-4, Section 6
20pub struct EcdsaP384;
21
22/// P-384 public key in uncompressed format (0x04 || X || Y)
23///
24/// Format: 97 bytes total (1 byte prefix + 48 bytes X + 48 bytes Y)
25#[derive(Clone, Zeroize)]
26pub struct EcdsaP384PublicKey(pub [u8; ec::P384_POINT_UNCOMPRESSED_SIZE]);
27
28/// P-384 secret key
29///
30/// Contains both the raw scalar value and its byte representation
31/// for efficient operations. The scalar d must satisfy 1 ≤ d ≤ n-1
32/// where n is the order of the base point G.
33#[derive(Clone)]
34pub struct EcdsaP384SecretKey {
35    raw: ec::Scalar,
36    bytes: [u8; ec::P384_SCALAR_SIZE],
37}
38
39// Manual Zeroize implementation for EcdsaP384SecretKey
40impl Zeroize for EcdsaP384SecretKey {
41    fn zeroize(&mut self) {
42        // Zeroize the byte representation
43        self.bytes.zeroize();
44        // Note: The ec::Scalar type doesn't implement Zeroize directly
45        // It will be dropped when the struct is dropped
46    }
47}
48
49// Secure cleanup on drop
50impl Drop for EcdsaP384SecretKey {
51    fn drop(&mut self) {
52        self.zeroize();
53    }
54}
55
56/// P-384 signature encoded in ASN.1 DER format
57///
58/// Format: SEQUENCE { r INTEGER, s INTEGER }
59#[derive(Clone)]
60pub struct EcdsaP384Signature(pub Vec<u8>);
61
62// AsRef/AsMut implementations for byte access
63impl AsRef<[u8]> for EcdsaP384PublicKey {
64    fn as_ref(&self) -> &[u8] {
65        &self.0
66    }
67}
68
69impl AsMut<[u8]> for EcdsaP384PublicKey {
70    fn as_mut(&mut self) -> &mut [u8] {
71        &mut self.0
72    }
73}
74
75impl AsRef<[u8]> for EcdsaP384SecretKey {
76    fn as_ref(&self) -> &[u8] {
77        &self.bytes
78    }
79}
80
81// REMOVED: AsMut<[u8]> for EcdsaP384SecretKey
82// This implementation was removed for security reasons. Direct mutation of secret
83// key bytes could create invalid keys outside the valid range [1, n-1], leading
84// to security vulnerabilities.
85
86impl AsRef<[u8]> for EcdsaP384Signature {
87    fn as_ref(&self) -> &[u8] {
88        &self.0
89    }
90}
91
92impl AsMut<[u8]> for EcdsaP384Signature {
93    fn as_mut(&mut self) -> &mut [u8] {
94        &mut self.0
95    }
96}
97
98impl SignatureTrait for EcdsaP384 {
99    type PublicKey = EcdsaP384PublicKey;
100    type SecretKey = EcdsaP384SecretKey;
101    type SignatureData = EcdsaP384Signature;
102    type KeyPair = (Self::PublicKey, Self::SecretKey);
103
104    fn name() -> &'static str {
105        "ECDSA-P384"
106    }
107
108    /// Generate an ECDSA key pair
109    ///
110    /// Generates a random private key d ∈ [1, n-1] and computes
111    /// the corresponding public key Q = d·G where G is the base point.
112    ///
113    /// Reference: FIPS 186-4, Appendix B.4.1
114    fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
115        // Generate EC keypair with private key in valid range [1, n-1]
116        let (sk_scalar, pk_point) = ec::generate_keypair(rng).map_err(ApiError::from)?;
117
118        // Serialize the private key scalar
119        let sk_bytes: [u8; ec::P384_SCALAR_SIZE] = sk_scalar.serialize();
120
121        // Verify the private key is non-zero (should never happen with proper generation)
122        if sk_bytes.iter().all(|&b| b == 0) {
123            return Err(ApiError::InvalidParameter {
124                context: "ECDSA-P384 keypair",
125                #[cfg(feature = "std")]
126                message: "Generated secret key is zero (internal error)".to_string(),
127            });
128        }
129
130        // Create the secret key structure
131        let secret_key = EcdsaP384SecretKey {
132            raw: sk_scalar,
133            bytes: sk_bytes,
134        };
135
136        // Serialize public key in uncompressed format
137        let public_key = EcdsaP384PublicKey(pk_point.serialize_uncompressed());
138
139        Ok((public_key, secret_key))
140    }
141
142    fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
143        keypair.0.clone()
144    }
145
146    fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
147        keypair.1.clone()
148    }
149
150    /// Sign a message using ECDSA
151    ///
152    /// Implements the ECDSA signature generation algorithm as specified in
153    /// FIPS 186-4, Section 6.3, with deterministic nonce generation per
154    /// RFC 6979 hedged with additional entropy (FIPS 186-5, Section 6.4).
155    ///
156    /// Algorithm:
157    /// 1. e = HASH(M), where HASH is SHA-384
158    /// 2. z = the leftmost min(N, bitlen(e)) bits of e, where N = 384
159    /// 3. Generate k deterministically per RFC 6979 with extra entropy
160    /// 4. (x₁, y₁) = k·G
161    /// 5. r = x₁ mod n; if r = 0, go back to step 3
162    /// 6. s = k⁻¹(z + rd) mod n; if s = 0, go back to step 3
163    /// 7. Return signature (r, s)
164    fn sign(message: &[u8], secret_key: &Self::SecretKey) -> ApiResult<Self::SignatureData> {
165        // Step 1: Hash the message using SHA-384 (FIPS 186-4 approved hash function)
166        let mut hasher = Sha384::new();
167        hasher.update(message).map_err(ApiError::from)?;
168        let hash_output = hasher.finalize().map_err(ApiError::from)?;
169
170        // Step 2: Convert hash to integer z
171        // For P-384, we use all 384 bits of the hash output
172        let mut h_bytes = [0u8; ec::P384_SCALAR_SIZE];
173        h_bytes.copy_from_slice(hash_output.as_ref());
174        let z = reduce_bytes_to_scalar(&h_bytes)?;
175
176        // Get the private key scalar d
177        let d = secret_key.raw.clone();
178
179        // Generate ephemeral key using RFC 6979 deterministic + hedged nonce generation
180        let mut rng = rand::thread_rng();
181
182        loop {
183            // Step 3: Derive deterministic k with extra entropy
184            let k = deterministic_k_hedged(&d, &z, &mut rng);
185
186            // Step 4: Compute (x₁, y₁) = k·G
187            let kg = ec::scalar_mult_base_g(&k).map_err(ApiError::from)?;
188            let r_bytes = kg.x_coordinate_bytes();
189
190            // Step 5: Compute r = x₁ mod n
191            let r = match reduce_bytes_to_scalar(&r_bytes) {
192                Ok(scalar) => scalar,
193                Err(_) => continue, // If r = 0, try again
194            };
195
196            // Compute k⁻¹ mod n
197            let k_inv = k.inv_mod_n().map_err(ApiError::from)?;
198
199            // Step 6: Compute s = k⁻¹(z + rd) mod n
200            let rd = r.mul_mod_n(&d).map_err(ApiError::from)?;
201
202            let z_plus_rd = z.add_mod_n(&rd).map_err(ApiError::from)?;
203
204            let s = k_inv.mul_mod_n(&z_plus_rd).map_err(ApiError::from)?;
205
206            // If s = 0, try again (extremely unlikely)
207            if s.is_zero() {
208                continue;
209            }
210
211            // Step 7: Create signature (r, s)
212            let sig = SignatureComponents {
213                r: r.serialize().to_vec(),
214                s: s.serialize().to_vec(),
215            };
216
217            // Encode signature in DER format
218            let der_sig = sig.to_der();
219
220            return Ok(EcdsaP384Signature(der_sig));
221        }
222    }
223
224    /// Verify an ECDSA signature
225    ///
226    /// Implements the ECDSA signature verification algorithm as specified in
227    /// FIPS 186-4, Section 6.4.
228    ///
229    /// Algorithm:
230    /// 1. Verify that r and s are integers in [1, n-1]
231    /// 2. e = HASH(M), where HASH is SHA-384
232    /// 3. z = the leftmost min(N, bitlen(e)) bits of e, where N = 384
233    /// 4. w = s⁻¹ mod n
234    /// 5. u₁ = zw mod n and u₂ = rw mod n
235    /// 6. (x₁, y₁) = u₁·G + u₂·Q
236    /// 7. If (x₁, y₁) = O, reject the signature
237    /// 8. v = x₁ mod n
238    /// 9. Accept the signature if and only if v = r
239    fn verify(
240        message: &[u8],
241        signature: &Self::SignatureData,
242        public_key: &Self::PublicKey,
243    ) -> ApiResult<()> {
244        // Parse signature from DER format
245        let sig = SignatureComponents::from_der(&signature.0)?;
246
247        // Step 1: Verify r and s are in valid range [1, n-1]
248        if sig.r.len() > ec::P384_SCALAR_SIZE || sig.s.len() > ec::P384_SCALAR_SIZE {
249            return Err(ApiError::InvalidSignature {
250                context: "ECDSA-P384 verify",
251                #[cfg(feature = "std")]
252                message: "Invalid signature component size".to_string(),
253            });
254        }
255
256        // Convert r and s to scalars (with proper padding)
257        let mut r_bytes = [0u8; ec::P384_SCALAR_SIZE];
258        let mut s_bytes = [0u8; ec::P384_SCALAR_SIZE];
259        r_bytes[ec::P384_SCALAR_SIZE - sig.r.len()..].copy_from_slice(&sig.r);
260        s_bytes[ec::P384_SCALAR_SIZE - sig.s.len()..].copy_from_slice(&sig.s);
261
262        let r = ec::Scalar::new(r_bytes).map_err(|_| ApiError::InvalidSignature {
263            context: "ECDSA-P384 verify",
264            #[cfg(feature = "std")]
265            message: "Invalid r component".to_string(),
266        })?;
267
268        let s = ec::Scalar::new(s_bytes).map_err(|_| ApiError::InvalidSignature {
269            context: "ECDSA-P384 verify",
270            #[cfg(feature = "std")]
271            message: "Invalid s component".to_string(),
272        })?;
273
274        // Step 2: Hash the message using SHA-384
275        let mut hasher = Sha384::new();
276        hasher.update(message).map_err(ApiError::from)?;
277        let hash_output = hasher.finalize().map_err(ApiError::from)?;
278
279        // Step 3: Convert hash to integer z
280        let mut h_bytes = [0u8; ec::P384_SCALAR_SIZE];
281        h_bytes.copy_from_slice(hash_output.as_ref());
282        let z = reduce_bytes_to_scalar(&h_bytes)?;
283
284        // Step 4: Compute w = s⁻¹ mod n
285        let s_inv = s.inv_mod_n().map_err(ApiError::from)?;
286
287        // Step 5: Compute u₁ = zw mod n and u₂ = rw mod n
288        let u1 = z.mul_mod_n(&s_inv).map_err(ApiError::from)?;
289        let u2 = r.mul_mod_n(&s_inv).map_err(ApiError::from)?;
290
291        // Parse the public key point Q
292        let q = ec::Point::deserialize_uncompressed(&public_key.0).map_err(ApiError::from)?;
293
294        // Step 6: Compute point (x₁, y₁) = u₁·G + u₂·Q
295        let u1g = ec::scalar_mult_base_g(&u1).map_err(ApiError::from)?;
296
297        let u2q = ec::scalar_mult(&u2, &q).map_err(ApiError::from)?;
298
299        let point = u1g.add(&u2q);
300
301        // Step 7: Check if point is identity (point at infinity)
302        if point.is_identity() {
303            return Err(ApiError::InvalidSignature {
304                context: "ECDSA-P384 verify",
305                #[cfg(feature = "std")]
306                message: "Invalid signature: verification point is identity".to_string(),
307            });
308        }
309
310        // Step 8: Compute v = x₁ mod n
311        let x1_bytes = point.x_coordinate_bytes();
312        let x1 = reduce_bytes_to_scalar(&x1_bytes)?;
313
314        // Step 9: Verify v = r using constant-time comparison
315        if !ct_eq(r.serialize(), x1.serialize()) {
316            return Err(ApiError::InvalidSignature {
317                context: "ECDSA-P384 verify",
318                #[cfg(feature = "std")]
319                message: "Signature verification failed".to_string(),
320            });
321        }
322
323        Ok(())
324    }
325}
326
327/* ------------------------------------------------------------------------- */
328/*                      RFC 6979 + extra-entropy helper                      */
329/* ------------------------------------------------------------------------- */
330
331/// Derive a deterministic nonce k as per RFC 6979 §3.2, hedged with
332/// 48 bytes of RNG-supplied entropy (recommended by §3.6 / FIPS 186-5 §6.4).
333///
334/// This implementation combines deterministic nonce generation with additional
335/// randomness to provide defense against weak RNG states while maintaining
336/// the benefits of deterministic signatures for fault attack resistance.
337fn deterministic_k_hedged<R: RngCore + CryptoRng>(
338    d: &ec::Scalar,
339    z: &ec::Scalar,
340    rng: &mut R,
341) -> ec::Scalar {
342    use zeroize::Zeroize;
343
344    let mut rbuf = [0u8; 48];
345    rng.fill_bytes(&mut rbuf); // extra entropy R
346
347    let mut v = [0x01u8; 48];
348    let mut k = [0x00u8; 48];
349
350    // ----- step C -----
351    // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || R)
352    {
353        let mut mac = Hmac::<Sha384>::new(&k).unwrap();
354        mac.update(&v).unwrap();
355        mac.update(&[0x00]).unwrap();
356        mac.update(&d.serialize()).unwrap();
357        mac.update(&z.serialize()).unwrap();
358        mac.update(&rbuf).unwrap();
359        k.copy_from_slice(&mac.finalize().unwrap());
360    }
361
362    // ----- step D -----
363    // V = HMAC_K(V)
364    let v_new = Hmac::<Sha384>::mac(&k, &v).unwrap();
365    v.copy_from_slice(&v_new);
366
367    // ----- step E -----
368    // K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1) || R)
369    {
370        let mut mac = Hmac::<Sha384>::new(&k).unwrap();
371        mac.update(&v).unwrap();
372        mac.update(&[0x01]).unwrap();
373        mac.update(&d.serialize()).unwrap();
374        mac.update(&z.serialize()).unwrap();
375        mac.update(&rbuf).unwrap();
376        k.copy_from_slice(&mac.finalize().unwrap());
377    }
378
379    // ----- step F -----
380    // V = HMAC_K(V)
381    let v_new = Hmac::<Sha384>::mac(&k, &v).unwrap();
382    v.copy_from_slice(&v_new);
383
384    // ----- step G/H -----
385    // Generate candidate k values until we find a valid one
386    loop {
387        let v_new = Hmac::<Sha384>::mac(&k, &v).unwrap();
388        v.copy_from_slice(&v_new);
389
390        // Try to create a scalar from v
391        if let Ok(candidate) = ec::Scalar::new(v) {
392            if !candidate.is_zero() {
393                rbuf.zeroize(); // scrub extra entropy
394                return candidate;
395            }
396        }
397
398        // retry path (step H): update K and V if candidate was invalid
399        let mut mac = Hmac::<Sha384>::new(&k).unwrap();
400        mac.update(&v).unwrap();
401        mac.update(&[0x00]).unwrap();
402        k.copy_from_slice(&mac.finalize().unwrap());
403        let v_new = Hmac::<Sha384>::mac(&k, &v).unwrap();
404        v.copy_from_slice(&v_new);
405    }
406}
407
408/* ------------------------------------------------------------------------- */
409/*                        P-384 scalar reduction helper                       */
410/* ------------------------------------------------------------------------- */
411
412/// P-384 curve order n in big-endian format
413const N_BE: [u8; 48] = [
414    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
415    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF,
416    0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A, 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73,
417];
418
419/// Check if big-endian a >= b
420fn ge_be(a: &[u8], b: &[u8]) -> bool {
421    for (&ai, &bi) in a.iter().zip(b) {
422        if ai > bi {
423            return true;
424        }
425        if ai < bi {
426            return false;
427        }
428    }
429    true
430}
431
432/// Subtract n from candidate (modular reduction)
433fn sub_mod_n(candidate: &mut [u8], n_be: &[u8]) {
434    let mut borrow = 0u16;
435    for i in (0..candidate.len()).rev() {
436        let tmp = (candidate[i] as i16) - (n_be[i] as i16) - (borrow as i16);
437        if tmp < 0 {
438            candidate[i] = (tmp + 256) as u8;
439            borrow = 1;
440        } else {
441            candidate[i] = tmp as u8;
442            borrow = 0;
443        }
444    }
445}
446
447/// Reduce a 48-byte value modulo the curve order *n*.
448/// Returns a scalar in **[1, n-1]**; zero inputs are **rejected** (the caller
449/// must decide how to handle them – e.g. retry in the signing loop or treat
450/// them as invalid during verification).
451fn reduce_bytes_to_scalar(bytes: &[u8; 48]) -> ApiResult<ec::Scalar> {
452    let mut candidate = *bytes;
453
454    // Full reduction: repeatedly subtract until < n
455    while ge_be(&candidate, &N_BE) {
456        sub_mod_n(&mut candidate, &N_BE);
457    }
458
459    ec::Scalar::new(candidate).map_err(ApiError::from)
460}
461
462#[cfg(test)]
463mod tests;