Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[zk-sdk] Add sigma_proofs and transcript modules #1065

Merged
merged 4 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zk-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ bincode = { workspace = true }
curve25519-dalek = { workspace = true, features = ["serde"] }
itertools = { workspace = true }
lazy_static = { workspace = true }
merlin = { workspace = true }
rand = { version = "0.7" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
Expand Down
6 changes: 6 additions & 0 deletions zk-sdk/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ pub enum ElGamalError {
#[error("failed to deserialize secret key")]
SecretKeyDeserialization,
}

#[derive(Error, Clone, Debug, Eq, PartialEq)]
pub enum TranscriptError {
#[error("point is the identity")]
ValidationError,
}
5 changes: 4 additions & 1 deletion zk-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@

#[cfg(not(target_os = "solana"))]
pub mod encryption;

pub mod errors;
#[cfg(not(target_os = "solana"))]
mod sigma_proofs;
#[cfg(not(target_os = "solana"))]
mod transcript;

/// Byte length of a compressed Ristretto point or scalar in Curve255519
const UNIT_LEN: usize = 32;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//! The ciphertext validity sigma proof system.
//!
//! The ciphertext validity proof is defined with respect to a Pedersen commitment and two
//! decryption handles. The proof certifies that a given Pedersen commitment can be decrypted using
//! ElGamal private keys that are associated with each of the two 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.

#[cfg(not(target_os = "solana"))]
use crate::encryption::{
elgamal::{DecryptHandle, ElGamalPubkey},
pedersen::{PedersenCommitment, PedersenOpening},
};
use {
crate::{
sigma_proofs::{
errors::ValidityProofVerificationError,
grouped_ciphertext_validity::GroupedCiphertext2HandlesValidityProof,
},
transcript::TranscriptProtocol,
},
curve25519_dalek::scalar::Scalar,
merlin::Transcript,
};

/// Batched grouped ciphertext validity proof with two handles.
///
/// A batched grouped ciphertext validity proof certifies the validity of two instances of a
/// standard ciphertext validity proof. An instance of a standard validity proof consists of one
/// ciphertext and two decryption handles: `(commitment, destination_handle, auditor_handle)`. An
/// instance of a batched ciphertext validity proof is a pair `(commitment_0,
/// destination_handle_0, auditor_handle_0)` and `(commitment_1, destination_handle_1,
/// auditor_handle_1)`. The proof certifies the analogous decryptable properties for each one of
/// these pairs of commitment and decryption handles.
#[allow(non_snake_case)]
#[derive(Clone)]
pub struct BatchedGroupedCiphertext2HandlesValidityProof(GroupedCiphertext2HandlesValidityProof);

#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl BatchedGroupedCiphertext2HandlesValidityProof {
/// Creates a batched grouped ciphertext validity proof.
///
/// The function simply batches the input openings and invokes the standard grouped ciphertext
/// validity proof constructor.
///
/// This function is randomized. It uses `OsRng` internally to generate random scalars.
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.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);

BatchedGroupedCiphertext2HandlesValidityProof(GroupedCiphertext2HandlesValidityProof::new(
(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.
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<(), 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 destination_batched_handle = destination_handle_lo + destination_handle_hi * t;
let auditor_batched_handle = auditor_handle_lo + auditor_handle_hi * t;

let BatchedGroupedCiphertext2HandlesValidityProof(validity_proof) = self;

validity_proof.verify(
&batched_commitment,
(destination_pubkey, auditor_pubkey),
(&destination_batched_handle, &auditor_batched_handle),
transcript,
)
}

pub fn to_bytes(&self) -> [u8; 160] {
self.0.to_bytes()
}

pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidityProofVerificationError> {
GroupedCiphertext2HandlesValidityProof::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 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 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 = BatchedGroupedCiphertext2HandlesValidityProof::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());
}
}
Original file line number Diff line number Diff line change
@@ -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::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());
}
}
Loading
Loading