@sip-protocol/sdk 0.9.0 → 0.10.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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{TransportWebUSB-YQMAGJAJ.mjs → TransportWebUSB-2KITI5HD.mjs} +24 -12
  3. package/dist/browser.d.mts +4 -4
  4. package/dist/browser.d.ts +4 -4
  5. package/dist/browser.js +1346 -838
  6. package/dist/browser.mjs +13 -3
  7. package/dist/{chunk-64AYA5F5.mjs → chunk-G3TBBG2K.mjs} +221 -146
  8. package/dist/{chunk-4GRJ5MAW.mjs → chunk-KXETSSKP.mjs} +4 -0
  9. package/dist/{chunk-6EU6WQFK.mjs → chunk-PT2DNA7E.mjs} +257 -235
  10. package/dist/{constants-LHAAUC2T.mjs → constants-DCJYTIU3.mjs} +5 -1
  11. package/dist/{dist-2OGQ7FED.mjs → dist-PYEXZNFD.mjs} +609 -221
  12. package/dist/{index-DeE1ZzA4.d.mts → index-B1d8pihL.d.mts} +117 -33
  13. package/dist/{index-DXh2IGkz.d.ts → index-UQhQJZbM.d.ts} +117 -33
  14. package/dist/index.d.mts +3 -3
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +1339 -831
  17. package/dist/index.mjs +13 -3
  18. package/dist/{interface-Bf7w1PLW.d.mts → interface-CQi0-WfS.d.mts} +2 -2
  19. package/dist/{interface-Bf7w1PLW.d.ts → interface-CQi0-WfS.d.ts} +2 -2
  20. package/dist/{noir-kzbLVTei.d.mts → noir-CwPIyBLj.d.mts} +1 -1
  21. package/dist/{noir-kzbLVTei.d.ts → noir-CwPIyBLj.d.ts} +1 -1
  22. package/dist/proofs/halo2.d.mts +1 -1
  23. package/dist/proofs/halo2.d.ts +1 -1
  24. package/dist/proofs/kimchi.d.mts +1 -1
  25. package/dist/proofs/kimchi.d.ts +1 -1
  26. package/dist/proofs/noir.d.mts +1 -1
  27. package/dist/proofs/noir.d.ts +1 -1
  28. package/dist/{solana-U3MEGU7W.mjs → solana-ZWNIQTSU.mjs} +6 -6
  29. package/package.json +32 -32
  30. package/src/adapters/gelato-relay.ts +386 -0
  31. package/src/adapters/index.ts +28 -0
  32. package/src/adapters/oneinch.ts +126 -0
  33. package/src/chains/ethereum/privacy-adapter.ts +8 -5
  34. package/src/chains/ethereum/stealth.ts +17 -14
  35. package/src/chains/near/privacy-adapter.ts +8 -5
  36. package/src/chains/near/resolver.ts +22 -8
  37. package/src/chains/near/stealth.ts +9 -9
  38. package/src/chains/solana/constants.ts +13 -1
  39. package/src/chains/solana/ephemeral-keys.ts +3 -257
  40. package/src/chains/solana/index.ts +2 -3
  41. package/src/chains/solana/providers/helius-enhanced.ts +6 -6
  42. package/src/chains/solana/providers/webhook.ts +2 -2
  43. package/src/chains/solana/scan.ts +9 -8
  44. package/src/chains/solana/stealth-scanner.ts +3 -3
  45. package/src/chains/solana/types.ts +18 -4
  46. package/src/cosmos/ibc-stealth.ts +6 -6
  47. package/src/index.ts +6 -0
  48. package/src/move/aptos.ts +15 -9
  49. package/src/move/sui.ts +15 -9
  50. package/src/nft/private-nft.ts +10 -6
  51. package/src/privacy-backends/shadowwire.ts +13 -0
  52. package/src/stealth/ed25519.ts +173 -12
  53. package/src/stealth/index.ts +47 -4
  54. package/src/stealth/secp256k1.ts +144 -7
  55. package/src/stealth.ts +7 -0
  56. package/src/wallet/ethereum/privacy-adapter.ts +1 -1
  57. package/src/wallet/hardware/ledger-privacy.ts +2 -2
  58. package/src/wallet/near/adapter.ts +2 -2
  59. package/src/wallet/near/meteor-wallet.ts +2 -2
  60. package/src/wallet/near/my-near-wallet.ts +2 -2
  61. package/src/wallet/near/wallet-selector.ts +2 -2
  62. package/src/wallet/solana/privacy-adapter.ts +9 -9
  63. package/dist/chunk-5EKF243P.mjs +0 -33809
  64. package/dist/chunk-YWGJ77A2.mjs +0 -33806
@@ -163,21 +163,22 @@ export function generateSecp256k1StealthAddress(
163
163
  const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2))
164
164
  const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2))
165
165
 
166
- // Compute shared secret: S = r * P (ephemeral private * spending public)
166
+ // Compute shared secret: S = r * K_view (ephemeral private * viewing public)
167
+ // Canonical EIP-5564: ECDH is on the VIEWING key.
167
168
  const sharedSecretPoint = secp256k1.getSharedSecret(
168
169
  ephemeralPrivateKey,
169
- spendingKeyBytes,
170
+ viewingKeyBytes,
170
171
  )
171
172
 
172
173
  // Hash the shared secret for use as a scalar
173
174
  const sharedSecretHash = sha256(sharedSecretPoint)
174
175
 
175
- // Compute stealth address: A = Q + hash(S)*G
176
+ // Compute stealth address: A = K_spend + hash(S)*G
176
177
  const hashTimesG = secp256k1.getPublicKey(sharedSecretHash, true)
177
178
 
178
- const viewingKeyPoint = secp256k1.ProjectivePoint.fromHex(viewingKeyBytes)
179
+ const spendingKeyPoint = secp256k1.ProjectivePoint.fromHex(spendingKeyBytes)
179
180
  const hashTimesGPoint = secp256k1.ProjectivePoint.fromHex(hashTimesG)
180
- const stealthPoint = viewingKeyPoint.add(hashTimesGPoint)
181
+ const stealthPoint = spendingKeyPoint.add(hashTimesGPoint)
181
182
  const stealthAddressBytes = stealthPoint.toRawBytes(true)
182
183
 
183
184
  // Compute view tag (first byte of hash for efficient scanning)
@@ -199,7 +200,9 @@ export function generateSecp256k1StealthAddress(
199
200
  // ─── Private Key Derivation ─────────────────────────────────────────────────
200
201
 
201
202
  /**
202
- * Derive the private key for a secp256k1 stealth address
203
+ * Derive the private key for a secp256k1 stealth address (canonical EIP-5564)
204
+ *
205
+ * Requires BOTH the spending and viewing private keys (spending authority).
203
206
  */
204
207
  export function deriveSecp256k1StealthPrivateKey(
205
208
  stealthAddress: StealthAddress,
@@ -226,6 +229,71 @@ export function deriveSecp256k1StealthPrivateKey(
226
229
  const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
227
230
  const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
228
231
 
232
+ try {
233
+ // Compute shared secret: S = k_view * R (viewing private * ephemeral public)
234
+ const sharedSecretPoint = secp256k1.getSharedSecret(
235
+ viewingPrivBytes,
236
+ ephemeralPubBytes,
237
+ )
238
+
239
+ // Hash the shared secret
240
+ const sharedSecretHash = sha256(sharedSecretPoint)
241
+
242
+ // Derive stealth private key: k_spend + hash(S) mod n (canonical)
243
+ const spendingScalar = bytesToBigInt(spendingPrivBytes)
244
+ const hashScalar = bytesToBigInt(sharedSecretHash)
245
+ const stealthPrivateScalar = (spendingScalar + hashScalar) % secp256k1.CURVE.n
246
+
247
+ // Convert back to bytes
248
+ const stealthPrivateKey = bigIntToBytes(stealthPrivateScalar, 32)
249
+
250
+ const result = {
251
+ stealthAddress: stealthAddress.address,
252
+ ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
253
+ privateKey: `0x${bytesToHex(stealthPrivateKey)}` as HexString,
254
+ }
255
+
256
+ secureWipe(stealthPrivateKey)
257
+
258
+ return result
259
+ } finally {
260
+ secureWipeAll(spendingPrivBytes, viewingPrivBytes)
261
+ }
262
+ }
263
+
264
+ /**
265
+ * @deprecated Legacy SIP:1 swapped-scheme derivation — claim-side back-compat ONLY.
266
+ *
267
+ * Recovers funds sent to secp256k1 stealth addresses generated before the
268
+ * canonical EIP-5564 flip (legacy scheme: `S = k_spend * R`, `p = k_view + H(S)`).
269
+ * Used only when claiming a `SIP:1` announcement. New (SIP:2) sends use
270
+ * {@link deriveSecp256k1StealthPrivateKey}.
271
+ */
272
+ export function deriveSecp256k1StealthPrivateKeyV1(
273
+ stealthAddress: StealthAddress,
274
+ spendingPrivateKey: HexString,
275
+ viewingPrivateKey: HexString,
276
+ ): StealthAddressRecovery {
277
+ validateSecp256k1StealthAddress(stealthAddress)
278
+
279
+ if (!isValidPrivateKey(spendingPrivateKey)) {
280
+ throw new ValidationError(
281
+ 'must be a valid 32-byte hex string',
282
+ 'spendingPrivateKey'
283
+ )
284
+ }
285
+
286
+ if (!isValidPrivateKey(viewingPrivateKey)) {
287
+ throw new ValidationError(
288
+ 'must be a valid 32-byte hex string',
289
+ 'viewingPrivateKey'
290
+ )
291
+ }
292
+
293
+ const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
294
+ const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
295
+ const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
296
+
229
297
  try {
230
298
  // Compute shared secret: S = p * R (spending private * ephemeral public)
231
299
  const sharedSecretPoint = secp256k1.getSharedSecret(
@@ -261,9 +329,78 @@ export function deriveSecp256k1StealthPrivateKey(
261
329
  // ─── Address Checking ───────────────────────────────────────────────────────
262
330
 
263
331
  /**
264
- * Check if a secp256k1 stealth address belongs to this recipient
332
+ * Check if a secp256k1 stealth address is ours canonical EIP-5564 view-only.
333
+ *
334
+ * Requires only the viewing PRIVATE key + the spending PUBLIC key, so a viewing
335
+ * key can be delegated for scanning without granting spend authority. Never
336
+ * touches the spending private key.
337
+ *
338
+ * @param stealthAddress - Stealth address to check
339
+ * @param viewingPrivateKey - Recipient's viewing private key
340
+ * @param spendingPublicKey - Recipient's compressed spending PUBLIC key (meta-address spendingKey)
341
+ * @returns true if this address belongs to the recipient
265
342
  */
266
343
  export function checkSecp256k1StealthAddress(
344
+ stealthAddress: StealthAddress,
345
+ viewingPrivateKey: HexString,
346
+ spendingPublicKey: HexString,
347
+ ): boolean {
348
+ validateSecp256k1StealthAddress(stealthAddress)
349
+
350
+ if (!isValidPrivateKey(viewingPrivateKey)) {
351
+ throw new ValidationError(
352
+ 'must be a valid 32-byte hex string',
353
+ 'viewingPrivateKey'
354
+ )
355
+ }
356
+
357
+ if (!isValidCompressedPublicKey(spendingPublicKey)) {
358
+ throw new ValidationError(
359
+ 'must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
360
+ 'spendingPublicKey'
361
+ )
362
+ }
363
+
364
+ const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
365
+ const spendingPubBytes = hexToBytes(spendingPublicKey.slice(2))
366
+ const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
367
+
368
+ try {
369
+ // Compute shared secret: S = k_view * R (canonical: ECDH on the viewing key)
370
+ const sharedSecretPoint = secp256k1.getSharedSecret(
371
+ viewingPrivBytes,
372
+ ephemeralPubBytes,
373
+ )
374
+ const sharedSecretHash = sha256(sharedSecretPoint)
375
+
376
+ // View tag check (fast reject)
377
+ if (sharedSecretHash[0] !== stealthAddress.viewTag) {
378
+ return false
379
+ }
380
+
381
+ // Expected address: A = K_spend + hash(S)*G (no spending private key needed)
382
+ const hashTimesG = secp256k1.getPublicKey(sharedSecretHash, true)
383
+ const expectedPoint = secp256k1.ProjectivePoint.fromHex(spendingPubBytes).add(
384
+ secp256k1.ProjectivePoint.fromHex(hashTimesG),
385
+ )
386
+
387
+ // Compare with provided stealth address
388
+ const providedAddress = hexToBytes(stealthAddress.address.slice(2))
389
+
390
+ return bytesToHex(expectedPoint.toRawBytes(true)) === bytesToHex(providedAddress)
391
+ } finally {
392
+ secureWipe(viewingPrivBytes)
393
+ }
394
+ }
395
+
396
+ /**
397
+ * @deprecated Legacy SIP:1 full-wallet check — requires BOTH private keys.
398
+ *
399
+ * For detecting/claiming pre-flip (SIP:1) announcements only (legacy swapped
400
+ * scheme: `S = k_spend * R`, address built on the viewing key). New code should
401
+ * use the view-only {@link checkSecp256k1StealthAddress}.
402
+ */
403
+ export function checkSecp256k1StealthAddressV1(
267
404
  stealthAddress: StealthAddress,
268
405
  spendingPrivateKey: HexString,
269
406
  viewingPrivateKey: HexString,
package/src/stealth.ts CHANGED
@@ -17,6 +17,7 @@ export {
17
17
  generateStealthMetaAddress,
18
18
  generateStealthAddress,
19
19
  deriveStealthPrivateKey,
20
+ deriveStealthPrivateKeyV1,
20
21
  checkStealthAddress,
21
22
 
22
23
  // Chain detection
@@ -27,11 +28,17 @@ export {
27
28
  generateEd25519StealthMetaAddress,
28
29
  generateEd25519StealthAddress,
29
30
  deriveEd25519StealthPrivateKey,
31
+ deriveEd25519StealthPrivateKeyV1,
30
32
  checkEd25519StealthAddress,
33
+ checkEd25519StealthAddressV1,
31
34
 
32
35
  // secp256k1 (Ethereum, Polygon, etc.)
33
36
  publicKeyToEthAddress,
34
37
 
38
+ // Legacy SIP:1 back-compat (claim/scan of pre-flip announcements)
39
+ deriveSecp256k1StealthPrivateKeyV1,
40
+ checkSecp256k1StealthAddressV1,
41
+
35
42
  // Meta-address encoding
36
43
  encodeStealthMetaAddress,
37
44
  decodeStealthMetaAddress,
@@ -337,8 +337,8 @@ export class PrivacyEthereumWalletAdapter extends EthereumWalletAdapter {
337
337
  try {
338
338
  const isOwned = checkEthereumStealthAddress(
339
339
  announcement,
340
- this.stealthKeys.metaAddress.spendingKey,
341
340
  this.stealthKeys.viewingPrivateKey,
341
+ this.stealthKeys.metaAddress.spendingKey,
342
342
  )
343
343
 
344
344
  let ethAddress: HexString
@@ -247,8 +247,8 @@ export class LedgerPrivacyAdapter extends LedgerWalletAdapter {
247
247
  // Check if this payment belongs to us using viewing key
248
248
  const isOurs = checkEthereumStealthAddress(
249
249
  announcement,
250
- this.stealthKeys!.spendingPrivateKey,
251
- this.stealthKeys!.viewingPrivateKey
250
+ this.stealthKeys!.viewingPrivateKey,
251
+ this.stealthKeys!.metaAddress.spendingKey
252
252
  )
253
253
 
254
254
  if (isOurs) {
@@ -441,8 +441,8 @@ export class NEARWalletAdapter extends BaseWalletAdapter {
441
441
 
442
442
  return checkNEARStealthAddress(
443
443
  stealthAddress,
444
- keys.spendingPublicKey,
445
- keys.viewingPrivateKey
444
+ keys.viewingPrivateKey,
445
+ keys.spendingPublicKey
446
446
  )
447
447
  }
448
448
 
@@ -710,8 +710,8 @@ export class MeteorWalletPrivacy {
710
710
 
711
711
  return checkNEARStealthAddress(
712
712
  stealthAddress,
713
- this.privacyKeys.spendingPrivateKey,
714
- this.privacyKeys.viewingPrivateKey
713
+ this.privacyKeys.viewingPrivateKey,
714
+ this.privacyKeys.spendingPublicKey
715
715
  )
716
716
  }
717
717
 
@@ -448,8 +448,8 @@ export class MyNearWalletPrivacy {
448
448
 
449
449
  return checkNEARStealthAddress(
450
450
  stealthAddress,
451
- this.privacyKeys.spendingPrivateKey,
452
- this.privacyKeys.viewingPrivateKey
451
+ this.privacyKeys.viewingPrivateKey,
452
+ this.privacyKeys.spendingPublicKey
453
453
  )
454
454
  }
455
455
 
@@ -471,8 +471,8 @@ export class PrivacyWalletSelector {
471
471
 
472
472
  return checkNEARStealthAddress(
473
473
  stealthAddress,
474
- keys.spendingPublicKey,
475
- keys.viewingPrivateKey
474
+ keys.viewingPrivateKey,
475
+ keys.spendingPublicKey
476
476
  )
477
477
  }
478
478
 
@@ -277,8 +277,8 @@ export class PrivacySolanaWalletAdapter extends SolanaWalletAdapter {
277
277
  try {
278
278
  const isOwned = checkEd25519StealthAddress(
279
279
  announcement,
280
- this.stealthKeys.metaAddress.spendingKey,
281
280
  this.stealthKeys.viewingPrivateKey,
281
+ this.stealthKeys.metaAddress.spendingKey,
282
282
  )
283
283
 
284
284
  results.push({
@@ -358,23 +358,23 @@ export class PrivacySolanaWalletAdapter extends SolanaWalletAdapter {
358
358
  // Compute stealth address from ephemeral key
359
359
  const ephemeralPubBytes = hexToBytes(ephemeralPublicKey.slice(2))
360
360
 
361
- // Compute shared secret: S = spending_scalar * R
362
- const spendingScalar = getEd25519ScalarFromPrivate(
363
- hexToBytes(this.stealthKeys.spendingPrivateKey.slice(2))
361
+ // Compute shared secret: S = viewing_scalar * R (canonical EIP-5564)
362
+ const viewingScalar = getEd25519ScalarFromPrivate(
363
+ hexToBytes(this.stealthKeys.viewingPrivateKey.slice(2))
364
364
  )
365
365
 
366
366
  const ephemeralPoint = ed25519.ExtendedPoint.fromHex(ephemeralPubBytes)
367
- const sharedSecretPoint = ephemeralPoint.multiply(spendingScalar)
367
+ const sharedSecretPoint = ephemeralPoint.multiply(viewingScalar)
368
368
  const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes())
369
369
 
370
- // Derive stealth public key: P_stealth = P_view + hash(S)*G
370
+ // Derive stealth public key: P_stealth = P_spend + hash(S)*G
371
371
  const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER
372
372
  const hashTimesG = ed25519.ExtendedPoint.BASE.multiply(hashScalar)
373
373
 
374
- const viewingPoint = ed25519.ExtendedPoint.fromHex(
375
- hexToBytes(this.stealthKeys.metaAddress.viewingKey.slice(2))
374
+ const spendingPoint = ed25519.ExtendedPoint.fromHex(
375
+ hexToBytes(this.stealthKeys.metaAddress.spendingKey.slice(2))
376
376
  )
377
- const stealthPoint = viewingPoint.add(hashTimesG)
377
+ const stealthPoint = spendingPoint.add(hashTimesG)
378
378
  const stealthAddressHex = `0x${bytesToHex(stealthPoint.toRawBytes())}` as HexString
379
379
 
380
380
  // Create StealthAddress for derivation