Repository navigation
x86-64 P-256 ECDH by 5-bit windows in Jacobian coordinates (−18%) - #1256
Merged
Merged
Conversation
`PrimeOrder C` (Proof/Weierstrass/PrimeOrder.lean, no Mathlib): every point of the curve but the point at infinity has order `C.n`. `primeOrder_of` (Proof/Weierstrass/GroupOrder.lean) proves it for a curve with no point of order 2 whose base point has prime order n with 2p + 1 < 3n: Mathlib's group of points has at most 2p + 1 elements (an injection into Option (ZMod p × Bool)), n divides its order, so the order is n or 2n, and 2n is ruled out by Cauchy's theorem, since there is no element of order 2. `Proof.P256.primeOrder` (Proof/P256/PrimeOrder.lean) instantiates it; only the P-256 law variant imports it. The x86-64 interface `HasLawInvOrd C` (Proof/Weierstrass/X86_64/ InvInterface.lean) extends `HasLawInv C` with `prime : PrimeOrder C`; P-256's x86-64 variant now supplies it, and its generic registration files take it. No TCB, Spec or generated code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
`Cfg.exchangeWith c mq` is the exchange with any program `mq` computing `[d]P`; `exchange` and `exchangeJ` are it with `mulQ` and `mulQJ` (definitionally: the generated code is unchanged). `MulOk c mq W` (Proof/Ecdh/X86_64/MulOk.lean) states what the exchange needs of `mq` followed by the power: from the state before it (the hypotheses of `mulPow_ok`), it leaves `MulPostW`, `R` representing `[k]P` for `k` in `[1, n-1]` and `Z^(p-2)` in `ACC`, writing only `W`; `MulW c W` is what the rest needs of `W` (the constants, `D` and the flag kept). `mulQ_ok`/`mulQ_w` give them for the window method or the ladder. `exchangeWith_ok` and `ecdh_x86_of` take them; `exchange_ok` is the corollary for `mulQ`, so P-224, P-384 and P-521 are unchanged. P-256's `ecdh_x86`, `ecdh_verified` and their ADX versions are commented out (TODO(WinJac)) until the Jacobian window method's `MulOk` is proven; its constant-time proofs still check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
The ECDH proof's MulOk runs the multiplication for any scalar below 2^256 and uses the result only for k < n, so the window method's frame now holds for any k, with R a triple of some point of the curve. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
mulQJ_ok: the Jacobian 5-bit window method computes [d]P for d < n (MulOk) for four words and a curve of prime order n = 17 (mod 32), with any doubling that doubles a Jacobian triple in place; mulQJ_w: what it writes keeps the constants, d and the flag (MulW). P-256's instances (mulQJP256_ok, mulQJP256x_ok) re-enable ecdh_x86, ecdh_verified and their _adx versions, which now take P-256's prime order, and the registration file passes the variant's h.prime. Rename the Jacobian window method's TblOk, storeEntry_ok, build_ok and buildStep_ok (JTblOk, jstoreEntry_ok, jbuild_ok, jbuildStep_ok), which clashed with the 4-bit window method's in modules that import both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
with_reducible assumption, instruction lists ascribed next to ++, and the doubling's slots by a sublist rather than tauto. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
The _adx variant selects the table's entries 32 bytes at a time with AVX2, so it requires avx and avx2 too (the emitter refused it); the notes describe the 5-bit Jacobian window method and the doubling by halving rather than the 4-bit windows with complete additions. src/asm/x86_64/ecdh_p256.rs regenerated (EmitOne P256.X86_64; its consts.rs, which holds every algorithm's tables, left as it was: the group's table is unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
…vwbldb-p256-ecdh-jac
alex
pushed a commit
that referenced
this pull request
Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
alex
pushed a commit
that referenced
this pull request
Oct 8, 2026
#1256 made the x86-64 ECDH proof generic over the scalar multiplication (exchangeWith, MulOk) and added PrimeOrder. P-521 now uses its own mulQJ4 (the 4-bit windows with a Jacobian accumulator) through exchangeWith, instead of a Cfg.jacWin switch in mulQ, and takes its prime order from #1256's PrimeOrder and primeOrder_of in place of OrdN and its own group-order proof. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019KRjToTNRB2yxFCdNMux4P
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.
P-256 ECDH on x86-64 (
vg_ecdh_p256,vg_ecdh_p256_adx) now computes[d]Pby a new window method: 5-bit signed windows in Jacobian coordinates, with no complete additions. P-256's group has prime order, which this PR proves, so the window's additions are never exceptional.The window method (
Impl/Weierstrass/X86_64/WinJac.lean)dis recoded asd + 16 Σ_{j<52} 32^j, giving 52 digits in[-16, 15]. They are read by the comb's digit code withw = 5.[1 … 16]P, each entry as(X, Y, Z, Z², Z³)in Jacobian coordinates:[2]Pis a doubling, and[m+1]P = [m]P + Pby the mixed addition (jacMixedHead/jacMixedTail).R = O:doubleHalfPublic, which the joint verification already uses), in a loop._adx(selPassY) and 16 at a time otherwise (selPassAt). It is then negated for a negative digit.Z²andZ³(CachedJac.head+jacTail), 11M + 3S.D = TwhereR = O(nzMask), andRis kept for a zero digit (eqMask 0).Ris converted to projective coordinates for the existing tail (TCombCfg.outFix/outOps).The previous method used 4-bit windows (65 digits), complete additions (Renes–Costello–Batina), and conversions between projective and Jacobian coordinates around every window's doublings.
The doubling is a parameter of the window method.
Cfg.exchangeJis the exchange with this multiplication, and only P-256 uses it. The other curves keepCfg.exchange, and their generated code is unchanged.Why no addition is exceptional (
Proof/Weierstrass/WinJacMath.lean)Proof/Weierstrass/GroupOrder.leanandProof/P256/PrimeOrder.leanprove that every point butOhas ordern:#E ≤ 2p + 1(an injection intoOption (ZMod p × Bool));n ∣ #E, sinceGhas ordern;2p + 1 < 3n;#Eis odd,#E = n, andnis prime.j,R = [32 e]Pwith0 ≤ e < n, ande < n - 32forj ≥ 1. Adding[d]P, fore ≥ 1andd ≠ 0, is exceptional only if32 e ≡ ±d (mod n).j = 0withk = n - 2|d|.d₀ = -|d|. Butd₀ = ((k + 16) mod 32) - 16, andn ≡ 17 (mod 32)for P-256, so it never happens (loop_noexc).[m]P + Pfor2 ≤ m ≤ 15is never exceptional (tbl_noexc).jstep_pt) only needsRto stay on the curve; the result is used only for1 ≤ d < n.winJac_ok(Proof/Weierstrass/X86_64/WinJac.lean) is the window method's theorem. Its pieces are inWinJac{Lay,State,Ops,Store,Build,Dbls,Select,Entry,Step,Iter}.lean, and the P-256 instance with the halving doubler is inProof/P256/X86_64/WinJac.lean.The ECDH proof is now generic over the multiplication
Proof/Ecdh/X86_64/MulOk.leandefines the interface:MulOk c mq W: the oldmulPow_ok's hypotheses, concludingR = [k]Pfor1 ≤ k < n, withWthe bytesmqmay write.MulW c W:Wis apart from what the rest of the exchange reads.exchangeWith_ok(Main.lean) proves the exchange for anymqthat satisfies them.d = 0is handled there directly.mulQ_ok. P-224, P-384 and P-521 go through it, unchanged.mulQJ_ok(Proof/Ecdh/X86_64/WinJac.lean).taint_decide, as before (ecdh_ct,ecdh_ct_adx).Prime order in the law variant
HasLawInvOrd(Proof/Weierstrass/X86_64/InvInterface.lean) extendsHasLawInvwithprime : PrimeOrder C.Variants/P256/X86_64/Law.lean) now has this type.Generic/P256/X86_64/*,Generic/MdHash/P256/X86_64/*) take it.GroupOrder.lean) is imported only through the variant.Features
vg_ecdh_p256_adxnow uses AVX2 for the selection, so it declares["bmi2", "adx", "avx", "avx2"], asvg_ec_p256_public_key_adxdoes. The Rust side already chooses the_adxfunctions together, byec::p256::Mul::select, which requires all four.This touches no TCB or spec files.
Measurements
Criterion on Cascade Lake,
mainand this branch alternated:ecdh_p256_adxVG_CPU_FEATURES=none)Checks
lake buildandlake env lean --run Emit.lean --check: pass. Onlysrc/asm/x86_64/ecdh_p256.rschanges.ci/check_lean_imports.py,check_lean_speed.py,check_vectors.py,check_arch_gates.py,check_variants.py,check_mcdt.py: pass.ci/algorithms_table.py: no change.cargo fmt --check,cargo clippy --all-targets -- -D warnings: pass.cargo test --releasewith Wycheproof: pass as is and withVG_CPU_FEATURES=none.d = n - jfor smallj.🤖 Generated with Claude Code
https://claude.ai/code/session_01KfFYaXu423bswZqpujcACe
Generated by Claude Code