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