1use crate::commitments::ajtai_commitment::{
2 AjtaiCommitment, AjtaiParameters, CommitError, Opening, ParameterError, VerificationError,
3};
4use crate::ring::rq::Rq;
5use crate::ring::rq_matrix::RqMatrix;
6use crate::ring::rq_vector::RqVector;
7use crate::ring::zq::Zq;
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11pub enum HierarchicalError {
12 #[error("parameter error: {0}")]
13 ParameterError(#[from] ParameterError),
14 #[error("commit error: {0}")]
15 CommitError(#[from] CommitError),
16 #[error("verification error: {0}")]
17 VerificationError(#[from] VerificationError),
18 #[error("invalid decomposition base: {0}")]
19 InvalidBase(Zq),
20 #[error("invalid number of parts: {0}")]
21 InvalidPartCount(usize),
22 #[error("empty witness batch")]
23 EmptyWitnessBatch,
24}
25
26#[derive(Debug, Clone)]
30pub struct DecompositionParameters {
31 base: Zq,
32 num_parts: usize,
33}
34
35impl DecompositionParameters {
36 pub fn new(base: Zq, num_parts: usize) -> Result<Self, HierarchicalError> {
40 if base <= Zq::ONE {
41 return Err(HierarchicalError::InvalidBase(base));
42 }
43 if num_parts == 0 {
44 return Err(HierarchicalError::InvalidPartCount(num_parts));
45 }
46
47 Ok(Self { base, num_parts })
48 }
49
50 pub fn base(&self) -> Zq {
52 self.base
53 }
54
55 pub fn num_parts(&self) -> usize {
57 self.num_parts
58 }
59}
60
61#[derive(Debug, Clone)]
67pub struct HierarchicalProof<const M: usize, const N: usize, const D: usize> {
68 pub outer_commitment: RqVector<M, D>,
70 pub outer_opening: Opening<N, D>,
72 pub inner_commitments: Vec<RqVector<M, D>>,
74 pub inner_openings: Vec<Opening<N, D>>,
76 pub flattened_parts: Vec<Rq<D>>,
78}
79
80#[derive(Debug)]
85pub struct HierarchicalCommitment<const M: usize, const N: usize, const D: usize> {
86 inner_scheme: AjtaiCommitment<M, N, D>,
88 outer_scheme: AjtaiCommitment<M, N, D>,
90 decomp_params: DecompositionParameters,
92}
93
94impl<const M: usize, const N: usize, const D: usize> HierarchicalCommitment<M, N, D> {
95 pub fn new(
98 params: AjtaiParameters,
99 inner_matrix: RqMatrix<M, N, D>,
100 outer_matrix: RqMatrix<M, N, D>,
101 decomp_params: DecompositionParameters,
102 ) -> Result<Self, HierarchicalError> {
103 let inner_scheme = AjtaiCommitment::new(params.clone(), inner_matrix)?;
104 let outer_scheme = AjtaiCommitment::new(params, outer_matrix)?;
105
106 Ok(Self {
107 inner_scheme,
108 outer_scheme,
109 decomp_params,
110 })
111 }
112
113 fn decompose_polynomial(&self, poly: &Rq<D>) -> Vec<Rq<D>> {
114 poly.decompose(self.decomp_params.base(), self.decomp_params.num_parts())
115 }
116
117 fn decompose_all_commitments(&self, commitments: &[RqVector<M, D>]) -> Vec<Rq<D>> {
120 let capacity = commitments.len() * M * self.decomp_params.num_parts();
121 let mut all_parts = Vec::with_capacity(capacity);
122 for commitment in commitments {
123 for poly in commitment.iter() {
124 let decomposed_parts = self.decompose_polynomial(poly);
125 all_parts.extend(decomposed_parts);
126 }
127 }
128 all_parts
129 }
130
131 fn create_outer_witness(&self, parts: &[Rq<D>]) -> RqVector<N, D> {
137 let witness_bound = self.outer_scheme.witness_bound();
138 let mut combined_coeffs = Vec::with_capacity(N);
139
140 if parts.len() >= N {
142 for part in parts.iter().take(N) {
144 combined_coeffs.push(part.clone());
145 }
146 } else {
147 for part in parts {
149 combined_coeffs.push(part.clone());
150 }
151
152 while combined_coeffs.len() < N {
154 combined_coeffs.push(Rq::zero());
155 }
156 }
157
158 let mut bounded_coeffs = Vec::with_capacity(N);
160 for poly in &combined_coeffs {
161 let mut bounded_coeffs_array = [Zq::ZERO; D];
163 for (i, coeff) in poly.get_coefficients().iter().enumerate() {
164 bounded_coeffs_array[i] = coeff.centered_mod(witness_bound);
165 }
166 bounded_coeffs.push(Rq::new(bounded_coeffs_array));
167 }
168
169 RqVector::from(bounded_coeffs)
170 }
171
172 pub fn commit_batch(
176 &self,
177 witnesses: &[RqVector<N, D>],
178 ) -> Result<HierarchicalProof<M, N, D>, HierarchicalError> {
179 if witnesses.is_empty() {
180 return Err(HierarchicalError::EmptyWitnessBatch);
181 }
182
183 let mut inner_commitments = Vec::with_capacity(witnesses.len());
185 let mut inner_openings = Vec::with_capacity(witnesses.len());
186
187 for witness in witnesses {
188 let (commitment, opening) = self.inner_scheme.commit(witness.clone())?;
189 inner_commitments.push(commitment);
190 inner_openings.push(opening);
191 }
192
193 let flattened_parts = self.decompose_all_commitments(&inner_commitments);
195
196 let outer_witness = self.create_outer_witness(&flattened_parts);
198
199 let (outer_commitment, outer_opening) = self.outer_scheme.commit(outer_witness)?;
201
202 Ok(HierarchicalProof {
203 outer_commitment,
204 outer_opening,
205 inner_commitments,
206 inner_openings,
207 flattened_parts,
208 })
209 }
210
211 pub fn verify(&self, proof: &HierarchicalProof<M, N, D>) -> Result<(), HierarchicalError> {
218 for (commitment, opening) in proof
220 .inner_commitments
221 .iter()
222 .zip(proof.inner_openings.iter())
223 {
224 self.inner_scheme.verify(commitment, opening)?;
225 }
226
227 let flattened_parts = self.decompose_all_commitments(&proof.inner_commitments);
230
231 if flattened_parts != proof.flattened_parts {
233 return Err(HierarchicalError::VerificationError(
234 VerificationError::CommitmentMismatch,
235 ));
236 }
237
238 let outer_witness = self.create_outer_witness(&flattened_parts);
240
241 let (expected_outer, _) = self.outer_scheme.commit(outer_witness)?;
243
244 if proof.outer_commitment != expected_outer {
246 return Err(HierarchicalError::VerificationError(
247 VerificationError::CommitmentMismatch,
248 ));
249 }
250
251 self.outer_scheme
253 .verify(&proof.outer_commitment, &proof.outer_opening)?;
254
255 Ok(())
256 }
257
258 pub fn inner_scheme(&self) -> &AjtaiCommitment<M, N, D> {
260 &self.inner_scheme
261 }
262
263 pub fn outer_scheme(&self) -> &AjtaiCommitment<M, N, D> {
265 &self.outer_scheme
266 }
267
268 pub fn decomp_params(&self) -> &DecompositionParameters {
270 &self.decomp_params
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use rand::rng;
278
279 const TEST_M: usize = 8;
280 const TEST_N: usize = 8;
281 const TEST_D: usize = 4;
282
283 fn create_test_scheme() -> HierarchicalCommitment<TEST_M, TEST_N, TEST_D> {
285 let beta = Zq::ONE;
286 let witness_bound = Zq::ONE;
287 let params = AjtaiParameters::new(beta, witness_bound).unwrap();
288
289 let mut rng = rng();
290 let inner_matrix = RqMatrix::<TEST_M, TEST_N, TEST_D>::random(&mut rng);
291 let outer_matrix = RqMatrix::<TEST_M, TEST_N, TEST_D>::random(&mut rng);
292
293 let decomp_params = DecompositionParameters::new(Zq::new(8), 2).unwrap();
294
295 HierarchicalCommitment::new(params, inner_matrix, outer_matrix, decomp_params).unwrap()
296 }
297
298 fn create_random_witnesses(count: usize) -> Vec<RqVector<TEST_N, TEST_D>> {
299 let mut rng = rng();
300 (0..count)
301 .map(|_| RqVector::random_ternary(&mut rng))
302 .collect()
303 }
304
305 #[test]
306 fn test_decomposition_parameters() {
307 assert!(DecompositionParameters::new(Zq::ZERO, 2).is_err());
308 assert!(DecompositionParameters::new(Zq::TWO, 0).is_err());
309 let params = DecompositionParameters::new(Zq::new(8), 3).unwrap();
310 assert_eq!(params.base(), Zq::new(8));
311 assert_eq!(params.num_parts(), 3);
312 }
313
314 #[test]
315 fn test_hierarchical_creation() {
316 let _ = create_test_scheme();
317 }
318
319 #[test]
320 fn test_commit_and_verify() {
321 let scheme = create_test_scheme();
322 let witnesses = create_random_witnesses(3);
323
324 let proof = scheme.commit_batch(&witnesses).unwrap();
325 assert!(scheme.verify(&proof).is_ok());
326 }
327
328 #[test]
329 fn test_tampered_inner_commitment() {
330 let scheme = create_test_scheme();
331 let witnesses = create_random_witnesses(3);
332
333 let mut proof = scheme.commit_batch(&witnesses).unwrap();
334
335 let mut rng = rng();
337 proof.inner_commitments[0] = RqVector::random(&mut rng);
338
339 assert!(scheme.verify(&proof).is_err());
340 }
341
342 #[test]
343 fn test_tampered_outer_commitment() {
344 let scheme = create_test_scheme();
345 let witnesses = create_random_witnesses(3);
346
347 let mut proof = scheme.commit_batch(&witnesses).unwrap();
348
349 let mut rng = rng();
351 proof.outer_commitment = RqVector::random(&mut rng);
352
353 assert!(scheme.verify(&proof).is_err());
354 }
355}