@@ -46,6 +46,7 @@ import { EddsaMPCv2KeyGenSendFn, KeyGenSenderForEnterprise } from './eddsaMPCv2K
4646export class EddsaMPCv2Utils extends BaseEddsaUtils {
4747 private static readonly MPS_DSG_SIGNING_USER_GPG_KEY = 'MPS_DSG_SIGNING_USER_GPG_KEY' ;
4848 private static readonly MPS_DSG_SIGNING_ROUND1_STATE = 'MPS_DSG_SIGNING_ROUND1_STATE' ;
49+ private static readonly MPS_DSG_SIGNING_ROUND2_STATE = 'MPS_DSG_SIGNING_ROUND2_STATE' ;
4950
5051 /** @inheritdoc */
5152 async createKeychains ( params : {
@@ -534,6 +535,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils {
534535
535536 // #region external signer
536537
538+ // #region Round1Share
537539 async createOfflineRound1Share ( params : {
538540 txRequest : TxRequest ;
539541 prv : string ;
@@ -597,6 +599,113 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils {
597599
598600 return { signatureShareRound1, userGpgPubKey, encryptedRound1Session, encryptedUserGpgPrvKey } ;
599601 }
602+ // #endregion
603+
604+ // #region Round2Share
605+ async createOfflineRound2Share ( params : {
606+ txRequest : TxRequest ;
607+ walletPassphrase : string ;
608+ bitgoPublicGpgKey : string ;
609+ encryptedUserGpgPrvKey : string ;
610+ encryptedRound1Session : string ;
611+ } ) : Promise < {
612+ signatureShareRound2 : SignatureShareRecord ;
613+ encryptedRound2Session : string ;
614+ } > {
615+ const { walletPassphrase, encryptedUserGpgPrvKey, encryptedRound1Session, bitgoPublicGpgKey, txRequest } = params ;
616+
617+ const { signableHex, derivationPath } = this . getSignableHexAndDerivationPath (
618+ txRequest ,
619+ 'Unable to find transactions in txRequest'
620+ ) ;
621+ const adata = `${ signableHex } :${ derivationPath } ` ;
622+
623+ const useV2 = isV2Envelope ( encryptedRound1Session ) ;
624+
625+ const { bitgoGpgKey, userGpgPrvKey } = await this . getBitgoAndUserGpgKeys (
626+ bitgoPublicGpgKey ,
627+ encryptedUserGpgPrvKey ,
628+ walletPassphrase ,
629+ adata ,
630+ EddsaMPCv2Utils . MPS_DSG_SIGNING_USER_GPG_KEY
631+ ) ;
632+
633+ const transactions = txRequest . transactions ;
634+ assert ( Array . isArray ( transactions ) && transactions . length === 1 , 'txRequest must have exactly one transaction' ) ;
635+ const signatureShares = transactions [ 0 ] . signatureShares ;
636+ assert ( signatureShares , 'Missing signature shares in round 1 txRequest' ) ;
637+
638+ const bitgoShareRoundOne = getBitgoSignatureShare ( signatureShares , SignatureShareType . USER ) ;
639+ const parsedBitGoToUserSigShareRoundOne = decodeWithCodec (
640+ EddsaMPCv2SignatureShareRound1Output ,
641+ JSON . parse ( bitgoShareRoundOne . share ) ,
642+ 'Unexpected signature share response. Unable to parse data.'
643+ ) ;
644+
645+ if ( parsedBitGoToUserSigShareRoundOne . type !== 'round1Output' ) {
646+ throw new Error ( 'Unexpected signature share response. Unable to parse data.' ) ;
647+ }
648+
649+ const bitgoDeserializedMsg1 = await verifyPeerMessageRoundOne ( parsedBitGoToUserSigShareRoundOne , bitgoGpgKey ) ;
650+
651+ this . validateAdata ( adata , encryptedRound1Session , EddsaMPCv2Utils . MPS_DSG_SIGNING_ROUND1_STATE ) ;
652+
653+ let decryptedRound1Session : string ;
654+ if ( useV2 ) {
655+ decryptedRound1Session = await this . bitgo . decryptAsync ( {
656+ input : encryptedRound1Session ,
657+ password : walletPassphrase ,
658+ } ) ;
659+ } else {
660+ decryptedRound1Session = this . bitgo . decrypt ( {
661+ input : encryptedRound1Session ,
662+ password : walletPassphrase ,
663+ } ) ;
664+ }
665+
666+ const { dsgSession, userMsgPayload } = JSON . parse ( decryptedRound1Session ) as {
667+ dsgSession : string ;
668+ userMsgPayload : string ;
669+ } ;
670+
671+ const userDsg = new EddsaMPSDsg . DSG ( MPCv2PartiesEnum . USER ) ;
672+ userDsg . restoreSession ( dsgSession ) ;
673+ const userMsg1 : MPSTypes . DeserializedMessage = {
674+ from : MPCv2PartiesEnum . USER ,
675+ payload : new Uint8Array ( Buffer . from ( userMsgPayload , 'base64' ) ) ,
676+ } ;
677+
678+ const [ userMsg2 ] = userDsg . handleIncomingMessages ( [ userMsg1 , bitgoDeserializedMsg1 ] ) ;
679+ assert ( userMsg2 , 'DSG handleIncomingMessages produced no round-2 output' ) ;
680+
681+ const signatureShareRound2 = await getSignatureShareRoundTwo ( userMsg2 , userGpgPrvKey ) ;
682+ const sessionPayload = JSON . stringify ( {
683+ dsgSession : userDsg . getSession ( ) ,
684+ userMsgPayload : Buffer . from ( userMsg2 . payload ) . toString ( 'base64' ) ,
685+ } ) ;
686+
687+ if ( useV2 ) {
688+ const session = await this . bitgo . createEncryptionSession ( walletPassphrase ) ;
689+ try {
690+ const encryptedRound2Session = await session . encrypt (
691+ sessionPayload ,
692+ `${ EddsaMPCv2Utils . MPS_DSG_SIGNING_ROUND2_STATE } :${ adata } `
693+ ) ;
694+ return { signatureShareRound2, encryptedRound2Session } ;
695+ } finally {
696+ session . destroy ( ) ;
697+ }
698+ }
699+
700+ const encryptedRound2Session = this . bitgo . encrypt ( {
701+ input : sessionPayload ,
702+ password : walletPassphrase ,
703+ adata : `${ EddsaMPCv2Utils . MPS_DSG_SIGNING_ROUND2_STATE } :${ adata } ` ,
704+ } ) ;
705+
706+ return { signatureShareRound2, encryptedRound2Session } ;
707+ }
708+ // #endregion
600709
601710 /** @inheritdoc */
602711 async signEddsaMPCv2TssUsingExternalSigner (
0 commit comments