Skip to content

Commit 198e9ae

Browse files
authored
fix: patch the query collision bug in multipoint opening (#400)
* test: add "test_identical_queries" in multiopen testing * test: improve tests of multiopen identical queries * fix: patch the case when multiopen includes identical queries
1 parent 495cbcd commit 198e9ae

7 files changed

Lines changed: 184 additions & 22 deletions

File tree

halo2_backend/src/poly/kzg/multiopen/gwc.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,28 @@ struct CommitmentData<F: Field, Q: Query<F>> {
2222
_marker: PhantomData<F>,
2323
}
2424

25-
fn construct_intermediate_sets<F: Field, I, Q: Query<F>>(queries: I) -> Vec<CommitmentData<F, Q>>
25+
fn construct_intermediate_sets<F: Field, I, Q: Query<F>>(
26+
queries: I,
27+
) -> Option<Vec<CommitmentData<F, Q>>>
2628
where
2729
I: IntoIterator<Item = Q> + Clone,
2830
{
31+
let queries = queries.into_iter().collect::<Vec<_>>();
32+
33+
// Caller tried to provide two different evaluations for the same
34+
// commitment. Permitting this would be unsound.
35+
{
36+
let mut query_set: Vec<(Q::Commitment, F)> = vec![];
37+
for query in queries.iter() {
38+
let commitment = query.get_commitment();
39+
let rotation = query.get_point();
40+
if query_set.contains(&(commitment, rotation)) {
41+
return None;
42+
}
43+
query_set.push((commitment, rotation));
44+
}
45+
}
46+
2947
let mut point_query_map: Vec<(F, Vec<Q>)> = Vec::new();
3048
for query in queries {
3149
if let Some(pos) = point_query_map
@@ -39,12 +57,14 @@ where
3957
}
4058
}
4159

42-
point_query_map
43-
.into_iter()
44-
.map(|(point, queries)| CommitmentData {
45-
queries,
46-
point,
47-
_marker: PhantomData,
48-
})
49-
.collect()
60+
Some(
61+
point_query_map
62+
.into_iter()
63+
.map(|(point, queries)| CommitmentData {
64+
queries,
65+
point,
66+
_marker: PhantomData,
67+
})
68+
.collect(),
69+
)
5070
}

halo2_backend/src/poly/kzg/multiopen/gwc/prover.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ where
5353
R: RngCore,
5454
{
5555
let v: ChallengeV<_> = transcript.squeeze_challenge_scalar();
56-
let commitment_data = construct_intermediate_sets(queries);
56+
let commitment_data = construct_intermediate_sets(queries).ok_or_else(|| {
57+
io::Error::new(
58+
io::ErrorKind::InvalidInput,
59+
"queries iterator contains mismatching evaluations",
60+
)
61+
})?;
5762

5863
for commitment_at_a_point in commitment_data.iter() {
5964
let z = commitment_at_a_point.point;

halo2_backend/src/poly/kzg/multiopen/gwc/verifier.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ where
5757
{
5858
let v: ChallengeV<_> = transcript.squeeze_challenge_scalar();
5959

60-
let commitment_data = construct_intermediate_sets(queries);
60+
let commitment_data = construct_intermediate_sets(queries).ok_or(Error::OpeningError)?;
6161

6262
let w: Vec<E::G1Affine> = (0..commitment_data.len())
6363
.map(|_| transcript.read_point().map_err(|_| Error::SamplingError))

halo2_backend/src/poly/kzg/multiopen/shplonk.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,26 @@ struct IntermediateSets<F: Field, Q: Query<F>> {
4747

4848
fn construct_intermediate_sets<F: Field + Ord, I, Q: Query<F, Eval = F>>(
4949
queries: I,
50-
) -> IntermediateSets<F, Q>
50+
) -> Option<IntermediateSets<F, Q>>
5151
where
5252
I: IntoIterator<Item = Q> + Clone,
5353
{
5454
let queries = queries.into_iter().collect::<Vec<_>>();
5555

56+
// Caller tried to provide two different evaluations for the same
57+
// commitment. Permitting this would be unsound.
58+
{
59+
let mut query_set: Vec<(Q::Commitment, F)> = vec![];
60+
for query in queries.iter() {
61+
let commitment = query.get_commitment();
62+
let rotation = query.get_point();
63+
if query_set.contains(&(commitment, rotation)) {
64+
return None;
65+
}
66+
query_set.push((commitment, rotation));
67+
}
68+
}
69+
5670
// Find evaluation of a commitment at a rotation
5771
let get_eval = |commitment: Q::Commitment, rotation: F| -> F {
5872
queries
@@ -133,18 +147,22 @@ where
133147
})
134148
.collect::<Vec<RotationSet<_, _>>>();
135149

136-
IntermediateSets {
150+
Some(IntermediateSets {
137151
rotation_sets,
138152
super_point_set,
139-
}
153+
})
140154
}
141155

142156
#[cfg(test)]
143157
mod proptests {
144158
use super::{construct_intermediate_sets, Commitment, IntermediateSets};
145159
use halo2_middleware::ff::FromUniformBytes;
146160
use halo2curves::pasta::Fp;
147-
use proptest::{collection::vec, prelude::*, sample::select};
161+
use proptest::{
162+
collection::{hash_set, vec},
163+
prelude::*,
164+
sample::select,
165+
};
148166
use std::convert::TryFrom;
149167

150168
#[derive(Debug, Clone)]
@@ -194,10 +212,16 @@ mod proptests {
194212
prop_compose! {
195213
// Mapping from column index to point index.
196214
fn arb_queries_inner(num_points: usize, num_cols: usize, num_queries: usize)(
197-
col_indices in vec(select((0..num_cols).collect::<Vec<_>>()), num_queries),
198-
point_indices in vec(select((0..num_points).collect::<Vec<_>>()), num_queries)
215+
// Use a HashSet to ensure we sample distinct (column, point) queries.
216+
queries in hash_set(
217+
(
218+
select((0..num_cols).collect::<Vec<_>>()),
219+
select((0..num_points).collect::<Vec<_>>()),
220+
),
221+
num_queries,
222+
)
199223
) -> Vec<(usize, usize)> {
200-
col_indices.into_iter().zip(point_indices.into_iter()).collect()
224+
queries.into_iter().collect()
201225
}
202226
}
203227

@@ -229,14 +253,14 @@ mod proptests {
229253
fn test_intermediate_sets(
230254
(queries_1, queries_2) in compare_queries(8, 8, 16)
231255
) {
232-
let IntermediateSets { rotation_sets, .. } = construct_intermediate_sets(queries_1);
256+
let IntermediateSets { rotation_sets, .. } = construct_intermediate_sets(queries_1).ok_or_else(|| TestCaseError::Fail("mismatched evals".into()))?;
233257
let commitment_sets = rotation_sets.iter().map(|data|
234258
data.commitments.iter().map(Commitment::get).collect::<Vec<_>>()
235259
).collect::<Vec<_>>();
236260

237261
// It shouldn't matter what the point or eval values are; we should get
238262
// the same exact point set indices and point indices again.
239-
let IntermediateSets { rotation_sets: new_rotation_sets, .. } = construct_intermediate_sets(queries_2);
263+
let IntermediateSets { rotation_sets: new_rotation_sets, .. } = construct_intermediate_sets(queries_2).ok_or_else(|| TestCaseError::Fail("mismatched evals".into()))?;
240264
let new_commitment_sets = new_rotation_sets.iter().map(|data|
241265
data.commitments.iter().map(Commitment::get).collect::<Vec<_>>()
242266
).collect::<Vec<_>>();

halo2_backend/src/poly/kzg/multiopen/shplonk/prover.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,12 @@ where
173173
}
174174
};
175175

176-
let intermediate_sets = construct_intermediate_sets(queries);
176+
let intermediate_sets = construct_intermediate_sets(queries).ok_or_else(|| {
177+
io::Error::new(
178+
io::ErrorKind::InvalidInput,
179+
"queries iterator contains mismatching evaluations",
180+
)
181+
})?;
177182
let (rotation_sets, super_point_set) = (
178183
intermediate_sets.rotation_sets,
179184
intermediate_sets.super_point_set,

halo2_backend/src/poly/kzg/multiopen/shplonk/verifier.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ where
6060
where
6161
I: IntoIterator<Item = VerifierQuery<'com, E::G1Affine, MSMKZG<E>>> + Clone,
6262
{
63-
let intermediate_sets = construct_intermediate_sets(queries);
63+
let intermediate_sets = construct_intermediate_sets(queries).ok_or(Error::OpeningError)?;
6464
let (rotation_sets, super_point_set) = (
6565
intermediate_sets.rotation_sets,
6666
intermediate_sets.super_point_set,

halo2_backend/src/poly/multiopen_test.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,64 @@ mod test {
9090
>(&verifier_params, &proof[..], true);
9191
}
9292

93+
#[test]
94+
fn test_identical_queries_gwc() {
95+
use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG};
96+
use crate::poly::kzg::multiopen::{ProverGWC, VerifierGWC};
97+
use crate::poly::kzg::strategy::AccumulatorStrategy;
98+
use halo2curves::bn256::Bn256;
99+
100+
const K: u32 = 4;
101+
102+
let engine = H2cEngine::new();
103+
let params = ParamsKZG::<Bn256>::new(K);
104+
105+
let proof = create_proof::<
106+
KZGCommitmentScheme<Bn256>,
107+
ProverGWC<_>,
108+
_,
109+
Blake2bWrite<_, _, Challenge255<_>>,
110+
>(&engine, &params);
111+
112+
let verifier_params = params.verifier_params();
113+
verify_identical_queries::<
114+
KZGCommitmentScheme<Bn256>,
115+
VerifierGWC<_>,
116+
_,
117+
Blake2bRead<_, _, Challenge255<_>>,
118+
AccumulatorStrategy<_>,
119+
>(&verifier_params, &proof[..]);
120+
}
121+
122+
#[test]
123+
fn test_identical_queries_shplonk() {
124+
use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG};
125+
use crate::poly::kzg::multiopen::{ProverSHPLONK, VerifierSHPLONK};
126+
use crate::poly::kzg::strategy::AccumulatorStrategy;
127+
use halo2curves::bn256::Bn256;
128+
129+
const K: u32 = 4;
130+
131+
let engine = H2cEngine::new();
132+
let params = ParamsKZG::<Bn256>::new(K);
133+
134+
let proof = create_proof::<
135+
KZGCommitmentScheme<Bn256>,
136+
ProverSHPLONK<_>,
137+
_,
138+
Blake2bWrite<_, _, Challenge255<_>>,
139+
>(&engine, &params);
140+
141+
let verifier_params = params.verifier_params();
142+
verify_identical_queries::<
143+
KZGCommitmentScheme<Bn256>,
144+
VerifierSHPLONK<_>,
145+
_,
146+
Blake2bRead<_, _, Challenge255<_>>,
147+
AccumulatorStrategy<_>,
148+
>(&verifier_params, &proof[..]);
149+
}
150+
93151
fn verify<
94152
'a,
95153
'params,
@@ -223,4 +281,54 @@ mod test {
223281

224282
transcript.finalize()
225283
}
284+
285+
fn verify_identical_queries<
286+
'a,
287+
'params,
288+
Scheme: CommitmentScheme,
289+
V: Verifier<'params, Scheme>,
290+
E: EncodedChallenge<Scheme::Curve>,
291+
T: TranscriptReadBuffer<&'a [u8], Scheme::Curve, E>,
292+
Strategy: VerificationStrategy<'params, Scheme, V> + std::fmt::Debug,
293+
>(
294+
params: &'params Scheme::ParamsVerifier,
295+
proof: &'a [u8],
296+
) {
297+
use assert_matches::assert_matches;
298+
use group::ff::Field;
299+
300+
let verifier = V::new();
301+
302+
let mut transcript = T::init(proof);
303+
304+
let a = transcript.read_point().unwrap();
305+
let b = transcript.read_point().unwrap();
306+
let c = transcript.read_point().unwrap();
307+
308+
let x = transcript.squeeze_challenge();
309+
let y = transcript.squeeze_challenge();
310+
311+
let avx = transcript.read_scalar().unwrap();
312+
let bvx = transcript.read_scalar().unwrap();
313+
let cvy = transcript.read_scalar().unwrap();
314+
315+
let bvx_bad = <Scheme as CommitmentScheme>::Scalar::random(OsRng);
316+
317+
#[rustfmt::skip]
318+
let invalid_queries = std::iter::empty()
319+
.chain(Some(VerifierQuery::new_commitment(&a, x.get_scalar(), avx)))
320+
.chain(Some(VerifierQuery::new_commitment(&b, x.get_scalar(), bvx)))
321+
.chain(Some(VerifierQuery::new_commitment(&b, x.get_scalar(), bvx_bad))) // This is wrong.
322+
.chain(Some(VerifierQuery::new_commitment(&c, y.get_scalar(), cvy)));
323+
324+
let strategy = Strategy::new(params);
325+
assert_matches!(
326+
strategy.process(|msm_accumulator| {
327+
verifier
328+
.verify_proof(&mut transcript, invalid_queries.clone(), msm_accumulator)
329+
.map_err(|_| Error::Opening)
330+
}),
331+
Err(Error::Opening)
332+
);
333+
}
226334
}

0 commit comments

Comments
 (0)