Skip to content

Commit 8154e4a

Browse files
authored
Merge pull request #60 from BitGo/WP-000000/mpcv2-create-follow-up
chore: refactor file names for mpc creation
2 parents 75d9341 + e718dd8 commit 8154e4a

File tree

8 files changed

+454
-483
lines changed

8 files changed

+454
-483
lines changed

src/api/enclaved/handlers/mpcV2Finalize.ts renamed to src/api/enclaved/handlers/ecdsaMPCv2Finalize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { KmsClient } from '../../../kms/kmsClient';
88
import assert from 'assert';
99

10-
export async function mpcV2Finalize(
10+
export async function ecdsaMPCv2Finalize(
1111
req: EnclavedApiSpecRouteRequest<'v1.mpcv2.finalize', 'post'>,
1212
): Promise<MpcV2FinalizeResponseType> {
1313
const { source, encryptedData, encryptedDataKey, broadcastMessages, bitgoCommonKeychain } =

src/api/enclaved/handlers/mpcV2Initialize.ts renamed to src/api/enclaved/handlers/ecdsaMPCv2Initialize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as bitgoSdk from '@bitgo/sdk-core';
88
import logger from '../../../logger';
99
import { MPCv2PartiesEnum } from '@bitgo/sdk-core/dist/src/bitgo/utils/tss/ecdsa';
1010

11-
export async function mpcV2Initialize(
11+
export async function ecdsaMPCv2Initialize(
1212
req: EnclavedApiSpecRouteRequest<'v1.mpcv2.initialize', 'post'>,
1313
): Promise<MpcV2InitializeResponseType> {
1414
const { source } = req.decoded;

src/api/enclaved/handlers/mpcV2Round.ts renamed to src/api/enclaved/handlers/ecdsaMPCv2Round.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { MPCv2PartiesEnum } from '@bitgo/sdk-core/dist/src/bitgo/utils/tss/ecdsa';
88
import { KmsClient } from '../../../kms/kmsClient';
99

10-
export async function mpcV2Round(
10+
export async function ecdsaMPCv2Round(
1111
req: EnclavedApiSpecRouteRequest<'v1.mpcv2.round', 'post'>,
1212
): Promise<MpcV2RoundResponseType> {
1313
const { source, encryptedData, encryptedDataKey, round, broadcastMessages, p2pMessages } =

src/api/master/clients/enclavedExpressClient.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,9 @@ export class EnclavedExpressClient {
508508
/**
509509
* Initialize MPCv2 key generation
510510
*/
511-
async initMpcV2(params: { source: 'user' | 'backup' }): Promise<MpcV2InitializeResponseType> {
511+
async initEcdsaMpcV2KeyGenMpcV2(params: {
512+
source: 'user' | 'backup';
513+
}): Promise<MpcV2InitializeResponseType> {
512514
if (!this.coin) {
513515
throw new Error('Coin must be specified to initialize MPCv2 key generation');
514516
}
@@ -536,7 +538,7 @@ export class EnclavedExpressClient {
536538
/**
537539
* Execute a round in the MPCv2 protocol
538540
*/
539-
async mpcV2Round(params: {
541+
async roundEcdsaMPCv2KeyGen(params: {
540542
source: 'user' | 'backup';
541543
encryptedData: string;
542544
encryptedDataKey: string;
@@ -573,7 +575,7 @@ export class EnclavedExpressClient {
573575
/**
574576
* Finalize MPCv2 key generation
575577
*/
576-
async mpcV2Finalize(params: {
578+
async finalizeEcdsaMPCv2KeyGen(params: {
577579
source: 'user' | 'backup';
578580
encryptedData: string;
579581
encryptedDataKey: string;

src/api/master/handlers/ecdsa.ts

Lines changed: 288 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import {
2+
BaseCoin,
23
BitGoBase,
4+
commonTssMethods,
5+
EcdsaMPCv2Utils,
36
getTxRequest,
4-
Wallet,
57
IRequestTracer,
6-
EcdsaMPCv2Utils,
7-
commonTssMethods,
88
RequestType,
9+
SupplementGenerateWalletOptions,
10+
Wallet,
911
} from '@bitgo/sdk-core';
1012
import { EnclavedExpressClient } from '../clients/enclavedExpressClient';
1113
import logger from '../../../logger';
@@ -113,3 +115,286 @@ export async function handleEcdsaSigning(
113115
reqId,
114116
);
115117
}
118+
119+
interface OrchestrateEcdsaKeyGenParams {
120+
bitgo: BitGoBase;
121+
baseCoin: BaseCoin;
122+
enclavedExpressClient: EnclavedExpressClient;
123+
enterprise: string;
124+
walletParams: SupplementGenerateWalletOptions;
125+
}
126+
127+
export async function orchestrateEcdsaKeyGen({
128+
bitgo,
129+
baseCoin,
130+
enclavedExpressClient,
131+
enterprise,
132+
walletParams,
133+
}: OrchestrateEcdsaKeyGenParams) {
134+
const constants = await bitgo.fetchConstants();
135+
if (!constants.mpc.bitgoMPCv2PublicKey) {
136+
throw new Error('Unable to create MPCv2 keys - bitgoMPCv2PublicKey is missing in constants');
137+
}
138+
const ecdsaUtils = new EcdsaMPCv2Utils(bitgo, baseCoin);
139+
140+
// INITIALIZE ROUND: GENERATE ALL GPG KEYS AND RETRIEVE GPG PUBS FROM ALL PARTIES
141+
const userInitResponse = await enclavedExpressClient.initEcdsaMpcV2KeyGenMpcV2({
142+
source: 'user',
143+
});
144+
if (
145+
!userInitResponse.gpgPub ||
146+
!userInitResponse.encryptedData ||
147+
!userInitResponse.encryptedDataKey
148+
) {
149+
throw new Error('Missing required fields in user init response');
150+
}
151+
const backupInitResponse = await enclavedExpressClient.initEcdsaMpcV2KeyGenMpcV2({
152+
source: 'backup',
153+
});
154+
if (
155+
!backupInitResponse.gpgPub ||
156+
!backupInitResponse.encryptedData ||
157+
!backupInitResponse.encryptedDataKey
158+
) {
159+
throw new Error('Missing required fields in backup init response');
160+
}
161+
162+
// ROUND 1
163+
const userRound1Promise = enclavedExpressClient.roundEcdsaMPCv2KeyGen({
164+
source: 'user',
165+
encryptedData: userInitResponse.encryptedData,
166+
encryptedDataKey: userInitResponse.encryptedDataKey,
167+
round: 1,
168+
bitgoGpgPub: constants.mpc.bitgoMPCv2PublicKey,
169+
counterPartyGpgPub: backupInitResponse.gpgPub,
170+
});
171+
const backupRound1Promise = enclavedExpressClient.roundEcdsaMPCv2KeyGen({
172+
source: 'backup',
173+
encryptedData: backupInitResponse.encryptedData,
174+
encryptedDataKey: backupInitResponse.encryptedDataKey,
175+
round: 1,
176+
bitgoGpgPub: constants.mpc.bitgoMPCv2PublicKey,
177+
counterPartyGpgPub: userInitResponse.gpgPub,
178+
});
179+
const [userRound1Response, backupRound1Response] = await Promise.all([
180+
userRound1Promise,
181+
backupRound1Promise,
182+
]);
183+
if (!userRound1Response.broadcastMessage) {
184+
throw new Error('Missing broadcast message in user round 1 response');
185+
}
186+
if (!backupRound1Response.broadcastMessage) {
187+
throw new Error('Missing broadcast message in backup round 1 response');
188+
}
189+
190+
// ROUND 1 & 2 BitGo
191+
const round1And2BitGoResponse = await ecdsaUtils.sendKeyGenerationRound1(
192+
enterprise,
193+
userInitResponse.gpgPub,
194+
backupInitResponse.gpgPub,
195+
{
196+
broadcastMessages: [
197+
userRound1Response.broadcastMessage,
198+
backupRound1Response.broadcastMessage,
199+
],
200+
p2pMessages: [],
201+
},
202+
);
203+
const { sessionId, bitgoMsg1, bitgoToUserMsg2, bitgoToBackupMsg2 } = round1And2BitGoResponse;
204+
205+
// ROUND 2
206+
const userRound2Promise = enclavedExpressClient.roundEcdsaMPCv2KeyGen({
207+
source: 'user',
208+
encryptedData: userRound1Response.encryptedData,
209+
encryptedDataKey: userRound1Response.encryptedDataKey,
210+
round: 2,
211+
broadcastMessages: {
212+
bitgo: ecdsaUtils.formatBitgoBroadcastMessage(bitgoMsg1),
213+
counterParty: backupRound1Response.broadcastMessage,
214+
},
215+
});
216+
const backupRound2Promise = enclavedExpressClient.roundEcdsaMPCv2KeyGen({
217+
source: 'backup',
218+
encryptedData: backupRound1Response.encryptedData,
219+
encryptedDataKey: backupRound1Response.encryptedDataKey,
220+
round: 2,
221+
broadcastMessages: {
222+
bitgo: ecdsaUtils.formatBitgoBroadcastMessage(bitgoMsg1),
223+
counterParty: userRound1Response.broadcastMessage,
224+
},
225+
});
226+
const [userRound2Response, backupRound2Response] = await Promise.all([
227+
userRound2Promise,
228+
backupRound2Promise,
229+
]);
230+
if (!userRound2Response.p2pMessages?.bitgo) {
231+
throw new Error('Missing BitGo p2p message in user round 2 response');
232+
}
233+
if (!backupRound2Response.p2pMessages?.bitgo) {
234+
throw new Error('Missing BitGo p2p message in backup round 2 response');
235+
}
236+
237+
// ROUND 3
238+
const userRound3Promise = enclavedExpressClient.roundEcdsaMPCv2KeyGen({
239+
source: 'user',
240+
encryptedData: userRound2Response.encryptedData,
241+
encryptedDataKey: userRound2Response.encryptedDataKey,
242+
round: 3,
243+
p2pMessages: {
244+
bitgo: ecdsaUtils.formatP2PMessage(bitgoToUserMsg2),
245+
counterParty: backupRound2Response.p2pMessages?.counterParty,
246+
},
247+
});
248+
const backupRound3Promise = enclavedExpressClient.roundEcdsaMPCv2KeyGen({
249+
source: 'backup',
250+
encryptedData: backupRound2Response.encryptedData,
251+
encryptedDataKey: backupRound2Response.encryptedDataKey,
252+
round: 3,
253+
p2pMessages: {
254+
bitgo: ecdsaUtils.formatP2PMessage(bitgoToBackupMsg2),
255+
counterParty: userRound2Response.p2pMessages?.counterParty,
256+
},
257+
});
258+
const round3BitGoPromise = ecdsaUtils.sendKeyGenerationRound2(enterprise, sessionId, {
259+
p2pMessages: [
260+
userRound2Response.p2pMessages?.bitgo,
261+
backupRound2Response.p2pMessages?.bitgo,
262+
].filter((msg) => msg !== undefined),
263+
broadcastMessages: [],
264+
});
265+
const [userRound3Response, backupRound3Response, round3BitGoResponse] = await Promise.all([
266+
userRound3Promise,
267+
backupRound3Promise,
268+
round3BitGoPromise,
269+
]);
270+
const {
271+
sessionId: sessionIdRound3,
272+
bitgoToUserMsg3,
273+
bitgoToBackupMsg3,
274+
bitgoCommitment2: bitgoCommitment3,
275+
} = round3BitGoResponse;
276+
if (!userRound3Response.p2pMessages?.bitgo) {
277+
throw new Error('Missing BitGo p2p message in user round 3 response');
278+
}
279+
if (!backupRound3Response.p2pMessages?.bitgo) {
280+
throw new Error('Missing BitGo p2p message in backup round 3 response');
281+
}
282+
if (sessionId !== sessionIdRound3) {
283+
throw new Error('Round 1 and 2 Session IDs do not match');
284+
}
285+
286+
// ROUND 4
287+
const userRound4Promise = enclavedExpressClient.roundEcdsaMPCv2KeyGen({
288+
source: 'user',
289+
encryptedData: userRound3Response.encryptedData,
290+
encryptedDataKey: userRound3Response.encryptedDataKey,
291+
round: 4,
292+
p2pMessages: {
293+
bitgo: ecdsaUtils.formatP2PMessage(bitgoToUserMsg3, bitgoCommitment3),
294+
counterParty: backupRound3Response.p2pMessages?.counterParty,
295+
},
296+
});
297+
const backupRound4Promise = enclavedExpressClient.roundEcdsaMPCv2KeyGen({
298+
source: 'backup',
299+
encryptedData: backupRound3Response.encryptedData,
300+
encryptedDataKey: backupRound3Response.encryptedDataKey,
301+
round: 4,
302+
p2pMessages: {
303+
bitgo: ecdsaUtils.formatP2PMessage(bitgoToBackupMsg3, bitgoCommitment3),
304+
counterParty: userRound3Response.p2pMessages?.counterParty,
305+
},
306+
});
307+
const [userRound4Response, backupRound4Response] = await Promise.all([
308+
userRound4Promise,
309+
backupRound4Promise,
310+
]);
311+
if (!userRound4Response.broadcastMessage) {
312+
throw new Error('Missing broadcast message in user round 4 response');
313+
}
314+
if (!backupRound4Response.broadcastMessage) {
315+
throw new Error('Missing broadcast message in backup round 4 response');
316+
}
317+
318+
// FINALIZE
319+
const round4BitGoResponse = await ecdsaUtils.sendKeyGenerationRound3(enterprise, sessionId, {
320+
p2pMessages: [
321+
userRound3Response.p2pMessages?.bitgo,
322+
backupRound3Response.p2pMessages?.bitgo,
323+
].filter((msg) => msg !== undefined),
324+
broadcastMessages: [
325+
userRound4Response.broadcastMessage,
326+
backupRound4Response.broadcastMessage,
327+
].filter((msg) => msg !== undefined),
328+
});
329+
const {
330+
sessionId: sessionIdRound4,
331+
bitgoMsg4,
332+
commonKeychain: bitgoCommonKeychain,
333+
} = round4BitGoResponse;
334+
const userFinalizePromise = enclavedExpressClient.finalizeEcdsaMPCv2KeyGen({
335+
source: 'user',
336+
encryptedData: userRound4Response.encryptedData,
337+
encryptedDataKey: userRound4Response.encryptedDataKey,
338+
broadcastMessages: {
339+
bitgo: ecdsaUtils.formatBitgoBroadcastMessage(bitgoMsg4),
340+
counterParty: backupRound4Response.broadcastMessage,
341+
},
342+
bitgoCommonKeychain,
343+
});
344+
const backupFinalizePromise = enclavedExpressClient.finalizeEcdsaMPCv2KeyGen({
345+
source: 'backup',
346+
encryptedData: backupRound4Response.encryptedData,
347+
encryptedDataKey: backupRound4Response.encryptedDataKey,
348+
broadcastMessages: {
349+
bitgo: ecdsaUtils.formatBitgoBroadcastMessage(bitgoMsg4),
350+
counterParty: userRound4Response.broadcastMessage,
351+
},
352+
bitgoCommonKeychain,
353+
});
354+
const [userFinalizeResponse, backupFinalizeResponse] = await Promise.all([
355+
userFinalizePromise,
356+
backupFinalizePromise,
357+
]);
358+
if (sessionId !== sessionIdRound4) {
359+
throw new Error('Round 4 Session IDs do not match');
360+
}
361+
if (!userFinalizeResponse.commonKeychain) {
362+
throw new Error('Missing common keychain in user finalize response');
363+
}
364+
if (!backupFinalizeResponse.commonKeychain) {
365+
throw new Error('Missing common keychain in backup finalize response');
366+
}
367+
if (userFinalizeResponse.commonKeychain !== backupFinalizeResponse.commonKeychain) {
368+
throw new Error('User and backup common keychains do not match');
369+
}
370+
if (userFinalizeResponse.commonKeychain !== bitgoCommonKeychain) {
371+
throw new Error('User and BitGo common keychains do not match');
372+
}
373+
374+
// CREATE KEYCHAINS
375+
const userMpcKey = await baseCoin.keychains().add({
376+
commonKeychain: userFinalizeResponse.commonKeychain,
377+
source: 'user',
378+
type: 'tss',
379+
isMPCv2: true,
380+
});
381+
const backupMpcKey = await baseCoin.keychains().add({
382+
commonKeychain: backupFinalizeResponse.commonKeychain,
383+
source: 'backup',
384+
type: 'tss',
385+
isMPCv2: true,
386+
});
387+
const bitgoKeychain = await baseCoin.keychains().add({
388+
commonKeychain: bitgoCommonKeychain,
389+
source: 'bitgo',
390+
type: 'tss',
391+
isMPCv2: true,
392+
});
393+
walletParams.keys = [userMpcKey.id, backupMpcKey.id, bitgoKeychain.id];
394+
const keychains = {
395+
userKeychain: userMpcKey,
396+
backupKeychain: backupMpcKey,
397+
bitgoKeychain,
398+
};
399+
return { walletParams, keychains };
400+
}

0 commit comments

Comments
 (0)