@sip-protocol/sdk 0.9.0 → 0.11.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 +1363 -844
- package/dist/browser.mjs +13 -3
- package/dist/{chunk-64AYA5F5.mjs → chunk-7IUKXWDN.mjs} +229 -148
- package/dist/{chunk-4GRJ5MAW.mjs → chunk-KXETSSKP.mjs} +4 -0
- package/dist/{chunk-6EU6WQFK.mjs → chunk-L4RKPNIJ.mjs} +266 -239
- 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-Cwo3WhxX.d.mts} +128 -37
- package/dist/{index-DXh2IGkz.d.ts → index-X8qPQdp6.d.ts} +128 -37
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1356 -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-7QOA3HBZ.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/index.ts +2 -0
- package/src/chains/ethereum/privacy-adapter.ts +64 -5
- package/src/chains/ethereum/stealth.ts +89 -14
- package/src/chains/ethereum/types.ts +18 -2
- package/src/chains/near/constants.ts +13 -1
- package/src/chains/near/index.ts +2 -0
- package/src/chains/near/privacy-adapter.ts +8 -5
- package/src/chains/near/resolver.ts +24 -10
- package/src/chains/near/stealth.ts +9 -9
- package/src/chains/near/types.ts +20 -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/transfer.ts +1 -1
- 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 +157 -9
- 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
|
@@ -348,9 +348,12 @@ export function deriveEthereumStealthPrivateKey(
|
|
|
348
348
|
*
|
|
349
349
|
* Used during scanning to quickly filter announcements.
|
|
350
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
|
+
*
|
|
351
354
|
* @param stealthAddress - The stealth address to check
|
|
352
|
-
* @param spendingPrivateKey - Recipient's spending private key
|
|
353
355
|
* @param viewingPrivateKey - Recipient's viewing private key
|
|
356
|
+
* @param spendingPublicKey - Recipient's spending public key (meta-address spendingKey)
|
|
354
357
|
* @returns True if the address belongs to this recipient
|
|
355
358
|
*
|
|
356
359
|
* @example
|
|
@@ -359,8 +362,8 @@ export function deriveEthereumStealthPrivateKey(
|
|
|
359
362
|
* for (const announcement of announcements) {
|
|
360
363
|
* const isMine = checkEthereumStealthAddress(
|
|
361
364
|
* announcement.stealthAddress,
|
|
362
|
-
*
|
|
363
|
-
*
|
|
365
|
+
* myViewingPrivateKey,
|
|
366
|
+
* mySpendingPublicKey
|
|
364
367
|
* )
|
|
365
368
|
* if (isMine) {
|
|
366
369
|
* console.log('Found incoming payment!')
|
|
@@ -370,13 +373,13 @@ export function deriveEthereumStealthPrivateKey(
|
|
|
370
373
|
*/
|
|
371
374
|
export function checkEthereumStealthAddress(
|
|
372
375
|
stealthAddress: StealthAddress,
|
|
373
|
-
|
|
374
|
-
|
|
376
|
+
viewingPrivateKey: HexString,
|
|
377
|
+
spendingPublicKey: HexString
|
|
375
378
|
): boolean {
|
|
376
379
|
return checkSecp256k1StealthAddress(
|
|
377
380
|
stealthAddress,
|
|
378
|
-
|
|
379
|
-
|
|
381
|
+
viewingPrivateKey,
|
|
382
|
+
spendingPublicKey
|
|
380
383
|
)
|
|
381
384
|
}
|
|
382
385
|
|
|
@@ -419,10 +422,10 @@ export function checkEthereumStealthByEthAddress(
|
|
|
419
422
|
const ephemeralPubBytes = hexToBytes(ephemeralPublicKey.slice(2))
|
|
420
423
|
|
|
421
424
|
try {
|
|
422
|
-
// Compute shared secret: S =
|
|
423
|
-
//
|
|
425
|
+
// Compute shared secret: S = viewingPrivateKey * ephemeralPublicKey
|
|
426
|
+
// Canonical EIP-5564: ECDH on the viewing key (mirrors generation S = r * K_view)
|
|
424
427
|
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
425
|
-
|
|
428
|
+
viewingPrivBytes,
|
|
426
429
|
ephemeralPubBytes,
|
|
427
430
|
)
|
|
428
431
|
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
@@ -432,11 +435,11 @@ export function checkEthereumStealthByEthAddress(
|
|
|
432
435
|
return null
|
|
433
436
|
}
|
|
434
437
|
|
|
435
|
-
// Derive stealth private key:
|
|
436
|
-
// Mirrors generation: stealth =
|
|
437
|
-
const
|
|
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))
|
|
438
441
|
const hashScalar = BigInt('0x' + bytesToHex(sharedSecretHash))
|
|
439
|
-
const stealthPrivScalar = (
|
|
442
|
+
const stealthPrivScalar = (spendingScalar + hashScalar) % secp256k1.CURVE.n
|
|
440
443
|
|
|
441
444
|
// Compute expected public key from derived private key
|
|
442
445
|
const stealthPrivHex = stealthPrivScalar.toString(16).padStart(64, '0')
|
|
@@ -457,6 +460,78 @@ export function checkEthereumStealthByEthAddress(
|
|
|
457
460
|
}
|
|
458
461
|
}
|
|
459
462
|
|
|
463
|
+
/**
|
|
464
|
+
* View-only check of an Ethereum stealth announcement by ETH address.
|
|
465
|
+
*
|
|
466
|
+
* Detects whether a payment was intended for this recipient using only the viewing
|
|
467
|
+
* PRIVATE key and the spending PUBLIC key — never the spending private key (which is
|
|
468
|
+
* required to claim, not to detect). Recomputes the expected stealth public key as
|
|
469
|
+
* `A = K_spend + H(S)*G` (point arithmetic on the spending public key), converts it to an
|
|
470
|
+
* ETH address, and compares. Mirrors {@link checkEthereumStealthByEthAddress} but without
|
|
471
|
+
* the spending private key and returning a boolean rather than the derived key.
|
|
472
|
+
*
|
|
473
|
+
* @param ethAddress - The ETH address from the announcement (20 bytes)
|
|
474
|
+
* @param ephemeralPublicKey - Ephemeral public key from the announcement
|
|
475
|
+
* @param viewTag - View tag from the announcement
|
|
476
|
+
* @param spendingPublicKey - Recipient's spending public key (meta-address spendingKey)
|
|
477
|
+
* @param viewingPrivateKey - Recipient's viewing private key
|
|
478
|
+
* @returns True if the address belongs to this recipient
|
|
479
|
+
*/
|
|
480
|
+
export function checkEthereumStealthByEthAddressViewOnly(
|
|
481
|
+
ethAddress: HexString,
|
|
482
|
+
ephemeralPublicKey: HexString,
|
|
483
|
+
viewTag: number,
|
|
484
|
+
spendingPublicKey: HexString,
|
|
485
|
+
viewingPrivateKey: HexString,
|
|
486
|
+
): boolean {
|
|
487
|
+
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
488
|
+
throw new ValidationError(
|
|
489
|
+
'must be a valid 32-byte hex string',
|
|
490
|
+
'viewingPrivateKey'
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
|
|
495
|
+
const spendingPubBytes = hexToBytes(spendingPublicKey.slice(2))
|
|
496
|
+
const ephemeralPubBytes = hexToBytes(ephemeralPublicKey.slice(2))
|
|
497
|
+
|
|
498
|
+
try {
|
|
499
|
+
// Compute shared secret: S = viewingPrivateKey * ephemeralPublicKey
|
|
500
|
+
// Canonical EIP-5564: ECDH on the viewing key (mirrors generation S = r * K_view)
|
|
501
|
+
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
502
|
+
viewingPrivBytes,
|
|
503
|
+
ephemeralPubBytes,
|
|
504
|
+
)
|
|
505
|
+
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
506
|
+
|
|
507
|
+
// Quick view tag check
|
|
508
|
+
if (sharedSecretHash[0] !== viewTag) {
|
|
509
|
+
return false
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Expected stealth address: A = K_spend + hash(S)*G — point arithmetic on the spending
|
|
513
|
+
// PUBLIC key, so no spending private key is needed. Reduce hash(S) into [1, n-1] (a zero
|
|
514
|
+
// offset would be degenerate) before scaling the generator.
|
|
515
|
+
const hashScalar = BigInt('0x' + bytesToHex(sharedSecretHash)) % secp256k1.CURVE.n
|
|
516
|
+
if (hashScalar === 0n) {
|
|
517
|
+
return false
|
|
518
|
+
}
|
|
519
|
+
const spendingPubPoint = secp256k1.ProjectivePoint.fromHex(spendingPubBytes)
|
|
520
|
+
const expectedPoint = spendingPubPoint.add(
|
|
521
|
+
secp256k1.ProjectivePoint.BASE.multiply(hashScalar),
|
|
522
|
+
)
|
|
523
|
+
const expectedPubKey = expectedPoint.toRawBytes(true)
|
|
524
|
+
|
|
525
|
+
// Convert to ETH address and compare (case-insensitive)
|
|
526
|
+
const expectedPubKeyHex = ('0x' + bytesToHex(expectedPubKey)) as HexString
|
|
527
|
+
const expectedEthAddress = publicKeyToEthAddress(expectedPubKeyHex)
|
|
528
|
+
|
|
529
|
+
return expectedEthAddress.toLowerCase() === ethAddress.toLowerCase()
|
|
530
|
+
} catch {
|
|
531
|
+
return false
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
460
535
|
// ─── Address Conversion ─────────────────────────────────────────────────────
|
|
461
536
|
|
|
462
537
|
/**
|
|
@@ -441,8 +441,12 @@ export interface EthereumScanRecipient {
|
|
|
441
441
|
viewingPrivateKey: HexString
|
|
442
442
|
/** Spending public key */
|
|
443
443
|
spendingPublicKey: HexString
|
|
444
|
-
/**
|
|
445
|
-
|
|
444
|
+
/**
|
|
445
|
+
* Spending private key — required to derive claimable keys during a full scan
|
|
446
|
+
* (`scanAnnouncements`). Optional for view-only registration: a recipient with only
|
|
447
|
+
* `viewingPrivateKey` + `spendingPublicKey` detects payments via `scanAnnouncementsViewOnly`.
|
|
448
|
+
*/
|
|
449
|
+
spendingPrivateKey?: HexString
|
|
446
450
|
/** Optional label */
|
|
447
451
|
label?: string
|
|
448
452
|
}
|
|
@@ -458,3 +462,15 @@ export interface EthereumDetectedPaymentResult {
|
|
|
458
462
|
/** Derived stealth private key (for claiming) */
|
|
459
463
|
stealthPrivateKey: HexString
|
|
460
464
|
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* View-only detected payment — carries no derived private key (detection used the
|
|
468
|
+
* spending PUBLIC key only). To claim, derive the stealth private key separately with
|
|
469
|
+
* both private keys at claim time.
|
|
470
|
+
*/
|
|
471
|
+
export interface EthereumViewOnlyDetectionResult {
|
|
472
|
+
/** The detected payment */
|
|
473
|
+
payment: EthereumDetectedPayment
|
|
474
|
+
/** The recipient that matched */
|
|
475
|
+
recipient: EthereumScanRecipient
|
|
476
|
+
}
|
|
@@ -77,11 +77,23 @@ export const NEAR_TOKEN_DECIMALS: Record<string, number> = {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
* SIP announcement prefix
|
|
80
|
+
* SIP announcement memo prefix (legacy SIP:1, swapped scheme — read-only back-compat)
|
|
81
81
|
* Format: SIP:1:<ephemeral_pubkey_hex>:<view_tag_hex>
|
|
82
82
|
*/
|
|
83
83
|
export const SIP_MEMO_PREFIX = 'SIP:1:'
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Canonical EIP-5564 (SIP:2) announcement memo prefix — emitted by new sends.
|
|
87
|
+
* Format: SIP:2:<ephemeral_pubkey_hex>:<view_tag_hex>
|
|
88
|
+
*/
|
|
89
|
+
export const SIP_MEMO_PREFIX_V2 = 'SIP:2:'
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Version-agnostic announcement memo prefix for scanning.
|
|
93
|
+
* Matches both SIP:1 (legacy) and SIP:2 (canonical) announcements.
|
|
94
|
+
*/
|
|
95
|
+
export const SIP_MEMO_PREFIX_ANY = 'SIP:'
|
|
96
|
+
|
|
85
97
|
/**
|
|
86
98
|
* NEAR implicit account length (64 hex characters = 32 bytes)
|
|
87
99
|
*/
|
package/src/chains/near/index.ts
CHANGED
|
@@ -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'
|
|
@@ -37,13 +38,26 @@ import { isValidHex } from '../../validation'
|
|
|
37
38
|
import { checkNEARStealthAddress, implicitAccountToEd25519PublicKey } from './stealth'
|
|
38
39
|
import { parseAnnouncement, type NEARAnnouncement } from './types'
|
|
39
40
|
import {
|
|
40
|
-
|
|
41
|
+
SIP_MEMO_PREFIX_ANY,
|
|
41
42
|
VIEW_TAG_MIN,
|
|
42
43
|
VIEW_TAG_MAX,
|
|
43
44
|
isImplicitAccount,
|
|
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) {
|
|
@@ -708,7 +722,7 @@ export class NEARStealthScanner {
|
|
|
708
722
|
const announcements: NEARAnnouncement[] = []
|
|
709
723
|
|
|
710
724
|
for (const log of logs) {
|
|
711
|
-
if (!log.includes(
|
|
725
|
+
if (!log.includes(SIP_MEMO_PREFIX_ANY)) {
|
|
712
726
|
continue
|
|
713
727
|
}
|
|
714
728
|
|
|
@@ -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
|
|
package/src/chains/near/types.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { HexString, StealthAddress } from '@sip-protocol/types'
|
|
11
|
-
import {
|
|
11
|
+
import { SIP_MEMO_PREFIX_V2, VIEW_TAG_MIN, VIEW_TAG_MAX } from './constants'
|
|
12
12
|
|
|
13
13
|
// ─── Announcement Types ──────────────────────────────────────────────────────
|
|
14
14
|
|
|
@@ -18,6 +18,8 @@ import { SIP_MEMO_PREFIX, VIEW_TAG_MIN, VIEW_TAG_MAX } from './constants'
|
|
|
18
18
|
* Contains the information needed for recipients to scan for payments.
|
|
19
19
|
*/
|
|
20
20
|
export interface NEARAnnouncement {
|
|
21
|
+
/** Announcement scheme version: '1' = legacy swapped, '2' = canonical EIP-5564 */
|
|
22
|
+
version?: string
|
|
21
23
|
/** Ephemeral public key (ed25519, 0x-prefixed hex) */
|
|
22
24
|
ephemeralPublicKey: HexString
|
|
23
25
|
/** View tag for efficient filtering (0-255) */
|
|
@@ -35,16 +37,21 @@ export interface NEARAnnouncement {
|
|
|
35
37
|
/**
|
|
36
38
|
* Parse an announcement from a NEAR memo string
|
|
37
39
|
*
|
|
38
|
-
*
|
|
40
|
+
* Accepts SIP:1 (legacy swapped scheme) and SIP:2 (canonical EIP-5564); the detected
|
|
41
|
+
* version is returned. NEAR derives canonically regardless, so the version is recorded
|
|
42
|
+
* for consistency rather than to route the claim.
|
|
43
|
+
*
|
|
44
|
+
* Format: SIP:<version>:<ephemeral_pubkey_hex>:<view_tag_hex>
|
|
39
45
|
*
|
|
40
46
|
* @param memo - The memo string to parse
|
|
41
47
|
* @returns Parsed announcement or null if invalid
|
|
42
48
|
*
|
|
43
49
|
* @example
|
|
44
50
|
* ```typescript
|
|
45
|
-
* const memo = 'SIP:
|
|
51
|
+
* const memo = 'SIP:2:1234...abcd:0f'
|
|
46
52
|
* const announcement = parseAnnouncement(memo)
|
|
47
53
|
* if (announcement) {
|
|
54
|
+
* console.log(announcement.version) // '2'
|
|
48
55
|
* console.log(announcement.ephemeralPublicKey)
|
|
49
56
|
* console.log(announcement.viewTag) // 15
|
|
50
57
|
* }
|
|
@@ -55,13 +62,15 @@ export function parseAnnouncement(memo: string): Partial<NEARAnnouncement> | nul
|
|
|
55
62
|
return null
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
//
|
|
59
|
-
|
|
65
|
+
// Accept SIP:1 (legacy) and SIP:2 (canonical); capture the version.
|
|
66
|
+
const versionMatch = /^SIP:([12]):/.exec(memo)
|
|
67
|
+
if (!versionMatch) {
|
|
60
68
|
return null
|
|
61
69
|
}
|
|
70
|
+
const version = versionMatch[1]
|
|
62
71
|
|
|
63
|
-
// Parse parts:
|
|
64
|
-
const content = memo.slice(
|
|
72
|
+
// Parse parts: <ephemeral_pubkey_hex>:<view_tag_hex>
|
|
73
|
+
const content = memo.slice(versionMatch[0].length)
|
|
65
74
|
const parts = content.split(':')
|
|
66
75
|
|
|
67
76
|
if (parts.length < 2) {
|
|
@@ -86,6 +95,7 @@ export function parseAnnouncement(memo: string): Partial<NEARAnnouncement> | nul
|
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
return {
|
|
98
|
+
version,
|
|
89
99
|
ephemeralPublicKey: `0x${ephemeralKeyHex.toLowerCase()}` as HexString,
|
|
90
100
|
viewTag,
|
|
91
101
|
}
|
|
@@ -104,7 +114,7 @@ export function parseAnnouncement(memo: string): Partial<NEARAnnouncement> | nul
|
|
|
104
114
|
* stealthAddress.ephemeralPublicKey,
|
|
105
115
|
* stealthAddress.viewTag
|
|
106
116
|
* )
|
|
107
|
-
* // => 'SIP:
|
|
117
|
+
* // => 'SIP:2:1234...abcd:0f'
|
|
108
118
|
* ```
|
|
109
119
|
*/
|
|
110
120
|
export function createAnnouncementMemo(
|
|
@@ -117,7 +127,8 @@ export function createAnnouncementMemo(
|
|
|
117
127
|
// Convert view tag to 2-char hex
|
|
118
128
|
const viewTagHex = viewTag.toString(16).padStart(2, '0')
|
|
119
129
|
|
|
120
|
-
|
|
130
|
+
// Emit the canonical SIP:2 announcement (EIP-5564). Legacy SIP:1 remains parseable.
|
|
131
|
+
return `${SIP_MEMO_PREFIX_V2}${ephemeralKeyHex}:${viewTagHex}`
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
// ─── Transfer Types ──────────────────────────────────────────────────────────
|
|
@@ -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
|