Skip to content

Commit 5995969

Browse files
authored
Merge pull request #8744 from BitGo/WCI-374/eddsa-mpcv2-external-signer-orchestrator
feat(sdk-core): implement EdDSA MPCv2 external signer orchestrator
2 parents af60c9e + 4636da5 commit 5995969

3 files changed

Lines changed: 462 additions & 2 deletions

File tree

modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,18 @@ import {
2626
} from '../../../tss/eddsa/eddsaMPCv2';
2727
import { generateGPGKeyPair } from '../../opengpgUtils';
2828
import { MPCv2PartiesEnum } from '../ecdsa/typesMPCv2';
29-
import { RequestType, SignatureShareType, TSSParamsForMessageWithPrv, TSSParamsWithPrv, TxRequest } from '../baseTypes';
29+
import {
30+
CustomEddsaMPCv2SigningRound1GeneratingFunction,
31+
CustomEddsaMPCv2SigningRound2GeneratingFunction,
32+
CustomEddsaMPCv2SigningRound3GeneratingFunction,
33+
RequestType,
34+
SignatureShareType,
35+
TSSParams,
36+
TSSParamsForMessage,
37+
TSSParamsForMessageWithPrv,
38+
TSSParamsWithPrv,
39+
TxRequest,
40+
} from '../baseTypes';
3041
import { BaseEddsaUtils } from './base';
3142
import { EddsaMPCv2KeyGenSendFn, KeyGenSenderForEnterprise } from './eddsaMPCv2KeyGenSender';
3243

@@ -515,4 +526,103 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils {
515526
}
516527

517528
// #endregion
529+
530+
// #region external signer
531+
/** @inheritdoc */
532+
async signEddsaMPCv2TssUsingExternalSigner(
533+
params: TSSParams | TSSParamsForMessage,
534+
externalSignerEddsaMPCv2SigningRound1Generator: CustomEddsaMPCv2SigningRound1GeneratingFunction,
535+
externalSignerEddsaMPCv2SigningRound2Generator: CustomEddsaMPCv2SigningRound2GeneratingFunction,
536+
externalSignerEddsaMPCv2SigningRound3Generator: CustomEddsaMPCv2SigningRound3GeneratingFunction,
537+
requestType: RequestType = RequestType.tx
538+
): Promise<TxRequest> {
539+
const { txRequest, reqId } = params;
540+
541+
// TODO(WP-2176): Add support for message signing
542+
assert(
543+
requestType === RequestType.tx,
544+
'Only transaction signing is supported for external signer, got: ' + requestType
545+
);
546+
547+
let txRequestResolved: TxRequest;
548+
if (typeof txRequest === 'string') {
549+
txRequestResolved = await getTxRequest(this.bitgo, this.wallet.id(), txRequest, reqId);
550+
} else {
551+
txRequestResolved = txRequest;
552+
}
553+
554+
const bitgoPublicGpgKey = await this.pickBitgoPubGpgKeyForSigning(
555+
true,
556+
reqId,
557+
txRequestResolved.enterpriseId,
558+
true
559+
);
560+
561+
if (!bitgoPublicGpgKey) {
562+
throw new Error('Missing BitGo GPG key for MPCv2');
563+
}
564+
565+
// round 1
566+
const { signatureShareRound1, userGpgPubKey, encryptedRound1Session, encryptedUserGpgPrvKey } =
567+
await externalSignerEddsaMPCv2SigningRound1Generator({ txRequest: txRequestResolved });
568+
const round1TxRequest = await sendSignatureShareV2(
569+
this.bitgo,
570+
txRequestResolved.walletId,
571+
txRequestResolved.txRequestId,
572+
[signatureShareRound1],
573+
requestType,
574+
this.baseCoin.getMPCAlgorithm(),
575+
userGpgPubKey,
576+
undefined,
577+
this.wallet.multisigTypeVersion(),
578+
reqId
579+
);
580+
581+
// round 2
582+
const { signatureShareRound2, encryptedRound2Session } = await externalSignerEddsaMPCv2SigningRound2Generator({
583+
txRequest: round1TxRequest,
584+
encryptedRound1Session,
585+
encryptedUserGpgPrvKey,
586+
bitgoPublicGpgKey: bitgoPublicGpgKey.armor(),
587+
});
588+
const round2TxRequest = await sendSignatureShareV2(
589+
this.bitgo,
590+
txRequestResolved.walletId,
591+
txRequestResolved.txRequestId,
592+
[signatureShareRound2],
593+
requestType,
594+
this.baseCoin.getMPCAlgorithm(),
595+
userGpgPubKey,
596+
undefined,
597+
this.wallet.multisigTypeVersion(),
598+
reqId
599+
);
600+
assert(
601+
round2TxRequest.transactions && round2TxRequest.transactions[0].signatureShares,
602+
'Missing signature shares in round 2 txRequest'
603+
);
604+
605+
// round 3
606+
const { signatureShareRound3 } = await externalSignerEddsaMPCv2SigningRound3Generator({
607+
txRequest: round2TxRequest,
608+
encryptedRound2Session,
609+
encryptedUserGpgPrvKey,
610+
bitgoPublicGpgKey: bitgoPublicGpgKey.armor(),
611+
});
612+
await sendSignatureShareV2(
613+
this.bitgo,
614+
txRequestResolved.walletId,
615+
txRequestResolved.txRequestId,
616+
[signatureShareRound3],
617+
requestType,
618+
this.baseCoin.getMPCAlgorithm(),
619+
userGpgPubKey,
620+
undefined,
621+
this.wallet.multisigTypeVersion(),
622+
reqId
623+
);
624+
625+
return sendTxRequest(this.bitgo, txRequestResolved.walletId, txRequestResolved.txRequestId, requestType, reqId);
626+
}
627+
// #endregion
518628
}

modules/sdk-core/src/bitgo/wallet/wallet.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,6 +2166,15 @@ export class Wallet implements IWallet {
21662166
return this.signTransactionTssExternalSignerECDSA(this.baseCoin, params);
21672167
}
21682168

2169+
if (
2170+
_.isFunction(params.customEddsaMPCv2SigningRound1GenerationFunction) &&
2171+
_.isFunction(params.customEddsaMPCv2SigningRound2GenerationFunction) &&
2172+
_.isFunction(params.customEddsaMPCv2SigningRound3GenerationFunction)
2173+
) {
2174+
// invoke external signer TSS for EdDSA MPCv2 workflow
2175+
return this.signTransactionTssExternalSignerEdDSAMPCv2(this.baseCoin, params);
2176+
}
2177+
21692178
if (
21702179
_.isFunction(params.customMPCv2SigningRound1GenerationFunction) &&
21712180
_.isFunction(params.customMPCv2SigningRound2GenerationFunction) &&
@@ -4405,6 +4414,59 @@ export class Wallet implements IWallet {
44054414
}
44064415
}
44074416

4417+
/**
4418+
* Signs a transaction from a TSS EdDSA MPCv2 wallet using external signer.
4419+
*
4420+
* @param params signing options
4421+
*/
4422+
private async signTransactionTssExternalSignerEdDSAMPCv2(
4423+
coin: IBaseCoin,
4424+
params: WalletSignTransactionOptions = {}
4425+
): Promise<TxRequest> {
4426+
let txRequestId = '';
4427+
if (params.txRequestId) {
4428+
txRequestId = params.txRequestId;
4429+
} else if (params.txPrebuild && params.txPrebuild.txRequestId) {
4430+
txRequestId = params.txPrebuild.txRequestId;
4431+
} else {
4432+
throw new Error('TxRequestId required to sign TSS transactions with External Signer.');
4433+
}
4434+
4435+
if (!params.customEddsaMPCv2SigningRound1GenerationFunction) {
4436+
throw new Error(
4437+
'Generator function for EdDSA MPCv2 Round 1 share required to sign transactions with External Signer.'
4438+
);
4439+
}
4440+
4441+
if (!params.customEddsaMPCv2SigningRound2GenerationFunction) {
4442+
throw new Error(
4443+
'Generator function for EdDSA MPCv2 Round 2 share required to sign transactions with External Signer.'
4444+
);
4445+
}
4446+
4447+
if (!params.customEddsaMPCv2SigningRound3GenerationFunction) {
4448+
throw new Error(
4449+
'Generator function for EdDSA MPCv2 Round 3 share required to sign transactions with External Signer.'
4450+
);
4451+
}
4452+
4453+
try {
4454+
assert(this.tssUtils, 'tssUtils must be defined');
4455+
const signedTxRequest = await this.tssUtils.signEddsaMPCv2TssUsingExternalSigner(
4456+
{
4457+
txRequest: txRequestId,
4458+
reqId: params.reqId || new RequestTracer(),
4459+
},
4460+
params.customEddsaMPCv2SigningRound1GenerationFunction,
4461+
params.customEddsaMPCv2SigningRound2GenerationFunction,
4462+
params.customEddsaMPCv2SigningRound3GenerationFunction
4463+
);
4464+
return signedTxRequest;
4465+
} catch (e) {
4466+
throw new Error('failed to sign transaction ' + e);
4467+
}
4468+
}
4469+
44084470
/**
44094471
* Signs a transaction from a TSS ECDSA wallet using external signer.
44104472
*

0 commit comments

Comments
 (0)