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
64 changes: 64 additions & 0 deletions src/api/enclaved/signMultisigTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as express from 'express';
import { KmsClient } from '../../kms/kmsClient';
import { BitGo, RequestTracer, TransactionPrebuild } from 'bitgo';
import logger from '../../logger';

export async function signMultisigTransaction(
req: express.Request,
res: express.Response,
): Promise<any> {
const {
source,
pub,
txPrebuild,
}: { source: string; pub: string; txPrebuild: TransactionPrebuild } = req.body;

if (!source || !pub) {
throw new Error('Source and public key are required for signing');
} else if (!txPrebuild || !txPrebuild.wallet) {
throw new Error('Transaction prebuild is required for signing');
}

const reqId = new RequestTracer();
const bitgo: BitGo = req.body.bitgo;
const baseCoin = bitgo.coin(req.params.coin);
const kms = new KmsClient();

// verify transaction prebuild
try {
await baseCoin.verifyTransaction({
txParams: { ...txPrebuild.buildParams },
txPrebuild,
wallet: txPrebuild.wallet,
verification: {},
reqId: reqId,
walletType: 'onchain',
});
} catch (e) {
const err = e as Error;
logger.error('transaction prebuild failed local validation:', err.message);
logger.error('transaction prebuild:', JSON.stringify(txPrebuild, null, 2));
logger.error(err);
}

// Retrieve the private key from KMS
let prv: string;
try {
const res = await kms.getKey({ pub, source });
prv = res.prv;
} catch (error: any) {
res.status(error.status || 500).json({
message: error.message || 'Failed to retrieve key from KMS',
});
return;
}

// Sign the transaction using BitGo SDK
const coin = bitgo.coin(req.params.coin);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to verify the transaction

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a verification for prebuild

try {
return await coin.signTransaction({ txPrebuild, prv });
} catch (error) {
console.log('error while signing wallet transaction ', error);
throw error;
}
}
30 changes: 30 additions & 0 deletions src/kms/kmsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import debug from 'debug';
import * as superagent from 'superagent';
import { config, isMasterExpressConfig } from '../config';
import { PostKeyKmsSchema, PostKeyParams, PostKeyResponse } from './types/postKey';
import { GetKeyKmsSchema, GetKeyParams, GetKeyResponse } from './types/getKey';

const debugLogger = debug('bitgo:express:kmsClient');

Expand All @@ -25,6 +26,7 @@ export class KmsClient {
async postKey(params: PostKeyParams): Promise<PostKeyResponse> {
debugLogger('Posting key to KMS: %O', params);

// Call KMS to post the key
let kmsResponse: any;
try {
kmsResponse = await superagent.post(`${this.url}/key`).set('x-api-key', 'abc').send(params);
Expand All @@ -33,6 +35,7 @@ export class KmsClient {
throw error;
}

// validate the response
try {
PostKeyKmsSchema.parse(kmsResponse.body);
} catch (error: any) {
Expand All @@ -44,4 +47,31 @@ export class KmsClient {
const { pub, coin, source } = kmsResponse.body;
return { pub, coin, source } as PostKeyResponse;
}

async getKey(params: GetKeyParams): Promise<GetKeyResponse> {
debugLogger('Getting key from KMS: %O', params);

// Call KMS to get the key
let kmsResponse: any;
try {
kmsResponse = await superagent
.get(`${this.url}/key/${params.pub}`)
.set('x-api-key', 'abc')
.query({ source: params.source });
} catch (error: any) {
console.log('Error getting key from KMS', error);
throw error;
}

// validate the response
try {
GetKeyKmsSchema.parse(kmsResponse.body);
} catch (error: any) {
throw new Error(
`KMS returned unexpected response${error.message ? `: ${error.message}` : ''}`,
);
}

return kmsResponse.body as GetKeyResponse;
}
}
20 changes: 20 additions & 0 deletions src/kms/types/getKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as z from 'zod';

export interface GetKeyParams {
pub: string;
source: string;
}

export interface GetKeyResponse {
pub: string;
prv: string;
source: 'user' | 'backup';
type: 'independent' | 'tss';
}

export const GetKeyKmsSchema = z.object({
pub: z.string(),
prv: z.string(),
source: z.enum(['user', 'backup']),
type: z.enum(['independent', 'tss']),
});
4 changes: 2 additions & 2 deletions src/kms/types/postKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ export interface PostKeyParams {
pub: string;
coin: string;
source: string;
type: 'independent' | 'enclaved';
type: 'independent' | 'tss';
seed?: string; // Optional seed for key generation
}

export interface PostKeyResponse {
pub: string;
coin: string;
source: string;
type: 'independent' | 'enclaved';
type: 'independent' | 'tss';
}

export const PostKeyKmsSchema = z.object({
Expand Down
2 changes: 2 additions & 0 deletions src/routes/enclaved.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import pjson from '../../package.json';
import type { BitGoOptions } from 'bitgo';
import { postIndependentKey } from '../api/enclaved/postIndependentKey';
import { promiseWrapper } from './utils';
import { signMultisigTransaction } from '../api/enclaved/signMultisigTransaction';

const debugLogger = debug('enclaved:routes');

Expand Down Expand Up @@ -50,6 +51,7 @@ async function prepBitGo(req: express.Request, res: express.Response, next: expr

function setupKeyGenRoutes(app: express.Application) {
app.post('/api/:coin/key/independent', prepBitGo, promiseWrapper(postIndependentKey));
app.post('/api/:coin/multisig/sign', prepBitGo, promiseWrapper(signMultisigTransaction));
debugLogger('KeyGen routes configured');
}

Expand Down