//! This module contains an implementation of the multipoint opening polynomial //! commitment scheme described in the [Halo][halo] paper. //! //! [halo]: https://eprint.iacr.org/2019/1021 use std::collections::{BTreeMap, BTreeSet}; use super::*; use crate::arithmetic::CurveAffine; mod prover; mod verifier; /// This is a multi-point opening proof used in the polynomial commitment scheme opening. #[derive(Debug, Clone)] pub struct Proof { // A vector of evaluations at each set of query points q_evals: Vec, // Commitment to final polynomial f_commitment: C, // Commitment proof opening: commitment::Proof, } /// A polynomial query at a point #[derive(Debug, Clone)] pub struct ProverQuery<'a, C: CurveAffine> { /// point at which polynomial is queried pub point: C::Scalar, /// coefficients of polynomial pub poly: &'a Polynomial, /// blinding factor of polynomial pub blind: commitment::Blind, /// evaluation of polynomial at query point pub eval: C::Scalar, } /// A polynomial query at a point #[derive(Debug, Clone)] pub struct VerifierQuery<'a, C: CurveAffine> { /// point at which polynomial is queried pub point: C::Scalar, /// commitment to polynomial pub commitment: &'a C, /// evaluation of polynomial at query point pub eval: C::Scalar, } struct CommitmentData { commitment: T, set_index: usize, point_indices: Vec, evals: Vec, } impl CommitmentData { fn new(commitment: T) -> Self { CommitmentData { commitment, set_index: 0, point_indices: vec![], evals: vec![], } } } trait Query: Sized { type Commitment: PartialEq + Copy; fn get_point(&self) -> F; fn get_eval(&self) -> F; fn get_commitment(&self) -> Self::Commitment; } fn construct_intermediate_sets>( queries: I, ) -> (Vec>, Vec>) where I: IntoIterator + Clone, { // Construct sets of unique commitments and corresponding information about // their queries. let mut commitment_map: Vec> = vec![]; // Also construct mapping from a unique point to a point_index. This defines // an ordering on the points. let mut point_index_map = BTreeMap::new(); // Iterate over all of the queries, computing the ordering of the points // while also creating new commitment data. for query in queries.clone() { let num_points = point_index_map.len(); let point_idx = point_index_map .entry(query.get_point()) .or_insert(num_points); if let Some(pos) = commitment_map .iter() .position(|comm| comm.commitment == query.get_commitment()) { commitment_map[pos].point_indices.push(*point_idx); } else { let mut tmp = CommitmentData::new(query.get_commitment()); tmp.point_indices.push(*point_idx); commitment_map.push(tmp); } } // Also construct inverse mapping from point_index to the point let mut inverse_point_index_map = BTreeMap::new(); for (&point, &point_index) in point_index_map.iter() { inverse_point_index_map.insert(point_index, point); } // Construct map of unique ordered point_idx_sets to their set_idx let mut point_idx_sets = BTreeMap::new(); // Also construct mapping from commitment to point_idx_set let mut commitment_set_map = Vec::new(); for commitment_data in commitment_map.iter() { let mut point_index_set = BTreeSet::new(); // Note that point_index_set is ordered, unlike point_indices for &point_index in commitment_data.point_indices.iter() { point_index_set.insert(point_index); } // Push point_index_set to CommitmentData for the relevant commitment commitment_set_map.push((commitment_data.commitment, point_index_set.clone())); let num_sets = point_idx_sets.len(); point_idx_sets.entry(point_index_set).or_insert(num_sets); } // Initialise empty evals vec for each unique commitment for commitment_data in commitment_map.iter_mut() { let len = commitment_data.point_indices.len(); commitment_data.evals = vec![F::zero(); len]; } // Populate set_index, evals and points for each commitment using point_idx_sets for query in queries { // The index of the point at which the commitment is queried let point_index = point_index_map.get(&query.get_point()).unwrap(); // The point_index_set at which the commitment was queried let mut point_index_set = BTreeSet::new(); for (commitment, point_idx_set) in commitment_set_map.iter() { if query.get_commitment() == *commitment { point_index_set = point_idx_set.clone(); } } assert!(!point_index_set.is_empty()); // The set_index of the point_index_set let set_index = point_idx_sets.get(&point_index_set).unwrap(); for commitment_data in commitment_map.iter_mut() { if query.get_commitment() == commitment_data.commitment { commitment_data.set_index = *set_index; } } let point_index_set: Vec = point_index_set.iter().cloned().collect(); // The offset of the point_index in the point_index_set let point_index_in_set = point_index_set .iter() .position(|i| i == point_index) .unwrap(); for commitment_data in commitment_map.iter_mut() { if query.get_commitment() == commitment_data.commitment { // Insert the eval using the ordering of the point_index_set commitment_data.evals[point_index_in_set] = query.get_eval(); } } } // Get actual points in each point set let mut point_sets: Vec> = vec![Vec::new(); point_idx_sets.len()]; for (point_idx_set, &set_idx) in point_idx_sets.iter() { for &point_idx in point_idx_set.iter() { let point = inverse_point_index_map.get(&point_idx).unwrap(); point_sets[set_idx].push(*point); } } (commitment_map, point_sets) } #[cfg(test)] mod tests { use super::{construct_intermediate_sets, Query}; use crate::arithmetic::{Field, Fp}; #[derive(Clone)] struct MyQuery { commitment: usize, point: F, eval: F, } impl Query for MyQuery { type Commitment = usize; fn get_point(&self) -> F { self.point } fn get_eval(&self) -> F { self.eval } fn get_commitment(&self) -> Self::Commitment { self.commitment } } #[test] fn test_coherence() { let points = &[ Fp::random(), Fp::random(), Fp::random(), Fp::random(), Fp::random(), ]; let queries = vec![ MyQuery { commitment: 0, point: points[0], eval: Fp::random(), }, MyQuery { commitment: 0, point: points[1], eval: Fp::random(), }, MyQuery { commitment: 1, point: points[0], eval: Fp::random(), }, MyQuery { commitment: 1, point: points[1], eval: Fp::random(), }, MyQuery { commitment: 2, point: points[0], eval: Fp::random(), }, MyQuery { commitment: 2, point: points[1], eval: Fp::random(), }, MyQuery { commitment: 2, point: points[2], eval: Fp::random(), }, MyQuery { commitment: 3, point: points[0], eval: Fp::random(), }, MyQuery { commitment: 3, point: points[3], eval: Fp::random(), }, MyQuery { commitment: 4, point: points[4], eval: Fp::random(), }, ]; let (commitment_data, point_sets) = construct_intermediate_sets(queries); let mut a = false; let mut a_set = 0; let mut b = false; let mut b_set = 0; let mut c = false; let mut c_set = 0; let mut d = false; let mut d_set = 0; for (i, mut point_set) in point_sets.into_iter().enumerate() { point_set.sort(); if point_set.len() == 1 { assert_eq!(point_set[0], points[4]); assert!(!a); a = true; a_set = i; } else if point_set.len() == 2 { let mut v0 = [points[0], points[1]]; let mut v1 = [points[0], points[3]]; v0.sort(); v1.sort(); if &point_set[..] == &v0[..] { assert!(!b); b = true; b_set = i; } else if &point_set[..] == &v1[..] { assert!(!c); c = true; c_set = i; } else { panic!("unexpected"); } } else if point_set.len() == 3 { let mut v = [points[0], points[1], points[2]]; v.sort(); assert_eq!(&point_set[..], &v[..]); assert!(!d); d = true; d_set = i; } else { panic!("unexpected"); } } assert!(a & b & c & d); for commitment_data in commitment_data { assert_eq!( commitment_data.set_index, match commitment_data.commitment { 0 => b_set, 1 => b_set, 2 => d_set, 3 => c_set, 4 => a_set, _ => unreachable!(), } ); } } }