Rust
Pinocchio Quantum Vault

Pinocchio Quantum Vault

9 Graduates

Split Vault

Die split Anweisung ermöglicht teilweise Abhebungen aus quantenresistenten Tresoren durch Verteilung von Lamports auf mehrere Konten. Dies ist für Winternitz-Signaturverfahren unerlässlich, die aus Sicherheitsgründen nur einmal verwendet werden können.

Im Gegensatz zur traditionellen Kryptographie werden Winternitz-Signaturen nach einmaliger Verwendung angreifbar. Die Split-Anweisung ermöglicht es dir:

  • Zahlungen in einer Transaktion auf mehrere Empfänger zu verteilen

  • Verbleibende Mittel in einen neuen Quantum-Tresor mit frischem Schlüsselpaar zu übertragen (indem ein Quantum-Tresor als refund Konto übergeben wird)

Required Accounts

Die Anweisung erfordert drei Konten:

  • vault: Quell-Tresor mit gespeicherten Lamports (muss veränderbar sein)

  • split: Empfängerkonto für den angegebenen Betrag (muss veränderbar sein)

  • refund: Empfängerkonto für das verbleibende Tresorguthaben (muss veränderbar sein)

Das refund Konto ist oft ein neuer Quantum-Tresor mit einem frischen Winternitz-Schlüsselpaar, um die kontinuierliche Sicherheit der verbleibenden Mittel zu gewährleisten.

So sieht es im Code aus:

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

Die Kontovalidierung wird von der Laufzeitumgebung übernommen. Wenn Konten die Anforderungen (Veränderbarkeit) nicht erfüllen, schlägt die Anweisung automatisch fehl.

Instruction Data

Drei Datenelemente sind erforderlich:

  • signature: Winternitz-Signatur, die den Besitz des Schlüsselpaars des Tresors nachweist

  • amount: Zu übertragende Lamports auf das Split-Konto (8 Bytes, Little-Endian)

  • bump: PDA-Ableitungs-Bump zur Optimierung (1 Byte)

So sieht es im Code aus:

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)?,
        })
    }
}

Instruction Logic

Der Verifizierungsprozess folgt diesen Schritten:

  1. Nachrichtenzusammenstellung: Eine 72-Byte-Nachricht wird erstellt, die Folgendes enthält: Zu splittender Betrag, den öffentlichen Schlüssel des split Kontos und den öffentlichen Schlüssel des refund Kontos

  2. Signaturüberprüfung: Die Winternitz-Signatur wird verwendet, um den ursprünglichen Public-Key-Hash wiederherzustellen, der dann mit den PDA-Ableitungssamen des Tresors verglichen wird.

  3. PDA-Validierung: Eine schnelle Äquivalenzprüfung stellt sicher, dass der wiederhergestellte Hash mit dem PDA des Tresors übereinstimmt und beweist damit, dass der Unterzeichner Eigentümer des Tresors ist.

  4. Fondsverteilung bei erfolgreicher Validierung: Der angegebene Betrag wird auf das split Konto überwiesen, das verbleibende Guthaben wird auf das refund Konto überwiesen und das vault Konto wird geschlossen.

Da das Programm Eigentümer des vault Kontos ist, können Sie Lamports direkt übertragen, ohne einen Cross-Program Invocation (CPI) aufrufen zu müssen.

So sieht es im Code aus:

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

Die Rekonstruktion der Nachricht verhindert Signatur-Replay-Angriffe, bei denen böswillige Akteure verschiedene Empfängerkonten austauschen, während sie gültige Signaturen aus dem Mempool MEV-en.

Next PageVault schließen
ODER DIREKT ZUR HERAUSFORDERUNG
Bereit für die Herausforderung?
Blueshift © 2025Commit: e573eab