mirror of https://github.com/zcash/halo2.git
466 lines
17 KiB
Rust
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)
|
|
})
|
|
}
|