Skip to content
This repository was archived by the owner on Aug 17, 2026. It is now read-only.

Commit fb4a64c

Browse files
committed
fix: patch the case when multiopen includes identical queries
1 parent 975ccc1 commit fb4a64c

6 files changed

Lines changed: 76 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,

0 commit comments

Comments
 (0)