Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/heimdall

guests/verify-bundle/src/decode.rs

// SPDX-License-Identifier: AGPL-3.0-only
//! Decoding compressed chunks OUTSIDE the verifier (docs/GATLING-WASM.md).
//! Heimdall is a defensive sandbox.
//!
//! In the split guest the verifier never runs OpenZL itself: it hands the
//! compressed frames of the chunks it needs to a [`ChunkDecoder`], which in the
//! wasm host is the `decode-chunks` import (N isolated decoder instances run
//! by the host, see `crates/heimdall-wasm`). A decoder answers, per chunk, the
//! decoded bytes and their BLAKE3-256, or why the frame does not decode.
//!
//! The verifier trusts nothing a decoder says: the length must be the size
//! the index declares, the digest must be the index checksum, and, unless the
//! decoder's digests are confirmed by trusted code ([`ChunkDecoder::digests_confirmed`],
//! the wasm host hashes what a decoder instance returns itself), the verifier
//! hashes the bytes again. A decoder that answers falsely is refused with
//! [`crate::refusal::GuestCode::DECODER`]; a frame that does not decode is
//! Holger's container refusal, exactly as in the single-instance guest.

/// One compressed chunk handed to a decoder: the whole frame as the archive
/// holds it, and the decoded size the index declares.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkRef<'a> {
    pub frame: &'a [u8],
    pub size: u64,
    /// `false`: only the digest is wanted, `bytes` comes back empty.
    pub want_bytes: bool,
}

/// What one chunk decoded to.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Decoded {
    /// BLAKE3-256 of the decoded bytes (32 bytes from an honest decoder).
    pub digest: Vec<u8>,
    /// The decoded bytes, when they were asked for; else empty.
    pub bytes: Vec<u8>,
}

/// One answer per chunk: the decoded chunk, or why its frame does not decode
/// (the reason goes into the container refusal as is).
pub type ChunkResult = Result<Decoded, String>;

/// SHA-256 and BLAKE3-256 of one member (32 bytes each from honest code).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemberDigest {
    pub sha256: Vec<u8>,
    pub blake3: Vec<u8>,
}

/// Something that decodes compressed chunks for the verifier.
pub trait ChunkDecoder {
    /// Decode every chunk; exactly one answer per chunk, in order.
    fn decode_chunks(&mut self, chunks: &[ChunkRef<'_>]) -> Vec<ChunkResult>;

    /// `true` when trusted code has already checked that every returned
    /// `digest` is the BLAKE3-256 of the returned `bytes` (the wasm host does,
    /// natively and in parallel). `false`: the verifier hashes them again.
    fn digests_confirmed(&self) -> bool {
        false
    }

    /// SHA-256 and BLAKE3-256 of every member, one per member in order, from
    /// TRUSTED code (the wasm host, natively and in parallel), or `None` to
    /// let the verifier hash them itself. The verifier holds the answer to
    /// the signed manifest exactly as if it had hashed the bytes.
    fn member_digests(&mut self, members: &[&[u8]]) -> Option<Vec<MemberDigest>> {
        let _ = members;
        None
    }
}

/// Decode one chunk exactly as the single-instance guest does: the frame
/// header must declare the index's size BEFORE the decoder runs, and the
/// decoder must fill exactly that many bytes. The `Err` text is the reason the
/// single-instance guest gives for the same frame.
#[cfg(feature = "openzl")]
pub fn decode_one(c: &ChunkRef<'_>) -> ChunkResult {
    let size = usize::try_from(c.size).map_err(|_| {
        format!(
            "the index declares {} bytes, more than a decoder can address",
            c.size
        )
    })?;
    let mut out = Vec::new();
    out.try_reserve_exact(size)
        .map_err(|_| format!("the decoder cannot allocate the declared {size} bytes"))?;
    out.resize(size, 0);
    crate::zl::decode_exact(c.frame, &mut out)?;
    let digest = blake3::hash(&out).as_bytes().to_vec();
    if !c.want_bytes {
        out = Vec::new();
    }
    Ok(Decoded { digest, bytes: out })
}

/// The decoder in this process (feature `openzl`): the same C decoder the
/// single-instance guest links, called through the split path. Used natively
/// to prove the split path gives the single-instance verdicts.
#[cfg(feature = "openzl")]
#[derive(Debug, Default, Clone, Copy)]
pub struct LocalDecoder;

#[cfg(feature = "openzl")]
impl ChunkDecoder for LocalDecoder {
    fn decode_chunks(&mut self, chunks: &[ChunkRef<'_>]) -> Vec<ChunkResult> {
        chunks.iter().map(decode_one).collect()
    }

    fn digests_confirmed(&self) -> bool {
        true // computed right here, by this process, over these bytes
    }
}