@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
package/src/lib/model/tsssign.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { singleton } from 'preconditions';
|
|
1
2
|
import { Common } from '../common';
|
|
2
3
|
|
|
4
|
+
const $ = singleton();
|
|
5
|
+
|
|
3
6
|
const { Defaults } = Common;
|
|
4
7
|
|
|
5
8
|
|
|
@@ -60,6 +63,14 @@ export interface ITssSigGenModel {
|
|
|
60
63
|
fromPartyId: number;
|
|
61
64
|
messages: ITssSigMessageObject;
|
|
62
65
|
}>>;
|
|
66
|
+
/**
|
|
67
|
+
* Timestamp the session was created
|
|
68
|
+
*/
|
|
69
|
+
createdOn: number;
|
|
70
|
+
/**
|
|
71
|
+
* Session expires after this many milliseconds.
|
|
72
|
+
*/
|
|
73
|
+
timeLimit?: number;
|
|
63
74
|
/**
|
|
64
75
|
* The signature generated as the result of the TSS signature generation process.
|
|
65
76
|
* The signature can be generated along a derivation path, which produces a pubKey that's
|
|
@@ -90,16 +101,27 @@ export class TssSigGenModel implements ITssSigGenModel {
|
|
|
90
101
|
}>>;
|
|
91
102
|
signature?: ITssSigMessageObject['signature'];
|
|
92
103
|
schemeVersion: number;
|
|
104
|
+
createdOn: number;
|
|
105
|
+
timeLimit?: number;
|
|
93
106
|
__v: number;
|
|
94
107
|
|
|
95
108
|
|
|
96
|
-
static create(params: { id: string; message: ITssSigMessageObject; m: number; copayerId: string }): TssSigGenModel {
|
|
97
|
-
const { id, message, m, copayerId } = params;
|
|
109
|
+
static create(params: { id: string; message: ITssSigMessageObject; m: number; copayerId: string; version: number; timeLimit?: number }): TssSigGenModel {
|
|
110
|
+
const { id, message, m, copayerId, version } = params;
|
|
98
111
|
const { partyId } = message;
|
|
112
|
+
const timeLimit = parseFloat(params.timeLimit?.toString());
|
|
113
|
+
$.checkArgument(!timeLimit || (Number.isFinite(timeLimit) && timeLimit > 0), 'Time limit must be a positive number');
|
|
114
|
+
|
|
115
|
+
const MAX_TIME_LIMIT = 7 * 24 * 60; // 7 days in minutes
|
|
116
|
+
const ONE_MINUTE = 60 * 1000;
|
|
99
117
|
|
|
100
118
|
const x = new TssSigGenModel();
|
|
101
119
|
x.id = id;
|
|
102
|
-
x.schemeVersion = Defaults.TSS_SIGGEN_SCHEME_VERSION;
|
|
120
|
+
x.schemeVersion = version || Defaults.TSS_SIGGEN_SCHEME_VERSION;
|
|
121
|
+
x.createdOn = Date.now();
|
|
122
|
+
x.timeLimit = (timeLimit === 0
|
|
123
|
+
? MAX_TIME_LIMIT
|
|
124
|
+
: Math.min(timeLimit || Defaults.TSS_SIGGEN_TIME_LIMIT, MAX_TIME_LIMIT)) * ONE_MINUTE;
|
|
103
125
|
x.m = m;
|
|
104
126
|
x.participants = [{
|
|
105
127
|
partyId,
|
|
@@ -117,6 +139,8 @@ export class TssSigGenModel implements ITssSigGenModel {
|
|
|
117
139
|
const x = new TssSigGenModel();
|
|
118
140
|
x.id = obj.id;
|
|
119
141
|
x.schemeVersion = obj.schemeVersion;
|
|
142
|
+
x.createdOn = obj.createdOn;
|
|
143
|
+
x.timeLimit = obj.timeLimit;
|
|
120
144
|
x.m = obj.m;
|
|
121
145
|
x.participants = obj.participants;
|
|
122
146
|
x.rounds = obj.rounds;
|
|
@@ -133,4 +157,11 @@ export class TssSigGenModel implements ITssSigGenModel {
|
|
|
133
157
|
}
|
|
134
158
|
return mostRecentRound;
|
|
135
159
|
}
|
|
160
|
+
|
|
161
|
+
isExpired(): boolean {
|
|
162
|
+
if (!this.timeLimit) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return Date.now() > this.createdOn + this.timeLimit;
|
|
166
|
+
}
|
|
136
167
|
}
|
|
@@ -224,6 +224,7 @@ export class PushNotificationsService {
|
|
|
224
224
|
const chain = notification?.data?.chain || notification?.data?.coin;
|
|
225
225
|
const coin = chain; // backwards compatibility
|
|
226
226
|
const network = notification?.data?.network ? Utils.getNetworkName(chain, notification.data.network) : null;
|
|
227
|
+
const txid = typeof notification?.data?.txid === 'string' ? notification.data.txid : null;
|
|
227
228
|
|
|
228
229
|
if (sub.token) {
|
|
229
230
|
notificationData = {
|
|
@@ -267,10 +268,11 @@ export class PushNotificationsService {
|
|
|
267
268
|
network,
|
|
268
269
|
tokenAddress,
|
|
269
270
|
multisigContractAddress,
|
|
271
|
+
txid,
|
|
270
272
|
title,
|
|
271
273
|
body
|
|
272
274
|
};
|
|
273
|
-
const custom_uri = `bitpay://wallet?walletId=${walletId}&tokenAddress=${tokenAddress}&multisigContractAddress=${multisigContractAddress}&copayerId=${copayerId}&coin=${coin}&chain=${chain}&network=${network}¬ification_type=${notification_type}&title=${title}&body=${body}`;
|
|
275
|
+
const custom_uri = `bitpay://wallet?walletId=${walletId}&tokenAddress=${tokenAddress}&multisigContractAddress=${multisigContractAddress}&copayerId=${copayerId}&coin=${coin}&chain=${chain}&network=${network}¬ification_type=${notification_type}&txid=${txid}&title=${title}&body=${body}`;
|
|
274
276
|
notificationData = {
|
|
275
277
|
external_user_ids: [sub.externalUserId],
|
|
276
278
|
messages: {
|
|
@@ -5,7 +5,8 @@ import { Errors } from '../../errors/errordefinitions';
|
|
|
5
5
|
import { WalletService, checkRequired } from '../../server';
|
|
6
6
|
import { error } from '../helpers';
|
|
7
7
|
import { getCredentials, getMessage } from './authRequest';
|
|
8
|
-
|
|
8
|
+
import type { TssKeyGenModel } from '../../model/tsskeygen';
|
|
9
|
+
import type { TssSigGenModel } from '../../model/tsssign';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Middleware to authenticate TSS requests and attach the TSS session to the request.
|
|
@@ -13,7 +14,6 @@ import { getCredentials, getMessage } from './authRequest';
|
|
|
13
14
|
* and retrieves the TSS session based on the request path.
|
|
14
15
|
*/
|
|
15
16
|
export function authTssRequest(): express.RequestHandler {
|
|
16
|
-
|
|
17
17
|
return async function(req, res, next) {
|
|
18
18
|
try {
|
|
19
19
|
const storage = WalletService.getStorage();
|
|
@@ -28,7 +28,8 @@ export function authTssRequest(): express.RequestHandler {
|
|
|
28
28
|
} = credentials;
|
|
29
29
|
|
|
30
30
|
const { id } = req.params as { [key: string]: string };
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
let session: TssKeyGenModel | TssSigGenModel | null = null;
|
|
32
33
|
let partyId = null;
|
|
33
34
|
let pubKey = null;
|
|
34
35
|
if (req.path.includes('/tss/keygen/')) {
|
|
@@ -38,7 +39,9 @@ export function authTssRequest(): express.RequestHandler {
|
|
|
38
39
|
} else if (req.path.includes('/tss/sign/')) {
|
|
39
40
|
session = await storage.fetchTssSigSession({ id });
|
|
40
41
|
partyId = session?.participants.find(p => p.copayerId === copayerId)?.partyId;
|
|
41
|
-
pubKey = partyId == null ? null : session
|
|
42
|
+
pubKey = partyId == null ? null : session?.rounds[0]?.find(r => r.fromPartyId === partyId)?.messages.publicKey;
|
|
43
|
+
} else {
|
|
44
|
+
throw new Error('Unknown TSS request path: ' + req.path);
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
if (!session) {
|
|
@@ -47,6 +50,9 @@ export function authTssRequest(): express.RequestHandler {
|
|
|
47
50
|
if (!pubKey) {
|
|
48
51
|
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found in session');
|
|
49
52
|
}
|
|
53
|
+
if (session.isExpired()) {
|
|
54
|
+
throw Errors.TSS_SESSION_EXPIRED;
|
|
55
|
+
}
|
|
50
56
|
|
|
51
57
|
const message = getMessage(req);
|
|
52
58
|
const isValid = !!Utils.verifyMessage(message, signature, pubKey);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { utils as tssUtils } from '@bitpay-labs/bitcore-tss';
|
|
2
|
+
import { ClientError } from '../../errors/clienterror';
|
|
2
3
|
import { Errors } from '../../errors/errordefinitions';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { error } from '../helpers';
|
|
5
|
+
import type { ITssKeyMessageObject } from '../../model/tsskeygen';
|
|
6
|
+
import type { ITssSigMessageObject } from '../../model/tsssign';
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
export async function verifyTssMessage(req, res, next) {
|
|
@@ -9,16 +11,16 @@ export async function verifyTssMessage(req, res, next) {
|
|
|
9
11
|
const { message }: { message: ITssKeyMessageObject | ITssSigMessageObject } = req.body;
|
|
10
12
|
const { publicKey } = message || {};
|
|
11
13
|
if (!publicKey) {
|
|
12
|
-
|
|
14
|
+
throw Errors.TSS_PUBKEY_MISSING;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
if (!message.broadcastMessages?.length && !message.p2pMessages?.length) {
|
|
16
|
-
|
|
18
|
+
throw Errors.TSS_INVALID_MESSAGE;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
for (const m of message.broadcastMessages) {
|
|
20
22
|
if (!tssUtils.verifySignedData(m.payload, publicKey)) {
|
|
21
|
-
|
|
23
|
+
throw Errors.TSS_INVALID_MESSAGE_SIG;
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
for (const m of message.p2pMessages) {
|
|
@@ -26,12 +28,15 @@ export async function verifyTssMessage(req, res, next) {
|
|
|
26
28
|
// `m.payload.signature` is for the unencrypted message.
|
|
27
29
|
// Only the recipients can verify on the client side.
|
|
28
30
|
if (!m.payload || !m.payload.encryptedMessage || !m.payload.signature) {
|
|
29
|
-
|
|
31
|
+
throw Errors.TSS_INVALID_MESSAGE_SIG;
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
return next();
|
|
33
|
-
} catch {
|
|
34
|
-
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err instanceof ClientError) {
|
|
37
|
+
return error.returnError(err, res, req);
|
|
38
|
+
}
|
|
39
|
+
return error.returnError(Errors.TSS_INVALID_MESSAGE, res, req);
|
|
35
40
|
}
|
|
36
41
|
};
|
|
37
42
|
|
package/src/lib/routes/tss.ts
CHANGED
|
@@ -21,12 +21,15 @@ export class TssRouter {
|
|
|
21
21
|
const { returnError, opts } = params;
|
|
22
22
|
const router = express.Router();
|
|
23
23
|
|
|
24
|
+
/** Key generation methods */
|
|
25
|
+
|
|
24
26
|
router.post('/v1/tss/keygen/:id', createWalletLimiter(opts), verifyTssMessage, async function(req, res) {
|
|
25
27
|
try {
|
|
26
28
|
const id = req.params.id;
|
|
27
|
-
|
|
29
|
+
// version was not given by client until 1.1, so fallback to 1.0
|
|
30
|
+
const { message, n, password, version = 1.0, timeLimit } = req.body;
|
|
28
31
|
const copayerId = req.headers['x-identity'];
|
|
29
|
-
await TssKeyGen.processMessage({ id, message, n, password, copayerId });
|
|
32
|
+
await TssKeyGen.processMessage({ id, message, n, password, copayerId, version, timeLimit });
|
|
30
33
|
return res.send();
|
|
31
34
|
} catch (err) {
|
|
32
35
|
return returnError(err ?? 'unknown', res, req);
|
|
@@ -84,12 +87,16 @@ export class TssRouter {
|
|
|
84
87
|
}
|
|
85
88
|
});
|
|
86
89
|
|
|
90
|
+
|
|
91
|
+
/** Signature Methods */
|
|
92
|
+
|
|
87
93
|
router.post('/v1/tss/sign/:id', authRequest(), verifyTssMessage, async function(req, res) {
|
|
88
94
|
try {
|
|
89
95
|
const id = req.params.id;
|
|
90
|
-
|
|
96
|
+
// version was not given by client until 1.1, so fallback to 1.0
|
|
97
|
+
const { message, m, version = 1.0, timeLimit } = req.body;
|
|
91
98
|
const copayerId = req.headers['x-identity'];
|
|
92
|
-
await TssSign.processMessage({ id, message, m, copayerId });
|
|
99
|
+
await TssSign.processMessage({ id, message, m, copayerId, version, timeLimit });
|
|
93
100
|
return res.send();
|
|
94
101
|
} catch (err) {
|
|
95
102
|
return returnError(err ?? 'unknown', res, req);
|
package/src/lib/server.ts
CHANGED
|
@@ -668,15 +668,17 @@ export class WalletService implements IWalletService {
|
|
|
668
668
|
}
|
|
669
669
|
|
|
670
670
|
if (opts.tssKeyId) {
|
|
671
|
-
opts.tssVersion = opts.tssVersion || Defaults.TSS_KEYGEN_SCHEME_VERSION;
|
|
672
|
-
if (!(opts.tssVersion > 0 && opts.tssVersion <= Constants.TSS_KEYGEN_SCHEME_VERSION_MAX)) {
|
|
673
|
-
return cb(new ClientError('Invalid TSS version'));
|
|
674
|
-
}
|
|
675
|
-
|
|
676
671
|
const keySession = await storage.fetchTssKeyGenSession({ id: opts.tssKeyId });
|
|
677
672
|
if (!keySession || !keySession.sharedPublicKey) {
|
|
673
|
+
// TODO: support importing a TSS key that was generated outside of this BWS instance.
|
|
678
674
|
return cb(new ClientError('Invalid TSS key session id'));
|
|
679
675
|
}
|
|
676
|
+
|
|
677
|
+
opts.tssVersion = opts.tssVersion || keySession.schemeVersion || Defaults.TSS_KEYGEN_SCHEME_VERSION;
|
|
678
|
+
if (!(opts.tssVersion >= Constants.TSS_KEYGEN_SCHEME_VERSION_MIN && opts.tssVersion <= Constants.TSS_KEYGEN_SCHEME_VERSION_MAX)) {
|
|
679
|
+
return cb(new ClientError('Invalid TSS version'));
|
|
680
|
+
}
|
|
681
|
+
|
|
680
682
|
// TSS wallets behave like a single-sig
|
|
681
683
|
opts.m = 1;
|
|
682
684
|
opts.n = 1;
|
|
@@ -1144,9 +1146,13 @@ export class WalletService implements IWalletService {
|
|
|
1144
1146
|
if (err) return cb(err);
|
|
1145
1147
|
if (!wallet) return cb(Errors.NOT_AUTHORIZED);
|
|
1146
1148
|
|
|
1147
|
-
const
|
|
1149
|
+
const target = wallet.copayers.find(c => c.id === opts.copayerId);
|
|
1150
|
+
if (!target?.xPubKey) return cb(Errors.NOT_AUTHORIZED);
|
|
1148
1151
|
|
|
1149
|
-
|
|
1152
|
+
try {
|
|
1153
|
+
const isValid = this._verifyRequestPubKey(opts.requestPubKey, opts.signature, target.xPubKey);
|
|
1154
|
+
if (!isValid) return cb(Errors.NOT_AUTHORIZED);
|
|
1155
|
+
} catch (e) {
|
|
1150
1156
|
return cb(Errors.NOT_AUTHORIZED);
|
|
1151
1157
|
}
|
|
1152
1158
|
|
|
@@ -1207,7 +1213,17 @@ export class WalletService implements IWalletService {
|
|
|
1207
1213
|
* @param {boolean} [opts.dryRun] Simulate the action but do not change server state.
|
|
1208
1214
|
*/
|
|
1209
1215
|
joinWallet(opts, cb) {
|
|
1210
|
-
|
|
1216
|
+
// xPubKey is the canonical copayer identity in the current protocol. Alternate public-key
|
|
1217
|
+
// fields are ancillary metadata; supporting an xPubKey-less copayer requires a versioned
|
|
1218
|
+
// end-to-end identity protocol rather than a local identity fallback.
|
|
1219
|
+
// SECURITY: xPubKey is required for every join, including hardwareSourcePublicKey/
|
|
1220
|
+
// clientDerivedPublicKey ones. Copayer.xPubToCopayerId() (called below both for the TSS
|
|
1221
|
+
// participant check and inside Copayer.create()) now throws on a missing xpub for every
|
|
1222
|
+
// coin (see its own guard), so an xPubKey-less join can no longer derive a copayer id at
|
|
1223
|
+
// all. bitcore-wallet-client's only join path (_doJoinWallet) always sends a real
|
|
1224
|
+
// xPubKey regardless of these two fields, so this doesn't restrict any flow this repo's
|
|
1225
|
+
// own client exercises - see the TSS-participant comment below for how that was confirmed.
|
|
1226
|
+
if (!checkRequired(opts, ['walletId', 'name', 'requestPubKey', 'copayerSignature', 'xPubKey'], cb)) return;
|
|
1211
1227
|
if (!opts.name) return cb(new ClientError('Invalid copayer name'));
|
|
1212
1228
|
|
|
1213
1229
|
opts.coin = opts.coin || Defaults.COIN;
|
|
@@ -1216,17 +1232,17 @@ export class WalletService implements IWalletService {
|
|
|
1216
1232
|
}
|
|
1217
1233
|
if (!Utils.checkValueInCollection(opts.chain, Constants.CHAINS)) return cb(new ClientError('Invalid coin'));
|
|
1218
1234
|
|
|
1235
|
+
// xPubKey is parsed and validated for every join; ancillary fields never suppress it.
|
|
1236
|
+
// A future update will re-add conditional logic which allows opts.hardwareSourcePublicKey
|
|
1237
|
+
// or opts.clientDerivedPublicKey to circumvent this requirement
|
|
1219
1238
|
let xPubKey;
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
if (xPubKey.network == null) {
|
|
1228
|
-
return cb(new ClientError('Invalid extended public key'));
|
|
1229
|
-
}
|
|
1239
|
+
try {
|
|
1240
|
+
xPubKey = Bitcore_[opts.chain].HDPublicKey(opts.xPubKey);
|
|
1241
|
+
} catch {
|
|
1242
|
+
return cb(new ClientError('Invalid extended public key'));
|
|
1243
|
+
}
|
|
1244
|
+
if (xPubKey.network == null) {
|
|
1245
|
+
return cb(new ClientError('Invalid extended public key'));
|
|
1230
1246
|
}
|
|
1231
1247
|
|
|
1232
1248
|
this.walletId = opts.walletId;
|
|
@@ -1235,10 +1251,9 @@ export class WalletService implements IWalletService {
|
|
|
1235
1251
|
if (err) return cb(err);
|
|
1236
1252
|
if (!wallet) return cb(Errors.WALLET_NOT_FOUND);
|
|
1237
1253
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
}
|
|
1254
|
+
// SECURITY: opts.hardwareSourcePublicKey or opts.clientDerivedPublicKey may not
|
|
1255
|
+
// circumvent the required checks below. A future update may enable either to replace xPubKey
|
|
1256
|
+
// That is currently not supported
|
|
1242
1257
|
|
|
1243
1258
|
if (this._upgradeNeeded(UPGRADES.BCH_bwc_$lt_8_3_multisig, { chain: opts.chain, n: wallet.n })) {
|
|
1244
1259
|
return cb(Errors.UPGRADE_NEEDED.withMessage('BWC clients < 8.3 are no longer supported for multisig BCH wallets.'));
|
|
@@ -3139,7 +3154,14 @@ export class WalletService implements IWalletService {
|
|
|
3139
3154
|
* @returns {Object} txProposal
|
|
3140
3155
|
*/
|
|
3141
3156
|
getTxByHash(opts, cb) {
|
|
3142
|
-
|
|
3157
|
+
if (!checkRequired(opts, 'txid', cb)) return;
|
|
3158
|
+
|
|
3159
|
+
// Scoped to this.walletId: the global storage.fetchTxByHash
|
|
3160
|
+
// would return any wallet's TxProposal for a known txid, disclosing a
|
|
3161
|
+
// foreign wallet's proposal data to any authenticated copayer. The
|
|
3162
|
+
// internal global consumers (BlockchainMonitor, getWalletFromIdentifier)
|
|
3163
|
+
// are unaffected; they keep calling storage.fetchTxByHash directly.
|
|
3164
|
+
this.storage.fetchTxByHashForWallet(this.walletId, opts.txid, (err, txp) => {
|
|
3143
3165
|
if (err) return cb(err);
|
|
3144
3166
|
if (!txp) return cb(Errors.TX_NOT_FOUND);
|
|
3145
3167
|
|
|
@@ -3250,7 +3272,7 @@ export class WalletService implements IWalletService {
|
|
|
3250
3272
|
{
|
|
3251
3273
|
txProposalId: opts.txProposalId
|
|
3252
3274
|
},
|
|
3253
|
-
(err, txp) => {
|
|
3275
|
+
(err, txp: TxProposal) => {
|
|
3254
3276
|
if (err) return cb(err);
|
|
3255
3277
|
|
|
3256
3278
|
if (!txp.isPending()) return cb(Errors.TX_NOT_PENDING);
|
|
@@ -3258,7 +3280,13 @@ export class WalletService implements IWalletService {
|
|
|
3258
3280
|
const deleteLockTime = this.getRemainingDeleteLockTime(txp);
|
|
3259
3281
|
if (deleteLockTime > 0) return cb(Errors.TX_CANNOT_REMOVE);
|
|
3260
3282
|
|
|
3261
|
-
this.storage.removeTx(this.walletId, txp.id, () => {
|
|
3283
|
+
this.storage.removeTx(this.walletId, txp.id, async () => {
|
|
3284
|
+
try {
|
|
3285
|
+
const inputPaths = Array.isArray(txp.inputPaths) && txp.inputPaths?.length ? txp.inputPaths : [txp.inputPaths || 'm/0/0']; // doesn't actually matter what's in the array
|
|
3286
|
+
await Promise.all(inputPaths.map((_, i) => this.storage.removeTssSigSession({ id: `${txp.id}:input${i}` })));
|
|
3287
|
+
} catch (err) {
|
|
3288
|
+
logger.warn('Error removing tss sig session for wallet %s txp %s: %o', this.walletId, txp.id, err);
|
|
3289
|
+
}
|
|
3262
3290
|
this._notifyTxProposalAction('TxProposalRemoved', txp, cb);
|
|
3263
3291
|
});
|
|
3264
3292
|
}
|
package/src/lib/storage.ts
CHANGED
|
@@ -360,6 +360,25 @@ export class Storage {
|
|
|
360
360
|
);
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
// Wallet-scoped counterpart to fetchTxByHash, used by the authenticated
|
|
364
|
+
// by-hash API read.
|
|
365
|
+
fetchTxByHashForWallet(walletId: string, hash, cb: (err?: any, tx?: TxProposal) => void) {
|
|
366
|
+
if (!this.db) return cb();
|
|
367
|
+
|
|
368
|
+
this.db.collection(collections.TXS).findOne(
|
|
369
|
+
{
|
|
370
|
+
walletId,
|
|
371
|
+
txid: hash
|
|
372
|
+
},
|
|
373
|
+
(err, result) => {
|
|
374
|
+
if (err) return cb(err);
|
|
375
|
+
if (!result) return cb();
|
|
376
|
+
|
|
377
|
+
return this._completeTxData(walletId, TxProposal.fromObj(result), cb);
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
363
382
|
fetchLastTxs(walletId, creatorId, limit, cb) {
|
|
364
383
|
this.db
|
|
365
384
|
.collection(collections.TXS)
|
|
@@ -1910,7 +1929,7 @@ export class Storage {
|
|
|
1910
1929
|
|
|
1911
1930
|
async storeTssSignature({ id, signature }: { id: string; signature: ITssSigMessageObject['signature'] }) {
|
|
1912
1931
|
return this.db.collection(collections.TSS_SIGN).updateOne(
|
|
1913
|
-
{ id },
|
|
1932
|
+
{ id, $or: [{ signature: null }, { signature: { $exists: false } }] },
|
|
1914
1933
|
{
|
|
1915
1934
|
$set: {
|
|
1916
1935
|
signature
|
|
@@ -1920,4 +1939,8 @@ export class Storage {
|
|
|
1920
1939
|
);
|
|
1921
1940
|
}
|
|
1922
1941
|
|
|
1942
|
+
async removeTssSigSession({ id }: { id: string }) {
|
|
1943
|
+
return this.db.collection(collections.TSS_SIGN).deleteOne({ id }, { w: 1 });
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1923
1946
|
}
|