@hsuite/smart-engines-sdk 3.4.0 → 3.5.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.
@@ -0,0 +1,40 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ export type EnvelopeVersionLiteral = "kyber-aes-v1" | "aes-v0";
4
+ export type SchemaValidationResult = {
5
+ ok: true;
6
+ version: EnvelopeVersionLiteral;
7
+ } | {
8
+ ok: false;
9
+ reason: string;
10
+ };
11
+ export declare function validateEnvelopeSchema(envelope: unknown): SchemaValidationResult;
12
+ export declare const KYBER_MIN_TIMESTAMP_MS = 1704067200000;
13
+ export type VerificationDetails = {
14
+ version: string;
15
+ kemAlgorithm?: "ml-kem-768" | "ml-kem-1024";
16
+ recipientPkFingerprint?: string;
17
+ kdfLabel?: string;
18
+ encryptedAt?: number;
19
+ schemaValid: boolean;
20
+ base64Valid: boolean;
21
+ timestampPlausible: boolean;
22
+ };
23
+ export type EnvelopeVerificationResult = {
24
+ valid: true;
25
+ version: EnvelopeVersionLiteral;
26
+ details: VerificationDetails;
27
+ } | {
28
+ valid: false;
29
+ reason: string;
30
+ details?: Partial<VerificationDetails>;
31
+ };
32
+ export type VerifyEnvelopeOptions = {
33
+ expectedLabel?: string;
34
+ expectedRecipientPkFingerprint?: string;
35
+ minTimestamp?: number;
36
+ maxTimestamp?: number;
37
+ };
38
+ export declare function verifyPqcEnvelope(envelope: unknown, options?: VerifyEnvelopeOptions): Promise<EnvelopeVerificationResult>;
39
+
40
+ export {};
@@ -0,0 +1,244 @@
1
+ 'use strict';
2
+
3
+ // src/pqc-verify-envelope/envelope-schema-validator.ts
4
+ var KEM_CT_LEN = {
5
+ "ml-kem-768": 1088,
6
+ "ml-kem-1024": 1568
7
+ };
8
+ var AES_IV_LEN = 12;
9
+ var AES_TAG_LEN = 16;
10
+ var KDF_SALT_LEN = 16;
11
+ function tryDecodeBase64(s) {
12
+ if (typeof s !== "string" || s.length === 0) return null;
13
+ try {
14
+ const buf = Buffer.from(s, "base64");
15
+ if (buf.toString("base64").replace(/=+$/, "") !== s.replace(/=+$/, "")) {
16
+ return null;
17
+ }
18
+ return new Uint8Array(buf);
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function isString(v) {
24
+ return typeof v === "string";
25
+ }
26
+ function isNonEmptyString(v) {
27
+ return typeof v === "string" && v.length > 0;
28
+ }
29
+ function isFiniteNumber(v) {
30
+ return typeof v === "number" && Number.isFinite(v);
31
+ }
32
+ function isPlainObject(v) {
33
+ return typeof v === "object" && v !== null && !Array.isArray(v);
34
+ }
35
+ function validateEnvelopeSchema(envelope) {
36
+ if (!isPlainObject(envelope)) {
37
+ return { ok: false, reason: "envelope must be a JSON object" };
38
+ }
39
+ const version = envelope.version;
40
+ if (version === "kyber-aes-v1") {
41
+ return validateKyberAesV1(envelope);
42
+ }
43
+ if (version === "aes-v0") {
44
+ return validateAesV0(envelope);
45
+ }
46
+ return {
47
+ ok: false,
48
+ reason: `unknown envelope version: ${JSON.stringify(version)}`
49
+ };
50
+ }
51
+ function validateKyberAesV1(env) {
52
+ const kemAlgorithm = env.kemAlgorithm;
53
+ if (kemAlgorithm !== "ml-kem-768" && kemAlgorithm !== "ml-kem-1024") {
54
+ return {
55
+ ok: false,
56
+ reason: `kemAlgorithm must be 'ml-kem-768' or 'ml-kem-1024'`
57
+ };
58
+ }
59
+ if (!isNonEmptyString(env.kemCiphertext)) {
60
+ return { ok: false, reason: "kemCiphertext must be a non-empty base64 string" };
61
+ }
62
+ const kemCtBytes = tryDecodeBase64(env.kemCiphertext);
63
+ if (!kemCtBytes) {
64
+ return { ok: false, reason: "kemCiphertext is not valid base64" };
65
+ }
66
+ const expectedKemLen = KEM_CT_LEN[kemAlgorithm];
67
+ if (kemCtBytes.length !== expectedKemLen) {
68
+ return {
69
+ ok: false,
70
+ reason: `kemCiphertext length ${kemCtBytes.length} != expected ${expectedKemLen} for ${kemAlgorithm}`
71
+ };
72
+ }
73
+ if (!isString(env.recipientPkFingerprint) || !/^[0-9a-f]{32}$/i.test(env.recipientPkFingerprint)) {
74
+ return {
75
+ ok: false,
76
+ reason: "recipientPkFingerprint must be a 32-char hex string (16-byte sha384 prefix)"
77
+ };
78
+ }
79
+ if (env.aesAlgorithm !== "aes-256-gcm") {
80
+ return { ok: false, reason: `aesAlgorithm must be 'aes-256-gcm'` };
81
+ }
82
+ if (!isNonEmptyString(env.aesIv)) {
83
+ return { ok: false, reason: "aesIv must be a non-empty base64 string" };
84
+ }
85
+ const ivBytes = tryDecodeBase64(env.aesIv);
86
+ if (!ivBytes) return { ok: false, reason: "aesIv is not valid base64" };
87
+ if (ivBytes.length !== AES_IV_LEN) {
88
+ return {
89
+ ok: false,
90
+ reason: `aesIv length ${ivBytes.length} != expected ${AES_IV_LEN} (AES-GCM 96-bit nonce)`
91
+ };
92
+ }
93
+ if (typeof env.aesCiphertext !== "string") {
94
+ return { ok: false, reason: "aesCiphertext must be a base64 string" };
95
+ }
96
+ if (env.aesCiphertext.length > 0) {
97
+ const ctBytes = tryDecodeBase64(env.aesCiphertext);
98
+ if (!ctBytes) return { ok: false, reason: "aesCiphertext is not valid base64" };
99
+ }
100
+ if (!isNonEmptyString(env.aesAuthTag)) {
101
+ return { ok: false, reason: "aesAuthTag must be a non-empty base64 string" };
102
+ }
103
+ const tagBytes = tryDecodeBase64(env.aesAuthTag);
104
+ if (!tagBytes) return { ok: false, reason: "aesAuthTag is not valid base64" };
105
+ if (tagBytes.length !== AES_TAG_LEN) {
106
+ return {
107
+ ok: false,
108
+ reason: `aesAuthTag length ${tagBytes.length} != expected ${AES_TAG_LEN} (AES-GCM 128-bit tag)`
109
+ };
110
+ }
111
+ if (env.kdfAlgorithm !== "hkdf-sha384") {
112
+ return { ok: false, reason: `kdfAlgorithm must be 'hkdf-sha384'` };
113
+ }
114
+ if (!isNonEmptyString(env.kdfSalt)) {
115
+ return { ok: false, reason: "kdfSalt must be a non-empty base64 string" };
116
+ }
117
+ const saltBytes = tryDecodeBase64(env.kdfSalt);
118
+ if (!saltBytes) return { ok: false, reason: "kdfSalt is not valid base64" };
119
+ if (saltBytes.length !== KDF_SALT_LEN) {
120
+ return {
121
+ ok: false,
122
+ reason: `kdfSalt length ${saltBytes.length} != expected ${KDF_SALT_LEN}`
123
+ };
124
+ }
125
+ if (!isNonEmptyString(env.kdfLabel)) {
126
+ return { ok: false, reason: "kdfLabel must be a non-empty string" };
127
+ }
128
+ if (env.kdfLabel.length > 256) {
129
+ return {
130
+ ok: false,
131
+ reason: `kdfLabel length ${env.kdfLabel.length} exceeds 256-char limit`
132
+ };
133
+ }
134
+ if (!isFiniteNumber(env.encryptedAt)) {
135
+ return { ok: false, reason: "encryptedAt must be a finite number (millis epoch)" };
136
+ }
137
+ return { ok: true, version: "kyber-aes-v1" };
138
+ }
139
+ function validateAesV0(env) {
140
+ if (!isNonEmptyString(env.aesIv)) {
141
+ return { ok: false, reason: "aesIv must be a non-empty base64 string" };
142
+ }
143
+ const ivBytes = tryDecodeBase64(env.aesIv);
144
+ if (!ivBytes) return { ok: false, reason: "aesIv is not valid base64" };
145
+ if (ivBytes.length !== AES_IV_LEN) {
146
+ return {
147
+ ok: false,
148
+ reason: `aesIv length ${ivBytes.length} != expected ${AES_IV_LEN} (AES-GCM 96-bit nonce)`
149
+ };
150
+ }
151
+ if (typeof env.aesCiphertext !== "string") {
152
+ return { ok: false, reason: "aesCiphertext must be a base64 string" };
153
+ }
154
+ if (env.aesCiphertext.length > 0) {
155
+ const ctBytes = tryDecodeBase64(env.aesCiphertext);
156
+ if (!ctBytes) return { ok: false, reason: "aesCiphertext is not valid base64" };
157
+ }
158
+ if (!isNonEmptyString(env.aesAuthTag)) {
159
+ return { ok: false, reason: "aesAuthTag must be a non-empty base64 string" };
160
+ }
161
+ const tagBytes = tryDecodeBase64(env.aesAuthTag);
162
+ if (!tagBytes) return { ok: false, reason: "aesAuthTag is not valid base64" };
163
+ if (tagBytes.length !== AES_TAG_LEN) {
164
+ return {
165
+ ok: false,
166
+ reason: `aesAuthTag length ${tagBytes.length} != expected ${AES_TAG_LEN} (AES-GCM 128-bit tag)`
167
+ };
168
+ }
169
+ return { ok: true, version: "aes-v0" };
170
+ }
171
+
172
+ // src/pqc-verify-envelope/verify-pqc-envelope.ts
173
+ var KYBER_MIN_TIMESTAMP_MS = 17040672e5;
174
+ async function verifyPqcEnvelope(envelope, options = {}) {
175
+ const schema = validateEnvelopeSchema(envelope);
176
+ if (!schema.ok) {
177
+ return {
178
+ valid: false,
179
+ reason: `schema-invalid: ${schema.reason}`,
180
+ details: { schemaValid: false, base64Valid: false, timestampPlausible: false }
181
+ };
182
+ }
183
+ const env = envelope;
184
+ const version = schema.version;
185
+ const details = {
186
+ version,
187
+ schemaValid: true,
188
+ base64Valid: true,
189
+ // computed below for kyber-aes-v1; legacy has no encryptedAt so treat as plausible.
190
+ timestampPlausible: true
191
+ };
192
+ if (version === "kyber-aes-v1") {
193
+ details.kemAlgorithm = env.kemAlgorithm;
194
+ details.recipientPkFingerprint = env.recipientPkFingerprint;
195
+ details.kdfLabel = env.kdfLabel;
196
+ details.encryptedAt = env.encryptedAt;
197
+ const minTs = options.minTimestamp ?? KYBER_MIN_TIMESTAMP_MS;
198
+ const maxTs = options.maxTimestamp ?? Date.now();
199
+ const ts = details.encryptedAt;
200
+ if (ts < minTs) {
201
+ details.timestampPlausible = false;
202
+ return {
203
+ valid: false,
204
+ reason: `timestamp-before-minimum: encryptedAt=${ts} < min=${minTs}`,
205
+ details
206
+ };
207
+ }
208
+ if (ts > maxTs) {
209
+ details.timestampPlausible = false;
210
+ return {
211
+ valid: false,
212
+ reason: `timestamp-in-future: encryptedAt=${ts} > max=${maxTs}`,
213
+ details
214
+ };
215
+ }
216
+ if (options.expectedLabel !== void 0) {
217
+ if (details.kdfLabel !== options.expectedLabel) {
218
+ return {
219
+ valid: false,
220
+ reason: `label-mismatch: expected='${options.expectedLabel}' actual='${details.kdfLabel}'`,
221
+ details
222
+ };
223
+ }
224
+ }
225
+ if (options.expectedRecipientPkFingerprint !== void 0) {
226
+ const a = (details.recipientPkFingerprint ?? "").toLowerCase();
227
+ const b = options.expectedRecipientPkFingerprint.toLowerCase();
228
+ if (a !== b) {
229
+ return {
230
+ valid: false,
231
+ reason: `recipient-pk-fingerprint-mismatch: expected='${b}' actual='${a}'`,
232
+ details
233
+ };
234
+ }
235
+ }
236
+ }
237
+ return { valid: true, version, details };
238
+ }
239
+
240
+ exports.KYBER_MIN_TIMESTAMP_MS = KYBER_MIN_TIMESTAMP_MS;
241
+ exports.validateEnvelopeSchema = validateEnvelopeSchema;
242
+ exports.verifyPqcEnvelope = verifyPqcEnvelope;
243
+ //# sourceMappingURL=index.js.map
244
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pqc-verify-envelope/envelope-schema-validator.ts","../../src/pqc-verify-envelope/verify-pqc-envelope.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Kyber Arc 11 — envelope schema validator (zero-dep, fail-soft).\n *\n * Pure structural validation for the two envelope versions defined by\n * `libs/security-core/src/encryption-pqc/envelope.types.ts`:\n *\n * - `kyber-aes-v1` — modern hybrid (ML-KEM + HKDF-SHA384 + AES-256-GCM)\n * - `aes-v0` — legacy AES-only (read-only 12-month compat window)\n *\n * Customer-facing — therefore intentionally re-declares the envelope shape\n * here (instead of importing from `security-core`) so the SDK bundle stays\n * decoupled from the platform-internal package tree. The schema literals\n * MUST stay in sync with `security-core/src/encryption-pqc/envelope.types.ts`;\n * any divergence is a bug.\n *\n * Validation is structure-only — no decryption, no key access, no\n * cryptographic verification. Customers use this to catch malformed\n * envelopes BEFORE handing them to a decrypt routine (so the platform's\n * format invariants are visible at the SDK layer).\n *\n * @see docs/superpowers/specs/2026-05-29-kyber-mlkem-production-readiness.md §5 Arc 11\n * @see docs/superpowers/specs/2026-05-29-kyber-mlkem-production-readiness.md §6.1\n */\n\n/**\n * Discriminator literals accepted by `validateEnvelopeSchema`.\n *\n * Keep this union in sync with\n * `libs/security-core/src/encryption-pqc/envelope.types.ts` (`AnyEnvelope`).\n * Any new envelope version added there MUST add a literal here AND a\n * matching branch in `validateEnvelopeSchema`.\n */\nexport type EnvelopeVersionLiteral = 'kyber-aes-v1' | 'aes-v0';\n\n/**\n * Structural validation result for one envelope.\n *\n * - `ok: true` → caller can trust every required field exists with the\n * correct type, base64 fields decode, and version-specific length\n * invariants hold.\n * - `ok: false` → first failure is reported; further failures are NOT\n * accumulated (early-exit keeps the verifier output single-cause).\n */\nexport type SchemaValidationResult =\n | { ok: true; version: EnvelopeVersionLiteral }\n | { ok: false; reason: string };\n\n/**\n * ML-KEM ciphertext byte-length invariants (NIST FIPS-203).\n * - ML-KEM-768 → 1088 bytes\n * - ML-KEM-1024 → 1568 bytes\n *\n * These are exact, not bounds — any deviation indicates a malformed\n * envelope (truncation, padding, wrong-algo emit). Verifier rejects\n * mismatches outright.\n */\nconst KEM_CT_LEN: Record<'ml-kem-768' | 'ml-kem-1024', number> = {\n 'ml-kem-768': 1088,\n 'ml-kem-1024': 1568,\n};\n\n/** AES-GCM standard nonce = 96 bits = 12 bytes. */\nconst AES_IV_LEN = 12;\n/** AES-GCM auth tag = 128 bits = 16 bytes. */\nconst AES_TAG_LEN = 16;\n/** Per-envelope HKDF salt = 16 bytes (matches `encryptHybrid` writer). */\nconst KDF_SALT_LEN = 16;\n\n/**\n * Decode a base64 string, returning `null` on any decode error (illegal\n * chars, length not multiple of 4, etc.) rather than throwing — the\n * verifier surface fail-softs every failure mode.\n *\n * Uses `Buffer.from(s, 'base64')` which is permissive (silently drops\n * illegal chars). The follow-up length check is the real gate.\n */\nfunction tryDecodeBase64(s: string): Uint8Array | null {\n if (typeof s !== 'string' || s.length === 0) return null;\n try {\n const buf = Buffer.from(s, 'base64');\n // Round-trip check: re-encode and compare. Catches malformed input\n // that `Buffer.from` silently accepted.\n if (buf.toString('base64').replace(/=+$/, '') !== s.replace(/=+$/, '')) {\n return null;\n }\n return new Uint8Array(buf);\n } catch {\n return null;\n }\n}\n\nfunction isString(v: unknown): v is string {\n return typeof v === 'string';\n}\n\nfunction isNonEmptyString(v: unknown): v is string {\n return typeof v === 'string' && v.length > 0;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v);\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/**\n * Validate the structural shape of an envelope.\n *\n * Branches on `version` discriminator and checks the per-version contract.\n * Returns `{ ok: false, reason }` on the first failure — single-cause\n * diagnostics keep verifier output actionable.\n *\n * Side-effect-free, throws never.\n */\nexport function validateEnvelopeSchema(envelope: unknown): SchemaValidationResult {\n if (!isPlainObject(envelope)) {\n return { ok: false, reason: 'envelope must be a JSON object' };\n }\n\n const version = (envelope as { version?: unknown }).version;\n if (version === 'kyber-aes-v1') {\n return validateKyberAesV1(envelope);\n }\n if (version === 'aes-v0') {\n return validateAesV0(envelope);\n }\n return {\n ok: false,\n reason: `unknown envelope version: ${JSON.stringify(version)}`,\n };\n}\n\nfunction validateKyberAesV1(env: Record<string, unknown>): SchemaValidationResult {\n // kemAlgorithm\n const kemAlgorithm = env.kemAlgorithm;\n if (kemAlgorithm !== 'ml-kem-768' && kemAlgorithm !== 'ml-kem-1024') {\n return {\n ok: false,\n reason: `kemAlgorithm must be 'ml-kem-768' or 'ml-kem-1024'`,\n };\n }\n\n // kemCiphertext (base64) — exact length per FIPS-203.\n if (!isNonEmptyString(env.kemCiphertext)) {\n return { ok: false, reason: 'kemCiphertext must be a non-empty base64 string' };\n }\n const kemCtBytes = tryDecodeBase64(env.kemCiphertext);\n if (!kemCtBytes) {\n return { ok: false, reason: 'kemCiphertext is not valid base64' };\n }\n const expectedKemLen = KEM_CT_LEN[kemAlgorithm];\n if (kemCtBytes.length !== expectedKemLen) {\n return {\n ok: false,\n reason: `kemCiphertext length ${kemCtBytes.length} != expected ${expectedKemLen} for ${kemAlgorithm}`,\n };\n }\n\n // recipientPkFingerprint — hex string, 32 chars (16-byte sha384 prefix).\n if (!isString(env.recipientPkFingerprint) || !/^[0-9a-f]{32}$/i.test(env.recipientPkFingerprint)) {\n return {\n ok: false,\n reason: 'recipientPkFingerprint must be a 32-char hex string (16-byte sha384 prefix)',\n };\n }\n\n // aesAlgorithm (pinned)\n if (env.aesAlgorithm !== 'aes-256-gcm') {\n return { ok: false, reason: `aesAlgorithm must be 'aes-256-gcm'` };\n }\n\n // aesIv (base64, 12 bytes)\n if (!isNonEmptyString(env.aesIv)) {\n return { ok: false, reason: 'aesIv must be a non-empty base64 string' };\n }\n const ivBytes = tryDecodeBase64(env.aesIv);\n if (!ivBytes) return { ok: false, reason: 'aesIv is not valid base64' };\n if (ivBytes.length !== AES_IV_LEN) {\n return {\n ok: false,\n reason: `aesIv length ${ivBytes.length} != expected ${AES_IV_LEN} (AES-GCM 96-bit nonce)`,\n };\n }\n\n // aesCiphertext (base64) — any length, including 0 (empty plaintext is valid).\n if (typeof env.aesCiphertext !== 'string') {\n return { ok: false, reason: 'aesCiphertext must be a base64 string' };\n }\n // Permit empty string explicitly (0-byte ciphertext from empty plaintext).\n if (env.aesCiphertext.length > 0) {\n const ctBytes = tryDecodeBase64(env.aesCiphertext);\n if (!ctBytes) return { ok: false, reason: 'aesCiphertext is not valid base64' };\n }\n\n // aesAuthTag (base64, 16 bytes)\n if (!isNonEmptyString(env.aesAuthTag)) {\n return { ok: false, reason: 'aesAuthTag must be a non-empty base64 string' };\n }\n const tagBytes = tryDecodeBase64(env.aesAuthTag);\n if (!tagBytes) return { ok: false, reason: 'aesAuthTag is not valid base64' };\n if (tagBytes.length !== AES_TAG_LEN) {\n return {\n ok: false,\n reason: `aesAuthTag length ${tagBytes.length} != expected ${AES_TAG_LEN} (AES-GCM 128-bit tag)`,\n };\n }\n\n // kdfAlgorithm (pinned)\n if (env.kdfAlgorithm !== 'hkdf-sha384') {\n return { ok: false, reason: `kdfAlgorithm must be 'hkdf-sha384'` };\n }\n\n // kdfSalt (base64, 16 bytes)\n if (!isNonEmptyString(env.kdfSalt)) {\n return { ok: false, reason: 'kdfSalt must be a non-empty base64 string' };\n }\n const saltBytes = tryDecodeBase64(env.kdfSalt);\n if (!saltBytes) return { ok: false, reason: 'kdfSalt is not valid base64' };\n if (saltBytes.length !== KDF_SALT_LEN) {\n return {\n ok: false,\n reason: `kdfSalt length ${saltBytes.length} != expected ${KDF_SALT_LEN}`,\n };\n }\n\n // kdfLabel — non-empty, reasonable upper bound (256 chars) to catch\n // pathological inputs without imposing a real schema constraint.\n if (!isNonEmptyString(env.kdfLabel)) {\n return { ok: false, reason: 'kdfLabel must be a non-empty string' };\n }\n if (env.kdfLabel.length > 256) {\n return {\n ok: false,\n reason: `kdfLabel length ${env.kdfLabel.length} exceeds 256-char limit`,\n };\n }\n\n // encryptedAt — finite number (millis epoch).\n if (!isFiniteNumber(env.encryptedAt)) {\n return { ok: false, reason: 'encryptedAt must be a finite number (millis epoch)' };\n }\n\n return { ok: true, version: 'kyber-aes-v1' };\n}\n\nfunction validateAesV0(env: Record<string, unknown>): SchemaValidationResult {\n if (!isNonEmptyString(env.aesIv)) {\n return { ok: false, reason: 'aesIv must be a non-empty base64 string' };\n }\n const ivBytes = tryDecodeBase64(env.aesIv);\n if (!ivBytes) return { ok: false, reason: 'aesIv is not valid base64' };\n if (ivBytes.length !== AES_IV_LEN) {\n return {\n ok: false,\n reason: `aesIv length ${ivBytes.length} != expected ${AES_IV_LEN} (AES-GCM 96-bit nonce)`,\n };\n }\n\n if (typeof env.aesCiphertext !== 'string') {\n return { ok: false, reason: 'aesCiphertext must be a base64 string' };\n }\n if (env.aesCiphertext.length > 0) {\n const ctBytes = tryDecodeBase64(env.aesCiphertext);\n if (!ctBytes) return { ok: false, reason: 'aesCiphertext is not valid base64' };\n }\n\n if (!isNonEmptyString(env.aesAuthTag)) {\n return { ok: false, reason: 'aesAuthTag must be a non-empty base64 string' };\n }\n const tagBytes = tryDecodeBase64(env.aesAuthTag);\n if (!tagBytes) return { ok: false, reason: 'aesAuthTag is not valid base64' };\n if (tagBytes.length !== AES_TAG_LEN) {\n return {\n ok: false,\n reason: `aesAuthTag length ${tagBytes.length} != expected ${AES_TAG_LEN} (AES-GCM 128-bit tag)`,\n };\n }\n\n return { ok: true, version: 'aes-v0' };\n}\n","/**\n * Kyber Arc 11 — `verifyPqcEnvelope` external verifier primitive.\n *\n * Observation-only customer surface for the Kyber/ML-KEM hybrid envelopes\n * the platform emits (Arcs 2-10 write paths). Does NOT decrypt — verifier\n * holds no key material. Checks structural well-formedness + claim\n * plausibility so customers can detect malformed envelopes BEFORE handing\n * them to a decryption routine.\n *\n * Three classes of checks:\n *\n * 1. Schema (`envelope-schema-validator.ts`) — discriminator literal,\n * required fields, types, base64 decodability, exact-length field\n * invariants per FIPS-203 (ML-KEM ciphertext) + AES-GCM (iv/tag).\n *\n * 2. Timestamp plausibility — `encryptedAt` must fall between FIPS-203\n * standardization (2024-01-01, before which no hybrid envelope could\n * have been validly emitted) and `Date.now()` (future timestamps\n * indicate clock skew / forged envelope).\n *\n * 3. Optional caller claims — when supplied, `expectedLabel` and\n * `expectedRecipientPkFingerprint` are byte-equality checked against\n * the envelope fields. Useful for verifying an envelope was emitted\n * for the surface the caller expects.\n *\n * Fail-soft at every step — returns a discriminated `EnvelopeVerificationResult`\n * with `reason` on failure. Throws only on truly impossible inputs (e.g.\n * the caller passing a non-Promise to `await`).\n *\n * @see docs/superpowers/specs/2026-05-29-kyber-mlkem-production-readiness.md §5 Arc 11\n * @see libs/security-core/src/encryption-pqc/envelope.types.ts (KyberAesEnvelopeV1, AesEnvelopeV0Legacy)\n * @see libs/security-core/src/encryption-pqc/encrypt-hybrid.ts (the producer this verifier observes)\n */\nimport {\n validateEnvelopeSchema,\n type EnvelopeVersionLiteral,\n} from './envelope-schema-validator';\n\n/**\n * FIPS-203 standardization date — millis since epoch for 2024-01-01 00:00 UTC.\n *\n * No production hybrid envelope can legitimately predate this. Customer-facing\n * floor for `encryptedAt` plausibility check; overridable via\n * `VerifyEnvelopeOptions.minTimestamp` for test/staging fixtures.\n */\nexport const KYBER_MIN_TIMESTAMP_MS = 1_704_067_200_000;\n\n/**\n * Detailed verification metadata exposed to callers regardless of valid/invalid\n * outcome. Partial on failure (whichever fields were inspectable before the\n * first failure).\n */\nexport type VerificationDetails = {\n /** Envelope version literal (`kyber-aes-v1` or `aes-v0`). */\n version: string;\n /** ML-KEM parameter set, only present for `kyber-aes-v1`. */\n kemAlgorithm?: 'ml-kem-768' | 'ml-kem-1024';\n /** 32-char hex sha384-prefix of the recipient pk; only present for hybrid. */\n recipientPkFingerprint?: string;\n /** Per-surface HKDF label as recorded by the writer. */\n kdfLabel?: string;\n /** Writer wall-clock at emit time (millis epoch). */\n encryptedAt?: number;\n /** True iff all schema-shape checks passed. */\n schemaValid: boolean;\n /** True iff every base64 field decoded successfully. (Subsumed by `schemaValid`.) */\n base64Valid: boolean;\n /** True iff `encryptedAt` fell within `[minTimestamp, maxTimestamp]`. */\n timestampPlausible: boolean;\n};\n\n/**\n * Discriminated verification outcome. `valid: true` indicates the envelope is\n * STRUCTURALLY well-formed for its declared version AND every caller-supplied\n * claim matched. It does NOT indicate the platform actually emitted the\n * envelope (no signature here) nor that the ciphertext will decrypt (no key\n * here) — those gates belong to PQC-Sig's `verifyPqcAttestation` (signature\n * provenance) and the platform's `decryptHybrid` / `decryptAny` (ciphertext\n * authenticity).\n */\nexport type EnvelopeVerificationResult =\n | {\n valid: true;\n version: EnvelopeVersionLiteral;\n details: VerificationDetails;\n }\n | {\n valid: false;\n reason: string;\n details?: Partial<VerificationDetails>;\n };\n\n/**\n * Caller-facing knobs for `verifyPqcEnvelope`.\n *\n * @field expectedLabel — when set, the envelope's `kdfLabel` MUST equal this\n * value (byte-equality). Surface-mismatch defense — catches envelopes\n * addressed to a different consumer being replayed at this consumer.\n * Only meaningful for `kyber-aes-v1` envelopes (legacy `aes-v0` has no label).\n *\n * @field expectedRecipientPkFingerprint — when set, the envelope's\n * `recipientPkFingerprint` MUST equal this value (case-insensitive hex\n * compare). Useful for asserting the envelope was emitted for a specific\n * cluster Kyber keypair. Only meaningful for `kyber-aes-v1`.\n *\n * @field minTimestamp — lower bound (inclusive, millis epoch) for\n * `encryptedAt`. Default: `KYBER_MIN_TIMESTAMP_MS` (2024-01-01).\n *\n * @field maxTimestamp — upper bound (inclusive, millis epoch) for\n * `encryptedAt`. Default: `Date.now()` evaluated at verify time.\n * Pass a fixed future-skew tolerance (e.g. `Date.now() + 60_000`) if\n * verifying envelopes from a known-skewed source.\n */\nexport type VerifyEnvelopeOptions = {\n expectedLabel?: string;\n expectedRecipientPkFingerprint?: string;\n minTimestamp?: number;\n maxTimestamp?: number;\n};\n\n/**\n * Verify a Kyber/ML-KEM hybrid envelope (or legacy AES-v0 envelope) for\n * structural well-formedness + claim consistency.\n *\n * Pure function — no I/O, no key material, no decryption. Customer code\n * calls this BEFORE passing the envelope to a decryption surface; if it\n * returns `{ valid: false }`, the envelope is malformed and decryption\n * would fail anyway (often with a less-actionable error).\n *\n * Async signature for forward-compatibility with potential network checks\n * (e.g. fetch recipient-pk registry to resolve the fingerprint to a known\n * cluster). The Arc 11 baseline does NOT make any network calls.\n *\n * @example\n * ```ts\n * import { verifyPqcEnvelope } from '@hsuite/smart-engines-sdk/pqc-verify-envelope';\n *\n * const result = await verifyPqcEnvelope(envelopeJson, {\n * expectedLabel: 'smart-app-secret-envelope-v1',\n * });\n * if (!result.valid) {\n * throw new Error(`Bad envelope: ${result.reason}`);\n * }\n * ```\n */\nexport async function verifyPqcEnvelope(\n envelope: unknown,\n options: VerifyEnvelopeOptions = {},\n): Promise<EnvelopeVerificationResult> {\n // ── Step 1: Schema validation ─────────────────────────────────────────────\n const schema = validateEnvelopeSchema(envelope);\n if (!schema.ok) {\n return {\n valid: false,\n reason: `schema-invalid: ${schema.reason}`,\n details: { schemaValid: false, base64Valid: false, timestampPlausible: false },\n };\n }\n\n const env = envelope as Record<string, unknown>;\n const version = schema.version;\n\n // ── Step 2: Build inspectable details ─────────────────────────────────────\n const details: VerificationDetails = {\n version,\n schemaValid: true,\n base64Valid: true,\n // computed below for kyber-aes-v1; legacy has no encryptedAt so treat as plausible.\n timestampPlausible: true,\n };\n\n if (version === 'kyber-aes-v1') {\n details.kemAlgorithm = env.kemAlgorithm as 'ml-kem-768' | 'ml-kem-1024';\n details.recipientPkFingerprint = env.recipientPkFingerprint as string;\n details.kdfLabel = env.kdfLabel as string;\n details.encryptedAt = env.encryptedAt as number;\n\n // ── Step 3: Timestamp plausibility ─────────────────────────────────────\n const minTs = options.minTimestamp ?? KYBER_MIN_TIMESTAMP_MS;\n const maxTs = options.maxTimestamp ?? Date.now();\n const ts = details.encryptedAt as number;\n if (ts < minTs) {\n details.timestampPlausible = false;\n return {\n valid: false,\n reason: `timestamp-before-minimum: encryptedAt=${ts} < min=${minTs}`,\n details,\n };\n }\n if (ts > maxTs) {\n details.timestampPlausible = false;\n return {\n valid: false,\n reason: `timestamp-in-future: encryptedAt=${ts} > max=${maxTs}`,\n details,\n };\n }\n\n // ── Step 4: Optional caller-claim checks ───────────────────────────────\n if (options.expectedLabel !== undefined) {\n if (details.kdfLabel !== options.expectedLabel) {\n return {\n valid: false,\n reason: `label-mismatch: expected='${options.expectedLabel}' actual='${details.kdfLabel}'`,\n details,\n };\n }\n }\n if (options.expectedRecipientPkFingerprint !== undefined) {\n const a = (details.recipientPkFingerprint ?? '').toLowerCase();\n const b = options.expectedRecipientPkFingerprint.toLowerCase();\n if (a !== b) {\n return {\n valid: false,\n reason: `recipient-pk-fingerprint-mismatch: expected='${b}' actual='${a}'`,\n details,\n };\n }\n }\n }\n // aes-v0: no extra checks beyond schema. Legacy envelopes are read-only\n // through the 12-month Arc 12 compat window; verifier accepts them as\n // structurally valid but offers no claim checks (label/fingerprint don't\n // exist on the legacy shape).\n\n return { valid: true, version, details };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hsuite/smart-engines-sdk",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Simplified client SDK for Smart Engines multi-chain infrastructure",
5
5
  "sideEffects": false,
6
6
  "main": "dist/index.js",
@@ -17,6 +17,18 @@
17
17
  "./pqc-verify": {
18
18
  "types": "./dist/pqc-verify/index.d.ts",
19
19
  "default": "./dist/pqc-verify/index.js"
20
+ },
21
+ "./pqc-verify-envelope": {
22
+ "types": "./dist/pqc-verify-envelope/index.d.ts",
23
+ "default": "./dist/pqc-verify-envelope/index.js"
24
+ },
25
+ "./ipfs-access-key": {
26
+ "types": "./dist/ipfs-access-key/index.d.ts",
27
+ "default": "./dist/ipfs-access-key/index.js"
28
+ },
29
+ "./k8s-secret-reader": {
30
+ "types": "./dist/k8s-secret-reader/index.d.ts",
31
+ "default": "./dist/k8s-secret-reader/index.js"
20
32
  }
21
33
  },
22
34
  "scripts": {
@@ -24,10 +36,11 @@
24
36
  "clean": "rm -rf dist",
25
37
  "lint": "eslint src --ext .ts",
26
38
  "test": "jest",
39
+ "docs": "compodoc -p tsconfig.build.json -c ../../.compodocrc.json -d dist/docs --name '@hsuite/smart-engines-sdk' --silent --disableCoverage",
27
40
  "build:tsc": "tsc --skipLibCheck --declaration",
28
41
  "prepublishOnly": "npm run clean && npm run build",
29
42
  "build:js": "tsup",
30
- "build:dts": "dts-bundle-generator --silent --project tsconfig.build.json -o dist/index.d.ts src/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/nestjs/index.d.ts src/nestjs/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/pqc-verify/index.d.ts src/pqc-verify/index.ts"
43
+ "build:dts": "dts-bundle-generator --silent --project tsconfig.build.json -o dist/index.d.ts src/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/nestjs/index.d.ts src/nestjs/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/pqc-verify/index.d.ts src/pqc-verify/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/pqc-verify-envelope/index.d.ts src/pqc-verify-envelope/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/ipfs-access-key/index.d.ts src/ipfs-access-key/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/k8s-secret-reader/index.d.ts src/k8s-secret-reader/index.ts"
31
44
  },
32
45
  "dependencies": {
33
46
  "@noble/hashes": "^1.4.0",