Skip to content

Commit 2c3a6c9

Browse files
authored
Merge pull request #7471 from BitGo/WP-6199-express-external-ofc-sign-payload
feat(express): migrated ofcExtSignPayload to type route
2 parents 85e720a + 4058591 commit 2c3a6c9

File tree

5 files changed

+436
-15
lines changed

5 files changed

+436
-15
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ export async function handleV2Sign(req: ExpressApiRouteRequest<'express.v2.coin.
540540
}
541541

542542
export async function handleV2OFCSignPayloadInExtSigningMode(
543-
req: express.Request
543+
req: ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>
544544
): Promise<{ payload: string; signature: string }> {
545545
const walletId = req.body.walletId;
546546
const payload = req.body.payload;
@@ -1742,12 +1742,10 @@ export function setupSigningRoutes(app: express.Application, config: Config): vo
17421742
prepareBitGo(config),
17431743
promiseWrapper(handleV2GenerateShareTSS)
17441744
);
1745-
app.post(
1746-
`/api/v2/ofc/signPayload`,
1747-
parseBody,
1745+
router.post('express.v2.ofc.extSignPayload', [
17481746
prepareBitGo(config),
1749-
promiseWrapper(handleV2OFCSignPayloadInExtSigningMode)
1750-
);
1747+
typedPromiseWrapper(handleV2OFCSignPayloadInExtSigningMode),
1748+
]);
17511749
}
17521750

17531751
export function setupLightningSignerNodeRoutes(app: express.Application, config: Config): void {

modules/express/src/typedRoutes/api/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { PostConsolidateUnspents } from './v2/consolidateunspents';
3939
import { PostPrebuildAndSignTransaction } from './v2/prebuildAndSignTransaction';
4040
import { PostCoinSign } from './v2/coinSign';
4141
import { PostSendCoins } from './v2/sendCoins';
42+
import { PostOfcExtSignPayload } from './v2/ofcExtSignPayload';
4243

4344
// Too large types can cause the following error
4445
//
@@ -227,6 +228,9 @@ export const ExpressExternalSigningApiSpec = apiSpec({
227228
'express.v2.coin.sign': {
228229
post: PostCoinSign,
229230
},
231+
'express.v2.ofc.extSignPayload': {
232+
post: PostOfcExtSignPayload,
233+
},
230234
});
231235

232236
export const ExpressWalletSigningApiSpec = apiSpec({
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
2+
import { OfcSignPayloadBody, OfcSignPayloadResponse } from './ofcSignPayload';
3+
4+
/**
5+
* Sign an arbitrary payload using an OFC trading account key (External Signing Mode).
6+
*
7+
* This endpoint is used when BitGo Express is running in external signing mode,
8+
* where private keys are stored in an encrypted file on the filesystem rather than
9+
* being fetched from the BitGo API.
10+
*
11+
* The request and response structure is identical to the regular OFC sign payload endpoint,
12+
* but the implementation reads the encrypted private key from a local file specified by
13+
* the `signerFileSystemPath` configuration.
14+
*
15+
* **External Signing Mode Requirements**:
16+
* - `signerFileSystemPath` must be configured in Express config
17+
* - Encrypted private keys must be available in the file system
18+
* - Wallet passphrase must be provided via request body or environment variable
19+
*
20+
* **Flow**:
21+
* 1. Reads encrypted private key from filesystem
22+
* 2. Decrypts private key using wallet passphrase
23+
* 3. Signs the payload with the decrypted key
24+
* 4. Returns signed payload and hex-encoded signature
25+
*
26+
* @operationId express.v2.ofc.extSignPayload
27+
* @tag express
28+
*/
29+
export const PostOfcExtSignPayload = httpRoute({
30+
path: '/api/v2/ofc/signPayload',
31+
method: 'POST',
32+
request: httpRequest({
33+
body: OfcSignPayloadBody,
34+
}),
35+
response: OfcSignPayloadResponse,
36+
});

modules/express/test/unit/clientRoutes/signPayload.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import 'should-http';
33
import 'should-sinon';
44
import 'should';
55
import * as fs from 'fs';
6-
import { Request } from 'express';
76
import { BitGo, Coin, BaseCoin, Wallet, Wallets } from 'bitgo';
87
import '../../lib/asserts';
98
import { handleV2OFCSignPayload, handleV2OFCSignPayloadInExtSigningMode } from '../../../src/clientRoutes';
@@ -158,10 +157,14 @@ describe('With the handler to sign an arbitrary payload in external signing mode
158157
walletId,
159158
payload,
160159
},
160+
decoded: {
161+
walletId,
162+
payload,
163+
},
161164
config: {
162165
signerFileSystemPath: 'signerFileSystemPath',
163166
},
164-
} as unknown as Request;
167+
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;
165168

166169
await handleV2OFCSignPayloadInExtSigningMode(req).should.be.resolvedWith(expectedResponse);
167170
readFileStub.should.be.calledOnceWith('signerFileSystemPath');
@@ -195,10 +198,15 @@ describe('With the handler to sign an arbitrary payload in external signing mode
195198
payload,
196199
walletPassphrase: walletPassword,
197200
},
201+
decoded: {
202+
walletId,
203+
payload,
204+
walletPassphrase: walletPassword,
205+
},
198206
config: {
199207
signerFileSystemPath: 'signerFileSystemPath',
200208
},
201-
} as unknown as Request;
209+
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;
202210

203211
await handleV2OFCSignPayloadInExtSigningMode(req).should.be.resolvedWith(expectedResponse);
204212
readFileStub.should.be.calledOnceWith('signerFileSystemPath');
@@ -233,10 +241,15 @@ describe('With the handler to sign an arbitrary payload in external signing mode
233241
payload,
234242
walletPassphrase: walletPassword,
235243
},
244+
decoded: {
245+
walletId,
246+
payload,
247+
walletPassphrase: walletPassword,
248+
},
236249
config: {
237250
signerFileSystemPath: 'signerFileSystemPath',
238251
},
239-
} as unknown as Request;
252+
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;
240253

241254
await handleV2OFCSignPayloadInExtSigningMode(req).should.be.resolvedWith(expectedResponse);
242255
readFileStub.should.be.calledOnceWith('signerFileSystemPath');
@@ -260,7 +273,11 @@ describe('With the handler to sign an arbitrary payload in external signing mode
260273
walletId,
261274
payload,
262275
},
263-
} as unknown as Request;
276+
decoded: {
277+
walletId,
278+
payload,
279+
},
280+
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;
264281

265282
await handleV2OFCSignPayloadInExtSigningMode(req).should.be.rejectedWith(
266283
'Could not find wallet passphrase WALLET_61f039aad587c2000745c687373e0fa9_PASSPHRASE in environment'
@@ -278,10 +295,14 @@ describe('With the handler to sign an arbitrary payload in external signing mode
278295
walletId,
279296
payload,
280297
},
298+
decoded: {
299+
walletId,
300+
payload,
301+
},
281302
config: {
282303
signerFileSystemPath: undefined,
283304
},
284-
} as unknown as Request;
305+
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;
285306

286307
await handleV2OFCSignPayloadInExtSigningMode(req).should.be.rejectedWith(
287308
'Missing required configuration: signerFileSystemPath'
@@ -301,10 +322,14 @@ describe('With the handler to sign an arbitrary payload in external signing mode
301322
walletId,
302323
payload,
303324
},
325+
decoded: {
326+
walletId,
327+
payload,
328+
},
304329
config: {
305330
signerFileSystemPath: 'signerFileSystemPath',
306331
},
307-
} as unknown as Request;
332+
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;
308333

309334
await handleV2OFCSignPayloadInExtSigningMode(req).should.be.rejectedWith(
310335
"Error when trying to decrypt private key: INVALID: json decode: this isn't json!"
@@ -326,10 +351,14 @@ describe('With the handler to sign an arbitrary payload in external signing mode
326351
walletId,
327352
payload,
328353
},
354+
decoded: {
355+
walletId,
356+
payload,
357+
},
329358
config: {
330359
signerFileSystemPath: 'signerFileSystemPath',
331360
},
332-
} as unknown as Request;
361+
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;
333362

334363
await handleV2OFCSignPayloadInExtSigningMode(req).should.be.rejectedWith(
335364
"Error when trying to decrypt private key: CORRUPT: password error - ccm: tag doesn't match"
@@ -349,10 +378,15 @@ describe('With the handler to sign an arbitrary payload in external signing mode
349378
payload,
350379
walletPassphrase: 'invalidPassphrase',
351380
},
381+
decoded: {
382+
walletId,
383+
payload,
384+
walletPassphrase: 'invalidPassphrase',
385+
},
352386
config: {
353387
signerFileSystemPath: 'signerFileSystemPath',
354388
},
355-
} as unknown as Request;
389+
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;
356390

357391
await handleV2OFCSignPayloadInExtSigningMode(req).should.be.rejectedWith(
358392
"Error when trying to decrypt private key: CORRUPT: password error - ccm: tag doesn't match"

0 commit comments

Comments
 (0)