Integrate dev hardening set into master - #37
Draft
piotr-roslaniec wants to merge 152 commits into
Draft
piotr-roslaniec wants to merge 152 commits into
piotr-roslaniec wants to merge 152 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.
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.
Adapt the generator check from bnb-chain/tss-lib commit d3c1af5, pinning v1.30.0 to match the four generated files in this fork.
Adapt the result handoff in common/safe_prime.go from public upstream commit 24bb7d3. Add bounded worker lifecycle tests for cancellation with no receiver and a full result buffer, plus ordinary result delivery. Test cleanup drains pending results and joins workers.
Adapt Xi and ShareID copying from public upstream commit 04bb840 and the roster-content guard from 1693884, limited to ecdsa/keygen/save_data.go. Preserve nil local secrets and existing pre-parameter and per-party pointer sharing. The nil saved-key diagnostic and focused ownership, subset-selection, and input-guard unit tests are additions for this branch.
Adapt the common prime-size and unit-domain guards from public upstream dc9b957. Adapt the Paillier sampling changes from public upstream b64213a to this base. Use an 18-bit minimum for the local safe-prime candidate range and separation condition. Propagate empty sampler results through direct callers while preserving function signatures and the ordinary 2048-bit challenge output. These are source-level adaptations, not cherry-picks. This base has no quadratic-non-residue sampling helper, and its ModProof is outside the scope of this change. Add bounded domain checks, modulus-width controls, and a fixed 2048-bit challenge digest captured from base 86bd1a3.
Validate helper inputs before indexed reads and coordinate-buffer allocation, require a commitment payload, and propagate nil signing point results through the existing error paths. Preserve base proof arities, optional-mode proof serialization, nil-key parameter skips, and valid routing and point encodings. Adapted from upstream commits: f1588ae (WC decoder arity) 8deab46 (sender metadata guards) 1a8e277 (embedded PartyID guards) 8069c6c (SortedPartyIDs.Keys guard) 6e0bcd4 (shared nullable-input guards) 58a131c (commitment minimum parts) cbb8fe4 (commitment boundary coverage) e65fb36 (Gob coordinate allocation bounds) 8acae9a (signing nil-result guards) 1693884 (PartyID.String content guard)
Include the message integer and fixed byte width in security-v2 signing SSIDs. Preserve legacy transcript selection and the 32-byte SSID encoding. Snapshot the caller's message when constructing a signing party so its context remains stable. Add context separation, matching-peer, fixed-width, message ownership, and legacy tests. SSID binding adapted from the ECDSA signing changes in upstream commits 71dad22 and b73eea7.
Validate ECDSA keygen decommitment part counts before hashing and point decoding. Adapted from public upstream commit 1693884.
Port the zero-exponent path, public exponent bounds, and toggle snapshot corrections from public PR #10 commits 3169e56 and cf15377. Adapt the default-on behavior from upstream PR bnb-chain#332 commit 44a95b2 on top of public PR #11 at 68686af. Keep the existing exported method and toggle signatures, add an explicit public exponent bound, and snapshot the mode across each multi-step operation. Use LambdaN for decryption's inverse so keys without PhiN remain supported. Restore prior toggle state in tests and select disabled baselines explicitly. Document the limited coverage of the bigmod path.
Raise the minimum Go version to 1.17 as required by the patched runtime. Preserve generated schemas and add binary encoding controls captured on the previous runtime.
Select x/crypto v0.52.0 and its required x/sys v0.45.0. Align the minimum Go version to 1.25.7 and development and CI toolchain to 1.26.8 while preserving runtime source and existing compatibility vectors.
…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.
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.
Both workflows filtered pull_request events to base branch master. This fork stacks pull requests on each other's branches, so every stacked PR and every PR targeting dev ran with no checks at all: #17 and #23 currently report no checks, and the whole in-flight batch targeting dev is covered only by locally-run tests. Drop the pull_request branch filter so any PR gets CI regardless of base, and add dev to the push trigger so the integration branch is covered on merge.
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)
fix(proofs): bound unknown-order verification
docs(changelog): record integration follow-ups
fix(signing): publish round state before messages
Add ExpCTWithBytes/ExpCTCanonicalWithBytes/ExpCTCanonicalWithBitLen/ MulCTCanonical to CTModInt so a caller that has already proven an operand is canonical (0 <= operand < modulus) can skip the generic big.Int.Mod reduction, and a proof that reuses one exponent across many iterations can encode it once instead of re-encoding per call. ModProof pre-encodes its invariant secret exponents (psP, psQ, rootExp, invN) once per proof and reuses them across all 80 iterations, wiping the owned encodings on completion. Existing reducing APIs, proof equations, and transcript bytes are unchanged.
ProveBobWC and ProveRangeAlice now reject a negative or over-width witness with an error, before any constant-time exponentiation, instead of panicking inside the fixed-width exponent encoder. The guard sits ahead of randomness sampling so an unusable key (N<=1) is rejected the same way in both modes. VerifyLegacy's compatibility-enabled bound is now derived inside the shared verifier core, after its existing nil-input guard, so a nil curve, key, or modulus returns false instead of panicking. Move the shared Bob/BobWC verifier core into proof_bob_verifier.go (proofs.go was 656 lines) with equations unchanged. Replace the wording-pinned randomness-domain assertion in sampling_test.go with a behavior-based rejection check.
Encrypt, HomoMult, and Decrypt now validate the modulus (and, for Decrypt, LambdaN) before constructing a constant-time context, in both timing modes, instead of panicking on a malformed caller-constructed key such as an even N. Add an owner-scoped reuse cache keyed by weak.Pointer to the PublicKey/ PrivateKey, so the N^2 constant-time context, the fixed-width LambdaN encoding, and the ciphertext-independent decryption coefficient are built once per unchanged key value and reused across calls. Entries are removed via runtime.AddCleanup when the owning key becomes unreachable, and value snapshots detect sequential mutation of the exported N/ LambdaN fields and rebuild. Exported struct layouts, JSON/Gob encoding, and by-value copy semantics are unchanged. Drop the wording-pinned empty-randomness-domain test in sampling_test.go now that an N=1 modulus is rejected earlier by the new key validation.
Replace the single ReachedRound8 flag with separate AliceReachedRound8/BobReachedRound8 actor flags and a round8BothReached termination predicate. The message pump now captures and drops a round- 8-or-later message instead of forwarding it, and keeps delivering pending lower-round messages until both actors have emitted round 8, so a successful exchange can no longer qualify on only one direction's evidence. The accept scenario now requires each captured Bob and BobWC proof to independently reject at the tight bound (compat off) and accept at the widened historical bound (compat on), pairing the accept run's own proof evidence with the same discrimination the reject scenario already performs, instead of relying on an identical-seed replay claim. Update the README and scenario comments to describe a fresh exchange with paired per-proof verification.
NewZKVProof and NewZKVProofWithSession reject V and R points from different curve groups, but no existing test exercised that boundary; every constructor test used same-curve fixtures. Add a regression with individually valid secp256k1/P256 points in both role assignments, plus a same-curve control whose proof verifies, so the negative cases are attributable to the curve mismatch rather than an invalid point.
README.md and the PR intent state Go 1.25.7 or newer is required, but the go directive declared 1.25.6, so the module's enforced minimum disagreed with the documented contract. The preferred development and CI toolchain remains 1.26.8. Record the PR #37 hardening fixes in the changelog: exported arithmetic boundary errors, owner-scoped Paillier CT reuse, proof-local exponent reuse, bidirectional live qualification, and the new boundary regression coverage.
A per-key cache of LambdaN, its byte encoding, and the decryption coefficient measured no Decrypt speedup (70.7 vs 70.6 ms) while keeping unzeroed secret-derived copies in package-global state. Decrypt rebuilds its state per call again; key validation and error returns are kept, as is the public-key N^2 cache.
Author
Review record and decisions (2026-10-02)The review of this PR was not posted on GitHub. Its fixes were pushed directly to Fixes pushed to
|
| Commit | Fix |
|---|---|
873b8ad |
ModProof encodes its invariant secret exponents once per proof and wipes them on completion; canonical-operand constant-time helpers |
3b609ae |
ProveBobWC / ProveRangeAlice reject out-of-domain witnesses with an error instead of panicking; compatibility-enabled Bob verification checks nil inputs first |
20d66f8 |
Paillier Encrypt / HomoMult / Decrypt validate keys and return errors instead of panicking; per-key constant-time state cache |
c0978ef |
Mixed-binary harness requires both peers to reach round 8 and checks each accept-run proof at both bounds |
98f642d |
Regression test for mixed-curve ZKV constructor rejection |
fa6ef4d |
go.mod minimum restored to the documented Go 1.25.7 (PR #33 had lowered it to 1.25.6) |
Verified on fa6ef4d: CI Test, Vet, Keygen units and Go-fmt pass. Local race and shuffle tests pass on common, crypto/... and tss. The mixed-binary harness passes in both directions.
Decisions
- Paillier private-key cache: removed in fix(paillier): stop caching private-key decryption state; review follow-ups #38. It gave no measured
Decryptgain (70.7 → 70.6 ms) and kept unzeroed copies ofLambdaN-derived secrets in package-global state. Key validation and the public-key cache stay. - Changes reach
devonly through PRs. Branch protection is now on: PR required, the Test / Vet / Keygen units / Go fmt project checks must pass, and it applies to admins too. The changelog now attributes these fixes to their commits instead of "(PR Integrate dev hardening set into master #37)". - keep-core's first release hardcodes
ProtocolModeLegacy. Historical Bob compatibility is enabled through node config. - The compatibility flag logs a startup warning while enabled and is removed in the next keep-core release.
- Latency gate: at most a 2x slowdown. Measured as full keep-core signing time on
devvsmaster, at the same group size on the same hardware.
…ow-ups (#38) fix(paillier): stop caching private-key decryption state; review follow-ups
common.SignatureData is a protobuf message and embeds a mutex, so delivering it by value over the signing end channel made every receiver copy a lock (go vet copylocks). The end channel is now chan<- *common.SignatureData and the party stores its data by pointer, matching upstream BNB fbb0ef7. finalize sends a proto.Clone so the receiver owns a deep copy.
feat(signing)!: deliver signature results by pointer
List every composing PR, add breaking changes for the btcec/v2 curve type, keygen decoder signatures and the Go 1.25.7 minimum, record the new exported API, correct the AliceEnd constant-time gap description and the Paillier cache entry, and fix the README release test commands and audit scope.
docs: complete changelog and README for the dev integration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tracking PR for everything integrated on
devsincemaster(1cd3f0b). Draft: do not merge until the merge gates below are cleared.masteris protected: merging needs one approving review (not the author's) and passing Test, Vet, Keygen units and Go fmt project checks.Scope at
9ff573c: 152 commits, 137 files, +14,583 / −602. Already onmasterand not repeated here: #2, #4, #5, #6, #7 (BNB hardening base stack) and #22 (release workflow). Per-change detail, compatibility notes, and provenance live inCHANGELOG.md.Breaking changes for callers (introduced on
dev)Numbers refer to
CHANGELOG.md→ Breaking changes. keep-core currently pins2e712689, so it also absorbs the earlier breaks already onmaster(Breaking changes 1–7).fullBytesLen*common.SignatureData(9)tss.S256()concrete curve type is btcec/v2 / Decred (10)UnmarshalFactorProof,UnmarshalFactorProofTilde,UnmarshalProofIntsreturn errors (11)PRs merged into
devTooling and dependencies
masterbtcec/v2Constant-time hardening
masterbigmodconstant-time operations enabled by default (supersedes #8 and #10, both closed unmerged)(N+1)^m, exponent width narrowed to publicN.BitLen(),sync.Poolboxing allocation removedInput validation and lifecycle
ModProofreuses constant-time contexts across its 80 iterationsProtocol transcript
ProtocolModeLegacy/ProtocolModeSecurityV2transcript selection; exact historical legacy compatibility; default-off, rollout-only historical Bob compatibility switchfullBytesLen)*common.SignatureData(protobuf message with an embedded mutex) instead of a by-value copy; matches upstreamBNB fbb0ef7, with a deep copy on sendgodirective matches the documented minimumAssurance and docs
m = q/q−1, out-of-order round readiness2e712689binaryReview fixes pushed directly to
devMade in response to the review of this PR; the review record and decisions are in the PR comments.
devhas since been protected, so future changes arrive by PR only.873b8adModProofencodes its invariant secret exponents once per proof and wipes them on completion3b609ae20d66f8c0978ef98f642dfa6ef4dVerification on
devf8bbaff(plus #39 at6056170, #40 at9ff573c)go test -race -shuffle=on:common,crypto/...,tss,ecdsa/signingandecdsa/keygen(561 s) pass.govulncheck: 0 reachable vulnerabilities. 5 advisories exist in required modules, but their code isn't called.6056170): CI pass;ecdsa/signingrace tests, mixed-binary harness and transcript oracle pass.9ff573c): documentation only; CI pass.6056170:pkg/tecdsa/...(full DKG and signing protocol tests),pkg/tbtc/...,pkg/tbtcpg/...,cmd/...pass.Earlier verification on `e7be9a0` (before the review fixes)
go test -race -shuffle=on ./...: pass (keygen 2058 s, signing 397 s).govulncheck: 0 reachable vulnerabilities. 5 advisories exist in required modules, but their code isn't called.pkg/tecdsa/...compiled againstdev; its runtime tests failed closed until the migration in feat(tecdsa): adopt hardened tss-lib in legacy mode keep-core#4349.Merge gates
1. keep-core migration: PR open, tests pass
threshold-network/keep-core#4349 (draft) pins tss-lib
6056170and meets the caller obligations:ProtocolModeLegacyfor its first release.fullBytesLento every signing constructor: the 32-byte sighash width.tbtc.legacyHistoricalBobCompatibilityflag, off by default.It also receives signing results as
*common.SignatureData(#39) and setsGOTOOLCHAIN=autoin its Docker build stages for the Go 1.25.7 minimum. Its DKG, signing,tbtc,tbtcpgandcmdtests pass. The migration also uncovered and fixed a keep-core issue caused by tss-lib's btcec/v2 move: wallet keys from tss-lib and from keep-core's own parser used different secp256k1 curve objects, so identical keys compared unequal. Remaining: review and merge threshold-network/keep-core#4349, then move its pin to a tagged tss-lib release after this PR merges.2. Rollout plan: decided for the first release; security-v2 cutover deferred
ProtocolModeLegacy. It speaks the exact historical transcript, so upgraded and old nodes can still sign together.tbtc.legacyHistoricalBobCompatibility. The node logs a startup warning while it is on.ProtocolModeSecurityV2is a wire break that needs every signer to switch together. Deferred; options tracked in Design: coordinated cutover to tss-lib ProtocolModeSecurityV2 keep-core#4350.3. Latency: within budget at 10 members; production size not yet measured
Agreed budget: full keep-core signing on
devat most 2x slower than onmaster. Measured in keep-core with 10 members and threshold 6, 10 signatures per side:2e712689)devf8bbaffThat's 1.30x. DKG time is unchanged (about 33 s). The constant-time cost grows with group size, so at 100 members the ratio could move toward the per-operation Paillier decryption ratio (about 70 ms vs 33 ms, roughly 2.1x). Before release: re-measure at production size on dedicated hardware.
Next steps
In order. Steps 1–3 can run in parallel; 4 follows the merge; 5 is required before mainnet.
masterprotection requires one approval from someone other than the author.AGENTS.md/CONTRIBUTING.md; #4349 follows its existing style and Conventional Commits.dev. Pass: median ratio at most 2x (merge gate 3). Measured so far: 1.30x at 10 members; the ratio may approach the ~2.1x Paillier decryption ratio at 100.v2.0.0-threshold.1).CHANGELOG.md[Unreleased]under the tag; the release workflow publishes from it.pkg/tecdsa/...tests.Known risks and residual gaps
y = N − Twrap family). It is default-off, legacy-only, and frozen at party construction. keep-core bounds its lifetime: a node flag, a startup warning while enabled, and removal in the following release.bigmodpath. Surroundingmath/bigconversion and reduction remain variable-time; known instances are the round-5 reduction ofrxand the lack of response-time normalization around Paillier decryption. An independent side-channel review is advisable before mainnet.Follow-ups (non-blocking)
golang.org/x/cryptofrom v0.52.0 to v0.56.0 or later (GO-2026-6354/6355/6303). Replacegogo/protobufv1.2.1 (GO-2021-0053): bump to v1.3.2, or better, migrate to thegoogle.golang.org/protobufruntime the repo already uses.devstays inactive until this PR merges.staticcheckfindings are deprecatedelliptic.Curve.ScalarBaseMultcalls in tests and one ST1005 error-string style warning.