From 8542b17590752d1bb547e3dd58fec1356b6318e7 Mon Sep 17 00:00:00 2001 From: Alex Tse Date: Wed, 29 Apr 2026 14:13:50 -0400 Subject: [PATCH] Revert "feat: implement BitGo signing in SDK" --- modules/express/src/clientRoutes.ts | 13 +- .../test/unit/typedRoutes/ofcSignPayload.ts | 93 ----------- .../src/bitgo/trading/iTradingAccount.ts | 7 +- .../src/bitgo/trading/network/network.ts | 7 +- .../src/bitgo/trading/network/types.ts | 2 +- .../src/bitgo/trading/tradingAccount.ts | 40 +---- modules/sdk-core/src/bitgo/wallet/iWallet.ts | 1 - modules/sdk-core/src/coins/ofc.ts | 21 --- modules/sdk-core/src/coins/ofcToken.ts | 20 +-- .../test/unit/bitgo/trading/tradingAccount.ts | 134 ---------------- .../bitgo/wallet/ofcWalletSignTransaction.ts | 79 ---------- modules/sdk-core/test/unit/coins/ofc.ts | 146 ------------------ 12 files changed, 13 insertions(+), 550 deletions(-) delete mode 100644 modules/sdk-core/test/unit/bitgo/trading/tradingAccount.ts delete mode 100644 modules/sdk-core/test/unit/bitgo/wallet/ofcWalletSignTransaction.ts delete mode 100644 modules/sdk-core/test/unit/coins/ofc.ts diff --git a/modules/express/src/clientRoutes.ts b/modules/express/src/clientRoutes.ts index dd39f1cece..0ae3d95021 100755 --- a/modules/express/src/clientRoutes.ts +++ b/modules/express/src/clientRoutes.ts @@ -405,15 +405,6 @@ function getWalletPwFromEnv(walletId: string): string { return walletPw; } -/** - * Returns the wallet passphrase from the environment, or undefined if not set. - * Unlike getWalletPwFromEnv, this does not throw when the env variable is absent. - * Use this when the passphrase is optional (e.g. KMS-backed wallets). - */ -function findWalletPwFromEnv(walletId: string): string | undefined { - return process.env[`WALLET_${walletId}_PASSPHRASE`]; -} - async function getEncryptedPrivKey(path: string, walletId: string): Promise { const privKeyFile = await fs.readFile(path, { encoding: 'utf8' }); const encryptedPrivKey = JSON.parse(privKeyFile); @@ -640,9 +631,7 @@ export async function handleV2OFCSignPayload( throw new ApiResponseError(`Could not find OFC wallet ${walletId}`, 404); } - // Prefer the passphrase from the request body; fall back to the env var. - // If neither is present, pass undefined — signPayload() routes to KMS internally. - const walletPassphrase = bodyWalletPassphrase ?? findWalletPwFromEnv(wallet.id()); + const walletPassphrase = bodyWalletPassphrase || getWalletPwFromEnv(wallet.id()); const tradingAccount = wallet.toTradingAccount(); const stringifiedPayload = typeof payload === 'string' ? payload : JSON.stringify(payload); const signature = await tradingAccount.signPayload({ diff --git a/modules/express/test/unit/typedRoutes/ofcSignPayload.ts b/modules/express/test/unit/typedRoutes/ofcSignPayload.ts index 9b0685183d..59cdd54f4d 100644 --- a/modules/express/test/unit/typedRoutes/ofcSignPayload.ts +++ b/modules/express/test/unit/typedRoutes/ofcSignPayload.ts @@ -223,103 +223,10 @@ describe('OfcSignPayload codec tests', function () { const decodedResponse = assertDecode(OfcSignPayloadResponse200, result.body); assert.strictEqual(decodedResponse.signature, mockSignPayloadResponse.signature); - // Verify env passphrase was forwarded to signPayload - const signCall = mockTradingAccount.signPayload.getCall(0); - assert.ok(signCall, 'tradingAccount.signPayload should have been called'); - assert.strictEqual(signCall.args[0].walletPassphrase, 'env_passphrase', 'env passphrase should be forwarded'); - // Cleanup environment variable delete process.env['WALLET_ofc-wallet-id-123_PASSPHRASE']; }); - it('should pass undefined walletPassphrase to signPayload when no passphrase in body or env (KMS path)', async function () { - const requestBody = { - walletId: 'ofc-wallet-id-no-passphrase', - payload: { amount: '1000000', currency: 'USD' }, - // no walletPassphrase - }; - - // Ensure no env var is set for this wallet - delete process.env['WALLET_ofc-wallet-id-no-passphrase_PASSPHRASE']; - - const mockTradingAccount = { - signPayload: sinon.stub().resolves(mockSignPayloadResponse.signature), - }; - - const mockWallet = { - id: () => requestBody.walletId, - toTradingAccount: sinon.stub().returns(mockTradingAccount), - }; - - const walletsGetStub = sinon.stub().resolves(mockWallet); - const mockWallets = { get: walletsGetStub }; - const mockCoin = { wallets: sinon.stub().returns(mockWallets) }; - sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - - const result = await agent - .post('/api/v2/ofc/signPayload') - .set('Authorization', 'Bearer test_access_token_12345') - .set('Content-Type', 'application/json') - .send(requestBody); - - assert.strictEqual(result.status, 200); - const decodedResponse = assertDecode(OfcSignPayloadResponse200, result.body); - assert.strictEqual(decodedResponse.signature, mockSignPayloadResponse.signature); - - // signPayload must be called with walletPassphrase=undefined so the SDK routes to KMS - const signCall = mockTradingAccount.signPayload.getCall(0); - assert.ok(signCall, 'tradingAccount.signPayload should have been called'); - assert.strictEqual( - signCall.args[0].walletPassphrase, - undefined, - 'walletPassphrase should be undefined to trigger KMS signing' - ); - }); - - it('should prefer body walletPassphrase over env passphrase', async function () { - const requestBody = { - walletId: 'ofc-wallet-id-123', - payload: { amount: '500' }, - walletPassphrase: 'body_passphrase', - }; - - // Set a different env passphrase — body should win - process.env['WALLET_ofc-wallet-id-123_PASSPHRASE'] = 'env_passphrase'; - - const mockTradingAccount = { - signPayload: sinon.stub().resolves(mockSignPayloadResponse.signature), - }; - - const mockWallet = { - id: () => requestBody.walletId, - toTradingAccount: sinon.stub().returns(mockTradingAccount), - }; - - const walletsGetStub = sinon.stub().resolves(mockWallet); - const mockWallets = { get: walletsGetStub }; - const mockCoin = { wallets: sinon.stub().returns(mockWallets) }; - sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - - const result = await agent - .post('/api/v2/ofc/signPayload') - .set('Authorization', 'Bearer test_access_token_12345') - .set('Content-Type', 'application/json') - .send(requestBody); - - assert.strictEqual(result.status, 200); - - // body passphrase should take precedence - const signCall = mockTradingAccount.signPayload.getCall(0); - assert.ok(signCall, 'tradingAccount.signPayload should have been called'); - assert.strictEqual( - signCall.args[0].walletPassphrase, - 'body_passphrase', - 'body passphrase should take precedence over env' - ); - - delete process.env['WALLET_ofc-wallet-id-123_PASSPHRASE']; - }); - it('should successfully sign complex nested JSON payload', async function () { const requestBody = { walletId: 'ofc-wallet-id-123', diff --git a/modules/sdk-core/src/bitgo/trading/iTradingAccount.ts b/modules/sdk-core/src/bitgo/trading/iTradingAccount.ts index b99dfd8596..462c7e25ba 100644 --- a/modules/sdk-core/src/bitgo/trading/iTradingAccount.ts +++ b/modules/sdk-core/src/bitgo/trading/iTradingAccount.ts @@ -1,13 +1,8 @@ import { ITradingNetwork } from './network'; -/** - * Parameters for the signing a payload from the trading account - * @param payload - The payload to sign - * @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. - */ export interface SignPayloadParameters { payload: string | Record; - walletPassphrase?: string; + walletPassphrase: string; } export interface ITradingAccount { diff --git a/modules/sdk-core/src/bitgo/trading/network/network.ts b/modules/sdk-core/src/bitgo/trading/network/network.ts index 90902cac32..ed8698d721 100644 --- a/modules/sdk-core/src/bitgo/trading/network/network.ts +++ b/modules/sdk-core/src/bitgo/trading/network/network.ts @@ -109,7 +109,7 @@ export class TradingNetwork implements ITradingNetwork { /** * Prepare an allocation for submission - * @param {string} walletPassphrase ofc wallet passphrase - required only when signing via user key + * @param {string} walletPassphrase ofc wallet passphrase * @param {string} connectionId connection to whom to make the allocation or deallocation * @param {string=} clientExternalId one time generated uuid v4 * @param {string} currency currency for which the allocation should be made. e.g. btc / tbtc @@ -130,7 +130,10 @@ export class TradingNetwork implements ITradingNetwork { } const payload = JSON.stringify(body); - const signature = await this.wallet.toTradingAccount().signPayload({ payload, walletPassphrase }); + + const prv = await this.wallet.getPrv({ walletPassphrase }); + const signedBuffer: Buffer = await this.wallet.baseCoin.signMessage({ prv }, payload); + const signature = signedBuffer.toString('hex'); return { ...body, diff --git a/modules/sdk-core/src/bitgo/trading/network/types.ts b/modules/sdk-core/src/bitgo/trading/network/types.ts index 0d120fa07c..e6da5283f4 100644 --- a/modules/sdk-core/src/bitgo/trading/network/types.ts +++ b/modules/sdk-core/src/bitgo/trading/network/types.ts @@ -125,7 +125,7 @@ export type GetNetworkAllocationByIdResponse = { }; export type PrepareNetworkAllocationParams = Omit & { - walletPassphrase?: string; + walletPassphrase: string; clientExternalId?: string; nonce?: string; }; diff --git a/modules/sdk-core/src/bitgo/trading/tradingAccount.ts b/modules/sdk-core/src/bitgo/trading/tradingAccount.ts index 19eaaa4f66..40489647ae 100644 --- a/modules/sdk-core/src/bitgo/trading/tradingAccount.ts +++ b/modules/sdk-core/src/bitgo/trading/tradingAccount.ts @@ -23,51 +23,13 @@ export class TradingAccount implements ITradingAccount { } /** - * Signs an arbitrary payload. Use the user key if passphrase is provided, or the BitGo key if not. + * Signs an arbitrary payload with the user key on this trading account * @param params * @param params.payload arbitrary payload object (string | Record) * @param params.walletPassphrase passphrase on this trading account, used to unlock the account user key * @returns hex-encoded signature of the payload */ async signPayload(params: SignPayloadParameters): Promise { - // if no passphrase is provided, attempt to sign using the wallet's bitgo key remotely - if (!params.walletPassphrase) { - return this.signPayloadByBitGoKey(params); - } - // if a passphrase is provided, we must be trying to sign using the user private key - decrypt and sign locally - return this.signPayloadByUserKey(params); - } - - /** - * Signs the payload of a trading account via the trading account BitGo key - * @param params - * @private - */ - private async signPayloadByBitGoKey(params: Omit): Promise { - const walletData = this.wallet.toJSON(); - if (walletData.userKeySigningRequired) { - throw new Error( - 'Wallet must use user key to sign ofc transaction, please provide the wallet passphrase or visit your wallet settings page to configure one.' - ); - } - if (walletData.keys.length < 2) { - throw new Error( - 'Wallet does not support BitGo signing. Please reach out to support@bitgo.com to resolve this issue.' - ); - } - - const url = this.wallet.url('/tx/sign'); - const { signature } = await this.wallet.bitgo.post(url).send(params.payload).result(); - - return signature; - } - - /** - * Signs the payload of a trading account locally by fetching the user's encrypted private key and decrypt using passphrase - * @param params - * @private - */ - private async signPayloadByUserKey(params: SignPayloadParameters): Promise { const key = (await this.wallet.baseCoin.keychains().get({ id: this.wallet.keyIds()[0] })) as any; const prv = this.wallet.bitgo.decrypt({ input: key.encryptedPrv, diff --git a/modules/sdk-core/src/bitgo/wallet/iWallet.ts b/modules/sdk-core/src/bitgo/wallet/iWallet.ts index 62d790d9c0..1f65b5a977 100644 --- a/modules/sdk-core/src/bitgo/wallet/iWallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/iWallet.ts @@ -909,7 +909,6 @@ export interface WalletData { evmKeyRingReferenceWalletId?: string; isParent?: boolean; enabledChildChains?: string[]; - userKeySigningRequired?: string; } export interface RecoverTokenOptions { diff --git a/modules/sdk-core/src/coins/ofc.ts b/modules/sdk-core/src/coins/ofc.ts index 18893123f6..4e089ba5a8 100644 --- a/modules/sdk-core/src/coins/ofc.ts +++ b/modules/sdk-core/src/coins/ofc.ts @@ -15,7 +15,6 @@ import { SignTransactionOptions, VerifyAddressOptions, VerifyTransactionOptions, - Wallet, } from '../'; export class Ofc extends BaseCoin { @@ -105,26 +104,6 @@ export class Ofc extends BaseCoin { throw new MethodNotImplementedError(); } - /** - * Signs a message using a trading wallet's BitGo Key - * @param wallet - uses the BitGo key of this trading wallet to sign the message remotely in a KMS - * @param message - */ - async signMessage(wallet: Wallet, message: string): Promise; - /** - * Signs a message using the private key - * @param key - uses the private key to sign the message - * @param message - */ - async signMessage(key: { prv: string }, message: string): Promise; - async signMessage(keyOrWallet: { prv: string } | Wallet, message: string): Promise { - if (!(keyOrWallet instanceof Wallet)) { - return super.signMessage(keyOrWallet, message); - } - const signatureHexString = await keyOrWallet.toTradingAccount().signPayload({ payload: message }); - return Buffer.from(signatureHexString, 'hex'); - } - /** @inheritDoc */ auditDecryptedKey(params: AuditDecryptedKeyParams) { throw new MethodNotImplementedError(); diff --git a/modules/sdk-core/src/coins/ofcToken.ts b/modules/sdk-core/src/coins/ofcToken.ts index e100285443..42cef18e75 100644 --- a/modules/sdk-core/src/coins/ofcToken.ts +++ b/modules/sdk-core/src/coins/ofcToken.ts @@ -9,7 +9,6 @@ import { SignTransactionOptions as BaseSignTransactionOptions, SignedTransaction, ITransactionRecipient, - Wallet, } from '../'; import { isBolt11Invoice } from '../lightning'; @@ -19,8 +18,7 @@ export interface SignTransactionOptions extends BaseSignTransactionOptions { txPrebuild: { payload: string; }; - prv?: string; - wallet?: Wallet; + prv: string; } export { OfcTokenConfig }; @@ -109,25 +107,15 @@ export class OfcToken extends Ofc { } /** - * Signs a half-signed OFC transaction. - * Signs the transaction remotely using the BitGo key if prv is not provided. + * Assemble keychain and half-sign prebuilt transaction * @param params * @returns {Promise} */ async signTransaction(params: SignTransactionOptions): Promise { const txPrebuild = params.txPrebuild; const payload = txPrebuild.payload; - - let signature: string; - if (params.wallet) { - signature = await params.wallet.toTradingAccount().signPayload({ payload, walletPassphrase: params.prv }); - } else if (params.prv) { - const signatureBuffer = (await this.signMessage({ prv: params.prv }, payload)) as any; - signature = signatureBuffer.toString('hex'); - } else { - throw new Error('You must pass in either one of wallet or prv'); - } - + const signatureBuffer = (await this.signMessage(params, payload)) as any; + const signature: string = signatureBuffer.toString('hex'); return { halfSigned: { payload, signature } } as any; } diff --git a/modules/sdk-core/test/unit/bitgo/trading/tradingAccount.ts b/modules/sdk-core/test/unit/bitgo/trading/tradingAccount.ts deleted file mode 100644 index f05262effa..0000000000 --- a/modules/sdk-core/test/unit/bitgo/trading/tradingAccount.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @prettier - */ -import sinon from 'sinon'; -import 'should'; -import { TradingAccount } from '../../../../src/bitgo/trading/tradingAccount'; - -describe('TradingAccount', function () { - let tradingAccount: TradingAccount; - let mockBitGo: any; - let mockWallet: any; - let mockBaseCoin: any; - - const enterpriseId = 'test-enterprise-id'; - const walletPassphrase = 'test-passphrase'; - const encryptedPrv = 'encrypted-prv'; - const decryptedPrv = 'decrypted-prv'; - const signature = 'aabbccdd'; - - beforeEach(function () { - const postStub = sinon.stub(); - postStub.returns({ - send: sinon.stub().returns({ - result: sinon.stub().resolves({ signature }), - }), - }); - - mockBitGo = { - post: postStub, - decrypt: sinon.stub().returns(decryptedPrv), - }; - - mockBaseCoin = { - keychains: sinon.stub().returns({ - get: sinon.stub().resolves({ encryptedPrv }), - }), - signMessage: sinon.stub().resolves(Buffer.from(signature, 'hex')), - }; - - mockWallet = { - id: sinon.stub().returns('test-wallet-id'), - keyIds: sinon.stub().returns(['user-key-id', 'backup-key-id', 'bitgo-key-id']), - url: sinon.stub().returns('https://example.com/wallet/test-wallet-id/tx/sign'), - toJSON: sinon.stub().returns({ - id: 'test-wallet-id', - keys: ['user-key-id', 'backup-key-id', 'bitgo-key-id'], - userKeySigningRequired: undefined, - }), - baseCoin: mockBaseCoin, - bitgo: mockBitGo, - }; - - tradingAccount = new TradingAccount(enterpriseId, mockWallet, mockBitGo); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe('signPayload', function () { - const payload = { data: 'test-payload' }; - const payloadString = 'test-payload-string'; - - describe('without walletPassphrase (BitGo remote signing)', function () { - it('should sign using the BitGo key remotely when no passphrase is provided', async function () { - const result = await tradingAccount.signPayload({ payload }); - - mockWallet.toJSON.calledOnce.should.be.true(); - mockWallet.url.calledWith('/tx/sign').should.be.true(); - mockBitGo.post.calledOnce.should.be.true(); - result.should.equal(signature); - }); - - it('should sign a string payload remotely when no passphrase is provided', async function () { - const result = await tradingAccount.signPayload({ payload: payloadString }); - - result.should.equal(signature); - }); - - it('should throw if userKeySigningRequired is set and no passphrase is provided', async function () { - mockWallet.toJSON.returns({ - id: 'test-wallet-id', - keys: ['user-key-id', 'backup-key-id', 'bitgo-key-id'], - userKeySigningRequired: 'true', - }); - - await tradingAccount - .signPayload({ payload }) - .should.be.rejectedWith( - 'Wallet must use user key to sign ofc transaction, please provide the wallet passphrase or visit your wallet settings page to configure one.' - ); - }); - - it('should throw if wallet has fewer than 2 keys and no passphrase is provided', async function () { - mockWallet.toJSON.returns({ - id: 'test-wallet-id', - keys: ['user-key-id'], - userKeySigningRequired: undefined, - }); - - await tradingAccount - .signPayload({ payload }) - .should.be.rejectedWith( - 'Wallet does not support BitGo signing. Please reach out to support@bitgo.com to resolve this issue.' - ); - }); - }); - - describe('with walletPassphrase (local user key signing)', function () { - it('should decrypt the user key and sign the payload locally', async function () { - const result = await tradingAccount.signPayload({ payload, walletPassphrase }); - - mockBaseCoin.keychains().get.calledWith({ id: 'user-key-id' }).should.be.true(); - mockBitGo.decrypt.calledWith({ input: encryptedPrv, password: walletPassphrase }).should.be.true(); - mockBaseCoin.signMessage.calledOnce.should.be.true(); - result.should.equal(Buffer.from(signature, 'hex').toString('hex')); - }); - - it('should stringify a Record payload before signing locally', async function () { - await tradingAccount.signPayload({ payload, walletPassphrase }); - - const signMessageCall = mockBaseCoin.signMessage.getCall(0); - signMessageCall.args[1].should.equal(JSON.stringify(payload)); - }); - - it('should pass a string payload directly to signMessage', async function () { - await tradingAccount.signPayload({ payload: payloadString, walletPassphrase }); - - const signMessageCall = mockBaseCoin.signMessage.getCall(0); - signMessageCall.args[1].should.equal(payloadString); - }); - }); - }); -}); diff --git a/modules/sdk-core/test/unit/bitgo/wallet/ofcWalletSignTransaction.ts b/modules/sdk-core/test/unit/bitgo/wallet/ofcWalletSignTransaction.ts deleted file mode 100644 index 4f0d26bbe4..0000000000 --- a/modules/sdk-core/test/unit/bitgo/wallet/ofcWalletSignTransaction.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @prettier - */ -import sinon from 'sinon'; -import 'should'; -import { Wallet } from '../../../../src'; - -describe('Wallet - OFC signTransaction', function () { - let wallet: Wallet; - let mockBitGo: any; - let mockBaseCoin: any; - let mockWalletData: any; - - beforeEach(function () { - mockBitGo = { - url: sinon.stub().returns('https://test.bitgo.com'), - post: sinon.stub(), - get: sinon.stub(), - setRequestTracer: sinon.stub(), - }; - - mockBaseCoin = { - getFamily: sinon.stub().returns('ofc'), - url: sinon.stub().returns('https://test.bitgo.com/wallet'), - keychains: sinon.stub(), - supportsTss: sinon.stub().returns(false), - getMPCAlgorithm: sinon.stub(), - presignTransaction: sinon.stub().resolvesArg(0), - keyIdsForSigning: sinon.stub().returns([0]), - signTransaction: sinon.stub().resolves({ halfSigned: { payload: 'test', signature: 'aabbcc' } }), - }; - - mockWalletData = { - id: 'test-wallet-id', - coin: 'ofcusdt', - keys: ['user-key', 'backup-key', 'bitgo-key'], - multisigType: 'onchain', - enterprise: 'ent-id', - }; - - wallet = new Wallet(mockBitGo, mockBaseCoin, mockWalletData); - }); - - afterEach(function () { - sinon.restore(); - }); - - it('should pass wallet instance to baseCoin.signTransaction', async function () { - const txPrebuild = { txInfo: { payload: '{"amount":"100"}' } } as any; - const prv = 'test-prv'; - - await wallet.signTransaction({ txPrebuild, prv }); - - mockBaseCoin.signTransaction.calledOnce.should.be.true(); - const callArgs = mockBaseCoin.signTransaction.getCall(0).args[0]; - callArgs.wallet.should.equal(wallet); - }); - - it('should pass prv to baseCoin.signTransaction when provided directly', async function () { - const txPrebuild = { txInfo: { payload: '{"amount":"100"}' } } as any; - const prv = 'test-prv'; - - await wallet.signTransaction({ txPrebuild, prv }); - - const callArgs = mockBaseCoin.signTransaction.getCall(0).args[0]; - callArgs.prv.should.equal(prv); - }); - - it('should pass wallet instance to baseCoin.signTransaction even when no prv is available', async function () { - sinon.stub(wallet, 'getUserPrv').returns(undefined as any); - const txPrebuild = { txInfo: { payload: '{"amount":"100"}' } } as any; - - await wallet.signTransaction({ txPrebuild }); - - mockBaseCoin.signTransaction.calledOnce.should.be.true(); - const callArgs = mockBaseCoin.signTransaction.getCall(0).args[0]; - callArgs.wallet.should.equal(wallet); - }); -}); diff --git a/modules/sdk-core/test/unit/coins/ofc.ts b/modules/sdk-core/test/unit/coins/ofc.ts deleted file mode 100644 index 780c288645..0000000000 --- a/modules/sdk-core/test/unit/coins/ofc.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @prettier - */ -import sinon from 'sinon'; -import 'should'; -import { Ofc } from '../../../src/coins/ofc'; -import { OfcToken } from '../../../src/coins/ofcToken'; -import { BaseCoin } from '../../../src/bitgo/baseCoin/baseCoin'; -import { Wallet } from '../../../src'; - -const TEST_TOKEN_CONFIG = { - coin: 'ofcusdt', - decimalPlaces: 6, - name: 'OFCUSDT', - type: 'ofcusdt', - backingCoin: 'usdt', - isFiat: false, -}; - -describe('Ofc / OfcToken', function () { - let mockBitGo: any; - - beforeEach(function () { - mockBitGo = { url: sinon.stub().returns('https://test.bitgo.com') }; - }); - - afterEach(function () { - sinon.restore(); - }); - - describe('signMessage', function () { - let ofc: Ofc; - - beforeEach(function () { - ofc = new Ofc(mockBitGo); - }); - - describe('with a Wallet instance', function () { - it('should delegate to wallet.toTradingAccount().signPayload() and return a Buffer', async function () { - const hexSignature = 'deadbeef'; - const signPayloadStub = sinon.stub().resolves(hexSignature); - - const mockBaseCoin = { supportsTss: sinon.stub().returns(false), getMPCAlgorithm: sinon.stub() }; - const walletData = { - id: 'wallet-id', - keys: ['key1', 'key2', 'key3'], - multisigType: 'onchain', - enterprise: 'ent-id', - }; - const wallet = new Wallet(mockBitGo, mockBaseCoin as any, walletData); - sinon.stub(wallet, 'toTradingAccount').returns({ signPayload: signPayloadStub } as any); - - const message = 'test message'; - const result = await ofc.signMessage(wallet, message); - - signPayloadStub.calledOnceWith({ payload: message }).should.be.true(); - result.should.deepEqual(Buffer.from(hexSignature, 'hex')); - }); - }); - - describe('with a prv key', function () { - it('should delegate to the base class signMessage', async function () { - const expectedResult = Buffer.from('basesignature', 'hex'); - const superSignMessageStub = sinon.stub(BaseCoin.prototype, 'signMessage').resolves(expectedResult); - - const key = { - prv: 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqhuCo36EkzGH6qiT9mJHBvuPKtLRYD4NxFb5hgXMQBB2LLT6mxLDHHo', - }; - const message = 'test message'; - const result = await ofc.signMessage(key, message); - - superSignMessageStub.calledOnceWith(key, message).should.be.true(); - result.should.equal(expectedResult); - }); - }); - }); - - describe('signTransaction (OfcToken)', function () { - let ofcToken: OfcToken; - const payload = '{"amount":"100","from":"alice","to":"bob"}'; - - beforeEach(function () { - ofcToken = new OfcToken(mockBitGo, TEST_TOKEN_CONFIG); - }); - - describe('with wallet and no prv (BitGo remote signing)', function () { - it('should call wallet.toTradingAccount().signPayload() without a passphrase', async function () { - const hexSignature = 'aabbccdd'; - const signPayloadStub = sinon.stub().resolves(hexSignature); - const mockWallet = { toTradingAccount: sinon.stub().returns({ signPayload: signPayloadStub }) }; - - const result = await ofcToken.signTransaction({ txPrebuild: { payload }, wallet: mockWallet as any }); - - signPayloadStub.calledOnceWith({ payload, walletPassphrase: undefined }).should.be.true(); - result.should.deepEqual({ halfSigned: { payload, signature: hexSignature } }); - }); - }); - - describe('with wallet and prv (local signing routed through wallet)', function () { - it('should call wallet.toTradingAccount().signPayload() with the wallet passphrase', async function () { - const hexSignature = 'aabbccdd'; - const passphrase = 'test-passphrase'; - const signPayloadStub = sinon.stub().resolves(hexSignature); - const mockWallet = { toTradingAccount: sinon.stub().returns({ signPayload: signPayloadStub }) }; - - const result = await ofcToken.signTransaction({ - txPrebuild: { payload }, - wallet: mockWallet as any, - prv: passphrase, - }); - - signPayloadStub.calledOnceWith({ payload, walletPassphrase: passphrase }).should.be.true(); - result.should.deepEqual({ halfSigned: { payload, signature: hexSignature } }); - }); - }); - - describe('with prv only (local signing without wallet)', function () { - it('should sign locally and return the correct halfSigned result', async function () { - const signatureBytes = Buffer.from('ccddee', 'hex'); - sinon.stub(BaseCoin.prototype, 'signMessage').resolves(signatureBytes); - - const result = await ofcToken.signTransaction({ txPrebuild: { payload }, prv: 'test-prv' }); - - result.should.deepEqual({ halfSigned: { payload, signature: signatureBytes.toString('hex') } }); - }); - - it('should pass the prv to signMessage', async function () { - const signatureBytes = Buffer.from('ccddee', 'hex'); - const superSignMessageStub = sinon.stub(BaseCoin.prototype, 'signMessage').resolves(signatureBytes); - const prv = 'test-prv'; - - await ofcToken.signTransaction({ txPrebuild: { payload }, prv }); - - superSignMessageStub.calledOnceWith({ prv }, payload).should.be.true(); - }); - }); - - describe('with neither wallet nor prv', function () { - it('should throw an error', async function () { - await ofcToken - .signTransaction({ txPrebuild: { payload } }) - .should.be.rejectedWith('You must pass in either one of wallet or prv'); - }); - }); - }); -});