Skip to content

Commit

Permalink
feat(express): support ECDSA TSS in external signer
Browse files Browse the repository at this point in the history
Ticket: HSM-110
  • Loading branch information
islamaminBitGo committed Jul 20, 2023
1 parent c74e335 commit 41b7fd8
Show file tree
Hide file tree
Showing 12 changed files with 2,404 additions and 88 deletions.
3 changes: 3 additions & 0 deletions modules/express/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
},
"devDependencies": {
"@bitgo/sdk-test": "^1.2.27",
"@bitgo/sdk-lib-mpc": "^8.5.0",
"@types/keccak": "^3.0.1",
"keccak": "^3.0.2",
"@types/argparse": "^1.0.36",
"@types/bluebird": "^3.5.25",
"@types/body-parser": "^1.17.0",
Expand Down
144 changes: 124 additions & 20 deletions modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import {
EddsaUtils,
EcdsaUtils,
CustomGShareGeneratingFunction,
CustomRShareGeneratingFunction,
UnsupportedCoinError,
Expand All @@ -11,6 +12,13 @@ import {
CustomCommitmentGeneratingFunction,
CommitmentShareRecord,
EncryptedSignerShareRecord,
CustomPaillierModulustGeneratingFunction,
CustomKShareGeneratingFunction,
CustomMuDeltaShareGeneratingFunction,
CustomSShareGeneratingFunction,
TssEcdsaStep1ReturnMessage,
TssEcdsaStep2ReturnMessage,
SShare,
} from '@bitgo/sdk-core';
import { BitGo, BitGoOptions, Coin, CustomSigningFunction, SignedTransaction, SignedTransactionRequest } from 'bitgo';
import * as bodyParser from 'body-parser';
Expand Down Expand Up @@ -393,7 +401,7 @@ function decryptPrivKey(bg: BitGo, encryptedPrivKey: string, walletPw: string):
}

export async function handleV2GenerateShareTSS(req: express.Request): Promise<any> {
const walletId = req.body.txRequest.walletId;
const walletId = req.body.txRequest ? req.body.txRequest.walletId : req.body.tssParams.txRequest.walletId;
if (!walletId) {
throw new Error('Missing required field: walletId');
}
Expand All @@ -409,19 +417,41 @@ export async function handleV2GenerateShareTSS(req: express.Request): Promise<an
const bitgo = req.bitgo;
const privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
const coin = bitgo.coin(req.params.coin);
const eddUtils = new EddsaUtils(bitgo, coin);
req.body.prv = privKey;
req.body.walletPassphrase = walletPw;
try {
switch (req.params.sharetype) {
case 'commitment':
return await eddUtils.createCommitmentShareFromTxRequest(req.body);
case 'R':
return await eddUtils.createRShareFromTxRequest(req.body);
case 'G':
return await eddUtils.createGShareFromTxRequest(req.body);
default:
throw new Error('Share type not supported, only commitment, G and R share generation is supported.');
if (coin.getMPCAlgorithm() === 'eddsa') {
const eddsaUtils = new EddsaUtils(bitgo, coin);
switch (req.params.sharetype) {
case 'commitment':
return await eddsaUtils.createCommitmentShareFromTxRequest(req.body);
case 'R':
return await eddsaUtils.createRShareFromTxRequest(req.body);
case 'G':
return await eddsaUtils.createGShareFromTxRequest(req.body);
default:
throw new Error(
`Share type ${req.params.sharetype} not supported, only commitment, G and R share generation is supported.`
);
}
} else if (coin.getMPCAlgorithm() === 'ecdsa') {
const ecdsaUtils = new EcdsaUtils(bitgo, coin);
switch (req.params.sharetype) {
case 'PaillierModulus':
return ecdsaUtils.createOfflineSignerPaillierModulus(req.body);
case 'K':
return await ecdsaUtils.createOfflineKShare(req.body);
case 'MuDelta':
return await ecdsaUtils.createOfflineMuDeltaShare(req.body);
case 'S':
return await ecdsaUtils.createOfflineSShare(req.body);
default:
throw new Error(
`Share type ${req.params.sharetype} not supported, only PaillierModulus, K, MUDelta, and S share generation is supported.`
);
}
} else {
throw new Error(`MPC Algorithm ${coin.getMPCAlgorithm()} is not supported.`);
}
} catch (error) {
console.error('error while signing wallet transaction ', error);
Expand Down Expand Up @@ -792,15 +822,34 @@ function createSendParams(req: express.Request) {

function createTSSSendParams(req: express.Request) {
if (req.config.externalSignerUrl !== undefined) {
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),
};
const coin = req.bitgo.coin(req.params.coin);
if (coin.getMPCAlgorithm() === '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),
};
} else if (coin.getMPCAlgorithm() === 'ecdsa') {
return {
...req.body,
customPaillierModulusGeneratingFunction: createCustomPaillierModulusGenerator(
req.config.externalSignerUrl,
req.params.coin
),
customKShareGeneratingFunction: createCustomKShareGenerator(req.config.externalSignerUrl, req.params.coin),
customMuDeltaShareGeneratingFunction: createCustomMuDeltaShareGenerator(
req.config.externalSignerUrl,
req.params.coin
),
customSShareGeneratingFunction: createCustomSShareGenerator(req.config.externalSignerUrl, req.params.coin),
};
} else {
throw new Error(`MPC Algorithm ${coin.getMPCAlgorithm()} is not supported.`);
}
} else {
return req.body;
}
Expand Down Expand Up @@ -1101,6 +1150,61 @@ export function createCustomSigningFunction(externalSignerUrl: string): CustomSi
return signedTx;
};
}
export function createCustomPaillierModulusGenerator(
externalSignerUrl: string,
coin: string
): CustomPaillierModulustGeneratingFunction {
return async function (params): Promise<{
userPaillierModulus: string;
}> {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/PaillierModulus`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}

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

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

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

export function createCustomCommitmentGenerator(
externalSignerUrl: string,
Expand Down
Loading

0 comments on commit 41b7fd8

Please sign in to comment.