Skip to content

feat(sdk-core): add EdDSA MPCv2 full 3-round signing orchestration#8697

Open
Marzooqa wants to merge 1 commit intomasterfrom
WCI-154
Open

feat(sdk-core): add EdDSA MPCv2 full 3-round signing orchestration#8697
Marzooqa wants to merge 1 commit intomasterfrom
WCI-154

Conversation

@Marzooqa
Copy link
Copy Markdown
Contributor

@Marzooqa Marzooqa commented May 5, 2026

  • Add signTxRequest, signTxRequestForMessage, and signRequestBase to EddsaMPCv2Utils implementing WASM round 0 + API rounds 1 and 2
  • Verify PGP signatures on BitGo round1Output and round2Output
  • Return latestTxRequest after round 2 (round 3 + send deferred to next PR)
  • Add signTxRequest unit tests using nock with real BitGo DSG sessions

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

TICKET: WCI-154

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 5, 2026

@Marzooqa Marzooqa changed the title feat(sdk-core): add EdDSA MPCv2 signing rounds 1 and 2 feat(sdk-core): add EdDSA MPCv2 full 3-round signing orchestration May 6, 2026
@Marzooqa Marzooqa marked this pull request as ready for review May 6, 2026 15:14
@Marzooqa Marzooqa requested review from a team as code owners May 6, 2026 15:14
Copy link
Copy Markdown
Contributor

@zahin-mohammad zahin-mohammad left a comment

Choose a reason for hiding this comment

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

Reviewed against base #8687. Built locally — tsc --noEmit clean in sdk-core, signTxRequest.ts test passes (5/5).

The orchestration faithfully mirrors ECDSA's EcdsaMPCv2Utils.signRequestBase and the protocol structure looks right: WASM rounds 0/1/2 interleaved with API rounds 1/2/3, no client-side verification on round 3 (WP finalises). Nothing here freezes public API surface — the two new public methods (signTxRequest, signTxRequestForMessage) match the existing ITSSUtils/ECDSA shape, and signRequestBase is private. So everything below is follow-up safe.

Most significant finding is the missing mpcv2PartyId plumbing — backup signing is implicitly unsupported here even though the helpers from #8687 already accept the party id. ECDSA supports it; this is a behavioral gap vs the parallel path.

See inline comments.

Comment thread modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts Outdated
Comment thread modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts Outdated
Comment thread modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts Outdated
Comment thread modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts
Comment thread modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts Outdated
Comment thread modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts
Comment thread modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/signTxRequest.ts Outdated
@Marzooqa Marzooqa force-pushed the WCI-153 branch 2 times, most recently from 969535a to 3d581de Compare May 7, 2026 13:10
@Marzooqa Marzooqa requested a review from zahin-mohammad May 7, 2026 14:35
zahin-mohammad
zahin-mohammad previously approved these changes May 7, 2026
Copy link
Copy Markdown
Contributor

@zahin-mohammad zahin-mohammad left a comment

Choose a reason for hiding this comment

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

LGTM — gated on #8687 landing first (per the DO NOT MERGE label).

All 8 prior comments are resolved in this revision:

  • Backup-signer plumbing: params.mpcv2PartyId now flows through DSG init + all three getSignatureShareRound{One,Two,Three} calls; signerShareType derived from partyId
  • ✅ Typo handleIncomingMessagexshandleIncomingMessages
  • ✅ Assert ordering moved before Buffer.from(...) in both tx and message branches
  • ✅ Defensive assert(userMsg2, ...) / assert(userMsg3, ...) after each handleIncomingMessages destructure
  • ✅ Fragile [length - 1] share lookup replaced with new getBitgoSignatureShare(shares, signerShareType) helper in tss/common.ts that filters by from === BITGO && to === signerShareType
  • messageRaw/bufferToSign footgun tracked as follow-up WCI-362
  • ✅ Rate-limit retry tests added for rounds 1 and 3 (round 2 was already covered)
  • ✅ Test coverage: BACKUP path ("signs a txRequest with backup key") + malformed round-3 response added. Round-2 tampered-signature is covered at the helper level in #8687 (verifyBitGoMessageRoundTwo should throw on a tampered message), so the orchestration-level gap is acceptable

Residual nits — non-blocking, address in a follow-up

  1. Inconsistent error style in signRequestBase. The GPG-key check uses if (!bitgoGpgPubKey) throw new Error(...) while every other invariant in this method uses assert(...). Unifying on assert(bitgoGpgPubKey, 'Missing BitGo GPG key for MPCv2') would keep the failure-mode style consistent.
  2. Unnamed boolean args to pickBitgoPubGpgKeyForSigning(true, params.reqId, txRequest.enterpriseId, true). This is a pre-existing pattern carried over from ECDSA, not introduced here, but the call site reads as flag soup. Worth a separate cleanup ticket against the helper signature (named-options object or named consts) so the pattern doesn't propagate further.
  3. Round-2 orchestration-level negative test for a wrong-type response would mirror the existing should throw if round 1 response has wrong type and round the symmetry out. Not strictly needed since verifyBitGoMessageRoundTwo is unit-tested at the helper layer, but cheap to add.

None of these block — happy to approve as-is.

Base automatically changed from WCI-153 to master May 7, 2026 17:24
@Marzooqa Marzooqa dismissed zahin-mohammad’s stale review May 7, 2026 17:24

The base branch was changed.

- Add signTxRequest, signTxRequestForMessage, and signRequestBase to
  EddsaMPCv2Utils implementing the full online DSG protocol:
  WASM round 0, API rounds 1-3, and sendTxRequest
- Support BACKUP party signing via mpcv2PartyId param
- Verify PGP signatures on BitGo round1Output and round2Output
- Use pickBitgoPubGpgKeyForSigning with isEddsaMpcv2=true for ed25519 key
- Use decodeWithCodec for type-safe parsing of signature share responses
- Move getBitgoSignatureShare helper to tss/common.ts; use .find() by
  from/to instead of fragile array-index lookup
- Add defensive asserts after each WASM and API round
- Add comprehensive signTxRequest unit tests covering:
  - tx signing and message signing (all 3 rounds + send)
  - BACKUP party signing path
  - 429 rate-limit retry handling on rounds 1, 2, and 3
  - rejection after 4+ consecutive 429 errors
  - rejection on wrong round1Output type
  - rejection on malformed round3 response

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

TICKET: WCI-154
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants