#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::{
elgamal::{DecryptHandle, ElGamalPubkey},
pedersen::{PedersenCommitment, PedersenOpening, G, H},
},
errors::ProofVerificationError,
},
curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng,
zeroize::Zeroize,
};
use {
crate::{sigma_proofs::errors::ValidityProofError, transcript::TranscriptProtocol},
arrayref::{array_ref, array_refs},
curve25519_dalek::{
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::{IsIdentity, VartimeMultiscalarMul},
},
merlin::Transcript,
};
#[allow(non_snake_case)]
#[derive(Clone)]
pub struct ValidityProof {
Y_0: CompressedRistretto,
Y_1: CompressedRistretto,
Y_2: CompressedRistretto,
z_r: Scalar,
z_x: Scalar,
}
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl ValidityProof {
pub fn new<T: Into<Scalar>>(
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey), amount: T,
opening: &PedersenOpening,
transcript: &mut Transcript,
) -> Self {
transcript.validity_proof_domain_sep();
let P_dest = destination_pubkey.get_point();
let P_auditor = auditor_pubkey.get_point();
let x = amount.into();
let r = opening.get_scalar();
let mut y_r = Scalar::random(&mut OsRng);
let mut y_x = Scalar::random(&mut OsRng);
let Y_0 = RistrettoPoint::multiscalar_mul(vec![&y_r, &y_x], vec![&(*H), &(*G)]).compress();
let Y_1 = (&y_r * P_dest).compress();
let Y_2 = (&y_r * P_auditor).compress();
transcript.append_point(b"Y_0", &Y_0);
transcript.append_point(b"Y_1", &Y_1);
transcript.append_point(b"Y_2", &Y_2);
let c = transcript.challenge_scalar(b"c");
transcript.challenge_scalar(b"w");
let z_r = &(&c * r) + &y_r;
let z_x = &(&c * &x) + &y_x;
y_r.zeroize();
y_x.zeroize();
Self {
Y_0,
Y_1,
Y_2,
z_r,
z_x,
}
}
pub fn verify(
self,
commitment: &PedersenCommitment,
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
(destination_handle, auditor_handle): (&DecryptHandle, &DecryptHandle),
transcript: &mut Transcript,
) -> Result<(), ValidityProofError> {
transcript.validity_proof_domain_sep();
transcript.validate_and_append_point(b"Y_0", &self.Y_0)?;
transcript.validate_and_append_point(b"Y_1", &self.Y_1)?;
transcript.validate_and_append_point(b"Y_2", &self.Y_2)?;
let c = transcript.challenge_scalar(b"c");
let w = transcript.challenge_scalar(b"w");
let ww = &w * &w;
let w_negated = -&w;
let ww_negated = -&ww;
let Y_0 = self
.Y_0
.decompress()
.ok_or(ProofVerificationError::Deserialization)?;
let Y_1 = self
.Y_1
.decompress()
.ok_or(ProofVerificationError::Deserialization)?;
let Y_2 = self
.Y_2
.decompress()
.ok_or(ProofVerificationError::Deserialization)?;
let P_dest = destination_pubkey.get_point();
let P_auditor = auditor_pubkey.get_point();
let C = commitment.get_point();
let D_dest = destination_handle.get_point();
let D_auditor = auditor_handle.get_point();
let check = RistrettoPoint::vartime_multiscalar_mul(
vec![
&self.z_r, &self.z_x, &(-&c), &-(&Scalar::one()), &(&w * &self.z_r), &(&w_negated * &c), &w_negated, &(&ww * &self.z_r), &(&ww_negated * &c), &ww_negated, ],
vec![
&(*H), &(*G), C, &Y_0, P_dest, D_dest, &Y_1, P_auditor, D_auditor, &Y_2, ],
);
if check.is_identity() {
Ok(())
} else {
Err(ProofVerificationError::AlgebraicRelation.into())
}
}
pub fn to_bytes(&self) -> [u8; 160] {
let mut buf = [0_u8; 160];
buf[..32].copy_from_slice(self.Y_0.as_bytes());
buf[32..64].copy_from_slice(self.Y_1.as_bytes());
buf[64..96].copy_from_slice(self.Y_2.as_bytes());
buf[96..128].copy_from_slice(self.z_r.as_bytes());
buf[128..160].copy_from_slice(self.z_x.as_bytes());
buf
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidityProofError> {
if bytes.len() != 160 {
return Err(ProofVerificationError::Deserialization.into());
}
let bytes = array_ref![bytes, 0, 160];
let (Y_0, Y_1, Y_2, z_r, z_x) = array_refs![bytes, 32, 32, 32, 32, 32];
let Y_0 = CompressedRistretto::from_slice(Y_0);
let Y_1 = CompressedRistretto::from_slice(Y_1);
let Y_2 = CompressedRistretto::from_slice(Y_2);
let z_r =
Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?;
let z_x =
Scalar::from_canonical_bytes(*z_x).ok_or(ProofVerificationError::Deserialization)?;
Ok(ValidityProof {
Y_0,
Y_1,
Y_2,
z_r,
z_x,
})
}
}
#[allow(non_snake_case)]
#[derive(Clone)]
pub struct AggregatedValidityProof(ValidityProof);
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl AggregatedValidityProof {
pub fn new<T: Into<Scalar>>(
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
(amount_lo, amount_hi): (T, T),
(opening_lo, opening_hi): (&PedersenOpening, &PedersenOpening),
transcript: &mut Transcript,
) -> Self {
transcript.aggregated_validity_proof_domain_sep();
let t = transcript.challenge_scalar(b"t");
let aggregated_message = amount_lo.into() + amount_hi.into() * t;
let aggregated_opening = opening_lo + &(opening_hi * &t);
AggregatedValidityProof(ValidityProof::new(
(destination_pubkey, auditor_pubkey),
aggregated_message,
&aggregated_opening,
transcript,
))
}
pub fn verify(
self,
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
(commitment_lo, commitment_hi): (&PedersenCommitment, &PedersenCommitment),
(destination_handle_lo, destination_handle_hi): (&DecryptHandle, &DecryptHandle),
(auditor_handle_lo, auditor_handle_hi): (&DecryptHandle, &DecryptHandle),
transcript: &mut Transcript,
) -> Result<(), ValidityProofError> {
transcript.aggregated_validity_proof_domain_sep();
let t = transcript.challenge_scalar(b"t");
let aggregated_commitment = commitment_lo + commitment_hi * t;
let destination_aggregated_handle = destination_handle_lo + destination_handle_hi * t;
let auditor_aggregated_handle = auditor_handle_lo + auditor_handle_hi * t;
let AggregatedValidityProof(validity_proof) = self;
validity_proof.verify(
&aggregated_commitment,
(destination_pubkey, auditor_pubkey),
(&destination_aggregated_handle, &auditor_aggregated_handle),
transcript,
)
}
pub fn to_bytes(&self) -> [u8; 160] {
self.0.to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidityProofError> {
ValidityProof::from_bytes(bytes).map(Self)
}
}
#[cfg(test)]
mod test {
use {
super::*,
crate::encryption::{elgamal::ElGamalKeypair, pedersen::Pedersen},
};
#[test]
fn test_validity_proof_correctness() {
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let amount: u64 = 55;
let (commitment, opening) = Pedersen::new(amount);
let destination_handle = destination_pubkey.decrypt_handle(&opening);
let auditor_handle = auditor_pubkey.decrypt_handle(&opening);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = ValidityProof::new(
(&destination_pubkey, &auditor_pubkey),
amount,
&opening,
&mut prover_transcript,
);
assert!(proof
.verify(
&commitment,
(&destination_pubkey, &auditor_pubkey),
(&destination_handle, &auditor_handle),
&mut verifier_transcript,
)
.is_ok());
}
#[test]
fn test_validity_proof_edge_cases() {
let destination_pubkey = ElGamalPubkey::from_bytes(&[0u8; 32]).unwrap();
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let amount: u64 = 55;
let (commitment, opening) = Pedersen::new(amount);
let destination_handle = destination_pubkey.decrypt_handle(&opening);
let auditor_handle = auditor_pubkey.decrypt_handle(&opening);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = ValidityProof::new(
(&destination_pubkey, &auditor_pubkey),
amount,
&opening,
&mut prover_transcript,
);
assert!(proof
.verify(
&commitment,
(&destination_pubkey, &auditor_pubkey),
(&destination_handle, &auditor_handle),
&mut verifier_transcript,
)
.is_err());
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalPubkey::from_bytes(&[0u8; 32]).unwrap();
let amount: u64 = 55;
let (commitment, opening) = Pedersen::new(amount);
let destination_handle = destination_pubkey.decrypt_handle(&opening);
let auditor_handle = auditor_pubkey.decrypt_handle(&opening);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = ValidityProof::new(
(&destination_pubkey, &auditor_pubkey),
amount,
&opening,
&mut prover_transcript,
);
assert!(proof
.verify(
&commitment,
(&destination_pubkey, &auditor_pubkey),
(&destination_handle, &auditor_handle),
&mut verifier_transcript,
)
.is_err());
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let amount: u64 = 0;
let commitment = PedersenCommitment::from_bytes(&[0u8; 32]).unwrap();
let opening = PedersenOpening::from_bytes(&[0u8; 32]).unwrap();
let destination_handle = destination_pubkey.decrypt_handle(&opening);
let auditor_handle = auditor_pubkey.decrypt_handle(&opening);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = ValidityProof::new(
(&destination_pubkey, &auditor_pubkey),
amount,
&opening,
&mut prover_transcript,
);
assert!(proof
.verify(
&commitment,
(&destination_pubkey, &auditor_pubkey),
(&destination_handle, &auditor_handle),
&mut verifier_transcript,
)
.is_ok());
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let amount: u64 = 55;
let (commitment, opening) = Pedersen::new(amount);
let destination_handle = destination_pubkey.decrypt_handle(&opening);
let auditor_handle = auditor_pubkey.decrypt_handle(&opening);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = ValidityProof::new(
(&destination_pubkey, &auditor_pubkey),
amount,
&opening,
&mut prover_transcript,
);
assert!(proof
.verify(
&commitment,
(&destination_pubkey, &auditor_pubkey),
(&destination_handle, &auditor_handle),
&mut verifier_transcript,
)
.is_ok());
}
#[test]
fn test_aggregated_validity_proof() {
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let amount_lo: u64 = 55;
let amount_hi: u64 = 77;
let (commitment_lo, open_lo) = Pedersen::new(amount_lo);
let (commitment_hi, open_hi) = Pedersen::new(amount_hi);
let destination_handle_lo = destination_pubkey.decrypt_handle(&open_lo);
let destination_handle_hi = destination_pubkey.decrypt_handle(&open_hi);
let auditor_handle_lo = auditor_pubkey.decrypt_handle(&open_lo);
let auditor_handle_hi = auditor_pubkey.decrypt_handle(&open_hi);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = AggregatedValidityProof::new(
(&destination_pubkey, &auditor_pubkey),
(amount_lo, amount_hi),
(&open_lo, &open_hi),
&mut prover_transcript,
);
assert!(proof
.verify(
(&destination_pubkey, &auditor_pubkey),
(&commitment_lo, &commitment_hi),
(&destination_handle_lo, &destination_handle_hi),
(&auditor_handle_lo, &auditor_handle_hi),
&mut verifier_transcript,
)
.is_ok());
}
}