Skip to content

Commit

Permalink
token-cli: Performing a confidential transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
joncinque committed Oct 26, 2023
1 parent 35f9ba0 commit 618c7cd
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 44 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions token/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
148 changes: 120 additions & 28 deletions token/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1687,21 +1691,109 @@ 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 context_state_authority_pubkey = context_state_authority.pubkey();
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::<ConfidentialTransferAccount>()
.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 range_proof_signers = [&range_proof_context_state_account];
let equality_proof_signers = [&equality_proof_context_state_account];
let ciphertext_validity_proof_signers =
[&ciphertext_validity_proof_context_state_account];
let _ = try_join!(
token.create_range_proof_context_state_for_transfer(
transfer_context_state_accounts,
&range_proof_data,
&range_proof_signers,
),
token.create_equality_proof_context_state_for_transfer(
transfer_context_state_accounts,
&equality_proof_data,
&equality_proof_signers,
),
token.create_ciphertext_validity_proof_context_state_for_transfer(
transfer_context_state_accounts,
&ciphertext_validity_proof_data,
&ciphertext_validity_proof_signers,
)
)?;

// 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 close_context_state_signers = [context_state_authority.as_ref()];
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.");
Expand Down Expand Up @@ -5188,7 +5280,6 @@ fn app<'a, 'b>(
.validator(is_valid_pubkey)
.value_name("TOKEN_ACCOUNT_ADDRESS")
.takes_value(true)
.conflicts_with("token")
.help("The address of the token account to configure confidential transfers for \
[default: owner's associated token account]")
)
Expand Down Expand Up @@ -5227,7 +5318,6 @@ fn app<'a, 'b>(
.validator(is_valid_pubkey)
.value_name("TOKEN_ACCOUNT_ADDRESS")
.takes_value(true)
.conflicts_with("token")
.help("The address of the token account to configure confidential transfers for \
[default: owner's associated token account]")
)
Expand Down Expand Up @@ -6369,7 +6459,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},
Expand Down Expand Up @@ -6414,6 +6504,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
}

Expand Down Expand Up @@ -9067,22 +9160,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
//
Expand Down
118 changes: 102 additions & 16 deletions token/client/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2382,6 +2382,89 @@ 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<S: Signers>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
equality_proof_data: &CiphertextCommitmentEqualityProofData,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
// create equality proof context state
let instruction_type = ProofInstruction::VerifyCiphertextCommitmentEquality;
let space = size_of::<ProofContextState<CiphertextCommitmentEqualityProofContext>>();
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,
),
],
signing_keypairs,
)
.await
}

/// Create ciphertext validity proof context state account for a confidential transfer.
#[allow(clippy::too_many_arguments)]
pub async fn create_ciphertext_validity_proof_context_state_for_transfer<S: Signers>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
// create ciphertext validity proof context state
let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity;
let space =
size_of::<ProofContextState<BatchedGroupedCiphertext2HandlesValidityProofContext>>();
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,
),
],
signing_keypairs,
)
.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<
Expand Down Expand Up @@ -2555,43 +2638,46 @@ where
context_state_authority: context_state_accounts.authority,
};

let mut instructions = vec![
system_instruction::create_account(
&self.payer.pubkey(),
context_state_accounts.range_proof,
rent,
space as u64,
&zk_token_proof_program::id(),
),
instruction_type
.encode_verify_proof(Some(range_proof_context_state_info), range_proof_data),
];
let instructions = vec![system_instruction::create_account(
&self.payer.pubkey(),
context_state_accounts.range_proof,
rent,
space as u64,
&zk_token_proof_program::id(),
)];
self.process_ixs(&instructions, signing_keypairs).await?;

// This instruction is right at the transaction size limit, but in the
// future it might be able to support the transfer too
let mut instructions = vec![instruction_type
.encode_verify_proof(Some(range_proof_context_state_info), range_proof_data)];
if let Some(transfer_instruction) = transfer_instruction {
instructions.push(transfer_instruction.clone());
}

self.process_ixs(&instructions, signing_keypairs).await
self.process_ixs(&instructions, &[] as &[&dyn Signer; 0])
.await
}

/// Close a ZK Token proof program context state
pub async fn confidential_transfer_close_context_state<S: Signer>(
pub async fn confidential_transfer_close_context_state<S: Signers>(
&self,
context_state_account: &Pubkey,
lamport_destination_account: &Pubkey,
context_state_authority: &S,
context_state_authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let context_state_info = ContextStateInfo {
context_state_account,
context_state_authority: &context_state_authority.pubkey(),
context_state_authority,
};

self.process_ixs(
&[zk_token_proof_instruction::close_context_state(
context_state_info,
lamport_destination_account,
)],
&[context_state_authority],
signing_keypairs,
)
.await
}
Expand Down

0 comments on commit 618c7cd

Please sign in to comment.