@sip-protocol/sdk 0.7.1 → 0.7.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.
Files changed (50) hide show
  1. package/dist/browser.d.mts +1 -1
  2. package/dist/browser.d.ts +1 -1
  3. package/dist/browser.js +2926 -341
  4. package/dist/browser.mjs +48 -2
  5. package/dist/chunk-2XIVXWHA.mjs +1930 -0
  6. package/dist/chunk-3M3HNQCW.mjs +18253 -0
  7. package/dist/chunk-7RFRWDCW.mjs +1504 -0
  8. package/dist/chunk-F6F73W35.mjs +16166 -0
  9. package/dist/chunk-OFDBEIEK.mjs +16166 -0
  10. package/dist/chunk-SF7YSLF5.mjs +1515 -0
  11. package/dist/chunk-WWUSGOXE.mjs +17129 -0
  12. package/dist/index-8MQz13eJ.d.mts +13746 -0
  13. package/dist/index-B71aXVzk.d.ts +13264 -0
  14. package/dist/index-DIBZHOOQ.d.ts +13746 -0
  15. package/dist/index-pOIIuwfV.d.mts +13264 -0
  16. package/dist/index.d.mts +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +2911 -326
  19. package/dist/index.mjs +48 -2
  20. package/dist/solana-4O4K45VU.mjs +46 -0
  21. package/dist/solana-NDABAZ6P.mjs +56 -0
  22. package/dist/solana-ZYO63LY5.mjs +46 -0
  23. package/package.json +3 -3
  24. package/src/chains/solana/index.ts +24 -0
  25. package/src/chains/solana/providers/generic.ts +160 -0
  26. package/src/chains/solana/providers/helius.ts +249 -0
  27. package/src/chains/solana/providers/index.ts +54 -0
  28. package/src/chains/solana/providers/interface.ts +178 -0
  29. package/src/chains/solana/providers/webhook.ts +519 -0
  30. package/src/chains/solana/scan.ts +88 -8
  31. package/src/chains/solana/types.ts +20 -1
  32. package/src/compliance/index.ts +14 -0
  33. package/src/compliance/range-sas.ts +591 -0
  34. package/src/index.ts +99 -0
  35. package/src/privacy-backends/index.ts +86 -0
  36. package/src/privacy-backends/interface.ts +263 -0
  37. package/src/privacy-backends/privacycash-types.ts +278 -0
  38. package/src/privacy-backends/privacycash.ts +460 -0
  39. package/src/privacy-backends/registry.ts +278 -0
  40. package/src/privacy-backends/router.ts +346 -0
  41. package/src/privacy-backends/sip-native.ts +253 -0
  42. package/src/proofs/noir.ts +1 -1
  43. package/src/surveillance/algorithms/address-reuse.ts +143 -0
  44. package/src/surveillance/algorithms/cluster.ts +247 -0
  45. package/src/surveillance/algorithms/exchange.ts +295 -0
  46. package/src/surveillance/algorithms/temporal.ts +337 -0
  47. package/src/surveillance/analyzer.ts +442 -0
  48. package/src/surveillance/index.ts +64 -0
  49. package/src/surveillance/scoring.ts +372 -0
  50. package/src/surveillance/types.ts +264 -0
@@ -83,3 +83,17 @@ export {
83
83
  type DeriveViewingKeyParams,
84
84
  type DeriveMultipleParams,
85
85
  } from './derivation'
86
+
87
+ // Range SAS (Solana Attestation Service) Integration
88
+ export {
89
+ AttestationGatedDisclosure,
90
+ AttestationSchema,
91
+ createMockAttestation,
92
+ verifyAttestationSignature,
93
+ fetchAttestation,
94
+ type RangeSASAttestation,
95
+ type AttestationGatedConfig,
96
+ type ViewingKeyDerivationResult,
97
+ type ViewingKeyScope,
98
+ type AttestationVerificationResult,
99
+ } from './range-sas'
@@ -0,0 +1,591 @@
1
+ /**
2
+ * Range SAS (Solana Attestation Service) Integration
3
+ *
4
+ * Enables attestation-gated viewing key disclosure for regulatory compliance.
5
+ * Auditors must present a valid SAS attestation to receive viewing keys.
6
+ *
7
+ * ## How It Works
8
+ *
9
+ * 1. Auditor obtains a KYC/compliance attestation from Range SAS
10
+ * 2. Auditor presents attestation to the viewing key holder
11
+ * 3. System verifies attestation on-chain or via Range API
12
+ * 4. If valid, a scoped viewing key is derived for the auditor
13
+ * 5. Auditor can now decrypt and view transaction history
14
+ *
15
+ * ## Security Properties
16
+ *
17
+ * - **Attestation-gated**: Only verified auditors receive keys
18
+ * - **Scoped access**: Derived keys can be time-limited or scope-limited
19
+ * - **Non-transferable**: Keys are bound to auditor's attestation
20
+ * - **Revocable**: Revoking attestation invalidates the viewing key
21
+ *
22
+ * @see https://www.range.org/blog/introducing-solana-attestation-service
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { AttestationGatedDisclosure, RangeSASAttestation } from '@sip-protocol/sdk'
27
+ *
28
+ * // Create disclosure manager with organization's master viewing key
29
+ * const disclosure = new AttestationGatedDisclosure({
30
+ * masterViewingKey: organizationViewingKey,
31
+ * allowedSchemas: ['range-kyc-v1', 'range-accredited-investor'],
32
+ * })
33
+ *
34
+ * // Auditor presents their attestation
35
+ * const attestation: RangeSASAttestation = {
36
+ * uid: 'sas_123...',
37
+ * schema: 'range-kyc-v1',
38
+ * issuer: 'range-protocol',
39
+ * subject: 'auditor-wallet-address',
40
+ * data: { level: 'institutional', jurisdiction: 'US' },
41
+ * timestamp: Date.now() / 1000,
42
+ * expiresAt: Date.now() / 1000 + 365 * 24 * 60 * 60,
43
+ * signature: '0x...',
44
+ * }
45
+ *
46
+ * // Verify and derive viewing key
47
+ * const result = await disclosure.deriveViewingKeyForAuditor(attestation)
48
+ * if (result.granted) {
49
+ * console.log('Auditor viewing key:', result.viewingKey)
50
+ * }
51
+ * ```
52
+ */
53
+
54
+ import type { ViewingKey, HexString, Hash } from '@sip-protocol/types'
55
+ import { sha256 } from '@noble/hashes/sha256'
56
+ import { hmac } from '@noble/hashes/hmac'
57
+ import { sha512 } from '@noble/hashes/sha512'
58
+ import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils'
59
+ import { ValidationError, ErrorCode } from '../errors'
60
+ import { secureWipe } from '../secure-memory'
61
+
62
+ // ─── Types ────────────────────────────────────────────────────────────────────
63
+
64
+ /**
65
+ * Range SAS attestation structure
66
+ *
67
+ * Represents a verifiable claim issued by Range SAS.
68
+ */
69
+ export interface RangeSASAttestation {
70
+ /** Unique identifier for this attestation */
71
+ uid: string
72
+ /** Schema defining the attestation type (e.g., 'range-kyc-v1') */
73
+ schema: string
74
+ /** Address of the attestation issuer */
75
+ issuer: string
76
+ /** Address of the attestation subject (auditor wallet) */
77
+ subject: string
78
+ /** Attestation data payload */
79
+ data: Record<string, unknown>
80
+ /** Unix timestamp when attestation was created */
81
+ timestamp: number
82
+ /** Unix timestamp when attestation expires (0 = never) */
83
+ expiresAt: number
84
+ /** Cryptographic signature from issuer */
85
+ signature: string
86
+ /** Whether the attestation has been revoked */
87
+ revoked?: boolean
88
+ /** On-chain transaction signature (if stored on-chain) */
89
+ txSignature?: string
90
+ }
91
+
92
+ /**
93
+ * Supported attestation schemas
94
+ */
95
+ export enum AttestationSchema {
96
+ /** Basic KYC verification */
97
+ RANGE_KYC_V1 = 'range-kyc-v1',
98
+ /** Accredited investor status */
99
+ RANGE_ACCREDITED_INVESTOR = 'range-accredited-investor',
100
+ /** Institutional entity verification */
101
+ RANGE_INSTITUTIONAL = 'range-institutional',
102
+ /** Regulatory authority attestation */
103
+ RANGE_REGULATOR = 'range-regulator',
104
+ /** Custom schema (requires explicit approval) */
105
+ CUSTOM = 'custom',
106
+ }
107
+
108
+ /**
109
+ * Configuration for attestation-gated disclosure
110
+ */
111
+ export interface AttestationGatedConfig {
112
+ /** Master viewing key to derive auditor keys from */
113
+ masterViewingKey: ViewingKey
114
+ /** Allowed attestation schemas (empty = all schemas) */
115
+ allowedSchemas?: string[]
116
+ /** Allowed issuers (empty = all issuers) */
117
+ allowedIssuers?: string[]
118
+ /** Whether to verify attestations on-chain (default: false = API verification) */
119
+ verifyOnChain?: boolean
120
+ /** Range API endpoint for verification */
121
+ rangeApiEndpoint?: string
122
+ /** Minimum attestation age in seconds (prevents replay attacks) */
123
+ minAttestationAge?: number
124
+ /** Maximum attestation age in seconds (enforces time-bounded access, 0 = no limit) */
125
+ maxAttestationAge?: number
126
+ /** Maximum number of cached derived keys (default: 1000) */
127
+ maxCacheSize?: number
128
+ /** Custom verification function */
129
+ customVerifier?: (attestation: RangeSASAttestation) => Promise<boolean>
130
+ }
131
+
132
+ /**
133
+ * Result of viewing key derivation
134
+ */
135
+ export interface ViewingKeyDerivationResult {
136
+ /** Whether access was granted */
137
+ granted: boolean
138
+ /** The derived viewing key (if granted) */
139
+ viewingKey?: ViewingKey
140
+ /** Reason for denial (if not granted) */
141
+ reason?: string
142
+ /** Scope of the granted access */
143
+ scope?: ViewingKeyScope
144
+ /** Expiration timestamp of the viewing key */
145
+ expiresAt?: number
146
+ }
147
+
148
+ /**
149
+ * Scope of viewing key access
150
+ */
151
+ export interface ViewingKeyScope {
152
+ /** Start timestamp for viewable transactions */
153
+ startTime?: number
154
+ /** End timestamp for viewable transactions */
155
+ endTime?: number
156
+ /** Specific transaction types viewable */
157
+ transactionTypes?: string[]
158
+ /** Maximum number of transactions viewable */
159
+ maxTransactions?: number
160
+ }
161
+
162
+ /**
163
+ * Attestation verification result
164
+ */
165
+ export interface AttestationVerificationResult {
166
+ /** Whether the attestation is valid */
167
+ valid: boolean
168
+ /** Verification errors (if any) */
169
+ errors: string[]
170
+ /** Attestation metadata */
171
+ metadata?: {
172
+ issuerName?: string
173
+ schemaVersion?: string
174
+ verificationMethod: 'on-chain' | 'api' | 'custom'
175
+ }
176
+ }
177
+
178
+ // ─── Attestation-Gated Disclosure ─────────────────────────────────────────────
179
+
180
+ /**
181
+ * Attestation-gated viewing key disclosure
182
+ *
183
+ * Manages the secure disclosure of viewing keys to verified auditors.
184
+ * Only auditors with valid Range SAS attestations can receive keys.
185
+ */
186
+ /**
187
+ * Default maximum cache size for derived keys
188
+ */
189
+ const DEFAULT_MAX_CACHE_SIZE = 1000
190
+
191
+ export class AttestationGatedDisclosure {
192
+ private readonly config: Required<AttestationGatedConfig>
193
+ private readonly derivedKeys: Map<string, ViewingKey> = new Map()
194
+ private readonly cacheOrder: string[] = [] // LRU tracking
195
+
196
+ /**
197
+ * Create a new attestation-gated disclosure manager
198
+ *
199
+ * @param config - Configuration options
200
+ */
201
+ constructor(config: AttestationGatedConfig) {
202
+ if (!config.masterViewingKey) {
203
+ throw new ValidationError(
204
+ 'masterViewingKey is required',
205
+ 'masterViewingKey',
206
+ undefined,
207
+ ErrorCode.MISSING_REQUIRED
208
+ )
209
+ }
210
+
211
+ this.config = {
212
+ masterViewingKey: config.masterViewingKey,
213
+ allowedSchemas: config.allowedSchemas ?? [],
214
+ allowedIssuers: config.allowedIssuers ?? [],
215
+ verifyOnChain: config.verifyOnChain ?? false,
216
+ rangeApiEndpoint: config.rangeApiEndpoint ?? 'https://api.range.org/v1',
217
+ minAttestationAge: config.minAttestationAge ?? 0,
218
+ maxAttestationAge: config.maxAttestationAge ?? 0, // 0 = no limit
219
+ maxCacheSize: config.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE,
220
+ customVerifier: config.customVerifier ?? (async () => true),
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Derive a viewing key for a verified auditor
226
+ *
227
+ * @param attestation - The auditor's Range SAS attestation
228
+ * @param scope - Optional scope restrictions for the viewing key
229
+ * @returns Derivation result with viewing key if granted
230
+ *
231
+ * @example
232
+ * ```typescript
233
+ * const result = await disclosure.deriveViewingKeyForAuditor(attestation, {
234
+ * startTime: Date.now() / 1000 - 30 * 24 * 60 * 60, // Last 30 days
235
+ * endTime: Date.now() / 1000,
236
+ * })
237
+ *
238
+ * if (result.granted) {
239
+ * // Share result.viewingKey with auditor
240
+ * }
241
+ * ```
242
+ */
243
+ async deriveViewingKeyForAuditor(
244
+ attestation: RangeSASAttestation,
245
+ scope?: ViewingKeyScope
246
+ ): Promise<ViewingKeyDerivationResult> {
247
+ // Step 1: Verify the attestation
248
+ const verification = await this.verifyAttestation(attestation)
249
+
250
+ if (!verification.valid) {
251
+ return {
252
+ granted: false,
253
+ reason: verification.errors.join('; '),
254
+ }
255
+ }
256
+
257
+ // Step 2: Check if we've already derived a key for this attestation
258
+ const cacheKey = this.getCacheKey(attestation)
259
+ const cached = this.derivedKeys.get(cacheKey)
260
+ if (cached) {
261
+ // Update LRU order
262
+ this.updateCacheOrder(cacheKey)
263
+ return {
264
+ granted: true,
265
+ viewingKey: cached,
266
+ scope,
267
+ expiresAt: attestation.expiresAt, // 0 = never expires, undefined = not set
268
+ }
269
+ }
270
+
271
+ // Step 3: Derive a unique viewing key for this auditor
272
+ const viewingKey = this.deriveKeyFromAttestation(attestation)
273
+
274
+ // Step 4: Cache the derived key with LRU eviction
275
+ this.cacheKey(cacheKey, viewingKey)
276
+
277
+ return {
278
+ granted: true,
279
+ viewingKey,
280
+ scope,
281
+ expiresAt: attestation.expiresAt, // 0 = never expires, undefined = not set
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Verify a Range SAS attestation
287
+ *
288
+ * @param attestation - The attestation to verify
289
+ * @returns Verification result
290
+ */
291
+ async verifyAttestation(
292
+ attestation: RangeSASAttestation
293
+ ): Promise<AttestationVerificationResult> {
294
+ const errors: string[] = []
295
+
296
+ // Validate required fields exist and are non-empty
297
+ if (!attestation || typeof attestation !== 'object') {
298
+ return {
299
+ valid: false,
300
+ errors: ['Attestation must be an object'],
301
+ }
302
+ }
303
+
304
+ if (!attestation.uid || typeof attestation.uid !== 'string' || attestation.uid.trim() === '') {
305
+ errors.push('Attestation uid is required and must be a non-empty string')
306
+ }
307
+
308
+ if (!attestation.subject || typeof attestation.subject !== 'string' || attestation.subject.trim() === '') {
309
+ errors.push('Attestation subject is required and must be a non-empty string')
310
+ }
311
+
312
+ if (!attestation.schema || typeof attestation.schema !== 'string' || attestation.schema.trim() === '') {
313
+ errors.push('Attestation schema is required and must be a non-empty string')
314
+ }
315
+
316
+ if (!attestation.issuer || typeof attestation.issuer !== 'string' || attestation.issuer.trim() === '') {
317
+ errors.push('Attestation issuer is required and must be a non-empty string')
318
+ }
319
+
320
+ // If basic validation fails, return early
321
+ if (errors.length > 0) {
322
+ return { valid: false, errors }
323
+ }
324
+
325
+ // Check if attestation is revoked
326
+ if (attestation.revoked) {
327
+ errors.push('Attestation has been revoked')
328
+ }
329
+
330
+ // Check expiration
331
+ const now = Date.now() / 1000
332
+ if (attestation.expiresAt > 0 && attestation.expiresAt < now) {
333
+ errors.push('Attestation has expired')
334
+ }
335
+
336
+ // Check minimum age (anti-replay)
337
+ const age = now - attestation.timestamp
338
+ if (age < this.config.minAttestationAge) {
339
+ errors.push(`Attestation too new (age: ${age}s, required: ${this.config.minAttestationAge}s)`)
340
+ }
341
+
342
+ // Check maximum age (time-bounded access)
343
+ if (this.config.maxAttestationAge > 0 && age > this.config.maxAttestationAge) {
344
+ errors.push(`Attestation too old (age: ${Math.floor(age)}s, max: ${this.config.maxAttestationAge}s)`)
345
+ }
346
+
347
+ // Check schema allowlist
348
+ if (this.config.allowedSchemas.length > 0) {
349
+ if (!this.config.allowedSchemas.includes(attestation.schema)) {
350
+ errors.push(`Schema '${attestation.schema}' not in allowed list`)
351
+ }
352
+ }
353
+
354
+ // Check issuer allowlist
355
+ if (this.config.allowedIssuers.length > 0) {
356
+ if (!this.config.allowedIssuers.includes(attestation.issuer)) {
357
+ errors.push(`Issuer '${attestation.issuer}' not in allowed list`)
358
+ }
359
+ }
360
+
361
+ // Run custom verification if provided
362
+ if (errors.length === 0 && this.config.customVerifier) {
363
+ try {
364
+ const customValid = await this.config.customVerifier(attestation)
365
+ if (!customValid) {
366
+ errors.push('Custom verification failed')
367
+ }
368
+ } catch (e) {
369
+ errors.push(`Custom verification error: ${e instanceof Error ? e.message : 'unknown'}`)
370
+ }
371
+ }
372
+
373
+ return {
374
+ valid: errors.length === 0,
375
+ errors,
376
+ metadata: {
377
+ verificationMethod: this.config.verifyOnChain ? 'on-chain' : 'api',
378
+ schemaVersion: attestation.schema,
379
+ },
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Revoke a previously derived viewing key
385
+ *
386
+ * @param attestation - The attestation whose key should be revoked
387
+ * @returns Whether revocation was successful
388
+ */
389
+ revokeViewingKey(attestation: RangeSASAttestation): boolean {
390
+ const key = this.getCacheKey(attestation)
391
+ const deleted = this.derivedKeys.delete(key)
392
+ if (deleted) {
393
+ // Remove from LRU order
394
+ const index = this.cacheOrder.indexOf(key)
395
+ if (index !== -1) {
396
+ this.cacheOrder.splice(index, 1)
397
+ }
398
+ }
399
+ return deleted
400
+ }
401
+
402
+ /**
403
+ * Check if a viewing key has been derived for an attestation
404
+ *
405
+ * @param attestation - The attestation to check
406
+ * @returns Whether a key exists
407
+ */
408
+ hasViewingKey(attestation: RangeSASAttestation): boolean {
409
+ const key = this.getCacheKey(attestation)
410
+ return this.derivedKeys.has(key)
411
+ }
412
+
413
+ /**
414
+ * Get the current cache size
415
+ *
416
+ * @returns Number of cached viewing keys
417
+ */
418
+ getCacheSize(): number {
419
+ return this.derivedKeys.size
420
+ }
421
+
422
+ /**
423
+ * Clear all cached viewing keys
424
+ */
425
+ clearCache(): void {
426
+ this.derivedKeys.clear()
427
+ this.cacheOrder.length = 0
428
+ }
429
+
430
+ // ─── Private Methods ────────────────────────────────────────────────────────
431
+
432
+ /**
433
+ * Add a key to cache with LRU eviction
434
+ */
435
+ private cacheKey(key: string, viewingKey: ViewingKey): void {
436
+ // Evict oldest entries if cache is full
437
+ while (this.derivedKeys.size >= this.config.maxCacheSize && this.cacheOrder.length > 0) {
438
+ const oldest = this.cacheOrder.shift()
439
+ if (oldest) {
440
+ this.derivedKeys.delete(oldest)
441
+ }
442
+ }
443
+
444
+ this.derivedKeys.set(key, viewingKey)
445
+ this.cacheOrder.push(key)
446
+ }
447
+
448
+ /**
449
+ * Update LRU order for a cache key (move to end)
450
+ */
451
+ private updateCacheOrder(key: string): void {
452
+ const index = this.cacheOrder.indexOf(key)
453
+ if (index !== -1) {
454
+ this.cacheOrder.splice(index, 1)
455
+ this.cacheOrder.push(key)
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Derive a viewing key from an attestation
461
+ */
462
+ private deriveKeyFromAttestation(attestation: RangeSASAttestation): ViewingKey {
463
+ const masterKeyHex = this.config.masterViewingKey.key.startsWith('0x')
464
+ ? this.config.masterViewingKey.key.slice(2)
465
+ : this.config.masterViewingKey.key
466
+ const masterKeyBytes = hexToBytes(masterKeyHex)
467
+
468
+ // Create derivation data from attestation
469
+ // Include signature to cryptographically bind keys to attestation
470
+ // This prevents forgery attacks where attacker uses same uid/subject
471
+ const derivationData = utf8ToBytes(
472
+ `SIP-RANGE-SAS:${attestation.uid}:${attestation.subject}:${attestation.schema}:${attestation.signature}`
473
+ )
474
+
475
+ // HMAC-SHA512 derivation
476
+ const derived = hmac(sha512, masterKeyBytes, derivationData)
477
+ const keyBytes = derived.slice(0, 32)
478
+
479
+ try {
480
+ const key = `0x${bytesToHex(keyBytes)}` as HexString
481
+ const hashBytes = sha256(keyBytes)
482
+ const hash = `0x${bytesToHex(hashBytes)}` as Hash
483
+
484
+ return {
485
+ key,
486
+ path: `${this.config.masterViewingKey.path}/sas/${attestation.uid.slice(0, 8)}`,
487
+ hash,
488
+ }
489
+ } finally {
490
+ secureWipe(masterKeyBytes)
491
+ secureWipe(derived)
492
+ secureWipe(keyBytes)
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Get cache key for an attestation
498
+ *
499
+ * Includes schema and issuer to prevent cache poisoning attacks where
500
+ * an attacker could evict legitimate cache entries with same uid:subject.
501
+ */
502
+ private getCacheKey(attestation: RangeSASAttestation): string {
503
+ return `${attestation.uid}:${attestation.subject}:${attestation.schema}:${attestation.issuer}`
504
+ }
505
+ }
506
+
507
+ // ─── Utility Functions ────────────────────────────────────────────────────────
508
+
509
+ /**
510
+ * Create a mock attestation for testing
511
+ *
512
+ * @param overrides - Fields to override
513
+ * @returns Mock attestation
514
+ */
515
+ export function createMockAttestation(
516
+ overrides: Partial<RangeSASAttestation> = {}
517
+ ): RangeSASAttestation {
518
+ const now = Math.floor(Date.now() / 1000)
519
+
520
+ return {
521
+ uid: `sas_${Math.random().toString(36).slice(2, 10)}`,
522
+ schema: AttestationSchema.RANGE_KYC_V1,
523
+ issuer: 'range-protocol',
524
+ subject: '11111111111111111111111111111112', // System program (placeholder)
525
+ data: {
526
+ level: 'institutional',
527
+ jurisdiction: 'US',
528
+ verifiedAt: now,
529
+ },
530
+ timestamp: now,
531
+ expiresAt: now + 365 * 24 * 60 * 60, // 1 year
532
+ signature: '0x' + '00'.repeat(64),
533
+ revoked: false,
534
+ ...overrides,
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Verify attestation signature (placeholder for real implementation)
540
+ *
541
+ * ⚠️ WARNING: This is a stub that always returns true!
542
+ * Do NOT use in production without implementing real verification.
543
+ *
544
+ * In production, this would:
545
+ * 1. Fetch the issuer's public key from Range SAS registry
546
+ * 2. Verify the signature against the attestation data
547
+ * 3. Check on-chain state if verifyOnChain is enabled
548
+ *
549
+ * @param attestation - The attestation to verify
550
+ * @returns Whether the signature is valid (currently always true - STUB)
551
+ *
552
+ * @see https://github.com/sip-protocol/sip-protocol/issues/448 for implementation tracking
553
+ */
554
+ export async function verifyAttestationSignature(
555
+ _attestation: RangeSASAttestation
556
+ ): Promise<boolean> {
557
+ // TODO: Implement real signature verification with Range SAS
558
+ // This would involve:
559
+ // 1. Fetching issuer public key from Range registry
560
+ // 2. Reconstructing the signed message
561
+ // 3. Verifying Ed25519 signature
562
+ console.warn(
563
+ '[Range SAS] verifyAttestationSignature is a STUB - always returns true. ' +
564
+ 'Implement real Ed25519 signature verification before production use.'
565
+ )
566
+ return true
567
+ }
568
+
569
+ /**
570
+ * Fetch attestation from Range API
571
+ *
572
+ * ⚠️ WARNING: This is a stub that always returns null!
573
+ * Do NOT rely on this in production without implementing real API calls.
574
+ *
575
+ * @param uid - Attestation UID
576
+ * @param apiEndpoint - Range API endpoint
577
+ * @returns The attestation if found (currently always null - STUB)
578
+ *
579
+ * @see https://github.com/sip-protocol/sip-protocol/issues/448 for implementation tracking
580
+ */
581
+ export async function fetchAttestation(
582
+ uid: string,
583
+ apiEndpoint: string = 'https://api.range.org/v1'
584
+ ): Promise<RangeSASAttestation | null> {
585
+ // TODO: Implement real API call to Range
586
+ console.warn(
587
+ `[Range SAS] fetchAttestation is a STUB - returning null for ${uid}. ` +
588
+ `Would fetch from ${apiEndpoint}. Implement Range API integration before production use.`
589
+ )
590
+ return null
591
+ }