@sip-protocol/sdk 0.2.1 → 0.2.3

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.
@@ -0,0 +1,790 @@
1
+ import {
2
+ fulfillment_proof_default,
3
+ funding_proof_default,
4
+ validity_proof_default
5
+ } from "../chunk-VITVG25F.mjs";
6
+ import {
7
+ ProofError,
8
+ ProofGenerationError
9
+ } from "../chunk-UHZKNGIT.mjs";
10
+
11
+ // src/proofs/noir.ts
12
+ import { Noir } from "@noir-lang/noir_js";
13
+ import { UltraHonkBackend } from "@aztec/bb.js";
14
+ import { secp256k1 } from "@noble/curves/secp256k1";
15
+ var NoirProofProvider = class {
16
+ framework = "noir";
17
+ _isReady = false;
18
+ config;
19
+ // Circuit instances
20
+ fundingNoir = null;
21
+ fundingBackend = null;
22
+ validityNoir = null;
23
+ validityBackend = null;
24
+ fulfillmentNoir = null;
25
+ fulfillmentBackend = null;
26
+ constructor(config = {}) {
27
+ this.config = {
28
+ backend: "barretenberg",
29
+ verbose: false,
30
+ ...config
31
+ };
32
+ }
33
+ get isReady() {
34
+ return this._isReady;
35
+ }
36
+ /**
37
+ * Derive secp256k1 public key coordinates from a private key
38
+ *
39
+ * Utility method that can be used to generate public key coordinates
40
+ * for use in ValidityProofParams.senderPublicKey or NoirProviderConfig.oraclePublicKey
41
+ *
42
+ * @param privateKey - 32-byte private key
43
+ * @returns X and Y coordinates as 32-byte arrays
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const privateKey = new Uint8Array(32).fill(1) // Your secret key
48
+ * const publicKey = NoirProofProvider.derivePublicKey(privateKey)
49
+ *
50
+ * // Use for oracle configuration
51
+ * const provider = new NoirProofProvider({
52
+ * oraclePublicKey: publicKey
53
+ * })
54
+ *
55
+ * // Or use for validity proof params
56
+ * const validityParams = {
57
+ * // ... other params
58
+ * senderPublicKey: {
59
+ * x: new Uint8Array(publicKey.x),
60
+ * y: new Uint8Array(publicKey.y)
61
+ * }
62
+ * }
63
+ * ```
64
+ */
65
+ static derivePublicKey(privateKey) {
66
+ const uncompressedPubKey = secp256k1.getPublicKey(privateKey, false);
67
+ const x = Array.from(uncompressedPubKey.slice(1, 33));
68
+ const y = Array.from(uncompressedPubKey.slice(33, 65));
69
+ return { x, y };
70
+ }
71
+ /**
72
+ * Initialize the Noir provider
73
+ *
74
+ * Loads circuit artifacts and initializes the proving backend.
75
+ */
76
+ async initialize() {
77
+ if (this._isReady) {
78
+ return;
79
+ }
80
+ try {
81
+ if (this.config.verbose) {
82
+ console.log("[NoirProofProvider] Initializing...");
83
+ }
84
+ const fundingCircuit = funding_proof_default;
85
+ this.fundingBackend = new UltraHonkBackend(fundingCircuit.bytecode);
86
+ this.fundingNoir = new Noir(fundingCircuit);
87
+ if (this.config.verbose) {
88
+ console.log("[NoirProofProvider] Funding circuit loaded");
89
+ const artifactVersion = funding_proof_default.noir_version;
90
+ console.log(`[NoirProofProvider] Noir version: ${artifactVersion ?? "unknown"}`);
91
+ }
92
+ const validityCircuit = validity_proof_default;
93
+ this.validityBackend = new UltraHonkBackend(validityCircuit.bytecode);
94
+ this.validityNoir = new Noir(validityCircuit);
95
+ if (this.config.verbose) {
96
+ console.log("[NoirProofProvider] Validity circuit loaded");
97
+ }
98
+ const fulfillmentCircuit = fulfillment_proof_default;
99
+ this.fulfillmentBackend = new UltraHonkBackend(fulfillmentCircuit.bytecode);
100
+ this.fulfillmentNoir = new Noir(fulfillmentCircuit);
101
+ if (this.config.verbose) {
102
+ console.log("[NoirProofProvider] Fulfillment circuit loaded");
103
+ }
104
+ this._isReady = true;
105
+ if (this.config.verbose) {
106
+ console.log("[NoirProofProvider] Initialization complete");
107
+ }
108
+ } catch (error) {
109
+ throw new ProofError(
110
+ `Failed to initialize NoirProofProvider: ${error instanceof Error ? error.message : String(error)}`,
111
+ "SIP_4003" /* PROOF_NOT_IMPLEMENTED */,
112
+ { context: { error } }
113
+ );
114
+ }
115
+ }
116
+ /**
117
+ * Generate a Funding Proof using Noir circuits
118
+ *
119
+ * Proves: balance >= minimumRequired without revealing balance
120
+ *
121
+ * @see docs/specs/FUNDING-PROOF.md
122
+ */
123
+ async generateFundingProof(params) {
124
+ this.ensureReady();
125
+ if (!this.fundingNoir || !this.fundingBackend) {
126
+ throw new ProofGenerationError(
127
+ "funding",
128
+ "Funding circuit not initialized"
129
+ );
130
+ }
131
+ try {
132
+ if (this.config.verbose) {
133
+ console.log("[NoirProofProvider] Generating funding proof...");
134
+ }
135
+ const { commitmentHash, blindingField } = await this.computeCommitmentHash(
136
+ params.balance,
137
+ params.blindingFactor,
138
+ params.assetId
139
+ );
140
+ const witnessInputs = {
141
+ // Public inputs
142
+ commitment_hash: commitmentHash,
143
+ minimum_required: params.minimumRequired.toString(),
144
+ asset_id: this.assetIdToField(params.assetId),
145
+ // Private inputs
146
+ balance: params.balance.toString(),
147
+ blinding: blindingField
148
+ };
149
+ if (this.config.verbose) {
150
+ console.log("[NoirProofProvider] Witness inputs:", {
151
+ commitment_hash: commitmentHash,
152
+ minimum_required: params.minimumRequired.toString(),
153
+ asset_id: this.assetIdToField(params.assetId),
154
+ balance: "[PRIVATE]",
155
+ blinding: "[PRIVATE]"
156
+ });
157
+ }
158
+ const { witness } = await this.fundingNoir.execute(witnessInputs);
159
+ if (this.config.verbose) {
160
+ console.log("[NoirProofProvider] Witness generated, creating proof...");
161
+ }
162
+ const proofData = await this.fundingBackend.generateProof(witness);
163
+ if (this.config.verbose) {
164
+ console.log("[NoirProofProvider] Proof generated successfully");
165
+ }
166
+ const publicInputs = [
167
+ `0x${commitmentHash}`,
168
+ `0x${params.minimumRequired.toString(16).padStart(16, "0")}`,
169
+ `0x${this.assetIdToField(params.assetId)}`
170
+ ];
171
+ const proof = {
172
+ type: "funding",
173
+ proof: `0x${Buffer.from(proofData.proof).toString("hex")}`,
174
+ publicInputs
175
+ };
176
+ return {
177
+ proof,
178
+ publicInputs
179
+ };
180
+ } catch (error) {
181
+ const message = error instanceof Error ? error.message : String(error);
182
+ if (message.includes("Insufficient balance")) {
183
+ throw new ProofGenerationError(
184
+ "funding",
185
+ "Insufficient balance to generate proof",
186
+ error instanceof Error ? error : void 0
187
+ );
188
+ }
189
+ if (message.includes("Commitment hash mismatch")) {
190
+ throw new ProofGenerationError(
191
+ "funding",
192
+ "Commitment hash verification failed",
193
+ error instanceof Error ? error : void 0
194
+ );
195
+ }
196
+ throw new ProofGenerationError(
197
+ "funding",
198
+ `Failed to generate funding proof: ${message}`,
199
+ error instanceof Error ? error : void 0
200
+ );
201
+ }
202
+ }
203
+ /**
204
+ * Generate a Validity Proof using Noir circuits
205
+ *
206
+ * Proves: Intent is authorized by sender without revealing identity
207
+ *
208
+ * @see docs/specs/VALIDITY-PROOF.md
209
+ */
210
+ async generateValidityProof(params) {
211
+ this.ensureReady();
212
+ if (!this.validityNoir || !this.validityBackend) {
213
+ throw new ProofGenerationError(
214
+ "validity",
215
+ "Validity circuit not initialized"
216
+ );
217
+ }
218
+ try {
219
+ if (this.config.verbose) {
220
+ console.log("[NoirProofProvider] Generating validity proof...");
221
+ }
222
+ const intentHashField = this.hexToField(params.intentHash);
223
+ const senderAddressField = this.hexToField(params.senderAddress);
224
+ const senderBlindingField = this.bytesToField(params.senderBlinding);
225
+ const senderSecretField = this.bytesToField(params.senderSecret);
226
+ const nonceField = this.bytesToField(params.nonce);
227
+ const { commitmentX, commitmentY } = await this.computeSenderCommitment(
228
+ senderAddressField,
229
+ senderBlindingField
230
+ );
231
+ const nullifier = await this.computeNullifier(
232
+ senderSecretField,
233
+ intentHashField,
234
+ nonceField
235
+ );
236
+ const signature = Array.from(params.authorizationSignature);
237
+ const messageHash = this.fieldToBytes32(intentHashField);
238
+ let pubKeyX;
239
+ let pubKeyY;
240
+ if (params.senderPublicKey) {
241
+ pubKeyX = Array.from(params.senderPublicKey.x);
242
+ pubKeyY = Array.from(params.senderPublicKey.y);
243
+ } else {
244
+ const coords = this.getPublicKeyCoordinates(params.senderSecret);
245
+ pubKeyX = coords.x;
246
+ pubKeyY = coords.y;
247
+ }
248
+ const witnessInputs = {
249
+ // Public inputs
250
+ intent_hash: intentHashField,
251
+ sender_commitment_x: commitmentX,
252
+ sender_commitment_y: commitmentY,
253
+ nullifier,
254
+ timestamp: params.timestamp.toString(),
255
+ expiry: params.expiry.toString(),
256
+ // Private inputs
257
+ sender_address: senderAddressField,
258
+ sender_blinding: senderBlindingField,
259
+ sender_secret: senderSecretField,
260
+ pub_key_x: pubKeyX,
261
+ pub_key_y: pubKeyY,
262
+ signature,
263
+ message_hash: messageHash,
264
+ nonce: nonceField
265
+ };
266
+ if (this.config.verbose) {
267
+ console.log("[NoirProofProvider] Validity witness inputs:", {
268
+ intent_hash: intentHashField,
269
+ sender_commitment_x: commitmentX,
270
+ sender_commitment_y: commitmentY,
271
+ nullifier,
272
+ timestamp: params.timestamp,
273
+ expiry: params.expiry,
274
+ sender_address: "[PRIVATE]",
275
+ sender_blinding: "[PRIVATE]",
276
+ sender_secret: "[PRIVATE]",
277
+ signature: "[PRIVATE]"
278
+ });
279
+ }
280
+ const { witness } = await this.validityNoir.execute(witnessInputs);
281
+ if (this.config.verbose) {
282
+ console.log("[NoirProofProvider] Validity witness generated, creating proof...");
283
+ }
284
+ const proofData = await this.validityBackend.generateProof(witness);
285
+ if (this.config.verbose) {
286
+ console.log("[NoirProofProvider] Validity proof generated successfully");
287
+ }
288
+ const publicInputs = [
289
+ `0x${intentHashField}`,
290
+ `0x${commitmentX}`,
291
+ `0x${commitmentY}`,
292
+ `0x${nullifier}`,
293
+ `0x${params.timestamp.toString(16).padStart(16, "0")}`,
294
+ `0x${params.expiry.toString(16).padStart(16, "0")}`
295
+ ];
296
+ const proof = {
297
+ type: "validity",
298
+ proof: `0x${Buffer.from(proofData.proof).toString("hex")}`,
299
+ publicInputs
300
+ };
301
+ return {
302
+ proof,
303
+ publicInputs
304
+ };
305
+ } catch (error) {
306
+ const message = error instanceof Error ? error.message : String(error);
307
+ if (message.includes("Sender commitment")) {
308
+ throw new ProofGenerationError(
309
+ "validity",
310
+ "Sender commitment verification failed",
311
+ error instanceof Error ? error : void 0
312
+ );
313
+ }
314
+ if (message.includes("Invalid ECDSA")) {
315
+ throw new ProofGenerationError(
316
+ "validity",
317
+ "Authorization signature verification failed",
318
+ error instanceof Error ? error : void 0
319
+ );
320
+ }
321
+ if (message.includes("Nullifier mismatch")) {
322
+ throw new ProofGenerationError(
323
+ "validity",
324
+ "Nullifier derivation failed",
325
+ error instanceof Error ? error : void 0
326
+ );
327
+ }
328
+ if (message.includes("Intent expired")) {
329
+ throw new ProofGenerationError(
330
+ "validity",
331
+ "Intent has expired (timestamp >= expiry)",
332
+ error instanceof Error ? error : void 0
333
+ );
334
+ }
335
+ throw new ProofGenerationError(
336
+ "validity",
337
+ `Failed to generate validity proof: ${message}`,
338
+ error instanceof Error ? error : void 0
339
+ );
340
+ }
341
+ }
342
+ /**
343
+ * Generate a Fulfillment Proof using Noir circuits
344
+ *
345
+ * Proves: Solver correctly executed the intent and delivered the required
346
+ * output to the recipient, without revealing execution path or liquidity sources.
347
+ *
348
+ * @see docs/specs/FULFILLMENT-PROOF.md
349
+ */
350
+ async generateFulfillmentProof(params) {
351
+ this.ensureReady();
352
+ if (!this.fulfillmentNoir || !this.fulfillmentBackend) {
353
+ throw new ProofGenerationError(
354
+ "fulfillment",
355
+ "Fulfillment circuit not initialized"
356
+ );
357
+ }
358
+ try {
359
+ if (this.config.verbose) {
360
+ console.log("[NoirProofProvider] Generating fulfillment proof...");
361
+ }
362
+ const intentHashField = this.hexToField(params.intentHash);
363
+ const recipientStealthField = this.hexToField(params.recipientStealth);
364
+ const { commitmentX, commitmentY } = await this.computeOutputCommitment(
365
+ params.outputAmount,
366
+ params.outputBlinding
367
+ );
368
+ const solverSecretField = this.bytesToField(params.solverSecret);
369
+ const solverId = await this.computeSolverId(solverSecretField);
370
+ const outputBlindingField = this.bytesToField(params.outputBlinding);
371
+ const attestation = params.oracleAttestation;
372
+ const attestationRecipientField = this.hexToField(attestation.recipient);
373
+ const attestationTxHashField = this.hexToField(attestation.txHash);
374
+ const oracleSignature = Array.from(attestation.signature);
375
+ const oracleMessageHash = await this.computeOracleMessageHash(
376
+ attestation.recipient,
377
+ attestation.amount,
378
+ attestation.txHash,
379
+ attestation.blockNumber
380
+ );
381
+ const oraclePubKeyX = this.config.oraclePublicKey?.x ?? new Array(32).fill(0);
382
+ const oraclePubKeyY = this.config.oraclePublicKey?.y ?? new Array(32).fill(0);
383
+ if (!this.config.oraclePublicKey && this.config.verbose) {
384
+ console.warn("[NoirProofProvider] Warning: No oracle public key configured. Using placeholder keys.");
385
+ }
386
+ const witnessInputs = {
387
+ // Public inputs
388
+ intent_hash: intentHashField,
389
+ output_commitment_x: commitmentX,
390
+ output_commitment_y: commitmentY,
391
+ recipient_stealth: recipientStealthField,
392
+ min_output_amount: params.minOutputAmount.toString(),
393
+ solver_id: solverId,
394
+ fulfillment_time: params.fulfillmentTime.toString(),
395
+ expiry: params.expiry.toString(),
396
+ // Private inputs
397
+ output_amount: params.outputAmount.toString(),
398
+ output_blinding: outputBlindingField,
399
+ solver_secret: solverSecretField,
400
+ attestation_recipient: attestationRecipientField,
401
+ attestation_amount: attestation.amount.toString(),
402
+ attestation_tx_hash: attestationTxHashField,
403
+ attestation_block: attestation.blockNumber.toString(),
404
+ oracle_signature: oracleSignature,
405
+ oracle_message_hash: oracleMessageHash,
406
+ oracle_pub_key_x: oraclePubKeyX,
407
+ oracle_pub_key_y: oraclePubKeyY
408
+ };
409
+ if (this.config.verbose) {
410
+ console.log("[NoirProofProvider] Fulfillment witness inputs:", {
411
+ intent_hash: intentHashField,
412
+ output_commitment_x: commitmentX,
413
+ output_commitment_y: commitmentY,
414
+ recipient_stealth: recipientStealthField,
415
+ min_output_amount: params.minOutputAmount.toString(),
416
+ solver_id: solverId,
417
+ fulfillment_time: params.fulfillmentTime,
418
+ expiry: params.expiry,
419
+ output_amount: "[PRIVATE]",
420
+ output_blinding: "[PRIVATE]",
421
+ solver_secret: "[PRIVATE]",
422
+ oracle_attestation: "[PRIVATE]"
423
+ });
424
+ }
425
+ const { witness } = await this.fulfillmentNoir.execute(witnessInputs);
426
+ if (this.config.verbose) {
427
+ console.log("[NoirProofProvider] Fulfillment witness generated, creating proof...");
428
+ }
429
+ const proofData = await this.fulfillmentBackend.generateProof(witness);
430
+ if (this.config.verbose) {
431
+ console.log("[NoirProofProvider] Fulfillment proof generated successfully");
432
+ }
433
+ const publicInputs = [
434
+ `0x${intentHashField}`,
435
+ `0x${commitmentX}`,
436
+ `0x${commitmentY}`,
437
+ `0x${recipientStealthField}`,
438
+ `0x${params.minOutputAmount.toString(16).padStart(16, "0")}`,
439
+ `0x${solverId}`,
440
+ `0x${params.fulfillmentTime.toString(16).padStart(16, "0")}`,
441
+ `0x${params.expiry.toString(16).padStart(16, "0")}`
442
+ ];
443
+ const proof = {
444
+ type: "fulfillment",
445
+ proof: `0x${Buffer.from(proofData.proof).toString("hex")}`,
446
+ publicInputs
447
+ };
448
+ return {
449
+ proof,
450
+ publicInputs
451
+ };
452
+ } catch (error) {
453
+ const message = error instanceof Error ? error.message : String(error);
454
+ if (message.includes("Output below minimum")) {
455
+ throw new ProofGenerationError(
456
+ "fulfillment",
457
+ "Output amount is below minimum required",
458
+ error instanceof Error ? error : void 0
459
+ );
460
+ }
461
+ if (message.includes("Commitment") && message.includes("mismatch")) {
462
+ throw new ProofGenerationError(
463
+ "fulfillment",
464
+ "Output commitment verification failed",
465
+ error instanceof Error ? error : void 0
466
+ );
467
+ }
468
+ if (message.includes("Recipient mismatch")) {
469
+ throw new ProofGenerationError(
470
+ "fulfillment",
471
+ "Attestation recipient does not match",
472
+ error instanceof Error ? error : void 0
473
+ );
474
+ }
475
+ if (message.includes("Invalid oracle")) {
476
+ throw new ProofGenerationError(
477
+ "fulfillment",
478
+ "Oracle attestation signature is invalid",
479
+ error instanceof Error ? error : void 0
480
+ );
481
+ }
482
+ if (message.includes("Unauthorized solver")) {
483
+ throw new ProofGenerationError(
484
+ "fulfillment",
485
+ "Solver not authorized for this intent",
486
+ error instanceof Error ? error : void 0
487
+ );
488
+ }
489
+ if (message.includes("Fulfillment after expiry")) {
490
+ throw new ProofGenerationError(
491
+ "fulfillment",
492
+ "Fulfillment occurred after intent expiry",
493
+ error instanceof Error ? error : void 0
494
+ );
495
+ }
496
+ throw new ProofGenerationError(
497
+ "fulfillment",
498
+ `Failed to generate fulfillment proof: ${message}`,
499
+ error instanceof Error ? error : void 0
500
+ );
501
+ }
502
+ }
503
+ /**
504
+ * Verify a Noir proof
505
+ */
506
+ async verifyProof(proof) {
507
+ this.ensureReady();
508
+ let backend = null;
509
+ switch (proof.type) {
510
+ case "funding":
511
+ backend = this.fundingBackend;
512
+ break;
513
+ case "validity":
514
+ backend = this.validityBackend;
515
+ break;
516
+ case "fulfillment":
517
+ backend = this.fulfillmentBackend;
518
+ break;
519
+ default:
520
+ throw new ProofError(
521
+ `Unknown proof type: ${proof.type}`,
522
+ "SIP_4003" /* PROOF_NOT_IMPLEMENTED */
523
+ );
524
+ }
525
+ if (!backend) {
526
+ throw new ProofError(
527
+ `${proof.type} backend not initialized`,
528
+ "SIP_4004" /* PROOF_PROVIDER_NOT_READY */
529
+ );
530
+ }
531
+ try {
532
+ const proofHex = proof.proof.startsWith("0x") ? proof.proof.slice(2) : proof.proof;
533
+ const proofBytes = new Uint8Array(Buffer.from(proofHex, "hex"));
534
+ const isValid = await backend.verifyProof({
535
+ proof: proofBytes,
536
+ publicInputs: proof.publicInputs.map(
537
+ (input) => input.startsWith("0x") ? input.slice(2) : input
538
+ )
539
+ });
540
+ return isValid;
541
+ } catch (error) {
542
+ if (this.config.verbose) {
543
+ console.error("[NoirProofProvider] Verification error:", error);
544
+ }
545
+ return false;
546
+ }
547
+ }
548
+ /**
549
+ * Destroy the provider and free resources
550
+ */
551
+ async destroy() {
552
+ if (this.fundingBackend) {
553
+ await this.fundingBackend.destroy();
554
+ this.fundingBackend = null;
555
+ }
556
+ if (this.validityBackend) {
557
+ await this.validityBackend.destroy();
558
+ this.validityBackend = null;
559
+ }
560
+ if (this.fulfillmentBackend) {
561
+ await this.fulfillmentBackend.destroy();
562
+ this.fulfillmentBackend = null;
563
+ }
564
+ this.fundingNoir = null;
565
+ this.validityNoir = null;
566
+ this.fulfillmentNoir = null;
567
+ this._isReady = false;
568
+ }
569
+ // ─── Private Methods ───────────────────────────────────────────────────────
570
+ ensureReady() {
571
+ if (!this._isReady) {
572
+ throw new ProofError(
573
+ "NoirProofProvider not initialized. Call initialize() first.",
574
+ "SIP_4004" /* PROOF_PROVIDER_NOT_READY */
575
+ );
576
+ }
577
+ }
578
+ /**
579
+ * Compute the commitment hash that the circuit expects
580
+ *
581
+ * The circuit computes:
582
+ * 1. commitment = pedersen_commitment([balance, blinding])
583
+ * 2. commitment_hash = pedersen_hash([commitment.x, commitment.y, asset_id])
584
+ *
585
+ * We need to compute this outside to pass as a public input.
586
+ *
587
+ * **IMPORTANT**: This SDK uses SHA256 as a deterministic stand-in for Pedersen hash.
588
+ * Both the SDK and circuit MUST use the same hash function. The bundled circuit
589
+ * artifacts are configured to use SHA256 for compatibility. If you use custom
590
+ * circuits with actual Pedersen hashing, you must update this implementation.
591
+ *
592
+ * @see docs/specs/HASH-COMPATIBILITY.md for hash function requirements
593
+ */
594
+ async computeCommitmentHash(balance, blindingFactor, assetId) {
595
+ const blindingField = this.bytesToField(blindingFactor);
596
+ const { sha256 } = await import("@noble/hashes/sha256");
597
+ const { bytesToHex } = await import("@noble/hashes/utils");
598
+ const preimage = new Uint8Array([
599
+ ...this.bigintToBytes(balance, 8),
600
+ ...blindingFactor.slice(0, 32),
601
+ ...this.hexToBytes(this.assetIdToField(assetId))
602
+ ]);
603
+ const hash = sha256(preimage);
604
+ const commitmentHash = bytesToHex(hash);
605
+ return { commitmentHash, blindingField };
606
+ }
607
+ /**
608
+ * Convert asset ID to field element
609
+ */
610
+ assetIdToField(assetId) {
611
+ if (assetId.startsWith("0x")) {
612
+ return assetId.slice(2).padStart(64, "0");
613
+ }
614
+ const encoder = new TextEncoder();
615
+ const bytes = encoder.encode(assetId);
616
+ let result = 0n;
617
+ for (let i = 0; i < bytes.length && i < 31; i++) {
618
+ result = result * 256n + BigInt(bytes[i]);
619
+ }
620
+ return result.toString(16).padStart(64, "0");
621
+ }
622
+ /**
623
+ * Convert bytes to field element string
624
+ */
625
+ bytesToField(bytes) {
626
+ let result = 0n;
627
+ const len = Math.min(bytes.length, 31);
628
+ for (let i = 0; i < len; i++) {
629
+ result = result * 256n + BigInt(bytes[i]);
630
+ }
631
+ return result.toString();
632
+ }
633
+ /**
634
+ * Convert bigint to bytes
635
+ */
636
+ bigintToBytes(value, length) {
637
+ const bytes = new Uint8Array(length);
638
+ let v = value;
639
+ for (let i = length - 1; i >= 0; i--) {
640
+ bytes[i] = Number(v & 0xffn);
641
+ v = v >> 8n;
642
+ }
643
+ return bytes;
644
+ }
645
+ /**
646
+ * Convert hex string to bytes
647
+ */
648
+ hexToBytes(hex) {
649
+ const h = hex.startsWith("0x") ? hex.slice(2) : hex;
650
+ const bytes = new Uint8Array(h.length / 2);
651
+ for (let i = 0; i < bytes.length; i++) {
652
+ bytes[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
653
+ }
654
+ return bytes;
655
+ }
656
+ /**
657
+ * Convert hex string to field element string
658
+ */
659
+ hexToField(hex) {
660
+ const h = hex.startsWith("0x") ? hex.slice(2) : hex;
661
+ return h.padStart(64, "0");
662
+ }
663
+ /**
664
+ * Convert field string to 32-byte array
665
+ */
666
+ fieldToBytes32(field) {
667
+ const hex = field.padStart(64, "0");
668
+ const bytes = [];
669
+ for (let i = 0; i < 32; i++) {
670
+ bytes.push(parseInt(hex.slice(i * 2, i * 2 + 2), 16));
671
+ }
672
+ return bytes;
673
+ }
674
+ /**
675
+ * Compute sender commitment for validity proof
676
+ *
677
+ * Uses SHA256 for SDK-side computation. The bundled circuit artifacts
678
+ * are compiled to use SHA256 for compatibility with this SDK.
679
+ *
680
+ * @see computeCommitmentHash for hash function compatibility notes
681
+ */
682
+ async computeSenderCommitment(senderAddressField, senderBlindingField) {
683
+ const { sha256 } = await import("@noble/hashes/sha256");
684
+ const { bytesToHex } = await import("@noble/hashes/utils");
685
+ const addressBytes = this.hexToBytes(senderAddressField);
686
+ const blindingBytes = this.hexToBytes(senderBlindingField.padStart(64, "0"));
687
+ const preimage = new Uint8Array([...addressBytes, ...blindingBytes]);
688
+ const hash = sha256(preimage);
689
+ const commitmentX = bytesToHex(hash.slice(0, 16)).padStart(64, "0");
690
+ const commitmentY = bytesToHex(hash.slice(16, 32)).padStart(64, "0");
691
+ return { commitmentX, commitmentY };
692
+ }
693
+ /**
694
+ * Compute nullifier for validity proof
695
+ *
696
+ * Uses SHA256 for SDK-side computation. The bundled circuit artifacts
697
+ * are compiled to use SHA256 for compatibility with this SDK.
698
+ *
699
+ * @see computeCommitmentHash for hash function compatibility notes
700
+ */
701
+ async computeNullifier(senderSecretField, intentHashField, nonceField) {
702
+ const { sha256 } = await import("@noble/hashes/sha256");
703
+ const { bytesToHex } = await import("@noble/hashes/utils");
704
+ const secretBytes = this.hexToBytes(senderSecretField.padStart(64, "0"));
705
+ const intentBytes = this.hexToBytes(intentHashField);
706
+ const nonceBytes = this.hexToBytes(nonceField.padStart(64, "0"));
707
+ const preimage = new Uint8Array([...secretBytes, ...intentBytes, ...nonceBytes]);
708
+ const hash = sha256(preimage);
709
+ return bytesToHex(hash);
710
+ }
711
+ /**
712
+ * Compute output commitment for fulfillment proof
713
+ *
714
+ * Uses SHA256 for SDK-side computation. The bundled circuit artifacts
715
+ * are compiled to use SHA256 for compatibility with this SDK.
716
+ *
717
+ * @see computeCommitmentHash for hash function compatibility notes
718
+ */
719
+ async computeOutputCommitment(outputAmount, outputBlinding) {
720
+ const { sha256 } = await import("@noble/hashes/sha256");
721
+ const { bytesToHex } = await import("@noble/hashes/utils");
722
+ const amountBytes = this.bigintToBytes(outputAmount, 8);
723
+ const blindingBytes = outputBlinding.slice(0, 32);
724
+ const preimage = new Uint8Array([...amountBytes, ...blindingBytes]);
725
+ const hash = sha256(preimage);
726
+ const commitmentX = bytesToHex(hash.slice(0, 16)).padStart(64, "0");
727
+ const commitmentY = bytesToHex(hash.slice(16, 32)).padStart(64, "0");
728
+ return { commitmentX, commitmentY };
729
+ }
730
+ /**
731
+ * Compute solver ID from solver secret
732
+ *
733
+ * Uses SHA256 for SDK-side computation. The bundled circuit artifacts
734
+ * are compiled to use SHA256 for compatibility with this SDK.
735
+ *
736
+ * @see computeCommitmentHash for hash function compatibility notes
737
+ */
738
+ async computeSolverId(solverSecretField) {
739
+ const { sha256 } = await import("@noble/hashes/sha256");
740
+ const { bytesToHex } = await import("@noble/hashes/utils");
741
+ const secretBytes = this.hexToBytes(solverSecretField.padStart(64, "0"));
742
+ const hash = sha256(secretBytes);
743
+ return bytesToHex(hash);
744
+ }
745
+ /**
746
+ * Compute oracle message hash for fulfillment proof
747
+ *
748
+ * Hash of attestation data that oracle signs
749
+ */
750
+ async computeOracleMessageHash(recipient, amount, txHash, blockNumber) {
751
+ const { sha256 } = await import("@noble/hashes/sha256");
752
+ const recipientBytes = this.hexToBytes(this.hexToField(recipient));
753
+ const amountBytes = this.bigintToBytes(amount, 8);
754
+ const txHashBytes = this.hexToBytes(this.hexToField(txHash));
755
+ const blockBytes = this.bigintToBytes(blockNumber, 8);
756
+ const preimage = new Uint8Array([
757
+ ...recipientBytes,
758
+ ...amountBytes,
759
+ ...txHashBytes,
760
+ ...blockBytes
761
+ ]);
762
+ const hash = sha256(preimage);
763
+ return Array.from(hash);
764
+ }
765
+ /**
766
+ * Derive secp256k1 public key coordinates from a private key
767
+ *
768
+ * @param privateKey - 32-byte private key as Uint8Array
769
+ * @returns X and Y coordinates as 32-byte arrays
770
+ */
771
+ getPublicKeyCoordinates(privateKey) {
772
+ const uncompressedPubKey = secp256k1.getPublicKey(privateKey, false);
773
+ const x = Array.from(uncompressedPubKey.slice(1, 33));
774
+ const y = Array.from(uncompressedPubKey.slice(33, 65));
775
+ return { x, y };
776
+ }
777
+ /**
778
+ * Derive public key coordinates from a field string (private key)
779
+ *
780
+ * @param privateKeyField - Private key as hex field string
781
+ * @returns X and Y coordinates as 32-byte arrays
782
+ */
783
+ getPublicKeyFromField(privateKeyField) {
784
+ const privateKeyBytes = this.hexToBytes(privateKeyField.padStart(64, "0"));
785
+ return this.getPublicKeyCoordinates(privateKeyBytes);
786
+ }
787
+ };
788
+ export {
789
+ NoirProofProvider
790
+ };