@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,717 @@
1
+ /**
2
+ * @fileoverview Policy compliance proof generation and verification.
3
+ *
4
+ * Location: packages/midnight-prover/src/proofs/policy-compliance-proof.ts
5
+ *
6
+ * Summary:
7
+ * This module implements the PolicyComplianceProver class which generates ZK proofs
8
+ * demonstrating that content complies with a specified policy without revealing the
9
+ * actual content. The prover evaluates policy rules (blocklist, allowlist, regex,
10
+ * classifier) against content hashes and produces a proof of compliance.
11
+ *
12
+ * Usage:
13
+ * - Used by the MidnightProver to generate policy compliance proofs
14
+ * - Integrates with the types defined in ../types.ts
15
+ * - Binds proofs to Cardano anchor transactions for cross-chain verification
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { PolicyComplianceProver } from './policy-compliance-proof.js';
20
+ *
21
+ * const prover = new PolicyComplianceProver();
22
+ *
23
+ * const proof = await prover.generateProof({
24
+ * promptHash: 'abc123...',
25
+ * outputHash: 'def456...',
26
+ * policy: { id: 'policy-1', version: '1.0.0', rules: [...] },
27
+ * cardanoAnchorTxHash: 'txhash...',
28
+ * });
29
+ *
30
+ * const isValid = await prover.verifyProof(proof);
31
+ * ```
32
+ */
33
+
34
+ import {
35
+ sha256StringHex,
36
+ canonicalize,
37
+ } from "@fluxpointstudios/orynq-sdk-core/utils";
38
+
39
+ import type {
40
+ PolicyInput,
41
+ PolicyProof,
42
+ PolicyPublicInputs,
43
+ ContentPolicy,
44
+ PolicyRule,
45
+ } from "../types.js";
46
+
47
+ import {
48
+ MidnightProverError,
49
+ MidnightProverException,
50
+ } from "../types.js";
51
+
52
+ // =============================================================================
53
+ // TYPES
54
+ // =============================================================================
55
+
56
+ /**
57
+ * Result of evaluating a single policy rule.
58
+ */
59
+ export interface RuleEvaluationResult {
60
+ rule: PolicyRule;
61
+ passed: boolean;
62
+ message: string;
63
+ }
64
+
65
+ /**
66
+ * Result of evaluating all policy rules.
67
+ */
68
+ export interface PolicyEvaluationResult {
69
+ compliant: boolean;
70
+ ruleResults: RuleEvaluationResult[];
71
+ evaluationTimeMs: number;
72
+ }
73
+
74
+ /**
75
+ * Options for the PolicyComplianceProver.
76
+ */
77
+ export interface PolicyComplianceProverOptions {
78
+ /**
79
+ * Enable debug logging.
80
+ */
81
+ debug?: boolean;
82
+
83
+ /**
84
+ * Custom rule evaluators for extending policy rule types.
85
+ */
86
+ customEvaluators?: Map<string, RuleEvaluator>;
87
+ }
88
+
89
+ /**
90
+ * Function type for evaluating a policy rule.
91
+ */
92
+ export type RuleEvaluator = (
93
+ rule: PolicyRule,
94
+ promptHash: string,
95
+ outputHash: string
96
+ ) => Promise<RuleEvaluationResult>;
97
+
98
+ // =============================================================================
99
+ // DOMAIN PREFIXES
100
+ // =============================================================================
101
+
102
+ /**
103
+ * Domain separation prefixes for policy compliance proofs.
104
+ */
105
+ const POLICY_DOMAIN_PREFIXES = {
106
+ proof: "poi-prover:policy:v1|",
107
+ witness: "poi-prover:policy-witness:v1|",
108
+ publicInput: "poi-prover:policy-input:v1|",
109
+ } as const;
110
+
111
+ // =============================================================================
112
+ // DEFAULT RULE EVALUATORS
113
+ // =============================================================================
114
+
115
+ /**
116
+ * Default evaluator for blocklist rules.
117
+ * In ZK context, this simulates checking content hashes against a blocklist.
118
+ *
119
+ * @param rule - The blocklist rule to evaluate
120
+ * @param promptHash - Hash of the prompt content
121
+ * @param outputHash - Hash of the output content
122
+ * @returns Evaluation result
123
+ */
124
+ async function evaluateBlocklistRule(
125
+ rule: PolicyRule,
126
+ promptHash: string,
127
+ outputHash: string
128
+ ): Promise<RuleEvaluationResult> {
129
+ // In a real ZK implementation, this would use a Bloom filter or
130
+ // commitment scheme to check against the blocklist without revealing items.
131
+ // For mock purposes, we simulate by checking if hashes match blocklist patterns.
132
+
133
+ const blockedHashes = (rule.params.blockedHashes as string[] | undefined) ?? [];
134
+ const target = rule.target;
135
+
136
+ let passed = true;
137
+ let message = "Content does not match any blocked patterns";
138
+
139
+ if (target === "prompt" || target === "both") {
140
+ if (blockedHashes.includes(promptHash)) {
141
+ passed = false;
142
+ message = "Prompt matches blocked pattern";
143
+ }
144
+ }
145
+
146
+ if (passed && (target === "output" || target === "both")) {
147
+ if (blockedHashes.includes(outputHash)) {
148
+ passed = false;
149
+ message = "Output matches blocked pattern";
150
+ }
151
+ }
152
+
153
+ return { rule, passed, message };
154
+ }
155
+
156
+ /**
157
+ * Default evaluator for allowlist rules.
158
+ * Content must match at least one allowed pattern.
159
+ *
160
+ * @param rule - The allowlist rule to evaluate
161
+ * @param promptHash - Hash of the prompt content
162
+ * @param outputHash - Hash of the output content
163
+ * @returns Evaluation result
164
+ */
165
+ async function evaluateAllowlistRule(
166
+ rule: PolicyRule,
167
+ promptHash: string,
168
+ outputHash: string
169
+ ): Promise<RuleEvaluationResult> {
170
+ // In a real ZK implementation, this would verify membership in an
171
+ // allowed set using accumulator proofs or similar techniques.
172
+
173
+ const allowedHashes = (rule.params.allowedHashes as string[] | undefined) ?? [];
174
+ const requireAllMatch = (rule.params.requireAllMatch as boolean | undefined) ?? false;
175
+ const target = rule.target;
176
+
177
+ // If allowedHashes is empty, everything is allowed (permissive default)
178
+ if (allowedHashes.length === 0) {
179
+ return { rule, passed: true, message: "No allowlist constraints (permissive)" };
180
+ }
181
+
182
+ let promptAllowed = true;
183
+ let outputAllowed = true;
184
+
185
+ if (target === "prompt" || target === "both") {
186
+ promptAllowed = allowedHashes.some((hash) => promptHash.startsWith(hash));
187
+ }
188
+
189
+ if (target === "output" || target === "both") {
190
+ outputAllowed = allowedHashes.some((hash) => outputHash.startsWith(hash));
191
+ }
192
+
193
+ const passed = requireAllMatch
194
+ ? promptAllowed && outputAllowed
195
+ : promptAllowed || outputAllowed;
196
+
197
+ const message = passed
198
+ ? "Content matches allowlist criteria"
199
+ : "Content does not match any allowed patterns";
200
+
201
+ return { rule, passed, message };
202
+ }
203
+
204
+ /**
205
+ * Default evaluator for regex rules.
206
+ * Simulates regex pattern matching in ZK context.
207
+ *
208
+ * @param rule - The regex rule to evaluate
209
+ * @param promptHash - Hash of the prompt content
210
+ * @param outputHash - Hash of the output content
211
+ * @returns Evaluation result
212
+ */
213
+ async function evaluateRegexRule(
214
+ rule: PolicyRule,
215
+ promptHash: string,
216
+ outputHash: string
217
+ ): Promise<RuleEvaluationResult> {
218
+ // In a real ZK implementation, regex matching would be done on
219
+ // the actual content as a private witness. The circuit would
220
+ // verify the regex match and output a boolean commitment.
221
+ // Here we simulate by checking pattern hash signatures.
222
+
223
+ const patternHash = (rule.params.patternHash as string | undefined) ?? "";
224
+ const isBlockPattern = (rule.params.isBlockPattern as boolean | undefined) ?? true;
225
+ const target = rule.target;
226
+
227
+ // Simulate pattern matching by checking if content hash contains pattern signature
228
+ let matchFound = false;
229
+ const hashesToCheck: string[] = [];
230
+
231
+ if (target === "prompt" || target === "both") {
232
+ hashesToCheck.push(promptHash);
233
+ }
234
+ if (target === "output" || target === "both") {
235
+ hashesToCheck.push(outputHash);
236
+ }
237
+
238
+ // Simulate: pattern matches if any hash shares prefix with pattern hash
239
+ for (const hash of hashesToCheck) {
240
+ if (patternHash && hash.slice(0, 8) === patternHash.slice(0, 8)) {
241
+ matchFound = true;
242
+ break;
243
+ }
244
+ }
245
+
246
+ // For block patterns, passing means no match; for allow patterns, passing means match
247
+ const passed = isBlockPattern ? !matchFound : matchFound;
248
+ const message = isBlockPattern
249
+ ? passed
250
+ ? "Content does not match blocked regex pattern"
251
+ : "Content matches blocked regex pattern"
252
+ : passed
253
+ ? "Content matches required regex pattern"
254
+ : "Content does not match required regex pattern";
255
+
256
+ return { rule, passed, message };
257
+ }
258
+
259
+ /**
260
+ * Default evaluator for classifier rules.
261
+ * Simulates ML classifier evaluation in ZK context.
262
+ *
263
+ * @param rule - The classifier rule to evaluate
264
+ * @param promptHash - Hash of the prompt content
265
+ * @param outputHash - Hash of the output content
266
+ * @returns Evaluation result
267
+ */
268
+ async function evaluateClassifierRule(
269
+ rule: PolicyRule,
270
+ promptHash: string,
271
+ outputHash: string
272
+ ): Promise<RuleEvaluationResult> {
273
+ // In a real ZK implementation (zkML), this would run a neural network
274
+ // classifier circuit on the content and produce a proof of the
275
+ // classification result. This is extremely expensive and limited
276
+ // to small models.
277
+ // Here we simulate with deterministic hash-based classification.
278
+
279
+ const classifierId = (rule.params.classifierId as string | undefined) ?? "default";
280
+ const threshold = (rule.params.threshold as number | undefined) ?? 0.5;
281
+ const target = rule.target;
282
+
283
+ // Simulate classification score based on hash
284
+ const hashesToClassify: string[] = [];
285
+ if (target === "prompt" || target === "both") {
286
+ hashesToClassify.push(promptHash);
287
+ }
288
+ if (target === "output" || target === "both") {
289
+ hashesToClassify.push(outputHash);
290
+ }
291
+
292
+ // Deterministic "classification" based on hash bytes
293
+ let simulatedScore = 0;
294
+ for (const hash of hashesToClassify) {
295
+ // Use first 4 bytes of hash as a pseudo-random score
296
+ const hashBytes = hash.slice(0, 8);
297
+ const numericValue = parseInt(hashBytes, 16);
298
+ simulatedScore += (numericValue % 100) / 100;
299
+ }
300
+ simulatedScore = simulatedScore / hashesToClassify.length;
301
+
302
+ const passed = simulatedScore >= threshold;
303
+ const message = passed
304
+ ? `Classifier '${classifierId}' score ${simulatedScore.toFixed(2)} meets threshold ${threshold}`
305
+ : `Classifier '${classifierId}' score ${simulatedScore.toFixed(2)} below threshold ${threshold}`;
306
+
307
+ return { rule, passed, message };
308
+ }
309
+
310
+ // =============================================================================
311
+ // POLICY COMPLIANCE PROVER
312
+ // =============================================================================
313
+
314
+ /**
315
+ * PolicyComplianceProver generates and verifies ZK proofs of policy compliance.
316
+ *
317
+ * This prover evaluates content against policy rules and produces a proof
318
+ * that the content complies (or not) without revealing the actual content.
319
+ * Only content hashes and policy identifiers are exposed in public inputs.
320
+ *
321
+ * Supported rule types:
322
+ * - blocklist: Content must not match blocked patterns
323
+ * - allowlist: Content must match at least one allowed pattern
324
+ * - regex: Content must (not) match a regex pattern
325
+ * - classifier: Content must pass ML classification threshold
326
+ *
327
+ * @example
328
+ * ```typescript
329
+ * const prover = new PolicyComplianceProver();
330
+ * const proof = await prover.generateProof(input);
331
+ * console.log(proof.publicInputs.compliant); // true or false
332
+ * ```
333
+ */
334
+ export class PolicyComplianceProver {
335
+ private readonly debug: boolean;
336
+ private readonly evaluators: Map<string, RuleEvaluator>;
337
+
338
+ /**
339
+ * Create a new PolicyComplianceProver instance.
340
+ *
341
+ * @param options - Configuration options
342
+ */
343
+ constructor(options: PolicyComplianceProverOptions = {}) {
344
+ this.debug = options.debug ?? false;
345
+
346
+ // Initialize with default evaluators
347
+ this.evaluators = new Map<string, RuleEvaluator>([
348
+ ["blocklist", evaluateBlocklistRule],
349
+ ["allowlist", evaluateAllowlistRule],
350
+ ["regex", evaluateRegexRule],
351
+ ["classifier", evaluateClassifierRule],
352
+ ]);
353
+
354
+ // Add custom evaluators if provided
355
+ if (options.customEvaluators) {
356
+ for (const [type, evaluator] of options.customEvaluators) {
357
+ this.evaluators.set(type, evaluator);
358
+ }
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Generate a policy compliance proof.
364
+ *
365
+ * This method evaluates the content against all policy rules and generates
366
+ * a ZK proof of the compliance result. The proof binds to the Cardano
367
+ * anchor transaction for cross-chain verification.
368
+ *
369
+ * @param input - Policy compliance input
370
+ * @returns Promise resolving to the policy proof
371
+ * @throws MidnightProverException on validation or generation failure
372
+ */
373
+ async generateProof(input: PolicyInput): Promise<PolicyProof> {
374
+ const startTime = performance.now();
375
+
376
+ // Validate input
377
+ this.validateInput(input);
378
+
379
+ if (this.debug) {
380
+ console.log("[PolicyComplianceProver] Generating proof for policy:", input.policy.id);
381
+ }
382
+
383
+ // Evaluate policy rules
384
+ const evaluationResult = await this.evaluatePolicy(
385
+ input.policy,
386
+ input.promptHash,
387
+ input.outputHash
388
+ );
389
+
390
+ // Generate proof bytes (mock implementation)
391
+ const proofBytes = await this.generateProofBytes(input, evaluationResult);
392
+
393
+ const endTime = performance.now();
394
+ const provingTimeMs = Math.round(endTime - startTime);
395
+
396
+ // Construct public inputs
397
+ const publicInputs: PolicyPublicInputs = {
398
+ promptHash: input.promptHash,
399
+ policyId: input.policy.id,
400
+ policyVersion: input.policy.version,
401
+ compliant: evaluationResult.compliant,
402
+ cardanoAnchorTxHash: input.cardanoAnchorTxHash,
403
+ };
404
+
405
+ // Generate proof ID
406
+ const proofId = await this.generateProofId(publicInputs);
407
+
408
+ const proof: PolicyProof = {
409
+ proofType: "policy-compliance",
410
+ proofId,
411
+ proof: proofBytes,
412
+ createdAt: new Date().toISOString(),
413
+ provingTimeMs,
414
+ proofSizeBytes: proofBytes.length,
415
+ publicInputs,
416
+ };
417
+
418
+ if (this.debug) {
419
+ console.log("[PolicyComplianceProver] Proof generated:", {
420
+ proofId,
421
+ compliant: evaluationResult.compliant,
422
+ provingTimeMs,
423
+ ruleCount: input.policy.rules.length,
424
+ });
425
+ }
426
+
427
+ return proof;
428
+ }
429
+
430
+ /**
431
+ * Verify a policy compliance proof.
432
+ *
433
+ * This method verifies the cryptographic validity of the proof and
434
+ * checks that public inputs are consistent.
435
+ *
436
+ * @param proof - The policy proof to verify
437
+ * @returns Promise resolving to true if valid
438
+ */
439
+ async verifyProof(proof: PolicyProof): Promise<boolean> {
440
+ try {
441
+ // Validate proof structure
442
+ if (proof.proofType !== "policy-compliance") {
443
+ return false;
444
+ }
445
+
446
+ if (!proof.proof || proof.proof.length === 0) {
447
+ return false;
448
+ }
449
+
450
+ // Validate public inputs
451
+ const { publicInputs } = proof;
452
+ if (!publicInputs.promptHash || publicInputs.promptHash.length !== 64) {
453
+ return false;
454
+ }
455
+ if (!publicInputs.policyId || publicInputs.policyId.length === 0) {
456
+ return false;
457
+ }
458
+ if (!publicInputs.policyVersion || publicInputs.policyVersion.length === 0) {
459
+ return false;
460
+ }
461
+ if (!publicInputs.cardanoAnchorTxHash || publicInputs.cardanoAnchorTxHash.length === 0) {
462
+ return false;
463
+ }
464
+ if (typeof publicInputs.compliant !== "boolean") {
465
+ return false;
466
+ }
467
+
468
+ // Verify proof ID matches public inputs
469
+ const expectedProofId = await this.generateProofId(publicInputs);
470
+ if (proof.proofId !== expectedProofId) {
471
+ return false;
472
+ }
473
+
474
+ // Verify proof bytes contain expected structure
475
+ // In a real implementation, this would verify the ZK proof cryptographically
476
+ const proofValid = this.verifyProofBytes(proof.proof, publicInputs);
477
+
478
+ return proofValid;
479
+ } catch (error) {
480
+ if (this.debug) {
481
+ console.error("[PolicyComplianceProver] Verification error:", error);
482
+ }
483
+ return false;
484
+ }
485
+ }
486
+
487
+ /**
488
+ * Evaluate all policy rules against content.
489
+ *
490
+ * @param policy - The policy to evaluate
491
+ * @param promptHash - Hash of prompt content
492
+ * @param outputHash - Hash of output content
493
+ * @returns Evaluation result with rule-by-rule breakdown
494
+ */
495
+ async evaluatePolicy(
496
+ policy: ContentPolicy,
497
+ promptHash: string,
498
+ outputHash: string
499
+ ): Promise<PolicyEvaluationResult> {
500
+ const startTime = performance.now();
501
+ const ruleResults: RuleEvaluationResult[] = [];
502
+
503
+ for (const rule of policy.rules) {
504
+ const evaluator = this.evaluators.get(rule.type);
505
+ if (!evaluator) {
506
+ throw new MidnightProverException(
507
+ MidnightProverError.INVALID_INPUT,
508
+ `Unknown rule type: ${rule.type}`
509
+ );
510
+ }
511
+
512
+ const result = await evaluator(rule, promptHash, outputHash);
513
+ ruleResults.push(result);
514
+
515
+ if (this.debug) {
516
+ console.log(`[PolicyComplianceProver] Rule ${rule.type}:`, result.message);
517
+ }
518
+ }
519
+
520
+ const endTime = performance.now();
521
+
522
+ // Policy is compliant only if all rules pass
523
+ const compliant = ruleResults.every((r) => r.passed);
524
+
525
+ return {
526
+ compliant,
527
+ ruleResults,
528
+ evaluationTimeMs: Math.round(endTime - startTime),
529
+ };
530
+ }
531
+
532
+ /**
533
+ * Register a custom rule evaluator.
534
+ *
535
+ * @param type - Rule type identifier
536
+ * @param evaluator - Evaluation function
537
+ */
538
+ registerEvaluator(type: string, evaluator: RuleEvaluator): void {
539
+ this.evaluators.set(type, evaluator);
540
+ }
541
+
542
+ // ===========================================================================
543
+ // PRIVATE METHODS
544
+ // ===========================================================================
545
+
546
+ /**
547
+ * Validate policy input.
548
+ */
549
+ private validateInput(input: PolicyInput): void {
550
+ if (!input.promptHash || input.promptHash.length !== 64) {
551
+ throw new MidnightProverException(
552
+ MidnightProverError.INVALID_INPUT,
553
+ "Invalid prompt hash: must be 64-character hex string"
554
+ );
555
+ }
556
+
557
+ if (!input.outputHash || input.outputHash.length !== 64) {
558
+ throw new MidnightProverException(
559
+ MidnightProverError.INVALID_INPUT,
560
+ "Invalid output hash: must be 64-character hex string"
561
+ );
562
+ }
563
+
564
+ if (!input.policy) {
565
+ throw new MidnightProverException(
566
+ MidnightProverError.MISSING_REQUIRED_FIELD,
567
+ "Policy is required"
568
+ );
569
+ }
570
+
571
+ if (!input.policy.id || input.policy.id.length === 0) {
572
+ throw new MidnightProverException(
573
+ MidnightProverError.INVALID_INPUT,
574
+ "Policy ID is required"
575
+ );
576
+ }
577
+
578
+ if (!input.policy.version || input.policy.version.length === 0) {
579
+ throw new MidnightProverException(
580
+ MidnightProverError.INVALID_INPUT,
581
+ "Policy version is required"
582
+ );
583
+ }
584
+
585
+ if (!input.cardanoAnchorTxHash || input.cardanoAnchorTxHash.length === 0) {
586
+ throw new MidnightProverException(
587
+ MidnightProverError.MISSING_REQUIRED_FIELD,
588
+ "Cardano anchor transaction hash is required"
589
+ );
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Generate mock proof bytes.
595
+ * In a real implementation, this would call the Midnight proof server.
596
+ */
597
+ private async generateProofBytes(
598
+ input: PolicyInput,
599
+ evaluation: PolicyEvaluationResult
600
+ ): Promise<Uint8Array> {
601
+ // Construct witness data (private inputs)
602
+ const witnessData = canonicalize({
603
+ promptHash: input.promptHash,
604
+ outputHash: input.outputHash,
605
+ policyId: input.policy.id,
606
+ policyVersion: input.policy.version,
607
+ rules: input.policy.rules,
608
+ compliant: evaluation.compliant,
609
+ cardanoAnchorTxHash: input.cardanoAnchorTxHash,
610
+ });
611
+
612
+ // Generate witness hash
613
+ const witnessHash = await sha256StringHex(POLICY_DOMAIN_PREFIXES.witness + witnessData);
614
+
615
+ // Construct public input commitment
616
+ const publicInputData = canonicalize({
617
+ promptHash: input.promptHash,
618
+ policyId: input.policy.id,
619
+ policyVersion: input.policy.version,
620
+ compliant: evaluation.compliant,
621
+ cardanoAnchorTxHash: input.cardanoAnchorTxHash,
622
+ });
623
+ const publicInputHash = await sha256StringHex(
624
+ POLICY_DOMAIN_PREFIXES.publicInput + publicInputData
625
+ );
626
+
627
+ // Generate mock proof: commitment to witness + public inputs
628
+ const proofCommitment = await sha256StringHex(
629
+ POLICY_DOMAIN_PREFIXES.proof + witnessHash + "|" + publicInputHash
630
+ );
631
+
632
+ // Construct proof bytes: version (1 byte) + commitment (32 bytes) + witness hash (32 bytes)
633
+ const proofBytes = new Uint8Array(65);
634
+ proofBytes[0] = 0x01; // Version 1
635
+
636
+ // Copy commitment hash
637
+ const commitmentBytes = this.hexToBytes(proofCommitment);
638
+ proofBytes.set(commitmentBytes, 1);
639
+
640
+ // Copy witness hash
641
+ const witnessBytes = this.hexToBytes(witnessHash);
642
+ proofBytes.set(witnessBytes, 33);
643
+
644
+ return proofBytes;
645
+ }
646
+
647
+ /**
648
+ * Verify mock proof bytes.
649
+ * In a real implementation, this would cryptographically verify the proof
650
+ * against the public inputs using the ZK verifier.
651
+ */
652
+ private verifyProofBytes(
653
+ proofBytes: Uint8Array,
654
+ _publicInputs: PolicyPublicInputs
655
+ ): boolean {
656
+ // Check minimum proof size
657
+ if (proofBytes.length < 65) {
658
+ return false;
659
+ }
660
+
661
+ // Check version byte
662
+ if (proofBytes[0] !== 0x01) {
663
+ return false;
664
+ }
665
+
666
+ // Note: _publicInputs would be used in real ZK verification
667
+
668
+ // Extract commitment from proof and verify structure is valid
669
+ const commitmentBytes = proofBytes.slice(1, 33);
670
+ const witnessHashBytes = proofBytes.slice(33, 65);
671
+
672
+ // Verify both are non-zero (basic sanity check)
673
+ const commitmentNonZero = commitmentBytes.some((b) => b !== 0);
674
+ const witnessNonZero = witnessHashBytes.some((b) => b !== 0);
675
+
676
+ return commitmentNonZero && witnessNonZero;
677
+ }
678
+
679
+ /**
680
+ * Generate proof ID from public inputs.
681
+ */
682
+ private async generateProofId(publicInputs: PolicyPublicInputs): Promise<string> {
683
+ const data = canonicalize({
684
+ type: "policy-compliance",
685
+ ...publicInputs,
686
+ });
687
+ const hash = await sha256StringHex(data);
688
+ return `policy-proof-${hash.slice(0, 16)}`;
689
+ }
690
+
691
+ /**
692
+ * Convert hex string to bytes.
693
+ */
694
+ private hexToBytes(hex: string): Uint8Array {
695
+ const bytes = new Uint8Array(hex.length / 2);
696
+ for (let i = 0; i < hex.length; i += 2) {
697
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
698
+ }
699
+ return bytes;
700
+ }
701
+ }
702
+
703
+ // =============================================================================
704
+ // FACTORY FUNCTION
705
+ // =============================================================================
706
+
707
+ /**
708
+ * Create a new PolicyComplianceProver instance.
709
+ *
710
+ * @param options - Configuration options
711
+ * @returns New prover instance
712
+ */
713
+ export function createPolicyComplianceProver(
714
+ options?: PolicyComplianceProverOptions
715
+ ): PolicyComplianceProver {
716
+ return new PolicyComplianceProver(options);
717
+ }