@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,682 @@
1
+ /**
2
+ * @fileoverview Cross-chain linking between Midnight proofs and Cardano anchors.
3
+ *
4
+ * Location: packages/midnight-prover/src/linking/cardano-anchor-link.ts
5
+ *
6
+ * Summary:
7
+ * This module implements the CardanoAnchorLinker class which creates and verifies
8
+ * bidirectional links between ZK proofs on Midnight and anchor transactions on Cardano.
9
+ * These links enable cross-chain verification of PoI traces.
10
+ *
11
+ * Usage:
12
+ * - Used after proof publication to establish cross-chain references
13
+ * - Integrates with poi-anchors-cardano for anchor verification
14
+ * - Enables verification that a Midnight proof corresponds to a specific Cardano anchor
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { CardanoAnchorLinker } from './cardano-anchor-link.js';
19
+ *
20
+ * const linker = new CardanoAnchorLinker();
21
+ *
22
+ * const link = linker.linkToAnchor(proof, cardanoTxHash);
23
+ * console.log('Cross-chain link created:', link.linkId);
24
+ *
25
+ * const isValid = await linker.verifyLink(link);
26
+ * console.log('Link valid:', isValid);
27
+ * ```
28
+ */
29
+
30
+ import type {
31
+ Proof,
32
+ AnyProof,
33
+ HashChainProof,
34
+ PolicyProof,
35
+ AttestationProof,
36
+ DisclosureProof,
37
+ InferenceProof,
38
+ } from "../types.js";
39
+
40
+ import {
41
+ MidnightProverError,
42
+ MidnightProverException,
43
+ isHashChainProof,
44
+ isPolicyProof,
45
+ isAttestationProof,
46
+ isDisclosureProof,
47
+ isInferenceProof,
48
+ } from "../types.js";
49
+
50
+ import { canonicalize } from "@fluxpointstudios/orynq-sdk-core/utils";
51
+
52
+ // =============================================================================
53
+ // TYPES
54
+ // =============================================================================
55
+
56
+ /**
57
+ * Bidirectional cross-chain link between a Midnight proof and a Cardano anchor.
58
+ *
59
+ * This link establishes a verifiable relationship between:
60
+ * - A ZK proof published on Midnight
61
+ * - An anchor transaction recorded on Cardano L1
62
+ *
63
+ * The link is cryptographically bound through hash commitments.
64
+ */
65
+ export interface CrossChainLink {
66
+ /**
67
+ * Unique identifier for this link.
68
+ */
69
+ linkId: string;
70
+
71
+ /**
72
+ * Version of the link format.
73
+ */
74
+ version: "1.0.0";
75
+
76
+ // Midnight side
77
+ /**
78
+ * Proof ID on the Midnight network.
79
+ */
80
+ midnightProofId: string;
81
+
82
+ /**
83
+ * Transaction hash on the Midnight network (if published).
84
+ */
85
+ midnightTxHash: string | undefined;
86
+
87
+ /**
88
+ * Type of the proof.
89
+ */
90
+ proofType: string;
91
+
92
+ /**
93
+ * Hash of the proof's public inputs.
94
+ */
95
+ publicInputsHash: string;
96
+
97
+ // Cardano side
98
+ /**
99
+ * Anchor transaction hash on Cardano L1.
100
+ */
101
+ cardanoAnchorTxHash: string;
102
+
103
+ /**
104
+ * Root hash that was anchored (e.g., trace merkle root or rolling hash).
105
+ */
106
+ anchoredRootHash: string;
107
+
108
+ // Link metadata
109
+ /**
110
+ * ISO 8601 timestamp when the link was created.
111
+ */
112
+ createdAt: string;
113
+
114
+ /**
115
+ * Hash commitment binding both chain references.
116
+ */
117
+ linkCommitment: string;
118
+ }
119
+
120
+ /**
121
+ * Result of link verification.
122
+ */
123
+ export interface LinkVerificationResult {
124
+ valid: boolean;
125
+ linkId: string;
126
+ errors: string[];
127
+ warnings: string[];
128
+ verifiedAt: string;
129
+
130
+ // Verification details
131
+ midnightVerified: boolean;
132
+ cardanoVerified: boolean;
133
+ commitmentVerified: boolean;
134
+ }
135
+
136
+ /**
137
+ * Options for the CardanoAnchorLinker.
138
+ */
139
+ export interface CardanoAnchorLinkerOptions {
140
+ /**
141
+ * Enable debug logging.
142
+ */
143
+ debug?: boolean;
144
+
145
+ /**
146
+ * Custom verification function for Cardano anchors.
147
+ * If not provided, uses mock verification.
148
+ */
149
+ cardanoVerifier?: (txHash: string) => Promise<boolean>;
150
+
151
+ /**
152
+ * Custom verification function for Midnight proofs.
153
+ * If not provided, uses mock verification.
154
+ */
155
+ midnightVerifier?: (proofId: string, txHash: string | undefined) => Promise<boolean>;
156
+ }
157
+
158
+ /**
159
+ * Domain separation prefixes for link operations.
160
+ */
161
+ const LINK_DOMAIN_PREFIXES = {
162
+ linkId: "poi-link:id:v1|",
163
+ commitment: "poi-link:commitment:v1|",
164
+ publicInputs: "poi-link:public-inputs:v1|",
165
+ } as const;
166
+
167
+ // =============================================================================
168
+ // CARDANO ANCHOR LINKER
169
+ // =============================================================================
170
+
171
+ /**
172
+ * CardanoAnchorLinker creates and verifies cross-chain links.
173
+ *
174
+ * This class provides:
175
+ * - Creation of bidirectional links between Midnight proofs and Cardano anchors
176
+ * - Cryptographic commitment generation for link verification
177
+ * - Link verification against both chains
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * const linker = new CardanoAnchorLinker({ debug: true });
182
+ *
183
+ * // Create a link after proof publication
184
+ * const link = linker.linkToAnchor(proof, cardanoTxHash);
185
+ *
186
+ * // Verify the link
187
+ * const result = await linker.verifyLink(link);
188
+ * if (result.valid) {
189
+ * console.log('Cross-chain link verified');
190
+ * }
191
+ * ```
192
+ */
193
+ export class CardanoAnchorLinker {
194
+ private readonly options: Required<CardanoAnchorLinkerOptions>;
195
+
196
+ // Cache for created links
197
+ private readonly linkCache = new Map<string, CrossChainLink>();
198
+
199
+ /**
200
+ * Create a new CardanoAnchorLinker instance.
201
+ *
202
+ * @param options - Configuration options
203
+ */
204
+ constructor(options: CardanoAnchorLinkerOptions = {}) {
205
+ this.options = {
206
+ debug: options.debug ?? false,
207
+ cardanoVerifier: options.cardanoVerifier ?? this.mockCardanoVerifier,
208
+ midnightVerifier: options.midnightVerifier ?? this.mockMidnightVerifier,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Create a cross-chain link between a proof and a Cardano anchor.
214
+ *
215
+ * This method:
216
+ * 1. Extracts relevant data from the proof
217
+ * 2. Generates a unique link ID
218
+ * 3. Creates a cryptographic commitment binding both chains
219
+ * 4. Returns the complete cross-chain link
220
+ *
221
+ * @param proof - The Midnight proof to link
222
+ * @param cardanoTxHash - The Cardano anchor transaction hash
223
+ * @param midnightTxHash - Optional Midnight transaction hash (if already published)
224
+ * @returns The cross-chain link
225
+ */
226
+ linkToAnchor(
227
+ proof: AnyProof,
228
+ cardanoTxHash: string,
229
+ midnightTxHash?: string
230
+ ): CrossChainLink {
231
+ this.debug(`Creating cross-chain link for proof ${proof.proofId}`);
232
+
233
+ // Validate inputs
234
+ this.validateProof(proof);
235
+ this.validateTxHash(cardanoTxHash, "Cardano");
236
+ if (midnightTxHash) {
237
+ this.validateTxHash(midnightTxHash, "Midnight");
238
+ }
239
+
240
+ // Extract public inputs hash and anchored root hash
241
+ const { publicInputsHash, anchoredRootHash } = this.extractProofData(proof);
242
+
243
+ // Verify that the proof's cardanoAnchorTxHash matches the provided one
244
+ const proofCardanoTxHash = this.extractCardanoAnchorTxHash(proof);
245
+ if (normalizeHash(proofCardanoTxHash) !== normalizeHash(cardanoTxHash)) {
246
+ throw new MidnightProverException(
247
+ MidnightProverError.HASH_MISMATCH,
248
+ `Proof's cardanoAnchorTxHash (${proofCardanoTxHash}) does not match provided cardanoTxHash (${cardanoTxHash})`
249
+ );
250
+ }
251
+
252
+ // Generate link ID
253
+ const linkId = this.generateLinkId(proof.proofId, cardanoTxHash);
254
+
255
+ // Generate link commitment
256
+ const linkCommitment = this.generateLinkCommitmentSync(
257
+ proof.proofId,
258
+ cardanoTxHash,
259
+ publicInputsHash,
260
+ anchoredRootHash
261
+ );
262
+
263
+ const link: CrossChainLink = {
264
+ linkId,
265
+ version: "1.0.0",
266
+ midnightProofId: proof.proofId,
267
+ midnightTxHash,
268
+ proofType: proof.proofType,
269
+ publicInputsHash,
270
+ cardanoAnchorTxHash: normalizeHash(cardanoTxHash),
271
+ anchoredRootHash,
272
+ createdAt: new Date().toISOString(),
273
+ linkCommitment,
274
+ };
275
+
276
+ // Cache the link
277
+ this.linkCache.set(linkId, link);
278
+
279
+ this.debug(`Cross-chain link created: ${linkId}`);
280
+
281
+ return link;
282
+ }
283
+
284
+ /**
285
+ * Verify a cross-chain link.
286
+ *
287
+ * This method verifies:
288
+ * 1. Link structure and format
289
+ * 2. Link commitment integrity
290
+ * 3. Midnight proof existence (if verifier provided)
291
+ * 4. Cardano anchor existence (if verifier provided)
292
+ *
293
+ * @param link - The cross-chain link to verify
294
+ * @returns Promise resolving to the verification result
295
+ */
296
+ async verifyLink(link: CrossChainLink): Promise<LinkVerificationResult> {
297
+ this.debug(`Verifying cross-chain link: ${link.linkId}`);
298
+
299
+ const errors: string[] = [];
300
+ const warnings: string[] = [];
301
+ let midnightVerified = false;
302
+ let cardanoVerified = false;
303
+ let commitmentVerified = false;
304
+
305
+ // Verify link structure
306
+ const structureErrors = this.validateLinkStructure(link);
307
+ errors.push(...structureErrors);
308
+
309
+ if (structureErrors.length === 0) {
310
+ // Verify commitment
311
+ const expectedCommitment = this.generateLinkCommitmentSync(
312
+ link.midnightProofId,
313
+ link.cardanoAnchorTxHash,
314
+ link.publicInputsHash,
315
+ link.anchoredRootHash
316
+ );
317
+
318
+ if (expectedCommitment === link.linkCommitment) {
319
+ commitmentVerified = true;
320
+ } else {
321
+ errors.push("Link commitment does not match");
322
+ }
323
+
324
+ // Verify Midnight proof
325
+ try {
326
+ midnightVerified = await this.options.midnightVerifier(
327
+ link.midnightProofId,
328
+ link.midnightTxHash
329
+ );
330
+ if (!midnightVerified) {
331
+ warnings.push("Midnight proof could not be verified on-chain");
332
+ }
333
+ } catch (error) {
334
+ warnings.push(`Midnight verification error: ${String(error)}`);
335
+ }
336
+
337
+ // Verify Cardano anchor
338
+ try {
339
+ cardanoVerified = await this.options.cardanoVerifier(link.cardanoAnchorTxHash);
340
+ if (!cardanoVerified) {
341
+ warnings.push("Cardano anchor could not be verified on-chain");
342
+ }
343
+ } catch (error) {
344
+ warnings.push(`Cardano verification error: ${String(error)}`);
345
+ }
346
+ }
347
+
348
+ const valid = errors.length === 0 && commitmentVerified;
349
+
350
+ const result: LinkVerificationResult = {
351
+ valid,
352
+ linkId: link.linkId,
353
+ errors,
354
+ warnings,
355
+ verifiedAt: new Date().toISOString(),
356
+ midnightVerified,
357
+ cardanoVerified,
358
+ commitmentVerified,
359
+ };
360
+
361
+ this.debug(`Link verification result: ${valid ? "VALID" : "INVALID"}`);
362
+
363
+ return result;
364
+ }
365
+
366
+ /**
367
+ * Get a cached link by ID.
368
+ *
369
+ * @param linkId - The link identifier
370
+ * @returns The cached link or undefined
371
+ */
372
+ getCachedLink(linkId: string): CrossChainLink | undefined {
373
+ return this.linkCache.get(linkId);
374
+ }
375
+
376
+ /**
377
+ * Get a link by proof ID.
378
+ *
379
+ * @param proofId - The proof identifier
380
+ * @returns The cached link or undefined
381
+ */
382
+ getLinkByProofId(proofId: string): CrossChainLink | undefined {
383
+ for (const link of this.linkCache.values()) {
384
+ if (link.midnightProofId === proofId) {
385
+ return link;
386
+ }
387
+ }
388
+ return undefined;
389
+ }
390
+
391
+ /**
392
+ * Get all links for a Cardano anchor.
393
+ *
394
+ * @param cardanoTxHash - The Cardano transaction hash
395
+ * @returns Array of links for this anchor
396
+ */
397
+ getLinksByCardanoAnchor(cardanoTxHash: string): CrossChainLink[] {
398
+ const normalizedHash = normalizeHash(cardanoTxHash);
399
+ const links: CrossChainLink[] = [];
400
+
401
+ for (const link of this.linkCache.values()) {
402
+ if (normalizeHash(link.cardanoAnchorTxHash) === normalizedHash) {
403
+ links.push(link);
404
+ }
405
+ }
406
+
407
+ return links;
408
+ }
409
+
410
+ // ===========================================================================
411
+ // PRIVATE METHODS
412
+ // ===========================================================================
413
+
414
+ /**
415
+ * Validate a proof for linking.
416
+ */
417
+ private validateProof(proof: Proof): void {
418
+ if (!proof.proofId) {
419
+ throw new MidnightProverException(
420
+ MidnightProverError.INVALID_INPUT,
421
+ "Proof ID is required"
422
+ );
423
+ }
424
+
425
+ if (!proof.proofType) {
426
+ throw new MidnightProverException(
427
+ MidnightProverError.INVALID_INPUT,
428
+ "Proof type is required"
429
+ );
430
+ }
431
+
432
+ if (!proof.proof || proof.proof.length === 0) {
433
+ throw new MidnightProverException(
434
+ MidnightProverError.INVALID_PROOF_FORMAT,
435
+ "Proof data is empty"
436
+ );
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Validate a transaction hash.
442
+ */
443
+ private validateTxHash(txHash: string, chainName: string): void {
444
+ const normalized = normalizeHash(txHash);
445
+ if (!/^[0-9a-f]{64}$/.test(normalized)) {
446
+ throw new MidnightProverException(
447
+ MidnightProverError.INVALID_INPUT,
448
+ `Invalid ${chainName} transaction hash format: ${txHash}`
449
+ );
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Validate link structure.
455
+ */
456
+ private validateLinkStructure(link: CrossChainLink): string[] {
457
+ const errors: string[] = [];
458
+
459
+ if (!link.linkId) {
460
+ errors.push("Missing linkId");
461
+ }
462
+
463
+ if (link.version !== "1.0.0") {
464
+ errors.push(`Unknown link version: ${link.version}`);
465
+ }
466
+
467
+ if (!link.midnightProofId) {
468
+ errors.push("Missing midnightProofId");
469
+ }
470
+
471
+ if (!link.proofType) {
472
+ errors.push("Missing proofType");
473
+ }
474
+
475
+ if (!link.publicInputsHash || !/^[0-9a-f]{64}$/i.test(link.publicInputsHash)) {
476
+ errors.push("Invalid publicInputsHash format");
477
+ }
478
+
479
+ if (!link.cardanoAnchorTxHash || !/^[0-9a-f]{64}$/i.test(link.cardanoAnchorTxHash)) {
480
+ errors.push("Invalid cardanoAnchorTxHash format");
481
+ }
482
+
483
+ if (!link.anchoredRootHash || !/^[0-9a-f]{64}$/i.test(link.anchoredRootHash)) {
484
+ errors.push("Invalid anchoredRootHash format");
485
+ }
486
+
487
+ if (!link.linkCommitment || !/^[0-9a-f]{64}$/i.test(link.linkCommitment)) {
488
+ errors.push("Invalid linkCommitment format");
489
+ }
490
+
491
+ if (!link.createdAt) {
492
+ errors.push("Missing createdAt timestamp");
493
+ }
494
+
495
+ return errors;
496
+ }
497
+
498
+ /**
499
+ * Extract data from proof for linking.
500
+ */
501
+ private extractProofData(proof: AnyProof): {
502
+ publicInputsHash: string;
503
+ anchoredRootHash: string;
504
+ } {
505
+ let publicInputsData: Record<string, unknown>;
506
+ let anchoredRootHash: string;
507
+
508
+ if (isHashChainProof(proof)) {
509
+ const p = proof as HashChainProof;
510
+ publicInputsData = p.publicInputs as unknown as Record<string, unknown>;
511
+ anchoredRootHash = p.publicInputs.rootHash;
512
+ } else if (isPolicyProof(proof)) {
513
+ const p = proof as PolicyProof;
514
+ publicInputsData = p.publicInputs as unknown as Record<string, unknown>;
515
+ // For policy proofs, use promptHash as the "root"
516
+ anchoredRootHash = p.publicInputs.promptHash;
517
+ } else if (isAttestationProof(proof)) {
518
+ const p = proof as AttestationProof;
519
+ publicInputsData = p.publicInputs as unknown as Record<string, unknown>;
520
+ anchoredRootHash = p.publicInputs.boundHash;
521
+ } else if (isDisclosureProof(proof)) {
522
+ const p = proof as DisclosureProof;
523
+ publicInputsData = p.publicInputs as unknown as Record<string, unknown>;
524
+ anchoredRootHash = p.publicInputs.merkleRoot;
525
+ } else if (isInferenceProof(proof)) {
526
+ const p = proof as InferenceProof;
527
+ publicInputsData = p.publicInputs as unknown as Record<string, unknown>;
528
+ anchoredRootHash = p.publicInputs.modelWeightDigest;
529
+ } else {
530
+ throw new MidnightProverException(
531
+ MidnightProverError.INVALID_INPUT,
532
+ `Unknown proof type: ${(proof as Proof).proofType}`
533
+ );
534
+ }
535
+
536
+ // Hash public inputs
537
+ const publicInputsHash = this.hashPublicInputsSync(publicInputsData);
538
+
539
+ return { publicInputsHash, anchoredRootHash };
540
+ }
541
+
542
+ /**
543
+ * Extract Cardano anchor transaction hash from proof.
544
+ */
545
+ private extractCardanoAnchorTxHash(proof: AnyProof): string {
546
+ const anyProof = proof as { publicInputs?: { cardanoAnchorTxHash?: string } };
547
+ const txHash = anyProof.publicInputs?.cardanoAnchorTxHash;
548
+
549
+ if (!txHash) {
550
+ throw new MidnightProverException(
551
+ MidnightProverError.MISSING_REQUIRED_FIELD,
552
+ "Proof must have cardanoAnchorTxHash in public inputs"
553
+ );
554
+ }
555
+
556
+ return txHash;
557
+ }
558
+
559
+ /**
560
+ * Generate a unique link ID.
561
+ */
562
+ private generateLinkId(proofId: string, cardanoTxHash: string): string {
563
+ // Simple hash-based link ID
564
+ const input = LINK_DOMAIN_PREFIXES.linkId + proofId + "|" + normalizeHash(cardanoTxHash);
565
+ // Use synchronous generation for now (matches existing pattern)
566
+ return `link-${this.simpleHash(input).slice(0, 16)}`;
567
+ }
568
+
569
+ /**
570
+ * Generate link commitment synchronously.
571
+ */
572
+ private generateLinkCommitmentSync(
573
+ proofId: string,
574
+ cardanoTxHash: string,
575
+ publicInputsHash: string,
576
+ anchoredRootHash: string
577
+ ): string {
578
+ const input = canonicalize({
579
+ domain: LINK_DOMAIN_PREFIXES.commitment,
580
+ proofId,
581
+ cardanoTxHash: normalizeHash(cardanoTxHash),
582
+ publicInputsHash,
583
+ anchoredRootHash,
584
+ });
585
+ return this.simpleHash(input);
586
+ }
587
+
588
+ /**
589
+ * Hash public inputs synchronously.
590
+ */
591
+ private hashPublicInputsSync(publicInputs: Record<string, unknown>): string {
592
+ const input = LINK_DOMAIN_PREFIXES.publicInputs + canonicalize(publicInputs);
593
+ return this.simpleHash(input);
594
+ }
595
+
596
+ /**
597
+ * Simple synchronous hash function (for use in sync contexts).
598
+ * Uses a deterministic algorithm suitable for identifiers.
599
+ */
600
+ private simpleHash(input: string): string {
601
+ // Simple hash implementation for sync contexts
602
+ // In production, this would use a proper hash function
603
+ let hash = 0x811c9dc5; // FNV offset basis
604
+ for (let i = 0; i < input.length; i++) {
605
+ hash ^= input.charCodeAt(i);
606
+ hash = Math.imul(hash, 0x01000193); // FNV prime
607
+ }
608
+
609
+ // Generate a 64-char hex string by repeating the process
610
+ let result = "";
611
+ let state = hash;
612
+ for (let j = 0; j < 8; j++) {
613
+ for (let i = 0; i < input.length; i++) {
614
+ state ^= input.charCodeAt((i + j * 7) % input.length);
615
+ state = Math.imul(state, 0x01000193);
616
+ }
617
+ result += (state >>> 0).toString(16).padStart(8, "0");
618
+ }
619
+
620
+ return result;
621
+ }
622
+
623
+ /**
624
+ * Mock Cardano verifier (always returns true for valid hashes).
625
+ */
626
+ private mockCardanoVerifier = async (txHash: string): Promise<boolean> => {
627
+ // Simulate network delay
628
+ await new Promise((resolve) => setTimeout(resolve, 50));
629
+ // In mock mode, accept any valid-looking hash
630
+ return /^[0-9a-f]{64}$/i.test(normalizeHash(txHash));
631
+ };
632
+
633
+ /**
634
+ * Mock Midnight verifier (always returns true for non-empty proof IDs).
635
+ */
636
+ private mockMidnightVerifier = async (
637
+ proofId: string,
638
+ _txHash: string | undefined
639
+ ): Promise<boolean> => {
640
+ // Simulate network delay
641
+ await new Promise((resolve) => setTimeout(resolve, 50));
642
+ // In mock mode, accept any non-empty proof ID
643
+ return proofId.length > 0;
644
+ };
645
+
646
+ /**
647
+ * Debug logging helper.
648
+ */
649
+ private debug(message: string): void {
650
+ if (this.options.debug) {
651
+ console.log(`[CardanoAnchorLinker] ${message}`);
652
+ }
653
+ }
654
+ }
655
+
656
+ // =============================================================================
657
+ // UTILITY FUNCTIONS
658
+ // =============================================================================
659
+
660
+ /**
661
+ * Normalize a hash to lowercase without 0x prefix.
662
+ */
663
+ function normalizeHash(hash: string): string {
664
+ const cleaned = hash.startsWith("0x") ? hash.slice(2) : hash;
665
+ return cleaned.toLowerCase();
666
+ }
667
+
668
+ // =============================================================================
669
+ // FACTORY FUNCTION
670
+ // =============================================================================
671
+
672
+ /**
673
+ * Create a new CardanoAnchorLinker instance.
674
+ *
675
+ * @param options - Configuration options
676
+ * @returns New CardanoAnchorLinker instance
677
+ */
678
+ export function createCardanoAnchorLinker(
679
+ options?: CardanoAnchorLinkerOptions
680
+ ): CardanoAnchorLinker {
681
+ return new CardanoAnchorLinker(options);
682
+ }