# Constant-Time Evidence Hardening Plan

Created: 2026-07-03

Goal: make rscrypto's constant-time evidence system sharper, more actionable,
and harder to accidentally weaken. The current macOS required gate is clean;
the next work is not "add more tests" blindly. The next work is to turn noisy
manual evidence into a triageable, reviewable, eventually near-zero-unknowns
signal.

## Baseline

Local macOS validation after the July 2026 CT tooling cleanup:

- `just ct-full`: pass
- `just ct-validate --manifest-only --strict-coverage`: pass
- `just ct-validate --target aarch64-apple-darwin --profile release --strict-coverage`: pass
- `cargo fmt --all --check`: pass
- `git diff --check`: pass

Current release CT report shape:

- Target: `aarch64-apple-darwin`
- Profile: `release`
- Status: `pass`
- Blocking findings: `0`
- Non-blocking diagnostics: `0`
- Primitive evidence entries: `24`
- CT-intended primitives with required DudeCT evidence: `20/20`
- Required DudeCT gate cases: `72/72` pass
- Total manifest DudeCT cases: `113`
- BINSEC status on macOS: `not_applicable`

ASM heuristic state:

- Unwaived fails: `0`
- Unwaived warnings: `3776`
- Waivers: `0`
- Findings:
  - `conditional_branch`: `3218`
  - `register_indexed_memory`: `434`
  - `indirect_call`: `124`

Interpretation: the enforced local macOS CT gate is healthy. The weak point is
the manual ASM heuristic layer. It is intentionally conservative, but thousands
of flat warnings are not a sharp engineering instrument.

## Recent Tooling Fixes

The immediate macOS failure was host-tooling, not crypto behavior:

- CT Python scripts assumed stdlib `tomllib`, which fails under Python < 3.11.
- The CT recipes now resolve a Python 3.10+ interpreter with TOML support.
- CT TOML parsing now goes through `scripts/ct/toml_compat.py`.

The evidence-sharpness audit also found a real DudeCT harness problem:

- Some manifest filters were substring-ambiguous.
- All-case runs could select extra benches, leading to misleading diagnostics
  and missing per-case reports.
- Chacha20-Poly1305 case names were made unique against XChaCha names.
- RSA PKCS#8 diagnostic stage probes now use the same legacy verification
  policy as the fixtures.
- Manifest validation now requires every DudeCT filter to match exactly one
  registered bench.

Do not remove that invariant. It prevents a quiet class of CT evidence rot.

## Non-Goals

- Do not force BINSEC onto macOS. This CT workflow is Mach-O artifact/heuristic
  plus DudeCT locally; BINSEC belongs on Linux/ELF runners.
- Do not add blanket ASM waivers before review.
- Do not treat a green DudeCT run as a proof. It is empirical evidence for a
  configuration, not a formal guarantee.
- Do not promote noisy diagnostic probes into release blockers unless they map
  to a real CT claim.
- Do not solve this by hiding warnings. Unknowns need classification, not
  deletion.

## Phase 1: Make ASM Heuristics Triageable

Objective: replace the flat warning pile with a compact review report sorted by
risk and impact.

Implementation target:

- Extend `scripts/ct/asm_heuristics.py` or add a companion report step.
- Group findings by:
  - primitive
  - reachable CT symbol
  - finding kind
  - source/disassembly artifact
- Emit a Markdown summary next to `asm-heuristics.json`.
- Put `register_indexed_memory` first, then `conditional_branch`, then
  `indirect_call`.
- Include top symbols by warning count and a stable list of exact instruction
  sites for the highest-risk class.

Verification:

```bash
just ct-full
just ct-validate --target aarch64-apple-darwin --profile release --strict-coverage
```

Success criteria:

- The report identifies the top risky symbols without requiring manual JSON
  spelunking.
- Counts in the Markdown report match `asm-heuristics.json`.
- No change to CT pass/fail semantics yet.

## Phase 2: Review Register-Indexed Memory First

Objective: classify every reachable `register_indexed_memory` warning in
CT-intended primitives.

Why first: secret-indexed memory is one of the highest-signal CT failure shapes.
Most crypto code can tolerate public indexing; it cannot tolerate secret table
lookups.

Classification buckets:

- `public-index`: index is public length, block counter, lane number, CPU
  feature, or fixed loop structure.
- `fixed-pattern`: address varies by static schedule, not by secret data.
- `compiler-artifact`: generated addressing is not secret-derived and maps back
  to public/fixed source behavior.
- `needs-fix`: plausible secret-derived address or insufficient evidence.
- `needs-binsec`: cannot be justified locally; needs Linux proof or a narrower
  harness.

Verification:

```bash
just ct-full --dudect-gate all
just ct-validate --manifest-only --strict-coverage
```

Success criteria:

- Every `register_indexed_memory` finding in a CT-intended reachable symbol is
  classified.
- Any `needs-fix` item has a source issue or patch plan.
- No blanket waiver covers multiple unrelated symbols.

## Phase 3: Review Conditional Branches

Objective: classify reachable CT-claim branches by whether the branch condition
is public, fixed, or potentially secret.

Expected public branch classes:

- public input length
- public algorithm parameter
- public CPU dispatch
- fixed loop bound
- error handling before secret material is introduced
- diagnostic-only probe shape

Risk classes:

- branches after secret material is loaded
- data-dependent early exit
- comparison-dependent control flow around keys, tags, scalars, or private
  components
- compiler-introduced branch from source code intended to be branchless

Verification:

```bash
just ct-full
```

Success criteria:

- Top branch-heavy symbols are classified.
- Any branch whose condition cannot be proven public is fixed or escalated.
- The triage report can distinguish public length/control branches from
  secret-dependent candidates.

## Phase 4: Review Indirect Calls

Objective: prove that indirect calls in reachable CT symbols are dispatched only
on public state.

Expected public classes:

- CPU feature dispatch
- public algorithm variant dispatch
- static function table selected before secret processing
- trait/vtable edge outside the secret-dependent region

Verification:

```bash
just ct-full
```

Success criteria:

- Every indirect call in CT-intended reachable symbols has a public dispatch
  explanation or a fix.
- No indirect call target is selected from secret bytes, secret scalar bits,
  private key limbs, password bytes, or tag material.

## Phase 5: Add Structured Waivers Only After Review

Objective: make reviewed findings durable without normalizing unknown warnings.

Waiver fields:

- primitive
- symbol
- finding kind
- instruction address or stable disassembly locator
- source reference when available
- classification bucket
- rationale
- reviewer
- date

Rules:

- Waivers are exact, not broad regex blankets.
- A waiver must explain why the value is public or fixed.
- A source edit that moves the instruction should invalidate or require
  revalidation of the waiver.
- New unwaived warnings in CT-intended reachable symbols should be visible in
  CI summary output.

Verification:

```bash
just ct-full
just ct-validate --manifest-only --strict-coverage
```

Success criteria:

- `unwaived_warn_count` trends down because findings were reviewed, not hidden.
- New unknown findings are obvious in the report.
- Required CT gate behavior remains unchanged until the waiver system is stable.

## Phase 6: Linux BINSEC Follow-Through

Objective: keep the formal proof lane honest and separate from macOS local
validation.

Work items:

- Confirm Linux CI runs BINSEC against the manifest-required kernels.
- Ensure macOS reports keep saying BINSEC is not applicable rather than skipped
  silently.
- For any `needs-binsec` ASM triage item, add or tighten a Linux kernel harness
  instead of trying to prove Mach-O locally.

Verification:

```bash
just ct-binsec --target x86_64-unknown-linux-gnu
just ct-validate --target x86_64-unknown-linux-gnu --profile release --strict-coverage
```

Success criteria:

- Linux reports show required BINSEC kernels passing or explicitly unsupported
  by policy.
- macOS reports remain honest about what they can and cannot prove.

## Decision Rule

Start with Phase 1. If the triage report does not make the top
`register_indexed_memory` findings obvious, fix the report before reviewing
hundreds of warnings by hand.

Once Phase 1 is good, spend review time in this order:

1. `register_indexed_memory`
2. `conditional_branch`
3. `indirect_call`
4. structured waivers
5. Linux BINSEC tightening

That order maximizes CT risk reduction per hour. More DudeCT cases can come
later, but they are not the current bottleneck.
