@omnituum/pqc-shared 0.4.0 → 0.6.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.
Files changed (40) hide show
  1. package/dist/crypto/index.cjs +91 -54
  2. package/dist/crypto/index.d.cts +43 -11
  3. package/dist/crypto/index.d.ts +43 -11
  4. package/dist/crypto/index.js +87 -55
  5. package/dist/{decrypt-eSHlbh1j.d.cts → decrypt-O1iqFIDL.d.cts} +80 -6
  6. package/dist/{decrypt-eSHlbh1j.d.ts → decrypt-O1iqFIDL.d.ts} +80 -6
  7. package/dist/fs/index.cjs +286 -147
  8. package/dist/fs/index.d.cts +28 -7
  9. package/dist/fs/index.d.ts +28 -7
  10. package/dist/fs/index.js +277 -147
  11. package/dist/index.cjs +368 -229
  12. package/dist/index.d.cts +4 -4
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +362 -230
  15. package/dist/{integrity-ByMp24VA.d.cts → integrity-BkBDrHA-.d.cts} +12 -4
  16. package/dist/{integrity-BPvNsC50.d.ts → integrity-DsFJv5c-.d.ts} +12 -4
  17. package/dist/{types-Dx_AFqow.d.cts → types-DmnVueAY.d.cts} +15 -5
  18. package/dist/{types-Dx_AFqow.d.ts → types-DmnVueAY.d.ts} +15 -5
  19. package/dist/utils/index.cjs +7 -10
  20. package/dist/utils/index.d.cts +2 -2
  21. package/dist/utils/index.d.ts +2 -2
  22. package/dist/utils/index.js +7 -10
  23. package/dist/vault/index.cjs +8 -11
  24. package/dist/vault/index.d.cts +2 -2
  25. package/dist/vault/index.d.ts +2 -2
  26. package/dist/vault/index.js +8 -11
  27. package/package.json +19 -25
  28. package/src/crypto/dilithium.ts +4 -4
  29. package/src/crypto/hybrid.ts +182 -80
  30. package/src/crypto/index.ts +8 -1
  31. package/src/crypto/kyber.ts +20 -0
  32. package/src/fs/decrypt.ts +145 -68
  33. package/src/fs/encrypt.ts +161 -112
  34. package/src/fs/format.ts +110 -15
  35. package/src/fs/index.ts +4 -0
  36. package/src/fs/types.ts +77 -6
  37. package/src/index.ts +9 -0
  38. package/src/utils/integrity.ts +16 -15
  39. package/src/vault/manager.ts +1 -1
  40. package/src/version.ts +29 -7
@@ -201,6 +201,11 @@ function deriveKeyFromShared(shared, salt, info) {
201
201
  return hkdfSha256(shared, { salt: u8(salt), info: u8(info), length: 32 });
202
202
  }
203
203
  var KYBER_SUITE = "ML-KEM-1024-FIPS203";
204
+ var KYBER_PUBLIC_KEY_SIZE = 1568;
205
+ var KYBER_SECRET_KEY_SIZE = 3168;
206
+ var KYBER_CIPHERTEXT_SIZE = 1568;
207
+ var KYBER_SHARED_SECRET_SIZE = 32;
208
+ var KYBER_SEED_SIZE = 64;
204
209
  async function isKyberAvailable() {
205
210
  return true;
206
211
  }
@@ -256,8 +261,7 @@ async function loadDilithium() {
256
261
  const { ml_dsa65 } = await import('@noble/post-quantum/ml-dsa.js');
257
262
  dilithiumModule = ml_dsa65;
258
263
  return dilithiumModule;
259
- } catch (e) {
260
- console.warn("[Dilithium] Failed to load @noble/post-quantum:", e);
264
+ } catch {
261
265
  return null;
262
266
  }
263
267
  }
@@ -275,8 +279,7 @@ async function generateDilithiumKeypair() {
275
279
  publicB64: toB64(kp.publicKey),
276
280
  secretB64: toB64(kp.secretKey)
277
281
  };
278
- } catch (e) {
279
- console.warn("[Dilithium] Key generation failed:", e);
282
+ } catch {
280
283
  return null;
281
284
  }
282
285
  }
@@ -345,11 +348,13 @@ var DILITHIUM_SECRET_KEY_SIZE = 4032;
345
348
  var DILITHIUM_SIGNATURE_SIZE = 3309;
346
349
  var DILITHIUM_ALGORITHM = "ML-DSA-65";
347
350
  var ENVELOPE_VERSION = envelopeRegistry.OMNI_VERSIONS.HYBRID_V1;
351
+ var ENVELOPE_VERSION_V2 = envelopeRegistry.OMNI_VERSIONS.HYBRID_V2;
348
352
  var ENVELOPE_VERSION_LEGACY = envelopeRegistry.DEPRECATED_VERSIONS.PQC_DEMO_HYBRID_V1;
349
- var ENVELOPE_SUITE = "x25519+kyber768";
353
+ var ENVELOPE_SUITE_V2 = "x25519+mlkem1024";
350
354
  var ENVELOPE_AEAD = "xsalsa20poly1305";
351
355
  var SUPPORTED_ENVELOPE_VERSIONS = [
352
356
  ENVELOPE_VERSION,
357
+ ENVELOPE_VERSION_V2,
353
358
  ENVELOPE_VERSION_LEGACY
354
359
  ];
355
360
  var VersionMismatchError = class extends Error {
@@ -381,7 +386,6 @@ async function generateHybridIdentity(name) {
381
386
  const x25519 = generateX25519Keypair();
382
387
  const kyber = await generateKyberKeypair();
383
388
  if (!kyber) {
384
- console.error("Kyber key generation failed - library not available");
385
389
  return null;
386
390
  }
387
391
  return {
@@ -423,49 +427,74 @@ function getSecretKeys(identity) {
423
427
  kyberSecB64: identity.kyberSecB64
424
428
  };
425
429
  }
430
+ function combinedKekV2(kyberShared, x25519Shared, x25519EpkHex, kyberKemCtB64) {
431
+ const ikm = new Uint8Array(kyberShared.length + x25519Shared.length);
432
+ ikm.set(kyberShared, 0);
433
+ ikm.set(x25519Shared, kyberShared.length);
434
+ try {
435
+ return hkdfFlex(ikm, "omnituum/hybrid-v2", `wrap-ck|${x25519EpkHex}|${kyberKemCtB64}`);
436
+ } finally {
437
+ ikm.fill(0);
438
+ }
439
+ }
426
440
  async function hybridEncrypt(plaintext, recipientPublicKeys, sender) {
427
441
  const pt = typeof plaintext === "string" ? textEncoder.encode(plaintext) : plaintext;
428
442
  const CK = rand32();
429
- const contentNonce = rand24();
430
- const ciphertext = nacl__default.default.secretbox(pt, contentNonce, CK);
431
- const x25519EphKp = nacl__default.default.box.keyPair();
432
- const recipientX25519Pk = fromHex(recipientPublicKeys.x25519PubHex);
433
- const x25519Shared = nacl__default.default.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
434
- const x25519Kek = hkdfFlex(x25519Shared, "omnituum/x25519", "wrap-ck");
435
- const x25519WrapNonce = rand24();
436
- const x25519Wrapped = nacl__default.default.secretbox(CK, x25519WrapNonce, x25519Kek);
437
- const kyberResult = await kyberEncapsulate(recipientPublicKeys.kyberPubB64);
438
- const kyberKek = hkdfFlex(kyberResult.sharedSecret, "omnituum/kyber", "wrap-ck");
439
- const kyberWrapNonce = rand24();
440
- const kyberWrapped = nacl__default.default.secretbox(CK, kyberWrapNonce, kyberKek);
441
- return {
442
- v: ENVELOPE_VERSION,
443
- suite: ENVELOPE_SUITE,
444
- aead: ENVELOPE_AEAD,
445
- x25519Epk: toHex(x25519EphKp.publicKey),
446
- x25519Wrap: {
447
- nonce: b64(x25519WrapNonce),
448
- wrapped: b64(x25519Wrapped)
449
- },
450
- kyberKemCt: b64(kyberResult.ciphertext),
451
- kyberWrap: {
452
- nonce: b64(kyberWrapNonce),
453
- wrapped: b64(kyberWrapped)
454
- },
455
- contentNonce: b64(contentNonce),
456
- ciphertext: b64(ciphertext),
457
- meta: {
458
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
459
- senderName: sender?.name,
460
- senderId: sender?.id
461
- }
462
- };
443
+ try {
444
+ const contentNonce = rand24();
445
+ const ciphertext = nacl__default.default.secretbox(pt, contentNonce, CK);
446
+ const x25519EphKp = nacl__default.default.box.keyPair();
447
+ const recipientX25519Pk = fromHex(recipientPublicKeys.x25519PubHex);
448
+ const x25519Shared = nacl__default.default.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
449
+ const x25519EpkHex = toHex(x25519EphKp.publicKey);
450
+ const kyberResult = await kyberEncapsulate(recipientPublicKeys.kyberPubB64);
451
+ const kyberKemCtB64 = b64(kyberResult.ciphertext);
452
+ const kek = combinedKekV2(kyberResult.sharedSecret, x25519Shared, x25519EpkHex, kyberKemCtB64);
453
+ const ckWrapNonce = rand24();
454
+ const ckWrapped = nacl__default.default.secretbox(CK, ckWrapNonce, kek);
455
+ kek.fill(0);
456
+ x25519Shared.fill(0);
457
+ kyberResult.sharedSecret.fill(0);
458
+ x25519EphKp.secretKey.fill(0);
459
+ return {
460
+ v: ENVELOPE_VERSION_V2,
461
+ suite: ENVELOPE_SUITE_V2,
462
+ aead: ENVELOPE_AEAD,
463
+ x25519Epk: x25519EpkHex,
464
+ kyberKemCt: kyberKemCtB64,
465
+ ckWrap: {
466
+ nonce: b64(ckWrapNonce),
467
+ wrapped: b64(ckWrapped)
468
+ },
469
+ contentNonce: b64(contentNonce),
470
+ ciphertext: b64(ciphertext),
471
+ meta: {
472
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
473
+ senderName: sender?.name,
474
+ senderId: sender?.id
475
+ }
476
+ };
477
+ } finally {
478
+ CK.fill(0);
479
+ }
480
+ }
481
+ async function unwrapCkV2(envelope, secretKeys) {
482
+ const kyberShared = await kyberDecapsulate(envelope.kyberKemCt, secretKeys.kyberSecB64);
483
+ const ephPk = fromHex(envelope.x25519Epk);
484
+ const sk = fromHex(secretKeys.x25519SecHex);
485
+ const x25519Shared = nacl__default.default.scalarMult(sk, ephPk);
486
+ const kek = combinedKekV2(kyberShared, x25519Shared, envelope.x25519Epk, envelope.kyberKemCt);
487
+ kyberShared.fill(0);
488
+ x25519Shared.fill(0);
489
+ const CK = nacl__default.default.secretbox.open(ub64(envelope.ckWrap.wrapped), ub64(envelope.ckWrap.nonce), kek);
490
+ kek.fill(0);
491
+ if (!CK) {
492
+ throw new Error("Could not unwrap content key \u2014 combined-KEK authentication failed");
493
+ }
494
+ return CK;
463
495
  }
464
- async function hybridDecrypt(envelope, secretKeys) {
465
- const version = envelope.v;
466
- assertEnvelopeVersion(version);
496
+ async function unwrapCkV1(envelope, secretKeys, saltPrefix) {
467
497
  let CK = null;
468
- const saltPrefix = version === "pqc-demo.hybrid.v1" ? "pqc-demo" : "omnituum";
469
498
  try {
470
499
  const kyberShared = await kyberDecapsulate(envelope.kyberKemCt, secretKeys.kyberSecB64);
471
500
  const kyberKek = hkdfFlex(kyberShared, `${saltPrefix}/kyber`, "wrap-ck");
@@ -474,11 +503,7 @@ async function hybridDecrypt(envelope, secretKeys) {
474
503
  ub64(envelope.kyberWrap.nonce),
475
504
  kyberKek
476
505
  );
477
- if (CK) {
478
- console.log("[Hybrid] Decrypted using Kyber (post-quantum)");
479
- }
480
- } catch (e) {
481
- console.warn("[Hybrid] Kyber decapsulation failed:", e);
506
+ } catch {
482
507
  }
483
508
  if (!CK) {
484
509
  try {
@@ -491,21 +516,28 @@ async function hybridDecrypt(envelope, secretKeys) {
491
516
  ub64(envelope.x25519Wrap.nonce),
492
517
  x25519Kek
493
518
  );
494
- if (CK) {
495
- console.log("[Hybrid] Decrypted using X25519 (classical)");
496
- }
497
- } catch (e) {
498
- console.warn("[Hybrid] X25519 decryption failed:", e);
519
+ } catch {
499
520
  }
500
521
  }
501
522
  if (!CK) {
502
523
  throw new Error("Could not unwrap content key with either algorithm");
503
524
  }
525
+ return CK;
526
+ }
527
+ async function hybridDecrypt(envelope, secretKeys) {
528
+ const version = envelope.v;
529
+ assertEnvelopeVersion(version);
530
+ const CK = version === ENVELOPE_VERSION_V2 ? await unwrapCkV2(envelope, secretKeys) : await unwrapCkV1(
531
+ envelope,
532
+ secretKeys,
533
+ version === "pqc-demo.hybrid.v1" ? "pqc-demo" : "omnituum"
534
+ );
504
535
  const pt = nacl__default.default.secretbox.open(
505
536
  ub64(envelope.ciphertext),
506
537
  ub64(envelope.contentNonce),
507
538
  CK
508
539
  );
540
+ CK.fill(0);
509
541
  if (!pt) {
510
542
  throw new Error("Content authentication failed");
511
543
  }
@@ -689,6 +721,11 @@ exports.HKDF_SHA256_MAX_OUTPUT = HKDF_SHA256_MAX_OUTPUT;
689
721
  exports.HKDF_SHA256_OUTPUT_SIZE = HKDF_SHA256_OUTPUT_SIZE;
690
722
  exports.HKDF_SHA512_MAX_OUTPUT = HKDF_SHA512_MAX_OUTPUT;
691
723
  exports.HKDF_SHA512_OUTPUT_SIZE = HKDF_SHA512_OUTPUT_SIZE;
724
+ exports.KYBER_CIPHERTEXT_SIZE = KYBER_CIPHERTEXT_SIZE;
725
+ exports.KYBER_PUBLIC_KEY_SIZE = KYBER_PUBLIC_KEY_SIZE;
726
+ exports.KYBER_SECRET_KEY_SIZE = KYBER_SECRET_KEY_SIZE;
727
+ exports.KYBER_SEED_SIZE = KYBER_SEED_SIZE;
728
+ exports.KYBER_SHARED_SECRET_SIZE = KYBER_SHARED_SECRET_SIZE;
692
729
  exports.KYBER_SUITE = KYBER_SUITE;
693
730
  exports.POLY1305_TAG_SIZE = POLY1305_TAG_SIZE;
694
731
  exports.SECRETBOX_KEY_SIZE = SECRETBOX_KEY_SIZE;
@@ -1,4 +1,4 @@
1
- import { OmniHybridV1 } from '@omnituum/envelope-registry';
1
+ import { OmniHybridV2, OmniHybridV1 } from '@omnituum/envelope-registry';
2
2
  import * as _noble_ciphers_utils_js from '@noble/ciphers/utils.js';
3
3
 
4
4
  /**
@@ -35,6 +35,16 @@ interface KyberEncapsulation {
35
35
  /** Canonical suite identifier for new material produced by this package. */
36
36
  declare const KYBER_SUITE: "ML-KEM-1024-FIPS203";
37
37
  type KyberSuite = typeof KYBER_SUITE;
38
+ /** ML-KEM-1024 public key size (bytes). */
39
+ declare const KYBER_PUBLIC_KEY_SIZE = 1568;
40
+ /** ML-KEM-1024 secret key size (bytes). */
41
+ declare const KYBER_SECRET_KEY_SIZE = 3168;
42
+ /** ML-KEM-1024 ciphertext size (bytes). */
43
+ declare const KYBER_CIPHERTEXT_SIZE = 1568;
44
+ /** ML-KEM-1024 shared-secret size (bytes). */
45
+ declare const KYBER_SHARED_SECRET_SIZE = 32;
46
+ /** Seed size accepted by `generateKyberKeypairFromSeed` (bytes). */
47
+ declare const KYBER_SEED_SIZE = 64;
38
48
  /**
39
49
  * @deprecated Always returns true. Retained for API stability across the
40
50
  * 0.3.x → 0.4.x cut. The noble backend is statically imported and always
@@ -67,10 +77,16 @@ declare function kyberUnwrapKey(sharedSecret: Uint8Array, nonceB64: string, wrap
67
77
  *
68
78
  * Post-quantum secure hybrid encryption combining:
69
79
  * - X25519 ECDH (classical, proven security)
70
- * - Kyber ML-KEM-768 (post-quantum, NIST Level 3)
80
+ * - ML-KEM-1024 (post-quantum, FIPS 203, NIST Level 5)
71
81
  *
72
- * Both key exchanges must succeed for decryption, providing
73
- * security against both classical and quantum attacks.
82
+ * v2 (current write format): the content key is wrapped once, under a KEK
83
+ * derived from BOTH shared secrets (HKDF(ss_mlkem || ss_x25519) with
84
+ * transcript binding) — both key exchanges must succeed to decrypt, so
85
+ * breaking either primitive alone is insufficient.
86
+ *
87
+ * v1 (read-only legacy): wrapped the key independently under each
88
+ * primitive; either secret alone sufficed. Kept only for decrypting
89
+ * existing envelopes — see the 2026-07-05 security audit.
74
90
  */
75
91
 
76
92
  interface HybridIdentity {
@@ -106,12 +122,21 @@ interface HybridSecretKeys {
106
122
  kyberSecB64: string;
107
123
  }
108
124
 
109
- type HybridEnvelope = Omit<OmniHybridV1, 'meta'> & {
125
+ type HybridEnvelopeV1 = Omit<OmniHybridV1, 'meta'> & {
110
126
  meta: OmniHybridV1['meta'] & {
111
127
  senderName?: string;
112
128
  senderId?: string;
113
129
  };
114
130
  };
131
+
132
+ type HybridEnvelopeV2 = Omit<OmniHybridV2, 'meta'> & {
133
+ meta: OmniHybridV2['meta'] & {
134
+ senderName?: string;
135
+ senderId?: string;
136
+ };
137
+ };
138
+ /** Any hybrid envelope this module can decrypt. */
139
+ type HybridEnvelope = HybridEnvelopeV1 | HybridEnvelopeV2;
115
140
  /**
116
141
  * Generate a new hybrid identity with both X25519 and Kyber keys.
117
142
  */
@@ -129,7 +154,12 @@ declare function getPublicKeys(identity: HybridIdentity): HybridPublicKeys;
129
154
  */
130
155
  declare function getSecretKeys(identity: HybridIdentity): HybridSecretKeys;
131
156
  /**
132
- * Encrypt a message using hybrid X25519 + Kyber encryption.
157
+ * Encrypt a message using hybrid X25519 + ML-KEM-1024 encryption (v2).
158
+ *
159
+ * The content key is wrapped exactly once, under a KEK derived from both
160
+ * shared secrets together — breaking either primitive alone is insufficient
161
+ * to unwrap. (v1 wrapped the key independently under each primitive, which
162
+ * reduced security to min(X25519, ML-KEM); v1 is now read-only legacy.)
133
163
  *
134
164
  * @param plaintext - Message to encrypt (string or bytes)
135
165
  * @param recipientPublicKeys - Recipient's public keys
@@ -138,12 +168,14 @@ declare function getSecretKeys(identity: HybridIdentity): HybridSecretKeys;
138
168
  declare function hybridEncrypt(plaintext: string | Uint8Array, recipientPublicKeys: HybridPublicKeys, sender?: {
139
169
  name?: string;
140
170
  id?: string;
141
- }): Promise<HybridEnvelope>;
171
+ }): Promise<HybridEnvelopeV2>;
142
172
  /**
143
- * Decrypt a hybrid envelope.
173
+ * Decrypt a hybrid envelope (v2, v1, or legacy pqc-demo).
144
174
  *
145
- * Tries Kyber first (post-quantum), falls back to X25519 (classical).
146
- * Both must be valid for the envelope to be considered secure.
175
+ * v2: the content key is unwrapped from a single AND-combined KEK — both
176
+ * ML-KEM and X25519 secrets are required, and any failure is fatal.
177
+ * v1/legacy: read-only compatibility path (either secret suffices — the
178
+ * defect v2 exists to fix).
147
179
  *
148
180
  * @param envelope - Encrypted envelope
149
181
  * @param secretKeys - Recipient's secret keys
@@ -631,4 +663,4 @@ declare const BOX_KEY_SIZE: number;
631
663
  /** NaCl box nonce size (24 bytes) */
632
664
  declare const BOX_NONCE_SIZE: number;
633
665
 
634
- export { BLAKE3_BLOCK_SIZE, BLAKE3_KEY_LENGTH, BLAKE3_OUTPUT_LENGTH, BOX_KEY_SIZE, BOX_NONCE_SIZE, type Blake3Options, type BoxPayload, CHACHA20_KEY_SIZE, CHACHA20_NONCE_SIZE, type ChaChaPayload, type ClassicalWrap, DILITHIUM_ALGORITHM, DILITHIUM_PUBLIC_KEY_SIZE, DILITHIUM_SECRET_KEY_SIZE, DILITHIUM_SIGNATURE_SIZE, type DilithiumKeypair, type DilithiumKeypairB64, type DilithiumSignature, HKDF_SHA256_MAX_OUTPUT, HKDF_SHA256_OUTPUT_SIZE, HKDF_SHA512_MAX_OUTPUT, HKDF_SHA512_OUTPUT_SIZE, type HkdfHash, type HkdfOptions, type HybridEnvelope, type HybridIdentity, type HybridPublicKeys, type HybridSecretKeys, KYBER_SUITE, type KyberEncapsulation, type KyberKeypair, type KyberKeypairB64, type KyberSuite, POLY1305_TAG_SIZE, SECRETBOX_KEY_SIZE, SECRETBOX_NONCE_SIZE, SECRETBOX_OVERHEAD, type SecretboxPayload, type X25519Keypair, type X25519KeypairHex, XCHACHA20_NONCE_SIZE, assertLen, b64, blake3, blake3DeriveKey, blake3Hex, blake3Mac, boxDecrypt, boxEncrypt, boxUnwrapWithX25519, boxWrapWithX25519, chaCha20Poly1305Decrypt, chaCha20Poly1305Encrypt, createChaCha20Poly1305, createXChaCha20Poly1305, deriveKeyFromShared, dilithiumSign, dilithiumSignRaw, dilithiumVerify, dilithiumVerifyRaw, fromB64, fromHex, generateDilithiumKeypair, generateDilithiumKeypairFromSeed, generateHybridIdentity, generateKyberKeypair, generateKyberKeypairFromSeed, generateX25519Keypair, generateX25519KeypairFromSeed, getPublicKeys, getSecretKeys, hkdfDerive, hkdfExpand, hkdfExtract, hkdfSha256, hkdfSplitForNoise, hkdfTripleSplitForNoise, hybridDecrypt, hybridDecryptToString, hybridEncrypt, isDilithiumAvailable, isKyberAvailable, kyberDecapsulate, kyberEncapsulate, kyberUnwrapKey, kyberWrapKey, rand12, rand24, rand32, randN, rotateHybridIdentity, secretboxDecrypt, secretboxDecryptString, secretboxEncrypt, secretboxEncryptString, secretboxOpenRaw, secretboxRaw, sha256, sha256String, textDecoder, textEncoder, toB64, toHex, u8, ub64, x25519SharedSecret, xChaCha20Poly1305Decrypt, xChaCha20Poly1305Encrypt };
666
+ export { BLAKE3_BLOCK_SIZE, BLAKE3_KEY_LENGTH, BLAKE3_OUTPUT_LENGTH, BOX_KEY_SIZE, BOX_NONCE_SIZE, type Blake3Options, type BoxPayload, CHACHA20_KEY_SIZE, CHACHA20_NONCE_SIZE, type ChaChaPayload, type ClassicalWrap, DILITHIUM_ALGORITHM, DILITHIUM_PUBLIC_KEY_SIZE, DILITHIUM_SECRET_KEY_SIZE, DILITHIUM_SIGNATURE_SIZE, type DilithiumKeypair, type DilithiumKeypairB64, type DilithiumSignature, HKDF_SHA256_MAX_OUTPUT, HKDF_SHA256_OUTPUT_SIZE, HKDF_SHA512_MAX_OUTPUT, HKDF_SHA512_OUTPUT_SIZE, type HkdfHash, type HkdfOptions, type HybridEnvelope, type HybridEnvelopeV1, type HybridEnvelopeV2, type HybridIdentity, type HybridPublicKeys, type HybridSecretKeys, KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, KYBER_SECRET_KEY_SIZE, KYBER_SEED_SIZE, KYBER_SHARED_SECRET_SIZE, KYBER_SUITE, type KyberEncapsulation, type KyberKeypair, type KyberKeypairB64, type KyberSuite, POLY1305_TAG_SIZE, SECRETBOX_KEY_SIZE, SECRETBOX_NONCE_SIZE, SECRETBOX_OVERHEAD, type SecretboxPayload, type X25519Keypair, type X25519KeypairHex, XCHACHA20_NONCE_SIZE, assertLen, b64, blake3, blake3DeriveKey, blake3Hex, blake3Mac, boxDecrypt, boxEncrypt, boxUnwrapWithX25519, boxWrapWithX25519, chaCha20Poly1305Decrypt, chaCha20Poly1305Encrypt, createChaCha20Poly1305, createXChaCha20Poly1305, deriveKeyFromShared, dilithiumSign, dilithiumSignRaw, dilithiumVerify, dilithiumVerifyRaw, fromB64, fromHex, generateDilithiumKeypair, generateDilithiumKeypairFromSeed, generateHybridIdentity, generateKyberKeypair, generateKyberKeypairFromSeed, generateX25519Keypair, generateX25519KeypairFromSeed, getPublicKeys, getSecretKeys, hkdfDerive, hkdfExpand, hkdfExtract, hkdfSha256, hkdfSplitForNoise, hkdfTripleSplitForNoise, hybridDecrypt, hybridDecryptToString, hybridEncrypt, isDilithiumAvailable, isKyberAvailable, kyberDecapsulate, kyberEncapsulate, kyberUnwrapKey, kyberWrapKey, rand12, rand24, rand32, randN, rotateHybridIdentity, secretboxDecrypt, secretboxDecryptString, secretboxEncrypt, secretboxEncryptString, secretboxOpenRaw, secretboxRaw, sha256, sha256String, textDecoder, textEncoder, toB64, toHex, u8, ub64, x25519SharedSecret, xChaCha20Poly1305Decrypt, xChaCha20Poly1305Encrypt };
@@ -1,4 +1,4 @@
1
- import { OmniHybridV1 } from '@omnituum/envelope-registry';
1
+ import { OmniHybridV2, OmniHybridV1 } from '@omnituum/envelope-registry';
2
2
  import * as _noble_ciphers_utils_js from '@noble/ciphers/utils.js';
3
3
 
4
4
  /**
@@ -35,6 +35,16 @@ interface KyberEncapsulation {
35
35
  /** Canonical suite identifier for new material produced by this package. */
36
36
  declare const KYBER_SUITE: "ML-KEM-1024-FIPS203";
37
37
  type KyberSuite = typeof KYBER_SUITE;
38
+ /** ML-KEM-1024 public key size (bytes). */
39
+ declare const KYBER_PUBLIC_KEY_SIZE = 1568;
40
+ /** ML-KEM-1024 secret key size (bytes). */
41
+ declare const KYBER_SECRET_KEY_SIZE = 3168;
42
+ /** ML-KEM-1024 ciphertext size (bytes). */
43
+ declare const KYBER_CIPHERTEXT_SIZE = 1568;
44
+ /** ML-KEM-1024 shared-secret size (bytes). */
45
+ declare const KYBER_SHARED_SECRET_SIZE = 32;
46
+ /** Seed size accepted by `generateKyberKeypairFromSeed` (bytes). */
47
+ declare const KYBER_SEED_SIZE = 64;
38
48
  /**
39
49
  * @deprecated Always returns true. Retained for API stability across the
40
50
  * 0.3.x → 0.4.x cut. The noble backend is statically imported and always
@@ -67,10 +77,16 @@ declare function kyberUnwrapKey(sharedSecret: Uint8Array, nonceB64: string, wrap
67
77
  *
68
78
  * Post-quantum secure hybrid encryption combining:
69
79
  * - X25519 ECDH (classical, proven security)
70
- * - Kyber ML-KEM-768 (post-quantum, NIST Level 3)
80
+ * - ML-KEM-1024 (post-quantum, FIPS 203, NIST Level 5)
71
81
  *
72
- * Both key exchanges must succeed for decryption, providing
73
- * security against both classical and quantum attacks.
82
+ * v2 (current write format): the content key is wrapped once, under a KEK
83
+ * derived from BOTH shared secrets (HKDF(ss_mlkem || ss_x25519) with
84
+ * transcript binding) — both key exchanges must succeed to decrypt, so
85
+ * breaking either primitive alone is insufficient.
86
+ *
87
+ * v1 (read-only legacy): wrapped the key independently under each
88
+ * primitive; either secret alone sufficed. Kept only for decrypting
89
+ * existing envelopes — see the 2026-07-05 security audit.
74
90
  */
75
91
 
76
92
  interface HybridIdentity {
@@ -106,12 +122,21 @@ interface HybridSecretKeys {
106
122
  kyberSecB64: string;
107
123
  }
108
124
 
109
- type HybridEnvelope = Omit<OmniHybridV1, 'meta'> & {
125
+ type HybridEnvelopeV1 = Omit<OmniHybridV1, 'meta'> & {
110
126
  meta: OmniHybridV1['meta'] & {
111
127
  senderName?: string;
112
128
  senderId?: string;
113
129
  };
114
130
  };
131
+
132
+ type HybridEnvelopeV2 = Omit<OmniHybridV2, 'meta'> & {
133
+ meta: OmniHybridV2['meta'] & {
134
+ senderName?: string;
135
+ senderId?: string;
136
+ };
137
+ };
138
+ /** Any hybrid envelope this module can decrypt. */
139
+ type HybridEnvelope = HybridEnvelopeV1 | HybridEnvelopeV2;
115
140
  /**
116
141
  * Generate a new hybrid identity with both X25519 and Kyber keys.
117
142
  */
@@ -129,7 +154,12 @@ declare function getPublicKeys(identity: HybridIdentity): HybridPublicKeys;
129
154
  */
130
155
  declare function getSecretKeys(identity: HybridIdentity): HybridSecretKeys;
131
156
  /**
132
- * Encrypt a message using hybrid X25519 + Kyber encryption.
157
+ * Encrypt a message using hybrid X25519 + ML-KEM-1024 encryption (v2).
158
+ *
159
+ * The content key is wrapped exactly once, under a KEK derived from both
160
+ * shared secrets together — breaking either primitive alone is insufficient
161
+ * to unwrap. (v1 wrapped the key independently under each primitive, which
162
+ * reduced security to min(X25519, ML-KEM); v1 is now read-only legacy.)
133
163
  *
134
164
  * @param plaintext - Message to encrypt (string or bytes)
135
165
  * @param recipientPublicKeys - Recipient's public keys
@@ -138,12 +168,14 @@ declare function getSecretKeys(identity: HybridIdentity): HybridSecretKeys;
138
168
  declare function hybridEncrypt(plaintext: string | Uint8Array, recipientPublicKeys: HybridPublicKeys, sender?: {
139
169
  name?: string;
140
170
  id?: string;
141
- }): Promise<HybridEnvelope>;
171
+ }): Promise<HybridEnvelopeV2>;
142
172
  /**
143
- * Decrypt a hybrid envelope.
173
+ * Decrypt a hybrid envelope (v2, v1, or legacy pqc-demo).
144
174
  *
145
- * Tries Kyber first (post-quantum), falls back to X25519 (classical).
146
- * Both must be valid for the envelope to be considered secure.
175
+ * v2: the content key is unwrapped from a single AND-combined KEK — both
176
+ * ML-KEM and X25519 secrets are required, and any failure is fatal.
177
+ * v1/legacy: read-only compatibility path (either secret suffices — the
178
+ * defect v2 exists to fix).
147
179
  *
148
180
  * @param envelope - Encrypted envelope
149
181
  * @param secretKeys - Recipient's secret keys
@@ -631,4 +663,4 @@ declare const BOX_KEY_SIZE: number;
631
663
  /** NaCl box nonce size (24 bytes) */
632
664
  declare const BOX_NONCE_SIZE: number;
633
665
 
634
- export { BLAKE3_BLOCK_SIZE, BLAKE3_KEY_LENGTH, BLAKE3_OUTPUT_LENGTH, BOX_KEY_SIZE, BOX_NONCE_SIZE, type Blake3Options, type BoxPayload, CHACHA20_KEY_SIZE, CHACHA20_NONCE_SIZE, type ChaChaPayload, type ClassicalWrap, DILITHIUM_ALGORITHM, DILITHIUM_PUBLIC_KEY_SIZE, DILITHIUM_SECRET_KEY_SIZE, DILITHIUM_SIGNATURE_SIZE, type DilithiumKeypair, type DilithiumKeypairB64, type DilithiumSignature, HKDF_SHA256_MAX_OUTPUT, HKDF_SHA256_OUTPUT_SIZE, HKDF_SHA512_MAX_OUTPUT, HKDF_SHA512_OUTPUT_SIZE, type HkdfHash, type HkdfOptions, type HybridEnvelope, type HybridIdentity, type HybridPublicKeys, type HybridSecretKeys, KYBER_SUITE, type KyberEncapsulation, type KyberKeypair, type KyberKeypairB64, type KyberSuite, POLY1305_TAG_SIZE, SECRETBOX_KEY_SIZE, SECRETBOX_NONCE_SIZE, SECRETBOX_OVERHEAD, type SecretboxPayload, type X25519Keypair, type X25519KeypairHex, XCHACHA20_NONCE_SIZE, assertLen, b64, blake3, blake3DeriveKey, blake3Hex, blake3Mac, boxDecrypt, boxEncrypt, boxUnwrapWithX25519, boxWrapWithX25519, chaCha20Poly1305Decrypt, chaCha20Poly1305Encrypt, createChaCha20Poly1305, createXChaCha20Poly1305, deriveKeyFromShared, dilithiumSign, dilithiumSignRaw, dilithiumVerify, dilithiumVerifyRaw, fromB64, fromHex, generateDilithiumKeypair, generateDilithiumKeypairFromSeed, generateHybridIdentity, generateKyberKeypair, generateKyberKeypairFromSeed, generateX25519Keypair, generateX25519KeypairFromSeed, getPublicKeys, getSecretKeys, hkdfDerive, hkdfExpand, hkdfExtract, hkdfSha256, hkdfSplitForNoise, hkdfTripleSplitForNoise, hybridDecrypt, hybridDecryptToString, hybridEncrypt, isDilithiumAvailable, isKyberAvailable, kyberDecapsulate, kyberEncapsulate, kyberUnwrapKey, kyberWrapKey, rand12, rand24, rand32, randN, rotateHybridIdentity, secretboxDecrypt, secretboxDecryptString, secretboxEncrypt, secretboxEncryptString, secretboxOpenRaw, secretboxRaw, sha256, sha256String, textDecoder, textEncoder, toB64, toHex, u8, ub64, x25519SharedSecret, xChaCha20Poly1305Decrypt, xChaCha20Poly1305Encrypt };
666
+ export { BLAKE3_BLOCK_SIZE, BLAKE3_KEY_LENGTH, BLAKE3_OUTPUT_LENGTH, BOX_KEY_SIZE, BOX_NONCE_SIZE, type Blake3Options, type BoxPayload, CHACHA20_KEY_SIZE, CHACHA20_NONCE_SIZE, type ChaChaPayload, type ClassicalWrap, DILITHIUM_ALGORITHM, DILITHIUM_PUBLIC_KEY_SIZE, DILITHIUM_SECRET_KEY_SIZE, DILITHIUM_SIGNATURE_SIZE, type DilithiumKeypair, type DilithiumKeypairB64, type DilithiumSignature, HKDF_SHA256_MAX_OUTPUT, HKDF_SHA256_OUTPUT_SIZE, HKDF_SHA512_MAX_OUTPUT, HKDF_SHA512_OUTPUT_SIZE, type HkdfHash, type HkdfOptions, type HybridEnvelope, type HybridEnvelopeV1, type HybridEnvelopeV2, type HybridIdentity, type HybridPublicKeys, type HybridSecretKeys, KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, KYBER_SECRET_KEY_SIZE, KYBER_SEED_SIZE, KYBER_SHARED_SECRET_SIZE, KYBER_SUITE, type KyberEncapsulation, type KyberKeypair, type KyberKeypairB64, type KyberSuite, POLY1305_TAG_SIZE, SECRETBOX_KEY_SIZE, SECRETBOX_NONCE_SIZE, SECRETBOX_OVERHEAD, type SecretboxPayload, type X25519Keypair, type X25519KeypairHex, XCHACHA20_NONCE_SIZE, assertLen, b64, blake3, blake3DeriveKey, blake3Hex, blake3Mac, boxDecrypt, boxEncrypt, boxUnwrapWithX25519, boxWrapWithX25519, chaCha20Poly1305Decrypt, chaCha20Poly1305Encrypt, createChaCha20Poly1305, createXChaCha20Poly1305, deriveKeyFromShared, dilithiumSign, dilithiumSignRaw, dilithiumVerify, dilithiumVerifyRaw, fromB64, fromHex, generateDilithiumKeypair, generateDilithiumKeypairFromSeed, generateHybridIdentity, generateKyberKeypair, generateKyberKeypairFromSeed, generateX25519Keypair, generateX25519KeypairFromSeed, getPublicKeys, getSecretKeys, hkdfDerive, hkdfExpand, hkdfExtract, hkdfSha256, hkdfSplitForNoise, hkdfTripleSplitForNoise, hybridDecrypt, hybridDecryptToString, hybridEncrypt, isDilithiumAvailable, isKyberAvailable, kyberDecapsulate, kyberEncapsulate, kyberUnwrapKey, kyberWrapKey, rand12, rand24, rand32, randN, rotateHybridIdentity, secretboxDecrypt, secretboxDecryptString, secretboxEncrypt, secretboxEncryptString, secretboxOpenRaw, secretboxRaw, sha256, sha256String, textDecoder, textEncoder, toB64, toHex, u8, ub64, x25519SharedSecret, xChaCha20Poly1305Decrypt, xChaCha20Poly1305Encrypt };