spl_token_2022/extension/confidential_mint_burn/
processor.rs1#[cfg(feature = "zk-ops")]
2use spl_token_confidential_transfer_ciphertext_arithmetic as ciphertext_arithmetic;
3use {
4 crate::{
5 check_auditor_ciphertext, check_program_account,
6 error::TokenError,
7 extension::{
8 confidential_mint_burn::{
9 instruction::{
10 BurnInstructionData, ConfidentialMintBurnInstruction, InitializeMintData,
11 MintInstructionData, RotateSupplyElGamalPubkeyData,
12 UpdateDecryptableSupplyData,
13 },
14 verify_proof::{verify_burn_proof, verify_mint_proof},
15 ConfidentialMintBurn,
16 },
17 confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint},
18 pausable::PausableConfig,
19 BaseStateWithExtensions, BaseStateWithExtensionsMut, PodStateWithExtensionsMut,
20 },
21 instruction::{decode_instruction_data, decode_instruction_type},
22 pod::{PodAccount, PodMint},
23 processor::Processor,
24 },
25 solana_program::{
26 account_info::{next_account_info, AccountInfo},
27 entrypoint::ProgramResult,
28 msg,
29 program_error::ProgramError,
30 pubkey::Pubkey,
31 },
32 solana_zk_sdk::{
33 encryption::pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey},
34 zk_elgamal_proof_program::proof_data::{
35 CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData,
36 },
37 },
38 spl_token_confidential_transfer_proof_extraction::instruction::verify_and_extract_context,
39};
40
41fn process_initialize_mint(accounts: &[AccountInfo], data: &InitializeMintData) -> ProgramResult {
43 let account_info_iter = &mut accounts.iter();
44 let mint_info = next_account_info(account_info_iter)?;
45
46 check_program_account(mint_info.owner)?;
47
48 let mint_data = &mut mint_info.data.borrow_mut();
49 let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack_uninitialized(mint_data)?;
50 let mint_burn_extension = mint.init_extension::<ConfidentialMintBurn>(true)?;
51
52 mint_burn_extension.supply_elgamal_pubkey = data.supply_elgamal_pubkey;
53 mint_burn_extension.decryptable_supply = data.decryptable_supply;
54
55 Ok(())
56}
57
58#[cfg(feature = "zk-ops")]
60fn process_rotate_supply_elgamal_pubkey(
61 program_id: &Pubkey,
62 accounts: &[AccountInfo],
63 data: &RotateSupplyElGamalPubkeyData,
64) -> ProgramResult {
65 let account_info_iter = &mut accounts.iter();
66 let mint_info = next_account_info(account_info_iter)?;
67
68 check_program_account(mint_info.owner)?;
69 let mint_data = &mut mint_info.data.borrow_mut();
70 let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack(mint_data)?;
71 let mint_authority = mint.base.mint_authority;
72 let mint_burn_extension = mint.get_extension_mut::<ConfidentialMintBurn>()?;
73
74 let proof_context = verify_and_extract_context::<
75 CiphertextCiphertextEqualityProofData,
76 CiphertextCiphertextEqualityProofContext,
77 >(
78 account_info_iter,
79 data.proof_instruction_offset as i64,
80 None,
81 )?;
82
83 let supply_elgamal_pubkey: Option<PodElGamalPubkey> =
84 mint_burn_extension.supply_elgamal_pubkey.into();
85 let Some(supply_elgamal_pubkey) = supply_elgamal_pubkey else {
86 return Err(TokenError::InvalidState.into());
87 };
88
89 if !supply_elgamal_pubkey.eq(&proof_context.first_pubkey) {
90 return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
91 }
92 if mint_burn_extension.confidential_supply != proof_context.first_ciphertext {
93 return Err(ProgramError::InvalidInstructionData);
94 }
95
96 let authority_info = next_account_info(account_info_iter)?;
97 let authority_info_data_len = authority_info.data_len();
98 let authority = mint_authority.ok_or(TokenError::NoAuthorityExists)?;
99
100 Processor::validate_owner(
101 program_id,
102 &authority,
103 authority_info,
104 authority_info_data_len,
105 account_info_iter.as_slice(),
106 )?;
107
108 mint_burn_extension.supply_elgamal_pubkey = proof_context.second_pubkey;
109 mint_burn_extension.confidential_supply = proof_context.second_ciphertext;
110
111 Ok(())
112}
113
114fn process_update_decryptable_supply(
116 program_id: &Pubkey,
117 accounts: &[AccountInfo],
118 new_decryptable_supply: PodAeCiphertext,
119) -> ProgramResult {
120 let account_info_iter = &mut accounts.iter();
121 let mint_info = next_account_info(account_info_iter)?;
122
123 check_program_account(mint_info.owner)?;
124 let mint_data = &mut mint_info.data.borrow_mut();
125 let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack(mint_data)?;
126 let mint_authority = mint.base.mint_authority;
127 let mint_burn_extension = mint.get_extension_mut::<ConfidentialMintBurn>()?;
128
129 let authority_info = next_account_info(account_info_iter)?;
130 let authority_info_data_len = authority_info.data_len();
131 let authority = mint_authority.ok_or(TokenError::NoAuthorityExists)?;
132
133 Processor::validate_owner(
134 program_id,
135 &authority,
136 authority_info,
137 authority_info_data_len,
138 account_info_iter.as_slice(),
139 )?;
140
141 mint_burn_extension.decryptable_supply = new_decryptable_supply;
142
143 Ok(())
144}
145
146#[cfg(feature = "zk-ops")]
148fn process_confidential_mint(
149 program_id: &Pubkey,
150 accounts: &[AccountInfo],
151 data: &MintInstructionData,
152) -> ProgramResult {
153 let account_info_iter = &mut accounts.iter();
154 let token_account_info = next_account_info(account_info_iter)?;
155 let mint_info = next_account_info(account_info_iter)?;
156
157 check_program_account(mint_info.owner)?;
158 let mint_data = &mut mint_info.data.borrow_mut();
159 let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack(mint_data)?;
160 let mint_authority = mint.base.mint_authority;
161
162 let auditor_elgamal_pubkey = mint
163 .get_extension::<ConfidentialTransferMint>()?
164 .auditor_elgamal_pubkey;
165 if let Ok(extension) = mint.get_extension::<PausableConfig>() {
166 if extension.paused.into() {
167 return Err(TokenError::MintPaused.into());
168 }
169 }
170 let mint_burn_extension = mint.get_extension_mut::<ConfidentialMintBurn>()?;
171
172 let proof_context = verify_mint_proof(
173 account_info_iter,
174 data.equality_proof_instruction_offset,
175 data.ciphertext_validity_proof_instruction_offset,
176 data.range_proof_instruction_offset,
177 )?;
178
179 check_program_account(token_account_info.owner)?;
180 let token_account_data = &mut token_account_info.data.borrow_mut();
181 let mut token_account = PodStateWithExtensionsMut::<PodAccount>::unpack(token_account_data)?;
182
183 let authority_info = next_account_info(account_info_iter)?;
184 let authority_info_data_len = authority_info.data_len();
185 let authority = mint_authority.ok_or(TokenError::NoAuthorityExists)?;
186
187 Processor::validate_owner(
188 program_id,
189 &authority,
190 authority_info,
191 authority_info_data_len,
192 account_info_iter.as_slice(),
193 )?;
194
195 if token_account.base.is_frozen() {
196 return Err(TokenError::AccountFrozen.into());
197 }
198
199 if token_account.base.mint != *mint_info.key {
200 return Err(TokenError::MintMismatch.into());
201 }
202
203 assert!(!token_account.base.is_native());
204
205 let confidential_transfer_account =
206 token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
207 confidential_transfer_account.valid_as_destination()?;
208
209 if proof_context.mint_pubkeys.destination != confidential_transfer_account.elgamal_pubkey {
210 return Err(ProgramError::InvalidInstructionData);
211 }
212
213 if let Some(auditor_pubkey) = Option::<PodElGamalPubkey>::from(auditor_elgamal_pubkey) {
214 if auditor_pubkey != proof_context.mint_pubkeys.auditor {
215 return Err(ProgramError::InvalidInstructionData);
216 }
217 }
218
219 let proof_context_auditor_ciphertext_lo = proof_context
220 .mint_amount_ciphertext_lo
221 .try_extract_ciphertext(2)
222 .map_err(TokenError::from)?;
223 let proof_context_auditor_ciphertext_hi = proof_context
224 .mint_amount_ciphertext_hi
225 .try_extract_ciphertext(2)
226 .map_err(TokenError::from)?;
227
228 check_auditor_ciphertext(
229 &data.mint_amount_auditor_ciphertext_lo,
230 &data.mint_amount_auditor_ciphertext_hi,
231 &proof_context_auditor_ciphertext_lo,
232 &proof_context_auditor_ciphertext_hi,
233 )?;
234
235 confidential_transfer_account.pending_balance_lo = ciphertext_arithmetic::add(
236 &confidential_transfer_account.pending_balance_lo,
237 &proof_context
238 .mint_amount_ciphertext_lo
239 .try_extract_ciphertext(0)
240 .map_err(TokenError::from)?,
241 )
242 .ok_or(TokenError::CiphertextArithmeticFailed)?;
243 confidential_transfer_account.pending_balance_hi = ciphertext_arithmetic::add(
244 &confidential_transfer_account.pending_balance_hi,
245 &proof_context
246 .mint_amount_ciphertext_hi
247 .try_extract_ciphertext(0)
248 .map_err(TokenError::from)?,
249 )
250 .ok_or(TokenError::CiphertextArithmeticFailed)?;
251
252 confidential_transfer_account.increment_pending_balance_credit_counter()?;
253
254 if mint_burn_extension.supply_elgamal_pubkey != proof_context.mint_pubkeys.supply {
256 return Err(ProgramError::InvalidInstructionData);
257 }
258 let current_supply = mint_burn_extension.confidential_supply;
259 mint_burn_extension.confidential_supply = ciphertext_arithmetic::add_with_lo_hi(
260 ¤t_supply,
261 &proof_context
262 .mint_amount_ciphertext_lo
263 .try_extract_ciphertext(2)
264 .map_err(|_| ProgramError::InvalidAccountData)?,
265 &proof_context
266 .mint_amount_ciphertext_hi
267 .try_extract_ciphertext(2)
268 .map_err(|_| ProgramError::InvalidAccountData)?,
269 )
270 .ok_or(TokenError::CiphertextArithmeticFailed)?;
271 mint_burn_extension.decryptable_supply = data.new_decryptable_supply;
272
273 Ok(())
274}
275
276#[cfg(feature = "zk-ops")]
278fn process_confidential_burn(
279 program_id: &Pubkey,
280 accounts: &[AccountInfo],
281 data: &BurnInstructionData,
282) -> ProgramResult {
283 let account_info_iter = &mut accounts.iter();
284 let token_account_info = next_account_info(account_info_iter)?;
285 let mint_info = next_account_info(account_info_iter)?;
286
287 check_program_account(mint_info.owner)?;
288 let mint_data = &mut mint_info.data.borrow_mut();
289 let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack(mint_data)?;
290
291 let auditor_elgamal_pubkey = mint
292 .get_extension::<ConfidentialTransferMint>()?
293 .auditor_elgamal_pubkey;
294 if let Ok(extension) = mint.get_extension::<PausableConfig>() {
295 if extension.paused.into() {
296 return Err(TokenError::MintPaused.into());
297 }
298 }
299 let mint_burn_extension = mint.get_extension_mut::<ConfidentialMintBurn>()?;
300
301 let proof_context = verify_burn_proof(
302 account_info_iter,
303 data.equality_proof_instruction_offset,
304 data.ciphertext_validity_proof_instruction_offset,
305 data.range_proof_instruction_offset,
306 )?;
307
308 check_program_account(token_account_info.owner)?;
309 let token_account_data = &mut token_account_info.data.borrow_mut();
310 let mut token_account = PodStateWithExtensionsMut::<PodAccount>::unpack(token_account_data)?;
311
312 let authority_info = next_account_info(account_info_iter)?;
313 let authority_info_data_len = authority_info.data_len();
314
315 Processor::validate_owner(
316 program_id,
317 &token_account.base.owner,
318 authority_info,
319 authority_info_data_len,
320 account_info_iter.as_slice(),
321 )?;
322
323 if token_account.base.is_frozen() {
324 return Err(TokenError::AccountFrozen.into());
325 }
326
327 if token_account.base.mint != *mint_info.key {
328 return Err(TokenError::MintMismatch.into());
329 }
330
331 let confidential_transfer_account =
332 token_account.get_extension_mut::<ConfidentialTransferAccount>()?;
333 confidential_transfer_account.valid_as_source()?;
334
335 if proof_context.burn_pubkeys.source != confidential_transfer_account.elgamal_pubkey {
338 return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
339 }
340
341 let proof_context_auditor_ciphertext_lo = proof_context
342 .burn_amount_ciphertext_lo
343 .try_extract_ciphertext(2)
344 .map_err(TokenError::from)?;
345 let proof_context_auditor_ciphertext_hi = proof_context
346 .burn_amount_ciphertext_hi
347 .try_extract_ciphertext(2)
348 .map_err(TokenError::from)?;
349
350 check_auditor_ciphertext(
351 &data.burn_amount_auditor_ciphertext_lo,
352 &data.burn_amount_auditor_ciphertext_hi,
353 &proof_context_auditor_ciphertext_lo,
354 &proof_context_auditor_ciphertext_hi,
355 )?;
356
357 let burn_amount_lo = &proof_context
358 .burn_amount_ciphertext_lo
359 .try_extract_ciphertext(0)
360 .map_err(TokenError::from)?;
361 let burn_amount_hi = &proof_context
362 .burn_amount_ciphertext_hi
363 .try_extract_ciphertext(0)
364 .map_err(TokenError::from)?;
365
366 let new_source_available_balance = ciphertext_arithmetic::subtract_with_lo_hi(
367 &confidential_transfer_account.available_balance,
368 burn_amount_lo,
369 burn_amount_hi,
370 )
371 .ok_or(TokenError::CiphertextArithmeticFailed)?;
372
373 if new_source_available_balance != proof_context.remaining_balance_ciphertext {
376 return Err(TokenError::ConfidentialTransferBalanceMismatch.into());
377 }
378
379 confidential_transfer_account.available_balance = new_source_available_balance;
380 confidential_transfer_account.decryptable_available_balance =
381 data.new_decryptable_available_balance;
382
383 if let Some(auditor_pubkey) = Option::<PodElGamalPubkey>::from(auditor_elgamal_pubkey) {
384 if auditor_pubkey != proof_context.burn_pubkeys.auditor {
385 return Err(ProgramError::InvalidInstructionData);
386 }
387 }
388
389 if mint_burn_extension.supply_elgamal_pubkey != proof_context.burn_pubkeys.supply {
391 return Err(ProgramError::InvalidInstructionData);
392 }
393 let current_supply = mint_burn_extension.confidential_supply;
394 mint_burn_extension.confidential_supply = ciphertext_arithmetic::subtract_with_lo_hi(
395 ¤t_supply,
396 &proof_context
397 .burn_amount_ciphertext_lo
398 .try_extract_ciphertext(2)
399 .map_err(|_| ProgramError::InvalidAccountData)?,
400 &proof_context
401 .burn_amount_ciphertext_hi
402 .try_extract_ciphertext(2)
403 .map_err(|_| ProgramError::InvalidAccountData)?,
404 )
405 .ok_or(TokenError::CiphertextArithmeticFailed)?;
406
407 Ok(())
408}
409
410#[allow(dead_code)]
411pub(crate) fn process_instruction(
412 program_id: &Pubkey,
413 accounts: &[AccountInfo],
414 input: &[u8],
415) -> ProgramResult {
416 check_program_account(program_id)?;
417
418 match decode_instruction_type(input)? {
419 ConfidentialMintBurnInstruction::InitializeMint => {
420 msg!("ConfidentialMintBurnInstruction::InitializeMint");
421 let data = decode_instruction_data::<InitializeMintData>(input)?;
422 process_initialize_mint(accounts, data)
423 }
424 ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey => {
425 msg!("ConfidentialMintBurnInstruction::RotateSupplyElGamal");
426 let data = decode_instruction_data::<RotateSupplyElGamalPubkeyData>(input)?;
427 process_rotate_supply_elgamal_pubkey(program_id, accounts, data)
428 }
429 ConfidentialMintBurnInstruction::UpdateDecryptableSupply => {
430 msg!("ConfidentialMintBurnInstruction::UpdateDecryptableSupply");
431 let data = decode_instruction_data::<UpdateDecryptableSupplyData>(input)?;
432 process_update_decryptable_supply(program_id, accounts, data.new_decryptable_supply)
433 }
434 ConfidentialMintBurnInstruction::Mint => {
435 msg!("ConfidentialMintBurnInstruction::ConfidentialMint");
436 let data = decode_instruction_data::<MintInstructionData>(input)?;
437 process_confidential_mint(program_id, accounts, data)
438 }
439 ConfidentialMintBurnInstruction::Burn => {
440 msg!("ConfidentialMintBurnInstruction::ConfidentialBurn");
441 let data = decode_instruction_data::<BurnInstructionData>(input)?;
442 process_confidential_burn(program_id, accounts, data)
443 }
444 }
445}