Repository: solana-labs/solana-program-library
Pull request #7319 (2024-10-01 19:46:50)

Confidential mint burn extension: Token program changes
PR separating out only the token program changes from #6881 6881


Patch:
diff --git a/token/confidential-transfer/proof-extraction/src/encryption.rs b/token/confidential-transfer/proof-extraction/src/encryption.rs
index b0a991235..383108d41 100644
--- a/token/confidential-transfer/proof-extraction/src/encryption.rs
+++ b/token/confidential-transfer/proof-extraction/src/encryption.rs
@@ -5,7 +5,6 @@ use {
         grouped_elgamal::{
             PodGroupedElGamalCiphertext2Handles, PodGroupedElGamalCiphertext3Handles,
         },
-        pedersen::PodPedersenCommitment,
     },
 };
 
@@ -14,10 +13,6 @@ use {
 pub struct PodTransferAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);
 
 impl PodTransferAmountCiphertext {
-    pub fn extract_commitment(&self) -> PodPedersenCommitment {
-        self.0.extract_commitment()
-    }
-
     pub fn try_extract_ciphertext(
         &self,
         index: usize,
@@ -33,10 +28,6 @@ impl PodTransferAmountCiphertext {
 pub struct PodFeeCiphertext(pub(crate) PodGroupedElGamalCiphertext2Handles);
 
 impl PodFeeCiphertext {
-    pub fn extract_commitment(&self) -> PodPedersenCommitment {
-        self.0.extract_commitment()
-    }
-
     pub fn try_extract_ciphertext(
         &self,
         index: usize,
@@ -51,6 +42,28 @@ impl PodFeeCiphertext {
 #[repr(C)]
 pub struct PodBurnAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);
 
+impl PodBurnAmountCiphertext {
+    pub fn try_extract_ciphertext(
+        &self,
+        index: usize,
+    ) -> Result<PodElGamalCiphertext, TokenProofExtractionError> {
+        self.0
+            .try_extract_ciphertext(index)
+            .map_err(|_| TokenProofExtractionError::CiphertextExtraction)
+    }
+}
+
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 #[repr(C)]
 pub struct PodMintAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);
+
+impl PodMintAmountCiphertext {
+    pub fn try_extract_ciphertext(
+        &self,
+        index: usize,
+    ) -> Result<PodElGamalCiphertext, TokenProofExtractionError> {
+        self.0
+            .try_extract_ciphertext(index)
+            .map_err(|_| TokenProofExtractionError::CiphertextExtraction)
+    }
+}
diff --git a/token/confidential-transfer/proof-extraction/src/instruction.rs b/token/confidential-transfer/proof-extraction/src/instruction.rs
index 761a2b3fc..be41a4bb6 100644
--- a/token/confidential-transfer/proof-extraction/src/instruction.rs
+++ b/token/confidential-transfer/proof-extraction/src/instruction.rs
@@ -6,11 +6,11 @@ use {
     solana_program::{
         account_info::{next_account_info, AccountInfo},
         entrypoint::ProgramResult,
-        instruction::Instruction,
+        instruction::{AccountMeta, Instruction},
         msg,
         program_error::ProgramError,
         pubkey::Pubkey,
-        sysvar::instructions::get_instruction_relative,
+        sysvar::{self, instructions::get_instruction_relative},
     },
     solana_zk_sdk::zk_elgamal_proof_program::{
         self,
@@ -146,6 +146,56 @@ pub fn verify_and_extract_context<'a, T: Pod + ZkProofData<U>, U: Pod>(
     }
 }
 
+/// Processes a proof location for instruction creation. Adds relevant accounts
+/// to supplied account vector
+///
+/// If the proof location is an instruction offset the corresponding proof
+/// instruction is created and added to the `proof_instructions` vector.
+pub fn process_proof_location<T, U>(
+    accounts: &mut Vec<AccountMeta>,
+    expected_instruction_offset: &mut i8,
+    proof_instructions: &mut Vec<Instruction>,
+    proof_location: ProofLocation<T>,
+    push_sysvar_to_accounts: bool,
+    proof_instruction_type: ProofInstruction,
+) -> Result<i8, ProgramError>
+where
+    T: Pod + ZkProofData<U>,
+    U: Pod,
+{
+    match proof_location {
+        ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
+            let proof_instruction_offset: i8 = proof_instruction_offset.into();
+            if &proof_instruction_offset != expected_instruction_offset {
+                return Err(ProgramError::InvalidInstructionData);
+            }
+
+            if push_sysvar_to_accounts {
+                accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
+            }
+            match proof_data {
+                ProofData::InstructionData(data) => proof_instructions
+                    .push(proof_instruction_type.encode_verify_proof::<T, U>(None, data)),
+                ProofData::RecordAccount(address, offset) => {
+                    accounts.push(AccountMeta::new_readonly(*address, false));
+                    proof_instructions.push(
+                        proof_instruction_type
+                            .encode_verify_proof_from_account(None, address, offset),
+                    )
+                }
+            };
+            *expected_instruction_offset = expected_instruction_offset
+                .checked_add(1)
+                .ok_or(ProgramError::InvalidInstructionData)?;
+            Ok(proof_instruction_offset)
+        }
+        ProofLocation::ContextStateAccount(context_state_account) => {
+            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
+            Ok(0)
+        }
+    }
+}
+
 /// Converts a zk proof type to a corresponding ZK ElGamal proof program
 /// instruction that verifies the proof.
 pub fn zk_proof_type_to_instruction(
diff --git a/token/confidential-transfer/proof-generation/src/mint.rs b/token/confidential-transfer/proof-generation/src/mint.rs
index e25dc70a2..1ada01840 100644
--- a/token/confidential-transfer/proof-generation/src/mint.rs
+++ b/token/confidential-transfer/proof-generation/src/mint.rs
@@ -27,12 +27,13 @@ pub struct MintProofData {
     pub equality_proof_data: CiphertextCommitmentEqualityProofData,
     pub ciphertext_validity_proof_data: BatchedGroupedCiphertext3HandlesValidityProofData,
     pub range_proof_data: BatchedRangeProofU128Data,
+    pub new_decryptable_supply: AeCiphertext,
 }
 
 pub fn mint_split_proof_data(
     current_supply_ciphertext: &ElGamalCiphertext,
-    current_decryptable_supply: &AeCiphertext,
     mint_amount: u64,
+    current_supply: u64,
     supply_elgamal_keypair: &ElGamalKeypair,
     supply_aes_key: &AeKey,
     destination_elgamal_pubkey: &ElGamalPubkey,
@@ -77,11 +78,6 @@ pub fn mint_split_proof_data(
         )
         .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
 
-    // decrypt the current supply
-    let current_supply = current_decryptable_supply
-        .decrypt(supply_aes_key)
-        .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
-
     // compute the new supply
     let new_supply = current_supply
         .checked_add(mint_amount)
@@ -142,5 +138,6 @@ pub fn mint_split_proof_data(
         equality_proof_data,
         ciphertext_validity_proof_data,
         range_proof_data,
+        new_decryptable_supply: supply_aes_key.encrypt(new_supply),
     })
 }
diff --git a/token/js/README.md b/token/js/README.md
index 11e207320..383403224 100644
--- a/token/js/README.md
+++ b/token/js/README.md
@@ -2,6 +2,11 @@
 
 A TypeScript library for interacting with the SPL Token and Token-2022 programs.
 
+> [!IMPORTANT]  
+> If you are using [@solana/web3.js version 2](https://github.com/solana-labs/solana-web3.js/?tab=readme-ov-file#whats-new-in-version-20)
+> , you should use the `@solana-program/token` and `@solana-program/token-2022` 
+> packages instead.
+
 ## Links
 
 - [TypeScript Docs](https://solana-labs.github.io/solana-program-library/token/js/)
diff --git a/token/program-2022/src/error.rs b/token/program-2022/src/error.rs
index a886eb29e..54e4e2501 100644
--- a/token/program-2022/src/error.rs
+++ b/token/program-2022/src/error.rs
@@ -258,6 +258,11 @@ pub enum TokenError {
     /// Fee calculation failed
     #[error("Fee calculation failed")]
     FeeCalculation,
+
+    //65
+    /// Withdraw / Deposit not allowed for confidential-mint-burn
+    #[error("Withdraw / Deposit not allowed for confidential-mint-burn")]
+    IllegalMintBurnConversion,
 }
 impl From<TokenError> for ProgramError {
     fn from(e: TokenError) -> Self {
@@ -445,6 +450,9 @@ impl PrintProgramError for TokenError {
             TokenError::FeeCalculation => {
                 msg!("Transfer fee calculation failed")
             }
+            TokenError::IllegalMintBurnConversion => {
+                msg!("Conversions from normal to confidential token balance and vice versa are illegal if the confidential-mint-burn extension is enabled")
+            }
         }
     }
 }
diff --git a/token/program-2022/src/extension/confidential_mint_burn/account_info.rs b/token/program-2022/src/extension/confidential_mint_burn/account_info.rs
new file mode 100644
index 000000000..01cae83ac
--- /dev/null
+++ b/token/program-2022/src/extension/confidential_mint_burn/account_info.rs
@@ -0,0 +1,105 @@
+use {
+    super::ConfidentialMintBurn,
+    crate::error::TokenError,
+    bytemuck::{Pod, Zeroable},
+    solana_zk_sdk::{
+        encryption::{
+            auth_encryption::{AeCiphertext, AeKey},
+            elgamal::{ElGamalCiphertext, ElGamalKeypair},
+            pedersen::PedersenOpening,
+            pod::{
+                auth_encryption::PodAeCiphertext,
+                elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
+            },
+        },
+        zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData,
+    },
+};
+
+/// Confidential Mint Burn extension information needed to construct a
+/// `RotateSupplyElgamalPubkey` instruction.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
+pub struct SupplyAccountInfo {
+    /// The available balance (encrypted by `supply_elgamal_pubkey`)
+    pub current_supply: PodElGamalCiphertext,
+    /// The decryptable supply
+    pub decryptable_supply: PodAeCiphertext,
+    /// The supply's elgamal pubkey
+    pub supply_elgamal_pubkey: PodElGamalPubkey,
+}
+
+impl SupplyAccountInfo {
+    /// Creates a SupplyAccountInfo from ConfidentialMintBurn extension account
+    /// data
+    pub fn new(extension: &ConfidentialMintBurn) -> Self {
+        Self {
+            current_supply: extension.confidential_supply,
+            decryptable_supply: extension.decryptable_supply,
+            supply_elgamal_pubkey: extension.supply_elgamal_pubkey,
+        }
+    }
+
+    /// Computes the current supply from the decryptable supply and the
+    /// difference between the decryptable supply and the elgamal encrypted
+    /// supply ciphertext
+    pub fn decrypt_current_supply(
+        &self,
+        aes_key: &AeKey,
+        elgamal_keypair: &ElGamalKeypair,
+    ) -> Result<u64, TokenError> {
+        // decrypt the decryptable supply
+        let current_decyptable_supply = AeCiphertext::try_from(self.decryptable_supply)
+            .map_err(|_| TokenError::MalformedCiphertext)?
+            .decrypt(aes_key)
+            .ok_or(TokenError::MalformedCiphertext)?;
+
+        // get the difference between the supply ciphertext and the decryptable supply
+        // explanation see https://github.com/solana-labs/solana-program-library/pull/6881#issuecomment-2385579058
+        let decryptable_supply_ciphertext =
+            elgamal_keypair.pubkey().encrypt(current_decyptable_supply);
+        #[allow(clippy::arithmetic_side_effects)]
+        let supply_delta_ciphertext = decryptable_supply_ciphertext
+            - ElGamalCiphertext::try_from(self.current_supply)
+                .map_err(|_| TokenError::MalformedCiphertext)?;
+        let decryptable_to_current_diff = elgamal_keypair
+            .secret()
+            .decrypt_u32(&supply_delta_ciphertext)
+            .ok_or(TokenError::MalformedCiphertext)?;
+
+        // compute the current supply
+        current_decyptable_supply
+            .checked_sub(decryptable_to_current_diff)
+            .ok_or(TokenError::Overflow)
+    }
+
+    /// Generates the `CiphertextCiphertextEqualityProofData` needed for a
+    /// `RotateSupplyElgamalPubkey` instruction
+    pub fn generate_rotate_supply_elgamal_pubkey_proof(
+        &self,
+        aes_key: &AeKey,
+        current_supply_elgamal_keypair: &ElGamalKeypair,
+        new_supply_elgamal_keypair: &ElGamalKeypair,
+    ) -> Result<CiphertextCiphertextEqualityProofData, TokenError> {
+        let current_supply =
+            self.decrypt_current_supply(aes_key, current_supply_elgamal_keypair)?;
+
+        let new_supply_opening = PedersenOpening::new_rand();
+        let new_supply_ciphertext = new_supply_elgamal_keypair
+            .pubkey()
+            .encrypt_with(current_supply, &new_supply_opening);
+
+        CiphertextCiphertextEqualityProofData::new(
+            current_supply_elgamal_keypair,
+            new_supply_elgamal_keypair.pubkey(),
+            &self
+                .current_supply
+                .try_into()
+                .map_err(|_| TokenError::MalformedCiphertext)?,
+            &new_supply_ciphertext,
+            &new_supply_opening,
+            current_supply,
+        )
+        .map_err(|_| TokenError::ProofGeneration)
+    }
+}
diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs
new file mode 100644
index 000000000..6e808d365
--- /dev/null
+++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs
@@ -0,0 +1,549 @@
+#[cfg(feature = "serde-traits")]
+use {
+    crate::serialization::{aeciphertext_fromstr, elgamalpubkey_fromstr},
+    serde::{Deserialize, Serialize},
+};
+use {
+    crate::{
+        check_program_account,
+        extension::confidential_transfer::DecryptableBalance,
+        instruction::{encode_instruction, TokenInstruction},
+    },
+    bytemuck::{Pod, Zeroable},
+    num_enum::{IntoPrimitive, TryFromPrimitive},
+    solana_program::{
+        instruction::{AccountMeta, Instruction},
+        program_error::ProgramError,
+        pubkey::Pubkey,
+    },
+    solana_zk_sdk::encryption::pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey},
+};
+#[cfg(not(target_os = "solana"))]
+use {
+    solana_zk_sdk::{
+        encryption::{auth_encryption::AeCiphertext, elgamal::ElGamalPubkey},
+        zk_elgamal_proof_program::{
+            instruction::ProofInstruction,
+            proof_data::{
+                BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data,
+                CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData,
+            },
+        },
+    },
+    spl_token_confidential_transfer_proof_extraction::instruction::{
+        process_proof_location, ProofLocation,
+    },
+};
+
+/// Confidential Transfer extension instructions
+#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
+#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
+#[repr(u8)]
+pub enum ConfidentialMintBurnInstruction {
+    /// Initializes confidential mints and burns for a mint.
+    ///
+    /// The `ConfidentialMintBurnInstruction::InitializeMint` instruction
+    /// requires no signers and MUST be included within the same Transaction
+    /// as `TokenInstruction::InitializeMint`. Otherwise another party can
+    /// initialize the configuration.
+    ///
+    /// The instruction fails if the `TokenInstruction::InitializeMint`
+    /// instruction has already executed for the mint.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]` The SPL Token mint.
+    ///
+    /// Data expected by this instruction:
+    ///   `InitializeMintData`
+    InitializeMint,
+    /// Rotates the ElGamal pubkey used to encrypt confidential supply
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single authority
+    ///   0. `[writable]` The SPL Token mint.
+    ///   1. `[]` Instructions sysvar if `CiphertextCiphertextEquality` is
+    ///      included in the same transaction or context state account if
+    ///      `CiphertextCiphertextEquality` is pre-verified into a context state
+    ///      account.
+    ///   2. `[signer]` Confidential mint authority.
+    ///
+    ///   * Multisignature authority
+    ///   0. `[writable]` The SPL Token mint.
+    ///   1. `[]` Instructions sysvar if `CiphertextCiphertextEquality` is
+    ///      included in the same transaction or context state account if
+    ///      `CiphertextCiphertextEquality` is pre-verified into a context state
+    ///      account.
+    ///   2. `[]` The multisig authority account owner.
+    ///   3.. `[signer]` Required M signer accounts for the SPL Token Multisig
+    ///
+    /// Data expected by this instruction:
+    ///   `RotateSupplyElGamalPubkeyData`
+    RotateSupplyElGamalPubkey,
+    /// Updates the decryptable supply of the mint
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single authority
+    ///   0. `[writable]` The SPL Token mint.
+    ///   1. `[signer]` Confidential mint authority.
+    ///
+    ///   * Multisignature authority
+    ///   0. `[writable]` The SPL Token mint.
+    ///   1. `[]` The multisig authority account owner.
+    ///   2.. `[signer]` Required M signer accounts for the SPL Token Multisig
+    ///
+    /// Data expected by this instruction:
+    ///   `UpdateDecryptableSupplyData`
+    UpdateDecryptableSupply,
+    /// Mints tokens to confidential balance
+    ///
+    /// Fails if the destination account is frozen.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single authority
+    ///   0. `[writable]` The SPL Token account.
+    ///   1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero
+    ///      supply elgamal-pubkey
+    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
+    ///      `zk_elgamal_proof` instructions are included in the same
+    ///      transaction.
+    ///   3. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyCiphertextCommitmentEquality` proof
+    ///   4. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyBatchedGroupedCiphertext3HandlesValidity` proof
+    ///   5. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyBatchedRangeProofU128`
+    ///   6. `[signer]` The single account owner.
+    ///
+    ///   * Multisignature authority
+    ///   0. `[writable]` The SPL Token mint.
+    ///   1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero
+    ///      supply elgamal-pubkey
+    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
+    ///      `zk_elgamal_proof` instructions are included in the same
+    ///      transaction.
+    ///   3. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyCiphertextCommitmentEquality` proof
+    ///   4. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyBatchedGroupedCiphertext3HandlesValidity` proof
+    ///   5. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyBatchedRangeProofU128`
+    ///   6. `[]` The multisig account owner.
+    ///   7.. `[signer]` Required M signer accounts for the SPL Token Multisig
+    ///
+    /// Data expected by this instruction:
+    ///   `MintInstructionData`
+    Mint,
+    /// Burn tokens from confidential balance
+    ///
+    /// Fails if the destination account is frozen.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single authority
+    ///   0. `[writable]` The SPL Token account.
+    ///   1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero
+    ///      supply elgamal-pubkey
+    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
+    ///      `zk_elgamal_proof` instructions are included in the same
+    ///      transaction.
+    ///   3. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyCiphertextCommitmentEquality` proof
+    ///   4. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyBatchedGroupedCiphertext3HandlesValidity` proof
+    ///   5. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyBatchedRangeProofU128`
+    ///   6. `[signer]` The single account owner.
+    ///
+    ///   * Multisignature authority
+    ///   0. `[writable]` The SPL Token mint.
+    ///   1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero
+    ///      supply elgamal-pubkey
+    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
+    ///      `zk_elgamal_proof` instructions are included in the same
+    ///      transaction.
+    ///   3. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyCiphertextCommitmentEquality` proof
+    ///   4. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyBatchedGroupedCiphertext3HandlesValidity` proof
+    ///   5. `[]` (Optional) The context state account containing the
+    ///      pre-verified `VerifyBatchedRangeProofU128`
+    ///   6. `[]` The multisig account owner.
+    ///   7.. `[signer]` Required M signer accounts for the SPL Token Multisig
+    ///
+    /// Data expected by this instruction:
+    ///   `BurnInstructionData`
+    Burn,
+}
+
+/// Data expected by `ConfidentialMintBurnInstruction::InitializeMint`
+#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
+#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
+#[repr(C)]
+pub struct InitializeMintData {
+    /// The ElGamal pubkey used to encrypt the confidential supply
+    #[cfg_attr(feature = "serde-traits", serde(with = "elgamalpubkey_fromstr"))]
+    pub supply_elgamal_pubkey: PodElGamalPubkey,
+    /// The initial 0 supply ecrypted with the supply aes key
+    #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))]
+    pub decryptable_supply: PodAeCiphertext,
+}
+
+/// Data expected by `ConfidentialMintBurnInstruction::RotateSupplyElGamal`
+#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
+#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
+#[repr(C)]
+pub struct RotateSupplyElGamalPubkeyData {
+    /// The new ElGamal pubkey for supply encryption
+    #[cfg_attr(feature = "serde-traits", serde(with = "elgamalpubkey_fromstr"))]
+    pub new_supply_elgamal_pubkey: PodElGamalPubkey,
+    /// The location of the
+    /// `ProofInstruction::VerifyCiphertextCiphertextEquality` instruction
+    /// relative to the `RotateSupplyElGamal` instruction in the transaction
+    pub proof_instruction_offset: i8,
+}
+
+/// Data expected by `ConfidentialMintBurnInstruction::UpdateDecryptableSupply`
+#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
+#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
+#[repr(C)]
+pub struct UpdateDecryptableSupplyData {
+    /// The new decryptable supply
+    #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))]
+    pub new_decryptable_supply: PodAeCiphertext,
+}
+
+/// Data expected by `ConfidentialMintBurnInstruction::ConfidentialMint`
+#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
+#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
+#[repr(C)]
+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,
+    /// Relative location of the
+    /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction
+    /// to the `ConfidentialMint` instruction in the transaction. 0 if the
+    /// proof is in a pre-verified context account
+    pub equality_proof_instruction_offset: i8,
+    /// Relative location of the
+    /// `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity`
+    /// instruction to the `ConfidentialMint` instruction in the
+    /// transaction. 0 if the proof is in a pre-verified context account
+    pub ciphertext_validity_proof_instruction_offset: i8,
+    /// Relative location of the `ProofInstruction::VerifyBatchedRangeProofU128`
+    /// instruction to the `ConfidentialMint` instruction in the
+    /// transaction. 0 if the proof is in a pre-verified context account
+    pub range_proof_instruction_offset: i8,
+}
+
+/// Data expected by `ConfidentialMintBurnInstruction::ConfidentialBurn`
+#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
+#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
+#[repr(C)]
+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,
+    /// Relative location of the
+    /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction
+    /// to the `ConfidentialMint` instruction in the transaction. 0 if the
+    /// proof is in a pre-verified context account
+    pub equality_proof_instruction_offset: i8,
+    /// Relative location of the
+    /// `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity`
+    /// instruction to the `ConfidentialMint` instruction in the
+    /// transaction. 0 if the proof is in a pre-verified context account
+    pub ciphertext_validity_proof_instruction_offset: i8,
+    /// Relative location of the `ProofInstruction::VerifyBatchedRangeProofU128`
+    /// instruction to the `ConfidentialMint` instruction in the
+    /// transaction. 0 if the proof is in a pre-verified context account
+    pub range_proof_instruction_offset: i8,
+}
+
+/// Create a `InitializeMint` instruction
+pub fn initialize_mint(
+    token_program_id: &Pubkey,
+    mint: &Pubkey,
+    supply_elgamal_pubkey: PodElGamalPubkey,
+    decryptable_supply: PodAeCiphertext,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let accounts = vec![AccountMeta::new(*mint, false)];
+
+    Ok(encode_instruction(
+        token_program_id,
+        accounts,
+        TokenInstruction::ConfidentialMintBurnExtension,
+        ConfidentialMintBurnInstruction::InitializeMint,
+        &InitializeMintData {
+            supply_elgamal_pubkey,
+            decryptable_supply,
+        },
+    ))
+}
+
+/// Create a `RotateSupplyElGamal` instruction
+#[allow(clippy::too_many_arguments)]
+#[cfg(not(target_os = "solana"))]
+pub fn rotate_supply_elgamal_pubkey(
+    token_program_id: &Pubkey,
+    mint: &Pubkey,
+    authority: &Pubkey,
+    multisig_signers: &[&Pubkey],
+    new_supply_elgamal_pubkey: ElGamalPubkey,
+    ciphertext_equality_proof: ProofLocation<CiphertextCiphertextEqualityProofData>,
+) -> Result<Vec<Instruction>, ProgramError> {
+    check_program_account(token_program_id)?;
+    let mut accounts = vec![AccountMeta::new(*mint, false)];
+
+    let mut expected_instruction_offset = 1;
+    let mut proof_instructions = vec![];
+
+    let proof_instruction_offset = process_proof_location(
+        &mut accounts,
+        &mut expected_instruction_offset,
+        &mut proof_instructions,
+        ciphertext_equality_proof,
+        true,
+        ProofInstruction::VerifyCiphertextCiphertextEquality,
+    )?;
+
+    accounts.push(AccountMeta::new_readonly(
+        *authority,
+        multisig_signers.is_empty(),
+    ));
+    for multisig_signer in multisig_signers.iter() {
+        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
+    }
+
+    let mut instructions = vec![encode_instruction(
+        token_program_id,
+        accounts,
+        TokenInstruction::ConfidentialMintBurnExtension,
+        ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey,
+        &RotateSupplyElGamalPubkeyData {
+            new_supply_elgamal_pubkey: PodElGamalPubkey::from(new_supply_elgamal_pubkey),
+            proof_instruction_offset,
+        },
+    )];
+
+    instructions.extend_from_slice(&proof_instructions);
+
+    Ok(instructions)
+}
+
+/// Create a `UpdateMint` instruction
+#[cfg(not(target_os = "solana"))]
+pub fn update_decryptable_supply(
+    token_program_id: &Pubkey,
+    mint: &Pubkey,
+    authority: &Pubkey,
+    multisig_signers: &[&Pubkey],
+    new_decryptable_supply: AeCiphertext,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let mut accounts = vec![
+        AccountMeta::new(*mint, false),
+        AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
+    ];
+    for multisig_signer in multisig_signers.iter() {
+        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
+    }
+    Ok(encode_instruction(
+        token_program_id,
+        accounts,
+        TokenInstruction::ConfidentialMintBurnExtension,
+        ConfidentialMintBurnInstruction::UpdateDecryptableSupply,
+        &UpdateDecryptableSupplyData {
+            new_decryptable_supply: new_decryptable_supply.into(),
+        },
+    ))
+}
+
+/// Context state accounts used in confidential mint
+#[derive(Clone, Copy)]
+pub struct MintSplitContextStateAccounts<'a> {
+    /// Location of equality proof
+    pub equality_proof: &'a Pubkey,
+    /// Location of ciphertext validity proof
+    pub ciphertext_validity_proof: &'a Pubkey,
+    /// Location of range proof
+    pub range_proof: &'a Pubkey,
+    /// Authority able to close proof accounts
+    pub authority: &'a Pubkey,
+}
+
+/// Create a `ConfidentialMint` instruction
+#[allow(clippy::too_many_arguments)]
+#[cfg(not(target_os = "solana"))]
+pub fn confidential_mint_with_split_proofs(
+    token_program_id: &Pubkey,
+    token_account: &Pubkey,
+    mint: &Pubkey,
+    supply_elgamal_pubkey: Option<ElGamalPubkey>,
+    authority: &Pubkey,
+    multisig_signers: &[&Pubkey],
+    equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
+    ciphertext_validity_proof_location: ProofLocation<
+        BatchedGroupedCiphertext3HandlesValidityProofData,
+    >,
+    range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
+    new_decryptable_supply: AeCiphertext,
+) -> Result<Vec<Instruction>, ProgramError> {
+    check_program_account(token_program_id)?;
+    let mut accounts = vec![AccountMeta::new(*token_account, false)];
+    // we only need write lock to adjust confidential suppy on
+    // mint if a value for supply_elgamal_pubkey has been set
+    if supply_elgamal_pubkey.is_some() {
+        accounts.push(AccountMeta::new(*mint, false));
+    } else {
+        accounts.push(AccountMeta::new_readonly(*mint, false));
+    }
+
+    let mut expected_instruction_offset = 1;
+    let mut proof_instructions = vec![];
+
+    let equality_proof_instruction_offset = process_proof_location(
+        &mut accounts,
+        &mut expected_instruction_offset,
+        &mut proof_instructions,
+        equality_proof_location,
+        true,
+        ProofInstruction::VerifyCiphertextCommitmentEquality,
+    )?;
+
+    let ciphertext_validity_proof_instruction_offset = process_proof_location(
+        &mut accounts,
+        &mut expected_instruction_offset,
+        &mut proof_instructions,
+        ciphertext_validity_proof_location,
+        false,
+        ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity,
+    )?;
+
+    let range_proof_instruction_offset = process_proof_location(
+        &mut accounts,
+        &mut expected_instruction_offset,
+        &mut proof_instructions,
+        range_proof_location,
+        false,
+        ProofInstruction::VerifyBatchedRangeProofU128,
+    )?;
+
+    accounts.push(AccountMeta::new_readonly(
+        *authority,
+        multisig_signers.is_empty(),
+    ));
+    for multisig_signer in multisig_signers.iter() {
+        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
+    }
+
+    let mut instructions = vec![encode_instruction(
+        token_program_id,
+        accounts,
+        TokenInstruction::ConfidentialMintBurnExtension,
+        ConfidentialMintBurnInstruction::Mint,
+        &MintInstructionData {
+            new_decryptable_supply: new_decryptable_supply.into(),
+            equality_proof_instruction_offset,
+            ciphertext_validity_proof_instruction_offset,
+            range_proof_instruction_offset,
+        },
+    )];
+
+    instructions.extend_from_slice(&proof_instructions);
+
+    Ok(instructions)
+}
+
+/// Create a inner `ConfidentialBurn` instruction
+#[allow(clippy::too_many_arguments)]
+#[cfg(not(target_os = "solana"))]
+pub fn confidential_burn_with_split_proofs(
+    token_program_id: &Pubkey,
+    token_account: &Pubkey,
+    mint: &Pubkey,
+    supply_elgamal_pubkey: Option<ElGamalPubkey>,
+    new_decryptable_available_balance: DecryptableBalance,
+    authority: &Pubkey,
+    multisig_signers: &[&Pubkey],
+    equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
+    ciphertext_validity_proof_location: ProofLocation<
+        BatchedGroupedCiphertext3HandlesValidityProofData,
+    >,
+    range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
+) -> Result<Vec<Instruction>, ProgramError> {
+    check_program_account(token_program_id)?;
+    let mut accounts = vec![AccountMeta::new(*token_account, false)];
+    if supply_elgamal_pubkey.is_some() {
+        accounts.push(AccountMeta::new(*mint, false));
+    } else {
+        accounts.push(AccountMeta::new_readonly(*mint, false));
+    }
+
+    let mut expected_instruction_offset = 1;
+    let mut proof_instructions = vec![];
+
+    let equality_proof_instruction_offset = process_proof_location(
+        &mut accounts,
+        &mut expected_instruction_offset,
+        &mut proof_instructions,
+        equality_proof_location,
+        true,
+        ProofInstruction::VerifyCiphertextCommitmentEquality,
+    )?;
+
+    let ciphertext_validity_proof_instruction_offset = process_proof_location(
+        &mut accounts,
+        &mut expected_instruction_offset,
+        &mut proof_instructions,
+        ciphertext_validity_proof_location,
+        false,
+        ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity,
+    )?;
+
+    let range_proof_instruction_offset = process_proof_location(
+        &mut accounts,
+        &mut expected_instruction_offset,
+        &mut proof_instructions,
+        range_proof_location,
+        false,
+        ProofInstruction::VerifyBatchedRangeProofU128,
+    )?;
+
+    accounts.push(AccountMeta::new_readonly(
+        *authority,
+        multisig_signers.is_empty(),
+    ));
+
+    for multisig_signer in multisig_signers.iter() {
+        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
+    }
+
+    let mut instructions = vec![encode_instruction(
+        token_program_id,
+        accounts,
+        TokenInstruction::ConfidentialMintBurnExtension,
+        ConfidentialMintBurnInstruction::Burn,
+        &BurnInstructionData {
+            new_decryptable_available_balance,
+            equality_proof_instruction_offset,
+            ciphertext_validity_proof_instruction_offset,
+            range_proof_instruction_offset,
+        },
+    )];
+
+    instructions.extend_from_slice(&proof_instructions);
+
+    Ok(instructions)
+}
diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs
new file mode 100644
index 000000000..049ced268
--- /dev/null
+++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs
@@ -0,0 +1,45 @@
+use {
+    crate::extension::{Extension, ExtensionType},
+    bytemuck::{Pod, Zeroable},
+    solana_zk_sdk::encryption::pod::{
+        auth_encryption::PodAeCiphertext,
+        elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
+    },
+};
+
+/// Maximum bit length of any mint or burn amount
+///
+/// Any mint or burn amount must be less than 2^48
+pub const MAXIMUM_DEPOSIT_TRANSFER_AMOUNT: u64 = (u16::MAX as u64) + (1 << 16) * (u32::MAX as u64);
+
+/// Bit length of the low bits of pending balance plaintext
+pub const PENDING_BALANCE_LO_BIT_LENGTH: u32 = 16;
+
+/// Confidential Mint-Burn Extension instructions
+pub mod instruction;
+
+/// Confidential Mint-Burn Extension processor
+pub mod processor;
+
+/// Confidential Mint-Burn proof verification
+pub mod verify_proof;
+
+/// Confidential Mint Burn Extension supply information needed for instructions
+#[cfg(not(target_os = "solana"))]
+pub mod account_info;
+
+/// Confidential mint-burn mint configuration
+#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
+#[repr(C)]
+pub struct ConfidentialMintBurn {
+    /// The confidential supply of the mint (encrypted by `encryption_pubkey`)
+    pub confidential_supply: PodElGamalCiphertext,
+    /// The decryptable confidential supply of the mint
+    pub decryptable_supply: PodAeCiphertext,
+    /// The ElGamal pubkey used to encrypt the confidential supply
+    pub supply_elgamal_pubkey: PodElGamalPubkey,
+}
+
+impl Extension for ConfidentialMintBurn {
+    const TYPE: ExtensionType = ExtensionType::ConfidentialMintBurn;
+}
diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs
new file mode 100644
index 000000000..f06973377
--- /dev/null
+++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs
@@ -0,0 +1,402 @@
+#[cfg(feature = "zk-ops")]
+use spl_token_confidential_transfer_ciphertext_arithmetic as ciphertext_arithmetic;
+use {
+    crate::{
+        check_program_account,
+        error::TokenError,
+        extension::{
+            confidential_mint_burn::{
+                instruction::{
+                    BurnInstructionData, ConfidentialMintBurnInstruction, InitializeMintData,
+                    MintInstructionData, RotateSupplyElGamalPubkeyData,
+                    UpdateDecryptableSupplyData,
+                },
+                verify_proof::{verify_burn_proof, verify_mint_proof},
+                ConfidentialMintBurn,
+            },
+            confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint},
+            BaseStateWithExtensions, BaseStateWithExtensionsMut, PodStateWithExtensionsMut,
+        },
+        instruction::{decode_instruction_data, decode_instruction_type},
+        pod::{PodAccount, PodMint},
+        processor::Processor,
+    },
+    solana_program::{
+        account_info::{next_account_info, AccountInfo},
+        entrypoint::ProgramResult,
+        msg,
+        program_error::ProgramError,
+        pubkey::Pubkey,
+    },
+    solana_zk_sdk::{
+        encryption::pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey},
+        zk_elgamal_proof_program::proof_data::{
+            CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData,
+        },
+    },
+    spl_token_confidential_transfer_proof_extraction::instruction::verify_and_extract_context,
+};
+
+/// Processes an [InitializeMint] instruction.
+fn process_initialize_mint(accounts: &[AccountInfo], data: &InitializeMintData) -> ProgramResult {
+    let account_info_iter = &mut accounts.iter();
+    let mint_info = next_account_info(account_info_iter)?;
+
+    check_program_account(mint_info.owner)?;
+
+    let mint_data = &mut mint_info.data.borrow_mut();
+    let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack_uninitialized(mint_data)?;
+    let mint_burn_extension = mint.init_extension::<ConfidentialMintBurn>(true)?;
+
+    mint_burn_extension.supply_elgamal_pubkey = data.supply_elgamal_pubkey;
+    mint_burn_extension.decryptable_supply = data.decryptable_supply;
+
+    Ok(())
+}
+
+/// Processes an [RotateSupplyElGamal] instruction.
+#[cfg(feature = "zk-ops")]
+fn process_rotate_supply_elgamal_pubkey(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    data: &RotateSupplyElGamalPubkeyData,
+) -> ProgramResult {
+    let account_info_iter = &mut accounts.iter();
+    let mint_info = next_account_info(account_info_iter)?;
+
+    check_program_account(mint_info.owner)?;
+    let mint_data = &mut mint_info.data.borrow_mut();
+    let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack(mint_data)?;
+    let mint_authority = mint.base.mint_authority;
+    let mint_burn_extension = mint.get_extension_mut::<ConfidentialMintBurn>()?;
+
+    let proof_context = verify_and_extract_context::<
+        CiphertextCiphertextEqualityProofData,
+        CiphertextCiphertextEqualityProofContext,
+    >(
+        account_info_iter,
+        data.proof_instruction_offset as i64,
+        None,
+    )?;
+
+    let supply_elgamal_pubkey: Option<PodElGamalPubkey> =
+        mint_burn_extension.supply_elgamal_pubkey.into();
+    let Some(supply_elgamal_pubkey) = supply_elgamal_pubkey else {
+        return Err(TokenError::InvalidState.into());
+    };
+
+    if !supply_elgamal_pubkey.eq(&proof_context.first_pubkey) {
+        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
+    }
+    if mint_burn_extension.confidential_supply != proof_context.first_ciphertext {
+        return Err(ProgramError::InvalidInstructionData);
+    }
+
+    let authority_info = next_account_info(account_info_iter)?;
+    let authority_info_data_len = authority_info.data_len();
+    let authority = mint_authority.ok_or(TokenError::NoAuthorityExists)?;
+
+    Processor::validate_owner(
+        program_id,
+        &authority,
+        authority_info,
+        authority_info_data_len,
+        account_info_iter.as_slice(),
+    )?;
+
+    mint_burn_extension.supply_elgamal_pubkey = proof_context.second_pubkey;
+    mint_burn_extension.confidential_supply = proof_context.second_ciphertext;
+
+    Ok(())
+}
+
+/// Processes an [UpdateAuthority] instruction.
+fn process_update_decryptable_supply(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    new_decryptable_supply: PodAeCiphertext,
+) -> ProgramResult {
+    let account_info_iter = &mut accounts.iter();
+    let mint_info = next_account_info(account_info_iter)?;
+
+    check_program_account(mint_info.owner)?;
+    let mint_data = &mut mint_info.data.borrow_mut();
+    let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack(mint_data)?;
+    let mint_authority = mint.base.mint_authority;
+    let mint_burn_extension = mint.get_extension_mut::<ConfidentialMintBurn>()?;
+
+    let authority_info = next_account_info(account_info_iter)?;
+    let authority_info_data_len = authority_info.data_len();
+    let authority = mint_authority.ok_or(TokenError::NoAuthorityExists)?;
+
+    Processor::validate_owner(
+        program_id,
+        &authority,
+        authority_info,
+        authority_info_data_len,
+        account_info_iter.as_slice(),
+    )?;
+
+    mint_burn_extension.decryptable_supply = new_decryptable_supply;
+
+    Ok(())
+}
+
+/// Processes a [ConfidentialMint] instruction.
+#[cfg(feature = "zk-ops")]
+fn process_confidential_mint(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    data: &MintInstructionData,
+) -> ProgramResult {
+    let account_info_iter = &mut accounts.iter();
+    let token_account_info = next_account_info(account_info_iter)?;
+    let mint_info = next_account_info(account_info_iter)?;
+
+    check_program_account(mint_info.owner)?;
+    let mint_data = &mut mint_info.data.borrow_mut();
+    let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack(mint_data)?;
+    let mint_authority = mint.base.mint_authority;
+
+    let auditor_elgamal_pubkey = mint
+        .get_extension::<ConfidentialTransferMint>()?
+        .auditor_elgamal_pubkey;
+    let mint_burn_extension = mint.get_extension_mut::<ConfidentialMintBurn>()?;
+
+    let proof_context = verify_mint_proof(
+        account_info_iter,
+        data.equality_proof_instruction_offset,
+        data.ciphertext_validity_proof_instruction_offset,
+        data.range_proof_instruction_offset,
+    )?;
+
+    check_program_account(token_account_info.owner)?;
+    let token_account_data = &mut token_account_info.data.borrow_mut();
+    let mut token_account = PodStateWithExtensionsMut::<PodAccount>::unpack(token_account_data)?;
+
+    let authority_info = next_account_info(account_info_iter)?;
+    let authority_info_data_len = authority_info.data_len();
+    let authority = mint_authority.ok_or(TokenError::NoAuthorityExists)?;
+
+    Processor::validate_owner(
+        program_id,
+        &authority,
+        authority_info,
+        authority_info_data_len,
+        account_info_iter.as_slice(),
+    )?;
+
+    if token_account.base.is_frozen() {
+        return Err(TokenError::AccountFrozen.into());
+    }
+
+    if token_account.base.mint != *mint_info.key {
+        return Err(TokenError::MintMismatch.into());
+    }
+
+    assert!(!token_account.base.is_native());
+
+    let confidential_transfer_account =
+        token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
+    confidential_transfer_account.valid_as_destination()?;
+
+    if proof_context.mint_pubkeys.destination != confidential_transfer_account.elgamal_pubkey {
+        return Err(ProgramError::InvalidInstructionData);
+    }
+
+    if let Some(auditor_pubkey) = Option::<PodElGamalPubkey>::from(auditor_elgamal_pubkey) {
+        if auditor_pubkey != proof_context.mint_pubkeys.auditor {
+            return Err(ProgramError::InvalidInstructionData);
+        }
+    }
+
+    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)?,
+    )
+    .ok_or(TokenError::CiphertextArithmeticFailed)?;
+    confidential_transfer_account.pending_balance_hi = ciphertext_arithmetic::add(
+        &confidential_transfer_account.pending_balance_hi,
+        &proof_context
+            .mint_amount_ciphertext_hi
+            .try_extract_ciphertext(0)
+            .map_err(|_| ProgramError::InvalidAccountData)?,
+    )
+    .ok_or(TokenError::CiphertextArithmeticFailed)?;
+
+    confidential_transfer_account.increment_pending_balance_credit_counter()?;
+
+    // update supply
+    if mint_burn_extension.supply_elgamal_pubkey != proof_context.mint_pubkeys.supply {
+        return Err(ProgramError::InvalidInstructionData);
+    }
+    let current_supply = mint_burn_extension.confidential_supply;
+    mint_burn_extension.confidential_supply = ciphertext_arithmetic::add_with_lo_hi(
+        &current_supply,
+        &proof_context
+            .mint_amount_ciphertext_lo
+            .try_extract_ciphertext(2)
+            .map_err(|_| ProgramError::InvalidAccountData)?,
+        &proof_context
+            .mint_amount_ciphertext_hi
+            .try_extract_ciphertext(2)
+            .map_err(|_| ProgramError::InvalidAccountData)?,
+    )
+    .ok_or(TokenError::CiphertextArithmeticFailed)?;
+    mint_burn_extension.decryptable_supply = data.new_decryptable_supply;
+
+    Ok(())
+}
+
+/// Processes a [ConfidentialBurn] instruction.
+#[cfg(feature = "zk-ops")]
+fn process_confidential_burn(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    data: &BurnInstructionData,
+) -> ProgramResult {
+    let account_info_iter = &mut accounts.iter();
+    let token_account_info = next_account_info(account_info_iter)?;
+    let mint_info = next_account_info(account_info_iter)?;
+
+    check_program_account(mint_info.owner)?;
+    let mint_data = &mut mint_info.data.borrow_mut();
+    let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack(mint_data)?;
+
+    let auditor_elgamal_pubkey = mint
+        .get_extension::<ConfidentialTransferMint>()?
+        .auditor_elgamal_pubkey;
+    let mint_burn_extension = mint.get_extension_mut::<ConfidentialMintBurn>()?;
+
+    let proof_context = verify_burn_proof(
+        account_info_iter,
+        data.equality_proof_instruction_offset,
+        data.ciphertext_validity_proof_instruction_offset,
+        data.range_proof_instruction_offset,
+    )?;
+
+    check_program_account(token_account_info.owner)?;
+    let token_account_data = &mut token_account_info.data.borrow_mut();
+    let mut token_account = PodStateWithExtensionsMut::<PodAccount>::unpack(token_account_data)?;
+
+    let authority_info = next_account_info(account_info_iter)?;
+    let authority_info_data_len = authority_info.data_len();
+
+    Processor::validate_owner(
+        program_id,
+        &token_account.base.owner,
+        authority_info,
+        authority_info_data_len,
+        account_info_iter.as_slice(),
+    )?;
+
+    if token_account.base.is_frozen() {
+        return Err(TokenError::AccountFrozen.into());
+    }
+
+    if token_account.base.mint != *mint_info.key {
+        return Err(TokenError::MintMismatch.into());
+    }
+
+    let confidential_transfer_account =
+        token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
+    confidential_transfer_account.valid_as_source()?;
+
+    // Check that the source encryption public key is consistent with what was
+    // actually used to generate the zkp.
+    if proof_context.burn_pubkeys.source != confidential_transfer_account.elgamal_pubkey {
+        return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
+    }
+
+    let burn_amount_lo = &proof_context
+        .burn_amount_ciphertext_lo
+        .try_extract_ciphertext(0)
+        .map_err(|_| ProgramError::InvalidAccountData)?;
+    let burn_amount_hi = &proof_context
+        .burn_amount_ciphertext_hi
+        .try_extract_ciphertext(0)
+        .map_err(|_| ProgramError::InvalidAccountData)?;
+
+    let new_source_available_balance = ciphertext_arithmetic::subtract_with_lo_hi(
+        &confidential_transfer_account.available_balance,
+        burn_amount_lo,
+        burn_amount_hi,
+    )
+    .ok_or(TokenError::CiphertextArithmeticFailed)?;
+
+    // Check that the computed available balance is consistent with what was
+    // actually used to generate the zkp on the client side.
+    if new_source_available_balance != proof_context.remaining_balance_ciphertext {
+        return Err(TokenError::ConfidentialTransferBalanceMismatch.into());
+    }
+
+    confidential_transfer_account.available_balance = new_source_available_balance;
+    confidential_transfer_account.decryptable_available_balance =
+        data.new_decryptable_available_balance;
+
+    if let Some(auditor_pubkey) = Option::<PodElGamalPubkey>::from(auditor_elgamal_pubkey) {
+        if auditor_pubkey != proof_context.burn_pubkeys.auditor {
+            return Err(ProgramError::InvalidInstructionData);
+        }
+    }
+
+    // update supply
+    if mint_burn_extension.supply_elgamal_pubkey != proof_context.burn_pubkeys.supply {
+        return Err(ProgramError::InvalidInstructionData);
+    }
+    let current_supply = mint_burn_extension.confidential_supply;
+    mint_burn_extension.confidential_supply = ciphertext_arithmetic::subtract_with_lo_hi(
+        &current_supply,
+        &proof_context
+            .burn_amount_ciphertext_lo
+            .try_extract_ciphertext(2)
+            .map_err(|_| ProgramError::InvalidAccountData)?,
+        &proof_context
+            .burn_amount_ciphertext_hi
+            .try_extract_ciphertext(2)
+            .map_err(|_| ProgramError::InvalidAccountData)?,
+    )
+    .ok_or(TokenError::CiphertextArithmeticFailed)?;
+
+    Ok(())
+}
+
+#[allow(dead_code)]
+pub(crate) fn process_instruction(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    input: &[u8],
+) -> ProgramResult {
+    check_program_account(program_id)?;
+
+    match decode_instruction_type(input)? {
+        ConfidentialMintBurnInstruction::InitializeMint => {
+            msg!("ConfidentialMintBurnInstruction::InitializeMint");
+            let data = decode_instruction_data::<InitializeMintData>(input)?;
+            process_initialize_mint(accounts, data)
+        }
+        ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey => {
+            msg!("ConfidentialMintBurnInstruction::RotateSupplyElGamal");
+            let data = decode_instruction_data::<RotateSupplyElGamalPubkeyData>(input)?;
+            process_rotate_supply_elgamal_pubkey(program_id, accounts, data)
+        }
+        ConfidentialMintBurnInstruction::UpdateDecryptableSupply => {
+            msg!("ConfidentialMintBurnInstruction::UpdateDecryptableSupply");
+            let data = decode_instruction_data::<UpdateDecryptableSupplyData>(input)?;
+            process_update_decryptable_supply(program_id, accounts, data.new_decryptable_supply)
+        }
+        ConfidentialMintBurnInstruction::Mint => {
+            msg!("ConfidentialMintBurnInstruction::ConfidentialMint");
+            let data = decode_instruction_data::<MintInstructionData>(input)?;
+            process_confidential_mint(program_id, accounts, data)
+        }
+        ConfidentialMintBurnInstruction::Burn => {
+            msg!("ConfidentialMintBurnInstruction::ConfidentialBurn");
+            let data = decode_instruction_data::<BurnInstructionData>(input)?;
+            process_confidential_burn(program_id, accounts, data)
+        }
+    }
+}
diff --git a/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs b/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs
new file mode 100644
index 000000000..cb1e3392d
--- /dev/null
+++ b/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs
@@ -0,0 +1,114 @@
+use crate::error::TokenError;
+#[cfg(feature = "zk-ops")]
+use {
+    solana_program::{
+        account_info::{next_account_info, AccountInfo},
+        program_error::ProgramError,
+    },
+    solana_zk_sdk::zk_elgamal_proof_program::proof_data::{
+        BatchedGroupedCiphertext3HandlesValidityProofContext,
+        BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofContext,
+        BatchedRangeProofU128Data, CiphertextCommitmentEqualityProofContext,
+        CiphertextCommitmentEqualityProofData,
+    },
+    spl_token_confidential_transfer_proof_extraction::{
+        burn::BurnProofContext, instruction::verify_and_extract_context, mint::MintProofContext,
+    },
+    std::slice::Iter,
+};
+
+/// Verify zero-knowledge proofs needed for a [ConfidentialMint] instruction and
+/// return the corresponding proof context information.
+#[cfg(feature = "zk-ops")]
+pub fn verify_mint_proof(
+    account_info_iter: &mut Iter<'_, AccountInfo<'_>>,
+    equality_proof_instruction_offset: i8,
+    ciphertext_validity_proof_instruction_offset: i8,
+    range_proof_instruction_offset: i8,
+) -> Result<MintProofContext, ProgramError> {
+    let sysvar_account_info = if equality_proof_instruction_offset != 0 {
+        Some(next_account_info(account_info_iter)?)
+    } else {
+        None
+    };
+
+    let equality_proof_context = verify_and_extract_context::<
+        CiphertextCommitmentEqualityProofData,
+        CiphertextCommitmentEqualityProofContext,
+    >(
+        account_info_iter,
+        equality_proof_instruction_offset as i64,
+        sysvar_account_info,
+    )?;
+
+    let ciphertext_validity_proof_context = verify_and_extract_context::<
+        BatchedGroupedCiphertext3HandlesValidityProofData,
+        BatchedGroupedCiphertext3HandlesValidityProofContext,
+    >(
+        account_info_iter,
+        ciphertext_validity_proof_instruction_offset as i64,
+        sysvar_account_info,
+    )?;
+
+    let range_proof_context =
+        verify_and_extract_context::<BatchedRangeProofU128Data, BatchedRangeProofContext>(
+            account_info_iter,
+            range_proof_instruction_offset as i64,
+            sysvar_account_info,
+        )?;
+
+    Ok(MintProofContext::verify_and_extract(
+        &equality_proof_context,
+        &ciphertext_validity_proof_context,
+        &range_proof_context,
+    )
+    .map_err(|e| -> TokenError { e.into() })?)
+}
+
+/// Verify zero-knowledge proofs needed for a [ConfidentialBurn] instruction and
+/// return the corresponding proof context information.
+#[cfg(feature = "zk-ops")]
+pub fn verify_burn_proof(
+    account_info_iter: &mut Iter<'_, AccountInfo<'_>>,
+    equality_proof_instruction_offset: i8,
+    ciphertext_validity_proof_instruction_offset: i8,
+    range_proof_instruction_offset: i8,
+) -> Result<BurnProofContext, ProgramError> {
+    let sysvar_account_info = if equality_proof_instruction_offset != 0 {
+        Some(next_account_info(account_info_iter)?)
+    } else {
+        None
+    };
+
+    let equality_proof_context = verify_and_extract_context::<
+        CiphertextCommitmentEqualityProofData,
+        CiphertextCommitmentEqualityProofContext,
+    >(
+        account_info_iter,
+        equality_proof_instruction_offset as i64,
+        sysvar_account_info,
+    )?;
+
+    let ciphertext_validity_proof_context = verify_and_extract_context::<
+        BatchedGroupedCiphertext3HandlesValidityProofData,
+        BatchedGroupedCiphertext3HandlesValidityProofContext,
+    >(
+        account_info_iter,
+        ciphertext_validity_proof_instruction_offset as i64,
+        sysvar_account_info,
+    )?;
+
+    let range_proof_context =
+        verify_and_extract_context::<BatchedRangeProofU128Data, BatchedRangeProofContext>(
+            account_info_iter,
+            range_proof_instruction_offset as i64,
+            sysvar_account_info,
+        )?;
+
+    Ok(BurnProofContext::verify_and_extract(
+        &equality_proof_context,
+        &ciphertext_validity_proof_context,
+        &range_proof_context,
+    )
+    .map_err(|e| -> TokenError { e.into() })?)
+}
diff --git a/token/program-2022/src/extension/confidential_transfer/account_info.rs b/token/program-2022/src/extension/confidential_transfer/account_info.rs
index ed9a94e68..39b819b9a 100644
--- a/token/program-2022/src/extension/confidential_transfer/account_info.rs
+++ b/token/program-2022/src/extension/confidential_transfer/account_info.rs
@@ -324,7 +324,8 @@ impl TransferAccountInfo {
     }
 }
 
-fn combine_balances(balance_lo: u64, balance_hi: u64) -> Option<u64> {
+/// Combines pending balances low and high bits into singular pending balance
+pub fn combine_balances(balance_lo: u64, balance_hi: u64) -> Option<u64> {
     balance_hi
         .checked_shl(PENDING_BALANCE_LO_BIT_LENGTH)?
         .checked_add(balance_lo)
diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs
index e82f4f521..335fdf4a2 100644
--- a/token/program-2022/src/extension/confidential_transfer/instruction.rs
+++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs
@@ -182,6 +182,7 @@ pub enum ConfidentialTransferInstruction {
     ///
     /// Fails if the source or destination accounts are frozen.
     /// Fails if the associated mint is extended as `NonTransferable`.
+    /// Fails if the associated mint is extended as `ConfidentialMintBurn`.
     ///
     /// Accounts expected by this instruction:
     ///
@@ -215,6 +216,7 @@ pub enum ConfidentialTransferInstruction {
     ///
     /// Fails if the source or destination accounts are frozen.
     /// Fails if the associated mint is extended as `NonTransferable`.
+    /// Fails if the associated mint is extended as `ConfidentialMintBurn`.
     ///
     /// Accounts expected by this instruction:
     ///
diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs
index a93b5e852..c387dedff 100644
--- a/token/program-2022/src/extension/confidential_transfer/processor.rs
+++ b/token/program-2022/src/extension/confidential_transfer/processor.rs
@@ -1,6 +1,7 @@
 // Remove feature once zk ops syscalls are enabled on all networks
 #[cfg(feature = "zk-ops")]
 use {
+    crate::extension::confidential_mint_burn::ConfidentialMintBurn,
     crate::extension::non_transferable::NonTransferableAccount,
     spl_token_confidential_transfer_ciphertext_arithmetic as ciphertext_arithmetic,
 };
@@ -262,6 +263,7 @@ fn process_configure_account(
     // `ConfidentialTransferAccount` extension
     let confidential_transfer_account =
         token_account.init_extension::<ConfidentialTransferAccount>(false)?;
+
     confidential_transfer_account.approved = confidential_transfer_mint.auto_approve_new_accounts;
     confidential_transfer_account.elgamal_pubkey = elgamal_pubkey;
     confidential_transfer_account.maximum_pending_balance_credit_counter =
@@ -399,6 +401,10 @@ fn process_deposit(
         return Err(TokenError::MintDecimalsMismatch.into());
     }
 
+    if mint.get_extension::<ConfidentialMintBurn>().is_ok() {
+        return Err(TokenError::IllegalMintBurnConversion.into());
+    }
+
     check_program_account(token_account_info.owner)?;
     let token_account_data = &mut token_account_info.data.borrow_mut();
     let mut token_account = PodStateWithExtensionsMut::<PodAccount>::unpack(token_account_data)?;
@@ -508,6 +514,10 @@ fn process_withdraw(
         return Err(TokenError::MintDecimalsMismatch.into());
     }
 
+    if mint.get_extension::<ConfidentialMintBurn>().is_ok() {
+        return Err(TokenError::IllegalMintBurnConversion.into());
+    }
+
     check_program_account(token_account_info.owner)?;
     let token_account_data = &mut token_account_info.data.borrow_mut();
     let mut token_account = PodStateWithExtensionsMut::<PodAccount>::unpack(token_account_data)?;
@@ -757,6 +767,7 @@ fn process_transfer(
     Ok(())
 }
 
+/// Processes the changes for the sending party of a confidential transfer
 #[cfg(feature = "zk-ops")]
 fn process_source_for_transfer(
     program_id: &Pubkey,
diff --git a/token/program-2022/src/extension/mod.rs b/token/program-2022/src/extension/mod.rs
index 0cfc0cbdc..f2e09ab2a 100644
--- a/token/program-2022/src/extension/mod.rs
+++ b/token/program-2022/src/extension/mod.rs
@@ -6,6 +6,7 @@ use {
     crate::{
         error::TokenError,
         extension::{
+            confidential_mint_burn::ConfidentialMintBurn,
             confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint},
             confidential_transfer_fee::{
                 ConfidentialTransferFeeAmount, ConfidentialTransferFeeConfig,
@@ -84,6 +85,9 @@ pub mod transfer_fee;
 /// Transfer Hook extension
 pub mod transfer_hook;
 
+/// Confidential mint-burn extension
+pub mod confidential_mint_burn;
+
 /// Length in TLV structure
 #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
 #[repr(transparent)]
@@ -1101,6 +1105,9 @@ pub enum ExtensionType {
     GroupMemberPointer,
     /// Mint contains token group member configurations
     TokenGroupMember,
+    /// Mint allowing the minting and burning of confidential tokens
+    ConfidentialMintBurn,
+
     /// Test variable-length mint extension
     #[cfg(test)]
     VariableLenMintTest = u16::MAX - 2,
@@ -1181,6 +1188,7 @@ impl ExtensionType {
             ExtensionType::TokenGroup => pod_get_packed_len::<TokenGroup>(),
             ExtensionType::GroupMemberPointer => pod_get_packed_len::<GroupMemberPointer>(),
             ExtensionType::TokenGroupMember => pod_get_packed_len::<TokenGroupMember>(),
+            ExtensionType::ConfidentialMintBurn => pod_get_packed_len::<ConfidentialMintBurn>(),
             #[cfg(test)]
             ExtensionType::AccountPaddingTest => pod_get_packed_len::<AccountPaddingTest>(),
             #[cfg(test)]
@@ -1244,6 +1252,7 @@ impl ExtensionType {
             | ExtensionType::GroupPointer
             | ExtensionType::TokenGroup
             | ExtensionType::GroupMemberPointer
+            | ExtensionType::ConfidentialMintBurn
             | ExtensionType::TokenGroupMember => AccountType::Mint,
             ExtensionType::ImmutableOwner
             | ExtensionType::TransferFeeAmount
@@ -1295,6 +1304,7 @@ impl ExtensionType {
         let mut transfer_fee_config = false;
         let mut confidential_transfer_mint = false;
         let mut confidential_transfer_fee_config = false;
+        let mut confidential_mint_burn = false;
 
         for extension_type in mint_extension_types {
             match extension_type {
@@ -1303,6 +1313,7 @@ impl ExtensionType {
                 ExtensionType::ConfidentialTransferFeeConfig => {
                     confidential_transfer_fee_config = true
                 }
+                ExtensionType::ConfidentialMintBurn => confidential_mint_burn = true,
                 _ => (),
             }
         }
@@ -1316,6 +1327,10 @@ impl ExtensionType {
             return Err(TokenError::InvalidExtensionCombination);
         }
 
+        if confidential_mint_burn && !confidential_transfer_mint {
+            return Err(TokenError::InvalidExtensionCombination);
+        }
+
         Ok(())
     }
 }
diff --git a/token/program-2022/src/instruction.rs b/token/program-2022/src/instruction.rs
index 9b2cb5510..3d766aa02 100644
--- a/token/program-2022/src/instruction.rs
+++ b/token/program-2022/src/instruction.rs
@@ -708,6 +708,9 @@ pub enum TokenInstruction<'a> {
     /// for further details about the extended instructions that share this
     /// instruction prefix
     GroupMemberPointerExtension,
+    /// Instruction prefix for instructions to the confidential-mint-burn
+    /// extension
+    ConfidentialMintBurnExtension,
 }
 impl<'a> TokenInstruction<'a> {
     /// Unpacks a byte buffer into a
@@ -847,6 +850,7 @@ impl<'a> TokenInstruction<'a> {
             39 => Self::MetadataPointerExtension,
             40 => Self::GroupPointerExtension,
             41 => Self::GroupMemberPointerExtension,
+            42 => Self::ConfidentialMintBurnExtension,
             _ => return Err(TokenError::InvalidInstruction.into()),
         })
     }
@@ -1018,6 +1022,9 @@ impl<'a> TokenInstruction<'a> {
             &Self::GroupMemberPointerExtension => {
                 buf.push(41);
             }
+            &Self::ConfidentialMintBurnExtension => {
+                buf.push(42);
+            }
         };
         buf
     }
diff --git a/token/program-2022/src/pod_instruction.rs b/token/program-2022/src/pod_instruction.rs
index dcf487c96..a08f8b68a 100644
--- a/token/program-2022/src/pod_instruction.rs
+++ b/token/program-2022/src/pod_instruction.rs
@@ -114,6 +114,7 @@ pub(crate) enum PodTokenInstruction {
     // 40
     GroupPointerExtension,
     GroupMemberPointerExtension,
+    ConfidentialMintBurnExtension,
 }
 
 fn unpack_pubkey_option(input: &[u8]) -> Result<PodCOption<Pubkey>, ProgramError> {
diff --git a/token/program-2022/src/processor.rs b/token/program-2022/src/processor.rs
index 93e00c157..697cf042f 100644
--- a/token/program-2022/src/processor.rs
+++ b/token/program-2022/src/processor.rs
@@ -5,6 +5,7 @@ use {
         check_program_account,
         error::TokenError,
         extension::{
+            confidential_mint_burn::{self, ConfidentialMintBurn},
             confidential_transfer::{self, ConfidentialTransferAccount, ConfidentialTransferMint},
             confidential_transfer_fee::{
                 self, ConfidentialTransferFeeAmount, ConfidentialTransferFeeConfig,
@@ -322,6 +323,7 @@ impl Processor {
         {
             return Err(TokenError::NonTransferable.into());
         }
+
         let (fee, maybe_permanent_delegate, maybe_transfer_hook_program_id) =
             if let Some((mint_info, expected_decimals)) = expected_mint_info {
                 if &source_account.base.mint != mint_info.key {
@@ -369,9 +371,9 @@ impl Processor {
                     .is_ok()
                 {
                     return Err(TokenError::MintRequiredForTransfer.into());
-                } else {
-                    (0, None, None)
                 }
+
+                (0, None, None)
             };
         if let Some(expected_fee) = expected_fee {
             if expected_fee != fee {
@@ -954,6 +956,10 @@ impl Processor {
             return Err(TokenError::NonTransferableNeedsImmutableOwnership.into());
         }
 
+        if mint.get_extension::<ConfidentialMintBurn>().is_ok() {
+            return Err(TokenError::IllegalMintBurnConversion.into());
+        }
+
         if let Some(expected_decimals) = expected_decimals {
             if expected_decimals != mint.base.decimals {
                 return Err(TokenError::MintDecimalsMismatch.into());
@@ -1793,6 +1799,14 @@ impl Processor {
                         &input[1..],
                     )
                 }
+                PodTokenInstruction::ConfidentialMintBurnExtension => {
+                    msg!("Instruction: ConfidentialMintBurnExtension");
+                    confidential_mint_burn::processor::process_instruction(
+                        program_id,
+                        accounts,
+                        &input[1..],
+                    )
+                }
             }
         } else if let Ok(instruction) = TokenMetadataInstruction::unpack(input) {
             token_metadata::processor::process_instruction(program_id, accounts, instruction)


Test patch:
diff --git a/token/confidential-transfer/proof-tests/tests/proof_test.rs b/token/confidential-transfer/proof-tests/tests/proof_test.rs
index f4c3a7f3a..6e73e303e 100644
--- a/token/confidential-transfer/proof-tests/tests/proof_test.rs
+++ b/token/confidential-transfer/proof-tests/tests/proof_test.rs
@@ -217,16 +217,16 @@ fn test_mint_validity(mint_amount: u64, supply: u64) {
     let supply_aes_key = AeKey::new_rand();
 
     let supply_ciphertext = supply_keypair.pubkey().encrypt(supply);
-    let decryptable_supply = supply_aes_key.encrypt(supply);
 
     let MintProofData {
         equality_proof_data,
         ciphertext_validity_proof_data,
         range_proof_data,
+        new_decryptable_supply: _,
     } = mint_split_proof_data(
         &supply_ciphertext,
-        &decryptable_supply,
         mint_amount,
+        supply,
         &supply_keypair,
         &supply_aes_key,
         destination_pubkey,