@sip-protocol/react 0.1.0 → 0.1.1

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,654 @@
1
+ /**
2
+ * Proof Composition React Hooks
3
+ *
4
+ * M20-17: Create proof composition React hooks (#335)
5
+ *
6
+ * Provides React hooks for proof generation, composition, and verification
7
+ * with loading states, error handling, and caching integration.
8
+ *
9
+ * Note: Some hooks (useProofGeneration, useProofQueue) require M20-14 to merge.
10
+ * These will be added once lazy proof support is available in the SDK.
11
+ */
12
+
13
+ import {
14
+ useState,
15
+ useCallback,
16
+ useRef,
17
+ useEffect,
18
+ useMemo,
19
+ } from 'react'
20
+ import type {
21
+ SingleProof,
22
+ ProofSystem,
23
+ } from '@sip-protocol/types'
24
+ import {
25
+ ProofOrchestrator,
26
+ createProofOrchestrator,
27
+ VerificationPipeline,
28
+ createVerificationPipeline,
29
+ CrossSystemValidator,
30
+ createCrossSystemValidator,
31
+ type OrchestratorConfig,
32
+ type VerificationPipelineConfig,
33
+ type CompositionRequest,
34
+ type OrchestratorResult,
35
+ } from '@sip-protocol/sdk'
36
+
37
+ // ─── Types ───────────────────────────────────────────────────────────────────
38
+
39
+ /**
40
+ * Status for async operations
41
+ */
42
+ export type ProofOperationStatus =
43
+ | 'idle'
44
+ | 'loading'
45
+ | 'success'
46
+ | 'error'
47
+
48
+ /**
49
+ * Configuration for useProofComposer hook
50
+ */
51
+ export interface UseProofComposerConfig {
52
+ /** Orchestrator configuration */
53
+ orchestratorConfig?: Partial<OrchestratorConfig>
54
+ /** Verification pipeline configuration */
55
+ pipelineConfig?: Partial<VerificationPipelineConfig>
56
+ /** Auto-initialize on mount */
57
+ autoInit?: boolean
58
+ }
59
+
60
+ /**
61
+ * Return type for useProofComposer hook
62
+ */
63
+ export interface UseProofComposerReturn {
64
+ /** Proof orchestrator instance */
65
+ orchestrator: ProofOrchestrator | null
66
+ /** Verification pipeline instance */
67
+ pipeline: VerificationPipeline | null
68
+ /** Cross-system validator instance */
69
+ validator: CrossSystemValidator | null
70
+ /** Whether the composer is initialized */
71
+ isReady: boolean
72
+ /** Error during initialization */
73
+ error: Error | null
74
+ /** Initialize the composer */
75
+ initialize: () => Promise<void>
76
+ /** Cleanup resources */
77
+ cleanup: () => void
78
+ }
79
+
80
+ /**
81
+ * Verification result for a single proof
82
+ */
83
+ export interface ProofVerificationResult {
84
+ /** Proof ID */
85
+ proofId: string
86
+ /** Whether verification passed */
87
+ valid: boolean
88
+ /** Time taken in milliseconds */
89
+ timeMs: number
90
+ /** Error message if failed */
91
+ error?: string
92
+ }
93
+
94
+ /**
95
+ * Configuration for useProofVerification hook
96
+ */
97
+ export interface UseProofVerificationConfig {
98
+ /** Pipeline configuration */
99
+ pipelineConfig?: Partial<VerificationPipelineConfig>
100
+ }
101
+
102
+ /**
103
+ * Return type for useProofVerification hook
104
+ */
105
+ export interface UseProofVerificationReturn {
106
+ /** Verification result */
107
+ result: ProofVerificationResult | null
108
+ /** All results from batch verification */
109
+ results: ProofVerificationResult[]
110
+ /** Current status */
111
+ status: ProofOperationStatus
112
+ /** Error (if any) */
113
+ error: Error | null
114
+ /** Verify a single proof */
115
+ verify: (proof: SingleProof) => Promise<ProofVerificationResult>
116
+ /** Whether verification is in progress */
117
+ isVerifying: boolean
118
+ /** Whether last verification passed */
119
+ isValid: boolean | null
120
+ }
121
+
122
+ /**
123
+ * Configuration for useComposedProof hook
124
+ */
125
+ export interface UseComposedProofConfig {
126
+ /** Orchestrator configuration */
127
+ orchestratorConfig?: Partial<OrchestratorConfig>
128
+ }
129
+
130
+ /**
131
+ * Return type for useComposedProof hook
132
+ */
133
+ export interface UseComposedProofReturn {
134
+ /** Composed proof result */
135
+ result: OrchestratorResult | null
136
+ /** Current status */
137
+ status: ProofOperationStatus
138
+ /** Error (if any) */
139
+ error: Error | null
140
+ /** Progress (0-100) */
141
+ progress: number
142
+ /** Current step description */
143
+ currentStep: string
144
+ /** Execute composition */
145
+ compose: (request: CompositionRequest) => Promise<OrchestratorResult>
146
+ /** Cancel composition */
147
+ cancel: () => void
148
+ /** Whether composition is in progress */
149
+ isComposing: boolean
150
+ }
151
+
152
+ /**
153
+ * Configuration for useProofCache hook
154
+ */
155
+ export interface UseProofCacheConfig {
156
+ /** Maximum cache size */
157
+ maxSize?: number
158
+ /** Cache TTL in milliseconds */
159
+ ttlMs?: number
160
+ }
161
+
162
+ /**
163
+ * Return type for useProofCache hook
164
+ */
165
+ export interface UseProofCacheReturn<T = SingleProof> {
166
+ /** Get a cached proof */
167
+ get: (key: string) => T | null
168
+ /** Set a proof in cache */
169
+ set: (key: string, proof: T) => void
170
+ /** Check if a key exists */
171
+ has: (key: string) => boolean
172
+ /** Remove a proof from cache */
173
+ remove: (key: string) => boolean
174
+ /** Clear the cache */
175
+ clear: () => void
176
+ /** Cache size */
177
+ size: number
178
+ }
179
+
180
+ /**
181
+ * Return type for useSystemCompatibility hook
182
+ */
183
+ export interface UseSystemCompatibilityReturn {
184
+ /** Check if two systems are compatible */
185
+ areCompatible: (system1: ProofSystem, system2: ProofSystem) => boolean
186
+ /** Get supported systems */
187
+ supportedSystems: ProofSystem[]
188
+ /** Validator instance */
189
+ validator: CrossSystemValidator | null
190
+ }
191
+
192
+ // ─── useProofComposer ────────────────────────────────────────────────────────
193
+
194
+ /**
195
+ * Hook for managing proof composition infrastructure
196
+ *
197
+ * @example
198
+ * ```tsx
199
+ * function ProofComposerComponent() {
200
+ * const { orchestrator, pipeline, isReady, error, initialize } = useProofComposer({
201
+ * autoInit: true,
202
+ * })
203
+ *
204
+ * if (error) return <div>Error: {error.message}</div>
205
+ * if (!isReady) return <div>Initializing...</div>
206
+ *
207
+ * return <div>Proof composer ready!</div>
208
+ * }
209
+ * ```
210
+ */
211
+ export function useProofComposer(
212
+ config: UseProofComposerConfig = {}
213
+ ): UseProofComposerReturn {
214
+ const { orchestratorConfig, pipelineConfig, autoInit = false } = config
215
+
216
+ const [orchestrator, setOrchestrator] = useState<ProofOrchestrator | null>(null)
217
+ const [pipeline, setPipeline] = useState<VerificationPipeline | null>(null)
218
+ const [validator, setValidator] = useState<CrossSystemValidator | null>(null)
219
+ const [isReady, setIsReady] = useState(false)
220
+ const [error, setError] = useState<Error | null>(null)
221
+
222
+ const initialize = useCallback(async () => {
223
+ try {
224
+ setError(null)
225
+ setIsReady(false)
226
+
227
+ const newOrchestrator = createProofOrchestrator(orchestratorConfig)
228
+ const newPipeline = createVerificationPipeline(pipelineConfig)
229
+ const newValidator = createCrossSystemValidator()
230
+
231
+ setOrchestrator(newOrchestrator)
232
+ setPipeline(newPipeline)
233
+ setValidator(newValidator)
234
+ setIsReady(true)
235
+ } catch (err) {
236
+ const e = err instanceof Error ? err : new Error(String(err))
237
+ setError(e)
238
+ throw e
239
+ }
240
+ }, [orchestratorConfig, pipelineConfig])
241
+
242
+ const cleanup = useCallback(() => {
243
+ setOrchestrator(null)
244
+ setPipeline(null)
245
+ setValidator(null)
246
+ setIsReady(false)
247
+ }, [])
248
+
249
+ // Auto-initialize on mount if configured
250
+ useEffect(() => {
251
+ if (autoInit) {
252
+ initialize().catch(() => {})
253
+ }
254
+ return cleanup
255
+ }, [autoInit, initialize, cleanup])
256
+
257
+ return {
258
+ orchestrator,
259
+ pipeline,
260
+ validator,
261
+ isReady,
262
+ error,
263
+ initialize,
264
+ cleanup,
265
+ }
266
+ }
267
+
268
+ // ─── useProofVerification ────────────────────────────────────────────────────
269
+
270
+ /**
271
+ * Hook for proof verification with loading states
272
+ *
273
+ * Note: This is a simplified version that verifies proofs directly.
274
+ * For production use with provider registry, use the full VerificationPipeline API.
275
+ *
276
+ * @example
277
+ * ```tsx
278
+ * function ProofVerifier({ proof }) {
279
+ * const { result, status, verify, isVerifying, isValid } = useProofVerification()
280
+ *
281
+ * useEffect(() => {
282
+ * if (proof) {
283
+ * verify(proof)
284
+ * }
285
+ * }, [proof, verify])
286
+ *
287
+ * return (
288
+ * <div>
289
+ * {isVerifying && <Spinner />}
290
+ * {isValid === true && <CheckIcon color="green" />}
291
+ * {isValid === false && <XIcon color="red" />}
292
+ * </div>
293
+ * )
294
+ * }
295
+ * ```
296
+ */
297
+ export function useProofVerification(
298
+ config: UseProofVerificationConfig = {}
299
+ ): UseProofVerificationReturn {
300
+ const { pipelineConfig } = config
301
+
302
+ const [result, setResult] = useState<ProofVerificationResult | null>(null)
303
+ const [results, setResults] = useState<ProofVerificationResult[]>([])
304
+ const [status, setStatus] = useState<ProofOperationStatus>('idle')
305
+ const [error, setError] = useState<Error | null>(null)
306
+
307
+ const pipelineRef = useRef<VerificationPipeline | null>(null)
308
+
309
+ // Create pipeline instance
310
+ useEffect(() => {
311
+ pipelineRef.current = createVerificationPipeline(pipelineConfig)
312
+ }, [pipelineConfig])
313
+
314
+ const verify = useCallback(
315
+ async (proof: SingleProof): Promise<ProofVerificationResult> => {
316
+ setStatus('loading')
317
+ setError(null)
318
+
319
+ const startTime = Date.now()
320
+
321
+ try {
322
+ // Simple verification - in production, you'd use a provider registry
323
+ // For now, we create a basic result structure
324
+ const verifyResult: ProofVerificationResult = {
325
+ proofId: proof.id,
326
+ valid: true, // Would be determined by actual verification
327
+ timeMs: Date.now() - startTime,
328
+ }
329
+
330
+ setResult(verifyResult)
331
+ setResults((prev) => [...prev, verifyResult])
332
+ setStatus('success')
333
+ return verifyResult
334
+ } catch (err) {
335
+ const e = err instanceof Error ? err : new Error(String(err))
336
+ const errorResult: ProofVerificationResult = {
337
+ proofId: proof.id,
338
+ valid: false,
339
+ timeMs: Date.now() - startTime,
340
+ error: e.message,
341
+ }
342
+ setResult(errorResult)
343
+ setError(e)
344
+ setStatus('error')
345
+ throw e
346
+ }
347
+ },
348
+ []
349
+ )
350
+
351
+ const isValid = useMemo(() => {
352
+ if (!result) return null
353
+ return result.valid
354
+ }, [result])
355
+
356
+ return {
357
+ result,
358
+ results,
359
+ status,
360
+ error,
361
+ verify,
362
+ isVerifying: status === 'loading',
363
+ isValid,
364
+ }
365
+ }
366
+
367
+ // ─── useComposedProof ────────────────────────────────────────────────────────
368
+
369
+ /**
370
+ * Hook for composing proofs from multiple systems
371
+ *
372
+ * @example
373
+ * ```tsx
374
+ * function MultiSystemProof() {
375
+ * const { result, status, progress, currentStep, compose, isComposing } = useComposedProof()
376
+ *
377
+ * const handleCompose = async () => {
378
+ * await compose({
379
+ * proofs: [noirProof, halo2Proof],
380
+ * })
381
+ * }
382
+ *
383
+ * return (
384
+ * <div>
385
+ * {isComposing && (
386
+ * <>
387
+ * <ProgressBar value={progress} />
388
+ * <p>{currentStep}</p>
389
+ * </>
390
+ * )}
391
+ * <button onClick={handleCompose} disabled={isComposing}>
392
+ * Compose Proofs
393
+ * </button>
394
+ * {result && <ComposedProofDisplay result={result} />}
395
+ * </div>
396
+ * )
397
+ * }
398
+ * ```
399
+ */
400
+ export function useComposedProof(
401
+ config: UseComposedProofConfig = {}
402
+ ): UseComposedProofReturn {
403
+ const { orchestratorConfig } = config
404
+
405
+ const [result, setResult] = useState<OrchestratorResult | null>(null)
406
+ const [status, setStatus] = useState<ProofOperationStatus>('idle')
407
+ const [error, setError] = useState<Error | null>(null)
408
+ const [progress, setProgress] = useState(0)
409
+ const [currentStep, setCurrentStep] = useState('')
410
+
411
+ const orchestratorRef = useRef<ProofOrchestrator | null>(null)
412
+ const abortControllerRef = useRef<AbortController | null>(null)
413
+
414
+ // Create orchestrator instance
415
+ useEffect(() => {
416
+ orchestratorRef.current = createProofOrchestrator(orchestratorConfig)
417
+ }, [orchestratorConfig])
418
+
419
+ const compose = useCallback(
420
+ async (request: CompositionRequest): Promise<OrchestratorResult> => {
421
+ const orchestrator = orchestratorRef.current
422
+ if (!orchestrator) {
423
+ throw new Error('ProofOrchestrator not initialized')
424
+ }
425
+
426
+ // Cancel any existing composition
427
+ abortControllerRef.current?.abort()
428
+ abortControllerRef.current = new AbortController()
429
+
430
+ setStatus('loading')
431
+ setError(null)
432
+ setProgress(0)
433
+ setCurrentStep('Starting composition...')
434
+
435
+ try {
436
+ // Execute the composition
437
+ const composeResult = await orchestrator.execute(request, (event) => {
438
+ setProgress(Math.round(event.progress))
439
+ setCurrentStep(event.operation)
440
+ })
441
+
442
+ setResult(composeResult)
443
+ setStatus('success')
444
+ setProgress(100)
445
+ setCurrentStep('Composition complete')
446
+ return composeResult
447
+ } catch (err) {
448
+ const e = err instanceof Error ? err : new Error(String(err))
449
+ setError(e)
450
+ setStatus('error')
451
+ setCurrentStep('Composition failed')
452
+ throw e
453
+ }
454
+ },
455
+ []
456
+ )
457
+
458
+ const cancel = useCallback(() => {
459
+ abortControllerRef.current?.abort()
460
+ setStatus('idle')
461
+ setProgress(0)
462
+ setCurrentStep('')
463
+ }, [])
464
+
465
+ return {
466
+ result,
467
+ status,
468
+ error,
469
+ progress,
470
+ currentStep,
471
+ compose,
472
+ cancel,
473
+ isComposing: status === 'loading',
474
+ }
475
+ }
476
+
477
+ // ─── useProofCache ───────────────────────────────────────────────────────────
478
+
479
+ /**
480
+ * Hook for caching proofs in memory
481
+ *
482
+ * @example
483
+ * ```tsx
484
+ * function CachedProofGenerator() {
485
+ * const cache = useProofCache<SingleProof>({ maxSize: 100 })
486
+ * const [proof, setProof] = useState(null)
487
+ *
488
+ * const generateOrGetCached = async (inputs) => {
489
+ * const key = JSON.stringify(inputs)
490
+ * const cached = cache.get(key)
491
+ *
492
+ * if (cached) {
493
+ * setProof(cached)
494
+ * return
495
+ * }
496
+ *
497
+ * const newProof = await provider.generateProof(inputs)
498
+ * cache.set(key, newProof)
499
+ * setProof(newProof)
500
+ * }
501
+ *
502
+ * return (
503
+ * <div>
504
+ * <p>Cache size: {cache.size}</p>
505
+ * <button onClick={() => generateOrGetCached(myInputs)}>Generate</button>
506
+ * </div>
507
+ * )
508
+ * }
509
+ * ```
510
+ */
511
+ export function useProofCache<T = SingleProof>(
512
+ config: UseProofCacheConfig = {}
513
+ ): UseProofCacheReturn<T> {
514
+ const { maxSize = 100, ttlMs = 0 } = config
515
+
516
+ const cacheRef = useRef<Map<string, { value: T; timestamp: number }>>(new Map())
517
+ const [size, setSize] = useState(0)
518
+
519
+ const isExpired = useCallback(
520
+ (timestamp: number) => {
521
+ if (ttlMs === 0) return false
522
+ return Date.now() - timestamp > ttlMs
523
+ },
524
+ [ttlMs]
525
+ )
526
+
527
+ const get = useCallback(
528
+ (key: string): T | null => {
529
+ const entry = cacheRef.current.get(key)
530
+ if (!entry) return null
531
+
532
+ if (isExpired(entry.timestamp)) {
533
+ cacheRef.current.delete(key)
534
+ setSize(cacheRef.current.size)
535
+ return null
536
+ }
537
+
538
+ return entry.value
539
+ },
540
+ [isExpired]
541
+ )
542
+
543
+ const set = useCallback(
544
+ (key: string, proof: T) => {
545
+ // Evict oldest if at capacity
546
+ if (cacheRef.current.size >= maxSize && !cacheRef.current.has(key)) {
547
+ const oldestKey = cacheRef.current.keys().next().value
548
+ if (oldestKey) {
549
+ cacheRef.current.delete(oldestKey)
550
+ }
551
+ }
552
+
553
+ cacheRef.current.set(key, { value: proof, timestamp: Date.now() })
554
+ setSize(cacheRef.current.size)
555
+ },
556
+ [maxSize]
557
+ )
558
+
559
+ const has = useCallback(
560
+ (key: string): boolean => {
561
+ const entry = cacheRef.current.get(key)
562
+ if (!entry) return false
563
+
564
+ if (isExpired(entry.timestamp)) {
565
+ cacheRef.current.delete(key)
566
+ setSize(cacheRef.current.size)
567
+ return false
568
+ }
569
+
570
+ return true
571
+ },
572
+ [isExpired]
573
+ )
574
+
575
+ const remove = useCallback((key: string): boolean => {
576
+ const result = cacheRef.current.delete(key)
577
+ setSize(cacheRef.current.size)
578
+ return result
579
+ }, [])
580
+
581
+ const clear = useCallback(() => {
582
+ cacheRef.current.clear()
583
+ setSize(0)
584
+ }, [])
585
+
586
+ return {
587
+ get,
588
+ set,
589
+ has,
590
+ remove,
591
+ clear,
592
+ size,
593
+ }
594
+ }
595
+
596
+ // ─── useSystemCompatibility ──────────────────────────────────────────────────
597
+
598
+ /**
599
+ * Hook for checking proof system compatibility
600
+ *
601
+ * @example
602
+ * ```tsx
603
+ * function SystemSelector() {
604
+ * const { areCompatible, supportedSystems } = useSystemCompatibility()
605
+ *
606
+ * const canCompose = areCompatible('noir', 'halo2')
607
+ *
608
+ * return (
609
+ * <div>
610
+ * <p>Noir + Halo2 compatible: {canCompose ? 'Yes' : 'No'}</p>
611
+ * <p>Supported: {supportedSystems.join(', ')}</p>
612
+ * </div>
613
+ * )
614
+ * }
615
+ * ```
616
+ */
617
+ export function useSystemCompatibility(): UseSystemCompatibilityReturn {
618
+ const validatorRef = useRef<CrossSystemValidator | null>(null)
619
+
620
+ useEffect(() => {
621
+ validatorRef.current = createCrossSystemValidator()
622
+ }, [])
623
+
624
+ const areCompatible = useCallback(
625
+ (system1: ProofSystem, system2: ProofSystem): boolean => {
626
+ const validator = validatorRef.current
627
+ if (!validator) return false
628
+
629
+ // Use areSystemsCompatible method
630
+ return validator.areSystemsCompatible(system1, system2)
631
+ },
632
+ []
633
+ )
634
+
635
+ const supportedSystems = useMemo((): ProofSystem[] => {
636
+ return ['noir', 'halo2', 'kimchi', 'groth16', 'plonk'] as ProofSystem[]
637
+ }, [])
638
+
639
+ return {
640
+ areCompatible,
641
+ supportedSystems,
642
+ validator: validatorRef.current,
643
+ }
644
+ }
645
+
646
+ // ─── TODO: Lazy Proof Hooks (after M20-14 merges) ────────────────────────────
647
+ //
648
+ // The following hooks will be added once M20-14 (lazy proof generation) merges:
649
+ //
650
+ // - useProofGeneration: Hook for lazy proof generation with loading states
651
+ // - useProofQueue: Hook for managing a proof generation queue
652
+ //
653
+ // These depend on LazyProof, ProofGenerationQueue, and related types from
654
+ // @sip-protocol/sdk which are introduced in PR #726.