@sip-protocol/sdk 0.2.8 → 0.2.10

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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -0
  3. package/dist/browser.d.mts +100 -2
  4. package/dist/browser.d.ts +100 -2
  5. package/dist/browser.js +1362 -268
  6. package/dist/browser.mjs +502 -16
  7. package/dist/{chunk-UPTISVCY.mjs → chunk-AV37IZST.mjs} +731 -15
  8. package/dist/{chunk-VITVG25F.mjs → chunk-XLEPIR2P.mjs} +2 -100
  9. package/dist/index-BFOKTz2z.d.ts +6062 -0
  10. package/dist/index-CAhjA4kh.d.mts +6062 -0
  11. package/dist/index.d.mts +2 -5609
  12. package/dist/index.d.ts +2 -5609
  13. package/dist/index.js +588 -154
  14. package/dist/index.mjs +5 -1
  15. package/dist/{noir-BHQtFvRk.d.mts → noir-BTyLXLlZ.d.mts} +1 -1
  16. package/dist/{noir-BHQtFvRk.d.ts → noir-BTyLXLlZ.d.ts} +1 -1
  17. package/dist/proofs/noir.d.mts +1 -1
  18. package/dist/proofs/noir.d.ts +1 -1
  19. package/dist/proofs/noir.js +11 -112
  20. package/dist/proofs/noir.mjs +10 -13
  21. package/package.json +16 -16
  22. package/src/browser.ts +23 -0
  23. package/src/index.ts +12 -0
  24. package/src/proofs/browser-utils.ts +389 -0
  25. package/src/proofs/browser.ts +246 -19
  26. package/src/proofs/circuits/funding_proof.json +1 -1
  27. package/src/proofs/noir.ts +14 -14
  28. package/src/proofs/worker.ts +426 -0
  29. package/src/zcash/bridge.ts +738 -0
  30. package/src/zcash/index.ts +36 -1
  31. package/src/zcash/swap-service.ts +793 -0
  32. package/dist/chunk-4VJHI66K.mjs +0 -12120
  33. package/dist/chunk-5BAS4D44.mjs +0 -10283
  34. package/dist/chunk-6WOV2YNG.mjs +0 -10179
  35. package/dist/chunk-DU7LQDD2.mjs +0 -10148
  36. package/dist/chunk-MR7HRCRS.mjs +0 -10165
  37. package/dist/chunk-NDGUWOOZ.mjs +0 -10157
  38. package/dist/chunk-O4Y2ZUDL.mjs +0 -12721
  39. package/dist/chunk-VXSHK7US.mjs +0 -10158
@@ -256,21 +256,15 @@ export class NoirProofProvider implements ProofProvider {
256
256
  console.log('[NoirProofProvider] Generating funding proof...')
257
257
  }
258
258
 
259
- // Compute the commitment hash that the circuit expects
260
- // The circuit computes: pedersen_hash([commitment.x, commitment.y, asset_id])
261
- // We need to compute this to pass as a public input
262
- const { commitmentHash, blindingField } = await this.computeCommitmentHash(
263
- params.balance,
264
- params.blindingFactor,
265
- params.assetId
266
- )
259
+ // Convert blinding factor to field element
260
+ const blindingField = this.bytesToField(params.blindingFactor)
267
261
 
268
262
  // Prepare witness inputs for the circuit
263
+ // New circuit signature: (minimum_required: pub u64, asset_id: pub Field, balance: u64, blinding: Field) -> [u8; 32]
269
264
  const witnessInputs = {
270
265
  // Public inputs
271
- commitment_hash: commitmentHash,
272
266
  minimum_required: params.minimumRequired.toString(),
273
- asset_id: this.assetIdToField(params.assetId),
267
+ asset_id: `0x${this.assetIdToField(params.assetId)}`,
274
268
  // Private inputs
275
269
  balance: params.balance.toString(),
276
270
  blinding: blindingField,
@@ -278,16 +272,16 @@ export class NoirProofProvider implements ProofProvider {
278
272
 
279
273
  if (this.config.verbose) {
280
274
  console.log('[NoirProofProvider] Witness inputs:', {
281
- commitment_hash: commitmentHash,
282
275
  minimum_required: params.minimumRequired.toString(),
283
- asset_id: this.assetIdToField(params.assetId),
276
+ asset_id: `0x${this.assetIdToField(params.assetId)}`,
284
277
  balance: '[PRIVATE]',
285
278
  blinding: '[PRIVATE]',
286
279
  })
287
280
  }
288
281
 
289
282
  // Execute circuit to generate witness
290
- const { witness } = await this.fundingNoir.execute(witnessInputs)
283
+ // The circuit returns the commitment hash as [u8; 32]
284
+ const { witness, returnValue } = await this.fundingNoir.execute(witnessInputs)
291
285
 
292
286
  if (this.config.verbose) {
293
287
  console.log('[NoirProofProvider] Witness generated, creating proof...')
@@ -300,11 +294,17 @@ export class NoirProofProvider implements ProofProvider {
300
294
  console.log('[NoirProofProvider] Proof generated successfully')
301
295
  }
302
296
 
297
+ // Extract commitment hash from circuit return value
298
+ const { bytesToHex } = await import('@noble/hashes/utils')
299
+ const commitmentHashBytes = returnValue as number[]
300
+ const commitmentHashHex = bytesToHex(new Uint8Array(commitmentHashBytes))
301
+
303
302
  // Extract public inputs from the proof
303
+ // Order: minimum_required, asset_id, commitment_hash (return value)
304
304
  const publicInputs: `0x${string}`[] = [
305
- `0x${commitmentHash}`,
306
305
  `0x${params.minimumRequired.toString(16).padStart(16, '0')}`,
307
306
  `0x${this.assetIdToField(params.assetId)}`,
307
+ `0x${commitmentHashHex}`,
308
308
  ]
309
309
 
310
310
  // Create ZKProof object
@@ -0,0 +1,426 @@
1
+ /**
2
+ * Proof Generation Web Worker
3
+ *
4
+ * Runs proof generation off the main thread to keep UI responsive.
5
+ * Communicates via postMessage with the main thread.
6
+ *
7
+ * @see https://github.com/sip-protocol/sip-protocol/issues/140
8
+ */
9
+
10
+ import type { ZKProof } from '@sip-protocol/types'
11
+ import type {
12
+ FundingProofParams,
13
+ ValidityProofParams,
14
+ FulfillmentProofParams,
15
+ ProofResult,
16
+ } from './interface'
17
+
18
+ // Worker message types
19
+ export type WorkerMessageType =
20
+ | 'init'
21
+ | 'generateFundingProof'
22
+ | 'generateValidityProof'
23
+ | 'generateFulfillmentProof'
24
+ | 'destroy'
25
+
26
+ export interface WorkerRequest {
27
+ id: string
28
+ type: WorkerMessageType
29
+ params?: FundingProofParams | ValidityProofParams | FulfillmentProofParams
30
+ config?: {
31
+ verbose?: boolean
32
+ oraclePublicKey?: { x: number[]; y: number[] }
33
+ }
34
+ }
35
+
36
+ export interface WorkerResponse {
37
+ id: string
38
+ type: 'success' | 'error' | 'progress'
39
+ result?: ProofResult
40
+ error?: string
41
+ progress?: {
42
+ stage: string
43
+ percent: number
44
+ message: string
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Create inline worker code as a blob URL
50
+ * This approach works with most bundlers without special configuration
51
+ */
52
+ export function createWorkerBlobURL(): string {
53
+ const workerCode = `
54
+ // Proof Generation Worker
55
+ // This code runs in a separate thread
56
+
57
+ let fundingNoir = null;
58
+ let fundingBackend = null;
59
+ let validityNoir = null;
60
+ let validityBackend = null;
61
+ let fulfillmentNoir = null;
62
+ let fulfillmentBackend = null;
63
+ let isReady = false;
64
+ let config = { verbose: false };
65
+
66
+ // Helper to send progress updates
67
+ function sendProgress(id, stage, percent, message) {
68
+ self.postMessage({
69
+ id,
70
+ type: 'progress',
71
+ progress: { stage, percent, message }
72
+ });
73
+ }
74
+
75
+ // Helper to send error
76
+ function sendError(id, error) {
77
+ self.postMessage({
78
+ id,
79
+ type: 'error',
80
+ error: error.message || String(error)
81
+ });
82
+ }
83
+
84
+ // Helper to send success
85
+ function sendSuccess(id, result) {
86
+ self.postMessage({
87
+ id,
88
+ type: 'success',
89
+ result
90
+ });
91
+ }
92
+
93
+ // Initialize circuits (called once)
94
+ async function initialize(id, initConfig) {
95
+ try {
96
+ sendProgress(id, 'initializing', 10, 'Loading Noir JS...');
97
+
98
+ // Dynamic imports for Noir
99
+ const { Noir } = await import('@noir-lang/noir_js');
100
+ const { UltraHonkBackend } = await import('@aztec/bb.js');
101
+
102
+ sendProgress(id, 'initializing', 30, 'Loading circuit artifacts...');
103
+
104
+ // Load circuit artifacts
105
+ const [fundingArtifact, validityArtifact, fulfillmentArtifact] = await Promise.all([
106
+ fetch(new URL('./circuits/funding_proof.json', import.meta.url)).then(r => r.json()),
107
+ fetch(new URL('./circuits/validity_proof.json', import.meta.url)).then(r => r.json()),
108
+ fetch(new URL('./circuits/fulfillment_proof.json', import.meta.url)).then(r => r.json()),
109
+ ]);
110
+
111
+ sendProgress(id, 'initializing', 50, 'Initializing backends...');
112
+
113
+ // Initialize Noir instances
114
+ fundingNoir = new Noir(fundingArtifact);
115
+ fundingBackend = new UltraHonkBackend(fundingArtifact.bytecode);
116
+
117
+ sendProgress(id, 'initializing', 70, 'Initializing validity circuit...');
118
+ validityNoir = new Noir(validityArtifact);
119
+ validityBackend = new UltraHonkBackend(validityArtifact.bytecode);
120
+
121
+ sendProgress(id, 'initializing', 90, 'Initializing fulfillment circuit...');
122
+ fulfillmentNoir = new Noir(fulfillmentArtifact);
123
+ fulfillmentBackend = new UltraHonkBackend(fulfillmentArtifact.bytecode);
124
+
125
+ config = initConfig || { verbose: false };
126
+ isReady = true;
127
+
128
+ sendProgress(id, 'complete', 100, 'Worker initialized');
129
+ sendSuccess(id, { initialized: true });
130
+ } catch (error) {
131
+ sendError(id, error);
132
+ }
133
+ }
134
+
135
+ // Generate funding proof
136
+ async function generateFundingProof(id, params) {
137
+ if (!isReady) {
138
+ sendError(id, new Error('Worker not initialized'));
139
+ return;
140
+ }
141
+
142
+ try {
143
+ sendProgress(id, 'witness', 20, 'Preparing witness...');
144
+
145
+ // Convert blinding factor to field
146
+ const blindingField = bytesToField(params.blindingFactor);
147
+
148
+ const witnessInputs = {
149
+ minimum_required: params.minimumRequired.toString(),
150
+ asset_id: '0x' + assetIdToField(params.assetId),
151
+ balance: params.balance.toString(),
152
+ blinding: blindingField,
153
+ };
154
+
155
+ sendProgress(id, 'witness', 40, 'Executing circuit...');
156
+ const { witness, returnValue } = await fundingNoir.execute(witnessInputs);
157
+
158
+ sendProgress(id, 'proving', 60, 'Generating proof...');
159
+ const proofData = await fundingBackend.generateProof(witness);
160
+
161
+ sendProgress(id, 'complete', 100, 'Proof generated');
162
+
163
+ // Extract commitment hash from return value
164
+ const commitmentHashHex = bytesToHex(new Uint8Array(returnValue));
165
+
166
+ const publicInputs = [
167
+ '0x' + params.minimumRequired.toString(16).padStart(16, '0'),
168
+ '0x' + assetIdToField(params.assetId),
169
+ '0x' + commitmentHashHex,
170
+ ];
171
+
172
+ const proof = {
173
+ type: 'funding',
174
+ proof: '0x' + bytesToHex(proofData.proof),
175
+ publicInputs,
176
+ };
177
+
178
+ sendSuccess(id, { proof, publicInputs });
179
+ } catch (error) {
180
+ sendError(id, error);
181
+ }
182
+ }
183
+
184
+ // Helper functions
185
+ function bytesToField(bytes) {
186
+ let result = 0n;
187
+ const len = Math.min(bytes.length, 31);
188
+ for (let i = 0; i < len; i++) {
189
+ result = result * 256n + BigInt(bytes[i]);
190
+ }
191
+ return result.toString();
192
+ }
193
+
194
+ function assetIdToField(assetId) {
195
+ if (assetId.startsWith('0x')) {
196
+ return assetId.slice(2).padStart(64, '0');
197
+ }
198
+ const encoder = new TextEncoder();
199
+ const bytes = encoder.encode(assetId);
200
+ let result = 0n;
201
+ for (let i = 0; i < bytes.length && i < 31; i++) {
202
+ result = result * 256n + BigInt(bytes[i]);
203
+ }
204
+ return result.toString(16).padStart(64, '0');
205
+ }
206
+
207
+ function bytesToHex(bytes) {
208
+ return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
209
+ }
210
+
211
+ // Message handler
212
+ self.onmessage = async function(event) {
213
+ const { id, type, params, config: initConfig } = event.data;
214
+
215
+ switch (type) {
216
+ case 'init':
217
+ await initialize(id, initConfig);
218
+ break;
219
+ case 'generateFundingProof':
220
+ await generateFundingProof(id, params);
221
+ break;
222
+ case 'generateValidityProof':
223
+ // TODO: Implement
224
+ sendError(id, new Error('Validity proof not yet implemented in worker'));
225
+ break;
226
+ case 'generateFulfillmentProof':
227
+ // TODO: Implement
228
+ sendError(id, new Error('Fulfillment proof not yet implemented in worker'));
229
+ break;
230
+ case 'destroy':
231
+ // Cleanup
232
+ fundingNoir = null;
233
+ fundingBackend = null;
234
+ validityNoir = null;
235
+ validityBackend = null;
236
+ fulfillmentNoir = null;
237
+ fulfillmentBackend = null;
238
+ isReady = false;
239
+ sendSuccess(id, { destroyed: true });
240
+ break;
241
+ default:
242
+ sendError(id, new Error('Unknown message type: ' + type));
243
+ }
244
+ };
245
+ `
246
+
247
+ const blob = new Blob([workerCode], { type: 'application/javascript' })
248
+ return URL.createObjectURL(blob)
249
+ }
250
+
251
+ /**
252
+ * ProofWorker class for managing Web Worker proof generation
253
+ *
254
+ * Provides a clean API for generating proofs in a Web Worker,
255
+ * with progress callbacks and automatic fallback to main thread.
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * const worker = new ProofWorker()
260
+ * await worker.initialize()
261
+ *
262
+ * const result = await worker.generateProof('funding', params, (progress) => {
263
+ * console.log(`${progress.stage}: ${progress.percent}%`)
264
+ * })
265
+ * ```
266
+ */
267
+ export class ProofWorker {
268
+ private worker: Worker | null = null
269
+ private pendingRequests: Map<
270
+ string,
271
+ {
272
+ resolve: (result: ProofResult) => void
273
+ reject: (error: Error) => void
274
+ onProgress?: (progress: NonNullable<WorkerResponse['progress']>) => void
275
+ }
276
+ > = new Map()
277
+ private _isReady = false
278
+ private requestCounter = 0
279
+
280
+ /**
281
+ * Check if Web Workers are supported
282
+ */
283
+ static isSupported(): boolean {
284
+ return typeof Worker !== 'undefined' && typeof Blob !== 'undefined'
285
+ }
286
+
287
+ /**
288
+ * Check if worker is initialized and ready
289
+ */
290
+ get isReady(): boolean {
291
+ return this._isReady
292
+ }
293
+
294
+ /**
295
+ * Initialize the worker
296
+ */
297
+ async initialize(config?: {
298
+ verbose?: boolean
299
+ oraclePublicKey?: { x: number[]; y: number[] }
300
+ }): Promise<void> {
301
+ if (this._isReady) {
302
+ return
303
+ }
304
+
305
+ if (!ProofWorker.isSupported()) {
306
+ throw new Error('Web Workers not supported in this environment')
307
+ }
308
+
309
+ // Create worker from blob URL
310
+ const workerURL = createWorkerBlobURL()
311
+ this.worker = new Worker(workerURL, { type: 'module' })
312
+
313
+ // Set up message handler
314
+ this.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {
315
+ this.handleWorkerMessage(event.data)
316
+ }
317
+
318
+ this.worker.onerror = (error) => {
319
+ console.error('[ProofWorker] Worker error:', error)
320
+ // Reject all pending requests
321
+ for (const [id, { reject }] of this.pendingRequests) {
322
+ reject(new Error(`Worker error: ${error.message}`))
323
+ this.pendingRequests.delete(id)
324
+ }
325
+ }
326
+
327
+ // Initialize the worker
328
+ await this.sendRequest('init', undefined, config)
329
+ this._isReady = true
330
+
331
+ // Cleanup blob URL
332
+ URL.revokeObjectURL(workerURL)
333
+ }
334
+
335
+ /**
336
+ * Generate a proof using the worker
337
+ */
338
+ async generateProof(
339
+ type: 'funding' | 'validity' | 'fulfillment',
340
+ params: FundingProofParams | ValidityProofParams | FulfillmentProofParams,
341
+ onProgress?: (progress: NonNullable<WorkerResponse['progress']>) => void
342
+ ): Promise<ProofResult> {
343
+ if (!this._isReady || !this.worker) {
344
+ throw new Error('Worker not initialized. Call initialize() first.')
345
+ }
346
+
347
+ const messageType =
348
+ type === 'funding'
349
+ ? 'generateFundingProof'
350
+ : type === 'validity'
351
+ ? 'generateValidityProof'
352
+ : 'generateFulfillmentProof'
353
+
354
+ return this.sendRequest(messageType, params, undefined, onProgress)
355
+ }
356
+
357
+ /**
358
+ * Destroy the worker and free resources
359
+ */
360
+ async destroy(): Promise<void> {
361
+ if (this.worker) {
362
+ try {
363
+ await this.sendRequest('destroy')
364
+ } catch {
365
+ // Ignore errors during cleanup
366
+ }
367
+ this.worker.terminate()
368
+ this.worker = null
369
+ }
370
+ this._isReady = false
371
+ this.pendingRequests.clear()
372
+ }
373
+
374
+ /**
375
+ * Send a request to the worker
376
+ */
377
+ private sendRequest(
378
+ type: WorkerMessageType,
379
+ params?: FundingProofParams | ValidityProofParams | FulfillmentProofParams,
380
+ config?: { verbose?: boolean; oraclePublicKey?: { x: number[]; y: number[] } },
381
+ onProgress?: (progress: NonNullable<WorkerResponse['progress']>) => void
382
+ ): Promise<ProofResult> {
383
+ return new Promise((resolve, reject) => {
384
+ if (!this.worker) {
385
+ reject(new Error('Worker not available'))
386
+ return
387
+ }
388
+
389
+ const id = `req_${++this.requestCounter}_${Date.now()}`
390
+ this.pendingRequests.set(id, { resolve, reject, onProgress })
391
+
392
+ const request: WorkerRequest = { id, type, params, config }
393
+ this.worker.postMessage(request)
394
+ })
395
+ }
396
+
397
+ /**
398
+ * Handle messages from the worker
399
+ */
400
+ private handleWorkerMessage(response: WorkerResponse): void {
401
+ const pending = this.pendingRequests.get(response.id)
402
+ if (!pending) {
403
+ console.warn('[ProofWorker] Received response for unknown request:', response.id)
404
+ return
405
+ }
406
+
407
+ switch (response.type) {
408
+ case 'success':
409
+ this.pendingRequests.delete(response.id)
410
+ pending.resolve(response.result as ProofResult)
411
+ break
412
+ case 'error':
413
+ this.pendingRequests.delete(response.id)
414
+ pending.reject(new Error(response.error))
415
+ break
416
+ case 'progress':
417
+ if (pending.onProgress && response.progress) {
418
+ pending.onProgress(response.progress)
419
+ }
420
+ break
421
+ }
422
+ }
423
+ }
424
+
425
+ // Re-export types
426
+ export type { ProofResult }