diff --git a/Cargo.toml b/Cargo.toml index e6ce28f..34c17f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,8 @@ serde = { version = "1", default-features = false, optional = true } zeroize = { version = "1", default-features = false, optional = true } [dev-dependencies] +crypto-primitives = { workspace = true, features = ["crypto_bigint"] } +criterion = { workspace = true } proptest = { workspace = true } rand = { workspace = true, features = ["std_rng"] } @@ -47,6 +49,17 @@ ark_ff = ["ark_std", "num_bigint", "zeroize", "dep:ark-ff", "dep:ark-serialize"] crypto_bigint = ["dep:crypto-bigint", "crypto-bigint/alloc"] num_bigint = ["dep:num-bigint"] +[[bench]] +name = "boxed_monty_field_benches" +harness = false + +[[bench]] +name = "const_monty_field_benches" +harness = false + +[[bench]] +name = "monty_field_benches" +harness = false # # Workspace configuration @@ -64,10 +77,12 @@ license = "Apache-2.0" publish = false [workspace.dependencies] +crypto-primitives = { path = "." } crypto-primitives-proc-macros = { path = "proc-macros" } num-traits = { version = "0.2", default-features = false } proptest = { version = "1", default-features = false, features = ["alloc"] } rand = { version = "0.10.0-rc.1", default-features = false } +criterion = "0.7.0" [workspace.lints.rust] missing_docs = "allow" # FIXME diff --git a/benches/boxed_monty_field_benches.rs b/benches/boxed_monty_field_benches.rs new file mode 100644 index 0000000..7669118 --- /dev/null +++ b/benches/boxed_monty_field_benches.rs @@ -0,0 +1,27 @@ +#![allow(non_local_definitions)] +#![allow(clippy::eq_op)] + +mod field_ops_bench_common; + +use criterion::{Criterion, criterion_group, criterion_main}; +use crypto_bigint::{Odd, modular::BoxedMontyParams}; +use crypto_primitives::crypto_bigint_boxed_monty::BoxedMontyField; + +use crate::field_ops_bench_common::field_benchmarks; + +fn bench_config() -> BoxedMontyParams { + let modulus = crypto_bigint::BoxedUint::from_be_hex( + "0000000000000000000000000000000000860995AE68FC80E1B1BD1E39D54B33", + 256, + ) + .unwrap(); + let modulus = Odd::new(modulus).expect("modulus should be odd"); + BoxedMontyParams::new(modulus) +} + +pub fn boxed_monty_field_benches(c: &mut Criterion) { + field_benchmarks::(c, "Boxed Monty Field Arithmetic", &bench_config()); +} + +criterion_group!(benches, boxed_monty_field_benches); +criterion_main!(benches); diff --git a/benches/const_monty_field_benches.rs b/benches/const_monty_field_benches.rs new file mode 100644 index 0000000..397ae28 --- /dev/null +++ b/benches/const_monty_field_benches.rs @@ -0,0 +1,23 @@ +#![allow(non_local_definitions)] +#![allow(clippy::eq_op)] + +mod field_ops_bench_common; + +use criterion::{Criterion, criterion_group, criterion_main}; +use crypto_bigint::{U256, const_monty_params}; +use crypto_primitives::crypto_bigint_const_monty::ConstMontyField; + +use crate::field_ops_bench_common::field_benchmarks; + +const_monty_params!( + Params, + U256, + "0000000000000000000000000000000000860995AE68FC80E1B1BD1E39D54B33" +); + +pub fn const_monty_field_benches(c: &mut Criterion) { + field_benchmarks::>(c, "Const Monty Field Arithmetic", &()); +} + +criterion_group!(benches, const_monty_field_benches); +criterion_main!(benches); diff --git a/benches/field_ops_bench_common.rs b/benches/field_ops_bench_common.rs new file mode 100644 index 0000000..90d6792 --- /dev/null +++ b/benches/field_ops_bench_common.rs @@ -0,0 +1,240 @@ +#![allow(non_local_definitions)] +#![allow(clippy::eq_op)] + +use std::{ + hint::black_box, + ops::{Add, Div, Mul}, +}; + +use criterion::{AxisScale, BatchSize, BenchmarkId, Criterion, PlotConfiguration}; +use crypto_primitives::FromPrimitiveWithConfig; + +fn bench_random_field( + group: &mut criterion::BenchmarkGroup, + num: u64, + config: &F::Config, +) where + for<'a> &'a F: Add<&'a F>, + for<'a> &'a F: Mul<&'a F>, + for<'a> &'a F: Div<&'a F>, +{ + let field_elem = F::from_with_cfg(num, config); + let param = format!("Param = {}", num); + + group.bench_with_input( + BenchmarkId::new("Mul owned by owned", ¶m), + &field_elem, + |b, unop_elem| { + b.iter_batched( + || { + ( + vec![unop_elem.clone(); 10000], + vec![unop_elem.clone(); 10000], + ) + }, + |(lhs, rhs)| { + for (lhs, rhs) in lhs.into_iter().zip(rhs.into_iter()) { + let _ = black_box(lhs * rhs); + } + }, + BatchSize::SmallInput, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("Mul owned by ref", ¶m), + &field_elem, + |b, unop_elem| { + b.iter_batched( + || { + ( + vec![unop_elem.clone(); 10000], + vec![unop_elem.clone(); 10000], + ) + }, + |(lhs, rhs)| { + for (lhs, rhs) in lhs.into_iter().zip(rhs.into_iter()) { + let _ = black_box(lhs * &rhs); + } + }, + BatchSize::SmallInput, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("Mul ref by ref", ¶m), + &field_elem, + |b, unop_elem| { + b.iter(|| { + for _ in 0..10000 { + let _ = black_box(unop_elem * unop_elem); + } + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("Add owned to owned", ¶m), + &field_elem, + |b, unop_elem| { + b.iter_batched( + || { + ( + vec![unop_elem.clone(); 10000], + vec![unop_elem.clone(); 10000], + ) + }, + |(lhs, rhs)| { + for (lhs, rhs) in lhs.into_iter().zip(rhs.into_iter()) { + let _ = black_box(lhs + rhs); + } + }, + BatchSize::SmallInput, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("Add owned to ref", ¶m), + &field_elem, + |b, unop_elem| { + b.iter_batched( + || { + ( + vec![unop_elem.clone(); 10000], + vec![unop_elem.clone(); 10000], + ) + }, + |(lhs, rhs)| { + for (lhs, rhs) in lhs.into_iter().zip(rhs.into_iter()) { + let _ = black_box(lhs + &rhs); + } + }, + BatchSize::SmallInput, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("Add ref to ref", ¶m), + &field_elem, + |b, unop_elem| { + b.iter(|| { + for _ in 0..10000 { + let _ = black_box(unop_elem + unop_elem); + } + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("Div owned by owned", ¶m), + &field_elem, + |b, unop_elem| { + b.iter_batched( + || { + ( + vec![unop_elem.clone(); 10000], + vec![unop_elem.clone(); 10000], + ) + }, + |(lhs, rhs)| { + for (lhs, rhs) in lhs.into_iter().zip(rhs.into_iter()) { + let _ = black_box(lhs / rhs); + } + }, + BatchSize::SmallInput, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("Div owned by ref", ¶m), + &field_elem, + |b, unop_elem| { + b.iter_batched( + || { + ( + vec![unop_elem.clone(); 10000], + vec![unop_elem.clone(); 10000], + ) + }, + |(lhs, rhs)| { + for (lhs, rhs) in lhs.into_iter().zip(rhs.into_iter()) { + let _ = black_box(lhs / &rhs); + } + }, + BatchSize::SmallInput, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("Div ref by ref", ¶m), + &field_elem, + |b, unop_elem| { + b.iter(|| { + for _ in 0..10000 { + let _ = black_box(unop_elem / unop_elem); + } + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("Negation", ¶m), + &field_elem, + |b, unop_elem| { + b.iter_batched( + || vec![unop_elem.clone(); 10000], + |unop_elem| { + for x in unop_elem.into_iter() { + let _ = black_box(-x); + } + }, + BatchSize::SmallInput, + ); + }, + ); + + let v = vec![field_elem; 10]; + + group.bench_with_input(BenchmarkId::new("Sum", ¶m), &v, |b, v| { + b.iter(|| { + for _ in 0..10000 { + let _ = black_box(F::sum(v.iter())); + } + }); + }); + + group.bench_with_input(BenchmarkId::new("Product", ¶m), &v, |b, v| { + b.iter(|| { + for _ in 0..10000 { + let _ = black_box(F::product(v.iter())); + } + }); + }); +} + +pub fn field_benchmarks( + c: &mut Criterion, + name: &str, + config: &F::Config, +) where + for<'a> &'a F: Add<&'a F>, + for<'a> &'a F: Mul<&'a F>, + for<'a> &'a F: Div<&'a F>, +{ + let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); + + let mut group = c.benchmark_group(name); + group.plot_config(plot_config); + + bench_random_field::(&mut group, 695962179703_u64, config); + bench_random_field::(&mut group, 2345695962179703_u64, config); + bench_random_field::(&mut group, 111111111111111111_u64, config); + bench_random_field::(&mut group, 12345678124578658568_u64, config); + group.finish(); +} diff --git a/benches/monty_field_benches.rs b/benches/monty_field_benches.rs new file mode 100644 index 0000000..46d4488 --- /dev/null +++ b/benches/monty_field_benches.rs @@ -0,0 +1,27 @@ +#![allow(non_local_definitions)] +#![allow(clippy::eq_op)] + +mod field_ops_bench_common; + +use criterion::{Criterion, criterion_group, criterion_main}; +use crypto_bigint::{Odd, modular::MontyParams}; +use crypto_primitives::crypto_bigint_monty::MontyField; + +use crate::field_ops_bench_common::field_benchmarks; + +const LIMBS: usize = 4; + +fn bench_config() -> MontyParams { + let modulus = crypto_bigint::Uint::::from_be_hex( + "0000000000000000000000000000000000860995AE68FC80E1B1BD1E39D54B33", + ); + let modulus = Odd::new(modulus).expect("modulus should be odd"); + MontyParams::new(modulus) +} + +fn monty_field_benches(c: &mut Criterion) { + field_benchmarks::>(c, "Monty Field Arithmetic", &bench_config()); +} + +criterion_group!(benches, monty_field_benches); +criterion_main!(benches); diff --git a/src/field/crypto_bigint_boxed_monty.rs b/src/field/crypto_bigint_boxed_monty.rs index c3e3e21..25448df 100644 --- a/src/field/crypto_bigint_boxed_monty.rs +++ b/src/field/crypto_bigint_boxed_monty.rs @@ -163,12 +163,15 @@ impl Inv for BoxedMontyField { type Output = Option; fn inv(self) -> Self::Output { - let result = self.0.invert_vartime(); - if result.is_some().into() { - Some(Self(result.unwrap())) - } else { - None - } + Some(Self(Option::from(self.0.invert_vartime())?)) + } +} + +impl Inv for &BoxedMontyField { + type Output = Option; + + fn inv(self) -> Self::Output { + Some(BoxedMontyField(Option::from(self.0.invert_vartime())?)) } } @@ -180,13 +183,7 @@ impl Inv for BoxedMontyField { impl CheckedDiv for BoxedMontyField { #[allow(clippy::arithmetic_side_effects)] // False alert fn checked_div(&self, rhs: &Self) -> Option { - let inv = rhs.0.invert(); - if inv.is_none().into() { - return None; // Division by zero - } - // Safe to unwrap since we checked for None above - let inv = inv.unwrap(); - Some(Self(BoxedMontyForm::mul(&self.0, &inv))) + Some(self * rhs.inv()?) } } diff --git a/src/field/crypto_bigint_const_monty.rs b/src/field/crypto_bigint_const_monty.rs index 54142cc..81c4b2a 100644 --- a/src/field/crypto_bigint_const_monty.rs +++ b/src/field/crypto_bigint_const_monty.rs @@ -255,6 +255,15 @@ impl, const LIMBS: usize> Div<&Self> for ConstMontyField, const LIMBS: usize> Div for &ConstMontyField { + type Output = ConstMontyField; + + #[inline(always)] + fn div(self, rhs: Self) -> Self::Output { + self.checked_div(rhs).expect("Division by zero") + } +} + impl, const LIMBS: usize> Pow for ConstMontyField { type Output = Self; @@ -267,12 +276,7 @@ impl, const LIMBS: usize> Inv for ConstMontyField type Output = Option; fn inv(self) -> Self::Output { - let result = self.0.invert_vartime(); - if result.is_some().into() { - Some(Self(result.unwrap())) - } else { - None - } + Some(Self(Option::from(self.0.invert_vartime())?)) } } @@ -284,13 +288,7 @@ impl, const LIMBS: usize> Inv for ConstMontyField impl, const LIMBS: usize> CheckedDiv for ConstMontyField { #[allow(clippy::arithmetic_side_effects)] // False alert fn checked_div(&self, rhs: &Self) -> Option { - let inv = rhs.0.invert(); - if inv.is_none().into() { - return None; // Division by zero - } - // Safe to unwrap since we checked for None above - let inv = inv.unwrap(); - Some(Self(self.0 * inv)) + Some(self * rhs.inv()?) } } diff --git a/src/field/crypto_bigint_monty.rs b/src/field/crypto_bigint_monty.rs index f3f7da1..1ad3c2b 100644 --- a/src/field/crypto_bigint_monty.rs +++ b/src/field/crypto_bigint_monty.rs @@ -226,12 +226,15 @@ impl Inv for MontyField { type Output = Option; fn inv(self) -> Self::Output { - let result = self.0.invert_vartime(); - if result.is_some().into() { - Some(Self(result.unwrap())) - } else { - None - } + Some(Self(Option::from(self.0.invert_vartime())?)) + } +} + +impl Inv for &MontyField { + type Output = Option>; + + fn inv(self) -> Self::Output { + Some(MontyField(Option::from(self.0.invert_vartime())?)) } } @@ -243,13 +246,7 @@ impl Inv for MontyField { impl CheckedDiv for MontyField { #[allow(clippy::arithmetic_side_effects)] // False alert fn checked_div(&self, rhs: &Self) -> Option { - let inv = rhs.0.invert(); - if inv.is_none().into() { - return None; // Division by zero - } - // Safe to unwrap since we checked for None above - let inv = inv.unwrap(); - Some(Self(MontyForm::mul(&self.0, &inv))) + Some(self * rhs.inv()?) } }