@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,368 @@
1
+ /**
2
+ * secp256k1 Stealth Address Implementation
3
+ *
4
+ * Implements EIP-5564 style stealth addresses using secp256k1.
5
+ * Used for Ethereum, Polygon, Arbitrum, Optimism, Base, Bitcoin, Zcash.
6
+ */
7
+
8
+ import { secp256k1 } from '@noble/curves/secp256k1'
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
+ isValidCompressedPublicKey,
21
+ isValidPrivateKey,
22
+ } from '../validation'
23
+ import { secureWipe, secureWipeAll } from '../secure-memory'
24
+ import {
25
+ bytesToBigInt,
26
+ bigIntToBytes,
27
+ sha256,
28
+ bytesToHex,
29
+ hexToBytes,
30
+ toChecksumAddress,
31
+ keccak_256,
32
+ } from './utils'
33
+ // Note: isEd25519Chain is imported in the main index.ts for dispatch logic
34
+
35
+ // ─── Meta-Address Generation ────────────────────────────────────────────────
36
+
37
+ /**
38
+ * Generate a new secp256k1 stealth meta-address keypair
39
+ *
40
+ * @internal Use generateStealthMetaAddress() which dispatches to this
41
+ */
42
+ export function generateSecp256k1StealthMetaAddress(
43
+ chain: ChainId,
44
+ label?: string,
45
+ ): {
46
+ metaAddress: StealthMetaAddress
47
+ spendingPrivateKey: HexString
48
+ viewingPrivateKey: HexString
49
+ } {
50
+ const spendingPrivateKey = randomBytes(32)
51
+ const viewingPrivateKey = randomBytes(32)
52
+
53
+ try {
54
+ // Derive public keys
55
+ const spendingKey = secp256k1.getPublicKey(spendingPrivateKey, true)
56
+ const viewingKey = secp256k1.getPublicKey(viewingPrivateKey, true)
57
+
58
+ return {
59
+ metaAddress: {
60
+ spendingKey: `0x${bytesToHex(spendingKey)}` as HexString,
61
+ viewingKey: `0x${bytesToHex(viewingKey)}` as HexString,
62
+ chain,
63
+ label,
64
+ },
65
+ spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}` as HexString,
66
+ viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}` as HexString,
67
+ }
68
+ } finally {
69
+ secureWipeAll(spendingPrivateKey, viewingPrivateKey)
70
+ }
71
+ }
72
+
73
+ // ─── Stealth Address Generation ─────────────────────────────────────────────
74
+
75
+ /**
76
+ * Validate a secp256k1 StealthMetaAddress
77
+ */
78
+ export function validateSecp256k1StealthMetaAddress(
79
+ metaAddress: StealthMetaAddress,
80
+ field: string = 'recipientMetaAddress'
81
+ ): void {
82
+ if (!metaAddress || typeof metaAddress !== 'object') {
83
+ throw new ValidationError('must be an object', field)
84
+ }
85
+
86
+ if (!isValidChainId(metaAddress.chain)) {
87
+ throw new ValidationError(
88
+ `invalid chain '${metaAddress.chain}'`,
89
+ `${field}.chain`
90
+ )
91
+ }
92
+
93
+ if (!isValidCompressedPublicKey(metaAddress.spendingKey)) {
94
+ throw new ValidationError(
95
+ 'spendingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
96
+ `${field}.spendingKey`
97
+ )
98
+ }
99
+
100
+ if (!isValidCompressedPublicKey(metaAddress.viewingKey)) {
101
+ throw new ValidationError(
102
+ 'viewingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
103
+ `${field}.viewingKey`
104
+ )
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Validate a secp256k1 StealthAddress
110
+ */
111
+ export function validateSecp256k1StealthAddress(
112
+ stealthAddress: StealthAddress,
113
+ field: string = 'stealthAddress'
114
+ ): void {
115
+ if (!stealthAddress || typeof stealthAddress !== 'object') {
116
+ throw new ValidationError('must be an object', field)
117
+ }
118
+
119
+ if (!isValidCompressedPublicKey(stealthAddress.address)) {
120
+ throw new ValidationError(
121
+ 'address must be a valid compressed secp256k1 public key',
122
+ `${field}.address`
123
+ )
124
+ }
125
+
126
+ if (!isValidCompressedPublicKey(stealthAddress.ephemeralPublicKey)) {
127
+ throw new ValidationError(
128
+ 'ephemeralPublicKey must be a valid compressed secp256k1 public key',
129
+ `${field}.ephemeralPublicKey`
130
+ )
131
+ }
132
+
133
+ if (typeof stealthAddress.viewTag !== 'number' ||
134
+ !Number.isInteger(stealthAddress.viewTag) ||
135
+ stealthAddress.viewTag < 0 ||
136
+ stealthAddress.viewTag > 255) {
137
+ throw new ValidationError(
138
+ 'viewTag must be an integer between 0 and 255',
139
+ `${field}.viewTag`
140
+ )
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Generate a one-time secp256k1 stealth address
146
+ *
147
+ * @internal Use generateStealthAddress() which dispatches to this
148
+ */
149
+ export function generateSecp256k1StealthAddress(
150
+ recipientMetaAddress: StealthMetaAddress,
151
+ ): {
152
+ stealthAddress: StealthAddress
153
+ sharedSecret: HexString
154
+ } {
155
+ validateSecp256k1StealthMetaAddress(recipientMetaAddress)
156
+
157
+ const ephemeralPrivateKey = randomBytes(32)
158
+
159
+ try {
160
+ const ephemeralPublicKey = secp256k1.getPublicKey(ephemeralPrivateKey, true)
161
+
162
+ // Parse recipient's keys (remove 0x prefix)
163
+ const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2))
164
+ const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2))
165
+
166
+ // Compute shared secret: S = r * P (ephemeral private * spending public)
167
+ const sharedSecretPoint = secp256k1.getSharedSecret(
168
+ ephemeralPrivateKey,
169
+ spendingKeyBytes,
170
+ )
171
+
172
+ // Hash the shared secret for use as a scalar
173
+ const sharedSecretHash = sha256(sharedSecretPoint)
174
+
175
+ // Compute stealth address: A = Q + hash(S)*G
176
+ const hashTimesG = secp256k1.getPublicKey(sharedSecretHash, true)
177
+
178
+ const viewingKeyPoint = secp256k1.ProjectivePoint.fromHex(viewingKeyBytes)
179
+ const hashTimesGPoint = secp256k1.ProjectivePoint.fromHex(hashTimesG)
180
+ const stealthPoint = viewingKeyPoint.add(hashTimesGPoint)
181
+ const stealthAddressBytes = stealthPoint.toRawBytes(true)
182
+
183
+ // Compute view tag (first byte of hash for efficient scanning)
184
+ const viewTag = sharedSecretHash[0]
185
+
186
+ return {
187
+ stealthAddress: {
188
+ address: `0x${bytesToHex(stealthAddressBytes)}` as HexString,
189
+ ephemeralPublicKey: `0x${bytesToHex(ephemeralPublicKey)}` as HexString,
190
+ viewTag,
191
+ },
192
+ sharedSecret: `0x${bytesToHex(sharedSecretHash)}` as HexString,
193
+ }
194
+ } finally {
195
+ secureWipe(ephemeralPrivateKey)
196
+ }
197
+ }
198
+
199
+ // ─── Private Key Derivation ─────────────────────────────────────────────────
200
+
201
+ /**
202
+ * Derive the private key for a secp256k1 stealth address
203
+ */
204
+ export function deriveSecp256k1StealthPrivateKey(
205
+ stealthAddress: StealthAddress,
206
+ spendingPrivateKey: HexString,
207
+ viewingPrivateKey: HexString,
208
+ ): StealthAddressRecovery {
209
+ validateSecp256k1StealthAddress(stealthAddress)
210
+
211
+ if (!isValidPrivateKey(spendingPrivateKey)) {
212
+ throw new ValidationError(
213
+ 'must be a valid 32-byte hex string',
214
+ 'spendingPrivateKey'
215
+ )
216
+ }
217
+
218
+ if (!isValidPrivateKey(viewingPrivateKey)) {
219
+ throw new ValidationError(
220
+ 'must be a valid 32-byte hex string',
221
+ 'viewingPrivateKey'
222
+ )
223
+ }
224
+
225
+ const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
226
+ const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
227
+ const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
228
+
229
+ try {
230
+ // Compute shared secret: S = p * R (spending private * ephemeral public)
231
+ const sharedSecretPoint = secp256k1.getSharedSecret(
232
+ spendingPrivBytes,
233
+ ephemeralPubBytes,
234
+ )
235
+
236
+ // Hash the shared secret
237
+ const sharedSecretHash = sha256(sharedSecretPoint)
238
+
239
+ // Derive stealth private key: q + hash(S) mod n
240
+ const viewingScalar = bytesToBigInt(viewingPrivBytes)
241
+ const hashScalar = bytesToBigInt(sharedSecretHash)
242
+ const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
243
+
244
+ // Convert back to bytes
245
+ const stealthPrivateKey = bigIntToBytes(stealthPrivateScalar, 32)
246
+
247
+ const result = {
248
+ stealthAddress: stealthAddress.address,
249
+ ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
250
+ privateKey: `0x${bytesToHex(stealthPrivateKey)}` as HexString,
251
+ }
252
+
253
+ secureWipe(stealthPrivateKey)
254
+
255
+ return result
256
+ } finally {
257
+ secureWipeAll(spendingPrivBytes, viewingPrivBytes)
258
+ }
259
+ }
260
+
261
+ // ─── Address Checking ───────────────────────────────────────────────────────
262
+
263
+ /**
264
+ * Check if a secp256k1 stealth address belongs to this recipient
265
+ */
266
+ export function checkSecp256k1StealthAddress(
267
+ stealthAddress: StealthAddress,
268
+ spendingPrivateKey: HexString,
269
+ viewingPrivateKey: HexString,
270
+ ): boolean {
271
+ validateSecp256k1StealthAddress(stealthAddress)
272
+
273
+ if (!isValidPrivateKey(spendingPrivateKey)) {
274
+ throw new ValidationError(
275
+ 'must be a valid 32-byte hex string',
276
+ 'spendingPrivateKey'
277
+ )
278
+ }
279
+
280
+ if (!isValidPrivateKey(viewingPrivateKey)) {
281
+ throw new ValidationError(
282
+ 'must be a valid 32-byte hex string',
283
+ 'viewingPrivateKey'
284
+ )
285
+ }
286
+
287
+ const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
288
+ const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
289
+ const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
290
+
291
+ try {
292
+ // Quick check: compute shared secret and verify view tag first
293
+ const sharedSecretPoint = secp256k1.getSharedSecret(
294
+ spendingPrivBytes,
295
+ ephemeralPubBytes,
296
+ )
297
+ const sharedSecretHash = sha256(sharedSecretPoint)
298
+
299
+ // View tag check (optimization - reject quickly if doesn't match)
300
+ if (sharedSecretHash[0] !== stealthAddress.viewTag) {
301
+ return false
302
+ }
303
+
304
+ // Full check: derive the expected stealth address
305
+ const viewingScalar = bytesToBigInt(viewingPrivBytes)
306
+ const hashScalar = bytesToBigInt(sharedSecretHash)
307
+ const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
308
+
309
+ // Compute expected public key from derived private key
310
+ const derivedKeyBytes = bigIntToBytes(stealthPrivateScalar, 32)
311
+ const expectedPubKey = secp256k1.getPublicKey(derivedKeyBytes, true)
312
+
313
+ // Wipe derived key immediately after use
314
+ secureWipe(derivedKeyBytes)
315
+
316
+ // Compare with provided stealth address
317
+ const providedAddress = hexToBytes(stealthAddress.address.slice(2))
318
+
319
+ return bytesToHex(expectedPubKey) === bytesToHex(providedAddress)
320
+ } finally {
321
+ secureWipeAll(spendingPrivBytes, viewingPrivBytes)
322
+ }
323
+ }
324
+
325
+ // ─── Ethereum Address Derivation ────────────────────────────────────────────
326
+
327
+ /**
328
+ * Convert a secp256k1 public key to an Ethereum address
329
+ *
330
+ * Algorithm (EIP-5564 style):
331
+ * 1. Decompress the public key to uncompressed form (65 bytes)
332
+ * 2. Remove the 0x04 prefix (take last 64 bytes)
333
+ * 3. keccak256 hash of the 64 bytes
334
+ * 4. Take the last 20 bytes as the address
335
+ */
336
+ export function publicKeyToEthAddress(publicKey: HexString): HexString {
337
+ // Remove 0x prefix if present
338
+ const keyHex = publicKey.startsWith('0x') ? publicKey.slice(2) : publicKey
339
+ const keyBytes = hexToBytes(keyHex)
340
+
341
+ let uncompressedBytes: Uint8Array
342
+
343
+ // Check if compressed (33 bytes) or uncompressed (65 bytes)
344
+ if (keyBytes.length === 33) {
345
+ // Decompress using secp256k1
346
+ const point = secp256k1.ProjectivePoint.fromHex(keyBytes)
347
+ uncompressedBytes = point.toRawBytes(false) // false = uncompressed
348
+ } else if (keyBytes.length === 65) {
349
+ uncompressedBytes = keyBytes
350
+ } else {
351
+ throw new ValidationError(
352
+ `invalid public key length: ${keyBytes.length}, expected 33 (compressed) or 65 (uncompressed)`,
353
+ 'publicKey'
354
+ )
355
+ }
356
+
357
+ // Remove the 0x04 prefix (first byte of uncompressed key)
358
+ const pubKeyWithoutPrefix = uncompressedBytes.slice(1)
359
+
360
+ // keccak256 hash
361
+ const hash = keccak_256(pubKeyWithoutPrefix)
362
+
363
+ // Take last 20 bytes
364
+ const addressBytes = hash.slice(-20)
365
+
366
+ // Convert to checksummed address
367
+ return toChecksumAddress(`0x${bytesToHex(addressBytes)}`)
368
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Stealth Address Utilities
3
+ *
4
+ * Shared utility functions for stealth address operations.
5
+ */
6
+
7
+ import { sha256 } from '@noble/hashes/sha256'
8
+ import { sha512 } from '@noble/hashes/sha512'
9
+ import { keccak_256 } from '@noble/hashes/sha3'
10
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
11
+ import type { HexString } from '@sip-protocol/types'
12
+ import { ValidationError } from '../errors'
13
+
14
+ // ─── Byte/BigInt Conversion ─────────────────────────────────────────────────
15
+
16
+ /**
17
+ * Convert bytes to bigint (big-endian)
18
+ */
19
+ export function bytesToBigInt(bytes: Uint8Array): bigint {
20
+ let result = 0n
21
+ for (const byte of bytes) {
22
+ result = (result << 8n) + BigInt(byte)
23
+ }
24
+ return result
25
+ }
26
+
27
+ /**
28
+ * Convert bigint to bytes (big-endian)
29
+ */
30
+ export function bigIntToBytes(value: bigint, length: number): Uint8Array {
31
+ const bytes = new Uint8Array(length)
32
+ for (let i = length - 1; i >= 0; i--) {
33
+ bytes[i] = Number(value & 0xffn)
34
+ value >>= 8n
35
+ }
36
+ return bytes
37
+ }
38
+
39
+ /**
40
+ * Convert bytes to bigint (little-endian, used by ed25519)
41
+ */
42
+ export function bytesToBigIntLE(bytes: Uint8Array): bigint {
43
+ let result = 0n
44
+ for (let i = bytes.length - 1; i >= 0; i--) {
45
+ result = (result << 8n) + BigInt(bytes[i])
46
+ }
47
+ return result
48
+ }
49
+
50
+ /**
51
+ * Convert bigint to bytes (little-endian, used by ed25519)
52
+ */
53
+ export function bigIntToBytesLE(value: bigint, length: number): Uint8Array {
54
+ const bytes = new Uint8Array(length)
55
+ for (let i = 0; i < length; i++) {
56
+ bytes[i] = Number(value & 0xffn)
57
+ value >>= 8n
58
+ }
59
+ return bytes
60
+ }
61
+
62
+ // ─── Base58 Encoding ────────────────────────────────────────────────────────
63
+
64
+ /** Base58 alphabet (Bitcoin/Solana standard) */
65
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
66
+
67
+ /**
68
+ * Encode bytes to base58 string
69
+ * Used for Solana address encoding
70
+ */
71
+ export function bytesToBase58(bytes: Uint8Array): string {
72
+ // Count leading zeros
73
+ let leadingZeros = 0
74
+ for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
75
+ leadingZeros++
76
+ }
77
+
78
+ // Convert bytes to bigint
79
+ let value = 0n
80
+ for (const byte of bytes) {
81
+ value = value * 256n + BigInt(byte)
82
+ }
83
+
84
+ // Convert to base58
85
+ let result = ''
86
+ while (value > 0n) {
87
+ const remainder = value % 58n
88
+ value = value / 58n
89
+ result = BASE58_ALPHABET[Number(remainder)] + result
90
+ }
91
+
92
+ // Add leading '1's for each leading zero byte
93
+ return '1'.repeat(leadingZeros) + result
94
+ }
95
+
96
+ /**
97
+ * Decode base58 string to bytes
98
+ * Used for Solana address validation
99
+ */
100
+ export function base58ToBytes(str: string): Uint8Array {
101
+ // Count leading '1's (they represent leading zero bytes)
102
+ let leadingOnes = 0
103
+ for (let i = 0; i < str.length && str[i] === '1'; i++) {
104
+ leadingOnes++
105
+ }
106
+
107
+ // Convert from base58 to bigint
108
+ let value = 0n
109
+ for (const char of str) {
110
+ const index = BASE58_ALPHABET.indexOf(char)
111
+ if (index === -1) {
112
+ throw new ValidationError(`Invalid base58 character: ${char}`, 'address')
113
+ }
114
+ value = value * 58n + BigInt(index)
115
+ }
116
+
117
+ // Convert bigint to bytes
118
+ const bytes: number[] = []
119
+ while (value > 0n) {
120
+ bytes.unshift(Number(value % 256n))
121
+ value = value / 256n
122
+ }
123
+
124
+ // Add leading zeros
125
+ const result = new Uint8Array(leadingOnes + bytes.length)
126
+ for (let i = 0; i < leadingOnes; i++) {
127
+ result[i] = 0
128
+ }
129
+ for (let i = 0; i < bytes.length; i++) {
130
+ result[leadingOnes + i] = bytes[i]
131
+ }
132
+
133
+ return result
134
+ }
135
+
136
+ // ─── EIP-55 Checksum Address ────────────────────────────────────────────────
137
+
138
+ /**
139
+ * Convert address to EIP-55 checksummed format
140
+ */
141
+ export function toChecksumAddress(address: string): HexString {
142
+ const addr = address.toLowerCase().replace('0x', '')
143
+ const hash = bytesToHex(keccak_256(new TextEncoder().encode(addr)))
144
+
145
+ let checksummed = '0x'
146
+ for (let i = 0; i < addr.length; i++) {
147
+ if (parseInt(hash[i], 16) >= 8) {
148
+ checksummed += addr[i].toUpperCase()
149
+ } else {
150
+ checksummed += addr[i]
151
+ }
152
+ }
153
+
154
+ return checksummed as HexString
155
+ }
156
+
157
+ // ─── ed25519 Scalar Derivation ──────────────────────────────────────────────
158
+
159
+ /**
160
+ * ed25519 curve order (L) - the order of the base point
161
+ */
162
+ export const ED25519_ORDER = 2n ** 252n + 27742317777372353535851937790883648493n
163
+
164
+ /**
165
+ * Chains that use ed25519 for stealth addresses
166
+ */
167
+ export const ED25519_CHAINS = ['solana', 'near', 'aptos', 'sui'] as const
168
+
169
+ /**
170
+ * Get the scalar from an ed25519 private key
171
+ *
172
+ * ed25519 key derivation:
173
+ * 1. Hash the 32-byte seed with SHA-512 to get 64 bytes
174
+ * 2. First 32 bytes are the scalar (after clamping)
175
+ * 3. Last 32 bytes are used for nonce generation (not needed here)
176
+ */
177
+ export function getEd25519Scalar(privateKey: Uint8Array): bigint {
178
+ // Hash the private key seed with SHA-512
179
+ const hash = sha512(privateKey)
180
+
181
+ // Take first 32 bytes and clamp as per ed25519 spec
182
+ const scalar = hash.slice(0, 32)
183
+
184
+ // Clamp: clear lowest 3 bits, clear highest bit, set second highest bit
185
+ scalar[0] &= 248
186
+ scalar[31] &= 127
187
+ scalar[31] |= 64
188
+
189
+ // Convert to bigint (little-endian for ed25519)
190
+ return bytesToBigIntLE(scalar)
191
+ }
192
+
193
+ // Re-export for convenience
194
+ export { sha256, sha512, keccak_256, bytesToHex, hexToBytes }