Rust
Pinocchio Quantum Vault

Pinocchio Quantum Vault

9 Graduates

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:

rust
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 })
    }
}

Việc xác minh account được xử lý bởi runtime. Nếu các account không đáp ứng yêu cầu (khả năng có thể sửa đổi), instruction sẽ tự động thất bại.

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 vault

  • amount: 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:

rust
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:

  1. Đó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 split và khóa công khai của account refund

  2. Xá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.

  3. 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.

  4. 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 account refund và account vault được đóng.

Vì chương trình sở hữu account vault, bạn có thể chuyển lamports trực tiếp mà không cần gọi Cross-Program Invocation (CPI).

Mã trông như thế này:

rust
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()
    }
}

Việc tái tạo thông điệp ngăn chặn các cuộc tấn công phát lại chữ ký, nơi các tác nhân độc hại thay thế các tài khoản người nhận khác nhau trong khi MEV-ing các chữ ký hợp lệ được ghi lại từ mempool.

Next PageĐóng Vault
HOẶC BỎ QUA ĐỂ LÀM THỬ THÁCH
Sẵn sàng làm thử thách?
Nội dung
Xem mã nguồn
Blueshift © 2025Commit: e573eab