@bitpay-labs/bitcore-wallet-service 11.10.13 → 11.11.0
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 +5 -5
- package/src/externalservices/moonpay.ts +6 -2
- package/src/lib/common/constants.ts +1 -1
- package/src/lib/errors/errordefinitions.ts +8 -0
- package/src/lib/model/tsskeygen.ts +12 -4
- package/src/lib/routes/middleware/authRequest.ts +6 -16
- package/src/lib/routes/middleware/authTssRequest.ts +130 -26
- package/src/lib/routes/tss.ts +21 -8
- package/src/lib/routes/wallets.ts +8 -9
- package/src/lib/server.ts +9 -17
- package/src/lib/storage.ts +55 -22
- package/src/lib/tss.ts +77 -1
- package/test/integration/externalservices/moonpay.test.ts +50 -0
- package/test/integration/tss.test.ts +706 -371
- package/test/storage.test.ts +6 -7
- package/ts_build/package.json +4 -4
- package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
- package/ts_build/src/externalservices/moonpay.js +8 -3
- package/ts_build/src/externalservices/moonpay.js.map +1 -1
- package/ts_build/src/lib/chain/btc/index.js +7 -7
- package/ts_build/src/lib/chain/btc/index.js.map +1 -1
- package/ts_build/src/lib/chain/eth/index.js +1 -1
- package/ts_build/src/lib/chain/eth/index.js.map +1 -1
- package/ts_build/src/lib/chain/sol/index.d.ts +2 -0
- package/ts_build/src/lib/chain/sol/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/sol/index.js +36 -3
- package/ts_build/src/lib/chain/sol/index.js.map +1 -1
- package/ts_build/src/lib/chain/xrp/index.js +2 -2
- package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
- package/ts_build/src/lib/common/constants.d.ts +1 -1
- package/ts_build/src/lib/common/constants.js +1 -1
- package/ts_build/src/lib/common/defaults.d.ts +3 -0
- package/ts_build/src/lib/common/defaults.d.ts.map +1 -1
- package/ts_build/src/lib/common/defaults.js +3 -0
- package/ts_build/src/lib/common/defaults.js.map +1 -1
- package/ts_build/src/lib/common/index.d.ts +4 -1
- package/ts_build/src/lib/common/index.d.ts.map +1 -1
- package/ts_build/src/lib/errors/errordefinitions.d.ts +6 -0
- package/ts_build/src/lib/errors/errordefinitions.d.ts.map +1 -1
- package/ts_build/src/lib/errors/errordefinitions.js +6 -0
- package/ts_build/src/lib/errors/errordefinitions.js.map +1 -1
- package/ts_build/src/lib/model/tsskeygen.d.ts +2 -0
- package/ts_build/src/lib/model/tsskeygen.d.ts.map +1 -1
- package/ts_build/src/lib/model/tsskeygen.js +6 -2
- package/ts_build/src/lib/model/tsskeygen.js.map +1 -1
- package/ts_build/src/lib/model/txproposal.d.ts +3 -2
- package/ts_build/src/lib/model/txproposal.d.ts.map +1 -1
- package/ts_build/src/lib/model/txproposal.js +33 -9
- package/ts_build/src/lib/model/txproposal.js.map +1 -1
- package/ts_build/src/lib/routes/context.d.ts.map +1 -1
- package/ts_build/src/lib/routes/context.js +3 -1
- package/ts_build/src/lib/routes/context.js.map +1 -1
- package/ts_build/src/lib/routes/helpers/error.js +2 -2
- package/ts_build/src/lib/routes/helpers/error.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/authRequest.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/authRequest.js +6 -16
- package/ts_build/src/lib/routes/middleware/authRequest.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts +2 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.js +104 -24
- package/ts_build/src/lib/routes/middleware/authTssRequest.js.map +1 -1
- package/ts_build/src/lib/routes/transactions.js +10 -5
- package/ts_build/src/lib/routes/transactions.js.map +1 -1
- package/ts_build/src/lib/routes/tss.d.ts.map +1 -1
- package/ts_build/src/lib/routes/tss.js +19 -7
- package/ts_build/src/lib/routes/tss.js.map +1 -1
- package/ts_build/src/lib/routes/wallets.d.ts.map +1 -1
- package/ts_build/src/lib/routes/wallets.js +12 -11
- package/ts_build/src/lib/routes/wallets.js.map +1 -1
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +14 -20
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/src/lib/storage.d.ts +9 -2
- package/ts_build/src/lib/storage.d.ts.map +1 -1
- package/ts_build/src/lib/storage.js +51 -20
- package/ts_build/src/lib/storage.js.map +1 -1
- package/ts_build/src/lib/tss.d.ts +8 -0
- package/ts_build/src/lib/tss.d.ts.map +1 -1
- package/ts_build/src/lib/tss.js +59 -1
- package/ts_build/src/lib/tss.js.map +1 -1
- package/ts_build/test/chain/btc.test.js +26 -0
- package/ts_build/test/chain/btc.test.js.map +1 -1
- package/ts_build/test/chain/eth.test.js +40 -0
- package/ts_build/test/chain/eth.test.js.map +1 -1
- package/ts_build/test/chain/sol.test.js +156 -0
- package/ts_build/test/chain/sol.test.js.map +1 -1
- package/ts_build/test/chain/xrp.test.js +18 -0
- package/ts_build/test/chain/xrp.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/moonpay.test.js +67 -0
- package/ts_build/test/integration/externalservices/moonpay.test.js.map +1 -1
- package/ts_build/test/integration/server.test.js +87 -0
- package/ts_build/test/integration/server.test.js.map +1 -1
- package/ts_build/test/integration/tss.test.d.ts.map +1 -1
- package/ts_build/test/integration/tss.test.js +599 -357
- package/ts_build/test/integration/tss.test.js.map +1 -1
- package/ts_build/test/model/txproposal.test.js +101 -0
- package/ts_build/test/model/txproposal.test.js.map +1 -1
- package/ts_build/test/numberformat-routes.test.d.ts +2 -0
- package/ts_build/test/numberformat-routes.test.d.ts.map +1 -0
- package/ts_build/test/numberformat-routes.test.js +116 -0
- package/ts_build/test/numberformat-routes.test.js.map +1 -0
- package/ts_build/test/storage.test.js +6 -7
- package/ts_build/test/storage.test.js.map +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@bitpay-labs/bitcore-wallet-service",
|
|
3
3
|
"description": "A service for Mutisig HD Bitcoin Wallets",
|
|
4
4
|
"author": "BitPay Inc",
|
|
5
|
-
"version": "11.
|
|
5
|
+
"version": "11.11.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "ts_build/src/index.js",
|
|
8
8
|
"types": "./ts_build/src/index.d.ts",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"start": "npm run clean && npm run build && ./start.sh",
|
|
26
26
|
"stop": "./stop.sh",
|
|
27
|
-
"test": "npm run compile && mcr mocha ts_build/test",
|
|
27
|
+
"test": "npm run compile && mcr mocha ts_build/test/**/*.js",
|
|
28
28
|
"build": "tsc && npm run cp:templates",
|
|
29
29
|
"build:prod": "tsc -p tsconfig.prod.json && npm run cp:templates",
|
|
30
30
|
"build:docker": "docker build -t bitpay/bitcore-wallet-service:latest -f ./Dockerfile ../..",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@bitpay-labs/bitcore-logging": "^11.10.9",
|
|
45
|
-
"@bitpay-labs/bitcore-tss": "^11.
|
|
46
|
-
"@bitpay-labs/crypto-wallet-core": "^11.
|
|
45
|
+
"@bitpay-labs/bitcore-tss": "^11.11.0",
|
|
46
|
+
"@bitpay-labs/crypto-wallet-core": "^11.11.0",
|
|
47
47
|
"@sendgrid/mail": "6.5.4",
|
|
48
48
|
"abi-decoder": "2.4.0",
|
|
49
49
|
"async": "0.9.2",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
"email": "kjoseph@bitpay.com"
|
|
108
108
|
}
|
|
109
109
|
],
|
|
110
|
-
"gitHead": "
|
|
110
|
+
"gitHead": "df3d9a76fd7418767d2951fbbfce5a0c7c1db215"
|
|
111
111
|
}
|
|
@@ -447,7 +447,8 @@ export class MoonpayService {
|
|
|
447
447
|
if (err) {
|
|
448
448
|
return reject(err.body ? err.body : err);
|
|
449
449
|
} else {
|
|
450
|
-
|
|
450
|
+
const success = data.statusCode >= 200 && data.statusCode < 300;
|
|
451
|
+
return resolve(data.body ? data.body : { success });
|
|
451
452
|
}
|
|
452
453
|
}
|
|
453
454
|
);
|
|
@@ -526,7 +527,10 @@ export class MoonpayService {
|
|
|
526
527
|
if (err) {
|
|
527
528
|
return reject(err.body ?? err);
|
|
528
529
|
} else {
|
|
529
|
-
|
|
530
|
+
if (!data.body) {
|
|
531
|
+
return reject(new ClientError('Moonpay did not return a session'));
|
|
532
|
+
}
|
|
533
|
+
return resolve(data.body);
|
|
530
534
|
}
|
|
531
535
|
}
|
|
532
536
|
);
|
|
@@ -226,7 +226,7 @@ export const Constants = {
|
|
|
226
226
|
|
|
227
227
|
// May be different from the defaults if we rollback
|
|
228
228
|
// or want to beta test new versions.
|
|
229
|
-
TSS_KEYGEN_SCHEME_VERSION_MAX: 1.
|
|
229
|
+
TSS_KEYGEN_SCHEME_VERSION_MAX: 1.2,
|
|
230
230
|
TSS_SIGGEN_SCHEME_VERSION_MAX: 1.1,
|
|
231
231
|
|
|
232
232
|
TSS_KEYGEN_SCHEME_VERSION_MIN: 1,
|
|
@@ -82,6 +82,10 @@ interface Errors<T> {
|
|
|
82
82
|
TSS_SESSION_EXPIRED: T;
|
|
83
83
|
TSS_MISMATCH_VERSION: T;
|
|
84
84
|
TSS_FINAL_SIGNATURE_MISMATCH: T;
|
|
85
|
+
TSS_INVALID_PARTY_DATA: T;
|
|
86
|
+
TSS_PARTY_ALREADY_REGISTERED: T;
|
|
87
|
+
TSS_ROUNDS_ALREADY_STARTED: T;
|
|
88
|
+
TSS_REGISTRATION_PENDING: T;
|
|
85
89
|
};
|
|
86
90
|
|
|
87
91
|
const errors: Errors<string> = {
|
|
@@ -159,6 +163,10 @@ const errors: Errors<string> = {
|
|
|
159
163
|
TSS_SESSION_EXPIRED: 'TSS session has expired',
|
|
160
164
|
TSS_MISMATCH_VERSION: 'TSS version does not match session version',
|
|
161
165
|
TSS_FINAL_SIGNATURE_MISMATCH: 'Final signature does not match the one generated by the session',
|
|
166
|
+
TSS_INVALID_PARTY_DATA: 'Invalid party data',
|
|
167
|
+
TSS_PARTY_ALREADY_REGISTERED: 'Party already registered with a different party ID',
|
|
168
|
+
TSS_ROUNDS_ALREADY_STARTED: 'Rounds have already started',
|
|
169
|
+
TSS_REGISTRATION_PENDING: 'Awaiting registration by the session initiator',
|
|
162
170
|
};
|
|
163
171
|
|
|
164
172
|
const errorsObject = { codes: {} };
|
|
@@ -44,10 +44,14 @@ export interface ITssKeyGenModel {
|
|
|
44
44
|
*/
|
|
45
45
|
n: number;
|
|
46
46
|
/**
|
|
47
|
-
* The list of
|
|
48
|
-
*
|
|
47
|
+
* The list of copayer IDs of the participants in the TSS key generation process.
|
|
48
|
+
* They are in partyId order.
|
|
49
49
|
*/
|
|
50
50
|
participants: Array<string>;
|
|
51
|
+
/**
|
|
52
|
+
* The list of authentication public keys corresponding to the participants in the TSS key generation process.
|
|
53
|
+
*/
|
|
54
|
+
authPublicKeys: Array<string>;
|
|
51
55
|
/**
|
|
52
56
|
* An array of the rounds in round order. Each round is an array of messages
|
|
53
57
|
*/
|
|
@@ -89,6 +93,7 @@ export class TssKeyGenModel implements ITssKeyGenModel {
|
|
|
89
93
|
id: string;
|
|
90
94
|
n: number;
|
|
91
95
|
participants: Array<string>;
|
|
96
|
+
authPublicKeys: Array<string>;
|
|
92
97
|
rounds: Array<Array<{
|
|
93
98
|
fromPartyId: number;
|
|
94
99
|
messages: ITssKeyMessageObject;
|
|
@@ -135,9 +140,11 @@ export class TssKeyGenModel implements ITssKeyGenModel {
|
|
|
135
140
|
x.schemeVersion = version || Defaults.TSS_KEYGEN_SCHEME_VERSION;
|
|
136
141
|
x.n = n;
|
|
137
142
|
x.participants = new Array(n);
|
|
138
|
-
x.participants[
|
|
143
|
+
x.participants[0] = copayerId; // caller's partyId is 0 (enforced above)
|
|
144
|
+
x.authPublicKeys = new Array(n);
|
|
145
|
+
x.authPublicKeys[0] = message.publicKey; // caller's partyId is 0 (enforced above)
|
|
139
146
|
x.rounds = [[{
|
|
140
|
-
fromPartyId: partyId
|
|
147
|
+
fromPartyId: 0, // caller's partyId is 0 (enforced above)
|
|
141
148
|
messages: message
|
|
142
149
|
}]];
|
|
143
150
|
x.joinPassword = passwordHash;
|
|
@@ -154,6 +161,7 @@ export class TssKeyGenModel implements ITssKeyGenModel {
|
|
|
154
161
|
x.schemeVersion = obj.schemeVersion;
|
|
155
162
|
x.n = obj.n;
|
|
156
163
|
x.participants = obj.participants;
|
|
164
|
+
x.authPublicKeys = obj.authPublicKeys;
|
|
157
165
|
x.rounds = obj.rounds;
|
|
158
166
|
x.sharedPublicKey = obj.sharedPublicKey;
|
|
159
167
|
x.joinPassword = obj.joinPassword;
|
|
@@ -39,16 +39,13 @@ async function withSession(storage: Storage, opts): Promise<Copayer> {
|
|
|
39
39
|
return reject(Errors.NOT_AUTHORIZED.withMessage('Session expired'));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
storage.fetchCopayerLookup(opts.copayerId
|
|
43
|
-
if (err) {
|
|
44
|
-
return reject(err);
|
|
45
|
-
}
|
|
42
|
+
storage.fetchCopayerLookup(opts.copayerId).then(copayer => {
|
|
46
43
|
if (!copayer) {
|
|
47
44
|
return reject(Errors.NOT_AUTHORIZED.withMessage('Copayer not found'));
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
return resolve(copayer);
|
|
51
|
-
});
|
|
48
|
+
}).catch(err => reject(err));
|
|
52
49
|
});
|
|
53
50
|
});
|
|
54
51
|
|
|
@@ -60,17 +57,10 @@ async function withSignature(storage: Storage, opts): Promise<Copayer> {
|
|
|
60
57
|
return;
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
const copayer = await
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
if (!copayer) {
|
|
69
|
-
return reject(Errors.NOT_AUTHORIZED.withMessage('Copayer not found'));
|
|
70
|
-
}
|
|
71
|
-
return resolve(copayer);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
60
|
+
const copayer = await storage.fetchCopayerLookup(opts.copayerId);
|
|
61
|
+
if (!copayer) {
|
|
62
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found');
|
|
63
|
+
}
|
|
74
64
|
|
|
75
65
|
const isValid = !!copayer.requestPubKeys.find(pubKey => Utils.verifyMessage(opts.message, opts.signature, pubKey.key));
|
|
76
66
|
if (!isValid) {
|
|
@@ -5,15 +5,23 @@ 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
|
-
import type { TssKeyGenModel } from '../../model/tsskeygen';
|
|
9
|
-
import type { TssSigGenModel } from '../../model/tsssign';
|
|
8
|
+
import type { ITssKeyMessageObject, TssKeyGenModel } from '../../model/tsskeygen';
|
|
9
|
+
import type { ITssSigMessageObject, TssSigGenModel } from '../../model/tsssign';
|
|
10
|
+
|
|
11
|
+
function verifyMessage(req: express.Request, signature: string, pubKey: string) {
|
|
12
|
+
const message = getMessage(req);
|
|
13
|
+
const isValid = !!Utils.verifyMessage(message, signature, pubKey);
|
|
14
|
+
if (!isValid) {
|
|
15
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Invalid signature');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
10
18
|
|
|
11
19
|
/**
|
|
12
|
-
* Middleware to authenticate TSS requests and attach the TSS session to the request.
|
|
20
|
+
* Middleware to authenticate TSS key generation requests and attach the TSS session to the request.
|
|
13
21
|
* It checks if the request has valid credentials, verifies the signature,
|
|
14
22
|
* and retrieves the TSS session based on the request path.
|
|
15
23
|
*/
|
|
16
|
-
export function
|
|
24
|
+
export function authTssKeyGenRequest(): express.RequestHandler {
|
|
17
25
|
return async function(req, res, next) {
|
|
18
26
|
try {
|
|
19
27
|
const storage = WalletService.getStorage();
|
|
@@ -28,37 +36,74 @@ export function authTssRequest(): express.RequestHandler {
|
|
|
28
36
|
} = credentials;
|
|
29
37
|
|
|
30
38
|
const { id } = req.params as { [key: string]: string };
|
|
39
|
+
const body = req.body as { message: ITssKeyMessageObject | undefined };
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let pubKey = null;
|
|
35
|
-
if (req.path.includes('/tss/keygen/')) {
|
|
36
|
-
session = await storage.fetchTssKeyGenSession({ id });
|
|
37
|
-
partyId = session?.participants.indexOf(copayerId);
|
|
38
|
-
pubKey = partyId > -1 ? session.rounds[0].find(r => r.fromPartyId === partyId)?.messages.publicKey : null;
|
|
39
|
-
} else if (req.path.includes('/tss/sign/')) {
|
|
40
|
-
session = await storage.fetchTssSigSession({ id });
|
|
41
|
-
partyId = session?.participants.find(p => p.copayerId === copayerId)?.partyId;
|
|
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);
|
|
45
|
-
}
|
|
41
|
+
// Note: the copayer is not fetched from the db here bc it's not stored
|
|
42
|
+
// until the key generation session is done and the wallet gets created.
|
|
46
43
|
|
|
44
|
+
const session: TssKeyGenModel = await storage.fetchTssKeyGenSession({ id });
|
|
47
45
|
if (!session) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
// This is a brand new session. Caller should be party 0 with the initial round message.
|
|
47
|
+
if (body?.message?.round === 0 && body?.message?.partyId === 0) {
|
|
48
|
+
const pubKey = body?.message?.publicKey;
|
|
49
|
+
verifyMessage(req, signature, pubKey); // Self verification, but at least ensures that the caller owns the message's private key
|
|
50
|
+
return next();
|
|
51
|
+
} else {
|
|
52
|
+
// Session should have been initialized by the client already in BWC > TssKeyGen.newKey()
|
|
53
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Session not found');
|
|
54
|
+
}
|
|
52
55
|
}
|
|
56
|
+
|
|
53
57
|
if (session.isExpired()) {
|
|
54
58
|
throw Errors.TSS_SESSION_EXPIRED;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
const isRound0Msg = body?.message?.round === 0;
|
|
62
|
+
const awaitingJoiners = session.participants.some(p => !p); // session.participants is initiated with n slots (initally null) that get populated as participants join
|
|
63
|
+
const participantIdx = session.participants.indexOf(copayerId); // equals -1 if the copayer has not joined yet
|
|
64
|
+
if (participantIdx === -1 && session.schemeVersion >= 1.2) {
|
|
65
|
+
// Check session.authPublicKeys to ensure initiator registered this participant
|
|
66
|
+
const msgPubKey = body?.message?.publicKey;
|
|
67
|
+
if (!msgPubKey) {
|
|
68
|
+
throw Errors.NOT_AUTHORIZED;
|
|
69
|
+
}
|
|
70
|
+
const _participantIdx = session.authPublicKeys.indexOf(msgPubKey);
|
|
71
|
+
if (_participantIdx === -1) {
|
|
72
|
+
throw Errors.NOT_AUTHORIZED;
|
|
73
|
+
}
|
|
74
|
+
if (session.participants[_participantIdx]) {
|
|
75
|
+
throw Errors.NOT_AUTHORIZED;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const partyId = participantIdx > -1
|
|
79
|
+
? participantIdx
|
|
80
|
+
: (awaitingJoiners && isRound0Msg)
|
|
81
|
+
? body?.message?.partyId // if participant has not joined session yet, use the partyId from the initial round message (verified below)
|
|
82
|
+
: -1;
|
|
83
|
+
|
|
84
|
+
if (partyId < 0 || partyId >= session.n || !Number.isInteger(partyId)) {
|
|
85
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found in session');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let pubKey: string = null;
|
|
89
|
+
if (session?.schemeVersion <= 1.1) {
|
|
90
|
+
// session.authPublicKeys was not added until 1.2, so fetch public key from the first round messages instead (old way)
|
|
91
|
+
pubKey = session.rounds[0].find(r => r.fromPartyId === partyId)?.messages.publicKey;
|
|
92
|
+
if (!pubKey && participantIdx === -1) {
|
|
93
|
+
// Joining party hasn't submitted its round 0 message yet and legacy sessions have no registration,
|
|
94
|
+
// so use the joining message's public key (verifyTssMessage verifies the message's payload against it)
|
|
95
|
+
pubKey = body?.message?.publicKey;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// authPublicKeys is set by the session leader, so this will verify that the leader and the calling participant here are on the same page
|
|
99
|
+
pubKey = session?.authPublicKeys[partyId];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!pubKey) {
|
|
103
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found in session');
|
|
61
104
|
}
|
|
105
|
+
|
|
106
|
+
verifyMessage(req, signature, pubKey);
|
|
62
107
|
|
|
63
108
|
req.session = session;
|
|
64
109
|
return next();
|
|
@@ -69,4 +114,63 @@ export function authTssRequest(): express.RequestHandler {
|
|
|
69
114
|
return error.returnError(new ClientError({ code: 'INTERNAL_ERROR' }), res, req);
|
|
70
115
|
}
|
|
71
116
|
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export function authTssSignRequest(): express.RequestHandler {
|
|
120
|
+
return async function(req, res, next) {
|
|
121
|
+
try {
|
|
122
|
+
const storage = WalletService.getStorage();
|
|
123
|
+
const credentials = getCredentials(req);
|
|
124
|
+
if (!checkRequired(credentials, ['copayerId', 'signature'])) {
|
|
125
|
+
throw Errors.NOT_AUTHORIZED;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const {
|
|
129
|
+
copayerId,
|
|
130
|
+
signature
|
|
131
|
+
} = credentials;
|
|
132
|
+
|
|
133
|
+
const { id } = req.params as { [key: string]: string };
|
|
134
|
+
const body = req.body as { message: ITssSigMessageObject | undefined };
|
|
135
|
+
// Only some requests carry a body.message (e.g. POST /store only sends the final signature).
|
|
136
|
+
// Omitting it is safe since routes that don't receive a message don't consume one.
|
|
137
|
+
const verifyMsg = !!body?.message;
|
|
138
|
+
|
|
139
|
+
const copayer = await storage.fetchCopayerLookup(copayerId);
|
|
140
|
+
if (!copayer) {
|
|
141
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found');
|
|
142
|
+
}
|
|
143
|
+
const pubKey = copayer.requestPubKeys[0].key;
|
|
144
|
+
if (verifyMsg && pubKey !== body?.message?.publicKey) {
|
|
145
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Invalid public key');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const session: TssSigGenModel = await storage.fetchTssSigSession({ id });
|
|
149
|
+
// If !session, it needs to be initiated
|
|
150
|
+
if (session) {
|
|
151
|
+
if (session.isExpired()) {
|
|
152
|
+
throw Errors.TSS_SESSION_EXPIRED;
|
|
153
|
+
}
|
|
154
|
+
const party = session.participants.find(p => p.copayerId === copayerId);
|
|
155
|
+
if (!party) {
|
|
156
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found in session');
|
|
157
|
+
}
|
|
158
|
+
if (verifyMsg && party.partyId !== body?.message?.partyId) {
|
|
159
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found in session');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
req.session = session;
|
|
163
|
+
} else if (body?.message?.round !== 0) {
|
|
164
|
+
throw Errors.TSS_SESSION_NOT_FOUND;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
verifyMessage(req, signature, pubKey);
|
|
168
|
+
return next();
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (err instanceof ClientError) {
|
|
171
|
+
return error.returnError(err, res, req);
|
|
172
|
+
}
|
|
173
|
+
return error.returnError(new ClientError({ code: 'INTERNAL_ERROR' }), res, req);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
72
176
|
};
|
package/src/lib/routes/tss.ts
CHANGED
|
@@ -2,7 +2,7 @@ import express from 'express';
|
|
|
2
2
|
import * as Types from '../../types/expressapp';
|
|
3
3
|
import { TssKeyGen, TssSign } from '../tss';
|
|
4
4
|
import { authRequest } from './middleware/authRequest';
|
|
5
|
-
import {
|
|
5
|
+
import { authTssKeyGenRequest, authTssSignRequest } from './middleware/authTssRequest';
|
|
6
6
|
import { createWalletLimiter } from './middleware/createWalletLimiter';
|
|
7
7
|
import { verifyTssMessage } from './middleware/verifyTssMessage';
|
|
8
8
|
|
|
@@ -23,7 +23,7 @@ export class TssRouter {
|
|
|
23
23
|
|
|
24
24
|
/** Key generation methods */
|
|
25
25
|
|
|
26
|
-
router.post('/v1/tss/keygen/:id', createWalletLimiter(opts), verifyTssMessage, async function(req, res) {
|
|
26
|
+
router.post('/v1/tss/keygen/:id', createWalletLimiter(opts), authTssKeyGenRequest(), verifyTssMessage, async function(req, res) {
|
|
27
27
|
try {
|
|
28
28
|
const id = req.params.id;
|
|
29
29
|
// version was not given by client until 1.1, so fallback to 1.0
|
|
@@ -36,7 +36,20 @@ export class TssRouter {
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
router.
|
|
39
|
+
router.post('/v1/tss/keygen/:id/register', authTssKeyGenRequest(), async function(req, res) {
|
|
40
|
+
try {
|
|
41
|
+
const id = req.params.id;
|
|
42
|
+
const copayerId = req.headers['x-identity'];
|
|
43
|
+
const data = req.body;
|
|
44
|
+
|
|
45
|
+
await TssKeyGen.registerParty({ id, copayerId, data });
|
|
46
|
+
return res.send();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return returnError(err ?? 'unknown', res, req);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
router.get('/v1/tss/keygen/:id/:round', authTssKeyGenRequest(), async function(req, res) {
|
|
40
53
|
let interval: NodeJS.Timeout;
|
|
41
54
|
try {
|
|
42
55
|
const abort = new AbortController();
|
|
@@ -74,7 +87,7 @@ export class TssRouter {
|
|
|
74
87
|
}
|
|
75
88
|
});
|
|
76
89
|
|
|
77
|
-
router.post('/v1/tss/keygen/:id/store',
|
|
90
|
+
router.post('/v1/tss/keygen/:id/store', authTssKeyGenRequest(), async function(req, res) {
|
|
78
91
|
try {
|
|
79
92
|
const id = req.params.id;
|
|
80
93
|
const copayerId = req.headers['x-identity'];
|
|
@@ -87,7 +100,7 @@ export class TssRouter {
|
|
|
87
100
|
}
|
|
88
101
|
});
|
|
89
102
|
|
|
90
|
-
router.post('/v1/tss/keygen/:id/secret',
|
|
103
|
+
router.post('/v1/tss/keygen/:id/secret', authTssKeyGenRequest(), async function(req, res) {
|
|
91
104
|
try {
|
|
92
105
|
const id = req.params.id;
|
|
93
106
|
const { secret } = req.body;
|
|
@@ -99,7 +112,7 @@ export class TssRouter {
|
|
|
99
112
|
}
|
|
100
113
|
});
|
|
101
114
|
|
|
102
|
-
router.get('/v1/tss/keygen/:id/secret',
|
|
115
|
+
router.get('/v1/tss/keygen/:id/secret', authTssKeyGenRequest(), async function(req, res) {
|
|
103
116
|
try {
|
|
104
117
|
const id = req.params.id;
|
|
105
118
|
const copayerId = req.headers['x-identity'];
|
|
@@ -126,7 +139,7 @@ export class TssRouter {
|
|
|
126
139
|
}
|
|
127
140
|
});
|
|
128
141
|
|
|
129
|
-
router.get('/v1/tss/sign/:id/:round',
|
|
142
|
+
router.get('/v1/tss/sign/:id/:round', authTssSignRequest(), async function(req, res) {
|
|
130
143
|
let interval: NodeJS.Timeout;
|
|
131
144
|
try {
|
|
132
145
|
const abort = new AbortController();
|
|
@@ -161,7 +174,7 @@ export class TssRouter {
|
|
|
161
174
|
}
|
|
162
175
|
});
|
|
163
176
|
|
|
164
|
-
router.post('/v1/tss/sign/:id/store',
|
|
177
|
+
router.post('/v1/tss/sign/:id/store', authTssSignRequest(), async function(req, res) {
|
|
165
178
|
try {
|
|
166
179
|
const id = req.params.id;
|
|
167
180
|
const { signature } = req.body;
|
|
@@ -229,15 +229,14 @@ export function registerWalletRoutes(router: express.Router, context: RouteConte
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
const storage = WalletService.getStorage();
|
|
232
|
-
const existing = await Promise.all<{ copayerId: string; verified: boolean }>(copayers.map(c =>
|
|
233
|
-
storage.fetchCopayerLookup(c.copayerId
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
})));
|
|
232
|
+
const existing = await Promise.all<{ copayerId: string; verified: boolean }>(copayers.map(async c => {
|
|
233
|
+
const copayer = await storage.fetchCopayerLookup(c.copayerId).catch(() => null);
|
|
234
|
+
if (!copayer) {
|
|
235
|
+
return { copayerId: c.copayerId, verified: false };
|
|
236
|
+
}
|
|
237
|
+
const verified = copayer.requestPubKeys.some(pubkey => Utils.verifyMessage(c.copayerId, c.signature, pubkey.key));
|
|
238
|
+
return { copayerId: c.copayerId, verified };
|
|
239
|
+
}));
|
|
241
240
|
return res.json(existing.filter(e => e.verified).map(e => e.copayerId));
|
|
242
241
|
} catch (err) {
|
|
243
242
|
return returnError(err, res, req);
|
package/src/lib/server.ts
CHANGED
|
@@ -394,10 +394,7 @@ export class WalletService implements IWalletService {
|
|
|
394
394
|
return cb(ex);
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
-
server.storage.fetchCopayerLookup(opts.copayerId
|
|
398
|
-
if (err) {
|
|
399
|
-
return cb(err);
|
|
400
|
-
}
|
|
397
|
+
server.storage.fetchCopayerLookup(opts.copayerId).then(copayer => {
|
|
401
398
|
if (!copayer) {
|
|
402
399
|
return cb(Errors.NOT_AUTHORIZED.withMessage('Copayer not found'));
|
|
403
400
|
}
|
|
@@ -420,7 +417,7 @@ export class WalletService implements IWalletService {
|
|
|
420
417
|
|
|
421
418
|
server.copayerId = opts.copayerId;
|
|
422
419
|
return cb(null, server);
|
|
423
|
-
});
|
|
420
|
+
}).catch(err => cb(err));
|
|
424
421
|
};
|
|
425
422
|
|
|
426
423
|
const withSession = cb => {
|
|
@@ -428,7 +425,7 @@ export class WalletService implements IWalletService {
|
|
|
428
425
|
return;
|
|
429
426
|
}
|
|
430
427
|
|
|
431
|
-
let server;
|
|
428
|
+
let server: WalletService;
|
|
432
429
|
try {
|
|
433
430
|
server = WalletService.getInstance(opts);
|
|
434
431
|
} catch (ex) {
|
|
@@ -445,10 +442,7 @@ export class WalletService implements IWalletService {
|
|
|
445
442
|
return cb(Errors.NOT_AUTHORIZED.withMessage('Session expired'));
|
|
446
443
|
}
|
|
447
444
|
|
|
448
|
-
server.storage.fetchCopayerLookup(opts.copayerId
|
|
449
|
-
if (err) {
|
|
450
|
-
return cb(err);
|
|
451
|
-
}
|
|
445
|
+
server.storage.fetchCopayerLookup(opts.copayerId).then(copayer => {
|
|
452
446
|
if (!copayer) {
|
|
453
447
|
return cb(Errors.NOT_AUTHORIZED.withMessage('Copayer not found'));
|
|
454
448
|
}
|
|
@@ -456,7 +450,7 @@ export class WalletService implements IWalletService {
|
|
|
456
450
|
server.copayerId = opts.copayerId;
|
|
457
451
|
server.walletId = copayer.walletId;
|
|
458
452
|
return cb(null, server);
|
|
459
|
-
});
|
|
453
|
+
}).catch(err => cb(err));
|
|
460
454
|
});
|
|
461
455
|
};
|
|
462
456
|
|
|
@@ -1060,8 +1054,7 @@ export class WalletService implements IWalletService {
|
|
|
1060
1054
|
derivationStrategy: wallet.derivationStrategy
|
|
1061
1055
|
});
|
|
1062
1056
|
|
|
1063
|
-
this.storage.fetchCopayerLookup(copayer.id
|
|
1064
|
-
if (err) return cb(err);
|
|
1057
|
+
this.storage.fetchCopayerLookup(copayer.id).then(res => {
|
|
1065
1058
|
if (res) return cb(Errors.COPAYER_REGISTERED);
|
|
1066
1059
|
|
|
1067
1060
|
if (opts.dryRun)
|
|
@@ -1113,7 +1106,7 @@ export class WalletService implements IWalletService {
|
|
|
1113
1106
|
}
|
|
1114
1107
|
);
|
|
1115
1108
|
});
|
|
1116
|
-
});
|
|
1109
|
+
}).catch(err => cb(err));
|
|
1117
1110
|
}
|
|
1118
1111
|
|
|
1119
1112
|
_addKeyToCopayer(wallet: Wallet, copayer: Copayer, opts, cb) {
|
|
@@ -1143,8 +1136,7 @@ export class WalletService implements IWalletService {
|
|
|
1143
1136
|
addAccess(opts, cb) {
|
|
1144
1137
|
if (!checkRequired(opts, ['copayerId', 'requestPubKey', 'signature'], cb)) return;
|
|
1145
1138
|
|
|
1146
|
-
this.storage.fetchCopayerLookup(opts.copayerId
|
|
1147
|
-
if (err) return cb(err);
|
|
1139
|
+
this.storage.fetchCopayerLookup(opts.copayerId).then(copayer => {
|
|
1148
1140
|
if (!copayer) return cb(Errors.NOT_AUTHORIZED);
|
|
1149
1141
|
this.storage.fetchWallet(copayer.walletId, (err, wallet) => {
|
|
1150
1142
|
if (err) return cb(err);
|
|
@@ -1164,7 +1156,7 @@ export class WalletService implements IWalletService {
|
|
|
1164
1156
|
|
|
1165
1157
|
this._addKeyToCopayer(wallet, copayer, opts, cb);
|
|
1166
1158
|
});
|
|
1167
|
-
});
|
|
1159
|
+
}).catch(err => cb(err));
|
|
1168
1160
|
}
|
|
1169
1161
|
|
|
1170
1162
|
_setClientVersion(version) {
|