Skip to content

fix(crypto): rebase and verify constant-time hardening backport onto current master - #11

Open
piotr-roslaniec wants to merge 7 commits into
devfrom
constant-time-hardening-backport
Open

piotr-roslaniec wants to merge 7 commits into
devfrom
constant-time-hardening-backport

Conversation

@piotr-roslaniec

Copy link
Copy Markdown

Summary

Rebases and verifies this fork's own unmerged constant-time-hardening branch
(commits 50ccd47, 4aee357) onto current master, closing the one real
security gap identified in a recent dependency-drift/variant-analysis audit of
this fork against bnb-chain/tss-lib: the optional constant-time
modular-exponentiation hardening from upstream PR bnb-chain#328 (3709c25, 7a10240,
0735081, CVE-2023-26557-class timing side-channel), which this fork's own
CHANGELOG.md explicitly lists as "Not ported / deferred" and a residual
risk ("the optional constant-time work is not integrated").

A literal cherry-pick of upstream's PR bnb-chain#328 commits is not viable — this fork
restructured crypto/facproof/crypto/modproof into
crypto/paillier/{factor_proof,mod_proof}.go and independently added
tagged-hash/session-context Fiat-Shamir changes to the exact functions
upstream's commits touch. This fork already had better ground for the
backport: its own unmerged constant-time-hardening branch, which
independently reimplements the same fix (same upstream-derived
common/constant_time.go design built on
filippo.io/bigmod, the same
constant-time bignum library used inside Go's own crypto/rsa/crypto/ecdsa)
directly against this fork's current code shape.

Relationship to #8: #8 opened directly from constant-time-hardening
against master at an earlier point and is now CONFLICTING (unrebased,
marked "DO NOT MERGE"). This PR is that same branch rebased onto current
master, with the resulting conflicts/fixups resolved and the whole suite
verified — a supersedes/replaces relationship. Recommend closing #8 in favor
of this one once reviewed.

What's included (7 commits)

  1. feat(crypto): add opt-in constant-time path for secret-exponent modexps
    rebase of the branch's 50ccd47. Adds common/constant_time.go
    (CTModInt.ExpCT/ModInverseCT/MulCT, EnableConstantTimeOps toggle)
    and wires it into Paillier Decrypt/Proof and the DLN/factor/modulus proofs.
  2. feat(crypto): extend constant-time coverage and harden CT primitives
    rebase of the branch's 4aee357, auto-merged cleanly onto master.
  3. chore(deps): reconcile indirect dependency graph via go mod tidy — the
    one real rebase conflict was in go.mod: the branch's base (pre-dating
    this fork's PR Remove unused EdDSA and resharing protocols #5) still had the EdDSA dependencies (agl/ed25519,
    decred/dcrd/dcrec/edwards/v2) that PR Remove unused EdDSA and resharing protocols #5 removed. Resolved by keeping
    master's dependency set and adding only the new filippo.io/bigmod
    requirement, then go mod tidy to reconcile the indirect graph.
  4. test(constant-time): adapt ported branch tests to current master API
    the branch's tests were written before three pieces of hardening that
    landed on master afterward (the 2048-bit DLN modulus floor, mandatory
    per-ceremony SetSessionNonce, mandatory fullBytesLen). Without these
    adaptations TestDLNProofCTVerifies and both TestE2EConcurrentConstantTime
    tests fail/panic even though the underlying constant-time crypto logic is
    unaffected.
  5. fix(safe_prime): correct errCh buffer sizing and p=2q+1 doc typo
  6. style(ecdsa/keygen): make round_3 multiErr accumulation error-handling style consistent
  7. cleanup(vss): remove redundant poly[0] = secret assignment in feldman_vss.Create

Commits 5-7 are small, independently-verified zero-risk backports of upstream
trivia (a stale doc comment, an over-sized channel buffer, a style
inconsistency, a dead-code assignment) found alongside the constant-time work
during the same audit; they have no security relevance and can be reviewed/
landed independently of 1-4 if preferred.

Verification

  • go build ./... - clean.
  • go vet ./... - no issues.
  • go mod tidy - resolves cleanly; no EdDSA-only dependencies reintroduced.
  • go test -p 2 -timeout 20m ./... - all 213 tests pass across 13
    packages
    , run twice: once on this exact tree before push, and once more
    against a fresh clone of the pushed branch as a final sanity check.

Design notes carried over from the source branch

  • Default-off (common.EnableConstantTimeOps() is opt-in), matching
    upstream's own default and this fork's stated "deferred to a separate
    follow-up with benchmarking and side-channel review" caveat. Turning it on
    by default is a separate decision this PR does not make.
  • No API breakage: the toggle is a new package-level function; no existing
    signatures change.

Non-goals / explicitly out of scope

Per the same audit, the other named security item in scope
(SRC-2026-644, EdDSA round-3 nil-pointer crash) does not apply to this fork -
eddsa/keygen, eddsa/signing, eddsa/resharing were deleted entirely in
PR #5, so there is no eddsa/signing/round_3.go and no attack surface. Not
addressed here because there is nothing to address.

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.
Follow-up to the go.mod merge conflict resolution when rebasing the
constant-time-hardening branch onto current master: go mod tidy moves
the indirect dependencies (previously listed inline from the branch's
older base) into their own require block with versions resolved
against master's current module graph. No functional change.
The constant-time-hardening branch's tests were written against an
older base, before this fork's own independent hardening added: (1)
the 2048-bit floor on dlnproof.Verify's modulus (verifyMinModulusBitLen),
(2) the mandatory per-ceremony SetSessionNonce on ECDSA keygen/signing
Start(), and (3) the mandatory positive fullBytesLen argument on ECDSA
signing constructors. Without these adaptations the ported tests
fail/panic against current master even though the underlying
constant-time crypto is unaffected:

- crypto/dlnproof/constant_time_equiv_test.go: use 1024-bit safe primes
  (NTilde ~2048 bits) instead of 512-bit, matching the
  verifyMinModulusBitLen=2048 floor and this fork's own convention
  (ecdsa/keygen/prepare.go safePrimeBitLen=1024).
- ecdsa/keygen/constant_time_e2e_test.go: call
  params.SetSessionNonce(big.NewInt(1)) before Start(), as required by
  ecdsa/keygen/round_1.go's fail-closed session-nonce check.
- ecdsa/signing/constant_time_e2e_test.go: same SetSessionNonce call,
  plus pass the now-mandatory fullBytesLen=32 argument to NewLocalParty
  (ecdsa/signing/local_party.go's validateFullBytesLen).
Two small zero-risk backports bundled together (same file, adjacent
sections):

- Size errCh to concurrency instead of concurrency*numPrimes (backport
  of upstream bnb-chain/tss-lib commit 4c83ace). At most 'concurrency'
  goroutines run at once, each sending at most one error, so the
  larger buffer was wasted allocation. No functional change.
- Fix p=2q+1 typo in GetRandomSafePrimesConcurrent doc comment
  (backport of upstream commit 27922e0). Doc-only; no functional
  change.
…_vss.Create

Backport of upstream bnb-chain/tss-lib commit b7b73a0. samplePolynomial
already sets v[0] = secret (crypto/vss/feldman_vss.go), so this
assignment was a no-op. Verified against fork's current
samplePolynomial before applying.
…g style consistent

Backport of upstream bnb-chain/tss-lib commit 0629cff. Behaviorally
identical (skip nil errs vs. include non-nil errs); pure style.
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