Skip to content

Commit

Permalink
Optimize some group circuits.
Browse files Browse the repository at this point in the history
Refactor `Group::new` and `Group::from_xy_coordinates` to save constraints and
increase clarity. The enforcement of a point being in the (sub)group is now
delegated to a new circuit that does just that, and uses another new circuit to
enforce that a point is twice another (this differs from the circuit that
doubles a point).

This code has more separation between witness computation (console world) and
circuit synthesis (circuit world), which is necessary to achieve some
optimizations in the circuits.

There is a TODO for the witness computation in `Group::new`: there must be
already code to check whether a pair of coordinates is in the (sub)group. This
TODO is just at the console level; the circuit enforcement is already done in
this commit.
  • Loading branch information
acoglio committed Oct 15, 2023
1 parent 6758aaf commit 9053fa0
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 40 deletions.
26 changes: 26 additions & 0 deletions circuit/types/group/src/double.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,32 @@ impl<E: Environment> Double for Group<E> {
}
}

impl<E: Environment> Group<E> {
/// Enforce that double = 2 * self.
pub fn enforce_double(&self, double: &Group<E>) {
let a = Field::constant(console::Field::new(E::EDWARDS_A));
let two = Field::one().double();

// Compute xy, xx, yy, axx.
let xy = &self.x * &self.y;
let x2 = self.x.square();
let y2 = self.y.square();
let ax2 = &x2 * &a;

// Ensure double.x is the abscissa of the double of self.
// double.x * (ax^2 + y^2) = 2xy
let ax2_plus_y2 = &ax2 + &y2;
let two_xy = xy.double();
E::enforce(|| (&double.x, &ax2_plus_y2, two_xy));

// Ensure double.y is the ordinate of the double of self.
// double.y * (2 - (ax^2 + y^2)) = y^2 - ax^2
let y2_minus_a_x2 = y2 - ax2;
let two_minus_ax2_minus_y2 = two - ax2_plus_y2;
E::enforce(|| (&double.y, two_minus_ax2_minus_y2, y2_minus_a_x2));
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
9 changes: 3 additions & 6 deletions circuit/types/group/src/helpers/from_xy_coordinates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@ impl<E: Environment> Group<E> {
/// Initializes an affine group element from a given x- and y-coordinate field element.
/// For safety, the resulting point is always enforced to be on the curve and in the subgroup.
pub fn from_xy_coordinates(x: Field<E>, y: Field<E>) -> Self {
// Recover point from the `(x, y)` coordinates as a witness.
//
// Note: We use the **unchecked** ('console::Group::from_xy_coordinates_unchecked') variant
// here so that the recovery does not halt in witness mode, and subsequently, the point is
// enforced to be on the curve by injecting with `circuit::Group::new`.
witness!(|x, y| console::Group::from_xy_coordinates_unchecked(x, y))
let point = Self {x: x.into(), y: y.into()};
point.enforce_in_group();
point
}

/// Initializes an affine group element from a given x- and y-coordinate field element.
Expand Down
77 changes: 43 additions & 34 deletions circuit/types/group/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use snarkvm_circuit_environment::{assert_count, assert_output_mode, assert_scope

use console::AffineCurve;
use snarkvm_circuit_environment::prelude::*;
use snarkvm_circuit_environment::prelude::num_traits::zero;
use snarkvm_circuit_types_boolean::Boolean;
use snarkvm_circuit_types_field::Field;
use snarkvm_circuit_types_scalar::Scalar;
Expand Down Expand Up @@ -61,40 +62,18 @@ impl<E: Environment> Inject for Group<E> {
/// For safety, the resulting point is always enforced to be on the curve with constraints.
/// regardless of whether the y-coordinate was recovered.
fn new(mode: Mode, group: Self::Primitive) -> Self {
// Inject `point_inv` from the `(x_inv, y_inv)` coordinates as field elements.
let point_inv = {
// Compute the `(x_inv, y_inv)` coordinates from `(point / COFACTOR)`.
let (x_inv, y_inv) = group.div_by_cofactor().to_xy_coordinates();
// If the mode is `Public`, then allocate them privately, as we will allocate a `Public` point at the end.
match mode.is_public() {
true => Self { x: Field::new(Mode::Private, x_inv), y: Field::new(Mode::Private, y_inv) },
false => Self { x: Field::new(mode, x_inv), y: Field::new(mode, y_inv) },
}
};

// Ensure `point_inv` is on the curve.
point_inv.enforce_on_curve();

// Return the `point` as `point_inv * COFACTOR`.
let point = point_inv.mul_by_cofactor();

if mode.is_public() {
// Inject the point as `Mode::Public`.
let public_point = {
// Initialize the (x, y) coordinates of the point as field elements.
let (x, y) = group.to_xy_coordinates();
// Inject the `(x, y)` coordinates as field elements.
Self { x: Field::new(mode, x), y: Field::new(mode, y) }
};

// Ensure the `point == public_point`.
E::assert_eq(&point, &public_point);

// Return the public point.
public_point
} else {
point
}

// TODO: check if `group` is in the group (in console-land, not circuit-land)

// Allocate two new variables for the coordinates, with the mode and values given as inputs.
let x = Field::new(mode, group.to_x_coordinate());
let y = Field::new(mode, group.to_y_coordinate());
// Put the coordinates together into a point.
let point = Self {x, y};
// Enforce in the circuit that the point is in the group.
point.enforce_in_group();
// Return the point.
point
}
}

Expand All @@ -119,6 +98,36 @@ impl<E: Environment> Group<E> {
}
}

impl<E: Environment> Group<E> {
/// Enforce that self is in the group.
///
/// Each point in the group is the quadruple of some point on the curve,
/// where 'quadruple' refers to the cofactor 4 of the curve.
/// Thus, to enforce that a given point is in the group,
/// there must exist some point on the curve such that 4 times the latter yields the former.
/// The point on the curve is existentially quantified,
/// so the constraints introduce new coordinate variables for that point.
pub fn enforce_in_group(&self) {
// Postulate a point on the curve.
// The coordinate values are irrelevant; we pick 0 for both.
let point_x = Field::new(Mode::Private, zero());
let point_y = Field::new(Mode::Private, zero());
let point = Self {x: point_x, y: point_y};
point.enforce_on_curve();

// Postulate another point that is double of the point on the curve above.
// The coordinate values are irrelevant; we pick 0 for both.
let double_point_x = Field::new(Mode::Private, zero());
let double_point_y = Field::new(Mode::Private, zero());
let double_point = Self {x: double_point_x, y: double_point_y};
point.enforce_double(&double_point);

// Enforce that the input point (self) is double the double of the point on the curve,
// i.e. that it is 4 (cofactor) times the postulated point on the curve.
double_point.enforce_double(self);
}
}

#[cfg(console)]
impl<E: Environment> Eject for Group<E> {
type Primitive = console::Group<E::Network>;
Expand Down

0 comments on commit 9053fa0

Please sign in to comment.