Implement AES-CMAC on x86 - #485
Conversation
The three AES-CMAC primitives specified in VG.Spec.Cmac (vg_cmac_aes_subkeys, vg_cmac_aes_update, vg_cmac_aes_finalize), proven against their contracts on x86-64: correct and constant time. Each encrypts a block by calling the verified vg_aes_ctr32 on a zero block with the block as the counter, so the CMAC code is generic over the implementations of ctr32 (a new AesCtr32 interface under Generic/ and Variants/): the emitter also emits the _aesni functions, calling vg_aes_ctr32_aesni. The Rust API is verified_garbage::cmac::aes::AesCmac (new, update, finalize, verify, mac), choosing the implementation by CPU feature. It is tested against Wycheproof's aes_cmac_test.json and every vector of the vendored NIST CAVP CMAC generation and verification files, and benchmarked against OpenSSL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WkLN6tAYk76HACiEWLbAMD
The three AES-CMAC primitives specified in VG.Spec.Cmac (vg_cmac_aes_subkeys, vg_cmac_aes_update, vg_cmac_aes_finalize), proven against their contracts on AArch64: correct and constant time. As on x86-64, each encrypts a block by calling the verified vg_aes_ctr32 on a zero block with the block as the counter, and is generic over the implementations of ctr32 (the AesCtr32 interface on AArch64): the emitter also emits the _aes functions, which call vg_aes_ctr32_aes. The calls keep the return address in x30, which each function saves in the scratch buffer, so no stack is used. The lemmas about blocks in memory that do not depend on the target move to Proof/Cmac/Frame.lean and Proof/Cmac/Block.lean. The Rust API (verified_garbage::cmac::aes::AesCmac), its tests and its benchmark now cover AArch64 too, choosing the AES extension's implementation when the CPU has it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WkLN6tAYk76HACiEWLbAMD
…ngth check The rust-cpu-features jobs run only the tests they name; without cmac, the scalar implementation (chosen under SDE's Pentium 4) never ran under coverage. The rejected key's assertion keeps its message on one line, which coverage counts as run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WkLN6tAYk76HACiEWLbAMD
…mistic-mayer-jdgs35-aarch64
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WkLN6tAYk76HACiEWLbAMD
…imistic-mayer-jdgs35-arm
vg_cmac_aes_subkeys, vg_cmac_aes_update and vg_cmac_aes_finalize for ARMv7, each encrypting a block by calling the verified vg_aes_ctr32 in a frame that pushes its two stack arguments (8 bytes of stack), with correctness and constant-time proofs against the shared contracts. The Rust API, its tests and benchmark now also build on ARMv7 (scalar only). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WkLN6tAYk76HACiEWLbAMD
vg_cmac_aes_subkeys, vg_cmac_aes_update and vg_cmac_aes_finalize for 32-bit x86, all arguments on the stack (cdecl). Each encrypts a block by calling the verified vg_aes_ctr32 with the block as the counter and a zero data block, in a frame that pushes its six arguments (28 bytes of stack with the return address). The callee-saved registers are saved in the scratch buffer; the arguments are reloaded from the stack after each call. The subkeys are doubled as four byte-reversed 32-bit words, the carry masked rather than branched on. Proofs of correctness and constant time against the shared contracts of Spec/Cmac/Contract.lean, the Rust API enabled on x86 (the scalar implementation only), and the README's table regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WkLN6tAYk76HACiEWLbAMD
|
The Benchmarks jobs (Compare with base) were cancelled at their 15-minute timeout. The cause is not this PR's code. This PR's only benchmark change is the arch #488 fixes this: a changed benchmark now narrows the run to the modules its Generated by Claude Code |
…er-jdgs35-arm # Conflicts: # bench/benches/primitives/cmac_aes.rs # src/cmac/aes.rs # tests/cavp/cmac_aes.rs # tests/wycheproof/cmac_aes.rs
…tic-mayer-jdgs35-x86
…er-jdgs35-x86 # Conflicts: # bench/benches/primitives/cmac_aes.rs # src/cmac/aes.rs # tests/cavp/cmac_aes.rs # tests/wycheproof/cmac_aes.rs
|
merge conflicts |
|
The merge conflicts are resolved in 0d3bda3, which merges
After the merge, these all pass locally:
The branch also merges cleanly with the current Generated by Claude Code |
Implements AES-CMAC on 32-bit x86, following the x86-64 (#463), AArch64 (#469) and ARMv7 (#477) implementations, all merged. Like those, this is an implementation only: it changes no
Spec/orTCB/files.Lean
Impl/CmacAes/X86.lean:vg_cmac_aes_subkeys,vg_cmac_aes_updateandvg_cmac_aes_finalizefor 32-bit x86, with every argument on the stack (cdecl).vg_aes_ctr32on a zero block, with the block as the counter.vg_aes_ctr32and pops them intoeax. With the return address, that is 28 bytes of stack (stack := 28).ebx,esi,ediandebpare saved in the scratch buffer ([2064, 2080)). After each call, the code reloads its arguments from the stack, which nothing writes.Las four byte-reversed words (bswap, thenadd r, rfor the shift), with the reduction masked (0 − msb&0x87).vg_aes_ctr32, so the calls are direct, with no generic file.Proof/CmacAes/X86/):Proof/MdStream/X86/Common.lean.Words.leanadds a 4-word XOR (xor4_ok) and a 4-word zeroing (zero4_ok).Save.leancovers saving and restoring the registers and reading the stack arguments.Call.leanproves the framed call (WP.callWith) and its constant time (RelCT.callWith,ctr_rel).Contract.lean), whichsig_impliesconnects to the shared contracts ofSpec/Cmac/Contract.leanwithstack := 28.espand the stack arguments, throughFramework/X86/ArgTaint.lean) andctr_relfor each call.Proof/Cmac/(Mem32,Block32,Dbl32,Frame,Block).Artifacts/CmacAes/X86.leanregisters the three functions (spSafebylit_decide).Rust
cmac::aes::AesCmacnow also builds on x86, scalar only: theBackend::selectcase and its unit test coverarmandx86.Testing
cargo test --target i686-unknown-linux-gnupassed: the CMAC unit tests, Wycheproofaes_cmac_test.json, all CAVPCMACGen/CMACVervectors, and the full suite. The full suite also passed on the host.main(with Implement AES-CMAC on ARMv7 #477), these pass again:lake buildandlake env lean --run Emit.lean --checkci/check_*.pyscriptscargo fmt --checkcargo clippy -D warningsfori686i686locally, because this machine has no 32-bit OpenSSL. CI's Benchmarks jobs lint it.🤖 Generated with Claude Code
https://claude.ai/code/session_01WkLN6tAYk76HACiEWLbAMD