Repository navigation
AES-ECB: wipe only the scratch the hardware block functions write - #3167
Merged
Merged
Conversation
AesEcb::crypt zeroed a 2 KB working space and wiped it again on every call,
which made a one-block encryption (QUIC header protection) about 5.5 times
slower than aws-lc-rs's on Neoverse N2. The AES-instruction implementations
vg_aes_{en,de}crypt_blocks_aes never touch it: prove it (their loop
invariant already frames memory to the data, `blocks_frame`), register
`written := [("scratch", 0)]`, and have AesEcb leave the working space
uninitialized and wipe only what the chosen function may write.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FoYWDDEDFq1DFev5X7n2aq
The loops of vg_aes_encrypt_blocks_aesni and _vaes keep their state predicate
(KPf) together with memory framed to the data, so encryption writes no byte
of `scratch`: register `written := [("scratch", 0)]` for both and wipe only
that in AesEcb. Decryption, which puts the AESIMC round keys in `scratch`,
and x86's AES-NI, which saves registers there, keep the full wipe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FoYWDDEDFq1DFev5X7n2aq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On every call,
AesEcb::encryptanddecryptzero-initialized a 2 KB working space and then wiped it. The hardware AES block functions mostly never touch it. One block, which is QUIC header protection's shape (#3154), was about 5× slower than aws-lc-rs: 41 ns against 7 ns on Neoverse N2.This PR proves which implementations write none of
scratchand registers that with the existingwrittenmechanism.AesEcbthen wipes only the bytes the chosen function may write. There is no Spec or TCB change.writtenvg_aes_{en,de}crypt_blocks_aesblocks_frame,blocks_scratch,Proof/Aes/AArch64/Aese/Blocks.lean).vg_aes_encrypt_blocks_aesniand_vaesKPf, withStable.frameandBlkOk.frame/BlkOk16.frame), so the shared loop invariant is unchanged.vg_aes_decrypt_blocks_aesniand_vaesscratch.vg_aes_{en,de}crypt_blocks_aesniesi/edithere, and decryption stores 240 bytes of round keys there.On the Rust side, in
src/aes_ecb.rs:MaybeUninit. The contracts hold for any contents ofscratch.*_SCRATCH_WRITTEN, or 2048 bytes.AesNiarm uses a cfg'd constant that stays at 2048.AesEcbis the only Rust caller that passes this working space.Speed
Runs 39875319491 (aarch64) and 39875323075 (x86-64), against
mainat acdcc1f, modulesaes_ecb:aes,ssse3: AES-NI)quic-hp-aes-128/16quic-hp-aes-256/16aes-ecb-encrypt-128/64aes-ecb-decrypt-128/64On N2 every AES-ECB row is faster, by 23–26% at 64 B and about 11% at 1 KiB. The
noneconfiguration (bitsliced, still fully wiped) moves within ±1%.Four x86-64 decrypt rows came out slower:
decrypt-192/16384: 2.9% on the 7763.decrypt-128/64: 3.6% on the 9V45.decrypt-256/64: 3.0% on the 9V45.decrypt-256/16384: 3.2% on the 9V45.The x86-64 decrypt functions' assembly is unchanged, and their Rust path only drops the 2 KB zero-initialization; it wipes as before. Other decrypt rows in the same two jobs are 1–7% faster, and the signs are mixed between neighbouring sizes and key lengths. I read these four as noise or code placement, not this change, and haven't rerun them.
Checks
These all pass:
lake buildandEmit.lean --check, andEmitOne --checkforAes.AArch64andAes.X86_64after rebasing ontomain.check_lean_public_importson the changed modules.check_lean_imports,check_lean_modules,check_registrations,check_lean_speed,check_vectors,check_arch_gates,check_variantsandcheck_mcdt.cargo fmtandclippyon x86-64, x86 and aarch64.aes_ecbtests: on x86-64 with the default features,noneandaes,ssse3, and on aarch64 under qemu (-cpu neoverse-n2) with the default features andnone.Generated by Claude Code