@onekeyfe/hd-core 1.2.0-alpha.42 → 1.2.0-alpha.43
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__/deviceUploadNft.test.ts +87 -2
- package/__tests__/open-wallet-session.test.ts +114 -20
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +35 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +110 -0
- package/__tests__/protocol-v2.test.ts +54 -14
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +1 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +3 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/events/ui-request.d.ts +1 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +157 -38
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +4 -1
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +2 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/protocol-v2/DeviceUploadNft.ts +25 -2
- package/src/core/index.ts +18 -2
- package/src/data/messages/messages-protocol-v2.json +15 -0
- package/src/device/Device.ts +15 -5
- package/src/events/ui-request.ts +1 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +20 -6
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
- package/src/protocols/protocol-v2/walletSession.ts +66 -17
- package/src/utils/pro2Nft.ts +51 -0
package/src/device/Device.ts
CHANGED
|
@@ -1644,18 +1644,28 @@ export class Device extends EventEmitter {
|
|
|
1644
1644
|
});
|
|
1645
1645
|
}
|
|
1646
1646
|
|
|
1647
|
-
finishProtocolV2UiInteraction(
|
|
1647
|
+
finishProtocolV2UiInteraction(
|
|
1648
|
+
outcome?: HardwareUiInteractionMeta['outcome'],
|
|
1649
|
+
options?: { ensureMetadata?: boolean }
|
|
1650
|
+
) {
|
|
1648
1651
|
const interaction = this.protocolV2UiInteraction;
|
|
1649
|
-
if (!interaction?.
|
|
1652
|
+
if (!interaction || (!interaction.opened && !options?.ensureMetadata)) {
|
|
1650
1653
|
this.protocolV2UiInteraction = undefined;
|
|
1651
1654
|
return undefined;
|
|
1652
1655
|
}
|
|
1653
1656
|
|
|
1654
1657
|
const phaseId = `${interaction.interactionId}:phase-${Math.max(interaction.phaseCounter, 1)}`;
|
|
1655
|
-
|
|
1658
|
+
interaction.opened = true;
|
|
1659
|
+
interaction.sequence += 1;
|
|
1660
|
+
const metadata: HardwareUiInteractionMeta = {
|
|
1661
|
+
interactionId: interaction.interactionId,
|
|
1656
1662
|
phaseId,
|
|
1657
|
-
|
|
1658
|
-
|
|
1663
|
+
sequence: interaction.sequence,
|
|
1664
|
+
phase: 'processing',
|
|
1665
|
+
transition: 'finish',
|
|
1666
|
+
outcome: outcome ?? 'succeeded',
|
|
1667
|
+
protocol: 'V2',
|
|
1668
|
+
};
|
|
1659
1669
|
this.protocolV2UiInteraction = undefined;
|
|
1660
1670
|
return metadata;
|
|
1661
1671
|
}
|
package/src/events/ui-request.ts
CHANGED
|
@@ -64,6 +64,7 @@ export type HardwareUiInteractionMeta = {
|
|
|
64
64
|
transition: 'start' | 'complete' | 'finish';
|
|
65
65
|
outcome?: 'submitted' | 'succeeded' | 'failed' | 'cancelled' | 'disconnected';
|
|
66
66
|
protocol: 'V2';
|
|
67
|
+
device?: Device;
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
export type ProtocolV2UiEventMetadata = {
|
|
@@ -203,20 +203,34 @@ export class ProtocolV2UiInteractionCoordinator {
|
|
|
203
203
|
this.enterMethodInteraction(this.methodInteraction);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
close(
|
|
206
|
+
close(
|
|
207
|
+
options: {
|
|
208
|
+
ensureOperationClose?: boolean;
|
|
209
|
+
protocolV2Operation?: boolean;
|
|
210
|
+
} = {}
|
|
211
|
+
): boolean {
|
|
207
212
|
if (
|
|
208
|
-
!this.device.isProtocolV2() ||
|
|
209
|
-
(!
|
|
213
|
+
(!options.protocolV2Operation && !this.device.isProtocolV2()) ||
|
|
214
|
+
(!options.ensureOperationClose &&
|
|
215
|
+
!this.opened &&
|
|
216
|
+
!this.device.hasOpenProtocolV2UiInteraction?.()) ||
|
|
210
217
|
this.closed
|
|
211
218
|
)
|
|
212
|
-
return;
|
|
219
|
+
return false;
|
|
213
220
|
this.closed = true;
|
|
214
|
-
const interaction = this.device.finishProtocolV2UiInteraction?.(
|
|
221
|
+
const interaction = this.device.finishProtocolV2UiInteraction?.('succeeded', {
|
|
222
|
+
ensureMetadata: options.ensureOperationClose,
|
|
223
|
+
});
|
|
224
|
+
const device = this.device.toMessageObject();
|
|
215
225
|
this.postMessage(
|
|
216
226
|
interaction
|
|
217
|
-
? createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW,
|
|
227
|
+
? createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW, {
|
|
228
|
+
...interaction,
|
|
229
|
+
...(device ? { device } : {}),
|
|
230
|
+
})
|
|
218
231
|
: createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW)
|
|
219
232
|
);
|
|
233
|
+
return true;
|
|
220
234
|
}
|
|
221
235
|
|
|
222
236
|
private emit(
|
|
@@ -80,7 +80,14 @@ export async function runMethodWithUnlockPolicy<T = unknown>(
|
|
|
80
80
|
|
|
81
81
|
await afterStatusBeforeUnlock?.();
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
const isStandardWalletRequest =
|
|
84
|
+
method.unlockPolicy !== 'unlock-before-run' &&
|
|
85
|
+
method.useDevicePassphraseState &&
|
|
86
|
+
method.payload?.useEmptyPassphrase === true;
|
|
87
|
+
const standardWalletSessionOwnsUnlock =
|
|
88
|
+
isStandardWalletRequest && status.passphraseProtection === true;
|
|
89
|
+
|
|
90
|
+
if (!status.unlocked && !standardWalletSessionOwnsUnlock) {
|
|
84
91
|
const unlockInteraction: HardwareUiInteractionMeta | undefined = shouldCoordinateUi
|
|
85
92
|
? uiCoordinator.enterUnlockInteraction(method.name)
|
|
86
93
|
: undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
-
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
2
|
+
import { DeviceSessionPinType, DeviceSessionSeedDomain } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
4
|
import { DEVICE } from '../../events';
|
|
5
5
|
import { assertCompleteDeviceSession } from './deviceSession';
|
|
@@ -63,12 +63,36 @@ const negotiateEventlessWalletSession = async (device: Device) => {
|
|
|
63
63
|
const getDeviceSession = async (device: Device, request: DeviceSessionGet) =>
|
|
64
64
|
device.commands.typedCall('DeviceSessionGet', 'DeviceSession', request);
|
|
65
65
|
|
|
66
|
+
const buildDeviceSessionGetRequest = ({
|
|
67
|
+
sessionId,
|
|
68
|
+
expectedPassphraseState,
|
|
69
|
+
deriveCardano,
|
|
70
|
+
}: {
|
|
71
|
+
sessionId?: string;
|
|
72
|
+
expectedPassphraseState?: string;
|
|
73
|
+
deriveCardano?: boolean;
|
|
74
|
+
} = {}): DeviceSessionGet => ({
|
|
75
|
+
...(sessionId ? { session_id: sessionId } : {}),
|
|
76
|
+
...(expectedPassphraseState ? { btc_test_address: expectedPassphraseState } : {}),
|
|
77
|
+
seed_domains:
|
|
78
|
+
deriveCardano === undefined
|
|
79
|
+
? []
|
|
80
|
+
: [
|
|
81
|
+
DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
82
|
+
...(deriveCardano ? [DeviceSessionSeedDomain.SeedDomain_Cardano] : []),
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
|
|
66
86
|
const askDevicePassphrase = async (device: Device, requestPayload: DeviceSessionAskPassphrase) => {
|
|
67
87
|
await device.commands.typedCall('DeviceSessionAskPassphrase', 'Success', requestPayload);
|
|
68
88
|
await refreshProtocolV2DeviceStatus(device);
|
|
69
89
|
};
|
|
70
90
|
|
|
71
|
-
const selectDeviceSession = async (
|
|
91
|
+
const selectDeviceSession = async (
|
|
92
|
+
device: Device,
|
|
93
|
+
expectedPassphraseState?: string,
|
|
94
|
+
deriveCardano?: boolean
|
|
95
|
+
) => {
|
|
72
96
|
const existsAttachPinUser = device.features?.attachToPinEnabled === true;
|
|
73
97
|
const metadata = {
|
|
74
98
|
source: 'wallet-session-coordinator' as const,
|
|
@@ -129,7 +153,7 @@ const selectDeviceSession = async (device: Device, expectedPassphraseState?: str
|
|
|
129
153
|
emitUiEvent: false,
|
|
130
154
|
interaction: attachPinInteraction,
|
|
131
155
|
});
|
|
132
|
-
return getDeviceSession(device, {});
|
|
156
|
+
return getDeviceSession(device, buildDeviceSessionGetRequest({ deriveCardano }));
|
|
133
157
|
}
|
|
134
158
|
|
|
135
159
|
if (hasHostPassphrase) {
|
|
@@ -137,7 +161,7 @@ const selectDeviceSession = async (device: Device, expectedPassphraseState?: str
|
|
|
137
161
|
passphrase: hostPassphrase,
|
|
138
162
|
on_device: false,
|
|
139
163
|
});
|
|
140
|
-
return getDeviceSession(device, {});
|
|
164
|
+
return getDeviceSession(device, buildDeviceSessionGetRequest({ deriveCardano }));
|
|
141
165
|
}
|
|
142
166
|
|
|
143
167
|
device.emit(DEVICE.PASSPHRASE_ON_DEVICE, device, {
|
|
@@ -145,7 +169,7 @@ const selectDeviceSession = async (device: Device, expectedPassphraseState?: str
|
|
|
145
169
|
...(passphraseInteraction ? { interaction: passphraseInteraction } : {}),
|
|
146
170
|
});
|
|
147
171
|
await askDevicePassphrase(device, { on_device: true });
|
|
148
|
-
return getDeviceSession(device, {});
|
|
172
|
+
return getDeviceSession(device, buildDeviceSessionGetRequest({ deriveCardano }));
|
|
149
173
|
};
|
|
150
174
|
|
|
151
175
|
export async function getProtocolV2WalletSession(
|
|
@@ -191,7 +215,10 @@ export async function getProtocolV2WalletSession(
|
|
|
191
215
|
: undefined;
|
|
192
216
|
let response;
|
|
193
217
|
let resumed = false;
|
|
194
|
-
let mainWalletSelected =
|
|
218
|
+
let mainWalletSelected =
|
|
219
|
+
options?.onlyMainPin === true &&
|
|
220
|
+
device.features?.unlocked === true &&
|
|
221
|
+
device.features?.unlockedAttachPin === false;
|
|
195
222
|
|
|
196
223
|
const clearCurrentWalletSession = () => {
|
|
197
224
|
if (options?.onlyMainPin) {
|
|
@@ -234,9 +261,14 @@ export async function getProtocolV2WalletSession(
|
|
|
234
261
|
if (options?.selectMainWalletBeforeRestore) {
|
|
235
262
|
await selectMainWallet();
|
|
236
263
|
}
|
|
237
|
-
response = await getDeviceSession(
|
|
238
|
-
|
|
239
|
-
|
|
264
|
+
response = await getDeviceSession(
|
|
265
|
+
device,
|
|
266
|
+
buildDeviceSessionGetRequest({
|
|
267
|
+
sessionId: cachedStandardSession.sessionId,
|
|
268
|
+
expectedPassphraseState,
|
|
269
|
+
deriveCardano: options?.deriveCardano,
|
|
270
|
+
})
|
|
271
|
+
);
|
|
240
272
|
resumed = true;
|
|
241
273
|
} catch (error) {
|
|
242
274
|
device.clearStandardInternalState?.();
|
|
@@ -249,13 +281,21 @@ export async function getProtocolV2WalletSession(
|
|
|
249
281
|
|
|
250
282
|
if (!response) {
|
|
251
283
|
await selectMainWallet();
|
|
252
|
-
response = await getDeviceSession(
|
|
284
|
+
response = await getDeviceSession(
|
|
285
|
+
device,
|
|
286
|
+
buildDeviceSessionGetRequest({ deriveCardano: options?.deriveCardano })
|
|
287
|
+
);
|
|
253
288
|
}
|
|
254
289
|
} else if (cachedSessionId && expectedPassphraseState) {
|
|
255
290
|
try {
|
|
256
|
-
response = await getDeviceSession(
|
|
257
|
-
|
|
258
|
-
|
|
291
|
+
response = await getDeviceSession(
|
|
292
|
+
device,
|
|
293
|
+
buildDeviceSessionGetRequest({
|
|
294
|
+
sessionId: cachedSessionId,
|
|
295
|
+
expectedPassphraseState,
|
|
296
|
+
deriveCardano: options?.deriveCardano,
|
|
297
|
+
})
|
|
298
|
+
);
|
|
259
299
|
resumed = true;
|
|
260
300
|
} catch (error) {
|
|
261
301
|
device.clearInternalState();
|
|
@@ -266,7 +306,13 @@ export async function getProtocolV2WalletSession(
|
|
|
266
306
|
}
|
|
267
307
|
} else if (expectedPassphraseState) {
|
|
268
308
|
try {
|
|
269
|
-
response = await getDeviceSession(
|
|
309
|
+
response = await getDeviceSession(
|
|
310
|
+
device,
|
|
311
|
+
buildDeviceSessionGetRequest({
|
|
312
|
+
expectedPassphraseState,
|
|
313
|
+
deriveCardano: options?.deriveCardano,
|
|
314
|
+
})
|
|
315
|
+
);
|
|
270
316
|
} catch (error) {
|
|
271
317
|
if (options?.resumeOnly || !isWalletSessionInvalidError(error)) {
|
|
272
318
|
throw error;
|
|
@@ -279,7 +325,7 @@ export async function getProtocolV2WalletSession(
|
|
|
279
325
|
device.clearInternalState();
|
|
280
326
|
throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
|
|
281
327
|
}
|
|
282
|
-
response = await selectDeviceSession(device, expectedPassphraseState);
|
|
328
|
+
response = await selectDeviceSession(device, expectedPassphraseState, options?.deriveCardano);
|
|
283
329
|
}
|
|
284
330
|
|
|
285
331
|
let { message } = response;
|
|
@@ -303,10 +349,13 @@ export async function getProtocolV2WalletSession(
|
|
|
303
349
|
if (options?.onlyMainPin) {
|
|
304
350
|
device.clearStandardInternalState?.();
|
|
305
351
|
await selectMainWallet(true);
|
|
306
|
-
response = await getDeviceSession(
|
|
352
|
+
response = await getDeviceSession(
|
|
353
|
+
device,
|
|
354
|
+
buildDeviceSessionGetRequest({ deriveCardano: options?.deriveCardano })
|
|
355
|
+
);
|
|
307
356
|
} else {
|
|
308
357
|
device.clearInternalState();
|
|
309
|
-
response = await selectDeviceSession(device, expectedPassphraseState);
|
|
358
|
+
response = await selectDeviceSession(device, expectedPassphraseState, options?.deriveCardano);
|
|
310
359
|
}
|
|
311
360
|
message = response.message;
|
|
312
361
|
try {
|
package/src/utils/pro2Nft.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const PRO2_NFT_DEFAULT_PACE_MS = 20;
|
|
|
14
14
|
export const PRO2_NFT_DEFAULT_TIMEOUT_MS = 15_000;
|
|
15
15
|
export const PRO2_NFT_MIN_CHUNK_SIZE = 64;
|
|
16
16
|
export const PRO2_NFT_MAX_CHUNK_SIZE = 2048;
|
|
17
|
+
export const PRO2_NFT_MAX_ITEMS = 10;
|
|
17
18
|
|
|
18
19
|
export type Pro2NftImage = {
|
|
19
20
|
width: number;
|
|
@@ -28,6 +29,56 @@ export type Pro2NftBundle = {
|
|
|
28
29
|
metadata: Uint8Array;
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
const PRO2_NFT_BASENAME_PATTERN = /^nft-[0-9a-f]{8}-[1-9][0-9]*$/;
|
|
33
|
+
|
|
34
|
+
type Pro2NftFileType = 'image' | 'thumbnail' | 'metadata';
|
|
35
|
+
|
|
36
|
+
function parsePro2NftFile(listedPath: string):
|
|
37
|
+
| {
|
|
38
|
+
basename: string;
|
|
39
|
+
fileType: Pro2NftFileType;
|
|
40
|
+
}
|
|
41
|
+
| undefined {
|
|
42
|
+
const filename = listedPath.trim().split('/').at(-1);
|
|
43
|
+
if (!filename) return undefined;
|
|
44
|
+
|
|
45
|
+
let basename: string | undefined;
|
|
46
|
+
let fileType: Pro2NftFileType | undefined;
|
|
47
|
+
if (filename.endsWith('_m.bin')) {
|
|
48
|
+
basename = filename.slice(0, -'_m.bin'.length);
|
|
49
|
+
fileType = 'thumbnail';
|
|
50
|
+
} else if (filename.endsWith('.json')) {
|
|
51
|
+
basename = filename.slice(0, -'.json'.length);
|
|
52
|
+
fileType = 'metadata';
|
|
53
|
+
} else if (filename.endsWith('.bin')) {
|
|
54
|
+
basename = filename.slice(0, -'.bin'.length);
|
|
55
|
+
fileType = 'image';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return basename && fileType && PRO2_NFT_BASENAME_PATTERN.test(basename)
|
|
59
|
+
? { basename, fileType }
|
|
60
|
+
: undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getCompletePro2NftBasenames(childFiles?: string): Set<string> {
|
|
64
|
+
const filesByBasename = new Map<string, Set<Pro2NftFileType>>();
|
|
65
|
+
|
|
66
|
+
for (const listedPath of childFiles?.split('\n') ?? []) {
|
|
67
|
+
const file = parsePro2NftFile(listedPath);
|
|
68
|
+
if (file) {
|
|
69
|
+
const fileTypes = filesByBasename.get(file.basename) ?? new Set<Pro2NftFileType>();
|
|
70
|
+
fileTypes.add(file.fileType);
|
|
71
|
+
filesByBasename.set(file.basename, fileTypes);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return new Set(
|
|
76
|
+
[...filesByBasename.entries()]
|
|
77
|
+
.filter(([, fileTypes]) => fileTypes.size === 3)
|
|
78
|
+
.map(([basename]) => basename)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
31
82
|
function utf8Length(value: string): number {
|
|
32
83
|
return new TextEncoder().encode(value).byteLength;
|
|
33
84
|
}
|