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;
#[derive(Debug, Clone)]
pub struct PedersenParams {
pub g: RistrettoPoint,
pub h: RistrettoPoint,
}
impl PedersenParams {
pub fn default_params() -> Self {
let g = RISTRETTO_BASEPOINT_POINT;
let g_bytes = g.compress().to_bytes();
let h_bytes = blake3::hash(&g_bytes);
let h_scalar = Scalar::from_bytes_mod_order(*h_bytes.as_bytes());
let h = h_scalar * g;
Self { g, h }
}
pub fn new(g: RistrettoPoint, h: RistrettoPoint) -> Self {
Self { g, h }
}
}
impl Default for PedersenParams {
fn default() -> Self {
Self::default_params()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PedersenCommitment {
pub point: RistrettoPoint,
pub compressed: CompressedRistretto,
}
#[derive(Debug, Clone)]
pub struct PedersenOpening {
pub value: Scalar,
pub randomness: Scalar,
}
impl PedersenCommitment {
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)
}
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 }
}
pub fn verify(
params: &PedersenParams,
opening: &PedersenOpening,
commitment: &PedersenCommitment,
) -> bool {
let expected = opening.randomness * params.g + opening.value * params.h;
expected == commitment.point
}
pub fn commit_u64(params: &PedersenParams, value: u64) -> (Self, PedersenOpening) {
Self::commit(params, &Scalar::from(value))
}
pub fn identity() -> Self {
let point = RistrettoPoint::identity();
Self {
point,
compressed: point.compress(),
}
}
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(¶ms, &value);
assert!(PedersenCommitment::verify(¶ms, &opening, &commitment));
}
#[test]
fn commit_u64_and_verify() {
let params = PedersenParams::default();
let (commitment, opening) = PedersenCommitment::commit_u64(¶ms, 12345);
assert!(PedersenCommitment::verify(¶ms, &opening, &commitment));
}
#[test]
fn wrong_value_fails() {
let params = PedersenParams::default();
let (commitment, mut opening) = PedersenCommitment::commit_u64(¶ms, 42);
opening.value = Scalar::from(99u64);
assert!(!PedersenCommitment::verify(¶ms, &opening, &commitment));
}
#[test]
fn wrong_randomness_fails() {
let params = PedersenParams::default();
let (commitment, mut opening) = PedersenCommitment::commit_u64(¶ms, 42);
opening.randomness = Scalar::random(&mut OsRng);
assert!(!PedersenCommitment::verify(¶ms, &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(¶ms, &v1);
let (c2, o2) = PedersenCommitment::commit(¶ms, &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(¶ms, &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(¶ms, &value);
let (c2, _) = PedersenCommitment::commit(¶ms, &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(¶ms, &value, &r);
let c2 = PedersenCommitment::commit_with_randomness(¶ms, &value, &r);
assert_eq!(c1.compressed, c2.compressed);
}
}