@pqc-sdk/core 0.1.1 → 0.2.0
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 +41 -36
- package/dist/index.cjs +27 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -48
- package/dist/index.d.ts +48 -48
- package/dist/index.js +27 -20
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
[](https://bundlephobia.com/package/@pqc-sdk/core)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
**ML-KEM-768** (FIPS 203) + AES-256-GCM
|
|
10
|
-
(FIPS 204)
|
|
8
|
+
Post-quantum cryptography for JS/TS with safe defaults and zero configuration.
|
|
9
|
+
**ML-KEM-768** (FIPS 203) + AES-256-GCM for hybrid encryption, **ML-DSA-65**
|
|
10
|
+
(FIPS 204) for signatures. Validated against the official NIST ACVP test
|
|
11
|
+
vectors.
|
|
11
12
|
|
|
12
13
|
```bash
|
|
13
14
|
npm install @pqc-sdk/core
|
|
@@ -17,57 +18,61 @@ npm install @pqc-sdk/core
|
|
|
17
18
|
import { pqc } from '@pqc-sdk/core';
|
|
18
19
|
|
|
19
20
|
const pair = await pqc.keys.generate();
|
|
20
|
-
const ciphertext = await pqc.encrypt('
|
|
21
|
+
const ciphertext = await pqc.encrypt('secret', pair.publicKey);
|
|
21
22
|
const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);
|
|
22
23
|
|
|
23
24
|
const signer = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });
|
|
24
|
-
const signature = await pqc.sign('
|
|
25
|
-
const valid = await pqc.verify('
|
|
25
|
+
const signature = await pqc.sign('document', signer.secretKey);
|
|
26
|
+
const valid = await pqc.verify('document', signature, signer.publicKey);
|
|
26
27
|
|
|
27
|
-
console.log(new TextDecoder().decode(plaintext), valid); // "
|
|
28
|
+
console.log(new TextDecoder().decode(plaintext), valid); // "secret" true
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
##
|
|
31
|
+
## Compatibility
|
|
31
32
|
|
|
32
|
-
| Runtime |
|
|
33
|
-
| ------------------ | ------- |
|
|
34
|
-
| Node 20+ | ✅ | ESM
|
|
35
|
-
| Cloudflare Workers | ✅ |
|
|
36
|
-
| Deno 2+ | ✅ | `npm:@pqc-sdk/core`
|
|
37
|
-
| Bun | ✅ |
|
|
38
|
-
| React Native | ✅ |
|
|
39
|
-
|
|
|
33
|
+
| Runtime | Support | Notes |
|
|
34
|
+
| ------------------ | ------- | ----------------------------------------- |
|
|
35
|
+
| Node 20+ | ✅ | ESM and CJS |
|
|
36
|
+
| Cloudflare Workers | ✅ | No `nodejs_compat`; ~20 KB gzip in bundle |
|
|
37
|
+
| Deno 2+ | ✅ | `npm:@pqc-sdk/core` |
|
|
38
|
+
| Bun | ✅ | |
|
|
39
|
+
| React Native | ✅ | Requires `react-native-get-random-values` |
|
|
40
|
+
| Browsers | ✅ | Any ES2022 target with WebCrypto |
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
No WASM or native addons: pure TypeScript on top of
|
|
42
43
|
[@noble/post-quantum](https://github.com/paulmillr/noble-post-quantum).
|
|
43
44
|
|
|
44
45
|
## Benchmarks
|
|
45
46
|
|
|
46
|
-
Node 24, x86_64 (
|
|
47
|
+
Node 24, x86_64 (1 KB messages):
|
|
47
48
|
|
|
48
|
-
|
|
|
49
|
+
| Operation | Time | Throughput |
|
|
49
50
|
| ----------------- | ---------- | ---------- |
|
|
50
|
-
| keygen ML-KEM-768 | 1
|
|
51
|
-
| encrypt | 1
|
|
52
|
-
| decrypt | 2
|
|
53
|
-
| keygen ML-DSA-65 | 4
|
|
54
|
-
| sign | 20
|
|
55
|
-
| verify | 5
|
|
51
|
+
| keygen ML-KEM-768 | 1.3 ms/op | 768 ops/s |
|
|
52
|
+
| encrypt | 1.7 ms/op | 585 ops/s |
|
|
53
|
+
| decrypt | 2.3 ms/op | 440 ops/s |
|
|
54
|
+
| keygen ML-DSA-65 | 4.8 ms/op | 210 ops/s |
|
|
55
|
+
| sign | 20.2 ms/op | 50 ops/s |
|
|
56
|
+
| verify | 5.1 ms/op | 195 ops/s |
|
|
56
57
|
|
|
57
|
-
##
|
|
58
|
+
## Documentation
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
- [Cifrado híbrido KEM+AES, explicado](https://github.com/jeloercc/pqc-sdk/tree/main/apps/docs/guide/hybrid-encryption.md)
|
|
61
|
-
- [Compatibilidad detallada](https://github.com/jeloercc/pqc-sdk/tree/main/docs/compatibility.md)
|
|
60
|
+
Full documentation at **[jeloercc.github.io/pqc-sdk](https://jeloercc.github.io/pqc-sdk/)**.
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
- [5-minute quickstart](https://jeloercc.github.io/pqc-sdk/guide/quickstart)
|
|
63
|
+
- [Hybrid KEM+AES encryption, explained](https://jeloercc.github.io/pqc-sdk/guide/hybrid-encryption)
|
|
64
|
+
- [Detailed compatibility](https://jeloercc.github.io/pqc-sdk/compatibility)
|
|
65
|
+
- [API reference](https://jeloercc.github.io/pqc-sdk/api/)
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
y AES-GCM de `@noble/ciphers`.
|
|
67
|
-
- `@noble/post-quantum` no tiene aún auditoría independiente (self-audit
|
|
68
|
-
04/2026). Como todo JS, sin garantías constant-time estrictas.
|
|
69
|
-
- Reportes de seguridad: abrí un issue privado o escribí al maintainer.
|
|
67
|
+
## Security
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
- We never implement primitives: ML-KEM/ML-DSA come from
|
|
70
|
+
`@noble/post-quantum` and AES-GCM from `@noble/ciphers`.
|
|
71
|
+
- `@noble/post-quantum` has no independent audit yet (self-audit 04/2026).
|
|
72
|
+
As with all JS, there are no strict constant-time guarantees.
|
|
73
|
+
- Security reports: see [SECURITY.md](https://github.com/jeloercc/pqc-sdk/blob/main/SECURITY.md) —
|
|
74
|
+
please do not open public issues.
|
|
75
|
+
|
|
76
|
+
## License
|
|
72
77
|
|
|
73
78
|
[MIT](./LICENSE)
|
package/dist/index.cjs
CHANGED
|
@@ -81,7 +81,7 @@ var ALGORITHMS = {
|
|
|
81
81
|
function getAlgorithm(algorithm) {
|
|
82
82
|
const spec = ALGORITHMS[algorithm];
|
|
83
83
|
if (!spec) {
|
|
84
|
-
throw new PqcError("UNSUPPORTED_ALGORITHM", `
|
|
84
|
+
throw new PqcError("UNSUPPORTED_ALGORITHM", `Unsupported algorithm: ${algorithm}`);
|
|
85
85
|
}
|
|
86
86
|
return spec;
|
|
87
87
|
}
|
|
@@ -93,16 +93,16 @@ function requireKey(key, kind, use, operation) {
|
|
|
93
93
|
if (spec.kind !== kind) {
|
|
94
94
|
throw new PqcError(
|
|
95
95
|
"WRONG_ALGORITHM",
|
|
96
|
-
`${operation}
|
|
96
|
+
`${operation} requires an ${kind === "kem" ? "ML-KEM" : "ML-DSA"} key, got ${key.algorithm}`
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
99
|
if (key.use !== use) {
|
|
100
|
-
throw new PqcError("WRONG_KEY_USE", `${operation}
|
|
100
|
+
throw new PqcError("WRONG_KEY_USE", `${operation} requires the ${use} key, got ${key.use}`);
|
|
101
101
|
}
|
|
102
102
|
if (key.bytes.length !== keyLengthFor(spec, use)) {
|
|
103
103
|
throw new PqcError(
|
|
104
104
|
"INVALID_KEY",
|
|
105
|
-
|
|
105
|
+
`${key.algorithm} ${use} key has invalid length: ${key.bytes.length}`
|
|
106
106
|
);
|
|
107
107
|
}
|
|
108
108
|
return spec;
|
|
@@ -121,10 +121,10 @@ async function encrypt(data, publicKey) {
|
|
|
121
121
|
const plaintext = toBytes(data);
|
|
122
122
|
const { cipherText, sharedSecret } = spec.kem.encapsulate(publicKey.bytes);
|
|
123
123
|
const nonce = (0, import_utils.randomBytes)(NONCE_LENGTH);
|
|
124
|
-
const
|
|
124
|
+
const header = new Uint8Array([FORMAT_VERSION, spec.headerId]);
|
|
125
|
+
const sealed = (0, import_aes.gcm)(sharedSecret, nonce, header).encrypt(plaintext);
|
|
125
126
|
const out = new Uint8Array(2 + cipherText.length + nonce.length + sealed.length);
|
|
126
|
-
out
|
|
127
|
-
out[1] = spec.headerId;
|
|
127
|
+
out.set(header, 0);
|
|
128
128
|
out.set(cipherText, 2);
|
|
129
129
|
out.set(nonce, 2 + cipherText.length);
|
|
130
130
|
out.set(sealed, 2 + cipherText.length + nonce.length);
|
|
@@ -134,14 +134,18 @@ async function decrypt(ciphertext, secretKey) {
|
|
|
134
134
|
const spec = requireKey(secretKey, "kem", "secret", "decrypt");
|
|
135
135
|
const minLength = 2 + spec.ciphertextLength + NONCE_LENGTH + GCM_TAG_LENGTH;
|
|
136
136
|
if (ciphertext.length < minLength) {
|
|
137
|
-
throw new PqcError(
|
|
137
|
+
throw new PqcError(
|
|
138
|
+
"INVALID_CIPHERTEXT",
|
|
139
|
+
"Ciphertext is truncated or was not produced by pqc.encrypt"
|
|
140
|
+
);
|
|
138
141
|
}
|
|
139
142
|
if (ciphertext[0] !== FORMAT_VERSION || ciphertext[1] !== spec.headerId) {
|
|
140
143
|
throw new PqcError(
|
|
141
144
|
"INVALID_CIPHERTEXT",
|
|
142
|
-
"
|
|
145
|
+
"Unknown header: the ciphertext does not match this version or algorithm"
|
|
143
146
|
);
|
|
144
147
|
}
|
|
148
|
+
const header = ciphertext.subarray(0, 2);
|
|
145
149
|
const kemCiphertext = ciphertext.subarray(2, 2 + spec.ciphertextLength);
|
|
146
150
|
const nonce = ciphertext.subarray(
|
|
147
151
|
2 + spec.ciphertextLength,
|
|
@@ -150,11 +154,11 @@ async function decrypt(ciphertext, secretKey) {
|
|
|
150
154
|
const sealed = ciphertext.subarray(2 + spec.ciphertextLength + NONCE_LENGTH);
|
|
151
155
|
const sharedSecret = spec.kem.decapsulate(kemCiphertext, secretKey.bytes);
|
|
152
156
|
try {
|
|
153
|
-
return Promise.resolve((0, import_aes.gcm)(sharedSecret, nonce).decrypt(sealed));
|
|
157
|
+
return Promise.resolve((0, import_aes.gcm)(sharedSecret, nonce, header).decrypt(sealed));
|
|
154
158
|
} catch {
|
|
155
159
|
throw new PqcError(
|
|
156
160
|
"DECRYPTION_FAILED",
|
|
157
|
-
"
|
|
161
|
+
"Decryption failed: tampered ciphertext or wrong secret key"
|
|
158
162
|
);
|
|
159
163
|
}
|
|
160
164
|
}
|
|
@@ -181,7 +185,7 @@ function toBase64Url(bytes) {
|
|
|
181
185
|
}
|
|
182
186
|
function fromBase64Url(encoded) {
|
|
183
187
|
if (encoded.length % 4 === 1) {
|
|
184
|
-
throw new TypeError("base64url
|
|
188
|
+
throw new TypeError("Invalid base64url: impossible length");
|
|
185
189
|
}
|
|
186
190
|
const out = new Uint8Array(Math.floor(encoded.length * 3 / 4));
|
|
187
191
|
let outIndex = 0;
|
|
@@ -190,7 +194,7 @@ function fromBase64Url(encoded) {
|
|
|
190
194
|
for (const char of encoded) {
|
|
191
195
|
const value = CHAR_TO_VALUE.get(char);
|
|
192
196
|
if (value === void 0) {
|
|
193
|
-
throw new TypeError(`base64url
|
|
197
|
+
throw new TypeError(`Invalid base64url: character ${JSON.stringify(char)}`);
|
|
194
198
|
}
|
|
195
199
|
buffer = buffer << 6 | value;
|
|
196
200
|
bits += 6;
|
|
@@ -199,6 +203,9 @@ function fromBase64Url(encoded) {
|
|
|
199
203
|
out[outIndex++] = buffer >> bits & 255;
|
|
200
204
|
}
|
|
201
205
|
}
|
|
206
|
+
if ((buffer & (1 << bits) - 1) !== 0) {
|
|
207
|
+
throw new TypeError("Invalid base64url: non-canonical trailing bits");
|
|
208
|
+
}
|
|
202
209
|
return out;
|
|
203
210
|
}
|
|
204
211
|
|
|
@@ -214,7 +221,7 @@ function generateKeyPairFromSeed(algorithm, seed) {
|
|
|
214
221
|
if (seed.length !== spec.seedLength) {
|
|
215
222
|
throw new PqcError(
|
|
216
223
|
"INVALID_KEY",
|
|
217
|
-
|
|
224
|
+
`${algorithm} seed must be ${spec.seedLength} bytes, got ${seed.length}`
|
|
218
225
|
);
|
|
219
226
|
}
|
|
220
227
|
const material = spec.kind === "kem" ? spec.kem.keygen(seed) : spec.signer.keygen(seed);
|
|
@@ -227,7 +234,7 @@ function generateKeyPairFromSeed(algorithm, seed) {
|
|
|
227
234
|
function serialize(key) {
|
|
228
235
|
const spec = getAlgorithm(key.algorithm);
|
|
229
236
|
if (key.bytes.length !== keyLengthFor(spec, key.use)) {
|
|
230
|
-
throw new PqcError("INVALID_KEY",
|
|
237
|
+
throw new PqcError("INVALID_KEY", `${key.algorithm} ${key.use} key has invalid length`);
|
|
231
238
|
}
|
|
232
239
|
return `${SERIAL_PREFIX}.${key.algorithm}.${key.use}.${toBase64Url(key.bytes)}`;
|
|
233
240
|
}
|
|
@@ -236,13 +243,13 @@ function deserialize(serialized) {
|
|
|
236
243
|
if (parts.length !== 4 || parts[0] !== SERIAL_PREFIX) {
|
|
237
244
|
throw new PqcError(
|
|
238
245
|
"INVALID_SERIALIZED_KEY",
|
|
239
|
-
"
|
|
246
|
+
"Expected format: pqcv1.<algorithm>.<use>.<base64url>"
|
|
240
247
|
);
|
|
241
248
|
}
|
|
242
249
|
const [, algorithm, use, encoded] = parts;
|
|
243
250
|
const spec = getAlgorithm(algorithm);
|
|
244
251
|
if (use !== "public" && use !== "secret") {
|
|
245
|
-
throw new PqcError("INVALID_SERIALIZED_KEY", `
|
|
252
|
+
throw new PqcError("INVALID_SERIALIZED_KEY", `Unknown key use: ${use}`);
|
|
246
253
|
}
|
|
247
254
|
let bytes;
|
|
248
255
|
try {
|
|
@@ -250,14 +257,14 @@ function deserialize(serialized) {
|
|
|
250
257
|
} catch (cause) {
|
|
251
258
|
throw new PqcError(
|
|
252
259
|
"INVALID_SERIALIZED_KEY",
|
|
253
|
-
cause instanceof Error ? cause.message : "base64url
|
|
260
|
+
cause instanceof Error ? cause.message : "Invalid base64url"
|
|
254
261
|
);
|
|
255
262
|
}
|
|
256
263
|
const key = { algorithm, use, bytes };
|
|
257
264
|
if (bytes.length !== keyLengthFor(spec, key.use)) {
|
|
258
265
|
throw new PqcError(
|
|
259
266
|
"INVALID_KEY",
|
|
260
|
-
|
|
267
|
+
`${algorithm} ${use} key must be ${keyLengthFor(spec, key.use)} bytes, got ${bytes.length}`
|
|
261
268
|
);
|
|
262
269
|
}
|
|
263
270
|
return key;
|
|
@@ -287,7 +294,7 @@ async function verify(data, signature, publicKey, options) {
|
|
|
287
294
|
}
|
|
288
295
|
|
|
289
296
|
// src/index.ts
|
|
290
|
-
var version = "0.
|
|
297
|
+
var version = "0.2.0";
|
|
291
298
|
var SUPPORTED_ALGORITHMS = ["ml-kem-768", "ml-dsa-65"];
|
|
292
299
|
var pqc = {
|
|
293
300
|
keys: { generate, serialize, deserialize },
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/encrypt.ts","../src/algorithms.ts","../src/errors.ts","../src/keys.ts","../src/base64url.ts","../src/sign.ts"],"sourcesContent":["import { encrypt, decrypt } from './encrypt.js';\nimport { deserialize, generate, serialize } from './keys.js';\nimport { sign, verify } from './sign.js';\n\nexport { PqcError, type PqcErrorCode } from './errors.js';\nexport type { GenerateOptions } from './keys.js';\nexport type {\n Algorithm,\n KemAlgorithm,\n KeyPair,\n KeyUse,\n PqcKey,\n PublicKey,\n SecretKey,\n SignatureAlgorithm,\n SignatureOptions,\n} from './types.js';\nexport { encrypt, decrypt, sign, verify, generate, serialize, deserialize };\n\n// Inyectada en build time desde el package.json (`define` en tsup.config.ts y vitest.config.ts).\ndeclare const __PQC_CORE_VERSION__: string;\n\n/**\n * Versión del SDK.\n *\n * @example\n * ```ts\n * import { version } from '@pqc-sdk/core';\n *\n * console.log(version); // p. ej. \"0.1.0\"\n * ```\n */\nexport const version = __PQC_CORE_VERSION__;\n\n/**\n * Algoritmos PQC implementados (FIPS 203 y FIPS 204).\n *\n * @example\n * ```ts\n * import { SUPPORTED_ALGORITHMS } from '@pqc-sdk/core';\n *\n * SUPPORTED_ALGORITHMS.includes('ml-kem-768'); // true\n * ```\n */\nexport const SUPPORTED_ALGORITHMS = ['ml-kem-768', 'ml-dsa-65'] as const;\n\nexport type SupportedAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number];\n\n/**\n * Punto de entrada del SDK: cifrado híbrido post-cuántico y firmas digitales\n * con defaults seguros, sin configuración.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * // Cifrado (ML-KEM-768 + AES-256-GCM)\n * const pair = await pqc.keys.generate();\n * const ciphertext = await pqc.encrypt('hola', pair.publicKey);\n * const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\n *\n * // Firmas (ML-DSA-65)\n * const signer = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * const signature = await pqc.sign('documento', signer.secretKey);\n * await pqc.verify('documento', signature, signer.publicKey); // true\n * ```\n */\nexport const pqc = {\n keys: { generate, serialize, deserialize },\n encrypt,\n decrypt,\n sign,\n verify,\n} as const;\n","import { gcm } from '@noble/ciphers/aes.js';\nimport { randomBytes } from '@noble/post-quantum/utils.js';\n\nimport { KEM_ALGORITHMS, requireKey } from './algorithms.js';\nimport { PqcError } from './errors.js';\nimport type { PublicKey, SecretKey } from './types.js';\n\nconst FORMAT_VERSION = 1;\nconst NONCE_LENGTH = 12;\nconst GCM_TAG_LENGTH = 16;\n\nconst utf8 = new TextEncoder();\n\nfunction toBytes(data: Uint8Array | string): Uint8Array {\n return typeof data === 'string' ? utf8.encode(data) : data;\n}\n\n/**\n * Cifrado híbrido: encapsula un secreto con ML-KEM-768 (FIPS 203) y cifra los\n * datos con AES-256-GCM usando ese secreto. El resultado es un único\n * `Uint8Array` autocontenido que solo {@link decrypt} puede abrir.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate();\n * const ciphertext = await pqc.encrypt('dato sensible', pair.publicKey);\n * ```\n */\nexport async function encrypt(\n data: Uint8Array | string,\n publicKey: PublicKey<'ml-kem-768'>,\n): Promise<Uint8Array> {\n const spec = requireKey(publicKey, 'kem', 'public', 'encrypt');\n const plaintext = toBytes(data);\n\n const { cipherText, sharedSecret } = spec.kem.encapsulate(publicKey.bytes);\n const nonce = randomBytes(NONCE_LENGTH);\n const sealed = gcm(sharedSecret, nonce).encrypt(plaintext);\n\n const out = new Uint8Array(2 + cipherText.length + nonce.length + sealed.length);\n out[0] = FORMAT_VERSION;\n out[1] = spec.headerId;\n out.set(cipherText, 2);\n out.set(nonce, 2 + cipherText.length);\n out.set(sealed, 2 + cipherText.length + nonce.length);\n return Promise.resolve(out);\n}\n\n/**\n * Descifra un ciphertext producido por {@link encrypt}. Si el ciphertext fue\n * manipulado o la key no corresponde, lanza {@link PqcError} con código\n * `DECRYPTION_FAILED` — nunca devuelve datos corruptos.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\n * new TextDecoder().decode(plaintext);\n * ```\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n secretKey: SecretKey<'ml-kem-768'>,\n): Promise<Uint8Array> {\n const spec = requireKey(secretKey, 'kem', 'secret', 'decrypt');\n\n const minLength = 2 + spec.ciphertextLength + NONCE_LENGTH + GCM_TAG_LENGTH;\n if (ciphertext.length < minLength) {\n throw new PqcError('INVALID_CIPHERTEXT', 'Ciphertext truncado o no producido por pqc.encrypt');\n }\n if (ciphertext[0] !== FORMAT_VERSION || ciphertext[1] !== spec.headerId) {\n throw new PqcError(\n 'INVALID_CIPHERTEXT',\n 'Header desconocido: el ciphertext no corresponde a esta versión o algoritmo',\n );\n }\n\n const kemCiphertext = ciphertext.subarray(2, 2 + spec.ciphertextLength);\n const nonce = ciphertext.subarray(\n 2 + spec.ciphertextLength,\n 2 + spec.ciphertextLength + NONCE_LENGTH,\n );\n const sealed = ciphertext.subarray(2 + spec.ciphertextLength + NONCE_LENGTH);\n\n const sharedSecret = spec.kem.decapsulate(kemCiphertext, secretKey.bytes);\n try {\n return Promise.resolve(gcm(sharedSecret, nonce).decrypt(sealed));\n } catch {\n throw new PqcError(\n 'DECRYPTION_FAILED',\n 'No se pudo descifrar: ciphertext manipulado o secret key incorrecta',\n );\n }\n}\n\n/** Algoritmos KEM disponibles, exportado para introspección. */\nexport const KEM_NAMES = Object.keys(KEM_ALGORITHMS);\n","import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';\nimport { ml_kem768 } from '@noble/post-quantum/ml-kem.js';\n\nimport { PqcError } from './errors.js';\nimport type { Algorithm, KemAlgorithm, KeyUse, PqcKey, SignatureAlgorithm } from './types.js';\n\ninterface AlgorithmSpec {\n readonly seedLength: number;\n readonly publicKeyLength: number;\n readonly secretKeyLength: number;\n}\n\nexport interface KemSpec extends AlgorithmSpec {\n readonly kind: 'kem';\n readonly headerId: number;\n readonly ciphertextLength: number;\n readonly kem: typeof ml_kem768;\n}\n\nexport interface SignerSpec extends AlgorithmSpec {\n readonly kind: 'signer';\n readonly signatureLength: number;\n readonly signer: typeof ml_dsa65;\n}\n\nexport const KEM_ALGORITHMS: Record<KemAlgorithm, KemSpec> = {\n 'ml-kem-768': {\n kind: 'kem',\n headerId: 1,\n kem: ml_kem768,\n seedLength: 64,\n publicKeyLength: 1184,\n secretKeyLength: 2400,\n ciphertextLength: 1088,\n },\n};\n\nexport const SIGNATURE_ALGORITHMS: Record<SignatureAlgorithm, SignerSpec> = {\n 'ml-dsa-65': {\n kind: 'signer',\n signer: ml_dsa65,\n seedLength: 32,\n publicKeyLength: 1952,\n secretKeyLength: 4032,\n signatureLength: 3309,\n },\n};\n\nexport const ALGORITHMS: Record<Algorithm, KemSpec | SignerSpec> = {\n ...KEM_ALGORITHMS,\n ...SIGNATURE_ALGORITHMS,\n};\n\nexport function getAlgorithm(algorithm: string): KemSpec | SignerSpec {\n const spec = (ALGORITHMS as Record<string, KemSpec | SignerSpec>)[algorithm];\n if (!spec) {\n throw new PqcError('UNSUPPORTED_ALGORITHM', `Algoritmo no soportado: ${algorithm}`);\n }\n return spec;\n}\n\nexport function keyLengthFor(spec: KemSpec | SignerSpec, use: KeyUse): number {\n return use === 'public' ? spec.publicKeyLength : spec.secretKeyLength;\n}\n\n/** Valida algoritmo, uso y longitud de una key antes de operar con ella. */\nexport function requireKey<K extends 'kem' | 'signer'>(\n key: PqcKey,\n kind: K,\n use: KeyUse,\n operation: string,\n): K extends 'kem' ? KemSpec : SignerSpec {\n const spec = getAlgorithm(key.algorithm);\n if (spec.kind !== kind) {\n throw new PqcError(\n 'WRONG_ALGORITHM',\n `${operation} requiere una key ${kind === 'kem' ? 'ML-KEM' : 'ML-DSA'}, recibió ${key.algorithm}`,\n );\n }\n if (key.use !== use) {\n throw new PqcError('WRONG_KEY_USE', `${operation} requiere la key ${use}, recibió ${key.use}`);\n }\n if (key.bytes.length !== keyLengthFor(spec, use)) {\n throw new PqcError(\n 'INVALID_KEY',\n `Key ${key.algorithm} ${use} con longitud inválida: ${key.bytes.length}`,\n );\n }\n return spec as K extends 'kem' ? KemSpec : SignerSpec;\n}\n","/** Códigos de error que puede emitir el SDK. */\nexport type PqcErrorCode =\n | 'UNSUPPORTED_ALGORITHM'\n | 'WRONG_ALGORITHM'\n | 'WRONG_KEY_USE'\n | 'INVALID_KEY'\n | 'INVALID_SERIALIZED_KEY'\n | 'INVALID_CIPHERTEXT'\n | 'DECRYPTION_FAILED';\n\n/**\n * Error tipado del SDK. Toda falla esperable expone un `code` estable para\n * manejarla programáticamente sin parsear mensajes.\n *\n * @example\n * ```ts\n * import { PqcError, pqc } from '@pqc-sdk/core';\n *\n * try {\n * await pqc.decrypt(ciphertext, secretKey);\n * } catch (error) {\n * if (error instanceof PqcError && error.code === 'DECRYPTION_FAILED') {\n * // ciphertext manipulado o key incorrecta\n * }\n * }\n * ```\n */\nexport class PqcError extends Error {\n readonly code: PqcErrorCode;\n\n constructor(code: PqcErrorCode, message: string) {\n super(message);\n this.name = 'PqcError';\n this.code = code;\n }\n}\n","import { randomBytes } from '@noble/post-quantum/utils.js';\n\nimport { getAlgorithm, keyLengthFor } from './algorithms.js';\nimport { fromBase64Url, toBase64Url } from './base64url.js';\nimport { PqcError } from './errors.js';\nimport type { Algorithm, KeyPair, PqcKey } from './types.js';\n\nconst SERIAL_PREFIX = 'pqcv1';\n\n/** Opciones de {@link generate}. */\nexport interface GenerateOptions<A extends Algorithm = Algorithm> {\n /** Algoritmo del par. Default: `'ml-kem-768'` (cifrado). */\n readonly algorithm?: A;\n}\n\n/**\n * Genera un par de keys post-cuánticas. Sin opciones genera ML-KEM-768,\n * listo para `pqc.encrypt`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const encryption = await pqc.keys.generate();\n * const signing = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * ```\n */\nexport async function generate(): Promise<KeyPair<'ml-kem-768'>>;\nexport async function generate<A extends Algorithm>(\n options: GenerateOptions<A> & { algorithm: A },\n): Promise<KeyPair<A>>;\nexport async function generate(options?: GenerateOptions): Promise<KeyPair>;\nexport async function generate(options?: GenerateOptions): Promise<KeyPair> {\n const algorithm = options?.algorithm ?? 'ml-kem-768';\n const spec = getAlgorithm(algorithm);\n return Promise.resolve(generateKeyPairFromSeed(algorithm, randomBytes(spec.seedLength)));\n}\n\n/**\n * Generación determinística a partir de una seed. Uso interno y de tests\n * (vectores NIST). Para uso normal preferir {@link generate}.\n */\nexport function generateKeyPairFromSeed(algorithm: Algorithm, seed: Uint8Array): KeyPair {\n const spec = getAlgorithm(algorithm);\n if (seed.length !== spec.seedLength) {\n throw new PqcError(\n 'INVALID_KEY',\n `Seed de ${algorithm} debe medir ${spec.seedLength} bytes, recibió ${seed.length}`,\n );\n }\n const material = spec.kind === 'kem' ? spec.kem.keygen(seed) : spec.signer.keygen(seed);\n return {\n algorithm,\n publicKey: { algorithm, use: 'public', bytes: material.publicKey },\n secretKey: { algorithm, use: 'secret', bytes: material.secretKey },\n };\n}\n\n/**\n * Serializa una key a un string portable: `pqcv1.<algoritmo>.<uso>.<base64url>`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate();\n * const token = pqc.keys.serialize(pair.publicKey);\n * // \"pqcv1.ml-kem-768.public.h1q3…\"\n * ```\n */\nexport function serialize(key: PqcKey): string {\n const spec = getAlgorithm(key.algorithm);\n if (key.bytes.length !== keyLengthFor(spec, key.use)) {\n throw new PqcError('INVALID_KEY', `Key ${key.algorithm} ${key.use} con longitud inválida`);\n }\n return `${SERIAL_PREFIX}.${key.algorithm}.${key.use}.${toBase64Url(key.bytes)}`;\n}\n\n/**\n * Reconstruye una key desde el formato de {@link serialize}. Valida versión,\n * algoritmo, uso y longitud; ante cualquier problema lanza {@link PqcError}\n * con código `INVALID_SERIALIZED_KEY` o `INVALID_KEY`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const publicKey = pqc.keys.deserialize(tokenRecibidoDelCliente);\n * const ciphertext = await pqc.encrypt(payload, publicKey);\n * ```\n */\nexport function deserialize(serialized: string): PqcKey {\n const parts = serialized.split('.');\n if (parts.length !== 4 || parts[0] !== SERIAL_PREFIX) {\n throw new PqcError(\n 'INVALID_SERIALIZED_KEY',\n 'Formato esperado: pqcv1.<algoritmo>.<uso>.<base64url>',\n );\n }\n const [, algorithm, use, encoded] = parts as [string, string, string, string];\n const spec = getAlgorithm(algorithm);\n if (use !== 'public' && use !== 'secret') {\n throw new PqcError('INVALID_SERIALIZED_KEY', `Uso de key desconocido: ${use}`);\n }\n let bytes: Uint8Array;\n try {\n bytes = fromBase64Url(encoded);\n } catch (cause) {\n throw new PqcError(\n 'INVALID_SERIALIZED_KEY',\n cause instanceof Error ? cause.message : 'base64url inválido',\n );\n }\n const key: PqcKey = { algorithm: algorithm as Algorithm, use, bytes };\n if (bytes.length !== keyLengthFor(spec, key.use)) {\n throw new PqcError(\n 'INVALID_KEY',\n `Key ${algorithm} ${use} debe medir ${keyLengthFor(spec, key.use)} bytes, midió ${bytes.length}`,\n );\n }\n return key;\n}\n","const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';\n\nconst CHAR_TO_VALUE = new Map<string, number>([...ALPHABET].map((c, i) => [c, i]));\n\n/** Codifica bytes a base64url sin padding. Implementación pura, sin Buffer/btoa. */\nexport function toBase64Url(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i]!;\n const b1 = bytes[i + 1];\n const b2 = bytes[i + 2];\n out += ALPHABET[b0 >> 2]!;\n out += ALPHABET[((b0 & 0x03) << 4) | ((b1 ?? 0) >> 4)]!;\n if (b1 !== undefined) out += ALPHABET[((b1 & 0x0f) << 2) | ((b2 ?? 0) >> 6)]!;\n if (b2 !== undefined) out += ALPHABET[b2 & 0x3f]!;\n }\n return out;\n}\n\n/** Decodifica base64url sin padding. Lanza TypeError ante caracteres inválidos. */\nexport function fromBase64Url(encoded: string): Uint8Array {\n if (encoded.length % 4 === 1) {\n throw new TypeError('base64url inválido: longitud imposible');\n }\n const out = new Uint8Array(Math.floor((encoded.length * 3) / 4));\n let outIndex = 0;\n let buffer = 0;\n let bits = 0;\n for (const char of encoded) {\n const value = CHAR_TO_VALUE.get(char);\n if (value === undefined) {\n throw new TypeError(`base64url inválido: carácter ${JSON.stringify(char)}`);\n }\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[outIndex++] = (buffer >> bits) & 0xff;\n }\n }\n return out;\n}\n","import { requireKey } from './algorithms.js';\nimport type { PublicKey, SecretKey, SignatureOptions } from './types.js';\n\nconst utf8 = new TextEncoder();\n\nfunction toBytes(data: Uint8Array | string): Uint8Array {\n return typeof data === 'string' ? utf8.encode(data) : data;\n}\n\nfunction toNobleOptions(options?: SignatureOptions): { context: Uint8Array } | undefined {\n return options?.context ? { context: options.context } : undefined;\n}\n\n/**\n * Firma datos con ML-DSA-65 (FIPS 204), modo hedged (firma aleatorizada,\n * el default del estándar). Devuelve la firma de 3309 bytes.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * const signature = await pqc.sign(documento, pair.secretKey);\n * ```\n */\nexport async function sign(\n data: Uint8Array | string,\n secretKey: SecretKey<'ml-dsa-65'>,\n options?: SignatureOptions,\n): Promise<Uint8Array> {\n const spec = requireKey(secretKey, 'signer', 'secret', 'sign');\n return Promise.resolve(spec.signer.sign(toBytes(data), secretKey.bytes, toNobleOptions(options)));\n}\n\n/**\n * Verifica una firma ML-DSA-65. Devuelve `false` ante firmas inválidas o\n * malformadas (nunca lanza por una firma corrupta); solo lanza si la key\n * no es ML-DSA.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const valid = await pqc.verify(documento, signature, pair.publicKey);\n * if (!valid) throw new Error('firma inválida');\n * ```\n */\nexport async function verify(\n data: Uint8Array | string,\n signature: Uint8Array,\n publicKey: PublicKey<'ml-dsa-65'>,\n options?: SignatureOptions,\n): Promise<boolean> {\n const spec = requireKey(publicKey, 'signer', 'public', 'verify');\n try {\n return Promise.resolve(\n spec.signer.verify(signature, toBytes(data), publicKey.bytes, toNobleOptions(options)),\n );\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["import_utils","utf8","toBytes"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/encrypt.ts","../src/algorithms.ts","../src/errors.ts","../src/keys.ts","../src/base64url.ts","../src/sign.ts"],"sourcesContent":["import { encrypt, decrypt } from './encrypt.js';\nimport { deserialize, generate, serialize } from './keys.js';\nimport { sign, verify } from './sign.js';\n\nexport { PqcError, type PqcErrorCode } from './errors.js';\nexport type { GenerateOptions } from './keys.js';\nexport type {\n Algorithm,\n KemAlgorithm,\n KeyPair,\n KeyUse,\n PqcKey,\n PublicKey,\n SecretKey,\n SignatureAlgorithm,\n SignatureOptions,\n} from './types.js';\nexport { encrypt, decrypt, sign, verify, generate, serialize, deserialize };\n\n// Injected at build time from package.json (`define` in tsup.config.ts and vitest.config.ts).\ndeclare const __PQC_CORE_VERSION__: string;\n\n/**\n * SDK version.\n *\n * @example\n * ```ts\n * import { version } from '@pqc-sdk/core';\n *\n * console.log(version); // e.g. \"0.1.0\"\n * ```\n */\nexport const version = __PQC_CORE_VERSION__;\n\n/**\n * Implemented PQC algorithms (FIPS 203 and FIPS 204).\n *\n * @example\n * ```ts\n * import { SUPPORTED_ALGORITHMS } from '@pqc-sdk/core';\n *\n * SUPPORTED_ALGORITHMS.includes('ml-kem-768'); // true\n * ```\n */\nexport const SUPPORTED_ALGORITHMS = ['ml-kem-768', 'ml-dsa-65'] as const;\n\nexport type SupportedAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number];\n\n/**\n * SDK entry point: post-quantum hybrid encryption and digital signatures\n * with safe defaults, zero configuration.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * // Encryption (ML-KEM-768 + AES-256-GCM)\n * const pair = await pqc.keys.generate();\n * const ciphertext = await pqc.encrypt('hello', pair.publicKey);\n * const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\n *\n * // Signatures (ML-DSA-65)\n * const signer = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * const signature = await pqc.sign('document', signer.secretKey);\n * await pqc.verify('document', signature, signer.publicKey); // true\n * ```\n */\nexport const pqc = {\n keys: { generate, serialize, deserialize },\n encrypt,\n decrypt,\n sign,\n verify,\n} as const;\n","import { gcm } from '@noble/ciphers/aes.js';\nimport { randomBytes } from '@noble/post-quantum/utils.js';\n\nimport { KEM_ALGORITHMS, requireKey } from './algorithms.js';\nimport { PqcError } from './errors.js';\nimport type { PublicKey, SecretKey } from './types.js';\n\nconst FORMAT_VERSION = 1;\nconst NONCE_LENGTH = 12;\nconst GCM_TAG_LENGTH = 16;\n\nconst utf8 = new TextEncoder();\n\nfunction toBytes(data: Uint8Array | string): Uint8Array {\n return typeof data === 'string' ? utf8.encode(data) : data;\n}\n\n/**\n * Hybrid encryption: encapsulates a secret with ML-KEM-768 (FIPS 203) and\n * encrypts the data with AES-256-GCM using that secret. The result is a\n * single self-contained `Uint8Array` that only {@link decrypt} can open.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate();\n * const ciphertext = await pqc.encrypt('sensitive data', pair.publicKey);\n * ```\n */\nexport async function encrypt(\n data: Uint8Array | string,\n publicKey: PublicKey<'ml-kem-768'>,\n): Promise<Uint8Array> {\n const spec = requireKey(publicKey, 'kem', 'public', 'encrypt');\n const plaintext = toBytes(data);\n\n const { cipherText, sharedSecret } = spec.kem.encapsulate(publicKey.bytes);\n const nonce = randomBytes(NONCE_LENGTH);\n\n // Bind the 2-byte header (FORMAT_VERSION, headerId) as AES-GCM additional\n // authenticated data so it is covered by the GCM tag (see decrypt).\n const header = new Uint8Array([FORMAT_VERSION, spec.headerId]);\n const sealed = gcm(sharedSecret, nonce, header).encrypt(plaintext);\n\n const out = new Uint8Array(2 + cipherText.length + nonce.length + sealed.length);\n out.set(header, 0);\n out.set(cipherText, 2);\n out.set(nonce, 2 + cipherText.length);\n out.set(sealed, 2 + cipherText.length + nonce.length);\n return Promise.resolve(out);\n}\n\n/**\n * Decrypts a ciphertext produced by {@link encrypt}. If the ciphertext was\n * tampered with or the key does not match, it throws {@link PqcError} with\n * code `DECRYPTION_FAILED` — it never returns corrupted data.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\n * new TextDecoder().decode(plaintext);\n * ```\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n secretKey: SecretKey<'ml-kem-768'>,\n): Promise<Uint8Array> {\n const spec = requireKey(secretKey, 'kem', 'secret', 'decrypt');\n\n const minLength = 2 + spec.ciphertextLength + NONCE_LENGTH + GCM_TAG_LENGTH;\n if (ciphertext.length < minLength) {\n throw new PqcError(\n 'INVALID_CIPHERTEXT',\n 'Ciphertext is truncated or was not produced by pqc.encrypt',\n );\n }\n // This equality check is fail-fast input validation: it discriminates the\n // version and algorithm with a clear INVALID_CIPHERTEXT error before any\n // cryptographic work. The AAD binding below is the *cryptographic* integrity\n // of the header — it becomes the only line of defence once more versions or\n // algorithms share this layout (a tampered-but-known header would pass this\n // check yet fail the GCM tag). Do not remove either guard in a refactor.\n if (ciphertext[0] !== FORMAT_VERSION || ciphertext[1] !== spec.headerId) {\n throw new PqcError(\n 'INVALID_CIPHERTEXT',\n 'Unknown header: the ciphertext does not match this version or algorithm',\n );\n }\n\n // Reconstruct the AAD from the header bytes actually present in the message\n // so AES-GCM authenticates them as part of the tag.\n const header = ciphertext.subarray(0, 2);\n const kemCiphertext = ciphertext.subarray(2, 2 + spec.ciphertextLength);\n const nonce = ciphertext.subarray(\n 2 + spec.ciphertextLength,\n 2 + spec.ciphertextLength + NONCE_LENGTH,\n );\n const sealed = ciphertext.subarray(2 + spec.ciphertextLength + NONCE_LENGTH);\n\n const sharedSecret = spec.kem.decapsulate(kemCiphertext, secretKey.bytes);\n try {\n return Promise.resolve(gcm(sharedSecret, nonce, header).decrypt(sealed));\n } catch {\n throw new PqcError(\n 'DECRYPTION_FAILED',\n 'Decryption failed: tampered ciphertext or wrong secret key',\n );\n }\n}\n\n/** Available KEM algorithms, exported for introspection. */\nexport const KEM_NAMES = Object.keys(KEM_ALGORITHMS);\n","import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';\nimport { ml_kem768 } from '@noble/post-quantum/ml-kem.js';\n\nimport { PqcError } from './errors.js';\nimport type { Algorithm, KemAlgorithm, KeyUse, PqcKey, SignatureAlgorithm } from './types.js';\n\ninterface AlgorithmSpec {\n readonly seedLength: number;\n readonly publicKeyLength: number;\n readonly secretKeyLength: number;\n}\n\nexport interface KemSpec extends AlgorithmSpec {\n readonly kind: 'kem';\n readonly headerId: number;\n readonly ciphertextLength: number;\n readonly kem: typeof ml_kem768;\n}\n\nexport interface SignerSpec extends AlgorithmSpec {\n readonly kind: 'signer';\n readonly signatureLength: number;\n readonly signer: typeof ml_dsa65;\n}\n\nexport const KEM_ALGORITHMS: Record<KemAlgorithm, KemSpec> = {\n 'ml-kem-768': {\n kind: 'kem',\n headerId: 1,\n kem: ml_kem768,\n seedLength: 64,\n publicKeyLength: 1184,\n secretKeyLength: 2400,\n ciphertextLength: 1088,\n },\n};\n\nexport const SIGNATURE_ALGORITHMS: Record<SignatureAlgorithm, SignerSpec> = {\n 'ml-dsa-65': {\n kind: 'signer',\n signer: ml_dsa65,\n seedLength: 32,\n publicKeyLength: 1952,\n secretKeyLength: 4032,\n signatureLength: 3309,\n },\n};\n\nexport const ALGORITHMS: Record<Algorithm, KemSpec | SignerSpec> = {\n ...KEM_ALGORITHMS,\n ...SIGNATURE_ALGORITHMS,\n};\n\nexport function getAlgorithm(algorithm: string): KemSpec | SignerSpec {\n const spec = (ALGORITHMS as Record<string, KemSpec | SignerSpec>)[algorithm];\n if (!spec) {\n throw new PqcError('UNSUPPORTED_ALGORITHM', `Unsupported algorithm: ${algorithm}`);\n }\n return spec;\n}\n\nexport function keyLengthFor(spec: KemSpec | SignerSpec, use: KeyUse): number {\n return use === 'public' ? spec.publicKeyLength : spec.secretKeyLength;\n}\n\n/** Validates a key's algorithm, use and length before operating with it. */\nexport function requireKey<K extends 'kem' | 'signer'>(\n key: PqcKey,\n kind: K,\n use: KeyUse,\n operation: string,\n): K extends 'kem' ? KemSpec : SignerSpec {\n const spec = getAlgorithm(key.algorithm);\n if (spec.kind !== kind) {\n throw new PqcError(\n 'WRONG_ALGORITHM',\n `${operation} requires an ${kind === 'kem' ? 'ML-KEM' : 'ML-DSA'} key, got ${key.algorithm}`,\n );\n }\n if (key.use !== use) {\n throw new PqcError('WRONG_KEY_USE', `${operation} requires the ${use} key, got ${key.use}`);\n }\n if (key.bytes.length !== keyLengthFor(spec, use)) {\n throw new PqcError(\n 'INVALID_KEY',\n `${key.algorithm} ${use} key has invalid length: ${key.bytes.length}`,\n );\n }\n return spec as K extends 'kem' ? KemSpec : SignerSpec;\n}\n","/** Error codes the SDK can emit. */\nexport type PqcErrorCode =\n | 'UNSUPPORTED_ALGORITHM'\n | 'WRONG_ALGORITHM'\n | 'WRONG_KEY_USE'\n | 'INVALID_KEY'\n | 'INVALID_SERIALIZED_KEY'\n | 'INVALID_CIPHERTEXT'\n | 'DECRYPTION_FAILED';\n\n/**\n * Typed SDK error. Every expected failure exposes a stable `code` so it can\n * be handled programmatically without parsing messages.\n *\n * @example\n * ```ts\n * import { PqcError, pqc } from '@pqc-sdk/core';\n *\n * try {\n * await pqc.decrypt(ciphertext, secretKey);\n * } catch (error) {\n * if (error instanceof PqcError && error.code === 'DECRYPTION_FAILED') {\n * // tampered ciphertext or wrong key\n * }\n * }\n * ```\n */\nexport class PqcError extends Error {\n readonly code: PqcErrorCode;\n\n constructor(code: PqcErrorCode, message: string) {\n super(message);\n this.name = 'PqcError';\n this.code = code;\n }\n}\n","import { randomBytes } from '@noble/post-quantum/utils.js';\n\nimport { getAlgorithm, keyLengthFor } from './algorithms.js';\nimport { fromBase64Url, toBase64Url } from './base64url.js';\nimport { PqcError } from './errors.js';\nimport type { Algorithm, KeyPair, PqcKey } from './types.js';\n\nconst SERIAL_PREFIX = 'pqcv1';\n\n/** Options for {@link generate}. */\nexport interface GenerateOptions<A extends Algorithm = Algorithm> {\n /** Algorithm of the pair. Default: `'ml-kem-768'` (encryption). */\n readonly algorithm?: A;\n}\n\n/**\n * Generates a post-quantum key pair. With no options it generates ML-KEM-768,\n * ready for `pqc.encrypt`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const encryption = await pqc.keys.generate();\n * const signing = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * ```\n */\nexport async function generate(): Promise<KeyPair<'ml-kem-768'>>;\nexport async function generate<A extends Algorithm>(\n options: GenerateOptions<A> & { algorithm: A },\n): Promise<KeyPair<A>>;\nexport async function generate(options?: GenerateOptions): Promise<KeyPair>;\nexport async function generate(options?: GenerateOptions): Promise<KeyPair> {\n const algorithm = options?.algorithm ?? 'ml-kem-768';\n const spec = getAlgorithm(algorithm);\n return Promise.resolve(generateKeyPairFromSeed(algorithm, randomBytes(spec.seedLength)));\n}\n\n/**\n * Deterministic generation from a seed. Internal and test use (NIST vectors).\n * Prefer {@link generate} for normal use.\n */\nexport function generateKeyPairFromSeed(algorithm: Algorithm, seed: Uint8Array): KeyPair {\n const spec = getAlgorithm(algorithm);\n if (seed.length !== spec.seedLength) {\n throw new PqcError(\n 'INVALID_KEY',\n `${algorithm} seed must be ${spec.seedLength} bytes, got ${seed.length}`,\n );\n }\n const material = spec.kind === 'kem' ? spec.kem.keygen(seed) : spec.signer.keygen(seed);\n return {\n algorithm,\n publicKey: { algorithm, use: 'public', bytes: material.publicKey },\n secretKey: { algorithm, use: 'secret', bytes: material.secretKey },\n };\n}\n\n/**\n * Serializes a key to a portable string: `pqcv1.<algorithm>.<use>.<base64url>`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate();\n * const token = pqc.keys.serialize(pair.publicKey);\n * // \"pqcv1.ml-kem-768.public.h1q3…\"\n * ```\n */\nexport function serialize(key: PqcKey): string {\n const spec = getAlgorithm(key.algorithm);\n if (key.bytes.length !== keyLengthFor(spec, key.use)) {\n throw new PqcError('INVALID_KEY', `${key.algorithm} ${key.use} key has invalid length`);\n }\n return `${SERIAL_PREFIX}.${key.algorithm}.${key.use}.${toBase64Url(key.bytes)}`;\n}\n\n/**\n * Rebuilds a key from the {@link serialize} format. Validates version,\n * algorithm, use and length; on any problem it throws {@link PqcError} with\n * code `INVALID_SERIALIZED_KEY` or `INVALID_KEY`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const publicKey = pqc.keys.deserialize(tokenReceivedFromClient);\n * const ciphertext = await pqc.encrypt(payload, publicKey);\n * ```\n */\nexport function deserialize(serialized: string): PqcKey {\n const parts = serialized.split('.');\n if (parts.length !== 4 || parts[0] !== SERIAL_PREFIX) {\n throw new PqcError(\n 'INVALID_SERIALIZED_KEY',\n 'Expected format: pqcv1.<algorithm>.<use>.<base64url>',\n );\n }\n const [, algorithm, use, encoded] = parts as [string, string, string, string];\n const spec = getAlgorithm(algorithm);\n if (use !== 'public' && use !== 'secret') {\n throw new PqcError('INVALID_SERIALIZED_KEY', `Unknown key use: ${use}`);\n }\n let bytes: Uint8Array;\n try {\n bytes = fromBase64Url(encoded);\n } catch (cause) {\n throw new PqcError(\n 'INVALID_SERIALIZED_KEY',\n cause instanceof Error ? cause.message : 'Invalid base64url',\n );\n }\n const key: PqcKey = { algorithm: algorithm as Algorithm, use, bytes };\n if (bytes.length !== keyLengthFor(spec, key.use)) {\n throw new PqcError(\n 'INVALID_KEY',\n `${algorithm} ${use} key must be ${keyLengthFor(spec, key.use)} bytes, got ${bytes.length}`,\n );\n }\n return key;\n}\n","const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';\n\nconst CHAR_TO_VALUE = new Map<string, number>([...ALPHABET].map((c, i) => [c, i]));\n\n/** Encodes bytes to unpadded base64url. Pure implementation, no Buffer/btoa. */\nexport function toBase64Url(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i]!;\n const b1 = bytes[i + 1];\n const b2 = bytes[i + 2];\n out += ALPHABET[b0 >> 2]!;\n out += ALPHABET[((b0 & 0x03) << 4) | ((b1 ?? 0) >> 4)]!;\n if (b1 !== undefined) out += ALPHABET[((b1 & 0x0f) << 2) | ((b2 ?? 0) >> 6)]!;\n if (b2 !== undefined) out += ALPHABET[b2 & 0x3f]!;\n }\n return out;\n}\n\n/** Decodes unpadded base64url. Throws TypeError on invalid characters. */\nexport function fromBase64Url(encoded: string): Uint8Array {\n if (encoded.length % 4 === 1) {\n throw new TypeError('Invalid base64url: impossible length');\n }\n const out = new Uint8Array(Math.floor((encoded.length * 3) / 4));\n let outIndex = 0;\n let buffer = 0;\n let bits = 0;\n for (const char of encoded) {\n const value = CHAR_TO_VALUE.get(char);\n if (value === undefined) {\n throw new TypeError(`Invalid base64url: character ${JSON.stringify(char)}`);\n }\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[outIndex++] = (buffer >> bits) & 0xff;\n }\n }\n // Reject non-canonical input: the leftover bits of the final group (those that\n // do not complete a byte) must be zero, otherwise the string is not the\n // canonical encoding of any byte sequence.\n if ((buffer & ((1 << bits) - 1)) !== 0) {\n throw new TypeError('Invalid base64url: non-canonical trailing bits');\n }\n return out;\n}\n","import { requireKey } from './algorithms.js';\nimport type { PublicKey, SecretKey, SignatureOptions } from './types.js';\n\nconst utf8 = new TextEncoder();\n\nfunction toBytes(data: Uint8Array | string): Uint8Array {\n return typeof data === 'string' ? utf8.encode(data) : data;\n}\n\nfunction toNobleOptions(options?: SignatureOptions): { context: Uint8Array } | undefined {\n return options?.context ? { context: options.context } : undefined;\n}\n\n/**\n * Signs data with ML-DSA-65 (FIPS 204) in hedged mode (randomized signing,\n * the standard's default). Returns the 3309-byte signature.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * const signature = await pqc.sign(document, pair.secretKey);\n * ```\n */\nexport async function sign(\n data: Uint8Array | string,\n secretKey: SecretKey<'ml-dsa-65'>,\n options?: SignatureOptions,\n): Promise<Uint8Array> {\n const spec = requireKey(secretKey, 'signer', 'secret', 'sign');\n return Promise.resolve(spec.signer.sign(toBytes(data), secretKey.bytes, toNobleOptions(options)));\n}\n\n/**\n * Verifies an ML-DSA-65 signature. Returns `false` for invalid or malformed\n * signatures (it never throws because of a corrupted signature); it only\n * throws if the key is not ML-DSA.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const valid = await pqc.verify(document, signature, pair.publicKey);\n * if (!valid) throw new Error('invalid signature');\n * ```\n */\nexport async function verify(\n data: Uint8Array | string,\n signature: Uint8Array,\n publicKey: PublicKey<'ml-dsa-65'>,\n options?: SignatureOptions,\n): Promise<boolean> {\n const spec = requireKey(publicKey, 'signer', 'public', 'verify');\n try {\n return Promise.resolve(\n spec.signer.verify(signature, toBytes(data), publicKey.bytes, toNobleOptions(options)),\n );\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["import_utils","utf8","toBytes"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Key encapsulation algorithm (hybrid encryption). */
|
|
2
2
|
type KemAlgorithm = 'ml-kem-768';
|
|
3
|
-
/**
|
|
3
|
+
/** Digital signature algorithm. */
|
|
4
4
|
type SignatureAlgorithm = 'ml-dsa-65';
|
|
5
|
-
/**
|
|
5
|
+
/** Algorithms supported by the SDK. */
|
|
6
6
|
type Algorithm = KemAlgorithm | SignatureAlgorithm;
|
|
7
|
-
/**
|
|
7
|
+
/** Role of a key within its pair. */
|
|
8
8
|
type KeyUse = 'public' | 'secret';
|
|
9
|
-
/**
|
|
9
|
+
/** SDK key: raw bytes plus algorithm and use metadata. */
|
|
10
10
|
interface PqcKey<A extends Algorithm = Algorithm, U extends KeyUse = KeyUse> {
|
|
11
11
|
readonly algorithm: A;
|
|
12
12
|
readonly use: U;
|
|
13
13
|
readonly bytes: Uint8Array;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/** Public key, safe to share. */
|
|
16
16
|
type PublicKey<A extends Algorithm = Algorithm> = PqcKey<A, 'public'>;
|
|
17
|
-
/**
|
|
17
|
+
/** Secret key. Must never leave its owner's environment. */
|
|
18
18
|
type SecretKey<A extends Algorithm = Algorithm> = PqcKey<A, 'secret'>;
|
|
19
|
-
/**
|
|
19
|
+
/** Key pair produced by `pqc.keys.generate`. */
|
|
20
20
|
interface KeyPair<A extends Algorithm = Algorithm> {
|
|
21
21
|
readonly algorithm: A;
|
|
22
22
|
readonly publicKey: PublicKey<A>;
|
|
23
23
|
readonly secretKey: SecretKey<A>;
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
25
|
+
/** Signing/verification options (FIPS 204 §5.2, optional context string). */
|
|
26
26
|
interface SignatureOptions {
|
|
27
|
-
/** Context string
|
|
27
|
+
/** Context string of up to 255 bytes. Default: empty. */
|
|
28
28
|
readonly context?: Uint8Array;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* `Uint8Array`
|
|
32
|
+
* Hybrid encryption: encapsulates a secret with ML-KEM-768 (FIPS 203) and
|
|
33
|
+
* encrypts the data with AES-256-GCM using that secret. The result is a
|
|
34
|
+
* single self-contained `Uint8Array` that only {@link decrypt} can open.
|
|
35
35
|
*
|
|
36
36
|
* @example
|
|
37
37
|
* ```ts
|
|
38
38
|
* import { pqc } from '@pqc-sdk/core';
|
|
39
39
|
*
|
|
40
40
|
* const pair = await pqc.keys.generate();
|
|
41
|
-
* const ciphertext = await pqc.encrypt('
|
|
41
|
+
* const ciphertext = await pqc.encrypt('sensitive data', pair.publicKey);
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
44
|
declare function encrypt(data: Uint8Array | string, publicKey: PublicKey<'ml-kem-768'>): Promise<Uint8Array>;
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `DECRYPTION_FAILED` —
|
|
46
|
+
* Decrypts a ciphertext produced by {@link encrypt}. If the ciphertext was
|
|
47
|
+
* tampered with or the key does not match, it throws {@link PqcError} with
|
|
48
|
+
* code `DECRYPTION_FAILED` — it never returns corrupted data.
|
|
49
49
|
*
|
|
50
50
|
* @example
|
|
51
51
|
* ```ts
|
|
@@ -57,14 +57,14 @@ declare function encrypt(data: Uint8Array | string, publicKey: PublicKey<'ml-kem
|
|
|
57
57
|
*/
|
|
58
58
|
declare function decrypt(ciphertext: Uint8Array, secretKey: SecretKey<'ml-kem-768'>): Promise<Uint8Array>;
|
|
59
59
|
|
|
60
|
-
/**
|
|
60
|
+
/** Options for {@link generate}. */
|
|
61
61
|
interface GenerateOptions<A extends Algorithm = Algorithm> {
|
|
62
|
-
/**
|
|
62
|
+
/** Algorithm of the pair. Default: `'ml-kem-768'` (encryption). */
|
|
63
63
|
readonly algorithm?: A;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* Generates a post-quantum key pair. With no options it generates ML-KEM-768,
|
|
67
|
+
* ready for `pqc.encrypt`.
|
|
68
68
|
*
|
|
69
69
|
* @example
|
|
70
70
|
* ```ts
|
|
@@ -80,7 +80,7 @@ declare function generate<A extends Algorithm>(options: GenerateOptions<A> & {
|
|
|
80
80
|
}): Promise<KeyPair<A>>;
|
|
81
81
|
declare function generate(options?: GenerateOptions): Promise<KeyPair>;
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
83
|
+
* Serializes a key to a portable string: `pqcv1.<algorithm>.<use>.<base64url>`.
|
|
84
84
|
*
|
|
85
85
|
* @example
|
|
86
86
|
* ```ts
|
|
@@ -93,53 +93,53 @@ declare function generate(options?: GenerateOptions): Promise<KeyPair>;
|
|
|
93
93
|
*/
|
|
94
94
|
declare function serialize(key: PqcKey): string;
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
96
|
+
* Rebuilds a key from the {@link serialize} format. Validates version,
|
|
97
|
+
* algorithm, use and length; on any problem it throws {@link PqcError} with
|
|
98
|
+
* code `INVALID_SERIALIZED_KEY` or `INVALID_KEY`.
|
|
99
99
|
*
|
|
100
100
|
* @example
|
|
101
101
|
* ```ts
|
|
102
102
|
* import { pqc } from '@pqc-sdk/core';
|
|
103
103
|
*
|
|
104
|
-
* const publicKey = pqc.keys.deserialize(
|
|
104
|
+
* const publicKey = pqc.keys.deserialize(tokenReceivedFromClient);
|
|
105
105
|
* const ciphertext = await pqc.encrypt(payload, publicKey);
|
|
106
106
|
* ```
|
|
107
107
|
*/
|
|
108
108
|
declare function deserialize(serialized: string): PqcKey;
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
111
|
+
* Signs data with ML-DSA-65 (FIPS 204) in hedged mode (randomized signing,
|
|
112
|
+
* the standard's default). Returns the 3309-byte signature.
|
|
113
113
|
*
|
|
114
114
|
* @example
|
|
115
115
|
* ```ts
|
|
116
116
|
* import { pqc } from '@pqc-sdk/core';
|
|
117
117
|
*
|
|
118
118
|
* const pair = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });
|
|
119
|
-
* const signature = await pqc.sign(
|
|
119
|
+
* const signature = await pqc.sign(document, pair.secretKey);
|
|
120
120
|
* ```
|
|
121
121
|
*/
|
|
122
122
|
declare function sign(data: Uint8Array | string, secretKey: SecretKey<'ml-dsa-65'>, options?: SignatureOptions): Promise<Uint8Array>;
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
124
|
+
* Verifies an ML-DSA-65 signature. Returns `false` for invalid or malformed
|
|
125
|
+
* signatures (it never throws because of a corrupted signature); it only
|
|
126
|
+
* throws if the key is not ML-DSA.
|
|
127
127
|
*
|
|
128
128
|
* @example
|
|
129
129
|
* ```ts
|
|
130
130
|
* import { pqc } from '@pqc-sdk/core';
|
|
131
131
|
*
|
|
132
|
-
* const valid = await pqc.verify(
|
|
133
|
-
* if (!valid) throw new Error('
|
|
132
|
+
* const valid = await pqc.verify(document, signature, pair.publicKey);
|
|
133
|
+
* if (!valid) throw new Error('invalid signature');
|
|
134
134
|
* ```
|
|
135
135
|
*/
|
|
136
136
|
declare function verify(data: Uint8Array | string, signature: Uint8Array, publicKey: PublicKey<'ml-dsa-65'>, options?: SignatureOptions): Promise<boolean>;
|
|
137
137
|
|
|
138
|
-
/**
|
|
138
|
+
/** Error codes the SDK can emit. */
|
|
139
139
|
type PqcErrorCode = 'UNSUPPORTED_ALGORITHM' | 'WRONG_ALGORITHM' | 'WRONG_KEY_USE' | 'INVALID_KEY' | 'INVALID_SERIALIZED_KEY' | 'INVALID_CIPHERTEXT' | 'DECRYPTION_FAILED';
|
|
140
140
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
141
|
+
* Typed SDK error. Every expected failure exposes a stable `code` so it can
|
|
142
|
+
* be handled programmatically without parsing messages.
|
|
143
143
|
*
|
|
144
144
|
* @example
|
|
145
145
|
* ```ts
|
|
@@ -149,7 +149,7 @@ type PqcErrorCode = 'UNSUPPORTED_ALGORITHM' | 'WRONG_ALGORITHM' | 'WRONG_KEY_USE
|
|
|
149
149
|
* await pqc.decrypt(ciphertext, secretKey);
|
|
150
150
|
* } catch (error) {
|
|
151
151
|
* if (error instanceof PqcError && error.code === 'DECRYPTION_FAILED') {
|
|
152
|
-
* // ciphertext
|
|
152
|
+
* // tampered ciphertext or wrong key
|
|
153
153
|
* }
|
|
154
154
|
* }
|
|
155
155
|
* ```
|
|
@@ -160,18 +160,18 @@ declare class PqcError extends Error {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
|
-
*
|
|
163
|
+
* SDK version.
|
|
164
164
|
*
|
|
165
165
|
* @example
|
|
166
166
|
* ```ts
|
|
167
167
|
* import { version } from '@pqc-sdk/core';
|
|
168
168
|
*
|
|
169
|
-
* console.log(version); //
|
|
169
|
+
* console.log(version); // e.g. "0.1.0"
|
|
170
170
|
* ```
|
|
171
171
|
*/
|
|
172
172
|
declare const version: string;
|
|
173
173
|
/**
|
|
174
|
-
*
|
|
174
|
+
* Implemented PQC algorithms (FIPS 203 and FIPS 204).
|
|
175
175
|
*
|
|
176
176
|
* @example
|
|
177
177
|
* ```ts
|
|
@@ -183,22 +183,22 @@ declare const version: string;
|
|
|
183
183
|
declare const SUPPORTED_ALGORITHMS: readonly ["ml-kem-768", "ml-dsa-65"];
|
|
184
184
|
type SupportedAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number];
|
|
185
185
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
186
|
+
* SDK entry point: post-quantum hybrid encryption and digital signatures
|
|
187
|
+
* with safe defaults, zero configuration.
|
|
188
188
|
*
|
|
189
189
|
* @example
|
|
190
190
|
* ```ts
|
|
191
191
|
* import { pqc } from '@pqc-sdk/core';
|
|
192
192
|
*
|
|
193
|
-
* //
|
|
193
|
+
* // Encryption (ML-KEM-768 + AES-256-GCM)
|
|
194
194
|
* const pair = await pqc.keys.generate();
|
|
195
|
-
* const ciphertext = await pqc.encrypt('
|
|
195
|
+
* const ciphertext = await pqc.encrypt('hello', pair.publicKey);
|
|
196
196
|
* const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);
|
|
197
197
|
*
|
|
198
|
-
* //
|
|
198
|
+
* // Signatures (ML-DSA-65)
|
|
199
199
|
* const signer = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });
|
|
200
|
-
* const signature = await pqc.sign('
|
|
201
|
-
* await pqc.verify('
|
|
200
|
+
* const signature = await pqc.sign('document', signer.secretKey);
|
|
201
|
+
* await pqc.verify('document', signature, signer.publicKey); // true
|
|
202
202
|
* ```
|
|
203
203
|
*/
|
|
204
204
|
declare const pqc: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Key encapsulation algorithm (hybrid encryption). */
|
|
2
2
|
type KemAlgorithm = 'ml-kem-768';
|
|
3
|
-
/**
|
|
3
|
+
/** Digital signature algorithm. */
|
|
4
4
|
type SignatureAlgorithm = 'ml-dsa-65';
|
|
5
|
-
/**
|
|
5
|
+
/** Algorithms supported by the SDK. */
|
|
6
6
|
type Algorithm = KemAlgorithm | SignatureAlgorithm;
|
|
7
|
-
/**
|
|
7
|
+
/** Role of a key within its pair. */
|
|
8
8
|
type KeyUse = 'public' | 'secret';
|
|
9
|
-
/**
|
|
9
|
+
/** SDK key: raw bytes plus algorithm and use metadata. */
|
|
10
10
|
interface PqcKey<A extends Algorithm = Algorithm, U extends KeyUse = KeyUse> {
|
|
11
11
|
readonly algorithm: A;
|
|
12
12
|
readonly use: U;
|
|
13
13
|
readonly bytes: Uint8Array;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/** Public key, safe to share. */
|
|
16
16
|
type PublicKey<A extends Algorithm = Algorithm> = PqcKey<A, 'public'>;
|
|
17
|
-
/**
|
|
17
|
+
/** Secret key. Must never leave its owner's environment. */
|
|
18
18
|
type SecretKey<A extends Algorithm = Algorithm> = PqcKey<A, 'secret'>;
|
|
19
|
-
/**
|
|
19
|
+
/** Key pair produced by `pqc.keys.generate`. */
|
|
20
20
|
interface KeyPair<A extends Algorithm = Algorithm> {
|
|
21
21
|
readonly algorithm: A;
|
|
22
22
|
readonly publicKey: PublicKey<A>;
|
|
23
23
|
readonly secretKey: SecretKey<A>;
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
25
|
+
/** Signing/verification options (FIPS 204 §5.2, optional context string). */
|
|
26
26
|
interface SignatureOptions {
|
|
27
|
-
/** Context string
|
|
27
|
+
/** Context string of up to 255 bytes. Default: empty. */
|
|
28
28
|
readonly context?: Uint8Array;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* `Uint8Array`
|
|
32
|
+
* Hybrid encryption: encapsulates a secret with ML-KEM-768 (FIPS 203) and
|
|
33
|
+
* encrypts the data with AES-256-GCM using that secret. The result is a
|
|
34
|
+
* single self-contained `Uint8Array` that only {@link decrypt} can open.
|
|
35
35
|
*
|
|
36
36
|
* @example
|
|
37
37
|
* ```ts
|
|
38
38
|
* import { pqc } from '@pqc-sdk/core';
|
|
39
39
|
*
|
|
40
40
|
* const pair = await pqc.keys.generate();
|
|
41
|
-
* const ciphertext = await pqc.encrypt('
|
|
41
|
+
* const ciphertext = await pqc.encrypt('sensitive data', pair.publicKey);
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
44
|
declare function encrypt(data: Uint8Array | string, publicKey: PublicKey<'ml-kem-768'>): Promise<Uint8Array>;
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `DECRYPTION_FAILED` —
|
|
46
|
+
* Decrypts a ciphertext produced by {@link encrypt}. If the ciphertext was
|
|
47
|
+
* tampered with or the key does not match, it throws {@link PqcError} with
|
|
48
|
+
* code `DECRYPTION_FAILED` — it never returns corrupted data.
|
|
49
49
|
*
|
|
50
50
|
* @example
|
|
51
51
|
* ```ts
|
|
@@ -57,14 +57,14 @@ declare function encrypt(data: Uint8Array | string, publicKey: PublicKey<'ml-kem
|
|
|
57
57
|
*/
|
|
58
58
|
declare function decrypt(ciphertext: Uint8Array, secretKey: SecretKey<'ml-kem-768'>): Promise<Uint8Array>;
|
|
59
59
|
|
|
60
|
-
/**
|
|
60
|
+
/** Options for {@link generate}. */
|
|
61
61
|
interface GenerateOptions<A extends Algorithm = Algorithm> {
|
|
62
|
-
/**
|
|
62
|
+
/** Algorithm of the pair. Default: `'ml-kem-768'` (encryption). */
|
|
63
63
|
readonly algorithm?: A;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* Generates a post-quantum key pair. With no options it generates ML-KEM-768,
|
|
67
|
+
* ready for `pqc.encrypt`.
|
|
68
68
|
*
|
|
69
69
|
* @example
|
|
70
70
|
* ```ts
|
|
@@ -80,7 +80,7 @@ declare function generate<A extends Algorithm>(options: GenerateOptions<A> & {
|
|
|
80
80
|
}): Promise<KeyPair<A>>;
|
|
81
81
|
declare function generate(options?: GenerateOptions): Promise<KeyPair>;
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
83
|
+
* Serializes a key to a portable string: `pqcv1.<algorithm>.<use>.<base64url>`.
|
|
84
84
|
*
|
|
85
85
|
* @example
|
|
86
86
|
* ```ts
|
|
@@ -93,53 +93,53 @@ declare function generate(options?: GenerateOptions): Promise<KeyPair>;
|
|
|
93
93
|
*/
|
|
94
94
|
declare function serialize(key: PqcKey): string;
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
96
|
+
* Rebuilds a key from the {@link serialize} format. Validates version,
|
|
97
|
+
* algorithm, use and length; on any problem it throws {@link PqcError} with
|
|
98
|
+
* code `INVALID_SERIALIZED_KEY` or `INVALID_KEY`.
|
|
99
99
|
*
|
|
100
100
|
* @example
|
|
101
101
|
* ```ts
|
|
102
102
|
* import { pqc } from '@pqc-sdk/core';
|
|
103
103
|
*
|
|
104
|
-
* const publicKey = pqc.keys.deserialize(
|
|
104
|
+
* const publicKey = pqc.keys.deserialize(tokenReceivedFromClient);
|
|
105
105
|
* const ciphertext = await pqc.encrypt(payload, publicKey);
|
|
106
106
|
* ```
|
|
107
107
|
*/
|
|
108
108
|
declare function deserialize(serialized: string): PqcKey;
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
111
|
+
* Signs data with ML-DSA-65 (FIPS 204) in hedged mode (randomized signing,
|
|
112
|
+
* the standard's default). Returns the 3309-byte signature.
|
|
113
113
|
*
|
|
114
114
|
* @example
|
|
115
115
|
* ```ts
|
|
116
116
|
* import { pqc } from '@pqc-sdk/core';
|
|
117
117
|
*
|
|
118
118
|
* const pair = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });
|
|
119
|
-
* const signature = await pqc.sign(
|
|
119
|
+
* const signature = await pqc.sign(document, pair.secretKey);
|
|
120
120
|
* ```
|
|
121
121
|
*/
|
|
122
122
|
declare function sign(data: Uint8Array | string, secretKey: SecretKey<'ml-dsa-65'>, options?: SignatureOptions): Promise<Uint8Array>;
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
124
|
+
* Verifies an ML-DSA-65 signature. Returns `false` for invalid or malformed
|
|
125
|
+
* signatures (it never throws because of a corrupted signature); it only
|
|
126
|
+
* throws if the key is not ML-DSA.
|
|
127
127
|
*
|
|
128
128
|
* @example
|
|
129
129
|
* ```ts
|
|
130
130
|
* import { pqc } from '@pqc-sdk/core';
|
|
131
131
|
*
|
|
132
|
-
* const valid = await pqc.verify(
|
|
133
|
-
* if (!valid) throw new Error('
|
|
132
|
+
* const valid = await pqc.verify(document, signature, pair.publicKey);
|
|
133
|
+
* if (!valid) throw new Error('invalid signature');
|
|
134
134
|
* ```
|
|
135
135
|
*/
|
|
136
136
|
declare function verify(data: Uint8Array | string, signature: Uint8Array, publicKey: PublicKey<'ml-dsa-65'>, options?: SignatureOptions): Promise<boolean>;
|
|
137
137
|
|
|
138
|
-
/**
|
|
138
|
+
/** Error codes the SDK can emit. */
|
|
139
139
|
type PqcErrorCode = 'UNSUPPORTED_ALGORITHM' | 'WRONG_ALGORITHM' | 'WRONG_KEY_USE' | 'INVALID_KEY' | 'INVALID_SERIALIZED_KEY' | 'INVALID_CIPHERTEXT' | 'DECRYPTION_FAILED';
|
|
140
140
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
141
|
+
* Typed SDK error. Every expected failure exposes a stable `code` so it can
|
|
142
|
+
* be handled programmatically without parsing messages.
|
|
143
143
|
*
|
|
144
144
|
* @example
|
|
145
145
|
* ```ts
|
|
@@ -149,7 +149,7 @@ type PqcErrorCode = 'UNSUPPORTED_ALGORITHM' | 'WRONG_ALGORITHM' | 'WRONG_KEY_USE
|
|
|
149
149
|
* await pqc.decrypt(ciphertext, secretKey);
|
|
150
150
|
* } catch (error) {
|
|
151
151
|
* if (error instanceof PqcError && error.code === 'DECRYPTION_FAILED') {
|
|
152
|
-
* // ciphertext
|
|
152
|
+
* // tampered ciphertext or wrong key
|
|
153
153
|
* }
|
|
154
154
|
* }
|
|
155
155
|
* ```
|
|
@@ -160,18 +160,18 @@ declare class PqcError extends Error {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
|
-
*
|
|
163
|
+
* SDK version.
|
|
164
164
|
*
|
|
165
165
|
* @example
|
|
166
166
|
* ```ts
|
|
167
167
|
* import { version } from '@pqc-sdk/core';
|
|
168
168
|
*
|
|
169
|
-
* console.log(version); //
|
|
169
|
+
* console.log(version); // e.g. "0.1.0"
|
|
170
170
|
* ```
|
|
171
171
|
*/
|
|
172
172
|
declare const version: string;
|
|
173
173
|
/**
|
|
174
|
-
*
|
|
174
|
+
* Implemented PQC algorithms (FIPS 203 and FIPS 204).
|
|
175
175
|
*
|
|
176
176
|
* @example
|
|
177
177
|
* ```ts
|
|
@@ -183,22 +183,22 @@ declare const version: string;
|
|
|
183
183
|
declare const SUPPORTED_ALGORITHMS: readonly ["ml-kem-768", "ml-dsa-65"];
|
|
184
184
|
type SupportedAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number];
|
|
185
185
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
186
|
+
* SDK entry point: post-quantum hybrid encryption and digital signatures
|
|
187
|
+
* with safe defaults, zero configuration.
|
|
188
188
|
*
|
|
189
189
|
* @example
|
|
190
190
|
* ```ts
|
|
191
191
|
* import { pqc } from '@pqc-sdk/core';
|
|
192
192
|
*
|
|
193
|
-
* //
|
|
193
|
+
* // Encryption (ML-KEM-768 + AES-256-GCM)
|
|
194
194
|
* const pair = await pqc.keys.generate();
|
|
195
|
-
* const ciphertext = await pqc.encrypt('
|
|
195
|
+
* const ciphertext = await pqc.encrypt('hello', pair.publicKey);
|
|
196
196
|
* const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);
|
|
197
197
|
*
|
|
198
|
-
* //
|
|
198
|
+
* // Signatures (ML-DSA-65)
|
|
199
199
|
* const signer = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });
|
|
200
|
-
* const signature = await pqc.sign('
|
|
201
|
-
* await pqc.verify('
|
|
200
|
+
* const signature = await pqc.sign('document', signer.secretKey);
|
|
201
|
+
* await pqc.verify('document', signature, signer.publicKey); // true
|
|
202
202
|
* ```
|
|
203
203
|
*/
|
|
204
204
|
declare const pqc: {
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ var ALGORITHMS = {
|
|
|
45
45
|
function getAlgorithm(algorithm) {
|
|
46
46
|
const spec = ALGORITHMS[algorithm];
|
|
47
47
|
if (!spec) {
|
|
48
|
-
throw new PqcError("UNSUPPORTED_ALGORITHM", `
|
|
48
|
+
throw new PqcError("UNSUPPORTED_ALGORITHM", `Unsupported algorithm: ${algorithm}`);
|
|
49
49
|
}
|
|
50
50
|
return spec;
|
|
51
51
|
}
|
|
@@ -57,16 +57,16 @@ function requireKey(key, kind, use, operation) {
|
|
|
57
57
|
if (spec.kind !== kind) {
|
|
58
58
|
throw new PqcError(
|
|
59
59
|
"WRONG_ALGORITHM",
|
|
60
|
-
`${operation}
|
|
60
|
+
`${operation} requires an ${kind === "kem" ? "ML-KEM" : "ML-DSA"} key, got ${key.algorithm}`
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
if (key.use !== use) {
|
|
64
|
-
throw new PqcError("WRONG_KEY_USE", `${operation}
|
|
64
|
+
throw new PqcError("WRONG_KEY_USE", `${operation} requires the ${use} key, got ${key.use}`);
|
|
65
65
|
}
|
|
66
66
|
if (key.bytes.length !== keyLengthFor(spec, use)) {
|
|
67
67
|
throw new PqcError(
|
|
68
68
|
"INVALID_KEY",
|
|
69
|
-
|
|
69
|
+
`${key.algorithm} ${use} key has invalid length: ${key.bytes.length}`
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
return spec;
|
|
@@ -85,10 +85,10 @@ async function encrypt(data, publicKey) {
|
|
|
85
85
|
const plaintext = toBytes(data);
|
|
86
86
|
const { cipherText, sharedSecret } = spec.kem.encapsulate(publicKey.bytes);
|
|
87
87
|
const nonce = randomBytes(NONCE_LENGTH);
|
|
88
|
-
const
|
|
88
|
+
const header = new Uint8Array([FORMAT_VERSION, spec.headerId]);
|
|
89
|
+
const sealed = gcm(sharedSecret, nonce, header).encrypt(plaintext);
|
|
89
90
|
const out = new Uint8Array(2 + cipherText.length + nonce.length + sealed.length);
|
|
90
|
-
out
|
|
91
|
-
out[1] = spec.headerId;
|
|
91
|
+
out.set(header, 0);
|
|
92
92
|
out.set(cipherText, 2);
|
|
93
93
|
out.set(nonce, 2 + cipherText.length);
|
|
94
94
|
out.set(sealed, 2 + cipherText.length + nonce.length);
|
|
@@ -98,14 +98,18 @@ async function decrypt(ciphertext, secretKey) {
|
|
|
98
98
|
const spec = requireKey(secretKey, "kem", "secret", "decrypt");
|
|
99
99
|
const minLength = 2 + spec.ciphertextLength + NONCE_LENGTH + GCM_TAG_LENGTH;
|
|
100
100
|
if (ciphertext.length < minLength) {
|
|
101
|
-
throw new PqcError(
|
|
101
|
+
throw new PqcError(
|
|
102
|
+
"INVALID_CIPHERTEXT",
|
|
103
|
+
"Ciphertext is truncated or was not produced by pqc.encrypt"
|
|
104
|
+
);
|
|
102
105
|
}
|
|
103
106
|
if (ciphertext[0] !== FORMAT_VERSION || ciphertext[1] !== spec.headerId) {
|
|
104
107
|
throw new PqcError(
|
|
105
108
|
"INVALID_CIPHERTEXT",
|
|
106
|
-
"
|
|
109
|
+
"Unknown header: the ciphertext does not match this version or algorithm"
|
|
107
110
|
);
|
|
108
111
|
}
|
|
112
|
+
const header = ciphertext.subarray(0, 2);
|
|
109
113
|
const kemCiphertext = ciphertext.subarray(2, 2 + spec.ciphertextLength);
|
|
110
114
|
const nonce = ciphertext.subarray(
|
|
111
115
|
2 + spec.ciphertextLength,
|
|
@@ -114,11 +118,11 @@ async function decrypt(ciphertext, secretKey) {
|
|
|
114
118
|
const sealed = ciphertext.subarray(2 + spec.ciphertextLength + NONCE_LENGTH);
|
|
115
119
|
const sharedSecret = spec.kem.decapsulate(kemCiphertext, secretKey.bytes);
|
|
116
120
|
try {
|
|
117
|
-
return Promise.resolve(gcm(sharedSecret, nonce).decrypt(sealed));
|
|
121
|
+
return Promise.resolve(gcm(sharedSecret, nonce, header).decrypt(sealed));
|
|
118
122
|
} catch {
|
|
119
123
|
throw new PqcError(
|
|
120
124
|
"DECRYPTION_FAILED",
|
|
121
|
-
"
|
|
125
|
+
"Decryption failed: tampered ciphertext or wrong secret key"
|
|
122
126
|
);
|
|
123
127
|
}
|
|
124
128
|
}
|
|
@@ -145,7 +149,7 @@ function toBase64Url(bytes) {
|
|
|
145
149
|
}
|
|
146
150
|
function fromBase64Url(encoded) {
|
|
147
151
|
if (encoded.length % 4 === 1) {
|
|
148
|
-
throw new TypeError("base64url
|
|
152
|
+
throw new TypeError("Invalid base64url: impossible length");
|
|
149
153
|
}
|
|
150
154
|
const out = new Uint8Array(Math.floor(encoded.length * 3 / 4));
|
|
151
155
|
let outIndex = 0;
|
|
@@ -154,7 +158,7 @@ function fromBase64Url(encoded) {
|
|
|
154
158
|
for (const char of encoded) {
|
|
155
159
|
const value = CHAR_TO_VALUE.get(char);
|
|
156
160
|
if (value === void 0) {
|
|
157
|
-
throw new TypeError(`base64url
|
|
161
|
+
throw new TypeError(`Invalid base64url: character ${JSON.stringify(char)}`);
|
|
158
162
|
}
|
|
159
163
|
buffer = buffer << 6 | value;
|
|
160
164
|
bits += 6;
|
|
@@ -163,6 +167,9 @@ function fromBase64Url(encoded) {
|
|
|
163
167
|
out[outIndex++] = buffer >> bits & 255;
|
|
164
168
|
}
|
|
165
169
|
}
|
|
170
|
+
if ((buffer & (1 << bits) - 1) !== 0) {
|
|
171
|
+
throw new TypeError("Invalid base64url: non-canonical trailing bits");
|
|
172
|
+
}
|
|
166
173
|
return out;
|
|
167
174
|
}
|
|
168
175
|
|
|
@@ -178,7 +185,7 @@ function generateKeyPairFromSeed(algorithm, seed) {
|
|
|
178
185
|
if (seed.length !== spec.seedLength) {
|
|
179
186
|
throw new PqcError(
|
|
180
187
|
"INVALID_KEY",
|
|
181
|
-
|
|
188
|
+
`${algorithm} seed must be ${spec.seedLength} bytes, got ${seed.length}`
|
|
182
189
|
);
|
|
183
190
|
}
|
|
184
191
|
const material = spec.kind === "kem" ? spec.kem.keygen(seed) : spec.signer.keygen(seed);
|
|
@@ -191,7 +198,7 @@ function generateKeyPairFromSeed(algorithm, seed) {
|
|
|
191
198
|
function serialize(key) {
|
|
192
199
|
const spec = getAlgorithm(key.algorithm);
|
|
193
200
|
if (key.bytes.length !== keyLengthFor(spec, key.use)) {
|
|
194
|
-
throw new PqcError("INVALID_KEY",
|
|
201
|
+
throw new PqcError("INVALID_KEY", `${key.algorithm} ${key.use} key has invalid length`);
|
|
195
202
|
}
|
|
196
203
|
return `${SERIAL_PREFIX}.${key.algorithm}.${key.use}.${toBase64Url(key.bytes)}`;
|
|
197
204
|
}
|
|
@@ -200,13 +207,13 @@ function deserialize(serialized) {
|
|
|
200
207
|
if (parts.length !== 4 || parts[0] !== SERIAL_PREFIX) {
|
|
201
208
|
throw new PqcError(
|
|
202
209
|
"INVALID_SERIALIZED_KEY",
|
|
203
|
-
"
|
|
210
|
+
"Expected format: pqcv1.<algorithm>.<use>.<base64url>"
|
|
204
211
|
);
|
|
205
212
|
}
|
|
206
213
|
const [, algorithm, use, encoded] = parts;
|
|
207
214
|
const spec = getAlgorithm(algorithm);
|
|
208
215
|
if (use !== "public" && use !== "secret") {
|
|
209
|
-
throw new PqcError("INVALID_SERIALIZED_KEY", `
|
|
216
|
+
throw new PqcError("INVALID_SERIALIZED_KEY", `Unknown key use: ${use}`);
|
|
210
217
|
}
|
|
211
218
|
let bytes;
|
|
212
219
|
try {
|
|
@@ -214,14 +221,14 @@ function deserialize(serialized) {
|
|
|
214
221
|
} catch (cause) {
|
|
215
222
|
throw new PqcError(
|
|
216
223
|
"INVALID_SERIALIZED_KEY",
|
|
217
|
-
cause instanceof Error ? cause.message : "base64url
|
|
224
|
+
cause instanceof Error ? cause.message : "Invalid base64url"
|
|
218
225
|
);
|
|
219
226
|
}
|
|
220
227
|
const key = { algorithm, use, bytes };
|
|
221
228
|
if (bytes.length !== keyLengthFor(spec, key.use)) {
|
|
222
229
|
throw new PqcError(
|
|
223
230
|
"INVALID_KEY",
|
|
224
|
-
|
|
231
|
+
`${algorithm} ${use} key must be ${keyLengthFor(spec, key.use)} bytes, got ${bytes.length}`
|
|
225
232
|
);
|
|
226
233
|
}
|
|
227
234
|
return key;
|
|
@@ -251,7 +258,7 @@ async function verify(data, signature, publicKey, options) {
|
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
// src/index.ts
|
|
254
|
-
var version = "0.
|
|
261
|
+
var version = "0.2.0";
|
|
255
262
|
var SUPPORTED_ALGORITHMS = ["ml-kem-768", "ml-dsa-65"];
|
|
256
263
|
var pqc = {
|
|
257
264
|
keys: { generate, serialize, deserialize },
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/encrypt.ts","../src/algorithms.ts","../src/errors.ts","../src/keys.ts","../src/base64url.ts","../src/sign.ts","../src/index.ts"],"sourcesContent":["import { gcm } from '@noble/ciphers/aes.js';\nimport { randomBytes } from '@noble/post-quantum/utils.js';\n\nimport { KEM_ALGORITHMS, requireKey } from './algorithms.js';\nimport { PqcError } from './errors.js';\nimport type { PublicKey, SecretKey } from './types.js';\n\nconst FORMAT_VERSION = 1;\nconst NONCE_LENGTH = 12;\nconst GCM_TAG_LENGTH = 16;\n\nconst utf8 = new TextEncoder();\n\nfunction toBytes(data: Uint8Array | string): Uint8Array {\n return typeof data === 'string' ? utf8.encode(data) : data;\n}\n\n/**\n * Cifrado híbrido: encapsula un secreto con ML-KEM-768 (FIPS 203) y cifra los\n * datos con AES-256-GCM usando ese secreto. El resultado es un único\n * `Uint8Array` autocontenido que solo {@link decrypt} puede abrir.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate();\n * const ciphertext = await pqc.encrypt('dato sensible', pair.publicKey);\n * ```\n */\nexport async function encrypt(\n data: Uint8Array | string,\n publicKey: PublicKey<'ml-kem-768'>,\n): Promise<Uint8Array> {\n const spec = requireKey(publicKey, 'kem', 'public', 'encrypt');\n const plaintext = toBytes(data);\n\n const { cipherText, sharedSecret } = spec.kem.encapsulate(publicKey.bytes);\n const nonce = randomBytes(NONCE_LENGTH);\n const sealed = gcm(sharedSecret, nonce).encrypt(plaintext);\n\n const out = new Uint8Array(2 + cipherText.length + nonce.length + sealed.length);\n out[0] = FORMAT_VERSION;\n out[1] = spec.headerId;\n out.set(cipherText, 2);\n out.set(nonce, 2 + cipherText.length);\n out.set(sealed, 2 + cipherText.length + nonce.length);\n return Promise.resolve(out);\n}\n\n/**\n * Descifra un ciphertext producido por {@link encrypt}. Si el ciphertext fue\n * manipulado o la key no corresponde, lanza {@link PqcError} con código\n * `DECRYPTION_FAILED` — nunca devuelve datos corruptos.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\n * new TextDecoder().decode(plaintext);\n * ```\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n secretKey: SecretKey<'ml-kem-768'>,\n): Promise<Uint8Array> {\n const spec = requireKey(secretKey, 'kem', 'secret', 'decrypt');\n\n const minLength = 2 + spec.ciphertextLength + NONCE_LENGTH + GCM_TAG_LENGTH;\n if (ciphertext.length < minLength) {\n throw new PqcError('INVALID_CIPHERTEXT', 'Ciphertext truncado o no producido por pqc.encrypt');\n }\n if (ciphertext[0] !== FORMAT_VERSION || ciphertext[1] !== spec.headerId) {\n throw new PqcError(\n 'INVALID_CIPHERTEXT',\n 'Header desconocido: el ciphertext no corresponde a esta versión o algoritmo',\n );\n }\n\n const kemCiphertext = ciphertext.subarray(2, 2 + spec.ciphertextLength);\n const nonce = ciphertext.subarray(\n 2 + spec.ciphertextLength,\n 2 + spec.ciphertextLength + NONCE_LENGTH,\n );\n const sealed = ciphertext.subarray(2 + spec.ciphertextLength + NONCE_LENGTH);\n\n const sharedSecret = spec.kem.decapsulate(kemCiphertext, secretKey.bytes);\n try {\n return Promise.resolve(gcm(sharedSecret, nonce).decrypt(sealed));\n } catch {\n throw new PqcError(\n 'DECRYPTION_FAILED',\n 'No se pudo descifrar: ciphertext manipulado o secret key incorrecta',\n );\n }\n}\n\n/** Algoritmos KEM disponibles, exportado para introspección. */\nexport const KEM_NAMES = Object.keys(KEM_ALGORITHMS);\n","import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';\nimport { ml_kem768 } from '@noble/post-quantum/ml-kem.js';\n\nimport { PqcError } from './errors.js';\nimport type { Algorithm, KemAlgorithm, KeyUse, PqcKey, SignatureAlgorithm } from './types.js';\n\ninterface AlgorithmSpec {\n readonly seedLength: number;\n readonly publicKeyLength: number;\n readonly secretKeyLength: number;\n}\n\nexport interface KemSpec extends AlgorithmSpec {\n readonly kind: 'kem';\n readonly headerId: number;\n readonly ciphertextLength: number;\n readonly kem: typeof ml_kem768;\n}\n\nexport interface SignerSpec extends AlgorithmSpec {\n readonly kind: 'signer';\n readonly signatureLength: number;\n readonly signer: typeof ml_dsa65;\n}\n\nexport const KEM_ALGORITHMS: Record<KemAlgorithm, KemSpec> = {\n 'ml-kem-768': {\n kind: 'kem',\n headerId: 1,\n kem: ml_kem768,\n seedLength: 64,\n publicKeyLength: 1184,\n secretKeyLength: 2400,\n ciphertextLength: 1088,\n },\n};\n\nexport const SIGNATURE_ALGORITHMS: Record<SignatureAlgorithm, SignerSpec> = {\n 'ml-dsa-65': {\n kind: 'signer',\n signer: ml_dsa65,\n seedLength: 32,\n publicKeyLength: 1952,\n secretKeyLength: 4032,\n signatureLength: 3309,\n },\n};\n\nexport const ALGORITHMS: Record<Algorithm, KemSpec | SignerSpec> = {\n ...KEM_ALGORITHMS,\n ...SIGNATURE_ALGORITHMS,\n};\n\nexport function getAlgorithm(algorithm: string): KemSpec | SignerSpec {\n const spec = (ALGORITHMS as Record<string, KemSpec | SignerSpec>)[algorithm];\n if (!spec) {\n throw new PqcError('UNSUPPORTED_ALGORITHM', `Algoritmo no soportado: ${algorithm}`);\n }\n return spec;\n}\n\nexport function keyLengthFor(spec: KemSpec | SignerSpec, use: KeyUse): number {\n return use === 'public' ? spec.publicKeyLength : spec.secretKeyLength;\n}\n\n/** Valida algoritmo, uso y longitud de una key antes de operar con ella. */\nexport function requireKey<K extends 'kem' | 'signer'>(\n key: PqcKey,\n kind: K,\n use: KeyUse,\n operation: string,\n): K extends 'kem' ? KemSpec : SignerSpec {\n const spec = getAlgorithm(key.algorithm);\n if (spec.kind !== kind) {\n throw new PqcError(\n 'WRONG_ALGORITHM',\n `${operation} requiere una key ${kind === 'kem' ? 'ML-KEM' : 'ML-DSA'}, recibió ${key.algorithm}`,\n );\n }\n if (key.use !== use) {\n throw new PqcError('WRONG_KEY_USE', `${operation} requiere la key ${use}, recibió ${key.use}`);\n }\n if (key.bytes.length !== keyLengthFor(spec, use)) {\n throw new PqcError(\n 'INVALID_KEY',\n `Key ${key.algorithm} ${use} con longitud inválida: ${key.bytes.length}`,\n );\n }\n return spec as K extends 'kem' ? KemSpec : SignerSpec;\n}\n","/** Códigos de error que puede emitir el SDK. */\nexport type PqcErrorCode =\n | 'UNSUPPORTED_ALGORITHM'\n | 'WRONG_ALGORITHM'\n | 'WRONG_KEY_USE'\n | 'INVALID_KEY'\n | 'INVALID_SERIALIZED_KEY'\n | 'INVALID_CIPHERTEXT'\n | 'DECRYPTION_FAILED';\n\n/**\n * Error tipado del SDK. Toda falla esperable expone un `code` estable para\n * manejarla programáticamente sin parsear mensajes.\n *\n * @example\n * ```ts\n * import { PqcError, pqc } from '@pqc-sdk/core';\n *\n * try {\n * await pqc.decrypt(ciphertext, secretKey);\n * } catch (error) {\n * if (error instanceof PqcError && error.code === 'DECRYPTION_FAILED') {\n * // ciphertext manipulado o key incorrecta\n * }\n * }\n * ```\n */\nexport class PqcError extends Error {\n readonly code: PqcErrorCode;\n\n constructor(code: PqcErrorCode, message: string) {\n super(message);\n this.name = 'PqcError';\n this.code = code;\n }\n}\n","import { randomBytes } from '@noble/post-quantum/utils.js';\n\nimport { getAlgorithm, keyLengthFor } from './algorithms.js';\nimport { fromBase64Url, toBase64Url } from './base64url.js';\nimport { PqcError } from './errors.js';\nimport type { Algorithm, KeyPair, PqcKey } from './types.js';\n\nconst SERIAL_PREFIX = 'pqcv1';\n\n/** Opciones de {@link generate}. */\nexport interface GenerateOptions<A extends Algorithm = Algorithm> {\n /** Algoritmo del par. Default: `'ml-kem-768'` (cifrado). */\n readonly algorithm?: A;\n}\n\n/**\n * Genera un par de keys post-cuánticas. Sin opciones genera ML-KEM-768,\n * listo para `pqc.encrypt`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const encryption = await pqc.keys.generate();\n * const signing = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * ```\n */\nexport async function generate(): Promise<KeyPair<'ml-kem-768'>>;\nexport async function generate<A extends Algorithm>(\n options: GenerateOptions<A> & { algorithm: A },\n): Promise<KeyPair<A>>;\nexport async function generate(options?: GenerateOptions): Promise<KeyPair>;\nexport async function generate(options?: GenerateOptions): Promise<KeyPair> {\n const algorithm = options?.algorithm ?? 'ml-kem-768';\n const spec = getAlgorithm(algorithm);\n return Promise.resolve(generateKeyPairFromSeed(algorithm, randomBytes(spec.seedLength)));\n}\n\n/**\n * Generación determinística a partir de una seed. Uso interno y de tests\n * (vectores NIST). Para uso normal preferir {@link generate}.\n */\nexport function generateKeyPairFromSeed(algorithm: Algorithm, seed: Uint8Array): KeyPair {\n const spec = getAlgorithm(algorithm);\n if (seed.length !== spec.seedLength) {\n throw new PqcError(\n 'INVALID_KEY',\n `Seed de ${algorithm} debe medir ${spec.seedLength} bytes, recibió ${seed.length}`,\n );\n }\n const material = spec.kind === 'kem' ? spec.kem.keygen(seed) : spec.signer.keygen(seed);\n return {\n algorithm,\n publicKey: { algorithm, use: 'public', bytes: material.publicKey },\n secretKey: { algorithm, use: 'secret', bytes: material.secretKey },\n };\n}\n\n/**\n * Serializa una key a un string portable: `pqcv1.<algoritmo>.<uso>.<base64url>`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate();\n * const token = pqc.keys.serialize(pair.publicKey);\n * // \"pqcv1.ml-kem-768.public.h1q3…\"\n * ```\n */\nexport function serialize(key: PqcKey): string {\n const spec = getAlgorithm(key.algorithm);\n if (key.bytes.length !== keyLengthFor(spec, key.use)) {\n throw new PqcError('INVALID_KEY', `Key ${key.algorithm} ${key.use} con longitud inválida`);\n }\n return `${SERIAL_PREFIX}.${key.algorithm}.${key.use}.${toBase64Url(key.bytes)}`;\n}\n\n/**\n * Reconstruye una key desde el formato de {@link serialize}. Valida versión,\n * algoritmo, uso y longitud; ante cualquier problema lanza {@link PqcError}\n * con código `INVALID_SERIALIZED_KEY` o `INVALID_KEY`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const publicKey = pqc.keys.deserialize(tokenRecibidoDelCliente);\n * const ciphertext = await pqc.encrypt(payload, publicKey);\n * ```\n */\nexport function deserialize(serialized: string): PqcKey {\n const parts = serialized.split('.');\n if (parts.length !== 4 || parts[0] !== SERIAL_PREFIX) {\n throw new PqcError(\n 'INVALID_SERIALIZED_KEY',\n 'Formato esperado: pqcv1.<algoritmo>.<uso>.<base64url>',\n );\n }\n const [, algorithm, use, encoded] = parts as [string, string, string, string];\n const spec = getAlgorithm(algorithm);\n if (use !== 'public' && use !== 'secret') {\n throw new PqcError('INVALID_SERIALIZED_KEY', `Uso de key desconocido: ${use}`);\n }\n let bytes: Uint8Array;\n try {\n bytes = fromBase64Url(encoded);\n } catch (cause) {\n throw new PqcError(\n 'INVALID_SERIALIZED_KEY',\n cause instanceof Error ? cause.message : 'base64url inválido',\n );\n }\n const key: PqcKey = { algorithm: algorithm as Algorithm, use, bytes };\n if (bytes.length !== keyLengthFor(spec, key.use)) {\n throw new PqcError(\n 'INVALID_KEY',\n `Key ${algorithm} ${use} debe medir ${keyLengthFor(spec, key.use)} bytes, midió ${bytes.length}`,\n );\n }\n return key;\n}\n","const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';\n\nconst CHAR_TO_VALUE = new Map<string, number>([...ALPHABET].map((c, i) => [c, i]));\n\n/** Codifica bytes a base64url sin padding. Implementación pura, sin Buffer/btoa. */\nexport function toBase64Url(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i]!;\n const b1 = bytes[i + 1];\n const b2 = bytes[i + 2];\n out += ALPHABET[b0 >> 2]!;\n out += ALPHABET[((b0 & 0x03) << 4) | ((b1 ?? 0) >> 4)]!;\n if (b1 !== undefined) out += ALPHABET[((b1 & 0x0f) << 2) | ((b2 ?? 0) >> 6)]!;\n if (b2 !== undefined) out += ALPHABET[b2 & 0x3f]!;\n }\n return out;\n}\n\n/** Decodifica base64url sin padding. Lanza TypeError ante caracteres inválidos. */\nexport function fromBase64Url(encoded: string): Uint8Array {\n if (encoded.length % 4 === 1) {\n throw new TypeError('base64url inválido: longitud imposible');\n }\n const out = new Uint8Array(Math.floor((encoded.length * 3) / 4));\n let outIndex = 0;\n let buffer = 0;\n let bits = 0;\n for (const char of encoded) {\n const value = CHAR_TO_VALUE.get(char);\n if (value === undefined) {\n throw new TypeError(`base64url inválido: carácter ${JSON.stringify(char)}`);\n }\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[outIndex++] = (buffer >> bits) & 0xff;\n }\n }\n return out;\n}\n","import { requireKey } from './algorithms.js';\nimport type { PublicKey, SecretKey, SignatureOptions } from './types.js';\n\nconst utf8 = new TextEncoder();\n\nfunction toBytes(data: Uint8Array | string): Uint8Array {\n return typeof data === 'string' ? utf8.encode(data) : data;\n}\n\nfunction toNobleOptions(options?: SignatureOptions): { context: Uint8Array } | undefined {\n return options?.context ? { context: options.context } : undefined;\n}\n\n/**\n * Firma datos con ML-DSA-65 (FIPS 204), modo hedged (firma aleatorizada,\n * el default del estándar). Devuelve la firma de 3309 bytes.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * const signature = await pqc.sign(documento, pair.secretKey);\n * ```\n */\nexport async function sign(\n data: Uint8Array | string,\n secretKey: SecretKey<'ml-dsa-65'>,\n options?: SignatureOptions,\n): Promise<Uint8Array> {\n const spec = requireKey(secretKey, 'signer', 'secret', 'sign');\n return Promise.resolve(spec.signer.sign(toBytes(data), secretKey.bytes, toNobleOptions(options)));\n}\n\n/**\n * Verifica una firma ML-DSA-65. Devuelve `false` ante firmas inválidas o\n * malformadas (nunca lanza por una firma corrupta); solo lanza si la key\n * no es ML-DSA.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const valid = await pqc.verify(documento, signature, pair.publicKey);\n * if (!valid) throw new Error('firma inválida');\n * ```\n */\nexport async function verify(\n data: Uint8Array | string,\n signature: Uint8Array,\n publicKey: PublicKey<'ml-dsa-65'>,\n options?: SignatureOptions,\n): Promise<boolean> {\n const spec = requireKey(publicKey, 'signer', 'public', 'verify');\n try {\n return Promise.resolve(\n spec.signer.verify(signature, toBytes(data), publicKey.bytes, toNobleOptions(options)),\n );\n } catch {\n return false;\n }\n}\n","import { encrypt, decrypt } from './encrypt.js';\nimport { deserialize, generate, serialize } from './keys.js';\nimport { sign, verify } from './sign.js';\n\nexport { PqcError, type PqcErrorCode } from './errors.js';\nexport type { GenerateOptions } from './keys.js';\nexport type {\n Algorithm,\n KemAlgorithm,\n KeyPair,\n KeyUse,\n PqcKey,\n PublicKey,\n SecretKey,\n SignatureAlgorithm,\n SignatureOptions,\n} from './types.js';\nexport { encrypt, decrypt, sign, verify, generate, serialize, deserialize };\n\n// Inyectada en build time desde el package.json (`define` en tsup.config.ts y vitest.config.ts).\ndeclare const __PQC_CORE_VERSION__: string;\n\n/**\n * Versión del SDK.\n *\n * @example\n * ```ts\n * import { version } from '@pqc-sdk/core';\n *\n * console.log(version); // p. ej. \"0.1.0\"\n * ```\n */\nexport const version = __PQC_CORE_VERSION__;\n\n/**\n * Algoritmos PQC implementados (FIPS 203 y FIPS 204).\n *\n * @example\n * ```ts\n * import { SUPPORTED_ALGORITHMS } from '@pqc-sdk/core';\n *\n * SUPPORTED_ALGORITHMS.includes('ml-kem-768'); // true\n * ```\n */\nexport const SUPPORTED_ALGORITHMS = ['ml-kem-768', 'ml-dsa-65'] as const;\n\nexport type SupportedAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number];\n\n/**\n * Punto de entrada del SDK: cifrado híbrido post-cuántico y firmas digitales\n * con defaults seguros, sin configuración.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * // Cifrado (ML-KEM-768 + AES-256-GCM)\n * const pair = await pqc.keys.generate();\n * const ciphertext = await pqc.encrypt('hola', pair.publicKey);\n * const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\n *\n * // Firmas (ML-DSA-65)\n * const signer = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * const signature = await pqc.sign('documento', signer.secretKey);\n * await pqc.verify('documento', signature, signer.publicKey); // true\n * ```\n */\nexport const pqc = {\n keys: { generate, serialize, deserialize },\n encrypt,\n decrypt,\n sign,\n verify,\n} as const;\n"],"mappings":"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","names":["randomBytes","randomBytes","utf8","toBytes"]}
|
|
1
|
+
{"version":3,"sources":["../src/encrypt.ts","../src/algorithms.ts","../src/errors.ts","../src/keys.ts","../src/base64url.ts","../src/sign.ts","../src/index.ts"],"sourcesContent":["import { gcm } from '@noble/ciphers/aes.js';\nimport { randomBytes } from '@noble/post-quantum/utils.js';\n\nimport { KEM_ALGORITHMS, requireKey } from './algorithms.js';\nimport { PqcError } from './errors.js';\nimport type { PublicKey, SecretKey } from './types.js';\n\nconst FORMAT_VERSION = 1;\nconst NONCE_LENGTH = 12;\nconst GCM_TAG_LENGTH = 16;\n\nconst utf8 = new TextEncoder();\n\nfunction toBytes(data: Uint8Array | string): Uint8Array {\n return typeof data === 'string' ? utf8.encode(data) : data;\n}\n\n/**\n * Hybrid encryption: encapsulates a secret with ML-KEM-768 (FIPS 203) and\n * encrypts the data with AES-256-GCM using that secret. The result is a\n * single self-contained `Uint8Array` that only {@link decrypt} can open.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate();\n * const ciphertext = await pqc.encrypt('sensitive data', pair.publicKey);\n * ```\n */\nexport async function encrypt(\n data: Uint8Array | string,\n publicKey: PublicKey<'ml-kem-768'>,\n): Promise<Uint8Array> {\n const spec = requireKey(publicKey, 'kem', 'public', 'encrypt');\n const plaintext = toBytes(data);\n\n const { cipherText, sharedSecret } = spec.kem.encapsulate(publicKey.bytes);\n const nonce = randomBytes(NONCE_LENGTH);\n\n // Bind the 2-byte header (FORMAT_VERSION, headerId) as AES-GCM additional\n // authenticated data so it is covered by the GCM tag (see decrypt).\n const header = new Uint8Array([FORMAT_VERSION, spec.headerId]);\n const sealed = gcm(sharedSecret, nonce, header).encrypt(plaintext);\n\n const out = new Uint8Array(2 + cipherText.length + nonce.length + sealed.length);\n out.set(header, 0);\n out.set(cipherText, 2);\n out.set(nonce, 2 + cipherText.length);\n out.set(sealed, 2 + cipherText.length + nonce.length);\n return Promise.resolve(out);\n}\n\n/**\n * Decrypts a ciphertext produced by {@link encrypt}. If the ciphertext was\n * tampered with or the key does not match, it throws {@link PqcError} with\n * code `DECRYPTION_FAILED` — it never returns corrupted data.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\n * new TextDecoder().decode(plaintext);\n * ```\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n secretKey: SecretKey<'ml-kem-768'>,\n): Promise<Uint8Array> {\n const spec = requireKey(secretKey, 'kem', 'secret', 'decrypt');\n\n const minLength = 2 + spec.ciphertextLength + NONCE_LENGTH + GCM_TAG_LENGTH;\n if (ciphertext.length < minLength) {\n throw new PqcError(\n 'INVALID_CIPHERTEXT',\n 'Ciphertext is truncated or was not produced by pqc.encrypt',\n );\n }\n // This equality check is fail-fast input validation: it discriminates the\n // version and algorithm with a clear INVALID_CIPHERTEXT error before any\n // cryptographic work. The AAD binding below is the *cryptographic* integrity\n // of the header — it becomes the only line of defence once more versions or\n // algorithms share this layout (a tampered-but-known header would pass this\n // check yet fail the GCM tag). Do not remove either guard in a refactor.\n if (ciphertext[0] !== FORMAT_VERSION || ciphertext[1] !== spec.headerId) {\n throw new PqcError(\n 'INVALID_CIPHERTEXT',\n 'Unknown header: the ciphertext does not match this version or algorithm',\n );\n }\n\n // Reconstruct the AAD from the header bytes actually present in the message\n // so AES-GCM authenticates them as part of the tag.\n const header = ciphertext.subarray(0, 2);\n const kemCiphertext = ciphertext.subarray(2, 2 + spec.ciphertextLength);\n const nonce = ciphertext.subarray(\n 2 + spec.ciphertextLength,\n 2 + spec.ciphertextLength + NONCE_LENGTH,\n );\n const sealed = ciphertext.subarray(2 + spec.ciphertextLength + NONCE_LENGTH);\n\n const sharedSecret = spec.kem.decapsulate(kemCiphertext, secretKey.bytes);\n try {\n return Promise.resolve(gcm(sharedSecret, nonce, header).decrypt(sealed));\n } catch {\n throw new PqcError(\n 'DECRYPTION_FAILED',\n 'Decryption failed: tampered ciphertext or wrong secret key',\n );\n }\n}\n\n/** Available KEM algorithms, exported for introspection. */\nexport const KEM_NAMES = Object.keys(KEM_ALGORITHMS);\n","import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';\nimport { ml_kem768 } from '@noble/post-quantum/ml-kem.js';\n\nimport { PqcError } from './errors.js';\nimport type { Algorithm, KemAlgorithm, KeyUse, PqcKey, SignatureAlgorithm } from './types.js';\n\ninterface AlgorithmSpec {\n readonly seedLength: number;\n readonly publicKeyLength: number;\n readonly secretKeyLength: number;\n}\n\nexport interface KemSpec extends AlgorithmSpec {\n readonly kind: 'kem';\n readonly headerId: number;\n readonly ciphertextLength: number;\n readonly kem: typeof ml_kem768;\n}\n\nexport interface SignerSpec extends AlgorithmSpec {\n readonly kind: 'signer';\n readonly signatureLength: number;\n readonly signer: typeof ml_dsa65;\n}\n\nexport const KEM_ALGORITHMS: Record<KemAlgorithm, KemSpec> = {\n 'ml-kem-768': {\n kind: 'kem',\n headerId: 1,\n kem: ml_kem768,\n seedLength: 64,\n publicKeyLength: 1184,\n secretKeyLength: 2400,\n ciphertextLength: 1088,\n },\n};\n\nexport const SIGNATURE_ALGORITHMS: Record<SignatureAlgorithm, SignerSpec> = {\n 'ml-dsa-65': {\n kind: 'signer',\n signer: ml_dsa65,\n seedLength: 32,\n publicKeyLength: 1952,\n secretKeyLength: 4032,\n signatureLength: 3309,\n },\n};\n\nexport const ALGORITHMS: Record<Algorithm, KemSpec | SignerSpec> = {\n ...KEM_ALGORITHMS,\n ...SIGNATURE_ALGORITHMS,\n};\n\nexport function getAlgorithm(algorithm: string): KemSpec | SignerSpec {\n const spec = (ALGORITHMS as Record<string, KemSpec | SignerSpec>)[algorithm];\n if (!spec) {\n throw new PqcError('UNSUPPORTED_ALGORITHM', `Unsupported algorithm: ${algorithm}`);\n }\n return spec;\n}\n\nexport function keyLengthFor(spec: KemSpec | SignerSpec, use: KeyUse): number {\n return use === 'public' ? spec.publicKeyLength : spec.secretKeyLength;\n}\n\n/** Validates a key's algorithm, use and length before operating with it. */\nexport function requireKey<K extends 'kem' | 'signer'>(\n key: PqcKey,\n kind: K,\n use: KeyUse,\n operation: string,\n): K extends 'kem' ? KemSpec : SignerSpec {\n const spec = getAlgorithm(key.algorithm);\n if (spec.kind !== kind) {\n throw new PqcError(\n 'WRONG_ALGORITHM',\n `${operation} requires an ${kind === 'kem' ? 'ML-KEM' : 'ML-DSA'} key, got ${key.algorithm}`,\n );\n }\n if (key.use !== use) {\n throw new PqcError('WRONG_KEY_USE', `${operation} requires the ${use} key, got ${key.use}`);\n }\n if (key.bytes.length !== keyLengthFor(spec, use)) {\n throw new PqcError(\n 'INVALID_KEY',\n `${key.algorithm} ${use} key has invalid length: ${key.bytes.length}`,\n );\n }\n return spec as K extends 'kem' ? KemSpec : SignerSpec;\n}\n","/** Error codes the SDK can emit. */\nexport type PqcErrorCode =\n | 'UNSUPPORTED_ALGORITHM'\n | 'WRONG_ALGORITHM'\n | 'WRONG_KEY_USE'\n | 'INVALID_KEY'\n | 'INVALID_SERIALIZED_KEY'\n | 'INVALID_CIPHERTEXT'\n | 'DECRYPTION_FAILED';\n\n/**\n * Typed SDK error. Every expected failure exposes a stable `code` so it can\n * be handled programmatically without parsing messages.\n *\n * @example\n * ```ts\n * import { PqcError, pqc } from '@pqc-sdk/core';\n *\n * try {\n * await pqc.decrypt(ciphertext, secretKey);\n * } catch (error) {\n * if (error instanceof PqcError && error.code === 'DECRYPTION_FAILED') {\n * // tampered ciphertext or wrong key\n * }\n * }\n * ```\n */\nexport class PqcError extends Error {\n readonly code: PqcErrorCode;\n\n constructor(code: PqcErrorCode, message: string) {\n super(message);\n this.name = 'PqcError';\n this.code = code;\n }\n}\n","import { randomBytes } from '@noble/post-quantum/utils.js';\n\nimport { getAlgorithm, keyLengthFor } from './algorithms.js';\nimport { fromBase64Url, toBase64Url } from './base64url.js';\nimport { PqcError } from './errors.js';\nimport type { Algorithm, KeyPair, PqcKey } from './types.js';\n\nconst SERIAL_PREFIX = 'pqcv1';\n\n/** Options for {@link generate}. */\nexport interface GenerateOptions<A extends Algorithm = Algorithm> {\n /** Algorithm of the pair. Default: `'ml-kem-768'` (encryption). */\n readonly algorithm?: A;\n}\n\n/**\n * Generates a post-quantum key pair. With no options it generates ML-KEM-768,\n * ready for `pqc.encrypt`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const encryption = await pqc.keys.generate();\n * const signing = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * ```\n */\nexport async function generate(): Promise<KeyPair<'ml-kem-768'>>;\nexport async function generate<A extends Algorithm>(\n options: GenerateOptions<A> & { algorithm: A },\n): Promise<KeyPair<A>>;\nexport async function generate(options?: GenerateOptions): Promise<KeyPair>;\nexport async function generate(options?: GenerateOptions): Promise<KeyPair> {\n const algorithm = options?.algorithm ?? 'ml-kem-768';\n const spec = getAlgorithm(algorithm);\n return Promise.resolve(generateKeyPairFromSeed(algorithm, randomBytes(spec.seedLength)));\n}\n\n/**\n * Deterministic generation from a seed. Internal and test use (NIST vectors).\n * Prefer {@link generate} for normal use.\n */\nexport function generateKeyPairFromSeed(algorithm: Algorithm, seed: Uint8Array): KeyPair {\n const spec = getAlgorithm(algorithm);\n if (seed.length !== spec.seedLength) {\n throw new PqcError(\n 'INVALID_KEY',\n `${algorithm} seed must be ${spec.seedLength} bytes, got ${seed.length}`,\n );\n }\n const material = spec.kind === 'kem' ? spec.kem.keygen(seed) : spec.signer.keygen(seed);\n return {\n algorithm,\n publicKey: { algorithm, use: 'public', bytes: material.publicKey },\n secretKey: { algorithm, use: 'secret', bytes: material.secretKey },\n };\n}\n\n/**\n * Serializes a key to a portable string: `pqcv1.<algorithm>.<use>.<base64url>`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate();\n * const token = pqc.keys.serialize(pair.publicKey);\n * // \"pqcv1.ml-kem-768.public.h1q3…\"\n * ```\n */\nexport function serialize(key: PqcKey): string {\n const spec = getAlgorithm(key.algorithm);\n if (key.bytes.length !== keyLengthFor(spec, key.use)) {\n throw new PqcError('INVALID_KEY', `${key.algorithm} ${key.use} key has invalid length`);\n }\n return `${SERIAL_PREFIX}.${key.algorithm}.${key.use}.${toBase64Url(key.bytes)}`;\n}\n\n/**\n * Rebuilds a key from the {@link serialize} format. Validates version,\n * algorithm, use and length; on any problem it throws {@link PqcError} with\n * code `INVALID_SERIALIZED_KEY` or `INVALID_KEY`.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const publicKey = pqc.keys.deserialize(tokenReceivedFromClient);\n * const ciphertext = await pqc.encrypt(payload, publicKey);\n * ```\n */\nexport function deserialize(serialized: string): PqcKey {\n const parts = serialized.split('.');\n if (parts.length !== 4 || parts[0] !== SERIAL_PREFIX) {\n throw new PqcError(\n 'INVALID_SERIALIZED_KEY',\n 'Expected format: pqcv1.<algorithm>.<use>.<base64url>',\n );\n }\n const [, algorithm, use, encoded] = parts as [string, string, string, string];\n const spec = getAlgorithm(algorithm);\n if (use !== 'public' && use !== 'secret') {\n throw new PqcError('INVALID_SERIALIZED_KEY', `Unknown key use: ${use}`);\n }\n let bytes: Uint8Array;\n try {\n bytes = fromBase64Url(encoded);\n } catch (cause) {\n throw new PqcError(\n 'INVALID_SERIALIZED_KEY',\n cause instanceof Error ? cause.message : 'Invalid base64url',\n );\n }\n const key: PqcKey = { algorithm: algorithm as Algorithm, use, bytes };\n if (bytes.length !== keyLengthFor(spec, key.use)) {\n throw new PqcError(\n 'INVALID_KEY',\n `${algorithm} ${use} key must be ${keyLengthFor(spec, key.use)} bytes, got ${bytes.length}`,\n );\n }\n return key;\n}\n","const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';\n\nconst CHAR_TO_VALUE = new Map<string, number>([...ALPHABET].map((c, i) => [c, i]));\n\n/** Encodes bytes to unpadded base64url. Pure implementation, no Buffer/btoa. */\nexport function toBase64Url(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i]!;\n const b1 = bytes[i + 1];\n const b2 = bytes[i + 2];\n out += ALPHABET[b0 >> 2]!;\n out += ALPHABET[((b0 & 0x03) << 4) | ((b1 ?? 0) >> 4)]!;\n if (b1 !== undefined) out += ALPHABET[((b1 & 0x0f) << 2) | ((b2 ?? 0) >> 6)]!;\n if (b2 !== undefined) out += ALPHABET[b2 & 0x3f]!;\n }\n return out;\n}\n\n/** Decodes unpadded base64url. Throws TypeError on invalid characters. */\nexport function fromBase64Url(encoded: string): Uint8Array {\n if (encoded.length % 4 === 1) {\n throw new TypeError('Invalid base64url: impossible length');\n }\n const out = new Uint8Array(Math.floor((encoded.length * 3) / 4));\n let outIndex = 0;\n let buffer = 0;\n let bits = 0;\n for (const char of encoded) {\n const value = CHAR_TO_VALUE.get(char);\n if (value === undefined) {\n throw new TypeError(`Invalid base64url: character ${JSON.stringify(char)}`);\n }\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[outIndex++] = (buffer >> bits) & 0xff;\n }\n }\n // Reject non-canonical input: the leftover bits of the final group (those that\n // do not complete a byte) must be zero, otherwise the string is not the\n // canonical encoding of any byte sequence.\n if ((buffer & ((1 << bits) - 1)) !== 0) {\n throw new TypeError('Invalid base64url: non-canonical trailing bits');\n }\n return out;\n}\n","import { requireKey } from './algorithms.js';\nimport type { PublicKey, SecretKey, SignatureOptions } from './types.js';\n\nconst utf8 = new TextEncoder();\n\nfunction toBytes(data: Uint8Array | string): Uint8Array {\n return typeof data === 'string' ? utf8.encode(data) : data;\n}\n\nfunction toNobleOptions(options?: SignatureOptions): { context: Uint8Array } | undefined {\n return options?.context ? { context: options.context } : undefined;\n}\n\n/**\n * Signs data with ML-DSA-65 (FIPS 204) in hedged mode (randomized signing,\n * the standard's default). Returns the 3309-byte signature.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const pair = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * const signature = await pqc.sign(document, pair.secretKey);\n * ```\n */\nexport async function sign(\n data: Uint8Array | string,\n secretKey: SecretKey<'ml-dsa-65'>,\n options?: SignatureOptions,\n): Promise<Uint8Array> {\n const spec = requireKey(secretKey, 'signer', 'secret', 'sign');\n return Promise.resolve(spec.signer.sign(toBytes(data), secretKey.bytes, toNobleOptions(options)));\n}\n\n/**\n * Verifies an ML-DSA-65 signature. Returns `false` for invalid or malformed\n * signatures (it never throws because of a corrupted signature); it only\n * throws if the key is not ML-DSA.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * const valid = await pqc.verify(document, signature, pair.publicKey);\n * if (!valid) throw new Error('invalid signature');\n * ```\n */\nexport async function verify(\n data: Uint8Array | string,\n signature: Uint8Array,\n publicKey: PublicKey<'ml-dsa-65'>,\n options?: SignatureOptions,\n): Promise<boolean> {\n const spec = requireKey(publicKey, 'signer', 'public', 'verify');\n try {\n return Promise.resolve(\n spec.signer.verify(signature, toBytes(data), publicKey.bytes, toNobleOptions(options)),\n );\n } catch {\n return false;\n }\n}\n","import { encrypt, decrypt } from './encrypt.js';\nimport { deserialize, generate, serialize } from './keys.js';\nimport { sign, verify } from './sign.js';\n\nexport { PqcError, type PqcErrorCode } from './errors.js';\nexport type { GenerateOptions } from './keys.js';\nexport type {\n Algorithm,\n KemAlgorithm,\n KeyPair,\n KeyUse,\n PqcKey,\n PublicKey,\n SecretKey,\n SignatureAlgorithm,\n SignatureOptions,\n} from './types.js';\nexport { encrypt, decrypt, sign, verify, generate, serialize, deserialize };\n\n// Injected at build time from package.json (`define` in tsup.config.ts and vitest.config.ts).\ndeclare const __PQC_CORE_VERSION__: string;\n\n/**\n * SDK version.\n *\n * @example\n * ```ts\n * import { version } from '@pqc-sdk/core';\n *\n * console.log(version); // e.g. \"0.1.0\"\n * ```\n */\nexport const version = __PQC_CORE_VERSION__;\n\n/**\n * Implemented PQC algorithms (FIPS 203 and FIPS 204).\n *\n * @example\n * ```ts\n * import { SUPPORTED_ALGORITHMS } from '@pqc-sdk/core';\n *\n * SUPPORTED_ALGORITHMS.includes('ml-kem-768'); // true\n * ```\n */\nexport const SUPPORTED_ALGORITHMS = ['ml-kem-768', 'ml-dsa-65'] as const;\n\nexport type SupportedAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number];\n\n/**\n * SDK entry point: post-quantum hybrid encryption and digital signatures\n * with safe defaults, zero configuration.\n *\n * @example\n * ```ts\n * import { pqc } from '@pqc-sdk/core';\n *\n * // Encryption (ML-KEM-768 + AES-256-GCM)\n * const pair = await pqc.keys.generate();\n * const ciphertext = await pqc.encrypt('hello', pair.publicKey);\n * const plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\n *\n * // Signatures (ML-DSA-65)\n * const signer = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\n * const signature = await pqc.sign('document', signer.secretKey);\n * await pqc.verify('document', signature, signer.publicKey); // true\n * ```\n */\nexport const pqc = {\n keys: { generate, serialize, deserialize },\n encrypt,\n decrypt,\n sign,\n verify,\n} as const;\n"],"mappings":"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","names":["randomBytes","randomBytes","utf8","toBytes"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pqc-sdk/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Post-quantum cryptography SDK for JS/TS (ML-KEM, ML-DSA)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@noble/post-quantum": "^0.6.1"
|
|
32
32
|
},
|
|
33
33
|
"license": "MIT",
|
|
34
|
-
"homepage": "https://github.
|
|
34
|
+
"homepage": "https://jeloercc.github.io/pqc-sdk/",
|
|
35
35
|
"bugs": "https://github.com/jeloercc/pqc-sdk/issues",
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|