提取代币

如何从保密可用余额中提取代币

要将代币从保密可用余额提取到公开余额:

  1. 客户端生成 两个证明

    等式证明 (CiphertextCommitmentEqualityProofData):验证提取后剩余的可用余额密文是否与其对应的 Pedersen 承诺匹配,确保账户的新可用余额正确计算为 remaining_balance = current_balance - withdraw_amount

    范围证明 (BatchedRangeProofU64Data):验证提取后剩余的可用余额是否为非负数并在指定范围内。

  2. 对于每个证明:

    • 调用 ZK ElGamal 证明程序以验证证明数据。
    • 将证明特定的元数据存储在一个证明“上下文状态”账户中,以便在其他指令中使用。
  3. 调用 ConfidentialTransferInstruction::Withdraw 指令,并提供两个证明账户。

  4. 关闭证明账户以回收用于创建它们的 SOL。

下图显示了从保密可用余额提取代币到公开余额的步骤:

Withdraw Tokens

所需指令

要将代币从保密可用余额提取到公开余额,您必须:

  • 在客户端生成等式证明和范围证明
  • 调用 ZK ElGamal 证明程序以验证证明并初始化“上下文状态”账户
  • 调用 ConfidentialTransferInstruction::Withdraw 指令,并提供两个证明账户。
  • 关闭两个证明账户以回收租金。

spl_token_client crate提供以下方法:

  • confidential_transfer_create_context_state_account 方法,用于创建一个证明账户。
  • confidential_transfer_withdraw 方法,用于调用 Withdraw 指令。
  • confidential_transfer_close_context_state_account 方法,用于关闭一个证明账户。

示例代码

以下示例演示了如何将代币从保密可用余额提取到公开余额。

要运行此示例,请使用以下命令启动一个从主网克隆的带有 Token Extension Program 的本地 validator。在启动本地 validator 之前,您必须安装 Solana CLI。

Terminal
$
solana-test-validator --clone-upgradeable-program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb --url https://api.mainnet-beta.solana.com -r

在撰写本文时,Confidential Transfers 尚未在默认的本地 validator 上启用。您必须克隆主网的 Token Extension Program 才能运行示例代码。

use anyhow::{Context, Result};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
signature::{Keypair, Signer},
transaction::Transaction,
};
use spl_associated_token_account::{
get_associated_token_address_with_program_id, instruction::create_associated_token_account,
};
use spl_token_client::{
client::{ProgramRpcClient, ProgramRpcClientSendTransaction},
spl_token_2022::{
extension::{
confidential_transfer::{
account_info::WithdrawAccountInfo,
ConfidentialTransferAccount,
instruction::{configure_account, PubkeyValidityProofData},
},
BaseStateWithExtensions, ExtensionType,
},
id as token_2022_program_id,
instruction::reallocate,
solana_zk_sdk::encryption::{auth_encryption::*, elgamal::*},
},
token::{ExtensionInitializationParams, Token},
};
use spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation};
use spl_token_confidential_transfer_proof_generation::withdraw::WithdrawProofData;
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<()> {
// Create connection to local test validator
let rpc_client = Arc::new(RpcClient::new_with_commitment(
String::from("http://localhost:8899"),
CommitmentConfig::confirmed(),
));
// Load the default Solana CLI keypair to use as the fee payer
// This will be the wallet paying for the transaction fees
// Use Arc to prevent multiple clones of the keypair
let payer = Arc::new(load_keypair()?);
println!("Using payer: {}", payer.pubkey());
// Generate a new keypair to use as the address of the token mint
let mint = Keypair::new();
println!("Mint keypair generated: {}", mint.pubkey());
// Set up program client for Token client
let program_client = ProgramRpcClient::new(rpc_client.clone(), ProgramRpcClientSendTransaction);
// Number of decimals for the mint
let decimals = 9;
// Create a token client for the Token-2022 program
// This provides high-level methods for token operations
let token = Token::new(
Arc::new(program_client),
&token_2022_program_id(), // Use the Token-2022 program (newer version with extensions)
&mint.pubkey(), // Address of the new token mint
Some(decimals), // Number of decimal places
payer.clone(), // Fee payer for transactions
);
// Create extension initialization parameters for the mint
// The ConfidentialTransferMint extension enables confidential (private) transfers of tokens
let extension_initialization_params =
vec![ExtensionInitializationParams::ConfidentialTransferMint {
authority: Some(payer.pubkey()), // Authority that can modify confidential transfer settings
auto_approve_new_accounts: true, // Automatically approve new confidential accounts
auditor_elgamal_pubkey: None, // Optional auditor ElGamal public key
}];
// Create and initialize the mint with the ConfidentialTransferMint extension
// This sends a transaction to create the new token mint
let transaction_signature = token
.create_mint(
&payer.pubkey(), // Mint authority - can mint new tokens
Some(&payer.pubkey()), // Freeze authority - can freeze token accounts
extension_initialization_params, // Add the ConfidentialTransferMint extension
&[&mint], // Mint keypair needed as signer
)
.await?;
// Print results for user verification
println!("Mint Address: {}", mint.pubkey());
println!(
"Mint Creation Transaction Signature: {}",
transaction_signature
);
// ===== Create and configure token account for confidential transfers =====
println!("\nCreate and configure token account for confidential transfers");
// Get the associated token account address for the owner
let token_account_pubkey = get_associated_token_address_with_program_id(
&payer.pubkey(), // Token account owner
&mint.pubkey(), // Mint
&token_2022_program_id(), // Token program ID
);
println!("Token Account Address: {}", token_account_pubkey);
// Step 1: Create the associated token account
let create_associated_token_account_instruction = create_associated_token_account(
&payer.pubkey(), // Funding account
&payer.pubkey(), // Token account owner
&mint.pubkey(), // Mint
&token_2022_program_id(), // Token program ID
);
// Step 2: Reallocate the token account to include space for the ConfidentialTransferAccount extension
let reallocate_instruction = reallocate(
&token_2022_program_id(), // Token program ID
&token_account_pubkey, // Token account
&payer.pubkey(), // Payer
&payer.pubkey(), // Token account owner
&[&payer.pubkey()], // Signers
&[ExtensionType::ConfidentialTransferAccount], // Extension to reallocate space for
)?;
// Step 3: Generate the ElGamal keypair and AES key for token account
let elgamal_keypair = ElGamalKeypair::new_from_signer(&payer, &token_account_pubkey.to_bytes())
.expect("Failed to create ElGamal keypair");
let aes_key = AeKey::new_from_signer(&payer, &token_account_pubkey.to_bytes())
.expect("Failed to create AES key");
// The maximum number of Deposit and Transfer instructions that can
// credit pending_balance before the ApplyPendingBalance instruction must be executed
let maximum_pending_balance_credit_counter = 65536;
// Initial token balance is 0
let decryptable_balance = aes_key.encrypt(0);
// Generate the proof data client-side
let proof_data = PubkeyValidityProofData::new(&elgamal_keypair)
.map_err(|_| anyhow::anyhow!("Failed to generate proof data"))?;
// Indicate that proof is included in the same transaction
let proof_location =
ProofLocation::InstructionOffset(1.try_into()?, ProofData::InstructionData(&proof_data));
// Step 4: Create instructions to configure the account for confidential transfers
let configure_account_instructions = configure_account(
&token_2022_program_id(), // Program ID
&token_account_pubkey, // Token account
&mint.pubkey(), // Mint
&decryptable_balance.into(), // Initial balance
maximum_pending_balance_credit_counter, // Maximum pending balance credit counter
&payer.pubkey(), // Token Account Owner
&[], // Additional signers
proof_location, // Proof location
)?;
// Combine all instructions
let mut instructions = vec![
create_associated_token_account_instruction,
reallocate_instruction,
];
instructions.extend(configure_account_instructions);
// Create and send the transaction
let recent_blockhash = rpc_client.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[&*payer],
recent_blockhash,
);
let setup_signature = rpc_client
.send_and_confirm_transaction(&transaction)
.await?;
println!(
"Token Account Setup Transaction Signature: {}",
setup_signature
);
// Mint some tokens to the newly created token account
// This gives the account some tokens to work with
let mint_signature = token
.mint_to(
&token_account_pubkey, // Destination account
&payer.pubkey(), // Mint authority
100 * 10u64.pow(decimals as u32), // Amount (100 tokens with decimal precision)
&[&payer], // Signers
)
.await?;
println!("Token Minting Transaction Signature: {}", mint_signature);
// Deposit the tokens to confidential state
// This converts regular tokens to confidential tokens
println!("Deposit tokens to confidential state pending balance");
let deposit_signature = token
.confidential_transfer_deposit(
&token_account_pubkey, // The token account
&payer.pubkey(), // Authority (owner) of the account
100 * 10u64.pow(decimals as u32), // Amount to deposit (100 tokens)
decimals, // Decimals of the token
&[&payer], // Signers (owner must sign)
)
.await?;
println!(
"Confidential Transfer Deposit Signature: {}",
deposit_signature
);
// Apply the pending balance to make funds available
println!("Apply pending balance to available balance");
let apply_signature = token
.confidential_transfer_apply_pending_balance(
&token_account_pubkey, // The token account
&payer.pubkey(), // Authority (owner) of the account
None, // Optional new decryptable available balance
elgamal_keypair.secret(), // ElGamal secret key encryption
&aes_key, // AES key for encryption
&[&payer], // Signers (owner must sign)
)
.await?;
println!("Apply Pending Balance Signature: {}", apply_signature);
// ===== Withdraw half of the tokens from confidential state =====
println!("\nWithdraw tokens from confidential available balance");
// Calculate the withdraw amount (half of the deposited amount)
let withdraw_amount = 50 * 10u64.pow(decimals as u32); // Half of the 100 tokens deposited
// Get the token account data to access the confidential transfer extension
let token_account = token.get_account_info(&token_account_pubkey).await?;
// Unpack the ConfidentialTransferAccount extension portion of the token account data
let extension_data = token_account.get_extension::<ConfidentialTransferAccount>()?;
// Confidential Transfer extension information needed to construct a `Withdraw` instruction
let withdraw_account_info =
WithdrawAccountInfo::new(
extension_data,
);
// Create keypairs for the proof accounts
let equality_proof_context_state_keypair = Keypair::new();
let equality_proof_context_state_pubkey = equality_proof_context_state_keypair.pubkey();
let range_proof_context_state_keypair = Keypair::new();
let range_proof_context_state_pubkey = range_proof_context_state_keypair.pubkey();
// Create a withdraw proof data
let WithdrawProofData {
equality_proof_data,
range_proof_data,
} = withdraw_account_info.generate_proof_data(
withdraw_amount, // Amount to withdraw from confidential state
&elgamal_keypair, // ElGamal keypair for encryption
&aes_key, // AES key for encryption
)?;
// Generate the equality proof account
println!("Create equality proof context state account");
let equality_proof_signature = token
.confidential_transfer_create_context_state_account(
&equality_proof_context_state_pubkey, // Public key of the new equality proof context state account
&payer.pubkey(), // Authority that can close the context state account
&equality_proof_data, // Proof data for the equality proof verification
false, // False: combine account creation and proof verification in one transaction
&[&equality_proof_context_state_keypair], // Signer for the new account
)
.await?;
println!(
"Equality Proof Context State Account Signature: {}",
equality_proof_signature
);
// Generate the range proof account
println!("Create range proof context state account");
let range_proof_signature = token
.confidential_transfer_create_context_state_account(
&range_proof_context_state_pubkey, // Public key of the new range proof context state account
&payer.pubkey(), // Authority that can close the context state account
&range_proof_data, // Proof data for the range proof verification
true, // True: split account creation and proof verification into separate transactions (for large proofs)
&[&range_proof_context_state_keypair], // Signer for the new account
)
.await?;
println!(
"Range Proof Context State Account Signature: {}",
range_proof_signature
);
// Perform the withdrawal
println!("Execute withdrawal transaction");
let withdraw_signature = token
.confidential_transfer_withdraw(
&token_account_pubkey, // Token account to withdraw from
&payer.pubkey(), // Owner of the token account
Some(&spl_token_client::token::ProofAccount::ContextAccount(
equality_proof_context_state_pubkey, // Reference to the equality proof account
)),
Some(&spl_token_client::token::ProofAccount::ContextAccount(
range_proof_context_state_pubkey, // Reference to the range proof account
)),
withdraw_amount, // Amount to withdraw from confidential state
decimals, // Decimal precision of the token
Some(withdraw_account_info), // Data from confidential transfer extension for proof verification
&elgamal_keypair, // ElGamal keypair for encryption
&aes_key, // AES key for encryption
&[&payer], // Owner must sign the transaction
)
.await?;
println!("Withdraw Transaction Signature: {}", withdraw_signature);
// Close the context state accounts to recover rent
println!("Close equality proof context state account");
let close_equality_signature = token
.confidential_transfer_close_context_state_account(
&equality_proof_context_state_pubkey, // Equality proof context state account to close
&token_account_pubkey, // Account that will receive the lamports
&payer.pubkey(), // Authority allowed to close the account
&[&payer], // Authority must sign
)
.await?;
println!(
"Close Equality Proof Account Signature: {}",
close_equality_signature
);
println!("Close range proof context state account");
let close_range_signature = token
.confidential_transfer_close_context_state_account(
&range_proof_context_state_pubkey, // Range proof context state account to close
&token_account_pubkey, // Account that will receive the lamports
&payer.pubkey(), // Authority allowed to close the account
&[&payer], // Authority must sign
)
.await?;
println!(
"Close Range Proof Account Signature: {}",
close_range_signature
);
Ok(())
}
// Load the keypair from the default Solana CLI keypair path (~/.config/solana/id.json)
// This enables using the same wallet as the Solana CLI tools
fn load_keypair() -> Result<Keypair> {
// Get the default keypair path
let keypair_path = dirs::home_dir()
.context("Could not find home directory")?
.join(".config/solana/id.json");
// Read the keypair file directly into bytes using serde_json
// The keypair file is a JSON array of bytes
let file = std::fs::File::open(&keypair_path)?;
let keypair_bytes: Vec<u8> = serde_json::from_reader(file)?;
// Create keypair from the loaded bytes
// This converts the byte array into a keypair
let keypair = Keypair::from_bytes(&keypair_bytes)?;
Ok(keypair)
}
Click to execute the code.

Is this page helpful?

Table of Contents

Edit Page