Skip to content

Commit 37306f2

Browse files
committed
crypto(matryoshka): wasm32+simd128 backend — same ndarray::simd::U32x16 source, browser lane
Completes the matryoshka's browser half. The fork's `ndarray_simd` backend cfg gates widen from x86_64+avx512f to `any(all(x86_64, avx512f), all(wasm32, simd128))` (backends.rs module decl + lib.rs::process_with_backend dispatch), and the target-scoped ndarray dep to `cfg(any(target_arch="x86_64", target_arch="wasm32"))`. The SAME src/backends/ndarray_simd.rs source now drives BOTH hardware lanes — the AVX-512 `__m512i` lane on a server build and the native `[U32x4; 4]` lane on a wasm32+simd128 browser build — because the backend only ever uses `U32x16` ops, and ndarray's polyfill lowers `U32x16` to the right lane per target. aarch64 and other fork builds still never pull ndarray (they use RustCrypto's backends). Verified: - `cargo build --manifest-path vendor/chacha20/Cargo.toml --target wasm32-unknown-unknown --lib` with `-Ctarget-feature=+simd128` compiles ndarray-for-wasm (std, minimal features) + the wasm backend clean; - x86 avx512 RustCrypto vectors still 9/9 green through ndarray_simd after the cfg widening. Bit-exact by composition: the ndarray_simd source is RFC-8439-proven on x86, and the wasm U32x16 [U32x4;4] lane is node-proven by the existing wasm_simd CI job. CI: added a compile-guard step to the wasm_simd job (build the fork for wasm32+simd128) so a future ndarray::simd change that broke the wasm backend fails there. A consumer building `encryption` as a wasm cdylib with +simd128 now gets the accelerated XChaCha20-Poly1305 keystream transparently — the browser goal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6pT32kk6pnuAAqR3JiYqu
1 parent 7781028 commit 37306f2

5 files changed

Lines changed: 38 additions & 15 deletions

File tree

.claude/CHACHA20_MATRYOSHKA_PLAN.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,18 @@ below **superseded it** — it is now **RETIRED** (2026-07-12, see below).
7171
build** (`.cargo/config-avx512.toml` / `-Ctarget-cpu=x86-64-v4`); the default v3
7272
build uses RustCrypto's own avx2 backend (fast, vetted). This is correct and
7373
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`.
74+
- ~~**wasm browser backend.**~~ **DONE 2026-07-12** — the fork's `ndarray_simd`
75+
cfg gates were widened to `any(all(x86_64, avx512f), all(wasm32, simd128))` in
76+
`backends.rs` + `lib.rs::process_with_backend`, and the ndarray dep to
77+
`cfg(any(x86_64, wasm32))`. The SAME `ndarray_simd.rs` source now drives the
78+
AVX-512 `__m512i` lane (server) and the native wasm `[U32x4;4]` lane (browser).
79+
Verified: `cargo build --manifest-path vendor/chacha20/Cargo.toml --target
80+
wasm32-unknown-unknown --lib` with `+simd128` compiles ndarray-for-wasm + the
81+
wasm backend clean; x86 avx512 vectors still 9/9 green after the widening. Bit-
82+
exact by composition (the `ndarray_simd` source is RFC-proven on x86; the wasm
83+
`U32x16` lane is node-proven by the `wasm_simd` CI job). A CI compile-guard step
84+
was added to the `wasm_simd` job. A consumer building `encryption` as a wasm
85+
cdylib with `+simd128` now gets the accelerated keystream transparently.
7986
- **cross-repo `[patch]` (next):** `[patch]` does not transit, so MedCare-rs (and
8087
any other consumer building `encryption` for avx512) needs its own
8188
`[patch.crates-io] chacha20 = { path = "vendor/chacha20" }` pointing at a vendored

.github/workflows/ci.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ jobs:
134134
node-version: "22"
135135
- name: wasm SIMD parity (build + node run)
136136
run: ./scripts/wasm-parity.sh
137+
# Guard the wasm matryoshka: the chacha20 fork's ndarray_simd backend must
138+
# keep compiling for wasm32+simd128 (it rides the same U32x16 lane the node
139+
# parity step above proves bit-exact). A future ndarray::simd change that
140+
# broke the wasm backend build would fail here.
141+
- name: wasm matryoshka backend (chacha20 fork) compiles for wasm32+simd128
142+
run: RUSTFLAGS="-C target-feature=+simd128" cargo build --manifest-path vendor/chacha20/Cargo.toml --target wasm32-unknown-unknown --lib
137143

138144
neon_simd:
139145
# The x86 `cargo test` suite never touches `simd_neon.rs` (it is cfg'd to

vendor/chacha20/Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,12 @@ zeroize = ["cipher/zeroize"]
5050
[target."cfg(any(target_arch = \"x86_64\", target_arch = \"x86\"))".dependencies.cpufeatures]
5151
version = "0.2"
5252

53-
# The matryoshka's one non-upstream dependency: the AVX-512 backend rides
54-
# `ndarray::simd::U32x16`. Scoped to x86_64 so wasm / aarch64 / other builds of
55-
# this crate never pull ndarray (they fall through to the upstream backends);
56-
# `simd` needs only ndarray's `std` feature, not the full HPC default set.
57-
[target."cfg(target_arch = \"x86_64\")".dependencies.ndarray]
53+
# The matryoshka's one non-upstream dependency: the ndarray_simd backend rides
54+
# `ndarray::simd::U32x16` (AVX-512 lane on x86_64, native [U32x4;4] on wasm32).
55+
# Scoped to x86_64 + wasm32 so aarch64 / other builds of this crate never pull
56+
# ndarray (they fall through to the upstream backends); `simd` needs only
57+
# ndarray's `std` feature, not the full HPC default set.
58+
[target."cfg(any(target_arch = \"x86_64\", target_arch = \"wasm32\"))".dependencies.ndarray]
5859
path = "../.."
5960
default-features = false
6061
features = ["std"]

vendor/chacha20/src/backends.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ use cfg_if::cfg_if;
33
cfg_if! {
44
if #[cfg(chacha20_force_soft)] {
55
pub(crate) mod soft;
6-
} else if #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] {
7-
// AdaWorldAPI matryoshka: on an AVX-512 build the keystream double-round
8-
// rides `ndarray::simd::U32x16` (the polyfill lowers it to `__m512i`).
6+
} else if #[cfg(any(
7+
all(target_arch = "x86_64", target_feature = "avx512f"),
8+
all(target_arch = "wasm32", target_feature = "simd128"),
9+
))] {
10+
// AdaWorldAPI matryoshka: the keystream double-round rides
11+
// `ndarray::simd::U32x16` — the polyfill lowers it to `__m512i` on an
12+
// AVX-512 build (server) and to the native `[U32x4; 4]` lane on a
13+
// wasm32+simd128 build (browser). Same source, two hardware lanes.
914
// Takes precedence over the runtime-detected AVX2/SSE2 path below.
1015
pub(crate) mod ndarray_simd;
1116
} else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {

vendor/chacha20/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,12 @@ impl<R: Unsigned> StreamCipherCore for ChaChaCore<R> {
263263
cfg_if! {
264264
if #[cfg(chacha20_force_soft)] {
265265
f.call(&mut backends::soft::Backend(self));
266-
} else if #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] {
267-
// AdaWorldAPI matryoshka: keystream via ndarray::simd::U32x16.
266+
} else if #[cfg(any(
267+
all(target_arch = "x86_64", target_feature = "avx512f"),
268+
all(target_arch = "wasm32", target_feature = "simd128"),
269+
))] {
270+
// AdaWorldAPI matryoshka: keystream via ndarray::simd::U32x16
271+
// (AVX-512 lane on x86_64, native [U32x4; 4] lane on wasm32).
268272
f.call(&mut backends::ndarray_simd::Backend(self));
269273
} else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
270274
cfg_if! {

0 commit comments

Comments
 (0)