@ukeyfe/react-native-nfc-litecard 1.0.0 → 1.0.1
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 +117 -46
- package/README.md +108 -38
- 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 +9 -2
- package/dist/writer.js +65 -5
- package/package.json +3 -2
package/README.en.md
CHANGED
|
@@ -55,36 +55,51 @@ import {
|
|
|
55
55
|
|
|
56
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
|
-
// 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
|
|
68
81
|
}
|
|
69
82
|
```
|
|
70
83
|
|
|
71
84
|
**Parameters:**
|
|
72
85
|
| Parameter | Type | Required | Description |
|
|
73
86
|
|-----------|------|----------|-------------|
|
|
74
|
-
| `
|
|
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”. |
|
|
75
89
|
|
|
76
90
|
**Result codes:**
|
|
77
91
|
| Code | Meaning |
|
|
78
92
|
|------|---------|
|
|
79
|
-
| `ResultCode.CHECK_EMPTY` (10104) | Empty card |
|
|
80
|
-
| `ResultCode.CHECK_HAS_DATA` (10105) | Card has data |
|
|
81
|
-
| `ResultCode.
|
|
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
|
-
Read mnemonic
|
|
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');
|
|
@@ -99,24 +114,24 @@ if (result.success) {
|
|
|
99
114
|
**Parameters:**
|
|
100
115
|
| Parameter | Type | Required | Description |
|
|
101
116
|
|-----------|------|----------|-------------|
|
|
102
|
-
| `password` | `string` | Yes | Card protection password |
|
|
103
|
-
| `onCardIdentified` | `() => void` | No |
|
|
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”. |
|
|
104
119
|
|
|
105
120
|
**Returned `data` fields:**
|
|
106
|
-
| Field | Description |
|
|
107
|
-
|
|
108
|
-
| `mnemonic` | BIP-39 mnemonic
|
|
109
|
-
| `type` | Mnemonic type (e.g. "12 words (128-bit)") |
|
|
110
|
-
| `entropyHex` | Entropy as
|
|
111
|
-
| `rawBytes` |
|
|
112
|
-
| `nickname` | User nickname
|
|
113
|
-
| `retryCount` | Retry count after successful
|
|
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
|
-
Initialize a blank card:
|
|
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(
|
|
@@ -131,15 +146,15 @@ if (result.code === ResultCode.INIT_SUCCESS) {
|
|
|
131
146
|
**Parameters:**
|
|
132
147
|
| Parameter | Type | Required | Description |
|
|
133
148
|
|-----------|------|----------|-------------|
|
|
134
|
-
| `mnemonic` | `string` | Yes | BIP-39 mnemonic
|
|
135
|
-
| `password` | `string` | Yes | Protection password to set |
|
|
136
|
-
| `onCardIdentified` | `() => void` | No |
|
|
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
|
-
Update card: authenticate with old password, then write new mnemonic
|
|
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 ...');
|
|
@@ -148,19 +163,34 @@ if (result.code === ResultCode.WRITE_SUCCESS) {
|
|
|
148
163
|
}
|
|
149
164
|
```
|
|
150
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
|
+
|
|
151
180
|
**Parameters:**
|
|
152
181
|
| Parameter | Type | Required | Description |
|
|
153
182
|
|-----------|------|----------|-------------|
|
|
154
|
-
| `oldPassword` | `string` | Yes | Current password |
|
|
155
|
-
| `newPassword` | `string` | Yes | New password |
|
|
156
|
-
| `newMnemonic` | `string` | Yes | New mnemonic |
|
|
157
|
-
| `onCardIdentified` | `() => void` | No |
|
|
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
|
-
Change password only
|
|
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');
|
|
@@ -169,11 +199,18 @@ if (result.code === ResultCode.UPDATE_PASSWORD_SUCCESS) {
|
|
|
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
|
-
Write a user nickname to the card
|
|
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');
|
|
@@ -182,11 +219,18 @@ if (result.code === ResultCode.WRITE_NICKNAME_SUCCESS) {
|
|
|
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
|
-
Read user nickname from the card.
|
|
233
|
+
Read the user nickname from the card.
|
|
190
234
|
|
|
191
235
|
```typescript
|
|
192
236
|
const result = await readUserNickname('your-password');
|
|
@@ -195,11 +239,17 @@ if (result.success) {
|
|
|
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
250
|
### `resetCard(password?, onCardIdentified?)`
|
|
201
251
|
|
|
202
|
-
Reset card: wipe all user data, set password to `"000000"
|
|
252
|
+
Reset the card: wipe all user data (mnemonic, nickname), set password to `"000000"`, and disable read/write protection.
|
|
203
253
|
|
|
204
254
|
```typescript
|
|
205
255
|
const result = await resetCard('your-password');
|
|
@@ -208,13 +258,19 @@ if (result.code === ResultCode.RESET_SUCCESS) {
|
|
|
208
258
|
}
|
|
209
259
|
```
|
|
210
260
|
|
|
211
|
-
> ⚠️ This operation is irreversible
|
|
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 |
|
|
212
268
|
|
|
213
269
|
---
|
|
214
270
|
|
|
215
|
-
### `readMnemonicRetryCount()`
|
|
271
|
+
### `readMnemonicRetryCount(onCardIdentified?)`
|
|
216
272
|
|
|
217
|
-
Read the
|
|
273
|
+
Read the PIN retry counter on the card. No password authentication is required (the counter page is outside the protected area).
|
|
218
274
|
|
|
219
275
|
```typescript
|
|
220
276
|
const result = await readMnemonicRetryCount();
|
|
@@ -223,16 +279,26 @@ if (result.success) {
|
|
|
223
279
|
}
|
|
224
280
|
```
|
|
225
281
|
|
|
282
|
+
**Parameters:**
|
|
283
|
+
| Parameter | Type | Required | Description |
|
|
284
|
+
|-----------|------|----------|-------------|
|
|
285
|
+
| `onCardIdentified` | `() => void` | No | Called after the NFC session is established |
|
|
286
|
+
|
|
226
287
|
---
|
|
227
288
|
|
|
228
|
-
### `resetRetryCountTo10()`
|
|
289
|
+
### `resetRetryCountTo10(onCardIdentified?)`
|
|
229
290
|
|
|
230
|
-
Reset the PIN retry counter to its default value (10).
|
|
291
|
+
Reset the PIN retry counter to its default value (10). No password authentication is required.
|
|
231
292
|
|
|
232
293
|
```typescript
|
|
233
294
|
const result = await resetRetryCountTo10();
|
|
234
295
|
```
|
|
235
296
|
|
|
297
|
+
**Parameters:**
|
|
298
|
+
| Parameter | Type | Required | Description |
|
|
299
|
+
|-----------|------|----------|-------------|
|
|
300
|
+
| `onCardIdentified` | `() => void` | No | Called after the NFC session is established |
|
|
301
|
+
|
|
236
302
|
---
|
|
237
303
|
|
|
238
304
|
### NFC Lock Management
|
|
@@ -299,6 +365,9 @@ switch (result.code) {
|
|
|
299
365
|
case ResultCode.READ_TIMEOUT:
|
|
300
366
|
alert('Read timeout – remove and re-tap the card');
|
|
301
367
|
break;
|
|
368
|
+
case ResultCode.RETRY_COUNT_EXHAUSTED:
|
|
369
|
+
alert('Retry count exhausted – card is locked');
|
|
370
|
+
break;
|
|
302
371
|
default:
|
|
303
372
|
alert('Operation failed');
|
|
304
373
|
}
|
|
@@ -320,6 +389,7 @@ switch (result.code) {
|
|
|
320
389
|
| `UPDATE_PASSWORD_SUCCESS` | 10204 | Password change successful |
|
|
321
390
|
| `WRITE_NICKNAME_SUCCESS` | 10205 | Nickname written |
|
|
322
391
|
| `RESET_SUCCESS` | 10206 | Card reset successful |
|
|
392
|
+
| `PRECHECK_HAS_BACKUP` | 10207 | Card already has a valid backup; write was skipped |
|
|
323
393
|
|
|
324
394
|
### Error Codes
|
|
325
395
|
|
|
@@ -339,6 +409,7 @@ switch (result.code) {
|
|
|
339
409
|
| `READ_TIMEOUT` | 40012 | Read timeout |
|
|
340
410
|
| `NFC_LOCK_TIMEOUT` | 40013 | NFC lock timeout |
|
|
341
411
|
| `CRC16_CHECK_FAILED` | 40014 | CRC16 check failed |
|
|
412
|
+
| `RETRY_COUNT_EXHAUSTED` | 40015 | PIN retry count exhausted, card locked |
|
|
342
413
|
|
|
343
414
|
## Storage Format
|
|
344
415
|
|
|
@@ -364,13 +435,6 @@ The card stores BIP-39 mnemonics using entropy compression:
|
|
|
364
435
|
- **PIN retry counter**: auto-decrements on wrong password, resets to 10 on success
|
|
365
436
|
- **Secure random**: authentication uses `crypto.getRandomValues()` (requires Hermes ≥ 0.72 or `react-native-get-random-values` polyfill)
|
|
366
437
|
|
|
367
|
-
## Platform Support
|
|
368
|
-
|
|
369
|
-
| Platform | Technology | Requirements |
|
|
370
|
-
|----------|-----------|--------------|
|
|
371
|
-
| iOS | MifareIOS | iPhone 7 or later |
|
|
372
|
-
| Android | NfcA | NFC-capable device |
|
|
373
|
-
|
|
374
438
|
## Project Structure
|
|
375
439
|
|
|
376
440
|
```
|
|
@@ -379,12 +443,19 @@ src/
|
|
|
379
443
|
├── constants.ts # Shared constants (page addresses, NFC commands, mnemonic types)
|
|
380
444
|
├── types.ts # Unified ResultCode, NfcResult interface, error mapping
|
|
381
445
|
├── crypto.ts # AES encrypt/decrypt, key derivation, secure random
|
|
382
|
-
├── utils.ts # CRC16, hex conversion
|
|
446
|
+
├── utils.ts # CRC16, hex conversion, array utilities
|
|
383
447
|
├── nfc-core.ts # NFC lock, transceive, authentication, retry counter
|
|
384
448
|
├── reader.ts # Reader API
|
|
385
449
|
└── writer.ts # Writer API
|
|
386
450
|
```
|
|
387
451
|
|
|
452
|
+
## Platform Support
|
|
453
|
+
|
|
454
|
+
| Platform | Technology | Requirements |
|
|
455
|
+
|----------|-----------|--------------|
|
|
456
|
+
| iOS | MifareIOS | iPhone 7 or later |
|
|
457
|
+
| Android | NfcA | NFC-capable device |
|
|
458
|
+
|
|
388
459
|
## License
|
|
389
460
|
|
|
390
461
|
MIT
|
package/README.md
CHANGED
|
@@ -55,36 +55,51 @@ import {
|
|
|
55
55
|
|
|
56
56
|
## API 文档
|
|
57
57
|
|
|
58
|
-
### `checkCard(onCardIdentified?)`
|
|
58
|
+
### `checkCard(password?, onCardIdentified?)`
|
|
59
59
|
|
|
60
60
|
检测卡片状态(空卡 / 有数据)。
|
|
61
61
|
|
|
62
|
+
**不传密码(快速探测):**
|
|
62
63
|
```typescript
|
|
63
64
|
const result = await checkCard();
|
|
64
65
|
if (result.code === ResultCode.CHECK_EMPTY) {
|
|
65
66
|
// 空卡,可以初始化
|
|
66
67
|
} else if (result.code === ResultCode.CHECK_HAS_DATA) {
|
|
67
|
-
//
|
|
68
|
+
// 有数据(或卡开了读保护无法确定)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**传密码(认证后深度检查,适用于开了读保护的卡):**
|
|
73
|
+
```typescript
|
|
74
|
+
const result = await checkCard('password');
|
|
75
|
+
if (result.code === ResultCode.CHECK_EMPTY) {
|
|
76
|
+
// 空卡,可以写入
|
|
77
|
+
} else if (result.code === ResultCode.CHECK_HAS_DATA) {
|
|
78
|
+
// 有合法助记词备份,类型: result.data?.type
|
|
79
|
+
} else if (result.code === ResultCode.AUTH_WRONG_PASSWORD) {
|
|
80
|
+
// 密码错误
|
|
68
81
|
}
|
|
69
82
|
```
|
|
70
83
|
|
|
71
84
|
**参数:**
|
|
72
85
|
| 参数 | 类型 | 必填 | 说明 |
|
|
73
86
|
|------|------|------|------|
|
|
74
|
-
| `
|
|
87
|
+
| `password` | `string` | 否 | 卡片保护密码。不传时直接尝试读取(适合未加密的卡);传入后先用密码进行 AES 认证再读取完整数据并校验 CRC16(适合开了读保护的卡,结果更准确) |
|
|
88
|
+
| `onCardIdentified` | `() => void` | 否 | NFC 连接建立后的回调,可用于 UI 提示"已识别到卡片" |
|
|
75
89
|
|
|
76
90
|
**返回值 (`NfcResult`):**
|
|
77
91
|
| code | 含义 |
|
|
78
92
|
|------|------|
|
|
79
|
-
| `ResultCode.CHECK_EMPTY` (10104) |
|
|
80
|
-
| `ResultCode.CHECK_HAS_DATA` (10105) |
|
|
81
|
-
| `ResultCode.
|
|
93
|
+
| `ResultCode.CHECK_EMPTY` (10104) | 空卡,没有助记词数据 |
|
|
94
|
+
| `ResultCode.CHECK_HAS_DATA` (10105) | 卡上有数据。传密码时 `data.type` 包含助记词类型(如 "12 words (128-bit)") |
|
|
95
|
+
| `ResultCode.AUTH_WRONG_PASSWORD` (40002) | 密码错误(仅传密码时可能返回) |
|
|
96
|
+
| `ResultCode.NFC_CONNECT_FAILED` (40001) | NFC 连接失败,卡片未贴近或设备不支持 |
|
|
82
97
|
|
|
83
98
|
---
|
|
84
99
|
|
|
85
100
|
### `readMnemonic(password, onCardIdentified?)`
|
|
86
101
|
|
|
87
|
-
|
|
102
|
+
读取助记词(需要密码认证)。认证前会自动递减重试计数器,认证成功后重置为 10。
|
|
88
103
|
|
|
89
104
|
```typescript
|
|
90
105
|
const result = await readMnemonic('your-password');
|
|
@@ -99,24 +114,24 @@ if (result.success) {
|
|
|
99
114
|
**参数:**
|
|
100
115
|
| 参数 | 类型 | 必填 | 说明 |
|
|
101
116
|
|------|------|------|------|
|
|
102
|
-
| `password` | `string` | 是 |
|
|
103
|
-
| `onCardIdentified` | `() => void` | 否 |
|
|
117
|
+
| `password` | `string` | 是 | 卡片保护密码,用于 AES-128 认证 |
|
|
118
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后、开始读取数据前的回调,可用于 UI 提示"正在读取" |
|
|
104
119
|
|
|
105
120
|
**返回 `data` 字段:**
|
|
106
|
-
| 字段 | 说明 |
|
|
107
|
-
|
|
108
|
-
| `mnemonic` | BIP-39
|
|
109
|
-
| `type` | 助记词类型(如 "12 words (128-bit)") |
|
|
110
|
-
| `entropyHex` |
|
|
111
|
-
| `rawBytes` |
|
|
112
|
-
| `nickname` |
|
|
113
|
-
| `retryCount` |
|
|
121
|
+
| 字段 | 类型 | 说明 |
|
|
122
|
+
|------|------|------|
|
|
123
|
+
| `mnemonic` | `string` | BIP-39 助记词(如 "abandon abandon ... about") |
|
|
124
|
+
| `type` | `string` | 助记词类型(如 "12 words (128-bit)"、"24 words (256-bit)") |
|
|
125
|
+
| `entropyHex` | `string` | 熵数据的十六进制字符串 |
|
|
126
|
+
| `rawBytes` | `string` | 卡上原始数据(类型+熵)的十六进制字符串 |
|
|
127
|
+
| `nickname` | `string` | 用户昵称(如果卡上有设置) |
|
|
128
|
+
| `retryCount` | `number` | 认证成功后重置的重试次数(正常为 10) |
|
|
114
129
|
|
|
115
130
|
---
|
|
116
131
|
|
|
117
132
|
### `initializeCard(mnemonic, password, onCardIdentified?)`
|
|
118
133
|
|
|
119
|
-
|
|
134
|
+
初始化空卡:将助记词转为 BIP-39 熵写入卡片,设置 AES 密码保护,开启读写认证。
|
|
120
135
|
|
|
121
136
|
```typescript
|
|
122
137
|
const result = await initializeCard(
|
|
@@ -131,15 +146,15 @@ if (result.code === ResultCode.INIT_SUCCESS) {
|
|
|
131
146
|
**参数:**
|
|
132
147
|
| 参数 | 类型 | 必填 | 说明 |
|
|
133
148
|
|------|------|------|------|
|
|
134
|
-
| `mnemonic` | `string` | 是 | BIP-39
|
|
135
|
-
| `password` | `string` | 是 |
|
|
136
|
-
| `onCardIdentified` | `() => void` | 否 |
|
|
149
|
+
| `mnemonic` | `string` | 是 | BIP-39 助记词,支持 12/15/18/21/24 词。库会自动转为熵 + CRC16 写入卡片 |
|
|
150
|
+
| `password` | `string` | 是 | 要设置的保护密码,用于生成 AES-128 密钥并写入卡片 |
|
|
151
|
+
| `onCardIdentified` | `() => void` | 否 | NFC 连接建立后、开始写入前的回调,可用于 UI 提示"正在写入" |
|
|
137
152
|
|
|
138
153
|
---
|
|
139
154
|
|
|
140
|
-
### `updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified?)`
|
|
155
|
+
### `updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified?, options?)`
|
|
141
156
|
|
|
142
|
-
|
|
157
|
+
更新卡片:用旧密码认证后,写入新助记词和新密码。认证前自动递减重试计数器。
|
|
143
158
|
|
|
144
159
|
```typescript
|
|
145
160
|
const result = await updateCard('old-password', 'new-password', 'new mnemonic words ...');
|
|
@@ -148,19 +163,33 @@ if (result.code === ResultCode.WRITE_SUCCESS) {
|
|
|
148
163
|
}
|
|
149
164
|
```
|
|
150
165
|
|
|
166
|
+
**写入前检查是否已有备份:**
|
|
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
|
+
console.log('卡上已有备份,未写入。类型:', result.data?.type);
|
|
174
|
+
} else if (result.code === ResultCode.WRITE_SUCCESS) {
|
|
175
|
+
console.log('写入成功');
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
151
179
|
**参数:**
|
|
152
180
|
| 参数 | 类型 | 必填 | 说明 |
|
|
153
181
|
|------|------|------|------|
|
|
154
|
-
| `oldPassword` | `string` | 是 |
|
|
155
|
-
| `newPassword` | `string` | 是 |
|
|
156
|
-
| `newMnemonic` | `string` | 是 |
|
|
157
|
-
| `onCardIdentified` | `() => void` | 否 |
|
|
182
|
+
| `oldPassword` | `string` | 是 | 当前卡片密码,用于 AES 认证 |
|
|
183
|
+
| `newPassword` | `string` | 是 | 新密码,写入后卡片将使用此密码保护 |
|
|
184
|
+
| `newMnemonic` | `string` | 是 | 新的 BIP-39 助记词(12/15/18/21/24 词) |
|
|
185
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后的回调,可用于 UI 提示"正在写入" |
|
|
186
|
+
| `options.precheckExistingMnemonic` | `boolean` | 否 | 设为 `true` 时,认证后先读取卡片数据并校验 CRC16。如果已有合法助记词,返回 `PRECHECK_HAS_BACKUP` 不执行写入 |
|
|
158
187
|
|
|
159
188
|
---
|
|
160
189
|
|
|
161
190
|
### `updatePassword(oldPassword, newPassword, onCardIdentified?)`
|
|
162
191
|
|
|
163
|
-
|
|
192
|
+
仅修改密码,不更改卡上的助记词数据。认证前自动递减重试计数器。
|
|
164
193
|
|
|
165
194
|
```typescript
|
|
166
195
|
const result = await updatePassword('old-password', 'new-password');
|
|
@@ -169,11 +198,18 @@ if (result.code === ResultCode.UPDATE_PASSWORD_SUCCESS) {
|
|
|
169
198
|
}
|
|
170
199
|
```
|
|
171
200
|
|
|
201
|
+
**参数:**
|
|
202
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
203
|
+
|------|------|------|------|
|
|
204
|
+
| `oldPassword` | `string` | 是 | 当前卡片密码,用于 AES 认证 |
|
|
205
|
+
| `newPassword` | `string` | 是 | 新密码,修改后卡片将使用此密码保护 |
|
|
206
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后的回调 |
|
|
207
|
+
|
|
172
208
|
---
|
|
173
209
|
|
|
174
|
-
### `writeUserNickname(password, nickname)`
|
|
210
|
+
### `writeUserNickname(password, nickname, onCardIdentified?)`
|
|
175
211
|
|
|
176
|
-
|
|
212
|
+
写入用户昵称到卡片。昵称使用 UTF-8 编码,最长 12 字节(超出自动截断)。
|
|
177
213
|
|
|
178
214
|
```typescript
|
|
179
215
|
const result = await writeUserNickname('your-password', 'MyCard');
|
|
@@ -182,11 +218,18 @@ if (result.code === ResultCode.WRITE_NICKNAME_SUCCESS) {
|
|
|
182
218
|
}
|
|
183
219
|
```
|
|
184
220
|
|
|
221
|
+
**参数:**
|
|
222
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
223
|
+
|------|------|------|------|
|
|
224
|
+
| `password` | `string` | 是 | 卡片保护密码,用于 AES 认证 |
|
|
225
|
+
| `nickname` | `string` | 是 | 用户昵称,UTF-8 编码最长 12 字节。中文约 4 个字,英文 12 个字符 |
|
|
226
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后的回调 |
|
|
227
|
+
|
|
185
228
|
---
|
|
186
229
|
|
|
187
|
-
### `readUserNickname(password?)`
|
|
230
|
+
### `readUserNickname(password?, onCardIdentified?)`
|
|
188
231
|
|
|
189
|
-
|
|
232
|
+
读取卡片上的用户昵称。
|
|
190
233
|
|
|
191
234
|
```typescript
|
|
192
235
|
const result = await readUserNickname('your-password');
|
|
@@ -195,11 +238,17 @@ if (result.success) {
|
|
|
195
238
|
}
|
|
196
239
|
```
|
|
197
240
|
|
|
241
|
+
**参数:**
|
|
242
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
243
|
+
|------|------|------|------|
|
|
244
|
+
| `password` | `string` | 否 | 卡片密码。如果卡开了读保护(PROT=1)则必须传入,否则可省略 |
|
|
245
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后的回调 |
|
|
246
|
+
|
|
198
247
|
---
|
|
199
248
|
|
|
200
249
|
### `resetCard(password?, onCardIdentified?)`
|
|
201
250
|
|
|
202
|
-
|
|
251
|
+
重置卡片:清空所有用户数据(助记词、昵称),密码重置为 `"000000"`,关闭读写保护。
|
|
203
252
|
|
|
204
253
|
```typescript
|
|
205
254
|
const result = await resetCard('your-password');
|
|
@@ -208,13 +257,19 @@ if (result.code === ResultCode.RESET_SUCCESS) {
|
|
|
208
257
|
}
|
|
209
258
|
```
|
|
210
259
|
|
|
211
|
-
> ⚠️
|
|
260
|
+
> ⚠️ 重置操作不可逆,卡上的助记词数据将被永久清除。
|
|
261
|
+
|
|
262
|
+
**参数:**
|
|
263
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
264
|
+
|------|------|------|------|
|
|
265
|
+
| `password` | `string` | 否 | 当前卡片密码。如果卡开了保护则必须传入,否则可省略。认证前自动递减重试计数器 |
|
|
266
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后的回调 |
|
|
212
267
|
|
|
213
268
|
---
|
|
214
269
|
|
|
215
|
-
### `readMnemonicRetryCount()`
|
|
270
|
+
### `readMnemonicRetryCount(onCardIdentified?)`
|
|
216
271
|
|
|
217
|
-
|
|
272
|
+
读取卡片上的 PIN 重试计数器值。不需要密码认证(计数器页不在保护区域内)。
|
|
218
273
|
|
|
219
274
|
```typescript
|
|
220
275
|
const result = await readMnemonicRetryCount();
|
|
@@ -223,16 +278,26 @@ if (result.success) {
|
|
|
223
278
|
}
|
|
224
279
|
```
|
|
225
280
|
|
|
281
|
+
**参数:**
|
|
282
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
283
|
+
|------|------|------|------|
|
|
284
|
+
| `onCardIdentified` | `() => void` | 否 | NFC 连接建立后的回调 |
|
|
285
|
+
|
|
226
286
|
---
|
|
227
287
|
|
|
228
|
-
### `resetRetryCountTo10()`
|
|
288
|
+
### `resetRetryCountTo10(onCardIdentified?)`
|
|
229
289
|
|
|
230
|
-
将 PIN 重试计数器重置为默认值 10
|
|
290
|
+
将 PIN 重试计数器重置为默认值 10。不需要密码认证。
|
|
231
291
|
|
|
232
292
|
```typescript
|
|
233
293
|
const result = await resetRetryCountTo10();
|
|
234
294
|
```
|
|
235
295
|
|
|
296
|
+
**参数:**
|
|
297
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
298
|
+
|------|------|------|------|
|
|
299
|
+
| `onCardIdentified` | `() => void` | 否 | NFC 连接建立后的回调 |
|
|
300
|
+
|
|
236
301
|
---
|
|
237
302
|
|
|
238
303
|
### NFC 锁管理
|
|
@@ -299,6 +364,9 @@ switch (result.code) {
|
|
|
299
364
|
case ResultCode.READ_TIMEOUT:
|
|
300
365
|
alert('读取超时,请将卡片移开后重新贴近');
|
|
301
366
|
break;
|
|
367
|
+
case ResultCode.RETRY_COUNT_EXHAUSTED:
|
|
368
|
+
alert('重试次数已用完,卡片已锁定');
|
|
369
|
+
break;
|
|
302
370
|
default:
|
|
303
371
|
alert('操作失败');
|
|
304
372
|
}
|
|
@@ -320,6 +388,7 @@ switch (result.code) {
|
|
|
320
388
|
| `UPDATE_PASSWORD_SUCCESS` | 10204 | 修改密码成功 |
|
|
321
389
|
| `WRITE_NICKNAME_SUCCESS` | 10205 | 写入昵称成功 |
|
|
322
390
|
| `RESET_SUCCESS` | 10206 | 重置卡片成功 |
|
|
391
|
+
| `PRECHECK_HAS_BACKUP` | 10207 | 卡上已有合法备份,写入被跳过 |
|
|
323
392
|
|
|
324
393
|
### 错误码
|
|
325
394
|
|
|
@@ -339,6 +408,7 @@ switch (result.code) {
|
|
|
339
408
|
| `READ_TIMEOUT` | 40012 | 读取超时 |
|
|
340
409
|
| `NFC_LOCK_TIMEOUT` | 40013 | NFC 锁超时 |
|
|
341
410
|
| `CRC16_CHECK_FAILED` | 40014 | CRC16 校验失败 |
|
|
411
|
+
| `RETRY_COUNT_EXHAUSTED` | 40015 | PIN 重试次数已用完,卡片锁定 |
|
|
342
412
|
|
|
343
413
|
## 存储格式
|
|
344
414
|
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* NFC read/write library for MIFARE Ultralight AES (LiteCard mnemonic storage).
|
|
5
5
|
*/
|
|
6
|
-
export { ResultCode, type NfcResult } from './types';
|
|
6
|
+
export { ResultCode, type NfcResult, nfcResultRetryCountExhausted, } from './types';
|
|
7
7
|
export { checkCard, readMnemonic, readUserNickname, readMnemonicRetryCount, resetRetryCountTo10, cardInfoToJson, } from './reader';
|
|
8
8
|
export { initializeCard, updateCard, updatePassword, writeUserNickname, resetCard, } from './writer';
|
|
9
9
|
export { isNfcOperationLocked, releaseNfcOperationLock, markNfcOperationCancelledByCleanup, consumeNfcOperationCancelledByCleanup, getNfcOperationCancelledByCleanupTimestamp, clearNfcOperationCancelledByCleanup, } from './nfc-core';
|
package/dist/index.js
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* NFC read/write library for MIFARE Ultralight AES (LiteCard mnemonic storage).
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.DEFAULT_PIN_RETRY_COUNT = exports.clearNfcOperationCancelledByCleanup = exports.getNfcOperationCancelledByCleanupTimestamp = exports.consumeNfcOperationCancelledByCleanup = exports.markNfcOperationCancelledByCleanup = exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.resetCard = exports.writeUserNickname = exports.updatePassword = exports.updateCard = exports.initializeCard = exports.cardInfoToJson = exports.resetRetryCountTo10 = exports.readMnemonicRetryCount = exports.readUserNickname = exports.readMnemonic = exports.checkCard = exports.ResultCode = void 0;
|
|
8
|
+
exports.DEFAULT_PIN_RETRY_COUNT = exports.clearNfcOperationCancelledByCleanup = exports.getNfcOperationCancelledByCleanupTimestamp = exports.consumeNfcOperationCancelledByCleanup = exports.markNfcOperationCancelledByCleanup = exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.resetCard = exports.writeUserNickname = exports.updatePassword = exports.updateCard = exports.initializeCard = exports.cardInfoToJson = exports.resetRetryCountTo10 = exports.readMnemonicRetryCount = exports.readUserNickname = exports.readMnemonic = exports.checkCard = exports.nfcResultRetryCountExhausted = exports.ResultCode = void 0;
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Unified types & constants (consumers can use a single ResultCode)
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
var types_1 = require("./types");
|
|
13
13
|
Object.defineProperty(exports, "ResultCode", { enumerable: true, get: function () { return types_1.ResultCode; } });
|
|
14
|
+
Object.defineProperty(exports, "nfcResultRetryCountExhausted", { enumerable: true, get: function () { return types_1.nfcResultRetryCountExhausted; } });
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Reader API
|
|
16
17
|
// ---------------------------------------------------------------------------
|
package/dist/nfc-core.js
CHANGED
|
@@ -257,7 +257,9 @@ async function decrementRetryCountInSession() {
|
|
|
257
257
|
const page = pageBlock.slice(0, constants_1.PAGE_SIZE);
|
|
258
258
|
const raw = page[constants_1.RETRY_COUNTER_OFFSET];
|
|
259
259
|
const current = typeof raw === 'number' ? raw & 0xff : 0;
|
|
260
|
-
|
|
260
|
+
if (current <= 0)
|
|
261
|
+
throw new Error('RETRY_COUNT_EXHAUSTED');
|
|
262
|
+
const next = current - 1;
|
|
261
263
|
page[constants_1.RETRY_COUNTER_OFFSET] = next & 0xff;
|
|
262
264
|
await transceive([constants_1.CMD_WRITE, constants_1.RETRY_COUNTER_PAGE, ...page]);
|
|
263
265
|
return next;
|
package/dist/reader.d.ts
CHANGED
|
@@ -58,12 +58,17 @@ export declare function cardInfoToJson(cardInfo: ReturnType<typeof parseCardInfo
|
|
|
58
58
|
/**
|
|
59
59
|
* Detect whether the card is empty or already contains data.
|
|
60
60
|
*
|
|
61
|
-
*
|
|
61
|
+
* Without password: tries to read without auth.
|
|
62
62
|
* - Read succeeds & first byte is a valid mnemonic type → HAS_DATA
|
|
63
63
|
* - Read succeeds & first byte is other → EMPTY
|
|
64
|
-
* - Read fails (auth required) → HAS_DATA (read-protection is on)
|
|
64
|
+
* - Read fails (auth required) → HAS_DATA (read-protection is on, cannot determine)
|
|
65
|
+
*
|
|
66
|
+
* With password: authenticates first, then reads and validates with CRC16.
|
|
67
|
+
* - Valid mnemonic payload → HAS_DATA (with type info)
|
|
68
|
+
* - Empty or invalid data → EMPTY
|
|
69
|
+
* - Auth failure → AUTH_WRONG_PASSWORD
|
|
65
70
|
*/
|
|
66
|
-
export declare function checkCard(onCardIdentified?: () => void): Promise<NfcResult>;
|
|
71
|
+
export declare function checkCard(password?: string, onCardIdentified?: () => void): Promise<NfcResult>;
|
|
67
72
|
/**
|
|
68
73
|
* Read the mnemonic from a password-protected card.
|
|
69
74
|
*
|
|
@@ -75,8 +80,8 @@ export declare function readMnemonic(password: string, onCardIdentified?: () =>
|
|
|
75
80
|
* Read the user nickname from the card.
|
|
76
81
|
* @param password – supply if the card has read-protection enabled.
|
|
77
82
|
*/
|
|
78
|
-
export declare function readUserNickname(password?: string): Promise<NfcResult>;
|
|
83
|
+
export declare function readUserNickname(password?: string, onCardIdentified?: () => void): Promise<NfcResult>;
|
|
79
84
|
/** Read the current PIN retry counter from the card. */
|
|
80
|
-
export declare function readMnemonicRetryCount(): Promise<NfcResult>;
|
|
85
|
+
export declare function readMnemonicRetryCount(onCardIdentified?: () => void): Promise<NfcResult>;
|
|
81
86
|
/** Reset the PIN retry counter to the default value (10). */
|
|
82
|
-
export declare function resetRetryCountTo10(): Promise<NfcResult>;
|
|
87
|
+
export declare function resetRetryCountTo10(onCardIdentified?: () => void): Promise<NfcResult>;
|
package/dist/reader.js
CHANGED
|
@@ -228,12 +228,17 @@ function cardInfoToJson(cardInfo, pretty = true) {
|
|
|
228
228
|
/**
|
|
229
229
|
* Detect whether the card is empty or already contains data.
|
|
230
230
|
*
|
|
231
|
-
*
|
|
231
|
+
* Without password: tries to read without auth.
|
|
232
232
|
* - Read succeeds & first byte is a valid mnemonic type → HAS_DATA
|
|
233
233
|
* - Read succeeds & first byte is other → EMPTY
|
|
234
|
-
* - Read fails (auth required) → HAS_DATA (read-protection is on)
|
|
234
|
+
* - Read fails (auth required) → HAS_DATA (read-protection is on, cannot determine)
|
|
235
|
+
*
|
|
236
|
+
* With password: authenticates first, then reads and validates with CRC16.
|
|
237
|
+
* - Valid mnemonic payload → HAS_DATA (with type info)
|
|
238
|
+
* - Empty or invalid data → EMPTY
|
|
239
|
+
* - Auth failure → AUTH_WRONG_PASSWORD
|
|
235
240
|
*/
|
|
236
|
-
async function checkCard(onCardIdentified) {
|
|
241
|
+
async function checkCard(password, onCardIdentified) {
|
|
237
242
|
try {
|
|
238
243
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
239
244
|
}
|
|
@@ -248,25 +253,54 @@ async function checkCard(onCardIdentified) {
|
|
|
248
253
|
(0, nfc_core_1.releaseNfcLock)();
|
|
249
254
|
return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
|
|
250
255
|
}
|
|
251
|
-
onCardIdentified?.();
|
|
252
|
-
let response = null;
|
|
253
256
|
try {
|
|
254
|
-
|
|
257
|
+
if (password) {
|
|
258
|
+
// Authenticated check: authenticate → read full memory → validate CRC16
|
|
259
|
+
const aesKey = (0, crypto_1.passwordToAesKey)(password);
|
|
260
|
+
await (0, nfc_core_1.authenticate)(aesKey);
|
|
261
|
+
onCardIdentified?.();
|
|
262
|
+
const data = await readUserMemory();
|
|
263
|
+
try {
|
|
264
|
+
const decoded = (0, utils_1.validateMnemonicPayload)(data);
|
|
265
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
266
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
267
|
+
return { code: types_1.ResultCode.CHECK_HAS_DATA, success: true, data: { type: decoded.type } };
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
271
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
272
|
+
return { code: types_1.ResultCode.CHECK_EMPTY, success: true };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Unauthenticated check: try to read first page directly
|
|
277
|
+
onCardIdentified?.();
|
|
278
|
+
let response = null;
|
|
279
|
+
try {
|
|
280
|
+
response = await (0, nfc_core_1.transceive)([constants_1.CMD_READ, constants_1.USER_PAGE_START]);
|
|
281
|
+
}
|
|
282
|
+
catch { /* read-protected */ }
|
|
283
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
284
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
285
|
+
if (response && response.length >= 4) {
|
|
286
|
+
const first = response[0];
|
|
287
|
+
const hasData = first === constants_1.MNEMONIC_TYPE_12 ||
|
|
288
|
+
first === constants_1.MNEMONIC_TYPE_15 ||
|
|
289
|
+
first === constants_1.MNEMONIC_TYPE_18 ||
|
|
290
|
+
first === constants_1.MNEMONIC_TYPE_21 ||
|
|
291
|
+
first === constants_1.MNEMONIC_TYPE_24;
|
|
292
|
+
return { code: hasData ? types_1.ResultCode.CHECK_HAS_DATA : types_1.ResultCode.CHECK_EMPTY, success: true };
|
|
293
|
+
}
|
|
294
|
+
// Read failed = protection is on, assume has data
|
|
295
|
+
return { code: types_1.ResultCode.CHECK_HAS_DATA, success: true };
|
|
296
|
+
}
|
|
255
297
|
}
|
|
256
|
-
catch {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const hasData = first === constants_1.MNEMONIC_TYPE_12 ||
|
|
262
|
-
first === constants_1.MNEMONIC_TYPE_15 ||
|
|
263
|
-
first === constants_1.MNEMONIC_TYPE_18 ||
|
|
264
|
-
first === constants_1.MNEMONIC_TYPE_21 ||
|
|
265
|
-
first === constants_1.MNEMONIC_TYPE_24;
|
|
266
|
-
const code = hasData ? types_1.ResultCode.CHECK_HAS_DATA : types_1.ResultCode.CHECK_EMPTY;
|
|
267
|
-
return { code, success: true };
|
|
298
|
+
catch (error) {
|
|
299
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
300
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
301
|
+
const code = (0, types_1.errorToCode)(error);
|
|
302
|
+
return { code, success: false };
|
|
268
303
|
}
|
|
269
|
-
return { code: types_1.ResultCode.CHECK_HAS_DATA, success: true };
|
|
270
304
|
}
|
|
271
305
|
catch (error) {
|
|
272
306
|
(0, nfc_core_1.releaseNfcLock)();
|
|
@@ -302,7 +336,14 @@ async function readMnemonic(password, onCardIdentified) {
|
|
|
302
336
|
if (typeof next === 'number')
|
|
303
337
|
retryCountAfterPreDecrement = next;
|
|
304
338
|
}
|
|
305
|
-
catch {
|
|
339
|
+
catch (error) {
|
|
340
|
+
const code = (0, types_1.errorToCode)(error);
|
|
341
|
+
if (code === types_1.ResultCode.RETRY_COUNT_EXHAUSTED) {
|
|
342
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
343
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
344
|
+
return (0, types_1.nfcResultRetryCountExhausted)();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
306
347
|
const aesKey = (0, crypto_1.passwordToAesKey)(password);
|
|
307
348
|
await (0, nfc_core_1.authenticate)(aesKey);
|
|
308
349
|
onCardIdentified?.();
|
|
@@ -359,7 +400,7 @@ async function readMnemonic(password, onCardIdentified) {
|
|
|
359
400
|
* Read the user nickname from the card.
|
|
360
401
|
* @param password – supply if the card has read-protection enabled.
|
|
361
402
|
*/
|
|
362
|
-
async function readUserNickname(password) {
|
|
403
|
+
async function readUserNickname(password, onCardIdentified) {
|
|
363
404
|
try {
|
|
364
405
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
365
406
|
}
|
|
@@ -379,6 +420,7 @@ async function readUserNickname(password) {
|
|
|
379
420
|
const aesKey = (0, crypto_1.passwordToAesKey)(password);
|
|
380
421
|
await (0, nfc_core_1.authenticate)(aesKey);
|
|
381
422
|
}
|
|
423
|
+
onCardIdentified?.();
|
|
382
424
|
const nickname = await readUserNicknameInternal();
|
|
383
425
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
384
426
|
(0, nfc_core_1.releaseNfcLock)();
|
|
@@ -401,7 +443,7 @@ async function readUserNickname(password) {
|
|
|
401
443
|
}
|
|
402
444
|
}
|
|
403
445
|
/** Read the current PIN retry counter from the card. */
|
|
404
|
-
async function readMnemonicRetryCount() {
|
|
446
|
+
async function readMnemonicRetryCount(onCardIdentified) {
|
|
405
447
|
try {
|
|
406
448
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
407
449
|
}
|
|
@@ -417,6 +459,7 @@ async function readMnemonicRetryCount() {
|
|
|
417
459
|
return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
|
|
418
460
|
}
|
|
419
461
|
try {
|
|
462
|
+
onCardIdentified?.();
|
|
420
463
|
const pageBlock = await (0, nfc_core_1.transceive)([constants_1.CMD_READ, constants_1.RETRY_COUNTER_PAGE]);
|
|
421
464
|
if (!pageBlock || pageBlock.length < constants_1.PAGE_SIZE) {
|
|
422
465
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
@@ -449,7 +492,7 @@ async function readMnemonicRetryCount() {
|
|
|
449
492
|
}
|
|
450
493
|
}
|
|
451
494
|
/** Reset the PIN retry counter to the default value (10). */
|
|
452
|
-
async function resetRetryCountTo10() {
|
|
495
|
+
async function resetRetryCountTo10(onCardIdentified) {
|
|
453
496
|
try {
|
|
454
497
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
455
498
|
}
|
|
@@ -465,6 +508,7 @@ async function resetRetryCountTo10() {
|
|
|
465
508
|
return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
|
|
466
509
|
}
|
|
467
510
|
try {
|
|
511
|
+
onCardIdentified?.();
|
|
468
512
|
await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
|
|
469
513
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
470
514
|
(0, nfc_core_1.releaseNfcLock)();
|
package/dist/types.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export declare const ResultCode: {
|
|
|
20
20
|
readonly UPDATE_PASSWORD_SUCCESS: 10204;
|
|
21
21
|
readonly WRITE_NICKNAME_SUCCESS: 10205;
|
|
22
22
|
readonly RESET_SUCCESS: 10206;
|
|
23
|
+
/** Card already has a valid mnemonic backup; write was skipped */
|
|
24
|
+
readonly PRECHECK_HAS_BACKUP: 10207;
|
|
23
25
|
readonly NFC_CONNECT_FAILED: 40001;
|
|
24
26
|
readonly AUTH_WRONG_PASSWORD: 40002;
|
|
25
27
|
readonly AUTH_INVALID_RESPONSE: 40003;
|
|
@@ -34,6 +36,8 @@ export declare const ResultCode: {
|
|
|
34
36
|
readonly READ_TIMEOUT: 40012;
|
|
35
37
|
readonly NFC_LOCK_TIMEOUT: 40013;
|
|
36
38
|
readonly CRC16_CHECK_FAILED: 40014;
|
|
39
|
+
/** PIN retry counter is 0; no authentication attempts left */
|
|
40
|
+
readonly RETRY_COUNT_EXHAUSTED: 40015;
|
|
37
41
|
};
|
|
38
42
|
export interface NfcResult {
|
|
39
43
|
/** Numeric result code – compare against ResultCode constants */
|
|
@@ -52,6 +56,11 @@ export interface NfcResult {
|
|
|
52
56
|
crc16?: number;
|
|
53
57
|
};
|
|
54
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Unified failure result when the PIN retry counter has reached 0.
|
|
61
|
+
* Used by readMnemonic, updateCard, updatePassword, and resetCard.
|
|
62
|
+
*/
|
|
63
|
+
export declare function nfcResultRetryCountExhausted(): NfcResult;
|
|
55
64
|
/**
|
|
56
65
|
* Derive a ResultCode from an error thrown during NFC operations.
|
|
57
66
|
* Handles iOS-specific cancel detection, known error strings, and fallback.
|
package/dist/types.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.ResultCode = void 0;
|
|
15
|
+
exports.nfcResultRetryCountExhausted = nfcResultRetryCountExhausted;
|
|
15
16
|
exports.errorToCode = errorToCode;
|
|
16
17
|
const react_native_1 = require("react-native");
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -30,6 +31,8 @@ exports.ResultCode = {
|
|
|
30
31
|
UPDATE_PASSWORD_SUCCESS: 10204,
|
|
31
32
|
WRITE_NICKNAME_SUCCESS: 10205,
|
|
32
33
|
RESET_SUCCESS: 10206,
|
|
34
|
+
/** Card already has a valid mnemonic backup; write was skipped */
|
|
35
|
+
PRECHECK_HAS_BACKUP: 10207,
|
|
33
36
|
// Failure (4xxxx – shared by reader & writer)
|
|
34
37
|
NFC_CONNECT_FAILED: 40001,
|
|
35
38
|
AUTH_WRONG_PASSWORD: 40002,
|
|
@@ -45,7 +48,20 @@ exports.ResultCode = {
|
|
|
45
48
|
READ_TIMEOUT: 40012,
|
|
46
49
|
NFC_LOCK_TIMEOUT: 40013,
|
|
47
50
|
CRC16_CHECK_FAILED: 40014,
|
|
51
|
+
/** PIN retry counter is 0; no authentication attempts left */
|
|
52
|
+
RETRY_COUNT_EXHAUSTED: 40015,
|
|
48
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Unified failure result when the PIN retry counter has reached 0.
|
|
56
|
+
* Used by readMnemonic, updateCard, updatePassword, and resetCard.
|
|
57
|
+
*/
|
|
58
|
+
function nfcResultRetryCountExhausted() {
|
|
59
|
+
return {
|
|
60
|
+
code: exports.ResultCode.RETRY_COUNT_EXHAUSTED,
|
|
61
|
+
success: false,
|
|
62
|
+
data: { retryCount: 0 },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
49
65
|
// ---------------------------------------------------------------------------
|
|
50
66
|
// Error → code mapping (internal use)
|
|
51
67
|
// ---------------------------------------------------------------------------
|
|
@@ -71,6 +87,7 @@ function errorToCode(error) {
|
|
|
71
87
|
if (msg.includes('NFC_USER_CANCELED_SIGNAL'))
|
|
72
88
|
return exports.ResultCode.NFC_USER_CANCELED;
|
|
73
89
|
const keywords = [
|
|
90
|
+
['RETRY_COUNT_EXHAUSTED', exports.ResultCode.RETRY_COUNT_EXHAUSTED],
|
|
74
91
|
['AUTH_WRONG_PASSWORD', exports.ResultCode.AUTH_WRONG_PASSWORD],
|
|
75
92
|
['AUTH_INVALID_RESPONSE', exports.ResultCode.AUTH_INVALID_RESPONSE],
|
|
76
93
|
['AUTH_VERIFY_FAILED', exports.ResultCode.AUTH_VERIFY_FAILED],
|
package/dist/utils.d.ts
CHANGED
|
@@ -15,3 +15,13 @@ export declare function crc16ToBytes(crc16: number): Uint8Array;
|
|
|
15
15
|
* @throws if buffer is shorter than 2 bytes.
|
|
16
16
|
*/
|
|
17
17
|
export declare function extractCRC16(data: Uint8Array): number;
|
|
18
|
+
/**
|
|
19
|
+
* Validate whether raw card data contains a valid mnemonic payload.
|
|
20
|
+
* Checks type byte, length, and CRC16 — does NOT decode the mnemonic.
|
|
21
|
+
*
|
|
22
|
+
* @throws INVALID_CARD_DATA / EMPTY_CARD / CRC16_CHECK_FAILED
|
|
23
|
+
* @returns The mnemonic type description string.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateMnemonicPayload(data: Uint8Array): {
|
|
26
|
+
type: string;
|
|
27
|
+
};
|
package/dist/utils.js
CHANGED
|
@@ -8,6 +8,8 @@ exports.hexToBytes = hexToBytes;
|
|
|
8
8
|
exports.calculateCRC16 = calculateCRC16;
|
|
9
9
|
exports.crc16ToBytes = crc16ToBytes;
|
|
10
10
|
exports.extractCRC16 = extractCRC16;
|
|
11
|
+
exports.validateMnemonicPayload = validateMnemonicPayload;
|
|
12
|
+
const constants_1 = require("./constants");
|
|
11
13
|
// ---------------------------------------------------------------------------
|
|
12
14
|
// Hex ↔ bytes
|
|
13
15
|
// ---------------------------------------------------------------------------
|
|
@@ -61,3 +63,54 @@ function extractCRC16(data) {
|
|
|
61
63
|
}
|
|
62
64
|
return data[data.length - 2] | (data[data.length - 1] << 8);
|
|
63
65
|
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Mnemonic payload validation
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
/**
|
|
70
|
+
* Validate whether raw card data contains a valid mnemonic payload.
|
|
71
|
+
* Checks type byte, length, and CRC16 — does NOT decode the mnemonic.
|
|
72
|
+
*
|
|
73
|
+
* @throws INVALID_CARD_DATA / EMPTY_CARD / CRC16_CHECK_FAILED
|
|
74
|
+
* @returns The mnemonic type description string.
|
|
75
|
+
*/
|
|
76
|
+
function validateMnemonicPayload(data) {
|
|
77
|
+
if (data.length < 19)
|
|
78
|
+
throw new Error('INVALID_CARD_DATA');
|
|
79
|
+
if (data.every(b => b === 0))
|
|
80
|
+
throw new Error('EMPTY_CARD');
|
|
81
|
+
const typeId = data[0];
|
|
82
|
+
let entropyLength;
|
|
83
|
+
let typeStr;
|
|
84
|
+
switch (typeId) {
|
|
85
|
+
case constants_1.MNEMONIC_TYPE_12:
|
|
86
|
+
entropyLength = 16;
|
|
87
|
+
typeStr = '12 words (128-bit)';
|
|
88
|
+
break;
|
|
89
|
+
case constants_1.MNEMONIC_TYPE_15:
|
|
90
|
+
entropyLength = 20;
|
|
91
|
+
typeStr = '15 words (160-bit)';
|
|
92
|
+
break;
|
|
93
|
+
case constants_1.MNEMONIC_TYPE_18:
|
|
94
|
+
entropyLength = 24;
|
|
95
|
+
typeStr = '18 words (192-bit)';
|
|
96
|
+
break;
|
|
97
|
+
case constants_1.MNEMONIC_TYPE_21:
|
|
98
|
+
entropyLength = 28;
|
|
99
|
+
typeStr = '21 words (224-bit)';
|
|
100
|
+
break;
|
|
101
|
+
case constants_1.MNEMONIC_TYPE_24:
|
|
102
|
+
entropyLength = 32;
|
|
103
|
+
typeStr = '24 words (256-bit)';
|
|
104
|
+
break;
|
|
105
|
+
default: throw new Error('INVALID_CARD_DATA');
|
|
106
|
+
}
|
|
107
|
+
const expectedTotal = 1 + entropyLength + 2;
|
|
108
|
+
if (data.length < expectedTotal)
|
|
109
|
+
throw new Error('INVALID_CARD_DATA');
|
|
110
|
+
const dataBlock = data.slice(0, 1 + entropyLength);
|
|
111
|
+
const storedCRC = extractCRC16(data.slice(1 + entropyLength, expectedTotal));
|
|
112
|
+
const calcCRC = calculateCRC16(dataBlock);
|
|
113
|
+
if (storedCRC !== calcCRC)
|
|
114
|
+
throw new Error('CRC16_CHECK_FAILED');
|
|
115
|
+
return { type: typeStr };
|
|
116
|
+
}
|
package/dist/writer.d.ts
CHANGED
|
@@ -19,12 +19,19 @@ export { ResultCode, type NfcResult, isNfcOperationLocked, releaseNfcOperationLo
|
|
|
19
19
|
export declare function initializeCard(mnemonic: string, password: string, onCardIdentified?: () => void): Promise<NfcResult>;
|
|
20
20
|
/**
|
|
21
21
|
* Update card: authenticate with old password, then write new mnemonic + new password.
|
|
22
|
+
*
|
|
23
|
+
* When `options.precheckExistingMnemonic` is true, the card is read after authentication.
|
|
24
|
+
* If a valid mnemonic backup already exists, the write is skipped and PRECHECK_HAS_BACKUP
|
|
25
|
+
* is returned (`success: true` — operation completed; distinguish outcome by `code`).
|
|
26
|
+
* Otherwise the normal write flow proceeds.
|
|
22
27
|
*/
|
|
23
|
-
export declare function updateCard(oldPassword: string, newPassword: string, newMnemonic: string, onCardIdentified?: () => void
|
|
28
|
+
export declare function updateCard(oldPassword: string, newPassword: string, newMnemonic: string, onCardIdentified?: () => void, options?: {
|
|
29
|
+
precheckExistingMnemonic?: boolean;
|
|
30
|
+
}): Promise<NfcResult>;
|
|
24
31
|
/** Change password only (old password required). */
|
|
25
32
|
export declare function updatePassword(oldPassword: string, newPassword: string, onCardIdentified?: () => void): Promise<NfcResult>;
|
|
26
33
|
/** Write a user nickname (password required for authentication). */
|
|
27
|
-
export declare function writeUserNickname(password: string, nickname: string): Promise<NfcResult>;
|
|
34
|
+
export declare function writeUserNickname(password: string, nickname: string, onCardIdentified?: () => void): Promise<NfcResult>;
|
|
28
35
|
/**
|
|
29
36
|
* Reset card: wipe user data, set password to "000000".
|
|
30
37
|
* @param password – current card password (required if protection is enabled).
|
package/dist/writer.js
CHANGED
|
@@ -101,6 +101,13 @@ async function writeUserMemory(data) {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
/** FAST_READ pages 0x08–0x27 (user memory). */
|
|
105
|
+
async function readUserMemory() {
|
|
106
|
+
const response = await (0, nfc_core_1.transceive)([constants_1.CMD_FAST_READ, constants_1.USER_PAGE_START, constants_1.USER_PAGE_END]);
|
|
107
|
+
if (!response || response.length < constants_1.USER_MEMORY_SIZE)
|
|
108
|
+
throw new Error('READ_FAILED');
|
|
109
|
+
return new Uint8Array(response.slice(0, constants_1.USER_MEMORY_SIZE));
|
|
110
|
+
}
|
|
104
111
|
/**
|
|
105
112
|
* Write a 16-byte AES key to AES_KEY0 (pages 0x30–0x33).
|
|
106
113
|
* Byte order is reversed per the datasheet.
|
|
@@ -273,8 +280,13 @@ async function initializeCard(mnemonic, password, onCardIdentified) {
|
|
|
273
280
|
}
|
|
274
281
|
/**
|
|
275
282
|
* Update card: authenticate with old password, then write new mnemonic + new password.
|
|
283
|
+
*
|
|
284
|
+
* When `options.precheckExistingMnemonic` is true, the card is read after authentication.
|
|
285
|
+
* If a valid mnemonic backup already exists, the write is skipped and PRECHECK_HAS_BACKUP
|
|
286
|
+
* is returned (`success: true` — operation completed; distinguish outcome by `code`).
|
|
287
|
+
* Otherwise the normal write flow proceeds.
|
|
276
288
|
*/
|
|
277
|
-
async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified) {
|
|
289
|
+
async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified, options) {
|
|
278
290
|
try {
|
|
279
291
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
280
292
|
}
|
|
@@ -297,7 +309,14 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
|
|
|
297
309
|
if (typeof n === 'number')
|
|
298
310
|
retryCountAfterPreDecrement = n;
|
|
299
311
|
}
|
|
300
|
-
catch {
|
|
312
|
+
catch (error) {
|
|
313
|
+
const code = (0, types_1.errorToCode)(error);
|
|
314
|
+
if (code === types_1.ResultCode.RETRY_COUNT_EXHAUSTED) {
|
|
315
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
316
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
317
|
+
return (0, types_1.nfcResultRetryCountExhausted)();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
301
320
|
const oldKey = (0, crypto_1.passwordToAesKey)(oldPassword);
|
|
302
321
|
await (0, nfc_core_1.authenticate)(oldKey);
|
|
303
322
|
try {
|
|
@@ -305,6 +324,32 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
|
|
|
305
324
|
}
|
|
306
325
|
catch { /* non-fatal */ }
|
|
307
326
|
onCardIdentified?.();
|
|
327
|
+
if (options?.precheckExistingMnemonic) {
|
|
328
|
+
try {
|
|
329
|
+
const data = await readUserMemory();
|
|
330
|
+
const decoded = (0, utils_1.validateMnemonicPayload)(data);
|
|
331
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
332
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
333
|
+
return {
|
|
334
|
+
code: types_1.ResultCode.PRECHECK_HAS_BACKUP,
|
|
335
|
+
success: true,
|
|
336
|
+
data: { type: decoded.type, retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
const c = (0, types_1.errorToCode)(e);
|
|
341
|
+
if (c === types_1.ResultCode.CHECK_EMPTY ||
|
|
342
|
+
c === types_1.ResultCode.CRC16_CHECK_FAILED ||
|
|
343
|
+
c === types_1.ResultCode.INVALID_CARD_DATA) {
|
|
344
|
+
// No valid mnemonic on card — fall through to write
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
348
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
349
|
+
return { code: c, success: false };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
308
353
|
await disableAuth();
|
|
309
354
|
await writeUserMemory(entropyResult.data);
|
|
310
355
|
const newKey = (0, crypto_1.passwordToAesKey)(newPassword);
|
|
@@ -366,7 +411,14 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
|
|
|
366
411
|
if (typeof n === 'number')
|
|
367
412
|
retryCountAfterPreDecrement = n;
|
|
368
413
|
}
|
|
369
|
-
catch {
|
|
414
|
+
catch (error) {
|
|
415
|
+
const code = (0, types_1.errorToCode)(error);
|
|
416
|
+
if (code === types_1.ResultCode.RETRY_COUNT_EXHAUSTED) {
|
|
417
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
418
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
419
|
+
return (0, types_1.nfcResultRetryCountExhausted)();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
370
422
|
const oldKey = (0, crypto_1.passwordToAesKey)(oldPassword);
|
|
371
423
|
await (0, nfc_core_1.authenticate)(oldKey);
|
|
372
424
|
try {
|
|
@@ -404,7 +456,7 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
|
|
|
404
456
|
}
|
|
405
457
|
}
|
|
406
458
|
/** Write a user nickname (password required for authentication). */
|
|
407
|
-
async function writeUserNickname(password, nickname) {
|
|
459
|
+
async function writeUserNickname(password, nickname, onCardIdentified) {
|
|
408
460
|
try {
|
|
409
461
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
410
462
|
}
|
|
@@ -422,6 +474,7 @@ async function writeUserNickname(password, nickname) {
|
|
|
422
474
|
try {
|
|
423
475
|
const aesKey = (0, crypto_1.passwordToAesKey)(password);
|
|
424
476
|
await (0, nfc_core_1.authenticate)(aesKey);
|
|
477
|
+
onCardIdentified?.();
|
|
425
478
|
await disableAuth();
|
|
426
479
|
await writeNicknameToCard(nickname);
|
|
427
480
|
await configureAuth();
|
|
@@ -471,7 +524,14 @@ async function resetCard(password, onCardIdentified) {
|
|
|
471
524
|
if (typeof n === 'number')
|
|
472
525
|
retryCountAfterPreDecrement = n;
|
|
473
526
|
}
|
|
474
|
-
catch {
|
|
527
|
+
catch (error) {
|
|
528
|
+
const code = (0, types_1.errorToCode)(error);
|
|
529
|
+
if (code === types_1.ResultCode.RETRY_COUNT_EXHAUSTED) {
|
|
530
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
531
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
532
|
+
return (0, types_1.nfcResultRetryCountExhausted)();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
475
535
|
const aesKey = (0, crypto_1.passwordToAesKey)(password);
|
|
476
536
|
try {
|
|
477
537
|
await (0, nfc_core_1.authenticate)(aesKey);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ukeyfe/react-native-nfc-litecard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "NFC read/write for MIFARE Ultralight AES (LiteCard mnemonic storage)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
|
-
"README.md"
|
|
30
|
+
"README.md",
|
|
31
|
+
"README.en.md"
|
|
31
32
|
]
|
|
32
33
|
}
|