feat(sdk-coin-dot): add MPCv2 signed hot recovery support - #9484
feat(sdk-coin-dot): add MPCv2 signed hot recovery support#9484ralph-bitgo[bot] wants to merge 1 commit into
Conversation
0a4c3b5 to
02798b6
Compare
02798b6 to
a079856
Compare
47b6045 to
5b6afb7
Compare
Phase 1 — SummaryAdds MPCv2 signed hot recovery support for DOT ( Files changed: flowchart TD
A["recover()"] --> B{isUnsignedSweep?}
B -- yes --> C[Return unsigned serializedTx]
B -- no --> D["assert userKey / backupKey / walletPassphrase"]
D --> E["getEddsaSigningMaterial(userKey, passphrase)"]
E --> F{signingMaterial.version === 'v2'?}
F -- MPCv2 --> G["signDotMpcV2Recovery() → rawSig (64 bytes)"]
G --> H["txnBuilder.addSignature(pub, rawSig)"]
F -- MPCv1 --> I["decryptKeychainPrivateKey(backupKey)"]
I --> J["EDDSAMethods.getTSSSignature(...)"]
J --> K["txnBuilder.addSignature(pub, signatureHex)"]
H --> L["txnBuilder.build() → serializedTx"]
K --> L
Phase 2 — Ticket AlignmentTicket: WCI-1227 Implied scope: "Add MPCv2 signed hot recovery for DOT." The implementation matches exactly — detection, dispatch, MPCv1 regression guard, and a cryptographic correctness test (including the ✅ Implementation aligns with ticket scope. Phase 3 — Index AnalysisNo new database queries introduced. Phase 4 — Type SafetyScanned all added lines for
No Phase 5 — MPCv2 Correctness & Test QualityCorrectness
Test Quality
Consistency
SummaryOne ❌ finding:
Everything else is clean. The 0x00 discriminant handling, the Generated by [ |
Detect CBOR (MPCv2) vs JSON (MPCv1) keycards in Dot.recover() using the shared getEddsaSigningMaterial/signDotMpcV2Recovery helpers from sdk-core. Uses assert() for guards in newly introduced recovery code. Ticket: WCI-1227
5b6afb7 to
8378a5a
Compare
What
isMpcv2SigningMaterial()andaddRecoverySignature()private methods toDot(modules/sdk-coin-dot/src/dot.ts), and updatesrecover()to detect the keycard format once (MPCv1 JSON vs MPCv2 CBOR) and dispatch signing accordingly.@bitgo/sdk-core(getEddsaSigningMaterial,signEddsaMpcV2RecoveryTx) rather than duplicating logic — same pattern assdk-coin-sol(WCI-398) andabstract-substrate(WCI-1276/WCI-1239).0x00Substrate discriminant, becauseTransaction#constructSignedPayloadalready prepends it unconditionally for every signature (the same mechanism the pre-existing MPCv1 path relies on). Manually prepending it, as literally described in the ticket and mirrored from the already-mergedabstract-substratecode, double-prefixes and corrupts the signature — verified empirically by round-tripping a signed transaction through decode.describe('Recover Transactions (MPCv2):')test block covering: MPCv2 signed recovery returning{ serializedTx, scanIndex }, MPCv1 regression (still usesgetTSSSignature), mismatchedbitgoKeyvs keycardcommonKeyChainthrowing, and a cryptographic verification (vianacl.sign.detached.verifyagainst the transaction's signable payload and derived pubkey) that the extrinsic signature is exactly 64 bytes with the discriminant applied once — not just a length check, which would not catch the double-prefix corruption class.recoverConsolidations()MPCv2 support is intentionally out of scope here — tracked separately in WCI-1236.Why
Wallets are migrating EdDSA MPC from the Zengo implementation (MPCv1) to Silence Labs (MPCv2). DOT's
recover()only handled MPCv1 keycards, so self-custody hot recovery would fail for any wallet backed by an MPCv2 keycard. This closes that gap for DOT, matching the detection + signing path already shipped for SOL (WCI-398) and the shared helpers extracted for Substrate coins (WCI-1276/WCI-1239).Test plan
yarn unit-testinmodules/sdk-coin-dot— 205 passing, 0 failing (includes the 5 new MPCv2 tests)yarn build:cjs(tsc --build) — compiles cleanlyyarn eslint --quiet src/dot.ts test/unit/dot.ts— no errorsTicket: WCI-1227