Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complete add for AffineGagdet + fix serialization to field elements #172

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions algebra/src/to_field_vec.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
use crate::{
fields::{
Field, FpParameters, PrimeField,
},
curves::{
Curve,
models::{SWModelParameters, TEModelParameters},
short_weierstrass_jacobian::Jacobian,
short_weierstrass_projective::Projective,
twisted_edwards_extended::TEExtended,
}, ToBits,
};
use crate::{fields::{
Field, FpParameters, PrimeField,
}, curves::{
Curve,
models::{SWModelParameters, TEModelParameters},
short_weierstrass_jacobian::Jacobian,
short_weierstrass_projective::Projective,
twisted_edwards_extended::TEExtended,
}, ToBits, Group};

type Error = Box<dyn std::error::Error>;

Expand Down Expand Up @@ -72,12 +69,13 @@ impl<M: SWModelParameters, ConstraintF: Field> ToConstraintField<ConstraintF> fo
x_fe.extend_from_slice(&y_fe);
Ok(x_fe)
} else {
// Otherwise, return the coordinates of the infinity representation in Jacobian
let mut x_fe = self.x.to_field_elements()?;
let y_fe = self.y.to_field_elements()?;
// Otherwise, serialize the point as the affine point x=0, y=0,
// which is for sure not on the curve unless b=0 (which should never happen in our case
// as if b=0 then the curve has non-prime order).
debug_assert!(!M::COEFF_B.is_zero());
DDT92 marked this conversation as resolved.
Show resolved Hide resolved
let mut x_fe = M::BaseField::zero().to_field_elements()?;
let y_fe = M::BaseField::zero().to_field_elements()?;
x_fe.extend_from_slice(&y_fe);
let z_fe = self.z.to_field_elements()?;
x_fe.extend_from_slice(&z_fe);
Ok(x_fe)
}
}
Expand All @@ -96,12 +94,13 @@ impl<M: SWModelParameters, ConstraintF: Field> ToConstraintField<ConstraintF> fo
x_fe.extend_from_slice(&y_fe);
Ok(x_fe)
} else {
// Otherwise, return the coordinates of the infinity representation in Projective
let mut x_fe = self.x.to_field_elements()?;
let y_fe = self.y.to_field_elements()?;
// Otherwise, serialize the point as the affine point x=0, y=0,
// which is for sure not on the curve unless b=0 (which should never happen in our case
// as if b=0 then the curve has non-prime order).
DDT92 marked this conversation as resolved.
Show resolved Hide resolved
debug_assert!(!M::COEFF_B.is_zero());
let mut x_fe = M::BaseField::zero().to_field_elements()?;
let y_fe = M::BaseField::zero().to_field_elements()?;
x_fe.extend_from_slice(&y_fe);
let z_fe = self.z.to_field_elements()?;
x_fe.extend_from_slice(&z_fe);
Ok(x_fe)
}
}
Expand Down
57 changes: 56 additions & 1 deletion r1cs/gadgets/std/src/fields/fp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use r1cs_core::{
};

use std::borrow::Borrow;
use std::convert::TryInto;

use crate::{boolean::AllocatedBit, prelude::*, Assignment};
use crate::{boolean::AllocatedBit, prelude::*, Assignment, FromGadget};

#[derive(Debug)]
pub struct FpGadget<F: PrimeField> {
Expand Down Expand Up @@ -125,6 +126,60 @@ impl<F: PrimeField> FpGadget<F> {
}
}

impl<F: PrimeField> TryInto<Boolean> for FpGadget<F> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's write a comment saying explicitly that this is intended to be used when we are sure that the 'self' FpGadget cannot have other values than 0 or 1 (for instance, because enforced by other constraints); i.e. this function doesn't explicitly enforce 'self' being 0 or 1 with the usual Boolean constraint.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. It was not completely what this function should do.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will add a detailed comment

type Error = SynthesisError;

fn try_into(self) -> Result<Boolean, Self::Error> {

let variable = match self.get_variable() {
Var(var) => var,
LC(_) => Err(SynthesisError::Other(String::from("cannot convert linear combination of variables to Boolean variable")))?
};
let value = self.get_value().map(|val| if val.is_zero() {
Ok(Some(false))
} else if val.is_one() {
Ok(Some(true))
} else {
Err(SynthesisError::Other(String::from("converting field element other than 0 or 1 to Boolean")))
}).unwrap_or(Ok(None))?;
let alloc_bit = AllocatedBit{
variable,
value,
};
Ok(Boolean::from(alloc_bit))
}
}

impl<F: PrimeField> FromGadget<Boolean, F> for FpGadget<F> {
fn from<CS: ConstraintSystemAbstract<F>>(bit: Boolean, mut cs: CS) -> Result<Self, SynthesisError> {
if bit.is_constant() {
return if bit.get_value().unwrap() {
Self::one(cs.ns(|| "alloc constant 1"))
} else {
Self::zero(cs.ns(|| "alloc constant 0"))
}
}

let alloc_bit = match bit {
Boolean::Is(alloc_bit) | Boolean::Not(alloc_bit) => alloc_bit,
_ => unreachable!(),
};

let variable = ConstraintVar::<F>::from(alloc_bit.get_variable());
let value = bit.get_value().map(|bit_val| if bit_val {
F::one()
} else {
F::zero()
});

Ok(
Self{
value,
variable,
})
}
}

impl<F: PrimeField> FieldGadget<F, F> for FpGadget<F> {
type Variable = ConstraintVar<F>;

Expand Down
5 changes: 4 additions & 1 deletion r1cs/gadgets/std/src/fields/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::convert::TryInto;
// use std::ops::{Mul, MulAssign};
use algebra::{BitIterator, Field};
use r1cs_core::{ConstraintSystemAbstract, SynthesisError};
use std::fmt::Debug;

use crate::{prelude::*, Assignment};
use crate::{prelude::*, Assignment, FromGadget};

pub mod cmp;
pub mod fp;
Expand All @@ -23,6 +24,8 @@ pub trait FieldGadget<F: Field, ConstraintF: Field>:
+ TwoBitLookupGadget<ConstraintF, TableConstant = F>
+ ThreeBitCondNegLookupGadget<ConstraintF, TableConstant = F>
+ Debug
+ TryInto<Boolean, Error=SynthesisError>
+ FromGadget<Boolean, ConstraintF>
{
type Variable: Clone + Debug;

Expand Down
33 changes: 20 additions & 13 deletions r1cs/gadgets/std/src/fields/nonnative/nonnative_field_gadget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,16 @@ use num_bigint::BigUint;
use num_traits::{One, Zero};

use crate::fields::nonnative::NonNativeFieldParams;
use crate::{
fields::fp::FpGadget,
fields::nonnative::{
nonnative_field_mul_result_gadget::NonNativeFieldMulResultGadget,
params::get_params,
reduce::Reducer,
},
fields::FieldGadget,
ceil_log_2,
prelude::*,
to_field_gadget_vec::ToConstraintFieldGadget,
Assignment,
};
use crate::{fields::fp::FpGadget, fields::nonnative::{
nonnative_field_mul_result_gadget::NonNativeFieldMulResultGadget,
params::get_params,
reduce::Reducer,
}, fields::FieldGadget, ceil_log_2, prelude::*, to_field_gadget_vec::ToConstraintFieldGadget, Assignment, FromGadget};
use r1cs_core::{ConstraintSystemAbstract, SynthesisError};
use std::cmp::max;
use std::marker::PhantomData;
use std::{borrow::Borrow, vec, vec::Vec};
use std::convert::TryInto;

#[derive(Debug, Eq, PartialEq)]
#[must_use]
Expand Down Expand Up @@ -1008,6 +1001,20 @@ impl<SimulationF: PrimeField, ConstraintF: PrimeField>
* The high-level functions for arithmetic mod p: Implementation of FieldGadget
*
* *****************************************************************************/
impl<SimulationF: PrimeField, ConstraintF: PrimeField> TryInto<Boolean> for NonNativeFieldGadget<SimulationF, ConstraintF> {
type Error = SynthesisError;

fn try_into(self) -> Result<Boolean, Self::Error> {
unimplemented!()
}
}

impl<SimulationF: PrimeField, ConstraintF: PrimeField> FromGadget<Boolean, ConstraintF> for NonNativeFieldGadget<SimulationF, ConstraintF> {
fn from<CS: ConstraintSystemAbstract<ConstraintF>>(other: Boolean, cs: CS) -> Result<Self, SynthesisError> {
unimplemented!()
}
}

impl<SimulationF: PrimeField, ConstraintF: PrimeField> FieldGadget<SimulationF, ConstraintF>
for NonNativeFieldGadget<SimulationF, ConstraintF>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ where
/// Compute 2 * self + other.
/// Incomplete, safe, addition: neither `self` nor `other` can be the neutral
/// element, and other != ±self.
// // ToDo: evaluate if it may be worthy to make it complete
pub fn double_and_add<CS: ConstraintSystemAbstract<ConstraintF>>(
&self,
cs: CS,
Expand Down Expand Up @@ -333,8 +334,8 @@ where
#[inline]
fn zero<CS: ConstraintSystemAbstract<ConstraintF>>(mut cs: CS) -> Result<Self, SynthesisError> {
Ok(Self::new(
F::zero(cs.ns(|| "zero"))?,
F::one(cs.ns(|| "one"))?,
F::zero(cs.ns(|| "x=zero"))?,
F::zero(cs.ns(|| "y=zero"))?,
Boolean::constant(true),
))
}
Expand All @@ -348,18 +349,120 @@ where
}

#[inline]
/// Incomplete, safe, addition: neither `self` nor `other` can be the neutral
/// element.
// Algorithm for complete addition ported in R1CS from zcash: https://github.com/ebfull/halo/blob/master/src/gadgets/ecc.rs#L357.
// The algorithm from zcash works only with Weierstrass curves y^2 = x^3+b, the algorithm here
// is modified to work with generic curves y^2 = x^3 +ax + b
fn add<CS: ConstraintSystemAbstract<ConstraintF>>(
&self,
cs: CS,
mut cs: CS,
other: &Self,
) -> Result<Self, SynthesisError> {
self.add_internal(cs, other, true)
let zero = F::zero(cs.ns(|| "alloc constant 0"))?;
let is_same_x = F::alloc(cs.ns(|| "alloc x1 == x2"),
|| Ok(
if self.x.get_value().get()? == other.x.get_value().get()? {
P::BaseField::one()
} else {
P::BaseField::zero()
})
)?;
let not_is_same_x = is_same_x.negate(cs.ns(|| "-is_same_x"))?.add_constant(cs.ns(|| "1 - is_same_x"), &P::BaseField::one())?;

let x2_minus_x1 = other.x.sub(cs.ns(|| "x2 - x1"), &self.x)?;
let inv = F::alloc(cs.ns(|| "alloc (x2-x1)^-1"), ||
Ok(
x2_minus_x1.get_value().get()?.inverse().unwrap_or(P::BaseField::one())
)
)?;

// enforce that is_same_x = 0 if x2 != x1
x2_minus_x1.mul_equals(cs.ns(|| "enforce (x2-x1)*is_same_x = 0"), &is_same_x, &zero)?;
// enforce that is_same_x = 1 if x2 == x1
x2_minus_x1.mul_equals(cs.ns(|| "enforce (x2-x1)*(x2-x1)^-1 = 1-is_same_x"), &inv, &not_is_same_x)?;

// compute lambda_diff = (y2-y1)/(x2-x1+is_same_x), the value for lambda to be used in case x1 != x2
let y2_minus_y1 = other.y.sub(cs.ns(|| "y2 - y1"), &self.y)?;
let x2_minus_x1_plus_x_same = x2_minus_x1.add(cs.ns(|| "x2 - x1 + x_is_same"), &is_same_x)?;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be
let x2_minus_x1_plus_is_same_x = x2_minus_x1.add(cs.ns(|| "x2 - x1 + is_same_x").

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks

let lambda_diff = F::alloc(cs.ns(|| "alloc lambda_diff"), || Ok(
y2_minus_y1.get_value().get()?*x2_minus_x1_plus_x_same.get_value().get()?.inverse().unwrap()
// safe to unwrap inverse as x2_minus_x1_plus_x_same cannot be 0 by construction
)
)?;
lambda_diff.mul_equals(cs.ns(|| "enforce lambda_diff = (y2-y1) div (x2-x1+is_same_x"), &x2_minus_x1_plus_x_same, &y2_minus_y1)?;

// compute lambda_same = (3x1^2+a)/(2y1), the value for lambda to be used in case x1 = x2.
// in case y1 = 0, which happens only when self is the identity, the constraint
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not true for a general curve.
The points with y-coordinate equal to 0 are the zero point plus the points with order 2.
In the case of Tweedle (a=0, b not a cube), these points are not defined over the prime field. But, if you have a general curve (and the algorithm looks to be general since we do not assume a = 0) then the number of points can be a multiple of 2. In this last case you have points of order 2 and then non-trivial points with y = 0.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation by @UlrichHaboeck75 This is not a problem, because the witness points are always chosen in a prime order subgroup of the elliptic curve.
Anyway it could be specified in the comment.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I modify the comment to specify that in our case we consider only curves with prime order, and in such curves there cannot be other oints with y=0 besides the point at infinity

// 2y1*lambda_same = 3x1^2+a cannot be satisfied, unless a == 0.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean that it cannot be satisfied?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean that when self is the identity, then x1=0 and y1=0, therefore there exists no value for lambda_same which would satisfy the constraint 2y1*lambda_same = 3x1^2+a, unless a=0. I will try to make the comment clearer.

// Therefore, we modify the constraint as (2y1+self.infinity)*lambda_same = 3x1^2+a, which
// allows to satisfy the constraint when y1=0 for an arbitrary value of a
let infinity_flag_to_field = F::from(self.infinity, cs.ns(|| "self infinity to field element"))?;
let two_y1_plus_infinity_flag = self.y.double(cs.ns(|| "2y1"))?.add(cs.ns(|| "2y1+self.infinity"), &infinity_flag_to_field)?;
let three_times_x1_square_plus_a = self.x.square(cs.ns(|| "x1^2"))?.mul_by_constant(cs.ns(|| "3x1^2"), &P::BaseField::from(3u128))?.add_constant(cs.ns(|| "3x1^2+a"), &P::COEFF_A)?;
let lambda_same = F::alloc(cs.ns(|| "alloc lambda_same"), || Ok(
three_times_x1_square_plus_a.get_value().get()?*two_y1_plus_infinity_flag.get_value().get()?.inverse().unwrap_or(P::BaseField::one())
))?;
lambda_same.mul_equals(cs.ns(|| "enforce lambda_same = (3x1^2+a) div 2y1"), &two_y1_plus_infinity_flag, &three_times_x1_square_plus_a)?;

// set lambda to either lambda_same or lambda_diff depending on is_same_x flag
let lambda = F::alloc(cs.ns(|| "alloc lambda"), || Ok(
if is_same_x.get_value().get()?.is_one() {
lambda_same.get_value().get()?
} else {
lambda_diff.get_value().get()?
}
))?;
let lambda_minus_diff = lambda.sub(cs.ns(|| "lambda - lambda_diff"), &lambda_diff)?;
let lambda_same_minus_diff = lambda_same.sub(cs.ns(|| "lambda_same - lambda_diff"), &lambda_diff)?;
lambda_same_minus_diff.mul_equals(cs.ns(|| "enforce (lambda-lambda_diff) = is_same_x*(lambda_same-lambda_diff)"), &is_same_x, &lambda_minus_diff)?;

// x3 = lambda^2-x1-x2
let x1_plus_x2 = self.x.add(cs.ns(|| "x1+x2"), &other.x)?;
let x3 = lambda.square(cs.ns(|| "lambda^2"))?.sub(cs.ns(|| "x3=lambda^2-x1-x2"), &x1_plus_x2)?;
// y3 = lambda*(x1-x3)-y1
let x1_minus_x3 = self.x.sub(cs.ns(|| "x1-x3"), &x3)?;
let y3 = lambda.mul(cs.ns(|| "lambda*(x1-x3)"), &x1_minus_x3)?.sub(cs.ns(|| "y3=lambda*(x1-x3)-y1"), &self.y)?;

// compute a flag is_sum_zero which is true iff self+other == identity
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is not precise: two points are opposite if and only if the x-coordinate is the same and the y-coordinate is the opposite.
The piece of code below is correct.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue of the comment does not seem clear to me, the comment just state that is_sum_zero = self + other == identity without delving into the relationship between the coordinates of the 2 points when this happens.

let y1_plus_y2 = self.y.add(cs.ns(|| "y1+y2"), &other.y)?;
let is_sum_zero = F::alloc(cs.ns(|| "alloc self+other == 0"), || Ok(
if y1_plus_y2.get_value().get()?.is_zero() {
is_same_x.get_value().get()?
} else {
P::BaseField::zero()
}
))?;
// enforce sum_is_zero == 0 if y1+y2 != 0, that is if the sum is not the identity
y1_plus_y2.mul_equals(cs.ns(|| "enforce (y1+y2)*sum_is_zero=0"), &is_sum_zero, &zero)?;
let same_x_minus_sum_zero = is_same_x.sub(cs.ns(|| "is_same_x-sum_is_zero"), &is_sum_zero)?;
let inv = F::alloc(cs.ns(|| "alloc (y1+y2)^-1"), || Ok(
// set inv to is_same_x if y1+y2 !=0, otherwise we can set it to an arbitrary as it has no impact on the constraint
is_same_x.get_value().get()?*y1_plus_y2.get_value().get()?.inverse().unwrap_or(P::BaseField::one())
))?;

// enforce sum_is_zero == is_same_x if y1+y2 == 0, as in this case the sum is the identity iff the x-coordinates are the same
y1_plus_y2.mul_equals(cs.ns(|| "enforce (y1+y2)*(y1+y2)^-1=is_same_x-sum_is_zero"), &inv, &same_x_minus_sum_zero)?;

// enforce that x4 = y4 = 0 if sum is zero
let is_sum_not_zero = is_sum_zero.negate(cs.ns(|| "-is_sum_zero"))?.add_constant(cs.ns(|| "1-is_sum_zero"), &P::BaseField::one())?;
let x4 = x3.mul(cs.ns(|| "x4 = x3*(1-is_sum_zero)"), &is_sum_not_zero)?;
let y4 = y3.mul(cs.ns(|| "y4 = y3*(1-is_sum_zero)"), &is_sum_not_zero)?;

// deal with the case when self is the identity
let x5 = F::conditionally_select(cs.ns(|| "x5=x2 if self == 0, x4 otherwise"), &self.infinity, &other.x, &x4)?;
let y5 = F::conditionally_select(cs.ns(|| "y5=y2 if self == 0, y4 otherwise"), &self.infinity, &other.y, &y4)?;

// deal with the case when other is the identity
let x_out = F::conditionally_select(cs.ns(|| "x_out=x1 if other == 0, x5 otherwise"), &other.infinity, &self.x, &x5)?;
let y_out = F::conditionally_select(cs.ns(|| "y_out=y1 if other == 0, y5 otherwise"), &other.infinity, &self.y, &y5)?;

let infinity_out = F::try_into(is_sum_zero)?;

Ok(Self::new(x_out, y_out, infinity_out))
}

/// Incomplete addition: neither `self` nor `other` can be the neutral
/// element.
// ToDo: make addition complete
fn add_constant<CS: ConstraintSystemAbstract<ConstraintF>>(
&self,
mut cs: CS,
Expand Down Expand Up @@ -804,7 +907,8 @@ where
// Last chunk we must use safe add
_ => {
let adder: Self = Self::new(x, y, Boolean::constant(false));
acc = acc.add(cs.ns(|| format!("Add_{}", i)), &adder)?;
// acc = acc.add(cs.ns(|| format!("Add_{}", i)), &adder)?;
acc = acc.add_internal(cs.ns(|| format!("Add_{}", i)), &adder, true)?;
}
}

Expand Down Expand Up @@ -1117,7 +1221,7 @@ where
Ok(ge) => {
let ge = ge.borrow();
if ge.is_zero() {
(Ok(P::BaseField::zero()), Ok(P::BaseField::one()), Ok(true))
(Ok(P::BaseField::zero()), Ok(P::BaseField::zero()), Ok(true))
} else {
let ge = ge.into_affine().unwrap();
(Ok(ge.x), Ok(ge.y), Ok(false))
Expand Down Expand Up @@ -1170,7 +1274,7 @@ where
Ok(ge) => {
let ge = ge.borrow();
if ge.is_zero() {
(Ok(P::BaseField::zero()), Ok(P::BaseField::one()), Ok(true))
(Ok(P::BaseField::zero()), Ok(P::BaseField::zero()), Ok(true))
} else {
let ge = ge.into_affine().unwrap();
(Ok(ge.x), Ok(ge.y), Ok(false))
Expand Down Expand Up @@ -1279,7 +1383,7 @@ where
Ok(ge) => {
let ge = ge.borrow();
if ge.is_zero() {
(Ok(P::BaseField::zero()), Ok(P::BaseField::one()), Ok(true))
(Ok(P::BaseField::zero()), Ok(P::BaseField::zero()), Ok(true))
} else {
let ge = ge.into_affine().unwrap();
(Ok(ge.x), Ok(ge.y), Ok(false))
Expand Down
Loading