ZK ElGamalプログラムは、セキュリティ監査を受けているため、現在メインネットとdevnetで一時的に無効化されています。これは、confidential transfers拡張機能が現在利用できないことを意味します。概念は依然として有効ですが、コード例は実行されません。
Confidential Transfer拡張機能を持つトークンアカウントの作成方法
Confidential Transfer拡張機能は、トークンアカウントに追加の状態を加えることで、プライベートなトークン転送を可能にします。このセクションでは、この拡張機能を有効にしたトークンアカウントの作成方法を説明します。
以下の図は、Confidential Transfer拡張機能を持つトークンアカウントを作成する際の手順を示しています:
Confidential Transferトークンアカウントの状態
この拡張機能は、トークンアカウントにConfidentialTransferAccount状態を追加します:
#[repr(C)]#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]pub struct ConfidentialTransferAccount {/// `true` if this account has been approved for use. All confidential/// transfer operations for the account will fail until approval is/// granted.pub approved: PodBool,/// The public key associated with ElGamal encryptionpub elgamal_pubkey: PodElGamalPubkey,/// The low 16 bits of the pending balance (encrypted by `elgamal_pubkey`)pub pending_balance_lo: EncryptedBalance,/// The high 48 bits of the pending balance (encrypted by `elgamal_pubkey`)pub pending_balance_hi: EncryptedBalance,/// The available balance (encrypted by `encryption_pubkey`)pub available_balance: EncryptedBalance,/// The decryptable available balancepub decryptable_available_balance: DecryptableBalance,/// If `false`, the extended account rejects any incoming confidential/// transferspub allow_confidential_credits: PodBool,/// If `false`, the base account rejects any incoming transferspub allow_non_confidential_credits: PodBool,/// The total number of `Deposit` and `Transfer` instructions that have/// credited `pending_balance`pub pending_balance_credit_counter: PodU64,/// The maximum number of `Deposit` and `Transfer` instructions that can/// credit `pending_balance` before the `ApplyPendingBalance`/// instruction is executedpub maximum_pending_balance_credit_counter: PodU64,/// The `expected_pending_balance_credit_counter` value that was included in/// the last `ApplyPendingBalance` instructionpub expected_pending_balance_credit_counter: PodU64,/// The actual `pending_balance_credit_counter` when the last/// `ApplyPendingBalance` instruction was executedpub actual_pending_balance_credit_counter: PodU64,}
*rsConfidentialTransferAccount*には、confidential
transfersを管理するためのいくつかのフィールドが含まれています:
-
approved: confidential transfersに対するアカウントの承認ステータス。mint accountの
auto_approve_new_accounts設定がtrueに設定されている場合、すべてのトークンアカウントは自動的にconfidential transfersが承認されます。 -
elgamal_pubkey: 残高と転送金額を暗号化するために使用されるElGamal公開鍵。
-
pending_balance_lo: 保留中の残高の下位16ビットの暗号化された値。残高は効率的な復号化のために上位と下位に分割されます。
-
pending_balance_hi: 保留中の残高の上位48ビットの暗号化された値。残高は効率的な復号化のために上位と下位に分割されます。
-
available_balance: 転送に利用可能な暗号化された残高。
-
decryptable_available_balance: アカウント所有者による効率的な復号化のために、Advanced Encryption Standard(AES)鍵で暗号化された利用可能な残高。
-
allow_confidential_credits: trueの場合、機密転送の受信を許可します。
-
allow_non_confidential_credits: trueの場合、非機密転送の受信を許可します。
-
pending_balance_credit_counter: 入金およびtransfer instructionsからの保留中の残高クレジットをカウントします。
-
maximum_pending_balance_credit_counter:
ApplyPendingBalanceinstructionを実行して保留中の残高を利用可能残高に変換する前の、保留中のクレジットのカウント制限。 -
expected_pending_balance_credit_counter: 最後に
ApplyPendingBalanceinstructionが処理された際に、クライアントがinstruction dataを通じて提供したpending_balance_credit_counterの値。 -
actual_pending_balance_credit_counter: 最後に
ApplyPendingBalanceinstructionが処理された時点でのtoken accountのpending_balance_credit_counterの値。
保留中の残高と利用可能残高
機密残高は、DoS攻撃を防ぐために保留中の残高と利用可能残高に分離されています。この分離がない場合、攻撃者はtoken accountに繰り返しトークンを送信し、token accountの所有者がトークンを転送する能力をブロックできます。トランザクションが送信されてから処理されるまでの間に暗号化された残高が変更されるため、token accountの所有者はトークンを転送できず、トランザクションが失敗します。
すべての入金および転送額は、最初に保留中の残高に追加されます。token
accountの所有者は、ApplyPendingBalance
instructionを使用して、保留中の残高を利用可能残高に変換する必要があります。受信した転送または入金は、token
accountの利用可能残高に影響しません。
保留中残高クレジットカウンター
保留中残高を利用可能残高に変換するために_rsApplyPendingBalance_instructionを呼び出す際:
-
クライアントは現在の保留中残高と利用可能残高を検索し、合計を暗号化し、トークンアカウント所有者のAES鍵を使用して暗号化された
decryptable_available_balanceを提供します。 -
期待されるカウンターと実際の保留中クレジットカウンターは、_rs
ApplyPendingBalance_instructionが作成されてから処理されるまでのカウンター値の変更を追跡します:expected_pending_balance_credit_counter:クライアントが_rsApplyPendingBalance_instructionを作成した時点のpending_balance_credit_counter値actual_pending_balance_credit_counter:_rsApplyPendingBalance_instructionが処理された時点のトークンアカウント上のpending_balance_credit_counter値
期待されるカウンターと実際のカウンターが一致することは、decryptable_available_balanceがavailable_balanceと一致することを示します。
トークンアカウントの状態を取得してdecryptable_available_balanceを読み取る際、期待されるカウンター値と実際のカウンター値が異なる場合、クライアントはカウンターの差分に一致する最近の入金/転送instructionsを検索して正しい残高を計算する必要があります。
残高調整プロセス
期待される保留中残高カウンターと実際の保留中残高カウンターが異なる場合、decryptable_available_balanceを調整するために以下の手順に従ってください:
- トークンアカウントの
decryptable_available_balanceから開始します - カウンターの差分(実際 - 期待)までの入金および転送instructionsを含む最新のトランザクションを取得します:
- 入金instructionsからの公開金額を追加
- 転送instructionsからの宛先暗号文金額を復号化して追加
必要なinstructions
Confidential Transfer拡張機能を持つトークンアカウントの作成には、3つのinstructionsが必要です:
-
トークンアカウントの作成:Associated Token Programの_rs
AssociatedTokenAccountInstruction:Create_instructionを呼び出してトークンアカウントを作成します。 -
アカウントスペースの再割り当て: Token Extension Programの
TokenInstruction::Reallocateinstructionを呼び出して、ConfidentialTransferAccount状態のスペースを追加します。 -
機密転送の設定: Token Extension ProgramのConfidentialTransferInstruction::ConfigureAccount instructionを呼び出して、
ConfidentialTransferAccount状態を初期化します。
トークンアカウントの所有者のみが機密転送用のトークンアカウントを設定できます。
ConfigureAccount
instructionは、トークンアカウント所有者のみが生成できる暗号化キーと証明データのクライアント側での生成が必要です。
PubkeyValidityProofData
は、ElGamalキーが有効であることを検証する証明を作成します。実装の詳細については、以下を参照してください:
コード例
以下のコードは、機密転送拡張機能を持つAssociated Token Accountを作成する方法を示しています。
この例を実行するには、以下のコマンドを使用してメインネットからクローンしたToken Extension Programでローカルvalidatorを起動します。ローカルvalidatorを起動するにはSolana CLIがインストールされている必要があります。
$solana-test-validator --clone-upgradeable-program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb --url https://api.mainnet.solana.com -r
執筆時点では、機密転送はデフォルトのローカル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::instruction::{configure_account, PubkeyValidityProofData},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 std::sync::Arc;#[tokio::main]async fn main() -> Result<()> {// Create connection to local test validatorlet 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 keypairlet payer = Arc::new(load_keypair()?);println!("Using payer: {}", payer.pubkey());// Generate a new keypair to use as the address of the token mintlet mint = Keypair::new();println!("Mint keypair generated: {}", mint.pubkey());// Set up program client for Token clientlet program_client = ProgramRpcClient::new(rpc_client.clone(), ProgramRpcClientSendTransaction);// Number of decimals for the mintlet decimals = 9;// Create a token client for the Token-2022 program// This provides high-level methods for token operationslet 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 mintSome(decimals), // Number of decimal placespayer.clone(), // Fee payer for transactions (cloning Arc, not keypair));// Create extension initialization parameters for the mint// The ConfidentialTransferMint extension enables confidential (private) transfers of tokenslet extension_initialization_params =vec![ExtensionInitializationParams::ConfidentialTransferMint {authority: Some(payer.pubkey()), // Authority that can modify confidential transfer settingsauto_approve_new_accounts: true, // Automatically approve new confidential accountsauditor_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 mintlet transaction_signature = token.create_mint(&payer.pubkey(), // Mint authority - can mint new tokensSome(&payer.pubkey()), // Freeze authority - can freeze token accountsextension_initialization_params, // Add the ConfidentialTransferMint extension&[&mint], // Mint keypair needed as signer).await?;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 ownerlet 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 accountlet 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 extensionlet 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 accountlet 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 is executedlet maximum_pending_balance_credit_counter = 65536;// Initial token balance is 0let decryptable_balance = aes_key.encrypt(0);// Generate the proof data client-sidelet proof_data = PubkeyValidityProofData::new(&elgamal_keypair).map_err(|_| anyhow::anyhow!("Failed to generate proof data"))?;// Indicate that proof is included in the same transactionlet proof_location =ProofLocation::InstructionOffset(1.try_into()?, ProofData::InstructionData(&proof_data));// Step 4: Create instructions to configure the account for confidential transferslet configure_account_instructions = configure_account(&token_2022_program_id(), // Program ID&token_account_pubkey, // Token account&mint.pubkey(), // Mint&decryptable_balance.into(), // Initial balancemaximum_pending_balance_credit_counter, // Maximum pending balance credit counter&payer.pubkey(), // Token Account Owner&[], // Additional signersproof_location, // Proof location)?;// Combine all instructionslet mut instructions = vec![create_associated_token_account_instruction,reallocate_instruction,];instructions.extend(configure_account_instructions);// Create and send the transactionlet recent_blockhash = rpc_client.get_latest_blockhash().await?;let transaction = Transaction::new_signed_with_payer(&instructions,Some(&payer.pubkey()),&[&payer],recent_blockhash,);let transaction_signature = rpc_client.send_and_confirm_transaction(&transaction).await?;println!("Create Token Account Transaction Signature: {}",transaction_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 toolsfn load_keypair() -> Result<Keypair> {// Get the default keypair pathlet 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 byteslet 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 keypairlet keypair = Keypair::from_bytes(&keypair_bytes)?;Ok(keypair)}
Is this page helpful?