@omnituum/pqc-shared 0.4.1 → 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 (39) hide show
  1. package/dist/crypto/index.cjs +81 -54
  2. package/dist/crypto/index.d.cts +33 -11
  3. package/dist/crypto/index.d.ts +33 -11
  4. package/dist/crypto/index.js +81 -54
  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 +358 -229
  12. package/dist/index.d.cts +4 -4
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +357 -230
  15. package/dist/{integrity-BenoFsmP.d.cts → integrity-BkBDrHA-.d.cts} +12 -4
  16. package/dist/{integrity-D9J98Bty.d.ts → integrity-DsFJv5c-.d.ts} +12 -4
  17. package/dist/{types-CRka8PC7.d.cts → types-DmnVueAY.d.cts} +14 -4
  18. package/dist/{types-CRka8PC7.d.ts → types-DmnVueAY.d.ts} +14 -4
  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 +7 -7
  28. package/src/crypto/dilithium.ts +4 -4
  29. package/src/crypto/hybrid.ts +182 -80
  30. package/src/crypto/index.ts +2 -0
  31. package/src/fs/decrypt.ts +145 -68
  32. package/src/fs/encrypt.ts +161 -112
  33. package/src/fs/format.ts +110 -15
  34. package/src/fs/index.ts +4 -0
  35. package/src/fs/types.ts +77 -6
  36. package/src/index.ts +4 -0
  37. package/src/utils/integrity.ts +16 -15
  38. package/src/vault/manager.ts +1 -1
  39. package/src/version.ts +29 -7
@@ -8,16 +8,43 @@
8
8
  */
9
9
  /** Magic bytes: "OQEF" (Omnituum Quantum Encrypted File) */
10
10
  declare const OQE_MAGIC: Uint8Array<ArrayBuffer>;
11
- /** Current format version */
12
- declare const OQE_FORMAT_VERSION = 1;
11
+ /**
12
+ * OQE format versions.
13
+ *
14
+ * v1 (LEGACY, read-only): a single AES-GCM IV was reused for both the
15
+ * metadata section and the content section under the same content key —
16
+ * catastrophic GCM nonce reuse — and hybrid mode wrapped the content key
17
+ * independently under X25519 and Kyber (either secret alone unwrapped it).
18
+ * Kept only so pre-existing files remain decryptable.
19
+ *
20
+ * v2 (CURRENT, write format): distinct random IVs per AES-GCM section, the
21
+ * serialized header bound as AEAD associated data, and hybrid mode wraps the
22
+ * content key once under an AND-combined KEK (HKDF(ss_mlkem || ss_x25519)
23
+ * with transcript binding) — both primitives must be broken to unwrap.
24
+ * See the 2026-07-06 fs security fix.
25
+ */
26
+ declare const OQE_FORMAT_VERSION_V1 = 1;
27
+ declare const OQE_FORMAT_VERSION_V2 = 2;
28
+ /** Current (write) format version. */
29
+ declare const OQE_FORMAT_VERSION = 2;
30
+ /** Format versions this library can read. */
31
+ declare const SUPPORTED_OQE_VERSIONS: readonly [1, 2];
13
32
  /** Supported encryption modes */
14
33
  type OQEMode = 'hybrid' | 'password';
15
34
  /** Algorithm suite identifiers */
16
35
  declare const ALGORITHM_SUITES: {
17
- /** Hybrid: X25519 ECDH + Kyber768 KEM + AES-256-GCM */
36
+ /**
37
+ * LEGACY (read-only): Hybrid X25519 + Kyber with independent per-primitive
38
+ * wraps (either secret unwraps). Only appears in v1 files. Never written.
39
+ */
18
40
  readonly HYBRID_X25519_KYBER768_AES256GCM: 1;
19
41
  /** Password: Argon2id + AES-256-GCM */
20
42
  readonly PASSWORD_ARGON2ID_AES256GCM: 2;
43
+ /**
44
+ * Hybrid X25519 + ML-KEM-1024 with a single AND-combined KEK wrap
45
+ * (HKDF(ss_mlkem || ss_x25519), transcript-bound). Written by v2.
46
+ */
47
+ readonly HYBRID_X25519_MLKEM1024_AES256GCM: 3;
21
48
  };
22
49
  type AlgorithmSuiteId = typeof ALGORITHM_SUITES[keyof typeof ALGORITHM_SUITES];
23
50
  interface Argon2idParams {
@@ -79,10 +106,25 @@ interface OQEHeader {
79
106
  metadataLength: number;
80
107
  /** Length of key material section */
81
108
  keyMaterialLength: number;
82
- /** AES-GCM initialization vector */
109
+ /**
110
+ * AES-GCM IV for the metadata section. In v1 this same IV was (incorrectly)
111
+ * also used for the content section; v2 uses `contentIv` for content.
112
+ */
83
113
  iv: Uint8Array;
114
+ /**
115
+ * AES-GCM IV for the content section (v2 only). Distinct from `iv` so the
116
+ * two sections never share a (key, nonce) pair. Undefined for v1 files.
117
+ */
118
+ contentIv?: Uint8Array;
84
119
  }
85
- /** Fixed header size in bytes (before variable-length sections) */
120
+ /** Fixed v1 header size in bytes (before variable-length sections). */
121
+ declare const OQE_HEADER_SIZE_V1 = 30;
122
+ /** Fixed v2 header size: v1 layout plus a 12-byte content IV. */
123
+ declare const OQE_HEADER_SIZE_V2 = 42;
124
+ /**
125
+ * @deprecated Use OQE_HEADER_SIZE_V1 / OQE_HEADER_SIZE_V2. Retained as the v1
126
+ * size for source compatibility.
127
+ */
86
128
  declare const OQE_HEADER_SIZE = 30;
87
129
  /**
88
130
  * Hybrid Mode Key Material:
@@ -107,6 +149,30 @@ interface HybridKeyMaterial {
107
149
  /** Kyber wrap nonce */
108
150
  kyberNonce: Uint8Array;
109
151
  }
152
+ /**
153
+ * v2 Hybrid Mode Key Material — single AND-combined wrap.
154
+ *
155
+ * The content key is wrapped exactly once under a KEK derived from BOTH shared
156
+ * secrets: HKDF(ss_mlkem || ss_x25519) with the ephemeral X25519 key and the
157
+ * Kyber ciphertext bound into the info string. Both the X25519 and ML-KEM
158
+ * exchanges must succeed to unwrap — there is no per-primitive fallback.
159
+ *
160
+ * Serialized layout:
161
+ * - X25519 ephemeral public key (32 bytes)
162
+ * - Kyber KEM ciphertext (2-byte length prefix + data)
163
+ * - Content-key wrap nonce (24 bytes)
164
+ * - Wrapped content key (2-byte length prefix + data, 48 bytes)
165
+ */
166
+ interface HybridKeyMaterialV2 {
167
+ /** X25519 ephemeral public key */
168
+ x25519EphemeralPk: Uint8Array;
169
+ /** Kyber KEM ciphertext */
170
+ kyberCiphertext: Uint8Array;
171
+ /** Wrap nonce for the single content-key wrap (NaCl secretbox) */
172
+ ckWrapNonce: Uint8Array;
173
+ /** Content key wrapped under the AND-combined KEK */
174
+ ckWrapped: Uint8Array;
175
+ }
110
176
  /**
111
177
  * Password Mode Key Material:
112
178
  * - Argon2id salt (32 bytes)
@@ -209,6 +275,14 @@ declare function toUint8Array(input: FileInput): Promise<Uint8Array>;
209
275
  * Outputs .oqe (Omnituum Quantum Encrypted) files.
210
276
  */
211
277
 
278
+ /**
279
+ * Derive the v2 AND-combined KEK for file encryption. Requires BOTH shared
280
+ * secrets; the envelope's own KEM values are bound into the HKDF info so a
281
+ * spliced ephemeral key or Kyber ciphertext derives a different KEK and the
282
+ * wrap fails authentication (X-Wing-style transcript binding). Domain-separated
283
+ * from the message-envelope KEK via the "omnituum/fs/hybrid-v2" salt.
284
+ */
285
+ declare function combinedFileKekV2(kyberShared: Uint8Array, x25519Shared: Uint8Array, x25519EpkHex: string, kyberKemCtB64: string): Uint8Array;
212
286
  interface EncryptFileInput {
213
287
  /** File data */
214
288
  data: FileInput;
@@ -318,4 +392,4 @@ interface OQEFileInfo {
318
392
  */
319
393
  declare function inspectOQEFile(data: FileInput): Promise<OQEFileInfo>;
320
394
 
321
- export { type Argon2idParams as A, type DecryptOptions as D, type EncryptOptions as E, type FileInput as F, type HybridKeyMaterial as H, MIN_ARGON2ID_PARAMS as M, type OQEEncryptResult as O, type PasswordKeyMaterial as P, encryptFileWithPassword as a, decryptFileWithPassword as b, type OQEDecryptResult as c, decryptFile as d, encryptFile as e, type OQEHeader as f, type OQEMetadata as g, type AlgorithmSuiteId as h, DEFAULT_ARGON2ID_PARAMS as i, encryptFileForSelf as j, decryptFileForSelf as k, inspectOQEFile as l, type OQEMode as m, type HybridEncryptOptions as n, type PasswordEncryptOptions as o, type HybridDecryptOptions as p, type PasswordDecryptOptions as q, type OQEErrorCode as r, type ProgressCallback as s, OQEError as t, OQE_MAGIC as u, OQE_FORMAT_VERSION as v, ALGORITHM_SUITES as w, OQE_HEADER_SIZE as x, toUint8Array as y };
395
+ export { type Argon2idParams as A, ALGORITHM_SUITES as B, OQE_HEADER_SIZE_V1 as C, type DecryptOptions as D, type EncryptOptions as E, type FileInput as F, OQE_HEADER_SIZE_V2 as G, type HybridKeyMaterial as H, OQE_HEADER_SIZE as I, toUint8Array as J, MIN_ARGON2ID_PARAMS as M, type OQEEncryptResult as O, type PasswordKeyMaterial as P, SUPPORTED_OQE_VERSIONS as S, encryptFileWithPassword as a, decryptFileWithPassword as b, type OQEDecryptResult as c, decryptFile as d, encryptFile as e, type OQEHeader as f, type HybridKeyMaterialV2 as g, type OQEMetadata as h, type AlgorithmSuiteId as i, DEFAULT_ARGON2ID_PARAMS as j, encryptFileForSelf as k, combinedFileKekV2 as l, decryptFileForSelf as m, inspectOQEFile as n, type OQEMode as o, type HybridEncryptOptions as p, type PasswordEncryptOptions as q, type HybridDecryptOptions as r, type PasswordDecryptOptions as s, type OQEErrorCode as t, type ProgressCallback as u, OQEError as v, OQE_MAGIC as w, OQE_FORMAT_VERSION_V1 as x, OQE_FORMAT_VERSION_V2 as y, OQE_FORMAT_VERSION as z };
@@ -8,16 +8,43 @@
8
8
  */
9
9
  /** Magic bytes: "OQEF" (Omnituum Quantum Encrypted File) */
10
10
  declare const OQE_MAGIC: Uint8Array<ArrayBuffer>;
11
- /** Current format version */
12
- declare const OQE_FORMAT_VERSION = 1;
11
+ /**
12
+ * OQE format versions.
13
+ *
14
+ * v1 (LEGACY, read-only): a single AES-GCM IV was reused for both the
15
+ * metadata section and the content section under the same content key —
16
+ * catastrophic GCM nonce reuse — and hybrid mode wrapped the content key
17
+ * independently under X25519 and Kyber (either secret alone unwrapped it).
18
+ * Kept only so pre-existing files remain decryptable.
19
+ *
20
+ * v2 (CURRENT, write format): distinct random IVs per AES-GCM section, the
21
+ * serialized header bound as AEAD associated data, and hybrid mode wraps the
22
+ * content key once under an AND-combined KEK (HKDF(ss_mlkem || ss_x25519)
23
+ * with transcript binding) — both primitives must be broken to unwrap.
24
+ * See the 2026-07-06 fs security fix.
25
+ */
26
+ declare const OQE_FORMAT_VERSION_V1 = 1;
27
+ declare const OQE_FORMAT_VERSION_V2 = 2;
28
+ /** Current (write) format version. */
29
+ declare const OQE_FORMAT_VERSION = 2;
30
+ /** Format versions this library can read. */
31
+ declare const SUPPORTED_OQE_VERSIONS: readonly [1, 2];
13
32
  /** Supported encryption modes */
14
33
  type OQEMode = 'hybrid' | 'password';
15
34
  /** Algorithm suite identifiers */
16
35
  declare const ALGORITHM_SUITES: {
17
- /** Hybrid: X25519 ECDH + Kyber768 KEM + AES-256-GCM */
36
+ /**
37
+ * LEGACY (read-only): Hybrid X25519 + Kyber with independent per-primitive
38
+ * wraps (either secret unwraps). Only appears in v1 files. Never written.
39
+ */
18
40
  readonly HYBRID_X25519_KYBER768_AES256GCM: 1;
19
41
  /** Password: Argon2id + AES-256-GCM */
20
42
  readonly PASSWORD_ARGON2ID_AES256GCM: 2;
43
+ /**
44
+ * Hybrid X25519 + ML-KEM-1024 with a single AND-combined KEK wrap
45
+ * (HKDF(ss_mlkem || ss_x25519), transcript-bound). Written by v2.
46
+ */
47
+ readonly HYBRID_X25519_MLKEM1024_AES256GCM: 3;
21
48
  };
22
49
  type AlgorithmSuiteId = typeof ALGORITHM_SUITES[keyof typeof ALGORITHM_SUITES];
23
50
  interface Argon2idParams {
@@ -79,10 +106,25 @@ interface OQEHeader {
79
106
  metadataLength: number;
80
107
  /** Length of key material section */
81
108
  keyMaterialLength: number;
82
- /** AES-GCM initialization vector */
109
+ /**
110
+ * AES-GCM IV for the metadata section. In v1 this same IV was (incorrectly)
111
+ * also used for the content section; v2 uses `contentIv` for content.
112
+ */
83
113
  iv: Uint8Array;
114
+ /**
115
+ * AES-GCM IV for the content section (v2 only). Distinct from `iv` so the
116
+ * two sections never share a (key, nonce) pair. Undefined for v1 files.
117
+ */
118
+ contentIv?: Uint8Array;
84
119
  }
85
- /** Fixed header size in bytes (before variable-length sections) */
120
+ /** Fixed v1 header size in bytes (before variable-length sections). */
121
+ declare const OQE_HEADER_SIZE_V1 = 30;
122
+ /** Fixed v2 header size: v1 layout plus a 12-byte content IV. */
123
+ declare const OQE_HEADER_SIZE_V2 = 42;
124
+ /**
125
+ * @deprecated Use OQE_HEADER_SIZE_V1 / OQE_HEADER_SIZE_V2. Retained as the v1
126
+ * size for source compatibility.
127
+ */
86
128
  declare const OQE_HEADER_SIZE = 30;
87
129
  /**
88
130
  * Hybrid Mode Key Material:
@@ -107,6 +149,30 @@ interface HybridKeyMaterial {
107
149
  /** Kyber wrap nonce */
108
150
  kyberNonce: Uint8Array;
109
151
  }
152
+ /**
153
+ * v2 Hybrid Mode Key Material — single AND-combined wrap.
154
+ *
155
+ * The content key is wrapped exactly once under a KEK derived from BOTH shared
156
+ * secrets: HKDF(ss_mlkem || ss_x25519) with the ephemeral X25519 key and the
157
+ * Kyber ciphertext bound into the info string. Both the X25519 and ML-KEM
158
+ * exchanges must succeed to unwrap — there is no per-primitive fallback.
159
+ *
160
+ * Serialized layout:
161
+ * - X25519 ephemeral public key (32 bytes)
162
+ * - Kyber KEM ciphertext (2-byte length prefix + data)
163
+ * - Content-key wrap nonce (24 bytes)
164
+ * - Wrapped content key (2-byte length prefix + data, 48 bytes)
165
+ */
166
+ interface HybridKeyMaterialV2 {
167
+ /** X25519 ephemeral public key */
168
+ x25519EphemeralPk: Uint8Array;
169
+ /** Kyber KEM ciphertext */
170
+ kyberCiphertext: Uint8Array;
171
+ /** Wrap nonce for the single content-key wrap (NaCl secretbox) */
172
+ ckWrapNonce: Uint8Array;
173
+ /** Content key wrapped under the AND-combined KEK */
174
+ ckWrapped: Uint8Array;
175
+ }
110
176
  /**
111
177
  * Password Mode Key Material:
112
178
  * - Argon2id salt (32 bytes)
@@ -209,6 +275,14 @@ declare function toUint8Array(input: FileInput): Promise<Uint8Array>;
209
275
  * Outputs .oqe (Omnituum Quantum Encrypted) files.
210
276
  */
211
277
 
278
+ /**
279
+ * Derive the v2 AND-combined KEK for file encryption. Requires BOTH shared
280
+ * secrets; the envelope's own KEM values are bound into the HKDF info so a
281
+ * spliced ephemeral key or Kyber ciphertext derives a different KEK and the
282
+ * wrap fails authentication (X-Wing-style transcript binding). Domain-separated
283
+ * from the message-envelope KEK via the "omnituum/fs/hybrid-v2" salt.
284
+ */
285
+ declare function combinedFileKekV2(kyberShared: Uint8Array, x25519Shared: Uint8Array, x25519EpkHex: string, kyberKemCtB64: string): Uint8Array;
212
286
  interface EncryptFileInput {
213
287
  /** File data */
214
288
  data: FileInput;
@@ -318,4 +392,4 @@ interface OQEFileInfo {
318
392
  */
319
393
  declare function inspectOQEFile(data: FileInput): Promise<OQEFileInfo>;
320
394
 
321
- export { type Argon2idParams as A, type DecryptOptions as D, type EncryptOptions as E, type FileInput as F, type HybridKeyMaterial as H, MIN_ARGON2ID_PARAMS as M, type OQEEncryptResult as O, type PasswordKeyMaterial as P, encryptFileWithPassword as a, decryptFileWithPassword as b, type OQEDecryptResult as c, decryptFile as d, encryptFile as e, type OQEHeader as f, type OQEMetadata as g, type AlgorithmSuiteId as h, DEFAULT_ARGON2ID_PARAMS as i, encryptFileForSelf as j, decryptFileForSelf as k, inspectOQEFile as l, type OQEMode as m, type HybridEncryptOptions as n, type PasswordEncryptOptions as o, type HybridDecryptOptions as p, type PasswordDecryptOptions as q, type OQEErrorCode as r, type ProgressCallback as s, OQEError as t, OQE_MAGIC as u, OQE_FORMAT_VERSION as v, ALGORITHM_SUITES as w, OQE_HEADER_SIZE as x, toUint8Array as y };
395
+ export { type Argon2idParams as A, ALGORITHM_SUITES as B, OQE_HEADER_SIZE_V1 as C, type DecryptOptions as D, type EncryptOptions as E, type FileInput as F, OQE_HEADER_SIZE_V2 as G, type HybridKeyMaterial as H, OQE_HEADER_SIZE as I, toUint8Array as J, MIN_ARGON2ID_PARAMS as M, type OQEEncryptResult as O, type PasswordKeyMaterial as P, SUPPORTED_OQE_VERSIONS as S, encryptFileWithPassword as a, decryptFileWithPassword as b, type OQEDecryptResult as c, decryptFile as d, encryptFile as e, type OQEHeader as f, type HybridKeyMaterialV2 as g, type OQEMetadata as h, type AlgorithmSuiteId as i, DEFAULT_ARGON2ID_PARAMS as j, encryptFileForSelf as k, combinedFileKekV2 as l, decryptFileForSelf as m, inspectOQEFile as n, type OQEMode as o, type HybridEncryptOptions as p, type PasswordEncryptOptions as q, type HybridDecryptOptions as r, type PasswordDecryptOptions as s, type OQEErrorCode as t, type ProgressCallback as u, OQEError as v, OQE_MAGIC as w, OQE_FORMAT_VERSION_V1 as x, OQE_FORMAT_VERSION_V2 as y, OQE_FORMAT_VERSION as z };