# SNP-004: Checksum Verification

## Summary

Snapshot integrity is protected by SHA-256 checksums. During export, the checksum is computed over all bytes written (manifest + length-prefixed blocks) before the checksum itself is appended. During import, the checksum is computed incrementally over all bytes read, then verified against the trailing 32-byte checksum. A mismatch results in rejection with `BlockStoreError::Serialization`.

## Specification

```rust
use chia_sha2::Sha256;

/// Checksum computation during snapshot export.
/// The hasher accumulates all bytes written to the stream
/// before the trailing checksum.
fn export_checksum_flow(writer: &mut impl Write) -> Result<Bytes32> {
    let mut hasher = Sha256::new();

    // --- Manifest bytes ---
    let manifest_bytes = bincode::serialize(&manifest)?;
    writer.write_all(&manifest_bytes)?;
    hasher.update(&manifest_bytes);

    // --- Block data (for each block) ---
    for block_compressed in blocks {
        let len_bytes = (block_compressed.len() as u32).to_le_bytes();
        writer.write_all(&len_bytes)?;
        hasher.update(&len_bytes);

        writer.write_all(&block_compressed)?;
        hasher.update(&block_compressed);
    }

    // --- Finalize and append checksum ---
    let checksum: [u8; 32] = hasher.finalize();
    writer.write_all(&checksum)?;
    // Note: the checksum bytes themselves are NOT included in the hash

    Ok(Bytes32::from(checksum))
}

/// Checksum verification during snapshot import.
/// The hasher accumulates all bytes read from the stream,
/// then verifies against the trailing 32-byte checksum.
fn import_checksum_flow(reader: &mut impl Read) -> Result<()> {
    let mut hasher = Sha256::new();

    // --- Read manifest (hashed) ---
    let manifest: SnapshotManifest = bincode::deserialize_from(&mut *reader)?;
    let manifest_bytes = bincode::serialize(&manifest)?;
    hasher.update(&manifest_bytes);

    // --- Read blocks (hashed) ---
    for _ in 0..manifest.block_count {
        let mut len_bytes = [0u8; 4];
        reader.read_exact(&mut len_bytes)?;
        hasher.update(&len_bytes);

        let block_len = u32::from_le_bytes(len_bytes) as usize;
        let mut block_data = vec![0u8; block_len];
        reader.read_exact(&mut block_data)?;
        hasher.update(&block_data);
    }

    // --- Read and verify trailing checksum ---
    let mut expected = [0u8; 32];
    reader.read_exact(&mut expected)?;

    let computed: [u8; 32] = hasher.finalize();

    if computed != expected {
        return Err(BlockStoreError::Serialization(
            "snapshot checksum mismatch".to_string()
        ));
    }

    Ok(())
}
```

## Acceptance Criteria

1. Export MUST compute SHA-256 using `chia-sha2::Sha256` (not a different SHA-256 implementation).
2. The hash MUST cover all bytes written before the checksum: manifest bytes + all (length-prefix + compressed-block) pairs.
3. The trailing checksum bytes themselves MUST NOT be included in the hash input.
4. The checksum MUST be exactly 32 bytes, appended as the final bytes of the snapshot stream.
5. Import MUST incrementally compute SHA-256 over all bytes read before the trailing checksum.
6. Import MUST reject with `BlockStoreError::Serialization` when the computed checksum does not match the trailing checksum.
7. A valid, unmodified snapshot MUST pass checksum verification.
8. Any single-byte modification to the snapshot data (excluding the checksum itself) MUST cause verification to fail.

## Implementation Notes

- `chia-sha2::Sha256` is used for consistency with other Chia ecosystem components; do not substitute `sha2::Sha256` or other implementations.
- The incremental hashing approach on import keeps memory usage constant regardless of snapshot size.
- The checksum covers the manifest bytes as well as block data, protecting against manifest tampering.
- On export, the manifest's `checksum` field contains a placeholder when serialized into the stream; the authoritative checksum is the trailing 32 bytes. This means the hash of the manifest includes the placeholder value, which is fine because the same placeholder is reproduced during import.
- For performance, the hasher's `update` calls should align with I/O buffer boundaries where possible.

## Test Plan

- **Integration: valid snapshot passes** -- Export a snapshot, import it, verify checksum passes (no error).
- **Integration: single-byte corruption** -- Export a snapshot, flip one byte in the middle of the block data, attempt import, verify `BlockStoreError::Serialization` with checksum mismatch.
- **Integration: manifest corruption** -- Export a snapshot, modify a byte in the manifest region, attempt import, verify rejection.
- **Integration: truncated snapshot** -- Export a snapshot, truncate the last 16 bytes (partial checksum), attempt import, verify I/O error.
- **Unit: checksum determinism** -- Export the same block range twice, verify both checksums are identical.
- **Unit: chia-sha2 consistency** -- Manually compute SHA-256 of exported bytes (minus trailing 32), verify it matches the appended checksum.
- **Integration: empty block range** -- Export a single-block snapshot (start == end), verify checksum covers manifest + one block.

## Expected Test Files

- `tests/snp_004_tests.rs`
