@ukeyfe/react-native-nfc-litecard 1.0.0
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 +390 -0
- package/README.md +390 -0
- package/dist/constants.d.ts +57 -0
- package/dist/constants.js +78 -0
- package/dist/crypto.d.ts +33 -0
- package/dist/crypto.js +121 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +47 -0
- package/dist/nfc-core.d.ts +65 -0
- package/dist/nfc-core.js +277 -0
- package/dist/reader.d.ts +82 -0
- package/dist/reader.js +488 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.js +94 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.js +63 -0
- package/dist/writer.d.ts +32 -0
- package/dist/writer.js +545 -0
- package/package.json +32 -0
package/README.en.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
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(onCardIdentified?)`
|
|
59
|
+
|
|
60
|
+
Detect card status (empty / has data).
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const result = await checkCard();
|
|
64
|
+
if (result.code === ResultCode.CHECK_EMPTY) {
|
|
65
|
+
// Empty card – ready to initialize
|
|
66
|
+
} else if (result.code === ResultCode.CHECK_HAS_DATA) {
|
|
67
|
+
// Has data – need password to read or update
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Parameters:**
|
|
72
|
+
| Parameter | Type | Required | Description |
|
|
73
|
+
|-----------|------|----------|-------------|
|
|
74
|
+
| `onCardIdentified` | `() => void` | No | Callback after card is identified |
|
|
75
|
+
|
|
76
|
+
**Result codes:**
|
|
77
|
+
| Code | Meaning |
|
|
78
|
+
|------|---------|
|
|
79
|
+
| `ResultCode.CHECK_EMPTY` (10104) | Empty card |
|
|
80
|
+
| `ResultCode.CHECK_HAS_DATA` (10105) | Card has data |
|
|
81
|
+
| `ResultCode.NFC_CONNECT_FAILED` (40001) | NFC connection failed |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### `readMnemonic(password, onCardIdentified?)`
|
|
86
|
+
|
|
87
|
+
Read mnemonic from a password-protected card.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const result = await readMnemonic('your-password');
|
|
91
|
+
if (result.success) {
|
|
92
|
+
console.log('Mnemonic:', result.data?.mnemonic);
|
|
93
|
+
console.log('Type:', result.data?.type); // "12 words (128-bit)"
|
|
94
|
+
console.log('Nickname:', result.data?.nickname);
|
|
95
|
+
console.log('Retry count:', result.data?.retryCount);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Parameters:**
|
|
100
|
+
| Parameter | Type | Required | Description |
|
|
101
|
+
|-----------|------|----------|-------------|
|
|
102
|
+
| `password` | `string` | Yes | Card protection password |
|
|
103
|
+
| `onCardIdentified` | `() => void` | No | Callback after successful authentication |
|
|
104
|
+
|
|
105
|
+
**Returned `data` fields:**
|
|
106
|
+
| Field | Description |
|
|
107
|
+
|-------|-------------|
|
|
108
|
+
| `mnemonic` | BIP-39 mnemonic phrase |
|
|
109
|
+
| `type` | Mnemonic type (e.g. "12 words (128-bit)") |
|
|
110
|
+
| `entropyHex` | Entropy as hex string |
|
|
111
|
+
| `rawBytes` | Raw data as hex string |
|
|
112
|
+
| `nickname` | User nickname (if set) |
|
|
113
|
+
| `retryCount` | Retry count after successful reset |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### `initializeCard(mnemonic, password, onCardIdentified?)`
|
|
118
|
+
|
|
119
|
+
Initialize a blank card: write mnemonic + set password protection.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const result = await initializeCard(
|
|
123
|
+
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
|
124
|
+
'your-password'
|
|
125
|
+
);
|
|
126
|
+
if (result.code === ResultCode.INIT_SUCCESS) {
|
|
127
|
+
console.log('Initialization successful');
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Parameters:**
|
|
132
|
+
| Parameter | Type | Required | Description |
|
|
133
|
+
|-----------|------|----------|-------------|
|
|
134
|
+
| `mnemonic` | `string` | Yes | BIP-39 mnemonic (12/15/18/21/24 words) |
|
|
135
|
+
| `password` | `string` | Yes | Protection password to set |
|
|
136
|
+
| `onCardIdentified` | `() => void` | No | Callback before writing begins |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### `updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified?)`
|
|
141
|
+
|
|
142
|
+
Update card: authenticate with old password, then write new mnemonic + new password.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const result = await updateCard('old-password', 'new-password', 'new mnemonic words ...');
|
|
146
|
+
if (result.code === ResultCode.WRITE_SUCCESS) {
|
|
147
|
+
console.log('Update successful');
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Parameters:**
|
|
152
|
+
| Parameter | Type | Required | Description |
|
|
153
|
+
|-----------|------|----------|-------------|
|
|
154
|
+
| `oldPassword` | `string` | Yes | Current password |
|
|
155
|
+
| `newPassword` | `string` | Yes | New password |
|
|
156
|
+
| `newMnemonic` | `string` | Yes | New mnemonic |
|
|
157
|
+
| `onCardIdentified` | `() => void` | No | Callback after successful authentication |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### `updatePassword(oldPassword, newPassword, onCardIdentified?)`
|
|
162
|
+
|
|
163
|
+
Change password only, without modifying mnemonic data.
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const result = await updatePassword('old-password', 'new-password');
|
|
167
|
+
if (result.code === ResultCode.UPDATE_PASSWORD_SUCCESS) {
|
|
168
|
+
console.log('Password updated');
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### `writeUserNickname(password, nickname)`
|
|
175
|
+
|
|
176
|
+
Write a user nickname to the card (max 12 bytes, UTF-8 encoded).
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const result = await writeUserNickname('your-password', 'MyCard');
|
|
180
|
+
if (result.code === ResultCode.WRITE_NICKNAME_SUCCESS) {
|
|
181
|
+
console.log('Nickname written');
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
### `readUserNickname(password?)`
|
|
188
|
+
|
|
189
|
+
Read user nickname from the card. Supply password if read-protection is enabled.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const result = await readUserNickname('your-password');
|
|
193
|
+
if (result.success) {
|
|
194
|
+
console.log('Nickname:', result.data?.nickname);
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### `resetCard(password?, onCardIdentified?)`
|
|
201
|
+
|
|
202
|
+
Reset card: wipe all user data, set password to `"000000"`.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const result = await resetCard('your-password');
|
|
206
|
+
if (result.code === ResultCode.RESET_SUCCESS) {
|
|
207
|
+
console.log('Reset successful');
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
> ⚠️ This operation is irreversible. Use with caution.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### `readMnemonicRetryCount()`
|
|
216
|
+
|
|
217
|
+
Read the current PIN retry counter value.
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const result = await readMnemonicRetryCount();
|
|
221
|
+
if (result.success) {
|
|
222
|
+
console.log('Remaining retries:', result.data?.retryCount);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
### `resetRetryCountTo10()`
|
|
229
|
+
|
|
230
|
+
Reset the PIN retry counter to its default value (10).
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const result = await resetRetryCountTo10();
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### NFC Lock Management
|
|
239
|
+
|
|
240
|
+
For app-level NFC session lifecycle management:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import {
|
|
244
|
+
isNfcOperationLocked,
|
|
245
|
+
releaseNfcOperationLock,
|
|
246
|
+
markNfcOperationCancelledByCleanup,
|
|
247
|
+
consumeNfcOperationCancelledByCleanup,
|
|
248
|
+
} from '@ukeyfe/react-native-nfc-litecard';
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
| Method | Description |
|
|
252
|
+
|--------|-------------|
|
|
253
|
+
| `isNfcOperationLocked()` | Check if the NFC operation lock is held |
|
|
254
|
+
| `releaseNfcOperationLock()` | Force-release the lock (use on page unmount) |
|
|
255
|
+
| `markNfcOperationCancelledByCleanup()` | Mark current operation as interrupted by cleanup |
|
|
256
|
+
| `consumeNfcOperationCancelledByCleanup()` | Consume the cleanup flag (returns whether it was set) |
|
|
257
|
+
|
|
258
|
+
## NfcResult Structure
|
|
259
|
+
|
|
260
|
+
All APIs return a unified `NfcResult`:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
interface NfcResult {
|
|
264
|
+
code: number; // Status code – compare against ResultCode
|
|
265
|
+
success: boolean; // Whether the operation succeeded
|
|
266
|
+
data?: { // Optional data, only present for some operations
|
|
267
|
+
mnemonic?: string;
|
|
268
|
+
type?: string;
|
|
269
|
+
entropyHex?: string;
|
|
270
|
+
rawBytes?: string;
|
|
271
|
+
nickname?: string;
|
|
272
|
+
retryCount?: number;
|
|
273
|
+
aesKeyHex?: string;
|
|
274
|
+
crc16?: number;
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Error Handling Example
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { ResultCode, readMnemonic } from '@ukeyfe/react-native-nfc-litecard';
|
|
283
|
+
|
|
284
|
+
const result = await readMnemonic('password');
|
|
285
|
+
|
|
286
|
+
switch (result.code) {
|
|
287
|
+
case ResultCode.READ_SUCCESS:
|
|
288
|
+
console.log('Read successful:', result.data?.mnemonic);
|
|
289
|
+
break;
|
|
290
|
+
case ResultCode.AUTH_WRONG_PASSWORD:
|
|
291
|
+
alert('Wrong password');
|
|
292
|
+
break;
|
|
293
|
+
case ResultCode.NFC_CONNECT_FAILED:
|
|
294
|
+
alert('NFC connection failed, please re-tap the card');
|
|
295
|
+
break;
|
|
296
|
+
case ResultCode.NFC_USER_CANCELED:
|
|
297
|
+
// iOS user cancelled – handle silently
|
|
298
|
+
break;
|
|
299
|
+
case ResultCode.READ_TIMEOUT:
|
|
300
|
+
alert('Read timeout – remove and re-tap the card');
|
|
301
|
+
break;
|
|
302
|
+
default:
|
|
303
|
+
alert('Operation failed');
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Result Codes
|
|
308
|
+
|
|
309
|
+
### Success Codes
|
|
310
|
+
|
|
311
|
+
| Constant | Value | Description |
|
|
312
|
+
|----------|-------|-------------|
|
|
313
|
+
| `READ_SUCCESS` | 10102 | Mnemonic read successful |
|
|
314
|
+
| `READ_NICKNAME_SUCCESS` | 10103 | Nickname read successful |
|
|
315
|
+
| `CHECK_EMPTY` | 10104 | Empty card |
|
|
316
|
+
| `CHECK_HAS_DATA` | 10105 | Card has data |
|
|
317
|
+
| `READ_RETRY_COUNT_SUCCESS` | 10106 | Retry count read successful |
|
|
318
|
+
| `INIT_SUCCESS` | 10201 | Initialization successful |
|
|
319
|
+
| `WRITE_SUCCESS` | 10203 | Write/update successful |
|
|
320
|
+
| `UPDATE_PASSWORD_SUCCESS` | 10204 | Password change successful |
|
|
321
|
+
| `WRITE_NICKNAME_SUCCESS` | 10205 | Nickname written |
|
|
322
|
+
| `RESET_SUCCESS` | 10206 | Card reset successful |
|
|
323
|
+
|
|
324
|
+
### Error Codes
|
|
325
|
+
|
|
326
|
+
| Constant | Value | Description |
|
|
327
|
+
|----------|-------|-------------|
|
|
328
|
+
| `NFC_CONNECT_FAILED` | 40001 | NFC connection failed |
|
|
329
|
+
| `AUTH_WRONG_PASSWORD` | 40002 | Wrong password |
|
|
330
|
+
| `AUTH_INVALID_RESPONSE` | 40003 | Invalid authentication response |
|
|
331
|
+
| `AUTH_VERIFY_FAILED` | 40004 | Authentication verification failed |
|
|
332
|
+
| `READ_FAILED` | 40005 | Read failed |
|
|
333
|
+
| `WRITE_FAILED` | 40006 | Write failed |
|
|
334
|
+
| `INVALID_MNEMONIC` | 40007 | Invalid mnemonic |
|
|
335
|
+
| `UNSUPPORTED_MNEMONIC_LENGTH` | 40008 | Unsupported mnemonic length |
|
|
336
|
+
| `INVALID_CARD_DATA` | 40009 | Invalid card data |
|
|
337
|
+
| `UNKNOWN_ERROR` | 40010 | Unknown error |
|
|
338
|
+
| `NFC_USER_CANCELED` | 40011 | User cancelled NFC scan (iOS) |
|
|
339
|
+
| `READ_TIMEOUT` | 40012 | Read timeout |
|
|
340
|
+
| `NFC_LOCK_TIMEOUT` | 40013 | NFC lock timeout |
|
|
341
|
+
| `CRC16_CHECK_FAILED` | 40014 | CRC16 check failed |
|
|
342
|
+
|
|
343
|
+
## Storage Format
|
|
344
|
+
|
|
345
|
+
The card stores BIP-39 mnemonics using entropy compression:
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
[type 1B] [entropy 16-32B] [CRC16 2B]
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
| Type | Mnemonic Length | Entropy Size |
|
|
352
|
+
|------|----------------|--------------|
|
|
353
|
+
| 0x01 | 12 words | 16 bytes (128-bit) |
|
|
354
|
+
| 0x02 | 15 words | 20 bytes (160-bit) |
|
|
355
|
+
| 0x03 | 18 words | 24 bytes (192-bit) |
|
|
356
|
+
| 0x04 | 21 words | 28 bytes (224-bit) |
|
|
357
|
+
| 0x05 | 24 words | 32 bytes (256-bit) |
|
|
358
|
+
|
|
359
|
+
## Security
|
|
360
|
+
|
|
361
|
+
- **AES-128 hardware-level mutual authentication** (3-pass)
|
|
362
|
+
- **SHA-256 key derivation**: password → SHA-256 → first 16 bytes as AES key
|
|
363
|
+
- **CRC16-Modbus checksum**: data integrity verification
|
|
364
|
+
- **PIN retry counter**: auto-decrements on wrong password, resets to 10 on success
|
|
365
|
+
- **Secure random**: authentication uses `crypto.getRandomValues()` (requires Hermes ≥ 0.72 or `react-native-get-random-values` polyfill)
|
|
366
|
+
|
|
367
|
+
## Platform Support
|
|
368
|
+
|
|
369
|
+
| Platform | Technology | Requirements |
|
|
370
|
+
|----------|-----------|--------------|
|
|
371
|
+
| iOS | MifareIOS | iPhone 7 or later |
|
|
372
|
+
| Android | NfcA | NFC-capable device |
|
|
373
|
+
|
|
374
|
+
## Project Structure
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
src/
|
|
378
|
+
├── index.ts # Public API exports
|
|
379
|
+
├── constants.ts # Shared constants (page addresses, NFC commands, mnemonic types)
|
|
380
|
+
├── types.ts # Unified ResultCode, NfcResult interface, error mapping
|
|
381
|
+
├── crypto.ts # AES encrypt/decrypt, key derivation, secure random
|
|
382
|
+
├── utils.ts # CRC16, hex conversion
|
|
383
|
+
├── nfc-core.ts # NFC lock, transceive, authentication, retry counter
|
|
384
|
+
├── reader.ts # Reader API
|
|
385
|
+
└── writer.ts # Writer API
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## License
|
|
389
|
+
|
|
390
|
+
MIT
|