@xtr-dev/rondevu-server 0.5.13 → 0.5.15

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/index.js CHANGED
@@ -29,320 +29,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  mod
30
30
  ));
31
31
 
32
- // src/crypto.ts
33
- var crypto_exports = {};
34
- __export(crypto_exports, {
35
- buildSignatureMessage: () => buildSignatureMessage,
36
- decryptSecret: () => decryptSecret,
37
- encryptSecret: () => encryptSecret,
38
- generateCredentialName: () => generateCredentialName,
39
- generateSecret: () => generateSecret,
40
- generateSignature: () => generateSignature,
41
- validateTag: () => validateTag,
42
- validateTags: () => validateTags,
43
- validateUsername: () => validateUsername,
44
- verifySignature: () => verifySignature
45
- });
46
- function generateCredentialName() {
47
- const adjectives = [
48
- "brave",
49
- "calm",
50
- "eager",
51
- "fancy",
52
- "gentle",
53
- "happy",
54
- "jolly",
55
- "kind",
56
- "lively",
57
- "merry",
58
- "nice",
59
- "proud",
60
- "quiet",
61
- "swift",
62
- "witty",
63
- "young",
64
- "bright",
65
- "clever",
66
- "daring",
67
- "fair",
68
- "grand",
69
- "humble",
70
- "noble",
71
- "quick"
72
- ];
73
- const nouns = [
74
- "tiger",
75
- "eagle",
76
- "river",
77
- "mountain",
78
- "ocean",
79
- "forest",
80
- "desert",
81
- "valley",
82
- "thunder",
83
- "wind",
84
- "fire",
85
- "stone",
86
- "cloud",
87
- "star",
88
- "moon",
89
- "sun",
90
- "wolf",
91
- "bear",
92
- "hawk",
93
- "lion",
94
- "fox",
95
- "deer",
96
- "owl",
97
- "swan"
98
- ];
99
- const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
100
- const noun = nouns[Math.floor(Math.random() * nouns.length)];
101
- const random = crypto.getRandomValues(new Uint8Array(8));
102
- const hex = Array.from(random).map((b) => b.toString(16).padStart(2, "0")).join("");
103
- return `${adjective}-${noun}-${hex}`;
104
- }
105
- function generateSecret() {
106
- const bytes = crypto.getRandomValues(new Uint8Array(32));
107
- const secret = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
108
- if (secret.length !== 64) {
109
- throw new Error("Secret generation failed: invalid length");
110
- }
111
- for (let i = 0; i < secret.length; i++) {
112
- const c = secret[i];
113
- if ((c < "0" || c > "9") && (c < "a" || c > "f")) {
114
- throw new Error(`Secret generation failed: invalid hex character at position ${i}: '${c}'`);
115
- }
116
- }
117
- return secret;
118
- }
119
- function hexToBytes(hex) {
120
- if (hex.length % 2 !== 0) {
121
- throw new Error("Hex string must have even length");
122
- }
123
- for (let i = 0; i < hex.length; i++) {
124
- const c = hex[i].toLowerCase();
125
- if ((c < "0" || c > "9") && (c < "a" || c > "f")) {
126
- throw new Error(`Invalid hex character at position ${i}: '${hex[i]}'`);
127
- }
128
- }
129
- const match = hex.match(/.{1,2}/g);
130
- if (!match) {
131
- throw new Error("Invalid hex string format");
132
- }
133
- return new Uint8Array(match.map((byte) => {
134
- const parsed = parseInt(byte, 16);
135
- if (isNaN(parsed)) {
136
- throw new Error(`Invalid hex byte: ${byte}`);
137
- }
138
- return parsed;
139
- }));
140
- }
141
- async function encryptSecret(secret, masterKeyHex) {
142
- if (!masterKeyHex || masterKeyHex.length !== 64) {
143
- throw new Error("Master key must be 64-character hex string (32 bytes)");
144
- }
145
- const keyBytes = hexToBytes(masterKeyHex);
146
- const key = await crypto.subtle.importKey(
147
- "raw",
148
- keyBytes,
149
- { name: "AES-GCM", length: 256 },
150
- false,
151
- ["encrypt"]
152
- );
153
- const iv = crypto.getRandomValues(new Uint8Array(12));
154
- const encoder = new TextEncoder();
155
- const secretBytes = encoder.encode(secret);
156
- const ciphertext = await crypto.subtle.encrypt(
157
- { name: "AES-GCM", iv, tagLength: 128 },
158
- key,
159
- secretBytes
160
- );
161
- const ivHex = Array.from(iv).map((b) => b.toString(16).padStart(2, "0")).join("");
162
- const ciphertextHex = Array.from(new Uint8Array(ciphertext)).map((b) => b.toString(16).padStart(2, "0")).join("");
163
- return `${ivHex}:${ciphertextHex}`;
164
- }
165
- async function decryptSecret(encryptedSecret, masterKeyHex) {
166
- if (!masterKeyHex || masterKeyHex.length !== 64) {
167
- throw new Error("Master key must be 64-character hex string (32 bytes)");
168
- }
169
- const parts = encryptedSecret.split(":");
170
- if (parts.length !== 2) {
171
- throw new Error("Invalid encrypted secret format (expected iv:ciphertext)");
172
- }
173
- const [ivHex, ciphertextHex] = parts;
174
- if (ivHex.length !== 24) {
175
- throw new Error("Invalid IV length (expected 12 bytes = 24 hex characters)");
176
- }
177
- if (ciphertextHex.length < 32) {
178
- throw new Error("Invalid ciphertext length (must include 16-byte auth tag)");
179
- }
180
- const iv = hexToBytes(ivHex);
181
- const ciphertext = hexToBytes(ciphertextHex);
182
- const keyBytes = hexToBytes(masterKeyHex);
183
- const key = await crypto.subtle.importKey(
184
- "raw",
185
- keyBytes,
186
- { name: "AES-GCM", length: 256 },
187
- false,
188
- ["decrypt"]
189
- );
190
- const decryptedBytes = await crypto.subtle.decrypt(
191
- { name: "AES-GCM", iv, tagLength: 128 },
192
- key,
193
- ciphertext
194
- );
195
- const decoder = new TextDecoder();
196
- return decoder.decode(decryptedBytes);
197
- }
198
- async function generateSignature(secret, message) {
199
- const secretBytes = hexToBytes(secret);
200
- const key = await crypto.subtle.importKey(
201
- "raw",
202
- secretBytes,
203
- { name: "HMAC", hash: "SHA-256" },
204
- false,
205
- ["sign"]
206
- );
207
- const encoder = new TextEncoder();
208
- const messageBytes = encoder.encode(message);
209
- const signatureBytes = await crypto.subtle.sign("HMAC", key, messageBytes);
210
- return import_node_buffer.Buffer.from(signatureBytes).toString("base64");
211
- }
212
- async function verifySignature(secret, message, signature) {
213
- try {
214
- const secretBytes = hexToBytes(secret);
215
- const key = await crypto.subtle.importKey(
216
- "raw",
217
- secretBytes,
218
- { name: "HMAC", hash: "SHA-256" },
219
- false,
220
- ["verify"]
221
- );
222
- const encoder = new TextEncoder();
223
- const messageBytes = encoder.encode(message);
224
- const signatureBytes = import_node_buffer.Buffer.from(signature, "base64");
225
- return await crypto.subtle.verify("HMAC", key, signatureBytes, messageBytes);
226
- } catch (error) {
227
- console.error("Signature verification error:", error);
228
- return false;
229
- }
230
- }
231
- function canonicalJSON(obj, depth = 0) {
232
- const MAX_DEPTH = 100;
233
- if (depth > MAX_DEPTH) {
234
- throw new Error("Object nesting too deep for canonicalization");
235
- }
236
- if (obj === null) return "null";
237
- if (obj === void 0) return JSON.stringify(void 0);
238
- const type = typeof obj;
239
- if (type === "function") throw new Error("Functions are not supported in RPC parameters");
240
- if (type === "symbol" || type === "bigint") throw new Error(`${type} is not supported in RPC parameters`);
241
- if (type === "number" && !Number.isFinite(obj)) throw new Error("NaN and Infinity are not supported in RPC parameters");
242
- if (type !== "object") return JSON.stringify(obj);
243
- if (Array.isArray(obj)) {
244
- return "[" + obj.map((item) => canonicalJSON(item, depth + 1)).join(",") + "]";
245
- }
246
- const sortedKeys = Object.keys(obj).sort();
247
- const pairs = sortedKeys.map((key) => JSON.stringify(key) + ":" + canonicalJSON(obj[key], depth + 1));
248
- return "{" + pairs.join(",") + "}";
249
- }
250
- function buildSignatureMessage(timestamp, nonce, method, params) {
251
- if (nonce.length !== 36) {
252
- throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
253
- }
254
- if (nonce[8] !== "-" || nonce[13] !== "-" || nonce[18] !== "-" || nonce[23] !== "-") {
255
- throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
256
- }
257
- if (nonce[14] !== "4") {
258
- throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
259
- }
260
- const variant = nonce[19].toLowerCase();
261
- if (variant !== "8" && variant !== "9" && variant !== "a" && variant !== "b") {
262
- throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
263
- }
264
- const hexChars = nonce.replace(/-/g, "");
265
- for (let i = 0; i < hexChars.length; i++) {
266
- const c = hexChars[i].toLowerCase();
267
- if ((c < "0" || c > "9") && (c < "a" || c > "f")) {
268
- throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
269
- }
270
- }
271
- const paramsStr = canonicalJSON(params || {});
272
- return `${timestamp}:${nonce}:${method}:${paramsStr}`;
273
- }
274
- function validateUsername(username) {
275
- if (typeof username !== "string") {
276
- return { valid: false, error: "Username must be a string" };
277
- }
278
- if (username.length < USERNAME_MIN_LENGTH) {
279
- return { valid: false, error: `Username must be at least ${USERNAME_MIN_LENGTH} characters` };
280
- }
281
- if (username.length > USERNAME_MAX_LENGTH) {
282
- return { valid: false, error: `Username must be at most ${USERNAME_MAX_LENGTH} characters` };
283
- }
284
- if (!USERNAME_REGEX.test(username)) {
285
- return { valid: false, error: "Username must be lowercase alphanumeric with optional dashes/periods, and start/end with alphanumeric" };
286
- }
287
- return { valid: true };
288
- }
289
- function validateTag(tag) {
290
- if (typeof tag !== "string") {
291
- return { valid: false, error: "Tag must be a string" };
292
- }
293
- if (tag.length < TAG_MIN_LENGTH) {
294
- return { valid: false, error: `Tag must be at least ${TAG_MIN_LENGTH} character` };
295
- }
296
- if (tag.length > TAG_MAX_LENGTH) {
297
- return { valid: false, error: `Tag must be at most ${TAG_MAX_LENGTH} characters` };
298
- }
299
- if (tag.length === 1) {
300
- if (!/^[a-z0-9]$/.test(tag)) {
301
- return { valid: false, error: "Tag must be lowercase alphanumeric" };
302
- }
303
- return { valid: true };
304
- }
305
- if (!TAG_REGEX.test(tag)) {
306
- return { valid: false, error: "Tag must be lowercase alphanumeric with optional dots/dashes, and start/end with alphanumeric" };
307
- }
308
- return { valid: true };
309
- }
310
- function validateTags(tags, maxTags = 20) {
311
- if (!Array.isArray(tags)) {
312
- return { valid: false, error: "Tags must be an array" };
313
- }
314
- if (tags.length === 0) {
315
- return { valid: false, error: "At least one tag is required" };
316
- }
317
- if (tags.length > maxTags) {
318
- return { valid: false, error: `Maximum ${maxTags} tags allowed` };
319
- }
320
- for (let i = 0; i < tags.length; i++) {
321
- const result = validateTag(tags[i]);
322
- if (!result.valid) {
323
- return { valid: false, error: `Tag ${i + 1}: ${result.error}` };
324
- }
325
- }
326
- const uniqueTags = new Set(tags);
327
- if (uniqueTags.size !== tags.length) {
328
- return { valid: false, error: "Duplicate tags are not allowed" };
329
- }
330
- return { valid: true };
331
- }
332
- var import_node_buffer, USERNAME_REGEX, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH, TAG_MIN_LENGTH, TAG_MAX_LENGTH, TAG_REGEX;
333
- var init_crypto = __esm({
334
- "src/crypto.ts"() {
335
- "use strict";
336
- import_node_buffer = require("node:buffer");
337
- USERNAME_REGEX = /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/;
338
- USERNAME_MIN_LENGTH = 4;
339
- USERNAME_MAX_LENGTH = 32;
340
- TAG_MIN_LENGTH = 1;
341
- TAG_MAX_LENGTH = 64;
342
- TAG_REGEX = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/;
343
- }
344
- });
345
-
346
32
  // src/storage/hash-id.ts
347
33
  async function generateOfferHash(sdp) {
348
34
  const randomBytes = crypto.getRandomValues(new Uint8Array(8));
@@ -371,31 +57,28 @@ var memory_exports = {};
371
57
  __export(memory_exports, {
372
58
  MemoryStorage: () => MemoryStorage
373
59
  });
374
- var YEAR_IN_MS, MemoryStorage;
60
+ var MemoryStorage;
375
61
  var init_memory = __esm({
376
62
  "src/storage/memory.ts"() {
377
63
  "use strict";
378
64
  init_hash_id();
379
- YEAR_IN_MS = 365 * 24 * 60 * 60 * 1e3;
380
65
  MemoryStorage = class {
381
- constructor(masterEncryptionKey) {
66
+ constructor() {
382
67
  // Primary storage
383
- this.credentials = /* @__PURE__ */ new Map();
384
68
  this.offers = /* @__PURE__ */ new Map();
385
69
  this.iceCandidates = /* @__PURE__ */ new Map();
386
70
  // offerId → candidates
387
71
  this.rateLimits = /* @__PURE__ */ new Map();
388
72
  this.nonces = /* @__PURE__ */ new Map();
389
73
  // Secondary indexes for efficient lookups
390
- this.offersByUsername = /* @__PURE__ */ new Map();
391
- // username → offer IDs
74
+ this.offersByPublicKey = /* @__PURE__ */ new Map();
75
+ // publicKey → offer IDs
392
76
  this.offersByTag = /* @__PURE__ */ new Map();
393
77
  // tag → offer IDs
394
78
  this.offersByAnswerer = /* @__PURE__ */ new Map();
395
- // answerer username → offer IDs
79
+ // answerer publicKey → offer IDs
396
80
  // Auto-increment counter for ICE candidates
397
81
  this.iceCandidateIdCounter = 0;
398
- this.masterEncryptionKey = masterEncryptionKey;
399
82
  }
400
83
  // ===== Offer Management =====
401
84
  async createOffers(offers) {
@@ -405,7 +88,7 @@ var init_memory = __esm({
405
88
  const id = request.id || await generateOfferHash(request.sdp);
406
89
  const offer = {
407
90
  id,
408
- username: request.username,
91
+ publicKey: request.publicKey,
409
92
  tags: request.tags,
410
93
  sdp: request.sdp,
411
94
  createdAt: now,
@@ -413,10 +96,10 @@ var init_memory = __esm({
413
96
  lastSeen: now
414
97
  };
415
98
  this.offers.set(id, offer);
416
- if (!this.offersByUsername.has(request.username)) {
417
- this.offersByUsername.set(request.username, /* @__PURE__ */ new Set());
99
+ if (!this.offersByPublicKey.has(request.publicKey)) {
100
+ this.offersByPublicKey.set(request.publicKey, /* @__PURE__ */ new Set());
418
101
  }
419
- this.offersByUsername.get(request.username).add(id);
102
+ this.offersByPublicKey.get(request.publicKey).add(id);
420
103
  for (const tag of request.tags) {
421
104
  if (!this.offersByTag.has(tag)) {
422
105
  this.offersByTag.set(tag, /* @__PURE__ */ new Set());
@@ -427,9 +110,9 @@ var init_memory = __esm({
427
110
  }
428
111
  return created;
429
112
  }
430
- async getOffersByUsername(username) {
113
+ async getOffersByPublicKey(publicKey) {
431
114
  const now = Date.now();
432
- const offerIds = this.offersByUsername.get(username);
115
+ const offerIds = this.offersByPublicKey.get(publicKey);
433
116
  if (!offerIds) return [];
434
117
  const offers = [];
435
118
  for (const id of offerIds) {
@@ -447,9 +130,9 @@ var init_memory = __esm({
447
130
  }
448
131
  return offer;
449
132
  }
450
- async deleteOffer(offerId, ownerUsername) {
133
+ async deleteOffer(offerId, ownerPublicKey) {
451
134
  const offer = this.offers.get(offerId);
452
- if (!offer || offer.username !== ownerUsername) {
135
+ if (!offer || offer.publicKey !== ownerPublicKey) {
453
136
  return false;
454
137
  }
455
138
  this.removeOfferFromIndexes(offer);
@@ -469,41 +152,41 @@ var init_memory = __esm({
469
152
  }
470
153
  return count;
471
154
  }
472
- async answerOffer(offerId, answererUsername, answerSdp, matchedTags) {
155
+ async answerOffer(offerId, answererPublicKey, answerSdp, matchedTags) {
473
156
  const offer = await this.getOfferById(offerId);
474
157
  if (!offer) {
475
158
  return { success: false, error: "Offer not found or expired" };
476
159
  }
477
- if (offer.answererUsername) {
160
+ if (offer.answererPublicKey) {
478
161
  return { success: false, error: "Offer already answered" };
479
162
  }
480
163
  const now = Date.now();
481
- offer.answererUsername = answererUsername;
164
+ offer.answererPublicKey = answererPublicKey;
482
165
  offer.answerSdp = answerSdp;
483
166
  offer.answeredAt = now;
484
167
  offer.matchedTags = matchedTags;
485
- if (!this.offersByAnswerer.has(answererUsername)) {
486
- this.offersByAnswerer.set(answererUsername, /* @__PURE__ */ new Set());
168
+ if (!this.offersByAnswerer.has(answererPublicKey)) {
169
+ this.offersByAnswerer.set(answererPublicKey, /* @__PURE__ */ new Set());
487
170
  }
488
- this.offersByAnswerer.get(answererUsername).add(offerId);
171
+ this.offersByAnswerer.get(answererPublicKey).add(offerId);
489
172
  return { success: true };
490
173
  }
491
- async getAnsweredOffers(offererUsername) {
174
+ async getAnsweredOffers(offererPublicKey) {
492
175
  const now = Date.now();
493
- const offerIds = this.offersByUsername.get(offererUsername);
176
+ const offerIds = this.offersByPublicKey.get(offererPublicKey);
494
177
  if (!offerIds) return [];
495
178
  const offers = [];
496
179
  for (const id of offerIds) {
497
180
  const offer = this.offers.get(id);
498
- if (offer && offer.answererUsername && offer.expiresAt > now) {
181
+ if (offer && offer.answererPublicKey && offer.expiresAt > now) {
499
182
  offers.push(offer);
500
183
  }
501
184
  }
502
185
  return offers.sort((a, b) => (b.answeredAt || 0) - (a.answeredAt || 0));
503
186
  }
504
- async getOffersAnsweredBy(answererUsername) {
187
+ async getOffersAnsweredBy(answererPublicKey) {
505
188
  const now = Date.now();
506
- const offerIds = this.offersByAnswerer.get(answererUsername);
189
+ const offerIds = this.offersByAnswerer.get(answererPublicKey);
507
190
  if (!offerIds) return [];
508
191
  const offers = [];
509
192
  for (const id of offerIds) {
@@ -515,7 +198,7 @@ var init_memory = __esm({
515
198
  return offers.sort((a, b) => (b.answeredAt || 0) - (a.answeredAt || 0));
516
199
  }
517
200
  // ===== Discovery =====
518
- async discoverOffers(tags, excludeUsername, limit, offset) {
201
+ async discoverOffers(tags, excludePublicKey, limit, offset) {
519
202
  if (tags.length === 0) return [];
520
203
  const now = Date.now();
521
204
  const matchingOfferIds = /* @__PURE__ */ new Set();
@@ -530,14 +213,14 @@ var init_memory = __esm({
530
213
  const offers = [];
531
214
  for (const id of matchingOfferIds) {
532
215
  const offer = this.offers.get(id);
533
- if (offer && offer.expiresAt > now && !offer.answererUsername && (!excludeUsername || offer.username !== excludeUsername)) {
216
+ if (offer && offer.expiresAt > now && !offer.answererPublicKey && (!excludePublicKey || offer.publicKey !== excludePublicKey)) {
534
217
  offers.push(offer);
535
218
  }
536
219
  }
537
220
  offers.sort((a, b) => b.createdAt - a.createdAt);
538
221
  return offers.slice(offset, offset + limit);
539
222
  }
540
- async getRandomOffer(tags, excludeUsername) {
223
+ async getRandomOffer(tags, excludePublicKey) {
541
224
  if (tags.length === 0) return null;
542
225
  const now = Date.now();
543
226
  const matchingOffers = [];
@@ -552,7 +235,7 @@ var init_memory = __esm({
552
235
  }
553
236
  for (const id of matchingOfferIds) {
554
237
  const offer = this.offers.get(id);
555
- if (offer && offer.expiresAt > now && !offer.answererUsername && (!excludeUsername || offer.username !== excludeUsername)) {
238
+ if (offer && offer.expiresAt > now && !offer.answererPublicKey && (!excludePublicKey || offer.publicKey !== excludePublicKey)) {
556
239
  matchingOffers.push(offer);
557
240
  }
558
241
  }
@@ -561,7 +244,7 @@ var init_memory = __esm({
561
244
  return matchingOffers[randomIndex];
562
245
  }
563
246
  // ===== ICE Candidate Management =====
564
- async addIceCandidates(offerId, username, role, candidates) {
247
+ async addIceCandidates(offerId, publicKey, role, candidates) {
565
248
  const baseTimestamp = Date.now();
566
249
  if (!this.iceCandidates.has(offerId)) {
567
250
  this.iceCandidates.set(offerId, []);
@@ -571,7 +254,7 @@ var init_memory = __esm({
571
254
  const candidate = {
572
255
  id: ++this.iceCandidateIdCounter,
573
256
  offerId,
574
- username,
257
+ publicKey,
575
258
  role,
576
259
  candidate: candidates[i],
577
260
  createdAt: baseTimestamp + i
@@ -584,7 +267,7 @@ var init_memory = __esm({
584
267
  const candidates = this.iceCandidates.get(offerId) || [];
585
268
  return candidates.filter((c) => c.role === targetRole && (since === void 0 || c.createdAt > since)).sort((a, b) => a.createdAt - b.createdAt);
586
269
  }
587
- async getIceCandidatesForMultipleOffers(offerIds, username, since) {
270
+ async getIceCandidatesForMultipleOffers(offerIds, publicKey, since) {
588
271
  const result = /* @__PURE__ */ new Map();
589
272
  if (offerIds.length === 0) return result;
590
273
  if (offerIds.length > 1e3) {
@@ -594,8 +277,8 @@ var init_memory = __esm({
594
277
  const offer = this.offers.get(offerId);
595
278
  if (!offer) continue;
596
279
  const candidates = this.iceCandidates.get(offerId) || [];
597
- const isOfferer = offer.username === username;
598
- const isAnswerer = offer.answererUsername === username;
280
+ const isOfferer = offer.publicKey === publicKey;
281
+ const isAnswerer = offer.answererPublicKey === publicKey;
599
282
  if (!isOfferer && !isAnswerer) continue;
600
283
  const targetRole = isOfferer ? "answerer" : "offerer";
601
284
  const filteredCandidates = candidates.filter((c) => c.role === targetRole && (since === void 0 || c.createdAt > since)).sort((a, b) => a.createdAt - b.createdAt);
@@ -605,79 +288,6 @@ var init_memory = __esm({
605
288
  }
606
289
  return result;
607
290
  }
608
- // ===== Credential Management =====
609
- async generateCredentials(request) {
610
- const now = Date.now();
611
- const expiresAt = request.expiresAt || now + YEAR_IN_MS;
612
- const { generateCredentialName: generateCredentialName2, generateSecret: generateSecret2, encryptSecret: encryptSecret2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
613
- let name;
614
- if (request.name) {
615
- if (this.credentials.has(request.name)) {
616
- throw new Error("Username already taken");
617
- }
618
- name = request.name;
619
- } else {
620
- let attempts = 0;
621
- const maxAttempts = 100;
622
- while (attempts < maxAttempts) {
623
- name = generateCredentialName2();
624
- if (!this.credentials.has(name)) break;
625
- attempts++;
626
- }
627
- if (attempts >= maxAttempts) {
628
- throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);
629
- }
630
- }
631
- const secret = generateSecret2();
632
- const encryptedSecret = await encryptSecret2(secret, this.masterEncryptionKey);
633
- const credential = {
634
- name,
635
- secret: encryptedSecret,
636
- createdAt: now,
637
- expiresAt,
638
- lastUsed: now
639
- };
640
- this.credentials.set(name, credential);
641
- return {
642
- ...credential,
643
- secret
644
- // Return plaintext, not encrypted
645
- };
646
- }
647
- async getCredential(name) {
648
- const credential = this.credentials.get(name);
649
- if (!credential || credential.expiresAt <= Date.now()) {
650
- return null;
651
- }
652
- try {
653
- const { decryptSecret: decryptSecret2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
654
- const decryptedSecret = await decryptSecret2(credential.secret, this.masterEncryptionKey);
655
- return {
656
- ...credential,
657
- secret: decryptedSecret
658
- };
659
- } catch (error) {
660
- console.error(`Failed to decrypt secret for credential '${name}':`, error);
661
- return null;
662
- }
663
- }
664
- async updateCredentialUsage(name, lastUsed, expiresAt) {
665
- const credential = this.credentials.get(name);
666
- if (credential) {
667
- credential.lastUsed = lastUsed;
668
- credential.expiresAt = expiresAt;
669
- }
670
- }
671
- async deleteExpiredCredentials(now) {
672
- let count = 0;
673
- for (const [name, credential] of this.credentials) {
674
- if (credential.expiresAt < now) {
675
- this.credentials.delete(name);
676
- count++;
677
- }
678
- }
679
- return count;
680
- }
681
291
  // ===== Rate Limiting =====
682
292
  async checkRateLimit(identifier, limit, windowMs) {
683
293
  const now = Date.now();
@@ -721,12 +331,11 @@ var init_memory = __esm({
721
331
  return count;
722
332
  }
723
333
  async close() {
724
- this.credentials.clear();
725
334
  this.offers.clear();
726
335
  this.iceCandidates.clear();
727
336
  this.rateLimits.clear();
728
337
  this.nonces.clear();
729
- this.offersByUsername.clear();
338
+ this.offersByPublicKey.clear();
730
339
  this.offersByTag.clear();
731
340
  this.offersByAnswerer.clear();
732
341
  }
@@ -734,24 +343,21 @@ var init_memory = __esm({
734
343
  async getOfferCount() {
735
344
  return this.offers.size;
736
345
  }
737
- async getOfferCountByUsername(username) {
738
- const offerIds = this.offersByUsername.get(username);
346
+ async getOfferCountByPublicKey(publicKey) {
347
+ const offerIds = this.offersByPublicKey.get(publicKey);
739
348
  return offerIds ? offerIds.size : 0;
740
349
  }
741
- async getCredentialCount() {
742
- return this.credentials.size;
743
- }
744
350
  async getIceCandidateCount(offerId) {
745
351
  const candidates = this.iceCandidates.get(offerId);
746
352
  return candidates ? candidates.length : 0;
747
353
  }
748
354
  // ===== Helper Methods =====
749
355
  removeOfferFromIndexes(offer) {
750
- const usernameOffers = this.offersByUsername.get(offer.username);
751
- if (usernameOffers) {
752
- usernameOffers.delete(offer.id);
753
- if (usernameOffers.size === 0) {
754
- this.offersByUsername.delete(offer.username);
356
+ const publicKeyOffers = this.offersByPublicKey.get(offer.publicKey);
357
+ if (publicKeyOffers) {
358
+ publicKeyOffers.delete(offer.id);
359
+ if (publicKeyOffers.size === 0) {
360
+ this.offersByPublicKey.delete(offer.publicKey);
755
361
  }
756
362
  }
757
363
  for (const tag of offer.tags) {
@@ -763,12 +369,12 @@ var init_memory = __esm({
763
369
  }
764
370
  }
765
371
  }
766
- if (offer.answererUsername) {
767
- const answererOffers = this.offersByAnswerer.get(offer.answererUsername);
372
+ if (offer.answererPublicKey) {
373
+ const answererOffers = this.offersByAnswerer.get(offer.answererPublicKey);
768
374
  if (answererOffers) {
769
375
  answererOffers.delete(offer.id);
770
376
  if (answererOffers.size === 0) {
771
- this.offersByAnswerer.delete(offer.answererUsername);
377
+ this.offersByAnswerer.delete(offer.answererPublicKey);
772
378
  }
773
379
  }
774
380
  }
@@ -782,54 +388,63 @@ var sqlite_exports = {};
782
388
  __export(sqlite_exports, {
783
389
  SQLiteStorage: () => SQLiteStorage
784
390
  });
785
- var import_better_sqlite3, YEAR_IN_MS2, SQLiteStorage;
391
+ var import_better_sqlite3, SQLiteStorage;
786
392
  var init_sqlite = __esm({
787
393
  "src/storage/sqlite.ts"() {
788
394
  "use strict";
789
395
  import_better_sqlite3 = __toESM(require("better-sqlite3"));
790
396
  init_hash_id();
791
- YEAR_IN_MS2 = 365 * 24 * 60 * 60 * 1e3;
792
397
  SQLiteStorage = class {
793
398
  /**
794
399
  * Creates a new SQLite storage instance
795
400
  * @param path Path to SQLite database file, or ':memory:' for in-memory database
796
- * @param masterEncryptionKey 64-char hex string for encrypting secrets (32 bytes)
797
401
  */
798
- constructor(path = ":memory:", masterEncryptionKey) {
402
+ constructor(path = ":memory:") {
799
403
  this.db = new import_better_sqlite3.default(path);
800
- this.masterEncryptionKey = masterEncryptionKey;
801
404
  this.initializeDatabase();
802
405
  }
803
406
  /**
804
- * Initializes database schema with tags-based offers
407
+ * Initializes database schema with Ed25519 public key identity
805
408
  */
806
409
  initializeDatabase() {
807
410
  this.db.exec(`
411
+ -- Identities table (Ed25519 public key as identity)
412
+ CREATE TABLE IF NOT EXISTS identities (
413
+ public_key TEXT PRIMARY KEY,
414
+ created_at INTEGER NOT NULL,
415
+ expires_at INTEGER NOT NULL,
416
+ last_used INTEGER NOT NULL,
417
+ CHECK(length(public_key) = 64)
418
+ );
419
+
420
+ CREATE INDEX IF NOT EXISTS idx_identities_expires ON identities(expires_at);
421
+
808
422
  -- WebRTC signaling offers with tags
809
423
  CREATE TABLE IF NOT EXISTS offers (
810
424
  id TEXT PRIMARY KEY,
811
- username TEXT NOT NULL,
425
+ public_key TEXT NOT NULL,
812
426
  tags TEXT NOT NULL,
813
427
  sdp TEXT NOT NULL,
814
428
  created_at INTEGER NOT NULL,
815
429
  expires_at INTEGER NOT NULL,
816
430
  last_seen INTEGER NOT NULL,
817
- answerer_username TEXT,
431
+ answerer_public_key TEXT,
818
432
  answer_sdp TEXT,
819
433
  answered_at INTEGER,
820
- matched_tags TEXT
434
+ matched_tags TEXT,
435
+ FOREIGN KEY (public_key) REFERENCES identities(public_key) ON DELETE CASCADE
821
436
  );
822
437
 
823
- CREATE INDEX IF NOT EXISTS idx_offers_username ON offers(username);
438
+ CREATE INDEX IF NOT EXISTS idx_offers_public_key ON offers(public_key);
824
439
  CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at);
825
440
  CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen);
826
- CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_username);
441
+ CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_public_key);
827
442
 
828
443
  -- ICE candidates table
829
444
  CREATE TABLE IF NOT EXISTS ice_candidates (
830
445
  id INTEGER PRIMARY KEY AUTOINCREMENT,
831
446
  offer_id TEXT NOT NULL,
832
- username TEXT NOT NULL,
447
+ public_key TEXT NOT NULL,
833
448
  role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')),
834
449
  candidate TEXT NOT NULL,
835
450
  created_at INTEGER NOT NULL,
@@ -837,22 +452,9 @@ var init_sqlite = __esm({
837
452
  );
838
453
 
839
454
  CREATE INDEX IF NOT EXISTS idx_ice_offer ON ice_candidates(offer_id);
840
- CREATE INDEX IF NOT EXISTS idx_ice_username ON ice_candidates(username);
455
+ CREATE INDEX IF NOT EXISTS idx_ice_public_key ON ice_candidates(public_key);
841
456
  CREATE INDEX IF NOT EXISTS idx_ice_created ON ice_candidates(created_at);
842
457
 
843
- -- Credentials table (replaces usernames with simpler name + secret auth)
844
- CREATE TABLE IF NOT EXISTS credentials (
845
- name TEXT PRIMARY KEY,
846
- secret TEXT NOT NULL UNIQUE,
847
- created_at INTEGER NOT NULL,
848
- expires_at INTEGER NOT NULL,
849
- last_used INTEGER NOT NULL,
850
- CHECK(length(name) >= 3 AND length(name) <= 32)
851
- );
852
-
853
- CREATE INDEX IF NOT EXISTS idx_credentials_expires ON credentials(expires_at);
854
- CREATE INDEX IF NOT EXISTS idx_credentials_secret ON credentials(secret);
855
-
856
458
  -- Rate limits table (for distributed rate limiting)
857
459
  CREATE TABLE IF NOT EXISTS rate_limits (
858
460
  identifier TEXT PRIMARY KEY,
@@ -883,14 +485,14 @@ var init_sqlite = __esm({
883
485
  );
884
486
  const transaction = this.db.transaction((offersWithIds2) => {
885
487
  const offerStmt = this.db.prepare(`
886
- INSERT INTO offers (id, username, tags, sdp, created_at, expires_at, last_seen)
488
+ INSERT INTO offers (id, public_key, tags, sdp, created_at, expires_at, last_seen)
887
489
  VALUES (?, ?, ?, ?, ?, ?, ?)
888
490
  `);
889
491
  for (const offer of offersWithIds2) {
890
492
  const now = Date.now();
891
493
  offerStmt.run(
892
494
  offer.id,
893
- offer.username,
495
+ offer.publicKey,
894
496
  JSON.stringify(offer.tags),
895
497
  offer.sdp,
896
498
  now,
@@ -899,7 +501,7 @@ var init_sqlite = __esm({
899
501
  );
900
502
  created.push({
901
503
  id: offer.id,
902
- username: offer.username,
504
+ publicKey: offer.publicKey,
903
505
  tags: offer.tags,
904
506
  sdp: offer.sdp,
905
507
  createdAt: now,
@@ -911,13 +513,13 @@ var init_sqlite = __esm({
911
513
  transaction(offersWithIds);
912
514
  return created;
913
515
  }
914
- async getOffersByUsername(username) {
516
+ async getOffersByPublicKey(publicKey) {
915
517
  const stmt = this.db.prepare(`
916
518
  SELECT * FROM offers
917
- WHERE username = ? AND expires_at > ?
519
+ WHERE public_key = ? AND expires_at > ?
918
520
  ORDER BY last_seen DESC
919
521
  `);
920
- const rows = stmt.all(username, Date.now());
522
+ const rows = stmt.all(publicKey, Date.now());
921
523
  return rows.map((row) => this.rowToOffer(row));
922
524
  }
923
525
  async getOfferById(offerId) {
@@ -931,12 +533,12 @@ var init_sqlite = __esm({
931
533
  }
932
534
  return this.rowToOffer(row);
933
535
  }
934
- async deleteOffer(offerId, ownerUsername) {
536
+ async deleteOffer(offerId, ownerPublicKey) {
935
537
  const stmt = this.db.prepare(`
936
538
  DELETE FROM offers
937
- WHERE id = ? AND username = ?
539
+ WHERE id = ? AND public_key = ?
938
540
  `);
939
- const result = stmt.run(offerId, ownerUsername);
541
+ const result = stmt.run(offerId, ownerPublicKey);
940
542
  return result.changes > 0;
941
543
  }
942
544
  async deleteExpiredOffers(now) {
@@ -944,7 +546,7 @@ var init_sqlite = __esm({
944
546
  const result = stmt.run(now);
945
547
  return result.changes;
946
548
  }
947
- async answerOffer(offerId, answererUsername, answerSdp, matchedTags) {
549
+ async answerOffer(offerId, answererPublicKey, answerSdp, matchedTags) {
948
550
  const offer = await this.getOfferById(offerId);
949
551
  if (!offer) {
950
552
  return {
@@ -952,7 +554,7 @@ var init_sqlite = __esm({
952
554
  error: "Offer not found or expired"
953
555
  };
954
556
  }
955
- if (offer.answererUsername) {
557
+ if (offer.answererPublicKey) {
956
558
  return {
957
559
  success: false,
958
560
  error: "Offer already answered"
@@ -960,11 +562,11 @@ var init_sqlite = __esm({
960
562
  }
961
563
  const stmt = this.db.prepare(`
962
564
  UPDATE offers
963
- SET answerer_username = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?
964
- WHERE id = ? AND answerer_username IS NULL
565
+ SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?
566
+ WHERE id = ? AND answerer_public_key IS NULL
965
567
  `);
966
568
  const matchedTagsJson = matchedTags ? JSON.stringify(matchedTags) : null;
967
- const result = stmt.run(answererUsername, answerSdp, Date.now(), matchedTagsJson, offerId);
569
+ const result = stmt.run(answererPublicKey, answerSdp, Date.now(), matchedTagsJson, offerId);
968
570
  if (result.changes === 0) {
969
571
  return {
970
572
  success: false,
@@ -973,26 +575,26 @@ var init_sqlite = __esm({
973
575
  }
974
576
  return { success: true };
975
577
  }
976
- async getAnsweredOffers(offererUsername) {
578
+ async getAnsweredOffers(offererPublicKey) {
977
579
  const stmt = this.db.prepare(`
978
580
  SELECT * FROM offers
979
- WHERE username = ? AND answerer_username IS NOT NULL AND expires_at > ?
581
+ WHERE public_key = ? AND answerer_public_key IS NOT NULL AND expires_at > ?
980
582
  ORDER BY answered_at DESC
981
583
  `);
982
- const rows = stmt.all(offererUsername, Date.now());
584
+ const rows = stmt.all(offererPublicKey, Date.now());
983
585
  return rows.map((row) => this.rowToOffer(row));
984
586
  }
985
- async getOffersAnsweredBy(answererUsername) {
587
+ async getOffersAnsweredBy(answererPublicKey) {
986
588
  const stmt = this.db.prepare(`
987
589
  SELECT * FROM offers
988
- WHERE answerer_username = ? AND expires_at > ?
590
+ WHERE answerer_public_key = ? AND expires_at > ?
989
591
  ORDER BY answered_at DESC
990
592
  `);
991
- const rows = stmt.all(answererUsername, Date.now());
593
+ const rows = stmt.all(answererPublicKey, Date.now());
992
594
  return rows.map((row) => this.rowToOffer(row));
993
595
  }
994
596
  // ===== Discovery =====
995
- async discoverOffers(tags, excludeUsername, limit, offset) {
597
+ async discoverOffers(tags, excludePublicKey, limit, offset) {
996
598
  if (tags.length === 0) {
997
599
  return [];
998
600
  }
@@ -1001,12 +603,12 @@ var init_sqlite = __esm({
1001
603
  SELECT DISTINCT o.* FROM offers o, json_each(o.tags) as t
1002
604
  WHERE t.value IN (${placeholders})
1003
605
  AND o.expires_at > ?
1004
- AND o.answerer_username IS NULL
606
+ AND o.answerer_public_key IS NULL
1005
607
  `;
1006
608
  const params = [...tags, Date.now()];
1007
- if (excludeUsername) {
1008
- query += " AND o.username != ?";
1009
- params.push(excludeUsername);
609
+ if (excludePublicKey) {
610
+ query += " AND o.public_key != ?";
611
+ params.push(excludePublicKey);
1010
612
  }
1011
613
  query += " ORDER BY o.created_at DESC LIMIT ? OFFSET ?";
1012
614
  params.push(limit, offset);
@@ -1014,7 +616,7 @@ var init_sqlite = __esm({
1014
616
  const rows = stmt.all(...params);
1015
617
  return rows.map((row) => this.rowToOffer(row));
1016
618
  }
1017
- async getRandomOffer(tags, excludeUsername) {
619
+ async getRandomOffer(tags, excludePublicKey) {
1018
620
  if (tags.length === 0) {
1019
621
  return null;
1020
622
  }
@@ -1023,12 +625,12 @@ var init_sqlite = __esm({
1023
625
  SELECT DISTINCT o.* FROM offers o, json_each(o.tags) as t
1024
626
  WHERE t.value IN (${placeholders})
1025
627
  AND o.expires_at > ?
1026
- AND o.answerer_username IS NULL
628
+ AND o.answerer_public_key IS NULL
1027
629
  `;
1028
630
  const params = [...tags, Date.now()];
1029
- if (excludeUsername) {
1030
- query += " AND o.username != ?";
1031
- params.push(excludeUsername);
631
+ if (excludePublicKey) {
632
+ query += " AND o.public_key != ?";
633
+ params.push(excludePublicKey);
1032
634
  }
1033
635
  query += " ORDER BY RANDOM() LIMIT 1";
1034
636
  const stmt = this.db.prepare(query);
@@ -1036,9 +638,9 @@ var init_sqlite = __esm({
1036
638
  return row ? this.rowToOffer(row) : null;
1037
639
  }
1038
640
  // ===== ICE Candidate Management =====
1039
- async addIceCandidates(offerId, username, role, candidates) {
641
+ async addIceCandidates(offerId, publicKey, role, candidates) {
1040
642
  const stmt = this.db.prepare(`
1041
- INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)
643
+ INSERT INTO ice_candidates (offer_id, public_key, role, candidate, created_at)
1042
644
  VALUES (?, ?, ?, ?, ?)
1043
645
  `);
1044
646
  const baseTimestamp = Date.now();
@@ -1046,7 +648,7 @@ var init_sqlite = __esm({
1046
648
  for (let i = 0; i < candidates2.length; i++) {
1047
649
  stmt.run(
1048
650
  offerId,
1049
- username,
651
+ publicKey,
1050
652
  role,
1051
653
  JSON.stringify(candidates2[i]),
1052
654
  baseTimestamp + i
@@ -1072,13 +674,13 @@ var init_sqlite = __esm({
1072
674
  return rows.map((row) => ({
1073
675
  id: row.id,
1074
676
  offerId: row.offer_id,
1075
- username: row.username,
677
+ publicKey: row.public_key,
1076
678
  role: row.role,
1077
679
  candidate: JSON.parse(row.candidate),
1078
680
  createdAt: row.created_at
1079
681
  }));
1080
682
  }
1081
- async getIceCandidatesForMultipleOffers(offerIds, username, since) {
683
+ async getIceCandidatesForMultipleOffers(offerIds, publicKey, since) {
1082
684
  const result = /* @__PURE__ */ new Map();
1083
685
  if (offerIds.length === 0) {
1084
686
  return result;
@@ -1091,16 +693,16 @@ var init_sqlite = __esm({
1091
693
  }
1092
694
  const placeholders = offerIds.map(() => "?").join(",");
1093
695
  let query = `
1094
- SELECT ic.*, o.username as offer_username
696
+ SELECT ic.*, o.public_key as offer_public_key
1095
697
  FROM ice_candidates ic
1096
698
  INNER JOIN offers o ON o.id = ic.offer_id
1097
699
  WHERE ic.offer_id IN (${placeholders})
1098
700
  AND (
1099
- (o.username = ? AND ic.role = 'answerer')
1100
- OR (o.answerer_username = ? AND ic.role = 'offerer')
701
+ (o.public_key = ? AND ic.role = 'answerer')
702
+ OR (o.answerer_public_key = ? AND ic.role = 'offerer')
1101
703
  )
1102
704
  `;
1103
- const params = [...offerIds, username, username];
705
+ const params = [...offerIds, publicKey, publicKey];
1104
706
  if (since !== void 0) {
1105
707
  query += " AND ic.created_at > ?";
1106
708
  params.push(since);
@@ -1112,7 +714,7 @@ var init_sqlite = __esm({
1112
714
  const candidate = {
1113
715
  id: row.id,
1114
716
  offerId: row.offer_id,
1115
- username: row.username,
717
+ publicKey: row.public_key,
1116
718
  role: row.role,
1117
719
  candidate: JSON.parse(row.candidate),
1118
720
  createdAt: row.created_at
@@ -1124,92 +726,6 @@ var init_sqlite = __esm({
1124
726
  }
1125
727
  return result;
1126
728
  }
1127
- // ===== Credential Management =====
1128
- async generateCredentials(request) {
1129
- const now = Date.now();
1130
- const expiresAt = request.expiresAt || now + YEAR_IN_MS2;
1131
- const { generateCredentialName: generateCredentialName2, generateSecret: generateSecret2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
1132
- let name;
1133
- if (request.name) {
1134
- const existing = this.db.prepare(`
1135
- SELECT name FROM credentials WHERE name = ?
1136
- `).get(request.name);
1137
- if (existing) {
1138
- throw new Error("Username already taken");
1139
- }
1140
- name = request.name;
1141
- } else {
1142
- let attempts = 0;
1143
- const maxAttempts = 100;
1144
- while (attempts < maxAttempts) {
1145
- name = generateCredentialName2();
1146
- const existing = this.db.prepare(`
1147
- SELECT name FROM credentials WHERE name = ?
1148
- `).get(name);
1149
- if (!existing) {
1150
- break;
1151
- }
1152
- attempts++;
1153
- }
1154
- if (attempts >= maxAttempts) {
1155
- throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);
1156
- }
1157
- }
1158
- const secret = generateSecret2();
1159
- const { encryptSecret: encryptSecret2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
1160
- const encryptedSecret = await encryptSecret2(secret, this.masterEncryptionKey);
1161
- const stmt = this.db.prepare(`
1162
- INSERT INTO credentials (name, secret, created_at, expires_at, last_used)
1163
- VALUES (?, ?, ?, ?, ?)
1164
- `);
1165
- stmt.run(name, encryptedSecret, now, expiresAt, now);
1166
- return {
1167
- name,
1168
- secret,
1169
- // Return plaintext secret, not encrypted
1170
- createdAt: now,
1171
- expiresAt,
1172
- lastUsed: now
1173
- };
1174
- }
1175
- async getCredential(name) {
1176
- const stmt = this.db.prepare(`
1177
- SELECT * FROM credentials
1178
- WHERE name = ? AND expires_at > ?
1179
- `);
1180
- const row = stmt.get(name, Date.now());
1181
- if (!row) {
1182
- return null;
1183
- }
1184
- try {
1185
- const { decryptSecret: decryptSecret2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
1186
- const decryptedSecret = await decryptSecret2(row.secret, this.masterEncryptionKey);
1187
- return {
1188
- name: row.name,
1189
- secret: decryptedSecret,
1190
- // Return decrypted secret
1191
- createdAt: row.created_at,
1192
- expiresAt: row.expires_at,
1193
- lastUsed: row.last_used
1194
- };
1195
- } catch (error) {
1196
- console.error(`Failed to decrypt secret for credential '${name}':`, error);
1197
- return null;
1198
- }
1199
- }
1200
- async updateCredentialUsage(name, lastUsed, expiresAt) {
1201
- const stmt = this.db.prepare(`
1202
- UPDATE credentials
1203
- SET last_used = ?, expires_at = ?
1204
- WHERE name = ?
1205
- `);
1206
- stmt.run(lastUsed, expiresAt, name);
1207
- }
1208
- async deleteExpiredCredentials(now) {
1209
- const stmt = this.db.prepare("DELETE FROM credentials WHERE expires_at < ?");
1210
- const result = stmt.run(now);
1211
- return result.changes;
1212
- }
1213
729
  // ===== Rate Limiting =====
1214
730
  async checkRateLimit(identifier, limit, windowMs) {
1215
731
  const now = Date.now();
@@ -1264,12 +780,8 @@ var init_sqlite = __esm({
1264
780
  const result = this.db.prepare("SELECT COUNT(*) as count FROM offers").get();
1265
781
  return result.count;
1266
782
  }
1267
- async getOfferCountByUsername(username) {
1268
- const result = this.db.prepare("SELECT COUNT(*) as count FROM offers WHERE username = ?").get(username);
1269
- return result.count;
1270
- }
1271
- async getCredentialCount() {
1272
- const result = this.db.prepare("SELECT COUNT(*) as count FROM credentials").get();
783
+ async getOfferCountByPublicKey(publicKey) {
784
+ const result = this.db.prepare("SELECT COUNT(*) as count FROM offers WHERE public_key = ?").get(publicKey);
1273
785
  return result.count;
1274
786
  }
1275
787
  async getIceCandidateCount(offerId) {
@@ -1283,13 +795,13 @@ var init_sqlite = __esm({
1283
795
  rowToOffer(row) {
1284
796
  return {
1285
797
  id: row.id,
1286
- username: row.username,
798
+ publicKey: row.public_key,
1287
799
  tags: JSON.parse(row.tags),
1288
800
  sdp: row.sdp,
1289
801
  createdAt: row.created_at,
1290
802
  expiresAt: row.expires_at,
1291
803
  lastSeen: row.last_seen,
1292
- answererUsername: row.answerer_username || void 0,
804
+ answererPublicKey: row.answerer_public_key || void 0,
1293
805
  answerSdp: row.answer_sdp || void 0,
1294
806
  answeredAt: row.answered_at || void 0,
1295
807
  matchedTags: row.matched_tags ? JSON.parse(row.matched_tags) : void 0
@@ -1304,25 +816,22 @@ var mysql_exports = {};
1304
816
  __export(mysql_exports, {
1305
817
  MySQLStorage: () => MySQLStorage
1306
818
  });
1307
- var import_promise, YEAR_IN_MS3, MySQLStorage;
819
+ var import_promise, MySQLStorage;
1308
820
  var init_mysql = __esm({
1309
821
  "src/storage/mysql.ts"() {
1310
822
  "use strict";
1311
823
  import_promise = __toESM(require("mysql2/promise"));
1312
824
  init_hash_id();
1313
- YEAR_IN_MS3 = 365 * 24 * 60 * 60 * 1e3;
1314
825
  MySQLStorage = class _MySQLStorage {
1315
- constructor(pool, masterEncryptionKey) {
826
+ constructor(pool) {
1316
827
  this.pool = pool;
1317
- this.masterEncryptionKey = masterEncryptionKey;
1318
828
  }
1319
829
  /**
1320
830
  * Creates a new MySQL storage instance with connection pooling
1321
831
  * @param connectionString MySQL connection URL
1322
- * @param masterEncryptionKey 64-char hex string for encrypting secrets
1323
832
  * @param poolSize Maximum number of connections in the pool
1324
833
  */
1325
- static async create(connectionString, masterEncryptionKey, poolSize = 10) {
834
+ static async create(connectionString, poolSize = 10) {
1326
835
  const pool = import_promise.default.createPool({
1327
836
  uri: connectionString,
1328
837
  waitForConnections: true,
@@ -1331,7 +840,7 @@ var init_mysql = __esm({
1331
840
  enableKeepAlive: true,
1332
841
  keepAliveInitialDelay: 1e4
1333
842
  });
1334
- const storage = new _MySQLStorage(pool, masterEncryptionKey);
843
+ const storage = new _MySQLStorage(pool);
1335
844
  await storage.initializeDatabase();
1336
845
  return storage;
1337
846
  }
@@ -1339,47 +848,47 @@ var init_mysql = __esm({
1339
848
  const conn = await this.pool.getConnection();
1340
849
  try {
1341
850
  await conn.query(`
851
+ CREATE TABLE IF NOT EXISTS identities (
852
+ public_key CHAR(64) PRIMARY KEY,
853
+ created_at BIGINT NOT NULL,
854
+ expires_at BIGINT NOT NULL,
855
+ last_used BIGINT NOT NULL,
856
+ INDEX idx_identities_expires (expires_at)
857
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
858
+ `);
859
+ await conn.query(`
1342
860
  CREATE TABLE IF NOT EXISTS offers (
1343
861
  id VARCHAR(64) PRIMARY KEY,
1344
- username VARCHAR(32) NOT NULL,
862
+ public_key CHAR(64) NOT NULL,
1345
863
  tags JSON NOT NULL,
1346
864
  sdp MEDIUMTEXT NOT NULL,
1347
865
  created_at BIGINT NOT NULL,
1348
866
  expires_at BIGINT NOT NULL,
1349
867
  last_seen BIGINT NOT NULL,
1350
- answerer_username VARCHAR(32),
868
+ answerer_public_key CHAR(64),
1351
869
  answer_sdp MEDIUMTEXT,
1352
870
  answered_at BIGINT,
1353
871
  matched_tags JSON,
1354
- INDEX idx_offers_username (username),
872
+ INDEX idx_offers_public_key (public_key),
1355
873
  INDEX idx_offers_expires (expires_at),
1356
874
  INDEX idx_offers_last_seen (last_seen),
1357
- INDEX idx_offers_answerer (answerer_username)
875
+ INDEX idx_offers_answerer (answerer_public_key),
876
+ FOREIGN KEY (public_key) REFERENCES identities(public_key) ON DELETE CASCADE
1358
877
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1359
878
  `);
1360
879
  await conn.query(`
1361
880
  CREATE TABLE IF NOT EXISTS ice_candidates (
1362
881
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
1363
882
  offer_id VARCHAR(64) NOT NULL,
1364
- username VARCHAR(32) NOT NULL,
883
+ public_key CHAR(64) NOT NULL,
1365
884
  role ENUM('offerer', 'answerer') NOT NULL,
1366
885
  candidate JSON NOT NULL,
1367
886
  created_at BIGINT NOT NULL,
1368
887
  INDEX idx_ice_offer (offer_id),
1369
- INDEX idx_ice_username (username),
888
+ INDEX idx_ice_public_key (public_key),
1370
889
  INDEX idx_ice_created (created_at),
1371
890
  FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE
1372
891
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1373
- `);
1374
- await conn.query(`
1375
- CREATE TABLE IF NOT EXISTS credentials (
1376
- name VARCHAR(32) PRIMARY KEY,
1377
- secret VARCHAR(512) NOT NULL UNIQUE,
1378
- created_at BIGINT NOT NULL,
1379
- expires_at BIGINT NOT NULL,
1380
- last_used BIGINT NOT NULL,
1381
- INDEX idx_credentials_expires (expires_at)
1382
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1383
892
  `);
1384
893
  await conn.query(`
1385
894
  CREATE TABLE IF NOT EXISTS rate_limits (
@@ -1411,13 +920,13 @@ var init_mysql = __esm({
1411
920
  for (const request of offers) {
1412
921
  const id = request.id || await generateOfferHash(request.sdp);
1413
922
  await conn.query(
1414
- `INSERT INTO offers (id, username, tags, sdp, created_at, expires_at, last_seen)
923
+ `INSERT INTO offers (id, public_key, tags, sdp, created_at, expires_at, last_seen)
1415
924
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
1416
- [id, request.username, JSON.stringify(request.tags), request.sdp, now, request.expiresAt, now]
925
+ [id, request.publicKey, JSON.stringify(request.tags), request.sdp, now, request.expiresAt, now]
1417
926
  );
1418
927
  created.push({
1419
928
  id,
1420
- username: request.username,
929
+ publicKey: request.publicKey,
1421
930
  tags: request.tags,
1422
931
  sdp: request.sdp,
1423
932
  createdAt: now,
@@ -1434,10 +943,10 @@ var init_mysql = __esm({
1434
943
  }
1435
944
  return created;
1436
945
  }
1437
- async getOffersByUsername(username) {
946
+ async getOffersByPublicKey(publicKey) {
1438
947
  const [rows] = await this.pool.query(
1439
- `SELECT * FROM offers WHERE username = ? AND expires_at > ? ORDER BY last_seen DESC`,
1440
- [username, Date.now()]
948
+ `SELECT * FROM offers WHERE public_key = ? AND expires_at > ? ORDER BY last_seen DESC`,
949
+ [publicKey, Date.now()]
1441
950
  );
1442
951
  return rows.map((row) => this.rowToOffer(row));
1443
952
  }
@@ -1448,10 +957,10 @@ var init_mysql = __esm({
1448
957
  );
1449
958
  return rows.length > 0 ? this.rowToOffer(rows[0]) : null;
1450
959
  }
1451
- async deleteOffer(offerId, ownerUsername) {
960
+ async deleteOffer(offerId, ownerPublicKey) {
1452
961
  const [result] = await this.pool.query(
1453
- `DELETE FROM offers WHERE id = ? AND username = ?`,
1454
- [offerId, ownerUsername]
962
+ `DELETE FROM offers WHERE id = ? AND public_key = ?`,
963
+ [offerId, ownerPublicKey]
1455
964
  );
1456
965
  return result.affectedRows > 0;
1457
966
  }
@@ -1462,94 +971,94 @@ var init_mysql = __esm({
1462
971
  );
1463
972
  return result.affectedRows;
1464
973
  }
1465
- async answerOffer(offerId, answererUsername, answerSdp, matchedTags) {
974
+ async answerOffer(offerId, answererPublicKey, answerSdp, matchedTags) {
1466
975
  const offer = await this.getOfferById(offerId);
1467
976
  if (!offer) {
1468
977
  return { success: false, error: "Offer not found or expired" };
1469
978
  }
1470
- if (offer.answererUsername) {
979
+ if (offer.answererPublicKey) {
1471
980
  return { success: false, error: "Offer already answered" };
1472
981
  }
1473
982
  const matchedTagsJson = matchedTags ? JSON.stringify(matchedTags) : null;
1474
983
  const [result] = await this.pool.query(
1475
- `UPDATE offers SET answerer_username = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?
1476
- WHERE id = ? AND answerer_username IS NULL`,
1477
- [answererUsername, answerSdp, Date.now(), matchedTagsJson, offerId]
984
+ `UPDATE offers SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?
985
+ WHERE id = ? AND answerer_public_key IS NULL`,
986
+ [answererPublicKey, answerSdp, Date.now(), matchedTagsJson, offerId]
1478
987
  );
1479
988
  if (result.affectedRows === 0) {
1480
989
  return { success: false, error: "Offer already answered (race condition)" };
1481
990
  }
1482
991
  return { success: true };
1483
992
  }
1484
- async getAnsweredOffers(offererUsername) {
993
+ async getAnsweredOffers(offererPublicKey) {
1485
994
  const [rows] = await this.pool.query(
1486
995
  `SELECT * FROM offers
1487
- WHERE username = ? AND answerer_username IS NOT NULL AND expires_at > ?
996
+ WHERE public_key = ? AND answerer_public_key IS NOT NULL AND expires_at > ?
1488
997
  ORDER BY answered_at DESC`,
1489
- [offererUsername, Date.now()]
998
+ [offererPublicKey, Date.now()]
1490
999
  );
1491
1000
  return rows.map((row) => this.rowToOffer(row));
1492
1001
  }
1493
- async getOffersAnsweredBy(answererUsername) {
1002
+ async getOffersAnsweredBy(answererPublicKey) {
1494
1003
  const [rows] = await this.pool.query(
1495
1004
  `SELECT * FROM offers
1496
- WHERE answerer_username = ? AND expires_at > ?
1005
+ WHERE answerer_public_key = ? AND expires_at > ?
1497
1006
  ORDER BY answered_at DESC`,
1498
- [answererUsername, Date.now()]
1007
+ [answererPublicKey, Date.now()]
1499
1008
  );
1500
1009
  return rows.map((row) => this.rowToOffer(row));
1501
1010
  }
1502
1011
  // ===== Discovery =====
1503
- async discoverOffers(tags, excludeUsername, limit, offset) {
1012
+ async discoverOffers(tags, excludePublicKey, limit, offset) {
1504
1013
  if (tags.length === 0) return [];
1505
1014
  const tagArray = JSON.stringify(tags);
1506
1015
  let query = `
1507
1016
  SELECT DISTINCT o.* FROM offers o
1508
1017
  WHERE JSON_OVERLAPS(o.tags, ?)
1509
1018
  AND o.expires_at > ?
1510
- AND o.answerer_username IS NULL
1019
+ AND o.answerer_public_key IS NULL
1511
1020
  `;
1512
1021
  const params = [tagArray, Date.now()];
1513
- if (excludeUsername) {
1514
- query += " AND o.username != ?";
1515
- params.push(excludeUsername);
1022
+ if (excludePublicKey) {
1023
+ query += " AND o.public_key != ?";
1024
+ params.push(excludePublicKey);
1516
1025
  }
1517
1026
  query += " ORDER BY o.created_at DESC LIMIT ? OFFSET ?";
1518
1027
  params.push(limit, offset);
1519
1028
  const [rows] = await this.pool.query(query, params);
1520
1029
  return rows.map((row) => this.rowToOffer(row));
1521
1030
  }
1522
- async getRandomOffer(tags, excludeUsername) {
1031
+ async getRandomOffer(tags, excludePublicKey) {
1523
1032
  if (tags.length === 0) return null;
1524
1033
  const tagArray = JSON.stringify(tags);
1525
1034
  let query = `
1526
1035
  SELECT DISTINCT o.* FROM offers o
1527
1036
  WHERE JSON_OVERLAPS(o.tags, ?)
1528
1037
  AND o.expires_at > ?
1529
- AND o.answerer_username IS NULL
1038
+ AND o.answerer_public_key IS NULL
1530
1039
  `;
1531
1040
  const params = [tagArray, Date.now()];
1532
- if (excludeUsername) {
1533
- query += " AND o.username != ?";
1534
- params.push(excludeUsername);
1041
+ if (excludePublicKey) {
1042
+ query += " AND o.public_key != ?";
1043
+ params.push(excludePublicKey);
1535
1044
  }
1536
1045
  query += " ORDER BY RAND() LIMIT 1";
1537
1046
  const [rows] = await this.pool.query(query, params);
1538
1047
  return rows.length > 0 ? this.rowToOffer(rows[0]) : null;
1539
1048
  }
1540
1049
  // ===== ICE Candidate Management =====
1541
- async addIceCandidates(offerId, username, role, candidates) {
1050
+ async addIceCandidates(offerId, publicKey, role, candidates) {
1542
1051
  if (candidates.length === 0) return 0;
1543
1052
  const baseTimestamp = Date.now();
1544
1053
  const values = candidates.map((c, i) => [
1545
1054
  offerId,
1546
- username,
1055
+ publicKey,
1547
1056
  role,
1548
1057
  JSON.stringify(c),
1549
1058
  baseTimestamp + i
1550
1059
  ]);
1551
1060
  await this.pool.query(
1552
- `INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)
1061
+ `INSERT INTO ice_candidates (offer_id, public_key, role, candidate, created_at)
1553
1062
  VALUES ?`,
1554
1063
  [values]
1555
1064
  );
@@ -1566,7 +1075,7 @@ var init_mysql = __esm({
1566
1075
  const [rows] = await this.pool.query(query, params);
1567
1076
  return rows.map((row) => this.rowToIceCandidate(row));
1568
1077
  }
1569
- async getIceCandidatesForMultipleOffers(offerIds, username, since) {
1078
+ async getIceCandidatesForMultipleOffers(offerIds, publicKey, since) {
1570
1079
  const result = /* @__PURE__ */ new Map();
1571
1080
  if (offerIds.length === 0) return result;
1572
1081
  if (offerIds.length > 1e3) {
@@ -1574,16 +1083,16 @@ var init_mysql = __esm({
1574
1083
  }
1575
1084
  const placeholders = offerIds.map(() => "?").join(",");
1576
1085
  let query = `
1577
- SELECT ic.*, o.username as offer_username
1086
+ SELECT ic.*, o.public_key as offer_public_key
1578
1087
  FROM ice_candidates ic
1579
1088
  INNER JOIN offers o ON o.id = ic.offer_id
1580
1089
  WHERE ic.offer_id IN (${placeholders})
1581
1090
  AND (
1582
- (o.username = ? AND ic.role = 'answerer')
1583
- OR (o.answerer_username = ? AND ic.role = 'offerer')
1091
+ (o.public_key = ? AND ic.role = 'answerer')
1092
+ OR (o.answerer_public_key = ? AND ic.role = 'offerer')
1584
1093
  )
1585
1094
  `;
1586
- const params = [...offerIds, username, username];
1095
+ const params = [...offerIds, publicKey, publicKey];
1587
1096
  if (since !== void 0) {
1588
1097
  query += " AND ic.created_at > ?";
1589
1098
  params.push(since);
@@ -1599,86 +1108,6 @@ var init_mysql = __esm({
1599
1108
  }
1600
1109
  return result;
1601
1110
  }
1602
- // ===== Credential Management =====
1603
- async generateCredentials(request) {
1604
- const now = Date.now();
1605
- const expiresAt = request.expiresAt || now + YEAR_IN_MS3;
1606
- const { generateCredentialName: generateCredentialName2, generateSecret: generateSecret2, encryptSecret: encryptSecret2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
1607
- let name;
1608
- if (request.name) {
1609
- const [existing] = await this.pool.query(
1610
- `SELECT name FROM credentials WHERE name = ?`,
1611
- [request.name]
1612
- );
1613
- if (existing.length > 0) {
1614
- throw new Error("Username already taken");
1615
- }
1616
- name = request.name;
1617
- } else {
1618
- let attempts = 0;
1619
- const maxAttempts = 100;
1620
- while (attempts < maxAttempts) {
1621
- name = generateCredentialName2();
1622
- const [existing] = await this.pool.query(
1623
- `SELECT name FROM credentials WHERE name = ?`,
1624
- [name]
1625
- );
1626
- if (existing.length === 0) break;
1627
- attempts++;
1628
- }
1629
- if (attempts >= maxAttempts) {
1630
- throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);
1631
- }
1632
- }
1633
- const secret = generateSecret2();
1634
- const encryptedSecret = await encryptSecret2(secret, this.masterEncryptionKey);
1635
- await this.pool.query(
1636
- `INSERT INTO credentials (name, secret, created_at, expires_at, last_used)
1637
- VALUES (?, ?, ?, ?, ?)`,
1638
- [name, encryptedSecret, now, expiresAt, now]
1639
- );
1640
- return {
1641
- name,
1642
- secret,
1643
- createdAt: now,
1644
- expiresAt,
1645
- lastUsed: now
1646
- };
1647
- }
1648
- async getCredential(name) {
1649
- const [rows] = await this.pool.query(
1650
- `SELECT * FROM credentials WHERE name = ? AND expires_at > ?`,
1651
- [name, Date.now()]
1652
- );
1653
- if (rows.length === 0) return null;
1654
- try {
1655
- const { decryptSecret: decryptSecret2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
1656
- const decryptedSecret = await decryptSecret2(rows[0].secret, this.masterEncryptionKey);
1657
- return {
1658
- name: rows[0].name,
1659
- secret: decryptedSecret,
1660
- createdAt: Number(rows[0].created_at),
1661
- expiresAt: Number(rows[0].expires_at),
1662
- lastUsed: Number(rows[0].last_used)
1663
- };
1664
- } catch (error) {
1665
- console.error(`Failed to decrypt secret for credential '${name}':`, error);
1666
- return null;
1667
- }
1668
- }
1669
- async updateCredentialUsage(name, lastUsed, expiresAt) {
1670
- await this.pool.query(
1671
- `UPDATE credentials SET last_used = ?, expires_at = ? WHERE name = ?`,
1672
- [lastUsed, expiresAt, name]
1673
- );
1674
- }
1675
- async deleteExpiredCredentials(now) {
1676
- const [result] = await this.pool.query(
1677
- `DELETE FROM credentials WHERE expires_at < ?`,
1678
- [now]
1679
- );
1680
- return result.affectedRows;
1681
- }
1682
1111
  // ===== Rate Limiting =====
1683
1112
  async checkRateLimit(identifier, limit, windowMs) {
1684
1113
  const now = Date.now();
@@ -1734,17 +1163,13 @@ var init_mysql = __esm({
1734
1163
  const [rows] = await this.pool.query("SELECT COUNT(*) as count FROM offers");
1735
1164
  return Number(rows[0].count);
1736
1165
  }
1737
- async getOfferCountByUsername(username) {
1166
+ async getOfferCountByPublicKey(publicKey) {
1738
1167
  const [rows] = await this.pool.query(
1739
- "SELECT COUNT(*) as count FROM offers WHERE username = ?",
1740
- [username]
1168
+ "SELECT COUNT(*) as count FROM offers WHERE public_key = ?",
1169
+ [publicKey]
1741
1170
  );
1742
1171
  return Number(rows[0].count);
1743
1172
  }
1744
- async getCredentialCount() {
1745
- const [rows] = await this.pool.query("SELECT COUNT(*) as count FROM credentials");
1746
- return Number(rows[0].count);
1747
- }
1748
1173
  async getIceCandidateCount(offerId) {
1749
1174
  const [rows] = await this.pool.query(
1750
1175
  "SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = ?",
@@ -1756,13 +1181,13 @@ var init_mysql = __esm({
1756
1181
  rowToOffer(row) {
1757
1182
  return {
1758
1183
  id: row.id,
1759
- username: row.username,
1184
+ publicKey: row.public_key,
1760
1185
  tags: typeof row.tags === "string" ? JSON.parse(row.tags) : row.tags,
1761
1186
  sdp: row.sdp,
1762
1187
  createdAt: Number(row.created_at),
1763
1188
  expiresAt: Number(row.expires_at),
1764
1189
  lastSeen: Number(row.last_seen),
1765
- answererUsername: row.answerer_username || void 0,
1190
+ answererPublicKey: row.answerer_public_key || void 0,
1766
1191
  answerSdp: row.answer_sdp || void 0,
1767
1192
  answeredAt: row.answered_at ? Number(row.answered_at) : void 0,
1768
1193
  matchedTags: row.matched_tags ? typeof row.matched_tags === "string" ? JSON.parse(row.matched_tags) : row.matched_tags : void 0
@@ -1772,7 +1197,7 @@ var init_mysql = __esm({
1772
1197
  return {
1773
1198
  id: Number(row.id),
1774
1199
  offerId: row.offer_id,
1775
- username: row.username,
1200
+ publicKey: row.public_key,
1776
1201
  role: row.role,
1777
1202
  candidate: typeof row.candidate === "string" ? JSON.parse(row.candidate) : row.candidate,
1778
1203
  createdAt: Number(row.created_at)
@@ -1787,32 +1212,29 @@ var postgres_exports = {};
1787
1212
  __export(postgres_exports, {
1788
1213
  PostgreSQLStorage: () => PostgreSQLStorage
1789
1214
  });
1790
- var import_pg, YEAR_IN_MS4, PostgreSQLStorage;
1215
+ var import_pg, PostgreSQLStorage;
1791
1216
  var init_postgres = __esm({
1792
1217
  "src/storage/postgres.ts"() {
1793
1218
  "use strict";
1794
1219
  import_pg = require("pg");
1795
1220
  init_hash_id();
1796
- YEAR_IN_MS4 = 365 * 24 * 60 * 60 * 1e3;
1797
1221
  PostgreSQLStorage = class _PostgreSQLStorage {
1798
- constructor(pool, masterEncryptionKey) {
1222
+ constructor(pool) {
1799
1223
  this.pool = pool;
1800
- this.masterEncryptionKey = masterEncryptionKey;
1801
1224
  }
1802
1225
  /**
1803
1226
  * Creates a new PostgreSQL storage instance with connection pooling
1804
1227
  * @param connectionString PostgreSQL connection URL
1805
- * @param masterEncryptionKey 64-char hex string for encrypting secrets
1806
1228
  * @param poolSize Maximum number of connections in the pool
1807
1229
  */
1808
- static async create(connectionString, masterEncryptionKey, poolSize = 10) {
1230
+ static async create(connectionString, poolSize = 10) {
1809
1231
  const pool = new import_pg.Pool({
1810
1232
  connectionString,
1811
1233
  max: poolSize,
1812
1234
  idleTimeoutMillis: 3e4,
1813
1235
  connectionTimeoutMillis: 5e3
1814
1236
  });
1815
- const storage = new _PostgreSQLStorage(pool, masterEncryptionKey);
1237
+ const storage = new _PostgreSQLStorage(pool);
1816
1238
  await storage.initializeDatabase();
1817
1239
  return storage;
1818
1240
  }
@@ -1820,49 +1242,48 @@ var init_postgres = __esm({
1820
1242
  const client = await this.pool.connect();
1821
1243
  try {
1822
1244
  await client.query(`
1823
- CREATE TABLE IF NOT EXISTS offers (
1824
- id VARCHAR(64) PRIMARY KEY,
1825
- username VARCHAR(32) NOT NULL,
1826
- tags JSONB NOT NULL,
1827
- sdp TEXT NOT NULL,
1245
+ CREATE TABLE IF NOT EXISTS identities (
1246
+ public_key CHAR(64) PRIMARY KEY,
1247
+ created_at BIGINT NOT NULL,
1248
+ expires_at BIGINT NOT NULL,
1249
+ last_used BIGINT NOT NULL
1250
+ )
1251
+ `);
1252
+ await client.query(`CREATE INDEX IF NOT EXISTS idx_identities_expires ON identities(expires_at)`);
1253
+ await client.query(`
1254
+ CREATE TABLE IF NOT EXISTS offers (
1255
+ id VARCHAR(64) PRIMARY KEY,
1256
+ public_key CHAR(64) NOT NULL REFERENCES identities(public_key) ON DELETE CASCADE,
1257
+ tags JSONB NOT NULL,
1258
+ sdp TEXT NOT NULL,
1828
1259
  created_at BIGINT NOT NULL,
1829
1260
  expires_at BIGINT NOT NULL,
1830
1261
  last_seen BIGINT NOT NULL,
1831
- answerer_username VARCHAR(32),
1262
+ answerer_public_key CHAR(64),
1832
1263
  answer_sdp TEXT,
1833
1264
  answered_at BIGINT,
1834
1265
  matched_tags JSONB
1835
1266
  )
1836
1267
  `);
1837
- await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_username ON offers(username)`);
1268
+ await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_public_key ON offers(public_key)`);
1838
1269
  await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at)`);
1839
1270
  await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen)`);
1840
- await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_username)`);
1271
+ await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_public_key)`);
1841
1272
  await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_tags ON offers USING GIN(tags)`);
1842
1273
  await client.query(`
1843
1274
  CREATE TABLE IF NOT EXISTS ice_candidates (
1844
1275
  id BIGSERIAL PRIMARY KEY,
1845
1276
  offer_id VARCHAR(64) NOT NULL REFERENCES offers(id) ON DELETE CASCADE,
1846
- username VARCHAR(32) NOT NULL,
1277
+ public_key CHAR(64) NOT NULL,
1847
1278
  role VARCHAR(8) NOT NULL CHECK (role IN ('offerer', 'answerer')),
1848
1279
  candidate JSONB NOT NULL,
1849
1280
  created_at BIGINT NOT NULL
1850
1281
  )
1851
1282
  `);
1852
1283
  await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_offer ON ice_candidates(offer_id)`);
1853
- await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_username ON ice_candidates(username)`);
1284
+ await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_public_key ON ice_candidates(public_key)`);
1854
1285
  await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_created ON ice_candidates(created_at)`);
1855
1286
  await client.query(`
1856
- CREATE TABLE IF NOT EXISTS credentials (
1857
- name VARCHAR(32) PRIMARY KEY,
1858
- secret VARCHAR(512) NOT NULL UNIQUE,
1859
- created_at BIGINT NOT NULL,
1860
- expires_at BIGINT NOT NULL,
1861
- last_used BIGINT NOT NULL
1862
- )
1863
- `);
1864
- await client.query(`CREATE INDEX IF NOT EXISTS idx_credentials_expires ON credentials(expires_at)`);
1865
- await client.query(`
1866
1287
  CREATE TABLE IF NOT EXISTS rate_limits (
1867
1288
  identifier VARCHAR(255) PRIMARY KEY,
1868
1289
  count INT NOT NULL,
@@ -1892,13 +1313,13 @@ var init_postgres = __esm({
1892
1313
  for (const request of offers) {
1893
1314
  const id = request.id || await generateOfferHash(request.sdp);
1894
1315
  await client.query(
1895
- `INSERT INTO offers (id, username, tags, sdp, created_at, expires_at, last_seen)
1316
+ `INSERT INTO offers (id, public_key, tags, sdp, created_at, expires_at, last_seen)
1896
1317
  VALUES ($1, $2, $3, $4, $5, $6, $7)`,
1897
- [id, request.username, JSON.stringify(request.tags), request.sdp, now, request.expiresAt, now]
1318
+ [id, request.publicKey, JSON.stringify(request.tags), request.sdp, now, request.expiresAt, now]
1898
1319
  );
1899
1320
  created.push({
1900
1321
  id,
1901
- username: request.username,
1322
+ publicKey: request.publicKey,
1902
1323
  tags: request.tags,
1903
1324
  sdp: request.sdp,
1904
1325
  createdAt: now,
@@ -1915,10 +1336,10 @@ var init_postgres = __esm({
1915
1336
  }
1916
1337
  return created;
1917
1338
  }
1918
- async getOffersByUsername(username) {
1339
+ async getOffersByPublicKey(publicKey) {
1919
1340
  const result = await this.pool.query(
1920
- `SELECT * FROM offers WHERE username = $1 AND expires_at > $2 ORDER BY last_seen DESC`,
1921
- [username, Date.now()]
1341
+ `SELECT * FROM offers WHERE public_key = $1 AND expires_at > $2 ORDER BY last_seen DESC`,
1342
+ [publicKey, Date.now()]
1922
1343
  );
1923
1344
  return result.rows.map((row) => this.rowToOffer(row));
1924
1345
  }
@@ -1929,10 +1350,10 @@ var init_postgres = __esm({
1929
1350
  );
1930
1351
  return result.rows.length > 0 ? this.rowToOffer(result.rows[0]) : null;
1931
1352
  }
1932
- async deleteOffer(offerId, ownerUsername) {
1353
+ async deleteOffer(offerId, ownerPublicKey) {
1933
1354
  const result = await this.pool.query(
1934
- `DELETE FROM offers WHERE id = $1 AND username = $2`,
1935
- [offerId, ownerUsername]
1355
+ `DELETE FROM offers WHERE id = $1 AND public_key = $2`,
1356
+ [offerId, ownerPublicKey]
1936
1357
  );
1937
1358
  return (result.rowCount ?? 0) > 0;
1938
1359
  }
@@ -1943,57 +1364,57 @@ var init_postgres = __esm({
1943
1364
  );
1944
1365
  return result.rowCount ?? 0;
1945
1366
  }
1946
- async answerOffer(offerId, answererUsername, answerSdp, matchedTags) {
1367
+ async answerOffer(offerId, answererPublicKey, answerSdp, matchedTags) {
1947
1368
  const offer = await this.getOfferById(offerId);
1948
1369
  if (!offer) {
1949
1370
  return { success: false, error: "Offer not found or expired" };
1950
1371
  }
1951
- if (offer.answererUsername) {
1372
+ if (offer.answererPublicKey) {
1952
1373
  return { success: false, error: "Offer already answered" };
1953
1374
  }
1954
1375
  const matchedTagsJson = matchedTags ? JSON.stringify(matchedTags) : null;
1955
1376
  const result = await this.pool.query(
1956
- `UPDATE offers SET answerer_username = $1, answer_sdp = $2, answered_at = $3, matched_tags = $4
1957
- WHERE id = $5 AND answerer_username IS NULL`,
1958
- [answererUsername, answerSdp, Date.now(), matchedTagsJson, offerId]
1377
+ `UPDATE offers SET answerer_public_key = $1, answer_sdp = $2, answered_at = $3, matched_tags = $4
1378
+ WHERE id = $5 AND answerer_public_key IS NULL`,
1379
+ [answererPublicKey, answerSdp, Date.now(), matchedTagsJson, offerId]
1959
1380
  );
1960
1381
  if ((result.rowCount ?? 0) === 0) {
1961
1382
  return { success: false, error: "Offer already answered (race condition)" };
1962
1383
  }
1963
1384
  return { success: true };
1964
1385
  }
1965
- async getAnsweredOffers(offererUsername) {
1386
+ async getAnsweredOffers(offererPublicKey) {
1966
1387
  const result = await this.pool.query(
1967
1388
  `SELECT * FROM offers
1968
- WHERE username = $1 AND answerer_username IS NOT NULL AND expires_at > $2
1389
+ WHERE public_key = $1 AND answerer_public_key IS NOT NULL AND expires_at > $2
1969
1390
  ORDER BY answered_at DESC`,
1970
- [offererUsername, Date.now()]
1391
+ [offererPublicKey, Date.now()]
1971
1392
  );
1972
1393
  return result.rows.map((row) => this.rowToOffer(row));
1973
1394
  }
1974
- async getOffersAnsweredBy(answererUsername) {
1395
+ async getOffersAnsweredBy(answererPublicKey) {
1975
1396
  const result = await this.pool.query(
1976
1397
  `SELECT * FROM offers
1977
- WHERE answerer_username = $1 AND expires_at > $2
1398
+ WHERE answerer_public_key = $1 AND expires_at > $2
1978
1399
  ORDER BY answered_at DESC`,
1979
- [answererUsername, Date.now()]
1400
+ [answererPublicKey, Date.now()]
1980
1401
  );
1981
1402
  return result.rows.map((row) => this.rowToOffer(row));
1982
1403
  }
1983
1404
  // ===== Discovery =====
1984
- async discoverOffers(tags, excludeUsername, limit, offset) {
1405
+ async discoverOffers(tags, excludePublicKey, limit, offset) {
1985
1406
  if (tags.length === 0) return [];
1986
1407
  let query = `
1987
1408
  SELECT DISTINCT o.* FROM offers o
1988
1409
  WHERE o.tags ?| $1
1989
1410
  AND o.expires_at > $2
1990
- AND o.answerer_username IS NULL
1411
+ AND o.answerer_public_key IS NULL
1991
1412
  `;
1992
1413
  const params = [tags, Date.now()];
1993
1414
  let paramIndex = 3;
1994
- if (excludeUsername) {
1995
- query += ` AND o.username != $${paramIndex}`;
1996
- params.push(excludeUsername);
1415
+ if (excludePublicKey) {
1416
+ query += ` AND o.public_key != $${paramIndex}`;
1417
+ params.push(excludePublicKey);
1997
1418
  paramIndex++;
1998
1419
  }
1999
1420
  query += ` ORDER BY o.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
@@ -2001,26 +1422,26 @@ var init_postgres = __esm({
2001
1422
  const result = await this.pool.query(query, params);
2002
1423
  return result.rows.map((row) => this.rowToOffer(row));
2003
1424
  }
2004
- async getRandomOffer(tags, excludeUsername) {
1425
+ async getRandomOffer(tags, excludePublicKey) {
2005
1426
  if (tags.length === 0) return null;
2006
1427
  let query = `
2007
1428
  SELECT DISTINCT o.* FROM offers o
2008
1429
  WHERE o.tags ?| $1
2009
1430
  AND o.expires_at > $2
2010
- AND o.answerer_username IS NULL
1431
+ AND o.answerer_public_key IS NULL
2011
1432
  `;
2012
1433
  const params = [tags, Date.now()];
2013
1434
  let paramIndex = 3;
2014
- if (excludeUsername) {
2015
- query += ` AND o.username != $${paramIndex}`;
2016
- params.push(excludeUsername);
1435
+ if (excludePublicKey) {
1436
+ query += ` AND o.public_key != $${paramIndex}`;
1437
+ params.push(excludePublicKey);
2017
1438
  }
2018
1439
  query += " ORDER BY RANDOM() LIMIT 1";
2019
1440
  const result = await this.pool.query(query, params);
2020
1441
  return result.rows.length > 0 ? this.rowToOffer(result.rows[0]) : null;
2021
1442
  }
2022
1443
  // ===== ICE Candidate Management =====
2023
- async addIceCandidates(offerId, username, role, candidates) {
1444
+ async addIceCandidates(offerId, publicKey, role, candidates) {
2024
1445
  if (candidates.length === 0) return 0;
2025
1446
  const baseTimestamp = Date.now();
2026
1447
  const client = await this.pool.connect();
@@ -2028,9 +1449,9 @@ var init_postgres = __esm({
2028
1449
  await client.query("BEGIN");
2029
1450
  for (let i = 0; i < candidates.length; i++) {
2030
1451
  await client.query(
2031
- `INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)
1452
+ `INSERT INTO ice_candidates (offer_id, public_key, role, candidate, created_at)
2032
1453
  VALUES ($1, $2, $3, $4, $5)`,
2033
- [offerId, username, role, JSON.stringify(candidates[i]), baseTimestamp + i]
1454
+ [offerId, publicKey, role, JSON.stringify(candidates[i]), baseTimestamp + i]
2034
1455
  );
2035
1456
  }
2036
1457
  await client.query("COMMIT");
@@ -2053,23 +1474,23 @@ var init_postgres = __esm({
2053
1474
  const result = await this.pool.query(query, params);
2054
1475
  return result.rows.map((row) => this.rowToIceCandidate(row));
2055
1476
  }
2056
- async getIceCandidatesForMultipleOffers(offerIds, username, since) {
1477
+ async getIceCandidatesForMultipleOffers(offerIds, publicKey, since) {
2057
1478
  const resultMap = /* @__PURE__ */ new Map();
2058
1479
  if (offerIds.length === 0) return resultMap;
2059
1480
  if (offerIds.length > 1e3) {
2060
1481
  throw new Error("Too many offer IDs (max 1000)");
2061
1482
  }
2062
1483
  let query = `
2063
- SELECT ic.*, o.username as offer_username
1484
+ SELECT ic.*, o.public_key as offer_public_key
2064
1485
  FROM ice_candidates ic
2065
1486
  INNER JOIN offers o ON o.id = ic.offer_id
2066
1487
  WHERE ic.offer_id = ANY($1)
2067
1488
  AND (
2068
- (o.username = $2 AND ic.role = 'answerer')
2069
- OR (o.answerer_username = $2 AND ic.role = 'offerer')
1489
+ (o.public_key = $2 AND ic.role = 'answerer')
1490
+ OR (o.answerer_public_key = $2 AND ic.role = 'offerer')
2070
1491
  )
2071
1492
  `;
2072
- const params = [offerIds, username];
1493
+ const params = [offerIds, publicKey];
2073
1494
  if (since !== void 0) {
2074
1495
  query += " AND ic.created_at > $3";
2075
1496
  params.push(since);
@@ -2085,86 +1506,6 @@ var init_postgres = __esm({
2085
1506
  }
2086
1507
  return resultMap;
2087
1508
  }
2088
- // ===== Credential Management =====
2089
- async generateCredentials(request) {
2090
- const now = Date.now();
2091
- const expiresAt = request.expiresAt || now + YEAR_IN_MS4;
2092
- const { generateCredentialName: generateCredentialName2, generateSecret: generateSecret2, encryptSecret: encryptSecret2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
2093
- let name;
2094
- if (request.name) {
2095
- const existing = await this.pool.query(
2096
- `SELECT name FROM credentials WHERE name = $1`,
2097
- [request.name]
2098
- );
2099
- if (existing.rows.length > 0) {
2100
- throw new Error("Username already taken");
2101
- }
2102
- name = request.name;
2103
- } else {
2104
- let attempts = 0;
2105
- const maxAttempts = 100;
2106
- while (attempts < maxAttempts) {
2107
- name = generateCredentialName2();
2108
- const existing = await this.pool.query(
2109
- `SELECT name FROM credentials WHERE name = $1`,
2110
- [name]
2111
- );
2112
- if (existing.rows.length === 0) break;
2113
- attempts++;
2114
- }
2115
- if (attempts >= maxAttempts) {
2116
- throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);
2117
- }
2118
- }
2119
- const secret = generateSecret2();
2120
- const encryptedSecret = await encryptSecret2(secret, this.masterEncryptionKey);
2121
- await this.pool.query(
2122
- `INSERT INTO credentials (name, secret, created_at, expires_at, last_used)
2123
- VALUES ($1, $2, $3, $4, $5)`,
2124
- [name, encryptedSecret, now, expiresAt, now]
2125
- );
2126
- return {
2127
- name,
2128
- secret,
2129
- createdAt: now,
2130
- expiresAt,
2131
- lastUsed: now
2132
- };
2133
- }
2134
- async getCredential(name) {
2135
- const result = await this.pool.query(
2136
- `SELECT * FROM credentials WHERE name = $1 AND expires_at > $2`,
2137
- [name, Date.now()]
2138
- );
2139
- if (result.rows.length === 0) return null;
2140
- try {
2141
- const { decryptSecret: decryptSecret2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
2142
- const decryptedSecret = await decryptSecret2(result.rows[0].secret, this.masterEncryptionKey);
2143
- return {
2144
- name: result.rows[0].name,
2145
- secret: decryptedSecret,
2146
- createdAt: Number(result.rows[0].created_at),
2147
- expiresAt: Number(result.rows[0].expires_at),
2148
- lastUsed: Number(result.rows[0].last_used)
2149
- };
2150
- } catch (error) {
2151
- console.error(`Failed to decrypt secret for credential '${name}':`, error);
2152
- return null;
2153
- }
2154
- }
2155
- async updateCredentialUsage(name, lastUsed, expiresAt) {
2156
- await this.pool.query(
2157
- `UPDATE credentials SET last_used = $1, expires_at = $2 WHERE name = $3`,
2158
- [lastUsed, expiresAt, name]
2159
- );
2160
- }
2161
- async deleteExpiredCredentials(now) {
2162
- const result = await this.pool.query(
2163
- `DELETE FROM credentials WHERE expires_at < $1`,
2164
- [now]
2165
- );
2166
- return result.rowCount ?? 0;
2167
- }
2168
1509
  // ===== Rate Limiting =====
2169
1510
  async checkRateLimit(identifier, limit, windowMs) {
2170
1511
  const now = Date.now();
@@ -2223,17 +1564,13 @@ var init_postgres = __esm({
2223
1564
  const result = await this.pool.query("SELECT COUNT(*) as count FROM offers");
2224
1565
  return Number(result.rows[0].count);
2225
1566
  }
2226
- async getOfferCountByUsername(username) {
1567
+ async getOfferCountByPublicKey(publicKey) {
2227
1568
  const result = await this.pool.query(
2228
- "SELECT COUNT(*) as count FROM offers WHERE username = $1",
2229
- [username]
1569
+ "SELECT COUNT(*) as count FROM offers WHERE public_key = $1",
1570
+ [publicKey]
2230
1571
  );
2231
1572
  return Number(result.rows[0].count);
2232
1573
  }
2233
- async getCredentialCount() {
2234
- const result = await this.pool.query("SELECT COUNT(*) as count FROM credentials");
2235
- return Number(result.rows[0].count);
2236
- }
2237
1574
  async getIceCandidateCount(offerId) {
2238
1575
  const result = await this.pool.query(
2239
1576
  "SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = $1",
@@ -2245,13 +1582,13 @@ var init_postgres = __esm({
2245
1582
  rowToOffer(row) {
2246
1583
  return {
2247
1584
  id: row.id,
2248
- username: row.username,
1585
+ publicKey: row.public_key.trim(),
2249
1586
  tags: typeof row.tags === "string" ? JSON.parse(row.tags) : row.tags,
2250
1587
  sdp: row.sdp,
2251
1588
  createdAt: Number(row.created_at),
2252
1589
  expiresAt: Number(row.expires_at),
2253
1590
  lastSeen: Number(row.last_seen),
2254
- answererUsername: row.answerer_username || void 0,
1591
+ answererPublicKey: row.answerer_public_key?.trim() || void 0,
2255
1592
  answerSdp: row.answer_sdp || void 0,
2256
1593
  answeredAt: row.answered_at ? Number(row.answered_at) : void 0,
2257
1594
  matchedTags: row.matched_tags || void 0
@@ -2261,7 +1598,7 @@ var init_postgres = __esm({
2261
1598
  return {
2262
1599
  id: Number(row.id),
2263
1600
  offerId: row.offer_id,
2264
- username: row.username,
1601
+ publicKey: row.public_key.trim(),
2265
1602
  role: row.role,
2266
1603
  candidate: typeof row.candidate === "string" ? JSON.parse(row.candidate) : row.candidate,
2267
1604
  createdAt: Number(row.created_at)
@@ -2278,10 +1615,610 @@ var import_node_server = require("@hono/node-server");
2278
1615
  var import_hono = require("hono");
2279
1616
  var import_cors = require("hono/cors");
2280
1617
 
1618
+ // src/crypto.ts
1619
+ var import_node_buffer = require("node:buffer");
1620
+
1621
+ // node_modules/@noble/ed25519/index.js
1622
+ var ed25519_CURVE = {
1623
+ p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
1624
+ n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
1625
+ h: 8n,
1626
+ a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
1627
+ d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
1628
+ Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
1629
+ Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n
1630
+ };
1631
+ var { p: P, n: N, Gx, Gy, a: _a, d: _d, h } = ed25519_CURVE;
1632
+ var L = 32;
1633
+ var L2 = 64;
1634
+ var captureTrace = (...args) => {
1635
+ if ("captureStackTrace" in Error && typeof Error.captureStackTrace === "function") {
1636
+ Error.captureStackTrace(...args);
1637
+ }
1638
+ };
1639
+ var err = (message = "") => {
1640
+ const e = new Error(message);
1641
+ captureTrace(e, err);
1642
+ throw e;
1643
+ };
1644
+ var isBig = (n) => typeof n === "bigint";
1645
+ var isStr = (s) => typeof s === "string";
1646
+ var isBytes = (a) => a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
1647
+ var abytes = (value, length, title = "") => {
1648
+ const bytes = isBytes(value);
1649
+ const len = value?.length;
1650
+ const needsLen = length !== void 0;
1651
+ if (!bytes || needsLen && len !== length) {
1652
+ const prefix = title && `"${title}" `;
1653
+ const ofLen = needsLen ? ` of length ${length}` : "";
1654
+ const got = bytes ? `length=${len}` : `type=${typeof value}`;
1655
+ err(prefix + "expected Uint8Array" + ofLen + ", got " + got);
1656
+ }
1657
+ return value;
1658
+ };
1659
+ var u8n = (len) => new Uint8Array(len);
1660
+ var u8fr = (buf) => Uint8Array.from(buf);
1661
+ var padh = (n, pad) => n.toString(16).padStart(pad, "0");
1662
+ var bytesToHex = (b) => Array.from(abytes(b)).map((e) => padh(e, 2)).join("");
1663
+ var C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
1664
+ var _ch = (ch) => {
1665
+ if (ch >= C._0 && ch <= C._9)
1666
+ return ch - C._0;
1667
+ if (ch >= C.A && ch <= C.F)
1668
+ return ch - (C.A - 10);
1669
+ if (ch >= C.a && ch <= C.f)
1670
+ return ch - (C.a - 10);
1671
+ return;
1672
+ };
1673
+ var hexToBytes = (hex) => {
1674
+ const e = "hex invalid";
1675
+ if (!isStr(hex))
1676
+ return err(e);
1677
+ const hl = hex.length;
1678
+ const al = hl / 2;
1679
+ if (hl % 2)
1680
+ return err(e);
1681
+ const array = u8n(al);
1682
+ for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
1683
+ const n1 = _ch(hex.charCodeAt(hi));
1684
+ const n2 = _ch(hex.charCodeAt(hi + 1));
1685
+ if (n1 === void 0 || n2 === void 0)
1686
+ return err(e);
1687
+ array[ai] = n1 * 16 + n2;
1688
+ }
1689
+ return array;
1690
+ };
1691
+ var cr = () => globalThis?.crypto;
1692
+ var subtle = () => cr()?.subtle ?? err("crypto.subtle must be defined, consider polyfill");
1693
+ var concatBytes = (...arrs) => {
1694
+ const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0));
1695
+ let pad = 0;
1696
+ arrs.forEach((a) => {
1697
+ r.set(a, pad);
1698
+ pad += a.length;
1699
+ });
1700
+ return r;
1701
+ };
1702
+ var big = BigInt;
1703
+ var assertRange = (n, min, max, msg = "bad number: out of range") => isBig(n) && min <= n && n < max ? n : err(msg);
1704
+ var M = (a, b = P) => {
1705
+ const r = a % b;
1706
+ return r >= 0n ? r : b + r;
1707
+ };
1708
+ var modN = (a) => M(a, N);
1709
+ var invert = (num, md) => {
1710
+ if (num === 0n || md <= 0n)
1711
+ err("no inverse n=" + num + " mod=" + md);
1712
+ let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
1713
+ while (a !== 0n) {
1714
+ const q = b / a, r = b % a;
1715
+ const m = x - u * q, n = y - v * q;
1716
+ b = a, a = r, x = u, y = v, u = m, v = n;
1717
+ }
1718
+ return b === 1n ? M(x, md) : err("no inverse");
1719
+ };
1720
+ var apoint = (p) => p instanceof Point ? p : err("Point expected");
1721
+ var B256 = 2n ** 256n;
1722
+ var Point = class _Point {
1723
+ static BASE;
1724
+ static ZERO;
1725
+ X;
1726
+ Y;
1727
+ Z;
1728
+ T;
1729
+ constructor(X, Y, Z, T) {
1730
+ const max = B256;
1731
+ this.X = assertRange(X, 0n, max);
1732
+ this.Y = assertRange(Y, 0n, max);
1733
+ this.Z = assertRange(Z, 1n, max);
1734
+ this.T = assertRange(T, 0n, max);
1735
+ Object.freeze(this);
1736
+ }
1737
+ static CURVE() {
1738
+ return ed25519_CURVE;
1739
+ }
1740
+ static fromAffine(p) {
1741
+ return new _Point(p.x, p.y, 1n, M(p.x * p.y));
1742
+ }
1743
+ /** RFC8032 5.1.3: Uint8Array to Point. */
1744
+ static fromBytes(hex, zip215 = false) {
1745
+ const d = _d;
1746
+ const normed = u8fr(abytes(hex, L));
1747
+ const lastByte = hex[31];
1748
+ normed[31] = lastByte & ~128;
1749
+ const y = bytesToNumLE(normed);
1750
+ const max = zip215 ? B256 : P;
1751
+ assertRange(y, 0n, max);
1752
+ const y2 = M(y * y);
1753
+ const u = M(y2 - 1n);
1754
+ const v = M(d * y2 + 1n);
1755
+ let { isValid, value: x } = uvRatio(u, v);
1756
+ if (!isValid)
1757
+ err("bad point: y not sqrt");
1758
+ const isXOdd = (x & 1n) === 1n;
1759
+ const isLastByteOdd = (lastByte & 128) !== 0;
1760
+ if (!zip215 && x === 0n && isLastByteOdd)
1761
+ err("bad point: x==0, isLastByteOdd");
1762
+ if (isLastByteOdd !== isXOdd)
1763
+ x = M(-x);
1764
+ return new _Point(x, y, 1n, M(x * y));
1765
+ }
1766
+ static fromHex(hex, zip215) {
1767
+ return _Point.fromBytes(hexToBytes(hex), zip215);
1768
+ }
1769
+ get x() {
1770
+ return this.toAffine().x;
1771
+ }
1772
+ get y() {
1773
+ return this.toAffine().y;
1774
+ }
1775
+ /** Checks if the point is valid and on-curve. */
1776
+ assertValidity() {
1777
+ const a = _a;
1778
+ const d = _d;
1779
+ const p = this;
1780
+ if (p.is0())
1781
+ return err("bad point: ZERO");
1782
+ const { X, Y, Z, T } = p;
1783
+ const X2 = M(X * X);
1784
+ const Y2 = M(Y * Y);
1785
+ const Z2 = M(Z * Z);
1786
+ const Z4 = M(Z2 * Z2);
1787
+ const aX2 = M(X2 * a);
1788
+ const left = M(Z2 * M(aX2 + Y2));
1789
+ const right = M(Z4 + M(d * M(X2 * Y2)));
1790
+ if (left !== right)
1791
+ return err("bad point: equation left != right (1)");
1792
+ const XY = M(X * Y);
1793
+ const ZT = M(Z * T);
1794
+ if (XY !== ZT)
1795
+ return err("bad point: equation left != right (2)");
1796
+ return this;
1797
+ }
1798
+ /** Equality check: compare points P&Q. */
1799
+ equals(other) {
1800
+ const { X: X1, Y: Y1, Z: Z1 } = this;
1801
+ const { X: X2, Y: Y2, Z: Z2 } = apoint(other);
1802
+ const X1Z2 = M(X1 * Z2);
1803
+ const X2Z1 = M(X2 * Z1);
1804
+ const Y1Z2 = M(Y1 * Z2);
1805
+ const Y2Z1 = M(Y2 * Z1);
1806
+ return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
1807
+ }
1808
+ is0() {
1809
+ return this.equals(I);
1810
+ }
1811
+ /** Flip point over y coordinate. */
1812
+ negate() {
1813
+ return new _Point(M(-this.X), this.Y, this.Z, M(-this.T));
1814
+ }
1815
+ /** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
1816
+ double() {
1817
+ const { X: X1, Y: Y1, Z: Z1 } = this;
1818
+ const a = _a;
1819
+ const A = M(X1 * X1);
1820
+ const B = M(Y1 * Y1);
1821
+ const C2 = M(2n * M(Z1 * Z1));
1822
+ const D = M(a * A);
1823
+ const x1y1 = X1 + Y1;
1824
+ const E = M(M(x1y1 * x1y1) - A - B);
1825
+ const G2 = D + B;
1826
+ const F = G2 - C2;
1827
+ const H = D - B;
1828
+ const X3 = M(E * F);
1829
+ const Y3 = M(G2 * H);
1830
+ const T3 = M(E * H);
1831
+ const Z3 = M(F * G2);
1832
+ return new _Point(X3, Y3, Z3, T3);
1833
+ }
1834
+ /** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
1835
+ add(other) {
1836
+ const { X: X1, Y: Y1, Z: Z1, T: T1 } = this;
1837
+ const { X: X2, Y: Y2, Z: Z2, T: T2 } = apoint(other);
1838
+ const a = _a;
1839
+ const d = _d;
1840
+ const A = M(X1 * X2);
1841
+ const B = M(Y1 * Y2);
1842
+ const C2 = M(T1 * d * T2);
1843
+ const D = M(Z1 * Z2);
1844
+ const E = M((X1 + Y1) * (X2 + Y2) - A - B);
1845
+ const F = M(D - C2);
1846
+ const G2 = M(D + C2);
1847
+ const H = M(B - a * A);
1848
+ const X3 = M(E * F);
1849
+ const Y3 = M(G2 * H);
1850
+ const T3 = M(E * H);
1851
+ const Z3 = M(F * G2);
1852
+ return new _Point(X3, Y3, Z3, T3);
1853
+ }
1854
+ subtract(other) {
1855
+ return this.add(apoint(other).negate());
1856
+ }
1857
+ /**
1858
+ * Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
1859
+ * Uses {@link wNAF} for base point.
1860
+ * Uses fake point to mitigate side-channel leakage.
1861
+ * @param n scalar by which point is multiplied
1862
+ * @param safe safe mode guards against timing attacks; unsafe mode is faster
1863
+ */
1864
+ multiply(n, safe = true) {
1865
+ if (!safe && (n === 0n || this.is0()))
1866
+ return I;
1867
+ assertRange(n, 1n, N);
1868
+ if (n === 1n)
1869
+ return this;
1870
+ if (this.equals(G))
1871
+ return wNAF(n).p;
1872
+ let p = I;
1873
+ let f = G;
1874
+ for (let d = this; n > 0n; d = d.double(), n >>= 1n) {
1875
+ if (n & 1n)
1876
+ p = p.add(d);
1877
+ else if (safe)
1878
+ f = f.add(d);
1879
+ }
1880
+ return p;
1881
+ }
1882
+ multiplyUnsafe(scalar) {
1883
+ return this.multiply(scalar, false);
1884
+ }
1885
+ /** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
1886
+ toAffine() {
1887
+ const { X, Y, Z } = this;
1888
+ if (this.equals(I))
1889
+ return { x: 0n, y: 1n };
1890
+ const iz = invert(Z, P);
1891
+ if (M(Z * iz) !== 1n)
1892
+ err("invalid inverse");
1893
+ const x = M(X * iz);
1894
+ const y = M(Y * iz);
1895
+ return { x, y };
1896
+ }
1897
+ toBytes() {
1898
+ const { x, y } = this.assertValidity().toAffine();
1899
+ const b = numTo32bLE(y);
1900
+ b[31] |= x & 1n ? 128 : 0;
1901
+ return b;
1902
+ }
1903
+ toHex() {
1904
+ return bytesToHex(this.toBytes());
1905
+ }
1906
+ clearCofactor() {
1907
+ return this.multiply(big(h), false);
1908
+ }
1909
+ isSmallOrder() {
1910
+ return this.clearCofactor().is0();
1911
+ }
1912
+ isTorsionFree() {
1913
+ let p = this.multiply(N / 2n, false).double();
1914
+ if (N % 2n)
1915
+ p = p.add(this);
1916
+ return p.is0();
1917
+ }
1918
+ };
1919
+ var G = new Point(Gx, Gy, 1n, M(Gx * Gy));
1920
+ var I = new Point(0n, 1n, 1n, 0n);
1921
+ Point.BASE = G;
1922
+ Point.ZERO = I;
1923
+ var numTo32bLE = (num) => hexToBytes(padh(assertRange(num, 0n, B256), L2)).reverse();
1924
+ var bytesToNumLE = (b) => big("0x" + bytesToHex(u8fr(abytes(b)).reverse()));
1925
+ var pow2 = (x, power) => {
1926
+ let r = x;
1927
+ while (power-- > 0n) {
1928
+ r *= r;
1929
+ r %= P;
1930
+ }
1931
+ return r;
1932
+ };
1933
+ var pow_2_252_3 = (x) => {
1934
+ const x2 = x * x % P;
1935
+ const b2 = x2 * x % P;
1936
+ const b4 = pow2(b2, 2n) * b2 % P;
1937
+ const b5 = pow2(b4, 1n) * x % P;
1938
+ const b10 = pow2(b5, 5n) * b5 % P;
1939
+ const b20 = pow2(b10, 10n) * b10 % P;
1940
+ const b40 = pow2(b20, 20n) * b20 % P;
1941
+ const b80 = pow2(b40, 40n) * b40 % P;
1942
+ const b160 = pow2(b80, 80n) * b80 % P;
1943
+ const b240 = pow2(b160, 80n) * b80 % P;
1944
+ const b250 = pow2(b240, 10n) * b10 % P;
1945
+ const pow_p_5_8 = pow2(b250, 2n) * x % P;
1946
+ return { pow_p_5_8, b2 };
1947
+ };
1948
+ var RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n;
1949
+ var uvRatio = (u, v) => {
1950
+ const v3 = M(v * v * v);
1951
+ const v7 = M(v3 * v3 * v);
1952
+ const pow = pow_2_252_3(u * v7).pow_p_5_8;
1953
+ let x = M(u * v3 * pow);
1954
+ const vx2 = M(v * x * x);
1955
+ const root1 = x;
1956
+ const root2 = M(x * RM1);
1957
+ const useRoot1 = vx2 === u;
1958
+ const useRoot2 = vx2 === M(-u);
1959
+ const noRoot = vx2 === M(-u * RM1);
1960
+ if (useRoot1)
1961
+ x = root1;
1962
+ if (useRoot2 || noRoot)
1963
+ x = root2;
1964
+ if ((M(x) & 1n) === 1n)
1965
+ x = M(-x);
1966
+ return { isValid: useRoot1 || useRoot2, value: x };
1967
+ };
1968
+ var modL_LE = (hash) => modN(bytesToNumLE(hash));
1969
+ var sha512a = (...m) => hashes.sha512Async(concatBytes(...m));
1970
+ var hashFinishA = (res) => sha512a(res.hashable).then(res.finish);
1971
+ var defaultVerifyOpts = { zip215: true };
1972
+ var _verify = (sig, msg, pub, opts = defaultVerifyOpts) => {
1973
+ sig = abytes(sig, L2);
1974
+ msg = abytes(msg);
1975
+ pub = abytes(pub, L);
1976
+ const { zip215 } = opts;
1977
+ let A;
1978
+ let R;
1979
+ let s;
1980
+ let SB;
1981
+ let hashable = Uint8Array.of();
1982
+ try {
1983
+ A = Point.fromBytes(pub, zip215);
1984
+ R = Point.fromBytes(sig.slice(0, L), zip215);
1985
+ s = bytesToNumLE(sig.slice(L, L2));
1986
+ SB = G.multiply(s, false);
1987
+ hashable = concatBytes(R.toBytes(), A.toBytes(), msg);
1988
+ } catch (error) {
1989
+ }
1990
+ const finish = (hashed) => {
1991
+ if (SB == null)
1992
+ return false;
1993
+ if (!zip215 && A.isSmallOrder())
1994
+ return false;
1995
+ const k = modL_LE(hashed);
1996
+ const RkA = R.add(A.multiply(k, false));
1997
+ return RkA.add(SB.negate()).clearCofactor().is0();
1998
+ };
1999
+ return { hashable, finish };
2000
+ };
2001
+ var verifyAsync = async (signature, message, publicKey, opts = defaultVerifyOpts) => hashFinishA(_verify(signature, message, publicKey, opts));
2002
+ var hashes = {
2003
+ sha512Async: async (message) => {
2004
+ const s = subtle();
2005
+ const m = concatBytes(message);
2006
+ return u8n(await s.digest("SHA-512", m.buffer));
2007
+ },
2008
+ sha512: void 0
2009
+ };
2010
+ var W = 8;
2011
+ var scalarBits = 256;
2012
+ var pwindows = Math.ceil(scalarBits / W) + 1;
2013
+ var pwindowSize = 2 ** (W - 1);
2014
+ var precompute = () => {
2015
+ const points = [];
2016
+ let p = G;
2017
+ let b = p;
2018
+ for (let w = 0; w < pwindows; w++) {
2019
+ b = p;
2020
+ points.push(b);
2021
+ for (let i = 1; i < pwindowSize; i++) {
2022
+ b = b.add(p);
2023
+ points.push(b);
2024
+ }
2025
+ p = b.double();
2026
+ }
2027
+ return points;
2028
+ };
2029
+ var Gpows = void 0;
2030
+ var ctneg = (cnd, p) => {
2031
+ const n = p.negate();
2032
+ return cnd ? n : p;
2033
+ };
2034
+ var wNAF = (n) => {
2035
+ const comp = Gpows || (Gpows = precompute());
2036
+ let p = I;
2037
+ let f = G;
2038
+ const pow_2_w = 2 ** W;
2039
+ const maxNum = pow_2_w;
2040
+ const mask = big(pow_2_w - 1);
2041
+ const shiftBy = big(W);
2042
+ for (let w = 0; w < pwindows; w++) {
2043
+ let wbits = Number(n & mask);
2044
+ n >>= shiftBy;
2045
+ if (wbits > pwindowSize) {
2046
+ wbits -= maxNum;
2047
+ n += 1n;
2048
+ }
2049
+ const off = w * pwindowSize;
2050
+ const offF = off;
2051
+ const offP = off + Math.abs(wbits) - 1;
2052
+ const isEven = w % 2 !== 0;
2053
+ const isNeg = wbits < 0;
2054
+ if (wbits === 0) {
2055
+ f = f.add(ctneg(isEven, comp[offF]));
2056
+ } else {
2057
+ p = p.add(ctneg(isNeg, comp[offP]));
2058
+ }
2059
+ }
2060
+ if (n !== 0n)
2061
+ err("invalid wnaf");
2062
+ return { p, f };
2063
+ };
2064
+
2065
+ // src/crypto.ts
2066
+ hashes.sha512Async = async (message) => {
2067
+ const hashBuffer = await crypto.subtle.digest("SHA-512", message);
2068
+ return new Uint8Array(hashBuffer);
2069
+ };
2070
+ function hexToBytes2(hex) {
2071
+ if (hex.length % 2 !== 0) {
2072
+ throw new Error("Hex string must have even length");
2073
+ }
2074
+ for (let i = 0; i < hex.length; i++) {
2075
+ const c = hex[i].toLowerCase();
2076
+ if ((c < "0" || c > "9") && (c < "a" || c > "f")) {
2077
+ throw new Error(`Invalid hex character at position ${i}: '${hex[i]}'`);
2078
+ }
2079
+ }
2080
+ const match = hex.match(/.{1,2}/g);
2081
+ if (!match) {
2082
+ throw new Error("Invalid hex string format");
2083
+ }
2084
+ return new Uint8Array(match.map((byte) => {
2085
+ const parsed = parseInt(byte, 16);
2086
+ if (isNaN(parsed)) {
2087
+ throw new Error(`Invalid hex byte: ${byte}`);
2088
+ }
2089
+ return parsed;
2090
+ }));
2091
+ }
2092
+ function canonicalJSON(obj, depth = 0) {
2093
+ const MAX_DEPTH = 100;
2094
+ if (depth > MAX_DEPTH) {
2095
+ throw new Error("Object nesting too deep for canonicalization");
2096
+ }
2097
+ if (obj === null) return "null";
2098
+ if (obj === void 0) return JSON.stringify(void 0);
2099
+ const type = typeof obj;
2100
+ if (type === "function") throw new Error("Functions are not supported in RPC parameters");
2101
+ if (type === "symbol" || type === "bigint") throw new Error(`${type} is not supported in RPC parameters`);
2102
+ if (type === "number" && !Number.isFinite(obj)) throw new Error("NaN and Infinity are not supported in RPC parameters");
2103
+ if (type !== "object") return JSON.stringify(obj);
2104
+ if (Array.isArray(obj)) {
2105
+ return "[" + obj.map((item) => canonicalJSON(item, depth + 1)).join(",") + "]";
2106
+ }
2107
+ const sortedKeys = Object.keys(obj).sort();
2108
+ const pairs = sortedKeys.map((key) => JSON.stringify(key) + ":" + canonicalJSON(obj[key], depth + 1));
2109
+ return "{" + pairs.join(",") + "}";
2110
+ }
2111
+ function buildSignatureMessage(timestamp, nonce, method, params) {
2112
+ if (nonce.length !== 36) {
2113
+ throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
2114
+ }
2115
+ if (nonce[8] !== "-" || nonce[13] !== "-" || nonce[18] !== "-" || nonce[23] !== "-") {
2116
+ throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
2117
+ }
2118
+ if (nonce[14] !== "4") {
2119
+ throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
2120
+ }
2121
+ const variant = nonce[19].toLowerCase();
2122
+ if (variant !== "8" && variant !== "9" && variant !== "a" && variant !== "b") {
2123
+ throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
2124
+ }
2125
+ const hexChars = nonce.replace(/-/g, "");
2126
+ for (let i = 0; i < hexChars.length; i++) {
2127
+ const c = hexChars[i].toLowerCase();
2128
+ if ((c < "0" || c > "9") && (c < "a" || c > "f")) {
2129
+ throw new Error("Nonce must be a valid UUID v4 (use crypto.randomUUID())");
2130
+ }
2131
+ }
2132
+ const paramsStr = canonicalJSON(params || {});
2133
+ return `${timestamp}:${nonce}:${method}:${paramsStr}`;
2134
+ }
2135
+ var ED25519_PUBLIC_KEY_LENGTH = 32;
2136
+ var ED25519_SIGNATURE_LENGTH = 64;
2137
+ function validatePublicKey(publicKey) {
2138
+ if (typeof publicKey !== "string") {
2139
+ return { valid: false, error: "Public key must be a string" };
2140
+ }
2141
+ if (publicKey.length !== ED25519_PUBLIC_KEY_LENGTH * 2) {
2142
+ return { valid: false, error: `Public key must be ${ED25519_PUBLIC_KEY_LENGTH * 2} hex characters (${ED25519_PUBLIC_KEY_LENGTH} bytes)` };
2143
+ }
2144
+ for (let i = 0; i < publicKey.length; i++) {
2145
+ const c = publicKey[i];
2146
+ if ((c < "0" || c > "9") && (c < "a" || c > "f")) {
2147
+ return { valid: false, error: `Invalid hex character at position ${i}: '${c}' (use lowercase hex)` };
2148
+ }
2149
+ }
2150
+ return { valid: true };
2151
+ }
2152
+ async function verifyEd25519Signature(publicKey, message, signature) {
2153
+ try {
2154
+ const pkValidation = validatePublicKey(publicKey);
2155
+ if (!pkValidation.valid) {
2156
+ console.error("Ed25519 verification error: invalid public key:", pkValidation.error);
2157
+ return false;
2158
+ }
2159
+ const publicKeyBytes = hexToBytes2(publicKey);
2160
+ const encoder = new TextEncoder();
2161
+ const messageBytes = encoder.encode(message);
2162
+ const signatureBytes = import_node_buffer.Buffer.from(signature, "base64");
2163
+ if (signatureBytes.length !== ED25519_SIGNATURE_LENGTH) {
2164
+ console.error(`Ed25519 verification error: signature length ${signatureBytes.length}, expected ${ED25519_SIGNATURE_LENGTH}`);
2165
+ return false;
2166
+ }
2167
+ return await verifyAsync(signatureBytes, messageBytes, publicKeyBytes);
2168
+ } catch (error) {
2169
+ console.error("Ed25519 verification error:", error);
2170
+ return false;
2171
+ }
2172
+ }
2173
+ var TAG_MIN_LENGTH = 1;
2174
+ var TAG_MAX_LENGTH = 64;
2175
+ var TAG_REGEX = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/;
2176
+ function validateTag(tag) {
2177
+ if (typeof tag !== "string") {
2178
+ return { valid: false, error: "Tag must be a string" };
2179
+ }
2180
+ if (tag.length < TAG_MIN_LENGTH) {
2181
+ return { valid: false, error: `Tag must be at least ${TAG_MIN_LENGTH} character` };
2182
+ }
2183
+ if (tag.length > TAG_MAX_LENGTH) {
2184
+ return { valid: false, error: `Tag must be at most ${TAG_MAX_LENGTH} characters` };
2185
+ }
2186
+ if (tag.length === 1) {
2187
+ if (!/^[a-z0-9]$/.test(tag)) {
2188
+ return { valid: false, error: "Tag must be lowercase alphanumeric" };
2189
+ }
2190
+ return { valid: true };
2191
+ }
2192
+ if (!TAG_REGEX.test(tag)) {
2193
+ return { valid: false, error: "Tag must be lowercase alphanumeric with optional dots/dashes, and start/end with alphanumeric" };
2194
+ }
2195
+ return { valid: true };
2196
+ }
2197
+ function validateTags(tags, maxTags = 20) {
2198
+ if (!Array.isArray(tags)) {
2199
+ return { valid: false, error: "Tags must be an array" };
2200
+ }
2201
+ if (tags.length === 0) {
2202
+ return { valid: false, error: "At least one tag is required" };
2203
+ }
2204
+ if (tags.length > maxTags) {
2205
+ return { valid: false, error: `Maximum ${maxTags} tags allowed` };
2206
+ }
2207
+ for (let i = 0; i < tags.length; i++) {
2208
+ const result = validateTag(tags[i]);
2209
+ if (!result.valid) {
2210
+ return { valid: false, error: `Tag ${i + 1}: ${result.error}` };
2211
+ }
2212
+ }
2213
+ const uniqueTags = new Set(tags);
2214
+ if (uniqueTags.size !== tags.length) {
2215
+ return { valid: false, error: "Duplicate tags are not allowed" };
2216
+ }
2217
+ return { valid: true };
2218
+ }
2219
+
2281
2220
  // src/rpc.ts
2282
- init_crypto();
2283
2221
  var MAX_PAGE_SIZE = 100;
2284
- var CREDENTIAL_RATE_WINDOW = 1e3;
2285
2222
  var REQUEST_RATE_WINDOW = 1e3;
2286
2223
  function getJsonDepth(obj, maxDepth, currentDepth = 0) {
2287
2224
  if (obj === null || typeof obj !== "object") {
@@ -2312,7 +2249,7 @@ var ErrorCodes = {
2312
2249
  AUTH_REQUIRED: "AUTH_REQUIRED",
2313
2250
  INVALID_CREDENTIALS: "INVALID_CREDENTIALS",
2314
2251
  // Validation errors
2315
- INVALID_NAME: "INVALID_NAME",
2252
+ INVALID_PUBLIC_KEY: "INVALID_PUBLIC_KEY",
2316
2253
  INVALID_TAG: "INVALID_TAG",
2317
2254
  INVALID_SDP: "INVALID_SDP",
2318
2255
  INVALID_PARAMS: "INVALID_PARAMS",
@@ -2353,108 +2290,29 @@ function validateTimestamp(timestamp, config) {
2353
2290
  throw new RpcError(ErrorCodes.INVALID_PARAMS, "Timestamp too far in future");
2354
2291
  }
2355
2292
  }
2356
- async function verifyRequestSignature(name, timestamp, nonce, signature, method, params, storage, config) {
2293
+ async function verifyRequestSignature(publicKey, timestamp, nonce, signature, method, params, storage, config) {
2357
2294
  validateTimestamp(timestamp, config);
2358
- const credential = await storage.getCredential(name);
2359
- if (!credential) {
2360
- throw new RpcError(ErrorCodes.INVALID_CREDENTIALS, "Invalid credentials");
2295
+ const pkValidation = validatePublicKey(publicKey);
2296
+ if (!pkValidation.valid) {
2297
+ throw new RpcError(ErrorCodes.INVALID_PUBLIC_KEY, pkValidation.error || "Invalid public key");
2361
2298
  }
2362
2299
  const message = buildSignatureMessage(timestamp, nonce, method, params);
2363
- const isValid = await verifySignature(credential.secret, message, signature);
2300
+ const isValid = await verifyEd25519Signature(publicKey, message, signature);
2364
2301
  if (!isValid) {
2365
2302
  throw new RpcError(ErrorCodes.INVALID_CREDENTIALS, "Invalid signature");
2366
2303
  }
2367
- const nonceKey = `nonce:${name}:${nonce}`;
2304
+ const nonceKey = `nonce:${publicKey}:${nonce}`;
2368
2305
  const nonceExpiresAt = timestamp + config.timestampMaxAge;
2369
2306
  const nonceIsNew = await storage.checkAndMarkNonce(nonceKey, nonceExpiresAt);
2370
2307
  if (!nonceIsNew) {
2371
2308
  throw new RpcError(ErrorCodes.INVALID_CREDENTIALS, "Nonce already used (replay attack detected)");
2372
2309
  }
2373
- const now = Date.now();
2374
- const credentialExpiresAt = now + 365 * 24 * 60 * 60 * 1e3;
2375
- await storage.updateCredentialUsage(name, now, credentialExpiresAt);
2376
2310
  }
2377
2311
  var handlers = {
2378
2312
  /**
2379
- * Generate new credentials (name + secret pair)
2380
- * No authentication required - this is how users get started
2381
- * SECURITY: Rate limited per IP to prevent abuse (database-backed for multi-instance support)
2382
- */
2383
- async generateCredentials(params, name, timestamp, signature, storage, config, request) {
2384
- const credentialCount = await storage.getCredentialCount();
2385
- if (credentialCount >= config.maxTotalCredentials) {
2386
- throw new RpcError(
2387
- ErrorCodes.STORAGE_FULL,
2388
- `Server credential limit reached (${config.maxTotalCredentials}). Try again later.`
2389
- );
2390
- }
2391
- let rateLimitKey;
2392
- let rateLimit;
2393
- if (!request.clientIp) {
2394
- console.warn("\u26A0\uFE0F WARNING: Unable to determine client IP for credential generation. Using global rate limit.");
2395
- rateLimitKey = "cred_gen:global_unknown";
2396
- rateLimit = 2;
2397
- } else {
2398
- rateLimitKey = `cred_gen:${request.clientIp}`;
2399
- rateLimit = config.credentialsPerIpPerSecond;
2400
- }
2401
- const allowed = await storage.checkRateLimit(
2402
- rateLimitKey,
2403
- rateLimit,
2404
- CREDENTIAL_RATE_WINDOW
2405
- );
2406
- if (!allowed) {
2407
- throw new RpcError(
2408
- ErrorCodes.RATE_LIMIT_EXCEEDED,
2409
- `Rate limit exceeded. Maximum ${rateLimit} credentials per second${request.clientIp ? " per IP" : " (global limit for unidentified IPs)"}.`
2410
- );
2411
- }
2412
- if (params.name !== void 0) {
2413
- if (typeof params.name !== "string") {
2414
- throw new RpcError(ErrorCodes.INVALID_PARAMS, "name must be a string");
2415
- }
2416
- const usernameValidation = validateUsername(params.name);
2417
- if (!usernameValidation.valid) {
2418
- throw new RpcError(ErrorCodes.INVALID_PARAMS, usernameValidation.error || "Invalid username");
2419
- }
2420
- }
2421
- if (params.expiresAt !== void 0) {
2422
- if (typeof params.expiresAt !== "number" || isNaN(params.expiresAt) || !Number.isFinite(params.expiresAt)) {
2423
- throw new RpcError(ErrorCodes.INVALID_PARAMS, "expiresAt must be a valid timestamp");
2424
- }
2425
- const now = Date.now();
2426
- if (params.expiresAt < now - 6e4) {
2427
- throw new RpcError(ErrorCodes.INVALID_PARAMS, "expiresAt cannot be in the past");
2428
- }
2429
- const maxFuture = now + 10 * 365 * 24 * 60 * 60 * 1e3;
2430
- if (params.expiresAt > maxFuture) {
2431
- throw new RpcError(ErrorCodes.INVALID_PARAMS, "expiresAt cannot be more than 10 years in the future");
2432
- }
2433
- }
2434
- try {
2435
- const credential = await storage.generateCredentials({
2436
- name: params.name,
2437
- expiresAt: params.expiresAt
2438
- });
2439
- return {
2440
- name: credential.name,
2441
- secret: credential.secret,
2442
- createdAt: credential.createdAt,
2443
- expiresAt: credential.expiresAt
2444
- };
2445
- } catch (error) {
2446
- if (error.message === "Username already taken") {
2447
- throw new RpcError(ErrorCodes.INVALID_PARAMS, "Username already taken");
2448
- }
2449
- throw error;
2450
- }
2451
- },
2452
- /**
2453
- * Discover offers by tags - Supports 2 modes:
2454
- * 1. Paginated discovery: tags array with limit/offset
2455
- * 2. Random discovery: tags array without limit (returns single random offer)
2313
+ * Discover offers by tags
2456
2314
  */
2457
- async discover(params, name, timestamp, signature, storage, config, request) {
2315
+ async discover(params, publicKey, timestamp, signature, storage, config, request) {
2458
2316
  const { tags, limit, offset } = params;
2459
2317
  const tagsValidation = validateTags(tags);
2460
2318
  if (!tagsValidation.valid) {
@@ -2469,17 +2327,17 @@ var handlers = {
2469
2327
  }
2470
2328
  const pageLimit = Math.min(Math.max(1, limit), MAX_PAGE_SIZE);
2471
2329
  const pageOffset = Math.max(0, offset || 0);
2472
- const excludeUsername2 = name || null;
2330
+ const excludePublicKey2 = publicKey || null;
2473
2331
  const offers = await storage.discoverOffers(
2474
2332
  tags,
2475
- excludeUsername2,
2333
+ excludePublicKey2,
2476
2334
  pageLimit,
2477
2335
  pageOffset
2478
2336
  );
2479
2337
  return {
2480
2338
  offers: offers.map((offer2) => ({
2481
2339
  offerId: offer2.id,
2482
- username: offer2.username,
2340
+ publicKey: offer2.publicKey,
2483
2341
  tags: offer2.tags,
2484
2342
  sdp: offer2.sdp,
2485
2343
  createdAt: offer2.createdAt,
@@ -2490,14 +2348,14 @@ var handlers = {
2490
2348
  offset: pageOffset
2491
2349
  };
2492
2350
  }
2493
- const excludeUsername = name || null;
2494
- const offer = await storage.getRandomOffer(tags, excludeUsername);
2351
+ const excludePublicKey = publicKey || null;
2352
+ const offer = await storage.getRandomOffer(tags, excludePublicKey);
2495
2353
  if (!offer) {
2496
2354
  throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, "No offers found matching tags");
2497
2355
  }
2498
2356
  return {
2499
2357
  offerId: offer.id,
2500
- username: offer.username,
2358
+ publicKey: offer.publicKey,
2501
2359
  tags: offer.tags,
2502
2360
  sdp: offer.sdp,
2503
2361
  createdAt: offer.createdAt,
@@ -2507,10 +2365,10 @@ var handlers = {
2507
2365
  /**
2508
2366
  * Publish offers with tags
2509
2367
  */
2510
- async publishOffer(params, name, timestamp, signature, storage, config, request) {
2368
+ async publishOffer(params, publicKey, timestamp, signature, storage, config, request) {
2511
2369
  const { tags, offers, ttl } = params;
2512
- if (!name) {
2513
- throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Name required for offer publishing");
2370
+ if (!publicKey) {
2371
+ throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Authentication required for offer publishing");
2514
2372
  }
2515
2373
  const tagsValidation = validateTags(tags);
2516
2374
  if (!tagsValidation.valid) {
@@ -2525,7 +2383,7 @@ var handlers = {
2525
2383
  `Too many offers (max ${config.maxOffersPerRequest})`
2526
2384
  );
2527
2385
  }
2528
- const userOfferCount = await storage.getOfferCountByUsername(name);
2386
+ const userOfferCount = await storage.getOfferCountByPublicKey(publicKey);
2529
2387
  if (userOfferCount + offers.length > config.maxOffersPerUser) {
2530
2388
  throw new RpcError(
2531
2389
  ErrorCodes.TOO_MANY_OFFERS_PER_USER,
@@ -2559,20 +2417,17 @@ var handlers = {
2559
2417
  }
2560
2418
  }
2561
2419
  const now = Date.now();
2562
- const offerTtl = ttl !== void 0 ? Math.min(
2563
- Math.max(ttl, config.offerMinTtl),
2564
- config.offerMaxTtl
2565
- ) : config.offerDefaultTtl;
2420
+ const offerTtl = ttl !== void 0 ? Math.min(Math.max(ttl, config.offerMinTtl), config.offerMaxTtl) : config.offerDefaultTtl;
2566
2421
  const expiresAt = now + offerTtl;
2567
2422
  const offerRequests = offers.map((offer) => ({
2568
- username: name,
2423
+ publicKey,
2569
2424
  tags,
2570
2425
  sdp: offer.sdp,
2571
2426
  expiresAt
2572
2427
  }));
2573
2428
  const createdOffers = await storage.createOffers(offerRequests);
2574
2429
  return {
2575
- username: name,
2430
+ publicKey,
2576
2431
  tags,
2577
2432
  offers: createdOffers.map((offer) => ({
2578
2433
  offerId: offer.id,
@@ -2587,26 +2442,26 @@ var handlers = {
2587
2442
  /**
2588
2443
  * Delete an offer by ID
2589
2444
  */
2590
- async deleteOffer(params, name, timestamp, signature, storage, config, request) {
2445
+ async deleteOffer(params, publicKey, timestamp, signature, storage, config, request) {
2591
2446
  const { offerId } = params;
2592
- if (!name) {
2593
- throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Name required");
2447
+ if (!publicKey) {
2448
+ throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Authentication required");
2594
2449
  }
2595
2450
  validateStringParam(offerId, "offerId");
2596
- const deleted = await storage.deleteOffer(offerId, name);
2451
+ const deleted = await storage.deleteOffer(offerId, publicKey);
2597
2452
  if (!deleted) {
2598
- throw new RpcError(ErrorCodes.NOT_AUTHORIZED, "Offer not found or not owned by this name");
2453
+ throw new RpcError(ErrorCodes.NOT_AUTHORIZED, "Offer not found or not owned by this identity");
2599
2454
  }
2600
2455
  return { success: true };
2601
2456
  },
2602
2457
  /**
2603
2458
  * Answer an offer
2604
2459
  */
2605
- async answerOffer(params, name, timestamp, signature, storage, config, request) {
2460
+ async answerOffer(params, publicKey, timestamp, signature, storage, config, request) {
2606
2461
  const { offerId, sdp, matchedTags } = params;
2607
2462
  validateStringParam(offerId, "offerId");
2608
- if (!name) {
2609
- throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Name required");
2463
+ if (!publicKey) {
2464
+ throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Authentication required");
2610
2465
  }
2611
2466
  if (!sdp || typeof sdp !== "string" || sdp.length === 0) {
2612
2467
  throw new RpcError(ErrorCodes.INVALID_SDP, "Invalid SDP");
@@ -2621,7 +2476,7 @@ var handlers = {
2621
2476
  if (!offer) {
2622
2477
  throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, "Offer not found");
2623
2478
  }
2624
- if (offer.answererUsername) {
2479
+ if (offer.answererPublicKey) {
2625
2480
  throw new RpcError(ErrorCodes.OFFER_ALREADY_ANSWERED, "Offer already answered");
2626
2481
  }
2627
2482
  if (matchedTags && matchedTags.length > 0) {
@@ -2631,53 +2486,53 @@ var handlers = {
2631
2486
  throw new RpcError(ErrorCodes.INVALID_PARAMS, `matchedTags contains tags not on offer: ${invalidTags.join(", ")}`);
2632
2487
  }
2633
2488
  }
2634
- await storage.answerOffer(offerId, name, sdp, matchedTags);
2489
+ await storage.answerOffer(offerId, publicKey, sdp, matchedTags);
2635
2490
  return { success: true, offerId };
2636
2491
  },
2637
2492
  /**
2638
2493
  * Get answer for an offer
2639
2494
  */
2640
- async getOfferAnswer(params, name, timestamp, signature, storage, config, request) {
2495
+ async getOfferAnswer(params, publicKey, timestamp, signature, storage, config, request) {
2641
2496
  const { offerId } = params;
2642
2497
  validateStringParam(offerId, "offerId");
2643
- if (!name) {
2644
- throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Name required");
2498
+ if (!publicKey) {
2499
+ throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Authentication required");
2645
2500
  }
2646
2501
  const offer = await storage.getOfferById(offerId);
2647
2502
  if (!offer) {
2648
2503
  throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, "Offer not found");
2649
2504
  }
2650
- if (offer.username !== name) {
2505
+ if (offer.publicKey !== publicKey) {
2651
2506
  throw new RpcError(ErrorCodes.NOT_AUTHORIZED, "Not authorized to access this offer");
2652
2507
  }
2653
- if (!offer.answererUsername || !offer.answerSdp) {
2508
+ if (!offer.answererPublicKey || !offer.answerSdp) {
2654
2509
  throw new RpcError(ErrorCodes.OFFER_NOT_ANSWERED, "Offer not yet answered");
2655
2510
  }
2656
2511
  return {
2657
2512
  sdp: offer.answerSdp,
2658
2513
  offerId: offer.id,
2659
- answererId: offer.answererUsername,
2514
+ answererPublicKey: offer.answererPublicKey,
2660
2515
  answeredAt: offer.answeredAt
2661
2516
  };
2662
2517
  },
2663
2518
  /**
2664
2519
  * Combined polling for answers and ICE candidates
2665
2520
  */
2666
- async poll(params, name, timestamp, signature, storage, config, request) {
2521
+ async poll(params, publicKey, timestamp, signature, storage, config, request) {
2667
2522
  const { since } = params;
2668
- if (!name) {
2669
- throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Name required");
2523
+ if (!publicKey) {
2524
+ throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Authentication required");
2670
2525
  }
2671
2526
  if (since !== void 0 && (typeof since !== "number" || since < 0 || !Number.isFinite(since))) {
2672
2527
  throw new RpcError(ErrorCodes.INVALID_PARAMS, "Invalid since parameter: must be a non-negative number");
2673
2528
  }
2674
2529
  const sinceTimestamp = since !== void 0 ? since : 0;
2675
- const answeredOffers = await storage.getAnsweredOffers(name);
2530
+ const answeredOffers = await storage.getAnsweredOffers(publicKey);
2676
2531
  const filteredAnswers = answeredOffers.filter(
2677
2532
  (offer) => offer.answeredAt && offer.answeredAt > sinceTimestamp
2678
2533
  );
2679
- const ownedOffers = await storage.getOffersByUsername(name);
2680
- const answeredByUser = await storage.getOffersAnsweredBy(name);
2534
+ const ownedOffers = await storage.getOffersByPublicKey(publicKey);
2535
+ const answeredByUser = await storage.getOffersAnsweredBy(publicKey);
2681
2536
  const allOfferIds = [
2682
2537
  ...ownedOffers.map((offer) => offer.id),
2683
2538
  ...answeredByUser.map((offer) => offer.id)
@@ -2685,7 +2540,7 @@ var handlers = {
2685
2540
  const offerIds = [...new Set(allOfferIds)];
2686
2541
  const iceCandidatesMap = await storage.getIceCandidatesForMultipleOffers(
2687
2542
  offerIds,
2688
- name,
2543
+ publicKey,
2689
2544
  sinceTimestamp
2690
2545
  );
2691
2546
  const iceCandidatesByOffer = {};
@@ -2695,7 +2550,7 @@ var handlers = {
2695
2550
  return {
2696
2551
  answers: filteredAnswers.map((offer) => ({
2697
2552
  offerId: offer.id,
2698
- answererId: offer.answererUsername,
2553
+ answererPublicKey: offer.answererPublicKey,
2699
2554
  sdp: offer.answerSdp,
2700
2555
  answeredAt: offer.answeredAt
2701
2556
  })),
@@ -2705,11 +2560,11 @@ var handlers = {
2705
2560
  /**
2706
2561
  * Add ICE candidates
2707
2562
  */
2708
- async addIceCandidates(params, name, timestamp, signature, storage, config, request) {
2563
+ async addIceCandidates(params, publicKey, timestamp, signature, storage, config, request) {
2709
2564
  const { offerId, candidates } = params;
2710
2565
  validateStringParam(offerId, "offerId");
2711
- if (!name) {
2712
- throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Name required");
2566
+ if (!publicKey) {
2567
+ throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Authentication required");
2713
2568
  }
2714
2569
  if (!Array.isArray(candidates) || candidates.length === 0) {
2715
2570
  throw new RpcError(ErrorCodes.MISSING_PARAMS, "Missing or invalid required parameter: candidates");
@@ -2755,10 +2610,10 @@ var handlers = {
2755
2610
  `ICE candidate limit exceeded for offer. Current: ${currentCandidateCount}, limit: ${config.maxIceCandidatesPerOffer}.`
2756
2611
  );
2757
2612
  }
2758
- const role = offer.username === name ? "offerer" : "answerer";
2613
+ const role = offer.publicKey === publicKey ? "offerer" : "answerer";
2759
2614
  const count = await storage.addIceCandidates(
2760
2615
  offerId,
2761
- name,
2616
+ publicKey,
2762
2617
  role,
2763
2618
  candidates
2764
2619
  );
@@ -2767,11 +2622,11 @@ var handlers = {
2767
2622
  /**
2768
2623
  * Get ICE candidates
2769
2624
  */
2770
- async getIceCandidates(params, name, timestamp, signature, storage, config, request) {
2625
+ async getIceCandidates(params, publicKey, timestamp, signature, storage, config, request) {
2771
2626
  const { offerId, since } = params;
2772
2627
  validateStringParam(offerId, "offerId");
2773
- if (!name) {
2774
- throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Name required");
2628
+ if (!publicKey) {
2629
+ throw new RpcError(ErrorCodes.AUTH_REQUIRED, "Authentication required");
2775
2630
  }
2776
2631
  if (since !== void 0 && (typeof since !== "number" || since < 0 || !Number.isFinite(since))) {
2777
2632
  throw new RpcError(ErrorCodes.INVALID_PARAMS, "Invalid since parameter: must be a non-negative number");
@@ -2781,8 +2636,8 @@ var handlers = {
2781
2636
  if (!offer) {
2782
2637
  throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, "Offer not found");
2783
2638
  }
2784
- const isOfferer = offer.username === name;
2785
- const isAnswerer = offer.answererUsername === name;
2639
+ const isOfferer = offer.publicKey === publicKey;
2640
+ const isAnswerer = offer.answererPublicKey === publicKey;
2786
2641
  if (!isOfferer && !isAnswerer) {
2787
2642
  throw new RpcError(ErrorCodes.NOT_AUTHORIZED, "Not authorized to access ICE candidates for this offer");
2788
2643
  }
@@ -2801,12 +2656,10 @@ var handlers = {
2801
2656
  };
2802
2657
  }
2803
2658
  };
2804
- var UNAUTHENTICATED_METHODS = /* @__PURE__ */ new Set(["generateCredentials", "discover"]);
2659
+ var UNAUTHENTICATED_METHODS = /* @__PURE__ */ new Set(["discover"]);
2805
2660
  async function handleRpc(requests, ctx, storage, config) {
2806
2661
  const responses = [];
2807
- const clientIp = ctx.req.header("cf-connecting-ip") || // Cloudflare
2808
- ctx.req.header("x-real-ip") || // Nginx
2809
- ctx.req.header("x-forwarded-for")?.split(",")[0].trim() || void 0;
2662
+ const clientIp = ctx.req.header("cf-connecting-ip") || ctx.req.header("x-real-ip") || ctx.req.header("x-forwarded-for")?.split(",")[0].trim() || void 0;
2810
2663
  if (clientIp) {
2811
2664
  const rateLimitKey = `req:${clientIp}`;
2812
2665
  const allowed = await storage.checkRateLimit(
@@ -2822,7 +2675,7 @@ async function handleRpc(requests, ctx, storage, config) {
2822
2675
  }));
2823
2676
  }
2824
2677
  }
2825
- const name = ctx.req.header("X-Name");
2678
+ const publicKey = ctx.req.header("X-PublicKey");
2826
2679
  const timestampHeader = ctx.req.header("X-Timestamp");
2827
2680
  const nonce = ctx.req.header("X-Nonce");
2828
2681
  const signature = ctx.req.header("X-Signature");
@@ -2867,10 +2720,10 @@ async function handleRpc(requests, ctx, storage, config) {
2867
2720
  }
2868
2721
  const requiresAuth = !UNAUTHENTICATED_METHODS.has(method);
2869
2722
  if (requiresAuth) {
2870
- if (!name || typeof name !== "string") {
2723
+ if (!publicKey || typeof publicKey !== "string") {
2871
2724
  responses.push({
2872
2725
  success: false,
2873
- error: "Missing or invalid X-Name header",
2726
+ error: "Missing or invalid X-PublicKey header",
2874
2727
  errorCode: ErrorCodes.AUTH_REQUIRED
2875
2728
  });
2876
2729
  continue;
@@ -2900,7 +2753,7 @@ async function handleRpc(requests, ctx, storage, config) {
2900
2753
  continue;
2901
2754
  }
2902
2755
  await verifyRequestSignature(
2903
- name,
2756
+ publicKey,
2904
2757
  timestamp,
2905
2758
  nonce,
2906
2759
  signature,
@@ -2911,7 +2764,7 @@ async function handleRpc(requests, ctx, storage, config) {
2911
2764
  );
2912
2765
  const result = await handler(
2913
2766
  params || {},
2914
- name,
2767
+ publicKey,
2915
2768
  timestamp,
2916
2769
  signature,
2917
2770
  storage,
@@ -2925,11 +2778,9 @@ async function handleRpc(requests, ctx, storage, config) {
2925
2778
  } else {
2926
2779
  const result = await handler(
2927
2780
  params || {},
2928
- name || "",
2781
+ publicKey || "",
2929
2782
  0,
2930
- // timestamp
2931
2783
  "",
2932
- // signature
2933
2784
  storage,
2934
2785
  config,
2935
2786
  { ...request, clientIp }
@@ -2939,15 +2790,15 @@ async function handleRpc(requests, ctx, storage, config) {
2939
2790
  result
2940
2791
  });
2941
2792
  }
2942
- } catch (err) {
2943
- if (err instanceof RpcError) {
2793
+ } catch (err2) {
2794
+ if (err2 instanceof RpcError) {
2944
2795
  responses.push({
2945
2796
  success: false,
2946
- error: err.message,
2947
- errorCode: err.errorCode
2797
+ error: err2.message,
2798
+ errorCode: err2.errorCode
2948
2799
  });
2949
2800
  } else {
2950
- console.error("Unexpected RPC error:", err);
2801
+ console.error("Unexpected RPC error:", err2);
2951
2802
  responses.push({
2952
2803
  success: false,
2953
2804
  error: "Internal server error",
@@ -2973,7 +2824,7 @@ function createApp(storage, config) {
2973
2824
  return config.corsOrigins[0];
2974
2825
  },
2975
2826
  allowMethods: ["GET", "POST", "OPTIONS"],
2976
- allowHeaders: ["Content-Type", "Origin", "X-Name", "X-Timestamp", "X-Nonce", "X-Signature"],
2827
+ allowHeaders: ["Content-Type", "Origin", "X-PublicKey", "X-Timestamp", "X-Nonce", "X-Signature"],
2977
2828
  exposeHeaders: ["Content-Type"],
2978
2829
  credentials: false,
2979
2830
  maxAge: 86400
@@ -2982,7 +2833,7 @@ function createApp(storage, config) {
2982
2833
  return c.json({
2983
2834
  version: config.version,
2984
2835
  name: "Rondevu",
2985
- description: "WebRTC signaling with RPC interface and HMAC signature-based authentication"
2836
+ description: "WebRTC signaling with RPC interface and Ed25519 signature-based authentication"
2986
2837
  }, 200);
2987
2838
  });
2988
2839
  app.get("/health", (c) => {
@@ -3019,9 +2870,9 @@ function createApp(storage, config) {
3019
2870
  }
3020
2871
  const responses = await handleRpc(requests, c, storage, config);
3021
2872
  return c.json(responses, 200);
3022
- } catch (err) {
3023
- console.error("RPC error:", err);
3024
- const errorMsg = err instanceof SyntaxError ? "Invalid JSON in request body" : "Request must be valid JSON array";
2873
+ } catch (err2) {
2874
+ console.error("RPC error:", err2);
2875
+ const errorMsg = err2 instanceof SyntaxError ? "Invalid JSON in request body" : "Request must be valid JSON array";
3025
2876
  return c.json([{
3026
2877
  success: false,
3027
2878
  error: errorMsg,
@@ -3038,24 +2889,8 @@ function createApp(storage, config) {
3038
2889
  }
3039
2890
 
3040
2891
  // src/config.ts
3041
- var BUILD_VERSION = true ? "0.5.12" : "unknown";
2892
+ var BUILD_VERSION = true ? "0.5.14" : "unknown";
3042
2893
  function loadConfig() {
3043
- let masterEncryptionKey = process.env.MASTER_ENCRYPTION_KEY;
3044
- if (!masterEncryptionKey) {
3045
- const isDevelopment = process.env.NODE_ENV === "development";
3046
- if (!isDevelopment) {
3047
- throw new Error(
3048
- "MASTER_ENCRYPTION_KEY environment variable must be set. Generate with: openssl rand -hex 32\nFor development only, set NODE_ENV=development to use insecure dev key."
3049
- );
3050
- }
3051
- console.error("\u26A0\uFE0F WARNING: Using insecure deterministic development key");
3052
- console.error("\u26A0\uFE0F ONLY use NODE_ENV=development for local development");
3053
- console.error("\u26A0\uFE0F Generate production key with: openssl rand -hex 32");
3054
- masterEncryptionKey = "a3f8b9c2d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0";
3055
- }
3056
- if (masterEncryptionKey.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(masterEncryptionKey)) {
3057
- throw new Error("MASTER_ENCRYPTION_KEY must be 64-character hex string (32 bytes). Generate with: openssl rand -hex 32");
3058
- }
3059
2894
  function parsePositiveInt(value, defaultValue, name, min = 1) {
3060
2895
  const parsed = parseInt(value || defaultValue, 10);
3061
2896
  if (isNaN(parsed)) {
@@ -3091,13 +2926,10 @@ function loadConfig() {
3091
2926
  // Min 1 second
3092
2927
  timestampMaxFuture: parsePositiveInt(process.env.TIMESTAMP_MAX_FUTURE, "60000", "TIMESTAMP_MAX_FUTURE", 1e3),
3093
2928
  // Min 1 second
3094
- masterEncryptionKey,
3095
2929
  // Resource limits
3096
2930
  maxOffersPerUser: parsePositiveInt(process.env.MAX_OFFERS_PER_USER, "1000", "MAX_OFFERS_PER_USER", 1),
3097
2931
  maxTotalOffers: parsePositiveInt(process.env.MAX_TOTAL_OFFERS, "100000", "MAX_TOTAL_OFFERS", 1),
3098
- maxTotalCredentials: parsePositiveInt(process.env.MAX_TOTAL_CREDENTIALS, "50000", "MAX_TOTAL_CREDENTIALS", 1),
3099
2932
  maxIceCandidatesPerOffer: parsePositiveInt(process.env.MAX_ICE_CANDIDATES_PER_OFFER, "50", "MAX_ICE_CANDIDATES_PER_OFFER", 1),
3100
- credentialsPerIpPerSecond: parsePositiveInt(process.env.CREDENTIALS_PER_IP_PER_SECOND, "5", "CREDENTIALS_PER_IP_PER_SECOND", 1),
3101
2933
  requestsPerIpPerSecond: parsePositiveInt(process.env.REQUESTS_PER_IP_PER_SECOND, "50", "REQUESTS_PER_IP_PER_SECOND", 1)
3102
2934
  };
3103
2935
  return config;
@@ -3119,17 +2951,14 @@ var CONFIG_DEFAULTS = {
3119
2951
  // Resource limits
3120
2952
  maxOffersPerUser: 1e3,
3121
2953
  maxTotalOffers: 1e5,
3122
- maxTotalCredentials: 5e4,
3123
2954
  maxIceCandidatesPerOffer: 50,
3124
- credentialsPerIpPerSecond: 5,
3125
2955
  requestsPerIpPerSecond: 50
3126
2956
  };
3127
2957
  async function runCleanup(storage, now) {
3128
2958
  const offers = await storage.deleteExpiredOffers(now);
3129
- const credentials = await storage.deleteExpiredCredentials(now);
3130
2959
  const rateLimits = await storage.deleteExpiredRateLimits(now);
3131
2960
  const nonces = await storage.deleteExpiredNonces(now);
3132
- return { offers, credentials, rateLimits, nonces };
2961
+ return { offers, rateLimits, nonces };
3133
2962
  }
3134
2963
 
3135
2964
  // src/storage/factory.ts
@@ -3137,36 +2966,25 @@ async function createStorage(config) {
3137
2966
  switch (config.type) {
3138
2967
  case "memory": {
3139
2968
  const { MemoryStorage: MemoryStorage2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
3140
- return new MemoryStorage2(config.masterEncryptionKey);
2969
+ return new MemoryStorage2();
3141
2970
  }
3142
2971
  case "sqlite": {
3143
2972
  const { SQLiteStorage: SQLiteStorage2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
3144
- return new SQLiteStorage2(
3145
- config.sqlitePath || ":memory:",
3146
- config.masterEncryptionKey
3147
- );
2973
+ return new SQLiteStorage2(config.sqlitePath || ":memory:");
3148
2974
  }
3149
2975
  case "mysql": {
3150
2976
  if (!config.connectionString) {
3151
2977
  throw new Error("MySQL storage requires DATABASE_URL connection string");
3152
2978
  }
3153
2979
  const { MySQLStorage: MySQLStorage2 } = await Promise.resolve().then(() => (init_mysql(), mysql_exports));
3154
- return MySQLStorage2.create(
3155
- config.connectionString,
3156
- config.masterEncryptionKey,
3157
- config.poolSize || 10
3158
- );
2980
+ return MySQLStorage2.create(config.connectionString, config.poolSize || 10);
3159
2981
  }
3160
2982
  case "postgres": {
3161
2983
  if (!config.connectionString) {
3162
2984
  throw new Error("PostgreSQL storage requires DATABASE_URL connection string");
3163
2985
  }
3164
2986
  const { PostgreSQLStorage: PostgreSQLStorage2 } = await Promise.resolve().then(() => (init_postgres(), postgres_exports));
3165
- return PostgreSQLStorage2.create(
3166
- config.connectionString,
3167
- config.masterEncryptionKey,
3168
- config.poolSize || 10
3169
- );
2987
+ return PostgreSQLStorage2.create(config.connectionString, config.poolSize || 10);
3170
2988
  }
3171
2989
  default:
3172
2990
  throw new Error(`Unsupported storage type: ${config.type}`);
@@ -3189,7 +3007,6 @@ async function main() {
3189
3007
  });
3190
3008
  const storage = await createStorage({
3191
3009
  type: config.storageType,
3192
- masterEncryptionKey: config.masterEncryptionKey,
3193
3010
  sqlitePath: config.storagePath,
3194
3011
  connectionString: config.databaseUrl,
3195
3012
  poolSize: config.dbPoolSize
@@ -3198,12 +3015,12 @@ async function main() {
3198
3015
  const cleanupTimer = setInterval(async () => {
3199
3016
  try {
3200
3017
  const result = await runCleanup(storage, Date.now());
3201
- const total = result.offers + result.credentials + result.rateLimits + result.nonces;
3018
+ const total = result.offers + result.rateLimits + result.nonces;
3202
3019
  if (total > 0) {
3203
- console.log(`Cleanup: ${result.offers} offers, ${result.credentials} credentials, ${result.rateLimits} rate limits, ${result.nonces} nonces`);
3020
+ console.log(`Cleanup: ${result.offers} offers, ${result.rateLimits} rate limits, ${result.nonces} nonces`);
3204
3021
  }
3205
- } catch (err) {
3206
- console.error("Cleanup error:", err);
3022
+ } catch (err2) {
3023
+ console.error("Cleanup error:", err2);
3207
3024
  }
3208
3025
  }, config.cleanupInterval);
3209
3026
  const app = createApp(storage, config);
@@ -3222,8 +3039,13 @@ async function main() {
3222
3039
  process.on("SIGINT", shutdown);
3223
3040
  process.on("SIGTERM", shutdown);
3224
3041
  }
3225
- main().catch((err) => {
3226
- console.error("Fatal error:", err);
3042
+ main().catch((err2) => {
3043
+ console.error("Fatal error:", err2);
3227
3044
  process.exit(1);
3228
3045
  });
3046
+ /*! Bundled license information:
3047
+
3048
+ @noble/ed25519/index.js:
3049
+ (*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) *)
3050
+ */
3229
3051
  //# sourceMappingURL=index.js.map