elastic_elgamal/group/
curve25519.rs

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