Skip to content

Commit bf8f0d4

Browse files
authored
Merge pull request #19 from BitGo/WP-4519
feat(ebe): added multisig signing API to EBE
2 parents e35bf00 + 49bd69f commit bf8f0d4

File tree

5 files changed

+118
-2
lines changed

5 files changed

+118
-2
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as express from 'express';
2+
import { KmsClient } from '../../kms/kmsClient';
3+
import { BitGo, RequestTracer, TransactionPrebuild } from 'bitgo';
4+
import logger from '../../logger';
5+
6+
export async function signMultisigTransaction(
7+
req: express.Request,
8+
res: express.Response,
9+
): Promise<any> {
10+
const {
11+
source,
12+
pub,
13+
txPrebuild,
14+
}: { source: string; pub: string; txPrebuild: TransactionPrebuild } = req.body;
15+
16+
if (!source || !pub) {
17+
throw new Error('Source and public key are required for signing');
18+
} else if (!txPrebuild || !txPrebuild.wallet) {
19+
throw new Error('Transaction prebuild is required for signing');
20+
}
21+
22+
const reqId = new RequestTracer();
23+
const bitgo: BitGo = req.body.bitgo;
24+
const baseCoin = bitgo.coin(req.params.coin);
25+
const kms = new KmsClient();
26+
27+
// verify transaction prebuild
28+
try {
29+
await baseCoin.verifyTransaction({
30+
txParams: { ...txPrebuild.buildParams },
31+
txPrebuild,
32+
wallet: txPrebuild.wallet,
33+
verification: {},
34+
reqId: reqId,
35+
walletType: 'onchain',
36+
});
37+
} catch (e) {
38+
const err = e as Error;
39+
logger.error('transaction prebuild failed local validation:', err.message);
40+
logger.error('transaction prebuild:', JSON.stringify(txPrebuild, null, 2));
41+
logger.error(err);
42+
}
43+
44+
// Retrieve the private key from KMS
45+
let prv: string;
46+
try {
47+
const res = await kms.getKey({ pub, source });
48+
prv = res.prv;
49+
} catch (error: any) {
50+
res.status(error.status || 500).json({
51+
message: error.message || 'Failed to retrieve key from KMS',
52+
});
53+
return;
54+
}
55+
56+
// Sign the transaction using BitGo SDK
57+
const coin = bitgo.coin(req.params.coin);
58+
try {
59+
return await coin.signTransaction({ txPrebuild, prv });
60+
} catch (error) {
61+
console.log('error while signing wallet transaction ', error);
62+
throw error;
63+
}
64+
}

src/kms/kmsClient.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import debug from 'debug';
22
import * as superagent from 'superagent';
33
import { config, isMasterExpressConfig } from '../config';
44
import { PostKeyKmsSchema, PostKeyParams, PostKeyResponse } from './types/postKey';
5+
import { GetKeyKmsSchema, GetKeyParams, GetKeyResponse } from './types/getKey';
56

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

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

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

38+
// validate the response
3639
try {
3740
PostKeyKmsSchema.parse(kmsResponse.body);
3841
} catch (error: any) {
@@ -44,4 +47,31 @@ export class KmsClient {
4447
const { pub, coin, source } = kmsResponse.body;
4548
return { pub, coin, source } as PostKeyResponse;
4649
}
50+
51+
async getKey(params: GetKeyParams): Promise<GetKeyResponse> {
52+
debugLogger('Getting key from KMS: %O', params);
53+
54+
// Call KMS to get the key
55+
let kmsResponse: any;
56+
try {
57+
kmsResponse = await superagent
58+
.get(`${this.url}/key/${params.pub}`)
59+
.set('x-api-key', 'abc')
60+
.query({ source: params.source });
61+
} catch (error: any) {
62+
console.log('Error getting key from KMS', error);
63+
throw error;
64+
}
65+
66+
// validate the response
67+
try {
68+
GetKeyKmsSchema.parse(kmsResponse.body);
69+
} catch (error: any) {
70+
throw new Error(
71+
`KMS returned unexpected response${error.message ? `: ${error.message}` : ''}`,
72+
);
73+
}
74+
75+
return kmsResponse.body as GetKeyResponse;
76+
}
4777
}

src/kms/types/getKey.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as z from 'zod';
2+
3+
export interface GetKeyParams {
4+
pub: string;
5+
source: string;
6+
}
7+
8+
export interface GetKeyResponse {
9+
pub: string;
10+
prv: string;
11+
source: 'user' | 'backup';
12+
type: 'independent' | 'tss';
13+
}
14+
15+
export const GetKeyKmsSchema = z.object({
16+
pub: z.string(),
17+
prv: z.string(),
18+
source: z.enum(['user', 'backup']),
19+
type: z.enum(['independent', 'tss']),
20+
});

src/kms/types/postKey.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ export interface PostKeyParams {
55
pub: string;
66
coin: string;
77
source: string;
8-
type: 'independent' | 'enclaved';
8+
type: 'independent' | 'tss';
99
seed?: string; // Optional seed for key generation
1010
}
1111

1212
export interface PostKeyResponse {
1313
pub: string;
1414
coin: string;
1515
source: string;
16-
type: 'independent' | 'enclaved';
16+
type: 'independent' | 'tss';
1717
}
1818

1919
export const PostKeyKmsSchema = z.object({

src/routes/enclaved.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import pjson from '../../package.json';
44
import type { BitGoOptions } from 'bitgo';
55
import { postIndependentKey } from '../api/enclaved/postIndependentKey';
66
import { promiseWrapper } from './utils';
7+
import { signMultisigTransaction } from '../api/enclaved/signMultisigTransaction';
78

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

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

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

0 commit comments

Comments
 (0)