@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.
Files changed (103) hide show
  1. package/package.json +5 -5
  2. package/src/externalservices/moonpay.ts +6 -2
  3. package/src/lib/common/constants.ts +1 -1
  4. package/src/lib/errors/errordefinitions.ts +8 -0
  5. package/src/lib/model/tsskeygen.ts +12 -4
  6. package/src/lib/routes/middleware/authRequest.ts +6 -16
  7. package/src/lib/routes/middleware/authTssRequest.ts +130 -26
  8. package/src/lib/routes/tss.ts +21 -8
  9. package/src/lib/routes/wallets.ts +8 -9
  10. package/src/lib/server.ts +9 -17
  11. package/src/lib/storage.ts +55 -22
  12. package/src/lib/tss.ts +77 -1
  13. package/test/integration/externalservices/moonpay.test.ts +50 -0
  14. package/test/integration/tss.test.ts +706 -371
  15. package/test/storage.test.ts +6 -7
  16. package/ts_build/package.json +4 -4
  17. package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
  18. package/ts_build/src/externalservices/moonpay.js +8 -3
  19. package/ts_build/src/externalservices/moonpay.js.map +1 -1
  20. package/ts_build/src/lib/chain/btc/index.js +7 -7
  21. package/ts_build/src/lib/chain/btc/index.js.map +1 -1
  22. package/ts_build/src/lib/chain/eth/index.js +1 -1
  23. package/ts_build/src/lib/chain/eth/index.js.map +1 -1
  24. package/ts_build/src/lib/chain/sol/index.d.ts +2 -0
  25. package/ts_build/src/lib/chain/sol/index.d.ts.map +1 -1
  26. package/ts_build/src/lib/chain/sol/index.js +36 -3
  27. package/ts_build/src/lib/chain/sol/index.js.map +1 -1
  28. package/ts_build/src/lib/chain/xrp/index.js +2 -2
  29. package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
  30. package/ts_build/src/lib/common/constants.d.ts +1 -1
  31. package/ts_build/src/lib/common/constants.js +1 -1
  32. package/ts_build/src/lib/common/defaults.d.ts +3 -0
  33. package/ts_build/src/lib/common/defaults.d.ts.map +1 -1
  34. package/ts_build/src/lib/common/defaults.js +3 -0
  35. package/ts_build/src/lib/common/defaults.js.map +1 -1
  36. package/ts_build/src/lib/common/index.d.ts +4 -1
  37. package/ts_build/src/lib/common/index.d.ts.map +1 -1
  38. package/ts_build/src/lib/errors/errordefinitions.d.ts +6 -0
  39. package/ts_build/src/lib/errors/errordefinitions.d.ts.map +1 -1
  40. package/ts_build/src/lib/errors/errordefinitions.js +6 -0
  41. package/ts_build/src/lib/errors/errordefinitions.js.map +1 -1
  42. package/ts_build/src/lib/model/tsskeygen.d.ts +2 -0
  43. package/ts_build/src/lib/model/tsskeygen.d.ts.map +1 -1
  44. package/ts_build/src/lib/model/tsskeygen.js +6 -2
  45. package/ts_build/src/lib/model/tsskeygen.js.map +1 -1
  46. package/ts_build/src/lib/model/txproposal.d.ts +3 -2
  47. package/ts_build/src/lib/model/txproposal.d.ts.map +1 -1
  48. package/ts_build/src/lib/model/txproposal.js +33 -9
  49. package/ts_build/src/lib/model/txproposal.js.map +1 -1
  50. package/ts_build/src/lib/routes/context.d.ts.map +1 -1
  51. package/ts_build/src/lib/routes/context.js +3 -1
  52. package/ts_build/src/lib/routes/context.js.map +1 -1
  53. package/ts_build/src/lib/routes/helpers/error.js +2 -2
  54. package/ts_build/src/lib/routes/helpers/error.js.map +1 -1
  55. package/ts_build/src/lib/routes/middleware/authRequest.d.ts.map +1 -1
  56. package/ts_build/src/lib/routes/middleware/authRequest.js +6 -16
  57. package/ts_build/src/lib/routes/middleware/authRequest.js.map +1 -1
  58. package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts +2 -1
  59. package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts.map +1 -1
  60. package/ts_build/src/lib/routes/middleware/authTssRequest.js +104 -24
  61. package/ts_build/src/lib/routes/middleware/authTssRequest.js.map +1 -1
  62. package/ts_build/src/lib/routes/transactions.js +10 -5
  63. package/ts_build/src/lib/routes/transactions.js.map +1 -1
  64. package/ts_build/src/lib/routes/tss.d.ts.map +1 -1
  65. package/ts_build/src/lib/routes/tss.js +19 -7
  66. package/ts_build/src/lib/routes/tss.js.map +1 -1
  67. package/ts_build/src/lib/routes/wallets.d.ts.map +1 -1
  68. package/ts_build/src/lib/routes/wallets.js +12 -11
  69. package/ts_build/src/lib/routes/wallets.js.map +1 -1
  70. package/ts_build/src/lib/server.d.ts.map +1 -1
  71. package/ts_build/src/lib/server.js +14 -20
  72. package/ts_build/src/lib/server.js.map +1 -1
  73. package/ts_build/src/lib/storage.d.ts +9 -2
  74. package/ts_build/src/lib/storage.d.ts.map +1 -1
  75. package/ts_build/src/lib/storage.js +51 -20
  76. package/ts_build/src/lib/storage.js.map +1 -1
  77. package/ts_build/src/lib/tss.d.ts +8 -0
  78. package/ts_build/src/lib/tss.d.ts.map +1 -1
  79. package/ts_build/src/lib/tss.js +59 -1
  80. package/ts_build/src/lib/tss.js.map +1 -1
  81. package/ts_build/test/chain/btc.test.js +26 -0
  82. package/ts_build/test/chain/btc.test.js.map +1 -1
  83. package/ts_build/test/chain/eth.test.js +40 -0
  84. package/ts_build/test/chain/eth.test.js.map +1 -1
  85. package/ts_build/test/chain/sol.test.js +156 -0
  86. package/ts_build/test/chain/sol.test.js.map +1 -1
  87. package/ts_build/test/chain/xrp.test.js +18 -0
  88. package/ts_build/test/chain/xrp.test.js.map +1 -1
  89. package/ts_build/test/integration/externalservices/moonpay.test.js +67 -0
  90. package/ts_build/test/integration/externalservices/moonpay.test.js.map +1 -1
  91. package/ts_build/test/integration/server.test.js +87 -0
  92. package/ts_build/test/integration/server.test.js.map +1 -1
  93. package/ts_build/test/integration/tss.test.d.ts.map +1 -1
  94. package/ts_build/test/integration/tss.test.js +599 -357
  95. package/ts_build/test/integration/tss.test.js.map +1 -1
  96. package/ts_build/test/model/txproposal.test.js +101 -0
  97. package/ts_build/test/model/txproposal.test.js.map +1 -1
  98. package/ts_build/test/numberformat-routes.test.d.ts +2 -0
  99. package/ts_build/test/numberformat-routes.test.d.ts.map +1 -0
  100. package/ts_build/test/numberformat-routes.test.js +116 -0
  101. package/ts_build/test/numberformat-routes.test.js.map +1 -0
  102. package/ts_build/test/storage.test.js +6 -7
  103. 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.10.13",
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.10.13",
46
- "@bitpay-labs/crypto-wallet-core": "^11.10.13",
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": "c51c900f0009f1237ef30dca2c35922381864491"
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
- return resolve(data.body ? data.body : data);
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
- return resolve(data.body ?? data);
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.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 public keys of the participants in the TSS key generation process.
48
- * The pub keys are in partyId order.
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[partyId] = copayerId;
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, (err, copayer) => {
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 new Promise<Copayer>((resolve, reject) => {
64
- storage.fetchCopayerLookup(opts.copayerId, (err, copayer) => {
65
- if (err) {
66
- return reject(err);
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 authTssRequest(): express.RequestHandler {
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
- let session: TssKeyGenModel | TssSigGenModel | null = null;
33
- let partyId = null;
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
- throw Errors.NOT_AUTHORIZED.withMessage('Session not found');
49
- }
50
- if (!pubKey) {
51
- throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found in session');
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 message = getMessage(req);
58
- const isValid = !!Utils.verifyMessage(message, signature, pubKey);
59
- if (!isValid) {
60
- throw Errors.NOT_AUTHORIZED.withMessage('Invalid signature');
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
  };
@@ -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 { authTssRequest } from './middleware/authTssRequest';
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.get('/v1/tss/keygen/:id/:round', authTssRequest(), async function(req, res) {
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', authTssRequest(), async function(req, res) {
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', authTssRequest(), async function(req, res) {
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', authTssRequest(), async function(req, res) {
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', authTssRequest(), async function(req, res) {
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', authTssRequest(), async function(req, res) {
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 => new Promise((resolve) => {
233
- storage.fetchCopayerLookup(c.copayerId, (err, copayer) => {
234
- if (err || !copayer) {
235
- return resolve({ copayerId: c.copayerId, verified: false });
236
- }
237
- const verified = copayer.requestPubKeys.some(pubkey => Utils.verifyMessage(c.copayerId, c.signature, pubkey.key));
238
- return resolve({ copayerId: c.copayerId, verified });
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, (err, copayer) => {
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, (err, copayer) => {
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, (err, res) => {
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, (err, copayer) => {
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) {