# 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
| 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
| `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:
| `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`:
| `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`.