@fluxpointstudios/orynq-sdk-midnight-prover 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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +78 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +89 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/linking/cardano-anchor-link.d.ts +250 -0
  7. package/dist/linking/cardano-anchor-link.d.ts.map +1 -0
  8. package/dist/linking/cardano-anchor-link.js +447 -0
  9. package/dist/linking/cardano-anchor-link.js.map +1 -0
  10. package/dist/linking/index.d.ts +33 -0
  11. package/dist/linking/index.d.ts.map +1 -0
  12. package/dist/linking/index.js +31 -0
  13. package/dist/linking/index.js.map +1 -0
  14. package/dist/linking/proof-publication.d.ts +217 -0
  15. package/dist/linking/proof-publication.d.ts.map +1 -0
  16. package/dist/linking/proof-publication.js +385 -0
  17. package/dist/linking/proof-publication.js.map +1 -0
  18. package/dist/midnight/index.d.ts +30 -0
  19. package/dist/midnight/index.d.ts.map +1 -0
  20. package/dist/midnight/index.js +27 -0
  21. package/dist/midnight/index.js.map +1 -0
  22. package/dist/midnight/proof-server-client.d.ts +236 -0
  23. package/dist/midnight/proof-server-client.d.ts.map +1 -0
  24. package/dist/midnight/proof-server-client.js +422 -0
  25. package/dist/midnight/proof-server-client.js.map +1 -0
  26. package/dist/midnight/public-inputs.d.ts +134 -0
  27. package/dist/midnight/public-inputs.d.ts.map +1 -0
  28. package/dist/midnight/public-inputs.js +338 -0
  29. package/dist/midnight/public-inputs.js.map +1 -0
  30. package/dist/midnight/witness-builder.d.ts +119 -0
  31. package/dist/midnight/witness-builder.d.ts.map +1 -0
  32. package/dist/midnight/witness-builder.js +238 -0
  33. package/dist/midnight/witness-builder.js.map +1 -0
  34. package/dist/proofs/hash-chain-proof.d.ts +171 -0
  35. package/dist/proofs/hash-chain-proof.d.ts.map +1 -0
  36. package/dist/proofs/hash-chain-proof.js +437 -0
  37. package/dist/proofs/hash-chain-proof.js.map +1 -0
  38. package/dist/proofs/index.d.ts +35 -0
  39. package/dist/proofs/index.d.ts.map +1 -0
  40. package/dist/proofs/index.js +34 -0
  41. package/dist/proofs/index.js.map +1 -0
  42. package/dist/proofs/policy-compliance-proof.d.ts +165 -0
  43. package/dist/proofs/policy-compliance-proof.d.ts.map +1 -0
  44. package/dist/proofs/policy-compliance-proof.js +514 -0
  45. package/dist/proofs/policy-compliance-proof.js.map +1 -0
  46. package/dist/proofs/selective-disclosure.d.ts +213 -0
  47. package/dist/proofs/selective-disclosure.d.ts.map +1 -0
  48. package/dist/proofs/selective-disclosure.js +629 -0
  49. package/dist/proofs/selective-disclosure.js.map +1 -0
  50. package/dist/prover-interface.d.ts +288 -0
  51. package/dist/prover-interface.d.ts.map +1 -0
  52. package/dist/prover-interface.js +114 -0
  53. package/dist/prover-interface.js.map +1 -0
  54. package/dist/prover.d.ts +163 -0
  55. package/dist/prover.d.ts.map +1 -0
  56. package/dist/prover.js +417 -0
  57. package/dist/prover.js.map +1 -0
  58. package/dist/types.d.ts +410 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +128 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +59 -0
  63. package/src/__tests__/hash-chain-proof.test.ts +709 -0
  64. package/src/__tests__/midnight-prover.test.ts +716 -0
  65. package/src/__tests__/policy-compliance.test.ts +567 -0
  66. package/src/__tests__/proof-publication.test.ts +644 -0
  67. package/src/__tests__/selective-disclosure.test.ts +921 -0
  68. package/src/index.ts +260 -0
  69. package/src/linking/cardano-anchor-link.ts +682 -0
  70. package/src/linking/index.ts +58 -0
  71. package/src/linking/proof-publication.ts +557 -0
  72. package/src/midnight/index.ts +73 -0
  73. package/src/midnight/proof-server-client.ts +595 -0
  74. package/src/midnight/public-inputs.ts +590 -0
  75. package/src/midnight/witness-builder.ts +341 -0
  76. package/src/proofs/hash-chain-proof.ts +610 -0
  77. package/src/proofs/index.ts +77 -0
  78. package/src/proofs/policy-compliance-proof.ts +717 -0
  79. package/src/proofs/selective-disclosure.ts +839 -0
  80. package/src/prover-interface.ts +410 -0
  81. package/src/prover.ts +537 -0
  82. package/src/types.ts +551 -0
@@ -0,0 +1,590 @@
1
+ /**
2
+ * @fileoverview Public inputs builder for ZK proofs.
3
+ *
4
+ * Location: packages/midnight-prover/src/midnight/public-inputs.ts
5
+ *
6
+ * Summary:
7
+ * This module handles the construction and serialization of public inputs
8
+ * for various ZK proof types. Public inputs are values that are visible
9
+ * to verifiers and form part of the proof statement.
10
+ *
11
+ * Usage:
12
+ * Used by proof generators to construct the public component of proofs.
13
+ * Public inputs are included in the proof and can be verified on-chain.
14
+ *
15
+ * Related files:
16
+ * - hash-chain-proof.ts: Uses buildPublicInputs for hash-chain proofs
17
+ * - witness-builder.ts: Builds the corresponding private witness
18
+ * - types.ts: Type definitions for public inputs
19
+ */
20
+
21
+ import type {
22
+ ProofType,
23
+ HashChainPublicInputs,
24
+ PolicyPublicInputs,
25
+ AttestationPublicInputs,
26
+ DisclosurePublicInputs,
27
+ InferencePublicInputs,
28
+ AnyPublicInputs,
29
+ } from "../types.js";
30
+
31
+ import type { TeeType } from "@fluxpointstudios/orynq-sdk-attestor";
32
+
33
+ import {
34
+ hexToBytes,
35
+ bytesToHex,
36
+ } from "@fluxpointstudios/orynq-sdk-core/utils";
37
+
38
+ // =============================================================================
39
+ // TYPES
40
+ // =============================================================================
41
+
42
+ /**
43
+ * Input data for building hash-chain public inputs.
44
+ */
45
+ export interface HashChainPublicInputData {
46
+ rootHash: string;
47
+ eventCount: number;
48
+ cardanoAnchorTxHash: string;
49
+ }
50
+
51
+ /**
52
+ * Input data for building policy compliance public inputs.
53
+ */
54
+ export interface PolicyPublicInputData {
55
+ promptHash: string;
56
+ policyId: string;
57
+ policyVersion: string;
58
+ compliant: boolean;
59
+ cardanoAnchorTxHash: string;
60
+ }
61
+
62
+ /**
63
+ * Input data for building attestation public inputs.
64
+ */
65
+ export interface AttestationPublicInputData {
66
+ teeType: TeeType;
67
+ measurementMatch: boolean;
68
+ boundHash: string;
69
+ cardanoAnchorTxHash: string;
70
+ }
71
+
72
+ /**
73
+ * Input data for building disclosure public inputs.
74
+ */
75
+ export interface DisclosurePublicInputData {
76
+ spanHash: string;
77
+ merkleRoot: string;
78
+ cardanoAnchorTxHash: string;
79
+ }
80
+
81
+ /**
82
+ * Input data for building inference public inputs.
83
+ */
84
+ export interface InferencePublicInputData {
85
+ modelWeightDigest: string;
86
+ inputHash: string;
87
+ outputHash: string;
88
+ paramsHash: string;
89
+ cardanoAnchorTxHash: string;
90
+ }
91
+
92
+ /**
93
+ * Union type for all public input data types.
94
+ */
95
+ export type AnyPublicInputData =
96
+ | { type: "hash-chain"; data: HashChainPublicInputData }
97
+ | { type: "policy-compliance"; data: PolicyPublicInputData }
98
+ | { type: "attestation-valid"; data: AttestationPublicInputData }
99
+ | { type: "selective-disclosure"; data: DisclosurePublicInputData }
100
+ | { type: "zkml-inference"; data: InferencePublicInputData };
101
+
102
+ // =============================================================================
103
+ // MAIN FUNCTION
104
+ // =============================================================================
105
+
106
+ /**
107
+ * Build public inputs for a ZK proof.
108
+ *
109
+ * This function constructs the public input structure for a given proof type.
110
+ * Public inputs are the values that verifiers can see and check against
111
+ * on-chain commitments (e.g., the Cardano anchor transaction).
112
+ *
113
+ * @param type - The type of proof to build public inputs for
114
+ * @param data - The input data for constructing public inputs
115
+ * @returns The constructed public inputs
116
+ * @throws Error if the data is invalid for the proof type
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * const publicInputs = buildPublicInputs("hash-chain", {
121
+ * rootHash: "abc123...",
122
+ * eventCount: 42,
123
+ * cardanoAnchorTxHash: "def456...",
124
+ * });
125
+ * ```
126
+ */
127
+ export function buildPublicInputs(
128
+ type: "hash-chain",
129
+ data: HashChainPublicInputData
130
+ ): HashChainPublicInputs;
131
+ export function buildPublicInputs(
132
+ type: "policy-compliance",
133
+ data: PolicyPublicInputData
134
+ ): PolicyPublicInputs;
135
+ export function buildPublicInputs(
136
+ type: "attestation-valid",
137
+ data: AttestationPublicInputData
138
+ ): AttestationPublicInputs;
139
+ export function buildPublicInputs(
140
+ type: "selective-disclosure",
141
+ data: DisclosurePublicInputData
142
+ ): DisclosurePublicInputs;
143
+ export function buildPublicInputs(
144
+ type: "zkml-inference",
145
+ data: InferencePublicInputData
146
+ ): InferencePublicInputs;
147
+ export function buildPublicInputs(
148
+ type: ProofType,
149
+ data: unknown
150
+ ): AnyPublicInputs;
151
+ export function buildPublicInputs(
152
+ type: ProofType,
153
+ data: unknown
154
+ ): AnyPublicInputs {
155
+ switch (type) {
156
+ case "hash-chain":
157
+ return buildHashChainPublicInputs(data as HashChainPublicInputData);
158
+
159
+ case "policy-compliance":
160
+ return buildPolicyPublicInputs(data as PolicyPublicInputData);
161
+
162
+ case "attestation-valid":
163
+ return buildAttestationPublicInputs(data as AttestationPublicInputData);
164
+
165
+ case "selective-disclosure":
166
+ return buildDisclosurePublicInputs(data as DisclosurePublicInputData);
167
+
168
+ case "zkml-inference":
169
+ return buildInferencePublicInputs(data as InferencePublicInputData);
170
+
171
+ default:
172
+ throw new Error(`Unknown proof type: ${type}`);
173
+ }
174
+ }
175
+
176
+ // =============================================================================
177
+ // TYPE-SPECIFIC BUILDERS
178
+ // =============================================================================
179
+
180
+ /**
181
+ * Build public inputs for hash-chain proof.
182
+ */
183
+ function buildHashChainPublicInputs(
184
+ data: HashChainPublicInputData
185
+ ): HashChainPublicInputs {
186
+ validateHexHash(data.rootHash, "rootHash");
187
+ validateHexHash(data.cardanoAnchorTxHash, "cardanoAnchorTxHash");
188
+ validatePositiveInteger(data.eventCount, "eventCount");
189
+
190
+ return {
191
+ rootHash: normalizeHash(data.rootHash),
192
+ eventCount: data.eventCount,
193
+ cardanoAnchorTxHash: normalizeHash(data.cardanoAnchorTxHash),
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Build public inputs for policy compliance proof.
199
+ */
200
+ function buildPolicyPublicInputs(
201
+ data: PolicyPublicInputData
202
+ ): PolicyPublicInputs {
203
+ validateHexHash(data.promptHash, "promptHash");
204
+ validateHexHash(data.cardanoAnchorTxHash, "cardanoAnchorTxHash");
205
+ validateNonEmptyString(data.policyId, "policyId");
206
+ validateNonEmptyString(data.policyVersion, "policyVersion");
207
+
208
+ return {
209
+ promptHash: normalizeHash(data.promptHash),
210
+ policyId: data.policyId,
211
+ policyVersion: data.policyVersion,
212
+ compliant: Boolean(data.compliant),
213
+ cardanoAnchorTxHash: normalizeHash(data.cardanoAnchorTxHash),
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Build public inputs for attestation validity proof.
219
+ */
220
+ function buildAttestationPublicInputs(
221
+ data: AttestationPublicInputData
222
+ ): AttestationPublicInputs {
223
+ validateHexHash(data.boundHash, "boundHash");
224
+ validateHexHash(data.cardanoAnchorTxHash, "cardanoAnchorTxHash");
225
+ validateTeeType(data.teeType);
226
+
227
+ return {
228
+ teeType: data.teeType,
229
+ measurementMatch: Boolean(data.measurementMatch),
230
+ boundHash: normalizeHash(data.boundHash),
231
+ cardanoAnchorTxHash: normalizeHash(data.cardanoAnchorTxHash),
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Build public inputs for selective disclosure proof.
237
+ */
238
+ function buildDisclosurePublicInputs(
239
+ data: DisclosurePublicInputData
240
+ ): DisclosurePublicInputs {
241
+ validateHexHash(data.spanHash, "spanHash");
242
+ validateHexHash(data.merkleRoot, "merkleRoot");
243
+ validateHexHash(data.cardanoAnchorTxHash, "cardanoAnchorTxHash");
244
+
245
+ return {
246
+ spanHash: normalizeHash(data.spanHash),
247
+ merkleRoot: normalizeHash(data.merkleRoot),
248
+ cardanoAnchorTxHash: normalizeHash(data.cardanoAnchorTxHash),
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Build public inputs for zkML inference proof.
254
+ */
255
+ function buildInferencePublicInputs(
256
+ data: InferencePublicInputData
257
+ ): InferencePublicInputs {
258
+ validateHexHash(data.modelWeightDigest, "modelWeightDigest");
259
+ validateHexHash(data.inputHash, "inputHash");
260
+ validateHexHash(data.outputHash, "outputHash");
261
+ validateHexHash(data.paramsHash, "paramsHash");
262
+ validateHexHash(data.cardanoAnchorTxHash, "cardanoAnchorTxHash");
263
+
264
+ return {
265
+ modelWeightDigest: normalizeHash(data.modelWeightDigest),
266
+ inputHash: normalizeHash(data.inputHash),
267
+ outputHash: normalizeHash(data.outputHash),
268
+ paramsHash: normalizeHash(data.paramsHash),
269
+ cardanoAnchorTxHash: normalizeHash(data.cardanoAnchorTxHash),
270
+ };
271
+ }
272
+
273
+ // =============================================================================
274
+ // SERIALIZATION
275
+ // =============================================================================
276
+
277
+ /**
278
+ * Serialize public inputs to a binary format for circuit consumption.
279
+ *
280
+ * The binary format is:
281
+ * - 1 byte: proof type ID
282
+ * - N bytes: type-specific fields (fixed-size binary encoding)
283
+ *
284
+ * @param type - Proof type
285
+ * @param publicInputs - Public inputs to serialize
286
+ * @returns Serialized public inputs as Uint8Array
287
+ */
288
+ export function serializePublicInputs(
289
+ type: ProofType,
290
+ publicInputs: AnyPublicInputs
291
+ ): Uint8Array {
292
+ const typeId = getProofTypeId(type);
293
+
294
+ switch (type) {
295
+ case "hash-chain":
296
+ return serializeHashChainInputs(
297
+ typeId,
298
+ publicInputs as HashChainPublicInputs
299
+ );
300
+
301
+ case "policy-compliance":
302
+ return serializePolicyInputs(typeId, publicInputs as PolicyPublicInputs);
303
+
304
+ case "attestation-valid":
305
+ return serializeAttestationInputs(
306
+ typeId,
307
+ publicInputs as AttestationPublicInputs
308
+ );
309
+
310
+ case "selective-disclosure":
311
+ return serializeDisclosureInputs(
312
+ typeId,
313
+ publicInputs as DisclosurePublicInputs
314
+ );
315
+
316
+ case "zkml-inference":
317
+ return serializeInferenceInputs(
318
+ typeId,
319
+ publicInputs as InferencePublicInputs
320
+ );
321
+
322
+ default:
323
+ throw new Error(`Cannot serialize unknown proof type: ${type}`);
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Get a numeric ID for a proof type.
329
+ */
330
+ function getProofTypeId(type: ProofType): number {
331
+ const typeIds: Record<ProofType, number> = {
332
+ "hash-chain": 1,
333
+ "policy-compliance": 2,
334
+ "attestation-valid": 3,
335
+ "selective-disclosure": 4,
336
+ "zkml-inference": 5,
337
+ };
338
+ return typeIds[type];
339
+ }
340
+
341
+ /**
342
+ * Serialize hash-chain public inputs.
343
+ * Format: 1 byte type + 32 bytes rootHash + 4 bytes eventCount + 32 bytes anchorTx
344
+ */
345
+ function serializeHashChainInputs(
346
+ typeId: number,
347
+ inputs: HashChainPublicInputs
348
+ ): Uint8Array {
349
+ const buffer = new Uint8Array(1 + 32 + 4 + 32);
350
+ const view = new DataView(buffer.buffer);
351
+
352
+ let offset = 0;
353
+ buffer[offset++] = typeId;
354
+
355
+ const rootHashBytes = hexToBytes(inputs.rootHash);
356
+ buffer.set(rootHashBytes, offset);
357
+ offset += 32;
358
+
359
+ view.setUint32(offset, inputs.eventCount, false);
360
+ offset += 4;
361
+
362
+ const anchorBytes = hexToBytes(inputs.cardanoAnchorTxHash);
363
+ buffer.set(anchorBytes, offset);
364
+
365
+ return buffer;
366
+ }
367
+
368
+ /**
369
+ * Serialize policy compliance public inputs.
370
+ */
371
+ function serializePolicyInputs(
372
+ typeId: number,
373
+ inputs: PolicyPublicInputs
374
+ ): Uint8Array {
375
+ // Variable length due to policyId and policyVersion strings
376
+ const policyIdBytes = new TextEncoder().encode(inputs.policyId);
377
+ const policyVersionBytes = new TextEncoder().encode(inputs.policyVersion);
378
+
379
+ const buffer = new Uint8Array(
380
+ 1 + 32 + 2 + policyIdBytes.length + 2 + policyVersionBytes.length + 1 + 32
381
+ );
382
+ const view = new DataView(buffer.buffer);
383
+
384
+ let offset = 0;
385
+ buffer[offset++] = typeId;
386
+
387
+ const promptHashBytes = hexToBytes(inputs.promptHash);
388
+ buffer.set(promptHashBytes, offset);
389
+ offset += 32;
390
+
391
+ view.setUint16(offset, policyIdBytes.length, false);
392
+ offset += 2;
393
+ buffer.set(policyIdBytes, offset);
394
+ offset += policyIdBytes.length;
395
+
396
+ view.setUint16(offset, policyVersionBytes.length, false);
397
+ offset += 2;
398
+ buffer.set(policyVersionBytes, offset);
399
+ offset += policyVersionBytes.length;
400
+
401
+ buffer[offset++] = inputs.compliant ? 1 : 0;
402
+
403
+ const anchorBytes = hexToBytes(inputs.cardanoAnchorTxHash);
404
+ buffer.set(anchorBytes, offset);
405
+
406
+ return buffer;
407
+ }
408
+
409
+ /**
410
+ * Serialize attestation public inputs.
411
+ */
412
+ function serializeAttestationInputs(
413
+ typeId: number,
414
+ inputs: AttestationPublicInputs
415
+ ): Uint8Array {
416
+ const teeTypeId = getTeeTypeId(inputs.teeType);
417
+ const buffer = new Uint8Array(1 + 1 + 1 + 32 + 32);
418
+
419
+ let offset = 0;
420
+ buffer[offset++] = typeId;
421
+ buffer[offset++] = teeTypeId;
422
+ buffer[offset++] = inputs.measurementMatch ? 1 : 0;
423
+
424
+ const boundHashBytes = hexToBytes(inputs.boundHash);
425
+ buffer.set(boundHashBytes, offset);
426
+ offset += 32;
427
+
428
+ const anchorBytes = hexToBytes(inputs.cardanoAnchorTxHash);
429
+ buffer.set(anchorBytes, offset);
430
+
431
+ return buffer;
432
+ }
433
+
434
+ /**
435
+ * Serialize disclosure public inputs.
436
+ */
437
+ function serializeDisclosureInputs(
438
+ typeId: number,
439
+ inputs: DisclosurePublicInputs
440
+ ): Uint8Array {
441
+ const buffer = new Uint8Array(1 + 32 + 32 + 32);
442
+
443
+ let offset = 0;
444
+ buffer[offset++] = typeId;
445
+
446
+ const spanHashBytes = hexToBytes(inputs.spanHash);
447
+ buffer.set(spanHashBytes, offset);
448
+ offset += 32;
449
+
450
+ const merkleRootBytes = hexToBytes(inputs.merkleRoot);
451
+ buffer.set(merkleRootBytes, offset);
452
+ offset += 32;
453
+
454
+ const anchorBytes = hexToBytes(inputs.cardanoAnchorTxHash);
455
+ buffer.set(anchorBytes, offset);
456
+
457
+ return buffer;
458
+ }
459
+
460
+ /**
461
+ * Serialize inference public inputs.
462
+ */
463
+ function serializeInferenceInputs(
464
+ typeId: number,
465
+ inputs: InferencePublicInputs
466
+ ): Uint8Array {
467
+ const buffer = new Uint8Array(1 + 32 + 32 + 32 + 32 + 32);
468
+
469
+ let offset = 0;
470
+ buffer[offset++] = typeId;
471
+
472
+ const modelBytes = hexToBytes(inputs.modelWeightDigest);
473
+ buffer.set(modelBytes, offset);
474
+ offset += 32;
475
+
476
+ const inputBytes = hexToBytes(inputs.inputHash);
477
+ buffer.set(inputBytes, offset);
478
+ offset += 32;
479
+
480
+ const outputBytes = hexToBytes(inputs.outputHash);
481
+ buffer.set(outputBytes, offset);
482
+ offset += 32;
483
+
484
+ const paramsBytes = hexToBytes(inputs.paramsHash);
485
+ buffer.set(paramsBytes, offset);
486
+ offset += 32;
487
+
488
+ const anchorBytes = hexToBytes(inputs.cardanoAnchorTxHash);
489
+ buffer.set(anchorBytes, offset);
490
+
491
+ return buffer;
492
+ }
493
+
494
+ /**
495
+ * Get numeric ID for TEE type.
496
+ * TeeType values: "sev-snp" | "tdx" | "sgx" | "nitro" | "gpu-cc"
497
+ */
498
+ function getTeeTypeId(teeType: TeeType): number {
499
+ const teeTypeIds: Record<TeeType, number> = {
500
+ "sev-snp": 1,
501
+ "tdx": 2,
502
+ "sgx": 3,
503
+ "nitro": 4,
504
+ "gpu-cc": 5,
505
+ };
506
+ return teeTypeIds[teeType] ?? 0;
507
+ }
508
+
509
+ // =============================================================================
510
+ // HASHING
511
+ // =============================================================================
512
+
513
+ /**
514
+ * Compute a hash commitment to public inputs.
515
+ * This can be used to create a compact reference to the full public inputs.
516
+ *
517
+ * @param type - Proof type
518
+ * @param publicInputs - Public inputs to hash
519
+ * @returns Promise resolving to the hash as a hex string
520
+ */
521
+ export async function hashPublicInputs(
522
+ type: ProofType,
523
+ publicInputs: AnyPublicInputs
524
+ ): Promise<string> {
525
+ const serialized = serializePublicInputs(type, publicInputs);
526
+ // Copy to a fresh ArrayBuffer to ensure compatibility with crypto.subtle.digest
527
+ const buffer = new ArrayBuffer(serialized.byteLength);
528
+ new Uint8Array(buffer).set(serialized);
529
+ const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
530
+ return bytesToHex(new Uint8Array(hashBuffer));
531
+ }
532
+
533
+ // =============================================================================
534
+ // VALIDATION HELPERS
535
+ // =============================================================================
536
+
537
+ /**
538
+ * Validate a hex hash string.
539
+ */
540
+ function validateHexHash(value: string, fieldName: string): void {
541
+ // Remove 0x prefix if present
542
+ const cleanHex = value.startsWith("0x") ? value.slice(2) : value;
543
+
544
+ if (!/^[0-9a-fA-F]{64}$/.test(cleanHex)) {
545
+ throw new Error(
546
+ `Invalid ${fieldName}: expected 64-character hex string, got "${value}"`
547
+ );
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Validate a positive integer.
553
+ */
554
+ function validatePositiveInteger(value: number, fieldName: string): void {
555
+ if (!Number.isInteger(value) || value < 0) {
556
+ throw new Error(
557
+ `Invalid ${fieldName}: expected non-negative integer, got ${value}`
558
+ );
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Validate a non-empty string.
564
+ */
565
+ function validateNonEmptyString(value: string, fieldName: string): void {
566
+ if (typeof value !== "string" || value.trim() === "") {
567
+ throw new Error(
568
+ `Invalid ${fieldName}: expected non-empty string, got "${value}"`
569
+ );
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Validate TEE type.
575
+ * TeeType values: "sev-snp" | "tdx" | "sgx" | "nitro" | "gpu-cc"
576
+ */
577
+ function validateTeeType(teeType: TeeType): void {
578
+ const validTypes: TeeType[] = ["sev-snp", "tdx", "sgx", "nitro", "gpu-cc"];
579
+ if (!validTypes.includes(teeType)) {
580
+ throw new Error(`Invalid teeType: "${teeType}"`);
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Normalize a hash to lowercase without 0x prefix.
586
+ */
587
+ function normalizeHash(hash: string): string {
588
+ const cleanHex = hash.startsWith("0x") ? hash.slice(2) : hash;
589
+ return cleanHex.toLowerCase();
590
+ }