@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.
- package/package.json +3 -3
- package/src/externalservices/coingecko.ts +24 -10
- package/src/externalservices/moonpay.ts +23 -1
- package/src/lib/chain/eth/abi-invoice.ts +139 -90
- package/src/lib/common/constants.ts +5 -2
- package/src/lib/common/defaults.ts +11 -2
- package/src/lib/errors/errordefinitions.ts +6 -0
- package/src/lib/model/copayer.ts +1 -0
- package/src/lib/model/tsskeygen.ts +33 -4
- package/src/lib/model/tsssign.ts +34 -3
- package/src/lib/pushnotificationsservice.ts +3 -1
- package/src/lib/routes/middleware/authTssRequest.ts +10 -4
- package/src/lib/routes/middleware/verifyTssMessage.ts +13 -8
- package/src/lib/routes/tss.ts +11 -4
- package/src/lib/server.ts +53 -25
- package/src/lib/storage.ts +24 -1
- package/src/lib/tss.ts +232 -30
- package/src/types/externalservices.d.ts +29 -1
- package/test/integration/externalservices/coinGecko.test.ts +71 -7
- package/test/integration/server.test.ts +731 -1
- package/test/integration/tss.test.ts +275 -0
- package/test/model/copayer.test.ts +128 -0
- package/test/storage.test.ts +91 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/externalservices/coingecko.d.ts.map +1 -1
- package/ts_build/src/externalservices/coingecko.js +14 -8
- package/ts_build/src/externalservices/coingecko.js.map +1 -1
- package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
- package/ts_build/src/externalservices/moonpay.js +38 -2
- package/ts_build/src/externalservices/moonpay.js.map +1 -1
- package/ts_build/src/lib/chain/eth/abi-invoice.d.ts +254 -41
- package/ts_build/src/lib/chain/eth/abi-invoice.d.ts.map +1 -1
- package/ts_build/src/lib/chain/eth/abi-invoice.js +138 -89
- package/ts_build/src/lib/chain/eth/abi-invoice.js.map +1 -1
- package/ts_build/src/lib/common/constants.d.ts +4 -2
- package/ts_build/src/lib/common/constants.d.ts.map +1 -1
- package/ts_build/src/lib/common/constants.js +4 -2
- package/ts_build/src/lib/common/constants.js.map +1 -1
- package/ts_build/src/lib/common/defaults.d.ts +4 -2
- package/ts_build/src/lib/common/defaults.d.ts.map +1 -1
- package/ts_build/src/lib/common/defaults.js +4 -2
- package/ts_build/src/lib/common/defaults.js.map +1 -1
- package/ts_build/src/lib/common/index.d.ts +8 -4
- package/ts_build/src/lib/common/index.d.ts.map +1 -1
- package/ts_build/src/lib/errors/errordefinitions.d.ts +3 -0
- package/ts_build/src/lib/errors/errordefinitions.d.ts.map +1 -1
- package/ts_build/src/lib/errors/errordefinitions.js +3 -0
- package/ts_build/src/lib/errors/errordefinitions.js.map +1 -1
- package/ts_build/src/lib/model/copayer.d.ts.map +1 -1
- package/ts_build/src/lib/model/copayer.js +1 -0
- package/ts_build/src/lib/model/copayer.js.map +1 -1
- package/ts_build/src/lib/model/tsskeygen.d.ts +3 -0
- package/ts_build/src/lib/model/tsskeygen.d.ts.map +1 -1
- package/ts_build/src/lib/model/tsskeygen.js +12 -3
- package/ts_build/src/lib/model/tsskeygen.js.map +1 -1
- package/ts_build/src/lib/model/tsssign.d.ts +7 -0
- package/ts_build/src/lib/model/tsssign.d.ts.map +1 -1
- package/ts_build/src/lib/model/tsssign.js +22 -2
- package/ts_build/src/lib/model/tsssign.js.map +1 -1
- package/ts_build/src/lib/pushnotificationsservice.d.ts.map +1 -1
- package/ts_build/src/lib/pushnotificationsservice.js +3 -1
- package/ts_build/src/lib/pushnotificationsservice.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.js +8 -2
- package/ts_build/src/lib/routes/middleware/authTssRequest.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.js +11 -6
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.js.map +1 -1
- package/ts_build/src/lib/routes/tss.d.ts.map +1 -1
- package/ts_build/src/lib/routes/tss.js +4 -4
- package/ts_build/src/lib/routes/tss.js.map +1 -1
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +33 -25
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/src/lib/storage.d.ts +4 -0
- package/ts_build/src/lib/storage.d.ts.map +1 -1
- package/ts_build/src/lib/storage.js +18 -1
- package/ts_build/src/lib/storage.js.map +1 -1
- package/ts_build/src/lib/tss.d.ts +4 -0
- package/ts_build/src/lib/tss.d.ts.map +1 -1
- package/ts_build/src/lib/tss.js +74 -17
- package/ts_build/src/lib/tss.js.map +1 -1
- package/ts_build/test/integration/externalservices/coinGecko.test.js +62 -8
- package/ts_build/test/integration/externalservices/coinGecko.test.js.map +1 -1
- package/ts_build/test/integration/server.test.js +597 -0
- package/ts_build/test/integration/server.test.js.map +1 -1
- package/ts_build/test/integration/tss.test.js +245 -0
- package/ts_build/test/integration/tss.test.js.map +1 -1
- package/ts_build/test/model/copayer.test.js +91 -0
- package/ts_build/test/model/copayer.test.js.map +1 -1
- package/ts_build/test/storage.test.js +73 -0
- 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;
|