@hive-p2p/browser 1.0.88 → 1.0.90

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.
@@ -9,7 +9,7 @@ import { concatBytes } from '@noble/ciphers/utils.js';
9
9
  export class CryptoCodex {
10
10
  argon2 = new Argon2Unified();
11
11
  converter = new Converter();
12
- AVOID_CRYPTO = false;
12
+ AVOID_CRYPTO = true; // AVOID CRYPTO OPERATIONS (default) => auto-enable when generate() is called, but can be set to true to disable crypto in any case (e.g. for testing with string ids)
13
13
  verbose = NODE.DEFAULT_VERBOSE;
14
14
  /** @type {string} */ id;
15
15
  /** @type {Uint8Array} */ publicKey;
@@ -92,6 +92,11 @@ export class CryptoCodex {
92
92
  const cipher = chacha20poly1305(sharedSecret, nonce);
93
93
  const encrypted = cipher.encrypt(data);
94
94
  return concatBytes(nonce, encrypted);
95
+ // Vanilla =>
96
+ /*const result = new Uint8Array(nonce.length + encrypted.length);
97
+ result.set(nonce, 0);
98
+ result.set(encrypted, nonce.length);
99
+ return result;*/
95
100
  }
96
101
  /** @param {Uint8Array} encryptedData @param {Uint8Array} sharedSecret */
97
102
  decryptData(encryptedData, sharedSecret) {
@@ -223,9 +228,10 @@ export class CryptoCodex {
223
228
  readGossipMessage(serialized) {
224
229
  if (this.verbose > 3) console.log(`%creadGossipMessage ${serialized.byteLength} bytes`, LOG_CSS.CRYPTO_CODEX);
225
230
  if (this.verbose > 4) console.log(`%c${serialized}`, LOG_CSS.CRYPTO_CODEX);
231
+ let topic;
226
232
  try { // 1, 1, 1, 8, 4, 32, X, 64, 1
227
233
  const { marker, dataCode, neighLength, timestamp, dataLength, pubkey, associatedId } = this.readBufferHeader(serialized);
228
- const topic = GOSSIP.MARKERS_BYTES[marker];
234
+ topic = GOSSIP.MARKERS_BYTES[marker];
229
235
  if (topic === undefined) throw new Error(`Failed to deserialize gossip message: unknown marker byte ${d[0]}.`);
230
236
  const NDBL = neighLength + dataLength;
231
237
  const neighbors = this.#bytesToIds(serialized.slice(47, 47 + neighLength));
@@ -243,9 +249,10 @@ export class CryptoCodex {
243
249
  readUnicastMessage(serialized, peerStore) {
244
250
  if (this.verbose > 3) console.log(`%creadUnicastMessage ${serialized.byteLength} bytes`, LOG_CSS.CRYPTO_CODEX);
245
251
  if (this.verbose > 4) console.log(`%c${serialized}`, LOG_CSS.CRYPTO_CODEX);
252
+ let type;
246
253
  try { // 1, 1, 1, 8, 4, 32, X, 1, X, 64
247
254
  const { marker, dataCode, neighLength, timestamp, dataLength, pubkey } = this.readBufferHeader(serialized, false);
248
- const type = UNICAST.MARKERS_BYTES[marker];
255
+ type = UNICAST.MARKERS_BYTES[marker];
249
256
  if (type === undefined) throw new Error(`Failed to deserialize unicast message: unknown marker byte ${d[0]}.`);
250
257
  const NDBL = neighLength + dataLength;
251
258
  const neighbors = this.#bytesToIds(serialized.slice(47, 47 + neighLength));
package/core/gossip.mjs CHANGED
@@ -83,7 +83,7 @@ class DegenerateBloomFilter {
83
83
  }
84
84
 
85
85
  export class Gossip {
86
- /** @type {Record<string, Function[]>} */ callbacks = { message_handle: [] };
86
+ /** @type {Record<string, function(GossipMessage)[]>} */ callbacks = { message_handle: [] };
87
87
  id; cryptoCodex; arbiter; peerStore; verbose; bloomFilter;
88
88
 
89
89
  /** @param {string} selfId @param {import('./crypto-codex.mjs').CryptoCodex} cryptoCodex @param {import('./arbiter.mjs').Arbiter} arbiter @param {import('./peer-store.mjs').PeerStore} peerStore */
@@ -96,7 +96,7 @@ export class Gossip {
96
96
  this.bloomFilter = new DegenerateBloomFilter(cryptoCodex);
97
97
  }
98
98
 
99
- /** @param {string} callbackType @param {Function} callback */
99
+ /** @param {string} callbackType @param {function(GossipMessage)} callback */
100
100
  on(callbackType, callback) {
101
101
  if (!this.callbacks[callbackType]) this.callbacks[callbackType] = [callback];
102
102
  else this.callbacks[callbackType].push(callback);
@@ -131,6 +131,7 @@ export class Gossip {
131
131
 
132
132
  const message = this.cryptoCodex.readGossipMessage(serialized);
133
133
  if (!message) return this.arbiter.countPeerAction(from, 'WRONG_SERIALIZATION');
134
+
134
135
  const isOk = await this.arbiter.digestMessage(from, message, serialized);
135
136
  if (!isOk) return; // invalid message or from banished peer
136
137
  if (this.arbiter.isBanished(from)) return; // ignore messages from banished peers
@@ -144,7 +145,8 @@ export class Gossip {
144
145
  else console.log(`(${this.id}) Gossip ${topic} from ${senderId} (by: ${from}): ${data}`);
145
146
 
146
147
  this.peerStore.digestPeerNeighbors(senderId, neighborsList);
147
- for (const cb of this.callbacks[topic] || []) cb(senderId, data, HOPS, message); // specific topic callback
148
+ //for (const cb of this.callbacks[topic] || []) cb(senderId, data, HOPS, message); // specific topic callback
149
+ for (const cb of this.callbacks[topic] || []) cb(message); // specific topic callback with full message object
148
150
  if (HOPS < 1) return; // stop forwarding if HOPS is 0
149
151
 
150
152
  const nCount = this.peerStore.neighborsList.length;
package/core/node.mjs CHANGED
@@ -9,6 +9,13 @@ import { Topologist } from './topologist.mjs';
9
9
  import { CryptoCodex } from './crypto-codex.mjs';
10
10
  import { NodeServices } from './node-services.mjs';
11
11
 
12
+ /**
13
+ * @typedef {import('./unicast.mjs').DirectMessage} DirectMessage
14
+ * @typedef {import('./unicast.mjs').ReroutedDirectMessage} ReroutedDirectMessage
15
+ * @typedef {import('./gossip.mjs').GossipMessage} GossipMessage
16
+ * @typedef {import('./topologist.mjs').SignalData} SignalData
17
+ */
18
+
12
19
  /** Create and start a new PublicNode instance.
13
20
  * @param {Object} options
14
21
  * @param {string[]} options.bootstraps List of bootstrap nodes used as P2P network entry
@@ -86,12 +93,12 @@ export class Node {
86
93
  peerStore.on('data', (peerId, data) => this.#onData(peerId, data));
87
94
 
88
95
  // UNICAST LISTENERS
89
- messager.on('signal_answer', (senderId, data) => topologist.handleIncomingSignal(senderId, data));
90
- messager.on('signal_offer', (senderId, data) => topologist.handleIncomingSignal(senderId, data));
91
- messager.on('privacy', (senderId, data) => this.#handlePrivacy(senderId, data));
96
+ messager.on('signal_answer', (msg) => topologist.handleIncomingSignal(msg.senderId, msg.data));
97
+ messager.on('signal_offer', (msg) => topologist.handleIncomingSignal(msg.senderId, msg.data));
98
+ messager.on('privacy', (msg) => this.#handlePrivacy(msg));
92
99
 
93
100
  // GOSSIP LISTENERS
94
- gossip.on('signal_offer', (senderId, data, HOPS) => topologist.handleIncomingSignal(senderId, data, HOPS));
101
+ gossip.on('signal_offer', (msg) => topologist.handleIncomingSignal(msg.senderId, msg.data));
95
102
 
96
103
  if (verbose > 2) console.log(`Node initialized: ${this.id}`);
97
104
  }
@@ -221,8 +228,13 @@ export class Node {
221
228
  }
222
229
  return p;
223
230
  }
224
- /** @param {string} senderId @param {Uint8Array} peerPub */
225
- #handlePrivacy(senderId, peerPub) {
231
+ /** @param {DirectMessage} msg */
232
+ #handlePrivacy(msg) {
233
+ const { senderId, data: peerPub } = msg;
234
+ if (!(peerPub instanceof Uint8Array)) {
235
+ this.verbose > 1 ? console.warn(`Received invalid privacy message from ${senderId}, expected peer public key as Uint8Array.`) : null;
236
+ return;
237
+ }
226
238
  if (this.peerStore.privacy[senderId]?.sharedSecret) return; // already have shared secret
227
239
 
228
240
  if (!this.peerStore.privacy[senderId])
@@ -256,36 +268,24 @@ export class Node {
256
268
  }
257
269
 
258
270
  // HANDLERS REGISTRATION --------------------------------------------------
259
-
260
271
  /** Triggered when a new peer connection is established.
261
272
  * @param {function} callback can use arguments: (peerId:string, direction:string) */
262
273
  onPeerConnect(callback) { this.peerStore.on('connect', callback); }
263
-
264
- /** Triggered when a peer connection is closed.
265
- * @param {function} callback can use arguments: (peerId:string, direction:string) */
274
+ /** Triggered when a peer connection is closed. @param {function} callback can use arguments: (peerId:string, direction:string) */
266
275
  onPeerDisconnect(callback) { this.peerStore.on('disconnect', callback); }
267
-
268
- /** Triggered when a new message is received.
269
- * @param {function} callback can use arguments: (senderId:string, data:any) */
276
+ /** Triggered when a new message is received. @param {function(DirectMessage)} callback */
270
277
  onMessageData(callback, includesPrivate = true) {
271
278
  this.messager.on('message', callback);
272
279
  if (includesPrivate) this.messager.on('private_message', callback);
273
280
  }
274
-
275
- /** Triggered when a new private message is received.
276
- * @param {function} callback can use arguments: (senderId:string, data:any) */
281
+ /** Triggered when a new private message is received. @param {function(DirectMessage)} callback */
277
282
  onPrivateMessageData(callback) { this.messager.on('private_message', callback); }
278
-
279
- /** Triggered when a new gossip message is received.
280
- * @param {function} callback can use arguments: (senderId:string, data:any, HOPS:number) */
283
+ /** Triggered when a new gossip message is received. @param {function(GossipMessage)} callback */
281
284
  onGossipData(callback) { this.gossip.on('gossip', callback); }
282
-
283
- /** Triggered when a new signal offer is received from another peer.
284
- * @param {function} callback can use arguments: (senderId:string, data:SignalData) */
285
+ /** Triggered when a new signal offer is received from another peer. @param {function(string, SignalData)} callback can use arguments: (senderId:string, data:SignalData) */
285
286
  onSignalOffer(callback) { this.messager.on('signal_offer', callback); this.gossip.on('signal_offer', callback); }
286
-
287
287
  /** Triggered when a new signal answer is received from another peer.
288
- * @param {function} callback can use arguments: (senderId:string, data:SignalData) */
288
+ * @param {function(string, SignalData)} callback can use arguments: (senderId:string, data:SignalData) */
289
289
  onSignalAnswer(callback) { this.messager.on('signal_answer', callback); }
290
290
 
291
291
  /** Kick a peer. @param {string} peerId @param {string} [reason] */
@@ -17,7 +17,9 @@ const { SANDBOX, ICE_CANDIDATE_EMITTER, TEST_WS_EVENT_MANAGER } = SIMULATION.ENA
17
17
  * @property {number} overlap
18
18
  * @property {number} neighborsCount
19
19
  * @property {number} timestamp
20
- * */
20
+ *
21
+ * @typedef {import('./unicast.mjs').DirectMessage} DirectMessage
22
+ * @typedef {import('./gossip.mjs').GossipMessage} GossipMessage */
21
23
 
22
24
  export class OfferQueue {
23
25
  maxOffers = 30;
@@ -88,8 +90,8 @@ export class Topologist {
88
90
  if (this.phase === 0) this.tryConnectNextBootstrap(neighborsCount, nonPublicNeighborsCount);
89
91
  if (this.phase === 1) this.#tryToSpreadSDP(nonPublicNeighborsCount, isHalfReached);
90
92
  }
91
- /** @param {string} peerId @param {SignalData} data @param {number} [HOPS] */
92
- handleIncomingSignal(senderId, data, HOPS) {
93
+ /** @param {string} senderId @param {SignalData} data */
94
+ handleIncomingSignal(senderId, data) {
93
95
  if (this.isPublicNode || !senderId || this.peerStore.isKicked(senderId)) return;
94
96
  if (data.signal?.type !== 'offer' && data.signal?.type !== 'answer') return;
95
97
 
package/core/unicast.mjs CHANGED
@@ -14,8 +14,10 @@ export class DirectMessage { // TYPE DEFINITION
14
14
  signature;
15
15
  signatureStart; // position in the serialized message where the signature starts
16
16
  expectedEnd; // expected length of the serialized message
17
+ /** @type {string[] | undefined} */ newRoute; // for re-routing patch
18
+ get senderId() { return this.newRoute ? this.newRoute[0] : this.route[0]; }
17
19
 
18
- /** @param {string} type @param {number} timestamp @param {string[]} neighborsList @param {string[]} route @param {string} pubkey @param {string | Uint8Array | Object} data @param {string | undefined} signature @param {number} signatureStart @param {number} expectedEnd */
20
+ /** @param {string} type @param {number} timestamp @param {string[]} neighborsList @param {string[]} route @param {Uint8Array} pubkey @param {string | Uint8Array | Object} data @param {Uint8Array | undefined} signature @param {number} signatureStart @param {number} expectedEnd */
19
21
  constructor(type, timestamp, neighborsList, route, pubkey, data, signature, signatureStart, expectedEnd) {
20
22
  this.type = type; this.timestamp = timestamp; this.neighborsList = neighborsList;
21
23
  this.route = route; this.pubkey = pubkey; this.data = data; this.signature = signature; this.signatureStart = signatureStart; this.expectedEnd = expectedEnd;
@@ -30,7 +32,8 @@ export class DirectMessage { // TYPE DEFINITION
30
32
  traveledRoute.push(route[i]);
31
33
  if (route[i] === selfId) { selfPosition = i; break; }
32
34
  }
33
- const senderId = route[0];
35
+ //const senderId = route[0];
36
+ const senderId = this.senderId;
34
37
  const targetId = route[route.length - 1];
35
38
  const prevId = selfPosition > 0 ? route[selfPosition - 1] : null;
36
39
  const nextId = (selfPosition !== -1) ? route[selfPosition + 1] : null;
@@ -51,7 +54,7 @@ export class ReroutedDirectMessage extends DirectMessage {
51
54
  }
52
55
 
53
56
  export class UnicastMessager {
54
- /** @type {Record<string, Function[]>} */ callbacks = { message_handle: [] };
57
+ /** @type {Record<string, function(DirectMessage)[]>} */ callbacks = { message_handle: [] };
55
58
  id; cryptoCodex; arbiter; peerStore; verbose; pathFinder;
56
59
 
57
60
  maxHops = UNICAST.MAX_HOPS;
@@ -68,7 +71,7 @@ export class UnicastMessager {
68
71
  this.pathFinder = new RouteBuilder(this.id, this.peerStore);
69
72
  }
70
73
 
71
- /** @param {string} callbackType @param {Function} callback */
74
+ /** @param {string} callbackType @param {function(DirectMessage)} callback */
72
75
  on(callbackType, callback) {
73
76
  if (!this.callbacks[callbackType]) this.callbacks[callbackType] = [callback];
74
77
  else this.callbacks[callbackType].push(callback);
@@ -135,7 +138,8 @@ export class UnicastMessager {
135
138
  this.peerStore.digestPeerNeighbors(senderId, message.neighborsList);
136
139
  if (from !== senderId) this.arbiter.adjustTrust(from, TRUST_VALUES.UNICAST_RELAYED, 'Relayed unicast message');
137
140
  if (DISCOVERY.ON_UNICAST.DIGEST_TRAVELED_ROUTE) this.peerStore.digestValidRoute(traveledRoute);
138
- if (this.id === targetId) { for (const cb of this.callbacks[message.type] || []) cb(senderId, message.data); return; } // message for self
141
+ //if (this.id === targetId) { for (const cb of this.callbacks[message.type] || []) cb(senderId, message.data); return; } // message for self
142
+ if (this.id === targetId) { for (const cb of this.callbacks[message.type] || []) cb(message); return; } // message for self
139
143
 
140
144
  // re-send the message to the next peer in the route
141
145
  const { success, reason } = this.#sendMessageToPeer(nextId, serialized);