feat(sdk-coin-sui): support MPCv2 signed hot recovery - #9493
feat(sdk-coin-sui): support MPCv2 signed hot recovery#9493ralph-bitgo[bot] wants to merge 2 commits into
Conversation
8e6e2c1 to
0ef61b5
Compare
0ef61b5 to
4c55071
Compare
Update Sui.recover()/signRecoveryTransaction() to detect and sign MPCv2 keycards using the shared eddsaMPCv2 helpers from @bitgo/sdk-core (getEddsaSigningMaterial, signEddsaMpcV2RecoveryTx), dispatching on the EddsaSigningMaterial discriminated union instead of a boolean flag. Both native SUI transfer and token transfer recovery share this path. The raw 64-byte MPCv2 signature is wrapped in SUI's existing 0x00 flag + pubkey signature envelope via the current addSignature/ setSerializedSig flow, unchanged for MPCv1. MPCv1 backup key decryption now goes through decryptKeychainPrivateKey instead of a bespoke try/catch, and parameter guards use assert() per the shared recovery pattern. Sui wallets using the newer Silence Labs MPCv2 EdDSA key scheme could not previously complete self-hosted (hot) recovery, since recover() only understood MPCv1 JSON keycards. This mirrors the pattern already adopted by TON/SOL for the EDDSA upgrade from Zengo to Silence Labs. Ticket: WCI-1224 Session-Id: 0c067bc3-368d-4c82-9e68-8d08c89766d5 Task-Id: 8c5b0392-4a96-4479-a83b-8ba80cd2428d
4c55071 to
0bc2ea6
Compare
0bc2ea6 to
e362cfa
Compare
e362cfa to
4994308
Compare
Phase 1 — Summary
Files changed: 2 | +223 / −30 flowchart TD
A[signRecoveryTransaction] --> B[getEddsaSigningMaterial\nuserKey stripped]
B -->|version === 'v2'| C[signSuiMpcV2Recovery\nMPS DSG]
C --> D[txBuilder.addSignature\nraw 64-byte sig]
D --> E[SUI envelope 0x00+sig+pub = 97 bytes]
B -->|version === 'v1'| F[JSON.parse userPrv\ndecryptKeychainPrivateKey backupKey]
F --> G[EDDSAMethods.getTSSSignature\nlegacy MPC]
G --> H[txBuilder.addSignature]
Phase 2 — Ticket AlignmentTicket: The PR title ( Phase 3 — Index AnalysisNo new database queries introduced. Phase 4 — Type SafetyNo Sui.prototype as unknown as { getEddsaSigningMaterial: unknown }✅ Clean. Phase 5 — MPCv2 Correctness & Test QualityCorrectness
Test Quality
Consistency vs DOT (#9484)
Summary of ❌ Findings
Generated by [ |
Add an MPCv2-signed recovery test under the token recovery describe block, mirroring the native-transfer MPCv2 test added in the prior commit. Asserts getTSSSignature is not called and the resulting signature is wrapped in SUI's 0x00-flag envelope. The prior commit wired signRecoveryTransaction()'s MPCv2 dispatch into both recover() and recoverSuiToken(), but only exercised the native-transfer path in tests. The ticket explicitly calls out that token recovery (getTokenTransferBuilder) must also handle MPCv2, so this closes that coverage gap found during review. Ticket: WCI-1224 Session-Id: 0c067bc3-368d-4c82-9e68-8d08c89766d5 Task-Id: 8c5b0392-4a96-4479-a83b-8ba80cd2428d
4994308 to
0349d5d
Compare
What
Sui.recover()/ the privatesignRecoveryTransaction()now dispatch on theEddsaSigningMaterialdiscriminated union (version: 'v1' | 'v2') instead of only handling MPCv1 JSON keycards.@bitgo/sdk-coreEdDSA MPCv2 recovery helpers so they can be stubbed with sinon in tests:getEddsaSigningMaterial(userKey, passphrase)— detects MPCv1 JSON vs MPCv2 CBOR keycards.signSuiMpcV2Recovery(params)— runs the MPS DSG signing flow locally and returns the raw 64-byte Ed25519 signature.'v2'branch, the raw signature is passed through the existingtxBuilder.addSignature()/setSerializedSig()flow, which already wraps it in SUI's0x00flag + pubkey signature envelope — unchanged from the MPCv1 path.decryptKeychainPrivateKey(shared helper) instead of a bespoketry/catch, and parameter guards useassert()per the pattern established for other MPCv2 coin migrations.recover()) and token transfer recovery (recoverSuiToken()/getTokenTransferBuilder) share the same dispatch, since both callsignRecoveryTransaction().createBroadcastableSweepTransaction()(unsigned sweep / OVC path) andrecoverConsolidations()MPCv2 support are unaffected/out of scope (the latter is tracked separately in WCI-1234).getTSSSignaturenot called, SUI envelope applied) for both native and token transfers, MPCv1 keycard still routes to the v1 path (regression), and a mismatchedbitgoKeyvs keycardcommonKeyChainthrows.Why
Sui wallets using the newer Silence Labs MPCv2 EdDSA key scheme could not previously complete self-hosted (hot) recovery, since
recover()only understood MPCv1 JSON keycards and would fail to parse or sign with an MPCv2 keycard. This closes that gap using the shared EdDSA MPCv2 recovery helpers that landed in@bitgo/sdk-core(WCI-1276), following the same pattern already adopted for TON and SOL as part of the broader EDDSA upgrade from Zengo to Silence Labs.Test plan
yarn build(tsc, scoped to@bitgo/sdk-coin-sui) compiles cleanly.yarn eslint --quiet src/sui.ts test/unit/sui.ts— no lint errors.yarn unit-test— 204 passing, 0 failing (all existing tests plus 4 new MPCv2 tests: native-transfer MPCv2 routing, native-transfer MPCv1 regression, token-transfer MPCv2 routing, and mismatched-key rejection).Ticket: WCI-1224