@sip-protocol/sdk 0.7.3 → 0.8.0

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 (263) hide show
  1. package/README.md +267 -0
  2. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  3. package/dist/browser.d.mts +10 -4
  4. package/dist/browser.d.ts +10 -4
  5. package/dist/browser.js +47556 -19603
  6. package/dist/browser.mjs +628 -48
  7. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  8. package/dist/chunk-5D7A3L3W.mjs +717 -0
  9. package/dist/chunk-64AYA5F5.mjs +7834 -0
  10. package/dist/chunk-GMDGB22A.mjs +379 -0
  11. package/dist/chunk-I534WKN7.mjs +328 -0
  12. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  13. package/dist/chunk-PRRZAWJE.mjs +223 -0
  14. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  15. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  16. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  17. package/dist/constants-LHAAUC2T.mjs +51 -0
  18. package/dist/dist-2OGQ7FED.mjs +3957 -0
  19. package/dist/dist-IFHPYLDX.mjs +254 -0
  20. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  21. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  22. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  23. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  24. package/dist/index.d.mts +9 -3
  25. package/dist/index.d.ts +9 -3
  26. package/dist/index.js +48396 -19623
  27. package/dist/index.mjs +537 -19
  28. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  29. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  30. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  31. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  32. package/dist/proofs/halo2.d.mts +151 -0
  33. package/dist/proofs/halo2.d.ts +151 -0
  34. package/dist/proofs/halo2.js +350 -0
  35. package/dist/proofs/halo2.mjs +11 -0
  36. package/dist/proofs/kimchi.d.mts +160 -0
  37. package/dist/proofs/kimchi.d.ts +160 -0
  38. package/dist/proofs/kimchi.js +431 -0
  39. package/dist/proofs/kimchi.mjs +13 -0
  40. package/dist/proofs/noir.d.mts +1 -1
  41. package/dist/proofs/noir.d.ts +1 -1
  42. package/dist/proofs/noir.js +74 -18
  43. package/dist/proofs/noir.mjs +84 -24
  44. package/dist/solana-U3MEGU7W.mjs +280 -0
  45. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  46. package/package.json +44 -11
  47. package/src/adapters/index.ts +41 -0
  48. package/src/adapters/jupiter.ts +571 -0
  49. package/src/adapters/near-intents.ts +135 -0
  50. package/src/advisor/advisor.ts +653 -0
  51. package/src/advisor/index.ts +54 -0
  52. package/src/advisor/tools.ts +303 -0
  53. package/src/advisor/types.ts +164 -0
  54. package/src/chains/ethereum/announcement.ts +536 -0
  55. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  56. package/src/chains/ethereum/commitment.ts +522 -0
  57. package/src/chains/ethereum/constants.ts +462 -0
  58. package/src/chains/ethereum/deployment.ts +596 -0
  59. package/src/chains/ethereum/gas-estimation.ts +538 -0
  60. package/src/chains/ethereum/index.ts +268 -0
  61. package/src/chains/ethereum/optimizations.ts +614 -0
  62. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  63. package/src/chains/ethereum/registry.ts +584 -0
  64. package/src/chains/ethereum/rpc.ts +905 -0
  65. package/src/chains/ethereum/stealth.ts +491 -0
  66. package/src/chains/ethereum/token.ts +790 -0
  67. package/src/chains/ethereum/transfer.ts +637 -0
  68. package/src/chains/ethereum/types.ts +456 -0
  69. package/src/chains/ethereum/viewing-key.ts +455 -0
  70. package/src/chains/near/commitment.ts +608 -0
  71. package/src/chains/near/constants.ts +284 -0
  72. package/src/chains/near/function-call.ts +871 -0
  73. package/src/chains/near/history.ts +654 -0
  74. package/src/chains/near/implicit-account.ts +840 -0
  75. package/src/chains/near/index.ts +393 -0
  76. package/src/chains/near/native-transfer.ts +658 -0
  77. package/src/chains/near/nep141.ts +775 -0
  78. package/src/chains/near/privacy-adapter.ts +889 -0
  79. package/src/chains/near/resolver.ts +971 -0
  80. package/src/chains/near/rpc.ts +1016 -0
  81. package/src/chains/near/stealth.ts +419 -0
  82. package/src/chains/near/types.ts +317 -0
  83. package/src/chains/near/viewing-key.ts +876 -0
  84. package/src/chains/solana/anchor-transfer.ts +386 -0
  85. package/src/chains/solana/commitment.ts +577 -0
  86. package/src/chains/solana/constants.ts +126 -12
  87. package/src/chains/solana/ephemeral-keys.ts +543 -0
  88. package/src/chains/solana/index.ts +252 -1
  89. package/src/chains/solana/key-derivation.ts +418 -0
  90. package/src/chains/solana/kit-compat.ts +334 -0
  91. package/src/chains/solana/optimizations.ts +560 -0
  92. package/src/chains/solana/privacy-adapter.ts +605 -0
  93. package/src/chains/solana/providers/generic.ts +47 -6
  94. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  95. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  96. package/src/chains/solana/providers/helius.ts +186 -33
  97. package/src/chains/solana/providers/index.ts +31 -0
  98. package/src/chains/solana/providers/interface.ts +61 -18
  99. package/src/chains/solana/providers/quicknode.ts +409 -0
  100. package/src/chains/solana/providers/triton.ts +426 -0
  101. package/src/chains/solana/providers/webhook.ts +338 -67
  102. package/src/chains/solana/rpc-client.ts +1150 -0
  103. package/src/chains/solana/scan.ts +83 -66
  104. package/src/chains/solana/sol-transfer.ts +732 -0
  105. package/src/chains/solana/spl-transfer.ts +886 -0
  106. package/src/chains/solana/stealth-scanner.ts +703 -0
  107. package/src/chains/solana/sunspot-verifier.ts +453 -0
  108. package/src/chains/solana/transaction-builder.ts +755 -0
  109. package/src/chains/solana/transfer.ts +74 -5
  110. package/src/chains/solana/types.ts +57 -6
  111. package/src/chains/solana/utils.ts +110 -0
  112. package/src/chains/solana/viewing-key.ts +807 -0
  113. package/src/compliance/fireblocks.ts +921 -0
  114. package/src/compliance/index.ts +23 -0
  115. package/src/compliance/range-sas.ts +398 -33
  116. package/src/config/endpoints.ts +100 -0
  117. package/src/crypto.ts +11 -8
  118. package/src/errors.ts +82 -0
  119. package/src/evm/erc4337-relayer.ts +830 -0
  120. package/src/evm/index.ts +47 -0
  121. package/src/fees/calculator.ts +396 -0
  122. package/src/fees/index.ts +87 -0
  123. package/src/fees/near-contract.ts +429 -0
  124. package/src/fees/types.ts +268 -0
  125. package/src/index.ts +686 -1
  126. package/src/intent.ts +6 -3
  127. package/src/logger.ts +324 -0
  128. package/src/network/index.ts +80 -0
  129. package/src/network/proxy.ts +691 -0
  130. package/src/optimizations/index.ts +541 -0
  131. package/src/oracle/types.ts +1 -0
  132. package/src/privacy-backends/arcium-types.ts +727 -0
  133. package/src/privacy-backends/arcium.ts +719 -0
  134. package/src/privacy-backends/combined-privacy.ts +866 -0
  135. package/src/privacy-backends/cspl-token.ts +595 -0
  136. package/src/privacy-backends/cspl-types.ts +512 -0
  137. package/src/privacy-backends/cspl.ts +907 -0
  138. package/src/privacy-backends/health.ts +488 -0
  139. package/src/privacy-backends/inco-types.ts +323 -0
  140. package/src/privacy-backends/inco.ts +616 -0
  141. package/src/privacy-backends/index.ts +254 -4
  142. package/src/privacy-backends/interface.ts +649 -6
  143. package/src/privacy-backends/lru-cache.ts +343 -0
  144. package/src/privacy-backends/magicblock.ts +458 -0
  145. package/src/privacy-backends/mock.ts +258 -0
  146. package/src/privacy-backends/privacycash.ts +13 -17
  147. package/src/privacy-backends/private-swap.ts +570 -0
  148. package/src/privacy-backends/rate-limiter.ts +683 -0
  149. package/src/privacy-backends/registry.ts +414 -2
  150. package/src/privacy-backends/router.ts +283 -3
  151. package/src/privacy-backends/shadowwire.ts +449 -0
  152. package/src/privacy-backends/sip-native.ts +3 -0
  153. package/src/privacy-logger.ts +191 -0
  154. package/src/production-safety.ts +373 -0
  155. package/src/proofs/aggregator.ts +1029 -0
  156. package/src/proofs/browser-composer.ts +1150 -0
  157. package/src/proofs/browser.ts +113 -25
  158. package/src/proofs/cache/index.ts +127 -0
  159. package/src/proofs/cache/interface.ts +545 -0
  160. package/src/proofs/cache/key-generator.ts +188 -0
  161. package/src/proofs/cache/lru-cache.ts +481 -0
  162. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  163. package/src/proofs/cache/persistent-cache.ts +788 -0
  164. package/src/proofs/compliance-proof.ts +872 -0
  165. package/src/proofs/composer/base.ts +923 -0
  166. package/src/proofs/composer/index.ts +25 -0
  167. package/src/proofs/composer/interface.ts +518 -0
  168. package/src/proofs/composer/types.ts +383 -0
  169. package/src/proofs/converters/halo2.ts +452 -0
  170. package/src/proofs/converters/index.ts +208 -0
  171. package/src/proofs/converters/interface.ts +363 -0
  172. package/src/proofs/converters/kimchi.ts +462 -0
  173. package/src/proofs/converters/noir.ts +451 -0
  174. package/src/proofs/fallback.ts +888 -0
  175. package/src/proofs/halo2.ts +42 -0
  176. package/src/proofs/index.ts +471 -0
  177. package/src/proofs/interface.ts +13 -0
  178. package/src/proofs/kimchi.ts +42 -0
  179. package/src/proofs/lazy.ts +1004 -0
  180. package/src/proofs/mock.ts +25 -1
  181. package/src/proofs/noir.ts +110 -29
  182. package/src/proofs/orchestrator.ts +960 -0
  183. package/src/proofs/parallel/concurrency.ts +297 -0
  184. package/src/proofs/parallel/dependency-graph.ts +602 -0
  185. package/src/proofs/parallel/executor.ts +420 -0
  186. package/src/proofs/parallel/index.ts +131 -0
  187. package/src/proofs/parallel/interface.ts +685 -0
  188. package/src/proofs/parallel/worker-pool.ts +644 -0
  189. package/src/proofs/providers/halo2.ts +560 -0
  190. package/src/proofs/providers/index.ts +34 -0
  191. package/src/proofs/providers/kimchi.ts +641 -0
  192. package/src/proofs/validator.ts +881 -0
  193. package/src/proofs/verifier.ts +867 -0
  194. package/src/quantum/index.ts +112 -0
  195. package/src/quantum/winternitz-vault.ts +639 -0
  196. package/src/quantum/wots.ts +611 -0
  197. package/src/settlement/backends/direct-chain.ts +1 -0
  198. package/src/settlement/index.ts +9 -0
  199. package/src/settlement/router.ts +732 -46
  200. package/src/solana/index.ts +72 -0
  201. package/src/solana/jito-relayer.ts +687 -0
  202. package/src/solana/noir-verifier-types.ts +430 -0
  203. package/src/solana/noir-verifier.ts +816 -0
  204. package/src/stealth/address-derivation.ts +193 -0
  205. package/src/stealth/ed25519.ts +431 -0
  206. package/src/stealth/index.ts +233 -0
  207. package/src/stealth/meta-address.ts +221 -0
  208. package/src/stealth/secp256k1.ts +368 -0
  209. package/src/stealth/utils.ts +194 -0
  210. package/src/stealth.ts +50 -1504
  211. package/src/sync/index.ts +106 -0
  212. package/src/sync/manager.ts +504 -0
  213. package/src/sync/mock-provider.ts +318 -0
  214. package/src/sync/oblivious.ts +625 -0
  215. package/src/tokens/index.ts +15 -0
  216. package/src/tokens/registry.ts +301 -0
  217. package/src/utils/deprecation.ts +94 -0
  218. package/src/utils/index.ts +9 -0
  219. package/src/wallet/ethereum/index.ts +68 -0
  220. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  221. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  222. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  223. package/src/wallet/ethereum/types.ts +3 -1
  224. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  225. package/src/wallet/hardware/index.ts +10 -0
  226. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  227. package/src/wallet/index.ts +71 -0
  228. package/src/wallet/near/adapter.ts +626 -0
  229. package/src/wallet/near/index.ts +86 -0
  230. package/src/wallet/near/meteor-wallet.ts +1153 -0
  231. package/src/wallet/near/my-near-wallet.ts +790 -0
  232. package/src/wallet/near/wallet-selector.ts +702 -0
  233. package/src/wallet/solana/adapter.ts +6 -4
  234. package/src/wallet/solana/index.ts +13 -0
  235. package/src/wallet/solana/privacy-adapter.ts +567 -0
  236. package/src/wallet/sui/types.ts +6 -4
  237. package/src/zcash/rpc-client.ts +13 -6
  238. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  239. package/dist/chunk-3INS3PR5.mjs +0 -884
  240. package/dist/chunk-3OVABDRH.mjs +0 -17096
  241. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  242. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  243. package/dist/chunk-E6SZWREQ.mjs +0 -57
  244. package/dist/chunk-F6F73W35.mjs +0 -16166
  245. package/dist/chunk-G33LB27A.mjs +0 -16166
  246. package/dist/chunk-HGU6HZRC.mjs +0 -231
  247. package/dist/chunk-L2K34JCU.mjs +0 -1496
  248. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  249. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  250. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  251. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  252. package/dist/constants-VOI7BSLK.mjs +0 -27
  253. package/dist/index-B71aXVzk.d.ts +0 -13264
  254. package/dist/index-BYZbDjal.d.ts +0 -11390
  255. package/dist/index-CHB3KuOB.d.mts +0 -11859
  256. package/dist/index-CzWPI6Le.d.ts +0 -11859
  257. package/dist/index-pOIIuwfV.d.mts +0 -13264
  258. package/dist/index-xbWjohNq.d.mts +0 -11390
  259. package/dist/solana-4O4K45VU.mjs +0 -46
  260. package/dist/solana-5EMCTPTS.mjs +0 -46
  261. package/dist/solana-NDABAZ6P.mjs +0 -56
  262. package/dist/solana-Q4NAVBTS.mjs +0 -46
  263. package/dist/solana-ZYO63LY5.mjs +0 -46
@@ -0,0 +1,876 @@
1
+ /**
2
+ * NEAR Viewing Key Management
3
+ *
4
+ * Provides viewing key generation, export/import, encryption, and storage
5
+ * for selective disclosure and compliance on NEAR.
6
+ *
7
+ * ## Architecture
8
+ *
9
+ * ```
10
+ * Spending Private Key
11
+ * │
12
+ * ▼ HMAC-SHA256(context)
13
+ * Viewing Private Key (32 bytes)
14
+ * │
15
+ * ▼ ed25519.getPublicKey()
16
+ * Viewing Public Key (32 bytes)
17
+ * │
18
+ * ▼ sha256()
19
+ * Viewing Key Hash (32 bytes) ← Used for announcement matching
20
+ * ```
21
+ *
22
+ * ## Security Properties
23
+ *
24
+ * - Viewing keys can decrypt but NOT spend funds
25
+ * - Hash is safe to publish on-chain for announcement matching
26
+ * - XChaCha20-Poly1305 provides authenticated encryption
27
+ *
28
+ * @module chains/near/viewing-key
29
+ */
30
+
31
+ import { ed25519 } from '@noble/curves/ed25519'
32
+ import { sha256 } from '@noble/hashes/sha256'
33
+ import { hmac } from '@noble/hashes/hmac'
34
+ import { hkdf } from '@noble/hashes/hkdf'
35
+ import { bytesToHex, hexToBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'
36
+ import { xchacha20poly1305 } from '@noble/ciphers/chacha.js'
37
+ import type { HexString, Hash } from '@sip-protocol/types'
38
+ import { ValidationError, CryptoError, ErrorCode } from '../../errors'
39
+ import { isValidHex } from '../../validation'
40
+ import { secureWipe } from '../../secure-memory'
41
+
42
+ // ─── Constants ────────────────────────────────────────────────────────────────
43
+
44
+ /**
45
+ * Domain separation for viewing key derivation from spending key
46
+ */
47
+ const VIEWING_KEY_CONTEXT = 'SIP-NEAR-viewing-key-v1'
48
+
49
+ /**
50
+ * Domain separation for encryption key derivation
51
+ */
52
+ const ENCRYPTION_DOMAIN = 'SIP-NEAR-VIEWING-KEY-ENCRYPTION-V1'
53
+
54
+ /**
55
+ * XChaCha20-Poly1305 nonce size (24 bytes)
56
+ */
57
+ const NONCE_SIZE = 24
58
+
59
+ /**
60
+ * Standard export format version for viewing keys
61
+ */
62
+ const EXPORT_VERSION = 1
63
+
64
+ // ─── Types ────────────────────────────────────────────────────────────────────
65
+
66
+ /**
67
+ * A NEAR viewing key with associated metadata
68
+ */
69
+ export interface NEARViewingKey {
70
+ /**
71
+ * The viewing private key (32 bytes)
72
+ * Used for decryption and stealth address scanning
73
+ */
74
+ privateKey: HexString
75
+
76
+ /**
77
+ * The viewing public key (32 bytes)
78
+ * Can be shared for encryption
79
+ */
80
+ publicKey: HexString
81
+
82
+ /**
83
+ * Hash of the viewing public key for announcement matching
84
+ * This is published on-chain to enable efficient scanning
85
+ */
86
+ hash: Hash
87
+
88
+ /**
89
+ * Optional label for this viewing key
90
+ */
91
+ label?: string
92
+
93
+ /**
94
+ * Timestamp when this key was created
95
+ */
96
+ createdAt: number
97
+ }
98
+
99
+ /**
100
+ * Standard export format for NEAR viewing keys
101
+ */
102
+ export interface NEARViewingKeyExport {
103
+ /**
104
+ * Export format version
105
+ */
106
+ version: number
107
+
108
+ /**
109
+ * The chain this key is for
110
+ */
111
+ chain: 'near'
112
+
113
+ /**
114
+ * The viewing private key (hex encoded)
115
+ */
116
+ privateKey: HexString
117
+
118
+ /**
119
+ * The viewing public key (hex encoded)
120
+ */
121
+ publicKey: HexString
122
+
123
+ /**
124
+ * Hash for announcement matching
125
+ */
126
+ hash: Hash
127
+
128
+ /**
129
+ * Optional label
130
+ */
131
+ label?: string
132
+
133
+ /**
134
+ * Creation timestamp
135
+ */
136
+ createdAt: number
137
+
138
+ /**
139
+ * Export timestamp
140
+ */
141
+ exportedAt: number
142
+ }
143
+
144
+ /**
145
+ * Encrypted data structure for viewing key operations
146
+ */
147
+ export interface NEAREncryptedPayload {
148
+ /**
149
+ * The encrypted ciphertext (hex encoded)
150
+ */
151
+ ciphertext: HexString
152
+
153
+ /**
154
+ * The nonce used for encryption (hex encoded, 24 bytes)
155
+ */
156
+ nonce: HexString
157
+
158
+ /**
159
+ * Hash of the viewing key that can decrypt this
160
+ */
161
+ viewingKeyHash: Hash
162
+ }
163
+
164
+ /**
165
+ * Transaction data that can be encrypted for viewing
166
+ */
167
+ export interface NEARTransactionData {
168
+ /**
169
+ * Sender's account ID
170
+ */
171
+ sender: string
172
+
173
+ /**
174
+ * Recipient's stealth address (implicit account)
175
+ */
176
+ recipient: string
177
+
178
+ /**
179
+ * Amount in yoctoNEAR (string for bigint serialization)
180
+ */
181
+ amount: string
182
+
183
+ /**
184
+ * Token contract address (null for native NEAR)
185
+ */
186
+ tokenContract: string | null
187
+
188
+ /**
189
+ * Token decimals (24 for NEAR, 6 for USDC, etc.)
190
+ */
191
+ decimals: number
192
+
193
+ /**
194
+ * Transaction timestamp
195
+ */
196
+ timestamp: number
197
+
198
+ /**
199
+ * Optional memo
200
+ */
201
+ memo?: string
202
+ }
203
+
204
+ /**
205
+ * Interface for viewing key storage providers
206
+ */
207
+ export interface NEARViewingKeyStorage {
208
+ /**
209
+ * Store a viewing key
210
+ * @param key - The viewing key to store
211
+ * @returns Promise resolving to the key's hash (for identification)
212
+ */
213
+ save(key: NEARViewingKey): Promise<Hash>
214
+
215
+ /**
216
+ * Retrieve a viewing key by its hash
217
+ * @param hash - The viewing key hash
218
+ * @returns Promise resolving to the key or null if not found
219
+ */
220
+ load(hash: Hash): Promise<NEARViewingKey | null>
221
+
222
+ /**
223
+ * List all stored viewing keys
224
+ * @returns Promise resolving to array of keys
225
+ */
226
+ list(): Promise<NEARViewingKey[]>
227
+
228
+ /**
229
+ * Delete a viewing key by its hash
230
+ * @param hash - The viewing key hash
231
+ * @returns Promise resolving to true if deleted, false if not found
232
+ */
233
+ delete(hash: Hash): Promise<boolean>
234
+ }
235
+
236
+ // ─── Viewing Key Generation ───────────────────────────────────────────────────
237
+
238
+ /**
239
+ * Generate a viewing key from a spending private key
240
+ *
241
+ * The viewing key is derived deterministically using HMAC-SHA256 with domain
242
+ * separation, ensuring it cannot be used to derive the spending key.
243
+ *
244
+ * @param spendingPrivateKey - The spending private key (32 bytes, hex)
245
+ * @param label - Optional label for the viewing key
246
+ * @returns The generated viewing key with public key and hash
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * const viewingKey = generateNEARViewingKeyFromSpending(
251
+ * spendingPrivateKey,
252
+ * 'My NEAR Wallet'
253
+ * )
254
+ *
255
+ * // Share the public key for encryption
256
+ * console.log('Public key:', viewingKey.publicKey)
257
+ *
258
+ * // Use hash for on-chain announcement matching
259
+ * console.log('Hash:', viewingKey.hash)
260
+ * ```
261
+ */
262
+ export function generateNEARViewingKeyFromSpending(
263
+ spendingPrivateKey: HexString,
264
+ label?: string
265
+ ): NEARViewingKey {
266
+ // Validate input
267
+ if (!spendingPrivateKey || !spendingPrivateKey.startsWith('0x')) {
268
+ throw new ValidationError(
269
+ 'spendingPrivateKey must be a hex string with 0x prefix',
270
+ 'spendingPrivateKey'
271
+ )
272
+ }
273
+
274
+ const spendingBytes = hexToBytes(spendingPrivateKey.slice(2))
275
+
276
+ if (spendingBytes.length !== 32) {
277
+ throw new ValidationError(
278
+ 'spendingPrivateKey must be 32 bytes',
279
+ 'spendingPrivateKey'
280
+ )
281
+ }
282
+
283
+ let viewingPrivateBytes: Uint8Array | null = null
284
+
285
+ try {
286
+ // Derive viewing key using HMAC-SHA256 with domain separation
287
+ viewingPrivateBytes = hmac(
288
+ sha256,
289
+ utf8ToBytes(VIEWING_KEY_CONTEXT),
290
+ spendingBytes
291
+ )
292
+
293
+ // Derive public key
294
+ const viewingPublicBytes = ed25519.getPublicKey(viewingPrivateBytes)
295
+
296
+ // Compute hash for announcement matching
297
+ const hashBytes = sha256(viewingPublicBytes)
298
+
299
+ return {
300
+ privateKey: `0x${bytesToHex(viewingPrivateBytes)}` as HexString,
301
+ publicKey: `0x${bytesToHex(viewingPublicBytes)}` as HexString,
302
+ hash: `0x${bytesToHex(hashBytes)}` as Hash,
303
+ label,
304
+ createdAt: Date.now(),
305
+ }
306
+ } finally {
307
+ // Secure wipe sensitive data
308
+ secureWipe(spendingBytes)
309
+ if (viewingPrivateBytes) secureWipe(viewingPrivateBytes)
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Generate a new random viewing key
315
+ *
316
+ * Creates a cryptographically random viewing key that is NOT derived from
317
+ * a spending key. Use this for standalone viewing keys or testing.
318
+ *
319
+ * @param label - Optional label for the viewing key
320
+ * @returns The generated viewing key
321
+ *
322
+ * @example
323
+ * ```typescript
324
+ * const viewingKey = generateRandomNEARViewingKey('Audit Key')
325
+ * ```
326
+ */
327
+ export function generateRandomNEARViewingKey(label?: string): NEARViewingKey {
328
+ const privateBytes = randomBytes(32)
329
+
330
+ try {
331
+ const publicBytes = ed25519.getPublicKey(privateBytes)
332
+ const hashBytes = sha256(publicBytes)
333
+
334
+ return {
335
+ privateKey: `0x${bytesToHex(privateBytes)}` as HexString,
336
+ publicKey: `0x${bytesToHex(publicBytes)}` as HexString,
337
+ hash: `0x${bytesToHex(hashBytes)}` as Hash,
338
+ label,
339
+ createdAt: Date.now(),
340
+ }
341
+ } finally {
342
+ secureWipe(privateBytes)
343
+ }
344
+ }
345
+
346
+ // ─── Viewing Key Hash ─────────────────────────────────────────────────────────
347
+
348
+ /**
349
+ * Compute the viewing key hash from a public key
350
+ *
351
+ * The hash is used for announcement matching on-chain. Recipients publish
352
+ * their viewing key hash, and senders include it in transaction announcements.
353
+ *
354
+ * @param viewingPublicKey - The viewing public key (32 bytes, hex)
355
+ * @returns The hash for announcement matching
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * const hash = computeNEARViewingKeyHash(viewingKey.publicKey)
360
+ * // Use hash in transaction announcements
361
+ * ```
362
+ */
363
+ export function computeNEARViewingKeyHash(viewingPublicKey: HexString): Hash {
364
+ if (!viewingPublicKey || !viewingPublicKey.startsWith('0x')) {
365
+ throw new ValidationError(
366
+ 'viewingPublicKey must be a hex string with 0x prefix',
367
+ 'viewingPublicKey'
368
+ )
369
+ }
370
+
371
+ const publicBytes = hexToBytes(viewingPublicKey.slice(2))
372
+
373
+ if (publicBytes.length !== 32) {
374
+ throw new ValidationError(
375
+ 'viewingPublicKey must be 32 bytes',
376
+ 'viewingPublicKey'
377
+ )
378
+ }
379
+
380
+ const hashBytes = sha256(publicBytes)
381
+ return `0x${bytesToHex(hashBytes)}` as Hash
382
+ }
383
+
384
+ /**
385
+ * Compute viewing key hash from a private key
386
+ *
387
+ * Derives the public key and computes its hash.
388
+ *
389
+ * @param viewingPrivateKey - The viewing private key (32 bytes, hex)
390
+ * @returns The hash for announcement matching
391
+ */
392
+ export function computeNEARViewingKeyHashFromPrivate(
393
+ viewingPrivateKey: HexString
394
+ ): Hash {
395
+ if (!viewingPrivateKey || !viewingPrivateKey.startsWith('0x')) {
396
+ throw new ValidationError(
397
+ 'viewingPrivateKey must be a hex string with 0x prefix',
398
+ 'viewingPrivateKey'
399
+ )
400
+ }
401
+
402
+ const privateBytes = hexToBytes(viewingPrivateKey.slice(2))
403
+
404
+ if (privateBytes.length !== 32) {
405
+ throw new ValidationError(
406
+ 'viewingPrivateKey must be 32 bytes',
407
+ 'viewingPrivateKey'
408
+ )
409
+ }
410
+
411
+ try {
412
+ const publicBytes = ed25519.getPublicKey(privateBytes)
413
+ const hashBytes = sha256(publicBytes)
414
+ return `0x${bytesToHex(hashBytes)}` as Hash
415
+ } finally {
416
+ secureWipe(privateBytes)
417
+ }
418
+ }
419
+
420
+ // ─── Export/Import ────────────────────────────────────────────────────────────
421
+
422
+ /**
423
+ * Export a viewing key in standard JSON format
424
+ *
425
+ * The export format includes version information for forward compatibility
426
+ * and can be safely serialized to JSON.
427
+ *
428
+ * @param viewingKey - The viewing key to export
429
+ * @returns The export object (serialize with JSON.stringify)
430
+ *
431
+ * @example
432
+ * ```typescript
433
+ * const exported = exportNEARViewingKey(viewingKey)
434
+ * const json = JSON.stringify(exported)
435
+ *
436
+ * // Save to file or send to auditor
437
+ * ```
438
+ */
439
+ export function exportNEARViewingKey(viewingKey: NEARViewingKey): NEARViewingKeyExport {
440
+ return {
441
+ version: EXPORT_VERSION,
442
+ chain: 'near',
443
+ privateKey: viewingKey.privateKey,
444
+ publicKey: viewingKey.publicKey,
445
+ hash: viewingKey.hash,
446
+ label: viewingKey.label,
447
+ createdAt: viewingKey.createdAt,
448
+ exportedAt: Date.now(),
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Import a viewing key from standard JSON format
454
+ *
455
+ * Validates the export format and reconstructs the viewing key object.
456
+ *
457
+ * @param exported - The exported viewing key data
458
+ * @returns The imported viewing key
459
+ * @throws {ValidationError} If the export format is invalid
460
+ *
461
+ * @example
462
+ * ```typescript
463
+ * const json = await readFile('near-viewing-key.json')
464
+ * const exported = JSON.parse(json)
465
+ * const viewingKey = importNEARViewingKey(exported)
466
+ * ```
467
+ */
468
+ export function importNEARViewingKey(exported: NEARViewingKeyExport): NEARViewingKey {
469
+ // Validate version
470
+ if (exported.version !== EXPORT_VERSION) {
471
+ throw new ValidationError(
472
+ `Unsupported export version: ${exported.version}. Expected: ${EXPORT_VERSION}`,
473
+ 'version'
474
+ )
475
+ }
476
+
477
+ // Validate chain
478
+ if (exported.chain !== 'near') {
479
+ throw new ValidationError(
480
+ `Invalid chain: ${exported.chain}. Expected: near`,
481
+ 'chain'
482
+ )
483
+ }
484
+
485
+ // Validate keys
486
+ if (!isValidHex(exported.privateKey) || exported.privateKey.length !== 66) {
487
+ throw new ValidationError('Invalid private key format', 'privateKey')
488
+ }
489
+
490
+ if (!isValidHex(exported.publicKey) || exported.publicKey.length !== 66) {
491
+ throw new ValidationError('Invalid public key format', 'publicKey')
492
+ }
493
+
494
+ if (!isValidHex(exported.hash) || exported.hash.length !== 66) {
495
+ throw new ValidationError('Invalid hash format', 'hash')
496
+ }
497
+
498
+ // Verify the hash matches the public key
499
+ const computedHash = computeNEARViewingKeyHash(exported.publicKey)
500
+ if (computedHash !== exported.hash) {
501
+ throw new ValidationError(
502
+ 'Hash does not match public key',
503
+ 'hash',
504
+ { expected: computedHash, received: exported.hash }
505
+ )
506
+ }
507
+
508
+ // Verify public key matches private key
509
+ const privateBytes = hexToBytes(exported.privateKey.slice(2))
510
+ try {
511
+ const derivedPublic = `0x${bytesToHex(ed25519.getPublicKey(privateBytes))}` as HexString
512
+ if (derivedPublic !== exported.publicKey) {
513
+ throw new ValidationError(
514
+ 'Public key does not match private key',
515
+ 'publicKey'
516
+ )
517
+ }
518
+ } finally {
519
+ secureWipe(privateBytes)
520
+ }
521
+
522
+ return {
523
+ privateKey: exported.privateKey,
524
+ publicKey: exported.publicKey,
525
+ hash: exported.hash,
526
+ label: exported.label,
527
+ createdAt: exported.createdAt,
528
+ }
529
+ }
530
+
531
+ // ─── Encryption/Decryption ────────────────────────────────────────────────────
532
+
533
+ /**
534
+ * Derive an encryption key from a viewing key using HKDF
535
+ *
536
+ * @param key - The viewing key (private or public depending on operation)
537
+ * @param salt - Optional salt for HKDF
538
+ * @returns 32-byte encryption key (caller must wipe after use)
539
+ */
540
+ function deriveEncryptionKey(key: HexString, salt?: Uint8Array): Uint8Array {
541
+ const keyBytes = hexToBytes(key.slice(2))
542
+
543
+ try {
544
+ // Use HKDF to derive a proper encryption key
545
+ const hkdfSalt = salt ?? utf8ToBytes(ENCRYPTION_DOMAIN)
546
+ return hkdf(sha256, keyBytes, hkdfSalt, utf8ToBytes('encryption'), 32)
547
+ } finally {
548
+ secureWipe(keyBytes)
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Encrypt transaction data for viewing key holders
554
+ *
555
+ * Uses XChaCha20-Poly1305 authenticated encryption with a random nonce.
556
+ * The encryption key is derived from the viewing private key using HKDF.
557
+ *
558
+ * @param data - Transaction data to encrypt
559
+ * @param viewingKey - The viewing key for encryption
560
+ * @returns Encrypted payload with nonce and key hash
561
+ *
562
+ * @example
563
+ * ```typescript
564
+ * const encrypted = encryptForNEARViewing({
565
+ * sender: 'alice.near',
566
+ * recipient: stealthAccountId,
567
+ * amount: '1000000000000000000000000', // 1 NEAR
568
+ * tokenContract: null, // native NEAR
569
+ * decimals: 24,
570
+ * timestamp: Date.now(),
571
+ * }, viewingKey)
572
+ *
573
+ * // Store encrypted.ciphertext on-chain or off-chain
574
+ * ```
575
+ */
576
+ export function encryptForNEARViewing(
577
+ data: NEARTransactionData,
578
+ viewingKey: NEARViewingKey
579
+ ): NEAREncryptedPayload {
580
+ // Derive encryption key from viewing private key
581
+ const encKey = deriveEncryptionKey(viewingKey.privateKey)
582
+
583
+ try {
584
+ // Generate random nonce
585
+ const nonce = randomBytes(NONCE_SIZE)
586
+
587
+ // Serialize data to JSON
588
+ const plaintext = utf8ToBytes(JSON.stringify(data))
589
+
590
+ // Encrypt with XChaCha20-Poly1305
591
+ const cipher = xchacha20poly1305(encKey, nonce)
592
+ const ciphertext = cipher.encrypt(plaintext)
593
+
594
+ return {
595
+ ciphertext: `0x${bytesToHex(ciphertext)}` as HexString,
596
+ nonce: `0x${bytesToHex(nonce)}` as HexString,
597
+ viewingKeyHash: viewingKey.hash,
598
+ }
599
+ } finally {
600
+ secureWipe(encKey)
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Decrypt transaction data with a viewing key
606
+ *
607
+ * @param encrypted - The encrypted payload
608
+ * @param viewingKey - The viewing key for decryption
609
+ * @returns The decrypted transaction data
610
+ * @throws {CryptoError} If decryption fails (wrong key or tampered data)
611
+ *
612
+ * @example
613
+ * ```typescript
614
+ * const data = decryptWithNEARViewing(encrypted, viewingKey)
615
+ * console.log('Amount:', data.amount)
616
+ * console.log('Sender:', data.sender)
617
+ * ```
618
+ */
619
+ export function decryptWithNEARViewing(
620
+ encrypted: NEAREncryptedPayload,
621
+ viewingKey: NEARViewingKey
622
+ ): NEARTransactionData {
623
+ // Verify the viewing key can decrypt this
624
+ if (encrypted.viewingKeyHash !== viewingKey.hash) {
625
+ throw new CryptoError(
626
+ 'Viewing key hash does not match encrypted payload',
627
+ ErrorCode.DECRYPTION_FAILED,
628
+ { context: { expected: encrypted.viewingKeyHash, received: viewingKey.hash } }
629
+ )
630
+ }
631
+
632
+ // Derive encryption key
633
+ const encKey = deriveEncryptionKey(viewingKey.privateKey)
634
+
635
+ try {
636
+ // Parse ciphertext and nonce
637
+ const ciphertext = hexToBytes(encrypted.ciphertext.slice(2))
638
+ const nonce = hexToBytes(encrypted.nonce.slice(2))
639
+
640
+ if (nonce.length !== NONCE_SIZE) {
641
+ throw new ValidationError(
642
+ `Invalid nonce length: ${nonce.length}. Expected: ${NONCE_SIZE}`,
643
+ 'nonce'
644
+ )
645
+ }
646
+
647
+ // Decrypt with XChaCha20-Poly1305
648
+ const cipher = xchacha20poly1305(encKey, nonce)
649
+ const plaintext = cipher.decrypt(ciphertext)
650
+
651
+ // Parse JSON
652
+ const json = new TextDecoder().decode(plaintext)
653
+ return JSON.parse(json) as NEARTransactionData
654
+ } catch (error) {
655
+ if (error instanceof ValidationError || error instanceof CryptoError) {
656
+ throw error
657
+ }
658
+ throw new CryptoError(
659
+ 'Failed to decrypt: authentication failed or data corrupted',
660
+ ErrorCode.DECRYPTION_FAILED,
661
+ { cause: error instanceof Error ? error : undefined }
662
+ )
663
+ } finally {
664
+ secureWipe(encKey)
665
+ }
666
+ }
667
+
668
+ // ─── In-Memory Storage ────────────────────────────────────────────────────────
669
+
670
+ /**
671
+ * Simple in-memory viewing key storage
672
+ *
673
+ * Useful for testing and temporary storage. For production use,
674
+ * implement a persistent storage provider.
675
+ *
676
+ * @example
677
+ * ```typescript
678
+ * const storage = createNEARMemoryStorage()
679
+ *
680
+ * // Store a key
681
+ * await storage.save(viewingKey)
682
+ *
683
+ * // List all keys
684
+ * const keys = await storage.list()
685
+ *
686
+ * // Load a specific key
687
+ * const key = await storage.load(hash)
688
+ * ```
689
+ */
690
+ export function createNEARMemoryStorage(): NEARViewingKeyStorage {
691
+ const keys = new Map<Hash, NEARViewingKey>()
692
+
693
+ return {
694
+ async save(key: NEARViewingKey): Promise<Hash> {
695
+ keys.set(key.hash, { ...key })
696
+ return key.hash
697
+ },
698
+
699
+ async load(hash: Hash): Promise<NEARViewingKey | null> {
700
+ const key = keys.get(hash)
701
+ return key ? { ...key } : null
702
+ },
703
+
704
+ async list(): Promise<NEARViewingKey[]> {
705
+ return Array.from(keys.values()).map(k => ({ ...k }))
706
+ },
707
+
708
+ async delete(hash: Hash): Promise<boolean> {
709
+ return keys.delete(hash)
710
+ },
711
+ }
712
+ }
713
+
714
+ // ─── Utilities ────────────────────────────────────────────────────────────────
715
+
716
+ /**
717
+ * Check if an announcement hash matches a viewing key
718
+ *
719
+ * Used during scanning to efficiently filter announcements that belong
720
+ * to this viewing key.
721
+ *
722
+ * @param announcementHash - Hash from the on-chain announcement
723
+ * @param viewingKey - The viewing key to check against
724
+ * @returns true if the announcement is for this viewing key
725
+ */
726
+ export function isNEARAnnouncementForViewingKey(
727
+ announcementHash: Hash,
728
+ viewingKey: NEARViewingKey
729
+ ): boolean {
730
+ return announcementHash === viewingKey.hash
731
+ }
732
+
733
+ /**
734
+ * Derive a child viewing key for hierarchical key management
735
+ *
736
+ * Uses HMAC-SHA256 with the parent key and child path to derive
737
+ * a new independent viewing key.
738
+ *
739
+ * @param parentKey - The parent viewing key
740
+ * @param childPath - A path string for derivation (e.g., "audit/2024")
741
+ * @param label - Optional label for the child key
742
+ * @returns The derived child viewing key
743
+ *
744
+ * @example
745
+ * ```typescript
746
+ * const auditKey = deriveNEARChildViewingKey(masterKey, 'audit/2024', 'Audit 2024')
747
+ * const accountingKey = deriveNEARChildViewingKey(masterKey, 'accounting', 'Accounting')
748
+ * ```
749
+ */
750
+ export function deriveNEARChildViewingKey(
751
+ parentKey: NEARViewingKey,
752
+ childPath: string,
753
+ label?: string
754
+ ): NEARViewingKey {
755
+ if (!childPath || typeof childPath !== 'string') {
756
+ throw new ValidationError('childPath must be a non-empty string', 'childPath')
757
+ }
758
+
759
+ const parentBytes = hexToBytes(parentKey.privateKey.slice(2))
760
+
761
+ try {
762
+ // Derive child key using HMAC-SHA256
763
+ const childBytes = hmac(sha256, utf8ToBytes(childPath), parentBytes)
764
+ const publicBytes = ed25519.getPublicKey(childBytes)
765
+ const hashBytes = sha256(publicBytes)
766
+
767
+ const result: NEARViewingKey = {
768
+ privateKey: `0x${bytesToHex(childBytes)}` as HexString,
769
+ publicKey: `0x${bytesToHex(publicBytes)}` as HexString,
770
+ hash: `0x${bytesToHex(hashBytes)}` as Hash,
771
+ label: label ?? `${parentKey.label ?? 'Key'}/${childPath}`,
772
+ createdAt: Date.now(),
773
+ }
774
+
775
+ // Wipe child bytes after creating hex
776
+ secureWipe(childBytes)
777
+
778
+ return result
779
+ } finally {
780
+ secureWipe(parentBytes)
781
+ }
782
+ }
783
+
784
+ /**
785
+ * Get the public key from a viewing private key
786
+ *
787
+ * @param viewingPrivateKey - The viewing private key
788
+ * @returns The corresponding public key
789
+ */
790
+ export function getNEARViewingPublicKey(viewingPrivateKey: HexString): HexString {
791
+ if (!viewingPrivateKey || !viewingPrivateKey.startsWith('0x')) {
792
+ throw new ValidationError(
793
+ 'viewingPrivateKey must be a hex string with 0x prefix',
794
+ 'viewingPrivateKey'
795
+ )
796
+ }
797
+
798
+ const privateBytes = hexToBytes(viewingPrivateKey.slice(2))
799
+
800
+ if (privateBytes.length !== 32) {
801
+ throw new ValidationError(
802
+ 'viewingPrivateKey must be 32 bytes',
803
+ 'viewingPrivateKey'
804
+ )
805
+ }
806
+
807
+ try {
808
+ const publicBytes = ed25519.getPublicKey(privateBytes)
809
+ return `0x${bytesToHex(publicBytes)}` as HexString
810
+ } finally {
811
+ secureWipe(privateBytes)
812
+ }
813
+ }
814
+
815
+ /**
816
+ * Validate a NEAR viewing key object
817
+ *
818
+ * @param viewingKey - The viewing key to validate
819
+ * @returns true if the viewing key is valid
820
+ * @throws {ValidationError} If the viewing key is invalid
821
+ */
822
+ export function validateNEARViewingKey(viewingKey: NEARViewingKey): boolean {
823
+ if (!viewingKey) {
824
+ throw new ValidationError('viewingKey is required', 'viewingKey')
825
+ }
826
+
827
+ // Check privateKey
828
+ if (!viewingKey.privateKey || !viewingKey.privateKey.startsWith('0x')) {
829
+ throw new ValidationError('privateKey must be a hex string with 0x prefix', 'privateKey')
830
+ }
831
+ if (viewingKey.privateKey.length !== 66) {
832
+ throw new ValidationError('privateKey must be 32 bytes (66 chars with 0x)', 'privateKey')
833
+ }
834
+
835
+ // Check publicKey
836
+ if (!viewingKey.publicKey || !viewingKey.publicKey.startsWith('0x')) {
837
+ throw new ValidationError('publicKey must be a hex string with 0x prefix', 'publicKey')
838
+ }
839
+ if (viewingKey.publicKey.length !== 66) {
840
+ throw new ValidationError('publicKey must be 32 bytes (66 chars with 0x)', 'publicKey')
841
+ }
842
+
843
+ // Check hash
844
+ if (!viewingKey.hash || !viewingKey.hash.startsWith('0x')) {
845
+ throw new ValidationError('hash must be a hex string with 0x prefix', 'hash')
846
+ }
847
+ if (viewingKey.hash.length !== 66) {
848
+ throw new ValidationError('hash must be 32 bytes (66 chars with 0x)', 'hash')
849
+ }
850
+
851
+ // Verify hash matches public key
852
+ const computedHash = computeNEARViewingKeyHash(viewingKey.publicKey)
853
+ if (computedHash !== viewingKey.hash) {
854
+ throw new ValidationError(
855
+ 'hash does not match publicKey',
856
+ 'hash',
857
+ { expected: computedHash, received: viewingKey.hash }
858
+ )
859
+ }
860
+
861
+ // Verify public key matches private key
862
+ const privateBytes = hexToBytes(viewingKey.privateKey.slice(2))
863
+ try {
864
+ const derivedPublic = `0x${bytesToHex(ed25519.getPublicKey(privateBytes))}` as HexString
865
+ if (derivedPublic !== viewingKey.publicKey) {
866
+ throw new ValidationError(
867
+ 'publicKey does not match privateKey',
868
+ 'publicKey'
869
+ )
870
+ }
871
+ } finally {
872
+ secureWipe(privateBytes)
873
+ }
874
+
875
+ return true
876
+ }