Skip to content

Constant-time EC scalar multiplication for prime and binary curves, plus AES bitsliced key-schedule hardening#159

Merged
Xor-el merged 7 commits into
masterfrom
feature/constant-time-ec-scalarmul
Jul 22, 2026
Merged

Constant-time EC scalar multiplication for prime and binary curves, plus AES bitsliced key-schedule hardening#159
Xor-el merged 7 commits into
masterfrom
feature/constant-time-ec-scalarmul

Conversation

@Xor-el

@Xor-el Xor-el commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

This PR closes the last secret-dependent timing leaks on ECDH [d]Q paths 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)

  • Replace the variable-time wNAF multiplier with TFixedWindowCTMultiplier as the default single-scalar variable-point multiplier
  • Add TCTHomogeneousPoint (Renes–Costello–Batina complete addition/doubling in homogeneous coordinates, exception-free for prime-order curves)
  • Add IFpFieldOps interface and per-curve TSecP*FpFieldOps adapters over each curve’s existing constant-time Nat field
  • Countermeasures: scalar blinding (k + r·n), randomized projective coordinates, fixed processing length, branchless masked table select, secret-array wiping, on-curve precondition
  • Wire each custom curve’s CreateDefaultMultiplier to the CT multiplier — ECDH, ECDHC, and ECIES secret paths are constant-time by default
  • secp256k1’s variable-time GLV is retired from the default path (ECDH perf regression; correctness unchanged); ECDSA/Schnorr verify keep GLV via the multi-scalar path

Binary curve (sect283k1)

  • Add Lopez–Dahab (x-only) Montgomery ladder as the default multiplier: branch-free conditional swap, batched field inversion for y-recovery, cardinality scalar blinding, randomized projective coordinates, fixed processing length
  • Shared reusable machinery: IF2mFieldOps, TLopezDahabLadder, TF2mMontgomeryLadderCTMultiplier, TNat.CSwap64 / TNat.CMov64, TSecT283K1F2mFieldOps, guard-free TSecT283Field.InvertCT
  • ECDSA/ECNR verification keeps the tau-adic NAF multiplier explicitly in SumOfTwoMultiplies (public inputs; ECDH still uses the CT default)

Hardening follow-ups

  • Reject scalars wider than the group order (BitLength > order bits) instead of silently overflowing the blinding buffer
  • Zeroize projective table, accumulator, selected entry, and coordinate blind on exit
  • Bound scalar-blind length to [64, 512] (multiple of 32; default 64)
  • Harden Nat allocators (TNat.Create / Copy and fixed-width Create*) with defensive Result := nil (FPC codegen workaround)

AES bitsliced engine

  • Zeroize expanded round-key schedule on destruction and before re-key reallocation
  • Zeroize key-schedule intermediates on every KeySchedule exit (including exception paths)
  • Stop wiping the caller’s key copy on success (matches TAesEngine / TAesLightEngine)

Tests

  • Add TTestECDHPrimeConstantTime (8 tests): default multiplier, wNAF parity, edge scalars, blinding transparency, exceptional formulas, ECDH round-trip, scalar range guard, blind-bits validation
  • Add TTestECDHBinaryConstantTime (4 tests): default multiplier, wNAF parity, edge scalars, blinding transparency
  • Extend TAesBitSlicedTests: cross-key-size re-Init KAT, caller-key-preserved, invalid-length behavior

Xor-el added 7 commits July 22, 2026 11:46
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.
@Xor-el
Xor-el merged commit a828d44 into master Jul 22, 2026
24 checks passed
@Xor-el
Xor-el deleted the feature/constant-time-ec-scalarmul branch July 22, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant