Skip to content

Embrace proptest v1 #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ Cargo.lock

/bench_wasm/target/
/bench_wasm/pkg/

proptest-regressions
1 change: 1 addition & 0 deletions ecdsa_fun/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ serde = ["secp256kfun/serde", "serde_crate"]
nightly = ["secp256kfun/nightly"]
# when https://github.com/rust-lang/cargo/issues/8832 is stabilized use the ? syntax to fix this
adaptor = ["sigma_fun", "bincode", "rand_chacha", "sigma_fun/serde", "sigma_fun/alloc"]
proptest = ["secp256kfun/proptest"]
1 change: 1 addition & 0 deletions schnorr_fun/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ std = ["alloc", "secp256kfun/std"]
serde = ["serde_crate", "secp256kfun/serde"]
libsecp_compat = ["secp256kfun/libsecp_compat"]
nightly = ["secp256kfun/nightly"]
proptest = ["secp256kfun/proptest"]
3 changes: 2 additions & 1 deletion secp256kfun/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ serde_crate = { package = "serde", version = "1.0", optional = true, default-fe
secp256kfun_k256_backend = { version = "1.0.0", features = ["expose-field"] }
secp256kfun_parity_backend = { version = "0.1.5", optional = true }
secp256k1 = { version = "0.20", optional = true, default-features = false }
proptest = { version = "0.10", optional = true }
proptest = { version = "1", optional = true }

[dev-dependencies]
serde_json = "1"
rand = { version = "0.8" }
lazy_static = "1.4"
sha2 = "0.9"
proptest = "1"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
secp256k1 = { default-features = false, version = "0.20", features = ["std"] }
Expand Down
2 changes: 1 addition & 1 deletion secp256kfun/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub extern crate secp256k1;
pub extern crate serde_crate as serde;
#[cfg(feature = "libsecp_compat")]
mod libsecp_compat;
#[cfg(feature = "proptest")]
#[cfg(any(feature = "proptest", test))]
pub mod proptest;
/// The main basepoint for secp256k1 as specified in [_SEC 2: Recommended Elliptic Curve Domain Parameters_] and used in Bitcoin.
///
Expand Down
2 changes: 1 addition & 1 deletion secp256kfun/src/marker/point_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/// ```
/// [`G`]: crate::G
/// [`Point<T,S,Z>`]: crate::Point
pub trait PointType: Sized + Clone + Copy {
pub trait PointType: Sized + Clone + Copy + 'static {
/// The point type returned from the negation of a point of this type.
type NegationType: Default;
}
Expand Down
2 changes: 1 addition & 1 deletion secp256kfun/src/marker/secrecy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
/// [`Point`s]: crate::Point
/// [`mark`]: crate::marker::Mark::mark
/// [_specialization_]: https://github.com/rust-lang/rust/issues/31844
pub trait Secrecy: Default + Clone + PartialEq + Copy {}
pub trait Secrecy: Default + Clone + PartialEq + Copy + 'static {}

/// Indicates that the value is secret and therefore makes core operations
/// executed on it to use _constant time_ versions of the operations.
Expand Down
2 changes: 1 addition & 1 deletion secp256kfun/src/marker/zero_choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct NonZero;
///
/// Note it is rarely useful to define a function over any `Z: ZeroChoice`.
/// This trait mostly just exists for consistency.
pub trait ZeroChoice {}
pub trait ZeroChoice: Default + Clone + PartialEq + Copy + 'static {}

impl ZeroChoice for Zero {}
impl ZeroChoice for NonZero {}
Expand Down
2 changes: 1 addition & 1 deletion secp256kfun/src/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ mod test {

#[test]
fn double_mul_spec_edgecase() {
// a random that took some time to track down.
// a random bug that took some time to track down.
let s = Scalar::<Secret, NonZero>::from_str(
"45941667583c8cfd65e01f696b1864c5c6a896a2722b6ebaddaf332a31ab42a9",
)
Expand Down
155 changes: 88 additions & 67 deletions secp256kfun/src/point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ crate::impl_fromstr_deserialize! {
mod test {
use super::*;
use crate::{g, G};
use proptest::prelude::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;

Expand All @@ -441,89 +442,109 @@ mod test {
}

macro_rules! operations_test {
(@binary $P:expr, $Q:expr) => {{
let p = $P;
let q = $Q;
let i = Point::zero();
expression_eq!([p] == [q]);
expression_eq!([q] == [p]);
expression_eq!([1 * p] == [q]);
expression_eq!([-1 * p] == [-q]);
expression_eq!([p - q] == [i]);
expression_eq!([i + p] == [q]);


if !p.is_zero() {
expression_eq!([p] != [i]);
expression_eq!([p + p] != [p]);
}

expression_eq!([-(p + p)] == [-q + -q]);
expression_eq!([p + p] == [2 * q]);
expression_eq!([p + q] == [2 * q]);
expression_eq!([q + p] == [2 * q]);
expression_eq!([p + p + p] == [3 * q]);
expression_eq!([-p - p - p] == [-3 * q]);
expression_eq!([42 * p + 1337 * p] == [1379 * q]);
expression_eq!([42 * p - 1337 * p] == [-1295 * q]);
let add_100_times = {
let p = p.clone().mark::<(Zero, Jacobian)>();
let i = g!(p - p);
assert_eq!(i, Point::zero());
(0..100).fold(i, |acc, _| g!(acc + p))
};

expression_eq!([add_100_times] == [100 * q]);
let undo = { (0..100).fold(add_100_times.clone(), |acc, _| g!(acc - p)) };
expression_eq!([undo] == [add_100_times - 100 * q]);
expression_eq!([undo] == [i]);
}};
($P:expr) => {{
let p = $P;
let i = Point::zero();

expression_eq!([p] == [p]);
expression_eq!([p] != [i]);
expression_eq!([p] == [p]);
expression_eq!([1 * p] == [p]);
expression_eq!([-1 * p] == [-p]);

expression_eq!([p - p] == [i]);
expression_eq!([i + p] == [p]);

expression_eq!([p + i] == [p]);
expression_eq!([i - p] == [-p]);
expression_eq!([p - i] == [p]);
expression_eq!([p + p] != [p]);
expression_eq!([0 * p] == [i]);
expression_eq!([-(p + p)] == [-p + -p]);
expression_eq!([p + p] == [2 * p]);
expression_eq!([p + p + p] == [3 * p]);
expression_eq!([-p - p - p] == [-3 * p]);

let add_100_times = {
let p = p.clone().mark::<(Zero, Jacobian)>();
let i = g!(p - p);
assert_eq!(i, Point::zero());
(0..100).fold(i, |acc, _| g!(acc + p))
};
let q = p.clone().mark::<(Normal, Public)>();
operations_test!(@binary p,q);
let q = p.clone().mark::<(Jacobian, Public)>();
operations_test!(@binary p,q);
let q = p.clone().mark::<(Normal, Secret)>();
operations_test!(@binary p,q);
let q = p.clone().mark::<(Jacobian, Secret)>();
operations_test!(@binary p,q);
}}
}

proptest! {
#[test]
fn operations_even_y(P in any::<Point<EvenY>>()) {
operations_test!(&P);
}

expression_eq!([add_100_times] == [100 * p]);
#[test]
fn operations_normal(P in any::<Point<Normal>>()) {
operations_test!(&P);
}

let undo = { (0..100).fold(add_100_times.clone(), |acc, _| g!(acc - p)) };
#[test]
fn operations_jacobian(P in any::<Point<Jacobian>>()) {
operations_test!(&P);
}

expression_eq!([undo] == [add_100_times - 100 * p]);
expression_eq!([undo] == [i]);
}};
}
#[test]
fn operations_normal_secret(P in any::<Point<Normal, Secret>>()) {
operations_test!(&P);
}

#[test]
fn operations() {
operations_test!(G.clone());
operations_test!(G.clone().mark::<Secret>());
operations_test!(G.clone().mark::<(Public, Jacobian)>());
operations_test!(G.clone().mark::<(Secret, Jacobian)>());
operations_test!(Point::random(&mut rand::thread_rng()).mark::<Secret>());
operations_test!(Point::random(&mut rand::thread_rng()).mark::<Public>());
let p = crate::op::scalar_mul_point(
&Scalar::random(&mut rand::thread_rng()).mark::<Secret>(),
G,
);
operations_test!(&p);
operations_test!(p.mark::<Public>())
}
#[test]
fn operations_jacobian_secret(P in any::<Point<Jacobian, Secret>>()) {
operations_test!(&P);
}

macro_rules! mixed_operations_test {
($P:expr, $Q:expr) => {{
let p = $P;
let q = $Q;
let i = Point::zero();
expression_eq!([p] == [q]);
expression_eq!([p + p + p] == [q + q + q]);
expression_eq!([p + q] == [q + p]);
expression_eq!([p - q] == [i]);
expression_eq!([p - q] == [q - p]);
expression_eq!([p - q - q] == [q - p - p]);
}};
}
#[test]
fn operations_normal_public_zero(P in any::<Point<Normal, Public, Zero>>()) {
operations_test!(&P);
}

#[test]
fn mixed_operations() {
mixed_operations_test!(G.clone(), G.clone().mark::<Jacobian>());
mixed_operations_test!(
G.clone().mark::<Secret>(),
G.clone().mark::<(Secret, Jacobian)>()
);
let p = g!(42 * G);
mixed_operations_test!(p, p);
mixed_operations_test!(p, p.mark::<Normal>());
mixed_operations_test!(p.mark::<Normal>(), p);
mixed_operations_test!(p.mark::<Normal>(), p);
mixed_operations_test!(p.mark::<Secret>(), p);
mixed_operations_test!(p, p.mark::<Secret>());
#[test]
fn operations_normal_secret_zero(P in any::<Point<Normal, Secret, Zero>>()) {
operations_test!(&P);
}

#[test]
fn operations_jacobian_public_zero(P in any::<Point<Jacobian, Public, Zero>>()) {
operations_test!(&P);
}

#[test]
fn operations_jacobian_secret_zero(P in any::<Point<Jacobian, Secret, Zero>>()) {
operations_test!(&P);
}
}

#[test]
Expand Down
114 changes: 91 additions & 23 deletions secp256kfun/src/proptest.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,102 @@
//! Functions used to generate test data for property-based testing with [`proptest`].
//! Implementations of [`Arbitrary`] for core types.
//!
//! [`proptest`]: https://github.com/altsysrq/proptest
use crate::{marker::*, Point, Scalar, G};
//! [`Arbitrary`]: proptest::arbitrary::Arbitrary

use crate::{marker::*, Point, Scalar, XOnly, G};
use ::proptest::prelude::*;

prop_compose! {
/// Generate a random `Scalar`.
pub fn scalar()(
bytes in any::<[u8; 32]>(),
) -> Scalar<Secret, Zero> {
Scalar::from_bytes_mod_order(bytes)
impl<S: Secrecy> Arbitrary for Scalar<S, NonZero> {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
prop_oneof![
// insert some pathological cases
1 => Just(Scalar::one().mark::<S>()),
1 => Just(Scalar::minus_one().mark::<S>()),
18 => any::<[u8;32]>().prop_map(|bytes| Scalar::from_bytes_mod_order(bytes).mark::<(S, NonZero)>().unwrap()),
].boxed()
}
}

impl<S: Secrecy> Arbitrary for Scalar<S, Zero> {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
prop_oneof![
1 => Just(Scalar::zero().mark::<S>()),
1 => Just(Scalar::one().mark::<(S, Zero)>()),
1 => Just(Scalar::minus_one().mark::<(S, Zero)>()),
27 => any::<[u8;32]>().prop_map(|bytes| Scalar::from_bytes_mod_order(bytes).mark::<S>()),
].boxed()
}
}

impl<S: Secrecy> Arbitrary for Point<Jacobian, S, NonZero> {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
any::<Scalar>()
.prop_map(|scalar| g!(scalar * G).mark::<S>())
.boxed()
}
}

impl<S: Secrecy> Arbitrary for Point<Normal, S, NonZero> {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
any::<Point<Jacobian, S>>()
.prop_map(|point| point.mark::<Normal>())
.boxed()
}
}

impl<S: Secrecy> Arbitrary for Point<EvenY, S, NonZero> {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
any::<Point<Normal, S>>()
.prop_map(|point| point.into_point_with_even_y().0.mark::<S>())
.boxed()
}
}

prop_compose! {
/// Generate a random, non-zero `Scalar`.
pub fn non_zero_scalar()(
bytes in any::<[u8; 32]>()
.prop_filter("Value cannot be zero",
|bytes| bytes != &[0u8; 32]),
) -> Scalar {
Scalar::from_bytes_mod_order(bytes).mark::<NonZero>().unwrap()
impl<S: Secrecy> Arbitrary for Point<Jacobian, S, Zero> {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
prop_oneof![
1 => Just(Point::zero().mark::<(Jacobian, S)>()),
9 => any::<Point<Jacobian,S>>().prop_map(|p| p.mark::<Zero>()),
]
.boxed()
}
}

impl<S: Secrecy> Arbitrary for Point<Normal, S, Zero> {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
prop_oneof![
1 => Just(Point::zero().mark::<S>()),
9 => any::<Point<Normal, S>>().prop_map(|p| p.mark::<Zero>())
]
.boxed()
}
}

prop_compose! {
/// Generate a random `Point`.
pub fn point()(
mut x in non_zero_scalar(),
) -> Point {
Point::from_scalar_mul(G, &mut x).mark::<Normal>()
impl Arbitrary for XOnly {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
any::<Point>().prop_map(|p| p.to_xonly()).boxed()
}
}
Loading