holosphere 2.0.0-alpha11 → 2.0.0-alpha13

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 (65) hide show
  1. package/dist/{2019-D2OG2idw.js → 2019-CLMqIAfQ.js} +1722 -1668
  2. package/dist/{2019-D2OG2idw.js.map → 2019-CLMqIAfQ.js.map} +1 -1
  3. package/dist/2019-Cp3uYhyY.cjs +8 -0
  4. package/dist/{2019-EION3wKo.cjs.map → 2019-Cp3uYhyY.cjs.map} +1 -1
  5. package/dist/browser-D6cNVl0v.cjs +2 -0
  6. package/dist/{browser-Cq59Ij19.cjs.map → browser-D6cNVl0v.cjs.map} +1 -1
  7. package/dist/{browser-BSniCNqO.js → browser-nUQt1cnB.js} +2 -2
  8. package/dist/{browser-BSniCNqO.js.map → browser-nUQt1cnB.js.map} +1 -1
  9. package/dist/cjs/holosphere.cjs +1 -1
  10. package/dist/esm/holosphere.js +67 -50
  11. package/dist/{index-D-jZhliX.js → index-BN_uoxQK.js} +20324 -735
  12. package/dist/index-BN_uoxQK.js.map +1 -0
  13. package/dist/{index-Bl6rM1NW.js → index-CoAjtqsD.js} +2 -2
  14. package/dist/{index-Bl6rM1NW.js.map → index-CoAjtqsD.js.map} +1 -1
  15. package/dist/{index-Bwg3OzRM.cjs → index-Cp3tI53z.cjs} +3 -3
  16. package/dist/{index-Bwg3OzRM.cjs.map → index-Cp3tI53z.cjs.map} +1 -1
  17. package/dist/index-DJjGSwXG.cjs +13 -0
  18. package/dist/index-DJjGSwXG.cjs.map +1 -0
  19. package/dist/index-V8EHMYEY.cjs +29 -0
  20. package/dist/index-V8EHMYEY.cjs.map +1 -0
  21. package/dist/index-Z5TstN1e.js +11663 -0
  22. package/dist/index-Z5TstN1e.js.map +1 -0
  23. package/dist/indexeddb-storage-CZK5A7XH.cjs +2 -0
  24. package/dist/indexeddb-storage-CZK5A7XH.cjs.map +1 -0
  25. package/dist/{indexeddb-storage-5eiUNsHC.js → indexeddb-storage-bpA01pAU.js} +39 -2
  26. package/dist/indexeddb-storage-bpA01pAU.js.map +1 -0
  27. package/dist/{memory-storage-DMt36uZO.cjs → memory-storage-B1k8Jszd.cjs} +2 -2
  28. package/dist/{memory-storage-DMt36uZO.cjs.map → memory-storage-B1k8Jszd.cjs.map} +1 -1
  29. package/dist/{memory-storage-CI-gfmuG.js → memory-storage-BqhmytP_.js} +2 -2
  30. package/dist/{memory-storage-CI-gfmuG.js.map → memory-storage-BqhmytP_.js.map} +1 -1
  31. package/docs/FEDERATION.md +474 -0
  32. package/package.json +3 -1
  33. package/src/crypto/nostr-utils.js +7 -0
  34. package/src/crypto/secp256k1.js +104 -38
  35. package/src/federation/capabilities.js +162 -0
  36. package/src/federation/card-storage.js +376 -0
  37. package/src/federation/handshake.js +561 -9
  38. package/src/federation/hologram.js +194 -57
  39. package/src/federation/holon-registry.js +187 -0
  40. package/src/federation/index.js +68 -0
  41. package/src/federation/registry.js +164 -6
  42. package/src/federation/request-card.js +373 -0
  43. package/src/hierarchical/upcast.js +19 -3
  44. package/src/index.js +209 -75
  45. package/src/lib/federation-methods.js +527 -5
  46. package/src/storage/indexeddb-storage.js +41 -0
  47. package/src/storage/nostr-async.js +14 -5
  48. package/src/storage/nostr-client.js +471 -155
  49. package/src/storage/nostr-wrapper.js +6 -3
  50. package/dist/2019-EION3wKo.cjs +0 -8
  51. package/dist/_commonjsHelpers-C37NGDzP.cjs +0 -2
  52. package/dist/_commonjsHelpers-C37NGDzP.cjs.map +0 -1
  53. package/dist/_commonjsHelpers-CUmg6egw.js +0 -7
  54. package/dist/_commonjsHelpers-CUmg6egw.js.map +0 -1
  55. package/dist/browser-Cq59Ij19.cjs +0 -2
  56. package/dist/index-D-jZhliX.js.map +0 -1
  57. package/dist/index-Dc6Z8Aob.cjs +0 -18
  58. package/dist/index-Dc6Z8Aob.cjs.map +0 -1
  59. package/dist/indexeddb-storage-5eiUNsHC.js.map +0 -1
  60. package/dist/indexeddb-storage-FNFUVvTJ.cjs +0 -2
  61. package/dist/indexeddb-storage-FNFUVvTJ.cjs.map +0 -1
  62. package/dist/secp256k1-CEwJNcfV.js +0 -1890
  63. package/dist/secp256k1-CEwJNcfV.js.map +0 -1
  64. package/dist/secp256k1-CiEONUnj.cjs +0 -12
  65. package/dist/secp256k1-CiEONUnj.cjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "holosphere",
3
- "version": "2.0.0-alpha11",
3
+ "version": "2.0.0-alpha13",
4
4
  "description": "Holonic geospatial communication infrastructure combining H3 hexagonal indexing with distributed P2P storage",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,6 +54,7 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@noble/curves": "^1.3.0",
57
+ "@nostr-dev-kit/ndk": "^2.10.0",
57
58
  "ajv": "^8.12.0",
58
59
  "dotenv": "^17.2.3",
59
60
  "ethers": "^6.13.4",
@@ -64,6 +65,7 @@
64
65
  "ws": "^8.18.3"
65
66
  },
66
67
  "optionalDependencies": {
68
+ "@nostr-dev-kit/ndk-cache-dexie": "^2.5.0",
67
69
  "gun": "^0.2020.1241"
68
70
  },
69
71
  "devDependencies": {
@@ -2,11 +2,18 @@
2
2
  * @fileoverview Browser-compatible Nostr utility functions for key handling, encryption, and event management.
3
3
  * Provides NIP-04 (legacy) and NIP-44 (modern, audited) encryption support, key conversion utilities,
4
4
  * and event creation helpers. Applications can use these utilities without directly importing nostr-tools.
5
+ *
6
+ * This module uses nostr-tools for cryptographic operations while the main client uses NDK.
7
+ * Both libraries are compatible and can be used together.
8
+ *
5
9
  * @module crypto/nostr-utils
6
10
  */
7
11
 
8
12
  import { nip04, nip44, nip19, getPublicKey as nostrGetPublicKey, finalizeEvent, verifyEvent as nostrVerifyEvent } from 'nostr-tools';
9
13
 
14
+ // Re-export NDK types for consumers who want to use NDK directly
15
+ export { default as NDK, NDKEvent, NDKPrivateKeySigner, NDKUser, NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk';
16
+
10
17
  // ============================================================================
11
18
  // Key Conversion Utilities
12
19
  // ============================================================================
@@ -10,29 +10,15 @@
10
10
 
11
11
  import { sha256 } from '@noble/hashes/sha256';
12
12
  import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
13
-
14
- let secp256k1 = null;
15
-
16
- /**
17
- * Lazy load secp256k1 module
18
- * @private
19
- */
20
- async function loadCrypto() {
21
- if (!secp256k1) {
22
- const module = await import('@noble/curves/secp256k1');
23
- secp256k1 = module.secp256k1;
24
- }
25
- return secp256k1;
26
- }
13
+ import { secp256k1 } from '@noble/curves/secp256k1';
27
14
 
28
15
  /**
29
16
  * Get public key from private key
30
17
  * @param {string} privateKey - Private key (hex string)
31
- * @returns {Promise<string>} Public key (hex string)
18
+ * @returns {string} Public key (hex string)
32
19
  */
33
- export async function getPublicKey(privateKey) {
34
- const crypto = await loadCrypto();
35
- const pubKey = crypto.getPublicKey(privateKey);
20
+ export function getPublicKey(privateKey) {
21
+ const pubKey = secp256k1.getPublicKey(privateKey);
36
22
  return bytesToHex(pubKey);
37
23
  }
38
24
 
@@ -44,13 +30,11 @@ export async function getPublicKey(privateKey) {
44
30
  */
45
31
  export async function sign(content, privateKey) {
46
32
  try {
47
- const crypto = await loadCrypto();
48
-
49
33
  // Hash content
50
34
  const msgHash = hashContent(content);
51
35
 
52
36
  // Sign - secp256k1.sign returns Signature object
53
- const signature = crypto.sign(msgHash, privateKey);
37
+ const signature = secp256k1.sign(msgHash, privateKey);
54
38
  return signature.toCompactHex();
55
39
  } catch (error) {
56
40
  throw new Error(`Signature generation failed: ${error.message}`);
@@ -71,10 +55,8 @@ export async function verify(content, signature, publicKey) {
71
55
  throw new Error('Invalid public key format');
72
56
  }
73
57
 
74
- const crypto = await loadCrypto();
75
-
76
58
  const msgHash = hashContent(content);
77
- const isValid = crypto.verify(signature, msgHash, publicKey);
59
+ const isValid = secp256k1.verify(signature, msgHash, publicKey);
78
60
  return isValid;
79
61
  } catch (error) {
80
62
  // Invalid signatures or keys throw errors
@@ -127,18 +109,24 @@ export function matchScope(tokenScope, requestedScope) {
127
109
  }
128
110
 
129
111
  /**
130
- * Issue capability token
112
+ * Issue capability token - UNIFIED MODEL
113
+ *
114
+ * Supports both self-capabilities (issuer === recipient) and cross-capabilities.
115
+ * Self-capabilities are used for same-author federation, providing consistent
116
+ * security model across all federation types.
117
+ *
131
118
  * @param {string[]} permissions - Permissions array (e.g., ['read', 'write', 'delete'])
132
119
  * @param {Object|string} scope - Scope (holon/lens path or object). Supports wildcards: { holonId: "*", lensName: "*" }
133
- * @param {string} recipient - Recipient public key
120
+ * @param {string} recipient - Recipient public key (can be same as issuer for self-capability)
134
121
  * @param {Object} options - Options
135
- * @param {number} options.expiresIn - Expiration in milliseconds (default: 1 hour)
136
- * @param {string} options.issuer - Issuer ID
122
+ * @param {number} options.expiresIn - Expiration in milliseconds (default: 1 hour, longer for self-caps)
123
+ * @param {string} options.issuer - Issuer ID/public key
137
124
  * @param {string} options.issuerKey - Issuer private key for signing
125
+ * @param {boolean} options.isSelfCapability - If true, marks as self-capability (auto-detected if issuer === recipient)
138
126
  * @returns {Promise<string>} Capability token (base64-encoded JWT-like)
139
127
  */
140
128
  export async function issueCapability(permissions, scope, recipient, options = {}) {
141
- const { expiresIn = 3600000, issuer = 'holosphere', issuerKey } = options;
129
+ const { expiresIn = 3600000, issuer = 'holosphere', issuerKey, isSelfCapability } = options;
142
130
 
143
131
  // Validate permissions
144
132
  if (!Array.isArray(permissions) || permissions.length === 0) {
@@ -165,12 +153,16 @@ export async function issueCapability(permissions, scope, recipient, options = {
165
153
  throw new Error('Invalid issuer key');
166
154
  }
167
155
 
156
+ // Detect self-capability (issuer === recipient)
157
+ const selfCap = isSelfCapability !== undefined ? isSelfCapability : (issuer === recipient);
158
+
168
159
  const token = {
169
160
  type: 'capability',
170
161
  permissions,
171
162
  scope,
172
163
  recipient,
173
164
  issuer,
165
+ isSelfCapability: selfCap, // Mark self-capabilities for clarity
174
166
  nonce: generateNonce(),
175
167
  issued: Date.now(),
176
168
  expires: Date.now() + expiresIn,
@@ -191,6 +183,31 @@ export async function issueCapability(permissions, scope, recipient, options = {
191
183
  return encoded;
192
184
  }
193
185
 
186
+ /**
187
+ * Issue a self-capability token - UNIFIED MODEL
188
+ *
189
+ * Convenience method for creating self-capabilities (same author federation).
190
+ * Self-capabilities have longer expiration by default (1 year).
191
+ *
192
+ * @param {string[]} permissions - Permissions array
193
+ * @param {Object} scope - Scope object { holonId, lensName, dataId? }
194
+ * @param {string} authorPubKey - Author's public key (both issuer and recipient)
195
+ * @param {Object} options - Options
196
+ * @param {string} options.privateKey - Author's private key for signing
197
+ * @param {number} [options.expiresIn=31536000000] - Expiration in ms (default: 1 year)
198
+ * @returns {Promise<string>} Self-capability token
199
+ */
200
+ export async function issueSelfCapability(permissions, scope, authorPubKey, options = {}) {
201
+ const { privateKey, expiresIn = 365 * 24 * 60 * 60 * 1000 } = options; // 1 year default
202
+
203
+ return issueCapability(permissions, scope, authorPubKey, {
204
+ expiresIn,
205
+ issuer: authorPubKey,
206
+ issuerKey: privateKey,
207
+ isSelfCapability: true,
208
+ });
209
+ }
210
+
194
211
  /**
195
212
  * Verify capability token
196
213
  * @param {string|Object} token - Capability token (string or object)
@@ -202,41 +219,90 @@ export async function verifyCapability(token, requiredPermission, scope) {
202
219
  try {
203
220
  let tokenObj;
204
221
 
222
+ // Handle capability object wrapper { token, scope, permissions }
223
+ // This normalizes both formats: raw token string and capability info object
224
+ if (token && typeof token === 'object' && token.token) {
225
+ token = token.token; // Extract the actual token string
226
+ }
227
+
205
228
  // Decode if string
206
229
  if (typeof token === 'string') {
207
- const parts = token.split('.');
208
- const payload = parts[0];
209
- const decoded = Buffer.from ?
210
- Buffer.from(payload, 'base64').toString('utf8') :
211
- atob(payload);
212
- tokenObj = JSON.parse(decoded);
213
-
214
- // TODO: Verify signature if present (parts[1])
215
- } else {
230
+ // Check if it's a base64-encoded token (starts with "ey" for JSON base64)
231
+ if (token.startsWith('ey') || (!token.includes('.') && !token.startsWith('{'))) {
232
+ // Base64 encoded (with or without signature)
233
+ try {
234
+ const payload = token.includes('.') ? token.split('.')[0] : token;
235
+ const decoded = Buffer.from ?
236
+ Buffer.from(payload, 'base64').toString('utf8') :
237
+ atob(payload);
238
+ tokenObj = JSON.parse(decoded);
239
+ } catch (e) {
240
+ console.log('[verifyCapability] ❌ Token is not valid base64 JSON:', {
241
+ preview: token.substring(0, 50),
242
+ error: e.message
243
+ });
244
+ return false;
245
+ }
246
+ } else if (token.startsWith('{')) {
247
+ // Already JSON string
248
+ try {
249
+ tokenObj = JSON.parse(token);
250
+ } catch (e) {
251
+ console.log('[verifyCapability] ❌ Token is not valid JSON:', {
252
+ preview: token.substring(0, 50),
253
+ error: e.message
254
+ });
255
+ return false;
256
+ }
257
+ } else {
258
+ console.log('[verifyCapability] ❌ Unknown token format:', token.substring(0, 50));
259
+ return false;
260
+ }
261
+ } else if (token && typeof token === 'object') {
262
+ // Already decoded token object
216
263
  tokenObj = token;
264
+ } else {
265
+ console.log('[verifyCapability] ❌ Invalid token:', typeof token);
266
+ return false;
217
267
  }
218
268
 
219
269
  if (!tokenObj || tokenObj.type !== 'capability') {
270
+ console.log('[verifyCapability] ❌ Invalid token type:', { type: tokenObj?.type, tokenObj });
220
271
  return false;
221
272
  }
222
273
 
223
274
  // Check expiration
224
275
  if (Date.now() > tokenObj.expires) {
276
+ console.log('[verifyCapability] ❌ Token expired:', {
277
+ expires: tokenObj.expires,
278
+ now: Date.now(),
279
+ expiredAgo: `${(Date.now() - tokenObj.expires) / 1000}s ago`
280
+ });
225
281
  return false;
226
282
  }
227
283
 
228
284
  // Check scope using matchScope (supports wildcards)
229
285
  if (!matchScope(tokenObj.scope, scope)) {
286
+ console.log('[verifyCapability] ❌ Scope mismatch:', {
287
+ tokenScope: tokenObj.scope,
288
+ requestedScope: scope
289
+ });
230
290
  return false;
231
291
  }
232
292
 
233
293
  // Check permission
234
294
  if (!tokenObj.permissions.includes(requiredPermission)) {
295
+ console.log('[verifyCapability] ❌ Permission denied:', {
296
+ required: requiredPermission,
297
+ has: tokenObj.permissions
298
+ });
235
299
  return false;
236
300
  }
237
301
 
302
+ console.log('[verifyCapability] ✅ Capability valid');
238
303
  return true;
239
304
  } catch (error) {
305
+ console.log('[verifyCapability] ❌ Error:', error.message);
240
306
  return false;
241
307
  }
242
308
  }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * @fileoverview Federation Capabilities Module
3
+ *
4
+ * Handles capability token issuance, verification, and management
5
+ * for federation operations. Extracted from federation-methods.js
6
+ * for better modularity.
7
+ *
8
+ * @module federation/capabilities
9
+ */
10
+
11
+ import * as crypto from '../crypto/secp256k1.js';
12
+ import * as registry from './registry.js';
13
+ import { sha256 } from '@noble/hashes/sha256';
14
+ import { bytesToHex } from '@noble/hashes/utils';
15
+
16
+ /**
17
+ * Issue a capability token for federation
18
+ * @param {Object} client - NostrClient instance
19
+ * @param {string} targetPubKey - Target public key to grant access to
20
+ * @param {Object} scope - Access scope { holonId, lensName, dataId }
21
+ * @param {string[]} permissions - Array of permissions ('read', 'write')
22
+ * @param {Object} options - Capability options
23
+ * @param {number} [options.expiresIn=3600000] - Expiration time in milliseconds
24
+ * @param {boolean} [options.isSelfCapability=false] - Whether this is a self-capability
25
+ * @returns {Promise<string>} Capability token
26
+ */
27
+ export async function issueCapability(client, targetPubKey, scope, permissions, options = {}) {
28
+ const {
29
+ expiresIn = 3600000,
30
+ isSelfCapability = false,
31
+ } = options;
32
+
33
+ const token = await crypto.issueCapability(
34
+ permissions,
35
+ scope,
36
+ targetPubKey,
37
+ {
38
+ expiresIn,
39
+ issuer: client.publicKey,
40
+ issuerKey: client.privateKey,
41
+ isSelfCapability,
42
+ }
43
+ );
44
+
45
+ return token;
46
+ }
47
+
48
+ /**
49
+ * Issue capability for a specific lens
50
+ * @param {Object} client - NostrClient instance
51
+ * @param {string} holonId - Holon ID
52
+ * @param {string} lensName - Lens name
53
+ * @param {string} targetPubKey - Target public key
54
+ * @param {Object} options - Options
55
+ * @returns {Promise<Object>} Capability info { token, scope, permissions }
56
+ */
57
+ export async function issueCapabilityForLens(client, holonId, lensName, targetPubKey, options = {}) {
58
+ const {
59
+ permissions = ['read'],
60
+ expiresIn = 365 * 24 * 60 * 60 * 1000, // 1 year default
61
+ } = options;
62
+
63
+ const scope = { holonId, lensName, dataId: '*' };
64
+
65
+ const token = await issueCapability(client, targetPubKey, scope, permissions, {
66
+ expiresIn,
67
+ isSelfCapability: client.publicKey === targetPubKey,
68
+ });
69
+
70
+ return {
71
+ token,
72
+ scope,
73
+ permissions,
74
+ expiresAt: Date.now() + expiresIn,
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Issue capabilities for multiple lenses
80
+ * @param {Object} client - NostrClient instance
81
+ * @param {string} holonId - Holon ID
82
+ * @param {string[]} lensNames - Array of lens names
83
+ * @param {string} targetPubKey - Target public key
84
+ * @param {Object} options - Options
85
+ * @returns {Promise<Object[]>} Array of capability info objects
86
+ */
87
+ export async function issueCapabilitiesForLenses(client, holonId, lensNames, targetPubKey, options = {}) {
88
+ const capabilities = [];
89
+
90
+ for (const lensName of lensNames) {
91
+ try {
92
+ const capInfo = await issueCapabilityForLens(client, holonId, lensName, targetPubKey, options);
93
+ capabilities.push(capInfo);
94
+ } catch (err) {
95
+ console.warn(`[Capabilities] Failed to issue capability for ${lensName}:`, err.message);
96
+ }
97
+ }
98
+
99
+ return capabilities;
100
+ }
101
+
102
+ /**
103
+ * Store a capability in the registry
104
+ * @param {Object} client - NostrClient instance
105
+ * @param {string} appname - Application namespace
106
+ * @param {string} partnerPubKey - Partner's public key
107
+ * @param {Object} capabilityInfo - Capability info to store
108
+ * @param {Object} options - Options
109
+ * @returns {Promise<boolean>} Success indicator
110
+ */
111
+ export async function storeCapability(client, appname, partnerPubKey, capabilityInfo, options = {}) {
112
+ const { isSelf = false } = options;
113
+
114
+ if (isSelf) {
115
+ return registry.storeSelfCapability(client, appname, capabilityInfo);
116
+ } else {
117
+ return registry.storeInboundCapability(client, appname, partnerPubKey, capabilityInfo);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Hash a capability token for storage
123
+ * @param {string} token - Capability token to hash
124
+ * @returns {string} SHA256 hash of token
125
+ */
126
+ export function hashToken(token) {
127
+ const encoder = new TextEncoder();
128
+ return bytesToHex(sha256(encoder.encode(token)));
129
+ }
130
+
131
+ /**
132
+ * Verify a capability token
133
+ * @param {string} token - Capability token
134
+ * @param {string} permission - Required permission
135
+ * @param {Object} scope - Requested scope
136
+ * @returns {Promise<boolean>} True if valid
137
+ */
138
+ export async function verifyCapability(token, permission, scope) {
139
+ return crypto.verifyCapability(token, permission, scope);
140
+ }
141
+
142
+ /**
143
+ * Get capability for accessing a partner's data
144
+ * @param {Object} client - NostrClient instance
145
+ * @param {string} appname - Application namespace
146
+ * @param {string} partnerPubKey - Partner's public key
147
+ * @param {Object} scope - Requested scope
148
+ * @returns {Promise<Object|null>} Capability entry or null
149
+ */
150
+ export async function getCapabilityForPartner(client, appname, partnerPubKey, scope) {
151
+ return registry.getCapabilityForAuthor(client, appname, partnerPubKey, scope);
152
+ }
153
+
154
+ export default {
155
+ issueCapability,
156
+ issueCapabilityForLens,
157
+ issueCapabilitiesForLenses,
158
+ storeCapability,
159
+ hashToken,
160
+ verifyCapability,
161
+ getCapabilityForPartner,
162
+ };