@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.
- package/README.md +138 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +19 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/chaff/index.d.ts +91 -0
- package/dist/chaff/index.d.ts.map +1 -0
- package/dist/chaff/index.js +268 -0
- package/dist/chaff/index.js.map +1 -0
- package/dist/cluster/index.d.ts +159 -0
- package/dist/cluster/index.d.ts.map +1 -0
- package/dist/cluster/index.js +393 -0
- package/dist/cluster/index.js.map +1 -0
- package/dist/compliance/index.d.ts +129 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +315 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/crypto/index.d.ts +65 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +146 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/group/index.d.ts +155 -0
- package/dist/group/index.d.ts.map +1 -0
- package/dist/group/index.js +560 -0
- package/dist/group/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/namespace/index.d.ts +155 -0
- package/dist/namespace/index.d.ts.map +1 -0
- package/dist/namespace/index.js +278 -0
- package/dist/namespace/index.js.map +1 -0
- package/dist/node/index.d.ts +4 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +19 -0
- package/dist/node/index.js.map +1 -0
- package/dist/packet/index.d.ts +63 -0
- package/dist/packet/index.d.ts.map +1 -0
- package/dist/packet/index.js +244 -0
- package/dist/packet/index.js.map +1 -0
- package/dist/permissions/index.d.ts +107 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +282 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/persistence/idb-storage.d.ts +27 -0
- package/dist/persistence/idb-storage.d.ts.map +1 -0
- package/dist/persistence/idb-storage.js +75 -0
- package/dist/persistence/idb-storage.js.map +1 -0
- package/dist/persistence/index.d.ts +4 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +3 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/node-storage.d.ts +33 -0
- package/dist/persistence/node-storage.d.ts.map +1 -0
- package/dist/persistence/node-storage.js +90 -0
- package/dist/persistence/node-storage.js.map +1 -0
- package/dist/persistence/serialization.d.ts +4 -0
- package/dist/persistence/serialization.d.ts.map +1 -0
- package/dist/persistence/serialization.js +49 -0
- package/dist/persistence/serialization.js.map +1 -0
- package/dist/persistence/types.d.ts +29 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +5 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/ratchet/index.d.ts +80 -0
- package/dist/ratchet/index.d.ts.map +1 -0
- package/dist/ratchet/index.js +259 -0
- package/dist/ratchet/index.js.map +1 -0
- package/dist/reciprocity/index.d.ts +109 -0
- package/dist/reciprocity/index.d.ts.map +1 -0
- package/dist/reciprocity/index.js +311 -0
- package/dist/reciprocity/index.js.map +1 -0
- package/dist/relay/index.d.ts +87 -0
- package/dist/relay/index.d.ts.map +1 -0
- package/dist/relay/index.js +286 -0
- package/dist/relay/index.js.map +1 -0
- package/dist/routing/index.d.ts +136 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +478 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sdk/index.d.ts +322 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +1530 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sybil/index.d.ts +123 -0
- package/dist/sybil/index.d.ts.map +1 -0
- package/dist/sybil/index.js +491 -0
- package/dist/sybil/index.js.map +1 -0
- package/dist/transport/browser/index.d.ts +34 -0
- package/dist/transport/browser/index.d.ts.map +1 -0
- package/dist/transport/browser/index.js +176 -0
- package/dist/transport/browser/index.js.map +1 -0
- package/dist/transport/local/index.d.ts +57 -0
- package/dist/transport/local/index.d.ts.map +1 -0
- package/dist/transport/local/index.js +442 -0
- package/dist/transport/local/index.js.map +1 -0
- package/dist/transport/negotiator/index.d.ts +79 -0
- package/dist/transport/negotiator/index.d.ts.map +1 -0
- package/dist/transport/negotiator/index.js +289 -0
- package/dist/transport/negotiator/index.js.map +1 -0
- package/dist/transport/node/index.d.ts +56 -0
- package/dist/transport/node/index.d.ts.map +1 -0
- package/dist/transport/node/index.js +209 -0
- package/dist/transport/node/index.js.map +1 -0
- package/dist/transport/noop/index.d.ts +11 -0
- package/dist/transport/noop/index.d.ts.map +1 -0
- package/dist/transport/noop/index.js +20 -0
- package/dist/transport/noop/index.js.map +1 -0
- package/dist/transport/p2p/index.d.ts +109 -0
- package/dist/transport/p2p/index.d.ts.map +1 -0
- package/dist/transport/p2p/index.js +237 -0
- package/dist/transport/p2p/index.js.map +1 -0
- package/dist/transport/websocket/index.d.ts +89 -0
- package/dist/transport/websocket/index.d.ts.map +1 -0
- package/dist/transport/websocket/index.js +498 -0
- package/dist/transport/websocket/index.js.map +1 -0
- package/dist/transport/websocket/serialize.d.ts +5 -0
- package/dist/transport/websocket/serialize.d.ts.map +1 -0
- package/dist/transport/websocket/serialize.js +55 -0
- package/dist/transport/websocket/serialize.js.map +1 -0
- package/dist/types.d.ts +215 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/x3dh/index.d.ts +120 -0
- package/dist/x3dh/index.d.ts.map +1 -0
- package/dist/x3dh/index.js +290 -0
- package/dist/x3dh/index.js.map +1 -0
- package/package.json +59 -0
- package/src/browser/index.ts +19 -0
- package/src/chaff/index.ts +340 -0
- package/src/cluster/index.ts +482 -0
- package/src/compliance/index.ts +407 -0
- package/src/crypto/index.ts +193 -0
- package/src/group/index.ts +719 -0
- package/src/index.ts +87 -0
- package/src/lz4js.d.ts +58 -0
- package/src/namespace/index.ts +336 -0
- package/src/node/index.ts +19 -0
- package/src/packet/index.ts +326 -0
- package/src/permissions/index.ts +405 -0
- package/src/persistence/idb-storage.ts +83 -0
- package/src/persistence/index.ts +3 -0
- package/src/persistence/node-storage.ts +96 -0
- package/src/persistence/serialization.ts +75 -0
- package/src/persistence/types.ts +33 -0
- package/src/ratchet/index.ts +363 -0
- package/src/reciprocity/index.ts +371 -0
- package/src/relay/index.ts +382 -0
- package/src/routing/index.ts +577 -0
- package/src/sdk/index.ts +1994 -0
- package/src/sybil/index.ts +661 -0
- package/src/transport/browser/index.ts +201 -0
- package/src/transport/local/index.ts +540 -0
- package/src/transport/negotiator/index.ts +397 -0
- package/src/transport/node/index.ts +234 -0
- package/src/transport/noop/index.ts +22 -0
- package/src/transport/p2p/index.ts +345 -0
- package/src/transport/websocket/index.ts +660 -0
- package/src/transport/websocket/serialize.ts +68 -0
- package/src/types.ts +275 -0
- package/src/x3dh/index.ts +388 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# MeshWhisper
|
|
2
|
+
|
|
3
|
+
Serverless P2P end-to-end encrypted messaging SDK. Drop messaging into any app — PWA, React Native, Node.js — without building or operating a message server that can read your users' messages.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Alice's device Node (relay) Bob's device
|
|
9
|
+
────────────── ──────────── ────────────
|
|
10
|
+
MeshWhisper.init() ──ws──► stores blob ◄──ws── MeshWhisper.init()
|
|
11
|
+
send(bobId, msg) ──────► routes by destHash ──────► onMessage(msg)
|
|
12
|
+
(cannot decrypt)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- **X3DH** key exchange on first contact — no prior communication needed
|
|
16
|
+
- **Double Ratchet** per message — forward secrecy, break-in recovery
|
|
17
|
+
- **Destination hash routing** — the Node knows *where* to route, never *who* sent what
|
|
18
|
+
- **Store and forward** — messages queue on the Node while the recipient is offline
|
|
19
|
+
- **Push wake** — Node triggers a silent APNs/FCM/Web Push when a message arrives for an offline device
|
|
20
|
+
|
|
21
|
+
The Node relay and push service are the only infrastructure you run. They are intentionally dumb: they relay encrypted bytes and ring a doorbell. They cannot read messages.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Repository layout
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
@meshwhisper/sdk — client SDK (browser + Node.js)
|
|
29
|
+
src/sdk/index.ts — public API surface
|
|
30
|
+
src/transport/browser/ — BrowserTransport (native WebSocket)
|
|
31
|
+
src/transport/node/ — NodeTransport (ws package)
|
|
32
|
+
src/persistence/idb-* — IndexedDB storage backend
|
|
33
|
+
src/persistence/node-* — Filesystem storage backend
|
|
34
|
+
|
|
35
|
+
@meshwhisper/node (node/) — relay server (WebSocket + HTTP)
|
|
36
|
+
@meshwhisper/push-service — APNs / FCM / Web Push dispatcher
|
|
37
|
+
@meshwhisper/cli (cli/) — npx @meshwhisper/cli init
|
|
38
|
+
@meshwhisper/service-worker — PWA service worker helper
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick start — PWA
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { MeshWhisper } from '@meshwhisper/sdk';
|
|
47
|
+
|
|
48
|
+
// Auto-detects browser: uses IDBStorage + BrowserTransport
|
|
49
|
+
const mw = await MeshWhisper.init({
|
|
50
|
+
namespace: 'com.example.myapp',
|
|
51
|
+
node: 'wss://relay.myapp.com', // your self-hosted Node, or 'mesh'
|
|
52
|
+
onMessage: (message) => {
|
|
53
|
+
const text = new TextDecoder().decode(new Uint8Array(message.payload));
|
|
54
|
+
appendToChat(message.senderId, text);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const myId = mw.getLocalPeerId(); // share this with contacts
|
|
59
|
+
|
|
60
|
+
// First message to a new contact initiates X3DH automatically
|
|
61
|
+
await MeshWhisper.send(contactId, new TextEncoder().encode('Hello!'));
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Quick start — Node.js
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { MeshWhisper } from '@meshwhisper/sdk/node';
|
|
68
|
+
import { NodeStorage } from '@meshwhisper/sdk/node';
|
|
69
|
+
|
|
70
|
+
const mw = await MeshWhisper.init({
|
|
71
|
+
namespace: 'com.example.myapp',
|
|
72
|
+
node: 'wss://relay.myapp.com',
|
|
73
|
+
storage: new NodeStorage('./data'), // persists identity + sessions to disk
|
|
74
|
+
onMessage: (message) => {
|
|
75
|
+
const text = new TextDecoder().decode(new Uint8Array(message.payload));
|
|
76
|
+
console.log(`[${message.senderId}]: ${text}`);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Self-hosting in 5 minutes
|
|
84
|
+
|
|
85
|
+
See **[docs/self-hosting.md](docs/self-hosting.md)** for the full guide.
|
|
86
|
+
|
|
87
|
+
The short version — copy this `docker-compose.yml` and fill in the push credentials you need:
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
services:
|
|
91
|
+
node:
|
|
92
|
+
image: ghcr.io/meshwhisper/node:latest # or build from node/Dockerfile
|
|
93
|
+
ports: ["8080:8080"]
|
|
94
|
+
environment:
|
|
95
|
+
BASE_URL: "https://relay.myapp.com"
|
|
96
|
+
PUSH_WEBHOOK_URL: "http://push:4000/notify"
|
|
97
|
+
|
|
98
|
+
push:
|
|
99
|
+
image: ghcr.io/meshwhisper/push-service:latest
|
|
100
|
+
environment:
|
|
101
|
+
VAPID_PUBLIC_KEY: "..."
|
|
102
|
+
VAPID_PRIVATE_KEY: "..."
|
|
103
|
+
VAPID_SUBJECT: "mailto:ops@myapp.com"
|
|
104
|
+
# APNS_KEY_ID / APNS_TEAM_ID / APNS_KEY_PATH / APNS_BUNDLE_ID (iOS)
|
|
105
|
+
# FCM_SERVICE_ACCOUNT_PATH / FCM_PROJECT_ID (Android)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
docker compose up -d
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Scaffolding a new project
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx @meshwhisper/cli init
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Prompts for your bundle ID and node URL, then prints your `.env` block, SDK init snippet, and (optionally) a `docker-compose.yml`.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Full API reference
|
|
125
|
+
|
|
126
|
+
See **[docs/api.md](docs/api.md)**.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Security model
|
|
131
|
+
|
|
132
|
+
- The relay Node sees only encrypted ciphertext and truncated destination hashes. It cannot link a message to a sender identity.
|
|
133
|
+
- Destination hashes rotate every hour, limiting traffic-analysis correlation windows.
|
|
134
|
+
- The push service receives a token/subscription and a destination hash — no message content.
|
|
135
|
+
- Identity keys are generated on-device and never leave the device. The private key is stored in the configured `StorageBackend` only.
|
|
136
|
+
- Media blobs are encrypted locally before upload. The Node stores ciphertext; the decryption key is sent through the ratchet-encrypted message channel, never via the Node's HTTP API.
|
|
137
|
+
|
|
138
|
+
For a detailed threat model see the PRD at `meshwhisper-prd-v1.2.md`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAgBA,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// MeshWhisper SDK — Browser / PWA entry point
|
|
3
|
+
// @meshwhisper/sdk/browser
|
|
4
|
+
//
|
|
5
|
+
// Re-exports everything from the main SDK plus browser-specific
|
|
6
|
+
// storage and transport implementations.
|
|
7
|
+
//
|
|
8
|
+
// Usage:
|
|
9
|
+
// import { MeshWhisper, IDBStorage, BrowserTransport } from '@meshwhisper/sdk/browser';
|
|
10
|
+
//
|
|
11
|
+
// When you call MeshWhisper.init() in a browser environment, the SDK
|
|
12
|
+
// detects window/indexedDB and auto-selects IDBStorage + BrowserTransport.
|
|
13
|
+
// You only need to import from this path if you want to reference the
|
|
14
|
+
// classes explicitly (e.g. to construct IDBStorage with a custom namespace).
|
|
15
|
+
// ============================================================
|
|
16
|
+
export * from '../index.js';
|
|
17
|
+
export { IDBStorage } from '../persistence/idb-storage.js';
|
|
18
|
+
export { BrowserTransport } from '../transport/browser/index.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,8CAA8C;AAC9C,2BAA2B;AAC3B,EAAE;AACF,gEAAgE;AAChE,yCAAyC;AACzC,EAAE;AACF,SAAS;AACT,0FAA0F;AAC1F,EAAE;AACF,qEAAqE;AACrE,2EAA2E;AAC3E,sEAAsE;AACtE,6EAA6E;AAC7E,+DAA+D;AAE/D,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Packet, ChaffRate, RelayWillingness } from '../types.js';
|
|
2
|
+
export interface ChaffOptions {
|
|
3
|
+
/** Emission rate preset. */
|
|
4
|
+
rate: ChaffRate;
|
|
5
|
+
/** Maximum chaff payload size in bytes. Defaults to 256. */
|
|
6
|
+
maxPacketSize?: number;
|
|
7
|
+
/** Minimum chaff payload size in bytes. Defaults to 32. */
|
|
8
|
+
minPacketSize?: number;
|
|
9
|
+
/** Randomness applied to inter-packet timing (0.0-1.0). Defaults to 0.3. */
|
|
10
|
+
burstVariance?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ChaffStats {
|
|
13
|
+
packetsGenerated: number;
|
|
14
|
+
bytesGenerated: number;
|
|
15
|
+
/** Elapsed time since start() was first called, in milliseconds. */
|
|
16
|
+
uptime: number;
|
|
17
|
+
currentRate: ChaffRate;
|
|
18
|
+
}
|
|
19
|
+
export declare class ChaffGenerator {
|
|
20
|
+
private readonly minPacketSize;
|
|
21
|
+
private readonly maxPacketSize;
|
|
22
|
+
private readonly burstVariance;
|
|
23
|
+
private rate;
|
|
24
|
+
private running;
|
|
25
|
+
private timer;
|
|
26
|
+
private startedAt;
|
|
27
|
+
private packetsGenerated;
|
|
28
|
+
private bytesGenerated;
|
|
29
|
+
private emitCallback;
|
|
30
|
+
constructor(options?: ChaffOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Generate a single chaff packet.
|
|
33
|
+
*
|
|
34
|
+
* The packet is constructed to be byte-for-byte indistinguishable from
|
|
35
|
+
* a real encrypted message to any external observer: all variable-length
|
|
36
|
+
* fields are filled with cryptographically random data, the flags field
|
|
37
|
+
* is set to CHAFF (which the local node recognises but a relay treats
|
|
38
|
+
* identically to DATA), and the TTL is kept low so chaff doesn't
|
|
39
|
+
* propagate far.
|
|
40
|
+
*/
|
|
41
|
+
generateChaffPacket(): Packet;
|
|
42
|
+
/** Begin emitting chaff on a jittered schedule. */
|
|
43
|
+
start(): void;
|
|
44
|
+
/** Stop chaff emission. */
|
|
45
|
+
stop(): void;
|
|
46
|
+
/** Whether the generator is currently emitting. */
|
|
47
|
+
isRunning(): boolean;
|
|
48
|
+
/** Change the emission rate. Takes effect on the next scheduling cycle. */
|
|
49
|
+
setRate(rate: ChaffRate): void;
|
|
50
|
+
/**
|
|
51
|
+
* Automatically adjust the chaff rate based on the device's relay
|
|
52
|
+
* willingness setting.
|
|
53
|
+
*
|
|
54
|
+
* - eager → high
|
|
55
|
+
* - willing → normal
|
|
56
|
+
* - reluctant → low
|
|
57
|
+
* - unavailable → stop entirely
|
|
58
|
+
*/
|
|
59
|
+
adaptToRelayWillingness(willingness: RelayWillingness): void;
|
|
60
|
+
/**
|
|
61
|
+
* Surround a real packet with 0-2 chaff packets whose payload sizes
|
|
62
|
+
* approximate the real packet's size, making it harder for an observer
|
|
63
|
+
* to distinguish the real message in a burst.
|
|
64
|
+
*
|
|
65
|
+
* Returns an array of 1-3 packets with the real packet placed at a
|
|
66
|
+
* random position.
|
|
67
|
+
*/
|
|
68
|
+
camouflageRealMessage(realPacket: Packet): Packet[];
|
|
69
|
+
/**
|
|
70
|
+
* Register a callback that receives each generated chaff packet.
|
|
71
|
+
* The transport layer will call this to enqueue chaff for sending.
|
|
72
|
+
*/
|
|
73
|
+
onChaffGenerated(callback: (packet: Packet) => void): void;
|
|
74
|
+
/** Return runtime statistics for monitoring / diagnostics. */
|
|
75
|
+
getStats(): ChaffStats;
|
|
76
|
+
/**
|
|
77
|
+
* Build a chaff packet with the given payload size.
|
|
78
|
+
*/
|
|
79
|
+
private buildChaffPacket;
|
|
80
|
+
/**
|
|
81
|
+
* Compute the next emission delay in milliseconds, adding jitter
|
|
82
|
+
* proportional to burstVariance so inter-packet timing is not
|
|
83
|
+
* predictable.
|
|
84
|
+
*/
|
|
85
|
+
private computeNextDelay;
|
|
86
|
+
/**
|
|
87
|
+
* Schedule the next chaff emission.
|
|
88
|
+
*/
|
|
89
|
+
private scheduleNext;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/chaff/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,EAAe,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA2C/E,MAAM,WAAW,YAAY;IAC3B,4BAA4B;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD,MAAM,WAAW,UAAU;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,SAAS,CAAC;CACxB;AAiDD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,SAAS,CAAuB;IAExC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,cAAc,CAAK;IAE3B,OAAO,CAAC,YAAY,CAA2C;gBAEnD,OAAO,CAAC,EAAE,YAAY;IAWlC;;;;;;;;;OASG;IACH,mBAAmB,IAAI,MAAM;IAS7B,mDAAmD;IACnD,KAAK,IAAI,IAAI;IASb,2BAA2B;IAC3B,IAAI,IAAI,IAAI;IAQZ,mDAAmD;IACnD,SAAS,IAAI,OAAO;IAQpB,2EAA2E;IAC3E,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAI9B;;;;;;;;OAQG;IACH,uBAAuB,CAAC,WAAW,EAAE,gBAAgB,GAAG,IAAI;IAe5D;;;;;;;OAOG;IACH,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IA0BnD;;;OAGG;IACH,gBAAgB,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAQ1D,8DAA8D;IAC9D,QAAQ,IAAI,UAAU;IAatB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAkBxB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAQxB;;OAEG;IACH,OAAO,CAAC,YAAY;CAoBrB"}
|
|
@@ -0,0 +1,268 @@
|
|
|
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
|
+
import { PacketFlags } from '../types.js';
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Constants
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/** Destination hash length in bytes (truncated BLAKE3). */
|
|
12
|
+
const DEST_HASH_LENGTH = 8;
|
|
13
|
+
/** Sender ephemeral ID length in bytes. */
|
|
14
|
+
const SENDER_EPHEMERAL_ID_LENGTH = 16;
|
|
15
|
+
/** Packet version used across the SDK. */
|
|
16
|
+
const PACKET_VERSION = 1;
|
|
17
|
+
/** Default minimum chaff payload size in bytes. */
|
|
18
|
+
const DEFAULT_MIN_PACKET_SIZE = 32;
|
|
19
|
+
/** Default maximum chaff payload size in bytes. */
|
|
20
|
+
const DEFAULT_MAX_PACKET_SIZE = 256;
|
|
21
|
+
/** Default burst variance (0.0-1.0). */
|
|
22
|
+
const DEFAULT_BURST_VARIANCE = 0.3;
|
|
23
|
+
/** Base emission intervals per rate, in milliseconds. */
|
|
24
|
+
const RATE_INTERVALS = {
|
|
25
|
+
low: 60_000, // ~1 packet / 60 s → ~1 KB/h
|
|
26
|
+
normal: 30_000, // ~1 packet / 30 s → ~2 KB/h
|
|
27
|
+
high: 10_000, // ~1 packet / 10 s → ~6 KB/h
|
|
28
|
+
};
|
|
29
|
+
/** Maps relay willingness to the appropriate chaff rate. */
|
|
30
|
+
const WILLINGNESS_TO_RATE = {
|
|
31
|
+
eager: 'high',
|
|
32
|
+
willing: 'normal',
|
|
33
|
+
reluctant: 'low',
|
|
34
|
+
unavailable: null,
|
|
35
|
+
};
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Crypto helpers (isomorphic: works in Node.js and browsers)
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
/**
|
|
40
|
+
* Fill a Uint8Array with cryptographically secure random bytes.
|
|
41
|
+
* Uses globalThis.crypto (Web Crypto) when available, otherwise
|
|
42
|
+
* falls back to Node.js crypto module.
|
|
43
|
+
*/
|
|
44
|
+
function secureRandomBytes(length) {
|
|
45
|
+
const buf = new Uint8Array(length);
|
|
46
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto?.getRandomValues) {
|
|
47
|
+
globalThis.crypto.getRandomValues(buf);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Node.js environments where globalThis.crypto may not exist (older Node)
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
52
|
+
const nodeCrypto = require('node:crypto');
|
|
53
|
+
nodeCrypto.randomFillSync(buf);
|
|
54
|
+
}
|
|
55
|
+
return buf;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Return a uniformly distributed random integer in [min, max] (inclusive).
|
|
59
|
+
*/
|
|
60
|
+
function randomInt(min, max) {
|
|
61
|
+
const range = max - min + 1;
|
|
62
|
+
const bytes = secureRandomBytes(4);
|
|
63
|
+
const value = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
|
|
64
|
+
return min + (value % range);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Return a random floating-point value in [0, 1).
|
|
68
|
+
*/
|
|
69
|
+
function randomFloat() {
|
|
70
|
+
const bytes = secureRandomBytes(4);
|
|
71
|
+
const value = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
|
|
72
|
+
return value / 0x1_0000_0000;
|
|
73
|
+
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// ChaffGenerator
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
export class ChaffGenerator {
|
|
78
|
+
minPacketSize;
|
|
79
|
+
maxPacketSize;
|
|
80
|
+
burstVariance;
|
|
81
|
+
rate;
|
|
82
|
+
running = false;
|
|
83
|
+
timer = null;
|
|
84
|
+
startedAt = null;
|
|
85
|
+
packetsGenerated = 0;
|
|
86
|
+
bytesGenerated = 0;
|
|
87
|
+
emitCallback = null;
|
|
88
|
+
constructor(options) {
|
|
89
|
+
this.rate = options?.rate ?? 'normal';
|
|
90
|
+
this.minPacketSize = options?.minPacketSize ?? DEFAULT_MIN_PACKET_SIZE;
|
|
91
|
+
this.maxPacketSize = options?.maxPacketSize ?? DEFAULT_MAX_PACKET_SIZE;
|
|
92
|
+
this.burstVariance = Math.max(0, Math.min(1, options?.burstVariance ?? DEFAULT_BURST_VARIANCE));
|
|
93
|
+
}
|
|
94
|
+
// -----------------------------------------------------------------------
|
|
95
|
+
// Packet generation
|
|
96
|
+
// -----------------------------------------------------------------------
|
|
97
|
+
/**
|
|
98
|
+
* Generate a single chaff packet.
|
|
99
|
+
*
|
|
100
|
+
* The packet is constructed to be byte-for-byte indistinguishable from
|
|
101
|
+
* a real encrypted message to any external observer: all variable-length
|
|
102
|
+
* fields are filled with cryptographically random data, the flags field
|
|
103
|
+
* is set to CHAFF (which the local node recognises but a relay treats
|
|
104
|
+
* identically to DATA), and the TTL is kept low so chaff doesn't
|
|
105
|
+
* propagate far.
|
|
106
|
+
*/
|
|
107
|
+
generateChaffPacket() {
|
|
108
|
+
const payloadSize = randomInt(this.minPacketSize, this.maxPacketSize);
|
|
109
|
+
return this.buildChaffPacket(payloadSize);
|
|
110
|
+
}
|
|
111
|
+
// -----------------------------------------------------------------------
|
|
112
|
+
// Emission scheduling
|
|
113
|
+
// -----------------------------------------------------------------------
|
|
114
|
+
/** Begin emitting chaff on a jittered schedule. */
|
|
115
|
+
start() {
|
|
116
|
+
if (this.running)
|
|
117
|
+
return;
|
|
118
|
+
this.running = true;
|
|
119
|
+
if (this.startedAt === null) {
|
|
120
|
+
this.startedAt = Date.now();
|
|
121
|
+
}
|
|
122
|
+
this.scheduleNext();
|
|
123
|
+
}
|
|
124
|
+
/** Stop chaff emission. */
|
|
125
|
+
stop() {
|
|
126
|
+
this.running = false;
|
|
127
|
+
if (this.timer !== null) {
|
|
128
|
+
clearTimeout(this.timer);
|
|
129
|
+
this.timer = null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** Whether the generator is currently emitting. */
|
|
133
|
+
isRunning() {
|
|
134
|
+
return this.running;
|
|
135
|
+
}
|
|
136
|
+
// -----------------------------------------------------------------------
|
|
137
|
+
// Rate control
|
|
138
|
+
// -----------------------------------------------------------------------
|
|
139
|
+
/** Change the emission rate. Takes effect on the next scheduling cycle. */
|
|
140
|
+
setRate(rate) {
|
|
141
|
+
this.rate = rate;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Automatically adjust the chaff rate based on the device's relay
|
|
145
|
+
* willingness setting.
|
|
146
|
+
*
|
|
147
|
+
* - eager → high
|
|
148
|
+
* - willing → normal
|
|
149
|
+
* - reluctant → low
|
|
150
|
+
* - unavailable → stop entirely
|
|
151
|
+
*/
|
|
152
|
+
adaptToRelayWillingness(willingness) {
|
|
153
|
+
const mapped = WILLINGNESS_TO_RATE[willingness];
|
|
154
|
+
if (mapped === null) {
|
|
155
|
+
this.stop();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this.rate = mapped;
|
|
159
|
+
// If already running, let the current timer expire naturally — the
|
|
160
|
+
// new rate will be picked up on the next scheduling cycle.
|
|
161
|
+
}
|
|
162
|
+
// -----------------------------------------------------------------------
|
|
163
|
+
// Real message camouflage
|
|
164
|
+
// -----------------------------------------------------------------------
|
|
165
|
+
/**
|
|
166
|
+
* Surround a real packet with 0-2 chaff packets whose payload sizes
|
|
167
|
+
* approximate the real packet's size, making it harder for an observer
|
|
168
|
+
* to distinguish the real message in a burst.
|
|
169
|
+
*
|
|
170
|
+
* Returns an array of 1-3 packets with the real packet placed at a
|
|
171
|
+
* random position.
|
|
172
|
+
*/
|
|
173
|
+
camouflageRealMessage(realPacket) {
|
|
174
|
+
const chaffCount = randomInt(0, 2);
|
|
175
|
+
if (chaffCount === 0)
|
|
176
|
+
return [realPacket];
|
|
177
|
+
const realSize = realPacket.encryptedPayload.length;
|
|
178
|
+
// Build chaff with sizes close (±20%) to the real payload.
|
|
179
|
+
const sizeFloor = Math.max(1, Math.round(realSize * 0.8));
|
|
180
|
+
const sizeCeil = Math.round(realSize * 1.2);
|
|
181
|
+
const chaffPackets = [];
|
|
182
|
+
for (let i = 0; i < chaffCount; i++) {
|
|
183
|
+
const size = randomInt(sizeFloor, sizeCeil);
|
|
184
|
+
chaffPackets.push(this.buildChaffPacket(size));
|
|
185
|
+
}
|
|
186
|
+
// Insert the real packet at a random position in the burst.
|
|
187
|
+
const insertionIndex = randomInt(0, chaffPackets.length);
|
|
188
|
+
chaffPackets.splice(insertionIndex, 0, realPacket);
|
|
189
|
+
return chaffPackets;
|
|
190
|
+
}
|
|
191
|
+
// -----------------------------------------------------------------------
|
|
192
|
+
// Callback registration
|
|
193
|
+
// -----------------------------------------------------------------------
|
|
194
|
+
/**
|
|
195
|
+
* Register a callback that receives each generated chaff packet.
|
|
196
|
+
* The transport layer will call this to enqueue chaff for sending.
|
|
197
|
+
*/
|
|
198
|
+
onChaffGenerated(callback) {
|
|
199
|
+
this.emitCallback = callback;
|
|
200
|
+
}
|
|
201
|
+
// -----------------------------------------------------------------------
|
|
202
|
+
// Statistics
|
|
203
|
+
// -----------------------------------------------------------------------
|
|
204
|
+
/** Return runtime statistics for monitoring / diagnostics. */
|
|
205
|
+
getStats() {
|
|
206
|
+
return {
|
|
207
|
+
packetsGenerated: this.packetsGenerated,
|
|
208
|
+
bytesGenerated: this.bytesGenerated,
|
|
209
|
+
uptime: this.startedAt !== null ? Date.now() - this.startedAt : 0,
|
|
210
|
+
currentRate: this.rate,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// -----------------------------------------------------------------------
|
|
214
|
+
// Private helpers
|
|
215
|
+
// -----------------------------------------------------------------------
|
|
216
|
+
/**
|
|
217
|
+
* Build a chaff packet with the given payload size.
|
|
218
|
+
*/
|
|
219
|
+
buildChaffPacket(payloadSize) {
|
|
220
|
+
const encryptedPayload = secureRandomBytes(payloadSize);
|
|
221
|
+
const packet = {
|
|
222
|
+
version: PACKET_VERSION,
|
|
223
|
+
flags: PacketFlags.CHAFF,
|
|
224
|
+
destHash: secureRandomBytes(DEST_HASH_LENGTH),
|
|
225
|
+
senderEphemeralId: secureRandomBytes(SENDER_EPHEMERAL_ID_LENGTH),
|
|
226
|
+
ttl: randomInt(1, 3),
|
|
227
|
+
payloadLength: payloadSize,
|
|
228
|
+
encryptedPayload,
|
|
229
|
+
};
|
|
230
|
+
this.packetsGenerated++;
|
|
231
|
+
this.bytesGenerated += payloadSize;
|
|
232
|
+
return packet;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Compute the next emission delay in milliseconds, adding jitter
|
|
236
|
+
* proportional to burstVariance so inter-packet timing is not
|
|
237
|
+
* predictable.
|
|
238
|
+
*/
|
|
239
|
+
computeNextDelay() {
|
|
240
|
+
const base = RATE_INTERVALS[this.rate];
|
|
241
|
+
const jitterRange = base * this.burstVariance;
|
|
242
|
+
// Uniform jitter in [-jitterRange, +jitterRange]
|
|
243
|
+
const jitter = (randomFloat() * 2 - 1) * jitterRange;
|
|
244
|
+
return Math.max(1, Math.round(base + jitter));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Schedule the next chaff emission.
|
|
248
|
+
*/
|
|
249
|
+
scheduleNext() {
|
|
250
|
+
if (!this.running)
|
|
251
|
+
return;
|
|
252
|
+
const delay = this.computeNextDelay();
|
|
253
|
+
this.timer = setTimeout(() => {
|
|
254
|
+
if (!this.running)
|
|
255
|
+
return;
|
|
256
|
+
const packet = this.generateChaffPacket();
|
|
257
|
+
if (this.emitCallback) {
|
|
258
|
+
this.emitCallback(packet);
|
|
259
|
+
}
|
|
260
|
+
this.scheduleNext();
|
|
261
|
+
}, delay);
|
|
262
|
+
// Prevent the timer from keeping the process alive in Node.js.
|
|
263
|
+
if (typeof this.timer === 'object' && 'unref' in this.timer) {
|
|
264
|
+
this.timer.unref();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chaff/index.ts"],"names":[],"mappings":"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"}
|