Using Arkworks: mixing constraint systems

Hi, I'm new to Rust and am making a zkSNARK circuit using Arkworks (arkworks · GitHub). I need a circuit that uses BLS signature (GitHub - lightec-xyz/bls-verify-gadget: Lightec's implementation of BLS signature verification over BLS12-381, in ZKP based on arkworks.) and other primitives from Arkworks/crypto-primitives. I realized that these primitives rely on different fields - more specifically, some use are based on ark_ff::Fq<MontBackend<ark_bls12_381::FrConfig, 4>, 4> and some others on ark_ff::Fq<MontBackend<ark_bls12_381::FrConfig, 6>, 6>. I figured that I can't use them in one circuit since the backend configurations for the fields do not match. So I split them up into two sub-circuits like below.

pub struct CombinedCircuit<P: Bls12Config, C: CurveGroup, GG: CurveVar<C, ConstraintF1<C>>> {
    bls_circuit: Option<BlsCircuit<P>>,
    rest_circuit: Option<RestCircuit<C,GG>>,
}

impl<P, C, GG> ConstraintSynthesizer<ConstraintF2<P>> for CombinedCircuit<P, C, GG> where 
    P: Bls12Config,
    C: CurveGroup,
    GG: CurveVar<C, ConstraintF1<C>>,
    <P as Bls12Config>::G2Config: WBConfig,
    ConstraintF1<C>: Field,
    for<'a> &'a GG: ark_r1cs_std::groups::GroupOpsBounds<'a, C, GG>,
    ark_ec::twisted_edwards::Affine<EdwardsConfig>: Borrow<<C as CurveGroup>::Affine>,
{
    fn generate_constraints(self, cs: ConstraintSystemRef<ConstraintF2<P>>) -> Result<(), SynthesisError> {
        let bls_ns = ConstraintSystem::<ConstraintF2<P>>::new_ref();
        self.bls_circuit.unwrap().generate_constraints(bls_ns)?;

        let elgamal_ns = ConstraintSystem::<ConstraintF1<C>>::new_ref();
        self.rest_circuit.unwrap().generate_constraints(elgamal_ns)?;

        Ok(())
    }
}

However, a similar issue arises when I try to set up the combined circuit like below.

type P = ark_bls12_381::Config; // this is only available in 6,6
    type ConstraintF1<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
    type ConstraintF2<P> = <P as Bls12Config>::Fp;
    type C = JubJub;  // Assuming JubJub is the curve used, update if different
    type GG = EdwardsVar;
    let rng = &mut OsRng;
    println!("here 1");
    // type Bls12Config = ark_bls12_381::Config;
    let (pk, vk) = Groth16::<E>::setup(CombinedCircuit {
        bls_circuit: None,
        rest_circuit: None,
    }, rng).unwrap();

The error message essentially says there are multiple implementations satisfying some constraints (type ambiguity). Is there a way around this? Or is it impossible to mix constraints in one circuit?

Please post the complete error report as printed by cargo check, and the exact version of your code that produced that error without trimming away context like the enclosing function signature and exactly what libraries you're use-ing.

You've got the assistance of the compiler running in front of you; we don't, so we need your help to tell us enough that we can help you identify the problem and explain what the compiler is telling you.

Hi, sorry I should have been more specific. Thank you very much for helping. Below is the entire error message.

type annotations needed
multiple `impl`s satisfying `for<'a> &'a _: ark_r1cs_std::groups::GroupOpsBounds<'a, _, _>` found in the `ark_r1cs_std` crate:
- impl<'a, P, F> ark_r1cs_std::groups::GroupOpsBounds<'a, ark_ec::short_weierstrass::Projective<P>, ProjectiveVar<P, F>> for &'a ProjectiveVar<P, F>
  where P: SWCurveConfig, F: FieldVar<<P as CurveConfig>::BaseField, <<P as CurveConfig>::BaseField as Field>::BasePrimeField>, for<'b> &'b F: FieldOpsBounds<'b, <P as CurveConfig>::BaseField, F>;
- impl<'a, P, F> ark_r1cs_std::groups::GroupOpsBounds<'a, ark_ec::twisted_edwards::Projective<P>, ark_r1cs_std::groups::curves::twisted_edwards::AffineVar<P, F>> for &'a ark_r1cs_std::groups::curves::twisted_edwards::AffineVar<P, F>
  where <F as TwoBitLookupGadget<<<P as CurveConfig>::BaseField as Field>::BasePrimeField>>::TableConstant == <P as CurveConfig>::BaseField, P: TECurveConfig, F: FieldVar<<P as CurveConfig>::BaseField, <<P as CurveConfig>::BaseField as Field>::BasePrimeField>, F: TwoBitLookupGadget<<<P as CurveConfig>::BaseField as Field>::BasePrimeField>, for<'b> &'b F: FieldOpsBounds<'b, <P as CurveConfig>::BaseField, F>;
subcircuit_ask.rs(119, 16): required for `subcircuit_ask::CombinedCircuit<_, _, _>` to implement `ConstraintSynthesizer<ark_ff::Fp<MontBackend<ark_bls12_381::FrConfig, 4>, 4>>`
lib.rs(85, 17): required by a bound in `ark_snark::CircuitSpecificSetupSNARK::setup`

The error comes from the following part of the code (the entirety of which I pasted below):

let (pk, vk) = Groth16::<E>::setup(CombinedCircuit {
        bls_circuit: None,
        rest_circuit: None,
    }, rng).unwrap(); 

Below is the entire code.

use ark_std::vec::Vec;
use ark_snark::{CircuitSpecificSetupSNARK, SNARK};
use rand::rngs::OsRng;
use ark_groth16::Groth16;
use std::borrow::Borrow;
use ark_ec::bls12::Bls12Config;
use ark_crypto_primitives::encryption::elgamal::{Parameters as EncParams, PublicKey as EncPubKey, Randomness as EncRand};
use ark_relations::r1cs::{ConstraintSystem, SynthesisError, ConstraintSynthesizer, ConstraintSystemRef};
use bls_verify_gadget::{constraints::{BlsSignatureVerifyGadget, SignatureVar, ParametersVar as BlsParametersVar, PublicKeyVar as BlsPublicKeyVar},
    bls::{PublicKey as BlsPublicKey, Signature, Parameters as BlsParams}};
use ark_crypto_primitives::signature::constraints::SigVerifyGadget;
use ark_bls12_381::Bls12_381 as E;
use ark_r1cs_std::{alloc::AllocVar, prelude::*};
use ark_ec::{twisted_edwards::Affine, CurveGroup, hashing::curve_maps::wb::WBConfig};
use ark_ff::Field;
use ark_serialize::{CanonicalSerialize, Compress};
use ark_ed_on_bls12_381::{constraints::EdwardsVar, EdwardsConfig, EdwardsProjective as JubJub};
use ark_std::marker::PhantomData;

type ConstraintF1<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;     // In main(), C=JubJub and GG=EdwardsVar, which uses ark_ff::Fq<MontBackend<ark_bls12_381::FrConfig, 4>, 4>
type ConstraintF2<P> = <P as Bls12Config>::Fp;   // This uses ark_ff::Fq<MontBackend<ark_bls12_381::FqConfig, 6>, 6>

pub struct BlsCircuit<P: Bls12Config> {
    bls_params: Option<BlsParams<P>>,            /* BLS parameters */
    apk: Option<BlsPublicKey<P>>,
    sig: Option<Signature<P>>,
    msg:  Option<Vec<u8>>,
}

pub struct RestCircuit<C: CurveGroup, GG: CurveVar<C, ConstraintF1<C>>> {    
    elgamal_rand: Option<EncRand<C>>,            /* Elgamal parameters */
    elgamal_ptxt_x: Option<ark_bls12_381::Fr>,
    elgamal_ptxt_y: Option<ark_bls12_381::Fr>,
    elgamal_pk: Option<EncPubKey<C>>,
    elgamal_params: Option<EncParams<C>>,
    _curve_var: PhantomData<GG>,
}

impl<P> ConstraintSynthesizer<ConstraintF2<P>> for BlsCircuit<P> where 
    P: Bls12Config,
    ConstraintF2<P>: Field,
    <P as Bls12Config>::G2Config: WBConfig,
{
    fn generate_constraints(self, cs: ConstraintSystemRef<ConstraintF2<P>>) -> Result<(), SynthesisError> {
        // 1. Check BLS signature
        let bls_params_wtns = BlsParametersVar::<P>::new_variable(
            cs.clone(),
            || self.bls_params.as_ref().ok_or(SynthesisError::AssignmentMissing),
            AllocationMode::Witness,
        )?;

        let apk_input = BlsPublicKeyVar::<P>::new_variable(
            cs.clone(),
            || self.apk.as_ref().ok_or(SynthesisError::AssignmentMissing),
            AllocationMode::Input,
        )?;

        let msg_wtns_vec = [0u8; 32];   // Populate this with something in actual code
        let msg_wtns = UInt8::<P::Fp>::new_witness_vec(cs.clone(), &msg_wtns_vec)?;

        let sig_wtns = SignatureVar::<P>::new_variable(
            cs.clone(),
            || self.sig.as_ref().ok_or(SynthesisError::AssignmentMissing),
            AllocationMode::Witness,
        )?;

        // All inputs to verify() are based on '<ark_ff::Fq<MontBackend<ark_bls12_377::FqConfig, 6>, 6>'
        let verified = BlsSignatureVerifyGadget::<P>::verify(&bls_params_wtns, &apk_input, &msg_wtns, &sig_wtns)?;
        verified.enforce_equal(&Boolean::TRUE)?;

        Ok(())
    }
}


impl<C, GG> ConstraintSynthesizer<ConstraintF1<C>> for RestCircuit<C, GG> where 
    ConstraintF1<C>: Field,
    C: CurveGroup,
    GG: CurveVar<C, ConstraintF1<C>>,
    for<'a> &'a GG: ark_r1cs_std::groups::GroupOpsBounds<'a, C, GG>,
    ark_ec::twisted_edwards::Affine<EdwardsConfig>: Borrow<<C as CurveGroup>::Affine>,
{
    fn generate_constraints(self, cs: ConstraintSystemRef<ConstraintF1<C>>) -> Result<(), SynthesisError> {
        let elgamal_ptxt_x_value: ark_ff::Fp<ark_ff::MontBackend<ark_bls12_381::FrConfig, 4>, 4> = self.elgamal_ptxt_x.ok_or(SynthesisError::AssignmentMissing)?;
        let elgamal_ptxt_y_value: ark_ff::Fp<ark_ff::MontBackend<ark_bls12_381::FrConfig, 4>, 4> = self.elgamal_ptxt_y.ok_or(SynthesisError::AssignmentMissing)?;

        // Reconstruct plaintext passed in as Affine x and y coordinates (bytes)
        let mut ptxt_x_bytes = vec![];
        elgamal_ptxt_x_value.serialize_with_mode(&mut ptxt_x_bytes, Compress::No).unwrap();
        let mut ptxt_y_bytes = vec![];
        elgamal_ptxt_y_value.serialize_with_mode(&mut ptxt_y_bytes, Compress::No).unwrap();

        // Declare input for circuit syntax, no need to actually use
        let elgamal_ptxt_x_input = UInt8::<ConstraintF1<C>>::new_input_vec(
            cs.clone(),
            &ptxt_x_bytes,
        );

        let elgamal_ptxt_y_input = UInt8::<ConstraintF1<C>>::new_input_vec(
            cs.clone(),
            &ptxt_y_bytes,
        );

        // let elgamal_ptxt_x: ark_ff::Fq<MontBackend<ark_bls12_381::FrConfig, 4>, 4> = self.elgamal_ptxt_x.unwrap();
        let point = Affine::<EdwardsConfig>::new(elgamal_ptxt_x_value, elgamal_ptxt_y_value);

        // Do some zero knowledge proof with 'point'

        Ok(())
    }
}


pub struct CombinedCircuit<P: Bls12Config, C: CurveGroup, GG: CurveVar<C, ConstraintF1<C>>> {
    bls_circuit: Option<BlsCircuit<P>>,
    rest_circuit: Option<RestCircuit<C,GG>>,
}

impl<P, C, GG> ConstraintSynthesizer<ConstraintF2<P>> for CombinedCircuit<P, C, GG> where 
    P: Bls12Config,
    C: CurveGroup,
    GG: CurveVar<C, ConstraintF1<C>>,
    <P as Bls12Config>::G2Config: WBConfig,
    ConstraintF1<C>: Field,
    for<'a> &'a GG: ark_r1cs_std::groups::GroupOpsBounds<'a, C, GG>,
    ark_ec::twisted_edwards::Affine<EdwardsConfig>: Borrow<<C as CurveGroup>::Affine>,
{
    fn generate_constraints(self, cs: ConstraintSystemRef<ConstraintF2<P>>) -> Result<(), SynthesisError> {
        let bls_ns = ConstraintSystem::<ConstraintF2<P>>::new_ref();
        self.bls_circuit.unwrap().generate_constraints(bls_ns)?;

        let elgamal_ns = ConstraintSystem::<ConstraintF1<C>>::new_ref();
        self.rest_circuit.unwrap().generate_constraints(elgamal_ns)?;

        Ok(())
    }
}

fn main() {
    type C = JubJub;  // Assuming JubJub is the curve used, update if different
    type GG = EdwardsVar;       // This uses 
    let rng = &mut OsRng;
    // type Bls12Config = ark_bls12_381::Config;
    let (pk, vk) = Groth16::<E>::setup(CombinedCircuit {
        bls_circuit: None,
        rest_circuit: None,
    }, rng).unwrap(); 
    let pvk = Groth16::<E>::process_vk(&vk).unwrap();
    
    // Then prove circuit and verify the proof with Groth16
}

So suppose I explicitly defined the types for CombinedCircuit like below.

fn main() {
    type P = ark_bls12_381::Config;
    type C = JubJub;
    type GG = EdwardsVar;
    let rng = &mut OsRng;
    type ImplCircuit = CombinedCircuit<P, C, GG>;
    let (pk, vk) = Groth16::<E>::setup(ImplCircuit {
        bls_circuit: None,
        rest_circuit: None,
    }, rng).unwrap(); 
    let pvk = Groth16::<E>::process_vk(&vk).unwrap();
    
    // Prove circuit and verify the proof with Groth16
}

Then it errors out saying this:

type mismatch resolving `<Config as Bls12Config>::Fp == Fp<MontBackend<FrConfig, 4>, 4>`
expected struct `ark_ff::Fp<MontBackend<ark_bls12_381::FrConfig, 4>, 4>`
   found struct `ark_ff::Fp<MontBackend<ark_bls12_381::FqConfig, 6>, 6>` subcircuit_ask.rs(145, 20): required by a bound introduced by this call
lib.rs(85, 17): required by a bound in `ark_snark::CircuitSpecificSetupSNARK::setup`

Essentially, the problem seems to be that I'm using fields (ConstraintF1 and ConstraintF2

) that are configured on different MontBackEnds. Assume I must use the exact fields and curve groups defined in the codes. Is it not allowed in Rust to mix fields like this?

That will help I'm sure, but is not the entire message printed by cargo check. You must be getting it from your IDE. Instead, run cargo check in the terminal and copy the while thing, which will include descriptions, hints, and ascii art that shows the variables and types involved.


Thanks. Also, if you're using vscode you can get this full message by hovering over the error and clicking "Click for full compiler diagnostic".

1 Like

Hi, oops, sorry again. I'm new to Rust. This is what I get when I run cargo check.

error[E0271]: type mismatch resolving `<Config as Bls12Config>::Fp == Fp<MontBackend<FrConfig, 4>, 4>`
   --> src/main.rs:145:40
    |
145 |       let (pk, vk) = Groth16::<E>::setup(ImplCircuit {
    |  ____________________-------------------_^
    | |                    |
    | |                    required by a bound introduced by this call
146 | |         bls_circuit: None,
147 | |         rest_circuit: None,
148 | |     }, rng).unwrap(); 
    | |_____^ expected `Fp<MontBackend<..., 4>, 4>`, found `Fp<MontBackend<..., 6>, 6>`
    |
    = note: expected struct `Fp<MontBackend<ark_bls12_381::FrConfig, 4>, 4>`
               found struct `Fp<MontBackend<ark_bls12_381::FqConfig, 6>, 6>`
note: required by a bound in `ark_snark::CircuitSpecificSetupSNARK::setup`
    |
85  |     fn setup<C: ConstraintSynthesizer<F>, R: RngCore + CryptoRng>(
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `CircuitSpecificSetupSNARK::setup`


1 Like

I found a way around the previous problem by defining the ConstraintSystem of CombinedCircuit with a third Field and passing that as PhantomData to CombinedCircuit.

impl<F, P, C, GG> ConstraintSynthesizer<F> for CombinedCircuit<F, P, C, GG> where 
    F: Field,
    P: Bls12Config,
    C: CurveGroup,
    GG: CurveVar<C, ConstraintF1<C>>,
    <P as Bls12Config>::G2Config: WBConfig,
    ConstraintF1<C>: Field,
    for<'a> &'a GG: ark_r1cs_std::groups::GroupOpsBounds<'a, C, GG>,
    ark_ec::twisted_edwards::Affine<EdwardsConfig>: Borrow<<C as CurveGroup>::Affine>,
{
    fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
        let bls_ns = ConstraintSystem::<ConstraintF2<P>>::new_ref();
        self.bls_circuit.unwrap().generate_constraints(bls_ns)?;

        let elgamal_ns = ConstraintSystem::<ConstraintF1<C>>::new_ref();
        self.rest_circuit.unwrap().generate_constraints(elgamal_ns)?;

        Ok(())
    }
}

fn main() {
    type P = ark_bls12_381::Config;
    type C = JubJub;
    type GG = EdwardsVar;
    type F = Fr;
    let rng = &mut OsRng;
    type ImplCircuit = CombinedCircuit<F, P, C, GG>;
    let (pk, vk) = Groth16::<E>::setup(ImplCircuit {
        bls_circuit: None,
        rest_circuit: None,
        _field_var: PhantomData::<F>,
    }, rng).unwrap(); 
    let pvk = Groth16::<E>::process_vk(&vk).unwrap();

    // Prove circuit and verify the proof with Groth16
}

Then I no longer get the error because all three circuits (Bls, Rest, and Combined) have field elements defined w.r.t. the ConstraintSystem field.

I have a new question for anyone familiar with Arkworks and Groth16. I can call the following to setup and generate Groth16 proof for CombinedCircuit:

let (pk, vk) = Groth16::<E>::setup(ImplCircuit {
        bls_circuit: None,
        rest_circuit: None,
        _field_var: PhantomData::<F>,
    }, rng).unwrap(); 

let bls_circuit = BlsCircuit { /* Provide actual fields */ };
let rest_circuit = BlsCircuit { /* Provide actual fields */ };
let proof = Groth16::<E>::prove(&pk, bls_circuit, rest_circuit), rng).unwrap();

Now, when calling Groth16::<E>::verify(&vk, &public_inputs, &proof) I need to provide public inputs to the subcircuits because CombinedCircuit itself doesn't have any public inputs -- it's just supposed to be a wrapper to put together BlsCircuit and RestCircuit.

However, CombinedCircuit itself does not directly include public inputs, but the public inputs are instead embedded within the sub-circuits. When the verifier calls Groth16::::verify(&vk, &public_inputs, &proof), the public inputs need to correctly propagate to the relevant parts of the sub-circuits - does anyone know how to do this?

Hi, I thought I'd just let people know that this was resolved by trying different curve configurations, in case anyone is still watching.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.