@vivipilot/render-manifest 0.1.0 → 0.1.4
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 +1 -0
- package/dist/index.d.cts +114 -0
- package/dist/index.d.ts +39 -38
- package/dist/index.js +1 -289
- package/package.json +6 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/src/index.test.ts +0 -186
- package/src/index.ts +0 -444
- package/tsconfig.json +0 -26
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var m=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var N=(e,n)=>{for(var i in n)m(e,i,{get:n[i],enumerable:!0})},K=(e,n,i,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of U(n))!H.call(e,s)&&s!==i&&m(e,s,{get:()=>n[s],enumerable:!(r=I(n,s))||r.enumerable});return e};var T=e=>K(m({},"__esModule",{value:!0}),e);var G={};N(G,{RENDER_MANIFEST_SCHEMA_VERSION:()=>w,RENDER_MANIFEST_SIGNATURE_ALG:()=>l,base64UrlToBytes:()=>g,bytesToBase64Url:()=>k,canonicalizeJson:()=>y,canonicalizeManifestForSigning:()=>x,computeCanonicalPayloadHash:()=>v,isManifestExpired:()=>P,sha256Hex:()=>A,signRenderManifest:()=>j,validateRenderManifestShape:()=>C,verifyAssetDigest:()=>z,verifyRenderManifest:()=>L,withCanonicalPayloadHash:()=>S});module.exports=T(G);var w="vivipilot.renderManifest.v1",l="Ed25519",p={name:l},b=/^[a-f0-9]{64}$/i;function t(e,n){return{ok:!1,reason:e,message:n}}function d(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function a(e){return typeof e=="string"&&e.length>0}function f(e){return typeof e=="number"&&Number.isFinite(e)&&e>0}function R(e,n="$"){if(e===null)return;let i=typeof e;if(!(i==="string"||i==="boolean")){if(i==="number"){if(!Number.isFinite(e))throw new Error(`Non-finite number at ${n}`);return}if(Array.isArray(e)){e.forEach((r,s)=>R(r,`${n}[${s}]`));return}if(i==="object"){for(let[r,s]of Object.entries(e)){if(s===void 0)throw new Error(`Undefined value at ${n}.${r}`);R(s,`${n}.${r}`)}return}throw new Error(`Unsupported JSON value at ${n}`)}}function y(e){return R(e),e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?`[${e.map(i=>y(i)).join(",")}]`:`{${Object.entries(e).filter(([,i])=>i!==void 0).sort(([i],[r])=>i.localeCompare(r)).map(([i,r])=>`${JSON.stringify(i)}:${y(r)}`).join(",")}}`}function M(e){return new TextEncoder().encode(e)}function F(e){let i="";for(let r=0;r<e.length;r+=32768){let s=e.slice(r,r+32768);i+=String.fromCharCode(...s)}return i}function k(e){return btoa(F(e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function g(e){let n=e.replace(/-/g,"+").replace(/_/g,"/"),i=n.padEnd(n.length+(4-n.length%4)%4,"="),r=atob(i),s=new Uint8Array(r.length);for(let o=0;o<r.length;o++)s[o]=r.charCodeAt(o);return s}function V(e){return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}function _(e){return e??globalThis.crypto??null}async function O(e,n){let i=_(n);if(!i?.subtle)throw new Error("crypto_unavailable");let r=typeof e=="string"?M(e):e,s=await i.subtle.digest("SHA-256",r);return new Uint8Array(s)}async function A(e,n){return[...await O(e,n)].map(r=>r.toString(16).padStart(2,"0")).join("")}function E(e){let n={...e};return delete n.signature,n}function J(e){let n=E(e);return{...n,integrity:{...n.integrity,canonicalPayloadHash:""}}}function x(e){return y(E(e))}async function v(e,n){return A(y(J(e)),n)}async function S(e,n){let i=await v(e,n);return{...e,integrity:{...e.integrity,canonicalPayloadHash:i}}}async function $(e,n){return e instanceof CryptoKey?e:typeof e=="string"?n.subtle.importKey("pkcs8",V(g(e)),p,!1,["sign"]):n.subtle.importKey("jwk",e,p,!1,["sign"])}async function q(e,n){return e instanceof CryptoKey?e:typeof e=="string"?n.subtle.importKey("raw",V(g(e)),p,!1,["verify"]):n.subtle.importKey("jwk",e,p,!1,["verify"])}async function j(e,n){let i=_(n.crypto);if(!i?.subtle)throw new Error("crypto_unavailable");let r=await S(e,i),s=M(x(r)),o=await $(n.privateKey,i),u=await i.subtle.sign(p,o,s);return{...r,signature:{alg:l,keyId:n.keyId,value:k(new Uint8Array(u))}}}function D(e){return d(e)?a(e.assetId)&&a(e.url)&&a(e.sha256)&&b.test(e.sha256)&&a(e.mimeType)&&f(e.byteLength):!1}function C(e){return d(e)?e.schema!==w?t("invalid_shape","Unsupported render manifest schema."):a(e.manifestId)?a(e.generationId)?a(e.userId)?!a(e.createdAt)||Number.isNaN(Date.parse(e.createdAt))?t("invalid_shape","createdAt must be an ISO date string."):e.expiresAt!==void 0&&(!a(e.expiresAt)||Number.isNaN(Date.parse(e.expiresAt)))?t("invalid_shape","expiresAt must be an ISO date string when provided."):a(e.engineVersion)?d(e.entitlement)?d(e.billing)?a(e.billing.creditTransactionId)?f(e.billing.creditsCharged)?a(e.billing.idempotencyKey)?a(e.billing.creditSource)?d(e.render)?d(e.render.canvas)?!f(e.render.canvas.width)||!f(e.render.canvas.height)||!f(e.render.canvas.fps)?t("invalid_shape","render.canvas dimensions and fps must be positive."):f(e.render.durationFrames)?Array.isArray(e.render.overlays)?e.render.assets!==void 0&&(!Array.isArray(e.render.assets)||!e.render.assets.every(D))?t("invalid_shape","render.assets must contain immutable asset references with SHA-256 hashes."):d(e.integrity)?!a(e.integrity.canonicalPayloadHash)||!b.test(e.integrity.canonicalPayloadHash)?t("invalid_shape","integrity.canonicalPayloadHash must be a SHA-256 hex digest."):!a(e.integrity.sceneHash)||!b.test(e.integrity.sceneHash)?t("invalid_shape","integrity.sceneHash must be a SHA-256 hex digest."):d(e.signature)?e.signature.alg!==l?t("invalid_shape","Unsupported signature algorithm."):a(e.signature.keyId)?a(e.signature.value)?{ok:!0,manifest:e,canonicalPayloadHash:e.integrity.canonicalPayloadHash}:t("invalid_shape","signature.value is required."):t("invalid_shape","signature.keyId is required."):t("invalid_shape","signature is required."):t("invalid_shape","integrity is required."):t("invalid_shape","render.overlays must be an array."):t("invalid_shape","render.durationFrames must be positive."):t("invalid_shape","render.canvas is required."):t("invalid_shape","render payload is required."):t("invalid_shape","billing.creditSource is required."):t("invalid_shape","billing.idempotencyKey is required."):t("invalid_shape","billing.creditsCharged must be positive."):t("invalid_shape","billing.creditTransactionId is required."):t("invalid_shape","billing is required."):t("invalid_shape","entitlement is required."):t("invalid_shape","engineVersion is required."):t("invalid_shape","userId is required."):t("invalid_shape","generationId is required."):t("invalid_shape","manifestId is required."):t("invalid_shape","Manifest must be an object.")}async function B(e,n){return typeof n=="function"?n(e):Array.isArray(n)?n.find(i=>i.keyId===e)?.publicKey:n[e]}function P(e,n=new Date){return typeof e.expiresAt=="string"&&Date.parse(e.expiresAt)<=n.getTime()}async function L(e,n){let i=C(e);if(!i.ok)return i;let r=i.manifest;if((n.checkExpiry??!0)&&P(r,n.now??new Date))return t("expired","Render manifest has expired.");let o;try{o=await v(r,n.crypto)}catch(c){return t("crypto_unavailable",c instanceof Error?c.message:"Unable to hash manifest payload.")}if(o!==r.integrity.canonicalPayloadHash)return t("invalid_canonical_payload_hash","Manifest canonical payload hash does not match payload.");let u=await B(r.signature.keyId,n.publicKeys);if(!u)return t("missing_public_key",`No public key registered for keyId ${r.signature.keyId}.`);let h=_(n.crypto);if(!h?.subtle)return t("crypto_unavailable","WebCrypto subtle crypto is unavailable.");try{let c=await q(u,h);if(!await h.subtle.verify(p,c,V(g(r.signature.value)),M(x(r))))return t("invalid_signature","Manifest signature is invalid.")}catch(c){return t("invalid_key",c instanceof Error?c.message:"Unable to import or verify public key.")}return{ok:!0,manifest:r,canonicalPayloadHash:o}}async function z(e,n,i){return e.byteLength!==n.byteLength?!1:(await A(n,i)).toLowerCase()===e.sha256.toLowerCase()}0&&(module.exports={RENDER_MANIFEST_SCHEMA_VERSION,RENDER_MANIFEST_SIGNATURE_ALG,base64UrlToBytes,bytesToBase64Url,canonicalizeJson,canonicalizeManifestForSigning,computeCanonicalPayloadHash,isManifestExpired,sha256Hex,signRenderManifest,validateRenderManifestShape,verifyAssetDigest,verifyRenderManifest,withCanonicalPayloadHash});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
declare const RENDER_MANIFEST_SCHEMA_VERSION: "vivipilot.renderManifest.v1";
|
|
2
|
+
declare const RENDER_MANIFEST_SIGNATURE_ALG: "Ed25519";
|
|
3
|
+
type JsonPrimitive = null | boolean | number | string;
|
|
4
|
+
type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
5
|
+
[key: string]: JsonValue;
|
|
6
|
+
};
|
|
7
|
+
type JsonObject = {
|
|
8
|
+
[key: string]: JsonValue;
|
|
9
|
+
};
|
|
10
|
+
type RenderManifestResolution = "720p" | "1080p" | "2k" | "4k";
|
|
11
|
+
type RenderManifestFormat = "mp4" | "webm" | "gif" | "mov";
|
|
12
|
+
type RenderManifestEngine = "pixi-dw-scene-v1" | "three-scene-v1" | "mixed";
|
|
13
|
+
type RenderManifestCreditSource = "paid_topup" | "paid_enterprise" | "internal_test";
|
|
14
|
+
type RenderManifestAsset = {
|
|
15
|
+
assetId: string;
|
|
16
|
+
url: string;
|
|
17
|
+
sha256: string;
|
|
18
|
+
mimeType: string;
|
|
19
|
+
byteLength: number;
|
|
20
|
+
role?: string;
|
|
21
|
+
cacheKey?: string;
|
|
22
|
+
};
|
|
23
|
+
type RenderManifestEntitlement = {
|
|
24
|
+
maxResolution: RenderManifestResolution;
|
|
25
|
+
watermark: boolean;
|
|
26
|
+
commercialUse: boolean;
|
|
27
|
+
allowedFormats?: RenderManifestFormat[];
|
|
28
|
+
};
|
|
29
|
+
type RenderManifestBilling = {
|
|
30
|
+
creditTransactionId: string;
|
|
31
|
+
creditsCharged: number;
|
|
32
|
+
idempotencyKey: string;
|
|
33
|
+
creditSource: RenderManifestCreditSource;
|
|
34
|
+
};
|
|
35
|
+
type RenderManifestCanvas = {
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
fps: number;
|
|
39
|
+
};
|
|
40
|
+
type RenderManifestRenderPayload = {
|
|
41
|
+
engine: RenderManifestEngine;
|
|
42
|
+
canvas: RenderManifestCanvas;
|
|
43
|
+
durationFrames: number;
|
|
44
|
+
backgroundColor?: string;
|
|
45
|
+
overlays: JsonValue[];
|
|
46
|
+
assets?: RenderManifestAsset[];
|
|
47
|
+
metadata?: JsonObject;
|
|
48
|
+
};
|
|
49
|
+
type RenderManifestIntegrity = {
|
|
50
|
+
canonicalPayloadHash: string;
|
|
51
|
+
sceneHash: string;
|
|
52
|
+
};
|
|
53
|
+
type RenderManifestSignature = {
|
|
54
|
+
alg: typeof RENDER_MANIFEST_SIGNATURE_ALG;
|
|
55
|
+
keyId: string;
|
|
56
|
+
value: string;
|
|
57
|
+
};
|
|
58
|
+
type VivipilotRenderManifestV1 = {
|
|
59
|
+
schema: typeof RENDER_MANIFEST_SCHEMA_VERSION;
|
|
60
|
+
manifestId: string;
|
|
61
|
+
generationId: string;
|
|
62
|
+
userId: string;
|
|
63
|
+
projectId?: string;
|
|
64
|
+
createdAt: string;
|
|
65
|
+
expiresAt?: string;
|
|
66
|
+
engineVersion: string;
|
|
67
|
+
entitlement: RenderManifestEntitlement;
|
|
68
|
+
billing: RenderManifestBilling;
|
|
69
|
+
render: RenderManifestRenderPayload;
|
|
70
|
+
integrity: RenderManifestIntegrity;
|
|
71
|
+
signature: RenderManifestSignature;
|
|
72
|
+
};
|
|
73
|
+
type UnsignedVivipilotRenderManifestV1 = Omit<VivipilotRenderManifestV1, "signature">;
|
|
74
|
+
type RenderManifestPublicKey = {
|
|
75
|
+
keyId: string;
|
|
76
|
+
publicKey: string;
|
|
77
|
+
};
|
|
78
|
+
type PublicKeyResolver = Record<string, string> | RenderManifestPublicKey[] | ((keyId: string) => string | undefined | Promise<string | undefined>);
|
|
79
|
+
type SignRenderManifestOptions = {
|
|
80
|
+
keyId: string;
|
|
81
|
+
privateKey: CryptoKey | JsonWebKey | string;
|
|
82
|
+
crypto?: Crypto;
|
|
83
|
+
};
|
|
84
|
+
type VerifyRenderManifestOptions = {
|
|
85
|
+
publicKeys: PublicKeyResolver;
|
|
86
|
+
crypto?: Crypto;
|
|
87
|
+
now?: Date;
|
|
88
|
+
checkExpiry?: boolean;
|
|
89
|
+
};
|
|
90
|
+
type RenderManifestVerificationSuccess = {
|
|
91
|
+
ok: true;
|
|
92
|
+
manifest: VivipilotRenderManifestV1;
|
|
93
|
+
canonicalPayloadHash: string;
|
|
94
|
+
};
|
|
95
|
+
type RenderManifestVerificationFailure = {
|
|
96
|
+
ok: false;
|
|
97
|
+
reason: "invalid_shape" | "invalid_canonical_payload_hash" | "invalid_signature" | "missing_public_key" | "expired" | "crypto_unavailable" | "invalid_key";
|
|
98
|
+
message: string;
|
|
99
|
+
};
|
|
100
|
+
type RenderManifestVerificationResult = RenderManifestVerificationSuccess | RenderManifestVerificationFailure;
|
|
101
|
+
declare function canonicalizeJson(value: unknown): string;
|
|
102
|
+
declare function bytesToBase64Url(bytes: Uint8Array): string;
|
|
103
|
+
declare function base64UrlToBytes(value: string): Uint8Array;
|
|
104
|
+
declare function sha256Hex(value: string | Uint8Array, cryptoImpl?: Crypto): Promise<string>;
|
|
105
|
+
declare function canonicalizeManifestForSigning(manifest: VivipilotRenderManifestV1 | UnsignedVivipilotRenderManifestV1): string;
|
|
106
|
+
declare function computeCanonicalPayloadHash(manifest: VivipilotRenderManifestV1 | UnsignedVivipilotRenderManifestV1, cryptoImpl?: Crypto): Promise<string>;
|
|
107
|
+
declare function withCanonicalPayloadHash(manifest: UnsignedVivipilotRenderManifestV1, cryptoImpl?: Crypto): Promise<UnsignedVivipilotRenderManifestV1>;
|
|
108
|
+
declare function signRenderManifest(manifest: UnsignedVivipilotRenderManifestV1, options: SignRenderManifestOptions): Promise<VivipilotRenderManifestV1>;
|
|
109
|
+
declare function validateRenderManifestShape(value: unknown): RenderManifestVerificationResult;
|
|
110
|
+
declare function isManifestExpired(manifest: VivipilotRenderManifestV1, now?: Date): boolean;
|
|
111
|
+
declare function verifyRenderManifest(value: unknown, options: VerifyRenderManifestOptions): Promise<RenderManifestVerificationResult>;
|
|
112
|
+
declare function verifyAssetDigest(asset: RenderManifestAsset, bytes: Uint8Array, cryptoImpl?: Crypto): Promise<boolean>;
|
|
113
|
+
|
|
114
|
+
export { type JsonObject, type JsonPrimitive, type JsonValue, type PublicKeyResolver, RENDER_MANIFEST_SCHEMA_VERSION, RENDER_MANIFEST_SIGNATURE_ALG, type RenderManifestAsset, type RenderManifestBilling, type RenderManifestCanvas, type RenderManifestCreditSource, type RenderManifestEngine, type RenderManifestEntitlement, type RenderManifestFormat, type RenderManifestIntegrity, type RenderManifestPublicKey, type RenderManifestRenderPayload, type RenderManifestResolution, type RenderManifestSignature, type RenderManifestVerificationFailure, type RenderManifestVerificationResult, type RenderManifestVerificationSuccess, type SignRenderManifestOptions, type UnsignedVivipilotRenderManifestV1, type VerifyRenderManifestOptions, type VivipilotRenderManifestV1, base64UrlToBytes, bytesToBase64Url, canonicalizeJson, canonicalizeManifestForSigning, computeCanonicalPayloadHash, isManifestExpired, sha256Hex, signRenderManifest, validateRenderManifestShape, verifyAssetDigest, verifyRenderManifest, withCanonicalPayloadHash };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
declare const RENDER_MANIFEST_SCHEMA_VERSION: "vivipilot.renderManifest.v1";
|
|
2
|
+
declare const RENDER_MANIFEST_SIGNATURE_ALG: "Ed25519";
|
|
3
|
+
type JsonPrimitive = null | boolean | number | string;
|
|
4
|
+
type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
5
5
|
[key: string]: JsonValue;
|
|
6
6
|
};
|
|
7
|
-
|
|
7
|
+
type JsonObject = {
|
|
8
8
|
[key: string]: JsonValue;
|
|
9
9
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
type RenderManifestResolution = "720p" | "1080p" | "2k" | "4k";
|
|
11
|
+
type RenderManifestFormat = "mp4" | "webm" | "gif" | "mov";
|
|
12
|
+
type RenderManifestEngine = "pixi-dw-scene-v1" | "three-scene-v1" | "mixed";
|
|
13
|
+
type RenderManifestCreditSource = "paid_topup" | "paid_enterprise" | "internal_test";
|
|
14
|
+
type RenderManifestAsset = {
|
|
15
15
|
assetId: string;
|
|
16
16
|
url: string;
|
|
17
17
|
sha256: string;
|
|
@@ -20,24 +20,24 @@ export type RenderManifestAsset = {
|
|
|
20
20
|
role?: string;
|
|
21
21
|
cacheKey?: string;
|
|
22
22
|
};
|
|
23
|
-
|
|
23
|
+
type RenderManifestEntitlement = {
|
|
24
24
|
maxResolution: RenderManifestResolution;
|
|
25
25
|
watermark: boolean;
|
|
26
26
|
commercialUse: boolean;
|
|
27
27
|
allowedFormats?: RenderManifestFormat[];
|
|
28
28
|
};
|
|
29
|
-
|
|
29
|
+
type RenderManifestBilling = {
|
|
30
30
|
creditTransactionId: string;
|
|
31
31
|
creditsCharged: number;
|
|
32
32
|
idempotencyKey: string;
|
|
33
33
|
creditSource: RenderManifestCreditSource;
|
|
34
34
|
};
|
|
35
|
-
|
|
35
|
+
type RenderManifestCanvas = {
|
|
36
36
|
width: number;
|
|
37
37
|
height: number;
|
|
38
38
|
fps: number;
|
|
39
39
|
};
|
|
40
|
-
|
|
40
|
+
type RenderManifestRenderPayload = {
|
|
41
41
|
engine: RenderManifestEngine;
|
|
42
42
|
canvas: RenderManifestCanvas;
|
|
43
43
|
durationFrames: number;
|
|
@@ -46,16 +46,16 @@ export type RenderManifestRenderPayload = {
|
|
|
46
46
|
assets?: RenderManifestAsset[];
|
|
47
47
|
metadata?: JsonObject;
|
|
48
48
|
};
|
|
49
|
-
|
|
49
|
+
type RenderManifestIntegrity = {
|
|
50
50
|
canonicalPayloadHash: string;
|
|
51
51
|
sceneHash: string;
|
|
52
52
|
};
|
|
53
|
-
|
|
53
|
+
type RenderManifestSignature = {
|
|
54
54
|
alg: typeof RENDER_MANIFEST_SIGNATURE_ALG;
|
|
55
55
|
keyId: string;
|
|
56
56
|
value: string;
|
|
57
57
|
};
|
|
58
|
-
|
|
58
|
+
type VivipilotRenderManifestV1 = {
|
|
59
59
|
schema: typeof RENDER_MANIFEST_SCHEMA_VERSION;
|
|
60
60
|
manifestId: string;
|
|
61
61
|
generationId: string;
|
|
@@ -70,44 +70,45 @@ export type VivipilotRenderManifestV1 = {
|
|
|
70
70
|
integrity: RenderManifestIntegrity;
|
|
71
71
|
signature: RenderManifestSignature;
|
|
72
72
|
};
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
type UnsignedVivipilotRenderManifestV1 = Omit<VivipilotRenderManifestV1, "signature">;
|
|
74
|
+
type RenderManifestPublicKey = {
|
|
75
75
|
keyId: string;
|
|
76
76
|
publicKey: string;
|
|
77
77
|
};
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
type PublicKeyResolver = Record<string, string> | RenderManifestPublicKey[] | ((keyId: string) => string | undefined | Promise<string | undefined>);
|
|
79
|
+
type SignRenderManifestOptions = {
|
|
80
80
|
keyId: string;
|
|
81
81
|
privateKey: CryptoKey | JsonWebKey | string;
|
|
82
82
|
crypto?: Crypto;
|
|
83
83
|
};
|
|
84
|
-
|
|
84
|
+
type VerifyRenderManifestOptions = {
|
|
85
85
|
publicKeys: PublicKeyResolver;
|
|
86
86
|
crypto?: Crypto;
|
|
87
87
|
now?: Date;
|
|
88
88
|
checkExpiry?: boolean;
|
|
89
89
|
};
|
|
90
|
-
|
|
90
|
+
type RenderManifestVerificationSuccess = {
|
|
91
91
|
ok: true;
|
|
92
92
|
manifest: VivipilotRenderManifestV1;
|
|
93
93
|
canonicalPayloadHash: string;
|
|
94
94
|
};
|
|
95
|
-
|
|
95
|
+
type RenderManifestVerificationFailure = {
|
|
96
96
|
ok: false;
|
|
97
97
|
reason: "invalid_shape" | "invalid_canonical_payload_hash" | "invalid_signature" | "missing_public_key" | "expired" | "crypto_unavailable" | "invalid_key";
|
|
98
98
|
message: string;
|
|
99
99
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
100
|
+
type RenderManifestVerificationResult = RenderManifestVerificationSuccess | RenderManifestVerificationFailure;
|
|
101
|
+
declare function canonicalizeJson(value: unknown): string;
|
|
102
|
+
declare function bytesToBase64Url(bytes: Uint8Array): string;
|
|
103
|
+
declare function base64UrlToBytes(value: string): Uint8Array;
|
|
104
|
+
declare function sha256Hex(value: string | Uint8Array, cryptoImpl?: Crypto): Promise<string>;
|
|
105
|
+
declare function canonicalizeManifestForSigning(manifest: VivipilotRenderManifestV1 | UnsignedVivipilotRenderManifestV1): string;
|
|
106
|
+
declare function computeCanonicalPayloadHash(manifest: VivipilotRenderManifestV1 | UnsignedVivipilotRenderManifestV1, cryptoImpl?: Crypto): Promise<string>;
|
|
107
|
+
declare function withCanonicalPayloadHash(manifest: UnsignedVivipilotRenderManifestV1, cryptoImpl?: Crypto): Promise<UnsignedVivipilotRenderManifestV1>;
|
|
108
|
+
declare function signRenderManifest(manifest: UnsignedVivipilotRenderManifestV1, options: SignRenderManifestOptions): Promise<VivipilotRenderManifestV1>;
|
|
109
|
+
declare function validateRenderManifestShape(value: unknown): RenderManifestVerificationResult;
|
|
110
|
+
declare function isManifestExpired(manifest: VivipilotRenderManifestV1, now?: Date): boolean;
|
|
111
|
+
declare function verifyRenderManifest(value: unknown, options: VerifyRenderManifestOptions): Promise<RenderManifestVerificationResult>;
|
|
112
|
+
declare function verifyAssetDigest(asset: RenderManifestAsset, bytes: Uint8Array, cryptoImpl?: Crypto): Promise<boolean>;
|
|
113
|
+
|
|
114
|
+
export { type JsonObject, type JsonPrimitive, type JsonValue, type PublicKeyResolver, RENDER_MANIFEST_SCHEMA_VERSION, RENDER_MANIFEST_SIGNATURE_ALG, type RenderManifestAsset, type RenderManifestBilling, type RenderManifestCanvas, type RenderManifestCreditSource, type RenderManifestEngine, type RenderManifestEntitlement, type RenderManifestFormat, type RenderManifestIntegrity, type RenderManifestPublicKey, type RenderManifestRenderPayload, type RenderManifestResolution, type RenderManifestSignature, type RenderManifestVerificationFailure, type RenderManifestVerificationResult, type RenderManifestVerificationSuccess, type SignRenderManifestOptions, type UnsignedVivipilotRenderManifestV1, type VerifyRenderManifestOptions, type VivipilotRenderManifestV1, base64UrlToBytes, bytesToBase64Url, canonicalizeJson, canonicalizeManifestForSigning, computeCanonicalPayloadHash, isManifestExpired, sha256Hex, signRenderManifest, validateRenderManifestShape, verifyAssetDigest, verifyRenderManifest, withCanonicalPayloadHash };
|
package/dist/index.js
CHANGED
|
@@ -1,289 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export const RENDER_MANIFEST_SIGNATURE_ALG = "Ed25519";
|
|
3
|
-
const ED25519_ALGORITHM = { name: RENDER_MANIFEST_SIGNATURE_ALG };
|
|
4
|
-
const HEX_SHA256_PATTERN = /^[a-f0-9]{64}$/i;
|
|
5
|
-
function fail(reason, message) {
|
|
6
|
-
return { ok: false, reason, message };
|
|
7
|
-
}
|
|
8
|
-
function isRecord(value) {
|
|
9
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
10
|
-
}
|
|
11
|
-
function isNonEmptyString(value) {
|
|
12
|
-
return typeof value === "string" && value.length > 0;
|
|
13
|
-
}
|
|
14
|
-
function isFinitePositiveNumber(value) {
|
|
15
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
16
|
-
}
|
|
17
|
-
function assertJsonCompatible(value, path = "$") {
|
|
18
|
-
if (value === null)
|
|
19
|
-
return;
|
|
20
|
-
const kind = typeof value;
|
|
21
|
-
if (kind === "string" || kind === "boolean")
|
|
22
|
-
return;
|
|
23
|
-
if (kind === "number") {
|
|
24
|
-
if (!Number.isFinite(value))
|
|
25
|
-
throw new Error(`Non-finite number at ${path}`);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (Array.isArray(value)) {
|
|
29
|
-
value.forEach((item, index) => assertJsonCompatible(item, `${path}[${index}]`));
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (kind === "object") {
|
|
33
|
-
for (const [key, entryValue] of Object.entries(value)) {
|
|
34
|
-
if (entryValue === undefined)
|
|
35
|
-
throw new Error(`Undefined value at ${path}.${key}`);
|
|
36
|
-
assertJsonCompatible(entryValue, `${path}.${key}`);
|
|
37
|
-
}
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
throw new Error(`Unsupported JSON value at ${path}`);
|
|
41
|
-
}
|
|
42
|
-
export function canonicalizeJson(value) {
|
|
43
|
-
assertJsonCompatible(value);
|
|
44
|
-
if (value === null || typeof value !== "object")
|
|
45
|
-
return JSON.stringify(value);
|
|
46
|
-
if (Array.isArray(value))
|
|
47
|
-
return `[${value.map((item) => canonicalizeJson(item)).join(",")}]`;
|
|
48
|
-
const entries = Object.entries(value)
|
|
49
|
-
.filter(([, entryValue]) => entryValue !== undefined)
|
|
50
|
-
.sort(([left], [right]) => left.localeCompare(right));
|
|
51
|
-
return `{${entries
|
|
52
|
-
.map(([key, entryValue]) => `${JSON.stringify(key)}:${canonicalizeJson(entryValue)}`)
|
|
53
|
-
.join(",")}}`;
|
|
54
|
-
}
|
|
55
|
-
function utf8ToBytes(value) {
|
|
56
|
-
return new TextEncoder().encode(value);
|
|
57
|
-
}
|
|
58
|
-
function bytesToBinary(bytes) {
|
|
59
|
-
const chunkSize = 0x8000;
|
|
60
|
-
let binary = "";
|
|
61
|
-
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
62
|
-
const chunk = bytes.slice(offset, offset + chunkSize);
|
|
63
|
-
binary += String.fromCharCode(...chunk);
|
|
64
|
-
}
|
|
65
|
-
return binary;
|
|
66
|
-
}
|
|
67
|
-
export function bytesToBase64Url(bytes) {
|
|
68
|
-
return btoa(bytesToBinary(bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
69
|
-
}
|
|
70
|
-
export function base64UrlToBytes(value) {
|
|
71
|
-
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
72
|
-
const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), "=");
|
|
73
|
-
const binary = atob(padded);
|
|
74
|
-
const bytes = new Uint8Array(binary.length);
|
|
75
|
-
for (let index = 0; index < binary.length; index++)
|
|
76
|
-
bytes[index] = binary.charCodeAt(index);
|
|
77
|
-
return bytes;
|
|
78
|
-
}
|
|
79
|
-
function toArrayBuffer(bytes) {
|
|
80
|
-
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
81
|
-
}
|
|
82
|
-
function getCrypto(cryptoImpl) {
|
|
83
|
-
return cryptoImpl ?? globalThis.crypto ?? null;
|
|
84
|
-
}
|
|
85
|
-
async function sha256Bytes(value, cryptoImpl) {
|
|
86
|
-
const cryptoRuntime = getCrypto(cryptoImpl);
|
|
87
|
-
if (!cryptoRuntime?.subtle)
|
|
88
|
-
throw new Error("crypto_unavailable");
|
|
89
|
-
const input = typeof value === "string" ? utf8ToBytes(value) : value;
|
|
90
|
-
const digest = await cryptoRuntime.subtle.digest("SHA-256", input);
|
|
91
|
-
return new Uint8Array(digest);
|
|
92
|
-
}
|
|
93
|
-
export async function sha256Hex(value, cryptoImpl) {
|
|
94
|
-
const bytes = await sha256Bytes(value, cryptoImpl);
|
|
95
|
-
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
96
|
-
}
|
|
97
|
-
function stripSignature(manifest) {
|
|
98
|
-
const unsigned = { ...manifest };
|
|
99
|
-
delete unsigned.signature;
|
|
100
|
-
return unsigned;
|
|
101
|
-
}
|
|
102
|
-
function payloadForCanonicalHash(manifest) {
|
|
103
|
-
const unsigned = stripSignature(manifest);
|
|
104
|
-
return {
|
|
105
|
-
...unsigned,
|
|
106
|
-
integrity: {
|
|
107
|
-
...unsigned.integrity,
|
|
108
|
-
canonicalPayloadHash: "",
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
export function canonicalizeManifestForSigning(manifest) {
|
|
113
|
-
return canonicalizeJson(stripSignature(manifest));
|
|
114
|
-
}
|
|
115
|
-
export async function computeCanonicalPayloadHash(manifest, cryptoImpl) {
|
|
116
|
-
return sha256Hex(canonicalizeJson(payloadForCanonicalHash(manifest)), cryptoImpl);
|
|
117
|
-
}
|
|
118
|
-
export async function withCanonicalPayloadHash(manifest, cryptoImpl) {
|
|
119
|
-
const canonicalPayloadHash = await computeCanonicalPayloadHash(manifest, cryptoImpl);
|
|
120
|
-
return {
|
|
121
|
-
...manifest,
|
|
122
|
-
integrity: {
|
|
123
|
-
...manifest.integrity,
|
|
124
|
-
canonicalPayloadHash,
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
async function importPrivateKey(privateKey, cryptoRuntime) {
|
|
129
|
-
if (privateKey instanceof CryptoKey)
|
|
130
|
-
return privateKey;
|
|
131
|
-
if (typeof privateKey === "string") {
|
|
132
|
-
return cryptoRuntime.subtle.importKey("pkcs8", toArrayBuffer(base64UrlToBytes(privateKey)), ED25519_ALGORITHM, false, ["sign"]);
|
|
133
|
-
}
|
|
134
|
-
return cryptoRuntime.subtle.importKey("jwk", privateKey, ED25519_ALGORITHM, false, ["sign"]);
|
|
135
|
-
}
|
|
136
|
-
async function importPublicKey(publicKey, cryptoRuntime) {
|
|
137
|
-
if (publicKey instanceof CryptoKey)
|
|
138
|
-
return publicKey;
|
|
139
|
-
if (typeof publicKey === "string") {
|
|
140
|
-
return cryptoRuntime.subtle.importKey("raw", toArrayBuffer(base64UrlToBytes(publicKey)), ED25519_ALGORITHM, false, ["verify"]);
|
|
141
|
-
}
|
|
142
|
-
return cryptoRuntime.subtle.importKey("jwk", publicKey, ED25519_ALGORITHM, false, ["verify"]);
|
|
143
|
-
}
|
|
144
|
-
export async function signRenderManifest(manifest, options) {
|
|
145
|
-
const cryptoRuntime = getCrypto(options.crypto);
|
|
146
|
-
if (!cryptoRuntime?.subtle)
|
|
147
|
-
throw new Error("crypto_unavailable");
|
|
148
|
-
const preparedManifest = await withCanonicalPayloadHash(manifest, cryptoRuntime);
|
|
149
|
-
const signingPayload = utf8ToBytes(canonicalizeManifestForSigning(preparedManifest));
|
|
150
|
-
const privateKey = await importPrivateKey(options.privateKey, cryptoRuntime);
|
|
151
|
-
const signature = await cryptoRuntime.subtle.sign(ED25519_ALGORITHM, privateKey, signingPayload);
|
|
152
|
-
return {
|
|
153
|
-
...preparedManifest,
|
|
154
|
-
signature: {
|
|
155
|
-
alg: RENDER_MANIFEST_SIGNATURE_ALG,
|
|
156
|
-
keyId: options.keyId,
|
|
157
|
-
value: bytesToBase64Url(new Uint8Array(signature)),
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
function validateAssetShape(asset) {
|
|
162
|
-
if (!isRecord(asset))
|
|
163
|
-
return false;
|
|
164
|
-
return isNonEmptyString(asset.assetId)
|
|
165
|
-
&& isNonEmptyString(asset.url)
|
|
166
|
-
&& isNonEmptyString(asset.sha256)
|
|
167
|
-
&& HEX_SHA256_PATTERN.test(asset.sha256)
|
|
168
|
-
&& isNonEmptyString(asset.mimeType)
|
|
169
|
-
&& isFinitePositiveNumber(asset.byteLength);
|
|
170
|
-
}
|
|
171
|
-
export function validateRenderManifestShape(value) {
|
|
172
|
-
if (!isRecord(value))
|
|
173
|
-
return fail("invalid_shape", "Manifest must be an object.");
|
|
174
|
-
if (value.schema !== RENDER_MANIFEST_SCHEMA_VERSION)
|
|
175
|
-
return fail("invalid_shape", "Unsupported render manifest schema.");
|
|
176
|
-
if (!isNonEmptyString(value.manifestId))
|
|
177
|
-
return fail("invalid_shape", "manifestId is required.");
|
|
178
|
-
if (!isNonEmptyString(value.generationId))
|
|
179
|
-
return fail("invalid_shape", "generationId is required.");
|
|
180
|
-
if (!isNonEmptyString(value.userId))
|
|
181
|
-
return fail("invalid_shape", "userId is required.");
|
|
182
|
-
if (!isNonEmptyString(value.createdAt) || Number.isNaN(Date.parse(value.createdAt)))
|
|
183
|
-
return fail("invalid_shape", "createdAt must be an ISO date string.");
|
|
184
|
-
if (value.expiresAt !== undefined && (!isNonEmptyString(value.expiresAt) || Number.isNaN(Date.parse(value.expiresAt)))) {
|
|
185
|
-
return fail("invalid_shape", "expiresAt must be an ISO date string when provided.");
|
|
186
|
-
}
|
|
187
|
-
if (!isNonEmptyString(value.engineVersion))
|
|
188
|
-
return fail("invalid_shape", "engineVersion is required.");
|
|
189
|
-
if (!isRecord(value.entitlement))
|
|
190
|
-
return fail("invalid_shape", "entitlement is required.");
|
|
191
|
-
if (!isRecord(value.billing))
|
|
192
|
-
return fail("invalid_shape", "billing is required.");
|
|
193
|
-
if (!isNonEmptyString(value.billing.creditTransactionId))
|
|
194
|
-
return fail("invalid_shape", "billing.creditTransactionId is required.");
|
|
195
|
-
if (!isFinitePositiveNumber(value.billing.creditsCharged))
|
|
196
|
-
return fail("invalid_shape", "billing.creditsCharged must be positive.");
|
|
197
|
-
if (!isNonEmptyString(value.billing.idempotencyKey))
|
|
198
|
-
return fail("invalid_shape", "billing.idempotencyKey is required.");
|
|
199
|
-
if (!isNonEmptyString(value.billing.creditSource))
|
|
200
|
-
return fail("invalid_shape", "billing.creditSource is required.");
|
|
201
|
-
if (!isRecord(value.render))
|
|
202
|
-
return fail("invalid_shape", "render payload is required.");
|
|
203
|
-
if (!isRecord(value.render.canvas))
|
|
204
|
-
return fail("invalid_shape", "render.canvas is required.");
|
|
205
|
-
if (!isFinitePositiveNumber(value.render.canvas.width) || !isFinitePositiveNumber(value.render.canvas.height) || !isFinitePositiveNumber(value.render.canvas.fps)) {
|
|
206
|
-
return fail("invalid_shape", "render.canvas dimensions and fps must be positive.");
|
|
207
|
-
}
|
|
208
|
-
if (!isFinitePositiveNumber(value.render.durationFrames))
|
|
209
|
-
return fail("invalid_shape", "render.durationFrames must be positive.");
|
|
210
|
-
if (!Array.isArray(value.render.overlays))
|
|
211
|
-
return fail("invalid_shape", "render.overlays must be an array.");
|
|
212
|
-
if (value.render.assets !== undefined && (!Array.isArray(value.render.assets) || !value.render.assets.every(validateAssetShape))) {
|
|
213
|
-
return fail("invalid_shape", "render.assets must contain immutable asset references with SHA-256 hashes.");
|
|
214
|
-
}
|
|
215
|
-
if (!isRecord(value.integrity))
|
|
216
|
-
return fail("invalid_shape", "integrity is required.");
|
|
217
|
-
if (!isNonEmptyString(value.integrity.canonicalPayloadHash) || !HEX_SHA256_PATTERN.test(value.integrity.canonicalPayloadHash)) {
|
|
218
|
-
return fail("invalid_shape", "integrity.canonicalPayloadHash must be a SHA-256 hex digest.");
|
|
219
|
-
}
|
|
220
|
-
if (!isNonEmptyString(value.integrity.sceneHash) || !HEX_SHA256_PATTERN.test(value.integrity.sceneHash)) {
|
|
221
|
-
return fail("invalid_shape", "integrity.sceneHash must be a SHA-256 hex digest.");
|
|
222
|
-
}
|
|
223
|
-
if (!isRecord(value.signature))
|
|
224
|
-
return fail("invalid_shape", "signature is required.");
|
|
225
|
-
if (value.signature.alg !== RENDER_MANIFEST_SIGNATURE_ALG)
|
|
226
|
-
return fail("invalid_shape", "Unsupported signature algorithm.");
|
|
227
|
-
if (!isNonEmptyString(value.signature.keyId))
|
|
228
|
-
return fail("invalid_shape", "signature.keyId is required.");
|
|
229
|
-
if (!isNonEmptyString(value.signature.value))
|
|
230
|
-
return fail("invalid_shape", "signature.value is required.");
|
|
231
|
-
return {
|
|
232
|
-
ok: true,
|
|
233
|
-
manifest: value,
|
|
234
|
-
canonicalPayloadHash: value.integrity.canonicalPayloadHash,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
async function resolvePublicKey(keyId, resolver) {
|
|
238
|
-
if (typeof resolver === "function")
|
|
239
|
-
return resolver(keyId);
|
|
240
|
-
if (Array.isArray(resolver))
|
|
241
|
-
return resolver.find((entry) => entry.keyId === keyId)?.publicKey;
|
|
242
|
-
return resolver[keyId];
|
|
243
|
-
}
|
|
244
|
-
export function isManifestExpired(manifest, now = new Date()) {
|
|
245
|
-
return typeof manifest.expiresAt === "string" && Date.parse(manifest.expiresAt) <= now.getTime();
|
|
246
|
-
}
|
|
247
|
-
export async function verifyRenderManifest(value, options) {
|
|
248
|
-
const shape = validateRenderManifestShape(value);
|
|
249
|
-
if (!shape.ok)
|
|
250
|
-
return shape;
|
|
251
|
-
const manifest = shape.manifest;
|
|
252
|
-
const checkExpiry = options.checkExpiry ?? true;
|
|
253
|
-
if (checkExpiry && isManifestExpired(manifest, options.now ?? new Date())) {
|
|
254
|
-
return fail("expired", "Render manifest has expired.");
|
|
255
|
-
}
|
|
256
|
-
let canonicalPayloadHash;
|
|
257
|
-
try {
|
|
258
|
-
canonicalPayloadHash = await computeCanonicalPayloadHash(manifest, options.crypto);
|
|
259
|
-
}
|
|
260
|
-
catch (error) {
|
|
261
|
-
return fail("crypto_unavailable", error instanceof Error ? error.message : "Unable to hash manifest payload.");
|
|
262
|
-
}
|
|
263
|
-
if (canonicalPayloadHash !== manifest.integrity.canonicalPayloadHash) {
|
|
264
|
-
return fail("invalid_canonical_payload_hash", "Manifest canonical payload hash does not match payload.");
|
|
265
|
-
}
|
|
266
|
-
const publicKeyMaterial = await resolvePublicKey(manifest.signature.keyId, options.publicKeys);
|
|
267
|
-
if (!publicKeyMaterial)
|
|
268
|
-
return fail("missing_public_key", `No public key registered for keyId ${manifest.signature.keyId}.`);
|
|
269
|
-
const cryptoRuntime = getCrypto(options.crypto);
|
|
270
|
-
if (!cryptoRuntime?.subtle)
|
|
271
|
-
return fail("crypto_unavailable", "WebCrypto subtle crypto is unavailable.");
|
|
272
|
-
try {
|
|
273
|
-
const publicKey = await importPublicKey(publicKeyMaterial, cryptoRuntime);
|
|
274
|
-
const isValid = await cryptoRuntime.subtle.verify(ED25519_ALGORITHM, publicKey, toArrayBuffer(base64UrlToBytes(manifest.signature.value)), utf8ToBytes(canonicalizeManifestForSigning(manifest)));
|
|
275
|
-
if (!isValid)
|
|
276
|
-
return fail("invalid_signature", "Manifest signature is invalid.");
|
|
277
|
-
}
|
|
278
|
-
catch (error) {
|
|
279
|
-
return fail("invalid_key", error instanceof Error ? error.message : "Unable to import or verify public key.");
|
|
280
|
-
}
|
|
281
|
-
return { ok: true, manifest, canonicalPayloadHash };
|
|
282
|
-
}
|
|
283
|
-
export async function verifyAssetDigest(asset, bytes, cryptoImpl) {
|
|
284
|
-
if (asset.byteLength !== bytes.byteLength)
|
|
285
|
-
return false;
|
|
286
|
-
const digest = await sha256Hex(bytes, cryptoImpl);
|
|
287
|
-
return digest.toLowerCase() === asset.sha256.toLowerCase();
|
|
288
|
-
}
|
|
289
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
var w="vivipilot.renderManifest.v1",m="Ed25519",p={name:m},g=/^[a-f0-9]{64}$/i;function t(e,n){return{ok:!1,reason:e,message:n}}function d(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function s(e){return typeof e=="string"&&e.length>0}function f(e){return typeof e=="number"&&Number.isFinite(e)&&e>0}function h(e,n="$"){if(e===null)return;let i=typeof e;if(!(i==="string"||i==="boolean")){if(i==="number"){if(!Number.isFinite(e))throw new Error(`Non-finite number at ${n}`);return}if(Array.isArray(e)){e.forEach((r,a)=>h(r,`${n}[${a}]`));return}if(i==="object"){for(let[r,a]of Object.entries(e)){if(a===void 0)throw new Error(`Undefined value at ${n}.${r}`);h(a,`${n}.${r}`)}return}throw new Error(`Unsupported JSON value at ${n}`)}}function u(e){return h(e),e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?`[${e.map(i=>u(i)).join(",")}]`:`{${Object.entries(e).filter(([,i])=>i!==void 0).sort(([i],[r])=>i.localeCompare(r)).map(([i,r])=>`${JSON.stringify(i)}:${u(r)}`).join(",")}}`}function b(e){return new TextEncoder().encode(e)}function k(e){let i="";for(let r=0;r<e.length;r+=32768){let a=e.slice(r,r+32768);i+=String.fromCharCode(...a)}return i}function E(e){return btoa(k(e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function R(e){let n=e.replace(/-/g,"+").replace(/_/g,"/"),i=n.padEnd(n.length+(4-n.length%4)%4,"="),r=atob(i),a=new Uint8Array(r.length);for(let o=0;o<r.length;o++)a[o]=r.charCodeAt(o);return a}function M(e){return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}function V(e){return e??globalThis.crypto??null}async function S(e,n){let i=V(n);if(!i?.subtle)throw new Error("crypto_unavailable");let r=typeof e=="string"?b(e):e,a=await i.subtle.digest("SHA-256",r);return new Uint8Array(a)}async function _(e,n){return[...await S(e,n)].map(r=>r.toString(16).padStart(2,"0")).join("")}function A(e){let n={...e};return delete n.signature,n}function C(e){let n=A(e);return{...n,integrity:{...n.integrity,canonicalPayloadHash:""}}}function x(e){return u(A(e))}async function v(e,n){return _(u(C(e)),n)}async function P(e,n){let i=await v(e,n);return{...e,integrity:{...e.integrity,canonicalPayloadHash:i}}}async function I(e,n){return e instanceof CryptoKey?e:typeof e=="string"?n.subtle.importKey("pkcs8",M(R(e)),p,!1,["sign"]):n.subtle.importKey("jwk",e,p,!1,["sign"])}async function U(e,n){return e instanceof CryptoKey?e:typeof e=="string"?n.subtle.importKey("raw",M(R(e)),p,!1,["verify"]):n.subtle.importKey("jwk",e,p,!1,["verify"])}async function O(e,n){let i=V(n.crypto);if(!i?.subtle)throw new Error("crypto_unavailable");let r=await P(e,i),a=b(x(r)),o=await I(n.privateKey,i),y=await i.subtle.sign(p,o,a);return{...r,signature:{alg:m,keyId:n.keyId,value:E(new Uint8Array(y))}}}function H(e){return d(e)?s(e.assetId)&&s(e.url)&&s(e.sha256)&&g.test(e.sha256)&&s(e.mimeType)&&f(e.byteLength):!1}function N(e){return d(e)?e.schema!==w?t("invalid_shape","Unsupported render manifest schema."):s(e.manifestId)?s(e.generationId)?s(e.userId)?!s(e.createdAt)||Number.isNaN(Date.parse(e.createdAt))?t("invalid_shape","createdAt must be an ISO date string."):e.expiresAt!==void 0&&(!s(e.expiresAt)||Number.isNaN(Date.parse(e.expiresAt)))?t("invalid_shape","expiresAt must be an ISO date string when provided."):s(e.engineVersion)?d(e.entitlement)?d(e.billing)?s(e.billing.creditTransactionId)?f(e.billing.creditsCharged)?s(e.billing.idempotencyKey)?s(e.billing.creditSource)?d(e.render)?d(e.render.canvas)?!f(e.render.canvas.width)||!f(e.render.canvas.height)||!f(e.render.canvas.fps)?t("invalid_shape","render.canvas dimensions and fps must be positive."):f(e.render.durationFrames)?Array.isArray(e.render.overlays)?e.render.assets!==void 0&&(!Array.isArray(e.render.assets)||!e.render.assets.every(H))?t("invalid_shape","render.assets must contain immutable asset references with SHA-256 hashes."):d(e.integrity)?!s(e.integrity.canonicalPayloadHash)||!g.test(e.integrity.canonicalPayloadHash)?t("invalid_shape","integrity.canonicalPayloadHash must be a SHA-256 hex digest."):!s(e.integrity.sceneHash)||!g.test(e.integrity.sceneHash)?t("invalid_shape","integrity.sceneHash must be a SHA-256 hex digest."):d(e.signature)?e.signature.alg!==m?t("invalid_shape","Unsupported signature algorithm."):s(e.signature.keyId)?s(e.signature.value)?{ok:!0,manifest:e,canonicalPayloadHash:e.integrity.canonicalPayloadHash}:t("invalid_shape","signature.value is required."):t("invalid_shape","signature.keyId is required."):t("invalid_shape","signature is required."):t("invalid_shape","integrity is required."):t("invalid_shape","render.overlays must be an array."):t("invalid_shape","render.durationFrames must be positive."):t("invalid_shape","render.canvas is required."):t("invalid_shape","render payload is required."):t("invalid_shape","billing.creditSource is required."):t("invalid_shape","billing.idempotencyKey is required."):t("invalid_shape","billing.creditsCharged must be positive."):t("invalid_shape","billing.creditTransactionId is required."):t("invalid_shape","billing is required."):t("invalid_shape","entitlement is required."):t("invalid_shape","engineVersion is required."):t("invalid_shape","userId is required."):t("invalid_shape","generationId is required."):t("invalid_shape","manifestId is required."):t("invalid_shape","Manifest must be an object.")}async function K(e,n){return typeof n=="function"?n(e):Array.isArray(n)?n.find(i=>i.keyId===e)?.publicKey:n[e]}function T(e,n=new Date){return typeof e.expiresAt=="string"&&Date.parse(e.expiresAt)<=n.getTime()}async function J(e,n){let i=N(e);if(!i.ok)return i;let r=i.manifest;if((n.checkExpiry??!0)&&T(r,n.now??new Date))return t("expired","Render manifest has expired.");let o;try{o=await v(r,n.crypto)}catch(c){return t("crypto_unavailable",c instanceof Error?c.message:"Unable to hash manifest payload.")}if(o!==r.integrity.canonicalPayloadHash)return t("invalid_canonical_payload_hash","Manifest canonical payload hash does not match payload.");let y=await K(r.signature.keyId,n.publicKeys);if(!y)return t("missing_public_key",`No public key registered for keyId ${r.signature.keyId}.`);let l=V(n.crypto);if(!l?.subtle)return t("crypto_unavailable","WebCrypto subtle crypto is unavailable.");try{let c=await U(y,l);if(!await l.subtle.verify(p,c,M(R(r.signature.value)),b(x(r))))return t("invalid_signature","Manifest signature is invalid.")}catch(c){return t("invalid_key",c instanceof Error?c.message:"Unable to import or verify public key.")}return{ok:!0,manifest:r,canonicalPayloadHash:o}}async function $(e,n,i){return e.byteLength!==n.byteLength?!1:(await _(n,i)).toLowerCase()===e.sha256.toLowerCase()}export{w as RENDER_MANIFEST_SCHEMA_VERSION,m as RENDER_MANIFEST_SIGNATURE_ALG,R as base64UrlToBytes,E as bytesToBase64Url,u as canonicalizeJson,x as canonicalizeManifestForSigning,v as computeCanonicalPayloadHash,T as isManifestExpired,_ as sha256Hex,O as signRenderManifest,N as validateRenderManifestShape,$ as verifyAssetDigest,J as verifyRenderManifest,P as withCanonicalPayloadHash};
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vivipilot/render-manifest",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Signed Vivipilot render manifest schema and verification helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
13
|
"types": "./dist/index.d.ts",
|
|
@@ -12,7 +15,7 @@
|
|
|
12
15
|
}
|
|
13
16
|
},
|
|
14
17
|
"scripts": {
|
|
15
|
-
"build": "
|
|
18
|
+
"build": "tsup",
|
|
16
19
|
"typecheck": "tsc --noEmit",
|
|
17
20
|
"test": "vitest run"
|
|
18
21
|
},
|
|
@@ -26,6 +29,7 @@
|
|
|
26
29
|
"license": "ISC",
|
|
27
30
|
"devDependencies": {
|
|
28
31
|
"@types/node": "^25.2.0",
|
|
32
|
+
"tsup": "^8.5.1",
|
|
29
33
|
"typescript": "^5.3.0",
|
|
30
34
|
"vitest": "4.0.18"
|
|
31
35
|
}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/src/index.test.ts
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
RENDER_MANIFEST_SCHEMA_VERSION,
|
|
4
|
-
base64UrlToBytes,
|
|
5
|
-
bytesToBase64Url,
|
|
6
|
-
canonicalizeJson,
|
|
7
|
-
computeCanonicalPayloadHash,
|
|
8
|
-
sha256Hex,
|
|
9
|
-
signRenderManifest,
|
|
10
|
-
verifyAssetDigest,
|
|
11
|
-
verifyRenderManifest,
|
|
12
|
-
type UnsignedVivipilotRenderManifestV1,
|
|
13
|
-
} from "./index";
|
|
14
|
-
|
|
15
|
-
async function createTestKeyPair() {
|
|
16
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
17
|
-
{ name: "Ed25519" },
|
|
18
|
-
true,
|
|
19
|
-
["sign", "verify"],
|
|
20
|
-
) as CryptoKeyPair;
|
|
21
|
-
|
|
22
|
-
const publicKey = bytesToBase64Url(new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey)));
|
|
23
|
-
const privateKey = bytesToBase64Url(new Uint8Array(await crypto.subtle.exportKey("pkcs8", keyPair.privateKey)));
|
|
24
|
-
return { publicKey, privateKey };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function baseManifest(): UnsignedVivipilotRenderManifestV1 {
|
|
28
|
-
return {
|
|
29
|
-
schema: RENDER_MANIFEST_SCHEMA_VERSION,
|
|
30
|
-
manifestId: "manifest_123",
|
|
31
|
-
generationId: "gen_123",
|
|
32
|
-
userId: "user_123",
|
|
33
|
-
projectId: "project_123",
|
|
34
|
-
createdAt: "2026-06-27T10:00:00.000Z",
|
|
35
|
-
engineVersion: "vivipilot-renderer@0.1.0",
|
|
36
|
-
entitlement: {
|
|
37
|
-
maxResolution: "1080p",
|
|
38
|
-
watermark: false,
|
|
39
|
-
commercialUse: true,
|
|
40
|
-
allowedFormats: ["mp4", "webm"],
|
|
41
|
-
},
|
|
42
|
-
billing: {
|
|
43
|
-
creditTransactionId: "txn_123",
|
|
44
|
-
creditsCharged: 40,
|
|
45
|
-
idempotencyKey: "idem_123",
|
|
46
|
-
creditSource: "paid_topup",
|
|
47
|
-
},
|
|
48
|
-
render: {
|
|
49
|
-
engine: "pixi-dw-scene-v1",
|
|
50
|
-
canvas: { width: 1280, height: 720, fps: 30 },
|
|
51
|
-
durationFrames: 120,
|
|
52
|
-
backgroundColor: "#ffffff",
|
|
53
|
-
overlays: [
|
|
54
|
-
{
|
|
55
|
-
id: "overlay_1",
|
|
56
|
-
type: "motion_graphics",
|
|
57
|
-
pixiScene: {
|
|
58
|
-
type: "pixi_scene",
|
|
59
|
-
schema: "vivipilot.pixi-motion.v1",
|
|
60
|
-
content: "Test scene",
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
assets: [
|
|
65
|
-
{
|
|
66
|
-
assetId: "asset_1",
|
|
67
|
-
url: "https://cdn.example.com/immutable/asset_1.png",
|
|
68
|
-
sha256: "a".repeat(64),
|
|
69
|
-
mimeType: "image/png",
|
|
70
|
-
byteLength: 4,
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
integrity: {
|
|
75
|
-
canonicalPayloadHash: "",
|
|
76
|
-
sceneHash: "b".repeat(64),
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
describe("render manifest canonicalization", () => {
|
|
82
|
-
it("canonicalizes object keys deterministically", () => {
|
|
83
|
-
expect(canonicalizeJson({ b: 1, a: { d: false, c: true } })).toBe('{"a":{"c":true,"d":false},"b":1}');
|
|
84
|
-
expect(canonicalizeJson({ a: { c: true, d: false }, b: 1 })).toBe('{"a":{"c":true,"d":false},"b":1}');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("round-trips base64url bytes", () => {
|
|
88
|
-
const bytes = new Uint8Array([0, 1, 2, 250, 251, 252, 253, 254, 255]);
|
|
89
|
-
expect(base64UrlToBytes(bytesToBase64Url(bytes))).toEqual(bytes);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe("render manifest signing", () => {
|
|
94
|
-
it("signs and verifies an untampered manifest", async () => {
|
|
95
|
-
const keys = await createTestKeyPair();
|
|
96
|
-
const signed = await signRenderManifest(baseManifest(), {
|
|
97
|
-
keyId: "test-key",
|
|
98
|
-
privateKey: keys.privateKey,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const result = await verifyRenderManifest(signed, {
|
|
102
|
-
publicKeys: { "test-key": keys.publicKey },
|
|
103
|
-
now: new Date("2026-06-27T10:01:00.000Z"),
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
expect(result.ok).toBe(true);
|
|
107
|
-
if (result.ok) {
|
|
108
|
-
expect(result.canonicalPayloadHash).toBe(signed.integrity.canonicalPayloadHash);
|
|
109
|
-
expect(result.manifest.signature.keyId).toBe("test-key");
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("rejects a tampered manifest payload", async () => {
|
|
114
|
-
const keys = await createTestKeyPair();
|
|
115
|
-
const signed = await signRenderManifest(baseManifest(), {
|
|
116
|
-
keyId: "test-key",
|
|
117
|
-
privateKey: keys.privateKey,
|
|
118
|
-
});
|
|
119
|
-
const tampered = {
|
|
120
|
-
...signed,
|
|
121
|
-
render: {
|
|
122
|
-
...signed.render,
|
|
123
|
-
durationFrames: signed.render.durationFrames + 1,
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const result = await verifyRenderManifest(tampered, {
|
|
128
|
-
publicKeys: { "test-key": keys.publicKey },
|
|
129
|
-
now: new Date("2026-06-27T10:01:00.000Z"),
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
expect(result.ok).toBe(false);
|
|
133
|
-
if (!result.ok) expect(result.reason).toBe("invalid_canonical_payload_hash");
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("rejects expired internal/test manifests when expiresAt is present", async () => {
|
|
137
|
-
const keys = await createTestKeyPair();
|
|
138
|
-
const signed = await signRenderManifest({
|
|
139
|
-
...baseManifest(),
|
|
140
|
-
expiresAt: "2026-06-27T10:10:00.000Z",
|
|
141
|
-
}, {
|
|
142
|
-
keyId: "test-key",
|
|
143
|
-
privateKey: keys.privateKey,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const result = await verifyRenderManifest(signed, {
|
|
147
|
-
publicKeys: { "test-key": keys.publicKey },
|
|
148
|
-
now: new Date("2026-06-27T10:11:00.000Z"),
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
expect(result.ok).toBe(false);
|
|
152
|
-
if (!result.ok) expect(result.reason).toBe("expired");
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("hashes asset bytes for immutable media checks", async () => {
|
|
156
|
-
const bytes = new TextEncoder().encode("asset");
|
|
157
|
-
const asset = {
|
|
158
|
-
assetId: "asset_1",
|
|
159
|
-
url: "https://cdn.example.com/immutable/asset_1.txt",
|
|
160
|
-
sha256: await sha256Hex(bytes),
|
|
161
|
-
mimeType: "text/plain",
|
|
162
|
-
byteLength: bytes.byteLength,
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
expect(await verifyAssetDigest(asset, bytes)).toBe(true);
|
|
166
|
-
expect(await verifyAssetDigest(asset, new TextEncoder().encode("tampered"))).toBe(false);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("computes a stable hash that excludes the signature and current hash field", async () => {
|
|
170
|
-
const keys = await createTestKeyPair();
|
|
171
|
-
const unsigned = baseManifest();
|
|
172
|
-
const signed = await signRenderManifest(unsigned, {
|
|
173
|
-
keyId: "test-key",
|
|
174
|
-
privateKey: keys.privateKey,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
expect(await computeCanonicalPayloadHash(unsigned)).toBe(signed.integrity.canonicalPayloadHash);
|
|
178
|
-
expect(await computeCanonicalPayloadHash({
|
|
179
|
-
...signed,
|
|
180
|
-
integrity: {
|
|
181
|
-
...signed.integrity,
|
|
182
|
-
canonicalPayloadHash: "c".repeat(64),
|
|
183
|
-
},
|
|
184
|
-
})).toBe(signed.integrity.canonicalPayloadHash);
|
|
185
|
-
});
|
|
186
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
export const RENDER_MANIFEST_SCHEMA_VERSION = "vivipilot.renderManifest.v1" as const;
|
|
2
|
-
export const RENDER_MANIFEST_SIGNATURE_ALG = "Ed25519" as const;
|
|
3
|
-
|
|
4
|
-
export type JsonPrimitive = null | boolean | number | string;
|
|
5
|
-
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
|
6
|
-
export type JsonObject = { [key: string]: JsonValue };
|
|
7
|
-
|
|
8
|
-
export type RenderManifestResolution = "720p" | "1080p" | "2k" | "4k";
|
|
9
|
-
export type RenderManifestFormat = "mp4" | "webm" | "gif" | "mov";
|
|
10
|
-
export type RenderManifestEngine = "pixi-dw-scene-v1" | "three-scene-v1" | "mixed";
|
|
11
|
-
export type RenderManifestCreditSource = "paid_topup" | "paid_enterprise" | "internal_test";
|
|
12
|
-
|
|
13
|
-
export type RenderManifestAsset = {
|
|
14
|
-
assetId: string;
|
|
15
|
-
url: string;
|
|
16
|
-
sha256: string;
|
|
17
|
-
mimeType: string;
|
|
18
|
-
byteLength: number;
|
|
19
|
-
role?: string;
|
|
20
|
-
cacheKey?: string;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export type RenderManifestEntitlement = {
|
|
24
|
-
maxResolution: RenderManifestResolution;
|
|
25
|
-
watermark: boolean;
|
|
26
|
-
commercialUse: boolean;
|
|
27
|
-
allowedFormats?: RenderManifestFormat[];
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export type RenderManifestBilling = {
|
|
31
|
-
creditTransactionId: string;
|
|
32
|
-
creditsCharged: number;
|
|
33
|
-
idempotencyKey: string;
|
|
34
|
-
creditSource: RenderManifestCreditSource;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export type RenderManifestCanvas = {
|
|
38
|
-
width: number;
|
|
39
|
-
height: number;
|
|
40
|
-
fps: number;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export type RenderManifestRenderPayload = {
|
|
44
|
-
engine: RenderManifestEngine;
|
|
45
|
-
canvas: RenderManifestCanvas;
|
|
46
|
-
durationFrames: number;
|
|
47
|
-
backgroundColor?: string;
|
|
48
|
-
overlays: JsonValue[];
|
|
49
|
-
assets?: RenderManifestAsset[];
|
|
50
|
-
metadata?: JsonObject;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export type RenderManifestIntegrity = {
|
|
54
|
-
canonicalPayloadHash: string;
|
|
55
|
-
sceneHash: string;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export type RenderManifestSignature = {
|
|
59
|
-
alg: typeof RENDER_MANIFEST_SIGNATURE_ALG;
|
|
60
|
-
keyId: string;
|
|
61
|
-
value: string;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export type VivipilotRenderManifestV1 = {
|
|
65
|
-
schema: typeof RENDER_MANIFEST_SCHEMA_VERSION;
|
|
66
|
-
manifestId: string;
|
|
67
|
-
generationId: string;
|
|
68
|
-
userId: string;
|
|
69
|
-
projectId?: string;
|
|
70
|
-
createdAt: string;
|
|
71
|
-
expiresAt?: string;
|
|
72
|
-
engineVersion: string;
|
|
73
|
-
entitlement: RenderManifestEntitlement;
|
|
74
|
-
billing: RenderManifestBilling;
|
|
75
|
-
render: RenderManifestRenderPayload;
|
|
76
|
-
integrity: RenderManifestIntegrity;
|
|
77
|
-
signature: RenderManifestSignature;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export type UnsignedVivipilotRenderManifestV1 = Omit<VivipilotRenderManifestV1, "signature">;
|
|
81
|
-
|
|
82
|
-
export type RenderManifestPublicKey = {
|
|
83
|
-
keyId: string;
|
|
84
|
-
publicKey: string;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
export type PublicKeyResolver =
|
|
88
|
-
| Record<string, string>
|
|
89
|
-
| RenderManifestPublicKey[]
|
|
90
|
-
| ((keyId: string) => string | undefined | Promise<string | undefined>);
|
|
91
|
-
|
|
92
|
-
export type SignRenderManifestOptions = {
|
|
93
|
-
keyId: string;
|
|
94
|
-
privateKey: CryptoKey | JsonWebKey | string;
|
|
95
|
-
crypto?: Crypto;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export type VerifyRenderManifestOptions = {
|
|
99
|
-
publicKeys: PublicKeyResolver;
|
|
100
|
-
crypto?: Crypto;
|
|
101
|
-
now?: Date;
|
|
102
|
-
checkExpiry?: boolean;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export type RenderManifestVerificationSuccess = {
|
|
106
|
-
ok: true;
|
|
107
|
-
manifest: VivipilotRenderManifestV1;
|
|
108
|
-
canonicalPayloadHash: string;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
export type RenderManifestVerificationFailure = {
|
|
112
|
-
ok: false;
|
|
113
|
-
reason:
|
|
114
|
-
| "invalid_shape"
|
|
115
|
-
| "invalid_canonical_payload_hash"
|
|
116
|
-
| "invalid_signature"
|
|
117
|
-
| "missing_public_key"
|
|
118
|
-
| "expired"
|
|
119
|
-
| "crypto_unavailable"
|
|
120
|
-
| "invalid_key";
|
|
121
|
-
message: string;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export type RenderManifestVerificationResult =
|
|
125
|
-
| RenderManifestVerificationSuccess
|
|
126
|
-
| RenderManifestVerificationFailure;
|
|
127
|
-
|
|
128
|
-
const ED25519_ALGORITHM: Algorithm = { name: RENDER_MANIFEST_SIGNATURE_ALG };
|
|
129
|
-
const HEX_SHA256_PATTERN = /^[a-f0-9]{64}$/i;
|
|
130
|
-
|
|
131
|
-
function fail(reason: RenderManifestVerificationFailure["reason"], message: string): RenderManifestVerificationFailure {
|
|
132
|
-
return { ok: false, reason, message };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
136
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function isNonEmptyString(value: unknown): value is string {
|
|
140
|
-
return typeof value === "string" && value.length > 0;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function isFinitePositiveNumber(value: unknown): value is number {
|
|
144
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function assertJsonCompatible(value: unknown, path = "$"): asserts value is JsonValue {
|
|
148
|
-
if (value === null) return;
|
|
149
|
-
const kind = typeof value;
|
|
150
|
-
if (kind === "string" || kind === "boolean") return;
|
|
151
|
-
if (kind === "number") {
|
|
152
|
-
if (!Number.isFinite(value)) throw new Error(`Non-finite number at ${path}`);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (Array.isArray(value)) {
|
|
156
|
-
value.forEach((item, index) => assertJsonCompatible(item, `${path}[${index}]`));
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
if (kind === "object") {
|
|
160
|
-
for (const [key, entryValue] of Object.entries(value as Record<string, unknown>)) {
|
|
161
|
-
if (entryValue === undefined) throw new Error(`Undefined value at ${path}.${key}`);
|
|
162
|
-
assertJsonCompatible(entryValue, `${path}.${key}`);
|
|
163
|
-
}
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
throw new Error(`Unsupported JSON value at ${path}`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function canonicalizeJson(value: unknown): string {
|
|
170
|
-
assertJsonCompatible(value);
|
|
171
|
-
|
|
172
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
173
|
-
if (Array.isArray(value)) return `[${value.map((item) => canonicalizeJson(item)).join(",")}]`;
|
|
174
|
-
|
|
175
|
-
const entries = Object.entries(value)
|
|
176
|
-
.filter(([, entryValue]) => entryValue !== undefined)
|
|
177
|
-
.sort(([left], [right]) => left.localeCompare(right));
|
|
178
|
-
|
|
179
|
-
return `{${entries
|
|
180
|
-
.map(([key, entryValue]) => `${JSON.stringify(key)}:${canonicalizeJson(entryValue)}`)
|
|
181
|
-
.join(",")}}`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function utf8ToBytes(value: string): Uint8Array {
|
|
185
|
-
return new TextEncoder().encode(value);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function bytesToBinary(bytes: Uint8Array): string {
|
|
189
|
-
const chunkSize = 0x8000;
|
|
190
|
-
let binary = "";
|
|
191
|
-
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
192
|
-
const chunk = bytes.slice(offset, offset + chunkSize);
|
|
193
|
-
binary += String.fromCharCode(...chunk);
|
|
194
|
-
}
|
|
195
|
-
return binary;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function bytesToBase64Url(bytes: Uint8Array): string {
|
|
199
|
-
return btoa(bytesToBinary(bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function base64UrlToBytes(value: string): Uint8Array {
|
|
203
|
-
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
204
|
-
const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), "=");
|
|
205
|
-
const binary = atob(padded);
|
|
206
|
-
const bytes = new Uint8Array(binary.length);
|
|
207
|
-
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
208
|
-
return bytes;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
|
212
|
-
return (bytes.buffer as ArrayBuffer).slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function getCrypto(cryptoImpl?: Crypto): Crypto | null {
|
|
216
|
-
return cryptoImpl ?? globalThis.crypto ?? null;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async function sha256Bytes(value: string | Uint8Array, cryptoImpl?: Crypto): Promise<Uint8Array> {
|
|
220
|
-
const cryptoRuntime = getCrypto(cryptoImpl);
|
|
221
|
-
if (!cryptoRuntime?.subtle) throw new Error("crypto_unavailable");
|
|
222
|
-
const input = typeof value === "string" ? utf8ToBytes(value) : value;
|
|
223
|
-
const digest = await cryptoRuntime.subtle.digest("SHA-256", input as any);
|
|
224
|
-
return new Uint8Array(digest);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export async function sha256Hex(value: string | Uint8Array, cryptoImpl?: Crypto): Promise<string> {
|
|
228
|
-
const bytes = await sha256Bytes(value, cryptoImpl);
|
|
229
|
-
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function stripSignature(manifest: VivipilotRenderManifestV1 | UnsignedVivipilotRenderManifestV1): UnsignedVivipilotRenderManifestV1 {
|
|
233
|
-
const unsigned = { ...(manifest as VivipilotRenderManifestV1) };
|
|
234
|
-
delete (unsigned as Partial<VivipilotRenderManifestV1>).signature;
|
|
235
|
-
return unsigned;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function payloadForCanonicalHash(manifest: VivipilotRenderManifestV1 | UnsignedVivipilotRenderManifestV1): UnsignedVivipilotRenderManifestV1 {
|
|
239
|
-
const unsigned = stripSignature(manifest);
|
|
240
|
-
return {
|
|
241
|
-
...unsigned,
|
|
242
|
-
integrity: {
|
|
243
|
-
...unsigned.integrity,
|
|
244
|
-
canonicalPayloadHash: "",
|
|
245
|
-
},
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export function canonicalizeManifestForSigning(manifest: VivipilotRenderManifestV1 | UnsignedVivipilotRenderManifestV1): string {
|
|
250
|
-
return canonicalizeJson(stripSignature(manifest));
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export async function computeCanonicalPayloadHash(
|
|
254
|
-
manifest: VivipilotRenderManifestV1 | UnsignedVivipilotRenderManifestV1,
|
|
255
|
-
cryptoImpl?: Crypto,
|
|
256
|
-
): Promise<string> {
|
|
257
|
-
return sha256Hex(canonicalizeJson(payloadForCanonicalHash(manifest)), cryptoImpl);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export async function withCanonicalPayloadHash(
|
|
261
|
-
manifest: UnsignedVivipilotRenderManifestV1,
|
|
262
|
-
cryptoImpl?: Crypto,
|
|
263
|
-
): Promise<UnsignedVivipilotRenderManifestV1> {
|
|
264
|
-
const canonicalPayloadHash = await computeCanonicalPayloadHash(manifest, cryptoImpl);
|
|
265
|
-
return {
|
|
266
|
-
...manifest,
|
|
267
|
-
integrity: {
|
|
268
|
-
...manifest.integrity,
|
|
269
|
-
canonicalPayloadHash,
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
async function importPrivateKey(privateKey: CryptoKey | JsonWebKey | string, cryptoRuntime: Crypto): Promise<CryptoKey> {
|
|
275
|
-
if (privateKey instanceof CryptoKey) return privateKey;
|
|
276
|
-
if (typeof privateKey === "string") {
|
|
277
|
-
return cryptoRuntime.subtle.importKey(
|
|
278
|
-
"pkcs8",
|
|
279
|
-
toArrayBuffer(base64UrlToBytes(privateKey)),
|
|
280
|
-
ED25519_ALGORITHM,
|
|
281
|
-
false,
|
|
282
|
-
["sign"],
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
return cryptoRuntime.subtle.importKey("jwk", privateKey, ED25519_ALGORITHM, false, ["sign"]);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function importPublicKey(publicKey: CryptoKey | JsonWebKey | string, cryptoRuntime: Crypto): Promise<CryptoKey> {
|
|
289
|
-
if (publicKey instanceof CryptoKey) return publicKey;
|
|
290
|
-
if (typeof publicKey === "string") {
|
|
291
|
-
return cryptoRuntime.subtle.importKey(
|
|
292
|
-
"raw",
|
|
293
|
-
toArrayBuffer(base64UrlToBytes(publicKey)),
|
|
294
|
-
ED25519_ALGORITHM,
|
|
295
|
-
false,
|
|
296
|
-
["verify"],
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
return cryptoRuntime.subtle.importKey("jwk", publicKey, ED25519_ALGORITHM, false, ["verify"]);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export async function signRenderManifest(
|
|
303
|
-
manifest: UnsignedVivipilotRenderManifestV1,
|
|
304
|
-
options: SignRenderManifestOptions,
|
|
305
|
-
): Promise<VivipilotRenderManifestV1> {
|
|
306
|
-
const cryptoRuntime = getCrypto(options.crypto);
|
|
307
|
-
if (!cryptoRuntime?.subtle) throw new Error("crypto_unavailable");
|
|
308
|
-
|
|
309
|
-
const preparedManifest = await withCanonicalPayloadHash(manifest, cryptoRuntime);
|
|
310
|
-
const signingPayload = utf8ToBytes(canonicalizeManifestForSigning(preparedManifest));
|
|
311
|
-
const privateKey = await importPrivateKey(options.privateKey, cryptoRuntime);
|
|
312
|
-
const signature = await cryptoRuntime.subtle.sign(ED25519_ALGORITHM, privateKey, signingPayload as any);
|
|
313
|
-
|
|
314
|
-
return {
|
|
315
|
-
...preparedManifest,
|
|
316
|
-
signature: {
|
|
317
|
-
alg: RENDER_MANIFEST_SIGNATURE_ALG,
|
|
318
|
-
keyId: options.keyId,
|
|
319
|
-
value: bytesToBase64Url(new Uint8Array(signature)),
|
|
320
|
-
},
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function validateAssetShape(asset: unknown): boolean {
|
|
325
|
-
if (!isRecord(asset)) return false;
|
|
326
|
-
return isNonEmptyString(asset.assetId)
|
|
327
|
-
&& isNonEmptyString(asset.url)
|
|
328
|
-
&& isNonEmptyString(asset.sha256)
|
|
329
|
-
&& HEX_SHA256_PATTERN.test(asset.sha256)
|
|
330
|
-
&& isNonEmptyString(asset.mimeType)
|
|
331
|
-
&& isFinitePositiveNumber(asset.byteLength);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
export function validateRenderManifestShape(value: unknown): RenderManifestVerificationResult {
|
|
335
|
-
if (!isRecord(value)) return fail("invalid_shape", "Manifest must be an object.");
|
|
336
|
-
if (value.schema !== RENDER_MANIFEST_SCHEMA_VERSION) return fail("invalid_shape", "Unsupported render manifest schema.");
|
|
337
|
-
if (!isNonEmptyString(value.manifestId)) return fail("invalid_shape", "manifestId is required.");
|
|
338
|
-
if (!isNonEmptyString(value.generationId)) return fail("invalid_shape", "generationId is required.");
|
|
339
|
-
if (!isNonEmptyString(value.userId)) return fail("invalid_shape", "userId is required.");
|
|
340
|
-
if (!isNonEmptyString(value.createdAt) || Number.isNaN(Date.parse(value.createdAt))) return fail("invalid_shape", "createdAt must be an ISO date string.");
|
|
341
|
-
if (value.expiresAt !== undefined && (!isNonEmptyString(value.expiresAt) || Number.isNaN(Date.parse(value.expiresAt)))) {
|
|
342
|
-
return fail("invalid_shape", "expiresAt must be an ISO date string when provided.");
|
|
343
|
-
}
|
|
344
|
-
if (!isNonEmptyString(value.engineVersion)) return fail("invalid_shape", "engineVersion is required.");
|
|
345
|
-
|
|
346
|
-
if (!isRecord(value.entitlement)) return fail("invalid_shape", "entitlement is required.");
|
|
347
|
-
if (!isRecord(value.billing)) return fail("invalid_shape", "billing is required.");
|
|
348
|
-
if (!isNonEmptyString(value.billing.creditTransactionId)) return fail("invalid_shape", "billing.creditTransactionId is required.");
|
|
349
|
-
if (!isFinitePositiveNumber(value.billing.creditsCharged)) return fail("invalid_shape", "billing.creditsCharged must be positive.");
|
|
350
|
-
if (!isNonEmptyString(value.billing.idempotencyKey)) return fail("invalid_shape", "billing.idempotencyKey is required.");
|
|
351
|
-
if (!isNonEmptyString(value.billing.creditSource)) return fail("invalid_shape", "billing.creditSource is required.");
|
|
352
|
-
|
|
353
|
-
if (!isRecord(value.render)) return fail("invalid_shape", "render payload is required.");
|
|
354
|
-
if (!isRecord(value.render.canvas)) return fail("invalid_shape", "render.canvas is required.");
|
|
355
|
-
if (!isFinitePositiveNumber(value.render.canvas.width) || !isFinitePositiveNumber(value.render.canvas.height) || !isFinitePositiveNumber(value.render.canvas.fps)) {
|
|
356
|
-
return fail("invalid_shape", "render.canvas dimensions and fps must be positive.");
|
|
357
|
-
}
|
|
358
|
-
if (!isFinitePositiveNumber(value.render.durationFrames)) return fail("invalid_shape", "render.durationFrames must be positive.");
|
|
359
|
-
if (!Array.isArray(value.render.overlays)) return fail("invalid_shape", "render.overlays must be an array.");
|
|
360
|
-
if (value.render.assets !== undefined && (!Array.isArray(value.render.assets) || !value.render.assets.every(validateAssetShape))) {
|
|
361
|
-
return fail("invalid_shape", "render.assets must contain immutable asset references with SHA-256 hashes.");
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (!isRecord(value.integrity)) return fail("invalid_shape", "integrity is required.");
|
|
365
|
-
if (!isNonEmptyString(value.integrity.canonicalPayloadHash) || !HEX_SHA256_PATTERN.test(value.integrity.canonicalPayloadHash)) {
|
|
366
|
-
return fail("invalid_shape", "integrity.canonicalPayloadHash must be a SHA-256 hex digest.");
|
|
367
|
-
}
|
|
368
|
-
if (!isNonEmptyString(value.integrity.sceneHash) || !HEX_SHA256_PATTERN.test(value.integrity.sceneHash)) {
|
|
369
|
-
return fail("invalid_shape", "integrity.sceneHash must be a SHA-256 hex digest.");
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (!isRecord(value.signature)) return fail("invalid_shape", "signature is required.");
|
|
373
|
-
if (value.signature.alg !== RENDER_MANIFEST_SIGNATURE_ALG) return fail("invalid_shape", "Unsupported signature algorithm.");
|
|
374
|
-
if (!isNonEmptyString(value.signature.keyId)) return fail("invalid_shape", "signature.keyId is required.");
|
|
375
|
-
if (!isNonEmptyString(value.signature.value)) return fail("invalid_shape", "signature.value is required.");
|
|
376
|
-
|
|
377
|
-
return {
|
|
378
|
-
ok: true,
|
|
379
|
-
manifest: value as VivipilotRenderManifestV1,
|
|
380
|
-
canonicalPayloadHash: value.integrity.canonicalPayloadHash,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
async function resolvePublicKey(keyId: string, resolver: PublicKeyResolver): Promise<string | undefined> {
|
|
385
|
-
if (typeof resolver === "function") return resolver(keyId);
|
|
386
|
-
if (Array.isArray(resolver)) return resolver.find((entry) => entry.keyId === keyId)?.publicKey;
|
|
387
|
-
return resolver[keyId];
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
export function isManifestExpired(manifest: VivipilotRenderManifestV1, now = new Date()): boolean {
|
|
391
|
-
return typeof manifest.expiresAt === "string" && Date.parse(manifest.expiresAt) <= now.getTime();
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export async function verifyRenderManifest(
|
|
395
|
-
value: unknown,
|
|
396
|
-
options: VerifyRenderManifestOptions,
|
|
397
|
-
): Promise<RenderManifestVerificationResult> {
|
|
398
|
-
const shape = validateRenderManifestShape(value);
|
|
399
|
-
if (!shape.ok) return shape;
|
|
400
|
-
|
|
401
|
-
const manifest = shape.manifest;
|
|
402
|
-
const checkExpiry = options.checkExpiry ?? true;
|
|
403
|
-
if (checkExpiry && isManifestExpired(manifest, options.now ?? new Date())) {
|
|
404
|
-
return fail("expired", "Render manifest has expired.");
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
let canonicalPayloadHash: string;
|
|
408
|
-
try {
|
|
409
|
-
canonicalPayloadHash = await computeCanonicalPayloadHash(manifest, options.crypto);
|
|
410
|
-
} catch (error) {
|
|
411
|
-
return fail("crypto_unavailable", error instanceof Error ? error.message : "Unable to hash manifest payload.");
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (canonicalPayloadHash !== manifest.integrity.canonicalPayloadHash) {
|
|
415
|
-
return fail("invalid_canonical_payload_hash", "Manifest canonical payload hash does not match payload.");
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const publicKeyMaterial = await resolvePublicKey(manifest.signature.keyId, options.publicKeys);
|
|
419
|
-
if (!publicKeyMaterial) return fail("missing_public_key", `No public key registered for keyId ${manifest.signature.keyId}.`);
|
|
420
|
-
|
|
421
|
-
const cryptoRuntime = getCrypto(options.crypto);
|
|
422
|
-
if (!cryptoRuntime?.subtle) return fail("crypto_unavailable", "WebCrypto subtle crypto is unavailable.");
|
|
423
|
-
|
|
424
|
-
try {
|
|
425
|
-
const publicKey = await importPublicKey(publicKeyMaterial, cryptoRuntime);
|
|
426
|
-
const isValid = await cryptoRuntime.subtle.verify(
|
|
427
|
-
ED25519_ALGORITHM,
|
|
428
|
-
publicKey,
|
|
429
|
-
toArrayBuffer(base64UrlToBytes(manifest.signature.value)),
|
|
430
|
-
utf8ToBytes(canonicalizeManifestForSigning(manifest)) as any,
|
|
431
|
-
);
|
|
432
|
-
if (!isValid) return fail("invalid_signature", "Manifest signature is invalid.");
|
|
433
|
-
} catch (error) {
|
|
434
|
-
return fail("invalid_key", error instanceof Error ? error.message : "Unable to import or verify public key.");
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return { ok: true, manifest, canonicalPayloadHash };
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
export async function verifyAssetDigest(asset: RenderManifestAsset, bytes: Uint8Array, cryptoImpl?: Crypto): Promise<boolean> {
|
|
441
|
-
if (asset.byteLength !== bytes.byteLength) return false;
|
|
442
|
-
const digest = await sha256Hex(bytes, cryptoImpl);
|
|
443
|
-
return digest.toLowerCase() === asset.sha256.toLowerCase();
|
|
444
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"lib": ["ES2020", "DOM"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"noImplicitAny": true,
|
|
9
|
-
"strictNullChecks": true,
|
|
10
|
-
"noUnusedLocals": true,
|
|
11
|
-
"noUnusedParameters": true,
|
|
12
|
-
"noImplicitReturns": true,
|
|
13
|
-
"noFallthroughCasesInSwitch": true,
|
|
14
|
-
"esModuleInterop": true,
|
|
15
|
-
"skipLibCheck": true,
|
|
16
|
-
"forceConsistentCasingInFileNames": true,
|
|
17
|
-
"declaration": true,
|
|
18
|
-
"declarationMap": true,
|
|
19
|
-
"sourceMap": true,
|
|
20
|
-
"outDir": "./dist",
|
|
21
|
-
"rootDir": "./src",
|
|
22
|
-
"isolatedModules": true
|
|
23
|
-
},
|
|
24
|
-
"include": ["src/**/*"],
|
|
25
|
-
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
|
26
|
-
}
|