diff --git a/programs/zk-token-proof-tests/tests/process_transaction.rs b/programs/zk-token-proof-tests/tests/process_transaction.rs index 555295e4e..ca94774b7 100644 --- a/programs/zk-token-proof-tests/tests/process_transaction.rs +++ b/programs/zk-token-proof-tests/tests/process_transaction.rs @@ -9,69 +9,79 @@ use { transaction::{Transaction, TransactionError}, }, solana_zk_token_sdk::{ - encryption::elgamal::ElGamalKeypair, instruction::*, zk_token_proof_instruction::*, - zk_token_proof_program, zk_token_proof_state::ProofContextState, + encryption::{elgamal::ElGamalKeypair, pedersen::PedersenOpening}, + instruction::*, + zk_token_proof_instruction::*, + zk_token_proof_program, + zk_token_proof_state::ProofContextState, }, std::mem::size_of, }; const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 6] = [ - ProofInstruction::VerifyCloseAccount, + ProofInstruction::VerifyZeroBalance, ProofInstruction::VerifyWithdraw, - ProofInstruction::VerifyWithdrawWithheldTokens, + ProofInstruction::VerifyCiphertextCiphertextEquality, ProofInstruction::VerifyTransfer, ProofInstruction::VerifyTransferWithFee, ProofInstruction::VerifyPubkeyValidity, ]; #[tokio::test] -async fn test_close_account() { +async fn test_zero_balance() { let elgamal_keypair = ElGamalKeypair::new_rand(); let zero_ciphertext = elgamal_keypair.public.encrypt(0_u64); - let success_proof_data = CloseAccountData::new(&elgamal_keypair, &zero_ciphertext).unwrap(); + let success_proof_data = ZeroBalanceProofData::new(&elgamal_keypair, &zero_ciphertext).unwrap(); let incorrect_keypair = ElGamalKeypair { public: ElGamalKeypair::new_rand().public, secret: ElGamalKeypair::new_rand().secret, }; - let fail_proof_data = CloseAccountData::new(&incorrect_keypair, &zero_ciphertext).unwrap(); + let fail_proof_data = ZeroBalanceProofData::new(&incorrect_keypair, &zero_ciphertext).unwrap(); test_verify_proof_without_context( - ProofInstruction::VerifyCloseAccount, + ProofInstruction::VerifyZeroBalance, &success_proof_data, &fail_proof_data, ) .await; test_verify_proof_with_context( - ProofInstruction::VerifyCloseAccount, - size_of::>(), + ProofInstruction::VerifyZeroBalance, + size_of::>(), &success_proof_data, &fail_proof_data, ) .await; test_close_context_state( - ProofInstruction::VerifyCloseAccount, - size_of::>(), + ProofInstruction::VerifyZeroBalance, + size_of::>(), &success_proof_data, ) .await; } #[tokio::test] -async fn test_withdraw_withheld_tokens() { - let elgamal_keypair = ElGamalKeypair::new_rand(); +async fn test_ciphertext_ciphertext_equality() { + let source_keypair = ElGamalKeypair::new_rand(); let destination_keypair = ElGamalKeypair::new_rand(); let amount: u64 = 0; - let withdraw_withheld_authority_ciphertext = elgamal_keypair.public.encrypt(amount); + let source_ciphertext = source_keypair.public.encrypt(amount); - let success_proof_data = WithdrawWithheldTokensData::new( - &elgamal_keypair, + let destination_opening = PedersenOpening::new_rand(); + let destination_ciphertext = destination_keypair + .public + .encrypt_with(amount, &destination_opening); + + let success_proof_data = CiphertextCiphertextEqualityProofData::new( + &source_keypair, &destination_keypair.public, - &withdraw_withheld_authority_ciphertext, + &source_ciphertext, + &destination_ciphertext, + &destination_opening, amount, ) .unwrap(); @@ -80,32 +90,34 @@ async fn test_withdraw_withheld_tokens() { public: ElGamalKeypair::new_rand().public, secret: ElGamalKeypair::new_rand().secret, }; - let fail_proof_data = WithdrawWithheldTokensData::new( + let fail_proof_data = CiphertextCiphertextEqualityProofData::new( &incorrect_keypair, &destination_keypair.public, - &withdraw_withheld_authority_ciphertext, + &source_ciphertext, + &destination_ciphertext, + &destination_opening, amount, ) .unwrap(); test_verify_proof_without_context( - ProofInstruction::VerifyWithdrawWithheldTokens, + ProofInstruction::VerifyCiphertextCiphertextEquality, &success_proof_data, &fail_proof_data, ) .await; test_verify_proof_with_context( - ProofInstruction::VerifyWithdrawWithheldTokens, - size_of::>(), + ProofInstruction::VerifyCiphertextCiphertextEquality, + size_of::>(), &success_proof_data, &fail_proof_data, ) .await; test_close_context_state( - ProofInstruction::VerifyWithdrawWithheldTokens, - size_of::>(), + ProofInstruction::VerifyCiphertextCiphertextEquality, + size_of::>(), &success_proof_data, ) .await; diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index 786113a0d..bbded2b94 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -135,14 +135,14 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| { ic_msg!(invoke_context, "CloseContextState"); process_close_proof_context(invoke_context) } - ProofInstruction::VerifyCloseAccount => { + ProofInstruction::VerifyZeroBalance => { if native_programs_consume_cu { invoke_context .consume_checked(6_012) .map_err(|_| InstructionError::ComputationalBudgetExceeded)?; } - ic_msg!(invoke_context, "VerifyCloseAccount"); - process_verify_proof::(invoke_context) + ic_msg!(invoke_context, "VerifyZeroBalance"); + process_verify_proof::(invoke_context) } ProofInstruction::VerifyWithdraw => { if native_programs_consume_cu { @@ -153,16 +153,17 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| { ic_msg!(invoke_context, "VerifyWithdraw"); process_verify_proof::(invoke_context) } - ProofInstruction::VerifyWithdrawWithheldTokens => { + ProofInstruction::VerifyCiphertextCiphertextEquality => { if native_programs_consume_cu { invoke_context .consume_checked(7_943) .map_err(|_| InstructionError::ComputationalBudgetExceeded)?; } - ic_msg!(invoke_context, "VerifyWithdrawWithheldTokens"); - process_verify_proof::( - invoke_context, - ) + ic_msg!(invoke_context, "VerifyCiphertextCiphertextEquality"); + process_verify_proof::< + CiphertextCiphertextEqualityProofData, + CiphertextCiphertextEqualityProofContext, + >(invoke_context) } ProofInstruction::VerifyTransfer => { if native_programs_consume_cu { diff --git a/zk-token-sdk/src/instruction/close_account.rs b/zk-token-sdk/src/instruction/close_account.rs deleted file mode 100644 index 561fb1a09..000000000 --- a/zk-token-sdk/src/instruction/close_account.rs +++ /dev/null @@ -1,153 +0,0 @@ -#[cfg(not(target_os = "solana"))] -use { - crate::{ - encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, - errors::ProofError, - sigma_proofs::zero_balance_proof::ZeroBalanceProof, - transcript::TranscriptProtocol, - }, - merlin::Transcript, - std::convert::TryInto, -}; -use { - crate::{ - instruction::{ProofType, ZkProofData}, - zk_token_elgamal::pod, - }, - bytemuck::{Pod, Zeroable}, -}; - -/// This struct includes the cryptographic proof *and* the account data information needed to verify -/// the proof -/// -/// - The pre-instruction should call CloseAccountData::verify_proof(&self) -/// - The actual program should check that `balance` is consistent with what is -/// currently stored in the confidential token account -/// -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -pub struct CloseAccountData { - /// The context data for the close account proof - pub context: CloseAccountProofContext, - - /// Proof that the source account available balance is zero - pub proof: CloseAccountProof, // 96 bytes -} - -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -pub struct CloseAccountProofContext { - /// The source account ElGamal pubkey - pub pubkey: pod::ElGamalPubkey, // 32 bytes - - /// The source account available balance in encrypted form - pub ciphertext: pod::ElGamalCiphertext, // 64 bytes -} - -#[cfg(not(target_os = "solana"))] -impl CloseAccountData { - pub fn new( - keypair: &ElGamalKeypair, - ciphertext: &ElGamalCiphertext, - ) -> Result { - let pod_pubkey = pod::ElGamalPubkey(keypair.public.to_bytes()); - let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes()); - - let context = CloseAccountProofContext { - pubkey: pod_pubkey, - ciphertext: pod_ciphertext, - }; - - let mut transcript = CloseAccountProof::transcript_new(&pod_pubkey, &pod_ciphertext); - let proof = CloseAccountProof::new(keypair, ciphertext, &mut transcript); - - Ok(CloseAccountData { context, proof }) - } -} - -impl ZkProofData for CloseAccountData { - const PROOF_TYPE: ProofType = ProofType::CloseAccount; - - fn context_data(&self) -> &CloseAccountProofContext { - &self.context - } - - #[cfg(not(target_os = "solana"))] - fn verify_proof(&self) -> Result<(), ProofError> { - let mut transcript = - CloseAccountProof::transcript_new(&self.context.pubkey, &self.context.ciphertext); - - let pubkey = self.context.pubkey.try_into()?; - let ciphertext = self.context.ciphertext.try_into()?; - self.proof.verify(&pubkey, &ciphertext, &mut transcript) - } -} - -/// This struct represents the cryptographic proof component that certifies that the encrypted -/// balance is zero -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -#[allow(non_snake_case)] -pub struct CloseAccountProof { - pub proof: pod::ZeroBalanceProof, -} - -#[allow(non_snake_case)] -#[cfg(not(target_os = "solana"))] -impl CloseAccountProof { - fn transcript_new( - pubkey: &pod::ElGamalPubkey, - ciphertext: &pod::ElGamalCiphertext, - ) -> Transcript { - let mut transcript = Transcript::new(b"CloseAccountProof"); - - transcript.append_pubkey(b"pubkey", pubkey); - transcript.append_ciphertext(b"ciphertext", ciphertext); - - transcript - } - - pub fn new( - keypair: &ElGamalKeypair, - ciphertext: &ElGamalCiphertext, - transcript: &mut Transcript, - ) -> Self { - let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript); - - Self { - proof: proof.into(), - } - } - - pub fn verify( - &self, - pubkey: &ElGamalPubkey, - ciphertext: &ElGamalCiphertext, - transcript: &mut Transcript, - ) -> Result<(), ProofError> { - let proof: ZeroBalanceProof = self.proof.try_into()?; - proof.verify(pubkey, ciphertext, transcript)?; - - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_close_account_correctness() { - let keypair = ElGamalKeypair::new_rand(); - - // general case: encryption of 0 - let ciphertext = keypair.public.encrypt(0_u64); - let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap(); - assert!(close_account_data.verify_proof().is_ok()); - - // general case: encryption of > 0 - let ciphertext = keypair.public.encrypt(1_u64); - let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap(); - assert!(close_account_data.verify_proof().is_err()); - } -} diff --git a/zk-token-sdk/src/instruction/ctxt_ctxt_equality.rs b/zk-token-sdk/src/instruction/ctxt_ctxt_equality.rs new file mode 100644 index 000000000..0451c05c6 --- /dev/null +++ b/zk-token-sdk/src/instruction/ctxt_ctxt_equality.rs @@ -0,0 +1,228 @@ +//! 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::{ + encryption::{ + elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + pedersen::PedersenOpening, + }, + errors::ProofError, + sigma_proofs::ctxt_ctxt_equality_proof::CiphertextCiphertextEqualityProof, + transcript::TranscriptProtocol, + }, + merlin::Transcript, + std::convert::TryInto, +}; +use { + crate::{ + instruction::{ProofType, ZkProofData}, + zk_token_elgamal::pod, + }, + 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: pod::CiphertextCiphertextEqualityProof, +} + +/// The context data needed to verify a ciphertext-ciphertext equality proof. +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct CiphertextCiphertextEqualityProofContext { + pub source_pubkey: pod::ElGamalPubkey, // 32 bytes + + pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes + + pub source_ciphertext: pod::ElGamalCiphertext, // 64 bytes + + pub destination_ciphertext: pod::ElGamalCiphertext, // 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 = pod::ElGamalPubkey(source_keypair.public.to_bytes()); + let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes()); + let pod_source_ciphertext = pod::ElGamalCiphertext(source_ciphertext.to_bytes()); + let pod_destination_ciphertext = pod::ElGamalCiphertext(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 = CiphertextCiphertextEqualityProof::transcript_new( + &pod_source_pubkey, + &pod_destination_pubkey, + &pod_source_ciphertext, + &pod_destination_ciphertext, + ); + + 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<(), ProofError> { + let mut transcript = CiphertextCiphertextEqualityProof::transcript_new( + &self.context.source_pubkey, + &self.context.destination_pubkey, + &self.context.source_ciphertext, + &self.context.destination_ciphertext, + ); + + 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 CiphertextCiphertextEqualityProof { + fn transcript_new( + source_pubkey: &pod::ElGamalPubkey, + destination_pubkey: &pod::ElGamalPubkey, + source_ciphertext: &pod::ElGamalCiphertext, + destination_ciphertext: &pod::ElGamalCiphertext, + ) -> Transcript { + let mut transcript = Transcript::new(b"CiphertextCiphertextEqualityProof"); + + transcript.append_pubkey(b"pubkey-source", source_pubkey); + transcript.append_pubkey(b"pubkey-dest", destination_pubkey); + + transcript.append_ciphertext(b"ciphertext-source", source_ciphertext); + transcript.append_ciphertext(b"ciphertext-dest", 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.public.encrypt(amount); + + let destination_opening = PedersenOpening::new_rand(); + let destination_ciphertext = destination_keypair + .public + .encrypt_with(amount, &destination_opening); + + let proof_data = CiphertextCiphertextEqualityProofData::new( + &source_keypair, + &destination_keypair.public, + &source_ciphertext, + &destination_ciphertext, + &destination_opening, + amount, + ) + .unwrap(); + + assert!(proof_data.verify_proof().is_ok()); + + let amount: u64 = 55; + let source_ciphertext = source_keypair.public.encrypt(amount); + + let destination_opening = PedersenOpening::new_rand(); + let destination_ciphertext = destination_keypair + .public + .encrypt_with(amount, &destination_opening); + + let proof_data = CiphertextCiphertextEqualityProofData::new( + &source_keypair, + &destination_keypair.public, + &source_ciphertext, + &destination_ciphertext, + &destination_opening, + amount, + ) + .unwrap(); + + assert!(proof_data.verify_proof().is_ok()); + + let amount = u64::max_value(); + let source_ciphertext = source_keypair.public.encrypt(amount); + + let destination_opening = PedersenOpening::new_rand(); + let destination_ciphertext = destination_keypair + .public + .encrypt_with(amount, &destination_opening); + + let proof_data = CiphertextCiphertextEqualityProofData::new( + &source_keypair, + &destination_keypair.public, + &source_ciphertext, + &destination_ciphertext, + &destination_opening, + amount, + ) + .unwrap(); + + assert!(proof_data.verify_proof().is_ok()); + } +} diff --git a/zk-token-sdk/src/instruction/mod.rs b/zk-token-sdk/src/instruction/mod.rs index f257ee961..f480551f0 100644 --- a/zk-token-sdk/src/instruction/mod.rs +++ b/zk-token-sdk/src/instruction/mod.rs @@ -1,9 +1,9 @@ -pub mod close_account; +pub mod ctxt_ctxt_equality; pub mod pubkey_validity; pub mod transfer; pub mod transfer_with_fee; pub mod withdraw; -pub mod withdraw_withheld; +pub mod zero_balance; use num_derive::{FromPrimitive, ToPrimitive}; #[cfg(not(target_os = "solana"))] @@ -19,12 +19,14 @@ use { }; pub use { bytemuck::Pod, - close_account::{CloseAccountData, CloseAccountProofContext}, + ctxt_ctxt_equality::{ + CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData, + }, pubkey_validity::{PubkeyValidityData, PubkeyValidityProofContext}, transfer::{TransferData, TransferProofContext}, transfer_with_fee::{FeeParameters, TransferWithFeeData, TransferWithFeeProofContext}, withdraw::{WithdrawData, WithdrawProofContext}, - withdraw_withheld::{WithdrawWithheldTokensData, WithdrawWithheldTokensProofContext}, + zero_balance::{ZeroBalanceProofContext, ZeroBalanceProofData}, }; #[derive(Clone, Copy, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)] @@ -32,9 +34,9 @@ pub use { pub enum ProofType { /// Empty proof type used to distinguish if a proof context account is initialized Uninitialized, - CloseAccount, + ZeroBalance, Withdraw, - WithdrawWithheldTokens, + CiphertextCiphertextEquality, Transfer, TransferWithFee, PubkeyValidity, diff --git a/zk-token-sdk/src/instruction/pubkey_validity.rs b/zk-token-sdk/src/instruction/pubkey_validity.rs index 83f0461a1..adaca3e7c 100644 --- a/zk-token-sdk/src/instruction/pubkey_validity.rs +++ b/zk-token-sdk/src/instruction/pubkey_validity.rs @@ -1,10 +1,15 @@ +//! 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::{ - encryption::elgamal::{ElGamalKeypair, ElGamalPubkey}, - errors::ProofError, - sigma_proofs::pubkey_proof::PubkeySigmaProof, - transcript::TranscriptProtocol, + encryption::elgamal::ElGamalKeypair, errors::ProofError, + sigma_proofs::pubkey_proof::PubkeyValidityProof, transcript::TranscriptProtocol, }, merlin::Transcript, std::convert::TryInto, @@ -17,22 +22,22 @@ use { bytemuck::{Pod, Zeroable}, }; -/// This struct includes the cryptographic proof *and* the account data information needed to -/// verify the proof +/// The instruction data that is needed for the `ProofInstruction::VerifyPubkeyValidity` +/// instruction. /// -/// - The pre-instruction should call PubkeyValidityData::verify_proof(&self) -/// - The actual program should check that the public key in this struct is consistent with what is -/// stored in the confidential token account +/// 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, + pub context: PubkeyValidityProofContext, // 32 bytes /// Proof that the public key is well-formed - pub proof: PubkeyValidityProof, // 64 bytes + pub proof: pod::PubkeyValidityProof, // 64 bytes } +/// The context data needed to verify a pubkey validity proof. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct PubkeyValidityProofContext { @@ -48,7 +53,7 @@ impl PubkeyValidityData { let context = PubkeyValidityProofContext { pubkey: pod_pubkey }; let mut transcript = PubkeyValidityProof::transcript_new(&pod_pubkey); - let proof = PubkeyValidityProof::new(keypair, &mut transcript); + let proof = PubkeyValidityProof::new(keypair, &mut transcript).into(); Ok(PubkeyValidityData { context, proof }) } @@ -65,18 +70,11 @@ impl ZkProofData for PubkeyValidityData { fn verify_proof(&self) -> Result<(), ProofError> { let mut transcript = PubkeyValidityProof::transcript_new(&self.context.pubkey); let pubkey = self.context.pubkey.try_into()?; - self.proof.verify(&pubkey, &mut transcript) + let proof: PubkeyValidityProof = self.proof.try_into()?; + proof.verify(&pubkey, &mut transcript).map_err(|e| e.into()) } } -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -#[allow(non_snake_case)] -pub struct PubkeyValidityProof { - /// Associated public-key sigma proof - pub proof: pod::PubkeySigmaProof, -} - #[allow(non_snake_case)] #[cfg(not(target_os = "solana"))] impl PubkeyValidityProof { @@ -85,23 +83,6 @@ impl PubkeyValidityProof { transcript.append_pubkey(b"pubkey", pubkey); transcript } - - pub fn new(keypair: &ElGamalKeypair, transcript: &mut Transcript) -> Self { - let proof = PubkeySigmaProof::new(keypair, transcript); - Self { - proof: proof.into(), - } - } - - pub fn verify( - &self, - pubkey: &ElGamalPubkey, - transcript: &mut Transcript, - ) -> Result<(), ProofError> { - let proof: PubkeySigmaProof = self.proof.try_into()?; - proof.verify(pubkey, transcript)?; - Ok(()) - } } #[cfg(test)] @@ -109,7 +90,7 @@ mod test { use super::*; #[test] - fn test_pubkey_validity_correctness() { + fn test_pubkey_validity_instruction_correctness() { let keypair = ElGamalKeypair::new_rand(); let pubkey_validity_data = PubkeyValidityData::new(&keypair).unwrap(); diff --git a/zk-token-sdk/src/instruction/transfer.rs b/zk-token-sdk/src/instruction/transfer.rs index 276993225..c402f0328 100644 --- a/zk-token-sdk/src/instruction/transfer.rs +++ b/zk-token-sdk/src/instruction/transfer.rs @@ -11,7 +11,8 @@ use { instruction::{combine_lo_hi_ciphertexts, split_u64, Role}, range_proof::RangeProof, sigma_proofs::{ - equality_proof::CtxtCommEqualityProof, validity_proof::AggregatedValidityProof, + ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof, + validity_proof::AggregatedValidityProof, }, transcript::TranscriptProtocol, }, @@ -42,6 +43,10 @@ lazy_static::lazy_static! { TRANSFER_AMOUNT_LO_NEGATED_BITS) - 1); } +/// The instruction data that is needed for the `ProofInstruction::VerifyTransfer` 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 TransferData { @@ -52,6 +57,7 @@ pub struct TransferData { pub proof: TransferProof, } +/// The context data needed to verify a transfer proof. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct TransferProofContext { @@ -252,7 +258,7 @@ pub struct TransferProof { pub new_source_commitment: pod::PedersenCommitment, /// Associated equality proof - pub equality_proof: pod::CtxtCommEqualityProof, + pub equality_proof: pod::CiphertextCommitmentEqualityProof, /// Associated ciphertext validity proof pub validity_proof: pod::AggregatedValidityProof, @@ -307,11 +313,11 @@ impl TransferProof { transcript.append_commitment(b"commitment-new-source", &pod_new_source_commitment); // generate equality_proof - let equality_proof = CtxtCommEqualityProof::new( + let equality_proof = CiphertextCommitmentEqualityProof::new( source_keypair, new_source_ciphertext, - source_new_balance, &source_opening, + source_new_balance, transcript, ); @@ -377,13 +383,11 @@ impl TransferProof { transcript.append_commitment(b"commitment-new-source", &self.new_source_commitment); let commitment: PedersenCommitment = self.new_source_commitment.try_into()?; - let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?; + let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?; let aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?; let range_proof: RangeProof = self.range_proof.try_into()?; // verify equality proof - // - // TODO: we can also consider verifying equality and range proof in a batch equality_proof.verify( &transfer_pubkeys.source_pubkey, ciphertext_new_spendable, diff --git a/zk-token-sdk/src/instruction/transfer_with_fee.rs b/zk-token-sdk/src/instruction/transfer_with_fee.rs index a6bdee776..45a9496d6 100644 --- a/zk-token-sdk/src/instruction/transfer_with_fee.rs +++ b/zk-token-sdk/src/instruction/transfer_with_fee.rs @@ -14,7 +14,7 @@ use { }, range_proof::RangeProof, sigma_proofs::{ - equality_proof::CtxtCommEqualityProof, fee_proof::FeeSigmaProof, + ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof, fee_proof::FeeSigmaProof, validity_proof::AggregatedValidityProof, }, transcript::TranscriptProtocol, @@ -60,7 +60,10 @@ lazy_static::lazy_static! { pub static ref COMMITMENT_MAX_FEE_BASIS_POINTS: PedersenCommitment = Pedersen::encode(MAX_FEE_BASIS_POINTS); } -// #[derive(Clone, Copy, Pod, Zeroable)] +/// The instruction data that is needed for the `ProofInstruction::TransferWithFee` instruction. +/// +/// It includes the cryptographic proof as well as the cotnext data information needed to verify +/// the proof. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct TransferWithFeeData { @@ -71,6 +74,7 @@ pub struct TransferWithFeeData { pub proof: TransferWithFeeProof, } +/// The context data needed to verify a transfer-with-fee proof. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct TransferWithFeeProofContext { @@ -386,7 +390,7 @@ impl ZkProofData for TransferWithFeeData { pub struct TransferWithFeeProof { pub new_source_commitment: pod::PedersenCommitment, pub claimed_commitment: pod::PedersenCommitment, - pub equality_proof: pod::CtxtCommEqualityProof, + pub equality_proof: pod::CiphertextCommitmentEqualityProof, pub ciphertext_amount_validity_proof: pod::AggregatedValidityProof, pub fee_sigma_proof: pod::FeeSigmaProof, pub fee_ciphertext_validity_proof: pod::AggregatedValidityProof, @@ -472,11 +476,11 @@ impl TransferWithFeeProof { transcript.append_commitment(b"commitment-new-source", &pod_new_source_commitment); // generate equality_proof - let equality_proof = CtxtCommEqualityProof::new( + let equality_proof = CiphertextCommitmentEqualityProof::new( source_keypair, new_source_ciphertext, - source_new_balance, &opening_source, + source_new_balance, transcript, ); @@ -602,7 +606,7 @@ impl TransferWithFeeProof { let new_source_commitment: PedersenCommitment = self.new_source_commitment.try_into()?; let claimed_commitment: PedersenCommitment = self.claimed_commitment.try_into()?; - let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?; + let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?; let ciphertext_amount_validity_proof: AggregatedValidityProof = self.ciphertext_amount_validity_proof.try_into()?; let fee_sigma_proof: FeeSigmaProof = self.fee_sigma_proof.try_into()?; diff --git a/zk-token-sdk/src/instruction/withdraw.rs b/zk-token-sdk/src/instruction/withdraw.rs index 1bd29d351..009cd9502 100644 --- a/zk-token-sdk/src/instruction/withdraw.rs +++ b/zk-token-sdk/src/instruction/withdraw.rs @@ -7,7 +7,7 @@ use { }, errors::ProofError, range_proof::RangeProof, - sigma_proofs::equality_proof::CtxtCommEqualityProof, + sigma_proofs::ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof, transcript::TranscriptProtocol, }, merlin::Transcript, @@ -24,13 +24,10 @@ use { #[cfg(not(target_os = "solana"))] const WITHDRAW_AMOUNT_BIT_LENGTH: usize = 64; -/// This struct includes the cryptographic proof *and* the account data information needed to verify -/// the proof -/// -/// - The pre-instruction should call WithdrawData::verify_proof(&self) -/// - The actual program should check that `current_ct` is consistent with what is -/// currently stored in the confidential token account TODO: update this statement +/// The instruction data that is needed for the `ProofInstruction::VerifyWithdraw` 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 WithdrawData { @@ -41,6 +38,7 @@ pub struct WithdrawData { pub proof: WithdrawProof, // 736 bytes } +/// The context data needed to verify a withdraw proof. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct WithdrawProofContext { @@ -105,8 +103,9 @@ impl ZkProofData for WithdrawData { } } -/// This struct represents the cryptographic proof component that certifies the account's solvency -/// for withdrawal +/// The withdraw proof. +/// +/// It contains a ciphertext-commitment equality proof and a 64-bit range proof. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] #[allow(non_snake_case)] @@ -115,7 +114,7 @@ pub struct WithdrawProof { pub commitment: pod::PedersenCommitment, /// Associated equality proof - pub equality_proof: pod::CtxtCommEqualityProof, + pub equality_proof: pod::CiphertextCommitmentEqualityProof, /// Associated range proof pub range_proof: pod::RangeProof64, // 672 bytes @@ -149,11 +148,11 @@ impl WithdrawProof { transcript.append_commitment(b"commitment", &pod_commitment); // generate equality_proof - let equality_proof = CtxtCommEqualityProof::new( + let equality_proof = CiphertextCommitmentEqualityProof::new( keypair, final_ciphertext, - final_balance, &opening, + final_balance, transcript, ); @@ -176,17 +175,13 @@ impl WithdrawProof { transcript.append_commitment(b"commitment", &self.commitment); let commitment: PedersenCommitment = self.commitment.try_into()?; - let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?; + let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?; let range_proof: RangeProof = self.range_proof.try_into()?; // verify equality proof - // - // TODO: we can also consider verifying equality and range proof in a batch equality_proof.verify(pubkey, final_ciphertext, &commitment, transcript)?; // verify range proof - // - // TODO: double compressing here - consider modifying range proof input type to `PedersenCommitment` range_proof.verify( vec![&commitment], vec![WITHDRAW_AMOUNT_BIT_LENGTH], diff --git a/zk-token-sdk/src/instruction/withdraw_withheld.rs b/zk-token-sdk/src/instruction/withdraw_withheld.rs deleted file mode 100644 index 7a9278770..000000000 --- a/zk-token-sdk/src/instruction/withdraw_withheld.rs +++ /dev/null @@ -1,260 +0,0 @@ -#[cfg(not(target_os = "solana"))] -use { - crate::{ - encryption::{ - elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, - pedersen::PedersenOpening, - }, - errors::ProofError, - sigma_proofs::equality_proof::CtxtCtxtEqualityProof, - transcript::TranscriptProtocol, - }, - merlin::Transcript, - std::convert::TryInto, -}; -use { - crate::{ - instruction::{ProofType, ZkProofData}, - zk_token_elgamal::pod, - }, - bytemuck::{Pod, Zeroable}, -}; - -/// This struct includes the cryptographic proof *and* the account data information needed to verify -/// the proof -/// -/// - The pre-instruction should call WithdrawWithheldTokensData::verify_proof(&self) -/// - The actual program should check that the ciphertext in this struct is consistent with what is -/// currently stored in the confidential token account -/// -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -pub struct WithdrawWithheldTokensData { - pub context: WithdrawWithheldTokensProofContext, - - pub proof: WithdrawWithheldTokensProof, -} - -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -pub struct WithdrawWithheldTokensProofContext { - pub withdraw_withheld_authority_pubkey: pod::ElGamalPubkey, // 32 bytes - - pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes - - pub withdraw_withheld_authority_ciphertext: pod::ElGamalCiphertext, // 64 bytes - - pub destination_ciphertext: pod::ElGamalCiphertext, // 64 bytes -} - -#[cfg(not(target_os = "solana"))] -impl WithdrawWithheldTokensData { - pub fn new( - withdraw_withheld_authority_keypair: &ElGamalKeypair, - destination_pubkey: &ElGamalPubkey, - withdraw_withheld_authority_ciphertext: &ElGamalCiphertext, - amount: u64, - ) -> Result { - // encrypt withdraw amount under destination public key - let destination_opening = PedersenOpening::new_rand(); - let destination_ciphertext = destination_pubkey.encrypt_with(amount, &destination_opening); - - let pod_withdraw_withheld_authority_pubkey = - pod::ElGamalPubkey(withdraw_withheld_authority_keypair.public.to_bytes()); - let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes()); - let pod_withdraw_withheld_authority_ciphertext = - pod::ElGamalCiphertext(withdraw_withheld_authority_ciphertext.to_bytes()); - let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes()); - - let context = WithdrawWithheldTokensProofContext { - withdraw_withheld_authority_pubkey: pod_withdraw_withheld_authority_pubkey, - destination_pubkey: pod_destination_pubkey, - withdraw_withheld_authority_ciphertext: pod_withdraw_withheld_authority_ciphertext, - destination_ciphertext: pod_destination_ciphertext, - }; - - let mut transcript = WithdrawWithheldTokensProof::transcript_new( - &pod_withdraw_withheld_authority_pubkey, - &pod_destination_pubkey, - &pod_withdraw_withheld_authority_ciphertext, - &pod_destination_ciphertext, - ); - - let proof = WithdrawWithheldTokensProof::new( - withdraw_withheld_authority_keypair, - destination_pubkey, - withdraw_withheld_authority_ciphertext, - amount, - &destination_opening, - &mut transcript, - ); - - Ok(Self { context, proof }) - } -} - -impl ZkProofData for WithdrawWithheldTokensData { - const PROOF_TYPE: ProofType = ProofType::WithdrawWithheldTokens; - - fn context_data(&self) -> &WithdrawWithheldTokensProofContext { - &self.context - } - - #[cfg(not(target_os = "solana"))] - fn verify_proof(&self) -> Result<(), ProofError> { - let mut transcript = WithdrawWithheldTokensProof::transcript_new( - &self.context.withdraw_withheld_authority_pubkey, - &self.context.destination_pubkey, - &self.context.withdraw_withheld_authority_ciphertext, - &self.context.destination_ciphertext, - ); - - let withdraw_withheld_authority_pubkey = - self.context.withdraw_withheld_authority_pubkey.try_into()?; - let destination_pubkey = self.context.destination_pubkey.try_into()?; - let withdraw_withheld_authority_ciphertext = self - .context - .withdraw_withheld_authority_ciphertext - .try_into()?; - let destination_ciphertext = self.context.destination_ciphertext.try_into()?; - - self.proof.verify( - &withdraw_withheld_authority_pubkey, - &destination_pubkey, - &withdraw_withheld_authority_ciphertext, - &destination_ciphertext, - &mut transcript, - ) - } -} - -/// This struct represents the cryptographic proof component that certifies the account's solvency -/// for withdrawal -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -#[allow(non_snake_case)] -pub struct WithdrawWithheldTokensProof { - pub proof: pod::CtxtCtxtEqualityProof, -} - -#[allow(non_snake_case)] -#[cfg(not(target_os = "solana"))] -impl WithdrawWithheldTokensProof { - fn transcript_new( - withdraw_withheld_authority_pubkey: &pod::ElGamalPubkey, - destination_pubkey: &pod::ElGamalPubkey, - withdraw_withheld_authority_ciphertext: &pod::ElGamalCiphertext, - destination_ciphertext: &pod::ElGamalCiphertext, - ) -> Transcript { - let mut transcript = Transcript::new(b"WithdrawWithheldTokensProof"); - - transcript.append_pubkey( - b"withdraw-withheld-authority-pubkey", - withdraw_withheld_authority_pubkey, - ); - transcript.append_pubkey(b"dest-pubkey", destination_pubkey); - - transcript.append_ciphertext( - b"ciphertext-withdraw-withheld-authority", - withdraw_withheld_authority_ciphertext, - ); - transcript.append_ciphertext(b"ciphertext-dest", destination_ciphertext); - - transcript - } - - pub fn new( - withdraw_withheld_authority_keypair: &ElGamalKeypair, - destination_pubkey: &ElGamalPubkey, - withdraw_withheld_authority_ciphertext: &ElGamalCiphertext, - amount: u64, - destination_opening: &PedersenOpening, - transcript: &mut Transcript, - ) -> Self { - let equality_proof = CtxtCtxtEqualityProof::new( - withdraw_withheld_authority_keypair, - destination_pubkey, - withdraw_withheld_authority_ciphertext, - amount, - destination_opening, - transcript, - ); - - Self { - proof: equality_proof.into(), - } - } - - pub fn verify( - &self, - source_pubkey: &ElGamalPubkey, - destination_pubkey: &ElGamalPubkey, - source_ciphertext: &ElGamalCiphertext, - destination_ciphertext: &ElGamalCiphertext, - transcript: &mut Transcript, - ) -> Result<(), ProofError> { - let proof: CtxtCtxtEqualityProof = self.proof.try_into()?; - proof.verify( - source_pubkey, - destination_pubkey, - source_ciphertext, - destination_ciphertext, - transcript, - )?; - - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_withdraw_withheld() { - let withdraw_withheld_authority_keypair = ElGamalKeypair::new_rand(); - let dest_keypair = ElGamalKeypair::new_rand(); - - let amount: u64 = 0; - let withdraw_withheld_authority_ciphertext = - withdraw_withheld_authority_keypair.public.encrypt(amount); - - let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new( - &withdraw_withheld_authority_keypair, - &dest_keypair.public, - &withdraw_withheld_authority_ciphertext, - amount, - ) - .unwrap(); - - assert!(withdraw_withheld_tokens_data.verify_proof().is_ok()); - - let amount: u64 = 55; - let withdraw_withheld_authority_ciphertext = - withdraw_withheld_authority_keypair.public.encrypt(amount); - - let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new( - &withdraw_withheld_authority_keypair, - &dest_keypair.public, - &withdraw_withheld_authority_ciphertext, - amount, - ) - .unwrap(); - - assert!(withdraw_withheld_tokens_data.verify_proof().is_ok()); - - let amount = u64::max_value(); - let withdraw_withheld_authority_ciphertext = - withdraw_withheld_authority_keypair.public.encrypt(amount); - - let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new( - &withdraw_withheld_authority_keypair, - &dest_keypair.public, - &withdraw_withheld_authority_ciphertext, - amount, - ) - .unwrap(); - - assert!(withdraw_withheld_tokens_data.verify_proof().is_ok()); - } -} diff --git a/zk-token-sdk/src/instruction/zero_balance.rs b/zk-token-sdk/src/instruction/zero_balance.rs new file mode 100644 index 000000000..0d676cb13 --- /dev/null +++ b/zk-token-sdk/src/instruction/zero_balance.rs @@ -0,0 +1,127 @@ +//! The zero-balance proof instruction. +//! +//! A zero-balance proof is defined with respect to a twisted ElGamal ciphertext. The proof +//! 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. + +#[cfg(not(target_os = "solana"))] +use { + crate::{ + encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair}, + errors::ProofError, + sigma_proofs::zero_balance_proof::ZeroBalanceProof, + transcript::TranscriptProtocol, + }, + merlin::Transcript, + std::convert::TryInto, +}; +use { + crate::{ + instruction::{ProofType, ZkProofData}, + zk_token_elgamal::pod, + }, + bytemuck::{Pod, Zeroable}, +}; + +/// The instruction data that is needed for the `ProofInstruction::ZeroBalance` 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 ZeroBalanceProofData { + /// The context data for the zero-balance proof + pub context: ZeroBalanceProofContext, // 96 bytes + + /// Proof that the source account available balance is zero + pub proof: pod::ZeroBalanceProof, // 96 bytes +} + +/// The context data needed to verify a zero-balance proof. +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct ZeroBalanceProofContext { + /// The source account ElGamal pubkey + pub pubkey: pod::ElGamalPubkey, // 32 bytes + + /// The source account available balance in encrypted form + pub ciphertext: pod::ElGamalCiphertext, // 64 bytes +} + +#[cfg(not(target_os = "solana"))] +impl ZeroBalanceProofData { + pub fn new( + keypair: &ElGamalKeypair, + ciphertext: &ElGamalCiphertext, + ) -> Result { + let pod_pubkey = pod::ElGamalPubkey(keypair.public.to_bytes()); + let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes()); + + let context = ZeroBalanceProofContext { + pubkey: pod_pubkey, + ciphertext: pod_ciphertext, + }; + + let mut transcript = ZeroBalanceProof::transcript_new(&pod_pubkey, &pod_ciphertext); + let proof = ZeroBalanceProof::new(keypair, ciphertext, &mut transcript).into(); + + Ok(ZeroBalanceProofData { context, proof }) + } +} + +impl ZkProofData for ZeroBalanceProofData { + const PROOF_TYPE: ProofType = ProofType::ZeroBalance; + + fn context_data(&self) -> &ZeroBalanceProofContext { + &self.context + } + + #[cfg(not(target_os = "solana"))] + fn verify_proof(&self) -> Result<(), ProofError> { + let mut transcript = + ZeroBalanceProof::transcript_new(&self.context.pubkey, &self.context.ciphertext); + + let pubkey = self.context.pubkey.try_into()?; + let ciphertext = self.context.ciphertext.try_into()?; + let proof: ZeroBalanceProof = self.proof.try_into()?; + proof + .verify(&pubkey, &ciphertext, &mut transcript) + .map_err(|e| e.into()) + } +} + +#[allow(non_snake_case)] +#[cfg(not(target_os = "solana"))] +impl ZeroBalanceProof { + fn transcript_new( + pubkey: &pod::ElGamalPubkey, + ciphertext: &pod::ElGamalCiphertext, + ) -> Transcript { + let mut transcript = Transcript::new(b"ZeroBalanceProof"); + + transcript.append_pubkey(b"pubkey", pubkey); + transcript.append_ciphertext(b"ciphertext", ciphertext); + + transcript + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_zero_balance_proof_instruction_correctness() { + let keypair = ElGamalKeypair::new_rand(); + + // general case: encryption of 0 + let ciphertext = keypair.public.encrypt(0_u64); + let zero_balance_proof_data = ZeroBalanceProofData::new(&keypair, &ciphertext).unwrap(); + assert!(zero_balance_proof_data.verify_proof().is_ok()); + + // general case: encryption of > 0 + let ciphertext = keypair.public.encrypt(1_u64); + let zero_balance_proof_data = ZeroBalanceProofData::new(&keypair, &ciphertext).unwrap(); + assert!(zero_balance_proof_data.verify_proof().is_err()); + } +} diff --git a/zk-token-sdk/src/sigma_proofs/equality_proof.rs b/zk-token-sdk/src/sigma_proofs/ctxt_comm_equality_proof.rs similarity index 53% rename from zk-token-sdk/src/sigma_proofs/equality_proof.rs rename to zk-token-sdk/src/sigma_proofs/ctxt_comm_equality_proof.rs index 02df193e3..efbfaa87e 100644 --- a/zk-token-sdk/src/sigma_proofs/equality_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/ctxt_comm_equality_proof.rs @@ -1,11 +1,9 @@ -//! The equality sigma proof system. +//! The ciphertext-commitment equality sigma proof system. //! -//! An equality proof is defined with respect to two cryptographic objects: a twisted ElGamal -//! ciphertext and a Pedersen commitment. The proof certifies that a given ciphertext and -//! commitment pair encrypts/encodes the same message. To generate the proof, a prover must provide -//! the decryption key for the ciphertext and the Pedersen opening for the commitment. -//! -//! TODO: verify with respect to ciphertext +//! 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. //! //! The protocol guarantees computationally soundness (by the hardness of discrete log) and perfect //! zero-knowledge in the random oracle model. @@ -39,7 +37,7 @@ use { /// Contains all the elliptic curve and scalar components that make up the sigma protocol. #[allow(non_snake_case)] #[derive(Clone)] -pub struct CtxtCommEqualityProof { +pub struct CiphertextCommitmentEqualityProof { Y_0: CompressedRistretto, Y_1: CompressedRistretto, Y_2: CompressedRistretto, @@ -50,7 +48,7 @@ pub struct CtxtCommEqualityProof { #[allow(non_snake_case)] #[cfg(not(target_os = "solana"))] -impl CtxtCommEqualityProof { +impl CiphertextCommitmentEqualityProof { /// Equality proof constructor. The proof is with respect to a ciphertext and commitment. /// /// The function does *not* hash the public key, ciphertext, or commitment into the transcript. @@ -70,8 +68,8 @@ impl CtxtCommEqualityProof { pub fn new( source_keypair: &ElGamalKeypair, source_ciphertext: &ElGamalCiphertext, - amount: u64, opening: &PedersenOpening, + amount: u64, transcript: &mut Transcript, ) -> Self { transcript.equality_proof_domain_sep(); @@ -112,7 +110,7 @@ impl CtxtCommEqualityProof { y_x.zeroize(); y_r.zeroize(); - CtxtCommEqualityProof { + CiphertextCommitmentEqualityProof { Y_0, Y_1, Y_2, @@ -235,7 +233,7 @@ impl CtxtCommEqualityProof { let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?; - Ok(CtxtCommEqualityProof { + Ok(CiphertextCommitmentEqualityProof { Y_0, Y_1, Y_2, @@ -246,246 +244,6 @@ impl CtxtCommEqualityProof { } } -/// Equality proof. -/// -/// Contains all the elliptic curve and scalar components that make up the sigma protocol. -#[allow(non_snake_case)] -#[derive(Clone)] -pub struct CtxtCtxtEqualityProof { - Y_0: CompressedRistretto, - Y_1: CompressedRistretto, - Y_2: CompressedRistretto, - Y_3: CompressedRistretto, - z_s: Scalar, - z_x: Scalar, - z_r: Scalar, -} - -#[allow(non_snake_case)] -#[cfg(not(target_os = "solana"))] -impl CtxtCtxtEqualityProof { - /// Equality proof constructor. The proof is with respect to two ciphertexts. - /// - /// The function does *not* hash the public key, ciphertext, or commitment 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. - /// - /// Note that the proof constructor does not take the actual Pedersen commitment as input; it - /// takes the associated Pedersen opening instead. - /// - /// * `source_keypair` - The ElGamal keypair associated with the first ciphertext to be proved - /// * `destination_pubkey` - The ElGamal pubkey associated with the second ElGamal ciphertext - /// * `source_ciphertext` - The first ElGamal ciphertext - /// * `amount` - The message associated with the ElGamal ciphertext and Pedersen commitment - /// * `destination_opening` - The opening associated with the second ElGamal ciphertext - /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic - pub fn new( - source_keypair: &ElGamalKeypair, - destination_pubkey: &ElGamalPubkey, - source_ciphertext: &ElGamalCiphertext, - amount: u64, - destination_opening: &PedersenOpening, - transcript: &mut Transcript, - ) -> Self { - transcript.equality_proof_domain_sep(); - - // extract the relevant scalar and Ristretto points from the inputs - let P_source = source_keypair.public.get_point(); - let D_source = source_ciphertext.handle.get_point(); - let P_destination = destination_pubkey.get_point(); - - let s = source_keypair.secret.get_scalar(); - let x = Scalar::from(amount); - let r = destination_opening.get_scalar(); - - // generate random masking factors that also serves as nonces - let mut y_s = Scalar::random(&mut OsRng); - let mut y_x = Scalar::random(&mut OsRng); - let mut y_r = Scalar::random(&mut OsRng); - - let Y_0 = (&y_s * P_source).compress(); - let Y_1 = - RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&(*G), D_source]).compress(); - let Y_2 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_r], vec![&(*G), &(*H)]).compress(); - let Y_3 = (&y_r * P_destination).compress(); - - // record masking factors in the transcript - transcript.append_point(b"Y_0", &Y_0); - transcript.append_point(b"Y_1", &Y_1); - transcript.append_point(b"Y_2", &Y_2); - transcript.append_point(b"Y_3", &Y_3); - - let c = transcript.challenge_scalar(b"c"); - transcript.challenge_scalar(b"w"); - - // compute the masked values - let z_s = &(&c * s) + &y_s; - let z_x = &(&c * &x) + &y_x; - let z_r = &(&c * r) + &y_r; - - // zeroize random scalars - y_s.zeroize(); - y_x.zeroize(); - y_r.zeroize(); - - CtxtCtxtEqualityProof { - Y_0, - Y_1, - Y_2, - Y_3, - z_s, - z_x, - z_r, - } - } - - /// Equality proof verifier. The proof is with respect to two ciphertexts. - /// - /// * `source_pubkey` - The ElGamal pubkey associated with the first ciphertext to be proved - /// * `destination_pubkey` - The ElGamal pubkey associated with the second ciphertext to be proved - /// * `source_ciphertext` - The first ElGamal ciphertext to be proved - /// * `destination_ciphertext` - The second ElGamal ciphertext to be proved - /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic - pub fn verify( - self, - source_pubkey: &ElGamalPubkey, - destination_pubkey: &ElGamalPubkey, - source_ciphertext: &ElGamalCiphertext, - destination_ciphertext: &ElGamalCiphertext, - transcript: &mut Transcript, - ) -> Result<(), EqualityProofError> { - transcript.equality_proof_domain_sep(); - - // extract the relevant scalar and Ristretto points from the inputs - let P_source = source_pubkey.get_point(); - let C_source = source_ciphertext.commitment.get_point(); - let D_source = source_ciphertext.handle.get_point(); - - let P_destination = destination_pubkey.get_point(); - let C_destination = destination_ciphertext.commitment.get_point(); - let D_destination = destination_ciphertext.handle.get_point(); - - // include Y_0, Y_1, Y_2 to transcript and extract challenges - transcript.validate_and_append_point(b"Y_0", &self.Y_0)?; - transcript.validate_and_append_point(b"Y_1", &self.Y_1)?; - transcript.validate_and_append_point(b"Y_2", &self.Y_2)?; - transcript.validate_and_append_point(b"Y_3", &self.Y_3)?; - - let c = transcript.challenge_scalar(b"c"); - let w = transcript.challenge_scalar(b"w"); // w used for batch verification - let ww = &w * &w; - let www = &w * &ww; - - let w_negated = -&w; - let ww_negated = -&ww; - let www_negated = -&www; - - // check that the required algebraic condition holds - let Y_0 = self - .Y_0 - .decompress() - .ok_or(ProofVerificationError::Deserialization)?; - let Y_1 = self - .Y_1 - .decompress() - .ok_or(ProofVerificationError::Deserialization)?; - let Y_2 = self - .Y_2 - .decompress() - .ok_or(ProofVerificationError::Deserialization)?; - let Y_3 = self - .Y_3 - .decompress() - .ok_or(ProofVerificationError::Deserialization)?; - - let check = RistrettoPoint::vartime_multiscalar_mul( - vec![ - &self.z_s, // z_s - &(-&c), // -c - &(-&Scalar::one()), // -identity - &(&w * &self.z_x), // w * z_x - &(&w * &self.z_s), // w * z_s - &(&w_negated * &c), // -w * c - &w_negated, // -w - &(&ww * &self.z_x), // ww * z_x - &(&ww * &self.z_r), // ww * z_r - &(&ww_negated * &c), // -ww * c - &ww_negated, // -ww - &(&www * &self.z_r), // z_r - &(&www_negated * &c), // -www * c - &www_negated, - ], - vec![ - P_source, // P_source - &(*H), // H - &Y_0, // Y_0 - &(*G), // G - D_source, // D_source - C_source, // C_source - &Y_1, // Y_1 - &(*G), // G - &(*H), // H - C_destination, // C_destination - &Y_2, // Y_2 - P_destination, // P_destination - D_destination, // D_destination - &Y_3, // Y_3 - ], - ); - - if check.is_identity() { - Ok(()) - } else { - Err(ProofVerificationError::AlgebraicRelation.into()) - } - } - - pub fn to_bytes(&self) -> [u8; 224] { - let mut buf = [0_u8; 224]; - buf[..32].copy_from_slice(self.Y_0.as_bytes()); - buf[32..64].copy_from_slice(self.Y_1.as_bytes()); - buf[64..96].copy_from_slice(self.Y_2.as_bytes()); - buf[96..128].copy_from_slice(self.Y_3.as_bytes()); - buf[128..160].copy_from_slice(self.z_s.as_bytes()); - buf[160..192].copy_from_slice(self.z_x.as_bytes()); - buf[192..224].copy_from_slice(self.z_r.as_bytes()); - buf - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 224 { - return Err(ProofVerificationError::Deserialization.into()); - } - - let bytes = array_ref![bytes, 0, 224]; - let (Y_0, Y_1, Y_2, Y_3, z_s, z_x, z_r) = array_refs![bytes, 32, 32, 32, 32, 32, 32, 32]; - - let Y_0 = CompressedRistretto::from_slice(Y_0); - let Y_1 = CompressedRistretto::from_slice(Y_1); - let Y_2 = CompressedRistretto::from_slice(Y_2); - let Y_3 = CompressedRistretto::from_slice(Y_3); - - let z_s = - Scalar::from_canonical_bytes(*z_s).ok_or(ProofVerificationError::Deserialization)?; - let z_x = - Scalar::from_canonical_bytes(*z_x).ok_or(ProofVerificationError::Deserialization)?; - let z_r = - Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?; - - Ok(CtxtCtxtEqualityProof { - Y_0, - Y_1, - Y_2, - Y_3, - z_s, - z_x, - z_r, - }) - } -} - #[cfg(test)] mod test { use { @@ -505,11 +263,11 @@ mod test { let mut prover_transcript = Transcript::new(b"Test"); let mut verifier_transcript = Transcript::new(b"Test"); - let proof = CtxtCommEqualityProof::new( + let proof = CiphertextCommitmentEqualityProof::new( &source_keypair, &source_ciphertext, - message, &destination_opening, + message, &mut prover_transcript, ); @@ -533,11 +291,11 @@ mod test { let mut prover_transcript = Transcript::new(b"Test"); let mut verifier_transcript = Transcript::new(b"Test"); - let proof = CtxtCommEqualityProof::new( + let proof = CiphertextCommitmentEqualityProof::new( &source_keypair, &source_ciphertext, - message, &destination_opening, + message, &mut prover_transcript, ); @@ -566,11 +324,11 @@ mod test { let mut prover_transcript = Transcript::new(b"Test"); let mut verifier_transcript = Transcript::new(b"Test"); - let proof = CtxtCommEqualityProof::new( + let proof = CiphertextCommitmentEqualityProof::new( &elgamal_keypair, &ciphertext, - message, &opening, + message, &mut prover_transcript, ); @@ -595,11 +353,11 @@ mod test { let mut prover_transcript = Transcript::new(b"Test"); let mut verifier_transcript = Transcript::new(b"Test"); - let proof = CtxtCommEqualityProof::new( + let proof = CiphertextCommitmentEqualityProof::new( &elgamal_keypair, &ciphertext, - message, &opening, + message, &mut prover_transcript, ); @@ -624,11 +382,11 @@ mod test { let mut prover_transcript = Transcript::new(b"Test"); let mut verifier_transcript = Transcript::new(b"Test"); - let proof = CtxtCommEqualityProof::new( + let proof = CiphertextCommitmentEqualityProof::new( &elgamal_keypair, &ciphertext, - message, &opening, + message, &mut prover_transcript, ); @@ -652,11 +410,11 @@ mod test { let mut prover_transcript = Transcript::new(b"Test"); let mut verifier_transcript = Transcript::new(b"Test"); - let proof = CtxtCommEqualityProof::new( + let proof = CiphertextCommitmentEqualityProof::new( &elgamal_keypair, &ciphertext, - message, &opening, + message, &mut prover_transcript, ); @@ -669,74 +427,4 @@ mod test { ) .is_ok()); } - - #[test] - fn test_ciphertext_ciphertext_equality_proof_correctness() { - // success case - let source_keypair = ElGamalKeypair::new_rand(); - let destination_keypair = ElGamalKeypair::new_rand(); - let message: u64 = 55; - - let source_ciphertext = source_keypair.public.encrypt(message); - - let destination_opening = PedersenOpening::new_rand(); - let destination_ciphertext = destination_keypair - .public - .encrypt_with(message, &destination_opening); - - let mut prover_transcript = Transcript::new(b"Test"); - let mut verifier_transcript = Transcript::new(b"Test"); - - let proof = CtxtCtxtEqualityProof::new( - &source_keypair, - &destination_keypair.public, - &source_ciphertext, - message, - &destination_opening, - &mut prover_transcript, - ); - - assert!(proof - .verify( - &source_keypair.public, - &destination_keypair.public, - &source_ciphertext, - &destination_ciphertext, - &mut verifier_transcript - ) - .is_ok()); - - // fail case: encrypted and committed messages are different - let source_message: u64 = 55; - let destination_message: u64 = 77; - - let source_ciphertext = source_keypair.public.encrypt(source_message); - - let destination_opening = PedersenOpening::new_rand(); - let destination_ciphertext = destination_keypair - .public - .encrypt_with(destination_message, &destination_opening); - - let mut prover_transcript = Transcript::new(b"Test"); - let mut verifier_transcript = Transcript::new(b"Test"); - - let proof = CtxtCtxtEqualityProof::new( - &source_keypair, - &destination_keypair.public, - &source_ciphertext, - message, - &destination_opening, - &mut prover_transcript, - ); - - assert!(proof - .verify( - &source_keypair.public, - &destination_keypair.public, - &source_ciphertext, - &destination_ciphertext, - &mut verifier_transcript - ) - .is_err()); - } } diff --git a/zk-token-sdk/src/sigma_proofs/ctxt_ctxt_equality_proof.rs b/zk-token-sdk/src/sigma_proofs/ctxt_ctxt_equality_proof.rs new file mode 100644 index 000000000..4cdeacb25 --- /dev/null +++ b/zk-token-sdk/src/sigma_proofs/ctxt_ctxt_equality_proof.rs @@ -0,0 +1,342 @@ +//! The ciphertext-ciphertext equality sigma proof system. +//! +//! 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::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + pedersen::{PedersenOpening, G, H}, + }, + errors::ProofVerificationError, + }, + curve25519_dalek::traits::MultiscalarMul, + rand::rngs::OsRng, + zeroize::Zeroize, +}; +use { + crate::{sigma_proofs::errors::EqualityProofError, transcript::TranscriptProtocol}, + arrayref::{array_ref, array_refs}, + curve25519_dalek::{ + ristretto::{CompressedRistretto, RistrettoPoint}, + scalar::Scalar, + traits::{IsIdentity, VartimeMultiscalarMul}, + }, + merlin::Transcript, +}; + +/// The ciphertext-ciphertext equality proof. +/// +/// Contains all the elliptic curve and scalar components that make up the sigma protocol. +#[allow(non_snake_case)] +#[derive(Clone)] +pub struct CiphertextCiphertextEqualityProof { + Y_0: CompressedRistretto, + Y_1: CompressedRistretto, + Y_2: CompressedRistretto, + Y_3: CompressedRistretto, + z_s: Scalar, + z_x: Scalar, + z_r: Scalar, +} + +#[allow(non_snake_case)] +#[cfg(not(target_os = "solana"))] +impl CiphertextCiphertextEqualityProof { + /// The ciphertext-ciphertext equality proof constructor. + /// + /// The function does *not* hash the public key, ciphertext, or commitment 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. + /// + /// * `source_keypair` - The ElGamal keypair associated with the first ciphertext to be proved + /// * `destination_pubkey` - The ElGamal pubkey associated with the second ElGamal ciphertext + /// * `source_ciphertext` - The first ElGamal ciphertext for which the prover knows a + /// decryption key for + /// * `destination_opening` - The opening (randomness) associated with the second ElGamal ciphertext + /// * `amount` - The message associated with the ElGamal ciphertext and Pedersen commitment + /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic + pub fn new( + source_keypair: &ElGamalKeypair, + destination_pubkey: &ElGamalPubkey, + source_ciphertext: &ElGamalCiphertext, + destination_opening: &PedersenOpening, + amount: u64, + transcript: &mut Transcript, + ) -> Self { + transcript.equality_proof_domain_sep(); + + // extract the relevant scalar and Ristretto points from the inputs + let P_source = source_keypair.public.get_point(); + let D_source = source_ciphertext.handle.get_point(); + let P_destination = destination_pubkey.get_point(); + + let s = source_keypair.secret.get_scalar(); + let x = Scalar::from(amount); + let r = destination_opening.get_scalar(); + + // generate random masking factors that also serves as nonces + let mut y_s = Scalar::random(&mut OsRng); + let mut y_x = Scalar::random(&mut OsRng); + let mut y_r = Scalar::random(&mut OsRng); + + let Y_0 = (&y_s * P_source).compress(); + let Y_1 = + RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&(*G), D_source]).compress(); + let Y_2 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_r], vec![&(*G), &(*H)]).compress(); + let Y_3 = (&y_r * P_destination).compress(); + + // record masking factors in the transcript + transcript.append_point(b"Y_0", &Y_0); + transcript.append_point(b"Y_1", &Y_1); + transcript.append_point(b"Y_2", &Y_2); + transcript.append_point(b"Y_3", &Y_3); + + let c = transcript.challenge_scalar(b"c"); + transcript.challenge_scalar(b"w"); + + // compute the masked values + let z_s = &(&c * s) + &y_s; + let z_x = &(&c * &x) + &y_x; + let z_r = &(&c * r) + &y_r; + + // zeroize random scalars + y_s.zeroize(); + y_x.zeroize(); + y_r.zeroize(); + + CiphertextCiphertextEqualityProof { + Y_0, + Y_1, + Y_2, + Y_3, + z_s, + z_x, + z_r, + } + } + + /// The ciphertext-ciphertext equality proof verifier. The proof is with respect to two + /// ciphertexts. + /// + /// * `source_pubkey` - The ElGamal pubkey associated with the first ciphertext to be proved + /// * `destination_pubkey` - The ElGamal pubkey associated with the second ciphertext to be proved + /// * `source_ciphertext` - The first ElGamal ciphertext to be proved + /// * `destination_ciphertext` - The second ElGamal ciphertext to be proved + /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic + pub fn verify( + self, + source_pubkey: &ElGamalPubkey, + destination_pubkey: &ElGamalPubkey, + source_ciphertext: &ElGamalCiphertext, + destination_ciphertext: &ElGamalCiphertext, + transcript: &mut Transcript, + ) -> Result<(), EqualityProofError> { + transcript.equality_proof_domain_sep(); + + // extract the relevant scalar and Ristretto points from the inputs + let P_source = source_pubkey.get_point(); + let C_source = source_ciphertext.commitment.get_point(); + let D_source = source_ciphertext.handle.get_point(); + + let P_destination = destination_pubkey.get_point(); + let C_destination = destination_ciphertext.commitment.get_point(); + let D_destination = destination_ciphertext.handle.get_point(); + + // include Y_0, Y_1, Y_2 to transcript and extract challenges + transcript.validate_and_append_point(b"Y_0", &self.Y_0)?; + transcript.validate_and_append_point(b"Y_1", &self.Y_1)?; + transcript.validate_and_append_point(b"Y_2", &self.Y_2)?; + transcript.validate_and_append_point(b"Y_3", &self.Y_3)?; + + let c = transcript.challenge_scalar(b"c"); + let w = transcript.challenge_scalar(b"w"); // w used for batch verification + let ww = &w * &w; + let www = &w * &ww; + + let w_negated = -&w; + let ww_negated = -&ww; + let www_negated = -&www; + + // check that the required algebraic condition holds + let Y_0 = self + .Y_0 + .decompress() + .ok_or(ProofVerificationError::Deserialization)?; + let Y_1 = self + .Y_1 + .decompress() + .ok_or(ProofVerificationError::Deserialization)?; + let Y_2 = self + .Y_2 + .decompress() + .ok_or(ProofVerificationError::Deserialization)?; + let Y_3 = self + .Y_3 + .decompress() + .ok_or(ProofVerificationError::Deserialization)?; + + let check = RistrettoPoint::vartime_multiscalar_mul( + vec![ + &self.z_s, // z_s + &(-&c), // -c + &(-&Scalar::one()), // -identity + &(&w * &self.z_x), // w * z_x + &(&w * &self.z_s), // w * z_s + &(&w_negated * &c), // -w * c + &w_negated, // -w + &(&ww * &self.z_x), // ww * z_x + &(&ww * &self.z_r), // ww * z_r + &(&ww_negated * &c), // -ww * c + &ww_negated, // -ww + &(&www * &self.z_r), // z_r + &(&www_negated * &c), // -www * c + &www_negated, + ], + vec![ + P_source, // P_source + &(*H), // H + &Y_0, // Y_0 + &(*G), // G + D_source, // D_source + C_source, // C_source + &Y_1, // Y_1 + &(*G), // G + &(*H), // H + C_destination, // C_destination + &Y_2, // Y_2 + P_destination, // P_destination + D_destination, // D_destination + &Y_3, // Y_3 + ], + ); + + if check.is_identity() { + Ok(()) + } else { + Err(ProofVerificationError::AlgebraicRelation.into()) + } + } + + pub fn to_bytes(&self) -> [u8; 224] { + let mut buf = [0_u8; 224]; + buf[..32].copy_from_slice(self.Y_0.as_bytes()); + buf[32..64].copy_from_slice(self.Y_1.as_bytes()); + buf[64..96].copy_from_slice(self.Y_2.as_bytes()); + buf[96..128].copy_from_slice(self.Y_3.as_bytes()); + buf[128..160].copy_from_slice(self.z_s.as_bytes()); + buf[160..192].copy_from_slice(self.z_x.as_bytes()); + buf[192..224].copy_from_slice(self.z_r.as_bytes()); + buf + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != 224 { + return Err(ProofVerificationError::Deserialization.into()); + } + + let bytes = array_ref![bytes, 0, 224]; + let (Y_0, Y_1, Y_2, Y_3, z_s, z_x, z_r) = array_refs![bytes, 32, 32, 32, 32, 32, 32, 32]; + + let Y_0 = CompressedRistretto::from_slice(Y_0); + let Y_1 = CompressedRistretto::from_slice(Y_1); + let Y_2 = CompressedRistretto::from_slice(Y_2); + let Y_3 = CompressedRistretto::from_slice(Y_3); + + let z_s = + Scalar::from_canonical_bytes(*z_s).ok_or(ProofVerificationError::Deserialization)?; + let z_x = + Scalar::from_canonical_bytes(*z_x).ok_or(ProofVerificationError::Deserialization)?; + let z_r = + Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?; + + Ok(CiphertextCiphertextEqualityProof { + Y_0, + Y_1, + Y_2, + Y_3, + z_s, + z_x, + z_r, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_ciphertext_ciphertext_equality_proof_correctness() { + // success case + let source_keypair = ElGamalKeypair::new_rand(); + let destination_keypair = ElGamalKeypair::new_rand(); + let message: u64 = 55; + + let source_ciphertext = source_keypair.public.encrypt(message); + + let destination_opening = PedersenOpening::new_rand(); + let destination_ciphertext = destination_keypair + .public + .encrypt_with(message, &destination_opening); + + let mut prover_transcript = Transcript::new(b"Test"); + let mut verifier_transcript = Transcript::new(b"Test"); + + let proof = CiphertextCiphertextEqualityProof::new( + &source_keypair, + &destination_keypair.public, + &source_ciphertext, + &destination_opening, + message, + &mut prover_transcript, + ); + + assert!(proof + .verify( + &source_keypair.public, + &destination_keypair.public, + &source_ciphertext, + &destination_ciphertext, + &mut verifier_transcript + ) + .is_ok()); + + // fail case: encrypted and committed messages are different + let source_message: u64 = 55; + let destination_message: u64 = 77; + + let source_ciphertext = source_keypair.public.encrypt(source_message); + + let destination_opening = PedersenOpening::new_rand(); + let destination_ciphertext = destination_keypair + .public + .encrypt_with(destination_message, &destination_opening); + + let mut prover_transcript = Transcript::new(b"Test"); + let mut verifier_transcript = Transcript::new(b"Test"); + + let proof = CiphertextCiphertextEqualityProof::new( + &source_keypair, + &destination_keypair.public, + &source_ciphertext, + &destination_opening, + message, + &mut prover_transcript, + ); + + assert!(proof + .verify( + &source_keypair.public, + &destination_keypair.public, + &source_ciphertext, + &destination_ciphertext, + &mut verifier_transcript + ) + .is_err()); + } +} diff --git a/zk-token-sdk/src/sigma_proofs/mod.rs b/zk-token-sdk/src/sigma_proofs/mod.rs index 3b0918fda..2ccf2fdf3 100644 --- a/zk-token-sdk/src/sigma_proofs/mod.rs +++ b/zk-token-sdk/src/sigma_proofs/mod.rs @@ -15,7 +15,8 @@ //! We refer to the zk-token paper for the formal details and security proofs of these argument //! systems. -pub mod equality_proof; +pub mod ctxt_comm_equality_proof; +pub mod ctxt_ctxt_equality_proof; pub mod errors; pub mod fee_proof; pub mod pubkey_proof; diff --git a/zk-token-sdk/src/sigma_proofs/pubkey_proof.rs b/zk-token-sdk/src/sigma_proofs/pubkey_proof.rs index bd611fc81..400e719e4 100644 --- a/zk-token-sdk/src/sigma_proofs/pubkey_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/pubkey_proof.rs @@ -1,9 +1,5 @@ //! The public-key (validity) proof system. //! -//! A public-key proof 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 prove the secret key for the public key. -//! //! The protocol guarantees computational soundness (by the hardness of discrete log) and perfect //! zero-knowledge in the random oracle model. @@ -35,14 +31,14 @@ use { /// Contains all the elliptic curve and scalar components that make up the sigma protocol. #[allow(non_snake_case)] #[derive(Clone)] -pub struct PubkeySigmaProof { +pub struct PubkeyValidityProof { Y: CompressedRistretto, z: Scalar, } #[allow(non_snake_case)] #[cfg(not(target_os = "solana"))] -impl PubkeySigmaProof { +impl PubkeyValidityProof { /// Public-key proof constructor. /// /// The function does *not* hash the public key and ciphertext into the transcript. For @@ -136,7 +132,7 @@ impl PubkeySigmaProof { let Y = CompressedRistretto::from_slice(Y); let z = Scalar::from_canonical_bytes(*z).ok_or(ProofVerificationError::Deserialization)?; - Ok(PubkeySigmaProof { Y, z }) + Ok(PubkeyValidityProof { Y, z }) } } @@ -155,7 +151,7 @@ mod test { let mut prover_transcript = Transcript::new(b"test"); let mut verifier_transcript = Transcript::new(b"test"); - let proof = PubkeySigmaProof::new(&keypair, &mut prover_transcript); + let proof = PubkeyValidityProof::new(&keypair, &mut prover_transcript); assert!(proof .verify(&keypair.public, &mut verifier_transcript) .is_ok()); @@ -166,7 +162,7 @@ mod test { let mut prover_transcript = Transcript::new(b"test"); let mut verifier_transcript = Transcript::new(b"test"); - let proof = PubkeySigmaProof::new(&keypair, &mut prover_transcript); + let proof = PubkeyValidityProof::new(&keypair, &mut prover_transcript); assert!(proof .verify(&keypair.public, &mut verifier_transcript) .is_ok()); diff --git a/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs b/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs index 14afd2830..310886054 100644 --- a/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs @@ -1,9 +1,5 @@ //! The zero-balance sigma proof system. //! -//! A zero-balance proof is defined with respect to a twisted ElGamal ciphertext. The proof -//! certifies that a given ciphertext encrypts the message 0 (`Scalar::zero()`). To generate the -//! proof, a prover must provide the decryption key for the ciphertext. -//! //! The protocol guarantees computationally soundness (by the hardness of discrete log) and perfect //! zero-knowledge in the random oracle model. diff --git a/zk-token-sdk/src/zk_token_elgamal/convert.rs b/zk-token-sdk/src/zk_token_elgamal/convert.rs index a327b3f2d..00a582be2 100644 --- a/zk-token-sdk/src/zk_token_elgamal/convert.rs +++ b/zk-token-sdk/src/zk_token_elgamal/convert.rs @@ -64,10 +64,11 @@ mod target_arch { }, range_proof::{errors::RangeProofError, RangeProof}, sigma_proofs::{ - equality_proof::{CtxtCommEqualityProof, CtxtCtxtEqualityProof}, + ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof, + ctxt_ctxt_equality_proof::CiphertextCiphertextEqualityProof, errors::*, fee_proof::FeeSigmaProof, - pubkey_proof::PubkeySigmaProof, + pubkey_proof::PubkeyValidityProof, validity_proof::{AggregatedValidityProof, ValidityProof}, zero_balance_proof::ZeroBalanceProof, }, @@ -191,30 +192,30 @@ mod target_arch { } } - impl From for pod::CtxtCommEqualityProof { - fn from(proof: CtxtCommEqualityProof) -> Self { + impl From for pod::CiphertextCommitmentEqualityProof { + fn from(proof: CiphertextCommitmentEqualityProof) -> Self { Self(proof.to_bytes()) } } - impl TryFrom for CtxtCommEqualityProof { + impl TryFrom for CiphertextCommitmentEqualityProof { type Error = EqualityProofError; - fn try_from(pod: pod::CtxtCommEqualityProof) -> Result { + fn try_from(pod: pod::CiphertextCommitmentEqualityProof) -> Result { Self::from_bytes(&pod.0) } } - impl From for pod::CtxtCtxtEqualityProof { - fn from(proof: CtxtCtxtEqualityProof) -> Self { + impl From for pod::CiphertextCiphertextEqualityProof { + fn from(proof: CiphertextCiphertextEqualityProof) -> Self { Self(proof.to_bytes()) } } - impl TryFrom for CtxtCtxtEqualityProof { + impl TryFrom for CiphertextCiphertextEqualityProof { type Error = EqualityProofError; - fn try_from(pod: pod::CtxtCtxtEqualityProof) -> Result { + fn try_from(pod: pod::CiphertextCiphertextEqualityProof) -> Result { Self::from_bytes(&pod.0) } } @@ -275,16 +276,16 @@ mod target_arch { } } - impl From for pod::PubkeySigmaProof { - fn from(proof: PubkeySigmaProof) -> Self { + impl From for pod::PubkeyValidityProof { + fn from(proof: PubkeyValidityProof) -> Self { Self(proof.to_bytes()) } } - impl TryFrom for PubkeySigmaProof { + impl TryFrom for PubkeyValidityProof { type Error = PubkeyValidityProofError; - fn try_from(pod: pod::PubkeySigmaProof) -> Result { + fn try_from(pod: pod::PubkeyValidityProof) -> Result { Self::from_bytes(&pod.0) } } diff --git a/zk-token-sdk/src/zk_token_elgamal/pod.rs b/zk-token-sdk/src/zk_token_elgamal/pod.rs index 041bd2177..f76681a96 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod.rs @@ -113,25 +113,25 @@ impl fmt::Debug for DecryptHandle { } } -/// Serialization of `CtxtCommEqualityProof` +/// Serialization of `CiphertextCommitmentEqualityProof` #[derive(Clone, Copy)] #[repr(transparent)] -pub struct CtxtCommEqualityProof(pub [u8; 192]); +pub struct CiphertextCommitmentEqualityProof(pub [u8; 192]); -// `CtxtCommEqualityProof` is a Pod and Zeroable. +// `CiphertextCommitmentEqualityProof` is a Pod and Zeroable. // Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays -unsafe impl Zeroable for CtxtCommEqualityProof {} -unsafe impl Pod for CtxtCommEqualityProof {} +unsafe impl Zeroable for CiphertextCommitmentEqualityProof {} +unsafe impl Pod for CiphertextCommitmentEqualityProof {} /// Serialization of `CtxtCtxtEqualityProof` #[derive(Clone, Copy)] #[repr(transparent)] -pub struct CtxtCtxtEqualityProof(pub [u8; 224]); +pub struct CiphertextCiphertextEqualityProof(pub [u8; 224]); // `CtxtCtxtEqualityProof` is a Pod and Zeroable. // Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays -unsafe impl Zeroable for CtxtCtxtEqualityProof {} -unsafe impl Pod for CtxtCtxtEqualityProof {} +unsafe impl Zeroable for CiphertextCiphertextEqualityProof {} +unsafe impl Pod for CiphertextCiphertextEqualityProof {} /// Serialization of validity proofs #[derive(Clone, Copy)] @@ -171,7 +171,7 @@ pub struct FeeSigmaProof(pub [u8; 256]); /// Serialization of public-key sigma proof #[derive(Clone, Copy, Pod, Zeroable)] #[repr(transparent)] -pub struct PubkeySigmaProof(pub [u8; 64]); +pub struct PubkeyValidityProof(pub [u8; 64]); /// Serialization of range proofs for 64-bit numbers (for `Withdraw` instruction) #[derive(Clone, Copy)] diff --git a/zk-token-sdk/src/zk_token_proof_instruction.rs b/zk-token-sdk/src/zk_token_proof_instruction.rs index b3745c238..2a74f9d21 100644 --- a/zk-token-sdk/src/zk_token_proof_instruction.rs +++ b/zk-token-sdk/src/zk_token_proof_instruction.rs @@ -25,7 +25,7 @@ pub enum ProofInstruction { /// CloseContextState, - /// Verify a close account zero-knowledge proof. + /// Verify a zero-balance proof. /// /// This instruction can be configured to optionally create a proof context state account. /// @@ -39,9 +39,9 @@ pub enum ProofInstruction { /// None /// /// Data expected by this instruction: - /// `CloseAccountData` + /// `ZeroBalanceProofData` /// - VerifyCloseAccount, + VerifyZeroBalance, /// Verify a withdraw zero-knowledge proof. /// @@ -61,7 +61,7 @@ pub enum ProofInstruction { /// VerifyWithdraw, - /// Verify a withdraw withheld tokens zero-knowledge proof. + /// Verify a ciphertext-ciphertext equality proof. /// /// This instruction can be configured to optionally create a proof context state account. /// @@ -75,9 +75,9 @@ pub enum ProofInstruction { /// None /// /// Data expected by this instruction: - /// `WithdrawWithheldTokensData` + /// `CiphertextCiphertextEqualityProofData` /// - VerifyWithdrawWithheldTokens, + VerifyCiphertextCiphertextEquality, /// Verify a transfer zero-knowledge proof. /// @@ -161,12 +161,12 @@ pub fn close_context_state( } } -/// Create a `VerifyCloseAccount` instruction. -pub fn verify_close_account( +/// Create a `VerifyZeroBalance` instruction. +pub fn verify_zero_balance( context_state_info: Option, - proof_data: &CloseAccountData, + proof_data: &ZeroBalanceProofData, ) -> Instruction { - ProofInstruction::VerifyCloseAccount.encode_verify_proof(context_state_info, proof_data) + ProofInstruction::VerifyZeroBalance.encode_verify_proof(context_state_info, proof_data) } /// Create a `VerifyWithdraw` instruction. @@ -177,12 +177,12 @@ pub fn verify_withdraw( ProofInstruction::VerifyWithdraw.encode_verify_proof(context_state_info, proof_data) } -/// Create a `VerifyWithdrawWithheldTokens` instruction. -pub fn verify_withdraw_withheld_tokens( +/// Create a `VerifyCiphertextCiphertextEquality` instruction. +pub fn verify_ciphertext_ciphertext_equality( context_state_info: Option, - proof_data: &WithdrawWithheldTokensData, + proof_data: &CiphertextCiphertextEqualityProofData, ) -> Instruction { - ProofInstruction::VerifyWithdrawWithheldTokens + ProofInstruction::VerifyCiphertextCiphertextEquality .encode_verify_proof(context_state_info, proof_data) }