Hoe maak je een token account aan met de Confidential Transfer-extensie
De Confidential Transfer-extensie maakt privétokenoverdrachten mogelijk door extra status toe te voegen aan het token account. In dit gedeelte wordt uitgelegd hoe je een token account aanmaakt met deze extensie ingeschakeld.
Het volgende diagram toont de stappen voor het aanmaken van een token account met de Confidential Transfer-extensie:
Statusvelden van het Confidential Transfer Token Account
De extensie voegt de ConfidentialTransferAccount status toe aan het 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,}
De ConfidentialTransferAccount bevat verschillende velden voor het beheren
van vertrouwelijke overdrachten:
-
approved: De goedkeuringsstatus van het account voor vertrouwelijke overdrachten. Als de configuratie
auto_approve_new_accountsvan het mint account is ingesteld optrue, worden alle token accounts automatisch goedgekeurd voor vertrouwelijke overdrachten. -
elgamal_pubkey: De openbare ElGamal-sleutel die wordt gebruikt om saldi en overdrachtsbedragen te versleutelen.
-
pending_balance_lo: De versleutelde lagere 16 bits van het openstaande saldo. Het saldo wordt opgesplitst in een hoog en een laag gedeelte voor efficiënte ontsleuteling.
-
pending_balance_hi: De versleutelde hogere 48 bits van het openstaande saldo. Het saldo wordt opgesplitst in een hoog en een laag gedeelte voor efficiënte ontsleuteling.
-
available_balance: Het versleutelde saldo dat beschikbaar is voor overdrachten.
-
decryptable_available_balance: Het beschikbare saldo versleuteld met een Advanced Encryption Standard (AES)-sleutel voor efficiënte ontsleuteling door de accounteigenaar.
-
allow_confidential_credits: Indien true, worden inkomende vertrouwelijke overdrachten toegestaan.
-
allow_non_confidential_credits: Indien true, worden inkomende niet-vertrouwelijke overdrachten toegestaan.
-
pending_balance_credit_counter: Telt inkomende tegoedbijschrijvingen voor het openstaande saldo van stortings- en overdrachtsInstructies.
-
maximum_pending_balance_credit_counter: De tellerlimiet voor openstaande tegoeden voordat een
ApplyPendingBalance-instructie vereist is om het openstaande saldo om te zetten naar het beschikbare saldo. -
expected_pending_balance_credit_counter: De waarde
pending_balance_credit_counterdie door de client is opgegeven via de instruction data de laatste keer dat deApplyPendingBalance-instructie werd verwerkt. -
actual_pending_balance_credit_counter: De
pending_balance_credit_counterwaarde op het token account op het moment dat de laatsteApplyPendingBalanceinstructie werd verwerkt.
Openstaand vs Beschikbaar Saldo
Vertrouwelijke saldi zijn opgesplitst in openstaande en beschikbare saldi om DoS-aanvallen te voorkomen. Zonder deze scheiding zou een aanvaller herhaaldelijk tokens naar een token account kunnen sturen, waardoor de eigenaar van het token account geen tokens meer kan overdragen. De eigenaar van het token account zou geen tokens kunnen overdragen omdat het versleutelde saldo zou veranderen tussen het moment van indiening en verwerking van de transactie, wat resulteert in een mislukte transactie.
Alle stortingen en overdrachtsbedragen worden aanvankelijk toegevoegd aan het
openstaande saldo. Eigenaren van token accounts moeten de
ApplyPendingBalance instructie gebruiken om het openstaande saldo om te
zetten naar het beschikbare saldo. Inkomende overdrachten of stortingen hebben
geen invloed op het beschikbare saldo van een token account.
Splitsing Openstaand Saldo Hoog/Laag
Het vertrouwelijke openstaande saldo is opgesplitst in pending_balance_lo en
pending_balance_hi omdat ElGamal-ontsleuteling meer berekeningen vereist voor
grote getallen. U kunt de implementatie van de cijfertekstaritmetiek
hier
vinden, die wordt gebruikt in de ApplyPendingBalance instructie
hier.
Tellers voor Openstaand Saldo Creditering
Bij het aanroepen van de ApplyPendingBalance instructie om het openstaande
saldo om te zetten naar het beschikbare saldo:
-
De client zoekt de huidige openstaande en beschikbare saldi op, versleutelt de som, en levert een
decryptable_available_balanceversleuteld met behulp van de AES-sleutel van de eigenaar van het token account. -
De verwachte en werkelijke tellers voor openstaande creditering houden wijzigingen in de tellerwaarde bij tussen het moment waarop de
ApplyPendingBalanceinstructie wordt aangemaakt en verwerkt:expected_pending_balance_credit_counter: Depending_balance_credit_counterwaarde wanneer de client deApplyPendingBalanceinstructie aanmaaktactual_pending_balance_credit_counter: Depending_balance_credit_counterwaarde op het token account op het moment dat deApplyPendingBalanceinstructie wordt verwerkt
Overeenkomende verwachte/werkelijke tellers geven aan dat de
decryptable_available_balance overeenkomt met de available_balance.
Bij het ophalen van de status van een token account om de
decryptable_available_balance te lezen, vereisen verschillende waarden van
verwachte/werkelijke tellers dat de client recente
stortings-/overdrachtsinstructies opzoekt die overeenkomen met het
tellerverschil om het correcte saldo te berekenen.
Saldo-reconciliatieproces
Wanneer de verwachte en werkelijke tellers voor het uitstaande saldo
verschillen, volg dan deze stappen om de decryptable_available_balance te
reconciliëren:
- Begin met de
decryptable_available_balancevan het token account - Haal de meest recente transacties op, inclusief storting- en
overdrachtsinstructies tot aan het tellerverschil (werkelijk - verwacht):
- Voeg openbare bedragen toe uit stortingsinstructies
- Ontsleutel en voeg bestemmings-ciphertekstbedragen toe uit overdrachtsinstructies
Vereiste instructies
Het aanmaken en configureren van een token account voor vertrouwelijke overdrachten maakt gebruik van de volgende instructies, die allemaal in één transactie passen:
-
Maak het token account aan: Roep de
AssociatedTokenAccountInstruction::Create-instructie van het Associated Token Program aan om het token account op het deterministische adres aan te maken. -
Herbestem accountruimte: Roep de
TokenInstruction::Reallocate-instructie van het Token Extensions Program aan om ruimte toe te voegen voor deConfidentialTransferAccount-status. -
Verifieer de pubkey-geldigheidsbewijslevering: Maak een account aan dat eigendom is van het ZK ElGamal Proof-programma, en roep vervolgens de
VerifyPubkeyValidity-instructie aan om het bewijs te verifiëren en het geverifieerde resultaat op te slaan in dat contextstatusaccount. -
Configureer vertrouwelijke overdrachten: Roep de ConfidentialTransferInstruction::ConfigureAccount-instructie van het Token Extensions Program aan, met verwijzing naar het bewijs-contextstatusaccount via
ProofLocation::ContextStateAccount, om deConfidentialTransferAccount-status te initialiseren.
Alleen de eigenaar van het token account kan een token account configureren voor confidential transfers.
De ConfigureAccount-instructie vereist dat aan de clientzijde
versleutelingssleutels worden gegenereerd en een bewijs dat alleen door de
eigenaar van het token account kan worden gegenereerd.
Het pubkey-geldigheidsbewijs verifieert dat de ElGamal-publieke sleutel van het
account geldig is. Het wordt gegenereerd met
build_pubkey_validity_proof_data, on-chain geverifieerd door het ZK
ElGamal Proof-programma in een contextstatusaccount, en vervolgens gerefereerd
vanuit ConfigureAccount via ProofLocation::ContextStateAccount,
zodat er geen bewijsbytes door de token-instructie zelf worden verstuurd. Zie
voor implementatiedetails:
Voorbeeldcode
De volgende code maakt een associated token account aan en configureert deze voor vertrouwelijke overdrachten tegen een bestaande vertrouwelijke mint.
Vertrouwelijke overdrachten zijn afhankelijk van het ZK ElGamal Proof-programma,
dat ingeschakeld is op mainnet en devnet. Een standaard solana-test-validator
schakelt dit niet in, maar een lokale validator die mainnet forkt, zoals
Surfpool, wel. Voer het voorbeeld uit op een van deze
omgevingen (de code gebruikt devnet) met een gefinancierde betaler, en vervang
de mint-placeholder door een mint die is aangemaakt via
Create a 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}`);
De TypeScript-hulpfuncties bevinden zich in het
@solana-program/token-2022/confidential subpad en bouwen voort op
@solana/zk-sdk voor de versleutelingsprimitieven. owner en client komen
uit uw @solana/kit-configuratie; het geretourneerde instructieplan wordt
verzonden met de instructieplanondersteuning van @solana/kit, die het werk
verdeelt over meerdere transacties wanneer de bewijzen te groot zijn voor één
transactie.
Is this page helpful?