Skip to main content

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::{is_canonical_nonzero_scalar, is_high_s, Rfc6979, SignatureComponents};
8use alloc::vec::Vec;
9use dcrypt_algorithms::ec::p521 as ec;
10use dcrypt_algorithms::hash::sha2::Sha512;
11use dcrypt_algorithms::hash::HashFunction;
12use dcrypt_api::{
13    error::Error as ApiError, Result as ApiResult, Signature as SignatureTrait, ZeroizingBytes,
14};
15use dcrypt_common::SecretBuffer;
16use dcrypt_internal::{
17    constant_time::ct_eq, zeroizing_bytes_from_slice, CryptoRng, RngCore, Zeroize, ZeroizeOnDrop,
18    Zeroizing,
19};
20use dcrypt_params::traditional::ecdsa::NIST_P521;
21
22/// ECDSA signature scheme using NIST P-521 curve (secp521r1)
23///
24/// Implements ECDSA as specified in FIPS 186-4, Section 6, with SHA-512
25/// as specified in FIPS 186-5 for P-521.
26pub struct EcdsaP521;
27
28/// P-521 public key in uncompressed format (0x04 || X || Y)
29///
30/// Format: 133 bytes total (1 byte prefix + 66 bytes X + 66 bytes Y)
31#[derive(Clone)]
32pub struct EcdsaP521PublicKey(pub(crate) [u8; ec::P521_POINT_UNCOMPRESSED_SIZE]);
33
34/// P-521 secret key
35///
36/// Contains both the raw scalar value and its byte representation
37/// for efficient operations. The scalar d must satisfy 1 ≤ d ≤ n-1
38/// where n is the order of the base point G.
39#[derive(Clone)]
40pub struct EcdsaP521SecretKey {
41    raw: ec::Scalar,
42    bytes: SecretBuffer<{ ec::P521_SCALAR_SIZE }>,
43}
44
45// Manual Zeroize implementation for EcdsaP521SecretKey
46impl Zeroize for EcdsaP521SecretKey {
47    fn zeroize(&mut self) {
48        self.raw.zeroize();
49        // Zeroize the byte representation
50        self.bytes.zeroize();
51    }
52}
53
54// Secure cleanup on drop
55impl Drop for EcdsaP521SecretKey {
56    fn drop(&mut self) {
57        self.zeroize();
58    }
59}
60
61impl ZeroizeOnDrop for EcdsaP521SecretKey {}
62
63/// P-521 signature encoded in ASN.1 DER format
64///
65/// Format: SEQUENCE { r INTEGER, s INTEGER }
66#[derive(Clone)]
67pub struct EcdsaP521Signature(pub(crate) Vec<u8>);
68
69// AsRef/AsMut implementations for byte access
70impl AsRef<[u8]> for EcdsaP521PublicKey {
71    fn as_ref(&self) -> &[u8] {
72        &self.0
73    }
74}
75
76impl AsMut<[u8]> for EcdsaP521PublicKey {
77    fn as_mut(&mut self) -> &mut [u8] {
78        &mut self.0
79    }
80}
81
82impl AsRef<[u8]> for EcdsaP521SecretKey {
83    fn as_ref(&self) -> &[u8] {
84        self.bytes.as_ref()
85    }
86}
87
88// REMOVED: AsMut<[u8]> for EcdsaP521SecretKey
89// This implementation was removed for security reasons. Direct mutation of secret
90// key bytes could create invalid keys outside the valid range [1, n-1], leading
91// to security vulnerabilities.
92
93impl AsRef<[u8]> for EcdsaP521Signature {
94    fn as_ref(&self) -> &[u8] {
95        &self.0
96    }
97}
98
99impl AsMut<[u8]> for EcdsaP521Signature {
100    fn as_mut(&mut self) -> &mut [u8] {
101        &mut self.0
102    }
103}
104
105impl EcdsaP521PublicKey {
106    /// Parse an uncompressed P-521 public key with on-curve validation.
107    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
108        let point = ec::Point::deserialize_uncompressed(bytes).map_err(ApiError::from)?;
109        if point.is_identity() {
110            return Err(ApiError::InvalidParameter {
111                context: "ECDSA-P521 public key",
112                #[cfg(feature = "std")]
113                message: "Identity is not a valid ECDSA public key".to_string(),
114            });
115        }
116        Ok(Self(point.serialize_uncompressed()))
117    }
118
119    /// Return the SEC1 uncompressed encoding.
120    pub fn to_bytes(&self) -> &[u8] {
121        &self.0
122    }
123}
124
125impl EcdsaP521SecretKey {
126    /// Parse a canonical, nonzero P-521 secret scalar.
127    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
128        let raw = ec::Scalar::deserialize(bytes).map_err(ApiError::from)?;
129        let serialized = raw.serialize();
130        Ok(Self {
131            raw,
132            bytes: serialized,
133        })
134    }
135
136    /// Export the secret scalar in a zeroizing buffer.
137    pub fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
138        zeroizing_bytes_from_slice(self.bytes.as_ref())
139    }
140}
141
142impl EcdsaP521Signature {
143    /// Parse a strictly encoded ASN.1 DER ECDSA signature.
144    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
145        SignatureComponents::from_der(bytes)?;
146        Ok(Self(bytes.to_vec()))
147    }
148
149    /// Return the DER encoding.
150    pub fn to_bytes(&self) -> &[u8] {
151        &self.0
152    }
153}
154
155impl SignatureTrait for EcdsaP521 {
156    type PublicKey = EcdsaP521PublicKey;
157    type SecretKey = EcdsaP521SecretKey;
158    type SignatureData = EcdsaP521Signature;
159    type KeyPair = (Self::PublicKey, Self::SecretKey);
160
161    fn name() -> &'static str {
162        "ECDSA-P521"
163    }
164
165    /// Generate an ECDSA key pair
166    ///
167    /// Generates a random private key d ∈ [1, n-1] and computes
168    /// the corresponding public key Q = d·G where G is the base point.
169    ///
170    /// Reference: FIPS 186-4, Appendix B.4.1
171    fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
172        // Generate EC keypair with private key in valid range [1, n-1]
173        let (sk_scalar, pk_point) = ec::generate_keypair(rng).map_err(ApiError::from)?;
174
175        // Serialize the private key scalar
176        let sk_bytes = sk_scalar.serialize();
177
178        // Verify the private key is non-zero (should never happen with proper generation)
179        if sk_bytes.iter().all(|&b| b == 0) {
180            return Err(ApiError::InvalidParameter {
181                context: "ECDSA-P521 keypair",
182                #[cfg(feature = "std")]
183                message: "Generated secret key is zero (internal error)".to_string(),
184            });
185        }
186
187        // Create the secret key structure
188        let secret_key = EcdsaP521SecretKey {
189            raw: sk_scalar,
190            bytes: sk_bytes,
191        };
192
193        // Serialize public key in uncompressed format
194        let public_key = EcdsaP521PublicKey(pk_point.serialize_uncompressed());
195
196        Ok((public_key, secret_key))
197    }
198
199    fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
200        keypair.0.clone()
201    }
202
203    fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
204        keypair.1.clone()
205    }
206
207    /// Sign a message using ECDSA
208    ///
209    /// Implements the ECDSA signature generation algorithm as specified in
210    /// FIPS 186-4, Section 6.3, with deterministic nonce generation per
211    /// RFC 6979.
212    ///
213    /// Algorithm:
214    /// 1. e = HASH(M), where HASH is SHA-512
215    /// 2. z = the leftmost min(N, bitlen(e)) bits of e, where N = 521
216    /// 3. Generate k deterministically per RFC 6979
217    /// 4. (x₁, y₁) = k·G
218    /// 5. r = x₁ mod n; if r = 0, go back to step 3
219    /// 6. s = k⁻¹(z + rd) mod n; if s = 0, go back to step 3
220    /// 7. Return signature (r, s)
221    fn sign(message: &[u8], secret_key: &Self::SecretKey) -> ApiResult<Self::SignatureData> {
222        // Step 1: Hash the message using SHA-512 (FIPS 186-5 specifies SHA-512 for P-521)
223        let mut hasher = Sha512::new();
224        hasher.update(message).map_err(ApiError::from)?;
225        let hash_output = hasher.finalize().map_err(ApiError::from)?;
226
227        // Step 2: Convert hash to integer z
228        // For P-521, we use the leftmost min(521, 512) = 512 bits of the hash
229        // SHA-512 produces 512 bits, so we use all of it
230        let mut z_bytes = [0u8; ec::P521_SCALAR_SIZE];
231        // Pad the hash output to 66 bytes by prepending zeros
232        z_bytes[ec::P521_SCALAR_SIZE - 64..].copy_from_slice(hash_output.as_ref());
233        let z = reduce_bytes_to_scalar(&z_bytes)?;
234
235        // Get the private key scalar d
236        let d = secret_key.raw.clone();
237        let mut d_bytes = d.serialize();
238        let nonces_result =
239            Rfc6979::<Sha512>::new(d_bytes.as_ref(), hash_output.as_ref(), &NIST_P521.n, 521);
240        d_bytes.zeroize();
241        let mut nonces = nonces_result?;
242
243        loop {
244            let mut nonce = nonces.next_nonce()?;
245            let mut nonce_bytes: [u8; ec::P521_SCALAR_SIZE] =
246                (&nonce[..])
247                    .try_into()
248                    .map_err(|_| ApiError::InvalidLength {
249                        context: "ECDSA-P521 nonce",
250                        expected: ec::P521_SCALAR_SIZE,
251                        actual: nonce.len(),
252                    })?;
253            nonce.zeroize();
254            let scalar = ec::Scalar::new(nonce_bytes).map_err(ApiError::from);
255            nonce_bytes.zeroize();
256            let k = scalar?;
257
258            // Step 4: Compute (x₁, y₁) = k·G
259            let kg = ec::scalar_mult_base_g(&k).map_err(ApiError::from)?;
260            let r_bytes = Zeroizing::new(kg.x_coordinate_bytes());
261
262            // Step 5: Compute r = x₁ mod n
263            let r = reduce_bytes_to_scalar(&r_bytes)?;
264            if r.is_zero() {
265                continue;
266            }
267
268            // Compute k⁻¹ mod n
269            let k_inv = k.inv_mod_n().map_err(ApiError::from)?;
270
271            // Step 6: Compute s = k⁻¹(z + rd) mod n
272            let rd = r.mul_mod_n(&d).map_err(ApiError::from)?;
273
274            let z_plus_rd = z.add_mod_n(&rd).map_err(ApiError::from)?;
275
276            let mut s = k_inv.mul_mod_n(&z_plus_rd).map_err(ApiError::from)?;
277
278            // If s = 0, try again (extremely unlikely)
279            if s.is_zero() {
280                continue;
281            }
282            if is_high_s(s.serialize().as_ref(), &NIST_P521.n) {
283                s = s.negate();
284            }
285
286            // Step 7: Create signature (r, s)
287            let sig = SignatureComponents {
288                r: r.serialize().to_vec(),
289                s: s.serialize().to_vec(),
290            };
291
292            // Encode signature in DER format
293            let der_sig = sig.to_der();
294
295            return Ok(EcdsaP521Signature(der_sig));
296        }
297    }
298
299    /// Verify an ECDSA signature
300    ///
301    /// Implements the ECDSA signature verification algorithm as specified in
302    /// FIPS 186-4, Section 6.4.
303    ///
304    /// Algorithm:
305    /// 1. Verify that r and s are integers in [1, n-1]
306    /// 2. e = HASH(M), where HASH is SHA-512
307    /// 3. z = the leftmost min(N, bitlen(e)) bits of e, where N = 521
308    /// 4. w = s⁻¹ mod n
309    /// 5. u₁ = zw mod n and u₂ = rw mod n
310    /// 6. (x₁, y₁) = u₁·G + u₂·Q
311    /// 7. If (x₁, y₁) = O, reject the signature
312    /// 8. v = x₁ mod n
313    /// 9. Accept the signature if and only if v = r
314    fn verify(
315        message: &[u8],
316        signature: &Self::SignatureData,
317        public_key: &Self::PublicKey,
318    ) -> ApiResult<()> {
319        // Parse signature from DER format
320        let sig = SignatureComponents::from_der(&signature.0)?;
321
322        // Step 1: Verify r and s are in valid range [1, n-1]
323        if sig.r.len() > ec::P521_SCALAR_SIZE || sig.s.len() > ec::P521_SCALAR_SIZE {
324            return Err(ApiError::InvalidSignature {
325                context: "ECDSA-P521 verify",
326                #[cfg(feature = "std")]
327                message: "Invalid signature component size".to_string(),
328            });
329        }
330
331        // Convert r and s to scalars (with proper padding)
332        let mut r_bytes = [0u8; ec::P521_SCALAR_SIZE];
333        let mut s_bytes = [0u8; ec::P521_SCALAR_SIZE];
334        r_bytes[ec::P521_SCALAR_SIZE - sig.r.len()..].copy_from_slice(&sig.r);
335        s_bytes[ec::P521_SCALAR_SIZE - sig.s.len()..].copy_from_slice(&sig.s);
336
337        if !is_canonical_nonzero_scalar(&r_bytes, &NIST_P521.n)
338            || !is_canonical_nonzero_scalar(&s_bytes, &NIST_P521.n)
339        {
340            return Err(ApiError::InvalidSignature {
341                context: "ECDSA-P521 verify",
342                #[cfg(feature = "std")]
343                message: "signature components must be canonical integers in [1, n-1]".to_string(),
344            });
345        }
346
347        let r = ec::Scalar::new(r_bytes).map_err(|_| ApiError::InvalidSignature {
348            context: "ECDSA-P521 verify",
349            #[cfg(feature = "std")]
350            message: "Invalid r component".to_string(),
351        })?;
352
353        let s = ec::Scalar::new(s_bytes).map_err(|_| ApiError::InvalidSignature {
354            context: "ECDSA-P521 verify",
355            #[cfg(feature = "std")]
356            message: "Invalid s component".to_string(),
357        })?;
358        if is_high_s(s.serialize().as_ref(), &NIST_P521.n) {
359            return Err(ApiError::InvalidSignature {
360                context: "ECDSA-P521 verify",
361                #[cfg(feature = "std")]
362                message: "high-s signatures are non-canonical".to_string(),
363            });
364        }
365
366        // Step 2: Hash the message using SHA-512
367        let mut hasher = Sha512::new();
368        hasher.update(message).map_err(ApiError::from)?;
369        let hash_output = hasher.finalize().map_err(ApiError::from)?;
370
371        // Step 3: Convert hash to integer z
372        let mut z_bytes = [0u8; ec::P521_SCALAR_SIZE];
373        z_bytes[ec::P521_SCALAR_SIZE - 64..].copy_from_slice(hash_output.as_ref());
374        let z = reduce_bytes_to_scalar(&z_bytes)?;
375
376        // Step 4: Compute w = s⁻¹ mod n
377        let s_inv = s.inv_mod_n().map_err(ApiError::from)?;
378
379        // Step 5: Compute u₁ = zw mod n and u₂ = rw mod n
380        let u1 = z.mul_mod_n(&s_inv).map_err(ApiError::from)?;
381        let u2 = r.mul_mod_n(&s_inv).map_err(ApiError::from)?;
382
383        // Parse the public key point Q
384        let q = ec::Point::deserialize_uncompressed(&public_key.0).map_err(ApiError::from)?;
385
386        // Step 6: Compute point (x₁, y₁) = u₁·G + u₂·Q
387        let u1g = ec::scalar_mult_base_g(&u1).map_err(ApiError::from)?;
388
389        let u2q = ec::scalar_mult(&u2, &q).map_err(ApiError::from)?;
390
391        let point = u1g.add(&u2q);
392
393        // Step 7: Check if point is identity (point at infinity)
394        if point.is_identity() {
395            return Err(ApiError::InvalidSignature {
396                context: "ECDSA-P521 verify",
397                #[cfg(feature = "std")]
398                message: "Invalid signature: verification point is identity".to_string(),
399            });
400        }
401
402        // Step 8: Compute v = x₁ mod n
403        let x1_bytes = point.x_coordinate_bytes();
404        let x1 = reduce_bytes_to_scalar(&x1_bytes)?;
405
406        // Step 9: Verify v = r using constant-time comparison
407        if !ct_eq(r.serialize(), x1.serialize()) {
408            return Err(ApiError::InvalidSignature {
409                context: "ECDSA-P521 verify",
410                #[cfg(feature = "std")]
411                message: "Signature verification failed".to_string(),
412            });
413        }
414
415        Ok(())
416    }
417}
418
419fn reduce_bytes_to_scalar(bytes: &[u8; 66]) -> ApiResult<ec::Scalar> {
420    Ok(ec::Scalar::from_bytes_reduced(*bytes))
421}
422
423#[cfg(test)]
424mod tests;