Skip to content

Commit 64f65f5

Browse files
authored
Merge pull request #74 from BitGo/WP-5234-nonce-hole-support
feat(mbe): allow fillNonce sendmany tx's
2 parents fd128b6 + 8d2054d commit 64f65f5

File tree

3 files changed

+218
-83
lines changed

3 files changed

+218
-83
lines changed

masterBitgoExpress.json

Lines changed: 140 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,7 @@
100100
"content": {
101101
"application/json": {
102102
"schema": {
103-
"type": "object",
104-
"properties": {
105-
"error": {
106-
"type": "string"
107-
},
108-
"details": {
109-
"type": "string"
110-
}
111-
},
112-
"required": [
113-
"error",
114-
"details"
115-
]
103+
"$ref": "#/components/schemas/ErrorResponse"
116104
}
117105
}
118106
}
@@ -193,7 +181,9 @@
193181
"description": "Bad Request",
194182
"content": {
195183
"application/json": {
196-
"schema": {}
184+
"schema": {
185+
"$ref": "#/components/schemas/ErrorResponse"
186+
}
197187
}
198188
}
199189
},
@@ -202,19 +192,7 @@
202192
"content": {
203193
"application/json": {
204194
"schema": {
205-
"type": "object",
206-
"properties": {
207-
"error": {
208-
"type": "string"
209-
},
210-
"details": {
211-
"type": "string"
212-
}
213-
},
214-
"required": [
215-
"error",
216-
"details"
217-
]
195+
"$ref": "#/components/schemas/ErrorResponse"
218196
}
219197
}
220198
}
@@ -355,7 +333,9 @@
355333
"description": "Bad Request",
356334
"content": {
357335
"application/json": {
358-
"schema": {}
336+
"schema": {
337+
"$ref": "#/components/schemas/ErrorResponse"
338+
}
359339
}
360340
}
361341
},
@@ -364,19 +344,7 @@
364344
"content": {
365345
"application/json": {
366346
"schema": {
367-
"type": "object",
368-
"properties": {
369-
"error": {
370-
"type": "string"
371-
},
372-
"details": {
373-
"type": "string"
374-
}
375-
},
376-
"required": [
377-
"error",
378-
"details"
379-
]
347+
"$ref": "#/components/schemas/ErrorResponse"
380348
}
381349
}
382350
}
@@ -417,6 +385,7 @@
417385
"type": "string",
418386
"enum": [
419387
"transfer",
388+
"fillNonce",
420389
"acceleration",
421390
"accountSet",
422391
"enabletoken",
@@ -532,11 +501,13 @@
532501
},
533502
"custodianTransactionId": {
534503
"type": "string"
504+
},
505+
"nonce": {
506+
"type": "string"
535507
}
536508
},
537509
"required": [
538-
"source",
539-
"recipients"
510+
"source"
540511
]
541512
}
542513
}
@@ -556,19 +527,7 @@
556527
"content": {
557528
"application/json": {
558529
"schema": {
559-
"type": "object",
560-
"properties": {
561-
"error": {
562-
"type": "string"
563-
},
564-
"details": {
565-
"type": "string"
566-
}
567-
},
568-
"required": [
569-
"error",
570-
"details"
571-
]
530+
"$ref": "#/components/schemas/ErrorResponse"
572531
}
573532
}
574533
}
@@ -642,19 +601,7 @@
642601
"content": {
643602
"application/json": {
644603
"schema": {
645-
"type": "object",
646-
"properties": {
647-
"error": {
648-
"type": "string"
649-
},
650-
"details": {
651-
"type": "string"
652-
}
653-
},
654-
"required": [
655-
"error",
656-
"details"
657-
]
604+
"$ref": "#/components/schemas/ErrorResponse"
658605
}
659606
}
660607
}
@@ -723,19 +670,7 @@
723670
"content": {
724671
"application/json": {
725672
"schema": {
726-
"type": "object",
727-
"properties": {
728-
"error": {
729-
"type": "string"
730-
},
731-
"details": {
732-
"type": "string"
733-
}
734-
},
735-
"required": [
736-
"error",
737-
"details"
738-
]
673+
"$ref": "#/components/schemas/ErrorResponse"
739674
}
740675
}
741676
}
@@ -939,6 +874,113 @@
939874
}
940875
}
941876
},
877+
"422": {
878+
"description": "Unprocessable Entity",
879+
"content": {
880+
"application/json": {
881+
"schema": {
882+
"$ref": "#/components/schemas/ErrorResponse"
883+
}
884+
}
885+
}
886+
},
887+
"500": {
888+
"description": "Internal Server Error",
889+
"content": {
890+
"application/json": {
891+
"schema": {
892+
"$ref": "#/components/schemas/ErrorResponse"
893+
}
894+
}
895+
}
896+
}
897+
}
898+
}
899+
},
900+
"/api/{coin}/wallet/recoveryconsolidations": {
901+
"post": {
902+
"parameters": [
903+
{
904+
"name": "coin",
905+
"in": "path",
906+
"required": true,
907+
"schema": {
908+
"type": "string"
909+
}
910+
}
911+
],
912+
"requestBody": {
913+
"content": {
914+
"application/json": {
915+
"schema": {
916+
"type": "object",
917+
"properties": {
918+
"userPub": {
919+
"type": "string"
920+
},
921+
"backupPub": {
922+
"type": "string"
923+
},
924+
"bitgoPub": {
925+
"type": "string"
926+
},
927+
"multisigType": {
928+
"type": "string",
929+
"enum": [
930+
"onchain",
931+
"tss"
932+
]
933+
},
934+
"commonKeychain": {
935+
"type": "string"
936+
},
937+
"tokenContractAddress": {
938+
"type": "string"
939+
},
940+
"startingScanIndex": {
941+
"type": "number"
942+
},
943+
"endingScanIndex": {
944+
"type": "number"
945+
},
946+
"apiKey": {
947+
"type": "string"
948+
},
949+
"durableNonces": {
950+
"type": "object",
951+
"properties": {
952+
"publicKeys": {
953+
"type": "array",
954+
"items": {
955+
"type": "string"
956+
}
957+
},
958+
"secretKey": {
959+
"type": "string"
960+
}
961+
},
962+
"required": [
963+
"publicKeys",
964+
"secretKey"
965+
]
966+
}
967+
},
968+
"required": [
969+
"multisigType"
970+
]
971+
}
972+
}
973+
}
974+
},
975+
"responses": {
976+
"200": {
977+
"description": "OK",
978+
"content": {
979+
"application/json": {
980+
"schema": {}
981+
}
982+
}
983+
},
942984
"500": {
943985
"description": "Internal Server Error",
944986
"content": {
@@ -1122,6 +1164,22 @@
11221164
"status",
11231165
"timestamp"
11241166
]
1167+
},
1168+
"ErrorResponse": {
1169+
"title": "ErrorResponse",
1170+
"type": "object",
1171+
"properties": {
1172+
"error": {
1173+
"type": "string"
1174+
},
1175+
"details": {
1176+
"type": "string"
1177+
}
1178+
},
1179+
"required": [
1180+
"error",
1181+
"details"
1182+
]
11251183
}
11261184
}
11271185
}

src/__tests__/api/master/sendMany.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,81 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => {
394394
sinon.assert.calledOnce(multisigTypeStub);
395395
});
396396

397+
it('should be able to sign a fill nonce transaction', async () => {
398+
// Mock wallet get request for TSS wallet
399+
const walletGetNock = nock(bitgoApiUrl)
400+
.get(`/api/v2/${coin}/wallet/${walletId}`)
401+
.matchHeader('any', () => true)
402+
.reply(200, {
403+
id: walletId,
404+
type: 'cold',
405+
subType: 'onPrem',
406+
keys: ['user-key-id', 'backup-key-id', 'bitgo-key-id'],
407+
multisigType: 'tss',
408+
});
409+
410+
// Mock keychain get request for TSS keychain
411+
const keychainGetNock = nock(bitgoApiUrl)
412+
.get(`/api/v2/${coin}/key/user-key-id`)
413+
.matchHeader('any', () => true)
414+
.reply(200, {
415+
id: 'user-key-id',
416+
pub: 'xpub_user',
417+
commonKeychain: 'test-common-keychain',
418+
source: 'user',
419+
type: 'tss',
420+
});
421+
422+
const sendManyStub = sinon.stub(Wallet.prototype, 'sendMany').resolves({
423+
txRequest: {
424+
txRequestId: 'test-tx-request-id',
425+
state: 'signed',
426+
apiVersion: 'full',
427+
pendingApprovalId: 'test-pending-approval-id',
428+
transactions: [
429+
{
430+
state: 'signed',
431+
unsignedTx: {
432+
derivationPath: 'm/0',
433+
signableHex: 'testMessage',
434+
serializedTxHex: 'testSerializedTxHex',
435+
},
436+
signatureShares: [],
437+
signedTx: {
438+
id: 'test-tx-id',
439+
tx: 'signed-transaction',
440+
},
441+
},
442+
],
443+
},
444+
txid: 'test-tx-id',
445+
tx: 'signed-transaction',
446+
});
447+
448+
// Mock multisigType to return 'tss'
449+
const multisigTypeStub = sinon.stub(Wallet.prototype, 'multisigType').returns('tss');
450+
451+
const response = await agent
452+
.post(`/api/${coin}/wallet/${walletId}/sendMany`)
453+
.set('Authorization', `Bearer ${accessToken}`)
454+
.send({
455+
type: 'fillNonce',
456+
nonce: '2',
457+
source: 'user',
458+
pubkey: 'xpub_user',
459+
});
460+
461+
response.status.should.equal(200);
462+
response.body.should.have.property('txRequest');
463+
response.body.should.have.property('txid', 'test-tx-id');
464+
response.body.should.have.property('tx', 'signed-transaction');
465+
466+
walletGetNock.done();
467+
keychainGetNock.done();
468+
sinon.assert.calledOnce(sendManyStub);
469+
sinon.assert.calledOnce(multisigTypeStub);
470+
});
471+
397472
it('should fail when backup key is used for ECDSA TSS signing', async () => {
398473
// Mock wallet get request for TSS wallet
399474
const walletGetNock = nock(bitgoApiUrl)

0 commit comments

Comments
 (0)