@trezor/connect 9.6.2 → 9.6.3-beta.2

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 (139) hide show
  1. package/CHANGELOG.md +23 -9
  2. package/README.md +1 -1
  3. package/lib/api/applySettings.js +16 -1
  4. package/lib/api/bitcoin/signtx.d.ts +2 -2
  5. package/lib/api/bitcoin/signtx.js +1 -1
  6. package/lib/api/bleUnpair.js +0 -1
  7. package/lib/api/cardano/api/cardanoGetAddress.d.ts +1 -0
  8. package/lib/api/cardano/api/cardanoSignMessage.d.ts +25 -0
  9. package/lib/api/cardano/api/cardanoSignMessage.js +97 -0
  10. package/lib/api/cardano/api/index.d.ts +1 -0
  11. package/lib/api/cardano/api/index.js +3 -1
  12. package/lib/api/cardano/cardanoUtils.d.ts +1 -1
  13. package/lib/api/cardano/cardanoUtils.js +2 -2
  14. package/lib/api/discoverAccounts.js +61 -33
  15. package/lib/api/ethereum/api/ethereumGetAddress.js +1 -0
  16. package/lib/api/ethereum/api/ethereumSignTransaction.js +22 -4
  17. package/lib/api/ethereum/ethereumSignTx.d.ts +3 -2
  18. package/lib/api/ethereum/ethereumSignTx.js +4 -2
  19. package/lib/api/evoluGetNode.d.ts +11 -0
  20. package/lib/api/evoluGetNode.js +26 -0
  21. package/lib/api/firmware/calculateFirmwareHash.d.ts +2 -1
  22. package/lib/api/firmware/calculateFirmwareHash.js +17 -4
  23. package/lib/api/firmware/getBinary.d.ts +7 -6
  24. package/lib/api/firmware/getBinary.js +18 -6
  25. package/lib/api/firmware/index.d.ts +0 -2
  26. package/lib/api/firmware/index.js +1 -5
  27. package/lib/api/firmware/uploadFirmware.d.ts +10 -1
  28. package/lib/api/firmware/uploadFirmware.js +8 -7
  29. package/lib/api/getFeatures.d.ts +2 -1
  30. package/lib/api/index.d.ts +3 -0
  31. package/lib/api/index.js +8 -2
  32. package/lib/api/nem/nemSignTx.d.ts +1 -1
  33. package/lib/api/pushTransaction.d.ts +2 -1
  34. package/lib/api/pushTransaction.js +2 -1
  35. package/lib/api/resetDevice.js +2 -1
  36. package/lib/api/ripple/api/rippleGetAddress.d.ts +1 -0
  37. package/lib/api/signMessage.js +1 -1
  38. package/lib/api/signTransaction.d.ts +1 -1
  39. package/lib/api/solana/api/solanaGetAddress.d.ts +1 -0
  40. package/lib/api/solana/api/solanaGetPublicKey.d.ts +2 -0
  41. package/lib/api/solana/api/solanaGetPublicKey.js +3 -0
  42. package/lib/api/stellar/api/stellarGetAddress.d.ts +1 -0
  43. package/lib/api/tezos/api/tezosGetAddress.d.ts +1 -0
  44. package/lib/api/thpGetCredentials.d.ts +10 -0
  45. package/lib/api/thpGetCredentials.js +31 -0
  46. package/lib/api/thpRemoveCredentials.d.ts +8 -0
  47. package/lib/api/thpRemoveCredentials.js +32 -0
  48. package/lib/api/wipeDevice.js +6 -2
  49. package/lib/backend/Blockchain.d.ts +4 -4
  50. package/lib/backend/Blockchain.js +12 -11
  51. package/lib/constants/cardano.d.ts +3 -0
  52. package/lib/constants/cardano.js +5 -1
  53. package/lib/constants/errors.d.ts +12 -9
  54. package/lib/constants/errors.js +18 -24
  55. package/lib/core/AbstractMethod.d.ts +5 -24
  56. package/lib/core/AbstractMethod.js +2 -2
  57. package/lib/core/index.js +5 -1
  58. package/lib/core/onCallFirmwareUpdate.d.ts +13 -1
  59. package/lib/core/onCallFirmwareUpdate.js +158 -94
  60. package/lib/data/DataManager.d.ts +13 -1
  61. package/lib/data/DataManager.js +44 -14
  62. package/lib/data/coinInfo.d.ts +8 -84
  63. package/lib/data/coinInfo.js +6 -9
  64. package/lib/data/config.d.ts +26 -192
  65. package/lib/data/config.js +37 -1
  66. package/lib/data/connectSettings.js +7 -0
  67. package/lib/data/defaultFeeLevels.js +2 -1
  68. package/lib/data/deviceAuthenticityConfig.js +10 -1
  69. package/lib/data/firmwareInfo.d.ts +44 -11
  70. package/lib/data/firmwareInfo.js +360 -122
  71. package/lib/data/thpSettings.js +5 -2
  72. package/lib/data/version.d.ts +2 -1
  73. package/lib/data/version.js +4 -3
  74. package/lib/device/Device.d.ts +17 -7
  75. package/lib/device/Device.js +72 -115
  76. package/lib/device/DeviceCommands.d.ts +1 -0
  77. package/lib/device/DeviceCurrentSession.d.ts +41 -20
  78. package/lib/device/DeviceCurrentSession.js +18 -22
  79. package/lib/device/TransportList.js +10 -5
  80. package/lib/device/checkFirmwareRevision.d.ts +3 -2
  81. package/lib/device/checkFirmwareRevision.js +6 -11
  82. package/lib/device/thp/handshake.d.ts +4 -0
  83. package/lib/device/thp/handshake.js +88 -0
  84. package/lib/device/thp/index.d.ts +4 -1
  85. package/lib/device/thp/index.js +24 -6
  86. package/lib/device/thp/pairing.d.ts +16 -0
  87. package/lib/device/thp/pairing.js +230 -0
  88. package/lib/device/thp/session.d.ts +3 -0
  89. package/lib/device/thp/session.js +27 -0
  90. package/lib/device/thp/thpCall.d.ts +41 -0
  91. package/lib/device/thp/thpCall.js +54 -0
  92. package/lib/device/workflow/checkFirmwareHash.d.ts +10 -0
  93. package/lib/device/workflow/checkFirmwareHash.js +79 -0
  94. package/lib/device/workflow/checkFirmwareHashWithRetries.d.ts +9 -0
  95. package/lib/device/workflow/checkFirmwareHashWithRetries.js +41 -0
  96. package/lib/device/workflow/handshake.js +7 -6
  97. package/lib/device/workflow/validateState.js +53 -10
  98. package/lib/events/ui-request.d.ts +1 -1
  99. package/lib/factory.js +4 -0
  100. package/lib/impl/core-in-module.js +3 -0
  101. package/lib/tsconfig.lib.tsbuildinfo +1 -1
  102. package/lib/types/api/bitcoin/index.d.ts +1 -1
  103. package/lib/types/api/cardano/index.d.ts +62 -1
  104. package/lib/types/api/cardano/index.js +30 -1
  105. package/lib/types/api/cardanoSignMessage.d.ts +4 -0
  106. package/lib/types/api/cardanoSignMessage.js +3 -0
  107. package/lib/types/api/discoverAccounts.d.ts +17 -12
  108. package/lib/types/api/ethereum/index.d.ts +108 -0
  109. package/lib/types/api/ethereum/index.js +3 -0
  110. package/lib/types/api/evoluGetNode.d.ts +4 -0
  111. package/lib/types/api/evoluGetNode.js +3 -0
  112. package/lib/types/api/index.d.ts +8 -0
  113. package/lib/types/api/pushTransaction.d.ts +4 -1
  114. package/lib/types/api/pushTransaction.js +7 -1
  115. package/lib/types/api/solana/index.d.ts +3 -0
  116. package/lib/types/api/solana/index.js +2 -0
  117. package/lib/types/api/thpGetCredentials.d.ts +4 -0
  118. package/lib/types/api/thpGetCredentials.js +3 -0
  119. package/lib/types/api/thpRemoveCredentials.d.ts +7 -0
  120. package/lib/types/api/thpRemoveCredentials.js +3 -0
  121. package/lib/types/device.d.ts +10 -10
  122. package/lib/types/firmware.d.ts +15 -12
  123. package/lib/types/firmware.js +0 -3
  124. package/lib/types/params.d.ts +1 -0
  125. package/lib/types/settings.d.ts +7 -0
  126. package/lib/utils/assetUtils.d.ts +5 -2
  127. package/lib/utils/assetUtils.js +14 -18
  128. package/lib/utils/assets.native.js +0 -2
  129. package/lib/utils/firmwareReleaseConfigUtils.d.ts +8 -5
  130. package/lib/utils/firmwareReleaseConfigUtils.js +53 -57
  131. package/lib/utils/firmwareUtils.d.ts +8 -4
  132. package/lib/utils/firmwareUtils.js +62 -20
  133. package/lib/utils/formatUtils.d.ts +1 -0
  134. package/lib/utils/formatUtils.js +3 -2
  135. package/package.json +16 -15
  136. package/lib/api/firmware/getBinaryForFirmwareUpgrade.d.ts +0 -12
  137. package/lib/api/firmware/getBinaryForFirmwareUpgrade.js +0 -32
  138. package/lib/data/getLanguage.d.ts +0 -6
  139. package/lib/data/getLanguage.js +0 -10
@@ -7,7 +7,6 @@ const transport_1 = require("@trezor/transport");
7
7
  const errors_groups_1 = require("@trezor/transport/lib/errors-groups");
8
8
  const utils_1 = require("@trezor/utils");
9
9
  const constants_1 = require("../constants");
10
- const DataManager_1 = require("../data/DataManager");
11
10
  const events_1 = require("../events");
12
11
  const debug_1 = require("../utils/debug");
13
12
  const blacklist = {
@@ -28,6 +27,7 @@ const allowedCallsBeforeInitialize = [
28
27
  'RebootToBootloader',
29
28
  'FirmwareErase',
30
29
  'FirmwareUpload',
30
+ 'RecoveryDevice',
31
31
  ];
32
32
  const filterForLog = (type, msg) => blacklist[type] === true
33
33
  ? '(redacted...)'
@@ -36,8 +36,8 @@ const logger = (0, debug_1.initLog)('DeviceCommands');
36
36
  const isExpectedResponse = (response, expected) => (Array.isArray(expected) ? expected : expected.split('|')).includes(response.type);
37
37
  const success = (payload) => ({ success: true, payload });
38
38
  const error = (error) => ({ success: false, error });
39
- const fail = (msg) => error(new Error(msg, (0, errors_groups_1.isErrorWithoutDeviceInteraction)(msg) ? { cause: 'transport-error' } : undefined));
40
- const getFeaturesTimeout = () => DataManager_1.DataManager.getSettings('env') === 'react-native' ? 20_000 : 3_000;
39
+ const nestedError = (cause) => error(constants_1.ERRORS.nestError(cause));
40
+ const fail = (msg) => error((0, errors_groups_1.isErrorWithoutDeviceInteraction)(msg) ? new constants_1.ERRORS.TransportError(msg) : new Error(msg));
41
41
  class DeviceCurrentSession {
42
42
  device;
43
43
  transport;
@@ -62,13 +62,14 @@ class DeviceCurrentSession {
62
62
  return !!this.disposed;
63
63
  }
64
64
  async typedCall(type, expectedType, msg = {}) {
65
- if (!allowedCallsBeforeInitialize.includes(type) && !this.device?.features?.session_id) {
65
+ const deviceSessionId = this.device.getThpState()?.sessionId || this.device?.features?.session_id;
66
+ if (!allowedCallsBeforeInitialize.includes(type) && !deviceSessionId) {
66
67
  console.error('Runtime', `typedCall: Device not initialized when calling ${type}. call Initialize first`);
67
68
  }
68
69
  (0, schema_utils_1.Assert)(protobuf_1.MessagesSchema.MessageType.properties[type], msg);
69
70
  this.abortController = new AbortController();
70
71
  const { signal } = this.abortController;
71
- const abortPromise = new Promise(resolve => signal.addEventListener('abort', () => resolve(error(signal.reason))));
72
+ const abortPromise = new Promise(resolve => signal.addEventListener('abort', () => resolve(signal.reason)));
72
73
  const callPromise = this.callLoop(type, msg, abortPromise);
73
74
  this.callPromise = callPromise;
74
75
  const response = await callPromise;
@@ -99,11 +100,11 @@ class DeviceCurrentSession {
99
100
  let [name, data] = [type, msg];
100
101
  let pinUnlocked = false;
101
102
  while (true) {
102
- const timeout = name === 'GetFeatures' ? getFeaturesTimeout() : undefined;
103
+ const timeout = name === 'GetFeatures' ? 3_000 : undefined;
103
104
  const callPromise = this.call(name, data, { timeout });
104
105
  const [abortedDuringCall, response] = await Promise.race([
105
106
  callPromise.then(res => [false, res]),
106
- abortPromise.then(res => [true, res]),
107
+ abortPromise.then(res => [true, nestedError(res)]),
107
108
  ]);
108
109
  if (name === 'ButtonAck' && abortedDuringCall && !this.disposed) {
109
110
  if (this.needCancelWorkaround()) {
@@ -117,18 +118,13 @@ class DeviceCurrentSession {
117
118
  }
118
119
  }
119
120
  else {
120
- await this.transport.send({
121
- name: 'Cancel',
122
- data: {},
123
- session: this.session,
124
- protocol: this.device.protocol,
125
- thpState: this.device.getThpState(),
126
- });
121
+ this.device.getThpState()?.sync('send', 'Cancel');
122
+ await this.send('Cancel', {});
127
123
  }
128
124
  }
129
125
  await callPromise;
130
126
  if (this.disposed)
131
- return error(this.disposed);
127
+ return nestedError(this.disposed);
132
128
  if (!response.success)
133
129
  return response;
134
130
  const res = response.payload;
@@ -161,7 +157,7 @@ class DeviceCurrentSession {
161
157
  case 'PinMatrixRequest': {
162
158
  const promptRes = await Promise.race([
163
159
  this.device.prompt(events_1.DEVICE.PIN, { type: res.message.type }),
164
- abortPromise,
160
+ abortPromise.then(nestedError),
165
161
  ]);
166
162
  if (!promptRes.success) {
167
163
  const cancelRes = await this.call('Cancel', {});
@@ -174,7 +170,7 @@ class DeviceCurrentSession {
174
170
  case 'PassphraseRequest': {
175
171
  const promptRes = await Promise.race([
176
172
  this.device.prompt(events_1.DEVICE.PASSPHRASE, {}),
177
- abortPromise,
173
+ abortPromise.then(nestedError),
178
174
  ]);
179
175
  if (!promptRes.success) {
180
176
  const cancelRes = await this.call('Cancel', {});
@@ -189,7 +185,7 @@ class DeviceCurrentSession {
189
185
  case 'WordRequest': {
190
186
  const promptRes = await Promise.race([
191
187
  this.device.prompt(events_1.DEVICE.WORD, { type: res.message.type }),
192
- abortPromise,
188
+ abortPromise.then(nestedError),
193
189
  ]);
194
190
  if (!promptRes.success) {
195
191
  const cancelRes = await this.call('Cancel', {});
@@ -200,7 +196,7 @@ class DeviceCurrentSession {
200
196
  }
201
197
  default: {
202
198
  if (!this.disposed && pinUnlocked && !this.device.features.unlocked) {
203
- await this.device.getFeatures();
199
+ await this.device.getFeatures().catch(() => { });
204
200
  }
205
201
  return success(res);
206
202
  }
@@ -209,7 +205,7 @@ class DeviceCurrentSession {
209
205
  }
210
206
  async call(name, data, options = {}) {
211
207
  if (this.disposed)
212
- return Promise.resolve(error(this.disposed));
208
+ return Promise.resolve(nestedError(this.disposed));
213
209
  logger.debug('Sending', name, filterForLog(name, data));
214
210
  const result = await this.transport.call({
215
211
  name,
@@ -230,7 +226,7 @@ class DeviceCurrentSession {
230
226
  }
231
227
  async send(name, data, options = {}) {
232
228
  if (this.disposed)
233
- return Promise.resolve(error(this.disposed));
229
+ return Promise.resolve(nestedError(this.disposed));
234
230
  const result = await this.transport.send({
235
231
  name,
236
232
  data,
@@ -243,7 +239,7 @@ class DeviceCurrentSession {
243
239
  }
244
240
  async receive(options = {}) {
245
241
  if (this.disposed)
246
- return Promise.resolve(error(this.disposed));
242
+ return Promise.resolve(nestedError(this.disposed));
247
243
  const result = await this.transport.receive({
248
244
  session: this.session,
249
245
  protocol: this.device.protocol,
@@ -5,6 +5,12 @@ const transport_1 = require("@trezor/transport");
5
5
  const constants_1 = require("../constants");
6
6
  const tryGetTransport = (transports, name) => transports.find(t => t.name === name);
7
7
  const getOrCreateTransport = (transports, transportType, params) => {
8
+ if (transportType === 'BridgeTransport') {
9
+ const existing = transports.filter(t => t.name === transportType);
10
+ return existing.length
11
+ ? existing
12
+ : [new transport_1.BridgeTransport({ ...params, port: 21328 }), new transport_1.BridgeTransport(params)];
13
+ }
8
14
  if (typeof transportType === 'string') {
9
15
  const existing = tryGetTransport(transports, transportType);
10
16
  if (existing)
@@ -14,8 +20,6 @@ const getOrCreateTransport = (transports, transportType, params) => {
14
20
  return new transport_1.WebUsbTransport(params);
15
21
  case 'NodeUsbTransport':
16
22
  return new transport_1.NodeUsbTransport(params);
17
- case 'BridgeTransport':
18
- return new transport_1.BridgeTransport(params);
19
23
  case 'UdpTransport':
20
24
  return new transport_1.UdpTransport(params);
21
25
  }
@@ -27,8 +31,9 @@ const getOrCreateTransport = (transports, transportType, params) => {
27
31
  }
28
32
  }
29
33
  else if ((0, transport_1.isTransportInstance)(transportType)) {
30
- if (tryGetTransport(transports, transportType.name)) {
31
- return transportType;
34
+ const existing = tryGetTransport(transports, transportType.name);
35
+ if (existing) {
36
+ return existing;
32
37
  }
33
38
  if (!transportType.getMessage()) {
34
39
  transportType.updateMessages(params.messages);
@@ -39,7 +44,7 @@ const getOrCreateTransport = (transports, transportType, params) => {
39
44
  };
40
45
  const createTransports = (existing, transports = [], params) => {
41
46
  const transportTypes = transports?.length ? transports : ['BridgeTransport'];
42
- return transportTypes.map(type => getOrCreateTransport(existing, type, params));
47
+ return transportTypes.flatMap(type => getOrCreateTransport(existing, type, params));
43
48
  };
44
49
  const createTransportList = (params) => (existing, transports) => createTransports(existing, transports, params);
45
50
  exports.createTransportList = createTransportList;
@@ -1,4 +1,4 @@
1
- import type { VersionArray } from '@trezor/device-utils';
1
+ import type { FirmwareType, VersionArray } from '@trezor/device-utils';
2
2
  import { PROTO } from '../constants';
3
3
  import { FirmwareRevisionCheckResult } from '../types/device';
4
4
  export type CheckFirmwareRevisionParams = {
@@ -6,6 +6,7 @@ export type CheckFirmwareRevisionParams = {
6
6
  internalModel: PROTO.DeviceModelInternal;
7
7
  deviceRevision: string | null;
8
8
  expectedRevision: string | undefined;
9
+ firmwareType: FirmwareType;
9
10
  };
10
- export declare const checkFirmwareRevision: ({ firmwareVersion, internalModel, deviceRevision, expectedRevision, }: CheckFirmwareRevisionParams) => Promise<FirmwareRevisionCheckResult>;
11
+ export declare const checkFirmwareRevision: ({ firmwareVersion, internalModel, deviceRevision, expectedRevision, firmwareType, }: CheckFirmwareRevisionParams) => Promise<FirmwareRevisionCheckResult>;
11
12
  //# sourceMappingURL=checkFirmwareRevision.d.ts.map
@@ -8,14 +8,12 @@ const assets_browser_1 = require("../utils/assets-browser");
8
8
  const isNotFoundError = (e) => e instanceof assets_browser_1.HttpRequestError && e.response.status === 404;
9
9
  const isNodeJSOfflineError = (e) => ['FetchError', 'AbortError'].includes(e.name);
10
10
  const isReactNativeOfflineError = (e) => e.name === 'TypeError' && e.message.includes('Network request failed');
11
+ const isAbortControllerTimeout = (e) => e.name === 'AbortError' ||
12
+ (e.name === 'TimeoutError' && e.message.includes('signal timed out'));
11
13
  const isOfflineError = (e) => {
12
14
  if (!(e instanceof Error))
13
15
  return false;
14
- return isNodeJSOfflineError(e) || isReactNativeOfflineError(e);
15
- };
16
- const getOnlineReleaseMetadata = async ({ firmwareVersion, internalModel, }) => {
17
- const onlineReleases = await (0, firmwareInfo_1.getOnlineReleases)(internalModel);
18
- return onlineReleases.find(onlineRelease => utils_1.versionUtils.isEqual(onlineRelease.version, firmwareVersion));
16
+ return isNodeJSOfflineError(e) || isReactNativeOfflineError(e) || isAbortControllerTimeout(e);
19
17
  };
20
18
  const failFirmwareRevisionCheck = (error, errorPayload) => ({
21
19
  success: false,
@@ -32,16 +30,13 @@ const doRevisionsMatch = ({ deviceRevision, expectedCommitRevision, firmwareVers
32
30
  });
33
31
  return adjustedExpected === deviceRevision;
34
32
  };
35
- const checkFirmwareRevision = async ({ firmwareVersion, internalModel, deviceRevision, expectedRevision, }) => {
33
+ const checkFirmwareRevision = async ({ firmwareVersion, internalModel, deviceRevision, expectedRevision, firmwareType, }) => {
36
34
  if (expectedRevision === undefined) {
37
- if (firmwareVersion.length !== 3) {
35
+ if (!utils_1.versionUtils.isVersionArray(firmwareVersion)) {
38
36
  return failFirmwareRevisionCheck('firmware-version-unknown');
39
37
  }
40
38
  try {
41
- const onlineRelease = await getOnlineReleaseMetadata({
42
- firmwareVersion,
43
- internalModel,
44
- });
39
+ const onlineRelease = await (0, firmwareInfo_1.getOnlineReleaseByVersion)(internalModel, firmwareVersion, firmwareType);
45
40
  if (onlineRelease?.firmware_revision === undefined) {
46
41
  return failFirmwareRevisionCheck('firmware-version-unknown');
47
42
  }
@@ -0,0 +1,4 @@
1
+ import type { Device } from '../Device';
2
+ export declare const createThpChannel: (device: Device) => Promise<void>;
3
+ export declare const thpHandshake: (device: Device) => Promise<void>;
4
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.thpHandshake = exports.createThpChannel = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const protobuf_1 = require("@trezor/protobuf");
6
+ const protocol_1 = require("@trezor/protocol");
7
+ const thpCall_1 = require("./thpCall");
8
+ const constants_1 = require("../../constants");
9
+ const DataManager_1 = require("../../data/DataManager");
10
+ const enumFromString = (dm) => typeof dm === 'string' ? protocol_1.thp.ThpPairingMethod[dm] : dm;
11
+ const getPairingMethods = (deviceMethods, settingsMethods) => deviceMethods?.flatMap(dm => {
12
+ const value = enumFromString(dm);
13
+ const isRequested = settingsMethods && settingsMethods.find(sm => value === enumFromString(sm));
14
+ return isRequested ? value : [];
15
+ });
16
+ const createThpChannel = async (device) => {
17
+ const thpState = device.getThpState();
18
+ if (!thpState) {
19
+ throw constants_1.ERRORS.TypedError('Device_ThpStateMissing');
20
+ }
21
+ thpState.setChannel(protocol_1.thp.constants.THP_DEFAULT_CHANNEL);
22
+ const nonce = (0, crypto_1.randomBytes)(8);
23
+ const createChannel = await (0, thpCall_1.thpCall)(device, 'ThpCreateChannelRequest', { nonce });
24
+ const { properties, ...resp } = createChannel.message;
25
+ if (nonce.compare(resp.nonce) !== 0) {
26
+ throw new Error('Nonce not meet' + nonce.toString('hex') + ' ' + resp.nonce.toString('hex'));
27
+ }
28
+ const settings = DataManager_1.DataManager.getSettings('thp');
29
+ const pairingMethods = getPairingMethods(properties.pairing_methods, settings?.pairingMethods);
30
+ if (!pairingMethods?.length) {
31
+ throw constants_1.ERRORS.TypedError('Device_ThpPairingMethodsException');
32
+ }
33
+ thpState.setThpProperties(properties);
34
+ thpState.setChannel(resp.channel);
35
+ thpState.updateHandshakeCredentials({
36
+ pairingMethods,
37
+ handshakeHash: resp.handshakeHash,
38
+ });
39
+ };
40
+ exports.createThpChannel = createThpChannel;
41
+ const thpHandshake = async (device) => {
42
+ const thpState = device.getThpState();
43
+ if (!thpState?.handshakeCredentials) {
44
+ throw constants_1.ERRORS.TypedError('Device_ThpStateMissing');
45
+ }
46
+ const settings = DataManager_1.DataManager.getSettings('thp');
47
+ const staticKey = settings?.staticKey
48
+ ? Buffer.from(settings.staticKey, 'hex')
49
+ : (0, crypto_1.randomBytes)(32);
50
+ const hostStaticKeys = protocol_1.thp.getCurve25519KeyPair(staticKey);
51
+ const knownCredentials = (settings?.knownCredentials || []).sort(cre => cre.autoconnect ? -1 : 1);
52
+ const hostEphemeralKeys = protocol_1.thp.getCurve25519KeyPair((0, crypto_1.randomBytes)(32));
53
+ const handshakeInit = await (0, thpCall_1.thpCall)(device, 'ThpHandshakeInitRequest', {
54
+ key: hostEphemeralKeys.publicKey,
55
+ });
56
+ const { trezorEncryptedStaticPubkey } = handshakeInit.message;
57
+ const handshakeCredentials = protocol_1.thp.handleHandshakeInit({
58
+ handshakeInitResponse: handshakeInit.message,
59
+ thpState,
60
+ hostStaticKeys,
61
+ hostEphemeralKeys,
62
+ knownCredentials,
63
+ protobufEncoder: (name, data) => (0, protobuf_1.encodeMessage)(device.transport.getMessages(), name, data),
64
+ });
65
+ const { hostKey, trezorKey, hostEncryptedStaticPubkey } = handshakeCredentials;
66
+ thpState.updateHandshakeCredentials({
67
+ trezorEncryptedStaticPubkey,
68
+ hostEncryptedStaticPubkey,
69
+ handshakeHash: handshakeCredentials.handshakeHash,
70
+ trezorKey,
71
+ hostKey,
72
+ staticKey,
73
+ hostStaticPublicKey: hostStaticKeys.publicKey,
74
+ });
75
+ thpState.setPairingCredentials(handshakeCredentials.allCredentials);
76
+ const handshakeCompletion = await (0, thpCall_1.thpCall)(device, 'ThpHandshakeCompletionRequest', {
77
+ hostPubkey: hostEncryptedStaticPubkey,
78
+ encryptedPayload: handshakeCredentials.encryptedPayload,
79
+ });
80
+ thpState.setIsPaired(!!handshakeCompletion.message.state);
81
+ thpState.setPhase('pairing');
82
+ if (thpState.isPaired && thpState.isAutoconnectPaired) {
83
+ await (0, thpCall_1.thpCall)(device, 'ThpEndRequest', {});
84
+ thpState.setPhase('paired');
85
+ }
86
+ };
87
+ exports.thpHandshake = thpHandshake;
88
+ //# sourceMappingURL=handshake.js.map
@@ -1,3 +1,6 @@
1
1
  import type { Device } from '../Device';
2
- export declare const getThpChannel: (_device: Device, _withInteraction?: boolean) => Promise<void>;
2
+ export { abortThpWorkflow } from './thpCall';
3
+ export { getThpCredentials } from './pairing';
4
+ export { createThpSession } from './session';
5
+ export declare const getThpChannel: (device: Device, withInteraction?: boolean) => Promise<void>;
3
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,11 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getThpChannel = void 0;
4
- const constants_1 = require("../../constants");
5
- const getThpChannel = async (_device, _withInteraction) => {
6
- await new Promise((_, reject) => {
7
- reject(constants_1.ERRORS.TypedError('Device_ThpStateMissing'));
8
- });
3
+ exports.getThpChannel = exports.createThpSession = exports.getThpCredentials = exports.abortThpWorkflow = void 0;
4
+ const handshake_1 = require("./handshake");
5
+ const pairing_1 = require("./pairing");
6
+ var thpCall_1 = require("./thpCall");
7
+ Object.defineProperty(exports, "abortThpWorkflow", { enumerable: true, get: function () { return thpCall_1.abortThpWorkflow; } });
8
+ var pairing_2 = require("./pairing");
9
+ Object.defineProperty(exports, "getThpCredentials", { enumerable: true, get: function () { return pairing_2.getThpCredentials; } });
10
+ var session_1 = require("./session");
11
+ Object.defineProperty(exports, "createThpSession", { enumerable: true, get: function () { return session_1.createThpSession; } });
12
+ const getThpChannel = async (device, withInteraction) => {
13
+ const thpState = device.getThpState();
14
+ try {
15
+ if (thpState?.phase === 'handshake') {
16
+ await (0, handshake_1.createThpChannel)(device);
17
+ await (0, handshake_1.thpHandshake)(device);
18
+ }
19
+ if (thpState?.phase === 'pairing' && withInteraction) {
20
+ await (0, pairing_1.thpPairing)(device);
21
+ }
22
+ }
23
+ catch (error) {
24
+ thpState?.resetState();
25
+ throw error;
26
+ }
9
27
  };
10
28
  exports.getThpChannel = getThpChannel;
11
29
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ import { thp as protocolThp } from '@trezor/protocol';
2
+ import type { Device } from '../Device';
3
+ export declare const getThpCredentials: (device: Device, autoconnect?: boolean) => Promise<{
4
+ autoconnect: boolean;
5
+ trezor_static_public_key: string;
6
+ credential: string;
7
+ }>;
8
+ export declare const thpPairingEnd: (device: Device) => Promise<{
9
+ type: "ThpEndResponse";
10
+ message: protocolThp.ThpEndResponse;
11
+ }>;
12
+ export declare const thpPairing: (device: Device) => Promise<{
13
+ type: "ThpEndResponse";
14
+ message: protocolThp.ThpEndResponse;
15
+ } | undefined>;
16
+ //# sourceMappingURL=pairing.d.ts.map
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.thpPairing = exports.thpPairingEnd = exports.getThpCredentials = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const protocol_1 = require("@trezor/protocol");
6
+ const utils_1 = require("@trezor/utils");
7
+ const constants_1 = require("../../constants");
8
+ const DataManager_1 = require("../../data/DataManager");
9
+ const events_1 = require("../../events");
10
+ const thpCall_1 = require("./thpCall");
11
+ const processQrCodeTag = async (device, value) => {
12
+ const thpState = device.getThpState();
13
+ if (!thpState?.handshakeCredentials) {
14
+ throw constants_1.ERRORS.TypedError('Device_ThpStateMissing');
15
+ }
16
+ const tagSha = (0, crypto_1.createHash)('sha256')
17
+ .update(thpState.handshakeCredentials.handshakeHash)
18
+ .update(Buffer.from(value, 'hex'))
19
+ .digest('hex');
20
+ const qrCodeSecret = await (0, thpCall_1.thpCall)(device, 'ThpQrCodeTag', {
21
+ tag: tagSha,
22
+ });
23
+ protocol_1.thp.validateQrCodeTag(thpState.handshakeCredentials, value, qrCodeSecret.message.secret);
24
+ return qrCodeSecret;
25
+ };
26
+ const processNfcTag = async (device, value) => {
27
+ const thpState = device.getThpState();
28
+ if (!thpState?.handshakeCredentials) {
29
+ throw constants_1.ERRORS.TypedError('Device_ThpStateMissing');
30
+ }
31
+ if (!thpState?.nfcSecret) {
32
+ throw new Error('missing nfcSecret');
33
+ }
34
+ const tagSha = (0, crypto_1.createHash)('sha256')
35
+ .update(Buffer.from([protocol_1.thp.ThpPairingMethod.NFC]))
36
+ .update(thpState.handshakeCredentials.handshakeHash)
37
+ .update(Buffer.from(value, 'hex'))
38
+ .digest('hex');
39
+ const nfcTagTrezor = await (0, thpCall_1.thpCall)(device, 'ThpNfcTagHost', {
40
+ tag: tagSha,
41
+ });
42
+ protocol_1.thp.validateNfcTag(thpState.handshakeCredentials, nfcTagTrezor.message.tag, thpState.nfcSecret);
43
+ return nfcTagTrezor;
44
+ };
45
+ const processCodeEntry = async (device, value) => {
46
+ if (value.length !== 6) {
47
+ throw constants_1.ERRORS.TypedError('Device_ThpPairingTagInvalid');
48
+ }
49
+ const codeValue = Buffer.from(value, 'ascii');
50
+ const thpState = device.getThpState();
51
+ if (!thpState?.handshakeCredentials) {
52
+ throw constants_1.ERRORS.TypedError('Device_ThpStateMissing');
53
+ }
54
+ const hostKeys = protocol_1.thp.getCpaceHostKeys(codeValue, thpState.handshakeCredentials.handshakeHash);
55
+ const tag = protocol_1.thp
56
+ .getSharedSecret(thpState.handshakeCredentials.trezorCpacePublicKey, hostKeys.privateKey)
57
+ .toString('hex');
58
+ const codeEntrySecret = await (0, thpCall_1.thpCall)(device, 'ThpCodeEntryCpaceHostTag', {
59
+ tag,
60
+ cpace_host_public_key: hostKeys.publicKey.toString('hex'),
61
+ });
62
+ protocol_1.thp.validateCodeEntryTag(thpState.handshakeCredentials, value, codeEntrySecret.message.secret);
63
+ return codeEntrySecret;
64
+ };
65
+ const processThpPairingResponse = (device, payload) => {
66
+ if ('selectedMethod' in payload) {
67
+ device.getThpState()?.setPairingMethod(payload.selectedMethod);
68
+ return (0, thpCall_1.thpCall)(device, 'ThpSelectMethod', {
69
+ selected_pairing_method: payload.selectedMethod,
70
+ });
71
+ }
72
+ if (payload.source === 'qr-code') {
73
+ return processQrCodeTag(device, payload.tag);
74
+ }
75
+ if (payload.source === 'nfc') {
76
+ return processNfcTag(device, payload.tag);
77
+ }
78
+ if (payload.source === 'code-entry') {
79
+ return processCodeEntry(device, payload.tag);
80
+ }
81
+ throw new Error(`Unknown THP pairing source ${payload.source}`);
82
+ };
83
+ const waitForPairingCancel = (device) => {
84
+ const readAbort = new AbortController();
85
+ device.getThpState()?.setExpectedResponses([0x04]);
86
+ const readCancel = device.getCurrentSession().receive({
87
+ signal: readAbort.signal,
88
+ });
89
+ return {
90
+ readAbort,
91
+ readCancel,
92
+ };
93
+ };
94
+ const waitForPairingTag = async (device) => {
95
+ const thpState = device.getThpState();
96
+ if (!thpState?.handshakeCredentials) {
97
+ throw constants_1.ERRORS.TypedError('Device_ThpStateMissing');
98
+ }
99
+ const dfd = (0, utils_1.createDeferred)();
100
+ const { readAbort, readCancel } = waitForPairingCancel(device);
101
+ const cancelResult = readCancel
102
+ .then(readResult => {
103
+ if (readResult.success) {
104
+ let error;
105
+ if (readResult.payload.type === 'Failure' && readResult.payload.message.message) {
106
+ error = readResult.payload.message.message;
107
+ }
108
+ else {
109
+ error = `Pairing tag cancelled (${readResult.payload.type})`;
110
+ }
111
+ dfd.resolve({ error });
112
+ }
113
+ })
114
+ .catch(() => {
115
+ });
116
+ thpState.setPairingTagPromise({
117
+ abort: async () => {
118
+ readAbort.abort();
119
+ await cancelResult;
120
+ },
121
+ });
122
+ const payload = {
123
+ availableMethods: thpState.handshakeCredentials.pairingMethods,
124
+ selectedMethod: thpState.pairingMethod,
125
+ nfcData: thpState.nfcData?.toString('hex'),
126
+ };
127
+ device.prompt('thp_pairing', { payload }).then(response => {
128
+ if (response.success) {
129
+ dfd.resolve(response.payload);
130
+ }
131
+ else {
132
+ (0, thpCall_1.abortThpWorkflow)(device).then(() => {
133
+ dfd.resolve({ error: response.error.message });
134
+ });
135
+ }
136
+ });
137
+ const pairingResponse = await dfd.promise;
138
+ readAbort.abort();
139
+ await readCancel;
140
+ thpState.setPairingTagPromise(undefined);
141
+ if ('error' in pairingResponse) {
142
+ throw new Error(pairingResponse.error);
143
+ }
144
+ await new Promise(resolve => setTimeout(resolve, 500));
145
+ return processThpPairingResponse(device, pairingResponse).catch(e => {
146
+ if (e.code === 'Failure_FirmwareError') {
147
+ throw constants_1.ERRORS.TypedError('Device_ThpPairingTagInvalid', e.message);
148
+ }
149
+ throw constants_1.ERRORS.TypedError(e.code, e.message);
150
+ });
151
+ };
152
+ const getThpCredentials = async (device, autoconnect = false) => {
153
+ const thpState = device.getThpState();
154
+ if (!thpState?.handshakeCredentials) {
155
+ throw constants_1.ERRORS.TypedError('Device_ThpStateMissing');
156
+ }
157
+ const credentials = await (0, thpCall_1.thpCall)(device, 'ThpCredentialRequest', {
158
+ autoconnect,
159
+ host_static_public_key: thpState.handshakeCredentials.hostStaticPublicKey.toString('hex'),
160
+ credential: thpState.pairingCredentials[0]?.credential,
161
+ });
162
+ return { ...credentials.message, autoconnect };
163
+ };
164
+ exports.getThpCredentials = getThpCredentials;
165
+ const thpPairingEnd = (device) => {
166
+ device.getThpState()?.setPhase('paired');
167
+ return (0, thpCall_1.thpCall)(device, 'ThpEndRequest', {});
168
+ };
169
+ exports.thpPairingEnd = thpPairingEnd;
170
+ const thpPairing = async (device) => {
171
+ const thpState = device.getThpState();
172
+ if (!thpState?.handshakeCredentials) {
173
+ throw constants_1.ERRORS.TypedError('Device_ThpStateMissing');
174
+ }
175
+ if (thpState.isPaired && thpState.pairingMethod !== protocol_1.thp.ThpPairingMethod.SkipPairing) {
176
+ if (!thpState.isAutoconnectPaired) {
177
+ await (0, exports.getThpCredentials)(device, false);
178
+ }
179
+ return (0, exports.thpPairingEnd)(device);
180
+ }
181
+ const [selected_pairing_method] = thpState.handshakeCredentials.pairingMethods;
182
+ thpState.setPairingMethod(selected_pairing_method);
183
+ const settings = DataManager_1.DataManager.getSettings('thp');
184
+ await (0, thpCall_1.thpCall)(device, 'ThpPairingRequest', {
185
+ host_name: settings?.hostName || 'Unknown hostName',
186
+ app_name: settings?.appName || 'Unknown appName',
187
+ });
188
+ const selectMethod = await (0, thpCall_1.thpCall)(device, 'ThpSelectMethod', { selected_pairing_method });
189
+ if (selectMethod.type === 'ThpEndResponse') {
190
+ thpState.setIsPaired(true);
191
+ device.getThpState()?.setPhase('paired');
192
+ return;
193
+ }
194
+ if (selectMethod.type === 'ThpCodeEntryCommitment') {
195
+ const codeEntryChallenge = (0, crypto_1.randomBytes)(32);
196
+ const handshakeCommitment = Buffer.from(selectMethod.message.commitment, 'hex');
197
+ thpState.updateHandshakeCredentials({
198
+ handshakeCommitment,
199
+ codeEntryChallenge,
200
+ });
201
+ const codeEntryCpace = await (0, thpCall_1.thpCall)(device, 'ThpCodeEntryChallenge', {
202
+ challenge: codeEntryChallenge.toString('hex'),
203
+ });
204
+ thpState.updateHandshakeCredentials({
205
+ trezorCpacePublicKey: Buffer.from(codeEntryCpace.message.cpace_trezor_public_key, 'hex'),
206
+ });
207
+ await waitForPairingTag(device);
208
+ }
209
+ if (selectMethod.type === 'ThpPairingPreparationsFinished') {
210
+ if (thpState.pairingMethod === protocol_1.thp.ThpPairingMethod.NFC) {
211
+ thpState.setNfcSecret((0, crypto_1.randomBytes)(16));
212
+ }
213
+ await waitForPairingTag(device);
214
+ }
215
+ const credentials = await (0, exports.getThpCredentials)(device, false);
216
+ device.emit(events_1.DEVICE.THP_CREDENTIALS_CHANGED, {
217
+ credentials,
218
+ staticKey: thpState.handshakeCredentials.staticKey.toString('hex'),
219
+ });
220
+ const settings1 = DataManager_1.DataManager.getSettings('thp');
221
+ if (settings1) {
222
+ settings1.knownCredentials?.push(credentials);
223
+ settings1.staticKey = thpState.handshakeCredentials.staticKey.toString('hex');
224
+ }
225
+ thpState.setPairingCredentials([credentials]);
226
+ thpState.setIsPaired(true);
227
+ await (0, exports.thpPairingEnd)(device);
228
+ };
229
+ exports.thpPairing = thpPairing;
230
+ //# sourceMappingURL=pairing.js.map
@@ -0,0 +1,3 @@
1
+ import type { Device } from '../Device';
2
+ export declare const createThpSession: (device: Device, deriveCardano: boolean) => Promise<number>;
3
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createThpSession = void 0;
4
+ const thpCall_1 = require("./thpCall");
5
+ const createThpSession = async (device, deriveCardano) => {
6
+ let passphrase;
7
+ if (!device.features.passphrase_protection) {
8
+ passphrase = { passphrase: '' };
9
+ }
10
+ else {
11
+ passphrase = await device.prompt('passphrase', {}).then(promptRes => {
12
+ if (!promptRes.success) {
13
+ return { passphrase: '' };
14
+ }
15
+ return promptRes.payload.passphraseOnDevice
16
+ ? { on_device: true }
17
+ : { passphrase: promptRes.payload.value };
18
+ });
19
+ }
20
+ await (0, thpCall_1.thpCall)(device, 'ThpCreateNewSession', {
21
+ ...passphrase,
22
+ derive_cardano: deriveCardano,
23
+ });
24
+ return 0;
25
+ };
26
+ exports.createThpSession = createThpSession;
27
+ //# sourceMappingURL=session.js.map