Skip to content

Commit

Permalink
feat: non-native ECC circuit APIs: followup (#302)
Browse files Browse the repository at this point in the history
* add select and enforce_eq apis for emulated vars

* add derived traits and to_vec

* make rescue sponge apis public

* cargo fmt

* update changelog

---------

Co-authored-by: MRain <[email protected]>
  • Loading branch information
chancharles92 and mrain authored Jun 14, 2023
1 parent 150e4e1 commit 86f221f
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ and follow [semantic versioning](https://semver.org/) for our releases.
- [#251](https://github.com/EspressoSystems/jellyfish/pull/251) add sign_key_ref api for BLSKeyPair
- [#297](https://github.com/EspressoSystems/jellyfish/pull/297) Updated `tagged-base64` dependency to the `crates.io` package
- [#299](https://github.com/EspressoSystems/jellyfish/pull/299) For Merkle tree, `DigestAlgorithm` now returns a `Result` type.
- [#302](https://github.com/EspressoSystems/jellyfish/pull/302) Followup APIs for non-native ECC circuit support.

### Removed

Expand Down
8 changes: 4 additions & 4 deletions primitives/src/rescue/sponge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct RescueSponge<F: RescueParameter, const RATE: usize> {

/// CRHF
#[derive(Debug, Clone)]
pub(crate) struct RescueCRHF<F: RescueParameter> {
pub struct RescueCRHF<F: RescueParameter> {
sponge: RescueSponge<F, CRHF_RATE>,
}

Expand All @@ -47,7 +47,7 @@ impl<F: RescueParameter> RescueCRHF<F> {
/// multiple of RATE
///
/// [padding]: https://en.wikipedia.org/wiki/Padding_(cryptography)#Bit_padding
pub(crate) fn sponge_with_bit_padding(input: &[F], num_outputs: usize) -> Vec<F> {
pub fn sponge_with_bit_padding(input: &[F], num_outputs: usize) -> Vec<F> {
let mut padded = input.to_vec();
padded.push(F::one());
pad_with_zeros(&mut padded, CRHF_RATE);
Expand All @@ -60,7 +60,7 @@ impl<F: RescueParameter> RescueCRHF<F> {
/// overall length to be a multiple of RATE.
///
/// [padding]: https://en.wikipedia.org/wiki/Padding_(cryptography)#Zero_padding
pub(crate) fn sponge_with_zero_padding(input: &[F], num_outputs: usize) -> Vec<F> {
pub fn sponge_with_zero_padding(input: &[F], num_outputs: usize) -> Vec<F> {
let mut padded = input.to_vec();
pad_with_zeros(&mut padded, CRHF_RATE);
Self::sponge_no_padding(padded.as_slice(), num_outputs)
Expand All @@ -70,7 +70,7 @@ impl<F: RescueParameter> RescueCRHF<F> {
/// Sponge hashing based on rescue permutation for RATE 3 and CAPACITY 1. It
/// allows inputs with length that is a multiple of `CRHF_RATE` and
/// returns a vector of `num_outputs` elements.
pub(crate) fn sponge_no_padding(input: &[F], num_output: usize) -> Result<Vec<F>, RescueError> {
pub fn sponge_no_padding(input: &[F], num_output: usize) -> Result<Vec<F>, RescueError> {
if input.len() % CRHF_RATE != 0 {
return Err(RescueError::ParameterError(
"Rescue sponge Error : input to sponge hashing function is not multiple of RATE."
Expand Down
94 changes: 93 additions & 1 deletion relation/src/gadgets/ecc/non_native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ use super::Point;
use crate::{
errors::CircuitError,
gadgets::{EmulatedVariable, EmulationConfig},
PlonkCircuit,
BoolVar, PlonkCircuit,
};
use ark_ff::PrimeField;

/// The variable represents an EC point in the emulated field.
#[derive(Debug, Clone)]
pub struct EmulatedPointVariable<E: PrimeField>(pub EmulatedVariable<E>, pub EmulatedVariable<E>);

impl<F: PrimeField> PlonkCircuit<F> {
Expand Down Expand Up @@ -110,6 +111,34 @@ impl<F: PrimeField> PlonkCircuit<F> {
self.emulated_ecc_add_gate(a, b, &c, d)?;
Ok(c)
}

/// Obtain an emulated point variable of the conditional selection from 2
/// emulated point variables. `b` is a boolean variable that indicates
/// selection of P_b from (P0, P1).
/// Return error if invalid input parameters are provided.
pub fn binary_emulated_point_vars_select<E: EmulationConfig<F>>(
&mut self,
b: BoolVar,
point0: &EmulatedPointVariable<E>,
point1: &EmulatedPointVariable<E>,
) -> Result<EmulatedPointVariable<E>, CircuitError> {
let select_x = self.conditional_select_emulated(b, &point0.0, &point1.0)?;
let select_y = self.conditional_select_emulated(b, &point0.1, &point1.1)?;

Ok(EmulatedPointVariable::<E>(select_x, select_y))
}

/// Constrain two emulated point variables to be the same.
/// Return error if the input point variables are invalid.
pub fn enforce_emulated_point_equal<E: EmulationConfig<F>>(
&mut self,
point0: &EmulatedPointVariable<E>,
point1: &EmulatedPointVariable<E>,
) -> Result<(), CircuitError> {
self.enforce_emulated_var_equal(&point0.0, &point1.0)?;
self.enforce_emulated_var_equal(&point0.1, &point1.1)?;
Ok(())
}
}

#[cfg(test)]
Expand Down Expand Up @@ -164,4 +193,67 @@ mod tests {
.unwrap();
assert!(circuit.check_circuit_satisfiability(&[]).is_err());
}

#[test]
fn test_emulated_point_select() {
test_emulated_point_select_helper::<Fq377, Fr254, Param377>();
}

fn test_emulated_point_select_helper<E, F, P>()
where
E: EmulationConfig<F> + SWToTEConParam,
F: PrimeField,
P: SWCurveConfig<BaseField = E>,
{
let mut rng = jf_utils::test_rng();
let p1 = Projective::<P>::rand(&mut rng).into_affine();
let p2 = Projective::<P>::rand(&mut rng).into_affine();
let p1: Point<E> = (&p1).into();
let p2: Point<E> = (&p2).into();

let mut circuit = PlonkCircuit::<F>::new_turbo_plonk();

let var_p1 = circuit.create_emulated_point_variable(p1).unwrap();
let var_p2 = circuit.create_emulated_point_variable(p2).unwrap();
let b = circuit.create_boolean_variable(true).unwrap();
let var_p3 = circuit
.binary_emulated_point_vars_select(b, &var_p1, &var_p2)
.unwrap();
assert_eq!(circuit.emulated_point_witness(&var_p3).unwrap(), p2);
assert!(circuit.check_circuit_satisfiability(&[]).is_ok());
*circuit.witness_mut(var_p3.0 .0[0]) = F::zero();
assert!(circuit.check_circuit_satisfiability(&[]).is_err());
}

#[test]
fn test_enforce_emulated_point_eq() {
test_enforce_emulated_point_eq_helper::<Fq377, Fr254, Param377>();
}

fn test_enforce_emulated_point_eq_helper<E, F, P>()
where
E: EmulationConfig<F> + SWToTEConParam,
F: PrimeField,
P: SWCurveConfig<BaseField = E>,
{
let mut rng = jf_utils::test_rng();
let p1 = Projective::<P>::rand(&mut rng).into_affine();
let p2 = (p1 + Projective::<P>::generator()).into_affine();
let p1: Point<E> = (&p1).into();
let p2: Point<E> = (&p2).into();

let mut circuit = PlonkCircuit::<F>::new_turbo_plonk();

let var_p1 = circuit.create_emulated_point_variable(p1).unwrap();
let var_p2 = circuit.create_emulated_point_variable(p2).unwrap();
let var_p3 = circuit.create_emulated_point_variable(p1).unwrap();
circuit
.enforce_emulated_point_equal(&var_p1, &var_p3)
.unwrap();
assert!(circuit.check_circuit_satisfiability(&[]).is_ok());
circuit
.enforce_emulated_point_equal(&var_p1, &var_p2)
.unwrap();
assert!(circuit.check_circuit_satisfiability(&[]).is_err());
}
}
95 changes: 94 additions & 1 deletion relation/src/gadgets/emulated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! componenet, with modulus 2^T, will be divided into limbs each with B bits
//! where 2^{2B} < p.
use crate::{errors::CircuitError, Circuit, PlonkCircuit, Variable};
use crate::{errors::CircuitError, BoolVar, Circuit, PlonkCircuit, Variable};
use ark_ff::PrimeField;
use ark_std::{string::ToString, vec, vec::Vec, One, Zero};
use core::marker::PhantomData;
Expand Down Expand Up @@ -71,8 +71,16 @@ where
}

/// The variable represents an element in the emulated field.
#[derive(Debug, Clone)]
pub struct EmulatedVariable<E: PrimeField>(pub(crate) Vec<Variable>, PhantomData<E>);

impl<E: PrimeField> EmulatedVariable<E> {
/// Return the list of variables that simulate the field element
pub fn to_vec(&self) -> Vec<Variable> {
self.0.clone()
}
}

impl<F: PrimeField> PlonkCircuit<F> {
/// Return the witness point for the circuit
pub fn emulated_witness<E: EmulationConfig<F>>(
Expand Down Expand Up @@ -542,6 +550,44 @@ impl<F: PrimeField> PlonkCircuit<F> {
Ok(c)
}

/// Obtain an emulated variable of the conditional selection from 2 emulated
/// variables. `b` is a boolean variable that indicates selection of P_b
/// from (P0, P1).
/// Return error if invalid input parameters are provided.
pub fn conditional_select_emulated<E: EmulationConfig<F>>(
&mut self,
b: BoolVar,
p0: &EmulatedVariable<E>,
p1: &EmulatedVariable<E>,
) -> Result<EmulatedVariable<E>, CircuitError> {
self.check_var_bound(b.into())?;
self.check_vars_bound(&p0.0[..])?;
self.check_vars_bound(&p1.0[..])?;

let mut vals = vec![];
for (&x_0, &x_1) in p0.0.iter().zip(p1.0.iter()) {
let selected = self.conditional_select(b, x_0, x_1)?;
vals.push(selected);
}

Ok(EmulatedVariable::<E>(vals, PhantomData::<E>))
}

/// Constrain two emulated variables to be the same.
/// Return error if the input variables are invalid.
pub fn enforce_emulated_var_equal<E: EmulationConfig<F>>(
&mut self,
p0: &EmulatedVariable<E>,
p1: &EmulatedVariable<E>,
) -> Result<(), CircuitError> {
self.check_vars_bound(&p0.0[..])?;
self.check_vars_bound(&p1.0[..])?;
for (&a, &b) in p0.0.iter().zip(p1.0.iter()) {
self.enforce_equal(a, b)?;
}
Ok(())
}

/// Given an emulated field element `a`, return `a mod F::MODULUS` in the
/// native field.
fn mod_to_native_field<E: EmulationConfig<F>>(
Expand Down Expand Up @@ -709,4 +755,51 @@ mod tests {
.check_circuit_satisfiability(&from_emulated_field(x))
.is_err());
}

#[test]
fn test_select() {
test_select_helper::<Fq377, Fr254>();
test_select_helper::<Fq254, Fr254>();
}

fn test_select_helper<E, F>()
where
E: EmulationConfig<F>,
F: PrimeField,
{
let mut circuit = PlonkCircuit::<F>::new_turbo_plonk();
let var_x = circuit.create_emulated_variable(E::one()).unwrap();
let overflow = E::from(E::MODULUS.into() - 1u64);
let var_y = circuit.create_emulated_variable(overflow).unwrap();
let b = circuit.create_boolean_variable(true).unwrap();
let var_z = circuit
.conditional_select_emulated(b, &var_x, &var_y)
.unwrap();
assert_eq!(circuit.emulated_witness(&var_z).unwrap(), overflow);
assert!(circuit.check_circuit_satisfiability(&[]).is_ok());
*circuit.witness_mut(var_z.0[0]) = F::zero();
assert!(circuit.check_circuit_satisfiability(&[]).is_err());
}

#[test]
fn test_enforce_equal() {
test_enforce_equal_helper::<Fq377, Fr254>();
test_enforce_equal_helper::<Fq254, Fr254>();
}

fn test_enforce_equal_helper<E, F>()
where
E: EmulationConfig<F>,
F: PrimeField,
{
let mut circuit = PlonkCircuit::<F>::new_turbo_plonk();
let var_x = circuit.create_emulated_variable(E::one()).unwrap();
let overflow = E::from(E::MODULUS.into() - 1u64);
let var_y = circuit.create_emulated_variable(overflow).unwrap();
let var_z = circuit.create_emulated_variable(overflow).unwrap();
circuit.enforce_emulated_var_equal(&var_y, &var_z).unwrap();
assert!(circuit.check_circuit_satisfiability(&[]).is_ok());
circuit.enforce_emulated_var_equal(&var_x, &var_y).unwrap();
assert!(circuit.check_circuit_satisfiability(&[]).is_err());
}
}

0 comments on commit 86f221f

Please sign in to comment.