Як створити token account з розширенням Confidential Transfer
Розширення Confidential Transfer забезпечує приватні перекази токенів, додаючи додатковий стан до token account. У цьому розділі пояснюється, як створити token account з увімкненим цим розширенням.
На наступній діаграмі показано кроки, необхідні для створення token account з розширенням Confidential Transfer:
Стан Token Account для Confidential Transfer
Розширення додає стан ConfidentialTransferAccount до 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 містить кілька полів для керування
конфіденційними переказами:
-
approved: Статус підтвердження акаунта для конфіденційних переказів. Якщо конфігурація
auto_approve_new_accountsу mint account встановлена якtrue, усі token accounts автоматично затверджуються для конфіденційних переказів. -
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: Підраховує вхідні кредити очікуваного балансу з інструкцій депозиту та переказу.
-
maximum_pending_balance_credit_counter: Ліміт кількості очікуваних кредитів перед виконанням інструкції
ApplyPendingBalanceдля конвертації очікуваного балансу в доступний. -
expected_pending_balance_credit_counter: Значення
pending_balance_credit_counter, надане клієнтом через instruction data під час останнього виконання інструкціїApplyPendingBalance. -
actual_pending_balance_credit_counter: Значення
pending_balance_credit_counterу token account на момент обробки останньої інструкціїApplyPendingBalance.
Очікуваний та доступний баланс
Конфіденційні баланси розділені на очікуваний та доступний баланси для запобігання DoS-атакам. Без цього розділення зловмисник міг би неодноразово надсилати токени на token account, блокуючи можливість власника token account переказувати токени. Власник token account не зміг би здійснювати перекази, оскільки зашифрований баланс змінювався б між моментом подання транзакції та її обробкою, що призводило б до помилки транзакції.
Усі депозити та суми переказів спочатку додаються до очікуваного балансу.
Власники token account повинні використовувати інструкцію
ApplyPendingBalance, щоб конвертувати очікуваний баланс у доступний.
Вхідні перекази або депозити не впливають на доступний баланс token account.
Розділення очікуваного балансу на старшу/молодшу частини
Конфіденційний очікуваний баланс розділяється на pending_balance_lo та
pending_balance_hi, оскільки дешифрування ElGamal вимагає більше обчислень для
великих чисел. Реалізацію арифметики шифротексту можна знайти
тут,
яка використовується в інструкції ApplyPendingBalance
тут.
Лічильники кредитів очікуваного балансу
При виклику інструкції ApplyPendingBalance для конвертації очікуваного
балансу в доступний:
-
Клієнт отримує поточні очікуваний та доступний баланси, шифрує їх суму та надає
decryptable_available_balance, зашифрований за допомогою AES-ключа власника token account. -
Очікуваний та фактичний лічильники кредитів відстежують зміни значення лічильника між моментом створення та обробки інструкції
ApplyPendingBalance:expected_pending_balance_credit_counter: значенняpending_balance_credit_counterна момент, коли клієнт створює інструкціюApplyPendingBalanceactual_pending_balance_credit_counter: значенняpending_balance_credit_counterу token account на момент обробки інструкціїApplyPendingBalance
Збіг очікуваних/фактичних лічильників вказує на те, що
decryptable_available_balance відповідає available_balance.
Під час отримання стану token account для читання
decryptable_available_balance, різні значення очікуваних/фактичних лічильників
зобов'язують клієнта знайти останні інструкції депозиту/переказу, що
відповідають різниці лічильників, щоб обчислити правильний баланс.
Процес узгодження балансу
Коли очікувані та фактичні лічильники очікуваного балансу відрізняються,
виконайте такі кроки для узгодження decryptable_available_balance:
- Почніть з
decryptable_available_balanceз token account - Отримайте найновіші транзакції, включаючи інструкції депозиту та переказу до
різниці лічильників (фактичний - очікуваний):
- Додайте публічні суми з інструкцій депозиту
- Розшифруйте та додайте суми зашифрованого тексту призначення з інструкцій переказу
Необхідні інструкції
Створення та налаштування token account для конфіденційних переказів використовує наступні інструкції, які всі вміщуються в одній транзакції:
-
Створіть Token Account: Викличте інструкцію
AssociatedTokenAccountInstruction::CreateAssociated Token Program для створення token account за його детерміністичною адресою. -
Перерозподіліть простір акаунта: Викличте інструкцію
TokenInstruction::ReallocateToken Extension Program, щоб додати простір для стануConfidentialTransferAccount. -
Перевірте доказ дійсності pubkey: Створіть акаунт, яким володіє програма ZK ElGamal Proof, потім викличте її інструкцію
VerifyPubkeyValidityдля перевірки доказу та збереження підтвердженого результату в акаунті стану контексту. -
Налаштуйте конфіденційні перекази: Викличте інструкцію Token Extension Program ConfidentialTransferInstruction::ConfigureAccount, посилаючись на акаунт стану контексту доказу через
ProofLocation::ContextStateAccount, для ініціалізації стануConfidentialTransferAccount.
Лише власник token account може налаштувати token account для конфіденційних переказів.
Інструкція ConfigureAccount вимагає генерації на стороні клієнта ключів
шифрування та доказу, який може бути згенерований лише власником token account.
Доказ дійсності pubkey підтверджує, що публічний ключ ElGamal акаунта є дійсним.
Він генерується за допомогою build_pubkey_validity_proof_data,
верифікується в мережі програмою ZK ElGamal Proof у акаунт стану контексту, а
потім посилається з ConfigureAccount через
ProofLocation::ContextStateAccount, тому жодні байти доказу не передаються
в самій інструкції токена. Для отримання деталей реалізації дивіться:
Приклад коду
Наведений нижче код створює associated token account та налаштовує його для конфіденційних переказів на основі існуючого конфіденційного мінту.
Конфіденційні перекази залежать від програми ZK ElGamal Proof, яка активована в
мережах mainnet та devnet. Стандартний solana-test-validator не вмикає її, але
локальний validator із форком mainnet, наприклад
Surfpool, підтримує це. Запустіть приклад на одному з
них (у коді використовується devnet) із поповненим гаманцем платника та замініть
заповнювач мінту на мінт, створений згідно з інструкцією
Створення мінту.
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}`);
Допоміжні функції TypeScript знаходяться у підшляху
@solana-program/token-2022/confidential та базуються на @solana/zk-sdk для
примітивів шифрування. owner та client надходять із вашого налаштування
@solana/kit; повернутий план інструкцій надсилається за допомогою підтримки
планів інструкцій @solana/kit, що розподіляє роботу між транзакціями, коли
докази занадто великі для однієї.
Is this page helpful?