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:
Zustandsverwaltung des Confidential Transfer Token Account
Die Erweiterung fügt den ConfidentialTransferAccount Zustand zum token account hinzu:
#[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,}
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_accountsdes mint account auftruegesetzt 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
ApplyPendingBalanceAnweisungen 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 derApplyPendingBalanceAnweisungen übermittelt wurde. -
actual_pending_balance_credit_counter: Der
pending_balance_credit_counterWert im token account zum Zeitpunkt, als die letzteApplyPendingBalanceAnweisung 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:
-
Der Client ruft den aktuellen ausstehenden und verfügbaren Saldo ab, verschlüsselt die Summe und stellt ein
decryptable_available_balancebereit, das mit dem AES-Schlüssel des token account-Inhabers verschlüsselt ist. -
Die erwarteten und tatsächlichen ausstehenden Kreditzähler verfolgen Änderungen am Zählerwert zwischen dem Zeitpunkt, an dem die
ApplyPendingBalanceAnweisung erstellt und verarbeitet wird:expected_pending_balance_credit_counter: Derpending_balance_credit_counterWert, wenn der Client dieApplyPendingBalanceAnweisung erstelltactual_pending_balance_credit_counter: Derpending_balance_credit_counterWert im token account zum Zeitpunkt, als dieApplyPendingBalanceAnweisung 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:
- Beginnen Sie mit dem
decryptable_available_balanceaus dem token account - 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:
-
Token Account erstellen: Rufen Sie die
AssociatedTokenAccountInstruction::Create-Anweisung des Associated Token Program auf, um den token account an seiner deterministischen Adresse zu erstellen. -
Kontospeicherplatz neu zuweisen: Rufen Sie die
TokenInstruction::Reallocate-Anweisung des Token Extensions Program auf, um Speicherplatz für denConfidentialTransferAccount-Zustand hinzuzufügen. -
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. -
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 denConfidentialTransferAccount-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?