elastic_elgamal/group/
curve25519.rs

1use core::convert::TryInto;
2
3use elliptic_curve::rand_core::{CryptoRng, RngCore};
4
5use crate::{
6    curve25519::{
7        constants::{ED25519_BASEPOINT_POINT, ED25519_BASEPOINT_TABLE},
8        edwards::{CompressedEdwardsY, EdwardsPoint},
9        scalar::Scalar,
10        traits::{Identity, IsIdentity, MultiscalarMul, VartimeMultiscalarMul},
11    },
12    group::{ElementOps, Group, RandomBytesProvider, ScalarOps},
13};
14
15/// Prime-order subgroup of Curve25519 without any transforms performed for EC points.
16///
17/// Since the curve has cofactor 8, [`ElementOps::deserialize_element()`] implementation
18/// explicitly checks on deserializing each EC point that the point is torsion-free
19/// (belongs to the prime-order subgroup), which is moderately slow (takes ~0.1ms on
20/// a laptop).
21///
22/// Prefer using [`Ristretto`] if compatibility with other Curve25519 applications is not a concern.
23/// (If it *is* a concern, beware of [cofactor pitfalls]!)
24///
25/// [`Ristretto`]: crate::group::Ristretto
26/// [cofactor pitfalls]: https://ristretto.group/why_ristretto.html#pitfalls-of-a-cofactor
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[cfg_attr(
29    docsrs,
30    doc(cfg(any(feature = "curve25519-dalek", feature = "curve25519-dalek-ng")))
31)]
32pub struct Curve25519Subgroup(());
33
34impl ScalarOps for Curve25519Subgroup {
35    type Scalar = Scalar;
36
37    const SCALAR_SIZE: usize = 32;
38
39    fn generate_scalar<R: CryptoRng + RngCore>(rng: &mut R) -> Self::Scalar {
40        let mut scalar_bytes = [0_u8; 64];
41        rng.fill_bytes(&mut scalar_bytes[..]);
42        Scalar::from_bytes_mod_order_wide(&scalar_bytes)
43    }
44
45    fn scalar_from_random_bytes(source: RandomBytesProvider<'_>) -> Self::Scalar {
46        let mut scalar_bytes = [0_u8; 64];
47        source.fill_bytes(&mut scalar_bytes);
48        Scalar::from_bytes_mod_order_wide(&scalar_bytes)
49    }
50
51    fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
52        scalar.invert()
53    }
54
55    #[cfg(feature = "curve25519-dalek")]
56    fn invert_scalars(scalars: &mut [Self::Scalar]) {
57        Scalar::invert_batch_alloc(scalars);
58    }
59
60    #[cfg(feature = "curve25519-dalek-ng")]
61    fn invert_scalars(scalars: &mut [Self::Scalar]) {
62        Scalar::batch_invert(scalars);
63    }
64
65    fn serialize_scalar(scalar: &Self::Scalar, buffer: &mut [u8]) {
66        buffer.copy_from_slice(&scalar.to_bytes());
67    }
68
69    #[cfg(feature = "curve25519-dalek")]
70    fn deserialize_scalar(buffer: &[u8]) -> Option<Self::Scalar> {
71        let bytes: &[u8; 32] = buffer.try_into().expect("input has incorrect byte size");
72        Scalar::from_canonical_bytes(*bytes).into()
73    }
74
75    #[cfg(feature = "curve25519-dalek-ng")]
76    fn deserialize_scalar(buffer: &[u8]) -> Option<Self::Scalar> {
77        let bytes: &[u8; 32] = buffer.try_into().expect("input has incorrect byte size");
78        Scalar::from_canonical_bytes(*bytes)
79    }
80}
81
82impl ElementOps for Curve25519Subgroup {
83    type Element = EdwardsPoint;
84
85    const ELEMENT_SIZE: usize = 32;
86
87    fn identity() -> Self::Element {
88        EdwardsPoint::identity()
89    }
90
91    fn is_identity(element: &Self::Element) -> bool {
92        element.is_identity()
93    }
94
95    fn generator() -> Self::Element {
96        ED25519_BASEPOINT_POINT
97    }
98
99    fn serialize_element(element: &Self::Element, buffer: &mut [u8]) {
100        buffer.copy_from_slice(&element.compress().to_bytes());
101    }
102
103    #[cfg(feature = "curve25519-dalek")]
104    fn deserialize_element(buffer: &[u8]) -> Option<Self::Element> {
105        CompressedEdwardsY::from_slice(buffer)
106            .ok()?
107            .decompress()
108            .filter(EdwardsPoint::is_torsion_free)
109    }
110
111    #[cfg(feature = "curve25519-dalek-ng")]
112    fn deserialize_element(buffer: &[u8]) -> Option<Self::Element> {
113        CompressedEdwardsY::from_slice(buffer)
114            .decompress()
115            .filter(EdwardsPoint::is_torsion_free)
116    }
117}
118
119impl Group for Curve25519Subgroup {
120    #[cfg(feature = "curve25519-dalek")]
121    fn mul_generator(k: &Scalar) -> Self::Element {
122        k * ED25519_BASEPOINT_TABLE
123    }
124
125    #[cfg(feature = "curve25519-dalek-ng")]
126    fn mul_generator(k: &Scalar) -> Self::Element {
127        k * &ED25519_BASEPOINT_TABLE
128    }
129
130    fn vartime_mul_generator(k: &Scalar) -> Self::Element {
131        #[cfg(feature = "curve25519-dalek")]
132        let zero = Scalar::ZERO;
133        #[cfg(feature = "curve25519-dalek-ng")]
134        let zero = Scalar::zero();
135
136        EdwardsPoint::vartime_double_scalar_mul_basepoint(&zero, &EdwardsPoint::identity(), k)
137    }
138
139    fn multi_mul<'a, I, J>(scalars: I, elements: J) -> Self::Element
140    where
141        I: IntoIterator<Item = &'a Self::Scalar>,
142        J: IntoIterator<Item = Self::Element>,
143    {
144        EdwardsPoint::multiscalar_mul(scalars, elements)
145    }
146
147    fn vartime_double_mul_generator(
148        k: &Scalar,
149        k_element: Self::Element,
150        r: &Scalar,
151    ) -> Self::Element {
152        EdwardsPoint::vartime_double_scalar_mul_basepoint(k, &k_element, r)
153    }
154
155    fn vartime_multi_mul<'a, I, J>(scalars: I, elements: J) -> Self::Element
156    where
157        I: IntoIterator<Item = &'a Self::Scalar>,
158        J: IntoIterator<Item = Self::Element>,
159    {
160        EdwardsPoint::vartime_multiscalar_mul(scalars, elements)
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use crate::{
168        PublicKeyConversionError,
169        curve25519::{constants::EIGHT_TORSION, scalar::Scalar, traits::Identity},
170    };
171
172    type PublicKey = crate::PublicKey<Curve25519Subgroup>;
173
174    #[test]
175    fn mangled_point_is_invalid_public_key() {
176        let mut rng = rand::rng();
177        for _ in 0..100 {
178            let mut point =
179                Curve25519Subgroup::mul_generator(&Curve25519Subgroup::generate_scalar(&mut rng));
180            point += EIGHT_TORSION[1];
181            assert!(!point.is_torsion_free());
182            let bytes = point.compress().to_bytes();
183            assert!(matches!(
184                PublicKey::from_bytes(&bytes).unwrap_err(),
185                PublicKeyConversionError::InvalidGroupElement
186            ));
187        }
188    }
189
190    #[test]
191    fn small_order_points_are_invalid_public_keys() {
192        let small_order = Scalar::from(8_u32);
193        // First element of `EIGHT_TORSION` is the point at infinity; since it
194        // would be processed differently, we skip it.
195        for point in EIGHT_TORSION.iter().skip(1) {
196            assert_eq!(point * small_order, EdwardsPoint::identity());
197            let bytes = point.compress().to_bytes();
198            assert!(matches!(
199                PublicKey::from_bytes(&bytes).unwrap_err(),
200                PublicKeyConversionError::InvalidGroupElement
201            ));
202        }
203    }
204}