@sip-protocol/sdk 0.7.3 → 0.7.4

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 (264) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  4. package/dist/browser.d.mts +10 -4
  5. package/dist/browser.d.ts +10 -4
  6. package/dist/browser.js +47556 -19603
  7. package/dist/browser.mjs +628 -48
  8. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  9. package/dist/chunk-5D7A3L3W.mjs +717 -0
  10. package/dist/chunk-64AYA5F5.mjs +7834 -0
  11. package/dist/chunk-GMDGB22A.mjs +379 -0
  12. package/dist/chunk-I534WKN7.mjs +328 -0
  13. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  14. package/dist/chunk-PRRZAWJE.mjs +223 -0
  15. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  16. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  17. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  18. package/dist/constants-LHAAUC2T.mjs +51 -0
  19. package/dist/dist-2OGQ7FED.mjs +3957 -0
  20. package/dist/dist-IFHPYLDX.mjs +254 -0
  21. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  22. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  23. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  24. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  25. package/dist/index.d.mts +9 -3
  26. package/dist/index.d.ts +9 -3
  27. package/dist/index.js +48396 -19623
  28. package/dist/index.mjs +537 -19
  29. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  30. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  31. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  32. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  33. package/dist/proofs/halo2.d.mts +151 -0
  34. package/dist/proofs/halo2.d.ts +151 -0
  35. package/dist/proofs/halo2.js +350 -0
  36. package/dist/proofs/halo2.mjs +11 -0
  37. package/dist/proofs/kimchi.d.mts +160 -0
  38. package/dist/proofs/kimchi.d.ts +160 -0
  39. package/dist/proofs/kimchi.js +431 -0
  40. package/dist/proofs/kimchi.mjs +13 -0
  41. package/dist/proofs/noir.d.mts +1 -1
  42. package/dist/proofs/noir.d.ts +1 -1
  43. package/dist/proofs/noir.js +74 -18
  44. package/dist/proofs/noir.mjs +84 -24
  45. package/dist/solana-U3MEGU7W.mjs +280 -0
  46. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  47. package/package.json +54 -21
  48. package/src/adapters/index.ts +41 -0
  49. package/src/adapters/jupiter.ts +571 -0
  50. package/src/adapters/near-intents.ts +135 -0
  51. package/src/advisor/advisor.ts +653 -0
  52. package/src/advisor/index.ts +54 -0
  53. package/src/advisor/tools.ts +303 -0
  54. package/src/advisor/types.ts +164 -0
  55. package/src/chains/ethereum/announcement.ts +536 -0
  56. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  57. package/src/chains/ethereum/commitment.ts +522 -0
  58. package/src/chains/ethereum/constants.ts +462 -0
  59. package/src/chains/ethereum/deployment.ts +596 -0
  60. package/src/chains/ethereum/gas-estimation.ts +538 -0
  61. package/src/chains/ethereum/index.ts +268 -0
  62. package/src/chains/ethereum/optimizations.ts +614 -0
  63. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  64. package/src/chains/ethereum/registry.ts +584 -0
  65. package/src/chains/ethereum/rpc.ts +905 -0
  66. package/src/chains/ethereum/stealth.ts +491 -0
  67. package/src/chains/ethereum/token.ts +790 -0
  68. package/src/chains/ethereum/transfer.ts +637 -0
  69. package/src/chains/ethereum/types.ts +456 -0
  70. package/src/chains/ethereum/viewing-key.ts +455 -0
  71. package/src/chains/near/commitment.ts +608 -0
  72. package/src/chains/near/constants.ts +284 -0
  73. package/src/chains/near/function-call.ts +871 -0
  74. package/src/chains/near/history.ts +654 -0
  75. package/src/chains/near/implicit-account.ts +840 -0
  76. package/src/chains/near/index.ts +393 -0
  77. package/src/chains/near/native-transfer.ts +658 -0
  78. package/src/chains/near/nep141.ts +775 -0
  79. package/src/chains/near/privacy-adapter.ts +889 -0
  80. package/src/chains/near/resolver.ts +971 -0
  81. package/src/chains/near/rpc.ts +1016 -0
  82. package/src/chains/near/stealth.ts +419 -0
  83. package/src/chains/near/types.ts +317 -0
  84. package/src/chains/near/viewing-key.ts +876 -0
  85. package/src/chains/solana/anchor-transfer.ts +386 -0
  86. package/src/chains/solana/commitment.ts +577 -0
  87. package/src/chains/solana/constants.ts +126 -12
  88. package/src/chains/solana/ephemeral-keys.ts +543 -0
  89. package/src/chains/solana/index.ts +252 -1
  90. package/src/chains/solana/key-derivation.ts +418 -0
  91. package/src/chains/solana/kit-compat.ts +334 -0
  92. package/src/chains/solana/optimizations.ts +560 -0
  93. package/src/chains/solana/privacy-adapter.ts +605 -0
  94. package/src/chains/solana/providers/generic.ts +47 -6
  95. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  96. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  97. package/src/chains/solana/providers/helius.ts +186 -33
  98. package/src/chains/solana/providers/index.ts +31 -0
  99. package/src/chains/solana/providers/interface.ts +61 -18
  100. package/src/chains/solana/providers/quicknode.ts +409 -0
  101. package/src/chains/solana/providers/triton.ts +426 -0
  102. package/src/chains/solana/providers/webhook.ts +338 -67
  103. package/src/chains/solana/rpc-client.ts +1150 -0
  104. package/src/chains/solana/scan.ts +83 -66
  105. package/src/chains/solana/sol-transfer.ts +732 -0
  106. package/src/chains/solana/spl-transfer.ts +886 -0
  107. package/src/chains/solana/stealth-scanner.ts +703 -0
  108. package/src/chains/solana/sunspot-verifier.ts +453 -0
  109. package/src/chains/solana/transaction-builder.ts +755 -0
  110. package/src/chains/solana/transfer.ts +74 -5
  111. package/src/chains/solana/types.ts +57 -6
  112. package/src/chains/solana/utils.ts +110 -0
  113. package/src/chains/solana/viewing-key.ts +807 -0
  114. package/src/compliance/fireblocks.ts +921 -0
  115. package/src/compliance/index.ts +23 -0
  116. package/src/compliance/range-sas.ts +398 -33
  117. package/src/config/endpoints.ts +100 -0
  118. package/src/crypto.ts +11 -8
  119. package/src/errors.ts +82 -0
  120. package/src/evm/erc4337-relayer.ts +830 -0
  121. package/src/evm/index.ts +47 -0
  122. package/src/fees/calculator.ts +396 -0
  123. package/src/fees/index.ts +87 -0
  124. package/src/fees/near-contract.ts +429 -0
  125. package/src/fees/types.ts +268 -0
  126. package/src/index.ts +686 -1
  127. package/src/intent.ts +6 -3
  128. package/src/logger.ts +324 -0
  129. package/src/network/index.ts +80 -0
  130. package/src/network/proxy.ts +691 -0
  131. package/src/optimizations/index.ts +541 -0
  132. package/src/oracle/types.ts +1 -0
  133. package/src/privacy-backends/arcium-types.ts +727 -0
  134. package/src/privacy-backends/arcium.ts +719 -0
  135. package/src/privacy-backends/combined-privacy.ts +866 -0
  136. package/src/privacy-backends/cspl-token.ts +595 -0
  137. package/src/privacy-backends/cspl-types.ts +512 -0
  138. package/src/privacy-backends/cspl.ts +907 -0
  139. package/src/privacy-backends/health.ts +488 -0
  140. package/src/privacy-backends/inco-types.ts +323 -0
  141. package/src/privacy-backends/inco.ts +616 -0
  142. package/src/privacy-backends/index.ts +254 -4
  143. package/src/privacy-backends/interface.ts +649 -6
  144. package/src/privacy-backends/lru-cache.ts +343 -0
  145. package/src/privacy-backends/magicblock.ts +458 -0
  146. package/src/privacy-backends/mock.ts +258 -0
  147. package/src/privacy-backends/privacycash.ts +13 -17
  148. package/src/privacy-backends/private-swap.ts +570 -0
  149. package/src/privacy-backends/rate-limiter.ts +683 -0
  150. package/src/privacy-backends/registry.ts +414 -2
  151. package/src/privacy-backends/router.ts +283 -3
  152. package/src/privacy-backends/shadowwire.ts +449 -0
  153. package/src/privacy-backends/sip-native.ts +3 -0
  154. package/src/privacy-logger.ts +191 -0
  155. package/src/production-safety.ts +373 -0
  156. package/src/proofs/aggregator.ts +1029 -0
  157. package/src/proofs/browser-composer.ts +1150 -0
  158. package/src/proofs/browser.ts +113 -25
  159. package/src/proofs/cache/index.ts +127 -0
  160. package/src/proofs/cache/interface.ts +545 -0
  161. package/src/proofs/cache/key-generator.ts +188 -0
  162. package/src/proofs/cache/lru-cache.ts +481 -0
  163. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  164. package/src/proofs/cache/persistent-cache.ts +788 -0
  165. package/src/proofs/compliance-proof.ts +872 -0
  166. package/src/proofs/composer/base.ts +923 -0
  167. package/src/proofs/composer/index.ts +25 -0
  168. package/src/proofs/composer/interface.ts +518 -0
  169. package/src/proofs/composer/types.ts +383 -0
  170. package/src/proofs/converters/halo2.ts +452 -0
  171. package/src/proofs/converters/index.ts +208 -0
  172. package/src/proofs/converters/interface.ts +363 -0
  173. package/src/proofs/converters/kimchi.ts +462 -0
  174. package/src/proofs/converters/noir.ts +451 -0
  175. package/src/proofs/fallback.ts +888 -0
  176. package/src/proofs/halo2.ts +42 -0
  177. package/src/proofs/index.ts +471 -0
  178. package/src/proofs/interface.ts +13 -0
  179. package/src/proofs/kimchi.ts +42 -0
  180. package/src/proofs/lazy.ts +1004 -0
  181. package/src/proofs/mock.ts +25 -1
  182. package/src/proofs/noir.ts +110 -29
  183. package/src/proofs/orchestrator.ts +960 -0
  184. package/src/proofs/parallel/concurrency.ts +297 -0
  185. package/src/proofs/parallel/dependency-graph.ts +602 -0
  186. package/src/proofs/parallel/executor.ts +420 -0
  187. package/src/proofs/parallel/index.ts +131 -0
  188. package/src/proofs/parallel/interface.ts +685 -0
  189. package/src/proofs/parallel/worker-pool.ts +644 -0
  190. package/src/proofs/providers/halo2.ts +560 -0
  191. package/src/proofs/providers/index.ts +34 -0
  192. package/src/proofs/providers/kimchi.ts +641 -0
  193. package/src/proofs/validator.ts +881 -0
  194. package/src/proofs/verifier.ts +867 -0
  195. package/src/quantum/index.ts +112 -0
  196. package/src/quantum/winternitz-vault.ts +639 -0
  197. package/src/quantum/wots.ts +611 -0
  198. package/src/settlement/backends/direct-chain.ts +1 -0
  199. package/src/settlement/index.ts +9 -0
  200. package/src/settlement/router.ts +732 -46
  201. package/src/solana/index.ts +72 -0
  202. package/src/solana/jito-relayer.ts +687 -0
  203. package/src/solana/noir-verifier-types.ts +430 -0
  204. package/src/solana/noir-verifier.ts +816 -0
  205. package/src/stealth/address-derivation.ts +193 -0
  206. package/src/stealth/ed25519.ts +431 -0
  207. package/src/stealth/index.ts +233 -0
  208. package/src/stealth/meta-address.ts +221 -0
  209. package/src/stealth/secp256k1.ts +368 -0
  210. package/src/stealth/utils.ts +194 -0
  211. package/src/stealth.ts +50 -1504
  212. package/src/sync/index.ts +106 -0
  213. package/src/sync/manager.ts +504 -0
  214. package/src/sync/mock-provider.ts +318 -0
  215. package/src/sync/oblivious.ts +625 -0
  216. package/src/tokens/index.ts +15 -0
  217. package/src/tokens/registry.ts +301 -0
  218. package/src/utils/deprecation.ts +94 -0
  219. package/src/utils/index.ts +9 -0
  220. package/src/wallet/ethereum/index.ts +68 -0
  221. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  222. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  223. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  224. package/src/wallet/ethereum/types.ts +3 -1
  225. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  226. package/src/wallet/hardware/index.ts +10 -0
  227. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  228. package/src/wallet/index.ts +71 -0
  229. package/src/wallet/near/adapter.ts +626 -0
  230. package/src/wallet/near/index.ts +86 -0
  231. package/src/wallet/near/meteor-wallet.ts +1153 -0
  232. package/src/wallet/near/my-near-wallet.ts +790 -0
  233. package/src/wallet/near/wallet-selector.ts +702 -0
  234. package/src/wallet/solana/adapter.ts +6 -4
  235. package/src/wallet/solana/index.ts +13 -0
  236. package/src/wallet/solana/privacy-adapter.ts +567 -0
  237. package/src/wallet/sui/types.ts +6 -4
  238. package/src/zcash/rpc-client.ts +13 -6
  239. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  240. package/dist/chunk-3INS3PR5.mjs +0 -884
  241. package/dist/chunk-3OVABDRH.mjs +0 -17096
  242. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  243. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  244. package/dist/chunk-E6SZWREQ.mjs +0 -57
  245. package/dist/chunk-F6F73W35.mjs +0 -16166
  246. package/dist/chunk-G33LB27A.mjs +0 -16166
  247. package/dist/chunk-HGU6HZRC.mjs +0 -231
  248. package/dist/chunk-L2K34JCU.mjs +0 -1496
  249. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  250. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  251. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  252. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  253. package/dist/constants-VOI7BSLK.mjs +0 -27
  254. package/dist/index-B71aXVzk.d.ts +0 -13264
  255. package/dist/index-BYZbDjal.d.ts +0 -11390
  256. package/dist/index-CHB3KuOB.d.mts +0 -11859
  257. package/dist/index-CzWPI6Le.d.ts +0 -11859
  258. package/dist/index-pOIIuwfV.d.mts +0 -13264
  259. package/dist/index-xbWjohNq.d.mts +0 -11390
  260. package/dist/solana-4O4K45VU.mjs +0 -46
  261. package/dist/solana-5EMCTPTS.mjs +0 -46
  262. package/dist/solana-NDABAZ6P.mjs +0 -56
  263. package/dist/solana-Q4NAVBTS.mjs +0 -46
  264. package/dist/solana-ZYO63LY5.mjs +0 -46
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Cache Key Generator for Proof Caching
3
+ *
4
+ * @module proofs/cache/key-generator
5
+ * @description Generates deterministic cache keys from proof inputs
6
+ *
7
+ * M20-13: Implement proof caching layer (#313)
8
+ */
9
+
10
+ import { sha256 } from '@noble/hashes/sha256'
11
+ import { bytesToHex } from '@noble/hashes/utils'
12
+ import type { CacheKey, CacheKeyComponents, ICacheKeyGenerator } from './interface'
13
+
14
+ // ─── Constants ───────────────────────────────────────────────────────────────
15
+
16
+ const KEY_SEPARATOR = ':'
17
+ const KEY_PREFIX = 'sip-proof'
18
+ const KEY_VERSION = 'v1'
19
+
20
+ // ─── Helper Functions ────────────────────────────────────────────────────────
21
+
22
+ /**
23
+ * Canonicalize an object for deterministic hashing
24
+ * Sorts keys recursively and handles special types
25
+ */
26
+ function canonicalize(value: unknown): string {
27
+ if (value === null) return 'null'
28
+ if (value === undefined) return 'undefined'
29
+
30
+ if (typeof value === 'bigint') {
31
+ return `bigint:${value.toString()}`
32
+ }
33
+
34
+ if (typeof value === 'number') {
35
+ if (Number.isNaN(value)) return 'NaN'
36
+ if (!Number.isFinite(value)) return value > 0 ? 'Infinity' : '-Infinity'
37
+ return value.toString()
38
+ }
39
+
40
+ if (typeof value === 'string') {
41
+ return JSON.stringify(value)
42
+ }
43
+
44
+ if (typeof value === 'boolean') {
45
+ return value.toString()
46
+ }
47
+
48
+ if (value instanceof Uint8Array) {
49
+ return `bytes:${bytesToHex(value)}`
50
+ }
51
+
52
+ if (Array.isArray(value)) {
53
+ return `[${value.map(canonicalize).join(',')}]`
54
+ }
55
+
56
+ if (typeof value === 'object') {
57
+ const obj = value as Record<string, unknown>
58
+ const sortedKeys = Object.keys(obj).sort()
59
+ const pairs = sortedKeys.map((key) => `${JSON.stringify(key)}:${canonicalize(obj[key])}`)
60
+ return `{${pairs.join(',')}}`
61
+ }
62
+
63
+ return String(value)
64
+ }
65
+
66
+ // ─── Cache Key Generator Implementation ──────────────────────────────────────
67
+
68
+ /**
69
+ * Generates deterministic cache keys from proof input components
70
+ */
71
+ export class CacheKeyGenerator implements ICacheKeyGenerator {
72
+ /**
73
+ * Generate a cache key from components
74
+ */
75
+ generate(components: CacheKeyComponents): CacheKey {
76
+ const parts = [
77
+ KEY_PREFIX,
78
+ KEY_VERSION,
79
+ components.system,
80
+ components.circuitId,
81
+ components.privateInputsHash,
82
+ components.publicInputsHash,
83
+ ]
84
+
85
+ if (components.version) {
86
+ parts.push(components.version)
87
+ }
88
+
89
+ const key = parts.join(KEY_SEPARATOR)
90
+
91
+ return {
92
+ key,
93
+ components,
94
+ generatedAt: Date.now(),
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Parse a cache key string back to components
100
+ */
101
+ parse(key: string): CacheKeyComponents | null {
102
+ const parts = key.split(KEY_SEPARATOR)
103
+
104
+ // Validate prefix and version
105
+ if (parts.length < 6 || parts[0] !== KEY_PREFIX || parts[1] !== KEY_VERSION) {
106
+ return null
107
+ }
108
+
109
+ const [, , system, circuitId, privateInputsHash, publicInputsHash, version] = parts
110
+
111
+ // Validate system is a known proof system
112
+ const validSystems = ['noir', 'halo2', 'kimchi', 'groth16', 'plonk', 'stark', 'bulletproofs']
113
+ if (!validSystems.includes(system)) {
114
+ return null
115
+ }
116
+
117
+ return {
118
+ system: system as CacheKeyComponents['system'],
119
+ circuitId,
120
+ privateInputsHash,
121
+ publicInputsHash,
122
+ version,
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Hash input data deterministically
128
+ */
129
+ hashInputs(inputs: Record<string, unknown>): string {
130
+ const canonical = canonicalize(inputs)
131
+ const hash = sha256(new TextEncoder().encode(canonical))
132
+ return bytesToHex(hash)
133
+ }
134
+
135
+ /**
136
+ * Generate a cache key from raw inputs
137
+ */
138
+ generateFromInputs(
139
+ system: CacheKeyComponents['system'],
140
+ circuitId: string,
141
+ privateInputs: Record<string, unknown>,
142
+ publicInputs: Record<string, unknown>,
143
+ version?: string
144
+ ): CacheKey {
145
+ return this.generate({
146
+ system,
147
+ circuitId,
148
+ privateInputsHash: this.hashInputs(privateInputs),
149
+ publicInputsHash: this.hashInputs(publicInputs),
150
+ version,
151
+ })
152
+ }
153
+
154
+ /**
155
+ * Check if two cache keys are equal
156
+ */
157
+ equals(a: CacheKey | string, b: CacheKey | string): boolean {
158
+ const keyA = typeof a === 'string' ? a : a.key
159
+ const keyB = typeof b === 'string' ? b : b.key
160
+ return keyA === keyB
161
+ }
162
+
163
+ /**
164
+ * Check if a key matches a pattern (glob-style)
165
+ */
166
+ matches(key: string, pattern: string): boolean {
167
+ // Convert glob pattern to regex
168
+ const regexPattern = pattern
169
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special chars
170
+ .replace(/\*/g, '.*') // Convert * to .*
171
+ .replace(/\?/g, '.') // Convert ? to .
172
+
173
+ const regex = new RegExp(`^${regexPattern}$`)
174
+ return regex.test(key)
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Create a cache key generator instance
180
+ */
181
+ export function createCacheKeyGenerator(): ICacheKeyGenerator {
182
+ return new CacheKeyGenerator()
183
+ }
184
+
185
+ /**
186
+ * Singleton instance for convenience
187
+ */
188
+ export const cacheKeyGenerator = new CacheKeyGenerator()
@@ -0,0 +1,481 @@
1
+ /**
2
+ * LRU Cache Implementation for Proof Caching
3
+ *
4
+ * @module proofs/cache/lru-cache
5
+ * @description In-memory LRU cache for hot proofs
6
+ *
7
+ * M20-13: Implement proof caching layer (#313)
8
+ */
9
+
10
+ import type { SingleProof } from '@sip-protocol/types'
11
+ import type {
12
+ CacheKey,
13
+ CacheEntry,
14
+ CacheEntryMetadata,
15
+ CacheLookupResult,
16
+ ProofCacheStats,
17
+ CacheEvent,
18
+ CacheEventListener,
19
+ LRUCacheConfig,
20
+ ILRUCache,
21
+ } from './interface'
22
+ import { DEFAULT_LRU_CONFIG } from './interface'
23
+
24
+ // ─── Internal Types ──────────────────────────────────────────────────────────
25
+
26
+ interface InternalEntry<T> {
27
+ key: string
28
+ value: T
29
+ metadata: CacheEntryMetadata
30
+ prev: InternalEntry<T> | null
31
+ next: InternalEntry<T> | null
32
+ }
33
+
34
+ // ─── LRU Cache Implementation ────────────────────────────────────────────────
35
+
36
+ /**
37
+ * In-memory LRU cache for proofs
38
+ * Uses a doubly-linked list for O(1) LRU operations
39
+ */
40
+ export class LRUCache<T = SingleProof> implements ILRUCache<T> {
41
+ private readonly config: LRUCacheConfig
42
+ private readonly cache = new Map<string, InternalEntry<T>>()
43
+ private head: InternalEntry<T> | null = null
44
+ private tail: InternalEntry<T> | null = null
45
+ private currentSizeBytes = 0
46
+ private readonly listeners: Set<CacheEventListener> = new Set()
47
+ private evictionTimer: ReturnType<typeof setInterval> | null = null
48
+
49
+ // Statistics
50
+ private totalLookups = 0
51
+ private hits = 0
52
+ private misses = 0
53
+ private evictions = 0
54
+ private expirations = 0
55
+ private totalLookupTimeMs = 0
56
+
57
+ constructor(config: Partial<LRUCacheConfig> = {}) {
58
+ this.config = { ...DEFAULT_LRU_CONFIG, ...config }
59
+
60
+ // Start eviction timer if configured
61
+ if (this.config.evictionIntervalMs > 0) {
62
+ this.startEvictionTimer()
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Get an entry from the cache
68
+ */
69
+ async get(key: CacheKey | string): Promise<CacheLookupResult<T>> {
70
+ const startTime = Date.now()
71
+ const keyStr = typeof key === 'string' ? key : key.key
72
+
73
+ this.totalLookups++
74
+
75
+ const entry = this.cache.get(keyStr)
76
+
77
+ if (!entry) {
78
+ this.misses++
79
+ this.updateLookupTime(startTime)
80
+ this.emitEvent({ type: 'miss', key: keyStr, timestamp: Date.now() })
81
+
82
+ return {
83
+ hit: false,
84
+ missReason: 'not_found',
85
+ lookupTimeMs: Date.now() - startTime,
86
+ }
87
+ }
88
+
89
+ // Check expiration
90
+ if (entry.metadata.expiresAt > 0 && Date.now() > entry.metadata.expiresAt) {
91
+ this.deleteEntry(keyStr)
92
+ this.expirations++
93
+ this.misses++
94
+ this.updateLookupTime(startTime)
95
+ this.emitEvent({ type: 'expire', key: keyStr, timestamp: Date.now() })
96
+
97
+ return {
98
+ hit: false,
99
+ missReason: 'expired',
100
+ lookupTimeMs: Date.now() - startTime,
101
+ }
102
+ }
103
+
104
+ // Update access tracking
105
+ if (this.config.trackAccess) {
106
+ entry.metadata.lastAccessedAt = Date.now()
107
+ entry.metadata.accessCount++
108
+ }
109
+
110
+ // Move to head (most recently used)
111
+ this.moveToHead(entry)
112
+
113
+ this.hits++
114
+ this.updateLookupTime(startTime)
115
+ this.emitEvent({ type: 'hit', key: keyStr, timestamp: Date.now() })
116
+
117
+ return {
118
+ hit: true,
119
+ entry: {
120
+ key: typeof key === 'string' ? { key, components: {} as CacheKey['components'], generatedAt: 0 } : key,
121
+ value: entry.value,
122
+ metadata: entry.metadata,
123
+ },
124
+ lookupTimeMs: Date.now() - startTime,
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Set an entry in the cache
130
+ */
131
+ async set(key: CacheKey | string, value: T, ttlMs?: number): Promise<boolean> {
132
+ const keyStr = typeof key === 'string' ? key : key.key
133
+ const effectiveTtl = ttlMs ?? this.config.defaultTtlMs
134
+ const sizeBytes = this.estimateSize(value)
135
+
136
+ // Check if entry already exists
137
+ const existing = this.cache.get(keyStr)
138
+ if (existing) {
139
+ // Update existing entry
140
+ this.currentSizeBytes -= existing.metadata.sizeBytes
141
+ existing.value = value
142
+ existing.metadata = {
143
+ ...existing.metadata,
144
+ sizeBytes,
145
+ ttlMs: effectiveTtl,
146
+ expiresAt: effectiveTtl > 0 ? Date.now() + effectiveTtl : 0,
147
+ }
148
+ this.currentSizeBytes += sizeBytes
149
+ this.moveToHead(existing)
150
+ this.emitEvent({ type: 'set', key: keyStr, timestamp: Date.now() })
151
+ return true
152
+ }
153
+
154
+ // Evict if necessary to make room
155
+ while (
156
+ (this.cache.size >= this.config.maxEntries ||
157
+ this.currentSizeBytes + sizeBytes > this.config.maxSizeBytes) &&
158
+ this.cache.size > 0
159
+ ) {
160
+ this.evictLRU()
161
+ }
162
+
163
+ // Create new entry
164
+ const metadata: CacheEntryMetadata = {
165
+ createdAt: Date.now(),
166
+ lastAccessedAt: Date.now(),
167
+ accessCount: 0,
168
+ sizeBytes,
169
+ ttlMs: effectiveTtl,
170
+ expiresAt: effectiveTtl > 0 ? Date.now() + effectiveTtl : 0,
171
+ source: 'generation',
172
+ }
173
+
174
+ const entry: InternalEntry<T> = {
175
+ key: keyStr,
176
+ value,
177
+ metadata,
178
+ prev: null,
179
+ next: null,
180
+ }
181
+
182
+ this.cache.set(keyStr, entry)
183
+ this.currentSizeBytes += sizeBytes
184
+ this.addToHead(entry)
185
+
186
+ this.emitEvent({ type: 'set', key: keyStr, timestamp: Date.now() })
187
+ return true
188
+ }
189
+
190
+ /**
191
+ * Delete an entry from the cache
192
+ */
193
+ async delete(key: CacheKey | string): Promise<boolean> {
194
+ const keyStr = typeof key === 'string' ? key : key.key
195
+ const deleted = this.deleteEntry(keyStr)
196
+
197
+ if (deleted) {
198
+ this.emitEvent({ type: 'delete', key: keyStr, timestamp: Date.now() })
199
+ }
200
+
201
+ return deleted
202
+ }
203
+
204
+ /**
205
+ * Check if an entry exists
206
+ */
207
+ async has(key: CacheKey | string): Promise<boolean> {
208
+ const keyStr = typeof key === 'string' ? key : key.key
209
+ const entry = this.cache.get(keyStr)
210
+
211
+ if (!entry) return false
212
+
213
+ // Check expiration
214
+ if (entry.metadata.expiresAt > 0 && Date.now() > entry.metadata.expiresAt) {
215
+ this.deleteEntry(keyStr)
216
+ this.expirations++
217
+ return false
218
+ }
219
+
220
+ return true
221
+ }
222
+
223
+ /**
224
+ * Clear all entries
225
+ */
226
+ async clear(): Promise<void> {
227
+ this.cache.clear()
228
+ this.head = null
229
+ this.tail = null
230
+ this.currentSizeBytes = 0
231
+ this.emitEvent({ type: 'clear', timestamp: Date.now() })
232
+ }
233
+
234
+ /**
235
+ * Get cache statistics
236
+ */
237
+ getStats(): ProofCacheStats {
238
+ const avgLookupTimeMs =
239
+ this.totalLookups > 0 ? this.totalLookupTimeMs / this.totalLookups : 0
240
+
241
+ // Calculate average entry age
242
+ let totalAge = 0
243
+ const now = Date.now()
244
+ for (const entry of this.cache.values()) {
245
+ totalAge += now - entry.metadata.createdAt
246
+ }
247
+ const avgEntryAgeMs = this.cache.size > 0 ? totalAge / this.cache.size : 0
248
+
249
+ return {
250
+ totalLookups: this.totalLookups,
251
+ hits: this.hits,
252
+ misses: this.misses,
253
+ hitRate: this.totalLookups > 0 ? this.hits / this.totalLookups : 0,
254
+ entryCount: this.cache.size,
255
+ sizeBytes: this.currentSizeBytes,
256
+ maxSizeBytes: this.config.maxSizeBytes,
257
+ evictions: this.evictions,
258
+ expirations: this.expirations,
259
+ avgLookupTimeMs,
260
+ avgEntryAgeMs,
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Get all keys matching a pattern
266
+ */
267
+ async keys(pattern?: string): Promise<string[]> {
268
+ const allKeys = Array.from(this.cache.keys())
269
+
270
+ if (!pattern) {
271
+ return allKeys
272
+ }
273
+
274
+ // Convert glob pattern to regex
275
+ const regexPattern = pattern
276
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
277
+ .replace(/\*/g, '.*')
278
+ .replace(/\?/g, '.')
279
+
280
+ const regex = new RegExp(`^${regexPattern}$`)
281
+ return allKeys.filter((key) => regex.test(key))
282
+ }
283
+
284
+ /**
285
+ * Add an event listener
286
+ */
287
+ addEventListener(listener: CacheEventListener): void {
288
+ this.listeners.add(listener)
289
+ }
290
+
291
+ /**
292
+ * Remove an event listener
293
+ */
294
+ removeEventListener(listener: CacheEventListener): void {
295
+ this.listeners.delete(listener)
296
+ }
297
+
298
+ /**
299
+ * Get the current size in bytes
300
+ */
301
+ getSizeBytes(): number {
302
+ return this.currentSizeBytes
303
+ }
304
+
305
+ /**
306
+ * Get the entry count
307
+ */
308
+ getEntryCount(): number {
309
+ return this.cache.size
310
+ }
311
+
312
+ /**
313
+ * Manually trigger eviction
314
+ */
315
+ evict(count?: number): number {
316
+ const toEvict = count ?? 1
317
+ let evicted = 0
318
+
319
+ for (let i = 0; i < toEvict && this.cache.size > 0; i++) {
320
+ if (this.evictLRU()) {
321
+ evicted++
322
+ }
323
+ }
324
+
325
+ return evicted
326
+ }
327
+
328
+ /**
329
+ * Get entries in LRU order (most recent first)
330
+ */
331
+ getEntriesLRU(): CacheEntry<T>[] {
332
+ const entries: CacheEntry<T>[] = []
333
+ let current = this.head
334
+
335
+ while (current) {
336
+ entries.push({
337
+ key: { key: current.key, components: {} as CacheKey['components'], generatedAt: 0 },
338
+ value: current.value,
339
+ metadata: current.metadata,
340
+ })
341
+ current = current.next
342
+ }
343
+
344
+ return entries
345
+ }
346
+
347
+ /**
348
+ * Dispose of the cache
349
+ */
350
+ dispose(): void {
351
+ if (this.evictionTimer) {
352
+ clearInterval(this.evictionTimer)
353
+ this.evictionTimer = null
354
+ }
355
+ this.clear()
356
+ this.listeners.clear()
357
+ }
358
+
359
+ // ─── Private Methods ─────────────────────────────────────────────────────────
360
+
361
+ private deleteEntry(key: string): boolean {
362
+ const entry = this.cache.get(key)
363
+ if (!entry) return false
364
+
365
+ this.removeFromList(entry)
366
+ this.cache.delete(key)
367
+ this.currentSizeBytes -= entry.metadata.sizeBytes
368
+
369
+ return true
370
+ }
371
+
372
+ private evictLRU(): boolean {
373
+ if (!this.tail) return false
374
+
375
+ const evictedKey = this.tail.key
376
+ this.deleteEntry(evictedKey)
377
+ this.evictions++
378
+ this.emitEvent({ type: 'evict', key: evictedKey, timestamp: Date.now() })
379
+
380
+ return true
381
+ }
382
+
383
+ private addToHead(entry: InternalEntry<T>): void {
384
+ entry.prev = null
385
+ entry.next = this.head
386
+
387
+ if (this.head) {
388
+ this.head.prev = entry
389
+ }
390
+
391
+ this.head = entry
392
+
393
+ if (!this.tail) {
394
+ this.tail = entry
395
+ }
396
+ }
397
+
398
+ private removeFromList(entry: InternalEntry<T>): void {
399
+ if (entry.prev) {
400
+ entry.prev.next = entry.next
401
+ } else {
402
+ this.head = entry.next
403
+ }
404
+
405
+ if (entry.next) {
406
+ entry.next.prev = entry.prev
407
+ } else {
408
+ this.tail = entry.prev
409
+ }
410
+
411
+ entry.prev = null
412
+ entry.next = null
413
+ }
414
+
415
+ private moveToHead(entry: InternalEntry<T>): void {
416
+ if (entry === this.head) return
417
+
418
+ this.removeFromList(entry)
419
+ this.addToHead(entry)
420
+ }
421
+
422
+ private estimateSize(value: T): number {
423
+ // Estimate size based on JSON serialization
424
+ try {
425
+ const json = JSON.stringify(value, (_, v) => {
426
+ if (typeof v === 'bigint') return v.toString()
427
+ return v
428
+ })
429
+ return new TextEncoder().encode(json).length
430
+ } catch {
431
+ // Fallback for non-serializable values
432
+ return 1024 // Default 1KB
433
+ }
434
+ }
435
+
436
+ private updateLookupTime(startTime: number): void {
437
+ this.totalLookupTimeMs += Date.now() - startTime
438
+ }
439
+
440
+ private emitEvent(event: CacheEvent): void {
441
+ for (const listener of this.listeners) {
442
+ try {
443
+ listener(event)
444
+ } catch {
445
+ // Ignore listener errors
446
+ }
447
+ }
448
+ }
449
+
450
+ private startEvictionTimer(): void {
451
+ this.evictionTimer = setInterval(() => {
452
+ this.cleanupExpired()
453
+ }, this.config.evictionIntervalMs)
454
+ }
455
+
456
+ private cleanupExpired(): void {
457
+ const now = Date.now()
458
+ const keysToDelete: string[] = []
459
+
460
+ for (const [key, entry] of this.cache) {
461
+ if (entry.metadata.expiresAt > 0 && now > entry.metadata.expiresAt) {
462
+ keysToDelete.push(key)
463
+ }
464
+ }
465
+
466
+ for (const key of keysToDelete) {
467
+ this.deleteEntry(key)
468
+ this.expirations++
469
+ this.emitEvent({ type: 'expire', key, timestamp: now })
470
+ }
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Create an LRU cache instance
476
+ */
477
+ export function createLRUCache<T = SingleProof>(
478
+ config?: Partial<LRUCacheConfig>
479
+ ): ILRUCache<T> {
480
+ return new LRUCache<T>(config)
481
+ }