@ukeyfe/react-native-nfc-litecard 1.0.2 → 1.0.4

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.md CHANGED
@@ -4,7 +4,7 @@ English | [中文](./README.zh.md)
4
4
 
5
5
  React Native NFC read/write library for **MIFARE Ultralight AES** (MF0AES(H)20), designed for LiteCard mnemonic storage.
6
6
 
7
- > **Design principle**: The library only returns status codes (`code`) and data (`data`). It does **not** provide user-facing messages. The caller should map `ResultCode` to their own localised strings.
7
+ > **Design principle**: The library only returns status codes (`code`) and data (`data`). It does **not** provide user-facing messages. The caller should map `NfcStatusCode` to their own localised strings.
8
8
 
9
9
  ## Features
10
10
 
@@ -15,11 +15,13 @@ React Native NFC read/write library for **MIFARE Ultralight AES** (MF0AES(H)20),
15
15
  | Read nickname | `readUserNickname()` | Read user nickname from card |
16
16
  | Read retry count | `readMnemonicRetryCount()` | Read PIN retry counter |
17
17
  | Reset retry count | `resetRetryCountTo10()` | Reset PIN retry counter to default (10) |
18
- | Initialize card | `initializeCard()` | Write mnemonic + set password on a blank card |
18
+ | Initialize card | `initializeCard()` | Authenticate with default password, write mnemonic + set new password |
19
19
  | Update card | `updateCard()` | Update mnemonic & password (old password required) |
20
20
  | Change password | `updatePassword()` | Change password only (old password required) |
21
21
  | Write nickname | `writeUserNickname()` | Write user nickname to card |
22
- | Reset card | `resetCard()` | Wipe mnemonic data, set a new password |
22
+ | Reset card | `resetCard()` | Wipe mnemonic data, set a new password, keep protection enabled |
23
+ | Card version | `getCardVersion()` | Read card product version info (no auth required) |
24
+ | Originality check | `readOriginality()` | Read ECC originality signature for NXP authenticity verification |
23
25
 
24
26
  ## Installation
25
27
 
@@ -39,7 +41,7 @@ npm install react-native react-native-nfc-manager
39
41
 
40
42
  ```typescript
41
43
  import {
42
- ResultCode,
44
+ NfcStatusCode,
43
45
  checkCard,
44
46
  readMnemonic,
45
47
  initializeCard,
@@ -50,6 +52,8 @@ import {
50
52
  resetCard,
51
53
  readMnemonicRetryCount,
52
54
  resetRetryCountTo10,
55
+ getCardVersion,
56
+ readOriginality,
53
57
  } from '@ukeyfe/react-native-nfc-litecard';
54
58
  ```
55
59
 
@@ -62,9 +66,9 @@ Detect card status (empty / has data).
62
66
  **Without password (quick probe):**
63
67
  ```typescript
64
68
  const result = await checkCard();
65
- if (result.code === ResultCode.CHECK_EMPTY) {
69
+ if (result.code === NfcStatusCode.CHECK_EMPTY) {
66
70
  // Empty card – ready to initialize
67
- } else if (result.code === ResultCode.CHECK_HAS_DATA) {
71
+ } else if (result.code === NfcStatusCode.CHECK_HAS_DATA) {
68
72
  // Has data (or read-protected, cannot determine)
69
73
  }
70
74
  ```
@@ -72,11 +76,11 @@ if (result.code === ResultCode.CHECK_EMPTY) {
72
76
  **With password (authenticated deep check, for read-protected cards):**
73
77
  ```typescript
74
78
  const result = await checkCard('password');
75
- if (result.code === ResultCode.CHECK_EMPTY) {
79
+ if (result.code === NfcStatusCode.CHECK_EMPTY) {
76
80
  // Empty card – ready to write
77
- } else if (result.code === ResultCode.CHECK_HAS_DATA) {
81
+ } else if (result.code === NfcStatusCode.CHECK_HAS_DATA) {
78
82
  // Valid mnemonic backup exists, type: result.data?.type
79
- } else if (result.code === ResultCode.AUTH_WRONG_PASSWORD) {
83
+ } else if (result.code === NfcStatusCode.AUTH_WRONG_PASSWORD) {
80
84
  // Wrong password
81
85
  }
82
86
  ```
@@ -90,10 +94,10 @@ if (result.code === ResultCode.CHECK_EMPTY) {
90
94
  **Result codes:**
91
95
  | Code | Meaning |
92
96
  |------|---------|
93
- | `ResultCode.CHECK_EMPTY` (10104) | Empty card – no mnemonic data |
94
- | `ResultCode.CHECK_HAS_DATA` (10105) | Card has data. When a password is supplied, `data.type` contains the mnemonic type (e.g. `"12 words (128-bit)"`). |
95
- | `ResultCode.AUTH_WRONG_PASSWORD` (40002) | Wrong password (only possible when a password is provided) |
96
- | `ResultCode.NFC_CONNECT_FAILED` (40001) | NFC connection failed – card not tapped or device unsupported |
97
+ | `NfcStatusCode.CHECK_EMPTY` (10104) | Empty card – no mnemonic data |
98
+ | `NfcStatusCode.CHECK_HAS_DATA` (10105) | Card has data. When a password is supplied, `data.type` contains the mnemonic type (e.g. `"12 words (128-bit)"`). |
99
+ | `NfcStatusCode.AUTH_WRONG_PASSWORD` (40002) | Wrong password (only possible when a password is provided) |
100
+ | `NfcStatusCode.NFC_CONNECT_FAILED` (40001) | NFC connection failed – card not tapped or device unsupported |
97
101
 
98
102
  ---
99
103
 
@@ -129,16 +133,17 @@ if (result.success) {
129
133
 
130
134
  ---
131
135
 
132
- ### `initializeCard(mnemonic, password, onCardIdentified?)`
136
+ ### `initializeCard(mnemonic, newPassword, defaultPassword, onCardIdentified?)`
133
137
 
134
- Initialize a blank card: convert the mnemonic to BIP-39 entropy, write it to the card, enable AES password protection, and require read/write authentication.
138
+ Initialize a card: authenticate with the factory-default password, write the mnemonic, and set a new password. The card must already have AES protection enabled (factory default).
135
139
 
136
140
  ```typescript
137
141
  const result = await initializeCard(
138
142
  'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
139
- 'your-password'
143
+ 'your-password',
144
+ '000000' // factory default password
140
145
  );
141
- if (result.code === ResultCode.INIT_SUCCESS) {
146
+ if (result.code === NfcStatusCode.INIT_SUCCESS) {
142
147
  console.log('Initialization successful');
143
148
  }
144
149
  ```
@@ -147,8 +152,9 @@ if (result.code === ResultCode.INIT_SUCCESS) {
147
152
  | Parameter | Type | Required | Description |
148
153
  |-----------|------|----------|-------------|
149
154
  | `mnemonic` | `string` | Yes | BIP-39 mnemonic; supports 12/15/18/21/24 words. The library converts to entropy + CRC16 and writes to the card. |
150
- | `password` | `string` | Yes | Protection password to set; used to derive the AES-128 key written to the card. |
151
- | `onCardIdentified` | `() => void` | No | Called after NFC connection is established and before writing begins; use for UI such as "writing". |
155
+ | `newPassword` | `string` | Yes | New protection password; used to derive the AES-128 key written to the card. |
156
+ | `defaultPassword` | `string` | Yes | Current (factory-default) password on the card for authentication. |
157
+ | `onCardIdentified` | `() => void` | No | Called after successful authentication and before writing begins; use for UI such as "writing". |
152
158
 
153
159
  ---
154
160
 
@@ -158,7 +164,7 @@ Update the card: authenticate with the old password, then write the new mnemonic
158
164
 
159
165
  ```typescript
160
166
  const result = await updateCard('old-password', 'new-password', 'new mnemonic words ...');
161
- if (result.code === ResultCode.WRITE_SUCCESS) {
167
+ if (result.code === NfcStatusCode.WRITE_SUCCESS) {
162
168
  console.log('Update successful');
163
169
  }
164
170
  ```
@@ -169,10 +175,10 @@ if (result.code === ResultCode.WRITE_SUCCESS) {
169
175
  const result = await updateCard('old-password', 'new-password', 'mnemonic ...', undefined, {
170
176
  precheckExistingMnemonic: true,
171
177
  });
172
- if (result.code === ResultCode.PRECHECK_HAS_BACKUP) {
178
+ if (result.code === NfcStatusCode.PRECHECK_HAS_BACKUP) {
173
179
  // Card already has a valid mnemonic backup; write was skipped
174
180
  console.log('Backup exists, type:', result.data?.type);
175
- } else if (result.code === ResultCode.WRITE_SUCCESS) {
181
+ } else if (result.code === NfcStatusCode.WRITE_SUCCESS) {
176
182
  console.log('Write successful');
177
183
  }
178
184
  ```
@@ -194,7 +200,7 @@ Change the password only; mnemonic data on the card is unchanged. The retry coun
194
200
 
195
201
  ```typescript
196
202
  const result = await updatePassword('old-password', 'new-password');
197
- if (result.code === ResultCode.UPDATE_PASSWORD_SUCCESS) {
203
+ if (result.code === NfcStatusCode.UPDATE_PASSWORD_SUCCESS) {
198
204
  console.log('Password updated');
199
205
  }
200
206
  ```
@@ -214,7 +220,7 @@ Write a user nickname to the card. The nickname is UTF-8 encoded, max 12 bytes (
214
220
 
215
221
  ```typescript
216
222
  const result = await writeUserNickname('your-password', 'MyCard');
217
- if (result.code === ResultCode.WRITE_NICKNAME_SUCCESS) {
223
+ if (result.code === NfcStatusCode.WRITE_NICKNAME_SUCCESS) {
218
224
  console.log('Nickname written');
219
225
  }
220
226
  ```
@@ -253,7 +259,7 @@ Reset the card: wipe mnemonic data, set a new password, and disable read/write p
253
259
 
254
260
  ```typescript
255
261
  const result = await resetCard('old-password', 'new-password');
256
- if (result.code === ResultCode.RESET_SUCCESS) {
262
+ if (result.code === NfcStatusCode.RESET_SUCCESS) {
257
263
  console.log('Reset successful');
258
264
  }
259
265
  ```
@@ -302,6 +308,34 @@ const result = await resetRetryCountTo10();
302
308
 
303
309
  ---
304
310
 
311
+ ### `getCardVersion(onCardIdentified?)`
312
+
313
+ Read card product version info. No authentication required.
314
+
315
+ ```typescript
316
+ const result = await getCardVersion();
317
+ if (result.success) {
318
+ console.log('Vendor:', result.data?.version?.vendorId); // 0x04 = NXP
319
+ console.log('Type:', result.data?.version?.productType); // 0x03 = Ultralight
320
+ console.log('Version:', result.data?.version?.majorVersion); // 0x04 = AES
321
+ }
322
+ ```
323
+
324
+ ---
325
+
326
+ ### `readOriginality(onCardIdentified?)`
327
+
328
+ Read the ECC originality signature to verify the card is a genuine NXP product. No authentication required.
329
+
330
+ ```typescript
331
+ const result = await readOriginality();
332
+ if (result.success) {
333
+ console.log('Signature:', result.data?.signature); // 96-char hex string
334
+ }
335
+ ```
336
+
337
+ ---
338
+
305
339
  ### NFC Lock Management
306
340
 
307
341
  For app-level NFC session lifecycle management:
@@ -328,7 +362,7 @@ All APIs return a unified `NfcResult`:
328
362
 
329
363
  ```typescript
330
364
  interface NfcResult {
331
- code: number; // Status code – compare against ResultCode
365
+ code: number; // Status code – compare against NfcStatusCode
332
366
  success: boolean; // Whether the operation succeeded
333
367
  data?: { // Optional data, only present for some operations
334
368
  mnemonic?: string;
@@ -346,27 +380,27 @@ interface NfcResult {
346
380
  ## Error Handling Example
347
381
 
348
382
  ```typescript
349
- import { ResultCode, readMnemonic } from '@ukeyfe/react-native-nfc-litecard';
383
+ import { NfcStatusCode, readMnemonic } from '@ukeyfe/react-native-nfc-litecard';
350
384
 
351
385
  const result = await readMnemonic('password');
352
386
 
353
387
  switch (result.code) {
354
- case ResultCode.READ_SUCCESS:
388
+ case NfcStatusCode.READ_SUCCESS:
355
389
  console.log('Read successful:', result.data?.mnemonic);
356
390
  break;
357
- case ResultCode.AUTH_WRONG_PASSWORD:
391
+ case NfcStatusCode.AUTH_WRONG_PASSWORD:
358
392
  alert('Wrong password');
359
393
  break;
360
- case ResultCode.NFC_CONNECT_FAILED:
394
+ case NfcStatusCode.NFC_CONNECT_FAILED:
361
395
  alert('NFC connection failed, please re-tap the card');
362
396
  break;
363
- case ResultCode.NFC_USER_CANCELED:
397
+ case NfcStatusCode.NFC_USER_CANCELED:
364
398
  // iOS user cancelled – handle silently
365
399
  break;
366
- case ResultCode.READ_TIMEOUT:
400
+ case NfcStatusCode.READ_TIMEOUT:
367
401
  alert('Read timeout – remove and re-tap the card');
368
402
  break;
369
- case ResultCode.RETRY_COUNT_EXHAUSTED:
403
+ case NfcStatusCode.RETRY_COUNT_EXHAUSTED:
370
404
  alert('Retry count exhausted – card is locked');
371
405
  break;
372
406
  default:
@@ -385,6 +419,8 @@ switch (result.code) {
385
419
  | `CHECK_EMPTY` | 10104 | Empty card |
386
420
  | `CHECK_HAS_DATA` | 10105 | Card has data |
387
421
  | `READ_RETRY_COUNT_SUCCESS` | 10106 | Retry count read successful |
422
+ | `GET_VERSION_SUCCESS` | 10107 | Card version read successful |
423
+ | `READ_SIG_SUCCESS` | 10108 | Originality signature read successful |
388
424
  | `INIT_SUCCESS` | 10201 | Initialization successful |
389
425
  | `WRITE_SUCCESS` | 10203 | Write/update successful |
390
426
  | `UPDATE_PASSWORD_SUCCESS` | 10204 | Password change successful |
@@ -434,6 +470,7 @@ The card stores BIP-39 mnemonics using entropy compression:
434
470
  - **SHA-256 key derivation**: password → SHA-256 → first 16 bytes as AES key
435
471
  - **CRC16-Modbus checksum**: data integrity verification
436
472
  - **PIN retry counter**: auto-decrements on wrong password, resets to 10 on success
473
+ - **CMAC secure messaging**: all commands after authentication are protected with AES-CMAC (NIST 800-38B) to prevent tampering
437
474
  - **Secure random**: authentication uses `crypto.getRandomValues()` (requires Hermes ≥ 0.72 or `react-native-get-random-values` polyfill)
438
475
 
439
476
  ## Project Structure
@@ -442,7 +479,7 @@ The card stores BIP-39 mnemonics using entropy compression:
442
479
  src/
443
480
  ├── index.ts # Public API exports
444
481
  ├── constants.ts # Shared constants (page addresses, NFC commands, mnemonic types)
445
- ├── types.ts # Unified ResultCode, NfcResult interface, error mapping
482
+ ├── types.ts # Unified NfcStatusCode, NfcResult interface, error mapping
446
483
  ├── crypto.ts # AES encrypt/decrypt, key derivation, secure random
447
484
  ├── utils.ts # CRC16, hex conversion, array utilities
448
485
  ├── nfc-core.ts # NFC lock, transceive, authentication, retry counter