shogun-core 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +12 -0
  2. package/dist/browser/shogun-core.js +108133 -43791
  3. package/dist/browser/shogun-core.js.map +1 -1
  4. package/dist/ship/examples/messenger-cli.js +173 -60
  5. package/dist/ship/examples/wallet-cli.js +767 -0
  6. package/dist/ship/implementation/SHIP_00.js +478 -0
  7. package/dist/ship/implementation/SHIP_01.js +300 -695
  8. package/dist/ship/implementation/SHIP_02.js +1366 -0
  9. package/dist/ship/implementation/SHIP_03.js +855 -0
  10. package/dist/ship/interfaces/ISHIP_00.js +135 -0
  11. package/dist/ship/interfaces/ISHIP_01.js +81 -24
  12. package/dist/ship/interfaces/ISHIP_02.js +57 -0
  13. package/dist/ship/interfaces/ISHIP_03.js +61 -0
  14. package/dist/src/gundb/db.js +55 -11
  15. package/dist/src/index.js +10 -2
  16. package/dist/src/managers/CoreInitializer.js +41 -13
  17. package/dist/src/storage/storage.js +22 -9
  18. package/dist/types/ship/examples/messenger-cli.d.ts +7 -1
  19. package/dist/types/ship/examples/wallet-cli.d.ts +131 -0
  20. package/dist/types/ship/implementation/SHIP_00.d.ts +113 -0
  21. package/dist/types/ship/implementation/SHIP_01.d.ts +47 -76
  22. package/dist/types/ship/implementation/SHIP_02.d.ts +297 -0
  23. package/dist/types/ship/implementation/SHIP_03.d.ts +127 -0
  24. package/dist/types/ship/interfaces/ISHIP_00.d.ts +410 -0
  25. package/dist/types/ship/interfaces/ISHIP_01.d.ts +157 -119
  26. package/dist/types/ship/interfaces/ISHIP_02.d.ts +470 -0
  27. package/dist/types/ship/interfaces/ISHIP_03.d.ts +295 -0
  28. package/dist/types/src/gundb/db.d.ts +10 -3
  29. package/dist/types/src/index.d.ts +7 -0
  30. package/dist/types/src/interfaces/shogun.d.ts +2 -0
  31. package/dist/types/src/storage/storage.d.ts +2 -1
  32. package/package.json +23 -9
@@ -1,446 +1,229 @@
1
1
  "use strict";
2
2
  /**
3
- * Esempio Pratico: Messaggistica Decentralizzata con Shogun Core
3
+ * SHIP-01: Decentralized Encrypted Messaging Implementation
4
4
  *
5
- * Questo esempio mostra come creare un sistema di messaggistica sicuro usando:
6
- * - Shogun Core per autenticazione (username/password)
7
- * - GunDB per storage decentralizzato P2P
8
- * - SEA (Security, Encryption, Authorization) per crittografia
5
+ * Messaggistica decentralizzata E2E che dipende da SHIP-00 per l'identità.
9
6
  *
10
- * Vantaggi:
11
- * Completamente decentralizzato (no server centrale)
12
- * Zero costi (no blockchain, no gas fees)
13
- * Real-time messaging
14
- * ✅ Offline-first
15
- * ✅ End-to-end encryption
7
+ * Dipendenze:
8
+ * - SHIP-00 (Identity & Authentication) - per gestione utenti e chiavi
9
+ * - GunDB - per storage decentralizzato P2P
10
+ * - SEA - per crittografia ECDH + AES-GCM
16
11
  *
17
- * Note sull'ERC7627:
18
- * L'EIP era un template concettuale. Questo esempio implementa
19
- * solo la parte GunDB/Shogun Core senza interazione blockchain.
20
- * La funzione deriveEthereumAddress() rimane come utility per derivare
21
- * un address Ethereum dalla chiave GunDB se necessario in futuro.
12
+ * Vantaggi dell'architettura modulare:
13
+ * Separazione delle responsabilità (Identity vs Messaging)
14
+ * Riusabilità di SHIP-00 in altre applicazioni
15
+ * Testing più semplice e isolato
16
+ * Manutenibilità migliorata
22
17
  */
23
- var __importDefault = (this && this.__importDefault) || function (mod) {
24
- return (mod && mod.__esModule) ? mod : { "default": mod };
25
- };
26
18
  Object.defineProperty(exports, "__esModule", { value: true });
27
19
  exports.SHIP_01 = void 0;
28
- const core_1 = require("../../src/core");
29
20
  const ethers_1 = require("ethers");
30
- const derive_1 = __importDefault(require("../../src/gundb/derive"));
31
21
  // ============================================================================
32
- // 1. SETUP: Inizializzazione del Sistema Completo
22
+ // IMPLEMENTATION
33
23
  // ============================================================================
34
24
  /**
35
- * Classe per messaggistica sicura con Shogun Core
36
- * Implementa l'interfaccia ISHIP_01
37
- * Usa solo GunDB per storage decentralizzato (no blockchain)
25
+ * SHIP-01 Reference Implementation
26
+ *
27
+ * Questa implementazione dipende da ISHIP_00 per tutte le operazioni di identità.
28
+ * Si concentra esclusivamente sulla logica di messaggistica.
38
29
  */
39
30
  class SHIP_01 {
40
- constructor(shogunConfig) {
41
- // Inizializza Shogun Core
42
- this.shogun = new core_1.ShogunCore(shogunConfig);
43
- }
44
- // ========================================================================
45
- // 2. AUTENTICAZIONE: Username e Password
46
- // ========================================================================
47
31
  /**
48
- * Registra un nuovo utente
32
+ * Constructor
33
+ * @param identity ISHIP_00 instance for identity operations
49
34
  */
50
- async signup(username, password) {
51
- try {
52
- // Registra con Shogun Core (crea SEA pair automaticamente)
53
- const signupResult = await this.shogun.signUp(username, password);
54
- if (!signupResult.success) {
55
- return {
56
- success: false,
57
- error: signupResult.error || "Signup failed"
58
- };
59
- }
60
- console.log("✅ Utente registrato");
61
- console.log(` Username: ${username}`);
62
- console.log(` GunDB Public Key: ${signupResult.pub}`);
63
- // Opzionale: deriva address Ethereum dalla chiave GunDB
64
- const derivedAddress = signupResult.pub
65
- ? await this.deriveEthereumAddress(signupResult.pub)
66
- : undefined;
67
- if (derivedAddress) {
68
- console.log(` Derived Address: ${derivedAddress}`);
69
- }
70
- return {
71
- success: true,
72
- userPub: signupResult.pub,
73
- derivedAddress
74
- };
75
- }
76
- catch (error) {
77
- return {
78
- success: false,
79
- error: error.message
80
- };
35
+ constructor(identity) {
36
+ if (!identity.isLoggedIn()) {
37
+ throw new Error("User must be authenticated via SHIP-00 before using SHIP-01");
81
38
  }
39
+ this.identity = identity;
40
+ console.log("✅ SHIP-01 initialized with authenticated identity");
82
41
  }
83
42
  /**
84
- * Login con username e password
43
+ * Get identity provider
85
44
  */
86
- async login(username, password) {
87
- try {
88
- // Login con Shogun Core
89
- const loginResult = await this.shogun.login(username, password);
90
- if (!loginResult.success) {
91
- return {
92
- success: false,
93
- error: loginResult.error || "Login failed"
94
- };
95
- }
96
- console.log("✅ Login effettuato");
97
- console.log(` Username: ${username}`);
98
- console.log(` GunDB Public Key: ${loginResult.userPub}`);
99
- // Opzionale: deriva address Ethereum dalla chiave GunDB
100
- const derivedAddress = loginResult.userPub
101
- ? await this.deriveEthereumAddress(loginResult.userPub)
102
- : undefined;
103
- if (derivedAddress) {
104
- console.log(` Derived Address: ${derivedAddress}`);
105
- }
106
- return {
107
- success: true,
108
- userPub: loginResult.userPub,
109
- derivedAddress
110
- };
111
- }
112
- catch (error) {
113
- return {
114
- success: false,
115
- error: error.message
116
- };
117
- }
118
- }
119
- /**
120
- * Login con SEA Key Pair
121
- *
122
- * Autenticazione diretta usando un key pair esportato.
123
- * Utile per:
124
- * - Recupero account senza password
125
- * - Portabilità tra dispositivi
126
- * - Backup dell'identità
127
- */
128
- async loginWithPair(seaPair) {
129
- try {
130
- console.log("🔐 Login con key pair...");
131
- // Autentica con GunDB usando il pair
132
- const authResult = await new Promise((resolve) => {
133
- this.shogun.db.gun.user().auth(seaPair, (ack) => {
134
- if (ack.err) {
135
- resolve({
136
- success: false,
137
- error: ack.err,
138
- });
139
- }
140
- else {
141
- resolve({
142
- success: true,
143
- userPub: seaPair.pub,
144
- });
145
- }
146
- });
147
- });
148
- if (!authResult.success) {
149
- console.log("❌ Login fallito:", authResult.error);
150
- return authResult;
151
- }
152
- // Opzionale: deriva address Ethereum dalla chiave GunDB
153
- const derivedAddress = await this.deriveEthereumAddress(seaPair.pub);
154
- console.log("✅ Login effettuato");
155
- console.log(` GunDB Public Key: ${seaPair.pub}`);
156
- console.log(` Derived Address: ${derivedAddress}`);
157
- return {
158
- success: true,
159
- userPub: authResult.userPub,
160
- derivedAddress
161
- };
162
- }
163
- catch (error) {
164
- console.error("❌ Errore login con pair:", error);
165
- return {
166
- success: false,
167
- error: error instanceof Error ? error.message : "Unknown error",
168
- };
169
- }
170
- }
171
- /**
172
- * Logout
173
- */
174
- logout() {
175
- this.shogun.logout();
176
- console.log("👋 Logout effettuato");
177
- }
178
- /**
179
- * Verifica se l'utente è autenticato
180
- */
181
- isLoggedIn() {
182
- return this.shogun.isLoggedIn();
45
+ getIdentity() {
46
+ return this.identity;
183
47
  }
184
48
  // ========================================================================
185
- // 3. GESTIONE CHIAVI PUBBLICHE: Salva su GunDB
49
+ // MESSAGING - Send
186
50
  // ========================================================================
187
51
  /**
188
- * Salva la chiave pubblica dell'utente su GunDB
189
- * Questo permette ad altri di trovare la tua chiave per criptare messaggi
190
- */
191
- async publishPublicKey() {
192
- try {
193
- if (!this.isLoggedIn()) {
194
- return { success: false, error: "Not logged in" };
195
- }
196
- // Ottieni il SEA pair dell'utente corrente
197
- const currentUser = this.shogun.db.user;
198
- if (!currentUser || !currentUser.is) {
199
- return { success: false, error: "No user session" };
200
- }
201
- // Salva chiave pubblica sul nodo globale usando il userPub come chiave
202
- // Questo permette ad altri di trovarla facilmente
203
- const userPub = currentUser.is.pub;
204
- await this.shogun.db.gun.get(userPub).put({
205
- pub: currentUser.is.pub,
206
- epub: currentUser.is.epub,
207
- algorithm: "ECDSA",
208
- timestamp: Date.now().toString()
209
- }).then();
210
- console.log(`📝 Chiave pubblica pubblicata su GunDB`);
211
- return { success: true };
212
- }
213
- catch (error) {
214
- console.error("❌ Errore pubblicazione chiave:", error);
215
- return { success: false, error: error.message };
216
- }
217
- }
218
- // ========================================================================
219
- // 4. INVIO MESSAGGI: Solo GunDB
220
- // ========================================================================
221
- /**
222
- * Invia un messaggio crittografato a un altro utente
52
+ * Send encrypted message to a user
53
+ * Uses identity provider (SHIP-00) to get keys
223
54
  */
224
55
  async sendMessage(recipientUsername, message) {
225
56
  try {
226
- if (!this.isLoggedIn()) {
227
- return { success: false, error: "Not logged in" };
228
- }
229
- const currentUser = this.shogun.db.user;
230
- if (!currentUser || !currentUser.is) {
231
- return { success: false, error: "No user session" };
57
+ // Verify authentication
58
+ if (!this.identity.isLoggedIn()) {
59
+ return { success: false, error: "Not authenticated" };
232
60
  }
233
- // 1. Ottieni la chiave pubblica del destinatario
234
- const recipientKey = await this.getRecipientPublicKey(recipientUsername);
61
+ // 1. Get recipient's public key from SHIP-00
62
+ const recipientKey = await this.identity.getPublicKey(recipientUsername);
235
63
  if (!recipientKey) {
236
64
  return {
237
65
  success: false,
238
- error: `Recipient ${recipientUsername} has not published their public key`
66
+ error: `Recipient ${recipientUsername} has not published their public key`,
239
67
  };
240
68
  }
241
- // 2. Ottieni il SEA pair completo (include chiavi private)
242
- const senderPair = this.shogun.db.gun.user()?._?.sea;
69
+ // 2. Get sender's key pair from SHIP-00
70
+ const senderPair = this.identity.getKeyPair();
243
71
  if (!senderPair) {
244
- return { success: false, error: "Cannot access SEA pair" };
72
+ return { success: false, error: "Cannot access sender key pair" };
73
+ }
74
+ // 3. Get current user from SHIP-00
75
+ const currentUser = this.identity.getCurrentUser();
76
+ if (!currentUser) {
77
+ return { success: false, error: "No current user" };
78
+ }
79
+ // 4. Access GunDB through identity provider
80
+ const shogun = this.identity.getShogun();
81
+ if (!shogun || !shogun.db) {
82
+ return { success: false, error: "Cannot access ShogunCore" };
245
83
  }
246
- // 3. Cripta il messaggio usando SEA.secret + SEA.encrypt (ECDH)
247
- const encryptedMessage = await this.shogun.db.crypto.encFor(message, senderPair, // sender pair completo
248
- { epub: recipientKey.epub } // recipient public encryption key
249
- );
250
- // 4. Genera ID messaggio
84
+ const gun = shogun.db.gun;
85
+ const crypto = shogun.db.crypto;
86
+ if (!gun || !crypto) {
87
+ return { success: false, error: "Cannot access GunDB or crypto" };
88
+ }
89
+ // 5. Encrypt message using ECDH
90
+ const encryptedMessage = await crypto.encFor(message, senderPair, { epub: recipientKey.epub });
91
+ // 6. Generate message ID
251
92
  const messageId = this.generateMessageId();
252
- const senderPub = currentUser.is.pub;
253
- // 5. Salva messaggio crittografato su GunDB
93
+ // 7. Save encrypted message on GunDB
254
94
  const messageData = {
255
- from: senderPub,
95
+ from: currentUser.pub,
256
96
  to: recipientUsername,
257
- content: encryptedMessage, // Contenuto crittografato!
97
+ content: encryptedMessage,
258
98
  timestamp: Date.now().toString(),
259
- messageId: messageId
99
+ messageId: messageId,
260
100
  };
261
- // Salva sul nodo globale 'messages' per permettere il listener
262
- await this.shogun.db.gun.get(SHIP_01.NODES.MESSAGES).get(messageId).put(messageData).then();
263
- console.log(`✅ Messaggio salvato: ${messageId}`);
101
+ await gun
102
+ .get(SHIP_01.NODES.MESSAGES)
103
+ .get(messageId)
104
+ .put(messageData)
105
+ .then();
106
+ console.log(`✅ Message sent: ${messageId}`);
264
107
  return {
265
108
  success: true,
266
- messageId: messageId
109
+ messageId: messageId,
267
110
  };
268
111
  }
269
112
  catch (error) {
113
+ console.error("❌ Error sending message:", error);
270
114
  return {
271
115
  success: false,
272
- error: error.message
116
+ error: error.message,
273
117
  };
274
118
  }
275
119
  }
276
- /**
277
- * Ottiene la chiave pubblica di un utente da GunDB
278
- */
279
- async getRecipientPublicKey(username) {
280
- try {
281
- // Prima trova l'utente dal username
282
- const userData = await this.shogun.db.getUserByAlias(username);
283
- if (!userData || !userData.userPub) {
284
- console.error(`❌ User ${username} not found`);
285
- return null;
286
- }
287
- const userPub = userData.userPub;
288
- // Le chiavi sono salvate direttamente sul nodo userPub
289
- const publicKeyData = await this.shogun.db.gun.get(userPub).then();
290
- if (publicKeyData && publicKeyData.epub && publicKeyData.pub) {
291
- return {
292
- pub: publicKeyData.pub,
293
- epub: publicKeyData.epub
294
- };
295
- }
296
- return null;
297
- }
298
- catch (error) {
299
- console.error("❌ Errore recupero chiave pubblica:", error);
300
- return null;
301
- }
302
- }
303
120
  // ========================================================================
304
- // 5. RICEZIONE MESSAGGI: Ascolta GunDB in real-time
121
+ // MESSAGING - Listen
305
122
  // ========================================================================
306
123
  /**
307
- * Ascolta messaggi crittografati in arrivo su GunDB
124
+ * Listen for incoming encrypted messages
125
+ * Uses identity provider (SHIP-00) to decrypt messages
308
126
  */
309
127
  async listenForMessages(onMessage) {
310
- if (!this.isLoggedIn()) {
311
- console.error("❌ Non autenticato");
128
+ if (!this.identity.isLoggedIn()) {
129
+ console.error("❌ Not authenticated");
130
+ return;
131
+ }
132
+ // Get current user from SHIP-00
133
+ const currentUser = this.identity.getCurrentUser();
134
+ if (!currentUser || !currentUser.alias) {
135
+ console.error("❌ No current user");
312
136
  return;
313
137
  }
314
- const currentUser = this.shogun.db.user;
315
- if (!currentUser || !currentUser.is) {
316
- console.error("❌ Nessuna sessione utente");
138
+ const username = currentUser.alias;
139
+ // Access GunDB
140
+ const shogun = this.identity.getShogun();
141
+ const gun = shogun?.db?.gun;
142
+ if (!gun) {
143
+ console.error("❌ Cannot access GunDB");
317
144
  return;
318
145
  }
319
- const userPub = currentUser.is.pub;
320
- // Ottieni username dell'utente corrente
321
- const username = currentUser.is.alias;
322
- // Set per tracciare messaggi già ricevuti (evita duplicati)
146
+ // Track received messages to avoid duplicates
323
147
  const receivedMessages = new Set();
324
- // Ascolta messaggi in tempo reale su GunDB
325
- this.shogun.db.gun
148
+ // Listen for messages in real-time
149
+ gun
326
150
  .get(SHIP_01.NODES.MESSAGES)
327
151
  .map()
328
152
  .on(async (data, key) => {
329
- // Filtra solo i messaggi destinati a questo utente (per username)
330
- if (data && data.to === username && data.from && data.content && data.messageId) {
331
- // Evita duplicati (GunDB può emettere più volte)
153
+ // Filter messages for this user
154
+ if (data &&
155
+ data.to === username &&
156
+ data.from &&
157
+ data.content &&
158
+ data.messageId) {
159
+ // Avoid duplicates
332
160
  if (receivedMessages.has(data.messageId)) {
333
161
  return;
334
162
  }
335
163
  receivedMessages.add(data.messageId);
336
164
  try {
337
- // Decripta il messaggio
165
+ // Decrypt message using SHIP-00
338
166
  const decryptedContent = await this.decryptMessage(data.content, data.from);
339
167
  onMessage({
340
168
  from: data.from,
341
169
  content: decryptedContent,
342
- timestamp: parseInt(data.timestamp)
170
+ timestamp: parseInt(data.timestamp),
343
171
  });
344
172
  }
345
173
  catch (error) {
346
- console.error("❌ Errore decrittazione messaggio:", error);
174
+ console.error("❌ Error decrypting message:", error);
347
175
  }
348
176
  }
349
177
  });
350
- console.log(`👂 In ascolto di messaggi per ${username} (${userPub.substring(0, 10)}...)`);
351
- }
352
- /**
353
- * Decripta un messaggio usando SEA.secret + SEA.decrypt (ECDH)
354
- */
355
- async decryptMessage(encryptedContent, senderPub) {
356
- // Ottieni il SEA pair completo del destinatario (noi)
357
- const receiverPair = this.shogun.db.gun.user()?._?.sea;
358
- if (!receiverPair) {
359
- throw new Error("Cannot access SEA pair");
360
- }
361
- // Ottieni epub del mittente
362
- const senderKey = await this.getPublicKeyByPub(senderPub);
363
- if (!senderKey) {
364
- throw new Error("Sender public key not found");
365
- }
366
- // Decripta usando SEA.secret + SEA.decrypt (ECDH)
367
- const decrypted = await this.shogun.db.crypto.decFrom(encryptedContent, { epub: senderKey.epub }, // sender's public encryption key
368
- receiverPair // receiver's pair completo
369
- );
370
- return decrypted;
371
- }
372
- /**
373
- * Ottiene la chiave pubblica di un utente dalla sua pub key
374
- */
375
- async getPublicKeyByPub(userPub) {
376
- try {
377
- // Le chiavi sono salvate direttamente sul nodo userPub
378
- const publicKeyData = await this.shogun.db.gun.get(userPub).then();
379
- if (publicKeyData && publicKeyData.epub && publicKeyData.pub) {
380
- return {
381
- pub: publicKeyData.pub,
382
- epub: publicKeyData.epub
383
- };
384
- }
385
- return null;
386
- }
387
- catch (error) {
388
- console.error("❌ Errore recupero chiave:", error);
389
- return null;
390
- }
178
+ console.log(`👂 Listening for messages to ${username}...`);
391
179
  }
392
180
  // ========================================================================
393
- // 6. RECUPERO STORICO: Query messaggi passati
181
+ // MESSAGING - History
394
182
  // ========================================================================
395
183
  /**
396
- * Recupera lo storico dei messaggi crittografati con un utente
184
+ * Get message history with a user
185
+ * Uses identity provider (SHIP-00) to decrypt messages
397
186
  */
398
187
  async getMessageHistory(withUsername) {
399
- if (!this.isLoggedIn()) {
400
- console.error("❌ Non autenticato");
188
+ if (!this.identity.isLoggedIn()) {
189
+ console.error("❌ Not authenticated");
401
190
  return [];
402
191
  }
403
- const currentUser = this.shogun.db.user;
404
- if (!currentUser || !currentUser.is) {
405
- console.error("❌ Nessuna sessione utente");
192
+ // Get current user from SHIP-00
193
+ const currentUser = this.identity.getCurrentUser();
194
+ if (!currentUser || !currentUser.alias) {
195
+ console.error("❌ No current user");
406
196
  return [];
407
197
  }
408
- const userPub = currentUser.is.pub;
409
- const username = currentUser.is.alias;
410
- const allMessages = [];
411
- console.log(`[SHIP-01] Getting history between ${username} (${userPub.substring(0, 20)}...) and ${withUsername}`);
412
- // Ottieni il userPub dell'altro utente
413
- const otherUserData = await this.shogun.db.getUserByAlias(withUsername);
198
+ const username = currentUser.alias;
199
+ const userPub = currentUser.pub;
200
+ // Get other user's public key from SHIP-00
201
+ const otherUserData = await this.identity.getUserByAlias(withUsername);
414
202
  const otherUserPub = otherUserData?.userPub;
415
- console.log(`[SHIP-01] Other user pub: ${otherUserPub?.substring(0, 20) || 'not found'}...`);
416
- // Use .map() instead of .then() to iterate all messages
203
+ // Access GunDB
204
+ const shogun = this.identity.getShogun();
205
+ const gun = shogun?.db?.gun;
206
+ if (!gun) {
207
+ console.error("❌ Cannot access GunDB");
208
+ return [];
209
+ }
210
+ // Get all messages and filter
417
211
  return new Promise((resolve) => {
418
212
  const messages = [];
419
- let messageCount = 0;
420
- this.shogun.db.gun.get(SHIP_01.NODES.MESSAGES).map().once(async (msgData, messageId) => {
421
- // Skip metadata fields
422
- if (!msgData || typeof msgData !== 'object' || messageId === '_') {
213
+ gun
214
+ .get(SHIP_01.NODES.MESSAGES)
215
+ .map()
216
+ .once(async (msgData, messageId) => {
217
+ // Skip metadata
218
+ if (!msgData || typeof msgData !== "object" || messageId === "_") {
423
219
  return;
424
220
  }
425
- messageCount++;
426
- console.log(`[SHIP-01] Checking message ${messageId}:`, {
427
- from: msgData.from?.substring(0, 20),
428
- to: msgData.to,
429
- hasContent: !!msgData.content
430
- });
431
221
  try {
432
222
  // Check if message is part of this conversation
433
- // Fixed: Handle both username and userPub in matching
434
- const isSentToTarget = msgData.from === userPub && (msgData.to === withUsername || msgData.to === otherUserPub);
435
- const isReceivedFromTarget = (msgData.to === username || msgData.to === userPub) && msgData.from === otherUserPub;
436
- console.log(`[SHIP-01] Message ${messageId} match:`, {
437
- isSentToTarget,
438
- isReceivedFromTarget,
439
- fromMatch: msgData.from === userPub,
440
- toMatch: msgData.to === withUsername,
441
- receivedToMatch: msgData.to === username,
442
- receivedFromMatch: msgData.from === otherUserPub
443
- });
223
+ const isSentToTarget = msgData.from === userPub &&
224
+ (msgData.to === withUsername || msgData.to === otherUserPub);
225
+ const isReceivedFromTarget = (msgData.to === username || msgData.to === userPub) &&
226
+ msgData.from === otherUserPub;
444
227
  if (isSentToTarget || isReceivedFromTarget) {
445
228
  // Decrypt message
446
229
  const decryptedContent = await this.decryptMessage(msgData.content, msgData.from);
@@ -448,381 +231,203 @@ class SHIP_01 {
448
231
  from: msgData.from,
449
232
  to: msgData.to,
450
233
  content: decryptedContent,
451
- timestamp: parseInt(msgData.timestamp)
234
+ timestamp: parseInt(msgData.timestamp),
452
235
  });
453
- console.log(`[SHIP-01] ✅ Decrypted message from ${msgData.from.substring(0, 20)}...`);
454
236
  }
455
237
  }
456
238
  catch (error) {
457
- console.error(`[SHIP-01] Error processing message ${messageId}:`, error);
239
+ // Silent error - message couldn't be decrypted
458
240
  }
459
241
  });
460
- // Wait a bit for GunDB to return all messages, then resolve
242
+ // Wait for GunDB to return all messages, then resolve
461
243
  setTimeout(() => {
462
- console.log(`[SHIP-01] Found ${messages.length} messages out of ${messageCount} total`);
463
244
  const sorted = messages.sort((a, b) => a.timestamp - b.timestamp);
464
245
  resolve(sorted);
465
246
  }, 2000);
466
247
  });
467
248
  }
468
249
  // ========================================================================
469
- // 7. UTILITY FUNCTIONS
250
+ // PRIVATE HELPERS
470
251
  // ========================================================================
471
252
  /**
472
- * Converte la chiave pubblica di GunDB in address Ethereum
473
- * Usa la chiave privata SEA come seed per derivazione deterministica
474
- *
475
- * Questo garantisce che:
476
- * - Stesso SEA pair → stesso address Ethereum
477
- * - Derivazione cryptografica sicura
478
- * - Identità unificata tra GunDB e blockchain
253
+ * Decrypt a message using ECDH
254
+ * Uses identity provider (SHIP-00) to get keys
479
255
  */
480
- async deriveEthereumAddress(publicKey) {
256
+ async decryptMessage(encryptedContent, senderPub) {
257
+ // Get receiver's key pair from SHIP-00
258
+ const receiverPair = this.identity.getKeyPair();
259
+ if (!receiverPair) {
260
+ throw new Error("Cannot access receiver key pair");
261
+ }
262
+ // Get sender's public key
263
+ const senderKeyData = await this.getPublicKeyByPub(senderPub);
264
+ if (!senderKeyData) {
265
+ throw new Error("Sender public key not found");
266
+ }
267
+ // Access crypto
268
+ const shogun = this.identity.getShogun();
269
+ const crypto = shogun?.db?.crypto;
270
+ if (!crypto) {
271
+ throw new Error("Cannot access crypto");
272
+ }
273
+ // Decrypt using ECDH
274
+ const decrypted = await crypto.decFrom(encryptedContent, { epub: senderKeyData.epub }, receiverPair);
275
+ return decrypted;
276
+ }
277
+ /**
278
+ * Get public key by pub key
279
+ * Uses identity provider internally
280
+ */
281
+ async getPublicKeyByPub(userPub) {
481
282
  try {
482
- // Ottieni il SEA pair completo
483
- const seaPair = this.shogun.db.gun.user()?._?.sea;
484
- if (!seaPair || !seaPair.priv) {
485
- throw new Error("Cannot access SEA pair");
283
+ // Access GunDB
284
+ const shogun = this.identity.getShogun();
285
+ const gun = shogun?.db?.gun;
286
+ if (!gun) {
287
+ return null;
486
288
  }
487
- // Usa la chiave privata SEA come seed per derive
488
- // Questo è MOLTO più sicuro e deterministico del username!
489
- const derived = await (0, derive_1.default)(seaPair.priv, null, {
490
- includeSecp256k1Ethereum: true,
491
- includeP256: false,
492
- includeSecp256k1Bitcoin: false
493
- });
494
- return derived.secp256k1Ethereum.address;
289
+ const publicKeyData = await gun.get(userPub).then();
290
+ if (publicKeyData && publicKeyData.epub && publicKeyData.pub) {
291
+ return {
292
+ pub: publicKeyData.pub,
293
+ epub: publicKeyData.epub,
294
+ };
295
+ }
296
+ return null;
495
297
  }
496
298
  catch (error) {
497
- console.error("❌ Errore derivazione address:", error);
498
- // Fallback: hash della chiave pubblica
499
- const fallbackKey = publicKey || "unknown";
500
- const hash = ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(fallbackKey));
501
- return ethers_1.ethers.getAddress('0x' + hash.slice(-40));
299
+ console.error("❌ Error getting public key:", error);
300
+ return null;
502
301
  }
503
302
  }
504
303
  /**
505
- * Genera ID messaggio univoco
304
+ * Generate unique message ID
506
305
  */
507
306
  generateMessageId() {
508
307
  return ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16));
509
308
  }
309
+ // ========================================================================
310
+ // TOKEN-BASED MESSAGING (Channels/Groups)
311
+ // ========================================================================
312
+ /**
313
+ * Send message encrypted with shared token/password
314
+ * Useful for group chats, channels, broadcast messages
315
+ */
316
+ async sendMessageWithToken(token, message, channel) {
317
+ try {
318
+ // Verify authentication
319
+ if (!this.identity.isLoggedIn()) {
320
+ return { success: false, error: "Not authenticated" };
321
+ }
322
+ // Get current user
323
+ const currentUser = this.identity.getCurrentUser();
324
+ if (!currentUser) {
325
+ return { success: false, error: "No current user" };
326
+ }
327
+ // Access crypto and GunDB
328
+ const shogun = this.identity.getShogun();
329
+ const crypto = shogun?.db?.crypto;
330
+ const gun = shogun?.db?.gun;
331
+ if (!crypto || !gun) {
332
+ return { success: false, error: "Cannot access crypto or GunDB" };
333
+ }
334
+ // Hash token for key derivation (more secure)
335
+ const hashedToken = await crypto.hashText(token);
336
+ // Encrypt message with hashed token
337
+ const encryptedMessage = await crypto.encrypt(message, hashedToken);
338
+ // Generate message ID
339
+ const messageId = this.generateMessageId();
340
+ // Save encrypted message
341
+ const messageData = {
342
+ from: currentUser.pub,
343
+ content: encryptedMessage,
344
+ channel: channel || "default",
345
+ timestamp: Date.now().toString(),
346
+ messageId: messageId,
347
+ type: "token", // Mark as token-encrypted
348
+ };
349
+ await gun
350
+ .get(SHIP_01.NODES.TOKEN_MESSAGES)
351
+ .get(messageId)
352
+ .put(messageData)
353
+ .then();
354
+ console.log(`✅ Token message sent: ${messageId} (channel: ${channel || "default"})`);
355
+ return {
356
+ success: true,
357
+ messageId: messageId,
358
+ };
359
+ }
360
+ catch (error) {
361
+ console.error("❌ Error sending token message:", error);
362
+ return {
363
+ success: false,
364
+ error: error.message,
365
+ };
366
+ }
367
+ }
368
+ /**
369
+ * Listen for token-encrypted messages
370
+ * Automatically decrypts with provided token
371
+ */
372
+ async listenForTokenMessages(token, onMessage, channel) {
373
+ if (!this.identity.isLoggedIn()) {
374
+ console.error("❌ Not authenticated");
375
+ return;
376
+ }
377
+ // Access GunDB and crypto
378
+ const shogun = this.identity.getShogun();
379
+ const gun = shogun?.db?.gun;
380
+ const crypto = shogun?.db?.crypto;
381
+ if (!gun || !crypto) {
382
+ console.error("❌ Cannot access GunDB or crypto");
383
+ return;
384
+ }
385
+ // Hash token for decryption
386
+ const hashedToken = await crypto.hashText(token);
387
+ // Track received messages to avoid duplicates
388
+ const receivedMessages = new Set();
389
+ // Listen for token messages in real-time
390
+ gun
391
+ .get(SHIP_01.NODES.TOKEN_MESSAGES)
392
+ .map()
393
+ .on(async (data, key) => {
394
+ // Filter by channel if specified
395
+ if (channel && data?.channel !== channel) {
396
+ return;
397
+ }
398
+ // Validate data
399
+ if (data &&
400
+ data.type === "token" &&
401
+ data.from &&
402
+ data.content &&
403
+ data.messageId) {
404
+ // Avoid duplicates
405
+ if (receivedMessages.has(data.messageId)) {
406
+ return;
407
+ }
408
+ receivedMessages.add(data.messageId);
409
+ try {
410
+ // Decrypt message with hashed token
411
+ const decryptedContent = await crypto.decrypt(data.content, hashedToken);
412
+ onMessage({
413
+ from: data.from,
414
+ content: decryptedContent,
415
+ channel: data.channel,
416
+ timestamp: parseInt(data.timestamp),
417
+ });
418
+ }
419
+ catch (error) {
420
+ // Silently skip messages that can't be decrypted
421
+ // (wrong token or corrupted data)
422
+ }
423
+ }
424
+ });
425
+ console.log(`👂 Listening for token messages (channel: ${channel || "all"})...`);
426
+ }
510
427
  }
511
428
  exports.SHIP_01 = SHIP_01;
512
- // GunDB Node Names - Constants for clarity
429
+ // GunDB Node Names for messaging
513
430
  SHIP_01.NODES = {
514
- MESSAGES: 'messages',
515
- USERS: 'users',
516
- PUBLIC_KEYS: 'publicKeys'
431
+ MESSAGES: "messages",
432
+ TOKEN_MESSAGES: "token_messages", // Token-encrypted messages (channels/groups)
517
433
  };
518
- // ============================================================================
519
- // 8. ESEMPIO D'USO COMPLETO
520
- // ============================================================================
521
- // async function main() {
522
- // console.log("🚀 Secure Messaging App - Shogun Core + GunDB\n");
523
- // // 1. Inizializza il sistema
524
- // const app = new SHIP_01({
525
- // gunOptions: {
526
- // peers: ["https://relay.shogun-eco.xyz/gun","https://v5g5jseqhgkp43lppgregcfbvi.srv.us/gun","https://peer.wallie.io/gun"],
527
- // radisk: false,
528
- // localStorage:false,
529
- // }
530
- // });
531
- // // 2. Signup (prima volta)
532
- // console.log("📝 Registrazione nuovo utente...");
533
- // const signupResult = await app.signup("alice", "password123");
534
- // if (!signupResult.success) {
535
- // console.log("ℹ️ Utente già esistente, procedo con login");
536
- // } else {
537
- // console.log("✅ Utente registrato!");
538
- // console.log(` Username: alice`);
539
- // console.log(` Public Key: ${signupResult.userPub}`);
540
- // console.log(` Derived Address: ${signupResult.derivedAddress}\n`);
541
- // }
542
- // // 3. Login
543
- // console.log("🔐 Login...");
544
- // const loginResult = await app.login("alice", "password123");
545
- // if (!loginResult.success) {
546
- // console.error("❌ Login fallito:", loginResult.error);
547
- // return;
548
- // }
549
- // console.log("✅ Login effettuato!");
550
- // console.log(` - Autenticazione: Shogun Core (Username/Password)`);
551
- // console.log(` - Storage: GunDB decentralizzato P2P\n`);
552
- // // 4. Pubblica chiave pubblica
553
- // console.log("📢 Pubblicazione chiave pubblica...");
554
- // await app.publishPublicKey();
555
- // // 5. Ascolta messaggi in arrivo
556
- // console.log("\n👂 In ascolto di messaggi...\n");
557
- // await app.listenForMessages((message) => {
558
- // console.log(`📨 Nuovo messaggio da ${message.from.substring(0, 10)}...:`);
559
- // console.log(` Contenuto: ${message.content}`);
560
- // console.log(` Timestamp: ${new Date(message.timestamp).toLocaleString()}\n`);
561
- // });
562
- // // 6. Invia un messaggio
563
- // const recipientUsername = "bob";
564
- // console.log(`📤 Invio messaggio a ${recipientUsername}...`);
565
- // const result = await app.sendMessage(
566
- // recipientUsername,
567
- // "Hello Bob! This is a secure message using Shogun Core"
568
- // );
569
- // if (result.success) {
570
- // console.log("✅ Messaggio inviato!");
571
- // console.log(` Message ID: ${result.messageId}`);
572
- // console.log(` Storage: GunDB P2P (gratis!)`);
573
- // }
574
- // // 7. Recupera storico conversazione
575
- // console.log("\n📚 Recupero storico messaggi...");
576
- // const history = await app.getMessageHistory(recipientUsername);
577
- // console.log(`\n💬 Conversazione con ${recipientUsername} (${history.length} messaggi):`);
578
- // history.forEach((msg, i) => {
579
- // const isMe = msg.from === loginResult.userPub;
580
- // console.log(`${i + 1}. ${isMe ? 'Tu' : 'Loro'}: ${msg.content}`);
581
- // });
582
- // }
583
- // ============================================================================
584
- // 9. VANTAGGI DELL'ARCHITETTURA
585
- // ============================================================================
586
- /**
587
- * VANTAGGI DI QUESTA ARCHITETTURA:
588
- *
589
- * 1. SEMPLICITÀ:
590
- * - Solo username/password (nessun wallet necessario)
591
- * - Nessun gas fee o transazioni blockchain
592
- * - Setup in pochi secondi
593
- *
594
- * 2. DECENTRALIZZAZIONE COMPLETA:
595
- * - Storage P2P su GunDB
596
- * - Nessun server centrale
597
- * - Censorship resistant
598
- * - User sovereignty
599
- *
600
- * 3. COSTI ZERO:
601
- * - Nessun costo per messaggi
602
- * - Nessun costo per storage
603
- * - Completamente gratis
604
- *
605
- * 4. END-TO-END ENCRYPTION:
606
- * - Messaggi crittografati con ECDH (Elliptic Curve Diffie-Hellman)
607
- * - SEA.secret deriva shared secret da epub (encryption public key)
608
- * - SEA.encrypt cripta con AES-GCM
609
- * - Solo mittente e destinatario possono leggere i messaggi
610
- * - Nessun server o relay può leggere il contenuto
611
- *
612
- * 5. IDENTITÀ DECENTRALIZZATA:
613
- * - Chiave pubblica GunDB come identità
614
- * - Può essere derivata in address Ethereum se necessario
615
- * - Portabile tra diverse applicazioni
616
- *
617
- * 6. REAL-TIME:
618
- * - Messaggi in tempo reale
619
- * - Sincronizzazione automatica
620
- * - Listener reattivi
621
- *
622
- * 7. OFFLINE-FIRST:
623
- * - Funziona anche offline
624
- * - Sincronizzazione automatica al riconnect
625
- * - GunDB gestisce conflitti automaticamente
626
- *
627
- * COME FUNZIONA LA CRITTOGRAFIA:
628
- *
629
- * 1. SETUP - Ogni utente ha un SEA pair:
630
- * - pub: chiave pubblica di signing
631
- * - priv: chiave privata di signing
632
- * - epub: chiave pubblica di encryption (per ECDH)
633
- * - epriv: chiave privata di encryption (per ECDH)
634
- *
635
- * 2. PUBBLICAZIONE CHIAVI:
636
- * - Ogni utente pubblica il suo epub su GunDB
637
- * - Altri possono trovarlo per criptare messaggi
638
- *
639
- * 3. INVIO MESSAGGIO (Alice → Bob):
640
- * a) Alice ottiene l'epub di Bob da GunDB
641
- * b) SEA.secret(bob.epub, alice.pair) → shared_secret (ECDH)
642
- * c) SEA.encrypt(message, shared_secret) → encrypted_message
643
- * d) Alice salva encrypted_message su GunDB
644
- *
645
- * 4. RICEZIONE MESSAGGIO (Bob riceve):
646
- * a) Bob legge encrypted_message da GunDB
647
- * b) Bob ottiene l'epub di Alice
648
- * c) SEA.secret(alice.epub, bob.pair) → shared_secret (stesso!)
649
- * d) SEA.decrypt(encrypted_message, shared_secret) → message
650
- *
651
- * 5. SICUREZZA:
652
- * - Shared secret derivato con ECDH (mai trasmesso)
653
- * - Solo Alice e Bob possono derivare lo stesso secret
654
- * - Messaggi crittografati con AES-256-GCM
655
- * - Forward secrecy se chiavi sono ruotate
656
- *
657
- * Esempio tecnico:
658
- * Alice.epub + Bob.epriv → shared_secret_AB
659
- * Bob.epub + Alice.epriv → shared_secret_AB (stesso!)
660
- *
661
- * Vantaggi:
662
- * ✅ End-to-end encryption
663
- * ✅ Perfect forward secrecy (con key rotation)
664
- * ✅ Nessun server può leggere i messaggi
665
- * ✅ Standard crittografico (ECDH + AES-GCM)
666
- * ✅ Una sola chiave privata da gestire
667
- * ✅ Address Ethereum derivabile (interoperabilità)
668
- */
669
- // ============================================================================
670
- // 10. TEST COMPLETO: Alice e Bob si scambiano messaggi
671
- // ============================================================================
672
- /**
673
- * Test completo che simula Alice e Bob che si scambiano messaggi CRITTOGRAFATI
674
- *
675
- * CRITTOGRAFIA END-TO-END:
676
- * - Ogni messaggio è crittografato con ECDH (Elliptic Curve Diffie-Hellman)
677
- * - Solo mittente e destinatario possono leggere i messaggi
678
- * - I relay GunDB vedono solo dati crittografati
679
- *
680
- * FLOW:
681
- * 1. Alice e Bob pubblicano le loro chiavi pubbliche (epub)
682
- * 2. Alice vuole inviare a Bob:
683
- * - Ottiene bob.epub da GunDB
684
- * - SEA.secret(bob.epub, alice.pair) → shared_secret
685
- * - SEA.encrypt(message, shared_secret) → encrypted
686
- * - Salva encrypted su GunDB
687
- * 3. Bob riceve:
688
- * - Legge encrypted da GunDB
689
- * - Ottiene alice.epub
690
- * - SEA.secret(alice.epub, bob.pair) → shared_secret (stesso!)
691
- * - SEA.decrypt(encrypted, shared_secret) → message
692
- */
693
- async function testAliceAndBob() {
694
- console.log("🧪 TEST: Alice e Bob - Conversazione Crittografata E2E\n");
695
- console.log("=".repeat(60));
696
- // Setup Alice
697
- console.log("\n👩 ALICE - Setup");
698
- console.log("-".repeat(60));
699
- const alice = new SHIP_01({
700
- gunOptions: {
701
- peers: ["https://peer.wallie.io/gun"],
702
- radisk: true,
703
- },
704
- });
705
- // Alice signup/login
706
- await alice.signup("alice", "password123");
707
- const aliceLogin = await alice.login("alice", "password123");
708
- if (!aliceLogin.success) {
709
- console.error("❌ Alice login failed");
710
- return;
711
- }
712
- await alice.publishPublicKey();
713
- // Aspetta che la chiave sia sincronizzata
714
- await new Promise(resolve => setTimeout(resolve, 1000));
715
- // Setup Bob
716
- console.log("\n👨 BOB - Setup");
717
- console.log("-".repeat(60));
718
- const bob = new SHIP_01({
719
- gunOptions: {
720
- peers: ["https://peer.wallie.io/gun"],
721
- radisk: true
722
- },
723
- });
724
- // Bob signup/login
725
- await bob.signup("bob", "password456");
726
- const bobLogin = await bob.login("bob", "password456");
727
- if (!bobLogin.success) {
728
- console.error("❌ Bob login failed");
729
- return;
730
- }
731
- await bob.publishPublicKey();
732
- // Aspetta che la chiave sia sincronizzata
733
- await new Promise(resolve => setTimeout(resolve, 1000));
734
- console.log("\n" + "=".repeat(60));
735
- console.log("💬 CONVERSAZIONE");
736
- console.log("=".repeat(60));
737
- // Alice e Bob ascoltano messaggi
738
- let aliceMessages = [];
739
- let bobMessages = [];
740
- await alice.listenForMessages((msg) => {
741
- aliceMessages.push(msg);
742
- console.log(`\n[Alice riceve] 📨`);
743
- console.log(` Da: ${msg.from.substring(0, 10)}...`);
744
- console.log(` Messaggio: "${msg.content}"`);
745
- console.log(` Timestamp: ${new Date(msg.timestamp).toLocaleTimeString()}`);
746
- });
747
- await bob.listenForMessages((msg) => {
748
- bobMessages.push(msg);
749
- console.log(`\n[Bob riceve] 📨`);
750
- console.log(` Da: ${msg.from.substring(0, 10)}...`);
751
- console.log(` Messaggio: "${msg.content}"`);
752
- console.log(` Timestamp: ${new Date(msg.timestamp).toLocaleTimeString()}`);
753
- });
754
- // Aspetta che i listener siano pronti
755
- await new Promise(resolve => setTimeout(resolve, 2000));
756
- // Alice invia messaggio a Bob
757
- console.log("\n[Alice invia] 📤");
758
- const msg1 = await alice.sendMessage("bob", "Ciao Bob! Come stai? 👋");
759
- console.log(` ✅ Inviato: "${msg1.messageId}"`);
760
- // Aspetta che Bob riceva
761
- await new Promise(resolve => setTimeout(resolve, 2000));
762
- // Bob risponde ad Alice
763
- console.log("\n[Bob invia] 📤");
764
- const msg2 = await bob.sendMessage("alice", "Ciao Alice! Tutto bene, grazie! Tu? 😊");
765
- console.log(` ✅ Inviato: "${msg2.messageId}"`);
766
- // Aspetta che Alice riceva
767
- await new Promise(resolve => setTimeout(resolve, 2000));
768
- // Alice risponde
769
- console.log("\n[Alice invia] 📤");
770
- const msg3 = await alice.sendMessage("bob", "Anch'io benissimo! Questo sistema di messaggistica è fantastico! 🚀");
771
- console.log(` ✅ Inviato: "${msg3.messageId}"`);
772
- // Aspetta che Bob riceva
773
- await new Promise(resolve => setTimeout(resolve, 2000));
774
- // Bob conclude
775
- console.log("\n[Bob invia] 📤");
776
- const msg4 = await bob.sendMessage("alice", "Vero! Zero costi, decentralizzato e veloce! 💪");
777
- console.log(` ✅ Inviato: "${msg4.messageId}"`);
778
- // Aspetta finale
779
- await new Promise(resolve => setTimeout(resolve, 3000));
780
- // Mostra statistiche
781
- console.log("\n" + "=".repeat(60));
782
- console.log("📊 STATISTICHE FINALI");
783
- console.log("=".repeat(60));
784
- console.log(`\n👩 Alice:`);
785
- console.log(` - Messaggi inviati: 2`);
786
- console.log(` - Messaggi ricevuti: ${aliceMessages.length}`);
787
- console.log(` - Public Key: ${aliceLogin.userPub?.substring(0, 20)}...`);
788
- console.log(` - Derived Address: ${aliceLogin.derivedAddress}`);
789
- console.log(`\n👨 Bob:`);
790
- console.log(` - Messaggi inviati: 2`);
791
- console.log(` - Messaggi ricevuti: ${bobMessages.length}`);
792
- console.log(` - Public Key: ${bobLogin.userPub?.substring(0, 20)}...`);
793
- console.log(` - Derived Address: ${bobLogin.derivedAddress}`);
794
- // Recupera storico
795
- console.log("\n" + "=".repeat(60));
796
- console.log("📚 STORICO CONVERSAZIONE");
797
- console.log("=".repeat(60));
798
- const aliceHistory = await alice.getMessageHistory("bob");
799
- console.log(`\n👩 Storico di Alice con Bob (${aliceHistory.length} messaggi):`);
800
- aliceHistory.forEach((msg, i) => {
801
- const sender = msg.from === aliceLogin.userPub ? "Alice" : "Bob";
802
- console.log(` ${i + 1}. [${sender}]: ${msg.content}`);
803
- });
804
- const bobHistory = await bob.getMessageHistory("alice");
805
- console.log(`\n👨 Storico di Bob con Alice (${bobHistory.length} messaggi):`);
806
- bobHistory.forEach((msg, i) => {
807
- const sender = msg.from === bobLogin.userPub ? "Bob" : "Alice";
808
- console.log(` ${i + 1}. [${sender}]: ${msg.content}`);
809
- });
810
- // Riepilogo finale
811
- console.log("\n" + "=".repeat(60));
812
- console.log("✅ TEST COMPLETATO CON SUCCESSO!");
813
- console.log("=".repeat(60));
814
- console.log("\n📝 Riepilogo:");
815
- console.log(" ✅ 2 utenti registrati (Alice, Bob)");
816
- console.log(" ✅ 4 messaggi scambiati");
817
- console.log(" ✅ Messaggi ricevuti in real-time");
818
- console.log(" ✅ Storico recuperato correttamente");
819
- console.log(" ✅ Storage: GunDB P2P (gratis!)");
820
- console.log(" ✅ Costi: $0.00");
821
- console.log("\n🎉 Sistema di messaggistica decentralizzato funzionante!\n");
822
- }
823
- // Esegui esempio o test
824
- if (require.main === module) {
825
- // Scegli quale eseguire decommentando una delle due righe:
826
- // main().catch(console.error); // Esempio singolo utente
827
- testAliceAndBob().catch(console.error); // Test completo Alice & Bob
828
- }