@sip-protocol/sdk 0.1.9 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sip-protocol/sdk",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "Core SDK for Shielded Intents Protocol - Privacy layer for cross-chain transactions",
5
5
  "author": "SIP Protocol <hello@sip-protocol.org>",
6
6
  "homepage": "https://sip-protocol.org",
@@ -20,23 +20,17 @@
20
20
  "types": "./dist/index.d.ts",
21
21
  "import": "./dist/index.mjs",
22
22
  "require": "./dist/index.js"
23
+ },
24
+ "./browser": {
25
+ "types": "./dist/browser.d.ts",
26
+ "import": "./dist/browser.mjs",
27
+ "require": "./dist/browser.js"
23
28
  }
24
29
  },
25
30
  "files": [
26
31
  "dist",
27
32
  "src"
28
33
  ],
29
- "scripts": {
30
- "build": "tsup src/index.ts --format cjs,esm --dts",
31
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
32
- "lint": "eslint --ext .ts src/",
33
- "typecheck": "tsc --noEmit",
34
- "clean": "rm -rf dist",
35
- "test": "vitest",
36
- "test:coverage": "vitest run --coverage",
37
- "bench": "vitest bench --config vitest.bench.config.ts",
38
- "bench:json": "vitest bench --config vitest.bench.config.ts --outputJson benchmarks/results.json"
39
- },
40
34
  "dependencies": {
41
35
  "@aztec/bb.js": "^0.63.1",
42
36
  "@noble/ciphers": "^2.0.1",
@@ -48,6 +42,7 @@
48
42
  },
49
43
  "devDependencies": {
50
44
  "@vitest/coverage-v8": "1.6.1",
45
+ "fast-check": "^4.3.0",
51
46
  "tsup": "^8.0.0",
52
47
  "typescript": "^5.3.0",
53
48
  "vitest": "^1.1.0"
@@ -60,5 +55,16 @@
60
55
  "stealth-addresses",
61
56
  "zcash"
62
57
  ],
63
- "license": "MIT"
64
- }
58
+ "license": "MIT",
59
+ "scripts": {
60
+ "build": "tsup src/index.ts src/browser.ts --format cjs,esm --dts",
61
+ "dev": "tsup src/index.ts src/browser.ts --format cjs,esm --dts --watch",
62
+ "lint": "eslint --ext .ts src/",
63
+ "typecheck": "tsc --noEmit",
64
+ "clean": "rm -rf dist",
65
+ "test": "vitest",
66
+ "test:coverage": "vitest run --coverage",
67
+ "bench": "vitest bench --config vitest.bench.config.ts",
68
+ "bench:json": "vitest bench --config vitest.bench.config.ts --outputJson benchmarks/results.json"
69
+ }
70
+ }
@@ -3,6 +3,12 @@
3
3
  *
4
4
  * Bridges SIP SDK with NEAR 1Click API, providing privacy-preserving
5
5
  * cross-chain swaps using stealth addresses.
6
+ *
7
+ * IMPORTANT: NEAR Intents (1Click API) operates on MAINNET ONLY.
8
+ * There is no testnet deployment. For testing:
9
+ * - Use `dry: true` for quote-only mode (real quotes, no execution)
10
+ * - Use MockSolver for unit tests
11
+ * - Use small mainnet amounts ($5-10) for integration testing
6
12
  */
7
13
 
8
14
  import {
@@ -22,7 +28,17 @@ import {
22
28
  OneClickRecipientType,
23
29
  } from '@sip-protocol/types'
24
30
  import { OneClickClient } from './oneclick-client'
25
- import { generateStealthAddress, decodeStealthMetaAddress, publicKeyToEthAddress } from '../stealth'
31
+ import {
32
+ generateStealthAddress,
33
+ decodeStealthMetaAddress,
34
+ publicKeyToEthAddress,
35
+ isEd25519Chain,
36
+ generateEd25519StealthAddress,
37
+ ed25519PublicKeyToSolanaAddress,
38
+ ed25519PublicKeyToNearAddress,
39
+ getCurveForChain,
40
+ type StealthCurve,
41
+ } from '../stealth'
26
42
  import { ValidationError } from '../errors'
27
43
 
28
44
  /**
@@ -59,6 +75,10 @@ export interface PreparedSwap {
59
75
  }
60
76
  /** Shared secret for stealth address derivation (keep private!) */
61
77
  sharedSecret?: HexString
78
+ /** Curve used for stealth address (for cross-chain compatibility) */
79
+ curve?: StealthCurve
80
+ /** Native recipient address (converted from stealth public key) */
81
+ nativeRecipientAddress?: string
62
82
  }
63
83
 
64
84
  /**
@@ -233,6 +253,10 @@ export class NEARIntentsAdapter {
233
253
  let stealthData: PreparedSwap['stealthAddress']
234
254
  let sharedSecret: HexString | undefined
235
255
 
256
+ // Track curve and native address for cross-chain metadata
257
+ let curve: StealthCurve | undefined
258
+ let nativeRecipientAddress: string | undefined
259
+
236
260
  if (request.privacyLevel !== PrivacyLevel.TRANSPARENT) {
237
261
  // Privacy mode requires stealth address
238
262
  if (!recipientMetaAddress) {
@@ -247,46 +271,128 @@ export class NEARIntentsAdapter {
247
271
  ? decodeStealthMetaAddress(recipientMetaAddress)
248
272
  : recipientMetaAddress
249
273
 
250
- // Generate stealth address for recipient (output chain)
251
- const { stealthAddress, sharedSecret: secret } = generateStealthAddress(metaAddr)
274
+ // Determine curve based on output chain
275
+ const outputChain = request.outputAsset.chain
276
+ const outputChainType = CHAIN_BLOCKCHAIN_MAP[outputChain]
277
+
278
+ if (isEd25519Chain(outputChain)) {
279
+ // Ed25519 chains (Solana, NEAR) use ed25519 stealth addresses
280
+ curve = 'ed25519'
281
+
282
+ // Validate that meta-address uses ed25519 keys (32 bytes)
283
+ const spendingKeyBytes = (metaAddr.spendingKey.length - 2) / 2
284
+ if (spendingKeyBytes !== 32) {
285
+ throw new ValidationError(
286
+ `Meta-address has ${spendingKeyBytes}-byte keys but ${outputChain} requires ed25519 (32-byte) keys. ` +
287
+ `Please generate an ed25519 meta-address using generateEd25519StealthMetaAddress('${outputChain}').`,
288
+ 'recipientMetaAddress',
289
+ { outputChain, keySize: spendingKeyBytes, expectedSize: 32 }
290
+ )
291
+ }
292
+
293
+ // Generate ed25519 stealth address
294
+ const { stealthAddress, sharedSecret: secret } = generateEd25519StealthAddress(metaAddr)
295
+ stealthData = stealthAddress
296
+ sharedSecret = secret
252
297
 
253
- // Stealth addresses are secp256k1-based (EIP-5564 style) and only work for EVM chains.
254
- // Non-EVM chains (Solana, Bitcoin, etc.) use different cryptographic schemes
255
- // and cannot receive funds at secp256k1-derived addresses.
256
- const outputChainType = CHAIN_BLOCKCHAIN_MAP[request.outputAsset.chain]
257
- if (outputChainType !== 'evm') {
298
+ // Convert stealth public key to native chain address
299
+ if (outputChain === 'solana') {
300
+ recipientAddress = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
301
+ } else if (outputChain === 'near') {
302
+ recipientAddress = ed25519PublicKeyToNearAddress(stealthAddress.address)
303
+ } else {
304
+ // Future ed25519 chains
305
+ throw new ValidationError(
306
+ `ed25519 address derivation not implemented for ${outputChain}`,
307
+ 'outputAsset',
308
+ { outputChain }
309
+ )
310
+ }
311
+ nativeRecipientAddress = recipientAddress
312
+ } else if (outputChainType === 'evm') {
313
+ // EVM chains use secp256k1 stealth addresses
314
+ curve = 'secp256k1'
315
+
316
+ // Validate that meta-address uses secp256k1 keys (33 bytes compressed)
317
+ const spendingKeyBytes = (metaAddr.spendingKey.length - 2) / 2
318
+ if (spendingKeyBytes !== 33) {
319
+ throw new ValidationError(
320
+ `Meta-address has ${spendingKeyBytes}-byte keys but ${outputChain} requires secp256k1 (33-byte compressed) keys. ` +
321
+ `Please generate a secp256k1 meta-address using generateStealthMetaAddress('${outputChain}').`,
322
+ 'recipientMetaAddress',
323
+ { outputChain, keySize: spendingKeyBytes, expectedSize: 33 }
324
+ )
325
+ }
326
+
327
+ // Generate secp256k1 stealth address
328
+ const { stealthAddress, sharedSecret: secret } = generateStealthAddress(metaAddr)
329
+ stealthData = stealthAddress
330
+ sharedSecret = secret
331
+
332
+ // Convert stealth public key to ETH address format
333
+ // The 1Click API expects 20-byte Ethereum addresses
334
+ recipientAddress = publicKeyToEthAddress(stealthAddress.address)
335
+ nativeRecipientAddress = recipientAddress
336
+ } else {
337
+ // Unsupported chain type (e.g., Bitcoin, Zcash - different schemes)
258
338
  throw new ValidationError(
259
- `Stealth addresses are not supported for ${request.outputAsset.chain} output. ` +
260
- `SIP stealth addresses use secp256k1 (EIP-5564 style) which only works for EVM chains. ` +
261
- `For ${request.outputAsset.chain} output, please connect a wallet or use an EVM output chain.`,
339
+ `Stealth addresses are not yet supported for ${outputChain} output. ` +
340
+ `Supported chains: EVM (Ethereum, Polygon, etc.), Solana, NEAR. ` +
341
+ `For ${outputChain}, please provide a direct wallet address.`,
262
342
  'outputAsset',
263
- { outputChain: request.outputAsset.chain, outputChainType }
343
+ { outputChain, outputChainType }
264
344
  )
265
345
  }
266
346
 
267
- // For EVM chains, convert stealth public key to ETH address format
268
- // The 1Click API expects 20-byte Ethereum addresses, not 33-byte secp256k1 public keys
269
- recipientAddress = publicKeyToEthAddress(stealthAddress.address)
270
- stealthData = stealthAddress
271
- sharedSecret = secret
272
-
273
347
  // Generate refund address for input chain (if no sender address provided)
274
348
  if (!senderAddress) {
275
- const inputChainType = CHAIN_BLOCKCHAIN_MAP[request.inputAsset.chain]
276
- if (inputChainType === 'evm') {
277
- // For EVM input chains, generate a stealth address and convert to ETH address
278
- const refundStealth = generateStealthAddress(metaAddr)
279
- refundAddress = publicKeyToEthAddress(refundStealth.stealthAddress.address)
349
+ const inputChain = request.inputAsset.chain
350
+ const inputChainType = CHAIN_BLOCKCHAIN_MAP[inputChain]
351
+
352
+ if (isEd25519Chain(inputChain)) {
353
+ // For ed25519 input chains, generate ed25519 stealth address for refunds
354
+ // Note: This requires the meta-address to have ed25519 keys
355
+ const inputKeyBytes = (metaAddr.spendingKey.length - 2) / 2
356
+ if (inputKeyBytes === 32) {
357
+ const refundStealth = generateEd25519StealthAddress(metaAddr)
358
+ if (inputChain === 'solana') {
359
+ refundAddress = ed25519PublicKeyToSolanaAddress(refundStealth.stealthAddress.address)
360
+ } else if (inputChain === 'near') {
361
+ refundAddress = ed25519PublicKeyToNearAddress(refundStealth.stealthAddress.address)
362
+ }
363
+ } else {
364
+ // Cross-curve: output is secp256k1 but input is ed25519
365
+ // Cannot generate ed25519 refund address from secp256k1 meta-address
366
+ throw new ValidationError(
367
+ `Cross-curve refunds not supported: input chain ${inputChain} requires ed25519 but meta-address uses secp256k1. ` +
368
+ `Please provide a senderAddress for refunds, or use matching curves for input/output chains.`,
369
+ 'senderAddress',
370
+ { inputChain, inputChainType, metaAddressCurve: 'secp256k1' }
371
+ )
372
+ }
373
+ } else if (inputChainType === 'evm') {
374
+ // For EVM input chains, generate secp256k1 stealth address for refunds
375
+ const inputKeyBytes = (metaAddr.spendingKey.length - 2) / 2
376
+ if (inputKeyBytes === 33) {
377
+ const refundStealth = generateStealthAddress(metaAddr)
378
+ refundAddress = publicKeyToEthAddress(refundStealth.stealthAddress.address)
379
+ } else {
380
+ // Cross-curve: output is ed25519 but input is EVM
381
+ // Cannot generate secp256k1 refund address from ed25519 meta-address
382
+ throw new ValidationError(
383
+ `Cross-curve refunds not supported: input chain ${inputChain} requires secp256k1 but meta-address uses ed25519. ` +
384
+ `Please provide a senderAddress for refunds, or use matching curves for input/output chains.`,
385
+ 'senderAddress',
386
+ { inputChain, inputChainType, metaAddressCurve: 'ed25519' }
387
+ )
388
+ }
280
389
  } else {
281
- // For non-EVM input chains (Solana, Bitcoin, etc.), we cannot generate
282
- // valid stealth addresses because they use different cryptographic schemes.
283
- // Require sender address for refunds on these chains.
390
+ // Unsupported input chain for refunds
284
391
  throw new ValidationError(
285
- `senderAddress is required for refunds on ${request.inputAsset.chain}. ` +
286
- `Stealth addresses are only supported for EVM-compatible chains. ` +
287
- `Please connect a wallet or provide a sender address.`,
392
+ `senderAddress is required for refunds on ${inputChain}. ` +
393
+ `Automatic refund address generation is only supported for EVM, Solana, and NEAR chains.`,
288
394
  'senderAddress',
289
- { inputChain: request.inputAsset.chain, inputChainType }
395
+ { inputChain, inputChainType }
290
396
  )
291
397
  }
292
398
  }
@@ -309,6 +415,8 @@ export class NEARIntentsAdapter {
309
415
  quoteRequest,
310
416
  stealthAddress: stealthData,
311
417
  sharedSecret,
418
+ curve,
419
+ nativeRecipientAddress,
312
420
  }
313
421
  }
314
422
 
package/src/browser.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @sip-protocol/sdk/browser
3
+ *
4
+ * Browser-optimized entry point for SIP Protocol SDK.
5
+ * Use this import when building for browser environments.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // Browser-specific import
10
+ * import { BrowserNoirProvider } from '@sip-protocol/sdk/browser'
11
+ *
12
+ * const provider = new BrowserNoirProvider()
13
+ * await provider.initialize()
14
+ * ```
15
+ *
16
+ * @see https://github.com/sip-protocol/sip-protocol/issues/121
17
+ */
18
+
19
+ // Re-export everything from main entry
20
+ export * from './index'
21
+
22
+ // Browser-specific exports (import directly from browser module to get WASM support)
23
+ export { BrowserNoirProvider } from './proofs/browser'
24
+
25
+ // Re-export utilities that are already in main (for convenience)
26
+ export {
27
+ isBrowser,
28
+ supportsWebWorkers,
29
+ supportsSharedArrayBuffer,
30
+ getBrowserInfo,
31
+ browserHexToBytes,
32
+ browserBytesToHex,
33
+ } from './proofs'
34
+
35
+ export type { BrowserNoirProviderConfig, ProofProgressCallback } from './proofs'
package/src/commitment.ts CHANGED
@@ -183,9 +183,9 @@ export function commit(
183
183
  }
184
184
 
185
185
  // Ensure blinding is in valid range (mod n), and non-zero for valid scalar
186
- let rScalar = bytesToBigInt(r) % CURVE_ORDER
186
+ const rScalar = bytesToBigInt(r) % CURVE_ORDER
187
187
  if (rScalar === 0n) {
188
- rScalar = 1n // Avoid zero scalar which is invalid
188
+ throw new Error('CRITICAL: Zero blinding scalar after reduction - investigate RNG')
189
189
  }
190
190
 
191
191
  // C = v*G + r*H
@@ -242,9 +242,9 @@ export function verifyOpening(
242
242
 
243
243
  // Recompute expected commitment
244
244
  const blindingBytes = hexToBytes(blinding.slice(2))
245
- let rScalar = bytesToBigInt(blindingBytes) % CURVE_ORDER
245
+ const rScalar = bytesToBigInt(blindingBytes) % CURVE_ORDER
246
246
  if (rScalar === 0n) {
247
- rScalar = 1n // Match the commit() behavior
247
+ throw new Error('CRITICAL: Zero blinding scalar after reduction - investigate RNG')
248
248
  }
249
249
 
250
250
  // Handle edge cases
package/src/index.ts CHANGED
@@ -65,6 +65,7 @@ export type { CreateIntentOptions } from './intent'
65
65
 
66
66
  // Stealth addresses
67
67
  export {
68
+ // secp256k1 (EVM chains)
68
69
  generateStealthMetaAddress,
69
70
  generateStealthAddress,
70
71
  deriveStealthPrivateKey,
@@ -72,8 +73,26 @@ export {
72
73
  encodeStealthMetaAddress,
73
74
  decodeStealthMetaAddress,
74
75
  publicKeyToEthAddress,
76
+ // ed25519 (Solana, NEAR)
77
+ isEd25519Chain,
78
+ getCurveForChain,
79
+ generateEd25519StealthMetaAddress,
80
+ generateEd25519StealthAddress,
81
+ deriveEd25519StealthPrivateKey,
82
+ checkEd25519StealthAddress,
83
+ // Solana address derivation
84
+ ed25519PublicKeyToSolanaAddress,
85
+ solanaAddressToEd25519PublicKey,
86
+ isValidSolanaAddress,
87
+ // NEAR address derivation
88
+ ed25519PublicKeyToNearAddress,
89
+ nearAddressToEd25519PublicKey,
90
+ isValidNearImplicitAddress,
91
+ isValidNearAccountId,
75
92
  } from './stealth'
76
93
 
94
+ export type { StealthCurve } from './stealth'
95
+
77
96
  // Privacy utilities
78
97
  export {
79
98
  getPrivacyConfig,
@@ -133,6 +152,7 @@ export {
133
152
  isValidSlippage,
134
153
  isValidStealthMetaAddress,
135
154
  isValidCompressedPublicKey,
155
+ isValidEd25519PublicKey,
136
156
  isValidPrivateKey,
137
157
  isValidScalar,
138
158
  validateCreateIntentParams,
@@ -144,10 +164,19 @@ export {
144
164
  } from './validation'
145
165
 
146
166
  // Proof providers
167
+ // NOTE: BrowserNoirProvider is NOT exported here to avoid bundling WASM in server builds.
168
+ // Import from '@sip-protocol/sdk/browser' for browser environments.
147
169
  export {
148
170
  MockProofProvider,
149
171
  NoirProofProvider,
150
172
  ProofGenerationError,
173
+ // Browser utilities (safe - no WASM)
174
+ isBrowser,
175
+ supportsWebWorkers,
176
+ supportsSharedArrayBuffer,
177
+ getBrowserInfo,
178
+ browserHexToBytes,
179
+ browserBytesToHex,
151
180
  } from './proofs'
152
181
 
153
182
  export type {
@@ -159,8 +188,51 @@ export type {
159
188
  OracleAttestation,
160
189
  ProofResult,
161
190
  NoirProviderConfig,
191
+ BrowserNoirProviderConfig,
192
+ ProofProgressCallback,
162
193
  } from './proofs'
163
194
 
195
+ // Oracle attestation (for fulfillment proofs)
196
+ export {
197
+ // Verification functions
198
+ verifyAttestation,
199
+ verifyOracleSignature,
200
+ signAttestationMessage,
201
+ deriveOracleId,
202
+ // Registry management
203
+ createOracleRegistry,
204
+ addOracle,
205
+ removeOracle,
206
+ updateOracleStatus,
207
+ getActiveOracles,
208
+ hasEnoughOracles,
209
+ // Serialization
210
+ serializeAttestationMessage,
211
+ deserializeAttestationMessage,
212
+ computeAttestationHash,
213
+ getChainNumericId,
214
+ // Constants
215
+ ORACLE_DOMAIN,
216
+ ATTESTATION_VERSION,
217
+ DEFAULT_THRESHOLD,
218
+ DEFAULT_TOTAL_ORACLES,
219
+ CHAIN_NUMERIC_IDS,
220
+ } from './oracle'
221
+
222
+ export type {
223
+ OracleId,
224
+ OracleStatus,
225
+ OracleInfo,
226
+ OracleAttestationMessage,
227
+ OracleSignature,
228
+ SignedOracleAttestation,
229
+ OracleRegistry,
230
+ OracleRegistryConfig,
231
+ AttestationRequest,
232
+ AttestationResult,
233
+ VerificationResult,
234
+ } from './oracle'
235
+
164
236
  // Re-export types for convenience
165
237
  export {
166
238
  PrivacyLevel,
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Oracle Module
3
+ *
4
+ * Provides oracle attestation verification and registry management
5
+ * for cross-chain fulfillment proofs.
6
+ *
7
+ * @see docs/specs/ORACLE-ATTESTATION.md
8
+ */
9
+
10
+ export * from './types'
11
+ export * from './verification'
12
+ export * from './serialization'