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