Skip to content

Commit 82a10e4

Browse files
committed
Convert more tests to Proptest
1 parent 4443375 commit 82a10e4

File tree

5 files changed

+238
-372
lines changed

5 files changed

+238
-372
lines changed

curve25519-dalek/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ sha2 = { version = "0.11.0-rc.0", default-features = false }
4141
bincode = "1"
4242
criterion = { version = "0.5", features = ["html_reports"] }
4343
hex = "0.4.2"
44-
proptest = "1"
44+
proptest = { version = "1", features = ["attr-macro"] }
45+
proptest-derive = "0.6"
4546
rand = "0.9"
4647
rand_core = { version = "0.9", default-features = false, features = ["os_rng"] }
4748

curve25519-dalek/src/edwards.rs

Lines changed: 48 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ use subtle::ConstantTimeEq;
128128
#[cfg(feature = "zeroize")]
129129
use zeroize::Zeroize;
130130

131+
#[cfg(test)]
132+
use proptest::{arbitrary::Arbitrary, strategy::BoxedStrategy};
133+
131134
use crate::constants;
132135

133136
use crate::field::FieldElement;
@@ -1771,6 +1774,18 @@ impl CofactorGroup for EdwardsPoint {
17711774
}
17721775
}
17731776

1777+
#[cfg(test)]
1778+
impl Arbitrary for EdwardsPoint {
1779+
type Parameters = ();
1780+
type Strategy = BoxedStrategy<Self>;
1781+
1782+
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1783+
use proptest::prelude::*;
1784+
1785+
Strategy::prop_map(any::<Scalar>(), |scalar| EdwardsPoint::mul_base(&scalar)).boxed()
1786+
}
1787+
}
1788+
17741789
// ------------------------------------------------------------------------
17751790
// Tests
17761791
// ------------------------------------------------------------------------
@@ -1779,7 +1794,7 @@ impl CofactorGroup for EdwardsPoint {
17791794
mod test {
17801795
use super::*;
17811796

1782-
use rand_core::TryRngCore;
1797+
use proptest::{prelude::*, property_test};
17831798

17841799
#[cfg(feature = "alloc")]
17851800
use alloc::vec::Vec;
@@ -2066,17 +2081,14 @@ mod test {
20662081
}
20672082

20682083
/// Check that mul_base_clamped and mul_clamped agree
2069-
#[test]
2070-
fn mul_base_clamped() {
2071-
let mut csprng = rand_core::OsRng;
2084+
#[property_test(config = ProptestConfig { cases: 50, .. ProptestConfig::default() })]
2085+
fn mul_base_clamped(b: EdwardsPoint, a: [[u8; 32]; 100]) {
2086+
#[cfg(not(feature = "precomputed-tables"))]
2087+
let _ = b;
20722088

20732089
// Make a random curve point in the curve. Give it torsion to make things interesting.
20742090
#[cfg(feature = "precomputed-tables")]
2075-
let random_point = {
2076-
let mut b = [0u8; 32];
2077-
csprng.try_fill_bytes(&mut b).unwrap();
2078-
EdwardsPoint::mul_base_clamped(b) + constants::EIGHT_TORSION[1]
2079-
};
2091+
let random_point = b + constants::EIGHT_TORSION[1];
20802092
// Make a basepoint table from the random point. We'll use this with mul_base_clamped
20812093
#[cfg(feature = "precomputed-tables")]
20822094
let random_table = EdwardsBasepointTableRadix256::create(&random_point);
@@ -2086,28 +2098,25 @@ mod test {
20862098
// Test that mul_base_clamped and mul_clamped agree on a large integer. Even after
20872099
// clamping, this integer is not reduced mod l.
20882100
let a_bytes = [0xff; 32];
2089-
assert_eq!(
2101+
prop_assert_eq!(
20902102
EdwardsPoint::mul_base_clamped(a_bytes),
20912103
constants::ED25519_BASEPOINT_POINT.mul_clamped(a_bytes)
20922104
);
20932105
#[cfg(feature = "precomputed-tables")]
2094-
assert_eq!(
2106+
prop_assert_eq!(
20952107
random_table.mul_base_clamped(a_bytes),
20962108
random_point.mul_clamped(a_bytes)
20972109
);
20982110

20992111
// Test agreement on random integers
2100-
for _ in 0..100 {
2112+
for a_bytes in a {
21012113
// This will be reduced mod l with probability l / 2^256 ≈ 6.25%
2102-
let mut a_bytes = [0u8; 32];
2103-
csprng.try_fill_bytes(&mut a_bytes).unwrap();
2104-
2105-
assert_eq!(
2114+
prop_assert_eq!(
21062115
EdwardsPoint::mul_base_clamped(a_bytes),
21072116
constants::ED25519_BASEPOINT_POINT.mul_clamped(a_bytes)
21082117
);
21092118
#[cfg(feature = "precomputed-tables")]
2110-
assert_eq!(
2119+
prop_assert_eq!(
21112120
random_table.mul_base_clamped(a_bytes),
21122121
random_point.mul_clamped(a_bytes)
21132122
);
@@ -2182,22 +2191,14 @@ mod test {
21822191
}
21832192
}
21842193

2194+
#[property_test]
21852195
#[cfg(feature = "alloc")]
2186-
#[test]
2187-
fn compress_batch() {
2188-
let mut rng = rand::rng();
2189-
2190-
// TODO(tarcieri): proptests?
2191-
// Make some points deterministically then randomly
2192-
let mut points = (1u64..16)
2193-
.map(|n| constants::ED25519_BASEPOINT_POINT * Scalar::from(n))
2194-
.collect::<Vec<_>>();
2195-
points.extend(core::iter::repeat_with(|| EdwardsPoint::random(&mut rng)).take(100));
2196+
fn compress_batch(points: Vec<EdwardsPoint>) {
21962197
let compressed = EdwardsPoint::compress_batch(&points);
21972198

21982199
// Check that the batch-compressed points match the individually compressed ones
21992200
for (point, compressed) in points.iter().zip(&compressed) {
2200-
assert_eq!(&point.compress(), compressed);
2201+
prop_assert_eq!(&point.compress(), compressed);
22012202
}
22022203
}
22032204

@@ -2238,14 +2239,11 @@ mod test {
22382239
assert!(P1.compress().to_bytes() == P2.compress().to_bytes());
22392240
}
22402241

2241-
// A single iteration of a consistency check for MSM.
2242+
#[property_test]
22422243
#[cfg(feature = "alloc")]
2243-
fn multiscalar_consistency_iter(n: usize) {
2244-
let mut rng = rand::rng();
2245-
2244+
fn multiscalar_consistency(xs: Vec<Scalar>) {
22462245
// Construct random coefficients x0, ..., x_{n-1},
22472246
// followed by some extra hardcoded ones.
2248-
let xs = (0..n).map(|_| Scalar::random(&mut rng)).collect::<Vec<_>>();
22492247
let check = xs.iter().map(|xi| xi * xi).sum::<Scalar>();
22502248

22512249
// Construct points G_i = x_i * B
@@ -2258,58 +2256,13 @@ mod test {
22582256
// Compute H3 = <xs, Gs> = sum(xi^2) * B
22592257
let H3 = EdwardsPoint::mul_base(&check);
22602258

2261-
assert_eq!(H1, H3);
2262-
assert_eq!(H2, H3);
2263-
}
2264-
2265-
// Use different multiscalar sizes to hit different internal
2266-
// parameters.
2267-
2268-
#[test]
2269-
#[cfg(feature = "alloc")]
2270-
fn multiscalar_consistency_n_100() {
2271-
let iters = 50;
2272-
for _ in 0..iters {
2273-
multiscalar_consistency_iter(100);
2274-
}
2275-
}
2276-
2277-
#[test]
2278-
#[cfg(feature = "alloc")]
2279-
fn multiscalar_consistency_n_250() {
2280-
let iters = 50;
2281-
for _ in 0..iters {
2282-
multiscalar_consistency_iter(250);
2283-
}
2259+
prop_assert_eq!(H1, H3);
2260+
prop_assert_eq!(H2, H3);
22842261
}
22852262

2286-
#[test]
2263+
#[property_test]
22872264
#[cfg(feature = "alloc")]
2288-
fn multiscalar_consistency_n_500() {
2289-
let iters = 50;
2290-
for _ in 0..iters {
2291-
multiscalar_consistency_iter(500);
2292-
}
2293-
}
2294-
2295-
#[test]
2296-
#[cfg(feature = "alloc")]
2297-
fn multiscalar_consistency_n_1000() {
2298-
let iters = 50;
2299-
for _ in 0..iters {
2300-
multiscalar_consistency_iter(1000);
2301-
}
2302-
}
2303-
2304-
#[test]
2305-
#[cfg(feature = "alloc")]
2306-
fn batch_to_montgomery() {
2307-
let mut rng = rand::rng();
2308-
2309-
let scalars = (0..128)
2310-
.map(|_| Scalar::random(&mut rng))
2311-
.collect::<Vec<_>>();
2312-
2265+
fn batch_to_montgomery(scalars: Vec<Scalar>) {
23132266
let points = scalars
23142267
.iter()
23152268
.map(EdwardsPoint::mul_base)
@@ -2320,25 +2273,19 @@ mod test {
23202273
.map(EdwardsPoint::to_montgomery)
23212274
.collect::<Vec<_>>();
23222275

2323-
for i in [0, 1, 2, 3, 10, 50, 128] {
2324-
let invs = EdwardsPoint::to_montgomery_batch(&points[..i]);
2325-
assert_eq!(&invs, &single_monts[..i]);
2326-
}
2276+
let invs = EdwardsPoint::to_montgomery_batch(&points);
2277+
prop_assert_eq!(&invs, &single_monts);
23272278
}
23282279

2329-
#[test]
2280+
#[property_test]
23302281
#[cfg(feature = "alloc")]
2331-
fn vartime_precomputed_vs_nonprecomputed_multiscalar() {
2332-
let mut rng = rand::rng();
2333-
2334-
let static_scalars = (0..128)
2335-
.map(|_| Scalar::random(&mut rng))
2336-
.collect::<Vec<_>>();
2337-
2338-
let dynamic_scalars = (0..128)
2339-
.map(|_| Scalar::random(&mut rng))
2340-
.collect::<Vec<_>>();
2341-
2282+
fn vartime_precomputed_vs_nonprecomputed_multiscalar(
2283+
#[strategy = any::<Vec<Scalar>>().prop_flat_map(|v1| {
2284+
let len = v1.len();
2285+
(Just(v1), prop::collection::vec(any::<Scalar>(), len))
2286+
})]
2287+
(static_scalars, dynamic_scalars): (Vec<Scalar>, Vec<Scalar>),
2288+
) {
23422289
let check_scalar: Scalar = static_scalars
23432290
.iter()
23442291
.chain(dynamic_scalars.iter())
@@ -2356,8 +2303,7 @@ mod test {
23562303

23572304
let precomputation = VartimeEdwardsPrecomputation::new(static_points.iter());
23582305

2359-
assert_eq!(precomputation.len(), 128);
2360-
assert!(!precomputation.is_empty());
2306+
prop_assert_eq!(precomputation.len(), static_scalars.len());
23612307

23622308
let P = precomputation.vartime_mixed_multiscalar_mul(
23632309
&static_scalars,
@@ -2373,8 +2319,8 @@ mod test {
23732319

23742320
let R = EdwardsPoint::mul_base(&check_scalar);
23752321

2376-
assert_eq!(P.compress(), R.compress());
2377-
assert_eq!(Q.compress(), R.compress());
2322+
prop_assert_eq!(P.compress(), R.compress());
2323+
prop_assert_eq!(Q.compress(), R.compress());
23782324
}
23792325

23802326
mod vartime {

0 commit comments

Comments
 (0)