@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.
- package/dist/crypto/index.cjs +81 -54
- package/dist/crypto/index.d.cts +33 -11
- package/dist/crypto/index.d.ts +33 -11
- package/dist/crypto/index.js +81 -54
- package/dist/{decrypt-eSHlbh1j.d.cts → decrypt-O1iqFIDL.d.cts} +80 -6
- package/dist/{decrypt-eSHlbh1j.d.ts → decrypt-O1iqFIDL.d.ts} +80 -6
- package/dist/fs/index.cjs +286 -147
- package/dist/fs/index.d.cts +28 -7
- package/dist/fs/index.d.ts +28 -7
- package/dist/fs/index.js +277 -147
- package/dist/index.cjs +358 -229
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +357 -230
- package/dist/{integrity-BenoFsmP.d.cts → integrity-BkBDrHA-.d.cts} +12 -4
- package/dist/{integrity-D9J98Bty.d.ts → integrity-DsFJv5c-.d.ts} +12 -4
- package/dist/{types-CRka8PC7.d.cts → types-DmnVueAY.d.cts} +14 -4
- package/dist/{types-CRka8PC7.d.ts → types-DmnVueAY.d.ts} +14 -4
- package/dist/utils/index.cjs +7 -10
- package/dist/utils/index.d.cts +2 -2
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +7 -10
- package/dist/vault/index.cjs +8 -11
- package/dist/vault/index.d.cts +2 -2
- package/dist/vault/index.d.ts +2 -2
- package/dist/vault/index.js +8 -11
- package/package.json +7 -7
- package/src/crypto/dilithium.ts +4 -4
- package/src/crypto/hybrid.ts +182 -80
- package/src/crypto/index.ts +2 -0
- package/src/fs/decrypt.ts +145 -68
- package/src/fs/encrypt.ts +161 -112
- package/src/fs/format.ts +110 -15
- package/src/fs/index.ts +4 -0
- package/src/fs/types.ts +77 -6
- package/src/index.ts +4 -0
- package/src/utils/integrity.ts +16 -15
- package/src/vault/manager.ts +1 -1
- package/src/version.ts +29 -7
package/dist/crypto/index.cjs
CHANGED
|
@@ -261,8 +261,7 @@ async function loadDilithium() {
|
|
|
261
261
|
const { ml_dsa65 } = await import('@noble/post-quantum/ml-dsa.js');
|
|
262
262
|
dilithiumModule = ml_dsa65;
|
|
263
263
|
return dilithiumModule;
|
|
264
|
-
} catch
|
|
265
|
-
console.warn("[Dilithium] Failed to load @noble/post-quantum:", e);
|
|
264
|
+
} catch {
|
|
266
265
|
return null;
|
|
267
266
|
}
|
|
268
267
|
}
|
|
@@ -280,8 +279,7 @@ async function generateDilithiumKeypair() {
|
|
|
280
279
|
publicB64: toB64(kp.publicKey),
|
|
281
280
|
secretB64: toB64(kp.secretKey)
|
|
282
281
|
};
|
|
283
|
-
} catch
|
|
284
|
-
console.warn("[Dilithium] Key generation failed:", e);
|
|
282
|
+
} catch {
|
|
285
283
|
return null;
|
|
286
284
|
}
|
|
287
285
|
}
|
|
@@ -350,11 +348,13 @@ var DILITHIUM_SECRET_KEY_SIZE = 4032;
|
|
|
350
348
|
var DILITHIUM_SIGNATURE_SIZE = 3309;
|
|
351
349
|
var DILITHIUM_ALGORITHM = "ML-DSA-65";
|
|
352
350
|
var ENVELOPE_VERSION = envelopeRegistry.OMNI_VERSIONS.HYBRID_V1;
|
|
351
|
+
var ENVELOPE_VERSION_V2 = envelopeRegistry.OMNI_VERSIONS.HYBRID_V2;
|
|
353
352
|
var ENVELOPE_VERSION_LEGACY = envelopeRegistry.DEPRECATED_VERSIONS.PQC_DEMO_HYBRID_V1;
|
|
354
|
-
var
|
|
353
|
+
var ENVELOPE_SUITE_V2 = "x25519+mlkem1024";
|
|
355
354
|
var ENVELOPE_AEAD = "xsalsa20poly1305";
|
|
356
355
|
var SUPPORTED_ENVELOPE_VERSIONS = [
|
|
357
356
|
ENVELOPE_VERSION,
|
|
357
|
+
ENVELOPE_VERSION_V2,
|
|
358
358
|
ENVELOPE_VERSION_LEGACY
|
|
359
359
|
];
|
|
360
360
|
var VersionMismatchError = class extends Error {
|
|
@@ -386,7 +386,6 @@ async function generateHybridIdentity(name) {
|
|
|
386
386
|
const x25519 = generateX25519Keypair();
|
|
387
387
|
const kyber = await generateKyberKeypair();
|
|
388
388
|
if (!kyber) {
|
|
389
|
-
console.error("Kyber key generation failed - library not available");
|
|
390
389
|
return null;
|
|
391
390
|
}
|
|
392
391
|
return {
|
|
@@ -428,49 +427,74 @@ function getSecretKeys(identity) {
|
|
|
428
427
|
kyberSecB64: identity.kyberSecB64
|
|
429
428
|
};
|
|
430
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
|
+
}
|
|
431
440
|
async function hybridEncrypt(plaintext, recipientPublicKeys, sender) {
|
|
432
441
|
const pt = typeof plaintext === "string" ? textEncoder.encode(plaintext) : plaintext;
|
|
433
442
|
const CK = rand32();
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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;
|
|
468
495
|
}
|
|
469
|
-
async function
|
|
470
|
-
const version = envelope.v;
|
|
471
|
-
assertEnvelopeVersion(version);
|
|
496
|
+
async function unwrapCkV1(envelope, secretKeys, saltPrefix) {
|
|
472
497
|
let CK = null;
|
|
473
|
-
const saltPrefix = version === "pqc-demo.hybrid.v1" ? "pqc-demo" : "omnituum";
|
|
474
498
|
try {
|
|
475
499
|
const kyberShared = await kyberDecapsulate(envelope.kyberKemCt, secretKeys.kyberSecB64);
|
|
476
500
|
const kyberKek = hkdfFlex(kyberShared, `${saltPrefix}/kyber`, "wrap-ck");
|
|
@@ -479,11 +503,7 @@ async function hybridDecrypt(envelope, secretKeys) {
|
|
|
479
503
|
ub64(envelope.kyberWrap.nonce),
|
|
480
504
|
kyberKek
|
|
481
505
|
);
|
|
482
|
-
|
|
483
|
-
console.log("[Hybrid] Decrypted using Kyber (post-quantum)");
|
|
484
|
-
}
|
|
485
|
-
} catch (e) {
|
|
486
|
-
console.warn("[Hybrid] Kyber decapsulation failed:", e);
|
|
506
|
+
} catch {
|
|
487
507
|
}
|
|
488
508
|
if (!CK) {
|
|
489
509
|
try {
|
|
@@ -496,21 +516,28 @@ async function hybridDecrypt(envelope, secretKeys) {
|
|
|
496
516
|
ub64(envelope.x25519Wrap.nonce),
|
|
497
517
|
x25519Kek
|
|
498
518
|
);
|
|
499
|
-
|
|
500
|
-
console.log("[Hybrid] Decrypted using X25519 (classical)");
|
|
501
|
-
}
|
|
502
|
-
} catch (e) {
|
|
503
|
-
console.warn("[Hybrid] X25519 decryption failed:", e);
|
|
519
|
+
} catch {
|
|
504
520
|
}
|
|
505
521
|
}
|
|
506
522
|
if (!CK) {
|
|
507
523
|
throw new Error("Could not unwrap content key with either algorithm");
|
|
508
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
|
+
);
|
|
509
535
|
const pt = nacl__default.default.secretbox.open(
|
|
510
536
|
ub64(envelope.ciphertext),
|
|
511
537
|
ub64(envelope.contentNonce),
|
|
512
538
|
CK
|
|
513
539
|
);
|
|
540
|
+
CK.fill(0);
|
|
514
541
|
if (!pt) {
|
|
515
542
|
throw new Error("Content authentication failed");
|
|
516
543
|
}
|
package/dist/crypto/index.d.cts
CHANGED
|
@@ -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
|
/**
|
|
@@ -77,10 +77,16 @@ declare function kyberUnwrapKey(sharedSecret: Uint8Array, nonceB64: string, wrap
|
|
|
77
77
|
*
|
|
78
78
|
* Post-quantum secure hybrid encryption combining:
|
|
79
79
|
* - X25519 ECDH (classical, proven security)
|
|
80
|
-
* -
|
|
80
|
+
* - ML-KEM-1024 (post-quantum, FIPS 203, NIST Level 5)
|
|
81
81
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
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.
|
|
84
90
|
*/
|
|
85
91
|
|
|
86
92
|
interface HybridIdentity {
|
|
@@ -116,12 +122,21 @@ interface HybridSecretKeys {
|
|
|
116
122
|
kyberSecB64: string;
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
type
|
|
125
|
+
type HybridEnvelopeV1 = Omit<OmniHybridV1, 'meta'> & {
|
|
120
126
|
meta: OmniHybridV1['meta'] & {
|
|
121
127
|
senderName?: string;
|
|
122
128
|
senderId?: string;
|
|
123
129
|
};
|
|
124
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;
|
|
125
140
|
/**
|
|
126
141
|
* Generate a new hybrid identity with both X25519 and Kyber keys.
|
|
127
142
|
*/
|
|
@@ -139,7 +154,12 @@ declare function getPublicKeys(identity: HybridIdentity): HybridPublicKeys;
|
|
|
139
154
|
*/
|
|
140
155
|
declare function getSecretKeys(identity: HybridIdentity): HybridSecretKeys;
|
|
141
156
|
/**
|
|
142
|
-
* Encrypt a message using hybrid X25519 +
|
|
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.)
|
|
143
163
|
*
|
|
144
164
|
* @param plaintext - Message to encrypt (string or bytes)
|
|
145
165
|
* @param recipientPublicKeys - Recipient's public keys
|
|
@@ -148,12 +168,14 @@ declare function getSecretKeys(identity: HybridIdentity): HybridSecretKeys;
|
|
|
148
168
|
declare function hybridEncrypt(plaintext: string | Uint8Array, recipientPublicKeys: HybridPublicKeys, sender?: {
|
|
149
169
|
name?: string;
|
|
150
170
|
id?: string;
|
|
151
|
-
}): Promise<
|
|
171
|
+
}): Promise<HybridEnvelopeV2>;
|
|
152
172
|
/**
|
|
153
|
-
* Decrypt a hybrid envelope.
|
|
173
|
+
* Decrypt a hybrid envelope (v2, v1, or legacy pqc-demo).
|
|
154
174
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
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).
|
|
157
179
|
*
|
|
158
180
|
* @param envelope - Encrypted envelope
|
|
159
181
|
* @param secretKeys - Recipient's secret keys
|
|
@@ -641,4 +663,4 @@ declare const BOX_KEY_SIZE: number;
|
|
|
641
663
|
/** NaCl box nonce size (24 bytes) */
|
|
642
664
|
declare const BOX_NONCE_SIZE: number;
|
|
643
665
|
|
|
644
|
-
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_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 };
|
|
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 };
|
package/dist/crypto/index.d.ts
CHANGED
|
@@ -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
|
/**
|
|
@@ -77,10 +77,16 @@ declare function kyberUnwrapKey(sharedSecret: Uint8Array, nonceB64: string, wrap
|
|
|
77
77
|
*
|
|
78
78
|
* Post-quantum secure hybrid encryption combining:
|
|
79
79
|
* - X25519 ECDH (classical, proven security)
|
|
80
|
-
* -
|
|
80
|
+
* - ML-KEM-1024 (post-quantum, FIPS 203, NIST Level 5)
|
|
81
81
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
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.
|
|
84
90
|
*/
|
|
85
91
|
|
|
86
92
|
interface HybridIdentity {
|
|
@@ -116,12 +122,21 @@ interface HybridSecretKeys {
|
|
|
116
122
|
kyberSecB64: string;
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
type
|
|
125
|
+
type HybridEnvelopeV1 = Omit<OmniHybridV1, 'meta'> & {
|
|
120
126
|
meta: OmniHybridV1['meta'] & {
|
|
121
127
|
senderName?: string;
|
|
122
128
|
senderId?: string;
|
|
123
129
|
};
|
|
124
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;
|
|
125
140
|
/**
|
|
126
141
|
* Generate a new hybrid identity with both X25519 and Kyber keys.
|
|
127
142
|
*/
|
|
@@ -139,7 +154,12 @@ declare function getPublicKeys(identity: HybridIdentity): HybridPublicKeys;
|
|
|
139
154
|
*/
|
|
140
155
|
declare function getSecretKeys(identity: HybridIdentity): HybridSecretKeys;
|
|
141
156
|
/**
|
|
142
|
-
* Encrypt a message using hybrid X25519 +
|
|
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.)
|
|
143
163
|
*
|
|
144
164
|
* @param plaintext - Message to encrypt (string or bytes)
|
|
145
165
|
* @param recipientPublicKeys - Recipient's public keys
|
|
@@ -148,12 +168,14 @@ declare function getSecretKeys(identity: HybridIdentity): HybridSecretKeys;
|
|
|
148
168
|
declare function hybridEncrypt(plaintext: string | Uint8Array, recipientPublicKeys: HybridPublicKeys, sender?: {
|
|
149
169
|
name?: string;
|
|
150
170
|
id?: string;
|
|
151
|
-
}): Promise<
|
|
171
|
+
}): Promise<HybridEnvelopeV2>;
|
|
152
172
|
/**
|
|
153
|
-
* Decrypt a hybrid envelope.
|
|
173
|
+
* Decrypt a hybrid envelope (v2, v1, or legacy pqc-demo).
|
|
154
174
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
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).
|
|
157
179
|
*
|
|
158
180
|
* @param envelope - Encrypted envelope
|
|
159
181
|
* @param secretKeys - Recipient's secret keys
|
|
@@ -641,4 +663,4 @@ declare const BOX_KEY_SIZE: number;
|
|
|
641
663
|
/** NaCl box nonce size (24 bytes) */
|
|
642
664
|
declare const BOX_NONCE_SIZE: number;
|
|
643
665
|
|
|
644
|
-
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_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 };
|
|
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 };
|
package/dist/crypto/index.js
CHANGED
|
@@ -255,8 +255,7 @@ async function loadDilithium() {
|
|
|
255
255
|
const { ml_dsa65 } = await import('@noble/post-quantum/ml-dsa.js');
|
|
256
256
|
dilithiumModule = ml_dsa65;
|
|
257
257
|
return dilithiumModule;
|
|
258
|
-
} catch
|
|
259
|
-
console.warn("[Dilithium] Failed to load @noble/post-quantum:", e);
|
|
258
|
+
} catch {
|
|
260
259
|
return null;
|
|
261
260
|
}
|
|
262
261
|
}
|
|
@@ -274,8 +273,7 @@ async function generateDilithiumKeypair() {
|
|
|
274
273
|
publicB64: toB64(kp.publicKey),
|
|
275
274
|
secretB64: toB64(kp.secretKey)
|
|
276
275
|
};
|
|
277
|
-
} catch
|
|
278
|
-
console.warn("[Dilithium] Key generation failed:", e);
|
|
276
|
+
} catch {
|
|
279
277
|
return null;
|
|
280
278
|
}
|
|
281
279
|
}
|
|
@@ -344,11 +342,13 @@ var DILITHIUM_SECRET_KEY_SIZE = 4032;
|
|
|
344
342
|
var DILITHIUM_SIGNATURE_SIZE = 3309;
|
|
345
343
|
var DILITHIUM_ALGORITHM = "ML-DSA-65";
|
|
346
344
|
var ENVELOPE_VERSION = OMNI_VERSIONS.HYBRID_V1;
|
|
345
|
+
var ENVELOPE_VERSION_V2 = OMNI_VERSIONS.HYBRID_V2;
|
|
347
346
|
var ENVELOPE_VERSION_LEGACY = DEPRECATED_VERSIONS.PQC_DEMO_HYBRID_V1;
|
|
348
|
-
var
|
|
347
|
+
var ENVELOPE_SUITE_V2 = "x25519+mlkem1024";
|
|
349
348
|
var ENVELOPE_AEAD = "xsalsa20poly1305";
|
|
350
349
|
var SUPPORTED_ENVELOPE_VERSIONS = [
|
|
351
350
|
ENVELOPE_VERSION,
|
|
351
|
+
ENVELOPE_VERSION_V2,
|
|
352
352
|
ENVELOPE_VERSION_LEGACY
|
|
353
353
|
];
|
|
354
354
|
var VersionMismatchError = class extends Error {
|
|
@@ -380,7 +380,6 @@ async function generateHybridIdentity(name) {
|
|
|
380
380
|
const x25519 = generateX25519Keypair();
|
|
381
381
|
const kyber = await generateKyberKeypair();
|
|
382
382
|
if (!kyber) {
|
|
383
|
-
console.error("Kyber key generation failed - library not available");
|
|
384
383
|
return null;
|
|
385
384
|
}
|
|
386
385
|
return {
|
|
@@ -422,49 +421,74 @@ function getSecretKeys(identity) {
|
|
|
422
421
|
kyberSecB64: identity.kyberSecB64
|
|
423
422
|
};
|
|
424
423
|
}
|
|
424
|
+
function combinedKekV2(kyberShared, x25519Shared, x25519EpkHex, kyberKemCtB64) {
|
|
425
|
+
const ikm = new Uint8Array(kyberShared.length + x25519Shared.length);
|
|
426
|
+
ikm.set(kyberShared, 0);
|
|
427
|
+
ikm.set(x25519Shared, kyberShared.length);
|
|
428
|
+
try {
|
|
429
|
+
return hkdfFlex(ikm, "omnituum/hybrid-v2", `wrap-ck|${x25519EpkHex}|${kyberKemCtB64}`);
|
|
430
|
+
} finally {
|
|
431
|
+
ikm.fill(0);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
425
434
|
async function hybridEncrypt(plaintext, recipientPublicKeys, sender) {
|
|
426
435
|
const pt = typeof plaintext === "string" ? textEncoder.encode(plaintext) : plaintext;
|
|
427
436
|
const CK = rand32();
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
437
|
+
try {
|
|
438
|
+
const contentNonce = rand24();
|
|
439
|
+
const ciphertext = nacl.secretbox(pt, contentNonce, CK);
|
|
440
|
+
const x25519EphKp = nacl.box.keyPair();
|
|
441
|
+
const recipientX25519Pk = fromHex(recipientPublicKeys.x25519PubHex);
|
|
442
|
+
const x25519Shared = nacl.scalarMult(x25519EphKp.secretKey, recipientX25519Pk);
|
|
443
|
+
const x25519EpkHex = toHex(x25519EphKp.publicKey);
|
|
444
|
+
const kyberResult = await kyberEncapsulate(recipientPublicKeys.kyberPubB64);
|
|
445
|
+
const kyberKemCtB64 = b64(kyberResult.ciphertext);
|
|
446
|
+
const kek = combinedKekV2(kyberResult.sharedSecret, x25519Shared, x25519EpkHex, kyberKemCtB64);
|
|
447
|
+
const ckWrapNonce = rand24();
|
|
448
|
+
const ckWrapped = nacl.secretbox(CK, ckWrapNonce, kek);
|
|
449
|
+
kek.fill(0);
|
|
450
|
+
x25519Shared.fill(0);
|
|
451
|
+
kyberResult.sharedSecret.fill(0);
|
|
452
|
+
x25519EphKp.secretKey.fill(0);
|
|
453
|
+
return {
|
|
454
|
+
v: ENVELOPE_VERSION_V2,
|
|
455
|
+
suite: ENVELOPE_SUITE_V2,
|
|
456
|
+
aead: ENVELOPE_AEAD,
|
|
457
|
+
x25519Epk: x25519EpkHex,
|
|
458
|
+
kyberKemCt: kyberKemCtB64,
|
|
459
|
+
ckWrap: {
|
|
460
|
+
nonce: b64(ckWrapNonce),
|
|
461
|
+
wrapped: b64(ckWrapped)
|
|
462
|
+
},
|
|
463
|
+
contentNonce: b64(contentNonce),
|
|
464
|
+
ciphertext: b64(ciphertext),
|
|
465
|
+
meta: {
|
|
466
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
467
|
+
senderName: sender?.name,
|
|
468
|
+
senderId: sender?.id
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
} finally {
|
|
472
|
+
CK.fill(0);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async function unwrapCkV2(envelope, secretKeys) {
|
|
476
|
+
const kyberShared = await kyberDecapsulate(envelope.kyberKemCt, secretKeys.kyberSecB64);
|
|
477
|
+
const ephPk = fromHex(envelope.x25519Epk);
|
|
478
|
+
const sk = fromHex(secretKeys.x25519SecHex);
|
|
479
|
+
const x25519Shared = nacl.scalarMult(sk, ephPk);
|
|
480
|
+
const kek = combinedKekV2(kyberShared, x25519Shared, envelope.x25519Epk, envelope.kyberKemCt);
|
|
481
|
+
kyberShared.fill(0);
|
|
482
|
+
x25519Shared.fill(0);
|
|
483
|
+
const CK = nacl.secretbox.open(ub64(envelope.ckWrap.wrapped), ub64(envelope.ckWrap.nonce), kek);
|
|
484
|
+
kek.fill(0);
|
|
485
|
+
if (!CK) {
|
|
486
|
+
throw new Error("Could not unwrap content key \u2014 combined-KEK authentication failed");
|
|
487
|
+
}
|
|
488
|
+
return CK;
|
|
462
489
|
}
|
|
463
|
-
async function
|
|
464
|
-
const version = envelope.v;
|
|
465
|
-
assertEnvelopeVersion(version);
|
|
490
|
+
async function unwrapCkV1(envelope, secretKeys, saltPrefix) {
|
|
466
491
|
let CK = null;
|
|
467
|
-
const saltPrefix = version === "pqc-demo.hybrid.v1" ? "pqc-demo" : "omnituum";
|
|
468
492
|
try {
|
|
469
493
|
const kyberShared = await kyberDecapsulate(envelope.kyberKemCt, secretKeys.kyberSecB64);
|
|
470
494
|
const kyberKek = hkdfFlex(kyberShared, `${saltPrefix}/kyber`, "wrap-ck");
|
|
@@ -473,11 +497,7 @@ async function hybridDecrypt(envelope, secretKeys) {
|
|
|
473
497
|
ub64(envelope.kyberWrap.nonce),
|
|
474
498
|
kyberKek
|
|
475
499
|
);
|
|
476
|
-
|
|
477
|
-
console.log("[Hybrid] Decrypted using Kyber (post-quantum)");
|
|
478
|
-
}
|
|
479
|
-
} catch (e) {
|
|
480
|
-
console.warn("[Hybrid] Kyber decapsulation failed:", e);
|
|
500
|
+
} catch {
|
|
481
501
|
}
|
|
482
502
|
if (!CK) {
|
|
483
503
|
try {
|
|
@@ -490,21 +510,28 @@ async function hybridDecrypt(envelope, secretKeys) {
|
|
|
490
510
|
ub64(envelope.x25519Wrap.nonce),
|
|
491
511
|
x25519Kek
|
|
492
512
|
);
|
|
493
|
-
|
|
494
|
-
console.log("[Hybrid] Decrypted using X25519 (classical)");
|
|
495
|
-
}
|
|
496
|
-
} catch (e) {
|
|
497
|
-
console.warn("[Hybrid] X25519 decryption failed:", e);
|
|
513
|
+
} catch {
|
|
498
514
|
}
|
|
499
515
|
}
|
|
500
516
|
if (!CK) {
|
|
501
517
|
throw new Error("Could not unwrap content key with either algorithm");
|
|
502
518
|
}
|
|
519
|
+
return CK;
|
|
520
|
+
}
|
|
521
|
+
async function hybridDecrypt(envelope, secretKeys) {
|
|
522
|
+
const version = envelope.v;
|
|
523
|
+
assertEnvelopeVersion(version);
|
|
524
|
+
const CK = version === ENVELOPE_VERSION_V2 ? await unwrapCkV2(envelope, secretKeys) : await unwrapCkV1(
|
|
525
|
+
envelope,
|
|
526
|
+
secretKeys,
|
|
527
|
+
version === "pqc-demo.hybrid.v1" ? "pqc-demo" : "omnituum"
|
|
528
|
+
);
|
|
503
529
|
const pt = nacl.secretbox.open(
|
|
504
530
|
ub64(envelope.ciphertext),
|
|
505
531
|
ub64(envelope.contentNonce),
|
|
506
532
|
CK
|
|
507
533
|
);
|
|
534
|
+
CK.fill(0);
|
|
508
535
|
if (!pt) {
|
|
509
536
|
throw new Error("Content authentication failed");
|
|
510
537
|
}
|