Een token account aanmaken

Een token account aanmaken met Confidential Balances

Confidential Balances 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 de vereiste extensie ingeschakeld.

Het volgende diagram toont de stappen voor het aanmaken van een token account met Confidential Balances ingeschakeld:

Create Token Account with Confidential Balances

Token Account-status van Confidential Balances

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 die Confidential Balances beheren:

  • approved: De goedkeuringsstatus van het account voor Confidential Balances. Als de configuratie auto_approve_new_accounts van het mint account is ingesteld op true, worden alle token accounts automatisch goedgekeurd voor Confidential Balances.

  • 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 Confidential Balances maakt gebruik van de volgende instructies, die allemaal in één enkele 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. Confidential Balances configureren: Roep de instructie ConfidentialTransferInstruction::ConfigureAccount van het Token Extension Program aan, waarbij het bewijs-contextstatusaccount wordt gerefereerd via ProofLocation::ContextStateAccount, om de ConfidentialTransferAccount-status te initialiseren.

Alleen de eigenaar van het token account kan Confidential Balances inschakelen op een token account.

De instructie ConfigureAccount vereist de clientzijdige generatie van versleutelingssleutels en een bewijs dat alleen door de eigenaar van het token account kan worden gegenereerd.

Het pubkey-geldigsheidsbewijs 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 worden doorgegeven in de token-instructie zelf. Zie voor implementatiedetails:

Voorbeeldcode

De volgende code maakt een associated token account aan en configureert het om Confidential Balances te gebruiken met een bestaande compatibele mint.

Confidential Balances is afhankelijk van het ZK ElGamal Proof-programma, dat is ingeschakeld op mainnet en devnet. Een standaard solana-test-validator schakelt dit niet in, maar een lokale validator met mainnet-fork zoals Surfpool wel. Voer het voorbeeld uit op een van die 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!("Enabled Confidential Balances on token account {token_account}: {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(
`Enabled Confidential Balances on token account ${tokenAccount}: ${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.