# AWS-LC / BoringSSL / OpenSSL / Cloudflare ASM Ownership Notebook

Started: 2026-06-25

Goal: replace the AWS-LC, BoringSSL, OpenSSL, and Cloudflare-derived assembly
files named in `docs/tasks/asm.md` with rscrypto-owned assembly or intrinsic
kernels that are faster on the measured hot rows, while making the crate leaner
and easier to audit.

This is not a license-header project. A file leaves the external-derived bucket
only when the live implementation is deleted or replaced by rscrypto-owned
source with correctness and benchmark evidence.

## Assumptions

- Scope is the live files in the AWS-LC / BoringSSL / OpenSSL / Cloudflare
  section of `docs/tasks/asm.md`.
- BLAKE3 remains closed for the phase-one pass tracked in
  `docs/tasks/blake3-notebook.md`.
- s2n-bignum and ML-KEM NTT ownership work stay out of this notebook.
- The primary target is more performant owned assembly. Deleting imported
  assembly is only acceptable when an owned Rust intrinsic path already wins or
  is effectively flat on the user-facing rows.
- If an owned candidate loses, that is a tuning signal, not a reason to settle
  for the slower path.
- Do not bulk-port another generated assembly source. If assembly is still
  needed, write a narrow rscrypto-owned kernel for the actual ABI and limb
  widths we use.

## Initial Ledger State

Verified before this notebook was created:

```bash
scripts/check/asm-ledger.sh
```

Output:

```text
asm-ledger: 68 assembly files, 13 owned, 55 external-derived, 0 candidates
```

The original slice missed the x86_64 integrated ChaCha20-Poly1305 file. The
correct eight-file source slice is 21,100 lines of imported assembly:

| Area | Files | Lines | Current role |
| --- | ---: | ---: | --- |
| ChaCha20-Poly1305 AArch64 integrated AEAD | 2 | 6,020 | Release-only macOS/Linux AArch64 seal/open fast path |
| ChaCha20-Poly1305 x86_64 integrated AEAD | 1 | 8,921 | Linux x86_64 AVX2+BMI2 seal/open fast path |
| RSA Montgomery | 3 | 5,408 | AArch64 and x86_64 Montgomery multiply/square/reduce helpers |
| SHA-2 Darwin AArch64 | 2 | 751 | macOS AArch64 SHA-256/SHA-512 multi-block compression |

Current retained state after the SHA-2 deletion slice and later ownership
cleanup:

- 63 tracked `.s` / `.S` files under `src/`.
- 48 tracked assembly files are explicitly external-derived.
- 15 tracked assembly files are explicitly marked `rscrypto-owned`.
- 6 files remain in this notebook's external-derived slice.

## Eight Files

- [remaining] `src/aead/chacha20poly1305/asm/rscrypto_chacha20_poly1305_aarch64_apple_darwin.s`
- [remaining] `src/aead/chacha20poly1305/asm/rscrypto_chacha20_poly1305_aarch64_linux.s`
- [remaining] `src/aead/chacha20poly1305/asm/rscrypto_chacha20_poly1305_x86_64_linux.s`
- [remaining] `src/auth/asm/rscrypto_rsa_bignum_mont_aarch64_elf.s`
- [remaining] `src/auth/asm/rscrypto_rsa_bignum_mont_apple.s`
- [remaining] `src/auth/asm/rscrypto_rsa_x86_64_elf.S`
- [deleted 2026-06-25] `src/hashes/crypto/sha256/asm/rscrypto_sha256_aarch64_apple_darwin.s`
- [deleted 2026-06-25] `src/hashes/crypto/sha512/asm/rscrypto_sha512_aarch64_apple_darwin.s`

## First-Pass Read

SHA-2 is the cleanest first experiment. The Darwin AArch64 SHA-256 and SHA-512
files are only used on macOS; non-macOS AArch64 already uses owned Rust
intrinsic kernels. The first question is whether the owned intrinsic path is
faster on Apple Silicon. If yes, delete the Darwin assembly because the owned
path is already the better implementation.

ChaCha20-Poly1305 is the largest owned-performance rewrite opportunity. Current
release builds on macOS/Linux AArch64 use the imported integrated seal/open
assembly for non-empty buffers before reaching the owned Rust composition path.
Encrypt also has an owned AArch64 interleaved path for large buffers. The
replacement target is a narrow owned fused AArch64 seal/open kernel, or a tuned
owned composition path, that beats the integrated assembly.

RSA Montgomery is the highest-risk slice. The imported files sit under public
and private Montgomery helpers, affect fixed RSA limb widths, and can easily
trade code ownership for a large verification or signing regression. Existing
portable CIOS/product paths and diagnostic public-operation benches give us a
measuring stick, but not an automatic deletion path.

## Work Order

1. SHA-256 Darwin AArch64 deletion experiment -> verified:
   `cargo test --features sha2 sha256`,
   `cargo check --features 'sha2,diag' --bench sha2`,
   Apple Silicon `cargo bench --features 'sha2,diag' --bench sha2 -- sha256`,
   and `scripts/check/asm-ledger.sh`. Retained.

2. SHA-512 Darwin AArch64 deletion experiment -> verified:
   `cargo test --features sha2 sha512`,
   `cargo check --features 'sha2,diag' --bench sha2`,
   Apple Silicon `cargo bench --features 'sha2,diag' --bench sha2 -- sha512`,
   and `scripts/check/asm-ledger.sh`. Retained.

3. ChaCha20-Poly1305 AArch64 baseline split -> verify:
   release-mode benches compare imported integrated assembly, owned interleaved
   encrypt, and owned composition for encrypt/decrypt rows. If the current diag
   surface cannot force those paths cleanly, add the smallest diag-only selector
   first. Completed 2026-06-25; integrated assembly still wins.

4. ChaCha20-Poly1305 owned fused replacement/tuning -> verify:
   `cargo test --features 'chacha20poly1305,diag' chacha20`,
   `cargo test --features aead --test aead_wycheproof wycheproof_chacha20poly1305_open_vectors_match_expected_results`,
   `cargo test --features aead --test chacha20poly1305`,
   AArch64 release benches for encrypt/decrypt against the imported integrated
   assembly, and ledger count/classification changes only after the owned path
   wins or ties.

5. RSA Montgomery baseline split -> verify:
   `cargo test --features 'rsa,diag' rsa_montgomery`,
   `cargo test --features 'rsa,diag' public_operation`,
   `cargo bench --features 'rsa,diag' --bench rsa -- rsa-montgomery-thresholds`,
   and focused RSA-2048/3072/4096/8192 verify benches on x86_64 Linux and
   AArch64.

6. RSA Montgomery owned assembly replacement -> verify:
   backend equivalence against portable CIOS/product paths for 32, 48, 64, and
   128 limbs; RSA public-key tests; RSA leakage tests when private-operation
   paths are affected; fixed-width benches against the imported Montgomery
   assembly; and `scripts/check/asm-ledger.sh`.

7. Close the slice -> verify:
   `docs/tasks/asm.md` count and section membership match the tree, every new
   `.s` / `.S` file has ownership/provenance headers, `git diff --check` is
   clean, and the notebook records accepted/rejected measurements.

## Replacement Criteria

Promote an owned assembly or intrinsic candidate only when all of these are
true:

- Correctness matches the portable implementation or external oracle across the
  existing tests.
- Runtime dispatch is based only on public platform capability and public input
  shape.
- Any private-key path touched by RSA still runs through blinding and fault
  checks.
- Benchmarks show the replacement is faster than the imported assembly on the
  hot rows, or effectively flat when the replacement deletes assembly by using
  an already-owned intrinsic path.
- The assembly ledger remains green after the file movement.

Keep tuning a candidate when it is correct but slower than the imported
assembly. Reject the shape of the attempt when it adds thresholds, wrappers, or
duplicate kernels that do not buy measurable speed or ownership. Ornamental
dispatch is just another maintenance tax.

## Notes For Future Entries

Each experiment should record:

- patch summary;
- exact commands;
- host CPU/OS;
- before/after benchmark rows;
- correctness result;
- decision: promote, reject, or keep measuring;
- whether `ct.toml` needs an update.

For this slice, `ct.toml` should change only if a replacement changes a secret
boundary or bypasses an existing constant-time harness. Pure SHA-2 compression
changes should not need it. ChaCha20-Poly1305 and RSA changes need a deliberate
constant-time check, because their inputs include keys, tags, private
representatives, or blinded private intermediates.

## 2026-06-25 SHA-2 Darwin AArch64 Deletion Retained

Deleted the two imported macOS AArch64 SHA-2 assembly files and routed Darwin
through the existing owned Rust intrinsic kernels:

- `src/hashes/crypto/sha256/asm/rscrypto_sha256_aarch64_apple_darwin.s`
- `src/hashes/crypto/sha512/asm/rscrypto_sha512_aarch64_apple_darwin.s`

The replacement uses direct ARM SHA instructions emitted from Rust intrinsics.
`cargo-show-asm` showed `sha256h`, `sha256h2`, `sha256su0`, `sha256su1`,
`sha512h`, `sha512h2`, `sha512su0`, and `sha512su1` in the release wrappers.

Host:

- Apple M1-class AArch64, macOS/Darwin 25.5.0
- Rust nightly 1.97.0, LLVM 22.1.2

Commands:

```bash
cargo check --features 'sha2,diag' --bench sha2
cargo test --features sha2 sha256
cargo test --features sha2 sha512
cargo bench --features 'sha2,diag' --bench sha2 -- "^(sha256|sha512)/(rscrypto|sha2|aws-lc-rs|ring)/(64|4096|1048576)$|^sha256/internal/compress/selected-kernel/(64B|128B|1024B)$" --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
cargo asm --features sha2 --lib --release --asm "rscrypto::hashes::crypto::sha256::kernels::compress_blocks_aarch64_sha2"
cargo asm --features sha2 --lib --release --asm "rscrypto::hashes::crypto::sha512::kernels::compress_blocks_aarch64_sha512"
cargo llvm-lines --features sha2 --lib
scripts/check/asm-ledger.sh
```

Linux cross-check used `just ssh-linux`, then ran the same focused SHA-2
compile/tests and the ledger on the synced Sapphire Rapids box.

Public digest rows:

| Row | Before | After | Decision |
| --- | ---: | ---: | --- |
| `sha256/rscrypto/64` | 57.586 ns | 51.325 ns | keep |
| `sha256/rscrypto/4096` | 4.0656 us | 2.0597 us | keep |
| `sha256/rscrypto/1048576` | 508.39 us | 516.33 us | flat |
| `sha512/rscrypto/64` | 101.49 ns | 71.197 ns | keep |
| `sha512/rscrypto/4096` | 3.3768 us | 3.0961 us | keep |
| `sha512/rscrypto/1048576` | 1.2355 ms | 772.99 us | keep |

The raw SHA-256 diagnostic compress row was noisier and showed a short-block
regression in the second run, but the public digest rows are the actual user
surface and improved or stayed flat. Do not keep imported assembly for a raw
diagnostic microbench when the public path wins and two external files vanish.

Decision: keep the deletion. This removes 751 lines of imported assembly,
reduces the ledger to 66 total assembly files and 53 external-derived files,
and leaves five files in this notebook's external-derived scope.

No `ct.toml` update is needed. SHA-2 compression has no key-dependent branch or
secret-dependent memory access boundary in this crate; dispatch remains based
on public CPU capability and public input length.

## 2026-06-25 ChaCha20-Poly1305 AArch64 Split Rejected For Deletion

Added a `diag`-only benchmark selector for the owned ChaCha20-Poly1305 path:

- production `rscrypto`: current release dispatch, including imported
  integrated AArch64 assembly on non-empty macOS/Linux AArch64 buffers;
- `rscrypto-owned`: bypasses only integrated AEAD assembly and keeps the owned
  ChaCha20 and Poly1305 dispatch, including AArch64 NEON/interleaved paths.

This is the narrow split we needed. The earlier `portable-only` experiment was
only a scalar lower bound because it disabled more acceleration than the
integrated assembly.

Host:

- Apple M1 Pro, Darwin 25.5.0 arm64
- Benchmark profile, Criterion sample size 20, 1 s warmup, 2 s measurement

Commands:

```bash
cargo check --features 'aead,diag' --bench aead
cargo check --features aead --bench aead
cargo test --features 'chacha20poly1305,diag' chacha20
cargo test --features aead --test aead_wycheproof wycheproof_chacha20poly1305_open_vectors_match_expected_results
cargo test --features aead --test chacha20poly1305
cargo bench --features 'aead,diag' --bench aead -- "^chacha20-poly1305/(encrypt|decrypt)/(rscrypto|rscrypto-owned|aws-lc-rs|ring)/(64|1024|4096|1048576)$" --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
cargo llvm-lines --features chacha20poly1305 --lib
scripts/check/asm-ledger.sh
```

Focused rows:

| Row | Production integrated ASM | Owned composition | Decision |
| --- | ---: | ---: | --- |
| `encrypt/64` | 239.97 ns | 291.66 ns | keep ASM |
| `encrypt/1024` | 850.64 ns | 1.0119 us | keep ASM |
| `encrypt/4096` | 2.8322 us | 3.5652 us | keep ASM |
| `encrypt/1048576` | 696.63 us | 862.68 us | keep ASM |
| `decrypt/64` | 246.66 ns | 294.15 ns | keep ASM |
| `decrypt/1024` | 992.44 ns | 1.0211 us | keep measuring |
| `decrypt/4096` | 2.8453 us | 3.4058 us | keep ASM |
| `decrypt/1048576` | 750.21 us | 1.2098 ms | keep ASM |

`cargo llvm-lines --features chacha20poly1305 --lib` reported 17,158 total
LLVM IR lines and 564 copies. The new owned helper split shows up as small
entries: 80 lines for encrypt and 69 lines for decrypt. The heavy owned code is
still the existing ChaCha20/Poly1305 kernels, led by the AArch64 ChaCha20 NEON
implementation at 1,322 lines.

Decision: do not delete the two imported ChaCha20-Poly1305 AArch64 assembly
files yet. The owned composition path is much better than scalar
`portable-only`, but it loses on every measured row and loses badly on 1 MiB
decrypt. Replacing these files needs either a real rscrypto-owned fused AArch64
seal/open kernel or a material redesign of the owned composition path.

No `ct.toml` update is needed for the diagnostic split. The secret boundary and
verification behavior did not change; the new public `diag` functions only
select an existing safe Rust fallback after the same public length checks.

## 2026-06-25 RSA Montgomery Split: Keep ASM, Widen CIOS Threshold

Added a `diag`-only portable CIOS public-operation selector:

- production `auto`: normal RSA public operation and runtime dispatch;
- `product-montgomery`: owned product Montgomery path;
- `cios-candidate`: current CIOS path, including assembly when the limb width
  is supported;
- `cios-portable`: owned portable CIOS path with no architecture assembly.

This split avoids the earlier circular benchmark where `cios-candidate` looked
like an owned replacement at 2048, 3072, 4096, and 8192-bit sizes while still
dispatching to imported Montgomery assembly.

Hosts:

- Apple M1 Pro, Darwin 25.5.0 arm64
- `linux-dev`, Intel Sapphire Rapids class, Ubuntu 24.04 x86_64

Commands:

```bash
cargo check --features 'rsa,diag' --bench rsa
cargo clippy --features 'rsa,diag' --bench rsa -- -D warnings
cargo test --features 'rsa,diag' public_operation_montgomery_candidates_match_current_path
cargo test --features 'rsa,diag' public_montgomery_threshold_uses_cios_through_rsa8192
cargo test --features 'rsa,diag' shared_montgomery_threshold_keeps_product_for_gap_widths
cargo bench --features 'rsa,diag' --bench rsa -- "^rsa-montgomery-thresholds/(rsa-2048-32-limbs|rsa-3072-48-limbs|rsa-4096-64-limbs|rsa-4160-65-limbs|rsa-6144-96-limbs|rsa-8128-127-limbs|rsa-8192-128-limbs)/(auto|product-montgomery|cios-candidate|cios-portable)$" --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
```

Supported fixed-width rows still need imported assembly:

| Host | Row | Current ASM path | Owned portable CIOS | Decision |
| --- | --- | ---: | ---: | --- |
| Apple M1 Pro | `rsa-2048-32-limbs` | 11.29 us | 34.38 us | keep ASM |
| Apple M1 Pro | `rsa-3072-48-limbs` | 25.13 us | 78.49 us | keep ASM |
| Apple M1 Pro | `rsa-4096-64-limbs` | 44.15 us | 143.70 us | keep ASM |
| Apple M1 Pro | `rsa-8192-128-limbs` | 173.84 us | 604.62 us | keep ASM |
| Sapphire Rapids | `rsa-2048-32-limbs` | 15.74 us | 42.86 us | keep ASM |
| Sapphire Rapids | `rsa-3072-48-limbs` | 31.14 us | 78.42 us | keep ASM |
| Sapphire Rapids | `rsa-4096-64-limbs` | 54.31 us | 138.12 us | keep ASM |
| Sapphire Rapids | `rsa-8192-128-limbs` | 242.17 us | 642.35 us | keep ASM |

Unsupported 65..127 limb public moduli were using product Montgomery. Portable
CIOS is faster on that band, so the public-operation threshold now returns true
for every modulus up to 128 limbs. The fixed-width assembly wrappers still only
support 32, 48, 64, and 128 limbs, so 65..127 remains owned portable Rust.
Shared/private Montgomery helpers keep the old `<=64 || ==128` threshold until
private-operation performance and leakage tests justify changing them.

| Host | Row | Old product path | New auto path | Result |
| --- | --- | ---: | ---: | --- |
| Apple M1 Pro | `rsa-4160-65-limbs` | 176.53 us | 146.34 us | ~17% faster |
| Apple M1 Pro | `rsa-6144-96-limbs` | 383.47 us | 340.45 us | ~11% faster |
| Apple M1 Pro | `rsa-8128-127-limbs` | 673.24 us | 592.38 us | ~12% faster |
| Sapphire Rapids | `rsa-4160-65-limbs` | 179.90 us | 141.73 us | ~21% faster |
| Sapphire Rapids | `rsa-6144-96-limbs` | 428.22 us | 349.98 us | ~18% faster |
| Sapphire Rapids | `rsa-8128-127-limbs` | 748.74 us | 630.16 us | ~16% faster |

Decision: do not delete the three RSA Montgomery assembly files yet:

- `src/auth/asm/rscrypto_rsa_bignum_mont_aarch64_elf.s`
- `src/auth/asm/rscrypto_rsa_bignum_mont_apple.s`
- `src/auth/asm/rscrypto_rsa_x86_64_elf.S`

The owned portable CIOS path is correct and useful, but it is not a replacement
for the fixed-width assembly today. The retained production change is the
public threshold simplification: all public moduli up to 8192 bits now use CIOS,
with assembly acceleration at the supported fixed widths and owned portable CIOS
for the gap widths.

No `ct.toml` update is needed. The RSA public-operation dispatch still depends
only on public modulus size and public CPU capability. Private-operation
arithmetic dispatch, blinding, and fault checks were not changed.

## 2026-06-25 Pushed Snapshot And Quick Bench Follow-up

Pushed commit `222e62f216691d706343b51fe5dca6ca69120890` to `main`.

Triggered GitHub Actions Bench run
[#28157440400](https://github.com/loadingalias/rscrypto/actions/runs/28157440400)
with:

- `targets=all`
- `platforms=all`
- `quick=true`

Status when refreshed: completed successfully. Artifacts have not been
extracted into `benchmark_results/` yet. The next benchmark update should
extract the `benchmark-*` artifacts, refresh any overview numbers that changed,
and compare the SHA-2 Darwin deletion, retained ChaCha20-Poly1305 AArch64
assembly rows, and RSA public Montgomery gap-width rows against the pre-commit
baseline.

## 2026-06-25 ChaCha20-Poly1305 Owned AArch64 Interleaving And Direct Poly1305 Groups

Added an owned AArch64 decrypt candidate that authenticates ciphertext chunks
before XOR-decrypting those same chunks. This keeps the AEAD input order
correct, removes the old owned decrypt double pass for large buffers, and still
zeroizes the caller buffer if the final tag check fails.

Also changed the AArch64 Poly1305 par4 updater to accumulate complete 64-byte
groups directly when the four-block cache is empty. The cache remains for AEAD
segment boundaries and tails, but full ciphertext blocks no longer take the old
copy-into-cache path.

This does not replace the imported integrated assembly yet. On release
macOS/Linux AArch64, the imported ASM remains the first production path for
non-empty buffers. The new path improves the `diag` owned candidate and the
AArch64 owned fallback path.

Host:

- Apple M1 Pro, Darwin 25.5.0 arm64
- Benchmark profile, Criterion sample size 20, 1 s warmup, 2 s measurement

Commands:

```bash
cargo check --features 'aead,diag' --bench aead
cargo test --features 'aead,diag' --test chacha20poly1305
cargo test --features 'chacha20poly1305,diag' chacha20poly1305_diag_owned_decrypt
cargo clippy --features 'aead,diag' --bench aead -- -D warnings
cargo test --features aead --test aead_wycheproof wycheproof_chacha20poly1305_open_vectors_match_expected_results
cargo test --features aead --test chacha20poly1305
cargo bench --features 'aead,diag' --bench aead -- "^chacha20-poly1305/(encrypt|decrypt)/(rscrypto|rscrypto-owned)/(64|1024|4096|1048576)$" --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
```

Focused rows after decrypt interleaving plus direct Poly1305 groups:

| Row | Imported integrated ASM | Owned before tuning | Owned after tuning | Decision |
| --- | ---: | ---: | ---: | --- |
| `encrypt/64` | 240.13 ns | 291.66 ns | 293.64 ns | keep ASM |
| `encrypt/1024` | 870.84 ns | 1.0119 us | 911.14 ns | keep ASM, gap narrowed |
| `encrypt/4096` | 2.8166 us | 3.5652 us | 3.0756 us | keep ASM, gap narrowed |
| `encrypt/1048576` | 684.00 us | 862.68 us | 742.81 us | keep ASM, gap narrowed |
| `decrypt/64` | 243.39 ns | 294.15 ns | 288.93 ns | keep ASM |
| `decrypt/1024` | 994.71 ns | 1.0211 us | 894.53 ns | owned wins current run |
| `decrypt/4096` | 2.9746 us | 3.4058 us | 3.2038 us | keep ASM, gap narrowed |
| `decrypt/1048576` | 692.61 us | 1.2098 ms | 747.15 us | keep ASM, gap narrowed |

Decision: keep both owned tuning patches, but do not promote the owned path as
the replacement yet. Large decrypt is now roughly 38% faster than the original
owned row, large encrypt is roughly 14% faster, and 1 KiB decrypt beats the
imported ASM in this run. The imported integrated assembly still wins the large
encrypt/decrypt rows by roughly 8-9%, which is the next gap to close.

Next tuning targets:

- fold counter-0 Poly1305-key generation into a fused AArch64 seal/open kernel;
- tune or generate that fused kernel so it schedules ChaCha rounds and Poly1305
  accumulation together instead of calling two Rust kernels per chunk.

No `ct.toml` update is needed. The new decrypt ordering branches only on public
length and CPU capability, authenticates ciphertext before decrypting each
chunk, uses the existing constant-time tag comparison, and preserves the
existing zeroize-on-failure behavior.

## 2026-06-25 ChaCha20 Poly1305-Key AArch64 ASM Spike Rejected

Wrote a narrow rscrypto-owned AArch64 helper for only the counter-0 ChaCha20
block used to derive the one-time Poly1305 key. Correctness passed against the
portable block function, but the unit was too small to matter.

Host:

- Apple M1 Pro, Darwin 25.5.0 arm64
- Benchmark profile, Criterion sample size 20, 1 s warmup, 2 s measurement

Commands:

```bash
cargo check --features 'chacha20poly1305,diag,sha2' --bench aead_kernels
cargo test --features 'chacha20poly1305,diag' --test aead_kernel_equivalence
cargo bench --features 'chacha20poly1305,diag,sha2' --bench aead_kernels -- "aead-kernel/chacha20-poly1305-key" --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
cargo bench --features 'aead,diag' --bench aead -- "^chacha20-poly1305/(encrypt|decrypt)/(rscrypto|rscrypto-owned)/(64|1024|4096|1048576)$" --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
```

Raw key derivation:

| Kernel | Time |
| --- | ---: |
| portable Rust | 99.787 ns |
| AArch64 ASM helper | 98.687 ns |

Full AEAD after the helper stayed in the noise band. The best large rows were
still imported ASM around 684 us and owned composition around 730-734 us for
1 MiB encrypt/decrypt. One 4 KiB owned decrypt run regressed badly, which was
not physically explained by a fixed startup helper and is treated as benchmark
noise, not evidence for the helper.

Decision: delete the helper and its diagnostic hooks. A separate keygen symbol
adds ABI surface, two platform assembly files, and extra ledger inventory for
roughly 1 ns of raw improvement. The right next unit is a fused AArch64
seal/open kernel that includes counter-0 key generation in the same schedule as
the message blocks. No ledger count changed.

## 2026-06-25 ChaCha20-Poly1305 Owned Direct NEON And Chunk Tuning

Tuned the owned AArch64 composition path again before writing a larger assembly
file. The prior owned interleaved path still called a resolved ChaCha20 function
pointer once per chunk after it had already proven AArch64 NEON availability.
The new path calls the AArch64 NEON ChaCha20 kernel directly from the
AArch64-only interleaved AEAD code and separates the interleaving threshold from
the full processing chunk size:

- minimum owned interleaved path length: 1 KiB;
- full processing chunk: 1 MiB.

This is intentionally not an assembly ownership change. It is a simpler owned
intrinsic tuning pass that tells us how much of the gap was call/chunk overhead
before committing to a full hand-written fused seal/open kernel.

Host:

- Apple M1 Pro, Darwin 25.5.0 arm64
- Benchmark profile, Criterion sample size 20, 1 s warmup, 2 s measurement

Commands:

```bash
cargo test --features 'aead,diag' --test chacha20poly1305
cargo test --features 'chacha20poly1305,diag' --test aead_kernel_equivalence
cargo bench --features 'aead,diag' --bench aead -- "^chacha20-poly1305/(encrypt|decrypt)/(rscrypto|rscrypto-owned)/(64|1024|4096|1048576)$" --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
```

Same-run focused rows after direct NEON plus 1 MiB chunk tuning:

| Row | Imported integrated ASM | Owned tuned path | Decision |
| --- | ---: | ---: | --- |
| `encrypt/64` | 227.88 ns | 279.41 ns | keep ASM |
| `encrypt/1024` | 831.52 ns | 870.41 ns | keep ASM, gap narrowed |
| `encrypt/4096` | 2.6911 us | 2.8280 us | keep ASM, gap narrowed |
| `encrypt/1048576` | 651.77 us | 672.74 us | keep ASM, gap narrowed |
| `decrypt/64` | 229.91 ns | 278.73 ns | keep ASM |
| `decrypt/1024` | 965.18 ns | 845.75 ns | owned wins current run |
| `decrypt/4096` | 2.7537 us | 2.8118 us | keep ASM, gap narrowed |
| `decrypt/1048576` | 654.18 us | 674.72 us | keep ASM, gap narrowed |

Decision: keep the direct-NEON and 1 MiB chunk tuning, but do not promote the
owned path over the imported integrated ASM. The owned large rows are now within
roughly 3-4% of the imported assembly, and 1 KiB decrypt wins, but the imported
assembly still wins encrypt and most hot rows in the same run.

Next tuning target: an actually fused AArch64 seal/open kernel needs to avoid
the two-pass chunk shape. The current owned path now spends less time on Rust
dispatch and chunk handoff, so the remaining gap is likely the separate
ChaCha20-then-Poly1305 memory pass inside each public-length chunk.

No `ct.toml` update is needed. The new branches and chunk boundaries are driven
only by public buffer length and public CPU capability. Secret key material
still flows through fixed-round ChaCha20, Poly1305 arithmetic, constant-time tag
comparison, and existing zeroize-on-failure behavior.

## 2026-06-25 ChaCha20-Poly1305 Rust/NEON Fused Chunk Prototype Rejected

Built and measured a `diag`-only AArch64 fused full-block prototype after the
direct-NEON/chunk tuning pass. The prototype processed exact four-ChaCha-block
groups, XORed/stored ciphertext, and fed the produced ciphertext into the
Poly1305 par4 state before tails/final length handling returned to Rust.

Two variants were tested:

- stack-collection prototype: wrote 16 ciphertext Poly1305 blocks into a stack
  reorder buffer before updating `AeadPar4`;
- direct-vector prototype: passed four 16-byte ciphertext NEON vectors directly
  to a temporary vector-limb Poly1305 accumulator.

Correctness passed for boundary encrypt/decrypt cases and bad-tag zeroization,
but both variants lost badly on throughput. The direct-vector version was the
best structure and still missed by a wide margin.

Host:

- Apple M1 Pro, Darwin 25.5.0 arm64
- Criterion quick mode

Command:

```bash
cargo bench --features 'chacha20poly1305,diag,sha2' --bench aead_kernels -- aead-kernel/chacha20poly1305-fused-aarch64 --quick
```

Best direct-vector same-run rows:

| Row | Current owned path | Fused prototype | Decision |
| --- | ---: | ---: | --- |
| `encrypt/1024` | 893.53 ns | 1.3420 us | reject |
| `decrypt/1024` | 882.81 ns | 1.2975 us | reject |
| `encrypt/4096` | 2.9415 us | 4.7570 us | reject |
| `decrypt/4096` | 3.0306 us | 4.6146 us | reject |
| `encrypt/65536` | 44.093 us | 74.442 us | reject |
| `decrypt/65536` | 44.415 us | 70.794 us | reject |
| `encrypt/1048576` | 708.54 us | 1.1796 ms | reject |
| `decrypt/1048576` | 732.88 us | 1.1785 ms | reject |

Decision: deleted the prototype and its bench/test hooks. This shape is the
wrong unit for replacement: it reuses the four-block ChaCha schedule instead of
the existing eight-block pipeline, crosses a Rust closure boundary per ChaCha
block, and extracts Poly1305 limbs through scalar lane operations. A real
replacement needs generated or hand-written AArch64 assembly that owns the
whole large-message loop, schedules at least eight ChaCha blocks at a time, and
keeps Poly1305 accumulation register-resident without stack reordering.

No ledger count changed because no assembly files were added.

## 2026-06-25 Rejected Low-Level Spikes

Two lower-level candidates were measured and rejected instead of retained:

- AArch64 ChaCha20 counter vectors built with direct lane construction instead
  of per-batch stack arrays. Raw Criterion output briefly looked better against
  a stale baseline, but immediate A/B showed no win: reverted code measured
  528.16 us for the 1 MiB raw XOR row versus roughly 536 us for the candidate,
  and `cargo asm` still showed a large stack frame.
- Linux x86_64 RSA public e=65537 wrapper that chained imported ADX/BMI2
  Montgomery square/multiply calls directly and exited Montgomery form with
  `mont(acc, 1)`. Sapphire Rapids correctness passed, but same-run threshold
  benches were not stable enough: 2048/3072/4096 were at best about 0-1.5%
  faster in one run, then 4096 reran slower than the old CIOS candidate. The
  wrapper did not replace the imported primitive, so it was deleted.

Decision: do not keep thin wrappers or instruction-selection tweaks unless they
produce a stable win. The next real replacement remains a generated fused
AArch64 ChaCha20-Poly1305 seal/open kernel, or a fixed-width RSA public-modexp
kernel that owns the whole square chain instead of wrapping the imported
Montgomery primitive.

## 2026-06-27 ChaCha20 4-Block Owned ASM Spike Rejected

Tried the smallest owned-ASM step toward replacing the imported integrated
ChaCha20-Poly1305 AArch64 files: a forced-kernel, diag-only ChaCha20 XOR
assembly routine processing four ChaCha blocks per iteration. It used the RFC
8439 counter layout and the existing Rust wrapper handled sub-256-byte tails by
falling back to the current AArch64 NEON kernel. The candidate did not touch
production dispatch.

Correctness passed before timing:

```bash
cargo test --features 'chacha20poly1305,diag' --test aead_kernel_equivalence
```

Local timing on Apple M1 Pro showed the four-block kernel is not competitive
with the current Rust NEON eight-block pipeline:

```bash
cargo bench --features 'chacha20poly1305,diag,sha2' --bench aead_kernels -- 'aead-kernel/chacha20-xor/(aarch64-neon|aarch64-asm)/(1024|4096|65536|1048576)$' --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
```

| Row | Current Rust NEON | 4-block owned ASM | Decision |
| --- | ---: | ---: | --- |
| `1024` | 516.55 ns | 932.22 ns | reject |
| `4096` | 2.1090 us | 3.7593 us | reject |
| `65536` | 31.822 us | 56.271 us | reject |
| `1048576` | 511.70 us | 915.36 us | reject |

Decision: deleted the spike immediately. This was a useful cheap local failure,
not a paid-bench candidate. The current Rust NEON kernel's eight-block
instruction-level parallelism is mandatory; a four-block hand-written loop gives
up too much latency hiding. The next ChaCha20-Poly1305 replacement attempt must
start from either compiler-generated eight-block code for the existing owned
Rust NEON kernel or a generated eight-block fused seal/open kernel. It must keep
the RFC 8439 ordering: counter 0 derives the Poly1305 one-time key, encryption
uses counter 1 onward, and Poly1305 authenticates AAD plus ciphertext and
lengths.

## 2026-06-28 Eight-Block Rust/NEON Fused AEAD Candidate Rejected

Built a correct diag-only AArch64 fused candidate that keeps the existing
eight-ChaCha-block schedule and feeds produced ciphertext vectors directly into
the AArch64 Poly1305 par4 accumulator. This version explicitly handled the
non-empty-AAD boundary where the Poly1305 accumulator starts ciphertext
mid-group, so it was not a benchmark-only empty-AAD shortcut.

Correctness:

```bash
cargo test --features 'aead,diag' --test chacha20poly1305 chacha20poly1305_diag_fused_aarch64
cargo test --features 'aead,diag' --test chacha20poly1305
cargo test --features 'chacha20poly1305,diag' --test aead_kernel_equivalence
cargo check --features 'aead,diag' --bench aead
```

Local Apple M1 Pro focused benchmark:

```bash
cargo bench --features 'aead,diag' --bench aead -- '^chacha20-poly1305/(encrypt|decrypt)/(rscrypto-owned|rscrypto-fused-aarch64)/(65536|1048576)$' --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
```

| Row | Current owned | Eight-block fused candidate | Decision |
| --- | ---: | ---: | --- |
| `encrypt/65536` | 44.053 us | 49.041 us | reject |
| `encrypt/1048576` | 704.84 us | 783.42 us | reject |
| `decrypt/65536` | 44.040 us | 48.127 us | reject |
| `decrypt/1048576` | 702.03 us | 774.56 us | reject |

`cargo asm` showed the fused Rust/NEON body compiled with a 1184-byte stack
frame, compared with an already-large but faster current Rust NEON ChaCha frame.
The vector feeder and Poly1305 reordering introduced enough register pressure
that avoiding the second ciphertext read did not matter; the candidate was still
roughly 10-11% slower on 1 MiB rows.

Decision: do not run paid G3/G4 benches for this candidate. The remote rscrypto
EC2 check before this work showed only `rscrypto-linux-g4`, already stopped, and
no G3 instance provisioned. Keep the boxes stopped until a local candidate beats
the current owned path on Apple Silicon.

Next attempt must be actual generated/hand-scheduled assembly, not another Rust
intrinsics feeder. The assembly needs to fuse both pieces in one schedule: keep
ChaCha's eight-block ILP, authenticate ciphertext in Poly1305 order without a
stack vector queue, and use a Poly1305 multiplication schedule that does not
reconstruct the current par4 Rust helper block-by-block.

## 2026-06-28 Hand-Written Fused8 ASM Candidate Rejected For Promotion

Added a diag-only hand-written AArch64 fused8 seal candidate in the owned
ChaCha20 assembly files. The Rust wrapper fed it the existing Poly1305 par4
state, cached AAD boundary blocks, key, counter, nonce, and mutable ciphertext
buffer. The assembly encrypted each 512-byte tile with the existing owned
eight-block ChaCha schedule, then folded the resulting ciphertext into the
owned par4 Poly1305 accumulator before moving to the next tile. This handled
non-empty AAD offsets instead of assuming an empty-AAD benchmark shortcut.

Correctness and static checks passed for the retained live-accumulator version:

```bash
cargo fmt --all --check
cargo test --features 'aead,diag' --test chacha20poly1305 chacha20poly1305_diag_owned_fused8_encrypt_matches_normal_path -- --nocapture
cargo test --features 'aead,diag' --test chacha20poly1305
cargo check --features 'aead,diag,sha2' --bench aead --test chacha20poly1305 --test aead_kernel_equivalence
rg -n "udiv|sdiv|fdiv|fsqrt|tbl|tbx|br\\b|blr\\b" src/aead/chacha20/asm/rscrypto_chacha20_xor_aarch64_linux.s src/aead/chacha20/asm/rscrypto_chacha20_xor_aarch64_apple_darwin.s src/aead/poly1305/aarch64_asm.rs src/aead/chacha20poly1305.rs
```

The first ASM shape was correct but slower than the production integrated
assembly. A live-accumulator revision kept Poly1305 `h` in registers across the
64-byte groups inside each 512-byte tile and stored it once per tile. That
improved the candidate slightly, but not enough:

| Row | Production integrated ASM | Owned par4 bulk diag | Owned fused8 ASM | Decision |
| --- | ---: | ---: | ---: | --- |
| `encrypt/1024` | 882.66 ns | 957.58 ns | 961.57 ns | reject |
| `encrypt/4096` | 2.8408 us | 3.2078 us | 3.2667 us | reject |
| `encrypt/65536` | 43.329 us | 48.649 us | 49.540 us | reject |
| `encrypt/1048576` | 694.18 us | 776.54 us | 796.08 us | reject |

Then a deeper register-fed four-block Poly1305 body was tried inside the fused
function. It avoided rereading ciphertext for those four blocks and converted
ChaCha output vectors directly into Poly1305 limbs. After fixing a clobbered
hibit vector, scalar `r4 * 5` register lifetime, and cached-boundary setup, the
variant was correct. It was still much slower:

| Row | Register-fed fused8 ASM |
| --- | ---: |
| `encrypt/1024` | 1.2635 us |
| `encrypt/4096` | 4.4776 us |
| `encrypt/65536` | 68.739 us |
| `encrypt/1048576` | 1.1031 ms |

Decision: reject the fused8 shape for promotion and do not run paid G3/G4
benches for it. The register-fed variant was deleted first, then the remaining
live-accumulator fused8 diagnostic path was deleted as well. The result is
evidence, not a retained implementation: it lost to the imported integrated
assembly and even lost to the composed owned par4 diagnostic path.

Root cause: this is still incremental glue. The candidate pays ChaCha's
eight-block register pressure, then separately rebuilds the Poly1305 par4 input
shape and reloads Poly1305 powers per 512-byte tile because the ChaCha schedule
clobbers the NEON registers. Direct register feeding made that worse by adding
scalar lane extraction and limb packing on the hot path. Avoiding one
ciphertext reread is not enough to pay for that machinery.

Next viable ChaCha20-Poly1305 attempt: write or generate a new production
kernel around the whole AEAD schedule. It needs a deliberate register allocation
for ChaCha state, Poly1305 powers, accumulator, and ciphertext lanes together.
It should not wrap the current owned ChaCha XOR macro and current par4
Poly1305 helper. If that larger rewrite is not the next task, pivot to another
remaining external-derived target with a clearer replacement path.
