Skip to main content

elliptic_curve_tools/
legacy.rs

1//! Bridge for curves still on the `group`/`ff` **0.13** traits.
2//!
3//! `elliptic-curve-tools` is built on `group`/`ff` 0.14. Many curve crates have not
4//! migrated yet (curve25519-dalek, ed448-goldilocks, bls12-381 forks, ...). Enable the
5//! `legacy` feature and this module bridges them: [`Group013`] wraps any 0.13 group
6//! element as a 0.14 [`Group`], [`Scalar013`] wraps its scalar as a 0.14 [`PrimeField`],
7//! by delegating every operation to the inner 0.13 type. Both ecosystems share `subtle`,
8//! so the constant-time machinery passes straight through.
9//!
10//! The ergonomic entry point is the [`SumOfProducts`] trait, implemented directly for any
11//! 0.13 group element:
12//!
13//! ```ignore
14//! use elliptic_curve_tools::legacy::SumOfProducts;
15//! // `point` is a group-0.13 type (e.g. curve25519_dalek::RistrettoPoint)
16//! let result = MyPoint::sum_of_products(&pairs); // pairs: &[(Scalar, MyPoint)]
17//! ```
18//!
19//! For the full API ([`Scratch`](crate::Scratch), [`Precomputed`](crate::Precomputed),
20//! the `_inplace`/`_iter` methods) wrap values in [`Group013`]/[`Scalar013`] yourself and
21//! use the regular [`SumOfProducts`](crate::SumOfProducts) trait.
22
23use core::iter::{Product, Sum};
24use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
25
26use elliptic_curve::{
27    Field, Group, PrimeField,
28    subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
29};
30use ff_013::PrimeField as PrimeField013;
31use group_013::Group as Group013Trait;
32use rand_core::TryRng;
33
34/// Wraps a `group` 0.13 element so it satisfies the `group` 0.14 [`Group`] trait.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub struct Group013<P>(
37    /// The wrapped 0.13 group element.
38    pub P,
39);
40
41/// Wraps an `ff` 0.13 element so it satisfies the `ff` 0.14 [`PrimeField`] trait.
42#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
43pub struct Scalar013<F>(
44    /// The wrapped 0.13 field element.
45    pub F,
46);
47
48// ----- scalar (Scalar013) operator delegation -----------------------------------
49
50macro_rules! scalar_binop {
51    ($trait:ident, $method:ident) => {
52        impl<F: PrimeField013> $trait for Scalar013<F> {
53            type Output = Self;
54            #[inline]
55            fn $method(self, rhs: Self) -> Self {
56                Scalar013($trait::$method(self.0, rhs.0))
57            }
58        }
59        impl<F: PrimeField013> $trait<&Scalar013<F>> for Scalar013<F> {
60            type Output = Self;
61            #[inline]
62            fn $method(self, rhs: &Scalar013<F>) -> Self {
63                Scalar013($trait::$method(self.0, rhs.0))
64            }
65        }
66    };
67}
68
69macro_rules! scalar_assign {
70    ($trait:ident, $method:ident) => {
71        impl<F: PrimeField013> $trait for Scalar013<F> {
72            #[inline]
73            fn $method(&mut self, rhs: Self) {
74                $trait::$method(&mut self.0, rhs.0)
75            }
76        }
77        impl<F: PrimeField013> $trait<&Scalar013<F>> for Scalar013<F> {
78            #[inline]
79            fn $method(&mut self, rhs: &Scalar013<F>) {
80                $trait::$method(&mut self.0, rhs.0)
81            }
82        }
83    };
84}
85
86scalar_binop!(Add, add);
87scalar_binop!(Sub, sub);
88scalar_binop!(Mul, mul);
89scalar_assign!(AddAssign, add_assign);
90scalar_assign!(SubAssign, sub_assign);
91scalar_assign!(MulAssign, mul_assign);
92
93impl<F: PrimeField013> Neg for Scalar013<F> {
94    type Output = Self;
95    #[inline]
96    fn neg(self) -> Self {
97        Scalar013(-self.0)
98    }
99}
100
101impl<F: PrimeField013> Sum for Scalar013<F> {
102    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
103        iter.fold(Scalar013(F::ZERO), Add::add)
104    }
105}
106
107impl<'a, F: PrimeField013> Sum<&'a Self> for Scalar013<F> {
108    fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
109        iter.fold(Scalar013(F::ZERO), |acc, x| acc + *x)
110    }
111}
112
113impl<F: PrimeField013> Product for Scalar013<F> {
114    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
115        iter.fold(Scalar013(F::ONE), Mul::mul)
116    }
117}
118
119impl<'a, F: PrimeField013> Product<&'a Self> for Scalar013<F> {
120    fn product<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
121        iter.fold(Scalar013(F::ONE), |acc, x| acc * *x)
122    }
123}
124
125impl<F: PrimeField013> ConstantTimeEq for Scalar013<F> {
126    fn ct_eq(&self, other: &Self) -> Choice {
127        self.0.ct_eq(&other.0)
128    }
129}
130
131impl<F: PrimeField013> ConditionallySelectable for Scalar013<F> {
132    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
133        Scalar013(F::conditional_select(&a.0, &b.0, choice))
134    }
135}
136
137impl<F: PrimeField013> From<u64> for Scalar013<F> {
138    fn from(value: u64) -> Self {
139        Scalar013(F::from(value))
140    }
141}
142
143impl<F: PrimeField013> Field for Scalar013<F> {
144    const ZERO: Self = Scalar013(F::ZERO);
145    const ONE: Self = Scalar013(F::ONE);
146
147    fn try_random<R: TryRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
148        // Rejection-sample a canonical representation; the 0.13 RNG API differs, so we
149        // sample bytes through the 0.14 RNG and reduce via the inner `from_repr`.
150        loop {
151            let mut repr = F::Repr::default();
152            let bytes: &mut [u8] = repr.as_mut();
153            rng.try_fill_bytes(bytes)?;
154            if let Some(value) = Option::<F>::from(F::from_repr(repr)) {
155                return Ok(Scalar013(value));
156            }
157        }
158    }
159
160    fn square(&self) -> Self {
161        Scalar013(self.0.square())
162    }
163
164    fn double(&self) -> Self {
165        Scalar013(self.0.double())
166    }
167
168    fn invert(&self) -> CtOption<Self> {
169        self.0.invert().map(Scalar013)
170    }
171
172    fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
173        let (choice, root) = F::sqrt_ratio(&num.0, &div.0);
174        (choice, Scalar013(root))
175    }
176}
177
178impl<F: PrimeField013> PrimeField for Scalar013<F> {
179    type Repr = F::Repr;
180
181    const MODULUS: &'static str = F::MODULUS;
182    const NUM_BITS: u32 = F::NUM_BITS;
183    const CAPACITY: u32 = F::CAPACITY;
184    const TWO_INV: Self = Scalar013(F::TWO_INV);
185    const MULTIPLICATIVE_GENERATOR: Self = Scalar013(F::MULTIPLICATIVE_GENERATOR);
186    const S: u32 = F::S;
187    const ROOT_OF_UNITY: Self = Scalar013(F::ROOT_OF_UNITY);
188    const ROOT_OF_UNITY_INV: Self = Scalar013(F::ROOT_OF_UNITY_INV);
189    const DELTA: Self = Scalar013(F::DELTA);
190
191    fn from_repr(repr: Self::Repr) -> CtOption<Self> {
192        F::from_repr(repr).map(Scalar013)
193    }
194
195    fn to_repr(&self) -> Self::Repr {
196        self.0.to_repr()
197    }
198
199    fn is_odd(&self) -> Choice {
200        self.0.is_odd()
201    }
202}
203
204// ----- group (Group013) operator delegation -------------------------------------
205
206macro_rules! group_binop {
207    ($trait:ident, $method:ident) => {
208        impl<P: Group013Trait + ConditionallySelectable> $trait for Group013<P> {
209            type Output = Self;
210            #[inline]
211            fn $method(self, rhs: Self) -> Self {
212                Group013($trait::$method(self.0, rhs.0))
213            }
214        }
215        impl<P: Group013Trait + ConditionallySelectable> $trait<&Group013<P>> for Group013<P> {
216            type Output = Self;
217            #[inline]
218            fn $method(self, rhs: &Group013<P>) -> Self {
219                Group013($trait::$method(self.0, rhs.0))
220            }
221        }
222    };
223}
224
225macro_rules! group_assign {
226    ($trait:ident, $method:ident) => {
227        impl<P: Group013Trait + ConditionallySelectable> $trait for Group013<P> {
228            #[inline]
229            fn $method(&mut self, rhs: Self) {
230                $trait::$method(&mut self.0, rhs.0)
231            }
232        }
233        impl<P: Group013Trait + ConditionallySelectable> $trait<&Group013<P>> for Group013<P> {
234            #[inline]
235            fn $method(&mut self, rhs: &Group013<P>) {
236                $trait::$method(&mut self.0, rhs.0)
237            }
238        }
239    };
240}
241
242group_binop!(Add, add);
243group_binop!(Sub, sub);
244group_assign!(AddAssign, add_assign);
245group_assign!(SubAssign, sub_assign);
246
247impl<P: Group013Trait + ConditionallySelectable> Neg for Group013<P> {
248    type Output = Self;
249    #[inline]
250    fn neg(self) -> Self {
251        Group013(-self.0)
252    }
253}
254
255impl<P: Group013Trait + ConditionallySelectable> Mul<Scalar013<P::Scalar>> for Group013<P> {
256    type Output = Self;
257    #[inline]
258    fn mul(self, rhs: Scalar013<P::Scalar>) -> Self {
259        Group013(self.0 * rhs.0)
260    }
261}
262
263impl<P: Group013Trait + ConditionallySelectable> Mul<&Scalar013<P::Scalar>> for Group013<P> {
264    type Output = Self;
265    #[inline]
266    fn mul(self, rhs: &Scalar013<P::Scalar>) -> Self {
267        Group013(self.0 * rhs.0)
268    }
269}
270
271impl<P: Group013Trait + ConditionallySelectable> MulAssign<Scalar013<P::Scalar>> for Group013<P> {
272    #[inline]
273    fn mul_assign(&mut self, rhs: Scalar013<P::Scalar>) {
274        self.0 *= rhs.0;
275    }
276}
277
278impl<P: Group013Trait + ConditionallySelectable> MulAssign<&Scalar013<P::Scalar>> for Group013<P> {
279    #[inline]
280    fn mul_assign(&mut self, rhs: &Scalar013<P::Scalar>) {
281        self.0 *= rhs.0;
282    }
283}
284
285impl<P: Group013Trait + ConditionallySelectable> Sum for Group013<P> {
286    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
287        iter.fold(Group013(P::identity()), Add::add)
288    }
289}
290
291impl<'a, P: Group013Trait + ConditionallySelectable> Sum<&'a Self> for Group013<P> {
292    fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
293        iter.fold(Group013(P::identity()), |acc, x| acc + *x)
294    }
295}
296
297impl<P: Group013Trait + ConditionallySelectable> ConditionallySelectable for Group013<P> {
298    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
299        Group013(P::conditional_select(&a.0, &b.0, choice))
300    }
301}
302
303impl<P: Group013Trait + ConditionallySelectable> Group for Group013<P> {
304    type Scalar = Scalar013<P::Scalar>;
305
306    fn try_random<R: TryRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
307        let scalar = <Scalar013<P::Scalar> as Field>::try_random(rng)?;
308        Ok(Group013(P::generator() * scalar.0))
309    }
310
311    fn identity() -> Self {
312        Group013(P::identity())
313    }
314
315    fn generator() -> Self {
316        Group013(P::generator())
317    }
318
319    fn is_identity(&self) -> Choice {
320        self.0.is_identity()
321    }
322
323    fn double(&self) -> Self {
324        Group013(self.0.double())
325    }
326}
327
328// ----- ergonomic entry point ----------------------------------------------------
329
330/// Reusable scratch for the in-place methods on a 0.13 group `P`.
331///
332/// An alias for [`crate::Scratch`] over the bridged group type. Allocate once with
333/// [`Scratch::new`](crate::Scratch::new) (e.g. `legacy::Scratch::<MyPoint>::new(n)`) and
334/// reuse it to avoid reallocating the multiexp tables across calls.
335#[cfg(any(feature = "alloc", feature = "std"))]
336pub type Scratch<P> = crate::Scratch<Group013<P>>;
337
338/// Sum of scalar products for curves still on the `group`/`ff` 0.13 traits.
339///
340/// Mirrors [`crate::SumOfProducts`] but is implemented directly for any 0.13 group
341/// element, wrapping into [`Group013`]/[`Scalar013`] internally. Requires `alloc`/`std`.
342#[cfg(any(feature = "alloc", feature = "std"))]
343pub trait SumOfProducts: Group013Trait + ConditionallySelectable {
344    /// Constant-time `sum(scalar_i * point_i)`. Use when any scalar may be secret.
345    fn sum_of_products(pairs: &[(<Self as Group013Trait>::Scalar, Self)]) -> Self;
346
347    /// Variable-time `sum(scalar_i * point_i)`. Use only when every scalar is public.
348    fn sum_of_products_vartime(pairs: &[(<Self as Group013Trait>::Scalar, Self)]) -> Self;
349
350    /// Constant-time [`sum_of_products`](SumOfProducts::sum_of_products) reusing a
351    /// caller-owned [`Scratch`], avoiding reallocating the multiexp tables each call.
352    ///
353    /// The bridge still wraps `pairs` into a temporary `Vec` per call; for a fully
354    /// streaming call use [`sum_of_products_iter`](SumOfProducts::sum_of_products_iter).
355    fn sum_of_products_inplace(
356        pairs: &[(<Self as Group013Trait>::Scalar, Self)],
357        scratch: &mut Scratch<Self>,
358    ) -> Result<Self, crate::InsufficientScratch>;
359
360    /// Variable-time [`sum_of_products_vartime`](SumOfProducts::sum_of_products_vartime)
361    /// reusing a caller-owned [`Scratch`].
362    fn sum_of_products_vartime_inplace(
363        pairs: &[(<Self as Group013Trait>::Scalar, Self)],
364        scratch: &mut Scratch<Self>,
365    ) -> Result<Self, crate::InsufficientScratch>;
366
367    /// Constant-time [`sum_of_products`](SumOfProducts::sum_of_products) reading pairs from
368    /// an iterator (for example a `zip`) without collecting them into a slice first.
369    fn sum_of_products_iter<I>(pairs: I) -> Self
370    where
371        I: IntoIterator<Item = (<Self as Group013Trait>::Scalar, Self)>,
372        I::IntoIter: ExactSizeIterator;
373}
374
375#[cfg(any(feature = "alloc", feature = "std"))]
376impl<P> SumOfProducts for P
377where
378    P: Group013Trait + ConditionallySelectable,
379    P::Scalar: PrimeField013,
380{
381    fn sum_of_products(pairs: &[(P::Scalar, Self)]) -> Self {
382        let wrapped = wrap(pairs);
383        crate::SumOfProducts::sum_of_products(wrapped.as_slice()).0
384    }
385
386    fn sum_of_products_vartime(pairs: &[(P::Scalar, Self)]) -> Self {
387        let wrapped = wrap(pairs);
388        crate::SumOfProducts::sum_of_products_vartime(wrapped.as_slice()).0
389    }
390
391    fn sum_of_products_inplace(
392        pairs: &[(P::Scalar, Self)],
393        scratch: &mut Scratch<Self>,
394    ) -> Result<Self, crate::InsufficientScratch> {
395        let wrapped = wrap(pairs);
396        crate::SumOfProducts::sum_of_products_inplace(wrapped.as_slice(), scratch).map(|g| g.0)
397    }
398
399    fn sum_of_products_vartime_inplace(
400        pairs: &[(P::Scalar, Self)],
401        scratch: &mut Scratch<Self>,
402    ) -> Result<Self, crate::InsufficientScratch> {
403        let wrapped = wrap(pairs);
404        crate::SumOfProducts::sum_of_products_vartime_inplace(wrapped.as_slice(), scratch)
405            .map(|g| g.0)
406    }
407
408    fn sum_of_products_iter<I>(pairs: I) -> Self
409    where
410        I: IntoIterator<Item = (P::Scalar, Self)>,
411        I::IntoIter: ExactSizeIterator,
412    {
413        let wrapped = pairs
414            .into_iter()
415            .map(|(scalar, point)| (Scalar013(scalar), Group013(point)));
416        crate::SumOfProducts::sum_of_products_iter(wrapped).0
417    }
418}
419
420#[cfg(all(feature = "alloc", not(feature = "std")))]
421use alloc::vec::Vec;
422#[cfg(feature = "std")]
423use std::vec::Vec;
424
425#[cfg(any(feature = "alloc", feature = "std"))]
426fn wrap<P>(pairs: &[(P::Scalar, P)]) -> Vec<(Scalar013<P::Scalar>, Group013<P>)>
427where
428    P: Group013Trait + ConditionallySelectable,
429    P::Scalar: PrimeField013,
430{
431    pairs
432        .iter()
433        .map(|(scalar, point)| (Scalar013(*scalar), Group013(*point)))
434        .collect()
435}