dcrypt_sign/ecdsa/p256/
mod.rs

1//! ECDSA implementation for NIST P-256 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::p256 as ec;
9use dcrypt_algorithms::hash::sha2::Sha256;
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-256 curve (secp256r1)
18///
19/// Implements ECDSA as specified in FIPS 186-4, Section 6
20pub struct EcdsaP256;
21
22/// P-256 public key in uncompressed format (0x04 || X || Y)
23///
24/// Format: 65 bytes total (1 byte prefix + 32 bytes X + 32 bytes Y)
25#[derive(Clone, Zeroize)]
26pub struct EcdsaP256PublicKey(pub [u8; ec::P256_POINT_UNCOMPRESSED_SIZE]);
27
28/// P-256 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 EcdsaP256SecretKey {
35    raw: ec::Scalar,
36    bytes: [u8; ec::P256_SCALAR_SIZE],
37}
38
39// Manual Zeroize implementation for EcdsaP256SecretKey
40impl Zeroize for EcdsaP256SecretKey {
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 EcdsaP256SecretKey {
51    fn drop(&mut self) {
52        self.zeroize();
53    }
54}
55
56/// P-256 signature encoded in ASN.1 DER format
57///
58/// Format: SEQUENCE { r INTEGER, s INTEGER }
59#[derive(Clone)]
60pub struct EcdsaP256Signature(pub Vec<u8>);
61
62// AsRef/AsMut implementations for byte access
63impl AsRef<[u8]> for EcdsaP256PublicKey {
64    fn as_ref(&self) -> &[u8] {
65        &self.0
66    }
67}
68
69impl AsMut<[u8]> for EcdsaP256PublicKey {
70    fn as_mut(&mut self) -> &mut [u8] {
71        &mut self.0
72    }
73}
74
75impl AsRef<[u8]> for EcdsaP256SecretKey {
76    fn as_ref(&self) -> &[u8] {
77        &self.bytes
78    }
79}
80
81// REMOVED: AsMut<[u8]> for EcdsaP256SecretKey
82// This implementation was removed for security reasons.
83// Direct mutation of secret key bytes could create invalid keys
84// outside the valid range [1, n-1], leading to security vulnerabilities.
85
86impl AsRef<[u8]> for EcdsaP256Signature {
87    fn as_ref(&self) -> &[u8] {
88        &self.0
89    }
90}
91
92impl AsMut<[u8]> for EcdsaP256Signature {
93    fn as_mut(&mut self) -> &mut [u8] {
94        &mut self.0
95    }
96}
97
98impl SignatureTrait for EcdsaP256 {
99    type PublicKey = EcdsaP256PublicKey;
100    type SecretKey = EcdsaP256SecretKey;
101    type SignatureData = EcdsaP256Signature;
102    type KeyPair = (Self::PublicKey, Self::SecretKey);
103
104    fn name() -> &'static str {
105        "ECDSA-P256"
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::P256_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-P256 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 = EcdsaP256SecretKey {
132            raw: sk_scalar,
133            bytes: sk_bytes,
134        };
135
136        // Serialize public key in uncompressed format
137        let public_key = EcdsaP256PublicKey(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-256
158    /// 2. z = the leftmost min(N, bitlen(e)) bits of e, where N = 256
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-256 (FIPS 186-4 approved hash function)
166        let mut hasher = Sha256::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-256, we use all 256 bits of the hash output
172        let mut z_bytes = [0u8; ec::P256_SCALAR_SIZE];
173        z_bytes.copy_from_slice(hash_output.as_ref());
174        let z = ec::Scalar::new(z_bytes).map_err(|e| ApiError::InvalidParameter {
175            context: "ECDSA-P256 sign",
176            #[cfg(feature = "std")]
177            message: format!("Hash to scalar conversion failed: {:?}", e),
178        })?;
179
180        // Get the private key scalar d
181        let d = secret_key.raw.clone();
182
183        // Generate ephemeral key using RFC 6979 deterministic + hedged nonce generation
184        let mut rng = rand::thread_rng();
185
186        loop {
187            // Step 3: Derive deterministic k with extra entropy
188            let k = deterministic_k_hedged(&d, &z, &mut rng);
189
190            // Step 4: Compute (x₁, y₁) = k·G
191            let kg = ec::scalar_mult_base_g(&k).map_err(ApiError::from)?;
192            let r_bytes = kg.x_coordinate_bytes();
193
194            // Step 5: Compute r = x₁ mod n
195            let r = match ec::Scalar::new(r_bytes) {
196                Ok(scalar) => scalar,
197                Err(_) => continue, // If r = 0, try again
198            };
199
200            // Compute k⁻¹ mod n
201            let k_inv = k.inv_mod_n().map_err(ApiError::from)?;
202
203            // Step 6: Compute s = k⁻¹(z + rd) mod n
204            let rd = r.mul_mod_n(&d).map_err(ApiError::from)?;
205
206            let z_plus_rd = z.add_mod_n(&rd).map_err(ApiError::from)?;
207
208            let s = k_inv.mul_mod_n(&z_plus_rd).map_err(ApiError::from)?;
209
210            // If s = 0, try again (extremely unlikely)
211            if s.is_zero() {
212                continue;
213            }
214
215            // Step 7: Create signature (r, s)
216            let sig = SignatureComponents {
217                r: r.serialize().to_vec(),
218                s: s.serialize().to_vec(),
219            };
220
221            // Encode signature in DER format
222            let der_sig = sig.to_der();
223
224            return Ok(EcdsaP256Signature(der_sig));
225        }
226    }
227
228    /// Verify an ECDSA signature
229    ///
230    /// Implements the ECDSA signature verification algorithm as specified in
231    /// FIPS 186-4, Section 6.4.
232    ///
233    /// Algorithm:
234    /// 1. Verify that r and s are integers in [1, n-1]
235    /// 2. e = HASH(M), where HASH is SHA-256
236    /// 3. z = the leftmost min(N, bitlen(e)) bits of e, where N = 256
237    /// 4. w = s⁻¹ mod n
238    /// 5. u₁ = zw mod n and u₂ = rw mod n
239    /// 6. (x₁, y₁) = u₁·G + u₂·Q
240    /// 7. If (x₁, y₁) = O, reject the signature
241    /// 8. v = x₁ mod n
242    /// 9. Accept the signature if and only if v = r
243    fn verify(
244        message: &[u8],
245        signature: &Self::SignatureData,
246        public_key: &Self::PublicKey,
247    ) -> ApiResult<()> {
248        // Parse signature from DER format
249        let sig = SignatureComponents::from_der(&signature.0)?;
250
251        // Step 1: Verify r and s are in valid range [1, n-1]
252        if sig.r.len() > ec::P256_SCALAR_SIZE || sig.s.len() > ec::P256_SCALAR_SIZE {
253            return Err(ApiError::InvalidSignature {
254                context: "ECDSA-P256 verify",
255                #[cfg(feature = "std")]
256                message: "Invalid signature component size".to_string(),
257            });
258        }
259
260        // Convert r and s to scalars (with proper padding)
261        let mut r_bytes = [0u8; ec::P256_SCALAR_SIZE];
262        let mut s_bytes = [0u8; ec::P256_SCALAR_SIZE];
263        r_bytes[ec::P256_SCALAR_SIZE - sig.r.len()..].copy_from_slice(&sig.r);
264        s_bytes[ec::P256_SCALAR_SIZE - sig.s.len()..].copy_from_slice(&sig.s);
265
266        let r = ec::Scalar::new(r_bytes).map_err(|_| ApiError::InvalidSignature {
267            context: "ECDSA-P256 verify",
268            #[cfg(feature = "std")]
269            message: "Invalid r component".to_string(),
270        })?;
271
272        let s = ec::Scalar::new(s_bytes).map_err(|_| ApiError::InvalidSignature {
273            context: "ECDSA-P256 verify",
274            #[cfg(feature = "std")]
275            message: "Invalid s component".to_string(),
276        })?;
277
278        // Step 2: Hash the message using SHA-256
279        let mut hasher = Sha256::new();
280        hasher.update(message).map_err(ApiError::from)?;
281        let hash_output = hasher.finalize().map_err(ApiError::from)?;
282
283        // Step 3: Convert hash to integer z
284        let mut z_bytes = [0u8; ec::P256_SCALAR_SIZE];
285        z_bytes.copy_from_slice(hash_output.as_ref());
286        let z = ec::Scalar::new(z_bytes).map_err(|e| ApiError::InvalidSignature {
287            context: "ECDSA-P256 verify",
288            #[cfg(feature = "std")]
289            message: format!("Hash to scalar conversion failed: {:?}", e),
290        })?;
291
292        // Step 4: Compute w = s⁻¹ mod n
293        let s_inv = s.inv_mod_n().map_err(ApiError::from)?;
294
295        // Step 5: Compute u₁ = zw mod n and u₂ = rw mod n
296        let u1 = z.mul_mod_n(&s_inv).map_err(ApiError::from)?;
297        let u2 = r.mul_mod_n(&s_inv).map_err(ApiError::from)?;
298
299        // Parse the public key point Q
300        let q = ec::Point::deserialize_uncompressed(&public_key.0).map_err(ApiError::from)?;
301
302        // Step 6: Compute point (x₁, y₁) = u₁·G + u₂·Q
303        let u1g = ec::scalar_mult_base_g(&u1).map_err(ApiError::from)?;
304
305        let u2q = ec::scalar_mult(&u2, &q).map_err(ApiError::from)?;
306
307        let point = u1g.add(&u2q);
308
309        // Step 7: Check if point is identity (point at infinity)
310        if point.is_identity() {
311            return Err(ApiError::InvalidSignature {
312                context: "ECDSA-P256 verify",
313                #[cfg(feature = "std")]
314                message: "Invalid signature: verification point is identity".to_string(),
315            });
316        }
317
318        // Step 8: Compute v = x₁ mod n
319        let x1_bytes = point.x_coordinate_bytes();
320        let x1 = ec::Scalar::new(x1_bytes).map_err(|_| ApiError::InvalidSignature {
321            context: "ECDSA-P256 verify",
322            #[cfg(feature = "std")]
323            message: "Recovered X coordinate out of range".to_string(),
324        })?;
325
326        // Step 9: Verify v = r using constant-time comparison
327        if !ct_eq(r.serialize(), x1.serialize()) {
328            return Err(ApiError::InvalidSignature {
329                context: "ECDSA-P256 verify",
330                #[cfg(feature = "std")]
331                message: "Signature verification failed".to_string(),
332            });
333        }
334
335        Ok(())
336    }
337}
338
339/* ------------------------------------------------------------------------- */
340/*                      RFC 6979 + extra-entropy helper                      */
341/* ------------------------------------------------------------------------- */
342
343/// Derive a deterministic nonce k as per RFC 6979 §3.2, hedged with
344/// 32 bytes of RNG-supplied entropy (recommended by §3.6 / FIPS 186-5 §6.4).
345///
346/// This implementation combines deterministic nonce generation with additional
347/// randomness to provide defense against weak RNG states while maintaining
348/// the benefits of deterministic signatures for fault attack resistance.
349fn deterministic_k_hedged<R: RngCore + CryptoRng>(
350    d: &ec::Scalar,
351    z: &ec::Scalar,
352    rng: &mut R,
353) -> ec::Scalar {
354    use zeroize::Zeroize;
355
356    let mut rbuf = [0u8; 32];
357    rng.fill_bytes(&mut rbuf); // extra entropy R
358
359    let mut v = [0x01u8; 32];
360    let mut k = [0x00u8; 32];
361
362    // ----- step C -----
363    // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || R)
364    {
365        let mut mac = Hmac::<Sha256>::new(&k).unwrap();
366        mac.update(&v).unwrap();
367        mac.update(&[0x00]).unwrap();
368        mac.update(&d.serialize()).unwrap();
369        mac.update(&z.serialize()).unwrap();
370        mac.update(&rbuf).unwrap();
371        k.copy_from_slice(&mac.finalize().unwrap());
372    }
373
374    // ----- step D -----
375    // V = HMAC_K(V)
376    let v_new = Hmac::<Sha256>::mac(&k, &v).unwrap();
377    v.copy_from_slice(&v_new);
378
379    // ----- step E -----
380    // K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1) || R)
381    {
382        let mut mac = Hmac::<Sha256>::new(&k).unwrap();
383        mac.update(&v).unwrap();
384        mac.update(&[0x01]).unwrap();
385        mac.update(&d.serialize()).unwrap();
386        mac.update(&z.serialize()).unwrap();
387        mac.update(&rbuf).unwrap();
388        k.copy_from_slice(&mac.finalize().unwrap());
389    }
390
391    // ----- step F -----
392    // V = HMAC_K(V)
393    let v_new = Hmac::<Sha256>::mac(&k, &v).unwrap();
394    v.copy_from_slice(&v_new);
395
396    // ----- step G/H -----
397    // Generate candidate k values until we find a valid one
398    loop {
399        let v_new = Hmac::<Sha256>::mac(&k, &v).unwrap();
400        v.copy_from_slice(&v_new);
401        if let Ok(candidate) = ec::Scalar::new(v) {
402            if !candidate.is_zero() {
403                rbuf.zeroize(); // scrub extra entropy
404                return candidate;
405            }
406        }
407
408        // retry path (step H): update K and V if candidate was invalid
409        let mut mac = Hmac::<Sha256>::new(&k).unwrap();
410        mac.update(&v).unwrap();
411        mac.update(&[0x00]).unwrap();
412        k.copy_from_slice(&mac.finalize().unwrap());
413        let v_new = Hmac::<Sha256>::mac(&k, &v).unwrap();
414        v.copy_from_slice(&v_new);
415    }
416}
417
418#[cfg(test)]
419mod tests;