Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

token-cli: Performing a confidential transfer sequentially #5670

Merged
merged 1 commit into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
142 changes: 116 additions & 26 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,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::<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 _ = 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.");
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
//
Expand Down
129 changes: 118 additions & 11 deletions token/client/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<S: Signer>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
equality_proof_data: &CiphertextCommitmentEqualityProofData,
equality_proof_signer: &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,
),
],
&[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<S: Signer>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData,
ciphertext_validity_proof_signer: &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,
),
],
&[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<
Expand Down Expand Up @@ -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<S: Signers>(
pub async fn create_range_proof_context_state_for_transfer<S: Signer>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
range_proof_data: &BatchedRangeProofU128Data,
signing_keypairs: &S,
range_proof_keypair: &S,
) -> TokenResult<T::Output> {
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::<ProofContextState<BatchedRangeProofContext>>();
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
}
Expand Down Expand Up @@ -2575,23 +2681,24 @@ where
}

/// 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
Loading
Loading