holosphere 2.0.0-alpha21 → 2.0.0-alpha23

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 (69) hide show
  1. package/README.md +1 -2
  2. package/dist/cjs/holosphere.cjs +1 -1
  3. package/dist/esm/holosphere.js +61 -58
  4. package/dist/{index-B6-8KAQm.js → index-BEkCLOwI.js} +2 -2
  5. package/dist/{index-B6-8KAQm.js.map → index-BEkCLOwI.js.map} +1 -1
  6. package/dist/{index-D2WstuZJ.js → index-BEvX6DxG.js} +2 -2
  7. package/dist/{index-D2WstuZJ.js.map → index-BEvX6DxG.js.map} +1 -1
  8. package/dist/{index--QsHG_gD.cjs → index-BGTOiJ2Y.cjs} +2 -2
  9. package/dist/{index--QsHG_gD.cjs.map → index-BGTOiJ2Y.cjs.map} +1 -1
  10. package/dist/{index-COpLk9gL.cjs → index-BH1woZXL.cjs} +2 -2
  11. package/dist/{index-COpLk9gL.cjs.map → index-BH1woZXL.cjs.map} +1 -1
  12. package/dist/{index-BHptWysv.js → index-Cvxov2jv.js} +2970 -7753
  13. package/dist/index-Cvxov2jv.js.map +1 -0
  14. package/dist/index-vTKI_BAX.cjs +29 -0
  15. package/dist/index-vTKI_BAX.cjs.map +1 -0
  16. package/dist/{indexeddb-storage-wKG4mICM.cjs → indexeddb-storage-BmnCNnSg.cjs} +2 -2
  17. package/dist/{indexeddb-storage-wKG4mICM.cjs.map → indexeddb-storage-BmnCNnSg.cjs.map} +1 -1
  18. package/dist/{indexeddb-storage-kQ53UHEE.js → indexeddb-storage-MIFisaPy.js} +2 -2
  19. package/dist/{indexeddb-storage-kQ53UHEE.js.map → indexeddb-storage-MIFisaPy.js.map} +1 -1
  20. package/dist/{memory-storage-CGC8xM2G.cjs → memory-storage-BJjK3F4r.cjs} +2 -2
  21. package/dist/{memory-storage-CGC8xM2G.cjs.map → memory-storage-BJjK3F4r.cjs.map} +1 -1
  22. package/dist/{memory-storage-DnXCSbBl.js → memory-storage-DhHXdKQ-.js} +2 -2
  23. package/dist/{memory-storage-DnXCSbBl.js.map → memory-storage-DhHXdKQ-.js.map} +1 -1
  24. package/examples/demo.html +2 -29
  25. package/package.json +3 -8
  26. package/src/content/social-protocols.js +3 -59
  27. package/src/core/holosphere.js +16 -554
  28. package/src/crypto/nostr-utils.js +98 -1
  29. package/src/crypto/secp256k1.js +4 -393
  30. package/src/federation/discovery.js +7 -75
  31. package/src/federation/handshake.js +69 -202
  32. package/src/federation/hologram.js +222 -298
  33. package/src/federation/index.js +2 -9
  34. package/src/federation/registry.js +67 -1257
  35. package/src/federation/request-card.js +21 -35
  36. package/src/hierarchical/upcast.js +4 -9
  37. package/src/index.js +145 -296
  38. package/src/lib/federation-methods.js +370 -909
  39. package/src/storage/global-tables.js +1 -1
  40. package/src/storage/nostr-wrapper.js +9 -5
  41. package/src/subscriptions/manager.js +1 -1
  42. package/types/index.d.ts +145 -37
  43. package/bin/holosphere-activitypub.js +0 -158
  44. package/dist/2019-BzVkRcax.js +0 -6680
  45. package/dist/2019-BzVkRcax.js.map +0 -1
  46. package/dist/2019-C1hPR_Os.cjs +0 -8
  47. package/dist/2019-C1hPR_Os.cjs.map +0 -1
  48. package/dist/browser-BcmACE3G.js +0 -3058
  49. package/dist/browser-BcmACE3G.js.map +0 -1
  50. package/dist/browser-DaqYUTcG.cjs +0 -2
  51. package/dist/browser-DaqYUTcG.cjs.map +0 -1
  52. package/dist/index-BHptWysv.js.map +0 -1
  53. package/dist/index-CDlhzxT2.cjs +0 -29
  54. package/dist/index-CDlhzxT2.cjs.map +0 -1
  55. package/src/federation/capabilities.js +0 -46
  56. package/src/storage/backend-factory.js +0 -130
  57. package/src/storage/backend-interface.js +0 -161
  58. package/src/storage/backends/activitypub/server.js +0 -675
  59. package/src/storage/backends/activitypub-backend.js +0 -295
  60. package/src/storage/backends/gundb-backend.js +0 -875
  61. package/src/storage/backends/nostr-backend.js +0 -251
  62. package/src/storage/gun-async.js +0 -341
  63. package/src/storage/gun-auth.js +0 -373
  64. package/src/storage/gun-federation.js +0 -785
  65. package/src/storage/gun-references.js +0 -209
  66. package/src/storage/gun-schema.js +0 -306
  67. package/src/storage/gun-wrapper.js +0 -642
  68. package/src/storage/migration.js +0 -351
  69. package/src/storage/unified-storage.js +0 -161
@@ -9,7 +9,7 @@
9
9
  * @module crypto/nostr-utils
10
10
  */
11
11
 
12
- import { nip04, nip44, nip19, getPublicKey as nostrGetPublicKey, finalizeEvent, verifyEvent as nostrVerifyEvent } from 'nostr-tools';
12
+ import { nip04, nip44, nip19, getPublicKey as nostrGetPublicKey, finalizeEvent, verifyEvent as nostrVerifyEvent, generateSecretKey } from 'nostr-tools';
13
13
 
14
14
  // Re-export NDK types for consumers who want to use NDK directly
15
15
  export { default as NDK, NDKEvent, NDKPrivateKeySigner, NDKUser, NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk';
@@ -113,6 +113,103 @@ export function npubToHex(npub) {
113
113
  }
114
114
  }
115
115
 
116
+ /**
117
+ * Convert a hex private key to nsec format.
118
+ * @param {string} hexPrivKey - Hex private key (64 characters)
119
+ * @returns {string} nsec-encoded private key (nsec1...)
120
+ */
121
+ export function hexToNsec(hexPrivKey) {
122
+ try {
123
+ return nip19.nsecEncode(hexToBytes(hexPrivKey));
124
+ } catch (e) {
125
+ console.error('Failed to encode hex to nsec:', e);
126
+ return hexPrivKey; // Return original on error
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Convert nsec to hex private key.
132
+ * @param {string} nsec - nsec-encoded private key string
133
+ * @returns {string|null} 64-character hex private key or null if invalid
134
+ */
135
+ export function nsecToHex(nsec) {
136
+ try {
137
+ const decoded = nip19.decode(nsec);
138
+ if (decoded.type === 'nsec') {
139
+ return bytesToHex(decoded.data);
140
+ }
141
+ return null;
142
+ } catch (e) {
143
+ return null;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Parse an nsec or hex private key string into hex format.
149
+ * Handles nostr: URI prefix and validates both nsec and hex formats.
150
+ * @param {string} input - nsec string (nsec1...) or 64-character hex private key
151
+ * @returns {Object} Validation result with valid, hexPrivKey (if valid), error (if invalid)
152
+ */
153
+ export function parseNsecOrHex(input) {
154
+ const trimmed = input?.trim();
155
+
156
+ if (!trimmed) {
157
+ return { valid: false, error: 'Private key is required' };
158
+ }
159
+
160
+ // Check if it's already hex (64 characters)
161
+ if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {
162
+ return { valid: true, hexPrivKey: trimmed.toLowerCase() };
163
+ }
164
+
165
+ // Handle nostr: URI prefix
166
+ let nsecString = trimmed;
167
+ if (nsecString.startsWith('nostr:')) {
168
+ nsecString = nsecString.slice(6);
169
+ }
170
+
171
+ // Try to decode as nsec
172
+ if (nsecString.startsWith('nsec1')) {
173
+ try {
174
+ const decoded = nip19.decode(nsecString);
175
+ if (decoded.type === 'nsec') {
176
+ return { valid: true, hexPrivKey: bytesToHex(decoded.data) };
177
+ }
178
+ return { valid: false, error: 'Invalid nsec format' };
179
+ } catch (e) {
180
+ return { valid: false, error: 'Invalid nsec: unable to decode' };
181
+ }
182
+ }
183
+
184
+ return { valid: false, error: 'Enter a valid nsec (nsec1...) or 64-character hex private key' };
185
+ }
186
+
187
+ /**
188
+ * Check if a string is a valid nsec (starts with nsec1 and decodes correctly).
189
+ * @param {string} str - String to validate
190
+ * @returns {boolean} True if valid nsec format
191
+ */
192
+ export function isValidNsec(str) {
193
+ if (typeof str !== 'string' || !str.startsWith('nsec1')) {
194
+ return false;
195
+ }
196
+ try {
197
+ const decoded = nip19.decode(str);
198
+ return decoded.type === 'nsec';
199
+ } catch {
200
+ return false;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Generate a new random private key.
206
+ * @returns {string} 64-character hex private key
207
+ */
208
+ export function generatePrivateKey() {
209
+ const secretKey = generateSecretKey();
210
+ return bytesToHex(secretKey);
211
+ }
212
+
116
213
  /**
117
214
  * Shorten a public key for display (first 8 and last 8 chars).
118
215
  * @param {string} pubKey - Public key in hex or npub format
@@ -1,16 +1,15 @@
1
1
  /**
2
2
  * @fileoverview Cryptographic Operations using secp256k1.
3
3
  *
4
- * Provides signing, verification, and capability token operations using
5
- * the secp256k1 elliptic curve (same curve used by Bitcoin and Nostr).
6
- * Uses lazy loading for the crypto module to improve startup performance.
4
+ * Provides signing and verification using the secp256k1 elliptic curve
5
+ * (same curve used by Bitcoin and Nostr).
7
6
  *
8
7
  * @module crypto/secp256k1
9
8
  */
10
9
 
11
10
  import { sha256 } from '@noble/hashes/sha256';
12
- import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
13
- import { secp256k1, schnorr } from '@noble/curves/secp256k1';
11
+ import { bytesToHex } from '@noble/hashes/utils';
12
+ import { schnorr } from '@noble/curves/secp256k1';
14
13
 
15
14
  /**
16
15
  * Check if a string is a 64-char hex public key (x-only schnorr format).
@@ -83,394 +82,6 @@ export async function verify(content, signature, publicKey) {
83
82
  }
84
83
  }
85
84
 
86
- /**
87
- * Normalize a capability token to a plain string.
88
- * Handles: object wrappers ({ token: "..." }), Buffer serialization, comma-separated byte strings.
89
- * @param {string|Object} token - Token in any supported format
90
- * @returns {string|null} Normalized token string, or null if invalid
91
- */
92
- export function normalizeTokenString(token) {
93
- // Handle capability object wrapper { token, scope, permissions }
94
- if (token && typeof token === 'object' && token.token) {
95
- token = token.token;
96
- }
97
-
98
- // Handle Buffer serialization format {"type":"Buffer","data":[...]}
99
- if (token && typeof token === 'object' && token.type === 'Buffer' && Array.isArray(token.data)) {
100
- try {
101
- token = String.fromCharCode.apply(null, token.data);
102
- } catch (e) {
103
- return null;
104
- }
105
- }
106
-
107
- // Handle comma-separated byte string (e.g., "123,34,116,...")
108
- if (typeof token === 'string' && /^\d+(,\d+)+$/.test(token.substring(0, 50))) {
109
- try {
110
- const bytes = token.split(',').map(Number);
111
- token = String.fromCharCode.apply(null, bytes);
112
- } catch (e) {
113
- return null;
114
- }
115
- }
116
-
117
- if (typeof token === 'string') {
118
- return token;
119
- }
120
-
121
- return null;
122
- }
123
-
124
- /**
125
- * Parse a capability token string into its components.
126
- * Handles base64-encoded, signed (payload.signature), and raw JSON formats.
127
- * @private
128
- * @param {string} token - Normalized token string
129
- * @returns {{ tokenObj: Object, payload: string, signature: string|null }|null} Parsed token or null
130
- */
131
- function _parseToken(token) {
132
- try {
133
- if (typeof token !== 'string') return null;
134
-
135
- let payload;
136
- let signature = null;
137
- let tokenObj;
138
-
139
- if (token.startsWith('ey') || (!token.includes('.') && !token.startsWith('{'))) {
140
- // Base64 encoded (with or without signature)
141
- if (token.includes('.')) {
142
- const parts = token.split('.');
143
- signature = parts[1];
144
- const decoded = typeof atob === 'function'
145
- ? atob(parts[0])
146
- : Buffer.from(parts[0], 'base64').toString('utf8');
147
- payload = decoded;
148
- } else {
149
- const decoded = typeof atob === 'function'
150
- ? atob(token)
151
- : Buffer.from(token, 'base64').toString('utf8');
152
- payload = decoded;
153
- }
154
- tokenObj = JSON.parse(payload);
155
- } else if (token.startsWith('{')) {
156
- payload = token;
157
- tokenObj = JSON.parse(token);
158
- } else {
159
- return null;
160
- }
161
-
162
- return { tokenObj, payload, signature };
163
- } catch (e) {
164
- return null;
165
- }
166
- }
167
-
168
- /**
169
- * Hash a capability token using SHA-256.
170
- * @param {string} token - Capability token string
171
- * @returns {string} Hex-encoded SHA-256 hash
172
- */
173
- export function hashToken(token) {
174
- const encoder = new TextEncoder();
175
- return bytesToHex(sha256(encoder.encode(token)));
176
- }
177
-
178
- /**
179
- * Match token scope against requested scope with wildcard support
180
- * Supports:
181
- * - Exact match: { holonId: "abc", lensName: "quests" }
182
- * - Item-level: { holonId: "abc", lensName: "quests", dataId: "quest-001" }
183
- * - Wildcards: { holonId: "*", lensName: "*" } matches everything
184
- * @param {Object} tokenScope - Scope from capability token
185
- * @param {Object} requestedScope - Scope being requested
186
- * @returns {boolean} True if token scope covers requested scope
187
- */
188
- export function matchScope(tokenScope, requestedScope) {
189
- // Handle string scopes (legacy support)
190
- if (typeof tokenScope === 'string' || typeof requestedScope === 'string') {
191
- const tokenStr = typeof tokenScope === 'string' ? tokenScope : JSON.stringify(tokenScope);
192
- const reqStr = typeof requestedScope === 'string' ? requestedScope : JSON.stringify(requestedScope);
193
- return tokenStr === reqStr;
194
- }
195
-
196
- // Handle holonId
197
- if (tokenScope.holonId !== '*' && tokenScope.holonId !== requestedScope.holonId) {
198
- return false;
199
- }
200
-
201
- // Handle lensName
202
- if (tokenScope.lensName !== '*' && tokenScope.lensName !== requestedScope.lensName) {
203
- return false;
204
- }
205
-
206
- // Handle optional dataId (item-level scope)
207
- // If token has specific dataId (not wildcard), it must match requested dataId
208
- if (tokenScope.dataId && tokenScope.dataId !== '*') {
209
- if (requestedScope.dataId && tokenScope.dataId !== requestedScope.dataId) {
210
- return false;
211
- }
212
- }
213
-
214
- return true;
215
- }
216
-
217
- /**
218
- * Issue capability token - UNIFIED MODEL
219
- *
220
- * Supports both self-capabilities (issuer === recipient) and cross-capabilities.
221
- * Self-capabilities are used for same-author federation, providing consistent
222
- * security model across all federation types.
223
- *
224
- * @param {string[]} permissions - Permissions array (e.g., ['read', 'write', 'delete'])
225
- * @param {Object|string} scope - Scope (holon/lens path or object). Supports wildcards: { holonId: "*", lensName: "*" }
226
- * @param {string} recipient - Recipient public key (can be same as issuer for self-capability)
227
- * @param {Object} options - Options
228
- * @param {number} options.expiresIn - Expiration in milliseconds (default: 1 hour, longer for self-caps)
229
- * @param {string} options.issuer - Issuer ID/public key
230
- * @param {string} options.issuerKey - Issuer private key for signing
231
- * @param {boolean} options.isSelfCapability - If true, marks as self-capability (auto-detected if issuer === recipient)
232
- * @returns {Promise<string>} Capability token (base64-encoded JWT-like)
233
- */
234
- export async function issueCapability(permissions, scope, recipient, options = {}) {
235
- const { expiresIn = 3600000, issuer = 'holosphere', issuerKey, isSelfCapability } = options;
236
-
237
- // Validate permissions
238
- if (!Array.isArray(permissions) || permissions.length === 0) {
239
- throw new Error('Permissions array cannot be empty');
240
- }
241
-
242
- // Validate scope - now supports wildcards
243
- if (typeof scope === 'object') {
244
- // holonId is required (can be '*' for wildcard)
245
- if (scope.holonId === undefined || scope.holonId === '') {
246
- throw new Error('Invalid scope: holonId is required (use "*" for wildcard)');
247
- }
248
- // lensName is required (can be '*' for wildcard)
249
- if (scope.lensName === undefined || scope.lensName === '') {
250
- throw new Error('Invalid scope: lensName is required (use "*" for wildcard)');
251
- }
252
- // dataId is optional (can be specific value, '*', or omitted)
253
- } else if (typeof scope === 'string' && scope === '') {
254
- throw new Error('Invalid scope: cannot be empty string');
255
- }
256
-
257
- // Validate issuerKey if provided
258
- if (issuerKey && (typeof issuerKey !== 'string' || issuerKey.length < 32)) {
259
- throw new Error('Invalid issuer key');
260
- }
261
-
262
- // Detect self-capability (issuer === recipient)
263
- const selfCap = isSelfCapability !== undefined ? isSelfCapability : (issuer === recipient);
264
-
265
- const token = {
266
- type: 'capability',
267
- permissions,
268
- scope,
269
- recipient,
270
- issuer,
271
- isSelfCapability: selfCap, // Mark self-capabilities for clarity
272
- nonce: generateNonce(),
273
- issued: Date.now(),
274
- expires: expiresIn != null ? Date.now() + expiresIn : null,
275
- };
276
-
277
- // Encode token as base64
278
- // Use browser-native btoa when available to avoid Buffer serialization issues
279
- // (Buffer can serialize to {"type":"Buffer","data":[...]} in JSON)
280
- const payload = JSON.stringify(token);
281
- const encoded = typeof btoa === 'function'
282
- ? btoa(payload)
283
- : Buffer.from(payload).toString('base64');
284
-
285
- // If issuerKey provided, sign the token
286
- if (issuerKey) {
287
- const signature = await sign(payload, issuerKey);
288
- return `${encoded}.${signature}`;
289
- }
290
-
291
- return encoded;
292
- }
293
-
294
- /**
295
- * Issue a self-capability token - UNIFIED MODEL
296
- *
297
- * Convenience method for creating self-capabilities (same author federation).
298
- * Self-capabilities have longer expiration by default (1 year).
299
- *
300
- * @param {string[]} permissions - Permissions array
301
- * @param {Object} scope - Scope object { holonId, lensName, dataId? }
302
- * @param {string} authorPubKey - Author's public key (both issuer and recipient)
303
- * @param {Object} options - Options
304
- * @param {string} options.privateKey - Author's private key for signing
305
- * @param {number} [options.expiresIn=31536000000] - Expiration in ms (default: 1 year)
306
- * @returns {Promise<string>} Self-capability token
307
- */
308
- export async function issueSelfCapability(permissions, scope, authorPubKey, options = {}) {
309
- const { privateKey, expiresIn = 365 * 24 * 60 * 60 * 1000 } = options; // 1 year default
310
-
311
- return issueCapability(permissions, scope, authorPubKey, {
312
- expiresIn,
313
- issuer: authorPubKey,
314
- issuerKey: privateKey,
315
- isSelfCapability: true,
316
- });
317
- }
318
-
319
- /**
320
- * Verify capability token
321
- * @param {string|Object} token - Capability token (string or object)
322
- * @param {string} requiredPermission - Required permission
323
- * @param {Object|string} scope - Scope to check (supports wildcards in token scope)
324
- * @returns {Promise<boolean>} True if valid
325
- */
326
- export async function verifyCapability(token, requiredPermission, scope) {
327
- try {
328
- const normalized = normalizeTokenString(token);
329
- if (!normalized) {
330
- console.log('[verifyCapability] ❌ Failed to normalize token');
331
- return false;
332
- }
333
-
334
- let tokenObj;
335
-
336
- // If it's a string, parse it
337
- if (typeof normalized === 'string') {
338
- const parsed = _parseToken(normalized);
339
- if (!parsed) {
340
- console.log('[verifyCapability] ❌ Failed to parse token');
341
- return false;
342
- }
343
- tokenObj = parsed.tokenObj;
344
- } else if (normalized && typeof normalized === 'object') {
345
- tokenObj = normalized;
346
- } else {
347
- console.log('[verifyCapability] ❌ Invalid token:', typeof normalized);
348
- return false;
349
- }
350
-
351
- if (!tokenObj || tokenObj.type !== 'capability') {
352
- console.log('[verifyCapability] ❌ Invalid token type:', { type: tokenObj?.type, tokenObj });
353
- return false;
354
- }
355
-
356
- // Check expiration (null/undefined expires = no expiration)
357
- if (tokenObj.expires != null && Date.now() > tokenObj.expires) {
358
- console.log('[verifyCapability] ❌ Token expired:', {
359
- expires: tokenObj.expires,
360
- now: Date.now(),
361
- expiredAgo: `${(Date.now() - tokenObj.expires) / 1000}s ago`
362
- });
363
- return false;
364
- }
365
-
366
- // Check scope using matchScope (supports wildcards)
367
- if (!matchScope(tokenObj.scope, scope)) {
368
- console.log('[verifyCapability] ❌ Scope mismatch:', {
369
- tokenScope: tokenObj.scope,
370
- requestedScope: scope
371
- });
372
- return false;
373
- }
374
-
375
- // Check permission
376
- if (!tokenObj.permissions.includes(requiredPermission)) {
377
- console.log('[verifyCapability] ❌ Permission denied:', {
378
- required: requiredPermission,
379
- has: tokenObj.permissions
380
- });
381
- return false;
382
- }
383
-
384
- console.log('[verifyCapability] ✅ Capability valid');
385
- return true;
386
- } catch (error) {
387
- console.log('[verifyCapability] ❌ Error:', error.message);
388
- return false;
389
- }
390
- }
391
-
392
- /**
393
- * Verify that a capability token was issued by the expected author.
394
- * This is the core security check for the object-capability model.
395
- * A hologram should only be resolvable if the capability issuer matches
396
- * the claimed data author AND the signature was made by that author's key.
397
- *
398
- * @param {string} token - Capability token
399
- * @param {string} expectedIssuer - Expected issuer public key (data author)
400
- * @returns {Promise<boolean>} True if issuer matches expected AND signature is valid
401
- */
402
- export async function verifyCapabilityIssuer(token, expectedIssuer) {
403
- try {
404
- const normalized = normalizeTokenString(token);
405
- if (!normalized) {
406
- console.log('[verifyCapabilityIssuer] ❌ Failed to normalize token');
407
- return false;
408
- }
409
-
410
- const parsed = _parseToken(normalized);
411
- if (!parsed) {
412
- console.log('[verifyCapabilityIssuer] ❌ Failed to parse token');
413
- return false;
414
- }
415
-
416
- const { tokenObj, payload, signature } = parsed;
417
-
418
- // Verify issuer field matches expected
419
- if (tokenObj.issuer !== expectedIssuer) {
420
- console.log('[verifyCapabilityIssuer] ❌ Issuer mismatch:', {
421
- tokenIssuer: tokenObj.issuer?.slice(0, 12) + '...',
422
- expectedIssuer: expectedIssuer?.slice(0, 12) + '...',
423
- });
424
- return false;
425
- }
426
-
427
- // CRITICAL: Verify signature was made by the claimed issuer's private key
428
- // This prevents forged capabilities where someone claims a different issuer
429
- if (signature) {
430
- const signatureValid = await verify(payload, signature, expectedIssuer);
431
- if (!signatureValid) {
432
- console.log('[verifyCapabilityIssuer] ❌ Signature verification failed - capability not signed by claimed issuer');
433
- return false;
434
- }
435
- } else {
436
- // No signature - cannot cryptographically verify issuer identity
437
- console.log('[verifyCapabilityIssuer] ❌ Token has no signature - cannot verify issuer');
438
- return false;
439
- }
440
-
441
- console.log('[verifyCapabilityIssuer] ✅ Issuer verified');
442
- return true;
443
- } catch (error) {
444
- console.log('[verifyCapabilityIssuer] ❌ Error:', error.message);
445
- return false;
446
- }
447
- }
448
-
449
- /**
450
- * Decode a capability token to extract its payload.
451
- * Useful for inspecting capability contents without full verification.
452
- *
453
- * @param {string} token - Capability token
454
- * @returns {Object|null} Decoded token payload or null if invalid
455
- */
456
- export function decodeCapability(token) {
457
- const normalized = normalizeTokenString(token);
458
- if (!normalized) return null;
459
-
460
- const parsed = _parseToken(normalized);
461
- return parsed ? parsed.tokenObj : null;
462
- }
463
-
464
- /**
465
- * Generate unique nonce
466
- * @private
467
- */
468
- function generateNonce() {
469
- return (
470
- Date.now().toString(36) + Math.random().toString(36).substring(2, 15)
471
- );
472
- }
473
-
474
85
  /**
475
86
  * Hash content using SHA-256
476
87
  * @private