55 EDDSAMethods ,
66 EDDSAMethodTypes ,
77 Environments ,
8+ getEddsaSigningMaterial ,
89 KeyPair ,
910 MPCAlgorithm ,
1011 MPCConsolidationRecoveryOptions ,
@@ -20,6 +21,7 @@ import {
2021 PopulatedIntent ,
2122 PrebuildTransactionWithIntentOptions ,
2223 RecoveryTxRequest ,
24+ signEddsaMpcV2RecoveryTx ,
2325 SignedTransaction ,
2426 SignTransactionOptions ,
2527 TransactionRecipient ,
@@ -303,6 +305,9 @@ export class Iota extends BaseCoin {
303305 const bitgoKey = params . bitgoKey . replace ( / \s / g, '' ) ;
304306 const MPC = await EDDSAMethods . getInitializedMpcInstance ( ) ;
305307
308+ // Detect MPCv2 keycard format once up front, to avoid decrypting on every scan iteration.
309+ const isMpcV2 = await this . isMpcv2SigningMaterial ( params . userKey , params . backupKey , params . walletPassphrase ) ;
310+
306311 for ( let idx = startIdx ; idx < endIdx ; idx ++ ) {
307312 const derivationPath = ( params . seed ? getDerivationPath ( params . seed ) : 'm' ) + `/${ idx } ` ;
308313 const derivedPublicKey = MPC . deriveUnhardened ( bitgoKey , derivationPath ) . slice ( 0 , 64 ) ;
@@ -337,7 +342,8 @@ export class Iota extends BaseCoin {
337342 derivationPath ,
338343 derivedPublicKey ,
339344 idx ,
340- bitgoKey
345+ bitgoKey ,
346+ isMpcV2
341347 ) ;
342348 } catch ( e ) {
343349 continue ;
@@ -398,7 +404,9 @@ export class Iota extends BaseCoin {
398404 params ,
399405 derivationPath ,
400406 derivedPublicKey ,
401- unsignedTx
407+ unsignedTx ,
408+ isMpcV2 ,
409+ bitgoKey
402410 ) ;
403411
404412 // Build and return signed transaction
@@ -706,7 +714,8 @@ export class Iota extends BaseCoin {
706714 derivationPath : string ,
707715 derivedPublicKey : string ,
708716 idx : number ,
709- bitgoKey : string
717+ bitgoKey : string ,
718+ isMpcV2 : boolean
710719 ) : Promise < MPCTxs | MPCSweepTxs > {
711720 tokenObjectsWithBalance = tokenObjectsWithBalance . sort ( ( a , b ) => ( BigInt ( b . balance ) > BigInt ( a . balance ) ? 1 : - 1 ) ) ;
712721 if ( tokenObjectsWithBalance . length > MAX_OBJECT_LIMIT ) {
@@ -780,7 +789,9 @@ export class Iota extends BaseCoin {
780789 params ,
781790 derivationPath ,
782791 derivedPublicKey ,
783- unsignedTx
792+ unsignedTx ,
793+ isMpcV2 ,
794+ bitgoKey
784795 ) ;
785796
786797 const finalTx = ( await txBuilder . build ( ) ) as TransferTransaction ;
@@ -800,12 +811,26 @@ export class Iota extends BaseCoin {
800811 } ;
801812 }
802813
814+ private async isMpcv2SigningMaterial (
815+ userKey ?: string ,
816+ backupKey ?: string ,
817+ walletPassphrase ?: string
818+ ) : Promise < boolean > {
819+ if ( ! walletPassphrase ) return false ;
820+ if ( ! userKey ) throw new Error ( 'missing userKey' ) ;
821+ if ( ! backupKey ) throw new Error ( 'missing backupKey' ) ;
822+ const material = await getEddsaSigningMaterial ( userKey . replace ( / \s / g, '' ) , walletPassphrase , this . bitgo ) ;
823+ return material . version === 'v2' ;
824+ }
825+
803826 private async signRecoveryTransaction (
804827 txBuilder : TransactionBuilder ,
805828 params : IotaRecoveryOptions ,
806829 derivationPath : string ,
807830 derivedPublicKey : string ,
808- unsignedTx : TransferTransaction
831+ unsignedTx : TransferTransaction ,
832+ isMpcV2 : boolean ,
833+ bitgoKey : string
809834 ) : Promise < string > {
810835 if ( ! params . userKey ) {
811836 throw new Error ( 'missing userKey' ) ;
@@ -820,30 +845,44 @@ export class Iota extends BaseCoin {
820845 const userKey = params . userKey . replace ( / \s / g, '' ) ;
821846 const backupKey = params . backupKey . replace ( / \s / g, '' ) ;
822847
823- // Decrypt private keys from KeyCard values
824- let userPrv : string ;
825- try {
826- userPrv = await this . bitgo . decrypt ( { input : userKey , password : params . walletPassphrase } ) ;
827- } catch ( e ) {
828- throw new Error ( `Error decrypting user keychain: ${ ( e as Error ) . message } ` ) ;
829- }
830- const userSigningMaterial = JSON . parse ( userPrv ) as EDDSAMethodTypes . UserSigningMaterial ;
848+ let signatureBuffer : Buffer ;
831849
832- let backupPrv : string ;
833- try {
834- backupPrv = await this . bitgo . decrypt ( { input : backupKey , password : params . walletPassphrase } ) ;
835- } catch ( e ) {
836- throw new Error ( `Error decrypting backup keychain: ${ ( e as Error ) . message } ` ) ;
837- }
838- const backupSigningMaterial = JSON . parse ( backupPrv ) as EDDSAMethodTypes . BackupSigningMaterial ;
850+ if ( ! isMpcV2 ) {
851+ // Decrypt private keys from KeyCard values
852+ let userPrv : string ;
853+ try {
854+ userPrv = await this . bitgo . decrypt ( { input : userKey , password : params . walletPassphrase } ) ;
855+ } catch ( e ) {
856+ throw new Error ( `Error decrypting user keychain: ${ ( e as Error ) . message } ` ) ;
857+ }
858+ const userSigningMaterial = JSON . parse ( userPrv ) as EDDSAMethodTypes . UserSigningMaterial ;
839859
840- // Generate TSS signature
841- const signatureBuffer = await EDDSAMethods . getTSSSignature (
842- userSigningMaterial ,
843- backupSigningMaterial ,
844- derivationPath ,
845- unsignedTx
846- ) ;
860+ let backupPrv : string ;
861+ try {
862+ backupPrv = await this . bitgo . decrypt ( { input : backupKey , password : params . walletPassphrase } ) ;
863+ } catch ( e ) {
864+ throw new Error ( `Error decrypting backup keychain: ${ ( e as Error ) . message } ` ) ;
865+ }
866+ const backupSigningMaterial = JSON . parse ( backupPrv ) as EDDSAMethodTypes . BackupSigningMaterial ;
867+
868+ // Generate TSS signature
869+ signatureBuffer = await EDDSAMethods . getTSSSignature (
870+ userSigningMaterial ,
871+ backupSigningMaterial ,
872+ derivationPath ,
873+ unsignedTx
874+ ) ;
875+ } else {
876+ signatureBuffer = await signEddsaMpcV2RecoveryTx ( {
877+ message : unsignedTx . signablePayload ,
878+ userKey,
879+ backupKey,
880+ walletPassphrase : params . walletPassphrase ,
881+ bitgoKey,
882+ derivationPath,
883+ bitgo : this . bitgo ,
884+ } ) ;
885+ }
847886
848887 // Build full signature: scheme_flag (1 byte) + signature (64 bytes) + public_key (32 bytes)
849888 const schemeFlag = Buffer . alloc ( 1 , 0x00 ) ; // Ed25519 scheme
0 commit comments