mirror of
https://github.com/saymrwulf/curve25519-dalek-source.git
synced 2026-09-04 20:24:10 +00:00
curve: add EdwardsPoint::compress_batch and inherent ::random (#759)
* curve: add `EdwardsPoint::compress_batch` and `::random` We've had various requests to implement batch point compression for `EdwardsPoint`, e.g. #705. We can leverage `FieldElement::batch_invert` to implement it, which results in a fairly significant speedup. The name `EdwardsPoint::compress_batch` has been chosen to match `RistrettoPoint::double_and_compress_batch`. For benchmarking, randomized `EdwardsPoint`s have been used. To obtain these, an inherent `EdwardsPoint::random` has been extracted from the existing `Group::random` implementation, which uses rejection sampling. `Group::random` has been updated to call the inherent `EdwardsPoint::random`. This avoids a `group` dependency just to run the batch compression benchmarks. The following benchmark results have been obtained: edwards benches/EdwardsPoint compression time: [3.5029 µs 3.5098 µs 3.5171 µs] edwards benches/Batch EdwardsPoint compression/1 time: [3.6698 µs 3.6758 µs 3.6817 µs] edwards benches/Batch EdwardsPoint compression/2 time: [3.8410 µs 3.8461 µs 3.8516 µs] edwards benches/Batch EdwardsPoint compression/4 time: [4.1534 µs 4.1961 µs 4.2558 µs] edwards benches/Batch EdwardsPoint compression/8 time: [4.8466 µs 4.8533 µs 4.8600 µs] edwards benches/Batch EdwardsPoint compression/16 time: [6.1216 µs 6.1315 µs 6.1410 µs] As you can see, it affords a fairly significant speedup, batch compressing 16 points in less time than the standard point compression algorithm would take to compress 2 in a row.
This commit is contained in:
parent
e3b5328202
commit
dd5bd108d6
2 changed files with 101 additions and 15 deletions
|
|
@ -22,6 +22,22 @@ mod edwards_benches {
|
||||||
c.bench_function("EdwardsPoint compression", move |b| b.iter(|| B.compress()));
|
c.bench_function("EdwardsPoint compression", move |b| b.iter(|| B.compress()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
fn compress_batch<M: Measurement>(c: &mut BenchmarkGroup<M>) {
|
||||||
|
for batch_size in BATCH_SIZES {
|
||||||
|
c.bench_with_input(
|
||||||
|
BenchmarkId::new("Batch EdwardsPoint compression", batch_size),
|
||||||
|
&batch_size,
|
||||||
|
|b, &size| {
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let points: Vec<EdwardsPoint> =
|
||||||
|
(0..size).map(|_| EdwardsPoint::random(&mut rng)).collect();
|
||||||
|
b.iter(|| EdwardsPoint::compress_batch(&points));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn decompress<M: Measurement>(c: &mut BenchmarkGroup<M>) {
|
fn decompress<M: Measurement>(c: &mut BenchmarkGroup<M>) {
|
||||||
let B_comp = &constants::ED25519_BASEPOINT_COMPRESSED;
|
let B_comp = &constants::ED25519_BASEPOINT_COMPRESSED;
|
||||||
c.bench_function("EdwardsPoint decompression", move |b| {
|
c.bench_function("EdwardsPoint decompression", move |b| {
|
||||||
|
|
@ -62,6 +78,8 @@ mod edwards_benches {
|
||||||
|
|
||||||
compress(&mut g);
|
compress(&mut g);
|
||||||
decompress(&mut g);
|
decompress(&mut g);
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
compress_batch(&mut g);
|
||||||
consttime_fixed_base_scalar_mul(&mut g);
|
consttime_fixed_base_scalar_mul(&mut g);
|
||||||
consttime_variable_base_scalar_mul(&mut g);
|
consttime_variable_base_scalar_mul(&mut g);
|
||||||
vartime_double_base_scalar_mul(&mut g);
|
vartime_double_base_scalar_mul(&mut g);
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@
|
||||||
// affine and projective cakes and eat both of them too.
|
// affine and projective cakes and eat both of them too.
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use cfg_if::cfg_if;
|
||||||
use core::array::TryFromSliceError;
|
use core::array::TryFromSliceError;
|
||||||
use core::borrow::Borrow;
|
use core::borrow::Borrow;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
|
@ -101,8 +102,6 @@ use core::ops::{Add, Neg, Sub};
|
||||||
use core::ops::{AddAssign, SubAssign};
|
use core::ops::{AddAssign, SubAssign};
|
||||||
use core::ops::{Mul, MulAssign};
|
use core::ops::{Mul, MulAssign};
|
||||||
|
|
||||||
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};
|
||||||
|
|
||||||
|
|
@ -112,7 +111,7 @@ use {
|
||||||
subtle::CtOption,
|
subtle::CtOption,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "group")]
|
#[cfg(any(test, feature = "rand_core"))]
|
||||||
use rand_core::RngCore;
|
use rand_core::RngCore;
|
||||||
|
|
||||||
use subtle::Choice;
|
use subtle::Choice;
|
||||||
|
|
@ -151,6 +150,8 @@ use crate::traits::{Identity, IsIdentity};
|
||||||
use crate::traits::MultiscalarMul;
|
use crate::traits::MultiscalarMul;
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
use crate::traits::{VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul};
|
use crate::traits::{VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul};
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Compressed points
|
// Compressed points
|
||||||
|
|
@ -567,9 +568,31 @@ impl EdwardsPoint {
|
||||||
let recip = self.Z.invert();
|
let recip = self.Z.invert();
|
||||||
let x = &self.X * &recip;
|
let x = &self.X * &recip;
|
||||||
let y = &self.Y * &recip;
|
let y = &self.Y * &recip;
|
||||||
let mut s: [u8; 32];
|
Self::compress_affine(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
s = y.as_bytes();
|
/// Compress several `EdwardsPoint`s into `CompressedEdwardsY` format, using a batch inversion
|
||||||
|
/// for a significant speedup.
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub fn compress_batch(inputs: &[EdwardsPoint]) -> Vec<CompressedEdwardsY> {
|
||||||
|
let mut zs = inputs.iter().map(|input| input.Z).collect::<Vec<_>>();
|
||||||
|
FieldElement::batch_invert(&mut zs);
|
||||||
|
|
||||||
|
inputs
|
||||||
|
.iter()
|
||||||
|
.zip(&zs)
|
||||||
|
.map(|(input, recip)| {
|
||||||
|
let x = &input.X * recip;
|
||||||
|
let y = &input.Y * recip;
|
||||||
|
Self::compress_affine(x, y)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compress affine Edwards coordinates into `CompressedEdwardsY` format.
|
||||||
|
#[inline]
|
||||||
|
fn compress_affine(x: FieldElement, y: FieldElement) -> CompressedEdwardsY {
|
||||||
|
let mut s = y.as_bytes();
|
||||||
s[31] ^= x.is_negative().unwrap_u8() << 7;
|
s[31] ^= x.is_negative().unwrap_u8() << 7;
|
||||||
CompressedEdwardsY(s)
|
CompressedEdwardsY(s)
|
||||||
}
|
}
|
||||||
|
|
@ -605,6 +628,33 @@ impl EdwardsPoint {
|
||||||
.expect("Montgomery conversion to Edwards point in Elligator failed")
|
.expect("Montgomery conversion to Edwards point in Elligator failed")
|
||||||
.mul_by_cofactor()
|
.mul_by_cofactor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return an `EdwardsPoint` chosen uniformly at random using a user-provided RNG.
|
||||||
|
///
|
||||||
|
/// # Inputs
|
||||||
|
///
|
||||||
|
/// * `rng`: any RNG which implements `RngCore`
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A random `EdwardsPoint`.
|
||||||
|
///
|
||||||
|
/// # Implementation
|
||||||
|
///
|
||||||
|
/// Uses rejection sampling, generating a random `CompressedEdwardsY` and then attempting point
|
||||||
|
/// decompression, rejecting invalid points.
|
||||||
|
#[cfg(any(test, feature = "rand_core"))]
|
||||||
|
pub 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -1291,16 +1341,9 @@ impl Debug for EdwardsPoint {
|
||||||
impl group::Group for EdwardsPoint {
|
impl group::Group for EdwardsPoint {
|
||||||
type Scalar = Scalar;
|
type Scalar = Scalar;
|
||||||
|
|
||||||
fn random(mut rng: impl RngCore) -> Self {
|
fn random(rng: impl RngCore) -> Self {
|
||||||
let mut repr = CompressedEdwardsY([0u8; 32]);
|
// Call the inherent `pub fn random` defined above
|
||||||
loop {
|
Self::random(rng)
|
||||||
rng.fill_bytes(&mut repr.0);
|
|
||||||
if let Some(p) = repr.decompress() {
|
|
||||||
if !IsIdentity::is_identity(&p) {
|
|
||||||
break p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identity() -> Self {
|
fn identity() -> Self {
|
||||||
|
|
@ -2019,6 +2062,31 @@ mod test {
|
||||||
EdwardsPoint::identity().compress(),
|
EdwardsPoint::identity().compress(),
|
||||||
CompressedEdwardsY::identity()
|
CompressedEdwardsY::identity()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
{
|
||||||
|
let compressed = EdwardsPoint::compress_batch(&[EdwardsPoint::identity()]);
|
||||||
|
assert_eq!(&compressed, &[CompressedEdwardsY::identity()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
#[test]
|
||||||
|
fn compress_batch() {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
// TODO(tarcieri): proptests?
|
||||||
|
// Make some points deterministically then randomly
|
||||||
|
let mut points = (1u64..16)
|
||||||
|
.map(|n| constants::ED25519_BASEPOINT_POINT * Scalar::from(n))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
points.extend(core::iter::repeat_with(|| EdwardsPoint::random(&mut rng)).take(100));
|
||||||
|
let compressed = EdwardsPoint::compress_batch(&points);
|
||||||
|
|
||||||
|
// Check that the batch-compressed points match the individually compressed ones
|
||||||
|
for (point, compressed) in points.iter().zip(&compressed) {
|
||||||
|
assert_eq!(&point.compress(), compressed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue