Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,20 @@ the required `Complete` check. Because the repository currently has one
maintainer, no second approval is required; review the final diff yourself,
resolve any open threads, and merge in the GitHub UI.

After the merge:
After GitHub reports the pull request merged:

```bash
git switch main
git pull --ff-only
git branch -d <short-feature-name>
git branch -D <short-feature-name>
```

GitHub can delete the remote branch during the merge. Do not create release
tags during daily development.
Squash merges do not place the topic branch tip in `main`'s ancestry, so
`git branch -d` can reject a branch whose pull request is already merged. Use
`-D` only after verifying that exact pull request. GitHub can delete the remote
branch during the merge; otherwise delete that exact branch with `git push
origin --delete <short-feature-name>`. Do not create release tags during daily
development.

## Security boundaries

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ Minimal `no_std` SHA-2 build:

```toml
[dependencies]
rscrypto = { version = "0.6.4", default-features = false, features = ["sha2"] }
rscrypto = { version = "0.7.8", default-features = false, features = ["sha2"] }
```

Full primitive stack with OS randomness enabled:

```toml
[dependencies]
rscrypto = { version = "0.6.4", features = ["full", "getrandom"] }
rscrypto = { version = "0.7.8", features = ["full", "getrandom"] }
```

Use `default-features = false` for `no_std` builds. Enable `getrandom` only when you need APIs that generate salts, keys, nonces, or RSA key-gen entropy from the operating system.
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Out of scope:

| Version | Supported |
| ------- | --------- |
| `0.6.x` | Yes |
| `0.7.x` | Yes |

Only the latest published minor release receives security patches. Users should
stay current.
Expand Down
14 changes: 9 additions & 5 deletions THREAT_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ unlisted build configurations are not blanket constant-time claims.

`rscrypto` is a primitives library. It computes hashes, MACs, KDFs, password
hashes, AEADs, signatures, key exchanges, and checksums on caller-provided
buffers. It does not open sockets, touch the filesystem, read the clock, or
spawn threads outside the opt-in `parallel` feature. Protocol design, key
storage, key rotation, and transport are the caller's responsibility.
inputs. It does not open sockets, read the clock, or spawn production threads
outside the opt-in `parallel` feature. With `std`, runtime CPU detection may
read OS-exposed capability data such as `/proc/self/auxv`, `/proc/cpuinfo`, and
sysfs; `getrandom` constructors obtain randomness from the operating system.
The crate does not read application data or manage keys on disk. Protocol
design, key storage, key rotation, and transport are the caller's
responsibility.

Everything that crosses the boundary:

Expand Down Expand Up @@ -98,14 +102,14 @@ Ordered by exposure to untrusted input:
| Parsers | RSA DER/SPKI/PKCS#8 import, ECDSA DER signatures and SEC1 points, ML-KEM key and ciphertext parsing, PHC strings, hex | Memory safety, panics, accepting what should be rejected |
| Verification oracles | MAC `verify_tag`, AEAD open, signature `verify`, ML-KEM implicit rejection | Timing or error detail beyond the single failure bit |
| Secret-bearing compute | Sign, decrypt, decapsulate, derive; the release-evidenced subset of `ct.toml` | Timing leakage, incorrect arithmetic |
| `unsafe` SIMD and assembly kernels | Per-architecture modules | Undefined behavior, divergence from the portable authority |
| `unsafe` low-level code | SIMD/assembly kernels, raw buffer helpers, zeroization, and dispatch | Undefined behavior, divergence from the portable authority |
| Dispatch | `src/platform`, `src/backend` | Selecting a kernel the CPU cannot run, or one that produces wrong output |

## Mitigations And Evidence

| Risk | Mitigation | Evidence |
|---|---|---|
| Memory safety | `unsafe` confined to kernel and platform modules; unsafe lint gates enabled | Miri on portable paths in CI |
| Memory safety | Unsafe operations are lint-gated and require local `SAFETY` proofs; the safe portable path remains authoritative | Miri on portable paths in CI |
| Parser abuse | Strict imports, `strict_*` arithmetic, release overflow checks | Fuzz targets, Wycheproof where mapped, official vectors |
| Wrong output from accelerated kernels | Portable path is the byte-for-byte authority | Portable-vs-accelerated differential tests and native CI |
| Timing leakage | Constant-time coding rules on claimed paths | `ct.toml` evidence gate: timing tests, generated-code review, binary checks where supported |
Expand Down
5 changes: 3 additions & 2 deletions docs/compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ building your own module boundary and evidence package. Do not use it as a
FIPS 140-3 answer by itself.

External standards references in this page were checked against NIST CSRC on
2026-06-23. Regulated deployments should check the current standard text and
their own assessor requirements before release.
2026-07-21. Several linked publications have active revision or errata notes;
regulated deployments must review those notes, the current standard text, and
their assessor's requirements before release.

## Quick Answer

Expand Down
16 changes: 8 additions & 8 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ Every primitive has its own leaf feature so size-conscious builds compile only w

```toml
# One algorithm, no_std.
rscrypto = { version = "0.6.4", default-features = false, features = ["sha2"] }
rscrypto = { version = "0.7.8", default-features = false, features = ["sha2"] }

# RSA public-key import and verification, no_std + alloc.
rscrypto = { version = "0.6.4", default-features = false, features = ["rsa"] }
rscrypto = { version = "0.7.8", default-features = false, features = ["rsa"] }

# RSA key generation, signing, encryption, and private-operation blinding.
rscrypto = { version = "0.6.4", default-features = false, features = ["rsa", "getrandom"] }
rscrypto = { version = "0.7.8", default-features = false, features = ["rsa", "getrandom"] }

# ECDSA P-256/SHA-256 and P-384/SHA-384 signing and verification.
rscrypto = { version = "0.6.4", default-features = false, features = ["ecdsa"] }
rscrypto = { version = "0.7.8", default-features = false, features = ["ecdsa"] }

# FIPS 203 ML-KEM-512/768/1024 KEM APIs with caller-supplied randomness.
rscrypto = { version = "0.6.4", default-features = false, features = ["ml-kem"] }
rscrypto = { version = "0.7.8", default-features = false, features = ["ml-kem"] }

# Everything.
rscrypto = { version = "0.6.4", features = ["full", "getrandom"] }
rscrypto = { version = "0.7.8", features = ["full", "getrandom"] }

# Everything, with parallel BLAKE3 / Argon2 lanes via Rayon.
rscrypto = { version = "0.6.4", features = ["full", "parallel", "getrandom"] }
rscrypto = { version = "0.7.8", features = ["full", "parallel", "getrandom"] }

# Audit-constrained: makes runtime dispatch ignore host SIMD/ASM capabilities.
rscrypto = { version = "0.6.4", features = ["full", "portable-only"] }
rscrypto = { version = "0.7.8", features = ["full", "portable-only"] }
```

## Complete Feature Index
Expand Down
6 changes: 3 additions & 3 deletions docs/migration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ API shape change, or not a good fit.

| From | To | Status |
|---|---|---|
| [`aes-gcm`](RustCrypto/aes-gcm.md) (RustCrypto) | `Aes128Gcm`, `Aes256Gcm` | Verified against `aes-gcm 0.10.3` |
| [`aes-gcm`](RustCrypto/aes-gcm.md) (RustCrypto) | `Aes128Gcm`, `Aes256Gcm` | Verified against `aes-gcm 0.11.0` |
| [`aes-gcm-siv`](RustCrypto/aes-gcm-siv.md) (RustCrypto) | `Aes128GcmSiv`, `Aes256GcmSiv` | Verified against `aes-gcm-siv 0.11.1` |
| [`chacha20poly1305`](RustCrypto/chacha20poly1305.md) (RustCrypto) | `ChaCha20Poly1305`, `XChaCha20Poly1305` | Verified against `chacha20poly1305 0.10.1` |
| [`ascon-aead`](RustCrypto/ascon-aead.md) (RustCrypto) | `AsconAead128` | Verified against `ascon-aead 0.5.2` |
| [`chacha20poly1305`](RustCrypto/chacha20poly1305.md) (RustCrypto) | `ChaCha20Poly1305`, `XChaCha20Poly1305` | Verified against `chacha20poly1305 0.11.0` |
| [`ascon-aead`](RustCrypto/ascon-aead.md) (RustCrypto) | `AsconAead128` | Verified against `ascon-aead 0.6.0` |
| [`aegis`](aegis.md) | `Aegis256` | Verified against `aegis 0.9.12` |

## Signatures + Key Exchange
Expand Down
8 changes: 4 additions & 4 deletions docs/migration/RustCrypto/aes-gcm-siv.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

> Same algorithm (RFC 8452), same nonce-misuse-resistant guarantees. Replace `Aes256GcmSiv` / `Key<Aes256GcmSiv>` / `Nonce` / `Payload { msg, aad }` with rscrypto's named types and a buffer-style API.

Verified against `aes-gcm-siv = "0.11.1"` and the `rscrypto` 0.5.0 line.
Verified against `aes-gcm-siv = "0.11.1"` and the `rscrypto` 0.7.8 line.
Evidence: `tests/aes128gcmsiv_oracle.rs`, `tests/aes256gcmsiv_oracle.rs`, and `tests/aead_wycheproof.rs`.

## TL;DR

| | Before (`aes-gcm-siv` 0.11.x) | After (`rscrypto` 0.5.0) |
| | Before (`aes-gcm-siv` 0.11.x) | After (`rscrypto` 0.7.8) |
|---|---|---|
| Cargo dep | `aes-gcm-siv = "0.11"` | `rscrypto = { version = "0.5.0", features = ["aes-gcm-siv"] }` |
| Cargo dep | `aes-gcm-siv = "0.11"` | `rscrypto = { version = "0.7.8", features = ["aes-gcm-siv"] }` |
| Import | `use aes_gcm_siv::{Aes256GcmSiv, Key, Nonce, KeyInit, aead::{Aead, Payload}};` | `use rscrypto::{Aead, Aes256GcmSiv, Aes256GcmSivKey, aead::Nonce96};` |
| Encrypt | `cipher.encrypt(nonce, Payload { msg, aad })?` | `cipher.encrypt(&nonce, aad, msg, &mut out)?` |

Expand All @@ -24,7 +24,7 @@ aes-gcm-siv = "0.11"
```toml
# After
[dependencies]
rscrypto = { version = "0.5.0", features = ["aes-gcm-siv"] }
rscrypto = { version = "0.7.8", features = ["aes-gcm-siv"] }
```

## Algorithm map
Expand Down
10 changes: 5 additions & 5 deletions docs/migration/RustCrypto/aes-gcm.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

> Replace the `Aes256Gcm` / `Key<Aes256Gcm>` / `Nonce` / `Payload { msg, aad }` builder with rscrypto's named types and a buffer-style `encrypt(&nonce, aad, plaintext, &mut out)`. Same algorithm, byte-identical ciphertext+tag, no `Vec` allocations on the hot path.

Verified against `aes-gcm = "0.10.3"` and the `rscrypto` 0.5.0 line.
Verified against `aes-gcm = "0.11.0"` and the `rscrypto` 0.7.8 line.
Evidence: `tests/aes128gcm_oracle.rs`, `tests/aes256gcm_oracle.rs`, and `tests/aead_wycheproof.rs`.

## TL;DR

| | Before (`aes-gcm` 0.10.x) | After (`rscrypto` 0.5.0) |
| | Before (`aes-gcm` 0.11.x) | After (`rscrypto` 0.7.8) |
|---|---|---|
| Cargo dep | `aes-gcm = "0.10"` | `rscrypto = { version = "0.5.0", features = ["aes-gcm"] }` |
| Cargo dep | `aes-gcm = "0.11"` | `rscrypto = { version = "0.7.8", features = ["aes-gcm"] }` |
| Import | `use aes_gcm::{Aes256Gcm, Key, Nonce, KeyInit, aead::{Aead, Payload}};` | `use rscrypto::{Aead, Aes256Gcm, Aes256GcmKey, aead::Nonce96};` |
| Encrypt | `cipher.encrypt(nonce, Payload { msg, aad })?` (returns `Vec<u8>`) | `cipher.encrypt(&nonce, aad, msg, &mut out)?` (writes into caller buffer) |

Expand All @@ -18,13 +18,13 @@ Evidence: `tests/aes128gcm_oracle.rs`, `tests/aes256gcm_oracle.rs`, and `tests/a
```toml
# Before
[dependencies]
aes-gcm = "0.10"
aes-gcm = "0.11"
```

```toml
# After
[dependencies]
rscrypto = { version = "0.5.0", features = ["aes-gcm"] }
rscrypto = { version = "0.7.8", features = ["aes-gcm"] }
```

## Algorithm map
Expand Down
4 changes: 2 additions & 2 deletions docs/migration/RustCrypto/argon2.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ The raw implementations are checked against RFC 9106 vectors and the RustCrypto

```toml
# Raw Argon2 KDF
rscrypto = { version = "0.7", default-features = false, features = ["argon2"] }
rscrypto = { version = "0.7.8", default-features = false, features = ["argon2"] }

# Password-record generation and verification
rscrypto = { version = "0.7", default-features = false, features = [
rscrypto = { version = "0.7.8", default-features = false, features = [
"argon2",
"phc-strings",
"getrandom",
Expand Down
10 changes: 5 additions & 5 deletions docs/migration/RustCrypto/ascon-aead.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

> NIST SP 800-232 lightweight AEAD. Replace `AsconAead128` / `Key<T>` / `Nonce<T>` / `Payload { msg, aad }` with rscrypto's named types and a buffer-style API. 128-bit key, 128-bit nonce, 128-bit tag: all the bytes are 16.

Verified against `ascon-aead = "0.5.2"` and the `rscrypto` 0.5.0 line.
Verified against `ascon-aead = "0.6.0"` and the `rscrypto` 0.7.8 line.
Evidence: `tests/ascon_aead_oracle.rs`.

## TL;DR

| | Before (`ascon-aead` 0.5.x) | After (`rscrypto` 0.5.0) |
| | Before (`ascon-aead` 0.6.x) | After (`rscrypto` 0.7.8) |
|---|---|---|
| Cargo dep | `ascon-aead = "0.5"` | `rscrypto = { version = "0.5.0", features = ["ascon-aead"] }` |
| Cargo dep | `ascon-aead = "0.6"` | `rscrypto = { version = "0.7.8", features = ["ascon-aead"] }` |
| Import | `use ascon_aead::{AsconAead128, Key, Nonce, aead::{Aead, KeyInit, Payload}};` | `use rscrypto::{Aead, AsconAead128, AsconAead128Key, aead::Nonce128};` |
| Encrypt | `cipher.encrypt(nonce, Payload { msg, aad })?` | `cipher.encrypt(&nonce, aad, msg, &mut out)?` |

Expand All @@ -18,13 +18,13 @@ Evidence: `tests/ascon_aead_oracle.rs`.
```toml
# Before
[dependencies]
ascon-aead = "0.5"
ascon-aead = "0.6"
```

```toml
# After
[dependencies]
rscrypto = { version = "0.5.0", features = ["ascon-aead"] }
rscrypto = { version = "0.7.8", features = ["ascon-aead"] }
```

## Algorithm map
Expand Down
8 changes: 4 additions & 4 deletions docs/migration/RustCrypto/ascon-hash.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

> Same algorithm (NIST LWC Ascon-Hash256). `ascon_hash::AsconHash256` becomes `rscrypto::AsconHash256`; everything else (trait shape, `update`, `finalize`) carries over.

Verified against `ascon-hash = "0.4.0"` and the `rscrypto` 0.5.0 line.
Verified against `ascon-hash = "0.4.0"` and the `rscrypto` 0.7.8 line.
Evidence: `tests/ascon_official_vectors.rs`, `tests/ascon_hash_oracle.rs`, `tests/ascon_cxof_vectors.rs`, and `tests/ascon_differential.rs`.

## TL;DR

| | Before (`ascon-hash` 0.4.x) | After (`rscrypto` 0.5.0) |
| | Before (`ascon-hash` 0.4.x) | After (`rscrypto` 0.7.8) |
|---|---|---|
| Cargo dep | `ascon-hash = "0.4"` | `rscrypto = { version = "0.5.0", features = ["ascon-hash"] }` |
| Cargo dep | `ascon-hash = "0.4"` | `rscrypto = { version = "0.7.8", features = ["ascon-hash"] }` |
| Import | `use ascon_hash::{AsconHash256, digest::Digest};` | `use rscrypto::{AsconHash256, Digest};` |
| Call | `AsconHash256::digest(data)` | `AsconHash256::digest(data)` |

Expand All @@ -24,7 +24,7 @@ ascon-hash = "0.4"
```toml
# After
[dependencies]
rscrypto = { version = "0.5.0", features = ["ascon-hash"] }
rscrypto = { version = "0.7.8", features = ["ascon-hash"] }
```

## Algorithm map
Expand Down
8 changes: 4 additions & 4 deletions docs/migration/RustCrypto/blake2.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

> Same algorithms (RFC 7693), with named convenience types replacing `Blake2b<U32>`-style generic instantiations and `Blake2b256::keyed_digest(key, data)` replacing the separate `Blake2bMac<U32>` MAC type.

Verified against `blake2 = "0.11.0-rc.6"` and the `rscrypto` 0.5.0 line.
Verified against `blake2 = "0.11.0-rc.6"` and the `rscrypto` 0.7.8 line.
Evidence: `tests/blake2_official_vectors.rs` and `tests/blake2_differential.rs`.
Code samples use the 0.10-style names where they remain the clearest migration
shape for existing projects.

## TL;DR

| | Before (`blake2` 0.10.x) | After (`rscrypto` 0.5.0) |
| | Before (`blake2` 0.10.x) | After (`rscrypto` 0.7.8) |
|---|---|---|
| Cargo dep | `blake2 = "0.10"` | `rscrypto = { version = "0.5.0", features = ["blake2b", "blake2s"] }` |
| Cargo dep | `blake2 = "0.10"` | `rscrypto = { version = "0.7.8", features = ["blake2b", "blake2s"] }` |
| Import | `use blake2::{Blake2b512, Digest};` | `use rscrypto::{Blake2b512, Digest};` |
| Call | `Blake2b512::digest(data)` | `Blake2b512::digest(data)` |

Expand All @@ -28,7 +28,7 @@ blake2 = "0.10"
```toml
# After
[dependencies]
rscrypto = { version = "0.5.0", features = ["blake2b", "blake2s"] }
rscrypto = { version = "0.7.8", features = ["blake2b", "blake2s"] }
```

## Algorithm map
Expand Down
10 changes: 5 additions & 5 deletions docs/migration/RustCrypto/chacha20poly1305.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

> Covers both `ChaCha20Poly1305` (96-bit nonce, RFC 8439) and `XChaCha20Poly1305` (192-bit nonce). Same algorithm, byte-identical ciphertext+tag; replace `Key<T>` / `Nonce` / `XNonce` / `Payload { msg, aad }` with `ChaCha20Poly1305Key` + `Nonce96` / `Nonce192` and a buffer-style API.

Verified against `chacha20poly1305 = "0.10.1"` and the `rscrypto` 0.5.0 line.
Verified against `chacha20poly1305 = "0.11.0"` and the `rscrypto` 0.7.8 line.
Evidence: `tests/chacha20poly1305.rs`, `tests/xchacha20poly1305.rs`, and `tests/aead_wycheproof.rs`.

## TL;DR

| | Before (`chacha20poly1305` 0.10.x) | After (`rscrypto` 0.5.0) |
| | Before (`chacha20poly1305` 0.11.x) | After (`rscrypto` 0.7.8) |
|---|---|---|
| Cargo dep | `chacha20poly1305 = "0.10"` | `rscrypto = { version = "0.5.0", features = ["chacha20poly1305", "xchacha20poly1305"] }` |
| Cargo dep | `chacha20poly1305 = "0.11"` | `rscrypto = { version = "0.7.8", features = ["chacha20poly1305", "xchacha20poly1305"] }` |
| Import | `use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce, KeyInit, aead::{Aead, Payload}};` | `use rscrypto::{Aead, ChaCha20Poly1305, ChaCha20Poly1305Key, aead::Nonce96};` |
| Encrypt | `cipher.encrypt(nonce, Payload { msg, aad })?` | `cipher.encrypt(&nonce, aad, msg, &mut out)?` |

Expand All @@ -20,13 +20,13 @@ Drop `xchacha20poly1305` from the feature list if you don't use the 192-bit-nonc
```toml
# Before
[dependencies]
chacha20poly1305 = "0.10"
chacha20poly1305 = "0.11"
```

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

## Algorithm map
Expand Down
8 changes: 4 additions & 4 deletions docs/migration/RustCrypto/ed25519-dalek.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

> Replace `SigningKey` / `VerifyingKey` / `Signature` with `Ed25519SecretKey` / `Ed25519PublicKey` / `Ed25519Signature`. Same RFC 8032 algorithm, byte-identical signatures (Ed25519 is deterministic), strict verification on by default.

Verified against `ed25519-dalek = "2.2.0"` and the `rscrypto` 0.5.0 line.
Verified against `ed25519-dalek = "2.2.0"` and the `rscrypto` 0.7.8 line.
Evidence: `tests/ed25519_rfc8032_vectors.rs`, `tests/ed25519_oracle.rs`, and `tests/ed25519_wycheproof.rs`.

## TL;DR

| | Before (`ed25519-dalek` 2.x) | After (`rscrypto` 0.5.0) |
| | Before (`ed25519-dalek` 2.x) | After (`rscrypto` 0.7.8) |
|---|---|---|
| Cargo dep | `ed25519-dalek = "2.2"` | `rscrypto = { version = "0.5.0", features = ["ed25519"] }` |
| Cargo dep | `ed25519-dalek = "2.2"` | `rscrypto = { version = "0.7.8", features = ["ed25519"] }` |
| Import | `use ed25519_dalek::{SigningKey, Signer, Verifier};` | `use rscrypto::{Ed25519SecretKey, Ed25519PublicKey, Ed25519Signature};` |
| Sign | `signing_key.sign(msg)` | `secret.sign(msg)` |
| Verify | `verifying_key.verify_strict(msg, &sig)?` | `public_key.verify(msg, &sig)?` |
Expand All @@ -25,7 +25,7 @@ ed25519-dalek = "2.2"
```toml
# After
[dependencies]
rscrypto = { version = "0.5.0", features = ["ed25519"] }
rscrypto = { version = "0.7.8", features = ["ed25519"] }
```

The `ed25519` feature implies `sha2` (Ed25519 uses SHA-512 internally per RFC 8032).
Expand Down
8 changes: 4 additions & 4 deletions docs/migration/RustCrypto/hkdf.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

> Replace `Hkdf::<Sha256>::new(Some(salt), ikm)` with `HkdfSha256::new(salt, ikm)`. The `Option<&[u8]>` salt becomes a plain `&[u8]` (empty slice == "no salt"); fused one-shot `HkdfSha256::derive_array::<N>(...)` collapses extract+expand.

Verified against `hkdf = "0.13.0"` and the `rscrypto` 0.6.4 line.
Verified against `hkdf = "0.13.0"` and the `rscrypto` 0.7.8 line.
Evidence: `tests/hkdf_sha256_vectors.rs`, `tests/hkdf_sha384_vectors.rs`, `tests/hkdf_sha512_vectors.rs`, the HKDF proptests, and `tests/hkdf_wycheproof.rs`.

## TL;DR

| | Before (`hkdf` 0.13.x) | After (`rscrypto` 0.6.4) |
| | Before (`hkdf` 0.13.x) | After (`rscrypto` 0.7.8) |
|---|---|---|
| Cargo dep | `hkdf = "0.13"` + `sha2 = "0.11"` | `rscrypto = { version = "0.6.4", features = ["hkdf"] }` |
| Cargo dep | `hkdf = "0.13"` + `sha2 = "0.11"` | `rscrypto = { version = "0.7.8", features = ["hkdf"] }` |
| Import | `use hkdf::Hkdf; use sha2::Sha256;` | `use rscrypto::HkdfSha256;` |
| Call | `Hkdf::<Sha256>::new(Some(salt), ikm).expand(info, &mut okm)?` | `HkdfSha256::new(salt, ikm).expand(info, &mut okm)?` |

Expand All @@ -25,7 +25,7 @@ sha2 = "0.11"
```toml
# After
[dependencies]
rscrypto = { version = "0.6.4", features = ["hkdf"] }
rscrypto = { version = "0.7.8", features = ["hkdf"] }
```

The `hkdf` feature implies `hmac` which implies `sha2`.
Expand Down
Loading