Skip to content

Add immutable per-party GG20 transcript modes - #9

Merged
piotr-roslaniec merged 13 commits into
devfrom
codex/dual-mode-transcript
Sep 29, 2026
Merged

piotr-roslaniec merged 13 commits into
devfrom
codex/dual-mode-transcript

Conversation

@lrsaturnino

@lrsaturnino lrsaturnino commented Jul 29, 2026 •

Copy link
Copy Markdown
Member

Summary

  • require an explicit legacy or security-v2 transcript mode for every ECDSA keygen/signing party
  • freeze the selected mode at local-party construction
  • reproduce the historical untagged DLN, range, Bob, factor, modulus, and Schnorr proof transcripts in legacy mode
  • retain session-bound/domain-tagged challenges and the per-ceremony nonce requirement in security-v2 mode
  • keep validation and memory-safety hardening active in both modes
  • add exact historical-challenge regressions and homogeneous keygen/signing coverage for both modes

This is a draft because the transcript boundary needs independent cryptographic review before release use.

Verification

  • go test ./...
  • go test -race ./tss ./crypto/dlnproof ./crypto/mta ./crypto/paillier ./crypto/schnorr ./ecdsa/keygen ./ecdsa/signing
  • go vet ./...

Rebased onto dev (2026-09-26)

  • Original head dd461c59a154524cbb8a6ac78a8666b18acc28fc preserved at backup/codex/dual-mode-transcript-20260926.
  • New head: 4cf0bd74ad91099e5e3441157812006e99423d1f (on dev @ 349282e).
  • Judgment calls applied:
    • CHANGELOG.md composing-PR list: kept both the PR #19 line from dev and this PR's PR #9 line (PR Add immutable per-party GG20 transcript modes #9 listed after Update protobuf runtime to v1.33.0 #19). If you prefer a different order or wording, edit that list only.
    • Added commit 4cf0bd7 "test: select security-v2 mode in constant-time e2e tests": the TestE2EConcurrentConstantTime tests that dev added in ecdsa/keygen and ecdsa/signing build parties without a transcript mode, so they panicked under this PR. They already set a session nonce, so they now call SetProtocolMode(tss.ProtocolModeSecurityV2), the same pattern as TestE2EWithHDKeyDerivation. If you want these tests to cover legacy mode too, turn them into the same legacy/security-v2 subtest loop that TestE2EConcurrent uses.
    • crypto/dlnproof/proof.go and crypto/mta/proofs.go merged automatically with dev's constant-time changes. The PR's proofChallenge(...) routing sits alongside the new CT branches.
  • Dropped commits: none. The original 5 commits are replayed, plus the test fix above.
  • Verification: no conflict markers. The PR's net diff against dev matches its net diff before the rebase line for line, except for the CHANGELOG context and the two added test lines. go build, go vet, and go test ./... pass (60m timeout, the same as the Makefile), and so does testdata/legacy_transcript/verify.sh. Not re-run: -race.

@piotr-roslaniec
piotr-roslaniec changed the base branch from master to dev September 14, 2026 11:49
@lrsaturnino
lrsaturnino marked this pull request as ready for review September 18, 2026 12:12

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

Found one P1 compatibility regression in the legacy T1 bound; see the inline comment.

Comment thread crypto/mta/proofs.go
Comment on lines +261 to +262
q6 := new(big.Int).Mul(q3, q3)
maxT1 = new(big.Int).Add(pk.N, q6)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P1] Account for the historical witness range in the legacy T1 bound

PR #9 breaks mixed-version legacy signing because historical BobMid and BobMidWC sample betaPrm (the proof’s y) below pkA.N, not q^5 (see the historical implementation). Consequently, honest T1 = e*y + gamma almost always exceeds N + q^6, causing AliceEnd/AliceEndWC to reject historical proofs and abort signing. Derive the finite legacy bound from the historical witness range and add coverage using historical BobMid output; the current compatibility vectors fix y = 11 and miss this failure.

lrsaturnino and others added 6 commits September 26, 2026 15:44
The constant-time keygen and signing e2e tests added on dev construct
local parties without choosing a transcript mode, which now panics at
party construction. They already set a per-ceremony session nonce, so
select security-v2 to keep their existing session-bound behavior.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YPfkvvm5ofFJV7bwtQmAxM
@claude
claude Bot force-pushed the codex/dual-mode-transcript branch from dd461c5 to 4cf0bd7 Compare September 26, 2026 16:21
…y toggle

- Add SetLegacyHistoricalBobCompatibility(bool) to tss.Parameters (default
  false, legacy-mode only, immutable after FreezeProtocolMode)
- Widen legacy T1 verifier bound in signing round 3 only when opt-in is
  enabled: from tight N + q^6 to historical witness-range bound (q+1)*N
  derived from T1 = e*y + gamma with e < q, y < N, gamma < N
- Default tight bound preserves PR9's hardened behavior; security-v2 unchanged
- Prover sampling (y < q^5) and standalone Verify/AliceEnd APIs unchanged

New tests:
- tss/params_test: guards (mode selection, freeze, security-v2 rejection)
- crypto/mta/legacy_bob_historical_witness_test: boundary sweep
  y=q^5-1, q^5, [q^5,N), N/2, N-1 for Bob and BobWC
- crypto/mta/legacy_bob_historical_witness_test: y=N-1 attack family
  rejection by default, acceptance when opted in
- ecdsa/signing/round_3_test: plumbing test proving the flag reaches
  both AliceEndLegacy and AliceEndWCLegacy checks

CHANGELOG: documents rollout-only risk under Unreleased
@piotr-roslaniec

Copy link
Copy Markdown

Fix for P1 9-F1 (Historical Bob compatibility) — pushed to this branch

Problem: PR #9's tightened legacy verifier (N + q^6) rejected historical 2e712689 BobMid/BobMidWC proofs (which sampled y < N), blocking the stated mixed-version rollout purpose.

Solution: Explicit opt-in toggle on tss.Parameters:

  • SetLegacyHistoricalBobCompatibility(bool) / LegacyHistoricalBobCompatibility() — default false
  • Only legal with ProtocolModeLegacy (panics if unset or ProtocolModeSecurityV2)
  • Immutable after FreezeProtocolMode / party construction
  • Widens only the legacy signing round-3 Bob/BobWC T1 verifier bound:
    • Default (off): tight N + q^6 (PR9's hardened bound)
    • Opt-in (on): historical exclusive bound (q+1)*N, derived from the historical witness range T1 = e*y + gamma with e < q, y < N, gamma < N
  • No changes to: prover sampling (y < q^5), security-v2 transcript, standalone Verify/AliceEnd APIs (remain tight by default)

Tests added:

  1. tss.TestLegacyHistoricalBobCompatibilityGuards — setter only works pre-freeze in legacy mode; security-v2 rejects
  2. crypto/mta.TestLegacyBobWidenedVerifierAcceptsHistoricalWitnessRange — boundary sweep at y = q^5-1, q^5, [q^5,N), N/2, N-1 for both Bob and BobWC; asserts documented T1 < N+q^6 boundary match
  3. crypto/mta.TestLegacyHistoricalWitnessRangeRejectsWiderT1 — y = N-1 attack family: default tight verifier rejects; opt-in accepts (via AliceEndLegacy/AliceEndWCLegacy)
  4. signing.TestSigningRound3_LegacyHistoricalBobCompatibilityPlumbing — end-to-end round 3 test: same historical-witness round-2 message rejected by default, accepted when flag enabled

CHANGELOG: Documents rollout-only risk — this toggle accepts historical Bob proofs and carries the historical legacy-mode residual risk; default remains hardened.

SHA: 8ae2cf8 (pushed to codex/dual-mode-transcript)

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.
@piotr-roslaniec

Copy link
Copy Markdown

Merge of origin/dev into PR #9 branch — 5e14eb0

PR #9 is now current with dev (post-#17/#23/#12/#13/#15/#14/#26), including the P1 9-F1 fix 8ae2cf8 (opt-in historical Bob compatibility).

Conflicts resolved (5):

File Resolution
CHANGELOG.md Kept dev's PR #17 (+#8/#10/#11/#23) composing entry alongside the PR #9 entry under Unreleased; retained the rollout-only historical Bob compatibility risk text from 8ae2cf8.
crypto/mta/proofs.go Preserved #9 legacy vs security-v2 tau/gamma sampling branches; added dev's #15 nil-sampling guard on beta before the branch. Default tight N + q^6 maxT1 and opt-in (q+1)*N bound (8ae2cf8) unchanged; dev's fixed public-width ExpCTWithBitLen CT calls kept.
crypto/schnorr/schnorr_proof.go Combined #9's legacy/v2 challenge API split (newZKProof + challenge abstraction) with dev's #23 constant-time MulCT branch for t = a + c·x. Neither transcript mode nor the CT path was dropped.
ecdsa/keygen/round_3.go Kept #14's unmarshalVSSCommitment part-count guard/refactor; re-applied #9's mode-conditional round.proofContext(j)... for FactorVerify instead of dev's unqualified contextJ (which does not exist on this branch).
ecdsa/signing/round_ct_wiring_test.go Adapted #23's new CT wiring test: local signing parties now select ProtocolModeSecurityV2 + session nonce before construction (required by PR #9's explicit-mode freeze).

Preserved from both sides via auto-merge: dev toolchain/deps (go 1.25.7, toolchain 1.26.8), #17 default-on CT behavior and fixed public-width exponent calls in range_proof.go/dlnproof/proof.go, #12 arity/input guards, #15 nil/length/bounds guards in paillier.go, #14 keygen changes, and all dev workflows/tests.

Verification: normal merge commit, standard push (no force). CI is pending — orchestrator will verify build/tests.

@piotr-roslaniec

Copy link
Copy Markdown

Corrective merge of advanced origin/dev (dec6df0, post-PR #27) — d82d7b7

Clean merge, no conflicts. PR #27 contributed one test-only file (crypto/mta/constant_time_equiv_test.go, +81): the unequal-width MtA constant-time regression tests. No local parties are constructed, so no protocol-mode selection was needed; existing PR #9 semantics and the CT regression from #23/#27 are both preserved.

@piotr-roslaniec
piotr-roslaniec merged commit 9771869 into dev Sep 29, 2026
4 checks passed
@piotr-roslaniec

Copy link
Copy Markdown

Integration verification completed before merge into dev at 97718693fac4fc96a7ae14d2a81982aa815011b3.

  • Legacy and security-v2 transcript modes remain explicit and immutable for a party lifetime.
  • Historical Bob/BobWC compatibility is a separate legacy-only opt-in, false by default and frozen at party construction. Default legacy verification uses the tight exclusive N + q^6 T1 bound; the rollout opt-in uses (q+1)*N. The opt-in deliberately re-exposes the historical wider-witness residual risk and must be limited to mixed-version rollout. Security-v2 is unchanged.
  • The branch was merged with every prior dev dependency, including input hardening and decoder fuzz coverage.
  • Local compatibility, decoder-caller, and round-3 plumbing tests passed on the final head.
  • The independent bidirectional oracle passed both historical vector sets and reported canonical_legacy_vectors_equal=true.
  • A prior full local suite passed on the reviewed implementation; final-head GitHub Actions run 36629905972 passed vet/format, keygen units, full tests, and the bidirectional legacy transcript oracle.
  • During integration, a missing Paillier test import was caught by parent verification, fixed, and then covered by the final CI run.

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.

4 participants