@trezor/connect 9.6.1 → 9.6.2-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 (53) hide show
  1. package/CHANGELOG.md +29 -11
  2. package/README.md +1 -1
  3. package/lib/api/applyFlags.js +1 -0
  4. package/lib/api/applySettings.js +1 -0
  5. package/lib/api/authenticateDevice.js +1 -0
  6. package/lib/api/backupDevice.js +1 -0
  7. package/lib/api/bitcoin/signtxVerify.d.ts +10 -4
  8. package/lib/api/bitcoin/signtxVerify.js +23 -72
  9. package/lib/api/bleUnpair.js +1 -0
  10. package/lib/api/changeLanguage.js +1 -0
  11. package/lib/api/changePin.js +1 -0
  12. package/lib/api/changeWipeCode.js +1 -0
  13. package/lib/api/composeTransaction.js +15 -6
  14. package/lib/api/ethereum/api/ethereumSignTypedData.js +1 -1
  15. package/lib/api/firmware/uploadFirmware.js +5 -0
  16. package/lib/api/loadDevice.js +1 -0
  17. package/lib/api/recoveryDevice.js +1 -0
  18. package/lib/api/resetDevice.js +1 -0
  19. package/lib/api/setBrightness.js +1 -0
  20. package/lib/api/setBusy.js +1 -0
  21. package/lib/api/signTransaction.js +19 -6
  22. package/lib/api/wipeDevice.js +1 -0
  23. package/lib/constants/errors.d.ts +1 -0
  24. package/lib/constants/errors.js +1 -0
  25. package/lib/core/AbstractMethod.js +1 -1
  26. package/lib/core/onCallFirmwareUpdate.js +1 -3
  27. package/lib/data/DataManager.d.ts +2 -1
  28. package/lib/data/DataManager.js +7 -6
  29. package/lib/data/coinInfo.d.ts +0 -64
  30. package/lib/data/coinInfo.js +1 -40
  31. package/lib/data/firmwareInfo.d.ts +1 -1
  32. package/lib/data/firmwareInfo.js +5 -3
  33. package/lib/data/version.d.ts +1 -1
  34. package/lib/data/version.js +1 -1
  35. package/lib/device/Device.d.ts +10 -10
  36. package/lib/device/Device.js +53 -62
  37. package/lib/device/DeviceCurrentSession.d.ts +38 -1
  38. package/lib/device/DeviceCurrentSession.js +28 -4
  39. package/lib/device/DeviceList.js +23 -39
  40. package/lib/device/TransportList.d.ts +2 -2
  41. package/lib/device/thp/index.d.ts +3 -0
  42. package/lib/device/thp/index.js +11 -0
  43. package/lib/device/workflow/handshake.d.ts +10 -0
  44. package/lib/device/workflow/handshake.js +57 -0
  45. package/lib/events/index.d.ts +1 -0
  46. package/lib/events/ui-request.d.ts +6 -1
  47. package/lib/events/ui-request.js +1 -0
  48. package/lib/tsconfig.lib.tsbuildinfo +1 -1
  49. package/lib/utils/assetUtils.d.ts +2 -1
  50. package/lib/utils/assetUtils.js +2 -1
  51. package/lib/utils/firmwareReleaseConfigUtils.d.ts +7 -0
  52. package/lib/utils/firmwareReleaseConfigUtils.js +83 -0
  53. package/package.json +20 -17
@@ -0,0 +1,10 @@
1
+ import { WorkflowContext } from '../../types/workflow';
2
+ import { Log } from '../../utils/debug';
3
+ type Context = {
4
+ device: WorkflowContext['device'];
5
+ signal: AbortSignal;
6
+ logger?: Log;
7
+ };
8
+ export declare const handshakeCancel: ({ device, logger, signal }: Context) => Promise<void>;
9
+ export {};
10
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handshakeCancel = void 0;
4
+ const protocol_1 = require("@trezor/protocol");
5
+ const transport_1 = require("@trezor/transport");
6
+ const utils_1 = require("@trezor/utils");
7
+ const DataManager_1 = require("../../data/DataManager");
8
+ const CANCEL_TIMEOUT = 1_000;
9
+ const CANCEL_TIMEOUT_REACT_NATIVE = 20_000;
10
+ const ATTEMPTS_LIMIT = 10;
11
+ const isLegacyBridge = (transport) => transport.name === 'BridgeTransport' &&
12
+ !utils_1.versionUtils.isNewerOrEqual(transport.version, '3.0.0');
13
+ const handshakeCancel = async ({ device, logger, signal }) => {
14
+ if (device.features || device.getThpState()?.properties) {
15
+ return;
16
+ }
17
+ const isNative = DataManager_1.DataManager.getSettings('env') === 'react-native';
18
+ const cancelTimeout = isNative ? CANCEL_TIMEOUT_REACT_NATIVE : CANCEL_TIMEOUT;
19
+ logger?.debug('handshake Cancel start');
20
+ const send = await device
21
+ .getCurrentSession()
22
+ .send('Cancel', {}, { signal, timeout: cancelTimeout });
23
+ if (!send.success) {
24
+ logger?.debug(`handshake Cancel send error ${send.error}`);
25
+ return;
26
+ }
27
+ logger?.debug('handshake Cancel sent');
28
+ for (let attempt = 0; attempt < ATTEMPTS_LIMIT; ++attempt) {
29
+ logger?.debug(`handshake Cancel read attempt ${attempt}`);
30
+ const result = await device.getCurrentSession().receive({
31
+ signal,
32
+ timeout: cancelTimeout,
33
+ });
34
+ if (!result.success && result.error.message === transport_1.TRANSPORT_ERROR.ABORTED_BY_TIMEOUT) {
35
+ if (isLegacyBridge(device.transport)) {
36
+ await (0, utils_1.resolveAfter)(501);
37
+ }
38
+ await device.acquire();
39
+ }
40
+ if (!result.success && result.error.message !== protocol_1.PROTOCOL_MALFORMED) {
41
+ logger?.debug(`handshake Cancel read error: ${result.error}`);
42
+ return;
43
+ }
44
+ else if (result.success) {
45
+ logger?.debug(`handshake Cancel read success: ${result.payload.type}`);
46
+ if (result.payload.type === 'Failure' &&
47
+ result.payload.message.code === 'Failure_InvalidProtocol') {
48
+ logger?.debug(`handshake Cancel protocol v2 detected`);
49
+ await device.setupThp();
50
+ }
51
+ return;
52
+ }
53
+ }
54
+ logger?.debug(`handshake Cancel attempts limit reached`);
55
+ };
56
+ exports.handshakeCancel = handshakeCancel;
57
+ //# sourceMappingURL=handshake.js.map
@@ -35,6 +35,7 @@ export declare const UI: {
35
35
  readonly FIRMWARE_NOT_COMPATIBLE: "ui-device_firmware_not_compatible";
36
36
  readonly FIRMWARE_NOT_INSTALLED: "ui-device_firmware_not_installed";
37
37
  readonly FIRMWARE_PROGRESS: "ui-firmware-progress";
38
+ readonly FIRMWARE_PROGRESS_UNEXPECTED_DELAY: "ui-firmware-progress-unexpected-delay";
38
39
  readonly FIRMWARE_RECONNECT: "ui-firmware_reconnect";
39
40
  readonly FIRMWARE_DISCONNECT: "ui-firmware_disconnect";
40
41
  readonly FIRMWARE_DOWNLOADED: "ui-firmware_downloaded";
@@ -20,6 +20,7 @@ export declare const UI_REQUEST: {
20
20
  readonly FIRMWARE_NOT_COMPATIBLE: "ui-device_firmware_not_compatible";
21
21
  readonly FIRMWARE_NOT_INSTALLED: "ui-device_firmware_not_installed";
22
22
  readonly FIRMWARE_PROGRESS: "ui-firmware-progress";
23
+ readonly FIRMWARE_PROGRESS_UNEXPECTED_DELAY: "ui-firmware-progress-unexpected-delay";
23
24
  readonly FIRMWARE_RECONNECT: "ui-firmware_reconnect";
24
25
  readonly FIRMWARE_DISCONNECT: "ui-firmware_disconnect";
25
26
  readonly FIRMWARE_DOWNLOADED: "ui-firmware_downloaded";
@@ -232,6 +233,10 @@ export interface FirmwareProgress {
232
233
  progress: number;
233
234
  };
234
235
  }
236
+ export interface FirmwareProgressUnexpectedDelay {
237
+ type: typeof UI_REQUEST.FIRMWARE_PROGRESS_UNEXPECTED_DELAY;
238
+ payload: {};
239
+ }
235
240
  export interface FirmwareReconnect {
236
241
  type: typeof UI_REQUEST.FIRMWARE_RECONNECT;
237
242
  payload: {
@@ -248,7 +253,7 @@ export interface FirmwareDisconnect {
248
253
  device: Device;
249
254
  };
250
255
  }
251
- export type UiEvent = UiRequestWithoutPayload | UiRequestDeviceAction | UiRequestButton | UiRequestPermission | UiRequestConfirmation | UiRequestSelectDevice | UiRequestUnexpectedDeviceMode | UiRequestSelectAccount | UiRequestSelectFee | UiRequestThpPairing | UpdateCustomFee | BundleProgress<any> | FirmwareProgress | FirmwareException | FirmwareReconnect | FirmwareDisconnect | UiRequestAddressValidation | UiRequestSetOperation | UiRequestFirmwareDownloaded;
256
+ export type UiEvent = UiRequestWithoutPayload | UiRequestDeviceAction | UiRequestButton | UiRequestPermission | UiRequestConfirmation | UiRequestSelectDevice | UiRequestUnexpectedDeviceMode | UiRequestSelectAccount | UiRequestSelectFee | UiRequestThpPairing | UpdateCustomFee | BundleProgress<any> | FirmwareProgress | FirmwareProgressUnexpectedDelay | FirmwareException | FirmwareReconnect | FirmwareDisconnect | UiRequestAddressValidation | UiRequestSetOperation | UiRequestFirmwareDownloaded;
252
257
  export type UiEventMessage = UiEvent & {
253
258
  event: typeof UI_EVENT;
254
259
  };
@@ -15,6 +15,7 @@ exports.UI_REQUEST = {
15
15
  FIRMWARE_NOT_COMPATIBLE: 'ui-device_firmware_not_compatible',
16
16
  FIRMWARE_NOT_INSTALLED: 'ui-device_firmware_not_installed',
17
17
  FIRMWARE_PROGRESS: 'ui-firmware-progress',
18
+ FIRMWARE_PROGRESS_UNEXPECTED_DELAY: 'ui-firmware-progress-unexpected-delay',
18
19
  FIRMWARE_RECONNECT: 'ui-firmware_reconnect',
19
20
  FIRMWARE_DISCONNECT: 'ui-firmware_disconnect',
20
21
  FIRMWARE_DOWNLOADED: 'ui-firmware_downloaded',
@@ -1 +1 @@
1
- {"root":["../src/exports.ts","../src/factory.ts","../src/index-browser.ts","../src/index.ts","../src/api/applyFlags.ts","../src/api/applySettings.ts","../src/api/authenticateDevice.ts","../src/api/authorizeCoinjoin.ts","../src/api/backupDevice.ts","../src/api/bleUnpair.ts","../src/api/blockchainDisconnect.ts","../src/api/blockchainEstimateFee.ts","../src/api/blockchainEvmRpcCall.ts","../src/api/blockchainGetAccountBalanceHistory.ts","../src/api/blockchainGetCurrentFiatRates.ts","../src/api/blockchainGetFiatRatesForTimestamps.ts","../src/api/blockchainGetInfo.ts","../src/api/blockchainGetTransactions.ts","../src/api/blockchainSetCustomBackend.ts","../src/api/blockchainSubscribe.ts","../src/api/blockchainSubscribeFiatRates.ts","../src/api/blockchainUnsubscribe.ts","../src/api/blockchainUnsubscribeFiatRates.ts","../src/api/cancelCoinjoinAuthorization.ts","../src/api/changeLanguage.ts","../src/api/changePin.ts","../src/api/changeWipeCode.ts","../src/api/cipherKeyValue.ts","../src/api/composeTransaction.ts","../src/api/discoverAccounts.ts","../src/api/getAccountDescriptor.ts","../src/api/getAccountInfo.ts","../src/api/getAddress.ts","../src/api/getCoinInfo.ts","../src/api/getDeviceState.ts","../src/api/getFeatures.ts","../src/api/getFirmwareHash.ts","../src/api/getNonce.ts","../src/api/getOwnershipId.ts","../src/api/getOwnershipProof.ts","../src/api/getPublicKey.ts","../src/api/getSettings.ts","../src/api/index.ts","../src/api/loadDevice.ts","../src/api/pushTransaction.ts","../src/api/recoveryDevice.ts","../src/api/requestLogin.ts","../src/api/resetDevice.ts","../src/api/setBrightness.ts","../src/api/setBusy.ts","../src/api/setProxy.ts","../src/api/showDeviceTutorial.ts","../src/api/signMessage.ts","../src/api/signTransaction.ts","../src/api/unlockPath.ts","../src/api/verifyMessage.ts","../src/api/wipeDevice.ts","../src/api/bitcoin/TransactionComposer.ts","../src/api/bitcoin/createPendingTx.ts","../src/api/bitcoin/enhanceSignTx.ts","../src/api/bitcoin/index.ts","../src/api/bitcoin/inputs.ts","../src/api/bitcoin/outputs.ts","../src/api/bitcoin/refTx.ts","../src/api/bitcoin/signtx.ts","../src/api/bitcoin/signtxLegacy.ts","../src/api/bitcoin/signtxVerify.ts","../src/api/bitcoin/transactionBytes.ts","../src/api/cardano/cardanoAddressParameters.ts","../src/api/cardano/cardanoAuxiliaryData.ts","../src/api/cardano/cardanoCertificate.ts","../src/api/cardano/cardanoInputs.ts","../src/api/cardano/cardanoOutputs.ts","../src/api/cardano/cardanoTokenBundle.ts","../src/api/cardano/cardanoUtils.ts","../src/api/cardano/cardanoWitnesses.ts","../src/api/cardano/api/cardanoComposeTransaction.ts","../src/api/cardano/api/cardanoGetAddress.ts","../src/api/cardano/api/cardanoGetNativeScriptHash.ts","../src/api/cardano/api/cardanoGetPublicKey.ts","../src/api/cardano/api/cardanoSignTransaction.ts","../src/api/cardano/api/index.ts","../src/api/common/Discovery.ts","../src/api/common/paramsValidator.ts","../src/api/eos/eosSignTx.ts","../src/api/eos/api/eosGetPublicKey.ts","../src/api/eos/api/eosSignTransaction.ts","../src/api/eos/api/index.ts","../src/api/ethereum/ethereumDefinitions.ts","../src/api/ethereum/ethereumSignTx.ts","../src/api/ethereum/ethereumSignTypedData.ts","../src/api/ethereum/api/ethereumGetAddress.ts","../src/api/ethereum/api/ethereumGetPublicKey.ts","../src/api/ethereum/api/ethereumSignMessage.ts","../src/api/ethereum/api/ethereumSignTransaction.ts","../src/api/ethereum/api/ethereumSignTypedData.ts","../src/api/ethereum/api/ethereumVerifyMessage.ts","../src/api/ethereum/api/index.ts","../src/api/firmware/calculateFirmwareHash.ts","../src/api/firmware/getBinary.ts","../src/api/firmware/getBinaryForFirmwareUpgrade.ts","../src/api/firmware/index.ts","../src/api/firmware/modifyFirmware.ts","../src/api/firmware/parseFirmwareHeaders.ts","../src/api/firmware/uploadFirmware.ts","../src/api/firmware/verifyAuthenticityProof.ts","../src/api/firmware/verifyEntropy.ts","../src/api/firmware/x509certificate.ts","../src/api/nem/nemSignTx.ts","../src/api/nem/api/index.ts","../src/api/nem/api/nemGetAddress.ts","../src/api/nem/api/nemSignTransaction.ts","../src/api/ripple/api/index.ts","../src/api/ripple/api/rippleGetAddress.ts","../src/api/ripple/api/rippleSignTransaction.ts","../src/api/solana/additionalInfo.ts","../src/api/solana/solanaDefinitions.ts","../src/api/solana/solanaUtils.ts","../src/api/solana/api/index.ts","../src/api/solana/api/solanaComposeTransaction.ts","../src/api/solana/api/solanaGetAddress.ts","../src/api/solana/api/solanaGetPublicKey.ts","../src/api/solana/api/solanaSignTransaction.ts","../src/api/stellar/stellarSignTx.ts","../src/api/stellar/api/index.ts","../src/api/stellar/api/stellarGetAddress.ts","../src/api/stellar/api/stellarSignTransaction.ts","../src/api/tezos/tezosSignTx.ts","../src/api/tezos/api/index.ts","../src/api/tezos/api/tezosGetAddress.ts","../src/api/tezos/api/tezosGetPublicKey.ts","../src/api/tezos/api/tezosSignTransaction.ts","../src/backend/BackendManager.ts","../src/backend/Blockchain.ts","../src/backend/BlockchainLink.ts","../src/backend/fees/BitcoinFeeLevels.ts","../src/backend/fees/EthereumFeeLevels.ts","../src/backend/fees/MiscFeeLevels.ts","../src/backend/fees/index.ts","../src/constants/cardano.ts","../src/constants/errors.ts","../src/constants/firmware.ts","../src/constants/index.ts","../src/constants/nem.ts","../src/constants/network.ts","../src/constants/utxo.ts","../src/core/AbstractMethod.ts","../src/core/index.ts","../src/core/method.native.ts","../src/core/method.ts","../src/core/onCallFirmwareUpdate.ts","../src/data/DataManager.ts","../src/data/analyticsInfo.ts","../src/data/coinInfo.ts","../src/data/config.ts","../src/data/connectSettings.ts","../src/data/defaultFeeLevels.ts","../src/data/deviceAuthenticityConfig.ts","../src/data/deviceAuthenticityConfigTypes.ts","../src/data/firmwareInfo.ts","../src/data/getLanguage.ts","../src/data/thpSettings.ts","../src/data/transportInfo.ts","../src/data/udevInfo.ts","../src/data/version.ts","../src/device/Device.ts","../src/device/DeviceCommands.ts","../src/device/DeviceCurrentSession.ts","../src/device/DeviceList.ts","../src/device/StateStorage.ts","../src/device/TransportList.ts","../src/device/TransportManager.ts","../src/device/calculateRevisionForDevice.ts","../src/device/checkFirmwareRevision.ts","../src/device/resolveDescriptorForTaproot.ts","../src/device/workflow/index.ts","../src/device/workflow/validateState.ts","../src/events/blockchain.ts","../src/events/call.ts","../src/events/core.ts","../src/events/device.ts","../src/events/iframe.ts","../src/events/index.ts","../src/events/popup.ts","../src/events/transport.ts","../src/events/ui-promise.ts","../src/events/ui-request.ts","../src/events/ui-response.ts","../src/events/webextension.ts","../src/impl/core-in-module.ts","../src/impl/dynamic.ts","../src/types/account.ts","../src/types/coinInfo.ts","../src/types/device.ts","../src/types/fees.ts","../src/types/firmware.ts","../src/types/index.ts","../src/types/params.ts","../src/types/settings.ts","../src/types/utils.ts","../src/types/workflow.ts","../src/types/api/applyFlags.ts","../src/types/api/applySettings.ts","../src/types/api/authenticateDevice.ts","../src/types/api/authorizeCoinjoin.ts","../src/types/api/backupDevice.ts","../src/types/api/bleUnpair.ts","../src/types/api/blockchainDisconnect.ts","../src/types/api/blockchainEstimateFee.ts","../src/types/api/blockchainEvmRpcCall.ts","../src/types/api/blockchainGetAccountBalanceHistory.ts","../src/types/api/blockchainGetCurrentFiatRates.ts","../src/types/api/blockchainGetFiatRatesForTimestamps.ts","../src/types/api/blockchainGetInfo.ts","../src/types/api/blockchainGetTransactions.ts","../src/types/api/blockchainSetCustomBackend.ts","../src/types/api/blockchainSubscribe.ts","../src/types/api/blockchainSubscribeFiatRates.ts","../src/types/api/blockchainUnsubscribe.ts","../src/types/api/blockchainUnsubscribeFiatRates.ts","../src/types/api/cancel.ts","../src/types/api/cancelCoinjoinAuthorization.ts","../src/types/api/cardanoComposeTransaction.ts","../src/types/api/cardanoGetAddress.ts","../src/types/api/cardanoGetNativeScriptHash.ts","../src/types/api/cardanoGetPublicKey.ts","../src/types/api/cardanoSignTransaction.ts","../src/types/api/changeLanguage.ts","../src/types/api/changePin.ts","../src/types/api/changeWipeCode.ts","../src/types/api/cipherKeyValue.ts","../src/types/api/composeTransaction.ts","../src/types/api/device.ts","../src/types/api/disableWebUSB.ts","../src/types/api/discoverAccounts.ts","../src/types/api/dispose.ts","../src/types/api/eosGetPublicKey.ts","../src/types/api/eosSignTransaction.ts","../src/types/api/ethereumGetAddress.ts","../src/types/api/ethereumGetPublicKey.ts","../src/types/api/ethereumSignMessage.ts","../src/types/api/ethereumSignTransaction.ts","../src/types/api/ethereumSignTypedData.ts","../src/types/api/ethereumVerifyMessage.ts","../src/types/api/firmwareUpdate.ts","../src/types/api/getAccountDescriptor.ts","../src/types/api/getAccountInfo.ts","../src/types/api/getAddress.ts","../src/types/api/getCoinInfo.ts","../src/types/api/getDeviceState.ts","../src/types/api/getFeatures.ts","../src/types/api/getFirmwareHash.ts","../src/types/api/getNonce.ts","../src/types/api/getOwnershipId.ts","../src/types/api/getOwnershipProof.ts","../src/types/api/getPublicKey.ts","../src/types/api/getSettings.ts","../src/types/api/index.ts","../src/types/api/init.ts","../src/types/api/loadDevice.ts","../src/types/api/manifest.ts","../src/types/api/nemGetAddress.ts","../src/types/api/nemSignTransaction.ts","../src/types/api/off.ts","../src/types/api/on.ts","../src/types/api/pushTransaction.ts","../src/types/api/recoveryDevice.ts","../src/types/api/removeAllListeners.ts","../src/types/api/renderWebUSBButton.ts","../src/types/api/requestLogin.ts","../src/types/api/requestWebUSBDevice.ts","../src/types/api/resetDevice.ts","../src/types/api/rippleGetAddress.ts","../src/types/api/rippleSignTransaction.ts","../src/types/api/setBrightness.ts","../src/types/api/setBusy.ts","../src/types/api/setProxy.ts","../src/types/api/setTransports.ts","../src/types/api/showDeviceTutorial.ts","../src/types/api/signMessage.ts","../src/types/api/signTransaction.ts","../src/types/api/solanaComposeTransaction.ts","../src/types/api/solanaGetAddress.ts","../src/types/api/solanaGetPublicKey.ts","../src/types/api/solanaSignTransaction.ts","../src/types/api/stellarGetAddress.ts","../src/types/api/stellarSignTransaction.ts","../src/types/api/tezosGetAddress.ts","../src/types/api/tezosGetPublicKey.ts","../src/types/api/tezosSignTransaction.ts","../src/types/api/uiResponse.ts","../src/types/api/unlockPath.ts","../src/types/api/verifyMessage.ts","../src/types/api/wipeDevice.ts","../src/types/api/bitcoin/index.ts","../src/types/api/cardano/index.ts","../src/types/api/eos/index.ts","../src/types/api/ethereum/index.ts","../src/types/api/nem/index.ts","../src/types/api/ripple/index.ts","../src/types/api/solana/index.ts","../src/types/api/stellar/index.ts","../src/types/api/tezos/index.ts","../src/utils/accountUtils.ts","../src/utils/addressUtils.ts","../src/utils/assetUtils.ts","../src/utils/assets-browser.ts","../src/utils/assets.native.ts","../src/utils/assets.ts","../src/utils/assetsTypes.ts","../src/utils/debug.ts","../src/utils/deviceFeaturesUtils.ts","../src/utils/ethereumUtils.ts","../src/utils/firmwareUtils.ts","../src/utils/formatUtils.ts","../src/utils/hdnodeUtils.ts","../src/utils/interactionTimeout.ts","../src/utils/pathUtils.ts","../src/utils/popupPromiseManager.ts","../src/utils/proxy-event-emitter.ts","../src/utils/uiPromiseManager.ts","../src/utils/urlUtils.ts","../src/utils/versionCheck.ts","../src/workers/workers-browser.ts","../src/workers/workers.native.ts","../src/workers/workers.ts"],"version":"5.8.3"}
1
+ {"root":["../src/exports.ts","../src/factory.ts","../src/index-browser.ts","../src/index.ts","../src/api/applyFlags.ts","../src/api/applySettings.ts","../src/api/authenticateDevice.ts","../src/api/authorizeCoinjoin.ts","../src/api/backupDevice.ts","../src/api/bleUnpair.ts","../src/api/blockchainDisconnect.ts","../src/api/blockchainEstimateFee.ts","../src/api/blockchainEvmRpcCall.ts","../src/api/blockchainGetAccountBalanceHistory.ts","../src/api/blockchainGetCurrentFiatRates.ts","../src/api/blockchainGetFiatRatesForTimestamps.ts","../src/api/blockchainGetInfo.ts","../src/api/blockchainGetTransactions.ts","../src/api/blockchainSetCustomBackend.ts","../src/api/blockchainSubscribe.ts","../src/api/blockchainSubscribeFiatRates.ts","../src/api/blockchainUnsubscribe.ts","../src/api/blockchainUnsubscribeFiatRates.ts","../src/api/cancelCoinjoinAuthorization.ts","../src/api/changeLanguage.ts","../src/api/changePin.ts","../src/api/changeWipeCode.ts","../src/api/cipherKeyValue.ts","../src/api/composeTransaction.ts","../src/api/discoverAccounts.ts","../src/api/getAccountDescriptor.ts","../src/api/getAccountInfo.ts","../src/api/getAddress.ts","../src/api/getCoinInfo.ts","../src/api/getDeviceState.ts","../src/api/getFeatures.ts","../src/api/getFirmwareHash.ts","../src/api/getNonce.ts","../src/api/getOwnershipId.ts","../src/api/getOwnershipProof.ts","../src/api/getPublicKey.ts","../src/api/getSettings.ts","../src/api/index.ts","../src/api/loadDevice.ts","../src/api/pushTransaction.ts","../src/api/recoveryDevice.ts","../src/api/requestLogin.ts","../src/api/resetDevice.ts","../src/api/setBrightness.ts","../src/api/setBusy.ts","../src/api/setProxy.ts","../src/api/showDeviceTutorial.ts","../src/api/signMessage.ts","../src/api/signTransaction.ts","../src/api/unlockPath.ts","../src/api/verifyMessage.ts","../src/api/wipeDevice.ts","../src/api/bitcoin/TransactionComposer.ts","../src/api/bitcoin/createPendingTx.ts","../src/api/bitcoin/enhanceSignTx.ts","../src/api/bitcoin/index.ts","../src/api/bitcoin/inputs.ts","../src/api/bitcoin/outputs.ts","../src/api/bitcoin/refTx.ts","../src/api/bitcoin/signtx.ts","../src/api/bitcoin/signtxLegacy.ts","../src/api/bitcoin/signtxVerify.ts","../src/api/bitcoin/transactionBytes.ts","../src/api/cardano/cardanoAddressParameters.ts","../src/api/cardano/cardanoAuxiliaryData.ts","../src/api/cardano/cardanoCertificate.ts","../src/api/cardano/cardanoInputs.ts","../src/api/cardano/cardanoOutputs.ts","../src/api/cardano/cardanoTokenBundle.ts","../src/api/cardano/cardanoUtils.ts","../src/api/cardano/cardanoWitnesses.ts","../src/api/cardano/api/cardanoComposeTransaction.ts","../src/api/cardano/api/cardanoGetAddress.ts","../src/api/cardano/api/cardanoGetNativeScriptHash.ts","../src/api/cardano/api/cardanoGetPublicKey.ts","../src/api/cardano/api/cardanoSignTransaction.ts","../src/api/cardano/api/index.ts","../src/api/common/Discovery.ts","../src/api/common/paramsValidator.ts","../src/api/eos/eosSignTx.ts","../src/api/eos/api/eosGetPublicKey.ts","../src/api/eos/api/eosSignTransaction.ts","../src/api/eos/api/index.ts","../src/api/ethereum/ethereumDefinitions.ts","../src/api/ethereum/ethereumSignTx.ts","../src/api/ethereum/ethereumSignTypedData.ts","../src/api/ethereum/api/ethereumGetAddress.ts","../src/api/ethereum/api/ethereumGetPublicKey.ts","../src/api/ethereum/api/ethereumSignMessage.ts","../src/api/ethereum/api/ethereumSignTransaction.ts","../src/api/ethereum/api/ethereumSignTypedData.ts","../src/api/ethereum/api/ethereumVerifyMessage.ts","../src/api/ethereum/api/index.ts","../src/api/firmware/calculateFirmwareHash.ts","../src/api/firmware/getBinary.ts","../src/api/firmware/getBinaryForFirmwareUpgrade.ts","../src/api/firmware/index.ts","../src/api/firmware/modifyFirmware.ts","../src/api/firmware/parseFirmwareHeaders.ts","../src/api/firmware/uploadFirmware.ts","../src/api/firmware/verifyAuthenticityProof.ts","../src/api/firmware/verifyEntropy.ts","../src/api/firmware/x509certificate.ts","../src/api/nem/nemSignTx.ts","../src/api/nem/api/index.ts","../src/api/nem/api/nemGetAddress.ts","../src/api/nem/api/nemSignTransaction.ts","../src/api/ripple/api/index.ts","../src/api/ripple/api/rippleGetAddress.ts","../src/api/ripple/api/rippleSignTransaction.ts","../src/api/solana/additionalInfo.ts","../src/api/solana/solanaDefinitions.ts","../src/api/solana/solanaUtils.ts","../src/api/solana/api/index.ts","../src/api/solana/api/solanaComposeTransaction.ts","../src/api/solana/api/solanaGetAddress.ts","../src/api/solana/api/solanaGetPublicKey.ts","../src/api/solana/api/solanaSignTransaction.ts","../src/api/stellar/stellarSignTx.ts","../src/api/stellar/api/index.ts","../src/api/stellar/api/stellarGetAddress.ts","../src/api/stellar/api/stellarSignTransaction.ts","../src/api/tezos/tezosSignTx.ts","../src/api/tezos/api/index.ts","../src/api/tezos/api/tezosGetAddress.ts","../src/api/tezos/api/tezosGetPublicKey.ts","../src/api/tezos/api/tezosSignTransaction.ts","../src/backend/BackendManager.ts","../src/backend/Blockchain.ts","../src/backend/BlockchainLink.ts","../src/backend/fees/BitcoinFeeLevels.ts","../src/backend/fees/EthereumFeeLevels.ts","../src/backend/fees/MiscFeeLevels.ts","../src/backend/fees/index.ts","../src/constants/cardano.ts","../src/constants/errors.ts","../src/constants/firmware.ts","../src/constants/index.ts","../src/constants/nem.ts","../src/constants/network.ts","../src/constants/utxo.ts","../src/core/AbstractMethod.ts","../src/core/index.ts","../src/core/method.native.ts","../src/core/method.ts","../src/core/onCallFirmwareUpdate.ts","../src/data/DataManager.ts","../src/data/analyticsInfo.ts","../src/data/coinInfo.ts","../src/data/config.ts","../src/data/connectSettings.ts","../src/data/defaultFeeLevels.ts","../src/data/deviceAuthenticityConfig.ts","../src/data/deviceAuthenticityConfigTypes.ts","../src/data/firmwareInfo.ts","../src/data/getLanguage.ts","../src/data/thpSettings.ts","../src/data/transportInfo.ts","../src/data/udevInfo.ts","../src/data/version.ts","../src/device/Device.ts","../src/device/DeviceCommands.ts","../src/device/DeviceCurrentSession.ts","../src/device/DeviceList.ts","../src/device/StateStorage.ts","../src/device/TransportList.ts","../src/device/TransportManager.ts","../src/device/calculateRevisionForDevice.ts","../src/device/checkFirmwareRevision.ts","../src/device/resolveDescriptorForTaproot.ts","../src/device/thp/index.ts","../src/device/workflow/handshake.ts","../src/device/workflow/index.ts","../src/device/workflow/validateState.ts","../src/events/blockchain.ts","../src/events/call.ts","../src/events/core.ts","../src/events/device.ts","../src/events/iframe.ts","../src/events/index.ts","../src/events/popup.ts","../src/events/transport.ts","../src/events/ui-promise.ts","../src/events/ui-request.ts","../src/events/ui-response.ts","../src/events/webextension.ts","../src/impl/core-in-module.ts","../src/impl/dynamic.ts","../src/types/account.ts","../src/types/coinInfo.ts","../src/types/device.ts","../src/types/fees.ts","../src/types/firmware.ts","../src/types/index.ts","../src/types/params.ts","../src/types/settings.ts","../src/types/utils.ts","../src/types/workflow.ts","../src/types/api/applyFlags.ts","../src/types/api/applySettings.ts","../src/types/api/authenticateDevice.ts","../src/types/api/authorizeCoinjoin.ts","../src/types/api/backupDevice.ts","../src/types/api/bleUnpair.ts","../src/types/api/blockchainDisconnect.ts","../src/types/api/blockchainEstimateFee.ts","../src/types/api/blockchainEvmRpcCall.ts","../src/types/api/blockchainGetAccountBalanceHistory.ts","../src/types/api/blockchainGetCurrentFiatRates.ts","../src/types/api/blockchainGetFiatRatesForTimestamps.ts","../src/types/api/blockchainGetInfo.ts","../src/types/api/blockchainGetTransactions.ts","../src/types/api/blockchainSetCustomBackend.ts","../src/types/api/blockchainSubscribe.ts","../src/types/api/blockchainSubscribeFiatRates.ts","../src/types/api/blockchainUnsubscribe.ts","../src/types/api/blockchainUnsubscribeFiatRates.ts","../src/types/api/cancel.ts","../src/types/api/cancelCoinjoinAuthorization.ts","../src/types/api/cardanoComposeTransaction.ts","../src/types/api/cardanoGetAddress.ts","../src/types/api/cardanoGetNativeScriptHash.ts","../src/types/api/cardanoGetPublicKey.ts","../src/types/api/cardanoSignTransaction.ts","../src/types/api/changeLanguage.ts","../src/types/api/changePin.ts","../src/types/api/changeWipeCode.ts","../src/types/api/cipherKeyValue.ts","../src/types/api/composeTransaction.ts","../src/types/api/device.ts","../src/types/api/disableWebUSB.ts","../src/types/api/discoverAccounts.ts","../src/types/api/dispose.ts","../src/types/api/eosGetPublicKey.ts","../src/types/api/eosSignTransaction.ts","../src/types/api/ethereumGetAddress.ts","../src/types/api/ethereumGetPublicKey.ts","../src/types/api/ethereumSignMessage.ts","../src/types/api/ethereumSignTransaction.ts","../src/types/api/ethereumSignTypedData.ts","../src/types/api/ethereumVerifyMessage.ts","../src/types/api/firmwareUpdate.ts","../src/types/api/getAccountDescriptor.ts","../src/types/api/getAccountInfo.ts","../src/types/api/getAddress.ts","../src/types/api/getCoinInfo.ts","../src/types/api/getDeviceState.ts","../src/types/api/getFeatures.ts","../src/types/api/getFirmwareHash.ts","../src/types/api/getNonce.ts","../src/types/api/getOwnershipId.ts","../src/types/api/getOwnershipProof.ts","../src/types/api/getPublicKey.ts","../src/types/api/getSettings.ts","../src/types/api/index.ts","../src/types/api/init.ts","../src/types/api/loadDevice.ts","../src/types/api/manifest.ts","../src/types/api/nemGetAddress.ts","../src/types/api/nemSignTransaction.ts","../src/types/api/off.ts","../src/types/api/on.ts","../src/types/api/pushTransaction.ts","../src/types/api/recoveryDevice.ts","../src/types/api/removeAllListeners.ts","../src/types/api/renderWebUSBButton.ts","../src/types/api/requestLogin.ts","../src/types/api/requestWebUSBDevice.ts","../src/types/api/resetDevice.ts","../src/types/api/rippleGetAddress.ts","../src/types/api/rippleSignTransaction.ts","../src/types/api/setBrightness.ts","../src/types/api/setBusy.ts","../src/types/api/setProxy.ts","../src/types/api/setTransports.ts","../src/types/api/showDeviceTutorial.ts","../src/types/api/signMessage.ts","../src/types/api/signTransaction.ts","../src/types/api/solanaComposeTransaction.ts","../src/types/api/solanaGetAddress.ts","../src/types/api/solanaGetPublicKey.ts","../src/types/api/solanaSignTransaction.ts","../src/types/api/stellarGetAddress.ts","../src/types/api/stellarSignTransaction.ts","../src/types/api/tezosGetAddress.ts","../src/types/api/tezosGetPublicKey.ts","../src/types/api/tezosSignTransaction.ts","../src/types/api/uiResponse.ts","../src/types/api/unlockPath.ts","../src/types/api/verifyMessage.ts","../src/types/api/wipeDevice.ts","../src/types/api/bitcoin/index.ts","../src/types/api/cardano/index.ts","../src/types/api/eos/index.ts","../src/types/api/ethereum/index.ts","../src/types/api/nem/index.ts","../src/types/api/ripple/index.ts","../src/types/api/solana/index.ts","../src/types/api/stellar/index.ts","../src/types/api/tezos/index.ts","../src/utils/accountUtils.ts","../src/utils/addressUtils.ts","../src/utils/assetUtils.ts","../src/utils/assets-browser.ts","../src/utils/assets.native.ts","../src/utils/assets.ts","../src/utils/assetsTypes.ts","../src/utils/debug.ts","../src/utils/deviceFeaturesUtils.ts","../src/utils/ethereumUtils.ts","../src/utils/firmwareReleaseConfigUtils.ts","../src/utils/firmwareUtils.ts","../src/utils/formatUtils.ts","../src/utils/hdnodeUtils.ts","../src/utils/interactionTimeout.ts","../src/utils/pathUtils.ts","../src/utils/popupPromiseManager.ts","../src/utils/proxy-event-emitter.ts","../src/utils/uiPromiseManager.ts","../src/utils/urlUtils.ts","../src/utils/versionCheck.ts","../src/workers/workers-browser.ts","../src/workers/workers.native.ts","../src/workers/workers.ts"],"version":"5.8.3"}
@@ -1,4 +1,5 @@
1
1
  import { DeviceModelInternal } from '@trezor/device-utils';
2
- export declare const firmwareAssets: Record<DeviceModelInternal, NodeRequire>;
2
+ export declare const firmwareAssets: Record<DeviceModelInternal, NodeJS.Require>;
3
+ export declare const firmwareReleaseConfigAssets: any;
3
4
  export declare const tryLocalAssetRequire: (url: string) => any;
4
5
  //# sourceMappingURL=assetUtils.d.ts.map
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.tryLocalAssetRequire = exports.firmwareAssets = void 0;
3
+ exports.tryLocalAssetRequire = exports.firmwareReleaseConfigAssets = exports.firmwareAssets = void 0;
4
4
  const device_utils_1 = require("@trezor/device-utils");
5
5
  const utils_1 = require("@trezor/utils");
6
6
  const isDeviceModel = (model) => (0, utils_1.isArrayMember)(model, Object.values(device_utils_1.DeviceModelInternal));
@@ -13,6 +13,7 @@ exports.firmwareAssets = {
13
13
  [device_utils_1.DeviceModelInternal.T3T1]: require('@trezor/connect-common/files/firmware/t3t1/releases.json'),
14
14
  [device_utils_1.DeviceModelInternal.T3W1]: require('@trezor/connect-common/files/firmware/t3w1/releases.json'),
15
15
  };
16
+ exports.firmwareReleaseConfigAssets = require('@trezor/connect-common/files/firmware/release/releases.v1.json');
16
17
  const tryLocalAssetRequire = (url) => {
17
18
  const fileUrl = url.split('?')[0];
18
19
  switch (fileUrl) {
@@ -0,0 +1,7 @@
1
+ import { FirmwareReleaseConfig } from '@trezor/device-utils';
2
+ type FirmwareUpdateSource = 'production' | 'test-unsigned' | 'test-signed';
3
+ export declare const RELEASES_URL_REMOTE_BASE = "https://data.trezor.io/suite/firmware";
4
+ export declare const RELEASES_URL_REMOTE: Record<FirmwareUpdateSource, string>;
5
+ export declare const getFirmwareReleaseConfig: () => Promise<FirmwareReleaseConfig>;
6
+ export {};
7
+ //# sourceMappingURL=firmwareReleaseConfigUtils.d.ts.map
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFirmwareReleaseConfig = exports.RELEASES_URL_REMOTE = exports.RELEASES_URL_REMOTE_BASE = void 0;
4
+ const jws_1 = require("jws");
5
+ const env_utils_1 = require("@trezor/env-utils");
6
+ const assetUtils_1 = require("./assetUtils");
7
+ const JWS_SIGN_ALGORITHM = 'ES256';
8
+ const VERSION = 1;
9
+ const JWS_RELEASES_FILENAME_REMOTE = `releases.v${VERSION}.jws`;
10
+ exports.RELEASES_URL_REMOTE_BASE = 'https://data.trezor.io/suite/firmware';
11
+ exports.RELEASES_URL_REMOTE = {
12
+ production: `${exports.RELEASES_URL_REMOTE_BASE}/production/${JWS_RELEASES_FILENAME_REMOTE}`,
13
+ 'test-unsigned': `${exports.RELEASES_URL_REMOTE_BASE}/unsigned/${JWS_RELEASES_FILENAME_REMOTE}`,
14
+ 'test-signed': `${exports.RELEASES_URL_REMOTE_BASE}/signed/${JWS_RELEASES_FILENAME_REMOTE}`,
15
+ };
16
+ const FORCE_LOCAL_JWS = true;
17
+ const getReleaseJWS = async (firmwareUpdateSource = 'production') => {
18
+ if (FORCE_LOCAL_JWS) {
19
+ return {
20
+ releasesJws: assetUtils_1.firmwareReleaseConfigAssets.jws,
21
+ isRemote: false,
22
+ };
23
+ }
24
+ const remoteReleasesUrl = exports.RELEASES_URL_REMOTE[firmwareUpdateSource];
25
+ try {
26
+ const controller = new AbortController();
27
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
28
+ const response = await fetch(remoteReleasesUrl, {
29
+ signal: controller.signal,
30
+ });
31
+ clearTimeout(timeoutId);
32
+ if (!response.ok) {
33
+ throw new Error(response.statusText);
34
+ }
35
+ const releasesJws = await response.text();
36
+ return {
37
+ releasesJws,
38
+ isRemote: true,
39
+ };
40
+ }
41
+ catch (error) {
42
+ console.error(`Fetching of remote JWS config failed: ${error}`);
43
+ return {
44
+ releasesJws: assetUtils_1.firmwareReleaseConfigAssets.jws,
45
+ isRemote: false,
46
+ };
47
+ }
48
+ };
49
+ const getFirmwareReleaseConfig = async () => {
50
+ const { releasesJws, isRemote } = await getReleaseJWS();
51
+ const decodedJws = (0, jws_1.decode)(releasesJws);
52
+ if (!decodedJws) {
53
+ throw new Error('Decoding of releases failed.');
54
+ }
55
+ if (isRemote) {
56
+ const decodedJwsLocal = (0, jws_1.decode)(assetUtils_1.firmwareReleaseConfigAssets.jws);
57
+ if (decodedJwsLocal && decodedJwsLocal.payload.sequence > decodedJws.payload.sequence) {
58
+ throw new Error('Local firmware release config cannot have greater sequence than remote.');
59
+ }
60
+ }
61
+ const algorithmInHeader = decodedJws?.header.alg;
62
+ if (algorithmInHeader !== JWS_SIGN_ALGORITHM) {
63
+ throw Error(`Wrong algorithm in JWS config header: ${algorithmInHeader}`);
64
+ }
65
+ const JWSPublicKey = (0, env_utils_1.getJWSPublicKey)('firmware-release');
66
+ if (!JWSPublicKey) {
67
+ throw new Error('JWS public key is not defined!');
68
+ }
69
+ try {
70
+ const isAuthenticityValid = (0, jws_1.verify)(releasesJws, JWS_SIGN_ALGORITHM, JWSPublicKey);
71
+ if (!isAuthenticityValid) {
72
+ throw new Error('Config authenticity is invalid');
73
+ }
74
+ const releases = JSON.parse(decodedJws.payload);
75
+ return releases;
76
+ }
77
+ catch (error) {
78
+ console.error('Error validating:', error);
79
+ throw new Error(`Failed to validate release message: ${error.message}`);
80
+ }
81
+ };
82
+ exports.getFirmwareReleaseConfig = getFirmwareReleaseConfig;
83
+ //# sourceMappingURL=firmwareReleaseConfigUtils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trezor/connect",
3
- "version": "9.6.1",
3
+ "version": "9.6.2-beta.2",
4
4
  "author": "Trezor <info@trezor.io>",
5
5
  "homepage": "https://github.com/trezor/trezor-suite/tree/develop/packages/connect",
6
6
  "description": "High-level javascript interface for Trezor hardware wallet.",
@@ -79,32 +79,35 @@
79
79
  "@solana-program/token": "^0.5.1",
80
80
  "@solana-program/token-2022": "^0.4.2",
81
81
  "@solana/kit": "^2.1.1",
82
- "@trezor/blockchain-link": "2.5.1",
83
- "@trezor/blockchain-link-types": "1.4.1",
84
- "@trezor/blockchain-link-utils": "1.4.1",
82
+ "@trezor/blockchain-link": "2.5.2-beta.1",
83
+ "@trezor/blockchain-link-types": "1.4.2-beta.2",
84
+ "@trezor/blockchain-link-utils": "1.4.2-beta.1",
85
85
  "@trezor/connect-analytics": "1.3.4",
86
- "@trezor/connect-common": "0.4.1",
87
- "@trezor/crypto-utils": "1.1.3",
88
- "@trezor/device-utils": "1.1.1",
89
- "@trezor/protobuf": "1.4.1",
90
- "@trezor/protocol": "1.2.7",
91
- "@trezor/schema-utils": "1.3.3",
92
- "@trezor/transport": "1.5.1",
93
- "@trezor/type-utils": "1.1.7",
94
- "@trezor/utils": "9.4.1",
95
- "@trezor/utxo-lib": "2.4.1",
86
+ "@trezor/connect-common": "0.4.2-beta.2",
87
+ "@trezor/crypto-utils": "1.1.4-beta.1",
88
+ "@trezor/device-utils": "1.1.2-beta.1",
89
+ "@trezor/env-utils": "^1.4.2-beta.1",
90
+ "@trezor/protobuf": "1.4.2-beta.1",
91
+ "@trezor/protocol": "1.2.8-beta.1",
92
+ "@trezor/schema-utils": "1.3.4-beta.1",
93
+ "@trezor/transport": "1.5.2-beta.1",
94
+ "@trezor/type-utils": "1.1.8-beta.1",
95
+ "@trezor/utils": "9.4.2-beta.2",
96
+ "@trezor/utxo-lib": "2.4.2-beta.1",
96
97
  "blakejs": "^1.2.1",
97
98
  "bs58": "^6.0.0",
98
99
  "bs58check": "^4.0.0",
99
- "cross-fetch": "^4.0.0"
100
+ "cross-fetch": "^4.0.0",
101
+ "jws": "^4.0.0"
100
102
  },
101
103
  "devDependencies": {
102
104
  "@babel/preset-typescript": "^7.27.1",
103
105
  "@trezor/eslint": "1.0.0",
104
106
  "@trezor/trezor-user-env-link": "1.0.2",
107
+ "@types/jws": "^3.2.10",
105
108
  "@types/karma": "^6.3.9",
106
109
  "babel-loader": "^10.0.0",
107
- "jest": "30.0.0",
110
+ "jest": "29.7.0",
108
111
  "karma": "^6.4.4",
109
112
  "karma-babel-preprocessor": "^8.0.2",
110
113
  "karma-chrome-launcher": "^3.2.0",
@@ -115,7 +118,7 @@
115
118
  "node-fetch": "^2.6.4",
116
119
  "node-libs-browser": "^2.2.1",
117
120
  "ts-node": "^10.9.2",
118
- "tsx": "^4.19.3",
121
+ "tsx": "^4.20.3",
119
122
  "web3-utils": "^4.3.2",
120
123
  "webpack": "5.99.9",
121
124
  "ws": "^8.18.0"