-
Notifications
You must be signed in to change notification settings - Fork 0
simd: ChaCha20 hardware-accel + U32x16 ARX lane + wasm parity CI (crypto-SIMD arc) #240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7923c17
564d62f
2260363
22c16de
6991356
65901e0
98f28f5
538495e
8c63223
261f736
5a914c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| # ChaCha20 SIMD via the `U32x16` ARX lane — matryoshka usage + open work | ||
|
|
||
| > Handoff doc. What this branch shipped, what's deferred, and the **matryoshka** | ||
| > plan for *using* the `U32x16` lane to accelerate ChaCha20 without owning any | ||
| > crypto algorithm. Operator-ratified design (2026-07-11 session). | ||
|
|
||
| ## The doctrine (why this shape) | ||
|
|
||
| - **RustCrypto owns the algorithm; ndarray owns the SIMD.** We do NOT hand-roll | ||
| ChaCha20/Poly1305/the XChaCha20-Poly1305 AEAD. Rolling your own AEAD (HChaCha20 | ||
| + Poly1305 + framing) is the footgun; it is forbidden. | ||
| - The `encryption` crate (`crates/encryption`, RustCrypto wrappers) stays | ||
| **unchanged**. See its `aead.rs` module doc for why the AEAD is not re-wired. | ||
| - The only crypto-algorithm SIMD we accelerate is the **ChaCha20 keystream ARX | ||
| core**, and even that is done by feeding RustCrypto's *own* backend a new | ||
| SIMD lane — never by re-implementing the cipher. | ||
|
|
||
| ## DONE on this branch (the lane is ready) | ||
|
|
||
| `ndarray::simd::U32x16` now carries the full ARX triple — `Add` + `BitXor` + | ||
| `rotate_left` — on every tier, so a ChaCha20 backend can ride it: | ||
|
|
||
| | tier | `U32x16` backing | `rotate_left` | verified | | ||
| |---|---|---|---| | ||
| | avx512 (server) | `__m512i` | `_mm512_rolv_epi32` (VPROLVD) | executed, parity-green | | ||
| | wasm128 (browser) | `[U32x4; 4]` (`v128`) | `v128_or(u32x4_shl, u32x4_shr)` | full-lib wasm build + **node parity** | | ||
| | avx2 | `[u32; 16]` polyfill (native 2×`__m256i` = deferred TD-SIMD-3) | `u32::rotate_left` loop | compiles; == reference | | ||
| | scalar / nightly | `[u32; 16]` / `core::simd` | `u32::rotate_left` / shift-or | completeness tier + reference | | ||
|
|
||
| CI: `wasm_simd` job (`.github/workflows/ci.yaml` + `scripts/wasm-parity.sh` + | ||
| `crates/wasm-simd-parity/`) builds the real `ndarray::simd` wasm types and runs a | ||
| lane-by-lane parity selfcheck under **node** — the standing guard for the wasm | ||
| tier (invisible to the x86 `cargo test`). Extend its per-lane blocks as lanes land. | ||
|
|
||
| The stand-alone `src/simd_crypto.rs` (`chacha20_block`/`chacha20_keystream`, | ||
| scalar+AVX-512+wasm128, RustCrypto-parity-proven) is the *interim* primitive; the | ||
| matryoshka below **supersedes it** (RustCrypto supplies the rounds, so the | ||
| from-scratch cipher retires once the fork lands). | ||
|
|
||
| ## DEFERRED — TO-DO (next session) | ||
|
|
||
| 1. **Native neon `U32x16 = [U32x4; 4]`.** `simd_neon.rs` has native | ||
| `U32x4(uint32x4_t)` with add/sub/min/max; add `bitxor` (`veorq_u32`) + | ||
| `rotate_left` (shift-or via `vshlq_u32` with variable count vectors, `n%32` | ||
| guard), then compose `U32x16([U32x4; 4])` with `impl Add`/`impl BitXor`/ | ||
| `rotate_left` (fan over 4 lanes, mirror the wasm shape in `simd_wasm.rs`), fix | ||
| `F32x16::to_bits/from_bits` to `from_array`/`to_array`, and swap the `simd.rs` | ||
| aarch64 arm's scalar `U32x16` re-export for the native one. | ||
| 2. **aarch64 cross parity CI.** Generalize `wasm-simd-parity` into a shared | ||
| `simd-parity` harness (or add a sibling) run under **qemu** via `cross` in a | ||
| new `neon_simd` CI job — same selfcheck, closing NEON's identical x86-suite | ||
| blind spot (its `F32x16`/`I8x16` are unverified in CI today too). | ||
| 3. **avx2 native `U32x16`** (2×`__m256i`) — the TD-SIMD-3 lowering; optional, the | ||
| scalar polyfill is correct meanwhile. | ||
|
|
||
| ## The MATRYOSHKA — how the lane gets USED (the finalization) | ||
|
|
||
| Goal: ChaCha20 (and thus the whole `encryption` AEAD stack) accelerated on | ||
| server (AVX-512) + browser (wasm128), with **essentially zero owned crypto code** | ||
| and a **one-file** delta over RustCrypto that is trivial to re-sync on security | ||
| updates. | ||
|
|
||
| **Structure (nesting):** | ||
|
|
||
| ``` | ||
| RustCrypto chacha20 (rounds / constants / counter / StreamBackend / TESTS — VERBATIM, vetted) | ||
| └─ one backend's round body expressed over → ndarray::simd::U32x16 (Add / BitXor / rotate_left — generic, NOT crypto) | ||
| └─ dispatched by ndarray's polyfill → avx512 / wasm128 / neon / scalar | ||
| ``` | ||
|
|
||
| **Steps:** | ||
|
|
||
| 1. **Fork `chacha20`** (the RustCrypto stream-cipher crate; `AdaWorldAPI/chacha20`, | ||
| or vendor). Its backends live in `src/backends/{soft,sse2,avx2,neon}.rs`, each a | ||
| `struct Backend<R>` impl of `StreamBackend` with `gen_ks_block` / | ||
| `gen_par_ks_blocks`. **Clone `avx2.rs` once**; keep soft/sse2/avx2/neon | ||
| untouched. | ||
| 2. **Rewire the clone over `ndarray::simd::U32x16`** — the word-sliced ChaCha | ||
| round (`state[a] = state[a] + state[b]; state[d] = (state[d] ^ state[a]).rotate_left(16);` | ||
| …) written against `U32x16`, `ParBlocksSize = U16`. `ndarray` lowers it to | ||
| AVX-512 (server) / wasm128 (browser) / NEON / scalar automatically. The | ||
| carried file has **no `unsafe`, no intrinsics** — all of that lives once in | ||
| `ndarray::simd` (audited once; only AMX+F16 are byte-asm, neither on this path). | ||
| 3. **Dispatch:** add the generic backend to the fork's `lib.rs` selection. | ||
| Per the ndarray model this is **compile-time** (`cfg(target_feature)`), not | ||
| `is_x86_feature_detected!` — server built `x86-64-v4`, browser built `+simd128`. | ||
| Non-portable per-target binaries by design (SIGILL/validation-fail on a CPU | ||
| built-for-but-absent) — matches how the servers/browsers are actually built. | ||
| 4. **`[patch]` the fork in** (the pattern already used in MedCare-rs for | ||
| `encryption`): `chacha20poly1305 → encryption → ogar-encryption → consumers` | ||
| all accelerate **transitively**, with **zero** change to the AEAD or any | ||
| consumer, because the AEAD just calls `apply_keystream` which now dispatches | ||
| to the new backend. HChaCha20 + Poly1305 + framing stay 100% RustCrypto. | ||
| 5. **Gate bit-exact vs RustCrypto's own `soft` backend** + run its stock test | ||
| vectors — the same "reference + KAT, then vectorize with parity" discipline. | ||
| 6. **Retire `src/simd_crypto.rs`** — the rounds now come from RustCrypto; only | ||
| the `U32x16` lane (not crypto) is ours. | ||
|
|
||
| **Maintenance win:** a RustCrypto security advisory almost never touches the ARX | ||
| lane ops (fixes land in framing/counter/AEAD — all pristine upstream), so the | ||
| one-file delta re-applies with ~zero conflict: bump the vendored `chacha20`, | ||
| re-apply the one backend file, run the stock vectors + the soft parity. And if | ||
| the backend is ever **upstreamed** (RustCrypto would plausibly take an avx512 / | ||
| wasm128 backend), the fork evaporates → maintenance goes to zero. AMX has no | ||
| ChaCha/Poly backend (it is a matrix engine); the upstream targets are avx512 + | ||
| wasm128 (NEON already exists upstream). |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,6 +217,11 @@ approx = { workspace = true, default-features = true } | |
| itertools = { workspace = true } | ||
| ndarray-gen = { workspace = true } | ||
| criterion = { version = "0.5", features = ["html_reports"] } | ||
| # The vetted RustCrypto ChaCha20 stream cipher — used ONLY as the independent | ||
| # oracle for the `chacha20_rustcrypto_parity` integration test that pins | ||
| # `ndarray::simd::chacha20_keystream` byte-for-byte against a trusted reference. | ||
| # Same version chacha20poly1305 0.10 pulls transitively (see Cargo.lock). | ||
| chacha20 = "0.9" | ||
|
|
||
| [[bench]] | ||
| name = "append" | ||
|
|
@@ -402,7 +407,7 @@ members = [ | |
| "ndarray-rand", | ||
| "crates/*", | ||
| ] | ||
| exclude = ["crates/burn"] | ||
| exclude = ["crates/burn", "crates/wasm-simd-parity"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Keep the excluded parity crate under format and Clippy coverage. Excluding Either keep it in the workspace or add standalone format and Clippy steps to the As per coding guidelines, Rust code must be formatted with 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| default-members = [ | ||
| ".", | ||
| "ndarray-rand", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Harden the new job’s GitHub token permissions.
Line 125 enables checkout credential persistence, and the job inherits broad default permissions. A compromised build script could read the persisted token; this job only needs read access to repository contents. Set
persist-credentials: falseand grant onlycontents: read.Proposed CI hardening
📝 Committable suggestion
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 125-125: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 115-136: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
🤖 Prompt for AI Agents
Source: Linters/SAST tools