curve: implement ff and group traits (#562)

Originally authored by @str4d as #473
This commit is contained in:
pinkforest(she/her) 2023-08-28 06:41:06 +10:00 committed by GitHub
parent 098658dc8b
commit 4373695c50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 662 additions and 22 deletions

View file

@ -5,6 +5,10 @@ major series.
## 4.x series ## 4.x series
### Unreleased
* Add implementations of the `ff` and `group` traits, behind the `group` feature flag.
### 4.0.0 ### 4.0.0
#### Breaking changes #### Breaking changes

View file

@ -48,6 +48,7 @@ required-features = ["alloc", "rand_core"]
[dependencies] [dependencies]
cfg-if = "1" cfg-if = "1"
group = { version = "0.13", default-features = false, optional = true }
rand_core = { version = "0.6.4", default-features = false, optional = true } rand_core = { version = "0.6.4", default-features = false, optional = true }
digest = { version = "0.10", default-features = false, optional = true } digest = { version = "0.10", default-features = false, optional = true }
subtle = { version = "2.3.0", default-features = false } subtle = { version = "2.3.0", default-features = false }
@ -65,6 +66,7 @@ default = ["alloc", "precomputed-tables", "zeroize"]
alloc = ["zeroize?/alloc"] alloc = ["zeroize?/alloc"]
precomputed-tables = [] precomputed-tables = []
legacy_compatibility = [] legacy_compatibility = []
group = ["dep:group", "rand_core"]
[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies] [target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies]
curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" } curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" }

View file

@ -49,6 +49,7 @@ curve25519-dalek = "4"
| `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. | | `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. |
| `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. | | `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. |
| `legacy_compatibility`| | Enables `Scalar::from_bits`, which allows the user to build unreduced scalars whose arithmetic is broken. Do not use this unless you know what you're doing. | | `legacy_compatibility`| | Enables `Scalar::from_bits`, which allows the user to build unreduced scalars whose arithmetic is broken. Do not use this unless you know what you're doing. |
| `group` | | Enables external `group` and `ff` crate traits |
To disable the default features when using `curve25519-dalek` as a dependency, To disable the default features when using `curve25519-dalek` as a dependency,
add `default-features = false` to the dependency in your `Cargo.toml`. To add `default-features = false` to the dependency in your `Cargo.toml`. To
@ -190,9 +191,9 @@ From 4.x and on, MSRV changes will be accompanied by a minor version bump.
Breaking changes to SemVer exempted components affecting the public API will be accompanied by Breaking changes to SemVer exempted components affecting the public API will be accompanied by
_some_ version bump. Below are the specific policies: _some_ version bump. Below are the specific policies:
| Releases | Public API Component(s) | Policy | | Releases | Public API Component(s) | Policy |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| 4.x | Dependencies `digest` and `rand_core` | Minor SemVer bump | | 4.x | Dependencies `group`, `digest` and `rand_core` | Minor SemVer bump |
# Safety # Safety

View file

@ -107,6 +107,13 @@ use cfg_if::cfg_if;
#[cfg(feature = "digest")] #[cfg(feature = "digest")]
use digest::{generic_array::typenum::U64, Digest}; use digest::{generic_array::typenum::U64, Digest};
#[cfg(feature = "group")]
use {
group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding},
rand_core::RngCore,
subtle::CtOption,
};
use subtle::Choice; use subtle::Choice;
use subtle::ConditionallyNegatable; use subtle::ConditionallyNegatable;
use subtle::ConditionallySelectable; use subtle::ConditionallySelectable;
@ -183,25 +190,52 @@ impl CompressedEdwardsY {
/// ///
/// Returns `None` if the input is not the \\(y\\)-coordinate of a /// Returns `None` if the input is not the \\(y\\)-coordinate of a
/// curve point. /// curve point.
#[rustfmt::skip] // keep alignment of explanatory comments
pub fn decompress(&self) -> Option<EdwardsPoint> { pub fn decompress(&self) -> Option<EdwardsPoint> {
let Y = FieldElement::from_bytes(self.as_bytes()); let (is_valid_y_coord, X, Y, Z) = decompress::step_1(self);
if is_valid_y_coord.into() {
Some(decompress::step_2(self, X, Y, Z))
} else {
None
}
}
}
mod decompress {
use super::*;
#[rustfmt::skip] // keep alignment of explanatory comments
pub(super) fn step_1(
repr: &CompressedEdwardsY,
) -> (Choice, FieldElement, FieldElement, FieldElement) {
let Y = FieldElement::from_bytes(repr.as_bytes());
let Z = FieldElement::ONE; let Z = FieldElement::ONE;
let YY = Y.square(); let YY = Y.square();
let u = &YY - &Z; // u = y²-1 let u = &YY - &Z; // u = y²-1
let v = &(&YY * &constants::EDWARDS_D) + &Z; // v = dy²+1 let v = &(&YY * &constants::EDWARDS_D) + &Z; // v = dy²+1
let (is_valid_y_coord, mut X) = FieldElement::sqrt_ratio_i(&u, &v); let (is_valid_y_coord, X) = FieldElement::sqrt_ratio_i(&u, &v);
if (!is_valid_y_coord).into() { (is_valid_y_coord, X, Y, Z)
return None; }
}
#[rustfmt::skip]
pub(super) fn step_2(
repr: &CompressedEdwardsY,
mut X: FieldElement,
Y: FieldElement,
Z: FieldElement,
) -> EdwardsPoint {
// FieldElement::sqrt_ratio_i always returns the nonnegative square root, // FieldElement::sqrt_ratio_i always returns the nonnegative square root,
// so we negate according to the supplied sign bit. // so we negate according to the supplied sign bit.
let compressed_sign_bit = Choice::from(self.as_bytes()[31] >> 7); let compressed_sign_bit = Choice::from(repr.as_bytes()[31] >> 7);
X.conditional_negate(compressed_sign_bit); X.conditional_negate(compressed_sign_bit);
Some(EdwardsPoint{ X, Y, Z, T: &X * &Y }) EdwardsPoint {
X,
Y,
Z,
T: &X * &Y,
}
} }
} }
@ -1238,6 +1272,318 @@ impl Debug for EdwardsPoint {
} }
} }
// ------------------------------------------------------------------------
// group traits
// ------------------------------------------------------------------------
// Use the full trait path to avoid Group::identity overlapping Identity::identity in the
// rest of the module (e.g. tests).
#[cfg(feature = "group")]
impl group::Group for EdwardsPoint {
type Scalar = Scalar;
fn random(mut rng: impl RngCore) -> Self {
let mut repr = CompressedEdwardsY([0u8; 32]);
loop {
rng.fill_bytes(&mut repr.0);
if let Some(p) = repr.decompress() {
if !IsIdentity::is_identity(&p) {
break p;
}
}
}
}
fn identity() -> Self {
Identity::identity()
}
fn generator() -> Self {
constants::ED25519_BASEPOINT_POINT
}
fn is_identity(&self) -> Choice {
self.ct_eq(&Identity::identity())
}
fn double(&self) -> Self {
self.double()
}
}
#[cfg(feature = "group")]
impl GroupEncoding for EdwardsPoint {
type Repr = [u8; 32];
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
let repr = CompressedEdwardsY(*bytes);
let (is_valid_y_coord, X, Y, Z) = decompress::step_1(&repr);
CtOption::new(decompress::step_2(&repr, X, Y, Z), is_valid_y_coord)
}
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
// Just use the checked API; there are no checks we can skip.
Self::from_bytes(bytes)
}
fn to_bytes(&self) -> Self::Repr {
self.compress().to_bytes()
}
}
/// A `SubgroupPoint` represents a point on the Edwards form of Curve25519, that is
/// guaranteed to be in the prime-order subgroup.
#[cfg(feature = "group")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SubgroupPoint(EdwardsPoint);
#[cfg(feature = "group")]
impl From<SubgroupPoint> for EdwardsPoint {
fn from(p: SubgroupPoint) -> Self {
p.0
}
}
#[cfg(feature = "group")]
impl Neg for SubgroupPoint {
type Output = Self;
fn neg(self) -> Self::Output {
SubgroupPoint(-self.0)
}
}
#[cfg(feature = "group")]
impl Add<&SubgroupPoint> for &SubgroupPoint {
type Output = SubgroupPoint;
fn add(self, other: &SubgroupPoint) -> SubgroupPoint {
SubgroupPoint(self.0 + other.0)
}
}
#[cfg(feature = "group")]
define_add_variants!(
LHS = SubgroupPoint,
RHS = SubgroupPoint,
Output = SubgroupPoint
);
#[cfg(feature = "group")]
impl Add<&SubgroupPoint> for &EdwardsPoint {
type Output = EdwardsPoint;
fn add(self, other: &SubgroupPoint) -> EdwardsPoint {
self + other.0
}
}
#[cfg(feature = "group")]
define_add_variants!(
LHS = EdwardsPoint,
RHS = SubgroupPoint,
Output = EdwardsPoint
);
#[cfg(feature = "group")]
impl AddAssign<&SubgroupPoint> for SubgroupPoint {
fn add_assign(&mut self, rhs: &SubgroupPoint) {
self.0 += rhs.0
}
}
#[cfg(feature = "group")]
define_add_assign_variants!(LHS = SubgroupPoint, RHS = SubgroupPoint);
#[cfg(feature = "group")]
impl AddAssign<&SubgroupPoint> for EdwardsPoint {
fn add_assign(&mut self, rhs: &SubgroupPoint) {
*self += rhs.0
}
}
#[cfg(feature = "group")]
define_add_assign_variants!(LHS = EdwardsPoint, RHS = SubgroupPoint);
#[cfg(feature = "group")]
impl Sub<&SubgroupPoint> for &SubgroupPoint {
type Output = SubgroupPoint;
fn sub(self, other: &SubgroupPoint) -> SubgroupPoint {
SubgroupPoint(self.0 - other.0)
}
}
#[cfg(feature = "group")]
define_sub_variants!(
LHS = SubgroupPoint,
RHS = SubgroupPoint,
Output = SubgroupPoint
);
#[cfg(feature = "group")]
impl Sub<&SubgroupPoint> for &EdwardsPoint {
type Output = EdwardsPoint;
fn sub(self, other: &SubgroupPoint) -> EdwardsPoint {
self - other.0
}
}
#[cfg(feature = "group")]
define_sub_variants!(
LHS = EdwardsPoint,
RHS = SubgroupPoint,
Output = EdwardsPoint
);
#[cfg(feature = "group")]
impl SubAssign<&SubgroupPoint> for SubgroupPoint {
fn sub_assign(&mut self, rhs: &SubgroupPoint) {
self.0 -= rhs.0;
}
}
#[cfg(feature = "group")]
define_sub_assign_variants!(LHS = SubgroupPoint, RHS = SubgroupPoint);
#[cfg(feature = "group")]
impl SubAssign<&SubgroupPoint> for EdwardsPoint {
fn sub_assign(&mut self, rhs: &SubgroupPoint) {
*self -= rhs.0;
}
}
#[cfg(feature = "group")]
define_sub_assign_variants!(LHS = EdwardsPoint, RHS = SubgroupPoint);
#[cfg(feature = "group")]
impl<T> Sum<T> for SubgroupPoint
where
T: Borrow<SubgroupPoint>,
{
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = T>,
{
use group::Group;
iter.fold(SubgroupPoint::identity(), |acc, item| acc + item.borrow())
}
}
#[cfg(feature = "group")]
impl Mul<&Scalar> for &SubgroupPoint {
type Output = SubgroupPoint;
/// Scalar multiplication: compute `scalar * self`.
///
/// For scalar multiplication of a basepoint,
/// `EdwardsBasepointTable` is approximately 4x faster.
fn mul(self, scalar: &Scalar) -> SubgroupPoint {
SubgroupPoint(self.0 * scalar)
}
}
#[cfg(feature = "group")]
define_mul_variants!(LHS = Scalar, RHS = SubgroupPoint, Output = SubgroupPoint);
#[cfg(feature = "group")]
impl Mul<&SubgroupPoint> for &Scalar {
type Output = SubgroupPoint;
/// Scalar multiplication: compute `scalar * self`.
///
/// For scalar multiplication of a basepoint,
/// `EdwardsBasepointTable` is approximately 4x faster.
fn mul(self, point: &SubgroupPoint) -> SubgroupPoint {
point * self
}
}
#[cfg(feature = "group")]
define_mul_variants!(LHS = SubgroupPoint, RHS = Scalar, Output = SubgroupPoint);
#[cfg(feature = "group")]
impl MulAssign<&Scalar> for SubgroupPoint {
fn mul_assign(&mut self, scalar: &Scalar) {
self.0 *= scalar;
}
}
#[cfg(feature = "group")]
define_mul_assign_variants!(LHS = SubgroupPoint, RHS = Scalar);
#[cfg(feature = "group")]
impl group::Group for SubgroupPoint {
type Scalar = Scalar;
fn random(mut rng: impl RngCore) -> Self {
use group::ff::Field;
// This will almost never loop, but `Group::random` is documented as returning a
// non-identity element.
let s = loop {
let s: Scalar = Field::random(&mut rng);
if !s.is_zero_vartime() {
break s;
}
};
// This gives an element of the prime-order subgroup.
Self::generator() * s
}
fn identity() -> Self {
SubgroupPoint(Identity::identity())
}
fn generator() -> Self {
SubgroupPoint(EdwardsPoint::generator())
}
fn is_identity(&self) -> Choice {
self.0.ct_eq(&Identity::identity())
}
fn double(&self) -> Self {
SubgroupPoint(self.0.double())
}
}
#[cfg(feature = "group")]
impl GroupEncoding for SubgroupPoint {
type Repr = <EdwardsPoint as GroupEncoding>::Repr;
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
EdwardsPoint::from_bytes(bytes).and_then(|p| p.into_subgroup())
}
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
EdwardsPoint::from_bytes_unchecked(bytes).and_then(|p| p.into_subgroup())
}
fn to_bytes(&self) -> Self::Repr {
self.0.compress().to_bytes()
}
}
#[cfg(feature = "group")]
impl PrimeGroup for SubgroupPoint {}
/// Ristretto has a cofactor of 1.
#[cfg(feature = "group")]
impl CofactorGroup for EdwardsPoint {
type Subgroup = SubgroupPoint;
fn clear_cofactor(&self) -> Self::Subgroup {
SubgroupPoint(self.mul_by_cofactor())
}
fn into_subgroup(self) -> CtOption<Self::Subgroup> {
CtOption::new(SubgroupPoint(self), CofactorGroup::is_torsion_free(&self))
}
fn is_torsion_free(&self) -> Choice {
(self * constants::BASEPOINT_ORDER).ct_eq(&Self::identity())
}
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Tests // Tests
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View file

@ -44,6 +44,9 @@ extern crate std;
#[cfg(feature = "digest")] #[cfg(feature = "digest")]
pub use digest; pub use digest;
#[cfg(feature = "group")]
extern crate group;
// Internal macros. Must come first! // Internal macros. Must come first!
#[macro_use] #[macro_use]
pub(crate) mod macros; pub(crate) mod macros;

View file

@ -180,6 +180,13 @@ use digest::Digest;
use crate::constants; use crate::constants;
use crate::field::FieldElement; use crate::field::FieldElement;
#[cfg(feature = "group")]
use {
group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding},
rand_core::RngCore,
subtle::CtOption,
};
use subtle::Choice; use subtle::Choice;
use subtle::ConditionallyNegatable; use subtle::ConditionallyNegatable;
use subtle::ConditionallySelectable; use subtle::ConditionallySelectable;
@ -246,6 +253,26 @@ impl CompressedRistretto {
/// ///
/// - `None` if `self` was not the canonical encoding of a point. /// - `None` if `self` was not the canonical encoding of a point.
pub fn decompress(&self) -> Option<RistrettoPoint> { pub fn decompress(&self) -> Option<RistrettoPoint> {
let (s_encoding_is_canonical, s_is_negative, s) = decompress::step_1(self);
if (!s_encoding_is_canonical | s_is_negative).into() {
return None;
}
let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s);
if (!ok | t_is_negative | y_is_zero).into() {
None
} else {
Some(res)
}
}
}
mod decompress {
use super::*;
pub(super) fn step_1(repr: &CompressedRistretto) -> (Choice, Choice, FieldElement) {
// Step 1. Check s for validity: // Step 1. Check s for validity:
// 1.a) s must be 32 bytes (we get this from the type system) // 1.a) s must be 32 bytes (we get this from the type system)
// 1.b) s < p // 1.b) s < p
@ -257,15 +284,15 @@ impl CompressedRistretto {
// converting back to bytes, and checking that we get the // converting back to bytes, and checking that we get the
// original input, since our encoding routine is canonical. // original input, since our encoding routine is canonical.
let s = FieldElement::from_bytes(self.as_bytes()); let s = FieldElement::from_bytes(repr.as_bytes());
let s_bytes_check = s.as_bytes(); let s_bytes_check = s.as_bytes();
let s_encoding_is_canonical = s_bytes_check[..].ct_eq(self.as_bytes()); let s_encoding_is_canonical = s_bytes_check[..].ct_eq(repr.as_bytes());
let s_is_negative = s.is_negative(); let s_is_negative = s.is_negative();
if (!s_encoding_is_canonical | s_is_negative).into() { (s_encoding_is_canonical, s_is_negative, s)
return None; }
}
pub(super) fn step_2(s: FieldElement) -> (Choice, Choice, Choice, RistrettoPoint) {
// Step 2. Compute (X:Y:Z:T). // Step 2. Compute (X:Y:Z:T).
let one = FieldElement::ONE; let one = FieldElement::ONE;
let ss = s.square(); let ss = s.square();
@ -292,16 +319,17 @@ impl CompressedRistretto {
// t == ((1+as²) sqrt(4s²/(ad(1+as²)² - (1-as²)²)))/(1-as²) // t == ((1+as²) sqrt(4s²/(ad(1+as²)² - (1-as²)²)))/(1-as²)
let t = &x * &y; let t = &x * &y;
if (!ok | t.is_negative() | y.is_zero()).into() { (
None ok,
} else { t.is_negative(),
Some(RistrettoPoint(EdwardsPoint { y.is_zero(),
RistrettoPoint(EdwardsPoint {
X: x, X: x,
Y: y, Y: y,
Z: one, Z: one,
T: t, T: t,
})) }),
} )
} }
} }
@ -1143,6 +1171,86 @@ impl Debug for RistrettoPoint {
} }
} }
// ------------------------------------------------------------------------
// group traits
// ------------------------------------------------------------------------
// Use the full trait path to avoid Group::identity overlapping Identity::identity in the
// rest of the module (e.g. tests).
#[cfg(feature = "group")]
impl group::Group for RistrettoPoint {
type Scalar = Scalar;
fn random(mut rng: impl RngCore) -> Self {
// NOTE: this is duplicated due to different `rng` bounds
let mut uniform_bytes = [0u8; 64];
rng.fill_bytes(&mut uniform_bytes);
RistrettoPoint::from_uniform_bytes(&uniform_bytes)
}
fn identity() -> Self {
Identity::identity()
}
fn generator() -> Self {
constants::RISTRETTO_BASEPOINT_POINT
}
fn is_identity(&self) -> Choice {
self.ct_eq(&Identity::identity())
}
fn double(&self) -> Self {
self + self
}
}
#[cfg(feature = "group")]
impl GroupEncoding for RistrettoPoint {
type Repr = [u8; 32];
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
let (s_encoding_is_canonical, s_is_negative, s) =
decompress::step_1(&CompressedRistretto(*bytes));
let s_is_valid = s_encoding_is_canonical & !s_is_negative;
let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s);
CtOption::new(res, s_is_valid & ok & !t_is_negative & !y_is_zero)
}
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
// Just use the checked API; the checks we could skip aren't expensive.
Self::from_bytes(bytes)
}
fn to_bytes(&self) -> Self::Repr {
self.compress().to_bytes()
}
}
#[cfg(feature = "group")]
impl PrimeGroup for RistrettoPoint {}
/// Ristretto has a cofactor of 1.
#[cfg(feature = "group")]
impl CofactorGroup for RistrettoPoint {
type Subgroup = Self;
fn clear_cofactor(&self) -> Self::Subgroup {
*self
}
fn into_subgroup(self) -> CtOption<Self::Subgroup> {
CtOption::new(self, Choice::from(1))
}
fn is_torsion_free(&self) -> Choice {
Choice::from(1)
}
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Zeroize traits // Zeroize traits
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View file

@ -124,6 +124,12 @@ use core::ops::{Sub, SubAssign};
use cfg_if::cfg_if; use cfg_if::cfg_if;
#[cfg(feature = "group")]
use {
group::ff::{Field, FromUniformBytes, PrimeField},
rand_core::RngCore,
};
#[cfg(any(test, feature = "rand_core"))] #[cfg(any(test, feature = "rand_core"))]
use rand_core::CryptoRngCore; use rand_core::CryptoRngCore;
@ -1202,6 +1208,126 @@ impl UnpackedScalar {
} }
} }
#[cfg(feature = "group")]
impl Field for Scalar {
const ZERO: Self = Self::ZERO;
const ONE: Self = Self::ONE;
fn random(mut rng: impl RngCore) -> Self {
// NOTE: this is duplicated due to different `rng` bounds
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Self::from_bytes_mod_order_wide(&scalar_bytes)
}
fn square(&self) -> Self {
self * self
}
fn double(&self) -> Self {
self + self
}
fn invert(&self) -> CtOption<Self> {
CtOption::new(self.invert(), !self.is_zero())
}
fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
group::ff::helpers::sqrt_ratio_generic(num, div)
}
fn sqrt(&self) -> CtOption<Self> {
group::ff::helpers::sqrt_tonelli_shanks(
self,
[
0xcb02_4c63_4b9e_ba7d,
0x029b_df3b_d45e_f39a,
0x0000_0000_0000_0000,
0x0200_0000_0000_0000,
],
)
}
}
#[cfg(feature = "group")]
impl PrimeField for Scalar {
type Repr = [u8; 32];
fn from_repr(repr: Self::Repr) -> CtOption<Self> {
Self::from_canonical_bytes(repr)
}
fn from_repr_vartime(repr: Self::Repr) -> Option<Self> {
// Check that the high bit is not set
if (repr[31] >> 7) != 0u8 {
return None;
}
let candidate = Scalar { bytes: repr };
if candidate == candidate.reduce() {
Some(candidate)
} else {
None
}
}
fn to_repr(&self) -> Self::Repr {
self.to_bytes()
}
fn is_odd(&self) -> Choice {
Choice::from(self.as_bytes()[0] & 1)
}
const MODULUS: &'static str =
"0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed";
const NUM_BITS: u32 = 253;
const CAPACITY: u32 = 252;
const TWO_INV: Self = Self {
bytes: [
0xf7, 0xe9, 0x7a, 0x2e, 0x8d, 0x31, 0x09, 0x2c, 0x6b, 0xce, 0x7b, 0x51, 0xef, 0x7c,
0x6f, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08,
],
};
const MULTIPLICATIVE_GENERATOR: Self = Self {
bytes: [
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
],
};
const S: u32 = 2;
const ROOT_OF_UNITY: Self = Self {
bytes: [
0xd4, 0x07, 0xbe, 0xeb, 0xdf, 0x75, 0x87, 0xbe, 0xfe, 0x83, 0xce, 0x42, 0x53, 0x56,
0xf0, 0x0e, 0x7a, 0xc2, 0xc1, 0xab, 0x60, 0x6d, 0x3d, 0x7d, 0xe7, 0x81, 0x79, 0xe0,
0x10, 0x73, 0x4a, 0x09,
],
};
const ROOT_OF_UNITY_INV: Self = Self {
bytes: [
0x19, 0xcc, 0x37, 0x71, 0x3a, 0xed, 0x8a, 0x99, 0xd7, 0x18, 0x29, 0x60, 0x8b, 0xa3,
0xee, 0x05, 0x86, 0x3d, 0x3e, 0x54, 0x9f, 0x92, 0xc2, 0x82, 0x18, 0x7e, 0x86, 0x1f,
0xef, 0x8c, 0xb5, 0x06,
],
};
const DELTA: Self = Self {
bytes: [
16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
],
};
}
#[cfg(feature = "group")]
impl FromUniformBytes<64> for Scalar {
fn from_uniform_bytes(bytes: &[u8; 64]) -> Self {
Scalar::from_bytes_mod_order_wide(bytes)
}
}
/// Read one or more u64s stored as little endian bytes. /// Read one or more u64s stored as little endian bytes.
/// ///
/// ## Panics /// ## Panics
@ -1814,6 +1940,56 @@ pub(crate) mod test {
assert_eq!(sx + s1, Scalar::from(x + 1)); assert_eq!(sx + s1, Scalar::from(x + 1));
} }
#[cfg(feature = "group")]
#[test]
fn ff_constants() {
assert_eq!(Scalar::from(2u64) * Scalar::TWO_INV, Scalar::ONE);
assert_eq!(
Scalar::ROOT_OF_UNITY * Scalar::ROOT_OF_UNITY_INV,
Scalar::ONE,
);
// ROOT_OF_UNITY^{2^s} mod m == 1
assert_eq!(
Scalar::ROOT_OF_UNITY.pow(&[1u64 << Scalar::S, 0, 0, 0]),
Scalar::ONE,
);
// DELTA^{t} mod m == 1
assert_eq!(
Scalar::DELTA.pow(&[
0x9604_98c6_973d_74fb,
0x0537_be77_a8bd_e735,
0x0000_0000_0000_0000,
0x0400_0000_0000_0000,
]),
Scalar::ONE,
);
}
#[cfg(feature = "group")]
#[test]
fn ff_impls() {
assert!(bool::from(Scalar::ZERO.is_even()));
assert!(bool::from(Scalar::ONE.is_odd()));
assert!(bool::from(Scalar::from(2u64).is_even()));
assert!(bool::from(Scalar::DELTA.is_even()));
assert!(bool::from(Field::invert(&Scalar::ZERO).is_none()));
assert_eq!(Field::invert(&X).unwrap(), XINV);
let x_sq = X.square();
// We should get back either the positive or negative root.
assert!([X, -X].contains(&x_sq.sqrt().unwrap()));
assert_eq!(Scalar::from_repr_vartime(X.to_repr()), Some(X));
assert_eq!(Scalar::from_repr_vartime([0xff; 32]), None);
assert_eq!(Scalar::from_repr(X.to_repr()).unwrap(), X);
assert!(bool::from(Scalar::from_repr([0xff; 32]).is_none()));
}
#[test] #[test]
#[should_panic] #[should_panic]
fn test_read_le_u64_into_should_panic_on_bad_input() { fn test_read_le_u64_into_should_panic_on_bad_input() {