diff --git a/src/__tests__/api/master/accelerate.test.ts b/src/__tests__/api/master/accelerate.test.ts index 728c3115..227ff1ea 100644 --- a/src/__tests__/api/master/accelerate.test.ts +++ b/src/__tests__/api/master/accelerate.test.ts @@ -34,6 +34,12 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => { type: 'independent', }; + const mockBitgoKeychain = { + id: 'bitgo-key-id', + pub: 'xpub661MyMwAqRbcHtYNxRNuEtDFmPMRzBVPDfBXNu2RUBVFNz8MnWQgkrMZCNB', + type: 'bitgo', + }; + before(() => { nock.disableNetConnect(); nock.enableNetConnect('127.0.0.1'); @@ -68,11 +74,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => { .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/user-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockUserKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const accelerateTransactionStub = sinon .stub(Wallet.prototype, 'accelerateTransaction') .resolves({ @@ -117,11 +138,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => { .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/backup-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockBackupKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const accelerateTransactionStub = sinon .stub(Wallet.prototype, 'accelerateTransaction') .resolves({ @@ -157,11 +193,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => { .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/user-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockUserKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const accelerateTransactionStub = sinon .stub(Wallet.prototype, 'accelerateTransaction') .resolves({ @@ -324,11 +375,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => { .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/user-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockUserKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const accelerateTransactionStub = sinon .stub(Wallet.prototype, 'accelerateTransaction') .rejects(new Error('Insufficient funds for acceleration')); @@ -408,4 +474,128 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => { response.body.should.have.property('error', 'Internal Server Error'); response.body.should.have.property('details'); }); + + it('should pass walletPubs (all 3 xpubs) to AWM for UTXO signing', async () => { + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/wallet/${walletId}`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockWalletData); + + // Signing keychain (user) — fetched once by getWalletAndSigningKeychain + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + + let capturedSignBody: any; + const awmSignNock = nock(advancedWalletManagerUrl) + .post(`/api/${coin}/multisig/sign`, (body) => { + capturedSignBody = body; + return true; + }) + .reply(200, { + halfSigned: { txHex: 'signed-tx-hex' }, + source: 'user', + pub: mockUserKeychain.pub, + }); + + // Stub accelerateTransaction to call customSigningFunction so the AWM request is made + sinon.stub(Wallet.prototype, 'accelerateTransaction').callsFake(async (params: any) => { + await params.customSigningFunction({ txPrebuild: { txHex: 'prebuilt-tx' } }); + return { txid: 'accelerated-tx-id', tx: '0100000001abcdef...', status: 'signed' }; + }); + + const response = await agent + .post(`/api/v1/${coin}/advancedwallet/${walletId}/accelerate`) + .set('Authorization', `Bearer ${accessToken}`) + .send({ + pubkey: mockUserKeychain.pub, + source: 'user', + cpfpTxIds: ['b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26'], + cpfpFeeRate: 50, + }); + + response.status.should.equal(200); + awmSignNock.done(); + capturedSignBody.should.have.property('walletPubs'); + capturedSignBody.walletPubs.should.deepEqual([ + mockUserKeychain.pub, + mockBackupKeychain.pub, + mockBitgoKeychain.pub, + ]); + }); + + it('should omit walletPubs from AWM request when any keychain is missing a pub', async () => { + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/wallet/${walletId}`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockWalletData); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, { id: 'backup-key-id' }); // no pub + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + + let capturedSignBody: any; + const awmSignNock = nock(advancedWalletManagerUrl) + .post(`/api/${coin}/multisig/sign`, (body) => { + capturedSignBody = body; + return true; + }) + .reply(200, { + halfSigned: { txHex: 'signed-tx-hex' }, + source: 'user', + pub: mockUserKeychain.pub, + }); + + sinon.stub(Wallet.prototype, 'accelerateTransaction').callsFake(async (params: any) => { + await params.customSigningFunction({ txPrebuild: { txHex: 'prebuilt-tx' } }); + return { txid: 'accelerated-tx-id', tx: '0100000001abcdef...', status: 'signed' }; + }); + + const response = await agent + .post(`/api/v1/${coin}/advancedwallet/${walletId}/accelerate`) + .set('Authorization', `Bearer ${accessToken}`) + .send({ + pubkey: mockUserKeychain.pub, + source: 'user', + cpfpTxIds: ['b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26'], + cpfpFeeRate: 50, + }); + + response.status.should.equal(200); + awmSignNock.done(); + capturedSignBody.should.not.have.property('walletPubs'); + }); }); diff --git a/src/__tests__/api/master/consolidateUnspents.test.ts b/src/__tests__/api/master/consolidateUnspents.test.ts index 9ee75e5c..08e8b3f4 100644 --- a/src/__tests__/api/master/consolidateUnspents.test.ts +++ b/src/__tests__/api/master/consolidateUnspents.test.ts @@ -34,6 +34,12 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidateunspents', () = type: 'independent', }; + const mockBitgoKeychain = { + id: 'bitgo-key-id', + pub: 'xpub661MyMwAqRbcHtYNxRNuEtDFmPMRzBVPDfBXNu2RUBVFNz8MnWQgkrMZCNB', + type: 'bitgo', + }; + before(() => { nock.disableNetConnect(); nock.enableNetConnect('127.0.0.1'); @@ -68,11 +74,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidateunspents', () = .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/user-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockUserKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const mockResult = { transfer: { entries: [ @@ -135,11 +156,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidateunspents', () = .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/backup-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockBackupKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const mockResult = { txid: 'backup-consolidation-tx-id', tx: '01000000000102backup...', @@ -178,11 +214,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidateunspents', () = .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/user-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockUserKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const mockArrayResult = [ { transfer: { @@ -241,11 +292,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidateunspents', () = .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/user-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockUserKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const mockArrayResult = [ { txid: 'first-tx-id', @@ -293,11 +359,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidateunspents', () = .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/user-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockUserKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const mockResult = { txid: 'full-params-consolidation-tx-id', tx: '01000000000102full...', @@ -472,11 +553,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidateunspents', () = .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockWalletData); + // Signing keychain fetched by getWalletAndSigningKeychain const keychainGetNock = nock(bitgoApiUrl) .get(`/api/v2/${coin}/key/user-key-id`) .matchHeader('authorization', `Bearer ${accessToken}`) .reply(200, mockUserKeychain); + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + const consolidateUnspentsStub = sinon .stub(Wallet.prototype, 'consolidateUnspents') .rejects(new Error('No unspents available for consolidation')); @@ -513,4 +609,126 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidateunspents', () = response.status.should.equal(400); response.body.should.have.property('error'); }); + + it('should pass walletPubs (all 3 xpubs) to AWM for UTXO signing', async () => { + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/wallet/${walletId}`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockWalletData); + + // Signing keychain (user) — fetched once by getWalletAndSigningKeychain + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + + // All 3 keychains fetched for walletPubs + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBackupKeychain); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + + let capturedSignBody: any; + const awmSignNock = nock(advancedWalletManagerUrl) + .post(`/api/${coin}/multisig/sign`, (body) => { + capturedSignBody = body; + return true; + }) + .reply(200, { + halfSigned: { txHex: 'signed-tx-hex' }, + source: 'user', + pub: mockUserKeychain.pub, + }); + + // Stub consolidateUnspents to call customSigningFunction so the AWM request is made + sinon.stub(Wallet.prototype, 'consolidateUnspents').callsFake(async (params: any) => { + await params.customSigningFunction({ txPrebuild: { txHex: 'prebuilt-tx' } }); + return { txid: 'consolidate-tx-id', tx: '01000000...', status: 'signed' }; + }); + + const response = await agent + .post(`/api/v1/${coin}/advancedwallet/${walletId}/consolidateunspents`) + .set('Authorization', `Bearer ${accessToken}`) + .send({ + pubkey: mockUserKeychain.pub, + source: 'user', + feeRate: 1000, + }); + + response.status.should.equal(200); + awmSignNock.done(); + capturedSignBody.should.have.property('walletPubs'); + capturedSignBody.walletPubs.should.deepEqual([ + mockUserKeychain.pub, + mockBackupKeychain.pub, + mockBitgoKeychain.pub, + ]); + }); + + it('should omit walletPubs from AWM request when any keychain is missing a pub', async () => { + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/wallet/${walletId}`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockWalletData); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/user-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockUserKeychain); + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/backup-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, { id: 'backup-key-id' }); // no pub + + nock(bitgoApiUrl) + .get(`/api/v2/${coin}/key/bitgo-key-id`) + .matchHeader('authorization', `Bearer ${accessToken}`) + .reply(200, mockBitgoKeychain); + + let capturedSignBody: any; + const awmSignNock = nock(advancedWalletManagerUrl) + .post(`/api/${coin}/multisig/sign`, (body) => { + capturedSignBody = body; + return true; + }) + .reply(200, { + halfSigned: { txHex: 'signed-tx-hex' }, + source: 'user', + pub: mockUserKeychain.pub, + }); + + sinon.stub(Wallet.prototype, 'consolidateUnspents').callsFake(async (params: any) => { + await params.customSigningFunction({ txPrebuild: { txHex: 'prebuilt-tx' } }); + return { txid: 'consolidate-tx-id', tx: '01000000...', status: 'signed' }; + }); + + const response = await agent + .post(`/api/v1/${coin}/advancedwallet/${walletId}/consolidateunspents`) + .set('Authorization', `Bearer ${accessToken}`) + .send({ + pubkey: mockUserKeychain.pub, + source: 'user', + feeRate: 1000, + }); + + response.status.should.equal(200); + awmSignNock.done(); + capturedSignBody.should.not.have.property('walletPubs'); + }); }); diff --git a/src/masterBitgoExpress/handlers/handleAccelerate.ts b/src/masterBitgoExpress/handlers/handleAccelerate.ts index 6057236b..e3e8c766 100644 --- a/src/masterBitgoExpress/handlers/handleAccelerate.ts +++ b/src/masterBitgoExpress/handlers/handleAccelerate.ts @@ -13,7 +13,7 @@ export async function handleAccelerate( const walletId = req.params.walletId; const coin = req.params.coin; - const { wallet, signingKeychain } = await getWalletAndSigningKeychain({ + const { baseCoin, wallet, signingKeychain } = await getWalletAndSigningKeychain({ bitgo, coin, walletId, @@ -22,12 +22,23 @@ export async function handleAccelerate( KeyIndices, }); + const [userKeychain, backupKeychain, bitgoKeychain] = await Promise.all([ + baseCoin.keychains().get({ id: wallet.keyIds()[KeyIndices.USER] }), + baseCoin.keychains().get({ id: wallet.keyIds()[KeyIndices.BACKUP] }), + baseCoin.keychains().get({ id: wallet.keyIds()[KeyIndices.BITGO] }), + ]); + const walletPubs = + userKeychain?.pub && backupKeychain?.pub && bitgoKeychain?.pub + ? [userKeychain.pub, backupKeychain.pub, bitgoKeychain.pub] + : undefined; + try { // Create custom signing function that delegates to EBE const customSigningFunction = makeCustomSigningFunction({ awmClient, source: params.source, pub: signingKeychain.pub!, + walletPubs, }); // Prepare acceleration parameters diff --git a/src/masterBitgoExpress/handlers/handleConsolidateUnspents.ts b/src/masterBitgoExpress/handlers/handleConsolidateUnspents.ts index 0842c8e0..ae034ec4 100644 --- a/src/masterBitgoExpress/handlers/handleConsolidateUnspents.ts +++ b/src/masterBitgoExpress/handlers/handleConsolidateUnspents.ts @@ -13,7 +13,7 @@ export async function handleConsolidateUnspents( const walletId = req.params.walletId; const coin = req.params.coin; - const { wallet, signingKeychain } = await getWalletAndSigningKeychain({ + const { baseCoin, wallet, signingKeychain } = await getWalletAndSigningKeychain({ bitgo, coin, walletId, @@ -22,12 +22,23 @@ export async function handleConsolidateUnspents( KeyIndices, }); + const [userKeychain, backupKeychain, bitgoKeychain] = await Promise.all([ + baseCoin.keychains().get({ id: wallet.keyIds()[KeyIndices.USER] }), + baseCoin.keychains().get({ id: wallet.keyIds()[KeyIndices.BACKUP] }), + baseCoin.keychains().get({ id: wallet.keyIds()[KeyIndices.BITGO] }), + ]); + const walletPubs = + userKeychain?.pub && backupKeychain?.pub && bitgoKeychain?.pub + ? [userKeychain.pub, backupKeychain.pub, bitgoKeychain.pub] + : undefined; + try { // Create custom signing function that delegates to EBE const customSigningFunction = makeCustomSigningFunction({ awmClient, source: params.source, pub: signingKeychain.pub!, + walletPubs, }); // Prepare consolidation parameters diff --git a/src/masterBitgoExpress/handlers/utils/utils.ts b/src/masterBitgoExpress/handlers/utils/utils.ts index d1aeb090..a8bc9f8f 100644 --- a/src/masterBitgoExpress/handlers/utils/utils.ts +++ b/src/masterBitgoExpress/handlers/utils/utils.ts @@ -65,16 +65,19 @@ export function makeCustomSigningFunction({ awmClient, source, pub, + walletPubs, }: { awmClient: AdvancedWalletManagerClient; source: 'user' | 'backup'; pub: string; + walletPubs?: string[]; }): CustomSigningFunction { return async function customSigningFunction(signParams: any) { return awmClient.signMultisig({ txPrebuild: signParams.txPrebuild, source, pub, + walletPubs, }); }; }