Skip to content

fix(crypto): extend constant-time coverage to Schnorr proofs and signing rounds 3-5 - #23

Merged
piotr-roslaniec merged 15 commits into
devfrom
ct-hardening-schnorr-signing-coverage
Sep 29, 2026
Merged

piotr-roslaniec merged 15 commits into
devfrom
ct-hardening-schnorr-signing-coverage

Conversation

@piotr-roslaniec

@piotr-roslaniec piotr-roslaniec commented Sep 18, 2026 •

Copy link
Copy Markdown

Context

This is an additive finding for the existing CT-hardening stack (PRs #8 / #10 / #11 / #17) — submitted for review/consideration, not asking for immediate merge. It stacks on #17 (the head branch), in the same stacked-draft style #10 and #16 already use.

Upstream bnb-chain/tss-lib commit 3709c25 (folded into the BNB #328 series) hardened the constant-time path in two more spots that this fork's stack never picked up:

  • crypto/schnorr/schnorr_proof.go — NewZKProofWithSession and NewZKVProofWithSession multiply the challenge c against the secret witnesses x, s, and l to build the proof responses t and u. Without CT, those math/big multiplications leak witness bits through timing.
  • ecdsa/signing/round_3.go / round_4.go / round_5.go — thelta = k·γ, sigma = k·w, thetaInverse = θ⁻¹ mod q, and si = m·k + rx·σ are all secret-key multiplications and inverses on the ECDSA signing hot path.

PR #17's own file list (the d08dc73 "extend constant-time coverage" commit) covers Paillier, DLN, MtA, factor/mod proofs, ring-Pedersen keygen, and the existing MulCT/ExpCT/ModInverseCT primitives — but does not touch either of the two files above. Same gap exists in #8, #10, and #11. This PR closes that gap using the exact same pattern already established in common/constant_time.go (common.NewCTModInt(...).MulCT / .ModInverseCT, gated on IsConstantTimeEnabled()).

Why no ExpCTWithBitLen adaptation needed (vs. PR #10)

The new code calls only .MulCT(...) and .ModInverseCT(...) — never .ExpCT(...):

  • MulCT takes no exponent at all (it's a constant-time modular multiplication, not an exponentiation), so PR fix(crypto): handle CT exponent bounds, zero values, and toggle changes #10's bounded-exponent-width hardening does not apply.
  • ModInverseCT's internal exponent is the fixed, modulus-derived constant mod-2 (or phiN-1 via NewCTModIntWithPhi) — also a public, fixed-width value, not a caller-supplied variable-width secret. Confirmed by grep -n '\.ExpCT(' crypto/schnorr/schnorr_proof.go ecdsa/signing/round_3.go ecdsa/signing/round_4.go ecdsa/signing/round_5.go returning zero hits on this branch.

So this addition slots in directly under the existing IsConstantTimeEnabled() toggle without re-touching the exponent-bound logic PR #10 added.

Changes

  • crypto/schnorr/schnorr_proof.go — wrap the c·x and c·s / c·l multiplications in NewZKProofWithSession / NewZKVProofWithSession with the CT branch.
  • ecdsa/signing/round_3.go — wrap k·γ and k·w (thelta/sigma computation) with the CT branch.
  • ecdsa/signing/round_4.go — wrap θ⁻¹ mod q with ModInverseCT.
  • ecdsa/signing/round_5.go — wrap m·k and rx·σ (the si addend pair) with the CT branch.
  • crypto/schnorr/constant_time_equiv_test.go — new equivalence test covering both NewZKProof and NewZKVProof: builds a non-CT baseline proof and a CT proof from the same witnesses and asserts both verify.
  • common/constant_time.go — COVERAGE doc comment updated to list the Schnorr proof responses and ECDSA signing rounds 3-5 among the sites now covered. Default value (constantTimeEnabled = 1, set by PR Enable bounded bigmod operations by default #17) and PR Enable bounded bigmod operations by default #17's "limited coverage" warning are preserved verbatim.
  • CHANGELOG.md — added a Breaking Change feat(crypto): opt-in constant-time path for secret-exponent modexps [DO NOT MERGE] #8 entry under the existing [Unreleased] heading describing the now-on-by-default CT framework plus this PR's Schnorr/signing-rounds extension, an Added entry for the CT API symbols, and removed two now-stale deferred-CT lines (the "optional constant-time framework ... deferred to a separate follow-up" bullet and "the optional constant-time work is not integrated" line). (An earlier revision of this branch introduced a dated [1.4.0] section header and removed the "has not yet published its own tagged release" line; both were reverted in commit 17e4771 after review feedback that this fork has no tags/releases yet — this description previously went stale relative to that revert.)

Verification on this branch (pushed)

$ gofmt -l .
(no output)

$ go build ./...
(no output)

$ go vet ./...
(no output)

$ go test ./...
ok    github.com/bnb-chain/tss-lib/common              28.469s
ok    github.com/bnb-chain/tss-lib/crypto              (cached)
ok    github.com/bnb-chain/tss-lib/crypto/ckd          (cached)
ok    github.com/bnb-chain/tss-lib/crypto/commitments  (cached)
ok    github.com/bnb-chain/tss-lib/crypto/dlnproof     23.557s
ok    github.com/bnb-chain/tss-lib/crypto/mta          286.977s
ok    github.com/bnb-chain/tss-lib/crypto/paillier     51.010s
ok    github.com/bnb-chain/tss-lib/crypto/schnorr      0.052s
ok    github.com/bnb-chain/tss-lib/crypto/vss          (cached)
ok    github.com/bnb-chain/tss-lib/ecdsa/keygen        340.405s
ok    github.com/bnb-chain/tss-lib/ecdsa/signing       186.710s
?     github.com/bnb-chain/tss-lib/test                [no test files]
ok    github.com/bnb-chain/tss-lib/tss                 (cached)

The new TestSchnorrProofCTVerifies / TestSchnorrVProofCTVerifies cases pass.

Benchmark (from the local pre-push run on this stack)

Constant-time modexp measured at parity with the standard math/big path on the same hardware:

go test ./common/... -bench BenchmarkExpCT -benchtime=2s
~2.73ms per op (CT,  n≈900)   vs   ~2.83ms per op (std,  n≈800)

The CPU-cost concern that originally motivated upstream's deferral did not materialize for this primitive.

Out of scope / NOT fixed here — for maintainer awareness only

Upstream's same 3709c25 commit also hardened crypto/mta/share_protocol.go's AliceEnd / AliceEndWC Paillier-decrypt paths — but with a different mechanism entirely: a sleep-based response-time normalizer (NewTimingProtection, ~200 ms target + jitter), not the bigmod constant-time path used everywhere else in common/constant_time.go. That primitive does not exist anywhere in this fork's lineage (master, both CT branches, or PRs #12–#17), and porting it would inject a fixed ~200 ms delay into every MtA share round — a real latency cost that needs its own sign-off rather than a mechanical extension of the existing CT pattern.

That gap is documented as a known, deliberately-deferred item in this PR's COVERAGE comment update to common/constant_time.go, not implemented in code. If/when a follow-up decides to port NewTimingProtection, it should land as its own PR with its own benchmark and a separate latency sign-off; the bigmod CT path doesn't subsume it.

…t v1.4.0 notes

NOT ready to merge/push. Exploratory work pending reconciliation with the
live #8/#10/#11/#17 stack (mswilkison) which already rebases+hardens the CT
backport more rigorously than this commit (bounded exponent widths, overflow
rejection, mode snapshotting) and already flips constantTimeEnabled's default
directly. This commit's own contribution -- CT coverage for
crypto/schnorr/schnorr_proof.go and ecdsa/signing/round_3-5.go, which none of
#8/#10/#11/#17 touch -- is real and not yet duplicated elsewhere, but should
land as an addition on top of that stack, not a competing branch.

Kept locally, unpushed, for reference pending user/maintainer coordination.
…tion gap

- Remove common/constant_time_init.go: PR #17 (mswilkison, stacked on #11)
  already flips constantTimeEnabled's default to 1 directly in
  common/constant_time.go, making a separate init() redundant. It was also
  actively wrong: dlnproof/constant_time_equiv_test.go (and this commit's own
  new schnorr test) generate their 'non-CT baseline' proof before calling
  EnableConstantTimeOps(), assuming an ambient disabled default -- an
  unconditional init() would silently make that baseline vacuous instead of
  failing loudly.
- Update the COVERAGE doc comment in common/constant_time.go to list the
  Schnorr proof responses and ECDSA signing rounds 3-5 now covered, and to
  record crypto/mta.AliceEnd/AliceEndWC's Paillier-decrypt timing protection
  (upstream BNB 3709c25) as a known, deliberately-NOT-ported gap: upstream
  uses a sleep-based response-time normalizer (NewTimingProtection, ~200ms
  target + jitter), a different mechanism entirely from the bigmod
  constant-time path used everywhere else in this file. That primitive
  exists nowhere in this fork's lineage (checked master, both CT branches,
  and PRs #12-17). Porting it would inject a fixed ~200ms delay into every
  MtA share round -- a real latency cost that needs its own sign-off, not a
  mechanical extension of the existing pattern.
- Fix CHANGELOG.md's now-stale 'has not yet published its own tagged
  release' line (contradicted by the new [1.4.0] section added earlier).
…_init.go

Two spots still described CT-enablement as happening via a package init()
in common/constant_time_init.go. That file was removed before this branch
was ever pushed (superseded by PR #17's direct default-value change,
constantTimeEnabled = 1 in common/constant_time.go) -- these two CHANGELOG
lines were never updated to match and got carried forward by the cherry-pick
onto this branch. Correct the mechanism description in both places.

@mswilkison mswilkison left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two P2 findings from the review.

Validation reported by the review: build and vet passed, and the Schnorr tests compiled; test binaries were not executed.

Comment on lines +37 to +38
common.EnableConstantTimeOps()
defer common.DisableConstantTimeOps()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Explicitly set and restore CT mode in both equivalence tests

CT is enabled by default, so proofOff actually exercises the CT branch when either test runs alone. The deferred disable then leaves subsequent Schnorr tests—including the existing P-256 and session-binding cases—running with CT disabled. This makes coverage depend on test order and removes default-path coverage. Save the previous mode, explicitly disable CT before constructing the baseline, and restore the saved mode during cleanup in both tests.

Comment thread CHANGELOG.md Outdated

---

## [1.4.0] - 2026-09-14 — BNB hardening integration

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep the untagged hardening stack under Unreleased

This repository currently has no tags or published GitHub releases, including v1.4.0. Moving the hardening stack into this dated release section therefore advertises security changes as released when consumers cannot obtain that version, and both new comparison links reference a nonexistent tag. Keep these entries under [Unreleased] until an actual release contains them.

Address review findings on PR #23:

- The equivalence tests built their non-CT baseline without disabling
  constant-time mode. Since this branch defaults constantTimeEnabled to 1,
  the baseline actually exercised the CT path, and the bare deferred
  DisableConstantTimeOps leaked disabled state into subsequent Schnorr
  tests, making coverage order-dependent and dropping default-path
  coverage. Save, set, and restore the ambient mode via a withCTMode
  helper, and assert the baseline really is running with CT off.

- Restore the changelog hardening entries to [Unreleased]. This fork has
  no tags or published releases, so a dated [1.4.0] heading advertised
  the security work as obtainable, and both comparison links pointed at
  a tag that does not exist.
@piotr-roslaniec

Copy link
Copy Markdown
Author

Both P2 findings addressed in 17e4771.

Test CT mode. Confirmed the finding is exactly right: this branch sets constantTimeEnabled = 1 (common/constant_time.go:54), so proofOff was exercising the CT branch and the bare defer DisableConstantTimeOps() was leaving CT off for every later test in the package. Added a withCTMode(t, bool) helper that saves the ambient mode, sets the requested one, and restores the saved mode via t.Cleanup; the baseline now explicitly disables CT and asserts IsConstantTimeEnabled() == false so it cannot silently go vacuous again. Verified each CT test passes run alone (the original order-dependence case) and that the full package passes under -count=2 with the pre-existing P-256 and session-binding tests running after them.

Changelog. Also correct, and the [1.4.0] heading plus both comparison links were introduced by this PR, not inherited. Reverted to [Unreleased]; no 1.4.0 reference remains in the file. The changelog diff against this PR's base is now purely the new content entry, with no heading or link churn.

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)
Addresses review findings on PR #23's constant-time hardening in
ecdsa/signing:

- round_5.go: correct the SECURITY comment's operand list -- m is the
  public message hash, not secret; name rx = R.X() instead, the
  operand that actually needed flagging (F9)
- constant_time_equiv_test.go (new): add targeted equivalence tests
  for round_3's thelta/sigma, round_4's thetaInverse, and round_5's si
  terms -- the five new MulCT/ModInverseCT call sites this PR's stack
  added had only e2e coverage before this (F13)
- constant_time_e2e_test.go: expand TestE2EConcurrentConstantTime's
  doc comment to name the Schnorr and round 3/4/5 CT paths it now
  also exercises, not just the pre-existing Paillier/MtA path (F14)
Addresses review findings on PR #23's constant-time hardening in
crypto/schnorr:

- constant_time_equiv_test.go: pin rand.Reader to a deterministic
  source (reset immediately before each proof generation, matching
  the crypto/paillier and crypto/mta sibling convention) and assert
  Alpha/T/U are bit-identical between the CT and non-CT paths, not
  just that both proofs verify -- catches a CT branch that silently
  diverges from, or falls back to, the non-CT computation (F12)
- constant_time_equiv_test.go: fix two doc-comment nits -- 'enabled by
  default in this package' should read 'in this library' (the default
  is process-wide, set in common/constant_time.go); the primitive
  equivalence note omitted ModInverseCT, which round-4's change relies
  on (F11)
Addresses the project go-rand-v2 rule on new code. crypto/schnorr/
constant_time_equiv_test.go used math/rand.NewSource for its deterministic
rand.Reader override; the sibling paillier/mta/dlnproof files pre-date
the rule and stay on legacy math/rand, but new code on this fork uses
math/rand/v2 (see PR #4243 for the established v2+PCG+fillRandom pattern).

math/rand/v2 dropped the Read method on *Rand, so the v2 *Rand does not
implement io.Reader and cannot substitute for crypto/rand.Reader directly.
This commit adds a small seededReader type that adapts a v2 PCG to
io.Reader (8-byte Little-Endian chunks, matching the byte granularity of
the legacy generator the test was written against) and uses it via
newSeededReader(1, 1). The two proof generations in each test reseed
the same (1, 1) tuple so both CT and non-CT runs draw identical bytes
from offset zero, preserving the bit-exact equivalence asserts.

Behaviour, determinism, and the test's catching-power property
(bit-exact Alpha/T/U identity between CT and non-CT under matched
entropy) are unchanged. CHANGELOG notes the refactor under Breaking
Change #8.
…curacies

The reduceToPaddedBytes COVERAGE note claimed rx 'becomes the public
signature r component within the same protocol round'; rx is actually
stored in round 5 and only published in round 10 (finalize.go:60), so
the variable-time big.Int.Mod(rx, N) runs while rx is still secret.
Document the bounded leak and the constant-time-reduction follow-up.

The COVERAGE comment's 'deliberately not added' phrasing implied this
change deferred the mta AliceEnd gap, which predates it; reword to make
the provenance explicit.

The round_5 SECURITY comment now states rx is the raw x-coordinate,
published as the signature r component in round 10 with the recovery-ID
convention.
…chnorr sites

NewCTModInt allocates a bigmod.NewModulus + sync.Pool + inverseExp per
call; the curve order N is constant for the lifetime of a signing
session, so the 5 CT sites (round_3/4/5, schnorr x2) were re-doing that
setup on every Start()/proof. Add common.GetCTModInt(mod) returning a
sync.Map-memoized shared *CTModInt and route the hot-path sites through
it. NewCTModInt is retained for one-shot/test use.
BenchmarkMulCT/BenchmarkModInverseCT had no BenchmarkMulStandard/
BenchmarkModInverseStandard counterpart at the 256-bit size this fork's
signing rounds actually use, so the changelog's 'no perf regression'
claim was unmeasurable. Add the two paired baselines (rand.Prime(256))
and a PERFORMANCE note on ModInverseCT: the CT inverse is a full 256-bit
Fermat modexp vs the standard path's extended-Euclidean inverse
(~15-20x slower, the explicit price of the CT guarantee).
…ng gate

- M4: TestRound4ThetaInverseCTEquivalence now also covers theta=0, the
  actually-reachable non-coprime value (round_4 accumulates theta mod N).
- M6: TestRound5SiCTEquivalence now covers rx >= N (field coordinate R.X()
  can exceed the curve order N); pins CT/non-CT equivalence for an
  out-of-range rx.
- M7: the schnorr 'byte-identical under matched entropy' comments now state
  precisely what is matched: the secret is fixed and only the commitment
  randomness is seeded.
- M8: withCTMode doc notes it mirrors setPaillierCTTestMode/setMTAProofTestMode
  and why a shared cross-package helper is avoided.
- M12: add TestMulCTTimingConsistency + TestModInverseCTTimingConsistency
  (log-only, -short-safe) so a future variable-time regression in the CT
  branch is observable.
- M5: new TestRounds3to5CTWiring - a -short-safe integration gate that drives
  the real round 3/4/5 Start() methods with CT enabled and asserts the
  CT-produced temp fields (theta/sigma/thetaInverse/si/rx) plus that
  thetaInverse is reduced mod N. The primitive tests and the heavy
  TestE2EConcurrentConstantTime did not directly assert the if/else CT wiring.
…256-bit perf

- M13: add a per-site table (file/func/CT-op/secret-operand) for the 7 CT
  branches extended by this PR, for audit traceability against BNB bnb-chain#328.
- M11: add a prominent 'Known residual gap' bullet at the top of entry 8 -
  the mta AliceEnd Paillier-decrypt path remains variable-time and enabling
  CT by default does not close it.
- M3: the 'Break type' section now quantifies the measured 256-bit costs
  (MulCT ~2x, ModInverseCT ~15-20x vs math/big) instead of the unsupported
  'far cheaper' claim.
Keep the #19 and #17 changelog entries and correct the round-5 rx secrecy
wording identified during review.
@piotr-roslaniec
piotr-roslaniec changed the base branch from codex/ct-defaults-and-bounds to dev September 29, 2026 14:15
@piotr-roslaniec
piotr-roslaniec merged commit 0efe90c into dev Sep 29, 2026
2 checks passed
@piotr-roslaniec

Copy link
Copy Markdown
Author

Merged into dev. #17 (including current dev) was merged into this branch first; the only conflict was CHANGELOG. Both #19 and #17 entries were retained, and the round-5 table now correctly says rx remains secret until round 10.

Verification:

  • CI Test and Go fmt green on integrated head.
  • Build, vet, tidy, format, and shuffled Schnorr tests green.
  • CT-on and CT-off 11-party signing ceremonies both produce valid signatures.
  • Round 3/4/5 equivalence tests pass, including inverse edge cases.
  • Schnorr fixed-vs-random timing ratio was 0.985x; math/big control was 0.827x. Host noise prevents a formal dudect verdict, but the primitive check is consistent with constant-time behavior.
  • Prior P2 findings fixed: equivalence tests explicitly set/restore CT mode and no longer depend on test order; all hardening remains under [Unreleased].

Residual limits are recorded, not hidden: surrounding big.Int add/reduce remains variable-time; round 5's rx reduction is value-dependent; secret-scalar EC point multiplication remains non-CT; the AliceEnd sleep normalizer was intentionally not ported because Decrypt itself is now CT.

Full report: agent-docs/pr-integration/23.md (local).

piotr-roslaniec added a commit that referenced this pull request Sep 29, 2026
Merge advanced origin/dev (includes #17/#23 CT hardening) into PR #14.

Conflict choices:
- No textual conflicts; clean automatic merge.
- Go toolchain/deps and CT changes kept from origin/dev (#17 defaults/bounds,
  #23 CT schnorr-signing coverage).
- Preserved PR #14 changes on top of dev: BaseParty fatal-error latch
  (abort/abortedWith) and keygen unmarshalVSSCommitment part-count guard
  in ecdsa/keygen/round_3.go.
piotr-roslaniec added a commit that referenced this pull request Sep 29, 2026
Absorb PR #17 (ct-defaults-and-bounds) and #23 (ct-hardening-schnorr-signing-coverage). Preserve this branch's safe-prime cancellation (b547dd9) and save-data ownership changes (0f88232); dev wins on Go toolchain and dependency versions.
piotr-roslaniec added a commit that referenced this pull request Sep 29, 2026
Second integration pass: origin/dev advanced to 0efe90c (merged
#17 ct-defaults-and-bounds and #23 ct-hardening-schnorr-signing-coverage
after the first merge at 39e7d87).

Auto-merged cleanly; key choices:
- crypto/mta/proofs.go: disjoint regions survived both sides —
  #12 decoder guards (ProofBobWC 12-part arity, Bytes nil-receiver
  panics) plus #17/#23 CT changes on the same file.
- ecdsa/signing/round_5.go: #12 R == nil guard retained below the
  #23 CT-context hunks; no conflict.
- go.mod/go.sum: origin/dev dependency set retained.
piotr-roslaniec added a commit that referenced this pull request Sep 29, 2026
Absorb #17/#23 constant-time defaults/bounds and signing-coverage changes from
advanced origin/dev. Preserve this branch's nil/sampling guards in
crypto/paillier/paillier.go and crypto/mta/proofs.go/range_proof.go; dev wins
on Go toolchain and dependency versions.
piotr-roslaniec added a commit that referenced this pull request Sep 29, 2026
Absorb PR #12 (codex/input-guards) on top of #17/#23. Preserve this
branch's safe-prime cancellation and save-data ownership changes; dev
wins on Go toolchain and dependency versions.
piotr-roslaniec added a commit that referenced this pull request Sep 29, 2026
Absorb #12 input guards on top of #17/#23. Preserve this branch's
nil/sampling guards in crypto/paillier/paillier.go and crypto/mta;
dev wins on Go toolchain and dependency versions.
piotr-roslaniec added a commit that referenced this pull request Sep 29, 2026
Bring PR #9 current with dev after #17/#23/#12/#13/#15/#14/#26 landed.

Conflicts resolved:
- CHANGELOG.md: retain dev's PR #17/#23 composing-PR entry alongside PR #9;
  keep the rollout-only historical Bob compatibility (8ae2cf8) risk text.
- crypto/mta/proofs.go: preserve #9 legacy vs security-v2 tau/gamma sampling
  branches; keep dev's #15 nil sampling guard on beta. P1 8ae2cf8 opt-in
  historical Bob compatibility (default tight maxT1, opt-in (q+1)*N bound)
  unchanged.
- crypto/schnorr/schnorr_proof.go: combine #9 legacy/v2 challenge API split
  with dev's #23 constant-time MulCT branch for t = a + c*x.
- ecdsa/keygen/round_3.go: keep #14 unmarshalVSSCommitment part-count guard
  and re-apply #9 mode-conditional round.proofContext(j) for FactorVerify.
- ecdsa/signing/round_ct_wiring_test.go: select ProtocolModeSecurityV2 +
  nonce for the CT wiring test that constructs local signing parties.

Auto-merged and preserved: dev toolchain (Go 1.25.7 / 1.26.8) and deps in
go.mod/go.sum, #17 CT default-on behavior and fixed public-width ExpCTWithBitLen
calls, #12 arity/input guards, #15 nil/bounds guards, #14 keygen changes, and
dev workflows/tests. No transcript mode or CT path was dropped.
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.

2 participants