Ein Token-Konto erstellen

So erstellen Sie ein token account mit der Confidential Transfer-Erweiterung

Die Confidential Transfer-Erweiterung ermöglicht private Token-Übertragungen, indem sie dem token account zusätzlichen Zustand hinzufügt. Dieser Abschnitt erklärt, wie ein token account mit aktivierter Erweiterung erstellt wird.

Das folgende Diagramm zeigt die Schritte zum Erstellen eines token account mit der Confidential Transfer-Erweiterung:

Create Token Account with Confidential Transfer Extension

Zustandsverwaltung des Confidential Transfer Token Account

Die Erweiterung fügt den ConfidentialTransferAccount Zustand zum token account hinzu:

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

Der ConfidentialTransferAccount enthält mehrere Felder zur Verwaltung vertraulicher Übertragungen:

  • approved: Der Genehmigungsstatus des Konten für vertrauliche Übertragungen. Wenn die Konfiguration auto_approve_new_accounts des mint account auf true gesetzt ist, werden alle token accounts automatisch für vertrauliche Übertragungen genehmigt.

  • elgamal_pubkey: Der öffentliche ElGamal-Schlüssel, der zur Verschlüsselung von Guthaben und Überweisungsbeträgen verwendet wird.

  • pending_balance_lo: Die verschlüsselten unteren 16 Bits des ausstehenden Guthabens. Das Guthaben wird zur effizienten Entschlüsselung in einen hohen und einen niedrigen Teil aufgeteilt.

  • pending_balance_hi: Die verschlüsselten oberen 48 Bits des ausstehenden Guthabens. Das Guthaben wird zur effizienten Entschlüsselung in einen hohen und einen niedrigen Teil aufgeteilt.

  • available_balance: Das verschlüsselte Guthaben, das für Übertragungen verfügbar ist.

  • decryptable_available_balance: Das verfügbare Guthaben, das mit einem Advanced Encryption Standard (AES)-Schlüssel für eine effiziente Entschlüsselung durch den Konten-Inhaber verschlüsselt ist.

  • allow_confidential_credits: Wenn true, werden eingehende vertrauliche Übertragungen erlaubt.

  • allow_non_confidential_credits: Wenn true, werden eingehende nicht-vertrauliche Übertragungen erlaubt.

  • pending_balance_credit_counter: Zählt eingehende ausstehende Guthabengutschriften aus Einzahlungs- und Übertragungs- Anweisungen.

  • maximum_pending_balance_credit_counter: Das Zähllimit für ausstehende Gutschriften, bevor eine ApplyPendingBalance Anweisungen erforderlich ist, um das ausstehende Guthaben in das verfügbare Guthaben umzuwandeln.

  • expected_pending_balance_credit_counter: Der pending_balance_credit_counter-Wert, der vom Client über die instruction data beim letzten Verarbeiten der ApplyPendingBalance Anweisungen übermittelt wurde.

  • actual_pending_balance_credit_counter: Der pending_balance_credit_counter Wert im token account zum Zeitpunkt, als die letzte ApplyPendingBalance Anweisung verarbeitet wurde.

Ausstehender vs. verfügbarer Saldo

Vertrauliche Salden werden in ausstehende und verfügbare Salden unterteilt, um DoS-Angriffe zu verhindern. Ohne diese Trennung könnte ein Angreifer wiederholt Token an ein token account senden und so die Fähigkeit des token account-Inhabers zum Übertragen von Token blockieren. Der token account-Inhaber wäre nicht in der Lage, Token zu übertragen, da sich der verschlüsselte Saldo zwischen dem Zeitpunkt der Einreichung und der Verarbeitung der Transaktion ändern würde, was zu einer fehlgeschlagenen Transaktion führen würde.

Alle Einzahlungen und Überweisungsbeträge werden zunächst dem ausstehenden Saldo hinzugefügt. Token account-Inhaber müssen die ApplyPendingBalance Anweisung verwenden, um den ausstehenden Saldo in den verfügbaren Saldo umzuwandeln. Eingehende Überweisungen oder Einzahlungen wirken sich nicht auf den verfügbaren Saldo eines token account aus.

Aufteilung des ausstehenden Saldos in Hoch/Niedrig

Der vertrauliche ausstehende Saldo wird in pending_balance_lo und pending_balance_hi aufgeteilt, da die ElGamal-Entschlüsselung bei größeren Zahlen mehr Rechenaufwand erfordert. Die Implementierung der Chiffretext-Arithmetik finden Sie hier, die in der ApplyPendingBalance Anweisung hier verwendet wird.

Ausstehende Saldo-Kreditzähler

Beim Aufrufen der ApplyPendingBalance Anweisung zur Umwandlung des ausstehenden Saldos in den verfügbaren Saldo:

  1. Der Client ruft den aktuellen ausstehenden und verfügbaren Saldo ab, verschlüsselt die Summe und stellt ein decryptable_available_balance bereit, das mit dem AES-Schlüssel des token account-Inhabers verschlüsselt ist.

  2. Die erwarteten und tatsächlichen ausstehenden Kreditzähler verfolgen Änderungen am Zählerwert zwischen dem Zeitpunkt, an dem die ApplyPendingBalance Anweisung erstellt und verarbeitet wird:

    • expected_pending_balance_credit_counter: Der pending_balance_credit_counter Wert, wenn der Client die ApplyPendingBalance Anweisung erstellt
    • actual_pending_balance_credit_counter: Der pending_balance_credit_counter Wert im token account zum Zeitpunkt, als die ApplyPendingBalance Anweisung verarbeitet wird

Übereinstimmende erwartete/tatsächliche Zähler zeigen an, dass der decryptable_available_balance mit dem available_balance übereinstimmt.

Beim Abrufen des Zustands eines token account, um den decryptable_available_balance zu lesen, erfordern unterschiedliche erwartete/tatsächliche Zählerwerte, dass der Client aktuelle Einzahlungs-/Überweisungs- Anweisungen passend zur Zählerdifferenz nachschlägt, um den korrekten Kontostand zu berechnen.

Kontostand-Abstimmungsprozess

Wenn die erwarteten und tatsächlichen ausstehenden Kontozähler voneinander abweichen, folgen Sie diesen Schritten, um den decryptable_available_balance abzugleichen:

  1. Beginnen Sie mit dem decryptable_available_balance aus dem token account
  2. Rufen Sie die aktuellsten Transaktionen ab, einschließlich Einzahlungs- und Überweisungs- Anweisungen bis zur Zählerdifferenz (tatsächlich - erwartet):
    • Öffentliche Beträge aus Einzahlungs- Anweisungen addieren
    • Ziel-Chiffretext-Beträge aus Überweisungs- Anweisungen entschlüsseln und addieren

Erforderliche Anweisungen

Das Erstellen und Konfigurieren eines token account für vertrauliche Überweisungen verwendet die folgenden Anweisungen, die alle in eine einzige Transaktion passen:

  1. Token Account erstellen: Rufen Sie die AssociatedTokenAccountInstruction::Create-Anweisung des Associated Token Program auf, um den token account an seiner deterministischen Adresse zu erstellen.

  2. Kontospeicherplatz neu zuweisen: Rufen Sie die TokenInstruction::Reallocate-Anweisung des Token Extensions Program auf, um Speicherplatz für den ConfidentialTransferAccount-Zustand hinzuzufügen.

  3. Pubkey-Gültigkeitsbeweis verifizieren: Erstellen Sie ein Konten, das dem ZK ElGamal Proof-Programm gehört, und rufen Sie dann dessen VerifyPubkeyValidity-Anweisung auf, um den Beweis zu verifizieren und das verifizierte Ergebnis in diesem Kontext-Zustandskonten zu speichern.

  4. Vertrauliche Überweisungen konfigurieren: Rufen Sie die ConfidentialTransferInstruction::ConfigureAccount-Anweisung des Token Extensions Program auf, und referenzieren Sie dabei das Beweis-Kontext-Zustandskonten über ProofLocation::ContextStateAccount, um den ConfidentialTransferAccount-Zustand zu initialisieren.

Nur der Besitzer des Token-Kontos kann ein Token-Konto für vertrauliche Überweisungen konfigurieren.

Die ConfigureAccount-Anweisung erfordert eine clientseitige Generierung von Verschlüsselungsschlüsseln und einem Beweis, der nur vom Eigentümer des token account generiert werden kann.

Der pubkey-Gültigkeitsbeweis verifiziert, dass der öffentliche ElGamal-Schlüssel des Konten gültig ist. Er wird mit build_pubkey_validity_proof_data generiert, on-chain vom ZK ElGamal Proof-Programm in ein Kontext-Zustandskonten verifiziert und anschließend aus ConfigureAccount über ProofLocation::ContextStateAccount referenziert, sodass keine Beweisbytes in der Token- Anweisung selbst übertragen werden. Für Implementierungsdetails siehe:

Beispielcode

Der folgende Code erstellt ein associated token account und konfiguriert es für vertrauliche Übertragungen gegen einen bestehenden vertraulichen Mint.

Vertrauliche Übertragungen sind auf das ZK ElGamal Proof-Programm angewiesen, das auf Mainnet und Devnet aktiviert ist. Ein Standard-solana-test-validator aktiviert es nicht, aber ein mainnet-forkendes lokales validator wie Surfpool schon. Führen Sie das Beispiel gegen eines davon aus (der Code verwendet Devnet) mit einem finanzierten Zahler, und ersetzen Sie den Mint-Platzhalter durch einen Mint, der gemäß Einen Mint erstellen erstellt wurde.

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}`
);

Die TypeScript-Hilfsfunktionen befinden sich im Unterpfad @solana-program/token-2022/confidential und bauen auf @solana/zk-sdk für die Verschlüsselungsprimitive auf. owner und client stammen aus Ihrem @solana/kit-Setup; der zurückgegebene Anweisungen-Plan wird mit der Anweisungen-Plan-Unterstützung von @solana/kit gesendet, die die Arbeit auf mehrere Transaktionen aufteilt, wenn die Beweise für eine einzelne zu groß sind.

Is this page helpful?

Inhaltsverzeichnis

Seite bearbeiten
© 2026 Solana Foundation. Alle Rechte vorbehalten.