[zk-token-sdk] Add ciphertext validity proof with 3 handles (#809)
* refactor ciphertext validity sigma proofs for 2 handles * add ciphertext validity sigma proofs for 3 handles * add pod types for ciphertext validity sigma proofs for 3 handles * add `allow(dead_code)` temporarily * cargo clippy * Update zk-token-sdk/src/zk_token_elgamal/pod/sigma_proofs.rs Co-authored-by: Jon C <me@jonc.dev> --------- Co-authored-by: Jon C <me@jonc.dev>
This commit is contained in:
parent
fb465b3788
commit
3426f7ec0a
|
@ -0,0 +1,196 @@
|
|||
//! The batched ciphertext with 3 handles validity sigma proof system.
|
||||
//!
|
||||
//! A batched grouped ciphertext validity proof certifies the validity of two instances of a
|
||||
//! standard grouped ciphertext validity proof. An instance of a standard grouped ciphertext
|
||||
//! with 3 handles validity proof consists of one ciphertext and three decryption handles:
|
||||
//! `(commitment, source_handle, destination_handle, auditor_handle)`. An instance of a batched
|
||||
//! grouped ciphertext with 3 handles validity proof consist of a pair of `(commitment_0,
|
||||
//! source_handle_0, destination_handle_0, auditor_handle_0)` and `(commitment_1, source_handle_1,
|
||||
//! destination_handle_1, auditor_handle_1)`. The proof certifies the anagolous decryptable
|
||||
//! properties for each one of these pairs of commitment and decryption handles.
|
||||
//!
|
||||
//! The protocol guarantees computational soundness (by the hardness of discrete log) and perfect
|
||||
//! zero-knowledge in the random oracle model.
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
use crate::encryption::{
|
||||
elgamal::{DecryptHandle, ElGamalPubkey},
|
||||
pedersen::{PedersenCommitment, PedersenOpening},
|
||||
};
|
||||
use {
|
||||
crate::{
|
||||
sigma_proofs::{
|
||||
errors::ValidityProofVerificationError,
|
||||
grouped_ciphertext_validity_proof::GroupedCiphertext3HandlesValidityProof,
|
||||
},
|
||||
transcript::TranscriptProtocol,
|
||||
UNIT_LEN,
|
||||
},
|
||||
curve25519_dalek::scalar::Scalar,
|
||||
merlin::Transcript,
|
||||
};
|
||||
|
||||
/// Byte length of a batched grouped ciphertext validity proof for 3 handles
|
||||
#[allow(dead_code)]
|
||||
const BATCHED_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN: usize = UNIT_LEN * 6;
|
||||
|
||||
/// Batched grouped ciphertext validity proof with two handles.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone)]
|
||||
pub struct BatchedGroupedCiphertext3HandlesValidityProof(GroupedCiphertext3HandlesValidityProof);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl BatchedGroupedCiphertext3HandlesValidityProof {
|
||||
/// Creates a batched grouped ciphertext validity proof.
|
||||
///
|
||||
/// The function simply batches the input openings and invokes the standard grouped ciphertext
|
||||
/// validity proof constructor.
|
||||
pub fn new<T: Into<Scalar>>(
|
||||
source_pubkey: &ElGamalPubkey,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
auditor_pubkey: &ElGamalPubkey,
|
||||
amount_lo: T,
|
||||
amount_hi: T,
|
||||
opening_lo: &PedersenOpening,
|
||||
opening_hi: &PedersenOpening,
|
||||
transcript: &mut Transcript,
|
||||
) -> Self {
|
||||
transcript.batched_grouped_ciphertext_validity_proof_domain_separator();
|
||||
|
||||
let t = transcript.challenge_scalar(b"t");
|
||||
|
||||
let batched_message = amount_lo.into() + amount_hi.into() * t;
|
||||
let batched_opening = opening_lo + &(opening_hi * &t);
|
||||
|
||||
BatchedGroupedCiphertext3HandlesValidityProof(GroupedCiphertext3HandlesValidityProof::new(
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
batched_message,
|
||||
&batched_opening,
|
||||
transcript,
|
||||
))
|
||||
}
|
||||
|
||||
/// Verifies a batched grouped ciphertext validity proof.
|
||||
///
|
||||
/// The function does *not* hash the public keys, commitment, or decryption handles into the
|
||||
/// transcript. For security, the caller (the main protocol) should hash these public
|
||||
/// components prior to invoking this constructor.
|
||||
///
|
||||
/// This function is randomized. It uses `OsRng` internally to generate random scalars.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn verify(
|
||||
self,
|
||||
source_pubkey: &ElGamalPubkey,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
auditor_pubkey: &ElGamalPubkey,
|
||||
commitment_lo: &PedersenCommitment,
|
||||
commitment_hi: &PedersenCommitment,
|
||||
source_handle_lo: &DecryptHandle,
|
||||
source_handle_hi: &DecryptHandle,
|
||||
destination_handle_lo: &DecryptHandle,
|
||||
destination_handle_hi: &DecryptHandle,
|
||||
auditor_handle_lo: &DecryptHandle,
|
||||
auditor_handle_hi: &DecryptHandle,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), ValidityProofVerificationError> {
|
||||
transcript.batched_grouped_ciphertext_validity_proof_domain_separator();
|
||||
|
||||
let t = transcript.challenge_scalar(b"t");
|
||||
|
||||
let batched_commitment = commitment_lo + commitment_hi * t;
|
||||
let source_batched_handle = source_handle_lo + source_handle_hi * t;
|
||||
let destination_batched_handle = destination_handle_lo + destination_handle_hi * t;
|
||||
let auditor_batched_handle = auditor_handle_lo + auditor_handle_hi * t;
|
||||
|
||||
let BatchedGroupedCiphertext3HandlesValidityProof(validity_proof) = self;
|
||||
|
||||
validity_proof.verify(
|
||||
&batched_commitment,
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
&source_batched_handle,
|
||||
&destination_batched_handle,
|
||||
&auditor_batched_handle,
|
||||
transcript,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; BATCHED_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidityProofVerificationError> {
|
||||
GroupedCiphertext3HandlesValidityProof::from_bytes(bytes).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use {
|
||||
super::*,
|
||||
crate::encryption::{elgamal::ElGamalKeypair, pedersen::Pedersen},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_batched_grouped_ciphertext_validity_proof() {
|
||||
let source_keypair = ElGamalKeypair::new_rand();
|
||||
let source_pubkey = source_keypair.pubkey();
|
||||
|
||||
let destination_keypair = ElGamalKeypair::new_rand();
|
||||
let destination_pubkey = destination_keypair.pubkey();
|
||||
|
||||
let auditor_keypair = ElGamalKeypair::new_rand();
|
||||
let auditor_pubkey = auditor_keypair.pubkey();
|
||||
|
||||
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 source_handle_lo = source_pubkey.decrypt_handle(&open_lo);
|
||||
let source_handle_hi = source_pubkey.decrypt_handle(&open_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 = BatchedGroupedCiphertext3HandlesValidityProof::new(
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
amount_lo,
|
||||
amount_hi,
|
||||
&open_lo,
|
||||
&open_hi,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
assert!(proof
|
||||
.verify(
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
&commitment_lo,
|
||||
&commitment_hi,
|
||||
&source_handle_lo,
|
||||
&source_handle_hi,
|
||||
&destination_handle_lo,
|
||||
&destination_handle_hi,
|
||||
&auditor_handle_lo,
|
||||
&auditor_handle_hi,
|
||||
&mut verifier_transcript,
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
mod handles_2;
|
||||
mod handles_3;
|
||||
|
||||
pub use {
|
||||
handles_2::BatchedGroupedCiphertext2HandlesValidityProof,
|
||||
handles_3::BatchedGroupedCiphertext3HandlesValidityProof,
|
||||
};
|
|
@ -0,0 +1,450 @@
|
|||
//! The grouped ciphertext with 3 handles validity sigma proof system.
|
||||
//!
|
||||
//! This ciphertext validity proof is defined with respect to a Pedersen commitment and three
|
||||
//! decryption handles. The proof certifies that a given Pedersen commitment can be decrypted using
|
||||
//! ElGamal private keys that are associated with each of the three decryption handles. To generate
|
||||
//! the proof, a prover must provide the Pedersen opening associated with the commitment.
|
||||
//!
|
||||
//! The protocol guarantees computational soundness (by the hardness of discrete log) and perfect
|
||||
//! zero-knowledge in the random oracle model.
|
||||
//!
|
||||
//! In accordance with its application to the SPL Token program, the first decryption handle
|
||||
//! associated with the proof is referred to as the "source" handle, the second as the
|
||||
//! "destination" handle, and the third as the "auditor" handle.
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
use {
|
||||
crate::{
|
||||
encryption::{
|
||||
elgamal::{DecryptHandle, ElGamalPubkey},
|
||||
pedersen::{PedersenCommitment, PedersenOpening, G, H},
|
||||
},
|
||||
sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice},
|
||||
UNIT_LEN,
|
||||
},
|
||||
curve25519_dalek::traits::MultiscalarMul,
|
||||
rand::rngs::OsRng,
|
||||
zeroize::Zeroize,
|
||||
};
|
||||
use {
|
||||
crate::{
|
||||
sigma_proofs::errors::{SigmaProofVerificationError, ValidityProofVerificationError},
|
||||
transcript::TranscriptProtocol,
|
||||
},
|
||||
curve25519_dalek::{
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
scalar::Scalar,
|
||||
traits::{IsIdentity, VartimeMultiscalarMul},
|
||||
},
|
||||
merlin::Transcript,
|
||||
};
|
||||
|
||||
/// Byte length of a grouped ciphertext validity proof for 3 handles
|
||||
const GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN: usize = UNIT_LEN * 6;
|
||||
|
||||
/// The grouped ciphertext validity proof for 3 handles.
|
||||
///
|
||||
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone)]
|
||||
pub struct GroupedCiphertext3HandlesValidityProof {
|
||||
Y_0: CompressedRistretto,
|
||||
Y_1: CompressedRistretto,
|
||||
Y_2: CompressedRistretto,
|
||||
Y_3: CompressedRistretto,
|
||||
z_r: Scalar,
|
||||
z_x: Scalar,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl GroupedCiphertext3HandlesValidityProof {
|
||||
/// Creates a grouped ciphertext with 3 handles validity proof.
|
||||
///
|
||||
/// The function does *not* hash the public keys, commitment, or decryption handles into the
|
||||
/// transcript. For security, the caller (the main protocol) should hash these public
|
||||
/// components prior to invoking this constructor.
|
||||
///
|
||||
/// This function is randomized. It uses `OsRng` internally to generate random scalars.
|
||||
///
|
||||
/// Note that the proof constructor does not take the actual Pedersen commitment or decryption
|
||||
/// handles as input; it only takes the associated Pedersen opening instead.
|
||||
///
|
||||
/// * `source_pubkey` - The source ElGamal public key
|
||||
/// * `destination_pubkey` - The destination ElGamal public key
|
||||
/// * `auditor_pubkey` - The auditor ElGamal public key
|
||||
/// * `amount` - The committed message in the commitment
|
||||
/// * `opening` - The opening associated with the Pedersen commitment
|
||||
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
||||
pub fn new<T: Into<Scalar>>(
|
||||
source_pubkey: &ElGamalPubkey,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
auditor_pubkey: &ElGamalPubkey,
|
||||
amount: T,
|
||||
opening: &PedersenOpening,
|
||||
transcript: &mut Transcript,
|
||||
) -> Self {
|
||||
transcript.grouped_ciphertext_validity_proof_domain_separator();
|
||||
|
||||
// extract the relevant scalar and Ristretto points from the inputs
|
||||
let P_source = source_pubkey.get_point();
|
||||
let P_destination = destination_pubkey.get_point();
|
||||
let P_auditor = auditor_pubkey.get_point();
|
||||
|
||||
let x = amount.into();
|
||||
let r = opening.get_scalar();
|
||||
|
||||
// generate random masking factors that also serves as nonces
|
||||
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_source).compress();
|
||||
let Y_2 = (&y_r * P_destination).compress();
|
||||
let Y_3 = (&y_r * P_auditor).compress();
|
||||
|
||||
// record masking factors in transcript and get challenges
|
||||
transcript.append_point(b"Y_0", &Y_0);
|
||||
transcript.append_point(b"Y_1", &Y_1);
|
||||
transcript.append_point(b"Y_2", &Y_2);
|
||||
transcript.append_point(b"Y_3", &Y_3);
|
||||
|
||||
let c = transcript.challenge_scalar(b"c");
|
||||
transcript.challenge_scalar(b"w");
|
||||
|
||||
// compute masked message and opening
|
||||
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,
|
||||
Y_3,
|
||||
z_r,
|
||||
z_x,
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies a grouped ciphertext with 3 handles validity proof.
|
||||
///
|
||||
/// * `commitment` - The Pedersen commitment
|
||||
/// * `source_pubkey` - The source ElGamal public key
|
||||
/// * `destination_pubkey` - The destination ElGamal public key
|
||||
/// * `auditor_pubkey` - The auditor ElGamal public key
|
||||
/// * `source_handle` - The source decryption handle
|
||||
/// * `destination_handle` - The destination decryption handle
|
||||
/// * `auditor_handle` - The auditor decryption handle
|
||||
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
||||
pub fn verify(
|
||||
self,
|
||||
commitment: &PedersenCommitment,
|
||||
source_pubkey: &ElGamalPubkey,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
auditor_pubkey: &ElGamalPubkey,
|
||||
source_handle: &DecryptHandle,
|
||||
destination_handle: &DecryptHandle,
|
||||
auditor_handle: &DecryptHandle,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), ValidityProofVerificationError> {
|
||||
transcript.grouped_ciphertext_validity_proof_domain_separator();
|
||||
|
||||
// include `Y_0`, `Y_1`, `Y_2` to transcript and extract challenges
|
||||
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)?;
|
||||
// the point `Y_3` is defined with respect to the auditor public key and can be zero if the
|
||||
// auditor public key is zero
|
||||
transcript.append_point(b"Y_3", &self.Y_3);
|
||||
|
||||
let c = transcript.challenge_scalar(b"c");
|
||||
let w = transcript.challenge_scalar(b"w");
|
||||
let ww = &w * &w;
|
||||
let www = &w * &ww;
|
||||
|
||||
let w_negated = -&w;
|
||||
let ww_negated = -&ww;
|
||||
let www_negated = -&www;
|
||||
|
||||
// check the required algebraic conditions
|
||||
let Y_0 = self
|
||||
.Y_0
|
||||
.decompress()
|
||||
.ok_or(SigmaProofVerificationError::Deserialization)?;
|
||||
let Y_1 = self
|
||||
.Y_1
|
||||
.decompress()
|
||||
.ok_or(SigmaProofVerificationError::Deserialization)?;
|
||||
let Y_2 = self
|
||||
.Y_2
|
||||
.decompress()
|
||||
.ok_or(SigmaProofVerificationError::Deserialization)?;
|
||||
let Y_3 = self
|
||||
.Y_3
|
||||
.decompress()
|
||||
.ok_or(SigmaProofVerificationError::Deserialization)?;
|
||||
|
||||
let P_source = source_pubkey.get_point();
|
||||
let P_destination = destination_pubkey.get_point();
|
||||
let P_auditor = auditor_pubkey.get_point();
|
||||
|
||||
let C = commitment.get_point();
|
||||
let D_source = source_handle.get_point();
|
||||
let D_destination = destination_handle.get_point();
|
||||
let D_auditor = auditor_handle.get_point();
|
||||
|
||||
let check = RistrettoPoint::vartime_multiscalar_mul(
|
||||
vec![
|
||||
&self.z_r, // z_r
|
||||
&self.z_x, // z_x
|
||||
&(-&c), // -c
|
||||
&-(&Scalar::one()), // -identity
|
||||
&(&w * &self.z_r), // w * z_r
|
||||
&(&w_negated * &c), // -w * c
|
||||
&w_negated, // -w
|
||||
&(&ww * &self.z_r), // ww * z_r
|
||||
&(&ww_negated * &c), // -ww * c
|
||||
&ww_negated, // -ww
|
||||
&(&www * &self.z_r), // www * z_r
|
||||
&(&www_negated * &c), // -www * c
|
||||
&www_negated, // -www
|
||||
],
|
||||
vec![
|
||||
&(*H), // H
|
||||
&(*G), // G
|
||||
C, // C
|
||||
&Y_0, // Y_0
|
||||
P_source, // P_destination
|
||||
D_source, // D_destination
|
||||
&Y_1, // Y_1
|
||||
P_destination, // P_destination
|
||||
D_destination, // D_destination
|
||||
&Y_2, // Y_1
|
||||
P_auditor, // P_auditor
|
||||
D_auditor, // D_auditor
|
||||
&Y_3, // Y_2
|
||||
],
|
||||
);
|
||||
|
||||
if check.is_identity() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SigmaProofVerificationError::AlgebraicRelation.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN] {
|
||||
let mut buf = [0_u8; GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN];
|
||||
let mut chunks = buf.chunks_mut(UNIT_LEN);
|
||||
chunks.next().unwrap().copy_from_slice(self.Y_0.as_bytes());
|
||||
chunks.next().unwrap().copy_from_slice(self.Y_1.as_bytes());
|
||||
chunks.next().unwrap().copy_from_slice(self.Y_2.as_bytes());
|
||||
chunks.next().unwrap().copy_from_slice(self.Y_3.as_bytes());
|
||||
chunks.next().unwrap().copy_from_slice(self.z_r.as_bytes());
|
||||
chunks.next().unwrap().copy_from_slice(self.z_x.as_bytes());
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidityProofVerificationError> {
|
||||
let mut chunks = bytes.chunks(UNIT_LEN);
|
||||
let Y_0 = ristretto_point_from_optional_slice(chunks.next())?;
|
||||
let Y_1 = ristretto_point_from_optional_slice(chunks.next())?;
|
||||
let Y_2 = ristretto_point_from_optional_slice(chunks.next())?;
|
||||
let Y_3 = ristretto_point_from_optional_slice(chunks.next())?;
|
||||
let z_r = canonical_scalar_from_optional_slice(chunks.next())?;
|
||||
let z_x = canonical_scalar_from_optional_slice(chunks.next())?;
|
||||
|
||||
Ok(GroupedCiphertext3HandlesValidityProof {
|
||||
Y_0,
|
||||
Y_1,
|
||||
Y_2,
|
||||
Y_3,
|
||||
z_r,
|
||||
z_x,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use {
|
||||
super::*,
|
||||
crate::encryption::{elgamal::ElGamalKeypair, pedersen::Pedersen},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_grouped_ciphertext_3_handles_validity_proof_correctness() {
|
||||
let source_keypair = ElGamalKeypair::new_rand();
|
||||
let source_pubkey = source_keypair.pubkey();
|
||||
|
||||
let destination_keypair = ElGamalKeypair::new_rand();
|
||||
let destination_pubkey = destination_keypair.pubkey();
|
||||
|
||||
let auditor_keypair = ElGamalKeypair::new_rand();
|
||||
let auditor_pubkey = auditor_keypair.pubkey();
|
||||
|
||||
let amount: u64 = 55;
|
||||
let (commitment, opening) = Pedersen::new(amount);
|
||||
|
||||
let source_handle = source_pubkey.decrypt_handle(&opening);
|
||||
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 = GroupedCiphertext3HandlesValidityProof::new(
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
amount,
|
||||
&opening,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
assert!(proof
|
||||
.verify(
|
||||
&commitment,
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
&source_handle,
|
||||
&destination_handle,
|
||||
&auditor_handle,
|
||||
&mut verifier_transcript,
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_grouped_ciphertext_3_handles_validity_proof_edge_cases() {
|
||||
// if source or destination public key zeroed, then the proof should always reject
|
||||
let source_pubkey = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap();
|
||||
let destination_pubkey = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap();
|
||||
|
||||
let auditor_keypair = ElGamalKeypair::new_rand();
|
||||
let auditor_pubkey = auditor_keypair.pubkey();
|
||||
|
||||
let amount: u64 = 55;
|
||||
let (commitment, opening) = Pedersen::new(amount);
|
||||
|
||||
let source_handle = destination_pubkey.decrypt_handle(&opening);
|
||||
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 = GroupedCiphertext3HandlesValidityProof::new(
|
||||
&source_pubkey,
|
||||
&destination_pubkey,
|
||||
auditor_pubkey,
|
||||
amount,
|
||||
&opening,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
assert!(proof
|
||||
.verify(
|
||||
&commitment,
|
||||
&source_pubkey,
|
||||
&destination_pubkey,
|
||||
auditor_pubkey,
|
||||
&source_handle,
|
||||
&destination_handle,
|
||||
&auditor_handle,
|
||||
&mut verifier_transcript,
|
||||
)
|
||||
.is_err());
|
||||
|
||||
// all zeroed ciphertext should still be valid
|
||||
let source_keypair = ElGamalKeypair::new_rand();
|
||||
let source_pubkey = source_keypair.pubkey();
|
||||
|
||||
let destination_keypair = ElGamalKeypair::new_rand();
|
||||
let destination_pubkey = destination_keypair.pubkey();
|
||||
|
||||
let auditor_keypair = ElGamalKeypair::new_rand();
|
||||
let auditor_pubkey = auditor_keypair.pubkey();
|
||||
|
||||
let amount: u64 = 0;
|
||||
let commitment = PedersenCommitment::from_bytes(&[0u8; 32]).unwrap();
|
||||
let opening = PedersenOpening::from_bytes(&[0u8; 32]).unwrap();
|
||||
|
||||
let source_handle = source_pubkey.decrypt_handle(&opening);
|
||||
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 = GroupedCiphertext3HandlesValidityProof::new(
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
amount,
|
||||
&opening,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
assert!(proof
|
||||
.verify(
|
||||
&commitment,
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
&source_handle,
|
||||
&destination_handle,
|
||||
&auditor_handle,
|
||||
&mut verifier_transcript,
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// decryption handles can be zero as long as the Pedersen commitment is valid
|
||||
let source_keypair = ElGamalKeypair::new_rand();
|
||||
let source_pubkey = source_keypair.pubkey();
|
||||
|
||||
let destination_keypair = ElGamalKeypair::new_rand();
|
||||
let destination_pubkey = destination_keypair.pubkey();
|
||||
|
||||
let auditor_keypair = ElGamalKeypair::new_rand();
|
||||
let auditor_pubkey = auditor_keypair.pubkey();
|
||||
|
||||
let amount: u64 = 55;
|
||||
let zeroed_opening = PedersenOpening::default();
|
||||
|
||||
let commitment = Pedersen::with(amount, &zeroed_opening);
|
||||
|
||||
let source_handle = source_pubkey.decrypt_handle(&zeroed_opening);
|
||||
let destination_handle = destination_pubkey.decrypt_handle(&zeroed_opening);
|
||||
let auditor_handle = auditor_pubkey.decrypt_handle(&zeroed_opening);
|
||||
|
||||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = GroupedCiphertext3HandlesValidityProof::new(
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
amount,
|
||||
&opening,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
assert!(proof
|
||||
.verify(
|
||||
&commitment,
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
auditor_pubkey,
|
||||
&source_handle,
|
||||
&destination_handle,
|
||||
&auditor_handle,
|
||||
&mut verifier_transcript,
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
mod handles_2;
|
||||
mod handles_3;
|
||||
|
||||
pub use {
|
||||
handles_2::GroupedCiphertext2HandlesValidityProof,
|
||||
handles_3::GroupedCiphertext3HandlesValidityProof,
|
||||
};
|
|
@ -23,7 +23,7 @@ pub use {
|
|||
sigma_proofs::{
|
||||
BatchedGroupedCiphertext2HandlesValidityProof, CiphertextCiphertextEqualityProof,
|
||||
CiphertextCommitmentEqualityProof, FeeSigmaProof, GroupedCiphertext2HandlesValidityProof,
|
||||
PubkeyValidityProof, ZeroBalanceProof,
|
||||
GroupedCiphertext3HandlesValidityProof, PubkeyValidityProof, ZeroBalanceProof,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
#[cfg(not(target_os = "solana"))]
|
||||
use crate::sigma_proofs::{
|
||||
batched_grouped_ciphertext_validity_proof::BatchedGroupedCiphertext2HandlesValidityProof as DecodedBatchedGroupedCiphertext2HandlesValidityProof,
|
||||
batched_grouped_ciphertext_validity_proof::BatchedGroupedCiphertext3HandlesValidityProof as DecodedBatchedGroupedCiphertext3HandlesValidityProof,
|
||||
ciphertext_ciphertext_equality_proof::CiphertextCiphertextEqualityProof as DecodedCiphertextCiphertextEqualityProof,
|
||||
ciphertext_commitment_equality_proof::CiphertextCommitmentEqualityProof as DecodedCiphertextCommitmentEqualityProof,
|
||||
errors::*, fee_proof::FeeSigmaProof as DecodedFeeSigmaProof,
|
||||
grouped_ciphertext_validity_proof::GroupedCiphertext2HandlesValidityProof as DecodedGroupedCiphertext2HandlesValidityProof,
|
||||
grouped_ciphertext_validity_proof::GroupedCiphertext3HandlesValidityProof as DecodedGroupedCiphertext3HandlesValidityProof,
|
||||
pubkey_proof::PubkeyValidityProof as DecodedPubkeyValidityProof,
|
||||
zero_balance_proof::ZeroBalanceProof as DecodedZeroBalanceProof,
|
||||
};
|
||||
|
@ -21,9 +23,15 @@ const CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN: usize = 224;
|
|||
/// Byte length of a grouped ciphertext for 2 handles validity proof
|
||||
const GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN: usize = 160;
|
||||
|
||||
/// Byte length of a grouped ciphertext for 3 handles validity proof
|
||||
const GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN: usize = 192;
|
||||
|
||||
/// Byte length of a batched grouped ciphertext for 2 handles validity proof
|
||||
const BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN: usize = 160;
|
||||
|
||||
/// Byte length of a batched grouped ciphertext for 3 handles validity proof
|
||||
const BATCHED_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN: usize = 192;
|
||||
|
||||
/// Byte length of a zero-balance proof
|
||||
const ZERO_BALANCE_PROOF_LEN: usize = 96;
|
||||
|
||||
|
@ -102,6 +110,33 @@ impl TryFrom<GroupedCiphertext2HandlesValidityProof>
|
|||
}
|
||||
}
|
||||
|
||||
/// The `GroupedCiphertext3HandlesValidityProof` type as a `Pod`.
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct GroupedCiphertext3HandlesValidityProof(
|
||||
pub [u8; GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN],
|
||||
);
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl From<DecodedGroupedCiphertext3HandlesValidityProof>
|
||||
for GroupedCiphertext3HandlesValidityProof
|
||||
{
|
||||
fn from(decoded_proof: DecodedGroupedCiphertext3HandlesValidityProof) -> Self {
|
||||
Self(decoded_proof.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl TryFrom<GroupedCiphertext3HandlesValidityProof>
|
||||
for DecodedGroupedCiphertext3HandlesValidityProof
|
||||
{
|
||||
type Error = ValidityProofVerificationError;
|
||||
|
||||
fn try_from(pod_proof: GroupedCiphertext3HandlesValidityProof) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod_proof.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `BatchedGroupedCiphertext2HandlesValidityProof` type as a `Pod`.
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
|
@ -131,6 +166,35 @@ impl TryFrom<BatchedGroupedCiphertext2HandlesValidityProof>
|
|||
}
|
||||
}
|
||||
|
||||
/// The `BatchedGroupedCiphertext3HandlesValidityProof` type as a `Pod`.
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct BatchedGroupedCiphertext3HandlesValidityProof(
|
||||
pub [u8; BATCHED_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN],
|
||||
);
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl From<DecodedBatchedGroupedCiphertext3HandlesValidityProof>
|
||||
for BatchedGroupedCiphertext3HandlesValidityProof
|
||||
{
|
||||
fn from(decoded_proof: DecodedBatchedGroupedCiphertext3HandlesValidityProof) -> Self {
|
||||
Self(decoded_proof.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl TryFrom<BatchedGroupedCiphertext3HandlesValidityProof>
|
||||
for DecodedBatchedGroupedCiphertext3HandlesValidityProof
|
||||
{
|
||||
type Error = ValidityProofVerificationError;
|
||||
|
||||
fn try_from(
|
||||
pod_proof: BatchedGroupedCiphertext3HandlesValidityProof,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod_proof.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `ZeroBalanceProof` type as a `Pod`.
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
|
@ -206,8 +270,14 @@ unsafe impl Pod for CiphertextCiphertextEqualityProof {}
|
|||
unsafe impl Zeroable for GroupedCiphertext2HandlesValidityProof {}
|
||||
unsafe impl Pod for GroupedCiphertext2HandlesValidityProof {}
|
||||
|
||||
unsafe impl Zeroable for GroupedCiphertext3HandlesValidityProof {}
|
||||
unsafe impl Pod for GroupedCiphertext3HandlesValidityProof {}
|
||||
|
||||
unsafe impl Zeroable for BatchedGroupedCiphertext2HandlesValidityProof {}
|
||||
unsafe impl Pod for BatchedGroupedCiphertext2HandlesValidityProof {}
|
||||
|
||||
unsafe impl Zeroable for BatchedGroupedCiphertext3HandlesValidityProof {}
|
||||
unsafe impl Pod for BatchedGroupedCiphertext3HandlesValidityProof {}
|
||||
|
||||
unsafe impl Zeroable for ZeroBalanceProof {}
|
||||
unsafe impl Pod for ZeroBalanceProof {}
|
||||
|
|
Loading…
Reference in New Issue