dalek_ff_group/
lib.rs

1#![allow(deprecated)]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![no_std] // Prevents writing new code, in what should be a simple wrapper, which requires std
4#![doc = include_str!("../README.md")]
5#![allow(clippy::redundant_closure_call)]
6
7use core::{
8  borrow::Borrow,
9  ops::{Deref, Add, AddAssign, Sub, SubAssign, Neg, Mul, MulAssign},
10  iter::{Iterator, Sum, Product},
11  hash::{Hash, Hasher},
12};
13
14use zeroize::Zeroize;
15use subtle::{ConstantTimeEq, ConditionallySelectable};
16
17use rand_core::RngCore;
18use digest::{consts::U64, Digest, HashMarker};
19
20use subtle::{Choice, CtOption};
21
22pub use curve25519_dalek as dalek;
23
24use dalek::{
25  constants::{self, BASEPOINT_ORDER},
26  scalar::Scalar as DScalar,
27  edwards::{EdwardsPoint as DEdwardsPoint, EdwardsBasepointTable, CompressedEdwardsY},
28  ristretto::{RistrettoPoint as DRistrettoPoint, RistrettoBasepointTable, CompressedRistretto},
29};
30pub use constants::{ED25519_BASEPOINT_TABLE, RISTRETTO_BASEPOINT_TABLE};
31
32use group::{
33  ff::{Field, PrimeField, FieldBits, PrimeFieldBits, FromUniformBytes},
34  Group, GroupEncoding,
35  prime::PrimeGroup,
36};
37
38mod field;
39pub use field::FieldElement;
40
41mod ciphersuite;
42pub use crate::ciphersuite::{Ed25519, Ristretto};
43
44// Use black_box when possible
45#[rustversion::since(1.66)]
46mod black_box {
47  pub(crate) fn black_box<T>(val: T) -> T {
48    #[allow(clippy::incompatible_msrv)]
49    core::hint::black_box(val)
50  }
51}
52#[rustversion::before(1.66)]
53mod black_box {
54  pub(crate) fn black_box<T>(val: T) -> T {
55    val
56  }
57}
58use black_box::black_box;
59
60fn u8_from_bool(bit_ref: &mut bool) -> u8 {
61  let bit_ref = black_box(bit_ref);
62
63  let mut bit = black_box(*bit_ref);
64  #[allow(clippy::cast_lossless)]
65  let res = black_box(bit as u8);
66  bit.zeroize();
67  debug_assert!((res | 1) == 1);
68
69  bit_ref.zeroize();
70  res
71}
72
73// Convert a boolean to a Choice in a *presumably* constant time manner
74fn choice(mut value: bool) -> Choice {
75  Choice::from(u8_from_bool(&mut value))
76}
77
78macro_rules! deref_borrow {
79  ($Source: ident, $Target: ident) => {
80    impl Deref for $Source {
81      type Target = $Target;
82
83      fn deref(&self) -> &Self::Target {
84        &self.0
85      }
86    }
87
88    impl Borrow<$Target> for $Source {
89      fn borrow(&self) -> &$Target {
90        &self.0
91      }
92    }
93
94    impl Borrow<$Target> for &$Source {
95      fn borrow(&self) -> &$Target {
96        &self.0
97      }
98    }
99  };
100}
101
102macro_rules! constant_time {
103  ($Value: ident, $Inner: ident) => {
104    impl ConstantTimeEq for $Value {
105      fn ct_eq(&self, other: &Self) -> Choice {
106        self.0.ct_eq(&other.0)
107      }
108    }
109
110    impl ConditionallySelectable for $Value {
111      fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
112        $Value($Inner::conditional_select(&a.0, &b.0, choice))
113      }
114    }
115  };
116}
117pub(crate) use constant_time;
118
119macro_rules! math_op {
120  (
121    $Value: ident,
122    $Other: ident,
123    $Op: ident,
124    $op_fn: ident,
125    $Assign: ident,
126    $assign_fn: ident,
127    $function: expr
128  ) => {
129    impl $Op<$Other> for $Value {
130      type Output = $Value;
131      fn $op_fn(self, other: $Other) -> Self::Output {
132        Self($function(self.0, other.0))
133      }
134    }
135    impl $Assign<$Other> for $Value {
136      fn $assign_fn(&mut self, other: $Other) {
137        self.0 = $function(self.0, other.0);
138      }
139    }
140    impl<'a> $Op<&'a $Other> for $Value {
141      type Output = $Value;
142      fn $op_fn(self, other: &'a $Other) -> Self::Output {
143        Self($function(self.0, other.0))
144      }
145    }
146    impl<'a> $Assign<&'a $Other> for $Value {
147      fn $assign_fn(&mut self, other: &'a $Other) {
148        self.0 = $function(self.0, other.0);
149      }
150    }
151  };
152}
153pub(crate) use math_op;
154
155macro_rules! math {
156  ($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
157    math_op!($Value, $Value, Add, add, AddAssign, add_assign, $add);
158    math_op!($Value, $Value, Sub, sub, SubAssign, sub_assign, $sub);
159    math_op!($Value, $Factor, Mul, mul, MulAssign, mul_assign, $mul);
160  };
161}
162pub(crate) use math;
163
164macro_rules! math_neg {
165  ($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
166    math!($Value, $Factor, $add, $sub, $mul);
167
168    impl Neg for $Value {
169      type Output = Self;
170      fn neg(self) -> Self::Output {
171        Self(-self.0)
172      }
173    }
174  };
175}
176
177/// Wrapper around the dalek Scalar type.
178#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
179pub struct Scalar(pub DScalar);
180deref_borrow!(Scalar, DScalar);
181constant_time!(Scalar, DScalar);
182math_neg!(Scalar, Scalar, DScalar::add, DScalar::sub, DScalar::mul);
183
184macro_rules! from_wrapper {
185  ($uint: ident) => {
186    impl From<$uint> for Scalar {
187      fn from(a: $uint) -> Scalar {
188        Scalar(DScalar::from(a))
189      }
190    }
191  };
192}
193
194from_wrapper!(u8);
195from_wrapper!(u16);
196from_wrapper!(u32);
197from_wrapper!(u64);
198from_wrapper!(u128);
199
200impl Scalar {
201  pub fn pow(&self, other: Scalar) -> Scalar {
202    let mut table = [Scalar::ONE; 16];
203    table[1] = *self;
204    for i in 2 .. 16 {
205      table[i] = table[i - 1] * self;
206    }
207
208    let mut res = Scalar::ONE;
209    let mut bits = 0;
210    for (i, mut bit) in other.to_le_bits().iter_mut().rev().enumerate() {
211      bits <<= 1;
212      let mut bit = u8_from_bool(&mut bit);
213      bits |= bit;
214      bit.zeroize();
215
216      if ((i + 1) % 4) == 0 {
217        if i != 3 {
218          for _ in 0 .. 4 {
219            res *= res;
220          }
221        }
222
223        let mut scale_by = Scalar::ONE;
224        #[allow(clippy::needless_range_loop)]
225        for i in 0 .. 16 {
226          #[allow(clippy::cast_possible_truncation)] // Safe since 0 .. 16
227          {
228            scale_by = <_>::conditional_select(&scale_by, &table[i], bits.ct_eq(&(i as u8)));
229          }
230        }
231        res *= scale_by;
232        bits = 0;
233      }
234    }
235    res
236  }
237
238  /// Perform wide reduction on a 64-byte array to create a Scalar without bias.
239  pub fn from_bytes_mod_order_wide(bytes: &[u8; 64]) -> Scalar {
240    Self(DScalar::from_bytes_mod_order_wide(bytes))
241  }
242
243  /// Derive a Scalar without bias from a digest via wide reduction.
244  pub fn from_hash<D: Digest<OutputSize = U64> + HashMarker>(hash: D) -> Scalar {
245    let mut output = [0u8; 64];
246    output.copy_from_slice(&hash.finalize());
247    let res = Scalar(DScalar::from_bytes_mod_order_wide(&output));
248    output.zeroize();
249    res
250  }
251}
252
253impl Field for Scalar {
254  const ZERO: Scalar = Scalar(DScalar::ZERO);
255  const ONE: Scalar = Scalar(DScalar::ONE);
256
257  fn random(rng: impl RngCore) -> Self {
258    Self(<DScalar as Field>::random(rng))
259  }
260
261  fn square(&self) -> Self {
262    Self(self.0.square())
263  }
264  fn double(&self) -> Self {
265    Self(self.0.double())
266  }
267  fn invert(&self) -> CtOption<Self> {
268    <DScalar as Field>::invert(&self.0).map(Self)
269  }
270
271  fn sqrt(&self) -> CtOption<Self> {
272    self.0.sqrt().map(Self)
273  }
274
275  fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
276    let (choice, res) = DScalar::sqrt_ratio(num, div);
277    (choice, Self(res))
278  }
279}
280
281impl PrimeField for Scalar {
282  type Repr = [u8; 32];
283
284  const MODULUS: &'static str = <DScalar as PrimeField>::MODULUS;
285
286  const NUM_BITS: u32 = <DScalar as PrimeField>::NUM_BITS;
287  const CAPACITY: u32 = <DScalar as PrimeField>::CAPACITY;
288
289  const TWO_INV: Scalar = Scalar(<DScalar as PrimeField>::TWO_INV);
290
291  const MULTIPLICATIVE_GENERATOR: Scalar =
292    Scalar(<DScalar as PrimeField>::MULTIPLICATIVE_GENERATOR);
293  const S: u32 = <DScalar as PrimeField>::S;
294
295  const ROOT_OF_UNITY: Scalar = Scalar(<DScalar as PrimeField>::ROOT_OF_UNITY);
296  const ROOT_OF_UNITY_INV: Scalar = Scalar(<DScalar as PrimeField>::ROOT_OF_UNITY_INV);
297
298  const DELTA: Scalar = Scalar(<DScalar as PrimeField>::DELTA);
299
300  fn from_repr(bytes: [u8; 32]) -> CtOption<Self> {
301    <DScalar as PrimeField>::from_repr(bytes).map(Scalar)
302  }
303  fn to_repr(&self) -> [u8; 32] {
304    self.0.to_repr()
305  }
306
307  fn is_odd(&self) -> Choice {
308    self.0.is_odd()
309  }
310
311  fn from_u128(num: u128) -> Self {
312    Scalar(DScalar::from_u128(num))
313  }
314}
315
316impl PrimeFieldBits for Scalar {
317  type ReprBits = [u8; 32];
318
319  fn to_le_bits(&self) -> FieldBits<Self::ReprBits> {
320    self.to_repr().into()
321  }
322
323  fn char_le_bits() -> FieldBits<Self::ReprBits> {
324    BASEPOINT_ORDER.to_bytes().into()
325  }
326}
327
328impl FromUniformBytes<64> for Scalar {
329  fn from_uniform_bytes(bytes: &[u8; 64]) -> Self {
330    Self::from_bytes_mod_order_wide(bytes)
331  }
332}
333
334impl Sum<Scalar> for Scalar {
335  fn sum<I: Iterator<Item = Scalar>>(iter: I) -> Scalar {
336    Self(DScalar::sum(iter))
337  }
338}
339
340impl<'a> Sum<&'a Scalar> for Scalar {
341  fn sum<I: Iterator<Item = &'a Scalar>>(iter: I) -> Scalar {
342    Self(DScalar::sum(iter))
343  }
344}
345
346impl Product<Scalar> for Scalar {
347  fn product<I: Iterator<Item = Scalar>>(iter: I) -> Scalar {
348    Self(DScalar::product(iter))
349  }
350}
351
352impl<'a> Product<&'a Scalar> for Scalar {
353  fn product<I: Iterator<Item = &'a Scalar>>(iter: I) -> Scalar {
354    Self(DScalar::product(iter))
355  }
356}
357
358macro_rules! dalek_group {
359  (
360    $Point: ident,
361    $DPoint: ident,
362    $torsion_free: expr,
363
364    $Table: ident,
365
366    $DCompressed: ident,
367
368    $BASEPOINT_POINT: ident,
369    $BASEPOINT_TABLE: ident
370  ) => {
371    /// Wrapper around the dalek Point type.
372    ///
373    /// All operations will be restricted to a prime-order subgroup (equivalent to the group itself
374    /// in the case of Ristretto). The exposure of the internal element does allow bypassing this
375    /// however, which may lead to undefined/computationally-unsafe behavior, and is entirely at
376    /// the user's risk.
377    #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
378    pub struct $Point(pub $DPoint);
379    deref_borrow!($Point, $DPoint);
380    constant_time!($Point, $DPoint);
381    math_neg!($Point, Scalar, $DPoint::add, $DPoint::sub, $DPoint::mul);
382
383    /// The basepoint for this curve.
384    pub const $BASEPOINT_POINT: $Point = $Point(constants::$BASEPOINT_POINT);
385
386    impl Sum<$Point> for $Point {
387      fn sum<I: Iterator<Item = $Point>>(iter: I) -> $Point {
388        Self($DPoint::sum(iter))
389      }
390    }
391    impl<'a> Sum<&'a $Point> for $Point {
392      fn sum<I: Iterator<Item = &'a $Point>>(iter: I) -> $Point {
393        Self($DPoint::sum(iter))
394      }
395    }
396
397    impl Group for $Point {
398      type Scalar = Scalar;
399      fn random(mut rng: impl RngCore) -> Self {
400        loop {
401          let mut bytes = [0; 32];
402          rng.fill_bytes(&mut bytes);
403          let Some(point) = Option::<$Point>::from($Point::from_bytes(&bytes)) else {
404            continue;
405          };
406          // Ban identity, per the trait specification
407          if !bool::from(point.is_identity()) {
408            return point;
409          }
410        }
411      }
412      fn identity() -> Self {
413        Self($DPoint::identity())
414      }
415      fn generator() -> Self {
416        $BASEPOINT_POINT
417      }
418      fn is_identity(&self) -> Choice {
419        self.0.ct_eq(&$DPoint::identity())
420      }
421      fn double(&self) -> Self {
422        Self(self.0.double())
423      }
424    }
425
426    impl GroupEncoding for $Point {
427      type Repr = [u8; 32];
428
429      fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
430        let decompressed = $DCompressed(*bytes).decompress();
431        // TODO: Same note on unwrap_or as above
432        let point = decompressed.unwrap_or($DPoint::identity());
433        CtOption::new(
434          $Point(point),
435          choice(black_box(decompressed).is_some()) & choice($torsion_free(point)),
436        )
437      }
438
439      fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
440        $Point::from_bytes(bytes)
441      }
442
443      fn to_bytes(&self) -> Self::Repr {
444        self.0.to_bytes()
445      }
446    }
447
448    impl PrimeGroup for $Point {}
449
450    impl Mul<Scalar> for &$Table {
451      type Output = $Point;
452      fn mul(self, b: Scalar) -> $Point {
453        $Point(&b.0 * self)
454      }
455    }
456
457    // Support being used as a key in a table
458    // While it is expensive as a key, due to the field operations required, there's frequently
459    // use cases for public key -> value lookups
460    #[allow(unknown_lints, renamed_and_removed_lints)]
461    #[allow(clippy::derived_hash_with_manual_eq, clippy::derive_hash_xor_eq)]
462    impl Hash for $Point {
463      fn hash<H: Hasher>(&self, state: &mut H) {
464        self.to_bytes().hash(state);
465      }
466    }
467  };
468}
469
470dalek_group!(
471  EdwardsPoint,
472  DEdwardsPoint,
473  |point: DEdwardsPoint| point.is_torsion_free(),
474  EdwardsBasepointTable,
475  CompressedEdwardsY,
476  ED25519_BASEPOINT_POINT,
477  ED25519_BASEPOINT_TABLE
478);
479
480impl EdwardsPoint {
481  pub fn mul_by_cofactor(&self) -> EdwardsPoint {
482    EdwardsPoint(self.0.mul_by_cofactor())
483  }
484}
485
486dalek_group!(
487  RistrettoPoint,
488  DRistrettoPoint,
489  |_| true,
490  RistrettoBasepointTable,
491  CompressedRistretto,
492  RISTRETTO_BASEPOINT_POINT,
493  RISTRETTO_BASEPOINT_TABLE
494);
495
496#[test]
497fn test_ed25519_group() {
498  ff_group_tests::group::test_prime_group_bits::<_, EdwardsPoint>(&mut rand_core::OsRng);
499}
500
501#[test]
502fn test_ristretto_group() {
503  ff_group_tests::group::test_prime_group_bits::<_, RistrettoPoint>(&mut rand_core::OsRng);
504}