Skip to content

Commit

Permalink
[zk-sdk] Add sigma_proofs and transcript modules (#1065)
Browse files Browse the repository at this point in the history
* add `sigma_proofs` and `transcript` modules

* remove `_proof` suffix from sigma proof module names

* remove the `sigma_proofs` and `transcript` modules from sbf target

* allow `dead_code` and `unused_imports` until the `instruction` module is added
  • Loading branch information
samkim-crypto authored Apr 26, 2024
1 parent e4ec48f commit 1bd7406
Show file tree
Hide file tree
Showing 18 changed files with 3,343 additions and 1 deletion.
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

0 comments on commit 1bd7406

Please sign in to comment.