# v0.4.0 — Hashing: BLAKE3, SHA-256, SHA-512

**Release date:** 2026-05-22
**Status:** pre-1.0 milestone. The public API is allowed to evolve in
breaking ways through the `0.x` series; 1.0 freezes it.

---

## What this release is

`crypt-io` now hashes. A new `crypt_io::hash` module ships three
cryptographic hash functions behind a consistent free-function API,
each paired with a matching streaming hasher for inputs that arrive
in chunks or don't fit in memory:

| Algorithm | One-shot         | Streaming         | Output | Feature        |
|-----------|------------------|-------------------|--------|----------------|
| BLAKE3    | `hash::blake3`   | `Blake3Hasher`    | 32 B   | `hash-blake3`  |
| BLAKE3 XOF| `hash::blake3_long` | `Blake3Hasher::finalize_xof` | N B | `hash-blake3` |
| SHA-256   | `hash::sha256`   | `Sha256Hasher`    | 32 B   | `hash-sha2`    |
| SHA-512   | `hash::sha512`   | `Sha512Hasher`    | 64 B   | `hash-sha2`    |

Every entry point is verified byte-for-byte against the spec — FIPS
180-4 vectors for SHA-2, the upstream-pinned constants for BLAKE3.

---

## Highlights

- **Three algorithms, one API shape.** The one-shot functions take
  `&[u8]` and return a fixed-size byte array; the streaming hashers
  share a `new` / `update` / `finalize` triad. Code that switches
  algorithms changes one identifier, not a paradigm.
- **BLAKE3 XOF mode.** `hash::blake3_long(data, len)` and
  `Blake3Hasher::finalize_xof(len)` give you arbitrary-length output
  for key derivation, fingerprinting larger ranges, or whatever else
  needs more than 32 bytes. The first 32 bytes of an extended
  output equal the default digest of the same input — verified by
  test.
- **Spec-pinned KATs.** SHA-256 has FIPS 180-4 B.1 (`abc`), B.2
  (the 56-byte two-block input), and empty-input. SHA-512 has C.1
  (`abc`), C.2 (the 112-byte two-block input), and empty-input.
  BLAKE3 has empty + `"IETF"`. All eight are byte-array constants
  pinned in source so any future wrapper-level mistake (wrong
  endianness, wrong slicing, accidental truncation) is caught
  immediately.
- **Streaming-equivalence proved.** Every algorithm has a test
  that feeds the same data in three different chunk boundaries to
  the streaming hasher and asserts the digest equals the one-shot
  output. This catches any state-management bug in the wrapper.

---

## API at a glance

One-shot:

```rust
use crypt_io::hash;

let b = hash::blake3(b"the quick brown fox");
assert_eq!(b.len(), 32);

let s256 = hash::sha256(b"the quick brown fox");
assert_eq!(s256.len(), 32);

let s512 = hash::sha512(b"the quick brown fox");
assert_eq!(s512.len(), 64);

let big = hash::blake3_long(b"the quick brown fox", 128);
assert_eq!(big.len(), 128);
```

Streaming:

```rust
use crypt_io::hash::Blake3Hasher;

let mut h = Blake3Hasher::new();
h.update(b"first ");
h.update(b"second ");
h.update(b"third");
let digest = h.finalize();
assert_eq!(digest.len(), 32);
```

Or chained:

```rust
use crypt_io::hash::Sha256Hasher;

let mut h = Sha256Hasher::new();
h.update(b"chain").update(b"-friendly");
let digest = h.finalize();
```

---

## Choosing a hash

Both BLAKE3 and SHA-2 are safe at 256-bit cryptographic strength.
The choice is about speed and ecosystem interop, not about
strength.

| You want… | Pick |
|---|---|
| Maximum throughput, modern hardware | `BLAKE3` |
| Variable-length output (KDF, fingerprinting, MGF) | `BLAKE3` (XOF) |
| TLS / JWT / certificate fingerprint interop | `SHA-256` |
| 64-byte output for spec compliance | `SHA-512` |
| Tree-hashing for very large inputs | `BLAKE3` |
| FIPS-certified algorithm (via a downstream FIPS-validated build) | `SHA-256` / `SHA-512` |

The hardware-acceleration story differs:

- **BLAKE3** uses `AVX2` / `AVX-512` on x86 and `NEON` on ARM — the
  upstream crate dispatches at runtime.
- **SHA-2** uses `SHA-NI` on supporting x86 chips and the ARMv8
  crypto extensions on AArch64 — also runtime-dispatched by
  upstream.

---

## What's NOT in 0.4.0

- **Keyed hashing / MACs.** `Blake3Hasher` does not expose
  `with_key`, and there's no `hmac_*` here. Both belong in the
  `mac` module shipping in Phase 0.5.0 — keeping "data integrity
  check" (no key) and "authentication tag" (key + constant-time
  verify) in separate modules is deliberate. Mixing them is the
  shape of API that produces "I used SHA-256 as my MAC" bug
  reports.
- **Benchmarks.** Deferred to Phase 0.8.0 alongside the AEAD
  benchmark suite. The hash performance contract (BLAKE3 ≤ 500 ns
  per 1 KiB, SHA-256 ≤ 2 µs per 1 KiB) gets measured there with
  the dedicated criterion harness, not stubbed in this phase.
- **No-key BLAKE3 `derive_key` mode.** That's the BLAKE3-specific
  key derivation API; KDF concerns live in the `kdf` module
  (Phase 0.6.0).

---

## Security notes

- **Hash-only, no MAC.** Using a raw hash as a MAC is a security
  mistake (`hash(key || message)` is vulnerable to length-extension
  on Merkle-Damgård hashes; even on BLAKE3 it produces a weaker
  authenticator than the keyed mode). The absence of a `with_key`
  on the hashers is intentional and documented.
- **Constant-time discipline preserved.** SHA-2 timing is
  data-dependent in the spec; BLAKE3 timing is data-independent.
  Neither matters at the hash level (the input isn't a secret in
  the threat model for "hash some bytes") — what would matter is
  comparing two digests, and that comparison must use
  `subtle::ConstantTimeEq` or equivalent. We do not expose a
  `Digest == Digest` shortcut for this reason.
- **No bytes in errors.** Hash functions don't fail — they have no
  `Result` return type — so there are no error variants to leak
  bytes through.

---

## Compatibility & build

- **Default features extended.** `default` now includes `hash-sha2`
  in addition to `hash-blake3`. A fresh `cargo add crypt-io` ships
  with both. Drop `hash-sha2` (via `default-features = false` + a
  custom feature list) if you want BLAKE3-only.
- **MSRV** unchanged: Rust 1.85 (edition 2024).
- **No breaking changes** to the 0.3.0 surface. `Crypt`,
  `Algorithm`, `Error`, `Result` are all unchanged.

---

## Installation

```toml
[dependencies]
crypt-io = "0.4"
```

Or:

```bash
cargo add crypt-io
```

---

## Verification

| Check | Result |
|---|---|
| `cargo fmt --all -- --check` | clean |
| `cargo clippy --all-targets --all-features -- -D warnings` | clean |
| `cargo test --all-features` | 64 unit + 1 smoke + 12 doctest — all passing |
| SHA-256 FIPS 180-4 B.1 / B.2 / empty | byte-exact match |
| SHA-512 FIPS 180-4 C.1 / C.2 / empty | byte-exact match |
| BLAKE3 empty + `"IETF"` | byte-exact match |
| Streaming-equals-one-shot (per algorithm) | verified at multiple chunk boundaries |
| MSRV (1.85) build | clean |

---

## Acknowledgements

`crypt-io` does not implement cryptographic primitives. The math
new in 0.4.0 comes from:

- [`blake3`](https://crates.io/crates/blake3) — official BLAKE3
  implementation, including SIMD dispatch.
- [`sha2`](https://crates.io/crates/sha2) — RustCrypto's SHA-2
  family, including SHA-NI / ARMv8 crypto-extension dispatch.

Carried forward from 0.3.0: the `chacha20poly1305`, `aes-gcm`, and
`mod-rand` stack.

---

## What's next

Phase 0.5.0: MAC. HMAC-SHA256, HMAC-SHA512, BLAKE3 keyed mode —
the authentication-tag surface that pairs with the hashing landing
here, with constant-time verification and known-answer tests from
RFC 4231.
