halo2/backend/src/plonk/verifier.rs

466 lines
17 KiB
Rust

use group::Curve;
use halo2_middleware::ff::{Field, FromUniformBytes, WithSmallOrderMulGroup};
use std::iter;
use super::{
vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error,
VerifyingKey,
};
use crate::arithmetic::compute_inner_product;
use crate::plonk::lookup::verifier::lookup_read_permuted_commitments;
use crate::plonk::permutation::verifier::permutation_read_product_commitments;
use crate::plonk::shuffle::verifier::shuffle_read_product_commitment;
use crate::poly::commitment::{CommitmentScheme, Verifier};
use crate::poly::VerificationStrategy;
use crate::poly::{
commitment::{Blind, Params},
VerifierQuery,
};
use crate::transcript::{read_n_scalars, EncodedChallenge, TranscriptRead};
#[cfg(feature = "batch")]
mod batch;
#[cfg(feature = "batch")]
pub use batch::BatchVerifier;
/// Returns a boolean indicating whether or not the proof is valid. Verifies a single proof (not
/// batched).
pub fn verify_proof_single<
'params,
Scheme: CommitmentScheme,
V: Verifier<'params, Scheme>,
E: EncodedChallenge<Scheme::Curve>,
T: TranscriptRead<Scheme::Curve, E>,
Strategy: VerificationStrategy<'params, Scheme, V>,
>(
params: &'params Scheme::ParamsVerifier,
vk: &VerifyingKey<Scheme::Curve>,
strategy: Strategy,
instance: &[&[Scheme::Scalar]],
transcript: &mut T,
) -> Result<Strategy::Output, Error>
where
Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>,
{
verify_proof(params, vk, strategy, &[instance], transcript)
}
/// Returns a boolean indicating whether or not the proof is valid
pub fn verify_proof<
'params,
Scheme: CommitmentScheme,
V: Verifier<'params, Scheme>,
E: EncodedChallenge<Scheme::Curve>,
T: TranscriptRead<Scheme::Curve, E>,
Strategy: VerificationStrategy<'params, Scheme, V>,
>(
params: &'params Scheme::ParamsVerifier,
vk: &VerifyingKey<Scheme::Curve>,
strategy: Strategy,
instances: &[&[&[Scheme::Scalar]]],
transcript: &mut T,
) -> Result<Strategy::Output, Error>
where
Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>,
{
// Check that instances matches the expected number of instance columns
for instances in instances.iter() {
if instances.len() != vk.cs.num_instance_columns {
return Err(Error::InvalidInstances);
}
}
let instance_commitments = if V::QUERY_INSTANCE {
instances
.iter()
.map(|instance| {
instance
.iter()
.map(|instance| {
if instance.len() > params.n() as usize - (vk.cs.blinding_factors() + 1) {
return Err(Error::InstanceTooLarge);
}
let mut poly = instance.to_vec();
poly.resize(params.n() as usize, Scheme::Scalar::ZERO);
let poly = vk.domain.lagrange_from_vec(poly);
Ok(params.commit_lagrange(&poly, Blind::default()).to_affine())
})
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?
} else {
vec![vec![]; instances.len()]
};
let num_proofs = instance_commitments.len();
// Hash verification key into transcript
vk.hash_into(transcript)?;
if V::QUERY_INSTANCE {
for instance_commitments in instance_commitments.iter() {
// Hash the instance (external) commitments into the transcript
for commitment in instance_commitments {
transcript.common_point(*commitment)?
}
}
} else {
for instance in instances.iter() {
for instance in instance.iter() {
for value in instance.iter() {
transcript.common_scalar(*value)?;
}
}
}
}
// Hash the prover's advice commitments into the transcript and squeeze challenges
let (advice_commitments, challenges) = {
let mut advice_commitments =
vec![vec![Scheme::Curve::default(); vk.cs.num_advice_columns]; num_proofs];
let mut challenges = vec![Scheme::Scalar::ZERO; vk.cs.num_challenges];
for current_phase in vk.cs.phases() {
for advice_commitments in advice_commitments.iter_mut() {
for (phase, commitment) in vk
.cs
.advice_column_phase
.iter()
.zip(advice_commitments.iter_mut())
{
if current_phase == *phase {
*commitment = transcript.read_point()?;
}
}
}
for (phase, challenge) in vk.cs.challenge_phase.iter().zip(challenges.iter_mut()) {
if current_phase == *phase {
*challenge = *transcript.squeeze_challenge_scalar::<()>();
}
}
}
(advice_commitments, challenges)
};
// Sample theta challenge for keeping lookup columns linearly independent
let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar();
let lookups_permuted = (0..num_proofs)
.map(|_| -> Result<Vec<_>, _> {
// Hash each lookup permuted commitment
vk.cs
.lookups
.iter()
.map(|_argument| lookup_read_permuted_commitments(transcript))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
// Sample beta challenge
let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar();
// Sample gamma challenge
let gamma: ChallengeGamma<_> = transcript.squeeze_challenge_scalar();
let permutations_committed = (0..num_proofs)
.map(|_| {
// Hash each permutation product commitment
permutation_read_product_commitments(&vk.cs.permutation, vk, transcript)
})
.collect::<Result<Vec<_>, _>>()?;
let lookups_committed = lookups_permuted
.into_iter()
.map(|lookups| {
// Hash each lookup product commitment
lookups
.into_iter()
.map(|lookup| lookup.read_product_commitment(transcript))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
let shuffles_committed = (0..num_proofs)
.map(|_| -> Result<Vec<_>, _> {
// Hash each shuffle product commitment
vk.cs
.shuffles
.iter()
.map(|_argument| shuffle_read_product_commitment(transcript))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
let vanishing = vanishing::Argument::read_commitments_before_y(transcript)?;
// Sample y challenge, which keeps the gates linearly independent.
let y: ChallengeY<_> = transcript.squeeze_challenge_scalar();
let vanishing = vanishing.read_commitments_after_y(vk, transcript)?;
// Sample x challenge, which is used to ensure the circuit is
// satisfied with high probability.
let x: ChallengeX<_> = transcript.squeeze_challenge_scalar();
let instance_evals = if V::QUERY_INSTANCE {
(0..num_proofs)
.map(|_| -> Result<Vec<_>, _> {
read_n_scalars(transcript, vk.cs.instance_queries.len())
})
.collect::<Result<Vec<_>, _>>()?
} else {
let xn = x.pow([params.n()]);
let (min_rotation, max_rotation) =
vk.cs
.instance_queries
.iter()
.fold((0, 0), |(min, max), (_, rotation)| {
if rotation.0 < min {
(rotation.0, max)
} else if rotation.0 > max {
(min, rotation.0)
} else {
(min, max)
}
});
let max_instance_len = instances
.iter()
.flat_map(|instance| instance.iter().map(|instance| instance.len()))
.max_by(Ord::cmp)
.unwrap_or_default();
let l_i_s = &vk.domain.l_i_range(
*x,
xn,
-max_rotation..max_instance_len as i32 + min_rotation.abs(),
);
instances
.iter()
.map(|instances| {
vk.cs
.instance_queries
.iter()
.map(|(column, rotation)| {
let instances = instances[column.index()];
let offset = (max_rotation - rotation.0) as usize;
compute_inner_product(instances, &l_i_s[offset..offset + instances.len()])
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
};
let advice_evals = (0..num_proofs)
.map(|_| -> Result<Vec<_>, _> { read_n_scalars(transcript, vk.cs.advice_queries.len()) })
.collect::<Result<Vec<_>, _>>()?;
let fixed_evals = read_n_scalars(transcript, vk.cs.fixed_queries.len())?;
let vanishing = vanishing.evaluate_after_x(transcript)?;
let permutations_common = vk.permutation.evaluate(transcript)?;
let permutations_evaluated = permutations_committed
.into_iter()
.map(|permutation| permutation.evaluate(transcript))
.collect::<Result<Vec<_>, _>>()?;
let lookups_evaluated = lookups_committed
.into_iter()
.map(|lookups| -> Result<Vec<_>, _> {
lookups
.into_iter()
.map(|lookup| lookup.evaluate(transcript))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
let shuffles_evaluated = shuffles_committed
.into_iter()
.map(|shuffles| -> Result<Vec<_>, _> {
shuffles
.into_iter()
.map(|shuffle| shuffle.evaluate(transcript))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
// This check ensures the circuit is satisfied so long as the polynomial
// commitments open to the correct values.
let vanishing = {
// x^n
let xn = x.pow([params.n()]);
let blinding_factors = vk.cs.blinding_factors();
let l_evals = vk
.domain
.l_i_range(*x, xn, (-((blinding_factors + 1) as i32))..=0);
assert_eq!(l_evals.len(), 2 + blinding_factors);
let l_last = l_evals[0];
let l_blind: Scheme::Scalar = l_evals[1..(1 + blinding_factors)]
.iter()
.fold(Scheme::Scalar::ZERO, |acc, eval| acc + eval);
let l_0 = l_evals[1 + blinding_factors];
// Compute the expected value of h(x)
let expressions = advice_evals
.iter()
.zip(instance_evals.iter())
.zip(permutations_evaluated.iter())
.zip(lookups_evaluated.iter())
.zip(shuffles_evaluated.iter())
.flat_map(
|((((advice_evals, instance_evals), permutation), lookups), shuffles)| {
let challenges = &challenges;
let fixed_evals = &fixed_evals;
std::iter::empty()
// Evaluate the circuit using the custom gates provided
.chain(vk.cs.gates.iter().flat_map(move |gate| {
gate.polynomials().iter().map(move |poly| {
poly.evaluate(
&|scalar| scalar,
&|_| {
panic!("virtual selectors are removed during optimization")
},
&|query| fixed_evals[query.index.unwrap()],
&|query| advice_evals[query.index.unwrap()],
&|query| instance_evals[query.index.unwrap()],
&|challenge| challenges[challenge.index()],
&|a| -a,
&|a, b| a + b,
&|a, b| a * b,
&|a, scalar| a * scalar,
)
})
}))
.chain(permutation.expressions(
vk,
&vk.cs.permutation,
&permutations_common,
advice_evals,
fixed_evals,
instance_evals,
l_0,
l_last,
l_blind,
beta,
gamma,
x,
))
.chain(lookups.iter().zip(vk.cs.lookups.iter()).flat_map(
move |(p, argument)| {
p.expressions(
l_0,
l_last,
l_blind,
argument,
theta,
beta,
gamma,
advice_evals,
fixed_evals,
instance_evals,
challenges,
)
},
))
.chain(shuffles.iter().zip(vk.cs.shuffles.iter()).flat_map(
move |(p, argument)| {
p.expressions(
l_0,
l_last,
l_blind,
argument,
theta,
gamma,
advice_evals,
fixed_evals,
instance_evals,
challenges,
)
},
))
},
);
vanishing.verify(params, expressions, y, xn)
};
let queries = instance_commitments
.iter()
.zip(instance_evals.iter())
.zip(advice_commitments.iter())
.zip(advice_evals.iter())
.zip(permutations_evaluated.iter())
.zip(lookups_evaluated.iter())
.zip(shuffles_evaluated.iter())
.flat_map(
|(
(
(
(
((instance_commitments, instance_evals), advice_commitments),
advice_evals,
),
permutation,
),
lookups,
),
shuffles,
)| {
iter::empty()
.chain(
V::QUERY_INSTANCE
.then_some(vk.cs.instance_queries.iter().enumerate().map(
move |(query_index, &(column, at))| {
VerifierQuery::new_commitment(
&instance_commitments[column.index()],
vk.domain.rotate_omega(*x, at),
instance_evals[query_index],
)
},
))
.into_iter()
.flatten(),
)
.chain(vk.cs.advice_queries.iter().enumerate().map(
move |(query_index, &(column, at))| {
VerifierQuery::new_commitment(
&advice_commitments[column.index()],
vk.domain.rotate_omega(*x, at),
advice_evals[query_index],
)
},
))
.chain(permutation.queries(vk, x))
.chain(lookups.iter().flat_map(move |p| p.queries(vk, x)))
.chain(shuffles.iter().flat_map(move |p| p.queries(vk, x)))
},
)
.chain(
vk.cs
.fixed_queries
.iter()
.enumerate()
.map(|(query_index, &(column, at))| {
VerifierQuery::new_commitment(
&vk.fixed_commitments[column.index()],
vk.domain.rotate_omega(*x, at),
fixed_evals[query_index],
)
}),
)
.chain(permutations_common.queries(&vk.permutation, x))
.chain(vanishing.queries(x));
// We are now convinced the circuit is satisfied so long as the
// polynomial commitments open to the correct values.
let verifier = V::new(params);
strategy.process(|msm| {
verifier
.verify_proof(transcript, queries, msm)
.map_err(|_| Error::Opening)
})
}