Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 111 additions & 21 deletions modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
CustomCommitmentGeneratingFunction,
CustomGShareGeneratingFunction,
CustomKShareGeneratingFunction,
CustomEddsaMPCv2SigningRound1GeneratingFunction,
CustomEddsaMPCv2SigningRound2GeneratingFunction,
CustomEddsaMPCv2SigningRound3GeneratingFunction,
CustomMPCv2SigningRound1GeneratingFunction,
CustomMPCv2SigningRound2GeneratingFunction,
CustomMPCv2SigningRound3GeneratingFunction,
Expand All @@ -17,6 +20,7 @@ import {
CustomSShareGeneratingFunction,
EcdsaMPCv2Utils,
EcdsaUtils,
EddsaMPCv2Utils,
EddsaUtils,
EncryptedSignerShareRecord,
encryptRsaWithAesGcm,
Expand Down Expand Up @@ -455,18 +459,41 @@ export async function handleV2GenerateShareTSS(
req.body.walletPassphrase = walletPw;
try {
if (coin.getMPCAlgorithm() === MPCType.EDDSA) {
const eddsaUtils = new EddsaUtils(bitgo, coin);
switch (req.decoded.sharetype) {
case ShareType.Commitment:
return await eddsaUtils.createCommitmentShareFromTxRequest(req.body);
case ShareType.R:
return await eddsaUtils.createRShareFromTxRequest(req.body);
case ShareType.G:
return await eddsaUtils.createGShareFromTxRequest(req.body);
default:
throw new Error(
`Share type ${req.decoded.sharetype} not supported, only commitment, G and R share generation is supported.`
);
const isMPCv2 =
[
ShareType.EddsaMPCv2Round1.toString(),
ShareType.EddsaMPCv2Round2.toString(),
ShareType.EddsaMPCv2Round3.toString(),
].includes(req.decoded.sharetype) || req.decoded.sharetype.startsWith('EddsaMPCv2');

if (isMPCv2) {
const eddsaMPCv2Utils = new EddsaMPCv2Utils(bitgo, coin);
switch (req.decoded.sharetype) {
case ShareType.EddsaMPCv2Round1:
return await eddsaMPCv2Utils.createOfflineRound1Share(req.body);
case ShareType.EddsaMPCv2Round2:
return await eddsaMPCv2Utils.createOfflineRound2Share(req.body);
case ShareType.EddsaMPCv2Round3:
return await eddsaMPCv2Utils.createOfflineRound3Share(req.body);
default:
throw new Error(
`Share type ${req.decoded.sharetype} not supported for EdDSA MPCv2, only EddsaMPCv2Round1, EddsaMPCv2Round2 and EddsaMPCv2Round3 is supported.`
);
}
Comment thread
vibhavgo marked this conversation as resolved.
} else {
const eddsaUtils = new EddsaUtils(bitgo, coin);
switch (req.decoded.sharetype) {
case ShareType.Commitment:
return await eddsaUtils.createCommitmentShareFromTxRequest(req.body);
case ShareType.R:
return await eddsaUtils.createRShareFromTxRequest(req.body);
case ShareType.G:
return await eddsaUtils.createGShareFromTxRequest(req.body);
default:
throw new Error(
`Share type ${req.decoded.sharetype} not supported, only commitment, G and R share generation is supported.`
);
}
}
} else if (coin.getMPCAlgorithm() === MPCType.ECDSA) {
const isMPCv2 = [
Expand Down Expand Up @@ -895,15 +922,33 @@ function createTSSSendParams(req: express.Request, wallet: Wallet) {
if (req.config?.externalSignerUrl !== undefined) {
const coin = req.bitgo.coin(req.params.coin);
if (coin.getMPCAlgorithm() === MPCType.EDDSA) {
return {
...req.body,
customCommitmentGeneratingFunction: createCustomCommitmentGenerator(
req.config.externalSignerUrl,
req.params.coin
),
customRShareGeneratingFunction: createCustomRShareGenerator(req.config.externalSignerUrl, req.params.coin),
customGShareGeneratingFunction: createCustomGShareGenerator(req.config.externalSignerUrl, req.params.coin),
};
if (wallet._wallet.multisigTypeVersion === 'MPCv2') {
return {
...req.body,
customEddsaMPCv2SigningRound1GenerationFunction: createCustomEddsaMPCv2SigningRound1Generator(
req.config.externalSignerUrl,
req.params.coin
),
customEddsaMPCv2SigningRound2GenerationFunction: createCustomEddsaMPCv2SigningRound2Generator(
req.config.externalSignerUrl,
req.params.coin
),
customEddsaMPCv2SigningRound3GenerationFunction: createCustomEddsaMPCv2SigningRound3Generator(
req.config.externalSignerUrl,
req.params.coin
),
};
} else {
return {
...req.body,
customCommitmentGeneratingFunction: createCustomCommitmentGenerator(
req.config.externalSignerUrl,
req.params.coin
),
customRShareGeneratingFunction: createCustomRShareGenerator(req.config.externalSignerUrl, req.params.coin),
customGShareGeneratingFunction: createCustomGShareGenerator(req.config.externalSignerUrl, req.params.coin),
};
}
} else if (coin.getMPCAlgorithm() === MPCType.ECDSA) {
if (wallet._wallet.multisigTypeVersion === 'MPCv2') {
return {
Expand Down Expand Up @@ -1776,6 +1821,51 @@ export function createCustomMPCv2SigningRound3Generator(
};
}

export function createCustomEddsaMPCv2SigningRound1Generator(
externalSignerUrl: string,
coin: string
): CustomEddsaMPCv2SigningRound1GeneratingFunction {
return async function (params) {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/EddsaMPCv2Round1`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}

export function createCustomEddsaMPCv2SigningRound2Generator(
externalSignerUrl: string,
coin: string
): CustomEddsaMPCv2SigningRound2GeneratingFunction {
return async function (params) {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/EddsaMPCv2Round2`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}

export function createCustomEddsaMPCv2SigningRound3Generator(
externalSignerUrl: string,
coin: string
): CustomEddsaMPCv2SigningRound3GeneratingFunction {
return async function (params) {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/EddsaMPCv2Round3`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}

export function setupAPIRoutes(app: express.Application, config: Config): void {
// When adding new routes to BitGo Express make sure that you also add the exact same routes to the server. Since
// some customers were confused when calling a BitGo Express route on the BitGo server, we now handle all BitGo
Expand Down
23 changes: 17 additions & 6 deletions modules/express/src/typedRoutes/api/v2/generateShareTSS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const GenerateShareTSSParams = {
/**
* The type of share to generate. Valid values depend on the MPC algorithm:
* - EDDSA: 'commitment', 'R', 'G'
* - EDDSA MPCv2: 'EddsaMPCv2Round1', 'EddsaMPCv2Round2', 'EddsaMPCv2Round3'
* - ECDSA: 'PaillierModulus', 'K', 'MuDelta', 'S'
* - ECDSA MPCv2: 'MPCv2Round1', 'MPCv2Round2', 'MPCv2Round3'
*/
Expand Down Expand Up @@ -471,8 +472,8 @@ export const EcdsaMuDeltaShareResponse = t.type({
/** ECDSA S share generation response (Step 3) with final signature components */
export const EcdsaSShareResponse = SShare;

/** ECDSA MPCv2 Round 1 response with initial signature share and encrypted session state */
export const EcdsaMPCv2Round1Response = t.type({
/** MPCv2 Round 1 response with initial signature share and encrypted session state (shared by ECDSA and EdDSA) */
export const MPCv2Round1Response = t.type({
/** First round signature share for MPCv2 protocol */
signatureShareRound1: SignatureShareRecord,
/** User's GPG public key for Round 2 communication */
Expand All @@ -483,20 +484,27 @@ export const EcdsaMPCv2Round1Response = t.type({
encryptedUserGpgPrvKey: t.string,
});

/** ECDSA MPCv2 Round 2 response with second signature share and session state */
export const EcdsaMPCv2Round2Response = t.type({
/** MPCv2 Round 2 response with second signature share and session state (shared by ECDSA and EdDSA) */
export const MPCv2Round2Response = t.type({
/** Second round signature share for MPCv2 protocol */
signatureShareRound2: SignatureShareRecord,
/** Encrypted session state to continue to Round 3 */
encryptedRound2Session: t.string,
});

/** ECDSA MPCv2 Round 3 response with final signature share */
export const EcdsaMPCv2Round3Response = t.type({
/** MPCv2 Round 3 response with final signature share (shared by ECDSA and EdDSA) */
export const MPCv2Round3Response = t.type({
/** Signature share for round 3 (final signature) */
signatureShareRound3: SignatureShareRecord,
});

/** @deprecated Use MPCv2Round1Response */
export const EcdsaMPCv2Round1Response = MPCv2Round1Response;
/** @deprecated Use MPCv2Round2Response */
export const EcdsaMPCv2Round2Response = MPCv2Round2Response;
/** @deprecated Use MPCv2Round3Response */
export const EcdsaMPCv2Round3Response = MPCv2Round3Response;

/** Union of all TSS share responses - EDDSA (Commitment/R/G), ECDSA (PaillierModulus/K/MuDelta/S), or MPCv2 (Round1/2/3) */
export const GenerateShareTSSResponse = {
/** Successfully generated TSS share (type depends on MPC algorithm and sharetype parameter) */
Expand All @@ -511,6 +519,9 @@ export const GenerateShareTSSResponse = {
EcdsaMPCv2Round1Response, // ECDSA MPCv2 Round 1
EcdsaMPCv2Round2Response, // ECDSA MPCv2 Round 2
EcdsaMPCv2Round3Response, // ECDSA MPCv2 Round 3
MPCv2Round1Response, // EdDSA MPCv2 Round 1
MPCv2Round2Response, // EdDSA MPCv2 Round 2
MPCv2Round3Response, // EdDSA MPCv2 Round 3
]),
/** Invalid request parameters, missing configuration, or share generation validation failure */
400: BitgoExpressError,
Expand Down
Loading
Loading