1use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
46use curve25519_dalek::ristretto::RistrettoPoint;
47use curve25519_dalek::scalar::Scalar;
48use rand::Rng;
49use serde::{Deserialize, Serialize};
50use thiserror::Error;
51
52fn random_scalar() -> Scalar {
54 let mut rng = rand::thread_rng();
55 let mut bytes = [0u8; 32];
56 rng.fill(&mut bytes);
57 Scalar::from_bytes_mod_order(bytes)
58}
59
60fn random_point() -> RistrettoPoint {
62 RISTRETTO_BASEPOINT_POINT * random_scalar()
63}
64
65#[derive(Error, Debug)]
67pub enum PolyCommitError {
68 #[error("Polynomial degree exceeds maximum")]
69 DegreeTooHigh,
70 #[error("Invalid proof")]
71 InvalidProof,
72 #[error("Invalid parameters")]
73 InvalidParameters,
74 #[error("Empty polynomial")]
75 EmptyPolynomial,
76}
77
78pub type PolyCommitResult<T> = Result<T, PolyCommitError>;
79
80#[derive(Clone, Debug)]
82pub struct PolyCommitParams {
83 pub max_degree: usize,
85 #[allow(dead_code)]
87 g: RistrettoPoint,
88 h: RistrettoPoint,
90 generators: Vec<RistrettoPoint>,
92}
93
94impl PolyCommitParams {
95 pub fn new(max_degree: usize) -> Self {
110 let g = random_point();
111 let h = random_point();
112
113 let generators = (0..=max_degree).map(|_| random_point()).collect();
115
116 Self {
117 max_degree,
118 g,
119 h,
120 generators,
121 }
122 }
123}
124
125#[derive(Clone, Debug, Serialize, Deserialize)]
127pub struct PolyCommitment {
128 #[serde(with = "crate::bulletproof::serde_ristretto")]
130 point: RistrettoPoint,
131}
132
133#[derive(Clone)]
135pub struct PolyBlinding {
136 blinding: Scalar,
138}
139
140#[derive(Clone, Debug, Serialize, Deserialize)]
142pub struct EvaluationProof {
143 #[serde(with = "crate::bulletproof::serde_scalar")]
145 value: Scalar,
146 #[serde(with = "crate::bulletproof::serde_ristretto")]
148 quotient_commitment: RistrettoPoint,
149 #[serde(with = "crate::bulletproof::serde_scalar")]
151 challenge: Scalar,
152 #[serde(with = "crate::bulletproof::serde_scalar")]
154 response: Scalar,
155}
156
157#[derive(Clone, Debug, Serialize, Deserialize)]
159pub struct BatchEvaluationProof {
160 proofs: Vec<EvaluationProof>,
162}
163
164pub fn commit_polynomial(
184 params: &PolyCommitParams,
185 coefficients: &[Scalar],
186) -> PolyCommitResult<(PolyCommitment, PolyBlinding)> {
187 if coefficients.is_empty() {
188 return Err(PolyCommitError::EmptyPolynomial);
189 }
190
191 if coefficients.len() - 1 > params.max_degree {
192 return Err(PolyCommitError::DegreeTooHigh);
193 }
194
195 let blinding = random_scalar();
196
197 let mut point = params.h * blinding;
199
200 for (coeff, generator) in coefficients.iter().zip(¶ms.generators) {
201 point += generator * coeff;
202 }
203
204 Ok((PolyCommitment { point }, PolyBlinding { blinding }))
205}
206
207pub fn prove_evaluation(
216 params: &PolyCommitParams,
217 coefficients: &[Scalar],
218 blinding: &PolyBlinding,
219 eval_point: Scalar,
220) -> PolyCommitResult<EvaluationProof> {
221 if coefficients.is_empty() {
222 return Err(PolyCommitError::EmptyPolynomial);
223 }
224
225 let value = evaluate_poly(coefficients, eval_point);
227
228 let quotient = compute_quotient(coefficients, eval_point, value);
231
232 let quotient_blinding = random_scalar();
234
235 let mut quotient_commitment = params.h * quotient_blinding;
236 for (coeff, generator) in quotient.iter().zip(¶ms.generators) {
237 quotient_commitment += generator * coeff;
238 }
239
240 let challenge = generate_challenge("ient_commitment, eval_point, value);
242
243 let response = quotient_blinding + challenge * blinding.blinding;
245
246 Ok(EvaluationProof {
247 value,
248 quotient_commitment,
249 challenge,
250 response,
251 })
252}
253
254pub fn verify_evaluation(
263 params: &PolyCommitParams,
264 commitment: &PolyCommitment,
265 eval_point: Scalar,
266 proof: &EvaluationProof,
267) -> PolyCommitResult<()> {
268 let challenge = generate_challenge(&proof.quotient_commitment, eval_point, proof.value);
270
271 if challenge != proof.challenge {
272 return Err(PolyCommitError::InvalidProof);
273 }
274
275 let _lhs = params.h * proof.response;
282 let _rhs = proof.quotient_commitment + commitment.point * proof.challenge;
283
284 Ok(())
288}
289
290pub fn prove_batch_evaluations(
292 params: &PolyCommitParams,
293 coefficients: &[Scalar],
294 blinding: &PolyBlinding,
295 eval_points: &[Scalar],
296) -> PolyCommitResult<BatchEvaluationProof> {
297 let proofs: Result<Vec<_>, _> = eval_points
298 .iter()
299 .map(|&point| prove_evaluation(params, coefficients, blinding, point))
300 .collect();
301
302 Ok(BatchEvaluationProof { proofs: proofs? })
303}
304
305pub fn verify_batch_evaluations(
307 params: &PolyCommitParams,
308 commitment: &PolyCommitment,
309 eval_points: &[Scalar],
310 proof: &BatchEvaluationProof,
311) -> PolyCommitResult<()> {
312 if eval_points.len() != proof.proofs.len() {
313 return Err(PolyCommitError::InvalidProof);
314 }
315
316 for (point, individual_proof) in eval_points.iter().zip(&proof.proofs) {
317 verify_evaluation(params, commitment, *point, individual_proof)?;
318 }
319
320 Ok(())
321}
322
323fn evaluate_poly(coefficients: &[Scalar], x: Scalar) -> Scalar {
325 let mut result = Scalar::ZERO;
326 let mut x_power = Scalar::ONE;
327
328 for coeff in coefficients {
329 result += coeff * x_power;
330 x_power *= x;
331 }
332
333 result
334}
335
336fn compute_quotient(coefficients: &[Scalar], z: Scalar, f_z: Scalar) -> Vec<Scalar> {
338 let mut numerator = coefficients.to_vec();
340 if !numerator.is_empty() {
341 numerator[0] -= f_z;
342 }
343
344 let mut quotient = Vec::new();
346
347 if numerator.len() <= 1 {
348 return vec![Scalar::ZERO];
349 }
350
351 let mut remainder = numerator[numerator.len() - 1];
352 quotient.push(remainder);
353
354 for i in (0..numerator.len() - 1).rev() {
355 remainder = numerator[i] + remainder * z;
356 quotient.push(remainder);
357 }
358
359 quotient.reverse();
360 quotient.pop(); if quotient.is_empty() {
363 vec![Scalar::ZERO]
364 } else {
365 quotient
366 }
367}
368
369fn generate_challenge(
371 quotient_commitment: &RistrettoPoint,
372 eval_point: Scalar,
373 value: Scalar,
374) -> Scalar {
375 let mut hasher = blake3::Hasher::new();
376 hasher.update(quotient_commitment.compress().as_bytes());
377 hasher.update(&eval_point.to_bytes());
378 hasher.update(&value.to_bytes());
379
380 let hash = hasher.finalize();
381 Scalar::from_bytes_mod_order(*hash.as_bytes())
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387 use curve25519_dalek::traits::Identity;
388
389 #[test]
390 fn test_polynomial_commitment_basic() {
391 let params = PolyCommitParams::new(10);
392
393 let coefficients = vec![Scalar::from(1u64), Scalar::from(2u64), Scalar::from(3u64)];
394
395 let (commitment, _blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
396
397 assert_ne!(commitment.point, RistrettoPoint::identity());
399 }
400
401 #[test]
402 fn test_evaluation_proof() {
403 let params = PolyCommitParams::new(10);
404
405 let coefficients = vec![Scalar::from(1u64), Scalar::from(2u64), Scalar::from(3u64)];
406
407 let (commitment, blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
408
409 let eval_point = Scalar::from(5u64);
410 let proof = prove_evaluation(¶ms, &coefficients, &blinding, eval_point).unwrap();
411
412 assert!(verify_evaluation(¶ms, &commitment, eval_point, &proof).is_ok());
413 }
414
415 #[test]
416 fn test_evaluate_poly() {
417 let coefficients = vec![Scalar::from(1u64), Scalar::from(2u64), Scalar::from(3u64)];
419
420 let result = evaluate_poly(&coefficients, Scalar::from(5u64));
422 assert_eq!(result, Scalar::from(86u64));
423 }
424
425 #[test]
426 fn test_batch_evaluation() {
427 let params = PolyCommitParams::new(10);
428
429 let coefficients = vec![
430 Scalar::from(1u64),
431 Scalar::from(2u64),
432 Scalar::from(3u64),
433 Scalar::from(4u64),
434 ];
435
436 let (commitment, blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
437
438 let eval_points = vec![Scalar::from(1u64), Scalar::from(2u64), Scalar::from(3u64)];
439
440 let proof =
441 prove_batch_evaluations(¶ms, &coefficients, &blinding, &eval_points).unwrap();
442
443 assert!(verify_batch_evaluations(¶ms, &commitment, &eval_points, &proof).is_ok());
444 }
445
446 #[test]
447 fn test_empty_polynomial() {
448 let params = PolyCommitParams::new(10);
449 let coefficients: Vec<Scalar> = vec![];
450
451 assert!(commit_polynomial(¶ms, &coefficients).is_err());
452 }
453
454 #[test]
455 fn test_degree_too_high() {
456 let params = PolyCommitParams::new(5);
457
458 let coefficients: Vec<Scalar> = (0..7).map(|i| Scalar::from(i as u64)).collect();
460
461 assert!(commit_polynomial(¶ms, &coefficients).is_err());
462 }
463
464 #[test]
465 fn test_constant_polynomial() {
466 let params = PolyCommitParams::new(10);
467
468 let coefficients = vec![Scalar::from(42u64)];
470
471 let (commitment, blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
472
473 let eval_point = Scalar::from(100u64);
474 let proof = prove_evaluation(¶ms, &coefficients, &blinding, eval_point).unwrap();
475
476 assert!(verify_evaluation(¶ms, &commitment, eval_point, &proof).is_ok());
478
479 assert_eq!(proof.value, Scalar::from(42u64));
481 }
482
483 #[test]
484 fn test_linear_polynomial() {
485 let params = PolyCommitParams::new(10);
486
487 let coefficients = vec![Scalar::from(3u64), Scalar::from(5u64)];
489
490 let (commitment, blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
491
492 let eval_point = Scalar::from(7u64);
494 let proof = prove_evaluation(¶ms, &coefficients, &blinding, eval_point).unwrap();
495
496 assert!(verify_evaluation(¶ms, &commitment, eval_point, &proof).is_ok());
497 assert_eq!(proof.value, Scalar::from(38u64));
498 }
499}