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 equality, pubkey validity, and percentage-with-cap instruction data #1467

Merged
merged 5 commits into from
May 23, 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
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
Loading