Rust
Pinocchio Quantum Vault

Pinocchio Quantum Vault

9 Graduates

Tresor schließen

Die close Anweisung führt eine vollständige Abhebung aus quantenresistenten Tresoren durch und überträgt alle Lamports an ein einzelnes Empfängerkonto.

Im Gegensatz zur split Anweisung bietet dies einen einfacheren Mechanismus für vollständige Abhebungen, wenn keine Fondsverteilung erforderlich ist.

Erforderliche Konten

Die Anweisung erfordert drei Konten:

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

  • refund: Empfängerkonto für das Lamports-Guthaben des Tresors (muss veränderbar sein)

So sieht es im Code aus:

rust
pub struct CloseVaultAccounts<'a> {
    pub vault: &'a AccountInfo,
    pub refund: &'a AccountInfo,
}

impl<'a> TryFrom<&'a [AccountInfo]> for CloseVaultAccounts<'a> {
    type Error = ProgramError;

    fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
        let [vault, refund] = accounts else {
            return Err(ProgramError::NotEnoughAccountKeys);
        };

        Ok(Self { vault, refund })
    }
}

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

Anweisungsdaten

Zwei Datenstücke sind erforderlich:

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

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

So sieht es im Code aus:

rust
pub struct CloseVaultInstructionData {
    pub signature: WinternitzSignature,
    pub bump: [u8; 1],
}

impl<'a> TryFrom<&'a [u8]> for CloseVaultInstructionData {
    type Error = ProgramError;

    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
        if data.len() != core::mem::size_of::<CloseVaultInstructionData>() {
            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)?,
        })
    }
}

Anweisungslogik

Der Verifizierungsprozess folgt diesen Schritten:

  1. Nachrichtenzusammenstellung: Eine 32-Byte-Nachricht wird erstellt, die den öffentlichen Schlüssel des refund Kontos enthält

  2. Signaturverifizierung: Die Winternitz-Signatur wird verwendet, um den ursprünglichen Hash des öffentlichen Schlüssels wiederherzustellen, der dann mit den PDA-Ableitungs-Seeds 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, dass der Unterzeichner der Besitzer des Tresors ist.

  4. Fondsverteilung bei erfolgreicher Validierung: Das gesamte Guthaben wird auf das refund Konto übertragen 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 CloseVault<'a> {
    pub accounts: CloseVaultAccounts<'a>,
    pub instruction_data: CloseVaultInstructionData,
}

impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for CloseVault<'a> {
    type Error = ProgramError;

    fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
        let instruction_data = CloseVaultInstructionData::try_from(data)?;
        let accounts = CloseVaultAccounts::try_from(accounts)?;

        Ok(Self { accounts, instruction_data })
    }
}

impl<'a> CloseVault<'a> {
    pub const DISCRIMINATOR: &'a u8 = &2;
 
    pub fn process(&self) -> ProgramResult {
        // Recover our pubkey hash from the signature
        let hash = self.instruction_data.signature.recover_pubkey(self.accounts.refund.key()).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 and refund balance to Refund account
        *self.accounts.refund.try_borrow_mut_lamports()? += self.accounts.vault.lamports();
        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 für MEV-Zwecke nutzen.

Bereit für die Herausforderung?
Blueshift © 2025Commit: e573eab