@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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { ComplianceConfig, Message, MessageHook } from '../types.js';
|
|
2
|
+
/** A single logged message with metadata. */
|
|
3
|
+
export interface LogEntry {
|
|
4
|
+
message: Message;
|
|
5
|
+
direction: 'sent' | 'received';
|
|
6
|
+
loggedAt: number;
|
|
7
|
+
}
|
|
8
|
+
/** Options for filtering the message log. */
|
|
9
|
+
export interface LogQueryOptions {
|
|
10
|
+
/** Only entries logged at or after this timestamp (ms). */
|
|
11
|
+
since?: number;
|
|
12
|
+
/** Only entries logged at or before this timestamp (ms). */
|
|
13
|
+
until?: number;
|
|
14
|
+
/** Only entries involving this peer (as sender or recipient). */
|
|
15
|
+
peerId?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Options for exporting the audit log. */
|
|
18
|
+
export interface AuditExportOptions {
|
|
19
|
+
/** Only entries logged at or after this timestamp (ms). */
|
|
20
|
+
since?: number;
|
|
21
|
+
/** Only entries logged at or before this timestamp (ms). */
|
|
22
|
+
until?: number;
|
|
23
|
+
}
|
|
24
|
+
/** Result of a content scan. */
|
|
25
|
+
export interface ScanResult {
|
|
26
|
+
approved: boolean;
|
|
27
|
+
reason?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare class ComplianceManager {
|
|
30
|
+
private config;
|
|
31
|
+
private log;
|
|
32
|
+
private complianceKey;
|
|
33
|
+
private beforeSendHooks;
|
|
34
|
+
private afterReceiveHooks;
|
|
35
|
+
private retentionTimer;
|
|
36
|
+
constructor(config?: ComplianceConfig);
|
|
37
|
+
/**
|
|
38
|
+
* Apply a new compliance configuration. Merges with the existing config.
|
|
39
|
+
*/
|
|
40
|
+
configure(config: ComplianceConfig): void;
|
|
41
|
+
/**
|
|
42
|
+
* Return a shallow copy of the current compliance configuration.
|
|
43
|
+
*/
|
|
44
|
+
getConfig(): ComplianceConfig;
|
|
45
|
+
/**
|
|
46
|
+
* Returns true if any compliance feature is active:
|
|
47
|
+
* logging enabled, audit export mode set, retention configured,
|
|
48
|
+
* or a content scanner registered.
|
|
49
|
+
*/
|
|
50
|
+
isEnabled(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Store a plaintext message in the local log.
|
|
53
|
+
* Only records when logging is enabled in the config.
|
|
54
|
+
*/
|
|
55
|
+
logMessage(message: Message, direction: 'sent' | 'received'): void;
|
|
56
|
+
/**
|
|
57
|
+
* Retrieve logged messages, optionally filtering by time range and peer.
|
|
58
|
+
*/
|
|
59
|
+
getMessageLog(options?: LogQueryOptions): LogEntry[];
|
|
60
|
+
/**
|
|
61
|
+
* Clear log entries older than the given timestamp.
|
|
62
|
+
* If no timestamp is provided, clears the entire log.
|
|
63
|
+
* Returns the number of entries removed.
|
|
64
|
+
*/
|
|
65
|
+
clearLog(before?: number): number;
|
|
66
|
+
/**
|
|
67
|
+
* Export the audit log as a Uint8Array.
|
|
68
|
+
*
|
|
69
|
+
* - If auditExport is 'plaintext', returns UTF-8 encoded JSON.
|
|
70
|
+
* - If auditExport is 'encrypted', encrypts the JSON with AES-256-GCM
|
|
71
|
+
* using the compliance key set via setComplianceKey().
|
|
72
|
+
* - If auditExport is not configured, defaults to 'plaintext'.
|
|
73
|
+
*
|
|
74
|
+
* The encrypted format is: [12-byte nonce][ciphertext+tag].
|
|
75
|
+
*/
|
|
76
|
+
exportAuditLog(options?: AuditExportOptions): Uint8Array;
|
|
77
|
+
/**
|
|
78
|
+
* Set the AES-256 key used to encrypt audit exports.
|
|
79
|
+
*/
|
|
80
|
+
setComplianceKey(key: Uint8Array): void;
|
|
81
|
+
/**
|
|
82
|
+
* Run the developer-supplied content scanner on a message.
|
|
83
|
+
* Returns { approved: true } if no scanner is configured.
|
|
84
|
+
*/
|
|
85
|
+
scanMessage(message: Message): Promise<ScanResult>;
|
|
86
|
+
/** Register a hook that runs before a message is sent. */
|
|
87
|
+
addBeforeSendHook(hook: MessageHook): void;
|
|
88
|
+
/** Register a hook that runs after a message is received. */
|
|
89
|
+
addAfterReceiveHook(hook: MessageHook): void;
|
|
90
|
+
/** Remove a previously registered before-send hook. */
|
|
91
|
+
removeBeforeSendHook(hook: MessageHook): void;
|
|
92
|
+
/** Remove a previously registered after-receive hook. */
|
|
93
|
+
removeAfterReceiveHook(hook: MessageHook): void;
|
|
94
|
+
/**
|
|
95
|
+
* Run all before-send hooks in order.
|
|
96
|
+
* Returns false if any hook returns false (message should be blocked).
|
|
97
|
+
*/
|
|
98
|
+
runBeforeSendHooks(message: Message): Promise<boolean>;
|
|
99
|
+
/**
|
|
100
|
+
* Run all after-receive hooks in order.
|
|
101
|
+
* Returns false if any hook returns false (message should be rejected).
|
|
102
|
+
*/
|
|
103
|
+
runAfterReceiveHooks(message: Message): Promise<boolean>;
|
|
104
|
+
/**
|
|
105
|
+
* Start a periodic timer that prunes log entries older than
|
|
106
|
+
* retentionDays. No-op if retentionDays is not configured.
|
|
107
|
+
*
|
|
108
|
+
* @param intervalMs How often to run the prune (default: 1 hour).
|
|
109
|
+
*/
|
|
110
|
+
startRetentionEnforcement(intervalMs?: number): void;
|
|
111
|
+
/**
|
|
112
|
+
* Stop the periodic retention enforcement timer.
|
|
113
|
+
*/
|
|
114
|
+
stopRetentionEnforcement(): void;
|
|
115
|
+
/**
|
|
116
|
+
* Run an ordered list of hooks against a message.
|
|
117
|
+
* Returns false as soon as any hook returns false; true if all pass.
|
|
118
|
+
*/
|
|
119
|
+
private runHooks;
|
|
120
|
+
/**
|
|
121
|
+
* Remove log entries older than the configured retentionDays.
|
|
122
|
+
*/
|
|
123
|
+
private pruneExpiredEntries;
|
|
124
|
+
/**
|
|
125
|
+
* Filter log entries by optional time range.
|
|
126
|
+
*/
|
|
127
|
+
private getFilteredEntries;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compliance/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,EAAmB,MAAM,aAAa,CAAC;AAsB3F,6CAA6C;AAC7C,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,UAAU,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,gCAAgC;AAChC,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAaD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,GAAG,CAAkB;IAC7B,OAAO,CAAC,aAAa,CAA2B;IAEhD,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,iBAAiB,CAAqB;IAE9C,OAAO,CAAC,cAAc,CAA+C;gBAEzD,MAAM,CAAC,EAAE,gBAAgB;IAQrC;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAIzC;;OAEG;IACH,SAAS,IAAI,gBAAgB;IAI7B;;;;OAIG;IACH,SAAS,IAAI,OAAO;IAapB;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAYlE;;OAEG;IACH,aAAa,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,QAAQ,EAAE;IAgBpD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;IAgBjC;;;;;;;;;OASG;IACH,cAAc,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,UAAU;IAoCxD;;OAEG;IACH,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAavC;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAYxD,0DAA0D;IAC1D,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAI1C,6DAA6D;IAC7D,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAI5C,uDAAuD;IACvD,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAO7C,yDAAyD;IACzD,sBAAsB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAO/C;;;OAGG;IACG,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAI5D;;;OAGG;IACG,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ9D;;;;;OAKG;IACH,yBAAyB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI;IAmBpD;;OAEG;IACH,wBAAwB,IAAI,IAAI;IAWhC;;;OAGG;YACW,QAAQ;IAQtB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAM3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAU3B"}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// MeshWhisper SDK — Compliance API
|
|
3
|
+
// Optional compliance layer for message logging, audit export,
|
|
4
|
+
// content scanning, and middleware hooks as described in PRD §12.3.
|
|
5
|
+
//
|
|
6
|
+
// The protocol never accesses plaintext — the developer's app does.
|
|
7
|
+
// All hooks are entirely optional and the app developer's
|
|
8
|
+
// responsibility.
|
|
9
|
+
// ============================================================
|
|
10
|
+
import { gcm } from '@noble/ciphers/aes';
|
|
11
|
+
import { randomBytes } from '@noble/hashes/utils';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Constants
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/** AES-256-GCM nonce length in bytes. */
|
|
16
|
+
const AES_GCM_NONCE_LENGTH = 12;
|
|
17
|
+
/** AES-256 key length in bytes. */
|
|
18
|
+
const AES_256_KEY_LENGTH = 32;
|
|
19
|
+
/** Default retention enforcement interval: 1 hour. */
|
|
20
|
+
const DEFAULT_ENFORCEMENT_INTERVAL_MS = 60 * 60 * 1000;
|
|
21
|
+
/** Milliseconds per day. */
|
|
22
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Text encoder / decoder
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
const textEncoder = new TextEncoder();
|
|
27
|
+
const textDecoder = new TextDecoder();
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// ComplianceManager
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
export class ComplianceManager {
|
|
32
|
+
config;
|
|
33
|
+
log = [];
|
|
34
|
+
complianceKey = null;
|
|
35
|
+
beforeSendHooks = [];
|
|
36
|
+
afterReceiveHooks = [];
|
|
37
|
+
retentionTimer = null;
|
|
38
|
+
constructor(config) {
|
|
39
|
+
this.config = config ?? {};
|
|
40
|
+
}
|
|
41
|
+
// -----------------------------------------------------------------------
|
|
42
|
+
// Configuration
|
|
43
|
+
// -----------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Apply a new compliance configuration. Merges with the existing config.
|
|
46
|
+
*/
|
|
47
|
+
configure(config) {
|
|
48
|
+
this.config = { ...this.config, ...config };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Return a shallow copy of the current compliance configuration.
|
|
52
|
+
*/
|
|
53
|
+
getConfig() {
|
|
54
|
+
return { ...this.config };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Returns true if any compliance feature is active:
|
|
58
|
+
* logging enabled, audit export mode set, retention configured,
|
|
59
|
+
* or a content scanner registered.
|
|
60
|
+
*/
|
|
61
|
+
isEnabled() {
|
|
62
|
+
return (this.config.logging === true ||
|
|
63
|
+
this.config.auditExport !== undefined ||
|
|
64
|
+
this.config.retentionDays !== undefined ||
|
|
65
|
+
this.config.contentScanning !== undefined);
|
|
66
|
+
}
|
|
67
|
+
// -----------------------------------------------------------------------
|
|
68
|
+
// Message logging
|
|
69
|
+
// -----------------------------------------------------------------------
|
|
70
|
+
/**
|
|
71
|
+
* Store a plaintext message in the local log.
|
|
72
|
+
* Only records when logging is enabled in the config.
|
|
73
|
+
*/
|
|
74
|
+
logMessage(message, direction) {
|
|
75
|
+
if (!this.config.logging)
|
|
76
|
+
return;
|
|
77
|
+
const entry = {
|
|
78
|
+
message,
|
|
79
|
+
direction,
|
|
80
|
+
loggedAt: Date.now(),
|
|
81
|
+
};
|
|
82
|
+
this.log.push(entry);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Retrieve logged messages, optionally filtering by time range and peer.
|
|
86
|
+
*/
|
|
87
|
+
getMessageLog(options) {
|
|
88
|
+
if (!options)
|
|
89
|
+
return [...this.log];
|
|
90
|
+
const { since, until, peerId } = options;
|
|
91
|
+
return this.log.filter((entry) => {
|
|
92
|
+
if (since !== undefined && entry.loggedAt < since)
|
|
93
|
+
return false;
|
|
94
|
+
if (until !== undefined && entry.loggedAt > until)
|
|
95
|
+
return false;
|
|
96
|
+
if (peerId !== undefined) {
|
|
97
|
+
const msg = entry.message;
|
|
98
|
+
if (msg.senderId !== peerId && msg.recipientId !== peerId)
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Clear log entries older than the given timestamp.
|
|
106
|
+
* If no timestamp is provided, clears the entire log.
|
|
107
|
+
* Returns the number of entries removed.
|
|
108
|
+
*/
|
|
109
|
+
clearLog(before) {
|
|
110
|
+
if (before === undefined) {
|
|
111
|
+
const count = this.log.length;
|
|
112
|
+
this.log = [];
|
|
113
|
+
return count;
|
|
114
|
+
}
|
|
115
|
+
const originalLength = this.log.length;
|
|
116
|
+
this.log = this.log.filter((entry) => entry.loggedAt >= before);
|
|
117
|
+
return originalLength - this.log.length;
|
|
118
|
+
}
|
|
119
|
+
// -----------------------------------------------------------------------
|
|
120
|
+
// Audit export
|
|
121
|
+
// -----------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* Export the audit log as a Uint8Array.
|
|
124
|
+
*
|
|
125
|
+
* - If auditExport is 'plaintext', returns UTF-8 encoded JSON.
|
|
126
|
+
* - If auditExport is 'encrypted', encrypts the JSON with AES-256-GCM
|
|
127
|
+
* using the compliance key set via setComplianceKey().
|
|
128
|
+
* - If auditExport is not configured, defaults to 'plaintext'.
|
|
129
|
+
*
|
|
130
|
+
* The encrypted format is: [12-byte nonce][ciphertext+tag].
|
|
131
|
+
*/
|
|
132
|
+
exportAuditLog(options) {
|
|
133
|
+
const entries = this.getFilteredEntries(options);
|
|
134
|
+
const json = JSON.stringify(entries, serializeReplacer);
|
|
135
|
+
const plainBytes = textEncoder.encode(json);
|
|
136
|
+
const mode = this.config.auditExport ?? 'plaintext';
|
|
137
|
+
if (mode === 'plaintext') {
|
|
138
|
+
return plainBytes;
|
|
139
|
+
}
|
|
140
|
+
// Encrypted export
|
|
141
|
+
if (!this.complianceKey) {
|
|
142
|
+
throw new Error('Compliance key not set. Call setComplianceKey() before exporting an encrypted audit log.');
|
|
143
|
+
}
|
|
144
|
+
if (this.complianceKey.length !== AES_256_KEY_LENGTH) {
|
|
145
|
+
throw new Error(`Compliance key must be ${AES_256_KEY_LENGTH} bytes for AES-256-GCM.`);
|
|
146
|
+
}
|
|
147
|
+
const nonce = randomBytes(AES_GCM_NONCE_LENGTH);
|
|
148
|
+
const cipher = gcm(this.complianceKey, nonce);
|
|
149
|
+
const ciphertext = cipher.encrypt(plainBytes);
|
|
150
|
+
// Pack as [nonce][ciphertext+tag]
|
|
151
|
+
const result = new Uint8Array(nonce.length + ciphertext.length);
|
|
152
|
+
result.set(nonce, 0);
|
|
153
|
+
result.set(ciphertext, nonce.length);
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Set the AES-256 key used to encrypt audit exports.
|
|
158
|
+
*/
|
|
159
|
+
setComplianceKey(key) {
|
|
160
|
+
if (key.length !== AES_256_KEY_LENGTH) {
|
|
161
|
+
throw new Error(`Compliance key must be ${AES_256_KEY_LENGTH} bytes for AES-256-GCM.`);
|
|
162
|
+
}
|
|
163
|
+
this.complianceKey = new Uint8Array(key);
|
|
164
|
+
}
|
|
165
|
+
// -----------------------------------------------------------------------
|
|
166
|
+
// Content scanning
|
|
167
|
+
// -----------------------------------------------------------------------
|
|
168
|
+
/**
|
|
169
|
+
* Run the developer-supplied content scanner on a message.
|
|
170
|
+
* Returns { approved: true } if no scanner is configured.
|
|
171
|
+
*/
|
|
172
|
+
async scanMessage(message) {
|
|
173
|
+
if (!this.config.contentScanning) {
|
|
174
|
+
return { approved: true };
|
|
175
|
+
}
|
|
176
|
+
return this.config.contentScanning(message);
|
|
177
|
+
}
|
|
178
|
+
// -----------------------------------------------------------------------
|
|
179
|
+
// Middleware hooks
|
|
180
|
+
// -----------------------------------------------------------------------
|
|
181
|
+
/** Register a hook that runs before a message is sent. */
|
|
182
|
+
addBeforeSendHook(hook) {
|
|
183
|
+
this.beforeSendHooks.push(hook);
|
|
184
|
+
}
|
|
185
|
+
/** Register a hook that runs after a message is received. */
|
|
186
|
+
addAfterReceiveHook(hook) {
|
|
187
|
+
this.afterReceiveHooks.push(hook);
|
|
188
|
+
}
|
|
189
|
+
/** Remove a previously registered before-send hook. */
|
|
190
|
+
removeBeforeSendHook(hook) {
|
|
191
|
+
const idx = this.beforeSendHooks.indexOf(hook);
|
|
192
|
+
if (idx !== -1) {
|
|
193
|
+
this.beforeSendHooks.splice(idx, 1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Remove a previously registered after-receive hook. */
|
|
197
|
+
removeAfterReceiveHook(hook) {
|
|
198
|
+
const idx = this.afterReceiveHooks.indexOf(hook);
|
|
199
|
+
if (idx !== -1) {
|
|
200
|
+
this.afterReceiveHooks.splice(idx, 1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Run all before-send hooks in order.
|
|
205
|
+
* Returns false if any hook returns false (message should be blocked).
|
|
206
|
+
*/
|
|
207
|
+
async runBeforeSendHooks(message) {
|
|
208
|
+
return this.runHooks(this.beforeSendHooks, message);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Run all after-receive hooks in order.
|
|
212
|
+
* Returns false if any hook returns false (message should be rejected).
|
|
213
|
+
*/
|
|
214
|
+
async runAfterReceiveHooks(message) {
|
|
215
|
+
return this.runHooks(this.afterReceiveHooks, message);
|
|
216
|
+
}
|
|
217
|
+
// -----------------------------------------------------------------------
|
|
218
|
+
// Retention enforcement
|
|
219
|
+
// -----------------------------------------------------------------------
|
|
220
|
+
/**
|
|
221
|
+
* Start a periodic timer that prunes log entries older than
|
|
222
|
+
* retentionDays. No-op if retentionDays is not configured.
|
|
223
|
+
*
|
|
224
|
+
* @param intervalMs How often to run the prune (default: 1 hour).
|
|
225
|
+
*/
|
|
226
|
+
startRetentionEnforcement(intervalMs) {
|
|
227
|
+
if (this.retentionTimer !== null)
|
|
228
|
+
return; // already running
|
|
229
|
+
if (this.config.retentionDays === undefined)
|
|
230
|
+
return;
|
|
231
|
+
const interval = intervalMs ?? DEFAULT_ENFORCEMENT_INTERVAL_MS;
|
|
232
|
+
this.retentionTimer = setInterval(() => {
|
|
233
|
+
this.pruneExpiredEntries();
|
|
234
|
+
}, interval);
|
|
235
|
+
// Prevent the timer from keeping the process alive in Node.js.
|
|
236
|
+
if (typeof this.retentionTimer === 'object' && 'unref' in this.retentionTimer) {
|
|
237
|
+
this.retentionTimer.unref();
|
|
238
|
+
}
|
|
239
|
+
// Run an initial prune immediately.
|
|
240
|
+
this.pruneExpiredEntries();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Stop the periodic retention enforcement timer.
|
|
244
|
+
*/
|
|
245
|
+
stopRetentionEnforcement() {
|
|
246
|
+
if (this.retentionTimer !== null) {
|
|
247
|
+
clearInterval(this.retentionTimer);
|
|
248
|
+
this.retentionTimer = null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// -----------------------------------------------------------------------
|
|
252
|
+
// Private helpers
|
|
253
|
+
// -----------------------------------------------------------------------
|
|
254
|
+
/**
|
|
255
|
+
* Run an ordered list of hooks against a message.
|
|
256
|
+
* Returns false as soon as any hook returns false; true if all pass.
|
|
257
|
+
*/
|
|
258
|
+
async runHooks(hooks, message) {
|
|
259
|
+
for (const hook of hooks) {
|
|
260
|
+
const result = await hook(message);
|
|
261
|
+
if (result === false)
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Remove log entries older than the configured retentionDays.
|
|
268
|
+
*/
|
|
269
|
+
pruneExpiredEntries() {
|
|
270
|
+
if (this.config.retentionDays === undefined)
|
|
271
|
+
return;
|
|
272
|
+
const cutoff = Date.now() - this.config.retentionDays * MS_PER_DAY;
|
|
273
|
+
this.log = this.log.filter((entry) => entry.loggedAt >= cutoff);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Filter log entries by optional time range.
|
|
277
|
+
*/
|
|
278
|
+
getFilteredEntries(options) {
|
|
279
|
+
if (!options)
|
|
280
|
+
return [...this.log];
|
|
281
|
+
const { since, until } = options;
|
|
282
|
+
return this.log.filter((entry) => {
|
|
283
|
+
if (since !== undefined && entry.loggedAt < since)
|
|
284
|
+
return false;
|
|
285
|
+
if (until !== undefined && entry.loggedAt > until)
|
|
286
|
+
return false;
|
|
287
|
+
return true;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// JSON serialization helpers
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
/**
|
|
295
|
+
* Custom replacer for JSON.stringify that converts Uint8Array fields
|
|
296
|
+
* to base64 strings so the audit log is portable.
|
|
297
|
+
*/
|
|
298
|
+
function serializeReplacer(_key, value) {
|
|
299
|
+
if (value instanceof Uint8Array) {
|
|
300
|
+
return { __type: 'Uint8Array', data: uint8ArrayToBase64(value) };
|
|
301
|
+
}
|
|
302
|
+
return value;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Encode a Uint8Array to a base64 string without depending on Node.js
|
|
306
|
+
* Buffer. Works in both browser and Node.js environments.
|
|
307
|
+
*/
|
|
308
|
+
function uint8ArrayToBase64(bytes) {
|
|
309
|
+
let binary = '';
|
|
310
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
311
|
+
binary += String.fromCharCode(bytes[i]);
|
|
312
|
+
}
|
|
313
|
+
return btoa(binary);
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/compliance/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,mCAAmC;AACnC,+DAA+D;AAC/D,oEAAoE;AACpE,EAAE;AACF,oEAAoE;AACpE,0DAA0D;AAC1D,kBAAkB;AAClB,+DAA+D;AAE/D,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,yCAAyC;AACzC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC,mCAAmC;AACnC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAE9B,sDAAsD;AACtD,MAAM,+BAA+B,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEvD,4BAA4B;AAC5B,MAAM,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAqCvC,8EAA8E;AAC9E,yBAAyB;AACzB,8EAA8E;AAE9E,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;AACtC,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;AAEtC,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,MAAM,OAAO,iBAAiB;IACpB,MAAM,CAAmB;IACzB,GAAG,GAAe,EAAE,CAAC;IACrB,aAAa,GAAsB,IAAI,CAAC;IAExC,eAAe,GAAkB,EAAE,CAAC;IACpC,iBAAiB,GAAkB,EAAE,CAAC;IAEtC,cAAc,GAA0C,IAAI,CAAC;IAErE,YAAY,MAAyB;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,0EAA0E;IAC1E,gBAAgB;IAChB,0EAA0E;IAE1E;;OAEG;IACH,SAAS,CAAC,MAAwB;QAChC,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9C,CAAC;IAED;;OAEG;IACH,SAAS;QACP,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,SAAS;QACP,OAAO,CACL,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,IAAI;YAC5B,IAAI,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS;YACrC,IAAI,CAAC,MAAM,CAAC,aAAa,KAAK,SAAS;YACvC,IAAI,CAAC,MAAM,CAAC,eAAe,KAAK,SAAS,CAC1C,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,kBAAkB;IAClB,0EAA0E;IAE1E;;;OAGG;IACH,UAAU,CAAC,OAAgB,EAAE,SAA8B;QACzD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO;QAEjC,MAAM,KAAK,GAAa;YACtB,OAAO;YACP,SAAS;YACT,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE;SACrB,CAAC;QAEF,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,OAAyB;QACrC,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QAEnC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAEzC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YAC/B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,GAAG,KAAK;gBAAE,OAAO,KAAK,CAAC;YAChE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,GAAG,KAAK;gBAAE,OAAO,KAAK,CAAC;YAChE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC;gBAC1B,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,GAAG,CAAC,WAAW,KAAK,MAAM;oBAAE,OAAO,KAAK,CAAC;YAC1E,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,MAAe;QACtB,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YAC9B,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;YACd,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;QACvC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC;QAChE,OAAO,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;IAC1C,CAAC;IAED,0EAA0E;IAC1E,eAAe;IACf,0EAA0E;IAE1E;;;;;;;;;OASG;IACH,cAAc,CAAC,OAA4B;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QACxD,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAE5C,MAAM,IAAI,GAAoB,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,WAAW,CAAC;QAErE,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;YACzB,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,mBAAmB;QACnB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,0FAA0F,CAC3F,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,KAAK,kBAAkB,EAAE,CAAC;YACrD,MAAM,IAAI,KAAK,CACb,0BAA0B,kBAAkB,yBAAyB,CACtE,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,WAAW,CAAC,oBAAoB,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;QAC9C,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAE9C,kCAAkC;QAClC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAChE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACrB,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAErC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,GAAe;QAC9B,IAAI,GAAG,CAAC,MAAM,KAAK,kBAAkB,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CACb,0BAA0B,kBAAkB,yBAAyB,CACtE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IAC3C,CAAC;IAED,0EAA0E;IAC1E,mBAAmB;IACnB,0EAA0E;IAE1E;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,OAAgB;QAChC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;YACjC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED,0EAA0E;IAC1E,mBAAmB;IACnB,0EAA0E;IAE1E,0DAA0D;IAC1D,iBAAiB,CAAC,IAAiB;QACjC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,6DAA6D;IAC7D,mBAAmB,CAAC,IAAiB;QACnC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,uDAAuD;IACvD,oBAAoB,CAAC,IAAiB;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,yDAAyD;IACzD,sBAAsB,CAAC,IAAiB;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,kBAAkB,CAAC,OAAgB;QACvC,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,oBAAoB,CAAC,OAAgB;QACzC,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED,0EAA0E;IAC1E,wBAAwB;IACxB,0EAA0E;IAE1E;;;;;OAKG;IACH,yBAAyB,CAAC,UAAmB;QAC3C,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI;YAAE,OAAO,CAAC,kBAAkB;QAC5D,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,KAAK,SAAS;YAAE,OAAO;QAEpD,MAAM,QAAQ,GAAG,UAAU,IAAI,+BAA+B,CAAC;QAE/D,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC7B,CAAC,EAAE,QAAQ,CAAC,CAAC;QAEb,+DAA+D;QAC/D,IAAI,OAAO,IAAI,CAAC,cAAc,KAAK,QAAQ,IAAI,OAAO,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YAC7E,IAAI,CAAC,cAAiC,CAAC,KAAK,EAAE,CAAC;QAClD,CAAC;QAED,oCAAoC;QACpC,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,wBAAwB;QACtB,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YACjC,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,0EAA0E;IAC1E,kBAAkB;IAClB,0EAA0E;IAE1E;;;OAGG;IACK,KAAK,CAAC,QAAQ,CAAC,KAAoB,EAAE,OAAgB;QAC3D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;YACnC,IAAI,MAAM,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC;QACrC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACK,mBAAmB;QACzB,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,KAAK,SAAS;YAAE,OAAO;QACpD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,UAAU,CAAC;QACnE,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC;IAClE,CAAC;IAED;;OAEG;IACK,kBAAkB,CAAC,OAA4B;QACrD,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QAEnC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;QACjC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YAC/B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,GAAG,KAAK;gBAAE,OAAO,KAAK,CAAC;YAChE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,GAAG,KAAK;gBAAE,OAAO,KAAK,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E;;;GAGG;AACH,SAAS,iBAAiB,CAAC,IAAY,EAAE,KAAc;IACrD,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAChC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;IACnE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CAAC,KAAiB;IAC3C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { KeyPair, EncryptedPayload } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the current epoch hour: floor(Date.now() / 3_600_000).
|
|
4
|
+
* Used for hourly destHash rotation.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getCurrentEpochHour(): number;
|
|
7
|
+
/**
|
|
8
|
+
* Generates cryptographically secure random bytes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function randomBytes(length: number): Uint8Array;
|
|
11
|
+
/**
|
|
12
|
+
* Concatenates multiple Uint8Arrays into a single Uint8Array.
|
|
13
|
+
*/
|
|
14
|
+
export declare function concat(...arrays: Uint8Array[]): Uint8Array;
|
|
15
|
+
/**
|
|
16
|
+
* General-purpose BLAKE3 hash. Returns 32-byte digest.
|
|
17
|
+
*/
|
|
18
|
+
export declare function hash(data: Uint8Array): Uint8Array;
|
|
19
|
+
/**
|
|
20
|
+
* Derives a 256-bit namespace ID via BLAKE3(appBundleId || developerPublicKey || salt).
|
|
21
|
+
* The namespace isolates different applications sharing the same mesh.
|
|
22
|
+
*/
|
|
23
|
+
export declare function deriveNamespaceId(appBundleId: string, developerPublicKey: Uint8Array, salt: Uint8Array): Uint8Array;
|
|
24
|
+
/**
|
|
25
|
+
* Derives a truncated 8-byte destination hash via BLAKE3(recipientPublicKey || epochHour).
|
|
26
|
+
* Rotates every hour to limit linkability.
|
|
27
|
+
*/
|
|
28
|
+
export declare function deriveDestHash(recipientPublicKey: Uint8Array, epochHour: number): Uint8Array;
|
|
29
|
+
/**
|
|
30
|
+
* Generates a long-lived X25519 identity key pair.
|
|
31
|
+
*/
|
|
32
|
+
export declare function generateKeyPair(): KeyPair;
|
|
33
|
+
/**
|
|
34
|
+
* Generates an ephemeral X25519 key pair for single-use key agreement.
|
|
35
|
+
* Functionally identical to generateKeyPair but semantically distinct:
|
|
36
|
+
* ephemeral keys should be discarded after use.
|
|
37
|
+
*/
|
|
38
|
+
export declare function generateEphemeralKeyPair(): KeyPair;
|
|
39
|
+
/**
|
|
40
|
+
* Computes an X25519 shared secret from a private key and a peer's public key.
|
|
41
|
+
* Returns a 32-byte shared secret suitable for key derivation.
|
|
42
|
+
*/
|
|
43
|
+
export declare function computeSharedSecret(privateKey: Uint8Array, publicKey: Uint8Array): Uint8Array;
|
|
44
|
+
/**
|
|
45
|
+
* Encrypts plaintext with AES-256-GCM using a random 12-byte nonce.
|
|
46
|
+
* The key must be exactly 32 bytes.
|
|
47
|
+
*
|
|
48
|
+
* Returns ciphertext, nonce, and authentication tag as separate fields.
|
|
49
|
+
*/
|
|
50
|
+
export declare function encrypt(plaintext: Uint8Array, key: Uint8Array): EncryptedPayload;
|
|
51
|
+
/**
|
|
52
|
+
* Decrypts an AES-256-GCM EncryptedPayload.
|
|
53
|
+
* Throws if the tag verification fails (tampered or wrong key).
|
|
54
|
+
*/
|
|
55
|
+
export declare function decrypt(encrypted: EncryptedPayload, key: Uint8Array): Uint8Array;
|
|
56
|
+
/**
|
|
57
|
+
* Key derivation function built on BLAKE3's built-in KDF mode.
|
|
58
|
+
* Uses BLAKE3(inputKeyMaterial, { context: info, dkLen: length }).
|
|
59
|
+
*
|
|
60
|
+
* @param inputKeyMaterial - The raw key material (e.g., shared secret).
|
|
61
|
+
* @param info - Application-specific context string for domain separation.
|
|
62
|
+
* @param length - Desired output length in bytes (default 32).
|
|
63
|
+
*/
|
|
64
|
+
export declare function kdf(inputKeyMaterial: Uint8Array, info: string, length?: number): Uint8Array;
|
|
65
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/crypto/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAkB7D;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAEtD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAY1D;AAID;;GAEG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAEjD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,UAAU,EAC9B,IAAI,EAAE,UAAU,GACf,UAAU,CAKZ;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,kBAAkB,EAAE,UAAU,EAC9B,SAAS,EAAE,MAAM,GAChB,UAAU,CAWZ;AAID;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAIzC;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAIlD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,UAAU,GACpB,UAAU,CAEZ;AAID;;;;;GAKG;AACH,wBAAgB,OAAO,CACrB,SAAS,EAAE,UAAU,EACrB,GAAG,EAAE,UAAU,GACd,gBAAgB,CAUlB;AAED;;;GAGG;AACH,wBAAgB,OAAO,CACrB,SAAS,EAAE,gBAAgB,EAC3B,GAAG,EAAE,UAAU,GACd,UAAU,CAOZ;AAID;;;;;;;GAOG;AACH,wBAAgB,GAAG,CACjB,gBAAgB,EAAE,UAAU,EAC5B,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAA2B,GAClC,UAAU,CAEZ"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// MeshWhisper SDK — Crypto Primitives
|
|
3
|
+
// BLAKE3 hashing, X25519 key exchange, AES-256-GCM encryption,
|
|
4
|
+
// BLAKE3-based KDF, and utilities.
|
|
5
|
+
// ============================================================
|
|
6
|
+
import { blake3 } from '@noble/hashes/blake3';
|
|
7
|
+
import { randomBytes as nobleRandomBytes } from '@noble/hashes/utils';
|
|
8
|
+
import { x25519 } from '@noble/curves/ed25519';
|
|
9
|
+
import { gcm } from '@noble/ciphers/aes';
|
|
10
|
+
// ---- Constants ----
|
|
11
|
+
/** AES-GCM nonce length in bytes (96-bit). */
|
|
12
|
+
const GCM_NONCE_LENGTH = 12;
|
|
13
|
+
/** AES-GCM authentication tag length in bytes. */
|
|
14
|
+
const GCM_TAG_LENGTH = 16;
|
|
15
|
+
/** DestHash truncation length in bytes. */
|
|
16
|
+
const DEST_HASH_LENGTH = 8;
|
|
17
|
+
/** Default KDF output length in bytes. */
|
|
18
|
+
const DEFAULT_KDF_LENGTH = 32;
|
|
19
|
+
// ---- Utilities ----
|
|
20
|
+
/**
|
|
21
|
+
* Returns the current epoch hour: floor(Date.now() / 3_600_000).
|
|
22
|
+
* Used for hourly destHash rotation.
|
|
23
|
+
*/
|
|
24
|
+
export function getCurrentEpochHour() {
|
|
25
|
+
return Math.floor(Date.now() / 3_600_000);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generates cryptographically secure random bytes.
|
|
29
|
+
*/
|
|
30
|
+
export function randomBytes(length) {
|
|
31
|
+
return nobleRandomBytes(length);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Concatenates multiple Uint8Arrays into a single Uint8Array.
|
|
35
|
+
*/
|
|
36
|
+
export function concat(...arrays) {
|
|
37
|
+
let totalLength = 0;
|
|
38
|
+
for (const arr of arrays) {
|
|
39
|
+
totalLength += arr.length;
|
|
40
|
+
}
|
|
41
|
+
const result = new Uint8Array(totalLength);
|
|
42
|
+
let offset = 0;
|
|
43
|
+
for (const arr of arrays) {
|
|
44
|
+
result.set(arr, offset);
|
|
45
|
+
offset += arr.length;
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
// ---- BLAKE3 Hashing ----
|
|
50
|
+
/**
|
|
51
|
+
* General-purpose BLAKE3 hash. Returns 32-byte digest.
|
|
52
|
+
*/
|
|
53
|
+
export function hash(data) {
|
|
54
|
+
return blake3(data);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Derives a 256-bit namespace ID via BLAKE3(appBundleId || developerPublicKey || salt).
|
|
58
|
+
* The namespace isolates different applications sharing the same mesh.
|
|
59
|
+
*/
|
|
60
|
+
export function deriveNamespaceId(appBundleId, developerPublicKey, salt) {
|
|
61
|
+
const encoder = new TextEncoder();
|
|
62
|
+
const appBytes = encoder.encode(appBundleId);
|
|
63
|
+
const input = concat(appBytes, developerPublicKey, salt);
|
|
64
|
+
return blake3(input);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Derives a truncated 8-byte destination hash via BLAKE3(recipientPublicKey || epochHour).
|
|
68
|
+
* Rotates every hour to limit linkability.
|
|
69
|
+
*/
|
|
70
|
+
export function deriveDestHash(recipientPublicKey, epochHour) {
|
|
71
|
+
// Encode epochHour as 8-byte big-endian uint64
|
|
72
|
+
const epochBytes = new Uint8Array(8);
|
|
73
|
+
const view = new DataView(epochBytes.buffer);
|
|
74
|
+
// Use two 32-bit writes for full 64-bit range (epochHour fits in 53-bit JS number)
|
|
75
|
+
view.setUint32(0, Math.floor(epochHour / 0x100000000), false);
|
|
76
|
+
view.setUint32(4, epochHour >>> 0, false);
|
|
77
|
+
const input = concat(recipientPublicKey, epochBytes);
|
|
78
|
+
const fullHash = blake3(input);
|
|
79
|
+
return fullHash.slice(0, DEST_HASH_LENGTH);
|
|
80
|
+
}
|
|
81
|
+
// ---- X25519 Key Exchange ----
|
|
82
|
+
/**
|
|
83
|
+
* Generates a long-lived X25519 identity key pair.
|
|
84
|
+
*/
|
|
85
|
+
export function generateKeyPair() {
|
|
86
|
+
const privateKey = x25519.utils.randomSecretKey();
|
|
87
|
+
const publicKey = x25519.getPublicKey(privateKey);
|
|
88
|
+
return { publicKey, privateKey };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Generates an ephemeral X25519 key pair for single-use key agreement.
|
|
92
|
+
* Functionally identical to generateKeyPair but semantically distinct:
|
|
93
|
+
* ephemeral keys should be discarded after use.
|
|
94
|
+
*/
|
|
95
|
+
export function generateEphemeralKeyPair() {
|
|
96
|
+
const privateKey = x25519.utils.randomSecretKey();
|
|
97
|
+
const publicKey = x25519.getPublicKey(privateKey);
|
|
98
|
+
return { publicKey, privateKey };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Computes an X25519 shared secret from a private key and a peer's public key.
|
|
102
|
+
* Returns a 32-byte shared secret suitable for key derivation.
|
|
103
|
+
*/
|
|
104
|
+
export function computeSharedSecret(privateKey, publicKey) {
|
|
105
|
+
return x25519.getSharedSecret(privateKey, publicKey);
|
|
106
|
+
}
|
|
107
|
+
// ---- AES-256-GCM Encryption ----
|
|
108
|
+
/**
|
|
109
|
+
* Encrypts plaintext with AES-256-GCM using a random 12-byte nonce.
|
|
110
|
+
* The key must be exactly 32 bytes.
|
|
111
|
+
*
|
|
112
|
+
* Returns ciphertext, nonce, and authentication tag as separate fields.
|
|
113
|
+
*/
|
|
114
|
+
export function encrypt(plaintext, key) {
|
|
115
|
+
const nonce = randomBytes(GCM_NONCE_LENGTH);
|
|
116
|
+
const cipher = gcm(key, nonce);
|
|
117
|
+
const sealed = cipher.encrypt(plaintext);
|
|
118
|
+
// noble/ciphers GCM appends the 16-byte tag to the ciphertext
|
|
119
|
+
const ciphertext = sealed.slice(0, sealed.length - GCM_TAG_LENGTH);
|
|
120
|
+
const tag = sealed.slice(sealed.length - GCM_TAG_LENGTH);
|
|
121
|
+
return { ciphertext, nonce, tag };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Decrypts an AES-256-GCM EncryptedPayload.
|
|
125
|
+
* Throws if the tag verification fails (tampered or wrong key).
|
|
126
|
+
*/
|
|
127
|
+
export function decrypt(encrypted, key) {
|
|
128
|
+
const { ciphertext, nonce, tag } = encrypted;
|
|
129
|
+
// Reconstruct the sealed format expected by noble: ciphertext || tag
|
|
130
|
+
const sealed = concat(ciphertext, tag);
|
|
131
|
+
const cipher = gcm(key, nonce);
|
|
132
|
+
return cipher.decrypt(sealed);
|
|
133
|
+
}
|
|
134
|
+
// ---- BLAKE3-based KDF ----
|
|
135
|
+
/**
|
|
136
|
+
* Key derivation function built on BLAKE3's built-in KDF mode.
|
|
137
|
+
* Uses BLAKE3(inputKeyMaterial, { context: info, dkLen: length }).
|
|
138
|
+
*
|
|
139
|
+
* @param inputKeyMaterial - The raw key material (e.g., shared secret).
|
|
140
|
+
* @param info - Application-specific context string for domain separation.
|
|
141
|
+
* @param length - Desired output length in bytes (default 32).
|
|
142
|
+
*/
|
|
143
|
+
export function kdf(inputKeyMaterial, info, length = DEFAULT_KDF_LENGTH) {
|
|
144
|
+
return blake3(inputKeyMaterial, { context: info, dkLen: length });
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=index.js.map
|