Token hesabı oluşturma

Confidential Transfer uzantısıyla token account nasıl oluşturulur

Confidential Transfer uzantısı, token account'a ekstra durum bilgisi ekleyerek gizli token transferlerini mümkün kılar. Bu bölüm, bu uzantı etkinleştirilmiş bir token account'ın nasıl oluşturulacağını açıklar.

Aşağıdaki diyagram, Confidential Transfer uzantısıyla bir token account oluşturulurken izlenen adımları göstermektedir:

Create Token Account with Confidential Transfer Extension

Confidential Transfer Token Account Durumu

Uzantı, token account'a ConfidentialTransferAccount durumunu ekler:

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,
}

ConfidentialTransferAccount, gizli transferleri yönetmek için çeşitli alanlar içerir:

  • approved: Hesabın gizli transferler için onay durumu. Eğer mint account'ın auto_approve_new_accounts yapılandırması true olarak ayarlanmışsa, tüm token account'lar gizli transferler için otomatik olarak onaylanır.

  • elgamal_pubkey: Bakiyeleri ve transfer tutarlarını şifrelemek için kullanılan ElGamal pubkey'i.

  • pending_balance_lo: Bekleyen bakiyenin şifrelenmiş alt 16 biti. Bakiye, verimli şifre çözme için yüksek ve düşük parçalara bölünür.

  • pending_balance_hi: Bekleyen bakiyenin şifrelenmiş üst 48 biti. Bakiye, verimli şifre çözme için yüksek ve düşük parçalara bölünür.

  • available_balance: Transferler için kullanılabilir şifrelenmiş bakiye.

  • decryptable_available_balance: Hesap sahibi tarafından verimli şifre çözme için Gelişmiş Şifreleme Standardı (AES) anahtarıyla şifrelenmiş kullanılabilir bakiye.

  • allow_confidential_credits: True ise, gelen gizli transferlere izin verir.

  • allow_non_confidential_credits: True ise, gelen gizli olmayan transferlere izin verir.

  • pending_balance_credit_counter: Yatırma ve transfer talimatlarından gelen bekleyen bakiye kredilerini sayar.

  • maximum_pending_balance_credit_counter: Bekleyen bakiyeyi kullanılabilir bakiyeye dönüştürmek için ApplyPendingBalance talimatının gerekli kılınmasından önce izin verilen bekleyen kredi sayısı sınırı.

  • expected_pending_balance_credit_counter: ApplyPendingBalance talimatının son işlendiği sırada istemci tarafından instruction data aracılığıyla sağlanan pending_balance_credit_counter değeri.

  • actual_pending_balance_credit_counter: token account üzerindeki pending_balance_credit_counter değeri, son ApplyPendingBalance talimatının işlendiği andaki değerdir.

Bekleyen ve Kullanılabilir Bakiye

Gizli bakiyeler, DoS saldırılarını önlemek amacıyla bekleyen ve kullanılabilir bakiyeler olarak ikiye ayrılır. Bu ayrım olmasaydı, bir saldırgan bir token account'a defalarca token göndererek token account sahibinin token transferi yapabilme yeteneğini engelleyebilirdi. Token account sahibi, işlem gönderildiği an ile işlendiği an arasında şifreli bakiye değişeceğinden token transferi yapamaz hale gelirdi ve bu durum işlemin başarısız olmasına yol açardı.

Tüm yatırma ve transfer tutarları başlangıçta bekleyen bakiyeye eklenir. Token account sahipleri, bekleyen bakiyeyi kullanılabilir bakiyeye dönüştürmek için ApplyPendingBalance talimatını kullanmalıdır. Gelen transferler veya yatırma işlemleri, token account'ın kullanılabilir bakiyesini etkilemez.

Bekleyen Bakiye Yüksek/Düşük Bölünmesi

Gizli bekleyen bakiye, pending_balance_lo ve pending_balance_hi olarak ikiye ayrılır; bunun nedeni ElGamal şifre çözme işleminin büyük sayılar için daha fazla hesaplama gerektirmesidir. Şifreli metin aritmetik uygulamasını burada bulabilirsiniz; bu uygulama ApplyPendingBalance talimatında burada kullanılmaktadır.

Bekleyen Bakiye Kredi Sayaçları

Bekleyen bakiyeyi kullanılabilir bakiyeye dönüştürmek için ApplyPendingBalance talimatı çağrıldığında:

  1. İstemci, mevcut bekleyen ve kullanılabilir bakiyeleri sorgular, toplamı şifreler ve token account sahibinin AES anahtarı kullanılarak şifrelenmiş bir decryptable_available_balance sağlar.

  2. Beklenen ve gerçek bekleyen kredi sayaçları, ApplyPendingBalance talimatının oluşturulduğu an ile işlendiği an arasındaki sayaç değeri değişikliklerini izler:

    • expected_pending_balance_credit_counter: İstemcinin ApplyPendingBalance talimatını oluşturduğu andaki pending_balance_credit_counter değeri
    • actual_pending_balance_credit_counter: ApplyPendingBalance talimatının işlendiği andaki token account üzerindeki pending_balance_credit_counter değeri

Beklenen/gerçek sayaçların eşleşmesi, decryptable_available_balance değerinin available_balance ile uyuştuğunu gösterir.

Bir token account'ın durumu decryptable_available_balance değerini okumak için getirildiğinde, beklenen/gerçek sayaç değerlerinin farklı olması durumunda istemcinin doğru bakiyeyi hesaplamak için sayaç farkına karşılık gelen en son para yatırma/transfer talimatlarını araması gerekir.

Bakiye Mutabakat Süreci

Beklenen ve gerçek bekleyen bakiye sayaçları farklı olduğunda, decryptable_available_balance değerini mutabık kılmak için şu adımları izleyin:

  1. token account'tan decryptable_available_balance ile başlayın
  2. Sayaç farkına (gerçek - beklenen) kadar para yatırma ve transfer talimatlarını içeren en son işlemleri getirin:
    • Para yatırma talimatlarından kamuya açık tutarları ekleyin
    • Transfer talimatlarından hedef şifreli metin tutarlarını çözümleyip ekleyin

Gerekli Talimatlar

Gizli transferler için bir token account oluşturmak ve yapılandırmak, tek bir işleme sığan aşağıdaki talimatları kullanır:

  1. Token Account'ı Oluşturun: Belirleyici adresinde token account'ı oluşturmak için Associated Token Program'ın AssociatedTokenAccountInstruction::Create talimatını çağırın.

  2. Hesap Alanını Yeniden Tahsis Edin: ConfidentialTransferAccount durumu için alan eklemek amacıyla Token Extension Program'ın TokenInstruction::Reallocate talimatını çağırın.

  3. Pubkey Geçerlilik Kanıtını Doğrulayın: ZK ElGamal Proof programına ait bir hesap oluşturun, ardından kanıtı doğrulamak ve doğrulanmış sonucu o bağlam durum hesabında depolamak için VerifyPubkeyValidity talimatını çağırın.

  4. Gizli Transferleri Yapılandırın: ConfidentialTransferAccount durumunu başlatmak için kanıt bağlam durum hesabını ProofLocation::ContextStateAccount aracılığıyla referans alarak Token Extension Program'ın ConfidentialTransferInstruction::ConfigureAccount talimatını çağırın.

Yalnızca token hesabı sahibi, bir token hesabını gizli transferler için yapılandırabilir.

ConfigureAccount talimatı, yalnızca token account sahibi tarafından oluşturulabilen şifreleme anahtarlarının ve bir kanıtın istemci tarafında üretilmesini gerektirir.

Pubkey geçerlilik kanıtı, hesabın ElGamal açık anahtarının geçerli olduğunu doğrular. build_pubkey_validity_proof_data ile oluşturulur, ZK ElGamal Proof programı tarafından zincir üzerinde bir bağlam durum hesabına doğrulanır ve ardından ProofLocation::ContextStateAccount aracılığıyla ConfigureAccount içinden referans alınır; böylece token talimatının kendisinde hiçbir kanıt baytı iletilmez. Uygulama ayrıntıları için bkz:

Örnek Kod

Aşağıdaki kod, bir associated token account oluşturur ve bunu mevcut bir gizli mint için gizli transferlere karşı yapılandırır.

Gizli transferler, mainnet ve devnet üzerinde etkinleştirilen ZK ElGamal Proof programına bağlıdır. Standart bir solana-test-validator bunu etkinleştirmez; ancak Surfpool gibi mainnet'i çatallayan yerel bir validator etkinleştirir. Örneği, bu ortamlardan biri üzerinde (kod devnet kullanır) fonlanmış bir ödeyiciyle çalıştırın ve mint yer tutucusunu Mint Oluşturma bölümüne göre oluşturulmuş bir mint ile değiştirin.

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 yardımcı araçları @solana-program/token-2022/confidential alt yolunda bulunur ve şifreleme ilkel öğeleri için @solana/zk-sdk üzerine inşa edilir. owner ve client, @solana/kit kurulumunuzdan gelir; döndürülen talimat planı, @solana/kit'nin talimat planı desteğiyle gönderilir ve kanıtlar tek bir işlem için çok büyük olduğunda çalışmayı işlemler arasında böler.

Is this page helpful?

İçindekiler

Sayfayı Düzenle
© 2026 Solana Vakfı. Tüm hakları saklıdır.