diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 46cbffa9262..8a7cf30b5dd 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -66,7 +66,10 @@ use { }, spl_token_client::{ client::{ProgramRpcClientSendTransaction, RpcClientResponse}, - token::{ComputeUnitLimit, ExtensionInitializationParams, ProofAccount, Token}, + token::{ + ComputeUnitLimit, ExtensionInitializationParams, ProofAccount, + ProofAccountWithCiphertext, Token, + }, }, spl_token_confidential_transfer_proof_generation::{ transfer::TransferProofData, withdraw::WithdrawProofData, @@ -1611,7 +1614,7 @@ async fn command_transfer( let TransferProofData { equality_proof_data, - ciphertext_validity_proof_data, + ciphertext_validity_proof_data_with_ciphertext, range_proof_data, } = transfer_account_info .generate_split_transfer_proof_data( @@ -1623,6 +1626,11 @@ async fn command_transfer( ) .unwrap(); + let transfer_amount_auditor_ciphertext_lo = + ciphertext_validity_proof_data_with_ciphertext.ciphertext_lo; + let transfer_amount_auditor_ciphertext_hi = + ciphertext_validity_proof_data_with_ciphertext.ciphertext_hi; + // setup proofs let create_range_proof_context_signer = &[&range_proof_context_state_account]; let create_equality_proof_context_signer = &[&equality_proof_context_state_account]; @@ -1647,7 +1655,7 @@ async fn command_transfer( token.confidential_transfer_create_context_state_account( &ciphertext_validity_proof_pubkey, &context_state_authority_pubkey, - &ciphertext_validity_proof_data, + &ciphertext_validity_proof_data_with_ciphertext.proof_data, false, create_ciphertext_validity_proof_context_signer ) @@ -1661,13 +1669,19 @@ async fn command_transfer( let range_proof_context_proof_account = ProofAccount::ContextAccount(range_proof_pubkey); + let ciphertext_validity_proof_account_with_ciphertext = ProofAccountWithCiphertext { + proof_account: ciphertext_validity_proof_context_proof_account, + ciphertext_lo: transfer_amount_auditor_ciphertext_lo, + ciphertext_hi: transfer_amount_auditor_ciphertext_hi, + }; + let transfer_result = token .confidential_transfer_transfer( &sender, &recipient_token_account, &sender_owner, Some(&equality_proof_context_proof_account), - Some(&ciphertext_validity_proof_context_proof_account), + Some(&ciphertext_validity_proof_account_with_ciphertext), Some(&range_proof_context_proof_account), transfer_balance, Some(transfer_account_info), diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 33bab151499..26780b6f6aa 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -51,7 +51,7 @@ use { encryption::{ auth_encryption::AeKey, elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey}, - pod::elgamal::PodElGamalPubkey, + pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, }, zk_elgamal_proof_program::{ self, @@ -348,6 +348,12 @@ pub enum ProofAccount { RecordAccount(Pubkey, u32), } +pub struct ProofAccountWithCiphertext { + pub proof_account: ProofAccount, + pub ciphertext_lo: PodElGamalCiphertext, + pub ciphertext_hi: PodElGamalCiphertext, +} + pub struct Token { client: Arc>, pubkey: Pubkey, /* token mint */ @@ -2198,7 +2204,7 @@ where destination_account: &Pubkey, source_authority: &Pubkey, equality_proof_account: Option<&ProofAccount>, - ciphertext_validity_proof_account: Option<&ProofAccount>, + ciphertext_validity_proof_account_with_ciphertext: Option<&ProofAccountWithCiphertext>, range_proof_account: Option<&ProofAccount>, transfer_amount: u64, account_info: Option, @@ -2220,46 +2226,65 @@ where TransferAccountInfo::new(confidential_transfer_account) }; - let (equality_proof_data, ciphertext_validity_proof_data, range_proof_data) = if [ - equality_proof_account, - ciphertext_validity_proof_account, - range_proof_account, - ] - .iter() - .all(|proof_account| proof_account.is_some()) - { - (None, None, None) - } else { - let TransferProofData { - equality_proof_data, - ciphertext_validity_proof_data, - range_proof_data, - } = account_info - .generate_split_transfer_proof_data( - transfer_amount, - source_elgamal_keypair, - source_aes_key, - destination_elgamal_pubkey, - auditor_elgamal_pubkey, - ) - .map_err(|_| TokenError::ProofGeneration)?; + let (equality_proof_data, ciphertext_validity_proof_data_with_ciphertext, range_proof_data) = + if equality_proof_account.is_some() + && ciphertext_validity_proof_account_with_ciphertext.is_some() + && range_proof_account.is_some() + { + (None, None, None) + } else { + let TransferProofData { + equality_proof_data, + ciphertext_validity_proof_data_with_ciphertext, + range_proof_data, + } = account_info + .generate_split_transfer_proof_data( + transfer_amount, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + ) + .map_err(|_| TokenError::ProofGeneration)?; - // if proof accounts are none, then proof data must be included as instruction - // data - let equality_proof_data = equality_proof_account - .is_none() - .then_some(equality_proof_data); - let ciphertext_validity_proof_data = ciphertext_validity_proof_account - .is_none() - .then_some(ciphertext_validity_proof_data); - let range_proof_data = range_proof_account.is_none().then_some(range_proof_data); + // if proof accounts are none, then proof data must be included as instruction + // data + let equality_proof_data = equality_proof_account + .is_none() + .then_some(equality_proof_data); + let ciphertext_validity_proof_data_with_ciphertext = + ciphertext_validity_proof_account_with_ciphertext + .is_none() + .then_some(ciphertext_validity_proof_data_with_ciphertext); + let range_proof_data = range_proof_account.is_none().then_some(range_proof_data); - ( - equality_proof_data, - ciphertext_validity_proof_data, - range_proof_data, - ) - }; + ( + equality_proof_data, + ciphertext_validity_proof_data_with_ciphertext, + range_proof_data, + ) + }; + + let (transfer_amount_auditor_ciphertext_lo, transfer_amount_auditor_ciphertext_hi) = + if let Some(proof_data_with_ciphertext) = ciphertext_validity_proof_data_with_ciphertext + { + ( + proof_data_with_ciphertext.ciphertext_lo, + proof_data_with_ciphertext.ciphertext_hi, + ) + } else { + // unwrap is safe as long as either `proof_data_with_ciphertext`, + // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the + // previous check + ( + ciphertext_validity_proof_account_with_ciphertext + .unwrap() + .ciphertext_lo, + ciphertext_validity_proof_account_with_ciphertext + .unwrap() + .ciphertext_hi, + ) + }; // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, // which is guaranteed by the previous check @@ -2269,9 +2294,11 @@ where 1, ) .unwrap(); + let ciphertext_validity_proof_data = + ciphertext_validity_proof_data_with_ciphertext.map(|data| data.proof_data); let ciphertext_validity_proof_location = Self::confidential_transfer_create_proof_location( ciphertext_validity_proof_data.as_ref(), - ciphertext_validity_proof_account, + ciphertext_validity_proof_account_with_ciphertext.map(|account| &account.proof_account), 2, ) .unwrap(); @@ -2292,6 +2319,8 @@ where self.get_address(), destination_account, new_decryptable_available_balance.into(), + &transfer_amount_auditor_ciphertext_lo, + &transfer_amount_auditor_ciphertext_hi, source_authority, &multisig_signers, equality_proof_location, @@ -2526,7 +2555,9 @@ where destination_account: &Pubkey, source_authority: &Pubkey, equality_proof_account: Option<&ProofAccount>, - transfer_amount_ciphertext_validity_proof_account: Option<&ProofAccount>, + transfer_amount_ciphertext_validity_proof_account_with_ciphertext: Option< + &ProofAccountWithCiphertext, + >, percentage_with_cap_proof_account: Option<&ProofAccount>, fee_ciphertext_validity_proof_account: Option<&ProofAccount>, range_proof_account: Option<&ProofAccount>, @@ -2555,26 +2586,22 @@ where let ( equality_proof_data, - transfer_amount_ciphertext_validity_proof_data, + transfer_amount_ciphertext_validity_proof_data_with_ciphertext, percentage_with_cap_proof_data, fee_ciphertext_validity_proof_data, range_proof_data, - ) = if [ - equality_proof_account, - transfer_amount_ciphertext_validity_proof_account, - percentage_with_cap_proof_account, - fee_ciphertext_validity_proof_account, - range_proof_account, - ] - .iter() - .all(|proof_account| proof_account.is_some()) + ) = if equality_proof_account.is_some() + && transfer_amount_ciphertext_validity_proof_account_with_ciphertext.is_some() + && percentage_with_cap_proof_account.is_some() + && fee_ciphertext_validity_proof_account.is_some() + && range_proof_account.is_some() { // is all proofs come from accounts, then skip proof generation (None, None, None, None, None) } else { let TransferWithFeeProofData { equality_proof_data, - transfer_amount_ciphertext_validity_proof_data, + transfer_amount_ciphertext_validity_proof_data_with_ciphertext, percentage_with_cap_proof_data, fee_ciphertext_validity_proof_data, range_proof_data, @@ -2594,10 +2621,10 @@ where let equality_proof_data = equality_proof_account .is_none() .then_some(equality_proof_data); - let transfer_amount_ciphertext_validity_proof_data = - transfer_amount_ciphertext_validity_proof_account + let transfer_amount_ciphertext_validity_proof_data_with_ciphertext = + transfer_amount_ciphertext_validity_proof_account_with_ciphertext .is_none() - .then_some(transfer_amount_ciphertext_validity_proof_data); + .then_some(transfer_amount_ciphertext_validity_proof_data_with_ciphertext); let percentage_with_cap_proof_data = percentage_with_cap_proof_account .is_none() .then_some(percentage_with_cap_proof_data); @@ -2608,13 +2635,35 @@ where ( equality_proof_data, - transfer_amount_ciphertext_validity_proof_data, + transfer_amount_ciphertext_validity_proof_data_with_ciphertext, percentage_with_cap_proof_data, fee_ciphertext_validity_proof_data, range_proof_data, ) }; + let (transfer_amount_auditor_ciphertext_lo, transfer_amount_auditor_ciphertext_hi) = + if let Some(proof_data_with_ciphertext) = + transfer_amount_ciphertext_validity_proof_data_with_ciphertext + { + ( + proof_data_with_ciphertext.ciphertext_lo, + proof_data_with_ciphertext.ciphertext_hi, + ) + } else { + // unwrap is safe as long as either `proof_data_with_ciphertext`, + // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the + // previous check + ( + transfer_amount_ciphertext_validity_proof_account_with_ciphertext + .unwrap() + .ciphertext_lo, + transfer_amount_ciphertext_validity_proof_account_with_ciphertext + .unwrap() + .ciphertext_hi, + ) + }; + // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, // which is guaranteed by the previous check let equality_proof_location = Self::confidential_transfer_create_proof_location( @@ -2623,10 +2672,14 @@ where 1, ) .unwrap(); + let transfer_amount_ciphertext_validity_proof_data = + transfer_amount_ciphertext_validity_proof_data_with_ciphertext + .map(|data| data.proof_data); let transfer_amount_ciphertext_validity_proof_location = Self::confidential_transfer_create_proof_location( transfer_amount_ciphertext_validity_proof_data.as_ref(), - transfer_amount_ciphertext_validity_proof_account, + transfer_amount_ciphertext_validity_proof_account_with_ciphertext + .map(|account| &account.proof_account), 2, ) .unwrap(); @@ -2660,6 +2713,8 @@ where self.get_address(), destination_account, new_decryptable_available_balance.into(), + &transfer_amount_auditor_ciphertext_lo, + &transfer_amount_auditor_ciphertext_hi, source_authority, &multisig_signers, equality_proof_location, diff --git a/token/confidential-transfer/proof-generation/src/burn.rs b/token/confidential-transfer/proof-generation/src/burn.rs index 9b927384ac8..ae127d9e9a6 100644 --- a/token/confidential-transfer/proof-generation/src/burn.rs +++ b/token/confidential-transfer/proof-generation/src/burn.rs @@ -1,7 +1,7 @@ use { crate::{ encryption::BurnAmountCiphertext, errors::TokenProofGenerationError, - try_combine_lo_hi_ciphertexts, try_split_u64, + try_combine_lo_hi_ciphertexts, try_split_u64, CiphertextValidityProofWithAuditorCiphertext, }, solana_zk_sdk::{ encryption::{ @@ -11,7 +11,7 @@ use { }, zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, - CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofData, ZkProofData, }, }, }; @@ -25,7 +25,8 @@ const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16; /// The proof data required for a confidential burn instruction pub struct BurnProofData { pub equality_proof_data: CiphertextCommitmentEqualityProofData, - pub ciphertext_validity_proof_data: BatchedGroupedCiphertext3HandlesValidityProofData, + pub ciphertext_validity_proof_data_with_ciphertext: + CiphertextValidityProofWithAuditorCiphertext, pub range_proof_data: BatchedRangeProofU128Data, } @@ -113,6 +114,25 @@ pub fn burn_split_proof_data( ) .map_err(TokenProofGenerationError::from)?; + let burn_amount_auditor_ciphertext_lo = ciphertext_validity_proof_data + .context_data() + .grouped_ciphertext_lo + .try_extract_ciphertext(2) + .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?; + + let burn_amount_auditor_ciphertext_hi = ciphertext_validity_proof_data + .context_data() + .grouped_ciphertext_hi + .try_extract_ciphertext(2) + .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?; + + let ciphertext_validity_proof_data_with_ciphertext = + CiphertextValidityProofWithAuditorCiphertext { + proof_data: ciphertext_validity_proof_data, + ciphertext_lo: burn_amount_auditor_ciphertext_lo, + ciphertext_hi: burn_amount_auditor_ciphertext_hi, + }; + // generate range proof data let (padding_commitment, padding_opening) = Pedersen::new(0_u64); let range_proof_data = BatchedRangeProofU128Data::new( @@ -140,7 +160,7 @@ pub fn burn_split_proof_data( Ok(BurnProofData { equality_proof_data, - ciphertext_validity_proof_data, + ciphertext_validity_proof_data_with_ciphertext, range_proof_data, }) } diff --git a/token/confidential-transfer/proof-generation/src/errors.rs b/token/confidential-transfer/proof-generation/src/errors.rs index 5cb5c14e794..9f27f6ebb42 100644 --- a/token/confidential-transfer/proof-generation/src/errors.rs +++ b/token/confidential-transfer/proof-generation/src/errors.rs @@ -10,4 +10,6 @@ pub enum TokenProofGenerationError { IllegalAmountBitLength, #[error("fee calculation failed")] FeeCalculation, + #[error("ciphertext extraction failed")] + CiphertextExtraction, } diff --git a/token/confidential-transfer/proof-generation/src/lib.rs b/token/confidential-transfer/proof-generation/src/lib.rs index f8883f31954..39a2db74da4 100644 --- a/token/confidential-transfer/proof-generation/src/lib.rs +++ b/token/confidential-transfer/proof-generation/src/lib.rs @@ -1,8 +1,12 @@ use { curve25519_dalek::scalar::Scalar, - solana_zk_sdk::encryption::{ - elgamal::ElGamalCiphertext, - pedersen::{PedersenCommitment, PedersenOpening}, + solana_zk_sdk::{ + encryption::{ + elgamal::ElGamalCiphertext, + pedersen::{PedersenCommitment, PedersenOpening}, + pod::elgamal::PodElGamalCiphertext, + }, + zk_elgamal_proof_program::proof_data::BatchedGroupedCiphertext3HandlesValidityProofData, }, }; @@ -87,3 +91,18 @@ pub fn try_combine_lo_hi_openings( let two_power = 1_u64.checked_shl(bit_length as u32)?; Some(opening_lo + opening_hi * Scalar::from(two_power)) } + +/// A type that wraps a ciphertext validity proof along with two `lo` and `hi` +/// ciphertexts. +/// +/// Ciphertext validity proof data contains grouped ElGamal ciphertexts (`lo` +/// and `hi`) and a proof containing the validity of these ciphertexts. Token +/// client-side logic often requires a function to extract specific forms of +/// the grouped ElGamal ciphertexts. This type is a convenience type that +/// contains the proof data and the extracted ciphertexts. +#[derive(Clone, Copy)] +pub struct CiphertextValidityProofWithAuditorCiphertext { + pub proof_data: BatchedGroupedCiphertext3HandlesValidityProofData, + pub ciphertext_lo: PodElGamalCiphertext, + pub ciphertext_hi: PodElGamalCiphertext, +} diff --git a/token/confidential-transfer/proof-generation/src/mint.rs b/token/confidential-transfer/proof-generation/src/mint.rs index 1ada01840ab..1f06a1c0156 100644 --- a/token/confidential-transfer/proof-generation/src/mint.rs +++ b/token/confidential-transfer/proof-generation/src/mint.rs @@ -1,7 +1,7 @@ use { crate::{ encryption::MintAmountCiphertext, errors::TokenProofGenerationError, - try_combine_lo_hi_ciphertexts, try_split_u64, + try_combine_lo_hi_ciphertexts, try_split_u64, CiphertextValidityProofWithAuditorCiphertext, }, solana_zk_sdk::{ encryption::{ @@ -11,7 +11,7 @@ use { }, zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, - CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofData, ZkProofData, }, }, }; @@ -25,7 +25,8 @@ const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16; /// The proof data required for a confidential mint instruction pub struct MintProofData { pub equality_proof_data: CiphertextCommitmentEqualityProofData, - pub ciphertext_validity_proof_data: BatchedGroupedCiphertext3HandlesValidityProofData, + pub ciphertext_validity_proof_data_with_ciphertext: + CiphertextValidityProofWithAuditorCiphertext, pub range_proof_data: BatchedRangeProofU128Data, pub new_decryptable_supply: AeCiphertext, } @@ -109,6 +110,25 @@ pub fn mint_split_proof_data( ) .map_err(TokenProofGenerationError::from)?; + let mint_amount_auditor_ciphertext_lo = ciphertext_validity_proof_data + .context_data() + .grouped_ciphertext_lo + .try_extract_ciphertext(2) + .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?; + + let mint_amount_auditor_ciphertext_hi = ciphertext_validity_proof_data + .context_data() + .grouped_ciphertext_hi + .try_extract_ciphertext(2) + .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?; + + let ciphertext_validity_proof_data_with_ciphertext = + CiphertextValidityProofWithAuditorCiphertext { + proof_data: ciphertext_validity_proof_data, + ciphertext_lo: mint_amount_auditor_ciphertext_lo, + ciphertext_hi: mint_amount_auditor_ciphertext_hi, + }; + // generate range proof data let (padding_commitment, padding_opening) = Pedersen::new(0_u64); let range_proof_data = BatchedRangeProofU128Data::new( @@ -136,7 +156,7 @@ pub fn mint_split_proof_data( Ok(MintProofData { equality_proof_data, - ciphertext_validity_proof_data, + ciphertext_validity_proof_data_with_ciphertext, range_proof_data, new_decryptable_supply: supply_aes_key.encrypt(new_supply), }) diff --git a/token/confidential-transfer/proof-generation/src/transfer.rs b/token/confidential-transfer/proof-generation/src/transfer.rs index fbb257a4fc1..4d59cc95f4d 100644 --- a/token/confidential-transfer/proof-generation/src/transfer.rs +++ b/token/confidential-transfer/proof-generation/src/transfer.rs @@ -1,8 +1,8 @@ use { crate::{ encryption::TransferAmountCiphertext, errors::TokenProofGenerationError, - try_combine_lo_hi_ciphertexts, try_split_u64, REMAINING_BALANCE_BIT_LENGTH, - TRANSFER_AMOUNT_HI_BITS, TRANSFER_AMOUNT_LO_BITS, + try_combine_lo_hi_ciphertexts, try_split_u64, CiphertextValidityProofWithAuditorCiphertext, + REMAINING_BALANCE_BIT_LENGTH, TRANSFER_AMOUNT_HI_BITS, TRANSFER_AMOUNT_LO_BITS, }, solana_zk_sdk::{ encryption::{ @@ -12,7 +12,7 @@ use { }, zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, - CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofData, ZkProofData, }, }, }; @@ -25,7 +25,8 @@ const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16; /// mint is not extended for fees pub struct TransferProofData { pub equality_proof_data: CiphertextCommitmentEqualityProofData, - pub ciphertext_validity_proof_data: BatchedGroupedCiphertext3HandlesValidityProofData, + pub ciphertext_validity_proof_data_with_ciphertext: + CiphertextValidityProofWithAuditorCiphertext, pub range_proof_data: BatchedRangeProofU128Data, } @@ -120,6 +121,25 @@ pub fn transfer_split_proof_data( ) .map_err(TokenProofGenerationError::from)?; + let transfer_amount_auditor_ciphertext_lo = ciphertext_validity_proof_data + .context_data() + .grouped_ciphertext_lo + .try_extract_ciphertext(2) + .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?; + + let transfer_amount_auditor_ciphertext_hi = ciphertext_validity_proof_data + .context_data() + .grouped_ciphertext_hi + .try_extract_ciphertext(2) + .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?; + + let ciphertext_validity_proof_data_with_ciphertext = + CiphertextValidityProofWithAuditorCiphertext { + proof_data: ciphertext_validity_proof_data, + ciphertext_lo: transfer_amount_auditor_ciphertext_lo, + ciphertext_hi: transfer_amount_auditor_ciphertext_hi, + }; + // generate range proof data let (padding_commitment, padding_opening) = Pedersen::new(0_u64); let range_proof_data = BatchedRangeProofU128Data::new( @@ -152,7 +172,7 @@ pub fn transfer_split_proof_data( Ok(TransferProofData { equality_proof_data, - ciphertext_validity_proof_data, + ciphertext_validity_proof_data_with_ciphertext, range_proof_data, }) } diff --git a/token/confidential-transfer/proof-generation/src/transfer_with_fee.rs b/token/confidential-transfer/proof-generation/src/transfer_with_fee.rs index f49e1aa00ec..5e5939f6184 100644 --- a/token/confidential-transfer/proof-generation/src/transfer_with_fee.rs +++ b/token/confidential-transfer/proof-generation/src/transfer_with_fee.rs @@ -3,7 +3,8 @@ use { encryption::{FeeCiphertext, TransferAmountCiphertext}, errors::TokenProofGenerationError, try_combine_lo_hi_ciphertexts, try_combine_lo_hi_commitments, try_combine_lo_hi_openings, - try_split_u64, TRANSFER_AMOUNT_HI_BITS, TRANSFER_AMOUNT_LO_BITS, + try_split_u64, CiphertextValidityProofWithAuditorCiphertext, TRANSFER_AMOUNT_HI_BITS, + TRANSFER_AMOUNT_LO_BITS, }, curve25519_dalek::scalar::Scalar, solana_zk_sdk::{ @@ -16,7 +17,7 @@ use { zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext2HandlesValidityProofData, BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU256Data, - CiphertextCommitmentEqualityProofData, PercentageWithCapProofData, + CiphertextCommitmentEqualityProofData, PercentageWithCapProofData, ZkProofData, }, }, }; @@ -34,8 +35,8 @@ const DELTA_BIT_LENGTH: usize = 48; /// mint is extended for fees pub struct TransferWithFeeProofData { pub equality_proof_data: CiphertextCommitmentEqualityProofData, - pub transfer_amount_ciphertext_validity_proof_data: - BatchedGroupedCiphertext3HandlesValidityProofData, + pub transfer_amount_ciphertext_validity_proof_data_with_ciphertext: + CiphertextValidityProofWithAuditorCiphertext, pub percentage_with_cap_proof_data: PercentageWithCapProofData, pub fee_ciphertext_validity_proof_data: BatchedGroupedCiphertext2HandlesValidityProofData, pub range_proof_data: BatchedRangeProofU256Data, @@ -138,6 +139,25 @@ pub fn transfer_with_fee_split_proof_data( ) .map_err(TokenProofGenerationError::from)?; + let transfer_amount_auditor_ciphertext_lo = transfer_amount_ciphertext_validity_proof_data + .context_data() + .grouped_ciphertext_lo + .try_extract_ciphertext(2) + .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?; + + let transfer_amount_auditor_ciphertext_hi = transfer_amount_ciphertext_validity_proof_data + .context_data() + .grouped_ciphertext_hi + .try_extract_ciphertext(2) + .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?; + + let transfer_amount_ciphertext_validity_proof_data_with_ciphertext = + CiphertextValidityProofWithAuditorCiphertext { + proof_data: transfer_amount_ciphertext_validity_proof_data, + ciphertext_lo: transfer_amount_auditor_ciphertext_lo, + ciphertext_hi: transfer_amount_auditor_ciphertext_hi, + }; + // calculate fee let transfer_fee_basis_points = fee_rate_basis_points; let transfer_fee_maximum_fee = maximum_fee; @@ -298,7 +318,7 @@ pub fn transfer_with_fee_split_proof_data( Ok(TransferWithFeeProofData { equality_proof_data, - transfer_amount_ciphertext_validity_proof_data, + transfer_amount_ciphertext_validity_proof_data_with_ciphertext, percentage_with_cap_proof_data, fee_ciphertext_validity_proof_data, range_proof_data, diff --git a/token/confidential-transfer/proof-tests/tests/proof_test.rs b/token/confidential-transfer/proof-tests/tests/proof_test.rs index 6e73e303ed3..d5e0110e0af 100644 --- a/token/confidential-transfer/proof-tests/tests/proof_test.rs +++ b/token/confidential-transfer/proof-tests/tests/proof_test.rs @@ -42,7 +42,7 @@ fn test_transfer_proof_validity(spendable_balance: u64, transfer_amount: u64) { let TransferProofData { equality_proof_data, - ciphertext_validity_proof_data, + ciphertext_validity_proof_data_with_ciphertext, range_proof_data, } = transfer_split_proof_data( &spendable_ciphertext, @@ -56,12 +56,17 @@ fn test_transfer_proof_validity(spendable_balance: u64, transfer_amount: u64) { .unwrap(); equality_proof_data.verify_proof().unwrap(); - ciphertext_validity_proof_data.verify_proof().unwrap(); + ciphertext_validity_proof_data_with_ciphertext + .proof_data + .verify_proof() + .unwrap(); range_proof_data.verify_proof().unwrap(); TransferProofContext::verify_and_extract( equality_proof_data.context_data(), - ciphertext_validity_proof_data.context_data(), + ciphertext_validity_proof_data_with_ciphertext + .proof_data + .context_data(), range_proof_data.context_data(), ) .unwrap(); @@ -112,7 +117,7 @@ fn test_transfer_with_fee_proof_validity( let TransferWithFeeProofData { equality_proof_data, - transfer_amount_ciphertext_validity_proof_data, + transfer_amount_ciphertext_validity_proof_data_with_ciphertext, percentage_with_cap_proof_data, fee_ciphertext_validity_proof_data, range_proof_data, @@ -131,7 +136,8 @@ fn test_transfer_with_fee_proof_validity( .unwrap(); equality_proof_data.verify_proof().unwrap(); - transfer_amount_ciphertext_validity_proof_data + transfer_amount_ciphertext_validity_proof_data_with_ciphertext + .proof_data .verify_proof() .unwrap(); percentage_with_cap_proof_data.verify_proof().unwrap(); @@ -140,7 +146,9 @@ fn test_transfer_with_fee_proof_validity( TransferWithFeeProofContext::verify_and_extract( equality_proof_data.context_data(), - transfer_amount_ciphertext_validity_proof_data.context_data(), + transfer_amount_ciphertext_validity_proof_data_with_ciphertext + .proof_data + .context_data(), percentage_with_cap_proof_data.context_data(), fee_ciphertext_validity_proof_data.context_data(), range_proof_data.context_data(), @@ -220,7 +228,7 @@ fn test_mint_validity(mint_amount: u64, supply: u64) { let MintProofData { equality_proof_data, - ciphertext_validity_proof_data, + ciphertext_validity_proof_data_with_ciphertext, range_proof_data, new_decryptable_supply: _, } = mint_split_proof_data( @@ -235,12 +243,17 @@ fn test_mint_validity(mint_amount: u64, supply: u64) { .unwrap(); equality_proof_data.verify_proof().unwrap(); - ciphertext_validity_proof_data.verify_proof().unwrap(); + ciphertext_validity_proof_data_with_ciphertext + .proof_data + .verify_proof() + .unwrap(); range_proof_data.verify_proof().unwrap(); MintProofContext::verify_and_extract( equality_proof_data.context_data(), - ciphertext_validity_proof_data.context_data(), + ciphertext_validity_proof_data_with_ciphertext + .proof_data + .context_data(), range_proof_data.context_data(), ) .unwrap(); @@ -270,7 +283,7 @@ fn test_burn_validity(spendable_balance: u64, burn_amount: u64) { let BurnProofData { equality_proof_data, - ciphertext_validity_proof_data, + ciphertext_validity_proof_data_with_ciphertext, range_proof_data, } = burn_split_proof_data( &spendable_balance_ciphertext, @@ -284,12 +297,17 @@ fn test_burn_validity(spendable_balance: u64, burn_amount: u64) { .unwrap(); equality_proof_data.verify_proof().unwrap(); - ciphertext_validity_proof_data.verify_proof().unwrap(); + ciphertext_validity_proof_data_with_ciphertext + .proof_data + .verify_proof() + .unwrap(); range_proof_data.verify_proof().unwrap(); BurnProofContext::verify_and_extract( equality_proof_data.context_data(), - ciphertext_validity_proof_data.context_data(), + ciphertext_validity_proof_data_with_ciphertext + .proof_data + .context_data(), range_proof_data.context_data(), ) .unwrap(); diff --git a/token/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index 7c86f2f93d3..e3c80fe6f44 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -37,8 +37,8 @@ use { spl_token_client::{ client::ProgramBanksClientProcessTransaction, token::{ - ExtensionInitializationParams, ProofAccount, Token, TokenError as TokenClientError, - TokenResult, + ExtensionInitializationParams, ProofAccount, ProofAccountWithCiphertext, Token, + TokenError as TokenClientError, TokenResult, }, }, spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, @@ -1338,7 +1338,7 @@ async fn confidential_transfer_with_option( let TransferProofData { equality_proof_data, - ciphertext_validity_proof_data, + ciphertext_validity_proof_data_with_ciphertext, range_proof_data, } = transfer_account_info .generate_split_transfer_proof_data( @@ -1350,6 +1350,11 @@ async fn confidential_transfer_with_option( ) .unwrap(); + let transfer_amount_auditor_ciphertext_lo = + ciphertext_validity_proof_data_with_ciphertext.ciphertext_lo; + let transfer_amount_auditor_ciphertext_hi = + ciphertext_validity_proof_data_with_ciphertext.ciphertext_hi; + let equality_proof_record_account = Keypair::new(); let ciphertext_validity_proof_record_account = Keypair::new(); let range_proof_record_account = Keypair::new(); @@ -1375,7 +1380,7 @@ async fn confidential_transfer_with_option( .confidential_transfer_create_record_account( &ciphertext_validity_proof_record_account.pubkey(), &record_account_authority.pubkey(), - &ciphertext_validity_proof_data, + &ciphertext_validity_proof_data_with_ciphertext.proof_data, &ciphertext_validity_proof_record_account, &record_account_authority, ) @@ -1409,13 +1414,19 @@ async fn confidential_transfer_with_option( token }; + let ciphertext_validity_proof_account_with_ciphertext = ProofAccountWithCiphertext { + proof_account: ciphertext_validity_proof_account, + ciphertext_lo: transfer_amount_auditor_ciphertext_lo, + ciphertext_hi: transfer_amount_auditor_ciphertext_hi, + }; + let result = transfer_token .confidential_transfer_transfer( source_account, destination_account, source_authority, Some(&equality_proof_account), - Some(&ciphertext_validity_proof_account), + Some(&ciphertext_validity_proof_account_with_ciphertext), Some(&range_proof_account), transfer_amount, None, @@ -1468,7 +1479,7 @@ async fn confidential_transfer_with_option( let TransferProofData { equality_proof_data, - ciphertext_validity_proof_data, + ciphertext_validity_proof_data_with_ciphertext, range_proof_data, } = transfer_account_info .generate_split_transfer_proof_data( @@ -1480,6 +1491,11 @@ async fn confidential_transfer_with_option( ) .unwrap(); + let transfer_amount_auditor_ciphertext_lo = + ciphertext_validity_proof_data_with_ciphertext.ciphertext_lo; + let transfer_amount_auditor_ciphertext_hi = + ciphertext_validity_proof_data_with_ciphertext.ciphertext_hi; + let equality_proof_context_account = Keypair::new(); let ciphertext_validity_proof_context_account = Keypair::new(); let range_proof_context_account = Keypair::new(); @@ -1503,7 +1519,7 @@ async fn confidential_transfer_with_option( .confidential_transfer_create_context_state_account( &ciphertext_validity_proof_context_account.pubkey(), &context_account_authority.pubkey(), - &ciphertext_validity_proof_data, + &ciphertext_validity_proof_data_with_ciphertext.proof_data, false, &[&ciphertext_validity_proof_context_account], ) @@ -1533,13 +1549,19 @@ async fn confidential_transfer_with_option( token }; + let ciphertext_validity_proof_account_with_ciphertext = ProofAccountWithCiphertext { + proof_account: ciphertext_validity_proof_context_proof_account, + ciphertext_lo: transfer_amount_auditor_ciphertext_lo, + ciphertext_hi: transfer_amount_auditor_ciphertext_hi, + }; + let result = transfer_token .confidential_transfer_transfer( source_account, destination_account, source_authority, Some(&equality_proof_context_proof_account), - Some(&ciphertext_validity_proof_context_proof_account), + Some(&ciphertext_validity_proof_account_with_ciphertext), Some(&range_proof_context_proof_account), transfer_amount, None, @@ -1892,7 +1914,7 @@ async fn confidential_transfer_with_fee_with_option( let TransferWithFeeProofData { equality_proof_data, - transfer_amount_ciphertext_validity_proof_data, + transfer_amount_ciphertext_validity_proof_data_with_ciphertext, percentage_with_cap_proof_data, fee_ciphertext_validity_proof_data, range_proof_data, @@ -1909,6 +1931,11 @@ async fn confidential_transfer_with_fee_with_option( ) .unwrap(); + let transfer_amount_auditor_ciphertext_lo = + transfer_amount_ciphertext_validity_proof_data_with_ciphertext.ciphertext_lo; + let transfer_amount_auditor_ciphertext_hi = + transfer_amount_ciphertext_validity_proof_data_with_ciphertext.ciphertext_hi; + let equality_proof_record_account = Keypair::new(); let transfer_amount_ciphertext_validity_proof_record_account = Keypair::new(); let fee_sigma_proof_record_account = Keypair::new(); @@ -1936,7 +1963,7 @@ async fn confidential_transfer_with_fee_with_option( .confidential_transfer_create_record_account( &transfer_amount_ciphertext_validity_proof_record_account.pubkey(), &record_account_authority.pubkey(), - &transfer_amount_ciphertext_validity_proof_data, + &transfer_amount_ciphertext_validity_proof_data_with_ciphertext.proof_data, &transfer_amount_ciphertext_validity_proof_record_account, &record_account_authority, ) @@ -2002,13 +2029,20 @@ async fn confidential_transfer_with_fee_with_option( token }; + let transfer_amount_ciphertext_validity_proof_account_with_ciphertext = + ProofAccountWithCiphertext { + proof_account: transfer_amount_ciphertext_validity_proof_account, + ciphertext_lo: transfer_amount_auditor_ciphertext_lo, + ciphertext_hi: transfer_amount_auditor_ciphertext_hi, + }; + let result = transfer_token .confidential_transfer_transfer_with_fee( source_account, destination_account, source_authority, Some(&equality_proof_account), - Some(&transfer_amount_ciphertext_validity_proof_account), + Some(&transfer_amount_ciphertext_validity_proof_account_with_ciphertext), Some(&fee_sigma_proof_account), Some(&fee_ciphertext_validity_proof_account), Some(&range_proof_account), @@ -2086,7 +2120,7 @@ async fn confidential_transfer_with_fee_with_option( let TransferWithFeeProofData { equality_proof_data, - transfer_amount_ciphertext_validity_proof_data, + transfer_amount_ciphertext_validity_proof_data_with_ciphertext, percentage_with_cap_proof_data, fee_ciphertext_validity_proof_data, range_proof_data, @@ -2103,6 +2137,11 @@ async fn confidential_transfer_with_fee_with_option( ) .unwrap(); + let transfer_amount_auditor_ciphertext_lo = + transfer_amount_ciphertext_validity_proof_data_with_ciphertext.ciphertext_lo; + let transfer_amount_auditor_ciphertext_hi = + transfer_amount_ciphertext_validity_proof_data_with_ciphertext.ciphertext_hi; + let equality_proof_context_account = Keypair::new(); let transfer_amount_ciphertext_validity_proof_context_account = Keypair::new(); let percentage_with_cap_proof_context_account = Keypair::new(); @@ -2128,7 +2167,7 @@ async fn confidential_transfer_with_fee_with_option( .confidential_transfer_create_context_state_account( &transfer_amount_ciphertext_validity_proof_context_account.pubkey(), &context_account_authority.pubkey(), - &transfer_amount_ciphertext_validity_proof_data, + &transfer_amount_ciphertext_validity_proof_data_with_ciphertext.proof_data, false, &[&transfer_amount_ciphertext_validity_proof_context_account], ) @@ -2189,13 +2228,20 @@ async fn confidential_transfer_with_fee_with_option( token }; + let transfer_amount_ciphertext_validity_proof_account_with_ciphertext = + ProofAccountWithCiphertext { + proof_account: transfer_amount_ciphertext_validity_proof_context_proof_account, + ciphertext_lo: transfer_amount_auditor_ciphertext_lo, + ciphertext_hi: transfer_amount_auditor_ciphertext_hi, + }; + let result = transfer_token .confidential_transfer_transfer_with_fee( source_account, destination_account, source_authority, Some(&equality_proof_context_proof_account), - Some(&transfer_amount_ciphertext_validity_proof_context_proof_account), + Some(&transfer_amount_ciphertext_validity_proof_account_with_ciphertext), Some(&fee_sigma_proof_context_proof_account), Some(&fee_ciphertext_validity_proof_context_proof_account), Some(&range_proof_context_proof_account), diff --git a/token/program-2022/src/error.rs b/token/program-2022/src/error.rs index 54e4e250190..832ccf65715 100644 --- a/token/program-2022/src/error.rs +++ b/token/program-2022/src/error.rs @@ -465,6 +465,7 @@ impl From for TokenError { TokenProofGenerationError::NotEnoughFunds => TokenError::InsufficientFunds, TokenProofGenerationError::IllegalAmountBitLength => TokenError::IllegalBitLength, TokenProofGenerationError::FeeCalculation => TokenError::FeeCalculation, + TokenProofGenerationError::CiphertextExtraction => TokenError::MalformedCiphertext, } } } diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 3fd523d18c2..2616ccb941e 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -1,6 +1,8 @@ #[cfg(feature = "serde-traits")] use { - crate::serialization::{aeciphertext_fromstr, elgamalpubkey_fromstr}, + crate::serialization::{ + aeciphertext_fromstr, elgamalciphertext_fromstr, elgamalpubkey_fromstr, + }, serde::{Deserialize, Serialize}, }; use { @@ -16,7 +18,10 @@ use { program_error::ProgramError, pubkey::Pubkey, }, - solana_zk_sdk::encryption::pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey}, + solana_zk_sdk::encryption::pod::{ + auth_encryption::PodAeCiphertext, + elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, + }, }; #[cfg(not(target_os = "solana"))] use { @@ -229,6 +234,12 @@ pub struct MintInstructionData { /// The new decryptable supply if the mint succeeds #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_decryptable_supply: PodAeCiphertext, + /// The transfer amount encrypted under the auditor ElGamal public key + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] + pub mint_amount_auditor_ciphertext_lo: PodElGamalCiphertext, + /// The transfer amount encrypted under the auditor ElGamal public key + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] + pub mint_amount_auditor_ciphertext_hi: PodElGamalCiphertext, /// Relative location of the /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction /// to the `ConfidentialMint` instruction in the transaction. 0 if the @@ -254,6 +265,12 @@ pub struct BurnInstructionData { /// The new decryptable balance of the burner if the burn succeeds #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_decryptable_available_balance: DecryptableBalance, + /// The transfer amount encrypted under the auditor ElGamal public key + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] + pub burn_amount_auditor_ciphertext_lo: PodElGamalCiphertext, + /// The transfer amount encrypted under the auditor ElGamal public key + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] + pub burn_amount_auditor_ciphertext_hi: PodElGamalCiphertext, /// Relative location of the /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction /// to the `ConfidentialMint` instruction in the transaction. 0 if the @@ -391,6 +408,8 @@ pub fn confidential_mint_with_split_proofs( token_account: &Pubkey, mint: &Pubkey, supply_elgamal_pubkey: Option, + mint_amount_auditor_ciphertext_lo: &PodElGamalCiphertext, + mint_amount_auditor_ciphertext_hi: &PodElGamalCiphertext, authority: &Pubkey, multisig_signers: &[&Pubkey], equality_proof_location: ProofLocation, @@ -455,6 +474,8 @@ pub fn confidential_mint_with_split_proofs( ConfidentialMintBurnInstruction::Mint, &MintInstructionData { new_decryptable_supply: new_decryptable_supply.into(), + mint_amount_auditor_ciphertext_lo: *mint_amount_auditor_ciphertext_lo, + mint_amount_auditor_ciphertext_hi: *mint_amount_auditor_ciphertext_hi, equality_proof_instruction_offset, ciphertext_validity_proof_instruction_offset, range_proof_instruction_offset, @@ -475,6 +496,8 @@ pub fn confidential_burn_with_split_proofs( mint: &Pubkey, supply_elgamal_pubkey: Option, new_decryptable_available_balance: DecryptableBalance, + burn_amount_auditor_ciphertext_lo: &PodElGamalCiphertext, + burn_amount_auditor_ciphertext_hi: &PodElGamalCiphertext, authority: &Pubkey, multisig_signers: &[&Pubkey], equality_proof_location: ProofLocation, @@ -537,6 +560,8 @@ pub fn confidential_burn_with_split_proofs( ConfidentialMintBurnInstruction::Burn, &BurnInstructionData { new_decryptable_available_balance, + burn_amount_auditor_ciphertext_lo: *burn_amount_auditor_ciphertext_lo, + burn_amount_auditor_ciphertext_hi: *burn_amount_auditor_ciphertext_hi, equality_proof_instruction_offset, ciphertext_validity_proof_instruction_offset, range_proof_instruction_offset, diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index f0697337783..676d6404286 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -2,7 +2,7 @@ use spl_token_confidential_transfer_ciphertext_arithmetic as ciphertext_arithmetic; use { crate::{ - check_program_account, + check_auditor_ciphertext, check_program_account, error::TokenError, extension::{ confidential_mint_burn::{ @@ -210,12 +210,28 @@ fn process_confidential_mint( } } + let proof_context_auditor_ciphertext_lo = proof_context + .mint_amount_ciphertext_lo + .try_extract_ciphertext(2) + .map_err(TokenError::from)?; + let proof_context_auditor_ciphertext_hi = proof_context + .mint_amount_ciphertext_hi + .try_extract_ciphertext(2) + .map_err(TokenError::from)?; + + check_auditor_ciphertext( + &data.mint_amount_auditor_ciphertext_lo, + &data.mint_amount_auditor_ciphertext_hi, + &proof_context_auditor_ciphertext_lo, + &proof_context_auditor_ciphertext_hi, + )?; + confidential_transfer_account.pending_balance_lo = ciphertext_arithmetic::add( &confidential_transfer_account.pending_balance_lo, &proof_context .mint_amount_ciphertext_lo .try_extract_ciphertext(0) - .map_err(|_| ProgramError::InvalidAccountData)?, + .map_err(TokenError::from)?, ) .ok_or(TokenError::CiphertextArithmeticFailed)?; confidential_transfer_account.pending_balance_hi = ciphertext_arithmetic::add( @@ -223,7 +239,7 @@ fn process_confidential_mint( &proof_context .mint_amount_ciphertext_hi .try_extract_ciphertext(0) - .map_err(|_| ProgramError::InvalidAccountData)?, + .map_err(TokenError::from)?, ) .ok_or(TokenError::CiphertextArithmeticFailed)?; @@ -311,14 +327,30 @@ fn process_confidential_burn( return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } + let proof_context_auditor_ciphertext_lo = proof_context + .burn_amount_ciphertext_lo + .try_extract_ciphertext(2) + .map_err(TokenError::from)?; + let proof_context_auditor_ciphertext_hi = proof_context + .burn_amount_ciphertext_hi + .try_extract_ciphertext(2) + .map_err(TokenError::from)?; + + check_auditor_ciphertext( + &data.burn_amount_auditor_ciphertext_lo, + &data.burn_amount_auditor_ciphertext_hi, + &proof_context_auditor_ciphertext_lo, + &proof_context_auditor_ciphertext_hi, + )?; + let burn_amount_lo = &proof_context .burn_amount_ciphertext_lo .try_extract_ciphertext(0) - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(TokenError::from)?; let burn_amount_hi = &proof_context .burn_amount_ciphertext_hi .try_extract_ciphertext(0) - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(TokenError::from)?; let new_source_available_balance = ciphertext_arithmetic::subtract_with_lo_hi( &confidential_transfer_account.available_balance, diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index a6e07793800..152302df3ba 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -3,7 +3,7 @@ pub use solana_zk_sdk::zk_elgamal_proof_program::{ }; #[cfg(feature = "serde-traits")] use { - crate::serialization::aeciphertext_fromstr, + crate::serialization::{aeciphertext_fromstr, elgamalciphertext_fromstr}, serde::{Deserialize, Serialize}, }; use { @@ -619,6 +619,12 @@ pub struct TransferInstructionData { /// The new source decryptable balance if the transfer succeeds #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_source_decryptable_available_balance: DecryptableBalance, + /// The transfer amount encrypted under the auditor ElGamal public key + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] + pub transfer_amount_auditor_ciphertext_lo: PodElGamalCiphertext, + /// The transfer amount encrypted under the auditor ElGamal public key + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] + pub transfer_amount_auditor_ciphertext_hi: PodElGamalCiphertext, /// Relative location of the /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction /// to the `Transfer` instruction in the transaction. If the offset is @@ -658,6 +664,12 @@ pub struct TransferWithFeeInstructionData { /// The new source decryptable balance if the transfer succeeds #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_source_decryptable_available_balance: DecryptableBalance, + /// The transfer amount encrypted under the auditor ElGamal public key + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] + pub transfer_amount_auditor_ciphertext_lo: PodElGamalCiphertext, + /// The transfer amount encrypted under the auditor ElGamal public key + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] + pub transfer_amount_auditor_ciphertext_hi: PodElGamalCiphertext, /// Relative location of the /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction /// to the `TransferWithFee` instruction in the transaction. If the offset @@ -1151,6 +1163,8 @@ pub fn inner_transfer( mint: &Pubkey, destination_token_account: &Pubkey, new_source_decryptable_available_balance: DecryptableBalance, + transfer_amount_auditor_ciphertext_lo: &PodElGamalCiphertext, + transfer_amount_auditor_ciphertext_hi: &PodElGamalCiphertext, authority: &Pubkey, multisig_signers: &[&Pubkey], equality_proof_data_location: ProofLocation, @@ -1231,6 +1245,8 @@ pub fn inner_transfer( ConfidentialTransferInstruction::Transfer, &TransferInstructionData { new_source_decryptable_available_balance, + transfer_amount_auditor_ciphertext_lo: *transfer_amount_auditor_ciphertext_lo, + transfer_amount_auditor_ciphertext_hi: *transfer_amount_auditor_ciphertext_hi, equality_proof_instruction_offset, ciphertext_validity_proof_instruction_offset, range_proof_instruction_offset, @@ -1246,6 +1262,8 @@ pub fn transfer( mint: &Pubkey, destination_token_account: &Pubkey, new_source_decryptable_available_balance: DecryptableBalance, + transfer_amount_auditor_ciphertext_lo: &PodElGamalCiphertext, + transfer_amount_auditor_ciphertext_hi: &PodElGamalCiphertext, authority: &Pubkey, multisig_signers: &[&Pubkey], equality_proof_data_location: ProofLocation, @@ -1260,6 +1278,8 @@ pub fn transfer( mint, destination_token_account, new_source_decryptable_available_balance, + transfer_amount_auditor_ciphertext_lo, + transfer_amount_auditor_ciphertext_hi, authority, multisig_signers, equality_proof_data_location, @@ -1484,6 +1504,8 @@ pub fn inner_transfer_with_fee( mint: &Pubkey, destination_token_account: &Pubkey, new_source_decryptable_available_balance: DecryptableBalance, + transfer_amount_auditor_ciphertext_lo: &PodElGamalCiphertext, + transfer_amount_auditor_ciphertext_hi: &PodElGamalCiphertext, authority: &Pubkey, multisig_signers: &[&Pubkey], equality_proof_data_location: ProofLocation, @@ -1597,6 +1619,8 @@ pub fn inner_transfer_with_fee( ConfidentialTransferInstruction::TransferWithFee, &TransferWithFeeInstructionData { new_source_decryptable_available_balance, + transfer_amount_auditor_ciphertext_lo: *transfer_amount_auditor_ciphertext_lo, + transfer_amount_auditor_ciphertext_hi: *transfer_amount_auditor_ciphertext_hi, equality_proof_instruction_offset, transfer_amount_ciphertext_validity_proof_instruction_offset, fee_sigma_proof_instruction_offset, @@ -1614,6 +1638,8 @@ pub fn transfer_with_fee( mint: &Pubkey, destination_token_account: &Pubkey, new_source_decryptable_available_balance: DecryptableBalance, + transfer_amount_auditor_ciphertext_lo: &PodElGamalCiphertext, + transfer_amount_auditor_ciphertext_hi: &PodElGamalCiphertext, authority: &Pubkey, multisig_signers: &[&Pubkey], equality_proof_data_location: ProofLocation, @@ -1632,6 +1658,8 @@ pub fn transfer_with_fee( mint, destination_token_account, new_source_decryptable_available_balance, + transfer_amount_auditor_ciphertext_lo, + transfer_amount_auditor_ciphertext_hi, authority, multisig_signers, equality_proof_data_location, diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index c387dedff1f..1622ad1abb8 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -7,7 +7,7 @@ use { }; use { crate::{ - check_elgamal_registry_program_account, check_program_account, + check_auditor_ciphertext, check_elgamal_registry_program_account, check_program_account, error::TokenError, extension::{ confidential_transfer::{instruction::*, verify_proof::*, *}, @@ -591,6 +591,8 @@ fn process_transfer( program_id: &Pubkey, accounts: &[AccountInfo], new_source_decryptable_available_balance: DecryptableBalance, + transfer_amount_auditor_ciphertext_lo: &PodElGamalCiphertext, + transfer_amount_auditor_ciphertext_hi: &PodElGamalCiphertext, equality_proof_instruction_offset: i64, transfer_amount_ciphertext_validity_proof_instruction_offset: i64, fee_sigma_proof_instruction_offset: Option, @@ -643,6 +645,22 @@ fn process_transfer( return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } + let proof_context_auditor_ciphertext_lo = proof_context + .ciphertext_lo + .try_extract_ciphertext(2) + .map_err(|e| -> TokenError { e.into() })?; + let proof_context_auditor_ciphertext_hi = proof_context + .ciphertext_hi + .try_extract_ciphertext(2) + .map_err(|e| -> TokenError { e.into() })?; + + check_auditor_ciphertext( + transfer_amount_auditor_ciphertext_lo, + transfer_amount_auditor_ciphertext_hi, + &proof_context_auditor_ciphertext_lo, + &proof_context_auditor_ciphertext_hi, + )?; + process_source_for_transfer( program_id, source_account_info, @@ -708,6 +726,22 @@ fn process_transfer( return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } + let proof_context_auditor_ciphertext_lo = proof_context + .ciphertext_lo + .try_extract_ciphertext(2) + .map_err(TokenError::from)?; + let proof_context_auditor_ciphertext_hi = proof_context + .ciphertext_hi + .try_extract_ciphertext(2) + .map_err(TokenError::from)?; + + check_auditor_ciphertext( + transfer_amount_auditor_ciphertext_lo, + transfer_amount_auditor_ciphertext_hi, + &proof_context_auditor_ciphertext_lo, + &proof_context_auditor_ciphertext_hi, + )?; + process_source_for_transfer_with_fee( program_id, source_account_info, @@ -818,11 +852,11 @@ fn process_source_for_transfer( let source_transfer_amount_lo = proof_context .ciphertext_lo .try_extract_ciphertext(0) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; let source_transfer_amount_hi = proof_context .ciphertext_hi .try_extract_ciphertext(0) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; let new_source_available_balance = ciphertext_arithmetic::subtract_with_lo_hi( &confidential_transfer_account.available_balance, @@ -880,11 +914,11 @@ fn process_destination_for_transfer( let destination_ciphertext_lo = proof_context .ciphertext_lo .try_extract_ciphertext(1) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; let destination_ciphertext_hi = proof_context .ciphertext_hi .try_extract_ciphertext(1) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; destination_confidential_transfer_account.pending_balance_lo = ciphertext_arithmetic::add( &destination_confidential_transfer_account.pending_balance_lo, @@ -956,11 +990,11 @@ fn process_source_for_transfer_with_fee( let source_transfer_amount_lo = proof_context .ciphertext_lo .try_extract_ciphertext(0) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; let source_transfer_amount_hi = proof_context .ciphertext_hi .try_extract_ciphertext(0) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; let new_source_available_balance = ciphertext_arithmetic::subtract_with_lo_hi( &confidential_transfer_account.available_balance, @@ -1019,11 +1053,11 @@ fn process_destination_for_transfer_with_fee( let destination_transfer_amount_lo = proof_context .ciphertext_lo .try_extract_ciphertext(1) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; let destination_transfer_amount_hi = proof_context .ciphertext_hi .try_extract_ciphertext(1) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; destination_confidential_transfer_account.pending_balance_lo = ciphertext_arithmetic::add( &destination_confidential_transfer_account.pending_balance_lo, @@ -1046,11 +1080,11 @@ fn process_destination_for_transfer_with_fee( let destination_fee_lo = proof_context .fee_ciphertext_lo .try_extract_ciphertext(0) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; let destination_fee_hi = proof_context .fee_ciphertext_hi .try_extract_ciphertext(0) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; // Subtract the fee amount from the destination pending balance destination_confidential_transfer_account.pending_balance_lo = @@ -1071,11 +1105,11 @@ fn process_destination_for_transfer_with_fee( let withdraw_withheld_authority_fee_lo = proof_context .fee_ciphertext_lo .try_extract_ciphertext(1) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; let withdraw_withheld_authority_fee_hi = proof_context .fee_ciphertext_hi .try_extract_ciphertext(1) - .map_err(|e| -> TokenError { e.into() })?; + .map_err(TokenError::from)?; let destination_confidential_transfer_fee_amount = destination_token_account.get_extension_mut::()?; @@ -1291,6 +1325,8 @@ pub(crate) fn process_instruction( program_id, accounts, data.new_source_decryptable_available_balance, + &data.transfer_amount_auditor_ciphertext_lo, + &data.transfer_amount_auditor_ciphertext_hi, data.equality_proof_instruction_offset as i64, data.ciphertext_validity_proof_instruction_offset as i64, None, @@ -1341,6 +1377,8 @@ pub(crate) fn process_instruction( program_id, accounts, data.new_source_decryptable_available_balance, + &data.transfer_amount_auditor_ciphertext_lo, + &data.transfer_amount_auditor_ciphertext_hi, data.equality_proof_instruction_offset as i64, data.transfer_amount_ciphertext_validity_proof_instruction_offset as i64, Some(data.fee_sigma_proof_instruction_offset as i64), diff --git a/token/program-2022/src/lib.rs b/token/program-2022/src/lib.rs index d413d5903a5..d803ff4a0ba 100644 --- a/token/program-2022/src/lib.rs +++ b/token/program-2022/src/lib.rs @@ -23,8 +23,12 @@ mod entrypoint; // Export current sdk types for downstream users building with a different sdk // version -use solana_program::{ - entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, system_program, +use { + error::TokenError, + solana_program::{ + entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, system_program, + }, + solana_zk_sdk::encryption::pod::elgamal::PodElGamalCiphertext, }; pub use {solana_program, solana_zk_sdk}; @@ -139,3 +143,20 @@ pub(crate) fn check_elgamal_registry_program_account( } Ok(()) } + +/// Check instruction data and proof data auditor ciphertext consistency +#[cfg(feature = "zk-ops")] +pub(crate) fn check_auditor_ciphertext( + instruction_data_auditor_ciphertext_lo: &PodElGamalCiphertext, + instruction_data_auditor_ciphertext_hi: &PodElGamalCiphertext, + proof_context_auditor_ciphertext_lo: &PodElGamalCiphertext, + proof_context_auditor_ciphertext_hi: &PodElGamalCiphertext, +) -> ProgramResult { + if instruction_data_auditor_ciphertext_lo != proof_context_auditor_ciphertext_lo { + return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); + } + if instruction_data_auditor_ciphertext_hi != proof_context_auditor_ciphertext_hi { + return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); + } + Ok(()) +} diff --git a/token/program-2022/src/serialization.rs b/token/program-2022/src/serialization.rs index 74d4f642adb..8d337c1903d 100644 --- a/token/program-2022/src/serialization.rs +++ b/token/program-2022/src/serialization.rs @@ -76,7 +76,7 @@ pub mod coption_fromstr { } } -/// helper to ser/deser AeCiphertext values +/// helper to ser/deser PodAeCiphertext values pub mod aeciphertext_fromstr { use { serde::{ @@ -121,7 +121,7 @@ pub mod aeciphertext_fromstr { } } -/// helper to ser/deser pod::ElGamalPubkey values +/// helper to ser/deser PodElGamalPubkey values pub mod elgamalpubkey_fromstr { use { serde::{ @@ -165,3 +165,48 @@ pub mod elgamalpubkey_fromstr { d.deserialize_str(ElGamalPubkeyVisitor) } } + +/// helper to ser/deser PodElGamalCiphertext values +pub mod elgamalciphertext_fromstr { + use { + serde::{ + de::{Error, Visitor}, + Deserializer, Serializer, + }, + solana_zk_sdk::encryption::pod::elgamal::PodElGamalCiphertext, + std::{fmt, str::FromStr}, + }; + + /// serialize ElGamalCiphertext values supporting Display trait + pub fn serialize(x: &PodElGamalCiphertext, s: S) -> Result + where + S: Serializer, + { + s.serialize_str(&x.to_string()) + } + + struct ElGamalCiphertextVisitor; + + impl<'de> Visitor<'de> for ElGamalCiphertextVisitor { + type Value = PodElGamalCiphertext; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a FromStr type") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + FromStr::from_str(v).map_err(Error::custom) + } + } + + /// deserialize ElGamalCiphertext values from str + pub fn deserialize<'de, D>(d: D) -> Result + where + D: Deserializer<'de>, + { + d.deserialize_str(ElGamalCiphertextVisitor) + } +}