si-commitment-scheme 0.1.0

Pedersen commitments, Blake3-based polynomial commitments, batch opening/verification with binding and hiding properties
Documentation
//! Pedersen commitment scheme over Curve25519.
//!
//! A Pedersen commitment `C = r*G + v*H` is perfectly hiding and
//! computationally binding under the discrete logarithm assumption.

use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::traits::Identity;
use rand::rngs::OsRng;

/// Public parameters for the Pedersen commitment scheme.
#[derive(Debug, Clone)]
pub struct PedersenParams {
    /// Generator G.
    pub g: RistrettoPoint,
    /// Generator H (independent of G).
    pub h: RistrettoPoint,
}

impl PedersenParams {
    /// Create default parameters using standard basepoint and a derived second generator.
    pub fn default_params() -> Self {
        let g = RISTRETTO_BASEPOINT_POINT;
        // Derive H deterministically from G by hashing G's encoding
        let g_bytes = g.compress().to_bytes();
        let h_bytes = blake3::hash(&g_bytes);
        // Convert hash to scalar and multiply by G to get H
        let h_scalar = Scalar::from_bytes_mod_order(*h_bytes.as_bytes());
        let h = h_scalar * g;
        Self { g, h }
    }

    /// Create custom parameters with given generators.
    pub fn new(g: RistrettoPoint, h: RistrettoPoint) -> Self {
        Self { g, h }
    }
}

impl Default for PedersenParams {
    fn default() -> Self {
        Self::default_params()
    }
}

/// A Pedersen commitment.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PedersenCommitment {
    /// The commitment point C = r*G + v*H.
    pub point: RistrettoPoint,
    /// The compressed (serialized) form.
    pub compressed: CompressedRistretto,
}

/// Opening information for a Pedersen commitment.
#[derive(Debug, Clone)]
pub struct PedersenOpening {
    /// The committed value.
    pub value: Scalar,
    /// The blinding factor.
    pub randomness: Scalar,
}

impl PedersenCommitment {
    /// Commit to a value with a random blinding factor.
    pub fn commit(params: &PedersenParams, value: &Scalar) -> (Self, PedersenOpening) {
        let randomness = Scalar::random(&mut OsRng);
        let commitment = Self::commit_with_randomness(params, value, &randomness);
        let opening = PedersenOpening {
            value: *value,
            randomness,
        };
        (commitment, opening)
    }

    /// Commit to a value with a specific blinding factor.
    pub fn commit_with_randomness(
        params: &PedersenParams,
        value: &Scalar,
        randomness: &Scalar,
    ) -> Self {
        let point = randomness * params.g + value * params.h;
        let compressed = point.compress();
        Self { point, compressed }
    }

    /// Verify an opening against this commitment.
    pub fn verify(
        params: &PedersenParams,
        opening: &PedersenOpening,
        commitment: &PedersenCommitment,
    ) -> bool {
        let expected = opening.randomness * params.g + opening.value * params.h;
        expected == commitment.point
    }

    /// Create a commitment from a u64 value.
    pub fn commit_u64(params: &PedersenParams, value: u64) -> (Self, PedersenOpening) {
        Self::commit(params, &Scalar::from(value))
    }

    /// The identity (zero) commitment.
    pub fn identity() -> Self {
        let point = RistrettoPoint::identity();
        Self {
            point,
            compressed: point.compress(),
        }
    }

    /// Add two commitments homomorphically: C1 + C2 commits to v1 + v2.
    pub fn add(&self, other: &PedersenCommitment) -> PedersenCommitment {
        let point = self.point + other.point;
        PedersenCommitment {
            point,
            compressed: point.compress(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn commit_and_verify() {
        let params = PedersenParams::default();
        let value = Scalar::from(42u64);
        let (commitment, opening) = PedersenCommitment::commit(&params, &value);
        assert!(PedersenCommitment::verify(&params, &opening, &commitment));
    }

    #[test]
    fn commit_u64_and_verify() {
        let params = PedersenParams::default();
        let (commitment, opening) = PedersenCommitment::commit_u64(&params, 12345);
        assert!(PedersenCommitment::verify(&params, &opening, &commitment));
    }

    #[test]
    fn wrong_value_fails() {
        let params = PedersenParams::default();
        let (commitment, mut opening) = PedersenCommitment::commit_u64(&params, 42);
        opening.value = Scalar::from(99u64);
        assert!(!PedersenCommitment::verify(&params, &opening, &commitment));
    }

    #[test]
    fn wrong_randomness_fails() {
        let params = PedersenParams::default();
        let (commitment, mut opening) = PedersenCommitment::commit_u64(&params, 42);
        opening.randomness = Scalar::random(&mut OsRng);
        assert!(!PedersenCommitment::verify(&params, &opening, &commitment));
    }

    #[test]
    fn homomorphic_addition() {
        let params = PedersenParams::default();
        let v1 = Scalar::from(10u64);
        let v2 = Scalar::from(20u64);
        let (c1, o1) = PedersenCommitment::commit(&params, &v1);
        let (c2, o2) = PedersenCommitment::commit(&params, &v2);
        let c_sum = c1.add(&c2);
        let combined_value = v1 + v2;
        let combined_randomness = o1.randomness + o2.randomness;
        let expected =
            PedersenCommitment::commit_with_randomness(&params, &combined_value, &combined_randomness);
        assert_eq!(c_sum.point, expected.point);
    }

    #[test]
    fn different_randomness_different_commitment() {
        let params = PedersenParams::default();
        let value = Scalar::from(42u64);
        let (c1, _) = PedersenCommitment::commit(&params, &value);
        let (c2, _) = PedersenCommitment::commit(&params, &value);
        assert_ne!(c1.compressed, c2.compressed);
    }

    #[test]
    fn deterministic_with_same_randomness() {
        let params = PedersenParams::default();
        let value = Scalar::from(42u64);
        let r = Scalar::from(999u64);
        let c1 = PedersenCommitment::commit_with_randomness(&params, &value, &r);
        let c2 = PedersenCommitment::commit_with_randomness(&params, &value, &r);
        assert_eq!(c1.compressed, c2.compressed);
    }
}