nahui 0.2.0

Authenticated encryption (ChaCha20-Poly1305) and BLAKE3 content hashing for document attestation.
Documentation
# nahui

Authenticated encryption (ChaCha20-Poly1305) and BLAKE3 content hashing for
document attestation.

`nahui` is a small, focused crypto crate built on top of [`chacha20poly1305`]
and [`blake3`]. It exposes a `Vault` type that handles single-shot and
streaming encryption, AAD binding, and a wire format that's versioned so
breaking changes can't silently corrupt old blobs.

It runs natively and on `wasm32-unknown-unknown` (browser).

## Features

- **AEAD encryption**`ChaCha20-Poly1305`, both single-shot (in-memory)
  and streaming (IETF STREAM construction, no `Seek` required on decrypt).
- **AAD on every API** — bind a ciphertext to its context (filename, user
  id, doc version) so an attacker can't swap one valid blob for another.
- **Versioned wire format** — every blob starts with a version byte, and
  the version is bound into the AEAD tag so cross-version confusion is
  impossible.
- **Plaintext hashing**`hash_bytes`, `hash_stream`, and a one-pass
  `hash_and_encrypt_stream` that returns the BLAKE3 hash of the
  *plaintext* (suitable for an attestation note).
- **Allocation-aware**`encrypt_into` / `decrypt_into` reuse a caller's
  `Vec` so the hot path runs allocation-free once warmed up.
- **Plaintext scrubbing on failure** — if AEAD verification fails, the
  unauthenticated keystream-XOR plaintext is zeroed before returning.
- **`#![forbid(unsafe_code)]`** in the library.
- **Zeroization** — master key and derived cipher state are wiped on drop.

## Usage

```rust
use nahui::crypto::Vault;

// Generate a fresh random key. Use `Vault::new(key)` if you already have
// one (e.g. derived from a password via Argon2 or fetched from a KMS).
let vault = Vault::generate().expect("OS RNG must be available");

// Single-shot, with AAD binding the ciphertext to a filename.
let ct = vault.encrypt(b"hello", b"file:notes.txt").unwrap();
let pt = vault.decrypt(&ct, b"file:notes.txt").unwrap();
assert_eq!(pt, b"hello");
```

Streaming + plaintext hash in one pass:

```rust,no_run
use nahui::crypto::Vault;
use std::fs::File;
use std::io::BufWriter;

let vault = Vault::generate().unwrap();
let mut input = File::open("notes.txt").unwrap();
let output = BufWriter::new(File::create("notes.txt.enc").unwrap());
let hash = vault
    .hash_and_encrypt_stream(&mut input, &mut { output }, b"file:notes.txt")
    .unwrap();
println!("BLAKE3({{plaintext}}) = {}", hash.to_hex());
```

## Wire format

Single-shot:

```text
[ version (1 B) | nonce (12 B) | ciphertext (N B) | tag (16 B) ]
```

Streaming:

```text
[ version (1 B) | header_nonce (7 B) | chunk_0 | chunk_1 | ... | chunk_final ]
```

Each streaming chunk carries its own 16-byte Poly1305 tag, and STREAM's
"last block" flag prevents truncation attacks.

## Status

`0.x` — the API and wire format are stable enough that we KAT-test them,
but they may evolve. Expect a `1.0` once the dust settles. The current
`VERSION` constant is `0x01`.

## License

MIT OR Apache-2.0, at your option.