@@ -33,6 +33,10 @@ import {
3333 AuditDecryptedKeyParams ,
3434 extractCommonKeychain ,
3535 TssVerifyAddressOptions ,
36+ getEddsaSigningMaterial as sharedGetEddsaSigningMaterial ,
37+ signEddsaMpcV2RecoveryTx ,
38+ EddsaSigningMaterial ,
39+ decryptKeychainPrivateKey ,
3640} from '@bitgo/sdk-core' ;
3741import { KeyPair as AdaKeyPair , Transaction , TransactionBuilderFactory , Utils } from './lib' ;
3842import type { Asset } from './lib/transaction' ;
@@ -370,7 +374,7 @@ export class Ada extends BaseCoin {
370374 * @returns {MPCTx | MPCSweepTxs } array of the serialized transaction hex strings and indices
371375 * of the addresses being swept
372376 */
373- async recover ( params : MPCRecoveryOptions ) : Promise < MPCTx | MPCSweepTxs > {
377+ async recover ( params : MPCRecoveryOptions , precomputedMaterial ?: EddsaSigningMaterial ) : Promise < MPCTx | MPCSweepTxs > {
374378 if ( ! params . bitgoKey ) {
375379 throw new Error ( 'missing bitgoKey' ) ;
376380 }
@@ -447,53 +451,50 @@ export class Ada extends BaseCoin {
447451
448452 let serializedTx = unsignedTransaction . toBroadcastFormat ( ) ;
449453 if ( ! isUnsignedSweep ) {
450- if ( ! params . userKey ) {
451- throw new Error ( 'missing userKey' ) ;
452- }
453- if ( ! params . backupKey ) {
454- throw new Error ( 'missing backupKey' ) ;
455- }
456- if ( ! params . walletPassphrase ) {
457- throw new Error ( 'missing wallet passphrase' ) ;
458- }
454+ assert ( params . userKey , 'missing userKey' ) ;
455+ assert ( params . backupKey , 'missing backupKey' ) ;
456+ assert ( params . walletPassphrase , 'missing wallet passphrase' ) ;
459457
460458 // Clean up whitespace from entered values
461459 const userKey = params . userKey . replace ( / \s / g, '' ) ;
462460 const backupKey = params . backupKey . replace ( / \s / g, '' ) ;
463-
464- // Decrypt private keys from KeyCard values
465- let userPrv ;
466- try {
467- userPrv = await this . bitgo . decrypt ( {
468- input : userKey ,
469- password : params . walletPassphrase ,
470- } ) ;
471- } catch ( e ) {
472- throw new Error ( `Error decrypting user keychain: ${ e . message } ` ) ;
473- }
474- /** TODO BG-52419 Implement Codec for parsing */
475- const userSigningMaterial = JSON . parse ( userPrv ) as EDDSAMethodTypes . UserSigningMaterial ;
476-
477- let backupPrv ;
478- try {
479- backupPrv = await this . bitgo . decrypt ( {
480- input : backupKey ,
481- password : params . walletPassphrase ,
461+ const adaKeyPair = new AdaKeyPair ( { pub : accountId } ) ;
462+ const signingMaterial =
463+ precomputedMaterial ?? ( await this . getEddsaSigningMaterial ( userKey , params . walletPassphrase ) ) ;
464+
465+ if ( signingMaterial . version === 'v2' ) {
466+ const signature = await this . signAdaMpcV2Recovery ( {
467+ message : unsignedTransaction . signablePayload ,
468+ userKey : signingMaterial . encryptedUserKey ,
469+ backupKey,
470+ walletPassphrase : params . walletPassphrase ,
471+ bitgoKey,
472+ derivationPath : currPath ,
473+ bitgo : this . bitgo ,
482474 } ) ;
483- } catch ( e ) {
484- throw new Error ( `Error decrypting backup keychain: ${ e . message } ` ) ;
475+ txBuilder . addSignature ( { pub : adaKeyPair . getKeys ( ) . pub } , signature ) ;
476+ } else {
477+ /** TODO BG-52419 Implement Codec for parsing */
478+ const userSigningMaterial = JSON . parse ( signingMaterial . userPrv ) as EDDSAMethodTypes . UserSigningMaterial ;
479+ const backupPrv = await decryptKeychainPrivateKey (
480+ this . bitgo ,
481+ { encryptedPrv : backupKey } ,
482+ params . walletPassphrase
483+ ) ;
484+ if ( ! backupPrv ) {
485+ throw new Error ( 'Error decrypting backup keychain: invalid password or corrupted key' ) ;
486+ }
487+ const backupSigningMaterial = JSON . parse ( backupPrv ) as EDDSAMethodTypes . BackupSigningMaterial ;
488+
489+ // add signature
490+ const signatureHex = await EDDSAMethods . getTSSSignature (
491+ userSigningMaterial ,
492+ backupSigningMaterial ,
493+ currPath ,
494+ unsignedTransaction
495+ ) ;
496+ txBuilder . addSignature ( { pub : adaKeyPair . getKeys ( ) . pub } , signatureHex ) ;
485497 }
486- const backupSigningMaterial = JSON . parse ( backupPrv ) as EDDSAMethodTypes . BackupSigningMaterial ;
487-
488- // add signature
489- const signatureHex = await EDDSAMethods . getTSSSignature (
490- userSigningMaterial ,
491- backupSigningMaterial ,
492- currPath ,
493- unsignedTransaction
494- ) ;
495- const adaKeyPair = new AdaKeyPair ( { pub : accountId } ) ;
496- txBuilder . addSignature ( { pub : adaKeyPair . getKeys ( ) . pub } , signatureHex ) ;
497498 const signedTransaction = await txBuilder . build ( ) ;
498499 serializedTx = signedTransaction . toBroadcastFormat ( ) ;
499500 } else {
@@ -574,6 +575,14 @@ export class Ada extends BaseCoin {
574575 seed : params . seed ,
575576 } ;
576577 const { address : baseAddress } = await this . getAdaAddressAndAccountId ( addressParams ) ;
578+
579+ // Detect signing material once to avoid re-decrypting the keycard on every loop iteration.
580+ let signingMaterial : EddsaSigningMaterial | undefined ;
581+ if ( params . walletPassphrase ) {
582+ assert ( params . userKey , 'missing userKey' ) ;
583+ signingMaterial = await this . getEddsaSigningMaterial ( params . userKey , params . walletPassphrase ) ;
584+ }
585+
577586 const consolidationTransactions : any [ ] = [ ] ;
578587 let lastScanIndex = startIdx ;
579588 for ( let i = startIdx ; i < endIdx ; i ++ ) {
@@ -589,7 +598,7 @@ export class Ada extends BaseCoin {
589598
590599 let recoveryTransaction ;
591600 try {
592- recoveryTransaction = await this . recover ( recoverParams ) ;
601+ recoveryTransaction = await this . recover ( recoverParams , signingMaterial ) ;
593602 } catch ( e ) {
594603 if (
595604 e . message === 'Did not find address with funds to recover.' ||
@@ -690,6 +699,18 @@ export class Ada extends BaseCoin {
690699 return new TransactionBuilderFactory ( coins . get ( this . getBaseChain ( ) ) ) ;
691700 }
692701
702+ /**
703+ * Detects whether a keycard's decrypted plaintext is MPCv1 JSON or MPCv2 CBOR.
704+ * Unsigned sweeps (no walletPassphrase) have no keycard to inspect and default to MPCv1.
705+ */
706+ protected async getEddsaSigningMaterial ( userKey : string , walletPassphrase : string ) : Promise < EddsaSigningMaterial > {
707+ return sharedGetEddsaSigningMaterial ( userKey . replace ( / \s / g, '' ) , walletPassphrase , this . bitgo ) ;
708+ }
709+
710+ protected async signAdaMpcV2Recovery ( params : Parameters < typeof signEddsaMpcV2RecoveryTx > [ 0 ] ) : Promise < Buffer > {
711+ return signEddsaMpcV2RecoveryTx ( params ) ;
712+ }
713+
693714 /** inherited doc */
694715 setCoinSpecificFieldsInIntent ( intent : PopulatedIntent , params : PrebuildTransactionWithIntentOptions ) : void {
695716 intent . unspents = params . unspents ;
0 commit comments