Tạo một Token Account

Cách tạo token account với Confidential Balances

Confidential Balances cho phép chuyển token riêng tư bằng cách thêm trạng thái bổ sung vào token account. Phần này giải thích cách tạo token account với extension cần thiết được bật.

Sơ đồ sau đây mô tả các bước liên quan đến việc tạo token account với Confidential Balances được bật:

Create Token Account with Confidential Balances

Trạng thái Token Account của Confidential Balances

Extension này thêm trạng thái ConfidentialTransferAccount vào token account:

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 chứa một số trường để quản lý Confidential Balances:

  • approved: Trạng thái phê duyệt của tài khoản đối với Confidential Balances. Nếu cấu hình auto_approve_new_accounts của mint account được đặt thành true, tất cả token account sẽ tự động được phê duyệt cho Confidential Balances.

  • elgamal_pubkey: Khóa công khai ElGamal được sử dụng để mã hóa số dư và số tiền chuyển.

  • pending_balance_lo: 16 bit thấp được mã hóa của số dư đang chờ xử lý. Số dư được chia thành phần cao và phần thấp để giải mã hiệu quả.

  • pending_balance_hi: 48 bit cao được mã hóa của số dư đang chờ xử lý. Số dư được chia thành phần cao và phần thấp để giải mã hiệu quả.

  • available_balance: Số dư được mã hóa khả dụng để chuyển.

  • decryptable_available_balance: Số dư khả dụng được mã hóa bằng khóa Advanced Encryption Standard (AES) để giải mã hiệu quả bởi chủ sở hữu tài khoản.

  • allow_confidential_credits: Nếu đúng, cho phép các lần chuyển bảo mật đến.

  • allow_non_confidential_credits: Nếu đúng, cho phép các lần chuyển không bảo mật đến.

  • pending_balance_credit_counter: Đếm số lần ghi có số dư đang chờ xử lý đến từ các lệnh nạp tiền và chuyển tiền.

  • maximum_pending_balance_credit_counter: Giới hạn số lần ghi có đang chờ xử lý trước khi yêu cầu lệnh ApplyPendingBalance để chuyển đổi số dư đang chờ xử lý sang số dư khả dụng.

  • expected_pending_balance_credit_counter: Giá trị pending_balance_credit_counter được cung cấp bởi client thông qua instruction data lần cuối cùng lệnh ApplyPendingBalance được xử lý.

  • actual_pending_balance_credit_counter: Giá trị pending_balance_credit_counter trên token account tại thời điểm lệnh ApplyPendingBalance cuối cùng được xử lý.

Số dư chờ xử lý và số dư khả dụng

Số dư bảo mật được tách thành số dư chờ xử lý và số dư khả dụng để ngăn chặn các cuộc tấn công DoS. Nếu không có sự tách biệt này, kẻ tấn công có thể liên tục gửi token đến một token account, chặn khả năng chuyển token của chủ sở hữu token account. Chủ sở hữu token account sẽ không thể chuyển token vì số dư được mã hóa sẽ thay đổi trong khoảng thời gian từ khi giao dịch được gửi đến khi được xử lý, dẫn đến giao dịch thất bại.

Tất cả các khoản tiền gửi và số tiền chuyển ban đầu đều được cộng vào số dư chờ xử lý. Chủ sở hữu token account phải sử dụng lệnh ApplyPendingBalance để chuyển đổi số dư chờ xử lý thành số dư khả dụng. Các khoản chuyển tiền đến hoặc tiền gửi không ảnh hưởng đến số dư khả dụng của token account.

Tách số dư chờ xử lý thành mức cao/thấp

Số dư chờ xử lý bảo mật được tách thành pending_balance_lopending_balance_hi vì giải mã ElGamal đòi hỏi nhiều tính toán hơn đối với các số lớn hơn. Bạn có thể tìm thấy cách triển khai phép tính số học trên bản mã tại đây, được sử dụng trong lệnh ApplyPendingBalance tại đây.

Bộ đếm tín dụng số dư chờ xử lý

Khi gọi lệnh ApplyPendingBalance để chuyển đổi số dư chờ xử lý thành số dư khả dụng:

  1. Client tra cứu số dư chờ xử lý và số dư khả dụng hiện tại, mã hóa tổng số, và cung cấp một decryptable_available_balance được mã hóa bằng khóa AES của chủ sở hữu token account.

  2. Bộ đếm tín dụng chờ xử lý dự kiến và thực tế theo dõi các thay đổi đối với giá trị bộ đếm trong khoảng thời gian từ khi lệnh ApplyPendingBalance được tạo đến khi được xử lý:

    • expected_pending_balance_credit_counter: Giá trị pending_balance_credit_counter khi client tạo lệnh ApplyPendingBalance
    • actual_pending_balance_credit_counter: Giá trị pending_balance_credit_counter trên token account tại thời điểm lệnh ApplyPendingBalance được xử lý

Bộ đếm expected/actual khớp nhau cho thấy decryptable_available_balance khớp với available_balance.

Khi lấy trạng thái của token account để đọc decryptable_available_balance, nếu các giá trị bộ đếm expected/actual khác nhau, client cần tra cứu các lệnh deposit/transfer gần đây khớp với sự chênh lệch bộ đếm để tính toán số dư chính xác.

Quy Trình Đối Soát Số Dư

Khi bộ đếm pending balance expected và actual khác nhau, hãy làm theo các bước sau để đối soát decryptable_available_balance:

  1. Bắt đầu với decryptable_available_balance từ token account
  2. Lấy các giao dịch gần nhất bao gồm các lệnh deposit và transfer tính đến sự chênh lệch bộ đếm (actual - expected):
    • Cộng thêm các số tiền công khai từ các lệnh deposit
    • Giải mã và cộng thêm các số tiền ciphertext đích từ các lệnh transfer

Các Lệnh Bắt Buộc

Việc tạo và cấu hình token account cho Confidential Balances sử dụng các lệnh sau, tất cả đều nằm gọn trong một giao dịch duy nhất:

  1. Tạo Token Account: Gọi lệnh AssociatedTokenAccountInstruction::Create của Associated Token Program để tạo token account tại địa chỉ xác định của nó.

  2. Cấp Phát Lại Không Gian Tài Khoản: Gọi lệnh TokenInstruction::Reallocate của Token Extensions Program để thêm không gian cho trạng thái ConfidentialTransferAccount.

  3. Xác Minh Bằng Chứng Hợp Lệ Pubkey: Tạo một tài khoản thuộc sở hữu của chương trình ZK ElGamal Proof, sau đó gọi lệnh VerifyPubkeyValidity của nó để xác minh bằng chứng và lưu kết quả đã xác minh vào tài khoản context state đó.

  4. Cấu hình Confidential Balances: Gọi lệnh ConfidentialTransferInstruction::ConfigureAccount của Token Extensions Program, tham chiếu đến tài khoản trạng thái context proof qua ProofLocation::ContextStateAccount, để khởi tạo trạng thái ConfidentialTransferAccount.

Chỉ chủ sở hữu token account mới có thể bật Confidential Balances trên token account.

Lệnh ConfigureAccount yêu cầu tạo khóa mã hóa và bằng chứng ở phía client, và chỉ có chủ sở hữu token account mới có thể tạo được.

Bằng chứng hợp lệ pubkey xác minh rằng khóa công khai ElGamal của tài khoản là hợp lệ. Nó được tạo bằng build_pubkey_validity_proof_data, được xác minh trên chuỗi bởi chương trình ZK ElGamal Proof vào một tài khoản trạng thái context, và sau đó được tham chiếu từ ConfigureAccount qua ProofLocation::ContextStateAccount, do đó không có byte bằng chứng nào được truyền trong bản thân lệnh token. Để biết chi tiết triển khai, xem:

Mã Ví Dụ

Đoạn mã sau tạo một associated token account và cấu hình nó để sử dụng Confidential Balances với một mint tương thích hiện có.

Confidential Balances phụ thuộc vào chương trình ZK ElGamal Proof, được bật trên mainnet và devnet. Một solana-test-validator thông thường không bật tính năng này, nhưng một validator local có khả năng fork mainnet như Surfpool thì có. Chạy ví dụ với một trong số đó (mã sử dụng devnet) với một payer đã được nạp tiền, và thay thế placeholder mint bằng một mint được tạo theo hướng dẫn Create a 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!("Enabled Confidential Balances on token account {token_account}: {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(
`Enabled Confidential Balances on token account ${tokenAccount}: ${result.context.signature}`
);

Các tiện ích TypeScript nằm trong subpath @solana-program/token-2022/confidential và được xây dựng dựa trên @solana/zk-sdk cho các nguyên thủy mã hóa. ownerclient đến từ thiết lập @solana/kit của bạn; kế hoạch lệnh được trả về được gửi với hỗ trợ kế hoạch lệnh của @solana/kit, phân chia công việc qua nhiều giao dịch khi các bằng chứng quá lớn cho một giao dịch.

Is this page helpful?

Mục lục

Chỉnh sửa trang