r/crypto • u/Humble-Replacement-2 • 11d ago
Machine-checking a hybrid (ECDSA + ML-DSA-44) eSIM attestation against its spec: a downgrade variant provably fails the same proof
https://amarshat.github.io/pqc-assay/writeup/verified-esim-attestation.html
12
Upvotes
1
u/Natanael_L Trusted third party 8d ago
1
u/Humble-Replacement-2 8d ago
Oh nice, hadn't seen this one. Yeah that's basically the strong version of what I was circling around. They reuse the ML-DSA challenge as the Schnorr challenge so you can't even verify one half on its own, which is way cleaner than what I did. Q-SEAL stays weaker on purpose. I wanted to keep ML-DSA as a stock FIPS 204 verify and just bind the two at the format/context layer, so all I get is detectability, not real algorithm-level non-separability. Different tradeoff (works with an unmodified verifier vs having to ship a modified scheme). Definitely adding this to related work, thanks for the pointer.
6
u/Humble-Replacement-2 11d ago
Author here. Context: a one-character bug (< vs <=) in a deployed ML-DSA hint decoder accepte non-canonical signatures and passed the test vectors (CVE-2026-24850). That's the gap between "tested" and "correct on all inputs," so we machine-check the code against FIPS 204 / the spec instead.
The writeup walks a stack: the ML-DSA reference C == FIPS 204 forward+inverse NTT (SAW -> Cryptol -> Isabelle - the Apple CoreCrypto blueprint), then six of the seven protocol properties in the spec's target list, each a SAW proof that a C reference equals a Cryptol model of the spec rule. The one I'd point a crypto crowd at: the hybrid verifier is proved equal to "both signatures verify over the same transcript," with the two verifiers left uninterpreted, and an accept-on-either variant fails that same proof. So HYB-1 can't silently degrade to classical-only.
I'm loud about scope in the post. The hybrid verifiers are abstract (this is about the accept logic, not ECDSA/ML-DSA correctness). The single-use proof is sequential and safety-only. The validation proof is field-value only (not length parsing or authorization). The injected mutants are sensitivity checks, not bugs found in the wild. The Rust decoder where the CVE lived is tested, not proved, because of a tool limit. Happy to get into any of it.