1+ import assert from 'assert' ;
12import crypto from 'crypto' ;
23import {
34 BaseBroadcastTransactionOptions ,
45 BaseBroadcastTransactionResult ,
56 BaseCoin ,
67 BaseTransaction ,
78 BitGoBase ,
9+ decryptKeychainPrivateKey ,
810 EDDSAMethods ,
911 EDDSAMethodTypes ,
12+ EddsaSigningMaterial ,
1013 Environments ,
14+ getEddsaSigningMaterial as sharedGetEddsaSigningMaterial ,
1115 KeyPair ,
1216 MPCAlgorithm ,
1317 MPCRecoveryOptions ,
@@ -20,6 +24,7 @@ import {
2024 ParsedTransaction ,
2125 ParseTransactionOptions as BaseParseTransactionOptions ,
2226 RecoveryTxRequest ,
27+ signEddsaMpcV2RecoveryTx ,
2328 SignedTransaction ,
2429 SignTransactionOptions ,
2530 TransactionExplanation ,
@@ -463,7 +468,7 @@ export class Sui extends BaseCoin {
463468 return this . buildUnsignedSweepTransaction ( txBuilder , senderAddress , bitgoKey , idx , derivationPath ) ;
464469 }
465470
466- await this . signRecoveryTransaction ( txBuilder , params , derivationPath , derivedPublicKey , false ) ;
471+ await this . signRecoveryTransaction ( txBuilder , params , derivationPath , derivedPublicKey , bitgoKey , false ) ;
467472 const tx = ( await txBuilder . build ( ) ) as TransferTransaction ;
468473 return {
469474 transactions : [
@@ -554,7 +559,7 @@ export class Sui extends BaseCoin {
554559 return this . buildUnsignedSweepTransaction ( txBuilder , senderAddress , bitgoKey , idx , derivationPath , token ) ;
555560 }
556561
557- await this . signRecoveryTransaction ( txBuilder , params , derivationPath , derivedPublicKey , true ) ;
562+ await this . signRecoveryTransaction ( txBuilder , params , derivationPath , derivedPublicKey , bitgoKey , true ) ;
558563 const tx = ( await txBuilder . build ( ) ) as TokenTransferTransaction ;
559564 return {
560565 transactions : [
@@ -630,55 +635,64 @@ export class Sui extends BaseCoin {
630635 return { txRequests : [ txRequest ] } ;
631636 }
632637
638+ /**
639+ * Detects whether a keycard's decrypted plaintext is MPCv1 JSON or MPCv2 CBOR.
640+ * Protected so tests can stub it via sinon; wraps the shared sdk-core helper.
641+ */
642+ protected async getEddsaSigningMaterial ( userKey : string , walletPassphrase : string ) : Promise < EddsaSigningMaterial > {
643+ return sharedGetEddsaSigningMaterial ( userKey , walletPassphrase , this . bitgo ) ;
644+ }
645+
646+ // Protected so tests can stub via instance overrides — direct module function bindings
647+ // cannot be intercepted by sinon after import.
648+ protected async signSuiMpcV2Recovery ( params : Parameters < typeof signEddsaMpcV2RecoveryTx > [ 0 ] ) : Promise < Buffer > {
649+ return signEddsaMpcV2RecoveryTx ( params ) ;
650+ }
651+
633652 private async signRecoveryTransaction (
634653 txBuilder : TransactionBuilder ,
635654 params : MPCRecoveryOptions ,
636655 derivationPath : string ,
637656 derivedPublicKey : string ,
657+ bitgoKey : string ,
638658 isTokenTransaction : boolean
639659 ) {
640660 // TODO(BG-51092): This looks like a common part which can be extracted out too
641661 const unsignedTx = isTokenTransaction
642662 ? ( ( await txBuilder . build ( ) ) as TokenTransferTransaction )
643663 : ( ( await txBuilder . build ( ) ) as TransferTransaction ) ;
644- if ( ! params . userKey ) {
645- throw new Error ( 'missing userKey' ) ;
646- }
647- if ( ! params . backupKey ) {
648- throw new Error ( 'missing backupKey' ) ;
649- }
650- if ( ! params . walletPassphrase ) {
651- throw new Error ( 'missing wallet passphrase' ) ;
652- }
664+ assert ( params . userKey , 'missing userKey' ) ;
665+ assert ( params . backupKey , 'missing backupKey' ) ;
666+ assert ( params . walletPassphrase , 'missing wallet passphrase' ) ;
653667
654668 // Clean up whitespace from entered values
655669 const userKey = params . userKey . replace ( / \s / g, '' ) ;
656670 const backupKey = params . backupKey . replace ( / \s / g, '' ) ;
657671
658- // Decrypt private keys from KeyCard values
659- let userPrv : string ;
660- try {
661- userPrv = await this . bitgo . decrypt ( {
662- input : userKey ,
663- password : params . walletPassphrase ,
672+ const signingMaterial = await this . getEddsaSigningMaterial ( userKey , params . walletPassphrase ) ;
673+
674+ if ( signingMaterial . version === 'v2' ) {
675+ const signature = await this . signSuiMpcV2Recovery ( {
676+ message : unsignedTx . signablePayload ,
677+ userKey : signingMaterial . encryptedUserKey ,
678+ backupKey,
679+ walletPassphrase : params . walletPassphrase ,
680+ bitgoKey,
681+ derivationPath,
682+ bitgo : this . bitgo ,
664683 } ) ;
665- } catch ( e ) {
666- throw new Error ( `Error decrypting user keychain: ${ e . message } ` ) ;
684+ txBuilder . addSignature ( { pub : derivedPublicKey } , signature ) ;
685+ return ;
667686 }
687+
668688 /** TODO BG-52419 Implement Codec for parsing */
669- const userSigningMaterial = JSON . parse ( userPrv ) as EDDSAMethodTypes . UserSigningMaterial ;
689+ const userSigningMaterial = JSON . parse ( signingMaterial . userPrv ) as EDDSAMethodTypes . UserSigningMaterial ;
670690
671- let backupPrv : string ;
672- try {
673- backupPrv = await this . bitgo . decrypt ( {
674- input : backupKey ,
675- password : params . walletPassphrase ,
676- } ) ;
677- } catch ( e ) {
678- throw new Error ( `Error decrypting backup keychain: ${ e . message } ` ) ;
691+ const backupPrv = await decryptKeychainPrivateKey ( this . bitgo , { encryptedPrv : backupKey } , params . walletPassphrase ) ;
692+ if ( ! backupPrv ) {
693+ throw new Error ( 'Error decrypting backup keychain: invalid password or corrupted key' ) ;
679694 }
680695 const backupSigningMaterial = JSON . parse ( backupPrv ) as EDDSAMethodTypes . BackupSigningMaterial ;
681- /* ********************** END ***********************************/
682696
683697 // add signature
684698 const signatureHex = await EDDSAMethods . getTSSSignature (
0 commit comments