From 11f0fd48b1d662cc70be32e5422e97612961d4f3 Mon Sep 17 00:00:00 2001 From: Will Lin Date: Thu, 13 May 2021 16:36:21 -0400 Subject: [PATCH] Implement an accumulation scheme for MarlinPC --- Cargo.toml | 2 + src/data_structures.rs | 14 +- src/lib.rs | 8 + .../constraints/data_structures.rs | 378 +++++++ src/marlin_pc_as/constraints/mod.rs | 549 +++++++++++ src/marlin_pc_as/data_structures.rs | 201 ++++ src/marlin_pc_as/mod.rs | 931 ++++++++++++++++++ 7 files changed, 2081 insertions(+), 2 deletions(-) create mode 100644 src/marlin_pc_as/constraints/data_structures.rs create mode 100644 src/marlin_pc_as/constraints/mod.rs create mode 100644 src/marlin_pc_as/data_structures.rs create mode 100644 src/marlin_pc_as/mod.rs diff --git a/Cargo.toml b/Cargo.toml index ff5c424..e0007ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ digest = { version = "0.9.0", default-features = false, optional = true } [dev-dependencies] ark-pallas = { version = "^0.2.0", features = [ "r1cs", "curve" ] } +ark-bls12-377 = { version = "^0.2.0", default-features = false, features = [ "curve", "r1cs" ] } tracing = { version = "0.1", default-features = false } tracing-subscriber = { version = "0.2", default-features = false, features = [ "registry" ] } @@ -59,6 +60,7 @@ std = [ "ark-crypto-primitives/std", "ark-ec/std", "ark-ff/std", "ark-nonnative- impl = [] hp-as = [ "impl", "ark-poly" ] ipa-pc-as = [ "impl", "ark-poly", "ark-poly-commit", "blake2" ] +marlin-pc-as = [ "impl", "ark-poly", "ark-poly-commit" ] r1cs-nark-as = [ "impl", "blake2", "digest", "hp-as", "r1cs" ] trivial-pc-as = [ "impl", "ark-poly", "ark-poly-commit", "blake2" ] diff --git a/src/data_structures.rs b/src/data_structures.rs index 02a9a2f..6157a22 100644 --- a/src/data_structures.rs +++ b/src/data_structures.rs @@ -5,10 +5,20 @@ use ark_std::io::{Read, Write}; use ark_std::rand::RngCore; // Useful type alias for implementations. -#[cfg(feature = "impl")] +#[cfg(any( + feature = "hp-as", + feature = "ipa-pc-as", + feature = "r1cs-nark-as", + feature = "trivial-pc-as" +))] use {ark_ec::AffineCurve, ark_ff::Field}; -#[cfg(feature = "impl")] +#[cfg(any( + feature = "hp-as", + feature = "ipa-pc-as", + feature = "r1cs-nark-as", + feature = "trivial-pc-as" +))] pub(crate) type ConstraintF = <::BaseField as Field>::BasePrimeField; /// A pair consisting of references to an instance and witness. diff --git a/src/lib.rs b/src/lib.rs index 1e3c7e2..e42717f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,14 @@ pub mod hp_as; #[cfg_attr(docsrs, doc(cfg(feature = "ipa-pc-as")))] pub mod ipa_pc_as; +/// An accumulation scheme based on bilinear groups. +/// The construction is described in detail in [\[BCMS20\]][\[BCMS20\]]. +/// +/// [\[BCMS20\]]: https://eprint.iacr.org/2020/499 +#[cfg(feature = "marlin-pc-as")] +#[cfg_attr(docsrs, doc(cfg(feature = "marlin-pc-as")))] +pub mod marlin_pc_as; + /// An accumulation scheme for a NARK for R1CS. /// The construction is described in detail in [\[BCLMS20\]][bclms20]. /// diff --git a/src/marlin_pc_as/constraints/data_structures.rs b/src/marlin_pc_as/constraints/data_structures.rs new file mode 100644 index 0000000..d85d2aa --- /dev/null +++ b/src/marlin_pc_as/constraints/data_structures.rs @@ -0,0 +1,378 @@ +use crate::marlin_pc_as::{AccumulatorInstance, InputInstance, MarlinPCInstance, Randomness}; + +use ark_ec::PairingEngine; +use ark_ff::{BitIteratorLE, PrimeField}; +use ark_poly_commit::marlin_pc; +use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; +use ark_r1cs_std::bits::boolean::Boolean; +use ark_r1cs_std::eq::EqGadget; +use ark_r1cs_std::fields::fp::FpVar; +use ark_r1cs_std::pairing::PairingVar; +use ark_r1cs_std::uint8::UInt8; +use ark_r1cs_std::ToBytesGadget; +use ark_relations::r1cs::{Namespace, SynthesisError}; +use ark_sponge::collect_sponge_field_elements_gadget; +use ark_sponge::constraints::AbsorbableGadget; +use ark_std::borrow::Borrow; + +/// An instance of the MarlinPC relation. +pub struct MarlinPCInstanceVar +where + E: PairingEngine, + PV: PairingVar, +{ + /// The MarlinPC commitment to a polynomial. + pub(crate) commitment: marlin_pc::LabeledCommitmentVar, + + /// Point where the proof was opened at. + pub(crate) point: Vec>, + + /// Evaluation of the committed polynomial at the point. + pub(crate) evaluation: Vec>, +} + +impl AllocVar, E::Fq> for MarlinPCInstanceVar +where + E: PairingEngine, + PV: PairingVar, +{ + fn new_variable>>( + cs: impl Into::Fq>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let ns = cs.into(); + f().and_then(|instance| { + let instance = instance.borrow(); + + let commitment = marlin_pc::LabeledCommitmentVar::new_variable( + ns.clone(), + || Ok(instance.commitment.clone()), + mode, + )?; + + let point = BitIteratorLE::new((&instance.point).into_repr()) + .map(|b| Boolean::new_variable(ns.clone(), || Ok(b), mode)) + .collect::, SynthesisError>>()?; + + let evaluation = BitIteratorLE::new((&instance.evaluation).into_repr()) + .map(|b| Boolean::new_variable(ns.clone(), || Ok(b), mode)) + .collect::, SynthesisError>>()?; + + Ok(Self { + commitment, + point, + evaluation, + }) + }) + } +} + +impl AbsorbableGadget for MarlinPCInstanceVar +where + E: PairingEngine, + PV: PairingVar, + PV::G1Var: AbsorbableGadget, +{ + fn to_sponge_field_elements( + &self, + ) -> Result::Fq>>, SynthesisError> { + let point_bytes = self + .point + .chunks(8) + .map(|c| { + if c.len() == 8 { + UInt8::::from_bits_le(c) + } else { + let mut resized = c.to_vec(); + resized.resize_with(8, || Boolean::FALSE); + UInt8::::from_bits_le(&resized) + } + }) + .collect::>(); + + let evaluation_bytes = self + .evaluation + .chunks(8) + .map(|c| { + if c.len() == 8 { + UInt8::::from_bits_le(c) + } else { + let mut resized = c.to_vec(); + resized.resize_with(8, || Boolean::FALSE); + UInt8::::from_bits_le(&resized) + } + }) + .collect::>(); + + let degree_bound = self + .commitment + .degree_bound + .as_ref() + .map(|d| d.to_bytes()) + .transpose()?; + + collect_sponge_field_elements_gadget!( + self.commitment.commitment, + degree_bound, + point_bytes, + evaluation_bytes + ) + } +} + +/// The [`InputInstance`][input_instance] of the +/// [`AtomicASForMarlinPCVerifierGadget`][as_for_marlin_pc_verifier]. +/// +/// [input_instance]: crate::constraints::ASVerifierGadget::InputInstance +/// [as_for_marlin_pc_verifier]: crate::marlin_pc_as::constraints::AtomicASForMarlinPCVerifierGadget +pub struct InputInstanceVar +where + E: PairingEngine, + PV: PairingVar, +{ + /// The MarlinPC instance. + pub marlin_pc_instance: MarlinPCInstanceVar, + + /// The MarlinPC proof. + pub marlin_pc_proof: marlin_pc::ProofVar, +} + +impl AllocVar, E::Fq> for InputInstanceVar +where + E: PairingEngine, + PV: PairingVar, +{ + fn new_variable>>( + cs: impl Into::Fq>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let ns = cs.into(); + f().and_then(|instance| { + let instance = instance.borrow(); + + let marlin_pc_instance = MarlinPCInstanceVar::new_variable( + ns.clone(), + || Ok(instance.marlin_pc_instance.clone()), + mode, + )?; + + let marlin_pc_proof = marlin_pc::ProofVar::new_variable( + ns.clone(), + || Ok(instance.marlin_pc_proof.clone()), + mode, + )?; + + Ok(Self { + marlin_pc_instance, + marlin_pc_proof, + }) + }) + } +} + +impl AbsorbableGadget for InputInstanceVar +where + E: PairingEngine, + PV: PairingVar, + PV::G1Var: AbsorbableGadget, +{ + fn to_sponge_field_elements( + &self, + ) -> Result::Fq>>, SynthesisError> { + collect_sponge_field_elements_gadget!(self.marlin_pc_instance, self.marlin_pc_proof) + } +} + +/// The [`AccumulatorInstance`][acc_instance] of the +/// [`AtomicASForMarlinPCVerifierGadget`][as_for_marlin_pc_verifier]. +/// +/// [acc_instance]: crate::constraints::ASVerifierGadget::AccumulatorInstance +/// [as_for_marlin_pc_verifier]: crate::marlin_pc_as::constraints::AtomicASForMarlinPCVerifierGadget +pub struct AccumulatorInstanceVar +where + E: PairingEngine, + PV: PairingVar, +{ + /// The accumulated MarlinPC commitment. + pub(crate) accumulated_commitment: PV::G1Var, + + /// The accumulated MarlinPC proof. + pub(crate) accumulated_proof: PV::G1Var, +} + +impl AllocVar, E::Fq> for AccumulatorInstanceVar +where + E: PairingEngine, + PV: PairingVar, +{ + fn new_variable>>( + cs: impl Into::Fq>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let ns = cs.into(); + f().and_then(|instance| { + let instance = instance.borrow(); + + let accumulated_commitment = PV::G1Var::new_variable( + ns.clone(), + || Ok(instance.accumulated_commitment.clone()), + mode, + )?; + + let accumulated_proof = PV::G1Var::new_variable( + ns.clone(), + || Ok(instance.accumulated_proof.clone()), + mode, + )?; + + Ok(Self { + accumulated_commitment, + accumulated_proof, + }) + }) + } +} + +impl EqGadget for AccumulatorInstanceVar +where + E: PairingEngine, + PV: PairingVar, +{ + fn is_eq(&self, other: &Self) -> Result::Fq>, SynthesisError> { + let commitment_is_eq = self + .accumulated_commitment + .is_eq(&other.accumulated_commitment)?; + + let proof_is_eq = self.accumulated_proof.is_eq(&other.accumulated_proof)?; + + Ok(commitment_is_eq.and(&proof_is_eq)?) + } +} + +impl AbsorbableGadget for AccumulatorInstanceVar +where + E: PairingEngine, + PV: PairingVar, + PV::G1Var: AbsorbableGadget, +{ + fn to_sponge_field_elements( + &self, + ) -> Result::Fq>>, SynthesisError> { + collect_sponge_field_elements_gadget!(self.accumulated_commitment, self.accumulated_proof) + } +} + +/// The randomness used to apply zero-knowledge to the accumulated commitment and proof. +pub struct RandomnessVar +where + E: PairingEngine, + PV: PairingVar, +{ + /// The randomness used to blind the accumulated commitment. + pub(crate) accumulated_commitment_randomness: PV::G1Var, + + /// The randomness used to blind the accumulated proof. + pub(crate) accumulated_proof_randomness: PV::G1Var, +} + +impl AllocVar, E::Fq> for RandomnessVar +where + E: PairingEngine, + PV: PairingVar, +{ + fn new_variable>>( + cs: impl Into::Fq>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let ns = cs.into(); + f().and_then(|randomness| { + let randomness = randomness.borrow(); + + let accumulated_commitment_randomness = PV::G1Var::new_variable( + ns.clone(), + || Ok(randomness.accumulated_commitment_randomness.clone()), + mode, + )?; + + let accumulated_proof_randomness = PV::G1Var::new_variable( + ns.clone(), + || Ok(randomness.accumulated_proof_randomness.clone()), + mode, + )?; + + Ok(Self { + accumulated_commitment_randomness, + accumulated_proof_randomness, + }) + }) + } +} + +impl AbsorbableGadget for RandomnessVar +where + E: PairingEngine, + PV: PairingVar, + PV::G1Var: AbsorbableGadget, +{ + fn to_sponge_field_elements( + &self, + ) -> Result::Fq>>, SynthesisError> { + collect_sponge_field_elements_gadget!( + self.accumulated_commitment_randomness, + self.accumulated_proof_randomness + ) + } +} + +/// The [`Proof`][proof] of the [`AtomicASForMarlinPCVerifierGadget`][as_for_marlin_pc_verifier]. +/// +/// [proof]: crate::constraints::ASVerifierGadget::Proof +/// [as_for_marlin_pc_verifier]: crate::marlin_pc_as::constraints::AtomicASForMarlinPCVerifierGadget +pub struct ProofVar +where + E: PairingEngine, + PV: PairingVar, +{ + /// The randomness used to apply zero-knowledge to the accumulated commitment and proof. + pub(crate) randomness: Option>, +} + +impl AllocVar>, E::Fq> for ProofVar +where + E: PairingEngine, + PV: PairingVar, +{ + fn new_variable>>>( + cs: impl Into::Fq>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let ns = cs.into(); + f().and_then(|randomness| { + let randomness = randomness + .borrow() + .as_ref() + .map(|r| RandomnessVar::new_variable(ns.clone(), || Ok(r.clone()), mode)) + .transpose()?; + + Ok(Self { randomness }) + }) + } +} + +impl AbsorbableGadget for ProofVar +where + E: PairingEngine, + PV: PairingVar, + PV::G1Var: AbsorbableGadget, +{ + fn to_sponge_field_elements( + &self, + ) -> Result::Fq>>, SynthesisError> { + self.randomness.to_sponge_field_elements() + } +} diff --git a/src/marlin_pc_as/constraints/mod.rs b/src/marlin_pc_as/constraints/mod.rs new file mode 100644 index 0000000..4d2e9f0 --- /dev/null +++ b/src/marlin_pc_as/constraints/mod.rs @@ -0,0 +1,549 @@ +use crate::constraints::{ASVerifierGadget, AtomicASVerifierGadget}; +use crate::marlin_pc_as::{ + AtomicASForMarlinPC, InputInstance, CHALLENGE_SIZE, MARLIN_PC_PROTOCOL_NAME, PROTOCOL_NAME, +}; + +use ark_ec::PairingEngine; +use ark_poly_commit::marlin::marlin_pc; +use ark_r1cs_std::alloc::AllocVar; +use ark_r1cs_std::bits::boolean::Boolean; +use ark_r1cs_std::eq::EqGadget; +use ark_r1cs_std::groups::CurveVar; +use ark_r1cs_std::pairing::PairingVar; +use ark_r1cs_std::ToBitsGadget; +use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; +use ark_sponge::absorb_gadget; +use ark_sponge::constraints::{AbsorbableGadget, CryptographicSpongeVar}; +use ark_sponge::{Absorbable, CryptographicSponge, FieldElementSize}; +use ark_std::marker::PhantomData; + +mod data_structures; +pub use data_structures::*; + +/// The verifier gadget of [`AtomicASForMarlinPC`][as_for_marlin_pc]. +/// +/// [as_for_marlin_pc]: crate::marlin_pc_as::AtomicASForMarlinPC +pub struct AtomicASForMarlinPCVerifierGadget +where + E: PairingEngine, + PV: PairingVar + 'static, + E::G1Affine: Absorbable, + E::G2Affine: Absorbable, + E::Fq: Absorbable, + PV::G1Var: AbsorbableGadget, + PV::G2Var: AbsorbableGadget, + S: CryptographicSponge, + SV: CryptographicSpongeVar, +{ + _pairing_engine_phantom: PhantomData, + _pairing_var_phantom: PhantomData, + _sponge_phantom: PhantomData, + _sponge_var_phantom: PhantomData, +} + +impl AtomicASForMarlinPCVerifierGadget +where + E: PairingEngine, + PV: PairingVar + 'static, + E::G1Affine: Absorbable, + E::G2Affine: Absorbable, + E::Fq: Absorbable, + PV::G1Var: AbsorbableGadget, + PV::G2Var: AbsorbableGadget, + S: CryptographicSponge, + SV: CryptographicSpongeVar, +{ + /// Squeezes `num_challenges` accumulation challenges, where the first challenge is one. + fn squeeze_accumulation_challenges( + sponge: &mut SV, + num_challenges: usize, + ) -> Result>>, SynthesisError> { + let mut challenges = Vec::with_capacity(num_challenges); + challenges.push(vec![Boolean::TRUE]); + + if num_challenges > 1 { + let rest_of_challenges_bits = + sponge.squeeze_bits(CHALLENGE_SIZE * (num_challenges - 1))?; + rest_of_challenges_bits + .chunks(CHALLENGE_SIZE) + .for_each(|c| { + challenges.push(c.to_vec()); + }); + } + + Ok(challenges) + } + + /// Accumulates inputs into a single tuple: (accumulated_commitment, accumulated_proof) + fn accumulate_inputs<'a>( + cs: ConstraintSystemRef, + verifier_key: &marlin_pc::VerifierKeyVar, + inputs: &Vec<&InputInstanceVar>, + accumulation_challenges: &Vec>>, + mut pc_sponge: SV, + ) -> Result, SynthesisError> { + assert!(inputs.len() <= accumulation_challenges.len()); + pc_sponge.absorb(&verifier_key)?; + + let mut accumulated_commitment = PV::G1Var::zero(); + let mut accumulated_proof = PV::G1Var::zero(); + for (i, input) in inputs.iter().enumerate() { + let labeled_commitment = &input.marlin_pc_instance.commitment; + let degree_bound = labeled_commitment.degree_bound.as_ref(); + let commitment = &labeled_commitment.commitment; + + if degree_bound.is_some() != commitment.shifted_comm.is_some() { + return Ok(None); + } + + let eval = &input.marlin_pc_instance.evaluation; + + // An element in the summation used to accumulate the commitments from the inputs + let mut commitment_addend: PV::G1Var = (commitment.comm.clone() + - &verifier_key.g.scalar_mul_le(eval.iter())?) + + &input + .marlin_pc_proof + .w + .scalar_mul_le(input.marlin_pc_instance.point.iter())?; + + if let Some(random_v) = &input.marlin_pc_proof.random_v { + commitment_addend -= &verifier_key + .gamma_g + .scalar_mul_le(random_v.to_bits_le()?.iter())?; + } + + if let Some(degree_bound) = degree_bound { + // The assertion statement above ensures that shifted_comm exists if degree_bound exists. + let shifted_comm = commitment.shifted_comm.clone().unwrap(); + let shift_power = verifier_key.get_shift_power(cs.clone(), degree_bound); + + if shift_power.is_none() { + return Ok(None); + } + + let shift_power = shift_power.unwrap(); + + let mut opening_challenge_sponge = pc_sponge.clone(); + opening_challenge_sponge.absorb(&input.marlin_pc_instance)?; + + let (_, mut opening_challenge_bits) = opening_challenge_sponge + .squeeze_nonnative_field_elements_with_sizes::( + vec![FieldElementSize::Truncated(CHALLENGE_SIZE)].as_slice(), + )?; + + let opening_challenge_bits = opening_challenge_bits.pop().unwrap(); + + let shifted_comm_diff: PV::G1Var = + shifted_comm - &shift_power.scalar_mul_le(eval.iter())?; + + commitment_addend += + &shifted_comm_diff.scalar_mul_le(opening_challenge_bits.iter())?; + } + + let challenge = accumulation_challenges[i].clone(); + accumulated_commitment += &commitment_addend.scalar_mul_le(challenge.iter())?; + accumulated_proof += &input.marlin_pc_proof.w.scalar_mul_le(challenge.iter())?; + } + + Ok(Some((accumulated_commitment, accumulated_proof))) + } + + /// Accumulates inputs and old accumulators into a new accumulator. + fn compute_accumulator( + cs: ConstraintSystemRef, + verifier_key: &marlin_pc::VerifierKeyVar, + inputs: &Vec<&InputInstanceVar>, + accumulators: &Vec<&AccumulatorInstanceVar>, + proof: &ProofVar, + sponge: SV, + ) -> Result>, SynthesisError> { + let mut accumulation_challenge_sponge = sponge.fork(&PROTOCOL_NAME)?; + absorb_gadget!( + &mut accumulation_challenge_sponge, + verifier_key, + inputs, + accumulators, + proof + ); + + let num_inputs = inputs.len(); + let num_accumulators = accumulators.len(); + + let accumulation_challenges: Vec>> = + Self::squeeze_accumulation_challenges( + &mut accumulation_challenge_sponge, + num_inputs + num_accumulators + if proof.randomness.is_some() { 1 } else { 0 }, + )?; + + // Accumulate the commitments and proofs from the inputs + let accumulated_inputs = Self::accumulate_inputs( + cs.clone(), + &verifier_key, + &inputs, + &accumulation_challenges, + sponge.fork(&MARLIN_PC_PROTOCOL_NAME)?, + )?; + + if accumulated_inputs.is_none() { + return Ok(None); + } + + let (mut accumulated_commitment, mut accumulated_proof) = accumulated_inputs.unwrap(); + + // Accumulate the accumulators + for (i, acc) in accumulators.into_iter().enumerate() { + accumulated_commitment += &acc + .accumulated_commitment + .scalar_mul_le(accumulation_challenges[num_inputs + i].iter())?; + accumulated_proof += &acc + .accumulated_proof + .scalar_mul_le(accumulation_challenges[num_inputs + i].iter())?; + } + + // Apply the randomness from the proof + if let Some(randomness) = proof.randomness.as_ref() { + accumulated_commitment += randomness + .accumulated_commitment_randomness + .scalar_mul_le(accumulation_challenges[num_inputs + num_accumulators].iter())?; + + accumulated_proof += randomness + .accumulated_proof_randomness + .scalar_mul_le(accumulation_challenges[num_inputs + num_accumulators].iter())?; + } + + let new_accumulator = AccumulatorInstanceVar { + accumulated_commitment, + accumulated_proof, + }; + + Ok(Some(new_accumulator)) + } +} + +impl ASVerifierGadget> + for AtomicASForMarlinPCVerifierGadget +where + E: PairingEngine, + PV: PairingVar + 'static, + E::G1Affine: Absorbable, + E::G2Affine: Absorbable, + E::Fq: Absorbable, + PV::G1Var: AbsorbableGadget, + PV::G2Var: AbsorbableGadget, + S: CryptographicSponge, + SV: CryptographicSpongeVar, +{ + type VerifierKey = marlin_pc::VerifierKeyVar; + type InputInstance = InputInstanceVar; + type AccumulatorInstance = AccumulatorInstanceVar; + type Proof = ProofVar; + + fn verify<'a>( + cs: ConstraintSystemRef<::Fq>, + verifier_key: &Self::VerifierKey, + input_instances: impl IntoIterator, + old_accumulator_instances: impl IntoIterator, + new_accumulator_instance: &Self::AccumulatorInstance, + proof: &Self::Proof, + sponge: Option, + ) -> Result::Fq>, SynthesisError> + where + Self::InputInstance: 'a, + Self::AccumulatorInstance: 'a, + { + let sponge = sponge.unwrap_or_else(|| SV::new(cs.clone())); + + let mut inputs = input_instances.into_iter().collect::>(); + let old_accumulators = old_accumulator_instances.into_iter().collect::>(); + + // Default input in the case there are no provided inputs or accumulators. + let default_input_instance; + if inputs.is_empty() && old_accumulators.is_empty() { + default_input_instance = Some(InputInstanceVar::new_constant( + cs.clone(), + InputInstance::zero(), + )?); + inputs.push(default_input_instance.as_ref().unwrap()); + } + + let test_accumulator = Self::compute_accumulator( + cs.clone(), + &verifier_key, + &inputs, + &old_accumulators, + &proof, + sponge, + ); + + if test_accumulator.is_err() { + return Ok(Boolean::FALSE); + } + + let test_accumulator = test_accumulator.unwrap(); + + if test_accumulator.is_none() { + return Ok(Boolean::FALSE); + } + + let test_accumulator = test_accumulator.unwrap(); + + Ok(test_accumulator.is_eq(&new_accumulator_instance)?) + } +} + +impl AtomicASVerifierGadget> + for AtomicASForMarlinPCVerifierGadget +where + E: PairingEngine, + PV: PairingVar + 'static, + E::G1Affine: Absorbable, + E::G2Affine: Absorbable, + E::Fq: Absorbable, + PV::G1Var: AbsorbableGadget, + PV::G2Var: AbsorbableGadget, + S: CryptographicSponge, + SV: CryptographicSpongeVar, +{ +} + +#[cfg(test)] +pub mod tests { + use crate::constraints::tests::ASVerifierGadgetTests; + use crate::marlin_pc_as::constraints::AtomicASForMarlinPCVerifierGadget; + use crate::marlin_pc_as::tests::{AtomicASForMarlinPCTestInput, AtomicASForMarlinPCTestParams}; + use crate::marlin_pc_as::AtomicASForMarlinPC; + use ark_bls12_377::Bls12_377; + use ark_relations::r1cs::SynthesisError; + use ark_sponge::poseidon::constraints::PoseidonSpongeVar; + use ark_sponge::poseidon::PoseidonSponge; + + type E = Bls12_377; + type PV = ark_bls12_377::constraints::PairingVar; + type CF = ark_bls12_377::Fq; + + type Sponge = PoseidonSponge; + type SpongeVar = PoseidonSpongeVar; + + type AS = AtomicASForMarlinPC; + type I = AtomicASForMarlinPCTestInput; + type ASV = AtomicASForMarlinPCVerifierGadget; + + type Tests = ASVerifierGadgetTests; + + #[test] + pub fn single_input_init_test_no_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::single_input_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn single_input_init_test_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::single_input_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn multiple_inputs_init_test_no_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::multiple_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn multiple_input_init_test_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::multiple_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn simple_accumulation_test_no_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::simple_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn simple_accumulation_test_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::simple_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn multiple_inputs_accumulation_test_no_zk_no_degree_bounds() -> Result<(), SynthesisError> + { + Tests::multiple_inputs_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn multiple_inputs_accumulation_test_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::multiple_inputs_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn accumulators_only_test_no_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::accumulators_only_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn accumulators_only_test_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::accumulators_only_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn no_inputs_init_test_no_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::no_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn no_inputs_init_test_zk_no_degree_bounds() -> Result<(), SynthesisError> { + Tests::no_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn single_input_init_test_no_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::single_input_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn single_input_init_test_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::single_input_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn multiple_inputs_init_test_no_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::multiple_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn multiple_input_init_test_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::multiple_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn simple_accumulation_test_no_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::simple_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn simple_accumulation_test_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::simple_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn multiple_inputs_accumulation_test_no_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::multiple_inputs_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn multiple_inputs_accumulation_test_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::multiple_inputs_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn accumulators_only_test_no_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::accumulators_only_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn accumulators_only_test_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::accumulators_only_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn no_inputs_init_test_no_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::no_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn no_inputs_init_test_zk_degree_bounds() -> Result<(), SynthesisError> { + Tests::no_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } +} diff --git a/src/marlin_pc_as/data_structures.rs b/src/marlin_pc_as/data_structures.rs new file mode 100644 index 0000000..4c5effe --- /dev/null +++ b/src/marlin_pc_as/data_structures.rs @@ -0,0 +1,201 @@ +use ark_ec::PairingEngine; +use ark_ff::{to_bytes, Zero}; +use ark_poly::polynomial::univariate::DensePolynomial; +use ark_poly_commit::marlin_pc::MarlinKZG10; +use ark_poly_commit::{kzg10, marlin_pc, LabeledCommitment, PolynomialCommitment, PolynomialLabel}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; +use ark_sponge::{collect_sponge_bytes, collect_sponge_field_elements, Absorbable}; +use ark_std::vec::Vec; + +/// The [`PredicateIndex`][predicate_index] of the [`AtomicASForMarlinPC`][as_for_marlin_pc]. +/// +/// [predicate_index]: crate::AccumulationScheme::PredicateIndex +/// [as_for_marlin_pc]: crate::marlin_pc_as::AtomicASForMarlinPC +#[derive(Clone)] +pub struct PredicateIndex { + /// The supported degree by the MarlinPC index. + pub supported_degree: usize, + + /// The hiding bound supported by the MarlinPC index. + pub supported_hiding_bound: usize, + + /// The degree bounds that are supported by the MarlinPC index. + pub enforced_degree_bounds: Option>, +} + +/// The [`ProverKey`][pk] of the [`AtomicASForMarlinPC`][as_for_marlin_pc]. +/// +/// [pk]: crate::AccumulationScheme::ProverKey +/// [as_for_marlin_pc]: crate::marlin_pc_as::AtomicASForMarlinPC +#[derive(Clone)] +pub struct ProverKey { + /// The verifier key of MarlinPC. + pub(crate) marlin_vk: marlin_pc::VerifierKey, + + /// The power used to commit to randomness. + pub(crate) beta_g: E::G1Affine, +} + +/// An instance of the MarlinPC relation. +#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct MarlinPCInstance { + /// The MarlinPC commitment to a polynomial. + pub commitment: LabeledCommitment>, + + /// Point where the proof was opened at. + pub point: E::Fr, + + /// Evaluation of the committed polynomial at the point. + pub evaluation: E::Fr, +} + +impl Absorbable for MarlinPCInstance +where + E::G1Affine: Absorbable, + E::Fq: Absorbable, +{ + fn to_sponge_bytes(&self) -> Vec { + let degree_bound = self + .commitment + .degree_bound() + .as_ref() + .map(|d| to_bytes!(>::from(*d as u64)).unwrap()); + + collect_sponge_bytes!( + E::Fq, + self.commitment.commitment(), + degree_bound, + to_bytes!(self.point).unwrap(), + to_bytes!(self.evaluation).unwrap() + ) + } + + fn to_sponge_field_elements(&self) -> Vec<::Fq> { + let degree_bound = self + .commitment + .degree_bound() + .as_ref() + .map(|d| to_bytes!(>::from(*d as u64)).unwrap()); + + collect_sponge_field_elements!( + self.commitment.commitment(), + degree_bound, + to_bytes!(self.point).unwrap(), + to_bytes!(self.evaluation).unwrap() + ) + } +} + +/// The [`InputInstance`][input_instance] of the [`AtomicASForMarlinPC`][as_for_marlin_pc]. +/// +/// [input_instance]: crate::AccumulationScheme::InputInstance +/// [as_for_marlin_pc]: crate::marlin_pc_as::AtomicASForMarlinPC +#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct InputInstance { + /// The MarlinPC instance. + pub marlin_pc_instance: MarlinPCInstance, + + /// The MarlinPC proof. + pub marlin_pc_proof: > as PolynomialCommitment< + E::Fr, + DensePolynomial, + >>::Proof, +} + +impl InputInstance { + pub(crate) fn zero() -> Self { + let marlin_pc_commitment = LabeledCommitment::new( + PolynomialLabel::new(), + marlin_pc::Commitment::default(), + None, + ); + + let marlin_pc_proof = kzg10::Proof::default(); + + let marlin_pc_instance = MarlinPCInstance { + commitment: marlin_pc_commitment, + point: E::Fr::zero(), + evaluation: E::Fr::zero(), + }; + + Self { + marlin_pc_instance, + marlin_pc_proof, + } + } +} + +impl Absorbable for InputInstance +where + E::G1Affine: Absorbable, + E::Fq: Absorbable, +{ + fn to_sponge_bytes(&self) -> Vec { + collect_sponge_bytes!(E::Fq, self.marlin_pc_instance, self.marlin_pc_proof) + } + + fn to_sponge_field_elements(&self) -> Vec<::Fq> { + collect_sponge_field_elements!(self.marlin_pc_instance, self.marlin_pc_proof) + } +} + +/// The [`AccumulatorInstance`][acc_instance] of the [`AtomicASForMarlinPC`][as_for_marlin_pc]. +/// +/// [acc_instance]: crate::AccumulationScheme::AccumulatorInstance +/// [as_for_marlin_pc]: crate::marlin_pc_as::AtomicASForMarlinPC +#[derive(Clone, CanonicalSerialize, CanonicalDeserialize, PartialEq, Eq)] +pub struct AccumulatorInstance { + /// The accumulated MarlinPC commitment. + pub(crate) accumulated_commitment: E::G1Affine, + + /// The accumulated MarlinPC proof. + pub(crate) accumulated_proof: E::G1Affine, +} + +impl Absorbable for AccumulatorInstance +where + E::G1Affine: Absorbable, +{ + fn to_sponge_bytes(&self) -> Vec { + collect_sponge_bytes!(E::Fq, self.accumulated_commitment, self.accumulated_proof) + } + + fn to_sponge_field_elements(&self) -> Vec<::Fq> { + collect_sponge_field_elements!(self.accumulated_commitment, self.accumulated_proof) + } +} + +/// The randomness used to apply zero-knowledge to the accumulated commitment and proof. +/// If used, the randomness is the [`Proof`][proof] of the +/// [`AtomicASForMarlinPC`][as_for_marlin_pc]. +/// +/// [Proof]: crate::AccumulationScheme::Proof +/// [as_for_marlin_pc]: crate::marlin_pc_as::AtomicASForMarlinPC +#[derive(Clone, PartialEq, Eq)] +pub struct Randomness { + /// The randomness used to blind the accumulated commitment. + pub(crate) accumulated_commitment_randomness: E::G1Affine, + + /// The randomness used to blind the accumulated proof. + pub(crate) accumulated_proof_randomness: E::G1Affine, +} + +impl Absorbable for Randomness +where + E::G1Affine: Absorbable, +{ + fn to_sponge_bytes(&self) -> Vec { + collect_sponge_bytes!( + E::Fq, + self.accumulated_commitment_randomness, + self.accumulated_proof_randomness + ) + } + + fn to_sponge_field_elements(&self) -> Vec<::Fq> { + collect_sponge_field_elements!( + self.accumulated_commitment_randomness, + self.accumulated_proof_randomness + ) + } +} diff --git a/src/marlin_pc_as/mod.rs b/src/marlin_pc_as/mod.rs new file mode 100644 index 0000000..c3165ea --- /dev/null +++ b/src/marlin_pc_as/mod.rs @@ -0,0 +1,931 @@ +use crate::data_structures::Accumulator; +use crate::error::{ASError, BoxedError}; +use crate::{AccumulationScheme, AccumulatorRef, AtomicAccumulationScheme, InputRef, MakeZK}; + +use ark_ec::{AffineCurve, PairingEngine, ProjectiveCurve}; +use ark_ff::{One, UniformRand, Zero}; +use ark_poly::polynomial::univariate::DensePolynomial; +use ark_poly_commit::{marlin_pc, Error as PCError, PolynomialCommitment}; +use ark_sponge::{absorb, Absorbable, CryptographicSponge, FieldElementSize}; +use ark_std::rand::RngCore; +use ark_std::string::ToString; +use ark_std::vec::Vec; +use core::marker::PhantomData; +use marlin_pc::MarlinKZG10; + +mod data_structures; +pub use data_structures::*; + +/// The verifier constraints of [`AtomicASForMarlinPC`]. +#[cfg(feature = "r1cs")] +pub mod constraints; + +pub(crate) const MARLIN_PC_PROTOCOL_NAME: &[u8] = b"MARLIN-PC-2020"; +pub(crate) const PROTOCOL_NAME: &[u8] = b"AS-FOR-MARLIN-PC-2020"; + +/// Size of squeezed challenges in terms of number of bits. +pub(crate) const CHALLENGE_SIZE: usize = 128; + +/// An accumulation scheme for a polynomial commitment scheme based on bilinear groups. +/// This implementation is specialized for [`MarlinKZG10`][marlin-pc]. +/// The construction is described in detail in Section 8 of [\[BCMS20\]][\[BCMS20\]]. +/// +/// The implementation substitutes power challenges with multiple independent challenges when +/// possible to lower constraint costs for the verifier. +/// See Remark 10.1 in [\[BCLMS20\]][bclms20] for more details. +/// +/// [marlin-pc]: ark_poly_commit::marlin_pc::MarlinKZG10 +/// [\[BCMS20\]]: https://eprint.iacr.org/2020/499 +/// [bclms20]: https://eprint.iacr.org/2020/1618 +/// +/// # Example Input +/// ``` +/// +/// use ark_accumulation::marlin_pc_as::{AtomicASForMarlinPC, InputInstance, MarlinPCInstance}; +/// use ark_accumulation::Input; +/// use ark_ec::{AffineCurve, PairingEngine}; +/// use ark_ff::Field; +/// use ark_poly::univariate::DensePolynomial; +/// use ark_poly_commit::marlin_pc::MarlinKZG10; +/// use ark_poly_commit::{marlin_pc, LabeledCommitment, PolynomialCommitment}; +/// use ark_sponge::{Absorbable, CryptographicSponge}; +/// +/// // An accumulation input for this scheme is formed from: +/// // 1. A MarlinPC commitment to a polynomial: `comm` +/// // 2. A point where the polynomial will be evaluated at: `point` +/// // 3. The evaluation of the polynomial at the point: `eval` +/// // 4. The MarlinPC opening at the point: `proof` +/// fn new_accumulation_input( +/// comm: LabeledCommitment>, +/// point: E::Fr, +/// eval: E::Fr, +/// proof: > as PolynomialCommitment< +/// E::Fr, +/// DensePolynomial, +/// >>::Proof, +/// ) -> Input> +/// where +/// E: PairingEngine, +/// E::G1Affine: Absorbable, +/// E::G2Affine: Absorbable, +/// E::Fq: Absorbable, +/// S: CryptographicSponge, +/// { +/// let instance = InputInstance { +/// marlin_pc_instance: MarlinPCInstance { +/// commitment: comm, +/// point, +/// evaluation: eval, +/// }, +/// marlin_pc_proof: proof, +/// }; +/// +/// let witness = (); +/// +/// Input::<_, _, AtomicASForMarlinPC> { instance, witness } +/// } +/// ``` +pub struct AtomicASForMarlinPC +where + E: PairingEngine, + E::G1Affine: Absorbable, + E::G2Affine: Absorbable, + E::Fq: Absorbable, + S: CryptographicSponge, +{ + _engine_phantom: PhantomData, + _sponge_phantom: PhantomData, +} + +impl AtomicASForMarlinPC +where + E: PairingEngine, + E::G1Affine: Absorbable, + E::G2Affine: Absorbable, + E::Fq: Absorbable, + S: CryptographicSponge, +{ + /// Generate the randomness used by the accumulation prover. + fn generate_prover_randomness( + prover_key: &ProverKey, + rng: &mut dyn RngCore, + ) -> Randomness { + let randomness = E::Fr::rand(rng); + + let accumulated_commitment_randomness_projective = prover_key.beta_g.mul(randomness); + + let accumulated_proof_randomness_projective = prover_key.marlin_vk.vk.g.mul(randomness); + + let mut affines = E::G1Projective::batch_normalization_into_affine(&[ + accumulated_commitment_randomness_projective, + accumulated_proof_randomness_projective, + ]); + + let accumulated_proof_randomness = affines.pop().unwrap(); + + let accumulated_commitment_randomness = affines.pop().unwrap(); + + Randomness { + accumulated_commitment_randomness, + accumulated_proof_randomness, + } + } + + /// Squeezes `num_challenges` accumulation challenges, where the first challenge is one. + fn squeeze_accumulation_challenges(sponge: &mut S, num_challenges: usize) -> Vec { + let mut challenges = Vec::with_capacity(num_challenges); + challenges.push(E::Fr::one()); + + if num_challenges > 1 { + let mut rest_of_challenges = sponge.squeeze_nonnative_field_elements_with_sizes( + vec![FieldElementSize::Truncated(CHALLENGE_SIZE); num_challenges - 1].as_slice(), + ); + + challenges.append(&mut rest_of_challenges); + } + + challenges + } + + /// Accumulates inputs into a single tuple: (accumulated_commitment, accumulated_proof) + /// Steps 7a, 7c of the scheme's prover and verifier common subroutine, as detailed in BCMS20. + fn accumulate_inputs<'a>( + verifier_key: &marlin_pc::VerifierKey, + inputs: &Vec<&InputInstance>, + accumulation_challenges: &Vec, + mut pc_sponge: S, + ) -> Result<(E::G1Projective, E::G1Projective), BoxedError> { + assert!(inputs.len() <= accumulation_challenges.len()); + pc_sponge.absorb(&verifier_key); + + let kzg10_vk = &verifier_key.vk; + let mut accumulated_commitment = E::G1Projective::zero(); + let mut accumulated_proof = E::G1Projective::zero(); + for (i, input) in inputs.iter().enumerate() { + let labeled_commitment = &input.marlin_pc_instance.commitment; + let degree_bound = labeled_commitment.degree_bound(); + let commitment = labeled_commitment.commitment(); + + if degree_bound.is_some() != commitment.shifted_comm.is_some() { + return Err(BoxedError::new(ASError::MalformedInput( + "The input's shifted commitment and degree bound exist or not exist together." + .to_string(), + ))); + } + + // Step 7a of the scheme's prover and verifier common subroutine, as detailed in BCMS20. + let eval = &input.marlin_pc_instance.evaluation; + + // An element in the summation used to accumulate the commitments from the inputs + let mut commitment_addend = (commitment.comm.0.into_projective() + - &kzg10_vk.g.mul(eval.clone())) + + &input + .marlin_pc_proof + .w + .mul(input.marlin_pc_instance.point.clone()); + + if let Some(random_v) = &input.marlin_pc_proof.random_v { + commitment_addend -= &kzg10_vk.gamma_g.mul(random_v.clone()); + } + + if let Some(degree_bound) = degree_bound { + // The assertion statement above ensures that shifted_comm exists if degree_bound exists. + let shifted_comm = commitment.shifted_comm.unwrap(); + let shift_power = + verifier_key + .get_shift_power(degree_bound) + .ok_or(BoxedError::new(PCError::UnsupportedDegreeBound( + degree_bound, + )))?; + + let mut opening_challenge_sponge = pc_sponge.clone(); + opening_challenge_sponge.absorb(&input.marlin_pc_instance); + + let opening_challenge: E::Fr = opening_challenge_sponge + .squeeze_nonnative_field_elements_with_sizes( + vec![FieldElementSize::Truncated(CHALLENGE_SIZE)].as_slice(), + ) + .pop() + .unwrap(); + + let shifted_comm_diff = + shifted_comm.0.into_projective() - &shift_power.mul(eval.clone()); + + commitment_addend += &shifted_comm_diff.mul(opening_challenge.into()); + } + + let challenge = accumulation_challenges[i].clone(); + + accumulated_commitment += &commitment_addend.mul(challenge.into()); + + // Step 7c of the scheme's prover and verifier common subroutine, as detailed in BCMS20. + accumulated_proof += &input.marlin_pc_proof.w.mul(challenge.into()); + } + + Ok((accumulated_commitment, accumulated_proof)) + } + + /// Accumulates inputs and old accumulators into a new accumulator from optional randomness. + fn compute_accumulator( + verifier_key: &marlin_pc::VerifierKey, + inputs: &Vec<&InputInstance>, + accumulators: &Vec<&AccumulatorInstance>, + proof: Option<&Randomness>, + sponge: S, + ) -> Result, BoxedError> { + // Step 6 of the scheme's prover and verifier common subroutine, as detailed in BCMS20. + let mut accumulation_challenge_sponge = sponge.fork(&PROTOCOL_NAME); + absorb!( + &mut accumulation_challenge_sponge, + verifier_key, + inputs, + accumulators, + proof + ); + + let num_inputs = inputs.len(); + let num_accumulators = accumulators.len(); + + let accumulation_challenges: Vec = Self::squeeze_accumulation_challenges( + &mut accumulation_challenge_sponge, + num_inputs + num_accumulators + if proof.is_some() { 1 } else { 0 }, + ); + + // Steps 7a, 7c of the scheme's prover and verifier common subroutine, as detailed in BCMS20. + // Accumulate the old commitments and proofs from the inputs + let (mut accumulated_commitment, mut accumulated_proof) = Self::accumulate_inputs( + &verifier_key, + &inputs, + &accumulation_challenges, + sponge.fork(&MARLIN_PC_PROTOCOL_NAME), + )?; + + // Step 7b of the scheme's prover and verifier common subroutine, as detailed in BCMS20. + // Accumulate the accumulators + for (i, acc) in accumulators.into_iter().enumerate() { + accumulated_commitment += &acc + .accumulated_commitment + .mul(accumulation_challenges[num_inputs + i]); + accumulated_proof += &acc + .accumulated_proof + .mul(accumulation_challenges[num_inputs + i]); + } + + // Step 7c of the scheme's prover and verifier common subroutine, as detailed in BCMS20. + // Apply the randomness from the proof + if let Some(randomness) = proof { + accumulated_commitment += randomness + .accumulated_commitment_randomness + .mul(accumulation_challenges[num_inputs + num_accumulators]); + + accumulated_proof += randomness + .accumulated_proof_randomness + .mul(accumulation_challenges[num_inputs + num_accumulators]); + } + + // Step 8 of the scheme's prover and verifier common subroutine, as detailed in BCMS20. + // Convert everything into affine and form a new accumulator instance. + let mut affines = E::G1Projective::batch_normalization_into_affine(&[ + accumulated_commitment, + accumulated_proof, + ]); + + let accumulated_proof = affines.pop().unwrap(); + let accumulated_commitment = affines.pop().unwrap(); + + let new_accumulator = AccumulatorInstance { + accumulated_commitment, + accumulated_proof, + }; + + Ok(new_accumulator) + } +} + +impl AccumulationScheme for AtomicASForMarlinPC +where + E: PairingEngine, + E::G1Affine: Absorbable, + E::G2Affine: Absorbable, + E::Fq: Absorbable, + S: CryptographicSponge, +{ + type PublicParameters = (); + type PredicateParams = marlin_pc::UniversalParams; + type PredicateIndex = PredicateIndex; + + type ProverKey = ProverKey; + type VerifierKey = marlin_pc::VerifierKey; + type DeciderKey = marlin_pc::VerifierKey; + + type InputInstance = InputInstance; + type InputWitness = (); + + type AccumulatorInstance = AccumulatorInstance; + type AccumulatorWitness = (); + + type Proof = Option>; + + type Error = BoxedError; + + fn setup(_rng: &mut impl RngCore) -> Result { + Ok(()) + } + + fn index( + _public_params: &Self::PublicParameters, + predicate_params: &Self::PredicateParams, + predicate_index: &Self::PredicateIndex, + ) -> Result<(Self::ProverKey, Self::VerifierKey, Self::DeciderKey), Self::Error> { + let enforced_degree_bounds = predicate_index + .enforced_degree_bounds + .as_ref() + .map(|bounds| bounds.as_slice()); + + let trim = MarlinKZG10::>::trim( + predicate_params, + predicate_index.supported_degree, + predicate_index.supported_hiding_bound, + enforced_degree_bounds, + ) + .map_err(|e| BoxedError::new(e))?; + + let marlin_vk = trim.1; + let beta_g = trim.0.powers[1].clone(); + let prover_key = ProverKey { + marlin_vk: marlin_vk.clone(), + beta_g, + }; + + Ok((prover_key, marlin_vk.clone(), marlin_vk)) + } + + /// Accumulates inputs and past accumulators. Additionally outputs a proof attesting that the + /// new accumulator was computed properly from the inputs and old accumulators. + fn prove<'a>( + prover_key: &Self::ProverKey, + inputs: impl IntoIterator>, + old_accumulators: impl IntoIterator>, + make_zk: MakeZK<'_>, + sponge: Option, + ) -> Result<(Accumulator, Self::Proof), Self::Error> + where + Self: 'a, + S: 'a, + { + let sponge = sponge.unwrap_or_else(|| S::new()); + + let mut inputs: Vec<&InputInstance> = InputRef::<_, _, Self>::instances(inputs) + .into_iter() + .collect::>(); + + let old_accumulators = AccumulatorRef::<_, _, Self>::instances(old_accumulators) + .into_iter() + .collect::>(); + + // Default input in the case there are no provided inputs or accumulators. + let default_input_instance; + if inputs.is_empty() && old_accumulators.is_empty() { + default_input_instance = Some(InputInstance::zero()); + inputs.push(default_input_instance.as_ref().unwrap()); + } + + let (make_zk_enabled, rng) = make_zk.into_components(); + if !make_zk_enabled { + // Check that none of the inputs to be accumulated requires hiding. + for input in &inputs { + if input.marlin_pc_proof.random_v.is_some() { + return Err(BoxedError::new(ASError::MissingRng( + "Accumulating inputs with hiding requires rng.".to_string(), + ))); + } + } + } + + // Generate the prover randomness as the proof. + let proof = if make_zk_enabled { + Some(Self::generate_prover_randomness(prover_key, rng.unwrap())) + } else { + None + }; + + // Compute an accumulator from the newly generated proof. + let accumulator_instance = Self::compute_accumulator( + &prover_key.marlin_vk, + &inputs, + &old_accumulators, + proof.as_ref(), + sponge, + )?; + + let accumulator = Accumulator::<_, _, Self> { + instance: accumulator_instance, + witness: (), + }; + + Ok((accumulator, proof)) + } + + fn verify<'a>( + verifier_key: &Self::VerifierKey, + input_instances: impl IntoIterator, + old_accumulator_instances: impl IntoIterator, + new_accumulator_instance: &Self::AccumulatorInstance, + proof: &Self::Proof, + sponge: Option, + ) -> Result + where + Self: 'a, + S: 'a, + { + let sponge = sponge.unwrap_or_else(|| S::new()); + + let mut inputs = input_instances.into_iter().collect::>(); + let old_accumulators = old_accumulator_instances.into_iter().collect::>(); + + // Default input in the case there are no provided inputs or accumulators. + let default_input_instance; + if inputs.is_empty() && old_accumulators.is_empty() { + default_input_instance = Some(InputInstance::zero()); + inputs.push(default_input_instance.as_ref().unwrap()); + } + + // Compute an accumulator from the proof. + let test_accumulator = Self::compute_accumulator( + &verifier_key, + &inputs, + &old_accumulators, + proof.as_ref(), + sponge, + ); + + if test_accumulator.is_err() { + return Ok(false); + } + + Ok(test_accumulator.unwrap().eq(new_accumulator_instance)) + } + + fn decide<'a>( + decider_key: &Self::DeciderKey, + accumulator: AccumulatorRef<'_, E::Fq, S, Self>, + _sponge: Option, + ) -> Result + where + Self: 'a, + { + let accumulator = &accumulator.instance; + + let lhs = E::pairing( + accumulator.accumulated_commitment.clone(), + decider_key.vk.h.clone(), + ); + + let rhs = E::pairing( + accumulator.accumulated_proof.clone(), + decider_key.vk.beta_h.clone(), + ); + + Ok(lhs == rhs) + } +} + +impl AtomicAccumulationScheme for AtomicASForMarlinPC +where + E: PairingEngine, + E::G1Affine: Absorbable, + E::G2Affine: Absorbable, + E::Fq: Absorbable, + S: CryptographicSponge, +{ +} + +#[cfg(test)] +pub mod tests { + use crate::data_structures::Input; + use crate::error::BoxedError; + use crate::marlin_pc_as::{ + AtomicASForMarlinPC, InputInstance, MarlinPCInstance, PredicateIndex, CHALLENGE_SIZE, + MARLIN_PC_PROTOCOL_NAME, + }; + use crate::tests::{ASTestInput, ASTests, TestParameters}; + use crate::AccumulationScheme; + use ark_bls12_377::Bls12_377; + use ark_ec::PairingEngine; + use ark_ff::UniformRand; + use ark_poly::polynomial::univariate::DensePolynomial; + use ark_poly_commit::marlin_pc::MarlinKZG10; + use ark_poly_commit::UVPolynomial; + use ark_poly_commit::{marlin_pc, LabeledPolynomial, PCCommitterKey, PolynomialCommitment}; + use ark_sponge::poseidon::PoseidonSponge; + use ark_sponge::{Absorbable, CryptographicSponge, FieldElementSize}; + use ark_std::marker::PhantomData; + use ark_std::rand::RngCore; + + pub struct AtomicASForMarlinPCTestParams { + pub(crate) max_degree: usize, + pub(crate) has_degree_bounds: bool, + pub(crate) has_hiding_bounds: bool, + } + + impl TestParameters for AtomicASForMarlinPCTestParams { + fn make_zk(&self) -> bool { + self.has_hiding_bounds + } + } + + pub struct AtomicASForMarlinPCTestInputParams { + has_hiding_bounds: bool, + ck: marlin_pc::CommitterKey, + vk: marlin_pc::VerifierKey, + } + + pub struct AtomicASForMarlinPCTestInput> { + _engine: PhantomData, + _sponge: PhantomData, + } + + impl ASTestInput> for AtomicASForMarlinPCTestInput + where + E: PairingEngine, + E::G1Affine: Absorbable, + E::G2Affine: Absorbable, + E::Fq: Absorbable, + S: CryptographicSponge, + { + type TestParams = AtomicASForMarlinPCTestParams; + type InputParams = AtomicASForMarlinPCTestInputParams; + + fn setup( + test_params: &Self::TestParams, + rng: &mut impl RngCore, + ) -> ( + Self::InputParams, + as AccumulationScheme>::PredicateParams, + as AccumulationScheme>::PredicateIndex, + ) { + let supported_degree = test_params.max_degree; + let predicate_params = + MarlinKZG10::>::setup(supported_degree, None, rng) + .unwrap(); + + let supported_hiding_bound = if test_params.has_hiding_bounds { + supported_degree + } else { + 0usize + }; + + let enforced_degree_bounds: Option> = if test_params.has_degree_bounds { + let degree_bounds = (0..=supported_degree).collect(); + Some(degree_bounds) + } else { + None + }; + + let (ck, vk) = MarlinKZG10::>::trim( + &predicate_params, + supported_degree, + supported_hiding_bound, + enforced_degree_bounds.as_ref().map(|v| v.as_slice()), + ) + .unwrap(); + + let input_params = AtomicASForMarlinPCTestInputParams { + has_hiding_bounds: test_params.has_hiding_bounds, + ck, + vk, + }; + + let predicate_index = PredicateIndex { + supported_degree, + supported_hiding_bound, + enforced_degree_bounds, + }; + + (input_params, predicate_params, predicate_index) + } + + fn generate_inputs( + input_params: &Self::InputParams, + num_inputs: usize, + rng: &mut impl RngCore, + ) -> Vec>> { + let ck = &input_params.ck; + let vk = &input_params.vk; + + let mut challenge_sponge = S::new().fork(&MARLIN_PC_PROTOCOL_NAME); + challenge_sponge.absorb(vk); + + let labeled_polynomials: Vec>> = (0 + ..num_inputs) + .map(|i| { + let degree = ck.supported_degree(); + let label = format!("Input{}", i); + let polynomial = DensePolynomial::rand(degree, rng); + + let hiding_bound = if input_params.has_hiding_bounds { + Some(degree) + } else { + None + }; + + let degree_bound = if input_params.ck.enforced_degree_bounds.is_some() { + Some(degree) + } else { + None + }; + + let labeled_polynomial = + LabeledPolynomial::new(label, polynomial, degree_bound, hiding_bound); + + labeled_polynomial + }) + .collect(); + + let (labeled_commitments, randomness) = + MarlinKZG10::commit(ck, &labeled_polynomials, Some(rng)).unwrap(); + + let mut inputs = Vec::with_capacity(num_inputs); + for ((labeled_polynomial, labeled_commitment), rand) in labeled_polynomials + .into_iter() + .zip(labeled_commitments) + .zip(randomness) + { + let point = E::Fr::rand(rng); + let evaluation = labeled_polynomial.evaluate(&point); + + let instance = MarlinPCInstance { + commitment: labeled_commitment.clone(), + point, + evaluation, + }; + + let mut pc_sponge = challenge_sponge.clone(); + pc_sponge.absorb(&instance); + + let challenge: E::Fr = pc_sponge + .squeeze_nonnative_field_elements_with_sizes( + vec![FieldElementSize::Truncated(CHALLENGE_SIZE)].as_slice(), + ) + .pop() + .unwrap(); + + let proof = MarlinKZG10::open( + ck, + vec![&labeled_polynomial], + vec![&labeled_commitment], + &point, + challenge, + vec![&rand], + Some(rng), + ) + .unwrap(); + + let input = InputInstance { + marlin_pc_instance: MarlinPCInstance { + commitment: labeled_commitment, + point, + evaluation, + }, + marlin_pc_proof: proof, + }; + + inputs.push(input); + } + + inputs + .into_iter() + .map(|input| Input::<_, _, AtomicASForMarlinPC> { + instance: input, + witness: (), + }) + .collect() + } + } + + type E = Bls12_377; + type CF = ark_bls12_377::Fq; + + type Sponge = PoseidonSponge; + + type AS = AtomicASForMarlinPC; + type I = AtomicASForMarlinPCTestInput; + + type Tests = ASTests; + + #[test] + pub fn single_input_init_test_no_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::single_input_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn single_input_init_test_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::single_input_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn multiple_inputs_init_test_no_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::multiple_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn multiple_input_init_test_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::multiple_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn simple_accumulation_test_no_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::simple_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn simple_accumulation_test_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::simple_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn multiple_inputs_accumulation_test_no_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::multiple_inputs_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn multiple_inputs_accumulation_test_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::multiple_inputs_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn accumulators_only_test_no_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::accumulators_only_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn accumulators_only_test_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::accumulators_only_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn no_inputs_init_test_no_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::no_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn no_inputs_init_test_zk_no_degree_bounds() -> Result<(), BoxedError> { + Tests::no_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: false, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn single_input_init_test_no_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::single_input_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn single_input_init_test_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::single_input_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn multiple_inputs_init_test_no_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::multiple_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn multiple_input_init_test_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::multiple_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn simple_accumulation_test_no_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::simple_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn simple_accumulation_test_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::simple_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn multiple_inputs_accumulation_test_no_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::multiple_inputs_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn multiple_inputs_accumulation_test_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::multiple_inputs_accumulation_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn accumulators_only_test_no_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::accumulators_only_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn accumulators_only_test_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::accumulators_only_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } + + #[test] + pub fn no_inputs_init_test_no_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::no_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: false, + }) + } + + #[test] + pub fn no_inputs_init_test_zk_degree_bounds() -> Result<(), BoxedError> { + Tests::no_inputs_init_test(&AtomicASForMarlinPCTestParams { + max_degree: 11, + has_degree_bounds: true, + has_hiding_bounds: true, + }) + } +}