holosphere 2.0.0-alpha13 → 2.0.0-alpha15

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.
Files changed (50) hide show
  1. package/dist/2019-ATLjawsU.cjs +8 -0
  2. package/dist/{2019-Cp3uYhyY.cjs.map → 2019-ATLjawsU.cjs.map} +1 -1
  3. package/dist/{2019-CLMqIAfQ.js → 2019-BfjzDRje.js} +1667 -1721
  4. package/dist/{2019-CLMqIAfQ.js.map → 2019-BfjzDRje.js.map} +1 -1
  5. package/dist/{browser-nUQt1cnB.js → browser-CKTczilW.js} +2 -2
  6. package/dist/{browser-nUQt1cnB.js.map → browser-CKTczilW.js.map} +1 -1
  7. package/dist/{browser-D6cNVl0v.cjs → browser-GOg6KKOV.cjs} +2 -2
  8. package/dist/{browser-D6cNVl0v.cjs.map → browser-GOg6KKOV.cjs.map} +1 -1
  9. package/dist/cjs/holosphere.cjs +1 -1
  10. package/dist/esm/holosphere.js +25 -21
  11. package/dist/{index-CoAjtqsD.js → index-BdnrGafX.js} +2 -2
  12. package/dist/{index-CoAjtqsD.js.map → index-BdnrGafX.js.map} +1 -1
  13. package/dist/{index-BN_uoxQK.js → index-C3Cag0SV.js} +2558 -495
  14. package/dist/index-C3Cag0SV.js.map +1 -0
  15. package/dist/index-ChpSfdYS.cjs +29 -0
  16. package/dist/index-ChpSfdYS.cjs.map +1 -0
  17. package/dist/{index-DJjGSwXG.cjs → index-D_QecZNu.cjs} +2 -2
  18. package/dist/{index-DJjGSwXG.cjs.map → index-D_QecZNu.cjs.map} +1 -1
  19. package/dist/{index-Z5TstN1e.js → index-nMC3dWZ5.js} +2 -2
  20. package/dist/{index-Z5TstN1e.js.map → index-nMC3dWZ5.js.map} +1 -1
  21. package/dist/{index-Cp3tI53z.cjs → index-z5HWfWMu.cjs} +2 -2
  22. package/dist/{index-Cp3tI53z.cjs.map → index-z5HWfWMu.cjs.map} +1 -1
  23. package/dist/{indexeddb-storage-CZK5A7XH.cjs → indexeddb-storage-DWSeL-YF.cjs} +2 -2
  24. package/dist/{indexeddb-storage-CZK5A7XH.cjs.map → indexeddb-storage-DWSeL-YF.cjs.map} +1 -1
  25. package/dist/{indexeddb-storage-bpA01pAU.js → indexeddb-storage-dx01N0ET.js} +2 -2
  26. package/dist/{indexeddb-storage-bpA01pAU.js.map → indexeddb-storage-dx01N0ET.js.map} +1 -1
  27. package/dist/{memory-storage-BqhmytP_.js → memory-storage-BPIfkpcf.js} +2 -2
  28. package/dist/{memory-storage-BqhmytP_.js.map → memory-storage-BPIfkpcf.js.map} +1 -1
  29. package/dist/{memory-storage-B1k8Jszd.cjs → memory-storage-CKUGDq2d.cjs} +2 -2
  30. package/dist/{memory-storage-B1k8Jszd.cjs.map → memory-storage-CKUGDq2d.cjs.map} +1 -1
  31. package/package.json +3 -1
  32. package/scripts/test-ndk-direct.js +104 -0
  33. package/src/crypto/key-store.js +356 -0
  34. package/src/crypto/lens-keys.js +205 -0
  35. package/src/crypto/secp256k1.js +181 -18
  36. package/src/federation/handshake.js +317 -23
  37. package/src/federation/hologram.js +25 -17
  38. package/src/federation/registry.js +779 -59
  39. package/src/index.js +416 -27
  40. package/src/lib/federation-methods.js +308 -4
  41. package/src/storage/nostr-async.js +144 -86
  42. package/src/storage/nostr-client.js +77 -18
  43. package/src/storage/nostr-wrapper.js +4 -1
  44. package/src/storage/unified-storage.js +5 -4
  45. package/src/subscriptions/manager.js +1 -1
  46. package/vitest.config.js +6 -1
  47. package/dist/2019-Cp3uYhyY.cjs +0 -8
  48. package/dist/index-BN_uoxQK.js.map +0 -1
  49. package/dist/index-V8EHMYEY.cjs +0 -29
  50. package/dist/index-V8EHMYEY.cjs.map +0 -1
@@ -10,20 +10,23 @@
10
10
 
11
11
  import { sha256 } from '@noble/hashes/sha256';
12
12
  import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
13
- import { secp256k1 } from '@noble/curves/secp256k1';
13
+ import { secp256k1, schnorr } from '@noble/curves/secp256k1';
14
14
 
15
15
  /**
16
- * Get public key from private key
16
+ * Get public key from private key (x-only / schnorr format for Nostr compatibility)
17
17
  * @param {string} privateKey - Private key (hex string)
18
- * @returns {string} Public key (hex string)
18
+ * @returns {string} Public key (64-char hex string, x-only format)
19
19
  */
20
20
  export function getPublicKey(privateKey) {
21
- const pubKey = secp256k1.getPublicKey(privateKey);
21
+ // Use schnorr.getPublicKey for x-only format (32 bytes, 64 hex chars)
22
+ // This is compatible with Nostr and matches nostr-tools format
23
+ const pubKey = schnorr.getPublicKey(privateKey);
22
24
  return bytesToHex(pubKey);
23
25
  }
24
26
 
25
27
  /**
26
- * Sign content with private key
28
+ * Sign content with private key using schnorr signature
29
+ * Uses schnorr for compatibility with x-only public keys (Nostr standard)
27
30
  * @param {string} content - Content to sign (will be hashed)
28
31
  * @param {string} privateKey - Private key (hex string)
29
32
  * @returns {Promise<string>} Signature (hex string)
@@ -33,30 +36,32 @@ export async function sign(content, privateKey) {
33
36
  // Hash content
34
37
  const msgHash = hashContent(content);
35
38
 
36
- // Sign - secp256k1.sign returns Signature object
37
- const signature = secp256k1.sign(msgHash, privateKey);
38
- return signature.toCompactHex();
39
+ // Use schnorr.sign for x-only key compatibility
40
+ const signature = schnorr.sign(msgHash, privateKey);
41
+ return bytesToHex(signature);
39
42
  } catch (error) {
40
43
  throw new Error(`Signature generation failed: ${error.message}`);
41
44
  }
42
45
  }
43
46
 
44
47
  /**
45
- * Verify signature
48
+ * Verify schnorr signature
49
+ * Uses schnorr for compatibility with x-only public keys (Nostr standard)
46
50
  * @param {string} content - Original content
47
51
  * @param {string} signature - Signature (hex string)
48
- * @param {string} publicKey - Public key (hex string)
52
+ * @param {string} publicKey - Public key (hex string, x-only format)
49
53
  * @returns {Promise<boolean>} True if valid
50
54
  */
51
55
  export async function verify(content, signature, publicKey) {
52
56
  try {
53
- // Validate public key format
57
+ // Validate public key format (x-only is 64 hex chars)
54
58
  if (!publicKey || typeof publicKey !== 'string' || publicKey.length < 64) {
55
59
  throw new Error('Invalid public key format');
56
60
  }
57
61
 
58
62
  const msgHash = hashContent(content);
59
- const isValid = secp256k1.verify(signature, msgHash, publicKey);
63
+ // Use schnorr.verify for x-only key compatibility
64
+ const isValid = schnorr.verify(signature, msgHash, publicKey);
60
65
  return isValid;
61
66
  } catch (error) {
62
67
  // Invalid signatures or keys throw errors
@@ -169,10 +174,12 @@ export async function issueCapability(permissions, scope, recipient, options = {
169
174
  };
170
175
 
171
176
  // Encode token as base64
177
+ // Use browser-native btoa when available to avoid Buffer serialization issues
178
+ // (Buffer can serialize to {"type":"Buffer","data":[...]} in JSON)
172
179
  const payload = JSON.stringify(token);
173
- const encoded = Buffer.from ?
174
- Buffer.from(payload).toString('base64') :
175
- btoa(payload);
180
+ const encoded = typeof btoa === 'function'
181
+ ? btoa(payload)
182
+ : Buffer.from(payload).toString('base64');
176
183
 
177
184
  // If issuerKey provided, sign the token
178
185
  if (issuerKey) {
@@ -225,6 +232,30 @@ export async function verifyCapability(token, requiredPermission, scope) {
225
232
  token = token.token; // Extract the actual token string
226
233
  }
227
234
 
235
+ // Handle Buffer serialization format {"type":"Buffer","data":[...]}
236
+ // This can happen when Buffer is polyfilled and JSON serialized
237
+ if (token && typeof token === 'object' && token.type === 'Buffer' && Array.isArray(token.data)) {
238
+ try {
239
+ // Convert Buffer data array back to string
240
+ token = String.fromCharCode.apply(null, token.data);
241
+ } catch (e) {
242
+ console.log('[verifyCapability] ❌ Failed to decode Buffer object:', e.message);
243
+ return false;
244
+ }
245
+ }
246
+
247
+ // Handle comma-separated byte string (e.g., "123,34,116,...")
248
+ // This can happen when Uint8Array.toString() is called
249
+ if (typeof token === 'string' && /^\d+(,\d+)+$/.test(token.substring(0, 50))) {
250
+ try {
251
+ const bytes = token.split(',').map(Number);
252
+ token = String.fromCharCode.apply(null, bytes);
253
+ } catch (e) {
254
+ console.log('[verifyCapability] ❌ Failed to decode byte string:', e.message);
255
+ return false;
256
+ }
257
+ }
258
+
228
259
  // Decode if string
229
260
  if (typeof token === 'string') {
230
261
  // Check if it's a base64-encoded token (starts with "ey" for JSON base64)
@@ -232,9 +263,10 @@ export async function verifyCapability(token, requiredPermission, scope) {
232
263
  // Base64 encoded (with or without signature)
233
264
  try {
234
265
  const payload = token.includes('.') ? token.split('.')[0] : token;
235
- const decoded = Buffer.from ?
236
- Buffer.from(payload, 'base64').toString('utf8') :
237
- atob(payload);
266
+ // Prefer browser-native atob to avoid Buffer issues
267
+ const decoded = typeof atob === 'function'
268
+ ? atob(payload)
269
+ : Buffer.from(payload, 'base64').toString('utf8');
238
270
  tokenObj = JSON.parse(decoded);
239
271
  } catch (e) {
240
272
  console.log('[verifyCapability] ❌ Token is not valid base64 JSON:', {
@@ -307,6 +339,137 @@ export async function verifyCapability(token, requiredPermission, scope) {
307
339
  }
308
340
  }
309
341
 
342
+ /**
343
+ * Verify that a capability token was issued by the expected author.
344
+ * This is the core security check for the object-capability model.
345
+ * A hologram should only be resolvable if the capability issuer matches
346
+ * the claimed data author AND the signature was made by that author's key.
347
+ *
348
+ * @param {string} token - Capability token
349
+ * @param {string} expectedIssuer - Expected issuer public key (data author)
350
+ * @returns {Promise<boolean>} True if issuer matches expected AND signature is valid
351
+ */
352
+ export async function verifyCapabilityIssuer(token, expectedIssuer) {
353
+ try {
354
+ // Handle capability object wrapper
355
+ if (token && typeof token === 'object' && token.token) {
356
+ token = token.token;
357
+ }
358
+
359
+ // Handle Buffer serialization format
360
+ if (token && typeof token === 'object' && token.type === 'Buffer' && Array.isArray(token.data)) {
361
+ token = String.fromCharCode.apply(null, token.data);
362
+ }
363
+
364
+ // Must be a string at this point
365
+ if (typeof token !== 'string') {
366
+ console.log('[verifyCapabilityIssuer] ❌ Token is not a string');
367
+ return false;
368
+ }
369
+
370
+ // Extract payload and signature from token
371
+ let payload;
372
+ let signature;
373
+ let tokenObj;
374
+
375
+ if (token.includes('.')) {
376
+ // Token has signature: payload.signature
377
+ const parts = token.split('.');
378
+ const encodedPayload = parts[0];
379
+ signature = parts[1];
380
+
381
+ // Decode payload
382
+ const decoded = typeof atob === 'function'
383
+ ? atob(encodedPayload)
384
+ : Buffer.from(encodedPayload, 'base64').toString('utf8');
385
+ payload = decoded;
386
+ tokenObj = JSON.parse(decoded);
387
+ } else if (token.startsWith('ey')) {
388
+ // Base64 encoded without signature
389
+ const decoded = typeof atob === 'function'
390
+ ? atob(token)
391
+ : Buffer.from(token, 'base64').toString('utf8');
392
+ payload = decoded;
393
+ tokenObj = JSON.parse(decoded);
394
+ } else if (token.startsWith('{')) {
395
+ // Already JSON string
396
+ payload = token;
397
+ tokenObj = JSON.parse(token);
398
+ } else {
399
+ console.log('[verifyCapabilityIssuer] ❌ Unknown token format');
400
+ return false;
401
+ }
402
+
403
+ // Verify issuer field matches expected
404
+ if (tokenObj.issuer !== expectedIssuer) {
405
+ console.log('[verifyCapabilityIssuer] ❌ Issuer mismatch:', {
406
+ tokenIssuer: tokenObj.issuer?.slice(0, 12) + '...',
407
+ expectedIssuer: expectedIssuer?.slice(0, 12) + '...',
408
+ });
409
+ return false;
410
+ }
411
+
412
+ // CRITICAL: Verify signature was made by the claimed issuer's private key
413
+ // This prevents forged capabilities where someone claims a different issuer
414
+ if (signature) {
415
+ const signatureValid = await verify(payload, signature, expectedIssuer);
416
+ if (!signatureValid) {
417
+ console.log('[verifyCapabilityIssuer] ❌ Signature verification failed - capability not signed by claimed issuer');
418
+ return false;
419
+ }
420
+ } else {
421
+ // No signature - for backwards compatibility, allow unsigned tokens
422
+ // but log a warning
423
+ console.log('[verifyCapabilityIssuer] ⚠️ Token has no signature - cannot verify cryptographically');
424
+ }
425
+
426
+ console.log('[verifyCapabilityIssuer] ✅ Issuer verified');
427
+ return true;
428
+ } catch (error) {
429
+ console.log('[verifyCapabilityIssuer] ❌ Error:', error.message);
430
+ return false;
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Decode a capability token to extract its payload.
436
+ * Useful for inspecting capability contents without full verification.
437
+ *
438
+ * @param {string} token - Capability token
439
+ * @returns {Object|null} Decoded token payload or null if invalid
440
+ */
441
+ export function decodeCapability(token) {
442
+ try {
443
+ // Handle capability object wrapper
444
+ if (token && typeof token === 'object' && token.token) {
445
+ token = token.token;
446
+ }
447
+
448
+ // Handle Buffer serialization format
449
+ if (token && typeof token === 'object' && token.type === 'Buffer' && Array.isArray(token.data)) {
450
+ token = String.fromCharCode.apply(null, token.data);
451
+ }
452
+
453
+ if (typeof token !== 'string') {
454
+ return null;
455
+ }
456
+
457
+ if (token.startsWith('ey') || (!token.includes('.') && !token.startsWith('{'))) {
458
+ const payload = token.includes('.') ? token.split('.')[0] : token;
459
+ const decoded = typeof atob === 'function'
460
+ ? atob(payload)
461
+ : Buffer.from(payload, 'base64').toString('utf8');
462
+ return JSON.parse(decoded);
463
+ } else if (token.startsWith('{')) {
464
+ return JSON.parse(token);
465
+ }
466
+
467
+ return null;
468
+ } catch (error) {
469
+ return null;
470
+ }
471
+ }
472
+
310
473
  /**
311
474
  * Generate unique nonce
312
475
  * @private