Repository navigation
SHA-384/512/512-224/512-256 streaming on AArch64 through the shared MdStream code - #619
Merged
Merged
Conversation
… (WIP: asm not yet regenerated) Add block and length-field sizes (B, L) to the AArch64 MdStream Params and generalise its proofs, instantiate them for SHA-384/512/512-224/512-256, and delete the bespoke SHA-512 streaming code and proofs. Ed25519 on AArch64 now calls framed SHA-512 update/finalize (stack 336 -> 352). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
`update` copied data into the partial-block buffer, and `finalize` zeroed the padding, one byte per iteration: up to 127 iterations each. Both now move eight bytes per iteration (unaligned `ldr`/`str`) while eight remain, then finish one byte at a time. This applies to both compression backends (scalar and FEAT_SHA512), and so to HMAC, PBKDF2 and Ed25519's hashing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…strs_keeps The generated code changes only for the SHA-512 family's streaming functions on AArch64 (which now call the compression function) and in Ed25519's AArch64 stack documentation (352 bytes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Its bespoke SHA-512 streaming code is replaced here by the generic code, into which its eight-bytes-at-a-time loops are ported next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
…kqbts6-sha512-mdstream-aarch64
…ight bytes at a time Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
… one definition The streaming code's scratch offset and the compress, update and finalize contracts all use Impl.Sha512.AArch64.scratchBytes, and the shared code admits scratch offsets up to 1024 bytes, so a larger compression scratch (#611) changes one number here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
…ises it with HMAC's) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
The SHA-512 compression scratch size is now scratchBytes := 640. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf
alex
marked this pull request as ready for review
October 2, 2026 17:19
alex
enabled auto-merge
October 2, 2026 17:20
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.
Summary
SHA-384, SHA-512, SHA-512/224 and SHA-512/256 streaming (
updateandfinalize, scalar and_sha3) on AArch64 now go through the shared Merkle–Damgård streaming code (Impl/MdStream/AArch64.leanandProof/MdStream/AArch64/). That code already served MD5, SHA-1, SHA-224 and SHA-256. This PR deletes SHA-512's own streaming code and proofs.It does the same as #600 did for x86-64. The proofs come out of a review for duplicated code, where each duplicate points to a missing abstraction. Here the missing abstraction was a block size and length-field size in the shared streaming code, which only knew 64-byte blocks with a 64-bit length.
The eight-bytes-at-a-time copy and zero-padding loops that #595 (merged) wrote for SHA-512 alone now live in the shared code. MD5, SHA-1 and SHA-256 get them too.
The abstraction
Impl.MdStream.AArch64.Paramsgains:B: the block size, 64 or 128 bytes.L: the length-field size, 8 or 16 bytes.The code is generic over both:
N + Band the length field atN + B - L;len >> lg Band the partial-block length islen & (B - 1);B - L;len128writes SHA-512's 128-bit big-endian bit length;out64writes the 64-bit big-endian state words.The generic proofs (
Proof/MdStream/AArch64/{Common,Update,Finalize,Words}) are proved once for anyMd B N LwithDims(B ∈ {64, 128},0 < L ≤ 16).The copy and zero loops are #595's. A word loop runs
x13 = x11 >> 3times, storing withstoreWord, and a byte loop handles the rest. Their proofs (copy_word_step/copy_words_ok/copy_ok,zero_word_step/zero_words_ok/zero_ok) follow #595's, generalised overP.SHA-512 is one instance:
Impl/Sha512/AArch64/Stream.lean(params,updateWith,finalizeWith) andProof/Sha512/AArch64/Stream/Md.lean(itsMd,DimsandShape, about 80 lines).Proof/Sha512/AArch64/Variant.leanderivesupdateandfinalizeand theirVerified, register and depth facts for any compression variant. The_sha3variant therefore needs nothing of its own (Sha3/Stream.leanandSha3/StreamLit.leanare deleted), andci/check_variants.pypasses.The SHA-512 functions now call
vg_sha512_compress*the way MD5, SHA-1 and SHA-256 call theirs, instead of inlining the whole compression function. The shared code savesx30in a 16-byte frame, so the callers changed:Proof/Ed25519/AArch64/Whole/) admits callees with one frame below the locals:CK E,call_okF,CallReady.wpF, andwrap_okfor depth ≤ 1;vg_ed25519_{public_key,sign_cached,verify_message}document 352 bytes of stack instead of 336.Impl/Pbkdf2/AArch64.leantakesout64andlen128from the shared code, andProof/Pbkdf2/AArch64/Sha512.lean(moved intoProof/MdStream/AArch64/Words.lean) is deleted.updateandfinalize.Interaction with #611
#611 (merged) gave the AArch64 compression function 640 bytes of scratch. Everything update and finalize reserve for the compression function now comes from one definition,
Impl.Sha512.AArch64.scratchBytes(640):params.so;compressAArch64,updateAArch64andfinalizeAArch64contracts (scratchBytes,scratchBytes + 48);Shared.lean;Wb.A later change to the compression function's scratch changes that one number on the streaming side. #611's edits to the deleted
Proof/Sha512/AArch64/Stream/*have no counterpart here.Copies removed
Proof/Sha512/AArch64/Stream/Update.leanProof/Sha512/AArch64/Stream/Finalize.leanProof/Sha512/AArch64/Stream/Common.leanProof/Pbkdf2/AArch64/Sha512.leanImpl/Sha512/AArch64/Stream.lean(bespoke code)Impl/Sha512/AArch64/Sha3/Stream.lean,Proof/Sha512/AArch64/Sha3/StreamLit.leanImpl/Pbkdf2/AArch64.lean(out64,len128,sha512)Against
main:src/asm/: +2,288 / −13,756.src/asm/aarch64/sha512.rsloses 10.4k lines because update and finalize are about 150 lines each instead of 1.2k–3.6k with the compression function inlined.Performance
Code
Instructions executed under
qemu-aarch64 -cpu max(-one-insn-per-tb -d exec) for oneupdateof n bytes afterinit, thenfinalize. The baseline ismainas of #611, which includes #595 and #610. Every digest is identical.What changed:
finalize.x30frame._sha3.Proofs
Heartbeats from Lean's profiler (
-Dtrace.profiler.useHeartbeats=true, the CLAUDE.md recipe;perfis unavailable on this VM) for every module this PR touches or that depends on it. Each module was measured on its own, on the same machine, and the elaboration and kernel totals are reported separately. The baseline ismainwith #595, and the PR column is this branch before it merged #610–#612; those PRs change the compression function, not these streaming proofs.MdStream.AArch64.CommonMdStream.AArch64.UpdateMdStream.AArch64.FinalizeMdStream.AArch64.WordsMd5.AArch64.Stream.MdSha1.AArch64.Stream.MdSha256.AArch64.Stream.MdSha1.AArch64.ScalarBackendSha1.AArch64.Sha2BackendSha256.AArch64.ScalarBackendSha256.AArch64.Sha2BackendSha256.AArch64.VariantSha512.AArch64.VariantSha512.AArch64.ScalarBackendSha512.AArch64.Sha3BackendSha512.AArch64.SharedSha512.AArch64.LitPbkdf2.Md.AArch64.Hashes.Sha512Pbkdf2.Md.AArch64.Hashes.Sha256Pbkdf2.AArch64.IterateEd25519.AArch64.Whole.HashEd25519.AArch64.Whole.LayoutEd25519.AArch64.Whole.WrapEd25519.AArch64.PublicKey.CTEd25519.AArch64.SignCached.EntrySha512.AArch64.Stream.CommonSha512.AArch64.Stream.UpdateSha512.AArch64.Stream.FinalizeSha512.AArch64.Sha3.StreamLitPbkdf2.AArch64.Sha512Sha512.AArch64.Stream.MdOverall, elaboration drops by 19% and the kernel by 14%. The shared modules grow, since they now cover two block sizes and the word loops. The four SHA-512 stream modules they replace cost more than that growth.
Sha512.AArch64.Litno longer materializes the stream code. The SHA-512 backends check their register facts throughinstrs_keeps (by lit_decide)on the compression literal, which avoids re-evaluating the whole stream function in the kernel. Every declaration stays within the defaultmaxHeartbeats, andci/check_lean_speed.pypasses.Validation
At the latest merge of
main(including #598, #605, #606, #610, #611, #612, #615 and #616):cd lean && lake build(every job),lake env lean --run Emit.lean, then--check(axiom and compiler audits, and everyVG.Specdefinition inSpec/).src/asm/is inaarch64/{md5,sha1,sha256,sha512,ed25519}.rsonly (the Ed25519 change is only its stack documentation).python3 ci/check_lean_imports.py,check_lean_speed.py,check_vectors.py,check_arch_gates.py,check_variants.pyandcheck_mcdt.pyall pass.ci/algorithms_table.pyleavesREADME.mdunchanged.cargo fmt --check;cargo clippy --all-targets -- -D warningson x86-64 and--target aarch64-unknown-linux-gnu.cargo testwith Wycheproof:qemu-aarch64, 246 pass with-cpu maxand 247 withVG_CPU_FEATURES=none --features cpu-features-env.Trusted code
There are no changes to
Spec/orTCB/. The per-target contracts inProof/Sha512/AArch64/Compress.lean(updateAArch64,finalizeAArch64, which areProof/) gain the stack conditions of the shared contracts. They are widened toSpec.Sha512.updateContract AArch64.abi 16(and the same forfinalize) inShared.lean.🤖 Generated with Claude Code
https://claude.ai/code/session_01RWYqd9UNiJ8dEEq3JQhyCf