Support for arbitrary arity for step circuit's IO (#107)

* support for arbitrary arity for F

* revive MinRoot example

* revive tests

* revive ecdsa

* remove unused code

* use None instead of Some(1u32)

* revive benches

* fix clippy warning
This commit is contained in:
Srinath Setty
2022-08-16 11:35:17 -07:00
committed by GitHub
parent 0a7cbf925f
commit ccc6ccd4c7
13 changed files with 322 additions and 331 deletions

View File

@@ -3,11 +3,6 @@ use bellperson::{
ConstraintSystem, SynthesisError,
};
use ff::{PrimeField, PrimeFieldBits};
use generic_array::typenum::U8;
use neptune::{
circuit::poseidon_hash,
poseidon::{Poseidon, PoseidonConstants},
};
use nova_snark::{gadgets::ecc::AllocatedPoint, traits::circuit::StepCircuit};
use subtle::Choice;
@@ -66,11 +61,6 @@ pub struct EcdsaCircuit<F>
where
F: PrimeField<Repr = [u8; 32]>,
{
pub z_r: Coordinate<F>,
pub z_g: Coordinate<F>,
pub z_pk: Coordinate<F>,
pub z_c: F,
pub z_s: F,
pub r: Coordinate<F>,
pub g: Coordinate<F>,
pub pk: Coordinate<F>,
@@ -78,7 +68,6 @@ where
pub s: F,
pub c_bits: Vec<Choice>,
pub s_bits: Vec<Choice>,
pub pc: PoseidonConstants<F, U8>,
}
impl<F> EcdsaCircuit<F>
@@ -88,42 +77,14 @@ where
// Creates a new [`EcdsaCircuit<Fb, Fs>`]. The base and scalar field elements from the curve
// field used by the signature are converted to scalar field elements from the cyclic curve
// field used by the circuit.
pub fn new<Fb, Fs>(
num_steps: usize,
signatures: &[EcdsaSignature<Fb, Fs>],
pc: &PoseidonConstants<F, U8>,
) -> (F, Vec<Self>)
pub fn new<Fb, Fs>(num_steps: usize, signatures: &[EcdsaSignature<Fb, Fs>]) -> (Vec<F>, Vec<Self>)
where
Fb: PrimeField<Repr = [u8; 32]>,
Fs: PrimeField<Repr = [u8; 32]> + PrimeFieldBits,
{
let mut z0 = F::zero();
let mut z0 = Vec::new();
let mut circuits = Vec::new();
for i in 0..num_steps {
let mut j = i;
if i > 0 {
j = i - 1
};
let z_signature = &signatures[j];
let z_r = Coordinate::new(
F::from_repr(z_signature.r.x.to_repr()).unwrap(),
F::from_repr(z_signature.r.y.to_repr()).unwrap(),
);
let z_g = Coordinate::new(
F::from_repr(z_signature.g.x.to_repr()).unwrap(),
F::from_repr(z_signature.g.y.to_repr()).unwrap(),
);
let z_pk = Coordinate::new(
F::from_repr(z_signature.pk.x.to_repr()).unwrap(),
F::from_repr(z_signature.pk.y.to_repr()).unwrap(),
);
let z_c = F::from_repr(z_signature.c.to_repr()).unwrap();
let z_s = F::from_repr(z_signature.s.to_repr()).unwrap();
let signature = &signatures[i];
for (i, signature) in signatures.iter().enumerate().take(num_steps) {
let r = Coordinate::new(
F::from_repr(signature.r.x.to_repr()).unwrap(),
F::from_repr(signature.r.y.to_repr()).unwrap(),
@@ -145,11 +106,6 @@ where
let s = F::from_repr(signature.s.to_repr()).unwrap();
let circuit = EcdsaCircuit {
z_r,
z_g,
z_pk,
z_c,
z_s,
r,
g,
pk,
@@ -157,13 +113,11 @@ where
s,
c_bits,
s_bits,
pc: pc.clone(),
};
circuits.push(circuit);
if i == 0 {
z0 =
Poseidon::<F, U8>::new_with_preimage(&[r.x, r.y, g.x, g.y, pk.x, pk.y, c, s], pc).hash();
z0 = vec![r.x, r.y, g.x, g.y, pk.x, pk.y, c, s];
}
}
@@ -208,36 +162,18 @@ impl<F> StepCircuit<F> for EcdsaCircuit<F>
where
F: PrimeField<Repr = [u8; 32]> + PrimeFieldBits,
{
fn arity(&self) -> usize {
8
}
// Prove knowledge of the sk used to generate the Ecdsa signature (R,s)
// with public key PK and message commitment c.
// [s]G == R + [c]PK
fn synthesize<CS: ConstraintSystem<F>>(
&self,
cs: &mut CS,
z: AllocatedNum<F>,
) -> Result<AllocatedNum<F>, SynthesisError> {
let z_rx = AllocatedNum::alloc(cs.namespace(|| "z_rx"), || Ok(self.z_r.x))?;
let z_ry = AllocatedNum::alloc(cs.namespace(|| "z_ry"), || Ok(self.z_r.y))?;
let z_gx = AllocatedNum::alloc(cs.namespace(|| "z_gx"), || Ok(self.z_g.x))?;
let z_gy = AllocatedNum::alloc(cs.namespace(|| "z_gy"), || Ok(self.z_g.y))?;
let z_pkx = AllocatedNum::alloc(cs.namespace(|| "z_pkx"), || Ok(self.z_pk.x))?;
let z_pky = AllocatedNum::alloc(cs.namespace(|| "z_pky"), || Ok(self.z_pk.y))?;
let z_c = AllocatedNum::alloc(cs.namespace(|| "z_c"), || Ok(self.z_c))?;
let z_s = AllocatedNum::alloc(cs.namespace(|| "z_s"), || Ok(self.z_s))?;
let z_hash = poseidon_hash(
cs.namespace(|| "input hash"),
vec![z_rx, z_ry, z_gx, z_gy, z_pkx, z_pky, z_c, z_s],
&self.pc,
)?;
cs.enforce(
|| "z == z1",
|lc| lc + z.get_variable(),
|lc| lc + CS::one(),
|lc| lc + z_hash.get_variable(),
);
_z: &[AllocatedNum<F>],
) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
let g = AllocatedPoint::alloc(
cs.namespace(|| "G"),
Some((self.g.x, self.g.y, self.g.is_infinity)),
@@ -282,36 +218,12 @@ where
let c = AllocatedNum::alloc(cs.namespace(|| "c"), || Ok(self.c))?;
let s = AllocatedNum::alloc(cs.namespace(|| "s"), || Ok(self.s))?;
poseidon_hash(
cs.namespace(|| "output hash"),
vec![rx, ry, gx, gy, pkx, pky, c, s],
&self.pc,
)
Ok(vec![rx, ry, gx, gy, pkx, pky, c, s])
}
fn output(&self, z: &F) -> F {
let z_hash = Poseidon::<F, U8>::new_with_preimage(
&[
self.z_r.x,
self.z_r.y,
self.z_g.x,
self.z_g.y,
self.z_pk.x,
self.z_pk.y,
self.z_c,
self.z_s,
],
&self.pc,
)
.hash();
debug_assert_eq!(z, &z_hash);
Poseidon::<F, U8>::new_with_preimage(
&[
self.r.x, self.r.y, self.g.x, self.g.y, self.pk.x, self.pk.y, self.c, self.s,
],
&self.pc,
)
.hash()
fn output(&self, _z: &[F]) -> Vec<F> {
vec![
self.r.x, self.r.y, self.g.x, self.g.y, self.pk.x, self.pk.y, self.c, self.s,
]
}
}

View File

@@ -9,8 +9,6 @@ use ff::{
derive::byteorder::{ByteOrder, LittleEndian},
Field, PrimeField, PrimeFieldBits,
};
use generic_array::typenum::U8;
use neptune::{poseidon::PoseidonConstants, Strength};
use nova_snark::{
traits::{circuit::TrivialTestCircuit, Group as Nova_Group},
CompressedSNARK, PublicParams, RecursiveSNARK,
@@ -165,22 +163,7 @@ fn main() {
// produce public parameters
println!("Generating public parameters...");
let pc = PoseidonConstants::<<G2 as Group>::Scalar, U8>::new_with_strength(Strength::Standard);
let circuit_primary = EcdsaCircuit::<<G2 as Nova_Group>::Scalar> {
z_r: Coordinate::new(
<G2 as Nova_Group>::Scalar::zero(),
<G2 as Nova_Group>::Scalar::zero(),
),
z_g: Coordinate::new(
<G2 as Nova_Group>::Scalar::zero(),
<G2 as Nova_Group>::Scalar::zero(),
),
z_pk: Coordinate::new(
<G2 as Nova_Group>::Scalar::zero(),
<G2 as Nova_Group>::Scalar::zero(),
),
z_c: <G2 as Nova_Group>::Scalar::zero(),
z_s: <G2 as Nova_Group>::Scalar::zero(),
r: Coordinate::new(
<G2 as Nova_Group>::Scalar::zero(),
<G2 as Nova_Group>::Scalar::zero(),
@@ -197,7 +180,6 @@ fn main() {
s: <G2 as Nova_Group>::Scalar::zero(),
c_bits: vec![Choice::from(0u8); 256],
s_bits: vec![Choice::from(0u8); 256],
pc: pc.clone(),
};
let circuit_secondary = TrivialTestCircuit::default();
@@ -258,10 +240,10 @@ fn main() {
let (z0_primary, circuits_primary) = EcdsaCircuit::<<G2 as Nova_Group>::Scalar>::new::<
<G1 as Nova_Group>::Base,
<G1 as Nova_Group>::Scalar,
>(num_steps, &signatures(), &pc);
>(num_steps, &signatures());
// Secondary circuit
let z0_secondary = <G1 as Group>::Scalar::zero();
let z0_secondary = vec![<G1 as Group>::Scalar::zero()];
// produce a recursive SNARK
println!("Generating a RecursiveSNARK...");
@@ -277,8 +259,8 @@ fn main() {
recursive_snark,
circuit_primary.clone(),
circuit_secondary.clone(),
z0_primary,
z0_secondary,
z0_primary.clone(),
z0_secondary.clone(),
);
assert!(result.is_ok());
println!("RecursiveSNARK::prove_step {}: {:?}", i, result.is_ok());
@@ -290,7 +272,7 @@ fn main() {
// verify the recursive SNARK
println!("Verifying the RecursiveSNARK...");
let res = recursive_snark.verify(&pp, num_steps, z0_primary, z0_secondary);
let res = recursive_snark.verify(&pp, num_steps, z0_primary.clone(), z0_secondary.clone());
println!("RecursiveSNARK::verify: {:?}", res.is_ok());
assert!(res.is_ok());

View File

@@ -5,12 +5,6 @@ type G1 = pasta_curves::pallas::Point;
type G2 = pasta_curves::vesta::Point;
use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
use ff::PrimeField;
use generic_array::typenum::U2;
use neptune::{
circuit::poseidon_hash,
poseidon::{Poseidon, PoseidonConstants},
Strength,
};
use nova_snark::{
traits::{
circuit::{StepCircuit, TrivialTestCircuit},
@@ -31,7 +25,7 @@ struct MinRootIteration<F: PrimeField> {
impl<F: PrimeField> MinRootIteration<F> {
// produces a sample non-deterministic advice, executing one invocation of MinRoot per step
fn new(num_iters: usize, x_0: &F, y_0: &F, pc: &PoseidonConstants<F, U2>) -> (F, Vec<Self>) {
fn new(num_iters: usize, x_0: &F, y_0: &F) -> (Vec<F>, Vec<Self>) {
// although this code is written generically, it is tailored to Pallas' scalar field
// (p - 3 / 5)
let exp = BigUint::parse_bytes(
@@ -65,7 +59,7 @@ impl<F: PrimeField> MinRootIteration<F> {
y_i = y_i_plus_1;
}
let z0 = Poseidon::<F, U2>::new_with_preimage(&[*x_0, *y_0], pc).hash();
let z0 = vec![*x_0, *y_0];
(z0, res)
}
@@ -74,23 +68,27 @@ impl<F: PrimeField> MinRootIteration<F> {
#[derive(Clone, Debug)]
struct MinRootCircuit<F: PrimeField> {
seq: Vec<MinRootIteration<F>>,
pc: PoseidonConstants<F, U2>,
}
impl<F> StepCircuit<F> for MinRootCircuit<F>
where
F: PrimeField,
{
fn arity(&self) -> usize {
2
}
fn synthesize<CS: ConstraintSystem<F>>(
&self,
cs: &mut CS,
z: AllocatedNum<F>,
) -> Result<AllocatedNum<F>, SynthesisError> {
let mut z_out: Result<AllocatedNum<F>, SynthesisError> = Err(SynthesisError::AssignmentMissing);
z: &[AllocatedNum<F>],
) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
let mut z_out: Result<Vec<AllocatedNum<F>>, SynthesisError> =
Err(SynthesisError::AssignmentMissing);
// allocate variables to hold x_0 and y_0
let x_0 = AllocatedNum::alloc(cs.namespace(|| "x_0"), || Ok(self.seq[0].x_i))?;
let y_0 = AllocatedNum::alloc(cs.namespace(|| "y_0"), || Ok(self.seq[0].y_i))?;
// use the provided inputs
let x_0 = z[0].clone();
let y_0 = z[1].clone();
// variables to hold running x_i and y_i
let mut x_i = x_0;
@@ -102,21 +100,6 @@ where
Ok(self.seq[i].x_i_plus_1)
})?;
// check that z = hash(x_i, y_i), where z is an output from the prior step
if i == 0 {
let z_hash = poseidon_hash(
cs.namespace(|| "input hash"),
vec![x_i.clone(), y_i.clone()],
&self.pc,
)?;
cs.enforce(
|| "z =? z_hash",
|lc| lc + z_hash.get_variable(),
|lc| lc + CS::one(),
|lc| lc + z.get_variable(),
);
}
// check the following conditions hold:
// (i) x_i_plus_1 = (x_i + y_i)^{1/5}, which can be more easily checked with x_i_plus_1^5 = x_i + y_i
// (ii) y_i_plus_1 = x_i
@@ -135,11 +118,7 @@ where
// return hash(x_i_plus_1, y_i_plus_1) since Nova circuits expect a single output
if i == self.seq.len() - 1 {
z_out = poseidon_hash(
cs.namespace(|| "output hash"),
vec![x_i_plus_1.clone(), x_i.clone()],
&self.pc,
);
z_out = Ok(vec![x_i_plus_1.clone(), x_i.clone()]);
}
// update x_i and y_i for the next iteration
@@ -150,22 +129,16 @@ where
z_out
}
fn output(&self, z: &F) -> F {
fn output(&self, z: &[F]) -> Vec<F> {
// sanity check
let z_hash =
Poseidon::<F, U2>::new_with_preimage(&[self.seq[0].x_i, self.seq[0].y_i], &self.pc).hash();
debug_assert_eq!(z, &z_hash);
debug_assert_eq!(z[0], self.seq[0].x_i);
debug_assert_eq!(z[1], self.seq[0].y_i);
// compute output hash using advice
let iters = self.seq.len();
Poseidon::<F, U2>::new_with_preimage(
&[
self.seq[iters - 1].x_i_plus_1,
self.seq[iters - 1].y_i_plus_1,
],
&self.pc,
)
.hash()
// compute output using advice
vec![
self.seq[self.seq.len() - 1].x_i_plus_1,
self.seq[self.seq.len() - 1].y_i_plus_1,
]
}
}
@@ -176,7 +149,6 @@ fn main() {
let num_steps = 10;
for num_iters_per_step in [1024, 2048, 4096, 8192, 16384, 32768, 65535] {
// number of iterations of MinRoot per Nova's recursive step
let pc = PoseidonConstants::<<G1 as Group>::Scalar, U2>::new_with_strength(Strength::Standard);
let circuit_primary = MinRootCircuit {
seq: vec![
MinRootIteration {
@@ -187,7 +159,6 @@ fn main() {
};
num_iters_per_step
],
pc: pc.clone(),
};
let circuit_secondary = TrivialTestCircuit::default();
@@ -228,7 +199,6 @@ fn main() {
num_iters_per_step * num_steps,
&<G1 as Group>::Scalar::zero(),
&<G1 as Group>::Scalar::one(),
&pc,
);
let minroot_circuits = (0..num_steps)
.map(|i| MinRootCircuit {
@@ -240,11 +210,10 @@ fn main() {
y_i_plus_1: minroot_iterations[i * num_iters_per_step + j].y_i_plus_1,
})
.collect::<Vec<_>>(),
pc: pc.clone(),
})
.collect::<Vec<_>>();
let z0_secondary = <G2 as Group>::Scalar::zero();
let z0_secondary = vec![<G2 as Group>::Scalar::zero()];
type C1 = MinRootCircuit<<G1 as Group>::Scalar>;
type C2 = TrivialTestCircuit<<G2 as Group>::Scalar>;
@@ -259,8 +228,8 @@ fn main() {
recursive_snark,
circuit_primary.clone(),
circuit_secondary.clone(),
z0_primary,
z0_secondary,
z0_primary.clone(),
z0_secondary.clone(),
);
assert!(res.is_ok());
println!(
@@ -278,7 +247,7 @@ fn main() {
// verify the recursive SNARK
println!("Verifying a RecursiveSNARK...");
let start = Instant::now();
let res = recursive_snark.verify(&pp, num_steps, z0_primary, z0_secondary);
let res = recursive_snark.verify(&pp, num_steps, z0_primary.clone(), z0_secondary.clone());
println!(
"RecursiveSNARK::verify: {:?}, took {:?}",
res.is_ok(),