Skip to content

Commit

Permalink
[zk-sdk] Add equality, pubkey validity, and percentage-with-cap instr…
Browse files Browse the repository at this point in the history
…uction data (solana-labs#1467)

* add proof data from zk-token-sdk verbatim

* clean up ciphertext-ciphertext equality proof data

* clean up ciphertext-commitment equality proof data

* clean up pubkey validity proof data

* clean up percentage-with-cap proof data
  • Loading branch information
samkim-crypto authored May 23, 2024
1 parent edd2fff commit 9dd01f5
Show file tree
Hide file tree
Showing 8 changed files with 806 additions and 14 deletions.
26 changes: 24 additions & 2 deletions zk-sdk/src/elgamal_program/errors.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#[cfg(not(target_os = "solana"))]
use crate::range_proof::errors::RangeProofGenerationError;
use {
crate::{
errors::ElGamalError,
range_proof::errors::{RangeProofGenerationError, RangeProofVerificationError},
errors::ElGamalError, range_proof::errors::RangeProofVerificationError,
sigma_proofs::errors::*,
},
thiserror::Error,
Expand Down Expand Up @@ -41,10 +42,31 @@ pub enum ProofVerificationError {
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SigmaProofType {
ZeroCiphertext,
Equality,
PubkeyValidity,
PercentageWithCap,
}

impl From<ZeroCiphertextProofVerificationError> for ProofVerificationError {
fn from(err: ZeroCiphertextProofVerificationError) -> Self {
Self::SigmaProof(SigmaProofType::ZeroCiphertext, err.0)
}
}

impl From<EqualityProofVerificationError> for ProofVerificationError {
fn from(err: EqualityProofVerificationError) -> Self {
Self::SigmaProof(SigmaProofType::Equality, err.0)
}
}

impl From<PubkeyValidityProofVerificationError> for ProofVerificationError {
fn from(err: PubkeyValidityProofVerificationError) -> Self {
Self::SigmaProof(SigmaProofType::PubkeyValidity, err.0)
}
}

impl From<PercentageWithCapProofVerificationError> for ProofVerificationError {
fn from(err: PercentageWithCapProofVerificationError) -> Self {
Self::SigmaProof(SigmaProofType::PercentageWithCap, err.0)
}
}
68 changes: 68 additions & 0 deletions zk-sdk/src/elgamal_program/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,74 @@ pub enum ProofInstruction {
/// ii. `u32` byte offset if proof is provided as an account
///
VerifyZeroCiphertext,

/// Verify a ciphertext-ciphertext equality proof.
///
/// A ciphertext-ciphertext equality proof certifies that two ElGamal ciphertexts encrypt the
/// same message.
///
/// Accounts expected by this instruction:
///
/// 0. `[]` (Optional) Account to read the proof from
/// 1. `[writable]` (Optional) The proof context account
/// 2. `[]` (Optional) The proof context account owner
///
/// The instruction expects either:
/// i. `CiphertextCiphertextEqualityProofData` if proof is provided as instruction data
/// ii. `u32` byte offset if proof is provided as an account
///
VerifyCiphertextCiphertextEquality,

/// Verify a ciphertext-commitment equality proof.
///
/// A ciphertext-commitment equality proof certifies that an ElGamal ciphertext and a Pedersen
/// commitment encrypt/encode the same message.
///
/// Accounts expected by this instruction:
///
/// 0. `[]` (Optional) Account to read the proof from
/// 1. `[writable]` (Optional) The proof context account
/// 2. `[]` (Optional) The proof context account owner
///
/// The instruction expects either:
/// i. `CiphertextCommitmentEqualityProofData` if proof is provided as instruction data
/// ii. `u32` byte offset if proof is provided as an account
///
VerifyCiphertextCommitmentEquality,

/// Verify a public key validity zero-knowledge proof.
///
/// A public key validity proof certifies that an ElGamal public key is well-formed and the
/// prover knows the corresponding secret key.
///
/// Accounts expected by this instruction:
///
/// 0. `[]` (Optional) Account to read the proof from
/// 1. `[writable]` (Optional) The proof context account
/// 2. `[]` (Optional) The proof context account owner
///
/// The instruction expects either:
/// i. `PubkeyValidityData` if proof is provided as instruction data
/// ii. `u32` byte offset if proof is provided as an account
///
VerifyPubkeyValidity,

/// Verify a percentage-with-cap proof.
///
/// A percentage-with-cap proof certifies that a tuple of Pedersen commitments satisfy a
/// percentage relation.
///
/// Accounts expected by this instruction:
///
/// 0. `[]` (Optional) Account to read the proof from
/// 1. `[writable]` (Optional) The proof context account
/// 2. `[]` (Optional) The proof context account owner
///
/// The instruction expects either:
/// i. `PercentageWithCapProofData` if proof is provided as instruction data
/// ii. `u32` byte offset if proof is provided as an account
///
VerifyPercentageWithCap,
}

/// Pubkeys associated with a context state account to be used as parameters to functions.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
//! The ciphertext-ciphertext equality proof instruction.
//!
//! A ciphertext-ciphertext equality proof is defined with respect to two twisted ElGamal
//! ciphertexts. The proof certifies that the two ciphertexts encrypt the same message. To generate
//! the proof, a prover must provide the decryption key for the first ciphertext and the randomness
//! used to generate the second ciphertext.
//!
//! The first ciphertext associated with the proof is referred to as the "source" ciphertext. The
//! second ciphertext associated with the proof is referred to as the "destination" ciphertext.
#[cfg(not(target_os = "solana"))]
use {
crate::{
elgamal_program::errors::{ProofGenerationError, ProofVerificationError},
encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::PedersenOpening,
},
sigma_proofs::ciphertext_ciphertext_equality::CiphertextCiphertextEqualityProof,
},
bytemuck::bytes_of,
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
elgamal_program::proof_data::{ProofType, ZkProofData},
encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
sigma_proofs::pod::PodCiphertextCiphertextEqualityProof,
},
bytemuck::{Pod, Zeroable},
};

/// The instruction data that is needed for the
/// `ProofInstruction::VerifyCiphertextCiphertextEquality` instruction.
///
/// It includes the cryptographic proof as well as the context data information needed to verify
/// the proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CiphertextCiphertextEqualityProofData {
pub context: CiphertextCiphertextEqualityProofContext,

pub proof: PodCiphertextCiphertextEqualityProof,
}

/// The context data needed to verify a ciphertext-ciphertext equality proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CiphertextCiphertextEqualityProofContext {
pub source_pubkey: PodElGamalPubkey, // 32 bytes

pub destination_pubkey: PodElGamalPubkey, // 32 bytes

pub source_ciphertext: PodElGamalCiphertext, // 64 bytes

pub destination_ciphertext: PodElGamalCiphertext, // 64 bytes
}

#[cfg(not(target_os = "solana"))]
impl CiphertextCiphertextEqualityProofData {
pub fn new(
source_keypair: &ElGamalKeypair,
destination_pubkey: &ElGamalPubkey,
source_ciphertext: &ElGamalCiphertext,
destination_ciphertext: &ElGamalCiphertext,
destination_opening: &PedersenOpening,
amount: u64,
) -> Result<Self, ProofGenerationError> {
let pod_source_pubkey = PodElGamalPubkey(source_keypair.pubkey().into());
let pod_destination_pubkey = PodElGamalPubkey(destination_pubkey.into());
let pod_source_ciphertext = PodElGamalCiphertext(source_ciphertext.to_bytes());
let pod_destination_ciphertext = PodElGamalCiphertext(destination_ciphertext.to_bytes());

let context = CiphertextCiphertextEqualityProofContext {
source_pubkey: pod_source_pubkey,
destination_pubkey: pod_destination_pubkey,
source_ciphertext: pod_source_ciphertext,
destination_ciphertext: pod_destination_ciphertext,
};

let mut transcript = context.new_transcript();

let proof = CiphertextCiphertextEqualityProof::new(
source_keypair,
destination_pubkey,
source_ciphertext,
destination_opening,
amount,
&mut transcript,
)
.into();

Ok(Self { context, proof })
}
}

impl ZkProofData<CiphertextCiphertextEqualityProofContext>
for CiphertextCiphertextEqualityProofData
{
const PROOF_TYPE: ProofType = ProofType::CiphertextCiphertextEquality;

fn context_data(&self) -> &CiphertextCiphertextEqualityProofContext {
&self.context
}

#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofVerificationError> {
let mut transcript = self.context.new_transcript();

let source_pubkey = self.context.source_pubkey.try_into()?;
let destination_pubkey = self.context.destination_pubkey.try_into()?;
let source_ciphertext = self.context.source_ciphertext.try_into()?;
let destination_ciphertext = self.context.destination_ciphertext.try_into()?;
let proof: CiphertextCiphertextEqualityProof = self.proof.try_into()?;

proof
.verify(
&source_pubkey,
&destination_pubkey,
&source_ciphertext,
&destination_ciphertext,
&mut transcript,
)
.map_err(|e| e.into())
}
}

#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl CiphertextCiphertextEqualityProofContext {
fn new_transcript(&self) -> Transcript {
let mut transcript = Transcript::new(b"ciphertext-ciphertext-equality-instruction");

transcript.append_message(b"source-pubkey", bytes_of(&self.source_pubkey));
transcript.append_message(b"destination-pubkey", bytes_of(&self.destination_pubkey));
transcript.append_message(b"source-ciphertext", bytes_of(&self.source_ciphertext));
transcript.append_message(
b"destination-ciphertext",
bytes_of(&self.destination_ciphertext),
);

transcript
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_ciphertext_ciphertext_instruction_correctness() {
let source_keypair = ElGamalKeypair::new_rand();
let destination_keypair = ElGamalKeypair::new_rand();

let amount: u64 = 0;
let source_ciphertext = source_keypair.pubkey().encrypt(amount);

let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.pubkey()
.encrypt_with(amount, &destination_opening);

let proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
destination_keypair.pubkey(),
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();

assert!(proof_data.verify_proof().is_ok());

let amount: u64 = 55;
let source_ciphertext = source_keypair.pubkey().encrypt(amount);

let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.pubkey()
.encrypt_with(amount, &destination_opening);

let proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
destination_keypair.pubkey(),
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();

assert!(proof_data.verify_proof().is_ok());

let amount = u64::MAX;
let source_ciphertext = source_keypair.pubkey().encrypt(amount);

let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.pubkey()
.encrypt_with(amount, &destination_opening);

let proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
destination_keypair.pubkey(),
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();

assert!(proof_data.verify_proof().is_ok());
}
}
Loading

0 comments on commit 9dd01f5

Please sign in to comment.