Skip to content

Commit bcee29b

Browse files
authored
Merge pull request #2277 from bishopcheckmate/perf/fast-fail-group-from-x-coord
perf: optimize console's group from_x_coordinate
2 parents 84c4512 + 271354b commit bcee29b

File tree

10 files changed

+168
-8
lines changed

10 files changed

+168
-8
lines changed

.github/workflows/benchmarks.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ jobs:
6262
cargo bench --bench merkle_tree -- --output-format bencher | tee -a ../../output.txt
6363
cd ../..
6464
65+
- name: Benchmark console/types
66+
run: |
67+
cd console/types
68+
cargo bench --bench group -- --output-format bencher | tee -a ../../output.txt
69+
cd ../..
70+
6571
- name: Benchmark curves
6672
run: |
6773
cd curves

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

console/types/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ description = "Console types for a decentralized virtual machine"
66
license = "Apache-2.0"
77
edition = "2021"
88

9+
[[bench]]
10+
name = "group"
11+
path = "benches/group.rs"
12+
harness = false
13+
914
[dependencies.snarkvm-console-network-environment]
1015
path = "../network/environment"
1116
version = "=0.16.16"
@@ -45,6 +50,13 @@ path = "./string"
4550
version = "=0.16.16"
4651
optional = true
4752

53+
[dev-dependencies.criterion]
54+
version = "0.5.1"
55+
56+
[dev-dependencies.snarkvm-console-network]
57+
path = "../network"
58+
version = "=0.16.16"
59+
4860
[features]
4961
default = [
5062
"address",

console/types/benches/group.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright (C) 2019-2023 Aleo Systems Inc.
2+
// This file is part of the snarkVM library.
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at:
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#[macro_use]
16+
extern crate criterion;
17+
18+
use criterion::Criterion;
19+
use snarkvm_console_network::{environment::prelude::*, Testnet3};
20+
use snarkvm_console_types::{Field, Group};
21+
22+
type CurrentNetwork = Testnet3;
23+
24+
fn group_from_field(c: &mut Criterion) {
25+
let rng = &mut TestRng::default();
26+
27+
c.bench_function("group_from_field", move |b| {
28+
let fields: Vec<Field<CurrentNetwork>> = (0..1000).map(|_| rng.gen()).collect();
29+
30+
b.iter(|| {
31+
for field in &fields {
32+
let group = Group::from_field(field);
33+
let _ = std::hint::black_box(group);
34+
}
35+
})
36+
});
37+
}
38+
39+
fn group_from_field_on_curve(c: &mut Criterion) {
40+
let rng = &mut TestRng::default();
41+
42+
c.bench_function("group_from_field_on_curve", move |b| {
43+
type Projective = <CurrentNetwork as Environment>::Projective;
44+
45+
let fields: Vec<Field<CurrentNetwork>> =
46+
(0..1000).map(|_| rng.gen::<Projective>().to_affine().to_x_coordinate()).map(Field::new).collect();
47+
48+
b.iter(|| {
49+
for field in &fields {
50+
let group = Group::from_field(field);
51+
let _ = std::hint::black_box(group);
52+
}
53+
})
54+
});
55+
}
56+
57+
fn group_from_field_off_curve(c: &mut Criterion) {
58+
let rng = &mut TestRng::default();
59+
60+
c.bench_function("group_from_field_off_curve", move |b| {
61+
type Affine = <CurrentNetwork as Environment>::Affine;
62+
63+
let fields: Vec<_> = std::iter::repeat(())
64+
.map(|_| rng.gen::<Field<CurrentNetwork>>())
65+
.filter(|&field| Affine::from_x_coordinate(*field, true).is_none())
66+
.take(1000)
67+
.collect();
68+
69+
b.iter(|| {
70+
for field in &fields {
71+
let group = Group::from_field(field);
72+
let _ = std::hint::black_box(group);
73+
}
74+
})
75+
});
76+
}
77+
78+
criterion_group! {
79+
name = group;
80+
config = Criterion::default().sample_size(20);
81+
targets = group_from_field, group_from_field_on_curve, group_from_field_off_curve
82+
}
83+
84+
criterion_main!(group);

console/types/group/src/from_x_coordinate.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,11 @@ impl<E: Environment> Group<E> {
1818
/// Attempts to recover an affine group element from a given x-coordinate field element.
1919
/// For safety, the resulting point is always enforced to be on the curve and in the correct subgroup.
2020
pub fn from_x_coordinate(x_coordinate: Field<E>) -> Result<Self> {
21-
if let Some(point) = E::Affine::from_x_coordinate(*x_coordinate, true) {
22-
if point.is_in_correct_subgroup_assuming_on_curve() {
23-
return Ok(Self::new(point));
24-
}
25-
}
26-
if let Some(point) = E::Affine::from_x_coordinate(*x_coordinate, false) {
27-
if point.is_in_correct_subgroup_assuming_on_curve() {
28-
return Ok(Self::new(point));
21+
if let Some((p1, p2)) = E::Affine::pair_from_x_coordinate(*x_coordinate) {
22+
for point in [p2, p1] {
23+
if point.is_in_correct_subgroup_assuming_on_curve() {
24+
return Ok(Self::new(point));
25+
}
2926
}
3027
}
3128
bail!("Failed to recover an affine group from an x-coordinate of {x_coordinate}")

curves/src/templates/short_weierstrass_jacobian/affine.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ impl<P: Parameters> AffineCurve for Affine<P> {
148148
})
149149
}
150150

151+
/// Attempts to construct both possible affine points given an x-coordinate.
152+
/// Points are not guaranteed to be in the prime order subgroup.
153+
///
154+
/// The affine points returned should be in lexicographically growing order.
155+
///
156+
/// Calling this should be equivalent (but likely more performant) to
157+
/// `(AffineCurve::from_x_coordinate(x, false), AffineCurve::from_x_coordinate(x, true))`.
158+
#[inline]
159+
fn pair_from_x_coordinate(x: Self::BaseField) -> Option<(Self, Self)> {
160+
Self::from_x_coordinate(x, false).map(|p1| (p1, Self::new(p1.x, -p1.y, false)))
161+
}
162+
151163
/// Attempts to construct an affine point given a y-coordinate. The
152164
/// point is not guaranteed to be in the prime order subgroup.
153165
///

curves/src/templates/short_weierstrass_jacobian/tests.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub const ITERATIONS: usize = 10;
2929
pub fn sw_tests<P: ShortWeierstrassParameters>(rng: &mut TestRng) {
3030
sw_curve_serialization_test::<P>(rng);
3131
sw_from_random_bytes::<P>(rng);
32+
sw_from_x_coordinate::<P>(rng);
3233
}
3334

3435
pub fn sw_curve_serialization_test<P: ShortWeierstrassParameters>(rng: &mut TestRng) {
@@ -138,3 +139,23 @@ pub fn sw_from_random_bytes<P: ShortWeierstrassParameters>(rng: &mut TestRng) {
138139
}
139140
}
140141
}
142+
143+
pub fn sw_from_x_coordinate<P: ShortWeierstrassParameters>(rng: &mut TestRng) {
144+
for _ in 0..ITERATIONS {
145+
let a = Projective::<P>::rand(rng);
146+
let a = a.to_affine();
147+
{
148+
let x = a.x;
149+
150+
let a1 = Affine::<P>::from_x_coordinate(x, true).unwrap();
151+
let a2 = Affine::<P>::from_x_coordinate(x, false).unwrap();
152+
153+
assert!(a == a1 || a == a2);
154+
155+
let (b2, b1) = Affine::<P>::pair_from_x_coordinate(x).unwrap();
156+
157+
assert_eq!(a1, b1);
158+
assert_eq!(a2, b2);
159+
}
160+
}
161+
}

curves/src/templates/twisted_edwards_extended/affine.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ impl<P: Parameters> AffineCurve for Affine<P> {
148148
})
149149
}
150150

151+
/// Attempts to construct both possible affine points given an x-coordinate.
152+
/// Points are not guaranteed to be in the prime order subgroup.
153+
///
154+
/// The affine points returned should be in lexicographically growing order.
155+
///
156+
/// Calling this should be equivalent (but likely more performant) to
157+
/// `(AffineCurve::from_x_coordinate(x, false), AffineCurve::from_x_coordinate(x, true))`.
158+
#[inline]
159+
fn pair_from_x_coordinate(x: Self::BaseField) -> Option<(Self, Self)> {
160+
Self::from_x_coordinate(x, false).map(|p1| (p1, Self::new(p1.x, -p1.y, -p1.t)))
161+
}
162+
151163
/// Attempts to construct an affine point given a y-coordinate. The
152164
/// point is not guaranteed to be in the prime order subgroup.
153165
///

curves/src/templates/twisted_edwards_extended/tests.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ where
201201
let a2 = Affine::<P>::from_x_coordinate(x, false).unwrap();
202202

203203
assert!(a == a1 || a == a2);
204+
205+
let (b2, b1) = Affine::<P>::pair_from_x_coordinate(x).unwrap();
206+
207+
assert_eq!(a1, b1);
208+
assert_eq!(a2, b2);
204209
}
205210
{
206211
let y = a.y;

curves/src/traits/group.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ pub trait AffineCurve:
164164
/// largest y-coordinate be selected.
165165
fn from_x_coordinate(x: Self::BaseField, greatest: bool) -> Option<Self>;
166166

167+
/// Attempts to construct both possible affine points given an x-coordinate.
168+
/// Points are not guaranteed to be in the prime order subgroup.
169+
///
170+
/// The affine points returned should be in lexicographically growing order.
171+
///
172+
/// Calling this should be equivalent (but likely more performant) to
173+
/// `(AffineCurve::from_x_coordinate(x, false), AffineCurve::from_x_coordinate(x, true))`.
174+
fn pair_from_x_coordinate(x: Self::BaseField) -> Option<(Self, Self)>;
175+
167176
/// Attempts to construct an affine point given a y-coordinate. The
168177
/// point is not guaranteed to be in the prime order subgroup.
169178
///

0 commit comments

Comments
 (0)