@cryptag/sdk 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,140 @@
1
+ Required Notice: Copyright 2026 Serdar Tepekule
2
+ https://github.com/serdartpkl/cryptag
3
+
4
+ CrypTag is free for noncommercial use under the PolyForm Noncommercial
5
+ License 1.0.0, reproduced in full below. Commercial use requires a separate
6
+ commercial license — to arrange one, open an issue at the repository above.
7
+
8
+ ================================================================================
9
+
10
+ # PolyForm Noncommercial License 1.0.0
11
+
12
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
13
+
14
+ ## Acceptance
15
+
16
+ In order to get any license under these terms, you must agree
17
+ to them as both strict obligations and conditions to all
18
+ your licenses.
19
+
20
+ ## Copyright License
21
+
22
+ The licensor grants you a copyright license for the
23
+ software to do everything you might do with the software
24
+ that would otherwise infringe the licensor's copyright
25
+ in it for any permitted purpose. However, you may
26
+ only distribute the software according to [Distribution
27
+ License](#distribution-license) and make changes or new works
28
+ based on the software according to [Changes and New Works
29
+ License](#changes-and-new-works-license).
30
+
31
+ ## Distribution License
32
+
33
+ The licensor grants you an additional copyright license
34
+ to distribute copies of the software. Your license
35
+ to distribute covers distributing the software with
36
+ changes and new works permitted by [Changes and New Works
37
+ License](#changes-and-new-works-license).
38
+
39
+ ## Notices
40
+
41
+ You must ensure that anyone who gets a copy of any part of
42
+ the software from you also gets a copy of these terms or the
43
+ URL for them above, as well as copies of any plain-text lines
44
+ beginning with `Required Notice:` that the licensor provided
45
+ with the software. For example:
46
+
47
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
48
+
49
+ ## Changes and New Works License
50
+
51
+ The licensor grants you an additional copyright license to
52
+ make changes and new works based on the software for any
53
+ permitted purpose.
54
+
55
+ ## Patent License
56
+
57
+ The licensor grants you a patent license for the software that
58
+ covers patent claims the licensor can license, or becomes able
59
+ to license, that you would infringe by using the software.
60
+
61
+ ## Noncommercial Purposes
62
+
63
+ Any noncommercial purpose is a permitted purpose.
64
+
65
+ ## Personal Uses
66
+
67
+ Personal use for research, experiment, and testing for
68
+ the benefit of public knowledge, personal study, private
69
+ entertainment, hobby projects, amateur pursuits, or religious
70
+ observance, without any anticipated commercial application,
71
+ is use for a permitted purpose.
72
+
73
+ ## Noncommercial Organizations
74
+
75
+ Use by any charitable organization, educational institution,
76
+ public research organization, public safety or health
77
+ organization, environmental protection organization,
78
+ or government institution is use for a permitted purpose
79
+ regardless of the source of funding or obligations resulting
80
+ from the funding.
81
+
82
+ ## Fair Use
83
+
84
+ You may have "fair use" rights for the software under the
85
+ law. These terms do not limit them.
86
+
87
+ ## No Other Rights
88
+
89
+ These terms do not allow you to sublicense or transfer any of
90
+ your licenses to anyone else, or prevent the licensor from
91
+ granting licenses to anyone else. These terms do not imply
92
+ any other licenses.
93
+
94
+ ## Patent Defense
95
+
96
+ If you make any written claim that the software infringes or
97
+ contributes to infringement of any patent, your patent license
98
+ for the software granted under these terms ends immediately. If
99
+ your company makes such a claim, your patent license ends
100
+ immediately for work on behalf of your company.
101
+
102
+ ## Violations
103
+
104
+ The first time you are notified in writing that you have
105
+ violated any of these terms, or done anything with the software
106
+ not covered by your licenses, your licenses can nonetheless
107
+ continue if you come into full compliance with these terms,
108
+ and take practical steps to correct past violations, within
109
+ 32 days of receiving notice. Otherwise, all your licenses
110
+ end immediately.
111
+
112
+ ## No Liability
113
+
114
+ ***As far as the law allows, the software comes as is, without
115
+ any warranty or condition, and the licensor will not be liable
116
+ to you for any damages arising out of these terms or the use
117
+ or nature of the software, under any kind of legal claim.***
118
+
119
+ ## Definitions
120
+
121
+ The **licensor** is the individual or entity offering these
122
+ terms, and the **software** is the software the licensor makes
123
+ available under these terms.
124
+
125
+ **You** refers to the individual or entity agreeing to these
126
+ terms.
127
+
128
+ **Your company** is any legal entity, sole proprietorship,
129
+ or other kind of organization that you work for, plus all
130
+ organizations that have control over, are under the control of,
131
+ or are under common control with that organization. **Control**
132
+ means ownership of substantially all the assets of an entity,
133
+ or the power to direct its management and policies by vote,
134
+ contract, or otherwise. Control can be direct or indirect.
135
+
136
+ **Your licenses** are all the licenses granted to you for the
137
+ software under these terms.
138
+
139
+ **Use** means anything you do with the software requiring one
140
+ of your licenses.
package/README.md ADDED
@@ -0,0 +1,262 @@
1
+ <p align="center">
2
+ <img src="cryptag-logo.png" alt="CrypTag" width="320">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/@cryptag/sdk"><img src="https://img.shields.io/npm/v/@cryptag/sdk.svg" alt="npm version"></a>
7
+ <a href="https://www.npmjs.com/package/@cryptag/sdk"><img src="https://img.shields.io/node/v/@cryptag/sdk.svg" alt="node version"></a>
8
+ <a href="https://github.com/serdartpkl/cryptag/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-PolyForm%20Noncommercial-blue.svg" alt="PolyForm Noncommercial license"></a>
9
+ </p>
10
+
11
+ ---
12
+
13
+ **CrypTag** is a complete NTAG424 DNA toolkit: one half **programs** tags over a contactless
14
+ reader, the other **verifies** the tap output they produce — sharing the same cryptographic
15
+ core and a single, structured error model.
16
+
17
+ - **Encoder** (`@cryptag/encoder`) — talk to a tag over a PC/SC reader: discover, authenticate
18
+ (EV2 & LRP), read/write files, configure SDM profiles, and manage keys (AN10922 diversification).
19
+ - **Decoder** (`@cryptag/decoder`) — verify the SDM URL/token a tag produces (plain, encrypted and
20
+ full). No hardware required.
21
+
22
+ `@cryptag/sdk` is the umbrella package that re-exports both. `@cryptag/crypto` (primitives) and
23
+ `@cryptag/common` (error model and validation) are shared internals.
24
+
25
+ ```js
26
+ import { CrypTagEncoder, CrypTagDecoder } from '@cryptag/sdk';
27
+ ```
28
+
29
+ > 📖 You can find detailed documentation on [cryptag.io](https://cryptag.io).
30
+
31
+ ## Packages
32
+
33
+ | Package | Role |
34
+ |---|---|
35
+ | `@cryptag/sdk` | Umbrella — re-exports `CrypTagEncoder` + `CrypTagDecoder` |
36
+ | `@cryptag/encoder` | Tag programming over PC/SC |
37
+ | `@cryptag/decoder` | SDM URL/token verification |
38
+ | `@cryptag/crypto` | Shared AES/LRP/CMAC/diversification primitives |
39
+ | `@cryptag/common` | Shared error model and argument validation |
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ npm install @cryptag/sdk
45
+ # …or just one half:
46
+ npm install @cryptag/encoder
47
+ npm install @cryptag/decoder
48
+ ```
49
+
50
+ > The encoder needs a **PC/SC contactless reader**. Its native binding (`nfc-pcsc` →
51
+ > `@pokusew/pcsclite`) is compiled on install. If you switch Node versions, rebuild it with
52
+ > `npm run rebuild`. For **Electron**, build it for the Electron ABI with `npm run rebuild:electron`
53
+ > (defaults to Electron 26; pass a version with `npm run rebuild:electron -- 28.2.0`).
54
+
55
+ ## Quick Start
56
+
57
+ A tag carries its SDM data in one of two NDEF records: a **URL** (the chip emits a
58
+ browser-openable link with the `picc_data`/`enc`/`cmac` query params) or a **text**
59
+ record (a JSON blob your own app reads). The cryptography is identical — only the container
60
+ differs, and `readNDEF` parses both into the same `sdmParams.params` (including the
61
+ `cmacSeparator` the tag needs), so the decode call is the same for both.
62
+
63
+ ### URL Record
64
+
65
+ Program a tag, read back the URL it emits, then verify that tap on your backend:
66
+
67
+ ```js
68
+ import { CrypTagEncoder, CrypTagDecoder } from '@cryptag/sdk';
69
+
70
+ const encoder = new CrypTagEncoder();
71
+ await encoder.connect();
72
+
73
+ // 'full' = encrypted PICC data + encrypted file data + CMAC. encodeTag discovers
74
+ // the tag and authenticates with key 0 internally.
75
+ await encoder.encodeTag('full', {
76
+ ndefType: 'url', // 'url' or 'text'
77
+ url: 'https://cryptag.app/verify',
78
+ fileData: 'HELLO-FROM-CRYPTAG',// plain string encrypted into every tap
79
+ encSize: 128, // enc field length in hex chars (32/64/128)
80
+ counterLimit: 3000, // SDM tap counter stops after 3000 taps
81
+ resetCounter: true, // reset the counter to zero before encoding
82
+ enableTTStatus: false, // TagTamper status mirroring (TagTamper variant only)
83
+ compressed: false, // shorter URLs without parameter names
84
+ sdmSettings: null, // advanced SDM access-right override
85
+ });
86
+
87
+ // encodeTag already re-discovered the new SDM config, so readNDEF sees it: the
88
+ // chip fills the placeholders live and readNDEF parses them into separate fields.
89
+ const ndef = await encoder.readNDEF();
90
+ await encoder.disconnect();
91
+
92
+ const sdm = ndef.data.sdmParams.params; // { picc_data, enc, cmac, cmacSeparator } — ready for decodeFull
93
+
94
+ // Verify and decode on your backend (no hardware).
95
+ const decoder = new CrypTagDecoder({
96
+ keyList: { // keys must match the tag (factory = all-zero)
97
+ '2': { masterKey: '00000000000000000000000000000000', diversify: false },
98
+ '3': { masterKey: '00000000000000000000000000000000', diversify: false },
99
+ },
100
+ sdmSettings: { sdmMetaRead: 2, sdmFileRead: 3 },
101
+ });
102
+
103
+ const out = decoder.decodeFull(sdm); // sdm.cmacSeparator wires the MAC input automatically
104
+ if (out.success && out.data.cmacValid) {
105
+ console.log('authentic tap →', { uid: out.data.uid, counter: out.data.counter, file: out.data.fileData });
106
+ }
107
+ ```
108
+
109
+ `readNDEF` also hands you the full link at `ndef.data.url`.
110
+
111
+ ### Text Record
112
+
113
+ Same `full` profile, but written as a JSON text record instead of a URL — drop `url`, set
114
+ `ndefType: 'text'`. SDM builds the JSON itself, and the decode side is identical:
115
+
116
+ ```js
117
+ import { CrypTagEncoder, CrypTagDecoder } from '@cryptag/sdk';
118
+
119
+ const encoder = new CrypTagEncoder();
120
+ await encoder.connect();
121
+
122
+ // 'full' = encrypted PICC data + encrypted file data + CMAC. encodeTag discovers
123
+ // the tag and authenticates with key 0 internally.
124
+ await encoder.encodeTag('full', {
125
+ ndefType: 'text', // JSON text record instead of a URL
126
+ fileData: 'HELLO-FROM-CRYPTAG',// plain string encrypted into every tap
127
+ encSize: 128, // enc field length in hex chars (32/64/128)
128
+ counterLimit: 3000, // SDM tap counter stops after 3000 taps
129
+ resetCounter: true, // reset the counter to zero before encoding
130
+ enableTTStatus: false, // TagTamper status mirroring (TagTamper variant only)
131
+ compressed: false, // shorter token instead of named fields
132
+ sdmSettings: null, // advanced SDM access-right override
133
+ });
134
+
135
+ // encodeTag already re-discovered the new SDM config, so readNDEF sees it: the
136
+ // chip fills the placeholders live and readNDEF parses them into separate fields.
137
+ const ndef = await encoder.readNDEF();
138
+ await encoder.disconnect();
139
+
140
+ const sdm = ndef.data.sdmParams.params; // same shape (incl. cmacSeparator) as the URL case
141
+
142
+ // Verify and decode on your backend (no hardware).
143
+ const decoder = new CrypTagDecoder({
144
+ keyList: { // keys must match the tag (factory = all-zero)
145
+ '2': { masterKey: '00000000000000000000000000000000', diversify: false },
146
+ '3': { masterKey: '00000000000000000000000000000000', diversify: false },
147
+ },
148
+ sdmSettings: { sdmMetaRead: 2, sdmFileRead: 3 },
149
+ });
150
+
151
+ const out = decoder.decodeFull(sdm); // identical to the URL example
152
+ if (out.success && out.data.cmacValid) {
153
+ console.log('authentic tap →', { uid: out.data.uid, counter: out.data.counter, file: out.data.fileData });
154
+ }
155
+ ```
156
+
157
+ `readNDEF` hands you the raw record at `ndef.data.text` (e.g. `{"picc_data":"…","enc":"…","cmac":"…"}`).
158
+
159
+ ## Commands
160
+
161
+ NTAG424 DNA commands implemented, by communication mode. The full per-method API (encoder +
162
+ decoder), with examples, lives in the **[documentation](https://cryptag.io)**.
163
+
164
+ **Authentication (4 Commands)**
165
+ - EV2First
166
+ - LRPFirst
167
+ - EV2NonFirst
168
+ - LRPNonFirst
169
+
170
+ **Plain Mode (10 Commands)**
171
+ - GetFileSettings
172
+ - ChangeFileSettings
173
+ - GetFileCounters
174
+ - GetVersion
175
+ - ISOReadBinary
176
+ - ISOSelectFile
177
+ - ISOUpdateBinary
178
+ - ReadData
179
+ - WriteData
180
+ - ReadSig
181
+
182
+ **MAC Mode (7 Commands)**
183
+ - GetFileSettings
184
+ - ChangeFileSettings
185
+ - GetFileCounters
186
+ - GetKeyVersion
187
+ - GetVersion
188
+ - ReadData
189
+ - WriteData
190
+
191
+ **Full Mode (11 Commands)**
192
+ - ChangeKey
193
+ - ChangeKey0
194
+ - ChangeFileSettings
195
+ - GetCardUID
196
+ - GetFileCounters
197
+ - ReadData
198
+ - WriteData
199
+ - ReadSig
200
+ - SetConfiguration (FailedCtr)
201
+ - SetConfiguration (RandomID)
202
+ - SetConfiguration (LRP)
203
+
204
+ **Total: 37 commands** (some appear in multiple communication modes with distinct implementations).
205
+
206
+ ## Results and Errors
207
+
208
+ **Both halves** return the same result shape (`duration` in ms is added on encoder tag operations):
209
+
210
+ ```js
211
+ { success: true, data: { /* method-specific */ }, duration: 12 }
212
+ { success: false, error: { code, message, details? } }
213
+ ```
214
+
215
+ For the **decoder**, `data` holds `{ cmacValid, uid, counter, … }`. `success: true` means the decode
216
+ ran — **authenticity is `data.cmacValid`**, so always check it (a tap can decode cleanly yet still fail
217
+ CMAC verification). A hard failure (bad input or a decode error) returns `{ success: false, error }`.
218
+
219
+ `error.code` is shared across both halves:
220
+
221
+ | Code | Category | When it happens |
222
+ |---|---|---|
223
+ | `E100` | Connection | Reader, card, or transport problem (no reader, no card, discovery failed) |
224
+ | `E200` | Authentication | Wrong key, MAC mismatch, permission denied, or auth temporarily blocked |
225
+ | `E300` | Command | A tag command did not complete (select, read/write, change key/settings) |
226
+ | `E400` | Validation | Bad argument or configuration (encoder + decoder) |
227
+ | `E500` | Unknown | Unmapped/unexpected error |
228
+ | `E600` | Decoding | A decode could not be completed (decoder: malformed data or a key/config problem) |
229
+
230
+ When a failure originates at the tag, its ISO/NTAG424 status word (e.g. `911E`, `919D`, `91AE`) is
231
+ translated into one of the categories above with a descriptive `message`. A result's `error` is always
232
+ a **plain object** `{ code, message, details? }` — no stack, no class — so it logs and `JSON.stringify`s
233
+ identically. (Internally the SDK throws a `CrypTagError`, but that never leaks into a result.)
234
+
235
+ ## How They Fit Together
236
+
237
+ `encodeTag()` configures a tag so that, on each tap, it emits a URL containing SDM placeholders
238
+ (`picc_data`, `enc`, `cmac`, …). Your backend parses those query parameters and passes them to the
239
+ matching decoder method (`decodePlain`/`decodeEncrypted`/`decodeFull`), which verifies the CMAC
240
+ and returns the UID, the tap counter, and — for the full profile — the decrypted file data.
241
+
242
+ ## Requirements
243
+
244
+ - Node.js ≥ 14 (ES modules).
245
+ - Encoder only: a PC/SC contactless reader (e.g. ACR122U) and an NTAG424 DNA tag.
246
+
247
+ ## Development
248
+
249
+ This is an npm-workspaces monorepo. From the repo root:
250
+
251
+ ```bash
252
+ npm install # install + link all workspaces
253
+ npm run types # type-check/emit .d.ts for all packages
254
+ npm run rebuild # rebuild the encoder's native binding for Node
255
+ npm run rebuild:electron # …or for the Electron ABI
256
+ ```
257
+
258
+ ## License
259
+
260
+ [PolyForm Noncommercial License 1.0.0](LICENSE) © Serdar Tepekule.
261
+
262
+ Free for noncommercial use. **Commercial use requires a separate license** — open an issue on [GitHub](https://github.com/serdartpkl/cryptag) to arrange one.
package/cryptag.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @cryptag/sdk — umbrella package re-exporting the encoder and decoder.
3
+ *
4
+ * import { CrypTagEncoder, CrypTagDecoder } from '@cryptag/sdk';
5
+ *
6
+ * @cryptag/crypto is an internal dependency of the two and is intentionally
7
+ * NOT re-exported here.
8
+ */
9
+
10
+ import { CrypTagEncoder } from '@cryptag/encoder';
11
+ import { CrypTagDecoder } from '@cryptag/decoder';
12
+
13
+ export { CrypTagEncoder, CrypTagDecoder };
14
+
15
+ /** @hidden */
16
+ export default { CrypTagEncoder, CrypTagDecoder };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@cryptag/sdk",
3
+ "version": "2.0.2",
4
+ "description": "CrypTag — NTAG424 DNA Toolkit",
5
+ "type": "module",
6
+ "main": "cryptag.js",
7
+ "types": "types/cryptag.d.ts",
8
+ "exports": {
9
+ ".": "./cryptag.js"
10
+ },
11
+ "scripts": {
12
+ "rebuild": "node scripts/rebuild.mjs",
13
+ "rebuild:electron": "node scripts/rebuild.mjs -e",
14
+ "types": "tsc -p packages/common/tsconfig.json && tsc -p packages/crypto/tsconfig.json && tsc -p packages/decoder/tsconfig.json && tsc -p packages/encoder/tsconfig.json && tsc -p tsconfig.json",
15
+ "docs": "npm run build --prefix docs",
16
+ "prepack": "node -e \"require('fs').rmSync('types',{recursive:true,force:true})\" && tsc -p tsconfig.json"
17
+ },
18
+ "workspaces": [
19
+ "packages/common",
20
+ "packages/crypto",
21
+ "packages/encoder",
22
+ "packages/decoder"
23
+ ],
24
+ "keywords": [
25
+ "nfc",
26
+ "ntag424",
27
+ "sdm",
28
+ "lrp",
29
+ "ev2"
30
+ ],
31
+ "author": "Serdar Tepekule",
32
+ "license": "PolyForm-Noncommercial-1.0.0",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/serdartpkl/cryptag.git"
36
+ },
37
+ "homepage": "https://cryptag.io",
38
+ "bugs": "https://github.com/serdartpkl/cryptag/issues",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "engines": {
43
+ "node": ">=14.0.0"
44
+ },
45
+ "files": [
46
+ "cryptag.js",
47
+ "types/"
48
+ ],
49
+ "dependencies": {
50
+ "@cryptag/decoder": "2.0.2",
51
+ "@cryptag/encoder": "2.0.2"
52
+ },
53
+ "devDependencies": {
54
+ "typescript": "^6.0.3"
55
+ }
56
+ }
@@ -0,0 +1,8 @@
1
+ declare namespace _default {
2
+ export { CrypTagEncoder };
3
+ export { CrypTagDecoder };
4
+ }
5
+ export default _default;
6
+ import { CrypTagEncoder } from '@cryptag/encoder';
7
+ import { CrypTagDecoder } from '@cryptag/decoder';
8
+ export { CrypTagEncoder, CrypTagDecoder };