@onekeyfe/hwk-trezor-adapter 1.2.1 → 1.2.2-alpha.1

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/dist/index.mjs CHANGED
@@ -12,6 +12,713 @@ import {
12
12
  runAllNetworkGetAddress,
13
13
  success
14
14
  } from "@onekeyfe/hwk-adapter-core";
15
+ import { randomBytes } from "@noble/hashes/utils";
16
+
17
+ // src/deviceAuthenticity/index.ts
18
+ import { sha3_256 as sha3Hash } from "@noble/hashes/sha3";
19
+
20
+ // src/deviceAuthenticity/config.ts
21
+ var deviceAuthenticityConfig = {
22
+ version: 2,
23
+ T2B1: {
24
+ rootPubKeysOptiga: [
25
+ // upstream: DEV_AUTH_ROOT_PROD_P256
26
+ "04ca97480ac0d7b1e6efafe518cd433cec2bf8ab9822d76eafd34363b55d63e60380bff20acc75cde03cffcb50ab6f8ce70c878e37ebc58ff7cca0a83b16b15fa5"
27
+ ],
28
+ debug: {
29
+ rootPubKeysOptiga: [
30
+ // upstream: DEV_AUTH_ROOT_DEBUG_P256 shared with T3B1
31
+ "047f77368dea2d4d61e989f474a56723c3212dacf8a808d8795595ef38441427c4389bc454f02089d7f08b873005e4c28d432468997871c0bf286fd3861e21e96a"
32
+ ]
33
+ }
34
+ },
35
+ T3B1: {
36
+ rootPubKeysOptiga: [
37
+ // upstream: DEV_AUTH_ROOT_PROD_P256
38
+ "045b5c3fdd01f3602092834209b86df0ca86a9faf25cac35c73bf6237d66eb21eafcec3706f1ccd5eb4cc7f2fa1751213eccb1c78389afba89a5788ff31ee46a5d"
39
+ ],
40
+ debug: {
41
+ rootPubKeysOptiga: [
42
+ // upstream: DEV_AUTH_ROOT_DEBUG_P256 shared with T2B1
43
+ "047f77368dea2d4d61e989f474a56723c3212dacf8a808d8795595ef38441427c4389bc454f02089d7f08b873005e4c28d432468997871c0bf286fd3861e21e96a"
44
+ ]
45
+ }
46
+ },
47
+ T3T1: {
48
+ rootPubKeysOptiga: [
49
+ // upstream: DEV_AUTH_ROOT_PROD_P256
50
+ "041854b27fb1d9f65abb66828e78c9dc0ca301e66081ab0c6a4d104f9df1cd0ad5a7c75f77a8c092f55cf825d2abaf734f934c9394d5e75f75a5a06a5ee9be93ae"
51
+ ],
52
+ debug: {
53
+ rootPubKeysOptiga: [
54
+ // upstream: DEV_AUTH_ROOT_DEBUG_P256
55
+ "04e48b69cd7962068d3cca3bcc6b1747ef496c1e28b5529e34ad7295215ea161dbe8fb08ae0479568f9d2cb07630cb3e52f4af0692102da5873559e45e9fa72959"
56
+ ]
57
+ }
58
+ },
59
+ T3W1: {
60
+ rootPubKeysOptiga: [
61
+ // upstream: DEV_AUTH_ROOT_PROD_P256
62
+ "040dde0d3e0d4da593fac6fd02a461d0e7eef238aca55c7c50b4e9ec37f3873303b6429ef1c9b78b4411a7dcbbc5dde5225979c1c2da3b073e82b1ed3f5f9825bb",
63
+ // upstream: DEV_AUTH_ROOT_PROD_BACKUP_P256
64
+ "04c6a673af4ec44b10441b1d78676e15173ad0e36df9f7f2fa1cd819955f20fe32917b60da5fed3b3aa54a9ab8b3ed27d198b3768cad26eef5935cd87af0af065e"
65
+ ],
66
+ rootPubKeysTropic: [
67
+ // upstream: DEV_AUTH_ROOT_PROD_ED25519
68
+ "59237acd17134061d655b3f8d624573ca06ce8d862f38ba4e05140ce1d3d609d",
69
+ // upstream: DEV_AUTH_ROOT_PROD_BACKUP_ED25519
70
+ "5612606584ee7e0bc313b13f7ac94156bb4cb75bd77585ddbe579301306e85f1"
71
+ ],
72
+ debug: {
73
+ rootPubKeysOptiga: [
74
+ // upstream: DEV_AUTH_ROOT_DEBUG_P256
75
+ "04521192e173a9da4e3023f747d836563725372681eba3079c56ff11b2fc137ab189eb4155f371127651b5594f8c332fc1e9c0f3b80d4212822668b63189706578",
76
+ // upstream: DEV_AUTH_ROOT_STAGING_P256
77
+ "0465e88f9b2cea67e8364f0cfcfacd500af24e9040b357beee629ccc4fce1704d1a7ef7284f387708f92ef14600e2caad6894016fee819d623b95d66210c3e7519"
78
+ ],
79
+ rootPubKeysTropic: [
80
+ // upstream: DEV_AUTH_ROOT_STAGING_ED25519
81
+ "cd318dc8405ae4f4144e3284dcb7b0cb0f0c2195c2ca14a0f6fccd9104e32a4b"
82
+ ]
83
+ }
84
+ }
85
+ };
86
+
87
+ // src/deviceAuthenticity/findSubjectContentByOid.ts
88
+ var findSubjectContentByOid = (deviceCert, oid) => {
89
+ const subjectItems = deviceCert.tbsCertificate.subject;
90
+ const matchedItem = subjectItems.find(({ algorithmOid }) => algorithmOid === oid);
91
+ if (!matchedItem || !matchedItem.parameters) return null;
92
+ return matchedItem.parameters.asn1.contents;
93
+ };
94
+
95
+ // src/deviceAuthenticity/validateCaCertExtensions.ts
96
+ var validateCaCertExtensions = (cert, pathLen) => {
97
+ let hasKeyUsage = false;
98
+ let hasBasicConstraints = false;
99
+ cert.tbsCertificate.extensions.forEach((ext) => {
100
+ if (ext.key === "keyUsage") {
101
+ if (ext.keyCertSign !== "1") {
102
+ throw new Error(`CA keyCertSign not set`);
103
+ }
104
+ hasKeyUsage = true;
105
+ } else if (ext.key === "basicConstraints") {
106
+ if (!ext.cA) {
107
+ throw new Error(`CA basicConstraints.cA not set`);
108
+ }
109
+ if (typeof ext.pathLenConstraint !== "number") {
110
+ throw new Error("CA basicConstraints.pathLenConstraint not set");
111
+ }
112
+ if (ext.pathLenConstraint < pathLen) {
113
+ throw new Error("Issuer was not permitted to issue certificate");
114
+ }
115
+ hasBasicConstraints = true;
116
+ } else if (ext.critical) {
117
+ throw new Error(`Unknown critical extension ${ext.key} in CA certificate`);
118
+ }
119
+ });
120
+ if (!hasKeyUsage || !hasBasicConstraints) {
121
+ throw new Error(
122
+ `CA missing extensions keyUsage: ${hasKeyUsage}, basicConstraints: ${hasBasicConstraints}`
123
+ );
124
+ }
125
+ };
126
+
127
+ // src/deviceAuthenticity/verifySignatures.ts
128
+ import { ed25519 } from "@noble/curves/ed25519";
129
+ import { p256 } from "@noble/curves/p256";
130
+ import { sha256 } from "@noble/hashes/sha256";
131
+
132
+ // src/deviceAuthenticity/x509certificate.ts
133
+ var parseOidToAlgorithmName = (oid) => {
134
+ if (oid === "1.2.840.10045.4.3.2") return "P-256";
135
+ if (oid === "1.3.101.112") return "Ed25519";
136
+ if (oid === "2.16.840.1.101.3.4.3.17") return "MLDSA44";
137
+ return "unknown";
138
+ };
139
+ var derToAsn1 = (byteArray) => {
140
+ let position = 0;
141
+ function getTag() {
142
+ let tag2 = byteArray[0] & 31;
143
+ position += 1;
144
+ if (tag2 === 31) {
145
+ tag2 = 0;
146
+ while (byteArray[position] >= 128) {
147
+ tag2 = tag2 * 128 + byteArray[position] - 128;
148
+ position += 1;
149
+ }
150
+ tag2 = tag2 * 128 + byteArray[position] - 128;
151
+ position += 1;
152
+ }
153
+ return tag2;
154
+ }
155
+ function getLength() {
156
+ let length2 = 0;
157
+ if (byteArray[position] < 128) {
158
+ length2 = byteArray[position];
159
+ position += 1;
160
+ } else {
161
+ const numberOfDigits = byteArray[position] & 127;
162
+ position += 1;
163
+ length2 = 0;
164
+ for (let i = 0; i < numberOfDigits; i++) {
165
+ length2 = length2 * 256 + byteArray[position];
166
+ position += 1;
167
+ }
168
+ }
169
+ return length2;
170
+ }
171
+ const cls = (byteArray[0] & 192) / 64;
172
+ const structured = (byteArray[0] & 32) === 32;
173
+ const tag = getTag();
174
+ if (byteArray[position] === 128) {
175
+ throw new Error("Unsupported length encoding");
176
+ }
177
+ const length = getLength();
178
+ const byteLength = position + length;
179
+ const contents = byteArray.subarray(position, byteLength);
180
+ const raw = byteArray.subarray(0, byteLength);
181
+ return {
182
+ cls,
183
+ tag,
184
+ structured,
185
+ byteLength,
186
+ contents,
187
+ raw
188
+ };
189
+ };
190
+ var derToAsn1List = (byteArray) => {
191
+ const result = [];
192
+ let nextPosition = 0;
193
+ while (nextPosition < byteArray.length) {
194
+ const nextPiece = derToAsn1(byteArray.subarray(nextPosition));
195
+ result.push(nextPiece);
196
+ nextPosition += nextPiece.byteLength;
197
+ }
198
+ return result;
199
+ };
200
+ var derBitStringValue = (byteArray) => ({
201
+ unusedBits: byteArray[0],
202
+ bytes: byteArray.subarray(1)
203
+ });
204
+ var parseSignatureValue = (asn1) => {
205
+ if (asn1.cls !== 0 || asn1.tag !== 3 || asn1.structured) {
206
+ throw new Error("Bad signature value. Not a BIT STRING.");
207
+ }
208
+ return {
209
+ asn1,
210
+ bits: derBitStringValue(asn1.contents)
211
+ };
212
+ };
213
+ var fixSignature = (byteArray) => {
214
+ const asn1 = derToAsn1(byteArray);
215
+ if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
216
+ throw new Error("Bad signature. Not a SEQUENCE.");
217
+ }
218
+ const items = derToAsn1List(asn1.contents);
219
+ let newLength = 0;
220
+ const fixedItems = items.map((chunk) => {
221
+ const index = chunk.contents.findIndex((value) => value > 0);
222
+ const data = chunk.contents.subarray(index);
223
+ const offset = data[0] >= 128 ? 1 : 0;
224
+ const chunkLength = data.length + offset;
225
+ const newChunk = new Uint8Array(chunkLength + 2);
226
+ newChunk.set([chunk.raw[0], chunkLength]);
227
+ if (offset > 0) {
228
+ newChunk.set([0], 2);
229
+ }
230
+ newChunk.set(data, 2 + offset);
231
+ newLength += newChunk.length;
232
+ return newChunk;
233
+ });
234
+ const signature = new Uint8Array(newLength + 2);
235
+ signature.set([byteArray[0], newLength]);
236
+ let signatureOffset = 2;
237
+ fixedItems.forEach((item) => {
238
+ signature.set(item, signatureOffset);
239
+ signatureOffset += item.length;
240
+ });
241
+ return signature;
242
+ };
243
+ var derObjectIdentifierValue = (byteArray) => {
244
+ let oid = `${Math.floor(byteArray[0] / 40)}.${byteArray[0] % 40}`;
245
+ let position = 1;
246
+ while (position < byteArray.length) {
247
+ let nextInteger = 0;
248
+ while (byteArray[position] >= 128) {
249
+ nextInteger = nextInteger * 128 + (byteArray[position] & 127);
250
+ position += 1;
251
+ }
252
+ nextInteger = nextInteger * 128 + byteArray[position];
253
+ position += 1;
254
+ oid += `.${nextInteger}`;
255
+ }
256
+ return oid;
257
+ };
258
+ var parseAlgorithmIdentifier = (asn1) => {
259
+ if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
260
+ throw new Error("Bad algorithm identifier. Not a SEQUENCE.");
261
+ }
262
+ const pieces = derToAsn1List(asn1.contents);
263
+ if (pieces.length > 2) {
264
+ throw new Error("Bad algorithm identifier. Contains too many child objects.");
265
+ }
266
+ const encodedAlgorithm = pieces[0];
267
+ if (encodedAlgorithm.cls !== 0 || encodedAlgorithm.tag !== 6 || encodedAlgorithm.structured) {
268
+ throw new Error("Bad algorithm identifier. Does not begin with an OBJECT IDENTIFIER.");
269
+ }
270
+ const algorithmOid = derObjectIdentifierValue(encodedAlgorithm.contents);
271
+ const algorithmName = parseOidToAlgorithmName(algorithmOid);
272
+ return {
273
+ asn1,
274
+ algorithmOid,
275
+ algorithmName,
276
+ parameters: pieces.length === 2 ? { asn1: pieces[1] } : null
277
+ };
278
+ };
279
+ var parseName = (asn1) => (
280
+ // SEQUENCE > SET > SEQUENCE
281
+ derToAsn1List(asn1.contents).map((item) => {
282
+ const attrSet = derToAsn1(item.contents);
283
+ return parseAlgorithmIdentifier(attrSet);
284
+ })
285
+ );
286
+ var parseSubjectPublicKeyInfo = (asn1) => {
287
+ if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
288
+ throw new Error("Bad SPKI. Not a SEQUENCE.");
289
+ }
290
+ const pieces = derToAsn1List(asn1.contents);
291
+ if (pieces.length !== 2) {
292
+ throw new Error("Bad SubjectPublicKeyInfo. Wrong number of child objects.");
293
+ }
294
+ return {
295
+ asn1,
296
+ algorithm: parseAlgorithmIdentifier(pieces[0]),
297
+ bits: derBitStringValue(pieces[1].contents)
298
+ };
299
+ };
300
+ var parseUtcTime = (time) => {
301
+ let offset = 4;
302
+ let yearOffset = 0;
303
+ if (time.tag === 23) {
304
+ offset = 2;
305
+ yearOffset = 2e3;
306
+ }
307
+ const utc = Buffer.from(time.contents).toString();
308
+ const year = yearOffset + Number(utc.substring(0, offset));
309
+ const month = Number(utc.substring(offset, offset + 2)) - 1;
310
+ const day = Number(utc.substring(offset + 2, offset + 4));
311
+ const hour = Number(utc.substring(offset + 4, offset + 6));
312
+ const minute = Number(utc.substring(offset + 6, offset + 8));
313
+ const date = /* @__PURE__ */ new Date();
314
+ date.setUTCFullYear(year, month, day);
315
+ date.setUTCHours(hour, minute, 0);
316
+ return date;
317
+ };
318
+ var parseValidity = (asn1) => {
319
+ const [from, to] = derToAsn1List(asn1.contents);
320
+ return {
321
+ from: parseUtcTime(from),
322
+ to: parseUtcTime(to)
323
+ };
324
+ };
325
+ var parseExtensions = (data) => {
326
+ const asn1 = derToAsn1(data.contents);
327
+ if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
328
+ throw new Error("This can't be a Extension. Wrong data type.");
329
+ }
330
+ const readBoolean = (value) => {
331
+ if (!value) return false;
332
+ if (value.cls !== 0 || value.tag !== 1 || value.contents.length !== 1 || value.structured) {
333
+ throw new Error("This can't be a boolean. Wrong data type.");
334
+ }
335
+ if (![0, 255].includes(value.contents[0])) {
336
+ throw new Error("Invalid boolean value.");
337
+ }
338
+ return value.contents[0] === 255;
339
+ };
340
+ const readBitString = (uint8Array) => {
341
+ const buffer = Buffer.from(uint8Array);
342
+ const tag = buffer.readUInt8(0);
343
+ if (tag !== 3) {
344
+ throw new Error("This can't be a bit string. Wrong data type.");
345
+ }
346
+ const length = buffer.readUInt8(1);
347
+ const unusedBits = buffer.readUInt8(2);
348
+ const bitStringBytes = buffer.subarray(3, 3 + length - 1);
349
+ const bitString = bitStringBytes.reduce(
350
+ (str, byte) => str + byte.toString(2).padStart(8, "0"),
351
+ ""
352
+ );
353
+ return bitString.slice(0, bitString.length - unusedBits);
354
+ };
355
+ const readInteger = (value) => {
356
+ if (!value) return void 0;
357
+ if (value.cls !== 0 || value.tag !== 2 || value.contents.length !== 1 || value.structured) {
358
+ throw new Error("This can't be a integer. Wrong data type.");
359
+ }
360
+ return Buffer.from(value.contents).readInt8();
361
+ };
362
+ const extensions = [];
363
+ derToAsn1List(asn1.contents).forEach((item) => {
364
+ const [id, ...pieces] = derToAsn1List(item.contents);
365
+ if (id.cls !== 0 || id.tag !== 6 || id.structured) {
366
+ throw new Error("Bad extension. Does not begin with an OBJECT IDENTIFIER.");
367
+ }
368
+ const algorithm = derObjectIdentifierValue(id.contents);
369
+ const critical = pieces.length > 1 ? readBoolean(pieces[0]) : false;
370
+ const extnValue = pieces.length > 1 ? pieces[1] : pieces[0];
371
+ if (extnValue.cls !== 0 || extnValue.tag !== 4 || extnValue.structured) {
372
+ throw new Error("This can't be a octet string. Wrong data type.");
373
+ }
374
+ if (algorithm === "2.5.29.15") {
375
+ extensions.push({
376
+ key: "keyUsage",
377
+ critical,
378
+ keyCertSign: readBitString(extnValue.contents)[5]
379
+ });
380
+ } else if (algorithm === "2.5.29.19") {
381
+ const fields = derToAsn1List(derToAsn1(extnValue.contents).contents);
382
+ const ca = fields.length > 0 && fields[0].tag === 1 ? fields[0] : void 0;
383
+ const len = fields.length > 0 && fields[0].tag === 2 ? fields[0] : fields[1];
384
+ extensions.push({
385
+ key: "basicConstraints",
386
+ critical,
387
+ cA: readBoolean(ca),
388
+ pathLenConstraint: readInteger(len)
389
+ });
390
+ } else {
391
+ extensions.push({
392
+ key: algorithm,
393
+ critical,
394
+ ...item
395
+ });
396
+ }
397
+ });
398
+ return extensions;
399
+ };
400
+ var parseTBSCertificate = (asn1) => {
401
+ if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
402
+ throw new Error("This can't be a TBSCertificate. Wrong data type.");
403
+ }
404
+ const pieces = derToAsn1List(asn1.contents);
405
+ if (pieces.length < 7) {
406
+ throw new Error("Bad TBS Certificate. There are fewer than the seven required children.");
407
+ }
408
+ return {
409
+ asn1,
410
+ version: pieces[0],
411
+ serialNumber: pieces[1],
412
+ signature: parseAlgorithmIdentifier(pieces[2]),
413
+ issuer: pieces[3],
414
+ validity: parseValidity(pieces[4]),
415
+ subject: parseName(pieces[5]),
416
+ subjectPublicKeyInfo: parseSubjectPublicKeyInfo(pieces[6]),
417
+ extensions: parseExtensions(pieces[7])
418
+ };
419
+ };
420
+ var parseCertificate = (byteArray) => {
421
+ const asn1 = derToAsn1(byteArray);
422
+ if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
423
+ throw new Error("This can't be an X.509 certificate. Wrong data type.");
424
+ }
425
+ const pieces = derToAsn1List(asn1.contents);
426
+ if (pieces.length !== 3) {
427
+ throw new Error("Certificate contains more than the three specified children.");
428
+ }
429
+ return {
430
+ asn1,
431
+ tbsCertificate: parseTBSCertificate(pieces[0]),
432
+ signatureAlgorithm: parseAlgorithmIdentifier(pieces[1]),
433
+ signatureValue: parseSignatureValue(pieces[2])
434
+ };
435
+ };
436
+
437
+ // src/deviceAuthenticity/verifySignatures.ts
438
+ var verifySignatureP256 = (rawKey, data, signature) => {
439
+ try {
440
+ const sig = p256.Signature.fromDER(Buffer.from(fixSignature(signature)).toString("hex"));
441
+ return p256.verify(sig.toBytes("compact"), sha256(data), rawKey, { lowS: false });
442
+ } catch {
443
+ return false;
444
+ }
445
+ };
446
+ var verifySignatureEd25519 = (rawKey, data, signature) => {
447
+ try {
448
+ return ed25519.verify(signature, data, rawKey);
449
+ } catch {
450
+ return false;
451
+ }
452
+ };
453
+ var getVerifyFn = (algorithmName) => {
454
+ if (algorithmName === "P-256") return verifySignatureP256;
455
+ if (algorithmName === "Ed25519") return verifySignatureEd25519;
456
+ throw new Error(`Unsupported signature algorithm: ${algorithmName}`);
457
+ };
458
+
459
+ // src/deviceAuthenticity/verifyAuthenticityProof.ts
460
+ var getChunkSize = (byteLength) => {
461
+ if (byteLength < 253) return Uint8Array.from([byteLength]);
462
+ if (byteLength <= 65535) {
463
+ const b2 = Buffer.alloc(3);
464
+ b2[0] = 253;
465
+ b2.writeUInt16LE(byteLength, 1);
466
+ return Uint8Array.from(b2);
467
+ }
468
+ const b = Buffer.alloc(5);
469
+ b[0] = 254;
470
+ b.writeUInt32LE(byteLength, 1);
471
+ return Uint8Array.from(b);
472
+ };
473
+ var prepareDeviceAuthenticityData = ({
474
+ payload,
475
+ prefix = "AuthenticateDevice:"
476
+ }) => {
477
+ const chunks = [
478
+ Uint8Array.from(Buffer.from(prefix)),
479
+ ...(Array.isArray(payload) ? payload : [payload]).map((chunk) => Uint8Array.from(chunk))
480
+ ];
481
+ const framed = chunks.flatMap((chunk) => [getChunkSize(chunk.byteLength), chunk]);
482
+ const result = new Uint8Array(framed.reduce((total, chunk) => total + chunk.byteLength, 0));
483
+ let offset = 0;
484
+ for (const chunk of framed) {
485
+ result.set(chunk, offset);
486
+ offset += chunk.byteLength;
487
+ }
488
+ return result;
489
+ };
490
+ var getRootPubKeys = ({
491
+ config,
492
+ deviceModel,
493
+ proofType,
494
+ allowDebugKeys
495
+ }) => {
496
+ const modelConfig = config[deviceModel];
497
+ if (!modelConfig || typeof modelConfig === "number") {
498
+ throw new Error(`Pubkeys for ${deviceModel} not found in config`);
499
+ }
500
+ const prod = proofType === "optiga" ? modelConfig.rootPubKeysOptiga ?? [] : modelConfig.rootPubKeysTropic ?? [];
501
+ if (!allowDebugKeys) return prod;
502
+ return [
503
+ ...prod,
504
+ ...proofType === "optiga" ? modelConfig.debug?.rootPubKeysOptiga ?? [] : modelConfig.debug?.rootPubKeysTropic ?? []
505
+ ];
506
+ };
507
+ var matchRootPubKeyToCertificate = ({
508
+ cert,
509
+ allRootPubKeys
510
+ }) => {
511
+ const verifySignatureFn = getVerifyFn(cert.signatureAlgorithm.algorithmName);
512
+ return allRootPubKeys.find(
513
+ (rootPubKey) => verifySignatureFn(
514
+ Uint8Array.from(Buffer.from(rootPubKey, "hex")),
515
+ cert.tbsCertificate.asn1.raw,
516
+ cert.signatureValue.bits.bytes
517
+ )
518
+ );
519
+ };
520
+ var parseModelFromDeviceCertSubject = (deviceCert) => {
521
+ const modelDescriptionBytes = findSubjectContentByOid(deviceCert, "2.5.4.3");
522
+ if (modelDescriptionBytes === null) return null;
523
+ return Buffer.from(modelDescriptionBytes.subarray(0, 4)).toString();
524
+ };
525
+ var parseSerialNumberFromDeviceCert = (deviceCert) => {
526
+ const serialNumber = findSubjectContentByOid(deviceCert, "2.5.4.5");
527
+ if (serialNumber === null) return void 0;
528
+ return Buffer.from(serialNumber).toString("hex");
529
+ };
530
+ var verifyAuthenticityProof = ({
531
+ proofType,
532
+ certificates,
533
+ signature,
534
+ signedData,
535
+ deviceModel,
536
+ config,
537
+ allowDebugKeys,
538
+ caPubKeyBlacklist: caPubKeyBlacklist2 = []
539
+ }) => {
540
+ const allRootPubKeys = getRootPubKeys({
541
+ config,
542
+ deviceModel,
543
+ proofType,
544
+ allowDebugKeys
545
+ });
546
+ const parsedCertificates = certificates.map(
547
+ (c) => parseCertificate(new Uint8Array(Buffer.from(c, "hex")))
548
+ );
549
+ const firstCertAlgName = parsedCertificates[0]?.signatureAlgorithm.algorithmName;
550
+ const expectedAlgorithmName = proofType === "optiga" ? "P-256" : "Ed25519";
551
+ if (firstCertAlgName !== expectedAlgorithmName) {
552
+ return { valid: false, error: "RESPONSE_MALFORMED" };
553
+ }
554
+ if (parsedCertificates.length !== 2) {
555
+ return { valid: false, error: "RESPONSE_MALFORMED" };
556
+ }
557
+ const [deviceCert, caCert] = parsedCertificates;
558
+ const deviceCertPubKey = Buffer.from(
559
+ deviceCert.tbsCertificate.subjectPublicKeyInfo.bits.bytes
560
+ ).toString("hex");
561
+ const deviceCertAlgName = deviceCert.signatureAlgorithm.algorithmName;
562
+ const caCertAlgName = caCert.signatureAlgorithm.algorithmName;
563
+ if (deviceCertAlgName !== caCertAlgName) {
564
+ return { valid: false, deviceCertPubKey, error: "RESPONSE_MALFORMED" };
565
+ }
566
+ validateCaCertExtensions(caCert, 0);
567
+ const verifySignatureFn = getVerifyFn(deviceCertAlgName);
568
+ const caPubKeyBytes = caCert.tbsCertificate.subjectPublicKeyInfo.bits.bytes;
569
+ const caPubKey = Buffer.from(caPubKeyBytes).toString("hex");
570
+ const rootPubKeyMatch = matchRootPubKeyToCertificate({ allRootPubKeys, cert: caCert });
571
+ if (rootPubKeyMatch === void 0) {
572
+ return { valid: false, caPubKey, deviceCertPubKey, error: "ROOT_PUBKEY_NOT_FOUND" };
573
+ }
574
+ if (caPubKeyBlacklist2.includes(caPubKey)) {
575
+ return {
576
+ valid: false,
577
+ caPubKey,
578
+ rootPubKey: rootPubKeyMatch,
579
+ deviceCertPubKey,
580
+ error: "CA_PUBKEY_BLACKLISTED"
581
+ };
582
+ }
583
+ const now = Date.now();
584
+ const isWithinValidity = (validity) => validity.from.getTime() <= now && now <= validity.to.getTime();
585
+ if (!isWithinValidity(caCert.tbsCertificate.validity) || !isWithinValidity(deviceCert.tbsCertificate.validity)) {
586
+ return {
587
+ valid: false,
588
+ caPubKey,
589
+ rootPubKey: rootPubKeyMatch,
590
+ deviceCertPubKey,
591
+ error: "CERTIFICATE_EXPIRED"
592
+ };
593
+ }
594
+ const modelFromSubject = parseModelFromDeviceCertSubject(deviceCert);
595
+ if (modelFromSubject !== deviceModel) {
596
+ return {
597
+ valid: false,
598
+ caPubKey,
599
+ rootPubKey: rootPubKeyMatch,
600
+ deviceCertPubKey,
601
+ error: "INVALID_DEVICE_MODEL"
602
+ };
603
+ }
604
+ const isDeviceCertValid = verifySignatureFn(
605
+ Uint8Array.from(caPubKeyBytes),
606
+ deviceCert.tbsCertificate.asn1.raw,
607
+ deviceCert.signatureValue.bits.bytes
608
+ );
609
+ if (!isDeviceCertValid) {
610
+ return {
611
+ valid: false,
612
+ caPubKey,
613
+ rootPubKey: rootPubKeyMatch,
614
+ deviceCertPubKey,
615
+ error: "INVALID_DEVICE_CERTIFICATE"
616
+ };
617
+ }
618
+ const isSignatureValid = verifySignatureFn(
619
+ Uint8Array.from(deviceCert.tbsCertificate.subjectPublicKeyInfo.bits.bytes),
620
+ signedData,
621
+ Uint8Array.from(Buffer.from(signature, "hex"))
622
+ );
623
+ if (!isSignatureValid) {
624
+ return {
625
+ valid: false,
626
+ caPubKey,
627
+ rootPubKey: rootPubKeyMatch,
628
+ deviceCertPubKey,
629
+ error: "INVALID_DEVICE_SIGNATURE"
630
+ };
631
+ }
632
+ return {
633
+ valid: true,
634
+ caPubKey,
635
+ rootPubKey: rootPubKeyMatch,
636
+ deviceCertPubKey,
637
+ serialNumber: parseSerialNumberFromDeviceCert(deviceCert)
638
+ };
639
+ };
640
+
641
+ // src/deviceAuthenticity/index.ts
642
+ var caPubKeyBlacklist = [];
643
+ var modelConfigOf = (config, deviceModel) => {
644
+ const modelConfig = config[deviceModel];
645
+ return modelConfig && typeof modelConfig !== "number" ? modelConfig : void 0;
646
+ };
647
+ var getRequiredDeviceAuthenticityLayers = (deviceModel) => deviceModel === "T3W1" ? ["optiga", "tropic"] : ["optiga"];
648
+ var isTropicExpected = (deviceModel) => getRequiredDeviceAuthenticityLayers(deviceModel).includes("tropic");
649
+ var matchedDebugKey = (config, deviceModel, rootPubKey) => {
650
+ if (!rootPubKey) return false;
651
+ const m = modelConfigOf(config, deviceModel);
652
+ if (!m?.debug) return false;
653
+ return [...m.debug.rootPubKeysOptiga ?? [], ...m.debug.rootPubKeysTropic ?? []].includes(
654
+ rootPubKey
655
+ );
656
+ };
657
+ var authenticateDeviceFromProof = ({
658
+ proof,
659
+ challenge,
660
+ deviceModel,
661
+ config = deviceAuthenticityConfig,
662
+ allowDebugKeys
663
+ }) => {
664
+ try {
665
+ const signedData = prepareDeviceAuthenticityData({ payload: challenge });
666
+ const common = { signedData, deviceModel, config, allowDebugKeys, caPubKeyBlacklist };
667
+ const { optiga_certificates: optigaCertificates, optiga_signature: optigaSignature } = proof;
668
+ if (!optigaSignature || !optigaCertificates?.length) {
669
+ return { verified: false, error: "RESPONSE_PAYLOAD_MISSING" };
670
+ }
671
+ const optiga = verifyAuthenticityProof({
672
+ ...common,
673
+ proofType: "optiga",
674
+ certificates: optigaCertificates,
675
+ signature: optigaSignature
676
+ });
677
+ if (!optiga.valid) {
678
+ return {
679
+ verified: false,
680
+ deviceCertPubKey: optiga.deviceCertPubKey,
681
+ caPubKey: optiga.caPubKey,
682
+ rootPubKey: optiga.rootPubKey,
683
+ error: optiga.error
684
+ };
685
+ }
686
+ let usedDebugKey = matchedDebugKey(config, deviceModel, optiga.rootPubKey);
687
+ if (isTropicExpected(deviceModel)) {
688
+ const { tropic_certificates: tropicCertificates, tropic_signature: tropicSignature } = proof;
689
+ if (!tropicSignature || !tropicCertificates?.length) {
690
+ return { verified: false, error: "RESPONSE_PAYLOAD_MISSING" };
691
+ }
692
+ const tropic = verifyAuthenticityProof({
693
+ ...common,
694
+ proofType: "tropic",
695
+ certificates: tropicCertificates,
696
+ signature: tropicSignature
697
+ });
698
+ if (!tropic.valid) {
699
+ return { verified: false, error: tropic.error };
700
+ }
701
+ usedDebugKey = usedDebugKey || matchedDebugKey(config, deviceModel, tropic.rootPubKey);
702
+ }
703
+ const deviceId = Buffer.from(
704
+ sha3Hash(Uint8Array.from(Buffer.from(optiga.deviceCertPubKey, "hex")))
705
+ ).toString("hex");
706
+ return {
707
+ verified: true,
708
+ deviceId,
709
+ deviceCertPubKey: optiga.deviceCertPubKey,
710
+ serialNumber: optiga.serialNumber,
711
+ rootPubKey: optiga.rootPubKey,
712
+ caPubKey: optiga.caPubKey,
713
+ usedDebugKey
714
+ };
715
+ } catch (error) {
716
+ return {
717
+ verified: false,
718
+ error: `RESPONSE_MALFORMED: ${error?.message ?? "parse error"}`
719
+ };
720
+ }
721
+ };
15
722
 
16
723
  // src/utils/sdkEventBus.ts
17
724
  var listeners = /* @__PURE__ */ new Set();
@@ -323,6 +1030,10 @@ var _TrezorAdapter = class _TrezorAdapter {
323
1030
  const { code } = error;
324
1031
  return code === HardwareErrorCode.DeviceNotFound || code === HardwareErrorCode.DeviceDisconnected;
325
1032
  }
1033
+ static _isRecoverableProtocolResidueError(error) {
1034
+ const protocolError = error;
1035
+ return protocolError.name === "TrezorProtocolError" && protocolError.code === "Malformed protocol format" && protocolError.message === "Malformed protocol format";
1036
+ }
326
1037
  /**
327
1038
  * Race a promise against an abort signal. Copied from LedgerAdapter:
328
1039
  * IConnector.call() can't actually be cancelled at the protocol level, but
@@ -353,8 +1064,8 @@ var _TrezorAdapter = class _TrezorAdapter {
353
1064
  });
354
1065
  }
355
1066
  async _connectSession(connectId, signal) {
356
- const connectPromise = this._connector.connect(connectId);
357
- const session = await (async () => {
1067
+ const connectOnce = async () => {
1068
+ const connectPromise = this._connector.connect(connectId);
358
1069
  if (!signal) return connectPromise;
359
1070
  try {
360
1071
  return await _TrezorAdapter._abortable(signal, connectPromise);
@@ -365,7 +1076,19 @@ var _TrezorAdapter = class _TrezorAdapter {
365
1076
  );
366
1077
  throw error;
367
1078
  }
368
- })();
1079
+ };
1080
+ let session;
1081
+ try {
1082
+ session = await connectOnce();
1083
+ } catch (error) {
1084
+ if (signal?.aborted || !_TrezorAdapter._isRecoverableProtocolResidueError(error)) {
1085
+ throw error;
1086
+ }
1087
+ debugLog("[TrezorAdapter] retrying connect after draining a stale THP frame", {
1088
+ connectId
1089
+ });
1090
+ session = await connectOnce();
1091
+ }
369
1092
  if (this._disconnectRequested.delete(connectId)) {
370
1093
  await this._connector.disconnect(session.sessionId).catch(() => void 0);
371
1094
  throw Object.assign(new Error("Trezor connection aborted"), {
@@ -529,6 +1252,64 @@ var _TrezorAdapter = class _TrezorAdapter {
529
1252
  getFeatures(connectId) {
530
1253
  return this._callDeviceManagerMethod("getFeatures", connectId, {});
531
1254
  }
1255
+ /**
1256
+ * Device authenticity attestation. Sends a host-generated challenge, has the
1257
+ * device sign it with its secure-element key, then verifies the returned
1258
+ * certificate chain up to a trusted Trezor root CA. On success returns a
1259
+ * `deviceId` that uniquely identifies the physical device, survives wipe, and
1260
+ * cannot be forged from a seed. Only trust the result when `verified` is true
1261
+ * (and `usedDebugKey` is false in production).
1262
+ *
1263
+ * Requires a secure-element model (Safe 3 = T2B1/T3B1, Safe 5 = T3T1, T3W1)
1264
+ * and an on-device confirmation. Older models (T1B1, T2T1) will fail.
1265
+ *
1266
+ * `dangerouslyAllowDebugKeys` accepts simulator / development root keys — NEVER
1267
+ * enable it in a production accounting flow, it lets an emulator mint any id.
1268
+ */
1269
+ async verifyDeviceAuthenticity(connectId, params = {}) {
1270
+ if (params.challenge && params.dangerouslyAllowDebugKeys) {
1271
+ return failure(
1272
+ HardwareErrorCode.InvalidParams,
1273
+ "Debug attestation roots cannot be used with a server challenge"
1274
+ );
1275
+ }
1276
+ if (params.challenge && !/^[0-9a-fA-F]{64}$/.test(params.challenge)) {
1277
+ return failure(
1278
+ HardwareErrorCode.InvalidParams,
1279
+ "Device authenticity challenge must be exactly 32 bytes encoded as hex"
1280
+ );
1281
+ }
1282
+ const featuresRes = await this.getFeatures(connectId);
1283
+ if (!featuresRes.success) return featuresRes;
1284
+ const internalModel = featuresRes.payload.internal_model;
1285
+ if (!internalModel) {
1286
+ return failure(HardwareErrorCode.UnknownError, "Device internal_model unavailable");
1287
+ }
1288
+ const challengeHex = params.challenge?.toLowerCase() ?? Buffer.from(randomBytes(32)).toString("hex");
1289
+ const challenge = Buffer.from(challengeHex, "hex");
1290
+ const proofRes = await this._callDeviceManagerMethod(
1291
+ "authenticateDevice",
1292
+ connectId,
1293
+ { challenge: challengeHex }
1294
+ );
1295
+ if (!proofRes.success) return proofRes;
1296
+ const proof = proofRes.payload;
1297
+ const verification = authenticateDeviceFromProof({
1298
+ proof,
1299
+ challenge,
1300
+ deviceModel: internalModel,
1301
+ allowDebugKeys: params.dangerouslyAllowDebugKeys
1302
+ });
1303
+ return success({
1304
+ ...verification,
1305
+ vendor: "trezor",
1306
+ trezorProof: {
1307
+ challenge: challengeHex,
1308
+ deviceModel: internalModel,
1309
+ proof
1310
+ }
1311
+ });
1312
+ }
532
1313
  deviceSettings(connectId, params) {
533
1314
  return this._callDeviceManagerMethod(
534
1315
  "deviceSettings",
@@ -1313,10 +2094,12 @@ export {
1313
2094
  TREZOR_BLE_UUIDS,
1314
2095
  TREZOR_SAFE_7_MODEL,
1315
2096
  TrezorAdapter,
2097
+ authenticateDeviceFromProof,
1316
2098
  buildEthereumDefinitionsForPath,
1317
2099
  buildEthereumDefinitionsForSignTx,
1318
2100
  fetchEthereumDefinitions,
1319
2101
  fetchSolanaTokenDefinition,
2102
+ getRequiredDeviceAuthenticityLayers,
1320
2103
  getSlip44FromPath,
1321
2104
  isTrezorBleDescriptor,
1322
2105
  isTrezorBleServiceUuid,