Implement AES-CMAC on ARMv7 - #477
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
|
The Benchmarks jobs (Compare with base on 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
Implements AES-CMAC on ARMv7, following the x86-64 (#463) and AArch64 (#469) implementations, both merged. Like those, this is an implementation only: it changes no
Spec/orTCB/files.Lean
Impl/CmacAes/Arm.lean:vg_cmac_aes_subkeys,vg_cmac_aes_updateandvg_cmac_aes_finalize.vg_aes_ctr32on a zero block, with the block as the counter.vg_aes_ctr32takesnand its scratch buffer on the stack. Each call is therefore in a frame (push {rA, rB}…pop) that pushes them, so the functions use 8 bytes of stack (stack := 8).lrare saved in the scratch buffer ([2064, 2096)).Las four byte-reversed words (rev), with the reduction masked (0 − (msb)&0x87).vg_aes_ctr32, so the calls are direct, with no generic file.Proof/CmacAes/Arm/):Proof/MdStream/Arm/Common.lean(wp_ldr,wp_str,saveList_ok, …), plus two reusable blocks inWords.lean: a 4-word XOR (xorBlk_ok) and a 4-word zeroing (zeroBlk_ok).Call.leanproves the framed call (WP.frame,WP.call) and its constant time (RelCT.frame,RelCT.call).Contract.lean), whichsig_impliesconnects to the shared contracts ofSpec/Cmac/Contract.leanwithstack := 8.ctr_relfor each call.Proof/Cmac/Mem32.lean,Block32.leanandDbl32.lean(doubling over four words, and the byte-reversed loads and stores).Proof/Framework/Arm/ArgTaint.lean(new, untrusted):argTaint,agree_argTaint,rel_agreeandrel_wp, the ARM counterpart ofFramework/X86/ArgTaint.lean. The same definitions are currently private copies in the BLAKE2 and HMAC proofs, which this PR doesn't touch.Artifacts/CmacAes/Arm.leanregisters the three functions.Rust
cmac::aes::AesCmacnow also builds on ARMv7, scalar only:Backend::selecthas an ARM case that always returnsScalar, with a unit test.Testing
cargo testpassed forarmv7-unknown-linux-gnueabihfandthumbv7neon-unknown-linux-gnueabihfunder qemu: the CMAC unit tests, Wycheproofaes_cmac_test.json, and all CAVPCMACGen/CMACVervectors. The full suite (198 tests) also passed forarmv7, and on the host.lake buildandlake env lean --run Emit.lean --checkci/check_*.pyscriptscargo fmt --checkandcargo clippy -D warnings(host,armv7,thumbv7neonand the bench crate)mainhas been merged in since. Its CMAC files matched the AArch64 branch this was built on, so only Benchmarks: a changed benchmark runs only the benchmarks of its modules #488's benchmark-selection change came along. With it, the Benchmarks run covers only AES-CMAC and AES-GCM.The x86 (32-bit) implementation is #485, stacked on this PR.
🤖 Generated with Claude Code
https://claude.ai/code/session_01WkLN6tAYk76HACiEWLbAMD