safe_token_2022/extension/confidential_transfer/
processor.rs

1use {
2    crate::{
3        check_program_account,
4        error::TokenError,
5        extension::{
6            confidential_transfer::{instruction::*, *},
7            BaseStateWithExtensions, StateWithExtensions, StateWithExtensionsMut,
8        },
9        instruction::{decode_instruction_data, decode_instruction_type},
10        processor::Processor,
11        state::{Account, Mint},
12    },
13    solana_program::{
14        account_info::{next_account_info, AccountInfo},
15        entrypoint::ProgramResult,
16        instruction::Instruction,
17        msg,
18        program_error::ProgramError,
19        pubkey::Pubkey,
20        sysvar::instructions::get_instruction_relative,
21    },
22    safe_zk_token_sdk::zk_token_proof_program,
23};
24// Remove feature once zk ops syscalls are enabled on all networks
25#[cfg(feature = "zk-ops")]
26use {
27    crate::extension::{
28        memo_transfer::{check_previous_sibling_instruction_is_memo, memo_required},
29        non_transferable::NonTransferable,
30        transfer_fee::TransferFeeConfig,
31    },
32    solana_program::{clock::Clock, sysvar::Sysvar},
33    safe_zk_token_sdk::zk_token_elgamal::ops as syscall,
34};
35
36/// Decodes the zero-knowledge proof instruction associated with the token instruction.
37///
38/// `ConfigureAccount`, `EmptyAccount`, `Withdraw`, `Transfer`, `WithdrawWithheldTokensFromMint`,
39/// and `WithdrawWithheldTokensFromAccounts` instructions require corresponding zero-knowledge
40/// proof instructions.
41fn decode_proof_instruction<T: Pod>(
42    expected: ProofInstruction,
43    instruction: &Instruction,
44) -> Result<&T, ProgramError> {
45    if instruction.program_id != zk_token_proof_program::id()
46        || ProofInstruction::decode_type(&instruction.data) != Some(expected)
47    {
48        msg!("Unexpected proof instruction");
49        return Err(ProgramError::InvalidInstructionData);
50    }
51
52    ProofInstruction::decode_data(&instruction.data).ok_or(ProgramError::InvalidInstructionData)
53}
54
55/// Processes an [InitializeMint] instruction.
56fn process_initialize_mint(
57    accounts: &[AccountInfo],
58    authority: &OptionalNonZeroPubkey,
59    auto_approve_new_account: PodBool,
60    auditor_encryption_pubkey: &OptionalNonZeroEncryptionPubkey,
61    withdraw_withheld_authority_encryption_pubkey: &OptionalNonZeroEncryptionPubkey,
62) -> ProgramResult {
63    let account_info_iter = &mut accounts.iter();
64    let mint_info = next_account_info(account_info_iter)?;
65
66    check_program_account(mint_info.owner)?;
67    let mint_data = &mut mint_info.data.borrow_mut();
68    let mut mint = StateWithExtensionsMut::<Mint>::unpack_uninitialized(mint_data)?;
69    let confidential_transfer_mint = mint.init_extension::<ConfidentialTransferMint>(true)?;
70
71    confidential_transfer_mint.authority = *authority;
72    confidential_transfer_mint.auto_approve_new_accounts = auto_approve_new_account;
73    confidential_transfer_mint.auditor_encryption_pubkey = *auditor_encryption_pubkey;
74    confidential_transfer_mint.withdraw_withheld_authority_encryption_pubkey =
75        *withdraw_withheld_authority_encryption_pubkey;
76    confidential_transfer_mint.withheld_amount = EncryptedWithheldAmount::zeroed();
77
78    Ok(())
79}
80
81/// Processes an [UpdateMint] instruction.
82fn process_update_mint(
83    accounts: &[AccountInfo],
84    auto_approve_new_account: PodBool,
85    auditor_encryption_pubkey: &OptionalNonZeroEncryptionPubkey,
86) -> ProgramResult {
87    let account_info_iter = &mut accounts.iter();
88    let mint_info = next_account_info(account_info_iter)?;
89    let authority_info = next_account_info(account_info_iter)?;
90
91    check_program_account(mint_info.owner)?;
92    let mint_data = &mut mint_info.data.borrow_mut();
93    let mut mint = StateWithExtensionsMut::<Mint>::unpack(mint_data)?;
94    let confidential_transfer_mint = mint.get_extension_mut::<ConfidentialTransferMint>()?;
95    let maybe_confidential_transfer_mint_authority: Option<Pubkey> =
96        confidential_transfer_mint.authority.into();
97    let confidential_transfer_mint_authority =
98        maybe_confidential_transfer_mint_authority.ok_or(TokenError::NoAuthorityExists)?;
99
100    if !authority_info.is_signer {
101        return Err(ProgramError::MissingRequiredSignature);
102    }
103
104    if confidential_transfer_mint_authority != *authority_info.key {
105        return Err(TokenError::OwnerMismatch.into());
106    }
107
108    confidential_transfer_mint.auto_approve_new_accounts = auto_approve_new_account;
109    confidential_transfer_mint.auditor_encryption_pubkey = *auditor_encryption_pubkey;
110    Ok(())
111}
112
113/// Processes a [ConfigureAccount] instruction.
114fn process_configure_account(
115    program_id: &Pubkey,
116    accounts: &[AccountInfo],
117    decryptable_zero_balance: &DecryptableBalance,
118    maximum_pending_balance_credit_counter: &PodU64,
119    proof_instruction_offset: i64,
120) -> ProgramResult {
121    let account_info_iter = &mut accounts.iter();
122    let token_account_info = next_account_info(account_info_iter)?;
123    let mint_info = next_account_info(account_info_iter)?;
124    let instructions_sysvar_info = next_account_info(account_info_iter)?;
125    let authority_info = next_account_info(account_info_iter)?;
126    let authority_info_data_len = authority_info.data_len();
127
128    check_program_account(token_account_info.owner)?;
129    let token_account_data = &mut token_account_info.data.borrow_mut();
130    let mut token_account = StateWithExtensionsMut::<Account>::unpack(token_account_data)?;
131
132    if token_account.base.mint != *mint_info.key {
133        return Err(TokenError::MintMismatch.into());
134    }
135
136    Processor::validate_owner(
137        program_id,
138        &token_account.base.owner,
139        authority_info,
140        authority_info_data_len,
141        account_info_iter.as_slice(),
142    )?;
143
144    check_program_account(mint_info.owner)?;
145    let mint_data = &mut mint_info.data.borrow();
146    let mint = StateWithExtensions::<Mint>::unpack(mint_data)?;
147    let confidential_transfer_mint = mint.get_extension::<ConfidentialTransferMint>()?;
148
149    // zero-knowledge proof certifies that the supplied encryption (ElGamal) public key is valid
150    let zkp_instruction =
151        get_instruction_relative(proof_instruction_offset, instructions_sysvar_info)?;
152    let proof_data = decode_proof_instruction::<PubkeyValidityData>(
153        ProofInstruction::VerifyPubkeyValidity,
154        &zkp_instruction,
155    )?;
156
157    // Note: The caller is expected to use the `Reallocate` instruction to ensure there is
158    // sufficient room in their token account for the new `ConfidentialTransferAccount` extension
159    let mut confidential_transfer_account =
160        token_account.init_extension::<ConfidentialTransferAccount>(false)?;
161    confidential_transfer_account.approved = confidential_transfer_mint.auto_approve_new_accounts;
162    confidential_transfer_account.encryption_pubkey = proof_data.pubkey;
163    confidential_transfer_account.maximum_pending_balance_credit_counter =
164        *maximum_pending_balance_credit_counter;
165
166    // The all-zero ciphertext [0; 64] is a valid encryption of zero
167    confidential_transfer_account.pending_balance_lo = EncryptedBalance::zeroed();
168    confidential_transfer_account.pending_balance_hi = EncryptedBalance::zeroed();
169    confidential_transfer_account.available_balance = EncryptedBalance::zeroed();
170
171    confidential_transfer_account.decryptable_available_balance = *decryptable_zero_balance;
172    confidential_transfer_account.allow_confidential_credits = true.into();
173    confidential_transfer_account.pending_balance_credit_counter = 0.into();
174    confidential_transfer_account.expected_pending_balance_credit_counter = 0.into();
175    confidential_transfer_account.actual_pending_balance_credit_counter = 0.into();
176    confidential_transfer_account.allow_non_confidential_credits = true.into();
177    confidential_transfer_account.withheld_amount = EncryptedWithheldAmount::zeroed();
178
179    Ok(())
180}
181
182/// Processes an [ApproveAccount] instruction.
183fn process_approve_account(accounts: &[AccountInfo]) -> ProgramResult {
184    let account_info_iter = &mut accounts.iter();
185    let token_account_info = next_account_info(account_info_iter)?;
186    let mint_info = next_account_info(account_info_iter)?;
187    let authority_info = next_account_info(account_info_iter)?;
188
189    check_program_account(token_account_info.owner)?;
190    let token_account_data = &mut token_account_info.data.borrow_mut();
191    let mut token_account = StateWithExtensionsMut::<Account>::unpack(token_account_data)?;
192
193    check_program_account(mint_info.owner)?;
194    let mint_data = &mint_info.data.borrow_mut();
195    let mint = StateWithExtensions::<Mint>::unpack(mint_data)?;
196    let confidential_transfer_mint = mint.get_extension::<ConfidentialTransferMint>()?;
197    let maybe_confidential_transfer_mint_authority: Option<Pubkey> =
198        confidential_transfer_mint.authority.into();
199    let confidential_transfer_mint_authority =
200        maybe_confidential_transfer_mint_authority.ok_or(TokenError::NoAuthorityExists)?;
201
202    if authority_info.is_signer && *authority_info.key == confidential_transfer_mint_authority {
203        let mut confidential_transfer_state =
204            token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
205        confidential_transfer_state.approved = true.into();
206        Ok(())
207    } else {
208        Err(ProgramError::MissingRequiredSignature)
209    }
210}
211
212/// Processes an [EmptyAccount] instruction.
213fn process_empty_account(
214    program_id: &Pubkey,
215    accounts: &[AccountInfo],
216    proof_instruction_offset: i64,
217) -> ProgramResult {
218    let account_info_iter = &mut accounts.iter();
219    let token_account_info = next_account_info(account_info_iter)?;
220    let instructions_sysvar_info = next_account_info(account_info_iter)?;
221    let authority_info = next_account_info(account_info_iter)?;
222    let authority_info_data_len = authority_info.data_len();
223
224    check_program_account(token_account_info.owner)?;
225    let token_account_data = &mut token_account_info.data.borrow_mut();
226    let mut token_account = StateWithExtensionsMut::<Account>::unpack(token_account_data)?;
227
228    Processor::validate_owner(
229        program_id,
230        &token_account.base.owner,
231        authority_info,
232        authority_info_data_len,
233        account_info_iter.as_slice(),
234    )?;
235
236    let mut confidential_transfer_account =
237        token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
238
239    // An account can be closed only if the remaining balance is zero. This means that for the
240    // confidential extension account, the ciphertexts associated with the following components
241    // must be an encryption of zero:
242    //   1. The pending balance
243    //   2. The available balance
244    //   3. The withheld balance
245    //
246    // For the pending and withheld balance ciphertexts, it suffices to check that they are
247    // all-zero ciphertexts (i.e. [0; 64]). If any of these ciphertexts are valid encryption of
248    // zero but not an all-zero ciphertext, then an `ApplyPendingBalance` or
249    // `HarvestWithheldTokensToMint` instructions can be used to flush-out these balances first.
250    //
251    // For the available balance, it is not possible to deduce whether the ciphertext encrypts zero
252    // or not by simply inspecting the ciphertext bytes (otherwise, this would violate
253    // confidentiality). The available balance is verified using a zero-knowledge proof.
254    let zkp_instruction =
255        get_instruction_relative(proof_instruction_offset, instructions_sysvar_info)?;
256    let proof_data = decode_proof_instruction::<CloseAccountData>(
257        ProofInstruction::VerifyCloseAccount,
258        &zkp_instruction,
259    )?;
260    // Check that the encryption public key and ciphertext associated with the confidential
261    // extension account are consistent with those that were actually used to generate the zkp.
262    if confidential_transfer_account.encryption_pubkey != proof_data.pubkey {
263        msg!("Encryption public-key mismatch");
264        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
265    }
266    if confidential_transfer_account.available_balance != proof_data.ciphertext {
267        msg!("Available balance mismatch");
268        return Err(ProgramError::InvalidInstructionData);
269    }
270    confidential_transfer_account.available_balance = EncryptedBalance::zeroed();
271
272    // check that all balances are all-zero ciphertexts
273    confidential_transfer_account.closable()?;
274
275    Ok(())
276}
277
278/// Processes a [Deposit] instruction.
279#[cfg(feature = "zk-ops")]
280fn process_deposit(
281    program_id: &Pubkey,
282    accounts: &[AccountInfo],
283    amount: u64,
284    expected_decimals: u8,
285) -> ProgramResult {
286    let account_info_iter = &mut accounts.iter();
287    let token_account_info = next_account_info(account_info_iter)?;
288    let mint_info = next_account_info(account_info_iter)?;
289    let authority_info = next_account_info(account_info_iter)?;
290    let authority_info_data_len = authority_info.data_len();
291
292    check_program_account(mint_info.owner)?;
293    let mint_data = &mint_info.data.borrow_mut();
294    let mint = StateWithExtensions::<Mint>::unpack(mint_data)?;
295
296    if expected_decimals != mint.base.decimals {
297        return Err(TokenError::MintDecimalsMismatch.into());
298    }
299
300    if mint.get_extension::<NonTransferable>().is_ok() {
301        return Err(TokenError::NonTransferable.into());
302    }
303
304    check_program_account(token_account_info.owner)?;
305    let token_account_data = &mut token_account_info.data.borrow_mut();
306    let mut token_account = StateWithExtensionsMut::<Account>::unpack(token_account_data)?;
307
308    Processor::validate_owner(
309        program_id,
310        &token_account.base.owner,
311        authority_info,
312        authority_info_data_len,
313        account_info_iter.as_slice(),
314    )?;
315
316    if token_account.base.is_frozen() {
317        return Err(TokenError::AccountFrozen.into());
318    }
319
320    if token_account.base.mint != *mint_info.key {
321        return Err(TokenError::MintMismatch.into());
322    }
323
324    // Wrapped SAFE deposits are not supported because lamports cannot be vanished.
325    assert!(!token_account.base.is_native());
326
327    token_account.base.amount = token_account
328        .base
329        .amount
330        .checked_sub(amount)
331        .ok_or(TokenError::Overflow)?;
332    token_account.pack_base();
333
334    let mut confidential_transfer_account =
335        token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
336    confidential_transfer_account.valid_as_destination()?;
337
338    // A deposit amount must be a 48-bit number
339    let (amount_lo, amount_hi) = verify_and_split_deposit_amount(amount)?;
340
341    // Prevent unnecessary ciphertext arithmetic syscalls if `amount_lo` or `amount_hi` is zero
342    if amount_lo > 0 {
343        confidential_transfer_account.pending_balance_lo =
344            syscall::add_to(&confidential_transfer_account.pending_balance_lo, amount_lo)
345                .ok_or(ProgramError::InvalidInstructionData)?;
346    }
347    if amount_hi > 0 {
348        confidential_transfer_account.pending_balance_hi =
349            syscall::add_to(&confidential_transfer_account.pending_balance_hi, amount_hi)
350                .ok_or(ProgramError::InvalidInstructionData)?;
351    }
352
353    confidential_transfer_account.increment_pending_balance_credit_counter()?;
354
355    Ok(())
356}
357
358/// Verifies that a deposit amount is a 48-bit number and returns the least significant 16 bits and
359/// most significant 32 bits of the amount.
360#[cfg(feature = "zk-ops")]
361fn verify_and_split_deposit_amount(amount: u64) -> Result<(u64, u64), TokenError> {
362    if amount >> MAXIMUM_DEPOSIT_TRANSFER_AMOUNT_BIT_LENGTH > 0 {
363        return Err(TokenError::MaximumDepositAmountExceeded);
364    }
365    let deposit_amount_lo =
366        amount << (64 - PENDING_BALANCE_LO_BIT_LENGTH) >> PENDING_BALANCE_HI_BIT_LENGTH;
367    let deposit_amount_hi = amount >> PENDING_BALANCE_LO_BIT_LENGTH;
368
369    Ok((deposit_amount_lo, deposit_amount_hi))
370}
371
372/// Processes a [Withdraw] instruction.
373#[cfg(feature = "zk-ops")]
374fn process_withdraw(
375    program_id: &Pubkey,
376    accounts: &[AccountInfo],
377    amount: u64,
378    expected_decimals: u8,
379    new_decryptable_available_balance: DecryptableBalance,
380    proof_instruction_offset: i64,
381) -> ProgramResult {
382    let account_info_iter = &mut accounts.iter();
383    let token_account_info = next_account_info(account_info_iter)?;
384    let mint_info = next_account_info(account_info_iter)?;
385    let instructions_sysvar_info = next_account_info(account_info_iter)?;
386    let authority_info = next_account_info(account_info_iter)?;
387    let authority_info_data_len = authority_info.data_len();
388
389    check_program_account(mint_info.owner)?;
390    let mint_data = &mint_info.data.borrow_mut();
391    let mint = StateWithExtensions::<Mint>::unpack(mint_data)?;
392
393    if expected_decimals != mint.base.decimals {
394        return Err(TokenError::MintDecimalsMismatch.into());
395    }
396
397    if mint.get_extension::<NonTransferable>().is_ok() {
398        return Err(TokenError::NonTransferable.into());
399    }
400
401    check_program_account(token_account_info.owner)?;
402    let token_account_data = &mut token_account_info.data.borrow_mut();
403    let mut token_account = StateWithExtensionsMut::<Account>::unpack(token_account_data)?;
404
405    Processor::validate_owner(
406        program_id,
407        &token_account.base.owner,
408        authority_info,
409        authority_info_data_len,
410        account_info_iter.as_slice(),
411    )?;
412
413    if token_account.base.is_frozen() {
414        return Err(TokenError::AccountFrozen.into());
415    }
416
417    if token_account.base.mint != *mint_info.key {
418        return Err(TokenError::MintMismatch.into());
419    }
420
421    // Wrapped SAFE withdrawals are not supported because lamports cannot be apparated.
422    assert!(!token_account.base.is_native());
423
424    let mut confidential_transfer_account =
425        token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
426    confidential_transfer_account.valid_as_source()?;
427
428    // Zero-knowledge proof certifies that the account has enough available balance to withdraw the
429    // amount.
430    let zkp_instruction =
431        get_instruction_relative(proof_instruction_offset, instructions_sysvar_info)?;
432    let proof_data = decode_proof_instruction::<WithdrawData>(
433        ProofInstruction::VerifyWithdraw,
434        &zkp_instruction,
435    )?;
436    // Check that the encryption public key associated with the confidential extension is
437    // consistent with the public key that was actually used to generate the zkp.
438    if confidential_transfer_account.encryption_pubkey != proof_data.pubkey {
439        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
440    }
441
442    // Prevent unnecessary ciphertext arithmetic syscalls if the withdraw amount is zero
443    if amount > 0 {
444        confidential_transfer_account.available_balance =
445            syscall::subtract_from(&confidential_transfer_account.available_balance, amount)
446                .ok_or(ProgramError::InvalidInstructionData)?;
447    }
448    // Check that the final available balance ciphertext is consistent with the actual ciphertext
449    // for which the zero-knowledge proof was generated for.
450    if confidential_transfer_account.available_balance != proof_data.final_ciphertext {
451        return Err(TokenError::ConfidentialTransferBalanceMismatch.into());
452    }
453
454    confidential_transfer_account.decryptable_available_balance = new_decryptable_available_balance;
455    token_account.base.amount = token_account
456        .base
457        .amount
458        .checked_add(amount)
459        .ok_or(TokenError::Overflow)?;
460    token_account.pack_base();
461
462    Ok(())
463}
464
465/// Processes an [Transfer] instruction.
466#[cfg(feature = "zk-ops")]
467fn process_transfer(
468    program_id: &Pubkey,
469    accounts: &[AccountInfo],
470    new_source_decryptable_available_balance: DecryptableBalance,
471    proof_instruction_offset: i64,
472) -> ProgramResult {
473    let account_info_iter = &mut accounts.iter();
474    let source_account_info = next_account_info(account_info_iter)?;
475    let destination_token_account_info = next_account_info(account_info_iter)?;
476    let mint_info = next_account_info(account_info_iter)?;
477    let instructions_sysvar_info = next_account_info(account_info_iter)?;
478    let authority_info = next_account_info(account_info_iter)?;
479
480    check_program_account(mint_info.owner)?;
481    let mint_data = &mint_info.data.borrow_mut();
482    let mint = StateWithExtensions::<Mint>::unpack(mint_data)?;
483
484    if mint.get_extension::<NonTransferable>().is_ok() {
485        return Err(TokenError::NonTransferable.into());
486    }
487    let confidential_transfer_mint = mint.get_extension::<ConfidentialTransferMint>()?;
488
489    // A `Transfer` instruction must be accompanied by a zero-knowledge proof instruction that
490    // certify the validity of the transfer amounts. The kind of zero-knowledge proof instruction
491    // depends on whether a transfer incurs a fee or not.
492    //   - If the mint is not extended for fees or the instruction is for a self-transfer, then
493    //   transfer fee is not required.
494    //   - If the mint is extended for fees and the instruction is not a self-transfer, then
495    //   transfer fee is required.
496    if mint.get_extension::<TransferFeeConfig>().is_err()
497        || source_account_info.key == destination_token_account_info.key
498    {
499        // Transfer fee is not required. Decode the zero-knowledge proof as `TransferData`.
500        //
501        // The zero-knowledge proof certifies that:
502        //   1. the transfer amount is encrypted in the correct form
503        //   2. the source account has enough balance to send the transfer amount
504        let zkp_instruction =
505            get_instruction_relative(proof_instruction_offset, instructions_sysvar_info)?;
506        let proof_data = decode_proof_instruction::<TransferData>(
507            ProofInstruction::VerifyTransfer,
508            &zkp_instruction,
509        )?;
510        // Check that the auditor encryption public key associated wth the confidential mint is
511        // consistent with what was actually used to generate the zkp.
512        if !confidential_transfer_mint
513            .auditor_encryption_pubkey
514            .equals(&proof_data.transfer_pubkeys.auditor_pubkey)
515        {
516            return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
517        }
518
519        let source_ciphertext_lo = EncryptedBalance::from((
520            proof_data.ciphertext_lo.commitment,
521            proof_data.ciphertext_lo.source_handle,
522        ));
523        let source_ciphertext_hi = EncryptedBalance::from((
524            proof_data.ciphertext_hi.commitment,
525            proof_data.ciphertext_hi.source_handle,
526        ));
527
528        process_source_for_transfer(
529            program_id,
530            source_account_info,
531            mint_info,
532            authority_info,
533            account_info_iter.as_slice(),
534            &proof_data.transfer_pubkeys.source_pubkey,
535            &source_ciphertext_lo,
536            &source_ciphertext_hi,
537            &proof_data.new_source_ciphertext,
538            new_source_decryptable_available_balance,
539        )?;
540
541        let destination_ciphertext_lo = EncryptedBalance::from((
542            proof_data.ciphertext_lo.commitment,
543            proof_data.ciphertext_lo.destination_handle,
544        ));
545        let destination_ciphertext_hi = EncryptedBalance::from((
546            proof_data.ciphertext_hi.commitment,
547            proof_data.ciphertext_hi.destination_handle,
548        ));
549
550        process_destination_for_transfer(
551            destination_token_account_info,
552            mint_info,
553            &proof_data.transfer_pubkeys.destination_pubkey,
554            &destination_ciphertext_lo,
555            &destination_ciphertext_hi,
556            None,
557        )?;
558    } else {
559        // Transfer fee is required. Decode the zero-knowledge proof as `TransferWithFeeData`.
560        //
561        // The zero-knowledge proof certifies that:
562        //   1. the transfer amount is encrypted in the correct form
563        //   2. the source account has enough balance to send the transfer amount
564        //   3. the transfer fee is computed correctly and encrypted in the correct form
565        let zkp_instruction =
566            get_instruction_relative(proof_instruction_offset, instructions_sysvar_info)?;
567        let proof_data = decode_proof_instruction::<TransferWithFeeData>(
568            ProofInstruction::VerifyTransferWithFee,
569            &zkp_instruction,
570        )?;
571        // Check that the encryption public keys associated with the confidential extension mint
572        // are consistent with the keys that were used to generate the zkp.
573        if !confidential_transfer_mint
574            .auditor_encryption_pubkey
575            .equals(&proof_data.transfer_with_fee_pubkeys.auditor_pubkey)
576        {
577            return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
578        }
579        if !confidential_transfer_mint
580            .withdraw_withheld_authority_encryption_pubkey
581            .equals(
582                &proof_data
583                    .transfer_with_fee_pubkeys
584                    .withdraw_withheld_authority_pubkey,
585            )
586        {
587            return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
588        }
589        // Check that the fee parameters in the mint are consistent with what were used to generate
590        // the zkp.
591        let transfer_fee_config = mint.get_extension::<TransferFeeConfig>()?;
592        let fee_parameters = transfer_fee_config.get_epoch_fee(Clock::get()?.epoch);
593        if u64::from(fee_parameters.maximum_fee) != u64::from(proof_data.fee_parameters.maximum_fee)
594            || u16::from(fee_parameters.transfer_fee_basis_points)
595                != u16::from(proof_data.fee_parameters.fee_rate_basis_points)
596        {
597            return Err(TokenError::FeeParametersMismatch.into());
598        }
599
600        // From the proof data, decode lo and hi transfer amounts encrypted under the source
601        // encryption public key
602        let source_transfer_amount_lo = EncryptedBalance::from((
603            proof_data.ciphertext_lo.commitment,
604            proof_data.ciphertext_lo.source_handle,
605        ));
606        let source_transfer_amount_hi = EncryptedBalance::from((
607            proof_data.ciphertext_hi.commitment,
608            proof_data.ciphertext_hi.source_handle,
609        ));
610
611        process_source_for_transfer(
612            program_id,
613            source_account_info,
614            mint_info,
615            authority_info,
616            account_info_iter.as_slice(),
617            &proof_data.transfer_with_fee_pubkeys.source_pubkey,
618            &source_transfer_amount_lo,
619            &source_transfer_amount_hi,
620            &proof_data.new_source_ciphertext,
621            new_source_decryptable_available_balance,
622        )?;
623
624        // From the proof datay, decode lo and hi transfer amounts encrypted under the destination
625        // encryption public key
626        let destination_transfer_amount_lo = EncryptedBalance::from((
627            proof_data.ciphertext_lo.commitment,
628            proof_data.ciphertext_lo.destination_handle,
629        ));
630        let destination_transfer_amount_hi = EncryptedBalance::from((
631            proof_data.ciphertext_hi.commitment,
632            proof_data.ciphertext_hi.destination_handle,
633        ));
634
635        process_destination_for_transfer(
636            destination_token_account_info,
637            mint_info,
638            &proof_data.transfer_with_fee_pubkeys.destination_pubkey,
639            &destination_transfer_amount_lo,
640            &destination_transfer_amount_hi,
641            Some((&proof_data.fee_ciphertext_lo, &proof_data.fee_ciphertext_hi)),
642        )?;
643    }
644
645    Ok(())
646}
647
648#[allow(clippy::too_many_arguments)]
649#[cfg(feature = "zk-ops")]
650fn process_source_for_transfer(
651    program_id: &Pubkey,
652    source_account_info: &AccountInfo,
653    mint_info: &AccountInfo,
654    authority_info: &AccountInfo,
655    signers: &[AccountInfo],
656    source_encryption_pubkey: &EncryptionPubkey,
657    source_transfer_amount_lo: &EncryptedBalance,
658    source_transfer_amount_hi: &EncryptedBalance,
659    expected_new_source_available_balance: &EncryptedBalance,
660    new_source_decryptable_available_balance: DecryptableBalance,
661) -> ProgramResult {
662    check_program_account(source_account_info.owner)?;
663    let authority_info_data_len = authority_info.data_len();
664    let token_account_data = &mut source_account_info.data.borrow_mut();
665    let mut token_account = StateWithExtensionsMut::<Account>::unpack(token_account_data)?;
666
667    Processor::validate_owner(
668        program_id,
669        &token_account.base.owner,
670        authority_info,
671        authority_info_data_len,
672        signers,
673    )?;
674
675    if token_account.base.is_frozen() {
676        return Err(TokenError::AccountFrozen.into());
677    }
678
679    if token_account.base.mint != *mint_info.key {
680        return Err(TokenError::MintMismatch.into());
681    }
682
683    let mut confidential_transfer_account =
684        token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
685    confidential_transfer_account.valid_as_source()?;
686
687    // Check that the source encryption public key is consistent with what was actually used to
688    // generate the zkp.
689    if *source_encryption_pubkey != confidential_transfer_account.encryption_pubkey {
690        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
691    }
692
693    let new_source_available_balance = syscall::subtract_with_lo_hi(
694        &confidential_transfer_account.available_balance,
695        source_transfer_amount_lo,
696        source_transfer_amount_hi,
697    )
698    .ok_or(ProgramError::InvalidInstructionData)?;
699
700    // Check that the computed available balance is consistent with what was actually used to
701    // generate the zkp on the client side.
702    if new_source_available_balance != *expected_new_source_available_balance {
703        return Err(TokenError::ConfidentialTransferBalanceMismatch.into());
704    }
705
706    confidential_transfer_account.available_balance = new_source_available_balance;
707    confidential_transfer_account.decryptable_available_balance =
708        new_source_decryptable_available_balance;
709
710    Ok(())
711}
712
713#[cfg(feature = "zk-ops")]
714fn process_destination_for_transfer(
715    destination_token_account_info: &AccountInfo,
716    mint_info: &AccountInfo,
717    destination_encryption_pubkey: &EncryptionPubkey,
718    destination_transfer_amount_lo: &EncryptedBalance,
719    destination_transfer_amount_hi: &EncryptedBalance,
720    encrypted_fee: Option<(&EncryptedFee, &EncryptedFee)>,
721) -> ProgramResult {
722    check_program_account(destination_token_account_info.owner)?;
723    let destination_token_account_data = &mut destination_token_account_info.data.borrow_mut();
724    let mut destination_token_account =
725        StateWithExtensionsMut::<Account>::unpack(destination_token_account_data)?;
726
727    if destination_token_account.base.is_frozen() {
728        return Err(TokenError::AccountFrozen.into());
729    }
730
731    if destination_token_account.base.mint != *mint_info.key {
732        return Err(TokenError::MintMismatch.into());
733    }
734
735    if memo_required(&destination_token_account) {
736        check_previous_sibling_instruction_is_memo()?;
737    }
738
739    let mut destination_confidential_transfer_account =
740        destination_token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
741    destination_confidential_transfer_account.valid_as_destination()?;
742
743    if *destination_encryption_pubkey != destination_confidential_transfer_account.encryption_pubkey
744    {
745        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
746    }
747
748    destination_confidential_transfer_account.pending_balance_lo = syscall::add(
749        &destination_confidential_transfer_account.pending_balance_lo,
750        destination_transfer_amount_lo,
751    )
752    .ok_or(ProgramError::InvalidInstructionData)?;
753
754    destination_confidential_transfer_account.pending_balance_hi = syscall::add(
755        &destination_confidential_transfer_account.pending_balance_hi,
756        destination_transfer_amount_hi,
757    )
758    .ok_or(ProgramError::InvalidInstructionData)?;
759
760    destination_confidential_transfer_account.increment_pending_balance_credit_counter()?;
761
762    // Process transfer fee
763    if let Some((ciphertext_fee_lo, ciphertext_fee_hi)) = encrypted_fee {
764        // Decode lo and hi fee amounts encrypted under the destination encryption public key
765        let destination_fee_lo: EncryptedWithheldAmount = (
766            ciphertext_fee_lo.commitment,
767            ciphertext_fee_lo.destination_handle,
768        )
769            .into();
770        let destination_fee_hi: EncryptedWithheldAmount = (
771            ciphertext_fee_hi.commitment,
772            ciphertext_fee_hi.destination_handle,
773        )
774            .into();
775
776        // Subtract the fee amount from the destination pending balance
777        destination_confidential_transfer_account.pending_balance_lo = syscall::subtract(
778            &destination_confidential_transfer_account.pending_balance_lo,
779            &destination_fee_lo,
780        )
781        .ok_or(ProgramError::InvalidInstructionData)?;
782        destination_confidential_transfer_account.pending_balance_hi = syscall::subtract(
783            &destination_confidential_transfer_account.pending_balance_hi,
784            &destination_fee_hi,
785        )
786        .ok_or(ProgramError::InvalidInstructionData)?;
787
788        // Decode lo and hi fee amounts encrypted under the withdraw authority encryption public
789        // key
790        let withdraw_withheld_authority_fee_lo: EncryptedWithheldAmount = (
791            ciphertext_fee_lo.commitment,
792            ciphertext_fee_lo.withdraw_withheld_authority_handle,
793        )
794            .into();
795        let withdraw_withheld_authority_fee_hi: EncryptedWithheldAmount = (
796            ciphertext_fee_hi.commitment,
797            ciphertext_fee_hi.withdraw_withheld_authority_handle,
798        )
799            .into();
800
801        // Add the fee amount to the destination withheld fee
802        destination_confidential_transfer_account.withheld_amount = syscall::add_with_lo_hi(
803            &destination_confidential_transfer_account.withheld_amount,
804            &withdraw_withheld_authority_fee_lo,
805            &withdraw_withheld_authority_fee_hi,
806        )
807        .ok_or(ProgramError::InvalidInstructionData)?;
808    }
809
810    Ok(())
811}
812
813/// Processes an [ApplyPendingBalance] instruction.
814#[cfg(feature = "zk-ops")]
815fn process_apply_pending_balance(
816    program_id: &Pubkey,
817    accounts: &[AccountInfo],
818    ApplyPendingBalanceData {
819        expected_pending_balance_credit_counter,
820        new_decryptable_available_balance,
821    }: &ApplyPendingBalanceData,
822) -> ProgramResult {
823    let account_info_iter = &mut accounts.iter();
824    let token_account_info = next_account_info(account_info_iter)?;
825    let authority_info = next_account_info(account_info_iter)?;
826    let authority_info_data_len = authority_info.data_len();
827
828    check_program_account(token_account_info.owner)?;
829    let token_account_data = &mut token_account_info.data.borrow_mut();
830    let mut token_account = StateWithExtensionsMut::<Account>::unpack(token_account_data)?;
831
832    Processor::validate_owner(
833        program_id,
834        &token_account.base.owner,
835        authority_info,
836        authority_info_data_len,
837        account_info_iter.as_slice(),
838    )?;
839
840    let mut confidential_transfer_account =
841        token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
842
843    confidential_transfer_account.available_balance = syscall::add_with_lo_hi(
844        &confidential_transfer_account.available_balance,
845        &confidential_transfer_account.pending_balance_lo,
846        &confidential_transfer_account.pending_balance_hi,
847    )
848    .ok_or(ProgramError::InvalidInstructionData)?;
849
850    confidential_transfer_account.actual_pending_balance_credit_counter =
851        confidential_transfer_account.pending_balance_credit_counter;
852    confidential_transfer_account.expected_pending_balance_credit_counter =
853        *expected_pending_balance_credit_counter;
854    confidential_transfer_account.decryptable_available_balance =
855        *new_decryptable_available_balance;
856    confidential_transfer_account.pending_balance_credit_counter = 0.into();
857    confidential_transfer_account.pending_balance_lo = EncryptedBalance::zeroed();
858    confidential_transfer_account.pending_balance_hi = EncryptedBalance::zeroed();
859
860    Ok(())
861}
862
863/// Processes a [DisableConfidentialCredits] or [EnableConfidentialCredits] instruction.
864fn process_allow_confidential_credits(
865    program_id: &Pubkey,
866    accounts: &[AccountInfo],
867    allow_confidential_credits: bool,
868) -> ProgramResult {
869    let account_info_iter = &mut accounts.iter();
870    let token_account_info = next_account_info(account_info_iter)?;
871    let authority_info = next_account_info(account_info_iter)?;
872    let authority_info_data_len = authority_info.data_len();
873
874    check_program_account(token_account_info.owner)?;
875    let token_account_data = &mut token_account_info.data.borrow_mut();
876    let mut token_account = StateWithExtensionsMut::<Account>::unpack(token_account_data)?;
877
878    Processor::validate_owner(
879        program_id,
880        &token_account.base.owner,
881        authority_info,
882        authority_info_data_len,
883        account_info_iter.as_slice(),
884    )?;
885
886    let mut confidential_transfer_account =
887        token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
888    confidential_transfer_account.allow_confidential_credits = allow_confidential_credits.into();
889
890    Ok(())
891}
892
893/// Processes an [DisableNonConfidentialCredits] or [EnableNonConfidentialCredits] instruction.
894fn process_allow_non_confidential_credits(
895    program_id: &Pubkey,
896    accounts: &[AccountInfo],
897    allow_non_confidential_credits: bool,
898) -> ProgramResult {
899    let account_info_iter = &mut accounts.iter();
900    let token_account_info = next_account_info(account_info_iter)?;
901    let authority_info = next_account_info(account_info_iter)?;
902    let authority_info_data_len = authority_info.data_len();
903
904    check_program_account(token_account_info.owner)?;
905    let token_account_data = &mut token_account_info.data.borrow_mut();
906    let mut token_account = StateWithExtensionsMut::<Account>::unpack(token_account_data)?;
907
908    Processor::validate_owner(
909        program_id,
910        &token_account.base.owner,
911        authority_info,
912        authority_info_data_len,
913        account_info_iter.as_slice(),
914    )?;
915
916    let mut confidential_transfer_account =
917        token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
918    confidential_transfer_account.allow_non_confidential_credits =
919        allow_non_confidential_credits.into();
920
921    Ok(())
922}
923
924/// Processes an [WithdrawWithheldTokensFromMint] instruction.
925#[cfg(feature = "zk-ops")]
926fn process_withdraw_withheld_tokens_from_mint(
927    program_id: &Pubkey,
928    accounts: &[AccountInfo],
929    proof_instruction_offset: i64,
930) -> ProgramResult {
931    let account_info_iter = &mut accounts.iter();
932    let mint_account_info = next_account_info(account_info_iter)?;
933    let destination_account_info = next_account_info(account_info_iter)?;
934    let instructions_sysvar_info = next_account_info(account_info_iter)?;
935    let authority_info = next_account_info(account_info_iter)?;
936    let authority_info_data_len = authority_info.data_len();
937
938    // unnecessary check, but helps for clarity
939    check_program_account(mint_account_info.owner)?;
940    let mut mint_data = mint_account_info.data.borrow_mut();
941    let mut mint = StateWithExtensionsMut::<Mint>::unpack(&mut mint_data)?;
942
943    // mint must be extended for fees
944    {
945        let transfer_fee_config = mint.get_extension::<TransferFeeConfig>()?;
946        let withdraw_withheld_authority =
947            Option::<Pubkey>::from(transfer_fee_config.withdraw_withheld_authority)
948                .ok_or(TokenError::NoAuthorityExists)?;
949        Processor::validate_owner(
950            program_id,
951            &withdraw_withheld_authority,
952            authority_info,
953            authority_info_data_len,
954            account_info_iter.as_slice(),
955        )?;
956    } // free `transfer_fee_config` to borrow `confidential_transfer_mint` as mutable
957
958    let confidential_transfer_mint = mint.get_extension_mut::<ConfidentialTransferMint>()?;
959
960    // basic checks for the destination account - must be extended for confidential transfers
961    let mut destination_account_data = destination_account_info.data.borrow_mut();
962    let mut destination_account =
963        StateWithExtensionsMut::<Account>::unpack(&mut destination_account_data)?;
964
965    if destination_account.base.mint != *mint_account_info.key {
966        return Err(TokenError::MintMismatch.into());
967    }
968    if destination_account.base.is_frozen() {
969        return Err(TokenError::AccountFrozen.into());
970    }
971    let mut destination_confidential_transfer_account =
972        destination_account.get_extension_mut::<ConfidentialTransferAccount>()?;
973    destination_confidential_transfer_account.valid_as_destination()?;
974
975    // Zero-knowledge proof certifies that the exact withheld amount is credited to the source
976    // account.
977    let zkp_instruction =
978        get_instruction_relative(proof_instruction_offset, instructions_sysvar_info)?;
979    let proof_data = decode_proof_instruction::<WithdrawWithheldTokensData>(
980        ProofInstruction::VerifyWithdrawWithheldTokens,
981        &zkp_instruction,
982    )?;
983    // Checks that the withdraw authority encryption public key associated with the mint is
984    // consistent with what was actually used to generate the zkp.
985    if !confidential_transfer_mint
986        .withdraw_withheld_authority_encryption_pubkey
987        .equals(&proof_data.withdraw_withheld_authority_pubkey)
988    {
989        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
990    }
991    // Checks that the encryption public key associated with the destination account is consistent
992    // with what was actually used to generate the zkp.
993    if proof_data.destination_pubkey != destination_confidential_transfer_account.encryption_pubkey
994    {
995        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
996    }
997    // Checks that the withheld amount ciphertext is consistent with the ciphertext data that was
998    // actually used to generate the zkp.
999    if proof_data.withdraw_withheld_authority_ciphertext
1000        != confidential_transfer_mint.withheld_amount
1001    {
1002        return Err(TokenError::ConfidentialTransferBalanceMismatch.into());
1003    }
1004
1005    // The proof data contains the mint withheld amount encrypted under the destination ElGamal pubkey.
1006    // This amount is added to the destination pending balance.
1007    destination_confidential_transfer_account.pending_balance_lo = syscall::add(
1008        &destination_confidential_transfer_account.pending_balance_lo,
1009        &proof_data.destination_ciphertext,
1010    )
1011    .ok_or(ProgramError::InvalidInstructionData)?;
1012
1013    destination_confidential_transfer_account.increment_pending_balance_credit_counter()?;
1014
1015    // Fee is now withdrawn, so zero out the mint withheld amount.
1016    confidential_transfer_mint.withheld_amount = EncryptedWithheldAmount::zeroed();
1017
1018    Ok(())
1019}
1020
1021#[cfg(feature = "zk-ops")]
1022fn process_withdraw_withheld_tokens_from_accounts(
1023    program_id: &Pubkey,
1024    accounts: &[AccountInfo],
1025    num_token_accounts: u8,
1026    proof_instruction_offset: i64,
1027) -> ProgramResult {
1028    let account_info_iter = &mut accounts.iter();
1029    let mint_account_info = next_account_info(account_info_iter)?;
1030    let destination_account_info = next_account_info(account_info_iter)?;
1031    let instructions_sysvar_info = next_account_info(account_info_iter)?;
1032    let authority_info = next_account_info(account_info_iter)?;
1033    let authority_info_data_len = authority_info.data_len();
1034    let account_infos = account_info_iter.as_slice();
1035    let num_signers = account_infos
1036        .len()
1037        .saturating_sub(num_token_accounts as usize);
1038
1039    // unnecessary check, but helps for clarity
1040    check_program_account(mint_account_info.owner)?;
1041    let mut mint_data = mint_account_info.data.borrow_mut();
1042    let mut mint = StateWithExtensionsMut::<Mint>::unpack(&mut mint_data)?;
1043
1044    // mint must be extended for fees
1045    let transfer_fee_config = mint.get_extension::<TransferFeeConfig>()?;
1046    let withdraw_withheld_authority =
1047        Option::<Pubkey>::from(transfer_fee_config.withdraw_withheld_authority)
1048            .ok_or(TokenError::NoAuthorityExists)?;
1049    Processor::validate_owner(
1050        program_id,
1051        &withdraw_withheld_authority,
1052        authority_info,
1053        authority_info_data_len,
1054        &account_infos[..num_signers],
1055    )?;
1056
1057    let mut destination_account_data = destination_account_info.data.borrow_mut();
1058    let mut destination_account =
1059        StateWithExtensionsMut::<Account>::unpack(&mut destination_account_data)?;
1060    if destination_account.base.mint != *mint_account_info.key {
1061        return Err(TokenError::MintMismatch.into());
1062    }
1063    if destination_account.base.is_frozen() {
1064        return Err(TokenError::AccountFrozen.into());
1065    }
1066
1067    // Sum up the withheld amounts in all the accounts.
1068    let mut aggregate_withheld_amount = EncryptedWithheldAmount::zeroed();
1069    for account_info in &account_infos[num_signers..] {
1070        // self-harvest, can't double-borrow the underlying data
1071        if account_info.key == destination_account_info.key {
1072            let confidential_transfer_destination_account = destination_account
1073                .get_extension_mut::<ConfidentialTransferAccount>()
1074                .map_err(|_| TokenError::InvalidState)?;
1075
1076            aggregate_withheld_amount = syscall::add(
1077                &aggregate_withheld_amount,
1078                &confidential_transfer_destination_account.withheld_amount,
1079            )
1080            .ok_or(ProgramError::InvalidInstructionData)?;
1081
1082            confidential_transfer_destination_account.withheld_amount =
1083                EncryptedWithheldAmount::zeroed();
1084        } else {
1085            match harvest_from_account(mint_account_info.key, account_info) {
1086                Ok(encrypted_withheld_amount) => {
1087                    aggregate_withheld_amount =
1088                        syscall::add(&aggregate_withheld_amount, &encrypted_withheld_amount)
1089                            .ok_or(ProgramError::InvalidInstructionData)?;
1090                }
1091                Err(e) => {
1092                    msg!("Error harvesting from {}: {}", account_info.key, e);
1093                }
1094            }
1095        }
1096    }
1097
1098    let mut destination_confidential_transfer_account =
1099        destination_account.get_extension_mut::<ConfidentialTransferAccount>()?;
1100    destination_confidential_transfer_account.valid_as_destination()?;
1101
1102    // Zero-knowledge proof certifies that the exact aggregate withheld amount is credited to the
1103    // source account.
1104    let zkp_instruction =
1105        get_instruction_relative(proof_instruction_offset, instructions_sysvar_info)?;
1106    let proof_data = decode_proof_instruction::<WithdrawWithheldTokensData>(
1107        ProofInstruction::VerifyWithdrawWithheldTokens,
1108        &zkp_instruction,
1109    )?;
1110    // Checks that the withdraw authority encryption public key associated with the mint is
1111    // consistent with what was actually used to generate the zkp.
1112    let confidential_transfer_mint = mint.get_extension_mut::<ConfidentialTransferMint>()?;
1113    if !confidential_transfer_mint
1114        .withdraw_withheld_authority_encryption_pubkey
1115        .equals(&proof_data.withdraw_withheld_authority_pubkey)
1116    {
1117        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
1118    }
1119    // Checks that the encryption public key associated with the destination account is consistent
1120    // with what was actually used to generate the zkp.
1121    if proof_data.destination_pubkey != destination_confidential_transfer_account.encryption_pubkey
1122    {
1123        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
1124    }
1125    // Checks that the withheld amount ciphertext is consistent with the ciphertext data that was
1126    // actually used to generate the zkp.
1127    if proof_data.withdraw_withheld_authority_ciphertext != aggregate_withheld_amount {
1128        return Err(TokenError::ConfidentialTransferBalanceMismatch.into());
1129    }
1130
1131    // The proof data contains the mint withheld amount encrypted under the destination ElGamal pubkey.
1132    // This amount is added to the destination pending balance.
1133    destination_confidential_transfer_account.pending_balance_lo = syscall::add(
1134        &destination_confidential_transfer_account.pending_balance_lo,
1135        &proof_data.destination_ciphertext,
1136    )
1137    .ok_or(ProgramError::InvalidInstructionData)?;
1138
1139    destination_confidential_transfer_account.increment_pending_balance_credit_counter()?;
1140
1141    Ok(())
1142}
1143
1144#[cfg(feature = "zk-ops")]
1145fn harvest_from_account<'a, 'b>(
1146    mint_key: &'b Pubkey,
1147    token_account_info: &'b AccountInfo<'a>,
1148) -> Result<EncryptedWithheldAmount, TokenError> {
1149    let mut token_account_data = token_account_info.data.borrow_mut();
1150    let mut token_account = StateWithExtensionsMut::<Account>::unpack(&mut token_account_data)
1151        .map_err(|_| TokenError::InvalidState)?;
1152    if token_account.base.mint != *mint_key {
1153        return Err(TokenError::MintMismatch);
1154    }
1155    check_program_account(token_account_info.owner).map_err(|_| TokenError::InvalidState)?;
1156
1157    let confidential_transfer_token_account = token_account
1158        .get_extension_mut::<ConfidentialTransferAccount>()
1159        .map_err(|_| TokenError::InvalidState)?;
1160
1161    let withheld_amount = confidential_transfer_token_account.withheld_amount;
1162    confidential_transfer_token_account.withheld_amount = EncryptedWithheldAmount::zeroed();
1163
1164    Ok(withheld_amount)
1165}
1166
1167/// Processes an [HarvestWithheldTokensToMint] instruction.
1168#[cfg(feature = "zk-ops")]
1169fn process_harvest_withheld_tokens_to_mint(accounts: &[AccountInfo]) -> ProgramResult {
1170    let account_info_iter = &mut accounts.iter();
1171    let mint_account_info = next_account_info(account_info_iter)?;
1172    let token_account_infos = account_info_iter.as_slice();
1173
1174    let mut mint_data = mint_account_info.data.borrow_mut();
1175    let mut mint = StateWithExtensionsMut::<Mint>::unpack(&mut mint_data)?;
1176    mint.get_extension::<TransferFeeConfig>()?;
1177    let confidential_transfer_mint = mint.get_extension_mut::<ConfidentialTransferMint>()?;
1178
1179    for token_account_info in token_account_infos {
1180        match harvest_from_account(mint_account_info.key, token_account_info) {
1181            Ok(withheld_amount) => {
1182                let new_mint_withheld_amount = syscall::add(
1183                    &confidential_transfer_mint.withheld_amount,
1184                    &withheld_amount,
1185                )
1186                .ok_or(ProgramError::InvalidInstructionData)?;
1187
1188                confidential_transfer_mint.withheld_amount = new_mint_withheld_amount;
1189            }
1190            Err(e) => {
1191                msg!("Error harvesting from {}: {}", token_account_info.key, e);
1192            }
1193        }
1194    }
1195    Ok(())
1196}
1197
1198#[allow(dead_code)]
1199pub(crate) fn process_instruction(
1200    program_id: &Pubkey,
1201    accounts: &[AccountInfo],
1202    input: &[u8],
1203) -> ProgramResult {
1204    check_program_account(program_id)?;
1205
1206    match decode_instruction_type(input)? {
1207        ConfidentialTransferInstruction::InitializeMint => {
1208            msg!("ConfidentialTransferInstruction::InitializeMint");
1209            let data = decode_instruction_data::<InitializeMintData>(input)?;
1210            process_initialize_mint(
1211                accounts,
1212                &data.authority,
1213                data.auto_approve_new_accounts,
1214                &data.auditor_encryption_pubkey,
1215                &data.withdraw_withheld_authority_encryption_pubkey,
1216            )
1217        }
1218        ConfidentialTransferInstruction::UpdateMint => {
1219            msg!("ConfidentialTransferInstruction::UpdateMint");
1220            let data = decode_instruction_data::<UpdateMintData>(input)?;
1221            process_update_mint(
1222                accounts,
1223                data.auto_approve_new_accounts,
1224                &data.auditor_encryption_pubkey,
1225            )
1226        }
1227        ConfidentialTransferInstruction::ConfigureAccount => {
1228            msg!("ConfidentialTransferInstruction::ConfigureAccount");
1229            let data = decode_instruction_data::<ConfigureAccountInstructionData>(input)?;
1230            process_configure_account(
1231                program_id,
1232                accounts,
1233                &data.decryptable_zero_balance,
1234                &data.maximum_pending_balance_credit_counter,
1235                data.proof_instruction_offset as i64,
1236            )
1237        }
1238        ConfidentialTransferInstruction::ApproveAccount => {
1239            msg!("ConfidentialTransferInstruction::ApproveAccount");
1240            process_approve_account(accounts)
1241        }
1242        ConfidentialTransferInstruction::EmptyAccount => {
1243            msg!("ConfidentialTransferInstruction::EmptyAccount");
1244            let data = decode_instruction_data::<EmptyAccountInstructionData>(input)?;
1245            process_empty_account(program_id, accounts, data.proof_instruction_offset as i64)
1246        }
1247        ConfidentialTransferInstruction::Deposit => {
1248            msg!("ConfidentialTransferInstruction::Deposit");
1249            #[cfg(feature = "zk-ops")]
1250            {
1251                let data = decode_instruction_data::<DepositInstructionData>(input)?;
1252                process_deposit(program_id, accounts, data.amount.into(), data.decimals)
1253            }
1254            #[cfg(not(feature = "zk-ops"))]
1255            Err(ProgramError::InvalidInstructionData)
1256        }
1257        ConfidentialTransferInstruction::Withdraw => {
1258            msg!("ConfidentialTransferInstruction::Withdraw");
1259            #[cfg(feature = "zk-ops")]
1260            {
1261                let data = decode_instruction_data::<WithdrawInstructionData>(input)?;
1262                process_withdraw(
1263                    program_id,
1264                    accounts,
1265                    data.amount.into(),
1266                    data.decimals,
1267                    data.new_decryptable_available_balance,
1268                    data.proof_instruction_offset as i64,
1269                )
1270            }
1271            #[cfg(not(feature = "zk-ops"))]
1272            Err(ProgramError::InvalidInstructionData)
1273        }
1274        ConfidentialTransferInstruction::Transfer => {
1275            msg!("ConfidentialTransferInstruction::Transfer");
1276            #[cfg(feature = "zk-ops")]
1277            {
1278                let data = decode_instruction_data::<TransferInstructionData>(input)?;
1279                process_transfer(
1280                    program_id,
1281                    accounts,
1282                    data.new_source_decryptable_available_balance,
1283                    data.proof_instruction_offset as i64,
1284                )
1285            }
1286            #[cfg(not(feature = "zk-ops"))]
1287            Err(ProgramError::InvalidInstructionData)
1288        }
1289        ConfidentialTransferInstruction::ApplyPendingBalance => {
1290            msg!("ConfidentialTransferInstruction::ApplyPendingBalance");
1291            #[cfg(feature = "zk-ops")]
1292            {
1293                process_apply_pending_balance(
1294                    program_id,
1295                    accounts,
1296                    decode_instruction_data::<ApplyPendingBalanceData>(input)?,
1297                )
1298            }
1299            #[cfg(not(feature = "zk-ops"))]
1300            {
1301                Err(ProgramError::InvalidInstructionData)
1302            }
1303        }
1304        ConfidentialTransferInstruction::DisableConfidentialCredits => {
1305            msg!("ConfidentialTransferInstruction::DisableConfidentialCredits");
1306            process_allow_confidential_credits(program_id, accounts, false)
1307        }
1308        ConfidentialTransferInstruction::EnableConfidentialCredits => {
1309            msg!("ConfidentialTransferInstruction::EnableConfidentialCredits");
1310            process_allow_confidential_credits(program_id, accounts, true)
1311        }
1312        ConfidentialTransferInstruction::DisableNonConfidentialCredits => {
1313            msg!("ConfidentialTransferInstruction::DisableNonConfidentialCredits");
1314            process_allow_non_confidential_credits(program_id, accounts, false)
1315        }
1316        ConfidentialTransferInstruction::EnableNonConfidentialCredits => {
1317            msg!("ConfidentialTransferInstruction::EnableNonConfidentialCredits");
1318            process_allow_non_confidential_credits(program_id, accounts, true)
1319        }
1320        ConfidentialTransferInstruction::WithdrawWithheldTokensFromMint => {
1321            msg!("ConfidentialTransferInstruction::WithdrawWithheldTokensFromMint");
1322            #[cfg(feature = "zk-ops")]
1323            {
1324                let data = decode_instruction_data::<WithdrawWithheldTokensFromMintData>(input)?;
1325                process_withdraw_withheld_tokens_from_mint(
1326                    program_id,
1327                    accounts,
1328                    data.proof_instruction_offset as i64,
1329                )
1330            }
1331            #[cfg(not(feature = "zk-ops"))]
1332            Err(ProgramError::InvalidInstructionData)
1333        }
1334        ConfidentialTransferInstruction::WithdrawWithheldTokensFromAccounts => {
1335            msg!("ConfidentialTransferInstruction::WithdrawWithheldTokensFromAccounts");
1336            #[cfg(feature = "zk-ops")]
1337            {
1338                let data =
1339                    decode_instruction_data::<WithdrawWithheldTokensFromAccountsData>(input)?;
1340                process_withdraw_withheld_tokens_from_accounts(
1341                    program_id,
1342                    accounts,
1343                    data.num_token_accounts,
1344                    data.proof_instruction_offset as i64,
1345                )
1346            }
1347            #[cfg(not(feature = "zk-ops"))]
1348            Err(ProgramError::InvalidInstructionData)
1349        }
1350        ConfidentialTransferInstruction::HarvestWithheldTokensToMint => {
1351            msg!("ConfidentialTransferInstruction::HarvestWithheldTokensToMint");
1352            #[cfg(feature = "zk-ops")]
1353            {
1354                process_harvest_withheld_tokens_to_mint(accounts)
1355            }
1356            #[cfg(not(feature = "zk-ops"))]
1357            {
1358                Err(ProgramError::InvalidInstructionData)
1359            }
1360        }
1361    }
1362}