mirror of
https://github.com/saymrwulf/curve25519-dalek-source.git
synced 2026-09-03 20:13:48 +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()));
|
||||
}
|
||||
|
||||
#[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>) {
|
||||
let B_comp = &constants::ED25519_BASEPOINT_COMPRESSED;
|
||||
c.bench_function("EdwardsPoint decompression", move |b| {
|
||||
|
|
@ -62,6 +78,8 @@ mod edwards_benches {
|
|||
|
||||
compress(&mut g);
|
||||
decompress(&mut g);
|
||||
#[cfg(feature = "alloc")]
|
||||
compress_batch(&mut g);
|
||||
consttime_fixed_base_scalar_mul(&mut g);
|
||||
consttime_variable_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.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use core::array::TryFromSliceError;
|
||||
use core::borrow::Borrow;
|
||||
use core::fmt::Debug;
|
||||
|
|
@ -101,8 +102,6 @@ use core::ops::{Add, Neg, Sub};
|
|||
use core::ops::{AddAssign, SubAssign};
|
||||
use core::ops::{Mul, MulAssign};
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
use digest::{generic_array::typenum::U64, Digest};
|
||||
|
||||
|
|
@ -112,7 +111,7 @@ use {
|
|||
subtle::CtOption,
|
||||
};
|
||||
|
||||
#[cfg(feature = "group")]
|
||||
#[cfg(any(test, feature = "rand_core"))]
|
||||
use rand_core::RngCore;
|
||||
|
||||
use subtle::Choice;
|
||||
|
|
@ -151,6 +150,8 @@ use crate::traits::{Identity, IsIdentity};
|
|||
use crate::traits::MultiscalarMul;
|
||||
#[cfg(feature = "alloc")]
|
||||
use crate::traits::{VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul};
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Compressed points
|
||||
|
|
@ -567,9 +568,31 @@ impl EdwardsPoint {
|
|||
let recip = self.Z.invert();
|
||||
let x = &self.X * &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;
|
||||
CompressedEdwardsY(s)
|
||||
}
|
||||
|
|
@ -605,6 +628,33 @@ impl EdwardsPoint {
|
|||
.expect("Montgomery conversion to Edwards point in Elligator failed")
|
||||
.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 {
|
||||
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 random(rng: impl RngCore) -> Self {
|
||||
// Call the inherent `pub fn random` defined above
|
||||
Self::random(rng)
|
||||
}
|
||||
|
||||
fn identity() -> Self {
|
||||
|
|
@ -2019,6 +2062,31 @@ mod test {
|
|||
EdwardsPoint::identity().compress(),
|
||||
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]
|
||||
|
|
|
|||
Loading…
Reference in a new issue