rscrypto 0.8.0

Pure Rust Cryptography: RSA, Ed25519, X25519, SHA-2/3, BLAKE2/3, AES-GCM/GCM-SIV, X/ChaCha20-Poly1305, Argon2, HMAC/HKDF, CRC. no_std, WASM, hardware acceleration.
Documentation
# Migration: `tiny-keccak``rscrypto`

Replace fixed-output `tiny_keccak::Kmac::v128` / `Kmac::v256` and
`tiny_keccak::CShake::v128` / `CShake::v256` with `rscrypto::Kmac128` /
`Kmac256` and `rscrypto::Cshake128` / `Cshake256`. KMAC construction is
infallible and adds verification helpers. `KmacXof` is not mapped.

Verified against `tiny-keccak = "2.0.2"` (with `kmac` and `cshake` features) and the `rscrypto` 0.7.8 line.
Evidence: `tests/kmac128_differential.rs`, `tests/kmac256_differential.rs`, `tests/cshake256_differential.rs`, `tests/cshake256_nist_vectors.rs`, and `tests/kmac_wycheproof.rs`.

## TL;DR

| | Before (`tiny-keccak` 2.x) | After (`rscrypto` 0.7.8) |
|---|---|---|
| Cargo dep | `tiny-keccak = { version = "2.0", features = ["kmac", "cshake"] }` | `rscrypto = { version = "0.7.8", features = ["kmac"] }` |
| KMAC import | `use tiny_keccak::{Hasher, Kmac};` | `use rscrypto::{Kmac128, Kmac256};` |
| KMAC call | `let mut k = Kmac::v256(key, custom); k.update(data); k.finalize(&mut tag);` | `Kmac256::mac_into(key, custom, data, &mut tag);` |
| cSHAKE import | `use tiny_keccak::{Hasher, CShake};` | `use rscrypto::{Cshake128, Cshake256, Xof};` |
| cSHAKE call | `let mut x = CShake::v256(name, custom); x.update(data); x.finalize(&mut out);` | `Cshake256::xof(name, custom, data).squeeze(&mut out);` |

## Cargo.toml

```toml
# Before
[dependencies]
tiny-keccak = { version = "2.0", features = ["kmac", "cshake"] }
```

```toml
# After
[dependencies]
rscrypto = { version = "0.7.8", features = ["kmac"] }
```

The `kmac` feature implies `sha3` (which provides the underlying `Cshake128` / `Cshake256` sponges for both KMAC variants and the standalone cSHAKE primitives).

If you only use cSHAKE and not KMAC, swap the feature for `sha3` alone. That exposes `Cshake128` / `Cshake256` without pulling in KMAC.

## Algorithm map

| `tiny-keccak` type | rscrypto type | Spec |
|---|---|---|
| `Kmac::v256(key, custom)` | `Kmac256` | NIST SP 800-185 §4.3 |
| `Kmac::v128(key, custom)` | `Kmac128` | NIST SP 800-185 §4.3 |
| `KmacXof::v128` / `KmacXof::v256` | not mapped | Keep `tiny-keccak` for KMACXOF |
| `CShake::v256(name, custom)` | `Cshake256` | NIST SP 800-185 §3 |
| `CShake::v128(name, custom)` | `Cshake128` | NIST SP 800-185 §3 |
| `Sha3*`, `Keccak*`, `Shake*`, `ParallelHash*`, `TupleHash*` | covered by `RustCrypto/sha3.md` (SHA-3/SHAKE) or unsupported (Keccak, ParallelHash, TupleHash) | FIPS 202 / SP 800-185 |

If you migrate from `tiny-keccak` for SHA-3 / SHAKE specifically (not KMAC / cSHAKE), follow `RustCrypto/sha3.md` instead: same destination types, slightly different upstream API.

## API patterns

### KMAC256: one-shot tag

```rust
// Before
use tiny_keccak::{Hasher, Kmac};
let mut k = Kmac::v256(key, custom);
k.update(data);
let mut tag = [0u8; 32];
k.finalize(&mut tag);                    // consumes k
```

```rust
// After
use rscrypto::Kmac256;
let tag: [u8; 32] = Kmac256::mac_array(key, custom, data);
```

Two changes:

| `tiny-keccak` | rscrypto |
|---|---|
| `Hasher` trait import required | inherent methods on `Kmac256` |
| `finalize(&mut tag)` consumes `self` | `mac_into` / `mac_array` are static; streaming `finalize_into` borrows `&mut self` |

### KMAC256: streaming

```rust
// Before
use tiny_keccak::{Hasher, Kmac};
let mut k = Kmac::v256(key, custom);
k.update(b"foo");
k.update(b"bar");
let mut tag = [0u8; 32];
k.finalize(&mut tag);
```

```rust
// After
use rscrypto::Kmac256;
let mut k = Kmac256::new(key, custom);
k.update(b"foo");
k.update(b"bar");
let mut tag = [0u8; 32];
k.finalize_into(&mut tag);               // borrows &mut self
```

`k.reset()` is available in rscrypto to start over without rebuilding the absorbed `(key, custom)` state.

### KMAC256: variable-length output

The output length is part of the KMAC tag derivation per SP 800-185: a 32-byte tag is *not* the prefix of a 64-byte tag. Both crates encode the length identically (verified at 32 and 64 bytes in the harness):

```rust
// After
use rscrypto::Kmac256;
let mut tag = [0u8; 64];
Kmac256::mac_into(key, custom, data, &mut tag);
```

### KMAC256: opaque verification

```rust
// Before
// tiny-keccak has no verify helper; hand-roll with `subtle`:
use subtle::ConstantTimeEq;
use tiny_keccak::{Hasher, Kmac};

let mut k = Kmac::v256(key, custom);
k.update(data);
let mut got = [0u8; 32];
k.finalize(&mut got);
let ok: bool = got.ct_eq(&expected).into();
```

```rust
// After
use rscrypto::Kmac256;
Kmac256::verify_tag(key, custom, data, &expected)?;   // Result<(), VerificationError>
```

Drop the `subtle` dependency for this verification path. For streaming
verification, construct `Kmac256`, call `update`, then call
`verify(&expected)`. The authentication helpers require at least 16 bytes for
KMAC128 and 32 bytes for KMAC256, preserving the named security strength.

Use `verify_primitive` or `verify_tag_primitive` only when a protocol specifies
a shorter output and defines its forgery budget and failed-attempt limit.
Arbitrary-length `finalize_into` and `mac_into` remain available for PRF or KDF
use. Verification traverses the public-length expected tag before returning one
opaque result. Generated-code timing claims remain limited to the matching
[release evidence](../constant-time.md).

### cSHAKE256: XOF streaming

```rust
// Before
use tiny_keccak::{Hasher, CShake};
let mut x = CShake::v256(function_name, customization);
x.update(data);
let mut out = [0u8; 64];
x.finalize(&mut out);                    // consumes x; uses Hasher::finalize for fixed length
```

```rust
// After
use rscrypto::{Cshake256, Xof};
let mut reader = Cshake256::xof(function_name, customization, data);
let mut out = [0u8; 64];
reader.squeeze(&mut out);
```

For the streaming form (data fed in chunks):

```rust
// After
use rscrypto::{Cshake256, Xof};
let mut x = Cshake256::new(function_name, customization);
x.update(b"foo");
x.update(b"bar");
let mut reader = x.finalize_xof();
let mut out = [0u8; 64];
reader.squeeze(&mut out);
```

Three changes from `tiny-keccak`:

| `tiny-keccak` | rscrypto |
|---|---|
| `Hasher::finalize` consumes the sponge and writes to a fixed buffer | `Cshake256` has a fused one-shot `xof(name, custom, data)` that returns a reader; streaming `finalize_xof()` returns the same reader |
| Cannot squeeze more bytes after `finalize` | Reader is a dedicated XOF type: call `squeeze` repeatedly for additional bytes |
| `Hasher::update` adds chunks | `Digest::update` plays the same role |

## Notes

- **Unsupported SP 800-185 functions.** rscrypto does not expose `ParallelHash`
  or `TupleHash`. Keep `tiny-keccak` for those primitives.
- **KMACXOF is not fixed-output KMAC with a longer buffer.** SP 800-185 KMAC
  appends `right_encode(L)`; KMACXOF appends `right_encode(0)`. Keep
  `tiny-keccak` for `KmacXof` until rscrypto exposes that mode.
- **Differential coverage.** The harness compares KMAC128/256 at multiple
  fixed output lengths and cSHAKE128/256 at a 64-byte squeeze. Add your
  protocol's parameter set before removing the old dependency.
- **Hand-rolled cSHAKE-based KMAC.** Verify `bytepad(encode_string(K))`, the
  function-name/customization encoding, and the trailing `right_encode(L)`.
  Treat divergent output as a mode, encoding, or implementation mismatch that
  must be resolved before migration.
- **`no_std`.** Both crates support `no_std`. rscrypto's `mac_to_vec` style helpers are gated on `alloc`; the fixed-array and user-supplied-buffer paths are pure `no_std`.