@onekeyfe/hd-core 1.2.0-alpha.33 → 1.2.0-alpha.35
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.
- package/__tests__/DeviceCommands.test.ts +84 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +31 -0
- package/__tests__/core-dispose.test.ts +3 -3
- package/__tests__/device-lifecycle-events.test.ts +52 -0
- package/__tests__/device-pool-state.test.ts +14 -2
- package/__tests__/device-settings.test.ts +25 -0
- package/__tests__/device-state-mapper.test.ts +13 -2
- package/__tests__/device-state-projector.test.ts +2 -2
- package/__tests__/device-utils.test.ts +1 -0
- package/__tests__/device-wallet-session-store.test.ts +137 -17
- package/__tests__/evmSignTransaction.test.ts +69 -0
- package/__tests__/get-device-state.test.ts +78 -30
- package/__tests__/kaspaSignTransaction.test.ts +9 -0
- package/__tests__/known-device-public-types.type-test.ts +3 -0
- package/__tests__/logBlockEvent.test.ts +89 -5
- package/__tests__/open-wallet-session.test.ts +499 -60
- package/__tests__/pro2Wallpaper.test.ts +1 -1
- package/__tests__/protocol-v2.test.ts +1180 -169
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +2 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/evm/protocol.d.ts +3 -0
- package/dist/api/evm/protocol.d.ts.map +1 -0
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +10 -4
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts +1 -0
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/device/DeviceStateProjector.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +7 -0
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts +7 -0
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -0
- package/dist/events/logBlockEvent.d.ts +2 -0
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +25 -11
- package/dist/index.js +1037 -838
- package/dist/protocols/protocol-v2/features.d.ts +17 -6
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +1 -0
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +1 -10
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +2 -2
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +6 -3
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +0 -5
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +11 -81
- package/src/api/FirmwareUpdateV4.ts +59 -25
- package/src/api/GetPassphraseState.ts +11 -2
- package/src/api/OpenWalletSession.ts +16 -8
- package/src/api/device/DeviceSettings.ts +8 -1
- package/src/api/evm/EVMGetAddress.ts +2 -2
- package/src/api/evm/EVMGetPublicKey.ts +2 -2
- package/src/api/evm/EVMSignMessage.ts +2 -2
- package/src/api/evm/EVMSignTransaction.ts +2 -2
- package/src/api/evm/EVMSignTypedData.ts +35 -45
- package/src/api/evm/EVMVerifyMessage.ts +2 -2
- package/src/api/evm/protocol.ts +6 -0
- package/src/api/kaspa/KaspaSignTransaction.ts +7 -0
- package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
- package/src/core/index.ts +7 -5
- package/src/data/messages/messages-protocol-v2.json +393 -485
- package/src/device/Device.ts +98 -44
- package/src/device/DeviceCommands.ts +12 -2
- package/src/device/DeviceConnector.ts +10 -7
- package/src/device/DevicePool.ts +5 -3
- package/src/device/DeviceStateMapper.ts +5 -3
- package/src/device/DeviceStateProjector.ts +0 -7
- package/src/device/DeviceWalletSessionStore.ts +43 -4
- package/src/deviceProfile/buildDeviceFeatures.ts +11 -4
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +32 -0
- package/src/events/logBlockEvent.ts +84 -1
- package/src/protocols/protocol-v2/deviceSession.ts +1 -1
- package/src/protocols/protocol-v2/features.ts +58 -16
- package/src/protocols/protocol-v2/index.ts +5 -0
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
- package/src/protocols/protocol-v2/unlockRetry.ts +18 -7
- package/src/protocols/protocol-v2/walletSession.ts +144 -62
- package/src/types/api/protocolV2.ts +2 -2
- package/src/types/device.ts +8 -2
- package/src/types/params.ts +2 -1
- package/src/utils/deviceFeaturesCompat.ts +8 -1
- package/src/utils/pro2Wallpaper.ts +3 -9
- package/tsconfig.type-tests.json +5 -1
|
@@ -1,10 +1,68 @@
|
|
|
1
|
+
import { UI_REQUEST } from './ui-request';
|
|
1
2
|
import { UI_RESPONSE } from './ui-response';
|
|
2
3
|
|
|
3
4
|
export const LogBlockEvent: Set<string> = new Set([
|
|
5
|
+
UI_REQUEST.REQUEST_PASSPHRASE,
|
|
6
|
+
UI_REQUEST.REQUEST_PASSPHRASE_ON_DEVICE,
|
|
4
7
|
UI_RESPONSE.RECEIVE_PIN,
|
|
5
8
|
UI_RESPONSE.RECEIVE_PASSPHRASE,
|
|
6
9
|
]);
|
|
7
10
|
|
|
11
|
+
const LogLabelMethod: Set<string> = new Set([
|
|
12
|
+
'openWalletSession',
|
|
13
|
+
'deviceUploadWallpaper',
|
|
14
|
+
'uploadPortfolio',
|
|
15
|
+
'fileWrite',
|
|
16
|
+
'fileRead',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const SensitiveLogKeys: Set<string> = new Set([
|
|
20
|
+
'devicestate',
|
|
21
|
+
'entropy',
|
|
22
|
+
'expectedpassphrasestate',
|
|
23
|
+
'mnemonic',
|
|
24
|
+
'passphrase',
|
|
25
|
+
'passphrasestate',
|
|
26
|
+
'password',
|
|
27
|
+
'pin',
|
|
28
|
+
'privatekey',
|
|
29
|
+
'seed',
|
|
30
|
+
'session',
|
|
31
|
+
'sessionid',
|
|
32
|
+
'walletsessionid',
|
|
33
|
+
'xprv',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const normalizeLogKey = (key: string) => key.replace(/[_-]/g, '').toLowerCase();
|
|
37
|
+
|
|
38
|
+
const isSigningMethod = (methodName: string) => /sign/i.test(methodName);
|
|
39
|
+
|
|
40
|
+
const redactLogValue = (value: unknown, seen: WeakSet<object>): unknown => {
|
|
41
|
+
if (ArrayBuffer.isView(value)) {
|
|
42
|
+
return `[BINARY:${value.byteLength}]`;
|
|
43
|
+
}
|
|
44
|
+
if (value instanceof ArrayBuffer) {
|
|
45
|
+
return `[BINARY:${value.byteLength}]`;
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
return value.map(item => redactLogValue(item, seen));
|
|
49
|
+
}
|
|
50
|
+
if (!value || typeof value !== 'object') return value;
|
|
51
|
+
if (seen.has(value)) return '[CIRCULAR]';
|
|
52
|
+
|
|
53
|
+
seen.add(value);
|
|
54
|
+
const redacted = Object.fromEntries(
|
|
55
|
+
Object.entries(value as Record<string, unknown>).map(([key, item]) => [
|
|
56
|
+
key,
|
|
57
|
+
SensitiveLogKeys.has(normalizeLogKey(key)) && item !== null && item !== undefined
|
|
58
|
+
? '[REDACTED]'
|
|
59
|
+
: redactLogValue(item, seen),
|
|
60
|
+
])
|
|
61
|
+
);
|
|
62
|
+
seen.delete(value);
|
|
63
|
+
return redacted;
|
|
64
|
+
};
|
|
65
|
+
|
|
8
66
|
export function getLogBlockLabel(message: unknown): string | undefined {
|
|
9
67
|
if (!message || typeof message !== 'object') return undefined;
|
|
10
68
|
|
|
@@ -19,5 +77,30 @@ export function getLogBlockLabel(message: unknown): string | undefined {
|
|
|
19
77
|
}
|
|
20
78
|
|
|
21
79
|
const methodName = method ?? payload?.method;
|
|
22
|
-
|
|
80
|
+
if (methodName && (LogLabelMethod.has(methodName) || isSigningMethod(methodName))) {
|
|
81
|
+
return methodName;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getSafeLogPayload(value: unknown, blockLabel?: string): unknown {
|
|
88
|
+
if (blockLabel && (LogBlockEvent.has(blockLabel) || isSigningMethod(blockLabel))) {
|
|
89
|
+
return { method: blockLabel, payload: '[REDACTED]' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const redactedValue = redactLogValue(value, new WeakSet());
|
|
93
|
+
if (
|
|
94
|
+
blockLabel &&
|
|
95
|
+
redactedValue &&
|
|
96
|
+
typeof redactedValue === 'object' &&
|
|
97
|
+
!Array.isArray(redactedValue)
|
|
98
|
+
) {
|
|
99
|
+
return { ...redactedValue, method: blockLabel };
|
|
100
|
+
}
|
|
101
|
+
return redactedValue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function formatLogMethodLabel(label: string, methodName?: string): string {
|
|
105
|
+
return methodName ? `${label} [${methodName}]` : label;
|
|
23
106
|
}
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
DeviceInfoGet,
|
|
5
5
|
DeviceSEInfo,
|
|
6
6
|
DeviceStatus,
|
|
7
|
+
ProtocolInfo,
|
|
7
8
|
ProtocolV2DeviceInfo,
|
|
8
9
|
} from '@onekeyfe/hd-transport';
|
|
9
10
|
import type { DeviceCommands } from '../../device/DeviceCommands';
|
|
@@ -53,27 +54,54 @@ export const getProtocolV2SeState = (se?: DeviceSEInfo): ProtocolV2SeStateLabel
|
|
|
53
54
|
export const getProtocolV2SeType = (se?: DeviceSEInfo): string | null =>
|
|
54
55
|
normalizeEnumValue(DeviceSeType, se?.type);
|
|
55
56
|
|
|
56
|
-
export type ProtocolV2RuntimeMode = 'normal' | 'bootloader';
|
|
57
|
+
export type ProtocolV2RuntimeMode = 'normal' | 'bootloader' | 'romloader';
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
|
-
*
|
|
60
|
-
* binary
|
|
61
|
-
* is a separate recovery flow and is excluded until its contract is defined.
|
|
62
|
-
*
|
|
63
|
-
* The temporary contract has two states: DeviceStatusGet success means normal and
|
|
64
|
-
* failure means bootloader. Do not infer mode from application/SE/romloader fields.
|
|
60
|
+
* Protocol V2 fingerprints use:
|
|
61
|
+
* <binary>__<version>__<commit>__<PROD|DEV>__<DEBUG|RELEASE>
|
|
65
62
|
*/
|
|
66
|
-
export
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}): ProtocolV2RuntimeMode => {
|
|
73
|
-
if (deviceStatusAvailable) return 'normal';
|
|
74
|
-
return 'bootloader';
|
|
63
|
+
export type ProtocolV2BuildFingerprint = {
|
|
64
|
+
binary: 'application' | 'bootloader' | 'romloader';
|
|
65
|
+
version: string;
|
|
66
|
+
commit: string;
|
|
67
|
+
environment: 'PROD' | 'DEV';
|
|
68
|
+
buildType: 'DEBUG' | 'RELEASE';
|
|
75
69
|
};
|
|
76
70
|
|
|
71
|
+
export const parseProtocolV2BuildFingerprint = (
|
|
72
|
+
buildFingerprint: string | null | undefined
|
|
73
|
+
): ProtocolV2BuildFingerprint | null => {
|
|
74
|
+
if (!buildFingerprint) return null;
|
|
75
|
+
const [binary, version, commit, environment, buildType, ...extra] = buildFingerprint.split('__');
|
|
76
|
+
if (
|
|
77
|
+
extra.length > 0 ||
|
|
78
|
+
(binary !== 'application' && binary !== 'bootloader' && binary !== 'romloader') ||
|
|
79
|
+
!version ||
|
|
80
|
+
!commit ||
|
|
81
|
+
(environment !== 'PROD' && environment !== 'DEV') ||
|
|
82
|
+
(buildType !== 'DEBUG' && buildType !== 'RELEASE')
|
|
83
|
+
) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return { binary, version, commit, environment, buildType };
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const getProtocolV2RuntimeMode = (
|
|
90
|
+
protocolInfo: ProtocolInfo
|
|
91
|
+
): ProtocolV2RuntimeMode | undefined => {
|
|
92
|
+
const binary = parseProtocolV2BuildFingerprint(protocolInfo.build_fingerprint)?.binary;
|
|
93
|
+
if (binary === 'application') return 'normal';
|
|
94
|
+
return binary;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// MessageType_DeviceStatusGet in the Protocol V2 protobuf registry.
|
|
98
|
+
export const PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE = 60602;
|
|
99
|
+
|
|
100
|
+
export const supportsProtocolV2Message = (
|
|
101
|
+
protocolInfo: ProtocolInfo,
|
|
102
|
+
messageType: number
|
|
103
|
+
): boolean => protocolInfo.supported_messages.includes(messageType);
|
|
104
|
+
|
|
77
105
|
export const PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST = {
|
|
78
106
|
targets: {
|
|
79
107
|
hw: true,
|
|
@@ -131,6 +159,20 @@ export const PROTOCOL_V2_FULL_DEVICE_INFO_REQUEST = {
|
|
|
131
159
|
export const PROTOCOL_V2_DEVICE_INFO_REQUEST = PROTOCOL_V2_FULL_DEVICE_INFO_REQUEST;
|
|
132
160
|
export const PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS = 10 * 1000;
|
|
133
161
|
|
|
162
|
+
export async function requestProtocolV2ProtocolInfo({
|
|
163
|
+
commands,
|
|
164
|
+
timeoutMs,
|
|
165
|
+
}: {
|
|
166
|
+
commands: DeviceCommands;
|
|
167
|
+
timeoutMs?: number;
|
|
168
|
+
}): Promise<ProtocolInfo> {
|
|
169
|
+
const response =
|
|
170
|
+
timeoutMs === undefined
|
|
171
|
+
? await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {})
|
|
172
|
+
: await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {}, { timeoutMs });
|
|
173
|
+
return response.message;
|
|
174
|
+
}
|
|
175
|
+
|
|
134
176
|
export async function requestProtocolV2DeviceInfo({
|
|
135
177
|
commands,
|
|
136
178
|
timeoutMs = PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
export {
|
|
2
2
|
PROTOCOL_V2_DEVICE_INFO_REQUEST,
|
|
3
3
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
4
|
+
PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE,
|
|
4
5
|
PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST,
|
|
5
6
|
PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
6
7
|
getProtocolV2RuntimeMode,
|
|
7
8
|
getProtocolV2SeState,
|
|
8
9
|
getProtocolV2SeType,
|
|
10
|
+
parseProtocolV2BuildFingerprint,
|
|
11
|
+
requestProtocolV2ProtocolInfo,
|
|
12
|
+
supportsProtocolV2Message,
|
|
9
13
|
} from './features';
|
|
10
14
|
export type {
|
|
15
|
+
ProtocolV2BuildFingerprint,
|
|
11
16
|
ProtocolV2DeviceInfo,
|
|
12
17
|
ProtocolV2FirmwareImageInfo,
|
|
13
18
|
ProtocolV2SEInfo,
|
|
@@ -2,14 +2,19 @@ import type { DeviceSettings, DeviceSettingsPage } from '@onekeyfe/hd-transport'
|
|
|
2
2
|
import type { UnlockPolicy } from '../../api/BaseMethod';
|
|
3
3
|
import type { ProtocolV2InteractionDescriptor } from './uiInteraction';
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
export const PROTOCOL_V2_LOCK_FREE_DEVICE_SETTINGS = [
|
|
6
6
|
'language',
|
|
7
7
|
'brightness',
|
|
8
|
+
'animation_enable',
|
|
9
|
+
'tap_to_wake',
|
|
8
10
|
'haptic_feedback',
|
|
9
|
-
|
|
11
|
+
'device_name_display_enabled',
|
|
12
|
+
] as const satisfies readonly (keyof DeviceSettings)[];
|
|
13
|
+
|
|
14
|
+
const lockFreeDeviceSettings = new Set<keyof DeviceSettings>(PROTOCOL_V2_LOCK_FREE_DEVICE_SETTINGS);
|
|
10
15
|
|
|
11
16
|
export const getProtocolV2SettingsUnlockPolicy = (settings: DeviceSettings): UnlockPolicy =>
|
|
12
|
-
Object.keys(settings).every(key =>
|
|
17
|
+
Object.keys(settings).every(key => lockFreeDeviceSettings.has(key as keyof DeviceSettings))
|
|
13
18
|
? 'none'
|
|
14
19
|
: 'unlock-before-run';
|
|
15
20
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LoggerNames, getLogger } from '../../utils';
|
|
2
2
|
import { isDeviceLockedError } from './lockedError';
|
|
3
3
|
import { isProtocolV2UiEnabled, resolveProtocolV2UiInteraction } from './uiInteraction';
|
|
4
|
-
import { restoreProtocolV2WalletSession } from './walletSession';
|
|
4
|
+
import { refreshProtocolV2DeviceStatus, restoreProtocolV2WalletSession } from './walletSession';
|
|
5
5
|
|
|
6
6
|
import type { BaseMethod } from '../../api/BaseMethod';
|
|
7
7
|
import type { Device } from '../../device/Device';
|
|
@@ -46,8 +46,23 @@ export async function runMethodWithUnlockRetry(
|
|
|
46
46
|
uiCoordinator?: UiInteractionCoordinator
|
|
47
47
|
) {
|
|
48
48
|
const shouldEmitUi = isProtocolV2UiEnabled(method);
|
|
49
|
+
const isProtocolV2 = device.isProtocolV2();
|
|
50
|
+
const requiresFreshStatus =
|
|
51
|
+
isProtocolV2 &&
|
|
52
|
+
method.unlockPolicy === 'unlock-before-run' &&
|
|
53
|
+
!device.isBootloader?.() &&
|
|
54
|
+
!device.isRomloader?.();
|
|
55
|
+
|
|
56
|
+
if (requiresFreshStatus) {
|
|
57
|
+
await refreshProtocolV2DeviceStatus(device);
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
const shouldUnlockBeforeRun =
|
|
50
|
-
|
|
61
|
+
isProtocolV2 &&
|
|
62
|
+
method.unlockPolicy !== 'none' &&
|
|
63
|
+
!device.isBootloader?.() &&
|
|
64
|
+
!device.isRomloader?.() &&
|
|
65
|
+
device.features?.unlocked === false;
|
|
51
66
|
|
|
52
67
|
if (shouldUnlockBeforeRun) {
|
|
53
68
|
if (shouldEmitUi) {
|
|
@@ -68,11 +83,7 @@ export async function runMethodWithUnlockRetry(
|
|
|
68
83
|
try {
|
|
69
84
|
return await method.run();
|
|
70
85
|
} catch (error) {
|
|
71
|
-
if (
|
|
72
|
-
!device.isProtocolV2() ||
|
|
73
|
-
method.unlockPolicy !== 'retry-on-locked' ||
|
|
74
|
-
!isDeviceLockedError(error)
|
|
75
|
-
) {
|
|
86
|
+
if (!isProtocolV2 || method.unlockPolicy !== 'retry-on-locked' || !isDeviceLockedError(error)) {
|
|
76
87
|
throw error;
|
|
77
88
|
}
|
|
78
89
|
|
|
@@ -3,11 +3,31 @@ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
|
3
3
|
|
|
4
4
|
import { DEVICE } from '../../events';
|
|
5
5
|
import { assertCompleteDeviceSession } from './deviceSession';
|
|
6
|
-
import { isDeviceLockedError } from './lockedError';
|
|
7
6
|
|
|
8
|
-
import type { DeviceSessionGet } from '@onekeyfe/hd-transport';
|
|
7
|
+
import type { DeviceSessionAskPassphrase, DeviceSessionGet } from '@onekeyfe/hd-transport';
|
|
9
8
|
import type { Device } from '../../device/Device';
|
|
10
9
|
|
|
10
|
+
const HOST_PASSPHRASE_MAX_BYTES = 50;
|
|
11
|
+
|
|
12
|
+
const isWalletSessionInvalidError = (error: unknown) =>
|
|
13
|
+
typeof error === 'object' &&
|
|
14
|
+
error !== null &&
|
|
15
|
+
'errorCode' in error &&
|
|
16
|
+
(error as { errorCode?: unknown }).errorCode === HardwareErrorCode.WalletSessionInvalid;
|
|
17
|
+
|
|
18
|
+
const utf8ByteLength = (value: string): number | undefined => {
|
|
19
|
+
let length = 0;
|
|
20
|
+
for (const character of value) {
|
|
21
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
22
|
+
if (codePoint >= 0xd800 && codePoint <= 0xdfff) return undefined;
|
|
23
|
+
if (codePoint <= 0x7f) length += 1;
|
|
24
|
+
else if (codePoint <= 0x7ff) length += 2;
|
|
25
|
+
else if (codePoint <= 0xffff) length += 3;
|
|
26
|
+
else length += 4;
|
|
27
|
+
}
|
|
28
|
+
return length;
|
|
29
|
+
};
|
|
30
|
+
|
|
11
31
|
export async function requestProtocolV2DeviceStatus(device: Device) {
|
|
12
32
|
const { message } = await device.commands.typedCall('DeviceStatusGet', 'DeviceStatus', {});
|
|
13
33
|
return message;
|
|
@@ -24,41 +44,20 @@ const negotiateEventlessWalletSession = async (device: Device) => {
|
|
|
24
44
|
});
|
|
25
45
|
};
|
|
26
46
|
|
|
27
|
-
const getDeviceSession = async (device: Device, request: DeviceSessionGet) =>
|
|
28
|
-
|
|
29
|
-
return await device.commands.typedCall('DeviceSessionGet', 'DeviceSession', request);
|
|
30
|
-
} catch (error) {
|
|
31
|
-
if (!isDeviceLockedError(error)) {
|
|
32
|
-
throw error;
|
|
33
|
-
}
|
|
34
|
-
await device.unlockDevice(DeviceSessionPinType.Main);
|
|
35
|
-
return device.commands.typedCall('DeviceSessionGet', 'DeviceSession', request);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
47
|
+
const getDeviceSession = async (device: Device, request: DeviceSessionGet) =>
|
|
48
|
+
device.commands.typedCall('DeviceSessionGet', 'DeviceSession', request);
|
|
38
49
|
|
|
39
|
-
const askDevicePassphrase = async (device: Device,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
'DeviceSessionAskPassphrase',
|
|
43
|
-
'Success',
|
|
44
|
-
passphrase ? { passphrase, on_device: false } : { on_device: true }
|
|
45
|
-
);
|
|
46
|
-
try {
|
|
47
|
-
return await request();
|
|
48
|
-
} catch (error) {
|
|
49
|
-
if (!isDeviceLockedError(error)) {
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
52
|
-
await device.unlockDevice(DeviceSessionPinType.Main);
|
|
53
|
-
return request();
|
|
54
|
-
}
|
|
50
|
+
const askDevicePassphrase = async (device: Device, requestPayload: DeviceSessionAskPassphrase) => {
|
|
51
|
+
await device.commands.typedCall('DeviceSessionAskPassphrase', 'Success', requestPayload);
|
|
52
|
+
await refreshProtocolV2DeviceStatus(device);
|
|
55
53
|
};
|
|
56
54
|
|
|
57
|
-
const selectDeviceSession = async (device: Device) => {
|
|
55
|
+
const selectDeviceSession = async (device: Device, expectedPassphraseState?: string) => {
|
|
58
56
|
const existsAttachPinUser = device.features?.attachToPinEnabled === true;
|
|
59
57
|
const metadata = {
|
|
60
58
|
source: 'wallet-session-coordinator' as const,
|
|
61
|
-
reason: 'open-wallet' as const,
|
|
59
|
+
reason: expectedPassphraseState ? ('session-recovery' as const) : ('open-wallet' as const),
|
|
60
|
+
...(expectedPassphraseState ? { expectedPassphraseState } : {}),
|
|
62
61
|
};
|
|
63
62
|
const response = await device.commands.promptPassphrase(
|
|
64
63
|
{
|
|
@@ -68,8 +67,21 @@ const selectDeviceSession = async (device: Device) => {
|
|
|
68
67
|
},
|
|
69
68
|
{ cancelDeviceOnReject: false }
|
|
70
69
|
);
|
|
71
|
-
const
|
|
72
|
-
typeof response.passphrase === 'string'
|
|
70
|
+
const hostPassphrase =
|
|
71
|
+
typeof response.passphrase === 'string' ? response.passphrase.normalize('NFKD') : undefined;
|
|
72
|
+
const hasHostPassphrase = typeof hostPassphrase === 'string' && hostPassphrase.length > 0;
|
|
73
|
+
const hostPassphraseByteLength = hasHostPassphrase ? utf8ByteLength(hostPassphrase) : undefined;
|
|
74
|
+
if (
|
|
75
|
+
hasHostPassphrase &&
|
|
76
|
+
(hostPassphrase.includes('\0') ||
|
|
77
|
+
hostPassphraseByteLength === undefined ||
|
|
78
|
+
hostPassphraseByteLength > HOST_PASSPHRASE_MAX_BYTES)
|
|
79
|
+
) {
|
|
80
|
+
throw ERRORS.TypedError(
|
|
81
|
+
HardwareErrorCode.RuntimeError,
|
|
82
|
+
`Host passphrase must contain 1 to ${HOST_PASSPHRASE_MAX_BYTES} valid UTF-8 bytes without NUL.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
73
85
|
const selections = [
|
|
74
86
|
hasHostPassphrase,
|
|
75
87
|
response.passphraseOnDevice === true,
|
|
@@ -96,26 +108,38 @@ const selectDeviceSession = async (device: Device) => {
|
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
if (hasHostPassphrase) {
|
|
99
|
-
await askDevicePassphrase(device,
|
|
111
|
+
await askDevicePassphrase(device, {
|
|
112
|
+
passphrase: hostPassphrase,
|
|
113
|
+
on_device: false,
|
|
114
|
+
});
|
|
100
115
|
return getDeviceSession(device, {});
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
device.emit(DEVICE.PASSPHRASE_ON_DEVICE, device, metadata);
|
|
104
|
-
await askDevicePassphrase(device);
|
|
119
|
+
await askDevicePassphrase(device, { on_device: true });
|
|
105
120
|
return getDeviceSession(device, {});
|
|
106
121
|
};
|
|
107
122
|
|
|
108
123
|
export async function getProtocolV2WalletSession(
|
|
109
124
|
device: Device,
|
|
110
125
|
options?: {
|
|
126
|
+
forceWalletSelection?: boolean;
|
|
127
|
+
/** @deprecated 兼容旧 getPassphraseState;新流程使用 forceWalletSelection。 */
|
|
111
128
|
initSession?: boolean;
|
|
112
129
|
expectedPassphraseState?: string;
|
|
113
130
|
onlyMainPin?: boolean;
|
|
114
131
|
resumeOnly?: boolean;
|
|
115
132
|
}
|
|
116
133
|
) {
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
const forceWalletSelection =
|
|
135
|
+
options?.forceWalletSelection === true || options?.initSession === true;
|
|
136
|
+
|
|
137
|
+
if (forceWalletSelection) {
|
|
138
|
+
if (options.onlyMainPin) {
|
|
139
|
+
device.clearStandardInternalState?.();
|
|
140
|
+
} else {
|
|
141
|
+
device.clearInternalState();
|
|
142
|
+
}
|
|
119
143
|
device.passphraseState = undefined;
|
|
120
144
|
}
|
|
121
145
|
|
|
@@ -124,35 +148,54 @@ export async function getProtocolV2WalletSession(
|
|
|
124
148
|
if (options?.onlyMainPin) {
|
|
125
149
|
device.passphraseState = undefined;
|
|
126
150
|
}
|
|
127
|
-
|
|
151
|
+
let expectedPassphraseState = options?.onlyMainPin
|
|
128
152
|
? undefined
|
|
129
153
|
: options?.expectedPassphraseState ?? device.passphraseState;
|
|
130
154
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (options?.onlyMainPin || device.getCurrentPassphraseProtection() === false) {
|
|
136
|
-
return {
|
|
137
|
-
passphraseState: undefined,
|
|
138
|
-
newSession: undefined,
|
|
139
|
-
unlockedAttachPin: device.features?.unlockedAttachPin ?? false,
|
|
140
|
-
resumed: false,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
155
|
+
const cachedStandardSession = options?.onlyMainPin
|
|
156
|
+
? device.getStandardInternalState?.()
|
|
157
|
+
: undefined;
|
|
144
158
|
const cachedSessionId =
|
|
145
|
-
typeof device.getInternalState === 'function'
|
|
159
|
+
!options?.onlyMainPin && typeof device.getInternalState === 'function'
|
|
160
|
+
? device.getInternalState()
|
|
161
|
+
: undefined;
|
|
146
162
|
let response;
|
|
147
163
|
let resumed = false;
|
|
148
164
|
|
|
149
|
-
if (
|
|
165
|
+
if (options?.onlyMainPin) {
|
|
166
|
+
expectedPassphraseState = cachedStandardSession?.passphraseState;
|
|
167
|
+
if (cachedStandardSession) {
|
|
168
|
+
try {
|
|
169
|
+
if (device.features?.unlockedAttachPin === true) {
|
|
170
|
+
await device.unlockDevice(DeviceSessionPinType.Main);
|
|
171
|
+
}
|
|
172
|
+
response = await getDeviceSession(device, {
|
|
173
|
+
session_id: cachedStandardSession.sessionId,
|
|
174
|
+
});
|
|
175
|
+
resumed = true;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
device.clearStandardInternalState?.();
|
|
178
|
+
if (!isWalletSessionInvalidError(error)) {
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
resumed = false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!response) {
|
|
186
|
+
await device.unlockDevice(DeviceSessionPinType.Main);
|
|
187
|
+
response = await getDeviceSession(device, {});
|
|
188
|
+
}
|
|
189
|
+
} else if (cachedSessionId && expectedPassphraseState) {
|
|
150
190
|
try {
|
|
151
191
|
response = await getDeviceSession(device, { session_id: cachedSessionId });
|
|
152
192
|
resumed = true;
|
|
153
193
|
} catch (error) {
|
|
154
194
|
device.clearInternalState();
|
|
155
|
-
|
|
195
|
+
if (options?.resumeOnly || !isWalletSessionInvalidError(error)) {
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
resumed = false;
|
|
156
199
|
}
|
|
157
200
|
}
|
|
158
201
|
|
|
@@ -161,28 +204,67 @@ export async function getProtocolV2WalletSession(
|
|
|
161
204
|
device.clearInternalState();
|
|
162
205
|
throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
|
|
163
206
|
}
|
|
164
|
-
response = await selectDeviceSession(device);
|
|
207
|
+
response = await selectDeviceSession(device, expectedPassphraseState);
|
|
165
208
|
}
|
|
166
209
|
|
|
167
|
-
|
|
210
|
+
let { message } = response;
|
|
168
211
|
try {
|
|
169
212
|
assertCompleteDeviceSession(message);
|
|
170
213
|
} catch (error) {
|
|
171
|
-
|
|
214
|
+
if (options?.onlyMainPin) {
|
|
215
|
+
device.clearStandardInternalState?.();
|
|
216
|
+
} else {
|
|
217
|
+
device.clearInternalState();
|
|
218
|
+
}
|
|
172
219
|
throw error;
|
|
173
220
|
}
|
|
174
221
|
if (expectedPassphraseState && expectedPassphraseState !== message.btc_test_address) {
|
|
175
|
-
|
|
176
|
-
|
|
222
|
+
resumed = false;
|
|
223
|
+
if (options?.resumeOnly) {
|
|
224
|
+
device.clearInternalState();
|
|
225
|
+
throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
|
|
226
|
+
}
|
|
227
|
+
if (options?.onlyMainPin) {
|
|
228
|
+
device.clearStandardInternalState?.();
|
|
229
|
+
await device.unlockDevice(DeviceSessionPinType.Main);
|
|
230
|
+
response = await getDeviceSession(device, {});
|
|
231
|
+
} else {
|
|
232
|
+
device.clearInternalState();
|
|
233
|
+
response = await selectDeviceSession(device, expectedPassphraseState);
|
|
234
|
+
}
|
|
235
|
+
message = response.message;
|
|
236
|
+
try {
|
|
237
|
+
assertCompleteDeviceSession(message);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (options?.onlyMainPin) {
|
|
240
|
+
device.clearStandardInternalState?.();
|
|
241
|
+
} else {
|
|
242
|
+
device.clearInternalState();
|
|
243
|
+
}
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
if (expectedPassphraseState !== message.btc_test_address) {
|
|
247
|
+
if (options?.onlyMainPin) {
|
|
248
|
+
device.clearStandardInternalState?.();
|
|
249
|
+
} else {
|
|
250
|
+
device.clearInternalState();
|
|
251
|
+
}
|
|
252
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
|
|
253
|
+
}
|
|
177
254
|
}
|
|
178
255
|
|
|
179
|
-
|
|
256
|
+
const internalStateArgs = [
|
|
180
257
|
true,
|
|
181
258
|
message.btc_test_address,
|
|
182
259
|
device.getCurrentDeviceId(),
|
|
183
260
|
message.session_id,
|
|
184
|
-
|
|
185
|
-
|
|
261
|
+
forceWalletSelection ? null : cachedSessionId ?? null,
|
|
262
|
+
] as const;
|
|
263
|
+
if (options?.onlyMainPin) {
|
|
264
|
+
device.updateInternalState(...internalStateArgs, 'standard');
|
|
265
|
+
} else {
|
|
266
|
+
device.updateInternalState(...internalStateArgs);
|
|
267
|
+
}
|
|
186
268
|
|
|
187
269
|
return {
|
|
188
270
|
passphraseState: message.btc_test_address,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommonParams, Response } from '../params';
|
|
2
|
-
import type {
|
|
2
|
+
import type { OnboardingStatus, Success } from '@onekeyfe/hd-transport';
|
|
3
3
|
import type { DeviceRebootParams } from '../../api/protocol-v2/helpers';
|
|
4
4
|
import type {
|
|
5
5
|
DeviceUploadWallpaperParams,
|
|
@@ -45,7 +45,7 @@ export declare function deviceReboot(
|
|
|
45
45
|
export declare function deviceGetOnboardingStatus(
|
|
46
46
|
connectId: string,
|
|
47
47
|
params?: CommonParams
|
|
48
|
-
): Response<
|
|
48
|
+
): Response<OnboardingStatus>;
|
|
49
49
|
|
|
50
50
|
export declare function deviceUploadWallpaper(
|
|
51
51
|
connectId: string,
|
package/src/types/device.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EDeviceType, type EFirmwareType } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, type EFirmwareType, type HardwareConnectProtocol } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import type { IVersionArray } from './settings';
|
|
4
4
|
import type { PROTO } from '../constants';
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
OneKeyDeviceCommType,
|
|
8
8
|
ProtocolV2DeviceInfo,
|
|
9
9
|
DeviceStatus as ProtocolV2DeviceStatus,
|
|
10
|
+
ProtocolInfo as ProtocolV2ProtocolInfo,
|
|
10
11
|
} from '@onekeyfe/hd-transport';
|
|
11
12
|
|
|
12
13
|
export type DeviceStatus = 'available' | 'occupied' | 'used';
|
|
@@ -28,6 +29,8 @@ export type UnavailableCapabilities = { [key: string]: UnavailableCapability };
|
|
|
28
29
|
|
|
29
30
|
export type KnownDevice = {
|
|
30
31
|
connectId: string | null;
|
|
32
|
+
/** Protocol family confirmed by the transport's active probe. */
|
|
33
|
+
connectProtocol?: HardwareConnectProtocol;
|
|
31
34
|
/**
|
|
32
35
|
* Stable physical-device identity. Current SDK responses always include this
|
|
33
36
|
* value; it remains optional in the type during the uuid compatibility window.
|
|
@@ -46,7 +49,7 @@ export type KnownDevice = {
|
|
|
46
49
|
/** Current SDK/transport usage state for connection indicators. */
|
|
47
50
|
status?: DeviceStatus;
|
|
48
51
|
mode: EOneKeyDeviceMode;
|
|
49
|
-
features
|
|
52
|
+
features: Features;
|
|
50
53
|
/** Unified SDK device-state snapshot; features remains a legacy projection. */
|
|
51
54
|
state?: DeviceState;
|
|
52
55
|
unavailableCapabilities: UnavailableCapabilities;
|
|
@@ -61,6 +64,8 @@ export type KnownDevice = {
|
|
|
61
64
|
|
|
62
65
|
export type SearchDevice = {
|
|
63
66
|
connectId: string | null;
|
|
67
|
+
/** Protocol family confirmed by the transport's active probe. */
|
|
68
|
+
connectProtocol?: HardwareConnectProtocol;
|
|
64
69
|
/**
|
|
65
70
|
* Available after device initialization. BLE discovery does not connect to the
|
|
66
71
|
* device, so it returns null until a later initialized device response. Current
|
|
@@ -155,6 +160,7 @@ export type DeviceFeaturesVerify = {
|
|
|
155
160
|
export type DeviceFeaturesRaw = {
|
|
156
161
|
protocolV1Features?: PROTO.Features;
|
|
157
162
|
protocolV1OneKeyFeatures?: OnekeyFeatures;
|
|
163
|
+
protocolV2ProtocolInfo?: ProtocolV2ProtocolInfo;
|
|
158
164
|
protocolV2DeviceInfo?: ProtocolV2DeviceInfo;
|
|
159
165
|
protocolV2DeviceStatus?: ProtocolV2DeviceStatus;
|
|
160
166
|
};
|
package/src/types/params.ts
CHANGED
|
@@ -60,7 +60,8 @@ export interface CommonParams {
|
|
|
60
60
|
usePreInitialize?: boolean;
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
63
|
+
* Strictly expected transport protocol. The SDK actively verifies this value and
|
|
64
|
+
* rejects a mismatch. If omitted, a cached protocol only influences probe order.
|
|
64
65
|
*/
|
|
65
66
|
connectProtocol?: HardwareConnectProtocol;
|
|
66
67
|
}
|