diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b93c6623..400622a32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: - run: RUSTDOCFLAGS="-D warnings --cfg doc_cfg" cargo +nightly doc --workspace --all-features --no-deps --document-private-items lint: name: Lint (${{ matrix.os }} + ${{ matrix.channel }}) - needs: [workspace-hack-check, format, format-cargo-toml, docs] + needs: [format, format-cargo-toml, docs] strategy: fail-fast: false matrix: @@ -68,7 +68,7 @@ jobs: - run: cargo hack clippy --workspace --feature-powerset --tests test: name: Test (${{ matrix.os }} + ${{ matrix.channel }}) - needs: [workspace-hack-check, format, format-cargo-toml, docs] + needs: [format, format-cargo-toml, docs] strategy: fail-fast: false matrix: @@ -87,7 +87,7 @@ jobs: - run: cargo nextest run --workspace --release --all-features compile-bench: name: Compile Benchmarks (${{ matrix.os }} + ${{ matrix.channel }}) - needs: [workspace-hack-check, format, format-cargo-toml, docs] + needs: [format, format-cargo-toml, docs] strategy: fail-fast: false matrix: diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c7f2deb..9c6fe5aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] ### Added +- [\#172](https://github.com/Manta-Network/manta-rs/pull/172) Add abstract Phase 2 for Groth16 trusted setup ### Changed @@ -99,7 +100,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [\#50](https://github.com/Manta-Network/manta-rs/pull/50) Remove Trapdoor from Circuit [Unreleased]: https://github.com/Manta-Network/manta-rs/compare/v0.5.4...HEAD -[0.5.3]: https://github.com/Manta-Network/manta-rs/releases/tag/v0.5.4 +[0.5.4]: https://github.com/Manta-Network/manta-rs/releases/tag/v0.5.4 [0.5.3]: https://github.com/Manta-Network/manta-rs/releases/tag/v0.5.3 [0.5.2]: https://github.com/Manta-Network/manta-rs/releases/tag/v0.5.2 [0.5.1]: https://github.com/Manta-Network/manta-rs/releases/tag/v0.5.1 diff --git a/manta-trusted-setup/Cargo.toml b/manta-trusted-setup/Cargo.toml index 7cbfd7d17..79d96e896 100644 --- a/manta-trusted-setup/Cargo.toml +++ b/manta-trusted-setup/Cargo.toml @@ -26,22 +26,33 @@ maintenance = { status = "actively-developed" } [features] # Rayon Parallelization -rayon = ["dep:rayon", "manta-util/rayon"] +rayon = ["manta-util/rayon"] # Standard Library std = [] +# Testing Frameworks +test = [] + [dependencies] ark-ec = { version = "0.3.0", default-features = false } ark-ff = { version = "0.3.0", default-features = false } ark-groth16 = { version = "0.3.0", default-features = false } -ark-r1cs-std = { version = "0.3.1", default-features = false } +ark-poly = { version = "0.3.0", default-features = false } ark-relations = { version = "0.3.0", default-features = false } ark-serialize = { version = "0.3.0", default-features = false, features = ["derive"] } -ark-snark = { version = "0.3.0", default-features = false } ark-std = { version = "0.3.0", default-features = false } +blake2 = { version = "0.10.4", default-features = false } +byteorder = { version = "1.4.3", default-features = false } derivative = { version = "2.2.0", default-features = false, features = ["use_core"] } manta-crypto = { path = "../manta-crypto", default-features = false, features = ["getrandom"] } -manta-util = { path = "../manta-util", default-features = false, features = ["alloc"] } -rayon = { version = "1.5.3", optional = true, default-features = false } +manta-util = { path = "../manta-util", default-features = false } +rand_chacha = { version = "0.3.1", default-features = false } workspace-hack = { version = "0.1.0", path = "../workspace-hack" } + +[dev-dependencies] +ark-bls12-381 = { version = "0.3.0", default-features = false, features = ["curve", "scalar_field"] } +ark-r1cs-std = { version = "0.3.1", default-features = false } +ark-snark = { version = "0.3.0", default-features = false } +manta-pay = { path = "../manta-pay", default-features = false, features = ["groth16"] } # TODO: To be removed +manta-trusted-setup = { path = ".", default-features = false, features = ["test"] } diff --git a/manta-trusted-setup/src/groth16/kzg.rs b/manta-trusted-setup/src/groth16/kzg.rs index 22e22b72e..3df74c5f7 100644 --- a/manta-trusted-setup/src/groth16/kzg.rs +++ b/manta-trusted-setup/src/groth16/kzg.rs @@ -16,20 +16,20 @@ //! KZG Trusted Setup for Groth16 -use crate::util::{ - power_pairs, scalar_mul, CanonicalDeserialize, CanonicalSerialize, Deserializer, - HasDistribution, NonZero, One, PairingEngineExt, Read, Sample, SerializationError, Serializer, - Write, Zero, +use crate::{ + pairing::{Pairing, PairingEngineExt}, + ratio::{HashToGroup, RatioProof}, + util::{power_pairs, scalar_mul, Deserializer, NonZero, Sample, Serializer}, }; use alloc::{vec, vec::Vec}; -use ark_ec::{AffineCurve, PairingEngine, ProjectiveCurve}; -use ark_ff::{PrimeField, UniformRand}; +use ark_ff::{One, UniformRand}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; use core::{iter, ops::Mul}; -use manta_crypto::rand::{CryptoRng, Rand, RngCore}; +use manta_crypto::rand::{CryptoRng, RngCore}; use manta_util::{cfg_iter, cfg_iter_mut, from_variant, vec::VecExt}; #[cfg(feature = "rayon")] -use rayon::iter::{IndexedParallelIterator, ParallelIterator}; +use manta_util::rayon::iter::{IndexedParallelIterator, ParallelIterator}; /// KZG Trusted Setup Size pub trait Size { @@ -44,42 +44,6 @@ pub trait Size { const G2_POWERS: usize; } -/// Pairing Configuration -pub trait Pairing: HasDistribution { - /// Underlying Scalar Field - type Scalar: PrimeField; - - /// First Group of the Pairing - type G1: AffineCurve - + Into - + Sample; - - /// First Group Pairing-Prepared Point - type G1Prepared; - - /// Second Group of the Pairing - type G2: AffineCurve - + Into - + Sample; - - /// Second Group Pairing-Prepared Point - type G2Prepared; - - /// Pairing Engine Type - type Pairing: PairingEngine< - G1Affine = Self::G1, - G2Affine = Self::G2, - G1Prepared = Self::G1Prepared, - G2Prepared = Self::G2Prepared, - >; - - /// Returns the base G1 generator for this configuration. - fn g1_prime_subgroup_generator() -> Self::G1; - - /// Returns the base G2 generator for this configuration. - fn g2_prime_subgroup_generator() -> Self::G2; -} - /// Trusted Setup Configuration pub trait Configuration: Pairing + Size { /// Domain Tag @@ -91,21 +55,20 @@ pub trait Configuration: Pairing + Size { /// Response Type type Response; - /// Tau Domain Tag for [`hash_to_g2`](Self::hash_to_g2) + /// Hash To Group Type + type HashToGroup: HashToGroup; + + /// Tau Domain Tag Type const TAU_DOMAIN_TAG: Self::DomainTag; - /// Alpha Domain Tag for [`hash_to_g2`](Self::hash_to_g2) + /// Alpha Domain Tag Type const ALPHA_DOMAIN_TAG: Self::DomainTag; - /// Beta Domain Tag for [`hash_to_g2`](Self::hash_to_g2) + /// Beta Domain Tag Type const BETA_DOMAIN_TAG: Self::DomainTag; - /// Computes the challenge G2 point from `domain_tag`, `challenge`, and `ratio`. - fn hash_to_g2( - domain_tag: Self::DomainTag, - challenge: &Self::Challenge, - ratio: (&Self::G1, &Self::G1), - ) -> Self::G2; + /// Generates a [`HashToGroup`](Self::HashToGroup) instance paramterized by `domain_tag`. + fn hasher(domain_tag: Self::DomainTag) -> Self::HashToGroup; /// Computes the challenge response from `state`, `challenge`, and `proof`. fn response( @@ -190,9 +153,9 @@ where R: CryptoRng + RngCore + ?Sized, { Some(Proof { - tau: RatioProof::build(C::TAU_DOMAIN_TAG, challenge, &self.tau, rng)?, - alpha: RatioProof::build(C::ALPHA_DOMAIN_TAG, challenge, &self.alpha, rng)?, - beta: RatioProof::build(C::BETA_DOMAIN_TAG, challenge, &self.beta, rng)?, + tau: RatioProof::prove(&C::hasher(C::TAU_DOMAIN_TAG), challenge, &self.tau, rng)?, + alpha: RatioProof::prove(&C::hasher(C::ALPHA_DOMAIN_TAG), challenge, &self.alpha, rng)?, + beta: RatioProof::prove(&C::hasher(C::BETA_DOMAIN_TAG), challenge, &self.beta, rng)?, }) } } @@ -214,88 +177,6 @@ where } } -/// Pairing Ratio Proof of Knowledge -#[derive(derivative::Derivative)] -#[derivative(Clone, Debug, Default, Eq, Hash, PartialEq)] -pub struct RatioProof -where - C: Pairing + ?Sized, -{ - /// Ratio in G1 - pub ratio: (C::G1, C::G1), - - /// Matching Point in G2 - pub matching_point: C::G2, -} - -impl RatioProof -where - C: Pairing, -{ - /// Builds a [`RatioProof`] for `scalar` against `challenge`. - #[inline] - pub fn build( - domain_tag: C::DomainTag, - challenge: &C::Challenge, - scalar: &C::Scalar, - rng: &mut R, - ) -> Option - where - C: Configuration, - R: CryptoRng + RngCore + ?Sized, - { - let g1_point = rng.gen::<_, C::G1>(); - if g1_point.is_zero() { - return None; - } - let scaled_g1_point = g1_point.mul(*scalar).into_affine(); - if scaled_g1_point.is_zero() { - return None; - } - let ratio = (g1_point, scaled_g1_point); - let g2_point = C::hash_to_g2(domain_tag, challenge, (&ratio.0, &ratio.1)); - if g2_point.is_zero() { - return None; - } - let scaled_g2_point = g2_point.mul(*scalar).into_affine(); - if scaled_g2_point.is_zero() { - return None; - } - Some(Self { - ratio, - matching_point: scaled_g2_point, - }) - } - - /// Computes the challenge point that corresponds with the given `challenge`. - #[inline] - pub fn challenge_point(&self, domain_tag: C::DomainTag, challenge: &C::Challenge) -> C::G2 - where - C: Configuration, - { - C::hash_to_g2(domain_tag, challenge, (&self.ratio.0, &self.ratio.1)) - } - - /// Verifies that `self` is a valid ratio proof-of-knowledge, returning the G2 ratio of the - /// underlying scalar. - #[inline] - pub fn verify( - self, - domain_tag: C::DomainTag, - challenge: &C::Challenge, - ) -> Option<(C::G2Prepared, C::G2Prepared)> - where - C: Configuration, - { - let challenge_point = self.challenge_point(domain_tag, challenge); - let ((_, matching_point), (_, challenge_point)) = C::Pairing::same( - (self.ratio.0, self.matching_point), - (self.ratio.1, challenge_point), - )?; - Some((matching_point, challenge_point)) - } -} - /// Knowledge Proof Certificate pub struct KnowledgeProofCertificate where @@ -345,16 +226,19 @@ where Ok(KnowledgeProofCertificate { tau: self .tau - .verify(C::TAU_DOMAIN_TAG, challenge) - .ok_or(KnowledgeError::TauKnowledgeProof)?, + .verify(&C::hasher(C::TAU_DOMAIN_TAG), challenge) + .ok_or(KnowledgeError::TauKnowledgeProof)? + .1, alpha: self .alpha - .verify(C::ALPHA_DOMAIN_TAG, challenge) - .ok_or(KnowledgeError::AlphaKnowledgeProof)?, + .verify(&C::hasher(C::ALPHA_DOMAIN_TAG), challenge) + .ok_or(KnowledgeError::AlphaKnowledgeProof)? + .1, beta: self .beta - .verify(C::BETA_DOMAIN_TAG, challenge) - .ok_or(KnowledgeError::BetaKnowledgeProof)?, + .verify(&C::hasher(C::BETA_DOMAIN_TAG), challenge) + .ok_or(KnowledgeError::BetaKnowledgeProof)? + .1, }) } } @@ -502,19 +386,19 @@ where C: Pairing + Size + ?Sized, { /// Vector of Tau Powers in G1 of size [`G1_POWERS`](Size::G1_POWERS) - tau_powers_g1: Vec, + pub tau_powers_g1: Vec, /// Vector of Tau Powers in G2 of size [`G2_POWERS`](Size::G2_POWERS) - tau_powers_g2: Vec, + pub tau_powers_g2: Vec, /// Vector of Alpha Multiplied by Tau Powers in G1 of size [`G2_POWERS`](Size::G2_POWERS) - alpha_tau_powers_g1: Vec, + pub alpha_tau_powers_g1: Vec, /// Vector of Beta Multiplied by Tau Powers in G1 of size [`G2_POWERS`](Size::G2_POWERS) - beta_tau_powers_g1: Vec, + pub beta_tau_powers_g1: Vec, /// Beta in G2 - beta_g2: C::G2, + pub beta_g2: C::G2, } impl Accumulator diff --git a/manta-trusted-setup/src/groth16/mod.rs b/manta-trusted-setup/src/groth16/mod.rs index d7ac2bd27..b652f5b00 100644 --- a/manta-trusted-setup/src/groth16/mod.rs +++ b/manta-trusted-setup/src/groth16/mod.rs @@ -17,3 +17,7 @@ //! Groth16 Trusted Setup pub mod kzg; +pub mod mpc; + +#[cfg(test)] +pub mod test; diff --git a/manta-trusted-setup/src/groth16/mpc.rs b/manta-trusted-setup/src/groth16/mpc.rs new file mode 100644 index 000000000..1f3585b14 --- /dev/null +++ b/manta-trusted-setup/src/groth16/mpc.rs @@ -0,0 +1,388 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Groth16 MPC + +use crate::{ + groth16::kzg::{self, Accumulator}, + pairing::{Pairing, PairingEngineExt}, + ratio::{HashToGroup, RatioProof}, + util::{batch_into_projective, batch_mul_fixed_scalar, merge_pairs_affine}, +}; +use alloc::{vec, vec::Vec}; +use ark_ec::{AffineCurve, ProjectiveCurve}; +use ark_ff::{Field, PrimeField, UniformRand, Zero}; +use ark_groth16::{ProvingKey, VerifyingKey}; +use ark_poly::{EvaluationDomain, Radix2EvaluationDomain}; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem, SynthesisError}; +use core::clone::Clone; +use manta_crypto::rand::{CryptoRng, RngCore}; + +/// Proving Key Hasher +pub trait ProvingKeyHasher

+where + P: Pairing, +{ + /// Output Type + type Output; + + /// Hashes the Groth16 `proving_key` state. + fn hash(proving_key: &ProvingKey) -> Self::Output; +} + +/// MPC State +pub type State

= ProvingKey<

::Pairing>; + +/// MPC Proof +pub type Proof

= RatioProof

; + +/// MPC Error +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Error { + /// Too Many Constraints Error + TooManyConstraints, + + /// Constraint System Error + ConstraintSystemError(SynthesisError), + + /// Missing Constraint System Matrices Error + MissingCSMatrices, + + /// Constraint System Hashes Mismatch Error + ConstraintSystemHashesDiffer, + + /// Invalid Ratio Proof Error + InvalidRatioProof, + + /// Inconsistent Delta Change Error + InconsistentDeltaChange, + + /// Inconsistent L Change Error + InconsistentLChange, + + /// Inconsistent H Change Error + InconsistentHChange, + + /// Invariant Violation Error + InvariantViolated(&'static str), +} + +/// Specialize all phase1 parameters to phase2 parameters (except `h_query`) at once. This is like +/// doing `eval_poly` in parallel on all four kinds of commitments that phase2 params require +/// (`a_g1`, `b_g1`, `b_g2`, `extra`) where `extra` is the cross term that we (may wish to) compute +/// separately for public/private inputs. +#[inline] +#[allow(clippy::too_many_arguments)] +pub fn specialize_to_phase_2( + tau_basis_g1: &[G1], + tau_basis_g2: &[G2], + alpha_tau_basis: &[G1], + beta_tau_basis: &[G1], + a_poly: &[Vec<(G1::ScalarField, usize)>], + b_poly: &[Vec<(G1::ScalarField, usize)>], + c_poly: &[Vec<(G1::ScalarField, usize)>], + a_g1: &mut [G1], + b_g1: &mut [G1], + b_g2: &mut [G2], + ext: &mut [G1], +) where + G1: ProjectiveCurve, + G2: ProjectiveCurve, +{ + assert_eq!(a_g1.len(), b_g1.len()); + assert_eq!(a_g1.len(), b_g2.len()); + assert_eq!(a_g1.len(), ext.len()); + a_poly + .iter() + .zip(b_poly.iter()) + .zip(c_poly.iter()) + .zip(tau_basis_g1.iter()) + .zip(tau_basis_g2.iter()) + .zip(alpha_tau_basis.iter()) + .zip(beta_tau_basis.iter()) + .for_each( + |((((((a_poly, b_poly), c_poly), tau_g1), tau_g2), alpha_tau), beta_tau)| { + for (coeff, index) in a_poly.iter() { + a_g1[*index] += tau_g1.mul(coeff.into_repr()); + ext[*index] += beta_tau.mul(coeff.into_repr()); + } + for (coeff, index) in b_poly.iter() { + b_g1[*index] += tau_g1.mul(coeff.into_repr()); + b_g2[*index] += tau_g2.mul(coeff.into_repr()); + ext[*index] += alpha_tau.mul(coeff.into_repr()); + } + for (coeff, index) in c_poly.iter() { + ext[*index] += tau_g1.mul(coeff.into_repr()); + } + }, + ); +} + +/// Checks that the parameters which are not changed by contributions are the same. +#[inline] +pub fn check_invariants

(prev: &State

, next: &State

) -> Result<(), Error> +where + P: Pairing, +{ + if prev.h_query.len() != next.h_query.len() { + return Err(Error::InvariantViolated("H length changed")); + } + if prev.l_query.len() != next.l_query.len() { + return Err(Error::InvariantViolated("L length changed")); + } + if prev.a_query != next.a_query { + return Err(Error::InvariantViolated("A query changed")); + } + if prev.b_g1_query != next.b_g1_query { + return Err(Error::InvariantViolated("B_G1 query changed")); + } + if prev.b_g2_query != next.b_g2_query { + return Err(Error::InvariantViolated("B_G2 query changed")); + } + if prev.vk.alpha_g1 != next.vk.alpha_g1 { + return Err(Error::InvariantViolated("alpha_G1 changed")); + } + if prev.beta_g1 != next.beta_g1 { + return Err(Error::InvariantViolated("beta_G1 changed")); + } + if prev.vk.beta_g2 != next.vk.beta_g2 { + return Err(Error::InvariantViolated("beta_G2 changed")); + } + if prev.vk.gamma_g2 != next.vk.gamma_g2 { + return Err(Error::InvariantViolated("gamma_G2 changed")); + } + if prev.vk.gamma_abc_g1 != next.vk.gamma_abc_g1 { + return Err(Error::InvariantViolated("Public input cross terms changed")); + } + Ok(()) +} + +/// Initialize [`State`] using the KZG accumulator `powers` and the given `constraint_system`. +#[inline] +pub fn initialize(powers: Accumulator, constraint_system: S) -> Result, Error> +where + C: kzg::Configuration, + S: ConstraintSynthesizer, +{ + let constraints = ConstraintSystem::new_ref(); + constraint_system + .generate_constraints(constraints.clone()) + .map_err(Error::ConstraintSystemError)?; + constraints.finalize(); + let num_constraints = constraints.num_constraints(); + let num_instance_variables = constraints.num_instance_variables(); + let domain = match Radix2EvaluationDomain::new(num_constraints + num_instance_variables) { + Some(domain) => domain, + None => return Err(Error::TooManyConstraints), + }; + let constraint_matrices = match constraints.to_matrices() { + Some(matrices) => matrices, + None => return Err(Error::MissingCSMatrices), + }; + let beta_g1 = powers.beta_tau_powers_g1[0]; + let degree = domain.size as usize; + let mut h_query = Vec::with_capacity(degree - 1); + for i in 0..degree { + let tmp = powers.tau_powers_g1[i + degree].into_projective(); + let tmp2 = powers.tau_powers_g1[i].into_projective(); + h_query.push((tmp - tmp2).into_affine()); + } + let tau_lagrange_g1 = domain.ifft(&batch_into_projective(&powers.tau_powers_g1)); + let tau_lagrange_g2 = domain.ifft(&batch_into_projective(&powers.tau_powers_g2)); + let alpha_lagrange_g1 = domain.ifft(&batch_into_projective(&powers.alpha_tau_powers_g1)); + let beta_lagrange_g1 = domain.ifft(&batch_into_projective(&powers.beta_tau_powers_g1)); + let num_witnesses = + constraint_matrices.num_witness_variables + constraint_matrices.num_instance_variables; + let mut a_g1 = vec![C::G1::zero().into_projective(); num_witnesses]; + let mut b_g1 = vec![C::G1::zero().into_projective(); num_witnesses]; + let mut b_g2 = vec![C::G2::zero().into_projective(); num_witnesses]; + let mut ext = vec![C::G1::zero().into_projective(); num_witnesses]; + { + let start = 0; + let end = num_instance_variables; + a_g1[start..end] + .copy_from_slice(&tau_lagrange_g1[(start + num_constraints)..(end + num_constraints)]); + ext[start..end] + .copy_from_slice(&beta_lagrange_g1[(start + num_constraints)..(end + num_constraints)]); + } + specialize_to_phase_2( + &tau_lagrange_g1, + &tau_lagrange_g2, + &alpha_lagrange_g1, + &beta_lagrange_g1, + &constraint_matrices.a, + &constraint_matrices.b, + &constraint_matrices.c, + &mut a_g1, + &mut b_g1, + &mut b_g2, + &mut ext, + ); + let a_query = ProjectiveCurve::batch_normalization_into_affine(&a_g1); + let b_g1_query = ProjectiveCurve::batch_normalization_into_affine(&b_g1); + let b_g2_query = ProjectiveCurve::batch_normalization_into_affine(&b_g2); + let ext = ProjectiveCurve::batch_normalization_into_affine(&ext); + let public_cross_terms = Vec::from(&ext[..constraint_matrices.num_instance_variables]); + let private_cross_terms = Vec::from(&ext[constraint_matrices.num_instance_variables..]); + let vk = VerifyingKey { + alpha_g1: powers.alpha_tau_powers_g1[0], + beta_g2: powers.beta_g2, + gamma_g2: C::g2_prime_subgroup_generator(), + delta_g2: C::g2_prime_subgroup_generator(), + gamma_abc_g1: public_cross_terms, + }; + Ok(ProvingKey { + vk, + beta_g1, + delta_g1: C::g1_prime_subgroup_generator(), + a_query, + b_g1_query, + b_g2_query, + h_query, + l_query: private_cross_terms, + }) +} + +/// Configuration +pub trait Configuration: Pairing { + /// Challenge Type + type Challenge; + + /// Hasher Type + type Hasher: Default + HashToGroup; + + /// Generates the next [`Challenge`](Self::Challenge) from `challenge`, `prev` state, `next` state, + /// and `proof`. + fn challenge( + challenge: &Self::Challenge, + prev: &State, + next: &State, + proof: &Proof, + ) -> Self::Challenge; +} + +/// Contributes to `state` with `hasher`, `challenge`, and `rng`, returning a [`proof`](Proof). +#[inline] +pub fn contribute( + hasher: &C::Hasher, + challenge: &C::Challenge, + state: &mut State, + rng: &mut R, +) -> Option> +where + C: Configuration, + R: CryptoRng + RngCore + ?Sized, +{ + let delta = C::Scalar::rand(rng); + let delta_inverse = match delta.inverse() { + Some(delta_inverse) => delta_inverse, + _ => return None, + }; + batch_mul_fixed_scalar(&mut state.l_query, delta_inverse); + batch_mul_fixed_scalar(&mut state.h_query, delta_inverse); + state.delta_g1 = state.delta_g1.mul(delta).into_affine(); + state.vk.delta_g2 = state.vk.delta_g2.mul(delta).into_affine(); + Proof::prove(hasher, challenge, &delta, rng) +} + +/// Verifies transforming from `prev` to `next` is correct given `challenge` and `proof`. +#[inline] +pub fn verify_transform( + challenge: &C::Challenge, + prev: State, + next: State, + proof: Proof, +) -> Result<(C::Challenge, State), Error> +where + C: Configuration, +{ + check_invariants::(&prev, &next)?; + let next_challenge = C::challenge(challenge, &prev, &next, &proof); + let ((ratio_0, ratio_1), _) = proof + .verify(&C::Hasher::default(), challenge) + .ok_or(Error::InvalidRatioProof)?; + if !C::Pairing::same_ratio((ratio_0, ratio_1), (prev.vk.delta_g2, next.vk.delta_g2)) { + return Err(Error::InconsistentDeltaChange); + } + if !C::Pairing::same_ratio( + (prev.delta_g1, next.delta_g1), + (prev.vk.delta_g2, next.vk.delta_g2), + ) { + return Err(Error::InconsistentDeltaChange); + } + if !C::Pairing::same_ratio( + merge_pairs_affine(&next.h_query, &prev.h_query), + (prev.vk.delta_g2, next.vk.delta_g2), + ) { + return Err(Error::InconsistentHChange); + } + if !C::Pairing::same_ratio( + merge_pairs_affine(&next.l_query, &prev.l_query), + (prev.vk.delta_g2, next.vk.delta_g2), + ) { + return Err(Error::InconsistentLChange); + } + Ok((next_challenge, next)) +} + +/// Verifies all contributions in `iter` chaining from an initial `state` and `challenge` +/// returning the newest [`State`](State) and [`Challenge`](Configuration::Challenge) if all the +/// contributions in the chain had valid transitions. +#[inline] +pub fn verify_transform_all( + mut challenge: C::Challenge, + mut state: State, + iter: I, +) -> Result<(), Error> +where + C: Configuration, + I: IntoIterator, Proof)>, +{ + let initial_state = state.clone(); + for (next_state, next_proof) in iter { + let next_challenge = C::challenge(&challenge, &state, &next_state, &next_proof); + let ((ratio_0, ratio_1), _) = next_proof + .verify(&C::Hasher::default(), &challenge) + .ok_or(Error::InvalidRatioProof)?; + if !C::Pairing::same_ratio( + (ratio_0, ratio_1), + (state.vk.delta_g2, next_state.vk.delta_g2), + ) { + return Err(Error::InconsistentDeltaChange); + } + (state, challenge) = (next_state, next_challenge); + } + check_invariants::(&initial_state, &state)?; + if !C::Pairing::same_ratio( + (initial_state.delta_g1, state.delta_g1), + (initial_state.vk.delta_g2, state.vk.delta_g2), + ) { + return Err(Error::InconsistentDeltaChange); + } + if !C::Pairing::same_ratio( + merge_pairs_affine(&state.h_query, &initial_state.h_query), + (initial_state.vk.delta_g2, state.vk.delta_g2), + ) { + return Err(Error::InconsistentHChange); + } + if !C::Pairing::same_ratio( + merge_pairs_affine(&state.l_query, &initial_state.l_query), + (initial_state.vk.delta_g2, state.vk.delta_g2), + ) { + return Err(Error::InconsistentLChange); + } + Ok(()) +} diff --git a/manta-trusted-setup/src/groth16/test.rs b/manta-trusted-setup/src/groth16/test.rs new file mode 100644 index 000000000..c60b84212 --- /dev/null +++ b/manta-trusted-setup/src/groth16/test.rs @@ -0,0 +1,264 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Groth16 Trusted Setup Testing + +use crate::{ + groth16::{ + kzg::{self, Accumulator, Configuration, Contribution, Size}, + mpc::{self, contribute, initialize, verify_transform, verify_transform_all, Proof, State}, + }, + mpc::{Transcript, Types}, + pairing::Pairing, + ratio::test::assert_valid_ratio_proof, + util::{BlakeHasher, HasDistribution, KZGBlakeHasher, Sample}, +}; +use alloc::vec::Vec; +use ark_bls12_381::Fr; +use ark_ec::{AffineCurve, PairingEngine}; +use ark_ff::{field_new, UniformRand}; +use ark_groth16::{Groth16, ProvingKey}; +use ark_r1cs_std::eq::EqGadget; +use ark_serialize::CanonicalSerialize; +use ark_snark::SNARK; +use blake2::Digest; +use manta_crypto::{ + constraint::Allocate, + eclair::alloc::mode::{Public, Secret}, + rand::{CryptoRng, OsRng, RngCore}, +}; +use manta_pay::crypto::constraint::arkworks::{Fp, FpVar, R1CS}; +use manta_util::into_array_unchecked; + +/// Test MPC +#[derive(Clone, Default)] +pub struct Test; + +impl Size for Test { + const G1_POWERS: usize = (Self::G2_POWERS << 1) - 1; + const G2_POWERS: usize = 1 << 3; +} + +impl HasDistribution for Test { + type Distribution = (); +} + +impl Pairing for Test { + type Scalar = ark_bls12_381::Fr; + type G1 = ark_bls12_381::G1Affine; + type G1Prepared = ::G1Prepared; + type G2 = ark_bls12_381::G2Affine; + type G2Prepared = ::G2Prepared; + type Pairing = ark_bls12_381::Bls12_381; + + #[inline] + fn g1_prime_subgroup_generator() -> Self::G1 { + ark_bls12_381::G1Affine::prime_subgroup_generator() + } + + #[inline] + fn g2_prime_subgroup_generator() -> Self::G2 { + ark_bls12_381::G2Affine::prime_subgroup_generator() + } +} + +impl kzg::Configuration for Test { + type DomainTag = u8; + type Challenge = [u8; 64]; + type Response = [u8; 64]; + type HashToGroup = KZGBlakeHasher; + + const TAU_DOMAIN_TAG: Self::DomainTag = 0; + const ALPHA_DOMAIN_TAG: Self::DomainTag = 1; + const BETA_DOMAIN_TAG: Self::DomainTag = 2; + + #[inline] + fn hasher(domain_tag: Self::DomainTag) -> Self::HashToGroup { + Self::HashToGroup { domain_tag } + } + + #[inline] + fn response( + state: &Accumulator, + challenge: &Self::Challenge, + proof: &kzg::Proof, + ) -> Self::Response { + let mut hasher = BlakeHasher::default(); + for item in &state.tau_powers_g1 { + item.serialize_uncompressed(&mut hasher).unwrap(); + } + for item in &state.tau_powers_g2 { + item.serialize_uncompressed(&mut hasher).unwrap(); + } + for item in &state.alpha_tau_powers_g1 { + item.serialize_uncompressed(&mut hasher).unwrap(); + } + for item in &state.beta_tau_powers_g1 { + item.serialize_uncompressed(&mut hasher).unwrap(); + } + state.beta_g2.serialize_uncompressed(&mut hasher).unwrap(); + hasher.0.update(&challenge); + proof + .tau + .serialize(&mut hasher) + .expect("Consuming ratio proof of tau failed."); + proof + .alpha + .serialize(&mut hasher) + .expect("Consuming ratio proof of alpha failed."); + proof + .beta + .serialize(&mut hasher) + .expect("Consuming ratio proof of beta failed."); + into_array_unchecked(hasher.0.finalize()) + } +} + +impl mpc::Configuration for Test { + type Challenge = [u8; 64]; + type Hasher = BlakeHasher; + + #[inline] + fn challenge( + challenge: &Self::Challenge, + prev: &State, + next: &State, + proof: &Proof, + ) -> Self::Challenge { + let mut hasher = Self::Hasher::default(); + hasher.0.update(challenge); + prev.serialize(&mut hasher) + .expect("Consuming the previous state failed."); + next.serialize(&mut hasher) + .expect("Consuming the current state failed."); + proof + .serialize(&mut hasher) + .expect("Consuming proof failed"); + into_array_unchecked(hasher.0.finalize()) + } +} + +impl

mpc::ProvingKeyHasher

for Test +where + P: Pairing, +{ + type Output = [u8; 64]; + + #[inline] + fn hash(proving_key: &ProvingKey) -> Self::Output { + let mut hasher = BlakeHasher::default(); + proving_key + .serialize(&mut hasher) + .expect("Hasher is not allowed to fail"); + into_array_unchecked(hasher.0.finalize()) + } +} + +impl Types for Test { + type State = State; + type Challenge = [u8; 64]; + type Proof = Proof; +} + +/// Conducts a dummy phase one trusted setup. +#[inline] +pub fn dummy_phase_one_trusted_setup() -> Accumulator { + let mut rng = OsRng; + let accumulator = Accumulator::default(); + let challenge = [0; 64]; + let contribution = Contribution::gen(&mut rng); + let proof = contribution + .proof(&challenge, &mut rng) + .expect("The contribution proof should have been generated correctly."); + let mut next_accumulator = accumulator.clone(); + next_accumulator.update(&contribution); + Accumulator::verify_transform(accumulator, next_accumulator, challenge, proof) + .expect("Accumulator should have been generated correctly.") +} + +/// Generates a dummy R1CS circuit. +#[inline] +pub fn dummy_circuit(cs: &mut R1CS) { + let a = Fp(field_new!(Fr, "2")).as_known::>(cs); + let b = Fp(field_new!(Fr, "3")).as_known::>(cs); + let c = &a * &b; + let d = Fp(field_new!(Fr, "6")).as_known::>(cs); + c.enforce_equal(&d) + .expect("enforce_equal is not allowed to fail"); +} + +/// Proves and verifies a R1CS circuit with proving key `pk` and a random number generator `rng`. +#[inline] +pub fn prove_and_verify_circuit(pk: ProvingKey

, cs: R1CS, mut rng: &mut R) +where + P: PairingEngine, + R: CryptoRng + RngCore + ?Sized, +{ + assert!( + Groth16::verify( + &pk.vk, + &[field_new!(Fr, "6")], + &Groth16::prove(&pk, cs, &mut rng).unwrap() + ) + .unwrap(), + "Verify proof should succeed." + ); +} + +/// Tests if proving and verifying ratio proof is correct. +#[test] +fn proving_and_verifying_ratio_proof_is_correct() { + assert_valid_ratio_proof( + &Test::hasher(Test::TAU_DOMAIN_TAG), + &[0; 64], + &::Scalar::rand(&mut OsRng), + &mut OsRng, + ); +} + +/// Tests if trusted setup phase 2 is valid with trusted setup phase 1 and proves and verifies a +/// dummy circuit. +#[test] +fn trusted_setup_phase_two_is_valid() { + let mut rng = OsRng; + let mut cs = R1CS::for_contexts(); + dummy_circuit(&mut cs); + let mut state = initialize(dummy_phase_one_trusted_setup(), cs).unwrap(); + let mut transcript = Transcript:: { + initial_challenge: >::hash(&state), + initial_state: state.clone(), + rounds: Vec::new(), + }; + let hasher = ::Hasher::default(); + let (mut prev_state, mut proof); + let mut challenge = transcript.initial_challenge; + for _ in 0..5 { + prev_state = state.clone(); + proof = contribute(&hasher, &challenge, &mut state, &mut rng).unwrap(); + (challenge, state) = verify_transform(&challenge, prev_state, state, proof.clone()) + .expect("Verify transform failed"); + transcript.rounds.push((state.clone(), proof)); + } + verify_transform_all( + transcript.initial_challenge, + transcript.initial_state, + transcript.rounds, + ) + .expect("Verifying all transformations failed."); + let mut cs = R1CS::for_proofs(); + dummy_circuit(&mut cs); + prove_and_verify_circuit(state, cs, &mut rng); +} diff --git a/manta-trusted-setup/src/lib.rs b/manta-trusted-setup/src/lib.rs index 84dabb22a..cf390a1fa 100644 --- a/manta-trusted-setup/src/lib.rs +++ b/manta-trusted-setup/src/lib.rs @@ -25,4 +25,6 @@ extern crate alloc; pub mod groth16; pub mod mpc; +pub mod pairing; +pub mod ratio; pub mod util; diff --git a/manta-trusted-setup/src/mpc.rs b/manta-trusted-setup/src/mpc.rs index d73e23dc9..a5ed458b2 100644 --- a/manta-trusted-setup/src/mpc.rs +++ b/manta-trusted-setup/src/mpc.rs @@ -16,6 +16,8 @@ //! Secure Multi-Party Computation Primitives +use alloc::vec::Vec; + /// Secure Multi-Party Computation Types pub trait Types { /// State Type @@ -44,20 +46,27 @@ pub trait Contribute: Types { /// Verification pub trait Verify: Types { - /// Error + /// Verification Error Type type Error; - /// Computes the challenge associated to `state` and `challenge` for the next player. - fn challenge(&self, state: &Self::State, challenge: &Self::Challenge) -> Self::Challenge; + /// Computes the challenge associated to `challenge`, `prev`, `next`, and `proof` for the next + /// player. + fn challenge( + &self, + challenge: &Self::Challenge, + prev: &Self::State, + next: &Self::State, + proof: &Self::Proof, + ) -> Self::Challenge; - /// Verifies the transformation from `last` to `next` using the `next_challenge` and `proof` as + /// Verifies the transformation from `last` to `next` using the `challenge` and `proof` as /// evidence for the correct update of the state. This method returns the `next` state and /// the next response. fn verify_transform( &self, + challenge: &Self::Challenge, last: Self::State, next: Self::State, - next_challenge: Self::Challenge, proof: Self::Proof, ) -> Result; @@ -67,20 +76,35 @@ pub trait Verify: Types { #[inline] fn verify_transform_all( &self, - mut state: Self::State, mut challenge: Self::Challenge, + mut state: Self::State, iter: I, - ) -> Result<(Self::State, Self::Challenge), Self::Error> + ) -> Result<(Self::Challenge, Self::State), Self::Error> where E: Into, I: IntoIterator>, { for item in iter { let (next, next_proof) = item.map_err(Into::into)?; - let next_challenge = self.challenge(&state, &challenge); - challenge = self.challenge(&next, &next_challenge); - state = self.verify_transform(state, next, next_challenge, next_proof)?; + let next_challenge = self.challenge(&challenge, &state, &next, &next_proof); + state = self.verify_transform(&challenge, state, next, next_proof)?; + challenge = next_challenge; } - Ok((state, challenge)) + Ok((challenge, state)) } } + +/// MPC Transcript +pub struct Transcript +where + T: Types, +{ + /// Initial Challenge + pub initial_challenge: T::Challenge, + + /// Initial State + pub initial_state: T::State, + + /// Rounds + pub rounds: Vec<(T::State, T::Proof)>, +} diff --git a/manta-trusted-setup/src/pairing.rs b/manta-trusted-setup/src/pairing.rs new file mode 100644 index 000000000..6629f6c0f --- /dev/null +++ b/manta-trusted-setup/src/pairing.rs @@ -0,0 +1,137 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Pairing + +use ark_ec::{AffineCurve, PairingEngine}; +use ark_ff::PrimeField; +use core::iter; + +/// Pairing Configuration +pub trait Pairing { + /// Underlying Scalar Field + type Scalar: PrimeField; + + /// First Group of the Pairing + type G1: AffineCurve + Into; + + /// First Group Pairing-Prepared Point + type G1Prepared; + + /// Second Group of the Pairing + type G2: AffineCurve + Into; + + /// Second Group Pairing-Prepared Point + type G2Prepared; + + /// Pairing Engine Type + type Pairing: PairingEngine< + G1Affine = Self::G1, + G2Affine = Self::G2, + G1Prepared = Self::G1Prepared, + G2Prepared = Self::G2Prepared, + >; + + /// Returns the base G1 generator for this configuration. + fn g1_prime_subgroup_generator() -> Self::G1; + + /// Returns the base G2 generator for this configuration. + fn g2_prime_subgroup_generator() -> Self::G2; +} + +/// Pair from a [`PairingEngine`] +type Pair

= ( +

::G1Prepared, +

::G2Prepared, +); + +/// Pairing Engine Extension +pub trait PairingEngineExt: PairingEngine { + /// Evaluates the pairing function on `pair`. + #[inline] + fn eval(pair: &Pair) -> Self::Fqk { + Self::product_of_pairings(iter::once(pair)) + } + + /// Checks if `lhs` and `rhs` evaluate to the same point under the pairing function. + #[inline] + fn has_same(lhs: &Pair, rhs: &Pair) -> bool { + Self::eval(lhs) == Self::eval(rhs) + } + + /// Checks if `lhs` and `rhs` evaluate to the same point under the pairing function, returning + /// `Some` with prepared points if the pairing outcome is the same. This function checks if + /// there exists an `r` such that `(r * lhs.0 == rhs.0) && (lhs.1 == r * rhs.1)`. + #[inline] + fn same(lhs: (L1, L2), rhs: (R1, R2)) -> Option<(Pair, Pair)> + where + L1: Into, + L2: Into, + R1: Into, + R2: Into, + { + let lhs = (lhs.0.into(), lhs.1.into()); + let rhs = (rhs.0.into(), rhs.1.into()); + Self::has_same(&lhs, &rhs).then_some((lhs, rhs)) + } + + /// Checks if the ratio of `(lhs.0, lhs.1)` from `G1` is the same as the ratio of + /// `(lhs.0, lhs.1)` from `G2`. + #[inline] + fn same_ratio(lhs: (L1, R1), rhs: (L2, R2)) -> bool + where + L1: Into, + L2: Into, + R1: Into, + R2: Into, + { + Self::has_same(&(lhs.0.into(), rhs.1.into()), &(lhs.1.into(), rhs.0.into())) + } +} + +impl PairingEngineExt for E where E: PairingEngine {} + +#[cfg(test)] +mod test { + use crate::{pairing::PairingEngineExt, util::Sample}; + use ark_bls12_381::{Bls12_381, Fr, G1Affine, G2Affine}; + use ark_ec::{AffineCurve, PairingEngine, ProjectiveCurve}; + use manta_crypto::rand::OsRng; + + /// Asserts that `g1` and `g1*scalar` are in the same ratio as `g2` and `g2*scalar`. + #[inline] + pub fn assert_valid_pairing_ratio(g1: E::G1Affine, g2: E::G2Affine, scalar: E::Fr) + where + E: PairingEngine, + { + assert!(E::same( + (g1, g2.mul(scalar).into_affine()), + (g1.mul(scalar).into_affine(), g2) + ) + .is_some()); + } + + /// Tests if bls13_381 pairing ratio is valid. + #[test] + fn has_valid_bls12_381_pairing_ratio() { + let mut rng = OsRng; + assert_valid_pairing_ratio::( + G1Affine::gen(&mut rng), + G2Affine::gen(&mut rng), + Fr::gen(&mut rng), + ); + } +} diff --git a/manta-trusted-setup/src/ratio.rs b/manta-trusted-setup/src/ratio.rs new file mode 100644 index 000000000..2755dd7a0 --- /dev/null +++ b/manta-trusted-setup/src/ratio.rs @@ -0,0 +1,150 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Ratio Proofs + +use crate::pairing::{Pairing, PairingEngineExt}; +use ark_ec::{AffineCurve, ProjectiveCurve}; +use ark_ff::{PrimeField, UniformRand, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; +use manta_crypto::rand::{CryptoRng, RngCore}; + +/// Hash to Group Trait for Ratio Proof +pub trait HashToGroup +where + P: Pairing + ?Sized, +{ + /// Hashes `challenge` and `ratio` into a group point. + fn hash(&self, challenge: &C, ratio: (&P::G1, &P::G1)) -> P::G2; +} + +/// Prepared Ratio Type +/// +/// # Note +/// +/// Expected format is `((g1, r * g1), (r * g2, g2))` given curve points +/// [`g1`](Pairing::G1Prepared), [`g2`](Pairing::G2Prepared) and a scalar [`r`](Pairing::Scalar). +pub type PreparedRatio

= ( + (

::G1Prepared,

::G1Prepared), + (

::G2Prepared,

::G2Prepared), +); + +/// Pairing Ratio Proof of Knowledge +#[derive(derivative::Derivative, CanonicalDeserialize, CanonicalSerialize)] +#[derivative(Clone, Debug, Default, Eq, Hash, PartialEq)] +pub struct RatioProof

+where + P: Pairing + ?Sized, +{ + /// Ratio in G1 of the form `(g1, r * g1)` given a curve point [`g1`](Pairing::G1) and + /// a scalar [`r`](Pairing::Scalar) + pub ratio: (P::G1, P::G1), + + /// Matching Point in G2 of the form [`r * g2`](Pairing::G2) given a challenge point + /// [`g2`](Pairing::G2) and a scalar [`r`](Pairing::Scalar) + pub matching_point: P::G2, +} + +impl

RatioProof

+where + P: Pairing + ?Sized, +{ + /// Builds a [`RatioProof`] for `scalar` against `challenge`. + #[inline] + pub fn prove( + hasher: &H, + challenge: &C, + scalar: &P::Scalar, + rng: &mut R, + ) -> Option + where + H: HashToGroup, + R: CryptoRng + RngCore + ?Sized, + { + let g1_point = ::Projective::rand(rng); + if g1_point.is_zero() { + return None; + } + let scaled_g1_point = g1_point.mul(scalar.into_repr()); + if scaled_g1_point.is_zero() { + return None; + } + let g1_point = g1_point.into_affine(); + let scaled_g1_point = scaled_g1_point.into_affine(); + let g2_point = Self::challenge_point(hasher, challenge, (&g1_point, &scaled_g1_point)); + if g2_point.is_zero() { + return None; + } + let scaled_g2_point = g2_point.mul(*scalar); + if scaled_g2_point.is_zero() { + return None; + } + Some(Self { + ratio: (g1_point, scaled_g1_point), + matching_point: scaled_g2_point.into_affine(), + }) + } + + /// Computes the challenge point that corresponds with the given `challenge`. + #[inline] + pub fn challenge_point(hasher: &H, challenge: &C, ratio: (&P::G1, &P::G1)) -> P::G2 + where + H: HashToGroup, + { + hasher.hash(challenge, (ratio.0, ratio.1)) + } + + /// Verifies that `self` is a valid ratio proof-of-knowledge, returning the ratio of the + /// underlying scalar. + #[inline] + pub fn verify(self, hasher: &H, challenge: &C) -> Option> + where + H: HashToGroup, + { + let challenge_point = + Self::challenge_point(hasher, challenge, (&self.ratio.0, &self.ratio.1)); + let ((ratio_0, matching_point), (ratio_1, challenge_point)) = P::Pairing::same( + (self.ratio.0, self.matching_point), + (self.ratio.1, challenge_point), + )?; + Some(((ratio_0, ratio_1), (matching_point, challenge_point))) + } +} + +/// Testing Framework +#[cfg(feature = "test")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] +pub mod test { + use super::*; + + /// Asserts that generating a ratio proof always produces a valid result. + #[inline] + pub fn assert_valid_ratio_proof( + hasher: &H, + challenge: &C, + scalar: &P::Scalar, + rng: &mut R, + ) where + P: Pairing, + H: HashToGroup, + R: CryptoRng + RngCore + ?Sized, + { + RatioProof::prove(hasher, challenge, scalar, rng) + .expect("Proving a ratio proof should be correct.") + .verify(hasher, challenge) + .expect("Verifying a ratio proof should be correct."); + } +} diff --git a/manta-trusted-setup/src/util.rs b/manta-trusted-setup/src/util.rs index 2357a08d2..1c48b3d4a 100644 --- a/manta-trusted-setup/src/util.rs +++ b/manta-trusted-setup/src/util.rs @@ -16,22 +16,24 @@ //! Utilities +use crate::{groth16::kzg, pairing::Pairing, ratio::HashToGroup}; use alloc::vec::Vec; -use ark_ec::{wnaf::WnafContext, AffineCurve, PairingEngine, ProjectiveCurve}; -use ark_ff::{BigInteger, PrimeField, UniformRand}; +use ark_ec::{ + short_weierstrass_jacobian::GroupAffine, wnaf::WnafContext, AffineCurve, ProjectiveCurve, + SWModelParameters, +}; +use ark_ff::{BigInteger, Fp256, PrimeField, UniformRand, Zero}; +use ark_serialize::{CanonicalSerialize, Read, SerializationError, Write}; use ark_std::io; -use core::{iter, marker::PhantomData}; -use manta_crypto::rand::OsRng; -use manta_util::{cfg_into_iter, cfg_iter, cfg_iter_mut, cfg_reduce}; +use blake2::{Blake2b512, Digest as Blake2Digest}; +use byteorder::{BigEndian, ReadBytesExt}; +use core::marker::PhantomData; +use manta_crypto::rand::{CryptoRng, OsRng, RngCore, SeedableRng}; +use manta_util::{cfg_into_iter, cfg_iter, cfg_iter_mut, cfg_reduce, into_array_unchecked}; +use rand_chacha::ChaCha20Rng; #[cfg(feature = "rayon")] -use rayon::iter::{IndexedParallelIterator, ParallelIterator}; - -pub use ark_ff::{One, Zero}; -pub use ark_serialize::{ - CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write, -}; -pub use manta_crypto::rand::Sample; +use manta_util::rayon::iter::{IndexedParallelIterator, ParallelIterator}; /// Distribution Type Extension pub trait HasDistribution { @@ -235,15 +237,6 @@ where *point = point.mul(scalar).into_affine(); } -/// Multiplies each element in `bases` by `scalar`. -#[inline] -pub fn batch_scalar_mul_affine(points: &mut [G], scalar: G::ScalarField) -where - G: AffineCurve, -{ - cfg_iter_mut!(points).for_each(|point| scalar_mul(point, scalar)) -} - /// Converts each affine point in `points` into its projective form. #[inline] pub fn batch_into_projective(points: &[G]) -> Vec @@ -288,40 +281,46 @@ where G: ProjectiveCurve, { assert_eq!(lhs.len(), rhs.len()); - let pairs = cfg_into_iter!(0..lhs.len()) - .map(|_| G::ScalarField::rand(&mut OsRng)) - .zip(lhs) - .zip(rhs) - .map(|((rho, lhs), rhs)| { - let wnaf = recommended_wnaf(&rho); - (wnaf.mul(*lhs, &rho), wnaf.mul(*rhs, &rho)) - }); - cfg_reduce!(pairs, || (G::zero(), G::zero()), |mut acc, next| { - acc.0 += next.0; - acc.1 += next.1; - acc - }) + cfg_reduce!( + cfg_into_iter!(0..lhs.len()) + .map(|_| G::ScalarField::rand(&mut OsRng)) + .zip(lhs) + .zip(rhs) + .map(|((rho, lhs), rhs)| { + let wnaf = recommended_wnaf(&rho); + (wnaf.mul(*lhs, &rho), wnaf.mul(*rhs, &rho)) + }), + || (G::zero(), G::zero()), + |mut acc, next| { + acc.0 += next.0; + acc.1 += next.1; + acc + } + ) } /// Compresses `lhs` and `rhs` into a pair of curve points by random linear combination. The same /// random linear combination is used for both `lhs` and `rhs`, allowing this pair to be used in a /// consistent ratio test. #[inline] -pub fn merge_pairs_affine(lhs: &[G], rhs: &[G]) -> (G::Projective, G::Projective) +pub fn merge_pairs_affine(lhs: &[G], rhs: &[G]) -> (G, G) where G: AffineCurve, { assert_eq!(lhs.len(), rhs.len()); - let pairs = cfg_into_iter!(0..lhs.len()) - .map(|_| G::ScalarField::rand(&mut OsRng)) - .zip(lhs) - .zip(rhs) - .map(|((rho, lhs), rhs)| (lhs.mul(rho), rhs.mul(rho))); - cfg_reduce!(pairs, || (Zero::zero(), Zero::zero()), |mut acc, next| { - acc.0 += next.0; - acc.1 += next.1; - acc - }) + cfg_reduce!( + cfg_into_iter!(0..lhs.len()) + .map(|_| { G::ScalarField::rand(&mut OsRng) }) + .zip(lhs) + .zip(rhs) + .map(|((rho, lhs), rhs)| (lhs.mul(rho).into_affine(), rhs.mul(rho).into_affine())), + || (Zero::zero(), Zero::zero()), + |mut acc, next| { + acc.0 = acc.0 + next.0; + acc.1 = acc.1 + next.1; + acc + } + ) } /// Prepares a sequence of curve points for a check that subsequent terms differ by a constant ratio. @@ -339,54 +338,152 @@ where (g1_proj.into_affine(), g2_proj.into_affine()) } -/// Pair from a [`PairingEngine`] -type Pair

= ( -

::G1Prepared, -

::G2Prepared, -); +/// Blake Hasher +#[derive(Default)] +pub struct BlakeHasher(pub Blake2b512); -/// Pairing Engine Extension -pub trait PairingEngineExt: PairingEngine { - /// Evaluates the pairing function on `pair`. +impl Write for BlakeHasher { #[inline] - fn eval(pair: &Pair) -> Self::Fqk { - Self::product_of_pairings(iter::once(pair)) + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.update(buf); + Ok(buf.len()) } - /// Checks if `lhs` and `rhs` evaluate to the same point under the pairing function. #[inline] - fn has_same(lhs: &Pair, rhs: &Pair) -> bool { - Self::eval(lhs) == Self::eval(rhs) + fn flush(&mut self) -> io::Result<()> { + Ok(()) } +} - /// Checks if `lhs` and `rhs` evaluate to the same point under the pairing function, returning - /// `Some` with prepared points if the pairing outcome is the same. This function checks if - /// there exists an `r` such that `(r * lhs.0 == rhs.0) && (lhs.1 == r * rhs.1)`. +impl HashToGroup for BlakeHasher +where + P: Pairing, + P::G2: Sample, +{ #[inline] - fn same(lhs: (L1, L2), rhs: (R1, R2)) -> Option<(Pair, Pair)> + fn hash(&self, challenge: &[u8; N], ratio: (&P::G1, &P::G1)) -> P::G2 { + let mut hasher = BlakeHasher::default(); + hasher.0.update(challenge); + ratio.0.serialize(&mut hasher).unwrap(); + ratio.1.serialize(&mut hasher).unwrap(); + hash_to_group::<_, (), 64>(into_array_unchecked(hasher.0.finalize())) + } +} + +/// KZG Blake Hasher +pub struct KZGBlakeHasher +where + C: kzg::Configuration + ?Sized, +{ + /// Domain Tag Type + pub domain_tag: C::DomainTag, +} + +impl HashToGroup for KZGBlakeHasher

+where + P: kzg::Configuration + ?Sized, + P::G2: Sample, +{ + #[inline] + fn hash(&self, challenge: &[u8; N], ratio: (&P::G1, &P::G1)) -> P::G2 { + let mut hasher = BlakeHasher::default(); + hasher.0.update(&[self.domain_tag]); + hasher.0.update(challenge); + ratio.0.serialize(&mut hasher).unwrap(); + ratio.1.serialize(&mut hasher).unwrap(); + hash_to_group::<_, (), 64>(into_array_unchecked(hasher.0.finalize())) + } +} + +/// Consumes `digest` as a seed to an RNG and use the RNG to sample a group point `G` +/// on affine curve. +#[inline] +pub fn hash_to_group(digest: [u8; N]) -> G +where + G: AffineCurve + Sample, + D: Default, +{ + assert!(N >= 32, "Needs at least 32 bytes to seed ChaCha20."); + let mut digest = digest.as_slice(); + let mut seed = Vec::with_capacity(32); + for _ in 0..8 { + let word = digest + .read_u32::() + .expect("This is always possible since we have enough bytes to begin with."); + seed.extend(word.to_le_bytes()); + } + G::gen(&mut ChaCha20Rng::from_seed(into_array_unchecked(seed))) +} + +/// Multiplies each element in `bases` by a fixed `scalar`. +#[inline] +pub fn batch_mul_fixed_scalar(points: &mut [G], scalar: G::ScalarField) +where + G: AffineCurve, +{ + cfg_iter_mut!(points).for_each(|point| scalar_mul(point, scalar)) +} + +/// Pointwise multiplication of a vector of `points` and a vector of `scalars`. +#[inline] +pub fn batch_mul_pointwise(points: &mut [G], scalars: &[G::ScalarField]) +where + G: ProjectiveCurve, +{ + assert_eq!( + points.len(), + scalars.len(), + "Points should have the same length as scalars." + ); + cfg_iter_mut!(points) + .zip(cfg_iter!(scalars)) + .for_each(|(base, scalar)| { + base.mul_assign(*scalar); + }) +} + +/// Sampling Trait +pub trait Sample: Sized { + /// Returns a random value of type `Self`, sampled according to the given `distribution`, + /// generated from the `rng`. + fn sample(distribution: D, rng: &mut R) -> Self + where + R: CryptoRng + RngCore + ?Sized; + + /// Returns a random value of type `Self`, sampled according to the default distribution of + /// type `D`, generated from the `rng`. + #[inline] + fn gen(rng: &mut R) -> Self where - L1: Into, - L2: Into, - R1: Into, - R2: Into, + D: Default, + R: CryptoRng + RngCore + ?Sized, { - let lhs = (lhs.0.into(), lhs.1.into()); - let rhs = (rhs.0.into(), rhs.1.into()); - Self::has_same(&lhs, &rhs).then_some((lhs, rhs)) + Self::sample(Default::default(), rng) } +} - /// Checks if the ratio of `(lhs.0, lhs.1)` from `G1` is the same as the ratio of - /// `(lhs.0, lhs.1)` from `G2`. +impl

Sample for GroupAffine

+where + P: SWModelParameters, +{ #[inline] - fn same_ratio(lhs: (L1, R1), rhs: (L2, R2)) -> bool + fn sample(_: (), rng: &mut R) -> Self where - L1: Into, - L2: Into, - R1: Into, - R2: Into, + R: CryptoRng + RngCore + ?Sized, { - Self::has_same(&(lhs.0.into(), rhs.1.into()), &(lhs.1.into(), rhs.0.into())) + as AffineCurve>::Projective::rand(rng).into_affine() } } -impl PairingEngineExt for E where E: PairingEngine {} +impl

Sample for Fp256

+where + Fp256

: UniformRand, +{ + #[inline] + fn sample(_: (), rng: &mut R) -> Self + where + R: CryptoRng + RngCore + ?Sized, + { + Self::rand(rng) + } +} diff --git a/manta-util/src/lib.rs b/manta-util/src/lib.rs index 382fdf90d..66f2e9d41 100644 --- a/manta-util/src/lib.rs +++ b/manta-util/src/lib.rs @@ -60,6 +60,11 @@ pub use serde; #[doc(inline)] pub use serde_with; +#[cfg(feature = "rayon")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "rayon")))] +#[doc(inline)] +pub use rayon; + /// Type Identity Reflection Mechanism pub trait IsType { /// Type Equal to `Self` diff --git a/manta-util/src/macros.rs b/manta-util/src/macros.rs index 28e6373ee..e04266aba 100644 --- a/manta-util/src/macros.rs +++ b/manta-util/src/macros.rs @@ -36,7 +36,7 @@ macro_rules! cfg_into_iter { ($e:expr) => {{ #[cfg(feature = "rayon")] { - use ::rayon::iter::IntoParallelIterator as _; + use $crate::rayon::iter::IntoParallelIterator as _; $e.into_par_iter() } #[cfg(not(feature = "rayon"))] @@ -45,7 +45,7 @@ macro_rules! cfg_into_iter { ($e:expr, $min_len:expr) => {{ #[cfg(feature = "rayon")] { - use ::rayon::iter::IntoParallelIterator as _; + use $crate::rayon::iter::IntoParallelIterator as _; $e.into_par_iter().with_min_len($min_len) } #[cfg(not(feature = "rayon"))] @@ -60,7 +60,7 @@ macro_rules! cfg_iter { ($e:expr) => {{ #[cfg(feature = "rayon")] { - use ::rayon::iter::IntoParallelRefIterator as _; + use $crate::rayon::iter::IntoParallelRefIterator as _; $e.par_iter() } #[cfg(not(feature = "rayon"))] @@ -69,7 +69,7 @@ macro_rules! cfg_iter { ($e:expr, $min_len:expr) => {{ #[cfg(feature = "rayon")] { - use ::rayon::iter::IntoParallelRefIterator as _; + use $crate::rayon::iter::IntoParallelRefIterator as _; $e.par_iter().with_min_len($min_len) } #[cfg(not(feature = "rayon"))] @@ -84,7 +84,7 @@ macro_rules! cfg_iter_mut { ($e:expr) => {{ #[cfg(feature = "rayon")] { - use ::rayon::iter::IntoParallelRefMutIterator as _; + use $crate::rayon::iter::IntoParallelRefMutIterator as _; $e.par_iter_mut() } #[cfg(not(feature = "rayon"))] @@ -93,7 +93,7 @@ macro_rules! cfg_iter_mut { ($e:expr, $min_len:expr) => {{ #[cfg(feature = "rayon")] { - use ::rayon::iter::IntoParallelRefMutIterator as _; + use $crate::rayon::iter::IntoParallelRefMutIterator as _; $e.par_iter_mut().with_min_len($min_len) } #[cfg(not(feature = "rayon"))] @@ -108,7 +108,7 @@ macro_rules! cfg_chunks { ($e:expr, $size:expr) => {{ #[cfg(feature = "rayon")] { - use ::rayon::iter::ParallelIterator as _; + use $crate::rayon::iter::ParallelIterator as _; $e.par_chunks($size) } #[cfg(not(feature = "rayon"))] @@ -123,7 +123,7 @@ macro_rules! cfg_chunks_mut { ($e:expr, $size:expr) => {{ #[cfg(feature = "rayon")] { - use ::rayon::iter::ParallelIterator as _; + use $crate::rayon::iter::ParallelIterator as _; $e.par_chunks_mut($size) } #[cfg(not(feature = "rayon"))] @@ -137,7 +137,7 @@ macro_rules! cfg_reduce { ($e:expr, $default:expr, $op:expr) => {{ #[cfg(feature = "rayon")] { - use ::rayon::iter::ParallelIterator as _; + use $crate::rayon::iter::ParallelIterator as _; $e.reduce($default, $op) } #[cfg(not(feature = "rayon"))] diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 7a1d1cc88..ab0e4dd42 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -13,8 +13,54 @@ publish = false # are managed by hakari. ### BEGIN HAKARI SECTION +[dependencies] +aes-gcm = { version = "0.9.4", features = ["aes", "alloc"] } +anyhow = { version = "1.0.58", features = ["std"] } +bitflags = { version = "1.3.2" } +blake3 = { version = "1.3.1", default-features = false, features = ["digest", "std"] } +byteorder = { version = "1.4.3", features = ["std"] } +crypto-common = { version = "0.1.6", default-features = false, features = ["std"] } +digest-93f6ce9d446188ac = { package = "digest", version = "0.10.3", features = ["alloc", "block-buffer", "core-api", "mac", "std", "subtle"] } +digest-274715c4dabd11b0 = { package = "digest", version = "0.9.0", default-features = false, features = ["alloc", "std"] } +futures = { version = "0.3.21", features = ["alloc", "async-await", "executor", "futures-executor", "std"] } +futures-channel = { version = "0.3.21", default-features = false, features = ["alloc", "futures-sink", "sink", "std"] } +futures-core = { version = "0.3.21", features = ["alloc", "std"] } +futures-io = { version = "0.3.21", features = ["std"] } +futures-sink = { version = "0.3.21", default-features = false, features = ["alloc", "std"] } +futures-task = { version = "0.3.21", default-features = false, features = ["alloc", "std"] } +futures-util = { version = "0.3.21", features = ["alloc", "async-await", "async-await-macro", "channel", "futures-channel", "futures-io", "futures-macro", "futures-sink", "io", "memchr", "sink", "slab", "std"] } +generic-array = { version = "0.14.5", default-features = false, features = ["more_lengths"] } +getrandom = { version = "0.2.7", default-features = false, features = ["js", "js-sys", "std", "wasm-bindgen"] } +indexmap = { version = "1.9.1", default-features = false, features = ["std"] } +log = { version = "0.4.17", default-features = false, features = ["kv_unstable", "kv_unstable_std", "std", "value-bag"] } +memchr = { version = "2.5.0", features = ["std"] } +num-traits = { version = "0.2.15", features = ["i128", "libm", "std"] } +ppv-lite86 = { version = "0.2.16", default-features = false, features = ["simd", "std"] } +rand = { version = "0.8.5", features = ["alloc", "getrandom", "libc", "rand_chacha", "std", "std_rng"] } +rand_chacha = { version = "0.3.1", default-features = false, features = ["std"] } +rand_core = { version = "0.6.3", default-features = false, features = ["alloc", "getrandom", "std"] } +serde = { version = "1.0.140", features = ["alloc", "derive", "serde_derive", "std"] } +serde_json = { version = "1.0.82", features = ["alloc", "std"] } +sha2 = { version = "0.9.9", features = ["std"] } +standback = { version = "0.2.17", default-features = false, features = ["std"] } +subtle = { version = "2.4.1", default-features = false, features = ["i128"] } +tracing = { version = "0.1.35", default-features = false, features = ["attributes", "tracing-attributes"] } +url = { version = "2.2.2", default-features = false, features = ["serde"] } +web-sys = { version = "0.3.59", default-features = false, features = ["BinaryType", "Blob", "CloseEvent", "DomException", "Event", "EventTarget", "MessageEvent", "WebSocket", "console"] } +zeroize = { version = "1.5.7", default-features = false, features = ["alloc", "zeroize_derive"] } + +[build-dependencies] +anyhow = { version = "1.0.58", features = ["std"] } +blake3 = { version = "1.3.1", default-features = false, features = ["digest", "std"] } +cc = { version = "1.0.73", default-features = false, features = ["jobserver", "parallel"] } +crypto-common = { version = "0.1.6", default-features = false, features = ["std"] } +digest-93f6ce9d446188ac = { package = "digest", version = "0.10.3", features = ["alloc", "block-buffer", "core-api", "mac", "std", "subtle"] } +generic-array = { version = "0.14.5", default-features = false, features = ["more_lengths"] } +log = { version = "0.4.17", default-features = false, features = ["kv_unstable", "kv_unstable_std", "std", "value-bag"] } +num-traits = { version = "0.2.15", features = ["i128", "libm", "std"] } +serde = { version = "1.0.140", features = ["alloc", "derive", "serde_derive", "std"] } +standback = { version = "0.2.17", default-features = false, features = ["std"] } +subtle = { version = "2.4.1", default-features = false, features = ["i128"] } +syn = { version = "1.0.98", features = ["clone-impls", "derive", "extra-traits", "fold", "full", "parsing", "printing", "proc-macro", "quote", "visit", "visit-mut"] } -# Disabled by running `cargo hakari disable`. -# To re-enable, run: -# cargo hakari generate ### END HAKARI SECTION