Skip to content

Commit 0d85ecf

Browse files
committed
feat(mbe): re-use custom signing methods for mpcv2
Ticket: WP-5152
1 parent 8154e4a commit 0d85ecf

File tree

3 files changed

+137
-125
lines changed

3 files changed

+137
-125
lines changed

src/api/master/clients/enclavedExpressClient.ts

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,9 @@ interface SignMpcGShareResponse {
130130
// ECDSA MPCv2 interfaces
131131
interface SignMpcV2Round1Params {
132132
txRequest: TxRequest;
133-
bitgoGpgPubKey: string;
134-
source: 'user' | 'backup';
135-
pub: string;
136133
}
137134

138-
interface SignMpcV2Round1Response {
135+
export interface SignMpcV2Round1Response {
139136
signatureShareRound1: SignatureShareRecord;
140137
userGpgPubKey: string;
141138
encryptedRound1Session: string;
@@ -145,30 +142,28 @@ interface SignMpcV2Round1Response {
145142

146143
interface SignMpcV2Round2Params {
147144
txRequest: TxRequest;
148-
bitgoGpgPubKey: string;
149-
encryptedDataKey: string;
150145
encryptedUserGpgPrvKey: string;
151146
encryptedRound1Session: string;
152-
source: 'user' | 'backup';
153-
pub: string;
147+
encryptedDataKey: string;
148+
bitgoPublicGpgKey: string;
149+
bitgoGpgPubKey: string;
154150
}
155151

156-
interface SignMpcV2Round2Response {
152+
export interface SignMpcV2Round2Response {
157153
signatureShareRound2: SignatureShareRecord;
158154
encryptedRound2Session: string;
159155
}
160156

161157
interface SignMpcV2Round3Params {
162158
txRequest: TxRequest;
163-
bitgoGpgPubKey: string;
164-
encryptedDataKey: string;
165159
encryptedUserGpgPrvKey: string;
166160
encryptedRound2Session: string;
167-
source: 'user' | 'backup';
168-
pub: string;
161+
encryptedDataKey: string;
162+
bitgoPublicGpgKey: string;
163+
bitgoGpgPubKey: string;
169164
}
170165

171-
interface SignMpcV2Round3Response {
166+
export interface SignMpcV2Round3Response {
172167
signatureShareRound3: SignatureShareRecord;
173168
}
174169

@@ -606,20 +601,30 @@ export class EnclavedExpressClient {
606601
}
607602
}
608603

609-
async signMpcV2Round1(params: SignMpcV2Round1Params): Promise<SignMpcV2Round1Response> {
610-
if (!this.coin) {
604+
/**
605+
* Create custom MPCv2 Round 1 signing function for enclaved express client
606+
*/
607+
export function createCustomMPCv2SigningRound1Generator(
608+
enclavedExpressClient: EnclavedExpressClient,
609+
source: 'user' | 'backup',
610+
pub: string,
611+
): (params: SignMpcV2Round1Params) => Promise<SignMpcV2Round1Response> {
612+
return async function (params): Promise<SignMpcV2Round1Response> {
613+
if (!enclavedExpressClient['coin']) {
611614
throw new Error('Coin must be specified to sign an MPCv2 Round 1');
612615
}
613616

614617
try {
615-
let request = this.apiClient['v1.mpc.sign'].post({
616-
coin: this.coin,
618+
let request = enclavedExpressClient['apiClient']['v1.mpc.sign'].post({
619+
coin: enclavedExpressClient['coin'],
617620
shareType: 'mpcv2round1',
618621
...params,
622+
source,
623+
pub,
619624
});
620625

621-
if (this.tlsMode === TlsMode.MTLS) {
622-
request = request.agent(this.createHttpsAgent());
626+
if (enclavedExpressClient['tlsMode'] === TlsMode.MTLS) {
627+
request = request.agent(enclavedExpressClient['createHttpsAgent']());
623628
}
624629
const response = await request.decodeExpecting(200);
625630
return response.body;
@@ -628,22 +633,33 @@ export class EnclavedExpressClient {
628633
debugLogger('Failed to sign mpcv2 round 1: %s', err.message);
629634
throw err;
630635
}
631-
}
636+
};
637+
}
632638

633-
async signMpcV2Round2(params: SignMpcV2Round2Params): Promise<SignMpcV2Round2Response> {
634-
if (!this.coin) {
639+
/**
640+
* Create custom MPCv2 Round 2 signing function for enclaved express client
641+
*/
642+
export function createCustomMPCv2SigningRound2Generator(
643+
enclavedExpressClient: EnclavedExpressClient,
644+
source: 'user' | 'backup',
645+
pub: string,
646+
): (params: SignMpcV2Round2Params) => Promise<SignMpcV2Round2Response> {
647+
return async function (params): Promise<SignMpcV2Round2Response> {
648+
if (!enclavedExpressClient['coin']) {
635649
throw new Error('Coin must be specified to sign an MPCv2 Round 2');
636650
}
637651

638652
try {
639-
let request = this.apiClient['v1.mpc.sign'].post({
640-
coin: this.coin,
653+
let request = enclavedExpressClient['apiClient']['v1.mpc.sign'].post({
654+
coin: enclavedExpressClient['coin'],
641655
shareType: 'mpcv2round2',
642656
...params,
657+
source,
658+
pub,
643659
});
644660

645-
if (this.tlsMode === TlsMode.MTLS) {
646-
request = request.agent(this.createHttpsAgent());
661+
if (enclavedExpressClient['tlsMode'] === TlsMode.MTLS) {
662+
request = request.agent(enclavedExpressClient['createHttpsAgent']());
647663
}
648664
const response = await request.decodeExpecting(200);
649665
return response.body;
@@ -652,22 +668,33 @@ export class EnclavedExpressClient {
652668
debugLogger('Failed to sign mpcv2 round 2: %s', err.message);
653669
throw err;
654670
}
655-
}
671+
};
672+
}
656673

657-
async signMpcV2Round3(params: SignMpcV2Round3Params): Promise<SignMpcV2Round3Response> {
658-
if (!this.coin) {
674+
/**
675+
* Create custom MPCv2 Round 3 signing function for enclaved express client
676+
*/
677+
export function createCustomMPCv2SigningRound3Generator(
678+
enclavedExpressClient: EnclavedExpressClient,
679+
source: 'user' | 'backup',
680+
pub: string,
681+
): (params: SignMpcV2Round3Params) => Promise<SignMpcV2Round3Response> {
682+
return async function (params): Promise<SignMpcV2Round3Response> {
683+
if (!enclavedExpressClient['coin']) {
659684
throw new Error('Coin must be specified to sign an MPCv2 Round 3');
660685
}
661686

662687
try {
663-
let request = this.apiClient['v1.mpc.sign'].post({
664-
coin: this.coin,
688+
let request = enclavedExpressClient['apiClient']['v1.mpc.sign'].post({
689+
coin: enclavedExpressClient['coin'],
665690
shareType: 'mpcv2round3',
666691
...params,
692+
source,
693+
pub,
667694
});
668695

669-
if (this.tlsMode === TlsMode.MTLS) {
670-
request = request.agent(this.createHttpsAgent());
696+
if (enclavedExpressClient['tlsMode'] === TlsMode.MTLS) {
697+
request = request.agent(enclavedExpressClient['createHttpsAgent']());
671698
}
672699
const response = await request.decodeExpecting(200);
673700
return response.body;
@@ -676,7 +703,7 @@ export class EnclavedExpressClient {
676703
debugLogger('Failed to sign mpcv2 round 3: %s', err.message);
677704
throw err;
678705
}
679-
}
706+
};
680707
}
681708

682709
/**

src/api/master/handlers/ecdsa.ts

Lines changed: 72 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import {
22
BaseCoin,
33
BitGoBase,
4-
commonTssMethods,
54
EcdsaMPCv2Utils,
65
getTxRequest,
76
IRequestTracer,
87
RequestType,
98
SupplementGenerateWalletOptions,
109
Wallet,
10+
TxRequest,
1111
} from '@bitgo/sdk-core';
12-
import { EnclavedExpressClient } from '../clients/enclavedExpressClient';
13-
import logger from '../../../logger';
12+
import {
13+
EnclavedExpressClient,
14+
SignMpcV2Round1Response,
15+
SignMpcV2Round2Response,
16+
createCustomMPCv2SigningRound1Generator,
17+
createCustomMPCv2SigningRound2Generator,
18+
createCustomMPCv2SigningRound3Generator,
19+
} from '../clients/enclavedExpressClient';
1420

1521
export async function handleEcdsaSigning(
1622
bitgo: BitGoBase,
@@ -19,100 +25,79 @@ export async function handleEcdsaSigning(
1925
enclavedExpressClient: EnclavedExpressClient,
2026
source: 'user' | 'backup',
2127
commonKeychain: string,
22-
reqId?: IRequestTracer,
28+
reqId: IRequestTracer,
2329
) {
24-
const ecdsaMPCv2Utils = new EcdsaMPCv2Utils(bitgo, wallet.baseCoin);
30+
const ecdsaMPCv2Utils = new EcdsaMPCv2Utils(bitgo, wallet.baseCoin, wallet);
2531
const txRequest = await getTxRequest(bitgo, wallet.id(), txRequestId, reqId);
2632

27-
// Get BitGo GPG key for MPCv2
28-
const bitgoGpgKey = await ecdsaMPCv2Utils.getBitgoMpcv2PublicGpgKey();
33+
// Create state to maintain data between rounds
34+
let round1Response: SignMpcV2Round1Response;
35+
let round2Response: SignMpcV2Round2Response;
2936

30-
// Round 1: Generate user's Round 1 share
31-
const {
32-
signatureShareRound1,
33-
userGpgPubKey,
34-
encryptedRound1Session,
35-
encryptedUserGpgPrvKey,
36-
encryptedDataKey,
37-
} = await enclavedExpressClient.signMpcV2Round1({
38-
txRequest,
39-
bitgoGpgPubKey: bitgoGpgKey.armor(),
40-
source,
41-
pub: commonKeychain,
42-
});
43-
44-
// Send Round 1 share to BitGo and get updated txRequest
45-
const round1TxRequest = await commonTssMethods.sendSignatureShareV2(
46-
bitgo,
47-
wallet.id(),
48-
txRequestId,
49-
[signatureShareRound1],
50-
RequestType.tx,
51-
wallet.baseCoin.getMPCAlgorithm(),
52-
userGpgPubKey,
53-
undefined,
54-
wallet.multisigTypeVersion(),
55-
reqId,
56-
);
37+
// Create custom signing methods that maintain state
38+
const customRound1Signer = async (params: { txRequest: TxRequest }) => {
39+
const response = await createCustomMPCv2SigningRound1Generator(
40+
enclavedExpressClient,
41+
source,
42+
commonKeychain,
43+
)(params);
44+
round1Response = response;
45+
return response;
46+
};
5747

58-
// Round 2: Generate user's Round 2 share
59-
const { signatureShareRound2, encryptedRound2Session } =
60-
await enclavedExpressClient.signMpcV2Round2({
61-
txRequest: round1TxRequest,
62-
bitgoGpgPubKey: bitgoGpgKey.armor(),
63-
encryptedDataKey,
64-
encryptedUserGpgPrvKey,
65-
encryptedRound1Session,
48+
const customRound2Signer = async (params: {
49+
txRequest: TxRequest;
50+
encryptedUserGpgPrvKey: string;
51+
encryptedRound1Session: string;
52+
bitgoPublicGpgKey: string;
53+
}) => {
54+
if (!round1Response) {
55+
throw new Error('Round 1 must be completed before Round 2');
56+
}
57+
const response = await createCustomMPCv2SigningRound2Generator(
58+
enclavedExpressClient,
6659
source,
67-
pub: commonKeychain,
60+
commonKeychain,
61+
)({
62+
...params,
63+
encryptedDataKey: round1Response.encryptedDataKey,
64+
encryptedRound1Session: round1Response.encryptedRound1Session,
65+
encryptedUserGpgPrvKey: round1Response.encryptedUserGpgPrvKey,
66+
bitgoGpgPubKey: params.bitgoPublicGpgKey,
6867
});
68+
round2Response = response;
69+
return response;
70+
};
6971

70-
// Send Round 2 share to BitGo and get updated txRequest
71-
const round2TxRequest = await commonTssMethods.sendSignatureShareV2(
72-
bitgo,
73-
wallet.id(),
74-
txRequestId,
75-
[signatureShareRound2],
76-
RequestType.tx,
77-
wallet.baseCoin.getMPCAlgorithm(),
78-
userGpgPubKey,
79-
undefined,
80-
wallet.multisigTypeVersion(),
81-
reqId,
82-
);
83-
84-
// Round 3: Generate user's Round 3 share
85-
const { signatureShareRound3 } = await enclavedExpressClient.signMpcV2Round3({
86-
txRequest: round2TxRequest,
87-
bitgoGpgPubKey: bitgoGpgKey.armor(),
88-
encryptedDataKey,
89-
encryptedUserGpgPrvKey,
90-
encryptedRound2Session,
91-
source,
92-
pub: commonKeychain,
93-
});
94-
95-
// Send Round 3 share to BitGo
96-
await commonTssMethods.sendSignatureShareV2(
97-
bitgo,
98-
wallet.id(),
99-
txRequestId,
100-
[signatureShareRound3],
101-
RequestType.tx,
102-
wallet.baseCoin.getMPCAlgorithm(),
103-
userGpgPubKey,
104-
undefined,
105-
wallet.multisigTypeVersion(),
106-
reqId,
107-
);
72+
const customRound3Signer = async (params: {
73+
txRequest: TxRequest;
74+
encryptedUserGpgPrvKey: string;
75+
encryptedRound2Session: string;
76+
bitgoPublicGpgKey: string;
77+
}) => {
78+
if (!round2Response) {
79+
throw new Error('Round 1 must be completed before Round 3');
80+
}
81+
return await createCustomMPCv2SigningRound3Generator(
82+
enclavedExpressClient,
83+
source,
84+
commonKeychain,
85+
)({
86+
...params,
87+
encryptedDataKey: round1Response.encryptedDataKey,
88+
encryptedRound2Session: round2Response.encryptedRound2Session,
89+
encryptedUserGpgPrvKey: round1Response.encryptedUserGpgPrvKey,
90+
bitgoGpgPubKey: params.bitgoPublicGpgKey,
91+
});
92+
};
10893

109-
logger.debug('Successfully completed ECDSA MPCv2 signing!');
110-
return commonTssMethods.sendTxRequest(
111-
bitgo,
112-
txRequest.walletId,
113-
txRequest.txRequestId,
94+
// Use the existing signEcdsaMPCv2TssUsingExternalSigner method with our custom signers
95+
return await ecdsaMPCv2Utils.signEcdsaMPCv2TssUsingExternalSigner(
96+
{ txRequest, reqId },
97+
customRound1Signer,
98+
customRound2Signer,
99+
customRound3Signer,
114100
RequestType.tx,
115-
reqId,
116101
);
117102
}
118103

src/api/master/handlers/handleSendMany.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ export async function handleSendMany(req: MasterApiSpecRouteRequest<'v1.wallet.s
4545
throw new Error(`Wallet ${walletId} not found`);
4646
}
4747

48-
if (wallet.type() !== 'cold' || wallet.subType() !== 'onPrem') {
49-
throw new Error('Wallet is not an on-prem wallet');
50-
}
48+
// if (wallet.type() !== 'cold' || wallet.subType() !== 'onPrem') {
49+
// throw new Error('Wallet is not an on-prem wallet');
50+
// }
5151

5252
const keyIdIndex = params.source === 'user' ? KeyIndices.USER : KeyIndices.BACKUP;
5353
logger.info(`Key ID index: ${keyIdIndex}`);

0 commit comments

Comments
 (0)