Skip to content

Commit f24c2ae

Browse files
authored
elliptic-curve: impl BatchNormalize for NonIdentity (#1896)
As discussed in #1889. I will add some tests in `elliptic-curves` as well. Resolves #1889. Companion PR: RustCrypto/elliptic-curves#1248.
1 parent 8534db4 commit f24c2ae

File tree

5 files changed

+150
-5
lines changed

5 files changed

+150
-5
lines changed

.github/workflows/elliptic-curve.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,21 @@ jobs:
8888
- run: cargo test --no-default-features
8989
- run: cargo test
9090
- run: cargo test --all-features
91+
92+
test-careful:
93+
runs-on: ubuntu-latest
94+
steps:
95+
- uses: actions/checkout@v4
96+
- uses: dtolnay/rust-toolchain@nightly
97+
- run: cargo install cargo-careful
98+
- run: cargo careful test --all-features
99+
100+
test-miri:
101+
runs-on: ubuntu-latest
102+
env:
103+
MIRIFLAGS: "-Zmiri-symbolic-alignment-check -Zmiri-strict-provenance"
104+
steps:
105+
- uses: actions/checkout@v4
106+
- uses: dtolnay/rust-toolchain@nightly
107+
- run: rustup component add miri && cargo miri setup
108+
- run: cargo miri test --all-features

elliptic-curve/src/dev.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! the traits in this crate.
55
66
use crate::{
7-
Curve, CurveArithmetic, FieldBytesEncoding, PrimeCurve,
7+
BatchNormalize, Curve, CurveArithmetic, FieldBytesEncoding, PrimeCurve,
88
array::typenum::U32,
99
bigint::{Limb, U256},
1010
error::{Error, Result},
@@ -17,13 +17,17 @@ use crate::{
1717
zeroize::DefaultIsZeroes,
1818
};
1919
use core::{
20+
array,
2021
iter::{Product, Sum},
2122
ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
2223
};
2324
use ff::{Field, PrimeField};
2425
use hex_literal::hex;
2526
use pkcs8::AssociatedOid;
2627

28+
#[cfg(feature = "alloc")]
29+
use alloc::vec::Vec;
30+
2731
#[cfg(feature = "bits")]
2832
use ff::PrimeFieldBits;
2933

@@ -584,6 +588,23 @@ pub enum ProjectivePoint {
584588
Other(AffinePoint),
585589
}
586590

591+
impl<const N: usize> BatchNormalize<[ProjectivePoint; N]> for ProjectivePoint {
592+
type Output = [AffinePoint; N];
593+
594+
fn batch_normalize(points: &[ProjectivePoint; N]) -> [AffinePoint; N] {
595+
array::from_fn(|index| points[index].into())
596+
}
597+
}
598+
599+
#[cfg(feature = "alloc")]
600+
impl BatchNormalize<[ProjectivePoint]> for ProjectivePoint {
601+
type Output = Vec<AffinePoint>;
602+
603+
fn batch_normalize(points: &[ProjectivePoint]) -> Vec<AffinePoint> {
604+
points.iter().copied().map(AffinePoint::from).collect()
605+
}
606+
}
607+
587608
impl ConstantTimeEq for ProjectivePoint {
588609
fn ct_eq(&self, other: &Self) -> Choice {
589610
match (self, other) {

elliptic-curve/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
66
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
77
)]
8-
#![forbid(unsafe_code)]
8+
// Only allowed for newtype casts.
9+
#![deny(unsafe_code)]
910
#![warn(
1011
clippy::cast_lossless,
1112
clippy::cast_possible_truncation,

elliptic-curve/src/point.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ pub trait AffineCoordinates {
4040

4141
/// Normalize point(s) in projective representation by converting them to their affine ones.
4242
#[cfg(feature = "arithmetic")]
43-
pub trait BatchNormalize<Points: ?Sized>: group::Curve {
43+
pub trait BatchNormalize<Points: ?Sized> {
4444
/// The output of the batch normalization; a container of affine points.
45-
type Output: AsRef<[Self::AffineRepr]>;
45+
type Output;
4646

4747
/// Perform a batched conversion to affine representation on a sequence of projective points
4848
/// at an amortized cost that should be practically as efficient as a single conversion.

elliptic-curve/src/point/non_identity.rs

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ use group::{Curve, Group, GroupEncoding, prime::PrimeCurveAffine};
66
use rand_core::CryptoRng;
77
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
88

9+
#[cfg(feature = "alloc")]
10+
use alloc::vec::Vec;
11+
912
#[cfg(feature = "serde")]
1013
use serdect::serde::{Deserialize, Serialize, de, ser};
1114
use zeroize::Zeroize;
1215

13-
use crate::{CurveArithmetic, NonZeroScalar, Scalar};
16+
use crate::{BatchNormalize, CurveArithmetic, NonZeroScalar, Scalar};
1417

1518
/// Non-identity point type.
1619
///
@@ -19,6 +22,7 @@ use crate::{CurveArithmetic, NonZeroScalar, Scalar};
1922
/// In the context of ECC, it's useful for ensuring that certain arithmetic
2023
/// cannot result in the identity point.
2124
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25+
#[repr(transparent)]
2226
pub struct NonIdentity<P> {
2327
point: P,
2428
}
@@ -103,6 +107,72 @@ impl<P> AsRef<P> for NonIdentity<P> {
103107
}
104108
}
105109

110+
impl<const N: usize, P> BatchNormalize<[Self; N]> for NonIdentity<P>
111+
where
112+
P: Curve + BatchNormalize<[P; N], Output = [P::AffineRepr; N]>,
113+
{
114+
type Output = [NonIdentity<P::AffineRepr>; N];
115+
116+
fn batch_normalize(points: &[Self; N]) -> [NonIdentity<P::AffineRepr>; N] {
117+
// Ensure casting is safe.
118+
// This always succeeds because `NonIdentity` is `repr(transparent)`.
119+
debug_assert_eq!(size_of::<P>(), size_of::<NonIdentity<P>>());
120+
debug_assert_eq!(align_of::<P>(), align_of::<NonIdentity<P>>());
121+
122+
#[allow(unsafe_code)]
123+
// SAFETY: `NonIdentity` is `repr(transparent)`.
124+
let points: &[P; N] = unsafe { &*points.as_ptr().cast() };
125+
let affine_points = <P as BatchNormalize<_>>::batch_normalize(points);
126+
127+
// Ensure `array::map()` can be optimized to a `memcpy`.
128+
debug_assert_eq!(
129+
size_of::<P::AffineRepr>(),
130+
size_of::<NonIdentity<P::AffineRepr>>()
131+
);
132+
debug_assert_eq!(
133+
align_of::<P::AffineRepr>(),
134+
align_of::<NonIdentity<P::AffineRepr>>()
135+
);
136+
137+
affine_points.map(|point| NonIdentity { point })
138+
}
139+
}
140+
141+
#[cfg(feature = "alloc")]
142+
impl<P> BatchNormalize<[Self]> for NonIdentity<P>
143+
where
144+
P: Curve + BatchNormalize<[P], Output = Vec<P::AffineRepr>>,
145+
{
146+
type Output = Vec<NonIdentity<P::AffineRepr>>;
147+
148+
fn batch_normalize(points: &[Self]) -> Vec<NonIdentity<P::AffineRepr>> {
149+
// Ensure casting is safe.
150+
// This always succeeds because `NonIdentity` is `repr(transparent)`.
151+
debug_assert_eq!(size_of::<P>(), size_of::<NonIdentity<P>>());
152+
debug_assert_eq!(align_of::<P>(), align_of::<NonIdentity<P>>());
153+
154+
#[allow(unsafe_code)]
155+
// SAFETY: `NonIdentity` is `repr(transparent)`.
156+
let points: &[P] = unsafe { &*(points as *const [NonIdentity<P>] as *const [P]) };
157+
let affine_points = <P as BatchNormalize<_>>::batch_normalize(points);
158+
159+
// Ensure `into_iter()` + `collect()` can be optimized away.
160+
debug_assert_eq!(
161+
size_of::<P::AffineRepr>(),
162+
size_of::<NonIdentity<P::AffineRepr>>()
163+
);
164+
debug_assert_eq!(
165+
align_of::<P::AffineRepr>(),
166+
align_of::<NonIdentity<P::AffineRepr>>()
167+
);
168+
169+
affine_points
170+
.into_iter()
171+
.map(|point| NonIdentity { point })
172+
.collect()
173+
}
174+
}
175+
106176
impl<P> ConditionallySelectable for NonIdentity<P>
107177
where
108178
P: ConditionallySelectable,
@@ -238,6 +308,7 @@ impl<P: Group> Zeroize for NonIdentity<P> {
238308
#[cfg(all(test, feature = "dev"))]
239309
mod tests {
240310
use super::NonIdentity;
311+
use crate::BatchNormalize;
241312
use crate::dev::{AffinePoint, NonZeroScalar, ProjectivePoint, SecretKey};
242313
use group::GroupEncoding;
243314
use hex_literal::hex;
@@ -303,4 +374,38 @@ mod tests {
303374

304375
assert_eq!(point.to_point(), pk.to_projective());
305376
}
377+
378+
#[test]
379+
fn batch_normalize() {
380+
let point = ProjectivePoint::from_bytes(
381+
&hex!("02c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").into(),
382+
)
383+
.unwrap();
384+
let point = NonIdentity::new(point).unwrap();
385+
let points = [point, point];
386+
387+
for (point, affine_point) in points
388+
.into_iter()
389+
.zip(NonIdentity::batch_normalize(&points))
390+
{
391+
assert_eq!(point.to_affine(), affine_point);
392+
}
393+
}
394+
395+
#[test]
396+
#[cfg(feature = "alloc")]
397+
fn batch_normalize_alloc() {
398+
let point = ProjectivePoint::from_bytes(
399+
&hex!("02c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").into(),
400+
)
401+
.unwrap();
402+
let point = NonIdentity::new(point).unwrap();
403+
let points = vec![point, point];
404+
405+
let affine_points = NonIdentity::batch_normalize(points.as_slice());
406+
407+
for (point, affine_point) in points.into_iter().zip(affine_points) {
408+
assert_eq!(point.to_affine(), affine_point);
409+
}
410+
}
306411
}

0 commit comments

Comments
 (0)