@hive-p2p/browser 1.0.67 → 1.0.69

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/core/arbiter.mjs CHANGED
@@ -99,7 +99,7 @@ export class Arbiter {
99
99
  /** Call from HiveP2P module only! @param {string} from @param {any} message @param {Uint8Array} serialized @param {number} [powCheckFactor] default: 0.01 (1%) */
100
100
  async digestMessage(from, message, serialized, powCheckFactor = .01) {
101
101
  const { senderId, pubkey, topic, expectedEnd } = message; // avoid powControl() on banished peers
102
- if (!await this.#signatureControl(from, message, serialized)) return;
102
+ if (!this.#signatureControl(from, message, serialized)) return;
103
103
  if (!this.#lengthControl(from, topic ? 'gossip' : 'unicast', serialized, expectedEnd)) return;
104
104
 
105
105
  const routeOrHopsOk = topic ? this.#hopsControl(from, message) : this.#routeLengthControl(from, message);
@@ -111,11 +111,11 @@ export class Arbiter {
111
111
  return true;
112
112
  }
113
113
  /** @param {string} from @param {import('./gossip.mjs').GossipMessage} message @param {Uint8Array} serialized */
114
- async #signatureControl(from, message, serialized) {
114
+ #signatureControl(from, message, serialized) {
115
115
  try {
116
116
  const { pubkey, signature, signatureStart } = message;
117
117
  const signedData = serialized.subarray(0, signatureStart);
118
- const signatureValid = await this.cryptoCodex.verifySignature(pubkey, signedData, signature);
118
+ const signatureValid = this.cryptoCodex.verifySignature(pubkey, signedData, signature);
119
119
  if (!signatureValid) throw new Error('Gossip signature invalid');
120
120
  this.adjustTrust(from, TRUST_VALUES.VALID_SIGNATURE, 'Gossip signature valid');
121
121
  return true;
@@ -5,54 +5,10 @@ import { DirectMessage, ReroutedDirectMessage } from './unicast.mjs';
5
5
  import { Converter } from '../services/converter.mjs';
6
6
  import { ed25519, Argon2Unified } from '../services/cryptos.mjs'; // now exposed in full and browser builds
7
7
 
8
- class Ed25519BatchVerifier {
9
- #verifyQueue = [];
10
- #verifyResolvers = new Map();
11
- #batchTimer = null;
12
- #nextId = 0;
13
- #BATCH_SIZE = 10;
14
- #BATCH_TIMEOUT = 5;
15
-
16
- verifySignature(publicKey, dataToVerify, signature) {
17
- return new Promise((resolve) => {
18
- const id = this.#nextId++;
19
- this.#verifyQueue.push({ id, publicKey, dataToVerify, signature });
20
- this.#verifyResolvers.set(id, resolve);
21
-
22
- // Flush immediately if batch full
23
- if (this.#verifyQueue.length >= this.#BATCH_SIZE) {
24
- this.#flushBatch();
25
- return;
26
- }
27
-
28
- // Otherwise schedule flush
29
- if (!this.#batchTimer)
30
- this.#batchTimer = setTimeout(() => this.#flushBatch(), this.#BATCH_TIMEOUT);
31
- });
32
- }
33
-
34
- async #flushBatch() {
35
- if (this.#batchTimer) clearTimeout(this.#batchTimer), this.#batchTimer = null;
36
- if (this.#verifyQueue.length === 0) return;
37
-
38
- const batch = this.#verifyQueue.splice(0);
39
- const results = await Promise.all(
40
- batch.map(item => ed25519.verifyAsync(item.signature, item.dataToVerify, item.publicKey))
41
- );
42
-
43
- for (let i = 0; i < batch.length; i++) {
44
- this.#verifyResolvers.get(batch[i].id)(results[i]);
45
- this.#verifyResolvers.delete(batch[i].id);
46
- }
47
- }
48
- }
49
-
50
8
  export class CryptoCodex {
51
9
  argon2 = new Argon2Unified();
52
10
  converter = new Converter();
53
11
  AVOID_CRYPTO = false;
54
- /** @type {Ed25519BatchVerifier} only if "AVOID_CRYPTO" is disabled*/
55
- verifier;
56
12
  verbose = NODE.DEFAULT_VERBOSE;
57
13
  /** @type {string} */ id;
58
14
  /** @type {Uint8Array} */ publicKey;
@@ -88,7 +44,6 @@ export class CryptoCodex {
88
44
  if (this.nodeId) return;
89
45
  await this.#generateAntiSybilIdentity(seed, asPublicNode);
90
46
  this.AVOID_CRYPTO = false; // enable crypto operations
91
- this.verifier = new Ed25519BatchVerifier();
92
47
  if (!this.id) throw new Error('Failed to generate identity');
93
48
  }
94
49
  /** Check if the pubKey meets the difficulty using Argon2 derivation @param {Uint8Array} publicKey */
@@ -105,7 +60,7 @@ export class CryptoCodex {
105
60
  async #generateAntiSybilIdentity(seed, asPublicNode) {
106
61
  const maxIterations = (2 ** IDENTITY.DIFFICULTY) * 100; // avoid infinite loop
107
62
  for (let i = 0; i < maxIterations; i++) { // avoid infinite loop
108
- const { secretKey, publicKey } = await ed25519.keygenAsync(seed);
63
+ const { secretKey, publicKey } = ed25519.keygen(seed);
109
64
  const id = this.#idFromPublicKey(publicKey);
110
65
  if (asPublicNode && !this.isPublicNode(id)) continue; // Check prefix
111
66
  if (!asPublicNode && this.isPublicNode(id)) continue; // Check prefix
@@ -121,14 +76,14 @@ export class CryptoCodex {
121
76
 
122
77
  // MESSSAGE CREATION (SERIALIZATION AND SIGNATURE INCLUDED)
123
78
  /** @param {Uint8Array} bufferView @param {Uint8Array} privateKey @param {number} [signaturePosition] */
124
- async signBufferViewAndAppendSignature(bufferView, privateKey, signaturePosition = bufferView.length - IDENTITY.SIGNATURE_LENGTH) {
79
+ signBufferViewAndAppendSignature(bufferView, privateKey, signaturePosition = bufferView.length - IDENTITY.SIGNATURE_LENGTH) {
125
80
  if (this.AVOID_CRYPTO) return;
126
81
  const dataToSign = bufferView.subarray(0, signaturePosition);
127
- const signature = await ed25519.signAsync(dataToSign, privateKey);
82
+ const signature = ed25519.sign(dataToSign, privateKey);
128
83
  bufferView.set(signature, signaturePosition);
129
84
  }
130
85
  /** @param {string} topic @param {string | Uint8Array | Object} data @param {number} [HOPS] @param {string[]} route @param {string[]} [neighbors] */
131
- async createGossipMessage(topic, data, HOPS = 3, neighbors = [], timestamp = CLOCK.time) {
86
+ createGossipMessage(topic, data, HOPS = 3, neighbors = [], timestamp = CLOCK.time) {
132
87
  const MARKER = GOSSIP.MARKERS_BYTES[topic];
133
88
  if (MARKER === undefined) throw new Error(`Failed to create gossip message: unknown topic '${topic}'.`);
134
89
 
@@ -142,7 +97,7 @@ export class CryptoCodex {
142
97
  bufferView.set(neighborsBytes, 47); // X bytes for neighbors
143
98
  bufferView.set(dataBytes, 47 + neighborsBytes.length); // X bytes for data
144
99
  bufferView.set([Math.min(255, HOPS)], totalBytes - 1); // 1 byte for HOPS (Unsigned)
145
- await this.signBufferViewAndAppendSignature(bufferView, this.privateKey, totalBytes - IDENTITY.SIGNATURE_LENGTH - 1);
100
+ this.signBufferViewAndAppendSignature(bufferView, this.privateKey, totalBytes - IDENTITY.SIGNATURE_LENGTH - 1);
146
101
  return bufferView;
147
102
  }
148
103
  /** @param {Uint8Array} serializedMessage */
@@ -153,7 +108,7 @@ export class CryptoCodex {
153
108
  return clone;
154
109
  }
155
110
  /** @param {string} type @param {string | Uint8Array | Object} data @param {string[]} route @param {string[]} [neighbors] */
156
- async createUnicastMessage(type, data, route, neighbors = [], timestamp = CLOCK.time) {
111
+ createUnicastMessage(type, data, route, neighbors = [], timestamp = CLOCK.time) {
157
112
  const MARKER = UNICAST.MARKERS_BYTES[type];
158
113
  if (MARKER === undefined) throw new Error(`Failed to create unicast message: unknown type '${type}'.`);
159
114
  if (route.length < 2) throw new Error('Failed to create unicast message: route must have at least 2 nodes (next hop and target).');
@@ -173,11 +128,11 @@ export class CryptoCodex {
173
128
  bufferView.set([route.length], 47 + NDBL); // 1 byte for route length
174
129
  bufferView.set(routeBytes, 47 + 1 + NDBL); // X bytes for route
175
130
 
176
- await this.signBufferViewAndAppendSignature(bufferView, this.privateKey, totalBytes - IDENTITY.SIGNATURE_LENGTH);
131
+ this.signBufferViewAndAppendSignature(bufferView, this.privateKey, totalBytes - IDENTITY.SIGNATURE_LENGTH);
177
132
  return bufferView;
178
133
  }
179
134
  /** @param {Uint8Array} serialized @param {string[]} newRoute */
180
- async createReroutedUnicastMessage(serialized, newRoute) {
135
+ createReroutedUnicastMessage(serialized, newRoute) {
181
136
  if (newRoute.length < 2) throw new Error('Failed to create rerouted unicast message: route must have at least 2 nodes (next hop and target).');
182
137
  if (newRoute.length > UNICAST.MAX_HOPS) throw new Error(`Failed to create rerouted unicast message: route exceeds max hops (${UNICAST.MAX_HOPS}).`);
183
138
 
@@ -189,7 +144,7 @@ export class CryptoCodex {
189
144
  bufferView.set(this.publicKey, serialized.length); // 32 bytes for new public key
190
145
  for (let i = 0; i < routeBytesArray.length; i++) bufferView.set(routeBytesArray[i], serialized.length + 32 + (i * IDENTITY.ID_LENGTH)); // new route
191
146
 
192
- await this.signBufferViewAndAppendSignature(bufferView, this.privateKey, totalBytes - IDENTITY.SIGNATURE_LENGTH);
147
+ this.signBufferViewAndAppendSignature(bufferView, this.privateKey, totalBytes - IDENTITY.SIGNATURE_LENGTH);
193
148
  return bufferView;
194
149
  }
195
150
  /** @param {string[]} ids */
@@ -217,9 +172,9 @@ export class CryptoCodex {
217
172
 
218
173
  // MESSSAGE READING (DESERIALIZATION AND SIGNATURE VERIFICATION INCLUDED)
219
174
  /** @param {Uint8Array} publicKey @param {Uint8Array} dataToVerify @param {Uint8Array} signature */
220
- async verifySignature(publicKey, dataToVerify, signature) {
175
+ verifySignature(publicKey, dataToVerify, signature) {
221
176
  if (this.AVOID_CRYPTO) return true;
222
- return this.verifier.verifySignature(publicKey, dataToVerify, signature);
177
+ return ed25519.verify(signature, dataToVerify, publicKey);
223
178
  }
224
179
  /** @param {Uint8Array} bufferView */
225
180
  readBufferHeader(bufferView, readAssociatedId = true) {
package/core/gossip.mjs CHANGED
@@ -103,9 +103,9 @@ export class Gossip {
103
103
  }
104
104
  /** Gossip a message to all connected peers > will be forwarded to all peers
105
105
  * @param {string | Uint8Array | Object} data @param {string} topic default: 'gossip' @param {number} [HOPS] */
106
- async broadcastToAll(data, topic = 'gossip', HOPS) {
106
+ broadcastToAll(data, topic = 'gossip', HOPS) {
107
107
  const hops = HOPS || GOSSIP.HOPS[topic] || GOSSIP.HOPS.default;
108
- const serializedMessage = await this.cryptoCodex.createGossipMessage(topic, data, hops, this.peerStore.neighborsList);
108
+ const serializedMessage = this.cryptoCodex.createGossipMessage(topic, data, hops, this.peerStore.neighborsList);
109
109
  if (!this.bloomFilter.addMessage(serializedMessage)) return; // avoid sending duplicate messages
110
110
  if (this.verbose > 3) console.log(`(${this.id}) Gossip ${topic}, to ${JSON.stringify(this.peerStore.neighborsList)}: ${data}`);
111
111
  for (const peerId of this.peerStore.neighborsList) this.#broadcastToPeer(peerId, serializedMessage);
@@ -51,12 +51,12 @@ export class NodeServices {
51
51
  #startWebSocketServer(domain = 'localhost', port = SERVICE.PORT) {
52
52
  this.wsServer = new TRANSPORTS.WS_SERVER({ port, host: domain });
53
53
  this.wsServer.on('error', (error) => console.error(`WebSocket error on Node #${this.id}:`, error));
54
- this.wsServer.on('connection', async (ws) => {
54
+ this.wsServer.on('connection', (ws) => {
55
55
  ws.on('close', () => { if (remoteId) for (const cb of this.peerStore.callbacks.disconnect) cb(remoteId, 'in'); });
56
56
  ws.on('error', (error) => console.error(`WebSocket error on Node #${this.id} with peer ${remoteId}:`, error.stack));
57
57
 
58
58
  let remoteId;
59
- ws.on('message', async (data) => { // When peer proves his id, we can handle data normally
59
+ ws.on('message', (data) => { // When peer proves his id, we can handle data normally
60
60
  if (remoteId) for (const cb of this.peerStore.callbacks.data) cb(remoteId, data);
61
61
  else { // FIRST MESSAGE SHOULD BE HANDSHAKE WITH ID
62
62
  const d = new Uint8Array(data); if (d[0] > 127) return; // not unicast, ignore
@@ -68,7 +68,7 @@ export class NodeServices {
68
68
 
69
69
  const { signatureStart, pubkey, signature } = message;
70
70
  const signedData = d.subarray(0, signatureStart);
71
- if (!await this.cryptoCodex.verifySignature(pubkey, signedData, signature)) return;
71
+ if (!this.cryptoCodex.verifySignature(pubkey, signedData, signature)) return;
72
72
 
73
73
  remoteId = route[0];
74
74
  this.peerStore.digestPeerNeighbors(remoteId, neighborsList); // Update known store
@@ -79,8 +79,7 @@ export class NodeServices {
79
79
  }
80
80
  });
81
81
 
82
- const handshakeMsg = await this.cryptoCodex.createUnicastMessage('handshake', null, [this.id, this.id], this.peerStore.neighborsList);
83
- ws.send(handshakeMsg);
82
+ ws.send(this.cryptoCodex.createUnicastMessage('handshake', null, [this.id, this.id], this.peerStore.neighborsList));
84
83
  });
85
84
  }
86
85
  #startSTUNServer(host = 'localhost', port = SERVICE.PORT + 1) {
package/core/node.mjs CHANGED
@@ -162,10 +162,10 @@ export class Node {
162
162
  /** Broadcast a message to all connected peers or to a specified peer
163
163
  * @param {string | Uint8Array | Object} data @param {string} topic default: 'gossip' @param {string} [targetId] default: broadcast to all
164
164
  * @param {number} [timestamp] default: CLOCK.time @param {number} [HOPS] default: GOSSIP.HOPS[topic] || GOSSIP.HOPS.default */
165
- async broadcast(data, topic, HOPS) { return this.gossip.broadcastToAll(data, topic, HOPS); }
165
+ broadcast(data, topic, HOPS) { return this.gossip.broadcastToAll(data, topic, HOPS); }
166
166
 
167
167
  /** @param {string} remoteId @param {string | Uint8Array | Object} data @param {string} type */
168
- async sendMessage(remoteId, data, type, spread = 1) { return this.messager.sendUnicast(remoteId, data, type, spread); }
168
+ sendMessage(remoteId, data, type, spread = 1) { return this.messager.sendUnicast(remoteId, data, type, spread); }
169
169
 
170
170
  /** Send a connection request to a peer */
171
171
  async tryConnectToPeer(targetId = 'toto', retry = 10) {
@@ -169,13 +169,13 @@ export class Topologist {
169
169
  let remoteId = null;
170
170
  const ws = new TRANSPORTS.WS_CLIENT(this.#getFullWsUrl(publicUrl)); ws.binaryType = 'arraybuffer';
171
171
  ws.onerror = (error) => console.error(`WebSocket error:`, error.stack);
172
- ws.onopen = async () => {
172
+ ws.onopen = () => {
173
173
  this.bootstrapsConnectionState.set(publicUrl, true);
174
174
  ws.onclose = () => {
175
175
  this.bootstrapsConnectionState.set(publicUrl, false);
176
176
  for (const cb of this.peerStore.callbacks.disconnect) cb(remoteId, 'out');
177
177
  }
178
- ws.onmessage = async (data) => {
178
+ ws.onmessage = (data) => {
179
179
  if (remoteId) for (const cb of this.peerStore.callbacks.data) cb(remoteId, data.data);
180
180
  else { // FIRST MESSAGE SHOULD BE HANDSHAKE WITH ID
181
181
  const d = new Uint8Array(data.data); if (d[0] > 127) return; // not unicast, ignore
@@ -187,7 +187,7 @@ export class Topologist {
187
187
 
188
188
  const { signatureStart, pubkey, signature } = message;
189
189
  const signedData = d.subarray(0, signatureStart);
190
- if (!await this.cryptoCodex.verifySignature(pubkey, signedData, signature)) return;
190
+ if (!this.cryptoCodex.verifySignature(pubkey, signedData, signature)) return;
191
191
 
192
192
  remoteId = route[0];
193
193
  this.peerStore.digestPeerNeighbors(remoteId, neighborsList); // Update known store
@@ -197,8 +197,8 @@ export class Topologist {
197
197
  for (const cb of this.peerStore.callbacks.connect) cb(remoteId, 'out');
198
198
  }
199
199
  };
200
- const handshakeMsg = await this.cryptoCodex.createUnicastMessage('handshake', null, [this.id, this.id], this.peerStore.neighborsList);
201
- ws.send(handshakeMsg);
200
+
201
+ ws.send(this.cryptoCodex.createUnicastMessage('handshake', null, [this.id, this.id], this.peerStore.neighborsList));
202
202
  };
203
203
  }
204
204
  #tryToSpreadSDP(nonPublicNeighborsCount = 0, isHalfReached = false) { // LOOP TO SELECT ONE UNSEND READY OFFER AND BROADCAST IT
package/core/unicast.mjs CHANGED
@@ -76,14 +76,13 @@ export class UnicastMessager {
76
76
  /** Send unicast message to a target
77
77
  * @param {string} remoteId @param {string | Uint8Array | Object} data @param {string} type
78
78
  * @param {number} [spread] Max neighbors used to relay the message, default: 1 */
79
- async sendUnicast(remoteId, data, type = 'message', spread = 1) {
79
+ sendUnicast(remoteId, data, type = 'message', spread = 1) {
80
80
  if (remoteId === this.id) return false;
81
81
 
82
82
  const builtResult = this.pathFinder.buildRoutes(remoteId, this.maxRoutes, this.maxHops, this.maxNodes, true);
83
83
  if (!builtResult.success) return false;
84
84
 
85
85
  // Caution: re-routing usage who can involve insane results
86
- const createdMessagePromises = [];
87
86
  const finalSpread = builtResult.success === 'blind' ? 1 : spread; // Spread only if re-routing is false
88
87
  for (let i = 0; i < Math.min(finalSpread, builtResult.routes.length); i++) {
89
88
  const route = builtResult.routes[i].path;
@@ -91,11 +90,9 @@ export class UnicastMessager {
91
90
  if (this.verbose > 1) console.warn(`Cannot send unicast message to ${remoteId} as route exceeds maxHops (${UNICAST.MAX_HOPS}). BFS incurred.`);
92
91
  continue; // too long route
93
92
  }
94
- createdMessagePromises.push(this.cryptoCodex.createUnicastMessage(type, data, route, this.peerStore.neighborsList));
95
- }
96
93
 
97
- const createdMessages = await Promise.all(createdMessagePromises);
98
- for (const message of createdMessages) this.#sendMessageToPeer(message.route[1], message);
94
+ this.#sendMessageToPeer(route[1], this.cryptoCodex.createUnicastMessage(type, data, route, this.peerStore.neighborsList));
95
+ }
99
96
  return true;
100
97
  }
101
98
  /** @param {string} targetId @param {Uint8Array} serialized */