@omnituum/pqc-shared 0.3.0 → 0.4.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/dist/crypto/index.cjs +29 -71
- package/dist/crypto/index.d.cts +38 -45
- package/dist/crypto/index.d.ts +38 -45
- package/dist/crypto/index.js +28 -72
- package/dist/crypto/safe/index.cjs +556 -0
- package/dist/crypto/safe/index.d.cts +341 -0
- package/dist/crypto/safe/index.d.ts +341 -0
- package/dist/crypto/safe/index.js +524 -0
- package/dist/fs/index.cjs +6 -34
- package/dist/fs/index.js +6 -34
- package/dist/index.cjs +29 -71
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +28 -72
- package/dist/{integrity-CCYjrap3.d.ts → integrity-BPvNsC50.d.ts} +1 -1
- package/dist/{integrity-Dx9jukMH.d.cts → integrity-ByMp24VA.d.cts} +1 -1
- package/dist/{types-61c7Q9ri.d.ts → types-Dx_AFqow.d.cts} +54 -2
- package/dist/{types-Ch0y-n7K.d.cts → types-Dx_AFqow.d.ts} +54 -2
- package/dist/utils/index.d.cts +2 -3
- package/dist/utils/index.d.ts +2 -3
- package/dist/vault/index.cjs +13 -41
- package/dist/vault/index.d.cts +2 -3
- package/dist/vault/index.d.ts +2 -3
- package/dist/vault/index.js +13 -41
- package/package.json +27 -13
- package/src/crypto/dilithium.ts +8 -4
- package/src/crypto/hybrid.ts +13 -29
- package/src/crypto/index.ts +3 -0
- package/src/crypto/kyber.ts +63 -121
- package/src/crypto/safe/adapters.ts +306 -0
- package/src/crypto/safe/dilithium.ts +74 -0
- package/src/crypto/safe/index.ts +97 -0
- package/src/crypto/safe/kyber.ts +47 -0
- package/src/crypto/safe/manifests.ts +192 -0
- package/src/crypto/safe/secretbox.ts +24 -0
- package/src/crypto/safe/types.ts +88 -0
- package/src/crypto/safe/x25519.ts +31 -0
- package/src/index.ts +7 -4
- package/src/version.ts +9 -4
- package/dist/version-BygzPVGs.d.cts +0 -55
- package/dist/version-BygzPVGs.d.ts +0 -55
package/dist/vault/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { O as OmnituumVault, E as EncryptedVaultFile,
|
|
2
|
-
export { D as DEFAULT_VAULT_SETTINGS,
|
|
3
|
-
import '../version-BygzPVGs.js';
|
|
1
|
+
import { O as OmnituumVault, E as EncryptedVaultFile, k as EncryptedVaultFileV2, H as HybridIdentityRecord, l as VaultSettings, m as VaultSession } from '../types-Dx_AFqow.js';
|
|
2
|
+
export { D as DEFAULT_VAULT_SETTINGS, n as EncryptedVaultFileV1, o as HealthStatus, I as IdentityHealth, P as PBKDF2_ITERATIONS } from '../types-Dx_AFqow.js';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Omnituum PQC Shared - Vault Encryption
|
package/dist/vault/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import '@noble/hashes/sha2.js';
|
|
2
2
|
import '@noble/hashes/hmac.js';
|
|
3
|
+
import { OMNI_VERSIONS, DEPRECATED_VERSIONS } from '@omnituum/envelope-registry';
|
|
3
4
|
import { argon2id } from 'hash-wasm';
|
|
4
5
|
import nacl from 'tweetnacl';
|
|
6
|
+
import { ml_kem1024 } from '@noble/post-quantum/ml-kem.js';
|
|
5
7
|
import '@noble/hashes/blake3.js';
|
|
6
8
|
import '@noble/ciphers/chacha.js';
|
|
7
9
|
import '@noble/hashes/hkdf.js';
|
|
@@ -33,9 +35,12 @@ function fromB64(str) {
|
|
|
33
35
|
function toHex(bytes) {
|
|
34
36
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
35
37
|
}
|
|
38
|
+
function randN(n) {
|
|
39
|
+
return globalThis.crypto.getRandomValues(new Uint8Array(n));
|
|
40
|
+
}
|
|
36
41
|
var b64 = toB64;
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
OMNI_VERSIONS.HYBRID_V1;
|
|
43
|
+
DEPRECATED_VERSIONS.PQC_DEMO_HYBRID_V1;
|
|
39
44
|
var VAULT_VERSION = "omnituum.vault.v1";
|
|
40
45
|
var VAULT_ENCRYPTED_VERSION = "omnituum.vault.enc.v1";
|
|
41
46
|
var VAULT_ENCRYPTED_VERSION_V2 = "omnituum.vault.enc.v2";
|
|
@@ -358,46 +363,13 @@ function generateX25519Keypair() {
|
|
|
358
363
|
secretBytes: kp.secretKey
|
|
359
364
|
};
|
|
360
365
|
}
|
|
361
|
-
var kyberModule = null;
|
|
362
|
-
async function loadKyber() {
|
|
363
|
-
if (kyberModule) return kyberModule;
|
|
364
|
-
try {
|
|
365
|
-
const m = await import('kyber-crystals');
|
|
366
|
-
const k = m.default ?? m;
|
|
367
|
-
kyberModule = k.kyber ?? k;
|
|
368
|
-
return kyberModule;
|
|
369
|
-
} catch (e) {
|
|
370
|
-
console.warn("[Kyber] Failed to load kyber-crystals:", e);
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
366
|
async function generateKyberKeypair() {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const fn = mod?.keypair ?? mod?.keyPair ?? mod?.generateKeyPair ?? null;
|
|
382
|
-
if (typeof fn !== "function") {
|
|
383
|
-
console.warn("[Kyber] No keypair function found");
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
const kp = await fn.call(mod);
|
|
387
|
-
const pub = kp?.publicKey ?? kp?.public ?? kp?.pk;
|
|
388
|
-
const priv = kp?.privateKey ?? kp?.secretKey ?? kp?.secret ?? kp?.sk;
|
|
389
|
-
if (!pub || !priv) {
|
|
390
|
-
console.warn("[Kyber] Invalid keypair result");
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
return {
|
|
394
|
-
publicB64: b64(new Uint8Array(pub)),
|
|
395
|
-
secretB64: b64(new Uint8Array(priv))
|
|
396
|
-
};
|
|
397
|
-
} catch (e) {
|
|
398
|
-
console.warn("[Kyber] Key generation failed:", e);
|
|
399
|
-
return null;
|
|
400
|
-
}
|
|
367
|
+
const seed = randN(64);
|
|
368
|
+
const kp = ml_kem1024.keygen(seed);
|
|
369
|
+
return {
|
|
370
|
+
publicB64: b64(kp.publicKey),
|
|
371
|
+
secretB64: b64(kp.secretKey)
|
|
372
|
+
};
|
|
401
373
|
}
|
|
402
374
|
|
|
403
375
|
// src/vault/manager.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnituum/pqc-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Omnituum PQC Shared Library - Crypto, Vault, File Encryption, and Utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -31,25 +31,43 @@
|
|
|
31
31
|
"types": "./dist/fs/index.d.ts",
|
|
32
32
|
"import": "./dist/fs/index.js",
|
|
33
33
|
"require": "./dist/fs/index.cjs"
|
|
34
|
+
},
|
|
35
|
+
"./safe": {
|
|
36
|
+
"types": "./dist/crypto/safe/index.d.ts",
|
|
37
|
+
"import": "./dist/crypto/safe/index.js",
|
|
38
|
+
"require": "./dist/crypto/safe/index.cjs"
|
|
34
39
|
}
|
|
35
40
|
},
|
|
36
41
|
"files": [
|
|
37
42
|
"dist",
|
|
38
43
|
"src"
|
|
39
44
|
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"dev": "tsup --watch",
|
|
48
|
+
"clean": "rm -rf dist",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"test:golden:generate": "pnpm -s build && tsx tests/golden/generate-vectors.ts",
|
|
51
|
+
"test:golden:verify": "pnpm -s build && tsx tests/golden/verify-vectors.ts",
|
|
52
|
+
"test:golden": "pnpm test:golden:verify",
|
|
53
|
+
"test": "vitest run tests/",
|
|
54
|
+
"test:watch": "vitest tests/",
|
|
55
|
+
"pretest": "pnpm build"
|
|
56
|
+
},
|
|
40
57
|
"dependencies": {
|
|
41
58
|
"@noble/ciphers": "~2.0.0",
|
|
42
59
|
"@noble/hashes": "~2.0.0",
|
|
60
|
+
"@omnituum/envelope-registry": "github:Omnituum/envelope-registry#v0.1.2",
|
|
43
61
|
"@noble/post-quantum": "~0.5.4",
|
|
44
62
|
"hash-wasm": "^4.11.0",
|
|
45
|
-
"kyber-crystals": "^1.0.7",
|
|
46
63
|
"tweetnacl": "^1.0.3"
|
|
47
64
|
},
|
|
48
65
|
"devDependencies": {
|
|
49
66
|
"@types/node": "^25.0.9",
|
|
50
67
|
"tsup": "^8.5.0",
|
|
51
68
|
"tsx": "^4.19.0",
|
|
52
|
-
"typescript": "^5.9.3"
|
|
69
|
+
"typescript": "^5.9.3",
|
|
70
|
+
"vitest": "^3.1.0"
|
|
53
71
|
},
|
|
54
72
|
"keywords": [
|
|
55
73
|
"pqc",
|
|
@@ -73,14 +91,10 @@
|
|
|
73
91
|
"url": "https://github.com/Omnituum/pqc-shared/issues"
|
|
74
92
|
},
|
|
75
93
|
"homepage": "https://github.com/Omnituum/pqc-shared#readme",
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"test:golden:generate": "pnpm -s build && tsx tests/golden/generate-vectors.ts",
|
|
82
|
-
"test:golden:verify": "pnpm -s build && tsx tests/golden/verify-vectors.ts",
|
|
83
|
-
"test:golden": "npm run test:golden:verify",
|
|
84
|
-
"pretest": "npm run build"
|
|
94
|
+
"packageManager": "pnpm@10.32.1",
|
|
95
|
+
"pnpm": {
|
|
96
|
+
"onlyBuiltDependencies": [
|
|
97
|
+
"@omnituum/envelope-registry"
|
|
98
|
+
]
|
|
85
99
|
}
|
|
86
|
-
}
|
|
100
|
+
}
|
package/src/crypto/dilithium.ts
CHANGED
|
@@ -140,7 +140,8 @@ export async function dilithiumSign(
|
|
|
140
140
|
const sk = fromB64(secretKeyB64);
|
|
141
141
|
const msg = typeof message === 'string' ? new TextEncoder().encode(message) : message;
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
// noble API: sign(message, secretKey)
|
|
144
|
+
const signature = mod.sign(msg, sk);
|
|
144
145
|
|
|
145
146
|
return {
|
|
146
147
|
signature: toB64(signature),
|
|
@@ -160,7 +161,8 @@ export async function dilithiumSignRaw(
|
|
|
160
161
|
throw new Error('Dilithium library not available');
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
|
|
164
|
+
// noble API: sign(message, secretKey)
|
|
165
|
+
return mod.sign(message, secretKey);
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -190,7 +192,8 @@ export async function dilithiumVerify(
|
|
|
190
192
|
const msg = typeof message === 'string' ? new TextEncoder().encode(message) : message;
|
|
191
193
|
|
|
192
194
|
try {
|
|
193
|
-
|
|
195
|
+
// noble API: verify(signature, message, publicKey)
|
|
196
|
+
return mod.verify(sig, msg, pk);
|
|
194
197
|
} catch {
|
|
195
198
|
return false;
|
|
196
199
|
}
|
|
@@ -210,7 +213,8 @@ export async function dilithiumVerifyRaw(
|
|
|
210
213
|
}
|
|
211
214
|
|
|
212
215
|
try {
|
|
213
|
-
|
|
216
|
+
// noble API: verify(signature, message, publicKey)
|
|
217
|
+
return mod.verify(signature, message, publicKey);
|
|
214
218
|
} catch {
|
|
215
219
|
return false;
|
|
216
220
|
}
|
package/src/crypto/hybrid.ts
CHANGED
|
@@ -75,38 +75,22 @@ export interface HybridSecretKeys {
|
|
|
75
75
|
kyberSecB64: string;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
wrapped: string;
|
|
91
|
-
};
|
|
92
|
-
/** Kyber KEM ciphertext (base64) */
|
|
93
|
-
kyberKemCt: string;
|
|
94
|
-
/** Kyber wrapped content key */
|
|
95
|
-
kyberWrap: {
|
|
96
|
-
nonce: string;
|
|
97
|
-
wrapped: string;
|
|
98
|
-
};
|
|
99
|
-
/** Content encryption nonce (base64) */
|
|
100
|
-
contentNonce: string;
|
|
101
|
-
/** Encrypted content (base64) */
|
|
102
|
-
ciphertext: string;
|
|
103
|
-
/** Metadata */
|
|
104
|
-
meta: {
|
|
105
|
-
createdAt: string;
|
|
78
|
+
/**
|
|
79
|
+
* HybridEnvelope -- OmniHybridV1 from the registry with
|
|
80
|
+
* app-semantic meta fields (senderName, senderId).
|
|
81
|
+
* The Omni registry type defines only the crypto-relevant surface.
|
|
82
|
+
*
|
|
83
|
+
* Intentionally a type alias (not interface extends) to discourage
|
|
84
|
+
* further field additions here. New app-semantic fields belong in
|
|
85
|
+
* product-level types, not in the shared crypto layer.
|
|
86
|
+
*/
|
|
87
|
+
import type { OmniHybridV1 } from '@omnituum/envelope-registry';
|
|
88
|
+
export type HybridEnvelope = Omit<OmniHybridV1, 'meta'> & {
|
|
89
|
+
meta: OmniHybridV1['meta'] & {
|
|
106
90
|
senderName?: string;
|
|
107
91
|
senderId?: string;
|
|
108
92
|
};
|
|
109
|
-
}
|
|
93
|
+
};
|
|
110
94
|
|
|
111
95
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
112
96
|
// HELPERS
|
package/src/crypto/index.ts
CHANGED
|
@@ -83,11 +83,14 @@ export type {
|
|
|
83
83
|
export {
|
|
84
84
|
isKyberAvailable,
|
|
85
85
|
generateKyberKeypair,
|
|
86
|
+
generateKyberKeypairFromSeed,
|
|
86
87
|
kyberEncapsulate,
|
|
87
88
|
kyberDecapsulate,
|
|
88
89
|
kyberWrapKey,
|
|
89
90
|
kyberUnwrapKey,
|
|
91
|
+
KYBER_SUITE,
|
|
90
92
|
} from './kyber';
|
|
93
|
+
export type { KyberSuite } from './kyber';
|
|
91
94
|
|
|
92
95
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
93
96
|
// DILITHIUM ML-DSA-65 (Post-Quantum Signatures)
|
package/src/crypto/kyber.ts
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Omnituum PQC Shared
|
|
2
|
+
* Omnituum PQC Shared — Kyber (ML-KEM-1024, FIPS 203)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Backend: @noble/post-quantum `ml_kem1024`. NIST FIPS 203 (final, 2024).
|
|
5
|
+
*
|
|
6
|
+
* Wire-format note: previous releases (<= 0.3.x) of this package were backed by
|
|
7
|
+
* `kyber-crystals`, which implements an earlier draft of Kyber and is NOT
|
|
8
|
+
* interoperable with FIPS 203 — see `tests/interop/historical/` and
|
|
9
|
+
* `package-docs/GAPS_AND_TASKS.md` (PQC-02 result). 0.4.0 is an intentional
|
|
10
|
+
* clean break: legacy draft-Kyber material cannot be read by this version.
|
|
11
|
+
*
|
|
12
|
+
* Sizes (ML-KEM-1024):
|
|
13
|
+
* publicKey 1568 bytes
|
|
14
|
+
* secretKey 3168 bytes
|
|
15
|
+
* ciphertext 1568 bytes
|
|
16
|
+
* sharedSecret 32 bytes
|
|
17
|
+
*
|
|
18
|
+
* Suite tag for new material: "ML-KEM-1024-FIPS203".
|
|
6
19
|
*/
|
|
7
20
|
|
|
8
|
-
import {
|
|
21
|
+
import { ml_kem1024 } from '@noble/post-quantum/ml-kem.js';
|
|
22
|
+
import { b64, ub64, sha256, randN } from './primitives';
|
|
9
23
|
import nacl from 'tweetnacl';
|
|
10
24
|
|
|
11
25
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -27,33 +41,21 @@ export interface KyberEncapsulation {
|
|
|
27
41
|
sharedSecret: Uint8Array;
|
|
28
42
|
}
|
|
29
43
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let kyberModule: any = null;
|
|
35
|
-
|
|
36
|
-
async function loadKyber(): Promise<any> {
|
|
37
|
-
if (kyberModule) return kyberModule;
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const m = await import('kyber-crystals');
|
|
41
|
-
const k = (m as any).default ?? m;
|
|
42
|
-
kyberModule = k.kyber ?? k;
|
|
43
|
-
return kyberModule;
|
|
44
|
-
} catch (e) {
|
|
45
|
-
console.warn('[Kyber] Failed to load kyber-crystals:', e);
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
44
|
+
/** Canonical suite identifier for new material produced by this package. */
|
|
45
|
+
export const KYBER_SUITE = 'ML-KEM-1024-FIPS203' as const;
|
|
46
|
+
export type KyberSuite = typeof KYBER_SUITE;
|
|
49
47
|
|
|
50
48
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
51
|
-
// AVAILABILITY
|
|
49
|
+
// AVAILABILITY
|
|
52
50
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
53
51
|
|
|
52
|
+
/**
|
|
53
|
+
* @deprecated Always returns true. Retained for API stability across the
|
|
54
|
+
* 0.3.x → 0.4.x cut. The noble backend is statically imported and always
|
|
55
|
+
* present; there is no runtime feature-detection path.
|
|
56
|
+
*/
|
|
54
57
|
export async function isKyberAvailable(): Promise<boolean> {
|
|
55
|
-
|
|
56
|
-
return mod !== null;
|
|
58
|
+
return true;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -61,139 +63,79 @@ export async function isKyberAvailable(): Promise<boolean> {
|
|
|
61
63
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
62
64
|
|
|
63
65
|
/**
|
|
64
|
-
* Generate a
|
|
66
|
+
* Generate a fresh ML-KEM-1024 keypair using a cryptographically random seed.
|
|
65
67
|
*/
|
|
66
68
|
export async function generateKyberKeypair(): Promise<KyberKeypairB64 | null> {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
console.warn('[Kyber] Invalid keypair result');
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
publicB64: b64(new Uint8Array(pub)),
|
|
92
|
-
secretB64: b64(new Uint8Array(priv)),
|
|
93
|
-
};
|
|
94
|
-
} catch (e) {
|
|
95
|
-
console.warn('[Kyber] Key generation failed:', e);
|
|
96
|
-
return null;
|
|
69
|
+
const seed = randN(64);
|
|
70
|
+
const kp = ml_kem1024.keygen(seed);
|
|
71
|
+
return {
|
|
72
|
+
publicB64: b64(kp.publicKey),
|
|
73
|
+
secretB64: b64(kp.secretKey),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Derive a deterministic ML-KEM-1024 keypair from a 64-byte seed.
|
|
79
|
+
* Same seed → byte-identical keypair, across runtimes (Node, browser).
|
|
80
|
+
*
|
|
81
|
+
* The seed is passed verbatim to the FIPS 203 keygen. Callers that need
|
|
82
|
+
* domain separation between Kyber and other key types must apply it
|
|
83
|
+
* upstream (e.g. via SHA-256 with a domain tag) before calling.
|
|
84
|
+
*/
|
|
85
|
+
export function generateKyberKeypairFromSeed(seed: Uint8Array): KyberKeypair {
|
|
86
|
+
if (seed.length !== 64) {
|
|
87
|
+
throw new Error('Kyber seed must be 64 bytes');
|
|
97
88
|
}
|
|
89
|
+
const kp = ml_kem1024.keygen(seed);
|
|
90
|
+
return { publicKey: kp.publicKey, secretKey: kp.secretKey };
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
101
94
|
// ENCAPSULATION / DECAPSULATION
|
|
102
95
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
103
96
|
|
|
104
|
-
/**
|
|
105
|
-
* Encapsulate a shared secret using Kyber ML-KEM-768.
|
|
106
|
-
*/
|
|
107
97
|
export async function kyberEncapsulate(pubKeyB64: string): Promise<KyberEncapsulation> {
|
|
108
|
-
const kyber = await loadKyber();
|
|
109
|
-
if (!kyber?.encrypt) {
|
|
110
|
-
throw new Error('Kyber encrypt not available');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
98
|
const pk = ub64(pubKeyB64);
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
// Normalize ciphertext
|
|
117
|
-
const ctRaw =
|
|
118
|
-
r?.ciphertext ?? r?.cyphertext ?? r?.ct ??
|
|
119
|
-
r?.bytes?.ciphertext ?? r?.bytes?.cyphertext ?? r?.bytes?.ct ??
|
|
120
|
-
(Array.isArray(r) ? r[0] : undefined);
|
|
121
|
-
|
|
122
|
-
// Normalize shared secret
|
|
123
|
-
const ssRaw =
|
|
124
|
-
r?.key ?? r?.sharedSecret ?? r?.secret ??
|
|
125
|
-
r?.bytes?.key ?? r?.bytes?.sharedSecret ??
|
|
126
|
-
(Array.isArray(r) ? r[1] : undefined);
|
|
127
|
-
|
|
128
|
-
if (!ctRaw || !ssRaw) {
|
|
129
|
-
throw new Error('Kyber encapsulate failed: missing ciphertext or shared secret');
|
|
130
|
-
}
|
|
131
|
-
|
|
99
|
+
const enc = ml_kem1024.encapsulate(pk);
|
|
132
100
|
return {
|
|
133
|
-
ciphertext:
|
|
134
|
-
sharedSecret:
|
|
101
|
+
ciphertext: enc.cipherText,
|
|
102
|
+
sharedSecret: enc.sharedSecret,
|
|
135
103
|
};
|
|
136
104
|
}
|
|
137
105
|
|
|
138
|
-
/**
|
|
139
|
-
* Decapsulate a shared secret using Kyber ML-KEM-768.
|
|
140
|
-
*/
|
|
141
106
|
export async function kyberDecapsulate(
|
|
142
107
|
kemCiphertextB64: string,
|
|
143
|
-
secretKeyB64: string
|
|
108
|
+
secretKeyB64: string,
|
|
144
109
|
): Promise<Uint8Array> {
|
|
145
|
-
const kyber = await loadKyber();
|
|
146
|
-
if (!kyber?.decrypt && !kyber?.decapsulate) {
|
|
147
|
-
throw new Error('Kyber decrypt/decapsulate not available');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
110
|
const ct = ub64(kemCiphertextB64);
|
|
151
111
|
const sk = ub64(secretKeyB64);
|
|
152
|
-
|
|
153
|
-
const r: any = kyber.decrypt
|
|
154
|
-
? await kyber.decrypt(ct, sk)
|
|
155
|
-
: await kyber.decapsulate(ct, sk);
|
|
156
|
-
|
|
157
|
-
const key = (r && (r.key ?? r.sharedSecret)) ? (r.key ?? r.sharedSecret) : r;
|
|
158
|
-
return new Uint8Array(key);
|
|
112
|
+
return ml_kem1024.decapsulate(ct, sk);
|
|
159
113
|
}
|
|
160
114
|
|
|
161
115
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
162
|
-
// KEY WRAPPING
|
|
116
|
+
// KEY WRAPPING (library-agnostic; NaCl secretbox over derived KEK)
|
|
163
117
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
164
118
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
nonce: string;
|
|
170
|
-
wrapped: string;
|
|
171
|
-
} {
|
|
119
|
+
export function kyberWrapKey(
|
|
120
|
+
sharedSecret: Uint8Array,
|
|
121
|
+
msgKey32: Uint8Array,
|
|
122
|
+
): { nonce: string; wrapped: string } {
|
|
172
123
|
if (msgKey32.length !== 32) {
|
|
173
124
|
throw new Error('Message key must be 32 bytes');
|
|
174
125
|
}
|
|
175
|
-
|
|
176
|
-
const kek = sha256(sharedSecret); // Derive KEK from shared secret
|
|
126
|
+
const kek = sha256(sharedSecret);
|
|
177
127
|
const nonce = globalThis.crypto.getRandomValues(new Uint8Array(24));
|
|
178
128
|
const wrapped = nacl.secretbox(msgKey32, nonce, kek);
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
nonce: b64(nonce),
|
|
182
|
-
wrapped: b64(wrapped),
|
|
183
|
-
};
|
|
129
|
+
return { nonce: b64(nonce), wrapped: b64(wrapped) };
|
|
184
130
|
}
|
|
185
131
|
|
|
186
|
-
/**
|
|
187
|
-
* Unwrap a message key using Kyber shared secret.
|
|
188
|
-
*/
|
|
189
132
|
export function kyberUnwrapKey(
|
|
190
133
|
sharedSecret: Uint8Array,
|
|
191
134
|
nonceB64: string,
|
|
192
|
-
wrappedB64: string
|
|
135
|
+
wrappedB64: string,
|
|
193
136
|
): Uint8Array | null {
|
|
194
137
|
const kek = sha256(sharedSecret);
|
|
195
138
|
const nonce = ub64(nonceB64);
|
|
196
139
|
const wrapped = ub64(wrappedB64);
|
|
197
|
-
|
|
198
140
|
return nacl.secretbox.open(wrapped, nonce, kek) || null;
|
|
199
141
|
}
|