From 9dd01f55182ff75a9c21bf89958ba5b37c8fe937 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 23 May 2024 21:36:04 +0900 Subject: [PATCH] [zk-sdk] Add equality, pubkey validity, and percentage-with-cap instruction data (#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 --- zk-sdk/src/elgamal_program/errors.rs | 26 +- zk-sdk/src/elgamal_program/instruction.rs | 68 ++++++ .../ciphertext_ciphertext_equality.rs | 216 +++++++++++++++++ .../ciphertext_commitment_equality.rs | 149 ++++++++++++ zk-sdk/src/elgamal_program/proof_data/mod.rs | 11 +- .../proof_data/percentage_with_cap.rs | 227 ++++++++++++++++++ .../src/elgamal_program/proof_data/pubkey.rs | 102 ++++++++ .../proof_data/zero_ciphertext.rs | 21 +- 8 files changed, 806 insertions(+), 14 deletions(-) create mode 100644 zk-sdk/src/elgamal_program/proof_data/ciphertext_ciphertext_equality.rs create mode 100644 zk-sdk/src/elgamal_program/proof_data/ciphertext_commitment_equality.rs create mode 100644 zk-sdk/src/elgamal_program/proof_data/percentage_with_cap.rs create mode 100644 zk-sdk/src/elgamal_program/proof_data/pubkey.rs diff --git a/zk-sdk/src/elgamal_program/errors.rs b/zk-sdk/src/elgamal_program/errors.rs index 10b83edbb6fa83..0c5ec47bcf2c66 100644 --- a/zk-sdk/src/elgamal_program/errors.rs +++ b/zk-sdk/src/elgamal_program/errors.rs @@ -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, @@ -41,6 +42,9 @@ pub enum ProofVerificationError { #[derive(Clone, Debug, Eq, PartialEq)] pub enum SigmaProofType { ZeroCiphertext, + Equality, + PubkeyValidity, + PercentageWithCap, } impl From for ProofVerificationError { @@ -48,3 +52,21 @@ impl From for ProofVerificationError { Self::SigmaProof(SigmaProofType::ZeroCiphertext, err.0) } } + +impl From for ProofVerificationError { + fn from(err: EqualityProofVerificationError) -> Self { + Self::SigmaProof(SigmaProofType::Equality, err.0) + } +} + +impl From for ProofVerificationError { + fn from(err: PubkeyValidityProofVerificationError) -> Self { + Self::SigmaProof(SigmaProofType::PubkeyValidity, err.0) + } +} + +impl From for ProofVerificationError { + fn from(err: PercentageWithCapProofVerificationError) -> Self { + Self::SigmaProof(SigmaProofType::PercentageWithCap, err.0) + } +} diff --git a/zk-sdk/src/elgamal_program/instruction.rs b/zk-sdk/src/elgamal_program/instruction.rs index fd3f8328bef4bf..e5c3ff607c7eb7 100644 --- a/zk-sdk/src/elgamal_program/instruction.rs +++ b/zk-sdk/src/elgamal_program/instruction.rs @@ -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. diff --git a/zk-sdk/src/elgamal_program/proof_data/ciphertext_ciphertext_equality.rs b/zk-sdk/src/elgamal_program/proof_data/ciphertext_ciphertext_equality.rs new file mode 100644 index 00000000000000..a00b9231edabd5 --- /dev/null +++ b/zk-sdk/src/elgamal_program/proof_data/ciphertext_ciphertext_equality.rs @@ -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 { + 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 + 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()); + } +} diff --git a/zk-sdk/src/elgamal_program/proof_data/ciphertext_commitment_equality.rs b/zk-sdk/src/elgamal_program/proof_data/ciphertext_commitment_equality.rs new file mode 100644 index 00000000000000..7e598b341e4cd4 --- /dev/null +++ b/zk-sdk/src/elgamal_program/proof_data/ciphertext_commitment_equality.rs @@ -0,0 +1,149 @@ +//! The ciphertext-commitment equality proof instruction. +//! +//! A ciphertext-commitment equality proof is defined with respect to a twisted ElGamal ciphertext +//! and a Pedersen commitment. The proof certifies that a given ciphertext and a commitment pair +//! encrypts/encodes the same message. To generate the proof, a prover must provide the decryption +//! key for the first ciphertext and the Pedersen opening for the commitment. + +#[cfg(not(target_os = "solana"))] +use { + crate::{ + elgamal_program::errors::{ProofGenerationError, ProofVerificationError}, + encryption::{ + elgamal::{ElGamalCiphertext, ElGamalKeypair}, + pedersen::{PedersenCommitment, PedersenOpening}, + }, + sigma_proofs::ciphertext_commitment_equality::CiphertextCommitmentEqualityProof, + }, + bytemuck::bytes_of, + merlin::Transcript, + std::convert::TryInto, +}; +use { + crate::{ + elgamal_program::proof_data::{ProofType, ZkProofData}, + encryption::pod::{ + elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, + pedersen::PodPedersenCommitment, + }, + sigma_proofs::pod::PodCiphertextCommitmentEqualityProof, + }, + bytemuck::{Pod, Zeroable}, +}; +/// The instruction data that is needed for the +/// `ProofInstruction::VerifyCiphertextCommitmentEquality` 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 CiphertextCommitmentEqualityProofData { + pub context: CiphertextCommitmentEqualityProofContext, + pub proof: PodCiphertextCommitmentEqualityProof, +} + +/// The context data needed to verify a ciphertext-commitment equality proof. +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct CiphertextCommitmentEqualityProofContext { + /// The ElGamal pubkey + pub pubkey: PodElGamalPubkey, // 32 bytes + + /// The ciphertext encrypted under the ElGamal pubkey + pub ciphertext: PodElGamalCiphertext, // 64 bytes + + /// The Pedersen commitment + pub commitment: PodPedersenCommitment, // 32 bytes +} + +#[cfg(not(target_os = "solana"))] +impl CiphertextCommitmentEqualityProofData { + pub fn new( + keypair: &ElGamalKeypair, + ciphertext: &ElGamalCiphertext, + commitment: &PedersenCommitment, + opening: &PedersenOpening, + amount: u64, + ) -> Result { + let context = CiphertextCommitmentEqualityProofContext { + pubkey: PodElGamalPubkey(keypair.pubkey().into()), + ciphertext: PodElGamalCiphertext(ciphertext.to_bytes()), + commitment: PodPedersenCommitment(commitment.to_bytes()), + }; + let mut transcript = context.new_transcript(); + let proof = CiphertextCommitmentEqualityProof::new( + keypair, + ciphertext, + opening, + amount, + &mut transcript, + ); + Ok(CiphertextCommitmentEqualityProofData { + context, + proof: proof.into(), + }) + } +} + +impl ZkProofData + for CiphertextCommitmentEqualityProofData +{ + const PROOF_TYPE: ProofType = ProofType::CiphertextCommitmentEquality; + + fn context_data(&self) -> &CiphertextCommitmentEqualityProofContext { + &self.context + } + + #[cfg(not(target_os = "solana"))] + fn verify_proof(&self) -> Result<(), ProofVerificationError> { + let mut transcript = self.context.new_transcript(); + + let pubkey = self.context.pubkey.try_into()?; + let ciphertext = self.context.ciphertext.try_into()?; + let commitment = self.context.commitment.try_into()?; + let proof: CiphertextCommitmentEqualityProof = self.proof.try_into()?; + + proof + .verify(&pubkey, &ciphertext, &commitment, &mut transcript) + .map_err(|e| e.into()) + } +} + +#[allow(non_snake_case)] +#[cfg(not(target_os = "solana"))] +impl CiphertextCommitmentEqualityProofContext { + fn new_transcript(&self) -> Transcript { + let mut transcript = Transcript::new(b"ciphertext-commitment-equality-instruction"); + transcript.append_message(b"pubkey", bytes_of(&self.pubkey)); + transcript.append_message(b"ciphertext", bytes_of(&self.ciphertext)); + transcript.append_message(b"commitment", bytes_of(&self.commitment)); + transcript + } +} + +#[cfg(test)] +mod test { + use { + super::*, + crate::encryption::{elgamal::ElGamalKeypair, pedersen::Pedersen}, + }; + + #[test] + fn test_ctxt_comm_equality_proof_correctness() { + let keypair = ElGamalKeypair::new_rand(); + let amount: u64 = 55; + let ciphertext = keypair.pubkey().encrypt(amount); + let (commitment, opening) = Pedersen::new(amount); + + let proof_data = CiphertextCommitmentEqualityProofData::new( + &keypair, + &ciphertext, + &commitment, + &opening, + amount, + ) + .unwrap(); + + assert!(proof_data.verify_proof().is_ok()); + } +} diff --git a/zk-sdk/src/elgamal_program/proof_data/mod.rs b/zk-sdk/src/elgamal_program/proof_data/mod.rs index ea8bfbac72adf3..d9f2016e0dece2 100644 --- a/zk-sdk/src/elgamal_program/proof_data/mod.rs +++ b/zk-sdk/src/elgamal_program/proof_data/mod.rs @@ -1,11 +1,16 @@ +#[cfg(not(target_os = "solana"))] +use crate::elgamal_program::errors::ProofVerificationError; use { - crate::elgamal_program::errors::ProofVerificationError, bytemuck::Pod, num_derive::{FromPrimitive, ToPrimitive}, }; +pub mod ciphertext_ciphertext_equality; +pub mod ciphertext_commitment_equality; pub mod errors; +pub mod percentage_with_cap; pub mod pod; +pub mod pubkey; pub mod zero_ciphertext; #[derive(Clone, Copy, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)] @@ -14,6 +19,10 @@ pub enum ProofType { /// Empty proof type used to distinguish if a proof context account is initialized Uninitialized, ZeroCiphertext, + CiphertextCiphertextEquality, + CiphertextCommitmentEquality, + PubkeyValidity, + PercentageWithCap, } pub trait ZkProofData { diff --git a/zk-sdk/src/elgamal_program/proof_data/percentage_with_cap.rs b/zk-sdk/src/elgamal_program/proof_data/percentage_with_cap.rs new file mode 100644 index 00000000000000..3ca93e51f6e908 --- /dev/null +++ b/zk-sdk/src/elgamal_program/proof_data/percentage_with_cap.rs @@ -0,0 +1,227 @@ +//! The percentage-with-cap proof instruction. +//! +//! The percentage-with-cap proof is defined with respect to three Pedersen commitments that +//! encodes values referred to as a `percentage`, `delta`, and `claimed` amounts. The proof +//! certifies that either +//! - the `percentage` amount is equal to a constant (referred to as the `max_value`) +//! - the `delta` and `claimed` amounts are equal + +#[cfg(not(target_os = "solana"))] +use { + crate::{ + elgamal_program::errors::{ProofGenerationError, ProofVerificationError}, + encryption::pedersen::{PedersenCommitment, PedersenOpening}, + sigma_proofs::percentage_with_cap::PercentageWithCapProof, + }, + bytemuck::bytes_of, + merlin::Transcript, + std::convert::TryInto, +}; +use { + crate::{ + elgamal_program::proof_data::{ProofType, ZkProofData}, + encryption::pod::pedersen::PodPedersenCommitment, + pod::PodU64, + sigma_proofs::pod::PodPercentageWithCapProof, + }, + bytemuck::{Pod, Zeroable}, +}; + +/// The instruction data that is needed for the `ProofInstruction::VerifyPercentageWithCap` +/// 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 PercentageWithCapProofData { + pub context: PercentageWithCapProofContext, + + pub proof: PodPercentageWithCapProof, +} + +/// The context data needed to verify a percentage-with-cap proof. +/// +/// We refer to [`ZK ElGamal proof`] for the formal details on how the percentage-with-cap proof is +/// computed. +/// +/// [`ZK ElGamal proof`]: https://docs.solanalabs.com/runtime/zk-token-proof +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct PercentageWithCapProofContext { + /// The Pedersen commitment to the percentage amount. + pub percentage_commitment: PodPedersenCommitment, + + /// The Pedersen commitment to the delta amount. + pub delta_commitment: PodPedersenCommitment, + + /// The Pedersen commitment to the claimed amount. + pub claimed_commitment: PodPedersenCommitment, + + /// The maximum cap bound. + pub max_value: PodU64, +} + +#[cfg(not(target_os = "solana"))] +impl PercentageWithCapProofData { + pub fn new( + percentage_commitment: &PedersenCommitment, + percentage_opening: &PedersenOpening, + percentage_amount: u64, + delta_commitment: &PedersenCommitment, + delta_opening: &PedersenOpening, + delta_amount: u64, + claimed_commitment: &PedersenCommitment, + claimed_opening: &PedersenOpening, + max_value: u64, + ) -> Result { + let pod_percentage_commitment = PodPedersenCommitment(percentage_commitment.to_bytes()); + let pod_delta_commitment = PodPedersenCommitment(delta_commitment.to_bytes()); + let pod_claimed_commitment = PodPedersenCommitment(claimed_commitment.to_bytes()); + let pod_max_value = max_value.into(); + + let context = PercentageWithCapProofContext { + percentage_commitment: pod_percentage_commitment, + delta_commitment: pod_delta_commitment, + claimed_commitment: pod_claimed_commitment, + max_value: pod_max_value, + }; + + let mut transcript = context.new_transcript(); + + let proof = PercentageWithCapProof::new( + percentage_commitment, + percentage_opening, + percentage_amount, + delta_commitment, + delta_opening, + delta_amount, + claimed_commitment, + claimed_opening, + max_value, + &mut transcript, + ) + .into(); + + Ok(Self { context, proof }) + } +} + +impl ZkProofData for PercentageWithCapProofData { + const PROOF_TYPE: ProofType = ProofType::PercentageWithCap; + + fn context_data(&self) -> &PercentageWithCapProofContext { + &self.context + } + + #[cfg(not(target_os = "solana"))] + fn verify_proof(&self) -> Result<(), ProofVerificationError> { + let mut transcript = self.context.new_transcript(); + + let percentage_commitment = self.context.percentage_commitment.try_into()?; + let delta_commitment = self.context.delta_commitment.try_into()?; + let claimed_commitment = self.context.claimed_commitment.try_into()?; + let max_value = self.context.max_value.into(); + let proof: PercentageWithCapProof = self.proof.try_into()?; + + proof + .verify( + &percentage_commitment, + &delta_commitment, + &claimed_commitment, + max_value, + &mut transcript, + ) + .map_err(|e| e.into()) + } +} + +#[cfg(not(target_os = "solana"))] +impl PercentageWithCapProofContext { + fn new_transcript(&self) -> Transcript { + let mut transcript = Transcript::new(b"percentage-with-cap-instruction"); + transcript.append_message( + b"percentage-commitment", + bytes_of(&self.percentage_commitment), + ); + transcript.append_message(b"delta-commitment", bytes_of(&self.delta_commitment)); + transcript.append_message(b"claimed-commitment", bytes_of(&self.claimed_commitment)); + transcript.append_u64(b"max-value", self.max_value.into()); + transcript + } +} + +#[cfg(test)] +mod test { + use {super::*, crate::encryption::pedersen::Pedersen, curve25519_dalek::scalar::Scalar}; + + #[test] + fn test_percentage_with_cap_instruction_correctness() { + // base amount is below max value + let base_amount: u64 = 1; + let max_value: u64 = 3; + + let percentage_rate: u16 = 400; + let percentage_amount: u64 = 1; + let delta_amount: u64 = 9600; + + let (base_commitment, base_opening) = Pedersen::new(base_amount); + let (percentage_commitment, percentage_opening) = Pedersen::new(percentage_amount); + + let scalar_rate = Scalar::from(percentage_rate); + let delta_commitment = + &percentage_commitment * Scalar::from(10_000_u64) - &base_commitment * &scalar_rate; + let delta_opening = + &percentage_opening * &Scalar::from(10_000_u64) - &base_opening * &scalar_rate; + + let (claimed_commitment, claimed_opening) = Pedersen::new(delta_amount); + + let proof_data = PercentageWithCapProofData::new( + &percentage_commitment, + &percentage_opening, + percentage_amount, + &delta_commitment, + &delta_opening, + delta_amount, + &claimed_commitment, + &claimed_opening, + max_value, + ) + .unwrap(); + + assert!(proof_data.verify_proof().is_ok()); + + // base amount is equal to max value + let base_amount: u64 = 55; + let max_value: u64 = 3; + + let percentage_rate: u16 = 555; + let percentage_amount: u64 = 4; + + let (transfer_commitment, transfer_opening) = Pedersen::new(base_amount); + let (percentage_commitment, percentage_opening) = Pedersen::new(max_value); + + let scalar_rate = Scalar::from(percentage_rate); + let delta_commitment = + &percentage_commitment * &Scalar::from(10000_u64) - &transfer_commitment * &scalar_rate; + let delta_opening = + &percentage_opening * &Scalar::from(10000_u64) - &transfer_opening * &scalar_rate; + + let (claimed_commitment, claimed_opening) = Pedersen::new(0_u64); + + let proof_data = PercentageWithCapProofData::new( + &percentage_commitment, + &percentage_opening, + percentage_amount, + &delta_commitment, + &delta_opening, + delta_amount, + &claimed_commitment, + &claimed_opening, + max_value, + ) + .unwrap(); + + assert!(proof_data.verify_proof().is_ok()); + } +} diff --git a/zk-sdk/src/elgamal_program/proof_data/pubkey.rs b/zk-sdk/src/elgamal_program/proof_data/pubkey.rs new file mode 100644 index 00000000000000..1d79dd242293a0 --- /dev/null +++ b/zk-sdk/src/elgamal_program/proof_data/pubkey.rs @@ -0,0 +1,102 @@ +//! The public-key validity proof instruction. +//! +//! A public-key validity proof system is defined with respect to an ElGamal public key. The proof +//! certifies that a given public key is a valid ElGamal public key (i.e. the prover knows a +//! corresponding secret key). To generate the proof, a prover must provide the secret key for the +//! public key. + +#[cfg(not(target_os = "solana"))] +use { + crate::{ + elgamal_program::errors::{ProofGenerationError, ProofVerificationError}, + encryption::elgamal::ElGamalKeypair, + sigma_proofs::pubkey::PubkeyValidityProof, + }, + bytemuck::bytes_of, + merlin::Transcript, + std::convert::TryInto, +}; +use { + crate::{ + elgamal_program::proof_data::{ProofType, ZkProofData}, + encryption::pod::elgamal::PodElGamalPubkey, + sigma_proofs::pod::PodPubkeyValidityProof, + }, + bytemuck::{Pod, Zeroable}, +}; + +/// The instruction data that is needed for the `ProofInstruction::VerifyPubkeyValidity` +/// 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 PubkeyValidityData { + /// The context data for the public key validity proof + pub context: PubkeyValidityProofContext, // 32 bytes + + /// Proof that the public key is well-formed + pub proof: PodPubkeyValidityProof, // 64 bytes +} + +/// The context data needed to verify a pubkey validity proof. +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct PubkeyValidityProofContext { + /// The public key to be proved + pub pubkey: PodElGamalPubkey, // 32 bytes +} + +#[cfg(not(target_os = "solana"))] +impl PubkeyValidityData { + pub fn new(keypair: &ElGamalKeypair) -> Result { + let pod_pubkey = PodElGamalPubkey(keypair.pubkey().into()); + + let context = PubkeyValidityProofContext { pubkey: pod_pubkey }; + + let mut transcript = context.new_transcript(); + let proof = PubkeyValidityProof::new(keypair, &mut transcript).into(); + + Ok(PubkeyValidityData { context, proof }) + } +} + +impl ZkProofData for PubkeyValidityData { + const PROOF_TYPE: ProofType = ProofType::PubkeyValidity; + + fn context_data(&self) -> &PubkeyValidityProofContext { + &self.context + } + + #[cfg(not(target_os = "solana"))] + fn verify_proof(&self) -> Result<(), ProofVerificationError> { + let mut transcript = self.context.new_transcript(); + let pubkey = self.context.pubkey.try_into()?; + let proof: PubkeyValidityProof = self.proof.try_into()?; + proof.verify(&pubkey, &mut transcript).map_err(|e| e.into()) + } +} + +#[allow(non_snake_case)] +#[cfg(not(target_os = "solana"))] +impl PubkeyValidityProofContext { + fn new_transcript(&self) -> Transcript { + let mut transcript = Transcript::new(b"pubkey-validity-instruction"); + transcript.append_message(b"pubkey", bytes_of(&self.pubkey)); + transcript + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_pubkey_validity_instruction_correctness() { + let keypair = ElGamalKeypair::new_rand(); + + let pubkey_validity_data = PubkeyValidityData::new(&keypair).unwrap(); + assert!(pubkey_validity_data.verify_proof().is_ok()); + } +} diff --git a/zk-sdk/src/elgamal_program/proof_data/zero_ciphertext.rs b/zk-sdk/src/elgamal_program/proof_data/zero_ciphertext.rs index c511c421b58eb4..71fbcff67d38ba 100644 --- a/zk-sdk/src/elgamal_program/proof_data/zero_ciphertext.rs +++ b/zk-sdk/src/elgamal_program/proof_data/zero_ciphertext.rs @@ -4,26 +4,25 @@ //! certifies that a given ciphertext encrypts the message 0 in the field (`Scalar::zero()`). To //! generate the proof, a prover must provide the decryption key for the ciphertext. -use { - crate::{ - elgamal_program::{ - errors::{ProofGenerationError, ProofVerificationError}, - proof_data::{ProofType, ZkProofData}, - }, - encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, - sigma_proofs::pod::PodZeroCiphertextProof, - }, - bytemuck::{bytes_of, Pod, Zeroable}, -}; #[cfg(not(target_os = "solana"))] use { crate::{ + elgamal_program::errors::{ProofGenerationError, ProofVerificationError}, encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair}, sigma_proofs::zero_ciphertext::ZeroCiphertextProof, }, + bytemuck::bytes_of, merlin::Transcript, std::convert::TryInto, }; +use { + crate::{ + elgamal_program::proof_data::{ProofType, ZkProofData}, + encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, + sigma_proofs::pod::PodZeroCiphertextProof, + }, + bytemuck::{Pod, Zeroable}, +}; /// The instruction data that is needed for the `ProofInstruction::ZeroCiphertext` instruction. ///