Skip to main content

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