@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.
- package/LICENSE +21 -0
- package/dist/{TransportWebUSB-YQMAGJAJ.mjs → TransportWebUSB-2KITI5HD.mjs} +24 -12
- package/dist/browser.d.mts +4 -4
- package/dist/browser.d.ts +4 -4
- package/dist/browser.js +1346 -838
- package/dist/browser.mjs +13 -3
- package/dist/{chunk-64AYA5F5.mjs → chunk-G3TBBG2K.mjs} +221 -146
- package/dist/{chunk-4GRJ5MAW.mjs → chunk-KXETSSKP.mjs} +4 -0
- package/dist/{chunk-6EU6WQFK.mjs → chunk-PT2DNA7E.mjs} +257 -235
- package/dist/{constants-LHAAUC2T.mjs → constants-DCJYTIU3.mjs} +5 -1
- package/dist/{dist-2OGQ7FED.mjs → dist-PYEXZNFD.mjs} +609 -221
- package/dist/{index-DeE1ZzA4.d.mts → index-B1d8pihL.d.mts} +117 -33
- package/dist/{index-DXh2IGkz.d.ts → index-UQhQJZbM.d.ts} +117 -33
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1339 -831
- package/dist/index.mjs +13 -3
- package/dist/{interface-Bf7w1PLW.d.mts → interface-CQi0-WfS.d.mts} +2 -2
- package/dist/{interface-Bf7w1PLW.d.ts → interface-CQi0-WfS.d.ts} +2 -2
- package/dist/{noir-kzbLVTei.d.mts → noir-CwPIyBLj.d.mts} +1 -1
- package/dist/{noir-kzbLVTei.d.ts → noir-CwPIyBLj.d.ts} +1 -1
- package/dist/proofs/halo2.d.mts +1 -1
- package/dist/proofs/halo2.d.ts +1 -1
- package/dist/proofs/kimchi.d.mts +1 -1
- package/dist/proofs/kimchi.d.ts +1 -1
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/{solana-U3MEGU7W.mjs → solana-ZWNIQTSU.mjs} +6 -6
- package/package.json +32 -32
- package/src/adapters/gelato-relay.ts +386 -0
- package/src/adapters/index.ts +28 -0
- package/src/adapters/oneinch.ts +126 -0
- package/src/chains/ethereum/privacy-adapter.ts +8 -5
- package/src/chains/ethereum/stealth.ts +17 -14
- package/src/chains/near/privacy-adapter.ts +8 -5
- package/src/chains/near/resolver.ts +22 -8
- package/src/chains/near/stealth.ts +9 -9
- package/src/chains/solana/constants.ts +13 -1
- package/src/chains/solana/ephemeral-keys.ts +3 -257
- package/src/chains/solana/index.ts +2 -3
- package/src/chains/solana/providers/helius-enhanced.ts +6 -6
- package/src/chains/solana/providers/webhook.ts +2 -2
- package/src/chains/solana/scan.ts +9 -8
- package/src/chains/solana/stealth-scanner.ts +3 -3
- package/src/chains/solana/types.ts +18 -4
- package/src/cosmos/ibc-stealth.ts +6 -6
- package/src/index.ts +6 -0
- package/src/move/aptos.ts +15 -9
- package/src/move/sui.ts +15 -9
- package/src/nft/private-nft.ts +10 -6
- package/src/privacy-backends/shadowwire.ts +13 -0
- package/src/stealth/ed25519.ts +173 -12
- package/src/stealth/index.ts +47 -4
- package/src/stealth/secp256k1.ts +144 -7
- package/src/stealth.ts +7 -0
- package/src/wallet/ethereum/privacy-adapter.ts +1 -1
- package/src/wallet/hardware/ledger-privacy.ts +2 -2
- package/src/wallet/near/adapter.ts +2 -2
- package/src/wallet/near/meteor-wallet.ts +2 -2
- package/src/wallet/near/my-near-wallet.ts +2 -2
- package/src/wallet/near/wallet-selector.ts +2 -2
- package/src/wallet/solana/privacy-adapter.ts +9 -9
- package/dist/chunk-5EKF243P.mjs +0 -33809
- package/dist/chunk-YWGJ77A2.mjs +0 -33806
package/src/stealth/secp256k1.ts
CHANGED
|
@@ -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 *
|
|
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
|
-
|
|
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 =
|
|
176
|
+
// Compute stealth address: A = K_spend + hash(S)*G
|
|
176
177
|
const hashTimesG = secp256k1.getPublicKey(sharedSecretHash, true)
|
|
177
178
|
|
|
178
|
-
const
|
|
179
|
+
const spendingKeyPoint = secp256k1.ProjectivePoint.fromHex(spendingKeyBytes)
|
|
179
180
|
const hashTimesGPoint = secp256k1.ProjectivePoint.fromHex(hashTimesG)
|
|
180
|
-
const stealthPoint =
|
|
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
|
|
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!.
|
|
251
|
-
this.stealthKeys!.
|
|
250
|
+
this.stealthKeys!.viewingPrivateKey,
|
|
251
|
+
this.stealthKeys!.metaAddress.spendingKey
|
|
252
252
|
)
|
|
253
253
|
|
|
254
254
|
if (isOurs) {
|
|
@@ -710,8 +710,8 @@ export class MeteorWalletPrivacy {
|
|
|
710
710
|
|
|
711
711
|
return checkNEARStealthAddress(
|
|
712
712
|
stealthAddress,
|
|
713
|
-
this.privacyKeys.
|
|
714
|
-
this.privacyKeys.
|
|
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.
|
|
452
|
-
this.privacyKeys.
|
|
451
|
+
this.privacyKeys.viewingPrivateKey,
|
|
452
|
+
this.privacyKeys.spendingPublicKey
|
|
453
453
|
)
|
|
454
454
|
}
|
|
455
455
|
|
|
@@ -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 =
|
|
362
|
-
const
|
|
363
|
-
hexToBytes(this.stealthKeys.
|
|
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(
|
|
367
|
+
const sharedSecretPoint = ephemeralPoint.multiply(viewingScalar)
|
|
368
368
|
const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes())
|
|
369
369
|
|
|
370
|
-
// Derive stealth public key: P_stealth =
|
|
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
|
|
375
|
-
hexToBytes(this.stealthKeys.metaAddress.
|
|
374
|
+
const spendingPoint = ed25519.ExtendedPoint.fromHex(
|
|
375
|
+
hexToBytes(this.stealthKeys.metaAddress.spendingKey.slice(2))
|
|
376
376
|
)
|
|
377
|
-
const stealthPoint =
|
|
377
|
+
const stealthPoint = spendingPoint.add(hashTimesG)
|
|
378
378
|
const stealthAddressHex = `0x${bytesToHex(stealthPoint.toRawBytes())}` as HexString
|
|
379
379
|
|
|
380
380
|
// Create StealthAddress for derivation
|