Upstream GBP, divisor, circuit abstraction, and EC gadgets from FCMP++

This commit is contained in:
Luke Parker
2024-07-21 21:48:54 -04:00
parent d5205ce231
commit dcc26ecf33
33 changed files with 4663 additions and 1 deletions

View File

@@ -0,0 +1,23 @@
[package]
name = "generalized-bulletproofs-ec-gadgets"
version = "0.1.0"
description = "Gadgets for working with an embedded Elliptic Curve in a Generalized Bulletproofs circuit"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/ec-gadgets"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["bulletproofs", "circuit", "divisors"]
edition = "2021"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
generic-array = { version = "1", default-features = false, features = ["alloc"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false, features = ["std"] }
generalized-bulletproofs = { path = "../generalized-bulletproofs" }
generalized-bulletproofs-circuit-abstraction = { path = "../circuit-abstraction" }

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Luke Parker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,3 @@
# Generalized Bulletproofs Circuit Abstraction
A circuit abstraction around `generalized-bulletproofs`.

View File

@@ -0,0 +1,524 @@
use core::fmt;
use ciphersuite::{
group::ff::{Field, PrimeField, BatchInverter},
Ciphersuite,
};
use generalized_bulletproofs_circuit_abstraction::*;
use crate::*;
/// Parameters for a discrete logarithm proof.
pub trait DiscreteLogParameters {
/// The amount of bits used to represent a scalar.
type ScalarBits: ArrayLength;
/// The amount of x**i coefficients in a divisor.
///
/// This is the amount of points in a divisor (the amount of bits in a scalar, plus one) divided
/// by two.
type XCoefficients: ArrayLength;
/// The amount of x**i coefficients in a divisor, minus one.
type XCoefficientsMinusOne: ArrayLength;
/// The amount of y x**i coefficients in a divisor.
///
/// This is the amount of points in a divisor (the amount of bits in a scalar, plus one) divided
/// by two, minus two.
type YxCoefficients: ArrayLength;
}
/// A tabled generator for proving/verifying discrete logarithm claims.
#[derive(Clone)]
pub struct GeneratorTable<F: PrimeField, Parameters: DiscreteLogParameters>(
GenericArray<(F, F), Parameters::ScalarBits>,
);
impl<F: PrimeField, Parameters: DiscreteLogParameters> fmt::Debug
for GeneratorTable<F, Parameters>
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("GeneratorTable")
.field("x", &self.0[0].0)
.field("y", &self.0[0].1)
.finish_non_exhaustive()
}
}
impl<F: PrimeField, Parameters: DiscreteLogParameters> GeneratorTable<F, Parameters> {
/// Create a new table for this generator.
///
/// The generator is assumed to be well-formed and on-curve. This function may panic if it's not.
pub fn new(curve: &CurveSpec<F>, generator_x: F, generator_y: F) -> Self {
// mdbl-2007-bl
fn dbl<F: PrimeField>(a: F, x1: F, y1: F) -> (F, F) {
let xx = x1 * x1;
let w = a + (xx + xx.double());
let y1y1 = y1 * y1;
let r = y1y1 + y1y1;
let sss = (y1 * r).double().double();
let rr = r * r;
let b = x1 + r;
let b = (b * b) - xx - rr;
let h = (w * w) - b.double();
let x3 = h.double() * y1;
let y3 = (w * (b - h)) - rr.double();
let z3 = sss;
// Normalize from XYZ to XY
let z3_inv = z3.invert().unwrap();
let x3 = x3 * z3_inv;
let y3 = y3 * z3_inv;
(x3, y3)
}
let mut res = Self(GenericArray::default());
res.0[0] = (generator_x, generator_y);
for i in 1 .. Parameters::ScalarBits::USIZE {
let last = res.0[i - 1];
res.0[i] = dbl(curve.a, last.0, last.1);
}
res
}
}
/// A representation of the divisor.
///
/// The coefficient for x**1 is explicitly excluded as it's expected to be normalized to 1.
#[derive(Clone)]
pub struct Divisor<Parameters: DiscreteLogParameters> {
/// The coefficient for the `y` term of the divisor.
///
/// There is never more than one `y**i x**0` coefficient as the leading term of the modulus is
/// `y**2`. It's assumed the coefficient is non-zero (and present) as it will be for any divisor
/// exceeding trivial complexity.
pub y: Variable,
/// The coefficients for the `y**1 x**i` terms of the polynomial.
// This subtraction enforces the divisor to have at least 4 points which is acceptable.
// TODO: Double check these constants
pub yx: GenericArray<Variable, Parameters::YxCoefficients>,
/// The coefficients for the `x**i` terms of the polynomial, skipping x**1.
///
/// x**1 is skipped as it's expected to be normalized to 1, and therefore constant, in order to
/// ensure the divisor is non-zero (as necessary for the proof to be complete).
// Subtract 1 from the length due to skipping the coefficient for x**1
pub x_from_power_of_2: GenericArray<Variable, Parameters::XCoefficientsMinusOne>,
/// The constant term in the polynomial (alternatively, the coefficient for y**0 x**0).
pub zero: Variable,
}
/// A point, its discrete logarithm, and the divisor to prove it.
#[derive(Clone)]
pub struct PointWithDlog<Parameters: DiscreteLogParameters> {
/// The point which is supposedly the result of scaling the generator by the discrete logarithm.
pub point: (Variable, Variable),
/// The discrete logarithm, represented as coefficients of a polynomial of 2**i.
pub dlog: GenericArray<Variable, Parameters::ScalarBits>,
/// The divisor interpolating the relevant doublings of generator with the inverse of the point.
pub divisor: Divisor<Parameters>,
}
/// A struct containing a point used for the evaluation of a divisor.
///
/// Preprocesses and caches as much of the calculation as possible to minimize work upon reuse of
/// challenge points.
struct ChallengePoint<F: PrimeField, Parameters: DiscreteLogParameters> {
y: F,
yx: GenericArray<F, Parameters::YxCoefficients>,
x: GenericArray<F, Parameters::XCoefficients>,
p_0_n_0: F,
x_p_0_n_0: GenericArray<F, Parameters::YxCoefficients>,
p_1_n: F,
p_1_d: F,
}
impl<F: PrimeField, Parameters: DiscreteLogParameters> ChallengePoint<F, Parameters> {
fn new(
curve: &CurveSpec<F>,
// The slope between all of the challenge points
slope: F,
// The x and y coordinates
x: F,
y: F,
// The inversion of twice the y coordinate
// We accept this as an argument so that the caller can calculcate these with a batch inversion
inv_two_y: F,
) -> Self {
// Powers of x, skipping x**0
let divisor_x_len = Parameters::XCoefficients::USIZE;
let mut x_pows = GenericArray::default();
x_pows[0] = x;
for i in 1 .. divisor_x_len {
let last = x_pows[i - 1];
x_pows[i] = last * x;
}
// Powers of x multiplied by y
let divisor_yx_len = Parameters::YxCoefficients::USIZE;
let mut yx = GenericArray::default();
// Skips x**0
yx[0] = y * x;
for i in 1 .. divisor_yx_len {
let last = yx[i - 1];
yx[i] = last * x;
}
let x_sq = x.square();
let three_x_sq = x_sq.double() + x_sq;
let three_x_sq_plus_a = three_x_sq + curve.a;
let two_y = y.double();
// p_0_n_0 from `DivisorChallenge`
let p_0_n_0 = three_x_sq_plus_a * inv_two_y;
let mut x_p_0_n_0 = GenericArray::default();
// Since this iterates over x, which skips x**0, this also skips p_0_n_0 x**0
for (i, x) in x_pows.iter().take(divisor_yx_len).enumerate() {
x_p_0_n_0[i] = p_0_n_0 * x;
}
// p_1_n from `DivisorChallenge`
let p_1_n = two_y;
// p_1_d from `DivisorChallenge`
let p_1_d = (-slope * p_1_n) + three_x_sq_plus_a;
ChallengePoint { x: x_pows, y, yx, p_0_n_0, x_p_0_n_0, p_1_n, p_1_d }
}
}
// `DivisorChallenge` from the section `Discrete Log Proof`
fn divisor_challenge_eval<C: Ciphersuite, Parameters: DiscreteLogParameters>(
circuit: &mut Circuit<C>,
divisor: &Divisor<Parameters>,
challenge: &ChallengePoint<C::F, Parameters>,
) -> Variable {
// The evaluation of the divisor differentiated by y, further multiplied by p_0_n_0
// Differentation drops everything without a y coefficient, and drops what remains by a power
// of y
// (y**1 -> y**0, yx**i -> x**i)
// This aligns with p_0_n_1 from `DivisorChallenge`
let p_0_n_1 = {
let mut p_0_n_1 = LinComb::empty().term(challenge.p_0_n_0, divisor.y);
for (j, var) in divisor.yx.iter().enumerate() {
// This does not raise by `j + 1` as x_p_0_n_0 omits x**0
p_0_n_1 = p_0_n_1.term(challenge.x_p_0_n_0[j], *var);
}
p_0_n_1
};
// The evaluation of the divisor differentiated by x
// This aligns with p_0_n_2 from `DivisorChallenge`
let p_0_n_2 = {
// The coefficient for x**1 is 1, so 1 becomes the new zero coefficient
let mut p_0_n_2 = LinComb::empty().constant(C::F::ONE);
// Handle the new y coefficient
p_0_n_2 = p_0_n_2.term(challenge.y, divisor.yx[0]);
// Handle the new yx coefficients
for (j, yx) in divisor.yx.iter().enumerate().skip(1) {
// For the power which was shifted down, we multiply this coefficient
// 3 x**2 -> 2 * 3 x**1
let original_power_of_x = C::F::from(u64::try_from(j + 1).unwrap());
// `j - 1` so `j = 1` indexes yx[0] as yx[0] is the y x**1
// (yx omits y x**0)
let this_weight = original_power_of_x * challenge.yx[j - 1];
p_0_n_2 = p_0_n_2.term(this_weight, *yx);
}
// Handle the x coefficients
// We don't skip the first one as `x_from_power_of_2` already omits x**1
for (i, x) in divisor.x_from_power_of_2.iter().enumerate() {
// i + 2 as the paper expects i to start from 1 and be + 1, yet we start from 0
let original_power_of_x = C::F::from(u64::try_from(i + 2).unwrap());
// Still x[i] as x[0] is x**1
let this_weight = original_power_of_x * challenge.x[i];
p_0_n_2 = p_0_n_2.term(this_weight, *x);
}
p_0_n_2
};
// p_0_n from `DivisorChallenge`
let p_0_n = p_0_n_1 + &p_0_n_2;
// Evaluation of the divisor
// p_0_d from `DivisorChallenge`
let p_0_d = {
let mut p_0_d = LinComb::empty().term(challenge.y, divisor.y);
for (var, c_yx) in divisor.yx.iter().zip(&challenge.yx) {
p_0_d = p_0_d.term(*c_yx, *var);
}
for (i, var) in divisor.x_from_power_of_2.iter().enumerate() {
// This `i+1` is preserved, despite most not being as x omits x**0, as this assumes we
// start with `i=1`
p_0_d = p_0_d.term(challenge.x[i + 1], *var);
}
// Adding x effectively adds a `1 x` term, ensuring the divisor isn't 0
p_0_d.term(C::F::ONE, divisor.zero).constant(challenge.x[0])
};
// Calculate the joint numerator
// p_n from `DivisorChallenge`
let p_n = p_0_n * challenge.p_1_n;
// Calculate the joint denominator
// p_d from `DivisorChallenge`
let p_d = p_0_d * challenge.p_1_d;
// We want `n / d = o`
// `n / d = o` == `n = d * o`
// These are safe unwraps as they're solely done by the prover and should always be non-zero
let witness =
circuit.eval(&p_d).map(|p_d| (p_d, circuit.eval(&p_n).unwrap() * p_d.invert().unwrap()));
let (_l, o, n_claim) = circuit.mul(Some(p_d), None, witness);
circuit.equality(p_n, &n_claim.into());
o
}
/// A challenge to evaluate divisors with.
///
/// This challenge must be sampled after writing the commitments to the transcript. This challenge
/// is reusable across various divisors.
pub struct DiscreteLogChallenge<F: PrimeField, Parameters: DiscreteLogParameters> {
c0: ChallengePoint<F, Parameters>,
c1: ChallengePoint<F, Parameters>,
c2: ChallengePoint<F, Parameters>,
slope: F,
intercept: F,
}
/// A generator which has been challenged and is ready for use in evaluating discrete logarithm
/// claims.
pub struct ChallengedGenerator<F: PrimeField, Parameters: DiscreteLogParameters>(
GenericArray<F, Parameters::ScalarBits>,
);
/// Gadgets for proving the discrete logarithm of points on an elliptic curve defined over the
/// scalar field of the curve of the Bulletproof.
pub trait EcDlogGadgets<C: Ciphersuite> {
/// Sample a challenge for a series of discrete logarithm claims.
///
/// This must be called after writing the commitments to the transcript.
///
/// The generators are assumed to be non-empty. They are not transcripted. If your generators are
/// dynamic, they must be properly transcripted into the context.
///
/// May panic/have undefined behavior if an assumption is broken.
#[allow(clippy::type_complexity)]
fn discrete_log_challenge<T: Transcript, Parameters: DiscreteLogParameters>(
&self,
transcript: &mut T,
curve: &CurveSpec<C::F>,
generators: &[&GeneratorTable<C::F, Parameters>],
) -> (DiscreteLogChallenge<C::F, Parameters>, Vec<ChallengedGenerator<C::F, Parameters>>);
/// Prove this point has the specified discrete logarithm over the specified generator.
///
/// The discrete logarithm is not validated to be in a canonical form. The only guarantee made on
/// it is that it's a consistent representation of _a_ discrete logarithm (reuse won't enable
/// re-interpretation as a distinct discrete logarithm).
///
/// This does ensure the point is on-curve.
///
/// This MUST only be called with `Variable`s present within commitments.
///
/// May panic/have undefined behavior if an assumption is broken, or if passed an invalid
/// witness.
fn discrete_log<Parameters: DiscreteLogParameters>(
&mut self,
curve: &CurveSpec<C::F>,
point: PointWithDlog<Parameters>,
challenge: &DiscreteLogChallenge<C::F, Parameters>,
challenged_generator: &ChallengedGenerator<C::F, Parameters>,
) -> OnCurve;
}
impl<C: Ciphersuite> EcDlogGadgets<C> for Circuit<C> {
// This is part of `DiscreteLog` from `Discrete Log Proof`, specifically, the challenges and
// the calculations dependent solely on them
fn discrete_log_challenge<T: Transcript, Parameters: DiscreteLogParameters>(
&self,
transcript: &mut T,
curve: &CurveSpec<C::F>,
generators: &[&GeneratorTable<C::F, Parameters>],
) -> (DiscreteLogChallenge<C::F, Parameters>, Vec<ChallengedGenerator<C::F, Parameters>>) {
// Get the challenge points
// TODO: Implement a proper hash to curve
let (c0_x, c0_y) = loop {
let c0_x: C::F = transcript.challenge();
let Some(c0_y) =
Option::<C::F>::from(((c0_x.square() * c0_x) + (curve.a * c0_x) + curve.b).sqrt())
else {
continue;
};
// Takes the even y coordinate as to not be dependent on whatever root the above sqrt
// happens to returns
// TODO: Randomly select which to take
break (c0_x, if bool::from(c0_y.is_odd()) { -c0_y } else { c0_y });
};
let (c1_x, c1_y) = loop {
let c1_x: C::F = transcript.challenge();
let Some(c1_y) =
Option::<C::F>::from(((c1_x.square() * c1_x) + (curve.a * c1_x) + curve.b).sqrt())
else {
continue;
};
break (c1_x, if bool::from(c1_y.is_odd()) { -c1_y } else { c1_y });
};
// mmadd-1998-cmo
fn incomplete_add<F: PrimeField>(x1: F, y1: F, x2: F, y2: F) -> Option<(F, F)> {
if x1 == x2 {
None?
}
let u = y2 - y1;
let uu = u * u;
let v = x2 - x1;
let vv = v * v;
let vvv = v * vv;
let r = vv * x1;
let a = uu - vvv - r.double();
let x3 = v * a;
let y3 = (u * (r - a)) - (vvv * y1);
let z3 = vvv;
// Normalize from XYZ to XY
let z3_inv = Option::<F>::from(z3.invert())?;
let x3 = x3 * z3_inv;
let y3 = y3 * z3_inv;
Some((x3, y3))
}
let (c2_x, c2_y) = incomplete_add::<C::F>(c0_x, c0_y, c1_x, c1_y)
.expect("randomly selected points shared an x coordinate");
// We want C0, C1, C2 = -(C0 + C1)
let c2_y = -c2_y;
// Calculate the slope and intercept
// Safe invert as these x coordinates must be distinct due to passing the above incomplete_add
let slope = (c1_y - c0_y) * (c1_x - c0_x).invert().unwrap();
let intercept = c0_y - (slope * c0_x);
// Calculate the inversions for 2 c_y (for each c) and all of the challenged generators
let mut inversions = vec![C::F::ZERO; 3 + (generators.len() * Parameters::ScalarBits::USIZE)];
// Needed for the left-hand side eval
{
inversions[0] = c0_y.double();
inversions[1] = c1_y.double();
inversions[2] = c2_y.double();
}
// Perform the inversions for the generators
for (i, generator) in generators.iter().enumerate() {
// Needed for the right-hand side eval
for (j, generator) in generator.0.iter().enumerate() {
// `DiscreteLog` has weights of `(mu - (G_i.y + (slope * G_i.x)))**-1` in its last line
inversions[3 + (i * Parameters::ScalarBits::USIZE) + j] =
intercept - (generator.1 - (slope * generator.0));
}
}
for challenge_inversion in &inversions {
// This should be unreachable barring negligible probability
if challenge_inversion.is_zero().into() {
panic!("trying to invert 0");
}
}
let mut scratch = vec![C::F::ZERO; inversions.len()];
let _ = BatchInverter::invert_with_external_scratch(&mut inversions, &mut scratch);
let mut inversions = inversions.into_iter();
let inv_c0_two_y = inversions.next().unwrap();
let inv_c1_two_y = inversions.next().unwrap();
let inv_c2_two_y = inversions.next().unwrap();
let c0 = ChallengePoint::new(curve, slope, c0_x, c0_y, inv_c0_two_y);
let c1 = ChallengePoint::new(curve, slope, c1_x, c1_y, inv_c1_two_y);
let c2 = ChallengePoint::new(curve, slope, c2_x, c2_y, inv_c2_two_y);
// Fill in the inverted values
let mut challenged_generators = Vec::with_capacity(generators.len());
for _ in 0 .. generators.len() {
let mut challenged_generator = GenericArray::default();
for i in 0 .. Parameters::ScalarBits::USIZE {
challenged_generator[i] = inversions.next().unwrap();
}
challenged_generators.push(ChallengedGenerator(challenged_generator));
}
(DiscreteLogChallenge { c0, c1, c2, slope, intercept }, challenged_generators)
}
// `DiscreteLog` from `Discrete Log Proof`
fn discrete_log<Parameters: DiscreteLogParameters>(
&mut self,
curve: &CurveSpec<C::F>,
point: PointWithDlog<Parameters>,
challenge: &DiscreteLogChallenge<C::F, Parameters>,
challenged_generator: &ChallengedGenerator<C::F, Parameters>,
) -> OnCurve {
let PointWithDlog { divisor, dlog, point } = point;
// Ensure this is being safely called
let arg_iter = [point.0, point.1, divisor.y, divisor.zero];
let arg_iter = arg_iter.iter().chain(divisor.yx.iter());
let arg_iter = arg_iter.chain(divisor.x_from_power_of_2.iter());
let arg_iter = arg_iter.chain(dlog.iter());
for variable in arg_iter {
debug_assert!(
matches!(variable, Variable::CG { .. } | Variable::CH { .. } | Variable::V(_)),
"discrete log proofs requires all arguments belong to commitments",
);
}
// Check the point is on curve
let point = self.on_curve(curve, point);
// The challenge has already been sampled so those lines aren't necessary
// lhs from the paper, evaluating the divisor
let lhs_eval = LinComb::from(divisor_challenge_eval(self, &divisor, &challenge.c0)) +
&LinComb::from(divisor_challenge_eval(self, &divisor, &challenge.c1)) +
&LinComb::from(divisor_challenge_eval(self, &divisor, &challenge.c2));
// Interpolate the doublings of the generator
let mut rhs_eval = LinComb::empty();
// We call this `bit` yet it's not constrained to being a bit
// It's presumed to be yet may be malleated
for (bit, weight) in dlog.into_iter().zip(&challenged_generator.0) {
rhs_eval = rhs_eval.term(*weight, bit);
}
// Interpolate the output point
// intercept - (y - (slope * x))
// intercept - y + (slope * x)
// -y + (slope * x) + intercept
// EXCEPT the output point we're proving the discrete log for isn't the one interpolated
// Its negative is, so -y becomes y
// y + (slope * x) + intercept
let output_interpolation = LinComb::empty()
.constant(challenge.intercept)
.term(C::F::ONE, point.y)
.term(challenge.slope, point.x);
let output_interpolation_eval = self.eval(&output_interpolation);
let (_output_interpolation, inverse) =
self.inverse(Some(output_interpolation), output_interpolation_eval);
rhs_eval = rhs_eval.term(C::F::ONE, inverse);
self.equality(lhs_eval, &rhs_eval);
point
}
}

View File

@@ -0,0 +1,130 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![allow(non_snake_case)]
use generic_array::{typenum::Unsigned, ArrayLength, GenericArray};
use ciphersuite::{group::ff::Field, Ciphersuite};
use generalized_bulletproofs_circuit_abstraction::*;
mod dlog;
pub use dlog::*;
/// The specification of a short Weierstrass curve over the field `F`.
///
/// The short Weierstrass curve is defined via the formula `y**2 = x**3 + a*x + b`.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct CurveSpec<F> {
/// The `a` constant in the curve formula.
pub a: F,
/// The `b` constant in the curve formula.
pub b: F,
}
/// A struct for a point on a towered curve which has been confirmed to be on-curve.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct OnCurve {
pub(crate) x: Variable,
pub(crate) y: Variable,
}
impl OnCurve {
/// The variable for the x-coordinate.
pub fn x(&self) -> Variable {
self.x
}
/// The variable for the y-coordinate.
pub fn y(&self) -> Variable {
self.y
}
}
/// Gadgets for working with points on an elliptic curve defined over the scalar field of the curve
/// of the Bulletproof.
pub trait EcGadgets<C: Ciphersuite> {
/// Constrain an x and y coordinate as being on the specified curve.
///
/// The specified curve is defined over the scalar field of the curve this proof is performed
/// over, offering efficient arithmetic.
///
/// May panic if the prover and the point is not actually on-curve.
fn on_curve(&mut self, curve: &CurveSpec<C::F>, point: (Variable, Variable)) -> OnCurve;
/// Perform incomplete addition for a fixed point and an on-curve point.
///
/// `a` is the x and y coordinates of the fixed point, assumed to be on-curve.
///
/// `b` is a point prior checked to be on-curve.
///
/// `c` is a point prior checked to be on-curve, constrained to be the sum of `a` and `b`.
///
/// `a` and `b` are checked to have distinct x coordinates.
///
/// This function may panic if `a` is malformed or if the prover and `c` is not actually the sum
/// of `a` and `b`.
fn incomplete_add_fixed(&mut self, a: (C::F, C::F), b: OnCurve, c: OnCurve) -> OnCurve;
}
impl<C: Ciphersuite> EcGadgets<C> for Circuit<C> {
fn on_curve(&mut self, curve: &CurveSpec<C::F>, (x, y): (Variable, Variable)) -> OnCurve {
let x_eval = self.eval(&LinComb::from(x));
let (_x, _x_2, x2) =
self.mul(Some(LinComb::from(x)), Some(LinComb::from(x)), x_eval.map(|x| (x, x)));
let (_x, _x_2, x3) =
self.mul(Some(LinComb::from(x2)), Some(LinComb::from(x)), x_eval.map(|x| (x * x, x)));
let expected_y2 = LinComb::from(x3).term(curve.a, x).constant(curve.b);
let y_eval = self.eval(&LinComb::from(y));
let (_y, _y_2, y2) =
self.mul(Some(LinComb::from(y)), Some(LinComb::from(y)), y_eval.map(|y| (y, y)));
self.equality(y2.into(), &expected_y2);
OnCurve { x, y }
}
fn incomplete_add_fixed(&mut self, a: (C::F, C::F), b: OnCurve, c: OnCurve) -> OnCurve {
// Check b.x != a.0
{
let bx_lincomb = LinComb::from(b.x);
let bx_eval = self.eval(&bx_lincomb);
self.inequality(bx_lincomb, &LinComb::empty().constant(a.0), bx_eval.map(|bx| (bx, a.0)));
}
let (x0, y0) = (a.0, a.1);
let (x1, y1) = (b.x, b.y);
let (x2, y2) = (c.x, c.y);
let slope_eval = self.eval(&LinComb::from(x1)).map(|x1| {
let y1 = self.eval(&LinComb::from(b.y)).unwrap();
(y1 - y0) * (x1 - x0).invert().unwrap()
});
// slope * (x1 - x0) = y1 - y0
let x1_minus_x0 = LinComb::from(x1).constant(-x0);
let x1_minus_x0_eval = self.eval(&x1_minus_x0);
let (slope, _r, o) =
self.mul(None, Some(x1_minus_x0), slope_eval.map(|slope| (slope, x1_minus_x0_eval.unwrap())));
self.equality(LinComb::from(o), &LinComb::from(y1).constant(-y0));
// slope * (x2 - x0) = -y2 - y0
let x2_minus_x0 = LinComb::from(x2).constant(-x0);
let x2_minus_x0_eval = self.eval(&x2_minus_x0);
let (_slope, _x2_minus_x0, o) = self.mul(
Some(slope.into()),
Some(x2_minus_x0),
slope_eval.map(|slope| (slope, x2_minus_x0_eval.unwrap())),
);
self.equality(o.into(), &LinComb::empty().term(-C::F::ONE, y2).constant(-y0));
// slope * slope = x0 + x1 + x2
let (_slope, _slope_2, o) =
self.mul(Some(slope.into()), Some(slope.into()), slope_eval.map(|slope| (slope, slope)));
self.equality(o.into(), &LinComb::from(x1).term(C::F::ONE, x2).constant(x0));
OnCurve { x: x2, y: y2 }
}
}