spl_token_2022/extension/confidential_mint_burn/
account_info.rs

1use {
2    super::ConfidentialMintBurn,
3    crate::{
4        error::TokenError,
5        extension::confidential_transfer::{
6            ConfidentialTransferAccount, DecryptableBalance, EncryptedBalance,
7        },
8    },
9    bytemuck::{Pod, Zeroable},
10    solana_zk_sdk::{
11        encryption::{
12            auth_encryption::{AeCiphertext, AeKey},
13            elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
14            pedersen::PedersenOpening,
15            pod::{
16                auth_encryption::PodAeCiphertext,
17                elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
18            },
19        },
20        zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData,
21    },
22    spl_token_confidential_transfer_proof_generation::{
23        burn::{burn_split_proof_data, BurnProofData},
24        mint::{mint_split_proof_data, MintProofData},
25    },
26};
27
28/// Confidential Mint Burn extension information needed to construct a
29/// `RotateSupplyElgamalPubkey` instruction.
30#[repr(C)]
31#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
32pub struct SupplyAccountInfo {
33    /// The available balance (encrypted by `supply_elgamal_pubkey`)
34    pub current_supply: PodElGamalCiphertext,
35    /// The decryptable supply
36    pub decryptable_supply: PodAeCiphertext,
37    /// The supply's ElGamal pubkey
38    pub supply_elgamal_pubkey: PodElGamalPubkey,
39}
40
41impl SupplyAccountInfo {
42    /// Creates a `SupplyAccountInfo` from `ConfidentialMintBurn` extension
43    /// account data
44    pub fn new(extension: &ConfidentialMintBurn) -> Self {
45        Self {
46            current_supply: extension.confidential_supply,
47            decryptable_supply: extension.decryptable_supply,
48            supply_elgamal_pubkey: extension.supply_elgamal_pubkey,
49        }
50    }
51
52    /// Computes the current supply from the decryptable supply and the
53    /// difference between the decryptable supply and the ElGamal encrypted
54    /// supply ciphertext
55    pub fn decrypted_current_supply(
56        &self,
57        aes_key: &AeKey,
58        elgamal_keypair: &ElGamalKeypair,
59    ) -> Result<u64, TokenError> {
60        // decrypt the decryptable supply
61        let current_decyptable_supply = AeCiphertext::try_from(self.decryptable_supply)
62            .map_err(|_| TokenError::MalformedCiphertext)?
63            .decrypt(aes_key)
64            .ok_or(TokenError::MalformedCiphertext)?;
65
66        // get the difference between the supply ciphertext and the decryptable supply
67        // explanation see https://github.com/solana-labs/solana-program-library/pull/6881#issuecomment-2385579058
68        let decryptable_supply_ciphertext =
69            elgamal_keypair.pubkey().encrypt(current_decyptable_supply);
70        #[allow(clippy::arithmetic_side_effects)]
71        let supply_delta_ciphertext = decryptable_supply_ciphertext
72            - ElGamalCiphertext::try_from(self.current_supply)
73                .map_err(|_| TokenError::MalformedCiphertext)?;
74        let decryptable_to_current_diff = elgamal_keypair
75            .secret()
76            .decrypt_u32(&supply_delta_ciphertext)
77            .ok_or(TokenError::MalformedCiphertext)?;
78
79        // compute the current supply
80        current_decyptable_supply
81            .checked_sub(decryptable_to_current_diff)
82            .ok_or(TokenError::Overflow)
83    }
84
85    /// Generates the `CiphertextCiphertextEqualityProofData` needed for a
86    /// `RotateSupplyElgamalPubkey` instruction
87    pub fn generate_rotate_supply_elgamal_pubkey_proof(
88        &self,
89        current_supply_elgamal_keypair: &ElGamalKeypair,
90        new_supply_elgamal_pubkey: &ElGamalPubkey,
91        aes_key: &AeKey,
92    ) -> Result<CiphertextCiphertextEqualityProofData, TokenError> {
93        let current_supply =
94            self.decrypted_current_supply(aes_key, current_supply_elgamal_keypair)?;
95
96        let new_supply_opening = PedersenOpening::new_rand();
97        let new_supply_ciphertext =
98            new_supply_elgamal_pubkey.encrypt_with(current_supply, &new_supply_opening);
99
100        CiphertextCiphertextEqualityProofData::new(
101            current_supply_elgamal_keypair,
102            new_supply_elgamal_pubkey,
103            &self
104                .current_supply
105                .try_into()
106                .map_err(|_| TokenError::MalformedCiphertext)?,
107            &new_supply_ciphertext,
108            &new_supply_opening,
109            current_supply,
110        )
111        .map_err(|_| TokenError::ProofGeneration)
112    }
113
114    /// Create a mint proof data that is split into equality, ciphertext
115    /// validity, and range proof.
116    pub fn generate_split_mint_proof_data(
117        &self,
118        mint_amount: u64,
119        supply_elgamal_keypair: &ElGamalKeypair,
120        aes_key: &AeKey,
121        destination_elgamal_pubkey: &ElGamalPubkey,
122        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
123    ) -> Result<MintProofData, TokenError> {
124        let current_supply_ciphertext = self
125            .current_supply
126            .try_into()
127            .map_err(|_| TokenError::MalformedCiphertext)?;
128
129        let current_supply = self.decrypted_current_supply(aes_key, supply_elgamal_keypair)?;
130
131        mint_split_proof_data(
132            &current_supply_ciphertext,
133            mint_amount,
134            current_supply,
135            supply_elgamal_keypair,
136            destination_elgamal_pubkey,
137            auditor_elgamal_pubkey,
138        )
139        .map_err(|e| -> TokenError { e.into() })
140    }
141
142    /// Compute the new decryptable supply.
143    pub fn new_decryptable_supply(
144        &self,
145        mint_amount: u64,
146        elgamal_keypair: &ElGamalKeypair,
147        aes_key: &AeKey,
148    ) -> Result<AeCiphertext, TokenError> {
149        let current_decrypted_supply = self.decrypted_current_supply(aes_key, elgamal_keypair)?;
150        let new_decrypted_available_balance = current_decrypted_supply
151            .checked_add(mint_amount)
152            .ok_or(TokenError::Overflow)?;
153
154        Ok(aes_key.encrypt(new_decrypted_available_balance))
155    }
156}
157
158/// Confidential Mint Burn extension information needed to construct a
159/// `Burn` instruction.
160#[repr(C)]
161#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
162pub struct BurnAccountInfo {
163    /// The available balance (encrypted by `encryption_pubkey`)
164    pub available_balance: EncryptedBalance,
165    /// The decryptable available balance
166    pub decryptable_available_balance: DecryptableBalance,
167}
168
169impl BurnAccountInfo {
170    /// Create the `ApplyPendingBalance` instruction account information from
171    /// `ConfidentialTransferAccount`.
172    pub fn new(account: &ConfidentialTransferAccount) -> Self {
173        Self {
174            available_balance: account.available_balance,
175            decryptable_available_balance: account.decryptable_available_balance,
176        }
177    }
178
179    /// Create a burn proof data that is split into equality, ciphertext
180    /// validity, and range proof.
181    pub fn generate_split_burn_proof_data(
182        &self,
183        burn_amount: u64,
184        source_elgamal_keypair: &ElGamalKeypair,
185        aes_key: &AeKey,
186        supply_elgamal_pubkey: &ElGamalPubkey,
187        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
188    ) -> Result<BurnProofData, TokenError> {
189        let current_available_balance_ciphertext = self
190            .available_balance
191            .try_into()
192            .map_err(|_| TokenError::MalformedCiphertext)?;
193        let current_decryptable_available_balance = self
194            .decryptable_available_balance
195            .try_into()
196            .map_err(|_| TokenError::MalformedCiphertext)?;
197
198        burn_split_proof_data(
199            &current_available_balance_ciphertext,
200            &current_decryptable_available_balance,
201            burn_amount,
202            source_elgamal_keypair,
203            aes_key,
204            supply_elgamal_pubkey,
205            auditor_elgamal_pubkey,
206        )
207        .map_err(|e| -> TokenError { e.into() })
208    }
209
210    /// Compute the new decryptable supply.
211    pub fn new_decryptable_balance(
212        &self,
213        burn_amount: u64,
214        aes_key: &AeKey,
215    ) -> Result<AeCiphertext, TokenError> {
216        let current_available_balance = AeCiphertext::try_from(self.decryptable_available_balance)
217            .map_err(|_| TokenError::MalformedCiphertext)?
218            .decrypt(aes_key)
219            .ok_or(TokenError::MalformedCiphertext)?;
220        let new_decryptable_balance = current_available_balance
221            .checked_sub(burn_amount)
222            .ok_or(TokenError::Overflow)?;
223
224        Ok(aes_key.encrypt(new_decryptable_balance))
225    }
226}