@@ -707,6 +707,100 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils {
707707 }
708708 // #endregion
709709
710+ // #region Round3Share
711+ async createOfflineRound3Share ( params : {
712+ txRequest : TxRequest ;
713+ walletPassphrase : string ;
714+ bitgoPublicGpgKey : string ;
715+ encryptedUserGpgPrvKey : string ;
716+ encryptedRound2Session : string ;
717+ } ) : Promise < {
718+ signatureShareRound3 : SignatureShareRecord ;
719+ } > {
720+ const { walletPassphrase, encryptedUserGpgPrvKey, encryptedRound2Session, bitgoPublicGpgKey, txRequest } = params ;
721+
722+ const { signableHex, derivationPath } = this . getSignableHexAndDerivationPath (
723+ txRequest ,
724+ 'Unable to find transactions in txRequest'
725+ ) ;
726+ const adata = `${ signableHex } :${ derivationPath } ` ;
727+
728+ const useV2 = isV2Envelope ( encryptedRound2Session ) ;
729+
730+ const { bitgoGpgKey, userGpgPrvKey } = await this . getBitgoAndUserGpgKeys (
731+ bitgoPublicGpgKey ,
732+ encryptedUserGpgPrvKey ,
733+ walletPassphrase ,
734+ adata ,
735+ EddsaMPCv2Utils . MPS_DSG_SIGNING_USER_GPG_KEY
736+ ) ;
737+
738+ const transactions = txRequest . transactions ;
739+ assert ( Array . isArray ( transactions ) && transactions . length === 1 , 'txRequest must have exactly one transaction' ) ;
740+ const signatureShares = transactions [ 0 ] . signatureShares ;
741+ assert ( signatureShares , 'Missing signature shares in round 2 txRequest' ) ;
742+
743+ const bitgoShareRoundTwo = [ ...signatureShares ] . reverse ( ) . find ( ( share ) => {
744+ if ( share . from !== SignatureShareType . BITGO || share . to !== SignatureShareType . USER ) {
745+ return false ;
746+ }
747+
748+ try {
749+ return JSON . parse ( share . share ) . type === 'round2Output' ;
750+ } catch {
751+ return false ;
752+ }
753+ } ) ;
754+ assert ( bitgoShareRoundTwo , 'Missing BitGo round 2 signature share' ) ;
755+
756+ const parsedBitGoToUserSigShareRoundTwo = decodeWithCodec (
757+ EddsaMPCv2SignatureShareRound2Output ,
758+ JSON . parse ( bitgoShareRoundTwo . share ) ,
759+ 'Unexpected signature share response. Unable to parse data.'
760+ ) ;
761+
762+ if ( parsedBitGoToUserSigShareRoundTwo . type !== 'round2Output' ) {
763+ throw new Error ( 'Unexpected signature share response. Unable to parse data.' ) ;
764+ }
765+
766+ const bitgoDeserializedMsg2 = await verifyPeerMessageRoundTwo ( parsedBitGoToUserSigShareRoundTwo , bitgoGpgKey ) ;
767+
768+ this . validateAdata ( adata , encryptedRound2Session , EddsaMPCv2Utils . MPS_DSG_SIGNING_ROUND2_STATE ) ;
769+
770+ let decryptedRound2Session : string ;
771+ if ( useV2 ) {
772+ decryptedRound2Session = await this . bitgo . decryptAsync ( {
773+ input : encryptedRound2Session ,
774+ password : walletPassphrase ,
775+ } ) ;
776+ } else {
777+ decryptedRound2Session = this . bitgo . decrypt ( {
778+ input : encryptedRound2Session ,
779+ password : walletPassphrase ,
780+ } ) ;
781+ }
782+
783+ const { dsgSession, userMsgPayload } = JSON . parse ( decryptedRound2Session ) as {
784+ dsgSession : string ;
785+ userMsgPayload : string ;
786+ } ;
787+
788+ const userDsg = new EddsaMPSDsg . DSG ( MPCv2PartiesEnum . USER ) ;
789+ userDsg . restoreSession ( dsgSession ) ;
790+ const userMsg2 : MPSTypes . DeserializedMessage = {
791+ from : MPCv2PartiesEnum . USER ,
792+ payload : new Uint8Array ( Buffer . from ( userMsgPayload , 'base64' ) ) ,
793+ } ;
794+
795+ const [ userMsg3 ] = userDsg . handleIncomingMessages ( [ userMsg2 , bitgoDeserializedMsg2 ] ) ;
796+ assert ( userMsg3 , 'DSG handleIncomingMessages produced no round-3 output' ) ;
797+
798+ const signatureShareRound3 = await getSignatureShareRoundThree ( userMsg3 , userGpgPrvKey ) ;
799+
800+ return { signatureShareRound3 } ;
801+ }
802+ // #endregion
803+
710804 /** @inheritdoc */
711805 async signEddsaMPCv2TssUsingExternalSigner (
712806 params : TSSParams | TSSParamsForMessage ,
0 commit comments