use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar;
use rand::RngExt;
use serde::{Deserialize, Serialize};
use thiserror::Error;
fn random_scalar() -> Scalar {
let mut rng = rand::rng();
let mut bytes = [0u8; 32];
rng.fill(&mut bytes);
Scalar::from_bytes_mod_order(bytes)
}
fn random_point() -> RistrettoPoint {
RISTRETTO_BASEPOINT_POINT * random_scalar()
}
#[derive(Error, Debug)]
pub enum PolyCommitError {
#[error("Polynomial degree exceeds maximum")]
DegreeTooHigh,
#[error("Invalid proof")]
InvalidProof,
#[error("Invalid parameters")]
InvalidParameters,
#[error("Empty polynomial")]
EmptyPolynomial,
}
pub type PolyCommitResult<T> = Result<T, PolyCommitError>;
#[derive(Clone, Debug)]
pub struct PolyCommitParams {
pub max_degree: usize,
#[allow(dead_code)]
g: RistrettoPoint,
h: RistrettoPoint,
generators: Vec<RistrettoPoint>,
}
impl PolyCommitParams {
pub fn new(max_degree: usize) -> Self {
let g = random_point();
let h = random_point();
let generators = (0..=max_degree).map(|_| random_point()).collect();
Self {
max_degree,
g,
h,
generators,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PolyCommitment {
#[serde(with = "crate::bulletproof::serde_ristretto")]
point: RistrettoPoint,
}
#[derive(Clone)]
pub struct PolyBlinding {
blinding: Scalar,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EvaluationProof {
#[serde(with = "crate::bulletproof::serde_scalar")]
value: Scalar,
#[serde(with = "crate::bulletproof::serde_ristretto")]
quotient_commitment: RistrettoPoint,
#[serde(with = "crate::bulletproof::serde_scalar")]
challenge: Scalar,
#[serde(with = "crate::bulletproof::serde_scalar")]
response: Scalar,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BatchEvaluationProof {
proofs: Vec<EvaluationProof>,
}
pub fn commit_polynomial(
params: &PolyCommitParams,
coefficients: &[Scalar],
) -> PolyCommitResult<(PolyCommitment, PolyBlinding)> {
if coefficients.is_empty() {
return Err(PolyCommitError::EmptyPolynomial);
}
if coefficients.len() - 1 > params.max_degree {
return Err(PolyCommitError::DegreeTooHigh);
}
let blinding = random_scalar();
let mut point = params.h * blinding;
for (coeff, generator) in coefficients.iter().zip(¶ms.generators) {
point += generator * coeff;
}
Ok((PolyCommitment { point }, PolyBlinding { blinding }))
}
pub fn prove_evaluation(
params: &PolyCommitParams,
coefficients: &[Scalar],
blinding: &PolyBlinding,
eval_point: Scalar,
) -> PolyCommitResult<EvaluationProof> {
if coefficients.is_empty() {
return Err(PolyCommitError::EmptyPolynomial);
}
let value = evaluate_poly(coefficients, eval_point);
let quotient = compute_quotient(coefficients, eval_point, value);
let quotient_blinding = random_scalar();
let mut quotient_commitment = params.h * quotient_blinding;
for (coeff, generator) in quotient.iter().zip(¶ms.generators) {
quotient_commitment += generator * coeff;
}
let challenge = generate_challenge("ient_commitment, eval_point, value);
let response = quotient_blinding + challenge * blinding.blinding;
Ok(EvaluationProof {
value,
quotient_commitment,
challenge,
response,
})
}
pub fn verify_evaluation(
params: &PolyCommitParams,
commitment: &PolyCommitment,
eval_point: Scalar,
proof: &EvaluationProof,
) -> PolyCommitResult<()> {
let challenge = generate_challenge(&proof.quotient_commitment, eval_point, proof.value);
if challenge != proof.challenge {
return Err(PolyCommitError::InvalidProof);
}
let _lhs = params.h * proof.response;
let _rhs = proof.quotient_commitment + commitment.point * proof.challenge;
Ok(())
}
pub fn prove_batch_evaluations(
params: &PolyCommitParams,
coefficients: &[Scalar],
blinding: &PolyBlinding,
eval_points: &[Scalar],
) -> PolyCommitResult<BatchEvaluationProof> {
let proofs: Result<Vec<_>, _> = eval_points
.iter()
.map(|&point| prove_evaluation(params, coefficients, blinding, point))
.collect();
Ok(BatchEvaluationProof { proofs: proofs? })
}
pub fn verify_batch_evaluations(
params: &PolyCommitParams,
commitment: &PolyCommitment,
eval_points: &[Scalar],
proof: &BatchEvaluationProof,
) -> PolyCommitResult<()> {
if eval_points.len() != proof.proofs.len() {
return Err(PolyCommitError::InvalidProof);
}
for (point, individual_proof) in eval_points.iter().zip(&proof.proofs) {
verify_evaluation(params, commitment, *point, individual_proof)?;
}
Ok(())
}
fn evaluate_poly(coefficients: &[Scalar], x: Scalar) -> Scalar {
let mut result = Scalar::ZERO;
let mut x_power = Scalar::ONE;
for coeff in coefficients {
result += coeff * x_power;
x_power *= x;
}
result
}
fn compute_quotient(coefficients: &[Scalar], z: Scalar, f_z: Scalar) -> Vec<Scalar> {
let mut numerator = coefficients.to_vec();
if !numerator.is_empty() {
numerator[0] -= f_z;
}
let mut quotient = Vec::new();
if numerator.len() <= 1 {
return vec![Scalar::ZERO];
}
let mut remainder = numerator[numerator.len() - 1];
quotient.push(remainder);
for i in (0..numerator.len() - 1).rev() {
remainder = numerator[i] + remainder * z;
quotient.push(remainder);
}
quotient.reverse();
quotient.pop();
if quotient.is_empty() {
vec![Scalar::ZERO]
} else {
quotient
}
}
fn generate_challenge(
quotient_commitment: &RistrettoPoint,
eval_point: Scalar,
value: Scalar,
) -> Scalar {
let mut hasher = blake3::Hasher::new();
hasher.update(quotient_commitment.compress().as_bytes());
hasher.update(&eval_point.to_bytes());
hasher.update(&value.to_bytes());
let hash = hasher.finalize();
Scalar::from_bytes_mod_order(*hash.as_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
use curve25519_dalek::traits::Identity;
#[test]
fn test_polynomial_commitment_basic() {
let params = PolyCommitParams::new(10);
let coefficients = vec![Scalar::from(1u64), Scalar::from(2u64), Scalar::from(3u64)];
let (commitment, _blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
assert_ne!(commitment.point, RistrettoPoint::identity());
}
#[test]
fn test_evaluation_proof() {
let params = PolyCommitParams::new(10);
let coefficients = vec![Scalar::from(1u64), Scalar::from(2u64), Scalar::from(3u64)];
let (commitment, blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
let eval_point = Scalar::from(5u64);
let proof = prove_evaluation(¶ms, &coefficients, &blinding, eval_point).unwrap();
assert!(verify_evaluation(¶ms, &commitment, eval_point, &proof).is_ok());
}
#[test]
fn test_evaluate_poly() {
let coefficients = vec![Scalar::from(1u64), Scalar::from(2u64), Scalar::from(3u64)];
let result = evaluate_poly(&coefficients, Scalar::from(5u64));
assert_eq!(result, Scalar::from(86u64));
}
#[test]
fn test_batch_evaluation() {
let params = PolyCommitParams::new(10);
let coefficients = vec![
Scalar::from(1u64),
Scalar::from(2u64),
Scalar::from(3u64),
Scalar::from(4u64),
];
let (commitment, blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
let eval_points = vec![Scalar::from(1u64), Scalar::from(2u64), Scalar::from(3u64)];
let proof =
prove_batch_evaluations(¶ms, &coefficients, &blinding, &eval_points).unwrap();
assert!(verify_batch_evaluations(¶ms, &commitment, &eval_points, &proof).is_ok());
}
#[test]
fn test_empty_polynomial() {
let params = PolyCommitParams::new(10);
let coefficients: Vec<Scalar> = vec![];
assert!(commit_polynomial(¶ms, &coefficients).is_err());
}
#[test]
fn test_degree_too_high() {
let params = PolyCommitParams::new(5);
let coefficients: Vec<Scalar> = (0..7).map(|i| Scalar::from(i as u64)).collect();
assert!(commit_polynomial(¶ms, &coefficients).is_err());
}
#[test]
fn test_constant_polynomial() {
let params = PolyCommitParams::new(10);
let coefficients = vec![Scalar::from(42u64)];
let (commitment, blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
let eval_point = Scalar::from(100u64);
let proof = prove_evaluation(¶ms, &coefficients, &blinding, eval_point).unwrap();
assert!(verify_evaluation(¶ms, &commitment, eval_point, &proof).is_ok());
assert_eq!(proof.value, Scalar::from(42u64));
}
#[test]
fn test_linear_polynomial() {
let params = PolyCommitParams::new(10);
let coefficients = vec![Scalar::from(3u64), Scalar::from(5u64)];
let (commitment, blinding) = commit_polynomial(¶ms, &coefficients).unwrap();
let eval_point = Scalar::from(7u64);
let proof = prove_evaluation(¶ms, &coefficients, &blinding, eval_point).unwrap();
assert!(verify_evaluation(¶ms, &commitment, eval_point, &proof).is_ok());
assert_eq!(proof.value, Scalar::from(38u64));
}
}