# s2n-bignum ASM Ownership Notebook

Started: 2026-06-26

Goal: replace s2n-bignum-derived public-key assembly with rscrypto-owned
implementations only when the replacement keeps or improves the measured hot
rows. The first target was P-384 ECDSA signing because the triggering Linux CI
benchmark pass showed it as the worst remaining ASM-linked public-key loss.

This is not a namespacing exercise. A file leaves the s2n-bignum bucket only
when the vendored source is deleted or replaced by source generated from
rscrypto-owned formulas, tables, and ABI needs.

## Current Status

Closed for the first target as of 2026-06-26. Linux x86_64 P-384 signing no
longer depends on the vendored arbitrary-point scalar-mul file, the dead
x86_64 point-helper files, or the x86_64 P-384 order-reduction file. Targeted
run [#28245803704](https://github.com/loadingalias/rscrypto/actions/runs/28245803704)
kept all 16 measured x86_64 P-384 signing rows ahead of AWS-LC: W/T/L 16/0/0,
geomean 1.34x, median 1.35x, worst row 1.21x.

The remaining s2n-bignum P-384 field, inversion, scalar-inversion, and aarch64
point-helper files are separate ownership targets. Do not reopen this slice by
restoring the deleted x86_64 helpers.

## Starting Baseline

Benchmark source: `benchmark_results/OVERVIEW.md`, Linux CI run
`#28202726811`, created 2026-06-25 21:50:27 UTC, commit
`c5ece53efd98980831de728974d1f588cf055ca5`.

Relevant rows:

| Group | Rows | W/T/L | Geomean | Median |
| --- | ---: | ---: | ---: | ---: |
| `ecdsa-p384` / `sign` | 36 | 16/4/16 | 0.96x | 0.98x |
| `ecdsa-p384` / `verify` | 36 | 36/0/0 | 1.81x | 1.39x |

Worst observed pressure:

- Intel Sapphire Rapids, `ecdsa-p384 / sign / 1024`, vs `aws-lc-rs`: 0.49x.
- Intel Sapphire Rapids, `ecdsa-p384 / sign / 0`, vs `aws-lc-rs`: 0.49x.
- Intel Sapphire Rapids, `ecdsa-p384 / sign / 32`, vs `aws-lc-rs`: 0.50x.

## First Target

Deleted target:

- `src/auth/asm/rscrypto_p384_montjscalarmul_alt_x86_64_unknown_linux.S`

Reason:

- The file is a large s2n-bignum arbitrary-point Montgomery-Jacobian scalar
  multiplication routine.
- P-384 signing needs fixed-base generator multiplication, not arbitrary-point
  multiplication.
- rscrypto already has generated P-384 fixed-base signing comb tables and a
  constant-time Rust comb path.
- Removing the arbitrary scalar-mul dependency moves the live P-384 signing
  path toward owned code instead of carrying a 9k-line vendored black box.

## Scope Boundary

This closed slice deletes only x86_64 helpers that the P-384 signing work made
safe to remove: arbitrary scalar multiplication, dead point helpers, and the
x86_64 P-384 order-reduction wrapper. P-384 field multiplication, field
squaring, field inversion, scalar inversion, aarch64 point helpers, and aarch64
order reduction remain separate kernels with separate benchmark and
constant-time evidence requirements.

## Verification Plan

1. Remove x86_64 P-384 arbitrary scalar-mul from the live build -> verify:
   `rg rscrypto_p384_montjscalarmul_alt src` returns no live wrapper/include
   references and `scripts/check/asm-ledger.sh` reports one fewer external file.

2. Route x86_64 P-384 basepoint multiplication through the existing owned
   fixed-base comb path -> verify: P-384 backend-vs-portable scalar tests pass.

3. Preserve signing semantics -> verify: P-384 deterministic and blinded
   signing oracle tests pass against RustCrypto.

4. Keep the broader crate healthy -> verify: `cargo check --features ecdsa`
   and `git diff --check` are clean.

5. Benchmark only after this correctness slice is stable -> verify: compare the
   same starting CI rows against a new run and tune the owned comb path if any
   worst offender remains below parity.

## Follow-Up Tuning Direction Completed

The first deletion made the dependency graph cleaner and improved the worst
rows, but it did not beat AWS-LC. The completed tuning path optimized the owned
fixed-base path directly:

- specialize P-384 fixed-base signing to avoid generic point scaffolding;
- replace the complete mixed-add cost with a proved fixed-base formula or a
  compact owned generated kernel;
- return the signing `r` path directly if it beats generic affine conversion;
- keep constant-time selection over the generated P-384 comb table.

## 2026-06-26 Deletion Slice

Result:

- Deleted `src/auth/asm/rscrypto_p384_montjscalarmul_alt_x86_64_unknown_linux.S`.
- Removed the x86_64 `global_asm!`, extern symbol, and Rust wrapper for
  `rscrypto_p384_montjscalarmul_alt`.
- Routed Linux x86_64 P-384 basepoint multiplication through
  `scalar_mul_basepoint_comb_ct_secret(&P384, ...)`.
- Updated `docs/tasks/asm.md` from 66 to 65 tracked assembly files and from 53
  to 52 external-derived files.

Verification:

```bash
scripts/check/asm-ledger.sh
cargo check --features ecdsa
cargo test --features ecdsa p384
cargo test --features ecdsa --test ecdsa_oracle p384
cargo test --features ecdsa --test ecdsa_oracle
cargo test --features ecdsa ecdsa
cargo fmt --all --check
git diff --check
just check
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS='-C target-cpu=x86-64' \
  cargo check --target x86_64-unknown-linux-gnu --features ecdsa
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS='-C target-cpu=x86-64' \
  cargo clippy --target x86_64-unknown-linux-gnu --features ecdsa --lib -- -D warnings
```

All completed successfully. The x86_64 Linux checks are compile/lint checks
only; the Linux benchmark verdicts on the same families are recorded below.

## 2026-06-26 Targeted Bench Follow-Up

Source: GitHub Actions benchmark run
[#28212427930](https://github.com/loadingalias/rscrypto/actions/runs/28212427930),
created 2026-06-26 02:02:44 UTC, commit
`128c76e546243ed7d048553573c4047099dab56b`.

Run shape:

- `auth` bench with filter `^ecdsa-p384/`.
- Quick Criterion mode.
- Runners: AMD Zen4, AMD Zen5, Intel Ice Lake, Intel Sapphire Rapids.

Result:

| Scope | Rows | W/T/L | Geomean | Median |
| --- | ---: | ---: | ---: | ---: |
| Pre-delete x86_64 P-384 sign rows | 16 | 0/0/16 | 0.53x | 0.52x |
| Post-delete x86_64 P-384 sign rows | 16 | 0/0/16 | 0.70x | 0.68x |
| Post-delete x86_64 P-384 verify guard rows | 16 | 16/0/0 | 1.33x | 1.34x |
| Blended full-run estimate if non-x86 rows stay unchanged | 36 | 16/4/16 | 1.08x | 0.98x |

Platform movement:

| Platform | Pre-delete sign geomean | Post-delete sign geomean | Direction |
| --- | ---: | ---: | ---: |
| AMD Zen4 | 0.53x | 0.70x | 1.33x better |
| AMD Zen5 | 0.57x | 0.66x | 1.17x better |
| Intel Ice Lake | 0.53x | 0.80x | 1.50x better |
| Intel Sapphire Rapids | 0.51x | 0.66x | 1.30x better |

Verdict: the deletion was worth keeping. It removed one vendored s2n-bignum
file and improved the exact worst x86_64 signing rows materially. It did not
close the AWS-LC gap: every targeted x86_64 P-384 signing row is still a loss,
with the post-delete geomean at 0.70x. That result triggered the fixed-base
tuning below; do not bring back arbitrary-point vendored scalar multiplication.

## 2026-06-26 Dead x86_64 Point Helper Cleanup

After the scalar-mul deletion, the x86_64 P-384 point double and mixed-add
assembly files no longer had live `global_asm!` includes or Rust wrappers. They
were removed as dead vendored code:

- `src/auth/asm/rscrypto_p384_montjdouble_alt_x86_64_unknown_linux.S`
- `src/auth/asm/rscrypto_p384_montjmixadd_alt_x86_64_unknown_linux.S`

The matching aarch64 files remain live in `ecdsa_aarch64_asm.rs` and were not
touched. This keeps the cleanup precise and avoids collateral platform or RSA
changes.

The auth bench now has a diagnostic group for P-384 signing internals:
`^ecdsa-p384/internal/`. Use it to measure nonce reduction, basepoint/blinded
basepoint work, scalar inversion, and final scalar arithmetic before writing a
new owned x86_64 backend.

## 2026-06-26 r-only Deterministic Signing Shortcut

P-384 deterministic signing no longer converts the basepoint multiplication
result to a full affine point when it only needs `r = x mod n`. Supported
platforms now compute the affine x-coordinate directly and skip the affine y
work. This adds no new unsafe code and keeps scalar multiplication, field
inversion, nonce generation, and final scalar arithmetic on their existing
constant-time paths.

Local Apple Silicon quick bench smoke test:

- Selector: `^ecdsa-p384/(sign|internal)/`.
- `rscrypto-deterministic / 0`: about 54.6 us.
- `aws-lc-rs / 0`: about 68.2 us.
- Internal `basepoint-r / 0`: about 52.2 us.
- Internal `basepoint-blinded / 0`: about 52.5 us.

This confirms the shortcut is directionally sound, but it does not close the
x86_64 issue by itself. Later x86_64 CI showed that the fixed-base comb
specialization below is the change that closed the signing gap.

## 2026-06-26 x86_64 Fixed-Base Comb Specialization

Linux x86_64 P-384 basepoint multiplication now uses a P-384-specific owned
fixed-base comb loop instead of the generic `Jacobian<6>` comb wrapper. This
keeps the generated signing comb table and existing P-384 field kernels, but
removes two hot costs from the x86_64 signing path:

- the unconditional exceptional-case doubling inside generic complete
  `add_mixed_ct`;
- generic field-element modulus dispatch inside the comb's point formulas.

Constant-time safety note: the new mixed-add is intentionally incomplete, but
only inside the P-384 fixed-base signing comb. For a nonzero row digit, the
selected coefficient `S_row` has bits only at `column * rows`, while the doubled
accumulator has bits only at `column * rows + offset` for `1 <= offset < rows`.
Both coefficients are below the prime group order, so equality modulo the order
would require integer equality, which the bit layout rules out. Opposite points
are also impossible: row 0 would require the scalar to equal the group order,
and later rows are bounded below the order. Zero digits are masked back to the
original accumulator.

Verification:

```bash
cargo fmt --all --check
cargo check --features ecdsa --lib
cargo test --features ecdsa p384_nonexceptional_comb_matches_complete_comb_for_structured_scalars
cargo test --features ecdsa p384
cargo test --features ecdsa --test ecdsa_oracle p384
cargo check --features ecdsa,diag --benches
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS='-C target-cpu=x86-64' \
  cargo check --target x86_64-unknown-linux-gnu --features ecdsa,diag --lib
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS='-C target-cpu=x86-64' \
  cargo clippy --target x86_64-unknown-linux-gnu --features ecdsa,diag --lib -- -D warnings
git diff --check
scripts/check/asm-ledger.sh
```

Bench verdict: the targeted x86_64 rows moved from losing every signing row to
beating AWS-LC on every signing row. The result is recorded below.

## 2026-06-26 x86_64 Fixed-Base Bench Result

Source: GitHub Actions benchmark run
[#28216163852](https://github.com/loadingalias/rscrypto/actions/runs/28216163852),
created 2026-06-26 03:58:13 UTC, commit
`d8481124c5b69cfba61f8135bfcec03763200530`.

Run shape:

- `auth` bench with filter `^ecdsa-p384/(sign|internal)/`.
- Runners: AMD Zen4, AMD Zen5, Intel Ice Lake, Intel Sapphire Rapids.

Result against `aws-lc-rs` for the targeted x86_64 P-384 sign rows:

| Scope | Rows | W/T/L | Geomean | Median |
| --- | ---: | ---: | ---: | ---: |
| Before x86_64 fixed-base specialization | 16 | 0/0/16 | 0.71x | 0.67x |
| After x86_64 fixed-base specialization | 16 | 16/0/0 | 1.38x | 1.40x |
| After x86_64 P-384 reduction cleanup | 16 | 16/0/0 | 1.34x | 1.35x |

Platform geomeans:

| Platform | Geomean vs AWS-LC | Worst row |
| --- | ---: | ---: |
| AMD Zen4 | 1.38x | 1.32x |
| AMD Zen5 | 1.41x | 1.36x |
| Intel Ice Lake | 1.40x | 1.28x |
| Intel Sapphire Rapids | 1.31x | 1.25x |

Post-reduction-cleanup run
[#28245803704](https://github.com/loadingalias/rscrypto/actions/runs/28245803704)
kept every targeted sign row ahead of AWS-LC:

| Platform | Geomean vs AWS-LC | Worst row |
| --- | ---: | ---: |
| AMD Zen4 | 1.33x | 1.28x |
| AMD Zen5 | 1.38x | 1.34x |
| Intel Ice Lake | 1.36x | 1.25x |
| Intel Sapphire Rapids | 1.28x | 1.21x |

Verdict: the x86_64 P-384 signing slice is closed. The remaining P-384 x86_64
s2n-bignum dependencies are not all gone, but they are separate ownership
targets. The arbitrary scalar-mul, dead point-helper, and wide-order reduction
files can stay deleted without giving AWS-LC the signing win back.

## 2026-06-26 x86_64 P-384 Reduction Cleanup

`src/auth/asm/rscrypto_bignum_mod_n384_x86_64_unknown_linux.S` was removed
after the signing win. Linux x86_64 P-384 nonce reduction now uses the existing
owned constant-time Montgomery reducer in `reduce_wide_order_nonzero`; the loop
count is fixed by the 96-byte input shape and zero normalization remains masked.
P-256 x86_64 reduction and aarch64 P-384 reduction still use their existing
assembly wrappers.

Bench result: run
[#28245803704](https://github.com/loadingalias/rscrypto/actions/runs/28245803704)
closes this slice. The internal wide-order reduction microbench becomes
1.4-2.5 us instead of 0.04-0.07 us on the targeted x86_64 runners, and
small-message nonce-reduce roughly doubles, but P-384 signing remains W/T/L
16/0/0 against AWS-LC with a 1.34x geomean. That is the right trade: one less
vendored s2n-bignum file, no unsafe FFI boundary for x86_64 P-384 order
reduction, and the public signing target still wins.

## 2026-06-27 Ed25519 AArch64 Verify Triage

`benchmark_results/OVERVIEW.md` shows `ed25519 / verify` in the top current
loss areas: 36 fastest-external Linux rows, W/T/L 9/18/9, geomean 0.99x.
This is the next s2n-bignum ownership target that is both bad enough to matter
and narrow enough to attack. The live AArch64 verify path still enters
`rscrypto_ed25519_aarch64_{apple_darwin,unknown_linux}.s` through
`aarch64_asm::double_scalar_basepoint_encoded`.

Added `diag`-only phase rows under `ed25519/verify-phase`:

- `challenge-reduce`
- `public-decode`
- `r-decode`
- `portable-double-scalar`
- `aarch64-asm-double-scalar` on AArch64 macOS/Linux

Local Apple Silicon command:

```bash
cargo bench --features 'hkdf,pbkdf2,ecdsa,ed25519,x25519,ml-kem,diag' \
  --bench auth -- "^ed25519/(verify|verify-phase)/" \
  --warm-up-time 1 --measurement-time 2 --sample-size 20 --noplot
```

Local result:

| Row | 0 bytes | 32 bytes | 1024 bytes | 16384 bytes |
| --- | ---: | ---: | ---: | ---: |
| `verify/rscrypto` | 28.72 us | 28.89 us | 29.73 us | 39.86 us |
| `verify/aws-lc-rs` | 28.99 us | 28.77 us | 29.79 us | 40.06 us |
| `verify/ring` | 37.55 us | 37.34 us | 38.32 us | 47.96 us |
| `verify-phase/challenge-reduce` | 196.71 ns | 198.51 ns | 926.05 ns | 12.08 us |
| `verify-phase/public-decode` | 8.33 us | 8.53 us | 8.52 us | 8.54 us |
| `verify-phase/r-decode` | 8.87 us | 9.03 us | 9.07 us | 9.05 us |
| `verify-phase/portable-double-scalar` | 46.93 us | 47.24 us | 48.09 us | 47.34 us |
| `verify-phase/aarch64-asm-double-scalar` | 28.46 us | 28.39 us | 28.54 us | 28.02 us |

Verdict: do not delete the AArch64 Ed25519 s2n-bignum verify assembly in favor
of the current owned portable path. The imported AArch64 decode + double-scalar
boundary is about 1.65x faster than the owned portable boundary locally. A real
replacement must be an owned AArch64 double-scalar implementation or a major
algorithmic rewrite of the owned path, not a dispatch flip.

Next validation target: run the same filtered diag rows on Graviton3 and
Graviton4. If those match the local shape, the next implementation task is an
owned AArch64 Straus/wNAF backend that keeps the public-key table and basepoint
table in a representation suitable for NEON field operations. Only delete the
two AArch64 verify assembly files after that owned boundary reaches parity.

## 2026-06-27 Ed25519 G3/G4 Verify-Phase Bench

Source: GitHub Actions benchmark run
[#28305662293](https://github.com/loadingalias/rscrypto/actions/runs/28305662293),
created 2026-06-27 23:56:05 UTC, commit
`dbeff3d1c363732f26352d17da3a47deb8988a82`.

Run shape:

- `bench.yaml`
- `targets=ed25519`
- `platforms=g3,g4`
- `filter=^ed25519/(verify|verify-phase)/`
- `quick=false`

Public verify rows:

| Platform | Row | rscrypto | aws-lc-rs | ring | Fastest-external verdict |
| --- | --- | ---: | ---: | ---: | --- |
| Graviton3 | 0 bytes | 47.585 us | 47.575 us | 54.850 us | tie vs AWS-LC |
| Graviton3 | 32 bytes | 47.563 us | 47.578 us | 53.905 us | tie vs AWS-LC |
| Graviton3 | 1024 bytes | 48.635 us | 48.615 us | 55.359 us | tie vs AWS-LC |
| Graviton3 | 16384 bytes | 64.485 us | 63.931 us | 71.279 us | tie vs AWS-LC |
| Graviton4 | 0 bytes | 39.951 us | 39.968 us | 46.418 us | tie vs AWS-LC |
| Graviton4 | 32 bytes | 39.934 us | 39.975 us | 45.658 us | tie vs AWS-LC |
| Graviton4 | 1024 bytes | 40.916 us | 40.953 us | 46.896 us | tie vs AWS-LC |
| Graviton4 | 16384 bytes | 55.596 us | 55.576 us | 61.351 us | tie vs AWS-LC |

Replacement-boundary rows:

| Platform | Size | Portable double-scalar | AArch64 ASM double-scalar | Portable / ASM |
| --- | ---: | ---: | ---: | ---: |
| Graviton3 | 0 | 66.820 us | 47.245 us | 1.41x |
| Graviton3 | 32 | 65.997 us | 47.242 us | 1.40x |
| Graviton3 | 1024 | 66.882 us | 47.250 us | 1.42x |
| Graviton3 | 16384 | 66.065 us | 47.247 us | 1.40x |
| Graviton4 | 0 | 61.275 us | 39.656 us | 1.55x |
| Graviton4 | 32 | 60.474 us | 39.651 us | 1.53x |
| Graviton4 | 1024 | 61.124 us | 39.660 us | 1.54x |
| Graviton4 | 16384 | 60.464 us | 39.656 us | 1.52x |

Verdict: Graviton confirms the local Apple Silicon conclusion. The imported
AArch64 verify assembly is the whole reason rscrypto ties AWS-LC on these
rows. The owned portable boundary is 1.4-1.55x slower and cannot replace the
vendored code.

Next implementation target: write an owned AArch64 double-scalar backend for
Ed25519 verify. Do not start by deleting files. Start by matching the imported
ABI boundary: public-key decode, `[s]B + [-h]A`, and compressed output. The
first acceptance gate is parity with `aarch64-asm-double-scalar` on Graviton3,
Graviton4, and Apple Silicon; only then delete
`rscrypto_ed25519_aarch64_{apple_darwin,unknown_linux}.s`.
