Skip to content

feat(crypto): opt-in constant-time path for secret-exponent modexps [DO NOT MERGE]#8

Draft
piotr-roslaniec wants to merge 2 commits into
masterfrom
constant-time-hardening
Draft

feat(crypto): opt-in constant-time path for secret-exponent modexps [DO NOT MERGE]#8
piotr-roslaniec wants to merge 2 commits into
masterfrom
constant-time-hardening

Conversation

@piotr-roslaniec

Copy link
Copy Markdown

⛔ DO NOT MERGE

This PR is not ready to merge. It is opened for review and discussion only.
Merging carries an external coordination cost (a Go toolchain bump that ripples
into keep-core — see "Blocking considerations" below) that must be agreed first.

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-lib constant-time framework
(common/constant_time.go, built on filippo.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/big is 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())

File Operation CT primitive Secret
crypto/paillier/paillier.go Decrypt c^λ, Γ^λ mod N²; ModInverse(Lg, N) ExpCT; NewCTModIntWithPhi.ModInverseCT LambdaN, PhiN
crypto/paillier/paillier.go Proof xs[i]^M mod N ExpCT M = N⁻¹ mod φ
crypto/dlnproof/proof.go NewDLNProof h1^a mod N; c·x mod pq ExpCT; MulCT DLN witness x
crypto/paillier/factor_proof.go FactorProof s^p, s^q mod NTilde ExpCT primes p, q
crypto/paillier/mod_proof.go ModProof + QR helpers y^invN, x^e, x^((p-1)/2) ExpCT φ, p, q

Every modulus fed to bigmod is odd; the two φ(N)-even modular inverses (M, invN) stay on
math/big exactly as upstream documents. Each else branch preserves the original code verbatim.

Default-off / opt-in

constantTimeEnabled defaults to 0. With it off (the default), behavior and performance are
identical to before. A consumer enables it once at startup:

common.EnableConstantTimeOps()

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

  • Default (CT-off), no regression: go build ./..., go vet (changed pkgs), full
    common/crypto/tss suites, ecdsa/keygen, ecdsa/signing all pass.
  • CT-on, functionally equivalent: framework suite; per-proof equivalence — byte-identical for
    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 pass
    vacuously, and the QR helper test covers an explicit non-residue.

Blocking considerations (why "do not merge" yet)

  1. Go toolchain bump 1.16 → 1.23 (required by filippo.io/bigmod). keep-core pins this fork
    and 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.
  2. New dependency: filippo.io/bigmod v0.1.0.
  3. Scope honesty:
    • isQuadResidueModPrime CT-wrapping goes beyond upstream (no analog) and is partial
      only the exponentiation is constant-time; NewCTModInt(p)'s reduction of the secret prime is
      not. Consistent with upstream's own MulCT mod pMulQ, but a future upstream re-sync shows a
      delta here. Conscious choice, flagged for reviewer agreement.
    • Mixed CT / non-CT party interop is sound by construction (CT outputs are byte-identical;
      Verify is 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 btcec NAF → btcec/v2) — large dependency
migration, rated low-exploitability in the variant analysis, and not part of "the constant_time.go
issue."

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.
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