@meshwhisper/sdk 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 (163) hide show
  1. package/README.md +138 -0
  2. package/dist/browser/index.d.ts +4 -0
  3. package/dist/browser/index.d.ts.map +1 -0
  4. package/dist/browser/index.js +19 -0
  5. package/dist/browser/index.js.map +1 -0
  6. package/dist/chaff/index.d.ts +91 -0
  7. package/dist/chaff/index.d.ts.map +1 -0
  8. package/dist/chaff/index.js +268 -0
  9. package/dist/chaff/index.js.map +1 -0
  10. package/dist/cluster/index.d.ts +159 -0
  11. package/dist/cluster/index.d.ts.map +1 -0
  12. package/dist/cluster/index.js +393 -0
  13. package/dist/cluster/index.js.map +1 -0
  14. package/dist/compliance/index.d.ts +129 -0
  15. package/dist/compliance/index.d.ts.map +1 -0
  16. package/dist/compliance/index.js +315 -0
  17. package/dist/compliance/index.js.map +1 -0
  18. package/dist/crypto/index.d.ts +65 -0
  19. package/dist/crypto/index.d.ts.map +1 -0
  20. package/dist/crypto/index.js +146 -0
  21. package/dist/crypto/index.js.map +1 -0
  22. package/dist/group/index.d.ts +155 -0
  23. package/dist/group/index.d.ts.map +1 -0
  24. package/dist/group/index.js +560 -0
  25. package/dist/group/index.js.map +1 -0
  26. package/dist/index.d.ts +7 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +11 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/namespace/index.d.ts +155 -0
  31. package/dist/namespace/index.d.ts.map +1 -0
  32. package/dist/namespace/index.js +278 -0
  33. package/dist/namespace/index.js.map +1 -0
  34. package/dist/node/index.d.ts +4 -0
  35. package/dist/node/index.d.ts.map +1 -0
  36. package/dist/node/index.js +19 -0
  37. package/dist/node/index.js.map +1 -0
  38. package/dist/packet/index.d.ts +63 -0
  39. package/dist/packet/index.d.ts.map +1 -0
  40. package/dist/packet/index.js +244 -0
  41. package/dist/packet/index.js.map +1 -0
  42. package/dist/permissions/index.d.ts +107 -0
  43. package/dist/permissions/index.d.ts.map +1 -0
  44. package/dist/permissions/index.js +282 -0
  45. package/dist/permissions/index.js.map +1 -0
  46. package/dist/persistence/idb-storage.d.ts +27 -0
  47. package/dist/persistence/idb-storage.d.ts.map +1 -0
  48. package/dist/persistence/idb-storage.js +75 -0
  49. package/dist/persistence/idb-storage.js.map +1 -0
  50. package/dist/persistence/index.d.ts +4 -0
  51. package/dist/persistence/index.d.ts.map +1 -0
  52. package/dist/persistence/index.js +3 -0
  53. package/dist/persistence/index.js.map +1 -0
  54. package/dist/persistence/node-storage.d.ts +33 -0
  55. package/dist/persistence/node-storage.d.ts.map +1 -0
  56. package/dist/persistence/node-storage.js +90 -0
  57. package/dist/persistence/node-storage.js.map +1 -0
  58. package/dist/persistence/serialization.d.ts +4 -0
  59. package/dist/persistence/serialization.d.ts.map +1 -0
  60. package/dist/persistence/serialization.js +49 -0
  61. package/dist/persistence/serialization.js.map +1 -0
  62. package/dist/persistence/types.d.ts +29 -0
  63. package/dist/persistence/types.d.ts.map +1 -0
  64. package/dist/persistence/types.js +5 -0
  65. package/dist/persistence/types.js.map +1 -0
  66. package/dist/ratchet/index.d.ts +80 -0
  67. package/dist/ratchet/index.d.ts.map +1 -0
  68. package/dist/ratchet/index.js +259 -0
  69. package/dist/ratchet/index.js.map +1 -0
  70. package/dist/reciprocity/index.d.ts +109 -0
  71. package/dist/reciprocity/index.d.ts.map +1 -0
  72. package/dist/reciprocity/index.js +311 -0
  73. package/dist/reciprocity/index.js.map +1 -0
  74. package/dist/relay/index.d.ts +87 -0
  75. package/dist/relay/index.d.ts.map +1 -0
  76. package/dist/relay/index.js +286 -0
  77. package/dist/relay/index.js.map +1 -0
  78. package/dist/routing/index.d.ts +136 -0
  79. package/dist/routing/index.d.ts.map +1 -0
  80. package/dist/routing/index.js +478 -0
  81. package/dist/routing/index.js.map +1 -0
  82. package/dist/sdk/index.d.ts +322 -0
  83. package/dist/sdk/index.d.ts.map +1 -0
  84. package/dist/sdk/index.js +1530 -0
  85. package/dist/sdk/index.js.map +1 -0
  86. package/dist/sybil/index.d.ts +123 -0
  87. package/dist/sybil/index.d.ts.map +1 -0
  88. package/dist/sybil/index.js +491 -0
  89. package/dist/sybil/index.js.map +1 -0
  90. package/dist/transport/browser/index.d.ts +34 -0
  91. package/dist/transport/browser/index.d.ts.map +1 -0
  92. package/dist/transport/browser/index.js +176 -0
  93. package/dist/transport/browser/index.js.map +1 -0
  94. package/dist/transport/local/index.d.ts +57 -0
  95. package/dist/transport/local/index.d.ts.map +1 -0
  96. package/dist/transport/local/index.js +442 -0
  97. package/dist/transport/local/index.js.map +1 -0
  98. package/dist/transport/negotiator/index.d.ts +79 -0
  99. package/dist/transport/negotiator/index.d.ts.map +1 -0
  100. package/dist/transport/negotiator/index.js +289 -0
  101. package/dist/transport/negotiator/index.js.map +1 -0
  102. package/dist/transport/node/index.d.ts +56 -0
  103. package/dist/transport/node/index.d.ts.map +1 -0
  104. package/dist/transport/node/index.js +209 -0
  105. package/dist/transport/node/index.js.map +1 -0
  106. package/dist/transport/noop/index.d.ts +11 -0
  107. package/dist/transport/noop/index.d.ts.map +1 -0
  108. package/dist/transport/noop/index.js +20 -0
  109. package/dist/transport/noop/index.js.map +1 -0
  110. package/dist/transport/p2p/index.d.ts +109 -0
  111. package/dist/transport/p2p/index.d.ts.map +1 -0
  112. package/dist/transport/p2p/index.js +237 -0
  113. package/dist/transport/p2p/index.js.map +1 -0
  114. package/dist/transport/websocket/index.d.ts +89 -0
  115. package/dist/transport/websocket/index.d.ts.map +1 -0
  116. package/dist/transport/websocket/index.js +498 -0
  117. package/dist/transport/websocket/index.js.map +1 -0
  118. package/dist/transport/websocket/serialize.d.ts +5 -0
  119. package/dist/transport/websocket/serialize.d.ts.map +1 -0
  120. package/dist/transport/websocket/serialize.js +55 -0
  121. package/dist/transport/websocket/serialize.js.map +1 -0
  122. package/dist/types.d.ts +215 -0
  123. package/dist/types.d.ts.map +1 -0
  124. package/dist/types.js +15 -0
  125. package/dist/types.js.map +1 -0
  126. package/dist/x3dh/index.d.ts +120 -0
  127. package/dist/x3dh/index.d.ts.map +1 -0
  128. package/dist/x3dh/index.js +290 -0
  129. package/dist/x3dh/index.js.map +1 -0
  130. package/package.json +59 -0
  131. package/src/browser/index.ts +19 -0
  132. package/src/chaff/index.ts +340 -0
  133. package/src/cluster/index.ts +482 -0
  134. package/src/compliance/index.ts +407 -0
  135. package/src/crypto/index.ts +193 -0
  136. package/src/group/index.ts +719 -0
  137. package/src/index.ts +87 -0
  138. package/src/lz4js.d.ts +58 -0
  139. package/src/namespace/index.ts +336 -0
  140. package/src/node/index.ts +19 -0
  141. package/src/packet/index.ts +326 -0
  142. package/src/permissions/index.ts +405 -0
  143. package/src/persistence/idb-storage.ts +83 -0
  144. package/src/persistence/index.ts +3 -0
  145. package/src/persistence/node-storage.ts +96 -0
  146. package/src/persistence/serialization.ts +75 -0
  147. package/src/persistence/types.ts +33 -0
  148. package/src/ratchet/index.ts +363 -0
  149. package/src/reciprocity/index.ts +371 -0
  150. package/src/relay/index.ts +382 -0
  151. package/src/routing/index.ts +577 -0
  152. package/src/sdk/index.ts +1994 -0
  153. package/src/sybil/index.ts +661 -0
  154. package/src/transport/browser/index.ts +201 -0
  155. package/src/transport/local/index.ts +540 -0
  156. package/src/transport/negotiator/index.ts +397 -0
  157. package/src/transport/node/index.ts +234 -0
  158. package/src/transport/noop/index.ts +22 -0
  159. package/src/transport/p2p/index.ts +345 -0
  160. package/src/transport/websocket/index.ts +660 -0
  161. package/src/transport/websocket/serialize.ts +68 -0
  162. package/src/types.ts +275 -0
  163. package/src/x3dh/index.ts +388 -0
@@ -0,0 +1,340 @@
1
+ // ============================================================
2
+ // MeshWhisper SDK — Chaff Generator & Traffic Analysis Defense
3
+ // Emits a constant stream of encrypted chaff packets that are
4
+ // byte-for-byte indistinguishable from real encrypted messages,
5
+ // defeating traffic analysis as described in PRD section 8.4.
6
+ // ============================================================
7
+
8
+ import { Packet, PacketFlags, ChaffRate, RelayWillingness } from '../types.js';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Constants
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /** Destination hash length in bytes (truncated BLAKE3). */
15
+ const DEST_HASH_LENGTH = 8;
16
+
17
+ /** Sender ephemeral ID length in bytes. */
18
+ const SENDER_EPHEMERAL_ID_LENGTH = 16;
19
+
20
+ /** Packet version used across the SDK. */
21
+ const PACKET_VERSION = 1;
22
+
23
+ /** Default minimum chaff payload size in bytes. */
24
+ const DEFAULT_MIN_PACKET_SIZE = 32;
25
+
26
+ /** Default maximum chaff payload size in bytes. */
27
+ const DEFAULT_MAX_PACKET_SIZE = 256;
28
+
29
+ /** Default burst variance (0.0-1.0). */
30
+ const DEFAULT_BURST_VARIANCE = 0.3;
31
+
32
+ /** Base emission intervals per rate, in milliseconds. */
33
+ const RATE_INTERVALS: Record<ChaffRate, number> = {
34
+ low: 60_000, // ~1 packet / 60 s → ~1 KB/h
35
+ normal: 30_000, // ~1 packet / 30 s → ~2 KB/h
36
+ high: 10_000, // ~1 packet / 10 s → ~6 KB/h
37
+ };
38
+
39
+ /** Maps relay willingness to the appropriate chaff rate. */
40
+ const WILLINGNESS_TO_RATE: Record<RelayWillingness, ChaffRate | null> = {
41
+ eager: 'high',
42
+ willing: 'normal',
43
+ reluctant: 'low',
44
+ unavailable: null,
45
+ };
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Options
49
+ // ---------------------------------------------------------------------------
50
+
51
+ export interface ChaffOptions {
52
+ /** Emission rate preset. */
53
+ rate: ChaffRate;
54
+ /** Maximum chaff payload size in bytes. Defaults to 256. */
55
+ maxPacketSize?: number;
56
+ /** Minimum chaff payload size in bytes. Defaults to 32. */
57
+ minPacketSize?: number;
58
+ /** Randomness applied to inter-packet timing (0.0-1.0). Defaults to 0.3. */
59
+ burstVariance?: number;
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Statistics
64
+ // ---------------------------------------------------------------------------
65
+
66
+ export interface ChaffStats {
67
+ packetsGenerated: number;
68
+ bytesGenerated: number;
69
+ /** Elapsed time since start() was first called, in milliseconds. */
70
+ uptime: number;
71
+ currentRate: ChaffRate;
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Crypto helpers (isomorphic: works in Node.js and browsers)
76
+ // ---------------------------------------------------------------------------
77
+
78
+ /**
79
+ * Fill a Uint8Array with cryptographically secure random bytes.
80
+ * Uses globalThis.crypto (Web Crypto) when available, otherwise
81
+ * falls back to Node.js crypto module.
82
+ */
83
+ function secureRandomBytes(length: number): Uint8Array {
84
+ const buf = new Uint8Array(length);
85
+
86
+ if (typeof globalThis !== 'undefined' && globalThis.crypto?.getRandomValues) {
87
+ globalThis.crypto.getRandomValues(buf);
88
+ } else {
89
+ // Node.js environments where globalThis.crypto may not exist (older Node)
90
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
91
+ const nodeCrypto = require('node:crypto') as typeof import('node:crypto');
92
+ nodeCrypto.randomFillSync(buf);
93
+ }
94
+
95
+ return buf;
96
+ }
97
+
98
+ /**
99
+ * Return a uniformly distributed random integer in [min, max] (inclusive).
100
+ */
101
+ function randomInt(min: number, max: number): number {
102
+ const range = max - min + 1;
103
+ const bytes = secureRandomBytes(4);
104
+ const value = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
105
+ return min + (value % range);
106
+ }
107
+
108
+ /**
109
+ * Return a random floating-point value in [0, 1).
110
+ */
111
+ function randomFloat(): number {
112
+ const bytes = secureRandomBytes(4);
113
+ const value = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
114
+ return value / 0x1_0000_0000;
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // ChaffGenerator
119
+ // ---------------------------------------------------------------------------
120
+
121
+ export class ChaffGenerator {
122
+ private readonly minPacketSize: number;
123
+ private readonly maxPacketSize: number;
124
+ private readonly burstVariance: number;
125
+
126
+ private rate: ChaffRate;
127
+ private running = false;
128
+ private timer: ReturnType<typeof setTimeout> | null = null;
129
+ private startedAt: number | null = null;
130
+
131
+ private packetsGenerated = 0;
132
+ private bytesGenerated = 0;
133
+
134
+ private emitCallback: ((packet: Packet) => void) | null = null;
135
+
136
+ constructor(options?: ChaffOptions) {
137
+ this.rate = options?.rate ?? 'normal';
138
+ this.minPacketSize = options?.minPacketSize ?? DEFAULT_MIN_PACKET_SIZE;
139
+ this.maxPacketSize = options?.maxPacketSize ?? DEFAULT_MAX_PACKET_SIZE;
140
+ this.burstVariance = Math.max(0, Math.min(1, options?.burstVariance ?? DEFAULT_BURST_VARIANCE));
141
+ }
142
+
143
+ // -----------------------------------------------------------------------
144
+ // Packet generation
145
+ // -----------------------------------------------------------------------
146
+
147
+ /**
148
+ * Generate a single chaff packet.
149
+ *
150
+ * The packet is constructed to be byte-for-byte indistinguishable from
151
+ * a real encrypted message to any external observer: all variable-length
152
+ * fields are filled with cryptographically random data, the flags field
153
+ * is set to CHAFF (which the local node recognises but a relay treats
154
+ * identically to DATA), and the TTL is kept low so chaff doesn't
155
+ * propagate far.
156
+ */
157
+ generateChaffPacket(): Packet {
158
+ const payloadSize = randomInt(this.minPacketSize, this.maxPacketSize);
159
+ return this.buildChaffPacket(payloadSize);
160
+ }
161
+
162
+ // -----------------------------------------------------------------------
163
+ // Emission scheduling
164
+ // -----------------------------------------------------------------------
165
+
166
+ /** Begin emitting chaff on a jittered schedule. */
167
+ start(): void {
168
+ if (this.running) return;
169
+ this.running = true;
170
+ if (this.startedAt === null) {
171
+ this.startedAt = Date.now();
172
+ }
173
+ this.scheduleNext();
174
+ }
175
+
176
+ /** Stop chaff emission. */
177
+ stop(): void {
178
+ this.running = false;
179
+ if (this.timer !== null) {
180
+ clearTimeout(this.timer);
181
+ this.timer = null;
182
+ }
183
+ }
184
+
185
+ /** Whether the generator is currently emitting. */
186
+ isRunning(): boolean {
187
+ return this.running;
188
+ }
189
+
190
+ // -----------------------------------------------------------------------
191
+ // Rate control
192
+ // -----------------------------------------------------------------------
193
+
194
+ /** Change the emission rate. Takes effect on the next scheduling cycle. */
195
+ setRate(rate: ChaffRate): void {
196
+ this.rate = rate;
197
+ }
198
+
199
+ /**
200
+ * Automatically adjust the chaff rate based on the device's relay
201
+ * willingness setting.
202
+ *
203
+ * - eager → high
204
+ * - willing → normal
205
+ * - reluctant → low
206
+ * - unavailable → stop entirely
207
+ */
208
+ adaptToRelayWillingness(willingness: RelayWillingness): void {
209
+ const mapped = WILLINGNESS_TO_RATE[willingness];
210
+ if (mapped === null) {
211
+ this.stop();
212
+ return;
213
+ }
214
+ this.rate = mapped;
215
+ // If already running, let the current timer expire naturally — the
216
+ // new rate will be picked up on the next scheduling cycle.
217
+ }
218
+
219
+ // -----------------------------------------------------------------------
220
+ // Real message camouflage
221
+ // -----------------------------------------------------------------------
222
+
223
+ /**
224
+ * Surround a real packet with 0-2 chaff packets whose payload sizes
225
+ * approximate the real packet's size, making it harder for an observer
226
+ * to distinguish the real message in a burst.
227
+ *
228
+ * Returns an array of 1-3 packets with the real packet placed at a
229
+ * random position.
230
+ */
231
+ camouflageRealMessage(realPacket: Packet): Packet[] {
232
+ const chaffCount = randomInt(0, 2);
233
+ if (chaffCount === 0) return [realPacket];
234
+
235
+ const realSize = realPacket.encryptedPayload.length;
236
+
237
+ // Build chaff with sizes close (±20%) to the real payload.
238
+ const sizeFloor = Math.max(1, Math.round(realSize * 0.8));
239
+ const sizeCeil = Math.round(realSize * 1.2);
240
+ const chaffPackets: Packet[] = [];
241
+ for (let i = 0; i < chaffCount; i++) {
242
+ const size = randomInt(sizeFloor, sizeCeil);
243
+ chaffPackets.push(this.buildChaffPacket(size));
244
+ }
245
+
246
+ // Insert the real packet at a random position in the burst.
247
+ const insertionIndex = randomInt(0, chaffPackets.length);
248
+ chaffPackets.splice(insertionIndex, 0, realPacket);
249
+
250
+ return chaffPackets;
251
+ }
252
+
253
+ // -----------------------------------------------------------------------
254
+ // Callback registration
255
+ // -----------------------------------------------------------------------
256
+
257
+ /**
258
+ * Register a callback that receives each generated chaff packet.
259
+ * The transport layer will call this to enqueue chaff for sending.
260
+ */
261
+ onChaffGenerated(callback: (packet: Packet) => void): void {
262
+ this.emitCallback = callback;
263
+ }
264
+
265
+ // -----------------------------------------------------------------------
266
+ // Statistics
267
+ // -----------------------------------------------------------------------
268
+
269
+ /** Return runtime statistics for monitoring / diagnostics. */
270
+ getStats(): ChaffStats {
271
+ return {
272
+ packetsGenerated: this.packetsGenerated,
273
+ bytesGenerated: this.bytesGenerated,
274
+ uptime: this.startedAt !== null ? Date.now() - this.startedAt : 0,
275
+ currentRate: this.rate,
276
+ };
277
+ }
278
+
279
+ // -----------------------------------------------------------------------
280
+ // Private helpers
281
+ // -----------------------------------------------------------------------
282
+
283
+ /**
284
+ * Build a chaff packet with the given payload size.
285
+ */
286
+ private buildChaffPacket(payloadSize: number): Packet {
287
+ const encryptedPayload = secureRandomBytes(payloadSize);
288
+ const packet: Packet = {
289
+ version: PACKET_VERSION,
290
+ flags: PacketFlags.CHAFF,
291
+ destHash: secureRandomBytes(DEST_HASH_LENGTH),
292
+ senderEphemeralId: secureRandomBytes(SENDER_EPHEMERAL_ID_LENGTH),
293
+ ttl: randomInt(1, 3),
294
+ payloadLength: payloadSize,
295
+ encryptedPayload,
296
+ };
297
+
298
+ this.packetsGenerated++;
299
+ this.bytesGenerated += payloadSize;
300
+
301
+ return packet;
302
+ }
303
+
304
+ /**
305
+ * Compute the next emission delay in milliseconds, adding jitter
306
+ * proportional to burstVariance so inter-packet timing is not
307
+ * predictable.
308
+ */
309
+ private computeNextDelay(): number {
310
+ const base = RATE_INTERVALS[this.rate];
311
+ const jitterRange = base * this.burstVariance;
312
+ // Uniform jitter in [-jitterRange, +jitterRange]
313
+ const jitter = (randomFloat() * 2 - 1) * jitterRange;
314
+ return Math.max(1, Math.round(base + jitter));
315
+ }
316
+
317
+ /**
318
+ * Schedule the next chaff emission.
319
+ */
320
+ private scheduleNext(): void {
321
+ if (!this.running) return;
322
+
323
+ const delay = this.computeNextDelay();
324
+ this.timer = setTimeout(() => {
325
+ if (!this.running) return;
326
+
327
+ const packet = this.generateChaffPacket();
328
+ if (this.emitCallback) {
329
+ this.emitCallback(packet);
330
+ }
331
+
332
+ this.scheduleNext();
333
+ }, delay);
334
+
335
+ // Prevent the timer from keeping the process alive in Node.js.
336
+ if (typeof this.timer === 'object' && 'unref' in this.timer) {
337
+ (this.timer as NodeJS.Timeout).unref();
338
+ }
339
+ }
340
+ }