@onekeyfe/hd-core 1.2.0-alpha.21 → 1.2.0-alpha.23

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 (192) hide show
  1. package/__tests__/DeviceCommands.test.ts +85 -0
  2. package/__tests__/connectSettings.test.ts +47 -5
  3. package/__tests__/device-identity.test.ts +24 -2
  4. package/__tests__/device-settings.test.ts +2 -2
  5. package/__tests__/device-wallet-session-store.test.ts +152 -7
  6. package/__tests__/firmware-update/check-all-firmware-release.test.ts +86 -0
  7. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  8. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  9. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  10. package/__tests__/firmware-update/firmware-update-plan.test.ts +326 -0
  11. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
  12. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +39 -30
  13. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  14. package/__tests__/logBlockEvent.test.ts +17 -0
  15. package/__tests__/open-wallet-session.test.ts +311 -12
  16. package/__tests__/pro2Wallpaper.test.ts +7 -18
  17. package/__tests__/protocol-v2-unlock-policy.test.ts +45 -0
  18. package/__tests__/protocol-v2.test.ts +685 -177
  19. package/__tests__/public-device-state-api.test.ts +69 -62
  20. package/__tests__/public-pro2-api-boundary.test.ts +8 -34
  21. package/dist/api/BaseMethod.d.ts +2 -1
  22. package/dist/api/BaseMethod.d.ts.map +1 -1
  23. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  24. package/dist/api/ClearSessionCache.d.ts.map +1 -1
  25. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  26. package/dist/api/FirmwareUpdateV2.d.ts +11 -0
  27. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  28. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  29. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  30. package/dist/api/FirmwareUpdateV4.d.ts +29 -10
  31. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  32. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  33. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  34. package/dist/api/conflux/ConfluxGetAddress.d.ts +1 -0
  35. package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
  36. package/dist/api/conflux/ConfluxSignMessage.d.ts +1 -0
  37. package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
  38. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +1 -0
  39. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
  40. package/dist/api/conflux/ConfluxSignTransaction.d.ts +1 -0
  41. package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
  42. package/dist/api/device/DeviceUnlock.d.ts +1 -1
  43. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  44. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  45. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  46. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  47. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  48. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  49. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  50. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  51. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +7 -0
  52. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  53. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  54. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  55. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +23 -0
  56. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  57. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +30 -0
  58. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  59. package/dist/api/firmware/getBinary.d.ts +7 -0
  60. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  61. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  62. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  63. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  64. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  65. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  66. package/dist/api/index.d.ts +0 -17
  67. package/dist/api/index.d.ts.map +1 -1
  68. package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
  69. package/dist/api/nexa/NexaSignTransaction.d.ts.map +1 -1
  70. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  71. package/dist/core/index.d.ts.map +1 -1
  72. package/dist/data-manager/DataManager.d.ts +1 -0
  73. package/dist/data-manager/DataManager.d.ts.map +1 -1
  74. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  75. package/dist/device/Device.d.ts +3 -2
  76. package/dist/device/Device.d.ts.map +1 -1
  77. package/dist/device/DeviceCommands.d.ts.map +1 -1
  78. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  79. package/dist/device/DeviceStateProjector.d.ts.map +1 -1
  80. package/dist/events/device.d.ts +1 -0
  81. package/dist/events/device.d.ts.map +1 -1
  82. package/dist/events/logBlockEvent.d.ts.map +1 -1
  83. package/dist/events/ui-request.d.ts +1 -0
  84. package/dist/events/ui-request.d.ts.map +1 -1
  85. package/dist/index.d.ts +273 -230
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +3060 -1676
  88. package/dist/inject.d.ts.map +1 -1
  89. package/dist/lowLevelInject.d.ts.map +1 -1
  90. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  91. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +4 -0
  92. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
  93. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  94. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  95. package/dist/topLevelInject.d.ts.map +1 -1
  96. package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -0
  97. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  98. package/dist/types/api/deviceUnlock.d.ts +2 -2
  99. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  100. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  101. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  102. package/dist/types/api/export.d.ts +4 -1
  103. package/dist/types/api/export.d.ts.map +1 -1
  104. package/dist/types/api/firmwareUpdate.d.ts +69 -0
  105. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  106. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  107. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  108. package/dist/types/api/firmwareUpdatePlan.d.ts +27 -0
  109. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  110. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  111. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  112. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  113. package/dist/types/api/index.d.ts +10 -19
  114. package/dist/types/api/index.d.ts.map +1 -1
  115. package/dist/types/api/openWalletSession.d.ts +1 -0
  116. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  117. package/dist/types/api/protocolV2.d.ts +3 -76
  118. package/dist/types/api/protocolV2.d.ts.map +1 -1
  119. package/dist/types/api/sessionCache.d.ts +4 -1
  120. package/dist/types/api/sessionCache.d.ts.map +1 -1
  121. package/dist/types/settings.d.ts +13 -0
  122. package/dist/types/settings.d.ts.map +1 -1
  123. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  124. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  125. package/dist/utils/patch.d.ts +1 -1
  126. package/dist/utils/patch.d.ts.map +1 -1
  127. package/package.json +4 -4
  128. package/src/api/BaseMethod.ts +8 -3
  129. package/src/api/CheckAllFirmwareRelease.ts +23 -3
  130. package/src/api/ClearSessionCache.ts +4 -0
  131. package/src/api/FirmwareUpdate.ts +8 -1
  132. package/src/api/FirmwareUpdateV2.ts +292 -120
  133. package/src/api/FirmwareUpdateV3.ts +256 -55
  134. package/src/api/FirmwareUpdateV4.ts +821 -331
  135. package/src/api/GetPassphraseState.ts +11 -5
  136. package/src/api/OpenWalletSession.ts +74 -25
  137. package/src/api/UploadPortfolio.ts +1 -1
  138. package/src/api/conflux/ConfluxGetAddress.ts +2 -0
  139. package/src/api/conflux/ConfluxSignMessage.ts +2 -0
  140. package/src/api/conflux/ConfluxSignMessageCIP23.ts +2 -0
  141. package/src/api/conflux/ConfluxSignTransaction.ts +2 -0
  142. package/src/api/device/DeviceChangePin.ts +1 -1
  143. package/src/api/device/DeviceUnlock.ts +1 -2
  144. package/src/api/device/DeviceUpdateBootloader.ts +122 -6
  145. package/src/api/device/DeviceWipe.ts +3 -1
  146. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  147. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  148. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +50 -0
  149. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  150. package/src/api/firmware/FirmwareUpdatePlan.ts +621 -0
  151. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +392 -0
  152. package/src/api/firmware/updateBootloader.ts +19 -4
  153. package/src/api/firmware/uploadFirmware.ts +140 -22
  154. package/src/api/helpers/protocolV2FileWrite.ts +6 -0
  155. package/src/api/index.ts +0 -18
  156. package/src/api/nexa/NexaSignTransaction.ts +2 -0
  157. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  158. package/src/core/index.ts +1 -0
  159. package/src/data/messages/messages-protocol-v2.json +35 -1
  160. package/src/data-manager/DataManager.ts +33 -33
  161. package/src/data-manager/connectSettings.ts +11 -0
  162. package/src/device/Device.ts +80 -67
  163. package/src/device/DeviceCommands.ts +76 -159
  164. package/src/device/DeviceStateMapper.ts +18 -1
  165. package/src/device/DeviceStateProjector.ts +7 -1
  166. package/src/events/device.ts +1 -0
  167. package/src/events/logBlockEvent.ts +1 -14
  168. package/src/events/ui-request.ts +1 -0
  169. package/src/index.ts +5 -0
  170. package/src/inject.ts +22 -25
  171. package/src/lowLevelInject.ts +5 -1
  172. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +2 -3
  173. package/src/protocols/protocol-v2/unlockPolicy.ts +105 -0
  174. package/src/protocols/protocol-v2/unlockRetry.ts +1 -3
  175. package/src/protocols/protocol-v2/walletSession.ts +86 -9
  176. package/src/topLevelInject.ts +5 -1
  177. package/src/types/api/checkAllFirmwareRelease.ts +3 -0
  178. package/src/types/api/deviceUnlock.ts +2 -5
  179. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  180. package/src/types/api/export.ts +18 -0
  181. package/src/types/api/firmwareUpdate.ts +76 -0
  182. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  183. package/src/types/api/firmwareUpdatePlan.ts +36 -0
  184. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  185. package/src/types/api/getPassphraseState.ts +4 -3
  186. package/src/types/api/index.ts +14 -34
  187. package/src/types/api/openWalletSession.ts +2 -0
  188. package/src/types/api/protocolV2.ts +3 -156
  189. package/src/types/api/sessionCache.ts +9 -4
  190. package/src/types/settings.ts +13 -0
  191. package/src/utils/deviceFeaturesUtils.ts +5 -1
  192. package/src/utils/deviceInfoUtils.ts +3 -16
@@ -0,0 +1,70 @@
1
+ import {
2
+ getFirmwareUpdateHostBindingGeneration,
3
+ registerFirmwareUpdateHostBinding,
4
+ resolveFirmwareUpdateHostBinding,
5
+ unregisterFirmwareUpdateHostBinding,
6
+ } from '../../src/api/firmware/FirmwareHostBinding';
7
+
8
+ const createBinding = () => ({
9
+ artifactReader: {
10
+ open: jest.fn(() => Promise.resolve({ readerId: 'reader-1', size: 4 })),
11
+ read: jest.fn(() =>
12
+ Promise.resolve({
13
+ data: new ArrayBuffer(4),
14
+ bytesRead: 4,
15
+ eof: true,
16
+ })
17
+ ),
18
+ close: jest.fn(() => Promise.resolve()),
19
+ },
20
+ });
21
+
22
+ describe('firmware host binding generation', () => {
23
+ afterEach(() => {
24
+ unregisterFirmwareUpdateHostBinding();
25
+ });
26
+
27
+ test('invalidates callbacks from a replaced generation', async () => {
28
+ const staleBinding = createBinding();
29
+ const staleGeneration = registerFirmwareUpdateHostBinding(staleBinding);
30
+ const stale = resolveFirmwareUpdateHostBinding(staleGeneration);
31
+ const activeGeneration = registerFirmwareUpdateHostBinding(createBinding());
32
+
33
+ expect(activeGeneration).toBeGreaterThan(staleGeneration);
34
+ expect(getFirmwareUpdateHostBindingGeneration()).toBe(activeGeneration);
35
+ await expect(
36
+ stale.artifactReader.read({
37
+ readerId: 'reader-1',
38
+ offset: 0,
39
+ length: 4,
40
+ })
41
+ ).rejects.toMatchObject({
42
+ params: {
43
+ firmwareUpdateCode: 'FirmwareArtifactReaderInvalid',
44
+ },
45
+ });
46
+ expect(unregisterFirmwareUpdateHostBinding(staleGeneration)).toBe(false);
47
+ expect(getFirmwareUpdateHostBindingGeneration()).toBe(activeGeneration);
48
+ });
49
+
50
+ test('closes an artifact opened during a generation race', async () => {
51
+ const binding = createBinding();
52
+ binding.artifactReader.open.mockImplementationOnce(() => {
53
+ registerFirmwareUpdateHostBinding(createBinding());
54
+ return Promise.resolve({ readerId: 'stale-reader', size: 4 });
55
+ });
56
+ const generation = registerFirmwareUpdateHostBinding(binding);
57
+ const guarded = resolveFirmwareUpdateHostBinding(generation);
58
+
59
+ await expect(
60
+ guarded.artifactReader.open({ artifactRef: `fw:${'a'.repeat(64)}` })
61
+ ).rejects.toMatchObject({
62
+ params: {
63
+ firmwareUpdateCode: 'FirmwareArtifactReaderInvalid',
64
+ },
65
+ });
66
+ expect(binding.artifactReader.close).toHaveBeenCalledWith({
67
+ readerId: 'stale-reader',
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,13 @@
1
+ import { getFirmwareUpdateCapabilities } from '../../src/api/firmware/FirmwareUpdateCapabilities';
2
+
3
+ describe('getFirmwareUpdateCapabilities', () => {
4
+ it('advertises only the prepared artifact contract', () => {
5
+ expect(getFirmwareUpdateCapabilities()).toEqual({
6
+ planSchemaVersion: 2,
7
+ preparedPlanSchemaVersion: 2,
8
+ hostBindingProtocolVersion: 2,
9
+ manifestModes: ['external-only', 'sdk-managed'],
10
+ supportsArtifactReader: true,
11
+ });
12
+ });
13
+ });
@@ -0,0 +1,326 @@
1
+ import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
+
3
+ import { buildFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePlan';
4
+
5
+ import type { Features } from '../../src/types';
6
+
7
+ const createFeatures = ({
8
+ deviceType,
9
+ firmwareVersion = '1.0.0',
10
+ bootloaderVersion = '1.0.0',
11
+ }: {
12
+ deviceType: EDeviceType;
13
+ firmwareVersion?: string;
14
+ bootloaderVersion?: string;
15
+ }) =>
16
+ ({
17
+ deviceType,
18
+ serialNo: `${deviceType}-device-id`,
19
+ firmwareVersion,
20
+ bootloaderVersion,
21
+ } as Features);
22
+
23
+ const noUpdate = {
24
+ status: 'valid',
25
+ release: undefined,
26
+ };
27
+
28
+ describe('buildFirmwareUpdatePlan', () => {
29
+ test('selects all legacy artifacts before execution and chooses the desktop full resource', () => {
30
+ const plan = buildFirmwareUpdatePlan({
31
+ features: createFeatures({
32
+ deviceType: EDeviceType.Classic1s,
33
+ firmwareVersion: '1.0.0',
34
+ }),
35
+ firmwareType: EFirmwareType.Universal,
36
+ platform: 'desktop',
37
+ firmware: {
38
+ status: 'outdated',
39
+ release: {
40
+ url: 'https://firmware.onekey.so/classic/firmware.bin',
41
+ expectedSize: 1024,
42
+ fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
43
+ version: [3, 0, 0],
44
+ resource: 'https://firmware.onekey.so/classic/resource.zip',
45
+ fullResource: 'https://firmware.onekey.so/classic/full-resource.zip',
46
+ fullResourceExpectedSize: 4096,
47
+ fullResourceFingerprint:
48
+ '2222222222222222222222222222222222222222222222222222222222222222',
49
+ fullResourceRange: ['2.0.0', '3.0.0'],
50
+ },
51
+ },
52
+ ble: {
53
+ status: 'outdated',
54
+ release: {
55
+ webUpdate: 'https://firmware.onekey.so/classic/ble.bin',
56
+ expectedSize: 512,
57
+ fingerprintWeb: '3333333333333333333333333333333333333333333333333333333333333333',
58
+ },
59
+ },
60
+ bootloader: {
61
+ status: 'outdated',
62
+ shouldUpdate: true,
63
+ release: {
64
+ bootloaderResource: 'https://firmware.onekey.so/classic/bootloader.bin',
65
+ bootloaderExpectedSize: 768,
66
+ bootloaderFingerprint: '4444444444444444444444444444444444444444444444444444444444444444',
67
+ bootloaderVersion: [2, 0, 0],
68
+ },
69
+ },
70
+ });
71
+
72
+ expect(plan.executor).toBe('v2');
73
+ expect(plan.artifacts).toEqual([
74
+ expect.objectContaining({
75
+ artifactId: 'bootloader',
76
+ role: 'bootloader',
77
+ expectedSize: 768,
78
+ targetVersion: '2.0.0',
79
+ }),
80
+ expect.objectContaining({
81
+ artifactId: 'firmware',
82
+ role: 'firmware',
83
+ expectedSize: 1024,
84
+ targetVersion: '3.0.0',
85
+ }),
86
+ expect.objectContaining({
87
+ artifactId: 'resource',
88
+ role: 'resource',
89
+ url: 'https://firmware.onekey.so/classic/full-resource.zip',
90
+ expectedSize: 4096,
91
+ }),
92
+ expect.objectContaining({
93
+ artifactId: 'ble',
94
+ role: 'ble',
95
+ expectedSize: 512,
96
+ }),
97
+ ]);
98
+ expect(plan.targetsToUpdate).toEqual(['bootloader', 'firmware', 'resource', 'ble']);
99
+ });
100
+
101
+ test('selects V3 for Pro with bootloader 2.8 or newer', () => {
102
+ const plan = buildFirmwareUpdatePlan({
103
+ features: createFeatures({
104
+ deviceType: EDeviceType.Pro,
105
+ bootloaderVersion: '2.8.0',
106
+ }),
107
+ firmwareType: EFirmwareType.BitcoinOnly,
108
+ platform: 'native',
109
+ firmware: {
110
+ status: 'outdated',
111
+ release: {
112
+ url: 'https://firmware.onekey.so/pro/firmware.bin',
113
+ version: [4, 0, 0],
114
+ },
115
+ },
116
+ ble: noUpdate,
117
+ bootloader: noUpdate,
118
+ });
119
+
120
+ expect(plan.executor).toBe('v3');
121
+ expect(plan.firmwareType).toBe(EFirmwareType.BitcoinOnly);
122
+ expect(plan.platform).toBe('native');
123
+ });
124
+
125
+ test('rejects a prepared native plan without a stable device identity', () => {
126
+ const features = createFeatures({
127
+ deviceType: EDeviceType.Classic1s,
128
+ });
129
+ delete (features as Partial<Features>).serialNo;
130
+
131
+ expect(() =>
132
+ buildFirmwareUpdatePlan({
133
+ features,
134
+ firmwareType: EFirmwareType.Universal,
135
+ platform: 'native',
136
+ firmware: {
137
+ status: 'outdated',
138
+ release: {
139
+ url: 'https://firmware.onekey.so/classic/firmware.bin',
140
+ },
141
+ },
142
+ ble: noUpdate,
143
+ bootloader: noUpdate,
144
+ })
145
+ ).toThrow('requires a stable device identity');
146
+ });
147
+
148
+ test('maps the Pro2 component order and resource bundles without downloading them', () => {
149
+ const plan = buildFirmwareUpdatePlan({
150
+ features: createFeatures({ deviceType: EDeviceType.Pro2 }),
151
+ firmwareType: EFirmwareType.Universal,
152
+ platform: 'desktop',
153
+ firmware: {
154
+ status: 'required',
155
+ release: {
156
+ installOrder: ['bootloader', 'applicationP1'],
157
+ components: {
158
+ applicationP1: {
159
+ target: 'APPLICATION_P1',
160
+ url: 'https://firmware.onekey.so/pro2/application-p1.bin',
161
+ expectedSize: 1024,
162
+ fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
163
+ },
164
+ bootloader: {
165
+ target: 'BOOTLOADER',
166
+ url: 'https://firmware.onekey.so/pro2/bootloader.bin',
167
+ },
168
+ se01: {
169
+ target: 'SE01',
170
+ url: 'https://firmware.onekey.so/pro2/se01.bin',
171
+ },
172
+ },
173
+ resourceBundles: [
174
+ {
175
+ name: 'images',
176
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
177
+ },
178
+ ],
179
+ },
180
+ },
181
+ ble: noUpdate,
182
+ bootloader: noUpdate,
183
+ });
184
+
185
+ expect(plan.executor).toBe('v4');
186
+ expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual([
187
+ 'component:boot',
188
+ 'component:app_v1',
189
+ 'component:se01',
190
+ 'resourceBundle:images',
191
+ ]);
192
+ expect(plan.targetsToUpdate).toEqual(['boot', 'app_v1', 'se01', 'resource']);
193
+ expect(plan.artifacts[1]).toEqual(
194
+ expect.objectContaining({
195
+ expectedSize: 1024,
196
+ expectedSha256: '1111111111111111111111111111111111111111111111111111111111111111',
197
+ })
198
+ );
199
+ });
200
+
201
+ test('rejects Pro2 ROMLOADER because it requires a separate loader flow', () => {
202
+ expect(() =>
203
+ buildFirmwareUpdatePlan({
204
+ features: createFeatures({ deviceType: EDeviceType.Pro2 }),
205
+ firmwareType: EFirmwareType.Universal,
206
+ platform: 'desktop',
207
+ firmware: {
208
+ status: 'required',
209
+ release: {
210
+ components: {
211
+ romloader: {
212
+ target: 'ROMLOADER',
213
+ url: 'https://firmware.onekey.so/pro2/romloader.bin',
214
+ },
215
+ },
216
+ },
217
+ },
218
+ ble: noUpdate,
219
+ bootloader: noUpdate,
220
+ })
221
+ ).toThrow('ROMLOADER requires its dedicated loader flow');
222
+ });
223
+
224
+ test.each([
225
+ {
226
+ label: 'component target',
227
+ release: {
228
+ components: {
229
+ application: {
230
+ target: 'APPLICATION_P1',
231
+ url: 'https://firmware.onekey.so/pro2/application.bin',
232
+ },
233
+ applicationDuplicate: {
234
+ target: 'APPLICATION_P1',
235
+ url: 'https://firmware.onekey.so/pro2/application-duplicate.bin',
236
+ },
237
+ },
238
+ },
239
+ expectedError: 'duplicates target app_v1',
240
+ },
241
+ {
242
+ label: 'resource bundle name',
243
+ release: {
244
+ components: {
245
+ application: {
246
+ target: 'APPLICATION_P1',
247
+ url: 'https://firmware.onekey.so/pro2/application.bin',
248
+ },
249
+ },
250
+ resourceBundles: [
251
+ {
252
+ name: 'images',
253
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
254
+ },
255
+ {
256
+ name: 'images',
257
+ url: 'https://firmware.onekey.so/pro2/images-duplicate.okpkg',
258
+ },
259
+ ],
260
+ },
261
+ expectedError: 'duplicates name images',
262
+ },
263
+ ])('rejects a duplicate Pro2 $label', ({ release, expectedError }) => {
264
+ expect(() =>
265
+ buildFirmwareUpdatePlan({
266
+ features: createFeatures({ deviceType: EDeviceType.Pro2 }),
267
+ firmwareType: EFirmwareType.Universal,
268
+ platform: 'desktop',
269
+ firmware: {
270
+ status: 'required',
271
+ release,
272
+ },
273
+ ble: noUpdate,
274
+ bootloader: noUpdate,
275
+ })
276
+ ).toThrow(expectedError);
277
+ });
278
+
279
+ test('produces a deterministic digest and binds it to the platform', () => {
280
+ const input = {
281
+ features: createFeatures({ deviceType: EDeviceType.Classic1s }),
282
+ firmwareType: EFirmwareType.Universal,
283
+ firmware: noUpdate,
284
+ ble: noUpdate,
285
+ bootloader: noUpdate,
286
+ };
287
+ const first = buildFirmwareUpdatePlan({ ...input, platform: 'native' });
288
+ const second = buildFirmwareUpdatePlan({ ...input, platform: 'native' });
289
+ const desktop = buildFirmwareUpdatePlan({ ...input, platform: 'desktop' });
290
+
291
+ expect(first.planDigest).toMatch(/^[a-f0-9]{64}$/);
292
+ expect(first.planDigest).toBe(second.planDigest);
293
+ expect(first.planDigest).not.toBe(desktop.planDigest);
294
+ });
295
+
296
+ test('binds trusted artifact integrity metadata into the plan digest', () => {
297
+ const createPlan = (fingerprint: string) =>
298
+ buildFirmwareUpdatePlan({
299
+ features: createFeatures({ deviceType: EDeviceType.Classic1s }),
300
+ firmwareType: EFirmwareType.Universal,
301
+ platform: 'native',
302
+ firmware: {
303
+ status: 'outdated',
304
+ release: {
305
+ url: 'https://firmware.onekey.so/classic/firmware.bin',
306
+ version: [3, 0, 0],
307
+ expectedSize: 1024,
308
+ fingerprint,
309
+ },
310
+ },
311
+ ble: noUpdate,
312
+ bootloader: noUpdate,
313
+ });
314
+
315
+ const first = createPlan('1111111111111111111111111111111111111111111111111111111111111111');
316
+ const second = createPlan('2222222222222222222222222222222222222222222222222222222222222222');
317
+
318
+ expect(first.artifacts[0]).toEqual(
319
+ expect.objectContaining({
320
+ expectedSize: 1024,
321
+ expectedSha256: '1111111111111111111111111111111111111111111111111111111111111111',
322
+ })
323
+ );
324
+ expect(first.planDigest).not.toBe(second.planDigest);
325
+ });
326
+ });
@@ -0,0 +1,213 @@
1
+ import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
+
3
+ import { buildFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePlan';
4
+ import {
5
+ assertFirmwareUpdatePreparedPlanDeviceIdentity,
6
+ assertFirmwareUpdatePreparedPlanBinding,
7
+ prepareFirmwareUpdatePlan,
8
+ validateFirmwareUpdatePreparedPlan,
9
+ } from '../../src/api/firmware/FirmwareUpdatePreparedPlan';
10
+
11
+ import type { Features } from '../../src/types';
12
+
13
+ const buildPlan = () =>
14
+ buildFirmwareUpdatePlan({
15
+ features: {
16
+ deviceType: EDeviceType.Classic1s,
17
+ serialNo: 'classic1s-device-id',
18
+ firmwareVersion: '1.0.0',
19
+ bootloaderVersion: '1.0.0',
20
+ } as Features,
21
+ firmwareType: EFirmwareType.Universal,
22
+ platform: 'native',
23
+ firmware: {
24
+ status: 'outdated',
25
+ release: {
26
+ url: 'https://firmware.onekey.so/classic/firmware.bin',
27
+ expectedSize: 4,
28
+ fingerprint: 'a'.repeat(64),
29
+ version: [3, 0, 0],
30
+ resource: 'https://firmware.onekey.so/classic/resource.zip',
31
+ resourceExpectedSize: 8,
32
+ resourceFingerprint: 'b'.repeat(64),
33
+ },
34
+ },
35
+ ble: {
36
+ status: 'valid',
37
+ release: undefined,
38
+ },
39
+ bootloader: {
40
+ status: 'valid',
41
+ release: undefined,
42
+ },
43
+ });
44
+
45
+ const artifact = (sha256: string, size: number) => ({
46
+ artifactRef: `fw:${sha256}`,
47
+ size,
48
+ sha256,
49
+ });
50
+
51
+ describe('FirmwareUpdatePreparedPlan', () => {
52
+ test('binds all receipts into a URL-free, network-forbidden plan', () => {
53
+ const plan = buildPlan();
54
+ const prepared = prepareFirmwareUpdatePlan({
55
+ plan,
56
+ leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
57
+ artifacts: [
58
+ {
59
+ artifactId: 'firmware',
60
+ artifact: artifact('a'.repeat(64), 4),
61
+ },
62
+ {
63
+ artifactId: 'resource',
64
+ artifact: artifact('b'.repeat(64), 8),
65
+ materializedEntries: [
66
+ {
67
+ entryName: 'resource/index.bin',
68
+ artifact: artifact('c'.repeat(64), 2),
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ });
74
+
75
+ expect(prepared.networkPolicy).toBe('forbid');
76
+ expect(prepared.planDigest).toBe(plan.planDigest);
77
+ expect(prepared.preparedPlanDigest).toMatch(/^[a-f0-9]{64}$/u);
78
+ expect(JSON.stringify(prepared)).not.toContain('https://');
79
+ expect(validateFirmwareUpdatePreparedPlan(prepared)).toEqual(prepared);
80
+ expect(
81
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
82
+ preparedPlan: prepared,
83
+ deviceIdentity: plan.deviceIdentity,
84
+ })
85
+ ).toEqual(prepared);
86
+ expect(() =>
87
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
88
+ preparedPlan: prepared,
89
+ deviceIdentity: 'different-device-id',
90
+ })
91
+ ).toThrow();
92
+ expect(
93
+ assertFirmwareUpdatePreparedPlanBinding({
94
+ preparedPlan: prepared,
95
+ executor: 'v2',
96
+ platform: 'native',
97
+ scopeTargets: ['firmware', 'resource'],
98
+ bindings: [
99
+ {
100
+ target: 'firmware',
101
+ artifact: artifact('a'.repeat(64), 4),
102
+ },
103
+ {
104
+ target: 'resource',
105
+ entryName: 'resource/index.bin',
106
+ artifact: artifact('c'.repeat(64), 2),
107
+ },
108
+ ],
109
+ })
110
+ ).toEqual(prepared);
111
+ expect(() =>
112
+ assertFirmwareUpdatePreparedPlanBinding({
113
+ preparedPlan: prepared,
114
+ executor: 'v2',
115
+ platform: 'native',
116
+ scopeTargets: ['firmware'],
117
+ bindings: [
118
+ {
119
+ target: 'firmware',
120
+ artifact: artifact('b'.repeat(64), 8),
121
+ },
122
+ ],
123
+ })
124
+ ).toThrow('artifact binding is invalid');
125
+ });
126
+
127
+ test('rejects receipt drift and a tampered update plan digest', () => {
128
+ const plan = buildPlan();
129
+ expect(() =>
130
+ prepareFirmwareUpdatePlan({
131
+ plan,
132
+ leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
133
+ artifacts: [
134
+ {
135
+ artifactId: 'firmware',
136
+ artifact: artifact('d'.repeat(64), 4),
137
+ },
138
+ {
139
+ artifactId: 'resource',
140
+ artifact: artifact('b'.repeat(64), 8),
141
+ materializedEntries: [
142
+ {
143
+ entryName: 'resource/index.bin',
144
+ artifact: artifact('c'.repeat(64), 2),
145
+ },
146
+ ],
147
+ },
148
+ ],
149
+ })
150
+ ).toThrow('receipt does not match');
151
+
152
+ expect(() =>
153
+ prepareFirmwareUpdatePlan({
154
+ plan: { ...plan, planDigest: 'f'.repeat(64) },
155
+ leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
156
+ artifacts: [],
157
+ })
158
+ ).toThrow('plan digest is invalid');
159
+ });
160
+
161
+ test('projects portable archive paths to unique device resource names', () => {
162
+ const plan = buildPlan();
163
+ const buildArtifacts = (entryNames: string[]) => [
164
+ {
165
+ artifactId: 'firmware',
166
+ artifact: artifact('a'.repeat(64), 4),
167
+ },
168
+ {
169
+ artifactId: 'resource',
170
+ artifact: artifact('b'.repeat(64), 8),
171
+ materializedEntries: entryNames.map((entryName, index) => ({
172
+ entryName,
173
+ artifact: artifact(String(index + 1).repeat(64), 2),
174
+ })),
175
+ },
176
+ ];
177
+
178
+ expect(() =>
179
+ prepareFirmwareUpdatePlan({
180
+ plan,
181
+ leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
182
+ artifacts: buildArtifacts(['icons/index.bin', 'fonts/index.bin']),
183
+ })
184
+ ).toThrow('duplicate entry names');
185
+ expect(() =>
186
+ prepareFirmwareUpdatePlan({
187
+ plan,
188
+ leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
189
+ artifacts: buildArtifacts(['../index.bin']),
190
+ })
191
+ ).toThrow('entry name is invalid');
192
+ });
193
+
194
+ test('rejects unknown fields even when a caller presents a plan-shaped object', () => {
195
+ const plan = buildPlan();
196
+ expect(() =>
197
+ prepareFirmwareUpdatePlan({
198
+ plan: {
199
+ ...plan,
200
+ artifacts: [
201
+ {
202
+ ...plan.artifacts[0],
203
+ unexpectedUrl: 'https://attacker.invalid/payload',
204
+ },
205
+ plan.artifacts[1],
206
+ ],
207
+ } as typeof plan,
208
+ leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
209
+ artifacts: [],
210
+ })
211
+ ).toThrow('invalid field set');
212
+ });
213
+ });