@sip-protocol/sdk 0.10.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/dist/browser.d.mts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +19 -8
- package/dist/browser.mjs +2 -2
- package/dist/{chunk-G3TBBG2K.mjs → chunk-7IUKXWDN.mjs} +10 -4
- package/dist/{chunk-PT2DNA7E.mjs → chunk-L4RKPNIJ.mjs} +11 -6
- package/dist/{index-B1d8pihL.d.mts → index-Cwo3WhxX.d.mts} +11 -4
- package/dist/{index-UQhQJZbM.d.ts → index-X8qPQdp6.d.ts} +11 -4
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -8
- package/dist/index.mjs +2 -2
- package/dist/{solana-ZWNIQTSU.mjs → solana-7QOA3HBZ.mjs} +1 -1
- package/package.json +1 -1
- package/src/chains/ethereum/index.ts +2 -0
- package/src/chains/ethereum/privacy-adapter.ts +56 -0
- package/src/chains/ethereum/stealth.ts +72 -0
- 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/resolver.ts +2 -2
- package/src/chains/near/types.ts +20 -9
- package/src/chains/solana/transfer.ts +1 -1
- package/src/stealth/secp256k1.ts +17 -6
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 ──────────────────────────────────────────────────────────
|
|
@@ -218,7 +218,7 @@ export async function sendPrivateSPLTransfer(
|
|
|
218
218
|
)
|
|
219
219
|
|
|
220
220
|
// 4. Add memo with announcement for recipient scanning
|
|
221
|
-
// Format: SIP:
|
|
221
|
+
// Format: SIP:2:<ephemeral_pubkey_base58>:<view_tag_hex>:<stealth_address_base58>
|
|
222
222
|
// viewTag is a number (0-255), convert to 2-char hex
|
|
223
223
|
const viewTagHex = stealthAddress.viewTag.toString(16).padStart(2, '0')
|
|
224
224
|
const memoContent = createAnnouncementMemo(
|
package/src/stealth/secp256k1.ts
CHANGED
|
@@ -173,11 +173,17 @@ export function generateSecp256k1StealthAddress(
|
|
|
173
173
|
// Hash the shared secret for use as a scalar
|
|
174
174
|
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
175
175
|
|
|
176
|
-
// Compute stealth address: A = K_spend + hash(S)*G
|
|
177
|
-
|
|
176
|
+
// Compute stealth address: A = K_spend + hash(S)*G.
|
|
177
|
+
// Reduce hash(S) to a scalar in [1, n-1] before deriving the point: secp256k1.getPublicKey
|
|
178
|
+
// throws for a scalar >= n or == 0 (~3.7e-39 for a random SHA-256 digest). Reducing mod n
|
|
179
|
+
// mirrors the ed25519 path and keeps generate symmetric with derive (k_spend + hash(S) mod n).
|
|
180
|
+
const hashScalar = bytesToBigInt(sharedSecretHash) % secp256k1.CURVE.n
|
|
181
|
+
if (hashScalar === 0n) {
|
|
182
|
+
throw new Error('CRITICAL: zero hash scalar after reduction - investigate hash computation')
|
|
183
|
+
}
|
|
178
184
|
|
|
179
185
|
const spendingKeyPoint = secp256k1.ProjectivePoint.fromHex(spendingKeyBytes)
|
|
180
|
-
const hashTimesGPoint = secp256k1.ProjectivePoint.
|
|
186
|
+
const hashTimesGPoint = secp256k1.ProjectivePoint.BASE.multiply(hashScalar)
|
|
181
187
|
const stealthPoint = spendingKeyPoint.add(hashTimesGPoint)
|
|
182
188
|
const stealthAddressBytes = stealthPoint.toRawBytes(true)
|
|
183
189
|
|
|
@@ -378,10 +384,15 @@ export function checkSecp256k1StealthAddress(
|
|
|
378
384
|
return false
|
|
379
385
|
}
|
|
380
386
|
|
|
381
|
-
// Expected address: A = K_spend + hash(S)*G (no spending private key needed)
|
|
382
|
-
|
|
387
|
+
// Expected address: A = K_spend + hash(S)*G (no spending private key needed).
|
|
388
|
+
// Reduce hash(S) mod n (symmetric with generate); a zero scalar can't correspond to a
|
|
389
|
+
// real stealth payment, so treat it as a non-match.
|
|
390
|
+
const hashScalar = bytesToBigInt(sharedSecretHash) % secp256k1.CURVE.n
|
|
391
|
+
if (hashScalar === 0n) {
|
|
392
|
+
return false
|
|
393
|
+
}
|
|
383
394
|
const expectedPoint = secp256k1.ProjectivePoint.fromHex(spendingPubBytes).add(
|
|
384
|
-
secp256k1.ProjectivePoint.
|
|
395
|
+
secp256k1.ProjectivePoint.BASE.multiply(hashScalar),
|
|
385
396
|
)
|
|
386
397
|
|
|
387
398
|
// Compare with provided stealth address
|