diff --git a/Cargo.lock b/Cargo.lock index 70f6e4c1909..5ddc2e6cf67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7291,6 +7291,7 @@ dependencies = [ "base64 0.21.5", "clap 2.34.0", "console", + "futures 0.3.28", "serde", "serde_derive", "serde_json", diff --git a/token/cli/Cargo.toml b/token/cli/Cargo.toml index 97454be8fe5..3a0a5e9d294 100644 --- a/token/cli/Cargo.toml +++ b/token/cli/Cargo.toml @@ -15,6 +15,7 @@ walkdir = "2" base64 = "0.21.5" clap = "2.33.3" console = "0.15.7" +futures = "0.3" serde = "1.0.190" serde_derive = "1.0.103" serde_json = "1.0.107" diff --git a/token/cli/src/main.rs b/token/cli/src/main.rs index 96944aa2b92..77eca6a3aa0 100644 --- a/token/cli/src/main.rs +++ b/token/cli/src/main.rs @@ -3,6 +3,7 @@ use clap::{ crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand, }; +use futures::try_join; use serde::Serialize; use solana_account_decoder::{ parse_token::{get_token_account_mint, parse_token, TokenAccountType, UiAccountState}, @@ -39,7 +40,10 @@ use spl_associated_token_account::get_associated_token_address_with_program_id; use spl_token_2022::{ extension::{ confidential_transfer::{ - account_info::{ApplyPendingBalanceAccountInfo, WithdrawAccountInfo}, + account_info::{ + ApplyPendingBalanceAccountInfo, TransferAccountInfo, WithdrawAccountInfo, + }, + instruction::TransferSplitContextStateAccounts, ConfidentialTransferAccount, ConfidentialTransferMint, }, confidential_transfer_fee::ConfidentialTransferFeeConfig, @@ -1687,21 +1691,105 @@ async fn command_transfer( pubkey.try_into().expect("Invalid auditor ElGamal pubkey"); auditor_elgamal_pubkey }); - token - .confidential_transfer_transfer( - &sender, - &recipient_token_account, - &sender_owner, - 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 transfer_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(&sender).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, + ) = transfer_account_info + .generate_split_transfer_proof_data( transfer_balance, - None, &args.sender_elgamal_keypair, &args.sender_aes_key, &recipient_elgamal_pubkey, auditor_elgamal_pubkey.as_ref(), + ) + .unwrap(); + + // setup proofs + let _ = try_join!( + token.create_range_proof_context_state_for_transfer( + transfer_context_state_accounts, + &range_proof_data, + &range_proof_context_state_account, + ), + token.create_equality_proof_context_state_for_transfer( + transfer_context_state_accounts, + &equality_proof_data, + &equality_proof_context_state_account, + ), + token.create_ciphertext_validity_proof_context_state_for_transfer( + transfer_context_state_accounts, + &ciphertext_validity_proof_data, + &ciphertext_validity_proof_context_state_account, + ) + )?; + + // do the transfer + let transfer_result = token + .confidential_transfer_transfer_with_split_proofs( + &sender, + &recipient_token_account, + &sender_owner, + transfer_context_state_accounts, + transfer_balance, + Some(transfer_account_info), + &args.sender_aes_key, + &source_decrypt_handles, &bulk_signers, ) - .await? + .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, + &sender, + &context_state_authority_pubkey, + close_context_state_signers, + ), + token.confidential_transfer_close_context_state( + &ciphertext_validity_proof_pubkey, + &sender, + &context_state_authority_pubkey, + close_context_state_signers, + ), + token.confidential_transfer_close_context_state( + &range_proof_pubkey, + &sender, + &context_state_authority_pubkey, + close_context_state_signers, + ), + )?; + + transfer_result } (None, Some(_), Some(_)) => { panic!("Confidential transfer with fee is not yet supported."); @@ -6369,7 +6457,7 @@ mod tests { super::*, serial_test::serial, solana_sdk::{ - bpf_loader_upgradeable, + bpf_loader_upgradeable, feature_set, hash::Hash, program_pack::Pack, signature::{write_keypair_file, Keypair, Signer}, @@ -6414,6 +6502,9 @@ mod tests { upgrade_authority: Pubkey::new_unique(), }, ]); + // TODO Remove this once the Range Proof cost goes under 200k compute units + test_validator_genesis + .deactivate_features(&[feature_set::native_programs_consume_cu::id()]); test_validator_genesis.start_async().await } @@ -9067,22 +9158,21 @@ mod tests { .await .unwrap(); // configure destination account for confidential transfers first - // NOTE: the test fails due to transaction size limit - // let transfer_amount = 100.0; - // process_test_command( - // &config, - // &payer, - // &[ - // "spl-token", - // CommandName::Transfer.into(), - // &token_pubkey.to_string(), - // &transfer_amount.to_string(), - // &destination_account.to_string(), - // "--confidential", - // ], - // ) - // .await - // .unwrap(); + let transfer_amount = 100.0; + process_test_command( + &config, + &payer, + &[ + "spl-token", + CommandName::Transfer.into(), + &token_pubkey.to_string(), + &transfer_amount.to_string(), + &destination_account.to_string(), + "--confidential", + ], + ) + .await + .unwrap(); // withdraw confidential tokens // diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 14051cfb328..44d6df96198 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -2382,6 +2382,88 @@ where ) } + /// Create equality proof context state account for a confidential transfer. + #[allow(clippy::too_many_arguments)] + pub async fn create_equality_proof_context_state_for_transfer( + &self, + context_state_accounts: TransferSplitContextStateAccounts<'_>, + equality_proof_data: &CiphertextCommitmentEqualityProofData, + equality_proof_signer: &S, + ) -> TokenResult { + // create equality proof context state + let instruction_type = ProofInstruction::VerifyCiphertextCommitmentEquality; + let space = size_of::>(); + let rent = self + .client + .get_minimum_balance_for_rent_exemption(space) + .await + .map_err(TokenError::Client)?; + + let equality_proof_context_state_info = ContextStateInfo { + context_state_account: context_state_accounts.equality_proof, + context_state_authority: context_state_accounts.authority, + }; + + self.process_ixs( + &[ + system_instruction::create_account( + &self.payer.pubkey(), + context_state_accounts.equality_proof, + rent, + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof( + Some(equality_proof_context_state_info), + equality_proof_data, + ), + ], + &[equality_proof_signer], + ) + .await + } + + /// Create ciphertext validity proof context state account for a confidential transfer. + pub async fn create_ciphertext_validity_proof_context_state_for_transfer( + &self, + context_state_accounts: TransferSplitContextStateAccounts<'_>, + 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], + ) + .await + } + /// Create equality and ciphertext validity proof context state accounts for a confidential transfer. #[allow(clippy::too_many_arguments)] pub async fn create_equality_and_ciphertext_validity_proof_context_states_for_transfer< @@ -2503,17 +2585,41 @@ where } /// Create a range proof context state account for a confidential transfer. - pub async fn create_range_proof_context_state_for_transfer( + pub async fn create_range_proof_context_state_for_transfer( &self, context_state_accounts: TransferSplitContextStateAccounts<'_>, range_proof_data: &BatchedRangeProofU128Data, - signing_keypairs: &S, + range_proof_keypair: &S, ) -> TokenResult { - self.create_range_proof_context_state_with_optional_transfer( - context_state_accounts, - range_proof_data, - None, - signing_keypairs, + let instruction_type = ProofInstruction::VerifyBatchedRangeProofU128; + 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: context_state_accounts.range_proof, + context_state_authority: context_state_accounts.authority, + }; + self.process_ixs( + &[system_instruction::create_account( + &self.payer.pubkey(), + context_state_accounts.range_proof, + rent, + space as u64, + &zk_token_proof_program::id(), + )], + &[range_proof_keypair], + ) + .await?; + + // This instruction is right at the transaction size limit, but in the + // future it might be able to support the transfer too + self.process_ixs( + &[instruction_type + .encode_verify_proof(Some(range_proof_context_state_info), range_proof_data)], + &[] as &[&dyn Signer; 0], ) .await } @@ -2575,15 +2681,16 @@ where } /// Close a ZK Token proof program context state - pub async fn confidential_transfer_close_context_state( + pub async fn confidential_transfer_close_context_state( &self, context_state_account: &Pubkey, lamport_destination_account: &Pubkey, - context_state_authority: &S, + context_state_authority: &Pubkey, + signing_keypairs: &S, ) -> TokenResult { let context_state_info = ContextStateInfo { context_state_account, - context_state_authority: &context_state_authority.pubkey(), + context_state_authority, }; self.process_ixs( @@ -2591,7 +2698,7 @@ where context_state_info, lamport_destination_account, )], - &[context_state_authority], + signing_keypairs, ) .await } diff --git a/token/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index cc3606bc884..f7e16a58a7f 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -2475,7 +2475,7 @@ async fn confidential_transfer_transfer_with_split_proof_context() { .create_range_proof_context_state_for_transfer( transfer_context_state_accounts, &range_proof_data, - &[&range_proof_context_state_account], + &range_proof_context_state_account, ) .await .unwrap(); @@ -2501,7 +2501,8 @@ async fn confidential_transfer_transfer_with_split_proof_context() { .confidential_transfer_close_context_state( &equality_proof_context_state_account.pubkey(), &alice_meta.token_account, - &context_state_authority, + &context_state_authority.pubkey(), + &[&context_state_authority], ) .await .unwrap(); @@ -2510,7 +2511,8 @@ async fn confidential_transfer_transfer_with_split_proof_context() { .confidential_transfer_close_context_state( &ciphertext_validity_proof_context_state_account.pubkey(), &alice_meta.token_account, - &context_state_authority, + &context_state_authority.pubkey(), + &[&context_state_authority], ) .await .unwrap(); @@ -2519,7 +2521,8 @@ async fn confidential_transfer_transfer_with_split_proof_context() { .confidential_transfer_close_context_state( &range_proof_context_state_account.pubkey(), &alice_meta.token_account, - &context_state_authority, + &context_state_authority.pubkey(), + &[&context_state_authority], ) .await .unwrap(); @@ -2843,7 +2846,8 @@ async fn confidential_transfer_transfer_with_fee_and_split_proof_context() { .confidential_transfer_close_context_state( &equality_proof_context_state_account.pubkey(), &alice_meta.token_account, - &context_state_authority, + &context_state_authority.pubkey(), + &[&context_state_authority], ) .await .unwrap(); @@ -2852,7 +2856,8 @@ async fn confidential_transfer_transfer_with_fee_and_split_proof_context() { .confidential_transfer_close_context_state( &transfer_amount_ciphertext_validity_proof_context_state_account.pubkey(), &alice_meta.token_account, - &context_state_authority, + &context_state_authority.pubkey(), + &[&context_state_authority], ) .await .unwrap(); @@ -2861,7 +2866,8 @@ async fn confidential_transfer_transfer_with_fee_and_split_proof_context() { .confidential_transfer_close_context_state( &fee_sigma_proof_context_state_account.pubkey(), &alice_meta.token_account, - &context_state_authority, + &context_state_authority.pubkey(), + &[&context_state_authority], ) .await .unwrap(); @@ -2870,7 +2876,8 @@ async fn confidential_transfer_transfer_with_fee_and_split_proof_context() { .confidential_transfer_close_context_state( &fee_ciphertext_validity_proof_context_state_account.pubkey(), &alice_meta.token_account, - &context_state_authority, + &context_state_authority.pubkey(), + &[&context_state_authority], ) .await .unwrap(); @@ -2879,7 +2886,8 @@ async fn confidential_transfer_transfer_with_fee_and_split_proof_context() { .confidential_transfer_close_context_state( &range_proof_context_state_account.pubkey(), &alice_meta.token_account, - &context_state_authority, + &context_state_authority.pubkey(), + &[&context_state_authority], ) .await .unwrap();