From b4100a9b5d54c0c9c03a5c7071f795f53c104745 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 17 Feb 2022 12:45:07 -0500 Subject: [PATCH] Add additional zkp for fee (#23112) * zk-token-sdk: add equality proof for fee * zk-token-sdk: tweak some naming conventions for readability * zk-token-sdk: add verify withdraw withheld instruction * zk-token-sdk: add test for withdraw withheld verification * zk-token-sdk: more renaming of variables for readability * zk-token-sdk: cargo fmt * zk-token-sdk: minor * zk-token-sdk: resolve bpf compilation warnings * zk-token-sdk: minor update to doc --- programs/zk-token-proof/src/lib.rs | 4 + zk-token-sdk/src/instruction/close_account.rs | 2 +- zk-token-sdk/src/instruction/mod.rs | 2 + zk-token-sdk/src/instruction/transfer.rs | 128 +++--- .../src/instruction/transfer_with_fee.rs | 187 ++++---- zk-token-sdk/src/instruction/withdraw.rs | 19 +- .../src/instruction/withdraw_withheld.rs | 214 ++++++++++ .../src/sigma_proofs/equality_proof.rs | 402 +++++++++++++++--- zk-token-sdk/src/sigma_proofs/mod.rs | 4 +- zk-token-sdk/src/zk_token_elgamal/convert.rs | 78 ++-- zk-token-sdk/src/zk_token_elgamal/pod.rs | 49 ++- .../src/zk_token_proof_instruction.rs | 14 + 12 files changed, 849 insertions(+), 254 deletions(-) create mode 100644 zk-token-sdk/src/instruction/withdraw_withheld.rs diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index d81f1f336a..797ae4dbdd 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -49,6 +49,10 @@ pub fn process_instruction( ic_msg!(invoke_context, "VerifyWithdraw"); verify::(input, invoke_context) } + ProofInstruction::VerifyWithdrawWithheldTokens => { + ic_msg!(invoke_context, "VerifyWithdraw"); + verify::(input, invoke_context) + } ProofInstruction::VerifyTransfer => { ic_msg!(invoke_context, "VerifyTransfer"); verify::(input, invoke_context) diff --git a/zk-token-sdk/src/instruction/close_account.rs b/zk-token-sdk/src/instruction/close_account.rs index 0c1e9302aa..0c8031fa4b 100644 --- a/zk-token-sdk/src/instruction/close_account.rs +++ b/zk-token-sdk/src/instruction/close_account.rs @@ -98,7 +98,7 @@ impl CloseAccountProof { ) -> Self { let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript); - CloseAccountProof { + Self { proof: proof.into(), } } diff --git a/zk-token-sdk/src/instruction/mod.rs b/zk-token-sdk/src/instruction/mod.rs index 73698dbfcb..a0b0c5443f 100644 --- a/zk-token-sdk/src/instruction/mod.rs +++ b/zk-token-sdk/src/instruction/mod.rs @@ -2,6 +2,7 @@ pub mod close_account; pub mod transfer; pub mod transfer_with_fee; pub mod withdraw; +pub mod withdraw_withheld; #[cfg(not(target_arch = "bpf"))] use { @@ -17,6 +18,7 @@ use { pub use { close_account::CloseAccountData, transfer::TransferData, transfer_with_fee::TransferWithFeeData, withdraw::WithdrawData, + withdraw_withheld::WithdrawWithheldTokensData, }; /// Constant for 2^32 diff --git a/zk-token-sdk/src/instruction/transfer.rs b/zk-token-sdk/src/instruction/transfer.rs index df5e1fdbb6..82ef897c1b 100644 --- a/zk-token-sdk/src/instruction/transfer.rs +++ b/zk-token-sdk/src/instruction/transfer.rs @@ -15,7 +15,9 @@ use { errors::ProofError, instruction::{combine_u32_ciphertexts, split_u64_into_u32, Role, Verifiable, TWO_32}, range_proof::RangeProof, - sigma_proofs::{equality_proof::EqualityProof, validity_proof::AggregatedValidityProof}, + sigma_proofs::{ + equality_proof::CtxtCommEqualityProof, validity_proof::AggregatedValidityProof, + }, transcript::TranscriptProtocol, }, arrayref::{array_ref, array_refs}, @@ -23,14 +25,21 @@ use { std::convert::TryInto, }; +#[cfg(not(target_arch = "bpf"))] +const TRANSFER_SOURCE_AMOUNT_BIT_LENGTH: usize = 64; +#[cfg(not(target_arch = "bpf"))] +const TRANSFER_AMOUNT_LO_BIT_LENGTH: usize = 32; +#[cfg(not(target_arch = "bpf"))] +const TRANSFER_AMOUNT_HI_BIT_LENGTH: usize = 32; + #[derive(Clone)] #[repr(C)] #[cfg(not(target_arch = "bpf"))] pub struct TransferAmountEncryption { pub commitment: PedersenCommitment, - pub source: DecryptHandle, - pub dest: DecryptHandle, - pub auditor: DecryptHandle, + pub handle_source: DecryptHandle, + pub handle_dest: DecryptHandle, + pub handle_auditor: DecryptHandle, } #[cfg(not(target_arch = "bpf"))] @@ -44,9 +53,9 @@ impl TransferAmountEncryption { let (commitment, opening) = Pedersen::new(amount); let transfer_amount_encryption = Self { commitment, - source: pubkey_source.decrypt_handle(&opening), - dest: pubkey_dest.decrypt_handle(&opening), - auditor: pubkey_auditor.decrypt_handle(&opening), + handle_source: pubkey_source.decrypt_handle(&opening), + handle_dest: pubkey_dest.decrypt_handle(&opening), + handle_auditor: pubkey_auditor.decrypt_handle(&opening), }; (transfer_amount_encryption, opening) @@ -55,9 +64,9 @@ impl TransferAmountEncryption { pub fn to_pod(&self) -> pod::TransferAmountEncryption { pod::TransferAmountEncryption { commitment: self.commitment.into(), - source: self.source.into(), - dest: self.dest.into(), - auditor: self.auditor.into(), + handle_source: self.handle_source.into(), + handle_dest: self.handle_dest.into(), + handle_auditor: self.handle_auditor.into(), } } } @@ -113,12 +122,12 @@ impl TransferData { let transfer_amount_lo_source = ElGamalCiphertext { commitment: ciphertext_lo.commitment, - handle: ciphertext_lo.source, + handle: ciphertext_lo.handle_source, }; let transfer_amount_hi_source = ElGamalCiphertext { commitment: ciphertext_hi.commitment, - handle: ciphertext_hi.source, + handle: ciphertext_hi.handle_source, }; let ciphertext_new_source = ciphertext_old_source @@ -126,9 +135,9 @@ impl TransferData { // generate transcript and append all public inputs let pod_transfer_pubkeys = pod::TransferPubkeys { - source: keypair_source.public.into(), - dest: (*pubkey_dest).into(), - auditor: (*pubkey_auditor).into(), + pubkey_source: keypair_source.public.into(), + pubkey_dest: (*pubkey_dest).into(), + pubkey_auditor: (*pubkey_auditor).into(), }; let pod_ciphertext_lo: pod::TransferAmountEncryption = ciphertext_lo.into(); let pod_ciphertext_hi: pod::TransferAmountEncryption = ciphertext_hi.into(); @@ -165,9 +174,9 @@ impl TransferData { let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?; let handle_lo = match role { - Role::Source => ciphertext_lo.source, - Role::Dest => ciphertext_lo.dest, - Role::Auditor => ciphertext_lo.auditor, + Role::Source => ciphertext_lo.handle_source, + Role::Dest => ciphertext_lo.handle_dest, + Role::Auditor => ciphertext_lo.handle_auditor, }; Ok(ElGamalCiphertext { @@ -181,9 +190,9 @@ impl TransferData { let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?; let handle_hi = match role { - Role::Source => ciphertext_hi.source, - Role::Dest => ciphertext_hi.dest, - Role::Auditor => ciphertext_hi.auditor, + Role::Source => ciphertext_hi.handle_source, + Role::Dest => ciphertext_hi.handle_dest, + Role::Auditor => ciphertext_hi.handle_auditor, }; Ok(ElGamalCiphertext { @@ -247,7 +256,7 @@ pub struct TransferProof { pub commitment_new_source: pod::PedersenCommitment, /// Associated equality proof - pub equality_proof: pod::EqualityProof, + pub equality_proof: pod::CtxtCommEqualityProof, /// Associated ciphertext validity proof pub validity_proof: pod::AggregatedValidityProof, @@ -267,19 +276,19 @@ impl TransferProof { ) -> Transcript { let mut transcript = Transcript::new(b"transfer-proof"); - transcript.append_pubkey(b"pubkey_source", &transfer_pubkeys.source); - transcript.append_pubkey(b"pubkey_dest", &transfer_pubkeys.dest); - transcript.append_pubkey(b"pubkey_auditor", &transfer_pubkeys.auditor); + transcript.append_pubkey(b"pubkey-source", &transfer_pubkeys.pubkey_source); + transcript.append_pubkey(b"pubkey-dest", &transfer_pubkeys.pubkey_dest); + transcript.append_pubkey(b"pubkey-auditor", &transfer_pubkeys.pubkey_auditor); transcript.append_commitment(b"comm-lo-amount", &ciphertext_lo.commitment); - transcript.append_handle(b"handle-lo-source", &ciphertext_lo.source); - transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.dest); - transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.auditor); + transcript.append_handle(b"handle-lo-source", &ciphertext_lo.handle_source); + transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.handle_dest); + transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.handle_auditor); transcript.append_commitment(b"comm-hi-amount", &ciphertext_hi.commitment); - transcript.append_handle(b"handle-hi-source", &ciphertext_hi.source); - transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.dest); - transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.auditor); + transcript.append_handle(b"handle-hi-source", &ciphertext_hi.handle_source); + transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.handle_dest); + transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.handle_auditor); transcript.append_ciphertext(b"ciphertext-new-source", ciphertext_new_source); @@ -302,7 +311,7 @@ impl TransferProof { transcript.append_commitment(b"commitment-new-source", &pod_commitment_new_source); // generate equality_proof - let equality_proof = EqualityProof::new( + let equality_proof = CtxtCommEqualityProof::new( keypair_source, ciphertext_new_source, source_new_balance, @@ -325,7 +334,11 @@ impl TransferProof { transfer_amount_lo as u64, transfer_amount_hi as u64, ], - vec![64, 32, 32], + vec![ + TRANSFER_SOURCE_AMOUNT_BIT_LENGTH, + TRANSFER_AMOUNT_LO_BIT_LENGTH, + TRANSFER_AMOUNT_HI_BIT_LENGTH, + ], vec![&opening_source, opening_lo, opening_hi], transcript, ); @@ -343,13 +356,13 @@ impl TransferProof { ciphertext_lo: &TransferAmountEncryption, ciphertext_hi: &TransferAmountEncryption, transfer_pubkeys: &TransferPubkeys, - new_spendable_ciphertext: &ElGamalCiphertext, + ciphertext_new_spendable: &ElGamalCiphertext, transcript: &mut Transcript, ) -> Result<(), ProofError> { transcript.append_commitment(b"commitment-new-source", &self.commitment_new_source); let commitment: PedersenCommitment = self.commitment_new_source.try_into()?; - let equality_proof: EqualityProof = self.equality_proof.try_into()?; + let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?; let aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?; let range_proof: RangeProof = self.range_proof.try_into()?; @@ -357,25 +370,24 @@ impl TransferProof { // // TODO: we can also consider verifying equality and range proof in a batch equality_proof.verify( - &transfer_pubkeys.source, - new_spendable_ciphertext, + &transfer_pubkeys.pubkey_source, + ciphertext_new_spendable, &commitment, transcript, )?; - println!("equality pass"); - // verify validity proof aggregated_validity_proof.verify( - (&transfer_pubkeys.dest, &transfer_pubkeys.auditor), + ( + &transfer_pubkeys.pubkey_dest, + &transfer_pubkeys.pubkey_auditor, + ), (&ciphertext_lo.commitment, &ciphertext_hi.commitment), - (&ciphertext_lo.dest, &ciphertext_hi.dest), - (&ciphertext_lo.auditor, &ciphertext_hi.auditor), + (&ciphertext_lo.handle_dest, &ciphertext_hi.handle_dest), + (&ciphertext_lo.handle_auditor, &ciphertext_hi.handle_auditor), transcript, )?; - println!("validity pass"); - // verify range proof let commitment_new_source = self.commitment_new_source.try_into()?; range_proof.verify( @@ -397,9 +409,9 @@ impl TransferProof { #[repr(C)] #[cfg(not(target_arch = "bpf"))] pub struct TransferPubkeys { - pub source: ElGamalPubkey, - pub dest: ElGamalPubkey, - pub auditor: ElGamalPubkey, + pub pubkey_source: ElGamalPubkey, + pub pubkey_dest: ElGamalPubkey, + pub pubkey_auditor: ElGamalPubkey, } #[cfg(not(target_arch = "bpf"))] @@ -407,24 +419,26 @@ impl TransferPubkeys { // TODO: use constructor instead pub fn to_bytes(&self) -> [u8; 96] { let mut bytes = [0u8; 96]; - bytes[..32].copy_from_slice(&self.source.to_bytes()); - bytes[32..64].copy_from_slice(&self.dest.to_bytes()); - bytes[64..96].copy_from_slice(&self.auditor.to_bytes()); + bytes[..32].copy_from_slice(&self.pubkey_source.to_bytes()); + bytes[32..64].copy_from_slice(&self.pubkey_dest.to_bytes()); + bytes[64..96].copy_from_slice(&self.pubkey_auditor.to_bytes()); bytes } pub fn from_bytes(bytes: &[u8]) -> Result { let bytes = array_ref![bytes, 0, 96]; - let (source, dest, auditor) = array_refs![bytes, 32, 32, 32]; + let (pubkey_source, pubkey_dest, pubkey_auditor) = array_refs![bytes, 32, 32, 32]; - let source = ElGamalPubkey::from_bytes(source).ok_or(ProofError::Verification)?; - let dest = ElGamalPubkey::from_bytes(dest).ok_or(ProofError::Verification)?; - let auditor = ElGamalPubkey::from_bytes(auditor).ok_or(ProofError::Verification)?; + let pubkey_source = + ElGamalPubkey::from_bytes(pubkey_source).ok_or(ProofError::Verification)?; + let pubkey_dest = ElGamalPubkey::from_bytes(pubkey_dest).ok_or(ProofError::Verification)?; + let pubkey_auditor = + ElGamalPubkey::from_bytes(pubkey_auditor).ok_or(ProofError::Verification)?; Ok(Self { - source, - dest, - auditor, + pubkey_source, + pubkey_dest, + pubkey_auditor, }) } } diff --git a/zk-token-sdk/src/instruction/transfer_with_fee.rs b/zk-token-sdk/src/instruction/transfer_with_fee.rs index 1c16f115f3..8261f209da 100644 --- a/zk-token-sdk/src/instruction/transfer_with_fee.rs +++ b/zk-token-sdk/src/instruction/transfer_with_fee.rs @@ -19,7 +19,7 @@ use { }, range_proof::RangeProof, sigma_proofs::{ - equality_proof::EqualityProof, + equality_proof::CtxtCommEqualityProof, fee_proof::FeeSigmaProof, validity_proof::{AggregatedValidityProof, ValidityProof}, }, @@ -33,11 +33,20 @@ use { }; #[cfg(not(target_arch = "bpf"))] -const FEE_DENOMINATOR: u64 = 10000; +const MAX_FEE_BASIS_POINTS: u64 = 10000; + +#[cfg(not(target_arch = "bpf"))] +const TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH: usize = 64; +#[cfg(not(target_arch = "bpf"))] +const TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH: usize = 32; +#[cfg(not(target_arch = "bpf"))] +const TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH: usize = 32; +#[cfg(not(target_arch = "bpf"))] +const TRANSFER_WITH_FEE_DELTA_BIT_LENGTH: usize = 64; #[cfg(not(target_arch = "bpf"))] lazy_static::lazy_static! { - pub static ref COMMITMENT_FEE_DENOMINATOR: PedersenCommitment = Pedersen::encode(FEE_DENOMINATOR); + pub static ref COMMITMENT_MAX_FEE_BASIS_POINTS: PedersenCommitment = Pedersen::encode(MAX_FEE_BASIS_POINTS); } // #[derive(Clone, Copy, Pod, Zeroable)] @@ -74,7 +83,7 @@ impl TransferWithFeeData { keypair_source: &ElGamalKeypair, (pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey), fee_parameters: FeeParameters, - pubkey_fee_collector: &ElGamalPubkey, + pubkey_withdraw_withheld_authority: &ElGamalPubkey, ) -> Result { // split and encrypt transfer amount let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount); @@ -99,12 +108,12 @@ impl TransferWithFeeData { let transfer_amount_lo_source = ElGamalCiphertext { commitment: ciphertext_lo.commitment, - handle: ciphertext_lo.source, + handle: ciphertext_lo.handle_source, }; let transfer_amount_hi_source = ElGamalCiphertext { commitment: ciphertext_hi.commitment, - handle: ciphertext_hi.source, + handle: ciphertext_hi.handle_source, }; let ciphertext_new_source = ciphertext_old_source @@ -119,15 +128,18 @@ impl TransferWithFeeData { u64::conditional_select(&fee_parameters.maximum_fee, &fee_amount, below_max); // u64::conditional_select(&fee_amount, &fee_parameters.maximum_fee, below_max); - let (ciphertext_fee, opening_fee) = - FeeEncryption::new(fee_to_encrypt, pubkey_dest, pubkey_fee_collector); + let (ciphertext_fee, opening_fee) = FeeEncryption::new( + fee_to_encrypt, + pubkey_dest, + pubkey_withdraw_withheld_authority, + ); // generate transcript and append all public inputs let pod_transfer_with_fee_pubkeys = pod::TransferWithFeePubkeys { - source: keypair_source.public.into(), - dest: (*pubkey_dest).into(), - auditor: (*pubkey_auditor).into(), - fee_collector: (*pubkey_fee_collector).into(), + pubkey_source: keypair_source.public.into(), + pubkey_dest: (*pubkey_dest).into(), + pubkey_auditor: (*pubkey_auditor).into(), + pubkey_withdraw_withheld_authority: (*pubkey_withdraw_withheld_authority).into(), }; let pod_ciphertext_lo: pod::TransferAmountEncryption = ciphertext_lo.to_pod(); let pod_ciphertext_hi: pod::TransferAmountEncryption = ciphertext_hi.to_pod(); @@ -150,7 +162,7 @@ impl TransferWithFeeData { (new_spendable_balance, &ciphertext_new_source), (fee_amount, &ciphertext_fee, &opening_fee), delta_fee, - pubkey_fee_collector, + pubkey_withdraw_withheld_authority, fee_parameters, &mut transcript, ); @@ -171,9 +183,9 @@ impl TransferWithFeeData { let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?; let handle_lo = match role { - Role::Source => ciphertext_lo.source, - Role::Dest => ciphertext_lo.dest, - Role::Auditor => ciphertext_lo.auditor, + Role::Source => ciphertext_lo.handle_source, + Role::Dest => ciphertext_lo.handle_dest, + Role::Auditor => ciphertext_lo.handle_auditor, }; Ok(ElGamalCiphertext { @@ -187,9 +199,9 @@ impl TransferWithFeeData { let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?; let handle_hi = match role { - Role::Source => ciphertext_hi.source, - Role::Dest => ciphertext_hi.dest, - Role::Auditor => ciphertext_hi.auditor, + Role::Source => ciphertext_hi.handle_source, + Role::Dest => ciphertext_hi.handle_dest, + Role::Auditor => ciphertext_hi.handle_auditor, }; Ok(ElGamalCiphertext { @@ -256,7 +268,7 @@ impl Verifiable for TransferWithFeeData { pub struct TransferWithFeeProof { pub commitment_new_source: pod::PedersenCommitment, pub commitment_claimed: pod::PedersenCommitment, - pub equality_proof: pod::EqualityProof, + pub equality_proof: pod::CtxtCommEqualityProof, pub ciphertext_amount_validity_proof: pod::AggregatedValidityProof, pub fee_sigma_proof: pod::FeeSigmaProof, pub ciphertext_fee_validity_proof: pod::ValidityProof, @@ -275,29 +287,32 @@ impl TransferWithFeeProof { ) -> Transcript { let mut transcript = Transcript::new(b"FeeProof"); - transcript.append_pubkey(b"pubkey_source", &transfer_with_fee_pubkeys.source); - transcript.append_pubkey(b"pubkey_dest", &transfer_with_fee_pubkeys.dest); - transcript.append_pubkey(b"pubkey_auditor", &transfer_with_fee_pubkeys.auditor); + transcript.append_pubkey(b"pubkey-source", &transfer_with_fee_pubkeys.pubkey_source); + transcript.append_pubkey(b"pubkey-dest", &transfer_with_fee_pubkeys.pubkey_dest); + transcript.append_pubkey(b"pubkey-auditor", &transfer_with_fee_pubkeys.pubkey_auditor); transcript.append_pubkey( - b"pubkey_fee_collector", - &transfer_with_fee_pubkeys.fee_collector, + b"pubkey_withdraw_withheld_authority", + &transfer_with_fee_pubkeys.pubkey_withdraw_withheld_authority, ); transcript.append_commitment(b"comm-lo-amount", &ciphertext_lo.commitment); - transcript.append_handle(b"handle-lo-source", &ciphertext_lo.source); - transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.dest); - transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.auditor); + transcript.append_handle(b"handle-lo-source", &ciphertext_lo.handle_source); + transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.handle_dest); + transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.handle_auditor); transcript.append_commitment(b"comm-hi-amount", &ciphertext_hi.commitment); - transcript.append_handle(b"handle-hi-source", &ciphertext_hi.source); - transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.dest); - transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.auditor); + transcript.append_handle(b"handle-hi-source", &ciphertext_hi.handle_source); + transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.handle_dest); + transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.handle_auditor); transcript.append_ciphertext(b"ctxt-new-source", ciphertext_new_source); transcript.append_commitment(b"comm-fee", &ciphertext_fee.commitment); - transcript.append_handle(b"handle-fee-dest", &ciphertext_fee.dest); - transcript.append_handle(b"handle-fee-auditor", &ciphertext_fee.fee_collector); + transcript.append_handle(b"fee-dest-handle", &ciphertext_fee.handle_dest); + transcript.append_handle( + b"handle-fee-auditor", + &ciphertext_fee.handle_withdraw_withheld_authority, + ); transcript } @@ -313,7 +328,7 @@ impl TransferWithFeeProof { (fee_amount, ciphertext_fee, opening_fee): (u64, &FeeEncryption, &PedersenOpening), delta_fee: u64, - pubkey_fee_collector: &ElGamalPubkey, + pubkey_withdraw_withheld_authority: &ElGamalPubkey, fee_parameters: FeeParameters, transcript: &mut Transcript, ) -> Self { @@ -331,7 +346,7 @@ impl TransferWithFeeProof { transcript.append_commitment(b"commitment-claimed", &pod_commitment_claimed); // generate equality_proof - let equality_proof = EqualityProof::new( + let equality_proof = CtxtCommEqualityProof::new( keypair_source, ciphertext_new_source, source_new_balance, @@ -363,7 +378,7 @@ impl TransferWithFeeProof { ); let ciphertext_fee_validity_proof = ValidityProof::new( - (pubkey_dest, pubkey_fee_collector), + (pubkey_dest, pubkey_withdraw_withheld_authority), fee_amount, opening_fee, transcript, @@ -376,11 +391,14 @@ impl TransferWithFeeProof { transfer_amount_lo as u64, transfer_amount_hi as u64, delta_fee, - FEE_DENOMINATOR - delta_fee, + MAX_FEE_BASIS_POINTS - delta_fee, ], vec![ - 64, 32, 32, 64, // double check - 64, + TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH, + TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH, + TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH, + TRANSFER_WITH_FEE_DELTA_BIT_LENGTH, + TRANSFER_WITH_FEE_DELTA_BIT_LENGTH, ], vec![ &opening_source, @@ -420,7 +438,7 @@ impl TransferWithFeeProof { let commitment_new_source: PedersenCommitment = self.commitment_new_source.try_into()?; let commitment_claimed: PedersenCommitment = self.commitment_claimed.try_into()?; - let equality_proof: EqualityProof = self.equality_proof.try_into()?; + let equality_proof: CtxtCommEqualityProof = 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()?; @@ -430,7 +448,7 @@ impl TransferWithFeeProof { // verify equality proof equality_proof.verify( - &transfer_with_fee_pubkeys.source, + &transfer_with_fee_pubkeys.pubkey_source, new_spendable_ciphertext, &commitment_new_source, transcript, @@ -439,12 +457,12 @@ impl TransferWithFeeProof { // verify that the transfer amount is encrypted correctly ciphertext_amount_validity_proof.verify( ( - &transfer_with_fee_pubkeys.dest, - &transfer_with_fee_pubkeys.auditor, + &transfer_with_fee_pubkeys.pubkey_dest, + &transfer_with_fee_pubkeys.pubkey_auditor, ), (&ciphertext_lo.commitment, &ciphertext_hi.commitment), - (&ciphertext_lo.dest, &ciphertext_hi.dest), - (&ciphertext_lo.auditor, &ciphertext_hi.auditor), + (&ciphertext_lo.handle_dest, &ciphertext_hi.handle_dest), + (&ciphertext_lo.handle_auditor, &ciphertext_hi.handle_auditor), transcript, )?; @@ -467,14 +485,17 @@ impl TransferWithFeeProof { ciphertext_fee_validity_proof.verify( &ciphertext_fee.commitment, ( - &transfer_with_fee_pubkeys.dest, - &transfer_with_fee_pubkeys.fee_collector, + &transfer_with_fee_pubkeys.pubkey_dest, + &transfer_with_fee_pubkeys.pubkey_withdraw_withheld_authority, + ), + ( + &ciphertext_fee.handle_dest, + &ciphertext_fee.handle_withdraw_withheld_authority, ), - (&ciphertext_fee.dest, &ciphertext_fee.fee_collector), transcript, )?; - let commitment_claimed_negated = &(*COMMITMENT_FEE_DENOMINATOR) - &commitment_claimed; + let commitment_claimed_negated = &(*COMMITMENT_MAX_FEE_BASIS_POINTS) - &commitment_claimed; range_proof.verify( vec![ &commitment_new_source, @@ -496,38 +517,42 @@ impl TransferWithFeeProof { #[repr(C)] #[cfg(not(target_arch = "bpf"))] pub struct TransferWithFeePubkeys { - pub source: ElGamalPubkey, - pub dest: ElGamalPubkey, - pub auditor: ElGamalPubkey, - pub fee_collector: ElGamalPubkey, + pub pubkey_source: ElGamalPubkey, + pub pubkey_dest: ElGamalPubkey, + pub pubkey_auditor: ElGamalPubkey, + pub pubkey_withdraw_withheld_authority: ElGamalPubkey, } #[cfg(not(target_arch = "bpf"))] impl TransferWithFeePubkeys { pub fn to_bytes(&self) -> [u8; 128] { let mut bytes = [0u8; 128]; - bytes[..32].copy_from_slice(&self.source.to_bytes()); - bytes[32..64].copy_from_slice(&self.dest.to_bytes()); - bytes[64..96].copy_from_slice(&self.auditor.to_bytes()); - bytes[96..128].copy_from_slice(&self.fee_collector.to_bytes()); + bytes[..32].copy_from_slice(&self.pubkey_source.to_bytes()); + bytes[32..64].copy_from_slice(&self.pubkey_dest.to_bytes()); + bytes[64..96].copy_from_slice(&self.pubkey_auditor.to_bytes()); + bytes[96..128].copy_from_slice(&self.pubkey_withdraw_withheld_authority.to_bytes()); bytes } pub fn from_bytes(bytes: &[u8]) -> Result { let bytes = array_ref![bytes, 0, 128]; - let (source, dest, auditor, fee_collector) = array_refs![bytes, 32, 32, 32, 32]; + let (pubkey_source, pubkey_dest, pubkey_auditor, pubkey_withdraw_withheld_authority) = + array_refs![bytes, 32, 32, 32, 32]; - let source = ElGamalPubkey::from_bytes(source).ok_or(ProofError::Verification)?; - let dest = ElGamalPubkey::from_bytes(dest).ok_or(ProofError::Verification)?; - let auditor = ElGamalPubkey::from_bytes(auditor).ok_or(ProofError::Verification)?; - let fee_collector = - ElGamalPubkey::from_bytes(fee_collector).ok_or(ProofError::Verification)?; + let pubkey_source = + ElGamalPubkey::from_bytes(pubkey_source).ok_or(ProofError::Verification)?; + let pubkey_dest = ElGamalPubkey::from_bytes(pubkey_dest).ok_or(ProofError::Verification)?; + let pubkey_auditor = + ElGamalPubkey::from_bytes(pubkey_auditor).ok_or(ProofError::Verification)?; + let pubkey_withdraw_withheld_authority = + ElGamalPubkey::from_bytes(pubkey_withdraw_withheld_authority) + .ok_or(ProofError::Verification)?; Ok(Self { - source, - dest, - auditor, - fee_collector, + pubkey_source, + pubkey_dest, + pubkey_auditor, + pubkey_withdraw_withheld_authority, }) } } @@ -537,8 +562,8 @@ impl TransferWithFeePubkeys { #[cfg(not(target_arch = "bpf"))] pub struct FeeEncryption { pub commitment: PedersenCommitment, - pub dest: DecryptHandle, - pub fee_collector: DecryptHandle, + pub handle_dest: DecryptHandle, + pub handle_withdraw_withheld_authority: DecryptHandle, } #[cfg(not(target_arch = "bpf"))] @@ -546,13 +571,13 @@ impl FeeEncryption { pub fn new( amount: u64, pubkey_dest: &ElGamalPubkey, - pubkey_fee_collector: &ElGamalPubkey, + pubkey_withdraw_withheld: &ElGamalPubkey, ) -> (Self, PedersenOpening) { let (commitment, opening) = Pedersen::new(amount); let fee_encryption = Self { commitment, - dest: pubkey_dest.decrypt_handle(&opening), - fee_collector: pubkey_fee_collector.decrypt_handle(&opening), + handle_dest: pubkey_dest.decrypt_handle(&opening), + handle_withdraw_withheld_authority: pubkey_withdraw_withheld.decrypt_handle(&opening), }; (fee_encryption, opening) @@ -561,8 +586,8 @@ impl FeeEncryption { pub fn to_pod(&self) -> pod::FeeEncryption { pod::FeeEncryption { commitment: self.commitment.into(), - dest: self.dest.into(), - fee_collector: self.fee_collector.into(), + handle_dest: self.handle_dest.into(), + handle_withdraw_withheld_authority: self.handle_withdraw_withheld_authority.into(), } } } @@ -601,8 +626,8 @@ impl FeeParameters { fn calculate_fee(transfer_amount: u64, fee_rate_basis_points: u16) -> (u64, u64) { let fee_scaled = (transfer_amount as u128) * (fee_rate_basis_points as u128); - let fee = (fee_scaled / FEE_DENOMINATOR as u128) as u64; - let rem = (fee_scaled % FEE_DENOMINATOR as u128) as u64; + let fee = (fee_scaled / MAX_FEE_BASIS_POINTS as u128) as u64; + let rem = (fee_scaled % MAX_FEE_BASIS_POINTS as u128) as u64; if rem == 0 { (fee, rem) @@ -620,10 +645,10 @@ fn compute_delta_commitment_and_opening( ) -> (PedersenCommitment, PedersenOpening) { let fee_rate_scalar = Scalar::from(fee_rate_basis_points); - let commitment_delta = commitment_fee * Scalar::from(FEE_DENOMINATOR) + let commitment_delta = commitment_fee * Scalar::from(MAX_FEE_BASIS_POINTS) - &(&combine_u32_commitments(commitment_lo, commitment_hi) * &fee_rate_scalar); - let opening_delta = opening_fee * Scalar::from(FEE_DENOMINATOR) + let opening_delta = opening_fee * Scalar::from(MAX_FEE_BASIS_POINTS) - &(&combine_u32_openings(opening_lo, opening_hi) * &fee_rate_scalar); (commitment_delta, opening_delta) @@ -638,7 +663,7 @@ fn compute_delta_commitment( ) -> PedersenCommitment { let fee_rate_scalar = Scalar::from(fee_rate_basis_points); - commitment_fee * Scalar::from(FEE_DENOMINATOR) + commitment_fee * Scalar::from(MAX_FEE_BASIS_POINTS) - &(&combine_u32_commitments(commitment_lo, commitment_hi) * &fee_rate_scalar) } @@ -651,7 +676,7 @@ mod test { let keypair_source = ElGamalKeypair::new_rand(); let pubkey_dest = ElGamalKeypair::new_rand().public; let pubkey_auditor = ElGamalKeypair::new_rand().public; - let pubkey_fee_collector = ElGamalKeypair::new_rand().public; + let pubkey_withdraw_withheld_authority = ElGamalKeypair::new_rand().public; let spendable_balance: u64 = 120; let spendable_ciphertext = keypair_source.public.encrypt(spendable_balance); @@ -669,7 +694,7 @@ mod test { &keypair_source, (&pubkey_dest, &pubkey_auditor), fee_parameters, - &pubkey_fee_collector, + &pubkey_withdraw_withheld_authority, ) .unwrap(); diff --git a/zk-token-sdk/src/instruction/withdraw.rs b/zk-token-sdk/src/instruction/withdraw.rs index 2feee099e0..d6c9d1a047 100644 --- a/zk-token-sdk/src/instruction/withdraw.rs +++ b/zk-token-sdk/src/instruction/withdraw.rs @@ -12,13 +12,16 @@ use { errors::ProofError, instruction::Verifiable, range_proof::RangeProof, - sigma_proofs::equality_proof::EqualityProof, + sigma_proofs::equality_proof::CtxtCommEqualityProof, transcript::TranscriptProtocol, }, merlin::Transcript, std::convert::TryInto, }; +#[cfg(not(target_arch = "bpf"))] +const WITHDRAW_AMOUNT_BIT_LENGTH: usize = 64; + /// This struct includes the cryptographic proof *and* the account data information needed to verify /// the proof /// @@ -94,7 +97,7 @@ pub struct WithdrawProof { pub commitment: pod::PedersenCommitment, /// Associated equality proof - pub equality_proof: pod::EqualityProof, + pub equality_proof: pod::CtxtCommEqualityProof, /// Associated range proof pub range_proof: pod::RangeProof64, // 672 bytes @@ -128,7 +131,7 @@ impl WithdrawProof { transcript.append_commitment(b"commitment", &pod_commitment); // generate equality_proof - let equality_proof = EqualityProof::new( + let equality_proof = CtxtCommEqualityProof::new( keypair, final_ciphertext, final_balance, @@ -139,7 +142,7 @@ impl WithdrawProof { let range_proof = RangeProof::new(vec![final_balance], vec![64], vec![&opening], transcript); - WithdrawProof { + Self { commitment: pod_commitment, equality_proof: equality_proof.try_into().expect("equality proof"), range_proof: range_proof.try_into().expect("range proof"), @@ -155,7 +158,7 @@ impl WithdrawProof { transcript.append_commitment(b"commitment", &self.commitment); let commitment: PedersenCommitment = self.commitment.try_into()?; - let equality_proof: EqualityProof = self.equality_proof.try_into()?; + let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?; let range_proof: RangeProof = self.range_proof.try_into()?; // verify equality proof @@ -166,7 +169,11 @@ impl WithdrawProof { // verify range proof // // TODO: double compressing here - consider modifying range proof input type to `PedersenCommitment` - range_proof.verify(vec![&commitment], vec![64_usize], transcript)?; + range_proof.verify( + vec![&commitment], + vec![WITHDRAW_AMOUNT_BIT_LENGTH], + transcript, + )?; Ok(()) } diff --git a/zk-token-sdk/src/instruction/withdraw_withheld.rs b/zk-token-sdk/src/instruction/withdraw_withheld.rs new file mode 100644 index 0000000000..d748668442 --- /dev/null +++ b/zk-token-sdk/src/instruction/withdraw_withheld.rs @@ -0,0 +1,214 @@ +use { + crate::zk_token_elgamal::pod, + bytemuck::{Pod, Zeroable}, +}; +#[cfg(not(target_arch = "bpf"))] +use { + crate::{ + encryption::{ + elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + pedersen::PedersenOpening, + }, + errors::ProofError, + instruction::Verifiable, + sigma_proofs::equality_proof::CtxtCtxtEqualityProof, + transcript::TranscriptProtocol, + }, + merlin::Transcript, + std::convert::TryInto, +}; + +/// 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 +/// +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct WithdrawWithheldTokensData { + pub pubkey_withdraw_withheld_authority: pod::ElGamalPubkey, + + pub pubkey_dest: pod::ElGamalPubkey, + + pub ciphertext_withdraw_withheld_authority: pod::ElGamalCiphertext, + + pub ciphertext_dest: pod::ElGamalCiphertext, + + pub proof: WithdrawWithheldTokensProof, +} + +impl WithdrawWithheldTokensData { + #[cfg(not(target_arch = "bpf"))] + pub fn new( + keypair_withdraw_withheld_authority: &ElGamalKeypair, + pubkey_dest: &ElGamalPubkey, + ciphertext_withdraw_withheld_authority: &ElGamalCiphertext, + amount: u64, + ) -> Result { + let opening_dest = PedersenOpening::new_rand(); + let ciphertext_dest = pubkey_dest.encrypt_with(amount, &opening_dest); + + let pod_pubkey_withdraw_withheld_authority = + pod::ElGamalPubkey(keypair_withdraw_withheld_authority.public.to_bytes()); + let pod_pubkey_dest = pod::ElGamalPubkey(pubkey_dest.to_bytes()); + let pod_ciphertext_withdraw_withheld_authority = + pod::ElGamalCiphertext(ciphertext_withdraw_withheld_authority.to_bytes()); + let pod_ciphertext_dest = pod::ElGamalCiphertext(ciphertext_dest.to_bytes()); + + let mut transcript = WithdrawWithheldTokensProof::transcript_new( + &pod_pubkey_withdraw_withheld_authority, + &pod_pubkey_dest, + &pod_ciphertext_withdraw_withheld_authority, + &pod_ciphertext_dest, + ); + + let proof = WithdrawWithheldTokensProof::new( + keypair_withdraw_withheld_authority, + pubkey_dest, + ciphertext_withdraw_withheld_authority, + amount, + &opening_dest, + &mut transcript, + ); + + Ok(Self { + pubkey_withdraw_withheld_authority: pod_pubkey_withdraw_withheld_authority, + pubkey_dest: pod_pubkey_dest, + ciphertext_withdraw_withheld_authority: pod_ciphertext_withdraw_withheld_authority, + ciphertext_dest: pod_ciphertext_dest, + proof, + }) + } +} + +#[cfg(not(target_arch = "bpf"))] +impl Verifiable for WithdrawWithheldTokensData { + fn verify(&self) -> Result<(), ProofError> { + let mut transcript = WithdrawWithheldTokensProof::transcript_new( + &self.pubkey_withdraw_withheld_authority, + &self.pubkey_dest, + &self.ciphertext_withdraw_withheld_authority, + &self.ciphertext_dest, + ); + + let pubkey_withdraw_withheld_authority = + self.pubkey_withdraw_withheld_authority.try_into()?; + let pubkey_dest = self.pubkey_dest.try_into()?; + let ciphertext_withdraw_withheld_authority = + self.ciphertext_withdraw_withheld_authority.try_into()?; + let ciphertext_dest = self.ciphertext_dest.try_into()?; + + self.proof.verify( + &pubkey_withdraw_withheld_authority, + &pubkey_dest, + &ciphertext_withdraw_withheld_authority, + &ciphertext_dest, + &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_arch = "bpf"))] +impl WithdrawWithheldTokensProof { + fn transcript_new( + pubkey_withdraw_withheld_authority: &pod::ElGamalPubkey, + pubkey_dest: &pod::ElGamalPubkey, + ciphertext_withdraw_withheld_authority: &pod::ElGamalCiphertext, + ciphertext_dest: &pod::ElGamalCiphertext, + ) -> Transcript { + let mut transcript = Transcript::new(b"WithdrawWithheldTokensProof"); + + transcript.append_pubkey( + b"withdraw-withheld-authority-pubkey", + pubkey_withdraw_withheld_authority, + ); + transcript.append_pubkey(b"dest-pubkey", pubkey_dest); + + transcript.append_ciphertext( + b"ciphertext-withdraw-withheld-authority", + ciphertext_withdraw_withheld_authority, + ); + transcript.append_ciphertext(b"ciphertext-dest", ciphertext_dest); + + transcript + } + + pub fn new( + keypair_withdraw_withheld_authority: &ElGamalKeypair, + pubkey_dest: &ElGamalPubkey, + ciphertext_withdraw_withheld_authority: &ElGamalCiphertext, + amount: u64, + opening_dest: &PedersenOpening, + transcript: &mut Transcript, + ) -> Self { + let equality_proof = CtxtCtxtEqualityProof::new( + keypair_withdraw_withheld_authority, + pubkey_dest, + ciphertext_withdraw_withheld_authority, + amount, + opening_dest, + transcript, + ); + + Self { + proof: equality_proof.into(), + } + } + + pub fn verify( + &self, + pubkey_source: &ElGamalPubkey, + pubkey_dest: &ElGamalPubkey, + ciphertext_source: &ElGamalCiphertext, + ciphertext_dest: &ElGamalCiphertext, + transcript: &mut Transcript, + ) -> Result<(), ProofError> { + let proof: CtxtCtxtEqualityProof = self.proof.try_into()?; + proof.verify( + pubkey_source, + pubkey_dest, + ciphertext_source, + ciphertext_dest, + transcript, + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_close_account_correctness() { + let keypair_withdraw_withheld_authority = ElGamalKeypair::new_rand(); + let keypair_dest = ElGamalKeypair::new_rand(); + + let amount: u64 = 55; + let ciphertext_withdraw_withheld_authority = + keypair_withdraw_withheld_authority.public.encrypt(amount); + + let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new( + &keypair_withdraw_withheld_authority, + &keypair_dest.public, + &ciphertext_withdraw_withheld_authority, + amount, + ) + .unwrap(); + + assert!(withdraw_withheld_tokens_data.verify().is_ok()); + } +} diff --git a/zk-token-sdk/src/sigma_proofs/equality_proof.rs b/zk-token-sdk/src/sigma_proofs/equality_proof.rs index 3b5fbeeef1..0c90660f6c 100644 --- a/zk-token-sdk/src/sigma_proofs/equality_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/equality_proof.rs @@ -5,6 +5,8 @@ //! 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 +//! //! The protocol guarantees computationally soundness (by the hardness of discrete log) and perfect //! zero-knowledge in the random oracle model. @@ -34,7 +36,7 @@ use { /// Contains all the elliptic curve and scalar components that make up the sigma protocol. #[allow(non_snake_case)] #[derive(Clone)] -pub struct EqualityProof { +pub struct CtxtCommEqualityProof { Y_0: CompressedRistretto, Y_1: CompressedRistretto, Y_2: CompressedRistretto, @@ -45,7 +47,7 @@ pub struct EqualityProof { #[allow(non_snake_case)] #[cfg(not(target_arch = "bpf"))] -impl EqualityProof { +impl CtxtCommEqualityProof { /// Equality proof constructor. /// /// The function does *not* hash the public key, ciphertext, or commitment into the transcript. @@ -63,8 +65,8 @@ impl EqualityProof { /// * `opening` - The opening associated with the main Pedersen commitment to be proved /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic pub fn new( - elgamal_keypair: &ElGamalKeypair, - ciphertext: &ElGamalCiphertext, + keypair_source: &ElGamalKeypair, + ciphertext_source: &ElGamalCiphertext, amount: u64, opening: &PedersenOpening, transcript: &mut Transcript, @@ -72,10 +74,10 @@ impl EqualityProof { transcript.equality_proof_domain_sep(); // extract the relevant scalar and Ristretto points from the inputs - let P_EG = elgamal_keypair.public.get_point(); - let D_EG = ciphertext.handle.get_point(); + let P_source = keypair_source.public.get_point(); + let D_source = ciphertext_source.handle.get_point(); - let s = elgamal_keypair.secret.get_scalar(); + let s = keypair_source.secret.get_scalar(); let x = Scalar::from(amount); let r = opening.get_scalar(); @@ -84,8 +86,9 @@ impl EqualityProof { let mut y_x = Scalar::random(&mut OsRng); let mut y_r = Scalar::random(&mut OsRng); - let Y_0 = (&y_s * P_EG).compress(); - let Y_1 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&(*G), D_EG]).compress(); + 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(); // record masking factors in the transcript @@ -106,7 +109,7 @@ impl EqualityProof { y_x.zeroize(); y_r.zeroize(); - EqualityProof { + CtxtCommEqualityProof { Y_0, Y_1, Y_2, @@ -116,7 +119,7 @@ impl EqualityProof { } } - /// Equality proof verifier. + /// Equality proof verifier. TODO: wrt commitment /// /// * `elgamal_pubkey` - The ElGamal pubkey associated with the ciphertext to be proved /// * `ciphertext` - The main ElGamal ciphertext to be proved @@ -124,18 +127,18 @@ impl EqualityProof { /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic pub fn verify( self, - elgamal_pubkey: &ElGamalPubkey, - ciphertext: &ElGamalCiphertext, - commitment: &PedersenCommitment, + pubkey_source: &ElGamalPubkey, + ciphertext_source: &ElGamalCiphertext, + commitment_dest: &PedersenCommitment, transcript: &mut Transcript, ) -> Result<(), EqualityProofError> { transcript.equality_proof_domain_sep(); // extract the relevant scalar and Ristretto points from the inputs - let P_EG = elgamal_pubkey.get_point(); - let C_EG = ciphertext.commitment.get_point(); - let D_EG = ciphertext.handle.get_point(); - let C_Ped = commitment.get_point(); + let P_source = pubkey_source.get_point(); + let C_source = ciphertext_source.commitment.get_point(); + let D_source = ciphertext_source.handle.get_point(); + let C_dest = commitment_dest.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)?; @@ -169,17 +172,17 @@ impl EqualityProof { &ww_negated, // -ww ], vec![ - P_EG, // P_EG - &(*H), // H - &Y_0, // Y_0 - &(*G), // G - D_EG, // D_EG - C_EG, // C_EG - &Y_1, // Y_1 - &(*G), // G - &(*H), // H - C_Ped, // C_Ped - &Y_2, // Y_2 + 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_dest, // C_dest + &Y_2, // Y_2 ], ); @@ -213,7 +216,7 @@ impl EqualityProof { let z_x = Scalar::from_canonical_bytes(*z_x).ok_or(EqualityProofError::Format)?; let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(EqualityProofError::Format)?; - Ok(EqualityProof { + Ok(CtxtCommEqualityProof { Y_0, Y_1, Y_2, @@ -224,71 +227,290 @@ impl EqualityProof { } } +/// 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_arch = "bpf"))] +impl CtxtCtxtEqualityProof { + /// 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. + /// + /// Note that the proof constructor does not take the actual Pedersen commitment as input; it + /// takes the associated Pedersen opening instead. + /// + /// * `elgamal_keypair` - The ElGamal keypair associated with the ciphertext to be proved + /// * `ciphertext` - The main ElGamal ciphertext to be proved + /// * `amount` - The message associated with the ElGamal ciphertext and Pedersen commitment + /// * `opening` - The opening associated with the main Pedersen commitment to be proved + /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic + pub fn new( + keypair_source: &ElGamalKeypair, + pubkey_dest: &ElGamalPubkey, + ciphertext_source: &ElGamalCiphertext, + amount: u64, + opening_dest: &PedersenOpening, + transcript: &mut Transcript, + ) -> Self { + transcript.equality_proof_domain_sep(); + + // extract the relevant scalar and Ristretto points from the inputs + let P_source = keypair_source.public.get_point(); + let D_source = ciphertext_source.handle.get_point(); + let P_dest = pubkey_dest.get_point(); + + let s = keypair_source.secret.get_scalar(); + let x = Scalar::from(amount); + let r = opening_dest.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_dest).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. TODO: wrt commitment + /// + /// * `elgamal_pubkey` - The ElGamal pubkey associated with the ciphertext to be proved + /// * `ciphertext` - The main ElGamal ciphertext to be proved + /// * `commitment` - The main Pedersen commitment to be proved + /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic + pub fn verify( + self, + pubkey_source: &ElGamalPubkey, + pubkey_dest: &ElGamalPubkey, + ciphertext_source: &ElGamalCiphertext, + ciphertext_dest: &ElGamalCiphertext, + transcript: &mut Transcript, + ) -> Result<(), EqualityProofError> { + transcript.equality_proof_domain_sep(); + + // extract the relevant scalar and Ristretto points from the inputs + let P_source = pubkey_source.get_point(); + let C_source = ciphertext_source.commitment.get_point(); + let D_source = ciphertext_source.handle.get_point(); + + let P_dest = pubkey_dest.get_point(); + let C_dest = ciphertext_dest.commitment.get_point(); + let D_dest = ciphertext_dest.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(EqualityProofError::Format)?; + let Y_1 = self.Y_1.decompress().ok_or(EqualityProofError::Format)?; + let Y_2 = self.Y_2.decompress().ok_or(EqualityProofError::Format)?; + let Y_3 = self.Y_3.decompress().ok_or(EqualityProofError::Format)?; + + 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_dest, // C_dest + &Y_2, // Y_2 + P_dest, // P_dest + D_dest, // D_dest + &Y_3, // Y_3 + ], + ); + + if check.is_identity() { + Ok(()) + } else { + Err(EqualityProofError::AlgebraicRelation) + } + } + + 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 { + 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(EqualityProofError::Format)?; + let z_x = Scalar::from_canonical_bytes(*z_x).ok_or(EqualityProofError::Format)?; + let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(EqualityProofError::Format)?; + + Ok(CtxtCtxtEqualityProof { + Y_0, + Y_1, + Y_2, + Y_3, + z_s, + z_x, + z_r, + }) + } +} + #[cfg(test)] mod test { use super::*; use crate::encryption::{elgamal::ElGamalSecretKey, pedersen::Pedersen}; #[test] - fn test_equality_proof_correctness() { + fn test_ciphertext_commitment_equality_proof_correctness() { // success case - let elgamal_keypair = ElGamalKeypair::new_rand(); + let keypair_source = ElGamalKeypair::new_rand(); let message: u64 = 55; - let ciphertext = elgamal_keypair.public.encrypt(message); - let (commitment, opening) = Pedersen::new(message); + let ciphertext_source = keypair_source.public.encrypt(message); + let (commitment_dest, opening_dest) = Pedersen::new(message); let mut transcript_prover = Transcript::new(b"Test"); let mut transcript_verifier = Transcript::new(b"Test"); - let proof = EqualityProof::new( - &elgamal_keypair, - &ciphertext, + let proof = CtxtCommEqualityProof::new( + &keypair_source, + &ciphertext_source, message, - &opening, + &opening_dest, &mut transcript_prover, ); assert!(proof .verify( - &elgamal_keypair.public, - &ciphertext, - &commitment, + &keypair_source.public, + &ciphertext_source, + &commitment_dest, &mut transcript_verifier ) .is_ok()); // fail case: encrypted and committed messages are different - let elgamal_keypair = ElGamalKeypair::new_rand(); + let keypair_source = ElGamalKeypair::new_rand(); let encrypted_message: u64 = 55; let committed_message: u64 = 77; - let ciphertext = elgamal_keypair.public.encrypt(encrypted_message); - let (commitment, opening) = Pedersen::new(committed_message); + let ciphertext_source = keypair_source.public.encrypt(encrypted_message); + let (commitment_dest, opening_dest) = Pedersen::new(committed_message); let mut transcript_prover = Transcript::new(b"Test"); let mut transcript_verifier = Transcript::new(b"Test"); - let proof = EqualityProof::new( - &elgamal_keypair, - &ciphertext, + let proof = CtxtCommEqualityProof::new( + &keypair_source, + &ciphertext_source, message, - &opening, + &opening_dest, &mut transcript_prover, ); assert!(proof .verify( - &elgamal_keypair.public, - &ciphertext, - &commitment, + &keypair_source.public, + &ciphertext_source, + &commitment_dest, &mut transcript_verifier ) .is_err()); } #[test] - fn test_equality_proof_edge_cases() { + fn test_ciphertext_commitment_equality_proof_edge_cases() { // if ElGamal public key zero (public key is invalid), then the proof should always reject let public = ElGamalPubkey::from_bytes(&[0u8; 32]).unwrap(); let secret = ElGamalSecretKey::new_rand(); @@ -302,7 +524,7 @@ mod test { let mut transcript_prover = Transcript::new(b"Test"); let mut transcript_verifier = Transcript::new(b"Test"); - let proof = EqualityProof::new( + let proof = CtxtCommEqualityProof::new( &elgamal_keypair, &ciphertext, message, @@ -331,7 +553,7 @@ mod test { let mut transcript_prover = Transcript::new(b"Test"); let mut transcript_verifier = Transcript::new(b"Test"); - let proof = EqualityProof::new( + let proof = CtxtCommEqualityProof::new( &elgamal_keypair, &ciphertext, message, @@ -360,7 +582,7 @@ mod test { let mut transcript_prover = Transcript::new(b"Test"); let mut transcript_verifier = Transcript::new(b"Test"); - let proof = EqualityProof::new( + let proof = CtxtCommEqualityProof::new( &elgamal_keypair, &ciphertext, message, @@ -388,7 +610,7 @@ mod test { let mut transcript_prover = Transcript::new(b"Test"); let mut transcript_verifier = Transcript::new(b"Test"); - let proof = EqualityProof::new( + let proof = CtxtCommEqualityProof::new( &elgamal_keypair, &ciphertext, message, @@ -405,4 +627,72 @@ mod test { ) .is_ok()); } + + #[test] + fn test_ciphertext_ciphertext_equality_proof_correctness() { + // success case + let keypair_source = ElGamalKeypair::new_rand(); + let keypair_dest = ElGamalKeypair::new_rand(); + let message: u64 = 55; + + let ciphertext_source = keypair_source.public.encrypt(message); + + let opening_dest = PedersenOpening::new_rand(); + let ciphertext_dest = keypair_dest.public.encrypt_with(message, &opening_dest); + + let mut transcript_prover = Transcript::new(b"Test"); + let mut transcript_verifier = Transcript::new(b"Test"); + + let proof = CtxtCtxtEqualityProof::new( + &keypair_source, + &keypair_dest.public, + &ciphertext_source, + message, + &opening_dest, + &mut transcript_prover, + ); + + assert!(proof + .verify( + &keypair_source.public, + &keypair_dest.public, + &ciphertext_source, + &ciphertext_dest, + &mut transcript_verifier + ) + .is_ok()); + + // fail case: encrypted and committed messages are different + let message_source: u64 = 55; + let message_dest: u64 = 77; + + let ciphertext_source = keypair_source.public.encrypt(message_source); + + let opening_dest = PedersenOpening::new_rand(); + let ciphertext_dest = keypair_dest + .public + .encrypt_with(message_dest, &opening_dest); + + let mut transcript_prover = Transcript::new(b"Test"); + let mut transcript_verifier = Transcript::new(b"Test"); + + let proof = CtxtCtxtEqualityProof::new( + &keypair_source, + &keypair_dest.public, + &ciphertext_source, + message, + &opening_dest, + &mut transcript_prover, + ); + + assert!(proof + .verify( + &keypair_source.public, + &keypair_dest.public, + &ciphertext_source, + &ciphertext_dest, + &mut transcript_verifier + ) + .is_err()); + } } diff --git a/zk-token-sdk/src/sigma_proofs/mod.rs b/zk-token-sdk/src/sigma_proofs/mod.rs index 42a392bdf6..53f5c7a9f2 100644 --- a/zk-token-sdk/src/sigma_proofs/mod.rs +++ b/zk-token-sdk/src/sigma_proofs/mod.rs @@ -3,8 +3,8 @@ //! //! The module contains implementations of the following proof systems that work on Pedersen //! commitments and twisted ElGamal ciphertexts: -//! - Equality proof: can be used to certify that a twisted ElGamal ciphertext and a Pedersen -//! commitment encrypt/encode the same message. +//! - Equality proof: can be used to certify that a twisted ElGamal ciphertext encrypts the same +//! message as either a Pedersen commitment or another ElGamal ciphertext. //! - Validity proof: can be used to certify that a twisted ElGamal ciphertext is a properly-formed //! ciphertext with respect to a pair of ElGamal public keys. //! - Zero-balance proof: can be used to certify that a twisted ElGamal ciphertext encrypts the diff --git a/zk-token-sdk/src/zk_token_elgamal/convert.rs b/zk-token-sdk/src/zk_token_elgamal/convert.rs index 2c53bb67dc..7339f62ffa 100644 --- a/zk-token-sdk/src/zk_token_elgamal/convert.rs +++ b/zk-token-sdk/src/zk_token_elgamal/convert.rs @@ -27,7 +27,7 @@ mod target_arch { }, range_proof::{errors::RangeProofError, RangeProof}, sigma_proofs::{ - equality_proof::EqualityProof, + equality_proof::{CtxtCommEqualityProof, CtxtCtxtEqualityProof}, errors::*, fee_proof::FeeSigmaProof, validity_proof::{AggregatedValidityProof, ValidityProof}, @@ -151,16 +151,30 @@ mod target_arch { } } - impl From for pod::EqualityProof { - fn from(proof: EqualityProof) -> Self { + impl From for pod::CtxtCommEqualityProof { + fn from(proof: CtxtCommEqualityProof) -> Self { Self(proof.to_bytes()) } } - impl TryFrom for EqualityProof { + impl TryFrom for CtxtCommEqualityProof { type Error = EqualityProofError; - fn try_from(pod: pod::EqualityProof) -> Result { + fn try_from(pod: pod::CtxtCommEqualityProof) -> Result { + Self::from_bytes(&pod.0) + } + } + + impl From for pod::CtxtCtxtEqualityProof { + fn from(proof: CtxtCtxtEqualityProof) -> Self { + Self(proof.to_bytes()) + } + } + + impl TryFrom for CtxtCtxtEqualityProof { + type Error = EqualityProofError; + + fn try_from(pod: pod::CtxtCtxtEqualityProof) -> Result { Self::from_bytes(&pod.0) } } @@ -313,9 +327,9 @@ mod target_arch { impl From for pod::TransferPubkeys { fn from(keys: TransferPubkeys) -> Self { Self { - source: keys.source.into(), - dest: keys.dest.into(), - auditor: keys.auditor.into(), + pubkey_source: keys.pubkey_source.into(), + pubkey_dest: keys.pubkey_dest.into(), + pubkey_auditor: keys.pubkey_auditor.into(), } } } @@ -325,9 +339,9 @@ mod target_arch { fn try_from(pod: pod::TransferPubkeys) -> Result { Ok(Self { - source: pod.source.try_into()?, - dest: pod.dest.try_into()?, - auditor: pod.auditor.try_into()?, + pubkey_source: pod.pubkey_source.try_into()?, + pubkey_dest: pod.pubkey_dest.try_into()?, + pubkey_auditor: pod.pubkey_auditor.try_into()?, }) } } @@ -335,10 +349,10 @@ mod target_arch { impl From for pod::TransferWithFeePubkeys { fn from(keys: TransferWithFeePubkeys) -> Self { Self { - source: keys.source.into(), - dest: keys.dest.into(), - auditor: keys.auditor.into(), - fee_collector: keys.fee_collector.into(), + pubkey_source: keys.pubkey_source.into(), + pubkey_dest: keys.pubkey_dest.into(), + pubkey_auditor: keys.pubkey_auditor.into(), + pubkey_withdraw_withheld_authority: keys.pubkey_withdraw_withheld_authority.into(), } } } @@ -348,10 +362,12 @@ mod target_arch { fn try_from(pod: pod::TransferWithFeePubkeys) -> Result { Ok(Self { - source: pod.source.try_into()?, - dest: pod.dest.try_into()?, - auditor: pod.auditor.try_into()?, - fee_collector: pod.fee_collector.try_into()?, + pubkey_source: pod.pubkey_source.try_into()?, + pubkey_dest: pod.pubkey_dest.try_into()?, + pubkey_auditor: pod.pubkey_auditor.try_into()?, + pubkey_withdraw_withheld_authority: pod + .pubkey_withdraw_withheld_authority + .try_into()?, }) } } @@ -360,9 +376,9 @@ mod target_arch { fn from(ciphertext: TransferAmountEncryption) -> Self { Self { commitment: ciphertext.commitment.into(), - source: ciphertext.source.into(), - dest: ciphertext.dest.into(), - auditor: ciphertext.auditor.into(), + handle_source: ciphertext.handle_source.into(), + handle_dest: ciphertext.handle_dest.into(), + handle_auditor: ciphertext.handle_auditor.into(), } } } @@ -373,9 +389,9 @@ mod target_arch { fn try_from(pod: pod::TransferAmountEncryption) -> Result { Ok(Self { commitment: pod.commitment.try_into()?, - source: pod.source.try_into()?, - dest: pod.dest.try_into()?, - auditor: pod.auditor.try_into()?, + handle_source: pod.handle_source.try_into()?, + handle_dest: pod.handle_dest.try_into()?, + handle_auditor: pod.handle_auditor.try_into()?, }) } } @@ -384,8 +400,10 @@ mod target_arch { fn from(ciphertext: FeeEncryption) -> Self { Self { commitment: ciphertext.commitment.into(), - dest: ciphertext.dest.into(), - fee_collector: ciphertext.fee_collector.into(), + handle_dest: ciphertext.handle_dest.into(), + handle_withdraw_withheld_authority: ciphertext + .handle_withdraw_withheld_authority + .into(), } } } @@ -396,8 +414,10 @@ mod target_arch { fn try_from(pod: pod::FeeEncryption) -> Result { Ok(Self { commitment: pod.commitment.try_into()?, - dest: pod.dest.try_into()?, - fee_collector: pod.fee_collector.try_into()?, + handle_dest: pod.handle_dest.try_into()?, + handle_withdraw_withheld_authority: pod + .handle_withdraw_withheld_authority + .try_into()?, }) } } diff --git a/zk-token-sdk/src/zk_token_elgamal/pod.rs b/zk-token-sdk/src/zk_token_elgamal/pod.rs index 158929e0d0..c717bc26e5 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod.rs @@ -83,15 +83,25 @@ impl fmt::Debug for DecryptHandle { } } -/// Serialization of equality proofs +/// Serialization of `CtxtCommEqualityProof` #[derive(Clone, Copy)] #[repr(transparent)] -pub struct EqualityProof(pub [u8; 192]); +pub struct CtxtCommEqualityProof(pub [u8; 192]); -// `EqualityProof` is a Pod and Zeroable. +// `CtxtCommEqualityProof` is a Pod and Zeroable. // Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays -unsafe impl Zeroable for EqualityProof {} -unsafe impl Pod for EqualityProof {} +unsafe impl Zeroable for CtxtCommEqualityProof {} +unsafe impl Pod for CtxtCommEqualityProof {} + +/// Serialization of `CtxtCtxtEqualityProof` +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct CtxtCtxtEqualityProof(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 {} /// Serialization of validity proofs #[derive(Clone, Copy)] @@ -184,40 +194,35 @@ impl Default for AeCiphertext { #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct TransferPubkeys { - pub source: ElGamalPubkey, - pub dest: ElGamalPubkey, - pub auditor: ElGamalPubkey, + pub pubkey_source: ElGamalPubkey, + pub pubkey_dest: ElGamalPubkey, + pub pubkey_auditor: ElGamalPubkey, } -// pub struct TransferPubkeys(pub [u8; 96]); - -// unsafe impl Zeroable for TransferPubkeys {} -// unsafe impl Pod for TransferPubkeys {} - #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct TransferWithFeePubkeys { - pub source: ElGamalPubkey, - pub dest: ElGamalPubkey, - pub auditor: ElGamalPubkey, - pub fee_collector: ElGamalPubkey, + pub pubkey_source: ElGamalPubkey, + pub pubkey_dest: ElGamalPubkey, + pub pubkey_auditor: ElGamalPubkey, + pub pubkey_withdraw_withheld_authority: ElGamalPubkey, } #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct TransferAmountEncryption { pub commitment: PedersenCommitment, - pub source: DecryptHandle, - pub dest: DecryptHandle, - pub auditor: DecryptHandle, + pub handle_source: DecryptHandle, + pub handle_dest: DecryptHandle, + pub handle_auditor: DecryptHandle, } #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct FeeEncryption { pub commitment: PedersenCommitment, - pub dest: DecryptHandle, - pub fee_collector: DecryptHandle, + pub handle_dest: DecryptHandle, + pub handle_withdraw_withheld_authority: DecryptHandle, } #[derive(Clone, Copy, Pod, Zeroable)] diff --git a/zk-token-sdk/src/zk_token_proof_instruction.rs b/zk-token-sdk/src/zk_token_proof_instruction.rs index d40e9f56e4..3dfdea18de 100644 --- a/zk-token-sdk/src/zk_token_proof_instruction.rs +++ b/zk-token-sdk/src/zk_token_proof_instruction.rs @@ -30,6 +30,16 @@ pub enum ProofInstruction { /// VerifyWithdraw, + /// Verify a `WithdrawWithheldTokensData` struct + /// + /// Accounts expected by this instruction: + /// None + /// + /// Data expected by this instruction: + /// `WithdrawWithheldTokensData` + /// + VerifyWithdrawWithheldTokens, + /// Verify a `TransferData` struct /// /// Accounts expected by this instruction: @@ -83,6 +93,10 @@ pub fn verify_withdraw(proof_data: &WithdrawData) -> Instruction { ProofInstruction::VerifyWithdraw.encode(proof_data) } +pub fn verify_withdraw_withheld_tokens(proof_data: &WithdrawWithheldTokensData) -> Instruction { + ProofInstruction::VerifyWithdrawWithheldTokens.encode(proof_data) +} + pub fn verify_transfer(proof_data: &TransferData) -> Instruction { ProofInstruction::VerifyTransfer.encode(proof_data) }