shogun-core 3.3.4 → 3.3.5

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.
@@ -1,59 +1,47 @@
1
1
  "use strict";
2
2
  /**
3
- * SHIP-06: Ephemeral P2P Messaging Interface
3
+ * SHIP-06: Secure Vault Interface
4
4
  *
5
- * @title ISHIP_06 - Ephemeral P2P Messaging
6
- * @notice Interface for ephemeral peer-to-peer messaging via Gun Relay
7
- * @dev Can work standalone OR with ISHIP_00 for authenticated sessions
5
+ * @title ISHIP_06 - Secure Encrypted Vault
6
+ * @notice Interface for secure encrypted key-value storage on GunDB
7
+ * @dev This interface depends on ISHIP_00 for identity and encryption
8
8
  *
9
9
  * ## Abstract
10
10
  *
11
- * This standard defines an interface for ephemeral P2P messaging that allows:
12
- * - Relay-based connections via Gun network
13
- * - End-to-end encrypted messages (no storage)
14
- * - Broadcast and direct messaging
15
- * - Deterministic room discovery (SHA-256)
16
- * - Standalone mode (no authentication needed!)
11
+ * This standard defines an interface for secure vault storage that allows:
12
+ * - End-to-end encrypted key-value storage
13
+ * - Soft delete with recovery
14
+ * - Export/import for backup
15
+ * - Rich metadata support
16
+ * - Simple, secure, focused on storage only
17
17
  *
18
18
  * ## Dependencies
19
19
  *
20
- * - Gun: Relay-based P2P database
21
- * - Gun SEA: Cryptography (ECDH + AES-GCM)
22
- * - ISHIP_00 (OPTIONAL): For authenticated sessions
23
- *
24
- * ## Modes
25
- *
26
- * **Standalone**: new SHIP_06(gunPeers[], roomId)
27
- * **With Identity**: new SHIP_06(ISHIP_00, roomId)
20
+ * - ISHIP_00: Identity and authentication layer
21
+ * - GunDB: P2P storage
22
+ * - SEA: Cryptography (AES-256-GCM)
28
23
  *
29
24
  * ## Inspiration
30
25
  *
31
- * Based on Bugoff (https://github.com/draeder/bugoff)
32
- * Simplified for Gun relay instead of WebRTC
26
+ * Based on Gunsafe (https://github.com/draeder/gunsafe)
27
+ * Adapted for Shogun ecosystem with SHIP-00 integration
33
28
  */
34
29
  Object.defineProperty(exports, "__esModule", { value: true });
35
30
  // ============================================================================
36
31
  // IMPLEMENTATION EXAMPLE
37
32
  // ============================================================================
38
33
  /**
39
- * Example of how to implement ISHIP_06 with ISHIP_00 dependency
34
+ * Example of how to implement ISHIP_07 with ISHIP_00 dependency
40
35
  *
41
36
  * ```typescript
42
37
  * import { ISHIP_00 } from './ISHIP_00';
43
- * import { ISHIP_06, EphemeralMessage } from './ISHIP_06';
44
- * import Bugout from 'bugout';
45
- *
46
- * class EphemeralMessaging implements ISHIP_06 {
47
- * private bugout: any;
48
- * private ephemeralPair: SEAPair | null = null;
49
- * private peers: Map<string, PeerInfo> = new Map();
50
- * private messageCallbacks: ((msg: EphemeralMessage) => void)[] = [];
51
- *
52
- * constructor(
53
- * private identity: ISHIP_00,
54
- * private roomId: string,
55
- * private config?: EphemeralConfig
56
- * ) {
38
+ * import { ISHIP_07, VaultRecord, VaultResult } from './ISHIP_07';
39
+ *
40
+ * class SecureVault implements ISHIP_07 {
41
+ * private vaultNode: any;
42
+ * private initialized: boolean = false;
43
+ *
44
+ * constructor(private identity: ISHIP_00) {
57
45
  * if (!identity.isLoggedIn()) {
58
46
  * throw new Error('User must be authenticated via SHIP-00');
59
47
  * }
@@ -63,82 +51,144 @@ Object.defineProperty(exports, "__esModule", { value: true });
63
51
  * return this.identity;
64
52
  * }
65
53
  *
66
- * async connect(): Promise<void> {
67
- * // 1. Generate ephemeral SEA pair
68
- * const crypto = this.identity.shogun.db.crypto;
69
- * this.ephemeralPair = await crypto.pair();
70
- *
71
- * // 2. Hash room ID
72
- * const swarmId = await crypto.hashText(this.roomId);
73
- *
74
- * // 3. Create Bugout swarm
75
- * this.bugout = new Bugout(swarmId, {
76
- * iceServers: this.config?.iceServers
77
- * });
78
- *
79
- * // 4. Set SEA pair
80
- * await this.bugout.SEA(this.ephemeralPair);
54
+ * async initialize(): Promise<void> {
55
+ * // Get Gun user node
56
+ * const gun = this.identity.shogun.db.gun;
57
+ * this.vaultNode = gun.user().get('vault').get('records');
81
58
  *
82
- * // 5. Listen for events
83
- * this.bugout.on('seen', (address: string) => {
84
- * this.handlePeerSeen(address);
59
+ * // Initialize vault metadata
60
+ * await gun.user().get('vault').get('metadata').put({
61
+ * version: '1.0.0',
62
+ * created: Date.now().toString()
85
63
  * });
86
64
  *
87
- * this.bugout.on('decrypted', (address: string, pubkeys: any, message: string) => {
88
- * this.handleMessage(address, pubkeys, message);
89
- * });
65
+ * this.initialized = true;
90
66
  * }
91
67
  *
92
- * disconnect(): void {
93
- * if (this.bugout) {
94
- * this.bugout.destroy();
95
- * }
68
+ * isInitialized(): boolean {
69
+ * return this.initialized;
96
70
  * }
97
71
  *
98
- * async sendBroadcast(message: string): Promise<void> {
99
- * if (!this.bugout) {
100
- * throw new Error('Not connected to swarm');
72
+ * async put(name: string, data: any, metadata?: RecordMetadata): Promise<VaultResult> {
73
+ * if (!this.initialized) {
74
+ * return { success: false, error: 'Vault not initialized' };
101
75
  * }
102
76
  *
103
- * this.bugout.send(message);
77
+ * try {
78
+ * // Get SEA crypto
79
+ * const crypto = this.identity.shogun.db.crypto;
80
+ * const pair = this.identity.getKeyPair();
81
+ *
82
+ * if (!pair) {
83
+ * return { success: false, error: 'Cannot access key pair' };
84
+ * }
85
+ *
86
+ * // Encrypt data
87
+ * const encryptedData = await crypto.encrypt(
88
+ * JSON.stringify(data),
89
+ * pair.epriv
90
+ * );
91
+ *
92
+ * // Encrypt metadata if provided
93
+ * const encryptedMetadata = metadata
94
+ * ? await crypto.encrypt(JSON.stringify(metadata), pair.epriv)
95
+ * : undefined;
96
+ *
97
+ * // Store in vault
98
+ * const record = {
99
+ * data: encryptedData,
100
+ * created: Date.now().toString(),
101
+ * updated: Date.now().toString(),
102
+ * deleted: false,
103
+ * metadata: encryptedMetadata
104
+ * };
105
+ *
106
+ * await this.vaultNode.get(name).put(record);
107
+ *
108
+ * return { success: true, recordName: name };
109
+ * } catch (error: any) {
110
+ * return { success: false, error: error.message };
111
+ * }
104
112
  * }
105
113
  *
106
- * async sendDirect(peerAddress: string, message: string): Promise<void> {
107
- * if (!this.bugout) {
108
- * throw new Error('Not connected to swarm');
114
+ * async get(name: string, options?: GetOptions): Promise<VaultRecord | null> {
115
+ * if (!this.initialized) {
116
+ * return null;
109
117
  * }
110
118
  *
111
- * this.bugout.send(peerAddress, message);
119
+ * try {
120
+ * // Retrieve from vault
121
+ * const encryptedRecord = await this.vaultNode.get(name).then();
122
+ *
123
+ * if (!encryptedRecord || !encryptedRecord.data) {
124
+ * return null;
125
+ * }
126
+ *
127
+ * // Skip if deleted (unless includeDeleted)
128
+ * if (encryptedRecord.deleted && !options?.includeDeleted) {
129
+ * return null;
130
+ * }
131
+ *
132
+ * // Decrypt data
133
+ * const crypto = this.identity.shogun.db.crypto;
134
+ * const pair = this.identity.getKeyPair();
135
+ *
136
+ * if (!pair) {
137
+ * return null;
138
+ * }
139
+ *
140
+ * const decryptedData = await crypto.decrypt(
141
+ * encryptedRecord.data,
142
+ * pair.epriv
143
+ * );
144
+ *
145
+ * // Decrypt metadata if present
146
+ * const decryptedMetadata = encryptedRecord.metadata
147
+ * ? JSON.parse(await crypto.decrypt(encryptedRecord.metadata, pair.epriv))
148
+ * : undefined;
149
+ *
150
+ * return {
151
+ * name,
152
+ * data: JSON.parse(decryptedData),
153
+ * created: parseInt(encryptedRecord.created),
154
+ * updated: parseInt(encryptedRecord.updated),
155
+ * deleted: encryptedRecord.deleted,
156
+ * metadata: decryptedMetadata
157
+ * };
158
+ * } catch (error) {
159
+ * console.error('Error retrieving record:', error);
160
+ * return null;
161
+ * }
112
162
  * }
113
163
  *
114
- * onMessage(callback: (message: EphemeralMessage) => void): void {
115
- * this.messageCallbacks.push(callback);
164
+ * async delete(name?: string): Promise<VaultResult> {
165
+ * // Implementation here
166
+ * return { success: true };
116
167
  * }
117
168
  *
118
- * private handleMessage(address: string, pubkeys: any, content: string) {
119
- * const message: EphemeralMessage = {
120
- * from: address,
121
- * fromPubKey: pubkeys.pub,
122
- * content,
123
- * timestamp: Date.now(),
124
- * type: 'broadcast'
125
- * };
126
- *
127
- * this.messageCallbacks.forEach(cb => cb(message));
169
+ * async list(options?: ListOptions): Promise<string[]> {
170
+ * // Implementation here
171
+ * return [];
128
172
  * }
173
+ *
174
+ * // ... implement other methods
129
175
  * }
130
176
  *
131
177
  * // Usage
132
178
  * const identity = new SHIP_00(config);
133
179
  * await identity.login('alice', 'password123');
134
180
  *
135
- * const ephemeral = new EphemeralMessaging(identity, 'my-room');
136
- * await ephemeral.connect();
181
+ * const vault = new SecureVault(identity);
182
+ * await vault.initialize();
137
183
  *
138
- * ephemeral.onMessage((msg) => {
139
- * console.log(`${msg.from}: ${msg.content}`);
184
+ * // Store encrypted data
185
+ * await vault.put('my-password', 'super_secret', {
186
+ * type: 'password',
187
+ * description: 'GitHub password'
140
188
  * });
141
189
  *
142
- * await ephemeral.sendBroadcast('Hello everyone!');
190
+ * // Retrieve decrypted data
191
+ * const record = await vault.get('my-password');
192
+ * console.log('Password:', record?.data);
143
193
  * ```
144
194
  */
@@ -113,9 +113,6 @@ class DataBase {
113
113
  this.user = this.gun.user().recall({ sessionStorage: true });
114
114
  }
115
115
  else {
116
- if (!this.silent && !this.disableAutoRecall) {
117
- console.log("No pair found in sessionStorage, using gun.user()");
118
- }
119
116
  this.user = this.gun.user();
120
117
  }
121
118
  this.subscribeToAuthEvents();
@@ -164,7 +161,6 @@ class DataBase {
164
161
  */
165
162
  addPeer(peer) {
166
163
  this.gun.opt({ peers: [peer] });
167
- console.log(`Added new peer: ${peer}`);
168
164
  }
169
165
  /**
170
166
  * Removes a peer from the network
@@ -182,10 +178,9 @@ class DataBase {
182
178
  if (peerConnection && typeof peerConnection.close === "function") {
183
179
  peerConnection.close();
184
180
  }
185
- console.log(`Removed peer: ${peer}`);
186
181
  }
187
182
  else {
188
- console.log(`Peer not found in current connections: ${peer}`);
183
+ console.error(`Peer not found in current connections: ${peer}`);
189
184
  }
190
185
  }
191
186
  catch (error) {
@@ -270,7 +265,6 @@ class DataBase {
270
265
  this.removePeer(peer);
271
266
  // Add it back immediately instead of with timeout
272
267
  this.addPeer(peer);
273
- console.log(`Reconnected to peer: ${peer}`);
274
268
  }
275
269
  catch (error) {
276
270
  console.error(`Error reconnecting to peer ${peer}:`, error);
@@ -294,7 +288,6 @@ class DataBase {
294
288
  this.addPeer(peer);
295
289
  });
296
290
  }
297
- console.log(`Gun database reset with ${newPeers ? newPeers.length : 0} peers: ${newPeers ? newPeers.join(", ") : "none"}`);
298
291
  }
299
292
  }
300
293
  catch (error) {
@@ -531,7 +524,6 @@ class DataBase {
531
524
  else {
532
525
  const recallResult = userInstance;
533
526
  }
534
- // console.log("recallResult", recallResult);
535
527
  }
536
528
  catch (recallError) {
537
529
  console.error("Error during recall:", recallError);
@@ -574,7 +566,6 @@ class DataBase {
574
566
  try {
575
567
  const currentUser = this.gun.user();
576
568
  if (!currentUser || !currentUser.is) {
577
- console.log("No user logged in, skipping logout");
578
569
  return;
579
570
  }
580
571
  // Log out user
@@ -733,7 +724,6 @@ class DataBase {
733
724
  });
734
725
  }
735
726
  else {
736
- console.log(`User created successfully with userPub: ${userPub}`);
737
727
  resolve({ success: true, userPub: userPub });
738
728
  }
739
729
  }
@@ -769,7 +759,6 @@ class DataBase {
769
759
  }
770
760
  if (pair) {
771
761
  this.gun.user().auth(pair, (ack) => {
772
- console.log(`Pair authentication after creation result:`, ack);
773
762
  if (ack.err) {
774
763
  console.error(`Authentication after creation failed: ${ack.err}`);
775
764
  resolve({ success: false, error: ack.err });
@@ -779,9 +768,6 @@ class DataBase {
779
768
  setTimeout(() => {
780
769
  // Extract userPub from multiple possible sources
781
770
  const userPub = ack.pub || this.gun.user().is?.pub || ack.user?.pub;
782
- console.log(`Extracted userPub after pair auth: ${userPub}`);
783
- console.log(`User object after pair auth:`, this.gun.user());
784
- console.log(`User.is after pair auth:`, this.gun.user().is);
785
771
  if (!userPub) {
786
772
  console.error("Authentication successful but no userPub found");
787
773
  resolve({
@@ -798,7 +784,6 @@ class DataBase {
798
784
  }
799
785
  else {
800
786
  this.gun.user().auth(normalizedUsername, password, (ack) => {
801
- console.log(`Password authentication after creation result:`, ack);
802
787
  if (ack.err) {
803
788
  console.error(`Authentication after creation failed: ${ack.err}`);
804
789
  resolve({ success: false, error: ack.err });
@@ -808,9 +793,6 @@ class DataBase {
808
793
  setTimeout(() => {
809
794
  // Extract userPub from multiple possible sources
810
795
  const userPub = ack.pub || this.gun.user().is?.pub || ack.user?.pub;
811
- console.log(`Extracted userPub after password auth: ${userPub}`);
812
- console.log(`User object after password auth:`, this.gun.user());
813
- console.log(`User.is after password auth:`, this.gun.user().is);
814
796
  if (!userPub) {
815
797
  console.error("Authentication successful but no userPub found");
816
798
  resolve({
@@ -874,7 +856,6 @@ class DataBase {
874
856
  this.user = this.gun.user();
875
857
  // Run post-authentication tasks
876
858
  try {
877
- console.log(`Running post-auth setup with userPub: ${authResult.userPub}`);
878
859
  const postAuthResult = await this.runPostAuthOnAuthResult(username, authResult.userPub, authResult);
879
860
  // Return the post-auth result which includes the complete user data
880
861
  return postAuthResult;
@@ -931,7 +912,6 @@ class DataBase {
931
912
  // For pair-based authentication, we don't need to call gun.user().create()
932
913
  // because the pair already contains the cryptographic credentials
933
914
  // We just need to validate that the pair is valid and return success
934
- console.log(`User created successfully with pair for: ${normalizedUsername}`);
935
915
  resolve({ success: true, userPub: pair.pub });
936
916
  });
937
917
  }
@@ -964,7 +944,6 @@ class DataBase {
964
944
  if (normalizedUsername.length === 0) {
965
945
  throw new Error("Username cannot be empty");
966
946
  }
967
- console.log(`Setting up user profile for ${normalizedUsername} with userPub: ${userPub}`);
968
947
  const existingUser = await this.gun.get(userPub).then();
969
948
  const isNewUser = !existingUser || !existingUser.alias;
970
949
  // const isNewUser = true;
@@ -1042,7 +1021,6 @@ class DataBase {
1042
1021
  if (!userMetadataResult) {
1043
1022
  return false;
1044
1023
  }
1045
- console.log(`Comprehensive user tracking setup completed for ${username}`);
1046
1024
  return true;
1047
1025
  }
1048
1026
  catch (error) {
@@ -1065,7 +1043,6 @@ class DataBase {
1065
1043
  return false;
1066
1044
  }
1067
1045
  else {
1068
- console.log(`Alias index created: ~@${username} -> ${userPub}`);
1069
1046
  return true;
1070
1047
  }
1071
1048
  }
@@ -1089,7 +1066,6 @@ class DataBase {
1089
1066
  return false;
1090
1067
  }
1091
1068
  else {
1092
- console.log(`Username mapping created: ${username} -> ${userPub}`);
1093
1069
  return true;
1094
1070
  }
1095
1071
  }
@@ -1120,7 +1096,6 @@ class DataBase {
1120
1096
  return false;
1121
1097
  }
1122
1098
  else {
1123
- console.log(`User registry created: ${userPub}`);
1124
1099
  return true;
1125
1100
  }
1126
1101
  }
@@ -1144,7 +1119,6 @@ class DataBase {
1144
1119
  return false;
1145
1120
  }
1146
1121
  else {
1147
- console.log(`Reverse lookup created: ${userPub} -> ${username}`);
1148
1122
  return true;
1149
1123
  }
1150
1124
  }
@@ -1164,7 +1138,6 @@ class DataBase {
1164
1138
  return false;
1165
1139
  }
1166
1140
  else {
1167
- console.log(`Epub index created: ${epub} -> ${userPub}`);
1168
1141
  return true;
1169
1142
  }
1170
1143
  }
@@ -1190,7 +1163,6 @@ class DataBase {
1190
1163
  return false;
1191
1164
  }
1192
1165
  else {
1193
- console.log(`User metadata created for ${userPub}`);
1194
1166
  return true;
1195
1167
  }
1196
1168
  }
@@ -1224,7 +1196,7 @@ class DataBase {
1224
1196
  }
1225
1197
  }
1226
1198
  catch (error) {
1227
- console.log(`GunDB alias lookup failed for ${normalizedAlias}:`, error);
1199
+ console.error(`GunDB alias lookup failed for ${normalizedAlias}:`, error);
1228
1200
  }
1229
1201
  // Method 2: Try username mapping (usernames/alias -> userPub)
1230
1202
  try {
@@ -1240,7 +1212,7 @@ class DataBase {
1240
1212
  }
1241
1213
  }
1242
1214
  catch (error) {
1243
- console.log(`Username mapping lookup failed for ${normalizedAlias}:`, error);
1215
+ console.error(`Username mapping lookup failed for ${normalizedAlias}:`, error);
1244
1216
  }
1245
1217
  return null;
1246
1218
  }
@@ -1273,7 +1245,7 @@ class DataBase {
1273
1245
  }
1274
1246
  }
1275
1247
  catch (error) {
1276
- console.log(`User registry lookup failed for ${userPub}:`, error);
1248
+ console.error(`User registry lookup failed for ${userPub}:`, error);
1277
1249
  }
1278
1250
  // Method 2: Try user's own node
1279
1251
  try {
@@ -1289,7 +1261,7 @@ class DataBase {
1289
1261
  }
1290
1262
  }
1291
1263
  catch (error) {
1292
- console.log(`User node lookup failed for ${userPub}:`, error);
1264
+ console.error(`User node lookup failed for ${userPub}:`, error);
1293
1265
  }
1294
1266
  return null;
1295
1267
  }
@@ -1373,14 +1345,14 @@ class DataBase {
1373
1345
  .then();
1374
1346
  }
1375
1347
  catch (error) {
1376
- console.log(`Failed to update lastSeen in user registry:`, error);
1348
+ console.error(`Failed to update lastSeen in user registry:`, error);
1377
1349
  }
1378
1350
  // Update in user's own node
1379
1351
  try {
1380
1352
  await this.gun.get(userPub).get("lastSeen").put(timestamp).then();
1381
1353
  }
1382
1354
  catch (error) {
1383
- console.log(`Failed to update lastSeen in user node:`, error);
1355
+ console.error(`Failed to update lastSeen in user node:`, error);
1384
1356
  }
1385
1357
  }
1386
1358
  catch (error) {
@@ -1392,10 +1364,8 @@ class DataBase {
1392
1364
  */
1393
1365
  async performAuthentication(username, password, pair) {
1394
1366
  return new Promise((resolve) => {
1395
- console.log(`Attempting authentication for user: ${username}`);
1396
1367
  if (pair) {
1397
1368
  this.gun.user().auth(pair, (ack) => {
1398
- console.log(`Pair authentication result:`, ack);
1399
1369
  if (ack.err) {
1400
1370
  console.error(`Login error for ${username}: ${ack.err}`);
1401
1371
  resolve({ success: false, error: ack.err });
@@ -1407,7 +1377,6 @@ class DataBase {
1407
1377
  }
1408
1378
  else {
1409
1379
  this.gun.user().auth(username, password, (ack) => {
1410
- console.log(`Password authentication result:`, ack);
1411
1380
  if (ack.err) {
1412
1381
  console.error(`Login error for ${username}: ${ack.err}`);
1413
1382
  resolve({ success: false, error: ack.err });
@@ -1456,9 +1425,6 @@ class DataBase {
1456
1425
  if (!alias) {
1457
1426
  alias = username;
1458
1427
  }
1459
- console.log(`Login authentication successful, extracted userPub: ${userPub}`);
1460
- console.log(`User object:`, this.gun.user());
1461
- console.log(`User.is:`, this.gun.user().is);
1462
1428
  if (!userPub) {
1463
1429
  return {
1464
1430
  success: false,
@@ -1624,7 +1590,6 @@ class DataBase {
1624
1590
  if (!userPub) {
1625
1591
  return { success: false, error: "User not found" };
1626
1592
  }
1627
- // console.log(`Found user public key for password recovery: ${userPub}`);
1628
1593
  // Access the user's security data directly from their public key node
1629
1594
  const securityData = await this.node.get(userPub)
1630
1595
  .get("security")
@@ -1840,14 +1805,7 @@ exports.DataBase = DataBase;
1840
1805
  // Errors
1841
1806
  DataBase.Errors = GunErrors;
1842
1807
  const createGun = (config, silent) => {
1843
- if (!silent) {
1844
- console.log("Creating Gun instance with config:", config);
1845
- console.log("Config peers:", config?.peers);
1846
- }
1847
1808
  const gunInstance = (0, gun_1.default)(config);
1848
- if (!silent) {
1849
- console.log("Created Gun instance:", gunInstance);
1850
- }
1851
1809
  return gunInstance;
1852
1810
  };
1853
1811
  exports.createGun = createGun;