@ukeyfe/react-native-nfc-litecard 1.0.1 → 1.0.3

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/README.zh.md ADDED
@@ -0,0 +1,499 @@
1
+ # @ukeyfe/react-native-nfc-litecard
2
+
3
+ [English](./README.md) | 中文
4
+
5
+ React Native NFC 读写库,适用于 **MIFARE Ultralight AES**(MF0AES(H)20),专为 LiteCard 助记词存储设计。
6
+
7
+ > **设计原则**:库只返回状态码(`code`)和数据(`data`),**不**提供面向用户的提示文案。调用方应根据 `NfcStatusCode` 自行映射本地化字符串。
8
+
9
+ ## 功能概览
10
+
11
+ | 功能 | 方法 | 说明 |
12
+ |------|------|------|
13
+ | 检测卡片 | `checkCard()` | 检测卡片是否为空或已有数据 |
14
+ | 读取助记词 | `readMnemonic()` | 读取 BIP-39 助记词(需密码) |
15
+ | 读取昵称 | `readUserNickname()` | 读取卡片上的用户昵称 |
16
+ | 读取重试次数 | `readMnemonicRetryCount()` | 读取 PIN 重试计数器 |
17
+ | 重置重试次数 | `resetRetryCountTo10()` | 将 PIN 重试计数器重置为默认值(10) |
18
+ | 初始化卡片 | `initializeCard()` | 用出厂密码认证,写入助记词 + 设置新密码 |
19
+ | 更新卡片 | `updateCard()` | 更新助记词和密码(需旧密码) |
20
+ | 修改密码 | `updatePassword()` | 仅修改密码(需旧密码) |
21
+ | 写入昵称 | `writeUserNickname()` | 写入用户昵称 |
22
+ | 重置卡片 | `resetCard()` | 清除助记词数据,设置新密码,保持保护开启 |
23
+ | 卡片版本 | `getCardVersion()` | 读取卡片产品版本信息(无需认证) |
24
+ | 真伪校验 | `readOriginality()` | 读取 ECC 原厂签名,验证 NXP 正品 |
25
+
26
+ ## 安装
27
+
28
+ ```bash
29
+ npm install @ukeyfe/react-native-nfc-litecard
30
+ ```
31
+
32
+ ### 对等依赖
33
+
34
+ 本库需要以下对等依赖:
35
+
36
+ ```bash
37
+ npm install react-native react-native-nfc-manager
38
+ ```
39
+
40
+ ## 快速开始
41
+
42
+ ```typescript
43
+ import {
44
+ NfcStatusCode,
45
+ checkCard,
46
+ readMnemonic,
47
+ initializeCard,
48
+ updateCard,
49
+ updatePassword,
50
+ writeUserNickname,
51
+ readUserNickname,
52
+ resetCard,
53
+ readMnemonicRetryCount,
54
+ resetRetryCountTo10,
55
+ getCardVersion,
56
+ readOriginality,
57
+ } from '@ukeyfe/react-native-nfc-litecard';
58
+ ```
59
+
60
+ ## API 文档
61
+
62
+ ### `checkCard(password?, onCardIdentified?)`
63
+
64
+ 检测卡片状态(空卡 / 有数据)。
65
+
66
+ **无密码(快速探测):**
67
+ ```typescript
68
+ const result = await checkCard();
69
+ if (result.code === NfcStatusCode.CHECK_EMPTY) {
70
+ // 空卡 – 可以初始化
71
+ } else if (result.code === NfcStatusCode.CHECK_HAS_DATA) {
72
+ // 有数据(或已读保护,无法确定)
73
+ }
74
+ ```
75
+
76
+ **有密码(认证后深度检测,适用于读保护卡):**
77
+ ```typescript
78
+ const result = await checkCard('password');
79
+ if (result.code === NfcStatusCode.CHECK_EMPTY) {
80
+ // 空卡 – 可以写入
81
+ } else if (result.code === NfcStatusCode.CHECK_HAS_DATA) {
82
+ // 已有合法助记词备份,类型:result.data?.type
83
+ } else if (result.code === NfcStatusCode.AUTH_WRONG_PASSWORD) {
84
+ // 密码错误
85
+ }
86
+ ```
87
+
88
+ **参数:**
89
+ | 参数 | 类型 | 必填 | 说明 |
90
+ |------|------|------|------|
91
+ | `password` | `string` | 否 | 卡片保护密码。不传时直接读取(适合未加密卡片);传入时先进行 AES 认证,再读取全量数据并 CRC16 校验(适合读保护卡,结果更准确)。 |
92
+ | `onCardIdentified` | `() => void` | 否 | NFC 会话建立后回调,可用于 UI 提示如"已检测到卡片"。 |
93
+
94
+ **返回状态码:**
95
+ | 状态码 | 含义 |
96
+ |--------|------|
97
+ | `NfcStatusCode.CHECK_EMPTY` (10104) | 空卡 – 无助记词数据 |
98
+ | `NfcStatusCode.CHECK_HAS_DATA` (10105) | 卡片有数据。传入密码时,`data.type` 包含助记词类型(如 `"12 words (128-bit)"`)。 |
99
+ | `NfcStatusCode.AUTH_WRONG_PASSWORD` (40002) | 密码错误(仅在传入密码时可能出现) |
100
+ | `NfcStatusCode.NFC_CONNECT_FAILED` (40001) | NFC 连接失败 – 未贴卡或设备不支持 |
101
+
102
+ ---
103
+
104
+ ### `readMnemonic(password, onCardIdentified?)`
105
+
106
+ 读取助记词(需密码认证)。认证前自动递减重试计数器,认证成功后重置为 10。
107
+
108
+ ```typescript
109
+ const result = await readMnemonic('your-password');
110
+ if (result.success) {
111
+ console.log('助记词:', result.data?.mnemonic);
112
+ console.log('类型:', result.data?.type); // "12 words (128-bit)"
113
+ console.log('昵称:', result.data?.nickname);
114
+ console.log('重试次数:', result.data?.retryCount);
115
+ }
116
+ ```
117
+
118
+ **参数:**
119
+ | 参数 | 类型 | 必填 | 说明 |
120
+ |------|------|------|------|
121
+ | `password` | `string` | 是 | 卡片保护密码,用于 AES-128 认证 |
122
+ | `onCardIdentified` | `() => void` | 否 | 认证成功、开始读取前回调,可用于 UI 提示如"正在读取"。 |
123
+
124
+ **返回 `data` 字段:**
125
+ | 字段 | 类型 | 说明 |
126
+ |------|------|------|
127
+ | `mnemonic` | `string` | BIP-39 助记词(如 `"abandon abandon ... about"`) |
128
+ | `type` | `string` | 助记词类型(如 `"12 words (128-bit)"`、`"24 words (256-bit)"`) |
129
+ | `entropyHex` | `string` | 熵的十六进制字符串 |
130
+ | `rawBytes` | `string` | 卡上原始数据的十六进制字符串(类型 + 熵) |
131
+ | `nickname` | `string` | 用户昵称(如卡上已设置) |
132
+ | `retryCount` | `number` | 认证成功后的重试次数(正常为 10) |
133
+
134
+ ---
135
+
136
+ ### `initializeCard(mnemonic, newPassword, defaultPassword, onCardIdentified?)`
137
+
138
+ 初始化卡片:用出厂默认密码认证,写入助记词,设置新密码。卡片必须已启用 AES 保护(出厂默认)。
139
+
140
+ ```typescript
141
+ const result = await initializeCard(
142
+ 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
143
+ 'your-password',
144
+ '000000' // 出厂默认密码
145
+ );
146
+ if (result.code === NfcStatusCode.INIT_SUCCESS) {
147
+ console.log('初始化成功');
148
+ }
149
+ ```
150
+
151
+ **参数:**
152
+ | 参数 | 类型 | 必填 | 说明 |
153
+ |------|------|------|------|
154
+ | `mnemonic` | `string` | 是 | BIP-39 助记词,支持 12/15/18/21/24 个词。库会转为熵 + CRC16 写入卡片。 |
155
+ | `newPassword` | `string` | 是 | 新保护密码,用于派生 AES-128 密钥写入卡片。 |
156
+ | `defaultPassword` | `string` | 是 | 卡片当前(出厂默认)密码,用于认证。 |
157
+ | `onCardIdentified` | `() => void` | 否 | 认证成功、开始写入前回调,可用于 UI 提示如"正在写入"。 |
158
+
159
+ ---
160
+
161
+ ### `updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified?, options?)`
162
+
163
+ 更新卡片:使用旧密码认证后,写入新助记词和新密码。认证前自动递减重试计数器。
164
+
165
+ ```typescript
166
+ const result = await updateCard('old-password', 'new-password', 'new mnemonic words ...');
167
+ if (result.code === NfcStatusCode.WRITE_SUCCESS) {
168
+ console.log('更新成功');
169
+ }
170
+ ```
171
+
172
+ **预检查已有备份:**
173
+
174
+ ```typescript
175
+ const result = await updateCard('old-password', 'new-password', 'mnemonic ...', undefined, {
176
+ precheckExistingMnemonic: true,
177
+ });
178
+ if (result.code === NfcStatusCode.PRECHECK_HAS_BACKUP) {
179
+ // 卡片已有合法助记词备份,跳过写入
180
+ console.log('已有备份,类型:', result.data?.type);
181
+ } else if (result.code === NfcStatusCode.WRITE_SUCCESS) {
182
+ console.log('写入成功');
183
+ }
184
+ ```
185
+
186
+ **参数:**
187
+ | 参数 | 类型 | 必填 | 说明 |
188
+ |------|------|------|------|
189
+ | `oldPassword` | `string` | 是 | 当前卡片密码,用于 AES 认证 |
190
+ | `newPassword` | `string` | 是 | 新密码,写入后卡片使用此密码保护 |
191
+ | `newMnemonic` | `string` | 是 | 新 BIP-39 助记词(12/15/18/21/24 个词) |
192
+ | `onCardIdentified` | `() => void` | 否 | 认证成功后回调,可用于 UI 提示如"正在写入"。 |
193
+ | `options.precheckExistingMnemonic` | `boolean` | 否 | 为 `true` 时,认证后先读取卡片数据并校验 CRC16,若已有合法助记词则返回 `PRECHECK_HAS_BACKUP`,不执行写入。 |
194
+
195
+ ---
196
+
197
+ ### `updatePassword(oldPassword, newPassword, onCardIdentified?)`
198
+
199
+ 仅修改密码,卡片上的助记词数据不变。认证前自动递减重试计数器。
200
+
201
+ ```typescript
202
+ const result = await updatePassword('old-password', 'new-password');
203
+ if (result.code === NfcStatusCode.UPDATE_PASSWORD_SUCCESS) {
204
+ console.log('密码修改成功');
205
+ }
206
+ ```
207
+
208
+ **参数:**
209
+ | 参数 | 类型 | 必填 | 说明 |
210
+ |------|------|------|------|
211
+ | `oldPassword` | `string` | 是 | 当前卡片密码,用于 AES 认证 |
212
+ | `newPassword` | `string` | 是 | 新密码,修改后卡片使用此密码 |
213
+ | `onCardIdentified` | `() => void` | 否 | 认证成功后回调 |
214
+
215
+ ---
216
+
217
+ ### `writeUserNickname(password, nickname, onCardIdentified?)`
218
+
219
+ 写入用户昵称。昵称以 UTF-8 编码,最大 12 字节(超出部分截断)。
220
+
221
+ ```typescript
222
+ const result = await writeUserNickname('your-password', 'MyCard');
223
+ if (result.code === NfcStatusCode.WRITE_NICKNAME_SUCCESS) {
224
+ console.log('昵称写入成功');
225
+ }
226
+ ```
227
+
228
+ **参数:**
229
+ | 参数 | 类型 | 必填 | 说明 |
230
+ |------|------|------|------|
231
+ | `password` | `string` | 是 | 卡片保护密码,用于 AES 认证 |
232
+ | `nickname` | `string` | 是 | 用户昵称;UTF-8 最大 12 字节(约 4 个中文字符或 12 个英文字母) |
233
+ | `onCardIdentified` | `() => void` | 否 | 认证成功后回调 |
234
+
235
+ ---
236
+
237
+ ### `readUserNickname(password?, onCardIdentified?)`
238
+
239
+ 读取卡片上的用户昵称。
240
+
241
+ ```typescript
242
+ const result = await readUserNickname('your-password');
243
+ if (result.success) {
244
+ console.log('昵称:', result.data?.nickname);
245
+ }
246
+ ```
247
+
248
+ **参数:**
249
+ | 参数 | 类型 | 必填 | 说明 |
250
+ |------|------|------|------|
251
+ | `password` | `string` | 否 | 卡片密码。读保护启用时(`PROT=1`)必填,否则可选。 |
252
+ | `onCardIdentified` | `() => void` | 否 | 认证成功后回调 |
253
+
254
+ ---
255
+
256
+ ### `resetCard(password, newPassword, onCardIdentified?)`
257
+
258
+ 重置卡片:清除助记词数据,设置新密码,关闭读写保护。昵称保留不变。
259
+
260
+ ```typescript
261
+ const result = await resetCard('old-password', 'new-password');
262
+ if (result.code === NfcStatusCode.RESET_SUCCESS) {
263
+ console.log('重置成功');
264
+ }
265
+ ```
266
+
267
+ > ⚠️ 此操作不可逆,卡片上的助记词数据将被永久擦除。
268
+
269
+ **参数:**
270
+ | 参数 | 类型 | 必填 | 说明 |
271
+ |------|------|------|------|
272
+ | `password` | `string \| undefined` | 否 | 当前卡片密码。卡片已保护时必填,否则传 `undefined`。认证前自动递减重试计数器。 |
273
+ | `newPassword` | `string` | 是 | 重置后要设置的密码。 |
274
+ | `onCardIdentified` | `() => void` | 否 | 认证成功后回调 |
275
+
276
+ ---
277
+
278
+ ### `readMnemonicRetryCount(onCardIdentified?)`
279
+
280
+ 读取卡片上的 PIN 重试计数器。无需密码认证(计数器页面在保护区域之外)。
281
+
282
+ ```typescript
283
+ const result = await readMnemonicRetryCount();
284
+ if (result.success) {
285
+ console.log('剩余重试次数:', result.data?.retryCount);
286
+ }
287
+ ```
288
+
289
+ **参数:**
290
+ | 参数 | 类型 | 必填 | 说明 |
291
+ |------|------|------|------|
292
+ | `onCardIdentified` | `() => void` | 否 | NFC 会话建立后回调 |
293
+
294
+ ---
295
+
296
+ ### `resetRetryCountTo10(onCardIdentified?)`
297
+
298
+ 将 PIN 重试计数器重置为默认值(10)。无需密码认证。
299
+
300
+ ```typescript
301
+ const result = await resetRetryCountTo10();
302
+ ```
303
+
304
+ **参数:**
305
+ | 参数 | 类型 | 必填 | 说明 |
306
+ |------|------|------|------|
307
+ | `onCardIdentified` | `() => void` | 否 | NFC 会话建立后回调 |
308
+
309
+ ---
310
+
311
+ ### `getCardVersion(onCardIdentified?)`
312
+
313
+ 读取卡片产品版本信息。无需密码认证。
314
+
315
+ ```typescript
316
+ const result = await getCardVersion();
317
+ if (result.success) {
318
+ console.log('厂商:', result.data?.version?.vendorId); // 0x04 = NXP
319
+ console.log('类型:', result.data?.version?.productType); // 0x03 = Ultralight
320
+ console.log('版本:', result.data?.version?.majorVersion); // 0x04 = AES
321
+ }
322
+ ```
323
+
324
+ ---
325
+
326
+ ### `readOriginality(onCardIdentified?)`
327
+
328
+ 读取 ECC 原厂签名,验证卡片是否为 NXP 正品。无需密码认证。
329
+
330
+ ```typescript
331
+ const result = await readOriginality();
332
+ if (result.success) {
333
+ console.log('签名:', result.data?.signature); // 96 字符 hex 字符串
334
+ }
335
+ ```
336
+
337
+ ---
338
+
339
+ ### NFC 锁管理
340
+
341
+ 用于应用层 NFC 会话生命周期管理:
342
+
343
+ ```typescript
344
+ import {
345
+ isNfcOperationLocked,
346
+ releaseNfcOperationLock,
347
+ markNfcOperationCancelledByCleanup,
348
+ consumeNfcOperationCancelledByCleanup,
349
+ } from '@ukeyfe/react-native-nfc-litecard';
350
+ ```
351
+
352
+ | 方法 | 说明 |
353
+ |------|------|
354
+ | `isNfcOperationLocked()` | 检查 NFC 操作锁是否被持有 |
355
+ | `releaseNfcOperationLock()` | 强制释放锁(用于页面卸载时) |
356
+ | `markNfcOperationCancelledByCleanup()` | 标记当前操作被清理中断 |
357
+ | `consumeNfcOperationCancelledByCleanup()` | 消费清理标志(返回是否被设置过) |
358
+
359
+ ## NfcResult 结构
360
+
361
+ 所有 API 返回统一的 `NfcResult`:
362
+
363
+ ```typescript
364
+ interface NfcResult {
365
+ code: number; // 状态码 – 与 NfcStatusCode 比较
366
+ success: boolean; // 操作是否成功
367
+ data?: { // 可选数据,仅部分操作返回
368
+ mnemonic?: string;
369
+ type?: string;
370
+ entropyHex?: string;
371
+ rawBytes?: string;
372
+ nickname?: string;
373
+ retryCount?: number;
374
+ aesKeyHex?: string;
375
+ crc16?: number;
376
+ };
377
+ }
378
+ ```
379
+
380
+ ## 错误处理示例
381
+
382
+ ```typescript
383
+ import { NfcStatusCode, readMnemonic } from '@ukeyfe/react-native-nfc-litecard';
384
+
385
+ const result = await readMnemonic('password');
386
+
387
+ switch (result.code) {
388
+ case NfcStatusCode.READ_SUCCESS:
389
+ console.log('读取成功:', result.data?.mnemonic);
390
+ break;
391
+ case NfcStatusCode.AUTH_WRONG_PASSWORD:
392
+ alert('密码错误');
393
+ break;
394
+ case NfcStatusCode.NFC_CONNECT_FAILED:
395
+ alert('NFC 连接失败,请重新贴卡');
396
+ break;
397
+ case NfcStatusCode.NFC_USER_CANCELED:
398
+ // iOS 用户取消 – 静默处理
399
+ break;
400
+ case NfcStatusCode.READ_TIMEOUT:
401
+ alert('读取超时 – 请移开卡片后重新贴卡');
402
+ break;
403
+ case NfcStatusCode.RETRY_COUNT_EXHAUSTED:
404
+ alert('重试次数已耗尽 – 卡片已锁定');
405
+ break;
406
+ default:
407
+ alert('操作失败');
408
+ }
409
+ ```
410
+
411
+ ## 状态码
412
+
413
+ ### 成功码
414
+
415
+ | 常量 | 值 | 说明 |
416
+ |------|------|------|
417
+ | `READ_SUCCESS` | 10102 | 助记词读取成功 |
418
+ | `READ_NICKNAME_SUCCESS` | 10103 | 昵称读取成功 |
419
+ | `CHECK_EMPTY` | 10104 | 空卡 |
420
+ | `CHECK_HAS_DATA` | 10105 | 卡片有数据 |
421
+ | `READ_RETRY_COUNT_SUCCESS` | 10106 | 重试次数读取成功 |
422
+ | `GET_VERSION_SUCCESS` | 10107 | 卡片版本读取成功 |
423
+ | `READ_SIG_SUCCESS` | 10108 | 原厂签名读取成功 |
424
+ | `INIT_SUCCESS` | 10201 | 初始化成功 |
425
+ | `WRITE_SUCCESS` | 10203 | 写入/更新成功 |
426
+ | `UPDATE_PASSWORD_SUCCESS` | 10204 | 密码修改成功 |
427
+ | `WRITE_NICKNAME_SUCCESS` | 10205 | 昵称写入成功 |
428
+ | `RESET_SUCCESS` | 10206 | 卡片重置成功 |
429
+ | `PRECHECK_HAS_BACKUP` | 10207 | 卡片已有合法备份,跳过写入 |
430
+
431
+ ### 错误码
432
+
433
+ | 常量 | 值 | 说明 |
434
+ |------|------|------|
435
+ | `NFC_CONNECT_FAILED` | 40001 | NFC 连接失败 |
436
+ | `AUTH_WRONG_PASSWORD` | 40002 | 密码错误 |
437
+ | `AUTH_INVALID_RESPONSE` | 40003 | 认证响应无效 |
438
+ | `AUTH_VERIFY_FAILED` | 40004 | 认证校验失败 |
439
+ | `READ_FAILED` | 40005 | 读取失败 |
440
+ | `WRITE_FAILED` | 40006 | 写入失败 |
441
+ | `INVALID_MNEMONIC` | 40007 | 助记词无效 |
442
+ | `UNSUPPORTED_MNEMONIC_LENGTH` | 40008 | 不支持的助记词长度 |
443
+ | `INVALID_CARD_DATA` | 40009 | 卡片数据无效 |
444
+ | `UNKNOWN_ERROR` | 40010 | 未知错误 |
445
+ | `NFC_USER_CANCELED` | 40011 | 用户取消 NFC 扫描(iOS) |
446
+ | `READ_TIMEOUT` | 40012 | 读取超时 |
447
+ | `NFC_LOCK_TIMEOUT` | 40013 | NFC 锁超时 |
448
+ | `CRC16_CHECK_FAILED` | 40014 | CRC16 校验失败 |
449
+ | `RETRY_COUNT_EXHAUSTED` | 40015 | PIN 重试次数耗尽,卡片已锁定 |
450
+
451
+ ## 存储格式
452
+
453
+ 卡片使用熵压缩方式存储 BIP-39 助记词:
454
+
455
+ ```
456
+ [类型 1B] [熵 16-32B] [CRC16 2B]
457
+ ```
458
+
459
+ | 类型 | 助记词长度 | 熵大小 |
460
+ |------|-----------|--------|
461
+ | 0x01 | 12 个词 | 16 字节(128 位) |
462
+ | 0x02 | 15 个词 | 20 字节(160 位) |
463
+ | 0x03 | 18 个词 | 24 字节(192 位) |
464
+ | 0x04 | 21 个词 | 28 字节(224 位) |
465
+ | 0x05 | 24 个词 | 32 字节(256 位) |
466
+
467
+ ## 安全性
468
+
469
+ - **AES-128 硬件级双向认证**(3-pass)
470
+ - **SHA-256 密钥派生**:密码 → SHA-256 → 取前 16 字节作为 AES 密钥
471
+ - **CRC16-Modbus 校验和**:数据完整性校验
472
+ - **CMAC 安全消息**:认证后所有命令附带 AES-CMAC(NIST 800-38B)防篡改
473
+ - **PIN 重试计数器**:密码错误时自动递减,认证成功后重置为 10
474
+ - **安全随机数**:认证使用 `crypto.getRandomValues()`(需要 Hermes ≥ 0.72 或 `react-native-get-random-values` polyfill)
475
+
476
+ ## 项目结构
477
+
478
+ ```
479
+ src/
480
+ ├── index.ts # 公共 API 导出
481
+ ├── constants.ts # 共享常量(页地址、NFC 指令、助记词类型)
482
+ ├── types.ts # 统一 NfcStatusCode、NfcResult 接口、错误映射
483
+ ├── crypto.ts # AES 加解密、密钥派生、安全随机数
484
+ ├── utils.ts # CRC16、十六进制转换、数组工具
485
+ ├── nfc-core.ts # NFC 锁、transceive、认证、重试计数器
486
+ ├── reader.ts # 读取 API
487
+ └── writer.ts # 写入 API
488
+ ```
489
+
490
+ ## 平台支持
491
+
492
+ | 平台 | 技术 | 要求 |
493
+ |------|------|------|
494
+ | iOS | MifareIOS | iPhone 7 及以上 |
495
+ | Android | NfcA | 支持 NFC 的设备 |
496
+
497
+ ## 许可证
498
+
499
+ MIT
@@ -11,6 +11,10 @@ export declare const CMD_FAST_READ = 58;
11
11
  export declare const CMD_AUTH_PART1 = 26;
12
12
  /** AES authentication part 2 */
13
13
  export declare const CMD_AUTH_PART2 = 175;
14
+ /** GET_VERSION command - retrieve product version info */
15
+ export declare const CMD_GET_VERSION = 96;
16
+ /** READ_SIG command - read ECC originality signature */
17
+ export declare const CMD_READ_SIG = 60;
14
18
  /** Data protection key slot (Key0) */
15
19
  export declare const KEY_NO_DATA_PROT = 0;
16
20
  /** Bytes per page */
@@ -43,6 +47,10 @@ export declare const MNEMONIC_TYPE_18 = 3;
43
47
  export declare const MNEMONIC_TYPE_21 = 4;
44
48
  /** 24-word mnemonic (256-bit entropy, 32 bytes) */
45
49
  export declare const MNEMONIC_TYPE_24 = 5;
50
+ /** Mnemonic data end page (stops before nickname area) */
51
+ export declare const MNEMONIC_PAGE_END: number;
52
+ /** Mnemonic data size: (0x24 - 0x08 + 1) * 4 = 116 bytes */
53
+ export declare const MNEMONIC_MEMORY_SIZE: number;
46
54
  /** Nickname start page */
47
55
  export declare const USER_NICKNAME_PAGE_START: number;
48
56
  /** Nickname end page */
@@ -55,3 +63,25 @@ export declare const RETRY_COUNTER_PAGE: number;
55
63
  export declare const RETRY_COUNTER_OFFSET: number;
56
64
  /** Default retry limit, restored after a successful authentication */
57
65
  export declare const DEFAULT_PIN_RETRY_COUNT = 10;
66
+ /** SEC_MSG_ACT bit mask in CFG0 byte 0 (bit 1) */
67
+ export declare const SEC_MSG_ACT_MASK = 2;
68
+ export declare const DELAY: {
69
+ /** Delay after requestNfcTech on Android */
70
+ readonly ANDROID_POST_TECH: 300;
71
+ /** Delay after releaseNfcTech (normal) */
72
+ readonly IOS_POST_RELEASE: 100;
73
+ readonly ANDROID_POST_RELEASE: 800;
74
+ /** Delay after releaseNfcTech (forced, e.g. after timeout) */
75
+ readonly IOS_POST_RELEASE_FORCED: 300;
76
+ readonly ANDROID_POST_RELEASE_FORCED: 1000;
77
+ /** Delay between page writes on iOS */
78
+ readonly IOS_PAGE_WRITE: 20;
79
+ /** Delay between AES key page writes on iOS */
80
+ readonly IOS_KEY_WRITE: 10;
81
+ /** Delay between nickname page writes on iOS */
82
+ readonly IOS_NICKNAME_WRITE: 100;
83
+ /** Delay between config operations on iOS */
84
+ readonly IOS_CONFIG: 80;
85
+ /** Keep-alive frequency (every N pages) during writes */
86
+ readonly KEEPALIVE_FREQ: 4;
87
+ };
package/dist/constants.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Shared constants for MIFARE Ultralight AES (MF0AES(H)20) NFC operations.
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DEFAULT_PIN_RETRY_COUNT = exports.RETRY_COUNTER_OFFSET = exports.RETRY_COUNTER_PAGE = exports.USER_NICKNAME_MAX_LENGTH = exports.USER_NICKNAME_PAGE_END = exports.USER_NICKNAME_PAGE_START = exports.MNEMONIC_TYPE_24 = exports.MNEMONIC_TYPE_21 = exports.MNEMONIC_TYPE_18 = exports.MNEMONIC_TYPE_15 = exports.MNEMONIC_TYPE_12 = exports.USER_CARD_INFO_SIZE = exports.USER_MEMORY_SIZE = exports.PAGE_AES_KEY0_START = exports.PAGE_CFG1 = exports.PAGE_CFG0 = exports.USER_CARD_INFO_PAGE_END = exports.USER_CARD_INFO_PAGE_START = exports.USER_PAGE_END = exports.USER_PAGE_START = exports.PAGE_SIZE = exports.KEY_NO_DATA_PROT = exports.CMD_AUTH_PART2 = exports.CMD_AUTH_PART1 = exports.CMD_FAST_READ = exports.CMD_WRITE = exports.CMD_READ = void 0;
6
+ exports.DELAY = exports.SEC_MSG_ACT_MASK = exports.DEFAULT_PIN_RETRY_COUNT = exports.RETRY_COUNTER_OFFSET = exports.RETRY_COUNTER_PAGE = exports.USER_NICKNAME_MAX_LENGTH = exports.USER_NICKNAME_PAGE_END = exports.USER_NICKNAME_PAGE_START = exports.MNEMONIC_MEMORY_SIZE = exports.MNEMONIC_PAGE_END = exports.MNEMONIC_TYPE_24 = exports.MNEMONIC_TYPE_21 = exports.MNEMONIC_TYPE_18 = exports.MNEMONIC_TYPE_15 = exports.MNEMONIC_TYPE_12 = exports.USER_CARD_INFO_SIZE = exports.USER_MEMORY_SIZE = exports.PAGE_AES_KEY0_START = exports.PAGE_CFG1 = exports.PAGE_CFG0 = exports.USER_CARD_INFO_PAGE_END = exports.USER_CARD_INFO_PAGE_START = exports.USER_PAGE_END = exports.USER_PAGE_START = exports.PAGE_SIZE = exports.KEY_NO_DATA_PROT = exports.CMD_READ_SIG = exports.CMD_GET_VERSION = exports.CMD_AUTH_PART2 = exports.CMD_AUTH_PART1 = exports.CMD_FAST_READ = exports.CMD_WRITE = exports.CMD_READ = void 0;
7
7
  // ---------------------------------------------------------------------------
8
8
  // NFC command codes
9
9
  // ---------------------------------------------------------------------------
@@ -17,6 +17,10 @@ exports.CMD_FAST_READ = 0x3a;
17
17
  exports.CMD_AUTH_PART1 = 0x1a;
18
18
  /** AES authentication part 2 */
19
19
  exports.CMD_AUTH_PART2 = 0xaf;
20
+ /** GET_VERSION command - retrieve product version info */
21
+ exports.CMD_GET_VERSION = 0x60;
22
+ /** READ_SIG command - read ECC originality signature */
23
+ exports.CMD_READ_SIG = 0x3c;
20
24
  /** Data protection key slot (Key0) */
21
25
  exports.KEY_NO_DATA_PROT = 0x00;
22
26
  // ---------------------------------------------------------------------------
@@ -59,6 +63,13 @@ exports.MNEMONIC_TYPE_21 = 0x04;
59
63
  /** 24-word mnemonic (256-bit entropy, 32 bytes) */
60
64
  exports.MNEMONIC_TYPE_24 = 0x05;
61
65
  // ---------------------------------------------------------------------------
66
+ // Mnemonic data area (excludes nickname pages)
67
+ // ---------------------------------------------------------------------------
68
+ /** Mnemonic data end page (stops before nickname area) */
69
+ exports.MNEMONIC_PAGE_END = exports.USER_PAGE_END - 3; // 0x24
70
+ /** Mnemonic data size: (0x24 - 0x08 + 1) * 4 = 116 bytes */
71
+ exports.MNEMONIC_MEMORY_SIZE = (exports.MNEMONIC_PAGE_END - exports.USER_PAGE_START + 1) * exports.PAGE_SIZE;
72
+ // ---------------------------------------------------------------------------
62
73
  // User nickname area (last 3 pages of user memory)
63
74
  // ---------------------------------------------------------------------------
64
75
  /** Nickname start page */
@@ -76,3 +87,31 @@ exports.RETRY_COUNTER_PAGE = exports.USER_PAGE_START - 1; // 0x07
76
87
  exports.RETRY_COUNTER_OFFSET = exports.PAGE_SIZE - 1;
77
88
  /** Default retry limit, restored after a successful authentication */
78
89
  exports.DEFAULT_PIN_RETRY_COUNT = 10;
90
+ // ---------------------------------------------------------------------------
91
+ // Secure messaging (CMAC)
92
+ // ---------------------------------------------------------------------------
93
+ /** SEC_MSG_ACT bit mask in CFG0 byte 0 (bit 1) */
94
+ exports.SEC_MSG_ACT_MASK = 0x02;
95
+ // ---------------------------------------------------------------------------
96
+ // Platform-specific delays (ms)
97
+ // ---------------------------------------------------------------------------
98
+ exports.DELAY = {
99
+ /** Delay after requestNfcTech on Android */
100
+ ANDROID_POST_TECH: 300,
101
+ /** Delay after releaseNfcTech (normal) */
102
+ IOS_POST_RELEASE: 100,
103
+ ANDROID_POST_RELEASE: 800,
104
+ /** Delay after releaseNfcTech (forced, e.g. after timeout) */
105
+ IOS_POST_RELEASE_FORCED: 300,
106
+ ANDROID_POST_RELEASE_FORCED: 1000,
107
+ /** Delay between page writes on iOS */
108
+ IOS_PAGE_WRITE: 20,
109
+ /** Delay between AES key page writes on iOS */
110
+ IOS_KEY_WRITE: 10,
111
+ /** Delay between nickname page writes on iOS */
112
+ IOS_NICKNAME_WRITE: 100,
113
+ /** Delay between config operations on iOS */
114
+ IOS_CONFIG: 80,
115
+ /** Keep-alive frequency (every N pages) during writes */
116
+ KEEPALIVE_FREQ: 4,
117
+ };
package/dist/crypto.d.ts CHANGED
@@ -23,6 +23,28 @@ export declare function aesEncrypt(key: Uint8Array, data: Uint8Array, iv: Uint8A
23
23
  export declare function rotateLeft8(data: Uint8Array): Uint8Array;
24
24
  /** Compare two Uint8Arrays for equality. */
25
25
  export declare function arraysEqual(a: Uint8Array, b: Uint8Array): boolean;
26
+ /** AES-128 ECB encrypt a single 16-byte block. */
27
+ export declare function aesEcbEncrypt(key: Uint8Array, data: Uint8Array): Uint8Array;
28
+ /**
29
+ * Generate CMAC subkeys K1 and K2 (NIST 800-38B §6.1).
30
+ */
31
+ export declare function generateCmacSubkeys(key: Uint8Array): {
32
+ k1: Uint8Array;
33
+ k2: Uint8Array;
34
+ };
35
+ /**
36
+ * Compute AES-CMAC (NIST 800-38B §6.2).
37
+ * Returns full 16-byte MAC.
38
+ */
39
+ export declare function aesCmac(key: Uint8Array, message: Uint8Array): Uint8Array;
40
+ /**
41
+ * Derive SesAuthMACKey from authentication key and random numbers.
42
+ * (MF0AES(H)20 §8.8.1)
43
+ *
44
+ * SV2 = 5Ah||A5h||00h||01h||00h||80h||RndA[15..14]||(RndA[13..8] XOR RndB[15..10])||RndB[9..0]||RndA[7..0]
45
+ * SesAuthMACKey = AES-CMAC(Kx, SV2)
46
+ */
47
+ export declare function deriveSessionKey(authKey: Uint8Array, rndA: Uint8Array, rndB: Uint8Array): Uint8Array;
26
48
  /**
27
49
  * Generate a 16-byte cryptographically-secure random value.
28
50
  *