Skip to main content

noxtls_crypto/pkc/primitive/
p384.rs

1// Copyright (c) 2019-2026, Argenox Technologies LLC
2// All rights reserved.
3//
4// SPDX-License-Identifier: GPL-2.0-only OR LicenseRef-Argenox-Commercial-License
5//
6// This file is part of the NoxTLS Library.
7//
8// This program is free software: you can redistribute it and/or modify
9// it under the terms of the GNU General Public License as published by the
10// Free Software Foundation; version 2 of the License.
11//
12// Alternatively, this file may be used under the terms of a commercial
13// license from Argenox Technologies LLC.
14//
15// See `noxtls/LICENSE` and `noxtls/LICENSE.md` in this repository for full details.
16// CONTACT: info@argenox.com
17
18use core::cmp::Ordering;
19
20use crate::drbg::HmacDrbgSha256;
21use crate::hash::noxtls_sha384;
22use crate::internal_alloc::Vec;
23use noxtls_core::{Error, Result};
24
25use super::bignum::BigUint;
26
27/// Represents a P-384 private scalar.
28#[derive(Debug, Clone, Eq, PartialEq)]
29pub struct P384PrivateKey {
30    scalar: BigUint,
31}
32
33/// Represents a P-384 public point on secp384r1.
34#[derive(Debug, Clone, Eq, PartialEq)]
35pub struct P384PublicKey {
36    point: CurvePoint,
37}
38
39#[derive(Debug, Clone, Eq, PartialEq)]
40struct CurvePoint {
41    x: BigUint,
42    y: BigUint,
43    infinity: bool,
44}
45
46#[derive(Debug, Clone, Eq, PartialEq)]
47struct JacobianPoint {
48    x: BigUint,
49    y: BigUint,
50    z: BigUint,
51    infinity: bool,
52}
53
54impl P384PrivateKey {
55    /// Parses and validates a private scalar as a big-endian 48-byte value.
56    ///
57    /// # Arguments
58    /// * `bytes`: 48-byte private scalar encoding.
59    ///
60    /// # Returns
61    /// Parsed `P384PrivateKey` when scalar is in the valid range `(0, n)`.
62    pub fn from_bytes(bytes: [u8; 48]) -> Result<Self> {
63        let scalar = BigUint::from_be_bytes(&bytes);
64        if scalar.is_zero() {
65            return Err(Error::CryptoFailure("p384 private scalar must be non-zero"));
66        }
67        let n = curve_order_n();
68        if scalar.cmp(&n) != Ordering::Less {
69            return Err(Error::CryptoFailure("p384 private scalar out of range"));
70        }
71        Ok(Self { scalar })
72    }
73
74    /// Serializes the private scalar as canonical 48-byte big-endian octets.
75    ///
76    /// # Arguments
77    /// * `self`: Private key scalar to encode.
78    ///
79    /// # Returns
80    /// 48-byte scalar encoding used by SEC1/PKCS#8 wrappers.
81    ///
82    /// # Errors
83    ///
84    /// Returns [`Error::InvalidLength`] when internal scalar encoding cannot be represented in 48 bytes.
85    ///
86    /// # Panics
87    ///
88    /// This function does not panic.
89    pub fn to_bytes(&self) -> Result<[u8; 48]> {
90        let bytes = self.scalar.to_be_bytes_padded(48)?;
91        let mut out = [0_u8; 48];
92        out.copy_from_slice(&bytes);
93        Ok(out)
94    }
95
96    /// Clears private scalar material by zeroing backing limbs in place.
97    ///
98    /// # Arguments
99    /// * `self` — Private key whose scalar memory is scrubbed.
100    ///
101    /// # Returns
102    /// `()`; leaves the scalar in a cleared zero state.
103    ///
104    /// # Panics
105    ///
106    /// This function does not panic.
107    pub fn clear(&mut self) {
108        self.scalar.clear();
109    }
110
111    /// Computes the corresponding public key by scalar multiplication of the base point.
112    ///
113    /// # Arguments
114    /// * `self`: Private key used for base-point multiplication.
115    ///
116    /// # Returns
117    /// Derived `P384PublicKey`.
118    pub fn public_key(&self) -> Result<P384PublicKey> {
119        let g = curve_base_point();
120        let point = scalar_mul(&self.scalar, &g)?;
121        if point.infinity {
122            return Err(Error::CryptoFailure(
123                "p384 public key derivation produced infinity",
124            ));
125        }
126        Ok(P384PublicKey { point })
127    }
128
129    /// Performs checked ECDH with peer public key and returns 48-byte x-coordinate secret.
130    ///
131    /// # Arguments
132    /// * `self`: Local private key.
133    /// * `peer`: Peer public key to validate and use.
134    ///
135    /// # Returns
136    /// 48-byte shared secret from affine x-coordinate.
137    pub fn diffie_hellman(&self, peer: &P384PublicKey) -> Result<[u8; 48]> {
138        peer.validate()?;
139        let shared = scalar_mul(&self.scalar, &peer.point)?;
140        if shared.infinity {
141            return Err(Error::CryptoFailure("p384 shared point is at infinity"));
142        }
143        let secret = shared.x.to_be_bytes_padded(48)?;
144        let mut out = [0_u8; 48];
145        out.copy_from_slice(&secret);
146        if is_all_zero(&out) {
147            return Err(Error::CryptoFailure("p384 shared secret is all-zero"));
148        }
149        Ok(out)
150    }
151
152    /// Signs a message with P-384 ECDSA using SHA-384 and deterministic nonce derivation.
153    ///
154    /// # Arguments
155    /// * `message`: Message bytes to hash and sign.
156    ///
157    /// # Returns
158    /// Signature tuple `(r, s)` as 48-byte big-endian scalars.
159    pub fn sign_sha384(&self, message: &[u8]) -> Result<([u8; 48], [u8; 48])> {
160        let digest = noxtls_sha384(message);
161        self.sign_digest(&digest)
162    }
163
164    /// Signs a message with P-384 ECDSA using SHA-384 and DRBG-generated nonce candidates.
165    ///
166    /// # Arguments
167    /// * `message`: Message bytes to hash and sign.
168    /// * `drbg`: DRBG used to generate per-signature nonce candidates.
169    ///
170    /// # Returns
171    /// Signature tuple `(r, s)` as 48-byte big-endian scalars.
172    pub fn sign_sha384_auto(
173        &self,
174        message: &[u8],
175        drbg: &mut HmacDrbgSha256,
176    ) -> Result<([u8; 48], [u8; 48])> {
177        let digest = noxtls_sha384(message);
178        self.sign_digest_auto(&digest, drbg)
179    }
180
181    /// Signs a precomputed 48-byte digest with P-384 ECDSA using deterministic nonce derivation.
182    ///
183    /// # Arguments
184    /// * `digest`: Precomputed SHA-384 digest bytes to sign.
185    ///
186    /// # Returns
187    /// Signature tuple `(r, s)` as 48-byte big-endian scalars.
188    pub fn sign_digest(&self, digest: &[u8; 48]) -> Result<([u8; 48], [u8; 48])> {
189        let n = curve_order_n();
190        let g = curve_base_point();
191        let e = BigUint::from_be_bytes(digest).modulo(&n);
192        let d = self.scalar.clone();
193
194        // Deterministic nonce derivation keeps signatures reproducible for fixed key+digest.
195        let mut counter = 0_u32;
196        loop {
197            let k = derive_signing_nonce(&d, digest, counter).modulo(&n);
198            counter = counter.wrapping_add(1);
199            if k.is_zero() {
200                if counter == 0 {
201                    return Err(Error::CryptoFailure(
202                        "p384 ecdsa nonce derivation exhausted",
203                    ));
204                }
205                continue;
206            }
207
208            let rp = scalar_mul(&k, &g)?;
209            if rp.infinity {
210                if counter == 0 {
211                    return Err(Error::CryptoFailure(
212                        "p384 ecdsa nonce derivation exhausted",
213                    ));
214                }
215                continue;
216            }
217            let r_bn = rp.x.modulo(&n);
218            if r_bn.is_zero() {
219                if counter == 0 {
220                    return Err(Error::CryptoFailure(
221                        "p384 ecdsa nonce derivation exhausted",
222                    ));
223                }
224                continue;
225            }
226
227            let rd = mod_mul(&r_bn, &d, &n);
228            let e_plus_rd = e.add(&rd).modulo(&n);
229            let k_inv = mod_inv(&k, &n)?;
230            let s_bn = mod_mul(&k_inv, &e_plus_rd, &n);
231            if s_bn.is_zero() {
232                if counter == 0 {
233                    return Err(Error::CryptoFailure(
234                        "p384 ecdsa nonce derivation exhausted",
235                    ));
236                }
237                continue;
238            }
239
240            let mut r = [0_u8; 48];
241            let mut s = [0_u8; 48];
242            r.copy_from_slice(&r_bn.to_be_bytes_padded(48)?);
243            s.copy_from_slice(&s_bn.to_be_bytes_padded(48)?);
244            return Ok((r, s));
245        }
246    }
247
248    /// Signs a precomputed 48-byte digest with P-384 ECDSA using DRBG-generated nonce candidates.
249    ///
250    /// # Arguments
251    /// * `digest`: Precomputed SHA-384 digest bytes to sign.
252    /// * `drbg`: DRBG used to generate per-signature nonce candidates.
253    ///
254    /// # Returns
255    /// Signature tuple `(r, s)` as 48-byte big-endian scalars.
256    pub fn sign_digest_auto(
257        &self,
258        digest: &[u8; 48],
259        drbg: &mut HmacDrbgSha256,
260    ) -> Result<([u8; 48], [u8; 48])> {
261        let n = curve_order_n();
262        let g = curve_base_point();
263        let e = BigUint::from_be_bytes(digest).modulo(&n);
264        let d = self.scalar.clone();
265
266        for _ in 0..64 {
267            let nonce_bytes = drbg.generate(48, b"p384_ecdsa_nonce")?;
268            let nonce_arr: [u8; 48] = nonce_bytes
269                .as_slice()
270                .try_into()
271                .map_err(|_| Error::InvalidLength("p384 ecdsa nonce length mismatch"))?;
272            let k = BigUint::from_be_bytes(&nonce_arr).modulo(&n);
273            if k.is_zero() {
274                continue;
275            }
276
277            let rp = scalar_mul(&k, &g)?;
278            if rp.infinity {
279                continue;
280            }
281            let r_bn = rp.x.modulo(&n);
282            if r_bn.is_zero() {
283                continue;
284            }
285
286            let rd = mod_mul(&r_bn, &d, &n);
287            let e_plus_rd = e.add(&rd).modulo(&n);
288            let k_inv = mod_inv(&k, &n)?;
289            let s_bn = mod_mul(&k_inv, &e_plus_rd, &n);
290            if s_bn.is_zero() {
291                continue;
292            }
293
294            let mut r = [0_u8; 48];
295            let mut s = [0_u8; 48];
296            r.copy_from_slice(&r_bn.to_be_bytes_padded(48)?);
297            s.copy_from_slice(&s_bn.to_be_bytes_padded(48)?);
298            return Ok((r, s));
299        }
300        Err(Error::CryptoFailure(
301            "p384 ecdsa nonce generation exhausted retry budget",
302        ))
303    }
304}
305
306impl Drop for P384PrivateKey {
307    fn drop(&mut self) {
308        self.clear();
309    }
310}
311
312impl P384PublicKey {
313    /// Parses uncompressed SEC1 point bytes (`04 || X || Y`) and validates curve membership.
314    ///
315    /// # Arguments
316    /// * `bytes`: SEC1 uncompressed public key bytes.
317    ///
318    /// # Returns
319    /// Parsed and validated `P384PublicKey`.
320    pub fn from_uncompressed(bytes: &[u8]) -> Result<Self> {
321        if bytes.len() != 97 {
322            return Err(Error::InvalidLength(
323                "p384 uncompressed public key must be 97 bytes",
324            ));
325        }
326        if bytes[0] != 0x04 {
327            return Err(Error::ParseFailure(
328                "p384 public key must be uncompressed SEC1 format",
329            ));
330        }
331        let x = BigUint::from_be_bytes(&bytes[1..49]);
332        let y = BigUint::from_be_bytes(&bytes[49..97]);
333        let point = CurvePoint {
334            x,
335            y,
336            infinity: false,
337        };
338        let key = Self { point };
339        key.validate()?;
340        Ok(key)
341    }
342
343    /// Encodes public point in uncompressed SEC1 format (`04 || X || Y`).
344    ///
345    /// # Arguments
346    /// * `self`: Public key to encode.
347    ///
348    /// # Returns
349    /// 97-byte uncompressed SEC1 encoding.
350    pub fn to_uncompressed(&self) -> Result<[u8; 97]> {
351        self.validate()?;
352        let mut out = [0_u8; 97];
353        out[0] = 0x04;
354        let x = self.point.x.to_be_bytes_padded(48)?;
355        let y = self.point.y.to_be_bytes_padded(48)?;
356        out[1..49].copy_from_slice(&x);
357        out[49..97].copy_from_slice(&y);
358        Ok(out)
359    }
360
361    /// Validates public point for range checks and on-curve equation.
362    ///
363    /// # Arguments
364    /// * `self`: Public key point to validate.
365    ///
366    /// # Returns
367    /// `Ok(())` when the point is finite, in range, and on curve.
368    pub fn validate(&self) -> Result<()> {
369        if self.point.infinity {
370            return Err(Error::CryptoFailure(
371                "p384 public point at infinity is invalid",
372            ));
373        }
374        let p = curve_modulus_p();
375        if self.point.x.cmp(&p) != Ordering::Less || self.point.y.cmp(&p) != Ordering::Less {
376            return Err(Error::CryptoFailure(
377                "p384 public point coordinates out of field range",
378            ));
379        }
380        if !is_point_on_curve(&self.point) {
381            return Err(Error::CryptoFailure("p384 public point is not on curve"));
382        }
383        Ok(())
384    }
385}
386
387/// Computes P-384 ECDH shared secret x-coordinate.
388///
389/// # Arguments
390/// * `private_key`: Local private key for scalar multiplication.
391/// * `peer_public_key`: Peer public key to validate and use.
392///
393/// # Returns
394/// 48-byte shared secret from the resulting affine x-coordinate.
395pub fn noxtls_p384_ecdh_shared_secret(
396    private_key: &P384PrivateKey,
397    peer_public_key: &P384PublicKey,
398) -> Result<[u8; 48]> {
399    private_key.diffie_hellman(peer_public_key)
400}
401
402/// Signs a message with P-384 ECDSA over SHA-384(message).
403///
404/// # Arguments
405/// * `private_key`: Private key used for signing.
406/// * `message`: Message bytes to hash and sign.
407///
408/// # Returns
409/// Signature tuple `(r, s)` as 48-byte scalars.
410pub fn noxtls_p384_ecdsa_sign_sha384(
411    private_key: &P384PrivateKey,
412    message: &[u8],
413) -> Result<([u8; 48], [u8; 48])> {
414    private_key.sign_sha384(message)
415}
416
417/// Signs a message with P-384 ECDSA over SHA-384(message) using DRBG-generated nonces.
418///
419/// # Arguments
420/// * `private_key`: Private key used for signing.
421/// * `message`: Message bytes to hash and sign.
422/// * `drbg`: DRBG used to generate per-signature nonce candidates.
423///
424/// # Returns
425/// Signature tuple `(r, s)` as 48-byte scalars.
426pub fn noxtls_p384_ecdsa_sign_sha384_auto(
427    private_key: &P384PrivateKey,
428    message: &[u8],
429    drbg: &mut HmacDrbgSha256,
430) -> Result<([u8; 48], [u8; 48])> {
431    private_key.sign_sha384_auto(message, drbg)
432}
433
434/// Signs a precomputed 48-byte digest with P-384 ECDSA.
435///
436/// # Arguments
437/// * `private_key`: Private key used for signing.
438/// * `digest`: Precomputed 48-byte digest.
439///
440/// # Returns
441/// Signature tuple `(r, s)` as 48-byte scalars.
442pub fn noxtls_p384_ecdsa_sign_digest(
443    private_key: &P384PrivateKey,
444    digest: &[u8; 48],
445) -> Result<([u8; 48], [u8; 48])> {
446    private_key.sign_digest(digest)
447}
448
449/// Signs a precomputed 48-byte digest with P-384 ECDSA using DRBG-generated nonces.
450///
451/// # Arguments
452/// * `private_key`: Private key used for signing.
453/// * `digest`: Precomputed 48-byte digest.
454/// * `drbg`: DRBG used to generate per-signature nonce candidates.
455///
456/// # Returns
457/// Signature tuple `(r, s)` as 48-byte scalars.
458pub fn noxtls_p384_ecdsa_sign_digest_auto(
459    private_key: &P384PrivateKey,
460    digest: &[u8; 48],
461    drbg: &mut HmacDrbgSha256,
462) -> Result<([u8; 48], [u8; 48])> {
463    private_key.sign_digest_auto(digest, drbg)
464}
465
466/// Verifies a P-384 ECDSA signature over SHA-384(message) using raw `(r, s)` values.
467///
468/// # Arguments
469/// * `public_key`: Public key used for verification.
470/// * `message`: Original message bytes.
471/// * `r`: Signature `r` scalar in big-endian form.
472/// * `s`: Signature `s` scalar in big-endian form.
473///
474/// # Returns
475/// `Ok(())` when the signature is valid.
476pub fn noxtls_p384_ecdsa_verify_sha384(
477    public_key: &P384PublicKey,
478    message: &[u8],
479    r: &[u8; 48],
480    s: &[u8; 48],
481) -> Result<()> {
482    let digest = noxtls_sha384(message);
483    noxtls_p384_ecdsa_verify_digest(public_key, &digest, r, s)
484}
485
486/// Verifies a P-384 ECDSA signature over a precomputed 48-byte digest.
487///
488/// # Arguments
489/// * `public_key`: Public key used for verification.
490/// * `digest`: Precomputed 48-byte digest.
491/// * `r`: Signature `r` scalar in big-endian form.
492/// * `s`: Signature `s` scalar in big-endian form.
493///
494/// # Returns
495/// `Ok(())` when the signature is valid.
496pub fn noxtls_p384_ecdsa_verify_digest(
497    public_key: &P384PublicKey,
498    digest: &[u8; 48],
499    r: &[u8; 48],
500    s: &[u8; 48],
501) -> Result<()> {
502    public_key.validate()?;
503
504    let n = curve_order_n();
505    if is_all_zero(r) || is_all_zero(s) {
506        return Err(Error::CryptoFailure(
507            "p384 ecdsa signature scalars must be non-zero",
508        ));
509    }
510    let r_bn = BigUint::from_be_bytes(r);
511    let s_bn = BigUint::from_be_bytes(s);
512    if r_bn.cmp(&n) != Ordering::Less || s_bn.cmp(&n) != Ordering::Less {
513        return Err(Error::CryptoFailure(
514            "p384 ecdsa signature scalars out of range",
515        ));
516    }
517
518    let e = BigUint::from_be_bytes(digest).modulo(&n);
519    let w = mod_inv(&s_bn, &n)?;
520    let u1 = mod_mul(&e, &w, &n);
521    let u2 = mod_mul(&r_bn, &w, &n);
522
523    let g = curve_base_point();
524    let p1 = scalar_mul_jacobian(&u1, &g);
525    let p2 = scalar_mul_jacobian(&u2, &public_key.point);
526    let r_point = jacobian_add(&p1, &p2).to_affine()?;
527    if r_point.infinity {
528        return Err(Error::CryptoFailure(
529            "p384 ecdsa verification produced point at infinity",
530        ));
531    }
532    let v = r_point.x.modulo(&n);
533    let v_bytes = v.to_be_bytes_padded(48)?;
534    if ct_bytes_eq(v_bytes.as_slice(), r) {
535        return Ok(());
536    }
537    Err(Error::CryptoFailure("p384 ecdsa verification failed"))
538}
539
540/// Generates a P-384 private key from DRBG output with bounded retry for scalar-range checks.
541///
542/// # Arguments
543/// * `drbg`: DRBG instance used to fill private scalar candidate bytes.
544///
545/// # Returns
546/// Parsed `P384PrivateKey` when a generated scalar is in the valid range `(0, n)`.
547pub fn noxtls_p384_generate_private_key_auto(drbg: &mut HmacDrbgSha256) -> Result<P384PrivateKey> {
548    for _ in 0..64 {
549        let scalar = drbg.generate(48, b"p384_private_scalar")?;
550        let bytes: [u8; 48] = scalar
551            .as_slice()
552            .try_into()
553            .map_err(|_| Error::InvalidLength("p384 private scalar length mismatch"))?;
554        if let Ok(key) = P384PrivateKey::from_bytes(bytes) {
555            return Ok(key);
556        }
557    }
558    Err(Error::CryptoFailure(
559        "p384 private key generation exhausted retry budget",
560    ))
561}
562
563/// Performs scalar multiplication by double-and-add in Jacobian coordinates. Parameters: `scalar` multiplier and `point` affine input point.
564///
565/// # Arguments
566///
567/// * `scalar` — `&BigUint`.
568/// * `point` — `&CurvePoint`.
569///
570/// # Returns
571///
572/// On success, the `Ok` payload from `scalar_mul`; see implementation for value shape.
573///
574/// # Errors
575///
576/// Returns `noxtls_core::Error` when validation or a numeric step fails; see implementation for specific variants.
577///
578/// # Panics
579///
580/// This function does not panic unless otherwise noted.
581fn scalar_mul(scalar: &BigUint, point: &CurvePoint) -> Result<CurvePoint> {
582    scalar_mul_jacobian(scalar, point).to_affine()
583}
584
585/// Performs scalar multiplication and keeps result in Jacobian coordinates. Parameters: `scalar` multiplier and `point` affine input point.
586///
587/// # Arguments
588///
589/// * `scalar` — `&BigUint`.
590/// * `point` — `&CurvePoint`.
591///
592/// # Returns
593///
594/// `JacobianPoint` produced by `scalar_mul_jacobian` (see implementation).
595///
596/// # Panics
597///
598/// This function does not panic unless otherwise noted.
599fn scalar_mul_jacobian(scalar: &BigUint, point: &CurvePoint) -> JacobianPoint {
600    if point.infinity {
601        return JacobianPoint::infinity();
602    }
603
604    let base = JacobianPoint::from_affine(point);
605    let table = precompute_nibble_window(&base);
606    let mut acc = JacobianPoint::infinity();
607    let bits = scalar.to_be_bytes();
608    for byte in bits {
609        let hi = usize::from(byte >> 4);
610        for _ in 0..4 {
611            acc = jacobian_double(&acc);
612        }
613        if hi != 0 {
614            acc = jacobian_add(&acc, &table[hi]);
615        }
616
617        let lo = usize::from(byte & 0x0F);
618        for _ in 0..4 {
619            acc = jacobian_double(&acc);
620        }
621        if lo != 0 {
622            acc = jacobian_add(&acc, &table[lo]);
623        }
624    }
625    acc
626}
627
628/// Precomputes [0..15] * P table for nibble-window scalar multiplication.
629///
630/// # Arguments
631///
632/// * `base` — `&JacobianPoint`.
633///
634/// # Returns
635///
636/// `Vec<JacobianPoint>` produced by `precompute_nibble_window` (see implementation).
637///
638/// # Panics
639///
640/// This function does not panic unless otherwise noted.
641fn precompute_nibble_window(base: &JacobianPoint) -> Vec<JacobianPoint> {
642    let mut table = Vec::with_capacity(16);
643    table.push(JacobianPoint::infinity());
644    table.push(base.clone());
645    for idx in 2..16 {
646        let next = jacobian_add(&table[idx - 1], base);
647        table.push(next);
648    }
649    table
650}
651
652/// Returns true when point satisfies secp384r1 equation over field modulus. Parameter: `point` affine candidate point.
653///
654/// # Arguments
655///
656/// * `point` — `&CurvePoint`.
657///
658/// # Returns
659///
660/// `bool` produced by `is_point_on_curve` (see implementation).
661///
662/// # Panics
663///
664/// This function does not panic unless otherwise noted.
665fn is_point_on_curve(point: &CurvePoint) -> bool {
666    if point.infinity {
667        return false;
668    }
669    let p = curve_modulus_p();
670    let a = curve_a();
671    let b = curve_b();
672    let y_sq = mod_mul(&point.y, &point.y, &p);
673    let x_sq = mod_mul(&point.x, &point.x, &p);
674    let x_cu = mod_mul(&x_sq, &point.x, &p);
675    let ax = mod_mul(&a, &point.x, &p);
676    let rhs = mod_add(&mod_add(&x_cu, &ax, &p), &b, &p);
677    y_sq == rhs
678}
679
680impl CurvePoint {
681    // Returns additive identity point representation.
682    // Returns: affine infinity marker point.
683    fn infinity() -> Self {
684        Self {
685            x: BigUint::zero(),
686            y: BigUint::zero(),
687            infinity: true,
688        }
689    }
690}
691
692impl JacobianPoint {
693    // Returns additive identity point representation.
694    // Returns: Jacobian infinity marker point.
695    fn infinity() -> Self {
696        Self {
697            x: BigUint::zero(),
698            y: BigUint::zero(),
699            z: BigUint::zero(),
700            infinity: true,
701        }
702    }
703
704    // Converts an affine point into Jacobian representation.
705    // Parameter: `point` affine input point to lift.
706    fn from_affine(point: &CurvePoint) -> Self {
707        if point.infinity {
708            return Self::infinity();
709        }
710        Self {
711            x: point.x.clone(),
712            y: point.y.clone(),
713            z: BigUint::one(),
714            infinity: false,
715        }
716    }
717
718    // Converts Jacobian point into affine representation with one modular inverse.
719    // Parameter: `self` Jacobian point to convert.
720    // Returns: affine point or infinity when projective point is at infinity.
721    fn to_affine(&self) -> Result<CurvePoint> {
722        if self.infinity || self.z.is_zero() {
723            return Ok(CurvePoint::infinity());
724        }
725        let p = curve_modulus_p();
726        let z_inv = mod_inv(&self.z, &p)?;
727        let z_inv2 = mod_mul(&z_inv, &z_inv, &p);
728        let z_inv3 = mod_mul(&z_inv2, &z_inv, &p);
729        Ok(CurvePoint {
730            x: mod_mul(&self.x, &z_inv2, &p),
731            y: mod_mul(&self.y, &z_inv3, &p),
732            infinity: false,
733        })
734    }
735}
736
737/// Doubles a Jacobian point using secp384r1 a=-3 formulas. Parameter: `a` Jacobian point to double.
738///
739/// # Arguments
740///
741/// * `a` — `&JacobianPoint`.
742///
743/// # Returns
744///
745/// `JacobianPoint` produced by `jacobian_double` (see implementation).
746///
747/// # Panics
748///
749/// This function does not panic unless otherwise noted.
750fn jacobian_double(a: &JacobianPoint) -> JacobianPoint {
751    if a.infinity || a.y.is_zero() {
752        return JacobianPoint::infinity();
753    }
754    let p = curve_modulus_p();
755    let two = BigUint::from_u128(2);
756    let three = BigUint::from_u128(3);
757    let four = BigUint::from_u128(4);
758    let eight = BigUint::from_u128(8);
759
760    let yy = mod_mul(&a.y, &a.y, &p);
761    let yyyy = mod_mul(&yy, &yy, &p);
762    let x_yy = mod_mul(&a.x, &yy, &p);
763    let s = mod_mul(&x_yy, &four, &p);
764
765    let zz = mod_mul(&a.z, &a.z, &p);
766    let x_minus_zz = mod_sub(&a.x, &zz, &p);
767    let x_plus_zz = mod_add(&a.x, &zz, &p);
768    let m_term = mod_mul(&x_minus_zz, &x_plus_zz, &p);
769    let m = mod_mul(&m_term, &three, &p);
770
771    let m2 = mod_mul(&m, &m, &p);
772    let two_s = mod_mul(&s, &two, &p);
773    let x3 = mod_sub(&m2, &two_s, &p);
774    let s_minus_x3 = mod_sub(&s, &x3, &p);
775    let m_s_minus_x3 = mod_mul(&m, &s_minus_x3, &p);
776    let eight_yyyy = mod_mul(&yyyy, &eight, &p);
777    let y3 = mod_sub(&m_s_minus_x3, &eight_yyyy, &p);
778    let yz = mod_mul(&a.y, &a.z, &p);
779    let z3 = mod_mul(&yz, &two, &p);
780
781    JacobianPoint {
782        x: x3,
783        y: y3,
784        z: z3,
785        infinity: false,
786    }
787}
788
789/// Adds two Jacobian points using complete formulas with exceptional-case handling. Parameters: `a` and `b` Jacobian input points.
790///
791/// # Arguments
792///
793/// * `a` — `&JacobianPoint`.
794/// * `b` — `&JacobianPoint`.
795///
796/// # Returns
797///
798/// `JacobianPoint` produced by `jacobian_add` (see implementation).
799///
800/// # Panics
801///
802/// This function does not panic unless otherwise noted.
803fn jacobian_add(a: &JacobianPoint, b: &JacobianPoint) -> JacobianPoint {
804    if a.infinity {
805        return b.clone();
806    }
807    if b.infinity {
808        return a.clone();
809    }
810
811    let p = curve_modulus_p();
812    let two = BigUint::from_u128(2);
813
814    let z1z1 = mod_mul(&a.z, &a.z, &p);
815    let z2z2 = mod_mul(&b.z, &b.z, &p);
816    let u1 = mod_mul(&a.x, &z2z2, &p);
817    let u2 = mod_mul(&b.x, &z1z1, &p);
818
819    let z1_cubed = mod_mul(&z1z1, &a.z, &p);
820    let z2_cubed = mod_mul(&z2z2, &b.z, &p);
821    let s1 = mod_mul(&a.y, &z2_cubed, &p);
822    let s2 = mod_mul(&b.y, &z1_cubed, &p);
823
824    if u1 == u2 {
825        if s1 != s2 {
826            return JacobianPoint::infinity();
827        }
828        return jacobian_double(a);
829    }
830
831    let h = mod_sub(&u2, &u1, &p);
832    let two_h = mod_mul(&h, &two, &p);
833    let i = mod_mul(&two_h, &two_h, &p);
834    let j = mod_mul(&h, &i, &p);
835    let s2_minus_s1 = mod_sub(&s2, &s1, &p);
836    let r = mod_mul(&s2_minus_s1, &two, &p);
837    let v = mod_mul(&u1, &i, &p);
838
839    let r2 = mod_mul(&r, &r, &p);
840    let two_v = mod_mul(&v, &two, &p);
841    let x3 = mod_sub(&mod_sub(&r2, &j, &p), &two_v, &p);
842
843    let v_minus_x3 = mod_sub(&v, &x3, &p);
844    let r_v_minus_x3 = mod_mul(&r, &v_minus_x3, &p);
845    let two_s1 = mod_mul(&s1, &two, &p);
846    let two_s1_j = mod_mul(&two_s1, &j, &p);
847    let y3 = mod_sub(&r_v_minus_x3, &two_s1_j, &p);
848
849    let z1_plus_z2 = mod_add(&a.z, &b.z, &p);
850    let z1_plus_z2_sq = mod_mul(&z1_plus_z2, &z1_plus_z2, &p);
851    let z_sum = mod_sub(&mod_sub(&z1_plus_z2_sq, &z1z1, &p), &z2z2, &p);
852    let z3 = mod_mul(&z_sum, &h, &p);
853
854    JacobianPoint {
855        x: x3,
856        y: y3,
857        z: z3,
858        infinity: false,
859    }
860}
861
862/// Computes `(a + b) mod m`. Parameters: operands `a`, `b`, and modulus `m`.
863///
864/// # Arguments
865///
866/// * `a` — `&BigUint`.
867/// * `b` — `&BigUint`.
868/// * `m` — `&BigUint`.
869///
870/// # Returns
871///
872/// `BigUint` produced by `mod_add` (see implementation).
873///
874/// # Panics
875///
876/// This function does not panic unless otherwise noted.
877fn mod_add(a: &BigUint, b: &BigUint, m: &BigUint) -> BigUint {
878    a.add(b).modulo(m)
879}
880
881/// Computes `(a - b) mod m`. Parameters: operands `a`, `b`, and modulus `m`.
882///
883/// # Arguments
884///
885/// * `a` — `&BigUint`.
886/// * `b` — `&BigUint`.
887/// * `m` — `&BigUint`.
888///
889/// # Returns
890///
891/// `BigUint` produced by `mod_sub` (see implementation).
892///
893/// # Panics
894///
895/// This function does not panic unless otherwise noted.
896fn mod_sub(a: &BigUint, b: &BigUint, m: &BigUint) -> BigUint {
897    if a.cmp(b) != Ordering::Less {
898        a.sub(b).modulo(m)
899    } else {
900        m.sub(&b.sub(a)).modulo(m)
901    }
902}
903
904/// Computes `(a * b) mod m`. Parameters: operands `a`, `b`, and modulus `m`.
905///
906/// # Arguments
907///
908/// * `a` — `&BigUint`.
909/// * `b` — `&BigUint`.
910/// * `m` — `&BigUint`.
911///
912/// # Returns
913///
914/// `BigUint` produced by `mod_mul` (see implementation).
915///
916/// # Panics
917///
918/// This function does not panic unless otherwise noted.
919fn mod_mul(a: &BigUint, b: &BigUint, m: &BigUint) -> BigUint {
920    a.mul(b).modulo(m)
921}
922
923/// Computes multiplicative inverse via Fermat's little theorem for prime modulus. Parameters: `a` value to invert and `m` prime modulus.
924///
925/// # Arguments
926///
927/// * `a` — `&BigUint`.
928/// * `m` — `&BigUint`.
929///
930/// # Returns
931///
932/// On success, the `Ok` payload from `mod_inv`; see implementation for value shape.
933///
934/// # Errors
935///
936/// Returns `noxtls_core::Error` when validation or a numeric step fails; see implementation for specific variants.
937///
938/// # Panics
939///
940/// This function does not panic unless otherwise noted.
941fn mod_inv(a: &BigUint, m: &BigUint) -> Result<BigUint> {
942    if a.is_zero() {
943        return Err(Error::CryptoFailure(
944            "p384 modular inverse of zero is undefined",
945        ));
946    }
947    let two = BigUint::from_u128(2);
948    let exp = m.sub(&two);
949    Ok(BigUint::mod_exp(a, &exp, m))
950}
951
952/// Returns secp384r1 field modulus p. Returns: prime field modulus as `BigUint`.
953///
954/// # Arguments
955///
956/// * *(none)* — This function takes no parameters.
957///
958/// # Returns
959///
960/// `BigUint` produced by `curve_modulus_p` (see implementation).
961///
962/// # Panics
963///
964/// This function does not panic unless otherwise noted.
965fn curve_modulus_p() -> BigUint {
966    BigUint::from_be_bytes(&[
967        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
968        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
969        0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
970        0xff, 0xff, 0xff,
971    ])
972}
973
974/// Returns secp384r1 curve coefficient a. Returns: curve `a` coefficient as `BigUint`.
975///
976/// # Arguments
977///
978/// * *(none)* — This function takes no parameters.
979///
980/// # Returns
981///
982/// `BigUint` produced by `curve_a` (see implementation).
983///
984/// # Panics
985///
986/// This function does not panic unless otherwise noted.
987fn curve_a() -> BigUint {
988    BigUint::from_be_bytes(&[
989        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
990        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
991        0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
992        0xff, 0xff, 0xfc,
993    ])
994}
995
996/// Returns secp384r1 curve coefficient b. Returns: curve `b` coefficient as `BigUint`.
997///
998/// # Arguments
999///
1000/// * *(none)* — This function takes no parameters.
1001///
1002/// # Returns
1003///
1004/// `BigUint` produced by `curve_b` (see implementation).
1005///
1006/// # Panics
1007///
1008/// This function does not panic unless otherwise noted.
1009fn curve_b() -> BigUint {
1010    BigUint::from_be_bytes(&[
1011        0xb3, 0x31, 0x2f, 0xa7, 0xe2, 0x3e, 0xe7, 0xe4, 0x98, 0x8e, 0x05, 0x6b, 0xe3, 0xf8, 0x2d,
1012        0x19, 0x18, 0x1d, 0x9c, 0x6e, 0xfe, 0x81, 0x41, 0x12, 0x03, 0x14, 0x08, 0x8f, 0x50, 0x13,
1013        0x87, 0x5a, 0xc6, 0x56, 0x39, 0x8d, 0x8a, 0x2e, 0xd1, 0x9d, 0x2a, 0x85, 0xc8, 0xed, 0xd3,
1014        0xec, 0x2a, 0xef,
1015    ])
1016}
1017
1018/// Returns secp384r1 subgroup order n. Returns: subgroup order as `BigUint`.
1019///
1020/// # Arguments
1021///
1022/// * *(none)* — This function takes no parameters.
1023///
1024/// # Returns
1025///
1026/// `BigUint` produced by `curve_order_n` (see implementation).
1027///
1028/// # Panics
1029///
1030/// This function does not panic unless otherwise noted.
1031fn curve_order_n() -> BigUint {
1032    BigUint::from_be_bytes(&[
1033        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1034        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37,
1035        0x2d, 0xdf, 0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a, 0xec, 0xec, 0x19, 0x6a, 0xcc,
1036        0xc5, 0x29, 0x73,
1037    ])
1038}
1039
1040/// Returns secp384r1 base point G. Returns: affine base point coordinates.
1041///
1042/// # Arguments
1043///
1044/// * *(none)* — This function takes no parameters.
1045///
1046/// # Returns
1047///
1048/// `CurvePoint` produced by `curve_base_point` (see implementation).
1049///
1050/// # Panics
1051///
1052/// This function does not panic unless otherwise noted.
1053fn curve_base_point() -> CurvePoint {
1054    CurvePoint {
1055        x: BigUint::from_be_bytes(&[
1056            0xaa, 0x87, 0xca, 0x22, 0xbe, 0x8b, 0x05, 0x37, 0x8e, 0xb1, 0xc7, 0x1e, 0xf3, 0x20,
1057            0xad, 0x74, 0x6e, 0x1d, 0x3b, 0x62, 0x8b, 0xa7, 0x9b, 0x98, 0x59, 0xf7, 0x41, 0xe0,
1058            0x82, 0x54, 0x2a, 0x38, 0x55, 0x02, 0xf2, 0x5d, 0xbf, 0x55, 0x29, 0x6c, 0x3a, 0x54,
1059            0x5e, 0x38, 0x72, 0x76, 0x0a, 0xb7,
1060        ]),
1061        y: BigUint::from_be_bytes(&[
1062            0x36, 0x17, 0xde, 0x4a, 0x96, 0x26, 0x2c, 0x6f, 0x5d, 0x9e, 0x98, 0xbf, 0x92, 0x92,
1063            0xdc, 0x29, 0xf8, 0xf4, 0x1d, 0xbd, 0x28, 0x9a, 0x14, 0x7c, 0xe9, 0xda, 0x31, 0x13,
1064            0xb5, 0xf0, 0xb8, 0xc0, 0x0a, 0x60, 0xb1, 0xce, 0x1d, 0x7e, 0x81, 0x9d, 0x7a, 0x43,
1065            0x1d, 0x7c, 0x90, 0xea, 0x0e, 0x5f,
1066        ]),
1067        infinity: false,
1068    }
1069}
1070
1071/// Returns true if every byte in the 48-byte input is zero. Parameter: `bytes` candidate secret bytes.
1072///
1073/// # Arguments
1074///
1075/// * `bytes` — `&[u8; 48]`.
1076///
1077/// # Returns
1078///
1079/// `bool` produced by `is_all_zero` (see implementation).
1080///
1081/// # Panics
1082///
1083/// This function does not panic unless otherwise noted.
1084fn is_all_zero(bytes: &[u8; 48]) -> bool {
1085    let mut acc = 0_u8;
1086    for byte in bytes {
1087        acc |= *byte;
1088    }
1089    acc == 0
1090}
1091
1092/// Compares two byte slices in constant-time when lengths are equal. Parameters: `left` and `right` byte slices to compare.
1093///
1094/// # Arguments
1095///
1096/// * `left` — `&[u8]`.
1097/// * `right` — `&[u8]`.
1098///
1099/// # Returns
1100///
1101/// `bool` produced by `ct_bytes_eq` (see implementation).
1102///
1103/// # Panics
1104///
1105/// This function does not panic unless otherwise noted.
1106fn ct_bytes_eq(left: &[u8], right: &[u8]) -> bool {
1107    if left.len() != right.len() {
1108        return false;
1109    }
1110    let mut diff = 0_u8;
1111    for (&l, &r) in left.iter().zip(right.iter()) {
1112        diff |= l ^ r;
1113    }
1114    diff == 0
1115}
1116
1117/// Derives a deterministic per-signature nonce candidate from key scalar, digest, and counter. Parameters: `private_scalar` signer key, `digest` message hash, and `counter` retry index.
1118///
1119/// # Arguments
1120///
1121/// * `private_scalar` — `&BigUint`.
1122/// * `digest` — `&[u8; 48]`.
1123/// * `counter` — `u32`.
1124///
1125/// # Returns
1126///
1127/// `BigUint` produced by `derive_signing_nonce` (see implementation).
1128///
1129/// # Panics
1130///
1131/// This function does not panic unless otherwise noted.
1132fn derive_signing_nonce(private_scalar: &BigUint, digest: &[u8; 48], counter: u32) -> BigUint {
1133    let mut seed = Vec::with_capacity(100);
1134    let scalar_bytes = private_scalar
1135        .to_be_bytes_padded(48)
1136        .expect("p384 private scalar should fit in 48 bytes");
1137    seed.extend_from_slice(&scalar_bytes);
1138    seed.extend_from_slice(digest);
1139    seed.extend_from_slice(&counter.to_be_bytes());
1140    BigUint::from_be_bytes(&noxtls_sha384(&seed))
1141}