diff --git a/src/__tests__/api/enclaved/signMpcTransaction.test.ts b/src/__tests__/api/enclaved/signMpcTransaction.test.ts index aa06902..1d57b2d 100644 --- a/src/__tests__/api/enclaved/signMpcTransaction.test.ts +++ b/src/__tests__/api/enclaved/signMpcTransaction.test.ts @@ -122,7 +122,7 @@ describe('signMpcTransaction', () => { source: 'user', pub: 'DSqMPMsMAbEJVNuPKv1ZFdzt6YvJaDPDddfeW7ajtqds', txRequest: mockTxRequest, - bitgoGpgPubKey: bitgoGpgPubKey, + bitgoPublicGpgKey: bitgoGpgPubKey, }; const mockDataKeyResponse = { @@ -275,46 +275,19 @@ describe('signMpcTransaction', () => { }); it('should fail for unsupported share type', async () => { - const user = MPC.keyShare(1, 2, 3); - const backup = MPC.keyShare(2, 2, 3); - const bitgo = MPC.keyShare(3, 2, 3); - - const userSigningMaterial = { - uShare: user.uShare, - bitgoYShare: bitgo.yShares[1], - backupYShare: backup.yShares[1], - }; - - const mockKmsResponse = { - prv: JSON.stringify(userSigningMaterial), - pub: 'DSqMPMsMAbEJVNuPKv1ZFdzt6YvJaDPDddfeW7ajtqds', - source: 'user', - type: 'independent', - }; - const input = { source: 'user', pub: 'DSqMPMsMAbEJVNuPKv1ZFdzt6YvJaDPDddfeW7ajtqds', txRequest: mockTxRequest, }; - const kmsNock = nock(kmsUrl) - .get(`/key/${input.pub}`) - .query({ source: 'user', useLocalEncipherment: false }) - .reply(200, mockKmsResponse); - const response = await agent .post(`/api/${coin}/mpc/sign/invalid`) .set('Authorization', `Bearer ${accessToken}`) .send(input); - response.status.should.equal(500); + response.status.should.equal(400); response.body.should.have.property('error'); - response.body.details.should.equal( - 'Share type invalid not supported for EDDSA, only commitment, G and R share generation is supported.', - ); - - kmsNock.done(); }); it('should fail when required fields are missing', async () => { @@ -343,7 +316,7 @@ describe('signMpcTransaction', () => { const derivationPath = 'm/0'; const [userShare, backupShare, bitgoShare] = await DklsUtils.generateDKGKeyShares(); - assert(backupShare, 'backupShare is not defined'); + assert(backupShare, 'Backup share is not defined'); const userKeyShare = userShare.getKeyShare().toString('base64'); @@ -389,7 +362,7 @@ describe('signMpcTransaction', () => { source: 'user', pub: 'mock-ecdsa-public-key', txRequest: mockTxRequest, - bitgoGpgPubKey: bitgoGpgKey.public, + bitgoPublicGpgKey: bitgoGpgKey.public, }; const mockDataKeyResponse = { @@ -449,7 +422,7 @@ describe('signMpcTransaction', () => { source: 'user', pub: 'mock-ecdsa-public-key', txRequest: txRequestRound1, - bitgoGpgPubKey: bitgoGpgKey.public, + bitgoPublicGpgKey: bitgoGpgKey.public, encryptedDataKey, encryptedUserGpgPrvKey, encryptedRound1Session, @@ -501,7 +474,7 @@ describe('signMpcTransaction', () => { source: 'user', pub: 'mock-ecdsa-public-key', txRequest: txRequestRound2, - bitgoGpgPubKey: bitgoGpgKey.public, + bitgoPublicGpgKey: bitgoGpgKey.public, encryptedDataKey, encryptedUserGpgPrvKey, encryptedRound2Session, @@ -643,36 +616,19 @@ describe('signMpcTransaction', () => { }); it('should fail for unsupported share type', async () => { - const mockKmsResponse = { - prv: 'mock-ecdsa-private-key', - pub: 'mock-ecdsa-public-key', - source: 'user', - type: 'independent', - }; - const input = { source: 'user', pub: 'mock-ecdsa-public-key', txRequest: mockTxRequest, }; - const kmsNock = nock(kmsUrl) - .get(`/key/${input.pub}`) - .query({ source: 'user', useLocalEncipherment: true }) - .reply(200, mockKmsResponse); - const response = await agent .post(`/api/${coin}/mpc/sign/invalid`) .set('Authorization', `Bearer ${accessToken}`) .send(input); - response.status.should.equal(500); + response.status.should.equal(400); response.body.should.have.property('error'); - response.body.details.should.equal( - 'Share type invalid not supported for MPCv2, only MPCv2Round1, MPCv2Round2 and MPCv2Round3 is supported.', - ); - - kmsNock.done(); }); }); }); diff --git a/src/__tests__/api/master/ecdsa.test.ts b/src/__tests__/api/master/ecdsa.test.ts index 69292da..f2eda71 100644 --- a/src/__tests__/api/master/ecdsa.test.ts +++ b/src/__tests__/api/master/ecdsa.test.ts @@ -6,7 +6,6 @@ import { Wallet, TxRequest, IRequestTracer, - TxRequestVersion, Environments, RequestTracer, EcdsaMPCv2Utils, @@ -16,7 +15,7 @@ import { TransactionState, } from '@bitgo/sdk-core'; import { EnclavedExpressClient } from '../../../../src/api/master/clients/enclavedExpressClient'; -import { handleEcdsaSigning } from '../../../../src/api/master/handlers/ecdsa'; +import { signAndSendEcdsaMPCv2FromTxRequest } from '../../../api/master/handlers/ecdsaMPCv2'; import { BitGo } from 'bitgo'; import { readKey } from 'openpgp'; @@ -69,7 +68,7 @@ describe('Ecdsa Signing Handler', () => { it('should successfully sign an ECDSA MPCv2 transaction', async () => { const txRequest: TxRequest = { txRequestId: 'test-tx-request-id', - apiVersion: '2.0.0' as TxRequestVersion, + apiVersion: 'full', enterpriseId: 'test-enterprise-id', transactions: [], state: 'pendingUserSignature', @@ -89,15 +88,6 @@ describe('Ecdsa Signing Handler', () => { const pgpKey = await readKey({ armoredKey: bitgoGpgKey.publicKey }); sinon.stub(EcdsaMPCv2Utils.prototype, 'getBitgoMpcv2PublicGpgKey').resolves(pgpKey); - // Mock getTxRequest call - const getTxRequestNock = nock(bitgoApiUrl) - .get(`/api/v2/wallet/${walletId}/txrequests`) - .query({ txRequestIds: 'test-tx-request-id', latest: true }) - .matchHeader('any', () => true) - .reply(200, { - txRequests: [txRequest], - }); - // Mock sendSignatureShareV2 calls for each round const round1SignatureShare: SignatureShareRecord = { from: SignatureShareType.USER, @@ -132,7 +122,7 @@ describe('Ecdsa Signing Handler', () => { .post(`/api/v2/wallet/${walletId}/txrequests/test-tx-request-id/transactions/0/sign`) .matchHeader('any', () => true) .reply(200, { - txRequest: round1TxRequest, + ...round1TxRequest, }); const round2SignatureShare: SignatureShareRecord = { @@ -171,7 +161,7 @@ describe('Ecdsa Signing Handler', () => { .post(`/api/v2/wallet/${walletId}/txrequests/test-tx-request-id/transactions/0/sign`) .matchHeader('any', () => true) .reply(200, { - txRequest: round2TxRequest, + ...round2TxRequest, }); const round3SignatureShare: SignatureShareRecord = { @@ -194,15 +184,13 @@ describe('Ecdsa Signing Handler', () => { .post(`/api/v2/wallet/${walletId}/txrequests/test-tx-request-id/transactions/0/sign`) .matchHeader('any', () => true) .reply(200, { - txRequest: { - ...round2TxRequest, - transactions: [ - { - ...round2TxRequest.transactions![0], - signatureShares: [round1SignatureShare, round2SignatureShare, round3SignatureShare], - }, - ], - }, + ...round2TxRequest, + transactions: [ + { + ...round2TxRequest.transactions![0], + signatureShares: [round1SignatureShare, round2SignatureShare, round3SignatureShare], + }, + ], }); // Mock sendTxRequest call @@ -240,10 +228,10 @@ describe('Ecdsa Signing Handler', () => { signatureShareRound3: round3SignatureShare, }); - const result = await handleEcdsaSigning( + const result = await signAndSendEcdsaMPCv2FromTxRequest( bitgo, wallet, - txRequest.txRequestId, + txRequest, enclavedExpressClient, 'user', userPubKey, @@ -255,7 +243,6 @@ describe('Ecdsa Signing Handler', () => { state: 'signed', }); - getTxRequestNock.done(); sendSignatureShareV2Round1Nock.done(); sendSignatureShareV2Round2Nock.done(); sendSignatureShareV2Round3Nock.done(); diff --git a/src/__tests__/api/master/eddsa.test.ts b/src/__tests__/api/master/eddsa.test.ts index a58225f..8b3b43d 100644 --- a/src/__tests__/api/master/eddsa.test.ts +++ b/src/__tests__/api/master/eddsa.test.ts @@ -6,7 +6,6 @@ import { Wallet, TxRequest, IRequestTracer, - TxRequestVersion, Environments, RequestTracer, EddsaUtils, @@ -17,7 +16,8 @@ import { handleEddsaSigning } from '../../../../src/api/master/handlers/eddsa'; import { BitGo } from 'bitgo'; import { readKey } from 'openpgp'; -describe('Eddsa Signing Handler', () => { +// TODO: Re-enable once using EDDSA Custom signing fns +xdescribe('Eddsa Signing Handler', () => { let bitgo: BitGoBase; let wallet: Wallet; let enclavedExpressClient: EnclavedExpressClient; @@ -62,9 +62,30 @@ describe('Eddsa Signing Handler', () => { it('should successfully sign an Eddsa transaction', async () => { const txRequest: TxRequest = { txRequestId: 'test-tx-request-id', - apiVersion: '2.0.0' as TxRequestVersion, + apiVersion: 'full', enterpriseId: 'test-enterprise-id', - transactions: [], + transactions: [ + { + state: 'pendingSignature', + unsignedTx: { + derivationPath: 'm/0', + signableHex: 'testMessage', + serializedTxHex: 'testSerializedTxHex', + }, + signatureShares: [ + { + share: 'bitgo-to-user-r-share', + from: 'bitgo', + to: 'user', + }, + { + share: 'user-to-bitgo-r-share', + from: 'user', + to: 'bitgo', + }, + ], + }, + ], state: 'pendingUserSignature', walletId: 'test-wallet-id', walletType: 'hot', @@ -234,7 +255,7 @@ describe('Eddsa Signing Handler', () => { const result = await handleEddsaSigning( bitgo, wallet, - txRequest.txRequestId, + txRequest, enclavedExpressClient, userPubKey, reqId, diff --git a/src/__tests__/api/master/sendMany.test.ts b/src/__tests__/api/master/sendMany.test.ts index 0b4e10f..74f5e23 100644 --- a/src/__tests__/api/master/sendMany.test.ts +++ b/src/__tests__/api/master/sendMany.test.ts @@ -9,15 +9,14 @@ import { Environments, Wallet } from '@bitgo/sdk-core'; import { Coin } from 'bitgo'; import assert from 'assert'; import * as eddsa from '../../../api/master/handlers/eddsa'; -import * as ecdsa from '../../../api/master/handlers/ecdsa'; describe('POST /api/:coin/wallet/:walletId/sendmany', () => { let agent: request.SuperAgentTest; const enclavedExpressUrl = 'http://enclaved.invalid'; const bitgoApiUrl = Environments.test.uri; - const coin = 'tbtc'; const accessToken = 'test-token'; const walletId = 'test-wallet-id'; + const coin = 'tbtc'; before(() => { nock.disableNetConnect(); @@ -49,6 +48,7 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { }); describe('SendMany Multisig:', () => { + const coin = 'tbtc'; it('should send many transactions by calling the enclaved express service', async () => { // Mock wallet get request const walletGetNock = nock(bitgoApiUrl) @@ -226,7 +226,27 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { }); describe('SendMany TSS EDDSA:', () => { + const coin = 'tsol'; it('should send many transactions using EDDSA TSS signing', async () => { + const mockTxRequest = { + txRequestId: 'test-tx-request-id', + state: 'signed', + apiVersion: 'full', + pendingApprovalId: 'test-pending-approval-id', + transactions: [ + { + unsignedTx: { + derivationPath: 'm/0', + signableHex: 'testMessage', + }, + signedTx: { + id: 'test-tx-id', + tx: 'signed-transaction', + }, + }, + ], + }; + // Mock wallet get request for TSS wallet const walletGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/wallet/${walletId}`) @@ -262,34 +282,28 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { walletId, }); - const verifyStub = sinon.stub(Coin.Btc.prototype, 'verifyTransaction').resolves(true); + const verifyStub = sinon.stub(Coin.Tsol.prototype, 'verifyTransaction').resolves(true); // Mock multisigType to return 'tss' const multisigTypeStub = sinon.stub(Wallet.prototype, 'multisigType').returns('tss'); - // Mock getMPCAlgorithm to return 'eddsa' - const getMPCAlgorithmStub = sinon - .stub(Coin.Btc.prototype, 'getMPCAlgorithm') - .returns('eddsa'); - // Mock handleEddsaSigning const handleEddsaSigningStub = sinon.stub().resolves({ - txRequestId: 'test-tx-request-id', - state: 'signed', - apiVersion: 'full', - transactions: [ - { - signedTx: { - id: 'test-tx-id', - tx: 'signed-transaction', - }, - }, - ], + ...mockTxRequest, }); - // Import and stub the handleEddsaSigning function + // Import and stub the signAndSendTxRequests function sinon.stub(eddsa, 'handleEddsaSigning').callsFake(handleEddsaSigningStub); + // Mock getTxRequest call + const getTxRequestNock = nock(bitgoApiUrl) + .get(`/api/v2/wallet/${walletId}/txrequests`) + .query({ txRequestIds: 'test-tx-request-id', latest: true }) + .matchHeader('any', () => true) + .reply(200, { + txRequests: [mockTxRequest], + }); + const response = await agent .post(`/api/${coin}/wallet/${walletId}/sendMany`) .set('Authorization', `Bearer ${accessToken}`) @@ -317,13 +331,14 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { keychainGetNock.done(); sinon.assert.calledOnce(prebuildStub); sinon.assert.calledOnce(verifyStub); - sinon.assert.calledTwice(multisigTypeStub); - sinon.assert.calledOnce(getMPCAlgorithmStub); + sinon.assert.calledThrice(multisigTypeStub); sinon.assert.calledOnce(handleEddsaSigningStub); + getTxRequestNock.done(); }); }); describe('SendMany TSS ECDSA:', () => { + const coin = 'hteth'; it('should send many transactions using ECDSA TSS signing', async () => { // Mock wallet get request for TSS wallet const walletGetNock = nock(bitgoApiUrl) @@ -349,45 +364,35 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { type: 'tss', }); - const prebuildStub = sinon.stub(Wallet.prototype, 'prebuildTransaction').resolves({ - txRequestId: 'test-tx-request-id', - txHex: 'prebuilt-tx-hex', - txInfo: { - nP2SHInputs: 1, - nSegwitInputs: 0, - nOutputs: 2, + const sendManyStub = sinon.stub(Wallet.prototype, 'sendMany').resolves({ + txRequest: { + txRequestId: 'test-tx-request-id', + state: 'signed', + apiVersion: 'full', + pendingApprovalId: 'test-pending-approval-id', + transactions: [ + { + state: 'signed', + unsignedTx: { + derivationPath: 'm/0', + signableHex: 'testMessage', + serializedTxHex: 'testSerializedTxHex', + }, + signatureShares: [], + signedTx: { + id: 'test-tx-id', + tx: 'signed-transaction', + }, + }, + ], }, - walletId, + txid: 'test-tx-id', + tx: 'signed-transaction', }); - const verifyStub = sinon.stub(Coin.Btc.prototype, 'verifyTransaction').resolves(true); - // Mock multisigType to return 'tss' const multisigTypeStub = sinon.stub(Wallet.prototype, 'multisigType').returns('tss'); - // Mock getMPCAlgorithm to return 'ecdsa' - const getMPCAlgorithmStub = sinon - .stub(Coin.Btc.prototype, 'getMPCAlgorithm') - .returns('ecdsa'); - - // Mock handleEcdsaSigning - const handleEcdsaSigningStub = sinon.stub().resolves({ - txRequestId: 'test-tx-request-id', - state: 'signed', - apiVersion: 'full', - transactions: [ - { - signedTx: { - id: 'test-tx-id', - tx: 'signed-transaction', - }, - }, - ], - }); - - // Import and stub the handleEcdsaSigning function - sinon.stub(ecdsa, 'handleEcdsaSigning').callsFake(handleEcdsaSigningStub); - const response = await agent .post(`/api/${coin}/wallet/${walletId}/sendMany`) .set('Authorization', `Bearer ${accessToken}`) @@ -413,11 +418,8 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { walletGetNock.done(); keychainGetNock.done(); - sinon.assert.calledOnce(prebuildStub); - sinon.assert.calledOnce(verifyStub); - sinon.assert.calledTwice(multisigTypeStub); - sinon.assert.calledOnce(getMPCAlgorithmStub); - sinon.assert.calledOnce(handleEcdsaSigningStub); + sinon.assert.calledOnce(sendManyStub); + sinon.assert.calledOnce(multisigTypeStub); }); it('should fail when backup key is used for ECDSA TSS signing', async () => { @@ -445,19 +447,17 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { type: 'tss', }); - const prebuildStub = sinon.stub(Wallet.prototype, 'prebuildTransaction').resolves({ - txRequestId: 'test-tx-request-id', - txHex: 'prebuilt-tx-hex', - txInfo: { - nP2SHInputs: 1, - nSegwitInputs: 0, - nOutputs: 2, + const sendManyStub = sinon.stub(Wallet.prototype, 'sendMany').resolves({ + txRequest: { + txRequestId: 'test-tx-request-id', + state: 'signed', + apiVersion: 'full', + pendingApprovalId: 'test-pending-approval-id', }, - walletId, + txid: 'test-tx-id', + tx: 'signed-transaction', }); - const verifyStub = sinon.stub(Coin.Btc.prototype, 'verifyTransaction').resolves(true); - // Mock multisigType to return 'tss' const multisigTypeStub = sinon.stub(Wallet.prototype, 'multisigType').returns('tss'); @@ -480,9 +480,8 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { walletGetNock.done(); keychainGetNock.done(); - sinon.assert.calledOnce(prebuildStub); - sinon.assert.calledOnce(verifyStub); - sinon.assert.calledTwice(multisigTypeStub); + sinon.assert.notCalled(sendManyStub); + sinon.assert.calledOnce(multisigTypeStub); }); }); diff --git a/src/api/enclaved/handlers/signMpcTransaction.ts b/src/api/enclaved/handlers/signMpcTransaction.ts index 1340712..345d44e 100644 --- a/src/api/enclaved/handlers/signMpcTransaction.ts +++ b/src/api/enclaved/handlers/signMpcTransaction.ts @@ -68,16 +68,16 @@ interface EddsaSigningParams { userToBitgoRShare?: SignShare; encryptedUserToBitgoRShare?: EncryptedSignerShareRecord; bitgoToUserCommitment?: CommitmentShareRecord; - bitgoGpgPubKey?: string; + bitgoPublicGpgKey?: string; } // Unified parameters for handleEcdsaSigning - includes all possible fields interface EcdsaSigningParams { coin: BaseCoin; - shareType: string; + shareType: ShareType; txRequest: TxRequest; prv: string; - bitgoGpgPubKey?: string; + bitgoPublicGpgKey?: string; encryptedDataKey?: string; encryptedUserGpgPrvKey?: string; encryptedRound1Session?: string; @@ -119,15 +119,15 @@ export async function signMpcTransaction(req: EnclavedApiSpecRouteRequest<'v1.mp userToBitgoRShare: req.decoded.userToBitgoRShare, encryptedUserToBitgoRShare: req.decoded.encryptedUserToBitgoRShare, bitgoToUserCommitment: req.decoded.bitgoToUserCommitment, - bitgoGpgPubKey: req.decoded.bitgoGpgPubKey, + bitgoPublicGpgKey: req.decoded.bitgoPublicGpgKey, }); } else if (mpcAlgorithm === MPCType.ECDSA) { return await handleEcdsaMpcV2Signing(req.bitgo, req.config, { coin: coinInstance, - shareType, + shareType: shareType as ShareType, txRequest: req.decoded.txRequest, prv, - bitgoGpgPubKey: req.decoded.bitgoGpgPubKey, + bitgoPublicGpgKey: req.decoded.bitgoPublicGpgKey, encryptedDataKey: req.decoded.encryptedDataKey, encryptedUserGpgPrvKey: req.decoded.encryptedUserGpgPrvKey, encryptedRound1Session: req.decoded.encryptedRound1Session, @@ -164,7 +164,7 @@ async function handleEddsaSigning( userToBitgoRShare, encryptedUserToBitgoRShare, bitgoToUserCommitment, - bitgoGpgPubKey, + bitgoPublicGpgKey, } = params; // Create EddsaUtils instance using the coin's bitgo instance @@ -172,15 +172,15 @@ async function handleEddsaSigning( switch (shareType.toLowerCase()) { case ShareType.Commitment: { - if (!bitgoGpgPubKey) { - throw new Error('bitgoGpgPubKey is required for commitment share generation'); + if (!bitgoPublicGpgKey) { + throw new Error('bitgoPublicGpgKey is required for commitment share generation'); } const dataKey = await generateDataKey({ keyType: 'AES-256', cfg }); const commitmentParams: CommitmentShareParams = { txRequest, prv, walletPassphrase: dataKey.plaintextKey, - bitgoGpgPubKey, + bitgoGpgPubKey: bitgoPublicGpgKey, }; return { ...(await eddsaUtils.createCommitmentShareFromTxRequest(commitmentParams)), @@ -257,8 +257,8 @@ async function handleEcdsaMpcV2Signing( if (!params.encryptedDataKey) { throw new Error('encryptedDataKey from Round 1 is required for MPCv2 Round 2'); } - if (!params.bitgoGpgPubKey) { - throw new Error('bitgoGpgPubKey is required for MPCv2 Round 2'); + if (!params.bitgoPublicGpgKey) { + throw new Error('bitgoPublicGpgKey is required for MPCv2 Round 2'); } if (!params.encryptedUserGpgPrvKey) { throw new Error('encryptedUserGpgPrvKey is required for MPCv2 Round 2'); @@ -274,7 +274,7 @@ async function handleEcdsaMpcV2Signing( txRequest: params.txRequest, prv: params.prv, walletPassphrase: plaintextDataKey, - bitgoPublicGpgKey: params.bitgoGpgPubKey, + bitgoPublicGpgKey: params.bitgoPublicGpgKey, encryptedUserGpgPrvKey: params.encryptedUserGpgPrvKey, encryptedRound1Session: params.encryptedRound1Session, }); @@ -283,7 +283,7 @@ async function handleEcdsaMpcV2Signing( if (!params.encryptedDataKey) { throw new Error('encryptedDataKey from Round 1 is required for MPCv2 Round 3'); } - if (!params.bitgoGpgPubKey) { + if (!params.bitgoPublicGpgKey) { throw new Error('bitgoGpgPubKey is required for MPCv2 Round 3'); } if (!params.encryptedUserGpgPrvKey) { @@ -300,7 +300,7 @@ async function handleEcdsaMpcV2Signing( txRequest: params.txRequest, prv: params.prv, walletPassphrase: plaintextDataKey, - bitgoPublicGpgKey: params.bitgoGpgPubKey, + bitgoPublicGpgKey: params.bitgoPublicGpgKey, encryptedUserGpgPrvKey: params.encryptedUserGpgPrvKey, encryptedRound2Session: params.encryptedRound2Session, }); diff --git a/src/api/master/clients/enclavedExpressClient.ts b/src/api/master/clients/enclavedExpressClient.ts index 9937a1a..99fd15f 100644 --- a/src/api/master/clients/enclavedExpressClient.ts +++ b/src/api/master/clients/enclavedExpressClient.ts @@ -90,7 +90,7 @@ interface RecoveryMultisigOptions { interface SignMpcCommitmentParams { txRequest: TxRequest; - bitgoGpgPubKey: string; + bitgoPublicGpgKey: string; source: 'user' | 'backup'; pub: string; } @@ -130,12 +130,9 @@ interface SignMpcGShareResponse { // ECDSA MPCv2 interfaces interface SignMpcV2Round1Params { txRequest: TxRequest; - bitgoGpgPubKey: string; - source: 'user' | 'backup'; - pub: string; } -interface SignMpcV2Round1Response { +export interface SignMpcV2Round1Response { signatureShareRound1: SignatureShareRecord; userGpgPubKey: string; encryptedRound1Session: string; @@ -145,30 +142,26 @@ interface SignMpcV2Round1Response { interface SignMpcV2Round2Params { txRequest: TxRequest; - bitgoGpgPubKey: string; - encryptedDataKey: string; encryptedUserGpgPrvKey: string; encryptedRound1Session: string; - source: 'user' | 'backup'; - pub: string; + encryptedDataKey: string; + bitgoPublicGpgKey: string; } -interface SignMpcV2Round2Response { +export interface SignMpcV2Round2Response { signatureShareRound2: SignatureShareRecord; encryptedRound2Session: string; } interface SignMpcV2Round3Params { txRequest: TxRequest; - bitgoGpgPubKey: string; - encryptedDataKey: string; encryptedUserGpgPrvKey: string; encryptedRound2Session: string; - source: 'user' | 'backup'; - pub: string; + encryptedDataKey: string; + bitgoPublicGpgKey: string; } -interface SignMpcV2Round3Response { +export interface SignMpcV2Round3Response { signatureShareRound3: SignatureShareRecord; } @@ -606,20 +599,29 @@ export class EnclavedExpressClient { } } - async signMpcV2Round1(params: SignMpcV2Round1Params): Promise { - if (!this.coin) { + /** + * Sign MPCv2 Round 1 + */ + async signMPCv2Round1( + source: 'user' | 'backup', + pub: string, + params: SignMpcV2Round1Params, + ): Promise { + if (!this['coin']) { throw new Error('Coin must be specified to sign an MPCv2 Round 1'); } try { let request = this.apiClient['v1.mpc.sign'].post({ - coin: this.coin, + coin: this['coin'], shareType: 'mpcv2round1', ...params, + source, + pub, }); - if (this.tlsMode === TlsMode.MTLS) { - request = request.agent(this.createHttpsAgent()); + if (this['tlsMode'] === TlsMode.MTLS) { + request = request.agent(this['createHttpsAgent']()); } const response = await request.decodeExpecting(200); return response.body; @@ -630,20 +632,29 @@ export class EnclavedExpressClient { } } - async signMpcV2Round2(params: SignMpcV2Round2Params): Promise { - if (!this.coin) { + /** + * Sign MPCv2 Round 2 + */ + async signMPCv2Round2( + source: 'user' | 'backup', + pub: string, + params: SignMpcV2Round2Params, + ): Promise { + if (!this['coin']) { throw new Error('Coin must be specified to sign an MPCv2 Round 2'); } try { let request = this.apiClient['v1.mpc.sign'].post({ - coin: this.coin, + coin: this['coin'], shareType: 'mpcv2round2', ...params, + source, + pub, }); - if (this.tlsMode === TlsMode.MTLS) { - request = request.agent(this.createHttpsAgent()); + if (this['tlsMode'] === TlsMode.MTLS) { + request = request.agent(this['createHttpsAgent']()); } const response = await request.decodeExpecting(200); return response.body; @@ -654,20 +665,29 @@ export class EnclavedExpressClient { } } - async signMpcV2Round3(params: SignMpcV2Round3Params): Promise { - if (!this.coin) { + /** + * Sign MPCv2 Round 3 + */ + async signMPCv2Round3( + source: 'user' | 'backup', + pub: string, + params: SignMpcV2Round3Params, + ): Promise { + if (!this['coin']) { throw new Error('Coin must be specified to sign an MPCv2 Round 3'); } try { let request = this.apiClient['v1.mpc.sign'].post({ - coin: this.coin, + coin: this['coin'], shareType: 'mpcv2round3', ...params, + source, + pub, }); - if (this.tlsMode === TlsMode.MTLS) { - request = request.agent(this.createHttpsAgent()); + if (this['tlsMode'] === TlsMode.MTLS) { + request = request.agent(this['createHttpsAgent']()); } const response = await request.decodeExpecting(200); return response.body; diff --git a/src/api/master/handlers/ecdsa.ts b/src/api/master/handlers/ecdsaMPCv2.ts similarity index 77% rename from src/api/master/handlers/ecdsa.ts rename to src/api/master/handlers/ecdsaMPCv2.ts index 749c5bb..0ced479 100644 --- a/src/api/master/handlers/ecdsa.ts +++ b/src/api/master/handlers/ecdsaMPCv2.ts @@ -1,118 +1,105 @@ import { BaseCoin, BitGoBase, - commonTssMethods, EcdsaMPCv2Utils, - getTxRequest, IRequestTracer, RequestType, SupplementGenerateWalletOptions, Wallet, + TxRequest, } from '@bitgo/sdk-core'; -import { EnclavedExpressClient } from '../clients/enclavedExpressClient'; -import logger from '../../../logger'; +import { + EnclavedExpressClient, + SignMpcV2Round1Response, + SignMpcV2Round2Response, +} from '../clients/enclavedExpressClient'; -export async function handleEcdsaSigning( - bitgo: BitGoBase, - wallet: Wallet, - txRequestId: string, +/** + * Creates custom ECDSA MPCv2 signing functions for use with enclaved express client + */ +export function createEcdsaMPCv2CustomSigners( enclavedExpressClient: EnclavedExpressClient, source: 'user' | 'backup', commonKeychain: string, - reqId?: IRequestTracer, ) { - const ecdsaMPCv2Utils = new EcdsaMPCv2Utils(bitgo, wallet.baseCoin); - const txRequest = await getTxRequest(bitgo, wallet.id(), txRequestId, reqId); + // Create state to maintain data between rounds + let round1Response: SignMpcV2Round1Response; + let round2Response: SignMpcV2Round2Response; - // Get BitGo GPG key for MPCv2 - const bitgoGpgKey = await ecdsaMPCv2Utils.getBitgoMpcv2PublicGpgKey(); - - // Round 1: Generate user's Round 1 share - const { - signatureShareRound1, - userGpgPubKey, - encryptedRound1Session, - encryptedUserGpgPrvKey, - encryptedDataKey, - } = await enclavedExpressClient.signMpcV2Round1({ - txRequest, - bitgoGpgPubKey: bitgoGpgKey.armor(), - source, - pub: commonKeychain, - }); + // Create custom signing methods that maintain state + const customMPCv2Round1Generator = async (params: { txRequest: TxRequest }) => { + const response = await enclavedExpressClient.signMPCv2Round1(source, commonKeychain, params); + round1Response = response; + return response; + }; - // Send Round 1 share to BitGo and get updated txRequest - const round1TxRequest = await commonTssMethods.sendSignatureShareV2( - bitgo, - wallet.id(), - txRequestId, - [signatureShareRound1], - RequestType.tx, - wallet.baseCoin.getMPCAlgorithm(), - userGpgPubKey, - undefined, - wallet.multisigTypeVersion(), - reqId, - ); + const customMPCv2Round2Generator = async (params: { + txRequest: TxRequest; + encryptedUserGpgPrvKey: string; + encryptedRound1Session: string; + bitgoPublicGpgKey: string; + }) => { + if (!round1Response) { + throw new Error('Round 1 must be completed before Round 2'); + } + const response = await enclavedExpressClient.signMPCv2Round2(source, commonKeychain, { + ...params, + encryptedDataKey: round1Response.encryptedDataKey, + encryptedRound1Session: round1Response.encryptedRound1Session, + encryptedUserGpgPrvKey: round1Response.encryptedUserGpgPrvKey, + bitgoPublicGpgKey: params.bitgoPublicGpgKey, + }); + round2Response = response; + return response; + }; - // Round 2: Generate user's Round 2 share - const { signatureShareRound2, encryptedRound2Session } = - await enclavedExpressClient.signMpcV2Round2({ - txRequest: round1TxRequest, - bitgoGpgPubKey: bitgoGpgKey.armor(), - encryptedDataKey, - encryptedUserGpgPrvKey, - encryptedRound1Session, - source, - pub: commonKeychain, + const customMPCv2Round3Generator = async (params: { + txRequest: TxRequest; + encryptedUserGpgPrvKey: string; + encryptedRound2Session: string; + bitgoPublicGpgKey: string; + }) => { + if (!round2Response) { + throw new Error('Round 2 must be completed before Round 3'); + } + return await enclavedExpressClient.signMPCv2Round3(source, commonKeychain, { + ...params, + encryptedDataKey: round1Response.encryptedDataKey, + encryptedRound2Session: round2Response.encryptedRound2Session, + encryptedUserGpgPrvKey: round1Response.encryptedUserGpgPrvKey, + bitgoPublicGpgKey: params.bitgoPublicGpgKey, }); + }; - // Send Round 2 share to BitGo and get updated txRequest - const round2TxRequest = await commonTssMethods.sendSignatureShareV2( - bitgo, - wallet.id(), - txRequestId, - [signatureShareRound2], - RequestType.tx, - wallet.baseCoin.getMPCAlgorithm(), - userGpgPubKey, - undefined, - wallet.multisigTypeVersion(), - reqId, - ); + return { + customMPCv2Round1Generator, + customMPCv2Round2Generator, + customMPCv2Round3Generator, + }; +} - // Round 3: Generate user's Round 3 share - const { signatureShareRound3 } = await enclavedExpressClient.signMpcV2Round3({ - txRequest: round2TxRequest, - bitgoGpgPubKey: bitgoGpgKey.armor(), - encryptedDataKey, - encryptedUserGpgPrvKey, - encryptedRound2Session, - source, - pub: commonKeychain, - }); +export async function signAndSendEcdsaMPCv2FromTxRequest( + bitgo: BitGoBase, + wallet: Wallet, + txRequest: TxRequest, + enclavedExpressClient: EnclavedExpressClient, + source: 'user' | 'backup', + commonKeychain: string, + reqId: IRequestTracer, +): Promise { + const ecdsaMPCv2Utils = new EcdsaMPCv2Utils(bitgo, wallet.baseCoin, wallet); - // Send Round 3 share to BitGo - await commonTssMethods.sendSignatureShareV2( - bitgo, - wallet.id(), - txRequestId, - [signatureShareRound3], - RequestType.tx, - wallet.baseCoin.getMPCAlgorithm(), - userGpgPubKey, - undefined, - wallet.multisigTypeVersion(), - reqId, - ); + // Use the shared custom signing functions + const { customMPCv2Round1Generator, customMPCv2Round2Generator, customMPCv2Round3Generator } = + createEcdsaMPCv2CustomSigners(enclavedExpressClient, source, commonKeychain); - logger.debug('Successfully completed ECDSA MPCv2 signing!'); - return commonTssMethods.sendTxRequest( - bitgo, - txRequest.walletId, - txRequest.txRequestId, + // This also sends the TxRequest for broadcast + return await ecdsaMPCv2Utils.signEcdsaMPCv2TssUsingExternalSigner( + { txRequest, reqId }, + customMPCv2Round1Generator, + customMPCv2Round2Generator, + customMPCv2Round3Generator, RequestType.tx, - reqId, ); } diff --git a/src/api/master/handlers/eddsa.ts b/src/api/master/handlers/eddsa.ts index d6118a3..6a1d3e9 100644 --- a/src/api/master/handlers/eddsa.ts +++ b/src/api/master/handlers/eddsa.ts @@ -9,6 +9,7 @@ import { EddsaUtils, BaseCoin, ApiKeyShare, + TxRequest, } from '@bitgo/sdk-core'; import { EnclavedExpressClient } from '../clients/enclavedExpressClient'; import { exchangeEddsaCommitments } from '@bitgo/sdk-core/dist/src/bitgo/tss/common'; @@ -17,13 +18,12 @@ import logger from '../../../logger'; export async function handleEddsaSigning( bitgo: BitGoBase, wallet: Wallet, - txRequestId: string, + txRequest: TxRequest, enclavedExpressClient: EnclavedExpressClient, commonKeychain: string, reqId?: IRequestTracer, ) { - const eddsaUtils = new EddsaUtils(bitgo, wallet.baseCoin); - const txRequest = await getTxRequest(bitgo, wallet.id(), txRequestId, reqId); + const eddsaUtils = new EddsaUtils(bitgo, wallet.baseCoin, wallet); const { apiVersion } = txRequest; const bitgoGpgKey = await eddsaUtils.getBitgoPublicGpgKey(); @@ -35,7 +35,7 @@ export async function handleEddsaSigning( encryptedDataKey, } = await enclavedExpressClient.signMpcCommitment({ txRequest, - bitgoGpgPubKey: bitgoGpgKey.armor(), + bitgoPublicGpgKey: bitgoGpgKey.armor(), source: 'user', pub: commonKeychain, }); @@ -43,7 +43,7 @@ export async function handleEddsaSigning( const { commitmentShare: bitgoToUserCommitment } = await exchangeEddsaCommitments( bitgo, wallet.id(), - txRequestId, + txRequest.txRequestId, userToBitgoCommitment, encryptedSignerShare, apiVersion, @@ -61,13 +61,18 @@ export async function handleEddsaSigning( await offerUserToBitgoRShare( bitgo, wallet.id(), - txRequestId, + txRequest.txRequestId, rShare, encryptedSignerShare.share, apiVersion, reqId, ); - const bitgoToUserRShare = await getBitgoToUserRShare(bitgo, wallet.id(), txRequestId, reqId); + const bitgoToUserRShare = await getBitgoToUserRShare( + bitgo, + wallet.id(), + txRequest.txRequestId, + reqId, + ); const gSignShareTransactionParams = { txRequest, bitgoToUserRShare: bitgoToUserRShare, @@ -80,9 +85,9 @@ export async function handleEddsaSigning( pub: commonKeychain, }); - await sendUserToBitgoGShare(bitgo, wallet.id(), txRequestId, gShare, apiVersion, reqId); + await sendUserToBitgoGShare(bitgo, wallet.id(), txRequest.txRequestId, gShare, apiVersion, reqId); logger.debug('Successfully completed signing!'); - return await getTxRequest(bitgo, wallet.id(), txRequestId, reqId); + return await getTxRequest(bitgo, wallet.id(), txRequest.txRequestId, reqId); } interface OrchestrateEddsaKeyGenParams { diff --git a/src/api/master/handlers/generateWallet.ts b/src/api/master/handlers/generateWallet.ts index d0c93a6..1f9ec31 100644 --- a/src/api/master/handlers/generateWallet.ts +++ b/src/api/master/handlers/generateWallet.ts @@ -11,7 +11,7 @@ import { } from '@bitgo/sdk-core'; import _ from 'lodash'; import { MasterApiSpecRouteRequest } from '../routers/masterApiSpec'; -import { orchestrateEcdsaKeyGen } from './ecdsa'; +import { orchestrateEcdsaKeyGen } from './ecdsaMPCv2'; import { orchestrateEddsaKeyGen } from './eddsa'; /** diff --git a/src/api/master/handlers/handleSendMany.ts b/src/api/master/handlers/handleSendMany.ts index c4306c7..46fdc5b 100644 --- a/src/api/master/handlers/handleSendMany.ts +++ b/src/api/master/handlers/handleSendMany.ts @@ -5,17 +5,15 @@ import { KeyIndices, Wallet, SendManyOptions, - BitGoBase, - PendingApprovals, PrebuildTransactionResult, Keychain, - TxRequest, + getTxRequest, } from '@bitgo/sdk-core'; import logger from '../../../logger'; import { MasterApiSpecRouteRequest } from '../routers/masterApiSpec'; -import { handleEddsaSigning } from './eddsa'; -import { handleEcdsaSigning } from './ecdsa'; +import { createEcdsaMPCv2CustomSigners } from './ecdsaMPCv2'; import { EnclavedExpressClient } from '../clients/enclavedExpressClient'; +import { signAndSendTxRequests } from './transactionRequests'; /** * Defines the structure for a single recipient in a send-many transaction. @@ -30,6 +28,43 @@ interface Recipient { tokenData?: any; } +/** + * Creates TSS send parameters for ECDSA MPCv2 signing with custom functions + */ +function createEcdsaMPCv2SendParams( + req: MasterApiSpecRouteRequest<'v1.wallet.sendMany', 'post'>, + wallet: Wallet, + enclavedExpressClient: EnclavedExpressClient, + signingKeychain: Keychain, +): SendManyOptions { + const coin = req.bitgo.coin(req.params.coin); + const mpcAlgorithm = coin.getMPCAlgorithm(); + + if (mpcAlgorithm === 'ecdsa') { + // For ECDSA MPCv2, we need to create custom signing functions + const source = signingKeychain.source as 'user' | 'backup'; + const commonKeychain = signingKeychain.commonKeychain; + + if (!commonKeychain) { + throw new Error('Common keychain is required for ECDSA MPCv2 signing'); + } + + // Use the shared custom signing functions + const { customMPCv2Round1Generator, customMPCv2Round2Generator, customMPCv2Round3Generator } = + createEcdsaMPCv2CustomSigners(enclavedExpressClient, source, commonKeychain); + + return { + ...(req.decoded as SendManyOptions), + customMPCv2SigningRound1GenerationFunction: customMPCv2Round1Generator, + customMPCv2SigningRound2GenerationFunction: customMPCv2Round2Generator, + customMPCv2SigningRound3GenerationFunction: customMPCv2Round3Generator, + }; + } else { + // For non-ECDSA algorithms, return the original parameters + return req.decoded as SendManyOptions; + } +} + export async function handleSendMany(req: MasterApiSpecRouteRequest<'v1.wallet.sendMany', 'post'>) { const enclavedExpressClient = req.enclavedExpressClient; const reqId = new RequestTracer(); @@ -71,6 +106,23 @@ export async function handleSendMany(req: MasterApiSpecRouteRequest<'v1.wallet.s } try { + // Create TSS send parameters with custom signing functions if needed + + if (wallet.multisigType() === 'tss') { + if (signingKeychain.source === 'backup') { + throw new Error('Backup MPC signing not supported for sendMany'); + } + if (wallet.baseCoin.getMPCAlgorithm() === 'ecdsa') { + const ecdsaMPCv2SendParams = createEcdsaMPCv2SendParams( + req, + wallet, + enclavedExpressClient, + signingKeychain, + ); + return wallet.sendMany(ecdsaMPCv2SendParams); + } + } + const prebuildParams: PrebuildTransactionOptions = { ...params, // Convert memo string to Memo object if present @@ -113,10 +165,11 @@ export async function handleSendMany(req: MasterApiSpecRouteRequest<'v1.wallet.s if (!txPrebuilt.txRequestId) { throw new Error('MPC tx not built correctly.'); } + const txRequest = await getTxRequest(bitgo, wallet.id(), txPrebuilt.txRequestId, reqId); return signAndSendTxRequests( bitgo, wallet, - txPrebuilt.txRequestId, + txRequest, enclavedExpressClient, signingKeychain, reqId, @@ -174,78 +227,3 @@ async function signAndSendMultisig( const result = (await wallet.submitTransaction(finalTxParams, reqId)) as any; return result; } - -/** - * Signs and sends a transaction from a TSS wallet. - * - * @param bitgo - BitGo instance - * @param wallet - Wallet instance - * @param txRequestId - Transaction request ID - * @param enclavedExpressClient - Enclaved express client - * @param signingKeychain - Signing keychain - * @param reqId - Request tracer - */ -async function signAndSendTxRequests( - bitgo: BitGoBase, - wallet: Wallet, - txRequestId: string, - enclavedExpressClient: EnclavedExpressClient, - signingKeychain: Keychain, - reqId: RequestTracer, -): Promise { - if (!signingKeychain.commonKeychain) { - throw new Error(`Common keychain not found for keychain ${signingKeychain.pub || 'unknown'}`); - } - if (signingKeychain.source === 'backup') { - throw new Error('Backup MPC signing not supported for sendMany'); - } - - let signedTxRequest: TxRequest; - const mpcAlgorithm = wallet.baseCoin.getMPCAlgorithm(); - - if (mpcAlgorithm === 'eddsa') { - signedTxRequest = await handleEddsaSigning( - bitgo, - wallet, - txRequestId, - enclavedExpressClient, - signingKeychain.commonKeychain, - reqId, - ); - } else if (mpcAlgorithm === 'ecdsa') { - signedTxRequest = await handleEcdsaSigning( - bitgo, - wallet, - txRequestId, - enclavedExpressClient, - signingKeychain.source as 'user' | 'backup', - signingKeychain.commonKeychain, - reqId, - ); - } else { - throw new Error(`Unsupported MPC algorithm: ${mpcAlgorithm}`); - } - - if (!signedTxRequest.txRequestId) { - throw new Error('txRequestId missing from signed transaction'); - } - - if (signedTxRequest.apiVersion !== 'full') { - throw new Error('Only TxRequest API version full is supported.'); - } - - bitgo.setRequestTracer(reqId); - if (signedTxRequest.state === 'pendingApproval') { - const pendingApprovals = new PendingApprovals(bitgo, wallet.baseCoin); - const pendingApproval = await pendingApprovals.get({ id: signedTxRequest.pendingApprovalId }); - return { - pendingApproval: pendingApproval.toJSON(), - txRequest: signedTxRequest, - }; - } - return { - txRequest: signedTxRequest, - txid: (signedTxRequest.transactions ?? [])[0]?.signedTx?.id, - tx: (signedTxRequest.transactions ?? [])[0]?.signedTx?.tx, - }; -} diff --git a/src/api/master/handlers/transactionRequests.ts b/src/api/master/handlers/transactionRequests.ts new file mode 100644 index 0000000..242d8dc --- /dev/null +++ b/src/api/master/handlers/transactionRequests.ts @@ -0,0 +1,83 @@ +import { + BitGoBase, + Keychain, + PendingApprovals, + RequestTracer, + TxRequest, + Wallet, +} from '@bitgo/sdk-core'; +import { EnclavedExpressClient } from '../clients/enclavedExpressClient'; +import { handleEddsaSigning } from './eddsa'; +import { signAndSendEcdsaMPCv2FromTxRequest } from './ecdsaMPCv2'; + +/** + * Signs and sends a transaction from a TSS wallet. + * + * @param bitgo - BitGo instance + * @param wallet - Wallet instance + * @param txRequestId - Transaction request ID + * @param enclavedExpressClient - Enclaved express client + * @param signingKeychain - Signing keychain + * @param reqId - Request tracer + */ +export async function signAndSendTxRequests( + bitgo: BitGoBase, + wallet: Wallet, + txRequest: TxRequest, + enclavedExpressClient: EnclavedExpressClient, + signingKeychain: Keychain, + reqId: RequestTracer, +): Promise { + if (!signingKeychain.commonKeychain) { + throw new Error(`Common keychain not found for keychain ${signingKeychain.pub || 'unknown'}`); + } + + let signedTxRequest: TxRequest; + const mpcAlgorithm = wallet.baseCoin.getMPCAlgorithm(); + + if (mpcAlgorithm === 'eddsa') { + signedTxRequest = await handleEddsaSigning( + bitgo, + wallet, + txRequest, + enclavedExpressClient, + signingKeychain.commonKeychain, + reqId, + ); + } else if (mpcAlgorithm === 'ecdsa') { + signedTxRequest = await signAndSendEcdsaMPCv2FromTxRequest( + bitgo, + wallet, + txRequest, + enclavedExpressClient, + signingKeychain.source as 'user' | 'backup', + signingKeychain.commonKeychain, + reqId, + ); + } else { + throw new Error(`Unsupported MPC algorithm: ${mpcAlgorithm}`); + } + + if (!signedTxRequest.txRequestId) { + throw new Error('txRequestId missing from signed transaction'); + } + + if (signedTxRequest.apiVersion !== 'full') { + throw new Error('Only TxRequest API version full is supported.'); + } + + bitgo.setRequestTracer(reqId); + if (signedTxRequest.state === 'pendingApproval') { + const pendingApprovals = new PendingApprovals(bitgo, wallet.baseCoin); + const pendingApproval = await pendingApprovals.get({ id: signedTxRequest.pendingApprovalId }); + return { + pendingApproval: pendingApproval.toJSON(), + txRequest: signedTxRequest, + }; + } + return { + txRequest: signedTxRequest, + txid: (signedTxRequest.transactions ?? [])[0]?.signedTx?.id, + tx: (signedTxRequest.transactions ?? [])[0]?.signedTx?.tx, + }; +} diff --git a/src/enclavedBitgoExpress/routers/enclavedApiSpec.ts b/src/enclavedBitgoExpress/routers/enclavedApiSpec.ts index 9aceeb6..e52e111 100644 --- a/src/enclavedBitgoExpress/routers/enclavedApiSpec.ts +++ b/src/enclavedBitgoExpress/routers/enclavedApiSpec.ts @@ -91,7 +91,7 @@ const SignMpcRequest = { userToBitgoRShare: t.union([t.undefined, t.any]), encryptedUserToBitgoRShare: t.union([t.undefined, t.any]), bitgoToUserCommitment: t.union([t.undefined, t.any]), - bitgoGpgPubKey: t.union([t.undefined, t.string]), + bitgoPublicGpgKey: t.union([t.undefined, t.string]), encryptedDataKey: t.union([t.undefined, t.string]), // ECDSA MPCv2 specific fields @@ -337,7 +337,14 @@ export const EnclavedAPiSpec = apiSpec({ request: httpRequest({ params: { coin: t.string, - shareType: t.string, + shareType: t.union([ + t.literal('commitment'), + t.literal('r'), + t.literal('g'), + t.literal('mpcv2round1'), + t.literal('mpcv2round2'), + t.literal('mpcv2round3'), + ]), }, body: SignMpcRequest, }),