krypteia-tessera 0.2.0

Shared pure-Rust hash, XOF, MAC and KDF primitives for the krypteia workspace: SHA-1/2/3, Keccak, SHAKE/cSHAKE, RIPEMD-160, BLAKE2/BLAKE3, HMAC, SP 800-185 (KMAC/TupleHash/ParallelHash), the KDF family (HKDF, PBKDF2, SP 800-108, scrypt, Argon2), MGF1 and CRC-16/32. no_std, zero runtime dependencies.
Documentation
# krypteia-tessera — Shared Hash Primitives for the krypteia workspace

[![Crates.io](https://img.shields.io/crates/v/krypteia-tessera.svg)](https://crates.io/crates/krypteia-tessera)
[![Docs.rs](https://docs.rs/krypteia-tessera/badge.svg)](https://docs.rs/krypteia-tessera)
[![docs (guide)](https://img.shields.io/badge/docs-krypteia--rs.dev-3fb950)](https://krypteia-rs.dev)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)

Pure-Rust implementations of every hash, XOF, keyed-hash, key-derivation
function and checksum the workspace needs — SHA-2, SHA-3 / Keccak, the
SHAKE / cSHAKE XOFs, MGF1, the legacy/alt family (SHA-1, RIPEMD-160,
BLAKE2b/2s, BLAKE3), the SP 800-185 keyed constructions (KMAC, TupleHash,
ParallelHash), HMAC, the KDF family (HKDF, PBKDF2, SP 800-108, scrypt,
Argon2), and the non-cryptographic CRC-16/32 checksums. `tessera` is the
**single home for every hash** in the [krypteia](https://github.com/cslashm/krypteia)
workspace: `quantica` (post-quantum) and `arcana` (classical) both build
on it rather than carrying their own copies of Keccak or SHA-2.

A *tessera* was the Roman token of identity and proof — a fitting name for
a crate whose whole job is fingerprints and derivation.

## Design rules

The crate inherits the krypteia workspace design rules:

1. **Pure Rust, zero external crates** — only `core` and `alloc`. The crate
   is `#![no_std]`; `alloc` is used only where an output length is not known
   at the call site (`hkdf::extract`, the SP 800-185 helpers). No workspace
   dependency at all — `tessera` sits at the bottom of the dependency graph
   so that both `arcana` and `quantica` can depend on it without a cycle.
2. **Embedded-friendly** — fixed-output APIs write into caller-provided
   slices (`finalize(&mut [u8])`, `pbkdf2(…, dk: &mut [u8])`), so the hot
   paths allocate nothing. Target devices: secure elements, STM32
   (Cortex-M0/M4/M33), RISC-V (ESP32-C3, …).
3. **Constant-timeness is scoped, not blanket** — the hash compression
   functions process **public** data, so their (data-oblivious) structure
   is CT by construction but CT is not a stated goal for them. The
   secret-handling primitives (HMAC, HKDF, PBKDF2, SP 800-108) are
   data-oblivious in the key. The memory-hard functions (scrypt, Argon2d/id)
   have **known data-dependent memory access** and are *not* constant-time —
   see [Side-channel posture]#side-channel-posture for the exact leaking
   paths. CRC is non-cryptographic and non-CT by design.
4. **Validated** — every algorithm is tested against pinned FIPS / NIST SP /
   RFC reference vectors plus the official NIST CAVP (SHS) and ACVP (hashes)
   corpora. See [Test validation]#test-validation.
5. **C / JS-exposable** — the companion crates `tessera_ffi` (C ABI) and
   `tessera_wasm` (WebAssembly) mirror the public surface.

## Algorithms

### Hash functions (`impl Digest`)

| Algorithm        | Output    | Module                       | Standard        |
|------------------|-----------|------------------------------|-----------------|
| SHA-1            | 160 b     | `sha1::Sha1` *(legacy)*      | FIPS 180-4      |
| SHA-224          | 224 b     | `sha2::Sha224`               | FIPS 180-4      |
| SHA-256          | 256 b     | `sha2::Sha256`               | FIPS 180-4      |
| SHA-384          | 384 b     | `sha2::Sha384`               | FIPS 180-4      |
| SHA-512          | 512 b     | `sha2::Sha512`               | FIPS 180-4      |
| SHA-512/224      | 224 b     | `sha2::Sha512_224`           | FIPS 180-4      |
| SHA-512/256      | 256 b     | `sha2::Sha512_256`           | FIPS 180-4      |
| SHA3-224         | 224 b     | `sha3::Sha3_224`             | FIPS 202        |
| SHA3-256         | 256 b     | `sha3::Sha3_256`             | FIPS 202        |
| SHA3-384         | 384 b     | `sha3::Sha3_384`             | FIPS 202        |
| SHA3-512         | 512 b     | `sha3::Sha3_512`             | FIPS 202        |
| RIPEMD-160       | 160 b     | `ripemd160::Ripemd160` *(legacy)* | ISO/IEC 10118-3 |
| BLAKE2b          | 1–64 B    | `blake2::Blake2b`            | RFC 7693        |
| BLAKE2s          | 1–32 B    | `blake2::Blake2s`            | RFC 7693        |
| BLAKE3           | 32 B / XOF| `blake3::{hash, keyed_hash, derive_key}` | [BLAKE3 spec] |

BLAKE2b/2s expose a variable-length constructor `with_output_len(n)`; the
default `Digest::OUTPUT_LEN` is the full 64 B / 32 B width.

### Extendable-output functions (`impl Xof`)

| Algorithm        | Module                       | Standard        |
|------------------|------------------------------|-----------------|
| SHAKE128         | `sha3::Shake128`             | FIPS 202        |
| SHAKE256         | `sha3::Shake256`             | FIPS 202        |
| cSHAKE128        | `sha3::CShake128`            | NIST SP 800-185 |
| cSHAKE256        | `sha3::CShake256`            | NIST SP 800-185 |
| MGF1             | `mgf1::mgf1<H>`              | RFC 8017 §B.2.1 |
| BLAKE3 (XOF mode)| `blake3::hash` (any out len) | [BLAKE3 spec]   |

`mgf1` is generic over any `Digest`, mirroring how RSA-OAEP / RSA-PSS in
`arcana` consume it. `sha3::KeccakState` (rate + domain suffix, `absorb` /
`squeeze`) is public so `quantica` can drive Keccak on its hot path and
`arcana` can build cSHAKE-keyed constructions on top.

### Keyed hashes / MACs

| Algorithm                 | Module                                 | Standard        |
|---------------------------|----------------------------------------|-----------------|
| HMAC (over any `Digest`)  | `hmac::{hmac, hmac_multi}`             | RFC 2104, FIPS 198-1 |
| KMAC128 / KMAC256 (+XOF)  | `sp800_185::{kmac128, kmac256, …_xof}` | NIST SP 800-185 |
| TupleHash128 / 256 (+XOF) | `sp800_185::{tuplehash128, …_xof}`     | NIST SP 800-185 |
| ParallelHash128 / 256 (+XOF) | `sp800_185::{parallelhash128, …_xof}` | NIST SP 800-185 |

`hmac::<H>(key, data, out)` instantiates HMAC over any hash implementing
`Digest`, so HMAC-SHA-256/384/512, HMAC-SHA3-*, HMAC-SHA-1 and
HMAC-RIPEMD-160 all come from one generic function. `hmac_multi` takes a
slice of message parts to avoid an intermediate concatenation.

### Key-derivation functions

| Algorithm            | Module                                            | Standard              |
|----------------------|---------------------------------------------------|-----------------------|
| HKDF                 | `hkdf::{extract, expand, derive}` (over any `Digest`) | RFC 5869          |
| PBKDF2               | `pbkdf2::pbkdf2` (over any `Digest`)              | RFC 2898 / SP 800-132 |
| SP 800-108 KBKDF     | `sp800_108::{counter, feedback, double_pipeline}` | NIST SP 800-108r1     |
| scrypt               | `scrypt::scrypt`                                  | RFC 7914              |
| Argon2 d / i / id    | `argon2::{argon2, Variant}`                       | RFC 9106              |

HKDF, PBKDF2 and SP 800-108 are HMAC-derived and generic over the hash.
scrypt carries an **inline Salsa20/8 core** (tessera cannot depend on
`arcana` where the full Salsa20 lives, so the 8-round core is reimplemented
locally — RFC 7914 §3). Argon2 is built on the crate's own BLAKE2b and
covers all three variants (`Variant::{D, I, Id}`), with the optional secret
(keyed) and associated-data inputs of RFC 9106 §3.1.

### Checksums (non-cryptographic — deliberately **not** `Digest`)

| Family  | Named variants (under `crc::`)                                  | Reference           |
|---------|-----------------------------------------------------------------|---------------------|
| CRC-32  | `CRC32_ISO_HDLC`, `CRC32_ISCSI`, `CRC32_BZIP2`, `CRC32_MPEG_2`, `CRC32_CKSUM` | RevEng / Rocksoft model |
| CRC-16  | `CRC16_ARC`, `CRC16_MODBUS`, `CRC16_USB`, `CRC16_CCITT_FALSE`, `CRC16_XMODEM`, `CRC16_KERMIT` | RevEng / Rocksoft model |

`Crc16` / `Crc32` are streaming engines over a `'static` algorithm
descriptor built at compile time (`const fn`), returning the native
`u16` / `u32`. A user can define an arbitrary variant with
`Crc32Algorithm::new(poly, init, refin, refout, xorout, check)`. CRC types
**do not implement `Digest` or `Xof`** — this is a deliberate misuse
barrier: `hmac::<Crc32>` and `mgf1::<Crc32>` will not compile, so a linear
checksum can never be mistaken for a MAC. See
[Side-channel posture](#side-channel-posture).

## Traits

Two small traits keep the generic code (`hmac`, `mgf1`, `hkdf`, `pbkdf2`,
the FIPS 204/205 encoders in `quantica`) written once:

```rust
pub trait Digest: Sized {
    const OUTPUT_LEN: usize;   // digest length, octets
    const BLOCK_LEN: usize;    // Merkle–Damgård / sponge rate (HMAC needs it)
    fn new() -> Self;
    fn update(&mut self, data: &[u8]);
    fn finalize(self, out: &mut [u8]);            // writes OUTPUT_LEN octets
    fn digest(data: &[u8], out: &mut [u8]) { /* … */ }  // one-shot default
}

pub trait Xof: Sized {
    const BLOCK_LEN: usize;    // sponge rate, octets
    fn new() -> Self;
    fn update(&mut self, data: &[u8]);
    fn squeeze(&mut self, out: &mut [u8]);        // repeatable
}
```

Output sizes are caller-provided slices to keep the trait `no_std` and
allocation-free.

## Cargo features

`tessera` has **no Cargo features** — it is `#![no_std]` + `alloc`,
zero-dependency, in every configuration. There is no `std` gate and no
feature-gated SCA layer: the crate is a leaf, and its consumers
(`arcana`, `quantica`) own the feature surface.

```toml
[dependencies]
tessera = { package = "krypteia-tessera", path = "../tessera" }
```

## Quick start

### Hashing (SHA-256)

```rust
use tessera::{Digest, Sha256};

let mut out = [0u8; 32];
Sha256::digest(b"hello, tessera", &mut out);
assert_eq!(out.len(), 32);
```

### XOF (SHAKE128, arbitrary output)

```rust
use tessera::{Xof, Shake128};

let mut xof = Shake128::new();
xof.update(b"squeeze me");
let mut out = [0u8; 100];
xof.squeeze(&mut out);   // may be called again for more
```

### HMAC-SHA-256

```rust
use tessera::{hmac, Sha256};

let mut tag = [0u8; 32];
hmac::<Sha256>(b"secret key", b"message", &mut tag);
```

### HKDF-SHA-256 (extract-and-expand)

```rust
use tessera::{hkdf, Sha256};

let prk = hkdf::extract::<Sha256>(b"salt", b"input keying material");
let mut okm = [0u8; 42];
hkdf::expand::<Sha256>(&prk, b"context info", &mut okm).unwrap();
```

### Password hashing (Argon2id)

```rust
use tessera::argon2::{argon2, Variant};

let mut tag = [0u8; 32];
argon2(
    Variant::Id,
    b"password", b"saltsaltsalt",
    &[], &[],        // no secret, no associated data
    64,              // memory: 64 KiB
    3,               // t_cost: 3 passes
    1,               // lanes / parallelism
    &mut tag,
).unwrap();
```

### CRC-32 (ISO-HDLC / zlib)

```rust
use tessera::{Crc32, crc::CRC32_ISO_HDLC};

let checksum = Crc32::checksum(&CRC32_ISO_HDLC, b"123456789");
assert_eq!(checksum, 0xCBF4_3926);   // the RevEng `check` value
```

## Side-channel posture

> **Scope.** `tessera` deals mostly in **public** data (message hashing,
> checksums), so most of it has no secret to leak. The exceptions below are
> called out precisely, per the workspace rule that a non-constant-time
> secret-dependent path is named explicitly, never shipped silently.

### Data-oblivious (no secret-dependent branch or memory index)

* **All hash compression functions** (SHA-1/2/3, RIPEMD-160, BLAKE2, BLAKE3)
  and the SHAKE / cSHAKE / MGF1 XOFs — their control flow and memory access
  depend only on the input **length**, not on input **values**.
* **HMAC, HKDF, SP 800-108 (counter/feedback/double-pipeline), KMAC** — the
  key is absorbed through the same data-oblivious compression function;
  there is no key-value-dependent branch. Tag/PRK comparison, when a caller
  needs it, must use `silentops::ct_eq` (tessera does not compare secrets
  itself).

### Known non-constant-time paths (by algorithm design)

| Path                          | Leak                                                                 | Why it is inherent |
|-------------------------------|---------------------------------------------------------------------|--------------------|
| `scrypt` (ROMix `Integerify`) | Memory index `j = Integerify(X) mod N` is **derived from the (secret-seeded) state** → cache-line access pattern leaks. | RFC 7914 §6: memory-hardness *requires* data-dependent addressing. |
| `argon2` with `Variant::D`, and the first slices of `Variant::Id` | Reference-block index is data-dependent → cache-timing. | RFC 9106 §4: Argon2d/id trade SCA resistance for GPU-cracking resistance. |
| `pbkdf2` (and HMAC in general) | The password is the HMAC **key**; a key longer than the block is hashed first, so the *number of compression calls* reveals the password **length class** (≤ block vs > block). | RFC 2104 key-length normalisation. |
| `crc::*`                       | Table-indexed by data bytes; **non-cryptographic**, no secret expected. | CRC is an error-detection code, not a MAC. |

**Mitigation guidance.** For side-channel-sensitive password hashing prefer
**`Argon2i`** (`Variant::I`), which uses data-*independent* addressing
throughout and is the RFC 9106 choice when timing/cache leakage is in the
threat model. scrypt has no data-independent mode — its memory-hardness is
built on data-dependent access and cannot be made cache-oblivious without
losing the property. These are documented in each module's `//!` header.

### Not defended

CRC is a linear, trivially forgeable checksum: it detects accidental
corruption of **public** data, and is neither a MAC nor an integrity-against-
tampering primitive. The `Digest` omission is the enforcement of that
boundary at the type level.

## Test validation

The inline `src/*.rs` tests are minimal smoke KATs; the bulk of the
conformance evidence lives in the integration `tests/` tree, driven by
official external corpora parsed by the dependency-free readers under
`tests/common/`.

| Suite (`tests/…`)   | Coverage                                                         | Vector source                                |
|---------------------|-----------------------------------------------------------------|----------------------------------------------|
| `cavp_shs.rs`       | SHA-1, SHA-2 family (short/long/Monte-Carlo)                     | NIST CAVP SHS `.rsp`                          |
| `acvp_hashes.rs`    | SHA-3, SHAKE, SHA-2                                              | NIST ACVP JSON                               |
| `sp800_185.rs`      | cSHAKE, KMAC, TupleHash, ParallelHash (+XOF)                     | NIST SP 800-185 sample 1/2/4                  |
| `hmac.rs`           | HMAC-SHA-1/2/3                                                   | RFC 4231, RFC 2202                           |
| `blake3.rs`         | BLAKE3 hash / keyed / derive_key                                | upstream BLAKE3 test vectors                  |
| `misc_hashes.rs`    | RIPEMD-160, BLAKE2b/2s                                          | ISO 10118-3, RFC 7693 §B                      |
| `crc.rs`            | 11 CRC-16/32 variants — each self-checked against its `check`    | RevEng catalogue + zlib/binascii cross-check  |
| `kdf.rs`            | HKDF (RFC 5869 A.1-A.3), PBKDF2 (RFC 6070), SP 800-108 (counter gold), scrypt (RFC 7914 §12), Argon2 d/i/id (RFC 9106 §5) | RFC vectors + independent Python cross-checks |

Argon2's three RFC 9106 §5 reference tags are pinned inline in
`argon2.rs`. The SP 800-108 **counter** mode is gold-validated against
the `cryptography` library; feedback / double-pipeline carry a
`[TO CONFIRM]` pending NIST CAVP KDF-108 wiring.

```bash
cargo test  -p krypteia-tessera                    # lib smoke + all integration suites
cargo build -p krypteia-tessera --no-default-features  # no_std build (alloc only)
cargo clippy -p krypteia-tessera --all-targets -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc -p krypteia-tessera --no-deps
```

## Bindings

| Companion       | Kind          | Status                                                        |
|-----------------|---------------|--------------------------------------------------------------|
| `tessera_ffi`   | C ABI (`.a` / `.so` + `include/tessera.h`) | v0.2 — mirrors the digest / XOF / HMAC / SP 800-185 surface. |
| `tessera_wasm`  | WebAssembly (wasm-bindgen) | v0.2 — JS-facing digests, XOFs and keyed hashes. |

Hashes need no CSPRNG, so `tessera_wasm` — unlike `arcana_wasm` /
`quantica_wasm` — pulls in no `web-sys::Crypto` random source.

## Module map

```text
tessera/src/
  lib.rs         Crate root: re-exports + Digest / Xof traits
  traits.rs      Digest, Xof
  sha1.rs        SHA-1 (FIPS 180-4) — legacy
  sha2.rs        SHA-224/256/384/512/512-224/512-256 (FIPS 180-4)
  sha3.rs        Keccak-f, SHA3-224/256/384/512, SHAKE128/256, cSHAKE128/256, KeccakState
  ripemd160.rs   RIPEMD-160 (ISO/IEC 10118-3) — legacy
  blake2.rs      BLAKE2b, BLAKE2s (RFC 7693)
  blake3.rs      BLAKE3: hash / keyed_hash / derive_key (fixed + XOF)
  mgf1.rs        MGF1 XOF over any Digest (RFC 8017 §B.2.1)
  hmac.rs        HMAC over any Digest (RFC 2104, FIPS 198-1)
  sp800_185.rs   KMAC128/256, TupleHash128/256, ParallelHash128/256 (+XOF)
  hkdf.rs        HKDF extract / expand / derive (RFC 5869)
  pbkdf2.rs      PBKDF2 (RFC 2898 / SP 800-132)
  sp800_108.rs   KBKDF counter / feedback / double-pipeline (SP 800-108r1)
  scrypt.rs      scrypt (RFC 7914) — inline Salsa20/8 core
  argon2.rs      Argon2 d/i/id (RFC 9106) — BLAKE2b core, secret + AD
  crc.rs         CRC-16/32 (RevEng catalogue) — non-cryptographic, not a Digest
```

## References

- [FIPS 180-4]https://csrc.nist.gov/publications/detail/fips/180/4/final — SHA-1, SHA-2 family
- [FIPS 202]https://csrc.nist.gov/publications/detail/fips/202/final — SHA-3, SHAKE
- [NIST SP 800-185]https://csrc.nist.gov/publications/detail/sp/800-185/final — cSHAKE, KMAC, TupleHash, ParallelHash
- [FIPS 198-1]https://csrc.nist.gov/publications/detail/fips/198/1/final — HMAC
- [RFC 2104]https://www.rfc-editor.org/rfc/rfc2104 — HMAC
- [RFC 7693]https://www.rfc-editor.org/rfc/rfc7693 — BLAKE2
- [BLAKE3 spec]https://github.com/BLAKE3-team/BLAKE3-specs — BLAKE3
- [ISO/IEC 10118-3]https://www.iso.org/standard/67116.html — RIPEMD-160
- [RFC 5869]https://www.rfc-editor.org/rfc/rfc5869 — HKDF
- [RFC 2898]https://www.rfc-editor.org/rfc/rfc2898 / [NIST SP 800-132]https://csrc.nist.gov/publications/detail/sp/800-132/final — PBKDF2
- [NIST SP 800-108r1]https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final — KBKDF (counter / feedback / double-pipeline)
- [RFC 7914]https://www.rfc-editor.org/rfc/rfc7914 — scrypt
- [RFC 9106]https://www.rfc-editor.org/rfc/rfc9106 — Argon2
- [RFC 8017]https://www.rfc-editor.org/rfc/rfc8017 — PKCS#1 (MGF1)
- [RevEng CRC catalogue]https://reveng.sourceforge.io/crc-catalogue/ — CRC parameter sets
- [NIST CAVP]https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program — SHS conformance vectors
- [NIST ACVP-Server]https://github.com/usnistgov/ACVP-Server — modern conformance vectors

## License

Apache-2.0.

[BLAKE3 spec]: https://github.com/BLAKE3-team/BLAKE3-specs
</content>