mirror of
https://github.com/saymrwulf/curve25519-dalek-source.git
synced 2026-09-07 20:50:39 +00:00
curve: implement ff and group traits (#562)
Originally authored by @str4d as #473
This commit is contained in:
parent
098658dc8b
commit
4373695c50
7 changed files with 662 additions and 22 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue