Cách tạo token account với extension Confidential Transfer
Extension Confidential Transfer cho phép chuyển token riêng tư bằng cách thêm trạng thái bổ sung vào token account. Phần này giải thích cách tạo token account với extension này được bật.
Sơ đồ sau đây mô tả các bước liên quan đến việc tạo token account với extension Confidential Transfer:
Trạng Thái Token Account Confidential Transfer
Extension này thêm trạng thái ConfidentialTransferAccount vào token account:
#[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,}
ConfidentialTransferAccount chứa một số trường để quản lý các lần chuyển
bảo mật:
-
approved: Trạng thái phê duyệt của tài khoản cho các lần chuyển bảo mật. Nếu cấu hình
auto_approve_new_accountscủa mint account được đặt thànhtrue, tất cả token account sẽ tự động được phê duyệt cho các lần chuyển bảo mật. -
elgamal_pubkey: Khóa công khai ElGamal được sử dụng để mã hóa số dư và số tiền chuyển.
-
pending_balance_lo: 16 bit thấp được mã hóa của số dư đang chờ xử lý. Số dư được chia thành phần cao và phần thấp để giải mã hiệu quả.
-
pending_balance_hi: 48 bit cao được mã hóa của số dư đang chờ xử lý. Số dư được chia thành phần cao và phần thấp để giải mã hiệu quả.
-
available_balance: Số dư được mã hóa khả dụng để chuyển.
-
decryptable_available_balance: Số dư khả dụng được mã hóa bằng khóa Advanced Encryption Standard (AES) để giải mã hiệu quả bởi chủ sở hữu tài khoản.
-
allow_confidential_credits: Nếu đúng, cho phép các lần chuyển bảo mật đến.
-
allow_non_confidential_credits: Nếu đúng, cho phép các lần chuyển không bảo mật đến.
-
pending_balance_credit_counter: Đếm số lần ghi có số dư đang chờ xử lý đến từ các lệnh nạp tiền và chuyển tiền.
-
maximum_pending_balance_credit_counter: Giới hạn số lần ghi có đang chờ xử lý trước khi yêu cầu lệnh
ApplyPendingBalanceđể chuyển đổi số dư đang chờ xử lý sang số dư khả dụng. -
expected_pending_balance_credit_counter: Giá trị
pending_balance_credit_counterđược cung cấp bởi client thông qua instruction data lần cuối cùng lệnhApplyPendingBalanceđược xử lý. -
actual_pending_balance_credit_counter: Giá trị
pending_balance_credit_countertrên token account tại thời điểm lệnhApplyPendingBalancecuối cùng được xử lý.
Số dư chờ xử lý và số dư khả dụng
Số dư bảo mật được tách thành số dư chờ xử lý và số dư khả dụng để ngăn chặn các cuộc tấn công DoS. Nếu không có sự tách biệt này, kẻ tấn công có thể liên tục gửi token đến một token account, chặn khả năng chuyển token của chủ sở hữu token account. Chủ sở hữu token account sẽ không thể chuyển token vì số dư được mã hóa sẽ thay đổi trong khoảng thời gian từ khi giao dịch được gửi đến khi được xử lý, dẫn đến giao dịch thất bại.
Tất cả các khoản tiền gửi và số tiền chuyển ban đầu đều được cộng vào số dư chờ
xử lý. Chủ sở hữu token account phải sử dụng lệnh ApplyPendingBalance để
chuyển đổi số dư chờ xử lý thành số dư khả dụng. Các khoản chuyển tiền đến hoặc
tiền gửi không ảnh hưởng đến số dư khả dụng của token account.
Tách số dư chờ xử lý thành mức cao/thấp
Số dư chờ xử lý bảo mật được tách thành pending_balance_lo và
pending_balance_hi vì giải mã ElGamal đòi hỏi nhiều tính toán hơn đối với các
số lớn hơn. Bạn có thể tìm thấy cách triển khai phép tính số học trên bản mã
tại đây,
được sử dụng trong lệnh ApplyPendingBalance
tại đây.
Bộ đếm tín dụng số dư chờ xử lý
Khi gọi lệnh ApplyPendingBalance để chuyển đổi số dư chờ xử lý thành số dư
khả dụng:
-
Client tra cứu số dư chờ xử lý và số dư khả dụng hiện tại, mã hóa tổng số, và cung cấp một
decryptable_available_balanceđược mã hóa bằng khóa AES của chủ sở hữu token account. -
Bộ đếm tín dụng chờ xử lý dự kiến và thực tế theo dõi các thay đổi đối với giá trị bộ đếm trong khoảng thời gian từ khi lệnh
ApplyPendingBalanceđược tạo đến khi được xử lý:expected_pending_balance_credit_counter: Giá trịpending_balance_credit_counterkhi client tạo lệnhApplyPendingBalanceactual_pending_balance_credit_counter: Giá trịpending_balance_credit_countertrên token account tại thời điểm lệnhApplyPendingBalanceđược xử lý
Bộ đếm expected/actual khớp nhau cho thấy decryptable_available_balance khớp
với available_balance.
Khi lấy trạng thái của token account để đọc decryptable_available_balance, nếu
các giá trị bộ đếm expected/actual khác nhau, client cần tra cứu các lệnh
deposit/transfer gần đây khớp với sự chênh lệch bộ đếm để tính toán số dư chính
xác.
Quy Trình Đối Soát Số Dư
Khi bộ đếm pending balance expected và actual khác nhau, hãy làm theo các bước
sau để đối soát decryptable_available_balance:
- Bắt đầu với
decryptable_available_balancetừ token account - Lấy các giao dịch gần nhất bao gồm các lệnh deposit và transfer tính đến sự
chênh lệch bộ đếm (actual - expected):
- Cộng thêm các số tiền công khai từ các lệnh deposit
- Giải mã và cộng thêm các số tiền ciphertext đích từ các lệnh transfer
Các Lệnh Bắt Buộc
Việc tạo và cấu hình token account cho chuyển khoản bảo mật sử dụng các lệnh sau, tất cả đều nằm trong một giao dịch duy nhất:
-
Tạo Token Account: Gọi lệnh
AssociatedTokenAccountInstruction::Createcủa Associated Token Program để tạo token account tại địa chỉ xác định của nó. -
Cấp Phát Lại Không Gian Tài Khoản: Gọi lệnh
TokenInstruction::Reallocatecủa Token Extensions Program để thêm không gian cho trạng tháiConfidentialTransferAccount. -
Xác Minh Bằng Chứng Hợp Lệ Pubkey: Tạo một tài khoản thuộc sở hữu của chương trình ZK ElGamal Proof, sau đó gọi lệnh
VerifyPubkeyValiditycủa nó để xác minh bằng chứng và lưu kết quả đã xác minh vào tài khoản context state đó. -
Cấu Hình Chuyển Khoản Bảo Mật: Gọi lệnh ConfidentialTransferInstruction::ConfigureAccount của Token Extensions Program, tham chiếu tài khoản context state bằng chứng qua
ProofLocation::ContextStateAccount, để khởi tạo trạng tháiConfidentialTransferAccount.
Chỉ chủ sở hữu token account mới có thể cấu hình token account cho các giao dịch bảo mật.
Lệnh ConfigureAccount yêu cầu tạo phía client các khóa mã hóa và một bằng
chứng chỉ có thể được tạo bởi chủ sở hữu token account.
Bằng chứng hợp lệ pubkey xác minh rằng khóa công khai ElGamal của tài khoản là
hợp lệ. Nó được tạo bằng build_pubkey_validity_proof_data, được xác minh
on-chain bởi chương trình ZK ElGamal Proof vào một tài khoản context state, và
sau đó được tham chiếu từ ConfigureAccount qua
ProofLocation::ContextStateAccount, do đó không có byte bằng chứng nào
được truyền trong bản thân lệnh token. Để biết chi tiết triển khai, xem:
Mã Ví Dụ
Đoạn mã sau đây tạo một associated token account và cấu hình nó cho các giao dịch bảo mật dựa trên một mint bảo mật đã có sẵn.
Các giao dịch bảo mật phụ thuộc vào chương trình ZK ElGamal Proof, được kích
hoạt trên mainnet và devnet. Một solana-test-validator thông thường không kích
hoạt nó, nhưng một validator cục bộ fork mainnet như
Surfpool thì có. Chạy ví dụ trên một trong những môi
trường đó (mã sử dụng devnet) với một payer được nạp tiền, và thay thế
placeholder mint bằng một mint được tạo theo
Tạo Mint.
Rust
// The native ZK ElGamal Proof program verifies the proof on chain.const ZK_PROOF_PROGRAM_ID: Pubkey =solana_pubkey::pubkey!("ZkE1Gama1Proof11111111111111111111111111111");fn main() -> Result<()> {// Use a cluster whose ZK ElGamal Proof program is enabled (mainnet, devnet).let rpc_client = RpcClient::new_with_commitment(String::from("https://api.devnet.solana.com"),CommitmentConfig::confirmed(),);// The Solana CLI default keypair, used as fee payer, mint authority, and// token account owner.let payer = load_keypair()?;let decimals: u8 = 2;// Setup: create a confidential mint for the token account.let mint = create_confidential_mint(&rpc_client, &payer, decimals)?;let token_account = get_associated_token_address_with_program_id(&payer.pubkey(),&mint,&spl_token_2022::id(),);// 1. Create the associated token account.let create_ata_ix = create_associated_token_account(&payer.pubkey(), // funding account&payer.pubkey(), // token account owner&mint,&spl_token_2022::id(),);// 2. Add space for the ConfidentialTransferAccount extension.let realloc_ix = reallocate(&spl_token_2022::id(),&token_account,&payer.pubkey(), // payer&payer.pubkey(), // owner&[&payer.pubkey()],&[ExtensionType::ConfidentialTransferAccount],)?;// 3. Derive the owner's ElGamal keypair and AES key from a signature over// the token account address. The same signer and address always derive// the same keys, so the owner can recover them from their wallet.let (elgamal_keypair, aes_key) = derive_confidential_keys(&payer, &token_account.to_bytes()).map_err(|e| anyhow::anyhow!("derive confidential keys: {e}"))?;// Initial decryptable available balance of 0, encrypted with the AES key.let decryptable_balance: PodAeCiphertext = aes_key.encrypt(0).into();let maximum_pending_balance_credit_counter: u64 = 65_536;// 4. Generate the pubkey-validity proof, then pre-verify it into a context// state account owned by the ZK ElGamal Proof program. configure_account// references the verified proof by account, so no proof bytes travel in// the token instruction itself.let proof_data = build_pubkey_validity_proof_data(&elgamal_keypair).map_err(|e| anyhow::anyhow!("generate pubkey validity proof: {e}"))?;let proof_account = Keypair::new();let context_state_size = size_of::<ProofContextState<PubkeyValidityProofContext>>();let context_state_rent =rpc_client.get_minimum_balance_for_rent_exemption(context_state_size)?;let create_proof_account_ix = system_instruction::create_account(&payer.pubkey(),&proof_account.pubkey(),context_state_rent,context_state_size as u64,&ZK_PROOF_PROGRAM_ID,);let proof_account_address: Address = proof_account.pubkey().to_bytes().into();let owner_address: Address = payer.pubkey().to_bytes().into();let verify_proof_ix = ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(Some(ContextStateInfo {context_state_account: &proof_account_address,context_state_authority: &owner_address,}),&proof_data,);// 5. Configure the account, pointing at the pre-verified proof account.let proof_location: ProofLocation<PubkeyValidityProofData> =ProofLocation::ContextStateAccount(&proof_account.pubkey());let configure_account_ixs = configure_account(&spl_token_2022::id(),&token_account,&mint,&decryptable_balance,maximum_pending_balance_credit_counter,&payer.pubkey(), // owner&[],proof_location,)?;// Everything fits in a single transaction.let mut instructions = vec![create_ata_ix,realloc_ix,create_proof_account_ix,verify_proof_ix,];instructions.extend(configure_account_ixs);let blockhash = rpc_client.get_latest_blockhash()?;let transaction = Transaction::new_signed_with_payer(&instructions,Some(&payer.pubkey()),&[&payer, &proof_account],blockhash,);let signature = rpc_client.send_and_confirm_transaction(&transaction)?;println!("Configured token account {token_account} for confidential transfers: {signature}");Ok(())}
Typescript
const client = await createClient().use(signerFromFile(join(homedir(), ".config/solana/id.json"))).use(solanaRpc({rpcUrl: "https://api.devnet.solana.com"}));// The Solana CLI default keypair, used as fee payer, mint authority, and// token account owner.const owner = client.payer;const decimals = 2;// Setup: create a confidential mint for the token account.const mint = await createConfidentialMint(client, owner, decimals);const [tokenAccount] = await findAssociatedTokenPda({owner: owner.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,mint});// Derive recoverable ElGamal and AES keys bound to (owner, mint). Re-deriving// from the same wallet always yields the same keys, so the owner can recover// them rather than having to back up a separate secret.const derivedElGamal = await deriveElGamalKeypairForOwnerMint({signer: owner,owner: owner.address,mint});const elgamalKeypair = ElGamalKeypair.fromSecretKey(ElGamalSecretKey.fromBytes(derivedElGamal.secretKey));const aesKey = AeKey.fromBytes(await deriveAeKeyForOwnerMint({ signer: owner, owner: owner.address, mint }));// Build the create-ATA + reallocate + verify-proof + configure plan, then send.// The helper returns an instruction plan because the steps may span more than// one transaction.const plan = await getCreateConfidentialTransferAccountInstructionPlan({rpc: client.rpc,payer: owner,owner,mint,elgamalKeypair,aesKey});const result = await client.sendTransaction(plan);console.log(`Configured token account ${tokenAccount} for confidential transfers: ${result.context.signature}`);
Các tiện ích TypeScript nằm trong subpath
@solana-program/token-2022/confidential và được xây dựng dựa trên
@solana/zk-sdk cho các nguyên thủy mã hóa. owner và client đến từ thiết
lập @solana/kit của bạn; kế hoạch lệnh được trả về được gửi với hỗ trợ kế
hoạch lệnh của @solana/kit, phân chia công việc qua nhiều giao dịch khi các
bằng chứng quá lớn cho một giao dịch.
Is this page helpful?