Skip to content

Commit df2d71a

Browse files
authored
Merge pull request #8655 from BitGo/revert-8624-WCN-217
Revert "feat: implement BitGo signing in SDK"
2 parents b37a6a1 + 8542b17 commit df2d71a

12 files changed

Lines changed: 13 additions & 550 deletions

File tree

modules/express/src/clientRoutes.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -405,15 +405,6 @@ function getWalletPwFromEnv(walletId: string): string {
405405
return walletPw;
406406
}
407407

408-
/**
409-
* Returns the wallet passphrase from the environment, or undefined if not set.
410-
* Unlike getWalletPwFromEnv, this does not throw when the env variable is absent.
411-
* Use this when the passphrase is optional (e.g. KMS-backed wallets).
412-
*/
413-
function findWalletPwFromEnv(walletId: string): string | undefined {
414-
return process.env[`WALLET_${walletId}_PASSPHRASE`];
415-
}
416-
417408
async function getEncryptedPrivKey(path: string, walletId: string): Promise<string> {
418409
const privKeyFile = await fs.readFile(path, { encoding: 'utf8' });
419410
const encryptedPrivKey = JSON.parse(privKeyFile);
@@ -640,9 +631,7 @@ export async function handleV2OFCSignPayload(
640631
throw new ApiResponseError(`Could not find OFC wallet ${walletId}`, 404);
641632
}
642633

643-
// Prefer the passphrase from the request body; fall back to the env var.
644-
// If neither is present, pass undefined — signPayload() routes to KMS internally.
645-
const walletPassphrase = bodyWalletPassphrase ?? findWalletPwFromEnv(wallet.id());
634+
const walletPassphrase = bodyWalletPassphrase || getWalletPwFromEnv(wallet.id());
646635
const tradingAccount = wallet.toTradingAccount();
647636
const stringifiedPayload = typeof payload === 'string' ? payload : JSON.stringify(payload);
648637
const signature = await tradingAccount.signPayload({

modules/express/test/unit/typedRoutes/ofcSignPayload.ts

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -223,103 +223,10 @@ describe('OfcSignPayload codec tests', function () {
223223
const decodedResponse = assertDecode(OfcSignPayloadResponse200, result.body);
224224
assert.strictEqual(decodedResponse.signature, mockSignPayloadResponse.signature);
225225

226-
// Verify env passphrase was forwarded to signPayload
227-
const signCall = mockTradingAccount.signPayload.getCall(0);
228-
assert.ok(signCall, 'tradingAccount.signPayload should have been called');
229-
assert.strictEqual(signCall.args[0].walletPassphrase, 'env_passphrase', 'env passphrase should be forwarded');
230-
231226
// Cleanup environment variable
232227
delete process.env['WALLET_ofc-wallet-id-123_PASSPHRASE'];
233228
});
234229

235-
it('should pass undefined walletPassphrase to signPayload when no passphrase in body or env (KMS path)', async function () {
236-
const requestBody = {
237-
walletId: 'ofc-wallet-id-no-passphrase',
238-
payload: { amount: '1000000', currency: 'USD' },
239-
// no walletPassphrase
240-
};
241-
242-
// Ensure no env var is set for this wallet
243-
delete process.env['WALLET_ofc-wallet-id-no-passphrase_PASSPHRASE'];
244-
245-
const mockTradingAccount = {
246-
signPayload: sinon.stub().resolves(mockSignPayloadResponse.signature),
247-
};
248-
249-
const mockWallet = {
250-
id: () => requestBody.walletId,
251-
toTradingAccount: sinon.stub().returns(mockTradingAccount),
252-
};
253-
254-
const walletsGetStub = sinon.stub().resolves(mockWallet);
255-
const mockWallets = { get: walletsGetStub };
256-
const mockCoin = { wallets: sinon.stub().returns(mockWallets) };
257-
sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any);
258-
259-
const result = await agent
260-
.post('/api/v2/ofc/signPayload')
261-
.set('Authorization', 'Bearer test_access_token_12345')
262-
.set('Content-Type', 'application/json')
263-
.send(requestBody);
264-
265-
assert.strictEqual(result.status, 200);
266-
const decodedResponse = assertDecode(OfcSignPayloadResponse200, result.body);
267-
assert.strictEqual(decodedResponse.signature, mockSignPayloadResponse.signature);
268-
269-
// signPayload must be called with walletPassphrase=undefined so the SDK routes to KMS
270-
const signCall = mockTradingAccount.signPayload.getCall(0);
271-
assert.ok(signCall, 'tradingAccount.signPayload should have been called');
272-
assert.strictEqual(
273-
signCall.args[0].walletPassphrase,
274-
undefined,
275-
'walletPassphrase should be undefined to trigger KMS signing'
276-
);
277-
});
278-
279-
it('should prefer body walletPassphrase over env passphrase', async function () {
280-
const requestBody = {
281-
walletId: 'ofc-wallet-id-123',
282-
payload: { amount: '500' },
283-
walletPassphrase: 'body_passphrase',
284-
};
285-
286-
// Set a different env passphrase — body should win
287-
process.env['WALLET_ofc-wallet-id-123_PASSPHRASE'] = 'env_passphrase';
288-
289-
const mockTradingAccount = {
290-
signPayload: sinon.stub().resolves(mockSignPayloadResponse.signature),
291-
};
292-
293-
const mockWallet = {
294-
id: () => requestBody.walletId,
295-
toTradingAccount: sinon.stub().returns(mockTradingAccount),
296-
};
297-
298-
const walletsGetStub = sinon.stub().resolves(mockWallet);
299-
const mockWallets = { get: walletsGetStub };
300-
const mockCoin = { wallets: sinon.stub().returns(mockWallets) };
301-
sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any);
302-
303-
const result = await agent
304-
.post('/api/v2/ofc/signPayload')
305-
.set('Authorization', 'Bearer test_access_token_12345')
306-
.set('Content-Type', 'application/json')
307-
.send(requestBody);
308-
309-
assert.strictEqual(result.status, 200);
310-
311-
// body passphrase should take precedence
312-
const signCall = mockTradingAccount.signPayload.getCall(0);
313-
assert.ok(signCall, 'tradingAccount.signPayload should have been called');
314-
assert.strictEqual(
315-
signCall.args[0].walletPassphrase,
316-
'body_passphrase',
317-
'body passphrase should take precedence over env'
318-
);
319-
320-
delete process.env['WALLET_ofc-wallet-id-123_PASSPHRASE'];
321-
});
322-
323230
it('should successfully sign complex nested JSON payload', async function () {
324231
const requestBody = {
325232
walletId: 'ofc-wallet-id-123',

modules/sdk-core/src/bitgo/trading/iTradingAccount.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import { ITradingNetwork } from './network';
22

3-
/**
4-
* Parameters for the signing a payload from the trading account
5-
* @param payload - The payload to sign
6-
* @param walletPassphrase - The passphrase of the wallet that will be used to decrypt the user key and sign the payload. If not provided, the BitGo key will be used.
7-
*/
83
export interface SignPayloadParameters {
94
payload: string | Record<string, unknown>;
10-
walletPassphrase?: string;
5+
walletPassphrase: string;
116
}
127

138
export interface ITradingAccount {

modules/sdk-core/src/bitgo/trading/network/network.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export class TradingNetwork implements ITradingNetwork {
109109

110110
/**
111111
* Prepare an allocation for submission
112-
* @param {string} walletPassphrase ofc wallet passphrase - required only when signing via user key
112+
* @param {string} walletPassphrase ofc wallet passphrase
113113
* @param {string} connectionId connection to whom to make the allocation or deallocation
114114
* @param {string=} clientExternalId one time generated uuid v4
115115
* @param {string} currency currency for which the allocation should be made. e.g. btc / tbtc
@@ -130,7 +130,10 @@ export class TradingNetwork implements ITradingNetwork {
130130
}
131131

132132
const payload = JSON.stringify(body);
133-
const signature = await this.wallet.toTradingAccount().signPayload({ payload, walletPassphrase });
133+
134+
const prv = await this.wallet.getPrv({ walletPassphrase });
135+
const signedBuffer: Buffer = await this.wallet.baseCoin.signMessage({ prv }, payload);
136+
const signature = signedBuffer.toString('hex');
134137

135138
return {
136139
...body,

modules/sdk-core/src/bitgo/trading/network/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export type GetNetworkAllocationByIdResponse = {
125125
};
126126

127127
export type PrepareNetworkAllocationParams = Omit<CreateNetworkAllocationParams, 'payload' | 'signature'> & {
128-
walletPassphrase?: string;
128+
walletPassphrase: string;
129129
clientExternalId?: string;
130130
nonce?: string;
131131
};

modules/sdk-core/src/bitgo/trading/tradingAccount.ts

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,51 +23,13 @@ export class TradingAccount implements ITradingAccount {
2323
}
2424

2525
/**
26-
* Signs an arbitrary payload. Use the user key if passphrase is provided, or the BitGo key if not.
26+
* Signs an arbitrary payload with the user key on this trading account
2727
* @param params
2828
* @param params.payload arbitrary payload object (string | Record<string, unknown>)
2929
* @param params.walletPassphrase passphrase on this trading account, used to unlock the account user key
3030
* @returns hex-encoded signature of the payload
3131
*/
3232
async signPayload(params: SignPayloadParameters): Promise<string> {
33-
// if no passphrase is provided, attempt to sign using the wallet's bitgo key remotely
34-
if (!params.walletPassphrase) {
35-
return this.signPayloadByBitGoKey(params);
36-
}
37-
// if a passphrase is provided, we must be trying to sign using the user private key - decrypt and sign locally
38-
return this.signPayloadByUserKey(params);
39-
}
40-
41-
/**
42-
* Signs the payload of a trading account via the trading account BitGo key
43-
* @param params
44-
* @private
45-
*/
46-
private async signPayloadByBitGoKey(params: Omit<SignPayloadParameters, 'walletPassphrase'>): Promise<string> {
47-
const walletData = this.wallet.toJSON();
48-
if (walletData.userKeySigningRequired) {
49-
throw new Error(
50-
'Wallet must use user key to sign ofc transaction, please provide the wallet passphrase or visit your wallet settings page to configure one.'
51-
);
52-
}
53-
if (walletData.keys.length < 2) {
54-
throw new Error(
55-
'Wallet does not support BitGo signing. Please reach out to support@bitgo.com to resolve this issue.'
56-
);
57-
}
58-
59-
const url = this.wallet.url('/tx/sign');
60-
const { signature } = await this.wallet.bitgo.post(url).send(params.payload).result();
61-
62-
return signature;
63-
}
64-
65-
/**
66-
* Signs the payload of a trading account locally by fetching the user's encrypted private key and decrypt using passphrase
67-
* @param params
68-
* @private
69-
*/
70-
private async signPayloadByUserKey(params: SignPayloadParameters): Promise<string> {
7133
const key = (await this.wallet.baseCoin.keychains().get({ id: this.wallet.keyIds()[0] })) as any;
7234
const prv = this.wallet.bitgo.decrypt({
7335
input: key.encryptedPrv,

modules/sdk-core/src/bitgo/wallet/iWallet.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,6 @@ export interface WalletData {
909909
evmKeyRingReferenceWalletId?: string;
910910
isParent?: boolean;
911911
enabledChildChains?: string[];
912-
userKeySigningRequired?: string;
913912
}
914913

915914
export interface RecoverTokenOptions {

modules/sdk-core/src/coins/ofc.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
SignTransactionOptions,
1616
VerifyAddressOptions,
1717
VerifyTransactionOptions,
18-
Wallet,
1918
} from '../';
2019

2120
export class Ofc extends BaseCoin {
@@ -105,26 +104,6 @@ export class Ofc extends BaseCoin {
105104
throw new MethodNotImplementedError();
106105
}
107106

108-
/**
109-
* Signs a message using a trading wallet's BitGo Key
110-
* @param wallet - uses the BitGo key of this trading wallet to sign the message remotely in a KMS
111-
* @param message
112-
*/
113-
async signMessage(wallet: Wallet, message: string): Promise<Buffer>;
114-
/**
115-
* Signs a message using the private key
116-
* @param key - uses the private key to sign the message
117-
* @param message
118-
*/
119-
async signMessage(key: { prv: string }, message: string): Promise<Buffer>;
120-
async signMessage(keyOrWallet: { prv: string } | Wallet, message: string): Promise<Buffer> {
121-
if (!(keyOrWallet instanceof Wallet)) {
122-
return super.signMessage(keyOrWallet, message);
123-
}
124-
const signatureHexString = await keyOrWallet.toTradingAccount().signPayload({ payload: message });
125-
return Buffer.from(signatureHexString, 'hex');
126-
}
127-
128107
/** @inheritDoc */
129108
auditDecryptedKey(params: AuditDecryptedKeyParams) {
130109
throw new MethodNotImplementedError();

modules/sdk-core/src/coins/ofcToken.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
SignTransactionOptions as BaseSignTransactionOptions,
1010
SignedTransaction,
1111
ITransactionRecipient,
12-
Wallet,
1312
} from '../';
1413
import { isBolt11Invoice } from '../lightning';
1514

@@ -19,8 +18,7 @@ export interface SignTransactionOptions extends BaseSignTransactionOptions {
1918
txPrebuild: {
2019
payload: string;
2120
};
22-
prv?: string;
23-
wallet?: Wallet;
21+
prv: string;
2422
}
2523

2624
export { OfcTokenConfig };
@@ -109,25 +107,15 @@ export class OfcToken extends Ofc {
109107
}
110108

111109
/**
112-
* Signs a half-signed OFC transaction.
113-
* Signs the transaction remotely using the BitGo key if prv is not provided.
110+
* Assemble keychain and half-sign prebuilt transaction
114111
* @param params
115112
* @returns {Promise<SignedTransaction>}
116113
*/
117114
async signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
118115
const txPrebuild = params.txPrebuild;
119116
const payload = txPrebuild.payload;
120-
121-
let signature: string;
122-
if (params.wallet) {
123-
signature = await params.wallet.toTradingAccount().signPayload({ payload, walletPassphrase: params.prv });
124-
} else if (params.prv) {
125-
const signatureBuffer = (await this.signMessage({ prv: params.prv }, payload)) as any;
126-
signature = signatureBuffer.toString('hex');
127-
} else {
128-
throw new Error('You must pass in either one of wallet or prv');
129-
}
130-
117+
const signatureBuffer = (await this.signMessage(params, payload)) as any;
118+
const signature: string = signatureBuffer.toString('hex');
131119
return { halfSigned: { payload, signature } } as any;
132120
}
133121

0 commit comments

Comments
 (0)