Een token account aanmaken

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:

Create Token Account with Confidential Transfer Extension

Statusvelden van het Confidential Transfer Token Account

De extensie voegt de ConfidentialTransferAccount status toe aan het token account:

Confidential Token Account State
#[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 encryption
pub 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 balance
pub decryptable_available_balance: DecryptableBalance,
/// If `false`, the extended account rejects any incoming confidential
/// transfers
pub allow_confidential_credits: PodBool,
/// If `false`, the base account rejects any incoming transfers
pub 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 executed
pub maximum_pending_balance_credit_counter: PodU64,
/// The `expected_pending_balance_credit_counter` value that was included in
/// the last `ApplyPendingBalance` instruction
pub expected_pending_balance_credit_counter: PodU64,
/// The actual `pending_balance_credit_counter` when the last
/// `ApplyPendingBalance` instruction was executed
pub 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_accounts van het mint account is ingesteld op true, 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 teller­limiet 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_counter die door de client is opgegeven via de instruction data de laatste keer dat de ApplyPendingBalance-instructie werd verwerkt.

  • actual_pending_balance_credit_counter: De pending_balance_credit_counter waarde op het token account op het moment dat de laatste ApplyPendingBalance instructie 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:

  1. De client zoekt de huidige openstaande en beschikbare saldi op, versleutelt de som, en levert een decryptable_available_balance versleuteld met behulp van de AES-sleutel van de eigenaar van het token account.

  2. De verwachte en werkelijke tellers voor openstaande creditering houden wijzigingen in de tellerwaarde bij tussen het moment waarop de ApplyPendingBalance instructie wordt aangemaakt en verwerkt:

    • expected_pending_balance_credit_counter: De pending_balance_credit_counter waarde wanneer de client de ApplyPendingBalance instructie aanmaakt
    • actual_pending_balance_credit_counter: De pending_balance_credit_counter waarde op het token account op het moment dat de ApplyPendingBalance instructie 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:

  1. Begin met de decryptable_available_balance van het token account
  2. 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:

  1. 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.

  2. Herbestem accountruimte: Roep de TokenInstruction::Reallocate-instructie van het Token Extensions Program aan om ruimte toe te voegen voor de ConfidentialTransferAccount-status.

  3. 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.

  4. Configureer vertrouwelijke overdrachten: Roep de ConfidentialTransferInstruction::ConfigureAccount-instructie van het Token Extensions Program aan, met verwijzing naar het bewijs-contextstatusaccount via ProofLocation::ContextStateAccount, om de ConfidentialTransferAccount-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?

Inhoudsopgave

Pagina Bewerken
© 2026 Solana Foundation. Alle rechten voorbehouden.