Constant-time EC scalar multiplication for prime and binary curves, plus AES bitsliced key-schedule hardening#159
Merged
Conversation
Replace the variable-time wNAF multiplier with a constant-time fixed-window multiplier as the default single-scalar variable-point multiplier on the custom prime-field curves (P-256, P-384, P-521, secp256k1), closing the last secret-dependent timing leak on the ECDH [d]Q path. - Add TCTHomogeneousMath (ClpHomogeneousPoint): Renes-Costello-Batina complete addition/doubling formulas in homogeneous coordinates (exception-free for prime-order curves), driven through a new ICTFieldOps interface (ClpICTFieldOps) over each curve's existing constant-time Nat field. - Add TFixedWindowCTMultiplier (ClpFixedWindowCTMultiplier): unsigned w=4 fixed window over a projective precompute table with a branchless masked select and one unconditional complete addition per window. Countermeasures: scalar blinding (k + r*n in fixed-width Nat), randomized projective coordinates, fixed processing length, secret-array wiping and an on-curve precondition. - Wire each custom curve's CreateDefaultMultiplier to the CT multiplier, making every secret x variable-point path (ECDH, ECDHC, ECIES) constant-time by default. secp256k1's variable-time GLV is retired from the default path; ECDSA/Schnorr verify keep GLV via the multi-scalar path (public, unaffected). - Add TTestECDHConstantTime: parity vs wNAF, exceptional-case, edge-scalar, blinding-transparency and ECDH round-trip tests across all four curves. - Harden the Nat allocators (TNat.Create/Copy and every fixed-width TNat*.Create*/CreateExt*) with a defensive Result := nil, working around an FPC codegen bug where an interface method returning a managed array leaves the next allocation's Result non-nil and SetLength does not re-zero it.
Pure rename, no behavior change. Rename the constant-time field-ops interface ICTFieldOps -> IFpFieldOps (unit ClpICTFieldOps -> ClpIFpFieldOps) and its per-curve adapters TSecP*CTFieldOps -> TSecP*FpFieldOps, updating all references and the FPC/Delphi package and consumer manifests. Name the interface by field type (Fp) to match the codebase's existing Fp/F2m convention (TAbstractFpCurve, IAbstractFpFieldElement, ...) and to leave room for a sibling IF2mFieldOps if constant-time binary-curve support is added later. "CT" is a contract documented on the interface, not part of its identity, so it stays off the type name; TFixedWindowCTMultiplier keeps "CT", where it marks the constant-time multiplier apart from the variable-time wNAF/GLV/tau-NAF ones.
Make secret-scalar x variable-point multiplication constant-time on the
custom Koblitz curve sect283k1, closing the last variable-time secret-
dependent path (ECDH [d]Q) for binary curves - mirroring the prime-curve
work already on this branch.
- Add a constant-time Lopez-Dahab (x-only) Montgomery ladder as the default
multiplier for sect283k1: branch-free conditional swap, batched single
field inversion for y-recovery, cardinality scalar blinding, randomized
projective coordinates, and a fixed processing length.
- Shared, reusable machinery so a future custom binary curve becomes
constant-time with just an IF2mFieldOps adapter + a CreateDefaultMultiplier
override (same shape as the prime-curve design, nothing else touched):
* ClpIF2mFieldOps - field-ops interface
* ClpLopezDahabLadder - ladder step + y-recovery
* ClpF2mMontgomeryLadderCTMultiplier - the multiplier
* TNat.CSwap64 / TNat.CMov64 - constant-time UInt64 swap/move
* TSecT283K1F2mFieldOps adapter + guard-free TSecT283Field.InvertCT
- Keep ECDSA/ECNR verification fast: SumOfTwoMultiplies names the tau-adic
NAF multiplier explicitly for Koblitz curves instead of reaching through
the curve default (now the constant-time ladder). All callers are verify/
recovery with public inputs, so this leaks nothing; ECDH still uses the
constant-time default.
- Rename the prime-curve test ECDHConstantTimeTests -> ECDHPrimeConstantTimeTests
(TTestECDHPrimeConstantTime) for parity with the new ECDHBinaryConstantTimeTests.
- TFixedWindowCTMultiplier: reject scalars wider than the group order (BitLength > order bits) instead of silently overflowing the fixed-width blinding buffer; mirrors FixedPointCombMultiplier's range check. - TFixedWindowCTMultiplier: zeroize the projective table, accumulator, selected entry and the coordinate blind on exit (defense-in-depth). - TFixedWindowCTMultiplier: make the scalar-blind length a constructor parameter (positive multiple of 32, default 64); no default-behaviour change. - TAesBitSlicedEngine: zeroize the expanded round-key schedule on destruction. - Tests: add out-of-range scalar guard test to the prime-curve CT suite. Note: secp256k1's default multiplier is now the constant-time multiplier (was TGlvMultiplier) — a perf regression on secp256k1 ECDH, correctness unaffected; ECDSA verify keeps its own SumOfTwoMultiplies path.
- TFixedWindowCTMultiplier: bound the scalar-blind length to [64, 512] and keep the multiple-of-32 requirement; the parameter may raise assurance, never lower it below the default, and can't be typo'd into huge buffers. - TAesBitSlicedEngine: zeroize the expanded round-key schedule before a re-key reallocates it, closing the cross-key-size re-Init case (the destructor already covers end-of-life). - Tests: blind-bits constructor validation (prime CT suite) and a cross-key-size re-Init KAT (bitsliced AES suite).
…copy - TAesBitSlicedEngine.KeySchedule: wrap the body in try/finally and zero the round-key intermediates (LCompSkey heap array, LSkey32 stack array) on every exit, including exception paths. - TAesBitSlicedEngine.KeySchedule: drop the success-path wipe of the incoming key array, matching TAesEngine/TAesLightEngine (the invalid-length wipe is kept). No behaviour change: Init receives TKeyParameter.GetKey(), which is a copy, so the caller's key was never mutated either way. - Tests: caller-key-preserved / invalid-length-raises in the bitsliced AES suite.
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
This PR closes the last secret-dependent timing leaks on ECDH
[d]Qpaths for custom prime-field and binary curves, and hardens the bitsliced AES engine’s key-schedule handling.Prime-field curves (P-256, P-384, P-521, secp256k1)
TFixedWindowCTMultiplieras the default single-scalar variable-point multiplierTCTHomogeneousPoint(Renes–Costello–Batina complete addition/doubling in homogeneous coordinates, exception-free for prime-order curves)IFpFieldOpsinterface and per-curveTSecP*FpFieldOpsadapters over each curve’s existing constant-time Nat fieldk + r·n), randomized projective coordinates, fixed processing length, branchless masked table select, secret-array wiping, on-curve preconditionCreateDefaultMultiplierto the CT multiplier — ECDH, ECDHC, and ECIES secret paths are constant-time by defaultBinary curve (sect283k1)
IF2mFieldOps,TLopezDahabLadder,TF2mMontgomeryLadderCTMultiplier,TNat.CSwap64/TNat.CMov64,TSecT283K1F2mFieldOps, guard-freeTSecT283Field.InvertCTSumOfTwoMultiplies(public inputs; ECDH still uses the CT default)Hardening follow-ups
BitLength > order bits) instead of silently overflowing the blinding buffer[64, 512](multiple of 32; default 64)TNat.Create/Copyand fixed-widthCreate*) with defensiveResult := nil(FPC codegen workaround)AES bitsliced engine
KeyScheduleexit (including exception paths)TAesEngine/TAesLightEngine)Tests
TTestECDHPrimeConstantTime(8 tests): default multiplier, wNAF parity, edge scalars, blinding transparency, exceptional formulas, ECDH round-trip, scalar range guard, blind-bits validationTTestECDHBinaryConstantTime(4 tests): default multiplier, wNAF parity, edge scalars, blinding transparencyTAesBitSlicedTests: cross-key-size re-Init KAT, caller-key-preserved, invalid-length behavior