From 8fbb0fb17166499781911f197ce99762a1cf5d1a Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Wed, 19 Jun 2024 09:29:50 -0300 Subject: [PATCH 01/34] confidential mint-burn extension --- token/cli/src/clap_app.rs | 115 +++++ token/cli/src/command.rs | 272 +++++++++++- token/client/src/token.rs | 182 +++++++- token/program-2022/src/error.rs | 8 + .../confidential_mint_burn/instruction.rs | 418 ++++++++++++++++++ .../extension/confidential_mint_burn/mod.rs | 34 ++ .../confidential_mint_burn/processor.rs | 304 +++++++++++++ .../confidential_transfer/account_info.rs | 6 +- .../ciphertext_extraction.rs | 27 ++ .../confidential_transfer/instruction.rs | 27 +- .../confidential_transfer/processor.rs | 52 ++- .../split_proof_generation.rs | 5 + token/program-2022/src/extension/mod.rs | 10 + token/program-2022/src/instruction.rs | 7 + token/program-2022/src/pod_instruction.rs | 1 + token/program-2022/src/processor.rs | 18 +- 16 files changed, 1470 insertions(+), 16 deletions(-) create mode 100644 token/program-2022/src/extension/confidential_mint_burn/instruction.rs create mode 100644 token/program-2022/src/extension/confidential_mint_burn/mod.rs create mode 100644 token/program-2022/src/extension/confidential_mint_burn/processor.rs diff --git a/token/cli/src/clap_app.rs b/token/cli/src/clap_app.rs index 94d67ef87c7..66540fa0305 100644 --- a/token/cli/src/clap_app.rs +++ b/token/cli/src/clap_app.rs @@ -133,6 +133,9 @@ pub enum CommandName { ApplyPendingBalance, UpdateGroupAddress, UpdateMemberAddress, + MintConfidentialTokens, + BurnConfidentialTokens, + ConfidentialBalance, } impl fmt::Display for CommandName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -819,6 +822,14 @@ pub fn app<'a, 'b>( .takes_value(false) .help("Enables group member configurations in the mint. The mint authority must initialize the member."), ) + .arg( + Arg::with_name("enable_confidential_mint_burn") + .long("enable-confidential-mint-burn") + .takes_value(false) + .help( + "Enables minting of new tokens into confidential balance and burning of tokens directly from the confidential balance" + ), + ) .nonce_args(true) .arg(memo_arg()) ) @@ -2616,4 +2627,108 @@ pub fn app<'a, 'b>( .arg(multisig_signer_arg()) .nonce_args(true) ) + .subcommand( + SubCommand::with_name(CommandName::MintConfidentialTokens.into()) + .about("Mint tokens amounts for into confidential balance") + .arg( + Arg::with_name("token") + .long("token") + .validator(is_valid_pubkey) + .value_name("TOKEN_MINT_ADDRESS") + .takes_value(true) + .index(1) + .required(true) + .help("The token address with confidential transfers enabled"), + ) + .arg( + Arg::with_name("amount") + .validator(is_amount_or_all) + .value_name("TOKEN_AMOUNT") + .takes_value(true) + .index(2) + .required(true) + .help("Amount to deposit; accepts keyword ALL"), + ) + .arg( + Arg::with_name("address") + .long("address") + .validator(is_valid_pubkey) + .value_name("TOKEN_ACCOUNT_ADDRESS") + .takes_value(true) + .help("The address of the token account to configure confidential transfers for \ + [default: owner's associated token account]") + ) + .arg( + owner_address_arg() + ) + .arg(multisig_signer_arg()) + .arg(mint_decimals_arg()) + .nonce_args(true) + ) + .subcommand( + SubCommand::with_name(CommandName::BurnConfidentialTokens.into()) + .about("Burn tokens from available confidential balance") + .arg( + Arg::with_name("token") + .long("token") + .validator(is_valid_pubkey) + .value_name("TOKEN_MINT_ADDRESS") + .takes_value(true) + .index(1) + .required(true) + .help("The token address with confidential transfers enabled"), + ) + .arg( + Arg::with_name("amount") + .validator(is_amount_or_all) + .value_name("TOKEN_AMOUNT") + .takes_value(true) + .index(2) + .required(true) + .help("Amount to deposit; accepts keyword ALL"), + ) + .arg( + Arg::with_name("address") + .long("address") + .validator(is_valid_pubkey) + .value_name("TOKEN_ACCOUNT_ADDRESS") + .takes_value(true) + .help("The address of the token account to configure confidential transfers for \ + [default: owner's associated token account]") + ) + .arg( + owner_address_arg() + ) + .arg(multisig_signer_arg()) + .arg(mint_decimals_arg()) + .nonce_args(true) + ) + .subcommand( + SubCommand::with_name(CommandName::ConfidentialBalance.into()) + .about("Display confidential balance") + .arg( + Arg::with_name("address") + .long("address") + .validator(is_valid_pubkey) + .value_name("TOKEN_ACCOUNT_ADDRESS") + .takes_value(true) + .index(1) + .help("The address of the token account to for which to fetch the confidential balance") + ) + .arg( + Arg::with_name("authority") + .long("authority") + .alias("owner") + .validator(is_valid_signer) + .value_name("SIGNER") + .takes_value(true) + .help("Keypair from which encryption keys for token account were derived.") + ) + .arg( + owner_address_arg() + ) + .arg(multisig_signer_arg()) + .arg(mint_decimals_arg()) + .nonce_args(true) + ) } diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 980408d74c7..3cbf7e35344 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -35,10 +35,12 @@ use { }, spl_associated_token_account::get_associated_token_address_with_program_id, spl_token_2022::{ + error::TokenError, extension::{ confidential_transfer::{ account_info::{ - ApplyPendingBalanceAccountInfo, TransferAccountInfo, WithdrawAccountInfo, + combine_balances, ApplyPendingBalanceAccountInfo, TransferAccountInfo, + WithdrawAccountInfo, }, instruction::TransferSplitContextStateAccounts, ConfidentialTransferAccount, ConfidentialTransferMint, @@ -72,7 +74,14 @@ use { }, spl_token_group_interface::state::TokenGroup, spl_token_metadata_interface::state::{Field, TokenMetadata}, - std::{collections::HashMap, fmt::Display, process::exit, rc::Rc, str::FromStr, sync::Arc}, + std::{ + collections::HashMap, + fmt::Display, + process::exit, + rc::Rc, + str::{self, FromStr}, + sync::Arc, + }, }; fn print_error_and_exit(e: E) -> T { @@ -239,6 +248,7 @@ async fn command_create_token( enable_group: bool, enable_member: bool, bulk_signers: Vec>, + enable_confidential_mint_burn: bool, ) -> CommandResult { println_display( config, @@ -285,6 +295,10 @@ async fn command_create_token( extensions.push(ExtensionInitializationParams::DefaultAccountState { state }) } + if enable_confidential_mint_burn { + extensions.push(ExtensionInitializationParams::ConfidentialMintBurnMint { authority }); + } + if let Some((transfer_fee_basis_points, maximum_fee)) = transfer_fee { extensions.push(ExtensionInitializationParams::TransferFeeConfig { transfer_fee_config_authority: Some(authority), @@ -1593,6 +1607,7 @@ async fn command_transfer( ciphertext_validity_proof_data, range_proof_data, source_decrypt_handles, + _, ) = transfer_account_info .generate_split_transfer_proof_data( transfer_balance, @@ -3200,10 +3215,11 @@ async fn command_enable_disable_confidential_transfers( enum ConfidentialInstructionType { Deposit, Withdraw, + Mint, } #[allow(clippy::too_many_arguments)] -async fn command_deposit_withdraw_confidential_tokens( +async fn command_deposit_withdraw_mint_confidential_tokens( config: &Config<'_>, token_pubkey: Pubkey, owner: Pubkey, @@ -3368,6 +3384,11 @@ async fn command_deposit_withdraw_confidential_tokens( ) .await? } + ConfidentialInstructionType::Mint => { + token + .confidential_mint(&token_account_address, &owner, amount, &bulk_signers) + .await? + } }; let tx_return = finish_tx(config, &res, false).await?; @@ -3523,6 +3544,7 @@ pub async fn process_command<'a>( arg_matches.is_present("enable_group"), arg_matches.is_present("enable_member"), bulk_signers, + arg_matches.is_present("enable_confidential_mint_burn"), ) .await } @@ -4472,7 +4494,43 @@ pub async fn process_command<'a>( ) .await } - (c @ CommandName::DepositConfidentialTokens, arg_matches) + (CommandName::BurnConfidentialTokens, arg_matches) => { + let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) + .unwrap() + .unwrap(); + let ui_amount = match arg_matches.value_of("amount").unwrap() { + "ALL" => None, + amount => Some(amount.parse::().unwrap()), + }; + let account = pubkey_of_signer(arg_matches, "address", &mut wallet_manager) + .unwrap() + .unwrap(); + + let (owner_signer, owner) = + config.signer_or_default(arg_matches, "owner", &mut wallet_manager); + + let mint_decimals = value_of::(arg_matches, MINT_DECIMALS_ARG.name); + + let elgamal_keypair = ElGamalKeypair::new_from_signer(&*owner_signer, b"").unwrap(); + let aes_key = AeKey::new_from_signer(&*owner_signer, b"").unwrap(); + + let bulk_signers = vec![owner_signer]; + + command_confidential_burn( + config, + token, + account, + ui_amount, + owner, + mint_decimals, + bulk_signers, + &elgamal_keypair, + &aes_key, + ) + .await + } + (c @ CommandName::MintConfidentialTokens, arg_matches) + | (c @ CommandName::DepositConfidentialTokens, arg_matches) | (c @ CommandName::WithdrawConfidentialTokens, arg_matches) => { let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) .unwrap() @@ -4489,6 +4547,9 @@ pub async fn process_command<'a>( CommandName::DepositConfidentialTokens => { (ConfidentialInstructionType::Deposit, None, None) } + CommandName::MintConfidentialTokens => { + (ConfidentialInstructionType::Mint, None, None) + } CommandName::WithdrawConfidentialTokens => { // Deriving ElGamal and AES key from signer. Custom ElGamal and AES keys will be // supported in the future once upgrading to clap-v3. @@ -4512,7 +4573,7 @@ pub async fn process_command<'a>( push_signer_with_dedup(owner_signer, &mut bulk_signers); } - command_deposit_withdraw_confidential_tokens( + command_deposit_withdraw_mint_confidential_tokens( config, token, owner, @@ -4557,7 +4618,208 @@ pub async fn process_command<'a>( ) .await } + (CommandName::ConfidentialBalance, arg_matches) => { + let address = config + .associated_token_address_or_override(arg_matches, "address", &mut wallet_manager) + .await?; + let (auth_signer, _auth) = + config.signer_or_default(arg_matches, "authority", &mut wallet_manager); + command_confidential_balance(config, address, auth_signer).await + } + } +} + +async fn command_confidential_balance( + config: &Config<'_>, + address: Pubkey, + authority_signer: Arc, +) -> CommandResult { + let account_data = config + .rpc_client + .get_account_data(&address) + .await + .map_err(|_| format!("Could not fetch token account account data for {}", address))?; + + let elgamal_keypair = ElGamalKeypair::new_from_signer(&*authority_signer, b"").unwrap(); + let aes_key = AeKey::new_from_signer(&*authority_signer, b"").unwrap(); + let state_with_extension = StateWithExtensionsOwned::::unpack(account_data)?; + let extension_state = state_with_extension.get_extension::()?; + + let decryptable_available_balance = extension_state + .decryptable_available_balance + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?; + let decrypted_available_balance = aes_key + .decrypt(&decryptable_available_balance) + .ok_or(TokenError::AccountDecryption)?; + + let pending_balance_lo = extension_state + .pending_balance_lo + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?; + let decrypted_pending_balance_lo = elgamal_keypair + .secret() + .decrypt_u32(&pending_balance_lo) + .ok_or(TokenError::AccountDecryption)?; + + let pending_balance_hi = extension_state + .pending_balance_hi + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?; + let decrypted_pending_balance_hi = elgamal_keypair + .secret() + .decrypt_u32(&pending_balance_hi) + .ok_or(TokenError::AccountDecryption)?; + let pending_balance = + combine_balances(decrypted_pending_balance_lo, decrypted_pending_balance_hi) + .ok_or(TokenError::AccountDecryption)?; + + Ok(format!("{address} has a pending balance of {pending_balance} and an available balance of {decrypted_available_balance}") + ) +} + +#[allow(clippy::too_many_arguments)] +async fn command_confidential_burn( + config: &Config<'_>, + token_pubkey: Pubkey, + ata_pubkey: Pubkey, + ui_amount: Option, + authority: Pubkey, + mint_decimals: Option, + bulk_signers: BulkSigners, + elgamal_keypair: &ElGamalKeypair, + aes_key: &AeKey, +) -> CommandResult { + let mint_info = config.get_mint_info(&token_pubkey, mint_decimals).await?; + + // if the user got the decimals wrong, they may well have calculated the + // transfer amount wrong we only check in online mode, because in offline, + // mint_info.decimals is always 9 + if !config.sign_only && mint_decimals.is_some() && mint_decimals != Some(mint_info.decimals) { + return Err(format!( + "Decimals {} was provided, but actual value is {}", + mint_decimals.unwrap(), + mint_info.decimals + ) + .into()); } + + let token = token_client_from_config(config, &token_pubkey, Some(mint_info.decimals))?; + + // the amount the user wants to tranfer, as a f64 + let burn_amount = ui_amount + .map(|ui_amount| spl_token::ui_amount_to_amount(ui_amount, mint_info.decimals)) + .unwrap(); + + // deserialize `pod` ElGamal pubkeys + let recipient_elgamal_pubkey = elgamal_keypair.pubkey(); + let auditor_elgamal_pubkey = None; + + let context_state_authority = config.fee_payer()?; + let equality_proof_context_state_account = Keypair::new(); + let equality_proof_pubkey = equality_proof_context_state_account.pubkey(); + let ciphertext_validity_proof_context_state_account = Keypair::new(); + let ciphertext_validity_proof_pubkey = ciphertext_validity_proof_context_state_account.pubkey(); + let range_proof_context_state_account = Keypair::new(); + let range_proof_pubkey = range_proof_context_state_account.pubkey(); + + let context_state_accounts = TransferSplitContextStateAccounts { + equality_proof: &equality_proof_pubkey, + ciphertext_validity_proof: &ciphertext_validity_proof_pubkey, + range_proof: &range_proof_pubkey, + authority: &context_state_authority.pubkey(), + no_op_on_uninitialized_split_context_state: false, + close_split_context_state_accounts: None, + }; + + let state = token.get_account_info(&ata_pubkey).await.unwrap(); + let extension = state + .get_extension::() + .unwrap(); + let transfer_account_info = TransferAccountInfo::new(extension); + + let ( + equality_proof_data, + ciphertext_validity_proof_data, + range_proof_data, + source_decrypt_handles, + pedersen_openings, + ) = transfer_account_info + .generate_split_transfer_proof_data( + burn_amount, + elgamal_keypair, + aes_key, + recipient_elgamal_pubkey, + auditor_elgamal_pubkey.as_ref(), + ) + .unwrap(); + + // setup proofs + let _ = try_join!( + token.create_range_proof_context_state_for_transfer( + context_state_accounts, + &range_proof_data, + &range_proof_context_state_account, + ), + token.create_equality_proof_context_state_for_transfer( + context_state_accounts, + &equality_proof_data, + &equality_proof_context_state_account, + ), + token.create_ciphertext_validity_proof_context_state_for_transfer( + context_state_accounts, + &ciphertext_validity_proof_data, + &ciphertext_validity_proof_context_state_account, + ) + )?; + + // do the burn + let res = token + .confidential_burn( + &ata_pubkey, + &authority, + context_state_accounts, + burn_amount, + aes_key, + &source_decrypt_handles, + &bulk_signers, + &pedersen_openings, + ) + .await?; + + // close context state accounts + let context_state_authority_pubkey = context_state_authority.pubkey(); + let close_context_state_signers = &[context_state_authority]; + let _ = try_join!( + token.confidential_transfer_close_context_state( + &equality_proof_pubkey, + &authority, + &context_state_authority_pubkey, + close_context_state_signers, + ), + token.confidential_transfer_close_context_state( + &ciphertext_validity_proof_pubkey, + &authority, + &context_state_authority_pubkey, + close_context_state_signers, + ), + token.confidential_transfer_close_context_state( + &range_proof_pubkey, + &authority, + &context_state_authority_pubkey, + close_context_state_signers, + ), + )?; + + let tx_return = finish_tx(config, &res, false).await?; + Ok(match tx_return { + TransactionReturnData::CliSignature(signature) => { + config.output_format.formatted_string(&signature) + } + TransactionReturnData::CliSignOnlyData(sign_only_data) => { + config.output_format.formatted_string(&sign_only_data) + } + }) } fn format_output(command_output: T, command_name: &CommandName, config: &Config) -> String diff --git a/token/client/src/token.rs b/token/client/src/token.rs index a9fefc21f70..93c97312f66 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -18,6 +18,7 @@ use { program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, + signature::Keypair, signer::{signers::Signers, Signer, SignerError}, system_instruction, transaction::Transaction, @@ -30,6 +31,7 @@ use { }, spl_token_2022::{ extension::{ + confidential_mint_burn::{self, instruction::mint_range_proof}, confidential_transfer::{ self, account_info::{ @@ -40,7 +42,7 @@ use { instruction::{ TransferSplitContextStateAccounts, TransferWithFeeSplitContextStateAccounts, }, - ConfidentialTransferAccount, DecryptableBalance, + ConfidentialTransferAccount, ConfidentialTransferMint, DecryptableBalance, }, confidential_transfer_fee::{ self, account_info::WithheldTokensInfo, ConfidentialTransferFeeAmount, @@ -56,9 +58,10 @@ use { encryption::{ auth_encryption::AeKey, elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey}, + pedersen::PedersenOpening, }, instruction::*, - zk_token_elgamal::pod::ElGamalPubkey as PodElGamalPubkey, + zk_token_elgamal::{self, pod::ElGamalPubkey as PodElGamalPubkey}, zk_token_proof_instruction::{self, ContextStateInfo, ProofInstruction}, zk_token_proof_program, zk_token_proof_state::ProofContextState, @@ -184,6 +187,9 @@ pub enum ExtensionInitializationParams { authority: Option, member_address: Option, }, + ConfidentialMintBurnMint { + authority: Pubkey, + }, } impl ExtensionInitializationParams { /// Get the extension type associated with the init params @@ -203,6 +209,7 @@ impl ExtensionInitializationParams { } Self::GroupPointer { .. } => ExtensionType::GroupPointer, Self::GroupMemberPointer { .. } => ExtensionType::GroupMemberPointer, + Self::ConfidentialMintBurnMint { .. } => ExtensionType::ConfidentialMintBurn, } } /// Generate an appropriate initialization instruction for the given mint @@ -312,6 +319,13 @@ impl ExtensionInitializationParams { authority, member_address, ), + Self::ConfidentialMintBurnMint { authority } => { + confidential_mint_burn::instruction::initialize_mint( + token_program_id, + mint, + authority, + ) + } } } } @@ -2341,6 +2355,7 @@ where ciphertext_validity_proof_data, range_proof_data, source_decrypt_handles, + _, ) = account_info .generate_split_transfer_proof_data( transfer_amount, @@ -4079,4 +4094,167 @@ where )); self.process_ixs(&instructions, signing_keypairs).await } + + /// Mint SPL Tokens into the pending balance of a confidential token account + pub async fn confidential_mint( + &self, + account: &Pubkey, + authority: &Pubkey, + amount: u64, + signing_keypairs: &S, + ) -> TokenResult { + let signing_pubkeys = signing_keypairs.pubkeys(); + let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); + + let account_data = self.get_account_info(account).await?; + let confidential_transfer_account = + account_data.get_extension::()?; + + let mint_to_elgamal_pubkey = &confidential_transfer_account + .elgamal_pubkey + .try_into() + .expect("invalid elgamal pubkey pod"); + let auditor_elgamal_pubkey = + TryInto::>::try_into( + self.get_mint_info() + .await? + .get_extension::()? + .auditor_elgamal_pubkey, + ) + .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData))? + .map(|pk| TryInto::::try_into(pk).unwrap()); + + let range_proof_context_state_account = Keypair::new(); + let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); + self.create_range_proof_context_state_for_mint( + &range_proof_context_pubkey, + &self.payer.pubkey(), + &mint_range_proof(amount, mint_to_elgamal_pubkey, &auditor_elgamal_pubkey)?, + &[&range_proof_context_state_account], + ) + .await?; + + let proof_location = ProofLocation::ContextStateAccount(&range_proof_context_pubkey); + + let res = self + .process_ixs( + &confidential_mint_burn::instruction::confidential_mint( + &self.program_id, + account, + &self.pubkey, + amount, + mint_to_elgamal_pubkey, + auditor_elgamal_pubkey, + authority, + &multisig_signers, + proof_location, + )?, + signing_keypairs, + ) + .await; + + let close_context_state_signers = &[self.payer.clone()]; + let _ = self + .confidential_transfer_close_context_state( + &range_proof_context_pubkey, + &self.payer.pubkey(), + &self.payer.pubkey(), + close_context_state_signers, + ) + .await; + + res + } + + /// Create a range proof context state account for mint + async fn create_range_proof_context_state_for_mint( + &self, + range_proof_pubkey: &Pubkey, + range_proof_authority: &Pubkey, + range_proof_data: &BatchedRangeProofU64Data, + signing_keypairs: &S, + ) -> TokenResult { + let instruction_type = ProofInstruction::VerifyBatchedRangeProofU64; + let space = size_of::>(); + let rent = self + .client + .get_minimum_balance_for_rent_exemption(space) + .await + .map_err(TokenError::Client)?; + let range_proof_context_state_info = ContextStateInfo { + context_state_account: range_proof_pubkey, + context_state_authority: range_proof_authority, + }; + + self.process_ixs( + &[system_instruction::create_account( + &self.payer.pubkey(), + range_proof_context_state_info.context_state_account, + rent, + space as u64, + &zk_token_proof_program::id(), + )], + signing_keypairs, + ) + .await?; + + self.process_ixs( + &[instruction_type + .encode_verify_proof(Some(range_proof_context_state_info), range_proof_data)], + &[self.payer.clone()], + ) + .await + } + + /// Burn SPL Tokens from the available balance of a confidential token + /// account + #[allow(clippy::too_many_arguments)] + pub async fn confidential_burn( + &self, + ata_pubkey: &Pubkey, + authority: &Pubkey, + context_state_accounts: TransferSplitContextStateAccounts<'_>, + amount: u64, + aes_key: &AeKey, + source_decrypt_handles: &SourceDecryptHandles, + signing_keypairs: &S, + pedersen_openings: &(PedersenOpening, PedersenOpening), + ) -> TokenResult { + let signing_pubkeys = signing_keypairs.pubkeys(); + let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); + + let account = self.get_account_info(ata_pubkey).await?; + let confidential_transfer_account = + account.get_extension::()?; + let account_info = TransferAccountInfo::new(confidential_transfer_account); + + let new_decryptable_available_balance = account_info + .new_decryptable_available_balance(amount, aes_key) + .map_err(|_| TokenError::AccountDecryption)?; + let mint_info = self.get_mint_info().await?; + + self.process_ixs( + &confidential_mint_burn::instruction::confidential_burn_with_split_proofs( + &self.program_id, + ata_pubkey, + self.get_address(), + TryInto::>::try_into( + mint_info + .get_extension::()? + .auditor_elgamal_pubkey, + ) + .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData))? + .map(|pk| TryInto::::try_into(pk).unwrap()), + amount, + new_decryptable_available_balance.into(), + context_state_accounts, + source_decrypt_handles, + authority, + &multisig_signers, + pedersen_openings, + )?, + signing_keypairs, + ) + .await + } } diff --git a/token/program-2022/src/error.rs b/token/program-2022/src/error.rs index ff004f0ffb7..b269108dc47 100644 --- a/token/program-2022/src/error.rs +++ b/token/program-2022/src/error.rs @@ -243,6 +243,11 @@ pub enum TokenError { /// Ciphertext arithmetic failed #[error("Ciphertext arithmetic failed")] CiphertextArithmeticFailed, + + //61 + /// Withdraw / Deposit not allowed for confidential-mint-burn + #[error("When the confidential-mint-burn extension is enabled, mints are only allowed into the confidential balance. Likewise conversions confidential token balance to normal balance and vice versa are illegal.")] + IllegalMintBurnConversion, } impl From for ProgramError { fn from(e: TokenError) -> Self { @@ -418,6 +423,9 @@ impl PrintProgramError for TokenError { TokenError::CiphertextArithmeticFailed => { msg!("Ciphertext arithmetic 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/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs new file mode 100644 index 00000000000..10ccb830bdb --- /dev/null +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -0,0 +1,418 @@ +#[cfg(not(target_os = "solana"))] +use crate::{ + error::TokenError, + extension::confidential_transfer::processor::verify_and_split_deposit_amount, + proof::ProofLocation, +}; +#[cfg(not(target_os = "solana"))] +use solana_zk_token_sdk::{ + encryption::pedersen::Pedersen, + instruction::{transfer::TransferAmountCiphertext, BatchedRangeProofU64Data}, + zk_token_proof_instruction::verify_batched_verify_range_proof_u64, +}; + +#[cfg(feature = "serde-traits")] +use serde::{Deserialize, Serialize}; +#[cfg(not(target_os = "solana"))] +use solana_zk_token_sdk::encryption::{elgamal::ElGamalPubkey, pedersen::PedersenOpening}; +use { + crate::extension::confidential_transfer::{ + ciphertext_extraction::SourceDecryptHandles, DecryptableBalance, + }, + bytemuck::{Pod, Zeroable}, + num_enum::{IntoPrimitive, TryFromPrimitive}, + solana_program::pubkey::Pubkey, + solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, +}; +#[cfg(not(target_os = "solana"))] +use { + crate::{ + check_program_account, + extension::confidential_transfer::instruction::TransferSplitContextStateAccounts, + instruction::{encode_instruction, TokenInstruction}, + }, + solana_program::{ + instruction::{AccountMeta, Instruction}, + program_error::ProgramError, + sysvar, + }, +}; + +/// 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, + /// Updates mint-authority for confidential-mint-burn mint. + UpdateMint, + /// Mints confidential tokens to + ConfidentialMint, + /// Removes whitelist designation for specific address + ConfidentialBurn, +} + +/// Data expected by `WhitelistedTransferInstruction::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 { + /// Authority to modify the `WhitelistTransferMint` configuration and to + /// approve new accounts. + pub authority: Pubkey, +} + +/// Data expected by `ConfidentialTransferInstruction::UpdateMint` +#[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 UpdateMintData { + /// Determines if newly configured accounts must be approved by the + /// `authority` before they may be used by the user. + pub new_authority: Pubkey, +} + +/// 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 { + /// low 16 bits of encrypted amount to be minted + pub mint_lo: ElGamalCiphertext, + /// high 48 bits of encrypted amount to be minted + pub mint_hi: ElGamalCiphertext, + /// low 16 bits of encrypted amount to be minted + pub audit_amount_lo: ElGamalCiphertext, + /// high 48 bits of encrypted amount to be minted + pub audit_amount_hi: ElGamalCiphertext, + /// Relative location of the `ProofInstruction::VerifyBatchedRangeProofU64` + /// instruction to the `ConfidentialMint` instruction in the + /// transaction. + pub 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 source decryptable balance if the transfer succeeds + #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] + pub new_decryptable_available_balance: DecryptableBalance, + /// The ElGamal decryption handle pertaining to the low and high bits of the + /// transfer amount. This field is used when the transfer proofs are + /// split and verified as smaller components. + /// + /// NOTE: This field is to be removed in the next Solana upgrade. + pub source_decrypt_handles: SourceDecryptHandles, + /// low 16 bits of encrypted amount to be minted + pub burn_lo: ElGamalCiphertext, + /// high 48 bits of encrypted amount to be minted + pub burn_hi: ElGamalCiphertext, +} + +/// Create a `InitializeMint` instruction +#[cfg(not(target_os = "solana"))] +pub fn initialize_mint( + token_program_id: &Pubkey, + mint: &Pubkey, + authority: Pubkey, +) -> Result { + 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 { authority }, + )) +} + +/// Create a `UpdateMint` instruction +#[cfg(not(target_os = "solana"))] +pub fn update_mint( + token_program_id: &Pubkey, + mint: &Pubkey, + authority: &Pubkey, + multisig_signers: &[&Pubkey], + new_authority: Pubkey, +) -> Result { + 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::UpdateMint, + &UpdateMintData { new_authority }, + )) +} + +/// Create a `ConfidentialMint` instruction +#[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] +pub fn confidential_mint( + token_program_id: &Pubkey, + token_account: &Pubkey, + mint: &Pubkey, + amount: u64, + destination_elgamal_pubkey: &ElGamalPubkey, + auditor_elgamal_pubkey: Option, + authority: &Pubkey, + multisig_signers: &[&Pubkey], + proof_data_location: ProofLocation, +) -> Result, ProgramError> { + check_program_account(token_program_id)?; + let mut accounts = vec![ + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(*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)); + } + + let (amount_lo, amount_hi) = verify_and_split_deposit_amount(amount)?; + let opening = PedersenOpening::new_rand(); + let mint_lo = destination_elgamal_pubkey.encrypt_with(amount_lo, &opening); + let mint_hi = destination_elgamal_pubkey.encrypt_with(amount_hi, &opening); + + let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); + let opening = PedersenOpening::new_rand(); + let audit_amount_hi = auditor_elgamal_pubkey.encrypt_with(amount_hi, &opening); + let audit_amount_lo = auditor_elgamal_pubkey.encrypt_with(amount_lo, &opening); + + let proof_instruction_offset = match proof_data_location { + ProofLocation::InstructionOffset(proof_instruction_offset, _) => { + accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + let mut instrs = vec![encode_instruction( + token_program_id, + accounts, + TokenInstruction::ConfidentialMintBurnExtension, + ConfidentialMintBurnInstruction::ConfidentialMint, + &MintInstructionData { + mint_lo: mint_lo.into(), + mint_hi: mint_hi.into(), + audit_amount_lo: audit_amount_lo.into(), + audit_amount_hi: audit_amount_hi.into(), + proof_instruction_offset, + }, + )]; + + if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = + proof_data_location + { + // This constructor appends the proof instruction right after the + // `ConfidentialMint` instruction. This means that the proof instruction + // offset must be always be 1. + let proof_instruction_offset: i8 = proof_instruction_offset.into(); + if proof_instruction_offset != 1 { + return Err(TokenError::InvalidProofInstructionOffset.into()); + } + instrs.push(verify_batched_verify_range_proof_u64( + None, + proof_data, + )) + }; + + Ok(instrs) +} + +/// Generates range proof for mint instruction +#[cfg(not(target_os = "solana"))] +pub fn mint_range_proof( + amount: u64, + destination_elgamal_pubkey: &ElGamalPubkey, + auditor_elgamal_pubkey: &Option, +) -> Result { + let (amount_lo, amount_hi) = verify_and_split_deposit_amount(amount)?; + let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); + + const MINT_AMOUNT_LO_BIT_LENGTH: usize = 16; + const MINT_AMOUNT_HI_BIT_LENGTH: usize = 32; + const PADDING_BIT_LENGTH: usize = 16; + + // Encrypt the `lo` and `hi` transfer amounts. + let (mint_amount_grouped_ciphertext_lo, transfer_amount_opening_lo) = + TransferAmountCiphertext::new( + amount_lo, + destination_elgamal_pubkey, + &ElGamalPubkey::default(), + &auditor_elgamal_pubkey, + ); + + let (transfer_amount_grouped_ciphertext_hi, transfer_amount_opening_hi) = + TransferAmountCiphertext::new( + amount_hi, + destination_elgamal_pubkey, + &ElGamalPubkey::default(), + &auditor_elgamal_pubkey, + ); + + let (padding_commitment, padding_opening) = Pedersen::new(0_u64); + + Ok(BatchedRangeProofU64Data::new( + vec![ + mint_amount_grouped_ciphertext_lo.get_commitment(), + transfer_amount_grouped_ciphertext_hi.get_commitment(), + &padding_commitment, + ], + vec![amount_lo, amount_hi, 0], + vec![ + MINT_AMOUNT_LO_BIT_LENGTH, + MINT_AMOUNT_HI_BIT_LENGTH, + PADDING_BIT_LENGTH, + ], + vec![ + &transfer_amount_opening_lo, + &transfer_amount_opening_hi, + &padding_opening, + ], + ) + .map_err(|_| TokenError::ProofGeneration)?) +} + +/// Create a `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, + auditor_pubkey: Option, + burn_amount: u64, + new_decryptable_available_balance: DecryptableBalance, + context_accounts: TransferSplitContextStateAccounts, + source_decrypt_handles: &SourceDecryptHandles, + authority: &Pubkey, + multisig_signers: &[&Pubkey], + pedersen_openings: &(PedersenOpening, PedersenOpening), +) -> Result, ProgramError> { + Ok(vec![inner_confidential_burn_with_split_proofs( + token_program_id, + token_account, + mint, + auditor_pubkey, + burn_amount, + new_decryptable_available_balance, + context_accounts, + source_decrypt_handles, + authority, + multisig_signers, + pedersen_openings, + )?]) +} + +/// Create a inner `ConfidentialBurn` instruction +#[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] +pub fn inner_confidential_burn_with_split_proofs( + token_program_id: &Pubkey, + token_account: &Pubkey, + mint: &Pubkey, + auditor_pubkey: Option, + burn_amount: u64, + new_decryptable_available_balance: DecryptableBalance, + context_accounts: TransferSplitContextStateAccounts, + source_decrypt_handles: &SourceDecryptHandles, + authority: &Pubkey, + multisig_signers: &[&Pubkey], + pedersen_openings: &(PedersenOpening, PedersenOpening), +) -> Result { + check_program_account(token_program_id)?; + let mut accounts = vec![ + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(*mint, false), + ]; + + if context_accounts + .close_split_context_state_accounts + .is_some() + { + println!("close split context accounts on execution not implemented for confidential burn"); + return Err(ProgramError::InvalidInstructionData); + } + + accounts.push(AccountMeta::new_readonly( + *context_accounts.equality_proof, + false, + )); + accounts.push(AccountMeta::new_readonly( + *context_accounts.ciphertext_validity_proof, + false, + )); + accounts.push(AccountMeta::new_readonly( + *context_accounts.range_proof, + false, + )); + + 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 (burn_hi, burn_lo) = if let Some(apk) = auditor_pubkey { + let (opening_hi, opening_lo) = pedersen_openings; + let (amount_lo, amount_hi) = verify_and_split_deposit_amount(burn_amount)?; + let burn_hi = apk.encrypt_with(amount_hi, opening_hi); + let burn_lo = apk.encrypt_with(amount_lo, opening_lo); + (burn_hi.into(), burn_lo.into()) + } else { + (ElGamalCiphertext::zeroed(), ElGamalCiphertext::zeroed()) + }; + + Ok(encode_instruction( + token_program_id, + accounts, + TokenInstruction::ConfidentialMintBurnExtension, + ConfidentialMintBurnInstruction::ConfidentialBurn, + &BurnInstructionData { + new_decryptable_available_balance, + source_decrypt_handles: *source_decrypt_handles, + burn_hi, + burn_lo, + }, + )) +} 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 00000000000..32ac159063a --- /dev/null +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -0,0 +1,34 @@ +use { + crate::extension::{Extension, ExtensionType}, + bytemuck::{Pod, Zeroable}, + solana_program::pubkey::Pubkey, +}; + +/// 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 Transfer Extension instructions +pub mod instruction; + +/// Confidential Transfer Extension processor +pub mod processor; + +/// Confidential transfer mint configuration +#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))] +#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] +#[repr(C)] +pub struct ConfidentialMintBurn { + /// Authority to modify the `WhitelistTransferMint` configuration and to + /// approve new accounts. + pub mint_authority: Pubkey, +} + +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 00000000000..eb5c3b63bbe --- /dev/null +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -0,0 +1,304 @@ +#[cfg(feature = "zk-ops")] +use { + self::ciphertext_extraction::SourceDecryptHandles, + self::processor::validate_auditor_ciphertext, + crate::check_zk_token_proof_program_account, + crate::extension::confidential_transfer::processor::process_source_for_transfer, + crate::extension::non_transferable::NonTransferable, + crate::proof::decode_proof_instruction_context, + solana_zk_token_sdk::instruction::BatchedRangeProofContext, + solana_zk_token_sdk::instruction::BatchedRangeProofU128Data, + solana_zk_token_sdk::zk_token_elgamal::ops as syscall, + solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, + solana_zk_token_sdk::zk_token_proof_instruction::ProofInstruction, + solana_zk_token_sdk::{instruction::ProofType, zk_token_proof_state::ProofContextState}, + spl_pod::bytemuck::pod_from_bytes, + std::slice::Iter, +}; +use { + crate::{ + check_program_account, + error::TokenError, + extension::{ + confidential_mint_burn::{ + instruction::{ + BurnInstructionData, ConfidentialMintBurnInstruction, InitializeMintData, + MintInstructionData, UpdateMintData, + }, + ConfidentialMintBurn, + }, + confidential_transfer::{verify_proof::*, *}, + BaseStateWithExtensions, BaseStateWithExtensionsMut, PodStateWithExtensions, + 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, + sysvar::instructions::get_instruction_relative, + }, +}; + +/// Processes an [InitializeMint] instruction. +fn process_initialize_mint(accounts: &[AccountInfo], authority: Pubkey) -> 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::::unpack_uninitialized(mint_data)?; + let mint = mint.init_extension::(true)?; + + mint.mint_authority = authority; + + Ok(()) +} + +/// Processes an [UpdateMint] instruction. +fn process_update_mint( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_authority: Pubkey, +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let mint_info = next_account_info(account_info_iter)?; + let authority_info = next_account_info(account_info_iter)?; + let authority_info_data_len = authority_info.data_len(); + + check_program_account(mint_info.owner)?; + let mint_data = &mut mint_info.data.borrow_mut(); + let mut mint = PodStateWithExtensionsMut::::unpack_uninitialized(mint_data)?; + let mint = mint.get_extension_mut::()?; + + Processor::validate_owner( + program_id, + &mint.mint_authority, + authority_info, + authority_info_data_len, + account_info_iter.as_slice(), + )?; + + mint.mint_authority = new_authority; + + 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)?; + let authority_info = next_account_info(account_info_iter)?; + let authority_info_data_len = authority_info.data_len(); + + check_program_account(mint_info.owner)?; + let mint_data = &mint_info.data.borrow_mut(); + let mint = PodStateWithExtensions::::unpack(mint_data)?; + + if let Err(_e) = mint.get_extension::() { + msg!("confidential-mint-burn extension initialized on mint without confidential transfer extension"); + return Err(TokenError::ExtensionNotFound.into()); + }; + + let Ok(conf_mint_ext) = mint.get_extension::() else { + msg!("attempted to confidentially mint tokens on mint without confidential mint-burn extension"); + return Err(TokenError::ExtensionNotFound.into()); + }; + + Processor::validate_owner( + program_id, + &conf_mint_ext.mint_authority, + authority_info, + authority_info_data_len, + account_info_iter.as_slice(), + )?; + + if mint.get_extension::().is_ok() { + return Err(TokenError::NonTransferable.into()); + } + + check_program_account(token_account_info.owner)?; + let token_account_data = &mut token_account_info.data.borrow_mut(); + let mut token_account = PodStateWithExtensionsMut::::unpack(token_account_data)?; + + if token_account.base.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } + + if token_account.base.mint != *mint_info.key { + return Err(TokenError::MintMismatch.into()); + } + + // Wrapped SOL mint obviously not possible since + // it'd enable creating SOL out of thin air + assert!(!token_account.base.is_native()); + + // TODO: how to verify actual conents of the proof? + // is it even possible without equality / validity proof? + let _proof_context = + verify_mint_proof(account_info_iter, data.proof_instruction_offset as i64)?; + + let confidential_transfer_account = + token_account.get_extension_mut::()?; + confidential_transfer_account.valid_as_destination()?; + + confidential_transfer_account.pending_balance_lo = syscall::add( + &confidential_transfer_account.pending_balance_lo, + &data.mint_lo, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; + confidential_transfer_account.pending_balance_hi = syscall::add( + &confidential_transfer_account.pending_balance_hi, + &data.mint_hi, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; + + confidential_transfer_account.increment_pending_balance_credit_counter()?; + + Ok(()) +} + +/// Verify zero-knowledge proof needed for a [ConfigureAccount] instruction and +/// return the corresponding proof context. +#[cfg(feature = "zk-ops")] +pub fn verify_mint_proof( + account_info_iter: &mut Iter<'_, AccountInfo<'_>>, + proof_instruction_offset: i64, +) -> Result { + if proof_instruction_offset == 0 { + // interpret `account_info` as a context state account + let context_state_account_info = next_account_info(account_info_iter)?; + check_zk_token_proof_program_account(context_state_account_info.owner)?; + let context_state_account_data = context_state_account_info.data.borrow(); + let context_state = pod_from_bytes::>( + &context_state_account_data, + )?; + + if context_state.proof_type != ProofType::BatchedRangeProofU64.into() { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(context_state.proof_context) + } else { + let sysvar_account_info = next_account_info(account_info_iter)?; + let zkp_instruction = + get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; + + Ok(*decode_proof_instruction_context::< + BatchedRangeProofU128Data, + BatchedRangeProofContext, + >( + ProofInstruction::VerifyBatchedRangeProofU64, + &zkp_instruction, + )?) + } +} + +/// Processes a [ConfidentialBurn] instruction. +#[cfg(feature = "zk-ops")] +fn process_confidential_burn( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_decryptable_available_balance: DecryptableBalance, + source_decrypt_handles: &SourceDecryptHandles, + auditor_hi: &ElGamalCiphertext, + auditor_lo: &ElGamalCiphertext, +) -> 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 = &mint_info.data.borrow(); + let mint = PodStateWithExtensions::::unpack(mint_data)?; + + if let Err(_e) = mint.get_extension::() { + msg!("attempted to confidentially burn tokens on mint without confidential mint-burn extension"); + return Err(TokenError::ExtensionNotFound.into()); + }; + + // The zero-knowledge proof certifies that: + // 1. the burn amount is encrypted in the correct form + // 2. the source account has enough balance to burn the amount + let maybe_proof_context = verify_transfer_proof( + account_info_iter, + 0, + true, // proof is split + false, // don't noop but fail if proof is missing + false, // not supported for burn + source_decrypt_handles, + )?; + + let authority_info = next_account_info(account_info_iter)?; + + process_source_for_transfer( + program_id, + token_account_info, + mint_info, + authority_info, + account_info_iter.as_slice(), + maybe_proof_context.as_ref(), + new_decryptable_available_balance, + )?; + + validate_auditor_ciphertext( + mint.get_extension::()?, + maybe_proof_context.as_ref(), + auditor_hi, + auditor_lo, + )?; + + 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::(input)?; + process_initialize_mint(accounts, data.authority) + } + ConfidentialMintBurnInstruction::UpdateMint => { + msg!("ConfidentialMintBurnInstruction::UpdateMint"); + let data = decode_instruction_data::(input)?; + process_update_mint(program_id, accounts, data.new_authority) + } + ConfidentialMintBurnInstruction::ConfidentialMint => { + msg!("ConfidentialMintBurnInstruction::ConfidentialMint"); + let data = decode_instruction_data::(input)?; + process_confidential_mint(program_id, accounts, data) + } + ConfidentialMintBurnInstruction::ConfidentialBurn => { + msg!("ConfidentialMintBurnInstruction::ConfidentialBurn"); + let data = decode_instruction_data::(input)?; + process_confidential_burn( + program_id, + accounts, + data.new_decryptable_available_balance, + &data.source_decrypt_handles, + &data.burn_hi, + &data.burn_lo, + ) + } + } +} 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 bfa88b73b3e..dfee663f2b1 100644 --- a/token/program-2022/src/extension/confidential_transfer/account_info.rs +++ b/token/program-2022/src/extension/confidential_transfer/account_info.rs @@ -12,6 +12,7 @@ use { encryption::{ auth_encryption::{AeCiphertext, AeKey}, elgamal::{ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey}, + pedersen::PedersenOpening, }, instruction::{ transfer::{FeeParameters, TransferData, TransferWithFeeData}, @@ -274,6 +275,7 @@ impl TransferAccountInfo { /// Create a transfer proof data that is split into equality, ciphertext /// validity, and range proofs. + #[allow(clippy::type_complexity)] pub fn generate_split_transfer_proof_data( &self, transfer_amount: u64, @@ -287,6 +289,7 @@ impl TransferAccountInfo { BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, SourceDecryptHandles, + (PedersenOpening, PedersenOpening), ), TokenError, > { @@ -367,7 +370,8 @@ impl TransferAccountInfo { } } -fn combine_balances(balance_lo: u64, balance_hi: u64) -> Option { +/// Combines pending balances low and high bits into singular pending balance +pub fn combine_balances(balance_lo: u64, balance_hi: u64) -> Option { balance_hi .checked_shl(PENDING_BALANCE_LO_BIT_LENGTH)? .checked_add(balance_lo) diff --git a/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs b/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs index 85f9fd0b031..28aad3ee9a3 100644 --- a/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs +++ b/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs @@ -106,6 +106,33 @@ pub(crate) fn transfer_amount_destination_ciphertext( ElGamalCiphertext(destination_ciphertext_bytes) } +/// Extract the transfer amount ciphertext encrypted under the auditor ElGamal +/// public key. +/// +/// A transfer amount ciphertext consists of the following 32-byte components +/// that are serialized in order: +/// 1. The `commitment` component that encodes the transfer amount. +/// 2. The `decryption handle` component with respect to the source public +/// key. +/// 3. The `decryption handle` component with respect to the destination +/// public key. +/// 4. The `decryption handle` component with respect to the auditor public +/// key. +/// +/// An ElGamal ciphertext for the auditor consists of the `commitment` component +/// and the `decryption handle` component with respect to the auditor. +pub fn transfer_amount_auditor_ciphertext( + transfer_amount_ciphertext: &TransferAmountCiphertext, +) -> ElGamalCiphertext { + let transfer_amount_ciphertext_bytes = bytemuck::bytes_of(transfer_amount_ciphertext); + + let mut auditor_ciphertext_bytes = [0u8; 64]; + auditor_ciphertext_bytes[..32].copy_from_slice(&transfer_amount_ciphertext_bytes[..32]); + auditor_ciphertext_bytes[32..].copy_from_slice(&transfer_amount_ciphertext_bytes[96..128]); + + ElGamalCiphertext(auditor_ciphertext_bytes) +} + /// Extract the fee amount ciphertext encrypted under the destination ElGamal /// public key. /// diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index ac73574f5a9..5f2dc698c7e 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -1,8 +1,11 @@ #[cfg(not(target_os = "solana"))] +use crate::{proof::ProofLocation, solana_program::sysvar}; +#[cfg(not(target_os = "solana"))] use solana_zk_token_sdk::encryption::auth_encryption::AeCiphertext; pub use solana_zk_token_sdk::{ zk_token_proof_instruction::*, zk_token_proof_state::ProofContextState, }; + #[cfg(feature = "serde-traits")] use { crate::serialization::aeciphertext_fromstr, @@ -13,7 +16,6 @@ use { check_program_account, extension::confidential_transfer::{ciphertext_extraction::SourceDecryptHandles, *}, instruction::{encode_instruction, TokenInstruction}, - proof::ProofLocation, }, bytemuck::Zeroable, // `Pod` comes from zk_token_proof_instruction num_enum::{IntoPrimitive, TryFromPrimitive}, @@ -21,7 +23,6 @@ use { instruction::{AccountMeta, Instruction}, program_error::ProgramError, pubkey::Pubkey, - sysvar, }, }; @@ -665,6 +666,7 @@ pub fn initialize_mint( } /// Create a `UpdateMint` instruction +#[cfg(not(target_os = "solana"))] pub fn update_mint( token_program_id: &Pubkey, mint: &Pubkey, @@ -697,6 +699,7 @@ pub fn update_mint( /// /// This instruction is suitable for use with a cross-program `invoke` #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn inner_configure_account( token_program_id: &Pubkey, token_account: &Pubkey, @@ -749,6 +752,7 @@ pub fn inner_configure_account( /// Create a `ConfigureAccount` instruction #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn configure_account( token_program_id: &Pubkey, token_account: &Pubkey, @@ -788,6 +792,7 @@ pub fn configure_account( } /// Create an `ApproveAccount` instruction +#[cfg(not(target_os = "solana"))] pub fn approve_account( token_program_id: &Pubkey, account_to_approve: &Pubkey, @@ -816,6 +821,7 @@ pub fn approve_account( /// Create an inner `EmptyAccount` instruction /// /// This instruction is suitable for use with a cross-program `invoke` +#[cfg(not(target_os = "solana"))] pub fn inner_empty_account( token_program_id: &Pubkey, token_account: &Pubkey, @@ -858,6 +864,7 @@ pub fn inner_empty_account( } /// Create a `EmptyAccount` instruction +#[cfg(not(target_os = "solana"))] pub fn empty_account( token_program_id: &Pubkey, token_account: &Pubkey, @@ -892,6 +899,7 @@ pub fn empty_account( /// Create a `Deposit` instruction #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn deposit( token_program_id: &Pubkey, token_account: &Pubkey, @@ -928,6 +936,7 @@ pub fn deposit( /// /// This instruction is suitable for use with a cross-program `invoke` #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn inner_withdraw( token_program_id: &Pubkey, token_account: &Pubkey, @@ -981,6 +990,7 @@ pub fn inner_withdraw( /// Create a `Withdraw` instruction #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn withdraw( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1025,6 +1035,7 @@ pub fn withdraw( /// /// This instruction is suitable for use with a cross-program `invoke` #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn inner_transfer( token_program_id: &Pubkey, source_token_account: &Pubkey, @@ -1076,6 +1087,7 @@ pub fn inner_transfer( /// Create a `Transfer` instruction with regular (no-fee) proof #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn transfer( token_program_id: &Pubkey, source_token_account: &Pubkey, @@ -1118,6 +1130,7 @@ pub fn transfer( /// /// This instruction is suitable for use with a cross-program `invoke` #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn inner_transfer_with_fee( token_program_id: &Pubkey, source_token_account: &Pubkey, @@ -1169,6 +1182,7 @@ pub fn inner_transfer_with_fee( /// Create a `Transfer` instruction with fee proof #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn transfer_with_fee( token_program_id: &Pubkey, source_token_account: &Pubkey, @@ -1210,6 +1224,7 @@ pub fn transfer_with_fee( /// Create a inner `ApplyPendingBalance` instruction /// /// This instruction is suitable for use with a cross-program `invoke` +#[cfg(not(target_os = "solana"))] pub fn inner_apply_pending_balance( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1241,6 +1256,7 @@ pub fn inner_apply_pending_balance( } /// Create a `ApplyPendingBalance` instruction +#[cfg(not(target_os = "solana"))] pub fn apply_pending_balance( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1259,6 +1275,7 @@ pub fn apply_pending_balance( ) // calls check_program_account } +#[cfg(not(target_os = "solana"))] fn enable_or_disable_balance_credits( instruction: ConfidentialTransferInstruction, token_program_id: &Pubkey, @@ -1286,6 +1303,7 @@ fn enable_or_disable_balance_credits( } /// Create a `EnableConfidentialCredits` instruction +#[cfg(not(target_os = "solana"))] pub fn enable_confidential_credits( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1302,6 +1320,7 @@ pub fn enable_confidential_credits( } /// Create a `DisableConfidentialCredits` instruction +#[cfg(not(target_os = "solana"))] pub fn disable_confidential_credits( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1318,6 +1337,7 @@ pub fn disable_confidential_credits( } /// Create a `EnableNonConfidentialCredits` instruction +#[cfg(not(target_os = "solana"))] pub fn enable_non_confidential_credits( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1334,6 +1354,7 @@ pub fn enable_non_confidential_credits( } /// Create a `DisableNonConfidentialCredits` instruction +#[cfg(not(target_os = "solana"))] pub fn disable_non_confidential_credits( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1351,6 +1372,7 @@ pub fn disable_non_confidential_credits( /// Create a `TransferWithSplitProof` instruction without fee #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn transfer_with_split_proofs( token_program_id: &Pubkey, source_token_account: &Pubkey, @@ -1429,6 +1451,7 @@ pub fn transfer_with_split_proofs( /// Create a `TransferWithSplitProof` instruction with fee #[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] pub fn transfer_with_fee_and_split_proofs( token_program_id: &Pubkey, source_token_account: &Pubkey, diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index 21fed2b3682..d009b096e7c 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -3,6 +3,7 @@ use crate::extension::transfer_hook; #[cfg(feature = "zk-ops")] use { + crate::extension::confidential_mint_burn::ConfidentialMintBurn, crate::extension::non_transferable::NonTransferableAccount, solana_zk_token_sdk::zk_token_elgamal::ops as syscall, }; @@ -135,6 +136,7 @@ fn process_configure_account( // `ConfidentialTransferAccount` extension let confidential_transfer_account = token_account.init_extension::(false)?; + confidential_transfer_account.approved = confidential_transfer_mint.auto_approve_new_accounts; confidential_transfer_account.elgamal_pubkey = proof_context.pubkey; confidential_transfer_account.maximum_pending_balance_credit_counter = @@ -269,6 +271,10 @@ fn process_deposit( return Err(TokenError::MintDecimalsMismatch.into()); } + if mint.get_extension::().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::::unpack(token_account_data)?; @@ -369,6 +375,10 @@ fn process_withdraw( return Err(TokenError::MintDecimalsMismatch.into()); } + if mint.get_extension::().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::::unpack(token_account_data)?; @@ -618,9 +628,7 @@ fn process_transfer( )?; if maybe_proof_context.is_none() { - msg!( - "Context state not fully initialized: returning with no op; transfer is NOT yet executed" - ); + msg!("Context state not fully initialized: returning with no op; transfer is NOT yet executed"); } authority_info }; @@ -664,8 +672,10 @@ fn process_transfer( Ok(()) } +/// Processes the changes for the sending party of a confidential transfer +#[allow(clippy::too_many_arguments)] #[cfg(feature = "zk-ops")] -fn process_source_for_transfer( +pub fn process_source_for_transfer( program_id: &Pubkey, source_account_info: &AccountInfo, mint_info: &AccountInfo, @@ -738,6 +748,40 @@ fn process_source_for_transfer( Ok(()) } +/// Validates the auditor transfer amounts from the instruction against those from zk-proofs +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "zk-ops")] +pub fn validate_auditor_ciphertext( + confidential_mint: &ConfidentialTransferMint, + maybe_proof_context: Option<&TransferProofContextInfo>, + auditor_hi: &ElGamalCiphertext, + auditor_lo: &ElGamalCiphertext, +) -> ProgramResult { + if let Some(proof_context) = maybe_proof_context { + if let Some(auditor_pk) = confidential_mint.auditor_elgamal_pubkey.into() { + // Check that the auditor encryption public key is consistent with what was + // actually used to generate the zkp. + if proof_context.transfer_pubkeys.auditor != auditor_pk { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } + + let auditor_transfer_amount_lo = + transfer_amount_auditor_ciphertext(&proof_context.ciphertext_lo); + let auditor_transfer_amount_hi = + transfer_amount_auditor_ciphertext(&proof_context.ciphertext_hi); + + if auditor_hi != &auditor_transfer_amount_hi { + return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); + } + if auditor_lo != &auditor_transfer_amount_lo { + return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); + } + } + } + + Ok(()) +} + #[cfg(feature = "zk-ops")] fn process_destination_for_transfer( destination_account_info: &AccountInfo, diff --git a/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs b/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs index 9d376bb0883..f9ce356c3a8 100644 --- a/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs +++ b/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs @@ -4,6 +4,8 @@ //! The logic in this submodule should belong to the `solana-zk-token-sdk` and //! will be removed with the next upgrade to the Solana program. +use solana_zk_token_sdk::encryption::pedersen::PedersenOpening; + use crate::{ extension::confidential_transfer::{ ciphertext_extraction::{transfer_amount_source_ciphertext, SourceDecryptHandles}, @@ -26,6 +28,7 @@ use crate::{ }; /// The main logic to create the three split proof data for a transfer. +#[allow(clippy::type_complexity)] pub fn transfer_split_proof_data( current_available_balance: &ElGamalCiphertext, current_decryptable_available_balance: &AeCiphertext, @@ -40,6 +43,7 @@ pub fn transfer_split_proof_data( BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, SourceDecryptHandles, + (PedersenOpening, PedersenOpening), ), TokenError, > { @@ -186,5 +190,6 @@ pub fn transfer_split_proof_data( ciphertext_validity_proof_data, range_proof_data, source_decrypt_handles, + (transfer_amount_opening_hi, transfer_amount_opening_lo), )) } diff --git a/token/program-2022/src/extension/mod.rs b/token/program-2022/src/extension/mod.rs index 3fefd993ca0..6f3312a309d 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,10 @@ pub enum ExtensionType { GroupMemberPointer, /// Mint contains token group member configurations TokenGroupMember, + + /// Mint allowing the minting and burn of confidential tokens + ConfidentialMintBurn = u16::MAX - 4269, + /// Test variable-length mint extension #[cfg(test)] VariableLenMintTest = u16::MAX - 2, @@ -1181,6 +1189,7 @@ impl ExtensionType { ExtensionType::TokenGroup => pod_get_packed_len::(), ExtensionType::GroupMemberPointer => pod_get_packed_len::(), ExtensionType::TokenGroupMember => pod_get_packed_len::(), + ExtensionType::ConfidentialMintBurn => pod_get_packed_len::(), #[cfg(test)] ExtensionType::AccountPaddingTest => pod_get_packed_len::(), #[cfg(test)] @@ -1244,6 +1253,7 @@ impl ExtensionType { | ExtensionType::GroupPointer | ExtensionType::TokenGroup | ExtensionType::GroupMemberPointer + | ExtensionType::ConfidentialMintBurn | ExtensionType::TokenGroupMember => AccountType::Mint, ExtensionType::ImmutableOwner | ExtensionType::TransferFeeAmount diff --git a/token/program-2022/src/instruction.rs b/token/program-2022/src/instruction.rs index 9b2cb55109b..3d766aa0230 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 dcf487c966d..a08f8b68a7a 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, ProgramError> { diff --git a/token/program-2022/src/processor.rs b/token/program-2022/src/processor.rs index 469b29f7f6a..d8d230aa0b0 100644 --- a/token/program-2022/src/processor.rs +++ b/token/program-2022/src/processor.rs @@ -5,6 +5,7 @@ use { check_program_account, cmp_pubkeys, error::TokenError, extension::{ + confidential_mint_burn::{self, ConfidentialMintBurn}, confidential_transfer::{self, ConfidentialTransferAccount, ConfidentialTransferMint}, confidential_transfer_fee::{ self, ConfidentialTransferFeeAmount, ConfidentialTransferFeeConfig, @@ -319,6 +320,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 !cmp_pubkeys(&source_account.base.mint, mint_info.key) { @@ -366,9 +368,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 { @@ -953,6 +955,10 @@ impl Processor { return Err(TokenError::NonTransferableNeedsImmutableOwnership.into()); } + if mint.get_extension::().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()); @@ -1787,6 +1793,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) From 32d528ed362dbec9671fd48534997805d90b5907 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Mon, 1 Jul 2024 18:01:51 -0300 Subject: [PATCH 02/34] add ciphertext validity proof to confidential mint --- token/cli/src/command.rs | 71 +++++++- token/client/src/token.rs | 170 +++++++++--------- .../ciphertext_extraction.rs | 153 ++++++++++++++++ .../confidential_mint_burn/instruction.rs | 142 ++++++--------- .../extension/confidential_mint_burn/mod.rs | 13 +- .../confidential_mint_burn/processor.rs | 79 +++----- .../proof_generation.rs | 87 +++++++++ .../confidential_mint_burn/verify_proof.rs | 88 +++++++++ .../ciphertext_extraction.rs | 4 +- .../confidential_transfer/verify_proof.rs | 8 +- 10 files changed, 578 insertions(+), 237 deletions(-) create mode 100644 token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs create mode 100644 token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs create mode 100644 token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 2bbd032e0f8..5beef827652 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -38,6 +38,7 @@ use { spl_token_2022::{ error::TokenError, extension::{ + confidential_mint_burn::proof_generation::generate_mint_proofs, confidential_transfer::{ account_info::{ combine_balances, ApplyPendingBalanceAccountInfo, TransferAccountInfo, @@ -60,6 +61,7 @@ use { transfer_hook::TransferHook, BaseStateWithExtensions, ExtensionType, StateWithExtensionsOwned, }, + proof::ProofLocation, solana_zk_token_sdk::{ encryption::{ auth_encryption::AeKey, @@ -3387,9 +3389,72 @@ async fn command_deposit_withdraw_mint_confidential_tokens( .await? } ConfidentialInstructionType::Mint => { - token - .confidential_mint(&token_account_address, &owner, amount, &bulk_signers) - .await? + let payer = config.fee_payer()?; + let range_proof_context_state_account = Keypair::new(); + let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); + let ciphertext_validity_proof_context_state_account = Keypair::new(); + let ciphertext_validity_proof_context_pubkey = + ciphertext_validity_proof_context_state_account.pubkey(); + + let mint_to_elgamal_pubkey = + token.account_elgamal_pubkey(&token_account_address).await?; + let auditor_elgamal_pubkey = token.auditor_elgamal().await?; + + let (range_proof, ciphertext_validity_proof, pedersen_openings) = + generate_mint_proofs(amount, &mint_to_elgamal_pubkey, &auditor_elgamal_pubkey)?; + + let context_state_auth = payer.pubkey(); + let _ = try_join!( + token.create_batched_u64_range_proof_context_state( + &range_proof_context_pubkey, + &context_state_auth, + &range_proof, + &range_proof_context_state_account, + ), + token.create_batched_grouped_2_handles_ciphertext_validity_proof_context_state( + &ciphertext_validity_proof_context_pubkey, + &context_state_auth, + &ciphertext_validity_proof, + &ciphertext_validity_proof_context_state_account, + ), + )?; + + let range_proof_location = + ProofLocation::ContextStateAccount(&range_proof_context_pubkey); + let ciphertext_validity_proof_location = + ProofLocation::ContextStateAccount(&ciphertext_validity_proof_context_pubkey); + + let res = token + .confidential_mint( + &token_account_address, + &owner, + amount, + auditor_elgamal_pubkey, + range_proof_location, + ciphertext_validity_proof_location, + &pedersen_openings, + &bulk_signers, + ) + .await?; + + let close_context_auth = payer.pubkey(); + let close_context_state_signers = &[payer]; + let _ = try_join!( + token.confidential_transfer_close_context_state( + &range_proof_context_pubkey, + &close_context_auth, + &close_context_auth, + close_context_state_signers, + ), + token.confidential_transfer_close_context_state( + &ciphertext_validity_proof_context_pubkey, + &close_context_auth, + &close_context_auth, + close_context_state_signers, + ), + )?; + + res } }; diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 93c97312f66..5ba728dc745 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -18,7 +18,6 @@ use { program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, - signature::Keypair, signer::{signers::Signers, Signer, SignerError}, system_instruction, transaction::Transaction, @@ -31,7 +30,7 @@ use { }, spl_token_2022::{ extension::{ - confidential_mint_burn::{self, instruction::mint_range_proof}, + confidential_mint_burn::{self}, confidential_transfer::{ self, account_info::{ @@ -2469,36 +2468,11 @@ where ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, ciphertext_validity_proof_signer: &S, ) -> TokenResult { - // create ciphertext validity proof context state - let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity; - let space = - size_of::>(); - let rent = self - .client - .get_minimum_balance_for_rent_exemption(space) - .await - .map_err(TokenError::Client)?; - - let ciphertext_validity_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.ciphertext_validity_proof, - context_state_authority: context_state_accounts.authority, - }; - - self.process_ixs( - &[ - system_instruction::create_account( - &self.payer.pubkey(), - context_state_accounts.ciphertext_validity_proof, - rent, - space as u64, - &zk_token_proof_program::id(), - ), - instruction_type.encode_verify_proof( - Some(ciphertext_validity_proof_context_state_info), - ciphertext_validity_proof_data, - ), - ], - &[ciphertext_validity_proof_signer], + self.create_batched_grouped_2_handles_ciphertext_validity_proof_context_state( + context_state_accounts.ciphertext_validity_proof, + context_state_accounts.authority, + ciphertext_validity_proof_data, + ciphertext_validity_proof_signer, ) .await } @@ -4095,26 +4069,8 @@ where self.process_ixs(&instructions, signing_keypairs).await } - /// Mint SPL Tokens into the pending balance of a confidential token account - pub async fn confidential_mint( - &self, - account: &Pubkey, - authority: &Pubkey, - amount: u64, - signing_keypairs: &S, - ) -> TokenResult { - let signing_pubkeys = signing_keypairs.pubkeys(); - let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); - - let account_data = self.get_account_info(account).await?; - let confidential_transfer_account = - account_data.get_extension::()?; - - let mint_to_elgamal_pubkey = &confidential_transfer_account - .elgamal_pubkey - .try_into() - .expect("invalid elgamal pubkey pod"); - let auditor_elgamal_pubkey = + pub async fn auditor_elgamal(&self) -> TokenResult> { + Ok( TryInto::>::try_into( self.get_mint_info() .await? @@ -4122,57 +4078,64 @@ where .auditor_elgamal_pubkey, ) .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData))? - .map(|pk| TryInto::::try_into(pk).unwrap()); - - let range_proof_context_state_account = Keypair::new(); - let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); - self.create_range_proof_context_state_for_mint( - &range_proof_context_pubkey, - &self.payer.pubkey(), - &mint_range_proof(amount, mint_to_elgamal_pubkey, &auditor_elgamal_pubkey)?, - &[&range_proof_context_state_account], + .map(|pk| TryInto::::try_into(pk).unwrap()), ) - .await?; + } - let proof_location = ProofLocation::ContextStateAccount(&range_proof_context_pubkey); + pub async fn account_elgamal_pubkey(&self, account: &Pubkey) -> TokenResult { + TryInto::::try_into( + self.get_account_info(account) + .await? + .get_extension::()? + .elgamal_pubkey, + ) + .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData)) + } - let res = self + /// Mint SPL Tokens into the pending balance of a confidential token account + #[allow(clippy::too_many_arguments)] + pub async fn confidential_mint( + &self, + account: &Pubkey, + authority: &Pubkey, + amount: u64, + auditor_elgamal_pubkey: Option, + range_proof_location: ProofLocation<'_, BatchedRangeProofU64Data>, + ciphertext_validity_proof_location: ProofLocation< + '_, + BatchedGroupedCiphertext2HandlesValidityProofData, + >, + pedersen_openings: &(PedersenOpening, PedersenOpening), + signing_keypairs: &S, + ) -> TokenResult { + let signing_pubkeys = signing_keypairs.pubkeys(); + let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); + self .process_ixs( &confidential_mint_burn::instruction::confidential_mint( &self.program_id, account, &self.pubkey, amount, - mint_to_elgamal_pubkey, auditor_elgamal_pubkey, authority, &multisig_signers, - proof_location, + range_proof_location, + ciphertext_validity_proof_location, + pedersen_openings, )?, signing_keypairs, ) - .await; - - let close_context_state_signers = &[self.payer.clone()]; - let _ = self - .confidential_transfer_close_context_state( - &range_proof_context_pubkey, - &self.payer.pubkey(), - &self.payer.pubkey(), - close_context_state_signers, - ) - .await; - - res + .await } /// Create a range proof context state account for mint - async fn create_range_proof_context_state_for_mint( + pub async fn create_batched_u64_range_proof_context_state( &self, range_proof_pubkey: &Pubkey, range_proof_authority: &Pubkey, range_proof_data: &BatchedRangeProofU64Data, - signing_keypairs: &S, + range_proof_signer: &S, ) -> TokenResult { let instruction_type = ProofInstruction::VerifyBatchedRangeProofU64; let space = size_of::>(); @@ -4194,7 +4157,7 @@ where space as u64, &zk_token_proof_program::id(), )], - signing_keypairs, + &[range_proof_signer], ) .await?; @@ -4206,6 +4169,49 @@ where .await } + /// Create a ciphertext validity proof context state account for mint + pub async fn create_batched_grouped_2_handles_ciphertext_validity_proof_context_state< + S: Signer, + >( + &self, + proof_pubkey: &Pubkey, + proof_authority: &Pubkey, + ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, + ciphertext_validity_proof_signer: &S, + ) -> TokenResult { + let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity; + let space = + size_of::>(); + let rent = self + .client + .get_minimum_balance_for_rent_exemption(space) + .await + .map_err(TokenError::Client)?; + + let ciphertext_validity_proof_context_state_info = ContextStateInfo { + context_state_account: proof_pubkey, + context_state_authority: proof_authority, + }; + + self.process_ixs( + &[ + system_instruction::create_account( + &self.payer.pubkey(), + proof_pubkey, + rent, + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof( + Some(ciphertext_validity_proof_context_state_info), + ciphertext_validity_proof_data, + ), + ], + &[ciphertext_validity_proof_signer], + ) + .await + } + /// Burn SPL Tokens from the available balance of a confidential token /// account #[allow(clippy::too_many_arguments)] diff --git a/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs b/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs new file mode 100644 index 00000000000..3b5dbe20943 --- /dev/null +++ b/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs @@ -0,0 +1,153 @@ +#[cfg(feature = "zk-ops")] +use bytemuck::{Pod, Zeroable}; +use solana_zk_token_sdk::zk_token_elgamal::pod::{ElGamalCiphertext, ElGamalPubkey}; + +#[cfg(feature = "zk-ops")] +use crate::{ + extension::confidential_transfer::ciphertext_extraction::extract_commitment_from_grouped_ciphertext, + solana_program::program_error::ProgramError, + solana_zk_token_sdk::{ + instruction::{ + BatchedGroupedCiphertext2HandlesValidityProofContext, BatchedRangeProofContext, + }, + zk_token_elgamal::pod::GroupedElGamalCiphertext2Handles, + }, +}; + +/// Wrapper for `GroupedElGamalCiphertext2Handles` when used during minting +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct MintAmountCiphertext(pub GroupedElGamalCiphertext2Handles); + +/// The proof context information needed to process a [Transfer] instruction. +#[cfg(feature = "zk-ops")] +pub struct MintProofContextInfo { + /// destination elgamal pubkey used in proof generation + pub destination_pubkey: ElGamalPubkey, + /// auditor elgamal pubkey used in proof generation + pub auditor_pubkey: ElGamalPubkey, + /// Ciphertext containing the low 16 bits of the transafer amount + pub ciphertext_lo: MintAmountCiphertext, + /// Ciphertext containing the high 32 bits of the transafer amount + pub ciphertext_hi: MintAmountCiphertext, +} + +#[cfg(feature = "zk-ops")] +impl MintProofContextInfo { + /// Create a transfer proof context information needed to process a + /// [Transfer] instruction from split proof contexts after verifying + /// their consistency. + pub fn verify_and_extract( + ciphertext_validity_proof_context: &BatchedGroupedCiphertext2HandlesValidityProofContext, + range_proof_context: &BatchedRangeProofContext, + ) -> Result { + // The ciphertext validity proof context consists of the destination ElGamal + // public key, auditor ElGamal public key, and the transfer amount + // ciphertexts. All of these fields should be returned as part of + // `MintProofContextInfo`. In addition, the commitments pertaining + // to the mint amount ciphertexts should be checked with range proof for + // consistency. + let BatchedGroupedCiphertext2HandlesValidityProofContext { + destination_pubkey, + auditor_pubkey, + grouped_ciphertext_lo: mint_amount_ciphertext_lo, + grouped_ciphertext_hi: mint_amount_ciphertext_hi, + } = ciphertext_validity_proof_context; + + // The range proof context consists of the Pedersen commitments and bit-lengths + // for which the range proof is proved. The commitments must consist of + // three commitments pertaining to the new source available balance, the + // low bits of the transfer amount, and high bits of the transfer + // amount. These commitments must be checked for bit lengths `64`, `16`, + // and `32`. + let BatchedRangeProofContext { + commitments: range_proof_commitments, + bit_lengths: range_proof_bit_lengths, + } = range_proof_context; + + // check that the range proof was created for the correct set of Pedersen + // commitments + let mint_amount_commitment_lo = + extract_commitment_from_grouped_ciphertext(mint_amount_ciphertext_lo); + let mint_amount_commitment_hi = + extract_commitment_from_grouped_ciphertext(mint_amount_ciphertext_hi); + + let expected_commitments = [mint_amount_commitment_lo, mint_amount_commitment_hi]; + + if !range_proof_commitments + .iter() + .zip(expected_commitments.iter()) + .all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment) + { + return Err(ProgramError::InvalidInstructionData); + } + + // check that the range proof was created for the correct number of bits + const MINT_AMOUNT_LO_BIT_LENGTH: u8 = 16; + const MINT_AMOUNT_HI_BIT_LENGTH: u8 = 32; + const PADDING_BIT_LENGTH: u8 = 16; + let expected_bit_lengths = [ + MINT_AMOUNT_LO_BIT_LENGTH, + MINT_AMOUNT_HI_BIT_LENGTH, + PADDING_BIT_LENGTH, + ] + .iter(); + + if !range_proof_bit_lengths + .iter() + .zip(expected_bit_lengths) + .all(|(proof_len, expected_len)| proof_len == expected_len) + { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(Self { + destination_pubkey: *destination_pubkey, + auditor_pubkey: *auditor_pubkey, + ciphertext_lo: MintAmountCiphertext(*mint_amount_ciphertext_lo), + ciphertext_hi: MintAmountCiphertext(*mint_amount_ciphertext_hi), + }) + } +} + +/// Extract the mint amount ciphertext encrypted under the auditor ElGamal +/// public key. +/// +/// A mint amount ciphertext consists of the following 32-byte components +/// that are serialized in order: +/// 1. The `commitment` component that encodes the mint amount. +/// key. +/// 2. The `decryption handle` component with respect to the destination +/// public key. +/// 3. The `decryption handle` component with respect to the auditor public +/// key. +/// +/// An ElGamal ciphertext for the auditor consists of the `commitment` component +/// and the `decryption handle` component with respect to the auditor. +pub fn mint_amount_auditor_ciphertext( + transfer_amount_ciphertext: &MintAmountCiphertext, +) -> ElGamalCiphertext { + let transfer_amount_ciphertext_bytes = bytemuck::bytes_of(transfer_amount_ciphertext); + + let mut auditor_ciphertext_bytes = [0u8; 64]; + auditor_ciphertext_bytes[..32].copy_from_slice(&transfer_amount_ciphertext_bytes[..32]); + auditor_ciphertext_bytes[32..].copy_from_slice(&transfer_amount_ciphertext_bytes[64..96]); + + ElGamalCiphertext(auditor_ciphertext_bytes) +} + +/// Extract the mint amount ciphertext encrypted under the destination ElGamal +/// public key. +/// +/// Structure see `mint_amount_auditor_ciphertext` +pub fn mint_amount_destination_ciphertext( + transfer_amount_ciphertext: &MintAmountCiphertext, +) -> ElGamalCiphertext { + let transfer_amount_ciphertext_bytes = bytemuck::bytes_of(transfer_amount_ciphertext); + + let mut destination_ciphertext_bytes = [0u8; 64]; + destination_ciphertext_bytes[..32].copy_from_slice(&transfer_amount_ciphertext_bytes[..32]); + destination_ciphertext_bytes[32..].copy_from_slice(&transfer_amount_ciphertext_bytes[32..64]); + + ElGamalCiphertext(destination_ciphertext_bytes) +} diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 10ccb830bdb..c4f861e5d7e 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -5,16 +5,17 @@ use crate::{ proof::ProofLocation, }; #[cfg(not(target_os = "solana"))] -use solana_zk_token_sdk::{ - encryption::pedersen::Pedersen, - instruction::{transfer::TransferAmountCiphertext, BatchedRangeProofU64Data}, - zk_token_proof_instruction::verify_batched_verify_range_proof_u64, +use solana_zk_token_sdk::instruction::{ + BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU64Data, }; #[cfg(feature = "serde-traits")] use serde::{Deserialize, Serialize}; #[cfg(not(target_os = "solana"))] -use solana_zk_token_sdk::encryption::{elgamal::ElGamalPubkey, pedersen::PedersenOpening}; +use solana_zk_token_sdk::{ + encryption::{elgamal::ElGamalPubkey, pedersen::PedersenOpening}, + zk_token_proof_instruction::{verify_batched_verify_range_proof_u64, ProofInstruction}, +}; use { crate::extension::confidential_transfer::{ ciphertext_extraction::SourceDecryptHandles, DecryptableBalance, @@ -97,17 +98,16 @@ pub struct UpdateMintData { #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] #[repr(C)] pub struct MintInstructionData { - /// low 16 bits of encrypted amount to be minted - pub mint_lo: ElGamalCiphertext, - /// high 48 bits of encrypted amount to be minted - pub mint_hi: ElGamalCiphertext, - /// low 16 bits of encrypted amount to be minted + /// low 16 bits of encrypted amount to be minted, exposes mint amounts + /// to the auditor through the data received via `get_transaction` pub audit_amount_lo: ElGamalCiphertext, - /// high 48 bits of encrypted amount to be minted + /// high 48 bits of encrypted amount to be minted, exposes mint amounts + /// to the auditor through the data received via `get_transaction` pub audit_amount_hi: ElGamalCiphertext, /// Relative location of the `ProofInstruction::VerifyBatchedRangeProofU64` /// instruction to the `ConfidentialMint` instruction in the - /// transaction. + /// transaction. The `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity` + /// has to always be at the instruction directly after the range proof one. pub proof_instruction_offset: i8, } @@ -185,11 +185,15 @@ pub fn confidential_mint( token_account: &Pubkey, mint: &Pubkey, amount: u64, - destination_elgamal_pubkey: &ElGamalPubkey, auditor_elgamal_pubkey: Option, authority: &Pubkey, multisig_signers: &[&Pubkey], - proof_data_location: ProofLocation, + range_proof_location: ProofLocation<'_, BatchedRangeProofU64Data>, + ciphertext_validity_proof_location: ProofLocation< + '_, + BatchedGroupedCiphertext2HandlesValidityProofData, + >, + pedersen_openings: &(PedersenOpening, PedersenOpening), ) -> Result, ProgramError> { check_program_account(token_program_id)?; let mut accounts = vec![ @@ -203,16 +207,11 @@ pub fn confidential_mint( } let (amount_lo, amount_hi) = verify_and_split_deposit_amount(amount)?; - let opening = PedersenOpening::new_rand(); - let mint_lo = destination_elgamal_pubkey.encrypt_with(amount_lo, &opening); - let mint_hi = destination_elgamal_pubkey.encrypt_with(amount_hi, &opening); - let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); - let opening = PedersenOpening::new_rand(); - let audit_amount_hi = auditor_elgamal_pubkey.encrypt_with(amount_hi, &opening); - let audit_amount_lo = auditor_elgamal_pubkey.encrypt_with(amount_lo, &opening); + let audit_amount_lo = auditor_elgamal_pubkey.encrypt_with(amount_lo, &pedersen_openings.0); + let audit_amount_hi = auditor_elgamal_pubkey.encrypt_with(amount_hi, &pedersen_openings.1); - let proof_instruction_offset = match proof_data_location { + let proof_instruction_offset = match range_proof_location { ProofLocation::InstructionOffset(proof_instruction_offset, _) => { accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); proof_instruction_offset.into() @@ -222,6 +221,14 @@ pub fn confidential_mint( 0 } }; + match ciphertext_validity_proof_location { + ProofLocation::InstructionOffset(_, _) => { + // already pushed instruction introspection sysvar previously + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + } + } let mut instrs = vec![encode_instruction( token_program_id, @@ -229,87 +236,42 @@ pub fn confidential_mint( TokenInstruction::ConfidentialMintBurnExtension, ConfidentialMintBurnInstruction::ConfidentialMint, &MintInstructionData { - mint_lo: mint_lo.into(), - mint_hi: mint_hi.into(), audit_amount_lo: audit_amount_lo.into(), audit_amount_hi: audit_amount_hi.into(), proof_instruction_offset, }, )]; - if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = - proof_data_location + if let ProofLocation::InstructionOffset(proof_instruction_offset, range_proof_data) = + range_proof_location { - // This constructor appends the proof instruction right after the - // `ConfidentialMint` instruction. This means that the proof instruction - // offset must be always be 1. - let proof_instruction_offset: i8 = proof_instruction_offset.into(); - if proof_instruction_offset != 1 { - return Err(TokenError::InvalidProofInstructionOffset.into()); + if let ProofLocation::InstructionOffset(_, ciphertext_validity_proof_data) = + ciphertext_validity_proof_location + { + // This constructor appends the proof instruction right after the + // `ConfidentialMint` instruction. This means that the proof instruction + // offset must be always be 1. + let proof_instruction_offset: i8 = proof_instruction_offset.into(); + if proof_instruction_offset != 1 { + return Err(TokenError::InvalidProofInstructionOffset.into()); + } + instrs.push(verify_batched_verify_range_proof_u64( + None, + range_proof_data, + )); + instrs.push( + ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity + .encode_verify_proof(None, ciphertext_validity_proof_data), + ); + } else { + // both proofs have to either be context state or instruction offset + return Err(ProgramError::InvalidArgument); } - instrs.push(verify_batched_verify_range_proof_u64( - None, - proof_data, - )) }; Ok(instrs) } -/// Generates range proof for mint instruction -#[cfg(not(target_os = "solana"))] -pub fn mint_range_proof( - amount: u64, - destination_elgamal_pubkey: &ElGamalPubkey, - auditor_elgamal_pubkey: &Option, -) -> Result { - let (amount_lo, amount_hi) = verify_and_split_deposit_amount(amount)?; - let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); - - const MINT_AMOUNT_LO_BIT_LENGTH: usize = 16; - const MINT_AMOUNT_HI_BIT_LENGTH: usize = 32; - const PADDING_BIT_LENGTH: usize = 16; - - // Encrypt the `lo` and `hi` transfer amounts. - let (mint_amount_grouped_ciphertext_lo, transfer_amount_opening_lo) = - TransferAmountCiphertext::new( - amount_lo, - destination_elgamal_pubkey, - &ElGamalPubkey::default(), - &auditor_elgamal_pubkey, - ); - - let (transfer_amount_grouped_ciphertext_hi, transfer_amount_opening_hi) = - TransferAmountCiphertext::new( - amount_hi, - destination_elgamal_pubkey, - &ElGamalPubkey::default(), - &auditor_elgamal_pubkey, - ); - - let (padding_commitment, padding_opening) = Pedersen::new(0_u64); - - Ok(BatchedRangeProofU64Data::new( - vec![ - mint_amount_grouped_ciphertext_lo.get_commitment(), - transfer_amount_grouped_ciphertext_hi.get_commitment(), - &padding_commitment, - ], - vec![amount_lo, amount_hi, 0], - vec![ - MINT_AMOUNT_LO_BIT_LENGTH, - MINT_AMOUNT_HI_BIT_LENGTH, - PADDING_BIT_LENGTH, - ], - vec![ - &transfer_amount_opening_lo, - &transfer_amount_opening_hi, - &padding_opening, - ], - ) - .map_err(|_| TokenError::ProofGeneration)?) -} - /// Create a `ConfidentialBurn` instruction #[allow(clippy::too_many_arguments)] #[cfg(not(target_os = "solana"))] diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index 32ac159063a..aee6570534e 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -12,12 +12,21 @@ pub const MAXIMUM_DEPOSIT_TRANSFER_AMOUNT: u64 = (u16::MAX as u64) + (1 << 16) * /// Bit length of the low bits of pending balance plaintext pub const PENDING_BALANCE_LO_BIT_LENGTH: u32 = 16; -/// Confidential Transfer Extension instructions +/// Confidential Mint-Burn Extension instructions pub mod instruction; -/// Confidential Transfer Extension processor +/// Confidential Mint-Burn Extension processor pub mod processor; +/// Confidential Mint-Burn proof generation +pub mod proof_generation; + +/// Confidential Mint-Burn proof verification +pub mod verify_proof; + +/// Confidential Mint-Burn proof verification +pub mod ciphertext_extraction; + /// Confidential transfer mint configuration #[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))] diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index eb5c3b63bbe..5b631a225dc 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -2,18 +2,10 @@ use { self::ciphertext_extraction::SourceDecryptHandles, self::processor::validate_auditor_ciphertext, - crate::check_zk_token_proof_program_account, crate::extension::confidential_transfer::processor::process_source_for_transfer, crate::extension::non_transferable::NonTransferable, - crate::proof::decode_proof_instruction_context, - solana_zk_token_sdk::instruction::BatchedRangeProofContext, - solana_zk_token_sdk::instruction::BatchedRangeProofU128Data, solana_zk_token_sdk::zk_token_elgamal::ops as syscall, solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, - solana_zk_token_sdk::zk_token_proof_instruction::ProofInstruction, - solana_zk_token_sdk::{instruction::ProofType, zk_token_proof_state::ProofContextState}, - spl_pod::bytemuck::pod_from_bytes, - std::slice::Iter, }; use { crate::{ @@ -21,10 +13,14 @@ use { error::TokenError, extension::{ confidential_mint_burn::{ + ciphertext_extraction::{ + mint_amount_auditor_ciphertext, mint_amount_destination_ciphertext, + }, instruction::{ BurnInstructionData, ConfidentialMintBurnInstruction, InitializeMintData, MintInstructionData, UpdateMintData, }, + verify_proof::verify_mint_proof, ConfidentialMintBurn, }, confidential_transfer::{verify_proof::*, *}, @@ -41,7 +37,6 @@ use { msg, program_error::ProgramError, pubkey::Pubkey, - sysvar::instructions::get_instruction_relative, }, }; @@ -107,7 +102,7 @@ fn process_confidential_mint( let mint_data = &mint_info.data.borrow_mut(); let mint = PodStateWithExtensions::::unpack(mint_data)?; - if let Err(_e) = mint.get_extension::() { + let Ok(conf_transf_ext) = mint.get_extension::() else { msg!("confidential-mint-burn extension initialized on mint without confidential transfer extension"); return Err(TokenError::ExtensionNotFound.into()); }; @@ -145,23 +140,39 @@ fn process_confidential_mint( // it'd enable creating SOL out of thin air assert!(!token_account.base.is_native()); - // TODO: how to verify actual conents of the proof? - // is it even possible without equality / validity proof? - let _proof_context = - verify_mint_proof(account_info_iter, data.proof_instruction_offset as i64)?; + let proof_context = verify_mint_proof(account_info_iter, data.proof_instruction_offset as i64)?; let confidential_transfer_account = token_account.get_extension_mut::()?; confidential_transfer_account.valid_as_destination()?; + if proof_context.destination_pubkey != confidential_transfer_account.elgamal_pubkey { + return Err(ProgramError::InvalidInstructionData); + } + + if !conf_transf_ext + .auditor_elgamal_pubkey + .equals(&proof_context.auditor_pubkey) + { + return Err(ProgramError::InvalidInstructionData); + } + + if data.audit_amount_lo != mint_amount_auditor_ciphertext(&proof_context.ciphertext_lo) { + return Err(ProgramError::InvalidInstructionData); + } + + if data.audit_amount_hi != mint_amount_auditor_ciphertext(&proof_context.ciphertext_hi) { + return Err(ProgramError::InvalidInstructionData); + } + confidential_transfer_account.pending_balance_lo = syscall::add( &confidential_transfer_account.pending_balance_lo, - &data.mint_lo, + &mint_amount_destination_ciphertext(&proof_context.ciphertext_lo), ) .ok_or(TokenError::CiphertextArithmeticFailed)?; confidential_transfer_account.pending_balance_hi = syscall::add( &confidential_transfer_account.pending_balance_hi, - &data.mint_hi, + &mint_amount_destination_ciphertext(&proof_context.ciphertext_hi), ) .ok_or(TokenError::CiphertextArithmeticFailed)?; @@ -170,42 +181,6 @@ fn process_confidential_mint( Ok(()) } -/// Verify zero-knowledge proof needed for a [ConfigureAccount] instruction and -/// return the corresponding proof context. -#[cfg(feature = "zk-ops")] -pub fn verify_mint_proof( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, - proof_instruction_offset: i64, -) -> Result { - if proof_instruction_offset == 0 { - // interpret `account_info` as a context state account - let context_state_account_info = next_account_info(account_info_iter)?; - check_zk_token_proof_program_account(context_state_account_info.owner)?; - let context_state_account_data = context_state_account_info.data.borrow(); - let context_state = pod_from_bytes::>( - &context_state_account_data, - )?; - - if context_state.proof_type != ProofType::BatchedRangeProofU64.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(context_state.proof_context) - } else { - let sysvar_account_info = next_account_info(account_info_iter)?; - let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - - Ok(*decode_proof_instruction_context::< - BatchedRangeProofU128Data, - BatchedRangeProofContext, - >( - ProofInstruction::VerifyBatchedRangeProofU64, - &zkp_instruction, - )?) - } -} - /// Processes a [ConfidentialBurn] instruction. #[cfg(feature = "zk-ops")] fn process_confidential_burn( diff --git a/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs b/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs new file mode 100644 index 00000000000..421edf073d9 --- /dev/null +++ b/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs @@ -0,0 +1,87 @@ +#[cfg(not(target_os = "solana"))] +use crate::{ + error::TokenError, extension::confidential_transfer::processor::verify_and_split_deposit_amount, +}; +#[cfg(not(target_os = "solana"))] +use solana_zk_token_sdk::{ + encryption::{grouped_elgamal::GroupedElGamal, pedersen::Pedersen}, + instruction::{BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU64Data}, +}; + +#[cfg(not(target_os = "solana"))] +use solana_program::program_error::ProgramError; +#[cfg(not(target_os = "solana"))] +use solana_zk_token_sdk::encryption::{elgamal::ElGamalPubkey, pedersen::PedersenOpening}; + +/// Generates range proof for mint instruction +#[cfg(not(target_os = "solana"))] +pub fn generate_mint_proofs( + amount: u64, + destination_elgamal_pubkey: &ElGamalPubkey, + auditor_elgamal_pubkey: &Option, +) -> Result< + ( + BatchedRangeProofU64Data, + BatchedGroupedCiphertext2HandlesValidityProofData, + (PedersenOpening, PedersenOpening), + ), + ProgramError, +> { + let (amount_lo, amount_hi) = verify_and_split_deposit_amount(amount)?; + let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); + + const MINT_AMOUNT_LO_BIT_LENGTH: usize = 16; + const MINT_AMOUNT_HI_BIT_LENGTH: usize = 32; + const PADDING_BIT_LENGTH: usize = 16; + + // Encrypt the `lo` and `hi` transfer amounts. + let mint_amount_opening_lo = PedersenOpening::new_rand(); + let mint_amount_grouped_ciphertext_lo = GroupedElGamal::<2>::encrypt_with( + [destination_elgamal_pubkey, &auditor_elgamal_pubkey], + amount_lo, + &mint_amount_opening_lo, + ); + + let mint_amount_opening_hi = PedersenOpening::new_rand(); + let mint_amount_grouped_ciphertext_hi = GroupedElGamal::<2>::encrypt_with( + [destination_elgamal_pubkey, &auditor_elgamal_pubkey], + amount_hi, + &mint_amount_opening_hi, + ); + + let (padding_commitment, padding_opening) = Pedersen::new(0_u64); + + Ok(( + BatchedRangeProofU64Data::new( + vec![ + &mint_amount_grouped_ciphertext_lo.commitment, + &mint_amount_grouped_ciphertext_hi.commitment, + &padding_commitment, + ], + vec![amount_lo, amount_hi, 0], + vec![ + MINT_AMOUNT_LO_BIT_LENGTH, + MINT_AMOUNT_HI_BIT_LENGTH, + PADDING_BIT_LENGTH, + ], + vec![ + &mint_amount_opening_lo, + &mint_amount_opening_hi, + &padding_opening, + ], + ) + .map_err(|_| TokenError::ProofGeneration)?, + BatchedGroupedCiphertext2HandlesValidityProofData::new( + destination_elgamal_pubkey, + &auditor_elgamal_pubkey, + &mint_amount_grouped_ciphertext_lo, + &mint_amount_grouped_ciphertext_hi, + amount_lo, + amount_hi, + &mint_amount_opening_lo, + &mint_amount_opening_hi, + ) + .map_err(|_| TokenError::ProofGeneration)?, + (mint_amount_opening_lo, mint_amount_opening_hi), + )) +} 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 00000000000..90d66632bfe --- /dev/null +++ b/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs @@ -0,0 +1,88 @@ +#[cfg(feature = "zk-ops")] +use { + super::ciphertext_extraction::MintProofContextInfo, + crate::check_zk_token_proof_program_account, + crate::extension::confidential_transfer::verify_proof::verify_ciphertext_validity_proof, + crate::proof::decode_proof_instruction_context, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + program_error::ProgramError, + sysvar::instructions::get_instruction_relative, + }, + solana_zk_token_sdk::instruction::BatchedRangeProofContext, + solana_zk_token_sdk::instruction::BatchedRangeProofU64Data, + solana_zk_token_sdk::instruction::{ + BatchedGroupedCiphertext2HandlesValidityProofContext, + BatchedGroupedCiphertext2HandlesValidityProofData, + }, + solana_zk_token_sdk::zk_token_proof_instruction::ProofInstruction, + solana_zk_token_sdk::{instruction::ProofType, zk_token_proof_state::ProofContextState}, + spl_pod::bytemuck::pod_from_bytes, + std::slice::Iter, +}; + +/// Verify zero-knowledge proof needed for a [ConfigureAccount] instruction and +/// return the corresponding proof context. +#[cfg(feature = "zk-ops")] +pub fn verify_mint_proof( + account_info_iter: &mut Iter<'_, AccountInfo<'_>>, + proof_instruction_offset: i64, +) -> Result { + if proof_instruction_offset == 0 { + // interpret `account_info` as a context state account + let range_proof_account_info = next_account_info(account_info_iter)?; + let cipher_text_validity_account_info = next_account_info(account_info_iter)?; + let range_proof_context = verify_batched_u64_range_proof(range_proof_account_info)?; + let ciphertext_validity_proof_context = + verify_ciphertext_validity_proof(cipher_text_validity_account_info)?; + + Ok(MintProofContextInfo::verify_and_extract( + &ciphertext_validity_proof_context, + &range_proof_context, + )?) + } else { + let sysvar_account_info = next_account_info(account_info_iter)?; + let range_proof_instruction = + get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; + + let ciphertext_validity_instruction = + get_instruction_relative(proof_instruction_offset + 1, sysvar_account_info)?; + + let range_proof_context = *decode_proof_instruction_context::< + BatchedRangeProofU64Data, + BatchedRangeProofContext, + >( + ProofInstruction::VerifyBatchedRangeProofU64, + &range_proof_instruction, + )?; + + let ciphertext_validity_proof_context = *decode_proof_instruction_context::< + BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext2HandlesValidityProofContext, + >( + ProofInstruction::VerifyGroupedCiphertext2HandlesValidity, + &ciphertext_validity_instruction, + )?; + + Ok(MintProofContextInfo::verify_and_extract( + &ciphertext_validity_proof_context, + &range_proof_context, + )?) + } +} + +/// Verify and process batched u64 range proof for [ConfidentialMint] instruction +pub fn verify_batched_u64_range_proof( + account_info: &AccountInfo<'_>, +) -> Result { + check_zk_token_proof_program_account(account_info.owner)?; + let context_state_account_data = account_info.data.borrow(); + let range_proof_context_state = + pod_from_bytes::>(&context_state_account_data)?; + + if range_proof_context_state.proof_type != ProofType::BatchedRangeProofU64.into() { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(range_proof_context_state.proof_context) +} diff --git a/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs b/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs index 28aad3ee9a3..d5a435242e4 100644 --- a/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs +++ b/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs @@ -219,9 +219,9 @@ pub struct TransferPubkeysInfo { /// The proof context information needed to process a [Transfer] instruction. #[cfg(feature = "zk-ops")] pub struct TransferProofContextInfo { - /// Ciphertext containing the low 16 bits of the transafer amount + /// Ciphertext containing the low 16 bits of the transfer amount pub ciphertext_lo: TransferAmountCiphertext, - /// Ciphertext containing the high 32 bits of the transafer amount + /// Ciphertext containing the high 32 bits of the transfer amount pub ciphertext_hi: TransferAmountCiphertext, /// The transfer public keys associated with a transfer pub transfer_pubkeys: TransferPubkeysInfo, diff --git a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs index 13c6001ba7b..428af485072 100644 --- a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs @@ -8,11 +8,7 @@ use { proof::decode_proof_instruction_context, }, solana_program::{ - account_info::{next_account_info, AccountInfo}, - msg, - program::invoke, - program_error::ProgramError, - sysvar::instructions::get_instruction_relative, + account_info::{next_account_info, AccountInfo}, msg, program::invoke, program_error::ProgramError, sysvar::instructions::get_instruction_relative }, solana_zk_token_sdk::zk_token_proof_instruction::{self, ContextStateInfo}, std::slice::Iter, @@ -533,7 +529,7 @@ fn verify_equality_proof( /// Verify and process ciphertext validity proof for [Transfer] and /// [TransferWithFee] instructions. -fn verify_ciphertext_validity_proof( +pub fn verify_ciphertext_validity_proof( account_info: &AccountInfo<'_>, ) -> Result { check_zk_token_proof_program_account(account_info.owner)?; From 3a9318d46a00ab7b3c69c71953e62b87bcd0d222 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 2 Jul 2024 12:49:19 -0300 Subject: [PATCH 03/34] simplify burn proofs --- token/cli/src/command.rs | 30 +-- token/client/src/token.rs | 35 ++- .../ciphertext_extraction.rs | 215 ++++++++++++++-- .../confidential_mint_burn/instruction.rs | 39 ++- .../extension/confidential_mint_burn/mod.rs | 4 +- .../confidential_mint_burn/processor.rs | 154 +++++++---- .../proof_generation.rs | 171 ++++++++++++- .../confidential_mint_burn/verify_proof.rs | 240 +++++++++++++++++- .../confidential_transfer/instruction.rs | 1 - .../confidential_transfer/processor.rs | 34 --- .../split_proof_generation.rs | 38 +-- .../confidential_transfer/verify_proof.rs | 10 +- 12 files changed, 769 insertions(+), 202 deletions(-) diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 5beef827652..c9780940460 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -38,7 +38,9 @@ use { spl_token_2022::{ error::TokenError, extension::{ - confidential_mint_burn::proof_generation::generate_mint_proofs, + confidential_mint_burn::proof_generation::{ + generate_burn_proofs, generate_mint_proofs, + }, confidential_transfer::{ account_info::{ combine_balances, ApplyPendingBalanceAccountInfo, TransferAccountInfo, @@ -4778,9 +4780,7 @@ async fn command_confidential_burn( .map(|ui_amount| spl_token::ui_amount_to_amount(ui_amount, mint_info.decimals)) .unwrap(); - // deserialize `pod` ElGamal pubkeys - let recipient_elgamal_pubkey = elgamal_keypair.pubkey(); - let auditor_elgamal_pubkey = None; + let auditor_elgamal_pubkey = token.auditor_elgamal().await?; let context_state_authority = config.fee_payer()?; let equality_proof_context_state_account = Keypair::new(); @@ -4805,19 +4805,20 @@ async fn command_confidential_burn( .unwrap(); let transfer_account_info = TransferAccountInfo::new(extension); - let ( - equality_proof_data, - ciphertext_validity_proof_data, - range_proof_data, - source_decrypt_handles, - pedersen_openings, - ) = transfer_account_info - .generate_split_transfer_proof_data( + let (equality_proof_data, ciphertext_validity_proof_data, range_proof_data, pedersen_openings) = + generate_burn_proofs( + &transfer_account_info + .available_balance + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?, + &transfer_account_info + .decryptable_available_balance + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?, burn_amount, elgamal_keypair, aes_key, - recipient_elgamal_pubkey, - auditor_elgamal_pubkey.as_ref(), + &auditor_elgamal_pubkey, ) .unwrap(); @@ -4848,7 +4849,6 @@ async fn command_confidential_burn( context_state_accounts, burn_amount, aes_key, - &source_decrypt_handles, &bulk_signers, &pedersen_openings, ) diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 5ba728dc745..26f7f79765d 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -4110,23 +4110,22 @@ where ) -> TokenResult { let signing_pubkeys = signing_keypairs.pubkeys(); let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); - self - .process_ixs( - &confidential_mint_burn::instruction::confidential_mint( - &self.program_id, - account, - &self.pubkey, - amount, - auditor_elgamal_pubkey, - authority, - &multisig_signers, - range_proof_location, - ciphertext_validity_proof_location, - pedersen_openings, - )?, - signing_keypairs, - ) - .await + self.process_ixs( + &confidential_mint_burn::instruction::confidential_mint( + &self.program_id, + account, + &self.pubkey, + amount, + auditor_elgamal_pubkey, + authority, + &multisig_signers, + range_proof_location, + ciphertext_validity_proof_location, + pedersen_openings, + )?, + signing_keypairs, + ) + .await } /// Create a range proof context state account for mint @@ -4222,7 +4221,6 @@ where context_state_accounts: TransferSplitContextStateAccounts<'_>, amount: u64, aes_key: &AeKey, - source_decrypt_handles: &SourceDecryptHandles, signing_keypairs: &S, pedersen_openings: &(PedersenOpening, PedersenOpening), ) -> TokenResult { @@ -4254,7 +4252,6 @@ where amount, new_decryptable_available_balance.into(), context_state_accounts, - source_decrypt_handles, authority, &multisig_signers, pedersen_openings, diff --git a/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs b/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs index 3b5dbe20943..925a4e1c8dc 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs @@ -1,9 +1,6 @@ -#[cfg(feature = "zk-ops")] -use bytemuck::{Pod, Zeroable}; -use solana_zk_token_sdk::zk_token_elgamal::pod::{ElGamalCiphertext, ElGamalPubkey}; - #[cfg(feature = "zk-ops")] use crate::{ + error::TokenError, extension::confidential_transfer::ciphertext_extraction::extract_commitment_from_grouped_ciphertext, solana_program::program_error::ProgramError, solana_zk_token_sdk::{ @@ -13,11 +10,39 @@ use crate::{ zk_token_elgamal::pod::GroupedElGamalCiphertext2Handles, }, }; +#[cfg(feature = "zk-ops")] +use bytemuck::{Pod, Zeroable}; +#[cfg(feature = "zk-ops")] +#[cfg(not(target_os = "solana"))] +use solana_zk_token_sdk::encryption::grouped_elgamal::GroupedElGamalCiphertext; +#[cfg(feature = "zk-ops")] +use solana_zk_token_sdk::{ + instruction::CiphertextCommitmentEqualityProofContext, + zk_token_elgamal::pod::{ElGamalCiphertext, ElGamalPubkey}, +}; /// Wrapper for `GroupedElGamalCiphertext2Handles` when used during minting #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] -pub struct MintAmountCiphertext(pub GroupedElGamalCiphertext2Handles); +pub struct MintBurnAmountCiphertext(pub(crate) GroupedElGamalCiphertext2Handles); + +#[cfg(not(target_os = "solana"))] +impl From> for MintBurnAmountCiphertext { + fn from(value: GroupedElGamalCiphertext<2>) -> Self { + Self(value.into()) + } +} + +/// Trait to retrieve auditor amounts from proof context information +#[cfg(feature = "zk-ops")] +pub trait AuditableProofContextInfo { + /// Return the low 16 bits of the amount to be audited + fn auditor_amount_lo(&self) -> ElGamalCiphertext; + /// Return the high 32 bits of the amount to be audited + fn auditor_amount_hi(&self) -> ElGamalCiphertext; + /// Return the auditors ElGamal public key + fn auditor_pubkey(&self) -> &ElGamalPubkey; +} /// The proof context information needed to process a [Transfer] instruction. #[cfg(feature = "zk-ops")] @@ -26,17 +51,58 @@ pub struct MintProofContextInfo { pub destination_pubkey: ElGamalPubkey, /// auditor elgamal pubkey used in proof generation pub auditor_pubkey: ElGamalPubkey, - /// Ciphertext containing the low 16 bits of the transafer amount - pub ciphertext_lo: MintAmountCiphertext, - /// Ciphertext containing the high 32 bits of the transafer amount - pub ciphertext_hi: MintAmountCiphertext, + /// Ciphertext containing the low 16 bits of the mint amount + pub ciphertext_lo: MintBurnAmountCiphertext, + /// Ciphertext containing the high 32 bits of the mint amount + pub ciphertext_hi: MintBurnAmountCiphertext, +} + +#[cfg(feature = "zk-ops")] +impl AuditableProofContextInfo for MintProofContextInfo { + fn auditor_amount_lo(&self) -> ElGamalCiphertext { + mint_burn_amount_auditor_ciphertext(&self.ciphertext_lo) + } + fn auditor_amount_hi(&self) -> ElGamalCiphertext { + mint_burn_amount_auditor_ciphertext(&self.ciphertext_hi) + } + fn auditor_pubkey(&self) -> &ElGamalPubkey { + &self.auditor_pubkey + } +} + +/// The proof context information needed to process a [Transfer] instruction. +#[cfg(feature = "zk-ops")] +pub struct BurnProofContextInfo { + /// destination elgamal pubkey used in proof generation + pub burner_pubkey: ElGamalPubkey, + /// auditor elgamal pubkey used in proof generation + pub auditor_pubkey: ElGamalPubkey, + /// Ciphertext containing the low 16 bits of the burn amount + pub ciphertext_lo: MintBurnAmountCiphertext, + /// Ciphertext containing the high 32 bits of the burn amount + pub ciphertext_hi: MintBurnAmountCiphertext, + /// The new available balance ciphertext for the burning account + pub new_burner_ciphertext: ElGamalCiphertext, +} + +#[cfg(feature = "zk-ops")] +impl AuditableProofContextInfo for BurnProofContextInfo { + fn auditor_amount_lo(&self) -> ElGamalCiphertext { + mint_burn_amount_auditor_ciphertext(&self.ciphertext_lo) + } + fn auditor_amount_hi(&self) -> ElGamalCiphertext { + mint_burn_amount_auditor_ciphertext(&self.ciphertext_hi) + } + fn auditor_pubkey(&self) -> &ElGamalPubkey { + &self.auditor_pubkey + } } #[cfg(feature = "zk-ops")] impl MintProofContextInfo { - /// Create a transfer proof context information needed to process a - /// [Transfer] instruction from split proof contexts after verifying - /// their consistency. + /// Create the mint proof context information needed to process a + /// [ConfidentialMint] instruction from context state accounts + /// after verifying their consistency. pub fn verify_and_extract( ciphertext_validity_proof_context: &BatchedGroupedCiphertext2HandlesValidityProofContext, range_proof_context: &BatchedRangeProofContext, @@ -104,8 +170,110 @@ impl MintProofContextInfo { Ok(Self { destination_pubkey: *destination_pubkey, auditor_pubkey: *auditor_pubkey, - ciphertext_lo: MintAmountCiphertext(*mint_amount_ciphertext_lo), - ciphertext_hi: MintAmountCiphertext(*mint_amount_ciphertext_hi), + ciphertext_lo: MintBurnAmountCiphertext(*mint_amount_ciphertext_lo), + ciphertext_hi: MintBurnAmountCiphertext(*mint_amount_ciphertext_hi), + }) + } +} + +#[cfg(feature = "zk-ops")] +impl BurnProofContextInfo { + /// Create a transfer proof context information needed to process a + /// [Transfer] instruction from split proof contexts after verifying + /// their consistency. + pub fn verify_and_extract( + equality_proof_context: &CiphertextCommitmentEqualityProofContext, + ciphertext_validity_proof_context: &BatchedGroupedCiphertext2HandlesValidityProofContext, + range_proof_context: &BatchedRangeProofContext, + ) -> Result { + // The equality proof context consists of the source ElGamal public key, the new + // source available balance ciphertext, and the new source available + // commitment. The public key and ciphertext should be returned as parts + // of `TransferProofContextInfo` and the commitment should be checked + // with range proof for consistency. + let CiphertextCommitmentEqualityProofContext { + pubkey: source_pubkey, + ciphertext: new_source_ciphertext, + commitment: new_source_commitment, + } = equality_proof_context; + + // The ciphertext validity proof context consists of the destination ElGamal + // public key, auditor ElGamal public key, and the transfer amount + // ciphertexts. All of these fields should be returned as part of + // `TransferProofContextInfo`. In addition, the commitments pertaining + // to the transfer amount ciphertexts should be checked with range proof for + // consistency. + let BatchedGroupedCiphertext2HandlesValidityProofContext { + destination_pubkey: burner_pubkey, + auditor_pubkey, + grouped_ciphertext_lo: transfer_amount_ciphertext_lo, + grouped_ciphertext_hi: transfer_amount_ciphertext_hi, + } = ciphertext_validity_proof_context; + + if burner_pubkey != source_pubkey { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } + + // The range proof context consists of the Pedersen commitments and bit-lengths + // for which the range proof is proved. The commitments must consist of + // three commitments pertaining to the new source available balance, the + // low bits of the transfer amount, and high bits of the transfer + // amount. These commitments must be checked for bit lengths `64`, `16`, + // and `32`. + let BatchedRangeProofContext { + commitments: range_proof_commitments, + bit_lengths: range_proof_bit_lengths, + } = range_proof_context; + + // check that the range proof was created for the correct set of Pedersen + // commitments + let transfer_amount_commitment_lo = + extract_commitment_from_grouped_ciphertext(transfer_amount_ciphertext_lo); + let transfer_amount_commitment_hi = + extract_commitment_from_grouped_ciphertext(transfer_amount_ciphertext_hi); + + let expected_commitments = [ + *new_source_commitment, + transfer_amount_commitment_lo, + transfer_amount_commitment_hi, + // the fourth dummy commitment can be any commitment + ]; + + if !range_proof_commitments + .iter() + .zip(expected_commitments.iter()) + .all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment) + { + return Err(ProgramError::InvalidInstructionData); + } + + // check that the range proof was created for the correct number of bits + const REMAINING_BALANCE_BIT_LENGTH: u8 = 64; + const TRANSFER_AMOUNT_LO_BIT_LENGTH: u8 = 16; + const TRANSFER_AMOUNT_HI_BIT_LENGTH: u8 = 32; + const PADDING_BIT_LENGTH: u8 = 16; + let expected_bit_lengths = [ + REMAINING_BALANCE_BIT_LENGTH, + TRANSFER_AMOUNT_LO_BIT_LENGTH, + TRANSFER_AMOUNT_HI_BIT_LENGTH, + PADDING_BIT_LENGTH, + ] + .iter(); + + if !range_proof_bit_lengths + .iter() + .zip(expected_bit_lengths) + .all(|(proof_len, expected_len)| proof_len == expected_len) + { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(Self { + burner_pubkey: *burner_pubkey, + auditor_pubkey: *auditor_pubkey, + ciphertext_lo: MintBurnAmountCiphertext(*transfer_amount_ciphertext_lo), + ciphertext_hi: MintBurnAmountCiphertext(*transfer_amount_ciphertext_hi), + new_burner_ciphertext: *new_source_ciphertext, }) } } @@ -115,17 +283,16 @@ impl MintProofContextInfo { /// /// A mint amount ciphertext consists of the following 32-byte components /// that are serialized in order: -/// 1. The `commitment` component that encodes the mint amount. -/// key. -/// 2. The `decryption handle` component with respect to the destination -/// public key. +/// 1. The `commitment` component that encodes the mint amount. key. +/// 2. The `decryption handle` component with respect to the destination or +/// source public key. /// 3. The `decryption handle` component with respect to the auditor public /// key. /// /// An ElGamal ciphertext for the auditor consists of the `commitment` component /// and the `decryption handle` component with respect to the auditor. -pub fn mint_amount_auditor_ciphertext( - transfer_amount_ciphertext: &MintAmountCiphertext, +pub fn mint_burn_amount_auditor_ciphertext( + transfer_amount_ciphertext: &MintBurnAmountCiphertext, ) -> ElGamalCiphertext { let transfer_amount_ciphertext_bytes = bytemuck::bytes_of(transfer_amount_ciphertext); @@ -136,12 +303,12 @@ pub fn mint_amount_auditor_ciphertext( ElGamalCiphertext(auditor_ciphertext_bytes) } -/// Extract the mint amount ciphertext encrypted under the destination ElGamal -/// public key. +/// Extract the mint amount ciphertext encrypted under the destination or source +/// ElGamal public key, for mint and burn respectively. /// /// Structure see `mint_amount_auditor_ciphertext` -pub fn mint_amount_destination_ciphertext( - transfer_amount_ciphertext: &MintAmountCiphertext, +pub fn mint_burn_amount_target_ciphertext( + transfer_amount_ciphertext: &MintBurnAmountCiphertext, ) -> ElGamalCiphertext { let transfer_amount_ciphertext_bytes = bytemuck::bytes_of(transfer_amount_ciphertext); diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index c4f861e5d7e..8b0b1a5f059 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -4,22 +4,19 @@ use crate::{ extension::confidential_transfer::processor::verify_and_split_deposit_amount, proof::ProofLocation, }; +#[cfg(feature = "serde-traits")] +use serde::{Deserialize, Serialize}; #[cfg(not(target_os = "solana"))] use solana_zk_token_sdk::instruction::{ BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU64Data, }; - -#[cfg(feature = "serde-traits")] -use serde::{Deserialize, Serialize}; #[cfg(not(target_os = "solana"))] use solana_zk_token_sdk::{ encryption::{elgamal::ElGamalPubkey, pedersen::PedersenOpening}, zk_token_proof_instruction::{verify_batched_verify_range_proof_u64, ProofInstruction}, }; use { - crate::extension::confidential_transfer::{ - ciphertext_extraction::SourceDecryptHandles, DecryptableBalance, - }, + crate::extension::confidential_transfer::DecryptableBalance, bytemuck::{Pod, Zeroable}, num_enum::{IntoPrimitive, TryFromPrimitive}, solana_program::pubkey::Pubkey, @@ -106,7 +103,8 @@ pub struct MintInstructionData { pub audit_amount_hi: ElGamalCiphertext, /// Relative location of the `ProofInstruction::VerifyBatchedRangeProofU64` /// instruction to the `ConfidentialMint` instruction in the - /// transaction. The `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity` + /// transaction. The + /// `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity` /// has to always be at the instruction directly after the range proof one. pub proof_instruction_offset: i8, } @@ -120,16 +118,18 @@ pub struct BurnInstructionData { /// The new source decryptable balance if the transfer succeeds #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_decryptable_available_balance: DecryptableBalance, - /// The ElGamal decryption handle pertaining to the low and high bits of the - /// transfer amount. This field is used when the transfer proofs are - /// split and verified as smaller components. - /// - /// NOTE: This field is to be removed in the next Solana upgrade. - pub source_decrypt_handles: SourceDecryptHandles, /// low 16 bits of encrypted amount to be minted - pub burn_lo: ElGamalCiphertext, + pub auditor_lo: ElGamalCiphertext, /// high 48 bits of encrypted amount to be minted - pub burn_hi: ElGamalCiphertext, + pub auditor_hi: ElGamalCiphertext, + /// Relative location of the + /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction + /// to the `ConfidentialBurn` instruction in the transaction. The + /// `ProofInstruction::VerifyBatchedRangeProofU128` has to always be at + /// the instruction directly after the equality proof one, + /// with the `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity` + /// following after that. + pub proof_instruction_offset: i8, } /// Create a `InitializeMint` instruction @@ -283,7 +283,6 @@ pub fn confidential_burn_with_split_proofs( burn_amount: u64, new_decryptable_available_balance: DecryptableBalance, context_accounts: TransferSplitContextStateAccounts, - source_decrypt_handles: &SourceDecryptHandles, authority: &Pubkey, multisig_signers: &[&Pubkey], pedersen_openings: &(PedersenOpening, PedersenOpening), @@ -296,7 +295,6 @@ pub fn confidential_burn_with_split_proofs( burn_amount, new_decryptable_available_balance, context_accounts, - source_decrypt_handles, authority, multisig_signers, pedersen_openings, @@ -314,7 +312,6 @@ pub fn inner_confidential_burn_with_split_proofs( burn_amount: u64, new_decryptable_available_balance: DecryptableBalance, context_accounts: TransferSplitContextStateAccounts, - source_decrypt_handles: &SourceDecryptHandles, authority: &Pubkey, multisig_signers: &[&Pubkey], pedersen_openings: &(PedersenOpening, PedersenOpening), @@ -372,9 +369,9 @@ pub fn inner_confidential_burn_with_split_proofs( ConfidentialMintBurnInstruction::ConfidentialBurn, &BurnInstructionData { new_decryptable_available_balance, - source_decrypt_handles: *source_decrypt_handles, - burn_hi, - burn_lo, + auditor_hi: burn_hi, + auditor_lo: burn_lo, + proof_instruction_offset: 0, }, )) } diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index aee6570534e..3801d77206c 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -21,10 +21,10 @@ pub mod processor; /// Confidential Mint-Burn proof generation pub mod proof_generation; -/// Confidential Mint-Burn proof verification +/// Confidential Mint-Burn proof verification pub mod verify_proof; -/// Confidential Mint-Burn proof verification +/// Confidential Mint-Burn proof verification pub mod ciphertext_extraction; /// Confidential transfer mint configuration diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index 5b631a225dc..f7e3f9ed8b6 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -1,11 +1,9 @@ #[cfg(feature = "zk-ops")] use { - self::ciphertext_extraction::SourceDecryptHandles, - self::processor::validate_auditor_ciphertext, - crate::extension::confidential_transfer::processor::process_source_for_transfer, + super::ciphertext_extraction::BurnProofContextInfo, + super::verify_proof::validate_auditor_ciphertext, crate::extension::non_transferable::NonTransferable, solana_zk_token_sdk::zk_token_elgamal::ops as syscall, - solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, }; use { crate::{ @@ -13,9 +11,7 @@ use { error::TokenError, extension::{ confidential_mint_burn::{ - ciphertext_extraction::{ - mint_amount_auditor_ciphertext, mint_amount_destination_ciphertext, - }, + ciphertext_extraction::mint_burn_amount_target_ciphertext, instruction::{ BurnInstructionData, ConfidentialMintBurnInstruction, InitializeMintData, MintInstructionData, UpdateMintData, @@ -23,7 +19,10 @@ use { verify_proof::verify_mint_proof, ConfidentialMintBurn, }, - confidential_transfer::{verify_proof::*, *}, + confidential_transfer::{ + ConfidentialTransferAccount, ConfidentialTransferMint, DecryptableBalance, + }, + non_transferable::NonTransferableAccount, BaseStateWithExtensions, BaseStateWithExtensionsMut, PodStateWithExtensions, PodStateWithExtensionsMut, }, @@ -140,7 +139,11 @@ fn process_confidential_mint( // it'd enable creating SOL out of thin air assert!(!token_account.base.is_native()); - let proof_context = verify_mint_proof(account_info_iter, data.proof_instruction_offset as i64)?; + let proof_context = verify_mint_proof( + account_info_iter, + data.proof_instruction_offset as i64, + false, + )?; let confidential_transfer_account = token_account.get_extension_mut::()?; @@ -150,29 +153,21 @@ fn process_confidential_mint( return Err(ProgramError::InvalidInstructionData); } - if !conf_transf_ext - .auditor_elgamal_pubkey - .equals(&proof_context.auditor_pubkey) - { - return Err(ProgramError::InvalidInstructionData); - } - - if data.audit_amount_lo != mint_amount_auditor_ciphertext(&proof_context.ciphertext_lo) { - return Err(ProgramError::InvalidInstructionData); - } - - if data.audit_amount_hi != mint_amount_auditor_ciphertext(&proof_context.ciphertext_hi) { - return Err(ProgramError::InvalidInstructionData); - } + validate_auditor_ciphertext( + conf_transf_ext, + &proof_context, + &data.audit_amount_lo, + &data.audit_amount_hi, + )?; confidential_transfer_account.pending_balance_lo = syscall::add( &confidential_transfer_account.pending_balance_lo, - &mint_amount_destination_ciphertext(&proof_context.ciphertext_lo), + &mint_burn_amount_target_ciphertext(&proof_context.ciphertext_lo), ) .ok_or(TokenError::CiphertextArithmeticFailed)?; confidential_transfer_account.pending_balance_hi = syscall::add( &confidential_transfer_account.pending_balance_hi, - &mint_amount_destination_ciphertext(&proof_context.ciphertext_hi), + &mint_burn_amount_target_ciphertext(&proof_context.ciphertext_hi), ) .ok_or(TokenError::CiphertextArithmeticFailed)?; @@ -186,11 +181,10 @@ fn process_confidential_mint( fn process_confidential_burn( program_id: &Pubkey, accounts: &[AccountInfo], - new_decryptable_available_balance: DecryptableBalance, - source_decrypt_handles: &SourceDecryptHandles, - auditor_hi: &ElGamalCiphertext, - auditor_lo: &ElGamalCiphertext, + data: &BurnInstructionData, ) -> ProgramResult { + use super::verify_proof::verify_burn_proof; + 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)?; @@ -208,13 +202,10 @@ fn process_confidential_burn( // The zero-knowledge proof certifies that: // 1. the burn amount is encrypted in the correct form // 2. the source account has enough balance to burn the amount - let maybe_proof_context = verify_transfer_proof( + let proof_context = verify_burn_proof( account_info_iter, - 0, - true, // proof is split - false, // don't noop but fail if proof is missing - false, // not supported for burn - source_decrypt_handles, + data.proof_instruction_offset as i64, + false, )?; let authority_info = next_account_info(account_info_iter)?; @@ -225,17 +216,91 @@ fn process_confidential_burn( mint_info, authority_info, account_info_iter.as_slice(), - maybe_proof_context.as_ref(), - new_decryptable_available_balance, + &proof_context, + data.new_decryptable_available_balance, )?; validate_auditor_ciphertext( mint.get_extension::()?, - maybe_proof_context.as_ref(), - auditor_hi, - auditor_lo, + &proof_context, + &data.auditor_lo, + &data.auditor_hi, + )?; + + Ok(()) +} + +/// Processes the changes for the sending party of a confidential transfer +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "zk-ops")] +pub fn process_source_for_transfer( + program_id: &Pubkey, + source_account_info: &AccountInfo, + mint_info: &AccountInfo, + authority_info: &AccountInfo, + signers: &[AccountInfo], + proof_context: &BurnProofContextInfo, + new_source_decryptable_available_balance: DecryptableBalance, +) -> ProgramResult { + check_program_account(source_account_info.owner)?; + let authority_info_data_len = authority_info.data_len(); + let token_account_data = &mut source_account_info.data.borrow_mut(); + let mut token_account = PodStateWithExtensionsMut::::unpack(token_account_data)?; + if token_account + .get_extension::() + .is_ok() + { + return Err(TokenError::NonTransferable.into()); + } + + Processor::validate_owner( + program_id, + &token_account.base.owner, + authority_info, + authority_info_data_len, + signers, )?; + 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::()?; + 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.burner_pubkey != confidential_transfer_account.elgamal_pubkey { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } + + let source_transfer_amount_lo = + mint_burn_amount_target_ciphertext(&proof_context.ciphertext_lo); + let source_transfer_amount_hi = + mint_burn_amount_target_ciphertext(&proof_context.ciphertext_hi); + + let new_source_available_balance = syscall::subtract_with_lo_hi( + &confidential_transfer_account.available_balance, + &source_transfer_amount_lo, + &source_transfer_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.new_burner_ciphertext { + return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); + } + + confidential_transfer_account.available_balance = new_source_available_balance; + confidential_transfer_account.decryptable_available_balance = + new_source_decryptable_available_balance; + Ok(()) } @@ -266,14 +331,7 @@ pub(crate) fn process_instruction( ConfidentialMintBurnInstruction::ConfidentialBurn => { msg!("ConfidentialMintBurnInstruction::ConfidentialBurn"); let data = decode_instruction_data::(input)?; - process_confidential_burn( - program_id, - accounts, - data.new_decryptable_available_balance, - &data.source_decrypt_handles, - &data.burn_hi, - &data.burn_lo, - ) + process_confidential_burn(program_id, accounts, data) } } } diff --git a/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs b/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs index 421edf073d9..f0403ebb6f0 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs @@ -1,22 +1,35 @@ #[cfg(not(target_os = "solana"))] use crate::{ - error::TokenError, extension::confidential_transfer::processor::verify_and_split_deposit_amount, + error::TokenError, + extension::{ + confidential_mint_burn::ciphertext_extraction::mint_burn_amount_target_ciphertext, + confidential_transfer::processor::verify_and_split_deposit_amount, + }, }; -#[cfg(not(target_os = "solana"))] -use solana_zk_token_sdk::{ - encryption::{grouped_elgamal::GroupedElGamal, pedersen::Pedersen}, - instruction::{BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU64Data}, -}; - #[cfg(not(target_os = "solana"))] use solana_program::program_error::ProgramError; #[cfg(not(target_os = "solana"))] use solana_zk_token_sdk::encryption::{elgamal::ElGamalPubkey, pedersen::PedersenOpening}; +#[cfg(not(target_os = "solana"))] +use solana_zk_token_sdk::{ + encryption::{ + auth_encryption::{AeCiphertext, AeKey}, + elgamal::ElGamalCiphertext, + elgamal::ElGamalKeypair, + grouped_elgamal::GroupedElGamal, + pedersen::Pedersen, + }, + instruction::{ + BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, + BatchedRangeProofU64Data, CiphertextCommitmentEqualityProofData, + }, + zk_token_elgamal::ops::subtract_with_lo_hi, +}; -/// Generates range proof for mint instruction +/// Generates proof data for mint instruction #[cfg(not(target_os = "solana"))] pub fn generate_mint_proofs( - amount: u64, + mint_amount: u64, destination_elgamal_pubkey: &ElGamalPubkey, auditor_elgamal_pubkey: &Option, ) -> Result< @@ -27,7 +40,7 @@ pub fn generate_mint_proofs( ), ProgramError, > { - let (amount_lo, amount_hi) = verify_and_split_deposit_amount(amount)?; + let (amount_lo, amount_hi) = verify_and_split_deposit_amount(mint_amount)?; let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); const MINT_AMOUNT_LO_BIT_LENGTH: usize = 16; @@ -85,3 +98,141 @@ pub fn generate_mint_proofs( (mint_amount_opening_lo, mint_amount_opening_hi), )) } + +/// Generates proof data for burn instruction +#[cfg(not(target_os = "solana"))] +#[allow(clippy::type_complexity)] +pub fn generate_burn_proofs( + current_available_balance: &ElGamalCiphertext, + current_decryptable_available_balance: &AeCiphertext, + burn_amount: u64, + source_elgamal_keypair: &ElGamalKeypair, + aes_key: &AeKey, + auditor_elgamal_pubkey: &Option, +) -> Result< + ( + CiphertextCommitmentEqualityProofData, + BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedRangeProofU128Data, + (PedersenOpening, PedersenOpening), + ), + TokenError, +> { + let burner_elgamal_pubkey = source_elgamal_keypair.pubkey(); + let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); + + // Split the transfer amount into the low and high bit components. + let (burn_amount_lo, burn_amount_hi) = verify_and_split_deposit_amount(burn_amount)?; + + // Encrypt the `lo` and `hi` transfer amounts. + let burn_amount_opening_lo = PedersenOpening::new_rand(); + let burn_amount_grouped_ciphertext_lo = GroupedElGamal::<2>::encrypt_with( + [burner_elgamal_pubkey, &auditor_elgamal_pubkey], + burn_amount_lo, + &burn_amount_opening_lo, + ); + + let burn_amount_opening_hi = PedersenOpening::new_rand(); + let burn_amount_grouped_ciphertext_hi = GroupedElGamal::<2>::encrypt_with( + [burner_elgamal_pubkey, &auditor_elgamal_pubkey], + burn_amount_hi, + &burn_amount_opening_hi, + ); + + // Decrypt the current available balance at the source + let current_decrypted_available_balance = current_decryptable_available_balance + .decrypt(aes_key) + .ok_or(TokenError::AccountDecryption)?; + + // Compute the remaining balance at the source + let new_decrypted_available_balance = current_decrypted_available_balance + .checked_sub(burn_amount) + .ok_or(TokenError::InsufficientFunds)?; + + // Create a new Pedersen commitment for the remaining balance at the source + let (new_available_balance_commitment, new_source_opening) = + Pedersen::new(new_decrypted_available_balance); + + // Compute the remaining balance at the source as ElGamal ciphertexts + let transfer_amount_source_ciphertext_lo = + mint_burn_amount_target_ciphertext(&burn_amount_grouped_ciphertext_lo.into()); + let transfer_amount_source_ciphertext_hi = + mint_burn_amount_target_ciphertext(&burn_amount_grouped_ciphertext_hi.into()); + + let current_available_balance = (*current_available_balance).into(); + let new_available_balance_ciphertext = subtract_with_lo_hi( + ¤t_available_balance, + &transfer_amount_source_ciphertext_lo, + &transfer_amount_source_ciphertext_hi, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; + let new_available_balance_ciphertext: ElGamalCiphertext = new_available_balance_ciphertext + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?; + + // generate equality proof data + let equality_proof_data = CiphertextCommitmentEqualityProofData::new( + source_elgamal_keypair, + &new_available_balance_ciphertext, + &new_available_balance_commitment, + &new_source_opening, + new_decrypted_available_balance, + ) + .map_err(|_| TokenError::ProofGeneration)?; + + // generate ciphertext validity data + let ciphertext_validity_proof_data = BatchedGroupedCiphertext2HandlesValidityProofData::new( + burner_elgamal_pubkey, + &auditor_elgamal_pubkey, + &burn_amount_grouped_ciphertext_lo, + &burn_amount_grouped_ciphertext_hi, + burn_amount_lo, + burn_amount_hi, + &burn_amount_opening_lo, + &burn_amount_opening_hi, + ) + .map_err(|_| TokenError::ProofGeneration)?; + + // generate range proof data + const REMAINING_BALANCE_BIT_LENGTH: usize = 64; + const TRANSFER_AMOUNT_LO_BIT_LENGTH: usize = 16; + const TRANSFER_AMOUNT_HI_BIT_LENGTH: usize = 32; + const PADDING_BIT_LENGTH: usize = 16; + + let (padding_commitment, padding_opening) = Pedersen::new(0_u64); + + let range_proof_data = BatchedRangeProofU128Data::new( + vec![ + &new_available_balance_commitment, + &burn_amount_grouped_ciphertext_lo.commitment, + &burn_amount_grouped_ciphertext_hi.commitment, + &padding_commitment, + ], + vec![ + new_decrypted_available_balance, + burn_amount_lo, + burn_amount_hi, + 0, + ], + vec![ + REMAINING_BALANCE_BIT_LENGTH, + TRANSFER_AMOUNT_LO_BIT_LENGTH, + TRANSFER_AMOUNT_HI_BIT_LENGTH, + PADDING_BIT_LENGTH, + ], + vec![ + &new_source_opening, + &burn_amount_opening_lo, + &burn_amount_opening_hi, + &padding_opening, + ], + ) + .map_err(|_| TokenError::ProofGeneration)?; + + Ok(( + equality_proof_data, + ciphertext_validity_proof_data, + range_proof_data, + (burn_amount_opening_hi, burn_amount_opening_lo), + )) +} 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 index 90d66632bfe..34cb339ec60 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs @@ -1,11 +1,21 @@ +use { + super::ciphertext_extraction::BurnProofContextInfo, + crate::{error::TokenError, extension::confidential_transfer::ConfidentialTransferMint}, + solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, +}; #[cfg(feature = "zk-ops")] use { - super::ciphertext_extraction::MintProofContextInfo, + super::ciphertext_extraction::{AuditableProofContextInfo, MintProofContextInfo}, + crate::check_system_program_account, crate::check_zk_token_proof_program_account, - crate::extension::confidential_transfer::verify_proof::verify_ciphertext_validity_proof, + crate::extension::confidential_transfer::verify_proof::{ + verify_ciphertext_validity_proof, verify_equality_proof, verify_transfer_range_proof, + }, crate::proof::decode_proof_instruction_context, solana_program::{ account_info::{next_account_info, AccountInfo}, + msg, + program::invoke, program_error::ProgramError, sysvar::instructions::get_instruction_relative, }, @@ -13,20 +23,23 @@ use { solana_zk_token_sdk::instruction::BatchedRangeProofU64Data, solana_zk_token_sdk::instruction::{ BatchedGroupedCiphertext2HandlesValidityProofContext, - BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCommitmentEqualityProofContext, CiphertextCommitmentEqualityProofData, }, - solana_zk_token_sdk::zk_token_proof_instruction::ProofInstruction, + solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalPubkey, + solana_zk_token_sdk::zk_token_proof_instruction::{self, ContextStateInfo, ProofInstruction}, solana_zk_token_sdk::{instruction::ProofType, zk_token_proof_state::ProofContextState}, spl_pod::bytemuck::pod_from_bytes, std::slice::Iter, }; -/// Verify zero-knowledge proof needed for a [ConfigureAccount] instruction and -/// return the corresponding proof context. +/// 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<'_>>, proof_instruction_offset: i64, + close_split_context_state_on_execution: bool, ) -> Result { if proof_instruction_offset == 0 { // interpret `account_info` as a context state account @@ -36,6 +49,44 @@ pub fn verify_mint_proof( let ciphertext_validity_proof_context = verify_ciphertext_validity_proof(cipher_text_validity_account_info)?; + if close_split_context_state_on_execution { + let lamport_destination_account_info = next_account_info(account_info_iter)?; + let context_state_account_authority_info = next_account_info(account_info_iter)?; + let _zk_token_proof_program = next_account_info(account_info_iter)?; + + msg!("Closing equality proof context state account"); + invoke( + &zk_token_proof_instruction::close_context_state( + ContextStateInfo { + context_state_account: cipher_text_validity_account_info.key, + context_state_authority: context_state_account_authority_info.key, + }, + lamport_destination_account_info.key, + ), + &[ + cipher_text_validity_account_info.clone(), + lamport_destination_account_info.clone(), + context_state_account_authority_info.clone(), + ], + )?; + + msg!("Closing range proof context state account"); + invoke( + &zk_token_proof_instruction::close_context_state( + ContextStateInfo { + context_state_account: range_proof_account_info.key, + context_state_authority: context_state_account_authority_info.key, + }, + lamport_destination_account_info.key, + ), + &[ + range_proof_account_info.clone(), + lamport_destination_account_info.clone(), + context_state_account_authority_info.clone(), + ], + )?; + } + Ok(MintProofContextInfo::verify_and_extract( &ciphertext_validity_proof_context, &range_proof_context, @@ -71,7 +122,153 @@ pub fn verify_mint_proof( } } -/// Verify and process batched u64 range proof for [ConfidentialMint] instruction +/// 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<'_>>, + proof_instruction_offset: i64, + close_split_context_state_on_execution: bool, +) -> Result { + if proof_instruction_offset == 0 { + let equality_proof_context_state_account_info = next_account_info(account_info_iter)?; + let ciphertext_validity_proof_context_state_account_info = + next_account_info(account_info_iter)?; + let range_proof_context_state_account_info = next_account_info(account_info_iter)?; + + if check_system_program_account(equality_proof_context_state_account_info.owner).is_ok() { + msg!("Equality proof context state account not initialized"); + return Err(ProgramError::UninitializedAccount); + } + + if check_system_program_account(ciphertext_validity_proof_context_state_account_info.owner) + .is_ok() + { + msg!("Ciphertext validity proof context state account not initialized"); + return Err(ProgramError::UninitializedAccount); + } + + if check_system_program_account(range_proof_context_state_account_info.owner).is_ok() { + msg!("Range proof context state account not initialized"); + return Err(ProgramError::UninitializedAccount); + } + + let equality_proof_context = + verify_equality_proof(equality_proof_context_state_account_info)?; + let ciphertext_validity_proof_context = + verify_ciphertext_validity_proof(ciphertext_validity_proof_context_state_account_info)?; + let range_proof_context = + verify_transfer_range_proof(range_proof_context_state_account_info)?; + + // The `TransferProofContextInfo` constructor verifies the consistency of the + // individual proof context and generates a `TransferWithFeeProofInfo` struct + // that is used to process the rest of the token-2022 logic. + let transfer_proof_context = BurnProofContextInfo::verify_and_extract( + &equality_proof_context, + &ciphertext_validity_proof_context, + &range_proof_context, + )?; + + if close_split_context_state_on_execution { + let lamport_destination_account_info = next_account_info(account_info_iter)?; + let context_state_account_authority_info = next_account_info(account_info_iter)?; + let _zk_token_proof_program = next_account_info(account_info_iter)?; + + msg!("Closing equality proof context state account"); + invoke( + &zk_token_proof_instruction::close_context_state( + ContextStateInfo { + context_state_account: equality_proof_context_state_account_info.key, + context_state_authority: context_state_account_authority_info.key, + }, + lamport_destination_account_info.key, + ), + &[ + equality_proof_context_state_account_info.clone(), + lamport_destination_account_info.clone(), + context_state_account_authority_info.clone(), + ], + )?; + + msg!("Closing ciphertext validity proof context state account"); + invoke( + &zk_token_proof_instruction::close_context_state( + ContextStateInfo { + context_state_account: ciphertext_validity_proof_context_state_account_info + .key, + context_state_authority: context_state_account_authority_info.key, + }, + lamport_destination_account_info.key, + ), + &[ + ciphertext_validity_proof_context_state_account_info.clone(), + lamport_destination_account_info.clone(), + context_state_account_authority_info.clone(), + ], + )?; + + msg!("Closing range proof context state account"); + invoke( + &zk_token_proof_instruction::close_context_state( + ContextStateInfo { + context_state_account: range_proof_context_state_account_info.key, + context_state_authority: context_state_account_authority_info.key, + }, + lamport_destination_account_info.key, + ), + &[ + range_proof_context_state_account_info.clone(), + lamport_destination_account_info.clone(), + context_state_account_authority_info.clone(), + ], + )?; + } + + Ok(transfer_proof_context) + } else { + let sysvar_account_info = next_account_info(account_info_iter)?; + let equality_proof_instruction = + get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; + let range_proof_instruction = + get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; + + let ciphertext_validity_instruction = + get_instruction_relative(proof_instruction_offset + 1, sysvar_account_info)?; + + let equality_proof_context = *decode_proof_instruction_context::< + CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofContext, + >( + ProofInstruction::VerifyCiphertextCommitmentEquality, + &equality_proof_instruction, + )?; + + let range_proof_context = *decode_proof_instruction_context::< + BatchedRangeProofU128Data, + BatchedRangeProofContext, + >( + ProofInstruction::VerifyBatchedRangeProofU128, + &range_proof_instruction, + )?; + + let ciphertext_validity_proof_context = *decode_proof_instruction_context::< + BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext2HandlesValidityProofContext, + >( + ProofInstruction::VerifyGroupedCiphertext2HandlesValidity, + &ciphertext_validity_instruction, + )?; + + Ok(BurnProofContextInfo::verify_and_extract( + &equality_proof_context, + &ciphertext_validity_proof_context, + &range_proof_context, + )?) + } +} + +/// Verify and process batched u64 range proof for [ConfidentialMint] +/// instruction pub fn verify_batched_u64_range_proof( account_info: &AccountInfo<'_>, ) -> Result { @@ -86,3 +283,32 @@ pub fn verify_batched_u64_range_proof( Ok(range_proof_context_state.proof_context) } + +/// Validates the auditor mint/burn amounts from the instruction against those +/// from zk-proofs +#[cfg(feature = "zk-ops")] +pub fn validate_auditor_ciphertext( + confidential_mint: &ConfidentialTransferMint, + proof_context: &impl AuditableProofContextInfo, + auditor_lo: &ElGamalCiphertext, + auditor_hi: &ElGamalCiphertext, +) -> Result<(), ProgramError> { + if let Some(auditor_pk) = + Into::>::into(confidential_mint.auditor_elgamal_pubkey) + { + // Check that the auditor encryption public key is consistent with what was + // actually used to generate the zkp. + if proof_context.auditor_pubkey() != &auditor_pk { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } + + if auditor_lo != &proof_context.auditor_amount_lo() { + return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); + } + if auditor_hi != &proof_context.auditor_amount_hi() { + return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); + } + } + + Ok(()) +} diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index 5f2dc698c7e..1e535a5ffd0 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -5,7 +5,6 @@ use solana_zk_token_sdk::encryption::auth_encryption::AeCiphertext; pub use solana_zk_token_sdk::{ zk_token_proof_instruction::*, zk_token_proof_state::ProofContextState, }; - #[cfg(feature = "serde-traits")] use { crate::serialization::aeciphertext_fromstr, diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index d009b096e7c..cd2728924c3 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -748,40 +748,6 @@ pub fn process_source_for_transfer( Ok(()) } -/// Validates the auditor transfer amounts from the instruction against those from zk-proofs -#[allow(clippy::too_many_arguments)] -#[cfg(feature = "zk-ops")] -pub fn validate_auditor_ciphertext( - confidential_mint: &ConfidentialTransferMint, - maybe_proof_context: Option<&TransferProofContextInfo>, - auditor_hi: &ElGamalCiphertext, - auditor_lo: &ElGamalCiphertext, -) -> ProgramResult { - if let Some(proof_context) = maybe_proof_context { - if let Some(auditor_pk) = confidential_mint.auditor_elgamal_pubkey.into() { - // Check that the auditor encryption public key is consistent with what was - // actually used to generate the zkp. - if proof_context.transfer_pubkeys.auditor != auditor_pk { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } - - let auditor_transfer_amount_lo = - transfer_amount_auditor_ciphertext(&proof_context.ciphertext_lo); - let auditor_transfer_amount_hi = - transfer_amount_auditor_ciphertext(&proof_context.ciphertext_hi); - - if auditor_hi != &auditor_transfer_amount_hi { - return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); - } - if auditor_lo != &auditor_transfer_amount_lo { - return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); - } - } - } - - Ok(()) -} - #[cfg(feature = "zk-ops")] fn process_destination_for_transfer( destination_account_info: &AccountInfo, diff --git a/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs b/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs index f9ce356c3a8..f68c4e15ccf 100644 --- a/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs +++ b/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs @@ -4,27 +4,29 @@ //! The logic in this submodule should belong to the `solana-zk-token-sdk` and //! will be removed with the next upgrade to the Solana program. -use solana_zk_token_sdk::encryption::pedersen::PedersenOpening; - -use crate::{ - extension::confidential_transfer::{ - ciphertext_extraction::{transfer_amount_source_ciphertext, SourceDecryptHandles}, - processor::verify_and_split_deposit_amount, - *, - }, - solana_zk_token_sdk::{ - encryption::{ - auth_encryption::{AeCiphertext, AeKey}, - elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, - grouped_elgamal::GroupedElGamal, - pedersen::Pedersen, +use { + crate::{ + extension::confidential_transfer::{ + ciphertext_extraction::{transfer_amount_source_ciphertext, SourceDecryptHandles}, + processor::verify_and_split_deposit_amount, + *, }, - instruction::{ - transfer::TransferAmountCiphertext, BatchedGroupedCiphertext2HandlesValidityProofData, - BatchedRangeProofU128Data, CiphertextCommitmentEqualityProofData, + solana_zk_token_sdk::{ + encryption::{ + auth_encryption::{AeCiphertext, AeKey}, + elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + grouped_elgamal::GroupedElGamal, + pedersen::Pedersen, + }, + instruction::{ + transfer::TransferAmountCiphertext, + BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCommitmentEqualityProofData, + }, + zk_token_elgamal::ops::subtract_with_lo_hi, }, - zk_token_elgamal::ops::subtract_with_lo_hi, }, + solana_zk_token_sdk::encryption::pedersen::PedersenOpening, }; /// The main logic to create the three split proof data for a transfer. diff --git a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs index 428af485072..a3e91ac725e 100644 --- a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs @@ -8,7 +8,11 @@ use { proof::decode_proof_instruction_context, }, solana_program::{ - account_info::{next_account_info, AccountInfo}, msg, program::invoke, program_error::ProgramError, sysvar::instructions::get_instruction_relative + account_info::{next_account_info, AccountInfo}, + msg, + program::invoke, + program_error::ProgramError, + sysvar::instructions::get_instruction_relative, }, solana_zk_token_sdk::zk_token_proof_instruction::{self, ContextStateInfo}, std::slice::Iter, @@ -511,7 +515,7 @@ pub fn verify_transfer_with_fee_proof( /// Verify and process equality proof for [Transfer] and [TransferWithFee] /// instructions. -fn verify_equality_proof( +pub fn verify_equality_proof( account_info: &AccountInfo<'_>, ) -> Result { check_zk_token_proof_program_account(account_info.owner)?; @@ -548,7 +552,7 @@ pub fn verify_ciphertext_validity_proof( } /// Verify and process range proof for [Transfer] instruction. -fn verify_transfer_range_proof( +pub fn verify_transfer_range_proof( account_info: &AccountInfo<'_>, ) -> Result { check_zk_token_proof_program_account(account_info.owner)?; From fee8acb517dbf1e4dc0995ec6277d3fab102b0d7 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Fri, 5 Jul 2024 08:05:50 -0300 Subject: [PATCH 04/34] add confidential supply --- libraries/pod/src/optional_keys.rs | 8 + token/cli/src/clap_app.rs | 112 +++++++++ token/cli/src/command.rs | 104 +++++++- token/cli/src/encryption_keypair.rs | 3 + token/client/src/token.rs | 151 ++++++++++-- .../ciphertext_extraction.rs | 96 ++++++-- .../confidential_mint_burn/instruction.rs | 184 ++++++++++---- .../extension/confidential_mint_burn/mod.rs | 10 +- .../confidential_mint_burn/processor.rs | 229 ++++++++++++------ .../proof_generation.rs | 48 +++- .../confidential_mint_burn/verify_proof.rs | 51 ++-- .../confidential_transfer/account_info.rs | 2 - .../split_proof_generation.rs | 38 ++- 13 files changed, 804 insertions(+), 232 deletions(-) diff --git a/libraries/pod/src/optional_keys.rs b/libraries/pod/src/optional_keys.rs index ad71b1bcc4d..535d3c0dfde 100644 --- a/libraries/pod/src/optional_keys.rs +++ b/libraries/pod/src/optional_keys.rs @@ -163,6 +163,14 @@ impl From for Option { } } } +impl OptionalNonZeroElGamalPubkey { + pub fn is_none(&self) -> bool { + self.0 == ElGamalPubkey::default() + } + pub fn is_some(&self) -> bool { + self.0 != ElGamalPubkey::default() + } +} #[cfg(any(feature = "serde-traits", test))] const OPTIONAL_NONZERO_ELGAMAL_PUBKEY_LEN: usize = 32; diff --git a/token/cli/src/clap_app.rs b/token/cli/src/clap_app.rs index 66540fa0305..1e7b79b5b6f 100644 --- a/token/cli/src/clap_app.rs +++ b/token/cli/src/clap_app.rs @@ -136,6 +136,8 @@ pub enum CommandName { MintConfidentialTokens, BurnConfidentialTokens, ConfidentialBalance, + ConfidentialSupply, + RotateSupplyElgamal, } impl fmt::Display for CommandName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -830,6 +832,34 @@ pub fn app<'a, 'b>( "Enables minting of new tokens into confidential balance and burning of tokens directly from the confidential balance" ), ) + .arg( + Arg::with_name("auditor_pubkey") + .long("auditor-pubkey") + .value_name("AUDITOR_PUBKEY") + .takes_value(true) + .help( + "The auditor encryption public key for mints with the confidential \ + transfer extension enabled. The corresponding private key for \ + this auditor public key can be used to decrypt all confidential \ + transfers involving tokens from this mint. Currently, the auditor \ + public key can only be specified as a direct *base64* encoding of \ + an ElGamal public key. More methods of specifying the auditor public \ + key will be supported in a future version. To disable auditability \ + feature for the token, use \"none\"." + ) + ) + .arg( + Arg::with_name("confidential_supply_pubkey") + .long("confidential-supply-pubkey") + .value_name("CONFIDENTIAL_SUPPLY_PUBKEY") + .takes_value(true) + .help( + "The confidential supply encryption public key for mints with the \ + confidential transfer and confidential mint-burn extension enabled. \ + The corresponding private key for this supply public key can be \ + used to decrypt the confidential supply of the token." + ) + ) .nonce_args(true) .arg(memo_arg()) ) @@ -2731,4 +2761,86 @@ pub fn app<'a, 'b>( .arg(mint_decimals_arg()) .nonce_args(true) ) + .subcommand( + SubCommand::with_name(CommandName::ConfidentialSupply.into()) + .about("Display supply of confidential token") + .arg( + Arg::with_name("token") + .long("token") + .validator(is_valid_pubkey) + .value_name("TOKEN_MINT_ADDRESS") + .takes_value(true) + .index(1) + .required(true) + .help("The token address with confidential transfers enabled"), + ) + .arg( + Arg::with_name("authority") + .long("authority") + .alias("owner") + .validator(is_valid_signer) + .value_name("SIGNER") + .takes_value(true) + .help("Keypair from which the supply elgamal keypair is derived. \ + Either the authority or the confidential-supply-keypair have \ + to be specified in order for the supply to be decrypted.") + ) + .arg( + Arg::with_name("confidential_supply_keypair") + .long("confidential-supply-keypair") + .value_name("CONFIDENTIAL_SUPPLY_KEYPAIR") + .takes_value(true) + .help( + "The confidential supply encryption keypair used to decrypt the supply. \ + Either the authority or the confidential-supply-keypair have \ + to be specified in order for the supply to be decrypted." + ) + ) + .nonce_args(true) + ) + .subcommand( + SubCommand::with_name(CommandName::RotateSupplyElgamal.into()) + .about("Display supply of confidential token") + .arg( + Arg::with_name("token") + .long("token") + .validator(is_valid_pubkey) + .value_name("TOKEN_MINT_ADDRESS") + .takes_value(true) + .index(1) + .required(true) + .help("The token address with confidential transfers enabled"), + ) + .arg( + Arg::with_name("authority") + .long("authority") + .alias("owner") + .validator(is_valid_signer) + .value_name("SIGNER") + .takes_value(true) + .required(true) + .help("Keypair holding the authority over the confidential-mint-burn extension.") + ) + .arg( + Arg::with_name("current_supply_keypair") + .long("current-supply-keypair") + .value_name("CURRENT_SUPPLY_KEYPAIR") + .takes_value(true) + .required(true) + .help( + "The current confidential supply encryption keypair." + ) + ) + .arg( + Arg::with_name("new_supply_keypair") + .long("new-supply-keypair") + .value_name("NEW_SUPPLY_KEYPAIR") + .takes_value(true) + .required(true) + .help( + "The new confidential supply encryption keypair to rotate to." + ) + ) + .nonce_args(true) + ) } diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index c9780940460..d9a6d99ea3f 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -254,6 +254,8 @@ async fn command_create_token( enable_member: bool, bulk_signers: Vec>, enable_confidential_mint_burn: bool, + auditor_pubkey: ElGamalPubkeyOrNone, + confidential_supply_pubkey: ElGamalPubkeyOrNone, ) -> CommandResult { println_display( config, @@ -300,10 +302,6 @@ async fn command_create_token( extensions.push(ExtensionInitializationParams::DefaultAccountState { state }) } - if enable_confidential_mint_burn { - extensions.push(ExtensionInitializationParams::ConfidentialMintBurnMint { authority }); - } - if let Some((transfer_fee_basis_points, maximum_fee)) = transfer_fee { extensions.push(ExtensionInitializationParams::TransferFeeConfig { transfer_fee_config_authority: Some(authority), @@ -317,7 +315,7 @@ async fn command_create_token( extensions.push(ExtensionInitializationParams::ConfidentialTransferMint { authority: Some(authority), auto_approve_new_accounts: auto_approve, - auditor_elgamal_pubkey: None, + auditor_elgamal_pubkey: auditor_pubkey.into(), }); if transfer_fee.is_some() { // Deriving ElGamal key from default signer. Custom ElGamal keys @@ -336,6 +334,13 @@ async fn command_create_token( } } + if enable_confidential_mint_burn { + extensions.push(ExtensionInitializationParams::ConfidentialMintBurnMint { + authority, + confidential_supply_pubkey: confidential_supply_pubkey.into(), + }); + } + if let Some(program_id) = transfer_hook_program_id { extensions.push(ExtensionInitializationParams::TransferHook { authority: Some(authority), @@ -1612,7 +1617,6 @@ async fn command_transfer( ciphertext_validity_proof_data, range_proof_data, source_decrypt_handles, - _, ) = transfer_account_info .generate_split_transfer_proof_data( transfer_balance, @@ -3400,10 +3404,15 @@ async fn command_deposit_withdraw_mint_confidential_tokens( let mint_to_elgamal_pubkey = token.account_elgamal_pubkey(&token_account_address).await?; - let auditor_elgamal_pubkey = token.auditor_elgamal().await?; + let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await?; + let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await?; - let (range_proof, ciphertext_validity_proof, pedersen_openings) = - generate_mint_proofs(amount, &mint_to_elgamal_pubkey, &auditor_elgamal_pubkey)?; + let (range_proof, ciphertext_validity_proof, pedersen_openings) = generate_mint_proofs( + amount, + &mint_to_elgamal_pubkey, + &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, + )?; let context_state_auth = payer.pubkey(); let _ = try_join!( @@ -3413,7 +3422,7 @@ async fn command_deposit_withdraw_mint_confidential_tokens( &range_proof, &range_proof_context_state_account, ), - token.create_batched_grouped_2_handles_ciphertext_validity_proof_context_state( + token.create_batched_grouped_3_handles_ciphertext_validity_proof_context_state( &ciphertext_validity_proof_context_pubkey, &context_state_auth, &ciphertext_validity_proof, @@ -3432,6 +3441,7 @@ async fn command_deposit_withdraw_mint_confidential_tokens( &owner, amount, auditor_elgamal_pubkey, + supply_elgamal_pubkey, range_proof_location, ciphertext_validity_proof_location, &pedersen_openings, @@ -3591,6 +3601,11 @@ pub async fn process_command<'a>( .value_of("enable_confidential_transfers") .map(|b| b == "auto"); + let auditor_elgamal_pubkey = + elgamal_pubkey_or_none(arg_matches, "auditor_pubkey").unwrap(); + let confidential_supply_pubkey = + elgamal_pubkey_or_none(arg_matches, "confidential_supply_pubkey").unwrap(); + command_create_token( config, decimals, @@ -3614,6 +3629,8 @@ pub async fn process_command<'a>( arg_matches.is_present("enable_member"), bulk_signers, arg_matches.is_present("enable_confidential_mint_burn"), + auditor_elgamal_pubkey, + confidential_supply_pubkey, ) .await } @@ -4695,6 +4712,62 @@ pub async fn process_command<'a>( config.signer_or_default(arg_matches, "authority", &mut wallet_manager); command_confidential_balance(config, address, auth_signer).await } + (CommandName::ConfidentialSupply, arg_matches) => { + let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) + .unwrap() + .unwrap(); + let elgamal_keypair = if arg_matches.is_present("confidential_supply_keypair") { + elgamal_keypair_of(arg_matches, "confidential_supply_keypair").unwrap() + } else { + let (auth_signer, _auth) = + config.signer_or_default(arg_matches, "authority", &mut wallet_manager); + + ElGamalKeypair::new_from_signer(&*auth_signer, b"").unwrap() + }; + + let token_cl = token_client_from_config(config, &token, None)?; + let supply = token_cl + .confidential_supply(&elgamal_keypair) + .await + .map_err(|e| format!("Could not fetch confidential supply for {token}: {e}",))?; + + Ok(format!("Supply of {token} is {supply}")) + } + (CommandName::RotateSupplyElgamal, arg_matches) => { + let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) + .unwrap() + .unwrap(); + let cur_elgamal_keypair = + elgamal_keypair_of(arg_matches, "current_supply_keypair").unwrap(); + let new_elgamal_keypair = + elgamal_keypair_of(arg_matches, "new_supply_keypair").unwrap(); + let (auth_signer, auth) = + config.signer_or_default(arg_matches, "authority", &mut wallet_manager); + + Ok( + match finish_tx( + config, + &token_client_from_config(config, &token, None)? + .rotate_supply_elgamal( + &auth, + cur_elgamal_keypair, + new_elgamal_keypair, + &[auth_signer], + ) + .await?, + false, + ) + .await? + { + TransactionReturnData::CliSignature(signature) => { + config.output_format.formatted_string(&signature) + } + TransactionReturnData::CliSignOnlyData(sign_only_data) => { + config.output_format.formatted_string(&sign_only_data) + } + }, + ) + } } } @@ -4780,7 +4853,8 @@ async fn command_confidential_burn( .map(|ui_amount| spl_token::ui_amount_to_amount(ui_amount, mint_info.decimals)) .unwrap(); - let auditor_elgamal_pubkey = token.auditor_elgamal().await?; + let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await?; + let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await?; let context_state_authority = config.fee_payer()?; let equality_proof_context_state_account = Keypair::new(); @@ -4819,6 +4893,7 @@ async fn command_confidential_burn( elgamal_keypair, aes_key, &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, ) .unwrap(); @@ -4834,8 +4909,9 @@ async fn command_confidential_burn( &equality_proof_data, &equality_proof_context_state_account, ), - token.create_ciphertext_validity_proof_context_state_for_transfer( - context_state_accounts, + token.create_batched_grouped_3_handles_ciphertext_validity_proof_context_state( + context_state_accounts.ciphertext_validity_proof, + context_state_accounts.authority, &ciphertext_validity_proof_data, &ciphertext_validity_proof_context_state_account, ) @@ -4848,6 +4924,8 @@ async fn command_confidential_burn( &authority, context_state_accounts, burn_amount, + auditor_elgamal_pubkey, + supply_elgamal_pubkey, aes_key, &bulk_signers, &pedersen_openings, diff --git a/token/cli/src/encryption_keypair.rs b/token/cli/src/encryption_keypair.rs index f2b2d733237..d8067b4798a 100644 --- a/token/cli/src/encryption_keypair.rs +++ b/token/cli/src/encryption_keypair.rs @@ -32,6 +32,9 @@ pub(crate) fn elgamal_pubkey_or_none( matches: &ArgMatches, name: &str, ) -> Result { + if !matches.is_present(name) { + return Ok(ElGamalPubkeyOrNone::None); + } let arg_str = matches.value_of(name).unwrap(); if arg_str == "none" { return Ok(ElGamalPubkeyOrNone::None); diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 26f7f79765d..00763af61e8 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -30,7 +30,7 @@ use { }, spl_token_2022::{ extension::{ - confidential_mint_burn::{self}, + confidential_mint_burn::{self, ConfidentialMintBurn}, confidential_transfer::{ self, account_info::{ @@ -188,6 +188,7 @@ pub enum ExtensionInitializationParams { }, ConfidentialMintBurnMint { authority: Pubkey, + confidential_supply_pubkey: Option, }, } impl ExtensionInitializationParams { @@ -318,13 +319,15 @@ impl ExtensionInitializationParams { authority, member_address, ), - Self::ConfidentialMintBurnMint { authority } => { - confidential_mint_burn::instruction::initialize_mint( - token_program_id, - mint, - authority, - ) - } + Self::ConfidentialMintBurnMint { + authority, + confidential_supply_pubkey, + } => confidential_mint_burn::instruction::initialize_mint( + token_program_id, + mint, + authority, + confidential_supply_pubkey, + ), } } } @@ -2354,7 +2357,6 @@ where ciphertext_validity_proof_data, range_proof_data, source_decrypt_handles, - _, ) = account_info .generate_split_transfer_proof_data( transfer_amount, @@ -4069,7 +4071,7 @@ where self.process_ixs(&instructions, signing_keypairs).await } - pub async fn auditor_elgamal(&self) -> TokenResult> { + pub async fn auditor_elgamal_pubkey(&self) -> TokenResult> { Ok( TryInto::>::try_into( self.get_mint_info() @@ -4100,10 +4102,11 @@ where authority: &Pubkey, amount: u64, auditor_elgamal_pubkey: Option, + supply_elgamal_pubkey: Option, range_proof_location: ProofLocation<'_, BatchedRangeProofU64Data>, ciphertext_validity_proof_location: ProofLocation< '_, - BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofData, >, pedersen_openings: &(PedersenOpening, PedersenOpening), signing_keypairs: &S, @@ -4117,6 +4120,7 @@ where &self.pubkey, amount, auditor_elgamal_pubkey, + supply_elgamal_pubkey, authority, &multisig_signers, range_proof_location, @@ -4211,6 +4215,49 @@ where .await } + /// Create a ciphertext validity proof context state account for mint + pub async fn create_batched_grouped_3_handles_ciphertext_validity_proof_context_state< + S: Signer, + >( + &self, + proof_pubkey: &Pubkey, + proof_authority: &Pubkey, + ciphertext_validity_proof_data: &BatchedGroupedCiphertext3HandlesValidityProofData, + ciphertext_validity_proof_signer: &S, + ) -> TokenResult { + let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity; + let space = + size_of::>(); + let rent = self + .client + .get_minimum_balance_for_rent_exemption(space) + .await + .map_err(TokenError::Client)?; + + let ciphertext_validity_proof_context_state_info = ContextStateInfo { + context_state_account: proof_pubkey, + context_state_authority: proof_authority, + }; + + self.process_ixs( + &[ + system_instruction::create_account( + &self.payer.pubkey(), + proof_pubkey, + rent, + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof( + Some(ciphertext_validity_proof_context_state_info), + ciphertext_validity_proof_data, + ), + ], + &[ciphertext_validity_proof_signer], + ) + .await + } + /// Burn SPL Tokens from the available balance of a confidential token /// account #[allow(clippy::too_many_arguments)] @@ -4220,6 +4267,8 @@ where authority: &Pubkey, context_state_accounts: TransferSplitContextStateAccounts<'_>, amount: u64, + auditor_elgamal_pubkey: Option, + supply_elgamal_pubkey: Option, aes_key: &AeKey, signing_keypairs: &S, pedersen_openings: &(PedersenOpening, PedersenOpening), @@ -4235,20 +4284,14 @@ where let new_decryptable_available_balance = account_info .new_decryptable_available_balance(amount, aes_key) .map_err(|_| TokenError::AccountDecryption)?; - let mint_info = self.get_mint_info().await?; self.process_ixs( &confidential_mint_burn::instruction::confidential_burn_with_split_proofs( &self.program_id, ata_pubkey, self.get_address(), - TryInto::>::try_into( - mint_info - .get_extension::()? - .auditor_elgamal_pubkey, - ) - .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData))? - .map(|pk| TryInto::::try_into(pk).unwrap()), + auditor_elgamal_pubkey, + supply_elgamal_pubkey, amount, new_decryptable_available_balance.into(), context_state_accounts, @@ -4260,4 +4303,74 @@ where ) .await } + + /// Burn SPL Tokens from the available balance of a confidential token + /// account + #[allow(clippy::too_many_arguments)] + pub async fn confidential_supply( + &self, + supply_elgamal_keypair: &ElGamalKeypair, + ) -> Result { + let mint = self.get_mint_info().await?; + let confidential_mint_burn_ext = mint.get_extension::()?; + + // Currently only correctly displays supplies until u32::MAX, + // the supply ciphertext will still be correct after, but it + // can't be decrypted here until a corresponding decryption + // method is added to the solana-zk-token-sdk + Ok(supply_elgamal_keypair + .secret() + .decrypt_u32( + &TryInto::::try_into( + confidential_mint_burn_ext.confidential_supply, + ) + .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData))?, + ) + .unwrap_or_default()) + } + + pub async fn supply_elgamal_pubkey(&self) -> TokenResult> { + Ok( + TryInto::>::try_into( + self.get_mint_info() + .await? + .get_extension::()? + .supply_elgamal_pubkey, + ) + .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData))? + .map(|pk| TryInto::::try_into(pk).unwrap()), + ) + } + + /// Rotate the elgamal pubkey encrypting the confidential supply + #[allow(clippy::too_many_arguments)] + pub async fn rotate_supply_elgamal( + &self, + authority: &Pubkey, + supply_elgamal_keypair: ElGamalKeypair, + new_supply_elgamal_keypair: ElGamalKeypair, + signing_keypairs: &S, + ) -> TokenResult { + let signing_pubkeys = signing_keypairs.pubkeys(); + let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); + + let mint = self.get_mint_info().await?; + let extension_state = mint.get_extension::()?; + let current_supply = self.confidential_supply(&supply_elgamal_keypair).await?; + + self.process_ixs( + &confidential_mint_burn::instruction::rotate_supply_elgamal( + &self.program_id, + self.get_address(), + authority, + &multisig_signers, + extension_state, + current_supply, + supply_elgamal_keypair, + new_supply_elgamal_keypair, + )?, + signing_keypairs, + ) + .await + } } diff --git a/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs b/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs index 925a4e1c8dc..eacee796642 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs @@ -1,13 +1,12 @@ #[cfg(feature = "zk-ops")] use crate::{ error::TokenError, - extension::confidential_transfer::ciphertext_extraction::extract_commitment_from_grouped_ciphertext, solana_program::program_error::ProgramError, solana_zk_token_sdk::{ instruction::{ - BatchedGroupedCiphertext2HandlesValidityProofContext, BatchedRangeProofContext, + BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext, }, - zk_token_elgamal::pod::GroupedElGamalCiphertext2Handles, + zk_token_elgamal::pod::GroupedElGamalCiphertext3Handles, }, }; #[cfg(feature = "zk-ops")] @@ -15,6 +14,7 @@ use bytemuck::{Pod, Zeroable}; #[cfg(feature = "zk-ops")] #[cfg(not(target_os = "solana"))] use solana_zk_token_sdk::encryption::grouped_elgamal::GroupedElGamalCiphertext; +use solana_zk_token_sdk::zk_token_elgamal::pod::PedersenCommitment; #[cfg(feature = "zk-ops")] use solana_zk_token_sdk::{ instruction::CiphertextCommitmentEqualityProofContext, @@ -24,11 +24,11 @@ use solana_zk_token_sdk::{ /// Wrapper for `GroupedElGamalCiphertext2Handles` when used during minting #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] -pub struct MintBurnAmountCiphertext(pub(crate) GroupedElGamalCiphertext2Handles); +pub struct MintBurnAmountCiphertext(pub(crate) GroupedElGamalCiphertext3Handles); #[cfg(not(target_os = "solana"))] -impl From> for MintBurnAmountCiphertext { - fn from(value: GroupedElGamalCiphertext<2>) -> Self { +impl From> for MintBurnAmountCiphertext { + fn from(value: GroupedElGamalCiphertext<3>) -> Self { Self(value.into()) } } @@ -48,9 +48,11 @@ pub trait AuditableProofContextInfo { #[cfg(feature = "zk-ops")] pub struct MintProofContextInfo { /// destination elgamal pubkey used in proof generation - pub destination_pubkey: ElGamalPubkey, + pub mint_to_pubkey: ElGamalPubkey, /// auditor elgamal pubkey used in proof generation pub auditor_pubkey: ElGamalPubkey, + /// supply elgamal pubkey used in proof generation + pub supply_pubkey: ElGamalPubkey, /// Ciphertext containing the low 16 bits of the mint amount pub ciphertext_lo: MintBurnAmountCiphertext, /// Ciphertext containing the high 32 bits of the mint amount @@ -77,6 +79,8 @@ pub struct BurnProofContextInfo { pub burner_pubkey: ElGamalPubkey, /// auditor elgamal pubkey used in proof generation pub auditor_pubkey: ElGamalPubkey, + /// supply elgamal pubkey used in proof generation + pub supply_pubkey: ElGamalPubkey, /// Ciphertext containing the low 16 bits of the burn amount pub ciphertext_lo: MintBurnAmountCiphertext, /// Ciphertext containing the high 32 bits of the burn amount @@ -104,7 +108,7 @@ impl MintProofContextInfo { /// [ConfidentialMint] instruction from context state accounts /// after verifying their consistency. pub fn verify_and_extract( - ciphertext_validity_proof_context: &BatchedGroupedCiphertext2HandlesValidityProofContext, + ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext, range_proof_context: &BatchedRangeProofContext, ) -> Result { // The ciphertext validity proof context consists of the destination ElGamal @@ -113,9 +117,13 @@ impl MintProofContextInfo { // `MintProofContextInfo`. In addition, the commitments pertaining // to the mint amount ciphertexts should be checked with range proof for // consistency. - let BatchedGroupedCiphertext2HandlesValidityProofContext { - destination_pubkey, - auditor_pubkey, + let BatchedGroupedCiphertext3HandlesValidityProofContext { + source_pubkey: mint_to_pubkey, + // the orignal proof context member names were given with transfers + // in mind as this was it's only usage, so the remapping here looks + // a bit confusing + destination_pubkey: auditor_pubkey, + auditor_pubkey: supply_pubkey, grouped_ciphertext_lo: mint_amount_ciphertext_lo, grouped_ciphertext_hi: mint_amount_ciphertext_hi, } = ciphertext_validity_proof_context; @@ -134,9 +142,9 @@ impl MintProofContextInfo { // check that the range proof was created for the correct set of Pedersen // commitments let mint_amount_commitment_lo = - extract_commitment_from_grouped_ciphertext(mint_amount_ciphertext_lo); + extract_commitment_from_3_grouped_ciphertext(mint_amount_ciphertext_lo); let mint_amount_commitment_hi = - extract_commitment_from_grouped_ciphertext(mint_amount_ciphertext_hi); + extract_commitment_from_3_grouped_ciphertext(mint_amount_ciphertext_hi); let expected_commitments = [mint_amount_commitment_lo, mint_amount_commitment_hi]; @@ -168,8 +176,9 @@ impl MintProofContextInfo { } Ok(Self { - destination_pubkey: *destination_pubkey, + mint_to_pubkey: *mint_to_pubkey, auditor_pubkey: *auditor_pubkey, + supply_pubkey: *supply_pubkey, ciphertext_lo: MintBurnAmountCiphertext(*mint_amount_ciphertext_lo), ciphertext_hi: MintBurnAmountCiphertext(*mint_amount_ciphertext_hi), }) @@ -183,7 +192,7 @@ impl BurnProofContextInfo { /// their consistency. pub fn verify_and_extract( equality_proof_context: &CiphertextCommitmentEqualityProofContext, - ciphertext_validity_proof_context: &BatchedGroupedCiphertext2HandlesValidityProofContext, + ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext, range_proof_context: &BatchedRangeProofContext, ) -> Result { // The equality proof context consists of the source ElGamal public key, the new @@ -203,9 +212,11 @@ impl BurnProofContextInfo { // `TransferProofContextInfo`. In addition, the commitments pertaining // to the transfer amount ciphertexts should be checked with range proof for // consistency. - let BatchedGroupedCiphertext2HandlesValidityProofContext { - destination_pubkey: burner_pubkey, - auditor_pubkey, + let BatchedGroupedCiphertext3HandlesValidityProofContext { + source_pubkey: burner_pubkey, + // see MintProofContextInfo::verify_and_extract + destination_pubkey: auditor_pubkey, + auditor_pubkey: supply_pubkey, grouped_ciphertext_lo: transfer_amount_ciphertext_lo, grouped_ciphertext_hi: transfer_amount_ciphertext_hi, } = ciphertext_validity_proof_context; @@ -228,9 +239,9 @@ impl BurnProofContextInfo { // check that the range proof was created for the correct set of Pedersen // commitments let transfer_amount_commitment_lo = - extract_commitment_from_grouped_ciphertext(transfer_amount_ciphertext_lo); + extract_commitment_from_3_grouped_ciphertext(transfer_amount_ciphertext_lo); let transfer_amount_commitment_hi = - extract_commitment_from_grouped_ciphertext(transfer_amount_ciphertext_hi); + extract_commitment_from_3_grouped_ciphertext(transfer_amount_ciphertext_hi); let expected_commitments = [ *new_source_commitment, @@ -271,6 +282,7 @@ impl BurnProofContextInfo { Ok(Self { burner_pubkey: *burner_pubkey, auditor_pubkey: *auditor_pubkey, + supply_pubkey: *supply_pubkey, ciphertext_lo: MintBurnAmountCiphertext(*transfer_amount_ciphertext_lo), ciphertext_hi: MintBurnAmountCiphertext(*transfer_amount_ciphertext_hi), new_burner_ciphertext: *new_source_ciphertext, @@ -278,7 +290,7 @@ impl BurnProofContextInfo { } } -/// Extract the mint amount ciphertext encrypted under the auditor ElGamal +/// Extract the amount ciphertext encrypted under the auditor ElGamal /// public key. /// /// A mint amount ciphertext consists of the following 32-byte components @@ -288,6 +300,8 @@ impl BurnProofContextInfo { /// source public key. /// 3. The `decryption handle` component with respect to the auditor public /// key. +/// 4. The `decryption handle` component with respect to the supply public +/// key. /// /// An ElGamal ciphertext for the auditor consists of the `commitment` component /// and the `decryption handle` component with respect to the auditor. @@ -303,7 +317,7 @@ pub fn mint_burn_amount_auditor_ciphertext( ElGamalCiphertext(auditor_ciphertext_bytes) } -/// Extract the mint amount ciphertext encrypted under the destination or source +/// Extract the amount ciphertext encrypted under the destination or source /// ElGamal public key, for mint and burn respectively. /// /// Structure see `mint_amount_auditor_ciphertext` @@ -318,3 +332,41 @@ pub fn mint_burn_amount_target_ciphertext( ElGamalCiphertext(destination_ciphertext_bytes) } + +/// Extract the amount ciphertext encrypted under the supply ElGamal +/// public key, for mint and burn respectively. +/// +/// Structure see `mint_amount_auditor_ciphertext` +pub fn mint_burn_amount_supply_ciphertext( + transfer_amount_ciphertext: &MintBurnAmountCiphertext, +) -> ElGamalCiphertext { + let transfer_amount_ciphertext_bytes = bytemuck::bytes_of(transfer_amount_ciphertext); + + let mut destination_ciphertext_bytes = [0u8; 64]; + destination_ciphertext_bytes[..32].copy_from_slice(&transfer_amount_ciphertext_bytes[..32]); + destination_ciphertext_bytes[32..].copy_from_slice(&transfer_amount_ciphertext_bytes[96..128]); + + ElGamalCiphertext(destination_ciphertext_bytes) +} + +/// Extract the commitment component from a grouped ciphertext with 3 handles. +/// +/// A grouped ciphertext with 2 handles consists of the following 32-bytes +/// components that are serialized in order: +/// 1. The `commitment` component that encodes the fee amount. +/// 3. The `decryption handle` component with respect to the target account's +/// public key. +/// 4. The `decryption handle` component with respect to the auditor's public +/// key +/// 5. The `decryption handle` component with respect to the supply's public +/// key +/// +/// The fee commitment component consists of the first 32-byte. +pub(crate) fn extract_commitment_from_3_grouped_ciphertext( + transfer_amount_ciphertext: &GroupedElGamalCiphertext3Handles, +) -> PedersenCommitment { + let transfer_amount_ciphertext_bytes = bytemuck::bytes_of(transfer_amount_ciphertext); + let transfer_amount_commitment_bytes = + transfer_amount_ciphertext_bytes[..32].try_into().unwrap(); + PedersenCommitment(transfer_amount_commitment_bytes) +} diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 8b0b1a5f059..430d0d9ed23 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -8,22 +8,23 @@ use crate::{ use serde::{Deserialize, Serialize}; #[cfg(not(target_os = "solana"))] use solana_zk_token_sdk::instruction::{ - BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU64Data, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU64Data, + CiphertextCiphertextEqualityProofData, }; #[cfg(not(target_os = "solana"))] use solana_zk_token_sdk::{ - encryption::{elgamal::ElGamalPubkey, pedersen::PedersenOpening}, - zk_token_proof_instruction::{verify_batched_verify_range_proof_u64, ProofInstruction}, -}; -use { - crate::extension::confidential_transfer::DecryptableBalance, - bytemuck::{Pod, Zeroable}, - num_enum::{IntoPrimitive, TryFromPrimitive}, - solana_program::pubkey::Pubkey, - solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, + encryption::{ + elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + pedersen::PedersenOpening, + }, + zk_token_proof_instruction::{ + verify_batched_verify_range_proof_u64, verify_ciphertext_ciphertext_equality, + ProofInstruction, + }, }; #[cfg(not(target_os = "solana"))] use { + super::ConfidentialMintBurn, crate::{ check_program_account, extension::confidential_transfer::instruction::TransferSplitContextStateAccounts, @@ -34,6 +35,15 @@ use { program_error::ProgramError, sysvar, }, + solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalPubkey as PodElGamalPubkey, +}; +use { + crate::extension::confidential_transfer::DecryptableBalance, + bytemuck::{Pod, Zeroable}, + num_enum::{IntoPrimitive, TryFromPrimitive}, + solana_program::pubkey::Pubkey, + solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext as PodElGamalCiphertext, + spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, }; /// Confidential Transfer extension instructions @@ -60,35 +70,52 @@ pub enum ConfidentialMintBurnInstruction { /// `InitializeMintData` InitializeMint, /// Updates mint-authority for confidential-mint-burn mint. - UpdateMint, + UpdateAuthority, + /// Rotates the ElGamal key used to encrypt confidential supply + RotateSupplyElGamal, /// Mints confidential tokens to ConfidentialMint, /// Removes whitelist designation for specific address ConfidentialBurn, } -/// Data expected by `WhitelistedTransferInstruction::InitializeMint` +/// 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 { - /// Authority to modify the `WhitelistTransferMint` configuration and to - /// approve new accounts. + /// Authority used to modify the `ConfidentialMintBurn` mint + /// configuration and mint new tokens pub authority: Pubkey, + /// The ElGamal pubkey used to encrypt the confidential supply + pub supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, } -/// Data expected by `ConfidentialTransferInstruction::UpdateMint` +/// Data expected by `ConfidentialMintBurnInstruction::UpdateMint` #[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 UpdateMintData { - /// Determines if newly configured accounts must be approved by the - /// `authority` before they may be used by the user. +pub struct UpdateAuthorityData { + /// The pubkey the `authority` is to be rotated to pub new_authority: Pubkey, } +/// 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 RotateSupplyElGamalData { + /// The new ElGamal pubkey for supply encryption + pub new_supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, + /// The location of the + /// `ProofInstruction::VerifyCiphertextCiphertextEquality` instruction + /// relative to the `RotateSupplyElGamal` instruction in the transaction + pub proof_location: i8, +} + /// Data expected by `ConfidentialMintBurnInstruction::ConfidentialMint` #[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))] @@ -97,10 +124,10 @@ pub struct UpdateMintData { pub struct MintInstructionData { /// low 16 bits of encrypted amount to be minted, exposes mint amounts /// to the auditor through the data received via `get_transaction` - pub audit_amount_lo: ElGamalCiphertext, + pub audit_amount_lo: PodElGamalCiphertext, /// high 48 bits of encrypted amount to be minted, exposes mint amounts /// to the auditor through the data received via `get_transaction` - pub audit_amount_hi: ElGamalCiphertext, + pub audit_amount_hi: PodElGamalCiphertext, /// Relative location of the `ProofInstruction::VerifyBatchedRangeProofU64` /// instruction to the `ConfidentialMint` instruction in the /// transaction. The @@ -119,9 +146,9 @@ pub struct BurnInstructionData { #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_decryptable_available_balance: DecryptableBalance, /// low 16 bits of encrypted amount to be minted - pub auditor_lo: ElGamalCiphertext, + pub auditor_lo: PodElGamalCiphertext, /// high 48 bits of encrypted amount to be minted - pub auditor_hi: ElGamalCiphertext, + pub auditor_hi: PodElGamalCiphertext, /// Relative location of the /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction /// to the `ConfidentialBurn` instruction in the transaction. The @@ -138,6 +165,7 @@ pub fn initialize_mint( token_program_id: &Pubkey, mint: &Pubkey, authority: Pubkey, + confidential_supply_pubkey: Option, ) -> Result { check_program_account(token_program_id)?; let accounts = vec![AccountMeta::new(*mint, false)]; @@ -147,13 +175,16 @@ pub fn initialize_mint( accounts, TokenInstruction::ConfidentialMintBurnExtension, ConfidentialMintBurnInstruction::InitializeMint, - &InitializeMintData { authority }, + &InitializeMintData { + authority, + supply_elgamal_pubkey: confidential_supply_pubkey.try_into()?, + }, )) } /// Create a `UpdateMint` instruction #[cfg(not(target_os = "solana"))] -pub fn update_mint( +pub fn update_authority( token_program_id: &Pubkey, mint: &Pubkey, authority: &Pubkey, @@ -172,11 +203,68 @@ pub fn update_mint( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, - ConfidentialMintBurnInstruction::UpdateMint, - &UpdateMintData { new_authority }, + ConfidentialMintBurnInstruction::UpdateAuthority, + &UpdateAuthorityData { new_authority }, )) } +/// Create a `RotateSupplyElGamal` instruction +#[allow(clippy::too_many_arguments)] +#[cfg(not(target_os = "solana"))] +pub fn rotate_supply_elgamal( + token_program_id: &Pubkey, + mint: &Pubkey, + authority: &Pubkey, + multisig_signers: &[&Pubkey], + extension_state: &ConfidentialMintBurn, + current_supply: u64, + supply_elgamal_keypair: ElGamalKeypair, + new_supply_elgamal_keypair: ElGamalKeypair, +) -> Result, 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)); + } + + let new_supply_opening = PedersenOpening::new_rand(); + let new_supply_ciphertext = new_supply_elgamal_keypair + .pubkey() + .encrypt_with(current_supply, &new_supply_opening); + + let proof_data = CiphertextCiphertextEqualityProofData::new( + &supply_elgamal_keypair, + new_supply_elgamal_keypair.pubkey(), + &ElGamalCiphertext::try_from(extension_state.confidential_supply) + .map_err(|_| TokenError::InvalidState)?, + &new_supply_ciphertext, + &new_supply_opening, + current_supply, + ) + .map_err(|_| TokenError::ProofGeneration)?; + accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); + + Ok(vec![ + encode_instruction( + token_program_id, + accounts, + TokenInstruction::ConfidentialMintBurnExtension, + ConfidentialMintBurnInstruction::RotateSupplyElGamal, + &RotateSupplyElGamalData { + new_supply_elgamal_pubkey: Some(Into::::into( + *new_supply_elgamal_keypair.pubkey(), + )) + .try_into()?, + proof_location: 1, + }, + ), + verify_ciphertext_ciphertext_equality(None, &proof_data), + ]) +} + /// Create a `ConfidentialMint` instruction #[allow(clippy::too_many_arguments)] #[cfg(not(target_os = "solana"))] @@ -186,22 +274,30 @@ pub fn confidential_mint( mint: &Pubkey, amount: u64, auditor_elgamal_pubkey: Option, + supply_elgamal_pubkey: Option, authority: &Pubkey, multisig_signers: &[&Pubkey], range_proof_location: ProofLocation<'_, BatchedRangeProofU64Data>, ciphertext_validity_proof_location: ProofLocation< '_, - BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofData, >, pedersen_openings: &(PedersenOpening, PedersenOpening), ) -> Result, ProgramError> { check_program_account(token_program_id)?; - let mut accounts = vec![ - AccountMeta::new(*token_account, false), - AccountMeta::new_readonly(*mint, false), - AccountMeta::new_readonly(*authority, multisig_signers.is_empty()), - ]; + 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)); + } + 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)); } @@ -280,6 +376,7 @@ pub fn confidential_burn_with_split_proofs( token_account: &Pubkey, mint: &Pubkey, auditor_pubkey: Option, + supply_elgamal_pubkey: Option, burn_amount: u64, new_decryptable_available_balance: DecryptableBalance, context_accounts: TransferSplitContextStateAccounts, @@ -292,6 +389,7 @@ pub fn confidential_burn_with_split_proofs( token_account, mint, auditor_pubkey, + supply_elgamal_pubkey, burn_amount, new_decryptable_available_balance, context_accounts, @@ -309,6 +407,7 @@ pub fn inner_confidential_burn_with_split_proofs( token_account: &Pubkey, mint: &Pubkey, auditor_pubkey: Option, + supply_elgamal_pubkey: Option, burn_amount: u64, new_decryptable_available_balance: DecryptableBalance, context_accounts: TransferSplitContextStateAccounts, @@ -317,17 +416,11 @@ pub fn inner_confidential_burn_with_split_proofs( pedersen_openings: &(PedersenOpening, PedersenOpening), ) -> Result { check_program_account(token_program_id)?; - let mut accounts = vec![ - AccountMeta::new(*token_account, false), - AccountMeta::new_readonly(*mint, false), - ]; - - if context_accounts - .close_split_context_state_accounts - .is_some() - { - println!("close split context accounts on execution not implemented for confidential burn"); - return Err(ProgramError::InvalidInstructionData); + 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)); } accounts.push(AccountMeta::new_readonly( @@ -359,7 +452,10 @@ pub fn inner_confidential_burn_with_split_proofs( let burn_lo = apk.encrypt_with(amount_lo, opening_lo); (burn_hi.into(), burn_lo.into()) } else { - (ElGamalCiphertext::zeroed(), ElGamalCiphertext::zeroed()) + ( + PodElGamalCiphertext::zeroed(), + PodElGamalCiphertext::zeroed(), + ) }; Ok(encode_instruction( diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index 3801d77206c..eddece3a482 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -2,6 +2,8 @@ use { crate::extension::{Extension, ExtensionType}, bytemuck::{Pod, Zeroable}, solana_program::pubkey::Pubkey, + solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, + spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, }; /// Maximum bit length of any mint or burn amount @@ -33,9 +35,13 @@ pub mod ciphertext_extraction; #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] #[repr(C)] pub struct ConfidentialMintBurn { - /// Authority to modify the `WhitelistTransferMint` configuration and to - /// approve new accounts. + /// Authority to modify the `ConfidentialMintBurnMint` configuration and to + /// mint new confidential tokens pub mint_authority: Pubkey, + /// The confidential supply of the mint + pub confidential_supply: ElGamalCiphertext, + /// The ElGamal pubkey used to encrypt the confidential supply + pub supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, } impl Extension for 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 index f7e3f9ed8b6..b5eb52585a7 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -1,8 +1,7 @@ #[cfg(feature = "zk-ops")] use { - super::ciphertext_extraction::BurnProofContextInfo, - super::verify_proof::validate_auditor_ciphertext, - crate::extension::non_transferable::NonTransferable, + super::ciphertext_extraction::mint_burn_amount_supply_ciphertext, + super::verify_proof::validate_auditor_ciphertext, super::verify_proof::verify_burn_proof, solana_zk_token_sdk::zk_token_elgamal::ops as syscall, }; use { @@ -14,21 +13,19 @@ use { ciphertext_extraction::mint_burn_amount_target_ciphertext, instruction::{ BurnInstructionData, ConfidentialMintBurnInstruction, InitializeMintData, - MintInstructionData, UpdateMintData, + MintInstructionData, RotateSupplyElGamalData, UpdateAuthorityData, }, verify_proof::verify_mint_proof, ConfidentialMintBurn, }, - confidential_transfer::{ - ConfidentialTransferAccount, ConfidentialTransferMint, DecryptableBalance, - }, + confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint}, non_transferable::NonTransferableAccount, - BaseStateWithExtensions, BaseStateWithExtensionsMut, PodStateWithExtensions, - PodStateWithExtensionsMut, + BaseStateWithExtensions, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, }, instruction::{decode_instruction_data, decode_instruction_type}, pod::{PodAccount, PodMint}, processor::Processor, + proof::decode_proof_instruction_context, }, solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -36,11 +33,18 @@ use { msg, program_error::ProgramError, pubkey::Pubkey, + sysvar::instructions::get_instruction_relative, + }, + solana_zk_token_sdk::{ + instruction::{ + CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData, + }, + zk_token_proof_instruction::ProofInstruction, }, }; /// Processes an [InitializeMint] instruction. -fn process_initialize_mint(accounts: &[AccountInfo], authority: Pubkey) -> ProgramResult { +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)?; @@ -48,14 +52,15 @@ fn process_initialize_mint(accounts: &[AccountInfo], authority: Pubkey) -> Progr let mint_data = &mut mint_info.data.borrow_mut(); let mut mint = PodStateWithExtensionsMut::::unpack_uninitialized(mint_data)?; - let mint = mint.init_extension::(true)?; + let conf_mint_burn_ext = mint.init_extension::(true)?; - mint.mint_authority = authority; + conf_mint_burn_ext.mint_authority = data.authority; + conf_mint_burn_ext.supply_elgamal_pubkey = data.supply_elgamal_pubkey; Ok(()) } -/// Processes an [UpdateMint] instruction. +/// Processes an [UpdateAuthority] instruction. fn process_update_mint( program_id: &Pubkey, accounts: &[AccountInfo], @@ -68,7 +73,7 @@ fn process_update_mint( check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow_mut(); - let mut mint = PodStateWithExtensionsMut::::unpack_uninitialized(mint_data)?; + let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; let mint = mint.get_extension_mut::()?; Processor::validate_owner( @@ -84,6 +89,74 @@ fn process_update_mint( Ok(()) } +/// Processes an [RotateSupplyElGamal] instruction. +#[cfg(feature = "zk-ops")] +fn process_rotate_supply_elgamal( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &RotateSupplyElGamalData, +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let mint_info = next_account_info(account_info_iter)?; + let authority_info = next_account_info(account_info_iter)?; + let authority_info_data_len = authority_info.data_len(); + solana_program::log::sol_log("rotate 1"); + + check_program_account(mint_info.owner)?; + let mint_data = &mut mint_info.data.borrow_mut(); + let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; + solana_program::log::sol_log("rotate 2"); + let mint = mint.get_extension_mut::()?; + solana_program::log::sol_log("rotate 3"); + + Processor::validate_owner( + program_id, + &mint.mint_authority, + authority_info, + authority_info_data_len, + account_info_iter.as_slice(), + )?; + solana_program::log::sol_log("rotate 4"); + + if data.proof_location != 1 { + return Err(ProgramError::InvalidInstructionData); + } + solana_program::log::sol_log("rotate 5"); + + let sysvar_account_info = next_account_info(account_info_iter)?; + let equality_proof_instruction = + get_instruction_relative(data.proof_location as i64, sysvar_account_info)?; + solana_program::log::sol_log("rotate 6"); + + let proof_context = *decode_proof_instruction_context::< + CiphertextCiphertextEqualityProofData, + CiphertextCiphertextEqualityProofContext, + >( + ProofInstruction::VerifyCiphertextCiphertextEquality, + &equality_proof_instruction, + )?; + solana_program::log::sol_log("rotate 7"); + + if !mint + .supply_elgamal_pubkey + .equals(&proof_context.source_pubkey) + { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } + solana_program::log::sol_log("rotate 8"); + if mint.confidential_supply != proof_context.source_ciphertext { + return Err(ProgramError::InvalidInstructionData); + } + solana_program::log::sol_log("rotate 9"); + + mint.supply_elgamal_pubkey = Some(proof_context.destination_pubkey).try_into()?; + solana_program::log::sol_log("rotate 10"); + mint.confidential_supply = proof_context.destination_ciphertext; + solana_program::log::sol_log("rotate 11"); + + Ok(()) +} + /// Processes a [ConfidentialMint] instruction. #[cfg(feature = "zk-ops")] fn process_confidential_mint( @@ -98,18 +171,13 @@ fn process_confidential_mint( let authority_info_data_len = authority_info.data_len(); check_program_account(mint_info.owner)?; - let mint_data = &mint_info.data.borrow_mut(); - let mint = PodStateWithExtensions::::unpack(mint_data)?; - - let Ok(conf_transf_ext) = mint.get_extension::() else { - msg!("confidential-mint-burn extension initialized on mint without confidential transfer extension"); - return Err(TokenError::ExtensionNotFound.into()); - }; + let mint_data = &mut mint_info.data.borrow_mut(); + let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; - let Ok(conf_mint_ext) = mint.get_extension::() else { - msg!("attempted to confidentially mint tokens on mint without confidential mint-burn extension"); - return Err(TokenError::ExtensionNotFound.into()); - }; + let auditor_elgamal_pubkey = mint + .get_extension::()? + .auditor_elgamal_pubkey; + let conf_mint_ext = mint.get_extension_mut::()?; Processor::validate_owner( program_id, @@ -119,10 +187,6 @@ fn process_confidential_mint( account_info_iter.as_slice(), )?; - if mint.get_extension::().is_ok() { - return Err(TokenError::NonTransferable.into()); - } - check_program_account(token_account_info.owner)?; let token_account_data = &mut token_account_info.data.borrow_mut(); let mut token_account = PodStateWithExtensionsMut::::unpack(token_account_data)?; @@ -145,16 +209,23 @@ fn process_confidential_mint( false, )?; + if token_account + .get_extension::() + .is_ok() + { + return Err(TokenError::NonTransferable.into()); + } + let confidential_transfer_account = token_account.get_extension_mut::()?; confidential_transfer_account.valid_as_destination()?; - if proof_context.destination_pubkey != confidential_transfer_account.elgamal_pubkey { + if proof_context.mint_to_pubkey != confidential_transfer_account.elgamal_pubkey { return Err(ProgramError::InvalidInstructionData); } validate_auditor_ciphertext( - conf_transf_ext, + &auditor_elgamal_pubkey, &proof_context, &data.audit_amount_lo, &data.audit_amount_hi, @@ -173,6 +244,18 @@ fn process_confidential_mint( confidential_transfer_account.increment_pending_balance_credit_counter()?; + // update supply + if conf_mint_ext.supply_elgamal_pubkey.is_some() { + solana_program::log::sol_log("UPDATING SUPPLY"); + let current_supply = conf_mint_ext.confidential_supply; + conf_mint_ext.confidential_supply = syscall::add_with_lo_hi( + ¤t_supply, + &mint_burn_amount_supply_ciphertext(&proof_context.ciphertext_lo), + &mint_burn_amount_supply_ciphertext(&proof_context.ciphertext_hi), + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; + } + Ok(()) } @@ -183,21 +266,18 @@ fn process_confidential_burn( accounts: &[AccountInfo], data: &BurnInstructionData, ) -> ProgramResult { - use super::verify_proof::verify_burn_proof; - 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::::unpack(mint_data)?; - let mint_data = &mint_info.data.borrow(); - let mint = PodStateWithExtensions::::unpack(mint_data)?; - - if let Err(_e) = mint.get_extension::() { - msg!("attempted to confidentially burn tokens on mint without confidential mint-burn extension"); - return Err(TokenError::ExtensionNotFound.into()); - }; + let auditor_elgamal_pubkey = mint + .get_extension::()? + .auditor_elgamal_pubkey; + let conf_mint_ext = mint.get_extension_mut::()?; // The zero-knowledge proof certifies that: // 1. the burn amount is encrypted in the correct form @@ -210,41 +290,9 @@ fn process_confidential_burn( let authority_info = next_account_info(account_info_iter)?; - process_source_for_transfer( - program_id, - token_account_info, - mint_info, - authority_info, - account_info_iter.as_slice(), - &proof_context, - data.new_decryptable_available_balance, - )?; - - validate_auditor_ciphertext( - mint.get_extension::()?, - &proof_context, - &data.auditor_lo, - &data.auditor_hi, - )?; - - Ok(()) -} - -/// Processes the changes for the sending party of a confidential transfer -#[allow(clippy::too_many_arguments)] -#[cfg(feature = "zk-ops")] -pub fn process_source_for_transfer( - program_id: &Pubkey, - source_account_info: &AccountInfo, - mint_info: &AccountInfo, - authority_info: &AccountInfo, - signers: &[AccountInfo], - proof_context: &BurnProofContextInfo, - new_source_decryptable_available_balance: DecryptableBalance, -) -> ProgramResult { - check_program_account(source_account_info.owner)?; + check_program_account(token_account_info.owner)?; let authority_info_data_len = authority_info.data_len(); - let token_account_data = &mut source_account_info.data.borrow_mut(); + let token_account_data = &mut token_account_info.data.borrow_mut(); let mut token_account = PodStateWithExtensionsMut::::unpack(token_account_data)?; if token_account .get_extension::() @@ -258,7 +306,7 @@ pub fn process_source_for_transfer( &token_account.base.owner, authority_info, authority_info_data_len, - signers, + account_info_iter.as_slice(), )?; if token_account.base.is_frozen() { @@ -299,7 +347,25 @@ pub fn process_source_for_transfer( confidential_transfer_account.available_balance = new_source_available_balance; confidential_transfer_account.decryptable_available_balance = - new_source_decryptable_available_balance; + data.new_decryptable_available_balance; + + validate_auditor_ciphertext( + &auditor_elgamal_pubkey, + &proof_context, + &data.auditor_lo, + &data.auditor_hi, + )?; + + // update supply + if conf_mint_ext.supply_elgamal_pubkey.is_some() { + let current_supply = conf_mint_ext.confidential_supply; + conf_mint_ext.confidential_supply = syscall::subtract_with_lo_hi( + ¤t_supply, + &mint_burn_amount_supply_ciphertext(&proof_context.ciphertext_lo), + &mint_burn_amount_supply_ciphertext(&proof_context.ciphertext_hi), + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; + } Ok(()) } @@ -316,13 +382,18 @@ pub(crate) fn process_instruction( ConfidentialMintBurnInstruction::InitializeMint => { msg!("ConfidentialMintBurnInstruction::InitializeMint"); let data = decode_instruction_data::(input)?; - process_initialize_mint(accounts, data.authority) + process_initialize_mint(accounts, data) } - ConfidentialMintBurnInstruction::UpdateMint => { + ConfidentialMintBurnInstruction::UpdateAuthority => { msg!("ConfidentialMintBurnInstruction::UpdateMint"); - let data = decode_instruction_data::(input)?; + let data = decode_instruction_data::(input)?; process_update_mint(program_id, accounts, data.new_authority) } + ConfidentialMintBurnInstruction::RotateSupplyElGamal => { + msg!("ConfidentialMintBurnInstruction::RotateSupplyElGamal"); + let data = decode_instruction_data::(input)?; + process_rotate_supply_elgamal(program_id, accounts, data) + } ConfidentialMintBurnInstruction::ConfidentialMint => { msg!("ConfidentialMintBurnInstruction::ConfidentialMint"); let data = decode_instruction_data::(input)?; diff --git a/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs b/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs index f0403ebb6f0..95d89b83813 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs @@ -20,7 +20,7 @@ use solana_zk_token_sdk::{ pedersen::Pedersen, }, instruction::{ - BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, BatchedRangeProofU64Data, CiphertextCommitmentEqualityProofData, }, zk_token_elgamal::ops::subtract_with_lo_hi, @@ -32,16 +32,18 @@ pub fn generate_mint_proofs( mint_amount: u64, destination_elgamal_pubkey: &ElGamalPubkey, auditor_elgamal_pubkey: &Option, + supply_elgamal_pubkey: &Option, ) -> Result< ( BatchedRangeProofU64Data, - BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofData, (PedersenOpening, PedersenOpening), ), ProgramError, > { let (amount_lo, amount_hi) = verify_and_split_deposit_amount(mint_amount)?; let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); + let supply_elgamal_pubkey = supply_elgamal_pubkey.unwrap_or_default(); const MINT_AMOUNT_LO_BIT_LENGTH: usize = 16; const MINT_AMOUNT_HI_BIT_LENGTH: usize = 32; @@ -49,15 +51,23 @@ pub fn generate_mint_proofs( // Encrypt the `lo` and `hi` transfer amounts. let mint_amount_opening_lo = PedersenOpening::new_rand(); - let mint_amount_grouped_ciphertext_lo = GroupedElGamal::<2>::encrypt_with( - [destination_elgamal_pubkey, &auditor_elgamal_pubkey], + let mint_amount_grouped_ciphertext_lo = GroupedElGamal::<3>::encrypt_with( + [ + destination_elgamal_pubkey, + &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, + ], amount_lo, &mint_amount_opening_lo, ); let mint_amount_opening_hi = PedersenOpening::new_rand(); - let mint_amount_grouped_ciphertext_hi = GroupedElGamal::<2>::encrypt_with( - [destination_elgamal_pubkey, &auditor_elgamal_pubkey], + let mint_amount_grouped_ciphertext_hi = GroupedElGamal::<3>::encrypt_with( + [ + destination_elgamal_pubkey, + &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, + ], amount_hi, &mint_amount_opening_hi, ); @@ -84,9 +94,10 @@ pub fn generate_mint_proofs( ], ) .map_err(|_| TokenError::ProofGeneration)?, - BatchedGroupedCiphertext2HandlesValidityProofData::new( + BatchedGroupedCiphertext3HandlesValidityProofData::new( destination_elgamal_pubkey, &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, &mint_amount_grouped_ciphertext_lo, &mint_amount_grouped_ciphertext_hi, amount_lo, @@ -109,10 +120,11 @@ pub fn generate_burn_proofs( source_elgamal_keypair: &ElGamalKeypair, aes_key: &AeKey, auditor_elgamal_pubkey: &Option, + supply_elgamal_pubkey: &Option, ) -> Result< ( CiphertextCommitmentEqualityProofData, - BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, (PedersenOpening, PedersenOpening), ), @@ -120,21 +132,30 @@ pub fn generate_burn_proofs( > { let burner_elgamal_pubkey = source_elgamal_keypair.pubkey(); let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); + let supply_elgamal_pubkey = supply_elgamal_pubkey.unwrap_or_default(); // Split the transfer amount into the low and high bit components. let (burn_amount_lo, burn_amount_hi) = verify_and_split_deposit_amount(burn_amount)?; // Encrypt the `lo` and `hi` transfer amounts. let burn_amount_opening_lo = PedersenOpening::new_rand(); - let burn_amount_grouped_ciphertext_lo = GroupedElGamal::<2>::encrypt_with( - [burner_elgamal_pubkey, &auditor_elgamal_pubkey], + let burn_amount_grouped_ciphertext_lo = GroupedElGamal::<3>::encrypt_with( + [ + burner_elgamal_pubkey, + &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, + ], burn_amount_lo, &burn_amount_opening_lo, ); let burn_amount_opening_hi = PedersenOpening::new_rand(); - let burn_amount_grouped_ciphertext_hi = GroupedElGamal::<2>::encrypt_with( - [burner_elgamal_pubkey, &auditor_elgamal_pubkey], + let burn_amount_grouped_ciphertext_hi = GroupedElGamal::<3>::encrypt_with( + [ + burner_elgamal_pubkey, + &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, + ], burn_amount_hi, &burn_amount_opening_hi, ); @@ -181,9 +202,10 @@ pub fn generate_burn_proofs( .map_err(|_| TokenError::ProofGeneration)?; // generate ciphertext validity data - let ciphertext_validity_proof_data = BatchedGroupedCiphertext2HandlesValidityProofData::new( + let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new( burner_elgamal_pubkey, &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, &burn_amount_grouped_ciphertext_lo, &burn_amount_grouped_ciphertext_hi, burn_amount_lo, 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 index 34cb339ec60..5cb1efa8b26 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs @@ -1,6 +1,5 @@ use { - super::ciphertext_extraction::BurnProofContextInfo, - crate::{error::TokenError, extension::confidential_transfer::ConfidentialTransferMint}, + super::ciphertext_extraction::BurnProofContextInfo, crate::error::TokenError, solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, }; #[cfg(feature = "zk-ops")] @@ -9,7 +8,7 @@ use { crate::check_system_program_account, crate::check_zk_token_proof_program_account, crate::extension::confidential_transfer::verify_proof::{ - verify_ciphertext_validity_proof, verify_equality_proof, verify_transfer_range_proof, + verify_equality_proof, verify_transfer_range_proof, }, crate::proof::decode_proof_instruction_context, solana_program::{ @@ -22,14 +21,15 @@ use { solana_zk_token_sdk::instruction::BatchedRangeProofContext, solana_zk_token_sdk::instruction::BatchedRangeProofU64Data, solana_zk_token_sdk::instruction::{ - BatchedGroupedCiphertext2HandlesValidityProofContext, - BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, + BatchedGroupedCiphertext3HandlesValidityProofContext, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, CiphertextCommitmentEqualityProofContext, CiphertextCommitmentEqualityProofData, }, solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalPubkey, solana_zk_token_sdk::zk_token_proof_instruction::{self, ContextStateInfo, ProofInstruction}, solana_zk_token_sdk::{instruction::ProofType, zk_token_proof_state::ProofContextState}, spl_pod::bytemuck::pod_from_bytes, + spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, std::slice::Iter, }; @@ -47,7 +47,7 @@ pub fn verify_mint_proof( let cipher_text_validity_account_info = next_account_info(account_info_iter)?; let range_proof_context = verify_batched_u64_range_proof(range_proof_account_info)?; let ciphertext_validity_proof_context = - verify_ciphertext_validity_proof(cipher_text_validity_account_info)?; + verify_3_ciphertext_validity_proof(cipher_text_validity_account_info)?; if close_split_context_state_on_execution { let lamport_destination_account_info = next_account_info(account_info_iter)?; @@ -108,8 +108,8 @@ pub fn verify_mint_proof( )?; let ciphertext_validity_proof_context = *decode_proof_instruction_context::< - BatchedGroupedCiphertext2HandlesValidityProofData, - BatchedGroupedCiphertext2HandlesValidityProofContext, + BatchedGroupedCiphertext3HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofContext, >( ProofInstruction::VerifyGroupedCiphertext2HandlesValidity, &ciphertext_validity_instruction, @@ -155,8 +155,9 @@ pub fn verify_burn_proof( let equality_proof_context = verify_equality_proof(equality_proof_context_state_account_info)?; - let ciphertext_validity_proof_context = - verify_ciphertext_validity_proof(ciphertext_validity_proof_context_state_account_info)?; + let ciphertext_validity_proof_context = verify_3_ciphertext_validity_proof( + ciphertext_validity_proof_context_state_account_info, + )?; let range_proof_context = verify_transfer_range_proof(range_proof_context_state_account_info)?; @@ -252,8 +253,8 @@ pub fn verify_burn_proof( )?; let ciphertext_validity_proof_context = *decode_proof_instruction_context::< - BatchedGroupedCiphertext2HandlesValidityProofData, - BatchedGroupedCiphertext2HandlesValidityProofContext, + BatchedGroupedCiphertext3HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofContext, >( ProofInstruction::VerifyGroupedCiphertext2HandlesValidity, &ciphertext_validity_instruction, @@ -288,14 +289,12 @@ pub fn verify_batched_u64_range_proof( /// from zk-proofs #[cfg(feature = "zk-ops")] pub fn validate_auditor_ciphertext( - confidential_mint: &ConfidentialTransferMint, + auditor_elgamal_pubkey: &OptionalNonZeroElGamalPubkey, proof_context: &impl AuditableProofContextInfo, auditor_lo: &ElGamalCiphertext, auditor_hi: &ElGamalCiphertext, ) -> Result<(), ProgramError> { - if let Some(auditor_pk) = - Into::>::into(confidential_mint.auditor_elgamal_pubkey) - { + if let Some(auditor_pk) = Into::>::into(*auditor_elgamal_pubkey) { // Check that the auditor encryption public key is consistent with what was // actually used to generate the zkp. if proof_context.auditor_pubkey() != &auditor_pk { @@ -312,3 +311,23 @@ pub fn validate_auditor_ciphertext( Ok(()) } + +/// Verify and process ciphertext validity proof for [ConfidentialMint] and +/// [ConfidentialBurn] instructions. +pub fn verify_3_ciphertext_validity_proof( + account_info: &AccountInfo<'_>, +) -> Result { + check_zk_token_proof_program_account(account_info.owner)?; + let context_state_account_data = account_info.data.borrow(); + let ciphertext_validity_proof_context_state = pod_from_bytes::< + ProofContextState, + >(&context_state_account_data)?; + + if ciphertext_validity_proof_context_state.proof_type + != ProofType::BatchedGroupedCiphertext3HandlesValidity.into() + { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(ciphertext_validity_proof_context_state.proof_context) +} 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 dfee663f2b1..6f311de06ad 100644 --- a/token/program-2022/src/extension/confidential_transfer/account_info.rs +++ b/token/program-2022/src/extension/confidential_transfer/account_info.rs @@ -12,7 +12,6 @@ use { encryption::{ auth_encryption::{AeCiphertext, AeKey}, elgamal::{ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey}, - pedersen::PedersenOpening, }, instruction::{ transfer::{FeeParameters, TransferData, TransferWithFeeData}, @@ -289,7 +288,6 @@ impl TransferAccountInfo { BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, SourceDecryptHandles, - (PedersenOpening, PedersenOpening), ), TokenError, > { diff --git a/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs b/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs index f68c4e15ccf..40593d48aa0 100644 --- a/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs +++ b/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs @@ -4,29 +4,25 @@ //! The logic in this submodule should belong to the `solana-zk-token-sdk` and //! will be removed with the next upgrade to the Solana program. -use { - crate::{ - extension::confidential_transfer::{ - ciphertext_extraction::{transfer_amount_source_ciphertext, SourceDecryptHandles}, - processor::verify_and_split_deposit_amount, - *, +use crate::{ + extension::confidential_transfer::{ + ciphertext_extraction::{transfer_amount_source_ciphertext, SourceDecryptHandles}, + processor::verify_and_split_deposit_amount, + *, + }, + solana_zk_token_sdk::{ + encryption::{ + auth_encryption::{AeCiphertext, AeKey}, + elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + grouped_elgamal::GroupedElGamal, + pedersen::Pedersen, }, - solana_zk_token_sdk::{ - encryption::{ - auth_encryption::{AeCiphertext, AeKey}, - elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, - grouped_elgamal::GroupedElGamal, - pedersen::Pedersen, - }, - instruction::{ - transfer::TransferAmountCiphertext, - BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, - CiphertextCommitmentEqualityProofData, - }, - zk_token_elgamal::ops::subtract_with_lo_hi, + instruction::{ + transfer::TransferAmountCiphertext, BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedRangeProofU128Data, CiphertextCommitmentEqualityProofData, }, + zk_token_elgamal::ops::subtract_with_lo_hi, }, - solana_zk_token_sdk::encryption::pedersen::PedersenOpening, }; /// The main logic to create the three split proof data for a transfer. @@ -45,7 +41,6 @@ pub fn transfer_split_proof_data( BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, SourceDecryptHandles, - (PedersenOpening, PedersenOpening), ), TokenError, > { @@ -192,6 +187,5 @@ pub fn transfer_split_proof_data( ciphertext_validity_proof_data, range_proof_data, source_decrypt_handles, - (transfer_amount_opening_hi, transfer_amount_opening_lo), )) } From 5bbafec12091078924ec3a2eae94234eb4070394 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Fri, 5 Jul 2024 08:11:36 -0300 Subject: [PATCH 05/34] remove debug statements --- .../src/extension/confidential_mint_burn/processor.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index b5eb52585a7..0a0fa56dd1b 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -100,14 +100,11 @@ fn process_rotate_supply_elgamal( let mint_info = next_account_info(account_info_iter)?; let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); - solana_program::log::sol_log("rotate 1"); check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow_mut(); let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; - solana_program::log::sol_log("rotate 2"); let mint = mint.get_extension_mut::()?; - solana_program::log::sol_log("rotate 3"); Processor::validate_owner( program_id, @@ -116,17 +113,14 @@ fn process_rotate_supply_elgamal( authority_info_data_len, account_info_iter.as_slice(), )?; - solana_program::log::sol_log("rotate 4"); if data.proof_location != 1 { return Err(ProgramError::InvalidInstructionData); } - solana_program::log::sol_log("rotate 5"); let sysvar_account_info = next_account_info(account_info_iter)?; let equality_proof_instruction = get_instruction_relative(data.proof_location as i64, sysvar_account_info)?; - solana_program::log::sol_log("rotate 6"); let proof_context = *decode_proof_instruction_context::< CiphertextCiphertextEqualityProofData, @@ -135,7 +129,6 @@ fn process_rotate_supply_elgamal( ProofInstruction::VerifyCiphertextCiphertextEquality, &equality_proof_instruction, )?; - solana_program::log::sol_log("rotate 7"); if !mint .supply_elgamal_pubkey @@ -143,16 +136,12 @@ fn process_rotate_supply_elgamal( { return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } - solana_program::log::sol_log("rotate 8"); if mint.confidential_supply != proof_context.source_ciphertext { return Err(ProgramError::InvalidInstructionData); } - solana_program::log::sol_log("rotate 9"); mint.supply_elgamal_pubkey = Some(proof_context.destination_pubkey).try_into()?; - solana_program::log::sol_log("rotate 10"); mint.confidential_supply = proof_context.destination_ciphertext; - solana_program::log::sol_log("rotate 11"); Ok(()) } From d6724a0470cef878244fb58f863d7359b4ad292f Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Fri, 5 Jul 2024 16:10:53 -0300 Subject: [PATCH 06/34] add tests for confidential mint-burn --- token/cli/src/clap_app.rs | 12 +- token/cli/src/command.rs | 59 +-- token/client/src/token.rs | 62 ++- .../tests/confidential_mint_burn.rs | 473 ++++++++++++++++++ .../confidential_mint_burn/instruction.rs | 6 +- 5 files changed, 560 insertions(+), 52 deletions(-) create mode 100644 token/program-2022-test/tests/confidential_mint_burn.rs diff --git a/token/cli/src/clap_app.rs b/token/cli/src/clap_app.rs index 1e7b79b5b6f..8fa67f880e6 100644 --- a/token/cli/src/clap_app.rs +++ b/token/cli/src/clap_app.rs @@ -2736,13 +2736,23 @@ pub fn app<'a, 'b>( .subcommand( SubCommand::with_name(CommandName::ConfidentialBalance.into()) .about("Display confidential balance") + .arg( + Arg::with_name("token") + .long("token") + .validator(is_valid_pubkey) + .value_name("TOKEN_MINT_ADDRESS") + .takes_value(true) + .index(1) + .required(true) + .help("The token address with confidential transfers enabled"), + ) .arg( Arg::with_name("address") .long("address") .validator(is_valid_pubkey) .value_name("TOKEN_ACCOUNT_ADDRESS") .takes_value(true) - .index(1) + .index(2) .help("The address of the token account to for which to fetch the confidential balance") ) .arg( diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index d9a6d99ea3f..83ac86471ac 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -43,8 +43,7 @@ use { }, confidential_transfer::{ account_info::{ - combine_balances, ApplyPendingBalanceAccountInfo, TransferAccountInfo, - WithdrawAccountInfo, + ApplyPendingBalanceAccountInfo, TransferAccountInfo, WithdrawAccountInfo, }, instruction::TransferSplitContextStateAccounts, ConfidentialTransferAccount, ConfidentialTransferMint, @@ -4705,12 +4704,15 @@ pub async fn process_command<'a>( .await } (CommandName::ConfidentialBalance, arg_matches) => { + let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) + .unwrap() + .unwrap(); let address = config .associated_token_address_or_override(arg_matches, "address", &mut wallet_manager) .await?; let (auth_signer, _auth) = config.signer_or_default(arg_matches, "authority", &mut wallet_manager); - command_confidential_balance(config, address, auth_signer).await + command_confidential_balance(config, token, address, auth_signer).await } (CommandName::ConfidentialSupply, arg_matches) => { let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) @@ -4750,8 +4752,8 @@ pub async fn process_command<'a>( &token_client_from_config(config, &token, None)? .rotate_supply_elgamal( &auth, - cur_elgamal_keypair, - new_elgamal_keypair, + &cur_elgamal_keypair, + &new_elgamal_keypair, &[auth_signer], ) .await?, @@ -4773,50 +4775,21 @@ pub async fn process_command<'a>( async fn command_confidential_balance( config: &Config<'_>, + token: Pubkey, address: Pubkey, authority_signer: Arc, ) -> CommandResult { - let account_data = config - .rpc_client - .get_account_data(&address) - .await - .map_err(|_| format!("Could not fetch token account account data for {}", address))?; - let elgamal_keypair = ElGamalKeypair::new_from_signer(&*authority_signer, b"").unwrap(); let aes_key = AeKey::new_from_signer(&*authority_signer, b"").unwrap(); - let state_with_extension = StateWithExtensionsOwned::::unpack(account_data)?; - let extension_state = state_with_extension.get_extension::()?; - let decryptable_available_balance = extension_state - .decryptable_available_balance - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?; - let decrypted_available_balance = aes_key - .decrypt(&decryptable_available_balance) - .ok_or(TokenError::AccountDecryption)?; - - let pending_balance_lo = extension_state - .pending_balance_lo - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?; - let decrypted_pending_balance_lo = elgamal_keypair - .secret() - .decrypt_u32(&pending_balance_lo) - .ok_or(TokenError::AccountDecryption)?; - - let pending_balance_hi = extension_state - .pending_balance_hi - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?; - let decrypted_pending_balance_hi = elgamal_keypair - .secret() - .decrypt_u32(&pending_balance_hi) - .ok_or(TokenError::AccountDecryption)?; - let pending_balance = - combine_balances(decrypted_pending_balance_lo, decrypted_pending_balance_hi) - .ok_or(TokenError::AccountDecryption)?; - - Ok(format!("{address} has a pending balance of {pending_balance} and an available balance of {decrypted_available_balance}") + let token = token_client_from_config(config, &token, None)?; + + let (available_balance, pending_balance) = token + .confidential_balance(&address, &elgamal_keypair, &aes_key) + .await + .unwrap(); + + Ok(format!("{address} has a pending balance of {pending_balance} and an available balance of {available_balance}") ) } diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 00763af61e8..9e5939cd034 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -29,13 +29,14 @@ use { }, }, spl_token_2022::{ + error::TokenError as Token2022Error, extension::{ confidential_mint_burn::{self, ConfidentialMintBurn}, confidential_transfer::{ self, account_info::{ - ApplyPendingBalanceAccountInfo, EmptyAccountAccountInfo, TransferAccountInfo, - WithdrawAccountInfo, + combine_balances, ApplyPendingBalanceAccountInfo, EmptyAccountAccountInfo, + TransferAccountInfo, WithdrawAccountInfo, }, ciphertext_extraction::SourceDecryptHandles, instruction::{ @@ -110,6 +111,13 @@ pub enum TokenError { MissingDecimals, #[error("decimals specified, but incorrect")] InvalidDecimals, + #[error("TokenProgramError: {0}")] + TokenProgramError(String), +} +impl From for TokenError { + fn from(e: Token2022Error) -> Self { + Self::TokenProgramError(e.to_string()) + } } impl PartialEq for TokenError { fn eq(&self, other: &Self) -> bool { @@ -4304,6 +4312,50 @@ where .await } + /// Fetch confidential balance for token account + #[allow(clippy::too_many_arguments)] + pub async fn confidential_balance( + &self, + token_account: &Pubkey, + elgamal_keypair: &ElGamalKeypair, + aes_key: &AeKey, + ) -> TokenResult<(u64, u64)> { + let account = self.get_account_info(token_account).await?; + let confidential_transfer_account = + account.get_extension::()?; + + let decryptable_available_balance = confidential_transfer_account + .decryptable_available_balance + .try_into() + .map_err(|_| Token2022Error::MalformedCiphertext)?; + let available_balance = aes_key + .decrypt(&decryptable_available_balance) + .ok_or(Token2022Error::AccountDecryption)?; + + let pending_balance_lo = confidential_transfer_account + .pending_balance_lo + .try_into() + .map_err(|_| Token2022Error::MalformedCiphertext)?; + let decrypted_pending_balance_lo = elgamal_keypair + .secret() + .decrypt_u32(&pending_balance_lo) + .ok_or(Token2022Error::AccountDecryption)?; + + let pending_balance_hi = confidential_transfer_account + .pending_balance_hi + .try_into() + .map_err(|_| Token2022Error::MalformedCiphertext)?; + let decrypted_pending_balance_hi = elgamal_keypair + .secret() + .decrypt_u32(&pending_balance_hi) + .ok_or(Token2022Error::AccountDecryption)?; + let pending_balance = + combine_balances(decrypted_pending_balance_lo, decrypted_pending_balance_hi) + .ok_or(Token2022Error::AccountDecryption)?; + + Ok((available_balance, pending_balance)) + } + /// Burn SPL Tokens from the available balance of a confidential token /// account #[allow(clippy::too_many_arguments)] @@ -4347,8 +4399,8 @@ where pub async fn rotate_supply_elgamal( &self, authority: &Pubkey, - supply_elgamal_keypair: ElGamalKeypair, - new_supply_elgamal_keypair: ElGamalKeypair, + supply_elgamal_keypair: &ElGamalKeypair, + new_supply_elgamal_keypair: &ElGamalKeypair, signing_keypairs: &S, ) -> TokenResult { let signing_pubkeys = signing_keypairs.pubkeys(); @@ -4356,7 +4408,7 @@ where let mint = self.get_mint_info().await?; let extension_state = mint.get_extension::()?; - let current_supply = self.confidential_supply(&supply_elgamal_keypair).await?; + let current_supply = self.confidential_supply(supply_elgamal_keypair).await?; self.process_ixs( &confidential_mint_burn::instruction::rotate_supply_elgamal( diff --git a/token/program-2022-test/tests/confidential_mint_burn.rs b/token/program-2022-test/tests/confidential_mint_burn.rs new file mode 100644 index 00000000000..fee124f334a --- /dev/null +++ b/token/program-2022-test/tests/confidential_mint_burn.rs @@ -0,0 +1,473 @@ +#![cfg(feature = "test-sbf")] + +mod program_test; +use { + program_test::{ConfidentialTokenAccountMeta, TestContext, TokenContext}, + solana_program_test::tokio, + solana_sdk::{pubkey::Pubkey, signature::Signer, signer::keypair::Keypair, signers::Signers}, + spl_token_2022::{ + extension::{ + confidential_mint_burn::{ + proof_generation::{generate_burn_proofs, generate_mint_proofs}, + ConfidentialMintBurn, + }, + confidential_transfer::{ + account_info::TransferAccountInfo, instruction::TransferSplitContextStateAccounts, + ConfidentialTransferAccount, + }, + BaseStateWithExtensions, + }, + proof::ProofLocation, + solana_zk_token_sdk::{ + encryption::elgamal::*, zk_token_elgamal::pod::ElGamalPubkey as PodElGamalPubkey, + }, + }, + spl_token_client::{ + client::ProgramBanksClientProcessTransaction, + token::{ExtensionInitializationParams, Token}, + }, + std::convert::TryInto, +}; + +const MINT_AMOUNT: u64 = 42; +const BURN_AMOUNT: u64 = 12; + +#[tokio::test] +async fn test_confidential_mint() { + let authority = Keypair::new(); + let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); + let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); + + let mut context = TestContext::new().await; + context + .init_token_with_mint(vec![ + ExtensionInitializationParams::ConfidentialTransferMint { + authority: Some(authority.pubkey()), + auto_approve_new_accounts: true, + auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), + }, + ExtensionInitializationParams::ConfidentialMintBurnMint { + authority: authority.pubkey(), + confidential_supply_pubkey: Some(auditor_elgamal_pubkey), + }, + ]) + .await + .unwrap(); + + let TokenContext { token, alice, .. } = context.token_context.unwrap(); + let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice, None, false, false).await; + //let alice_elgamal_pubkey = (*alice_meta.elgamal_keypair.pubkey()).into(); + + mint_tokens( + &token, + &alice_meta.token_account, + &authority.pubkey(), + MINT_AMOUNT, + &[&authority], + ) + .await; + + assert_eq!( + token + .confidential_balance( + &alice_meta.token_account, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key + ) + .await + .unwrap() + .1, + MINT_AMOUNT + ); + + token + .confidential_transfer_apply_pending_balance( + &alice_meta.token_account, + &alice.pubkey(), + None, + alice_meta.elgamal_keypair.secret(), + &alice_meta.aes_key, + &[&alice], + ) + .await + .unwrap(); + + assert_eq!( + token + .confidential_balance( + &alice_meta.token_account, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key + ) + .await + .unwrap() + .0, + MINT_AMOUNT + ); + + assert_eq!( + token + .confidential_supply(&auditor_elgamal_keypair,) + .await + .unwrap(), + MINT_AMOUNT + ); +} + +#[tokio::test] +async fn test_confidential_burn() { + let authority = Keypair::new(); + let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); + let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); + + let mut context = TestContext::new().await; + context + .init_token_with_mint(vec![ + ExtensionInitializationParams::ConfidentialTransferMint { + authority: Some(authority.pubkey()), + auto_approve_new_accounts: true, + auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), + }, + ExtensionInitializationParams::ConfidentialMintBurnMint { + authority: authority.pubkey(), + confidential_supply_pubkey: Some(auditor_elgamal_pubkey), + }, + ]) + .await + .unwrap(); + + let TokenContext { token, alice, .. } = context.token_context.unwrap(); + let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice, None, false, false).await; + //let alice_elgamal_pubkey = (*alice_meta.elgamal_keypair.pubkey()).into(); + + mint_tokens( + &token, + &alice_meta.token_account, + &authority.pubkey(), + MINT_AMOUNT, + &[&authority], + ) + .await; + + assert_eq!( + token + .confidential_supply(&auditor_elgamal_keypair,) + .await + .unwrap(), + MINT_AMOUNT + ); + + token + .confidential_transfer_apply_pending_balance( + &alice_meta.token_account, + &alice.pubkey(), + None, + alice_meta.elgamal_keypair.secret(), + &alice_meta.aes_key, + &[&alice], + ) + .await + .unwrap(); + + let context_state_authority = Keypair::new(); + let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await.unwrap(); + let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await.unwrap(); + + let equality_proof_context_state_account = Keypair::new(); + let equality_proof_pubkey = equality_proof_context_state_account.pubkey(); + let ciphertext_validity_proof_context_state_account = Keypair::new(); + let ciphertext_validity_proof_pubkey = ciphertext_validity_proof_context_state_account.pubkey(); + let range_proof_context_state_account = Keypair::new(); + let range_proof_pubkey = range_proof_context_state_account.pubkey(); + + let context_state_accounts = TransferSplitContextStateAccounts { + equality_proof: &equality_proof_pubkey, + ciphertext_validity_proof: &ciphertext_validity_proof_pubkey, + range_proof: &range_proof_pubkey, + authority: &context_state_authority.pubkey(), + no_op_on_uninitialized_split_context_state: false, + close_split_context_state_accounts: None, + }; + + let state = token + .get_account_info(&alice_meta.token_account) + .await + .unwrap(); + let extension = state + .get_extension::() + .unwrap(); + let transfer_account_info = TransferAccountInfo::new(extension); + + let (equality_proof_data, ciphertext_validity_proof_data, range_proof_data, pedersen_openings) = + generate_burn_proofs( + &transfer_account_info.available_balance.try_into().unwrap(), + &transfer_account_info + .decryptable_available_balance + .try_into() + .unwrap(), + BURN_AMOUNT, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, + ) + .unwrap(); + + // setup proofs + token + .create_range_proof_context_state_for_transfer( + context_state_accounts, + &range_proof_data, + &range_proof_context_state_account, + ) + .await + .unwrap(); + token + .create_equality_proof_context_state_for_transfer( + context_state_accounts, + &equality_proof_data, + &equality_proof_context_state_account, + ) + .await + .unwrap(); + token + .create_batched_grouped_3_handles_ciphertext_validity_proof_context_state( + context_state_accounts.ciphertext_validity_proof, + context_state_accounts.authority, + &ciphertext_validity_proof_data, + &ciphertext_validity_proof_context_state_account, + ) + .await + .unwrap(); + + // do the burn + token + .confidential_burn( + &alice_meta.token_account, + &alice.pubkey(), + context_state_accounts, + BURN_AMOUNT, + auditor_elgamal_pubkey, + supply_elgamal_pubkey, + &alice_meta.aes_key, + &[&alice], + &pedersen_openings, + ) + .await + .unwrap(); + + // close context state accounts + let context_state_authority_pubkey = context_state_authority.pubkey(); + let close_context_state_signers = &[context_state_authority]; + token + .confidential_transfer_close_context_state( + &equality_proof_pubkey, + &context_state_authority_pubkey, + &context_state_authority_pubkey, + close_context_state_signers, + ) + .await + .unwrap(); + token + .confidential_transfer_close_context_state( + &ciphertext_validity_proof_pubkey, + &context_state_authority_pubkey, + &context_state_authority_pubkey, + close_context_state_signers, + ) + .await + .unwrap(); + token + .confidential_transfer_close_context_state( + &range_proof_pubkey, + &context_state_authority_pubkey, + &context_state_authority_pubkey, + close_context_state_signers, + ) + .await + .unwrap(); + + assert_eq!( + token + .confidential_balance( + &alice_meta.token_account, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key + ) + .await + .unwrap() + .0, + MINT_AMOUNT - BURN_AMOUNT, + ); + + assert_eq!( + token + .confidential_supply(&auditor_elgamal_keypair,) + .await + .unwrap(), + MINT_AMOUNT - BURN_AMOUNT, + ); +} + +#[tokio::test] +async fn test_rotate_supply_elgamal() { + let authority = Keypair::new(); + let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); + let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); + + let mut context = TestContext::new().await; + context + .init_token_with_mint(vec![ + ExtensionInitializationParams::ConfidentialTransferMint { + authority: Some(authority.pubkey()), + auto_approve_new_accounts: true, + auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), + }, + ExtensionInitializationParams::ConfidentialMintBurnMint { + authority: authority.pubkey(), + confidential_supply_pubkey: Some(auditor_elgamal_pubkey), + }, + ]) + .await + .unwrap(); + + let TokenContext { token, alice, .. } = context.token_context.unwrap(); + let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice, None, false, false).await; + //let alice_elgamal_pubkey = (*alice_meta.elgamal_keypair.pubkey()).into(); + + mint_tokens( + &token, + &alice_meta.token_account, + &authority.pubkey(), + MINT_AMOUNT, + &[&authority], + ) + .await; + + assert_eq!( + token + .confidential_supply(&auditor_elgamal_keypair,) + .await + .unwrap(), + MINT_AMOUNT + ); + + let new_supply_elgamal_keypair = ElGamalKeypair::new_rand(); + token + .rotate_supply_elgamal( + &authority.pubkey(), + &auditor_elgamal_keypair, + &new_supply_elgamal_keypair, + &[authority], + ) + .await + .unwrap(); + + assert_eq!( + token + .confidential_supply(&new_supply_elgamal_keypair) + .await + .unwrap(), + MINT_AMOUNT + ); + + let mint = token.get_mint_info().await.unwrap(); + let conf_mint_burn_ext = mint.get_extension::().unwrap(); + + assert_eq!( + conf_mint_burn_ext.supply_elgamal_pubkey, + Some(Into::::into( + *new_supply_elgamal_keypair.pubkey(), + )) + .try_into() + .unwrap(), + ); +} + +async fn mint_tokens( + token: &Token, + token_account: &Pubkey, + authority: &Pubkey, + mint_amount: u64, + bulk_signers: &impl Signers, +) { + let context_state_authority = Keypair::new(); + let range_proof_context_state_account = Keypair::new(); + let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); + let ciphertext_validity_proof_context_state_account = Keypair::new(); + let ciphertext_validity_proof_context_pubkey = + ciphertext_validity_proof_context_state_account.pubkey(); + + let mint_to_elgamal_pubkey = token.account_elgamal_pubkey(token_account).await.unwrap(); + let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await.unwrap(); + let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await.unwrap(); + + let (range_proof, ciphertext_validity_proof, pedersen_openings) = generate_mint_proofs( + mint_amount, + &mint_to_elgamal_pubkey, + &auditor_elgamal_pubkey, + &supply_elgamal_pubkey, + ) + .unwrap(); + + token + .create_batched_u64_range_proof_context_state( + &range_proof_context_pubkey, + &context_state_authority.pubkey(), + &range_proof, + &range_proof_context_state_account, + ) + .await + .unwrap(); + token + .create_batched_grouped_3_handles_ciphertext_validity_proof_context_state( + &ciphertext_validity_proof_context_pubkey, + &context_state_authority.pubkey(), + &ciphertext_validity_proof, + &ciphertext_validity_proof_context_state_account, + ) + .await + .unwrap(); + + let range_proof_location = ProofLocation::ContextStateAccount(&range_proof_context_pubkey); + let ciphertext_validity_proof_location = + ProofLocation::ContextStateAccount(&ciphertext_validity_proof_context_pubkey); + + println!("MINT: account is {token_account} with owner {authority}"); + token + .confidential_mint( + &token_account, + &authority, + mint_amount, + auditor_elgamal_pubkey, + supply_elgamal_pubkey, + range_proof_location, + ciphertext_validity_proof_location, + &pedersen_openings, + bulk_signers, + ) + .await + .map_err(|e| println!("{}", e)) + .unwrap(); + + let close_context_auth = context_state_authority.pubkey(); + let close_context_state_signers = &[context_state_authority]; + token + .confidential_transfer_close_context_state( + &range_proof_context_pubkey, + &close_context_auth, + &close_context_auth, + close_context_state_signers, + ) + .await + .unwrap(); + token + .confidential_transfer_close_context_state( + &ciphertext_validity_proof_context_pubkey, + &close_context_auth, + &close_context_auth, + close_context_state_signers, + ) + .await + .unwrap(); +} diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 430d0d9ed23..c86024e0dbd 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -218,8 +218,8 @@ pub fn rotate_supply_elgamal( multisig_signers: &[&Pubkey], extension_state: &ConfidentialMintBurn, current_supply: u64, - supply_elgamal_keypair: ElGamalKeypair, - new_supply_elgamal_keypair: ElGamalKeypair, + supply_elgamal_keypair: &ElGamalKeypair, + new_supply_elgamal_keypair: &ElGamalKeypair, ) -> Result, ProgramError> { check_program_account(token_program_id)?; let mut accounts = vec![ @@ -236,7 +236,7 @@ pub fn rotate_supply_elgamal( .encrypt_with(current_supply, &new_supply_opening); let proof_data = CiphertextCiphertextEqualityProofData::new( - &supply_elgamal_keypair, + supply_elgamal_keypair, new_supply_elgamal_keypair.pubkey(), &ElGamalCiphertext::try_from(extension_state.confidential_supply) .map_err(|_| TokenError::InvalidState)?, From 5939b72f9ad0fd2b280287b5a142a8cba8063a80 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Thu, 11 Jul 2024 09:38:50 -0300 Subject: [PATCH 07/34] fix clippy & serde tests --- token/client/src/token.rs | 30 +++++------- .../tests/confidential_mint_burn.rs | 4 +- .../confidential_mint_burn/instruction.rs | 11 ++++- .../extension/confidential_mint_burn/mod.rs | 4 +- token/program-2022/src/serialization.rs | 48 +++++++++++++++++++ 5 files changed, 72 insertions(+), 25 deletions(-) diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 9e5939cd034..51554cbeeb1 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -4080,16 +4080,13 @@ where } pub async fn auditor_elgamal_pubkey(&self) -> TokenResult> { - Ok( - TryInto::>::try_into( - self.get_mint_info() - .await? - .get_extension::()? - .auditor_elgamal_pubkey, - ) - .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData))? - .map(|pk| TryInto::::try_into(pk).unwrap()), + Ok(Into::>::into( + self.get_mint_info() + .await? + .get_extension::()? + .auditor_elgamal_pubkey, ) + .map(|pk| TryInto::::try_into(pk).unwrap())) } pub async fn account_elgamal_pubkey(&self, account: &Pubkey) -> TokenResult { @@ -4382,16 +4379,13 @@ where } pub async fn supply_elgamal_pubkey(&self) -> TokenResult> { - Ok( - TryInto::>::try_into( - self.get_mint_info() - .await? - .get_extension::()? - .supply_elgamal_pubkey, - ) - .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData))? - .map(|pk| TryInto::::try_into(pk).unwrap()), + Ok(Into::>::into( + self.get_mint_info() + .await? + .get_extension::()? + .supply_elgamal_pubkey, ) + .map(|pk| TryInto::::try_into(pk).unwrap())) } /// Rotate the elgamal pubkey encrypting the confidential supply diff --git a/token/program-2022-test/tests/confidential_mint_burn.rs b/token/program-2022-test/tests/confidential_mint_burn.rs index fee124f334a..b47620b89c7 100644 --- a/token/program-2022-test/tests/confidential_mint_burn.rs +++ b/token/program-2022-test/tests/confidential_mint_burn.rs @@ -436,8 +436,8 @@ async fn mint_tokens( println!("MINT: account is {token_account} with owner {authority}"); token .confidential_mint( - &token_account, - &authority, + token_account, + authority, mint_amount, auditor_elgamal_pubkey, supply_elgamal_pubkey, diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index c86024e0dbd..8d7f42972df 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -4,8 +4,6 @@ use crate::{ extension::confidential_transfer::processor::verify_and_split_deposit_amount, proof::ProofLocation, }; -#[cfg(feature = "serde-traits")] -use serde::{Deserialize, Serialize}; #[cfg(not(target_os = "solana"))] use solana_zk_token_sdk::instruction::{ BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU64Data, @@ -45,6 +43,11 @@ use { solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext as PodElGamalCiphertext, spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, }; +#[cfg(feature = "serde-traits")] +use { + crate::serialization::{aeciphertext_fromstr, elgamalciphertext_fromstr}, + serde::{Deserialize, Serialize}, +}; /// Confidential Transfer extension instructions #[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] @@ -124,9 +127,11 @@ pub struct RotateSupplyElGamalData { pub struct MintInstructionData { /// low 16 bits of encrypted amount to be minted, exposes mint amounts /// to the auditor through the data received via `get_transaction` + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] pub audit_amount_lo: PodElGamalCiphertext, /// high 48 bits of encrypted amount to be minted, exposes mint amounts /// to the auditor through the data received via `get_transaction` + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] pub audit_amount_hi: PodElGamalCiphertext, /// Relative location of the `ProofInstruction::VerifyBatchedRangeProofU64` /// instruction to the `ConfidentialMint` instruction in the @@ -146,8 +151,10 @@ pub struct BurnInstructionData { #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_decryptable_available_balance: DecryptableBalance, /// low 16 bits of encrypted amount to be minted + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] pub auditor_lo: PodElGamalCiphertext, /// high 48 bits of encrypted amount to be minted + #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] pub auditor_hi: PodElGamalCiphertext, /// Relative location of the /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index eddece3a482..ac98f67f8b1 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -29,9 +29,7 @@ pub mod verify_proof; /// Confidential Mint-Burn proof verification pub mod ciphertext_extraction; -/// Confidential transfer mint configuration -#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))] +/// Confidential mint-burn mint configuration #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] #[repr(C)] pub struct ConfidentialMintBurn { diff --git a/token/program-2022/src/serialization.rs b/token/program-2022/src/serialization.rs index 9287caacab8..97488c77441 100644 --- a/token/program-2022/src/serialization.rs +++ b/token/program-2022/src/serialization.rs @@ -147,6 +147,54 @@ pub mod aeciphertext_fromstr { } } +/// helper to ser/deser ElGamalCiphertext values +pub mod elgamalciphertext_fromstr { + use { + serde::{ + de::{Error, Visitor}, + Deserializer, Serializer, + }, + solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, + std::fmt, + }; + + const AE_CIPHERTEXT_LEN: usize = 64; + + /// serialize AeCiphertext values supporting Display trait + pub fn serialize(x: &ElGamalCiphertext, s: S) -> Result + where + S: Serializer, + { + s.serialize_str(&x.to_string()) + } + + struct AeCiphertextVisitor; + + impl<'de> Visitor<'de> for AeCiphertextVisitor { + type Value = ElGamalCiphertext; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a FromStr type") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + let array = super::base64_to_bytes::(v)?; + Ok(ElGamalCiphertext(array)) + } + } + + /// deserialize AeCiphertext values from str + pub fn deserialize<'de, D>(d: D) -> Result + where + D: Deserializer<'de>, + { + d.deserialize_str(AeCiphertextVisitor) + } +} + /// helper to ser/deser pod::ElGamalPubkey values pub mod elgamalpubkey_fromstr { use { From 8479bad5ccc4106406825fca00a3464be3078c80 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Wed, 4 Sep 2024 13:31:33 +0200 Subject: [PATCH 08/34] fix some tests --- token/cli/src/command.rs | 65 +++-- token/client/src/token.rs | 147 ++---------- .../program-2022-test/tests/close_account.rs | 2 +- .../tests/confidential_mint_burn.rs | 69 +++--- .../confidential_mint_burn/processor.rs | 3 - .../confidential_mint_burn/verify_proof.rs | 222 +++--------------- token/program-2022/src/processor.rs | 1 + token/program/src/error.rs | 1 + 8 files changed, 125 insertions(+), 385 deletions(-) diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index b63be5dd13f..dec49d319e4 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -36,10 +36,12 @@ use { }, spl_associated_token_account::get_associated_token_address_with_program_id, spl_token_2022::{ - error::TokenError, extension::{ - confidential_mint_burn::{instruction::BurnSplitContextStateAccounts, proof_generation::{ - generate_burn_proofs, generate_mint_proofs, - }}, + error::TokenError, + extension::{ + confidential_mint_burn::{ + instruction::BurnSplitContextStateAccounts, + proof_generation::{generate_burn_proofs, generate_mint_proofs}, + }, confidential_transfer::{ account_info::{ ApplyPendingBalanceAccountInfo, TransferAccountInfo, WithdrawAccountInfo, @@ -59,11 +61,14 @@ use { transfer_fee::{TransferFeeAmount, TransferFeeConfig}, transfer_hook::TransferHook, BaseStateWithExtensions, ExtensionType, StateWithExtensionsOwned, - }, proof::ProofLocation, solana_zk_sdk::encryption::{ + }, + proof::ProofLocation, + solana_zk_sdk::encryption::{ auth_encryption::AeKey, elgamal::{self, ElGamalKeypair}, pod::elgamal::PodElGamalPubkey, - }, state::{Account, AccountState, Mint} + }, + state::{Account, AccountState, Mint}, }, spl_token_client::{ client::{ProgramRpcClientSendTransaction, RpcClientResponse}, @@ -3457,19 +3462,23 @@ async fn command_deposit_withdraw_mint_confidential_tokens( &supply_elgamal_pubkey, )?; + let range_proof_signer = &[&range_proof_context_state_account]; + let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; let context_state_auth = payer.pubkey(); let _ = try_join!( - token.create_batched_u64_range_proof_context_state( + token.confidential_transfer_create_context_state_account( &range_proof_context_pubkey, &context_state_auth, &range_proof, - &range_proof_context_state_account, + true, + range_proof_signer, ), - token.create_batched_grouped_3_handles_ciphertext_validity_proof_context_state( + token.confidential_transfer_create_context_state_account( &ciphertext_validity_proof_context_pubkey, &context_state_auth, &ciphertext_validity_proof, - &ciphertext_validity_proof_context_state_account, + false, + ciphertext_validity_proof_signer, ), )?; @@ -3495,13 +3504,13 @@ async fn command_deposit_withdraw_mint_confidential_tokens( let close_context_auth = payer.pubkey(); let close_context_state_signers = &[payer]; let _ = try_join!( - token.confidential_transfer_close_context_state( + token.confidential_transfer_close_context_state_account( &range_proof_context_pubkey, &close_context_auth, &close_context_auth, close_context_state_signers, ), - token.confidential_transfer_close_context_state( + token.confidential_transfer_close_context_state_account( &ciphertext_validity_proof_context_pubkey, &close_context_auth, &close_context_auth, @@ -4920,24 +4929,32 @@ async fn command_confidential_burn( ) .unwrap(); + let range_proof_signer = &[&range_proof_context_state_account]; + let equality_proof_signer = &[&equality_proof_context_state_account]; + let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; // setup proofs let _ = try_join!( - token.create_range_proof_context_state_for_transfer( - context_state_accounts, + token.confidential_transfer_create_context_state_account( + context_state_accounts.range_proof, + context_state_accounts.authority, &range_proof_data, - &range_proof_context_state_account, + true, + range_proof_signer, ), - token.create_equality_proof_context_state_for_transfer( - context_state_accounts, + token.confidential_transfer_create_context_state_account( + context_state_accounts.equality_proof, + context_state_accounts.authority, &equality_proof_data, - &equality_proof_context_state_account, + false, + equality_proof_signer, ), - token.create_batched_grouped_3_handles_ciphertext_validity_proof_context_state( + token.confidential_transfer_create_context_state_account( context_state_accounts.ciphertext_validity_proof, context_state_accounts.authority, &ciphertext_validity_proof_data, - &ciphertext_validity_proof_context_state_account, - ) + false, + ciphertext_validity_proof_signer, + ), )?; // do the burn @@ -4959,19 +4976,19 @@ async fn command_confidential_burn( let context_state_authority_pubkey = context_state_authority.pubkey(); let close_context_state_signers = &[context_state_authority]; let _ = try_join!( - token.confidential_transfer_close_context_state( + token.confidential_transfer_close_context_state_account( &equality_proof_pubkey, &authority, &context_state_authority_pubkey, close_context_state_signers, ), - token.confidential_transfer_close_context_state( + token.confidential_transfer_close_context_state_account( &ciphertext_validity_proof_pubkey, &authority, &context_state_authority_pubkey, close_context_state_signers, ), - token.confidential_transfer_close_context_state( + token.confidential_transfer_close_context_state_account( &range_proof_pubkey, &authority, &context_state_authority_pubkey, diff --git a/token/client/src/token.rs b/token/client/src/token.rs index e94cdbf5d27..77b4337617c 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -7,7 +7,19 @@ use { futures_util::TryFutureExt, solana_program_test::tokio::time, solana_sdk::{ - account::Account as BaseAccount, compute_budget::ComputeBudgetInstruction, hash::Hash, instruction::{AccountMeta, Instruction}, message::Message, packet::PACKET_DATA_SIZE, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, signature::Signature, signer::{signers::Signers, Signer, SignerError}, system_instruction, transaction::Transaction + account::Account as BaseAccount, + compute_budget::ComputeBudgetInstruction, + hash::Hash, + instruction::{AccountMeta, Instruction}, + message::Message, + packet::PACKET_DATA_SIZE, + program_error::ProgramError, + program_pack::Pack, + pubkey::Pubkey, + signature::Signature, + signer::{signers::Signers, Signer, SignerError}, + system_instruction, + transaction::Transaction, }, spl_associated_token_account::{ get_associated_token_address_with_program_id, @@ -29,10 +41,8 @@ use { TransferAccountInfo, WithdrawAccountInfo, }, instruction::{ - BatchedGroupedCiphertext2HandlesValidityProofContext, - BatchedGroupedCiphertext2HandlesValidityProofData, - BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofContext, - BatchedRangeProofU64Data, ProofContextState, ProofInstruction, ZkProofData, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU64Data, + ProofContextState, ZkProofData, }, ConfidentialTransferAccount, ConfidentialTransferMint, DecryptableBalance, }, @@ -56,7 +66,6 @@ use { zk_elgamal_proof_program::{ self, instruction::{close_context_state, ContextStateInfo}, - proof_data::BatchedGroupedCiphertext3HandlesValidityProofContext, }, }, state::{Account, AccountState, Mint, Multisig}, @@ -3483,132 +3492,6 @@ where .await } - /// Create a range proof context state account for mint - pub async fn create_batched_u64_range_proof_context_state( - &self, - range_proof_pubkey: &Pubkey, - range_proof_authority: &Pubkey, - range_proof_data: &BatchedRangeProofU64Data, - range_proof_signer: &S, - ) -> TokenResult { - let instruction_type = ProofInstruction::VerifyBatchedRangeProofU64; - let space = size_of::>(); - let rent = self - .client - .get_minimum_balance_for_rent_exemption(space) - .await - .map_err(TokenError::Client)?; - let range_proof_context_state_info = ContextStateInfo { - context_state_account: range_proof_pubkey, - context_state_authority: range_proof_authority, - }; - - self.process_ixs( - &[system_instruction::create_account( - &self.payer.pubkey(), - range_proof_context_state_info.context_state_account, - rent, - space as u64, - &zk_elgamal_proof_program::id(), - )], - &[range_proof_signer], - ) - .await?; - - self.process_ixs( - &[instruction_type - .encode_verify_proof(Some(range_proof_context_state_info), range_proof_data)], - &[self.payer.clone()], - ) - .await - } - - /// Create a ciphertext validity proof context state account for mint - pub async fn create_batched_grouped_2_handles_ciphertext_validity_proof_context_state< - S: Signer, - >( - &self, - proof_pubkey: &Pubkey, - proof_authority: &Pubkey, - ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - ciphertext_validity_proof_signer: &S, - ) -> TokenResult { - let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity; - let space = - size_of::>(); - let rent = self - .client - .get_minimum_balance_for_rent_exemption(space) - .await - .map_err(TokenError::Client)?; - - let ciphertext_validity_proof_context_state_info = ContextStateInfo { - context_state_account: proof_pubkey, - context_state_authority: proof_authority, - }; - - self.process_ixs( - &[ - system_instruction::create_account( - &self.payer.pubkey(), - proof_pubkey, - rent, - space as u64, - &zk_elgamal_proof_program::id(), - ), - instruction_type.encode_verify_proof( - Some(ciphertext_validity_proof_context_state_info), - ciphertext_validity_proof_data, - ), - ], - &[ciphertext_validity_proof_signer], - ) - .await - } - - /// Create a ciphertext validity proof context state account for mint - pub async fn create_batched_grouped_3_handles_ciphertext_validity_proof_context_state< - S: Signer, - >( - &self, - proof_pubkey: &Pubkey, - proof_authority: &Pubkey, - ciphertext_validity_proof_data: &BatchedGroupedCiphertext3HandlesValidityProofData, - ciphertext_validity_proof_signer: &S, - ) -> TokenResult { - let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity; - let space = - size_of::>(); - let rent = self - .client - .get_minimum_balance_for_rent_exemption(space) - .await - .map_err(TokenError::Client)?; - - let ciphertext_validity_proof_context_state_info = ContextStateInfo { - context_state_account: proof_pubkey, - context_state_authority: proof_authority, - }; - - self.process_ixs( - &[ - system_instruction::create_account( - &self.payer.pubkey(), - proof_pubkey, - rent, - space as u64, - &zk_elgamal_proof_program::id(), - ), - instruction_type.encode_verify_proof( - Some(ciphertext_validity_proof_context_state_info), - ciphertext_validity_proof_data, - ), - ], - &[ciphertext_validity_proof_signer], - ) - .await - } - /// Burn SPL Tokens from the available balance of a confidential token /// account #[allow(clippy::too_many_arguments)] diff --git a/token/program-2022-test/tests/close_account.rs b/token/program-2022-test/tests/close_account.rs index d070b9d1134..97706c126ed 100644 --- a/token/program-2022-test/tests/close_account.rs +++ b/token/program-2022-test/tests/close_account.rs @@ -162,7 +162,7 @@ async fn fail_init_after_close_mint() { assert_eq!( error, TokenClientError::Client(Box::new(TransportError::TransactionError( - TransactionError::InstructionError(2, InstructionError::InvalidAccountData,) + TransactionError::InstructionError(3, InstructionError::InvalidAccountData,) ))) ); let error = token.get_account(destination).await.unwrap_err(); diff --git a/token/program-2022-test/tests/confidential_mint_burn.rs b/token/program-2022-test/tests/confidential_mint_burn.rs index b47620b89c7..32d40436882 100644 --- a/token/program-2022-test/tests/confidential_mint_burn.rs +++ b/token/program-2022-test/tests/confidential_mint_burn.rs @@ -8,19 +8,18 @@ use { spl_token_2022::{ extension::{ confidential_mint_burn::{ + instruction::BurnSplitContextStateAccounts, proof_generation::{generate_burn_proofs, generate_mint_proofs}, ConfidentialMintBurn, }, confidential_transfer::{ - account_info::TransferAccountInfo, instruction::TransferSplitContextStateAccounts, + account_info::TransferAccountInfo, ConfidentialTransferAccount, }, BaseStateWithExtensions, }, proof::ProofLocation, - solana_zk_token_sdk::{ - encryption::elgamal::*, zk_token_elgamal::pod::ElGamalPubkey as PodElGamalPubkey, - }, + solana_zk_sdk::encryption::{elgamal::*, pod::elgamal::PodElGamalPubkey}, }, spl_token_client::{ client::ProgramBanksClientProcessTransaction, @@ -180,13 +179,11 @@ async fn test_confidential_burn() { let range_proof_context_state_account = Keypair::new(); let range_proof_pubkey = range_proof_context_state_account.pubkey(); - let context_state_accounts = TransferSplitContextStateAccounts { + let context_state_accounts = BurnSplitContextStateAccounts { equality_proof: &equality_proof_pubkey, ciphertext_validity_proof: &ciphertext_validity_proof_pubkey, range_proof: &range_proof_pubkey, authority: &context_state_authority.pubkey(), - no_op_on_uninitialized_split_context_state: false, - close_split_context_state_accounts: None, }; let state = token @@ -213,29 +210,37 @@ async fn test_confidential_burn() { ) .unwrap(); + let range_proof_signer = &[&range_proof_context_state_account]; + let equality_proof_signer = &[&equality_proof_context_state_account]; + let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; // setup proofs token - .create_range_proof_context_state_for_transfer( - context_state_accounts, + .confidential_transfer_create_context_state_account( + context_state_accounts.range_proof, + context_state_accounts.authority, &range_proof_data, - &range_proof_context_state_account, + true, + range_proof_signer, ) .await .unwrap(); token - .create_equality_proof_context_state_for_transfer( - context_state_accounts, + .confidential_transfer_create_context_state_account( + context_state_accounts.equality_proof, + context_state_accounts.authority, &equality_proof_data, - &equality_proof_context_state_account, + false, + equality_proof_signer, ) .await .unwrap(); token - .create_batched_grouped_3_handles_ciphertext_validity_proof_context_state( + .confidential_transfer_create_context_state_account( context_state_accounts.ciphertext_validity_proof, context_state_accounts.authority, &ciphertext_validity_proof_data, - &ciphertext_validity_proof_context_state_account, + false, + ciphertext_validity_proof_signer, ) .await .unwrap(); @@ -245,7 +250,7 @@ async fn test_confidential_burn() { .confidential_burn( &alice_meta.token_account, &alice.pubkey(), - context_state_accounts, + &context_state_accounts, BURN_AMOUNT, auditor_elgamal_pubkey, supply_elgamal_pubkey, @@ -260,7 +265,7 @@ async fn test_confidential_burn() { let context_state_authority_pubkey = context_state_authority.pubkey(); let close_context_state_signers = &[context_state_authority]; token - .confidential_transfer_close_context_state( + .confidential_transfer_close_context_state_account( &equality_proof_pubkey, &context_state_authority_pubkey, &context_state_authority_pubkey, @@ -269,7 +274,7 @@ async fn test_confidential_burn() { .await .unwrap(); token - .confidential_transfer_close_context_state( + .confidential_transfer_close_context_state_account( &ciphertext_validity_proof_pubkey, &context_state_authority_pubkey, &context_state_authority_pubkey, @@ -278,7 +283,7 @@ async fn test_confidential_burn() { .await .unwrap(); token - .confidential_transfer_close_context_state( + .confidential_transfer_close_context_state_account( &range_proof_pubkey, &context_state_authority_pubkey, &context_state_authority_pubkey, @@ -391,7 +396,7 @@ async fn mint_tokens( mint_amount: u64, bulk_signers: &impl Signers, ) { - let context_state_authority = Keypair::new(); + let context_state_auth = Keypair::new(); let range_proof_context_state_account = Keypair::new(); let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); let ciphertext_validity_proof_context_state_account = Keypair::new(); @@ -410,21 +415,25 @@ async fn mint_tokens( ) .unwrap(); + let range_proof_signer = &[&range_proof_context_state_account]; + let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; token - .create_batched_u64_range_proof_context_state( + .confidential_transfer_create_context_state_account( &range_proof_context_pubkey, - &context_state_authority.pubkey(), + &context_state_auth.pubkey(), &range_proof, - &range_proof_context_state_account, + true, + range_proof_signer, ) .await .unwrap(); token - .create_batched_grouped_3_handles_ciphertext_validity_proof_context_state( + .confidential_transfer_create_context_state_account( &ciphertext_validity_proof_context_pubkey, - &context_state_authority.pubkey(), + &context_state_auth.pubkey(), &ciphertext_validity_proof, - &ciphertext_validity_proof_context_state_account, + false, + ciphertext_validity_proof_signer, ) .await .unwrap(); @@ -450,10 +459,10 @@ async fn mint_tokens( .map_err(|e| println!("{}", e)) .unwrap(); - let close_context_auth = context_state_authority.pubkey(); - let close_context_state_signers = &[context_state_authority]; + let close_context_auth = context_state_auth.pubkey(); + let close_context_state_signers = &[context_state_auth]; token - .confidential_transfer_close_context_state( + .confidential_transfer_close_context_state_account( &range_proof_context_pubkey, &close_context_auth, &close_context_auth, @@ -462,7 +471,7 @@ async fn mint_tokens( .await .unwrap(); token - .confidential_transfer_close_context_state( + .confidential_transfer_close_context_state_account( &ciphertext_validity_proof_context_pubkey, &close_context_auth, &close_context_auth, diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index ccfbc414c9b..137311b45d9 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -194,7 +194,6 @@ fn process_confidential_mint( let proof_context = verify_mint_proof( account_info_iter, data.proof_instruction_offset as i64, - false, )?; if token_account @@ -242,7 +241,6 @@ fn process_confidential_mint( // update supply if conf_mint_ext.supply_elgamal_pubkey.is_some() { - solana_program::log::sol_log("UPDATING SUPPLY"); let current_supply = conf_mint_ext.confidential_supply; conf_mint_ext.confidential_supply = ciphertext_arithmetic::add_with_lo_hi( ¤t_supply, @@ -289,7 +287,6 @@ fn process_confidential_burn( let proof_context = verify_burn_proof( account_info_iter, data.proof_instruction_offset as i64, - false, )?; let authority_info = next_account_info(account_info_iter)?; 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 index c55b240041f..ac1c88f642a 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs @@ -1,36 +1,24 @@ use { - super::ciphertext_extraction::BurnProofContextInfo, - crate::{check_zk_elgamal_proof_program_account, error::TokenError}, + super::ciphertext_extraction::BurnProofContextInfo, crate::error::TokenError, solana_zk_sdk::encryption::pod::elgamal::PodElGamalCiphertext, }; #[cfg(feature = "zk-ops")] use { super::ciphertext_extraction::{AuditableProofContextInfo, MintProofContextInfo}, - crate::check_system_program_account, - //crate::extension::confidential_transfer::verify_proof::{ - // verify_equality_proof, verify_transfer_range_proof, - //}, crate::proof::{decode_proof_instruction_context, verify_and_extract_context}, solana_program::{ account_info::{next_account_info, AccountInfo}, - msg, - program::invoke, program_error::ProgramError, sysvar::instructions::get_instruction_relative, }, solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey, - solana_zk_sdk::zk_elgamal_proof_program::instruction::{ - self as zk_token_proof_instruction, ContextStateInfo, ProofInstruction, - }, + solana_zk_sdk::zk_elgamal_proof_program::instruction::ProofInstruction, solana_zk_sdk::zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofContext, BatchedRangeProofU128Data, BatchedRangeProofU64Data, CiphertextCommitmentEqualityProofContext, CiphertextCommitmentEqualityProofData, - ProofType, }, - solana_zk_sdk::zk_elgamal_proof_program::state::ProofContextState, - spl_pod::bytemuck::pod_from_bytes, spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, std::slice::Iter, }; @@ -41,53 +29,17 @@ use { pub fn verify_mint_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, - close_split_context_state_on_execution: bool, ) -> Result { if proof_instruction_offset == 0 { - // interpret `account_info` as a context state account - let range_proof_account_info = next_account_info(account_info_iter)?; - let cipher_text_validity_account_info = next_account_info(account_info_iter)?; - let range_proof_context = verify_batched_u64_range_proof(range_proof_account_info)?; + let range_proof_context = verify_and_extract_context::< + BatchedRangeProofU64Data, + BatchedRangeProofContext, + >(account_info_iter, proof_instruction_offset, None)?; let ciphertext_validity_proof_context = - verify_3_ciphertext_validity_proof(cipher_text_validity_account_info)?; - - if close_split_context_state_on_execution { - let lamport_destination_account_info = next_account_info(account_info_iter)?; - let context_state_account_authority_info = next_account_info(account_info_iter)?; - let _zk_token_proof_program = next_account_info(account_info_iter)?; - - msg!("Closing equality proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: cipher_text_validity_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - cipher_text_validity_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - - msg!("Closing range proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: range_proof_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - range_proof_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - } + verify_and_extract_context::< + BatchedGroupedCiphertext3HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofContext, + >(account_info_iter, proof_instruction_offset, None)?; Ok(MintProofContextInfo::verify_and_extract( &ciphertext_validity_proof_context, @@ -101,14 +53,12 @@ pub fn verify_mint_proof( let ciphertext_validity_instruction = get_instruction_relative(proof_instruction_offset + 1, sysvar_account_info)?; - let range_proof_context = decode_proof_instruction_context::< - BatchedRangeProofU64Data, - BatchedRangeProofContext, - >( - account_info_iter, - ProofInstruction::VerifyBatchedRangeProofU64, - &range_proof_instruction, - )?; + let range_proof_context = + decode_proof_instruction_context::( + account_info_iter, + ProofInstruction::VerifyBatchedRangeProofU64, + &range_proof_instruction, + )?; let ciphertext_validity_proof_context = decode_proof_instruction_context::< BatchedGroupedCiphertext3HandlesValidityProofData, @@ -132,49 +82,23 @@ pub fn verify_mint_proof( pub fn verify_burn_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, - close_split_context_state_on_execution: bool, ) -> Result { if proof_instruction_offset == 0 { - let equality_proof_context_state_account_info = next_account_info(account_info_iter)?; - let ciphertext_validity_proof_context_state_account_info = - next_account_info(account_info_iter)?; - let range_proof_context_state_account_info = next_account_info(account_info_iter)?; - - if check_system_program_account(equality_proof_context_state_account_info.owner).is_ok() { - msg!("Equality proof context state account not initialized"); - return Err(ProgramError::UninitializedAccount); - } - - if check_system_program_account(ciphertext_validity_proof_context_state_account_info.owner) - .is_ok() - { - msg!("Ciphertext validity proof context state account not initialized"); - return Err(ProgramError::UninitializedAccount); - } - - if check_system_program_account(range_proof_context_state_account_info.owner).is_ok() { - msg!("Range proof context state account not initialized"); - return Err(ProgramError::UninitializedAccount); - } - let equality_proof_context = verify_and_extract_context::< CiphertextCommitmentEqualityProofData, CiphertextCommitmentEqualityProofContext, - >( - account_info_iter, - proof_instruction_offset, - None, - )?; - let ciphertext_validity_proof_context = verify_3_ciphertext_validity_proof( - ciphertext_validity_proof_context_state_account_info, - )?; + >(account_info_iter, proof_instruction_offset, None)?; - let range_proof_context = - verify_and_extract_context::( - account_info_iter, - proof_instruction_offset, - None, - )?; + let ciphertext_validity_proof_context = + verify_and_extract_context::< + BatchedGroupedCiphertext3HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofContext, + >(account_info_iter, proof_instruction_offset, None)?; + + let range_proof_context = verify_and_extract_context::< + BatchedRangeProofU128Data, + BatchedRangeProofContext, + >(account_info_iter, proof_instruction_offset, None)?; // The `TransferProofContextInfo` constructor verifies the consistency of the // individual proof context and generates a `TransferWithFeeProofInfo` struct @@ -185,61 +109,6 @@ pub fn verify_burn_proof( &range_proof_context, )?; - if close_split_context_state_on_execution { - let lamport_destination_account_info = next_account_info(account_info_iter)?; - let context_state_account_authority_info = next_account_info(account_info_iter)?; - let _zk_token_proof_program = next_account_info(account_info_iter)?; - - msg!("Closing equality proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: equality_proof_context_state_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - equality_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - - msg!("Closing ciphertext validity proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: ciphertext_validity_proof_context_state_account_info - .key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - ciphertext_validity_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - - msg!("Closing range proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: range_proof_context_state_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - range_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - } - Ok(transfer_proof_context) } else { let sysvar_account_info = next_account_info(account_info_iter)?; @@ -286,23 +155,6 @@ pub fn verify_burn_proof( } } -/// Verify and process batched u64 range proof for [ConfidentialMint] -/// instruction -pub fn verify_batched_u64_range_proof( - account_info: &AccountInfo<'_>, -) -> Result { - check_zk_elgamal_proof_program_account(account_info.owner)?; - let context_state_account_data = account_info.data.borrow(); - let range_proof_context_state = - pod_from_bytes::>(&context_state_account_data)?; - - if range_proof_context_state.proof_type != ProofType::BatchedRangeProofU64.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(range_proof_context_state.proof_context) -} - /// Validates the auditor mint/burn amounts from the instruction against those /// from zk-proofs #[cfg(feature = "zk-ops")] @@ -329,23 +181,3 @@ pub fn validate_auditor_ciphertext( Ok(()) } - -/// Verify and process ciphertext validity proof for [ConfidentialMint] and -/// [ConfidentialBurn] instructions. -pub fn verify_3_ciphertext_validity_proof( - account_info: &AccountInfo<'_>, -) -> Result { - check_zk_elgamal_proof_program_account(account_info.owner)?; - let context_state_account_data = account_info.data.borrow(); - let ciphertext_validity_proof_context_state = pod_from_bytes::< - ProofContextState, - >(&context_state_account_data)?; - - if ciphertext_validity_proof_context_state.proof_type - != ProofType::BatchedGroupedCiphertext3HandlesValidity.into() - { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(ciphertext_validity_proof_context_state.proof_context) -} diff --git a/token/program-2022/src/processor.rs b/token/program-2022/src/processor.rs index 697cf042f8a..92d582792ec 100644 --- a/token/program-2022/src/processor.rs +++ b/token/program-2022/src/processor.rs @@ -207,6 +207,7 @@ impl Processor { }; account.init_account_type()?; + println!("initialize account done"); Ok(()) } diff --git a/token/program/src/error.rs b/token/program/src/error.rs index a758d2c9ba7..b0f3c39437d 100644 --- a/token/program/src/error.rs +++ b/token/program/src/error.rs @@ -139,3 +139,4 @@ impl PrintProgramError for TokenError { } } } + From 0b20da35d541b5e7677826c05d06e3820d0b8655 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Wed, 4 Sep 2024 13:59:44 +0200 Subject: [PATCH 09/34] clippy --- token/program-2022-test/tests/close_account.rs | 2 +- .../src/extension/confidential_mint_burn/processor.rs | 4 ++-- token/program-2022/src/processor.rs | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/token/program-2022-test/tests/close_account.rs b/token/program-2022-test/tests/close_account.rs index 97706c126ed..d070b9d1134 100644 --- a/token/program-2022-test/tests/close_account.rs +++ b/token/program-2022-test/tests/close_account.rs @@ -162,7 +162,7 @@ async fn fail_init_after_close_mint() { assert_eq!( error, TokenClientError::Client(Box::new(TransportError::TransactionError( - TransactionError::InstructionError(3, InstructionError::InvalidAccountData,) + TransactionError::InstructionError(2, InstructionError::InvalidAccountData,) ))) ); let error = token.get_account(destination).await.unwrap_err(); diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index 137311b45d9..78410f1ad5d 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -343,8 +343,8 @@ fn process_confidential_burn( let new_source_available_balance = ciphertext_arithmetic::subtract_with_lo_hi( &confidential_transfer_account.available_balance, - &source_transfer_amount_lo, - &source_transfer_amount_hi, + source_transfer_amount_lo, + source_transfer_amount_hi, ) .ok_or(TokenError::CiphertextArithmeticFailed)?; diff --git a/token/program-2022/src/processor.rs b/token/program-2022/src/processor.rs index 92d582792ec..697cf042f8a 100644 --- a/token/program-2022/src/processor.rs +++ b/token/program-2022/src/processor.rs @@ -207,7 +207,6 @@ impl Processor { }; account.init_account_type()?; - println!("initialize account done"); Ok(()) } From 18aa58dc845bc63aeecaa991e74f61b4911cbd67 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 1 Oct 2024 15:57:36 -0300 Subject: [PATCH 10/34] mint burn with new proof generation --- token/cli/src/command.rs | 142 +++++++++----- token/client/src/token.rs | 55 ++---- .../proof-extraction/src/encryption.rs | 30 +++ .../proof-generation/src/errors.rs | 2 + .../proof-generation/src/mint.rs | 33 +++- .../tests/confidential_mint_burn.rs | 123 +++++++----- token/program-2022/src/error.rs | 7 + .../ciphertext_extraction.rs | 49 ----- .../confidential_mint_burn/instruction.rs | 179 +++++------------- .../extension/confidential_mint_burn/mod.rs | 8 +- .../confidential_mint_burn/processor.rs | 160 ++++++++-------- .../confidential_mint_burn/verify_proof.rs | 95 ++++------ token/program/src/error.rs | 1 - 13 files changed, 423 insertions(+), 461 deletions(-) diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 34df747519e..98fd636bb28 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -39,8 +39,8 @@ use { error::TokenError, extension::{ confidential_mint_burn::{ - instruction::BurnSplitContextStateAccounts, - proof_generation::{generate_burn_proofs, generate_mint_proofs}, + instruction::{BurnSplitContextStateAccounts, MintSplitContextStateAccounts}, + ConfidentialMintBurn, }, confidential_transfer::{ account_info::{ @@ -62,7 +62,6 @@ use { transfer_hook::TransferHook, BaseStateWithExtensions, ExtensionType, StateWithExtensionsOwned, }, - proof::ProofLocation, solana_zk_sdk::encryption::{ auth_encryption::AeKey, elgamal::{self, ElGamalKeypair}, @@ -75,7 +74,8 @@ use { token::{ComputeUnitLimit, ExtensionInitializationParams, ProofAccount, Token}, }, spl_token_confidential_transfer_proof_generation::{ - transfer::TransferProofData, withdraw::WithdrawProofData, + burn::burn_split_proof_data, mint::mint_split_proof_data, transfer::TransferProofData, + withdraw::WithdrawProofData, }, spl_token_group_interface::state::TokenGroup, spl_token_metadata_interface::state::{Field, TokenMetadata}, @@ -3444,59 +3444,100 @@ async fn command_deposit_withdraw_mint_confidential_tokens( } ConfidentialInstructionType::Mint => { let payer = config.fee_payer()?; - let range_proof_context_state_account = Keypair::new(); - let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); + + let equality_proof_context_state_account = Keypair::new(); + let equality_proof_context_pubkey = equality_proof_context_state_account.pubkey(); let ciphertext_validity_proof_context_state_account = Keypair::new(); let ciphertext_validity_proof_context_pubkey = ciphertext_validity_proof_context_state_account.pubkey(); + let range_proof_context_state_account = Keypair::new(); + let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); let mint_to_elgamal_pubkey = token.account_elgamal_pubkey(&token_account_address).await?; let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await?; let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await?; - let (range_proof, ciphertext_validity_proof, pedersen_openings) = generate_mint_proofs( + let dummy_elgamal = ElGamalKeypair::new_rand(); + let dummy_aes = AeKey::new_rand(); + let supply_elgamal_keypair = match elgamal_keypair { + Some(e) => e, + None => { + // if no ElGamalKeypair supplied just use a dummy for + // proof creation + &dummy_elgamal + } + }; + let supply_aes_key = match aes_key { + Some(e) => e, + None => { + // see above + &dummy_aes + } + }; + + let mint = token.get_mint_info().await?; + let conf_mb_ext = mint.get_extension::()?; + + let context_state_accounts = MintSplitContextStateAccounts { + equality_proof: &equality_proof_context_pubkey, + ciphertext_validity_proof: &ciphertext_validity_proof_context_pubkey, + range_proof: &range_proof_context_pubkey, + authority: &payer.pubkey(), + }; + + let proof_data = mint_split_proof_data( + &conf_mb_ext + .confidential_supply + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?, + &conf_mb_ext + .decryptable_supply + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?, amount, + supply_elgamal_keypair, + supply_aes_key, &mint_to_elgamal_pubkey, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, + &auditor_elgamal_pubkey.unwrap_or_default(), )?; + let equality_proof_signer = &[&equality_proof_context_state_account]; + let ciphertext_validity_proof_signer = + &[&ciphertext_validity_proof_context_state_account]; let range_proof_signer = &[&range_proof_context_state_account]; - let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; let context_state_auth = payer.pubkey(); let _ = try_join!( token.confidential_transfer_create_context_state_account( - &range_proof_context_pubkey, + &equality_proof_context_pubkey, &context_state_auth, - &range_proof, + &proof_data.equality_proof_data, true, - range_proof_signer, + equality_proof_signer, ), token.confidential_transfer_create_context_state_account( &ciphertext_validity_proof_context_pubkey, &context_state_auth, - &ciphertext_validity_proof, + &proof_data.ciphertext_validity_proof_data, false, ciphertext_validity_proof_signer, ), + token.confidential_transfer_create_context_state_account( + &range_proof_context_pubkey, + &context_state_auth, + &proof_data.range_proof_data, + true, + range_proof_signer, + ), )?; - let range_proof_location = - ProofLocation::ContextStateAccount(&range_proof_context_pubkey); - let ciphertext_validity_proof_location = - ProofLocation::ContextStateAccount(&ciphertext_validity_proof_context_pubkey); - let res = token .confidential_mint( &token_account_address, &owner, - amount, - auditor_elgamal_pubkey, supply_elgamal_pubkey, - range_proof_location, - ciphertext_validity_proof_location, - &pedersen_openings, + &context_state_accounts, + proof_data.new_decryptable_supply, &bulk_signers, ) .await?; @@ -4911,50 +4952,49 @@ async fn command_confidential_burn( .unwrap(); let transfer_account_info = TransferAccountInfo::new(extension); - let (equality_proof_data, ciphertext_validity_proof_data, range_proof_data, pedersen_openings) = - generate_burn_proofs( - &transfer_account_info - .available_balance - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?, - &transfer_account_info - .decryptable_available_balance - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?, - burn_amount, - elgamal_keypair, - aes_key, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, - ) - .unwrap(); + let proof_data = burn_split_proof_data( + &transfer_account_info + .available_balance + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?, + &transfer_account_info + .decryptable_available_balance + .try_into() + .map_err(|_| TokenError::MalformedCiphertext)?, + burn_amount, + elgamal_keypair, + aes_key, + &auditor_elgamal_pubkey.unwrap_or_default(), + &supply_elgamal_pubkey.unwrap_or_default(), + ) + .unwrap(); let range_proof_signer = &[&range_proof_context_state_account]; let equality_proof_signer = &[&equality_proof_context_state_account]; let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; // setup proofs let _ = try_join!( - token.confidential_transfer_create_context_state_account( - context_state_accounts.range_proof, - context_state_accounts.authority, - &range_proof_data, - true, - range_proof_signer, - ), token.confidential_transfer_create_context_state_account( context_state_accounts.equality_proof, context_state_accounts.authority, - &equality_proof_data, + &proof_data.equality_proof_data, false, equality_proof_signer, ), token.confidential_transfer_create_context_state_account( context_state_accounts.ciphertext_validity_proof, context_state_accounts.authority, - &ciphertext_validity_proof_data, + &proof_data.ciphertext_validity_proof_data, false, ciphertext_validity_proof_signer, ), + token.confidential_transfer_create_context_state_account( + context_state_accounts.range_proof, + context_state_accounts.authority, + &proof_data.range_proof_data, + true, + range_proof_signer, + ), )?; // do the burn @@ -4964,11 +5004,9 @@ async fn command_confidential_burn( &authority, &context_state_accounts, burn_amount, - auditor_elgamal_pubkey, supply_elgamal_pubkey, aes_key, &bulk_signers, - &pedersen_openings, ) .await?; diff --git a/token/client/src/token.rs b/token/client/src/token.rs index b982d3ead02..ec878b3aede 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -32,7 +32,9 @@ use { error::TokenError as Token2022Error, extension::{ confidential_mint_burn::{ - self, instruction::BurnSplitContextStateAccounts, ConfidentialMintBurn, + self, + instruction::{BurnSplitContextStateAccounts, MintSplitContextStateAccounts}, + ConfidentialMintBurn, }, confidential_transfer::{ self, @@ -40,10 +42,7 @@ use { combine_balances, ApplyPendingBalanceAccountInfo, EmptyAccountAccountInfo, TransferAccountInfo, WithdrawAccountInfo, }, - instruction::{ - BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU64Data, - ProofContextState, ZkProofData, - }, + instruction::{ProofContextState, ZkProofData}, ConfidentialTransferAccount, ConfidentialTransferMint, DecryptableBalance, }, confidential_transfer_fee::{ @@ -58,9 +57,8 @@ use { proof::{zk_proof_type_to_instruction, ProofData, ProofLocation}, solana_zk_sdk::{ encryption::{ - auth_encryption::AeKey, + auth_encryption::{AeCiphertext, AeKey}, elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey}, - pedersen::PedersenOpening, pod::elgamal::PodElGamalPubkey, }, zk_elgamal_proof_program::{ @@ -3483,33 +3481,27 @@ where &self, account: &Pubkey, authority: &Pubkey, - amount: u64, - auditor_elgamal_pubkey: Option, supply_elgamal_pubkey: Option, - range_proof_location: ProofLocation<'_, BatchedRangeProofU64Data>, - ciphertext_validity_proof_location: ProofLocation< - '_, - BatchedGroupedCiphertext3HandlesValidityProofData, - >, - pedersen_openings: &(PedersenOpening, PedersenOpening), + context_state_accounts: &MintSplitContextStateAccounts<'_>, + new_decryptable_supply: AeCiphertext, signing_keypairs: &S, ) -> TokenResult { let signing_pubkeys = signing_keypairs.pubkeys(); let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); + self.process_ixs( - &confidential_mint_burn::instruction::confidential_mint( - &self.program_id, - account, - &self.pubkey, - amount, - auditor_elgamal_pubkey, - supply_elgamal_pubkey, - authority, - &multisig_signers, - range_proof_location, - ciphertext_validity_proof_location, - pedersen_openings, - )?, + &[ + confidential_mint_burn::instruction::confidential_mint_with_split_proofs( + &self.program_id, + account, + &self.pubkey, + supply_elgamal_pubkey, + authority, + &multisig_signers, + context_state_accounts, + new_decryptable_supply, + )?, + ], signing_keypairs, ) .await @@ -3524,11 +3516,9 @@ where authority: &Pubkey, context_state_accounts: &BurnSplitContextStateAccounts<'_>, amount: u64, - auditor_elgamal_pubkey: Option, supply_elgamal_pubkey: Option, aes_key: &AeKey, signing_keypairs: &S, - pedersen_openings: &(PedersenOpening, PedersenOpening), ) -> TokenResult { let signing_pubkeys = signing_keypairs.pubkeys(); let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); @@ -3546,15 +3536,12 @@ where &confidential_mint_burn::instruction::confidential_burn_with_split_proofs( &self.program_id, ata_pubkey, - self.get_address(), - auditor_elgamal_pubkey, + &self.pubkey, supply_elgamal_pubkey, - amount, new_decryptable_available_balance.into(), context_state_accounts, authority, &multisig_signers, - pedersen_openings, )?, signing_keypairs, ) diff --git a/token/confidential-transfer/proof-extraction/src/encryption.rs b/token/confidential-transfer/proof-extraction/src/encryption.rs index b0a991235f3..a8badaa22fa 100644 --- a/token/confidential-transfer/proof-extraction/src/encryption.rs +++ b/token/confidential-transfer/proof-extraction/src/encryption.rs @@ -51,6 +51,36 @@ impl PodFeeCiphertext { #[repr(C)] pub struct PodBurnAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles); +impl PodBurnAmountCiphertext { + pub fn extract_commitment(&self) -> PodPedersenCommitment { + self.0.extract_commitment() + } + + pub fn try_extract_ciphertext( + &self, + index: usize, + ) -> Result { + 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 extract_commitment(&self) -> PodPedersenCommitment { + self.0.extract_commitment() + } + + pub fn try_extract_ciphertext( + &self, + index: usize, + ) -> Result { + self.0 + .try_extract_ciphertext(index) + .map_err(|_| TokenProofExtractionError::CiphertextExtraction) + } +} diff --git a/token/confidential-transfer/proof-generation/src/errors.rs b/token/confidential-transfer/proof-generation/src/errors.rs index 5cb5c14e794..fd11c8b8578 100644 --- a/token/confidential-transfer/proof-generation/src/errors.rs +++ b/token/confidential-transfer/proof-generation/src/errors.rs @@ -10,4 +10,6 @@ pub enum TokenProofGenerationError { IllegalAmountBitLength, #[error("fee calculation failed")] FeeCalculation, + #[error("supply decryption failed")] + SupplyDecryption, } diff --git a/token/confidential-transfer/proof-generation/src/mint.rs b/token/confidential-transfer/proof-generation/src/mint.rs index e25dc70a210..0cb392a43ca 100644 --- a/token/confidential-transfer/proof-generation/src/mint.rs +++ b/token/confidential-transfer/proof-generation/src/mint.rs @@ -8,6 +8,7 @@ use { auth_encryption::{AeCiphertext, AeKey}, elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, pedersen::Pedersen, + pod::auth_encryption::PodAeCiphertext, }, zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, @@ -27,6 +28,7 @@ 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( @@ -77,13 +79,33 @@ 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)?; + // fresh mints are initialized with a zeroed decryptable_supply + // TODO: @samkim is there a better way to do this? + let pod_decryptable_supply: PodAeCiphertext = current_decryptable_supply.clone().into(); + let current_decyptable_supply = if pod_decryptable_supply != PodAeCiphertext::default() { + // decrypt the current supply + current_decryptable_supply + .decrypt(supply_aes_key) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)? + } else { + 0 + }; + + // 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 = supply_elgamal_keypair + .pubkey() + .encrypt(current_decyptable_supply); + let ct_decryptable_to_current_diff = decryptable_supply_ciphertext - current_supply_ciphertext; + let decryptable_to_current_diff = supply_elgamal_keypair + .secret() + .decrypt_u32(&ct_decryptable_to_current_diff) + .ok_or(TokenProofGenerationError::SupplyDecryption)?; // compute the new supply - let new_supply = current_supply + let new_supply = current_decyptable_supply + .checked_add(decryptable_to_current_diff) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)? .checked_add(mint_amount) .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; @@ -142,5 +164,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/program-2022-test/tests/confidential_mint_burn.rs b/token/program-2022-test/tests/confidential_mint_burn.rs index 32d40436882..73cd50bc330 100644 --- a/token/program-2022-test/tests/confidential_mint_burn.rs +++ b/token/program-2022-test/tests/confidential_mint_burn.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "test-sbf")] +//#![cfg(feature = "test-sbf")] mod program_test; use { @@ -8,23 +8,26 @@ use { spl_token_2022::{ extension::{ confidential_mint_burn::{ - instruction::BurnSplitContextStateAccounts, - proof_generation::{generate_burn_proofs, generate_mint_proofs}, + instruction::{BurnSplitContextStateAccounts, MintSplitContextStateAccounts}, ConfidentialMintBurn, }, confidential_transfer::{ - account_info::TransferAccountInfo, - ConfidentialTransferAccount, + account_info::TransferAccountInfo, ConfidentialTransferAccount, }, BaseStateWithExtensions, }, proof::ProofLocation, - solana_zk_sdk::encryption::{elgamal::*, pod::elgamal::PodElGamalPubkey}, + solana_zk_sdk::encryption::{ + auth_encryption::AeKey, elgamal::*, pod::elgamal::PodElGamalPubkey, + }, }, spl_token_client::{ client::ProgramBanksClientProcessTransaction, token::{ExtensionInitializationParams, Token}, }, + spl_token_confidential_transfer_proof_generation::{ + burn::burn_split_proof_data, mint::mint_split_proof_data, + }, std::convert::TryInto, }; @@ -36,6 +39,7 @@ async fn test_confidential_mint() { let authority = Keypair::new(); let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); + let supply_aes_key = AeKey::new_rand(); let mut context = TestContext::new().await; context @@ -62,6 +66,8 @@ async fn test_confidential_mint() { &alice_meta.token_account, &authority.pubkey(), MINT_AMOUNT, + &auditor_elgamal_keypair, + &supply_aes_key, &[&authority], ) .await; @@ -118,6 +124,7 @@ async fn test_confidential_burn() { let authority = Keypair::new(); let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); + let supply_aes_key = AeKey::new_rand(); let mut context = TestContext::new().await; context @@ -144,6 +151,8 @@ async fn test_confidential_burn() { &alice_meta.token_account, &authority.pubkey(), MINT_AMOUNT, + &auditor_elgamal_keypair, + &supply_aes_key, &[&authority], ) .await; @@ -195,20 +204,19 @@ async fn test_confidential_burn() { .unwrap(); let transfer_account_info = TransferAccountInfo::new(extension); - let (equality_proof_data, ciphertext_validity_proof_data, range_proof_data, pedersen_openings) = - generate_burn_proofs( - &transfer_account_info.available_balance.try_into().unwrap(), - &transfer_account_info - .decryptable_available_balance - .try_into() - .unwrap(), - BURN_AMOUNT, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, - ) - .unwrap(); + let proof_data = burn_split_proof_data( + &transfer_account_info.available_balance.try_into().unwrap(), + &transfer_account_info + .decryptable_available_balance + .try_into() + .unwrap(), + BURN_AMOUNT, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + &auditor_elgamal_pubkey.unwrap_or_default(), + &supply_elgamal_pubkey.unwrap_or_default(), + ) + .unwrap(); let range_proof_signer = &[&range_proof_context_state_account]; let equality_proof_signer = &[&equality_proof_context_state_account]; @@ -218,7 +226,7 @@ async fn test_confidential_burn() { .confidential_transfer_create_context_state_account( context_state_accounts.range_proof, context_state_accounts.authority, - &range_proof_data, + &proof_data.range_proof_data, true, range_proof_signer, ) @@ -228,7 +236,7 @@ async fn test_confidential_burn() { .confidential_transfer_create_context_state_account( context_state_accounts.equality_proof, context_state_accounts.authority, - &equality_proof_data, + &proof_data.equality_proof_data, false, equality_proof_signer, ) @@ -238,7 +246,7 @@ async fn test_confidential_burn() { .confidential_transfer_create_context_state_account( context_state_accounts.ciphertext_validity_proof, context_state_accounts.authority, - &ciphertext_validity_proof_data, + &proof_data.ciphertext_validity_proof_data, false, ciphertext_validity_proof_signer, ) @@ -252,11 +260,9 @@ async fn test_confidential_burn() { &alice.pubkey(), &context_state_accounts, BURN_AMOUNT, - auditor_elgamal_pubkey, supply_elgamal_pubkey, &alice_meta.aes_key, &[&alice], - &pedersen_openings, ) .await .unwrap(); @@ -319,6 +325,7 @@ async fn test_rotate_supply_elgamal() { let authority = Keypair::new(); let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); + let supply_aes_key = AeKey::new_rand(); let mut context = TestContext::new().await; context @@ -345,6 +352,8 @@ async fn test_rotate_supply_elgamal() { &alice_meta.token_account, &authority.pubkey(), MINT_AMOUNT, + &auditor_elgamal_keypair, + &supply_aes_key, &[&authority], ) .await; @@ -394,36 +403,55 @@ async fn mint_tokens( token_account: &Pubkey, authority: &Pubkey, mint_amount: u64, + supply_elgamal_keypair: &ElGamalKeypair, + supply_aes_key: &AeKey, bulk_signers: &impl Signers, ) { let context_state_auth = Keypair::new(); - let range_proof_context_state_account = Keypair::new(); - let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); + let equality_proof_context_state_account = Keypair::new(); + let equality_proof_context_pubkey = equality_proof_context_state_account.pubkey(); let ciphertext_validity_proof_context_state_account = Keypair::new(); let ciphertext_validity_proof_context_pubkey = ciphertext_validity_proof_context_state_account.pubkey(); + let range_proof_context_state_account = Keypair::new(); + let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); let mint_to_elgamal_pubkey = token.account_elgamal_pubkey(token_account).await.unwrap(); let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await.unwrap(); let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await.unwrap(); - let (range_proof, ciphertext_validity_proof, pedersen_openings) = generate_mint_proofs( + let mint = token.get_mint_info().await.unwrap(); + let conf_mb_ext = mint.get_extension::().unwrap(); + + let context_state_accounts = MintSplitContextStateAccounts { + equality_proof: &equality_proof_context_pubkey, + ciphertext_validity_proof: &ciphertext_validity_proof_context_pubkey, + range_proof: &range_proof_context_pubkey, + authority: &context_state_auth.pubkey(), + }; + + let proof_data = mint_split_proof_data( + &conf_mb_ext.confidential_supply.try_into().unwrap(), + &conf_mb_ext.decryptable_supply.try_into().unwrap(), mint_amount, + supply_elgamal_keypair, + supply_aes_key, &mint_to_elgamal_pubkey, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, + &auditor_elgamal_pubkey.unwrap_or_default(), ) .unwrap(); - let range_proof_signer = &[&range_proof_context_state_account]; + let equality_proof_signer = &[&equality_proof_context_state_account]; let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; + let range_proof_signer = &[&range_proof_context_state_account]; + token .confidential_transfer_create_context_state_account( - &range_proof_context_pubkey, + &equality_proof_context_pubkey, &context_state_auth.pubkey(), - &range_proof, - true, - range_proof_signer, + &proof_data.equality_proof_data, + false, + equality_proof_signer, ) .await .unwrap(); @@ -431,32 +459,33 @@ async fn mint_tokens( .confidential_transfer_create_context_state_account( &ciphertext_validity_proof_context_pubkey, &context_state_auth.pubkey(), - &ciphertext_validity_proof, + &proof_data.ciphertext_validity_proof_data, false, ciphertext_validity_proof_signer, ) .await .unwrap(); + token + .confidential_transfer_create_context_state_account( + &range_proof_context_pubkey, + &context_state_auth.pubkey(), + &proof_data.range_proof_data, + false, + range_proof_signer, + ) + .await + .unwrap(); - let range_proof_location = ProofLocation::ContextStateAccount(&range_proof_context_pubkey); - let ciphertext_validity_proof_location = - ProofLocation::ContextStateAccount(&ciphertext_validity_proof_context_pubkey); - - println!("MINT: account is {token_account} with owner {authority}"); token .confidential_mint( token_account, authority, - mint_amount, - auditor_elgamal_pubkey, supply_elgamal_pubkey, - range_proof_location, - ciphertext_validity_proof_location, - &pedersen_openings, + &context_state_accounts, + proof_data.new_decryptable_supply, bulk_signers, ) .await - .map_err(|e| println!("{}", e)) .unwrap(); let close_context_auth = context_state_auth.pubkey(); diff --git a/token/program-2022/src/error.rs b/token/program-2022/src/error.rs index 6d6773dfcac..726eb6808b9 100644 --- a/token/program-2022/src/error.rs +++ b/token/program-2022/src/error.rs @@ -263,6 +263,9 @@ pub enum TokenError { /// Withdraw / Deposit not allowed for confidential-mint-burn #[error("When the confidential-mint-burn extension is enabled, mints are only allowed into the confidential balance. Likewise conversions confidential token balance to normal balance and vice versa are illegal.")] IllegalMintBurnConversion, + /// Undecryptable supply when trying to generate confidential-mint proofs + #[error("Could not decrypt difference between current supply and decryptable supply when generating mint proofs")] + SupplyDecryption, } impl From for ProgramError { fn from(e: TokenError) -> Self { @@ -453,6 +456,9 @@ impl PrintProgramError for TokenError { TokenError::IllegalMintBurnConversion => { msg!("Conversions from normal to confidential token balance and vice versa are illegal if the confidential-mint-burn extension is enabled") } + TokenError::SupplyDecryption => { + msg!("Could not decrypt difference between current supply and decryptable supply when generating mint proofs") + } } } } @@ -465,6 +471,7 @@ impl From for TokenError { TokenProofGenerationError::NotEnoughFunds => TokenError::InsufficientFunds, TokenProofGenerationError::IllegalAmountBitLength => TokenError::IllegalBitLength, TokenProofGenerationError::FeeCalculation => TokenError::FeeCalculation, + TokenProofGenerationError::SupplyDecryption => TokenError::SupplyDecryption, } } } diff --git a/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs b/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs index f3c7b7f5c47..c1acb20bc59 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs @@ -42,17 +42,6 @@ impl From> for MintBurnAmountCiphertext { } } -/// Trait to retrieve auditor amounts from proof context information -#[cfg(feature = "zk-ops")] -pub trait AuditableProofContextInfo { - /// Return the low 16 bits of the amount to be audited - fn auditor_amount_lo(&self) -> Result; - /// Return the high 32 bits of the amount to be audited - fn auditor_amount_hi(&self) -> Result; - /// Return the auditors ElGamal public key - fn auditor_pubkey(&self) -> &PodElGamalPubkey; -} - /// The proof context information needed to process a [Transfer] instruction. #[cfg(feature = "zk-ops")] pub struct MintProofContextInfo { @@ -68,25 +57,6 @@ pub struct MintProofContextInfo { pub ciphertext_hi: MintBurnAmountCiphertext, } -#[cfg(feature = "zk-ops")] -impl AuditableProofContextInfo for MintProofContextInfo { - fn auditor_amount_lo(&self) -> Result { - self.ciphertext_lo - .0 - .try_extract_ciphertext(1) - .map_err(|_| ProgramError::InvalidAccountData) - } - fn auditor_amount_hi(&self) -> Result { - self.ciphertext_hi - .0 - .try_extract_ciphertext(1) - .map_err(|_| ProgramError::InvalidAccountData) - } - fn auditor_pubkey(&self) -> &PodElGamalPubkey { - &self.auditor_pubkey - } -} - /// The proof context information needed to process a [Transfer] instruction. #[cfg(feature = "zk-ops")] pub struct BurnProofContextInfo { @@ -104,25 +74,6 @@ pub struct BurnProofContextInfo { pub new_burner_ciphertext: PodElGamalCiphertext, } -#[cfg(feature = "zk-ops")] -impl AuditableProofContextInfo for BurnProofContextInfo { - fn auditor_amount_lo(&self) -> Result { - self.ciphertext_lo - .0 - .try_extract_ciphertext(1) - .map_err(|_| ProgramError::InvalidAccountData) - } - fn auditor_amount_hi(&self) -> Result { - self.ciphertext_hi - .0 - .try_extract_ciphertext(1) - .map_err(|_| ProgramError::InvalidAccountData) - } - fn auditor_pubkey(&self) -> &PodElGamalPubkey { - &self.auditor_pubkey - } -} - #[cfg(feature = "zk-ops")] impl MintProofContextInfo { /// Create the mint proof context information needed to process a diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 023d4ccc3a9..174b90ad82a 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -1,17 +1,13 @@ #[cfg(not(target_os = "solana"))] -use crate::{ - error::TokenError, - extension::confidential_transfer::processor::verify_and_split_deposit_amount, - proof::ProofLocation, -}; +use crate::error::TokenError; #[cfg(not(target_os = "solana"))] -use solana_zk_sdk::zk_elgamal_proof_program::proof_data::{ - BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU64Data, - CiphertextCiphertextEqualityProofData, -}; +use solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey; +#[cfg(not(target_os = "solana"))] +use solana_zk_sdk::zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData; #[cfg(not(target_os = "solana"))] use solana_zk_sdk::{ encryption::{ + auth_encryption::AeCiphertext, elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, pedersen::PedersenOpening, }, @@ -30,19 +26,17 @@ use { sysvar, }, }; -#[cfg(not(target_os = "solana"))] -use solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey; use { crate::extension::confidential_transfer::DecryptableBalance, bytemuck::{Pod, Zeroable}, num_enum::{IntoPrimitive, TryFromPrimitive}, solana_program::pubkey::Pubkey, - solana_zk_sdk::encryption::pod::elgamal::{PodElGamalCiphertext}, + solana_zk_sdk::encryption::pod::auth_encryption::PodAeCiphertext, spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, }; #[cfg(feature = "serde-traits")] use { - crate::serialization::{aeciphertext_fromstr, elgamalciphertext_fromstr}, + crate::serialization::aeciphertext_fromstr, serde::{Deserialize, Serialize}, }; @@ -122,14 +116,9 @@ pub struct RotateSupplyElGamalData { #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] #[repr(C)] pub struct MintInstructionData { - /// low 16 bits of encrypted amount to be minted, exposes mint amounts - /// to the auditor through the data received via `get_transaction` - #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] - pub audit_amount_lo: PodElGamalCiphertext, - /// high 48 bits of encrypted amount to be minted, exposes mint amounts - /// to the auditor through the data received via `get_transaction` - #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] - pub audit_amount_hi: PodElGamalCiphertext, + /// 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::VerifyBatchedRangeProofU64` /// instruction to the `ConfidentialMint` instruction in the /// transaction. The @@ -144,15 +133,9 @@ pub struct MintInstructionData { #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] #[repr(C)] pub struct BurnInstructionData { - /// The new source decryptable balance if the transfer succeeds + /// 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, - /// low 16 bits of encrypted amount to be minted - #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] - pub auditor_lo: PodElGamalCiphertext, - /// high 48 bits of encrypted amount to be minted - #[cfg_attr(feature = "serde-traits", serde(with = "elgamalciphertext_fromstr"))] - pub auditor_hi: PodElGamalCiphertext, /// Relative location of the /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction /// to the `ConfidentialBurn` instruction in the transaction. The @@ -272,24 +255,16 @@ pub fn rotate_supply_elgamal( /// Create a `ConfidentialMint` instruction #[allow(clippy::too_many_arguments)] #[cfg(not(target_os = "solana"))] -pub fn confidential_mint( +pub fn confidential_mint_with_split_proofs( token_program_id: &Pubkey, token_account: &Pubkey, mint: &Pubkey, - amount: u64, - auditor_elgamal_pubkey: Option, supply_elgamal_pubkey: Option, authority: &Pubkey, multisig_signers: &[&Pubkey], - range_proof_location: ProofLocation<'_, BatchedRangeProofU64Data>, - ciphertext_validity_proof_location: ProofLocation< - '_, - BatchedGroupedCiphertext3HandlesValidityProofData, - >, - pedersen_openings: &(PedersenOpening, PedersenOpening), -) -> Result, ProgramError> { - use crate::proof::ProofData; - + context_accounts: &MintSplitContextStateAccounts, + new_decryptable_supply: AeCiphertext, +) -> Result { 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 @@ -300,6 +275,19 @@ pub fn confidential_mint( accounts.push(AccountMeta::new_readonly(*mint, false)); } + accounts.push(AccountMeta::new_readonly( + *context_accounts.equality_proof, + false, + )); + accounts.push(AccountMeta::new_readonly( + *context_accounts.ciphertext_validity_proof, + false, + )); + accounts.push(AccountMeta::new_readonly( + *context_accounts.range_proof, + false, + )); + accounts.push(AccountMeta::new_readonly( *authority, multisig_signers.is_empty(), @@ -308,82 +296,16 @@ pub fn confidential_mint( accounts.push(AccountMeta::new_readonly(**multisig_signer, true)); } - let (amount_lo, amount_hi) = verify_and_split_deposit_amount(amount)?; - let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); - let audit_amount_lo = auditor_elgamal_pubkey.encrypt_with(amount_lo, &pedersen_openings.0); - let audit_amount_hi = auditor_elgamal_pubkey.encrypt_with(amount_hi, &pedersen_openings.1); - - let proof_instruction_offset = match range_proof_location { - ProofLocation::InstructionOffset(proof_instruction_offset, _) => { - accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); - proof_instruction_offset.into() - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - 0 - } - }; - match ciphertext_validity_proof_location { - ProofLocation::InstructionOffset(_, _) => { - // already pushed instruction introspection sysvar previously - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - } - } - - let mut instrs = vec![encode_instruction( + Ok(encode_instruction( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, ConfidentialMintBurnInstruction::ConfidentialMint, &MintInstructionData { - audit_amount_lo: audit_amount_lo.into(), - audit_amount_hi: audit_amount_hi.into(), - proof_instruction_offset, + new_decryptable_supply: new_decryptable_supply.into(), + proof_instruction_offset: 0, }, - )]; - - if let ProofLocation::InstructionOffset(proof_instruction_offset, range_proof_data) = - range_proof_location - { - if let ProofLocation::InstructionOffset(_, ciphertext_validity_proof_data) = - ciphertext_validity_proof_location - { - // This constructor appends the proof instruction right after the - // `ConfidentialMint` instruction. This means that the proof instruction - // offset must be always be 1. - let proof_instruction_offset: i8 = proof_instruction_offset.into(); - if proof_instruction_offset != 1 { - return Err(TokenError::InvalidProofInstructionOffset.into()); - } - - match range_proof_data { - ProofData::InstructionData(data) => instrs - .push(ProofInstruction::VerifyBatchedRangeProofU64.encode_verify_proof(None, data)), - ProofData::RecordAccount(address, offset) => instrs.push( - ProofInstruction::VerifyBatchedRangeProofU64 - .encode_verify_proof_from_account(None, address, offset), - ), - }; - - match ciphertext_validity_proof_data { - ProofData::InstructionData(data) => instrs.push( - ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity - .encode_verify_proof(None, data), - ), - ProofData::RecordAccount(address, offset) => instrs.push( - ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity - .encode_verify_proof_from_account(None, address, offset), - ), - }; - } else { - // both proofs have to either be context state or instruction offset - return Err(ProgramError::InvalidArgument); - } - }; - - Ok(instrs) + )) } /// Context state accounts used in confidential mint @@ -399,6 +321,19 @@ pub struct BurnSplitContextStateAccounts<'a> { pub authority: &'a Pubkey, } +/// 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 `ConfidentialBurn` instruction #[allow(clippy::too_many_arguments)] #[cfg(not(target_os = "solana"))] @@ -406,27 +341,21 @@ pub fn confidential_burn_with_split_proofs( token_program_id: &Pubkey, token_account: &Pubkey, mint: &Pubkey, - auditor_pubkey: Option, supply_elgamal_pubkey: Option, - burn_amount: u64, new_decryptable_available_balance: DecryptableBalance, context_accounts: &BurnSplitContextStateAccounts, authority: &Pubkey, multisig_signers: &[&Pubkey], - pedersen_openings: &(PedersenOpening, PedersenOpening), ) -> Result, ProgramError> { Ok(vec![inner_confidential_burn_with_split_proofs( token_program_id, token_account, mint, - auditor_pubkey, supply_elgamal_pubkey, - burn_amount, new_decryptable_available_balance, context_accounts, authority, multisig_signers, - pedersen_openings, )?]) } @@ -437,14 +366,11 @@ pub fn inner_confidential_burn_with_split_proofs( token_program_id: &Pubkey, token_account: &Pubkey, mint: &Pubkey, - auditor_pubkey: Option, supply_elgamal_pubkey: Option, - burn_amount: u64, new_decryptable_available_balance: DecryptableBalance, context_accounts: &BurnSplitContextStateAccounts, authority: &Pubkey, multisig_signers: &[&Pubkey], - pedersen_openings: &(PedersenOpening, PedersenOpening), ) -> Result { check_program_account(token_program_id)?; let mut accounts = vec![AccountMeta::new(*token_account, false)]; @@ -476,19 +402,6 @@ pub fn inner_confidential_burn_with_split_proofs( accounts.push(AccountMeta::new_readonly(**multisig_signer, true)); } - let (burn_hi, burn_lo) = if let Some(apk) = auditor_pubkey { - let (opening_hi, opening_lo) = pedersen_openings; - let (amount_lo, amount_hi) = verify_and_split_deposit_amount(burn_amount)?; - let burn_hi = apk.encrypt_with(amount_hi, opening_hi); - let burn_lo = apk.encrypt_with(amount_lo, opening_lo); - (burn_hi.into(), burn_lo.into()) - } else { - ( - PodElGamalCiphertext::zeroed(), - PodElGamalCiphertext::zeroed(), - ) - }; - Ok(encode_instruction( token_program_id, accounts, @@ -496,8 +409,6 @@ pub fn inner_confidential_burn_with_split_proofs( ConfidentialMintBurnInstruction::ConfidentialBurn, &BurnInstructionData { new_decryptable_available_balance, - auditor_hi: burn_hi, - auditor_lo: burn_lo, proof_instruction_offset: 0, }, )) diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index 5db81a5113a..ca40e98b7d6 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -2,7 +2,9 @@ use { crate::extension::{Extension, ExtensionType}, bytemuck::{Pod, Zeroable}, solana_program::pubkey::Pubkey, - solana_zk_sdk::encryption::pod::elgamal::PodElGamalCiphertext, + solana_zk_sdk::encryption::pod::{ + auth_encryption::PodAeCiphertext, elgamal::PodElGamalCiphertext, + }, spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, }; @@ -36,8 +38,10 @@ pub struct ConfidentialMintBurn { /// Authority to modify the `ConfidentialMintBurnMint` configuration and to /// mint new confidential tokens pub mint_authority: Pubkey, - /// The confidential supply of the mint + /// The confidential supply of the mint (encrypted by `encrypiton_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: OptionalNonZeroElGamalPubkey, } diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index 78410f1ad5d..b8d1b810964 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -1,8 +1,5 @@ #[cfg(feature = "zk-ops")] -use { - super::verify_proof::validate_auditor_ciphertext, super::verify_proof::verify_burn_proof, - spl_token_confidential_transfer_ciphertext_arithmetic as ciphertext_arithmetic, -}; +use spl_token_confidential_transfer_ciphertext_arithmetic as ciphertext_arithmetic; use { crate::{ check_program_account, @@ -13,7 +10,7 @@ use { BurnInstructionData, ConfidentialMintBurnInstruction, InitializeMintData, MintInstructionData, RotateSupplyElGamalData, UpdateAuthorityData, }, - verify_proof::verify_mint_proof, + verify_proof::{verify_burn_proof, verify_mint_proof}, ConfidentialMintBurn, }, confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint}, @@ -25,6 +22,7 @@ use { processor::Processor, proof::decode_proof_instruction_context, }, + bytemuck::Zeroable, solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, @@ -33,10 +31,13 @@ use { pubkey::Pubkey, sysvar::instructions::get_instruction_relative, }, - solana_zk_sdk::zk_elgamal_proof_program::{ - instruction::ProofInstruction, - proof_data::{ - CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData, + solana_zk_sdk::{ + encryption::pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey}, + zk_elgamal_proof_program::{ + instruction::ProofInstruction, + proof_data::{ + CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData, + }, }, }, }; @@ -54,6 +55,7 @@ fn process_initialize_mint(accounts: &[AccountInfo], data: &InitializeMintData) conf_mint_burn_ext.mint_authority = data.authority; conf_mint_burn_ext.supply_elgamal_pubkey = data.supply_elgamal_pubkey; + conf_mint_burn_ext.decryptable_supply = PodAeCiphertext::zeroed(); Ok(()) } @@ -155,8 +157,6 @@ fn process_confidential_mint( 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)?; - let authority_info = next_account_info(account_info_iter)?; - let authority_info_data_len = authority_info.data_len(); check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow_mut(); @@ -167,6 +167,15 @@ fn process_confidential_mint( .auditor_elgamal_pubkey; let conf_mint_ext = mint.get_extension_mut::()?; + let proof_context = verify_mint_proof(account_info_iter, data.proof_instruction_offset as i64)?; + + check_program_account(token_account_info.owner)?; + let token_account_data = &mut token_account_info.data.borrow_mut(); + let mut token_account = PodStateWithExtensionsMut::::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, &conf_mint_ext.mint_authority, @@ -175,9 +184,12 @@ fn process_confidential_mint( account_info_iter.as_slice(), )?; - check_program_account(token_account_info.owner)?; - let token_account_data = &mut token_account_info.data.borrow_mut(); - let mut token_account = PodStateWithExtensionsMut::::unpack(token_account_data)?; + if token_account + .get_extension::() + .is_ok() + { + return Err(TokenError::NonTransferable.into()); + } if token_account.base.is_frozen() { return Err(TokenError::AccountFrozen.into()); @@ -191,38 +203,24 @@ fn process_confidential_mint( // it'd enable creating SOL out of thin air assert!(!token_account.base.is_native()); - let proof_context = verify_mint_proof( - account_info_iter, - data.proof_instruction_offset as i64, - )?; - - if token_account - .get_extension::() - .is_ok() - { - return Err(TokenError::NonTransferable.into()); - } - let confidential_transfer_account = token_account.get_extension_mut::()?; confidential_transfer_account.valid_as_destination()?; - if proof_context.mint_to_pubkey != confidential_transfer_account.elgamal_pubkey { + if proof_context.mint_pubkeys.destination != confidential_transfer_account.elgamal_pubkey { return Err(ProgramError::InvalidInstructionData); } - validate_auditor_ciphertext( - &auditor_elgamal_pubkey, - &proof_context, - &data.audit_amount_lo, - &data.audit_amount_hi, - )?; + if let Some(auditor_pk) = Into::>::into(auditor_elgamal_pubkey) { + if auditor_pk != 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 - .ciphertext_lo - .0 + .mint_amount_ciphertext_lo .try_extract_ciphertext(0) .map_err(|_| ProgramError::InvalidAccountData)?, ) @@ -230,8 +228,7 @@ fn process_confidential_mint( confidential_transfer_account.pending_balance_hi = ciphertext_arithmetic::add( &confidential_transfer_account.pending_balance_hi, &proof_context - .ciphertext_hi - .0 + .mint_amount_ciphertext_hi .try_extract_ciphertext(0) .map_err(|_| ProgramError::InvalidAccountData)?, ) @@ -240,22 +237,26 @@ fn process_confidential_mint( confidential_transfer_account.increment_pending_balance_credit_counter()?; // update supply - if conf_mint_ext.supply_elgamal_pubkey.is_some() { + if let Some(supply_pk) = + Into::>::into(conf_mint_ext.supply_elgamal_pubkey) + { + if supply_pk != proof_context.mint_pubkeys.supply { + return Err(ProgramError::InvalidInstructionData); + } let current_supply = conf_mint_ext.confidential_supply; conf_mint_ext.confidential_supply = ciphertext_arithmetic::add_with_lo_hi( ¤t_supply, &proof_context - .ciphertext_lo - .0 + .mint_amount_ciphertext_lo .try_extract_ciphertext(2) .map_err(|_| ProgramError::InvalidAccountData)?, &proof_context - .ciphertext_hi - .0 + .mint_amount_ciphertext_hi .try_extract_ciphertext(2) .map_err(|_| ProgramError::InvalidAccountData)?, ) .ok_or(TokenError::CiphertextArithmeticFailed)?; + conf_mint_ext.decryptable_supply = data.new_decryptable_supply; } Ok(()) @@ -281,26 +282,14 @@ fn process_confidential_burn( .auditor_elgamal_pubkey; let conf_mint_ext = mint.get_extension_mut::()?; - // The zero-knowledge proof certifies that: - // 1. the burn amount is encrypted in the correct form - // 2. the source account has enough balance to burn the amount - let proof_context = verify_burn_proof( - account_info_iter, - data.proof_instruction_offset as i64, - )?; - - let authority_info = next_account_info(account_info_iter)?; + let proof_context = verify_burn_proof(account_info_iter, data.proof_instruction_offset as i64)?; check_program_account(token_account_info.owner)?; - let authority_info_data_len = authority_info.data_len(); let token_account_data = &mut token_account_info.data.borrow_mut(); let mut token_account = PodStateWithExtensionsMut::::unpack(token_account_data)?; - if token_account - .get_extension::() - .is_ok() - { - return Err(TokenError::NonTransferable.into()); - } + + let authority_info = next_account_info(account_info_iter)?; + let authority_info_data_len = authority_info.data_len(); Processor::validate_owner( program_id, @@ -310,6 +299,13 @@ fn process_confidential_burn( account_info_iter.as_slice(), )?; + if token_account + .get_extension::() + .is_ok() + { + return Err(TokenError::NonTransferable.into()); + } + if token_account.base.is_frozen() { return Err(TokenError::AccountFrozen.into()); } @@ -324,22 +320,18 @@ fn process_confidential_burn( // Check that the source encryption public key is consistent with what was // actually used to generate the zkp. - if proof_context.burner_pubkey != confidential_transfer_account.elgamal_pubkey { + if proof_context.burn_pubkeys.source != confidential_transfer_account.elgamal_pubkey { return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } - let source_transfer_amount_lo = - &proof_context - .ciphertext_lo - .0 - .try_extract_ciphertext(0) - .map_err(|_| ProgramError::InvalidAccountData)?; - let source_transfer_amount_hi = - &proof_context - .ciphertext_hi - .0 - .try_extract_ciphertext(0) - .map_err(|_| ProgramError::InvalidAccountData)?; + let source_transfer_amount_lo = &proof_context + .burn_amount_ciphertext_lo + .try_extract_ciphertext(0) + .map_err(|_| ProgramError::InvalidAccountData)?; + let source_transfer_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, @@ -350,7 +342,7 @@ fn process_confidential_burn( // 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.new_burner_ciphertext { + if new_source_available_balance != proof_context.remaining_balance_ciphertext { return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); } @@ -358,26 +350,28 @@ fn process_confidential_burn( confidential_transfer_account.decryptable_available_balance = data.new_decryptable_available_balance; - validate_auditor_ciphertext( - &auditor_elgamal_pubkey, - &proof_context, - &data.auditor_lo, - &data.auditor_hi, - )?; + if let Some(auditor_pk) = Into::>::into(auditor_elgamal_pubkey) { + if auditor_pk != proof_context.burn_pubkeys.auditor { + return Err(ProgramError::InvalidInstructionData); + } + } // update supply - if conf_mint_ext.supply_elgamal_pubkey.is_some() { + if let Some(supply_pk) = + Into::>::into(conf_mint_ext.supply_elgamal_pubkey) + { + if supply_pk != proof_context.burn_pubkeys.supply { + return Err(ProgramError::InvalidInstructionData); + } let current_supply = conf_mint_ext.confidential_supply; conf_mint_ext.confidential_supply = ciphertext_arithmetic::subtract_with_lo_hi( ¤t_supply, &proof_context - .ciphertext_lo - .0 + .burn_amount_ciphertext_lo .try_extract_ciphertext(2) .map_err(|_| ProgramError::InvalidAccountData)?, &proof_context - .ciphertext_hi - .0 + .burn_amount_ciphertext_hi .try_extract_ciphertext(2) .map_err(|_| ProgramError::InvalidAccountData)?, ) 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 index ac1c88f642a..3cae4effb49 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs @@ -1,17 +1,12 @@ -use { - super::ciphertext_extraction::BurnProofContextInfo, crate::error::TokenError, - solana_zk_sdk::encryption::pod::elgamal::PodElGamalCiphertext, -}; +use crate::error::TokenError; #[cfg(feature = "zk-ops")] use { - super::ciphertext_extraction::{AuditableProofContextInfo, MintProofContextInfo}, crate::proof::{decode_proof_instruction_context, verify_and_extract_context}, solana_program::{ account_info::{next_account_info, AccountInfo}, program_error::ProgramError, sysvar::instructions::get_instruction_relative, }, - solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey, solana_zk_sdk::zk_elgamal_proof_program::instruction::ProofInstruction, solana_zk_sdk::zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext3HandlesValidityProofContext, @@ -19,7 +14,8 @@ use { BatchedRangeProofU128Data, BatchedRangeProofU64Data, CiphertextCommitmentEqualityProofContext, CiphertextCommitmentEqualityProofData, }, - spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, + spl_token_confidential_transfer_proof_extraction::burn::BurnProofContext, + spl_token_confidential_transfer_proof_extraction::mint::MintProofContext, std::slice::Iter, }; @@ -29,29 +25,48 @@ use { pub fn verify_mint_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, -) -> Result { +) -> Result { if proof_instruction_offset == 0 { - let range_proof_context = verify_and_extract_context::< - BatchedRangeProofU64Data, - BatchedRangeProofContext, + let equality_proof_context = verify_and_extract_context::< + CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofContext, >(account_info_iter, proof_instruction_offset, None)?; + let ciphertext_validity_proof_context = verify_and_extract_context::< BatchedGroupedCiphertext3HandlesValidityProofData, BatchedGroupedCiphertext3HandlesValidityProofContext, >(account_info_iter, proof_instruction_offset, None)?; - Ok(MintProofContextInfo::verify_and_extract( + let range_proof_context = verify_and_extract_context::< + BatchedRangeProofU128Data, + BatchedRangeProofContext, + >(account_info_iter, proof_instruction_offset, None)?; + + Ok(MintProofContext::verify_and_extract( + &equality_proof_context, &ciphertext_validity_proof_context, &range_proof_context, - )?) + ) + .map_err(|e| -> TokenError { e.into() })?) } else { let sysvar_account_info = next_account_info(account_info_iter)?; - let range_proof_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; + let equality_proof_instruction = + get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; let ciphertext_validity_instruction = get_instruction_relative(proof_instruction_offset + 1, sysvar_account_info)?; + let range_proof_instruction = + get_instruction_relative(proof_instruction_offset + 2, sysvar_account_info)?; + + let equality_proof_context = decode_proof_instruction_context::< + CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofContext, + >( + account_info_iter, + ProofInstruction::VerifyBatchedRangeProofU64, + &equality_proof_instruction, + )?; let range_proof_context = decode_proof_instruction_context::( @@ -69,10 +84,12 @@ pub fn verify_mint_proof( &ciphertext_validity_instruction, )?; - Ok(MintProofContextInfo::verify_and_extract( + Ok(MintProofContext::verify_and_extract( + &equality_proof_context, &ciphertext_validity_proof_context, &range_proof_context, - )?) + ) + .map_err(|e| -> TokenError { e.into() })?) } } @@ -82,7 +99,7 @@ pub fn verify_mint_proof( pub fn verify_burn_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, -) -> Result { +) -> Result { if proof_instruction_offset == 0 { let equality_proof_context = verify_and_extract_context::< CiphertextCommitmentEqualityProofData, @@ -100,16 +117,12 @@ pub fn verify_burn_proof( BatchedRangeProofContext, >(account_info_iter, proof_instruction_offset, None)?; - // The `TransferProofContextInfo` constructor verifies the consistency of the - // individual proof context and generates a `TransferWithFeeProofInfo` struct - // that is used to process the rest of the token-2022 logic. - let transfer_proof_context = BurnProofContextInfo::verify_and_extract( + Ok(BurnProofContext::verify_and_extract( &equality_proof_context, &ciphertext_validity_proof_context, &range_proof_context, - )?; - - Ok(transfer_proof_context) + ) + .map_err(|e| -> TokenError { e.into() })?) } else { let sysvar_account_info = next_account_info(account_info_iter)?; let equality_proof_instruction = @@ -147,37 +160,11 @@ pub fn verify_burn_proof( &ciphertext_validity_instruction, )?; - Ok(BurnProofContextInfo::verify_and_extract( + Ok(BurnProofContext::verify_and_extract( &equality_proof_context, &ciphertext_validity_proof_context, &range_proof_context, - )?) + ) + .map_err(|e| -> TokenError { e.into() })?) } } - -/// Validates the auditor mint/burn amounts from the instruction against those -/// from zk-proofs -#[cfg(feature = "zk-ops")] -pub fn validate_auditor_ciphertext( - auditor_elgamal_pubkey: &OptionalNonZeroElGamalPubkey, - proof_context: &impl AuditableProofContextInfo, - auditor_lo: &PodElGamalCiphertext, - auditor_hi: &PodElGamalCiphertext, -) -> Result<(), ProgramError> { - if let Some(auditor_pk) = Into::>::into(*auditor_elgamal_pubkey) { - // Check that the auditor encryption public key is consistent with what was - // actually used to generate the zkp. - if proof_context.auditor_pubkey() != &auditor_pk { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } - - if auditor_lo != &proof_context.auditor_amount_lo()? { - return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); - } - if auditor_hi != &proof_context.auditor_amount_hi()? { - return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); - } - } - - Ok(()) -} diff --git a/token/program/src/error.rs b/token/program/src/error.rs index b0f3c39437d..a758d2c9ba7 100644 --- a/token/program/src/error.rs +++ b/token/program/src/error.rs @@ -139,4 +139,3 @@ impl PrintProgramError for TokenError { } } } - From 59234ba31672ae80d15467110bf9dafd69df4828 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 1 Oct 2024 16:00:46 -0300 Subject: [PATCH 11/34] remove old mintburn proof generation --- .../ciphertext_extraction.rs | 259 ----------------- .../extension/confidential_mint_burn/mod.rs | 6 - .../proof_generation.rs | 262 ------------------ 3 files changed, 527 deletions(-) delete mode 100644 token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs delete mode 100644 token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs diff --git a/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs b/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs deleted file mode 100644 index c1acb20bc59..00000000000 --- a/token/program-2022/src/extension/confidential_mint_burn/ciphertext_extraction.rs +++ /dev/null @@ -1,259 +0,0 @@ -#[cfg(feature = "zk-ops")] -use crate::{ - error::TokenError, - solana_program::program_error::ProgramError, - solana_zk_sdk::{ - encryption::pod::grouped_elgamal::PodGroupedElGamalCiphertext3Handles, - zk_elgamal_proof_program::proof_data::{ - BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext, - }, - }, -}; -#[cfg(feature = "zk-ops")] -use bytemuck::{Pod, Zeroable}; -#[cfg(feature = "zk-ops")] -#[cfg(not(target_os = "solana"))] -use solana_zk_sdk::encryption::grouped_elgamal::GroupedElGamalCiphertext; -#[cfg(feature = "zk-ops")] -use solana_zk_sdk::{ - encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, - zk_elgamal_proof_program::proof_data::CiphertextCommitmentEqualityProofContext, -}; - -/// Wrapper for `GroupedElGamalCiphertext2Handles` when used during minting -/// -/// The ciphertext consists of the following 32-byte components -/// that are serialized in order: -/// 1. The `commitment` component that encodes the mint amount. key. -/// 2. The `decryption handle` component with respect to the destination or -/// source public key. -/// 3. The `decryption handle` component with respect to the auditor public -/// key. -/// 4. The `decryption handle` component with respect to the supply public -/// key. -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -pub struct MintBurnAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles); - -#[cfg(not(target_os = "solana"))] -impl From> for MintBurnAmountCiphertext { - fn from(value: GroupedElGamalCiphertext<3>) -> Self { - Self(value.into()) - } -} - -/// The proof context information needed to process a [Transfer] instruction. -#[cfg(feature = "zk-ops")] -pub struct MintProofContextInfo { - /// destination elgamal pubkey used in proof generation - pub mint_to_pubkey: PodElGamalPubkey, - /// auditor elgamal pubkey used in proof generation - pub auditor_pubkey: PodElGamalPubkey, - /// supply elgamal pubkey used in proof generation - pub supply_pubkey: PodElGamalPubkey, - /// Ciphertext containing the low 16 bits of the mint amount - pub ciphertext_lo: MintBurnAmountCiphertext, - /// Ciphertext containing the high 32 bits of the mint amount - pub ciphertext_hi: MintBurnAmountCiphertext, -} - -/// The proof context information needed to process a [Transfer] instruction. -#[cfg(feature = "zk-ops")] -pub struct BurnProofContextInfo { - /// destination elgamal pubkey used in proof generation - pub burner_pubkey: PodElGamalPubkey, - /// auditor elgamal pubkey used in proof generation - pub auditor_pubkey: PodElGamalPubkey, - /// supply elgamal pubkey used in proof generation - pub supply_pubkey: PodElGamalPubkey, - /// Ciphertext containing the low 16 bits of the burn amount - pub ciphertext_lo: MintBurnAmountCiphertext, - /// Ciphertext containing the high 32 bits of the burn amount - pub ciphertext_hi: MintBurnAmountCiphertext, - /// The new available balance ciphertext for the burning account - pub new_burner_ciphertext: PodElGamalCiphertext, -} - -#[cfg(feature = "zk-ops")] -impl MintProofContextInfo { - /// Create the mint proof context information needed to process a - /// [ConfidentialMint] instruction from context state accounts - /// after verifying their consistency. - pub fn verify_and_extract( - ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext, - range_proof_context: &BatchedRangeProofContext, - ) -> Result { - // The ciphertext validity proof context consists of the destination ElGamal - // public key, auditor ElGamal public key, and the transfer amount - // ciphertexts. All of these fields should be returned as part of - // `MintProofContextInfo`. In addition, the commitments pertaining - // to the mint amount ciphertexts should be checked with range proof for - // consistency. - let BatchedGroupedCiphertext3HandlesValidityProofContext { - first_pubkey: mint_to_pubkey, - // the orignal proof context member names were given with transfers - // in mind as this was it's only usage, so the remapping here looks - // a bit confusing - second_pubkey: auditor_pubkey, - third_pubkey: supply_pubkey, - grouped_ciphertext_lo: mint_amount_ciphertext_lo, - grouped_ciphertext_hi: mint_amount_ciphertext_hi, - } = ciphertext_validity_proof_context; - - // The range proof context consists of the Pedersen commitments and bit-lengths - // for which the range proof is proved. The commitments must consist of - // three commitments pertaining to the new source available balance, the - // low bits of the transfer amount, and high bits of the transfer - // amount. These commitments must be checked for bit lengths `64`, `16`, - // and `32`. - let BatchedRangeProofContext { - commitments: range_proof_commitments, - bit_lengths: range_proof_bit_lengths, - } = range_proof_context; - - // check that the range proof was created for the correct set of Pedersen - // commitments - let mint_amount_commitment_lo = mint_amount_ciphertext_lo.extract_commitment(); - let mint_amount_commitment_hi = mint_amount_ciphertext_hi.extract_commitment(); - - let expected_commitments = [mint_amount_commitment_lo, mint_amount_commitment_hi]; - - if !range_proof_commitments - .iter() - .zip(expected_commitments.iter()) - .all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment) - { - return Err(ProgramError::InvalidInstructionData); - } - - // check that the range proof was created for the correct number of bits - const MINT_AMOUNT_LO_BIT_LENGTH: u8 = 16; - const MINT_AMOUNT_HI_BIT_LENGTH: u8 = 32; - const PADDING_BIT_LENGTH: u8 = 16; - let expected_bit_lengths = [ - MINT_AMOUNT_LO_BIT_LENGTH, - MINT_AMOUNT_HI_BIT_LENGTH, - PADDING_BIT_LENGTH, - ] - .iter(); - - if !range_proof_bit_lengths - .iter() - .zip(expected_bit_lengths) - .all(|(proof_len, expected_len)| proof_len == expected_len) - { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(Self { - mint_to_pubkey: *mint_to_pubkey, - auditor_pubkey: *auditor_pubkey, - supply_pubkey: *supply_pubkey, - ciphertext_lo: MintBurnAmountCiphertext(*mint_amount_ciphertext_lo), - ciphertext_hi: MintBurnAmountCiphertext(*mint_amount_ciphertext_hi), - }) - } -} - -#[cfg(feature = "zk-ops")] -impl BurnProofContextInfo { - /// Create a transfer proof context information needed to process a - /// [Transfer] instruction from split proof contexts after verifying - /// their consistency. - pub fn verify_and_extract( - equality_proof_context: &CiphertextCommitmentEqualityProofContext, - ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext, - range_proof_context: &BatchedRangeProofContext, - ) -> Result { - // The equality proof context consists of the source ElGamal public key, the new - // source available balance ciphertext, and the new source available - // commitment. The public key and ciphertext should be returned as parts - // of `TransferProofContextInfo` and the commitment should be checked - // with range proof for consistency. - let CiphertextCommitmentEqualityProofContext { - pubkey: source_pubkey, - ciphertext: new_source_ciphertext, - commitment: new_source_commitment, - } = equality_proof_context; - - // The ciphertext validity proof context consists of the destination ElGamal - // public key, auditor ElGamal public key, and the transfer amount - // ciphertexts. All of these fields should be returned as part of - // `TransferProofContextInfo`. In addition, the commitments pertaining - // to the transfer amount ciphertexts should be checked with range proof for - // consistency. - let BatchedGroupedCiphertext3HandlesValidityProofContext { - first_pubkey: burner_pubkey, - // see MintProofContextInfo::verify_and_extract - second_pubkey: auditor_pubkey, - third_pubkey: supply_pubkey, - grouped_ciphertext_lo: transfer_amount_ciphertext_lo, - grouped_ciphertext_hi: transfer_amount_ciphertext_hi, - } = ciphertext_validity_proof_context; - - if burner_pubkey != source_pubkey { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } - - // The range proof context consists of the Pedersen commitments and bit-lengths - // for which the range proof is proved. The commitments must consist of - // three commitments pertaining to the new source available balance, the - // low bits of the transfer amount, and high bits of the transfer - // amount. These commitments must be checked for bit lengths `64`, `16`, - // and `32`. - let BatchedRangeProofContext { - commitments: range_proof_commitments, - bit_lengths: range_proof_bit_lengths, - } = range_proof_context; - - // check that the range proof was created for the correct set of Pedersen - // commitments - let transfer_amount_commitment_lo = transfer_amount_ciphertext_lo.extract_commitment(); - let transfer_amount_commitment_hi = transfer_amount_ciphertext_hi.extract_commitment(); - - let expected_commitments = [ - *new_source_commitment, - transfer_amount_commitment_lo, - transfer_amount_commitment_hi, - // the fourth dummy commitment can be any commitment - ]; - - if !range_proof_commitments - .iter() - .zip(expected_commitments.iter()) - .all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment) - { - return Err(ProgramError::InvalidInstructionData); - } - - // check that the range proof was created for the correct number of bits - const REMAINING_BALANCE_BIT_LENGTH: u8 = 64; - const TRANSFER_AMOUNT_LO_BIT_LENGTH: u8 = 16; - const TRANSFER_AMOUNT_HI_BIT_LENGTH: u8 = 32; - const PADDING_BIT_LENGTH: u8 = 16; - let expected_bit_lengths = [ - REMAINING_BALANCE_BIT_LENGTH, - TRANSFER_AMOUNT_LO_BIT_LENGTH, - TRANSFER_AMOUNT_HI_BIT_LENGTH, - PADDING_BIT_LENGTH, - ] - .iter(); - - if !range_proof_bit_lengths - .iter() - .zip(expected_bit_lengths) - .all(|(proof_len, expected_len)| proof_len == expected_len) - { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(Self { - burner_pubkey: *burner_pubkey, - auditor_pubkey: *auditor_pubkey, - supply_pubkey: *supply_pubkey, - ciphertext_lo: MintBurnAmountCiphertext(*transfer_amount_ciphertext_lo), - ciphertext_hi: MintBurnAmountCiphertext(*transfer_amount_ciphertext_hi), - new_burner_ciphertext: *new_source_ciphertext, - }) - } -} diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index ca40e98b7d6..fd5ff4b1d04 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -22,15 +22,9 @@ pub mod instruction; /// Confidential Mint-Burn Extension processor pub mod processor; -/// Confidential Mint-Burn proof generation -pub mod proof_generation; - /// Confidential Mint-Burn proof verification pub mod verify_proof; -/// Confidential Mint-Burn proof verification -pub mod ciphertext_extraction; - /// Confidential mint-burn mint configuration #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] #[repr(C)] diff --git a/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs b/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs deleted file mode 100644 index fb04df13f38..00000000000 --- a/token/program-2022/src/extension/confidential_mint_burn/proof_generation.rs +++ /dev/null @@ -1,262 +0,0 @@ -#[cfg(not(target_os = "solana"))] -use solana_program::program_error::ProgramError; -#[cfg(not(target_os = "solana"))] -use solana_zk_sdk::encryption::{elgamal::ElGamalPubkey, pedersen::PedersenOpening}; -#[cfg(not(target_os = "solana"))] -use solana_zk_sdk::{ - encryption::{ - auth_encryption::{AeCiphertext, AeKey}, - elgamal::ElGamalCiphertext, - elgamal::ElGamalKeypair, - grouped_elgamal::GroupedElGamal, - pedersen::Pedersen, - }, - zk_elgamal_proof_program::proof_data::{ - BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, - BatchedRangeProofU64Data, CiphertextCommitmentEqualityProofData, - }, -}; -#[cfg(not(target_os = "solana"))] -use { - crate::{ - error::TokenError, - extension::confidential_transfer::processor::verify_and_split_deposit_amount, - }, - spl_token_confidential_transfer_ciphertext_arithmetic::subtract_with_lo_hi, -}; - -/// Generates proof data for mint instruction -#[cfg(not(target_os = "solana"))] -pub fn generate_mint_proofs( - mint_amount: u64, - destination_elgamal_pubkey: &ElGamalPubkey, - auditor_elgamal_pubkey: &Option, - supply_elgamal_pubkey: &Option, -) -> Result< - ( - BatchedRangeProofU64Data, - BatchedGroupedCiphertext3HandlesValidityProofData, - (PedersenOpening, PedersenOpening), - ), - ProgramError, -> { - let (amount_lo, amount_hi) = verify_and_split_deposit_amount(mint_amount)?; - let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); - let supply_elgamal_pubkey = supply_elgamal_pubkey.unwrap_or_default(); - - const MINT_AMOUNT_LO_BIT_LENGTH: usize = 16; - const MINT_AMOUNT_HI_BIT_LENGTH: usize = 32; - const PADDING_BIT_LENGTH: usize = 16; - - // Encrypt the `lo` and `hi` transfer amounts. - let mint_amount_opening_lo = PedersenOpening::new_rand(); - let mint_amount_grouped_ciphertext_lo = GroupedElGamal::<3>::encrypt_with( - [ - destination_elgamal_pubkey, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, - ], - amount_lo, - &mint_amount_opening_lo, - ); - - let mint_amount_opening_hi = PedersenOpening::new_rand(); - let mint_amount_grouped_ciphertext_hi = GroupedElGamal::<3>::encrypt_with( - [ - destination_elgamal_pubkey, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, - ], - amount_hi, - &mint_amount_opening_hi, - ); - - let (padding_commitment, padding_opening) = Pedersen::new(0_u64); - - Ok(( - BatchedRangeProofU64Data::new( - vec![ - &mint_amount_grouped_ciphertext_lo.commitment, - &mint_amount_grouped_ciphertext_hi.commitment, - &padding_commitment, - ], - vec![amount_lo, amount_hi, 0], - vec![ - MINT_AMOUNT_LO_BIT_LENGTH, - MINT_AMOUNT_HI_BIT_LENGTH, - PADDING_BIT_LENGTH, - ], - vec![ - &mint_amount_opening_lo, - &mint_amount_opening_hi, - &padding_opening, - ], - ) - .map_err(|_| TokenError::ProofGeneration)?, - BatchedGroupedCiphertext3HandlesValidityProofData::new( - destination_elgamal_pubkey, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, - &mint_amount_grouped_ciphertext_lo, - &mint_amount_grouped_ciphertext_hi, - amount_lo, - amount_hi, - &mint_amount_opening_lo, - &mint_amount_opening_hi, - ) - .map_err(|_| TokenError::ProofGeneration)?, - (mint_amount_opening_lo, mint_amount_opening_hi), - )) -} - -/// Generates proof data for burn instruction -#[cfg(not(target_os = "solana"))] -#[allow(clippy::type_complexity)] -pub fn generate_burn_proofs( - current_available_balance: &ElGamalCiphertext, - current_decryptable_available_balance: &AeCiphertext, - burn_amount: u64, - source_elgamal_keypair: &ElGamalKeypair, - aes_key: &AeKey, - auditor_elgamal_pubkey: &Option, - supply_elgamal_pubkey: &Option, -) -> Result< - ( - CiphertextCommitmentEqualityProofData, - BatchedGroupedCiphertext3HandlesValidityProofData, - BatchedRangeProofU128Data, - (PedersenOpening, PedersenOpening), - ), - TokenError, -> { - let burner_elgamal_pubkey = source_elgamal_keypair.pubkey(); - let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or_default(); - let supply_elgamal_pubkey = supply_elgamal_pubkey.unwrap_or_default(); - - // Split the transfer amount into the low and high bit components. - let (burn_amount_lo, burn_amount_hi) = verify_and_split_deposit_amount(burn_amount)?; - - // Encrypt the `lo` and `hi` transfer amounts. - let burn_amount_opening_lo = PedersenOpening::new_rand(); - let burn_amount_grouped_ciphertext_lo = GroupedElGamal::<3>::encrypt_with( - [ - burner_elgamal_pubkey, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, - ], - burn_amount_lo, - &burn_amount_opening_lo, - ); - - let burn_amount_opening_hi = PedersenOpening::new_rand(); - let burn_amount_grouped_ciphertext_hi = GroupedElGamal::<3>::encrypt_with( - [ - burner_elgamal_pubkey, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, - ], - burn_amount_hi, - &burn_amount_opening_hi, - ); - - // Decrypt the current available balance at the source - let current_decrypted_available_balance = current_decryptable_available_balance - .decrypt(aes_key) - .ok_or(TokenError::AccountDecryption)?; - - // Compute the remaining balance at the source - let new_decrypted_available_balance = current_decrypted_available_balance - .checked_sub(burn_amount) - .ok_or(TokenError::InsufficientFunds)?; - - // Create a new Pedersen commitment for the remaining balance at the source - let (new_available_balance_commitment, new_source_opening) = - Pedersen::new(new_decrypted_available_balance); - - // Compute the remaining balance at the source as ElGamal ciphertexts - let transfer_amount_source_ciphertext_lo = burn_amount_grouped_ciphertext_lo - .to_elgamal_ciphertext(0) - .expect("ElGamalCiphertext for burn incorrectly generated"); - - let transfer_amount_source_ciphertext_hi = burn_amount_grouped_ciphertext_hi - .to_elgamal_ciphertext(0) - .expect("ElGamalCiphertext for burn incorrectly generated"); - - let current_available_balance = (*current_available_balance).into(); - let new_available_balance_ciphertext = subtract_with_lo_hi( - ¤t_available_balance, - &transfer_amount_source_ciphertext_lo.into(), - &transfer_amount_source_ciphertext_hi.into(), - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; - let new_available_balance_ciphertext: ElGamalCiphertext = new_available_balance_ciphertext - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?; - - // generate equality proof data - let equality_proof_data = CiphertextCommitmentEqualityProofData::new( - source_elgamal_keypair, - &new_available_balance_ciphertext, - &new_available_balance_commitment, - &new_source_opening, - new_decrypted_available_balance, - ) - .map_err(|_| TokenError::ProofGeneration)?; - - // generate ciphertext validity data - let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new( - burner_elgamal_pubkey, - &auditor_elgamal_pubkey, - &supply_elgamal_pubkey, - &burn_amount_grouped_ciphertext_lo, - &burn_amount_grouped_ciphertext_hi, - burn_amount_lo, - burn_amount_hi, - &burn_amount_opening_lo, - &burn_amount_opening_hi, - ) - .map_err(|_| TokenError::ProofGeneration)?; - - // generate range proof data - const REMAINING_BALANCE_BIT_LENGTH: usize = 64; - const TRANSFER_AMOUNT_LO_BIT_LENGTH: usize = 16; - const TRANSFER_AMOUNT_HI_BIT_LENGTH: usize = 32; - const PADDING_BIT_LENGTH: usize = 16; - - let (padding_commitment, padding_opening) = Pedersen::new(0_u64); - - let range_proof_data = BatchedRangeProofU128Data::new( - vec![ - &new_available_balance_commitment, - &burn_amount_grouped_ciphertext_lo.commitment, - &burn_amount_grouped_ciphertext_hi.commitment, - &padding_commitment, - ], - vec![ - new_decrypted_available_balance, - burn_amount_lo, - burn_amount_hi, - 0, - ], - vec![ - REMAINING_BALANCE_BIT_LENGTH, - TRANSFER_AMOUNT_LO_BIT_LENGTH, - TRANSFER_AMOUNT_HI_BIT_LENGTH, - PADDING_BIT_LENGTH, - ], - vec![ - &new_source_opening, - &burn_amount_opening_lo, - &burn_amount_opening_hi, - &padding_opening, - ], - ) - .map_err(|_| TokenError::ProofGeneration)?; - - Ok(( - equality_proof_data, - ciphertext_validity_proof_data, - range_proof_data, - (burn_amount_opening_hi, burn_amount_opening_lo), - )) -} From 89c7cd20d316eeb0c14d9cca646decfb57c02a23 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 1 Oct 2024 16:22:59 -0300 Subject: [PATCH 12/34] cleanup --- token/cli/src/clap_app.rs | 21 +++++++- token/cli/src/command.rs | 14 ++++- token/cli/src/encryption_keypair.rs | 17 +++--- token/client/src/token.rs | 52 ++++++++++++++----- .../proof-generation/src/mint.rs | 2 +- .../tests/confidential_mint_burn.rs | 12 ++--- 6 files changed, 88 insertions(+), 30 deletions(-) diff --git a/token/cli/src/clap_app.rs b/token/cli/src/clap_app.rs index 864c96f4bea..427a8c4a294 100644 --- a/token/cli/src/clap_app.rs +++ b/token/cli/src/clap_app.rs @@ -2848,11 +2848,20 @@ pub fn app<'a, 'b>( .value_name("CONFIDENTIAL_SUPPLY_KEYPAIR") .takes_value(true) .help( - "The confidential supply encryption keypair used to decrypt the supply. \ + "The confidential supply encryption keypair used to decrypt ElGamalCiphertext supply. \ Either the authority or the confidential-supply-keypair have \ to be specified in order for the supply to be decrypted." ) ) + .arg( + Arg::with_name("confidential_supply_aes_key") + .long("confidential-supply-aes-key") + .value_name("CONFIDENTIAL_SUPPLY_AES_KEY") + .takes_value(true) + .help( + "The aes key used to decrypt the decryptable portion of the confidential supply." + ) + ) .nonce_args(true) ) .subcommand( @@ -2888,6 +2897,16 @@ pub fn app<'a, 'b>( "The current confidential supply encryption keypair." ) ) + .arg( + Arg::with_name("supply_aes_key") + .long("supply-aes-key") + .value_name("SUPPLY_AES_KEY") + .takes_value(true) + .required(true) + .help( + "The aes key to decrypt the decryptable confidential supply." + ) + ) .arg( Arg::with_name("new_supply_keypair") .long("new-supply-keypair") diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 98fd636bb28..57471bf940e 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -4828,10 +4828,18 @@ pub async fn process_command<'a>( ElGamalKeypair::new_from_signer(&*auth_signer, b"").unwrap() }; + let aes_key = if arg_matches.is_present("confidential_supply_aes_key") { + aes_key_of(arg_matches, "confidential_supply_aes_key").unwrap() + } else { + let (auth_signer, _auth) = + config.signer_or_default(arg_matches, "authority", &mut wallet_manager); + + AeKey::new_from_signer(&*auth_signer, b"").unwrap() + }; let token_cl = token_client_from_config(config, &token, None)?; let supply = token_cl - .confidential_supply(&elgamal_keypair) + .confidential_supply(&elgamal_keypair, &aes_key) .await .map_err(|e| format!("Could not fetch confidential supply for {token}: {e}",))?; @@ -4843,6 +4851,7 @@ pub async fn process_command<'a>( .unwrap(); let cur_elgamal_keypair = elgamal_keypair_of(arg_matches, "current_supply_keypair").unwrap(); + let supply_aes_key = aes_key_of(arg_matches, "supply_aes_key").unwrap(); let new_elgamal_keypair = elgamal_keypair_of(arg_matches, "new_supply_keypair").unwrap(); let (auth_signer, auth) = @@ -4855,8 +4864,9 @@ pub async fn process_command<'a>( .rotate_supply_elgamal( &auth, &cur_elgamal_keypair, + &supply_aes_key, &new_elgamal_keypair, - &[auth_signer], + &[&auth_signer], ) .await?, false, diff --git a/token/cli/src/encryption_keypair.rs b/token/cli/src/encryption_keypair.rs index f28bd69fd6e..f61e9ff50b1 100644 --- a/token/cli/src/encryption_keypair.rs +++ b/token/cli/src/encryption_keypair.rs @@ -3,12 +3,9 @@ //! NOTE: this module should be remoeved in the next Solana upgrade. use { - base64::{prelude::BASE64_STANDARD, Engine}, - clap::ArgMatches, - spl_token_2022::solana_zk_sdk::encryption::{ - elgamal::{ElGamalKeypair, ElGamalPubkey}, - pod::elgamal::PodElGamalPubkey, - }, + base64::{prelude::BASE64_STANDARD, Engine}, clap::ArgMatches, solana_sdk::signer::EncodableKey, spl_token_2022::solana_zk_sdk::encryption::{ + auth_encryption::AeKey, elgamal::{ElGamalKeypair, ElGamalPubkey}, pod::elgamal::PodElGamalPubkey + } }; const ELGAMAL_PUBKEY_MAX_BASE64_LEN: usize = 44; @@ -68,6 +65,14 @@ pub(crate) fn elgamal_keypair_of( ElGamalKeypair::read_json_file(path).map_err(|e| e.to_string()) } +pub(crate) fn aes_key_of( + matches: &ArgMatches, + name: &str, +) -> Result { + let path = matches.value_of(name).unwrap(); + AeKey::read_from_file(path).map_err(|e| e.to_string()) +} + fn elgamal_pubkey_from_str(s: &str) -> Option { if s.len() > ELGAMAL_PUBKEY_MAX_BASE64_LEN { return None; diff --git a/token/client/src/token.rs b/token/client/src/token.rs index ec878b3aede..e67c65c89e7 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -59,7 +59,7 @@ use { encryption::{ auth_encryption::{AeCiphertext, AeKey}, elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey}, - pod::elgamal::PodElGamalPubkey, + pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey}, }, zk_elgamal_proof_program::{ self, @@ -3598,23 +3598,46 @@ where pub async fn confidential_supply( &self, supply_elgamal_keypair: &ElGamalKeypair, + supply_aes_key: &AeKey, ) -> Result { let mint = self.get_mint_info().await?; let confidential_mint_burn_ext = mint.get_extension::()?; - // Currently only correctly displays supplies until u32::MAX, - // the supply ciphertext will still be correct after, but it - // can't be decrypted here until a corresponding decryption - // method is added to the solana-zk-token-sdk - Ok(supply_elgamal_keypair + let current_decyptable_supply = + if confidential_mint_burn_ext.decryptable_supply != PodAeCiphertext::default() { + // decrypt the current supply + let decryptable_supply: AeCiphertext = confidential_mint_burn_ext + .decryptable_supply + .try_into() + .map_err(|_| TokenError::AccountDecryption)?; + decryptable_supply + .decrypt(supply_aes_key) + .ok_or(TokenError::AccountDecryption)? + } else { + 0 + }; + + // 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 = supply_elgamal_keypair + .pubkey() + .encrypt(current_decyptable_supply); + let current_supply: ElGamalCiphertext = confidential_mint_burn_ext + .confidential_supply + .try_into() + .map_err(|_| TokenError::AccountDecryption)?; + let ct_decryptable_to_current_diff = decryptable_supply_ciphertext - current_supply; + let decryptable_to_current_diff = supply_elgamal_keypair .secret() - .decrypt_u32( - &TryInto::::try_into( - confidential_mint_burn_ext.confidential_supply, - ) - .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData))?, - ) - .unwrap_or_default()) + .decrypt_u32(&ct_decryptable_to_current_diff) + .ok_or(TokenError::AccountDecryption)?; + + // compute the total supply + current_decyptable_supply + .checked_sub(decryptable_to_current_diff) + .ok_or(TokenError::TokenProgramError(String::from( + "Confidential supply underflow", + ))) } pub async fn supply_elgamal_pubkey(&self) -> TokenResult> { @@ -3633,6 +3656,7 @@ where &self, authority: &Pubkey, supply_elgamal_keypair: &ElGamalKeypair, + supply_aes_key: &AeKey, new_supply_elgamal_keypair: &ElGamalKeypair, signing_keypairs: &S, ) -> TokenResult { @@ -3641,7 +3665,7 @@ where let mint = self.get_mint_info().await?; let extension_state = mint.get_extension::()?; - let current_supply = self.confidential_supply(supply_elgamal_keypair).await?; + let current_supply = self.confidential_supply(supply_elgamal_keypair, supply_aes_key).await?; self.process_ixs( &confidential_mint_burn::instruction::rotate_supply_elgamal( diff --git a/token/confidential-transfer/proof-generation/src/mint.rs b/token/confidential-transfer/proof-generation/src/mint.rs index 0cb392a43ca..849dfc5de20 100644 --- a/token/confidential-transfer/proof-generation/src/mint.rs +++ b/token/confidential-transfer/proof-generation/src/mint.rs @@ -104,7 +104,7 @@ pub fn mint_split_proof_data( // compute the new supply let new_supply = current_decyptable_supply - .checked_add(decryptable_to_current_diff) + .checked_sub(decryptable_to_current_diff) .ok_or(TokenProofGenerationError::IllegalAmountBitLength)? .checked_add(mint_amount) .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; diff --git a/token/program-2022-test/tests/confidential_mint_burn.rs b/token/program-2022-test/tests/confidential_mint_burn.rs index 73cd50bc330..bba0057caa1 100644 --- a/token/program-2022-test/tests/confidential_mint_burn.rs +++ b/token/program-2022-test/tests/confidential_mint_burn.rs @@ -16,7 +16,6 @@ use { }, BaseStateWithExtensions, }, - proof::ProofLocation, solana_zk_sdk::encryption::{ auth_encryption::AeKey, elgamal::*, pod::elgamal::PodElGamalPubkey, }, @@ -112,7 +111,7 @@ async fn test_confidential_mint() { assert_eq!( token - .confidential_supply(&auditor_elgamal_keypair,) + .confidential_supply(&auditor_elgamal_keypair, &supply_aes_key) .await .unwrap(), MINT_AMOUNT @@ -159,7 +158,7 @@ async fn test_confidential_burn() { assert_eq!( token - .confidential_supply(&auditor_elgamal_keypair,) + .confidential_supply(&auditor_elgamal_keypair, &supply_aes_key) .await .unwrap(), MINT_AMOUNT @@ -313,7 +312,7 @@ async fn test_confidential_burn() { assert_eq!( token - .confidential_supply(&auditor_elgamal_keypair,) + .confidential_supply(&auditor_elgamal_keypair, &supply_aes_key) .await .unwrap(), MINT_AMOUNT - BURN_AMOUNT, @@ -360,7 +359,7 @@ async fn test_rotate_supply_elgamal() { assert_eq!( token - .confidential_supply(&auditor_elgamal_keypair,) + .confidential_supply(&auditor_elgamal_keypair, &supply_aes_key) .await .unwrap(), MINT_AMOUNT @@ -371,6 +370,7 @@ async fn test_rotate_supply_elgamal() { .rotate_supply_elgamal( &authority.pubkey(), &auditor_elgamal_keypair, + &supply_aes_key, &new_supply_elgamal_keypair, &[authority], ) @@ -379,7 +379,7 @@ async fn test_rotate_supply_elgamal() { assert_eq!( token - .confidential_supply(&new_supply_elgamal_keypair) + .confidential_supply(&new_supply_elgamal_keypair, &supply_aes_key) .await .unwrap(), MINT_AMOUNT From d46407ca53413295dea3ca9dfb68b9d4c97ba8eb Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 1 Oct 2024 16:30:58 -0300 Subject: [PATCH 13/34] remove cli / client changes and mint-burn tests --- token/cli/src/clap_app.rs | 232 -------- token/cli/src/command.rs | 428 +-------------- token/client/src/token.rs | 235 +------- .../tests/confidential_mint_burn.rs | 511 ------------------ 4 files changed, 6 insertions(+), 1400 deletions(-) delete mode 100644 token/program-2022-test/tests/confidential_mint_burn.rs diff --git a/token/cli/src/clap_app.rs b/token/cli/src/clap_app.rs index 427a8c4a294..38f75b13c9f 100644 --- a/token/cli/src/clap_app.rs +++ b/token/cli/src/clap_app.rs @@ -133,11 +133,6 @@ pub enum CommandName { ApplyPendingBalance, UpdateGroupAddress, UpdateMemberAddress, - MintConfidentialTokens, - BurnConfidentialTokens, - ConfidentialBalance, - ConfidentialSupply, - RotateSupplyElgamal, } impl fmt::Display for CommandName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -888,18 +883,6 @@ pub fn app<'a, 'b>( feature for the token, use \"none\"." ) ) - .arg( - Arg::with_name("confidential_supply_pubkey") - .long("confidential-supply-pubkey") - .value_name("CONFIDENTIAL_SUPPLY_PUBKEY") - .takes_value(true) - .help( - "The confidential supply encryption public key for mints with the \ - confidential transfer and confidential mint-burn extension enabled. \ - The corresponding private key for this supply public key can be \ - used to decrypt the confidential supply of the token." - ) - ) .nonce_args(true) .arg(memo_arg()) ) @@ -2704,219 +2687,4 @@ pub fn app<'a, 'b>( .arg(multisig_signer_arg()) .nonce_args(true) ) - .subcommand( - SubCommand::with_name(CommandName::MintConfidentialTokens.into()) - .about("Mint tokens amounts for into confidential balance") - .arg( - Arg::with_name("token") - .long("token") - .validator(is_valid_pubkey) - .value_name("TOKEN_MINT_ADDRESS") - .takes_value(true) - .index(1) - .required(true) - .help("The token address with confidential transfers enabled"), - ) - .arg( - Arg::with_name("amount") - .validator(is_amount_or_all) - .value_name("TOKEN_AMOUNT") - .takes_value(true) - .index(2) - .required(true) - .help("Amount to deposit; accepts keyword ALL"), - ) - .arg( - Arg::with_name("address") - .long("address") - .validator(is_valid_pubkey) - .value_name("TOKEN_ACCOUNT_ADDRESS") - .takes_value(true) - .help("The address of the token account to configure confidential transfers for \ - [default: owner's associated token account]") - ) - .arg( - owner_address_arg() - ) - .arg(multisig_signer_arg()) - .arg(mint_decimals_arg()) - .nonce_args(true) - ) - .subcommand( - SubCommand::with_name(CommandName::BurnConfidentialTokens.into()) - .about("Burn tokens from available confidential balance") - .arg( - Arg::with_name("token") - .long("token") - .validator(is_valid_pubkey) - .value_name("TOKEN_MINT_ADDRESS") - .takes_value(true) - .index(1) - .required(true) - .help("The token address with confidential transfers enabled"), - ) - .arg( - Arg::with_name("amount") - .validator(is_amount_or_all) - .value_name("TOKEN_AMOUNT") - .takes_value(true) - .index(2) - .required(true) - .help("Amount to deposit; accepts keyword ALL"), - ) - .arg( - Arg::with_name("address") - .long("address") - .validator(is_valid_pubkey) - .value_name("TOKEN_ACCOUNT_ADDRESS") - .takes_value(true) - .help("The address of the token account to configure confidential transfers for \ - [default: owner's associated token account]") - ) - .arg( - owner_address_arg() - ) - .arg(multisig_signer_arg()) - .arg(mint_decimals_arg()) - .nonce_args(true) - ) - .subcommand( - SubCommand::with_name(CommandName::ConfidentialBalance.into()) - .about("Display confidential balance") - .arg( - Arg::with_name("token") - .long("token") - .validator(is_valid_pubkey) - .value_name("TOKEN_MINT_ADDRESS") - .takes_value(true) - .index(1) - .required(true) - .help("The token address with confidential transfers enabled"), - ) - .arg( - Arg::with_name("address") - .long("address") - .validator(is_valid_pubkey) - .value_name("TOKEN_ACCOUNT_ADDRESS") - .takes_value(true) - .index(2) - .help("The address of the token account to for which to fetch the confidential balance") - ) - .arg( - Arg::with_name("authority") - .long("authority") - .alias("owner") - .validator(is_valid_signer) - .value_name("SIGNER") - .takes_value(true) - .help("Keypair from which encryption keys for token account were derived.") - ) - .arg( - owner_address_arg() - ) - .arg(multisig_signer_arg()) - .arg(mint_decimals_arg()) - .nonce_args(true) - ) - .subcommand( - SubCommand::with_name(CommandName::ConfidentialSupply.into()) - .about("Display supply of confidential token") - .arg( - Arg::with_name("token") - .long("token") - .validator(is_valid_pubkey) - .value_name("TOKEN_MINT_ADDRESS") - .takes_value(true) - .index(1) - .required(true) - .help("The token address with confidential transfers enabled"), - ) - .arg( - Arg::with_name("authority") - .long("authority") - .alias("owner") - .validator(is_valid_signer) - .value_name("SIGNER") - .takes_value(true) - .help("Keypair from which the supply elgamal keypair is derived. \ - Either the authority or the confidential-supply-keypair have \ - to be specified in order for the supply to be decrypted.") - ) - .arg( - Arg::with_name("confidential_supply_keypair") - .long("confidential-supply-keypair") - .value_name("CONFIDENTIAL_SUPPLY_KEYPAIR") - .takes_value(true) - .help( - "The confidential supply encryption keypair used to decrypt ElGamalCiphertext supply. \ - Either the authority or the confidential-supply-keypair have \ - to be specified in order for the supply to be decrypted." - ) - ) - .arg( - Arg::with_name("confidential_supply_aes_key") - .long("confidential-supply-aes-key") - .value_name("CONFIDENTIAL_SUPPLY_AES_KEY") - .takes_value(true) - .help( - "The aes key used to decrypt the decryptable portion of the confidential supply." - ) - ) - .nonce_args(true) - ) - .subcommand( - SubCommand::with_name(CommandName::RotateSupplyElgamal.into()) - .about("Display supply of confidential token") - .arg( - Arg::with_name("token") - .long("token") - .validator(is_valid_pubkey) - .value_name("TOKEN_MINT_ADDRESS") - .takes_value(true) - .index(1) - .required(true) - .help("The token address with confidential transfers enabled"), - ) - .arg( - Arg::with_name("authority") - .long("authority") - .alias("owner") - .validator(is_valid_signer) - .value_name("SIGNER") - .takes_value(true) - .required(true) - .help("Keypair holding the authority over the confidential-mint-burn extension.") - ) - .arg( - Arg::with_name("current_supply_keypair") - .long("current-supply-keypair") - .value_name("CURRENT_SUPPLY_KEYPAIR") - .takes_value(true) - .required(true) - .help( - "The current confidential supply encryption keypair." - ) - ) - .arg( - Arg::with_name("supply_aes_key") - .long("supply-aes-key") - .value_name("SUPPLY_AES_KEY") - .takes_value(true) - .required(true) - .help( - "The aes key to decrypt the decryptable confidential supply." - ) - ) - .arg( - Arg::with_name("new_supply_keypair") - .long("new-supply-keypair") - .value_name("NEW_SUPPLY_KEYPAIR") - .takes_value(true) - .required(true) - .help( - "The new confidential supply encryption keypair to rotate to." - ) - ) - .nonce_args(true) - ) } diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 57471bf940e..463c88d2dd0 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -36,12 +36,7 @@ use { }, spl_associated_token_account_client::address::get_associated_token_address_with_program_id, spl_token_2022::{ - error::TokenError, extension::{ - confidential_mint_burn::{ - instruction::{BurnSplitContextStateAccounts, MintSplitContextStateAccounts}, - ConfidentialMintBurn, - }, confidential_transfer::{ account_info::{ ApplyPendingBalanceAccountInfo, TransferAccountInfo, WithdrawAccountInfo, @@ -74,7 +69,7 @@ use { token::{ComputeUnitLimit, ExtensionInitializationParams, ProofAccount, Token}, }, spl_token_confidential_transfer_proof_generation::{ - burn::burn_split_proof_data, mint::mint_split_proof_data, transfer::TransferProofData, + transfer::TransferProofData, withdraw::WithdrawProofData, }, spl_token_group_interface::state::TokenGroup, @@ -261,9 +256,7 @@ async fn command_create_token( enable_group: bool, enable_member: bool, bulk_signers: Vec>, - enable_confidential_mint_burn: bool, auditor_pubkey: ElGamalPubkeyOrNone, - confidential_supply_pubkey: ElGamalPubkeyOrNone, ) -> CommandResult { println_display( config, @@ -342,13 +335,6 @@ async fn command_create_token( } } - if enable_confidential_mint_burn { - extensions.push(ExtensionInitializationParams::ConfidentialMintBurnMint { - authority, - confidential_supply_pubkey: confidential_supply_pubkey.into(), - }); - } - if let Some(program_id) = transfer_hook_program_id { extensions.push(ExtensionInitializationParams::TransferHook { authority: Some(authority), @@ -3245,7 +3231,6 @@ async fn command_enable_disable_confidential_transfers( enum ConfidentialInstructionType { Deposit, Withdraw, - Mint, } #[allow(clippy::too_many_arguments)] @@ -3442,125 +3427,6 @@ async fn command_deposit_withdraw_mint_confidential_tokens( withdraw_result } - ConfidentialInstructionType::Mint => { - let payer = config.fee_payer()?; - - let equality_proof_context_state_account = Keypair::new(); - let equality_proof_context_pubkey = equality_proof_context_state_account.pubkey(); - let ciphertext_validity_proof_context_state_account = Keypair::new(); - let ciphertext_validity_proof_context_pubkey = - ciphertext_validity_proof_context_state_account.pubkey(); - let range_proof_context_state_account = Keypair::new(); - let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); - - let mint_to_elgamal_pubkey = - token.account_elgamal_pubkey(&token_account_address).await?; - let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await?; - let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await?; - - let dummy_elgamal = ElGamalKeypair::new_rand(); - let dummy_aes = AeKey::new_rand(); - let supply_elgamal_keypair = match elgamal_keypair { - Some(e) => e, - None => { - // if no ElGamalKeypair supplied just use a dummy for - // proof creation - &dummy_elgamal - } - }; - let supply_aes_key = match aes_key { - Some(e) => e, - None => { - // see above - &dummy_aes - } - }; - - let mint = token.get_mint_info().await?; - let conf_mb_ext = mint.get_extension::()?; - - let context_state_accounts = MintSplitContextStateAccounts { - equality_proof: &equality_proof_context_pubkey, - ciphertext_validity_proof: &ciphertext_validity_proof_context_pubkey, - range_proof: &range_proof_context_pubkey, - authority: &payer.pubkey(), - }; - - let proof_data = mint_split_proof_data( - &conf_mb_ext - .confidential_supply - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?, - &conf_mb_ext - .decryptable_supply - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?, - amount, - supply_elgamal_keypair, - supply_aes_key, - &mint_to_elgamal_pubkey, - &auditor_elgamal_pubkey.unwrap_or_default(), - )?; - - let equality_proof_signer = &[&equality_proof_context_state_account]; - let ciphertext_validity_proof_signer = - &[&ciphertext_validity_proof_context_state_account]; - let range_proof_signer = &[&range_proof_context_state_account]; - let context_state_auth = payer.pubkey(); - let _ = try_join!( - token.confidential_transfer_create_context_state_account( - &equality_proof_context_pubkey, - &context_state_auth, - &proof_data.equality_proof_data, - true, - equality_proof_signer, - ), - token.confidential_transfer_create_context_state_account( - &ciphertext_validity_proof_context_pubkey, - &context_state_auth, - &proof_data.ciphertext_validity_proof_data, - false, - ciphertext_validity_proof_signer, - ), - token.confidential_transfer_create_context_state_account( - &range_proof_context_pubkey, - &context_state_auth, - &proof_data.range_proof_data, - true, - range_proof_signer, - ), - )?; - - let res = token - .confidential_mint( - &token_account_address, - &owner, - supply_elgamal_pubkey, - &context_state_accounts, - proof_data.new_decryptable_supply, - &bulk_signers, - ) - .await?; - - let close_context_auth = payer.pubkey(); - let close_context_state_signers = &[payer]; - let _ = try_join!( - token.confidential_transfer_close_context_state_account( - &range_proof_context_pubkey, - &close_context_auth, - &close_context_auth, - close_context_state_signers, - ), - token.confidential_transfer_close_context_state_account( - &ciphertext_validity_proof_context_pubkey, - &close_context_auth, - &close_context_auth, - close_context_state_signers, - ), - )?; - - res - } }; let tx_return = finish_tx(config, &res, false).await?; @@ -3704,8 +3570,6 @@ pub async fn process_command<'a>( let auditor_elgamal_pubkey = elgamal_pubkey_or_none(arg_matches, "auditor_pubkey").unwrap(); - let confidential_supply_pubkey = - elgamal_pubkey_or_none(arg_matches, "confidential_supply_pubkey").unwrap(); command_create_token( config, @@ -3729,9 +3593,7 @@ pub async fn process_command<'a>( arg_matches.is_present("enable_group"), arg_matches.is_present("enable_member"), bulk_signers, - arg_matches.is_present("enable_confidential_mint_burn"), auditor_elgamal_pubkey, - confidential_supply_pubkey, ) .await } @@ -4681,43 +4543,7 @@ pub async fn process_command<'a>( ) .await } - (CommandName::BurnConfidentialTokens, arg_matches) => { - let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) - .unwrap() - .unwrap(); - let ui_amount = match arg_matches.value_of("amount").unwrap() { - "ALL" => None, - amount => Some(amount.parse::().unwrap()), - }; - let account = pubkey_of_signer(arg_matches, "address", &mut wallet_manager) - .unwrap() - .unwrap(); - - let (owner_signer, owner) = - config.signer_or_default(arg_matches, "owner", &mut wallet_manager); - - let mint_decimals = value_of::(arg_matches, MINT_DECIMALS_ARG.name); - - let elgamal_keypair = ElGamalKeypair::new_from_signer(&*owner_signer, b"").unwrap(); - let aes_key = AeKey::new_from_signer(&*owner_signer, b"").unwrap(); - - let bulk_signers = vec![owner_signer]; - - command_confidential_burn( - config, - token, - account, - ui_amount, - owner, - mint_decimals, - bulk_signers, - &elgamal_keypair, - &aes_key, - ) - .await - } - (c @ CommandName::MintConfidentialTokens, arg_matches) - | (c @ CommandName::DepositConfidentialTokens, arg_matches) + (c @ CommandName::DepositConfidentialTokens, arg_matches) | (c @ CommandName::WithdrawConfidentialTokens, arg_matches) => { let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) .unwrap() @@ -4734,9 +4560,6 @@ pub async fn process_command<'a>( CommandName::DepositConfidentialTokens => { (ConfidentialInstructionType::Deposit, None, None) } - CommandName::MintConfidentialTokens => { - (ConfidentialInstructionType::Mint, None, None) - } CommandName::WithdrawConfidentialTokens => { // Deriving ElGamal and AES key from signer. Custom ElGamal and AES keys will be // supported in the future once upgrading to clap-v3. @@ -4805,254 +4628,7 @@ pub async fn process_command<'a>( ) .await } - (CommandName::ConfidentialBalance, arg_matches) => { - let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) - .unwrap() - .unwrap(); - let address = config - .associated_token_address_or_override(arg_matches, "address", &mut wallet_manager) - .await?; - let (auth_signer, _auth) = - config.signer_or_default(arg_matches, "authority", &mut wallet_manager); - command_confidential_balance(config, token, address, auth_signer).await - } - (CommandName::ConfidentialSupply, arg_matches) => { - let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) - .unwrap() - .unwrap(); - let elgamal_keypair = if arg_matches.is_present("confidential_supply_keypair") { - elgamal_keypair_of(arg_matches, "confidential_supply_keypair").unwrap() - } else { - let (auth_signer, _auth) = - config.signer_or_default(arg_matches, "authority", &mut wallet_manager); - - ElGamalKeypair::new_from_signer(&*auth_signer, b"").unwrap() - }; - let aes_key = if arg_matches.is_present("confidential_supply_aes_key") { - aes_key_of(arg_matches, "confidential_supply_aes_key").unwrap() - } else { - let (auth_signer, _auth) = - config.signer_or_default(arg_matches, "authority", &mut wallet_manager); - - AeKey::new_from_signer(&*auth_signer, b"").unwrap() - }; - - let token_cl = token_client_from_config(config, &token, None)?; - let supply = token_cl - .confidential_supply(&elgamal_keypair, &aes_key) - .await - .map_err(|e| format!("Could not fetch confidential supply for {token}: {e}",))?; - - Ok(format!("Supply of {token} is {supply}")) - } - (CommandName::RotateSupplyElgamal, arg_matches) => { - let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) - .unwrap() - .unwrap(); - let cur_elgamal_keypair = - elgamal_keypair_of(arg_matches, "current_supply_keypair").unwrap(); - let supply_aes_key = aes_key_of(arg_matches, "supply_aes_key").unwrap(); - let new_elgamal_keypair = - elgamal_keypair_of(arg_matches, "new_supply_keypair").unwrap(); - let (auth_signer, auth) = - config.signer_or_default(arg_matches, "authority", &mut wallet_manager); - - Ok( - match finish_tx( - config, - &token_client_from_config(config, &token, None)? - .rotate_supply_elgamal( - &auth, - &cur_elgamal_keypair, - &supply_aes_key, - &new_elgamal_keypair, - &[&auth_signer], - ) - .await?, - false, - ) - .await? - { - TransactionReturnData::CliSignature(signature) => { - config.output_format.formatted_string(&signature) - } - TransactionReturnData::CliSignOnlyData(sign_only_data) => { - config.output_format.formatted_string(&sign_only_data) - } - }, - ) - } - } -} - -async fn command_confidential_balance( - config: &Config<'_>, - token: Pubkey, - address: Pubkey, - authority_signer: Arc, -) -> CommandResult { - let elgamal_keypair = ElGamalKeypair::new_from_signer(&*authority_signer, b"").unwrap(); - let aes_key = AeKey::new_from_signer(&*authority_signer, b"").unwrap(); - - let token = token_client_from_config(config, &token, None)?; - - let (available_balance, pending_balance) = token - .confidential_balance(&address, &elgamal_keypair, &aes_key) - .await - .unwrap(); - - Ok(format!("{address} has a pending balance of {pending_balance} and an available balance of {available_balance}") - ) -} - -#[allow(clippy::too_many_arguments)] -async fn command_confidential_burn( - config: &Config<'_>, - token_pubkey: Pubkey, - ata_pubkey: Pubkey, - ui_amount: Option, - authority: Pubkey, - mint_decimals: Option, - bulk_signers: BulkSigners, - elgamal_keypair: &ElGamalKeypair, - aes_key: &AeKey, -) -> CommandResult { - let mint_info = config.get_mint_info(&token_pubkey, mint_decimals).await?; - - // if the user got the decimals wrong, they may well have calculated the - // transfer amount wrong we only check in online mode, because in offline, - // mint_info.decimals is always 9 - if !config.sign_only && mint_decimals.is_some() && mint_decimals != Some(mint_info.decimals) { - return Err(format!( - "Decimals {} was provided, but actual value is {}", - mint_decimals.unwrap(), - mint_info.decimals - ) - .into()); } - - let token = token_client_from_config(config, &token_pubkey, Some(mint_info.decimals))?; - - // the amount the user wants to tranfer, as a f64 - let burn_amount = ui_amount - .map(|ui_amount| spl_token::ui_amount_to_amount(ui_amount, mint_info.decimals)) - .unwrap(); - - let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await?; - let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await?; - - let context_state_authority = config.fee_payer()?; - let equality_proof_context_state_account = Keypair::new(); - let equality_proof_pubkey = equality_proof_context_state_account.pubkey(); - let ciphertext_validity_proof_context_state_account = Keypair::new(); - let ciphertext_validity_proof_pubkey = ciphertext_validity_proof_context_state_account.pubkey(); - let range_proof_context_state_account = Keypair::new(); - let range_proof_pubkey = range_proof_context_state_account.pubkey(); - - let context_state_accounts = BurnSplitContextStateAccounts { - equality_proof: &equality_proof_pubkey, - ciphertext_validity_proof: &ciphertext_validity_proof_pubkey, - range_proof: &range_proof_pubkey, - authority: &context_state_authority.pubkey(), - }; - - let state = token.get_account_info(&ata_pubkey).await.unwrap(); - let extension = state - .get_extension::() - .unwrap(); - let transfer_account_info = TransferAccountInfo::new(extension); - - let proof_data = burn_split_proof_data( - &transfer_account_info - .available_balance - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?, - &transfer_account_info - .decryptable_available_balance - .try_into() - .map_err(|_| TokenError::MalformedCiphertext)?, - burn_amount, - elgamal_keypair, - aes_key, - &auditor_elgamal_pubkey.unwrap_or_default(), - &supply_elgamal_pubkey.unwrap_or_default(), - ) - .unwrap(); - - let range_proof_signer = &[&range_proof_context_state_account]; - let equality_proof_signer = &[&equality_proof_context_state_account]; - let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; - // setup proofs - let _ = try_join!( - token.confidential_transfer_create_context_state_account( - context_state_accounts.equality_proof, - context_state_accounts.authority, - &proof_data.equality_proof_data, - false, - equality_proof_signer, - ), - token.confidential_transfer_create_context_state_account( - context_state_accounts.ciphertext_validity_proof, - context_state_accounts.authority, - &proof_data.ciphertext_validity_proof_data, - false, - ciphertext_validity_proof_signer, - ), - token.confidential_transfer_create_context_state_account( - context_state_accounts.range_proof, - context_state_accounts.authority, - &proof_data.range_proof_data, - true, - range_proof_signer, - ), - )?; - - // do the burn - let res = token - .confidential_burn( - &ata_pubkey, - &authority, - &context_state_accounts, - burn_amount, - supply_elgamal_pubkey, - aes_key, - &bulk_signers, - ) - .await?; - - // close context state accounts - let context_state_authority_pubkey = context_state_authority.pubkey(); - let close_context_state_signers = &[context_state_authority]; - let _ = try_join!( - token.confidential_transfer_close_context_state_account( - &equality_proof_pubkey, - &authority, - &context_state_authority_pubkey, - close_context_state_signers, - ), - token.confidential_transfer_close_context_state_account( - &ciphertext_validity_proof_pubkey, - &authority, - &context_state_authority_pubkey, - close_context_state_signers, - ), - token.confidential_transfer_close_context_state_account( - &range_proof_pubkey, - &authority, - &context_state_authority_pubkey, - close_context_state_signers, - ), - )?; - - let tx_return = finish_tx(config, &res, false).await?; - Ok(match tx_return { - TransactionReturnData::CliSignature(signature) => { - config.output_format.formatted_string(&signature) - } - TransactionReturnData::CliSignOnlyData(sign_only_data) => { - config.output_format.formatted_string(&sign_only_data) - } - }) } fn format_output(command_output: T, command_name: &CommandName, config: &Config) -> String diff --git a/token/client/src/token.rs b/token/client/src/token.rs index e67c65c89e7..c57e101489c 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -31,16 +31,11 @@ use { spl_token_2022::{ error::TokenError as Token2022Error, extension::{ - confidential_mint_burn::{ - self, - instruction::{BurnSplitContextStateAccounts, MintSplitContextStateAccounts}, - ConfidentialMintBurn, - }, confidential_transfer::{ self, account_info::{ - combine_balances, ApplyPendingBalanceAccountInfo, EmptyAccountAccountInfo, - TransferAccountInfo, WithdrawAccountInfo, + ApplyPendingBalanceAccountInfo, EmptyAccountAccountInfo, TransferAccountInfo, + WithdrawAccountInfo, }, instruction::{ProofContextState, ZkProofData}, ConfidentialTransferAccount, ConfidentialTransferMint, DecryptableBalance, @@ -57,9 +52,9 @@ use { proof::{zk_proof_type_to_instruction, ProofData, ProofLocation}, solana_zk_sdk::{ encryption::{ - auth_encryption::{AeCiphertext, AeKey}, + auth_encryption::AeKey, elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey}, - pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey}, + pod::elgamal::PodElGamalPubkey, }, zk_elgamal_proof_program::{ self, @@ -198,10 +193,6 @@ pub enum ExtensionInitializationParams { authority: Option, member_address: Option, }, - ConfidentialMintBurnMint { - authority: Pubkey, - confidential_supply_pubkey: Option, - }, } impl ExtensionInitializationParams { /// Get the extension type associated with the init params @@ -221,7 +212,6 @@ impl ExtensionInitializationParams { } Self::GroupPointer { .. } => ExtensionType::GroupPointer, Self::GroupMemberPointer { .. } => ExtensionType::GroupMemberPointer, - Self::ConfidentialMintBurnMint { .. } => ExtensionType::ConfidentialMintBurn, } } /// Generate an appropriate initialization instruction for the given mint @@ -331,15 +321,6 @@ impl ExtensionInitializationParams { authority, member_address, ), - Self::ConfidentialMintBurnMint { - authority, - confidential_supply_pubkey, - } => confidential_mint_burn::instruction::initialize_mint( - token_program_id, - mint, - authority, - confidential_supply_pubkey, - ), } } } @@ -3474,214 +3455,6 @@ where ) .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData)) } - - /// Mint SPL Tokens into the pending balance of a confidential token account - #[allow(clippy::too_many_arguments)] - pub async fn confidential_mint( - &self, - account: &Pubkey, - authority: &Pubkey, - supply_elgamal_pubkey: Option, - context_state_accounts: &MintSplitContextStateAccounts<'_>, - new_decryptable_supply: AeCiphertext, - signing_keypairs: &S, - ) -> TokenResult { - let signing_pubkeys = signing_keypairs.pubkeys(); - let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); - - self.process_ixs( - &[ - confidential_mint_burn::instruction::confidential_mint_with_split_proofs( - &self.program_id, - account, - &self.pubkey, - supply_elgamal_pubkey, - authority, - &multisig_signers, - context_state_accounts, - new_decryptable_supply, - )?, - ], - signing_keypairs, - ) - .await - } - - /// Burn SPL Tokens from the available balance of a confidential token - /// account - #[allow(clippy::too_many_arguments)] - pub async fn confidential_burn( - &self, - ata_pubkey: &Pubkey, - authority: &Pubkey, - context_state_accounts: &BurnSplitContextStateAccounts<'_>, - amount: u64, - supply_elgamal_pubkey: Option, - aes_key: &AeKey, - signing_keypairs: &S, - ) -> TokenResult { - let signing_pubkeys = signing_keypairs.pubkeys(); - let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); - - let account = self.get_account_info(ata_pubkey).await?; - let confidential_transfer_account = - account.get_extension::()?; - let account_info = TransferAccountInfo::new(confidential_transfer_account); - - let new_decryptable_available_balance = account_info - .new_decryptable_available_balance(amount, aes_key) - .map_err(|_| TokenError::AccountDecryption)?; - - self.process_ixs( - &confidential_mint_burn::instruction::confidential_burn_with_split_proofs( - &self.program_id, - ata_pubkey, - &self.pubkey, - supply_elgamal_pubkey, - new_decryptable_available_balance.into(), - context_state_accounts, - authority, - &multisig_signers, - )?, - signing_keypairs, - ) - .await - } - - /// Fetch confidential balance for token account - #[allow(clippy::too_many_arguments)] - pub async fn confidential_balance( - &self, - token_account: &Pubkey, - elgamal_keypair: &ElGamalKeypair, - aes_key: &AeKey, - ) -> TokenResult<(u64, u64)> { - let account = self.get_account_info(token_account).await?; - let confidential_transfer_account = - account.get_extension::()?; - - let decryptable_available_balance = confidential_transfer_account - .decryptable_available_balance - .try_into() - .map_err(|_| Token2022Error::MalformedCiphertext)?; - let available_balance = aes_key - .decrypt(&decryptable_available_balance) - .ok_or(Token2022Error::AccountDecryption)?; - - let pending_balance_lo = confidential_transfer_account - .pending_balance_lo - .try_into() - .map_err(|_| Token2022Error::MalformedCiphertext)?; - let decrypted_pending_balance_lo = elgamal_keypair - .secret() - .decrypt_u32(&pending_balance_lo) - .ok_or(Token2022Error::AccountDecryption)?; - - let pending_balance_hi = confidential_transfer_account - .pending_balance_hi - .try_into() - .map_err(|_| Token2022Error::MalformedCiphertext)?; - let decrypted_pending_balance_hi = elgamal_keypair - .secret() - .decrypt_u32(&pending_balance_hi) - .ok_or(Token2022Error::AccountDecryption)?; - let pending_balance = - combine_balances(decrypted_pending_balance_lo, decrypted_pending_balance_hi) - .ok_or(Token2022Error::AccountDecryption)?; - - Ok((available_balance, pending_balance)) - } - - /// Burn SPL Tokens from the available balance of a confidential token - /// account - #[allow(clippy::too_many_arguments)] - pub async fn confidential_supply( - &self, - supply_elgamal_keypair: &ElGamalKeypair, - supply_aes_key: &AeKey, - ) -> Result { - let mint = self.get_mint_info().await?; - let confidential_mint_burn_ext = mint.get_extension::()?; - - let current_decyptable_supply = - if confidential_mint_burn_ext.decryptable_supply != PodAeCiphertext::default() { - // decrypt the current supply - let decryptable_supply: AeCiphertext = confidential_mint_burn_ext - .decryptable_supply - .try_into() - .map_err(|_| TokenError::AccountDecryption)?; - decryptable_supply - .decrypt(supply_aes_key) - .ok_or(TokenError::AccountDecryption)? - } else { - 0 - }; - - // 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 = supply_elgamal_keypair - .pubkey() - .encrypt(current_decyptable_supply); - let current_supply: ElGamalCiphertext = confidential_mint_burn_ext - .confidential_supply - .try_into() - .map_err(|_| TokenError::AccountDecryption)?; - let ct_decryptable_to_current_diff = decryptable_supply_ciphertext - current_supply; - let decryptable_to_current_diff = supply_elgamal_keypair - .secret() - .decrypt_u32(&ct_decryptable_to_current_diff) - .ok_or(TokenError::AccountDecryption)?; - - // compute the total supply - current_decyptable_supply - .checked_sub(decryptable_to_current_diff) - .ok_or(TokenError::TokenProgramError(String::from( - "Confidential supply underflow", - ))) - } - - pub async fn supply_elgamal_pubkey(&self) -> TokenResult> { - Ok(Into::>::into( - self.get_mint_info() - .await? - .get_extension::()? - .supply_elgamal_pubkey, - ) - .map(|pk| TryInto::::try_into(pk).unwrap())) - } - - /// Rotate the elgamal pubkey encrypting the confidential supply - #[allow(clippy::too_many_arguments)] - pub async fn rotate_supply_elgamal( - &self, - authority: &Pubkey, - supply_elgamal_keypair: &ElGamalKeypair, - supply_aes_key: &AeKey, - new_supply_elgamal_keypair: &ElGamalKeypair, - signing_keypairs: &S, - ) -> TokenResult { - let signing_pubkeys = signing_keypairs.pubkeys(); - let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); - - let mint = self.get_mint_info().await?; - let extension_state = mint.get_extension::()?; - let current_supply = self.confidential_supply(supply_elgamal_keypair, supply_aes_key).await?; - - self.process_ixs( - &confidential_mint_burn::instruction::rotate_supply_elgamal( - &self.program_id, - self.get_address(), - authority, - &multisig_signers, - extension_state, - current_supply, - supply_elgamal_keypair, - new_supply_elgamal_keypair, - )?, - signing_keypairs, - ) - .await - } } /// Calculates the maximum chunk size for a zero-knowledge proof record diff --git a/token/program-2022-test/tests/confidential_mint_burn.rs b/token/program-2022-test/tests/confidential_mint_burn.rs deleted file mode 100644 index bba0057caa1..00000000000 --- a/token/program-2022-test/tests/confidential_mint_burn.rs +++ /dev/null @@ -1,511 +0,0 @@ -//#![cfg(feature = "test-sbf")] - -mod program_test; -use { - program_test::{ConfidentialTokenAccountMeta, TestContext, TokenContext}, - solana_program_test::tokio, - solana_sdk::{pubkey::Pubkey, signature::Signer, signer::keypair::Keypair, signers::Signers}, - spl_token_2022::{ - extension::{ - confidential_mint_burn::{ - instruction::{BurnSplitContextStateAccounts, MintSplitContextStateAccounts}, - ConfidentialMintBurn, - }, - confidential_transfer::{ - account_info::TransferAccountInfo, ConfidentialTransferAccount, - }, - BaseStateWithExtensions, - }, - solana_zk_sdk::encryption::{ - auth_encryption::AeKey, elgamal::*, pod::elgamal::PodElGamalPubkey, - }, - }, - spl_token_client::{ - client::ProgramBanksClientProcessTransaction, - token::{ExtensionInitializationParams, Token}, - }, - spl_token_confidential_transfer_proof_generation::{ - burn::burn_split_proof_data, mint::mint_split_proof_data, - }, - std::convert::TryInto, -}; - -const MINT_AMOUNT: u64 = 42; -const BURN_AMOUNT: u64 = 12; - -#[tokio::test] -async fn test_confidential_mint() { - let authority = Keypair::new(); - let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); - let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); - let supply_aes_key = AeKey::new_rand(); - - let mut context = TestContext::new().await; - context - .init_token_with_mint(vec![ - ExtensionInitializationParams::ConfidentialTransferMint { - authority: Some(authority.pubkey()), - auto_approve_new_accounts: true, - auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), - }, - ExtensionInitializationParams::ConfidentialMintBurnMint { - authority: authority.pubkey(), - confidential_supply_pubkey: Some(auditor_elgamal_pubkey), - }, - ]) - .await - .unwrap(); - - let TokenContext { token, alice, .. } = context.token_context.unwrap(); - let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice, None, false, false).await; - //let alice_elgamal_pubkey = (*alice_meta.elgamal_keypair.pubkey()).into(); - - mint_tokens( - &token, - &alice_meta.token_account, - &authority.pubkey(), - MINT_AMOUNT, - &auditor_elgamal_keypair, - &supply_aes_key, - &[&authority], - ) - .await; - - assert_eq!( - token - .confidential_balance( - &alice_meta.token_account, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key - ) - .await - .unwrap() - .1, - MINT_AMOUNT - ); - - token - .confidential_transfer_apply_pending_balance( - &alice_meta.token_account, - &alice.pubkey(), - None, - alice_meta.elgamal_keypair.secret(), - &alice_meta.aes_key, - &[&alice], - ) - .await - .unwrap(); - - assert_eq!( - token - .confidential_balance( - &alice_meta.token_account, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key - ) - .await - .unwrap() - .0, - MINT_AMOUNT - ); - - assert_eq!( - token - .confidential_supply(&auditor_elgamal_keypair, &supply_aes_key) - .await - .unwrap(), - MINT_AMOUNT - ); -} - -#[tokio::test] -async fn test_confidential_burn() { - let authority = Keypair::new(); - let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); - let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); - let supply_aes_key = AeKey::new_rand(); - - let mut context = TestContext::new().await; - context - .init_token_with_mint(vec![ - ExtensionInitializationParams::ConfidentialTransferMint { - authority: Some(authority.pubkey()), - auto_approve_new_accounts: true, - auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), - }, - ExtensionInitializationParams::ConfidentialMintBurnMint { - authority: authority.pubkey(), - confidential_supply_pubkey: Some(auditor_elgamal_pubkey), - }, - ]) - .await - .unwrap(); - - let TokenContext { token, alice, .. } = context.token_context.unwrap(); - let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice, None, false, false).await; - //let alice_elgamal_pubkey = (*alice_meta.elgamal_keypair.pubkey()).into(); - - mint_tokens( - &token, - &alice_meta.token_account, - &authority.pubkey(), - MINT_AMOUNT, - &auditor_elgamal_keypair, - &supply_aes_key, - &[&authority], - ) - .await; - - assert_eq!( - token - .confidential_supply(&auditor_elgamal_keypair, &supply_aes_key) - .await - .unwrap(), - MINT_AMOUNT - ); - - token - .confidential_transfer_apply_pending_balance( - &alice_meta.token_account, - &alice.pubkey(), - None, - alice_meta.elgamal_keypair.secret(), - &alice_meta.aes_key, - &[&alice], - ) - .await - .unwrap(); - - let context_state_authority = Keypair::new(); - let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await.unwrap(); - let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await.unwrap(); - - let equality_proof_context_state_account = Keypair::new(); - let equality_proof_pubkey = equality_proof_context_state_account.pubkey(); - let ciphertext_validity_proof_context_state_account = Keypair::new(); - let ciphertext_validity_proof_pubkey = ciphertext_validity_proof_context_state_account.pubkey(); - let range_proof_context_state_account = Keypair::new(); - let range_proof_pubkey = range_proof_context_state_account.pubkey(); - - let context_state_accounts = BurnSplitContextStateAccounts { - equality_proof: &equality_proof_pubkey, - ciphertext_validity_proof: &ciphertext_validity_proof_pubkey, - range_proof: &range_proof_pubkey, - authority: &context_state_authority.pubkey(), - }; - - let state = token - .get_account_info(&alice_meta.token_account) - .await - .unwrap(); - let extension = state - .get_extension::() - .unwrap(); - let transfer_account_info = TransferAccountInfo::new(extension); - - let proof_data = burn_split_proof_data( - &transfer_account_info.available_balance.try_into().unwrap(), - &transfer_account_info - .decryptable_available_balance - .try_into() - .unwrap(), - BURN_AMOUNT, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - &auditor_elgamal_pubkey.unwrap_or_default(), - &supply_elgamal_pubkey.unwrap_or_default(), - ) - .unwrap(); - - let range_proof_signer = &[&range_proof_context_state_account]; - let equality_proof_signer = &[&equality_proof_context_state_account]; - let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; - // setup proofs - token - .confidential_transfer_create_context_state_account( - context_state_accounts.range_proof, - context_state_accounts.authority, - &proof_data.range_proof_data, - true, - range_proof_signer, - ) - .await - .unwrap(); - token - .confidential_transfer_create_context_state_account( - context_state_accounts.equality_proof, - context_state_accounts.authority, - &proof_data.equality_proof_data, - false, - equality_proof_signer, - ) - .await - .unwrap(); - token - .confidential_transfer_create_context_state_account( - context_state_accounts.ciphertext_validity_proof, - context_state_accounts.authority, - &proof_data.ciphertext_validity_proof_data, - false, - ciphertext_validity_proof_signer, - ) - .await - .unwrap(); - - // do the burn - token - .confidential_burn( - &alice_meta.token_account, - &alice.pubkey(), - &context_state_accounts, - BURN_AMOUNT, - supply_elgamal_pubkey, - &alice_meta.aes_key, - &[&alice], - ) - .await - .unwrap(); - - // close context state accounts - let context_state_authority_pubkey = context_state_authority.pubkey(); - let close_context_state_signers = &[context_state_authority]; - token - .confidential_transfer_close_context_state_account( - &equality_proof_pubkey, - &context_state_authority_pubkey, - &context_state_authority_pubkey, - close_context_state_signers, - ) - .await - .unwrap(); - token - .confidential_transfer_close_context_state_account( - &ciphertext_validity_proof_pubkey, - &context_state_authority_pubkey, - &context_state_authority_pubkey, - close_context_state_signers, - ) - .await - .unwrap(); - token - .confidential_transfer_close_context_state_account( - &range_proof_pubkey, - &context_state_authority_pubkey, - &context_state_authority_pubkey, - close_context_state_signers, - ) - .await - .unwrap(); - - assert_eq!( - token - .confidential_balance( - &alice_meta.token_account, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key - ) - .await - .unwrap() - .0, - MINT_AMOUNT - BURN_AMOUNT, - ); - - assert_eq!( - token - .confidential_supply(&auditor_elgamal_keypair, &supply_aes_key) - .await - .unwrap(), - MINT_AMOUNT - BURN_AMOUNT, - ); -} - -#[tokio::test] -async fn test_rotate_supply_elgamal() { - let authority = Keypair::new(); - let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); - let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); - let supply_aes_key = AeKey::new_rand(); - - let mut context = TestContext::new().await; - context - .init_token_with_mint(vec![ - ExtensionInitializationParams::ConfidentialTransferMint { - authority: Some(authority.pubkey()), - auto_approve_new_accounts: true, - auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), - }, - ExtensionInitializationParams::ConfidentialMintBurnMint { - authority: authority.pubkey(), - confidential_supply_pubkey: Some(auditor_elgamal_pubkey), - }, - ]) - .await - .unwrap(); - - let TokenContext { token, alice, .. } = context.token_context.unwrap(); - let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice, None, false, false).await; - //let alice_elgamal_pubkey = (*alice_meta.elgamal_keypair.pubkey()).into(); - - mint_tokens( - &token, - &alice_meta.token_account, - &authority.pubkey(), - MINT_AMOUNT, - &auditor_elgamal_keypair, - &supply_aes_key, - &[&authority], - ) - .await; - - assert_eq!( - token - .confidential_supply(&auditor_elgamal_keypair, &supply_aes_key) - .await - .unwrap(), - MINT_AMOUNT - ); - - let new_supply_elgamal_keypair = ElGamalKeypair::new_rand(); - token - .rotate_supply_elgamal( - &authority.pubkey(), - &auditor_elgamal_keypair, - &supply_aes_key, - &new_supply_elgamal_keypair, - &[authority], - ) - .await - .unwrap(); - - assert_eq!( - token - .confidential_supply(&new_supply_elgamal_keypair, &supply_aes_key) - .await - .unwrap(), - MINT_AMOUNT - ); - - let mint = token.get_mint_info().await.unwrap(); - let conf_mint_burn_ext = mint.get_extension::().unwrap(); - - assert_eq!( - conf_mint_burn_ext.supply_elgamal_pubkey, - Some(Into::::into( - *new_supply_elgamal_keypair.pubkey(), - )) - .try_into() - .unwrap(), - ); -} - -async fn mint_tokens( - token: &Token, - token_account: &Pubkey, - authority: &Pubkey, - mint_amount: u64, - supply_elgamal_keypair: &ElGamalKeypair, - supply_aes_key: &AeKey, - bulk_signers: &impl Signers, -) { - let context_state_auth = Keypair::new(); - let equality_proof_context_state_account = Keypair::new(); - let equality_proof_context_pubkey = equality_proof_context_state_account.pubkey(); - let ciphertext_validity_proof_context_state_account = Keypair::new(); - let ciphertext_validity_proof_context_pubkey = - ciphertext_validity_proof_context_state_account.pubkey(); - let range_proof_context_state_account = Keypair::new(); - let range_proof_context_pubkey = range_proof_context_state_account.pubkey(); - - let mint_to_elgamal_pubkey = token.account_elgamal_pubkey(token_account).await.unwrap(); - let auditor_elgamal_pubkey = token.auditor_elgamal_pubkey().await.unwrap(); - let supply_elgamal_pubkey = token.supply_elgamal_pubkey().await.unwrap(); - - let mint = token.get_mint_info().await.unwrap(); - let conf_mb_ext = mint.get_extension::().unwrap(); - - let context_state_accounts = MintSplitContextStateAccounts { - equality_proof: &equality_proof_context_pubkey, - ciphertext_validity_proof: &ciphertext_validity_proof_context_pubkey, - range_proof: &range_proof_context_pubkey, - authority: &context_state_auth.pubkey(), - }; - - let proof_data = mint_split_proof_data( - &conf_mb_ext.confidential_supply.try_into().unwrap(), - &conf_mb_ext.decryptable_supply.try_into().unwrap(), - mint_amount, - supply_elgamal_keypair, - supply_aes_key, - &mint_to_elgamal_pubkey, - &auditor_elgamal_pubkey.unwrap_or_default(), - ) - .unwrap(); - - let equality_proof_signer = &[&equality_proof_context_state_account]; - let ciphertext_validity_proof_signer = &[&ciphertext_validity_proof_context_state_account]; - let range_proof_signer = &[&range_proof_context_state_account]; - - token - .confidential_transfer_create_context_state_account( - &equality_proof_context_pubkey, - &context_state_auth.pubkey(), - &proof_data.equality_proof_data, - false, - equality_proof_signer, - ) - .await - .unwrap(); - token - .confidential_transfer_create_context_state_account( - &ciphertext_validity_proof_context_pubkey, - &context_state_auth.pubkey(), - &proof_data.ciphertext_validity_proof_data, - false, - ciphertext_validity_proof_signer, - ) - .await - .unwrap(); - token - .confidential_transfer_create_context_state_account( - &range_proof_context_pubkey, - &context_state_auth.pubkey(), - &proof_data.range_proof_data, - false, - range_proof_signer, - ) - .await - .unwrap(); - - token - .confidential_mint( - token_account, - authority, - supply_elgamal_pubkey, - &context_state_accounts, - proof_data.new_decryptable_supply, - bulk_signers, - ) - .await - .unwrap(); - - let close_context_auth = context_state_auth.pubkey(); - let close_context_state_signers = &[context_state_auth]; - token - .confidential_transfer_close_context_state_account( - &range_proof_context_pubkey, - &close_context_auth, - &close_context_auth, - close_context_state_signers, - ) - .await - .unwrap(); - token - .confidential_transfer_close_context_state_account( - &ciphertext_validity_proof_context_pubkey, - &close_context_auth, - &close_context_auth, - close_context_state_signers, - ) - .await - .unwrap(); -} From 39441954b15ad9d39e4e3312073776f3aa87b988 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 1 Oct 2024 16:38:13 -0300 Subject: [PATCH 14/34] cleanup --- token/cli/src/clap_app.rs | 24 ---------------------- token/cli/src/command.rs | 14 ++++--------- token/cli/src/encryption_keypair.rs | 20 ++++++------------- token/client/src/token.rs | 31 ++--------------------------- 4 files changed, 12 insertions(+), 77 deletions(-) diff --git a/token/cli/src/clap_app.rs b/token/cli/src/clap_app.rs index 38f75b13c9f..74c5f4bf186 100644 --- a/token/cli/src/clap_app.rs +++ b/token/cli/src/clap_app.rs @@ -859,30 +859,6 @@ pub fn app<'a, 'b>( .takes_value(false) .help("Enables group member configurations in the mint. The mint authority must initialize the member."), ) - .arg( - Arg::with_name("enable_confidential_mint_burn") - .long("enable-confidential-mint-burn") - .takes_value(false) - .help( - "Enables minting of new tokens into confidential balance and burning of tokens directly from the confidential balance" - ), - ) - .arg( - Arg::with_name("auditor_pubkey") - .long("auditor-pubkey") - .value_name("AUDITOR_PUBKEY") - .takes_value(true) - .help( - "The auditor encryption public key for mints with the confidential \ - transfer extension enabled. The corresponding private key for \ - this auditor public key can be used to decrypt all confidential \ - transfers involving tokens from this mint. Currently, the auditor \ - public key can only be specified as a direct *base64* encoding of \ - an ElGamal public key. More methods of specifying the auditor public \ - key will be supported in a future version. To disable auditability \ - feature for the token, use \"none\"." - ) - ) .nonce_args(true) .arg(memo_arg()) ) diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 463c88d2dd0..00001d3c454 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -69,8 +69,7 @@ use { token::{ComputeUnitLimit, ExtensionInitializationParams, ProofAccount, Token}, }, spl_token_confidential_transfer_proof_generation::{ - transfer::TransferProofData, - withdraw::WithdrawProofData, + transfer::TransferProofData, withdraw::WithdrawProofData, }, spl_token_group_interface::state::TokenGroup, spl_token_metadata_interface::state::{Field, TokenMetadata}, @@ -256,7 +255,6 @@ async fn command_create_token( enable_group: bool, enable_member: bool, bulk_signers: Vec>, - auditor_pubkey: ElGamalPubkeyOrNone, ) -> CommandResult { println_display( config, @@ -316,7 +314,7 @@ async fn command_create_token( extensions.push(ExtensionInitializationParams::ConfidentialTransferMint { authority: Some(authority), auto_approve_new_accounts: auto_approve, - auditor_elgamal_pubkey: auditor_pubkey.into(), + auditor_elgamal_pubkey: None, }); if transfer_fee.is_some() { // Deriving ElGamal key from default signer. Custom ElGamal keys @@ -3234,7 +3232,7 @@ enum ConfidentialInstructionType { } #[allow(clippy::too_many_arguments)] -async fn command_deposit_withdraw_mint_confidential_tokens( +async fn command_deposit_withdraw_confidential_tokens( config: &Config<'_>, token_pubkey: Pubkey, owner: Pubkey, @@ -3568,9 +3566,6 @@ pub async fn process_command<'a>( .value_of("enable_confidential_transfers") .map(|b| b == "auto"); - let auditor_elgamal_pubkey = - elgamal_pubkey_or_none(arg_matches, "auditor_pubkey").unwrap(); - command_create_token( config, decimals, @@ -3593,7 +3588,6 @@ pub async fn process_command<'a>( arg_matches.is_present("enable_group"), arg_matches.is_present("enable_member"), bulk_signers, - auditor_elgamal_pubkey, ) .await } @@ -4583,7 +4577,7 @@ pub async fn process_command<'a>( push_signer_with_dedup(owner_signer, &mut bulk_signers); } - command_deposit_withdraw_mint_confidential_tokens( + command_deposit_withdraw_confidential_tokens( config, token, owner, diff --git a/token/cli/src/encryption_keypair.rs b/token/cli/src/encryption_keypair.rs index f61e9ff50b1..3a5aecbcf76 100644 --- a/token/cli/src/encryption_keypair.rs +++ b/token/cli/src/encryption_keypair.rs @@ -3,9 +3,12 @@ //! NOTE: this module should be remoeved in the next Solana upgrade. use { - base64::{prelude::BASE64_STANDARD, Engine}, clap::ArgMatches, solana_sdk::signer::EncodableKey, spl_token_2022::solana_zk_sdk::encryption::{ - auth_encryption::AeKey, elgamal::{ElGamalKeypair, ElGamalPubkey}, pod::elgamal::PodElGamalPubkey - } + base64::{prelude::BASE64_STANDARD, Engine}, + clap::ArgMatches, + spl_token_2022::solana_zk_sdk::encryption::{ + elgamal::{ElGamalKeypair, ElGamalPubkey}, + pod::elgamal::PodElGamalPubkey, + }, }; const ELGAMAL_PUBKEY_MAX_BASE64_LEN: usize = 44; @@ -29,9 +32,6 @@ pub(crate) fn elgamal_pubkey_or_none( matches: &ArgMatches, name: &str, ) -> Result { - if !matches.is_present(name) { - return Ok(ElGamalPubkeyOrNone::None); - } let arg_str = matches.value_of(name).unwrap(); if arg_str == "none" { return Ok(ElGamalPubkeyOrNone::None); @@ -65,14 +65,6 @@ pub(crate) fn elgamal_keypair_of( ElGamalKeypair::read_json_file(path).map_err(|e| e.to_string()) } -pub(crate) fn aes_key_of( - matches: &ArgMatches, - name: &str, -) -> Result { - let path = matches.value_of(name).unwrap(); - AeKey::read_from_file(path).map_err(|e| e.to_string()) -} - fn elgamal_pubkey_from_str(s: &str) -> Option { if s.len() > ELGAMAL_PUBKEY_MAX_BASE64_LEN { return None; diff --git a/token/client/src/token.rs b/token/client/src/token.rs index c57e101489c..2258dda5a43 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -29,7 +29,6 @@ use { }, spl_record::state::RecordData, spl_token_2022::{ - error::TokenError as Token2022Error, extension::{ confidential_transfer::{ self, @@ -38,7 +37,7 @@ use { WithdrawAccountInfo, }, instruction::{ProofContextState, ZkProofData}, - ConfidentialTransferAccount, ConfidentialTransferMint, DecryptableBalance, + ConfidentialTransferAccount, DecryptableBalance, }, confidential_transfer_fee::{ self, account_info::WithheldTokensInfo, ConfidentialTransferFeeAmount, @@ -110,14 +109,8 @@ pub enum TokenError { MissingDecimals, #[error("decimals specified, but incorrect")] InvalidDecimals, - #[error("TokenProgramError: {0}")] - TokenProgramError(String), -} -impl From for TokenError { - fn from(e: Token2022Error) -> Self { - Self::TokenProgramError(e.to_string()) - } } + impl PartialEq for TokenError { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -3435,26 +3428,6 @@ where )); self.process_ixs(&instructions, signing_keypairs).await } - - pub async fn auditor_elgamal_pubkey(&self) -> TokenResult> { - Ok(Into::>::into( - self.get_mint_info() - .await? - .get_extension::()? - .auditor_elgamal_pubkey, - ) - .map(|pk| TryInto::::try_into(pk).unwrap())) - } - - pub async fn account_elgamal_pubkey(&self, account: &Pubkey) -> TokenResult { - TryInto::::try_into( - self.get_account_info(account) - .await? - .get_extension::()? - .elgamal_pubkey, - ) - .map_err(|_| TokenError::Program(ProgramError::InvalidAccountData)) - } } /// Calculates the maximum chunk size for a zero-knowledge proof record From e0100031f4a26d2b1efca3e5ed8cb808243a84f9 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 1 Oct 2024 16:44:17 -0300 Subject: [PATCH 15/34] more cleanup --- .../confidential_transfer/instruction.rs | 18 ------------------ .../confidential_transfer/processor.rs | 3 +-- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index 3904852becd..661af463f2f 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -672,7 +672,6 @@ pub fn initialize_mint( } /// Create a `UpdateMint` instruction -#[cfg(not(target_os = "solana"))] pub fn update_mint( token_program_id: &Pubkey, mint: &Pubkey, @@ -705,7 +704,6 @@ pub fn update_mint( /// /// This instruction is suitable for use with a cross-program `invoke` #[allow(clippy::too_many_arguments)] -#[cfg(not(target_os = "solana"))] pub fn inner_configure_account( token_program_id: &Pubkey, token_account: &Pubkey, @@ -761,7 +759,6 @@ pub fn inner_configure_account( /// Create a `ConfigureAccount` instruction #[allow(clippy::too_many_arguments)] -#[cfg(not(target_os = "solana"))] pub fn configure_account( token_program_id: &Pubkey, token_account: &Pubkey, @@ -808,7 +805,6 @@ pub fn configure_account( } /// Create an `ApproveAccount` instruction -#[cfg(not(target_os = "solana"))] pub fn approve_account( token_program_id: &Pubkey, account_to_approve: &Pubkey, @@ -837,7 +833,6 @@ pub fn approve_account( /// Create an inner `EmptyAccount` instruction /// /// This instruction is suitable for use with a cross-program `invoke` -#[cfg(not(target_os = "solana"))] pub fn inner_empty_account( token_program_id: &Pubkey, token_account: &Pubkey, @@ -883,7 +878,6 @@ pub fn inner_empty_account( } /// Create a `EmptyAccount` instruction -#[cfg(not(target_os = "solana"))] pub fn empty_account( token_program_id: &Pubkey, token_account: &Pubkey, @@ -925,7 +919,6 @@ pub fn empty_account( /// Create a `Deposit` instruction #[allow(clippy::too_many_arguments)] -#[cfg(not(target_os = "solana"))] pub fn deposit( token_program_id: &Pubkey, token_account: &Pubkey, @@ -962,7 +955,6 @@ pub fn deposit( /// /// This instruction is suitable for use with a cross-program `invoke` #[allow(clippy::too_many_arguments)] -#[cfg(not(target_os = "solana"))] pub fn inner_withdraw( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1041,7 +1033,6 @@ pub fn inner_withdraw( /// Create a `Withdraw` instruction #[allow(clippy::too_many_arguments)] -#[cfg(not(target_os = "solana"))] pub fn withdraw( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1114,7 +1105,6 @@ pub fn withdraw( /// /// This instruction is suitable for use with a cross-program `invoke` #[allow(clippy::too_many_arguments)] -#[cfg(not(target_os = "solana"))] pub fn inner_transfer( token_program_id: &Pubkey, source_token_account: &Pubkey, @@ -1210,7 +1200,6 @@ pub fn inner_transfer( /// Create a `Transfer` instruction #[allow(clippy::too_many_arguments)] -#[cfg(not(target_os = "solana"))] pub fn transfer( token_program_id: &Pubkey, source_token_account: &Pubkey, @@ -1306,7 +1295,6 @@ pub fn transfer( /// Create a inner `ApplyPendingBalance` instruction /// /// This instruction is suitable for use with a cross-program `invoke` -#[cfg(not(target_os = "solana"))] pub fn inner_apply_pending_balance( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1338,7 +1326,6 @@ pub fn inner_apply_pending_balance( } /// Create a `ApplyPendingBalance` instruction -#[cfg(not(target_os = "solana"))] pub fn apply_pending_balance( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1357,7 +1344,6 @@ pub fn apply_pending_balance( ) // calls check_program_account } -#[cfg(not(target_os = "solana"))] fn enable_or_disable_balance_credits( instruction: ConfidentialTransferInstruction, token_program_id: &Pubkey, @@ -1385,7 +1371,6 @@ fn enable_or_disable_balance_credits( } /// Create a `EnableConfidentialCredits` instruction -#[cfg(not(target_os = "solana"))] pub fn enable_confidential_credits( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1402,7 +1387,6 @@ pub fn enable_confidential_credits( } /// Create a `DisableConfidentialCredits` instruction -#[cfg(not(target_os = "solana"))] pub fn disable_confidential_credits( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1419,7 +1403,6 @@ pub fn disable_confidential_credits( } /// Create a `EnableNonConfidentialCredits` instruction -#[cfg(not(target_os = "solana"))] pub fn enable_non_confidential_credits( token_program_id: &Pubkey, token_account: &Pubkey, @@ -1436,7 +1419,6 @@ pub fn enable_non_confidential_credits( } /// Create a `DisableNonConfidentialCredits` instruction -#[cfg(not(target_os = "solana"))] pub fn disable_non_confidential_credits( token_program_id: &Pubkey, token_account: &Pubkey, diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index 91875706e12..9ddf242eecc 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -645,9 +645,8 @@ fn process_transfer( } /// Processes the changes for the sending party of a confidential transfer -#[allow(clippy::too_many_arguments)] #[cfg(feature = "zk-ops")] -pub fn process_source_for_transfer( +fn process_source_for_transfer( program_id: &Pubkey, source_account_info: &AccountInfo, mint_info: &AccountInfo, From 42e0193e9db726c587a45ea1ae2e82417827b97c Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Wed, 2 Oct 2024 09:09:23 -0300 Subject: [PATCH 16/34] fix clippy; serde logic --- .../proof-generation/src/mint.rs | 1 + .../proof-tests/tests/proof_test.rs | 1 + token/program-2022/src/serialization.rs | 26 +++++++------------ token/program-2022/tests/serialization.rs | 8 +----- 4 files changed, 12 insertions(+), 24 deletions(-) diff --git a/token/confidential-transfer/proof-generation/src/mint.rs b/token/confidential-transfer/proof-generation/src/mint.rs index 849dfc5de20..87752bc4c9e 100644 --- a/token/confidential-transfer/proof-generation/src/mint.rs +++ b/token/confidential-transfer/proof-generation/src/mint.rs @@ -96,6 +96,7 @@ pub fn mint_split_proof_data( let decryptable_supply_ciphertext = supply_elgamal_keypair .pubkey() .encrypt(current_decyptable_supply); + #[allow(clippy::arithmetic_side_effects)] let ct_decryptable_to_current_diff = decryptable_supply_ciphertext - current_supply_ciphertext; let decryptable_to_current_diff = supply_elgamal_keypair .secret() diff --git a/token/confidential-transfer/proof-tests/tests/proof_test.rs b/token/confidential-transfer/proof-tests/tests/proof_test.rs index f4c3a7f3a9e..3cb5d1ad5ae 100644 --- a/token/confidential-transfer/proof-tests/tests/proof_test.rs +++ b/token/confidential-transfer/proof-tests/tests/proof_test.rs @@ -223,6 +223,7 @@ fn test_mint_validity(mint_amount: u64, supply: u64) { equality_proof_data, ciphertext_validity_proof_data, range_proof_data, + new_decryptable_supply: _, } = mint_split_proof_data( &supply_ciphertext, &decryptable_supply, diff --git a/token/program-2022/src/serialization.rs b/token/program-2022/src/serialization.rs index 0cd75c044b9..9d72b2f9287 100644 --- a/token/program-2022/src/serialization.rs +++ b/token/program-2022/src/serialization.rs @@ -1,11 +1,6 @@ //! serialization module - contains helpers for serde types from other crates, //! deserialization visitors -use { - base64::{prelude::BASE64_STANDARD, Engine}, - serde::de::Error, -}; - /// helper function to ser/deser COption wrapped values pub mod coption_fromstr { use { @@ -135,24 +130,22 @@ pub mod elgamalciphertext_fromstr { de::{Error, Visitor}, Deserializer, Serializer, }, - solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalCiphertext, - std::fmt, + solana_zk_sdk::encryption::pod::elgamal::PodElGamalCiphertext, + std::{fmt, str::FromStr}, }; - const AE_CIPHERTEXT_LEN: usize = 64; - /// serialize AeCiphertext values supporting Display trait - pub fn serialize(x: &ElGamalCiphertext, s: S) -> Result + pub fn serialie(x: &PodElGamalCiphertext, s: S) -> Result where S: Serializer, { s.serialize_str(&x.to_string()) } - struct AeCiphertextVisitor; + struct ElGamalCiphertextVisitor; - impl<'de> Visitor<'de> for AeCiphertextVisitor { - type Value = ElGamalCiphertext; + impl<'de> Visitor<'de> for ElGamalCiphertextVisitor { + type Value = PodElGamalCiphertext; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a FromStr type") @@ -162,17 +155,16 @@ pub mod elgamalciphertext_fromstr { where E: Error, { - let array = super::base64_to_bytes::(v)?; - Ok(ElGamalCiphertext(array)) + FromStr::from_str(v).map_err(Error::custom) } } /// deserialize AeCiphertext values from str - pub fn deserialize<'de, D>(d: D) -> Result + pub fn deserialize<'de, D>(d: D) -> Result where D: Deserializer<'de>, { - d.deserialize_str(AeCiphertextVisitor) + d.deserialize_str(ElGamalCiphertextVisitor) } } diff --git a/token/program-2022/tests/serialization.rs b/token/program-2022/tests/serialization.rs index 1d8d3b5021f..80e9fab1fc5 100644 --- a/token/program-2022/tests/serialization.rs +++ b/token/program-2022/tests/serialization.rs @@ -5,13 +5,7 @@ use { solana_program::program_option::COption, solana_sdk::pubkey::Pubkey, spl_pod::optional_keys::{OptionalNonZeroElGamalPubkey, OptionalNonZeroPubkey}, - spl_token_2022::{ - extension::confidential_transfer, - instruction, - solana_zk_sdk::encryption::pod::{ - auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey, - }, - }, + spl_token_2022::{extension::confidential_transfer, instruction}, std::str::FromStr, }; From e0aefa2a7553fa2edd8d2670b9e0657ef1f76b54 Mon Sep 17 00:00:00 2001 From: abcalphabet Date: Fri, 4 Oct 2024 12:10:49 -0300 Subject: [PATCH 17/34] Update token/program-2022/src/extension/confidential_mint_burn/mod.rs Co-authored-by: samkim-crypto --- token/program-2022/src/extension/confidential_mint_burn/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index fd5ff4b1d04..ce86579ba23 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -32,7 +32,7 @@ pub struct ConfidentialMintBurn { /// Authority to modify the `ConfidentialMintBurnMint` configuration and to /// mint new confidential tokens pub mint_authority: Pubkey, - /// The confidential supply of the mint (encrypted by `encrypiton_pubkey`) + /// 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, From f795a3f85bc307e5c3e961bc93c944be11ab6ce9 Mon Sep 17 00:00:00 2001 From: abcalphabet Date: Fri, 4 Oct 2024 12:12:18 -0300 Subject: [PATCH 18/34] Update token/program-2022/src/extension/confidential_mint_burn/instruction.rs Co-authored-by: samkim-crypto --- .../src/extension/confidential_mint_burn/instruction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 174b90ad82a..e46fd2cb6b8 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -92,7 +92,7 @@ pub struct InitializeMintData { #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] #[repr(C)] pub struct UpdateAuthorityData { - /// The pubkey the `authority` is to be rotated to + /// The new `authority` pubkey pub new_authority: Pubkey, } From c2ec29a5dab1e31bfb0b780c9eb7c3bc1f6f8f90 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Fri, 4 Oct 2024 13:47:44 -0300 Subject: [PATCH 19/34] review fixes --- .../proof-generation/src/lib.rs | 1 + .../proof-generation/src/mint.rs | 2 +- .../proof-generation/src/supply.rs | 31 ++ .../confidential_mint_burn/instruction.rs | 462 ++++++++++++------ .../confidential_mint_burn/processor.rs | 154 +++--- .../confidential_mint_burn/verify_proof.rs | 214 ++++---- .../confidential_transfer/instruction.rs | 2 + token/program-2022/src/extension/mod.rs | 5 +- 8 files changed, 529 insertions(+), 342 deletions(-) create mode 100644 token/confidential-transfer/proof-generation/src/supply.rs diff --git a/token/confidential-transfer/proof-generation/src/lib.rs b/token/confidential-transfer/proof-generation/src/lib.rs index f8883f31954..7685c223b96 100644 --- a/token/confidential-transfer/proof-generation/src/lib.rs +++ b/token/confidential-transfer/proof-generation/src/lib.rs @@ -10,6 +10,7 @@ pub mod burn; pub mod encryption; pub mod errors; pub mod mint; +pub mod supply; pub mod transfer; pub mod transfer_with_fee; pub mod withdraw; diff --git a/token/confidential-transfer/proof-generation/src/mint.rs b/token/confidential-transfer/proof-generation/src/mint.rs index 87752bc4c9e..da3f668c8c3 100644 --- a/token/confidential-transfer/proof-generation/src/mint.rs +++ b/token/confidential-transfer/proof-generation/src/mint.rs @@ -80,7 +80,7 @@ pub fn mint_split_proof_data( .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; // fresh mints are initialized with a zeroed decryptable_supply - // TODO: @samkim is there a better way to do this? + // TODO: don't clone here once AeCiphertext implement Copy in the zk-sdk let pod_decryptable_supply: PodAeCiphertext = current_decryptable_supply.clone().into(); let current_decyptable_supply = if pod_decryptable_supply != PodAeCiphertext::default() { // decrypt the current supply diff --git a/token/confidential-transfer/proof-generation/src/supply.rs b/token/confidential-transfer/proof-generation/src/supply.rs new file mode 100644 index 00000000000..a13c16a39a5 --- /dev/null +++ b/token/confidential-transfer/proof-generation/src/supply.rs @@ -0,0 +1,31 @@ +use { + crate::errors::TokenProofGenerationError, + solana_zk_sdk::{ + encryption::{ + elgamal::{ElGamalCiphertext, ElGamalKeypair}, + pedersen::PedersenOpening, + }, + zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData, + }, +}; + +pub fn supply_elgamal_pubkey_rotation_proof( + current_supply: u64, + supply_elgamal_keypair: &ElGamalKeypair, + new_supply_elgamal_keypair: &ElGamalKeypair, + current_supply_ciphertext: ElGamalCiphertext, +) -> Result { + let new_supply_opening = PedersenOpening::new_rand(); + let new_supply_ciphertext = new_supply_elgamal_keypair + .pubkey() + .encrypt_with(current_supply, &new_supply_opening); + + Ok(CiphertextCiphertextEqualityProofData::new( + supply_elgamal_keypair, + new_supply_elgamal_keypair.pubkey(), + ¤t_supply_ciphertext, + &new_supply_ciphertext, + &new_supply_opening, + current_supply, + )?) +} diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index e46fd2cb6b8..f8eb8ad7b69 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -1,30 +1,11 @@ #[cfg(not(target_os = "solana"))] -use crate::error::TokenError; -#[cfg(not(target_os = "solana"))] use solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey; #[cfg(not(target_os = "solana"))] -use solana_zk_sdk::zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData; +use solana_zk_sdk::encryption::{auth_encryption::AeCiphertext, elgamal::ElGamalPubkey}; #[cfg(not(target_os = "solana"))] -use solana_zk_sdk::{ - encryption::{ - auth_encryption::AeCiphertext, - elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, - pedersen::PedersenOpening, - }, - zk_elgamal_proof_program::instruction::ProofInstruction, -}; -#[cfg(not(target_os = "solana"))] -use { - super::ConfidentialMintBurn, - crate::{ - check_program_account, - instruction::{encode_instruction, TokenInstruction}, - }, - solana_program::{ - instruction::{AccountMeta, Instruction}, - program_error::ProgramError, - sysvar, - }, +use solana_zk_sdk::zk_elgamal_proof_program::proof_data::{ + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData, }; use { crate::extension::confidential_transfer::DecryptableBalance, @@ -39,6 +20,19 @@ use { crate::serialization::aeciphertext_fromstr, serde::{Deserialize, Serialize}, }; +#[cfg(not(target_os = "solana"))] +use { + crate::{ + check_program_account, + instruction::{encode_instruction, TokenInstruction}, + proof::{ProofData, ProofLocation}, + }, + solana_program::{ + instruction::{AccountMeta, Instruction}, + program_error::ProgramError, + sysvar, + }, +}; /// Confidential Transfer extension instructions #[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] @@ -64,12 +58,124 @@ pub enum ConfidentialMintBurnInstruction { /// `InitializeMintData` InitializeMint, /// Updates mint-authority for confidential-mint-burn 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: + /// `UpdateAuthorityData` UpdateAuthority, - /// Rotates the ElGamal key used to encrypt confidential supply - RotateSupplyElGamal, - /// Mints confidential tokens to + /// Rotates the ElGamal pubkey used to encrypt confidential supply + /// + /// 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: + /// `RotateSupplyElGamalPubkeyData` + RotateSupplyElGamalPubkey, + /// Updates the decryptable supply of the mint + /// + /// 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: + /// `UpdateDecryptableSupplyData` + UpdateDecryptableSupply, + /// Mints tokens to confidential balance + /// + /// 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. `[]` The context state account containing the pre-verified + /// `CiphertextCommitmentEquality` proof + /// 3. `[]` The context state account containing the pre-verified + /// `BatchedGroupedCiphertext3HandlesValidity` proof + /// 4. `[]` The context state account containing the pre-verified + /// `BatchedRangeProofU128` + /// 5. `[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. `[]` The context state account containing the pre-verified + /// `CiphertextCommitmentEquality` proof + /// 3. `[]` The context state account containing the pre-verified + /// `BatchedGroupedCiphertext3HandlesValidity` proof + /// 4. `[]` The context state account containing the pre-verified + /// `BatchedRangeProofU128` + /// 2. `[]` The multisig account owner. + /// 3.. `[signer]` Required M signer accounts for the SPL Token Multisig + /// + /// Data expected by this instruction: + /// `MintInstructionData` ConfidentialMint, - /// Removes whitelist designation for specific address + /// Burn tokens from confidential balance + /// + /// 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. `[]` The context state account containing the pre-verified + /// `CiphertextCommitmentEquality` proof + /// 3. `[]` The context state account containing the pre-verified + /// `BatchedGroupedCiphertext3HandlesValidity` proof + /// 4. `[]` The context state account containing the pre-verified + /// `BatchedRangeProofU128` + /// 5. `[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. `[]` The context state account containing the pre-verified + /// `CiphertextCommitmentEquality` proof + /// 3. `[]` The context state account containing the pre-verified + /// `BatchedGroupedCiphertext3HandlesValidity` proof + /// 4. `[]` The context state account containing the pre-verified + /// `BatchedRangeProofU128` + /// 2. `[]` The multisig account owner. + /// 3.. `[signer]` Required M signer accounts for the SPL Token Multisig + /// + /// Data expected by this instruction: + /// `BurnInstructionData` ConfidentialBurn, } @@ -101,13 +207,24 @@ pub struct UpdateAuthorityData { #[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] #[repr(C)] -pub struct RotateSupplyElGamalData { +pub struct RotateSupplyElGamalPubkeyData { /// The new ElGamal pubkey for supply encryption pub new_supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, /// The location of the /// `ProofInstruction::VerifyCiphertextCiphertextEquality` instruction /// relative to the `RotateSupplyElGamal` instruction in the transaction - pub proof_location: i8, + 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` @@ -198,129 +315,85 @@ pub fn update_authority( /// Create a `RotateSupplyElGamal` instruction #[allow(clippy::too_many_arguments)] #[cfg(not(target_os = "solana"))] -pub fn rotate_supply_elgamal( +pub fn rotate_supply_elgamal_pubkey( token_program_id: &Pubkey, mint: &Pubkey, authority: &Pubkey, multisig_signers: &[&Pubkey], - extension_state: &ConfidentialMintBurn, - current_supply: u64, - supply_elgamal_keypair: &ElGamalKeypair, - new_supply_elgamal_keypair: &ElGamalKeypair, -) -> Result, ProgramError> { + new_supply_elgamal_pubkey: ElGamalPubkey, + ciphertext_equality_proof: ProofLocation, +) -> Result { check_program_account(token_program_id)?; let mut accounts = vec![ AccountMeta::new(*mint, false), - AccountMeta::new_readonly(*authority, multisig_signers.is_empty()), + AccountMeta::new_readonly(sysvar::instructions::id(), false), ]; + + let proof_instruction_offset: i8 = match ciphertext_equality_proof { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + 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 new_supply_opening = PedersenOpening::new_rand(); - let new_supply_ciphertext = new_supply_elgamal_keypair - .pubkey() - .encrypt_with(current_supply, &new_supply_opening); - - let proof_data = CiphertextCiphertextEqualityProofData::new( - supply_elgamal_keypair, - new_supply_elgamal_keypair.pubkey(), - &ElGamalCiphertext::try_from(extension_state.confidential_supply) - .map_err(|_| TokenError::InvalidState)?, - &new_supply_ciphertext, - &new_supply_opening, - current_supply, - ) - .map_err(|_| TokenError::ProofGeneration)?; - accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); - - Ok(vec![ - encode_instruction( - token_program_id, - accounts, - TokenInstruction::ConfidentialMintBurnExtension, - ConfidentialMintBurnInstruction::RotateSupplyElGamal, - &RotateSupplyElGamalData { - new_supply_elgamal_pubkey: Some(Into::::into( - *new_supply_elgamal_keypair.pubkey(), - )) - .try_into()?, - proof_location: 1, - }, - ), - ProofInstruction::VerifyCiphertextCiphertextEquality.encode_verify_proof(None, &proof_data), - ]) + Ok(encode_instruction( + token_program_id, + accounts, + TokenInstruction::ConfidentialMintBurnExtension, + ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey, + &RotateSupplyElGamalPubkeyData { + new_supply_elgamal_pubkey: Some(Into::::into( + new_supply_elgamal_pubkey, + )) + .try_into()?, + proof_instruction_offset, + }, + )) } -/// Create a `ConfidentialMint` instruction -#[allow(clippy::too_many_arguments)] +/// Create a `UpdateMint` instruction #[cfg(not(target_os = "solana"))] -pub fn confidential_mint_with_split_proofs( +pub fn update_decryptable_supply( token_program_id: &Pubkey, - token_account: &Pubkey, mint: &Pubkey, - supply_elgamal_pubkey: Option, authority: &Pubkey, multisig_signers: &[&Pubkey], - context_accounts: &MintSplitContextStateAccounts, new_decryptable_supply: AeCiphertext, ) -> Result { 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)); - } - - accounts.push(AccountMeta::new_readonly( - *context_accounts.equality_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.ciphertext_validity_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.range_proof, - false, - )); - - accounts.push(AccountMeta::new_readonly( - *authority, - multisig_signers.is_empty(), - )); + 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::ConfidentialMint, - &MintInstructionData { + ConfidentialMintBurnInstruction::UpdateAuthority, + &UpdateDecryptableSupplyData { new_decryptable_supply: new_decryptable_supply.into(), - proof_instruction_offset: 0, }, )) } -/// Context state accounts used in confidential mint -#[derive(Clone, Copy)] -pub struct BurnSplitContextStateAccounts<'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, -} - /// Context state accounts used in confidential mint #[derive(Clone, Copy)] pub struct MintSplitContextStateAccounts<'a> { @@ -334,43 +407,111 @@ pub struct MintSplitContextStateAccounts<'a> { pub authority: &'a Pubkey, } -/// Create a `ConfidentialBurn` instruction +/// Create a `ConfidentialMint` instruction #[allow(clippy::too_many_arguments)] #[cfg(not(target_os = "solana"))] -pub fn confidential_burn_with_split_proofs( +pub fn confidential_mint_with_split_proofs( token_program_id: &Pubkey, token_account: &Pubkey, mint: &Pubkey, supply_elgamal_pubkey: Option, - new_decryptable_available_balance: DecryptableBalance, - context_accounts: &BurnSplitContextStateAccounts, authority: &Pubkey, multisig_signers: &[&Pubkey], -) -> Result, ProgramError> { - Ok(vec![inner_confidential_burn_with_split_proofs( + equality_proof_location: ProofLocation, + ciphertext_validity_proof_location: ProofLocation< + '_, + BatchedGroupedCiphertext3HandlesValidityProofData, + >, + range_proof_location: ProofLocation, + new_decryptable_supply: AeCiphertext, +) -> Result { + 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 proof_instruction_offset = match equality_proof_location { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + match ciphertext_validity_proof_location { + ProofLocation::InstructionOffset(_, proof_data) => { + // sysvar only pushed once since verify_proof reads it out and then + // supplies it to verify_and_extract + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + } + } + + match range_proof_location { + ProofLocation::InstructionOffset(_, proof_data) => { + // sysvar only pushed once since verify_proof reads it out and then + // supplies it to verify_and_extract + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + } + } + + 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)); + } + + Ok(encode_instruction( token_program_id, - token_account, - mint, - supply_elgamal_pubkey, - new_decryptable_available_balance, - context_accounts, - authority, - multisig_signers, - )?]) + accounts, + TokenInstruction::ConfidentialMintBurnExtension, + ConfidentialMintBurnInstruction::ConfidentialMint, + &MintInstructionData { + new_decryptable_supply: new_decryptable_supply.into(), + proof_instruction_offset, + }, + )) } /// Create a inner `ConfidentialBurn` instruction #[allow(clippy::too_many_arguments)] #[cfg(not(target_os = "solana"))] -pub fn inner_confidential_burn_with_split_proofs( +pub fn confidential_burn_with_split_proofs( token_program_id: &Pubkey, token_account: &Pubkey, mint: &Pubkey, supply_elgamal_pubkey: Option, new_decryptable_available_balance: DecryptableBalance, - context_accounts: &BurnSplitContextStateAccounts, authority: &Pubkey, multisig_signers: &[&Pubkey], + equality_proof_location: ProofLocation, + ciphertext_validity_proof_location: ProofLocation< + '_, + BatchedGroupedCiphertext3HandlesValidityProofData, + >, + range_proof_location: ProofLocation, ) -> Result { check_program_account(token_program_id)?; let mut accounts = vec![AccountMeta::new(*token_account, false)]; @@ -380,18 +521,45 @@ pub fn inner_confidential_burn_with_split_proofs( accounts.push(AccountMeta::new_readonly(*mint, false)); } - accounts.push(AccountMeta::new_readonly( - *context_accounts.equality_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.ciphertext_validity_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.range_proof, - false, - )); + let proof_instruction_offset = match equality_proof_location { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + match ciphertext_validity_proof_location { + ProofLocation::InstructionOffset(_, proof_data) => { + // sysvar only pushed once since verify_proof reads it out and then + // supplies it to verify_and_extract + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + } + } + + match range_proof_location { + ProofLocation::InstructionOffset(_, proof_data) => { + // sysvar only pushed once since verify_proof reads it out and then + // supplies it to verify_and_extract + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + } + } accounts.push(AccountMeta::new_readonly( *authority, @@ -409,7 +577,7 @@ pub fn inner_confidential_burn_with_split_proofs( ConfidentialMintBurnInstruction::ConfidentialBurn, &BurnInstructionData { new_decryptable_available_balance, - proof_instruction_offset: 0, + proof_instruction_offset, }, )) } diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index b8d1b810964..c04b5d9ecd2 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -8,7 +8,8 @@ use { confidential_mint_burn::{ instruction::{ BurnInstructionData, ConfidentialMintBurnInstruction, InitializeMintData, - MintInstructionData, RotateSupplyElGamalData, UpdateAuthorityData, + MintInstructionData, RotateSupplyElGamalPubkeyData, UpdateAuthorityData, + UpdateDecryptableSupplyData, }, verify_proof::{verify_burn_proof, verify_mint_proof}, ConfidentialMintBurn, @@ -20,7 +21,7 @@ use { instruction::{decode_instruction_data, decode_instruction_type}, pod::{PodAccount, PodMint}, processor::Processor, - proof::decode_proof_instruction_context, + proof::verify_and_extract_context, }, bytemuck::Zeroable, solana_program::{ @@ -29,15 +30,11 @@ use { msg, program_error::ProgramError, pubkey::Pubkey, - sysvar::instructions::get_instruction_relative, }, solana_zk_sdk::{ encryption::pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey}, - zk_elgamal_proof_program::{ - instruction::ProofInstruction, - proof_data::{ - CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData, - }, + zk_elgamal_proof_program::proof_data::{ + CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData, }, }, }; @@ -51,11 +48,11 @@ fn process_initialize_mint(accounts: &[AccountInfo], data: &InitializeMintData) let mint_data = &mut mint_info.data.borrow_mut(); let mut mint = PodStateWithExtensionsMut::::unpack_uninitialized(mint_data)?; - let conf_mint_burn_ext = mint.init_extension::(true)?; + let mint_burn_extension = mint.init_extension::(true)?; - conf_mint_burn_ext.mint_authority = data.authority; - conf_mint_burn_ext.supply_elgamal_pubkey = data.supply_elgamal_pubkey; - conf_mint_burn_ext.decryptable_supply = PodAeCiphertext::zeroed(); + mint_burn_extension.mint_authority = data.authority; + mint_burn_extension.supply_elgamal_pubkey = data.supply_elgamal_pubkey; + mint_burn_extension.decryptable_supply = PodAeCiphertext::zeroed(); Ok(()) } @@ -74,75 +71,97 @@ fn process_update_mint( check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow_mut(); let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; - let mint = mint.get_extension_mut::()?; + let mint_burn_extension = mint.get_extension_mut::()?; Processor::validate_owner( program_id, - &mint.mint_authority, + &mint_burn_extension.mint_authority, authority_info, authority_info_data_len, account_info_iter.as_slice(), )?; - mint.mint_authority = new_authority; + mint_burn_extension.mint_authority = new_authority; Ok(()) } /// Processes an [RotateSupplyElGamal] instruction. #[cfg(feature = "zk-ops")] -fn process_rotate_supply_elgamal( +fn process_rotate_supply_elgamal_pubkey( program_id: &Pubkey, accounts: &[AccountInfo], - data: &RotateSupplyElGamalData, + data: &RotateSupplyElGamalPubkeyData, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let mint_info = next_account_info(account_info_iter)?; - let authority_info = next_account_info(account_info_iter)?; - let authority_info_data_len = authority_info.data_len(); check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow_mut(); let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; - let mint = mint.get_extension_mut::()?; - - Processor::validate_owner( - program_id, - &mint.mint_authority, - authority_info, - authority_info_data_len, - account_info_iter.as_slice(), - )?; - - if data.proof_location != 1 { - return Err(ProgramError::InvalidInstructionData); - } + let mint_burn_extension = mint.get_extension_mut::()?; - let sysvar_account_info = next_account_info(account_info_iter)?; - let equality_proof_instruction = - get_instruction_relative(data.proof_location as i64, sysvar_account_info)?; - - let proof_context = decode_proof_instruction_context::< + let proof_context = verify_and_extract_context::< CiphertextCiphertextEqualityProofData, CiphertextCiphertextEqualityProofContext, >( account_info_iter, - ProofInstruction::VerifyCiphertextCiphertextEquality, - &equality_proof_instruction, + data.proof_instruction_offset as i64, + None, )?; - if !mint + if !mint_burn_extension .supply_elgamal_pubkey .equals(&proof_context.first_pubkey) { return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } - if mint.confidential_supply != proof_context.first_ciphertext { + if mint_burn_extension.confidential_supply != proof_context.first_ciphertext { return Err(ProgramError::InvalidInstructionData); } - mint.supply_elgamal_pubkey = Some(proof_context.second_pubkey).try_into()?; - mint.confidential_supply = proof_context.second_ciphertext; + let authority_info = next_account_info(account_info_iter)?; + let authority_info_data_len = authority_info.data_len(); + + Processor::validate_owner( + program_id, + &mint_burn_extension.mint_authority, + authority_info, + authority_info_data_len, + account_info_iter.as_slice(), + )?; + + mint_burn_extension.supply_elgamal_pubkey = Some(proof_context.second_pubkey).try_into()?; + 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)?; + let authority_info = next_account_info(account_info_iter)?; + let authority_info_data_len = authority_info.data_len(); + + check_program_account(mint_info.owner)?; + let mint_data = &mut mint_info.data.borrow_mut(); + let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; + let mint_burn_extension = mint.get_extension_mut::()?; + + Processor::validate_owner( + program_id, + &mint_burn_extension.mint_authority, + authority_info, + authority_info_data_len, + account_info_iter.as_slice(), + )?; + + mint_burn_extension.decryptable_supply = new_decryptable_supply; Ok(()) } @@ -165,7 +184,7 @@ fn process_confidential_mint( let auditor_elgamal_pubkey = mint .get_extension::()? .auditor_elgamal_pubkey; - let conf_mint_ext = mint.get_extension_mut::()?; + let mint_burn_extension = mint.get_extension_mut::()?; let proof_context = verify_mint_proof(account_info_iter, data.proof_instruction_offset as i64)?; @@ -178,7 +197,7 @@ fn process_confidential_mint( Processor::validate_owner( program_id, - &conf_mint_ext.mint_authority, + &mint_burn_extension.mint_authority, authority_info, authority_info_data_len, account_info_iter.as_slice(), @@ -199,8 +218,6 @@ fn process_confidential_mint( return Err(TokenError::MintMismatch.into()); } - // Wrapped SOL mint obviously not possible since - // it'd enable creating SOL out of thin air assert!(!token_account.base.is_native()); let confidential_transfer_account = @@ -211,8 +228,8 @@ fn process_confidential_mint( return Err(ProgramError::InvalidInstructionData); } - if let Some(auditor_pk) = Into::>::into(auditor_elgamal_pubkey) { - if auditor_pk != proof_context.mint_pubkeys.auditor { + if let Some(auditor_pubkey) = Into::>::into(auditor_elgamal_pubkey) { + if auditor_pubkey != proof_context.mint_pubkeys.auditor { return Err(ProgramError::InvalidInstructionData); } } @@ -237,14 +254,14 @@ fn process_confidential_mint( confidential_transfer_account.increment_pending_balance_credit_counter()?; // update supply - if let Some(supply_pk) = - Into::>::into(conf_mint_ext.supply_elgamal_pubkey) + if let Some(supply_pubkey) = + Into::>::into(mint_burn_extension.supply_elgamal_pubkey) { - if supply_pk != proof_context.mint_pubkeys.supply { + if supply_pubkey != proof_context.mint_pubkeys.supply { return Err(ProgramError::InvalidInstructionData); } - let current_supply = conf_mint_ext.confidential_supply; - conf_mint_ext.confidential_supply = ciphertext_arithmetic::add_with_lo_hi( + let current_supply = mint_burn_extension.confidential_supply; + mint_burn_extension.confidential_supply = ciphertext_arithmetic::add_with_lo_hi( ¤t_supply, &proof_context .mint_amount_ciphertext_lo @@ -256,7 +273,7 @@ fn process_confidential_mint( .map_err(|_| ProgramError::InvalidAccountData)?, ) .ok_or(TokenError::CiphertextArithmeticFailed)?; - conf_mint_ext.decryptable_supply = data.new_decryptable_supply; + mint_burn_extension.decryptable_supply = data.new_decryptable_supply; } Ok(()) @@ -280,7 +297,7 @@ fn process_confidential_burn( let auditor_elgamal_pubkey = mint .get_extension::()? .auditor_elgamal_pubkey; - let conf_mint_ext = mint.get_extension_mut::()?; + let mint_burn_extension = mint.get_extension_mut::()?; let proof_context = verify_burn_proof(account_info_iter, data.proof_instruction_offset as i64)?; @@ -350,21 +367,21 @@ fn process_confidential_burn( confidential_transfer_account.decryptable_available_balance = data.new_decryptable_available_balance; - if let Some(auditor_pk) = Into::>::into(auditor_elgamal_pubkey) { - if auditor_pk != proof_context.burn_pubkeys.auditor { + if let Some(auditor_pubkey) = Into::>::into(auditor_elgamal_pubkey) { + if auditor_pubkey != proof_context.burn_pubkeys.auditor { return Err(ProgramError::InvalidInstructionData); } } // update supply - if let Some(supply_pk) = - Into::>::into(conf_mint_ext.supply_elgamal_pubkey) + if let Some(supply_pubkey) = + Into::>::into(mint_burn_extension.supply_elgamal_pubkey) { - if supply_pk != proof_context.burn_pubkeys.supply { + if supply_pubkey != proof_context.burn_pubkeys.supply { return Err(ProgramError::InvalidInstructionData); } - let current_supply = conf_mint_ext.confidential_supply; - conf_mint_ext.confidential_supply = ciphertext_arithmetic::subtract_with_lo_hi( + let current_supply = mint_burn_extension.confidential_supply; + mint_burn_extension.confidential_supply = ciphertext_arithmetic::subtract_with_lo_hi( ¤t_supply, &proof_context .burn_amount_ciphertext_lo @@ -400,10 +417,15 @@ pub(crate) fn process_instruction( let data = decode_instruction_data::(input)?; process_update_mint(program_id, accounts, data.new_authority) } - ConfidentialMintBurnInstruction::RotateSupplyElGamal => { + ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey => { msg!("ConfidentialMintBurnInstruction::RotateSupplyElGamal"); - let data = decode_instruction_data::(input)?; - process_rotate_supply_elgamal(program_id, accounts, data) + let data = decode_instruction_data::(input)?; + process_rotate_supply_elgamal_pubkey(program_id, accounts, data) + } + ConfidentialMintBurnInstruction::UpdateDecryptableSupply => { + msg!("ConfidentialMintBurnInstruction::UpdateDecryptableSupply"); + let data = decode_instruction_data::(input)?; + process_update_decryptable_supply(program_id, accounts, data.new_decryptable_supply) } ConfidentialMintBurnInstruction::ConfidentialMint => { msg!("ConfidentialMintBurnInstruction::ConfidentialMint"); 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 index 3cae4effb49..46a14af70ed 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs @@ -1,18 +1,16 @@ use crate::error::TokenError; #[cfg(feature = "zk-ops")] use { - crate::proof::{decode_proof_instruction_context, verify_and_extract_context}, + crate::proof::verify_and_extract_context, solana_program::{ account_info::{next_account_info, AccountInfo}, program_error::ProgramError, - sysvar::instructions::get_instruction_relative, }, - solana_zk_sdk::zk_elgamal_proof_program::instruction::ProofInstruction, solana_zk_sdk::zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofContext, - BatchedRangeProofU128Data, BatchedRangeProofU64Data, - CiphertextCommitmentEqualityProofContext, CiphertextCommitmentEqualityProofData, + BatchedRangeProofU128Data, CiphertextCommitmentEqualityProofContext, + CiphertextCommitmentEqualityProofData, }, spl_token_confidential_transfer_proof_extraction::burn::BurnProofContext, spl_token_confidential_transfer_proof_extraction::mint::MintProofContext, @@ -26,71 +24,55 @@ pub fn verify_mint_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, ) -> Result { - if proof_instruction_offset == 0 { - let equality_proof_context = verify_and_extract_context::< - CiphertextCommitmentEqualityProofData, - CiphertextCommitmentEqualityProofContext, - >(account_info_iter, proof_instruction_offset, None)?; - - let ciphertext_validity_proof_context = - verify_and_extract_context::< - BatchedGroupedCiphertext3HandlesValidityProofData, - BatchedGroupedCiphertext3HandlesValidityProofContext, - >(account_info_iter, proof_instruction_offset, None)?; - - let range_proof_context = verify_and_extract_context::< - BatchedRangeProofU128Data, - BatchedRangeProofContext, - >(account_info_iter, proof_instruction_offset, None)?; - - Ok(MintProofContext::verify_and_extract( - &equality_proof_context, - &ciphertext_validity_proof_context, - &range_proof_context, - ) - .map_err(|e| -> TokenError { e.into() })?) + let sysvar_account_info = if proof_instruction_offset != 0 { + Some(next_account_info(account_info_iter)?) } else { - let sysvar_account_info = next_account_info(account_info_iter)?; - - let equality_proof_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - let ciphertext_validity_instruction = - get_instruction_relative(proof_instruction_offset + 1, sysvar_account_info)?; - let range_proof_instruction = - get_instruction_relative(proof_instruction_offset + 2, sysvar_account_info)?; - - let equality_proof_context = decode_proof_instruction_context::< - CiphertextCommitmentEqualityProofData, - CiphertextCommitmentEqualityProofContext, - >( - account_info_iter, - ProofInstruction::VerifyBatchedRangeProofU64, - &equality_proof_instruction, - )?; + None + }; + + let equality_proof_context = verify_and_extract_context::< + CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofContext, + >( + account_info_iter, + proof_instruction_offset, + sysvar_account_info, + )?; + + let proof_instruction_offset = if proof_instruction_offset != 0 { + proof_instruction_offset + 1 + } else { + proof_instruction_offset + }; - let range_proof_context = - decode_proof_instruction_context::( - account_info_iter, - ProofInstruction::VerifyBatchedRangeProofU64, - &range_proof_instruction, - )?; + let ciphertext_validity_proof_context = verify_and_extract_context::< + BatchedGroupedCiphertext3HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofContext, + >( + account_info_iter, + proof_instruction_offset, + sysvar_account_info, + )?; + + let proof_instruction_offset = if proof_instruction_offset != 0 { + proof_instruction_offset + 1 + } else { + proof_instruction_offset + }; - let ciphertext_validity_proof_context = decode_proof_instruction_context::< - BatchedGroupedCiphertext3HandlesValidityProofData, - BatchedGroupedCiphertext3HandlesValidityProofContext, - >( + let range_proof_context = + verify_and_extract_context::( account_info_iter, - ProofInstruction::VerifyGroupedCiphertext2HandlesValidity, - &ciphertext_validity_instruction, + proof_instruction_offset, + sysvar_account_info, )?; - Ok(MintProofContext::verify_and_extract( - &equality_proof_context, - &ciphertext_validity_proof_context, - &range_proof_context, - ) - .map_err(|e| -> TokenError { e.into() })?) - } + 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 @@ -100,71 +82,53 @@ pub fn verify_burn_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, ) -> Result { - if proof_instruction_offset == 0 { - let equality_proof_context = verify_and_extract_context::< - CiphertextCommitmentEqualityProofData, - CiphertextCommitmentEqualityProofContext, - >(account_info_iter, proof_instruction_offset, None)?; - - let ciphertext_validity_proof_context = - verify_and_extract_context::< - BatchedGroupedCiphertext3HandlesValidityProofData, - BatchedGroupedCiphertext3HandlesValidityProofContext, - >(account_info_iter, proof_instruction_offset, None)?; - - let range_proof_context = verify_and_extract_context::< - BatchedRangeProofU128Data, - BatchedRangeProofContext, - >(account_info_iter, proof_instruction_offset, None)?; - - Ok(BurnProofContext::verify_and_extract( - &equality_proof_context, - &ciphertext_validity_proof_context, - &range_proof_context, - ) - .map_err(|e| -> TokenError { e.into() })?) + let sysvar_account_info = if proof_instruction_offset != 0 { + Some(next_account_info(account_info_iter)?) } else { - let sysvar_account_info = next_account_info(account_info_iter)?; - let equality_proof_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - let range_proof_instruction = - get_instruction_relative(proof_instruction_offset + 1, sysvar_account_info)?; - - let ciphertext_validity_instruction = - get_instruction_relative(proof_instruction_offset + 2, sysvar_account_info)?; - - let equality_proof_context = decode_proof_instruction_context::< - CiphertextCommitmentEqualityProofData, - CiphertextCommitmentEqualityProofContext, - >( - account_info_iter, - ProofInstruction::VerifyCiphertextCommitmentEquality, - &equality_proof_instruction, - )?; + None + }; + + let equality_proof_context = verify_and_extract_context::< + CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofContext, + >( + account_info_iter, + proof_instruction_offset, + sysvar_account_info, + )?; + + let proof_instruction_offset = if proof_instruction_offset != 0 { + proof_instruction_offset + 1 + } else { + proof_instruction_offset + }; - let range_proof_context = decode_proof_instruction_context::< - BatchedRangeProofU128Data, - BatchedRangeProofContext, - >( - account_info_iter, - ProofInstruction::VerifyBatchedRangeProofU128, - &range_proof_instruction, - )?; + let ciphertext_validity_proof_context = verify_and_extract_context::< + BatchedGroupedCiphertext3HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofContext, + >( + account_info_iter, + proof_instruction_offset, + sysvar_account_info, + )?; + + let proof_instruction_offset = if proof_instruction_offset != 0 { + proof_instruction_offset + 1 + } else { + proof_instruction_offset + }; - let ciphertext_validity_proof_context = decode_proof_instruction_context::< - BatchedGroupedCiphertext3HandlesValidityProofData, - BatchedGroupedCiphertext3HandlesValidityProofContext, - >( + let range_proof_context = + verify_and_extract_context::( account_info_iter, - ProofInstruction::VerifyGroupedCiphertext2HandlesValidity, - &ciphertext_validity_instruction, + proof_instruction_offset, + sysvar_account_info, )?; - Ok(BurnProofContext::verify_and_extract( - &equality_proof_context, - &ciphertext_validity_proof_context, - &range_proof_context, - ) - .map_err(|e| -> TokenError { e.into() })?) - } + 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/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index 661af463f2f..671e9e51f7e 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/mod.rs b/token/program-2022/src/extension/mod.rs index 815599e231c..4e9f3d6e88e 100644 --- a/token/program-2022/src/extension/mod.rs +++ b/token/program-2022/src/extension/mod.rs @@ -1105,9 +1105,8 @@ pub enum ExtensionType { GroupMemberPointer, /// Mint contains token group member configurations TokenGroupMember, - - /// Mint allowing the minting and burn of confidential tokens - ConfidentialMintBurn = u16::MAX - 4269, + /// Mint allowing the minting and burning of confidential tokens + ConfidentialMintBurn, /// Test variable-length mint extension #[cfg(test)] From 2a1316fe5e4e76c7243c55bae7de54804f336681 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Fri, 4 Oct 2024 14:47:12 -0300 Subject: [PATCH 20/34] refactor proof location processing --- .../confidential_mint_burn/instruction.rs | 270 +++++++++--------- .../confidential_mint_burn/processor.rs | 14 +- .../confidential_mint_burn/verify_proof.rs | 48 +--- token/program-2022/src/proof.rs | 50 +++- 4 files changed, 214 insertions(+), 168 deletions(-) diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index f8eb8ad7b69..27f3d84cc71 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -3,10 +3,14 @@ use solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey; #[cfg(not(target_os = "solana"))] use solana_zk_sdk::encryption::{auth_encryption::AeCiphertext, elgamal::ElGamalPubkey}; #[cfg(not(target_os = "solana"))] -use solana_zk_sdk::zk_elgamal_proof_program::proof_data::{ - BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, - CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData, +use solana_zk_sdk::zk_elgamal_proof_program::{ + instruction::ProofInstruction, + proof_data::{ + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData, + }, }; +#[cfg(not(target_os = "solana"))] use { crate::extension::confidential_transfer::DecryptableBalance, bytemuck::{Pod, Zeroable}, @@ -25,7 +29,7 @@ use { crate::{ check_program_account, instruction::{encode_instruction, TokenInstruction}, - proof::{ProofData, ProofLocation}, + proof::{process_proof_location, ProofLocation}, }, solana_program::{ instruction::{AccountMeta, Instruction}, @@ -122,11 +126,11 @@ pub enum ConfidentialMintBurnInstruction { /// 1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero /// supply elgamal-pubkey /// 2. `[]` The context state account containing the pre-verified - /// `CiphertextCommitmentEquality` proof + /// `VerifyCiphertextCommitmentEquality` proof /// 3. `[]` The context state account containing the pre-verified - /// `BatchedGroupedCiphertext3HandlesValidity` proof + /// `VerifyBatchedGroupedCiphertext3HandlesValidity` proof /// 4. `[]` The context state account containing the pre-verified - /// `BatchedRangeProofU128` + /// `VerifyBatchedRangeProofU128` /// 5. `[signer]` The single account owner. /// /// * Multisignature authority @@ -134,11 +138,11 @@ pub enum ConfidentialMintBurnInstruction { /// 1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero /// supply elgamal-pubkey /// 2. `[]` The context state account containing the pre-verified - /// `CiphertextCommitmentEquality` proof + /// `VerifyCiphertextCommitmentEquality` proof /// 3. `[]` The context state account containing the pre-verified - /// `BatchedGroupedCiphertext3HandlesValidity` proof + /// `VerifyBatchedGroupedCiphertext3HandlesValidity` proof /// 4. `[]` The context state account containing the pre-verified - /// `BatchedRangeProofU128` + /// `VerifyBatchedRangeProofU128` /// 2. `[]` The multisig account owner. /// 3.. `[signer]` Required M signer accounts for the SPL Token Multisig /// @@ -154,11 +158,11 @@ pub enum ConfidentialMintBurnInstruction { /// 1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero /// supply elgamal-pubkey /// 2. `[]` The context state account containing the pre-verified - /// `CiphertextCommitmentEquality` proof + /// `VerifyCiphertextCommitmentEquality` proof /// 3. `[]` The context state account containing the pre-verified - /// `BatchedGroupedCiphertext3HandlesValidity` proof + /// `VerifyBatchedGroupedCiphertext3HandlesValidity` proof /// 4. `[]` The context state account containing the pre-verified - /// `BatchedRangeProofU128` + /// `VerifyBatchedRangeProofU128` /// 5. `[signer]` The single account owner. /// /// * Multisignature authority @@ -166,13 +170,13 @@ pub enum ConfidentialMintBurnInstruction { /// 1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero /// supply elgamal-pubkey /// 2. `[]` The context state account containing the pre-verified - /// `CiphertextCommitmentEquality` proof + /// `VerifyCiphertextCommitmentEquality` proof /// 3. `[]` The context state account containing the pre-verified - /// `BatchedGroupedCiphertext3HandlesValidity` proof + /// `VerifyBatchedGroupedCiphertext3HandlesValidity` proof /// 4. `[]` The context state account containing the pre-verified - /// `BatchedRangeProofU128` - /// 2. `[]` The multisig account owner. - /// 3.. `[signer]` Required M signer accounts for the SPL Token Multisig + /// `VerifyBatchedRangeProofU128` + /// 5. `[]` The multisig account owner. + /// 6.. `[signer]` Required M signer accounts for the SPL Token Multisig /// /// Data expected by this instruction: /// `BurnInstructionData` @@ -236,12 +240,20 @@ 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::VerifyBatchedRangeProofU64` + /// 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. The - /// `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity` - /// has to always be at the instruction directly after the range proof one. - pub proof_instruction_offset: i8, + /// 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` @@ -255,12 +267,18 @@ pub struct BurnInstructionData { pub new_decryptable_available_balance: DecryptableBalance, /// Relative location of the /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction - /// to the `ConfidentialBurn` instruction in the transaction. The - /// `ProofInstruction::VerifyBatchedRangeProofU128` has to always be at - /// the instruction directly after the equality proof one, - /// with the `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity` - /// following after that. - pub proof_instruction_offset: i8, + /// 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 @@ -322,26 +340,24 @@ pub fn rotate_supply_elgamal_pubkey( multisig_signers: &[&Pubkey], new_supply_elgamal_pubkey: ElGamalPubkey, ciphertext_equality_proof: ProofLocation, -) -> Result { +) -> Result, ProgramError> { check_program_account(token_program_id)?; let mut accounts = vec![ AccountMeta::new(*mint, false), AccountMeta::new_readonly(sysvar::instructions::id(), false), ]; - let proof_instruction_offset: i8 = match ciphertext_equality_proof { - ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { - accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); - if let ProofData::RecordAccount(record_address, _) = proof_data { - accounts.push(AccountMeta::new_readonly(*record_address, false)); - } - proof_instruction_offset.into() - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - 0 - } - }; + 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, @@ -351,7 +367,7 @@ pub fn rotate_supply_elgamal_pubkey( accounts.push(AccountMeta::new_readonly(**multisig_signer, true)); } - Ok(encode_instruction( + let mut instructions = vec![encode_instruction( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, @@ -363,7 +379,11 @@ pub fn rotate_supply_elgamal_pubkey( .try_into()?, proof_instruction_offset, }, - )) + )]; + + instructions.extend_from_slice(&proof_instructions); + + Ok(instructions) } /// Create a `UpdateMint` instruction @@ -424,7 +444,7 @@ pub fn confidential_mint_with_split_proofs( >, range_proof_location: ProofLocation, new_decryptable_supply: AeCiphertext, -) -> Result { +) -> Result, 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 @@ -435,45 +455,35 @@ pub fn confidential_mint_with_split_proofs( accounts.push(AccountMeta::new_readonly(*mint, false)); } - let proof_instruction_offset = match equality_proof_location { - ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { - accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); - if let ProofData::RecordAccount(record_address, _) = proof_data { - accounts.push(AccountMeta::new_readonly(*record_address, false)); - } - proof_instruction_offset.into() - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - 0 - } - }; - - match ciphertext_validity_proof_location { - ProofLocation::InstructionOffset(_, proof_data) => { - // sysvar only pushed once since verify_proof reads it out and then - // supplies it to verify_and_extract - if let ProofData::RecordAccount(record_address, _) = proof_data { - accounts.push(AccountMeta::new_readonly(*record_address, false)); - } - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - } - } + let mut expected_instruction_offset = 1; + let mut proof_instructions = vec![]; - match range_proof_location { - ProofLocation::InstructionOffset(_, proof_data) => { - // sysvar only pushed once since verify_proof reads it out and then - // supplies it to verify_and_extract - if let ProofData::RecordAccount(record_address, _) = proof_data { - accounts.push(AccountMeta::new_readonly(*record_address, false)); - } - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - } - } + 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, @@ -483,16 +493,22 @@ pub fn confidential_mint_with_split_proofs( accounts.push(AccountMeta::new_readonly(**multisig_signer, true)); } - Ok(encode_instruction( + let mut instructions = vec![encode_instruction( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, ConfidentialMintBurnInstruction::ConfidentialMint, &MintInstructionData { new_decryptable_supply: new_decryptable_supply.into(), - proof_instruction_offset, + 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 @@ -512,7 +528,7 @@ pub fn confidential_burn_with_split_proofs( BatchedGroupedCiphertext3HandlesValidityProofData, >, range_proof_location: ProofLocation, -) -> Result { +) -> Result, ProgramError> { check_program_account(token_program_id)?; let mut accounts = vec![AccountMeta::new(*token_account, false)]; if supply_elgamal_pubkey.is_some() { @@ -521,45 +537,35 @@ pub fn confidential_burn_with_split_proofs( accounts.push(AccountMeta::new_readonly(*mint, false)); } - let proof_instruction_offset = match equality_proof_location { - ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { - accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); - if let ProofData::RecordAccount(record_address, _) = proof_data { - accounts.push(AccountMeta::new_readonly(*record_address, false)); - } - proof_instruction_offset.into() - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - 0 - } - }; - - match ciphertext_validity_proof_location { - ProofLocation::InstructionOffset(_, proof_data) => { - // sysvar only pushed once since verify_proof reads it out and then - // supplies it to verify_and_extract - if let ProofData::RecordAccount(record_address, _) = proof_data { - accounts.push(AccountMeta::new_readonly(*record_address, false)); - } - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - } - } + let mut expected_instruction_offset = 1; + let mut proof_instructions = vec![]; - match range_proof_location { - ProofLocation::InstructionOffset(_, proof_data) => { - // sysvar only pushed once since verify_proof reads it out and then - // supplies it to verify_and_extract - if let ProofData::RecordAccount(record_address, _) = proof_data { - accounts.push(AccountMeta::new_readonly(*record_address, false)); - } - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - } - } + 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, @@ -570,14 +576,20 @@ pub fn confidential_burn_with_split_proofs( accounts.push(AccountMeta::new_readonly(**multisig_signer, true)); } - Ok(encode_instruction( + let mut instructions = vec![encode_instruction( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, ConfidentialMintBurnInstruction::ConfidentialBurn, &BurnInstructionData { new_decryptable_available_balance, - proof_instruction_offset, + 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/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index c04b5d9ecd2..c3a23d33037 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -186,7 +186,12 @@ fn process_confidential_mint( .auditor_elgamal_pubkey; let mint_burn_extension = mint.get_extension_mut::()?; - let proof_context = verify_mint_proof(account_info_iter, data.proof_instruction_offset as i64)?; + 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(); @@ -299,7 +304,12 @@ fn process_confidential_burn( .auditor_elgamal_pubkey; let mint_burn_extension = mint.get_extension_mut::()?; - let proof_context = verify_burn_proof(account_info_iter, data.proof_instruction_offset as i64)?; + 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(); 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 index 46a14af70ed..45bfa36168b 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/verify_proof.rs @@ -22,9 +22,11 @@ use { #[cfg(feature = "zk-ops")] pub fn verify_mint_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, - proof_instruction_offset: i64, + equality_proof_instruction_offset: i8, + ciphertext_validity_proof_instruction_offset: i8, + range_proof_instruction_offset: i8, ) -> Result { - let sysvar_account_info = if proof_instruction_offset != 0 { + let sysvar_account_info = if equality_proof_instruction_offset != 0 { Some(next_account_info(account_info_iter)?) } else { None @@ -35,35 +37,23 @@ pub fn verify_mint_proof( CiphertextCommitmentEqualityProofContext, >( account_info_iter, - proof_instruction_offset, + equality_proof_instruction_offset as i64, sysvar_account_info, )?; - let proof_instruction_offset = if proof_instruction_offset != 0 { - proof_instruction_offset + 1 - } else { - proof_instruction_offset - }; - let ciphertext_validity_proof_context = verify_and_extract_context::< BatchedGroupedCiphertext3HandlesValidityProofData, BatchedGroupedCiphertext3HandlesValidityProofContext, >( account_info_iter, - proof_instruction_offset, + ciphertext_validity_proof_instruction_offset as i64, sysvar_account_info, )?; - let proof_instruction_offset = if proof_instruction_offset != 0 { - proof_instruction_offset + 1 - } else { - proof_instruction_offset - }; - let range_proof_context = verify_and_extract_context::( account_info_iter, - proof_instruction_offset, + range_proof_instruction_offset as i64, sysvar_account_info, )?; @@ -80,9 +70,11 @@ pub fn verify_mint_proof( #[cfg(feature = "zk-ops")] pub fn verify_burn_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, - proof_instruction_offset: i64, + equality_proof_instruction_offset: i8, + ciphertext_validity_proof_instruction_offset: i8, + range_proof_instruction_offset: i8, ) -> Result { - let sysvar_account_info = if proof_instruction_offset != 0 { + let sysvar_account_info = if equality_proof_instruction_offset != 0 { Some(next_account_info(account_info_iter)?) } else { None @@ -93,35 +85,23 @@ pub fn verify_burn_proof( CiphertextCommitmentEqualityProofContext, >( account_info_iter, - proof_instruction_offset, + equality_proof_instruction_offset as i64, sysvar_account_info, )?; - let proof_instruction_offset = if proof_instruction_offset != 0 { - proof_instruction_offset + 1 - } else { - proof_instruction_offset - }; - let ciphertext_validity_proof_context = verify_and_extract_context::< BatchedGroupedCiphertext3HandlesValidityProofData, BatchedGroupedCiphertext3HandlesValidityProofContext, >( account_info_iter, - proof_instruction_offset, + ciphertext_validity_proof_instruction_offset as i64, sysvar_account_info, )?; - let proof_instruction_offset = if proof_instruction_offset != 0 { - proof_instruction_offset + 1 - } else { - proof_instruction_offset - }; - let range_proof_context = verify_and_extract_context::( account_info_iter, - proof_instruction_offset, + range_proof_instruction_offset as i64, sysvar_account_info, )?; diff --git a/token/program-2022/src/proof.rs b/token/program-2022/src/proof.rs index 1bebfabe827..2b8c55347f1 100644 --- a/token/program-2022/src/proof.rs +++ b/token/program-2022/src/proof.rs @@ -1,15 +1,15 @@ //! Helper for processing instruction data from ZK ElGamal proof program use { - crate::check_zk_elgamal_proof_program_account, + crate::{check_zk_elgamal_proof_program_account, error::TokenError}, bytemuck::Pod, solana_program::{ account_info::{next_account_info, AccountInfo}, - 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, @@ -134,6 +134,50 @@ pub fn verify_and_extract_context<'a, T: Pod + ZkProofData, U: Pod>( } } +/// todo +pub fn process_proof_location( + accounts: &mut Vec, + expected_instruction_offset: &mut i8, + proof_instructions: &mut Vec, + proof_location: ProofLocation, + push_sysvar_to_accounts: bool, + proof_instruction_type: ProofInstruction, +) -> Result +where + T: Pod + ZkProofData, + 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(TokenError::InvalidProofInstructionOffset.into()); + } + + 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::(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 += 1; + 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( From 390868840b8e7238fc961439c9ed1248c282975a Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Fri, 4 Oct 2024 14:50:22 -0300 Subject: [PATCH 21/34] comment --- token/program-2022/src/proof.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/token/program-2022/src/proof.rs b/token/program-2022/src/proof.rs index 2b8c55347f1..0384c2451fc 100644 --- a/token/program-2022/src/proof.rs +++ b/token/program-2022/src/proof.rs @@ -134,7 +134,11 @@ pub fn verify_and_extract_context<'a, T: Pod + ZkProofData, U: Pod>( } } -/// todo +/// 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( accounts: &mut Vec, expected_instruction_offset: &mut i8, From 57684cf581415494d2ec92ab5b03006a65f74818 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Fri, 4 Oct 2024 15:53:24 -0300 Subject: [PATCH 22/34] cleanup --- .../src/extension/confidential_mint_burn/instruction.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 27f3d84cc71..40396198057 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -34,7 +34,6 @@ use { solana_program::{ instruction::{AccountMeta, Instruction}, program_error::ProgramError, - sysvar, }, }; @@ -342,10 +341,7 @@ pub fn rotate_supply_elgamal_pubkey( ciphertext_equality_proof: ProofLocation, ) -> Result, ProgramError> { check_program_account(token_program_id)?; - let mut accounts = vec![ - AccountMeta::new(*mint, false), - AccountMeta::new_readonly(sysvar::instructions::id(), false), - ]; + let mut accounts = vec![AccountMeta::new(*mint, false)]; let mut expected_instruction_offset = 1; let mut proof_instructions = vec![]; @@ -439,7 +435,6 @@ pub fn confidential_mint_with_split_proofs( multisig_signers: &[&Pubkey], equality_proof_location: ProofLocation, ciphertext_validity_proof_location: ProofLocation< - '_, BatchedGroupedCiphertext3HandlesValidityProofData, >, range_proof_location: ProofLocation, @@ -524,7 +519,6 @@ pub fn confidential_burn_with_split_proofs( multisig_signers: &[&Pubkey], equality_proof_location: ProofLocation, ciphertext_validity_proof_location: ProofLocation< - '_, BatchedGroupedCiphertext3HandlesValidityProofData, >, range_proof_location: ProofLocation, From 32e06db2b873392a05f64bb244c5197550334860 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Sat, 5 Oct 2024 09:08:05 -0300 Subject: [PATCH 23/34] fix target_os=solana ; test-sbf --- .../confidential_mint_burn/instruction.rs | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 40396198057..13074acd1d6 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -1,5 +1,5 @@ #[cfg(not(target_os = "solana"))] -use solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey; +use crate::proof::{process_proof_location, ProofLocation}; #[cfg(not(target_os = "solana"))] use solana_zk_sdk::encryption::{auth_encryption::AeCiphertext, elgamal::ElGamalPubkey}; #[cfg(not(target_os = "solana"))] @@ -10,31 +10,26 @@ use solana_zk_sdk::zk_elgamal_proof_program::{ CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData, }, }; -#[cfg(not(target_os = "solana"))] -use { - crate::extension::confidential_transfer::DecryptableBalance, - bytemuck::{Pod, Zeroable}, - num_enum::{IntoPrimitive, TryFromPrimitive}, - solana_program::pubkey::Pubkey, - solana_zk_sdk::encryption::pod::auth_encryption::PodAeCiphertext, - spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, -}; #[cfg(feature = "serde-traits")] use { crate::serialization::aeciphertext_fromstr, serde::{Deserialize, Serialize}, }; -#[cfg(not(target_os = "solana"))] use { crate::{ check_program_account, + extension::confidential_transfer::DecryptableBalance, instruction::{encode_instruction, TokenInstruction}, - proof::{process_proof_location, ProofLocation}, }, + 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}, + spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, }; /// Confidential Transfer extension instructions @@ -281,7 +276,6 @@ pub struct BurnInstructionData { } /// Create a `InitializeMint` instruction -#[cfg(not(target_os = "solana"))] pub fn initialize_mint( token_program_id: &Pubkey, mint: &Pubkey, @@ -304,7 +298,6 @@ pub fn initialize_mint( } /// Create a `UpdateMint` instruction -#[cfg(not(target_os = "solana"))] pub fn update_authority( token_program_id: &Pubkey, mint: &Pubkey, From 4fea36d6aeaddc3a362223bc387d3466bcff4b06 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Mon, 7 Oct 2024 17:44:37 -0300 Subject: [PATCH 24/34] review fixes --- .../proof-generation/src/lib.rs | 1 - .../proof-generation/src/mint.rs | 31 +--- .../proof-generation/src/supply.rs | 31 ---- .../proof-tests/tests/proof_test.rs | 3 +- .../confidential_mint_burn/account_info.rs | 112 ++++++++++++ .../confidential_mint_burn/instruction.rs | 170 ++++++++---------- .../extension/confidential_mint_burn/mod.rs | 9 +- .../confidential_mint_burn/processor.rs | 67 +++---- token/program-2022/src/instruction.rs | 4 + token/program-2022/src/processor.rs | 14 ++ 10 files changed, 232 insertions(+), 210 deletions(-) delete mode 100644 token/confidential-transfer/proof-generation/src/supply.rs create mode 100644 token/program-2022/src/extension/confidential_mint_burn/account_info.rs diff --git a/token/confidential-transfer/proof-generation/src/lib.rs b/token/confidential-transfer/proof-generation/src/lib.rs index 7685c223b96..f8883f31954 100644 --- a/token/confidential-transfer/proof-generation/src/lib.rs +++ b/token/confidential-transfer/proof-generation/src/lib.rs @@ -10,7 +10,6 @@ pub mod burn; pub mod encryption; pub mod errors; pub mod mint; -pub mod supply; pub mod transfer; pub mod transfer_with_fee; pub mod withdraw; diff --git a/token/confidential-transfer/proof-generation/src/mint.rs b/token/confidential-transfer/proof-generation/src/mint.rs index da3f668c8c3..1ada01840ab 100644 --- a/token/confidential-transfer/proof-generation/src/mint.rs +++ b/token/confidential-transfer/proof-generation/src/mint.rs @@ -8,7 +8,6 @@ use { auth_encryption::{AeCiphertext, AeKey}, elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, pedersen::Pedersen, - pod::auth_encryption::PodAeCiphertext, }, zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, @@ -33,8 +32,8 @@ pub struct MintProofData { 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, @@ -79,34 +78,8 @@ pub fn mint_split_proof_data( ) .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; - // fresh mints are initialized with a zeroed decryptable_supply - // TODO: don't clone here once AeCiphertext implement Copy in the zk-sdk - let pod_decryptable_supply: PodAeCiphertext = current_decryptable_supply.clone().into(); - let current_decyptable_supply = if pod_decryptable_supply != PodAeCiphertext::default() { - // decrypt the current supply - current_decryptable_supply - .decrypt(supply_aes_key) - .ok_or(TokenProofGenerationError::IllegalAmountBitLength)? - } else { - 0 - }; - - // 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 = supply_elgamal_keypair - .pubkey() - .encrypt(current_decyptable_supply); - #[allow(clippy::arithmetic_side_effects)] - let ct_decryptable_to_current_diff = decryptable_supply_ciphertext - current_supply_ciphertext; - let decryptable_to_current_diff = supply_elgamal_keypair - .secret() - .decrypt_u32(&ct_decryptable_to_current_diff) - .ok_or(TokenProofGenerationError::SupplyDecryption)?; - // compute the new supply - let new_supply = current_decyptable_supply - .checked_sub(decryptable_to_current_diff) - .ok_or(TokenProofGenerationError::IllegalAmountBitLength)? + let new_supply = current_supply .checked_add(mint_amount) .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; diff --git a/token/confidential-transfer/proof-generation/src/supply.rs b/token/confidential-transfer/proof-generation/src/supply.rs deleted file mode 100644 index a13c16a39a5..00000000000 --- a/token/confidential-transfer/proof-generation/src/supply.rs +++ /dev/null @@ -1,31 +0,0 @@ -use { - crate::errors::TokenProofGenerationError, - solana_zk_sdk::{ - encryption::{ - elgamal::{ElGamalCiphertext, ElGamalKeypair}, - pedersen::PedersenOpening, - }, - zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData, - }, -}; - -pub fn supply_elgamal_pubkey_rotation_proof( - current_supply: u64, - supply_elgamal_keypair: &ElGamalKeypair, - new_supply_elgamal_keypair: &ElGamalKeypair, - current_supply_ciphertext: ElGamalCiphertext, -) -> Result { - let new_supply_opening = PedersenOpening::new_rand(); - let new_supply_ciphertext = new_supply_elgamal_keypair - .pubkey() - .encrypt_with(current_supply, &new_supply_opening); - - Ok(CiphertextCiphertextEqualityProofData::new( - supply_elgamal_keypair, - new_supply_elgamal_keypair.pubkey(), - ¤t_supply_ciphertext, - &new_supply_ciphertext, - &new_supply_opening, - current_supply, - )?) -} diff --git a/token/confidential-transfer/proof-tests/tests/proof_test.rs b/token/confidential-transfer/proof-tests/tests/proof_test.rs index 3cb5d1ad5ae..6e73e303ed3 100644 --- a/token/confidential-transfer/proof-tests/tests/proof_test.rs +++ b/token/confidential-transfer/proof-tests/tests/proof_test.rs @@ -217,7 +217,6 @@ 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, @@ -226,8 +225,8 @@ fn test_mint_validity(mint_amount: u64, supply: u64) { new_decryptable_supply: _, } = mint_split_proof_data( &supply_ciphertext, - &decryptable_supply, mint_amount, + supply, &supply_keypair, &supply_aes_key, destination_pubkey, 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 00000000000..3d9e88b5ef6 --- /dev/null +++ b/token/program-2022/src/extension/confidential_mint_burn/account_info.rs @@ -0,0 +1,112 @@ +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}, + }, + zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData, + }, + spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, +}; + +/// 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 `encrypiton_pubkey`) + pub current_supply: PodElGamalCiphertext, + /// The decryptable supply + pub decryptable_supply: PodAeCiphertext, + /// The supply's elgamal pubkey + pub supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, +} + +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 { + if self.supply_elgamal_pubkey.is_none() { + return Err(TokenError::InvalidState); + } + // fresh mints are initialized with a zeroed decryptable_supply + // TODO: include decryptable supply in InitMint instruction + let current_decyptable_supply = if self.decryptable_supply != PodAeCiphertext::default() { + // decrypt the current supply + TryInto::::try_into(self.decryptable_supply) + .map_err(|_| TokenError::MalformedCiphertext)? + .decrypt(aes_key) + .ok_or(TokenError::MalformedCiphertext)? + } else { + 0 + }; + + // 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 + - (TryInto::::try_into(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 { + 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 index 13074acd1d6..858c191d661 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -10,6 +10,7 @@ use solana_zk_sdk::zk_elgamal_proof_program::{ CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData, }, }; +use spl_pod::optional_keys::OptionalNonZeroPubkey; #[cfg(feature = "serde-traits")] use { crate::serialization::aeciphertext_fromstr, @@ -55,34 +56,26 @@ pub enum ConfidentialMintBurnInstruction { /// Data expected by this instruction: /// `InitializeMintData` InitializeMint, - /// Updates mint-authority for confidential-mint-burn 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: - /// `UpdateAuthorityData` - UpdateAuthority, /// Rotates the ElGamal pubkey used to encrypt confidential supply /// /// Accounts expected by this instruction: /// /// * Single authority /// 0. `[writable]` The SPL Token mint. - /// 1. `[signer]` Confidential mint authority. + /// 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. `[]` The multisig authority account owner. - /// 2.. `[signer]` Required M signer accounts for the SPL Token Multisig + /// 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` @@ -93,20 +86,12 @@ pub enum ConfidentialMintBurnInstruction { /// /// * 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. + /// 1. `[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 + /// 1. `[]` The multisig authority account owner. + /// 2.. `[signer]` Required M signer accounts for the SPL Token Multisig /// /// Data expected by this instruction: /// `UpdateDecryptableSupplyData` @@ -119,26 +104,32 @@ pub enum ConfidentialMintBurnInstruction { /// 0. `[writable]` The SPL Token account. /// 1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero /// supply elgamal-pubkey - /// 2. `[]` The context state account containing the pre-verified - /// `VerifyCiphertextCommitmentEquality` proof - /// 3. `[]` The context state account containing the pre-verified - /// `VerifyBatchedGroupedCiphertext3HandlesValidity` proof - /// 4. `[]` The context state account containing the pre-verified - /// `VerifyBatchedRangeProofU128` - /// 5. `[signer]` The single account owner. + /// 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. `[]` The context state account containing the pre-verified - /// `VerifyCiphertextCommitmentEquality` proof - /// 3. `[]` The context state account containing the pre-verified - /// `VerifyBatchedGroupedCiphertext3HandlesValidity` proof - /// 4. `[]` The context state account containing the pre-verified - /// `VerifyBatchedRangeProofU128` - /// 2. `[]` The multisig account owner. - /// 3.. `[signer]` Required M signer accounts for the SPL Token Multisig + /// 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` @@ -151,26 +142,32 @@ pub enum ConfidentialMintBurnInstruction { /// 0. `[writable]` The SPL Token account. /// 1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero /// supply elgamal-pubkey - /// 2. `[]` The context state account containing the pre-verified - /// `VerifyCiphertextCommitmentEquality` proof - /// 3. `[]` The context state account containing the pre-verified - /// `VerifyBatchedGroupedCiphertext3HandlesValidity` proof - /// 4. `[]` The context state account containing the pre-verified - /// `VerifyBatchedRangeProofU128` - /// 5. `[signer]` The single account owner. + /// 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. `[]` The context state account containing the pre-verified - /// `VerifyCiphertextCommitmentEquality` proof - /// 3. `[]` The context state account containing the pre-verified - /// `VerifyBatchedGroupedCiphertext3HandlesValidity` proof - /// 4. `[]` The context state account containing the pre-verified - /// `VerifyBatchedRangeProofU128` - /// 5. `[]` The multisig account owner. - /// 6.. `[signer]` Required M signer accounts for the SPL Token Multisig + /// 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` @@ -185,19 +182,11 @@ pub enum ConfidentialMintBurnInstruction { pub struct InitializeMintData { /// Authority used to modify the `ConfidentialMintBurn` mint /// configuration and mint new tokens - pub authority: Pubkey, + pub authority: OptionalNonZeroPubkey, /// The ElGamal pubkey used to encrypt the confidential supply pub supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, -} - -/// Data expected by `ConfidentialMintBurnInstruction::UpdateMint` -#[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 UpdateAuthorityData { - /// The new `authority` pubkey - pub new_authority: Pubkey, + /// The initial 0 supply ecrypted with the supply aes key + pub decryptable_supply: PodAeCiphertext, } /// Data expected by `ConfidentialMintBurnInstruction::RotateSupplyElGamal` @@ -279,49 +268,32 @@ pub struct BurnInstructionData { pub fn initialize_mint( token_program_id: &Pubkey, mint: &Pubkey, - authority: Pubkey, + authority: Option, confidential_supply_pubkey: Option, + decryptable_supply: Option, ) -> Result { check_program_account(token_program_id)?; let accounts = vec![AccountMeta::new(*mint, false)]; + let decryptable_supply = if confidential_supply_pubkey.is_some() { + decryptable_supply.ok_or(ProgramError::InvalidInstructionData)? + } else { + PodAeCiphertext::zeroed() + }; + Ok(encode_instruction( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, ConfidentialMintBurnInstruction::InitializeMint, &InitializeMintData { - authority, + authority: authority.try_into()?, supply_elgamal_pubkey: confidential_supply_pubkey.try_into()?, + decryptable_supply, }, )) } -/// Create a `UpdateMint` instruction -pub fn update_authority( - token_program_id: &Pubkey, - mint: &Pubkey, - authority: &Pubkey, - multisig_signers: &[&Pubkey], - new_authority: Pubkey, -) -> Result { - 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::UpdateAuthority, - &UpdateAuthorityData { new_authority }, - )) -} - /// Create a `RotateSupplyElGamal` instruction #[allow(clippy::too_many_arguments)] #[cfg(not(target_os = "solana"))] @@ -396,7 +368,7 @@ pub fn update_decryptable_supply( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, - ConfidentialMintBurnInstruction::UpdateAuthority, + ConfidentialMintBurnInstruction::UpdateDecryptableSupply, &UpdateDecryptableSupplyData { new_decryptable_supply: new_decryptable_supply.into(), }, diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index ce86579ba23..ea12fb2cf4c 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -1,11 +1,10 @@ use { crate::extension::{Extension, ExtensionType}, bytemuck::{Pod, Zeroable}, - solana_program::pubkey::Pubkey, solana_zk_sdk::encryption::pod::{ auth_encryption::PodAeCiphertext, elgamal::PodElGamalCiphertext, }, - spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, + spl_pod::optional_keys::{OptionalNonZeroElGamalPubkey, OptionalNonZeroPubkey}, }; /// Maximum bit length of any mint or burn amount @@ -25,13 +24,17 @@ 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 { /// Authority to modify the `ConfidentialMintBurnMint` configuration and to /// mint new confidential tokens - pub mint_authority: Pubkey, + pub authority: OptionalNonZeroPubkey, /// The confidential supply of the mint (encrypted by `encryption_pubkey`) pub confidential_supply: PodElGamalCiphertext, /// The decryptable confidential supply of the mint diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index c3a23d33037..6029181f14a 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -8,7 +8,7 @@ use { confidential_mint_burn::{ instruction::{ BurnInstructionData, ConfidentialMintBurnInstruction, InitializeMintData, - MintInstructionData, RotateSupplyElGamalPubkeyData, UpdateAuthorityData, + MintInstructionData, RotateSupplyElGamalPubkeyData, UpdateDecryptableSupplyData, }, verify_proof::{verify_burn_proof, verify_mint_proof}, @@ -23,7 +23,6 @@ use { processor::Processor, proof::verify_and_extract_context, }, - bytemuck::Zeroable, solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, @@ -50,38 +49,9 @@ fn process_initialize_mint(accounts: &[AccountInfo], data: &InitializeMintData) let mut mint = PodStateWithExtensionsMut::::unpack_uninitialized(mint_data)?; let mint_burn_extension = mint.init_extension::(true)?; - mint_burn_extension.mint_authority = data.authority; + mint_burn_extension.authority = data.authority; mint_burn_extension.supply_elgamal_pubkey = data.supply_elgamal_pubkey; - mint_burn_extension.decryptable_supply = PodAeCiphertext::zeroed(); - - Ok(()) -} - -/// Processes an [UpdateAuthority] instruction. -fn process_update_mint( - program_id: &Pubkey, - accounts: &[AccountInfo], - new_authority: Pubkey, -) -> ProgramResult { - let account_info_iter = &mut accounts.iter(); - let mint_info = next_account_info(account_info_iter)?; - let authority_info = next_account_info(account_info_iter)?; - let authority_info_data_len = authority_info.data_len(); - - check_program_account(mint_info.owner)?; - let mint_data = &mut mint_info.data.borrow_mut(); - let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; - let mint_burn_extension = mint.get_extension_mut::()?; - - Processor::validate_owner( - program_id, - &mint_burn_extension.mint_authority, - authority_info, - authority_info_data_len, - account_info_iter.as_slice(), - )?; - - mint_burn_extension.mint_authority = new_authority; + mint_burn_extension.decryptable_supply = data.decryptable_supply; Ok(()) } @@ -110,10 +80,13 @@ fn process_rotate_supply_elgamal_pubkey( None, )?; - if !mint_burn_extension - .supply_elgamal_pubkey - .equals(&proof_context.first_pubkey) - { + let supply_elgamal_pubkey: Option = + 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 { @@ -123,9 +96,12 @@ fn process_rotate_supply_elgamal_pubkey( let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); + let authority = Option::::from(mint_burn_extension.authority) + .ok_or(TokenError::NoAuthorityExists)?; + Processor::validate_owner( program_id, - &mint_burn_extension.mint_authority, + &authority, authority_info, authority_info_data_len, account_info_iter.as_slice(), @@ -153,9 +129,12 @@ fn process_update_decryptable_supply( let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; let mint_burn_extension = mint.get_extension_mut::()?; + let authority = Option::::from(mint_burn_extension.authority) + .ok_or(TokenError::NoAuthorityExists)?; + Processor::validate_owner( program_id, - &mint_burn_extension.mint_authority, + &authority, authority_info, authority_info_data_len, account_info_iter.as_slice(), @@ -200,9 +179,12 @@ fn process_confidential_mint( let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); + let authority = Option::::from(mint_burn_extension.authority) + .ok_or(TokenError::NoAuthorityExists)?; + Processor::validate_owner( program_id, - &mint_burn_extension.mint_authority, + &authority, authority_info, authority_info_data_len, account_info_iter.as_slice(), @@ -422,11 +404,6 @@ pub(crate) fn process_instruction( let data = decode_instruction_data::(input)?; process_initialize_mint(accounts, data) } - ConfidentialMintBurnInstruction::UpdateAuthority => { - msg!("ConfidentialMintBurnInstruction::UpdateMint"); - let data = decode_instruction_data::(input)?; - process_update_mint(program_id, accounts, data.new_authority) - } ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey => { msg!("ConfidentialMintBurnInstruction::RotateSupplyElGamal"); let data = decode_instruction_data::(input)?; diff --git a/token/program-2022/src/instruction.rs b/token/program-2022/src/instruction.rs index 3d766aa0230..490fafa12ff 100644 --- a/token/program-2022/src/instruction.rs +++ b/token/program-2022/src/instruction.rs @@ -1122,6 +1122,8 @@ pub enum AuthorityType { GroupPointer, /// Authority to set the group member address GroupMemberPointer, + /// Authority to mint new confidential tokens + MintConfidentialTokens, } impl AuthorityType { @@ -1142,6 +1144,7 @@ impl AuthorityType { AuthorityType::MetadataPointer => 12, AuthorityType::GroupPointer => 13, AuthorityType::GroupMemberPointer => 14, + AuthorityType::MintConfidentialTokens => 15, } } @@ -1162,6 +1165,7 @@ impl AuthorityType { 12 => Ok(AuthorityType::MetadataPointer), 13 => Ok(AuthorityType::GroupPointer), 14 => Ok(AuthorityType::GroupMemberPointer), + 15 => Ok(AuthorityType::MintConfidentialTokens), _ => Err(TokenError::InvalidInstruction.into()), } } diff --git a/token/program-2022/src/processor.rs b/token/program-2022/src/processor.rs index 697cf042f8a..37c0295d4b7 100644 --- a/token/program-2022/src/processor.rs +++ b/token/program-2022/src/processor.rs @@ -905,6 +905,20 @@ impl Processor { )?; extension.authority = new_authority.try_into()?; } + AuthorityType::MintConfidentialTokens => { + let extension = mint.get_extension_mut::()?; + let maybe_authority: Option = extension.authority.into(); + let authority = maybe_authority.ok_or(TokenError::AuthorityTypeNotSupported)?; + + Self::validate_owner( + program_id, + &authority, + authority_info, + authority_info_data_len, + account_info_iter.as_slice(), + )?; + extension.authority = new_authority.try_into()?; + } _ => { return Err(TokenError::AuthorityTypeNotSupported.into()); } From 3a14e2edd595b270f8d622f617b80c00bb47c373 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 8 Oct 2024 11:34:20 -0300 Subject: [PATCH 25/34] fix fmt / serde --- .../src/extension/confidential_mint_burn/instruction.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 858c191d661..3acec6e973f 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -10,7 +10,6 @@ use solana_zk_sdk::zk_elgamal_proof_program::{ CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData, }, }; -use spl_pod::optional_keys::OptionalNonZeroPubkey; #[cfg(feature = "serde-traits")] use { crate::serialization::aeciphertext_fromstr, @@ -30,7 +29,7 @@ use { pubkey::Pubkey, }, solana_zk_sdk::encryption::pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey}, - spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, + spl_pod::optional_keys::{OptionalNonZeroElGamalPubkey, OptionalNonZeroPubkey}, }; /// Confidential Transfer extension instructions @@ -186,6 +185,7 @@ pub struct InitializeMintData { /// The ElGamal pubkey used to encrypt the confidential supply pub supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, /// The initial 0 supply ecrypted with the supply aes key + #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub decryptable_supply: PodAeCiphertext, } @@ -268,7 +268,7 @@ pub struct BurnInstructionData { pub fn initialize_mint( token_program_id: &Pubkey, mint: &Pubkey, - authority: Option, + authority: Pubkey, confidential_supply_pubkey: Option, decryptable_supply: Option, ) -> Result { @@ -281,6 +281,7 @@ pub fn initialize_mint( PodAeCiphertext::zeroed() }; + let authority = Some(authority); Ok(encode_instruction( token_program_id, accounts, From 8ad746efe0b6094b5c06fdc75c39cf9ffd105707 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 8 Oct 2024 11:37:12 -0300 Subject: [PATCH 26/34] construct supply account info from ref to extension --- .../src/extension/confidential_mint_burn/account_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 3d9e88b5ef6..d9fc3915624 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/account_info.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/account_info.rs @@ -30,7 +30,7 @@ pub struct SupplyAccountInfo { impl SupplyAccountInfo { /// Creates a SupplyAccountInfo from ConfidentialMintBurn extension account /// data - pub fn new(extension: ConfidentialMintBurn) -> Self { + pub fn new(extension: &ConfidentialMintBurn) -> Self { Self { current_supply: extension.confidential_supply, decryptable_supply: extension.decryptable_supply, From df6cd8bc51050b64f75e7d54a0f9017c9e33c90d Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Wed, 9 Oct 2024 09:42:35 -0300 Subject: [PATCH 27/34] add conf mint/burn failure docs; remove todo --- .../src/extension/confidential_mint_burn/account_info.rs | 1 - .../src/extension/confidential_mint_burn/instruction.rs | 6 ++++++ .../src/extension/confidential_transfer/account_info.rs | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) 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 index d9fc3915624..557c3bc7ac2 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/account_info.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/account_info.rs @@ -50,7 +50,6 @@ impl SupplyAccountInfo { return Err(TokenError::InvalidState); } // fresh mints are initialized with a zeroed decryptable_supply - // TODO: include decryptable supply in InitMint instruction let current_decyptable_supply = if self.decryptable_supply != PodAeCiphertext::default() { // decrypt the current supply TryInto::::try_into(self.decryptable_supply) diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 3acec6e973f..db3b4f27bff 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -97,6 +97,9 @@ pub enum ConfidentialMintBurnInstruction { UpdateDecryptableSupply, /// Mints tokens to confidential balance /// + /// Fails if the destination account is frozen. + /// Fails if the associated mint is extended as `NonTransferable`. + /// /// Accounts expected by this instruction: /// /// * Single authority @@ -135,6 +138,9 @@ pub enum ConfidentialMintBurnInstruction { ConfidentialMint, /// Burn tokens from confidential balance /// + /// Fails if the destination account is frozen. + /// Fails if the associated mint is extended as `NonTransferable`. + /// /// Accounts expected by this instruction: /// /// * Single authority 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 be01ae4d1a0..39b819b9a70 100644 --- a/token/program-2022/src/extension/confidential_transfer/account_info.rs +++ b/token/program-2022/src/extension/confidential_transfer/account_info.rs @@ -241,7 +241,6 @@ impl TransferAccountInfo { /// Create a transfer proof data that is split into equality, ciphertext /// validity, and range proofs. - #[allow(clippy::type_complexity)] pub fn generate_split_transfer_proof_data( &self, transfer_amount: u64, From 20c84b5872760f3a91093814da0c01c9c4030c3b Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Wed, 9 Oct 2024 10:41:10 -0300 Subject: [PATCH 28/34] remove obsolete null check --- .../confidential_mint_burn/account_info.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) 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 index 557c3bc7ac2..a4110635bd7 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/account_info.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/account_info.rs @@ -49,16 +49,11 @@ impl SupplyAccountInfo { if self.supply_elgamal_pubkey.is_none() { return Err(TokenError::InvalidState); } - // fresh mints are initialized with a zeroed decryptable_supply - let current_decyptable_supply = if self.decryptable_supply != PodAeCiphertext::default() { - // decrypt the current supply - TryInto::::try_into(self.decryptable_supply) - .map_err(|_| TokenError::MalformedCiphertext)? - .decrypt(aes_key) - .ok_or(TokenError::MalformedCiphertext)? - } else { - 0 - }; + // decrypt the decryptable supply + let current_decyptable_supply = TryInto::::try_into(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 From bcc081224cc5ef3643c2fcc0eaf0d9e93b26dd5d Mon Sep 17 00:00:00 2001 From: abcalphabet Date: Thu, 24 Oct 2024 16:19:42 -0300 Subject: [PATCH 29/34] Update token/program-2022/src/error.rs Co-authored-by: Jon C --- token/program-2022/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token/program-2022/src/error.rs b/token/program-2022/src/error.rs index 726eb6808b9..3266f6c22f2 100644 --- a/token/program-2022/src/error.rs +++ b/token/program-2022/src/error.rs @@ -261,7 +261,7 @@ pub enum TokenError { //65 /// Withdraw / Deposit not allowed for confidential-mint-burn - #[error("When the confidential-mint-burn extension is enabled, mints are only allowed into the confidential balance. Likewise conversions confidential token balance to normal balance and vice versa are illegal.")] + #[error("Withdraw / Deposit not allowed for confidential-mint-burn")] IllegalMintBurnConversion, /// Undecryptable supply when trying to generate confidential-mint proofs #[error("Could not decrypt difference between current supply and decryptable supply when generating mint proofs")] From 1d2b8044adac71a770c41da8b9f927951c61fcbe Mon Sep 17 00:00:00 2001 From: abcalphabet Date: Thu, 24 Oct 2024 16:19:55 -0300 Subject: [PATCH 30/34] Update token/program-2022/src/extension/confidential_mint_burn/instruction.rs Co-authored-by: Jon C --- .../src/extension/confidential_mint_burn/instruction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index db3b4f27bff..13da814744d 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -274,7 +274,7 @@ pub struct BurnInstructionData { pub fn initialize_mint( token_program_id: &Pubkey, mint: &Pubkey, - authority: Pubkey, + authority: &Pubkey, confidential_supply_pubkey: Option, decryptable_supply: Option, ) -> Result { From 534861e7fd8f98dc77f2db55b00eec685a2ac194 Mon Sep 17 00:00:00 2001 From: abcalphabet Date: Thu, 24 Oct 2024 16:34:43 -0300 Subject: [PATCH 31/34] Update token/program-2022/src/extension/confidential_mint_burn/account_info.rs Co-authored-by: Jon C --- .../src/extension/confidential_mint_burn/account_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a4110635bd7..cce20d68252 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/account_info.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/account_info.rs @@ -19,7 +19,7 @@ use { #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] pub struct SupplyAccountInfo { - /// The available balance (encrypted by `encrypiton_pubkey`) + /// The available balance (encrypted by `supply_elgamal_pubkey`) pub current_supply: PodElGamalCiphertext, /// The decryptable supply pub decryptable_supply: PodAeCiphertext, From b01ac2476517952fb06c8ca7d558f6ca8110d5c9 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Thu, 24 Oct 2024 17:05:48 -0300 Subject: [PATCH 32/34] review fixes --- libraries/pod/src/optional_keys.rs | 10 +- token/cli/src/command.rs | 9 +- token/client/src/token.rs | 4 +- .../proof-generation/src/errors.rs | 2 - token/program-2022/src/error.rs | 7 -- .../confidential_mint_burn/account_info.rs | 17 ++-- .../confidential_mint_burn/instruction.rs | 59 +++++------- .../extension/confidential_mint_burn/mod.rs | 7 +- .../confidential_mint_burn/processor.rs | 95 +++++++------------ token/program-2022/src/extension/mod.rs | 6 ++ token/program-2022/src/serialization.rs | 49 +--------- 11 files changed, 85 insertions(+), 180 deletions(-) diff --git a/libraries/pod/src/optional_keys.rs b/libraries/pod/src/optional_keys.rs index b2a78efd117..1bfc28fd113 100644 --- a/libraries/pod/src/optional_keys.rs +++ b/libraries/pod/src/optional_keys.rs @@ -101,7 +101,7 @@ impl<'de> Visitor<'de> for OptionalNonZeroPubkeyVisitor { where E: Error, { - let pkey = Pubkey::from_str(&v) + let pkey = Pubkey::from_str(v) .map_err(|_| Error::invalid_value(Unexpected::Str(v), &"value string"))?; OptionalNonZeroPubkey::try_from(Some(pkey)) @@ -162,14 +162,6 @@ impl From for Option { } } } -impl OptionalNonZeroElGamalPubkey { - pub fn is_none(&self) -> bool { - self.0 == PodElGamalPubkey::default() - } - pub fn is_some(&self) -> bool { - self.0 != PodElGamalPubkey::default() - } -} #[cfg(feature = "serde-traits")] impl Serialize for OptionalNonZeroElGamalPubkey { diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 00001d3c454..cd9c9f12252 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -73,14 +73,7 @@ use { }, spl_token_group_interface::state::TokenGroup, spl_token_metadata_interface::state::{Field, TokenMetadata}, - std::{ - collections::HashMap, - fmt::Display, - process::exit, - rc::Rc, - str::{self, FromStr}, - sync::Arc, - }, + std::{collections::HashMap, fmt::Display, process::exit, rc::Rc, str::FromStr, sync::Arc}, }; fn print_error_and_exit(e: E) -> T { diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 2258dda5a43..165616a645f 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -36,7 +36,6 @@ use { ApplyPendingBalanceAccountInfo, EmptyAccountAccountInfo, TransferAccountInfo, WithdrawAccountInfo, }, - instruction::{ProofContextState, ZkProofData}, ConfidentialTransferAccount, DecryptableBalance, }, confidential_transfer_fee::{ @@ -58,6 +57,8 @@ use { zk_elgamal_proof_program::{ self, instruction::{close_context_state, ContextStateInfo}, + proof_data::*, + state::ProofContextState, }, }, state::{Account, AccountState, Mint, Multisig}, @@ -110,7 +111,6 @@ pub enum TokenError { #[error("decimals specified, but incorrect")] InvalidDecimals, } - impl PartialEq for TokenError { fn eq(&self, other: &Self) -> bool { match (self, other) { diff --git a/token/confidential-transfer/proof-generation/src/errors.rs b/token/confidential-transfer/proof-generation/src/errors.rs index fd11c8b8578..5cb5c14e794 100644 --- a/token/confidential-transfer/proof-generation/src/errors.rs +++ b/token/confidential-transfer/proof-generation/src/errors.rs @@ -10,6 +10,4 @@ pub enum TokenProofGenerationError { IllegalAmountBitLength, #[error("fee calculation failed")] FeeCalculation, - #[error("supply decryption failed")] - SupplyDecryption, } diff --git a/token/program-2022/src/error.rs b/token/program-2022/src/error.rs index 3266f6c22f2..54e4e250190 100644 --- a/token/program-2022/src/error.rs +++ b/token/program-2022/src/error.rs @@ -263,9 +263,6 @@ pub enum TokenError { /// Withdraw / Deposit not allowed for confidential-mint-burn #[error("Withdraw / Deposit not allowed for confidential-mint-burn")] IllegalMintBurnConversion, - /// Undecryptable supply when trying to generate confidential-mint proofs - #[error("Could not decrypt difference between current supply and decryptable supply when generating mint proofs")] - SupplyDecryption, } impl From for ProgramError { fn from(e: TokenError) -> Self { @@ -456,9 +453,6 @@ impl PrintProgramError for TokenError { TokenError::IllegalMintBurnConversion => { msg!("Conversions from normal to confidential token balance and vice versa are illegal if the confidential-mint-burn extension is enabled") } - TokenError::SupplyDecryption => { - msg!("Could not decrypt difference between current supply and decryptable supply when generating mint proofs") - } } } } @@ -471,7 +465,6 @@ impl From for TokenError { TokenProofGenerationError::NotEnoughFunds => TokenError::InsufficientFunds, TokenProofGenerationError::IllegalAmountBitLength => TokenError::IllegalBitLength, TokenProofGenerationError::FeeCalculation => TokenError::FeeCalculation, - TokenProofGenerationError::SupplyDecryption => TokenError::SupplyDecryption, } } } 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 index cce20d68252..01cae83acc7 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/account_info.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/account_info.rs @@ -7,11 +7,13 @@ use { auth_encryption::{AeCiphertext, AeKey}, elgamal::{ElGamalCiphertext, ElGamalKeypair}, pedersen::PedersenOpening, - pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalCiphertext}, + pod::{ + auth_encryption::PodAeCiphertext, + elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, + }, }, zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData, }, - spl_pod::optional_keys::OptionalNonZeroElGamalPubkey, }; /// Confidential Mint Burn extension information needed to construct a @@ -24,7 +26,7 @@ pub struct SupplyAccountInfo { /// The decryptable supply pub decryptable_supply: PodAeCiphertext, /// The supply's elgamal pubkey - pub supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, + pub supply_elgamal_pubkey: PodElGamalPubkey, } impl SupplyAccountInfo { @@ -46,11 +48,8 @@ impl SupplyAccountInfo { aes_key: &AeKey, elgamal_keypair: &ElGamalKeypair, ) -> Result { - if self.supply_elgamal_pubkey.is_none() { - return Err(TokenError::InvalidState); - } // decrypt the decryptable supply - let current_decyptable_supply = TryInto::::try_into(self.decryptable_supply) + let current_decyptable_supply = AeCiphertext::try_from(self.decryptable_supply) .map_err(|_| TokenError::MalformedCiphertext)? .decrypt(aes_key) .ok_or(TokenError::MalformedCiphertext)?; @@ -61,8 +60,8 @@ impl SupplyAccountInfo { elgamal_keypair.pubkey().encrypt(current_decyptable_supply); #[allow(clippy::arithmetic_side_effects)] let supply_delta_ciphertext = decryptable_supply_ciphertext - - (TryInto::::try_into(self.current_supply) - .map_err(|_| TokenError::MalformedCiphertext)?); + - ElGamalCiphertext::try_from(self.current_supply) + .map_err(|_| TokenError::MalformedCiphertext)?; let decryptable_to_current_diff = elgamal_keypair .secret() .decrypt_u32(&supply_delta_ciphertext) diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index 13da814744d..c8500f1cf26 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -1,18 +1,20 @@ #[cfg(not(target_os = "solana"))] -use crate::proof::{process_proof_location, ProofLocation}; -#[cfg(not(target_os = "solana"))] -use solana_zk_sdk::encryption::{auth_encryption::AeCiphertext, elgamal::ElGamalPubkey}; -#[cfg(not(target_os = "solana"))] -use solana_zk_sdk::zk_elgamal_proof_program::{ - instruction::ProofInstruction, - proof_data::{ - BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, - CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData, +use { + crate::proof::{process_proof_location, ProofLocation}, + solana_zk_sdk::{ + encryption::{auth_encryption::AeCiphertext, elgamal::ElGamalPubkey}, + zk_elgamal_proof_program::{ + instruction::ProofInstruction, + proof_data::{ + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData, + }, + }, }, }; #[cfg(feature = "serde-traits")] use { - crate::serialization::aeciphertext_fromstr, + crate::serialization::{aeciphertext_fromstr, elgamalpubkey_fromstr}, serde::{Deserialize, Serialize}, }; use { @@ -29,7 +31,7 @@ use { pubkey::Pubkey, }, solana_zk_sdk::encryption::pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey}, - spl_pod::optional_keys::{OptionalNonZeroElGamalPubkey, OptionalNonZeroPubkey}, + spl_pod::optional_keys::OptionalNonZeroPubkey, }; /// Confidential Transfer extension instructions @@ -98,7 +100,6 @@ pub enum ConfidentialMintBurnInstruction { /// Mints tokens to confidential balance /// /// Fails if the destination account is frozen. - /// Fails if the associated mint is extended as `NonTransferable`. /// /// Accounts expected by this instruction: /// @@ -135,11 +136,10 @@ pub enum ConfidentialMintBurnInstruction { /// /// Data expected by this instruction: /// `MintInstructionData` - ConfidentialMint, + Mint, /// Burn tokens from confidential balance /// /// Fails if the destination account is frozen. - /// Fails if the associated mint is extended as `NonTransferable`. /// /// Accounts expected by this instruction: /// @@ -176,7 +176,7 @@ pub enum ConfidentialMintBurnInstruction { /// /// Data expected by this instruction: /// `BurnInstructionData` - ConfidentialBurn, + Burn, } /// Data expected by `ConfidentialMintBurnInstruction::InitializeMint` @@ -189,7 +189,8 @@ pub struct InitializeMintData { /// configuration and mint new tokens pub authority: OptionalNonZeroPubkey, /// The ElGamal pubkey used to encrypt the confidential supply - pub supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, + #[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, @@ -202,7 +203,8 @@ pub struct InitializeMintData { #[repr(C)] pub struct RotateSupplyElGamalPubkeyData { /// The new ElGamal pubkey for supply encryption - pub new_supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, + #[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 @@ -275,19 +277,13 @@ pub fn initialize_mint( token_program_id: &Pubkey, mint: &Pubkey, authority: &Pubkey, - confidential_supply_pubkey: Option, - decryptable_supply: Option, + supply_elgamal_pubkey: PodElGamalPubkey, + decryptable_supply: PodAeCiphertext, ) -> Result { check_program_account(token_program_id)?; let accounts = vec![AccountMeta::new(*mint, false)]; - let decryptable_supply = if confidential_supply_pubkey.is_some() { - decryptable_supply.ok_or(ProgramError::InvalidInstructionData)? - } else { - PodAeCiphertext::zeroed() - }; - - let authority = Some(authority); + let authority = Some(*authority); Ok(encode_instruction( token_program_id, accounts, @@ -295,7 +291,7 @@ pub fn initialize_mint( ConfidentialMintBurnInstruction::InitializeMint, &InitializeMintData { authority: authority.try_into()?, - supply_elgamal_pubkey: confidential_supply_pubkey.try_into()?, + supply_elgamal_pubkey, decryptable_supply, }, )) @@ -341,10 +337,7 @@ pub fn rotate_supply_elgamal_pubkey( TokenInstruction::ConfidentialMintBurnExtension, ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey, &RotateSupplyElGamalPubkeyData { - new_supply_elgamal_pubkey: Some(Into::::into( - new_supply_elgamal_pubkey, - )) - .try_into()?, + new_supply_elgamal_pubkey: PodElGamalPubkey::from(new_supply_elgamal_pubkey), proof_instruction_offset, }, )]; @@ -464,7 +457,7 @@ pub fn confidential_mint_with_split_proofs( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, - ConfidentialMintBurnInstruction::ConfidentialMint, + ConfidentialMintBurnInstruction::Mint, &MintInstructionData { new_decryptable_supply: new_decryptable_supply.into(), equality_proof_instruction_offset, @@ -546,7 +539,7 @@ pub fn confidential_burn_with_split_proofs( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, - ConfidentialMintBurnInstruction::ConfidentialBurn, + ConfidentialMintBurnInstruction::Burn, &BurnInstructionData { new_decryptable_available_balance, equality_proof_instruction_offset, diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index ea12fb2cf4c..fe28b4448c1 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -2,9 +2,10 @@ use { crate::extension::{Extension, ExtensionType}, bytemuck::{Pod, Zeroable}, solana_zk_sdk::encryption::pod::{ - auth_encryption::PodAeCiphertext, elgamal::PodElGamalCiphertext, + auth_encryption::PodAeCiphertext, + elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, }, - spl_pod::optional_keys::{OptionalNonZeroElGamalPubkey, OptionalNonZeroPubkey}, + spl_pod::optional_keys::OptionalNonZeroPubkey, }; /// Maximum bit length of any mint or burn amount @@ -40,7 +41,7 @@ pub struct ConfidentialMintBurn { /// The decryptable confidential supply of the mint pub decryptable_supply: PodAeCiphertext, /// The ElGamal pubkey used to encrypt the confidential supply - pub supply_elgamal_pubkey: OptionalNonZeroElGamalPubkey, + pub supply_elgamal_pubkey: PodElGamalPubkey, } impl Extension for 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 index 6029181f14a..156e7dea592 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -15,7 +15,6 @@ use { ConfidentialMintBurn, }, confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint}, - non_transferable::NonTransferableAccount, BaseStateWithExtensions, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, }, instruction::{decode_instruction_data, decode_instruction_type}, @@ -107,7 +106,7 @@ fn process_rotate_supply_elgamal_pubkey( account_info_iter.as_slice(), )?; - mint_burn_extension.supply_elgamal_pubkey = Some(proof_context.second_pubkey).try_into()?; + mint_burn_extension.supply_elgamal_pubkey = proof_context.second_pubkey; mint_burn_extension.confidential_supply = proof_context.second_ciphertext; Ok(()) @@ -190,13 +189,6 @@ fn process_confidential_mint( account_info_iter.as_slice(), )?; - if token_account - .get_extension::() - .is_ok() - { - return Err(TokenError::NonTransferable.into()); - } - if token_account.base.is_frozen() { return Err(TokenError::AccountFrozen.into()); } @@ -215,7 +207,7 @@ fn process_confidential_mint( return Err(ProgramError::InvalidInstructionData); } - if let Some(auditor_pubkey) = Into::>::into(auditor_elgamal_pubkey) { + if let Some(auditor_pubkey) = Option::::from(auditor_elgamal_pubkey) { if auditor_pubkey != proof_context.mint_pubkeys.auditor { return Err(ProgramError::InvalidInstructionData); } @@ -241,27 +233,23 @@ fn process_confidential_mint( confidential_transfer_account.increment_pending_balance_credit_counter()?; // update supply - if let Some(supply_pubkey) = - Into::>::into(mint_burn_extension.supply_elgamal_pubkey) - { - if supply_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( - ¤t_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; + 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( + ¤t_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(()) } @@ -308,13 +296,6 @@ fn process_confidential_burn( account_info_iter.as_slice(), )?; - if token_account - .get_extension::() - .is_ok() - { - return Err(TokenError::NonTransferable.into()); - } - if token_account.base.is_frozen() { return Err(TokenError::AccountFrozen.into()); } @@ -359,33 +340,29 @@ fn process_confidential_burn( confidential_transfer_account.decryptable_available_balance = data.new_decryptable_available_balance; - if let Some(auditor_pubkey) = Into::>::into(auditor_elgamal_pubkey) { + if let Some(auditor_pubkey) = Option::::from(auditor_elgamal_pubkey) { if auditor_pubkey != proof_context.burn_pubkeys.auditor { return Err(ProgramError::InvalidInstructionData); } } // update supply - if let Some(supply_pubkey) = - Into::>::into(mint_burn_extension.supply_elgamal_pubkey) - { - if supply_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( - ¤t_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)?; + 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( + ¤t_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(()) } @@ -414,12 +391,12 @@ pub(crate) fn process_instruction( let data = decode_instruction_data::(input)?; process_update_decryptable_supply(program_id, accounts, data.new_decryptable_supply) } - ConfidentialMintBurnInstruction::ConfidentialMint => { + ConfidentialMintBurnInstruction::Mint => { msg!("ConfidentialMintBurnInstruction::ConfidentialMint"); let data = decode_instruction_data::(input)?; process_confidential_mint(program_id, accounts, data) } - ConfidentialMintBurnInstruction::ConfidentialBurn => { + ConfidentialMintBurnInstruction::Burn => { msg!("ConfidentialMintBurnInstruction::ConfidentialBurn"); let data = decode_instruction_data::(input)?; process_confidential_burn(program_id, accounts, data) diff --git a/token/program-2022/src/extension/mod.rs b/token/program-2022/src/extension/mod.rs index 4e9f3d6e88e..f2e09ab2a07 100644 --- a/token/program-2022/src/extension/mod.rs +++ b/token/program-2022/src/extension/mod.rs @@ -1304,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 { @@ -1312,6 +1313,7 @@ impl ExtensionType { ExtensionType::ConfidentialTransferFeeConfig => { confidential_transfer_fee_config = true } + ExtensionType::ConfidentialMintBurn => confidential_mint_burn = true, _ => (), } } @@ -1325,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/serialization.rs b/token/program-2022/src/serialization.rs index 9d72b2f9287..74d4f642adb 100644 --- a/token/program-2022/src/serialization.rs +++ b/token/program-2022/src/serialization.rs @@ -72,9 +72,7 @@ pub mod coption_fromstr { D: Deserializer<'de>, T: FromStr, { - d.deserialize_option(COptionVisitor { - s: PhantomData::default(), - }) + d.deserialize_option(COptionVisitor { s: PhantomData }) } } @@ -123,51 +121,6 @@ pub mod aeciphertext_fromstr { } } -/// helper to ser/deser ElGamalCiphertext values -pub mod elgamalciphertext_fromstr { - use { - serde::{ - de::{Error, Visitor}, - Deserializer, Serializer, - }, - solana_zk_sdk::encryption::pod::elgamal::PodElGamalCiphertext, - std::{fmt, str::FromStr}, - }; - - /// serialize AeCiphertext values supporting Display trait - pub fn serialie(x: &PodElGamalCiphertext, s: S) -> Result - where - S: Serializer, - { - s.serialize_str(&x.to_string()) - } - - struct ElGamalCiphertextVisitor; - - impl<'de> Visitor<'de> for ElGamalCiphertextVisitor { - type Value = PodElGamalCiphertext; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a FromStr type") - } - - fn visit_str(self, v: &str) -> Result - where - E: Error, - { - FromStr::from_str(v).map_err(Error::custom) - } - } - - /// deserialize AeCiphertext values from str - pub fn deserialize<'de, D>(d: D) -> Result - where - D: Deserializer<'de>, - { - d.deserialize_str(ElGamalCiphertextVisitor) - } -} - /// helper to ser/deser pod::ElGamalPubkey values pub mod elgamalpubkey_fromstr { use { From 5256e9deded1edba54ddbd811ea2e0ff4d341c17 Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Mon, 28 Oct 2024 18:10:55 -0300 Subject: [PATCH 33/34] more fixes --- .../confidential_mint_burn/instruction.rs | 7 ------ .../extension/confidential_mint_burn/mod.rs | 4 --- .../confidential_mint_burn/processor.rs | 25 +++++++++++-------- token/program-2022/src/processor.rs | 14 ----------- 4 files changed, 14 insertions(+), 36 deletions(-) diff --git a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs index cd441fecf02..6e808d3653e 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/instruction.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/instruction.rs @@ -17,7 +17,6 @@ use { pubkey::Pubkey, }, solana_zk_sdk::encryption::pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey}, - spl_pod::optional_keys::OptionalNonZeroPubkey, }; #[cfg(not(target_os = "solana"))] use { @@ -187,9 +186,6 @@ pub enum ConfidentialMintBurnInstruction { #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] #[repr(C)] pub struct InitializeMintData { - /// Authority used to modify the `ConfidentialMintBurn` mint - /// configuration and mint new tokens - pub authority: OptionalNonZeroPubkey, /// The ElGamal pubkey used to encrypt the confidential supply #[cfg_attr(feature = "serde-traits", serde(with = "elgamalpubkey_fromstr"))] pub supply_elgamal_pubkey: PodElGamalPubkey, @@ -278,21 +274,18 @@ pub struct BurnInstructionData { pub fn initialize_mint( token_program_id: &Pubkey, mint: &Pubkey, - authority: &Pubkey, supply_elgamal_pubkey: PodElGamalPubkey, decryptable_supply: PodAeCiphertext, ) -> Result { check_program_account(token_program_id)?; let accounts = vec![AccountMeta::new(*mint, false)]; - let authority = Some(*authority); Ok(encode_instruction( token_program_id, accounts, TokenInstruction::ConfidentialMintBurnExtension, ConfidentialMintBurnInstruction::InitializeMint, &InitializeMintData { - authority: authority.try_into()?, supply_elgamal_pubkey, decryptable_supply, }, diff --git a/token/program-2022/src/extension/confidential_mint_burn/mod.rs b/token/program-2022/src/extension/confidential_mint_burn/mod.rs index fe28b4448c1..049ced2684c 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/mod.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/mod.rs @@ -5,7 +5,6 @@ use { auth_encryption::PodAeCiphertext, elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, }, - spl_pod::optional_keys::OptionalNonZeroPubkey, }; /// Maximum bit length of any mint or burn amount @@ -33,9 +32,6 @@ pub mod account_info; #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] #[repr(C)] pub struct ConfidentialMintBurn { - /// Authority to modify the `ConfidentialMintBurnMint` configuration and to - /// mint new confidential tokens - pub authority: OptionalNonZeroPubkey, /// The confidential supply of the mint (encrypted by `encryption_pubkey`) pub confidential_supply: PodElGamalCiphertext, /// The decryptable confidential supply of the mint diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index e75f7ecd2c7..15103d4166b 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -34,6 +34,7 @@ use { CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData, }, }, + spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_confidential_transfer_proof_extraction::instruction::verify_and_extract_context, }; @@ -48,7 +49,6 @@ fn process_initialize_mint(accounts: &[AccountInfo], data: &InitializeMintData) let mut mint = PodStateWithExtensionsMut::::unpack_uninitialized(mint_data)?; let mint_burn_extension = mint.init_extension::(true)?; - mint_burn_extension.authority = data.authority; mint_burn_extension.supply_elgamal_pubkey = data.supply_elgamal_pubkey; mint_burn_extension.decryptable_supply = data.decryptable_supply; @@ -68,6 +68,7 @@ fn process_rotate_supply_elgamal_pubkey( check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow_mut(); let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; + let mint_authority = mint.base.mint_authority; let mint_burn_extension = mint.get_extension_mut::()?; let proof_context = verify_and_extract_context::< @@ -95,8 +96,8 @@ fn process_rotate_supply_elgamal_pubkey( let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); - let authority = Option::::from(mint_burn_extension.authority) - .ok_or(TokenError::NoAuthorityExists)?; + let authority = OptionalNonZeroPubkey::try_from(mint_authority)?; + let authority = Option::::from(authority).ok_or(TokenError::NoAuthorityExists)?; Processor::validate_owner( program_id, @@ -126,10 +127,11 @@ fn process_update_decryptable_supply( check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow_mut(); let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; + let mint_authority = mint.base.mint_authority; let mint_burn_extension = mint.get_extension_mut::()?; - let authority = Option::::from(mint_burn_extension.authority) - .ok_or(TokenError::NoAuthorityExists)?; + let authority = OptionalNonZeroPubkey::try_from(mint_authority)?; + let authority = Option::::from(authority).ok_or(TokenError::NoAuthorityExists)?; Processor::validate_owner( program_id, @@ -158,6 +160,7 @@ fn process_confidential_mint( check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow_mut(); let mut mint = PodStateWithExtensionsMut::::unpack(mint_data)?; + let mint_authority = mint.base.mint_authority; let auditor_elgamal_pubkey = mint .get_extension::()? @@ -178,8 +181,8 @@ fn process_confidential_mint( let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); - let authority = Option::::from(mint_burn_extension.authority) - .ok_or(TokenError::NoAuthorityExists)?; + let authority = OptionalNonZeroPubkey::try_from(mint_authority)?; + let authority = Option::::from(authority).ok_or(TokenError::NoAuthorityExists)?; Processor::validate_owner( program_id, @@ -314,19 +317,19 @@ fn process_confidential_burn( return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } - let source_transfer_amount_lo = &proof_context + let burn_amount_lo = &proof_context .burn_amount_ciphertext_lo .try_extract_ciphertext(0) .map_err(|_| ProgramError::InvalidAccountData)?; - let source_transfer_amount_hi = &proof_context + 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, - source_transfer_amount_lo, - source_transfer_amount_hi, + burn_amount_lo, + burn_amount_hi, ) .ok_or(TokenError::CiphertextArithmeticFailed)?; diff --git a/token/program-2022/src/processor.rs b/token/program-2022/src/processor.rs index 37c0295d4b7..697cf042f8a 100644 --- a/token/program-2022/src/processor.rs +++ b/token/program-2022/src/processor.rs @@ -905,20 +905,6 @@ impl Processor { )?; extension.authority = new_authority.try_into()?; } - AuthorityType::MintConfidentialTokens => { - let extension = mint.get_extension_mut::()?; - let maybe_authority: Option = extension.authority.into(); - let authority = maybe_authority.ok_or(TokenError::AuthorityTypeNotSupported)?; - - Self::validate_owner( - program_id, - &authority, - authority_info, - authority_info_data_len, - account_info_iter.as_slice(), - )?; - extension.authority = new_authority.try_into()?; - } _ => { return Err(TokenError::AuthorityTypeNotSupported.into()); } From fbff1907eb6b42d5c693c13aa8858b98705e1fae Mon Sep 17 00:00:00 2001 From: Dario Bargel Date: Tue, 29 Oct 2024 08:25:41 -0300 Subject: [PATCH 34/34] clippy; review fixes --- .../proof-extraction/src/encryption.rs | 17 ----------------- .../proof-extraction/src/instruction.rs | 4 +++- .../confidential_mint_burn/processor.rs | 16 +++++----------- token/program-2022/src/instruction.rs | 4 ---- 4 files changed, 8 insertions(+), 33 deletions(-) diff --git a/token/confidential-transfer/proof-extraction/src/encryption.rs b/token/confidential-transfer/proof-extraction/src/encryption.rs index a8badaa22fa..383108d41b1 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, @@ -52,10 +43,6 @@ impl PodFeeCiphertext { pub struct PodBurnAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles); impl PodBurnAmountCiphertext { - pub fn extract_commitment(&self) -> PodPedersenCommitment { - self.0.extract_commitment() - } - pub fn try_extract_ciphertext( &self, index: usize, @@ -71,10 +58,6 @@ impl PodBurnAmountCiphertext { pub struct PodMintAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles); impl PodMintAmountCiphertext { - pub fn extract_commitment(&self) -> PodPedersenCommitment { - self.0.extract_commitment() - } - pub fn try_extract_ciphertext( &self, index: usize, diff --git a/token/confidential-transfer/proof-extraction/src/instruction.rs b/token/confidential-transfer/proof-extraction/src/instruction.rs index d59a5c6d74b..be41a4bb6a9 100644 --- a/token/confidential-transfer/proof-extraction/src/instruction.rs +++ b/token/confidential-transfer/proof-extraction/src/instruction.rs @@ -184,7 +184,9 @@ where ) } }; - *expected_instruction_offset += 1; + *expected_instruction_offset = expected_instruction_offset + .checked_add(1) + .ok_or(ProgramError::InvalidInstructionData)?; Ok(proof_instruction_offset) } ProofLocation::ContextStateAccount(context_state_account) => { diff --git a/token/program-2022/src/extension/confidential_mint_burn/processor.rs b/token/program-2022/src/extension/confidential_mint_burn/processor.rs index 15103d4166b..f0697337783 100644 --- a/token/program-2022/src/extension/confidential_mint_burn/processor.rs +++ b/token/program-2022/src/extension/confidential_mint_burn/processor.rs @@ -34,7 +34,6 @@ use { CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData, }, }, - spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_confidential_transfer_proof_extraction::instruction::verify_and_extract_context, }; @@ -95,9 +94,7 @@ fn process_rotate_supply_elgamal_pubkey( let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); - - let authority = OptionalNonZeroPubkey::try_from(mint_authority)?; - let authority = Option::::from(authority).ok_or(TokenError::NoAuthorityExists)?; + let authority = mint_authority.ok_or(TokenError::NoAuthorityExists)?; Processor::validate_owner( program_id, @@ -121,8 +118,6 @@ fn process_update_decryptable_supply( ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let mint_info = next_account_info(account_info_iter)?; - let authority_info = next_account_info(account_info_iter)?; - let authority_info_data_len = authority_info.data_len(); check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow_mut(); @@ -130,8 +125,9 @@ fn process_update_decryptable_supply( let mint_authority = mint.base.mint_authority; let mint_burn_extension = mint.get_extension_mut::()?; - let authority = OptionalNonZeroPubkey::try_from(mint_authority)?; - let authority = Option::::from(authority).ok_or(TokenError::NoAuthorityExists)?; + 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, @@ -180,9 +176,7 @@ fn process_confidential_mint( let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); - - let authority = OptionalNonZeroPubkey::try_from(mint_authority)?; - let authority = Option::::from(authority).ok_or(TokenError::NoAuthorityExists)?; + let authority = mint_authority.ok_or(TokenError::NoAuthorityExists)?; Processor::validate_owner( program_id, diff --git a/token/program-2022/src/instruction.rs b/token/program-2022/src/instruction.rs index 490fafa12ff..3d766aa0230 100644 --- a/token/program-2022/src/instruction.rs +++ b/token/program-2022/src/instruction.rs @@ -1122,8 +1122,6 @@ pub enum AuthorityType { GroupPointer, /// Authority to set the group member address GroupMemberPointer, - /// Authority to mint new confidential tokens - MintConfidentialTokens, } impl AuthorityType { @@ -1144,7 +1142,6 @@ impl AuthorityType { AuthorityType::MetadataPointer => 12, AuthorityType::GroupPointer => 13, AuthorityType::GroupMemberPointer => 14, - AuthorityType::MintConfidentialTokens => 15, } } @@ -1165,7 +1162,6 @@ impl AuthorityType { 12 => Ok(AuthorityType::MetadataPointer), 13 => Ok(AuthorityType::GroupPointer), 14 => Ok(AuthorityType::GroupMemberPointer), - 15 => Ok(AuthorityType::MintConfidentialTokens), _ => Err(TokenError::InvalidInstruction.into()), } }