@obelyzk/sdk 1.0.0 → 1.1.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.
@@ -23,7 +23,9 @@ __export(obelysk_exports, {
23
23
  CURVE_ORDER: () => CURVE_ORDER,
24
24
  ConfidentialTransferClient: () => ConfidentialTransferClient,
25
25
  DARKPOOL_ASSET_IDS: () => DARKPOOL_ASSET_IDS,
26
+ DENOMINATION_BY_SYMBOL: () => DENOMINATION_BY_SYMBOL,
26
27
  DarkPoolClient: () => DarkPoolClient,
28
+ ETH_DENOMINATIONS: () => ETH_DENOMINATIONS,
27
29
  FIELD_PRIME: () => FIELD_PRIME,
28
30
  GENERATOR_G: () => GENERATOR_G,
29
31
  GENERATOR_H: () => GENERATOR_H,
@@ -38,27 +40,36 @@ __export(obelysk_exports, {
38
40
  PrivacyPoolClient: () => PrivacyPoolClient,
39
41
  PrivacyRouterClient: () => PrivacyRouterClient,
40
42
  ProverStakingClient: () => ProverStakingClient,
43
+ SAGE_DENOMINATIONS: () => SAGE_DENOMINATIONS,
44
+ STRK_DENOMINATIONS: () => STRK_DENOMINATIONS,
41
45
  ShieldedSwapClient: () => ShieldedSwapClient,
42
46
  StealthClient: () => StealthClient,
43
47
  TOKEN_DECIMALS: () => TOKEN_DECIMALS,
48
+ USDC_DENOMINATIONS: () => USDC_DENOMINATIONS,
44
49
  VM31BridgeClient: () => VM31BridgeClient,
45
50
  VM31VaultClient: () => VM31VaultClient,
46
51
  VM31_ASSET_IDS: () => VM31_ASSET_IDS,
52
+ VM31_DENOMINATIONS: () => VM31_DENOMINATIONS,
53
+ WBTC_DENOMINATIONS: () => WBTC_DENOMINATIONS,
47
54
  commitmentToHash: () => commitmentToHash,
48
55
  createAEHint: () => createAEHint,
49
56
  createEncryptionProof: () => createEncryptionProof,
50
57
  deriveNullifier: () => deriveNullifier,
51
58
  ecAdd: () => ecAdd2,
52
59
  ecMul: () => ecMul2,
60
+ eciesEncrypt: () => eciesEncrypt,
53
61
  elgamalEncrypt: () => elgamalEncrypt,
54
62
  formatAmount: () => formatAmount,
55
63
  getContracts: () => getContracts,
64
+ getDenominations: () => getDenominations,
56
65
  getRpcUrl: () => getRpcUrl,
57
66
  mod: () => mod,
58
67
  modInverse: () => modInverse,
59
68
  parseAmount: () => parseAmount,
60
69
  pedersenCommit: () => pedersenCommit,
61
- randomScalar: () => randomScalar2
70
+ randomScalar: () => randomScalar2,
71
+ splitIntoDenominations: () => splitIntoDenominations,
72
+ validateDenomination: () => validateDenomination
62
73
  });
63
74
  module.exports = __toCommonJS(obelysk_exports);
64
75
 
@@ -2104,6 +2115,225 @@ var ShieldedSwapClient = class {
2104
2115
  }
2105
2116
  };
2106
2117
 
2118
+ // src/obelysk/ecies.ts
2119
+ var HKDF_INFO = "obelysk-ecies-v1";
2120
+ var ECIES_VERSION = 1;
2121
+ async function eciesEncrypt(payload, relayerPubkeyHex) {
2122
+ const subtle = getCrypto();
2123
+ const relayerPubkeyBytes = hexToBytes(relayerPubkeyHex);
2124
+ if (relayerPubkeyBytes.length !== 32) {
2125
+ throw new Error(`Invalid relayer public key length: ${relayerPubkeyBytes.length} (expected 32)`);
2126
+ }
2127
+ const relayerPubkey = await subtle.importKey(
2128
+ "raw",
2129
+ relayerPubkeyBytes.buffer,
2130
+ { name: "X25519" },
2131
+ false,
2132
+ []
2133
+ );
2134
+ const ephemeralKeyPair = await subtle.generateKey(
2135
+ { name: "X25519" },
2136
+ true,
2137
+ ["deriveBits"]
2138
+ );
2139
+ const sharedBits = await subtle.deriveBits(
2140
+ { name: "X25519", public: relayerPubkey },
2141
+ ephemeralKeyPair.privateKey,
2142
+ 256
2143
+ );
2144
+ const sharedKey = await subtle.importKey(
2145
+ "raw",
2146
+ sharedBits,
2147
+ { name: "HKDF" },
2148
+ false,
2149
+ ["deriveKey"]
2150
+ );
2151
+ const aesKey = await subtle.deriveKey(
2152
+ {
2153
+ name: "HKDF",
2154
+ hash: "SHA-256",
2155
+ salt: new ArrayBuffer(0),
2156
+ info: new TextEncoder().encode(HKDF_INFO)
2157
+ },
2158
+ sharedKey,
2159
+ { name: "AES-GCM", length: 256 },
2160
+ false,
2161
+ ["encrypt"]
2162
+ );
2163
+ const nonce = getRandomBytes(12);
2164
+ const plaintext = new TextEncoder().encode(JSON.stringify(payload));
2165
+ const ciphertext = await subtle.encrypt(
2166
+ { name: "AES-GCM", iv: nonce },
2167
+ aesKey,
2168
+ plaintext
2169
+ );
2170
+ const ephPubRaw = await subtle.exportKey("raw", ephemeralKeyPair.publicKey);
2171
+ return {
2172
+ ephemeral_pubkey: bytesToHex(new Uint8Array(ephPubRaw)),
2173
+ ciphertext: bytesToBase64(new Uint8Array(ciphertext)),
2174
+ nonce: bytesToHex(nonce),
2175
+ version: ECIES_VERSION
2176
+ };
2177
+ }
2178
+ function getCrypto() {
2179
+ if (typeof globalThis.crypto?.subtle !== "undefined") {
2180
+ return globalThis.crypto.subtle;
2181
+ }
2182
+ try {
2183
+ const { webcrypto } = require("crypto");
2184
+ return webcrypto.subtle;
2185
+ } catch {
2186
+ throw new Error("ECIES requires Web Crypto API (Node 20+ or a modern browser)");
2187
+ }
2188
+ }
2189
+ function getRandomBytes(n) {
2190
+ if (typeof globalThis.crypto?.getRandomValues !== "undefined") {
2191
+ return globalThis.crypto.getRandomValues(new Uint8Array(n));
2192
+ }
2193
+ const { randomBytes: randomBytes2 } = require("crypto");
2194
+ return new Uint8Array(randomBytes2(n));
2195
+ }
2196
+ function hexToBytes(hex) {
2197
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
2198
+ const bytes = new Uint8Array(clean.length / 2);
2199
+ for (let i = 0; i < bytes.length; i++) {
2200
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
2201
+ }
2202
+ return bytes;
2203
+ }
2204
+ function bytesToHex(bytes) {
2205
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
2206
+ }
2207
+ function bytesToBase64(bytes) {
2208
+ if (typeof btoa === "function") {
2209
+ return btoa(String.fromCharCode(...bytes));
2210
+ }
2211
+ return Buffer.from(bytes).toString("base64");
2212
+ }
2213
+
2214
+ // src/obelysk/denominations.ts
2215
+ var WBTC_DENOMINATIONS = [
2216
+ 50000n,
2217
+ // 0.0005 BTC
2218
+ 100000n,
2219
+ // 0.001 BTC
2220
+ 500000n,
2221
+ // 0.005 BTC
2222
+ 1000000n,
2223
+ // 0.01 BTC
2224
+ 5000000n,
2225
+ // 0.05 BTC
2226
+ 10000000n
2227
+ // 0.1 BTC
2228
+ ];
2229
+ var SAGE_DENOMINATIONS = [
2230
+ 10000000000000000n,
2231
+ // 0.01 SAGE
2232
+ 50000000000000000n,
2233
+ // 0.05 SAGE
2234
+ 100000000000000000n,
2235
+ // 0.1 SAGE
2236
+ 500000000000000000n,
2237
+ // 0.5 SAGE
2238
+ 1000000000000000000n,
2239
+ // 1 SAGE
2240
+ 5000000000000000000n
2241
+ // 5 SAGE
2242
+ ];
2243
+ var ETH_DENOMINATIONS = [
2244
+ 1000000000000000n,
2245
+ // 0.001 ETH
2246
+ 5000000000000000n,
2247
+ // 0.005 ETH
2248
+ 10000000000000000n,
2249
+ // 0.01 ETH
2250
+ 50000000000000000n,
2251
+ // 0.05 ETH
2252
+ 100000000000000000n,
2253
+ // 0.1 ETH
2254
+ 500000000000000000n
2255
+ // 0.5 ETH
2256
+ ];
2257
+ var STRK_DENOMINATIONS = [
2258
+ 50000000000000000n,
2259
+ // 0.05 STRK
2260
+ 100000000000000000n,
2261
+ // 0.1 STRK
2262
+ 500000000000000000n,
2263
+ // 0.5 STRK
2264
+ 1000000000000000000n,
2265
+ // 1 STRK
2266
+ 5000000000000000000n
2267
+ // 5 STRK
2268
+ ];
2269
+ var USDC_DENOMINATIONS = [
2270
+ 1000000n,
2271
+ // 1 USDC
2272
+ 5000000n,
2273
+ // 5 USDC
2274
+ 10000000n,
2275
+ // 10 USDC
2276
+ 50000000n,
2277
+ // 50 USDC
2278
+ 100000000n,
2279
+ // 100 USDC
2280
+ 500000000n
2281
+ // 500 USDC
2282
+ ];
2283
+ var VM31_DENOMINATIONS = {
2284
+ 0: WBTC_DENOMINATIONS,
2285
+ 1: SAGE_DENOMINATIONS,
2286
+ 2: ETH_DENOMINATIONS,
2287
+ 3: STRK_DENOMINATIONS,
2288
+ 4: USDC_DENOMINATIONS
2289
+ };
2290
+ var DENOMINATION_BY_SYMBOL = {
2291
+ wbtc: WBTC_DENOMINATIONS,
2292
+ sage: SAGE_DENOMINATIONS,
2293
+ eth: ETH_DENOMINATIONS,
2294
+ strk: STRK_DENOMINATIONS,
2295
+ usdc: USDC_DENOMINATIONS
2296
+ };
2297
+ function validateDenomination(amount, assetIdOrSymbol) {
2298
+ let denoms;
2299
+ if (typeof assetIdOrSymbol === "number") {
2300
+ denoms = VM31_DENOMINATIONS[assetIdOrSymbol];
2301
+ } else {
2302
+ denoms = DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
2303
+ }
2304
+ if (!denoms) return;
2305
+ if (!denoms.includes(amount)) {
2306
+ throw new Error(
2307
+ `Deposit must use a standard denomination for asset ${assetIdOrSymbol}. Got ${amount}. Valid: [${denoms.join(", ")}]`
2308
+ );
2309
+ }
2310
+ }
2311
+ function splitIntoDenominations(amount, assetIdOrSymbol) {
2312
+ let denoms;
2313
+ if (typeof assetIdOrSymbol === "number") {
2314
+ denoms = VM31_DENOMINATIONS[assetIdOrSymbol];
2315
+ } else {
2316
+ denoms = DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
2317
+ }
2318
+ if (!denoms) return { denominations: [amount], remainder: 0n };
2319
+ const sorted = [...denoms].sort((a, b) => b > a ? 1 : b < a ? -1 : 0);
2320
+ const result = [];
2321
+ let remaining = amount;
2322
+ for (const denom of sorted) {
2323
+ while (remaining >= denom) {
2324
+ result.push(denom);
2325
+ remaining -= denom;
2326
+ }
2327
+ }
2328
+ return { denominations: result, remainder: remaining };
2329
+ }
2330
+ function getDenominations(assetIdOrSymbol) {
2331
+ if (typeof assetIdOrSymbol === "number") {
2332
+ return VM31_DENOMINATIONS[assetIdOrSymbol];
2333
+ }
2334
+ return DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
2335
+ }
2336
+
2107
2337
  // src/obelysk/vm31Vault.ts
2108
2338
  var VM31VaultClient = class {
2109
2339
  constructor(obelysk) {
@@ -2118,6 +2348,8 @@ var VM31VaultClient = class {
2118
2348
  get relayerApiKey() {
2119
2349
  return this.obelysk.relayerApiKey;
2120
2350
  }
2351
+ /** Cached relayer X25519 public key (fetched on first encrypted submit) */
2352
+ _relayerPubkey = null;
2121
2353
  async relayerFetch(path, init) {
2122
2354
  const url = `${this.relayerUrl}${path}`;
2123
2355
  const headers = { "Content-Type": "application/json" };
@@ -2129,6 +2361,27 @@ var VM31VaultClient = class {
2129
2361
  }
2130
2362
  return res.json();
2131
2363
  }
2364
+ /**
2365
+ * Submit a payload to the relayer, encrypted with ECIES.
2366
+ * Falls back to plaintext if `encrypt: false` is passed.
2367
+ */
2368
+ async relayerSubmit(payload, encrypt = true) {
2369
+ if (!encrypt) {
2370
+ return this.relayerFetch("/submit", {
2371
+ method: "POST",
2372
+ body: JSON.stringify(payload)
2373
+ });
2374
+ }
2375
+ if (!this._relayerPubkey) {
2376
+ const keyInfo = await this.getRelayerPublicKey();
2377
+ this._relayerPubkey = keyInfo.publicKey;
2378
+ }
2379
+ const envelope = await eciesEncrypt(payload, this._relayerPubkey);
2380
+ return this.relayerFetch("/submit", {
2381
+ method: "POST",
2382
+ body: JSON.stringify(envelope)
2383
+ });
2384
+ }
2132
2385
  // --------------------------------------------------------------------------
2133
2386
  // On-chain reads (callContract to vm31_pool)
2134
2387
  // --------------------------------------------------------------------------
@@ -2292,61 +2545,65 @@ var VM31VaultClient = class {
2292
2545
  algorithm: data.algorithm
2293
2546
  };
2294
2547
  }
2295
- /** Submit a deposit transaction to the relayer */
2296
- async submitDeposit(params) {
2297
- return this.relayerFetch("/submit", {
2298
- method: "POST",
2299
- body: JSON.stringify({
2300
- type: "deposit",
2301
- amount: Number(params.amount),
2302
- asset_id: params.assetId,
2303
- recipient_pubkey: params.recipientPubkey,
2304
- recipient_viewing_key: params.recipientViewingKey
2305
- })
2306
- });
2307
- }
2308
- /** Submit a withdrawal transaction to the relayer */
2309
- async submitWithdraw(params) {
2310
- return this.relayerFetch("/submit", {
2311
- method: "POST",
2312
- body: JSON.stringify({
2313
- type: "withdraw",
2314
- amount: Number(params.amount),
2315
- asset_id: params.assetId,
2316
- note: {
2317
- owner_pubkey: params.note.owner_pubkey,
2318
- asset_id: params.note.asset_id,
2319
- amount_lo: params.note.amount_lo,
2320
- amount_hi: params.note.amount_hi,
2321
- blinding: params.note.blinding
2322
- },
2323
- spending_key: params.spendingKey,
2324
- merkle_path: params.merklePath,
2325
- merkle_root: params.merkleRoot,
2326
- withdrawal_binding: params.withdrawalBinding,
2327
- binding_salt: params.bindingSalt
2328
- })
2329
- });
2330
- }
2331
- /** Submit a private transfer transaction to the relayer */
2332
- async submitTransfer(params) {
2333
- return this.relayerFetch("/submit", {
2334
- method: "POST",
2335
- body: JSON.stringify({
2336
- type: "transfer",
2337
- amount: Number(params.amount),
2338
- asset_id: params.assetId,
2339
- recipient_pubkey: params.recipientPubkey,
2340
- recipient_viewing_key: params.recipientViewingKey,
2341
- sender_viewing_key: params.senderViewingKey,
2342
- input_notes: params.inputNotes.map((n) => ({
2343
- note: n.note,
2344
- spending_key: n.spendingKey,
2345
- merkle_path: n.merklePath
2346
- })),
2347
- merkle_root: params.merkleRoot
2348
- })
2349
- });
2548
+ /**
2549
+ * Submit a deposit transaction to the relayer.
2550
+ * Validates denomination (privacy gap #7) and encrypts with ECIES by default.
2551
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
2552
+ */
2553
+ async submitDeposit(params, encrypt = true) {
2554
+ validateDenomination(params.amount, params.assetId);
2555
+ return this.relayerSubmit({
2556
+ type: "deposit",
2557
+ amount: Number(params.amount),
2558
+ asset_id: params.assetId,
2559
+ recipient_pubkey: params.recipientPubkey,
2560
+ recipient_viewing_key: params.recipientViewingKey
2561
+ }, encrypt);
2562
+ }
2563
+ /**
2564
+ * Submit a withdrawal transaction to the relayer.
2565
+ * Withdrawals are not denomination-restricted.
2566
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
2567
+ */
2568
+ async submitWithdraw(params, encrypt = true) {
2569
+ return this.relayerSubmit({
2570
+ type: "withdraw",
2571
+ amount: Number(params.amount),
2572
+ asset_id: params.assetId,
2573
+ note: {
2574
+ owner_pubkey: params.note.owner_pubkey,
2575
+ asset_id: params.note.asset_id,
2576
+ amount_lo: params.note.amount_lo,
2577
+ amount_hi: params.note.amount_hi,
2578
+ blinding: params.note.blinding
2579
+ },
2580
+ spending_key: params.spendingKey,
2581
+ merkle_path: params.merklePath,
2582
+ merkle_root: params.merkleRoot,
2583
+ withdrawal_binding: params.withdrawalBinding,
2584
+ binding_salt: params.bindingSalt
2585
+ }, encrypt);
2586
+ }
2587
+ /**
2588
+ * Submit a private transfer transaction to the relayer.
2589
+ * Transfers are not denomination-restricted.
2590
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
2591
+ */
2592
+ async submitTransfer(params, encrypt = true) {
2593
+ return this.relayerSubmit({
2594
+ type: "transfer",
2595
+ amount: Number(params.amount),
2596
+ asset_id: params.assetId,
2597
+ recipient_pubkey: params.recipientPubkey,
2598
+ recipient_viewing_key: params.recipientViewingKey,
2599
+ sender_viewing_key: params.senderViewingKey,
2600
+ input_notes: params.inputNotes.map((n) => ({
2601
+ note: n.note,
2602
+ spending_key: n.spendingKey,
2603
+ merkle_path: n.merklePath
2604
+ })),
2605
+ merkle_root: params.merkleRoot
2606
+ }, encrypt);
2350
2607
  }
2351
2608
  /** Query batch info from the relayer */
2352
2609
  async queryBatch(batchId) {
@@ -3064,7 +3321,9 @@ var ObelyskClient = class {
3064
3321
  CURVE_ORDER,
3065
3322
  ConfidentialTransferClient,
3066
3323
  DARKPOOL_ASSET_IDS,
3324
+ DENOMINATION_BY_SYMBOL,
3067
3325
  DarkPoolClient,
3326
+ ETH_DENOMINATIONS,
3068
3327
  FIELD_PRIME,
3069
3328
  GENERATOR_G,
3070
3329
  GENERATOR_H,
@@ -3079,25 +3338,34 @@ var ObelyskClient = class {
3079
3338
  PrivacyPoolClient,
3080
3339
  PrivacyRouterClient,
3081
3340
  ProverStakingClient,
3341
+ SAGE_DENOMINATIONS,
3342
+ STRK_DENOMINATIONS,
3082
3343
  ShieldedSwapClient,
3083
3344
  StealthClient,
3084
3345
  TOKEN_DECIMALS,
3346
+ USDC_DENOMINATIONS,
3085
3347
  VM31BridgeClient,
3086
3348
  VM31VaultClient,
3087
3349
  VM31_ASSET_IDS,
3350
+ VM31_DENOMINATIONS,
3351
+ WBTC_DENOMINATIONS,
3088
3352
  commitmentToHash,
3089
3353
  createAEHint,
3090
3354
  createEncryptionProof,
3091
3355
  deriveNullifier,
3092
3356
  ecAdd,
3093
3357
  ecMul,
3358
+ eciesEncrypt,
3094
3359
  elgamalEncrypt,
3095
3360
  formatAmount,
3096
3361
  getContracts,
3362
+ getDenominations,
3097
3363
  getRpcUrl,
3098
3364
  mod,
3099
3365
  modInverse,
3100
3366
  parseAmount,
3101
3367
  pedersenCommit,
3102
- randomScalar
3368
+ randomScalar,
3369
+ splitIntoDenominations,
3370
+ validateDenomination
3103
3371
  });