Skip to content

Commit de4e4d1

Browse files
committed
audit: measure what vendor/chacha20 actually reaches
The vendored chacha20 tree is described in three manifests as accelerating consumers' XChaCha20-Poly1305 keystreams. Measured, it reaches far less than that, along two independent axes that had been conflated. Patch reach: [patch.crates-io] applies only from a top-level workspace manifest, so ndarray's patch does not travel with ndarray. Two repos link the vendored source -- ndarray itself and MedCare-rs, which declares its own. lance-graph, OGAR, tesseract-rs, stockfish-rs and a2ui-rs link the registry crate. Backend reach: the added ndarray_simd backend is gated on the compile-time avx512f feature cfg. The workspace pins x86-64-v3, which supplies avx2 and not avx512f, and MedCare-rs pins nothing at all. So no default build of any repo here compiles that backend -- only an opt-in v4 build or wasm32+simd128 does. The fallthrough to RustCrypto's own backends is correct and MedCare's comment describes it accurately; the scope simply keeps being restated one size too large. Also records that AdaWorldAPI/stream-ciphers -- the fork the P0 rule names -- is a single-commit mirror of upstream at chacha20 0.10.1 carrying no AdaWorldAPI delta and no ndarray backend, while the vendored tree is a hand copy of 0.9.1 with its repository field rewritten. The two are a cipher major apart, and upstream 0.10.1 now ships its own avx512 backend in the niche ndarray_simd was written to fill. One inaccuracy is recorded, not fixed: the root manifest attributes target-cpu=x86-64-v4 to the workspace, which pins v3. Manifest string edits need a ruling, so the finding is filed instead of applied.
1 parent 87b9b4c commit de4e4d1

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# `vendor/chacha20` — what it actually reaches
2+
3+
> **Status: MEASURED, 2026-07-29.** Every row below is read from
4+
> `Cargo.lock`, a `rustc --print cfg`, or a manifest on disk. No estimates.
5+
6+
## READ BY:
7+
- Anyone about to re-sync, extend, or delete `vendor/chacha20`
8+
- Anyone citing "the AdaWorldAPI chacha20 fork" as an accelerated dependency
9+
- Anyone applying the P0 fork rule to a crate that also has a vendored copy
10+
11+
## P0 TRIGGER
12+
About to state that a consumer's ChaCha20 keystream is accelerated because
13+
ndarray patches `chacha20`? **Read the two reach tables first. The patch and
14+
the acceleration have different, and both narrower-than-assumed, reach.**
15+
16+
---
17+
18+
## Two different questions, two different answers
19+
20+
"Does the fork reach consumer X?" is really two questions, and conflating
21+
them is what produced the overstated claims in the manifest comments.
22+
23+
1. **Does the `[patch.crates-io]` apply** — i.e. does the build link the
24+
vendored source at all?
25+
2. **Does `ndarray_simd` compile** — i.e. does the added backend get
26+
selected, or does the vendored crate fall through to RustCrypto's own?
27+
28+
A build can answer yes to (1) and no to (2). Most do.
29+
30+
## Reach of the patch
31+
32+
`[patch.crates-io]` takes effect **only from the top-level workspace
33+
manifest**. It is ignored in a dependency's manifest, so ndarray's patch
34+
does not travel with ndarray — each consumer must declare its own.
35+
36+
| repo | declares a chacha20 patch | links the vendored source |
37+
|---|---|---|
38+
| ndarray | yes — `Cargo.toml:488` | **yes** |
39+
| MedCare-rs | yes — `vendor/ndarray/vendor/chacha20` | **yes** |
40+
| lance-graph | no | no — registry crate |
41+
| OGAR | no | no — registry crate |
42+
| tesseract-rs | no | no — registry crate |
43+
| stockfish-rs | no | no — registry crate |
44+
| a2ui-rs | no | no — registry crate |
45+
46+
Evidence for the two "yes" rows: ndarray's `Cargo.lock` carries
47+
`chacha20 0.9.1` with **no `source` line**, which is how a path override
48+
appears; MedCare-rs's root manifest declares
49+
`chacha20 = { path = "vendor/ndarray/vendor/chacha20" }`, and its
50+
`vendor/ndarray` symlink canonicalizes to the same checkout, so one
51+
`ndarray` node resolves.
52+
53+
Evidence for the five "no" rows: `grep 'chacha20'` and `grep '\[patch'`
54+
across each root manifest. lance-graph's manifest contains only a comment
55+
*about* patching, explicitly recording that no `[patch]` block exists there.
56+
57+
## Reach of the `ndarray_simd` backend
58+
59+
`vendor/chacha20/src/backends.rs` selects the added backend under
60+
61+
```rust
62+
all(target_arch = "x86_64", target_feature = "avx512f", …) // or
63+
all(target_arch = "wasm32", target_feature = "simd128")
64+
```
65+
66+
`avx512f` is a **compile-time** feature cfg, so it is present only if the
67+
build pins a target-cpu that includes it. Measured:
68+
69+
```
70+
$ rustc --print cfg -Ctarget-cpu=x86-64-v3 | grep avx
71+
target_feature="avx2" # <- no avx512f
72+
$ rustc --print cfg -Ctarget-cpu=x86-64-v4 | grep avx512f
73+
target_feature="avx512f"
74+
```
75+
76+
| build | target-cpu | `avx512f` | backend compiled |
77+
|---|---|---|---|
78+
| ndarray, default | `x86-64-v3` (`.cargo/config.toml`) | absent | RustCrypto soft/avx2/sse2 |
79+
| ndarray, `--config .cargo/config-avx512.toml` | `x86-64-v4` | present | **`ndarray_simd`** |
80+
| MedCare-rs, default | none — no `.cargo/config.toml` at all | absent | RustCrypto soft/avx2/sse2 |
81+
| wasm32 + `simd128` || n/a | **`ndarray_simd`** |
82+
| the five unpatched repos ||| not present in the source they link |
83+
84+
**No default build of any repo in the workspace runs `ndarray_simd`.** Two
85+
opt-in configurations do: an explicitly-selected AVX-512 x86_64 build, and a
86+
wasm32 build with `simd128`.
87+
88+
This is not a defect — the fallthrough is correct and the comments in
89+
MedCare-rs's manifest describe it accurately ("non-avx512 builds fall through
90+
to RustCrypto's own backends"). It is a scope fact that keeps being stated
91+
one size too large.
92+
93+
## One manifest comment overstates it
94+
95+
`Cargo.toml:481-482` reads:
96+
97+
> This transitively accelerates the `encryption` crate's XChaCha20-Poly1305
98+
> keystream … **on any x86_64+avx512f build (the workspace's
99+
> `target-cpu=x86-64-v4`)**.
100+
101+
The workspace's pinned target-cpu is **v3**, in `.cargo/config.toml`. `v4`
102+
is the opt-in `.cargo/config-avx512.toml`. The conditional half of the
103+
sentence is right; the parenthetical asserts the default build satisfies it,
104+
and it does not.
105+
106+
**Not edited here.** Manifest string changes in this repo need an operator
107+
ruling; the finding is recorded instead of applied.
108+
109+
## The fork the P0 rule names is not this tree
110+
111+
`AdaWorldAPI/stream-ciphers` exists and is now cloned in-session.
112+
113+
| | `AdaWorldAPI/stream-ciphers` | `ndarray/vendor/chacha20` |
114+
|---|---|---|
115+
| what it is | mirror of RustCrypto upstream | hand-copied source tree |
116+
| head | `5f3430b` "Release chacha20 v0.10.1 (#574)" ||
117+
| branches | `master` only ||
118+
| chacha20 version | **0.10.1** | **0.9.1** |
119+
| `cipher` | 0.5 | 0.4.4 |
120+
| edition / rust-version | 2024 / 1.85 | 2021 / 1.95 |
121+
| `backends/ndarray_simd.rs` | **absent** | present |
122+
| `backends/avx512.rs` | **present (upstream's own)** | absent |
123+
| `repository` field | RustCrypto | rewritten to `AdaWorldAPI/ndarray` |
124+
125+
Three consequences worth stating plainly:
126+
127+
1. **The fork carries no AdaWorldAPI delta.** One commit, no branches beyond
128+
`master`. It is a mirror, not a modified fork — so "depend on the fork"
129+
and "depend on upstream" are currently the same bytes.
130+
2. **The vendored tree is not a checkout of the fork**, and is one minor
131+
release behind it. It cannot be re-synced by `git pull`; the re-sync
132+
procedure its own header describes (bump the source, re-apply the backend
133+
and the four cfg branches, re-run the vectors) is a manual port, and
134+
0.9.1 → 0.10.1 crosses a `cipher` major (0.4 → 0.5).
135+
3. **Upstream now ships its own AVX-512 backend.** `backends/avx512.rs`
136+
at 0.10.1 occupies the exact niche `ndarray_simd.rs` was written to fill.
137+
Whether the ndarray-lane version still earns its place is a measurement
138+
question — and the instrument for it already exists
139+
(`.claude/knowledge/simd-codegen-oracle/`), with the relevant lane already
140+
measured: the u32 ARX triple on `U32x16` hits the AVX2 instruction floor
141+
(`td-t22-asm-investigation.md`).
142+
143+
## What is NOT claimed here
144+
145+
- Not that the vendored backend is wrong. It is untested by default builds,
146+
which is a different statement.
147+
- Not that the patch should be removed. That is a decision, and it depends on
148+
(3) above plus the AVX-512 deployment question, neither of which this audit
149+
settles.
150+
- Not that MedCare-rs's wiring is incorrect. It is the one consumer that
151+
declared its patch deliberately and documented the fallthrough honestly.
152+
153+
## Open, for an operator ruling
154+
155+
1. **Point `vendor/chacha20` at the fork, or keep the copy?** The P0 rule
156+
says depend on the AdaWorldAPI fork. Today the tree is neither — a copy of
157+
a version the fork does not carry, with `repository` rewritten to point at
158+
ndarray itself.
159+
2. **Does `ndarray_simd` still beat upstream's own `avx512.rs`?** Answerable
160+
with the oracle. Unmeasured today, on both sides.
161+
3. **The v3/v4 manifest comment** — correct in place, or leave the record and
162+
annotate?

0 commit comments

Comments
 (0)