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.
- package/README.md +12 -0
- package/dist/browser/shogun-core.js +108133 -43791
- package/dist/browser/shogun-core.js.map +1 -1
- package/dist/ship/examples/messenger-cli.js +173 -60
- package/dist/ship/examples/wallet-cli.js +767 -0
- package/dist/ship/implementation/SHIP_00.js +478 -0
- package/dist/ship/implementation/SHIP_01.js +300 -695
- package/dist/ship/implementation/SHIP_02.js +1366 -0
- package/dist/ship/implementation/SHIP_03.js +855 -0
- package/dist/ship/interfaces/ISHIP_00.js +135 -0
- package/dist/ship/interfaces/ISHIP_01.js +81 -24
- package/dist/ship/interfaces/ISHIP_02.js +57 -0
- package/dist/ship/interfaces/ISHIP_03.js +61 -0
- package/dist/src/gundb/db.js +55 -11
- package/dist/src/index.js +10 -2
- package/dist/src/managers/CoreInitializer.js +41 -13
- package/dist/src/storage/storage.js +22 -9
- package/dist/types/ship/examples/messenger-cli.d.ts +7 -1
- package/dist/types/ship/examples/wallet-cli.d.ts +131 -0
- package/dist/types/ship/implementation/SHIP_00.d.ts +113 -0
- package/dist/types/ship/implementation/SHIP_01.d.ts +47 -76
- package/dist/types/ship/implementation/SHIP_02.d.ts +297 -0
- package/dist/types/ship/implementation/SHIP_03.d.ts +127 -0
- package/dist/types/ship/interfaces/ISHIP_00.d.ts +410 -0
- package/dist/types/ship/interfaces/ISHIP_01.d.ts +157 -119
- package/dist/types/ship/interfaces/ISHIP_02.d.ts +470 -0
- package/dist/types/ship/interfaces/ISHIP_03.d.ts +295 -0
- package/dist/types/src/gundb/db.d.ts +10 -3
- package/dist/types/src/index.d.ts +7 -0
- package/dist/types/src/interfaces/shogun.d.ts +2 -0
- package/dist/types/src/storage/storage.d.ts +2 -1
- package/package.json +23 -9
|
@@ -1,446 +1,229 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* SHIP-01: Decentralized Encrypted Messaging Implementation
|
|
4
4
|
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
-
//
|
|
22
|
+
// IMPLEMENTATION
|
|
33
23
|
// ============================================================================
|
|
34
24
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
-
*
|
|
32
|
+
* Constructor
|
|
33
|
+
* @param identity ISHIP_00 instance for identity operations
|
|
49
34
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
*
|
|
43
|
+
* Get identity provider
|
|
85
44
|
*/
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
//
|
|
49
|
+
// MESSAGING - Send
|
|
186
50
|
// ========================================================================
|
|
187
51
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
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
|
-
|
|
227
|
-
|
|
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.
|
|
234
|
-
const recipientKey = await this.
|
|
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.
|
|
242
|
-
const senderPair = this.
|
|
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
|
|
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
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
253
|
-
// 5. Salva messaggio crittografato su GunDB
|
|
93
|
+
// 7. Save encrypted message on GunDB
|
|
254
94
|
const messageData = {
|
|
255
|
-
from:
|
|
95
|
+
from: currentUser.pub,
|
|
256
96
|
to: recipientUsername,
|
|
257
|
-
content: encryptedMessage,
|
|
97
|
+
content: encryptedMessage,
|
|
258
98
|
timestamp: Date.now().toString(),
|
|
259
|
-
messageId: messageId
|
|
99
|
+
messageId: messageId,
|
|
260
100
|
};
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
//
|
|
121
|
+
// MESSAGING - Listen
|
|
305
122
|
// ========================================================================
|
|
306
123
|
/**
|
|
307
|
-
*
|
|
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("❌
|
|
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
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
325
|
-
|
|
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
|
-
//
|
|
330
|
-
if (data &&
|
|
331
|
-
|
|
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
|
-
//
|
|
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("❌
|
|
174
|
+
console.error("❌ Error decrypting message:", error);
|
|
347
175
|
}
|
|
348
176
|
}
|
|
349
177
|
});
|
|
350
|
-
console.log(`👂
|
|
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
|
-
//
|
|
181
|
+
// MESSAGING - History
|
|
394
182
|
// ========================================================================
|
|
395
183
|
/**
|
|
396
|
-
*
|
|
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("❌
|
|
188
|
+
if (!this.identity.isLoggedIn()) {
|
|
189
|
+
console.error("❌ Not authenticated");
|
|
401
190
|
return [];
|
|
402
191
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
416
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
const isReceivedFromTarget = (msgData.to === username || msgData.to === userPub) &&
|
|
436
|
-
|
|
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
|
-
|
|
239
|
+
// Silent error - message couldn't be decrypted
|
|
458
240
|
}
|
|
459
241
|
});
|
|
460
|
-
// Wait
|
|
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
|
-
//
|
|
250
|
+
// PRIVATE HELPERS
|
|
470
251
|
// ========================================================================
|
|
471
252
|
/**
|
|
472
|
-
*
|
|
473
|
-
*
|
|
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
|
|
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
|
-
//
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
283
|
+
// Access GunDB
|
|
284
|
+
const shogun = this.identity.getShogun();
|
|
285
|
+
const gun = shogun?.db?.gun;
|
|
286
|
+
if (!gun) {
|
|
287
|
+
return null;
|
|
486
288
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
return
|
|
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("❌
|
|
498
|
-
|
|
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
|
-
*
|
|
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
|
|
429
|
+
// GunDB Node Names for messaging
|
|
513
430
|
SHIP_01.NODES = {
|
|
514
|
-
MESSAGES:
|
|
515
|
-
|
|
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
|
-
}
|