dcrypt_sign/ecdsa/p521/
mod.rs

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