Jak utworzyć token account z rozszerzeniem Confidential Transfer
Rozszerzenie Confidential Transfer umożliwia prywatne przelewy tokenów poprzez dodanie dodatkowego stanu do token account. W tej sekcji wyjaśniono, jak utworzyć token account z włączonym tym rozszerzeniem.
Poniższy diagram przedstawia kroki związane z tworzeniem token account z rozszerzeniem Confidential Transfer:
Stan Token Account z rozszerzeniem Confidential Transfer
Rozszerzenie dodaje stan ConfidentialTransferAccount do token account:
#[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,}
ConfidentialTransferAccount zawiera kilka pól do zarządzania poufnymi
przelewami:
-
approved: Status zatwierdzenia konta dla poufnych przelewów. Jeśli konfiguracja
auto_approve_new_accountsmint account jest ustawiona jakotrue, wszystkie token accounts są automatycznie zatwierdzone do poufnych przelewów. -
elgamal_pubkey: Klucz publiczny ElGamal używany do szyfrowania sald i kwot przelewów.
-
pending_balance_lo: Zaszyfrowane młodsze 16 bitów oczekującego salda. Saldo jest podzielone na część wysoką i niską dla wydajnego odszyfrowywania.
-
pending_balance_hi: Zaszyfrowane starsze 48 bitów oczekującego salda. Saldo jest podzielone na część wysoką i niską dla wydajnego odszyfrowywania.
-
available_balance: Zaszyfrowane saldo dostępne do przelewów.
-
decryptable_available_balance: Dostępne saldo zaszyfrowane kluczem Advanced Encryption Standard (AES) umożliwiające wydajne odszyfrowanie przez właściciela konta.
-
allow_confidential_credits: Jeśli true, zezwala na przychodzące poufne przelewy.
-
allow_non_confidential_credits: Jeśli true, zezwala na przychodzące niepoufne przelewy.
-
pending_balance_credit_counter: Zlicza przychodzące uznania oczekującego salda z instrukcji depozytów i przelewów.
-
maximum_pending_balance_credit_counter: Limit liczby oczekujących uznań przed wymaganiem instrukcji
ApplyPendingBalancedo przekształcenia oczekującego salda w dostępne saldo. -
expected_pending_balance_credit_counter: Wartość
pending_balance_credit_counterdostarczona przez klienta poprzez instruction data podczas ostatniego przetwarzania instrukcjiApplyPendingBalance. -
actual_pending_balance_credit_counter: Wartość
pending_balance_credit_counterna token account w momencie przetworzenia ostatniej instrukcjiApplyPendingBalance.
Saldo oczekujące a saldo dostępne
Poufne salda są podzielone na salda oczekujące i dostępne, aby zapobiec atakom DoS. Bez tego podziału atakujący mógłby wielokrotnie wysyłać tokeny na token account, blokując właścicielowi token account możliwość transferu tokenów. Właściciel token account nie byłby w stanie transferować tokenów, ponieważ zaszyfrowane saldo zmieniałoby się między momentem przesłania transakcji a jej przetworzeniem, co skutkowałoby niepowodzeniem transakcji.
Wszystkie depozyty i kwoty transferów są początkowo dodawane do salda
oczekującego. Właściciele token account muszą użyć instrukcji
ApplyPendingBalance, aby przekonwertować saldo oczekujące na saldo
dostępne. Przychodzące transfery lub depozyty nie wpływają na dostępne saldo
token account.
Podział salda oczekującego na wysokie/niskie
Poufne saldo oczekujące jest podzielone na pending_balance_lo i
pending_balance_hi, ponieważ deszyfrowanie ElGamal wymaga większej liczby
obliczeń dla większych liczb. Implementację arytmetyki na szyfrogramach można
znaleźć
tutaj,
która jest używana w instrukcji ApplyPendingBalance
tutaj.
Liczniki kredytów salda oczekującego
Podczas wywoływania instrukcji ApplyPendingBalance w celu
przekonwertowania salda oczekującego na saldo dostępne:
-
Klient pobiera aktualne salda oczekujące i dostępne, szyfruje ich sumę i dostarcza
decryptable_available_balancezaszyfrowane kluczem AES właściciela token account. -
Oczekiwane i rzeczywiste liczniki kredytów oczekujących śledzą zmiany wartości licznika między momentem utworzenia a przetworzeniem instrukcji
ApplyPendingBalance:expected_pending_balance_credit_counter: Wartośćpending_balance_credit_counterw momencie, gdy klient tworzy instrukcjęApplyPendingBalanceactual_pending_balance_credit_counter: Wartośćpending_balance_credit_counterna token account w momencie przetworzenia instrukcjiApplyPendingBalance
Zgodność oczekiwanych/rzeczywistych liczników wskazuje, że
decryptable_available_balance pasuje do available_balance.
Podczas pobierania stanu token account w celu odczytania
decryptable_available_balance, różne wartości oczekiwanych/rzeczywistych
liczników wymagają od klienta wyszukania ostatnich instrukcji
depozytów/transferów pasujących do różnicy liczników w celu obliczenia
poprawnego salda.
Proces uzgadniania salda
Gdy oczekiwane i rzeczywiste liczniki oczekującego salda różnią się, wykonaj
następujące kroki, aby uzgodnić decryptable_available_balance:
- Zacznij od
decryptable_available_balancez token account - Pobierz najnowsze transakcje zawierające instrukcje depozytów i transferów do
wartości różnicy liczników (rzeczywisty - oczekiwany):
- Dodaj publiczne kwoty z instrukcji depozytów
- Odszyfruj i dodaj kwoty docelowego szyfrogramu z instrukcji transferów
Wymagane instrukcje
Tworzenie i konfigurowanie token account do poufnych transferów wykorzystuje następujące instrukcje, które wszystkie mieszczą się w jednej transakcji:
-
Utwórz token account: Wywołaj instrukcję
AssociatedTokenAccountInstruction::CreateAssociated Token Program, aby utworzyć token account pod jego deterministycznym adresem. -
Przydziel przestrzeń dla konta: Wywołaj instrukcję
TokenInstruction::ReallocateToken Extensions Program, aby dodać przestrzeń dla stanuConfidentialTransferAccount. -
Zweryfikuj dowód poprawności pubkey: Utwórz konto należące do programu ZK ElGamal Proof, a następnie wywołaj jego instrukcję
VerifyPubkeyValidityw celu weryfikacji dowodu i zapisania zweryfikowanego wyniku na tym koncie stanu kontekstu. -
Skonfiguruj poufne transfery: Wywołaj instrukcję ConfidentialTransferInstruction::ConfigureAccount Token Extensions Program, odwołując się do konta stanu kontekstu dowodu poprzez
ProofLocation::ContextStateAccount, aby zainicjować stanConfidentialTransferAccount.
Tylko właściciel token account może skonfigurować token account do confidential transfers.
Instrukcja ConfigureAccount wymaga po stronie klienta wygenerowania kluczy
szyfrowania oraz dowodu, który może zostać wygenerowany wyłącznie przez
właściciela token account.
Dowód poprawności pubkey weryfikuje, że klucz publiczny ElGamal konta jest
poprawny. Generowany jest przy użyciu build_pubkey_validity_proof_data,
weryfikowany onchain przez program ZK ElGamal Proof do konta stanu kontekstu, a
następnie przywoływany z ConfigureAccount poprzez
ProofLocation::ContextStateAccount, dzięki czemu żadne bajty dowodu nie są
przesyłane w samej instrukcji token. Szczegóły implementacji — patrz:
Przykładowy kod
Poniższy kod tworzy associated token account i konfiguruje go do poufnych transferów w oparciu o istniejący poufny mint.
Poufne transfery są uzależnione od programu ZK ElGamal Proof, który jest
włączony w sieci mainnet i devnet. Standardowy solana-test-validator nie
włącza go, natomiast lokalny validator forwardujący mainnet, taki jak
Surfpool, już tak. Uruchom przykład na jednej z tych
sieci (kod używa devnet) z zasilonym kontem płatnika i zastąp symbol zastępczy
mintu mintem utworzonym zgodnie z
Utwórz 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}`);
Pomocniki TypeScript znajdują się w podścieżce
@solana-program/token-2022/confidential i są oparte na @solana/zk-sdk dla
prymitywów szyfrowania. owner i client pochodzą z konfiguracji
@solana/kit; zwrócony plan instrukcji jest wysyłany z obsługą planów
instrukcji @solana/kit, która dzieli pracę na wiele transakcji, gdy dowody
są zbyt duże dla jednej.
Is this page helpful?