@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,193 @@
1
+ /**
2
+ * Chain-Specific Address Derivation
3
+ *
4
+ * Functions for converting stealth addresses to chain-specific formats.
5
+ */
6
+
7
+ import type { HexString } from '@sip-protocol/types'
8
+ import { ValidationError } from '../errors'
9
+ import { isValidHex, isValidEd25519PublicKey } from '../validation'
10
+ import { bytesToBase58, base58ToBytes, bytesToHex, hexToBytes } from './utils'
11
+
12
+ // ─── Solana Address Derivation ──────────────────────────────────────────────
13
+
14
+ /**
15
+ * Convert an ed25519 public key (hex) to a Solana address (base58)
16
+ *
17
+ * Solana addresses are base58-encoded 32-byte ed25519 public keys.
18
+ *
19
+ * @param publicKey - 32-byte ed25519 public key as hex string (with 0x prefix)
20
+ * @returns Base58-encoded Solana address
21
+ * @throws {ValidationError} If public key is invalid
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const { stealthAddress } = generateEd25519StealthAddress(metaAddress)
26
+ * const solanaAddress = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
27
+ * // Returns: "7Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPBJCyqLqzQPvN" (example)
28
+ * ```
29
+ */
30
+ export function ed25519PublicKeyToSolanaAddress(publicKey: HexString): string {
31
+ if (!isValidHex(publicKey)) {
32
+ throw new ValidationError(
33
+ 'publicKey must be a valid hex string with 0x prefix',
34
+ 'publicKey'
35
+ )
36
+ }
37
+
38
+ if (!isValidEd25519PublicKey(publicKey)) {
39
+ throw new ValidationError(
40
+ 'publicKey must be 32 bytes (64 hex characters)',
41
+ 'publicKey'
42
+ )
43
+ }
44
+
45
+ // Convert hex to bytes (remove 0x prefix)
46
+ const publicKeyBytes = hexToBytes(publicKey.slice(2))
47
+
48
+ // Encode as base58
49
+ return bytesToBase58(publicKeyBytes)
50
+ }
51
+
52
+ /**
53
+ * Validate a Solana address format
54
+ *
55
+ * Checks that the address:
56
+ * - Is a valid base58 string
57
+ * - Decodes to exactly 32 bytes (ed25519 public key size)
58
+ */
59
+ export function isValidSolanaAddress(address: string): boolean {
60
+ if (typeof address !== 'string' || address.length === 0) {
61
+ return false
62
+ }
63
+
64
+ // Solana addresses are typically 32-44 characters
65
+ if (address.length < 32 || address.length > 44) {
66
+ return false
67
+ }
68
+
69
+ try {
70
+ const decoded = base58ToBytes(address)
71
+ // Valid Solana address is exactly 32 bytes
72
+ return decoded.length === 32
73
+ } catch {
74
+ return false
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Convert a Solana address (base58) back to ed25519 public key (hex)
80
+ */
81
+ export function solanaAddressToEd25519PublicKey(address: string): HexString {
82
+ if (!isValidSolanaAddress(address)) {
83
+ throw new ValidationError(
84
+ 'Invalid Solana address format',
85
+ 'address'
86
+ )
87
+ }
88
+
89
+ const decoded = base58ToBytes(address)
90
+ return `0x${bytesToHex(decoded)}` as HexString
91
+ }
92
+
93
+ // ─── NEAR Address Derivation ────────────────────────────────────────────────
94
+
95
+ /**
96
+ * Convert ed25519 public key to NEAR implicit account address
97
+ *
98
+ * NEAR implicit accounts are lowercase hex-encoded ed25519 public keys (64 characters).
99
+ * No prefix, just raw 32 bytes as lowercase hex.
100
+ */
101
+ export function ed25519PublicKeyToNearAddress(publicKey: HexString): string {
102
+ if (!isValidHex(publicKey)) {
103
+ throw new ValidationError(
104
+ 'publicKey must be a valid hex string with 0x prefix',
105
+ 'publicKey'
106
+ )
107
+ }
108
+
109
+ if (!isValidEd25519PublicKey(publicKey)) {
110
+ throw new ValidationError(
111
+ 'publicKey must be 32 bytes (64 hex characters)',
112
+ 'publicKey'
113
+ )
114
+ }
115
+
116
+ // NEAR implicit accounts are lowercase hex without 0x prefix
117
+ return publicKey.slice(2).toLowerCase()
118
+ }
119
+
120
+ /**
121
+ * Convert NEAR implicit account address back to ed25519 public key
122
+ */
123
+ export function nearAddressToEd25519PublicKey(address: string): HexString {
124
+ if (!isValidNearImplicitAddress(address)) {
125
+ throw new ValidationError(
126
+ 'Invalid NEAR implicit address format',
127
+ 'address'
128
+ )
129
+ }
130
+
131
+ return `0x${address.toLowerCase()}` as HexString
132
+ }
133
+
134
+ /**
135
+ * Validate a NEAR implicit account address
136
+ *
137
+ * NEAR implicit accounts are:
138
+ * - Exactly 64 lowercase hex characters
139
+ * - No prefix (no "0x")
140
+ * - Represent a 32-byte ed25519 public key
141
+ */
142
+ export function isValidNearImplicitAddress(address: string): boolean {
143
+ // Must be a string
144
+ if (typeof address !== 'string' || address.length === 0) {
145
+ return false
146
+ }
147
+
148
+ // Must be exactly 64 characters (32 bytes as hex)
149
+ if (address.length !== 64) {
150
+ return false
151
+ }
152
+
153
+ // Must be lowercase hex only (no 0x prefix)
154
+ return /^[0-9a-f]{64}$/.test(address)
155
+ }
156
+
157
+ /**
158
+ * Check if a string is a valid NEAR account ID (named or implicit)
159
+ *
160
+ * Supports both:
161
+ * - Named accounts: alice.near, bob.testnet
162
+ * - Implicit accounts: 64 hex characters
163
+ */
164
+ export function isValidNearAccountId(accountId: string): boolean {
165
+ // Must be a string
166
+ if (typeof accountId !== 'string' || accountId.length === 0) {
167
+ return false
168
+ }
169
+
170
+ // Check if it's a valid implicit account (64 hex chars)
171
+ if (isValidNearImplicitAddress(accountId)) {
172
+ return true
173
+ }
174
+
175
+ // Named accounts: 2-64 characters, lowercase alphanumeric with . _ -
176
+ // Must start and end with alphanumeric
177
+ if (accountId.length < 2 || accountId.length > 64) {
178
+ return false
179
+ }
180
+
181
+ // NEAR account ID pattern
182
+ const nearAccountPattern = /^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/
183
+ if (!nearAccountPattern.test(accountId)) {
184
+ return false
185
+ }
186
+
187
+ // Cannot have consecutive dots
188
+ if (accountId.includes('..')) {
189
+ return false
190
+ }
191
+
192
+ return true
193
+ }
@@ -0,0 +1,431 @@
1
+ /**
2
+ * ed25519 Stealth Address Implementation
3
+ *
4
+ * Implements DKSAP (Dual-Key Stealth Address Protocol) for ed25519 curves.
5
+ * Used for Solana, NEAR, Aptos, and Sui chains.
6
+ */
7
+
8
+ import { ed25519 } from '@noble/curves/ed25519'
9
+ import { randomBytes } from '@noble/hashes/utils'
10
+ import type {
11
+ StealthMetaAddress,
12
+ StealthAddress,
13
+ StealthAddressRecovery,
14
+ ChainId,
15
+ HexString,
16
+ } from '@sip-protocol/types'
17
+ import { ValidationError } from '../errors'
18
+ import {
19
+ isValidChainId,
20
+ isValidEd25519PublicKey,
21
+ isValidPrivateKey,
22
+ } from '../validation'
23
+ import { secureWipe, secureWipeAll } from '../secure-memory'
24
+ import {
25
+ bytesToBigInt,
26
+ bigIntToBytesLE,
27
+ sha256,
28
+ bytesToHex,
29
+ hexToBytes,
30
+ ED25519_ORDER,
31
+ ED25519_CHAINS,
32
+ getEd25519Scalar,
33
+ } from './utils'
34
+
35
+ // ─── Chain Detection ────────────────────────────────────────────────────────
36
+
37
+ /**
38
+ * Check if a chain uses ed25519 for stealth addresses
39
+ */
40
+ export function isEd25519Chain(chain: ChainId): boolean {
41
+ return (ED25519_CHAINS as readonly string[]).includes(chain)
42
+ }
43
+
44
+ /**
45
+ * Curve type used for stealth addresses
46
+ */
47
+ export type StealthCurve = 'secp256k1' | 'ed25519'
48
+
49
+ /**
50
+ * Get the curve type used by a chain for stealth addresses
51
+ *
52
+ * @param chain - Chain identifier
53
+ * @returns 'ed25519' for Solana/NEAR/Aptos/Sui, 'secp256k1' for EVM chains
54
+ */
55
+ export function getCurveForChain(chain: ChainId): StealthCurve {
56
+ return isEd25519Chain(chain) ? 'ed25519' : 'secp256k1'
57
+ }
58
+
59
+ // ─── Validation ─────────────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * Validate an ed25519 StealthMetaAddress object
63
+ */
64
+ export function validateEd25519StealthMetaAddress(
65
+ metaAddress: StealthMetaAddress,
66
+ field: string = 'recipientMetaAddress'
67
+ ): void {
68
+ if (!metaAddress || typeof metaAddress !== 'object') {
69
+ throw new ValidationError('must be an object', field)
70
+ }
71
+
72
+ if (!isValidChainId(metaAddress.chain)) {
73
+ throw new ValidationError(
74
+ `invalid chain '${metaAddress.chain}'`,
75
+ `${field}.chain`
76
+ )
77
+ }
78
+
79
+ if (!isEd25519Chain(metaAddress.chain)) {
80
+ throw new ValidationError(
81
+ `chain '${metaAddress.chain}' does not use ed25519, use secp256k1 functions instead`,
82
+ `${field}.chain`
83
+ )
84
+ }
85
+
86
+ if (!isValidEd25519PublicKey(metaAddress.spendingKey)) {
87
+ throw new ValidationError(
88
+ 'spendingKey must be a valid ed25519 public key (32 bytes)',
89
+ `${field}.spendingKey`
90
+ )
91
+ }
92
+
93
+ if (!isValidEd25519PublicKey(metaAddress.viewingKey)) {
94
+ throw new ValidationError(
95
+ 'viewingKey must be a valid ed25519 public key (32 bytes)',
96
+ `${field}.viewingKey`
97
+ )
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Validate an ed25519 StealthAddress object
103
+ */
104
+ export function validateEd25519StealthAddress(
105
+ stealthAddress: StealthAddress,
106
+ field: string = 'stealthAddress'
107
+ ): void {
108
+ if (!stealthAddress || typeof stealthAddress !== 'object') {
109
+ throw new ValidationError('must be an object', field)
110
+ }
111
+
112
+ if (!isValidEd25519PublicKey(stealthAddress.address)) {
113
+ throw new ValidationError(
114
+ 'address must be a valid ed25519 public key (32 bytes)',
115
+ `${field}.address`
116
+ )
117
+ }
118
+
119
+ if (!isValidEd25519PublicKey(stealthAddress.ephemeralPublicKey)) {
120
+ throw new ValidationError(
121
+ 'ephemeralPublicKey must be a valid ed25519 public key (32 bytes)',
122
+ `${field}.ephemeralPublicKey`
123
+ )
124
+ }
125
+
126
+ if (typeof stealthAddress.viewTag !== 'number' ||
127
+ !Number.isInteger(stealthAddress.viewTag) ||
128
+ stealthAddress.viewTag < 0 ||
129
+ stealthAddress.viewTag > 255) {
130
+ throw new ValidationError(
131
+ 'viewTag must be an integer between 0 and 255',
132
+ `${field}.viewTag`
133
+ )
134
+ }
135
+ }
136
+
137
+ // ─── Meta-Address Generation ────────────────────────────────────────────────
138
+
139
+ /**
140
+ * Generate a new ed25519 stealth meta-address keypair
141
+ *
142
+ * @param chain - Target chain (must be ed25519-compatible: solana, near, aptos, sui)
143
+ * @param label - Optional human-readable label
144
+ * @returns Stealth meta-address and private keys
145
+ * @throws {ValidationError} If chain is invalid or not ed25519-compatible
146
+ */
147
+ export function generateEd25519StealthMetaAddress(
148
+ chain: ChainId,
149
+ label?: string,
150
+ ): {
151
+ metaAddress: StealthMetaAddress
152
+ spendingPrivateKey: HexString
153
+ viewingPrivateKey: HexString
154
+ } {
155
+ if (!isValidChainId(chain)) {
156
+ throw new ValidationError(
157
+ `invalid chain '${chain}', must be one of: solana, ethereum, near, zcash, polygon, arbitrum, optimism, base, bitcoin, aptos, sui, cosmos, osmosis, injective, celestia, sei, dydx`,
158
+ 'chain'
159
+ )
160
+ }
161
+
162
+ if (!isEd25519Chain(chain)) {
163
+ throw new ValidationError(
164
+ `chain '${chain}' does not use ed25519, use generateStealthMetaAddress() for secp256k1 chains`,
165
+ 'chain'
166
+ )
167
+ }
168
+
169
+ // Generate random private keys (32-byte seeds)
170
+ const spendingPrivateKey = randomBytes(32)
171
+ const viewingPrivateKey = randomBytes(32)
172
+
173
+ try {
174
+ // Derive public keys using ed25519
175
+ const spendingKey = ed25519.getPublicKey(spendingPrivateKey)
176
+ const viewingKey = ed25519.getPublicKey(viewingPrivateKey)
177
+
178
+ return {
179
+ metaAddress: {
180
+ spendingKey: `0x${bytesToHex(spendingKey)}` as HexString,
181
+ viewingKey: `0x${bytesToHex(viewingKey)}` as HexString,
182
+ chain,
183
+ label,
184
+ },
185
+ spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}` as HexString,
186
+ viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}` as HexString,
187
+ }
188
+ } finally {
189
+ secureWipeAll(spendingPrivateKey, viewingPrivateKey)
190
+ }
191
+ }
192
+
193
+ // ─── Stealth Address Generation ─────────────────────────────────────────────
194
+
195
+ /**
196
+ * Generate a one-time ed25519 stealth address for a recipient
197
+ *
198
+ * Algorithm (DKSAP for ed25519):
199
+ * 1. Generate ephemeral keypair (r, R = r*G)
200
+ * 2. Compute shared secret: S = r * P_spend (ephemeral scalar * spending public)
201
+ * 3. Hash shared secret: h = SHA256(S)
202
+ * 4. Derive stealth public key: P_stealth = P_view + h*G
203
+ */
204
+ export function generateEd25519StealthAddress(
205
+ recipientMetaAddress: StealthMetaAddress,
206
+ ): {
207
+ stealthAddress: StealthAddress
208
+ sharedSecret: HexString
209
+ } {
210
+ validateEd25519StealthMetaAddress(recipientMetaAddress)
211
+
212
+ // Generate ephemeral keypair
213
+ const ephemeralPrivateKey = randomBytes(32)
214
+
215
+ try {
216
+ const ephemeralPublicKey = ed25519.getPublicKey(ephemeralPrivateKey)
217
+
218
+ // Parse recipient's keys (remove 0x prefix)
219
+ const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2))
220
+ const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2))
221
+
222
+ // Get ephemeral scalar from private key and reduce mod L
223
+ const rawEphemeralScalar = getEd25519Scalar(ephemeralPrivateKey)
224
+ const ephemeralScalar = rawEphemeralScalar % ED25519_ORDER
225
+ if (ephemeralScalar === 0n) {
226
+ throw new Error('CRITICAL: Zero ephemeral scalar after reduction - investigate RNG')
227
+ }
228
+
229
+ // S = ephemeral_scalar * P_spend
230
+ const spendingPoint = ed25519.ExtendedPoint.fromHex(spendingKeyBytes)
231
+ const sharedSecretPoint = spendingPoint.multiply(ephemeralScalar)
232
+
233
+ // Hash the shared secret point
234
+ const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes())
235
+
236
+ // Derive stealth public key: P_stealth = P_view + hash(S)*G
237
+ const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER
238
+ if (hashScalar === 0n) {
239
+ throw new Error('CRITICAL: Zero hash scalar after reduction - investigate hash computation')
240
+ }
241
+
242
+ // Compute hash(S) * G
243
+ const hashTimesG = ed25519.ExtendedPoint.BASE.multiply(hashScalar)
244
+
245
+ // Add to viewing key: P_stealth = P_view + hash(S)*G
246
+ const viewingPoint = ed25519.ExtendedPoint.fromHex(viewingKeyBytes)
247
+ const stealthPoint = viewingPoint.add(hashTimesG)
248
+ const stealthAddressBytes = stealthPoint.toRawBytes()
249
+
250
+ // Compute view tag
251
+ const viewTag = sharedSecretHash[0]
252
+
253
+ return {
254
+ stealthAddress: {
255
+ address: `0x${bytesToHex(stealthAddressBytes)}` as HexString,
256
+ ephemeralPublicKey: `0x${bytesToHex(ephemeralPublicKey)}` as HexString,
257
+ viewTag,
258
+ },
259
+ sharedSecret: `0x${bytesToHex(sharedSecretHash)}` as HexString,
260
+ }
261
+ } finally {
262
+ secureWipe(ephemeralPrivateKey)
263
+ }
264
+ }
265
+
266
+ // ─── Private Key Derivation ─────────────────────────────────────────────────
267
+
268
+ /**
269
+ * Derive the private key for an ed25519 stealth address
270
+ *
271
+ * **IMPORTANT: Derived Key Format**
272
+ *
273
+ * The returned `privateKey` is a **raw scalar** in little-endian format, NOT a standard
274
+ * ed25519 seed. To compute the public key from the derived private key:
275
+ * ```typescript
276
+ * const scalar = bytesToBigIntLE(hexToBytes(privateKey.slice(2)))
277
+ * const publicKey = ed25519.ExtendedPoint.BASE.multiply(scalar)
278
+ * ```
279
+ */
280
+ export function deriveEd25519StealthPrivateKey(
281
+ stealthAddress: StealthAddress,
282
+ spendingPrivateKey: HexString,
283
+ viewingPrivateKey: HexString,
284
+ ): StealthAddressRecovery {
285
+ validateEd25519StealthAddress(stealthAddress)
286
+
287
+ if (!isValidPrivateKey(spendingPrivateKey)) {
288
+ throw new ValidationError(
289
+ 'must be a valid 32-byte hex string',
290
+ 'spendingPrivateKey'
291
+ )
292
+ }
293
+
294
+ if (!isValidPrivateKey(viewingPrivateKey)) {
295
+ throw new ValidationError(
296
+ 'must be a valid 32-byte hex string',
297
+ 'viewingPrivateKey'
298
+ )
299
+ }
300
+
301
+ const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
302
+ const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
303
+ const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
304
+
305
+ try {
306
+ // Get spending scalar and reduce mod L
307
+ const rawSpendingScalar = getEd25519Scalar(spendingPrivBytes)
308
+ const spendingScalar = rawSpendingScalar % ED25519_ORDER
309
+ if (spendingScalar === 0n) {
310
+ throw new Error('CRITICAL: Zero spending scalar after reduction - investigate key derivation')
311
+ }
312
+
313
+ // Compute shared secret: S = spending_scalar * R
314
+ const ephemeralPoint = ed25519.ExtendedPoint.fromHex(ephemeralPubBytes)
315
+ const sharedSecretPoint = ephemeralPoint.multiply(spendingScalar)
316
+
317
+ // Hash the shared secret
318
+ const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes())
319
+
320
+ // Get viewing scalar and reduce mod L
321
+ const rawViewingScalar = getEd25519Scalar(viewingPrivBytes)
322
+ const viewingScalar = rawViewingScalar % ED25519_ORDER
323
+ if (viewingScalar === 0n) {
324
+ throw new Error('CRITICAL: Zero viewing scalar after reduction - investigate key derivation')
325
+ }
326
+
327
+ // Derive stealth private key: s_stealth = s_view + hash(S) mod L
328
+ const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER
329
+ if (hashScalar === 0n) {
330
+ throw new Error('CRITICAL: Zero hash scalar after reduction - investigate hash computation')
331
+ }
332
+ const stealthPrivateScalar = (viewingScalar + hashScalar) % ED25519_ORDER
333
+ if (stealthPrivateScalar === 0n) {
334
+ throw new Error('CRITICAL: Zero stealth scalar after reduction - investigate key derivation')
335
+ }
336
+
337
+ // Convert to bytes (little-endian for ed25519)
338
+ const stealthPrivateKey = bigIntToBytesLE(stealthPrivateScalar, 32)
339
+
340
+ const result = {
341
+ stealthAddress: stealthAddress.address,
342
+ ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
343
+ privateKey: `0x${bytesToHex(stealthPrivateKey)}` as HexString,
344
+ }
345
+
346
+ secureWipe(stealthPrivateKey)
347
+
348
+ return result
349
+ } finally {
350
+ secureWipeAll(spendingPrivBytes, viewingPrivBytes)
351
+ }
352
+ }
353
+
354
+ // ─── Address Checking ───────────────────────────────────────────────────────
355
+
356
+ /**
357
+ * Check if an ed25519 stealth address was intended for this recipient
358
+ */
359
+ export function checkEd25519StealthAddress(
360
+ stealthAddress: StealthAddress,
361
+ spendingPrivateKey: HexString,
362
+ viewingPrivateKey: HexString,
363
+ ): boolean {
364
+ validateEd25519StealthAddress(stealthAddress)
365
+
366
+ if (!isValidPrivateKey(spendingPrivateKey)) {
367
+ throw new ValidationError(
368
+ 'must be a valid 32-byte hex string',
369
+ 'spendingPrivateKey'
370
+ )
371
+ }
372
+
373
+ if (!isValidPrivateKey(viewingPrivateKey)) {
374
+ throw new ValidationError(
375
+ 'must be a valid 32-byte hex string',
376
+ 'viewingPrivateKey'
377
+ )
378
+ }
379
+
380
+ const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
381
+ const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
382
+ const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
383
+
384
+ try {
385
+ // Get spending scalar and reduce mod L
386
+ const rawSpendingScalar = getEd25519Scalar(spendingPrivBytes)
387
+ const spendingScalar = rawSpendingScalar % ED25519_ORDER
388
+ if (spendingScalar === 0n) {
389
+ throw new Error('CRITICAL: Zero spending scalar after reduction - investigate key derivation')
390
+ }
391
+
392
+ // Compute shared secret: S = spending_scalar * R
393
+ const ephemeralPoint = ed25519.ExtendedPoint.fromHex(ephemeralPubBytes)
394
+ const sharedSecretPoint = ephemeralPoint.multiply(spendingScalar)
395
+
396
+ // Hash the shared secret
397
+ const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes())
398
+
399
+ // View tag check
400
+ if (sharedSecretHash[0] !== stealthAddress.viewTag) {
401
+ return false
402
+ }
403
+
404
+ // Full check
405
+ const rawViewingScalar = getEd25519Scalar(viewingPrivBytes)
406
+ const viewingScalar = rawViewingScalar % ED25519_ORDER
407
+ if (viewingScalar === 0n) {
408
+ throw new Error('CRITICAL: Zero viewing scalar after reduction - investigate key derivation')
409
+ }
410
+
411
+ const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER
412
+ if (hashScalar === 0n) {
413
+ throw new Error('CRITICAL: Zero hash scalar after reduction - investigate hash computation')
414
+ }
415
+ const stealthPrivateScalar = (viewingScalar + hashScalar) % ED25519_ORDER
416
+ if (stealthPrivateScalar === 0n) {
417
+ throw new Error('CRITICAL: Zero stealth scalar after reduction - investigate key derivation')
418
+ }
419
+
420
+ // Compute expected public key from derived scalar
421
+ const expectedPubKey = ed25519.ExtendedPoint.BASE.multiply(stealthPrivateScalar)
422
+ const expectedPubKeyBytes = expectedPubKey.toRawBytes()
423
+
424
+ // Compare with provided stealth address
425
+ const providedAddress = hexToBytes(stealthAddress.address.slice(2))
426
+
427
+ return bytesToHex(expectedPubKeyBytes) === bytesToHex(providedAddress)
428
+ } finally {
429
+ secureWipeAll(spendingPrivBytes, viewingPrivBytes)
430
+ }
431
+ }