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