use curve25519_dalek::{
constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT},
ristretto::RistrettoPoint,
scalar::Scalar,
traits::MultiscalarMul,
};
use rand_core::{OsRng, RngCore};
use sha3::{Digest, Sha3_512};
#[allow(non_snake_case)]
#[derive(Copy, Clone)]
pub(crate) struct PedersenGens {
pub B: RistrettoPoint,
pub B_blinding: RistrettoPoint,
}
impl PedersenGens {
pub fn commit(&self, value: Scalar, blinding: Scalar) -> RistrettoPoint {
RistrettoPoint::multiscalar_mul(&[value, blinding], &[self.B, self.B_blinding])
}
}
impl Default for PedersenGens {
fn default() -> Self {
PedersenGens {
B: RISTRETTO_BASEPOINT_POINT,
B_blinding: RistrettoPoint::hash_from_bytes::<Sha3_512>(
RISTRETTO_BASEPOINT_COMPRESSED.as_bytes(),
),
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct PedersenCommitment {
commitment: RistrettoPoint,
blinding_factor: Scalar,
value: Scalar,
}
impl PedersenCommitment {
#[inline]
pub fn get_commitment(&self) -> RistrettoPoint {
self.commitment
}
#[inline]
pub fn get_blinding(&self) -> Scalar {
self.blinding_factor
}
#[inline]
pub fn get_value(&self) -> Scalar {
self.value
}
pub fn commit(value: Scalar) -> PedersenCommitment {
let mut rng = OsRng {};
let blinding_factor = Scalar::random(&mut rng);
Self {
commitment: PedersenGens::default().commit(value, blinding_factor),
blinding_factor,
value,
}
}
#[allow(dead_code)]
pub fn verify(&self) -> bool {
PedersenCommitment::verify_from_values(self.commitment, self.blinding_factor, self.value)
}
pub fn verify_from_values(
commitment: RistrettoPoint,
blinding_factor: Scalar,
value: Scalar,
) -> bool {
PedersenGens::default()
.commit(value, blinding_factor)
.eq(&commitment)
}
}
#[derive(Clone, Debug)]
pub struct RistrettoCommitment {
commitment: Scalar,
blinding_factor: Scalar,
value: RistrettoPoint,
}
impl RistrettoCommitment {
#[inline]
pub fn get_commitment(&self) -> Scalar {
self.commitment
}
#[inline]
pub fn get_blinding(&self) -> Scalar {
self.blinding_factor
}
#[inline]
pub fn get_value(&self) -> RistrettoPoint {
self.value
}
pub fn commit(point: RistrettoPoint) -> RistrettoCommitment {
let mut rng = OsRng {};
let blinding_factor = rng.next_u64();
let mut hasher = Sha3_512::new();
hasher.input(point.compress().as_bytes());
hasher.input(blinding_factor.to_le_bytes());
Self {
commitment: Scalar::from_hash(hasher),
blinding_factor: Scalar::from(blinding_factor),
value: point,
}
}
pub fn verify(&self) -> bool {
RistrettoCommitment::verify_from_values(self.commitment, self.blinding_factor, self.value)
}
pub fn verify_from_values(
commitment: Scalar,
blinding_factor: Scalar,
value: RistrettoPoint,
) -> bool {
let mut hasher = Sha3_512::new();
hasher.input(value.compress().as_bytes());
let blinding_factor_bytes: [u8; 8] = blinding_factor.to_bytes()[..8]
.try_into()
.expect("Not enough bytes in hash");
hasher.input(blinding_factor_bytes);
Scalar::from_hash(hasher).eq(&commitment)
}
}
#[cfg(test)]
mod pedersen_tests {
use curve25519_dalek::scalar::Scalar;
use rand_core::OsRng;
use super::PedersenCommitment;
#[test]
fn test_commit_and_open() {
let mut rng = OsRng {};
let value = Scalar::random(&mut rng);
let bad_value = Scalar::random(&mut rng);
let commitment = PedersenCommitment::commit(value);
assert!(commitment.verify());
assert!(!PedersenCommitment::verify_from_values(
commitment.commitment,
commitment.blinding_factor,
bad_value
))
}
}
#[cfg(test)]
mod hash_commit_tests {
use curve25519_dalek::ristretto::RistrettoPoint;
use rand_core::OsRng;
use super::RistrettoCommitment;
#[test]
fn test_commit_and_open() {
let mut rng = OsRng {};
let value = RistrettoPoint::random(&mut rng);
let bad_value = RistrettoPoint::random(&mut rng);
let commitment = RistrettoCommitment::commit(value);
assert!(commitment.verify());
assert!(!RistrettoCommitment::verify_from_values(
commitment.get_commitment(),
commitment.get_blinding(),
bad_value
))
}
}