@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.en.md DELETED
@@ -1,461 +0,0 @@
1
- # @ukeyfe/react-native-nfc-litecard
2
-
3
- English | [中文](./README.md)
4
-
5
- React Native NFC read/write library for **MIFARE Ultralight AES** (MF0AES(H)20), designed for LiteCard mnemonic storage.
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.
8
-
9
- ## Features
10
-
11
- | Feature | Method | Description |
12
- |---------|--------|-------------|
13
- | Check card | `checkCard()` | Detect whether the card is empty or contains data |
14
- | Read mnemonic | `readMnemonic()` | Read BIP-39 mnemonic (password required) |
15
- | Read nickname | `readUserNickname()` | Read user nickname from card |
16
- | Read retry count | `readMnemonicRetryCount()` | Read PIN retry counter |
17
- | Reset retry count | `resetRetryCountTo10()` | Reset PIN retry counter to default (10) |
18
- | Initialize card | `initializeCard()` | Write mnemonic + set password on a blank card |
19
- | Update card | `updateCard()` | Update mnemonic & password (old password required) |
20
- | Change password | `updatePassword()` | Change password only (old password required) |
21
- | Write nickname | `writeUserNickname()` | Write user nickname to card |
22
- | Reset card | `resetCard()` | Wipe data, reset password to "000000" |
23
-
24
- ## Installation
25
-
26
- ```bash
27
- npm install @ukeyfe/react-native-nfc-litecard
28
- ```
29
-
30
- ### Peer Dependencies
31
-
32
- This library requires the following peer dependencies:
33
-
34
- ```bash
35
- npm install react-native react-native-nfc-manager
36
- ```
37
-
38
- ## Quick Start
39
-
40
- ```typescript
41
- import {
42
- ResultCode,
43
- checkCard,
44
- readMnemonic,
45
- initializeCard,
46
- updateCard,
47
- updatePassword,
48
- writeUserNickname,
49
- readUserNickname,
50
- resetCard,
51
- readMnemonicRetryCount,
52
- resetRetryCountTo10,
53
- } from '@ukeyfe/react-native-nfc-litecard';
54
- ```
55
-
56
- ## API Reference
57
-
58
- ### `checkCard(password?, onCardIdentified?)`
59
-
60
- Detect card status (empty / has data).
61
-
62
- **Without password (quick probe):**
63
- ```typescript
64
- const result = await checkCard();
65
- if (result.code === ResultCode.CHECK_EMPTY) {
66
- // Empty card – ready to initialize
67
- } else if (result.code === ResultCode.CHECK_HAS_DATA) {
68
- // Has data (or read-protected, cannot determine)
69
- }
70
- ```
71
-
72
- **With password (authenticated deep check, for read-protected cards):**
73
- ```typescript
74
- const result = await checkCard('password');
75
- if (result.code === ResultCode.CHECK_EMPTY) {
76
- // Empty card – ready to write
77
- } else if (result.code === ResultCode.CHECK_HAS_DATA) {
78
- // Valid mnemonic backup exists, type: result.data?.type
79
- } else if (result.code === ResultCode.AUTH_WRONG_PASSWORD) {
80
- // Wrong password
81
- }
82
- ```
83
-
84
- **Parameters:**
85
- | Parameter | Type | Required | Description |
86
- |-----------|------|----------|-------------|
87
- | `password` | `string` | No | Card protection password. If omitted, the library reads directly (suited for unencrypted cards). If provided, AES authentication runs first, then full data is read and CRC16 is verified (suited for read-protected cards; more accurate). |
88
- | `onCardIdentified` | `() => void` | No | Called after the NFC session is established; use for UI such as “card detected”. |
89
-
90
- **Result codes:**
91
- | Code | Meaning |
92
- |------|---------|
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
-
98
- ---
99
-
100
- ### `readMnemonic(password, onCardIdentified?)`
101
-
102
- Read the mnemonic (password authentication required). The retry counter is decremented before authentication and reset to 10 after a successful authentication.
103
-
104
- ```typescript
105
- const result = await readMnemonic('your-password');
106
- if (result.success) {
107
- console.log('Mnemonic:', result.data?.mnemonic);
108
- console.log('Type:', result.data?.type); // "12 words (128-bit)"
109
- console.log('Nickname:', result.data?.nickname);
110
- console.log('Retry count:', result.data?.retryCount);
111
- }
112
- ```
113
-
114
- **Parameters:**
115
- | Parameter | Type | Required | Description |
116
- |-----------|------|----------|-------------|
117
- | `password` | `string` | Yes | Card protection password for AES-128 authentication |
118
- | `onCardIdentified` | `() => void` | No | Called after successful authentication and before reading data; use for UI such as “reading”. |
119
-
120
- **Returned `data` fields:**
121
- | Field | Type | Description |
122
- |-------|------|-------------|
123
- | `mnemonic` | `string` | BIP-39 mnemonic (e.g. `"abandon abandon ... about"`) |
124
- | `type` | `string` | Mnemonic type (e.g. `"12 words (128-bit)"`, `"24 words (256-bit)"`) |
125
- | `entropyHex` | `string` | Entropy as a hexadecimal string |
126
- | `rawBytes` | `string` | Hex string of raw on-card data (type + entropy) |
127
- | `nickname` | `string` | User nickname if set on the card |
128
- | `retryCount` | `number` | Retry count after successful authentication (normally 10) |
129
-
130
- ---
131
-
132
- ### `initializeCard(mnemonic, password, onCardIdentified?)`
133
-
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.
135
-
136
- ```typescript
137
- const result = await initializeCard(
138
- 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
139
- 'your-password'
140
- );
141
- if (result.code === ResultCode.INIT_SUCCESS) {
142
- console.log('Initialization successful');
143
- }
144
- ```
145
-
146
- **Parameters:**
147
- | Parameter | Type | Required | Description |
148
- |-----------|------|----------|-------------|
149
- | `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”. |
152
-
153
- ---
154
-
155
- ### `updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified?, options?)`
156
-
157
- Update the card: authenticate with the old password, then write the new mnemonic and new password. The retry counter is decremented automatically before authentication.
158
-
159
- ```typescript
160
- const result = await updateCard('old-password', 'new-password', 'new mnemonic words ...');
161
- if (result.code === ResultCode.WRITE_SUCCESS) {
162
- console.log('Update successful');
163
- }
164
- ```
165
-
166
- **Pre-check for existing backup:**
167
-
168
- ```typescript
169
- const result = await updateCard('old-password', 'new-password', 'mnemonic ...', undefined, {
170
- precheckExistingMnemonic: true,
171
- });
172
- if (result.code === ResultCode.PRECHECK_HAS_BACKUP) {
173
- // Card already has a valid mnemonic backup; write was skipped
174
- console.log('Backup exists, type:', result.data?.type);
175
- } else if (result.code === ResultCode.WRITE_SUCCESS) {
176
- console.log('Write successful');
177
- }
178
- ```
179
-
180
- **Parameters:**
181
- | Parameter | Type | Required | Description |
182
- |-----------|------|----------|-------------|
183
- | `oldPassword` | `string` | Yes | Current card password for AES authentication |
184
- | `newPassword` | `string` | Yes | New password; the card will be protected with this password after the write |
185
- | `newMnemonic` | `string` | Yes | New BIP-39 mnemonic (12/15/18/21/24 words) |
186
- | `onCardIdentified` | `() => void` | No | Called after successful authentication; use for UI such as “writing”. |
187
- | `options.precheckExistingMnemonic` | `boolean` | No | When `true`, after authentication the library reads card data and verifies CRC16. If a valid mnemonic already exists, returns `PRECHECK_HAS_BACKUP` and does not write. |
188
-
189
- ---
190
-
191
- ### `updatePassword(oldPassword, newPassword, onCardIdentified?)`
192
-
193
- Change the password only; mnemonic data on the card is unchanged. The retry counter is decremented automatically before authentication.
194
-
195
- ```typescript
196
- const result = await updatePassword('old-password', 'new-password');
197
- if (result.code === ResultCode.UPDATE_PASSWORD_SUCCESS) {
198
- console.log('Password updated');
199
- }
200
- ```
201
-
202
- **Parameters:**
203
- | Parameter | Type | Required | Description |
204
- |-----------|------|----------|-------------|
205
- | `oldPassword` | `string` | Yes | Current card password for AES authentication |
206
- | `newPassword` | `string` | Yes | New password; the card will use this password after the change |
207
- | `onCardIdentified` | `() => void` | No | Called after successful authentication |
208
-
209
- ---
210
-
211
- ### `writeUserNickname(password, nickname, onCardIdentified?)`
212
-
213
- Write a user nickname to the card. The nickname is UTF-8 encoded, max 12 bytes (longer input is truncated).
214
-
215
- ```typescript
216
- const result = await writeUserNickname('your-password', 'MyCard');
217
- if (result.code === ResultCode.WRITE_NICKNAME_SUCCESS) {
218
- console.log('Nickname written');
219
- }
220
- ```
221
-
222
- **Parameters:**
223
- | Parameter | Type | Required | Description |
224
- |-----------|------|----------|-------------|
225
- | `password` | `string` | Yes | Card protection password for AES authentication |
226
- | `nickname` | `string` | Yes | User nickname; UTF-8 max 12 bytes (roughly 4 CJK characters or 12 Latin letters) |
227
- | `onCardIdentified` | `() => void` | No | Called after successful authentication |
228
-
229
- ---
230
-
231
- ### `readUserNickname(password?, onCardIdentified?)`
232
-
233
- Read the user nickname from the card.
234
-
235
- ```typescript
236
- const result = await readUserNickname('your-password');
237
- if (result.success) {
238
- console.log('Nickname:', result.data?.nickname);
239
- }
240
- ```
241
-
242
- **Parameters:**
243
- | Parameter | Type | Required | Description |
244
- |-----------|------|----------|-------------|
245
- | `password` | `string` | No | Card password. Required if read protection is enabled (`PROT=1`); otherwise optional. |
246
- | `onCardIdentified` | `() => void` | No | Called after successful authentication |
247
-
248
- ---
249
-
250
- ### `resetCard(password?, onCardIdentified?)`
251
-
252
- Reset the card: wipe all user data (mnemonic, nickname), set password to `"000000"`, and disable read/write protection.
253
-
254
- ```typescript
255
- const result = await resetCard('your-password');
256
- if (result.code === ResultCode.RESET_SUCCESS) {
257
- console.log('Reset successful');
258
- }
259
- ```
260
-
261
- > ⚠️ This operation is irreversible; mnemonic data on the card is permanently erased.
262
-
263
- **Parameters:**
264
- | Parameter | Type | Required | Description |
265
- |-----------|------|----------|-------------|
266
- | `password` | `string` | No | Current card password. Required if the card is protected; otherwise optional. The retry counter is decremented automatically before authentication. |
267
- | `onCardIdentified` | `() => void` | No | Called after successful authentication |
268
-
269
- ---
270
-
271
- ### `readMnemonicRetryCount(onCardIdentified?)`
272
-
273
- Read the PIN retry counter on the card. No password authentication is required (the counter page is outside the protected area).
274
-
275
- ```typescript
276
- const result = await readMnemonicRetryCount();
277
- if (result.success) {
278
- console.log('Remaining retries:', result.data?.retryCount);
279
- }
280
- ```
281
-
282
- **Parameters:**
283
- | Parameter | Type | Required | Description |
284
- |-----------|------|----------|-------------|
285
- | `onCardIdentified` | `() => void` | No | Called after the NFC session is established |
286
-
287
- ---
288
-
289
- ### `resetRetryCountTo10(onCardIdentified?)`
290
-
291
- Reset the PIN retry counter to its default value (10). No password authentication is required.
292
-
293
- ```typescript
294
- const result = await resetRetryCountTo10();
295
- ```
296
-
297
- **Parameters:**
298
- | Parameter | Type | Required | Description |
299
- |-----------|------|----------|-------------|
300
- | `onCardIdentified` | `() => void` | No | Called after the NFC session is established |
301
-
302
- ---
303
-
304
- ### NFC Lock Management
305
-
306
- For app-level NFC session lifecycle management:
307
-
308
- ```typescript
309
- import {
310
- isNfcOperationLocked,
311
- releaseNfcOperationLock,
312
- markNfcOperationCancelledByCleanup,
313
- consumeNfcOperationCancelledByCleanup,
314
- } from '@ukeyfe/react-native-nfc-litecard';
315
- ```
316
-
317
- | Method | Description |
318
- |--------|-------------|
319
- | `isNfcOperationLocked()` | Check if the NFC operation lock is held |
320
- | `releaseNfcOperationLock()` | Force-release the lock (use on page unmount) |
321
- | `markNfcOperationCancelledByCleanup()` | Mark current operation as interrupted by cleanup |
322
- | `consumeNfcOperationCancelledByCleanup()` | Consume the cleanup flag (returns whether it was set) |
323
-
324
- ## NfcResult Structure
325
-
326
- All APIs return a unified `NfcResult`:
327
-
328
- ```typescript
329
- interface NfcResult {
330
- code: number; // Status code – compare against ResultCode
331
- success: boolean; // Whether the operation succeeded
332
- data?: { // Optional data, only present for some operations
333
- mnemonic?: string;
334
- type?: string;
335
- entropyHex?: string;
336
- rawBytes?: string;
337
- nickname?: string;
338
- retryCount?: number;
339
- aesKeyHex?: string;
340
- crc16?: number;
341
- };
342
- }
343
- ```
344
-
345
- ## Error Handling Example
346
-
347
- ```typescript
348
- import { ResultCode, readMnemonic } from '@ukeyfe/react-native-nfc-litecard';
349
-
350
- const result = await readMnemonic('password');
351
-
352
- switch (result.code) {
353
- case ResultCode.READ_SUCCESS:
354
- console.log('Read successful:', result.data?.mnemonic);
355
- break;
356
- case ResultCode.AUTH_WRONG_PASSWORD:
357
- alert('Wrong password');
358
- break;
359
- case ResultCode.NFC_CONNECT_FAILED:
360
- alert('NFC connection failed, please re-tap the card');
361
- break;
362
- case ResultCode.NFC_USER_CANCELED:
363
- // iOS user cancelled – handle silently
364
- break;
365
- case ResultCode.READ_TIMEOUT:
366
- alert('Read timeout – remove and re-tap the card');
367
- break;
368
- case ResultCode.RETRY_COUNT_EXHAUSTED:
369
- alert('Retry count exhausted – card is locked');
370
- break;
371
- default:
372
- alert('Operation failed');
373
- }
374
- ```
375
-
376
- ## Result Codes
377
-
378
- ### Success Codes
379
-
380
- | Constant | Value | Description |
381
- |----------|-------|-------------|
382
- | `READ_SUCCESS` | 10102 | Mnemonic read successful |
383
- | `READ_NICKNAME_SUCCESS` | 10103 | Nickname read successful |
384
- | `CHECK_EMPTY` | 10104 | Empty card |
385
- | `CHECK_HAS_DATA` | 10105 | Card has data |
386
- | `READ_RETRY_COUNT_SUCCESS` | 10106 | Retry count read successful |
387
- | `INIT_SUCCESS` | 10201 | Initialization successful |
388
- | `WRITE_SUCCESS` | 10203 | Write/update successful |
389
- | `UPDATE_PASSWORD_SUCCESS` | 10204 | Password change successful |
390
- | `WRITE_NICKNAME_SUCCESS` | 10205 | Nickname written |
391
- | `RESET_SUCCESS` | 10206 | Card reset successful |
392
- | `PRECHECK_HAS_BACKUP` | 10207 | Card already has a valid backup; write was skipped |
393
-
394
- ### Error Codes
395
-
396
- | Constant | Value | Description |
397
- |----------|-------|-------------|
398
- | `NFC_CONNECT_FAILED` | 40001 | NFC connection failed |
399
- | `AUTH_WRONG_PASSWORD` | 40002 | Wrong password |
400
- | `AUTH_INVALID_RESPONSE` | 40003 | Invalid authentication response |
401
- | `AUTH_VERIFY_FAILED` | 40004 | Authentication verification failed |
402
- | `READ_FAILED` | 40005 | Read failed |
403
- | `WRITE_FAILED` | 40006 | Write failed |
404
- | `INVALID_MNEMONIC` | 40007 | Invalid mnemonic |
405
- | `UNSUPPORTED_MNEMONIC_LENGTH` | 40008 | Unsupported mnemonic length |
406
- | `INVALID_CARD_DATA` | 40009 | Invalid card data |
407
- | `UNKNOWN_ERROR` | 40010 | Unknown error |
408
- | `NFC_USER_CANCELED` | 40011 | User cancelled NFC scan (iOS) |
409
- | `READ_TIMEOUT` | 40012 | Read timeout |
410
- | `NFC_LOCK_TIMEOUT` | 40013 | NFC lock timeout |
411
- | `CRC16_CHECK_FAILED` | 40014 | CRC16 check failed |
412
- | `RETRY_COUNT_EXHAUSTED` | 40015 | PIN retry count exhausted, card locked |
413
-
414
- ## Storage Format
415
-
416
- The card stores BIP-39 mnemonics using entropy compression:
417
-
418
- ```
419
- [type 1B] [entropy 16-32B] [CRC16 2B]
420
- ```
421
-
422
- | Type | Mnemonic Length | Entropy Size |
423
- |------|----------------|--------------|
424
- | 0x01 | 12 words | 16 bytes (128-bit) |
425
- | 0x02 | 15 words | 20 bytes (160-bit) |
426
- | 0x03 | 18 words | 24 bytes (192-bit) |
427
- | 0x04 | 21 words | 28 bytes (224-bit) |
428
- | 0x05 | 24 words | 32 bytes (256-bit) |
429
-
430
- ## Security
431
-
432
- - **AES-128 hardware-level mutual authentication** (3-pass)
433
- - **SHA-256 key derivation**: password → SHA-256 → first 16 bytes as AES key
434
- - **CRC16-Modbus checksum**: data integrity verification
435
- - **PIN retry counter**: auto-decrements on wrong password, resets to 10 on success
436
- - **Secure random**: authentication uses `crypto.getRandomValues()` (requires Hermes ≥ 0.72 or `react-native-get-random-values` polyfill)
437
-
438
- ## Project Structure
439
-
440
- ```
441
- src/
442
- ├── index.ts # Public API exports
443
- ├── constants.ts # Shared constants (page addresses, NFC commands, mnemonic types)
444
- ├── types.ts # Unified ResultCode, NfcResult interface, error mapping
445
- ├── crypto.ts # AES encrypt/decrypt, key derivation, secure random
446
- ├── utils.ts # CRC16, hex conversion, array utilities
447
- ├── nfc-core.ts # NFC lock, transceive, authentication, retry counter
448
- ├── reader.ts # Reader API
449
- └── writer.ts # Writer API
450
- ```
451
-
452
- ## Platform Support
453
-
454
- | Platform | Technology | Requirements |
455
- |----------|-----------|--------------|
456
- | iOS | MifareIOS | iPhone 7 or later |
457
- | Android | NfcA | NFC-capable device |
458
-
459
- ## License
460
-
461
- MIT