Wypłata tokenów

Jak wypłacić tokeny z poufnego dostępnego salda

Aby wypłacić tokeny z poufnego dostępnego salda na saldo publiczne:

  1. Utwórz dwa dowody po stronie klienta:

    Dowód równości (CiphertextCommitmentEqualityProofData): Weryfikuje, że szyfrogram pozostałego dostępnego salda po wypłacie odpowiada swojemu zobowiązaniu Pedersena, zapewniając poprawne obliczenie nowego dostępnego salda konta jako remaining_balance = current_balance - withdraw_amount.

    Dowód zakresu (BatchedRangeProofU64Data): Weryfikuje, że pozostałe dostępne saldo po wypłacie jest nieujemne i mieści się w określonym zakresie.

  2. Dla każdego dowodu:

    • Wywołaj program dowodów ZK ElGamal w celu weryfikacji danych dowodowych.
    • Zapisz metadane specyficzne dla dowodu w koncie "stanu kontekstu" dowodu, aby użyć ich w innych instrukcjach.
  3. Wywołaj instrukcję ConfidentialTransferInstruction::Withdraw podając dwa konta dowodowe.

  4. Zamknij konta dowodowe, aby odzyskać SOL użyty do ich utworzenia.

Poniższy diagram przedstawia kroki związane z wypłatą tokenów z poufnego dostępnego salda na saldo publiczne:

Withdraw Tokens

Wymagane instrukcje

Aby wypłacić tokeny z poufnego dostępnego salda na saldo publiczne, musisz:

  • Wygenerować dowód równości i dowód zakresu po stronie klienta
  • Wywołać program dowodów Zk ElGamal w celu weryfikacji dowodów i zainicjowania kont "stanu kontekstu"
  • Wywołać instrukcję ConfidentialTransferInstruction::Withdraw podając dwa konta dowodowe.
  • Zamknąć dwa konta dowodowe, aby odzyskać rent.

Poniższy przykład w języku Rust generuje dowody przy użyciu skrzynki spl-token-confidential-transfer-proof-generation, weryfikuje każdy z nich do konta stanu kontekstu za pośrednictwem programu ZK ElGamal Proof, odwołuje się do obu kont w instrukcji wypłaty i zamyka je po zakończeniu. Przykład w TypeScript korzysta z pomocnika getConfidentialWithdrawInstructionPlan z @solana-program/token-2022/confidential, który składa te same konta dowodowe, wypłatę i zamknięcie jako plan instrukcji wielu transakcji.

Przykładowy kod

Poniższy przykład wypłaca tokeny z poufnego dostępnego salda z powrotem do salda publicznego. Konto musi być już skonfigurowane do poufnych transferów i posiadać dostępne poufne saldo.

Poufne transfery zależą od programu ZK ElGamal Proof, który jest włączony w sieci mainnet i devnet. Standardowy solana-test-validator nie włącza go, ale lokalny validator z forkiem mainnet, taki jak Surfpool, już tak. Uruchom przykład na jednej z tych sieci (kod używa devnet) z zasilonym płatnikiem i zastąp symbole zastępcze swoim mint oraz token account.

Rust

const ZK_PROOF_PROGRAM_ID: Pubkey =
solana_pubkey::pubkey!("ZkE1Gama1Proof11111111111111111111111111111");
fn main() -> Result<()> {
let rpc_client = RpcClient::new_with_commitment(
String::from("https://api.devnet.solana.com"),
CommitmentConfig::confirmed(),
);
// Owner = fee payer = token account owner. The account must already be
// configured for confidential transfers with an available confidential
// balance to withdraw from.
let owner = load_keypair()?;
let amount: u64 = 100;
let decimals: u8 = 2;
// Setup: create an available confidential balance to withdraw.
let (mint, token_account) = setup_withdrawable_account(&rpc_client, &owner, amount, decimals)?;
// Derive the owner's keys and read the current confidential available balance.
let (elgamal_keypair, aes_key) = derive_confidential_keys(&owner, &token_account.to_bytes())
.map_err(|e| anyhow::anyhow!("derive confidential keys: {e}"))?;
let account_data = rpc_client.get_account(&token_account)?;
let account = StateWithExtensions::<TokenAccount>::unpack(&account_data.data)?;
let ct_extension = account.get_extension::<ConfidentialTransferAccount>()?;
// The ElGamal available-balance ciphertext is required to build the proof.
let available_balance: ElGamalCiphertext = ct_extension
.available_balance
.try_into()
.map_err(|e| anyhow::anyhow!("decode available balance: {e:?}"))?;
// Read the plaintext balance from the AES-encrypted decryptable balance.
// ElGamal's decrypt_u32 only recovers values up to 2^32 raw units, so it
// fails for realistic balances; the AES field has no such limit.
let decryptable_balance: AeCiphertext = ct_extension
.decryptable_available_balance
.try_into()
.map_err(|e| anyhow::anyhow!("decode decryptable balance: {e:?}"))?;
let current_available = decryptable_balance
.decrypt(&aes_key)
.context("decrypt available balance")?;
// Generate the equality and range proofs for the withdrawal.
let proof_data = withdraw_proof_data(&available_balance, current_available, amount, &elgamal_keypair)
.map_err(|e| anyhow::anyhow!("withdraw_proof_data: {e}"))?;
let new_available = current_available
.checked_sub(amount)
.context("insufficient confidential balance")?;
let new_decryptable: PodAeCiphertext = aes_key.encrypt(new_available).into();
// The owner is the context-state authority for both proof accounts.
let authority: Address = owner.pubkey().to_bytes().into();
// Equality proof context state account.
let equality_account = Keypair::new();
let equality_size = size_of::<ProofContextState<CiphertextCommitmentEqualityProofContext>>();
let equality_create_ix = system_instruction::create_account(
&owner.pubkey(),
&equality_account.pubkey(),
rpc_client.get_minimum_balance_for_rent_exemption(equality_size)?,
equality_size as u64,
&ZK_PROOF_PROGRAM_ID,
);
let equality_verify_ix = ProofInstruction::VerifyCiphertextCommitmentEquality
.encode_verify_proof(
Some(ContextStateInfo {
context_state_account: &Address::from(equality_account.pubkey().to_bytes()),
context_state_authority: &authority,
}),
&proof_data.equality_proof_data,
);
send_tx(&rpc_client, &[equality_create_ix], &[&owner, &equality_account])?;
send_tx(&rpc_client, &[equality_verify_ix], &[&owner])?;
// Range proof context state account.
let range_account = Keypair::new();
let range_size = size_of::<ProofContextState<BatchedRangeProofContext>>();
let range_create_ix = system_instruction::create_account(
&owner.pubkey(),
&range_account.pubkey(),
rpc_client.get_minimum_balance_for_rent_exemption(range_size)?,
range_size as u64,
&ZK_PROOF_PROGRAM_ID,
);
let range_verify_ix = ProofInstruction::VerifyBatchedRangeProofU64.encode_verify_proof(
Some(ContextStateInfo {
context_state_account: &Address::from(range_account.pubkey().to_bytes()),
context_state_authority: &authority,
}),
&proof_data.range_proof_data,
);
send_tx(&rpc_client, &[range_create_ix], &[&owner, &range_account])?;
send_tx(&rpc_client, &[range_verify_ix], &[&owner])?;
// Withdraw, referencing both pre-verified proof accounts.
let withdraw_ixs = withdraw(
&spl_token_2022::id(),
&token_account,
&mint,
amount,
decimals,
&new_decryptable,
&owner.pubkey(),
&[&owner.pubkey()],
ProofLocation::ContextStateAccount(&equality_account.pubkey()),
ProofLocation::ContextStateAccount(&range_account.pubkey()),
)?;
let blockhash = rpc_client.get_latest_blockhash()?;
let transaction =
Transaction::new_signed_with_payer(&withdraw_ixs, Some(&owner.pubkey()), &[&owner], blockhash);
let signature = rpc_client.send_and_confirm_transaction(&transaction)?;
// Close both proof accounts to reclaim their rent.
for account in [&equality_account, &range_account] {
let close_ix = close_context_state(
ContextStateInfo {
context_state_account: &Address::from(account.pubkey().to_bytes()),
context_state_authority: &authority,
},
&authority,
);
send_tx(&rpc_client, &[close_ix], &[&owner])?;
}
println!("Withdrew {amount} tokens to the public balance: {signature}");
Ok(())
}

Typescript

const client = await createClient()
.use(signerFromFile(join(homedir(), ".config/solana/id.json")))
.use(
solanaRpc({
rpcUrl: "https://api.devnet.solana.com",
maxConcurrency: 1
})
);
// The Solana CLI default keypair, used as fee payer, mint authority, and
// token account owner.
const owner = client.payer;
const depositAmount = 100n;
const amount = 25n;
const decimals = 2;
// Setup: create a confidential account with an available confidential balance.
const mint = await createConfidentialMint(client, owner, decimals);
const token = await createConfidentialTokenAccount(client, owner, mint);
await mintPublicTokens(client, owner, mint, token, depositAmount);
await depositTokens(client, owner, mint, token, depositAmount, decimals);
await applyPendingBalance(client, owner, mint, token);
// Derive the owner's recoverable ElGamal and AES keys, bound to (owner, mint).
const { elgamalKeypair, aesKey } = await deriveConfidentialKeys(owner, mint);
const tokenAccount = (await fetchToken(client.rpc, token)).data;
// Builds the equality + range proof accounts, the withdraw, and the closes as a
// multi-transaction instruction plan.
const plan = await getConfidentialWithdrawInstructionPlan({
rpc: client.rpc,
payer: owner,
authority: owner,
token,
mint,
tokenAccount,
amount,
decimals,
elgamalKeypair,
aesKey
});
const result = await client.sendTransactions(plan);
const summary = summarizeTransactionPlanResult(result);
const signature =
summary.successfulTransactions[summary.successfulTransactions.length - 1]
.context.signature;
console.log(`Withdrew ${amount} tokens to the public balance: ${signature}`);

Is this page helpful?

Spis treści

Edytuj stronę