@ukeyfe/react-native-nfc-litecard 1.0.6 → 1.0.8

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/dist/index.js CHANGED
@@ -1,50 +1,1450 @@
1
1
  "use strict";
2
- /**
3
- * @ukeyfe/react-native-nfc-litecard
4
- *
5
- * NFC read/write library for MIFARE Ultralight AES (LiteCard mnemonic storage).
6
- */
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.readOriginality = exports.getCardVersion = exports.cardInfoToJson = exports.resetRetryCountTo10 = exports.readMnemonicRetryCount = exports.readUserNickname = exports.readMnemonic = exports.checkCard = exports.nfcResultRetryCountExhausted = exports.NfcStatusCode = void 0;
9
- // ---------------------------------------------------------------------------
10
- // Unified types & constants (consumers can use a single NfcStatusCode)
11
- // ---------------------------------------------------------------------------
12
- var types_1 = require("./types");
13
- Object.defineProperty(exports, "NfcStatusCode", { enumerable: true, get: function () { return types_1.NfcStatusCode; } });
14
- Object.defineProperty(exports, "nfcResultRetryCountExhausted", { enumerable: true, get: function () { return types_1.nfcResultRetryCountExhausted; } });
15
- // ---------------------------------------------------------------------------
16
- // Reader API
17
- // ---------------------------------------------------------------------------
18
- var reader_1 = require("./reader");
19
- Object.defineProperty(exports, "checkCard", { enumerable: true, get: function () { return reader_1.checkCard; } });
20
- Object.defineProperty(exports, "readMnemonic", { enumerable: true, get: function () { return reader_1.readMnemonic; } });
21
- Object.defineProperty(exports, "readUserNickname", { enumerable: true, get: function () { return reader_1.readUserNickname; } });
22
- Object.defineProperty(exports, "readMnemonicRetryCount", { enumerable: true, get: function () { return reader_1.readMnemonicRetryCount; } });
23
- Object.defineProperty(exports, "resetRetryCountTo10", { enumerable: true, get: function () { return reader_1.resetRetryCountTo10; } });
24
- Object.defineProperty(exports, "cardInfoToJson", { enumerable: true, get: function () { return reader_1.cardInfoToJson; } });
25
- Object.defineProperty(exports, "getCardVersion", { enumerable: true, get: function () { return reader_1.getCardVersion; } });
26
- Object.defineProperty(exports, "readOriginality", { enumerable: true, get: function () { return reader_1.readOriginality; } });
27
- // ---------------------------------------------------------------------------
28
- // Writer API
29
- // ---------------------------------------------------------------------------
30
- var writer_1 = require("./writer");
31
- Object.defineProperty(exports, "initializeCard", { enumerable: true, get: function () { return writer_1.initializeCard; } });
32
- Object.defineProperty(exports, "updateCard", { enumerable: true, get: function () { return writer_1.updateCard; } });
33
- Object.defineProperty(exports, "updatePassword", { enumerable: true, get: function () { return writer_1.updatePassword; } });
34
- Object.defineProperty(exports, "writeUserNickname", { enumerable: true, get: function () { return writer_1.writeUserNickname; } });
35
- Object.defineProperty(exports, "resetCard", { enumerable: true, get: function () { return writer_1.resetCard; } });
36
- // ---------------------------------------------------------------------------
37
- // NFC lock & cleanup helpers (for app-level lifecycle management)
38
- // ---------------------------------------------------------------------------
39
- var nfc_core_1 = require("./nfc-core");
40
- Object.defineProperty(exports, "isNfcOperationLocked", { enumerable: true, get: function () { return nfc_core_1.isNfcOperationLocked; } });
41
- Object.defineProperty(exports, "releaseNfcOperationLock", { enumerable: true, get: function () { return nfc_core_1.releaseNfcOperationLock; } });
42
- Object.defineProperty(exports, "markNfcOperationCancelledByCleanup", { enumerable: true, get: function () { return nfc_core_1.markNfcOperationCancelledByCleanup; } });
43
- Object.defineProperty(exports, "consumeNfcOperationCancelledByCleanup", { enumerable: true, get: function () { return nfc_core_1.consumeNfcOperationCancelledByCleanup; } });
44
- Object.defineProperty(exports, "getNfcOperationCancelledByCleanupTimestamp", { enumerable: true, get: function () { return nfc_core_1.getNfcOperationCancelledByCleanupTimestamp; } });
45
- Object.defineProperty(exports, "clearNfcOperationCancelledByCleanup", { enumerable: true, get: function () { return nfc_core_1.clearNfcOperationCancelledByCleanup; } });
46
- // ---------------------------------------------------------------------------
47
- // Constants that apps may need
48
- // ---------------------------------------------------------------------------
49
- var constants_1 = require("./constants");
50
- Object.defineProperty(exports, "DEFAULT_PIN_RETRY_COUNT", { enumerable: true, get: function () { return constants_1.DEFAULT_PIN_RETRY_COUNT; } });
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DEFAULT_PIN_RETRY_COUNT: () => DEFAULT_PIN_RETRY_COUNT,
34
+ NfcStatusCode: () => NfcStatusCode,
35
+ cardInfoToJson: () => cardInfoToJson,
36
+ checkCard: () => checkCard,
37
+ clearNfcOperationCancelledByCleanup: () => clearNfcOperationCancelledByCleanup,
38
+ consumeNfcOperationCancelledByCleanup: () => consumeNfcOperationCancelledByCleanup,
39
+ getCardVersion: () => getCardVersion,
40
+ getNfcOperationCancelledByCleanupTimestamp: () => getNfcOperationCancelledByCleanupTimestamp,
41
+ initializeCard: () => initializeCard,
42
+ isNfcOperationLocked: () => isNfcOperationLocked,
43
+ markNfcOperationCancelledByCleanup: () => markNfcOperationCancelledByCleanup,
44
+ nfcResultRetryCountExhausted: () => nfcResultRetryCountExhausted,
45
+ readMnemonic: () => readMnemonic,
46
+ readMnemonicRetryCount: () => readMnemonicRetryCount,
47
+ readOriginality: () => readOriginality,
48
+ readUserNickname: () => readUserNickname,
49
+ releaseNfcOperationLock: () => releaseNfcOperationLock,
50
+ resetCard: () => resetCard,
51
+ resetRetryCountTo10: () => resetRetryCountTo10,
52
+ updateCard: () => updateCard,
53
+ updatePassword: () => updatePassword,
54
+ writeUserNickname: () => writeUserNickname
55
+ });
56
+ module.exports = __toCommonJS(index_exports);
57
+
58
+ // src/types.ts
59
+ var import_react_native = require("react-native");
60
+ var NfcStatusCode = {
61
+ // Reader success (101xx)
62
+ READ_SUCCESS: 10102,
63
+ READ_NICKNAME_SUCCESS: 10103,
64
+ CHECK_EMPTY: 10104,
65
+ CHECK_HAS_DATA: 10105,
66
+ READ_RETRY_COUNT_SUCCESS: 10106,
67
+ GET_VERSION_SUCCESS: 10107,
68
+ READ_SIG_SUCCESS: 10108,
69
+ // Writer success (102xx)
70
+ INIT_SUCCESS: 10201,
71
+ WRITE_SUCCESS: 10203,
72
+ UPDATE_PASSWORD_SUCCESS: 10204,
73
+ WRITE_NICKNAME_SUCCESS: 10205,
74
+ RESET_SUCCESS: 10206,
75
+ /** Card already has a valid mnemonic backup; write was skipped */
76
+ PRECHECK_HAS_BACKUP: 10207,
77
+ // Failure (4xxxx – shared by reader & writer)
78
+ NFC_CONNECT_FAILED: 40001,
79
+ AUTH_WRONG_PASSWORD: 40002,
80
+ AUTH_INVALID_RESPONSE: 40003,
81
+ AUTH_VERIFY_FAILED: 40004,
82
+ READ_FAILED: 40005,
83
+ WRITE_FAILED: 40006,
84
+ INVALID_MNEMONIC: 40007,
85
+ UNSUPPORTED_MNEMONIC_LENGTH: 40008,
86
+ INVALID_CARD_DATA: 40009,
87
+ UNKNOWN_ERROR: 40010,
88
+ NFC_USER_CANCELED: 40011,
89
+ READ_TIMEOUT: 40012,
90
+ NFC_LOCK_TIMEOUT: 40013,
91
+ CRC16_CHECK_FAILED: 40014,
92
+ /** PIN retry counter is 0; no authentication attempts left */
93
+ RETRY_COUNT_EXHAUSTED: 40015,
94
+ /** CMAC verification failed – response integrity compromised */
95
+ CMAC_VERIFY_FAILED: 40016
96
+ };
97
+ function nfcResultRetryCountExhausted() {
98
+ return {
99
+ code: NfcStatusCode.RETRY_COUNT_EXHAUSTED,
100
+ success: false,
101
+ data: { retryCount: 0 }
102
+ };
103
+ }
104
+ function errorToCode(error) {
105
+ const msg = (typeof error === "string" ? error : error?.message || "").trim();
106
+ if (import_react_native.Platform.OS === "ios") {
107
+ if (error && typeof error === "object" && (error.name === "UserCancel" || error.constructor?.name === "UserCancel")) {
108
+ return NfcStatusCode.NFC_USER_CANCELED;
109
+ }
110
+ if (!msg || msg.includes("User Canceled") || msg.includes("Unknown empty error")) {
111
+ return NfcStatusCode.NFC_USER_CANCELED;
112
+ }
113
+ }
114
+ if (msg.includes("NFC_USER_CANCELED_SIGNAL")) return NfcStatusCode.NFC_USER_CANCELED;
115
+ const keywords = [
116
+ ["CMAC_VERIFY_FAILED", NfcStatusCode.CMAC_VERIFY_FAILED],
117
+ ["RETRY_COUNT_EXHAUSTED", NfcStatusCode.RETRY_COUNT_EXHAUSTED],
118
+ ["AUTH_WRONG_PASSWORD", NfcStatusCode.AUTH_WRONG_PASSWORD],
119
+ ["AUTH_INVALID_RESPONSE", NfcStatusCode.AUTH_INVALID_RESPONSE],
120
+ ["AUTH_VERIFY_FAILED", NfcStatusCode.AUTH_VERIFY_FAILED],
121
+ ["READ_FAILED", NfcStatusCode.READ_FAILED],
122
+ ["WRITE_FAILED", NfcStatusCode.WRITE_FAILED],
123
+ ["CRC16_CHECK_FAILED", NfcStatusCode.CRC16_CHECK_FAILED],
124
+ ["EMPTY_CARD", NfcStatusCode.CHECK_EMPTY],
125
+ ["INVALID_CARD_DATA", NfcStatusCode.INVALID_CARD_DATA],
126
+ ["INVALID_MNEMONIC", NfcStatusCode.INVALID_MNEMONIC],
127
+ ["UNSUPPORTED_MNEMONIC_LENGTH", NfcStatusCode.UNSUPPORTED_MNEMONIC_LENGTH],
128
+ ["NFC_LOCK_TIMEOUT", NfcStatusCode.NFC_LOCK_TIMEOUT],
129
+ ["NFC_CONNECT_FAILED", NfcStatusCode.NFC_CONNECT_FAILED],
130
+ ["connection failed", NfcStatusCode.NFC_CONNECT_FAILED],
131
+ ["transceive fail", NfcStatusCode.NFC_CONNECT_FAILED]
132
+ ];
133
+ for (const [keyword, code] of keywords) {
134
+ if (msg.includes(keyword)) return code;
135
+ }
136
+ return NfcStatusCode.UNKNOWN_ERROR;
137
+ }
138
+
139
+ // src/reader.ts
140
+ var bip39 = __toESM(require("bip39"));
141
+
142
+ // src/constants.ts
143
+ var CMD_READ = 48;
144
+ var CMD_WRITE = 162;
145
+ var CMD_FAST_READ = 58;
146
+ var CMD_AUTH_PART1 = 26;
147
+ var CMD_AUTH_PART2 = 175;
148
+ var CMD_GET_VERSION = 96;
149
+ var CMD_READ_SIG = 60;
150
+ var KEY_NO_DATA_PROT = 0;
151
+ var PAGE_SIZE = 4;
152
+ var USER_PAGE_START = 8;
153
+ var USER_PAGE_END = 39;
154
+ var USER_CARD_INFO_PAGE_START = 4;
155
+ var USER_CARD_INFO_PAGE_END = 7;
156
+ var PAGE_CFG0 = 41;
157
+ var PAGE_CFG1 = 42;
158
+ var PAGE_AES_KEY0_START = 48;
159
+ var USER_MEMORY_SIZE = (USER_PAGE_END - USER_PAGE_START + 1) * PAGE_SIZE;
160
+ var USER_CARD_INFO_SIZE = (USER_CARD_INFO_PAGE_END - USER_CARD_INFO_PAGE_START + 1) * PAGE_SIZE;
161
+ var MNEMONIC_TYPE_12 = 1;
162
+ var MNEMONIC_TYPE_15 = 2;
163
+ var MNEMONIC_TYPE_18 = 3;
164
+ var MNEMONIC_TYPE_21 = 4;
165
+ var MNEMONIC_TYPE_24 = 5;
166
+ var MNEMONIC_PAGE_END = USER_PAGE_END - 3;
167
+ var MNEMONIC_MEMORY_SIZE = (MNEMONIC_PAGE_END - USER_PAGE_START + 1) * PAGE_SIZE;
168
+ var USER_NICKNAME_PAGE_START = USER_PAGE_END - 2;
169
+ var USER_NICKNAME_PAGE_END = USER_PAGE_END;
170
+ var USER_NICKNAME_MAX_LENGTH = 12;
171
+ var RETRY_COUNTER_PAGE = USER_PAGE_START - 1;
172
+ var RETRY_COUNTER_OFFSET = PAGE_SIZE - 1;
173
+ var DEFAULT_PIN_RETRY_COUNT = 10;
174
+ var SEC_MSG_ACT_MASK = 2;
175
+ var DELAY = {
176
+ /** Delay after requestNfcTech on Android */
177
+ ANDROID_POST_TECH: 300,
178
+ /** Delay after releaseNfcTech (normal) */
179
+ IOS_POST_RELEASE: 100,
180
+ ANDROID_POST_RELEASE: 800,
181
+ /** Delay after releaseNfcTech (forced, e.g. after timeout) */
182
+ IOS_POST_RELEASE_FORCED: 300,
183
+ ANDROID_POST_RELEASE_FORCED: 1e3,
184
+ /** Delay between page writes on iOS */
185
+ IOS_PAGE_WRITE: 20,
186
+ /** Delay between AES key page writes on iOS */
187
+ IOS_KEY_WRITE: 10,
188
+ /** Delay between nickname page writes on iOS */
189
+ IOS_NICKNAME_WRITE: 100,
190
+ /** Delay between config operations on iOS */
191
+ IOS_CONFIG: 80,
192
+ /** Keep-alive frequency (every N pages) during writes */
193
+ KEEPALIVE_FREQ: 4
194
+ };
195
+
196
+ // src/utils.ts
197
+ function bytesToHex(bytes) {
198
+ let hex = "";
199
+ for (let i = 0; i < bytes.length; i++) {
200
+ hex += bytes[i].toString(16).padStart(2, "0");
201
+ }
202
+ return hex;
203
+ }
204
+ function hexToBytes(hex) {
205
+ const bytes = new Uint8Array(hex.length / 2);
206
+ for (let i = 0; i < hex.length; i += 2) {
207
+ bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
208
+ }
209
+ return bytes;
210
+ }
211
+ function calculateCRC16(data) {
212
+ let crc = 65535;
213
+ for (let i = 0; i < data.length; i++) {
214
+ crc ^= data[i];
215
+ for (let j = 0; j < 8; j++) {
216
+ if ((crc & 1) !== 0) {
217
+ crc = crc >> 1 ^ 40961;
218
+ } else {
219
+ crc = crc >> 1;
220
+ }
221
+ }
222
+ }
223
+ return crc & 65535;
224
+ }
225
+ function crc16ToBytes(crc16) {
226
+ return new Uint8Array([crc16 & 255, crc16 >> 8 & 255]);
227
+ }
228
+ function extractCRC16(data) {
229
+ if (data.length < 2) {
230
+ throw new Error("INVALID_CARD_DATA");
231
+ }
232
+ return data[data.length - 2] | data[data.length - 1] << 8;
233
+ }
234
+ var MNEMONIC_TYPE_TABLE = [
235
+ [MNEMONIC_TYPE_12, 16, "12 words (128-bit)"],
236
+ [MNEMONIC_TYPE_15, 20, "15 words (160-bit)"],
237
+ [MNEMONIC_TYPE_18, 24, "18 words (192-bit)"],
238
+ [MNEMONIC_TYPE_21, 28, "21 words (224-bit)"],
239
+ [MNEMONIC_TYPE_24, 32, "24 words (256-bit)"]
240
+ ];
241
+ function getMnemonicTypeInfo(typeId) {
242
+ const entry = MNEMONIC_TYPE_TABLE.find(([id]) => id === typeId);
243
+ if (!entry) throw new Error("INVALID_CARD_DATA");
244
+ return { entropyLength: entry[1], description: entry[2] };
245
+ }
246
+ function getMnemonicTypeByEntropyLength(entropyLength) {
247
+ const entry = MNEMONIC_TYPE_TABLE.find(([, len]) => len === entropyLength);
248
+ if (!entry) throw new Error("UNSUPPORTED_MNEMONIC_LENGTH");
249
+ return { typeId: entry[0], description: entry[2] };
250
+ }
251
+ function validateMnemonicPayload(data) {
252
+ if (data.length < 19) throw new Error("INVALID_CARD_DATA");
253
+ if (data.every((b) => b === 0)) throw new Error("EMPTY_CARD");
254
+ const { entropyLength, description } = getMnemonicTypeInfo(data[0]);
255
+ const expectedTotal = 1 + entropyLength + 2;
256
+ if (data.length < expectedTotal) throw new Error("INVALID_CARD_DATA");
257
+ const dataBlock = data.slice(0, 1 + entropyLength);
258
+ const storedCRC = extractCRC16(data.slice(1 + entropyLength, expectedTotal));
259
+ const calcCRC = calculateCRC16(dataBlock);
260
+ if (storedCRC !== calcCRC) throw new Error("CRC16_CHECK_FAILED");
261
+ return { type: description };
262
+ }
263
+
264
+ // src/crypto.ts
265
+ var import_aes_js = __toESM(require("aes-js"));
266
+ var import_js_sha256 = require("js-sha256");
267
+ function passwordToAesKey(password) {
268
+ const safePassword = password || "";
269
+ const hashBytes = import_js_sha256.sha256.arrayBuffer(String(safePassword));
270
+ return new Uint8Array(hashBytes).slice(0, 16);
271
+ }
272
+ function aesDecrypt(key, data, iv) {
273
+ const aesCbc = new import_aes_js.default.ModeOfOperation.cbc(Array.from(key), Array.from(iv));
274
+ return new Uint8Array(aesCbc.decrypt(Array.from(data)));
275
+ }
276
+ function aesEncrypt(key, data, iv) {
277
+ const aesCbc = new import_aes_js.default.ModeOfOperation.cbc(Array.from(key), Array.from(iv));
278
+ return new Uint8Array(aesCbc.encrypt(Array.from(data)));
279
+ }
280
+ function rotateLeft8(data) {
281
+ const result = new Uint8Array(data.length);
282
+ for (let i = 0; i < data.length - 1; i++) {
283
+ result[i] = data[i + 1];
284
+ }
285
+ result[data.length - 1] = data[0];
286
+ return result;
287
+ }
288
+ function arraysEqual(a, b) {
289
+ if (a.length !== b.length) return false;
290
+ for (let i = 0; i < a.length; i++) {
291
+ if (a[i] !== b[i]) return false;
292
+ }
293
+ return true;
294
+ }
295
+ function aesEcbEncrypt(key, data) {
296
+ const aesEcb = new import_aes_js.default.ModeOfOperation.ecb(Array.from(key));
297
+ return new Uint8Array(aesEcb.encrypt(Array.from(data)));
298
+ }
299
+ var CONST_RB = 135;
300
+ function shiftLeft1(data) {
301
+ const result = new Uint8Array(16);
302
+ let carry = 0;
303
+ for (let i = 15; i >= 0; i--) {
304
+ const shifted = data[i] << 1 | carry;
305
+ result[i] = shifted & 255;
306
+ carry = data[i] & 128 ? 1 : 0;
307
+ }
308
+ return result;
309
+ }
310
+ function xor16(a, b) {
311
+ const result = new Uint8Array(16);
312
+ for (let i = 0; i < 16; i++) {
313
+ result[i] = a[i] ^ b[i];
314
+ }
315
+ return result;
316
+ }
317
+ function generateCmacSubkeys(key) {
318
+ const zero = new Uint8Array(16);
319
+ const L = aesEcbEncrypt(key, zero);
320
+ let k1 = shiftLeft1(L);
321
+ if (L[0] & 128) {
322
+ k1[15] ^= CONST_RB;
323
+ }
324
+ let k2 = shiftLeft1(k1);
325
+ if (k1[0] & 128) {
326
+ k2[15] ^= CONST_RB;
327
+ }
328
+ return { k1, k2 };
329
+ }
330
+ function aesCmac(key, message) {
331
+ const { k1, k2 } = generateCmacSubkeys(key);
332
+ const n = message.length === 0 ? 1 : Math.ceil(message.length / 16);
333
+ const lastBlockComplete = message.length > 0 && message.length % 16 === 0;
334
+ const lastBlock = new Uint8Array(16);
335
+ if (lastBlockComplete) {
336
+ const offset = (n - 1) * 16;
337
+ for (let i = 0; i < 16; i++) {
338
+ lastBlock[i] = message[offset + i] ^ k1[i];
339
+ }
340
+ } else {
341
+ const offset = (n - 1) * 16;
342
+ const remaining = message.length - offset;
343
+ for (let i = 0; i < 16; i++) {
344
+ if (i < remaining) {
345
+ lastBlock[i] = message[offset + i];
346
+ } else if (i === remaining) {
347
+ lastBlock[i] = 128;
348
+ } else {
349
+ lastBlock[i] = 0;
350
+ }
351
+ }
352
+ for (let i = 0; i < 16; i++) {
353
+ lastBlock[i] ^= k2[i];
354
+ }
355
+ }
356
+ const iv0 = new Uint8Array(16);
357
+ let x = iv0;
358
+ for (let i = 0; i < n - 1; i++) {
359
+ const block = new Uint8Array(message.slice(i * 16, (i + 1) * 16));
360
+ const y2 = xor16(x, block);
361
+ x = aesEcbEncrypt(key, y2);
362
+ }
363
+ const y = xor16(x, lastBlock);
364
+ return aesEcbEncrypt(key, y);
365
+ }
366
+ function deriveSessionKey(authKey, rndA, rndB) {
367
+ const sv2 = new Uint8Array(32);
368
+ sv2[0] = 90;
369
+ sv2[1] = 165;
370
+ sv2[2] = 0;
371
+ sv2[3] = 1;
372
+ sv2[4] = 0;
373
+ sv2[5] = 128;
374
+ sv2[6] = rndA[0];
375
+ sv2[7] = rndA[1];
376
+ for (let i = 0; i < 6; i++) {
377
+ sv2[8 + i] = rndA[2 + i] ^ rndB[i];
378
+ }
379
+ for (let i = 0; i < 10; i++) {
380
+ sv2[14 + i] = rndB[6 + i];
381
+ }
382
+ for (let i = 0; i < 8; i++) {
383
+ sv2[24 + i] = rndA[8 + i];
384
+ }
385
+ return aesCmac(authKey, sv2);
386
+ }
387
+ function generateRandom16() {
388
+ if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.getRandomValues !== "function") {
389
+ throw new Error(
390
+ "[nfc-litecard] crypto.getRandomValues is not available. Please add the react-native-get-random-values polyfill or use Hermes >= 0.72."
391
+ );
392
+ }
393
+ try {
394
+ const arr = new Uint8Array(16);
395
+ globalThis.crypto.getRandomValues(arr);
396
+ return arr;
397
+ } catch (e) {
398
+ throw e;
399
+ }
400
+ }
401
+
402
+ // src/nfc-core.ts
403
+ var import_react_native_nfc_manager = __toESM(require("react-native-nfc-manager"));
404
+ var import_react_native2 = require("react-native");
405
+ var nfcOperationLock = false;
406
+ async function acquireNfcLock() {
407
+ const maxWait = 1e4;
408
+ const interval = 100;
409
+ let waited = 0;
410
+ while (nfcOperationLock) {
411
+ if (waited >= maxWait) throw new Error("NFC_LOCK_TIMEOUT");
412
+ await new Promise((r) => setTimeout(r, interval));
413
+ waited += interval;
414
+ }
415
+ nfcOperationLock = true;
416
+ clearNfcOperationCancelledByCleanup();
417
+ }
418
+ function releaseNfcLock() {
419
+ nfcOperationLock = false;
420
+ }
421
+ function isNfcOperationLocked() {
422
+ return nfcOperationLock;
423
+ }
424
+ function releaseNfcOperationLock() {
425
+ releaseNfcLock();
426
+ }
427
+ var nfcCancelledByCleanup = false;
428
+ var nfcCancelledByCleanupTimestamp = 0;
429
+ function markNfcOperationCancelledByCleanup() {
430
+ nfcCancelledByCleanup = true;
431
+ nfcCancelledByCleanupTimestamp = Date.now();
432
+ }
433
+ function consumeNfcOperationCancelledByCleanup() {
434
+ const cancelled = nfcCancelledByCleanup;
435
+ nfcCancelledByCleanup = false;
436
+ nfcCancelledByCleanupTimestamp = 0;
437
+ return cancelled;
438
+ }
439
+ function getNfcOperationCancelledByCleanupTimestamp() {
440
+ return nfcCancelledByCleanupTimestamp;
441
+ }
442
+ function clearNfcOperationCancelledByCleanup() {
443
+ nfcCancelledByCleanup = false;
444
+ nfcCancelledByCleanupTimestamp = 0;
445
+ }
446
+ var cmacSessionKey = null;
447
+ var cmacCmdCtr = 0;
448
+ function clearCmacSession() {
449
+ cmacSessionKey = null;
450
+ cmacCmdCtr = 0;
451
+ }
452
+ function computeCommandCmac(sessionKey, cmdCtr, command) {
453
+ const ctrLo = cmdCtr & 255;
454
+ const ctrHi = cmdCtr >> 8 & 255;
455
+ const input = new Uint8Array(2 + command.length);
456
+ input[0] = ctrLo;
457
+ input[1] = ctrHi;
458
+ input.set(command, 2);
459
+ const mac = aesCmac(sessionKey, input);
460
+ return truncateCmac(mac);
461
+ }
462
+ function truncateCmac(mac) {
463
+ return new Uint8Array([mac[1], mac[3], mac[5], mac[7], mac[9], mac[11], mac[13], mac[15]]);
464
+ }
465
+ function computeResponseCmac(sessionKey, cmdCtr, responseData) {
466
+ const ctrLo = cmdCtr & 255;
467
+ const ctrHi = cmdCtr >> 8 & 255;
468
+ const input = new Uint8Array(2 + responseData.length);
469
+ input[0] = ctrLo;
470
+ input[1] = ctrHi;
471
+ input.set(responseData, 2);
472
+ const mac = aesCmac(sessionKey, input);
473
+ return truncateCmac(mac);
474
+ }
475
+ async function transceive(command, _timeoutMs = 2e3, retryCount = 0) {
476
+ let cmdToSend = command;
477
+ const cmdHex = command.map((b) => b.toString(16).padStart(2, "0")).join(" ");
478
+ if (cmacSessionKey) {
479
+ const mac = computeCommandCmac(cmacSessionKey, cmacCmdCtr, command);
480
+ cmdToSend = [...command, ...Array.from(mac)];
481
+ const macHex = Array.from(mac).map((b) => b.toString(16).padStart(2, "0")).join(" ");
482
+ } else {
483
+ }
484
+ try {
485
+ const result = import_react_native2.Platform.OS === "ios" ? await import_react_native_nfc_manager.default.sendMifareCommandIOS(cmdToSend) : await import_react_native_nfc_manager.default.nfcAHandler.transceive(cmdToSend);
486
+ const resultHex = result ? result.map((b) => b.toString(16).padStart(2, "0")).join(" ") : "null";
487
+ if (cmacSessionKey) {
488
+ cmacCmdCtr++;
489
+ if (result && result.length >= 8) {
490
+ const dataLen = result.length - 8;
491
+ const responseData = result.slice(0, dataLen);
492
+ const responseMac = result.slice(dataLen);
493
+ const expectedMac = computeResponseCmac(cmacSessionKey, cmacCmdCtr, responseData);
494
+ const rMacHex = responseMac.map((b) => b.toString(16).padStart(2, "0")).join(" ");
495
+ const eMacHex = Array.from(expectedMac).map((b) => b.toString(16).padStart(2, "0")).join(" ");
496
+ let diff = 0;
497
+ for (let i = 0; i < 8; i++) {
498
+ diff |= responseMac[i] ^ expectedMac[i];
499
+ }
500
+ if (diff !== 0) {
501
+ throw new Error("CMAC_VERIFY_FAILED");
502
+ }
503
+ cmacCmdCtr++;
504
+ return responseData;
505
+ }
506
+ cmacCmdCtr++;
507
+ return result;
508
+ }
509
+ return result;
510
+ } catch (error) {
511
+ const maxRetries = import_react_native2.Platform.OS === "ios" ? 1 : 2;
512
+ if (retryCount < maxRetries && error?.message !== "CMAC_VERIFY_FAILED") {
513
+ const wait = 200 * (retryCount + 1);
514
+ await new Promise((r) => setTimeout(r, wait));
515
+ return transceive(command, _timeoutMs, retryCount + 1);
516
+ }
517
+ throw error;
518
+ }
519
+ }
520
+ async function requestNfcTech() {
521
+ await new Promise((r) => setTimeout(r, 100));
522
+ if (import_react_native2.Platform.OS === "ios") {
523
+ await import_react_native_nfc_manager.default.requestTechnology(import_react_native_nfc_manager.NfcTech.MifareIOS);
524
+ } else {
525
+ await import_react_native_nfc_manager.default.requestTechnology(import_react_native_nfc_manager.NfcTech.NfcA);
526
+ }
527
+ if (import_react_native2.Platform.OS === "android") {
528
+ await new Promise((r) => setTimeout(r, DELAY.ANDROID_POST_TECH));
529
+ } else {
530
+ try {
531
+ await import_react_native_nfc_manager.default.sendMifareCommandIOS([CMD_READ, 0]);
532
+ } catch (_) {
533
+ }
534
+ }
535
+ }
536
+ async function releaseNfcTech(forceLongDelay = false) {
537
+ clearCmacSession();
538
+ try {
539
+ await import_react_native_nfc_manager.default.cancelTechnologyRequest();
540
+ const delay = import_react_native2.Platform.OS === "ios" ? forceLongDelay ? DELAY.IOS_POST_RELEASE_FORCED : DELAY.IOS_POST_RELEASE : forceLongDelay ? DELAY.ANDROID_POST_RELEASE_FORCED : DELAY.ANDROID_POST_RELEASE;
541
+ await new Promise((r) => setTimeout(r, delay));
542
+ } catch (error) {
543
+ }
544
+ }
545
+ async function authenticate(key) {
546
+ clearCmacSession();
547
+ const iv0 = new Uint8Array(16);
548
+ const response1 = await transceive([CMD_AUTH_PART1, KEY_NO_DATA_PROT]);
549
+ if (!response1 || response1.length < 17 || response1[0] !== 175) {
550
+ throw new Error("AUTH_INVALID_RESPONSE");
551
+ }
552
+ const ekRndB = new Uint8Array(response1.slice(1, 17));
553
+ const rndB = aesDecrypt(key, ekRndB, iv0);
554
+ const rndBRot = rotateLeft8(rndB);
555
+ const rndA = generateRandom16();
556
+ const rndAB = new Uint8Array(32);
557
+ rndAB.set(rndA, 0);
558
+ rndAB.set(rndBRot, 16);
559
+ const ekPart1 = aesEncrypt(key, rndAB.slice(0, 16), iv0);
560
+ const ekPart2 = aesEncrypt(key, rndAB.slice(16, 32), ekPart1);
561
+ const ekRndAB = new Uint8Array(32);
562
+ ekRndAB.set(ekPart1, 0);
563
+ ekRndAB.set(ekPart2, 16);
564
+ let response2;
565
+ try {
566
+ response2 = await transceive([CMD_AUTH_PART2, ...Array.from(ekRndAB)]);
567
+ } catch (authErr) {
568
+ throw new Error("AUTH_WRONG_PASSWORD");
569
+ }
570
+ if (response2.length < 17 || response2[0] !== 0) {
571
+ throw new Error("AUTH_WRONG_PASSWORD");
572
+ }
573
+ const ekRndARot = new Uint8Array(response2.slice(1, 17));
574
+ const rndARot = aesDecrypt(key, ekRndARot, iv0);
575
+ const expectedRndARot = rotateLeft8(rndA);
576
+ if (!arraysEqual(rndARot, expectedRndARot)) {
577
+ throw new Error("AUTH_VERIFY_FAILED");
578
+ }
579
+ cmacSessionKey = deriveSessionKey(key, rndA, rndB);
580
+ cmacCmdCtr = 0;
581
+ }
582
+ async function readUserMemory() {
583
+ const response = await transceive([CMD_FAST_READ, USER_PAGE_START, USER_PAGE_END]);
584
+ if (!response || response.length < USER_MEMORY_SIZE) throw new Error("READ_FAILED");
585
+ return new Uint8Array(response.slice(0, USER_MEMORY_SIZE));
586
+ }
587
+ async function decrementRetryCountInSession() {
588
+ const pageBlock = await transceive([CMD_READ, RETRY_COUNTER_PAGE]);
589
+ if (!pageBlock || pageBlock.length < PAGE_SIZE) return void 0;
590
+ const page = pageBlock.slice(0, PAGE_SIZE);
591
+ const raw = page[RETRY_COUNTER_OFFSET];
592
+ const current = typeof raw === "number" ? raw & 255 : 0;
593
+ if (current <= 0) throw new Error("RETRY_COUNT_EXHAUSTED");
594
+ const next = current - 1;
595
+ page[RETRY_COUNTER_OFFSET] = next & 255;
596
+ await transceive([CMD_WRITE, RETRY_COUNTER_PAGE, ...page]);
597
+ return next;
598
+ }
599
+ async function writeRetryCountInSession(count) {
600
+ const safeCount = Math.max(0, Math.min(255, count | 0));
601
+ const pageBlock = await transceive([CMD_READ, RETRY_COUNTER_PAGE]);
602
+ if (!pageBlock || pageBlock.length < PAGE_SIZE) throw new Error("READ_FAILED");
603
+ const page = pageBlock.slice(0, PAGE_SIZE);
604
+ page[RETRY_COUNTER_OFFSET] = safeCount & 255;
605
+ await transceive([CMD_WRITE, RETRY_COUNTER_PAGE, ...page]);
606
+ }
607
+
608
+ // src/reader.ts
609
+ async function readUserNicknameInternal() {
610
+ const response = await transceive([CMD_FAST_READ, USER_NICKNAME_PAGE_START, USER_NICKNAME_PAGE_END]);
611
+ if (!response || response.length < USER_NICKNAME_MAX_LENGTH) throw new Error("READ_FAILED");
612
+ const responseArray = Array.isArray(response) ? response : Array.from(response);
613
+ const nicknameBytes = new Uint8Array(responseArray.slice(0, USER_NICKNAME_MAX_LENGTH));
614
+ let actualLength = 0;
615
+ for (let i = 0; i < USER_NICKNAME_MAX_LENGTH; i++) {
616
+ if (nicknameBytes[i] !== 0) actualLength = i + 1;
617
+ }
618
+ if (actualLength === 0) return "";
619
+ try {
620
+ return new TextDecoder("utf-8").decode(nicknameBytes.slice(0, actualLength));
621
+ } catch {
622
+ let result = "";
623
+ for (let i = 0; i < actualLength; i++) result += String.fromCharCode(nicknameBytes[i]);
624
+ return result;
625
+ }
626
+ }
627
+ function entropyToMnemonic2(data) {
628
+ if (data.length < 19) throw new Error("INVALID_CARD_DATA");
629
+ if (data.every((b) => b === 0)) throw new Error("EMPTY_CARD");
630
+ const { entropyLength, description } = getMnemonicTypeInfo(data[0]);
631
+ const expectedTotal = 1 + entropyLength + 2;
632
+ if (data.length < expectedTotal) throw new Error("INVALID_CARD_DATA");
633
+ const dataBlock = data.slice(0, 1 + entropyLength);
634
+ const storedCRC = extractCRC16(data.slice(1 + entropyLength, expectedTotal));
635
+ const calcCRC = calculateCRC16(dataBlock);
636
+ if (storedCRC !== calcCRC) throw new Error("CRC16_CHECK_FAILED");
637
+ const entropyBytes = data.slice(1, 1 + entropyLength);
638
+ const entropyHex = bytesToHex(Array.from(entropyBytes));
639
+ const rawBytes = bytesToHex(Array.from(dataBlock));
640
+ const mnemonic = bip39.entropyToMnemonic(entropyHex);
641
+ return { mnemonic, entropyHex, type: description, rawBytes, crc16: calcCRC };
642
+ }
643
+ function cardInfoToJson(cardInfo, pretty = true) {
644
+ return pretty ? JSON.stringify(cardInfo.json, null, 2) : JSON.stringify(cardInfo.json);
645
+ }
646
+ async function checkCard(password, onCardIdentified) {
647
+ try {
648
+ await acquireNfcLock();
649
+ } catch {
650
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
651
+ }
652
+ try {
653
+ try {
654
+ await requestNfcTech();
655
+ } catch {
656
+ releaseNfcLock();
657
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
658
+ }
659
+ try {
660
+ if (password) {
661
+ try {
662
+ await decrementRetryCountInSession();
663
+ } catch (error) {
664
+ const code = errorToCode(error);
665
+ if (code === NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
666
+ await releaseNfcTech();
667
+ releaseNfcLock();
668
+ return nfcResultRetryCountExhausted();
669
+ }
670
+ }
671
+ const aesKey = passwordToAesKey(password);
672
+ await authenticate(aesKey);
673
+ try {
674
+ await writeRetryCountInSession(DEFAULT_PIN_RETRY_COUNT);
675
+ } catch {
676
+ }
677
+ onCardIdentified?.();
678
+ const data = await readUserMemory();
679
+ try {
680
+ const decoded = validateMnemonicPayload(data);
681
+ await releaseNfcTech();
682
+ releaseNfcLock();
683
+ return { code: NfcStatusCode.CHECK_HAS_DATA, success: true, data: { type: decoded.type } };
684
+ } catch {
685
+ await releaseNfcTech();
686
+ releaseNfcLock();
687
+ return { code: NfcStatusCode.CHECK_EMPTY, success: true };
688
+ }
689
+ } else {
690
+ onCardIdentified?.();
691
+ let response = null;
692
+ try {
693
+ response = await transceive([CMD_READ, USER_PAGE_START]);
694
+ } catch {
695
+ }
696
+ await releaseNfcTech();
697
+ releaseNfcLock();
698
+ if (response && response.length >= 4) {
699
+ let hasData = false;
700
+ try {
701
+ getMnemonicTypeInfo(response[0]);
702
+ hasData = true;
703
+ } catch {
704
+ }
705
+ return { code: hasData ? NfcStatusCode.CHECK_HAS_DATA : NfcStatusCode.CHECK_EMPTY, success: true };
706
+ }
707
+ return { code: NfcStatusCode.CHECK_HAS_DATA, success: true };
708
+ }
709
+ } catch (error) {
710
+ await releaseNfcTech();
711
+ releaseNfcLock();
712
+ const code = errorToCode(error);
713
+ return { code, success: false };
714
+ }
715
+ } catch (error) {
716
+ releaseNfcLock();
717
+ throw error;
718
+ }
719
+ }
720
+ async function readMnemonic(password, onCardIdentified) {
721
+ try {
722
+ await acquireNfcLock();
723
+ } catch {
724
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
725
+ }
726
+ let retryCountAfterPreDecrement;
727
+ try {
728
+ try {
729
+ await requestNfcTech();
730
+ } catch {
731
+ releaseNfcLock();
732
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
733
+ }
734
+ try {
735
+ try {
736
+ const next = await decrementRetryCountInSession();
737
+ if (typeof next === "number") retryCountAfterPreDecrement = next;
738
+ } catch (error) {
739
+ const code = errorToCode(error);
740
+ if (code === NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
741
+ await releaseNfcTech();
742
+ releaseNfcLock();
743
+ return nfcResultRetryCountExhausted();
744
+ }
745
+ }
746
+ const aesKey = passwordToAesKey(password);
747
+ await authenticate(aesKey);
748
+ onCardIdentified?.();
749
+ const data = await readUserMemory();
750
+ const mnemonicData = data.slice(0, MNEMONIC_MEMORY_SIZE);
751
+ if (mnemonicData.every((b) => b === 0)) {
752
+ await releaseNfcTech();
753
+ releaseNfcLock();
754
+ return { code: NfcStatusCode.CHECK_EMPTY, success: false };
755
+ }
756
+ const result = entropyToMnemonic2(data);
757
+ let nickname;
758
+ try {
759
+ nickname = await readUserNicknameInternal();
760
+ } catch {
761
+ }
762
+ let finalRetryCount;
763
+ try {
764
+ await writeRetryCountInSession(DEFAULT_PIN_RETRY_COUNT);
765
+ finalRetryCount = DEFAULT_PIN_RETRY_COUNT;
766
+ } catch {
767
+ }
768
+ await releaseNfcTech();
769
+ releaseNfcLock();
770
+ return {
771
+ code: NfcStatusCode.READ_SUCCESS,
772
+ success: true,
773
+ data: {
774
+ mnemonic: result.mnemonic,
775
+ type: result.type,
776
+ entropyHex: result.entropyHex,
777
+ rawBytes: result.rawBytes,
778
+ nickname,
779
+ retryCount: finalRetryCount
780
+ }
781
+ };
782
+ } catch (error) {
783
+ const isTimeout = error?.message?.includes("TRANSCEIVE_TIMEOUT") || error?.message?.includes("timeout") || error?.message?.includes("TagLost");
784
+ await releaseNfcTech(isTimeout);
785
+ releaseNfcLock();
786
+ const code = errorToCode(error);
787
+ return {
788
+ code,
789
+ success: false,
790
+ data: typeof retryCountAfterPreDecrement === "number" ? { retryCount: retryCountAfterPreDecrement } : void 0
791
+ };
792
+ }
793
+ } catch (error) {
794
+ releaseNfcLock();
795
+ throw error;
796
+ }
797
+ }
798
+ async function readUserNickname(password, onCardIdentified) {
799
+ try {
800
+ await acquireNfcLock();
801
+ } catch {
802
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
803
+ }
804
+ try {
805
+ try {
806
+ await requestNfcTech();
807
+ } catch {
808
+ releaseNfcLock();
809
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
810
+ }
811
+ try {
812
+ if (password) {
813
+ const aesKey = passwordToAesKey(password);
814
+ await authenticate(aesKey);
815
+ }
816
+ onCardIdentified?.();
817
+ const nickname = await readUserNicknameInternal();
818
+ await releaseNfcTech();
819
+ releaseNfcLock();
820
+ return {
821
+ code: NfcStatusCode.READ_NICKNAME_SUCCESS,
822
+ success: true,
823
+ data: { nickname }
824
+ };
825
+ } catch (error) {
826
+ await releaseNfcTech();
827
+ releaseNfcLock();
828
+ const code = errorToCode(error);
829
+ return { code, success: false };
830
+ }
831
+ } catch (error) {
832
+ releaseNfcLock();
833
+ throw error;
834
+ }
835
+ }
836
+ async function readMnemonicRetryCount(onCardIdentified) {
837
+ try {
838
+ await acquireNfcLock();
839
+ } catch {
840
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
841
+ }
842
+ try {
843
+ try {
844
+ await requestNfcTech();
845
+ } catch {
846
+ releaseNfcLock();
847
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
848
+ }
849
+ try {
850
+ onCardIdentified?.();
851
+ const pageBlock = await transceive([CMD_READ, RETRY_COUNTER_PAGE]);
852
+ if (!pageBlock || pageBlock.length < PAGE_SIZE) {
853
+ await releaseNfcTech();
854
+ releaseNfcLock();
855
+ return {
856
+ code: NfcStatusCode.READ_RETRY_COUNT_SUCCESS,
857
+ success: true,
858
+ data: { retryCount: 0 }
859
+ };
860
+ }
861
+ const retryCount = typeof pageBlock[RETRY_COUNTER_OFFSET] === "number" ? pageBlock[RETRY_COUNTER_OFFSET] & 255 : 0;
862
+ await releaseNfcTech();
863
+ releaseNfcLock();
864
+ return {
865
+ code: NfcStatusCode.READ_RETRY_COUNT_SUCCESS,
866
+ success: true,
867
+ data: { retryCount }
868
+ };
869
+ } catch (error) {
870
+ await releaseNfcTech();
871
+ releaseNfcLock();
872
+ const code = errorToCode(error);
873
+ return { code, success: false };
874
+ }
875
+ } catch (error) {
876
+ releaseNfcLock();
877
+ throw error;
878
+ }
879
+ }
880
+ async function resetRetryCountTo10(onCardIdentified) {
881
+ try {
882
+ await acquireNfcLock();
883
+ } catch {
884
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
885
+ }
886
+ try {
887
+ try {
888
+ await requestNfcTech();
889
+ } catch {
890
+ releaseNfcLock();
891
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
892
+ }
893
+ try {
894
+ onCardIdentified?.();
895
+ await writeRetryCountInSession(DEFAULT_PIN_RETRY_COUNT);
896
+ await releaseNfcTech();
897
+ releaseNfcLock();
898
+ return {
899
+ code: NfcStatusCode.READ_RETRY_COUNT_SUCCESS,
900
+ success: true,
901
+ data: { retryCount: DEFAULT_PIN_RETRY_COUNT }
902
+ };
903
+ } catch (error) {
904
+ await releaseNfcTech();
905
+ releaseNfcLock();
906
+ const code = errorToCode(error);
907
+ return { code, success: false };
908
+ }
909
+ } catch (error) {
910
+ releaseNfcLock();
911
+ throw error;
912
+ }
913
+ }
914
+ async function getCardVersion(onCardIdentified) {
915
+ try {
916
+ await acquireNfcLock();
917
+ } catch {
918
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
919
+ }
920
+ try {
921
+ try {
922
+ await requestNfcTech();
923
+ } catch {
924
+ releaseNfcLock();
925
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
926
+ }
927
+ try {
928
+ onCardIdentified?.();
929
+ const response = await transceive([CMD_GET_VERSION]);
930
+ if (!response || response.length < 8) {
931
+ throw new Error("READ_FAILED");
932
+ }
933
+ await releaseNfcTech();
934
+ releaseNfcLock();
935
+ return {
936
+ code: NfcStatusCode.GET_VERSION_SUCCESS,
937
+ success: true,
938
+ data: {
939
+ version: {
940
+ vendorId: response[1],
941
+ productType: response[2],
942
+ productSubtype: response[3],
943
+ majorVersion: response[4],
944
+ minorVersion: response[5],
945
+ storageSize: response[6],
946
+ protocolType: response[7]
947
+ }
948
+ }
949
+ };
950
+ } catch (error) {
951
+ await releaseNfcTech();
952
+ releaseNfcLock();
953
+ const code = errorToCode(error);
954
+ return { code, success: false };
955
+ }
956
+ } catch (error) {
957
+ releaseNfcLock();
958
+ throw error;
959
+ }
960
+ }
961
+ async function readOriginality(onCardIdentified) {
962
+ try {
963
+ await acquireNfcLock();
964
+ } catch {
965
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
966
+ }
967
+ try {
968
+ try {
969
+ await requestNfcTech();
970
+ } catch {
971
+ releaseNfcLock();
972
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
973
+ }
974
+ try {
975
+ onCardIdentified?.();
976
+ const response = await transceive([CMD_READ_SIG, 0]);
977
+ if (!response || response.length < 48) {
978
+ throw new Error("READ_FAILED");
979
+ }
980
+ const signatureHex = bytesToHex(response.slice(0, 48));
981
+ await releaseNfcTech();
982
+ releaseNfcLock();
983
+ return {
984
+ code: NfcStatusCode.READ_SIG_SUCCESS,
985
+ success: true,
986
+ data: { signature: signatureHex }
987
+ };
988
+ } catch (error) {
989
+ await releaseNfcTech();
990
+ releaseNfcLock();
991
+ const code = errorToCode(error);
992
+ return { code, success: false };
993
+ }
994
+ } catch (error) {
995
+ releaseNfcLock();
996
+ throw error;
997
+ }
998
+ }
999
+
1000
+ // src/writer.ts
1001
+ var import_react_native3 = require("react-native");
1002
+ var bip392 = __toESM(require("bip39"));
1003
+ async function readPage(page) {
1004
+ return await transceive([CMD_READ, page]);
1005
+ }
1006
+ async function writePage(page, data) {
1007
+ if (data.length !== 4) throw new Error("INVALID_CARD_DATA");
1008
+ await transceive([CMD_WRITE, page, ...data]);
1009
+ }
1010
+ async function writeUserMemory(data) {
1011
+ const buffer = new Uint8Array(MNEMONIC_MEMORY_SIZE);
1012
+ buffer.set(data.slice(0, MNEMONIC_MEMORY_SIZE), 0);
1013
+ const totalPages = MNEMONIC_PAGE_END - USER_PAGE_START + 1;
1014
+ for (let i = 0; i < totalPages; i++) {
1015
+ const page = USER_PAGE_START + i;
1016
+ const offset = i * PAGE_SIZE;
1017
+ const pageData = Array.from(buffer.slice(offset, offset + PAGE_SIZE));
1018
+ await writePage(page, pageData);
1019
+ }
1020
+ }
1021
+ async function writeAesKey(key) {
1022
+ for (let i = 0; i < 4; i++) {
1023
+ const page = PAGE_AES_KEY0_START + i;
1024
+ const off = (3 - i) * 4;
1025
+ const pageData = [key[off + 3], key[off + 2], key[off + 1], key[off + 0]];
1026
+ await writePage(page, pageData);
1027
+ if (import_react_native3.Platform.OS === "ios" && i < 3) await new Promise((r) => setTimeout(r, DELAY.IOS_KEY_WRITE));
1028
+ }
1029
+ }
1030
+ async function writeNicknameToCard(nickname) {
1031
+ const safeNickname = nickname || "";
1032
+ const encoder = new TextEncoder();
1033
+ let nicknameBytes = encoder.encode(String(safeNickname));
1034
+ if (nicknameBytes.length > USER_NICKNAME_MAX_LENGTH) {
1035
+ nicknameBytes = nicknameBytes.slice(0, USER_NICKNAME_MAX_LENGTH);
1036
+ }
1037
+ const buffer = new Uint8Array(USER_NICKNAME_MAX_LENGTH);
1038
+ buffer.set(nicknameBytes, 0);
1039
+ const totalPages = USER_NICKNAME_PAGE_END - USER_NICKNAME_PAGE_START + 1;
1040
+ for (let i = 0; i < totalPages; i++) {
1041
+ const page = USER_NICKNAME_PAGE_START + i;
1042
+ const offset = i * PAGE_SIZE;
1043
+ await writePage(page, Array.from(buffer.slice(offset, offset + PAGE_SIZE)));
1044
+ if (import_react_native3.Platform.OS === "ios" && i < totalPages - 1) await new Promise((r) => setTimeout(r, DELAY.IOS_NICKNAME_WRITE));
1045
+ }
1046
+ }
1047
+ async function configureAuth() {
1048
+ await writePage(PAGE_CFG0, [SEC_MSG_ACT_MASK, 0, 0, USER_PAGE_START]);
1049
+ await writePage(PAGE_CFG1, [128, 0, 0, 0]);
1050
+ }
1051
+ async function disableAuth() {
1052
+ await writePage(PAGE_CFG0, [SEC_MSG_ACT_MASK, 0, 0, 60]);
1053
+ await writePage(PAGE_CFG1, [0, 0, 0, 0]);
1054
+ }
1055
+ function mnemonicToEntropyWithCRC(mnemonic) {
1056
+ if (!bip392.validateMnemonic(mnemonic)) throw new Error("INVALID_MNEMONIC");
1057
+ const entropyHex = bip392.mnemonicToEntropy(mnemonic);
1058
+ const entropyBytes = hexToBytes(entropyHex);
1059
+ const { typeId, description: typeStr } = getMnemonicTypeByEntropyLength(entropyBytes.length);
1060
+ const dataBlock = new Uint8Array(1 + entropyBytes.length);
1061
+ dataBlock[0] = typeId;
1062
+ dataBlock.set(entropyBytes, 1);
1063
+ const crc16 = calculateCRC16(dataBlock);
1064
+ const result = new Uint8Array(dataBlock.length + 2);
1065
+ result.set(dataBlock, 0);
1066
+ result.set(crc16ToBytes(crc16), dataBlock.length);
1067
+ return { data: result, type: typeStr, entropyHex, rawBytes: bytesToHex(result), crc16 };
1068
+ }
1069
+ async function initializeCard(mnemonic, newPassword, defaultPassword, onCardIdentified) {
1070
+ try {
1071
+ await acquireNfcLock();
1072
+ } catch {
1073
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
1074
+ }
1075
+ let retryCountAfterPreDecrement;
1076
+ try {
1077
+ try {
1078
+ await requestNfcTech();
1079
+ } catch {
1080
+ releaseNfcLock();
1081
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
1082
+ }
1083
+ try {
1084
+ const entropyResult = mnemonicToEntropyWithCRC(mnemonic);
1085
+ try {
1086
+ const n = await decrementRetryCountInSession();
1087
+ if (typeof n === "number") retryCountAfterPreDecrement = n;
1088
+ } catch (error) {
1089
+ const code = errorToCode(error);
1090
+ if (code === NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
1091
+ await releaseNfcTech();
1092
+ releaseNfcLock();
1093
+ return nfcResultRetryCountExhausted();
1094
+ }
1095
+ }
1096
+ const oldKey = passwordToAesKey(defaultPassword);
1097
+ await authenticate(oldKey);
1098
+ try {
1099
+ await writeRetryCountInSession(DEFAULT_PIN_RETRY_COUNT);
1100
+ retryCountAfterPreDecrement = DEFAULT_PIN_RETRY_COUNT;
1101
+ } catch {
1102
+ }
1103
+ onCardIdentified?.();
1104
+ await writeUserMemory(entropyResult.data);
1105
+ const newKey = passwordToAesKey(newPassword);
1106
+ await disableAuth();
1107
+ await writeAesKey(newKey);
1108
+ await configureAuth();
1109
+ await releaseNfcTech();
1110
+ releaseNfcLock();
1111
+ return {
1112
+ code: NfcStatusCode.INIT_SUCCESS,
1113
+ success: true,
1114
+ data: {
1115
+ type: entropyResult.type,
1116
+ entropyHex: entropyResult.entropyHex,
1117
+ rawBytes: entropyResult.rawBytes,
1118
+ crc16: entropyResult.crc16,
1119
+ retryCount: DEFAULT_PIN_RETRY_COUNT
1120
+ }
1121
+ };
1122
+ } catch (error) {
1123
+ await releaseNfcTech();
1124
+ releaseNfcLock();
1125
+ const code = errorToCode(error);
1126
+ return {
1127
+ code,
1128
+ success: false,
1129
+ data: typeof retryCountAfterPreDecrement === "number" ? { retryCount: retryCountAfterPreDecrement } : void 0
1130
+ };
1131
+ }
1132
+ } catch (error) {
1133
+ releaseNfcLock();
1134
+ throw error;
1135
+ }
1136
+ }
1137
+ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified, options) {
1138
+ try {
1139
+ await acquireNfcLock();
1140
+ } catch {
1141
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
1142
+ }
1143
+ let retryCountAfterPreDecrement;
1144
+ try {
1145
+ try {
1146
+ await requestNfcTech();
1147
+ } catch {
1148
+ releaseNfcLock();
1149
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
1150
+ }
1151
+ try {
1152
+ try {
1153
+ const n = await decrementRetryCountInSession();
1154
+ if (typeof n === "number") retryCountAfterPreDecrement = n;
1155
+ } catch (error) {
1156
+ const code = errorToCode(error);
1157
+ if (code === NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
1158
+ await releaseNfcTech();
1159
+ releaseNfcLock();
1160
+ return nfcResultRetryCountExhausted();
1161
+ }
1162
+ }
1163
+ const oldKey = passwordToAesKey(oldPassword);
1164
+ await authenticate(oldKey);
1165
+ try {
1166
+ await writeRetryCountInSession(DEFAULT_PIN_RETRY_COUNT);
1167
+ retryCountAfterPreDecrement = DEFAULT_PIN_RETRY_COUNT;
1168
+ } catch {
1169
+ }
1170
+ onCardIdentified?.();
1171
+ if (options?.precheckExistingMnemonic) {
1172
+ try {
1173
+ const data = await readUserMemory();
1174
+ const decoded = validateMnemonicPayload(data);
1175
+ await releaseNfcTech();
1176
+ releaseNfcLock();
1177
+ return {
1178
+ code: NfcStatusCode.PRECHECK_HAS_BACKUP,
1179
+ success: true,
1180
+ data: { type: decoded.type, retryCount: DEFAULT_PIN_RETRY_COUNT }
1181
+ };
1182
+ } catch (e) {
1183
+ const c = errorToCode(e);
1184
+ if (c === NfcStatusCode.CHECK_EMPTY || c === NfcStatusCode.CRC16_CHECK_FAILED || c === NfcStatusCode.INVALID_CARD_DATA) {
1185
+ } else {
1186
+ await releaseNfcTech();
1187
+ releaseNfcLock();
1188
+ return { code: c, success: false };
1189
+ }
1190
+ }
1191
+ }
1192
+ const entropyResult = mnemonicToEntropyWithCRC(newMnemonic);
1193
+ await writeUserMemory(entropyResult.data);
1194
+ await disableAuth();
1195
+ const newKey = passwordToAesKey(newPassword);
1196
+ const aesKeyHex = bytesToHex(newKey);
1197
+ await writeAesKey(newKey);
1198
+ await configureAuth();
1199
+ await releaseNfcTech();
1200
+ releaseNfcLock();
1201
+ return {
1202
+ code: NfcStatusCode.WRITE_SUCCESS,
1203
+ success: true,
1204
+ data: {
1205
+ type: entropyResult.type,
1206
+ entropyHex: entropyResult.entropyHex,
1207
+ rawBytes: entropyResult.rawBytes,
1208
+ aesKeyHex,
1209
+ retryCount: DEFAULT_PIN_RETRY_COUNT
1210
+ }
1211
+ };
1212
+ } catch (error) {
1213
+ const isTransceiveErr = error?.message?.includes("transceive fail") || error?.message?.includes("TagLost");
1214
+ await releaseNfcTech(isTransceiveErr);
1215
+ releaseNfcLock();
1216
+ const code = errorToCode(error);
1217
+ return {
1218
+ code,
1219
+ success: false,
1220
+ data: typeof retryCountAfterPreDecrement === "number" ? { retryCount: retryCountAfterPreDecrement } : void 0
1221
+ };
1222
+ }
1223
+ } catch (error) {
1224
+ releaseNfcLock();
1225
+ throw error;
1226
+ }
1227
+ }
1228
+ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
1229
+ try {
1230
+ await acquireNfcLock();
1231
+ } catch {
1232
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
1233
+ }
1234
+ let retryCountAfterPreDecrement;
1235
+ try {
1236
+ try {
1237
+ await requestNfcTech();
1238
+ } catch {
1239
+ releaseNfcLock();
1240
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
1241
+ }
1242
+ try {
1243
+ try {
1244
+ const n = await decrementRetryCountInSession();
1245
+ if (typeof n === "number") retryCountAfterPreDecrement = n;
1246
+ } catch (error) {
1247
+ const code = errorToCode(error);
1248
+ if (code === NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
1249
+ await releaseNfcTech();
1250
+ releaseNfcLock();
1251
+ return nfcResultRetryCountExhausted();
1252
+ }
1253
+ }
1254
+ const oldKey = passwordToAesKey(oldPassword);
1255
+ await authenticate(oldKey);
1256
+ try {
1257
+ await writeRetryCountInSession(DEFAULT_PIN_RETRY_COUNT);
1258
+ retryCountAfterPreDecrement = DEFAULT_PIN_RETRY_COUNT;
1259
+ } catch {
1260
+ }
1261
+ onCardIdentified?.();
1262
+ await disableAuth();
1263
+ const newKey = passwordToAesKey(newPassword);
1264
+ const aesKeyHex = bytesToHex(newKey);
1265
+ await writeAesKey(newKey);
1266
+ await configureAuth();
1267
+ await releaseNfcTech();
1268
+ releaseNfcLock();
1269
+ return {
1270
+ code: NfcStatusCode.UPDATE_PASSWORD_SUCCESS,
1271
+ success: true,
1272
+ data: { aesKeyHex, retryCount: DEFAULT_PIN_RETRY_COUNT }
1273
+ };
1274
+ } catch (error) {
1275
+ await releaseNfcTech();
1276
+ releaseNfcLock();
1277
+ const code = errorToCode(error);
1278
+ return {
1279
+ code,
1280
+ success: false,
1281
+ data: typeof retryCountAfterPreDecrement === "number" ? { retryCount: retryCountAfterPreDecrement } : void 0
1282
+ };
1283
+ }
1284
+ } catch (error) {
1285
+ releaseNfcLock();
1286
+ throw error;
1287
+ }
1288
+ }
1289
+ async function writeUserNickname(password, nickname, onCardIdentified) {
1290
+ try {
1291
+ await acquireNfcLock();
1292
+ } catch {
1293
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
1294
+ }
1295
+ try {
1296
+ try {
1297
+ await requestNfcTech();
1298
+ } catch {
1299
+ releaseNfcLock();
1300
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
1301
+ }
1302
+ try {
1303
+ try {
1304
+ await decrementRetryCountInSession();
1305
+ } catch (error) {
1306
+ const code = errorToCode(error);
1307
+ if (code === NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
1308
+ await releaseNfcTech();
1309
+ releaseNfcLock();
1310
+ return nfcResultRetryCountExhausted();
1311
+ }
1312
+ }
1313
+ const aesKey = passwordToAesKey(password);
1314
+ await authenticate(aesKey);
1315
+ try {
1316
+ await writeRetryCountInSession(DEFAULT_PIN_RETRY_COUNT);
1317
+ } catch {
1318
+ }
1319
+ onCardIdentified?.();
1320
+ await disableAuth();
1321
+ await writeNicknameToCard(nickname);
1322
+ await configureAuth();
1323
+ await releaseNfcTech();
1324
+ releaseNfcLock();
1325
+ return {
1326
+ code: NfcStatusCode.WRITE_NICKNAME_SUCCESS,
1327
+ success: true
1328
+ };
1329
+ } catch (error) {
1330
+ await releaseNfcTech();
1331
+ releaseNfcLock();
1332
+ const code = errorToCode(error);
1333
+ return { code, success: false };
1334
+ }
1335
+ } catch (error) {
1336
+ releaseNfcLock();
1337
+ throw error;
1338
+ }
1339
+ }
1340
+ async function resetCard(password, newPassword, onCardIdentified) {
1341
+ try {
1342
+ await acquireNfcLock();
1343
+ } catch {
1344
+ return { code: NfcStatusCode.UNKNOWN_ERROR, success: false };
1345
+ }
1346
+ let retryCountAfterPreDecrement;
1347
+ try {
1348
+ try {
1349
+ await requestNfcTech();
1350
+ } catch {
1351
+ releaseNfcLock();
1352
+ return { code: NfcStatusCode.NFC_CONNECT_FAILED, success: false };
1353
+ }
1354
+ try {
1355
+ if (password) {
1356
+ try {
1357
+ const n = await decrementRetryCountInSession();
1358
+ if (typeof n === "number") retryCountAfterPreDecrement = n;
1359
+ } catch (error) {
1360
+ const code = errorToCode(error);
1361
+ if (code === NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
1362
+ await releaseNfcTech();
1363
+ releaseNfcLock();
1364
+ return nfcResultRetryCountExhausted();
1365
+ }
1366
+ }
1367
+ const aesKey = passwordToAesKey(password);
1368
+ try {
1369
+ await authenticate(aesKey);
1370
+ try {
1371
+ await writeRetryCountInSession(DEFAULT_PIN_RETRY_COUNT);
1372
+ retryCountAfterPreDecrement = DEFAULT_PIN_RETRY_COUNT;
1373
+ } catch {
1374
+ }
1375
+ } catch (authError) {
1376
+ const msg = authError?.message || "";
1377
+ if (msg.includes("AUTH_WRONG_PASSWORD") || import_react_native3.Platform.OS === "ios" && (msg.includes("AUTH_INVALID_RESPONSE") || !msg.trim())) {
1378
+ throw new Error("AUTH_WRONG_PASSWORD");
1379
+ }
1380
+ throw authError;
1381
+ }
1382
+ onCardIdentified?.();
1383
+ if (import_react_native3.Platform.OS === "ios") {
1384
+ try {
1385
+ await readPage(USER_PAGE_START);
1386
+ } catch {
1387
+ }
1388
+ }
1389
+ } else {
1390
+ try {
1391
+ await authenticate(passwordToAesKey(newPassword));
1392
+ } catch {
1393
+ throw new Error("AUTH_WRONG_PASSWORD");
1394
+ }
1395
+ onCardIdentified?.();
1396
+ }
1397
+ await writeUserMemory(new Uint8Array(MNEMONIC_MEMORY_SIZE));
1398
+ const resetKey = passwordToAesKey(newPassword);
1399
+ await disableAuth();
1400
+ await writeAesKey(resetKey);
1401
+ await configureAuth();
1402
+ await releaseNfcTech();
1403
+ releaseNfcLock();
1404
+ return {
1405
+ code: NfcStatusCode.RESET_SUCCESS,
1406
+ success: true,
1407
+ data: { retryCount: DEFAULT_PIN_RETRY_COUNT }
1408
+ };
1409
+ } catch (error) {
1410
+ await releaseNfcTech();
1411
+ releaseNfcLock();
1412
+ const rawMsg = error.message || "";
1413
+ const isWriteErr = rawMsg.includes("WRITE_FAILED") || rawMsg.includes("transceive");
1414
+ const code = errorToCode(isWriteErr ? new Error("WRITE_FAILED") : error);
1415
+ return {
1416
+ code,
1417
+ success: false,
1418
+ data: typeof retryCountAfterPreDecrement === "number" ? { retryCount: retryCountAfterPreDecrement } : void 0
1419
+ };
1420
+ }
1421
+ } catch (error) {
1422
+ releaseNfcLock();
1423
+ throw error;
1424
+ }
1425
+ }
1426
+ // Annotate the CommonJS export names for ESM import in node:
1427
+ 0 && (module.exports = {
1428
+ DEFAULT_PIN_RETRY_COUNT,
1429
+ NfcStatusCode,
1430
+ cardInfoToJson,
1431
+ checkCard,
1432
+ clearNfcOperationCancelledByCleanup,
1433
+ consumeNfcOperationCancelledByCleanup,
1434
+ getCardVersion,
1435
+ getNfcOperationCancelledByCleanupTimestamp,
1436
+ initializeCard,
1437
+ isNfcOperationLocked,
1438
+ markNfcOperationCancelledByCleanup,
1439
+ nfcResultRetryCountExhausted,
1440
+ readMnemonic,
1441
+ readMnemonicRetryCount,
1442
+ readOriginality,
1443
+ readUserNickname,
1444
+ releaseNfcOperationLock,
1445
+ resetCard,
1446
+ resetRetryCountTo10,
1447
+ updateCard,
1448
+ updatePassword,
1449
+ writeUserNickname
1450
+ });