@sip-protocol/sdk 0.8.1 → 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 +1358 -847
- 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-YWGJ77A2.mjs → chunk-PT2DNA7E.mjs} +335 -310
- 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 +1348 -837
- 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/constants.ts +33 -1
- package/src/chains/ethereum/index.ts +2 -1
- package/src/chains/ethereum/privacy-adapter.ts +44 -26
- package/src/chains/ethereum/stealth.ts +84 -30
- package/src/chains/ethereum/types.ts +4 -0
- 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
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { StealthMetaAddress, HexString, StealthAddress } from '@sip-protocol/types'
|
|
11
|
+
import { secp256k1 } from '@noble/curves/secp256k1'
|
|
12
|
+
import { hexToBytes, bytesToHex } from '@noble/hashes/utils'
|
|
11
13
|
import {
|
|
12
14
|
generateEthereumStealthMetaAddress,
|
|
13
15
|
generateEthereumStealthAddress,
|
|
@@ -15,6 +17,8 @@ import {
|
|
|
15
17
|
encodeEthereumStealthMetaAddress,
|
|
16
18
|
deriveEthereumStealthPrivateKey,
|
|
17
19
|
checkEthereumStealthAddress,
|
|
20
|
+
checkEthereumStealthByEthAddress,
|
|
21
|
+
stealthPublicKeyToEthAddress,
|
|
18
22
|
type EthereumStealthMetaAddress,
|
|
19
23
|
type EthereumStealthAddress,
|
|
20
24
|
} from './stealth'
|
|
@@ -359,20 +363,23 @@ export class EthereumPrivacyAdapter {
|
|
|
359
363
|
/**
|
|
360
364
|
* Check if a stealth address belongs to a recipient
|
|
361
365
|
*
|
|
366
|
+
* Canonical EIP-5564 view-only check: requires only the recipient's viewing
|
|
367
|
+
* private key plus their spending PUBLIC key (no spending private key needed).
|
|
368
|
+
*
|
|
362
369
|
* @param stealthAddress - Stealth address object
|
|
363
|
-
* @param spendingPrivateKey - Spending private key (hex)
|
|
364
370
|
* @param viewingPrivateKey - Viewing private key (hex)
|
|
371
|
+
* @param spendingPublicKey - Spending public key (hex, meta-address spendingKey)
|
|
365
372
|
* @returns True if the address belongs to the recipient
|
|
366
373
|
*/
|
|
367
374
|
checkStealthAddress(
|
|
368
375
|
stealthAddress: StealthAddress,
|
|
369
|
-
|
|
370
|
-
|
|
376
|
+
viewingPrivateKey: HexString,
|
|
377
|
+
spendingPublicKey: HexString
|
|
371
378
|
): boolean {
|
|
372
379
|
return checkEthereumStealthAddress(
|
|
373
380
|
stealthAddress,
|
|
374
|
-
|
|
375
|
-
|
|
381
|
+
viewingPrivateKey,
|
|
382
|
+
spendingPublicKey
|
|
376
383
|
)
|
|
377
384
|
}
|
|
378
385
|
|
|
@@ -582,20 +589,17 @@ export class EthereumPrivacyAdapter {
|
|
|
582
589
|
|
|
583
590
|
// Check each recipient
|
|
584
591
|
for (const recipient of this.scanRecipients.values()) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
592
|
+
// Use ETH address comparison since announcements store 20-byte addresses
|
|
593
|
+
// Returns the stealth private key if match found, null otherwise
|
|
594
|
+
const stealthPrivateKey = checkEthereumStealthByEthAddress(
|
|
595
|
+
announcement.stealthAddress,
|
|
596
|
+
announcement.ephemeralPublicKey,
|
|
597
|
+
announcement.viewTag,
|
|
598
|
+
recipient.spendingPrivateKey,
|
|
599
|
+
recipient.viewingPrivateKey,
|
|
589
600
|
)
|
|
590
601
|
|
|
591
|
-
if (
|
|
592
|
-
// Derive stealth private key for claiming
|
|
593
|
-
const recovery = deriveEthereumStealthPrivateKey(
|
|
594
|
-
stealthAddress,
|
|
595
|
-
recipient.spendingPublicKey, // This should be spending PRIVATE key
|
|
596
|
-
recipient.viewingPrivateKey
|
|
597
|
-
)
|
|
598
|
-
|
|
602
|
+
if (stealthPrivateKey) {
|
|
599
603
|
results.push({
|
|
600
604
|
payment: {
|
|
601
605
|
stealthAddress,
|
|
@@ -606,7 +610,7 @@ export class EthereumPrivacyAdapter {
|
|
|
606
610
|
timestamp: announcement.timestamp,
|
|
607
611
|
},
|
|
608
612
|
recipient,
|
|
609
|
-
stealthPrivateKey
|
|
613
|
+
stealthPrivateKey,
|
|
610
614
|
})
|
|
611
615
|
break // Found owner, no need to check other recipients
|
|
612
616
|
}
|
|
@@ -641,12 +645,26 @@ export class EthereumPrivacyAdapter {
|
|
|
641
645
|
* @returns Built claim transaction
|
|
642
646
|
*/
|
|
643
647
|
buildClaimTransaction(params: EthereumClaimParams): EthereumClaimBuild {
|
|
644
|
-
//
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
648
|
+
// Use pre-derived key if available (e.g. from scanning flow), otherwise derive
|
|
649
|
+
let stealthPrivateKey: HexString
|
|
650
|
+
let stealthEthAddress: HexString
|
|
651
|
+
|
|
652
|
+
if (params.stealthPrivateKey) {
|
|
653
|
+
stealthPrivateKey = params.stealthPrivateKey
|
|
654
|
+
stealthEthAddress = stealthPublicKeyToEthAddress(
|
|
655
|
+
('0x' + bytesToHex(
|
|
656
|
+
secp256k1.getPublicKey(hexToBytes(params.stealthPrivateKey.slice(2)), true)
|
|
657
|
+
)) as HexString
|
|
658
|
+
)
|
|
659
|
+
} else {
|
|
660
|
+
const recovery = deriveEthereumStealthPrivateKey(
|
|
661
|
+
params.stealthAddress,
|
|
662
|
+
params.spendingPrivateKey,
|
|
663
|
+
params.viewingPrivateKey
|
|
664
|
+
)
|
|
665
|
+
stealthPrivateKey = recovery.privateKey
|
|
666
|
+
stealthEthAddress = recovery.ethAddress
|
|
667
|
+
}
|
|
650
668
|
|
|
651
669
|
// Build transaction
|
|
652
670
|
const amount = params.amount ?? 0n // Full balance if not specified
|
|
@@ -673,8 +691,8 @@ export class EthereumPrivacyAdapter {
|
|
|
673
691
|
}
|
|
674
692
|
|
|
675
693
|
return {
|
|
676
|
-
stealthEthAddress
|
|
677
|
-
stealthPrivateKey
|
|
694
|
+
stealthEthAddress,
|
|
695
|
+
stealthPrivateKey,
|
|
678
696
|
destinationAddress: params.destinationAddress,
|
|
679
697
|
amount,
|
|
680
698
|
tx,
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
StealthAddressRecovery,
|
|
22
22
|
HexString,
|
|
23
23
|
} from '@sip-protocol/types'
|
|
24
|
+
import { secp256k1 } from '@noble/curves/secp256k1'
|
|
24
25
|
import {
|
|
25
26
|
generateSecp256k1StealthMetaAddress,
|
|
26
27
|
generateSecp256k1StealthAddress,
|
|
@@ -28,6 +29,8 @@ import {
|
|
|
28
29
|
checkSecp256k1StealthAddress,
|
|
29
30
|
publicKeyToEthAddress,
|
|
30
31
|
} from '../../stealth/secp256k1'
|
|
32
|
+
import { sha256, hexToBytes, bytesToHex } from '../../stealth/utils'
|
|
33
|
+
import { isValidPrivateKey } from '../../validation'
|
|
31
34
|
import { ValidationError } from '../../errors'
|
|
32
35
|
import { SECP256K1_SCHEME_ID } from './constants'
|
|
33
36
|
|
|
@@ -345,9 +348,12 @@ export function deriveEthereumStealthPrivateKey(
|
|
|
345
348
|
*
|
|
346
349
|
* Used during scanning to quickly filter announcements.
|
|
347
350
|
*
|
|
351
|
+
* Canonical EIP-5564 view-only check: requires only the recipient's viewing
|
|
352
|
+
* private key plus their spending PUBLIC key (no spending private key needed).
|
|
353
|
+
*
|
|
348
354
|
* @param stealthAddress - The stealth address to check
|
|
349
|
-
* @param spendingPrivateKey - Recipient's spending private key
|
|
350
355
|
* @param viewingPrivateKey - Recipient's viewing private key
|
|
356
|
+
* @param spendingPublicKey - Recipient's spending public key (meta-address spendingKey)
|
|
351
357
|
* @returns True if the address belongs to this recipient
|
|
352
358
|
*
|
|
353
359
|
* @example
|
|
@@ -356,8 +362,8 @@ export function deriveEthereumStealthPrivateKey(
|
|
|
356
362
|
* for (const announcement of announcements) {
|
|
357
363
|
* const isMine = checkEthereumStealthAddress(
|
|
358
364
|
* announcement.stealthAddress,
|
|
359
|
-
*
|
|
360
|
-
*
|
|
365
|
+
* myViewingPrivateKey,
|
|
366
|
+
* mySpendingPublicKey
|
|
361
367
|
* )
|
|
362
368
|
* if (isMine) {
|
|
363
369
|
* console.log('Found incoming payment!')
|
|
@@ -367,43 +373,91 @@ export function deriveEthereumStealthPrivateKey(
|
|
|
367
373
|
*/
|
|
368
374
|
export function checkEthereumStealthAddress(
|
|
369
375
|
stealthAddress: StealthAddress,
|
|
370
|
-
|
|
371
|
-
|
|
376
|
+
viewingPrivateKey: HexString,
|
|
377
|
+
spendingPublicKey: HexString
|
|
372
378
|
): boolean {
|
|
373
379
|
return checkSecp256k1StealthAddress(
|
|
374
380
|
stealthAddress,
|
|
375
|
-
|
|
376
|
-
|
|
381
|
+
viewingPrivateKey,
|
|
382
|
+
spendingPublicKey
|
|
377
383
|
)
|
|
378
384
|
}
|
|
379
385
|
|
|
380
386
|
/**
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
* Before doing the full elliptic curve check, verify the view tag matches.
|
|
384
|
-
* This is ~256x faster for non-matching addresses.
|
|
387
|
+
* Check if an Ethereum stealth address matches by ETH address comparison
|
|
385
388
|
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
* @returns True if view tag matches (address *might* be ours)
|
|
389
|
+
* Used when the announcement only contains the 20-byte ETH address (not the
|
|
390
|
+
* full 33-byte compressed public key). Derives the expected stealth public key,
|
|
391
|
+
* converts it to an ETH address, and compares.
|
|
390
392
|
*
|
|
391
|
-
* @
|
|
393
|
+
* @param ethAddress - The ETH address from the announcement (20 bytes)
|
|
394
|
+
* @param ephemeralPublicKey - Ephemeral public key from the announcement
|
|
395
|
+
* @param viewTag - View tag from the announcement
|
|
396
|
+
* @param spendingPublicKey - Recipient's spending public key
|
|
397
|
+
* @param viewingPrivateKey - Recipient's viewing private key
|
|
398
|
+
* @returns True if the address belongs to this recipient
|
|
392
399
|
*/
|
|
393
|
-
export function
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
400
|
+
export function checkEthereumStealthByEthAddress(
|
|
401
|
+
ethAddress: HexString,
|
|
402
|
+
ephemeralPublicKey: HexString,
|
|
403
|
+
viewTag: number,
|
|
404
|
+
spendingPrivateKey: HexString,
|
|
405
|
+
viewingPrivateKey: HexString,
|
|
406
|
+
): HexString | null {
|
|
407
|
+
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
408
|
+
throw new ValidationError(
|
|
409
|
+
'must be a valid 32-byte hex string',
|
|
410
|
+
'spendingPrivateKey'
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
414
|
+
throw new ValidationError(
|
|
415
|
+
'must be a valid 32-byte hex string',
|
|
416
|
+
'viewingPrivateKey'
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
|
|
421
|
+
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
|
|
422
|
+
const ephemeralPubBytes = hexToBytes(ephemeralPublicKey.slice(2))
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
// Compute shared secret: S = viewingPrivateKey * ephemeralPublicKey
|
|
426
|
+
// Canonical EIP-5564: ECDH on the viewing key (mirrors generation S = r * K_view)
|
|
427
|
+
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
428
|
+
viewingPrivBytes,
|
|
429
|
+
ephemeralPubBytes,
|
|
430
|
+
)
|
|
431
|
+
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
432
|
+
|
|
433
|
+
// Quick view tag check
|
|
434
|
+
if (sharedSecretHash[0] !== viewTag) {
|
|
435
|
+
return null
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Derive stealth private key: spendingPriv + hash(S) mod n
|
|
439
|
+
// Mirrors generation: stealth = spendingPub + hash(S)*G
|
|
440
|
+
const spendingScalar = BigInt('0x' + bytesToHex(spendingPrivBytes))
|
|
441
|
+
const hashScalar = BigInt('0x' + bytesToHex(sharedSecretHash))
|
|
442
|
+
const stealthPrivScalar = (spendingScalar + hashScalar) % secp256k1.CURVE.n
|
|
443
|
+
|
|
444
|
+
// Compute expected public key from derived private key
|
|
445
|
+
const stealthPrivHex = stealthPrivScalar.toString(16).padStart(64, '0')
|
|
446
|
+
const stealthPrivKeyBytes = hexToBytes(stealthPrivHex)
|
|
447
|
+
const expectedPubKey = secp256k1.getPublicKey(stealthPrivKeyBytes, true)
|
|
448
|
+
|
|
449
|
+
// Convert to ETH address
|
|
450
|
+
const expectedPubKeyHex = ('0x' + bytesToHex(expectedPubKey)) as HexString
|
|
451
|
+
const expectedEthAddress = publicKeyToEthAddress(expectedPubKeyHex)
|
|
452
|
+
|
|
453
|
+
// Compare addresses (case-insensitive)
|
|
454
|
+
if (expectedEthAddress.toLowerCase() === ethAddress.toLowerCase()) {
|
|
455
|
+
return ('0x' + stealthPrivHex) as HexString
|
|
456
|
+
}
|
|
457
|
+
return null
|
|
458
|
+
} catch {
|
|
459
|
+
return null
|
|
460
|
+
}
|
|
407
461
|
}
|
|
408
462
|
|
|
409
463
|
// ─── Address Conversion ─────────────────────────────────────────────────────
|
|
@@ -224,6 +224,8 @@ export interface EthereumClaimParams {
|
|
|
224
224
|
spendingPrivateKey: HexString
|
|
225
225
|
/** Destination address to receive funds */
|
|
226
226
|
destinationAddress: HexString
|
|
227
|
+
/** Pre-derived stealth private key (skips derivation if provided, e.g. from scanning) */
|
|
228
|
+
stealthPrivateKey?: HexString
|
|
227
229
|
/** Network */
|
|
228
230
|
network?: EthereumNetwork
|
|
229
231
|
/** RPC URL (overrides default) */
|
|
@@ -439,6 +441,8 @@ export interface EthereumScanRecipient {
|
|
|
439
441
|
viewingPrivateKey: HexString
|
|
440
442
|
/** Spending public key */
|
|
441
443
|
spendingPublicKey: HexString
|
|
444
|
+
/** Spending private key (required for scanning and key derivation) */
|
|
445
|
+
spendingPrivateKey: HexString
|
|
442
446
|
/** Optional label */
|
|
443
447
|
label?: string
|
|
444
448
|
}
|
|
@@ -426,20 +426,23 @@ export class NEARPrivacyAdapter {
|
|
|
426
426
|
/**
|
|
427
427
|
* Check if a stealth address belongs to a recipient
|
|
428
428
|
*
|
|
429
|
+
* Canonical EIP-5564 view-only check: requires only the recipient's viewing
|
|
430
|
+
* private key plus their spending PUBLIC key (no spending private key needed).
|
|
431
|
+
*
|
|
429
432
|
* @param stealthAddress - Stealth address object
|
|
430
|
-
* @param spendingPrivateKey - Spending private key (hex)
|
|
431
433
|
* @param viewingPrivateKey - Viewing private key (hex)
|
|
434
|
+
* @param spendingPublicKey - Spending public key (hex, meta-address spendingKey)
|
|
432
435
|
* @returns True if the address belongs to the recipient
|
|
433
436
|
*/
|
|
434
437
|
checkStealthAddress(
|
|
435
438
|
stealthAddress: StealthAddress,
|
|
436
|
-
|
|
437
|
-
|
|
439
|
+
viewingPrivateKey: HexString,
|
|
440
|
+
spendingPublicKey: HexString
|
|
438
441
|
): boolean {
|
|
439
442
|
return checkNEARStealthAddress(
|
|
440
443
|
stealthAddress,
|
|
441
|
-
|
|
442
|
-
|
|
444
|
+
viewingPrivateKey,
|
|
445
|
+
spendingPublicKey
|
|
443
446
|
)
|
|
444
447
|
}
|
|
445
448
|
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* @module chains/near/resolver
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
+
import { ed25519 } from '@noble/curves/ed25519'
|
|
33
34
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
|
34
35
|
import type { HexString, StealthAddress } from '@sip-protocol/types'
|
|
35
36
|
import { ValidationError } from '../../errors'
|
|
@@ -44,6 +45,19 @@ import {
|
|
|
44
45
|
} from './constants'
|
|
45
46
|
import type { NEARViewingKey } from './viewing-key'
|
|
46
47
|
|
|
48
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Derive the ed25519 spending PUBLIC key from a spending private key.
|
|
52
|
+
*
|
|
53
|
+
* Canonical EIP-5564 view-only scanning needs the spending public key, but
|
|
54
|
+
* scan recipients in this module hold the spending private key. This converts
|
|
55
|
+
* one to the other on the fly.
|
|
56
|
+
*/
|
|
57
|
+
function nearSpendingPublicFromPrivate(spendingPrivateKey: HexString): HexString {
|
|
58
|
+
return `0x${bytesToHex(ed25519.getPublicKey(hexToBytes(spendingPrivateKey.slice(2))))}` as HexString
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
48
62
|
|
|
49
63
|
/**
|
|
@@ -652,8 +666,8 @@ export class NEARStealthScanner {
|
|
|
652
666
|
try {
|
|
653
667
|
const isMatch = checkNEARStealthAddress(
|
|
654
668
|
stealthAddressToCheck,
|
|
655
|
-
recipient.
|
|
656
|
-
recipient.
|
|
669
|
+
recipient.viewingPrivateKey,
|
|
670
|
+
nearSpendingPublicFromPrivate(recipient.spendingPrivateKey)
|
|
657
671
|
)
|
|
658
672
|
|
|
659
673
|
if (isMatch) {
|
|
@@ -764,8 +778,8 @@ export class NEARStealthScanner {
|
|
|
764
778
|
try {
|
|
765
779
|
return checkNEARStealthAddress(
|
|
766
780
|
stealthAddressToCheck,
|
|
767
|
-
|
|
768
|
-
|
|
781
|
+
viewingPrivateKey,
|
|
782
|
+
nearSpendingPublicFromPrivate(spendingPrivateKey)
|
|
769
783
|
)
|
|
770
784
|
} catch {
|
|
771
785
|
return false
|
|
@@ -814,8 +828,8 @@ export class NEARStealthScanner {
|
|
|
814
828
|
try {
|
|
815
829
|
const isMatch = checkNEARStealthAddress(
|
|
816
830
|
stealthAddressToCheck,
|
|
817
|
-
recipient.
|
|
818
|
-
recipient.
|
|
831
|
+
recipient.viewingPrivateKey,
|
|
832
|
+
nearSpendingPublicFromPrivate(recipient.spendingPrivateKey)
|
|
819
833
|
)
|
|
820
834
|
|
|
821
835
|
if (isMatch) {
|
|
@@ -954,8 +968,8 @@ export function hasNEARAnnouncementMatch(
|
|
|
954
968
|
try {
|
|
955
969
|
const isMatch = checkNEARStealthAddress(
|
|
956
970
|
stealthAddressToCheck,
|
|
957
|
-
recipient.
|
|
958
|
-
recipient.
|
|
971
|
+
recipient.viewingPrivateKey,
|
|
972
|
+
nearSpendingPublicFromPrivate(recipient.spendingPrivateKey)
|
|
959
973
|
)
|
|
960
974
|
|
|
961
975
|
if (isMatch) {
|
|
@@ -204,12 +204,12 @@ export function deriveNEARStealthPrivateKey(
|
|
|
204
204
|
/**
|
|
205
205
|
* Check if a NEAR stealth address was intended for this recipient
|
|
206
206
|
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
207
|
+
* Canonical EIP-5564 view-only check: requires only the recipient's viewing
|
|
208
|
+
* private key plus their spending PUBLIC key (no spending private key needed).
|
|
209
209
|
*
|
|
210
210
|
* @param stealthAddress - The stealth address to check
|
|
211
|
-
* @param spendingPrivateKey - Recipient's spending private key
|
|
212
211
|
* @param viewingPrivateKey - Recipient's viewing private key
|
|
212
|
+
* @param spendingPublicKey - Recipient's spending public key (meta-address spendingKey)
|
|
213
213
|
* @returns True if the address belongs to this recipient
|
|
214
214
|
*
|
|
215
215
|
* @example
|
|
@@ -217,8 +217,8 @@ export function deriveNEARStealthPrivateKey(
|
|
|
217
217
|
* // Check if a detected address is for us
|
|
218
218
|
* const isForMe = checkNEARStealthAddress(
|
|
219
219
|
* announcement.stealthAddress,
|
|
220
|
-
*
|
|
221
|
-
*
|
|
220
|
+
* myViewingPrivateKey,
|
|
221
|
+
* mySpendingPublicKey
|
|
222
222
|
* )
|
|
223
223
|
*
|
|
224
224
|
* if (isForMe) {
|
|
@@ -228,13 +228,13 @@ export function deriveNEARStealthPrivateKey(
|
|
|
228
228
|
*/
|
|
229
229
|
export function checkNEARStealthAddress(
|
|
230
230
|
stealthAddress: StealthAddress,
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
viewingPrivateKey: HexString,
|
|
232
|
+
spendingPublicKey: HexString
|
|
233
233
|
): boolean {
|
|
234
234
|
return checkEd25519StealthAddress(
|
|
235
235
|
stealthAddress,
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
viewingPrivateKey,
|
|
237
|
+
spendingPublicKey
|
|
238
238
|
)
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -66,11 +66,23 @@ export const SOLANA_EXPLORER_URLS = {
|
|
|
66
66
|
export const MEMO_PROGRAM_ID = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
* SIP announcement memo prefix
|
|
69
|
+
* SIP announcement memo prefix (legacy SIP:1, swapped scheme — read-only back-compat)
|
|
70
70
|
* Format: SIP:1:<ephemeral_pubkey_base58>:<view_tag_hex>
|
|
71
71
|
*/
|
|
72
72
|
export const SIP_MEMO_PREFIX = 'SIP:1:'
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Canonical EIP-5564 (SIP:2) announcement memo prefix — emitted by new sends.
|
|
76
|
+
* Format: SIP:2:<ephemeral_pubkey_base58>:<view_tag_hex>[:<stealth_address_base58>]
|
|
77
|
+
*/
|
|
78
|
+
export const SIP_MEMO_PREFIX_V2 = 'SIP:2:'
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Version-agnostic prefix for filtering announcement logs during scanning.
|
|
82
|
+
* Matches both SIP:1 (legacy) and SIP:2 (canonical) announcements.
|
|
83
|
+
*/
|
|
84
|
+
export const SIP_MEMO_PREFIX_ANY = 'SIP:'
|
|
85
|
+
|
|
74
86
|
/**
|
|
75
87
|
* Estimated transaction fee in lamports
|
|
76
88
|
* Includes base fee + rent for ATA creation
|