@bitpay-labs/bitcore-wallet-service 11.10.14 → 11.11.1

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 (155) hide show
  1. package/package.json +5 -5
  2. package/src/config.ts +0 -20
  3. package/src/externalservices/banxa.ts +51 -10
  4. package/src/externalservices/changelly.ts +54 -6
  5. package/src/externalservices/moonpay.ts +85 -19
  6. package/src/externalservices/oneInch.ts +13 -5
  7. package/src/externalservices/ramp.ts +42 -8
  8. package/src/externalservices/sardine.ts +51 -10
  9. package/src/externalservices/simplex.ts +58 -11
  10. package/src/externalservices/thorswap.ts +46 -11
  11. package/src/externalservices/transak.ts +53 -11
  12. package/src/lib/common/constants.ts +1 -1
  13. package/src/lib/errors/errordefinitions.ts +8 -0
  14. package/src/lib/model/tsskeygen.ts +12 -4
  15. package/src/lib/routes/middleware/authRequest.ts +6 -16
  16. package/src/lib/routes/middleware/authTssRequest.ts +130 -26
  17. package/src/lib/routes/services.ts +0 -12
  18. package/src/lib/routes/tss.ts +21 -8
  19. package/src/lib/routes/wallets.ts +8 -9
  20. package/src/lib/server.ts +9 -20
  21. package/src/lib/storage.ts +55 -22
  22. package/src/lib/tss.ts +77 -1
  23. package/src/types/externalservices.d.ts +0 -1
  24. package/test/integration/externalservices/banxa.test.ts +95 -5
  25. package/test/integration/externalservices/changelly.test.ts +118 -6
  26. package/test/integration/externalservices/coinGecko.test.ts +4 -0
  27. package/test/integration/externalservices/moonpay.test.ts +178 -9
  28. package/test/integration/externalservices/oneInch.test.ts +28 -1
  29. package/test/integration/externalservices/ramp.test.ts +126 -4
  30. package/test/integration/externalservices/sardine.test.ts +95 -5
  31. package/test/integration/externalservices/simplex.test.ts +95 -5
  32. package/test/integration/externalservices/thorswap.test.ts +81 -5
  33. package/test/integration/externalservices/transak.test.ts +98 -6
  34. package/test/integration/helpers.ts +1 -1
  35. package/test/integration/server.test.ts +56 -88
  36. package/test/integration/tss.test.ts +706 -371
  37. package/test/storage.test.ts +6 -7
  38. package/ts_build/package.json +4 -4
  39. package/ts_build/src/config.d.ts.map +1 -1
  40. package/ts_build/src/config.js +0 -4
  41. package/ts_build/src/config.js.map +1 -1
  42. package/ts_build/src/externalservices/banxa.d.ts.map +1 -1
  43. package/ts_build/src/externalservices/banxa.js +51 -10
  44. package/ts_build/src/externalservices/banxa.js.map +1 -1
  45. package/ts_build/src/externalservices/changelly.d.ts.map +1 -1
  46. package/ts_build/src/externalservices/changelly.js +54 -6
  47. package/ts_build/src/externalservices/changelly.js.map +1 -1
  48. package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
  49. package/ts_build/src/externalservices/moonpay.js +85 -19
  50. package/ts_build/src/externalservices/moonpay.js.map +1 -1
  51. package/ts_build/src/externalservices/oneInch.d.ts.map +1 -1
  52. package/ts_build/src/externalservices/oneInch.js +13 -5
  53. package/ts_build/src/externalservices/oneInch.js.map +1 -1
  54. package/ts_build/src/externalservices/ramp.d.ts.map +1 -1
  55. package/ts_build/src/externalservices/ramp.js +42 -8
  56. package/ts_build/src/externalservices/ramp.js.map +1 -1
  57. package/ts_build/src/externalservices/sardine.d.ts.map +1 -1
  58. package/ts_build/src/externalservices/sardine.js +51 -10
  59. package/ts_build/src/externalservices/sardine.js.map +1 -1
  60. package/ts_build/src/externalservices/simplex.d.ts.map +1 -1
  61. package/ts_build/src/externalservices/simplex.js +55 -11
  62. package/ts_build/src/externalservices/simplex.js.map +1 -1
  63. package/ts_build/src/externalservices/thorswap.d.ts.map +1 -1
  64. package/ts_build/src/externalservices/thorswap.js +45 -11
  65. package/ts_build/src/externalservices/thorswap.js.map +1 -1
  66. package/ts_build/src/externalservices/transak.d.ts.map +1 -1
  67. package/ts_build/src/externalservices/transak.js +53 -11
  68. package/ts_build/src/externalservices/transak.js.map +1 -1
  69. package/ts_build/src/lib/common/constants.d.ts +1 -1
  70. package/ts_build/src/lib/common/constants.js +1 -1
  71. package/ts_build/src/lib/common/index.d.ts +1 -1
  72. package/ts_build/src/lib/errors/errordefinitions.d.ts +4 -0
  73. package/ts_build/src/lib/errors/errordefinitions.d.ts.map +1 -1
  74. package/ts_build/src/lib/errors/errordefinitions.js +4 -0
  75. package/ts_build/src/lib/errors/errordefinitions.js.map +1 -1
  76. package/ts_build/src/lib/model/tsskeygen.d.ts +2 -0
  77. package/ts_build/src/lib/model/tsskeygen.d.ts.map +1 -1
  78. package/ts_build/src/lib/model/tsskeygen.js +6 -2
  79. package/ts_build/src/lib/model/tsskeygen.js.map +1 -1
  80. package/ts_build/src/lib/routes/middleware/authRequest.d.ts.map +1 -1
  81. package/ts_build/src/lib/routes/middleware/authRequest.js +6 -16
  82. package/ts_build/src/lib/routes/middleware/authRequest.js.map +1 -1
  83. package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts +2 -1
  84. package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts.map +1 -1
  85. package/ts_build/src/lib/routes/middleware/authTssRequest.js +104 -24
  86. package/ts_build/src/lib/routes/middleware/authTssRequest.js.map +1 -1
  87. package/ts_build/src/lib/routes/services.d.ts.map +1 -1
  88. package/ts_build/src/lib/routes/services.js +0 -10
  89. package/ts_build/src/lib/routes/services.js.map +1 -1
  90. package/ts_build/src/lib/routes/tss.d.ts.map +1 -1
  91. package/ts_build/src/lib/routes/tss.js +19 -7
  92. package/ts_build/src/lib/routes/tss.js.map +1 -1
  93. package/ts_build/src/lib/routes/wallets.d.ts.map +1 -1
  94. package/ts_build/src/lib/routes/wallets.js +8 -9
  95. package/ts_build/src/lib/routes/wallets.js.map +1 -1
  96. package/ts_build/src/lib/server.d.ts +0 -2
  97. package/ts_build/src/lib/server.d.ts.map +1 -1
  98. package/ts_build/src/lib/server.js +8 -20
  99. package/ts_build/src/lib/server.js.map +1 -1
  100. package/ts_build/src/lib/storage.d.ts +9 -2
  101. package/ts_build/src/lib/storage.d.ts.map +1 -1
  102. package/ts_build/src/lib/storage.js +51 -20
  103. package/ts_build/src/lib/storage.js.map +1 -1
  104. package/ts_build/src/lib/tss.d.ts +8 -0
  105. package/ts_build/src/lib/tss.d.ts.map +1 -1
  106. package/ts_build/src/lib/tss.js +59 -1
  107. package/ts_build/src/lib/tss.js.map +1 -1
  108. package/ts_build/test/integration/externalservices/banxa.test.d.ts.map +1 -1
  109. package/ts_build/test/integration/externalservices/banxa.test.js +93 -5
  110. package/ts_build/test/integration/externalservices/banxa.test.js.map +1 -1
  111. package/ts_build/test/integration/externalservices/changelly.test.d.ts.map +1 -1
  112. package/ts_build/test/integration/externalservices/changelly.test.js +111 -6
  113. package/ts_build/test/integration/externalservices/changelly.test.js.map +1 -1
  114. package/ts_build/test/integration/externalservices/coinGecko.test.js +4 -0
  115. package/ts_build/test/integration/externalservices/coinGecko.test.js.map +1 -1
  116. package/ts_build/test/integration/externalservices/moonpay.test.d.ts.map +1 -1
  117. package/ts_build/test/integration/externalservices/moonpay.test.js +169 -9
  118. package/ts_build/test/integration/externalservices/moonpay.test.js.map +1 -1
  119. package/ts_build/test/integration/externalservices/oneInch.test.d.ts.map +1 -1
  120. package/ts_build/test/integration/externalservices/oneInch.test.js +26 -1
  121. package/ts_build/test/integration/externalservices/oneInch.test.js.map +1 -1
  122. package/ts_build/test/integration/externalservices/ramp.test.d.ts.map +1 -1
  123. package/ts_build/test/integration/externalservices/ramp.test.js +115 -4
  124. package/ts_build/test/integration/externalservices/ramp.test.js.map +1 -1
  125. package/ts_build/test/integration/externalservices/sardine.test.d.ts.map +1 -1
  126. package/ts_build/test/integration/externalservices/sardine.test.js +94 -5
  127. package/ts_build/test/integration/externalservices/sardine.test.js.map +1 -1
  128. package/ts_build/test/integration/externalservices/simplex.test.d.ts.map +1 -1
  129. package/ts_build/test/integration/externalservices/simplex.test.js +89 -5
  130. package/ts_build/test/integration/externalservices/simplex.test.js.map +1 -1
  131. package/ts_build/test/integration/externalservices/thorswap.test.d.ts.map +1 -1
  132. package/ts_build/test/integration/externalservices/thorswap.test.js +76 -5
  133. package/ts_build/test/integration/externalservices/thorswap.test.js.map +1 -1
  134. package/ts_build/test/integration/externalservices/transak.test.d.ts.map +1 -1
  135. package/ts_build/test/integration/externalservices/transak.test.js +93 -6
  136. package/ts_build/test/integration/externalservices/transak.test.js.map +1 -1
  137. package/ts_build/test/integration/helpers.js +1 -1
  138. package/ts_build/test/integration/helpers.js.map +1 -1
  139. package/ts_build/test/integration/server.test.js +44 -76
  140. package/ts_build/test/integration/server.test.js.map +1 -1
  141. package/ts_build/test/integration/tss.test.d.ts.map +1 -1
  142. package/ts_build/test/integration/tss.test.js +599 -357
  143. package/ts_build/test/integration/tss.test.js.map +1 -1
  144. package/ts_build/test/storage.test.js +6 -7
  145. package/ts_build/test/storage.test.js.map +1 -1
  146. package/src/externalservices/wyre.ts +0 -131
  147. package/test/integration/externalservices/wyre.test.ts +0 -270
  148. package/ts_build/src/externalservices/wyre.d.ts +0 -7
  149. package/ts_build/src/externalservices/wyre.d.ts.map +0 -1
  150. package/ts_build/src/externalservices/wyre.js +0 -144
  151. package/ts_build/src/externalservices/wyre.js.map +0 -1
  152. package/ts_build/test/integration/externalservices/wyre.test.d.ts +0 -2
  153. package/ts_build/test/integration/externalservices/wyre.test.d.ts.map +0 -1
  154. package/ts_build/test/integration/externalservices/wyre.test.js +0 -281
  155. package/ts_build/test/integration/externalservices/wyre.test.js.map +0 -1
@@ -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
  };
@@ -459,18 +459,6 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
459
459
  });
460
460
  });
461
461
 
462
- router.post('/v1/service/wyre/walletOrderQuotation', (req, res) => {
463
- respondWithAuthServer(req, res, context, server => {
464
- return server.externalServices.wyre.wyreWalletOrderQuotation(req);
465
- });
466
- });
467
-
468
- router.post('/v1/service/wyre/walletOrderReservation', (req, res) => {
469
- respondWithAuthServer(req, res, context, server => {
470
- return server.externalServices.wyre.wyreWalletOrderReservation(req);
471
- });
472
- });
473
-
474
462
  router.post('/v1/service/changelly/getCurrencies', (req, res) => {
475
463
  respondWithPublicServer(req, res, context, server => {
476
464
  return server.externalServices.changelly.changellyGetCurrencies(req);
@@ -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
@@ -27,7 +27,6 @@ import { SardineService } from '../externalservices/sardine';
27
27
  import { SimplexService } from '../externalservices/simplex';
28
28
  import { ThorswapService } from '../externalservices/thorswap';
29
29
  import { TransakService } from '../externalservices/transak';
30
- import { WyreService } from '../externalservices/wyre';
31
30
  import { serverMessages } from '../serverMessages';
32
31
  import { BCHAddressTranslator } from './bchaddresstranslator';
33
32
  import { BlockChainExplorer } from './blockchainexplorer';
@@ -160,7 +159,6 @@ export class WalletService implements IWalletService {
160
159
  simplex: SimplexService;
161
160
  thorswap: ThorswapService;
162
161
  transak: TransakService;
163
- wyre: WyreService;
164
162
  coinGecko: CoinGeckoService;
165
163
  };
166
164
 
@@ -190,7 +188,6 @@ export class WalletService implements IWalletService {
190
188
  simplex: new SimplexService(),
191
189
  thorswap: new ThorswapService(),
192
190
  transak: new TransakService(),
193
- wyre: new WyreService(),
194
191
  coinGecko: new CoinGeckoService(this.storage),
195
192
  };
196
193
  }
@@ -394,10 +391,7 @@ export class WalletService implements IWalletService {
394
391
  return cb(ex);
395
392
  }
396
393
 
397
- server.storage.fetchCopayerLookup(opts.copayerId, (err, copayer) => {
398
- if (err) {
399
- return cb(err);
400
- }
394
+ server.storage.fetchCopayerLookup(opts.copayerId).then(copayer => {
401
395
  if (!copayer) {
402
396
  return cb(Errors.NOT_AUTHORIZED.withMessage('Copayer not found'));
403
397
  }
@@ -420,7 +414,7 @@ export class WalletService implements IWalletService {
420
414
 
421
415
  server.copayerId = opts.copayerId;
422
416
  return cb(null, server);
423
- });
417
+ }).catch(err => cb(err));
424
418
  };
425
419
 
426
420
  const withSession = cb => {
@@ -428,7 +422,7 @@ export class WalletService implements IWalletService {
428
422
  return;
429
423
  }
430
424
 
431
- let server;
425
+ let server: WalletService;
432
426
  try {
433
427
  server = WalletService.getInstance(opts);
434
428
  } catch (ex) {
@@ -445,10 +439,7 @@ export class WalletService implements IWalletService {
445
439
  return cb(Errors.NOT_AUTHORIZED.withMessage('Session expired'));
446
440
  }
447
441
 
448
- server.storage.fetchCopayerLookup(opts.copayerId, (err, copayer) => {
449
- if (err) {
450
- return cb(err);
451
- }
442
+ server.storage.fetchCopayerLookup(opts.copayerId).then(copayer => {
452
443
  if (!copayer) {
453
444
  return cb(Errors.NOT_AUTHORIZED.withMessage('Copayer not found'));
454
445
  }
@@ -456,7 +447,7 @@ export class WalletService implements IWalletService {
456
447
  server.copayerId = opts.copayerId;
457
448
  server.walletId = copayer.walletId;
458
449
  return cb(null, server);
459
- });
450
+ }).catch(err => cb(err));
460
451
  });
461
452
  };
462
453
 
@@ -1060,8 +1051,7 @@ export class WalletService implements IWalletService {
1060
1051
  derivationStrategy: wallet.derivationStrategy
1061
1052
  });
1062
1053
 
1063
- this.storage.fetchCopayerLookup(copayer.id, (err, res) => {
1064
- if (err) return cb(err);
1054
+ this.storage.fetchCopayerLookup(copayer.id).then(res => {
1065
1055
  if (res) return cb(Errors.COPAYER_REGISTERED);
1066
1056
 
1067
1057
  if (opts.dryRun)
@@ -1113,7 +1103,7 @@ export class WalletService implements IWalletService {
1113
1103
  }
1114
1104
  );
1115
1105
  });
1116
- });
1106
+ }).catch(err => cb(err));
1117
1107
  }
1118
1108
 
1119
1109
  _addKeyToCopayer(wallet: Wallet, copayer: Copayer, opts, cb) {
@@ -1143,8 +1133,7 @@ export class WalletService implements IWalletService {
1143
1133
  addAccess(opts, cb) {
1144
1134
  if (!checkRequired(opts, ['copayerId', 'requestPubKey', 'signature'], cb)) return;
1145
1135
 
1146
- this.storage.fetchCopayerLookup(opts.copayerId, (err, copayer) => {
1147
- if (err) return cb(err);
1136
+ this.storage.fetchCopayerLookup(opts.copayerId).then(copayer => {
1148
1137
  if (!copayer) return cb(Errors.NOT_AUTHORIZED);
1149
1138
  this.storage.fetchWallet(copayer.walletId, (err, wallet) => {
1150
1139
  if (err) return cb(err);
@@ -1164,7 +1153,7 @@ export class WalletService implements IWalletService {
1164
1153
 
1165
1154
  this._addKeyToCopayer(wallet, copayer, opts, cb);
1166
1155
  });
1167
- });
1156
+ }).catch(err => cb(err));
1168
1157
  }
1169
1158
 
1170
1159
  _setClientVersion(version) {
@@ -288,27 +288,19 @@ export class Storage {
288
288
  );
289
289
  }
290
290
 
291
- fetchCopayerLookup(copayerId: string, cb: (err?: any, copayer?: Copayer) => void) {
292
- this.db.collection(collections.COPAYERS_LOOKUP).findOne(
293
- {
294
- copayerId
295
- },
296
- (err, result) => {
297
- if (err) return cb(err);
298
- if (!result) return cb();
299
-
300
- if (!result.requestPubKeys) {
301
- result.requestPubKeys = [
302
- {
303
- key: result.requestPubKey,
304
- signature: result.signature
305
- }
306
- ];
291
+ async fetchCopayerLookup(copayerId: string): Promise<Copayer | undefined> {
292
+ const result = await this.db.collection(collections.COPAYERS_LOOKUP).findOne({ copayerId });
293
+ if (!result) return;
294
+ if (!result.requestPubKeys) {
295
+ result.requestPubKeys = [
296
+ {
297
+ key: result.requestPubKey,
298
+ signature: result.signature
307
299
  }
300
+ ];
301
+ }
308
302
 
309
- return cb(null, Copayer.fromObj(result));
310
- }
311
- );
303
+ return Copayer.fromObj(result);
312
304
  }
313
305
 
314
306
  // TODO: should be done client-side
@@ -1806,16 +1798,48 @@ export class Storage {
1806
1798
  return this.db.collection(collections.TSS_KEYGEN).insertOne(doc);
1807
1799
  }
1808
1800
 
1809
- async storeTssKeyGenParticipant({ id, partyId, copayerId }: { id: string; partyId: number; copayerId: string }) {
1810
- return this.db.collection(collections.TSS_KEYGEN).updateOne(
1811
- { id },
1801
+ async storeTssKeyGenAuthPublicKey({ id, partyId, authPublicKey, __v }: { id: string; partyId: number; authPublicKey: string; __v: number }) {
1802
+ const result = await this.db.collection(collections.TSS_KEYGEN).updateOne(
1803
+ { id, __v },
1804
+ {
1805
+ $set: {
1806
+ [`authPublicKeys.${partyId}`]: authPublicKey
1807
+ },
1808
+ $inc: {
1809
+ __v: 1
1810
+ }
1811
+ },
1812
+ { upsert: false }
1813
+ );
1814
+ if (!result.matchedCount) {
1815
+ if ((await this.db.collection(collections.TSS_KEYGEN).countDocuments({ id })) === 0) {
1816
+ throw new Error('MONGO_DOC_NOT_FOUND: No document found for id ' + id);
1817
+ }
1818
+ throw new Error('MONGO_DOC_OUTDATED: No document found for version ' + __v);
1819
+ }
1820
+ return result;
1821
+ }
1822
+
1823
+ async storeTssKeyGenParticipant({ id, partyId, copayerId, __v }: { id: string; partyId: number; copayerId: string; __v: number }) {
1824
+ const result = await this.db.collection(collections.TSS_KEYGEN).updateOne(
1825
+ { id, __v },
1812
1826
  {
1813
1827
  $set: {
1814
1828
  [`participants.${partyId}`]: copayerId
1829
+ },
1830
+ $inc: {
1831
+ __v: 1
1815
1832
  }
1816
1833
  },
1817
1834
  { upsert: false }
1818
1835
  );
1836
+ if (!result.matchedCount) {
1837
+ if ((await this.db.collection(collections.TSS_KEYGEN).countDocuments({ id })) === 0) {
1838
+ throw new Error('MONGO_DOC_NOT_FOUND: No document found for id ' + id);
1839
+ }
1840
+ throw new Error('MONGO_DOC_OUTDATED: No document found for version ' + __v);
1841
+ }
1842
+ return result;
1819
1843
  }
1820
1844
 
1821
1845
  async storeTssKeyGenMessage({ id, message, __v }: { id: string; message: ITssKeyMessageObject; __v: number }) {
@@ -1835,6 +1859,9 @@ export class Storage {
1835
1859
  { upsert: false }
1836
1860
  );
1837
1861
  if (!result.matchedCount) {
1862
+ if ((await this.db.collection(collections.TSS_KEYGEN).countDocuments({ id })) === 0) {
1863
+ throw new Error('MONGO_DOC_NOT_FOUND: No document found for id ' + id);
1864
+ }
1838
1865
  throw new Error('MONGO_DOC_OUTDATED: No document found for version ' + __v);
1839
1866
  }
1840
1867
  return result;
@@ -1902,6 +1929,9 @@ export class Storage {
1902
1929
  { upsert: false }
1903
1930
  );
1904
1931
  if (!result.matchedCount) {
1932
+ if ((await this.db.collection(collections.TSS_SIGN).countDocuments({ id })) === 0) {
1933
+ throw new Error('MONGO_DOC_NOT_FOUND: No document found for id ' + id);
1934
+ }
1905
1935
  throw new Error('MONGO_DOC_OUTDATED: No document found for version ' + __v);
1906
1936
  }
1907
1937
  return result;
@@ -1924,6 +1954,9 @@ export class Storage {
1924
1954
  { upsert: false }
1925
1955
  );
1926
1956
  if (!result.matchedCount) {
1957
+ if ((await this.db.collection(collections.TSS_SIGN).countDocuments({ id })) === 0) {
1958
+ throw new Error('MONGO_DOC_NOT_FOUND: No document found for id ' + id);
1959
+ }
1927
1960
  throw new Error('MONGO_DOC_OUTDATED: No document found for version ' + __v);
1928
1961
  }
1929
1962
  return result;
package/src/lib/tss.ts CHANGED
@@ -155,6 +155,57 @@ class TssKeyGenClass {
155
155
  return session;
156
156
  }
157
157
 
158
+ async registerParty(params: {
159
+ /** Session ID */
160
+ id: string;
161
+ /** Copayer ID of the requesting party */
162
+ copayerId: string;
163
+ /* Copayer data to register */
164
+ data: {
165
+ /** Party ID */
166
+ partyId: string | number;
167
+ /** Public key/Copayer ID of the party */
168
+ partyPubKey: string;
169
+ };
170
+ }) {
171
+ const { id, copayerId, data } = params;
172
+
173
+ const session = await this.getSessionForCopayer({ id, copayerId });
174
+ const reqPartyId = session.participants.indexOf(copayerId);
175
+ if (reqPartyId !== 0) {
176
+ throw Errors.TSS_NON_PARTICIPANT.withMessage('Only the first participant can register a party');
177
+ }
178
+
179
+ if (data.partyId == null || isNaN(data.partyId as number)) {
180
+ throw Errors.TSS_INVALID_PARTY_DATA.withMessage(`Invalid partyId: ${data.partyId}`);
181
+ }
182
+
183
+ const partyId = parseInt(data.partyId as string);
184
+ const pubKey = data.partyPubKey?.trim();
185
+
186
+ if (!(partyId > 0) || !pubKey || pubKey === copayerId || partyId >= session.n) {
187
+ throw Errors.TSS_INVALID_PARTY_DATA.withMessage(`Invalid data for party ${partyId} registration`);
188
+ }
189
+
190
+ if (session.authPublicKeys.includes(pubKey)) {
191
+ if (session.authPublicKeys.indexOf(pubKey) === partyId) {
192
+ // Already registered at partyId -> return idempotently
193
+ return;
194
+ } else {
195
+ // Trying to re-register a party that is already registered under a different party ID
196
+ throw Errors.TSS_PARTY_ALREADY_REGISTERED;
197
+ }
198
+ }
199
+
200
+ // Has party already submitted initial rounds message? (case when this is a re-register)
201
+ if (session.rounds?.[0]?.some(round => round.fromPartyId === partyId)) {
202
+ throw Errors.TSS_ROUNDS_ALREADY_STARTED.withMessage(`Party ${partyId} has already submitted an initial message. Cannot register a new party ${partyId}`);
203
+ }
204
+
205
+ const storage = WalletService.getStorage();
206
+ await storage.storeTssKeyGenAuthPublicKey({ id, partyId, authPublicKey: pubKey, __v: session.__v });
207
+ }
208
+
158
209
  /**
159
210
  * Get messages for a given party in a TSS keygen session.
160
211
  * Only returns messages if all other parties have sent their messages for the round.
@@ -279,10 +330,29 @@ class TssKeyGenClass {
279
330
 
280
331
 
281
332
  if (!session.participants[message.partyId]) {
333
+ if (session.participants.includes(copayerId)) {
334
+ throw Errors.TSS_PARTY_ALREADY_REGISTERED;
335
+ }
282
336
  if (!this._checkPassword({ session, password })) {
283
337
  throw Errors.TSS_INVALID_PASSWORD;
284
338
  }
285
- await storage.storeTssKeyGenParticipant({ id, partyId: message.partyId, copayerId });
339
+ // Sanity check for the public key of the participant in the session
340
+ // session.authPublicKeys was not added until version 1.2
341
+ if (session.schemeVersion >= 1.2 && session.authPublicKeys[message.partyId] !== message.publicKey) {
342
+ throw Errors.NOT_AUTHORIZED;
343
+ }
344
+ try {
345
+ await storage.storeTssKeyGenParticipant({ id, partyId: message.partyId, copayerId, __v: session.__v });
346
+ } catch (e) {
347
+ if (e?.message?.startsWith('MONGO_DOC_OUTDATED')) {
348
+ return this.processMessage(params);
349
+ } else if (e?.message?.startsWith('MONGO_DOC_NOT_FOUND')) {
350
+ throw Errors.TSS_SESSION_NOT_FOUND;
351
+ }
352
+ throw e;
353
+ }
354
+ } else if (session.participants[message.partyId] !== copayerId) {
355
+ throw Errors.TSS_NON_PARTICIPANT.withMessage('Incorrect copayerId for this partyId');
286
356
  }
287
357
 
288
358
  let result = false;
@@ -427,6 +497,8 @@ class TssKeyGenClass {
427
497
  } catch (e) {
428
498
  if (e?.message?.startsWith('MONGO_DOC_OUTDATED')) {
429
499
  return false;
500
+ } else if (e?.message?.startsWith('MONGO_DOC_NOT_FOUND')) {
501
+ throw Errors.TSS_SESSION_NOT_FOUND;
430
502
  }
431
503
  throw e;
432
504
  }
@@ -694,6 +766,8 @@ class TssSignClass {
694
766
  } catch (e) {
695
767
  if (e?.message?.startsWith('MONGO_DOC_OUTDATED')) {
696
768
  return this.processMessage(params);
769
+ } else if (e?.message?.startsWith('MONGO_DOC_NOT_FOUND')) {
770
+ throw Errors.TSS_SESSION_NOT_FOUND;
697
771
  }
698
772
  throw e;
699
773
  }
@@ -814,6 +888,8 @@ class TssSignClass {
814
888
  } catch (e) {
815
889
  if (e?.message?.startsWith('MONGO_DOC_OUTDATED')) {
816
890
  return false;
891
+ } else if (e?.message?.startsWith('MONGO_DOC_NOT_FOUND')) {
892
+ throw Errors.TSS_SESSION_NOT_FOUND;
817
893
  }
818
894
  throw e;
819
895
  }
@@ -20,7 +20,6 @@ export interface BuyCryptoConfig {
20
20
  sardine?: ExchangeConfig;
21
21
  simplex?: ExchangeConfig;
22
22
  transak?: ExchangeConfig;
23
- wyre?: ExchangeConfig;
24
23
  }
25
24
 
26
25
  export interface SellCryptoConfig {