feat(crypto): opt-in constant-time path for secret-exponent modexps [DO NOT MERGE] - #8
piotr-roslaniec wants to merge 2 commits into
Conversation
Port the upstream constant-time framework (common/constant_time.go, built on filippo.io/bigmod) and wire it, behind a default-off runtime toggle, into the secret-exponent modular exponentiations in Paillier Decrypt/Proof and the DLN, factor, and Paillier-Blum modulus proofs. Mitigates the timing side-channel gap (CVE-2023-26557 class) flagged in the threshold-ECDSA variant analysis. - common: EnableConstantTimeOps / DisableConstantTimeOps / IsConstantTimeEnabled toggle + CTModInt (ExpCT / ModInverseCT / MulCT) over filippo.io/bigmod. - Wired ops use odd moduli only; phi(N)-even inverses stay on math/big. - Default off: zero behavior/perf change unless EnableConstantTimeOps() is called. - Bump go directive 1.16 -> 1.23 (filippo.io/bigmod floor). - Tests: framework equivalence, per-proof byte-identical/verifies equivalence, and full constant-time keygen + signing end-to-end.
Close the secret-exponent coverage gap and fix correctness/robustness issues in the opt-in constant-time path: - Harden the remaining secret-exponent modexps: MtA ProveBobWC (h1^x, h1^y), ProveRangeAlice (h1^m), the ring-Pedersen trapdoor setup in keygen (h1i^alpha), and Paillier Encrypt (gamma^m) / HomoMult (c1^m). - Pad the exponent to a fixed width in ExpCT so its running time no longer leaks the secret exponent's magnitude. - ModInverseCT returns nil for non-coprime inputs (matching math/big.ModInverse) instead of a silent wrong value. - reduceToPaddedBytes reduces unconditionally (no secret-dependent branch). - Assert odd modulus at CTModInt construction (fail at build, not at Exp). - Remove the unused TimingProtection / ConstantTimeCompare / Mod() API (the jitter helper also discarded a rand error -> nil-deref panic path). - Document the hardening coverage scope in the package doc. - Add equivalence/regression tests for every new site.
|
Update on blocking consideration 1: the Go >=1.23 compiler requirement is already satisfied on keep-core's keep-core PR #4312 goes further: it raises the module minimum to Go 1.25.7 and aligns the toolchain, CI, and Docker builders on Go 1.26.8. That PR is currently open and unmerged, but the minimum-version requirement here does not depend on it landing. The remaining consumer work is integrating the updated tss-lib revision, explicitly enabling constant-time operations where intended, and validating that integration. The keep-core PR retains the historical tss-lib pin, so its passing CI does not exercise this PR's constant-time implementation. The loop-variable semantics change from tss-lib's own The minimum keep-core compiler-version requirement in this PR's blocker list can be marked satisfied for |
Addresses review findings on PR #23's constant-time hardening entry: - CHANGELOG.md: tag Breaking Change #8 and the Added CT-symbols entry with PR #17/#23, extend the Composing PRs list (F2) - CHANGELOG.md: soften 'closing the timing side-channel' framing to scope it to the operations covered, cross-reference the mta AliceEnd/AliceEndWC gap instead of implying full closure (F6) - CHANGELOG.md: note the coverage broadening from secret-exponent-only to secret-operand operations (F7) - CHANGELOG.md: restore a 'Not ported / deferred' bullet for the mta gap so the dangling '(see below)' cross-reference resolves again (F8) - CHANGELOG.md: fix the benchmark command to actually run both BenchmarkExpCT and BenchmarkExpStandard, correct the mismatched sample-count claim (F10) - common/constant_time.go: note in the reduceToPaddedBytes NOTE that round_5's rx (a field-prime coordinate) is the one operand reduced into group-order space, and why that's still safe (F3) - common/constant_time_test.go: add BenchmarkMulCT/BenchmarkModInverseCT on a 256-bit-class modulus so the CHANGELOG's performance claim can cite the operations this PR's stack actually added, not just the 2048-bit ExpCT benchmark (F5)
Summary
Closes the timing side-channel / constant-time gap (CVE-2023-26557 class) identified in the
threshold-ECDSA variant analysis. Ports the upstream BNB
tss-libconstant-time framework(
common/constant_time.go, built onfilippo.io/bigmod—the same constant-time bignum library used inside Go's
crypto/rsa/crypto/ecdsa) and wires it,behind a default-off runtime toggle, into the secret-exponent modular exponentiations.
math/bigis variable-time and leaks information about secret exponents through execution time;this provides a constant-time alternative for the operations that touch long-lived secrets.
What is wired (all behind
common.IsConstantTimeEnabled())crypto/paillier/paillier.goDecryptc^λ,Γ^λ mod N²;ModInverse(Lg, N)ExpCT;NewCTModIntWithPhi.ModInverseCTLambdaN,PhiNcrypto/paillier/paillier.goProofxs[i]^M mod NExpCTM = N⁻¹ mod φcrypto/dlnproof/proof.goNewDLNProofh1^a mod N;c·x mod pqExpCT;MulCTxcrypto/paillier/factor_proof.goFactorProofs^p,s^q mod NTildeExpCTp,qcrypto/paillier/mod_proof.goModProof+ QR helpersy^invN,x^e,x^((p-1)/2)ExpCTφ,p,qEvery modulus fed to bigmod is odd; the two φ(N)-even modular inverses (
M,invN) stay onmath/bigexactly as upstream documents. Eachelsebranch preserves the original code verbatim.Default-off / opt-in
constantTimeEnableddefaults to0. With it off (the default), behavior and performance areidentical to before. A consumer enables it once at startup:
The gap only closes once the consumer opts in. keep-core integration note: call
EnableConstantTimeOps()at startup, and note the Go-version requirement below.Verification
go build ./...,go vet(changed pkgs), fullcommon/crypto/tsssuites,ecdsa/keygen,ecdsa/signingall pass.the deterministic ops (
Decrypt,Proof, QR helpers), verifies for the randomized proofs;full CT-on keygen E2E (all parties agree on the public key) and CT-on signing E2E
(valid ECDSA signature). Every CT test asserts
IsConstantTimeEnabled()so it cannot passvacuously, and the QR helper test covers an explicit non-residue.
Blocking considerations (why "do not merge" yet)
filippo.io/bigmod). keep-core pins this forkand would need to build with Go ≥1.23. The bump also crosses Go 1.22 loop-variable
semantics — broader than just the bigmod floor. This coordination must be agreed before merge.
filippo.io/bigmod v0.1.0.isQuadResidueModPrimeCT-wrapping goes beyond upstream (no analog) and is partial —only the exponentiation is constant-time;
NewCTModInt(p)'s reduction of the secret prime isnot. Consistent with upstream's own
MulCT mod pMulQ, but a future upstream re-sync shows adelta here. Conscious choice, flagged for reviewer agreement.
Verifyis always non-CT over public data) but is not directly tested.Out of scope (separate follow-up)
The secp256k1 EC scalar-mult side channel (2019
btcecNAF →btcec/v2) — large dependencymigration, rated low-exploitability in the variant analysis, and not part of "the constant_time.go
issue."