@ukeyfe/react-native-nfc-litecard 1.0.2 → 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 +71 -34
- package/README.zh.md +275 -238
- package/dist/constants.d.ts +26 -0
- package/dist/constants.js +33 -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 +9 -4
- package/dist/writer.js +95 -82
- package/package.json +1 -1
package/README.zh.md
CHANGED
|
@@ -2,44 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
[English](./README.md) | 中文
|
|
4
4
|
|
|
5
|
-
React Native NFC
|
|
5
|
+
React Native NFC 读写库,适用于 **MIFARE Ultralight AES**(MF0AES(H)20),专为 LiteCard 助记词存储设计。
|
|
6
6
|
|
|
7
|
-
>
|
|
7
|
+
> **设计原则**:库只返回状态码(`code`)和数据(`data`),**不**提供面向用户的提示文案。调用方应根据 `NfcStatusCode` 自行映射本地化字符串。
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 功能概览
|
|
10
10
|
|
|
11
|
-
|
|
|
12
|
-
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
11
|
+
| 功能 | 方法 | 说明 |
|
|
12
|
+
|------|------|------|
|
|
13
|
+
| 检测卡片 | `checkCard()` | 检测卡片是否为空或已有数据 |
|
|
14
|
+
| 读取助记词 | `readMnemonic()` | 读取 BIP-39 助记词(需密码) |
|
|
15
|
+
| 读取昵称 | `readUserNickname()` | 读取卡片上的用户昵称 |
|
|
16
|
+
| 读取重试次数 | `readMnemonicRetryCount()` | 读取 PIN 重试计数器 |
|
|
17
|
+
| 重置重试次数 | `resetRetryCountTo10()` | 将 PIN 重试计数器重置为默认值(10) |
|
|
18
|
+
| 初始化卡片 | `initializeCard()` | 用出厂密码认证,写入助记词 + 设置新密码 |
|
|
19
|
+
| 更新卡片 | `updateCard()` | 更新助记词和密码(需旧密码) |
|
|
20
|
+
| 修改密码 | `updatePassword()` | 仅修改密码(需旧密码) |
|
|
21
|
+
| 写入昵称 | `writeUserNickname()` | 写入用户昵称 |
|
|
22
|
+
| 重置卡片 | `resetCard()` | 清除助记词数据,设置新密码,保持保护开启 |
|
|
23
|
+
| 卡片版本 | `getCardVersion()` | 读取卡片产品版本信息(无需认证) |
|
|
24
|
+
| 真伪校验 | `readOriginality()` | 读取 ECC 原厂签名,验证 NXP 正品 |
|
|
23
25
|
|
|
24
|
-
##
|
|
26
|
+
## 安装
|
|
25
27
|
|
|
26
28
|
```bash
|
|
27
29
|
npm install @ukeyfe/react-native-nfc-litecard
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
###
|
|
32
|
+
### 对等依赖
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
本库需要以下对等依赖:
|
|
33
35
|
|
|
34
36
|
```bash
|
|
35
37
|
npm install react-native react-native-nfc-manager
|
|
36
38
|
```
|
|
37
39
|
|
|
38
|
-
##
|
|
40
|
+
## 快速开始
|
|
39
41
|
|
|
40
42
|
```typescript
|
|
41
43
|
import {
|
|
42
|
-
|
|
44
|
+
NfcStatusCode,
|
|
43
45
|
checkCard,
|
|
44
46
|
readMnemonic,
|
|
45
47
|
initializeCard,
|
|
@@ -50,261 +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 文档
|
|
57
61
|
|
|
58
62
|
### `checkCard(password?, onCardIdentified?)`
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
检测卡片状态(空卡 / 有数据)。
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
**无密码(快速探测):**
|
|
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
|
+
// 空卡 – 可以初始化
|
|
71
|
+
} else if (result.code === NfcStatusCode.CHECK_HAS_DATA) {
|
|
72
|
+
// 有数据(或已读保护,无法确定)
|
|
69
73
|
}
|
|
70
74
|
```
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
**有密码(认证后深度检测,适用于读保护卡):**
|
|
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
|
+
// 空卡 – 可以写入
|
|
81
|
+
} else if (result.code === NfcStatusCode.CHECK_HAS_DATA) {
|
|
82
|
+
// 已有合法助记词备份,类型:result.data?.type
|
|
83
|
+
} else if (result.code === NfcStatusCode.AUTH_WRONG_PASSWORD) {
|
|
84
|
+
// 密码错误
|
|
81
85
|
}
|
|
82
86
|
```
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
|
86
|
-
|
|
87
|
-
| `password` | `string` |
|
|
88
|
-
| `onCardIdentified` | `() => void` |
|
|
88
|
+
**参数:**
|
|
89
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
90
|
+
|------|------|------|------|
|
|
91
|
+
| `password` | `string` | 否 | 卡片保护密码。不传时直接读取(适合未加密卡片);传入时先进行 AES 认证,再读取全量数据并 CRC16 校验(适合读保护卡,结果更准确)。 |
|
|
92
|
+
| `onCardIdentified` | `() => void` | 否 | NFC 会话建立后回调,可用于 UI 提示如"已检测到卡片"。 |
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
|
92
|
-
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
94
|
+
**返回状态码:**
|
|
95
|
+
| 状态码 | 含义 |
|
|
96
|
+
|--------|------|
|
|
97
|
+
| `NfcStatusCode.CHECK_EMPTY` (10104) | 空卡 – 无助记词数据 |
|
|
98
|
+
| `NfcStatusCode.CHECK_HAS_DATA` (10105) | 卡片有数据。传入密码时,`data.type` 包含助记词类型(如 `"12 words (128-bit)"`)。 |
|
|
99
|
+
| `NfcStatusCode.AUTH_WRONG_PASSWORD` (40002) | 密码错误(仅在传入密码时可能出现) |
|
|
100
|
+
| `NfcStatusCode.NFC_CONNECT_FAILED` (40001) | NFC 连接失败 – 未贴卡或设备不支持 |
|
|
97
101
|
|
|
98
102
|
---
|
|
99
103
|
|
|
100
104
|
### `readMnemonic(password, onCardIdentified?)`
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
读取助记词(需密码认证)。认证前自动递减重试计数器,认证成功后重置为 10。
|
|
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('助记词:', result.data?.mnemonic);
|
|
112
|
+
console.log('类型:', result.data?.type); // "12 words (128-bit)"
|
|
113
|
+
console.log('昵称:', result.data?.nickname);
|
|
114
|
+
console.log('重试次数:', 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
|
+
**参数:**
|
|
119
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
120
|
+
|------|------|------|------|
|
|
121
|
+
| `password` | `string` | 是 | 卡片保护密码,用于 AES-128 认证 |
|
|
122
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功、开始读取前回调,可用于 UI 提示如"正在读取"。 |
|
|
123
|
+
|
|
124
|
+
**返回 `data` 字段:**
|
|
125
|
+
| 字段 | 类型 | 说明 |
|
|
126
|
+
|------|------|------|
|
|
127
|
+
| `mnemonic` | `string` | BIP-39 助记词(如 `"abandon abandon ... about"`) |
|
|
128
|
+
| `type` | `string` | 助记词类型(如 `"12 words (128-bit)"`、`"24 words (256-bit)"`) |
|
|
129
|
+
| `entropyHex` | `string` | 熵的十六进制字符串 |
|
|
130
|
+
| `rawBytes` | `string` | 卡上原始数据的十六进制字符串(类型 + 熵) |
|
|
131
|
+
| `nickname` | `string` | 用户昵称(如卡上已设置) |
|
|
132
|
+
| `retryCount` | `number` | 认证成功后的重试次数(正常为 10) |
|
|
129
133
|
|
|
130
134
|
---
|
|
131
135
|
|
|
132
|
-
### `initializeCard(mnemonic,
|
|
136
|
+
### `initializeCard(mnemonic, newPassword, defaultPassword, onCardIdentified?)`
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
初始化卡片:用出厂默认密码认证,写入助记词,设置新密码。卡片必须已启用 AES 保护(出厂默认)。
|
|
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' // 出厂默认密码
|
|
140
145
|
);
|
|
141
|
-
if (result.code ===
|
|
142
|
-
console.log('
|
|
146
|
+
if (result.code === NfcStatusCode.INIT_SUCCESS) {
|
|
147
|
+
console.log('初始化成功');
|
|
143
148
|
}
|
|
144
149
|
```
|
|
145
150
|
|
|
146
|
-
|
|
147
|
-
|
|
|
148
|
-
|
|
149
|
-
| `mnemonic` | `string` |
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
151
|
+
**参数:**
|
|
152
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
153
|
+
|------|------|------|------|
|
|
154
|
+
| `mnemonic` | `string` | 是 | BIP-39 助记词,支持 12/15/18/21/24 个词。库会转为熵 + CRC16 写入卡片。 |
|
|
155
|
+
| `newPassword` | `string` | 是 | 新保护密码,用于派生 AES-128 密钥写入卡片。 |
|
|
156
|
+
| `defaultPassword` | `string` | 是 | 卡片当前(出厂默认)密码,用于认证。 |
|
|
157
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功、开始写入前回调,可用于 UI 提示如"正在写入"。 |
|
|
152
158
|
|
|
153
159
|
---
|
|
154
160
|
|
|
155
161
|
### `updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified?, options?)`
|
|
156
162
|
|
|
157
|
-
|
|
163
|
+
更新卡片:使用旧密码认证后,写入新助记词和新密码。认证前自动递减重试计数器。
|
|
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('更新成功');
|
|
163
169
|
}
|
|
164
170
|
```
|
|
165
171
|
|
|
166
|
-
|
|
172
|
+
**预检查已有备份:**
|
|
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
|
-
console.log('
|
|
175
|
-
} else if (result.code ===
|
|
176
|
-
console.log('
|
|
178
|
+
if (result.code === NfcStatusCode.PRECHECK_HAS_BACKUP) {
|
|
179
|
+
// 卡片已有合法助记词备份,跳过写入
|
|
180
|
+
console.log('已有备份,类型:', result.data?.type);
|
|
181
|
+
} else if (result.code === NfcStatusCode.WRITE_SUCCESS) {
|
|
182
|
+
console.log('写入成功');
|
|
177
183
|
}
|
|
178
184
|
```
|
|
179
185
|
|
|
180
|
-
|
|
181
|
-
|
|
|
182
|
-
|
|
183
|
-
| `oldPassword` | `string` |
|
|
184
|
-
| `newPassword` | `string` |
|
|
185
|
-
| `newMnemonic` | `string` |
|
|
186
|
-
| `onCardIdentified` | `() => void` |
|
|
187
|
-
| `options.precheckExistingMnemonic` | `boolean` |
|
|
186
|
+
**参数:**
|
|
187
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
188
|
+
|------|------|------|------|
|
|
189
|
+
| `oldPassword` | `string` | 是 | 当前卡片密码,用于 AES 认证 |
|
|
190
|
+
| `newPassword` | `string` | 是 | 新密码,写入后卡片使用此密码保护 |
|
|
191
|
+
| `newMnemonic` | `string` | 是 | 新 BIP-39 助记词(12/15/18/21/24 个词) |
|
|
192
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后回调,可用于 UI 提示如"正在写入"。 |
|
|
193
|
+
| `options.precheckExistingMnemonic` | `boolean` | 否 | 为 `true` 时,认证后先读取卡片数据并校验 CRC16,若已有合法助记词则返回 `PRECHECK_HAS_BACKUP`,不执行写入。 |
|
|
188
194
|
|
|
189
195
|
---
|
|
190
196
|
|
|
191
197
|
### `updatePassword(oldPassword, newPassword, onCardIdentified?)`
|
|
192
198
|
|
|
193
|
-
|
|
199
|
+
仅修改密码,卡片上的助记词数据不变。认证前自动递减重试计数器。
|
|
194
200
|
|
|
195
201
|
```typescript
|
|
196
202
|
const result = await updatePassword('old-password', 'new-password');
|
|
197
|
-
if (result.code ===
|
|
198
|
-
console.log('
|
|
203
|
+
if (result.code === NfcStatusCode.UPDATE_PASSWORD_SUCCESS) {
|
|
204
|
+
console.log('密码修改成功');
|
|
199
205
|
}
|
|
200
206
|
```
|
|
201
207
|
|
|
202
|
-
|
|
203
|
-
|
|
|
204
|
-
|
|
205
|
-
| `oldPassword` | `string` |
|
|
206
|
-
| `newPassword` | `string` |
|
|
207
|
-
| `onCardIdentified` | `() => void` |
|
|
208
|
+
**参数:**
|
|
209
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
210
|
+
|------|------|------|------|
|
|
211
|
+
| `oldPassword` | `string` | 是 | 当前卡片密码,用于 AES 认证 |
|
|
212
|
+
| `newPassword` | `string` | 是 | 新密码,修改后卡片使用此密码 |
|
|
213
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后回调 |
|
|
208
214
|
|
|
209
215
|
---
|
|
210
216
|
|
|
211
217
|
### `writeUserNickname(password, nickname, onCardIdentified?)`
|
|
212
218
|
|
|
213
|
-
|
|
219
|
+
写入用户昵称。昵称以 UTF-8 编码,最大 12 字节(超出部分截断)。
|
|
214
220
|
|
|
215
221
|
```typescript
|
|
216
222
|
const result = await writeUserNickname('your-password', 'MyCard');
|
|
217
|
-
if (result.code ===
|
|
218
|
-
console.log('
|
|
223
|
+
if (result.code === NfcStatusCode.WRITE_NICKNAME_SUCCESS) {
|
|
224
|
+
console.log('昵称写入成功');
|
|
219
225
|
}
|
|
220
226
|
```
|
|
221
227
|
|
|
222
|
-
|
|
223
|
-
|
|
|
224
|
-
|
|
225
|
-
| `password` | `string` |
|
|
226
|
-
| `nickname` | `string` |
|
|
227
|
-
| `onCardIdentified` | `() => void` |
|
|
228
|
+
**参数:**
|
|
229
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
230
|
+
|------|------|------|------|
|
|
231
|
+
| `password` | `string` | 是 | 卡片保护密码,用于 AES 认证 |
|
|
232
|
+
| `nickname` | `string` | 是 | 用户昵称;UTF-8 最大 12 字节(约 4 个中文字符或 12 个英文字母) |
|
|
233
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后回调 |
|
|
228
234
|
|
|
229
235
|
---
|
|
230
236
|
|
|
231
237
|
### `readUserNickname(password?, onCardIdentified?)`
|
|
232
238
|
|
|
233
|
-
|
|
239
|
+
读取卡片上的用户昵称。
|
|
234
240
|
|
|
235
241
|
```typescript
|
|
236
242
|
const result = await readUserNickname('your-password');
|
|
237
243
|
if (result.success) {
|
|
238
|
-
console.log('
|
|
244
|
+
console.log('昵称:', result.data?.nickname);
|
|
239
245
|
}
|
|
240
246
|
```
|
|
241
247
|
|
|
242
|
-
|
|
243
|
-
|
|
|
244
|
-
|
|
245
|
-
| `password` | `string` |
|
|
246
|
-
| `onCardIdentified` | `() => void` |
|
|
248
|
+
**参数:**
|
|
249
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
250
|
+
|------|------|------|------|
|
|
251
|
+
| `password` | `string` | 否 | 卡片密码。读保护启用时(`PROT=1`)必填,否则可选。 |
|
|
252
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后回调 |
|
|
247
253
|
|
|
248
254
|
---
|
|
249
255
|
|
|
250
256
|
### `resetCard(password, newPassword, onCardIdentified?)`
|
|
251
257
|
|
|
252
|
-
|
|
258
|
+
重置卡片:清除助记词数据,设置新密码,关闭读写保护。昵称保留不变。
|
|
253
259
|
|
|
254
260
|
```typescript
|
|
255
261
|
const result = await resetCard('old-password', 'new-password');
|
|
256
|
-
if (result.code ===
|
|
257
|
-
console.log('
|
|
262
|
+
if (result.code === NfcStatusCode.RESET_SUCCESS) {
|
|
263
|
+
console.log('重置成功');
|
|
258
264
|
}
|
|
259
265
|
```
|
|
260
266
|
|
|
261
|
-
> ⚠️
|
|
267
|
+
> ⚠️ 此操作不可逆,卡片上的助记词数据将被永久擦除。
|
|
262
268
|
|
|
263
|
-
|
|
264
|
-
|
|
|
265
|
-
|
|
266
|
-
| `password` | `string \| undefined` |
|
|
267
|
-
| `newPassword` | `string` |
|
|
268
|
-
| `onCardIdentified` | `() => void` |
|
|
269
|
+
**参数:**
|
|
270
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
271
|
+
|------|------|------|------|
|
|
272
|
+
| `password` | `string \| undefined` | 否 | 当前卡片密码。卡片已保护时必填,否则传 `undefined`。认证前自动递减重试计数器。 |
|
|
273
|
+
| `newPassword` | `string` | 是 | 重置后要设置的密码。 |
|
|
274
|
+
| `onCardIdentified` | `() => void` | 否 | 认证成功后回调 |
|
|
269
275
|
|
|
270
276
|
---
|
|
271
277
|
|
|
272
278
|
### `readMnemonicRetryCount(onCardIdentified?)`
|
|
273
279
|
|
|
274
|
-
|
|
280
|
+
读取卡片上的 PIN 重试计数器。无需密码认证(计数器页面在保护区域之外)。
|
|
275
281
|
|
|
276
282
|
```typescript
|
|
277
283
|
const result = await readMnemonicRetryCount();
|
|
278
284
|
if (result.success) {
|
|
279
|
-
console.log('
|
|
285
|
+
console.log('剩余重试次数:', result.data?.retryCount);
|
|
280
286
|
}
|
|
281
287
|
```
|
|
282
288
|
|
|
283
|
-
|
|
284
|
-
|
|
|
285
|
-
|
|
286
|
-
| `onCardIdentified` | `() => void` |
|
|
289
|
+
**参数:**
|
|
290
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
291
|
+
|------|------|------|------|
|
|
292
|
+
| `onCardIdentified` | `() => void` | 否 | NFC 会话建立后回调 |
|
|
287
293
|
|
|
288
294
|
---
|
|
289
295
|
|
|
290
296
|
### `resetRetryCountTo10(onCardIdentified?)`
|
|
291
297
|
|
|
292
|
-
|
|
298
|
+
将 PIN 重试计数器重置为默认值(10)。无需密码认证。
|
|
293
299
|
|
|
294
300
|
```typescript
|
|
295
301
|
const result = await resetRetryCountTo10();
|
|
296
302
|
```
|
|
297
303
|
|
|
298
|
-
|
|
299
|
-
|
|
|
300
|
-
|
|
301
|
-
| `onCardIdentified` | `() => void` |
|
|
304
|
+
**参数:**
|
|
305
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
306
|
+
|------|------|------|------|
|
|
307
|
+
| `onCardIdentified` | `() => void` | 否 | NFC 会话建立后回调 |
|
|
302
308
|
|
|
303
309
|
---
|
|
304
310
|
|
|
305
|
-
###
|
|
311
|
+
### `getCardVersion(onCardIdentified?)`
|
|
306
312
|
|
|
307
|
-
|
|
313
|
+
读取卡片产品版本信息。无需密码认证。
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const result = await getCardVersion();
|
|
317
|
+
if (result.success) {
|
|
318
|
+
console.log('厂商:', result.data?.version?.vendorId); // 0x04 = NXP
|
|
319
|
+
console.log('类型:', result.data?.version?.productType); // 0x03 = Ultralight
|
|
320
|
+
console.log('版本:', result.data?.version?.majorVersion); // 0x04 = AES
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### `readOriginality(onCardIdentified?)`
|
|
327
|
+
|
|
328
|
+
读取 ECC 原厂签名,验证卡片是否为 NXP 正品。无需密码认证。
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
const result = await readOriginality();
|
|
332
|
+
if (result.success) {
|
|
333
|
+
console.log('签名:', result.data?.signature); // 96 字符 hex 字符串
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
### NFC 锁管理
|
|
340
|
+
|
|
341
|
+
用于应用层 NFC 会话生命周期管理:
|
|
308
342
|
|
|
309
343
|
```typescript
|
|
310
344
|
import {
|
|
@@ -315,22 +349,22 @@ import {
|
|
|
315
349
|
} from '@ukeyfe/react-native-nfc-litecard';
|
|
316
350
|
```
|
|
317
351
|
|
|
318
|
-
|
|
|
319
|
-
|
|
320
|
-
| `isNfcOperationLocked()` |
|
|
321
|
-
| `releaseNfcOperationLock()` |
|
|
322
|
-
| `markNfcOperationCancelledByCleanup()` |
|
|
323
|
-
| `consumeNfcOperationCancelledByCleanup()` |
|
|
352
|
+
| 方法 | 说明 |
|
|
353
|
+
|------|------|
|
|
354
|
+
| `isNfcOperationLocked()` | 检查 NFC 操作锁是否被持有 |
|
|
355
|
+
| `releaseNfcOperationLock()` | 强制释放锁(用于页面卸载时) |
|
|
356
|
+
| `markNfcOperationCancelledByCleanup()` | 标记当前操作被清理中断 |
|
|
357
|
+
| `consumeNfcOperationCancelledByCleanup()` | 消费清理标志(返回是否被设置过) |
|
|
324
358
|
|
|
325
|
-
## NfcResult
|
|
359
|
+
## NfcResult 结构
|
|
326
360
|
|
|
327
|
-
|
|
361
|
+
所有 API 返回统一的 `NfcResult`:
|
|
328
362
|
|
|
329
363
|
```typescript
|
|
330
364
|
interface NfcResult {
|
|
331
|
-
code: number; //
|
|
332
|
-
success: boolean; //
|
|
333
|
-
data?: { //
|
|
365
|
+
code: number; // 状态码 – 与 NfcStatusCode 比较
|
|
366
|
+
success: boolean; // 操作是否成功
|
|
367
|
+
data?: { // 可选数据,仅部分操作返回
|
|
334
368
|
mnemonic?: string;
|
|
335
369
|
type?: string;
|
|
336
370
|
entropyHex?: string;
|
|
@@ -343,120 +377,123 @@ interface NfcResult {
|
|
|
343
377
|
}
|
|
344
378
|
```
|
|
345
379
|
|
|
346
|
-
##
|
|
380
|
+
## 错误处理示例
|
|
347
381
|
|
|
348
382
|
```typescript
|
|
349
|
-
import {
|
|
383
|
+
import { NfcStatusCode, readMnemonic } from '@ukeyfe/react-native-nfc-litecard';
|
|
350
384
|
|
|
351
385
|
const result = await readMnemonic('password');
|
|
352
386
|
|
|
353
387
|
switch (result.code) {
|
|
354
|
-
case
|
|
355
|
-
console.log('
|
|
388
|
+
case NfcStatusCode.READ_SUCCESS:
|
|
389
|
+
console.log('读取成功:', result.data?.mnemonic);
|
|
356
390
|
break;
|
|
357
|
-
case
|
|
358
|
-
alert('
|
|
391
|
+
case NfcStatusCode.AUTH_WRONG_PASSWORD:
|
|
392
|
+
alert('密码错误');
|
|
359
393
|
break;
|
|
360
|
-
case
|
|
361
|
-
alert('NFC
|
|
394
|
+
case NfcStatusCode.NFC_CONNECT_FAILED:
|
|
395
|
+
alert('NFC 连接失败,请重新贴卡');
|
|
362
396
|
break;
|
|
363
|
-
case
|
|
364
|
-
// iOS
|
|
397
|
+
case NfcStatusCode.NFC_USER_CANCELED:
|
|
398
|
+
// iOS 用户取消 – 静默处理
|
|
365
399
|
break;
|
|
366
|
-
case
|
|
367
|
-
alert('
|
|
400
|
+
case NfcStatusCode.READ_TIMEOUT:
|
|
401
|
+
alert('读取超时 – 请移开卡片后重新贴卡');
|
|
368
402
|
break;
|
|
369
|
-
case
|
|
370
|
-
alert('
|
|
403
|
+
case NfcStatusCode.RETRY_COUNT_EXHAUSTED:
|
|
404
|
+
alert('重试次数已耗尽 – 卡片已锁定');
|
|
371
405
|
break;
|
|
372
406
|
default:
|
|
373
|
-
alert('
|
|
407
|
+
alert('操作失败');
|
|
374
408
|
}
|
|
375
409
|
```
|
|
376
410
|
|
|
377
|
-
##
|
|
378
|
-
|
|
379
|
-
###
|
|
380
|
-
|
|
381
|
-
|
|
|
382
|
-
|
|
383
|
-
| `READ_SUCCESS` | 10102 |
|
|
384
|
-
| `READ_NICKNAME_SUCCESS` | 10103 |
|
|
385
|
-
| `CHECK_EMPTY` | 10104 |
|
|
386
|
-
| `CHECK_HAS_DATA` | 10105 |
|
|
387
|
-
| `READ_RETRY_COUNT_SUCCESS` | 10106 |
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
411
|
+
## 状态码
|
|
412
|
+
|
|
413
|
+
### 成功码
|
|
414
|
+
|
|
415
|
+
| 常量 | 值 | 说明 |
|
|
416
|
+
|------|------|------|
|
|
417
|
+
| `READ_SUCCESS` | 10102 | 助记词读取成功 |
|
|
418
|
+
| `READ_NICKNAME_SUCCESS` | 10103 | 昵称读取成功 |
|
|
419
|
+
| `CHECK_EMPTY` | 10104 | 空卡 |
|
|
420
|
+
| `CHECK_HAS_DATA` | 10105 | 卡片有数据 |
|
|
421
|
+
| `READ_RETRY_COUNT_SUCCESS` | 10106 | 重试次数读取成功 |
|
|
422
|
+
| `GET_VERSION_SUCCESS` | 10107 | 卡片版本读取成功 |
|
|
423
|
+
| `READ_SIG_SUCCESS` | 10108 | 原厂签名读取成功 |
|
|
424
|
+
| `INIT_SUCCESS` | 10201 | 初始化成功 |
|
|
425
|
+
| `WRITE_SUCCESS` | 10203 | 写入/更新成功 |
|
|
426
|
+
| `UPDATE_PASSWORD_SUCCESS` | 10204 | 密码修改成功 |
|
|
427
|
+
| `WRITE_NICKNAME_SUCCESS` | 10205 | 昵称写入成功 |
|
|
428
|
+
| `RESET_SUCCESS` | 10206 | 卡片重置成功 |
|
|
429
|
+
| `PRECHECK_HAS_BACKUP` | 10207 | 卡片已有合法备份,跳过写入 |
|
|
430
|
+
|
|
431
|
+
### 错误码
|
|
432
|
+
|
|
433
|
+
| 常量 | 值 | 说明 |
|
|
434
|
+
|------|------|------|
|
|
435
|
+
| `NFC_CONNECT_FAILED` | 40001 | NFC 连接失败 |
|
|
436
|
+
| `AUTH_WRONG_PASSWORD` | 40002 | 密码错误 |
|
|
437
|
+
| `AUTH_INVALID_RESPONSE` | 40003 | 认证响应无效 |
|
|
438
|
+
| `AUTH_VERIFY_FAILED` | 40004 | 认证校验失败 |
|
|
439
|
+
| `READ_FAILED` | 40005 | 读取失败 |
|
|
440
|
+
| `WRITE_FAILED` | 40006 | 写入失败 |
|
|
441
|
+
| `INVALID_MNEMONIC` | 40007 | 助记词无效 |
|
|
442
|
+
| `UNSUPPORTED_MNEMONIC_LENGTH` | 40008 | 不支持的助记词长度 |
|
|
443
|
+
| `INVALID_CARD_DATA` | 40009 | 卡片数据无效 |
|
|
444
|
+
| `UNKNOWN_ERROR` | 40010 | 未知错误 |
|
|
445
|
+
| `NFC_USER_CANCELED` | 40011 | 用户取消 NFC 扫描(iOS) |
|
|
446
|
+
| `READ_TIMEOUT` | 40012 | 读取超时 |
|
|
447
|
+
| `NFC_LOCK_TIMEOUT` | 40013 | NFC 锁超时 |
|
|
448
|
+
| `CRC16_CHECK_FAILED` | 40014 | CRC16 校验失败 |
|
|
449
|
+
| `RETRY_COUNT_EXHAUSTED` | 40015 | PIN 重试次数耗尽,卡片已锁定 |
|
|
450
|
+
|
|
451
|
+
## 存储格式
|
|
452
|
+
|
|
453
|
+
卡片使用熵压缩方式存储 BIP-39 助记词:
|
|
418
454
|
|
|
419
455
|
```
|
|
420
|
-
[
|
|
456
|
+
[类型 1B] [熵 16-32B] [CRC16 2B]
|
|
421
457
|
```
|
|
422
458
|
|
|
423
|
-
|
|
|
424
|
-
|
|
425
|
-
| 0x01 | 12
|
|
426
|
-
| 0x02 | 15
|
|
427
|
-
| 0x03 | 18
|
|
428
|
-
| 0x04 | 21
|
|
429
|
-
| 0x05 | 24
|
|
459
|
+
| 类型 | 助记词长度 | 熵大小 |
|
|
460
|
+
|------|-----------|--------|
|
|
461
|
+
| 0x01 | 12 个词 | 16 字节(128 位) |
|
|
462
|
+
| 0x02 | 15 个词 | 20 字节(160 位) |
|
|
463
|
+
| 0x03 | 18 个词 | 24 字节(192 位) |
|
|
464
|
+
| 0x04 | 21 个词 | 28 字节(224 位) |
|
|
465
|
+
| 0x05 | 24 个词 | 32 字节(256 位) |
|
|
430
466
|
|
|
431
|
-
##
|
|
467
|
+
## 安全性
|
|
432
468
|
|
|
433
|
-
- **AES-128
|
|
434
|
-
- **SHA-256
|
|
435
|
-
- **CRC16-Modbus
|
|
436
|
-
- **
|
|
437
|
-
- **
|
|
469
|
+
- **AES-128 硬件级双向认证**(3-pass)
|
|
470
|
+
- **SHA-256 密钥派生**:密码 → SHA-256 → 取前 16 字节作为 AES 密钥
|
|
471
|
+
- **CRC16-Modbus 校验和**:数据完整性校验
|
|
472
|
+
- **CMAC 安全消息**:认证后所有命令附带 AES-CMAC(NIST 800-38B)防篡改
|
|
473
|
+
- **PIN 重试计数器**:密码错误时自动递减,认证成功后重置为 10
|
|
474
|
+
- **安全随机数**:认证使用 `crypto.getRandomValues()`(需要 Hermes ≥ 0.72 或 `react-native-get-random-values` polyfill)
|
|
438
475
|
|
|
439
|
-
##
|
|
476
|
+
## 项目结构
|
|
440
477
|
|
|
441
478
|
```
|
|
442
479
|
src/
|
|
443
|
-
├── index.ts #
|
|
444
|
-
├── constants.ts #
|
|
445
|
-
├── types.ts #
|
|
446
|
-
├── crypto.ts # AES
|
|
447
|
-
├── utils.ts # CRC16
|
|
448
|
-
├── nfc-core.ts # NFC
|
|
449
|
-
├── reader.ts #
|
|
450
|
-
└── writer.ts #
|
|
480
|
+
├── index.ts # 公共 API 导出
|
|
481
|
+
├── constants.ts # 共享常量(页地址、NFC 指令、助记词类型)
|
|
482
|
+
├── types.ts # 统一 NfcStatusCode、NfcResult 接口、错误映射
|
|
483
|
+
├── crypto.ts # AES 加解密、密钥派生、安全随机数
|
|
484
|
+
├── utils.ts # CRC16、十六进制转换、数组工具
|
|
485
|
+
├── nfc-core.ts # NFC 锁、transceive、认证、重试计数器
|
|
486
|
+
├── reader.ts # 读取 API
|
|
487
|
+
└── writer.ts # 写入 API
|
|
451
488
|
```
|
|
452
489
|
|
|
453
|
-
##
|
|
490
|
+
## 平台支持
|
|
454
491
|
|
|
455
|
-
|
|
|
456
|
-
|
|
457
|
-
| iOS | MifareIOS | iPhone 7
|
|
458
|
-
| Android | NfcA | NFC
|
|
492
|
+
| 平台 | 技术 | 要求 |
|
|
493
|
+
|------|------|------|
|
|
494
|
+
| iOS | MifareIOS | iPhone 7 及以上 |
|
|
495
|
+
| Android | NfcA | 支持 NFC 的设备 |
|
|
459
496
|
|
|
460
|
-
##
|
|
497
|
+
## 许可证
|
|
461
498
|
|
|
462
499
|
MIT
|