diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index bd6e7ad390..32054890db 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -62,6 +62,12 @@ jobs: cargo bench --bench merkle_tree -- --output-format bencher | tee -a ../../output.txt cd ../.. + - name: Benchmark console/types + run: | + cd console/types + cargo bench --bench group -- --output-format bencher | tee -a ../../output.txt + cd ../.. + - name: Benchmark curves run: | cd curves diff --git a/Cargo.lock b/Cargo.lock index c03b491735..535855bb94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2978,6 +2978,8 @@ dependencies = [ name = "snarkvm-console-types" version = "0.16.16" dependencies = [ + "criterion", + "snarkvm-console-network", "snarkvm-console-network-environment", "snarkvm-console-types-address", "snarkvm-console-types-boolean", diff --git a/console/types/Cargo.toml b/console/types/Cargo.toml index 3a0dafaf8f..b6c58870ad 100644 --- a/console/types/Cargo.toml +++ b/console/types/Cargo.toml @@ -6,6 +6,11 @@ description = "Console types for a decentralized virtual machine" license = "Apache-2.0" edition = "2021" +[[bench]] +name = "group" +path = "benches/group.rs" +harness = false + [dependencies.snarkvm-console-network-environment] path = "../network/environment" version = "=0.16.16" @@ -45,6 +50,13 @@ path = "./string" version = "=0.16.16" optional = true +[dev-dependencies.criterion] +version = "0.5.1" + +[dev-dependencies.snarkvm-console-network] +path = "../network" +version = "=0.16.16" + [features] default = [ "address", diff --git a/console/types/benches/group.rs b/console/types/benches/group.rs new file mode 100644 index 0000000000..97782f3881 --- /dev/null +++ b/console/types/benches/group.rs @@ -0,0 +1,84 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate criterion; + +use criterion::Criterion; +use snarkvm_console_network::{environment::prelude::*, Testnet3}; +use snarkvm_console_types::{Field, Group}; + +type CurrentNetwork = Testnet3; + +fn group_from_field(c: &mut Criterion) { + let rng = &mut TestRng::default(); + + c.bench_function("group_from_field", move |b| { + let fields: Vec> = (0..1000).map(|_| rng.gen()).collect(); + + b.iter(|| { + for field in &fields { + let group = Group::from_field(field); + let _ = std::hint::black_box(group); + } + }) + }); +} + +fn group_from_field_on_curve(c: &mut Criterion) { + let rng = &mut TestRng::default(); + + c.bench_function("group_from_field_on_curve", move |b| { + type Projective = ::Projective; + + let fields: Vec> = + (0..1000).map(|_| rng.gen::().to_affine().to_x_coordinate()).map(Field::new).collect(); + + b.iter(|| { + for field in &fields { + let group = Group::from_field(field); + let _ = std::hint::black_box(group); + } + }) + }); +} + +fn group_from_field_off_curve(c: &mut Criterion) { + let rng = &mut TestRng::default(); + + c.bench_function("group_from_field_off_curve", move |b| { + type Affine = ::Affine; + + let fields: Vec<_> = std::iter::repeat(()) + .map(|_| rng.gen::>()) + .filter(|&field| Affine::from_x_coordinate(*field, true).is_none()) + .take(1000) + .collect(); + + b.iter(|| { + for field in &fields { + let group = Group::from_field(field); + let _ = std::hint::black_box(group); + } + }) + }); +} + +criterion_group! { + name = group; + config = Criterion::default().sample_size(20); + targets = group_from_field, group_from_field_on_curve, group_from_field_off_curve +} + +criterion_main!(group); diff --git a/console/types/group/src/from_x_coordinate.rs b/console/types/group/src/from_x_coordinate.rs index 62a075b42a..f8be208f71 100644 --- a/console/types/group/src/from_x_coordinate.rs +++ b/console/types/group/src/from_x_coordinate.rs @@ -18,14 +18,11 @@ impl Group { /// Attempts to recover an affine group element from a given x-coordinate field element. /// For safety, the resulting point is always enforced to be on the curve and in the correct subgroup. pub fn from_x_coordinate(x_coordinate: Field) -> Result { - if let Some(point) = E::Affine::from_x_coordinate(*x_coordinate, true) { - if point.is_in_correct_subgroup_assuming_on_curve() { - return Ok(Self::new(point)); - } - } - if let Some(point) = E::Affine::from_x_coordinate(*x_coordinate, false) { - if point.is_in_correct_subgroup_assuming_on_curve() { - return Ok(Self::new(point)); + if let Some((p1, p2)) = E::Affine::pair_from_x_coordinate(*x_coordinate) { + for point in [p2, p1] { + if point.is_in_correct_subgroup_assuming_on_curve() { + return Ok(Self::new(point)); + } } } bail!("Failed to recover an affine group from an x-coordinate of {x_coordinate}") diff --git a/curves/src/templates/short_weierstrass_jacobian/affine.rs b/curves/src/templates/short_weierstrass_jacobian/affine.rs index 49799cb7ea..d5cbe730f5 100644 --- a/curves/src/templates/short_weierstrass_jacobian/affine.rs +++ b/curves/src/templates/short_weierstrass_jacobian/affine.rs @@ -148,6 +148,18 @@ impl AffineCurve for Affine

{ }) } + /// Attempts to construct both possible affine points given an x-coordinate. + /// Points are not guaranteed to be in the prime order subgroup. + /// + /// The affine points returned should be in lexicographically growing order. + /// + /// Calling this should be equivalent (but likely more performant) to + /// `(AffineCurve::from_x_coordinate(x, false), AffineCurve::from_x_coordinate(x, true))`. + #[inline] + fn pair_from_x_coordinate(x: Self::BaseField) -> Option<(Self, Self)> { + Self::from_x_coordinate(x, false).map(|p1| (p1, Self::new(p1.x, -p1.y, false))) + } + /// Attempts to construct an affine point given a y-coordinate. The /// point is not guaranteed to be in the prime order subgroup. /// diff --git a/curves/src/templates/short_weierstrass_jacobian/tests.rs b/curves/src/templates/short_weierstrass_jacobian/tests.rs index 623d7fc026..b8f94bbd23 100644 --- a/curves/src/templates/short_weierstrass_jacobian/tests.rs +++ b/curves/src/templates/short_weierstrass_jacobian/tests.rs @@ -29,6 +29,7 @@ pub const ITERATIONS: usize = 10; pub fn sw_tests(rng: &mut TestRng) { sw_curve_serialization_test::

(rng); sw_from_random_bytes::

(rng); + sw_from_x_coordinate::

(rng); } pub fn sw_curve_serialization_test(rng: &mut TestRng) { @@ -138,3 +139,23 @@ pub fn sw_from_random_bytes(rng: &mut TestRng) { } } } + +pub fn sw_from_x_coordinate(rng: &mut TestRng) { + for _ in 0..ITERATIONS { + let a = Projective::

::rand(rng); + let a = a.to_affine(); + { + let x = a.x; + + let a1 = Affine::

::from_x_coordinate(x, true).unwrap(); + let a2 = Affine::

::from_x_coordinate(x, false).unwrap(); + + assert!(a == a1 || a == a2); + + let (b2, b1) = Affine::

::pair_from_x_coordinate(x).unwrap(); + + assert_eq!(a1, b1); + assert_eq!(a2, b2); + } + } +} diff --git a/curves/src/templates/twisted_edwards_extended/affine.rs b/curves/src/templates/twisted_edwards_extended/affine.rs index 1be2c48122..dc8cf8d41f 100644 --- a/curves/src/templates/twisted_edwards_extended/affine.rs +++ b/curves/src/templates/twisted_edwards_extended/affine.rs @@ -148,6 +148,18 @@ impl AffineCurve for Affine

{ }) } + /// Attempts to construct both possible affine points given an x-coordinate. + /// Points are not guaranteed to be in the prime order subgroup. + /// + /// The affine points returned should be in lexicographically growing order. + /// + /// Calling this should be equivalent (but likely more performant) to + /// `(AffineCurve::from_x_coordinate(x, false), AffineCurve::from_x_coordinate(x, true))`. + #[inline] + fn pair_from_x_coordinate(x: Self::BaseField) -> Option<(Self, Self)> { + Self::from_x_coordinate(x, false).map(|p1| (p1, Self::new(p1.x, -p1.y, -p1.t))) + } + /// Attempts to construct an affine point given a y-coordinate. The /// point is not guaranteed to be in the prime order subgroup. /// diff --git a/curves/src/templates/twisted_edwards_extended/tests.rs b/curves/src/templates/twisted_edwards_extended/tests.rs index f8cee43f9d..e1ccbded0d 100644 --- a/curves/src/templates/twisted_edwards_extended/tests.rs +++ b/curves/src/templates/twisted_edwards_extended/tests.rs @@ -201,6 +201,11 @@ where let a2 = Affine::

::from_x_coordinate(x, false).unwrap(); assert!(a == a1 || a == a2); + + let (b2, b1) = Affine::

::pair_from_x_coordinate(x).unwrap(); + + assert_eq!(a1, b1); + assert_eq!(a2, b2); } { let y = a.y; diff --git a/curves/src/traits/group.rs b/curves/src/traits/group.rs index a913134f6b..47f8bdcc69 100644 --- a/curves/src/traits/group.rs +++ b/curves/src/traits/group.rs @@ -164,6 +164,15 @@ pub trait AffineCurve: /// largest y-coordinate be selected. fn from_x_coordinate(x: Self::BaseField, greatest: bool) -> Option; + /// Attempts to construct both possible affine points given an x-coordinate. + /// Points are not guaranteed to be in the prime order subgroup. + /// + /// The affine points returned should be in lexicographically growing order. + /// + /// Calling this should be equivalent (but likely more performant) to + /// `(AffineCurve::from_x_coordinate(x, false), AffineCurve::from_x_coordinate(x, true))`. + fn pair_from_x_coordinate(x: Self::BaseField) -> Option<(Self, Self)>; + /// Attempts to construct an affine point given a y-coordinate. The /// point is not guaranteed to be in the prime order subgroup. ///