Skip to content

Validate signing round 9 decommitments as curve points#7

Merged
piotr-roslaniec merged 4 commits into
masterfrom
codex/round9-decommit-validation
Jun 15, 2026
Merged

Validate signing round 9 decommitments as curve points#7
piotr-roslaniec merged 4 commits into
masterfrom
codex/round9-decommit-validation

Conversation

@mswilkison

@mswilkison mswilkison commented Jun 10, 2026

Copy link
Copy Markdown

Stacked on #6.

Summary

Round 9 of ECDSA signing was the last place in the protocol where adversarial wire data reached raw elliptic.Curve.Add without on-curve validation. The decommitted U_j/T_j coordinates from SignRound7Message/SignRound8Message went straight into group addition, while the analogous decommitments in rounds 5 and 7 are validated via crypto.NewECPoint.

  • Malformed decommitments could crash or misdirect honest signers. Go's stdlib curves (registrable via tss.RegisterCurve) panic on off-curve inputs to Add since Go 1.19, so a malicious decommitment was a remote crash vector. btcec returns undefined coordinates instead, turning the malformed input into a U != T abort that blamed the honest reporting party itself (round.WrapError(..., round.PartyID())) — actively wrong fault attribution for orchestration layers that act on culprits.
  • Fix: validate decommitted coordinates as canonical curve points before any group operation and attribute the failure to the sending party; accumulate with ECPoint.Add so an identity-summing decommitment (e.g. U_j = -U_i) is also rejected with sender attribution. The final U != T mismatch — which proves phase-5 misbehaviour but cannot identify the misbehaving party — is now reported without a culprit.
  • Round 1 message validity: also reject nil and negative m up front. Nil previously panicked on Cmp inside the protocol goroutine (bypassing tss.Error reporting), and a negative message only surfaced as an unattributed signature-verification failure in finalize.

Upstream BNB master has the same round-9 gap (plus the !ok && len(values) != 4 decommit condition this fork already fixed), so there is no upstream commit to port; this is a residual finding from re-reviewing the hardened surface.

Tests

  • TestRound9_RejectsMalformedDecommitments — off-curve U_j, off-curve T_j, and identity-summing U_j each abort with the sender attributed (previously: panic on stdlib curves / self-blame on btcec).
  • TestRound9_UTMismatchHasNoCulprit — on-curve but inconsistent decommitments abort with no culprit.
  • TestRound9_ConsistentDecommitmentsSucceed — accept path still completes.
  • TestSigning_Start_RejectsInvalidMessage — nil and negative messages fail Start cleanly.
  • go test ./..., go vet ./..., gofmt -l all clean.

@piotr-roslaniec piotr-roslaniec force-pushed the codex/round9-decommit-validation branch 2 times, most recently from edb23e5 to 1ed1a03 Compare June 15, 2026 07:35
@piotr-roslaniec piotr-roslaniec force-pushed the codex/review-residual-cleanup branch from 7e210dd to 20bb989 Compare June 15, 2026 09:38
piotr-roslaniec added a commit that referenced this pull request Jun 15, 2026
Record PR #7's non-breaking hardening: signing round-9 decommitment
curve-point validation (layered on PR #4's decommitFour guard), round-1
hashed-message range validation, and the Paillier FactorVerify
distinct-generator check. Add PR #7 to the composing-PRs list. No new
breaking changes.
@piotr-roslaniec piotr-roslaniec force-pushed the codex/round9-decommit-validation branch from 1ed1a03 to 627be04 Compare June 15, 2026 09:43
@piotr-roslaniec piotr-roslaniec force-pushed the codex/review-residual-cleanup branch from 20bb989 to 03554f7 Compare June 15, 2026 09:57
mswilkison and others added 4 commits June 15, 2026 09:58
Round 9 was the last place where adversarial wire data reached raw
elliptic.Curve.Add without on-curve validation: a malformed U_j/T_j
decommitment panics Go stdlib curves and yields undefined coordinates on
btcec, and the resulting U != T abort blamed the honest reporting party
itself. Validate the decommitted coordinates via crypto.NewECPoint,
attribute failures to the sender, and report the unattributable U != T
mismatch without a culprit.

Also reject nil and negative messages in signing round 1: nil previously
panicked on Cmp inside the protocol goroutine, and negative values only
surfaced as an unattributed verification failure in finalize.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The block comments above the Fiat-Shamir challenge derivation claimed
rejection sampling, but the code uses common.ModReduceHash (modular
reduction). Describe the actual behaviour to avoid misleading auditors.
FactorVerify required s and t to be canonical generators but not
distinct. With s == t the Pedersen-style commitment binding degenerates
and a self-consistent proof over equal bases verifies as valid. Sibling
proofs (dlnproof, MtA range/respondent) already reject equal generators;
mirror that policy here as defense-in-depth.

Add a regression test that builds a proof with s == t and confirms it is
rejected (it verifies as true without the guard).
Record PR #7's non-breaking hardening: signing round-9 decommitment
curve-point validation (layered on PR #4's decommitFour guard), round-1
hashed-message range validation, and the Paillier FactorVerify
distinct-generator check. Add PR #7 to the composing-PRs list. No new
breaking changes.
@piotr-roslaniec piotr-roslaniec force-pushed the codex/round9-decommit-validation branch from 627be04 to 3af3ee0 Compare June 15, 2026 09:58
@piotr-roslaniec piotr-roslaniec changed the base branch from codex/review-residual-cleanup to master June 15, 2026 17:58
@piotr-roslaniec piotr-roslaniec merged commit 86bd1a3 into master Jun 15, 2026
4 checks passed
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