feat(sdk-coin-sol): add MPCv2 signed recovery to Sol.recover()#9045
feat(sdk-coin-sol): add MPCv2 signed recovery to Sol.recover()#9045vibhavgo wants to merge 1 commit into
Conversation
4753207 to
c08950b
Compare
c08950b to
e1f98cb
Compare
|
Claude finished @Marzooqa's task —— View job Code Review for MPCv2 Signed RecoveryTasks:
Overall AssessmentThis PR successfully implements MPCv2 signed recovery for Solana, adding automatic keycard detection and routing to the MPS DSG signing path. The implementation is well-structured and maintains backward compatibility with MPCv1 paths. Detailed Review✅ Strengths
|
f4bb7d1 to
0b31392
Compare
|
@claude review the pr, Are there bugs introduced? Is it well tested? Is it consistent with the ecdsa MPCv2 approach? |
|
Claude finished @Marzooqa's task —— View job PR Review: MPCv2 Signed Recovery for SolanaTasks:
Overall Assessment🔍 BUGS FOUND: No critical bugs, but there are issues that need addressing based on existing review comments and code analysis. ✅ WELL TESTED: Yes, comprehensive test coverage with 5 new test cases covering all major scenarios. Detailed Analysis1. Existing Review Comments to AddressHIGH PRIORITY - Must Fix: Duplicate Method (Comment by Marzooqa)
There's already a method Recommendation: Move the existing class method out as a standalone function instead of creating a new one. Unnecessary Try-Catch (Comment by Marzooqa)
} catch (e) {
// Decryption errors (e.g., wrong password) should propagate; don't catch them.
throw e;
}This try-catch block simply re-throws the error, serving no purpose. Recommendation: Remove the try-catch block around the decryption logic. 2. Code Quality IssuesError Context Loss
catch (e) {
throw new Error(`Error decrypting user keychain: ${e instanceof Error ? e.message : String(e)}`);
}Issue: Generic error message loses context about which operation failed. Magic Numbers
accountId = deriveUnhardenedMps(bitgoKey, currPath).slice(0, 64);
accountId = MPC.deriveUnhardened(bitgoKey, currPath).slice(0, 64);Issue: Magic number 3. Security Analysis ✅
4. Test Coverage Analysis ✅Excellent coverage with 5 comprehensive tests:
Test Quality: Proper use of sinon sandbox, realistic stubs, good assertion coverage. 5. Consistency with ECDSA MPCv2
|
Ticket: WCI-398 Detects MPCv2 keycards automatically in Sol.recover() using isEddsaMpcV1SigningMaterial before any network calls; no new params required in SolRecoveryOptions. Validates userKey and backupKey early for signed recovery, before any network calls, and propagates decryption errors (wrong password) instead of silently treating them as MPCv2. MPCv2 uses deriveUnhardenedMps (BIP32-Ed25519 / Silence Labs formula) for address derivation in place of MPC.deriveUnhardened; signing uses the MPS DSG protocol via signRecoveryEddsaMPCv2. EddsaMPCv2RecoveryFunctions is exported as a mutable plain object from eddsaMPCv2.ts rather than relying solely on the barrel re-export. TypeScript compiles `export * from` into Object.defineProperty calls with configurable:false, which sinon cannot stub. Plain object properties are configurable and writable by default, enabling unit tests to swap out the three MPCv2 helpers without modifying sol.ts. Type narrowing via control flow in parameter validation avoids non-null assertions. isEddsaMpcV1SigningMaterial splits decryption and JSON parsing into separate try/catch blocks — decryption errors propagate, JSON parse errors indicate MPCv2 format (return false). Adds five unit tests covering native SOL, SPL token, Token-2022, durable-nonce, and commonKeyChain mismatch scenarios.
0b31392 to
8ef93db
Compare
Summary
Adds automatic MPCv2 keycard detection to
Sol.recover(), routing signed recovery through the new MPS DSG signing path while leaving the MPCv1 and unsigned sweep paths entirely unchanged.Ticket: WCI-398
Changes
modules/sdk-coin-sol/src/sol.tsEDDSAUtils.EddsaMPCv2RecoveryFunctions.isEddsaMpcV1SigningMaterialearly inrecover(), before balance lookup or transaction construction, to detect whether the keycard is MPCv2 (CBOR) or MPCv1 (JSON). No new parameters required inSolRecoveryOptions.deriveUnhardenedMps(BIP32-Ed25519 / Silence Labs formula) instead ofMPC.deriveUnhardened, since MPCv2 keycards are generated by the MPS stack which uses a different derivation formula.getEddsaMpcV2RecoveryKeySharesFromReducedKeyto decrypt and reconstruct key shares, assertscommonKeyChainmatchesbitgoKey, then callssignRecoveryEddsaMPCv2for the MPS DSG 2-party signing round.modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.tsEddsaMPCv2RecoveryFunctionsas a mutable plain object groupingisEddsaMpcV1SigningMaterial,getEddsaMpcV2RecoveryKeySharesFromReducedKey, andsignRecoveryEddsaMPCv2.export * from './eddsaMPCv2'intoObject.definePropertycalls withconfigurable: false. Sinon'ssandbox.stub(obj, 'method')internally usesObject.definePropertyto replace the descriptor, which throwsTypeError: Cannot redefine propertyon non-configurable properties. A plain object's properties areconfigurable: trueandwritable: trueby default, so sinon can swap them at test time without any changes tosol.tsitself.modules/sdk-coin-sol/test/unit/sol.tsdescribe('Recover Transactions (MPCv2):')block with five tests:{ serializedTx, scanIndex }commonKeyChainmismatch — throws with descriptive errorgetTokenAccountsByOwnerprogram ID routingprogramIdthrough the token recovery pathTest Plan
yarn unit-test --scope @bitgo/sdk-coin-sol -- --grep "MPCv2"— 5 passingCLOSES TICKET: WCI-398