mirror of
https://github.com/saymrwulf/curve25519-dalek-source.git
synced 2026-09-07 20:50:39 +00:00
Add allocation-free EdwardsPoint::compress_batch() (#832)
This commit is contained in:
parent
0fbf15e3c2
commit
aaff294bfa
4 changed files with 81 additions and 28 deletions
|
|
@ -34,7 +34,7 @@ mod edwards_benches {
|
||||||
let mut rng = OsRng.unwrap_err();
|
let mut rng = OsRng.unwrap_err();
|
||||||
let points: Vec<EdwardsPoint> =
|
let points: Vec<EdwardsPoint> =
|
||||||
(0..size).map(|_| EdwardsPoint::random(&mut rng)).collect();
|
(0..size).map(|_| EdwardsPoint::random(&mut rng)).collect();
|
||||||
b.iter(|| EdwardsPoint::compress_batch(&points));
|
b.iter(|| EdwardsPoint::compress_batch_alloc(&points));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -599,7 +599,7 @@ impl EdwardsPoint {
|
||||||
|
|
||||||
// Compute the denominators in a batch
|
// Compute the denominators in a batch
|
||||||
let mut denominators = eds.iter().map(|p| &p.Z - &p.Y).collect::<Vec<_>>();
|
let mut denominators = eds.iter().map(|p| &p.Z - &p.Y).collect::<Vec<_>>();
|
||||||
FieldElement::batch_invert(&mut denominators);
|
FieldElement::invert_batch_alloc(&mut denominators);
|
||||||
|
|
||||||
// Now compute the Montgomery u coordinate for every point
|
// Now compute the Montgomery u coordinate for every point
|
||||||
let mut ret = Vec::with_capacity(eds.len());
|
let mut ret = Vec::with_capacity(eds.len());
|
||||||
|
|
@ -616,12 +616,24 @@ impl EdwardsPoint {
|
||||||
self.to_affine().compress()
|
self.to_affine().compress()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compress several `EdwardsPoint`s into `CompressedEdwardsY` format, using a batch inversion
|
||||||
|
/// for a significant speedup.
|
||||||
|
pub fn compress_batch<const N: usize>(inputs: &[EdwardsPoint; N]) -> [CompressedEdwardsY; N] {
|
||||||
|
let mut zs: [_; N] = core::array::from_fn(|i| inputs[i].Z);
|
||||||
|
FieldElement::invert_batch(&mut zs);
|
||||||
|
|
||||||
|
core::array::from_fn(|i| {
|
||||||
|
let x = &inputs[i].X * &zs[i];
|
||||||
|
let y = &inputs[i].Y * &zs[i];
|
||||||
|
AffinePoint { x, y }.compress()
|
||||||
|
})
|
||||||
|
}
|
||||||
/// Compress several `EdwardsPoint`s into `CompressedEdwardsY` format, using a batch inversion
|
/// Compress several `EdwardsPoint`s into `CompressedEdwardsY` format, using a batch inversion
|
||||||
/// for a significant speedup.
|
/// for a significant speedup.
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn compress_batch(inputs: &[EdwardsPoint]) -> Vec<CompressedEdwardsY> {
|
pub fn compress_batch_alloc(inputs: &[EdwardsPoint]) -> Vec<CompressedEdwardsY> {
|
||||||
let mut zs = inputs.iter().map(|input| input.Z).collect::<Vec<_>>();
|
let mut zs = inputs.iter().map(|input| input.Z).collect::<Vec<_>>();
|
||||||
FieldElement::batch_invert(&mut zs);
|
FieldElement::invert_batch_alloc(&mut zs);
|
||||||
|
|
||||||
inputs
|
inputs
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -2175,30 +2187,49 @@ mod test {
|
||||||
CompressedEdwardsY::identity()
|
CompressedEdwardsY::identity()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
EdwardsPoint::compress_batch(&[EdwardsPoint::identity()]),
|
||||||
|
[CompressedEdwardsY::identity()]
|
||||||
|
);
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
{
|
assert_eq!(
|
||||||
let compressed = EdwardsPoint::compress_batch(&[EdwardsPoint::identity()]);
|
&EdwardsPoint::compress_batch_alloc(&[EdwardsPoint::identity()]),
|
||||||
assert_eq!(&compressed, &[CompressedEdwardsY::identity()]);
|
&[CompressedEdwardsY::identity()]
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "alloc", feature = "rand_core"))]
|
#[cfg(feature = "rand_core")]
|
||||||
#[test]
|
#[test]
|
||||||
fn compress_batch() {
|
fn compress_batch() {
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::rng();
|
||||||
|
|
||||||
// TODO(tarcieri): proptests?
|
// TODO(tarcieri): proptests?
|
||||||
// Make some points deterministically then randomly
|
|
||||||
let mut points = (1u64..16)
|
// Make some test points deterministically then randomly
|
||||||
.map(|n| constants::ED25519_BASEPOINT_POINT * Scalar::from(n))
|
const TEST_VEC_LEN: usize = 117;
|
||||||
.collect::<Vec<_>>();
|
let points: [EdwardsPoint; TEST_VEC_LEN] = core::array::from_fn(|i| {
|
||||||
points.extend(core::iter::repeat_with(|| EdwardsPoint::random(&mut rng)).take(100));
|
if i < 17 {
|
||||||
let compressed = EdwardsPoint::compress_batch(&points);
|
// The first 17 are multiple of the basepoint
|
||||||
|
constants::ED25519_BASEPOINT_POINT * Scalar::from(i as u64)
|
||||||
|
} else {
|
||||||
|
// The rest are random
|
||||||
|
EdwardsPoint::random(&mut rng)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compress the points individually. This is our reference result
|
||||||
|
let expected_compressed = core::array::from_fn(|i| points[i].compress());
|
||||||
|
|
||||||
// Check that the batch-compressed points match the individually compressed ones
|
// Check that the batch-compressed points match the individually compressed ones
|
||||||
for (point, compressed) in points.iter().zip(&compressed) {
|
assert_eq!(EdwardsPoint::compress_batch(&points), expected_compressed);
|
||||||
assert_eq!(&point.compress(), compressed);
|
|
||||||
}
|
// Check that the batch-compressed (with alloc) points match the individually compressed
|
||||||
|
// ones
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
assert_eq!(
|
||||||
|
EdwardsPoint::compress_batch_alloc(&points),
|
||||||
|
expected_compressed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -209,17 +209,39 @@ impl FieldElement {
|
||||||
(t19, t3)
|
(t19, t3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a slice of pub(crate)lic `FieldElements`, replace each with its inverse.
|
||||||
|
///
|
||||||
|
/// When an input `FieldElement` is zero, its value is unchanged.
|
||||||
|
pub(crate) fn invert_batch<const N: usize>(inputs: &mut [FieldElement; N]) {
|
||||||
|
let mut scratch = [FieldElement::ONE; N];
|
||||||
|
|
||||||
|
Self::internal_invert_batch(inputs, &mut scratch);
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a slice of pub(crate)lic `FieldElements`, replace each with its inverse.
|
/// Given a slice of pub(crate)lic `FieldElements`, replace each with its inverse.
|
||||||
///
|
///
|
||||||
/// When an input `FieldElement` is zero, its value is unchanged.
|
/// When an input `FieldElement` is zero, its value is unchanged.
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub(crate) fn batch_invert(inputs: &mut [FieldElement]) {
|
pub(crate) fn invert_batch_alloc(inputs: &mut [FieldElement]) {
|
||||||
|
let n = inputs.len();
|
||||||
|
let mut scratch = vec![FieldElement::ONE; n];
|
||||||
|
|
||||||
|
Self::internal_invert_batch(inputs, &mut scratch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a slice of pub(crate)lic `FieldElements`, replace each with its inverse. `scratch` can
|
||||||
|
/// contain anything, so long as its length is the same as `inputs`.
|
||||||
|
///
|
||||||
|
/// When an input `FieldElement` is zero, its value is unchanged.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics when `scratch.len() != inputs.len()`
|
||||||
|
fn internal_invert_batch(inputs: &mut [FieldElement], scratch: &mut [FieldElement]) {
|
||||||
// Montgomery’s Trick and Fast Implementation of Masked AES
|
// Montgomery’s Trick and Fast Implementation of Masked AES
|
||||||
// Genelle, Prouff and Quisquater
|
// Genelle, Prouff and Quisquater
|
||||||
// Section 3.2
|
// Section 3.2
|
||||||
|
|
||||||
let n = inputs.len();
|
debug_assert_eq!(inputs.len(), scratch.len());
|
||||||
let mut scratch = vec![FieldElement::ONE; n];
|
|
||||||
|
|
||||||
// Keep an accumulator of all of the previous products
|
// Keep an accumulator of all of the previous products
|
||||||
let mut acc = FieldElement::ONE;
|
let mut acc = FieldElement::ONE;
|
||||||
|
|
@ -240,12 +262,12 @@ impl FieldElement {
|
||||||
|
|
||||||
// Pass through the vector backwards to compute the inverses
|
// Pass through the vector backwards to compute the inverses
|
||||||
// in place
|
// in place
|
||||||
for (input, scratch) in inputs.iter_mut().rev().zip(scratch.into_iter().rev()) {
|
for (input, scratch) in inputs.iter_mut().rev().zip(scratch.iter_mut().rev()) {
|
||||||
let tmp = &acc * input;
|
let tmp = &acc * input;
|
||||||
// input <- acc * scratch, then acc <- tmp
|
// input <- acc * scratch, then acc <- tmp
|
||||||
// Again, we skip zeros in a constant-time way
|
// Again, we skip zeros in a constant-time way
|
||||||
let nz = !input.is_zero();
|
let nz = !input.is_zero();
|
||||||
input.conditional_assign(&(&acc * &scratch), nz);
|
input.conditional_assign(&(&acc * scratch), nz);
|
||||||
acc.conditional_assign(&tmp, nz);
|
acc.conditional_assign(&tmp, nz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -559,7 +581,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
fn batch_invert_a_matches_nonbatched() {
|
fn invert_batch_a_matches_nonbatched() {
|
||||||
let a = FieldElement::from_bytes(&A_BYTES);
|
let a = FieldElement::from_bytes(&A_BYTES);
|
||||||
let ap58 = FieldElement::from_bytes(&AP58_BYTES);
|
let ap58 = FieldElement::from_bytes(&AP58_BYTES);
|
||||||
let asq = FieldElement::from_bytes(&ASQ_BYTES);
|
let asq = FieldElement::from_bytes(&ASQ_BYTES);
|
||||||
|
|
@ -568,7 +590,7 @@ mod test {
|
||||||
let a2 = &a + &a;
|
let a2 = &a + &a;
|
||||||
let a_list = vec![a, ap58, asq, ainv, a0, a2];
|
let a_list = vec![a, ap58, asq, ainv, a0, a2];
|
||||||
let mut ainv_list = a_list.clone();
|
let mut ainv_list = a_list.clone();
|
||||||
FieldElement::batch_invert(&mut ainv_list[..]);
|
FieldElement::invert_batch_alloc(&mut ainv_list[..]);
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
assert_eq!(a_list[i].invert(), ainv_list[i]);
|
assert_eq!(a_list[i].invert(), ainv_list[i]);
|
||||||
}
|
}
|
||||||
|
|
@ -677,8 +699,8 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
fn batch_invert_empty() {
|
fn invert_batch_empty() {
|
||||||
FieldElement::batch_invert(&mut []);
|
FieldElement::invert_batch_alloc(&mut []);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following two consts were generated with the following sage script:
|
// The following two consts were generated with the following sage script:
|
||||||
|
|
|
||||||
|
|
@ -606,7 +606,7 @@ impl RistrettoPoint {
|
||||||
|
|
||||||
let mut invs: Vec<FieldElement> = states.iter().map(|state| state.efgh()).collect();
|
let mut invs: Vec<FieldElement> = states.iter().map(|state| state.efgh()).collect();
|
||||||
|
|
||||||
FieldElement::batch_invert(&mut invs[..]);
|
FieldElement::invert_batch_alloc(&mut invs[..]);
|
||||||
|
|
||||||
states
|
states
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue