Chia Vault
Instruction split cho phép rút tiền một phần từ các vault kháng lượng tử bằng cách phân phối lamports qua nhiều account. Điều này rất cần thiết cho các lược đồ chữ ký Winternitz, chỉ có thể được sử dụng một lần một cách an toàn.
Khác với mật mã truyền thống, chữ ký Winternitz trở nên dễ bị tổn thương sau một lần sử dụng. Instruction split cho phép bạn:
Phân phối thanh toán qua nhiều người nhận trong một giao dịch
Chuyển số tiền còn lại vào một vault kháng lượng tử mới với cặp khóa mới (bằng cách truyền vào một vault kháng lượng tử làm account
refund)
Các account cần thiết
Instruction yêu cầu 3 account:
vault: Vault nguồn để chứa tất cả lamports (phải là mutable)split: Account nhận cho số tiền đã chỉ định (phải là mutable)refund: Account nhận cho số dư còn lại của vault (phải là mutable)
Account refund thường là một vault kháng lượng tử mới với cặp khóa Winternitz mới, đảm bảo an toàn liên tục cho các quỹ còn lại.
Mã sẽ trông như thế này:
pub struct SplitVaultAccounts<'a> {
pub vault: &'a AccountInfo,
pub split: &'a AccountInfo,
pub refund: &'a AccountInfo,
}
impl<'a> TryFrom<&'a [AccountInfo]> for SplitVaultAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
let [vault, split, refund] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
Ok(Self { vault, split, refund })
}
}Dữ liệu cho instruction
Ba phần dữ liệu là cần thiết:
signature: chữ ký Winternitz chứng minh quyền sở hữu cặp khóa của vaultamount: số lượng lamports để chuyển đến tài khoản nhận (8 byte, little-endian)bump: số bump để dẫn xuất ra PDA nhằm tối ưu hóa (1 byte)
Mã sẽ trông như thế này:
pub struct SplitVaultInstructionData {
pub signature: WinternitzSignature,
pub amount: [u8; 8],
pub bump: [u8; 1],
}
impl<'a> TryFrom<&'a [u8]> for SplitVaultInstructionData {
type Error = ProgramError;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
if data.len() != core::mem::size_of::<SplitVaultInstructionData>() {
return Err(ProgramError::InvalidInstructionData);
}
let mut signature_array = MaybeUninit::<[u8; 896]>::uninit();
unsafe {
core::ptr::copy_nonoverlapping(data[0..896].as_ptr(), signature_array.as_mut_ptr() as *mut u8, 896);
}
Ok(Self {
signature: WinternitzSignature::from(unsafe { signature_array.assume_init() }),
bump: data[896..897].try_into().map_err(|_| ProgramError::InvalidInstructionData)?,
amount: data[897..905].try_into().map_err(|_| ProgramError::InvalidInstructionData)?,
})
}
}Logic của instruction
Quá trình xác minh tuân theo các bước sau:
Đóng gói thông điệp: Một thông điệp 72 byte được xây dựng chứa: Số lượng để phân chia, khóa công khai của account
splitvà khóa công khai của accountrefundXác minh chữ ký: Chữ ký Winternitz được sử dụng để khôi phục lại mã băm khóa công khai gốc, sau đó được so sánh với các hạt giống dẫn xuất PDA của vault.
Xác thực PDA: Một kiểm tra tương đương nhanh đảm bảo rằng mã băm đã khôi phục khớp với PDA của vault, chứng minh rằng người ký sở hữu vault.
Phân phối quỹ nếu xác minh thành công: Số tiền đã chỉ định được chuyển đến account
split, số dư còn lại được chuyển đến accountrefundvà accountvaultđược đóng.
Mã trông như thế này:
pub struct SplitVault<'a> {
pub accounts: SplitVaultAccounts<'a>,
pub instruction_data: SplitVaultInstructionData,
}
impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for SplitVault<'a> {
type Error = ProgramError;
fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
let instruction_data = SplitVaultInstructionData::try_from(data)?;
let accounts = SplitVaultAccounts::try_from(accounts)?;
Ok(Self { accounts, instruction_data })
}
}
impl<'a> SplitVault<'a> {
pub const DISCRIMINATOR: &'a u8 = &1;
pub fn process(&self) -> ProgramResult {
// Assemble our Split message
let mut message = [0u8; 72];
message[0..8].clone_from_slice(&self.instruction_data.amount);
message[8..40].clone_from_slice(self.accounts.split.key());
message[40..].clone_from_slice(self.accounts.refund.key());
// Recover our pubkey hash from the signature
let hash = self.instruction_data.signature.recover_pubkey(&message).merklize();
// Fast PDA equivalence check
if solana_nostd_sha256::hashv(&[
hash.as_ref(),
self.instruction_data.bump.as_ref(),
crate::ID.as_ref(),
b"ProgramDerivedAddress",
])
.ne(self.accounts.vault.key())
{
return Err(ProgramError::MissingRequiredSignature);
}
// Close Vault, send split balance to Split account, refund remainder to Refund account
*self.accounts.split.try_borrow_mut_lamports()? += u64::from_le_bytes(self.instruction_data.amount);
*self.accounts.refund.try_borrow_mut_lamports()? += self.accounts.vault.lamports().saturating_sub(u64::from_le_bytes(self.instruction_data.amount));
self.accounts.vault.close()
}
}