Skip to content

Commit

Permalink
Support ECDSA TSS in external signer
Browse files Browse the repository at this point in the history
  • Loading branch information
islamaminBitGo committed Jul 19, 2023
1 parent af99372 commit d3abd9a
Show file tree
Hide file tree
Showing 9 changed files with 584 additions and 85 deletions.
139 changes: 121 additions & 18 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 @@ -410,18 +418,41 @@ export async function handleV2GenerateShareTSS(req: express.Request): Promise<an
const privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
const coin = bitgo.coin(req.params.coin);
const eddUtils = new EddsaUtils(bitgo, coin);
const ecUtils = new EcdsaUtils(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') {
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 ${req.params.sharetype} not supported, only commitment, G and R share generation is supported.`
);
}
} else {
if (coin.getMPCAlgorithm() !== 'ecdsa') {
console.error(`MPC Algorithm ${coin.getMPCAlgorithm()} is not supported.`);
}
switch (req.params.sharetype) {
case 'PaillierModulus':
return ecUtils.createOfflineSignerPaillierModulus(req.body);
case 'K':
return await ecUtils.createOfflineKShare(req.body);
case 'MuDelta':
return await ecUtils.createOfflineMuDeltaShare(req.body);
case 'S':
return await ecUtils.createOfflineSShare(req.body);
default:
throw new Error(
`Share type ${req.params.sharetype} not supported, only PaillierModulus, K, MUDelta, and S share generation is supported.`
);
}
}
} catch (error) {
console.error('error while signing wallet transaction ', error);
Expand Down Expand Up @@ -792,15 +823,32 @@ 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 {
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 {
return req.body;
}
Expand Down Expand Up @@ -1101,6 +1149,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
19 changes: 19 additions & 0 deletions modules/sdk-core/src/bitgo/tss/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { EcdsaTypes } from '@bitgo/sdk-lib-mpc';
import { KShare, OShare, WShare } from './ecdsa/types';
import { MuDShare } from './ecdsa/ecdsa';

export enum ShareKeyPosition {
USER = 1,
Expand All @@ -9,3 +11,20 @@ export enum ShareKeyPosition {
export type TxRequestChallengeResponse = EcdsaTypes.SerializedEcdsaChallenges & {
n: string;
};

export type TssEcdsaStep1ReturnMessage = {
privateShareProof: string;
vssProof?: string;
userPublicGpgKey: string;
publicShare: string;
encryptedSignerOffsetShare: string;
kShare: KShare;
// wShare could be encrypted. If it is encrypted, it will be a string, otherwise it will be a WShare.
wShare: WShare | string;
};

export type TssEcdsaStep2ReturnMessage = {
muDShare: MuDShare;
// oShare could be encrypted. If it is encrypted, it will be a string, otherwise it will be an OShare.
oShare: OShare | string;
};
29 changes: 28 additions & 1 deletion modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ import {
CommitmentShareRecord,
EncryptedSignerShareRecord,
CustomCommitmentGeneratingFunction,
TSSParamsForMessage,
RequestType,
CustomPaillierModulustGeneratingFunction,
CustomKShareGeneratingFunction,
CustomMuDeltaShareGeneratingFunction,
CustomSShareGeneratingFunction,
} from './baseTypes';
import { GShare, SignShare } from '../../../account-lib/mpc/tss';

Expand Down Expand Up @@ -117,7 +123,7 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
* @param {CustomGShareGeneratingFunction} externalSignerGShareGenerator a function that creates G shares in the EdDSA TSS flow
* @returns {Promise<TxRequest>} - a signed tx request
*/
signUsingExternalSigner(
signEddsaTssUsingExternalSigner(
txRequest: string | TxRequest,
externalSignerCommitmentGenerator: CustomCommitmentGeneratingFunction,
externalSignerRShareGenerator: CustomRShareGeneratingFunction,
Expand All @@ -126,6 +132,27 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
throw new Error('Method not implemented.');
}

/**
* Signs a transaction using TSS for ECDSA and through utilization of custom share generators
*
* @param {params: TSSParams | TSSParamsForMessage} params - params object that represents parameters to sign a transaction or a message.
* @param {RequestType} requestType - the type of the request to sign (transaction or message).
* @param {CustomPaillierModulustGeneratingFunction} externalSignerPaillierModulusGenerator a function that creates Paillier Modulus shares in the ECDSA TSS flow.
* @param {CustomKShareGeneratingFunction} externalSignerKShareGenerator a function that creates K shares in the ECDSA TSS flow.
* @param {CustomMuDeltaShareGeneratingFunction} externalSignerMuDeltaShareGenerator a function that creates Mu and Delta shares in the ECDSA TSS flow.
* @param {CustomSShareGeneratingFunction} externalSignerSShareGenerator a function that creates S shares in the ECDSA TSS flow.
*/
signEcdsaTssUsingExternalSigner(
params: TSSParams | TSSParamsForMessage,
requestType: RequestType,
externalSignerPaillierModulusGenerator: CustomPaillierModulustGeneratingFunction,
externalSignerKShareGenerator: CustomKShareGeneratingFunction,
externalSignerMuDeltaShareGenerator: CustomMuDeltaShareGeneratingFunction,
externalSignerSShareGenerator: CustomSShareGeneratingFunction
): Promise<TxRequest> {
throw new Error('Method not implemented.');
}

/**
* Create an Commitment (User to BitGo) share from an unsigned transaction and private user signing material
* EDDSA only
Expand Down
47 changes: 46 additions & 1 deletion modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { ApiVersion, Memo, WalletType } from '../../wallet';
import { EDDSA, GShare, SignShare } from '../../../account-lib/mpc/tss';
import { KeyShare } from './ecdsa';
import { Hash } from 'crypto';
import { EcdsaTypes } from '@bitgo/sdk-lib-mpc';
import { TssEcdsaStep1ReturnMessage, TssEcdsaStep2ReturnMessage, TxRequestChallengeResponse } from '../../tss/types';
import { AShare, DShare, SShare } from '../../tss/ecdsa/types';

export type TxRequestVersion = 'full' | 'lite';
export interface HopParams {
Expand All @@ -32,6 +35,40 @@ export interface TokenEnablement {
name: string;
address?: string; // Some chains like Solana require tokens to be enabled for specific address. If absent, we will enable it for the wallet's root address
}
export interface CustomPaillierModulustGeneratingFunction {
(params: { txRequest: TxRequest }): Promise<{
userPaillierModulus: string;
}>;
}

export interface CustomKShareGeneratingFunction {
(params: {
tssParams: TSSParams | TSSParamsForMessage;
challenges: {
enterpriseChallenge: EcdsaTypes.SerializedEcdsaChallenges;
bitgoChallenge: TxRequestChallengeResponse;
};
requestType: RequestType;
}): Promise<TssEcdsaStep1ReturnMessage>;
}

export interface CustomMuDeltaShareGeneratingFunction {
(params: {
txRequest: TxRequest;
aShareFromBitgo: Omit<AShare, 'ntilde' | 'h1' | 'h2'>;
bitgoChallenge: TxRequestChallengeResponse;
encryptedWShare: string;
}): Promise<TssEcdsaStep2ReturnMessage>;
}

export interface CustomSShareGeneratingFunction {
(params: {
tssParams: TSSParams | TSSParamsForMessage;
dShareFromBitgo: DShare;
requestType: RequestType;
encryptedOShare: string;
}): Promise<SShare>;
}

export interface CustomCommitmentGeneratingFunction {
(params: { txRequest: TxRequest }): Promise<{
Expand Down Expand Up @@ -348,12 +385,20 @@ export interface ITssUtils<KeyShare = EDDSA.KeyShare> {
}): Promise<KeychainsTriplet>;
signTxRequest(params: { txRequest: string | TxRequest; prv: string; reqId: IRequestTracer }): Promise<TxRequest>;
signTxRequestForMessage(params: TSSParams): Promise<TxRequest>;
signUsingExternalSigner(
signEddsaTssUsingExternalSigner(
txRequest: string | TxRequest,
externalSignerCommitmentGenerator: CustomCommitmentGeneratingFunction,
externalSignerRShareGenerator: CustomRShareGeneratingFunction,
externalSignerGShareGenerator: CustomGShareGeneratingFunction
): Promise<TxRequest>;
signEcdsaTssUsingExternalSigner(
params: TSSParams | TSSParamsForMessage,
requestType: RequestType,
externalSignerPaillierModulusGenerator: CustomPaillierModulustGeneratingFunction,
externalSignerKShareGenerator: CustomKShareGeneratingFunction,
externalSignerMuDeltaShareGenerator: CustomMuDeltaShareGeneratingFunction,
externalSignerSShareGenerator: CustomSShareGeneratingFunction
): Promise<TxRequest>;
createCommitmentShareFromTxRequest(params: { txRequest: TxRequest; prv: string; walletPassphrase: string }): Promise<{
userToBitgoCommitment: CommitmentShareRecord;
encryptedSignerShare: EncryptedSignerShareRecord;
Expand Down
Loading

0 comments on commit d3abd9a

Please sign in to comment.