bitchat-node 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +223 -0
  2. package/dist/bin/bitchat.d.ts +7 -0
  3. package/dist/bin/bitchat.d.ts.map +1 -0
  4. package/dist/bin/bitchat.js +69 -0
  5. package/dist/bin/bitchat.js.map +1 -0
  6. package/dist/client.d.ts +77 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +411 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/crypto/index.d.ts +6 -0
  11. package/dist/crypto/index.d.ts.map +1 -0
  12. package/dist/crypto/index.js +6 -0
  13. package/dist/crypto/index.js.map +1 -0
  14. package/dist/crypto/noise.d.ts +72 -0
  15. package/dist/crypto/noise.d.ts.map +1 -0
  16. package/dist/crypto/noise.js +470 -0
  17. package/dist/crypto/noise.js.map +1 -0
  18. package/dist/crypto/signing.d.ts +34 -0
  19. package/dist/crypto/signing.d.ts.map +1 -0
  20. package/dist/crypto/signing.js +56 -0
  21. package/dist/crypto/signing.js.map +1 -0
  22. package/dist/index.d.ts +32 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +48 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/mesh/deduplicator.d.ts +48 -0
  27. package/dist/mesh/deduplicator.d.ts.map +1 -0
  28. package/dist/mesh/deduplicator.js +107 -0
  29. package/dist/mesh/deduplicator.js.map +1 -0
  30. package/dist/mesh/index.d.ts +6 -0
  31. package/dist/mesh/index.d.ts.map +1 -0
  32. package/dist/mesh/index.js +6 -0
  33. package/dist/mesh/index.js.map +1 -0
  34. package/dist/mesh/router.d.ts +90 -0
  35. package/dist/mesh/router.d.ts.map +1 -0
  36. package/dist/mesh/router.js +204 -0
  37. package/dist/mesh/router.js.map +1 -0
  38. package/dist/protocol/binary.d.ts +37 -0
  39. package/dist/protocol/binary.d.ts.map +1 -0
  40. package/dist/protocol/binary.js +310 -0
  41. package/dist/protocol/binary.js.map +1 -0
  42. package/dist/protocol/constants.d.ts +30 -0
  43. package/dist/protocol/constants.d.ts.map +1 -0
  44. package/dist/protocol/constants.js +37 -0
  45. package/dist/protocol/constants.js.map +1 -0
  46. package/dist/protocol/index.d.ts +8 -0
  47. package/dist/protocol/index.d.ts.map +1 -0
  48. package/dist/protocol/index.js +8 -0
  49. package/dist/protocol/index.js.map +1 -0
  50. package/dist/protocol/packets.d.ts +38 -0
  51. package/dist/protocol/packets.d.ts.map +1 -0
  52. package/dist/protocol/packets.js +177 -0
  53. package/dist/protocol/packets.js.map +1 -0
  54. package/dist/protocol/types.d.ts +134 -0
  55. package/dist/protocol/types.d.ts.map +1 -0
  56. package/dist/protocol/types.js +108 -0
  57. package/dist/protocol/types.js.map +1 -0
  58. package/dist/session/index.d.ts +5 -0
  59. package/dist/session/index.d.ts.map +1 -0
  60. package/dist/session/index.js +5 -0
  61. package/dist/session/index.js.map +1 -0
  62. package/dist/session/manager.d.ts +113 -0
  63. package/dist/session/manager.d.ts.map +1 -0
  64. package/dist/session/manager.js +371 -0
  65. package/dist/session/manager.js.map +1 -0
  66. package/dist/transport/ble.d.ts +92 -0
  67. package/dist/transport/ble.d.ts.map +1 -0
  68. package/dist/transport/ble.js +434 -0
  69. package/dist/transport/ble.js.map +1 -0
  70. package/dist/transport/index.d.ts +5 -0
  71. package/dist/transport/index.d.ts.map +1 -0
  72. package/dist/transport/index.js +5 -0
  73. package/dist/transport/index.js.map +1 -0
  74. package/dist/ui/index.d.ts +2 -0
  75. package/dist/ui/index.d.ts.map +1 -0
  76. package/dist/ui/index.js +2 -0
  77. package/dist/ui/index.js.map +1 -0
  78. package/dist/ui/server.d.ts +16 -0
  79. package/dist/ui/server.d.ts.map +1 -0
  80. package/dist/ui/server.js +510 -0
  81. package/dist/ui/server.js.map +1 -0
  82. package/package.json +79 -0
  83. package/src/bin/bitchat.ts +87 -0
  84. package/src/client.ts +519 -0
  85. package/src/crypto/index.ts +22 -0
  86. package/src/crypto/noise.ts +574 -0
  87. package/src/crypto/signing.ts +66 -0
  88. package/src/index.ts +95 -0
  89. package/src/mesh/deduplicator.ts +129 -0
  90. package/src/mesh/index.ts +6 -0
  91. package/src/mesh/router.ts +258 -0
  92. package/src/protocol/binary.ts +345 -0
  93. package/src/protocol/constants.ts +43 -0
  94. package/src/protocol/index.ts +15 -0
  95. package/src/protocol/packets.ts +223 -0
  96. package/src/protocol/types.ts +182 -0
  97. package/src/session/index.ts +9 -0
  98. package/src/session/manager.ts +476 -0
  99. package/src/transport/ble.ts +553 -0
  100. package/src/transport/index.ts +10 -0
  101. package/src/ui/index.ts +1 -0
  102. package/src/ui/server.ts +569 -0
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bitchat Node CLI
4
+ * Run a Bitchat mesh node with web UI
5
+ */
6
+
7
+ import { BitchatClient } from '../client.js';
8
+ import { startUIServer } from '../ui/server.js';
9
+
10
+ const args = process.argv.slice(2);
11
+ const nickname =
12
+ args.find((a) => a.startsWith('--nickname='))?.split('=')[1] ??
13
+ args.find((a) => a.startsWith('-n='))?.split('=')[1] ??
14
+ process.env.BITCHAT_NICKNAME ??
15
+ `node-${Math.random().toString(36).slice(2, 6)}`;
16
+
17
+ const port = parseInt(
18
+ args.find((a) => a.startsWith('--port='))?.split('=')[1] ??
19
+ args.find((a) => a.startsWith('-p='))?.split('=')[1] ??
20
+ process.env.BITCHAT_PORT ??
21
+ '3939',
22
+ 10
23
+ );
24
+
25
+ const testnet = args.includes('--testnet');
26
+
27
+ console.log('Starting Bitchat Node...');
28
+ console.log(` Nickname: ${nickname}`);
29
+ console.log(` Port: ${port}`);
30
+ console.log(` Network: ${testnet ? 'testnet' : 'mainnet'}`);
31
+ console.log();
32
+
33
+ const client = new BitchatClient({
34
+ nickname,
35
+ testnet,
36
+ });
37
+
38
+ // Event logging
39
+ client.on('ready', () => {
40
+ console.log(`✓ Ready`);
41
+ console.log(` Peer ID: ${client.peerID.toHex()}`);
42
+ console.log(` Fingerprint: ${client.fingerprint}`);
43
+ console.log();
44
+ });
45
+
46
+ client.on('peer:connected', (peer) => {
47
+ console.log(`+ Peer: ${peer.nickname} (${peer.peerID.toHex().slice(0, 8)}...)`);
48
+ });
49
+
50
+ client.on('peer:disconnected', (peerID) => {
51
+ console.log(`- Peer: ${peerID.toHex().slice(0, 8)}...`);
52
+ });
53
+
54
+ client.on('message', (msg) => {
55
+ const prefix = msg.isPrivate ? '[DM]' : '[PUB]';
56
+ console.log(`${prefix} ${msg.senderNickname}: ${msg.content}`);
57
+ });
58
+
59
+ client.on('error', (err, ctx) => {
60
+ console.error(`! Error (${ctx}): ${err.message}`);
61
+ });
62
+
63
+ // Start
64
+ async function main() {
65
+ try {
66
+ await client.start();
67
+
68
+ const ui = startUIServer(client, { port });
69
+ console.log(`\n🌐 Web UI: http://localhost:${port}\n`);
70
+
71
+ // Graceful shutdown
72
+ const shutdown = async () => {
73
+ console.log('\nShutting down...');
74
+ ui.stop();
75
+ await client.stop();
76
+ process.exit(0);
77
+ };
78
+
79
+ process.on('SIGINT', shutdown);
80
+ process.on('SIGTERM', shutdown);
81
+ } catch (error) {
82
+ console.error('Failed to start:', error);
83
+ process.exit(1);
84
+ }
85
+ }
86
+
87
+ main();
package/src/client.ts ADDED
@@ -0,0 +1,519 @@
1
+ /**
2
+ * Bitchat Client
3
+ * High-level API for Bitchat mesh networking
4
+ */
5
+
6
+ import { EventEmitter } from 'node:events';
7
+ import type { NoiseKeyPair, NoiseSession } from './crypto/noise.js';
8
+ import { type SigningKeyPair, sign, verify } from './crypto/signing.js';
9
+ import { type Link, MeshRouter } from './mesh/router.js';
10
+ import { decode, encode, encodeForSigning, makeDeduplicationID } from './protocol/binary.js';
11
+ import { DEFAULT_TTL } from './protocol/constants.js';
12
+ import { decodeAnnouncement, encodeAnnouncement } from './protocol/packets.js';
13
+ import {
14
+ type BitchatPacket,
15
+ type ChatMessage,
16
+ MessageType,
17
+ NoisePayloadType,
18
+ type PeerID,
19
+ type PeerInfo,
20
+ } from './protocol/types.js';
21
+ import { SessionManager } from './session/manager.js';
22
+ import { type BLELink, BLETransport } from './transport/ble.js';
23
+
24
+ export interface BitchatClientConfig {
25
+ nickname: string;
26
+ staticKeyPair?: NoiseKeyPair;
27
+ signingKeyPair?: SigningKeyPair;
28
+ testnet?: boolean;
29
+ }
30
+
31
+ export interface BitchatClientEvents {
32
+ ready: () => void;
33
+ message: (message: ChatMessage) => void;
34
+ 'peer:connected': (peer: PeerInfo) => void;
35
+ 'peer:disconnected': (peerID: PeerID) => void;
36
+ 'peer:updated': (peer: PeerInfo) => void;
37
+ error: (error: Error, context: string) => void;
38
+ }
39
+
40
+ /**
41
+ * Bitchat mesh network client
42
+ */
43
+ export class BitchatClient extends EventEmitter {
44
+ private readonly config: BitchatClientConfig;
45
+ private readonly transport: BLETransport;
46
+ private readonly router: MeshRouter;
47
+ private readonly sessions: SessionManager;
48
+ private running = false;
49
+
50
+ // Our identity
51
+ readonly nickname: string;
52
+ readonly peerID: PeerID;
53
+ readonly fingerprint: string;
54
+
55
+ /** Get the client configuration */
56
+ getConfig(): BitchatClientConfig {
57
+ return this.config;
58
+ }
59
+
60
+ constructor(config: BitchatClientConfig) {
61
+ super();
62
+ this.config = config;
63
+ this.nickname = config.nickname;
64
+
65
+ // Initialize session manager (handles keys and Noise sessions)
66
+ this.sessions = new SessionManager(
67
+ {},
68
+ {
69
+ static: config.staticKeyPair,
70
+ signing: config.signingKeyPair,
71
+ }
72
+ );
73
+
74
+ this.peerID = this.sessions.myPeerID;
75
+ this.fingerprint = this.sessions.fingerprint;
76
+
77
+ // Initialize router
78
+ this.router = new MeshRouter();
79
+ this.router.setMyPeerID(this.peerID);
80
+
81
+ // Initialize transport
82
+ this.transport = new BLETransport({
83
+ testnet: config.testnet ?? false,
84
+ });
85
+
86
+ // Wire up event handlers
87
+ this.setupEventHandlers();
88
+ }
89
+
90
+ /**
91
+ * Start the client
92
+ */
93
+ async start(): Promise<void> {
94
+ if (this.running) return;
95
+
96
+ try {
97
+ // Start transport
98
+ await this.transport.start();
99
+
100
+ this.running = true;
101
+ this.emit('ready');
102
+
103
+ // Send initial announce
104
+ setTimeout(() => this.sendAnnounce(), 1000);
105
+ } catch (error) {
106
+ this.emit('error', error as Error, 'start');
107
+ throw error;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Stop the client
113
+ */
114
+ async stop(): Promise<void> {
115
+ if (!this.running) return;
116
+
117
+ try {
118
+ // Send leave message
119
+ await this.sendLeave();
120
+
121
+ // Stop transport
122
+ await this.transport.stop();
123
+
124
+ // Clean up
125
+ this.router.destroy();
126
+ this.sessions.destroy();
127
+
128
+ this.running = false;
129
+ } catch (error) {
130
+ this.emit('error', error as Error, 'stop');
131
+ throw error;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Send a public message to the mesh
137
+ */
138
+ async sendPublicMessage(content: string): Promise<string> {
139
+ console.log('[Client] sendPublicMessage:', content);
140
+ const messageID = crypto.randomUUID();
141
+ const timestamp = BigInt(Date.now());
142
+
143
+ const packet: BitchatPacket = {
144
+ version: 1,
145
+ type: MessageType.MESSAGE,
146
+ ttl: DEFAULT_TTL,
147
+ timestamp,
148
+ senderID: this.peerID,
149
+ payload: new TextEncoder().encode(content),
150
+ isRSR: false,
151
+ };
152
+
153
+ // Sign the packet
154
+ const dataToSign = encodeForSigning(packet);
155
+ const signature = sign(dataToSign, this.sessions.signingKeyPair.secretKey);
156
+ packet.signature = signature;
157
+
158
+ // Send via router (to noble links) and transport (to bleno subscribers)
159
+ console.log('[Client] Sending to router...');
160
+ await this.router.sendPacket(packet);
161
+ console.log('[Client] Encoding packet...');
162
+ const encoded = encode(packet);
163
+ console.log('[Client] Broadcasting via transport, size:', encoded.length);
164
+ await this.transport.broadcast(encoded);
165
+ console.log('[Client] Broadcast complete');
166
+
167
+ return messageID;
168
+ }
169
+
170
+ /**
171
+ * Send a private message to a specific peer
172
+ */
173
+ async sendPrivateMessage(content: string, toPeerID: PeerID): Promise<string> {
174
+ const messageID = crypto.randomUUID();
175
+
176
+ // Check if we have a session
177
+ if (!this.sessions.hasSession(toPeerID)) {
178
+ // Need to establish session first
179
+ if (!this.sessions.hasHandshakeInProgress(toPeerID)) {
180
+ // Initiate handshake
181
+ const handshakeMsg = this.sessions.initiateHandshake(toPeerID);
182
+ await this.sendHandshakePacket(toPeerID, handshakeMsg);
183
+ }
184
+
185
+ // Queue message for after handshake
186
+ const payload = this.buildPrivateMessagePayload(content, messageID);
187
+ this.sessions.queueMessage(toPeerID, payload);
188
+
189
+ return messageID;
190
+ }
191
+
192
+ // Encrypt and send
193
+ await this.sendEncryptedMessage(toPeerID, content, messageID);
194
+ return messageID;
195
+ }
196
+
197
+ /**
198
+ * Get list of connected peers
199
+ */
200
+ getConnectedPeers(): PeerInfo[] {
201
+ return this.sessions.getAllPeers().filter((p) => p.isConnected);
202
+ }
203
+
204
+ /**
205
+ * Get peer info
206
+ */
207
+ getPeerInfo(peerID: PeerID): PeerInfo | undefined {
208
+ return this.sessions.getPeerInfo(peerID);
209
+ }
210
+
211
+ // --- Private methods ---
212
+
213
+ private setupEventHandlers(): void {
214
+ // Transport events
215
+ this.transport.on('ready', () => {
216
+ // Add links to router as they're discovered
217
+ });
218
+
219
+ this.transport.on('link:connected', (link: BLELink) => {
220
+ this.router.addLink(link);
221
+ });
222
+
223
+ this.transport.on('link:disconnected', (linkId: string) => {
224
+ this.router.removeLink(linkId);
225
+ });
226
+
227
+ this.transport.on('data', (data: Uint8Array, link: BLELink) => {
228
+ this.handleIncomingData(data, link);
229
+ });
230
+
231
+ this.transport.on('error', (error: Error, context: string) => {
232
+ this.emit('error', error, `transport:${context}`);
233
+ });
234
+
235
+ // Router events
236
+ this.router.on('packet', (packet: BitchatPacket, fromLink: Link | null) => {
237
+ this.handlePacket(packet, fromLink as BLELink | null);
238
+ });
239
+
240
+ // Session events
241
+ this.sessions.on('session:established', (peerID: PeerID, _session: NoiseSession) => {
242
+ const peer = this.sessions.getPeerInfo(peerID);
243
+ if (peer) {
244
+ this.emit('peer:updated', peer);
245
+ }
246
+ });
247
+
248
+ this.sessions.on('handshake:message', async (peerID: PeerID, encrypted: Uint8Array) => {
249
+ // Send queued encrypted message
250
+ const packet: BitchatPacket = {
251
+ version: 1,
252
+ type: MessageType.NOISE_ENCRYPTED,
253
+ ttl: DEFAULT_TTL,
254
+ timestamp: BigInt(Date.now()),
255
+ senderID: this.peerID,
256
+ recipientID: peerID,
257
+ payload: encrypted,
258
+ isRSR: false,
259
+ };
260
+ await this.router.sendToPeer(packet, peerID);
261
+ });
262
+ }
263
+
264
+ private async handleIncomingData(data: Uint8Array, link: BLELink): Promise<void> {
265
+ const packet = decode(data);
266
+ if (!packet) return;
267
+
268
+ await this.router.handlePacket(packet, link);
269
+ }
270
+
271
+ private handlePacket(packet: BitchatPacket, fromLink: BLELink | null): void {
272
+ switch (packet.type) {
273
+ case MessageType.ANNOUNCE:
274
+ this.handleAnnounce(packet, fromLink);
275
+ break;
276
+
277
+ case MessageType.MESSAGE:
278
+ this.handlePublicMessage(packet);
279
+ break;
280
+
281
+ case MessageType.LEAVE:
282
+ this.handleLeave(packet);
283
+ break;
284
+
285
+ case MessageType.NOISE_HANDSHAKE:
286
+ this.handleHandshake(packet);
287
+ break;
288
+
289
+ case MessageType.NOISE_ENCRYPTED:
290
+ this.handleEncrypted(packet);
291
+ break;
292
+
293
+ default:
294
+ // Unknown type - already relayed by router
295
+ break;
296
+ }
297
+ }
298
+
299
+ private handleAnnounce(packet: BitchatPacket, fromLink: BLELink | null): void {
300
+ const peerID = packet.senderID;
301
+
302
+ // Decode TLV announcement payload
303
+ const announcement = decodeAnnouncement(packet.payload);
304
+ const nickname = announcement?.nickname ?? 'anon';
305
+
306
+ // Verify signature if present
307
+ if (packet.signature && announcement?.signingPublicKey) {
308
+ const dataToSign = encodeForSigning(packet);
309
+ const isValid = verify(dataToSign, packet.signature, announcement.signingPublicKey);
310
+ if (!isValid) {
311
+ console.warn('[Announce] Invalid signature from', nickname, peerID.toHex().slice(0, 8));
312
+ return; // Reject invalid announces
313
+ }
314
+ }
315
+
316
+ // Check if this is a NEW peer or an existing one
317
+ const existingPeer = this.sessions.getPeerInfo(peerID);
318
+ const isNewPeer = !existingPeer || !existingPeer.isConnected;
319
+
320
+ // Update peer info
321
+ this.sessions.updatePeerInfo(peerID, {
322
+ nickname,
323
+ isConnected: true,
324
+ lastSeen: new Date(),
325
+ // Store keys if available
326
+ noisePublicKey: announcement?.noisePublicKey,
327
+ signingPublicKey: announcement?.signingPublicKey,
328
+ });
329
+
330
+ // Associate peer ID with link
331
+ if (fromLink) {
332
+ fromLink.setPeerID(peerID);
333
+ }
334
+
335
+ const peer = this.sessions.getPeerInfo(peerID);
336
+ if (peer) {
337
+ // Only emit 'peer:connected' for NEW peers, not repeat announces
338
+ if (isNewPeer) {
339
+ this.emit('peer:connected', peer);
340
+ } else {
341
+ // Existing peer sent another announce - just update internally
342
+ this.emit('peer:updated', peer);
343
+ }
344
+ }
345
+ }
346
+
347
+ private handlePublicMessage(packet: BitchatPacket): void {
348
+ const content = new TextDecoder().decode(packet.payload);
349
+ const peer = this.sessions.getPeerInfo(packet.senderID);
350
+
351
+ const message: ChatMessage = {
352
+ id: makeDeduplicationID(packet),
353
+ sender: packet.senderID,
354
+ senderNickname: peer?.nickname ?? 'anon',
355
+ content,
356
+ timestamp: new Date(Number(packet.timestamp)),
357
+ isPrivate: false,
358
+ deliveryStatus: { type: 'delivered', to: this.peerID, at: new Date() },
359
+ };
360
+
361
+ this.emit('message', message);
362
+ }
363
+
364
+ private handleLeave(packet: BitchatPacket): void {
365
+ const peerID = packet.senderID;
366
+
367
+ this.sessions.updatePeerInfo(peerID, {
368
+ isConnected: false,
369
+ });
370
+
371
+ this.emit('peer:disconnected', peerID);
372
+ }
373
+
374
+ private async handleHandshake(packet: BitchatPacket): Promise<void> {
375
+ const peerID = packet.senderID;
376
+ const response = this.sessions.handleHandshakeMessage(peerID, packet.payload);
377
+
378
+ if (response) {
379
+ await this.sendHandshakePacket(peerID, response);
380
+ }
381
+ }
382
+
383
+ private handleEncrypted(packet: BitchatPacket): void {
384
+ const peerID = packet.senderID;
385
+
386
+ try {
387
+ const decrypted = this.sessions.decrypt(peerID, packet.payload);
388
+
389
+ // First byte is payload type
390
+ const payloadType = decrypted[0] as NoisePayloadType;
391
+ const payloadData = decrypted.subarray(1);
392
+
393
+ switch (payloadType) {
394
+ case NoisePayloadType.PRIVATE_MESSAGE:
395
+ this.handlePrivateMessage(peerID, payloadData, packet.timestamp);
396
+ break;
397
+
398
+ case NoisePayloadType.READ_RECEIPT:
399
+ // TODO: Handle read receipt
400
+ break;
401
+
402
+ case NoisePayloadType.DELIVERED:
403
+ // TODO: Handle delivery confirmation
404
+ break;
405
+
406
+ default:
407
+ // Unknown payload type
408
+ break;
409
+ }
410
+ } catch (error) {
411
+ this.emit('error', error as Error, 'decrypt');
412
+ }
413
+ }
414
+
415
+ private handlePrivateMessage(senderID: PeerID, payload: Uint8Array, timestamp: bigint): void {
416
+ const content = new TextDecoder().decode(payload);
417
+ const peer = this.sessions.getPeerInfo(senderID);
418
+
419
+ const message: ChatMessage = {
420
+ id: `${senderID.toHex()}-${timestamp}-private`,
421
+ sender: senderID,
422
+ senderNickname: peer?.nickname ?? 'anon',
423
+ content,
424
+ timestamp: new Date(Number(timestamp)),
425
+ isPrivate: true,
426
+ deliveryStatus: { type: 'delivered', to: this.peerID, at: new Date() },
427
+ };
428
+
429
+ this.emit('message', message);
430
+ }
431
+
432
+ private async sendAnnounce(): Promise<void> {
433
+ // Encode announcement with TLV format
434
+ const payload = encodeAnnouncement({
435
+ nickname: this.nickname,
436
+ noisePublicKey: this.sessions.staticKeyPair.publicKey,
437
+ signingPublicKey: this.sessions.signingKeyPair.publicKey,
438
+ directNeighbors: this.router.getConnectedPeerIDs().map((p) => p.toBytes()),
439
+ });
440
+
441
+ const packet: BitchatPacket = {
442
+ version: 1,
443
+ type: MessageType.ANNOUNCE,
444
+ ttl: DEFAULT_TTL,
445
+ timestamp: BigInt(Date.now()),
446
+ senderID: this.peerID,
447
+ payload,
448
+ isRSR: false,
449
+ };
450
+
451
+ // Sign the announce packet
452
+ const dataToSign = encodeForSigning(packet);
453
+ const signature = sign(dataToSign, this.sessions.signingKeyPair.secretKey);
454
+ packet.signature = signature;
455
+
456
+ await this.router.sendPacket(packet);
457
+ const encoded = encode(packet);
458
+ await this.transport.broadcast(encoded);
459
+ }
460
+
461
+ private async sendLeave(): Promise<void> {
462
+ const packet: BitchatPacket = {
463
+ version: 1,
464
+ type: MessageType.LEAVE,
465
+ ttl: DEFAULT_TTL,
466
+ timestamp: BigInt(Date.now()),
467
+ senderID: this.peerID,
468
+ payload: new Uint8Array(0),
469
+ isRSR: false,
470
+ };
471
+
472
+ await this.router.sendPacket(packet);
473
+ }
474
+
475
+ private async sendHandshakePacket(toPeerID: PeerID, message: Uint8Array): Promise<void> {
476
+ const packet: BitchatPacket = {
477
+ version: 1,
478
+ type: MessageType.NOISE_HANDSHAKE,
479
+ ttl: DEFAULT_TTL,
480
+ timestamp: BigInt(Date.now()),
481
+ senderID: this.peerID,
482
+ recipientID: toPeerID,
483
+ payload: message,
484
+ isRSR: false,
485
+ };
486
+
487
+ await this.router.sendToPeer(packet, toPeerID);
488
+ }
489
+
490
+ private buildPrivateMessagePayload(content: string, _messageID: string): Uint8Array {
491
+ const contentBytes = new TextEncoder().encode(content);
492
+ const payload = new Uint8Array(1 + contentBytes.length);
493
+ payload[0] = NoisePayloadType.PRIVATE_MESSAGE;
494
+ payload.set(contentBytes, 1);
495
+ return payload;
496
+ }
497
+
498
+ private async sendEncryptedMessage(
499
+ toPeerID: PeerID,
500
+ content: string,
501
+ messageID: string
502
+ ): Promise<void> {
503
+ const payload = this.buildPrivateMessagePayload(content, messageID);
504
+ const encrypted = this.sessions.encrypt(toPeerID, payload);
505
+
506
+ const packet: BitchatPacket = {
507
+ version: 1,
508
+ type: MessageType.NOISE_ENCRYPTED,
509
+ ttl: DEFAULT_TTL,
510
+ timestamp: BigInt(Date.now()),
511
+ senderID: this.peerID,
512
+ recipientID: toPeerID,
513
+ payload: encrypted,
514
+ isRSR: false,
515
+ };
516
+
517
+ await this.router.sendToPeer(packet, toPeerID);
518
+ }
519
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Crypto module exports
3
+ */
4
+
5
+ export {
6
+ CipherState,
7
+ generateKeyPair,
8
+ HandshakeState,
9
+ type NoiseKeyPair,
10
+ type NoiseRole,
11
+ NoiseSession,
12
+ } from './noise.js';
13
+
14
+ export {
15
+ fingerprint,
16
+ formatFingerprint,
17
+ generateSigningKeyPair,
18
+ type SigningKeyPair,
19
+ sha256,
20
+ sign,
21
+ verify,
22
+ } from './signing.js';