Formal verification boundaries in corecrypto

Formal verification boundaries in corecrypto

Apple’s ML-KEM and ML-DSA work shows where formal proof helps and where it stops. The strongest part sits in the portable C implementation, where the code can be tied closely to the FIPS 203 and FIPS 204 formulas. Once hand-tuned routines, compiler behaviour, and processor features enter the picture, the proof boundary gets narrower and the engineering gets fussier.

The portable C layer is where the proof starts to matter

The core of the proof work sits in the portable C layer. That is the part that can be expressed in a way that maps cleanly to the specification formula, which is the whole point if the goal is conformance rather than just passing tests.

For ML-KEM and ML-DSA, that matters because the implementation is not just trying to produce the right outputs in ordinary cases. It has to stay inside the mathematical shape of the standard while avoiding secret-dependent behaviour that could leak timing signals. Portable C gives a controlled place to do that, before the code gets split across fast paths and machine-specific tricks.

Keep the implementation formula close to the FIPS specification

The useful proof target is not vague correctness. It is formula equivalence: if the implementation formula matches the specification formula, the output should match the standard for the supported cases. That is a stricter claim than conventional testing can give, and it cuts through a lot of hand-waving.

That also sets a boundary. A proof that covers the implementation formula does not automatically prove the whole stack, the API surface, or every compiler decision. It proves the code that was written in the portable layer, under the assumptions used for that proof. If a change moves logic into a different path, or changes a compiler assumption, the old proof does not magically follow along.

Hand-tuned code and compiler effects sit outside the easy part of the proof

Fast paths are where proof work gets awkward. Apple uses hand-optimised code for performance-sensitive and security-sensitive routines, plus processor features such as Data Independent Timing and Pointer Authentication. Those choices are sensible, but they also move more behaviour away from the neat portable C world.

Compiler effects are the obvious nuisance. A compiler can reshape code in ways that preserve output but weaken timing-leak protections if the implementation is not written and constrained carefully. The proof boundary has to account for that, or the result is a neat proof attached to messy machine code that no longer behaves the same way in practice.

Preserve timing behaviour across optimisation and processor features

Timing behaviour needs to survive optimisation, not just source review. If a routine is meant to avoid secret-dependent execution timing, the compiled form still has to respect that intent. Data Independent Timing helps on Apple silicon, but it is a hardware feature, not a proof substitute. It reduces exposure to micro-architectural timing signals, which is useful, but it does not erase the need to check what the compiler and the processor actually do.

Pointer Authentication sits in a different bucket. It hardens against memory corruption exploits, which is useful when the threat model includes code-reuse or corrupted pointers. It does nothing for a proof about mathematical equivalence, and it should not be treated as if it does. The boundary stays real: correctness proofs, side-channel controls, and memory hardening each cover different failure modes.

Test the dump path, not the policy wording

The practical mistake is to trust the policy statement and ignore the generated code. If a dump, assembly listing, or optimisation report shows a branch, load pattern, or data dependency that should not be there, that is the thing to inspect. The words in the design document do not stop a compiler from being clever.

That is especially relevant for code meant to resist timing leakage. A proof may show that the portable C formula is safe under its assumptions, but the dump path shows whether those assumptions survived compilation. If they did not, the boundary has shifted and the proof has not followed.

Related posts

Weekly Tech Digest | 06 Jul 2026

Stay updated with the latest in tech! This digest covers AI ethics, auto industry shifts, and the impact of politics on technology, exploring today's pressing issues.

wolfCOSE zero-allocation parsing in embedded C

wolfCOSE looks sensible only if you care about what your firmware actually has to carry. I like that, because on small targets the wrong crypto feature can cost more than the message itself, and there...

restic | v0.19.1

restic v0 19 1: safer FUSE mounts and mountpoint checks, robust backup source and exclude handling, clearer CLI JSON output, Windows SFTP deletion fixes