Skip to content

Commit 83d2ca7

Browse files
committed
crypto(matryoshka): vendor chacha20 fork over ndarray::simd::U32x16, retire simd_crypto.rs
The AdaWorldAPI ChaCha20 "matryoshka": RustCrypto owns the cipher; ndarray owns only the U32x16 ARX lane. The standalone from-scratch `src/simd_crypto.rs` (raw _mm512_*/u32x4_* intrinsics) is RETIRED and superseded by a one-backend fork. vendor/chacha20/ — fork of RustCrypto chacha20 0.9.1 (name/version kept for [patch]; own [workspace] table, excluded from the parent workspace). Upstream cipher verbatim (state schedule, counter, XChaCha, RNG, soft/sse2/avx2/neon). The ONE delta: src/backends/ndarray_simd.rs — the transpose block16 (16 blocks in parallel, working vector w holds word w of every block across its 16 lanes, counter word carries lane-index 0..=15) expressed over ndarray::simd::U32x16: every quarter-round is a pure U32x16 + / ^ / rotate_left, NO cross-lane shuffle, NO raw intrinsics, NO unsafe (all of that lives once inside ndarray::simd). Wrapped in RustCrypto's StreamBackend (ParBlocksSize = U16), generic over R. Selected at compile time under cfg(all(target_arch="x86_64", target_feature= "avx512f")) via four cfg branches in backends.rs + lib.rs (Tokens / new / dispatch). The ndarray dep is target.'cfg(target_arch="x86_64")' + default-features=false, features=["std"] so wasm/aarch64 fork builds never pull ndarray. [patch.crates-io] chacha20 = { path = "vendor/chacha20" } folds it transitively under chacha20poly1305 -> encryption with zero change to the AEAD. Parity — triple-gated, GREEN: - the fork's own RustCrypto RFC 8439 vectors (chacha20/xchacha20 encryption + keystream, core, seek) pass THROUGH ndarray_simd under -Ctarget-cpu=x86-64-v4; - cargo test -p encryption (23 AEAD tests incl. XChaCha20-Poly1305 round-trip, bit-flip-fails, wrong-key-fails) passes on the default v3 build (RustCrypto avx2 fallback) and the avx512 build (ndarray_simd). Retired: deleted src/simd_crypto.rs + tests/chacha20_rustcrypto_parity.rs + the chacha20="0.9" dev-dep; removed the ndarray::simd::{chacha20_block, chacha20_keystream, chacha20_state} surface (zero downstream consumers). aead.rs doc rewritten to the transitive-acceleration framing. Note: the workspace default is target-cpu=x86-64-v3 (avx2), so ndarray_simd activates only on an avx512 build; the wasm-fork backend + cross-repo [patch] propagation (MedCare-rs) are documented follow-ups in .claude/CHACHA20_MATRYOSHKA_PLAN.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6pT32kk6pnuAAqR3JiYqu
1 parent a503291 commit 83d2ca7

26 files changed

Lines changed: 2561 additions & 658 deletions

.claude/CHACHA20_MATRYOSHKA_PLAN.md

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,54 @@ CI: `wasm_simd` job (`.github/workflows/ci.yaml` + `scripts/wasm-parity.sh` +
3232
lane-by-lane parity selfcheck under **node** — the standing guard for the wasm
3333
tier (invisible to the x86 `cargo test`). Extend its per-lane blocks as lanes land.
3434

35-
The stand-alone `src/simd_crypto.rs` (`chacha20_block`/`chacha20_keystream`,
36-
scalar+AVX-512+wasm128, RustCrypto-parity-proven) is the *interim* primitive; the
37-
matryoshka below **supersedes it** (RustCrypto supplies the rounds, so the
38-
from-scratch cipher retires once the fork lands).
35+
The stand-alone `src/simd_crypto.rs` was the *interim* primitive; the matryoshka
36+
below **superseded it** — it is now **RETIRED** (2026-07-12, see below).
37+
38+
## ✅ SHIPPED 2026-07-12 — fork landed + `simd_crypto.rs` retired
39+
40+
- **Native NEON `U32x16 = [U32x4; 4]`**`U32x4` gained `bitxor`(veorq_u32) +
41+
`rotate_left`(vshlq_u32 shift-or); composed `U32x16` with Add/BitXor/
42+
rotate_left. aarch64 now `cargo check`-clean on stable (also fixed the
43+
pre-existing `u16x8` alias + the nightly-only `vdotq_s32` breakage → stable
44+
widening NEON). Every tier of the ARX lane is now native.
45+
- **The fork** lives at `vendor/chacha20/` (name/version kept `chacha20`/`0.9.1`,
46+
excluded from the workspace, own `[workspace]` table). Upstream cipher verbatim;
47+
the ONE delta is `src/backends/ndarray_simd.rs` — the transpose block16 (16
48+
blocks ∥, word→16-lane vector, counter lane-index) over `ndarray::simd::U32x16`,
49+
pure `+`/`^`/`rotate_left`, **no raw intrinsics, no `unsafe`**. Selected at
50+
compile time under `cfg(all(x86_64, avx512f))` in `backends.rs` +
51+
`lib.rs::process_with_backend` (Tokens/new/dispatch — four cfg branches). The
52+
`ndarray` dep is `target.'cfg(target_arch="x86_64")'` + `default-features=false,
53+
features=["std"]` so wasm/aarch64 fork builds never pull ndarray.
54+
- **`[patch.crates-io] chacha20 = { path = "vendor/chacha20" }`** in the ndarray
55+
root `Cargo.toml` folds it under `chacha20poly1305 → encryption` transparently.
56+
- **Parity — triple-gated, GREEN:** the fork's own RustCrypto RFC 8439 vectors
57+
(`chacha20_encryption`/`_keystream`, `xchacha20_*`, `chacha20_core`, seek) pass
58+
**through `ndarray_simd`** under `-Ctarget-cpu=x86-64-v4`; `cargo test -p
59+
encryption` (23 AEAD tests incl. XChaCha20-Poly1305 round-trip / bit-flip /
60+
wrong-key) passes on both the default (v3→RustCrypto avx2 fallback) and avx512
61+
(→`ndarray_simd`) builds.
62+
- **RETIRED:** deleted `src/simd_crypto.rs` + `tests/chacha20_rustcrypto_parity.rs`
63+
+ the `chacha20 = "0.9"` dev-dep; removed the `ndarray::simd::{chacha20_block,
64+
chacha20_keystream, chacha20_state}` surface. RustCrypto owns the cipher; ndarray
65+
owns only the `U32x16` lane. `aead.rs` doc updated to the transitive-acceleration
66+
framing.
67+
68+
### Reality note + remaining follow-ups
69+
- **The workspace default is `target-cpu=x86-64-v3` (AVX2), NOT v4** — the CLAUDE.md
70+
"v4 mandatory" line is stale. So `ndarray_simd` activates only on an **avx512
71+
build** (`.cargo/config-avx512.toml` / `-Ctarget-cpu=x86-64-v4`); the default v3
72+
build uses RustCrypto's own avx2 backend (fast, vetted). This is correct and
73+
safe — the matryoshka is the *server-avx512* accelerator.
74+
- **wasm browser backend (next):** add a sibling `#[cfg(all(target_arch="wasm32",
75+
target_feature="simd128"))]` branch selecting an `ndarray_simd` backend (same
76+
`U32x16` source; the wasm arm is native `[U32x4;4]`) + a `target.'cfg(wasm32)'`
77+
ndarray dep. Gated on verifying `ndarray/std` builds inside the encryption
78+
`cdylib` for `wasm32-unknown-unknown`.
79+
- **cross-repo `[patch]` (next):** `[patch]` does not transit, so MedCare-rs (and
80+
any other consumer building `encryption` for avx512) needs its own
81+
`[patch.crates-io] chacha20 = { path = "vendor/chacha20" }` pointing at a vendored
82+
copy of the fork, to inherit the acceleration. Documented, low-risk.
3983

4084
## DEFERRED — TO-DO (next session)
4185

Cargo.lock

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,6 @@ approx = { workspace = true, default-features = true }
217217
itertools = { workspace = true }
218218
ndarray-gen = { workspace = true }
219219
criterion = { version = "0.5", features = ["html_reports"] }
220-
# The vetted RustCrypto ChaCha20 stream cipher — used ONLY as the independent
221-
# oracle for the `chacha20_rustcrypto_parity` integration test that pins
222-
# `ndarray::simd::chacha20_keystream` byte-for-byte against a trusted reference.
223-
# Same version chacha20poly1305 0.10 pulls transitively (see Cargo.lock).
224-
chacha20 = "0.9"
225220

226221
[[bench]]
227222
name = "append"
@@ -407,7 +402,7 @@ members = [
407402
"ndarray-rand",
408403
"crates/*",
409404
]
410-
exclude = ["crates/burn", "crates/wasm-simd-parity"]
405+
exclude = ["crates/burn", "crates/wasm-simd-parity", "vendor/chacha20"]
411406
default-members = [
412407
".",
413408
"ndarray-rand",
@@ -449,3 +444,15 @@ tag-name = "{{version}}"
449444
features = ["approx", "serde", "rayon"]
450445
# Define the configuration attribute `docsrs`
451446
rustdoc-args = ["--cfg", "docsrs"]
447+
448+
# ── ChaCha20 matryoshka (AdaWorldAPI fork) ──────────────────────────────────
449+
# Fold the AVX-512/ndarray::simd-accelerated `chacha20` fork over the registry
450+
# crate. This transitively accelerates the `encryption` crate's XChaCha20-Poly1305
451+
# keystream (chacha20poly1305 -> chacha20) with zero change to the AEAD, on any
452+
# x86_64+avx512f build (the workspace's `target-cpu=x86-64-v4`). Non-x86 builds of
453+
# the fork fall through to RustCrypto's own backends and never pull ndarray.
454+
# NOTE: a "Patch `chacha20` was not used in the crate graph" note appears on
455+
# scoped builds that don't pull `encryption` (e.g. `-p ndarray`); it is benign —
456+
# the patch is exercised by `cargo test -p encryption` and the full workspace.
457+
[patch.crates-io]
458+
chacha20 = { path = "vendor/chacha20" }

crates/encryption/src/aead.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@
66
//! and a server. Callers normally use [`crate::envelope`], which manages
77
//! nonce generation and layout; this module is the raw primitive.
88
//!
9-
//! ## Deliberately NOT hand-composed from `ndarray::simd::chacha20_keystream`
9+
//! ## Acceleration is transitive (the matryoshka), never hand-composed
1010
//!
11-
//! `ndarray` now ships a hardware-accelerated ChaCha20 keystream
12-
//! (`ndarray::simd::chacha20_keystream`, AVX-512 / wasm128, byte-parity-proven
13-
//! against RustCrypto). It is tempting to swap this AEAD's keystream to that
14-
//! primitive for speed — **do not.** XChaCha20-Poly1305 is not just a keystream:
15-
//! it is HChaCha20 subkey derivation + the Poly1305 one-time-key + MAC framing +
16-
//! the AAD/length encoding. Re-wiring only the keystream means re-implementing
17-
//! that authenticated composition by hand, which is exactly the "roll your own
18-
//! AEAD" footgun the stack forbids. The vetted RustCrypto `XChaCha20Poly1305`
19-
//! construction stays here. The accelerated primitive is for *raw-stream* use
20-
//! sites whose caller already owns a vetted MAC/framing — not this module.
11+
//! This AEAD stays 100% vetted RustCrypto `XChaCha20Poly1305` — HChaCha20 subkey
12+
//! derivation + Poly1305 one-time-key + MAC framing + AAD/length encoding — and
13+
//! we do **not** hand-wire a raw keystream into it (that would be the "roll your
14+
//! own AEAD" footgun the stack forbids). Hardware acceleration instead arrives
15+
//! *underneath*, transparently: the AdaWorldAPI `chacha20` fork (`vendor/chacha20/`)
16+
//! replaces one backend of the transitive `chacha20` dependency with a keystream
17+
//! double-round expressed over `ndarray::simd::U32x16` (the AVX-512 lane), folded
18+
//! in via `[patch.crates-io]`. So `chacha20poly1305 -> chacha20 -> ndarray::simd`
19+
//! accelerates on an AVX-512 build with zero change to this module, and every
20+
//! RustCrypto RFC 8439 test vector (incl. XChaCha20) still passes through it.
2121
2222
use chacha20poly1305::aead::{Aead, KeyInit, Payload};
2323
use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce};

src/lib.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,10 @@ pub(crate) mod simd_avx512;
246246
#[allow(clippy::all, missing_docs, dead_code, unused_variables, unused_imports)]
247247
pub mod simd_avx2;
248248

249-
// Crypto ARX primitives (ChaCha20 block; Poly1305 / Blake2 to follow) — the
250-
// hardware-acceleration layer the Ada `encryption` crate draws its hot-path
251-
// keystream from. Scalar reference + RFC 8439 KAT land first (arch-agnostic,
252-
// no cfg gate); the AVX-512 / wasm128 / NEON vectorized backends slot in behind
253-
// the same signature, parity-checked against the KAT (W1a contract).
254-
pub mod simd_crypto;
249+
// (The standalone `simd_crypto` ChaCha20 primitive was RETIRED — superseded by
250+
// the AdaWorldAPI `chacha20` fork whose backend rides `ndarray::simd::U32x16`,
251+
// `[patch]`ed under the `encryption` AEAD. RustCrypto owns the cipher; ndarray
252+
// owns only the U32x16 ARX lane. See `vendor/chacha20/` + `.claude/CHACHA20_MATRYOSHKA_PLAN.md`.)
255253

256254
// Portable-SIMD backend — nightly-only. Wraps `core::simd::*` so miri can
257255
// execute the polyfill paths (intrinsic-based backends are opaque to

src/simd.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -682,12 +682,10 @@ pub use crate::simd_ops::{
682682
sub_f32_inplace,
683683
};
684684

685-
// ChaCha20 ARX keystream — the crypto hot path the `encryption` AEAD draws from.
686-
// `chacha20_block` is the scalar reference (RFC 8439 KAT); `chacha20_keystream`
687-
// is the runtime-dispatched accelerated fill (AVX-512 16-block strides + scalar
688-
// tail), byte-parity-pinned to the reference. Consumers pull from
689-
// `ndarray::simd::*` per the W1a contract.
690-
pub use crate::simd_crypto::{chacha20_block, chacha20_keystream, chacha20_state};
685+
// ChaCha20 keystream is NO LONGER an `ndarray::simd` surface: the AdaWorldAPI
686+
// `chacha20` fork (`vendor/chacha20/`) carries the accelerated backend, riding
687+
// the `U32x16` ARX lane above, and is `[patch]`ed transitively under the
688+
// `encryption` AEAD. RustCrypto owns the cipher; ndarray exposes only the lane.
691689

692690
// ============================================================================
693691
// Tests

0 commit comments

Comments
 (0)