Skip to main content

primitives/algebra/elliptic_curve/p384/
mod.rs

1use std::{
2    hash::{Hash, Hasher},
3    iter::Sum,
4    mem::MaybeUninit,
5    ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
6};
7
8use elliptic_curve::{
9    bigint::U384,
10    group::{Group, GroupEncoding},
11    hash2curve::{ExpandMsgXmd, GroupDigest},
12    ops::MulByGenerator,
13    sec1::{FromEncodedPoint, ToEncodedPoint},
14    FieldBytesEncoding,
15};
16use ff::Field;
17use hybrid_array::Array;
18use rand::RngCore;
19use sha2::Sha384;
20use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
21use typenum::{U3, U48};
22
23use crate::{
24    algebra::{
25        elliptic_curve::{
26            curve::{FromCoordinates, PointAtInfinityError, ToCoordinates},
27            BaseFieldElement,
28            Curve,
29        },
30        field::{FieldExtension, SubfieldElement},
31    },
32    errors::PrimitiveError,
33    utils::codec::InPlaceCodec,
34};
35
36/// Implements the locally-defined field trait surface shared by both P-384 fields (scalar and
37/// base): hashing, byte encodings, uniform sampling, naive wide ops, and `FieldExtension`.
38///
39/// `$field` is an `ff`-derived prime field over 7 limbs (the derive requires `2 * modulus` to
40/// fit the backing representation, so a 384-bit modulus needs 448 bits); `$repr` is the derive's
41/// generated 56-byte little-endian repr type.
42macro_rules! impl_p384_field {
43    ($field:ident, $repr:ident, $name:literal) => {
44        impl std::hash::Hash for $field {
45            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
46                use crate::algebra::field::FieldExtension;
47                self.to_le_bytes().into_iter().for_each(|x| {
48                    x.hash(state);
49                });
50            }
51        }
52
53        impl From<u128> for $field {
54            fn from(value: u128) -> Self {
55                let mut bytes = [0u8; 56];
56                bytes[..16].copy_from_slice(&value.to_le_bytes());
57                <Self as ff::PrimeField>::from_repr($repr(bytes)).unwrap()
58            }
59        }
60
61        impl crate::algebra::uniform_bytes::FromUniformBytes for $field {
62            type UniformBytes = typenum::U64;
63
64            fn from_uniform_bytes(bytes: &hybrid_array::Array<u8, Self::UniformBytes>) -> Self {
65                use ff::PrimeField;
66                // Split the 512-bit input as lo + 2^256 * hi. Both halves are < 2^256 and the
67                // modulus is 384 bits, so all three `from_repr` inputs are canonical.
68                let mut lo = [0u8; 56];
69                lo[..32].copy_from_slice(&bytes[..32]);
70                let mut hi = [0u8; 56];
71                hi[..32].copy_from_slice(&bytes[32..]);
72                let mut pow = [0u8; 56];
73                pow[32] = 1;
74                let lo = Self::from_repr($repr(lo)).unwrap();
75                let hi = Self::from_repr($repr(hi)).unwrap();
76                let two_pow_256 = Self::from_repr($repr(pow)).unwrap();
77                hi * two_pow_256 + lo
78            }
79        }
80
81        impl crate::algebra::field::FieldExtension for $field {
82            type Subfield = Self;
83
84            type Degree = typenum::U1;
85            type FieldBitSize = typenum::U384;
86            type FieldBytesSize = typenum::U48;
87
88            fn to_subfield_elements(&self) -> hybrid_array::Array<Self::Subfield, Self::Degree> {
89                hybrid_array::Array([*self])
90            }
91
92            fn from_subfield_elements(
93                elems: hybrid_array::Array<Self::Subfield, Self::Degree>,
94            ) -> Self {
95                elems[0]
96            }
97
98            fn to_le_bytes(&self) -> hybrid_array::Array<u8, Self::FieldBytesSize> {
99                // The derived repr is 56 bytes; the top 8 are always zero for a 384-bit modulus.
100                <[u8; 48]>::try_from(&ff::PrimeField::to_repr(self).as_ref()[..48])
101                    .unwrap()
102                    .into()
103            }
104
105            fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
106                if bytes.len() != 48 {
107                    return None;
108                }
109                let mut repr = [0u8; 56];
110                repr[..48].copy_from_slice(bytes);
111                ff::PrimeField::from_repr($repr(repr)).into()
112            }
113
114            fn mul_by_subfield(&self, other: &Self::Subfield) -> Self {
115                *self * other
116            }
117
118            fn generator() -> Self {
119                <Self as ff::PrimeField>::MULTIPLICATIVE_GENERATOR
120            }
121        }
122
123        // SAFETY: `write_le_bytes`/`read_le_bytes` delegate to `FieldExtension::to_le_bytes`/
124        // `from_le_bytes` above, a fixed 48-byte, architecture-independent little-endian encoding
125        // backed by `ff::PrimeField`'s `to_repr`/`from_repr`. `write_le_bytes` initializes every
126        // byte, and `from_le_bytes` range-validates via `from_repr`, so the round-trip is unbiased.
127        unsafe impl crate::utils::codec::InPlaceCodec for $field {
128            const ENCODED_SIZE: usize = 48;
129
130            fn write_le_bytes(&self, out: &mut [std::mem::MaybeUninit<u8>]) {
131                let bytes = crate::algebra::field::FieldExtension::to_le_bytes(self);
132                for (slot, &b) in out.iter_mut().zip(bytes.iter()) {
133                    slot.write(b);
134                }
135            }
136
137            fn read_le_bytes(bytes: &[u8]) -> Result<Self, crate::errors::PrimitiveError> {
138                <Self as crate::algebra::field::FieldExtension>::from_le_bytes(bytes).ok_or_else(
139                    || {
140                        crate::errors::PrimitiveError::DeserializationFailed(
141                            concat!("non-canonical ", $name, " encoding").into(),
142                        )
143                    },
144                )
145            }
146        }
147
148        impl crate::random::Random for $field {
149            fn random(rng: impl crate::random::CryptoRngCore) -> Self {
150                <Self as ff::Field>::random(rng)
151            }
152        }
153
154        impl crate::types::identifiers::Named for $field {
155            fn get_name() -> String {
156                $name.to_string()
157            }
158        }
159
160        // ponytail: eager reduction (WideType = Self); add a lazy-reduction accumulator like
161        // BF25519MulAccRepr if P-384 dot-product throughput ever matters.
162        impl crate::algebra::ops::IntoWide for $field {
163            #[inline]
164            fn to_wide(&self) -> Self {
165                *self
166            }
167
168            #[inline]
169            fn zero_wide() -> Self {
170                <Self as ff::Field>::ZERO
171            }
172        }
173
174        impl crate::algebra::ops::ReduceWide for $field {
175            #[inline]
176            fn reduce_mod_order(a: Self) -> Self {
177                a
178            }
179        }
180
181        impl crate::algebra::ops::MulAccReduce for $field {
182            type WideType = Self;
183
184            #[inline]
185            fn mul_acc(acc: &mut Self, a: Self, b: Self) {
186                *acc += a * b;
187            }
188        }
189
190        impl<'a> crate::algebra::ops::MulAccReduce<Self, &'a Self> for $field {
191            type WideType = Self;
192
193            #[inline]
194            fn mul_acc(acc: &mut Self, a: Self, b: &'a Self) {
195                *acc += a * b;
196            }
197        }
198
199        impl<'a> crate::algebra::ops::MulAccReduce<&'a Self, Self> for $field {
200            type WideType = Self;
201
202            #[inline]
203            fn mul_acc(acc: &mut Self, a: &'a Self, b: Self) {
204                *acc += *a * b;
205            }
206        }
207
208        impl<'a> crate::algebra::ops::MulAccReduce<&'a Self, &'a Self> for $field {
209            type WideType = Self;
210
211            #[inline]
212            fn mul_acc(acc: &mut Self, a: &'a Self, b: &'a Self) {
213                *acc += *a * b;
214            }
215        }
216
217        impl crate::algebra::ops::AccReduce for $field {
218            type WideType = Self;
219
220            #[inline]
221            fn acc(acc: &mut Self, a: Self) {
222                *acc += a;
223            }
224        }
225
226        impl<'a> crate::algebra::ops::AccReduce<&'a Self> for $field {
227            type WideType = Self;
228
229            #[inline]
230            fn acc(acc: &mut Self, a: &'a Self) {
231                *acc += a;
232            }
233        }
234
235        impl crate::algebra::ops::DefaultDotProduct for $field {}
236        impl crate::algebra::ops::DefaultDotProduct<Self, &Self> for $field {}
237        impl<'a> crate::algebra::ops::DefaultDotProduct<&'a Self, &'a Self> for $field {}
238        impl crate::algebra::ops::DefaultDotProduct<&Self, Self> for $field {}
239    };
240}
241
242pub mod base_field;
243pub mod scalar_field;
244
245pub use base_field::BaseFieldP384;
246pub use scalar_field::ScalarP384;
247
248/// Marker type for NIST P-384 (secp384r1).
249///
250/// A local marker (instead of [`::p384::NistP384`]) because the [`Curve`] trait requires `Hash`
251/// and the associated types require impls that the orphan rule forbids on foreign types.
252#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
253pub struct P384;
254
255impl elliptic_curve::Curve for P384 {
256    type FieldBytesSize = U48;
257    type Uint = U384;
258
259    const ORDER: U384 = U384::from_be_hex(
260        "ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973",
261    );
262}
263
264impl FieldBytesEncoding<P384> for U384 {}
265
266impl Curve for P384 {
267    const NAME: &'static str = "NIST P-384";
268    const SCALAR_BIG_ENDIAN: bool = false;
269    const POINT_BIG_ENDIAN: bool = true;
270    const BASE_FIELD_BIG_ENDIAN: bool = false;
271
272    type Point = PointP384;
273    type Scalar = ScalarP384;
274    type BaseField = BaseFieldP384;
275
276    fn hash_to_curve(bytes: &[u8]) -> Self::Point {
277        // RFC 9380 P384_XMD:SHA-384_SSWU_RO_ suite.
278        PointP384(
279            ::p384::NistP384::hash_from_bytes::<ExpandMsgXmd<Sha384>>(
280                &[bytes],
281                &[b"P384_XMD:SHA-384_SSWU_RO_"],
282            )
283            .expect("hash-to-curve with a fixed non-empty DST cannot fail"),
284        )
285    }
286}
287
288/// A NIST P-384 point in projective coordinates, wrapping [`::p384::ProjectivePoint`] to attach
289/// the impls the [`Curve`] trait needs but the orphan rule forbids on the foreign type (`Hash`,
290/// the `InPlaceCodec` encoding, coordinate conversions, and `Group<Scalar = ScalarP384>`).
291#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
292#[repr(transparent)]
293pub struct PointP384(pub ::p384::ProjectivePoint);
294
295impl Hash for PointP384 {
296    fn hash<H: Hasher>(&self, state: &mut H) {
297        // Canonical compressed encoding: consistent with `Eq` on projective points.
298        self.to_bytes().as_slice().hash(state);
299    }
300}
301
302impl ConstantTimeEq for PointP384 {
303    #[inline]
304    fn ct_eq(&self, other: &Self) -> Choice {
305        self.0.ct_eq(&other.0)
306    }
307}
308
309impl ConditionallySelectable for PointP384 {
310    #[inline]
311    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
312        Self(::p384::ProjectivePoint::conditional_select(
313            &a.0, &b.0, choice,
314        ))
315    }
316}
317
318#[macros::op_variants(owned)]
319impl Add<&PointP384> for PointP384 {
320    type Output = PointP384;
321
322    #[inline]
323    fn add(mut self, rhs: &PointP384) -> PointP384 {
324        self.0 += rhs.0;
325        self
326    }
327}
328
329#[macros::op_variants(owned)]
330impl AddAssign<&PointP384> for PointP384 {
331    #[inline]
332    fn add_assign(&mut self, rhs: &PointP384) {
333        self.0 += rhs.0;
334    }
335}
336
337#[macros::op_variants(owned)]
338impl Sub<&PointP384> for PointP384 {
339    type Output = PointP384;
340
341    #[inline]
342    fn sub(mut self, rhs: &PointP384) -> PointP384 {
343        self.0 -= rhs.0;
344        self
345    }
346}
347
348#[macros::op_variants(owned)]
349impl SubAssign<&PointP384> for PointP384 {
350    #[inline]
351    fn sub_assign(&mut self, rhs: &PointP384) {
352        self.0 -= rhs.0;
353    }
354}
355
356impl Neg for PointP384 {
357    type Output = PointP384;
358
359    #[inline]
360    fn neg(self) -> PointP384 {
361        Self(-self.0)
362    }
363}
364
365#[macros::op_variants(owned)]
366impl Mul<&ScalarP384> for PointP384 {
367    type Output = PointP384;
368
369    #[inline]
370    fn mul(self, rhs: &ScalarP384) -> PointP384 {
371        Self(self.0 * rhs.to_p384())
372    }
373}
374
375#[macros::op_variants(owned)]
376impl MulAssign<&ScalarP384> for PointP384 {
377    #[inline]
378    fn mul_assign(&mut self, rhs: &ScalarP384) {
379        self.0 *= rhs.to_p384();
380    }
381}
382
383impl Sum for PointP384 {
384    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
385        iter.fold(Self::identity(), |acc, x| acc + x)
386    }
387}
388
389impl<'a> Sum<&'a PointP384> for PointP384 {
390    fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
391        iter.fold(Self::identity(), |acc, x| acc + x)
392    }
393}
394
395impl Group for PointP384 {
396    type Scalar = ScalarP384;
397
398    fn random(rng: impl RngCore) -> Self {
399        Self(::p384::ProjectivePoint::random(rng))
400    }
401
402    fn identity() -> Self {
403        Self(::p384::ProjectivePoint::IDENTITY)
404    }
405
406    fn generator() -> Self {
407        Self(::p384::ProjectivePoint::GENERATOR)
408    }
409
410    fn is_identity(&self) -> Choice {
411        self.0.is_identity()
412    }
413
414    fn double(&self) -> Self {
415        Self(self.0.double())
416    }
417}
418
419impl MulByGenerator for PointP384 {}
420
421impl GroupEncoding for PointP384 {
422    type Repr = <::p384::ProjectivePoint as GroupEncoding>::Repr;
423
424    fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
425        ::p384::ProjectivePoint::from_bytes(bytes).map(Self)
426    }
427
428    fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
429        ::p384::ProjectivePoint::from_bytes_unchecked(bytes).map(Self)
430    }
431
432    fn to_bytes(&self) -> Self::Repr {
433        self.0.to_bytes()
434    }
435}
436
437// SAFETY: `write_le_bytes`/`read_le_bytes` use the curve's canonical `GroupEncoding`
438// (`to_bytes`/`from_bytes`), a fixed `ENCODED_SIZE`-byte, architecture-independent encoding.
439// `write_le_bytes` initializes every byte, and `read_le_bytes` validates the encoding via
440// `from_bytes`, so the round-trip is unbiased.
441unsafe impl InPlaceCodec for PointP384 {
442    const ENCODED_SIZE: usize = size_of::<<Self as GroupEncoding>::Repr>();
443
444    fn write_le_bytes(&self, out: &mut [MaybeUninit<u8>]) {
445        let bytes = self.to_bytes();
446        for (slot, &b) in out.iter_mut().zip(bytes.as_slice()) {
447            slot.write(b);
448        }
449    }
450
451    fn read_le_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError> {
452        let mut repr = <Self as GroupEncoding>::Repr::default();
453        if bytes.len() != repr.len() {
454            return Err(PrimitiveError::InvalidSize(repr.len(), bytes.len()));
455        }
456        repr.as_mut_slice().copy_from_slice(bytes);
457        Option::from(Self::from_bytes(&repr)).ok_or_else(|| {
458            PrimitiveError::DeserializationFailed("invalid curve point encoding".into())
459        })
460    }
461}
462
463fn base_field_to_be_bytes(element: BaseFieldP384) -> ::p384::FieldBytes {
464    let mut bytes: [u8; 48] = element.to_le_bytes().into();
465    bytes.reverse();
466    bytes.into()
467}
468
469fn base_field_from_be_bytes(bytes: &[u8]) -> Option<BaseFieldP384> {
470    let mut bytes: [u8; 48] = bytes.try_into().ok()?;
471    bytes.reverse();
472    BaseFieldP384::from_le_bytes(&bytes)
473}
474
475impl ToCoordinates for PointP384 {
476    type BaseFieldElement = BaseFieldElement<P384>;
477    type NumCoordinates = U3;
478
479    /// Returns projective Weierstrass coordinates [X, Y, Z], dehomogenized to [x, y, 1] so that
480    /// all nodes obtain identical coordinates for MAC checks. The identity maps to (0 : 1 : 0).
481    fn to_coordinates(self) -> Result<Array<Self::BaseFieldElement, U3>, PointAtInfinityError> {
482        if self.0.is_identity().into() {
483            return Ok(Array(
484                [BaseFieldP384::ZERO, BaseFieldP384::ONE, BaseFieldP384::ZERO]
485                    .map(SubfieldElement::new),
486            ));
487        }
488        let encoded = self.0.to_affine().to_encoded_point(false);
489        let x = base_field_from_be_bytes(encoded.x().unwrap()).unwrap();
490        let y = base_field_from_be_bytes(encoded.y().unwrap()).unwrap();
491        Ok(Array([x, y, BaseFieldP384::ONE].map(SubfieldElement::new)))
492    }
493}
494
495impl FromCoordinates for PointP384 {
496    type BaseFieldElement = BaseFieldElement<P384>;
497    type NumCoordinates = U3;
498
499    /// Inverse of [`ToCoordinates`]: accepts any projective representative, dehomogenizes by Z,
500    /// and validates the Weierstrass curve equation (via the SEC1 uncompressed decoder).
501    /// For Z = 0 the only projective point on the curve is the identity (0 : Y : 0) with Y != 0.
502    /// Returns `None` for points not on the curve.
503    #[allow(non_snake_case)]
504    fn from_coordinates(coordinates: Array<Self::BaseFieldElement, U3>) -> Option<Self> {
505        let [X, Y, Z] = coordinates.0.map(|coordinate| coordinate.inner());
506        let Some(z_inv) = Option::<BaseFieldP384>::from(Z.invert()) else {
507            let is_identity = bool::from(X.is_zero()) && !bool::from(Y.is_zero());
508            return is_identity.then_some(Self(::p384::ProjectivePoint::IDENTITY));
509        };
510        let x = X * z_inv;
511        let y = Y * z_inv;
512        let encoded = ::p384::EncodedPoint::from_affine_coordinates(
513            &base_field_to_be_bytes(x),
514            &base_field_to_be_bytes(y),
515            false,
516        );
517        let affine =
518            Option::<::p384::AffinePoint>::from(::p384::AffinePoint::from_encoded_point(&encoded))?;
519        Some(Self(affine.into()))
520    }
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526    use crate::random::{test_rng, Random};
527
528    fn random_scalar(rng: impl crate::random::CryptoRngCore) -> ScalarP384 {
529        Random::random(rng)
530    }
531
532    #[test]
533    fn test_group_law_and_encoding_roundtrip() {
534        let mut rng = test_rng();
535        let point = PointP384::random(&mut rng);
536        assert_eq!(point + point, point.double());
537
538        let bytes = point.to_bytes();
539        let decoded = Option::<PointP384>::from(PointP384::from_bytes(&bytes)).unwrap();
540        assert_eq!(point, decoded);
541
542        // Invalid encodings must be rejected.
543        let mut bad = bytes;
544        bad.as_mut_slice()[1..].fill(0xFF);
545        assert!(Option::<PointP384>::from(PointP384::from_bytes(&bad)).is_none());
546    }
547
548    #[test]
549    fn test_scalar_mul_matches_p384_crate() {
550        let mut rng = test_rng();
551        for _ in 0..10 {
552            let a = random_scalar(&mut rng);
553            let b = random_scalar(&mut rng);
554            assert_eq!((a * b).to_p384(), a.to_p384() * b.to_p384());
555            assert_eq!((a + b).to_p384(), a.to_p384() + b.to_p384());
556        }
557
558        let scalar = random_scalar(&mut rng);
559        let expected = ::p384::ProjectivePoint::GENERATOR * scalar.to_p384();
560        assert_eq!(PointP384::generator() * scalar, PointP384(expected));
561    }
562
563    #[test]
564    fn test_coordinates_roundtrip() {
565        let mut rng = test_rng();
566        let point = PointP384::random(&mut rng);
567        let coordinates = point.to_coordinates().unwrap();
568        assert_eq!(coordinates[2], SubfieldElement::new(BaseFieldP384::ONE));
569        assert_eq!(PointP384::from_coordinates(coordinates).unwrap(), point);
570
571        // Any projective representative of the same point is accepted.
572        let lambda = SubfieldElement::new(BaseFieldP384::from(7u128));
573        let scaled = Array([
574            coordinates[0] * lambda,
575            coordinates[1] * lambda,
576            coordinates[2] * lambda,
577        ]);
578        assert_eq!(PointP384::from_coordinates(scaled).unwrap(), point);
579
580        // The identity is (0 : 1 : 0) and round-trips.
581        let identity_coordinates = PointP384::identity().to_coordinates().unwrap();
582        assert_eq!(
583            PointP384::from_coordinates(identity_coordinates).unwrap(),
584            PointP384::identity()
585        );
586
587        // Z = 0 with X != 0 is not on the curve.
588        let mut bad_infinity = coordinates;
589        bad_infinity[2] = SubfieldElement::new(BaseFieldP384::ZERO);
590        assert!(PointP384::from_coordinates(bad_infinity).is_none());
591
592        // On-curve check: (x, y) not on the curve is rejected.
593        let mut off_curve = coordinates;
594        off_curve[1] += SubfieldElement::new(BaseFieldP384::ONE);
595        assert!(PointP384::from_coordinates(off_curve).is_none());
596    }
597
598    #[test]
599    fn test_hash_to_curve() {
600        let point1 = P384::hash_to_curve(b"async-mpc test input");
601        let point2 = P384::hash_to_curve(b"async-mpc test input");
602        let point3 = P384::hash_to_curve(b"different input");
603        assert_eq!(point1, point2);
604        assert_ne!(point1, point3);
605        assert!(!bool::from(point1.is_identity()));
606    }
607
608    #[test]
609    fn test_inplace_roundtrip() {
610        let mut rng = test_rng();
611        let point = PointP384::random(&mut rng);
612        let serialized = point.to_inplace_bytes();
613        assert_eq!(PointP384::from_inplace_bytes(&serialized).unwrap(), point);
614    }
615}