@fabricorg/experiments-manifest-signing 0.0.1 → 0.1.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/index.cjs CHANGED
@@ -17,6 +17,14 @@ function canonicalSignedBytes(m) {
17
17
  return new TextEncoder().encode(canonicalSignedPayload(m));
18
18
  }
19
19
 
20
+ // src/subtle.ts
21
+ async function getSubtle() {
22
+ if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;
23
+ const nodeCryptoSpecifier = "node:crypto";
24
+ const { webcrypto } = await import(nodeCryptoSpecifier);
25
+ return webcrypto.subtle;
26
+ }
27
+
20
28
  // src/hash.ts
21
29
  async function sha256(data) {
22
30
  const subtle = await getSubtle();
@@ -28,15 +36,10 @@ function hexEncode(bytes) {
28
36
  for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, "0");
29
37
  return s;
30
38
  }
31
- async function getSubtle() {
32
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
33
- const { webcrypto } = await import('crypto');
34
- return webcrypto.subtle;
35
- }
36
39
 
37
40
  // src/sign.ts
38
41
  async function signManifest(manifest, key) {
39
- const subtle = await getSubtle2();
42
+ const subtle = await getSubtle();
40
43
  const payload = canonicalSignedBytes(manifest);
41
44
  const signedHash = await sha256(payload);
42
45
  const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));
@@ -55,11 +58,6 @@ async function signManifest(manifest, key) {
55
58
  signedHash: hexEncode(signedHash)
56
59
  };
57
60
  }
58
- async function getSubtle2() {
59
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
60
- const { webcrypto } = await import('crypto');
61
- return webcrypto.subtle;
62
- }
63
61
  function hexDecode(hex) {
64
62
  if (hex.length % 2 !== 0) throw new Error("hexDecode: odd length");
65
63
  const out = new Uint8Array(hex.length / 2);
@@ -102,7 +100,7 @@ async function verifyManifest(manifest, options) {
102
100
  if (expectedHash !== manifest.signature.signedHash) {
103
101
  return { ok: false, reason: "signedHash-mismatch" };
104
102
  }
105
- const subtle = await getSubtle3();
103
+ const subtle = await getSubtle();
106
104
  const pubKey = await subtle.importKey(
107
105
  "raw",
108
106
  hexDecode2(options.publicKeyHex),
@@ -118,11 +116,6 @@ async function verifyManifest(manifest, options) {
118
116
  );
119
117
  return valid ? { ok: true } : { ok: false, reason: "sig-invalid" };
120
118
  }
121
- async function getSubtle3() {
122
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
123
- const { webcrypto } = await import('crypto');
124
- return webcrypto.subtle;
125
- }
126
119
  function hexDecode2(hex) {
127
120
  if (hex.length % 2 !== 0) throw new Error("hexDecode: odd length");
128
121
  const out = new Uint8Array(hex.length / 2);
@@ -130,28 +123,6 @@ function hexDecode2(hex) {
130
123
  return out;
131
124
  }
132
125
 
133
- // src/keypair.ts
134
- async function generateKeypair(keyId) {
135
- const subtle = await getSubtle4();
136
- const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
137
- "sign",
138
- "verify"
139
- ]);
140
- const rawPub = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
141
- const pkcs8Priv = new Uint8Array(await subtle.exportKey("pkcs8", kp.privateKey));
142
- const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);
143
- return {
144
- publicKeyHex: hexEncode(rawPub),
145
- privateKeyHex: hexEncode(seed),
146
- keyId
147
- };
148
- }
149
- async function getSubtle4() {
150
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
151
- const { webcrypto } = await import('crypto');
152
- return webcrypto.subtle;
153
- }
154
-
155
126
  // src/jwks.ts
156
127
  function jwksFromKeys(keys) {
157
128
  return {
@@ -181,7 +152,7 @@ async function verifyAgainstJwks(manifest, options) {
181
152
  const payload = canonicalSignedBytes(manifest);
182
153
  const expectedHash = hexEncode(await sha256(payload));
183
154
  if (expectedHash !== sig.signedHash) return { ok: false, reason: "signedHash-mismatch" };
184
- const subtle = await getSubtle5();
155
+ const subtle = await getSubtle();
185
156
  const pubKey = await subtle.importKey(
186
157
  "raw",
187
158
  base64UrlToBytes(entry.x),
@@ -197,11 +168,6 @@ async function verifyAgainstJwks(manifest, options) {
197
168
  );
198
169
  return valid ? { ok: true } : { ok: false, reason: "sig-invalid" };
199
170
  }
200
- async function getSubtle5() {
201
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
202
- const { webcrypto } = await import('crypto');
203
- return webcrypto.subtle;
204
- }
205
171
  function hexToBytes(hex) {
206
172
  const out = new Uint8Array(hex.length / 2);
207
173
  for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
@@ -224,12 +190,168 @@ function base64UrlToBytes(s) {
224
190
  return out;
225
191
  }
226
192
 
193
+ // src/delivery.ts
194
+ async function verifyManifestDelivery(input, options) {
195
+ const parsed = parseDeliveryManifest(input);
196
+ if (!parsed.ok) return parsed;
197
+ if (options.revokedVersions?.includes(parsed.manifest.manifestVersion)) {
198
+ return { ok: false, reason: "manifest-revoked" };
199
+ }
200
+ const payload = {
201
+ schemaVersion: parsed.manifest.schemaVersion,
202
+ manifestVersion: parsed.manifest.manifestVersion,
203
+ tenantId: parsed.manifest.tenantId,
204
+ experiments: parsed.manifest.experiments
205
+ };
206
+ const actualHash = hexEncode(await sha256(new TextEncoder().encode(canonicalJson(payload))));
207
+ if (actualHash !== parsed.manifest.contentHash) {
208
+ return { ok: false, reason: "content-hash-mismatch" };
209
+ }
210
+ try {
211
+ const signature = await verifyAgainstJwks(parsed.manifest, options);
212
+ return signature.ok ? { ok: true, manifest: parsed.manifest } : signature;
213
+ } catch (error) {
214
+ return {
215
+ ok: false,
216
+ reason: "schema-invalid",
217
+ details: error instanceof Error ? error.message : String(error)
218
+ };
219
+ }
220
+ }
221
+ function parseManifestRevocations(input) {
222
+ if (!isRecord(input) || !Array.isArray(input.versions)) {
223
+ throw new Error("manifest revocations must contain a versions array");
224
+ }
225
+ const versions = input.versions;
226
+ if (!versions.every((version) => Number.isInteger(version) && Number(version) >= 0)) {
227
+ throw new Error("manifest revocation versions must be non-negative integers");
228
+ }
229
+ return [...new Set(versions)];
230
+ }
231
+ function parseDeliveryManifest(input) {
232
+ if (!isRecord(input)) return invalid("manifest must be an object");
233
+ if (input.schemaVersion !== 1) return invalid("unsupported schemaVersion");
234
+ if (!Number.isInteger(input.manifestVersion) || Number(input.manifestVersion) < 0) {
235
+ return invalid("manifestVersion must be a non-negative integer");
236
+ }
237
+ if (!isHex(input.contentHash, 64)) return invalid("contentHash must be SHA-256 hex");
238
+ if (!isNonEmptyString(input.tenantId)) return invalid("tenantId is required");
239
+ if (!isIsoDate(input.builtAt)) return invalid("builtAt must be an ISO timestamp");
240
+ if (!Array.isArray(input.experiments)) return invalid("experiments must be an array");
241
+ if (!isSignature(input.signature)) return invalid("a valid Ed25519 signature is required");
242
+ const experimentIds = /* @__PURE__ */ new Set();
243
+ for (const [index, experiment] of input.experiments.entries()) {
244
+ const issue = validateExperiment(experiment, experimentIds);
245
+ if (issue) return invalid(`experiments[${index}]: ${issue}`);
246
+ }
247
+ return { ok: true, manifest: input };
248
+ }
249
+ function validateExperiment(value, experimentIds) {
250
+ if (!isRecord(value)) return "must be an object";
251
+ for (const key of ["id", "name", "salt"]) {
252
+ if (!isNonEmptyString(value[key])) return `${key} is required`;
253
+ }
254
+ if (experimentIds.has(value.id)) return `duplicate experiment id ${value.id}`;
255
+ experimentIds.add(value.id);
256
+ if (value.state !== "running" && value.state !== "paused") return "invalid state";
257
+ if (!isRate(value.sampleRate)) return "sampleRate must be between 0 and 1";
258
+ if (typeof value.applyVariants !== "boolean") return "applyVariants must be boolean";
259
+ if (!Array.isArray(value.variants) || value.variants.length < 2) {
260
+ return "at least two variants are required";
261
+ }
262
+ const variantKeys = /* @__PURE__ */ new Set();
263
+ let declaredWeight = 0;
264
+ let weightedVariants = 0;
265
+ for (const [index, variant] of value.variants.entries()) {
266
+ if (!isRecord(variant) || !isNonEmptyString(variant.key)) {
267
+ return `variants[${index}].key is required`;
268
+ }
269
+ if (!isNonEmptyString(variant.name)) return `variants[${index}].name is required`;
270
+ if (variantKeys.has(variant.key)) return `duplicate variant key ${variant.key}`;
271
+ variantKeys.add(variant.key);
272
+ if (variant.weight !== void 0) {
273
+ if (!Number.isInteger(variant.weight) || Number(variant.weight) < 0 || Number(variant.weight) > 1e4) {
274
+ return `variants[${index}].weight must be an integer between 0 and 10000`;
275
+ }
276
+ weightedVariants += 1;
277
+ declaredWeight += Number(variant.weight);
278
+ }
279
+ if (variant.recipeSampleRate !== void 0 && !isRate(variant.recipeSampleRate)) {
280
+ return `variants[${index}].recipeSampleRate must be between 0 and 1`;
281
+ }
282
+ }
283
+ if (weightedVariants > 0 && weightedVariants !== value.variants.length) {
284
+ return "variant weights must be declared for every variant or none";
285
+ }
286
+ if (weightedVariants > 0 && declaredWeight !== 1e4) {
287
+ return "variant weights must sum to 10000";
288
+ }
289
+ for (const key of ["defaultVariantKey", "divertTo"]) {
290
+ if (value[key] !== void 0 && (!isNonEmptyString(value[key]) || !variantKeys.has(value[key]))) {
291
+ return `${key} must reference an existing variant`;
292
+ }
293
+ }
294
+ return null;
295
+ }
296
+ function canonicalJson(value) {
297
+ return JSON.stringify(sortDeep(value));
298
+ }
299
+ function sortDeep(value) {
300
+ if (Array.isArray(value)) return value.map(sortDeep);
301
+ if (isRecord(value)) {
302
+ const sorted = {};
303
+ for (const key of Object.keys(value).sort()) sorted[key] = sortDeep(value[key]);
304
+ return sorted;
305
+ }
306
+ return value;
307
+ }
308
+ function invalid(details) {
309
+ return { ok: false, reason: "schema-invalid", details };
310
+ }
311
+ function isRecord(value) {
312
+ return value !== null && typeof value === "object" && !Array.isArray(value);
313
+ }
314
+ function isNonEmptyString(value) {
315
+ return typeof value === "string" && value.length > 0;
316
+ }
317
+ function isRate(value) {
318
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
319
+ }
320
+ function isHex(value, length) {
321
+ return typeof value === "string" && new RegExp(`^[0-9a-f]{${length}}$`).test(value);
322
+ }
323
+ function isIsoDate(value) {
324
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
325
+ }
326
+ function isSignature(value) {
327
+ return isRecord(value) && isNonEmptyString(value.keyId) && value.alg === "ed25519" && isHex(value.sig, 128) && isHex(value.signedHash, 64);
328
+ }
329
+
330
+ // src/keypair.ts
331
+ async function generateKeypair(keyId) {
332
+ const subtle = await getSubtle();
333
+ const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
334
+ "sign",
335
+ "verify"
336
+ ]);
337
+ const rawPub = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
338
+ const pkcs8Priv = new Uint8Array(await subtle.exportKey("pkcs8", kp.privateKey));
339
+ const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);
340
+ return {
341
+ publicKeyHex: hexEncode(rawPub),
342
+ privateKeyHex: hexEncode(seed),
343
+ keyId
344
+ };
345
+ }
346
+
227
347
  exports.canonicalSignedBytes = canonicalSignedBytes;
228
348
  exports.canonicalSignedPayload = canonicalSignedPayload;
229
349
  exports.generateKeypair = generateKeypair;
230
350
  exports.jwksFromKeys = jwksFromKeys;
351
+ exports.parseManifestRevocations = parseManifestRevocations;
231
352
  exports.signManifest = signManifest;
232
353
  exports.verifyAgainstJwks = verifyAgainstJwks;
233
354
  exports.verifyManifest = verifyManifest;
355
+ exports.verifyManifestDelivery = verifyManifestDelivery;
234
356
  //# sourceMappingURL=index.cjs.map
235
357
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/canonical.ts","../src/hash.ts","../src/sign.ts","../src/verify.ts","../src/keypair.ts","../src/jwks.ts"],"names":["getSubtle","hexDecode"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { SignableManifest } from './types.js';\n\n/**\n * Canonical JSON of the signed payload — sorted keys, no whitespace.\n *\n * The payload is intentionally NOT the full manifest. It is the minimum that\n * binds a (manifestVersion, contentHash) pair to a tenant under a schema. See\n * ADR-0002.\n */\nexport function canonicalSignedPayload(m: SignableManifest): string {\n return JSON.stringify(\n {\n contentHash: m.contentHash,\n manifestVersion: m.manifestVersion,\n schemaVersion: m.schemaVersion,\n tenantId: m.tenantId,\n },\n null,\n 0,\n );\n}\n\nexport function canonicalSignedBytes(m: SignableManifest): Uint8Array {\n return new TextEncoder().encode(canonicalSignedPayload(m));\n}\n","export async function sha256(data: Uint8Array): Promise<Uint8Array> {\n const subtle = await getSubtle();\n const buf = await subtle.digest('SHA-256', data as BufferSource);\n return new Uint8Array(buf);\n}\n\nexport function hexEncode(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, '0');\n return s;\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/** Universal-runtime Ed25519 signer (Node + Edge + browsers via crypto.subtle). */\nexport interface SignKey {\n /** Raw 32-byte Ed25519 private key as hex. */\n privateKeyHex: string;\n /** Identifier for the key — included in the signature for rotation tracking. */\n keyId: string;\n}\n\nexport async function signManifest(\n manifest: SignableManifest,\n key: SignKey,\n): Promise<ManifestSignature> {\n const subtle = await getSubtle();\n const payload = canonicalSignedBytes(manifest);\n const signedHash = await sha256(payload);\n\n const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));\n const cryptoKey = await subtle.importKey(\n 'pkcs8',\n pkcs8 as BufferSource,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n const sigBuf = await subtle.sign({ name: 'Ed25519' }, cryptoKey, payload as BufferSource);\n return {\n keyId: key.keyId,\n alg: 'ed25519',\n sig: hexEncode(new Uint8Array(sigBuf)),\n signedHash: hexEncode(signedHash),\n };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\n/** Wrap a raw 32-byte Ed25519 seed into a minimal PKCS#8 envelope (RFC 8410). */\nfunction ed25519PrivateKeyToPkcs8(seed: Uint8Array): Uint8Array {\n if (seed.length !== 32) throw new Error('ed25519 seed must be 32 bytes');\n const prefix = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,\n ]);\n const out = new Uint8Array(prefix.length + 32);\n out.set(prefix, 0);\n out.set(seed, prefix.length);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface VerifyOptions {\n /** Hex-encoded raw 32-byte Ed25519 public key. */\n publicKeyHex: string;\n /** Required keyId — caller pins this at SDK init time. */\n expectedKeyId: string;\n}\n\nexport type VerifyResult =\n | { ok: true }\n | { ok: false; reason: 'keyId-mismatch' | 'signedHash-mismatch' | 'sig-invalid' };\n\nexport async function verifyManifest(\n manifest: SignableManifest & { signature: ManifestSignature },\n options: VerifyOptions,\n): Promise<VerifyResult> {\n if (manifest.signature.keyId !== options.expectedKeyId) {\n return { ok: false, reason: 'keyId-mismatch' };\n }\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== manifest.signature.signedHash) {\n return { ok: false, reason: 'signedHash-mismatch' };\n }\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n hexDecode(options.publicKeyHex) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexDecode(manifest.signature.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","/**\n * Ed25519 keypair generation for `fx dev` and tests. Production keys are\n * generated offline and stored in a secrets manager.\n */\nimport { hexEncode } from './hash.js';\n\nexport interface Ed25519Keypair {\n publicKeyHex: string;\n privateKeyHex: string;\n keyId: string;\n}\n\nexport async function generateKeypair(keyId: string): Promise<Ed25519Keypair> {\n const subtle = await getSubtle();\n const kp = (await subtle.generateKey({ name: 'Ed25519' }, true, [\n 'sign',\n 'verify',\n ])) as CryptoKeyPair;\n const rawPub = new Uint8Array(await subtle.exportKey('raw', kp.publicKey));\n const pkcs8Priv = new Uint8Array(await subtle.exportKey('pkcs8', kp.privateKey));\n // PKCS#8 ed25519 = 16-byte prefix + 32-byte seed. Strip the prefix.\n const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);\n return {\n publicKeyHex: hexEncode(rawPub),\n privateKeyHex: hexEncode(seed),\n keyId,\n };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/**\n * JWKS-style key set for a single org. Each entry is an Ed25519 public key\n * with a kid that matches a `manifest_keys.key_id` in the control plane.\n */\nexport interface JwksEntry {\n kid: string;\n kty: 'OKP';\n crv: 'Ed25519';\n /** Base64url-encoded raw 32-byte public key. */\n x: string;\n alg: 'EdDSA';\n use: 'sig';\n /** Lifecycle status — extension on top of the JWKS standard. */\n status: 'active' | 'retiring' | 'retired';\n}\n\nexport interface Jwks {\n keys: JwksEntry[];\n}\n\nexport function jwksFromKeys(\n keys: readonly {\n keyId: string;\n publicKeyHex: string;\n status: 'active' | 'retiring' | 'retired';\n }[],\n): Jwks {\n return {\n keys: keys.map((k) => ({\n kid: k.keyId,\n kty: 'OKP',\n crv: 'Ed25519',\n x: hexToBase64Url(k.publicKeyHex),\n alg: 'EdDSA',\n use: 'sig',\n status: k.status,\n })),\n };\n}\n\nexport interface VerifyAgainstJwksOptions {\n jwks: Jwks;\n /** Optional pinning. If set, rejects keys whose kid is not in this list. */\n expectedKeyIds?: string[];\n /** Reject manifests older than this many milliseconds (replay window). */\n maxAgeMs?: number;\n /** Override clock for tests. */\n now?: () => Date;\n}\n\nexport type JwksVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | 'unknown-key'\n | 'retired-key'\n | 'pinned-key-mismatch'\n | 'signedHash-mismatch'\n | 'sig-invalid'\n | 'replay-window';\n };\n\n/**\n * Verify a signed manifest against a JWKS document. Replaces ADR-0002's\n * single-key verifier when SDKs run in M3+ hosted mode.\n */\nexport async function verifyAgainstJwks(\n manifest: SignableManifest & { signature: ManifestSignature; builtAt?: string },\n options: VerifyAgainstJwksOptions,\n): Promise<JwksVerifyResult> {\n const sig = manifest.signature;\n if (options.expectedKeyIds && !options.expectedKeyIds.includes(sig.keyId)) {\n return { ok: false, reason: 'pinned-key-mismatch' };\n }\n const entry = options.jwks.keys.find((k) => k.kid === sig.keyId);\n if (!entry) return { ok: false, reason: 'unknown-key' };\n if (entry.status === 'retired') return { ok: false, reason: 'retired-key' };\n\n if (options.maxAgeMs && manifest.builtAt) {\n const age =\n (options.now ?? (() => new Date()))().getTime() - new Date(manifest.builtAt).getTime();\n if (age > options.maxAgeMs) return { ok: false, reason: 'replay-window' };\n }\n\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== sig.signedHash) return { ok: false, reason: 'signedHash-mismatch' };\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n base64UrlToBytes(entry.x) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexToBytes(sig.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nfunction hexToBase64Url(hex: string): string {\n return bytesToBase64Url(hexToBytes(hex));\n}\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!);\n return btoa(s).replace(/=+$/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\nfunction base64UrlToBytes(s: string): Uint8Array {\n const pad = '='.repeat((4 - (s.length % 4)) % 4);\n const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n"]}
1
+ {"version":3,"sources":["../src/canonical.ts","../src/subtle.ts","../src/hash.ts","../src/sign.ts","../src/verify.ts","../src/jwks.ts","../src/delivery.ts","../src/keypair.ts"],"names":["hexDecode"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { SignableManifest } from './types.js';\n\n/**\n * Canonical JSON of the signed payload — sorted keys, no whitespace.\n *\n * The payload is intentionally NOT the full manifest. It is the minimum that\n * binds a (manifestVersion, contentHash) pair to a tenant under a schema. See\n * ADR-0002.\n */\nexport function canonicalSignedPayload(m: SignableManifest): string {\n return JSON.stringify(\n {\n contentHash: m.contentHash,\n manifestVersion: m.manifestVersion,\n schemaVersion: m.schemaVersion,\n tenantId: m.tenantId,\n },\n null,\n 0,\n );\n}\n\nexport function canonicalSignedBytes(m: SignableManifest): Uint8Array {\n return new TextEncoder().encode(canonicalSignedPayload(m));\n}\n","/**\n * Resolve WebCrypto without forcing browser bundlers to resolve Node's built-in\n * crypto module. Modern browsers and supported Node releases use the global\n * implementation; the runtime-only import is a compatibility fallback.\n */\nexport async function getSubtle(): Promise<SubtleCrypto> {\n if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;\n const nodeCryptoSpecifier = 'node:crypto';\n const { webcrypto } = await import(nodeCryptoSpecifier);\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { getSubtle } from './subtle.js';\n\nexport async function sha256(data: Uint8Array): Promise<Uint8Array> {\n const subtle = await getSubtle();\n const buf = await subtle.digest('SHA-256', data as BufferSource);\n return new Uint8Array(buf);\n}\n\nexport function hexEncode(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, '0');\n return s;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/** Universal-runtime Ed25519 signer (Node + Edge + browsers via crypto.subtle). */\nexport interface SignKey {\n /** Raw 32-byte Ed25519 private key as hex. */\n privateKeyHex: string;\n /** Identifier for the key — included in the signature for rotation tracking. */\n keyId: string;\n}\n\nexport async function signManifest(\n manifest: SignableManifest,\n key: SignKey,\n): Promise<ManifestSignature> {\n const subtle = await getSubtle();\n const payload = canonicalSignedBytes(manifest);\n const signedHash = await sha256(payload);\n\n const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));\n const cryptoKey = await subtle.importKey(\n 'pkcs8',\n pkcs8 as BufferSource,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n const sigBuf = await subtle.sign({ name: 'Ed25519' }, cryptoKey, payload as BufferSource);\n return {\n keyId: key.keyId,\n alg: 'ed25519',\n sig: hexEncode(new Uint8Array(sigBuf)),\n signedHash: hexEncode(signedHash),\n };\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\n/** Wrap a raw 32-byte Ed25519 seed into a minimal PKCS#8 envelope (RFC 8410). */\nfunction ed25519PrivateKeyToPkcs8(seed: Uint8Array): Uint8Array {\n if (seed.length !== 32) throw new Error('ed25519 seed must be 32 bytes');\n const prefix = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,\n ]);\n const out = new Uint8Array(prefix.length + 32);\n out.set(prefix, 0);\n out.set(seed, prefix.length);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface VerifyOptions {\n /** Hex-encoded raw 32-byte Ed25519 public key. */\n publicKeyHex: string;\n /** Required keyId — caller pins this at SDK init time. */\n expectedKeyId: string;\n}\n\nexport type VerifyResult =\n | { ok: true }\n | { ok: false; reason: 'keyId-mismatch' | 'signedHash-mismatch' | 'sig-invalid' };\n\nexport async function verifyManifest(\n manifest: SignableManifest & { signature: ManifestSignature },\n options: VerifyOptions,\n): Promise<VerifyResult> {\n if (manifest.signature.keyId !== options.expectedKeyId) {\n return { ok: false, reason: 'keyId-mismatch' };\n }\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== manifest.signature.signedHash) {\n return { ok: false, reason: 'signedHash-mismatch' };\n }\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n hexDecode(options.publicKeyHex) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexDecode(manifest.signature.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/**\n * JWKS-style key set for a single org. Each entry is an Ed25519 public key\n * with a kid that matches a `manifest_keys.key_id` in the control plane.\n */\nexport interface JwksEntry {\n kid: string;\n kty: 'OKP';\n crv: 'Ed25519';\n /** Base64url-encoded raw 32-byte public key. */\n x: string;\n alg: 'EdDSA';\n use: 'sig';\n /** Lifecycle status — extension on top of the JWKS standard. */\n status: 'active' | 'retiring' | 'retired';\n}\n\nexport interface Jwks {\n keys: JwksEntry[];\n}\n\nexport function jwksFromKeys(\n keys: readonly {\n keyId: string;\n publicKeyHex: string;\n status: 'active' | 'retiring' | 'retired';\n }[],\n): Jwks {\n return {\n keys: keys.map((k) => ({\n kid: k.keyId,\n kty: 'OKP',\n crv: 'Ed25519',\n x: hexToBase64Url(k.publicKeyHex),\n alg: 'EdDSA',\n use: 'sig',\n status: k.status,\n })),\n };\n}\n\nexport interface VerifyAgainstJwksOptions {\n jwks: Jwks;\n /** Optional pinning. If set, rejects keys whose kid is not in this list. */\n expectedKeyIds?: string[];\n /** Reject manifests older than this many milliseconds (replay window). */\n maxAgeMs?: number;\n /** Override clock for tests. */\n now?: () => Date;\n}\n\nexport type JwksVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | 'unknown-key'\n | 'retired-key'\n | 'pinned-key-mismatch'\n | 'signedHash-mismatch'\n | 'sig-invalid'\n | 'replay-window';\n };\n\n/**\n * Verify a signed manifest against a JWKS document. Replaces ADR-0002's\n * single-key verifier when SDKs run in M3+ hosted mode.\n */\nexport async function verifyAgainstJwks(\n manifest: SignableManifest & { signature: ManifestSignature; builtAt?: string },\n options: VerifyAgainstJwksOptions,\n): Promise<JwksVerifyResult> {\n const sig = manifest.signature;\n if (options.expectedKeyIds && !options.expectedKeyIds.includes(sig.keyId)) {\n return { ok: false, reason: 'pinned-key-mismatch' };\n }\n const entry = options.jwks.keys.find((k) => k.kid === sig.keyId);\n if (!entry) return { ok: false, reason: 'unknown-key' };\n if (entry.status === 'retired') return { ok: false, reason: 'retired-key' };\n\n if (options.maxAgeMs && manifest.builtAt) {\n const age =\n (options.now ?? (() => new Date()))().getTime() - new Date(manifest.builtAt).getTime();\n if (age > options.maxAgeMs) return { ok: false, reason: 'replay-window' };\n }\n\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== sig.signedHash) return { ok: false, reason: 'signedHash-mismatch' };\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n base64UrlToBytes(entry.x) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexToBytes(sig.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nfunction hexToBase64Url(hex: string): string {\n return bytesToBase64Url(hexToBytes(hex));\n}\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!);\n return btoa(s).replace(/=+$/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\nfunction base64UrlToBytes(s: string): Uint8Array {\n const pad = '='.repeat((4 - (s.length % 4)) % 4);\n const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n","import { hexEncode, sha256 } from './hash.js';\nimport { type Jwks, type JwksVerifyResult, verifyAgainstJwks } from './jwks.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface DeliveryManifest extends SignableManifest {\n builtAt: string;\n experiments: Array<Record<string, unknown>>;\n signature: ManifestSignature;\n}\n\nexport interface VerifyManifestDeliveryOptions {\n jwks: Jwks;\n expectedKeyIds?: string[];\n revokedVersions?: readonly number[];\n maxAgeMs?: number;\n now?: () => Date;\n}\n\ntype JwksFailureReason = Extract<JwksVerifyResult, { ok: false }>['reason'];\n\nexport type ManifestDeliveryFailure =\n | 'schema-invalid'\n | 'content-hash-mismatch'\n | 'manifest-revoked'\n | JwksFailureReason;\n\nexport type ManifestDeliveryResult =\n | { ok: true; manifest: DeliveryManifest }\n | { ok: false; reason: ManifestDeliveryFailure; details?: string };\n\n/**\n * Validate and authenticate an untrusted manifest response before an SDK uses it.\n * This function is WebCrypto-only and is shared by browser, edge, and Node SDKs.\n */\nexport async function verifyManifestDelivery(\n input: unknown,\n options: VerifyManifestDeliveryOptions,\n): Promise<ManifestDeliveryResult> {\n const parsed = parseDeliveryManifest(input);\n if (!parsed.ok) return parsed;\n if (options.revokedVersions?.includes(parsed.manifest.manifestVersion)) {\n return { ok: false, reason: 'manifest-revoked' };\n }\n\n const payload = {\n schemaVersion: parsed.manifest.schemaVersion,\n manifestVersion: parsed.manifest.manifestVersion,\n tenantId: parsed.manifest.tenantId,\n experiments: parsed.manifest.experiments,\n };\n const actualHash = hexEncode(await sha256(new TextEncoder().encode(canonicalJson(payload))));\n if (actualHash !== parsed.manifest.contentHash) {\n return { ok: false, reason: 'content-hash-mismatch' };\n }\n\n try {\n const signature = await verifyAgainstJwks(parsed.manifest, options);\n return signature.ok ? { ok: true, manifest: parsed.manifest } : signature;\n } catch (error) {\n return {\n ok: false,\n reason: 'schema-invalid',\n details: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nexport function parseManifestRevocations(input: unknown): number[] {\n if (!isRecord(input) || !Array.isArray(input.versions)) {\n throw new Error('manifest revocations must contain a versions array');\n }\n const versions = input.versions;\n if (!versions.every((version) => Number.isInteger(version) && Number(version) >= 0)) {\n throw new Error('manifest revocation versions must be non-negative integers');\n }\n return [...new Set(versions as number[])];\n}\n\nfunction parseDeliveryManifest(\n input: unknown,\n):\n | { ok: true; manifest: DeliveryManifest }\n | { ok: false; reason: 'schema-invalid'; details: string } {\n if (!isRecord(input)) return invalid('manifest must be an object');\n if (input.schemaVersion !== 1) return invalid('unsupported schemaVersion');\n if (!Number.isInteger(input.manifestVersion) || Number(input.manifestVersion) < 0) {\n return invalid('manifestVersion must be a non-negative integer');\n }\n if (!isHex(input.contentHash, 64)) return invalid('contentHash must be SHA-256 hex');\n if (!isNonEmptyString(input.tenantId)) return invalid('tenantId is required');\n if (!isIsoDate(input.builtAt)) return invalid('builtAt must be an ISO timestamp');\n if (!Array.isArray(input.experiments)) return invalid('experiments must be an array');\n if (!isSignature(input.signature)) return invalid('a valid Ed25519 signature is required');\n\n const experimentIds = new Set<string>();\n for (const [index, experiment] of input.experiments.entries()) {\n const issue = validateExperiment(experiment, experimentIds);\n if (issue) return invalid(`experiments[${index}]: ${issue}`);\n }\n\n return { ok: true, manifest: input as unknown as DeliveryManifest };\n}\n\nfunction validateExperiment(value: unknown, experimentIds: Set<string>): string | null {\n if (!isRecord(value)) return 'must be an object';\n for (const key of ['id', 'name', 'salt'] as const) {\n if (!isNonEmptyString(value[key])) return `${key} is required`;\n }\n if (experimentIds.has(value.id as string)) return `duplicate experiment id ${value.id as string}`;\n experimentIds.add(value.id as string);\n if (value.state !== 'running' && value.state !== 'paused') return 'invalid state';\n if (!isRate(value.sampleRate)) return 'sampleRate must be between 0 and 1';\n if (typeof value.applyVariants !== 'boolean') return 'applyVariants must be boolean';\n if (!Array.isArray(value.variants) || value.variants.length < 2) {\n return 'at least two variants are required';\n }\n\n const variantKeys = new Set<string>();\n let declaredWeight = 0;\n let weightedVariants = 0;\n for (const [index, variant] of value.variants.entries()) {\n if (!isRecord(variant) || !isNonEmptyString(variant.key)) {\n return `variants[${index}].key is required`;\n }\n if (!isNonEmptyString(variant.name)) return `variants[${index}].name is required`;\n if (variantKeys.has(variant.key)) return `duplicate variant key ${variant.key}`;\n variantKeys.add(variant.key);\n if (variant.weight !== undefined) {\n if (\n !Number.isInteger(variant.weight) ||\n Number(variant.weight) < 0 ||\n Number(variant.weight) > 10000\n ) {\n return `variants[${index}].weight must be an integer between 0 and 10000`;\n }\n weightedVariants += 1;\n declaredWeight += Number(variant.weight);\n }\n if (variant.recipeSampleRate !== undefined && !isRate(variant.recipeSampleRate)) {\n return `variants[${index}].recipeSampleRate must be between 0 and 1`;\n }\n }\n if (weightedVariants > 0 && weightedVariants !== value.variants.length) {\n return 'variant weights must be declared for every variant or none';\n }\n if (weightedVariants > 0 && declaredWeight !== 10000) {\n return 'variant weights must sum to 10000';\n }\n for (const key of ['defaultVariantKey', 'divertTo'] as const) {\n if (\n value[key] !== undefined &&\n (!isNonEmptyString(value[key]) || !variantKeys.has(value[key]))\n ) {\n return `${key} must reference an existing variant`;\n }\n }\n return null;\n}\n\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(sortDeep(value));\n}\n\nfunction sortDeep(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortDeep);\n if (isRecord(value)) {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) sorted[key] = sortDeep(value[key]);\n return sorted;\n }\n return value;\n}\n\nfunction invalid(details: string) {\n return { ok: false as const, reason: 'schema-invalid' as const, details };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction isRate(value: unknown): boolean {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;\n}\n\nfunction isHex(value: unknown, length: number): value is string {\n return typeof value === 'string' && new RegExp(`^[0-9a-f]{${length}}$`).test(value);\n}\n\nfunction isIsoDate(value: unknown): value is string {\n return typeof value === 'string' && Number.isFinite(Date.parse(value));\n}\n\nfunction isSignature(value: unknown): value is ManifestSignature {\n return (\n isRecord(value) &&\n isNonEmptyString(value.keyId) &&\n value.alg === 'ed25519' &&\n isHex(value.sig, 128) &&\n isHex(value.signedHash, 64)\n );\n}\n","/**\n * Ed25519 keypair generation for `fx dev` and tests. Production keys are\n * generated offline and stored in a secrets manager.\n */\nimport { hexEncode } from './hash.js';\nimport { getSubtle } from './subtle.js';\n\nexport interface Ed25519Keypair {\n publicKeyHex: string;\n privateKeyHex: string;\n keyId: string;\n}\n\nexport async function generateKeypair(keyId: string): Promise<Ed25519Keypair> {\n const subtle = await getSubtle();\n const kp = (await subtle.generateKey({ name: 'Ed25519' }, true, [\n 'sign',\n 'verify',\n ])) as CryptoKeyPair;\n const rawPub = new Uint8Array(await subtle.exportKey('raw', kp.publicKey));\n const pkcs8Priv = new Uint8Array(await subtle.exportKey('pkcs8', kp.privateKey));\n // PKCS#8 ed25519 = 16-byte prefix + 32-byte seed. Strip the prefix.\n const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);\n return {\n publicKeyHex: hexEncode(rawPub),\n privateKeyHex: hexEncode(seed),\n keyId,\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -58,13 +58,6 @@ declare function verifyManifest(manifest: SignableManifest & {
58
58
  signature: ManifestSignature;
59
59
  }, options: VerifyOptions): Promise<VerifyResult>;
60
60
 
61
- interface Ed25519Keypair {
62
- publicKeyHex: string;
63
- privateKeyHex: string;
64
- keyId: string;
65
- }
66
- declare function generateKeypair(keyId: string): Promise<Ed25519Keypair>;
67
-
68
61
  /**
69
62
  * JWKS-style key set for a single org. Each entry is an Ed25519 public key
70
63
  * with a kid that matches a `manifest_keys.key_id` in the control plane.
@@ -112,4 +105,42 @@ declare function verifyAgainstJwks(manifest: SignableManifest & {
112
105
  builtAt?: string;
113
106
  }, options: VerifyAgainstJwksOptions): Promise<JwksVerifyResult>;
114
107
 
115
- export { type Ed25519Keypair, type Jwks, type JwksEntry, type JwksVerifyResult, type ManifestSignature, type SignKey, type SignableManifest, type SignedManifest, type VerifyAgainstJwksOptions, type VerifyOptions, type VerifyResult, canonicalSignedBytes, canonicalSignedPayload, generateKeypair, jwksFromKeys, signManifest, verifyAgainstJwks, verifyManifest };
108
+ interface DeliveryManifest extends SignableManifest {
109
+ builtAt: string;
110
+ experiments: Array<Record<string, unknown>>;
111
+ signature: ManifestSignature;
112
+ }
113
+ interface VerifyManifestDeliveryOptions {
114
+ jwks: Jwks;
115
+ expectedKeyIds?: string[];
116
+ revokedVersions?: readonly number[];
117
+ maxAgeMs?: number;
118
+ now?: () => Date;
119
+ }
120
+ type JwksFailureReason = Extract<JwksVerifyResult, {
121
+ ok: false;
122
+ }>['reason'];
123
+ type ManifestDeliveryFailure = 'schema-invalid' | 'content-hash-mismatch' | 'manifest-revoked' | JwksFailureReason;
124
+ type ManifestDeliveryResult = {
125
+ ok: true;
126
+ manifest: DeliveryManifest;
127
+ } | {
128
+ ok: false;
129
+ reason: ManifestDeliveryFailure;
130
+ details?: string;
131
+ };
132
+ /**
133
+ * Validate and authenticate an untrusted manifest response before an SDK uses it.
134
+ * This function is WebCrypto-only and is shared by browser, edge, and Node SDKs.
135
+ */
136
+ declare function verifyManifestDelivery(input: unknown, options: VerifyManifestDeliveryOptions): Promise<ManifestDeliveryResult>;
137
+ declare function parseManifestRevocations(input: unknown): number[];
138
+
139
+ interface Ed25519Keypair {
140
+ publicKeyHex: string;
141
+ privateKeyHex: string;
142
+ keyId: string;
143
+ }
144
+ declare function generateKeypair(keyId: string): Promise<Ed25519Keypair>;
145
+
146
+ export { type DeliveryManifest, type Ed25519Keypair, type Jwks, type JwksEntry, type JwksVerifyResult, type ManifestDeliveryFailure, type ManifestDeliveryResult, type ManifestSignature, type SignKey, type SignableManifest, type SignedManifest, type VerifyAgainstJwksOptions, type VerifyManifestDeliveryOptions, type VerifyOptions, type VerifyResult, canonicalSignedBytes, canonicalSignedPayload, generateKeypair, jwksFromKeys, parseManifestRevocations, signManifest, verifyAgainstJwks, verifyManifest, verifyManifestDelivery };
package/dist/index.d.ts CHANGED
@@ -58,13 +58,6 @@ declare function verifyManifest(manifest: SignableManifest & {
58
58
  signature: ManifestSignature;
59
59
  }, options: VerifyOptions): Promise<VerifyResult>;
60
60
 
61
- interface Ed25519Keypair {
62
- publicKeyHex: string;
63
- privateKeyHex: string;
64
- keyId: string;
65
- }
66
- declare function generateKeypair(keyId: string): Promise<Ed25519Keypair>;
67
-
68
61
  /**
69
62
  * JWKS-style key set for a single org. Each entry is an Ed25519 public key
70
63
  * with a kid that matches a `manifest_keys.key_id` in the control plane.
@@ -112,4 +105,42 @@ declare function verifyAgainstJwks(manifest: SignableManifest & {
112
105
  builtAt?: string;
113
106
  }, options: VerifyAgainstJwksOptions): Promise<JwksVerifyResult>;
114
107
 
115
- export { type Ed25519Keypair, type Jwks, type JwksEntry, type JwksVerifyResult, type ManifestSignature, type SignKey, type SignableManifest, type SignedManifest, type VerifyAgainstJwksOptions, type VerifyOptions, type VerifyResult, canonicalSignedBytes, canonicalSignedPayload, generateKeypair, jwksFromKeys, signManifest, verifyAgainstJwks, verifyManifest };
108
+ interface DeliveryManifest extends SignableManifest {
109
+ builtAt: string;
110
+ experiments: Array<Record<string, unknown>>;
111
+ signature: ManifestSignature;
112
+ }
113
+ interface VerifyManifestDeliveryOptions {
114
+ jwks: Jwks;
115
+ expectedKeyIds?: string[];
116
+ revokedVersions?: readonly number[];
117
+ maxAgeMs?: number;
118
+ now?: () => Date;
119
+ }
120
+ type JwksFailureReason = Extract<JwksVerifyResult, {
121
+ ok: false;
122
+ }>['reason'];
123
+ type ManifestDeliveryFailure = 'schema-invalid' | 'content-hash-mismatch' | 'manifest-revoked' | JwksFailureReason;
124
+ type ManifestDeliveryResult = {
125
+ ok: true;
126
+ manifest: DeliveryManifest;
127
+ } | {
128
+ ok: false;
129
+ reason: ManifestDeliveryFailure;
130
+ details?: string;
131
+ };
132
+ /**
133
+ * Validate and authenticate an untrusted manifest response before an SDK uses it.
134
+ * This function is WebCrypto-only and is shared by browser, edge, and Node SDKs.
135
+ */
136
+ declare function verifyManifestDelivery(input: unknown, options: VerifyManifestDeliveryOptions): Promise<ManifestDeliveryResult>;
137
+ declare function parseManifestRevocations(input: unknown): number[];
138
+
139
+ interface Ed25519Keypair {
140
+ publicKeyHex: string;
141
+ privateKeyHex: string;
142
+ keyId: string;
143
+ }
144
+ declare function generateKeypair(keyId: string): Promise<Ed25519Keypair>;
145
+
146
+ export { type DeliveryManifest, type Ed25519Keypair, type Jwks, type JwksEntry, type JwksVerifyResult, type ManifestDeliveryFailure, type ManifestDeliveryResult, type ManifestSignature, type SignKey, type SignableManifest, type SignedManifest, type VerifyAgainstJwksOptions, type VerifyManifestDeliveryOptions, type VerifyOptions, type VerifyResult, canonicalSignedBytes, canonicalSignedPayload, generateKeypair, jwksFromKeys, parseManifestRevocations, signManifest, verifyAgainstJwks, verifyManifest, verifyManifestDelivery };
package/dist/index.js CHANGED
@@ -15,6 +15,14 @@ function canonicalSignedBytes(m) {
15
15
  return new TextEncoder().encode(canonicalSignedPayload(m));
16
16
  }
17
17
 
18
+ // src/subtle.ts
19
+ async function getSubtle() {
20
+ if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;
21
+ const nodeCryptoSpecifier = "node:crypto";
22
+ const { webcrypto } = await import(nodeCryptoSpecifier);
23
+ return webcrypto.subtle;
24
+ }
25
+
18
26
  // src/hash.ts
19
27
  async function sha256(data) {
20
28
  const subtle = await getSubtle();
@@ -26,15 +34,10 @@ function hexEncode(bytes) {
26
34
  for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, "0");
27
35
  return s;
28
36
  }
29
- async function getSubtle() {
30
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
31
- const { webcrypto } = await import('crypto');
32
- return webcrypto.subtle;
33
- }
34
37
 
35
38
  // src/sign.ts
36
39
  async function signManifest(manifest, key) {
37
- const subtle = await getSubtle2();
40
+ const subtle = await getSubtle();
38
41
  const payload = canonicalSignedBytes(manifest);
39
42
  const signedHash = await sha256(payload);
40
43
  const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));
@@ -53,11 +56,6 @@ async function signManifest(manifest, key) {
53
56
  signedHash: hexEncode(signedHash)
54
57
  };
55
58
  }
56
- async function getSubtle2() {
57
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
58
- const { webcrypto } = await import('crypto');
59
- return webcrypto.subtle;
60
- }
61
59
  function hexDecode(hex) {
62
60
  if (hex.length % 2 !== 0) throw new Error("hexDecode: odd length");
63
61
  const out = new Uint8Array(hex.length / 2);
@@ -100,7 +98,7 @@ async function verifyManifest(manifest, options) {
100
98
  if (expectedHash !== manifest.signature.signedHash) {
101
99
  return { ok: false, reason: "signedHash-mismatch" };
102
100
  }
103
- const subtle = await getSubtle3();
101
+ const subtle = await getSubtle();
104
102
  const pubKey = await subtle.importKey(
105
103
  "raw",
106
104
  hexDecode2(options.publicKeyHex),
@@ -116,11 +114,6 @@ async function verifyManifest(manifest, options) {
116
114
  );
117
115
  return valid ? { ok: true } : { ok: false, reason: "sig-invalid" };
118
116
  }
119
- async function getSubtle3() {
120
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
121
- const { webcrypto } = await import('crypto');
122
- return webcrypto.subtle;
123
- }
124
117
  function hexDecode2(hex) {
125
118
  if (hex.length % 2 !== 0) throw new Error("hexDecode: odd length");
126
119
  const out = new Uint8Array(hex.length / 2);
@@ -128,28 +121,6 @@ function hexDecode2(hex) {
128
121
  return out;
129
122
  }
130
123
 
131
- // src/keypair.ts
132
- async function generateKeypair(keyId) {
133
- const subtle = await getSubtle4();
134
- const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
135
- "sign",
136
- "verify"
137
- ]);
138
- const rawPub = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
139
- const pkcs8Priv = new Uint8Array(await subtle.exportKey("pkcs8", kp.privateKey));
140
- const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);
141
- return {
142
- publicKeyHex: hexEncode(rawPub),
143
- privateKeyHex: hexEncode(seed),
144
- keyId
145
- };
146
- }
147
- async function getSubtle4() {
148
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
149
- const { webcrypto } = await import('crypto');
150
- return webcrypto.subtle;
151
- }
152
-
153
124
  // src/jwks.ts
154
125
  function jwksFromKeys(keys) {
155
126
  return {
@@ -179,7 +150,7 @@ async function verifyAgainstJwks(manifest, options) {
179
150
  const payload = canonicalSignedBytes(manifest);
180
151
  const expectedHash = hexEncode(await sha256(payload));
181
152
  if (expectedHash !== sig.signedHash) return { ok: false, reason: "signedHash-mismatch" };
182
- const subtle = await getSubtle5();
153
+ const subtle = await getSubtle();
183
154
  const pubKey = await subtle.importKey(
184
155
  "raw",
185
156
  base64UrlToBytes(entry.x),
@@ -195,11 +166,6 @@ async function verifyAgainstJwks(manifest, options) {
195
166
  );
196
167
  return valid ? { ok: true } : { ok: false, reason: "sig-invalid" };
197
168
  }
198
- async function getSubtle5() {
199
- if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
200
- const { webcrypto } = await import('crypto');
201
- return webcrypto.subtle;
202
- }
203
169
  function hexToBytes(hex) {
204
170
  const out = new Uint8Array(hex.length / 2);
205
171
  for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
@@ -222,6 +188,160 @@ function base64UrlToBytes(s) {
222
188
  return out;
223
189
  }
224
190
 
225
- export { canonicalSignedBytes, canonicalSignedPayload, generateKeypair, jwksFromKeys, signManifest, verifyAgainstJwks, verifyManifest };
191
+ // src/delivery.ts
192
+ async function verifyManifestDelivery(input, options) {
193
+ const parsed = parseDeliveryManifest(input);
194
+ if (!parsed.ok) return parsed;
195
+ if (options.revokedVersions?.includes(parsed.manifest.manifestVersion)) {
196
+ return { ok: false, reason: "manifest-revoked" };
197
+ }
198
+ const payload = {
199
+ schemaVersion: parsed.manifest.schemaVersion,
200
+ manifestVersion: parsed.manifest.manifestVersion,
201
+ tenantId: parsed.manifest.tenantId,
202
+ experiments: parsed.manifest.experiments
203
+ };
204
+ const actualHash = hexEncode(await sha256(new TextEncoder().encode(canonicalJson(payload))));
205
+ if (actualHash !== parsed.manifest.contentHash) {
206
+ return { ok: false, reason: "content-hash-mismatch" };
207
+ }
208
+ try {
209
+ const signature = await verifyAgainstJwks(parsed.manifest, options);
210
+ return signature.ok ? { ok: true, manifest: parsed.manifest } : signature;
211
+ } catch (error) {
212
+ return {
213
+ ok: false,
214
+ reason: "schema-invalid",
215
+ details: error instanceof Error ? error.message : String(error)
216
+ };
217
+ }
218
+ }
219
+ function parseManifestRevocations(input) {
220
+ if (!isRecord(input) || !Array.isArray(input.versions)) {
221
+ throw new Error("manifest revocations must contain a versions array");
222
+ }
223
+ const versions = input.versions;
224
+ if (!versions.every((version) => Number.isInteger(version) && Number(version) >= 0)) {
225
+ throw new Error("manifest revocation versions must be non-negative integers");
226
+ }
227
+ return [...new Set(versions)];
228
+ }
229
+ function parseDeliveryManifest(input) {
230
+ if (!isRecord(input)) return invalid("manifest must be an object");
231
+ if (input.schemaVersion !== 1) return invalid("unsupported schemaVersion");
232
+ if (!Number.isInteger(input.manifestVersion) || Number(input.manifestVersion) < 0) {
233
+ return invalid("manifestVersion must be a non-negative integer");
234
+ }
235
+ if (!isHex(input.contentHash, 64)) return invalid("contentHash must be SHA-256 hex");
236
+ if (!isNonEmptyString(input.tenantId)) return invalid("tenantId is required");
237
+ if (!isIsoDate(input.builtAt)) return invalid("builtAt must be an ISO timestamp");
238
+ if (!Array.isArray(input.experiments)) return invalid("experiments must be an array");
239
+ if (!isSignature(input.signature)) return invalid("a valid Ed25519 signature is required");
240
+ const experimentIds = /* @__PURE__ */ new Set();
241
+ for (const [index, experiment] of input.experiments.entries()) {
242
+ const issue = validateExperiment(experiment, experimentIds);
243
+ if (issue) return invalid(`experiments[${index}]: ${issue}`);
244
+ }
245
+ return { ok: true, manifest: input };
246
+ }
247
+ function validateExperiment(value, experimentIds) {
248
+ if (!isRecord(value)) return "must be an object";
249
+ for (const key of ["id", "name", "salt"]) {
250
+ if (!isNonEmptyString(value[key])) return `${key} is required`;
251
+ }
252
+ if (experimentIds.has(value.id)) return `duplicate experiment id ${value.id}`;
253
+ experimentIds.add(value.id);
254
+ if (value.state !== "running" && value.state !== "paused") return "invalid state";
255
+ if (!isRate(value.sampleRate)) return "sampleRate must be between 0 and 1";
256
+ if (typeof value.applyVariants !== "boolean") return "applyVariants must be boolean";
257
+ if (!Array.isArray(value.variants) || value.variants.length < 2) {
258
+ return "at least two variants are required";
259
+ }
260
+ const variantKeys = /* @__PURE__ */ new Set();
261
+ let declaredWeight = 0;
262
+ let weightedVariants = 0;
263
+ for (const [index, variant] of value.variants.entries()) {
264
+ if (!isRecord(variant) || !isNonEmptyString(variant.key)) {
265
+ return `variants[${index}].key is required`;
266
+ }
267
+ if (!isNonEmptyString(variant.name)) return `variants[${index}].name is required`;
268
+ if (variantKeys.has(variant.key)) return `duplicate variant key ${variant.key}`;
269
+ variantKeys.add(variant.key);
270
+ if (variant.weight !== void 0) {
271
+ if (!Number.isInteger(variant.weight) || Number(variant.weight) < 0 || Number(variant.weight) > 1e4) {
272
+ return `variants[${index}].weight must be an integer between 0 and 10000`;
273
+ }
274
+ weightedVariants += 1;
275
+ declaredWeight += Number(variant.weight);
276
+ }
277
+ if (variant.recipeSampleRate !== void 0 && !isRate(variant.recipeSampleRate)) {
278
+ return `variants[${index}].recipeSampleRate must be between 0 and 1`;
279
+ }
280
+ }
281
+ if (weightedVariants > 0 && weightedVariants !== value.variants.length) {
282
+ return "variant weights must be declared for every variant or none";
283
+ }
284
+ if (weightedVariants > 0 && declaredWeight !== 1e4) {
285
+ return "variant weights must sum to 10000";
286
+ }
287
+ for (const key of ["defaultVariantKey", "divertTo"]) {
288
+ if (value[key] !== void 0 && (!isNonEmptyString(value[key]) || !variantKeys.has(value[key]))) {
289
+ return `${key} must reference an existing variant`;
290
+ }
291
+ }
292
+ return null;
293
+ }
294
+ function canonicalJson(value) {
295
+ return JSON.stringify(sortDeep(value));
296
+ }
297
+ function sortDeep(value) {
298
+ if (Array.isArray(value)) return value.map(sortDeep);
299
+ if (isRecord(value)) {
300
+ const sorted = {};
301
+ for (const key of Object.keys(value).sort()) sorted[key] = sortDeep(value[key]);
302
+ return sorted;
303
+ }
304
+ return value;
305
+ }
306
+ function invalid(details) {
307
+ return { ok: false, reason: "schema-invalid", details };
308
+ }
309
+ function isRecord(value) {
310
+ return value !== null && typeof value === "object" && !Array.isArray(value);
311
+ }
312
+ function isNonEmptyString(value) {
313
+ return typeof value === "string" && value.length > 0;
314
+ }
315
+ function isRate(value) {
316
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
317
+ }
318
+ function isHex(value, length) {
319
+ return typeof value === "string" && new RegExp(`^[0-9a-f]{${length}}$`).test(value);
320
+ }
321
+ function isIsoDate(value) {
322
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
323
+ }
324
+ function isSignature(value) {
325
+ return isRecord(value) && isNonEmptyString(value.keyId) && value.alg === "ed25519" && isHex(value.sig, 128) && isHex(value.signedHash, 64);
326
+ }
327
+
328
+ // src/keypair.ts
329
+ async function generateKeypair(keyId) {
330
+ const subtle = await getSubtle();
331
+ const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
332
+ "sign",
333
+ "verify"
334
+ ]);
335
+ const rawPub = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
336
+ const pkcs8Priv = new Uint8Array(await subtle.exportKey("pkcs8", kp.privateKey));
337
+ const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);
338
+ return {
339
+ publicKeyHex: hexEncode(rawPub),
340
+ privateKeyHex: hexEncode(seed),
341
+ keyId
342
+ };
343
+ }
344
+
345
+ export { canonicalSignedBytes, canonicalSignedPayload, generateKeypair, jwksFromKeys, parseManifestRevocations, signManifest, verifyAgainstJwks, verifyManifest, verifyManifestDelivery };
226
346
  //# sourceMappingURL=index.js.map
227
347
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/canonical.ts","../src/hash.ts","../src/sign.ts","../src/verify.ts","../src/keypair.ts","../src/jwks.ts"],"names":["getSubtle","hexDecode"],"mappings":"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","file":"index.js","sourcesContent":["import type { SignableManifest } from './types.js';\n\n/**\n * Canonical JSON of the signed payload — sorted keys, no whitespace.\n *\n * The payload is intentionally NOT the full manifest. It is the minimum that\n * binds a (manifestVersion, contentHash) pair to a tenant under a schema. See\n * ADR-0002.\n */\nexport function canonicalSignedPayload(m: SignableManifest): string {\n return JSON.stringify(\n {\n contentHash: m.contentHash,\n manifestVersion: m.manifestVersion,\n schemaVersion: m.schemaVersion,\n tenantId: m.tenantId,\n },\n null,\n 0,\n );\n}\n\nexport function canonicalSignedBytes(m: SignableManifest): Uint8Array {\n return new TextEncoder().encode(canonicalSignedPayload(m));\n}\n","export async function sha256(data: Uint8Array): Promise<Uint8Array> {\n const subtle = await getSubtle();\n const buf = await subtle.digest('SHA-256', data as BufferSource);\n return new Uint8Array(buf);\n}\n\nexport function hexEncode(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, '0');\n return s;\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/** Universal-runtime Ed25519 signer (Node + Edge + browsers via crypto.subtle). */\nexport interface SignKey {\n /** Raw 32-byte Ed25519 private key as hex. */\n privateKeyHex: string;\n /** Identifier for the key — included in the signature for rotation tracking. */\n keyId: string;\n}\n\nexport async function signManifest(\n manifest: SignableManifest,\n key: SignKey,\n): Promise<ManifestSignature> {\n const subtle = await getSubtle();\n const payload = canonicalSignedBytes(manifest);\n const signedHash = await sha256(payload);\n\n const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));\n const cryptoKey = await subtle.importKey(\n 'pkcs8',\n pkcs8 as BufferSource,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n const sigBuf = await subtle.sign({ name: 'Ed25519' }, cryptoKey, payload as BufferSource);\n return {\n keyId: key.keyId,\n alg: 'ed25519',\n sig: hexEncode(new Uint8Array(sigBuf)),\n signedHash: hexEncode(signedHash),\n };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\n/** Wrap a raw 32-byte Ed25519 seed into a minimal PKCS#8 envelope (RFC 8410). */\nfunction ed25519PrivateKeyToPkcs8(seed: Uint8Array): Uint8Array {\n if (seed.length !== 32) throw new Error('ed25519 seed must be 32 bytes');\n const prefix = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,\n ]);\n const out = new Uint8Array(prefix.length + 32);\n out.set(prefix, 0);\n out.set(seed, prefix.length);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface VerifyOptions {\n /** Hex-encoded raw 32-byte Ed25519 public key. */\n publicKeyHex: string;\n /** Required keyId — caller pins this at SDK init time. */\n expectedKeyId: string;\n}\n\nexport type VerifyResult =\n | { ok: true }\n | { ok: false; reason: 'keyId-mismatch' | 'signedHash-mismatch' | 'sig-invalid' };\n\nexport async function verifyManifest(\n manifest: SignableManifest & { signature: ManifestSignature },\n options: VerifyOptions,\n): Promise<VerifyResult> {\n if (manifest.signature.keyId !== options.expectedKeyId) {\n return { ok: false, reason: 'keyId-mismatch' };\n }\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== manifest.signature.signedHash) {\n return { ok: false, reason: 'signedHash-mismatch' };\n }\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n hexDecode(options.publicKeyHex) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexDecode(manifest.signature.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","/**\n * Ed25519 keypair generation for `fx dev` and tests. Production keys are\n * generated offline and stored in a secrets manager.\n */\nimport { hexEncode } from './hash.js';\n\nexport interface Ed25519Keypair {\n publicKeyHex: string;\n privateKeyHex: string;\n keyId: string;\n}\n\nexport async function generateKeypair(keyId: string): Promise<Ed25519Keypair> {\n const subtle = await getSubtle();\n const kp = (await subtle.generateKey({ name: 'Ed25519' }, true, [\n 'sign',\n 'verify',\n ])) as CryptoKeyPair;\n const rawPub = new Uint8Array(await subtle.exportKey('raw', kp.publicKey));\n const pkcs8Priv = new Uint8Array(await subtle.exportKey('pkcs8', kp.privateKey));\n // PKCS#8 ed25519 = 16-byte prefix + 32-byte seed. Strip the prefix.\n const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);\n return {\n publicKeyHex: hexEncode(rawPub),\n privateKeyHex: hexEncode(seed),\n keyId,\n };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/**\n * JWKS-style key set for a single org. Each entry is an Ed25519 public key\n * with a kid that matches a `manifest_keys.key_id` in the control plane.\n */\nexport interface JwksEntry {\n kid: string;\n kty: 'OKP';\n crv: 'Ed25519';\n /** Base64url-encoded raw 32-byte public key. */\n x: string;\n alg: 'EdDSA';\n use: 'sig';\n /** Lifecycle status — extension on top of the JWKS standard. */\n status: 'active' | 'retiring' | 'retired';\n}\n\nexport interface Jwks {\n keys: JwksEntry[];\n}\n\nexport function jwksFromKeys(\n keys: readonly {\n keyId: string;\n publicKeyHex: string;\n status: 'active' | 'retiring' | 'retired';\n }[],\n): Jwks {\n return {\n keys: keys.map((k) => ({\n kid: k.keyId,\n kty: 'OKP',\n crv: 'Ed25519',\n x: hexToBase64Url(k.publicKeyHex),\n alg: 'EdDSA',\n use: 'sig',\n status: k.status,\n })),\n };\n}\n\nexport interface VerifyAgainstJwksOptions {\n jwks: Jwks;\n /** Optional pinning. If set, rejects keys whose kid is not in this list. */\n expectedKeyIds?: string[];\n /** Reject manifests older than this many milliseconds (replay window). */\n maxAgeMs?: number;\n /** Override clock for tests. */\n now?: () => Date;\n}\n\nexport type JwksVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | 'unknown-key'\n | 'retired-key'\n | 'pinned-key-mismatch'\n | 'signedHash-mismatch'\n | 'sig-invalid'\n | 'replay-window';\n };\n\n/**\n * Verify a signed manifest against a JWKS document. Replaces ADR-0002's\n * single-key verifier when SDKs run in M3+ hosted mode.\n */\nexport async function verifyAgainstJwks(\n manifest: SignableManifest & { signature: ManifestSignature; builtAt?: string },\n options: VerifyAgainstJwksOptions,\n): Promise<JwksVerifyResult> {\n const sig = manifest.signature;\n if (options.expectedKeyIds && !options.expectedKeyIds.includes(sig.keyId)) {\n return { ok: false, reason: 'pinned-key-mismatch' };\n }\n const entry = options.jwks.keys.find((k) => k.kid === sig.keyId);\n if (!entry) return { ok: false, reason: 'unknown-key' };\n if (entry.status === 'retired') return { ok: false, reason: 'retired-key' };\n\n if (options.maxAgeMs && manifest.builtAt) {\n const age =\n (options.now ?? (() => new Date()))().getTime() - new Date(manifest.builtAt).getTime();\n if (age > options.maxAgeMs) return { ok: false, reason: 'replay-window' };\n }\n\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== sig.signedHash) return { ok: false, reason: 'signedHash-mismatch' };\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n base64UrlToBytes(entry.x) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexToBytes(sig.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nfunction hexToBase64Url(hex: string): string {\n return bytesToBase64Url(hexToBytes(hex));\n}\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!);\n return btoa(s).replace(/=+$/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\nfunction base64UrlToBytes(s: string): Uint8Array {\n const pad = '='.repeat((4 - (s.length % 4)) % 4);\n const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n"]}
1
+ {"version":3,"sources":["../src/canonical.ts","../src/subtle.ts","../src/hash.ts","../src/sign.ts","../src/verify.ts","../src/jwks.ts","../src/delivery.ts","../src/keypair.ts"],"names":["hexDecode"],"mappings":"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","file":"index.js","sourcesContent":["import type { SignableManifest } from './types.js';\n\n/**\n * Canonical JSON of the signed payload — sorted keys, no whitespace.\n *\n * The payload is intentionally NOT the full manifest. It is the minimum that\n * binds a (manifestVersion, contentHash) pair to a tenant under a schema. See\n * ADR-0002.\n */\nexport function canonicalSignedPayload(m: SignableManifest): string {\n return JSON.stringify(\n {\n contentHash: m.contentHash,\n manifestVersion: m.manifestVersion,\n schemaVersion: m.schemaVersion,\n tenantId: m.tenantId,\n },\n null,\n 0,\n );\n}\n\nexport function canonicalSignedBytes(m: SignableManifest): Uint8Array {\n return new TextEncoder().encode(canonicalSignedPayload(m));\n}\n","/**\n * Resolve WebCrypto without forcing browser bundlers to resolve Node's built-in\n * crypto module. Modern browsers and supported Node releases use the global\n * implementation; the runtime-only import is a compatibility fallback.\n */\nexport async function getSubtle(): Promise<SubtleCrypto> {\n if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;\n const nodeCryptoSpecifier = 'node:crypto';\n const { webcrypto } = await import(nodeCryptoSpecifier);\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { getSubtle } from './subtle.js';\n\nexport async function sha256(data: Uint8Array): Promise<Uint8Array> {\n const subtle = await getSubtle();\n const buf = await subtle.digest('SHA-256', data as BufferSource);\n return new Uint8Array(buf);\n}\n\nexport function hexEncode(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, '0');\n return s;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/** Universal-runtime Ed25519 signer (Node + Edge + browsers via crypto.subtle). */\nexport interface SignKey {\n /** Raw 32-byte Ed25519 private key as hex. */\n privateKeyHex: string;\n /** Identifier for the key — included in the signature for rotation tracking. */\n keyId: string;\n}\n\nexport async function signManifest(\n manifest: SignableManifest,\n key: SignKey,\n): Promise<ManifestSignature> {\n const subtle = await getSubtle();\n const payload = canonicalSignedBytes(manifest);\n const signedHash = await sha256(payload);\n\n const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));\n const cryptoKey = await subtle.importKey(\n 'pkcs8',\n pkcs8 as BufferSource,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n const sigBuf = await subtle.sign({ name: 'Ed25519' }, cryptoKey, payload as BufferSource);\n return {\n keyId: key.keyId,\n alg: 'ed25519',\n sig: hexEncode(new Uint8Array(sigBuf)),\n signedHash: hexEncode(signedHash),\n };\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\n/** Wrap a raw 32-byte Ed25519 seed into a minimal PKCS#8 envelope (RFC 8410). */\nfunction ed25519PrivateKeyToPkcs8(seed: Uint8Array): Uint8Array {\n if (seed.length !== 32) throw new Error('ed25519 seed must be 32 bytes');\n const prefix = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,\n ]);\n const out = new Uint8Array(prefix.length + 32);\n out.set(prefix, 0);\n out.set(seed, prefix.length);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface VerifyOptions {\n /** Hex-encoded raw 32-byte Ed25519 public key. */\n publicKeyHex: string;\n /** Required keyId — caller pins this at SDK init time. */\n expectedKeyId: string;\n}\n\nexport type VerifyResult =\n | { ok: true }\n | { ok: false; reason: 'keyId-mismatch' | 'signedHash-mismatch' | 'sig-invalid' };\n\nexport async function verifyManifest(\n manifest: SignableManifest & { signature: ManifestSignature },\n options: VerifyOptions,\n): Promise<VerifyResult> {\n if (manifest.signature.keyId !== options.expectedKeyId) {\n return { ok: false, reason: 'keyId-mismatch' };\n }\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== manifest.signature.signedHash) {\n return { ok: false, reason: 'signedHash-mismatch' };\n }\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n hexDecode(options.publicKeyHex) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexDecode(manifest.signature.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/**\n * JWKS-style key set for a single org. Each entry is an Ed25519 public key\n * with a kid that matches a `manifest_keys.key_id` in the control plane.\n */\nexport interface JwksEntry {\n kid: string;\n kty: 'OKP';\n crv: 'Ed25519';\n /** Base64url-encoded raw 32-byte public key. */\n x: string;\n alg: 'EdDSA';\n use: 'sig';\n /** Lifecycle status — extension on top of the JWKS standard. */\n status: 'active' | 'retiring' | 'retired';\n}\n\nexport interface Jwks {\n keys: JwksEntry[];\n}\n\nexport function jwksFromKeys(\n keys: readonly {\n keyId: string;\n publicKeyHex: string;\n status: 'active' | 'retiring' | 'retired';\n }[],\n): Jwks {\n return {\n keys: keys.map((k) => ({\n kid: k.keyId,\n kty: 'OKP',\n crv: 'Ed25519',\n x: hexToBase64Url(k.publicKeyHex),\n alg: 'EdDSA',\n use: 'sig',\n status: k.status,\n })),\n };\n}\n\nexport interface VerifyAgainstJwksOptions {\n jwks: Jwks;\n /** Optional pinning. If set, rejects keys whose kid is not in this list. */\n expectedKeyIds?: string[];\n /** Reject manifests older than this many milliseconds (replay window). */\n maxAgeMs?: number;\n /** Override clock for tests. */\n now?: () => Date;\n}\n\nexport type JwksVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | 'unknown-key'\n | 'retired-key'\n | 'pinned-key-mismatch'\n | 'signedHash-mismatch'\n | 'sig-invalid'\n | 'replay-window';\n };\n\n/**\n * Verify a signed manifest against a JWKS document. Replaces ADR-0002's\n * single-key verifier when SDKs run in M3+ hosted mode.\n */\nexport async function verifyAgainstJwks(\n manifest: SignableManifest & { signature: ManifestSignature; builtAt?: string },\n options: VerifyAgainstJwksOptions,\n): Promise<JwksVerifyResult> {\n const sig = manifest.signature;\n if (options.expectedKeyIds && !options.expectedKeyIds.includes(sig.keyId)) {\n return { ok: false, reason: 'pinned-key-mismatch' };\n }\n const entry = options.jwks.keys.find((k) => k.kid === sig.keyId);\n if (!entry) return { ok: false, reason: 'unknown-key' };\n if (entry.status === 'retired') return { ok: false, reason: 'retired-key' };\n\n if (options.maxAgeMs && manifest.builtAt) {\n const age =\n (options.now ?? (() => new Date()))().getTime() - new Date(manifest.builtAt).getTime();\n if (age > options.maxAgeMs) return { ok: false, reason: 'replay-window' };\n }\n\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== sig.signedHash) return { ok: false, reason: 'signedHash-mismatch' };\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n base64UrlToBytes(entry.x) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexToBytes(sig.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nfunction hexToBase64Url(hex: string): string {\n return bytesToBase64Url(hexToBytes(hex));\n}\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!);\n return btoa(s).replace(/=+$/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\nfunction base64UrlToBytes(s: string): Uint8Array {\n const pad = '='.repeat((4 - (s.length % 4)) % 4);\n const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n","import { hexEncode, sha256 } from './hash.js';\nimport { type Jwks, type JwksVerifyResult, verifyAgainstJwks } from './jwks.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface DeliveryManifest extends SignableManifest {\n builtAt: string;\n experiments: Array<Record<string, unknown>>;\n signature: ManifestSignature;\n}\n\nexport interface VerifyManifestDeliveryOptions {\n jwks: Jwks;\n expectedKeyIds?: string[];\n revokedVersions?: readonly number[];\n maxAgeMs?: number;\n now?: () => Date;\n}\n\ntype JwksFailureReason = Extract<JwksVerifyResult, { ok: false }>['reason'];\n\nexport type ManifestDeliveryFailure =\n | 'schema-invalid'\n | 'content-hash-mismatch'\n | 'manifest-revoked'\n | JwksFailureReason;\n\nexport type ManifestDeliveryResult =\n | { ok: true; manifest: DeliveryManifest }\n | { ok: false; reason: ManifestDeliveryFailure; details?: string };\n\n/**\n * Validate and authenticate an untrusted manifest response before an SDK uses it.\n * This function is WebCrypto-only and is shared by browser, edge, and Node SDKs.\n */\nexport async function verifyManifestDelivery(\n input: unknown,\n options: VerifyManifestDeliveryOptions,\n): Promise<ManifestDeliveryResult> {\n const parsed = parseDeliveryManifest(input);\n if (!parsed.ok) return parsed;\n if (options.revokedVersions?.includes(parsed.manifest.manifestVersion)) {\n return { ok: false, reason: 'manifest-revoked' };\n }\n\n const payload = {\n schemaVersion: parsed.manifest.schemaVersion,\n manifestVersion: parsed.manifest.manifestVersion,\n tenantId: parsed.manifest.tenantId,\n experiments: parsed.manifest.experiments,\n };\n const actualHash = hexEncode(await sha256(new TextEncoder().encode(canonicalJson(payload))));\n if (actualHash !== parsed.manifest.contentHash) {\n return { ok: false, reason: 'content-hash-mismatch' };\n }\n\n try {\n const signature = await verifyAgainstJwks(parsed.manifest, options);\n return signature.ok ? { ok: true, manifest: parsed.manifest } : signature;\n } catch (error) {\n return {\n ok: false,\n reason: 'schema-invalid',\n details: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nexport function parseManifestRevocations(input: unknown): number[] {\n if (!isRecord(input) || !Array.isArray(input.versions)) {\n throw new Error('manifest revocations must contain a versions array');\n }\n const versions = input.versions;\n if (!versions.every((version) => Number.isInteger(version) && Number(version) >= 0)) {\n throw new Error('manifest revocation versions must be non-negative integers');\n }\n return [...new Set(versions as number[])];\n}\n\nfunction parseDeliveryManifest(\n input: unknown,\n):\n | { ok: true; manifest: DeliveryManifest }\n | { ok: false; reason: 'schema-invalid'; details: string } {\n if (!isRecord(input)) return invalid('manifest must be an object');\n if (input.schemaVersion !== 1) return invalid('unsupported schemaVersion');\n if (!Number.isInteger(input.manifestVersion) || Number(input.manifestVersion) < 0) {\n return invalid('manifestVersion must be a non-negative integer');\n }\n if (!isHex(input.contentHash, 64)) return invalid('contentHash must be SHA-256 hex');\n if (!isNonEmptyString(input.tenantId)) return invalid('tenantId is required');\n if (!isIsoDate(input.builtAt)) return invalid('builtAt must be an ISO timestamp');\n if (!Array.isArray(input.experiments)) return invalid('experiments must be an array');\n if (!isSignature(input.signature)) return invalid('a valid Ed25519 signature is required');\n\n const experimentIds = new Set<string>();\n for (const [index, experiment] of input.experiments.entries()) {\n const issue = validateExperiment(experiment, experimentIds);\n if (issue) return invalid(`experiments[${index}]: ${issue}`);\n }\n\n return { ok: true, manifest: input as unknown as DeliveryManifest };\n}\n\nfunction validateExperiment(value: unknown, experimentIds: Set<string>): string | null {\n if (!isRecord(value)) return 'must be an object';\n for (const key of ['id', 'name', 'salt'] as const) {\n if (!isNonEmptyString(value[key])) return `${key} is required`;\n }\n if (experimentIds.has(value.id as string)) return `duplicate experiment id ${value.id as string}`;\n experimentIds.add(value.id as string);\n if (value.state !== 'running' && value.state !== 'paused') return 'invalid state';\n if (!isRate(value.sampleRate)) return 'sampleRate must be between 0 and 1';\n if (typeof value.applyVariants !== 'boolean') return 'applyVariants must be boolean';\n if (!Array.isArray(value.variants) || value.variants.length < 2) {\n return 'at least two variants are required';\n }\n\n const variantKeys = new Set<string>();\n let declaredWeight = 0;\n let weightedVariants = 0;\n for (const [index, variant] of value.variants.entries()) {\n if (!isRecord(variant) || !isNonEmptyString(variant.key)) {\n return `variants[${index}].key is required`;\n }\n if (!isNonEmptyString(variant.name)) return `variants[${index}].name is required`;\n if (variantKeys.has(variant.key)) return `duplicate variant key ${variant.key}`;\n variantKeys.add(variant.key);\n if (variant.weight !== undefined) {\n if (\n !Number.isInteger(variant.weight) ||\n Number(variant.weight) < 0 ||\n Number(variant.weight) > 10000\n ) {\n return `variants[${index}].weight must be an integer between 0 and 10000`;\n }\n weightedVariants += 1;\n declaredWeight += Number(variant.weight);\n }\n if (variant.recipeSampleRate !== undefined && !isRate(variant.recipeSampleRate)) {\n return `variants[${index}].recipeSampleRate must be between 0 and 1`;\n }\n }\n if (weightedVariants > 0 && weightedVariants !== value.variants.length) {\n return 'variant weights must be declared for every variant or none';\n }\n if (weightedVariants > 0 && declaredWeight !== 10000) {\n return 'variant weights must sum to 10000';\n }\n for (const key of ['defaultVariantKey', 'divertTo'] as const) {\n if (\n value[key] !== undefined &&\n (!isNonEmptyString(value[key]) || !variantKeys.has(value[key]))\n ) {\n return `${key} must reference an existing variant`;\n }\n }\n return null;\n}\n\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(sortDeep(value));\n}\n\nfunction sortDeep(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortDeep);\n if (isRecord(value)) {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) sorted[key] = sortDeep(value[key]);\n return sorted;\n }\n return value;\n}\n\nfunction invalid(details: string) {\n return { ok: false as const, reason: 'schema-invalid' as const, details };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction isRate(value: unknown): boolean {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;\n}\n\nfunction isHex(value: unknown, length: number): value is string {\n return typeof value === 'string' && new RegExp(`^[0-9a-f]{${length}}$`).test(value);\n}\n\nfunction isIsoDate(value: unknown): value is string {\n return typeof value === 'string' && Number.isFinite(Date.parse(value));\n}\n\nfunction isSignature(value: unknown): value is ManifestSignature {\n return (\n isRecord(value) &&\n isNonEmptyString(value.keyId) &&\n value.alg === 'ed25519' &&\n isHex(value.sig, 128) &&\n isHex(value.signedHash, 64)\n );\n}\n","/**\n * Ed25519 keypair generation for `fx dev` and tests. Production keys are\n * generated offline and stored in a secrets manager.\n */\nimport { hexEncode } from './hash.js';\nimport { getSubtle } from './subtle.js';\n\nexport interface Ed25519Keypair {\n publicKeyHex: string;\n privateKeyHex: string;\n keyId: string;\n}\n\nexport async function generateKeypair(keyId: string): Promise<Ed25519Keypair> {\n const subtle = await getSubtle();\n const kp = (await subtle.generateKey({ name: 'Ed25519' }, true, [\n 'sign',\n 'verify',\n ])) as CryptoKeyPair;\n const rawPub = new Uint8Array(await subtle.exportKey('raw', kp.publicKey));\n const pkcs8Priv = new Uint8Array(await subtle.exportKey('pkcs8', kp.privateKey));\n // PKCS#8 ed25519 = 16-byte prefix + 32-byte seed. Strip the prefix.\n const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);\n return {\n publicKeyHex: hexEncode(rawPub),\n privateKeyHex: hexEncode(seed),\n keyId,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabricorg/experiments-manifest-signing",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Ed25519 sign + verify utilities for Fabric Experiments manifests. Works in Node, Edge, and browsers.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,28 +10,20 @@
10
10
  "module": "./dist/index.js",
11
11
  "exports": {
12
12
  ".": {
13
- "import": {
14
- "types": "./dist/index.d.ts",
15
- "default": "./dist/index.js"
16
- },
17
- "require": {
18
- "types": "./dist/index.d.cts",
19
- "default": "./dist/index.cjs"
20
- }
13
+ "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
14
+ "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
21
15
  }
22
16
  },
23
- "files": [
24
- "dist"
25
- ],
26
- "devDependencies": {
27
- "tsup": "^8.3.5",
28
- "typescript": "^5.8.3",
29
- "vitest": "^2.1.8"
30
- },
17
+ "files": ["dist"],
31
18
  "scripts": {
32
19
  "build": "tsup",
33
20
  "type-check": "tsc --noEmit",
34
21
  "test": "vitest run",
35
22
  "clean": "rm -rf dist"
23
+ },
24
+ "devDependencies": {
25
+ "tsup": "^8.3.5",
26
+ "typescript": "^5.8.3",
27
+ "vitest": "^3.2.7"
36
28
  }
37
- }
29
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Fabric
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.