@onekeyfe/hd-core 1.2.0-alpha.98 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
  2. package/__tests__/DeviceCommands.test.ts +203 -27
  3. package/__tests__/base64Data.test.ts +69 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +89 -38
  5. package/__tests__/check-firmware-release-protocol-v2.test.ts +83 -14
  6. package/__tests__/device-connector-protocol.test.ts +21 -0
  7. package/__tests__/device-lifecycle-events.test.ts +427 -7
  8. package/__tests__/device-pool-state.test.ts +25 -0
  9. package/__tests__/device-settings.test.ts +237 -9
  10. package/__tests__/device-state-events.test.ts +2 -2
  11. package/__tests__/device-state-mapper.test.ts +61 -5
  12. package/__tests__/device-utils.test.ts +15 -1
  13. package/__tests__/device-wallet-session-store.test.ts +147 -21
  14. package/__tests__/deviceSettings.test.ts +7 -3
  15. package/__tests__/deviceUploadNft.test.ts +36 -4
  16. package/__tests__/firmware-update/device-update-bootloader.test.ts +14 -3
  17. package/__tests__/firmware-update/firmware-artifact-source.test.ts +61 -9
  18. package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +19 -9
  19. package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +21 -0
  20. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +128 -2
  21. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
  22. package/__tests__/firmware-update/firmware-update-plan.test.ts +114 -50
  23. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +19 -5
  24. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
  25. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +678 -0
  26. package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
  27. package/__tests__/get-device-state.test.ts +60 -21
  28. package/__tests__/logBlockEvent.test.ts +13 -1
  29. package/__tests__/open-wallet-session.test.ts +127 -1
  30. package/__tests__/protocol-v2-firmware-targets.test.ts +39 -0
  31. package/__tests__/protocol-v2-resources.test.ts +185 -88
  32. package/__tests__/protocol-v2-unlock-policy.test.ts +235 -2
  33. package/__tests__/protocol-v2.test.ts +2274 -466
  34. package/__tests__/refresh-device-state.test.ts +3 -1
  35. package/__tests__/resourceBase64Boundary.test.ts +48 -0
  36. package/__tests__/search-devices.test.ts +79 -32
  37. package/__tests__/ton-sign-message.test.ts +84 -0
  38. package/dist/api/BaseMethod.d.ts +2 -1
  39. package/dist/api/BaseMethod.d.ts.map +1 -1
  40. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  41. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  42. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  43. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  44. package/dist/api/FirmwareUpdateV4.d.ts +22 -7
  45. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  46. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  47. package/dist/api/SearchDevices.d.ts.map +1 -1
  48. package/dist/api/UploadPortfolio.d.ts +1 -1
  49. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  50. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  51. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  52. package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
  53. package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
  54. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  55. package/dist/api/device/DeviceVerify.d.ts +1 -0
  56. package/dist/api/device/DeviceVerify.d.ts.map +1 -1
  57. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  58. package/dist/api/firmware/FirmwareArtifactSource.d.ts +5 -1
  59. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
  60. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
  61. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -1
  62. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  63. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  64. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -15
  65. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  66. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +1 -0
  67. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  68. package/dist/api/firmware/getBinary.d.ts +3 -2
  69. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  70. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
  71. package/dist/api/helpers/base64Data.d.ts +16 -0
  72. package/dist/api/helpers/base64Data.d.ts.map +1 -0
  73. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +3 -1
  74. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
  75. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  76. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  77. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  78. package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -1
  79. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
  80. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
  81. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  82. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
  83. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  84. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  85. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  86. package/dist/core/index.d.ts +6 -1
  87. package/dist/core/index.d.ts.map +1 -1
  88. package/dist/data-manager/DataManager.d.ts +4 -3
  89. package/dist/data-manager/DataManager.d.ts.map +1 -1
  90. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  91. package/dist/device/Device.d.ts +16 -3
  92. package/dist/device/Device.d.ts.map +1 -1
  93. package/dist/device/DeviceCommands.d.ts +4 -4
  94. package/dist/device/DeviceCommands.d.ts.map +1 -1
  95. package/dist/device/DeviceConnector.d.ts +1 -1
  96. package/dist/device/DeviceConnector.d.ts.map +1 -1
  97. package/dist/device/DevicePool.d.ts.map +1 -1
  98. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  99. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  100. package/dist/events/logBlockEvent.d.ts.map +1 -1
  101. package/dist/events/ui-request.d.ts +3 -0
  102. package/dist/events/ui-request.d.ts.map +1 -1
  103. package/dist/index.d.ts +54 -84
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +2333 -1515
  106. package/dist/protocols/protocol-v2/features.d.ts +13 -5
  107. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  108. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  109. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  110. package/dist/protocols/protocol-v2/resources.d.ts +12 -7
  111. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  112. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +3 -1
  113. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  114. package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
  115. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  116. package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
  117. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
  118. package/dist/types/api/protocolV2.d.ts +1 -1
  119. package/dist/types/api/protocolV2.d.ts.map +1 -1
  120. package/dist/types/device.d.ts.map +1 -1
  121. package/dist/types/settings.d.ts +3 -31
  122. package/dist/types/settings.d.ts.map +1 -1
  123. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  124. package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
  125. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  126. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  127. package/dist/utils/deviceSettings.d.ts +1 -0
  128. package/dist/utils/deviceSettings.d.ts.map +1 -1
  129. package/dist/utils/index.d.ts +1 -1
  130. package/dist/utils/index.d.ts.map +1 -1
  131. package/dist/utils/patch.d.ts +1 -1
  132. package/dist/utils/patch.d.ts.map +1 -1
  133. package/dist/utils/pro2Nft.d.ts +7 -0
  134. package/dist/utils/pro2Nft.d.ts.map +1 -1
  135. package/package.json +6 -4
  136. package/src/api/BaseMethod.ts +8 -1
  137. package/src/api/CheckAllFirmwareRelease.ts +8 -5
  138. package/src/api/CheckBLEFirmwareRelease.ts +2 -2
  139. package/src/api/CheckBootloaderRelease.ts +3 -2
  140. package/src/api/CheckFirmwareRelease.ts +2 -2
  141. package/src/api/FirmwareUpdateV2.ts +130 -69
  142. package/src/api/FirmwareUpdateV3.ts +29 -19
  143. package/src/api/FirmwareUpdateV4.ts +966 -743
  144. package/src/api/OpenWalletSession.ts +39 -9
  145. package/src/api/PromptWebDeviceAccess.ts +2 -2
  146. package/src/api/SearchDevices.ts +6 -3
  147. package/src/api/UploadPortfolio.ts +9 -2
  148. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
  149. package/src/api/device/DeviceChangePin.ts +4 -1
  150. package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
  151. package/src/api/device/DeviceRebootToBootloader.ts +3 -1
  152. package/src/api/device/DeviceSettings.ts +34 -20
  153. package/src/api/device/DeviceVerify.ts +25 -0
  154. package/src/api/device/DeviceWipe.ts +4 -1
  155. package/src/api/firmware/FirmwareArtifactSource.ts +41 -14
  156. package/src/api/firmware/FirmwareHostBinding.ts +10 -0
  157. package/src/api/firmware/FirmwarePreparedResourceArchive.ts +16 -27
  158. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +131 -76
  159. package/src/api/firmware/FirmwareUpdatePlan.ts +55 -82
  160. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +32 -15
  161. package/src/api/firmware/protocolV2Release.ts +7 -2
  162. package/src/api/helpers/base64Data.ts +85 -0
  163. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  164. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
  165. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
  166. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  167. package/src/api/protocol-v2/DeviceReboot.ts +5 -0
  168. package/src/api/protocol-v2/DeviceUploadNft.ts +61 -9
  169. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +53 -19
  170. package/src/api/protocol-v2/helpers.ts +8 -0
  171. package/src/api/ton/TonSignMessage.ts +5 -3
  172. package/src/core/index.ts +264 -55
  173. package/src/data/messages/messages-protocol-v2.json +140 -13
  174. package/src/data-manager/DataManager.ts +46 -39
  175. package/src/data-manager/TransportManager.ts +3 -1
  176. package/src/device/Device.ts +210 -44
  177. package/src/device/DeviceCommands.ts +27 -18
  178. package/src/device/DeviceConnector.ts +17 -3
  179. package/src/device/DevicePool.ts +9 -2
  180. package/src/device/DeviceStateMapper.ts +30 -24
  181. package/src/deviceProfile/buildDeviceFeatures.ts +10 -5
  182. package/src/events/logBlockEvent.ts +14 -1
  183. package/src/events/ui-request.ts +3 -0
  184. package/src/index.ts +0 -10
  185. package/src/protocols/protocol-v2/features.ts +22 -5
  186. package/src/protocols/protocol-v2/index.ts +2 -0
  187. package/src/protocols/protocol-v2/resources.ts +110 -147
  188. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +13 -1
  189. package/src/protocols/protocol-v2/walletSession.ts +65 -22
  190. package/src/types/api/firmwareUpdate.ts +1 -1
  191. package/src/types/api/firmwareUpdatePlan.ts +2 -2
  192. package/src/types/api/protocolV2.ts +1 -1
  193. package/src/types/device.ts +2 -1
  194. package/src/types/settings.ts +6 -34
  195. package/src/utils/deviceFeaturesCompat.ts +9 -4
  196. package/src/utils/deviceFeaturesUtils.ts +4 -0
  197. package/src/utils/deviceInfoUtils.ts +9 -2
  198. package/src/utils/deviceSettings.ts +11 -4
  199. package/src/utils/index.ts +1 -0
  200. package/src/utils/pro2Nft.ts +31 -8
  201. package/__tests__/firmware-memory-host.test.ts +0 -112
  202. package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
  203. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
  204. package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
@@ -1,14 +1,14 @@
1
1
  import axios from 'axios';
2
- import { sha256 } from '@noble/hashes/sha256';
3
- import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { EDeviceType, EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
4
3
 
5
4
  import { DataManager } from '../src/data-manager';
6
5
  import {
7
- parseProtocolV2ResourceManifest,
6
+ isProtocolV2ResourceArchiveEntryName,
7
+ parseProtocolV2ResourcePackage,
8
8
  parseProtocolV2Resources,
9
9
  } from '../src/protocols/protocol-v2/resources';
10
10
 
11
- import type { ConnectSettings, RemoteConfigResponse } from '../src/types';
11
+ import type { ConnectSettings, Features, RemoteConfigResponse } from '../src/types';
12
12
 
13
13
  jest.mock('axios');
14
14
  jest.mock('../src/data/config', () => ({
@@ -16,9 +16,6 @@ jest.mock('../src/data/config', () => ({
16
16
  DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0-test/',
17
17
  }));
18
18
 
19
- const bytesToHex = (bytes: Uint8Array) =>
20
- Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
21
-
22
19
  const resourceSource = {
23
20
  archiveUrl: 'https://example.com/resource/pro2-resource.zip',
24
21
  archiveSha256: 'a'.repeat(64),
@@ -31,65 +28,21 @@ const neoResourceSource = {
31
28
  archiveSize: 12_345_678,
32
29
  };
33
30
 
34
- const manifestFiles = [
35
- ['bundles/firmware_logo-build.okpkg', 'firmware_logo.okpkg', 'vol0:/bundles/firmware_logo.okpkg'],
36
- ['bundles/font/noto-build.okpkg', 'noto.okpkg', 'vol0:/bundles/font/noto.okpkg'],
37
- ['bundles/font/roobert-build.okpkg', 'roobert.okpkg', 'vol0:/bundles/font/roobert.okpkg'],
38
- [
39
- 'bundles/images/animation-build.okpkg',
40
- 'animation.okpkg',
41
- 'vol0:/bundles/images/animation.okpkg',
42
- ],
43
- ['bundles/images/images-build.okpkg', 'images.okpkg', 'vol0:/bundles/images/images.okpkg'],
44
- [
45
- 'bundles/images/wallpaper-build.okpkg',
46
- 'wallpaper.okpkg',
47
- 'vol0:/bundles/images/wallpaper.okpkg',
48
- ],
49
- [
50
- 'bundles/translations/translations-build.okpkg',
51
- 'translations.okpkg',
52
- 'vol0:/bundles/translations/translations.okpkg',
53
- ],
54
- [
55
- 'loaders/bootloader/boot_resource-build.okpkg',
56
- 'boot_resource.okpkg',
57
- 'vol0:/loaders/bootloader/boot_resource.okpkg',
58
- ],
59
- ['loaders/rom/params-build.okpkg', 'params.okpkg', 'vol0:/loaders/rom/params.okpkg'],
60
- ].map(([archive_path, original_name, device_path], index) => {
61
- const binary = new Uint8Array([index + 1]).buffer;
62
- return {
63
- archive_path,
64
- original_name,
65
- device_path,
66
- size: 1,
67
- sha256: bytesToHex(sha256(new Uint8Array(binary))),
68
- signed: true,
69
- sig_algo: device_path.startsWith('vol0:/bundles/') ? 'ed25519' : 'mldsa65',
70
- payload_version: '1.0.0',
71
- binary,
72
- };
73
- });
74
-
75
- const resourceManifest = {
76
- schema: 1,
77
- artifact_name: 'pro2-resource-build',
78
- release_name: 'resource-build',
79
- variant: 'resource',
80
- commit: 'a'.repeat(40),
81
- short_sha: 'aaaaaaa',
82
- timestamp_utc: '20260807_091424',
83
- core_version: '1.0.0',
84
- key_set: 'dev',
85
- device_root: 'vol0:',
86
- restore_mode: 'bootloader_update',
87
- trees: [
88
- { path: 'bundles', device: 'vol0:/bundles' },
89
- { path: 'loaders/bootloader', device: 'vol0:/loaders/bootloader' },
90
- { path: 'loaders/rom', device: 'vol0:/loaders/rom' },
91
- ],
92
- files: manifestFiles.map(({ binary: _binary, ...file }) => file),
31
+ const createResourcePackage = (devicePath: string) => {
32
+ const headerSize = 0x5f90;
33
+ const bytes = new Uint8Array(headerSize + 1);
34
+ bytes.set(new TextEncoder().encode('OKPP'), 0);
35
+ bytes.set(new TextEncoder().encode('RESC'), 0x08);
36
+ bytes.set(new TextEncoder().encode(devicePath), 0x6c);
37
+ const view = new DataView(bytes.buffer);
38
+ view.setUint32(0x04, 1, true);
39
+ view.setUint32(0x0c, headerSize, true);
40
+ view.setUint32(0x10, 0x010203, true);
41
+ view.setUint32(0x14, 1, true);
42
+ bytes.fill(0x11, 0x200, 0x240);
43
+ bytes.fill(0x22, 0x240, 0x280);
44
+ bytes[headerSize] = 0xaa;
45
+ return bytes;
93
46
  };
94
47
 
95
48
  const createSettings = (configFetcher: ConnectSettings['configFetcher']): ConnectSettings =>
@@ -108,25 +61,109 @@ const createRemoteConfig = (): RemoteConfigResponse =>
108
61
  mini: { firmware: [], ble: [] },
109
62
  touch: { firmware: [], ble: [] },
110
63
  pro: { firmware: [], ble: [] },
111
- pro2: { firmware: [], ble: [], resources: { source: resourceSource } },
112
- neo: { firmware: [], ble: [], resources: { source: neoResourceSource } },
64
+ pro2: {
65
+ 'firmware-v1': [
66
+ {
67
+ required: false,
68
+ version: [1, 0, 0],
69
+ changelog: { 'zh-CN': 'Pro2', 'en-US': 'Pro2' },
70
+ resources: { source: resourceSource },
71
+ },
72
+ ],
73
+ ble: [],
74
+ },
75
+ neo: {
76
+ 'firmware-v1': [
77
+ {
78
+ required: false,
79
+ version: [1, 0, 0],
80
+ changelog: { 'zh-CN': 'Neo', 'en-US': 'Neo' },
81
+ resources: { source: neoResourceSource },
82
+ },
83
+ ],
84
+ ble: [],
85
+ },
113
86
  bridge: {},
114
87
  } as unknown as RemoteConfigResponse);
115
88
 
89
+ const getProtocolV2Release = (deviceType: EDeviceType.Pro2 | EDeviceType.Neo) =>
90
+ DataManager.deviceMap[deviceType]?.['firmware-v1']?.[0];
91
+
116
92
  describe('Pro2 resource configuration', () => {
117
93
  beforeEach(() => {
118
94
  jest.clearAllMocks();
119
95
  DataManager.lastCheckTimestamp = 0;
120
96
  });
121
97
 
122
- test('accepts the manifest source and the CI schema 1 file set', () => {
98
+ test('accepts the release archive source', () => {
123
99
  expect(parseProtocolV2Resources({ source: resourceSource })).toEqual({
124
100
  source: resourceSource,
125
101
  });
126
- expect(parseProtocolV2ResourceManifest(resourceManifest).files).toHaveLength(9);
127
102
  });
128
103
 
129
- test('rejects missing source and malformed manifest paths', () => {
104
+ test('ignores macOS ZIP metadata that only looks like a resource package', () => {
105
+ expect(
106
+ isProtocolV2ResourceArchiveEntryName('bundles/firmware_logo-pro2-prod_resource-signed.okpkg')
107
+ ).toBe(true);
108
+ expect(
109
+ isProtocolV2ResourceArchiveEntryName(
110
+ 'pro2-prod_resource/bundles/firmware_logo-pro2-prod_resource-signed.okpkg'
111
+ )
112
+ ).toBe(true);
113
+ expect(
114
+ isProtocolV2ResourceArchiveEntryName(
115
+ '__MACOSX/pro2-prod_resource 2/bundles/._firmware_logo-pro2-prod_resource-signed.okpkg'
116
+ )
117
+ ).toBe(false);
118
+ expect(
119
+ isProtocolV2ResourceArchiveEntryName(
120
+ 'bundles/._firmware_logo-pro2-prod_resource-signed.okpkg'
121
+ )
122
+ ).toBe(false);
123
+ expect(isProtocolV2ResourceArchiveEntryName('pro2-prod_resource/manifest.json')).toBe(false);
124
+ expect(isProtocolV2ResourceArchiveEntryName('pro2-prod_resource/.DS_Store')).toBe(false);
125
+ });
126
+
127
+ test('reads the version, hashes, and direct device path from a RESC package', () => {
128
+ expect(
129
+ parseProtocolV2ResourcePackage(createResourcePackage('vol0:/bundles/images.okpkg'))
130
+ ).toEqual({
131
+ version: [1, 2, 3],
132
+ payloadLength: 1,
133
+ devicePath: 'vol0:/bundles/images.okpkg',
134
+ payloadHash: '11'.repeat(64),
135
+ headerHash: '22'.repeat(64),
136
+ });
137
+ });
138
+
139
+ test('uses the RESC header device path and only rejects malformed paths', () => {
140
+ expect(
141
+ parseProtocolV2ResourcePackage(
142
+ createResourcePackage('vol0:/loaders/bootloader/boot_resource.okpkg.staging')
143
+ ).devicePath
144
+ ).toBe('vol0:/loaders/bootloader/boot_resource.okpkg.staging');
145
+ expect(
146
+ parseProtocolV2ResourcePackage(
147
+ createResourcePackage('vol0:/loaders/bootloader/params.okpkg.staging')
148
+ ).devicePath
149
+ ).toBe('vol0:/loaders/bootloader/params.okpkg.staging');
150
+ expect(
151
+ parseProtocolV2ResourcePackage(createResourcePackage('vol0:/loaders/rom/params.okpkg'))
152
+ .devicePath
153
+ ).toBe('vol0:/loaders/rom/params.okpkg');
154
+ expect(
155
+ parseProtocolV2ResourcePackage(createResourcePackage('vol0:/unexpected/images.okpkg'))
156
+ .devicePath
157
+ ).toBe('vol0:/unexpected/images.okpkg');
158
+ expect(() =>
159
+ parseProtocolV2ResourcePackage(createResourcePackage('vol0:/bundles/../images.okpkg'))
160
+ ).toThrow('device path');
161
+ expect(() =>
162
+ parseProtocolV2ResourcePackage(createResourcePackage('vol0://bundles/images.okpkg'))
163
+ ).toThrow('device path');
164
+ });
165
+
166
+ test('rejects missing or malformed release sources', () => {
130
167
  expect(() => parseProtocolV2Resources({})).toThrow('source is required');
131
168
  expect(() =>
132
169
  parseProtocolV2Resources({
@@ -146,14 +183,6 @@ describe('Pro2 resource configuration', () => {
146
183
  source: { ...resourceSource, archiveSize: 0 },
147
184
  })
148
185
  ).toThrow('positive integer');
149
- expect(() =>
150
- parseProtocolV2ResourceManifest({
151
- ...resourceManifest,
152
- files: resourceManifest.files.map((file, index) =>
153
- index === 0 ? { ...file, archive_path: '../outside.okpkg' } : file
154
- ),
155
- })
156
- ).toThrow('archive_path');
157
186
  });
158
187
 
159
188
  test('applies a validated pre-release config and exposes its archive source', async () => {
@@ -164,10 +193,70 @@ describe('Pro2 resource configuration', () => {
164
193
  expect(configFetcher).toHaveBeenCalledWith(
165
194
  expect.stringMatching(/^https:\/\/data\.onekey\.so\/pre-config\.json\?noCache=/)
166
195
  );
167
- expect(DataManager.getProtocolV2ResourceSource()).toEqual(resourceSource);
196
+ expect(DataManager.getProtocolV2ResourceSource(getProtocolV2Release(EDeviceType.Pro2))).toEqual(
197
+ resourceSource
198
+ );
168
199
  expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
169
200
  });
170
201
 
202
+ test('uses the resource archive bound to the selected firmware release', async () => {
203
+ const newerResourceSource = {
204
+ archiveUrl: 'https://example.com/resource/pro2-resource-v2.zip',
205
+ archiveSha256: 'c'.repeat(64),
206
+ archiveSize: 20_000_000,
207
+ };
208
+ const remoteConfig = createRemoteConfig();
209
+ remoteConfig.pro2['firmware-v1']?.push({
210
+ ...remoteConfig.pro2['firmware-v1'][0],
211
+ version: [2, 0, 0],
212
+ resources: { source: newerResourceSource },
213
+ });
214
+
215
+ await expect(
216
+ DataManager.load(createSettings(jest.fn().mockResolvedValue(remoteConfig)))
217
+ ).resolves.toBe(true);
218
+
219
+ const selectedRelease = DataManager.getFirmwareLatestRelease(
220
+ { deviceType: 'pro2', firmwareVersion: '1.0.0' } as Features,
221
+ EFirmwareType.Universal
222
+ );
223
+ expect(selectedRelease?.version).toEqual([2, 0, 0]);
224
+ expect(DataManager.getProtocolV2ResourceSource(selectedRelease)).toEqual(newerResourceSource);
225
+ });
226
+
227
+ test('does not use a device-level Protocol V2 resource archive', async () => {
228
+ const remoteConfig = createRemoteConfig();
229
+ delete remoteConfig.pro2['firmware-v1']?.[0].resources;
230
+ (remoteConfig.pro2 as unknown as { resources: unknown }).resources = {
231
+ source: resourceSource,
232
+ };
233
+
234
+ await expect(
235
+ DataManager.load(createSettings(jest.fn().mockResolvedValue(remoteConfig)))
236
+ ).resolves.toBe(true);
237
+
238
+ expect(
239
+ DataManager.getProtocolV2ResourceSource(getProtocolV2Release(EDeviceType.Pro2))
240
+ ).toBeUndefined();
241
+ });
242
+
243
+ test('does not block the latest release when an older release has invalid resources', async () => {
244
+ const remoteConfig = createRemoteConfig();
245
+ (remoteConfig.pro2['firmware-v1']?.[0] as { resources?: unknown }).resources = { source: {} };
246
+ remoteConfig.pro2['firmware-v1']?.push({
247
+ ...remoteConfig.pro2['firmware-v1'][0],
248
+ version: [2, 0, 0],
249
+ resources: { source: resourceSource },
250
+ });
251
+ const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
252
+
253
+ await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
254
+ await expect(DataManager.forceReloadData({ requireResources: true })).resolves.toBeUndefined();
255
+
256
+ const latestRelease = DataManager.deviceMap[EDeviceType.Pro2]?.['firmware-v1']?.[1];
257
+ expect(DataManager.getProtocolV2ResourceSource(latestRelease)).toEqual(resourceSource);
258
+ });
259
+
171
260
  test('reuses the configuration fetched during SDK initialization for an immediate update', async () => {
172
261
  const configFetcher = jest.fn().mockResolvedValue(createRemoteConfig());
173
262
  const settings = createSettings(configFetcher);
@@ -180,25 +269,33 @@ describe('Pro2 resource configuration', () => {
180
269
 
181
270
  test('keeps base SDK initialization available when remote Pro2 resources are invalid', async () => {
182
271
  const remoteConfig = createRemoteConfig();
183
- (remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
272
+ (remoteConfig.pro2['firmware-v1']?.[0] as { resources?: unknown }).resources = { source: {} };
184
273
  const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
185
274
 
186
275
  await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
187
276
 
188
- expect(DataManager.getProtocolV2ResourceSource()).toBeUndefined();
277
+ expect(
278
+ DataManager.getProtocolV2ResourceSource(getProtocolV2Release(EDeviceType.Pro2))
279
+ ).toBeUndefined();
189
280
  expect(DataManager.protocolV2ResourcesConfigError).toBeInstanceOf(Error);
190
- expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toEqual(neoResourceSource);
281
+ expect(DataManager.getProtocolV2ResourceSource(getProtocolV2Release(EDeviceType.Neo))).toEqual(
282
+ neoResourceSource
283
+ );
191
284
  });
192
285
 
193
286
  test('keeps Pro2 resources available when remote Neo resources are invalid', async () => {
194
287
  const remoteConfig = createRemoteConfig();
195
- (remoteConfig.neo as { resources?: unknown }).resources = { source: {} };
288
+ (remoteConfig.neo?.['firmware-v1']?.[0] as { resources?: unknown }).resources = { source: {} };
196
289
  const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
197
290
 
198
291
  await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
199
292
 
200
- expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Pro2)).toEqual(resourceSource);
201
- expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toBeUndefined();
293
+ expect(DataManager.getProtocolV2ResourceSource(getProtocolV2Release(EDeviceType.Pro2))).toEqual(
294
+ resourceSource
295
+ );
296
+ expect(
297
+ DataManager.getProtocolV2ResourceSource(getProtocolV2Release(EDeviceType.Neo))
298
+ ).toBeUndefined();
202
299
  await expect(
203
300
  DataManager.forceReloadData({
204
301
  requireResources: true,
@@ -218,7 +315,7 @@ describe('Pro2 resource configuration', () => {
218
315
 
219
316
  test('only blocks resource mutation when the refreshed Pro2 resources are invalid', async () => {
220
317
  const remoteConfig = createRemoteConfig();
221
- (remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
318
+ (remoteConfig.pro2['firmware-v1']?.[0] as { resources?: unknown }).resources = { source: {} };
222
319
  const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
223
320
  DataManager.settings = settings;
224
321
 
@@ -232,7 +329,7 @@ describe('Pro2 resource configuration', () => {
232
329
 
233
330
  test('checks resource configuration errors for the requested device only', async () => {
234
331
  const remoteConfig = createRemoteConfig();
235
- (remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
332
+ (remoteConfig.pro2['firmware-v1']?.[0] as { resources?: unknown }).resources = { source: {} };
236
333
  const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
237
334
  DataManager.settings = settings;
238
335
 
@@ -1,8 +1,20 @@
1
- import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
1
+ import TransportUtils, { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
+ import { encode as encodeJpeg } from 'jpeg-js';
2
3
 
3
4
  import ConfluxSignMessageCIP23 from '../src/api/conflux/ConfluxSignMessageCIP23';
5
+ import DeviceChangePin from '../src/api/device/DeviceChangePin';
4
6
  import DeviceLock from '../src/api/device/DeviceLock';
7
+ import DeviceRebootToBoardloader from '../src/api/device/DeviceRebootToBoardloader';
8
+ import DeviceRebootToBootloader from '../src/api/device/DeviceRebootToBootloader';
9
+ import DeviceSettings from '../src/api/device/DeviceSettings';
10
+ import DeviceVerify from '../src/api/device/DeviceVerify';
11
+ import DeviceWipe from '../src/api/device/DeviceWipe';
12
+ import DeviceReboot from '../src/api/protocol-v2/DeviceReboot';
13
+ import DeviceUploadNft from '../src/api/protocol-v2/DeviceUploadNft';
14
+ import DeviceUploadWallpaper from '../src/api/protocol-v2/DeviceUploadWallpaper';
15
+ import FirmwareUpdateV4 from '../src/api/FirmwareUpdateV4';
5
16
  import OpenWalletSession from '../src/api/OpenWalletSession';
17
+ import DataManager from '../src/data-manager/DataManager';
6
18
  import { runMethodWithUnlockPolicy } from '../src/protocols/protocol-v2/unlockPolicyRunner';
7
19
 
8
20
  jest.mock('../src/data/config', () => ({
@@ -10,7 +22,49 @@ jest.mock('../src/data/config', () => ({
10
22
  getSDKVersion: () => '0.0.0-test',
11
23
  }));
12
24
 
25
+ const createJpegBase64 = (width: number, height: number) => {
26
+ const data = new Uint8Array(width * height * 4);
27
+ for (let index = 3; index < data.length; index += 4) data[index] = 0xff;
28
+ return encodeJpeg({ width, height, data }, 80).data.toString('base64');
29
+ };
30
+
13
31
  describe('Protocol V2 unlock semantics', () => {
32
+ test('serializes genuine-device verification with Protocol V2 certificate messages', async () => {
33
+ const method = new DeviceVerify({
34
+ id: 1,
35
+ payload: { method: 'deviceVerify', dataHex: 'aabb' },
36
+ });
37
+ method.init();
38
+ const protocolV2Messages = TransportUtils.parseConfigure(
39
+ DataManager.getProtobufMessages('v2Schema')
40
+ );
41
+ const typedCall = jest.fn(
42
+ (requestType: string, responseType: string, payload: Record<string, unknown> = {}) => {
43
+ const request = TransportUtils.createMessageFromName(protocolV2Messages, requestType);
44
+ request.Message.encode(request.Message.create(payload)).finish();
45
+ TransportUtils.createMessageFromName(protocolV2Messages, responseType);
46
+ if (requestType === 'DeviceCertificateSign') {
47
+ return Promise.resolve({ message: { data: 'signature' } });
48
+ }
49
+ return Promise.resolve({ message: { cert_and_pubkey: 'certificate' } });
50
+ }
51
+ );
52
+ method.device = {
53
+ getCurrentDeviceType: () => 'pro2',
54
+ isProtocolV2: () => true,
55
+ commands: { typedCall },
56
+ } as any;
57
+
58
+ await expect(method.run()).resolves.toEqual({
59
+ cert: 'certificate',
60
+ signature: 'signature',
61
+ });
62
+ expect(typedCall.mock.calls.map(call => call.slice(0, 2))).toEqual([
63
+ ['DeviceCertificateSign', 'DeviceCertificateSignature'],
64
+ ['DeviceCertificateRead', 'DeviceCertificate'],
65
+ ]);
66
+ });
67
+
14
68
  test('wallet business methods inherit the wallet-session unlock requirement', () => {
15
69
  const method = new ConfluxSignMessageCIP23({
16
70
  id: 1,
@@ -22,6 +76,40 @@ describe('Protocol V2 unlock semantics', () => {
22
76
  expect(method.getSupportedProtocols()).toContain('V2');
23
77
  });
24
78
 
79
+ test.each([
80
+ [
81
+ 'deviceReboot',
82
+ () =>
83
+ new DeviceReboot({
84
+ id: 1,
85
+ payload: { method: 'deviceReboot', rebootType: 2 },
86
+ }),
87
+ ],
88
+ [
89
+ 'deviceRebootToBootloader',
90
+ () =>
91
+ new DeviceRebootToBootloader({
92
+ id: 1,
93
+ payload: { method: 'deviceRebootToBootloader' },
94
+ }),
95
+ ],
96
+ [
97
+ 'deviceRebootToBoardloader',
98
+ () =>
99
+ new DeviceRebootToBoardloader({
100
+ id: 1,
101
+ payload: { method: 'deviceRebootToBoardloader' },
102
+ }),
103
+ ],
104
+ ])('pre-unlocks %s before sending DeviceReboot', (_name, createMethod) => {
105
+ const method = createMethod();
106
+ method.init();
107
+
108
+ expect(method.unlockPolicy).toBe('unlock-before-run');
109
+ expect(method.protocolV2PreUnlockPinType).toBe(DeviceSessionPinType.Any);
110
+ expect(method.useDevicePassphraseState).toBe(false);
111
+ });
112
+
25
113
  test('lock-free Protocol V2 controls explicitly opt out of wallet-session handling', () => {
26
114
  const method = new DeviceLock({
27
115
  id: 1,
@@ -63,8 +151,42 @@ describe('Protocol V2 unlock semantics', () => {
63
151
  });
64
152
 
65
153
  expect(method.unlockPolicy).toBe('unlock-before-run');
66
- expect(device.unlockDevice).toHaveBeenCalledTimes(1);
154
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, expect.any(Object));
67
155
  expect(run).toHaveBeenCalledTimes(1);
156
+ expect(method.protocolV2UnlockContext).toMatchObject({
157
+ preflightCompleted: true,
158
+ preflightStatusRefreshed: true,
159
+ preflightMainPinSelected: true,
160
+ });
161
+ });
162
+
163
+ test('allows either PIN type when pre-unlocking hidden-wallet selection', async () => {
164
+ const method = new OpenWalletSession({
165
+ id: 1,
166
+ payload: { method: 'openWalletSession', mode: 'select-hidden' },
167
+ });
168
+ method.init();
169
+ method.run = jest.fn().mockResolvedValue({ walletType: 'hidden' }) as any;
170
+ const features = { unlocked: false };
171
+ const device = {
172
+ features,
173
+ commands: {
174
+ typedCall: jest.fn().mockResolvedValue({ message: { unlocked: false } }),
175
+ },
176
+ isProtocolV2: () => true,
177
+ isBootloader: () => false,
178
+ isRomloader: () => false,
179
+ updateProtocolV2Status: jest.fn(() => features),
180
+ unlockDevice: jest.fn().mockImplementation(() => {
181
+ features.unlocked = true;
182
+ return Promise.resolve(features);
183
+ }),
184
+ };
185
+
186
+ await runMethodWithUnlockPolicy(method, device as any);
187
+
188
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, expect.any(Object));
189
+ expect(method.protocolV2UnlockContext?.preflightMainPinSelected).toBe(false);
68
190
  });
69
191
 
70
192
  test('pre-unlocks an unregistered wallet business method', async () => {
@@ -160,6 +282,117 @@ describe('Protocol V2 unlock semantics', () => {
160
282
  expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, expect.any(Object));
161
283
  });
162
284
 
285
+ test('allows either PIN type when pre-unlocking device settings', async () => {
286
+ const method = new DeviceSettings({
287
+ id: 1,
288
+ payload: { method: 'deviceSettings', autoLockDelayMs: 60_000 },
289
+ });
290
+ method.init();
291
+ const features = { unlocked: false };
292
+ const device = {
293
+ features,
294
+ commands: {
295
+ typedCall: jest.fn().mockResolvedValue({ message: { unlocked: false } }),
296
+ },
297
+ isProtocolV2: () => true,
298
+ isBootloader: () => false,
299
+ isRomloader: () => false,
300
+ updateProtocolV2Status: jest.fn(() => features),
301
+ unlockDevice: jest.fn().mockImplementation(() => {
302
+ features.unlocked = true;
303
+ return Promise.resolve(features);
304
+ }),
305
+ };
306
+ method.run = jest.fn().mockResolvedValue({ message: 'ok' });
307
+
308
+ await expect(runMethodWithUnlockPolicy(method, device as any)).resolves.toEqual({
309
+ message: 'ok',
310
+ });
311
+
312
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, expect.any(Object));
313
+ });
314
+
315
+ test.each([
316
+ [
317
+ 'PIN changes',
318
+ () =>
319
+ new DeviceChangePin({
320
+ id: 1,
321
+ payload: { method: 'deviceChangePin', remove: false },
322
+ }),
323
+ ],
324
+ ['device wipe', () => new DeviceWipe({ id: 1, payload: { method: 'deviceWipe' } })],
325
+ [
326
+ 'firmware updates',
327
+ () =>
328
+ new FirmwareUpdateV4({
329
+ id: 1,
330
+ payload: { method: 'firmwareUpdateV4', platform: 'desktop' } as any,
331
+ }),
332
+ ],
333
+ [
334
+ 'genuine-device verification',
335
+ () =>
336
+ new DeviceVerify({
337
+ id: 1,
338
+ payload: { method: 'deviceVerify', dataHex: '00' },
339
+ }),
340
+ ],
341
+ [
342
+ 'wallpaper uploads',
343
+ () =>
344
+ new DeviceUploadWallpaper({
345
+ id: 1,
346
+ payload: {
347
+ method: 'deviceUploadWallpaper',
348
+ jpegBase64: createJpegBase64(604, 1024),
349
+ },
350
+ }),
351
+ ],
352
+ [
353
+ 'NFT uploads',
354
+ () =>
355
+ new DeviceUploadNft({
356
+ id: 1,
357
+ payload: {
358
+ method: 'deviceUploadNft',
359
+ imageJpegBase64: createJpegBase64(540, 540),
360
+ thumbnailJpegBase64: createJpegBase64(263, 263),
361
+ title: 'NFT',
362
+ subtitle: '',
363
+ timestampMs: 1,
364
+ },
365
+ }),
366
+ ],
367
+ ])('allows either PIN type when pre-unlocking %s', async (_name, createMethod) => {
368
+ const method = createMethod();
369
+ method.init();
370
+ const features = { unlocked: false };
371
+ const device = {
372
+ features,
373
+ commands: {
374
+ typedCall: jest.fn().mockResolvedValue({ message: { unlocked: false } }),
375
+ },
376
+ isProtocolV2: () => true,
377
+ isBootloader: () => false,
378
+ isRomloader: () => false,
379
+ updateProtocolV2Status: jest.fn(() => features),
380
+ unlockDevice: jest.fn().mockImplementation(() => {
381
+ features.unlocked = true;
382
+ return Promise.resolve(features);
383
+ }),
384
+ };
385
+ method.run = jest.fn().mockResolvedValue({ message: 'ok' });
386
+
387
+ await expect(runMethodWithUnlockPolicy(method, device as any)).resolves.toEqual({
388
+ message: 'ok',
389
+ });
390
+
391
+ expect(method.unlockPolicy).toBe('unlock-before-run');
392
+ expect(method.getSupportedProtocols()).toContain('V2');
393
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, expect.any(Object));
394
+ });
395
+
163
396
  test('pre-unlocks a locked standard wallet before wallet-session preparation', async () => {
164
397
  const calls: string[] = [];
165
398
  const features = {