@bitpay-labs/bitcore-wallet-service 11.10.7 → 11.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/package.json +3 -3
  2. package/src/externalservices/coingecko.ts +24 -10
  3. package/src/externalservices/moonpay.ts +23 -1
  4. package/src/lib/chain/eth/abi-invoice.ts +139 -90
  5. package/src/lib/common/constants.ts +5 -2
  6. package/src/lib/common/defaults.ts +11 -2
  7. package/src/lib/errors/errordefinitions.ts +6 -0
  8. package/src/lib/model/copayer.ts +1 -0
  9. package/src/lib/model/tsskeygen.ts +33 -4
  10. package/src/lib/model/tsssign.ts +34 -3
  11. package/src/lib/pushnotificationsservice.ts +3 -1
  12. package/src/lib/routes/middleware/authTssRequest.ts +10 -4
  13. package/src/lib/routes/middleware/verifyTssMessage.ts +13 -8
  14. package/src/lib/routes/tss.ts +11 -4
  15. package/src/lib/server.ts +53 -25
  16. package/src/lib/storage.ts +24 -1
  17. package/src/lib/tss.ts +232 -30
  18. package/src/types/externalservices.d.ts +29 -1
  19. package/test/integration/externalservices/coinGecko.test.ts +71 -7
  20. package/test/integration/server.test.ts +731 -1
  21. package/test/integration/tss.test.ts +275 -0
  22. package/test/model/copayer.test.ts +128 -0
  23. package/test/storage.test.ts +91 -0
  24. package/ts_build/package.json +1 -1
  25. package/ts_build/src/externalservices/coingecko.d.ts.map +1 -1
  26. package/ts_build/src/externalservices/coingecko.js +14 -8
  27. package/ts_build/src/externalservices/coingecko.js.map +1 -1
  28. package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
  29. package/ts_build/src/externalservices/moonpay.js +38 -2
  30. package/ts_build/src/externalservices/moonpay.js.map +1 -1
  31. package/ts_build/src/lib/chain/eth/abi-invoice.d.ts +254 -41
  32. package/ts_build/src/lib/chain/eth/abi-invoice.d.ts.map +1 -1
  33. package/ts_build/src/lib/chain/eth/abi-invoice.js +138 -89
  34. package/ts_build/src/lib/chain/eth/abi-invoice.js.map +1 -1
  35. package/ts_build/src/lib/common/constants.d.ts +4 -2
  36. package/ts_build/src/lib/common/constants.d.ts.map +1 -1
  37. package/ts_build/src/lib/common/constants.js +4 -2
  38. package/ts_build/src/lib/common/constants.js.map +1 -1
  39. package/ts_build/src/lib/common/defaults.d.ts +4 -2
  40. package/ts_build/src/lib/common/defaults.d.ts.map +1 -1
  41. package/ts_build/src/lib/common/defaults.js +4 -2
  42. package/ts_build/src/lib/common/defaults.js.map +1 -1
  43. package/ts_build/src/lib/common/index.d.ts +8 -4
  44. package/ts_build/src/lib/common/index.d.ts.map +1 -1
  45. package/ts_build/src/lib/errors/errordefinitions.d.ts +3 -0
  46. package/ts_build/src/lib/errors/errordefinitions.d.ts.map +1 -1
  47. package/ts_build/src/lib/errors/errordefinitions.js +3 -0
  48. package/ts_build/src/lib/errors/errordefinitions.js.map +1 -1
  49. package/ts_build/src/lib/model/copayer.d.ts.map +1 -1
  50. package/ts_build/src/lib/model/copayer.js +1 -0
  51. package/ts_build/src/lib/model/copayer.js.map +1 -1
  52. package/ts_build/src/lib/model/tsskeygen.d.ts +3 -0
  53. package/ts_build/src/lib/model/tsskeygen.d.ts.map +1 -1
  54. package/ts_build/src/lib/model/tsskeygen.js +12 -3
  55. package/ts_build/src/lib/model/tsskeygen.js.map +1 -1
  56. package/ts_build/src/lib/model/tsssign.d.ts +7 -0
  57. package/ts_build/src/lib/model/tsssign.d.ts.map +1 -1
  58. package/ts_build/src/lib/model/tsssign.js +22 -2
  59. package/ts_build/src/lib/model/tsssign.js.map +1 -1
  60. package/ts_build/src/lib/pushnotificationsservice.d.ts.map +1 -1
  61. package/ts_build/src/lib/pushnotificationsservice.js +3 -1
  62. package/ts_build/src/lib/pushnotificationsservice.js.map +1 -1
  63. package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts.map +1 -1
  64. package/ts_build/src/lib/routes/middleware/authTssRequest.js +8 -2
  65. package/ts_build/src/lib/routes/middleware/authTssRequest.js.map +1 -1
  66. package/ts_build/src/lib/routes/middleware/verifyTssMessage.d.ts.map +1 -1
  67. package/ts_build/src/lib/routes/middleware/verifyTssMessage.js +11 -6
  68. package/ts_build/src/lib/routes/middleware/verifyTssMessage.js.map +1 -1
  69. package/ts_build/src/lib/routes/tss.d.ts.map +1 -1
  70. package/ts_build/src/lib/routes/tss.js +4 -4
  71. package/ts_build/src/lib/routes/tss.js.map +1 -1
  72. package/ts_build/src/lib/server.d.ts.map +1 -1
  73. package/ts_build/src/lib/server.js +33 -25
  74. package/ts_build/src/lib/server.js.map +1 -1
  75. package/ts_build/src/lib/storage.d.ts +4 -0
  76. package/ts_build/src/lib/storage.d.ts.map +1 -1
  77. package/ts_build/src/lib/storage.js +18 -1
  78. package/ts_build/src/lib/storage.js.map +1 -1
  79. package/ts_build/src/lib/tss.d.ts +4 -0
  80. package/ts_build/src/lib/tss.d.ts.map +1 -1
  81. package/ts_build/src/lib/tss.js +74 -17
  82. package/ts_build/src/lib/tss.js.map +1 -1
  83. package/ts_build/test/integration/externalservices/coinGecko.test.js +62 -8
  84. package/ts_build/test/integration/externalservices/coinGecko.test.js.map +1 -1
  85. package/ts_build/test/integration/server.test.js +597 -0
  86. package/ts_build/test/integration/server.test.js.map +1 -1
  87. package/ts_build/test/integration/tss.test.js +245 -0
  88. package/ts_build/test/integration/tss.test.js.map +1 -1
  89. package/ts_build/test/model/copayer.test.js +91 -0
  90. package/ts_build/test/model/copayer.test.js.map +1 -1
  91. package/ts_build/test/storage.test.js +73 -0
  92. package/ts_build/test/storage.test.js.map +1 -1
@@ -40,6 +40,8 @@ const chai = __importStar(require("chai"));
40
40
  require("chai/register-should");
41
41
  const util_1 = __importDefault(require("util"));
42
42
  const sinon_1 = __importDefault(require("sinon"));
43
+ const http_1 = __importDefault(require("http"));
44
+ const request_1 = __importDefault(require("request"));
43
45
  const CWC = __importStar(require("@bitpay-labs/crypto-wallet-core"));
44
46
  const index_1 = require("../../src/lib/chain/index");
45
47
  const config_1 = __importDefault(require("../../src/config"));
@@ -47,10 +49,12 @@ const server_1 = require("../../src/lib/server");
47
49
  const storage_1 = require("../../src/lib/storage");
48
50
  const common_1 = require("../../src/lib/common");
49
51
  const Model = __importStar(require("../../src/lib/model"));
52
+ const tsskeygen_1 = require("../../src/lib/model/tsskeygen");
50
53
  const bchaddresstranslator_1 = require("../../src/lib/bchaddresstranslator");
51
54
  const TestData = __importStar(require("../testdata"));
52
55
  const helpers_1 = __importDefault(require("./helpers"));
53
56
  const clienterror_1 = require("../../src/lib/errors/clienterror");
57
+ const expressapp_1 = require("../../src/lib/expressapp");
54
58
  const should = chai.should();
55
59
  config_1.default.moralis = config_1.default.moralis ?? {
56
60
  apiKey: 'apiKey',
@@ -1085,6 +1089,231 @@ describe('Wallet service', function () {
1085
1089
  done();
1086
1090
  });
1087
1091
  });
1092
+ it('should fail to join with wrong signature even when hardwareSourcePublicKey is present', function (done) {
1093
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1094
+ walletId: walletId,
1095
+ name: 'me',
1096
+ xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
1097
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1098
+ });
1099
+ copayerOpts.name = 'me2';
1100
+ copayerOpts.hardwareSourcePublicKey = 'attacker-controlled-hw-pubkey';
1101
+ server.joinWallet(copayerOpts, function (err, result) {
1102
+ should.not.exist(result);
1103
+ should.exist(err);
1104
+ err.message.should.equal('Bad request');
1105
+ server.getWallet({}, function (err, wallet) {
1106
+ should.not.exist(err);
1107
+ wallet.copayers.length.should.equal(0);
1108
+ done();
1109
+ });
1110
+ });
1111
+ });
1112
+ it('should fail to join with wrong signature even when clientDerivedPublicKey is present', function (done) {
1113
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1114
+ walletId: walletId,
1115
+ name: 'me',
1116
+ xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
1117
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1118
+ });
1119
+ copayerOpts.name = 'me2';
1120
+ copayerOpts.clientDerivedPublicKey = 'attacker-controlled-client-derived-pubkey';
1121
+ server.joinWallet(copayerOpts, function (err, result) {
1122
+ should.not.exist(result);
1123
+ should.exist(err);
1124
+ err.message.should.equal('Bad request');
1125
+ server.getWallet({}, function (err, wallet) {
1126
+ should.not.exist(err);
1127
+ wallet.copayers.length.should.equal(0);
1128
+ done();
1129
+ });
1130
+ });
1131
+ });
1132
+ it('should join existing wallet with a valid signature and hardwareSourcePublicKey present', function (done) {
1133
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1134
+ walletId: walletId,
1135
+ name: 'me',
1136
+ xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
1137
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1138
+ });
1139
+ copayerOpts.hardwareSourcePublicKey = 'legit-hw-pubkey';
1140
+ server.joinWallet(copayerOpts, function (err, result) {
1141
+ should.not.exist(err);
1142
+ should.exist(result);
1143
+ should.exist(result.copayerId);
1144
+ server.getWallet({}, function (err, wallet) {
1145
+ should.not.exist(err);
1146
+ wallet.copayers.length.should.equal(1);
1147
+ const copayer = wallet.copayers[0];
1148
+ copayer.id.should.equal(result.copayerId);
1149
+ copayer.hardwareSourcePublicKey.should.equal('legit-hw-pubkey');
1150
+ done();
1151
+ });
1152
+ });
1153
+ });
1154
+ it('should join existing wallet with a valid signature and clientDerivedPublicKey present', function (done) {
1155
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1156
+ walletId: walletId,
1157
+ name: 'me',
1158
+ xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
1159
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1160
+ });
1161
+ copayerOpts.clientDerivedPublicKey = 'legit-client-derived-pubkey';
1162
+ server.joinWallet(copayerOpts, function (err, result) {
1163
+ should.not.exist(err);
1164
+ should.exist(result);
1165
+ should.exist(result.copayerId);
1166
+ server.getWallet({}, function (err, wallet) {
1167
+ should.not.exist(err);
1168
+ wallet.copayers.length.should.equal(1);
1169
+ const copayer = wallet.copayers[0];
1170
+ copayer.id.should.equal(result.copayerId);
1171
+ copayer.clientDerivedPublicKey.should.equal('legit-client-derived-pubkey');
1172
+ done();
1173
+ });
1174
+ });
1175
+ });
1176
+ it('should reject a join with hardwareSourcePublicKey present when xPubKey is absent', function (done) {
1177
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1178
+ walletId: walletId,
1179
+ name: 'me',
1180
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1181
+ });
1182
+ should.not.exist(copayerOpts.xPubKey);
1183
+ copayerOpts.hardwareSourcePublicKey = 'legit-hw-pubkey-no-xpub';
1184
+ server.joinWallet(copayerOpts, function (err, result) {
1185
+ should.not.exist(result);
1186
+ should.exist(err);
1187
+ err.should.be.instanceof(clienterror_1.ClientError);
1188
+ server.storage.fetchWallet(walletId, function (err, wallet) {
1189
+ should.not.exist(err);
1190
+ should.exist(wallet);
1191
+ wallet.copayers.length.should.equal(0);
1192
+ done();
1193
+ });
1194
+ });
1195
+ });
1196
+ for (const ancillaryField of ['hardwareSourcePublicKey', 'clientDerivedPublicKey']) {
1197
+ it(`should reject an xPubKey-less ${ancillaryField} join without persisting a copayer`, function (done) {
1198
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1199
+ walletId: walletId,
1200
+ name: 'me',
1201
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1202
+ });
1203
+ should.not.exist(copayerOpts.xPubKey);
1204
+ copayerOpts[ancillaryField] = 'legit-value-no-xpub';
1205
+ server.joinWallet(copayerOpts, function (err, result) {
1206
+ should.not.exist(result);
1207
+ should.exist(err);
1208
+ err.should.be.instanceof(clienterror_1.ClientError);
1209
+ server.storage.fetchWallet(walletId, function (err, wallet) {
1210
+ should.not.exist(err);
1211
+ should.exist(wallet);
1212
+ wallet.copayers.length.should.equal(0);
1213
+ done();
1214
+ });
1215
+ });
1216
+ });
1217
+ }
1218
+ it('should fail to join with a malformed xPubKey even when hardwareSourcePublicKey is present', function (done) {
1219
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1220
+ walletId: walletId,
1221
+ name: 'me',
1222
+ xPubKey: 'invalid',
1223
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1224
+ });
1225
+ copayerOpts.hardwareSourcePublicKey = 'legit-hw-pubkey';
1226
+ server.joinWallet(copayerOpts, function (err, result) {
1227
+ should.not.exist(result);
1228
+ should.exist(err);
1229
+ err.should.be.instanceof(clienterror_1.ClientError);
1230
+ server.storage.fetchWallet(walletId, function (err, wallet) {
1231
+ should.not.exist(err);
1232
+ should.exist(wallet);
1233
+ wallet.copayers.length.should.equal(0);
1234
+ done();
1235
+ });
1236
+ });
1237
+ });
1238
+ it('should fail to join with a malformed xPubKey even when clientDerivedPublicKey is present', function (done) {
1239
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1240
+ walletId: walletId,
1241
+ name: 'me',
1242
+ xPubKey: 'invalid',
1243
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1244
+ });
1245
+ copayerOpts.clientDerivedPublicKey = 'legit-client-derived-pubkey';
1246
+ server.joinWallet(copayerOpts, function (err, result) {
1247
+ should.not.exist(result);
1248
+ should.exist(err);
1249
+ err.should.be.instanceof(clienterror_1.ClientError);
1250
+ server.storage.fetchWallet(walletId, function (err, wallet) {
1251
+ should.not.exist(err);
1252
+ should.exist(wallet);
1253
+ wallet.copayers.length.should.equal(0);
1254
+ done();
1255
+ });
1256
+ });
1257
+ });
1258
+ it('should reject a join when xPubKey, hardwareSourcePublicKey and clientDerivedPublicKey are all missing or empty', function (done) {
1259
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1260
+ walletId: walletId,
1261
+ name: 'me',
1262
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1263
+ });
1264
+ should.not.exist(copayerOpts.xPubKey);
1265
+ copayerOpts.hardwareSourcePublicKey = '';
1266
+ copayerOpts.clientDerivedPublicKey = '';
1267
+ server.joinWallet(copayerOpts, function (err, result) {
1268
+ should.not.exist(result);
1269
+ should.exist(err);
1270
+ err.should.be.instanceof(clienterror_1.ClientError);
1271
+ server.storage.fetchWallet(walletId, function (err, wallet) {
1272
+ should.not.exist(err);
1273
+ should.exist(wallet);
1274
+ wallet.copayers.length.should.equal(0);
1275
+ done();
1276
+ });
1277
+ });
1278
+ });
1279
+ it('should fail to join a full wallet with a valid signature and hardwareSourcePublicKey present', function (done) {
1280
+ const firstCopayerOpts = helpers_1.default.getSignedCopayerOpts({
1281
+ walletId: walletId,
1282
+ name: 'copayer 1',
1283
+ xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
1284
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1285
+ });
1286
+ server.joinWallet(firstCopayerOpts, function (err) {
1287
+ should.not.exist(err);
1288
+ const secondCopayerOpts = helpers_1.default.getSignedCopayerOpts({
1289
+ walletId: walletId,
1290
+ name: 'copayer 2',
1291
+ xPubKey: TestData.copayers[1].xPubKey_44H_0H_0H,
1292
+ requestPubKey: TestData.copayers[1].pubKey_1H_0,
1293
+ });
1294
+ server.joinWallet(secondCopayerOpts, function (err) {
1295
+ should.not.exist(err);
1296
+ const thirdCopayerOpts = helpers_1.default.getSignedCopayerOpts({
1297
+ walletId: walletId,
1298
+ name: 'copayer 3',
1299
+ xPubKey: TestData.copayers[2].xPubKey_44H_0H_0H,
1300
+ requestPubKey: TestData.copayers[2].pubKey_1H_0,
1301
+ });
1302
+ thirdCopayerOpts.hardwareSourcePublicKey = 'attacker-or-legit-hw-pubkey';
1303
+ server.joinWallet(thirdCopayerOpts, function (err, result) {
1304
+ should.not.exist(result);
1305
+ should.exist(err);
1306
+ err.should.be.instanceof(clienterror_1.ClientError);
1307
+ err.code.should.equal('WALLET_FULL');
1308
+ server.getWallet({}, function (err, wallet) {
1309
+ should.not.exist(err);
1310
+ wallet.copayers.length.should.equal(2);
1311
+ done();
1312
+ });
1313
+ });
1314
+ });
1315
+ });
1316
+ });
1088
1317
  it('should set pkr and status = complete on last copayer joining (2-3)', function (done) {
1089
1318
  helpers_1.default.createAndJoinWallet(2, 3).then(function ({ server }) {
1090
1319
  server.getWallet({}, function (err, wallet) {
@@ -1317,6 +1546,87 @@ describe('Wallet service', function () {
1317
1546
  });
1318
1547
  });
1319
1548
  });
1549
+ describe('TSS wallets (non-participant bypass)', function () {
1550
+ it('should reject a join from a copayer who is not a TSS keygen session participant, even with clientDerivedPublicKey supplied', async function () {
1551
+ const server = new server_1.WalletService();
1552
+ const legitXPubKey = TestData.copayers[0].xPubKey_44H_0H_0H;
1553
+ const legitCopayerId = Model.Copayer.xPubToCopayerId('btc', legitXPubKey);
1554
+ const session = tsskeygen_1.TssKeyGenModel.create({
1555
+ id: 'tss-non-participant-bypass-test-session',
1556
+ message: { partyId: 0, broadcastMessages: [], p2pMessages: [], publicKey: 'dummy', round: 0 },
1557
+ n: 1,
1558
+ copayerId: legitCopayerId,
1559
+ version: 1.1,
1560
+ });
1561
+ session.sharedPublicKey = 'dummy-shared-public-key';
1562
+ await server.storage.db.collection('tss_keygen').deleteMany({ id: session.id });
1563
+ await server.storage.storeTssKeyGenSession({ doc: session });
1564
+ const walletId = await util_1.default.promisify(server.createWallet).call(server, {
1565
+ name: 'tss wallet',
1566
+ m: 1,
1567
+ n: 1,
1568
+ pubKey: TestData.keyPair.pub,
1569
+ coin: 'btc',
1570
+ tssKeyId: session.id,
1571
+ });
1572
+ should.exist(walletId);
1573
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1574
+ walletId,
1575
+ name: 'attacker',
1576
+ xPubKey: TestData.copayers[1].xPubKey_44H_0H_0H,
1577
+ clientDerivedPublicKey: 'attacker-controlled-client-derived-pubkey',
1578
+ requestPubKey: TestData.copayers[1].pubKey_1H_0,
1579
+ });
1580
+ const { err, result } = await new Promise(resolve => {
1581
+ server.joinWallet(copayerOpts, (err, result) => resolve({ err, result }));
1582
+ });
1583
+ should.not.exist(result);
1584
+ should.exist(err);
1585
+ err.code.should.equal('TSS_NON_PARTICIPANT');
1586
+ const wallet = await util_1.default.promisify(server.getWallet).call(server, {});
1587
+ wallet.copayers.length.should.equal(0);
1588
+ });
1589
+ it('should reject a TSS join with clientDerivedPublicKey present when xPubKey is absent, even if the ancillary-derived ID is a keygen session participant', async function () {
1590
+ const server = new server_1.WalletService();
1591
+ const clientDerivedPublicKey = 'legit-client-derived-pubkey-no-xpub';
1592
+ const ancillaryDerivedCopayerId = Model.Copayer.xPubToCopayerId('btc', clientDerivedPublicKey);
1593
+ const session = tsskeygen_1.TssKeyGenModel.create({
1594
+ id: 'tss-no-xpubkey-rejected-test-session',
1595
+ message: { partyId: 0, broadcastMessages: [], p2pMessages: [], publicKey: 'dummy', round: 0 },
1596
+ n: 1,
1597
+ copayerId: ancillaryDerivedCopayerId,
1598
+ version: 1.1,
1599
+ });
1600
+ session.sharedPublicKey = 'dummy-shared-public-key';
1601
+ await server.storage.db.collection('tss_keygen').deleteMany({ id: session.id });
1602
+ await server.storage.storeTssKeyGenSession({ doc: session });
1603
+ const walletId = await util_1.default.promisify(server.createWallet).call(server, {
1604
+ name: 'tss wallet',
1605
+ m: 1,
1606
+ n: 1,
1607
+ pubKey: TestData.keyPair.pub,
1608
+ coin: 'btc',
1609
+ tssKeyId: session.id,
1610
+ });
1611
+ should.exist(walletId);
1612
+ const copayerOpts = helpers_1.default.getSignedCopayerOpts({
1613
+ walletId,
1614
+ name: 'me',
1615
+ clientDerivedPublicKey,
1616
+ requestPubKey: TestData.copayers[0].pubKey_1H_0,
1617
+ });
1618
+ should.not.exist(copayerOpts.xPubKey);
1619
+ const { err, result } = await new Promise(resolve => {
1620
+ server.joinWallet(copayerOpts, (err, result) => resolve({ err, result }));
1621
+ });
1622
+ should.not.exist(result);
1623
+ should.exist(err);
1624
+ err.should.be.instanceof(clienterror_1.ClientError);
1625
+ const wallet = await util_1.default.promisify(server.storage.fetchWallet).call(server.storage, walletId);
1626
+ should.exist(wallet);
1627
+ wallet.copayers.length.should.equal(0);
1628
+ });
1629
+ });
1320
1630
  });
1321
1631
  describe('#removeWallet', function () {
1322
1632
  let server;
@@ -2609,6 +2919,93 @@ describe('Wallet service', function () {
2609
2919
  });
2610
2920
  });
2611
2921
  });
2922
+ describe('#add access with historical copayer state', function () {
2923
+ beforeEach(async function () {
2924
+ ({ server, wallet } = await helpers_1.default.createAndJoinWallet(1, 1));
2925
+ });
2926
+ function snapshotState(cb) {
2927
+ server.storage.db.collection(storage_1.Storage.collections.WALLETS).findOne({ id: wallet.id }, (err, walletDoc) => {
2928
+ if (err)
2929
+ return cb(err);
2930
+ server.storage.db.collection(storage_1.Storage.collections.COPAYERS_LOOKUP).findOne({ copayerId: opts.copayerId }, (err2, lookupDoc) => {
2931
+ if (err2)
2932
+ return cb(err2);
2933
+ return cb(null, {
2934
+ wallet: JSON.parse(JSON.stringify(walletDoc)),
2935
+ lookup: JSON.parse(JSON.stringify(lookupDoc))
2936
+ });
2937
+ });
2938
+ });
2939
+ }
2940
+ function mutateWalletDoc(mutation, cb) {
2941
+ server.storage.db.collection(storage_1.Storage.collections.WALLETS).findOne({ id: wallet.id }, (err, walletDoc) => {
2942
+ if (err)
2943
+ return cb(err);
2944
+ mutation(walletDoc);
2945
+ server.storage.db.collection(storage_1.Storage.collections.WALLETS).replaceOne({ id: wallet.id }, walletDoc, cb);
2946
+ });
2947
+ }
2948
+ it('should return NOT_AUTHORIZED when the target copayer is missing from the wallet', function (done) {
2949
+ mutateWalletDoc(doc => {
2950
+ doc.copayers = doc.copayers.filter(c => c.id !== opts.copayerId);
2951
+ }, (err) => {
2952
+ should.not.exist(err);
2953
+ snapshotState((err, before) => {
2954
+ should.not.exist(err);
2955
+ ws.addAccess(opts, (err, res) => {
2956
+ err.should.be.instanceof(clienterror_1.ClientError);
2957
+ err.code.should.equal('NOT_AUTHORIZED');
2958
+ should.not.exist(res);
2959
+ snapshotState((err, after) => {
2960
+ should.not.exist(err);
2961
+ after.should.deep.equal(before);
2962
+ done();
2963
+ });
2964
+ });
2965
+ });
2966
+ });
2967
+ });
2968
+ it('should return NOT_AUTHORIZED when the target copayer has no xPubKey', function (done) {
2969
+ mutateWalletDoc(doc => {
2970
+ delete doc.copayers[0].xPubKey;
2971
+ }, (err) => {
2972
+ should.not.exist(err);
2973
+ snapshotState((err, before) => {
2974
+ should.not.exist(err);
2975
+ ws.addAccess(opts, (err, res) => {
2976
+ err.should.be.instanceof(clienterror_1.ClientError);
2977
+ err.code.should.equal('NOT_AUTHORIZED');
2978
+ should.not.exist(res);
2979
+ snapshotState((err, after) => {
2980
+ should.not.exist(err);
2981
+ after.should.deep.equal(before);
2982
+ done();
2983
+ });
2984
+ });
2985
+ });
2986
+ });
2987
+ });
2988
+ it('should return NOT_AUTHORIZED when the target copayer has a malformed xPubKey', function (done) {
2989
+ mutateWalletDoc(doc => {
2990
+ doc.copayers[0].xPubKey = 'invalid';
2991
+ }, (err) => {
2992
+ should.not.exist(err);
2993
+ snapshotState((err, before) => {
2994
+ should.not.exist(err);
2995
+ ws.addAccess(opts, (err, res) => {
2996
+ err.should.be.instanceof(clienterror_1.ClientError);
2997
+ err.code.should.equal('NOT_AUTHORIZED');
2998
+ should.not.exist(res);
2999
+ snapshotState((err, after) => {
3000
+ should.not.exist(err);
3001
+ after.should.deep.equal(before);
3002
+ done();
3003
+ });
3004
+ });
3005
+ });
3006
+ });
3007
+ });
3008
+ });
2612
3009
  });
2613
3010
  describe('#getBalance', function () {
2614
3011
  let server;
@@ -8355,6 +8752,206 @@ describe('Wallet service', function () {
8355
8752
  it.skip('should get accepted/rejected transaction proposal', function (done) { });
8356
8753
  it.skip('should get broadcasted transaction proposal', function (done) { });
8357
8754
  });
8755
+ describe('#getTxByHash', function () {
8756
+ let serverA;
8757
+ let walletA;
8758
+ let serverB;
8759
+ let txp;
8760
+ beforeEach(async function () {
8761
+ ({ server: serverA, wallet: walletA } = await helpers_1.default.createAndJoinWallet(1, 1));
8762
+ await helpers_1.default.stubUtxos(serverA, walletA, 1);
8763
+ const txOpts = {
8764
+ outputs: [{
8765
+ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
8766
+ amount: 0.5e8
8767
+ }],
8768
+ feePerKb: 100e2,
8769
+ message: 'some message',
8770
+ };
8771
+ txp = await helpers_1.default.createAndPublishTx(serverA, txOpts, TestData.copayers[0].privKey_1H_0);
8772
+ should.exist(txp);
8773
+ const signatures = helpers_1.default.clientSign(txp, TestData.copayers[0].xPrivKey_44H_0H_0H);
8774
+ txp = await util_1.default.promisify(serverA.signTx).call(serverA, {
8775
+ txProposalId: txp.id,
8776
+ signatures,
8777
+ });
8778
+ should.exist(txp.txid);
8779
+ ({ server: serverB } = await helpers_1.default.createAndJoinWallet(1, 1, { offset: 1 }));
8780
+ });
8781
+ it('should not disclose another wallet\'s transaction proposal by hash', function (done) {
8782
+ serverB.getTxByHash({
8783
+ txid: txp.txid
8784
+ }, function (err, res) {
8785
+ should.exist(err);
8786
+ should.not.exist(res);
8787
+ err.should.be.instanceof(clienterror_1.ClientError);
8788
+ err.code.should.equal('TX_NOT_FOUND');
8789
+ err.message.should.equal('Transaction proposal not found');
8790
+ JSON.stringify(err).should.not.include(walletA.id);
8791
+ done();
8792
+ });
8793
+ });
8794
+ it('should get own transaction proposal by hash, unchanged from before the fix', function (done) {
8795
+ serverA.editTxNote({
8796
+ txid: txp.txid,
8797
+ body: 'a note from wallet A'
8798
+ }, function (err) {
8799
+ should.not.exist(err);
8800
+ serverA.getTxByHash({
8801
+ txid: txp.txid
8802
+ }, function (err, res) {
8803
+ should.not.exist(err);
8804
+ should.exist(res);
8805
+ res.id.should.equal(txp.id);
8806
+ res.walletId.should.equal(walletA.id);
8807
+ res.txid.should.equal(txp.txid);
8808
+ should.exist(res.raw);
8809
+ should.exist(res.note);
8810
+ res.note.body.should.equal('a note from wallet A');
8811
+ done();
8812
+ });
8813
+ });
8814
+ });
8815
+ it('should return the identical not-found error for a foreign txid and an unknown txid', function (done) {
8816
+ serverB.getTxByHash({
8817
+ txid: txp.txid
8818
+ }, function (errForeign, resForeign) {
8819
+ should.exist(errForeign);
8820
+ should.not.exist(resForeign);
8821
+ serverB.getTxByHash({
8822
+ txid: helpers_1.default.randomTXID()
8823
+ }, function (errUnknown, resUnknown) {
8824
+ should.exist(errUnknown);
8825
+ should.not.exist(resUnknown);
8826
+ errForeign.code.should.equal(errUnknown.code);
8827
+ errForeign.message.should.equal(errUnknown.message);
8828
+ errForeign.code.should.equal('TX_NOT_FOUND');
8829
+ done();
8830
+ });
8831
+ });
8832
+ });
8833
+ it('should return a bad-request error when txid is missing', function (done) {
8834
+ serverA.getTxByHash({}, function (err, res) {
8835
+ should.exist(err);
8836
+ should.not.exist(res);
8837
+ err.should.be.instanceof(clienterror_1.ClientError);
8838
+ err.code.should.equal('BADREQUEST');
8839
+ err.message.should.equal('Required argument: txid missing.');
8840
+ done();
8841
+ });
8842
+ });
8843
+ });
8844
+ describe('GET /v1/txproposalsbyhash/:id/ (HTTP route)', function () {
8845
+ const testHost = 'http://127.0.0.1';
8846
+ let testPort;
8847
+ let httpServer;
8848
+ let verifyStub;
8849
+ let walletA;
8850
+ let walletB;
8851
+ let txp;
8852
+ beforeEach(async function () {
8853
+ httpServer = undefined;
8854
+ verifyStub = undefined;
8855
+ let serverA;
8856
+ let serverB;
8857
+ ({ server: serverA, wallet: walletA } = await helpers_1.default.createAndJoinWallet(1, 1));
8858
+ await helpers_1.default.stubUtxos(serverA, walletA, 1);
8859
+ const txOpts = {
8860
+ outputs: [{
8861
+ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
8862
+ amount: 0.5e8
8863
+ }],
8864
+ feePerKb: 100e2,
8865
+ message: 'some message',
8866
+ };
8867
+ txp = await helpers_1.default.createAndPublishTx(serverA, txOpts, TestData.copayers[0].privKey_1H_0);
8868
+ const signatures = helpers_1.default.clientSign(txp, TestData.copayers[0].xPrivKey_44H_0H_0H);
8869
+ txp = await util_1.default.promisify(serverA.signTx).call(serverA, {
8870
+ txProposalId: txp.id,
8871
+ signatures,
8872
+ });
8873
+ should.exist(txp.txid);
8874
+ ({ server: serverB, wallet: walletB } = await helpers_1.default.createAndJoinWallet(1, 1, { offset: 1 }));
8875
+ verifyStub = sinon_1.default.stub(server_1.WalletService.prototype, '_verifySignature').returns(true);
8876
+ const app = new expressapp_1.ExpressApp();
8877
+ httpServer = new http_1.default.Server(app.app);
8878
+ await util_1.default.promisify(app.start).call(app, {
8879
+ storage: helpers_1.default.getStorage(),
8880
+ blockchainExplorer: helpers_1.default.getBlockchainExplorer(),
8881
+ request: sinon_1.default.stub(),
8882
+ disableLogs: true,
8883
+ basePath: config_1.default.basePath
8884
+ });
8885
+ await new Promise((resolve, reject) => {
8886
+ httpServer.once('error', reject);
8887
+ httpServer.listen(0, '127.0.0.1', () => {
8888
+ httpServer.off('error', reject);
8889
+ const address = httpServer.address();
8890
+ if (!address || typeof address === 'string') {
8891
+ return reject(new Error('HTTP test server did not bind to a TCP port'));
8892
+ }
8893
+ testPort = address.port;
8894
+ resolve();
8895
+ });
8896
+ });
8897
+ });
8898
+ afterEach(function (done) {
8899
+ if (verifyStub) {
8900
+ verifyStub.restore();
8901
+ verifyStub = undefined;
8902
+ }
8903
+ if (!httpServer?.listening) {
8904
+ httpServer = undefined;
8905
+ return done();
8906
+ }
8907
+ httpServer.close(err => {
8908
+ httpServer = undefined;
8909
+ done(err);
8910
+ });
8911
+ });
8912
+ function getByHash(copayerId, txid, cb) {
8913
+ (0, request_1.default)({
8914
+ method: 'GET',
8915
+ url: testHost + ':' + testPort + config_1.default.basePath + '/v1/txproposalsbyhash/' + txid + '/',
8916
+ headers: {
8917
+ 'x-identity': copayerId,
8918
+ 'x-signature': 'stubbed'
8919
+ },
8920
+ json: true
8921
+ }, (err, res, body) => cb(err, res, body));
8922
+ }
8923
+ it('foreign wallet gets HTTP 400 TX_NOT_FOUND with no owner data in the body', function (done) {
8924
+ getByHash(walletB.copayers[0].id, txp.txid, (err, res, body) => {
8925
+ should.not.exist(err);
8926
+ res.statusCode.should.equal(400);
8927
+ body.code.should.equal('TX_NOT_FOUND');
8928
+ JSON.stringify(body).should.not.include(walletA.id);
8929
+ done();
8930
+ });
8931
+ });
8932
+ it('owner wallet gets HTTP 200 with the full proposal, unchanged', function (done) {
8933
+ getByHash(walletA.copayers[0].id, txp.txid, (err, res, body) => {
8934
+ should.not.exist(err);
8935
+ res.statusCode.should.equal(200);
8936
+ body.walletId.should.equal(walletA.id);
8937
+ body.txid.should.equal(txp.txid);
8938
+ should.exist(body.raw);
8939
+ done();
8940
+ });
8941
+ });
8942
+ it('foreign and unknown txids produce an identical HTTP 400 TX_NOT_FOUND body', function (done) {
8943
+ getByHash(walletB.copayers[0].id, txp.txid, (err, resForeign, bodyForeign) => {
8944
+ should.not.exist(err);
8945
+ getByHash(walletB.copayers[0].id, helpers_1.default.randomTXID(), (err, resUnknown, bodyUnknown) => {
8946
+ should.not.exist(err);
8947
+ resForeign.statusCode.should.equal(resUnknown.statusCode);
8948
+ bodyForeign.code.should.equal(bodyUnknown.code);
8949
+ bodyForeign.code.should.equal('TX_NOT_FOUND');
8950
+ done();
8951
+ });
8952
+ });
8953
+ });
8954
+ });
8358
8955
  describe('#getTxs', function () {
8359
8956
  let server;
8360
8957
  let wallet;