@haven-chat-org/core 1.0.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 (81) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +71 -0
  3. package/dist/crypto/backup.d.ts +87 -0
  4. package/dist/crypto/backup.js +62 -0
  5. package/dist/crypto/backup.js.map +1 -0
  6. package/dist/crypto/double-ratchet.d.ts +104 -0
  7. package/dist/crypto/double-ratchet.js +274 -0
  8. package/dist/crypto/double-ratchet.js.map +1 -0
  9. package/dist/crypto/file.d.ts +14 -0
  10. package/dist/crypto/file.js +20 -0
  11. package/dist/crypto/file.js.map +1 -0
  12. package/dist/crypto/index.d.ts +9 -0
  13. package/dist/crypto/index.js +10 -0
  14. package/dist/crypto/index.js.map +1 -0
  15. package/dist/crypto/keys.d.ts +61 -0
  16. package/dist/crypto/keys.js +79 -0
  17. package/dist/crypto/keys.js.map +1 -0
  18. package/dist/crypto/passphrase.d.ts +10 -0
  19. package/dist/crypto/passphrase.js +142 -0
  20. package/dist/crypto/passphrase.js.map +1 -0
  21. package/dist/crypto/profile.d.ts +31 -0
  22. package/dist/crypto/profile.js +73 -0
  23. package/dist/crypto/profile.js.map +1 -0
  24. package/dist/crypto/sender-keys.d.ts +76 -0
  25. package/dist/crypto/sender-keys.js +170 -0
  26. package/dist/crypto/sender-keys.js.map +1 -0
  27. package/dist/crypto/sender-keys.test.d.ts +1 -0
  28. package/dist/crypto/sender-keys.test.js +272 -0
  29. package/dist/crypto/sender-keys.test.js.map +1 -0
  30. package/dist/crypto/utils.d.ts +41 -0
  31. package/dist/crypto/utils.js +102 -0
  32. package/dist/crypto/utils.js.map +1 -0
  33. package/dist/crypto/x3dh.d.ts +45 -0
  34. package/dist/crypto/x3dh.js +106 -0
  35. package/dist/crypto/x3dh.js.map +1 -0
  36. package/dist/export/__tests__/archive.test.d.ts +1 -0
  37. package/dist/export/__tests__/archive.test.js +276 -0
  38. package/dist/export/__tests__/archive.test.js.map +1 -0
  39. package/dist/export/archive.d.ts +38 -0
  40. package/dist/export/archive.js +107 -0
  41. package/dist/export/archive.js.map +1 -0
  42. package/dist/export/index.d.ts +4 -0
  43. package/dist/export/index.js +4 -0
  44. package/dist/export/index.js.map +1 -0
  45. package/dist/export/reader.d.ts +27 -0
  46. package/dist/export/reader.js +101 -0
  47. package/dist/export/reader.js.map +1 -0
  48. package/dist/export/signing.d.ts +15 -0
  49. package/dist/export/signing.js +44 -0
  50. package/dist/export/signing.js.map +1 -0
  51. package/dist/export/types.d.ts +128 -0
  52. package/dist/export/types.js +3 -0
  53. package/dist/export/types.js.map +1 -0
  54. package/dist/index.d.ts +5 -0
  55. package/dist/index.js +6 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/net/api.d.ts +200 -0
  58. package/dist/net/api.js +715 -0
  59. package/dist/net/api.js.map +1 -0
  60. package/dist/net/api.test.d.ts +1 -0
  61. package/dist/net/api.test.js +884 -0
  62. package/dist/net/api.test.js.map +1 -0
  63. package/dist/net/index.d.ts +2 -0
  64. package/dist/net/index.js +3 -0
  65. package/dist/net/index.js.map +1 -0
  66. package/dist/net/ws.d.ts +71 -0
  67. package/dist/net/ws.js +257 -0
  68. package/dist/net/ws.js.map +1 -0
  69. package/dist/store/index.d.ts +2 -0
  70. package/dist/store/index.js +2 -0
  71. package/dist/store/index.js.map +1 -0
  72. package/dist/store/memory.d.ts +24 -0
  73. package/dist/store/memory.js +50 -0
  74. package/dist/store/memory.js.map +1 -0
  75. package/dist/store/types.d.ts +23 -0
  76. package/dist/store/types.js +2 -0
  77. package/dist/store/types.js.map +1 -0
  78. package/dist/types.d.ts +850 -0
  79. package/dist/types.js +35 -0
  80. package/dist/types.js.map +1 -0
  81. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # haven-core
2
+
3
+ Shared TypeScript library providing cryptographic primitives, API client, and type definitions used by the web frontend (and any future clients).
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ src/
9
+ ├── types.ts # All shared TypeScript interfaces and types
10
+ ├── index.ts # Public API re-exports
11
+ ├── crypto/
12
+ │ ├── utils.ts # libsodium init, base64 helpers, key generation
13
+ │ ├── x3dh.ts # X3DH key agreement (initiator + responder)
14
+ │ ├── double-ratchet.ts # Double Ratchet session (DM encryption)
15
+ │ ├── sender-keys.ts # Sender Keys protocol (group channel encryption)
16
+ │ ├── file-crypto.ts # XChaCha20-Poly1305 file encryption
17
+ │ └── store.ts # MemoryStore for key material (in-memory only)
18
+ ├── net/
19
+ │ ├── api.ts # HavenApi — type-safe REST client with JWT management
20
+ │ └── ws.ts # HavenWs — WebSocket client with auto-reconnect
21
+ └── store/
22
+ └── memory-store.ts # In-memory key/session storage
23
+ ```
24
+
25
+ ## E2EE Model
26
+
27
+ ### DMs (X3DH + Double Ratchet)
28
+ 1. Initiator fetches recipient's key bundle (identity key + signed prekey + one-time prekey)
29
+ 2. X3DH key agreement produces a shared secret
30
+ 3. Double Ratchet session provides forward secrecy and break-in recovery
31
+ 4. Wire format: `[0x01 or 0x02][encrypted payload]`
32
+
33
+ ### Server Channels (Sender Keys)
34
+ 1. Each user generates a sender key per channel
35
+ 2. Sender Key Distribution Messages (SKDMs) are encrypted to each member's identity key via `crypto_box_seal`
36
+ 3. Wire format: `[0x03][distributionId:16][chainIndex:4 LE][nonce:24][ciphertext+tag]`
37
+
38
+ ### Files
39
+ - XChaCha20-Poly1305 client-side encryption
40
+ - Key and nonce embedded in the message payload (encrypted along with the message)
41
+ - File sizes are padded to prevent type inference
42
+
43
+ ## Building
44
+
45
+ ```bash
46
+ npm install
47
+ npm run build # Compiles to dist/ via tsc
48
+ ```
49
+
50
+ **Important**: The web frontend imports from `dist/`, not `src/`. You must rebuild haven-core after any changes for the frontend to pick them up.
51
+
52
+ ## Testing
53
+
54
+ ```bash
55
+ npx vitest run # 78 tests
56
+ ```
57
+
58
+ ## API Client
59
+
60
+ The `HavenApi` class handles JWT token management, automatic PoW solving for registration, and typed request/response for every endpoint:
61
+
62
+ ```typescript
63
+ import { HavenApi } from '@haven/core';
64
+
65
+ const api = new HavenApi({ baseUrl: 'https://chat.example.com' });
66
+ await api.register({ username, password, ...keys });
67
+ await api.login({ username, password });
68
+ const servers = await api.listServers();
69
+ ```
70
+
71
+ Covers all Haven API areas: auth, servers, channels, messages, sender keys, roles, friends, invites, voice (join/leave/participants/mute/deafen), attachments (upload/download), link previews, admin, and user management.
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Encrypted key backup — derives a symmetric key from a security phrase
3
+ * using Argon2id, then encrypts all crypto state with XSalsa20-Poly1305.
4
+ */
5
+ export interface BackupEncryptResult {
6
+ encrypted: Uint8Array;
7
+ nonce: Uint8Array;
8
+ salt: Uint8Array;
9
+ }
10
+ /**
11
+ * The plaintext JSON structure that gets encrypted into the backup blob.
12
+ */
13
+ export interface BackupPayload {
14
+ version: 1;
15
+ identity: {
16
+ publicKey: string;
17
+ privateKey: string;
18
+ };
19
+ signedPreKey: {
20
+ publicKey: string;
21
+ privateKey: string;
22
+ signature: string;
23
+ };
24
+ sessions: Record<string, {
25
+ state: SerializedSessionState;
26
+ ad: string;
27
+ }>;
28
+ mySenderKeys: Record<string, {
29
+ distributionId: string;
30
+ chainKey: string;
31
+ chainIndex: number;
32
+ }>;
33
+ receivedSenderKeys: Record<string, {
34
+ fromUserId: string;
35
+ key: {
36
+ distributionId: string;
37
+ chainKey: string;
38
+ chainIndex: number;
39
+ };
40
+ }>;
41
+ distributedChannels: string[];
42
+ channelPeerMap: Record<string, string>;
43
+ timestamp: string;
44
+ }
45
+ /**
46
+ * SessionState with Uint8Arrays serialized to base64 strings for JSON safety.
47
+ */
48
+ export interface SerializedSessionState {
49
+ dhSend: {
50
+ publicKey: string;
51
+ privateKey: string;
52
+ };
53
+ dhRecv: string | null;
54
+ rootKey: string;
55
+ chainKeySend: string | null;
56
+ chainKeyRecv: string | null;
57
+ sendCount: number;
58
+ recvCount: number;
59
+ prevSendCount: number;
60
+ skippedKeys: Array<{
61
+ dhPub: string;
62
+ n: number;
63
+ mk: string;
64
+ }>;
65
+ }
66
+ /**
67
+ * Derive a 32-byte symmetric key from a security phrase using Argon2id.
68
+ */
69
+ export declare function deriveBackupKey(securityPhrase: string, salt: Uint8Array): Uint8Array;
70
+ /**
71
+ * Encrypt a backup payload with a security phrase.
72
+ * Generates a fresh salt and nonce.
73
+ */
74
+ export declare function encryptBackup(plaintext: Uint8Array, securityPhrase: string): BackupEncryptResult;
75
+ /**
76
+ * Decrypt a backup payload using a security phrase, salt, and nonce.
77
+ * Throws if the phrase is wrong (authentication failure).
78
+ */
79
+ export declare function decryptBackup(encrypted: Uint8Array, nonce: Uint8Array, salt: Uint8Array, securityPhrase: string): Uint8Array;
80
+ /**
81
+ * Generate a cryptographically random recovery key as a human-readable
82
+ * string (groups of 4 base32 characters separated by dashes).
83
+ *
84
+ * Example: "ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ23-4567"
85
+ * 20 random bytes = 160 bits of entropy.
86
+ */
87
+ export declare function generateRecoveryKey(): string;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Encrypted key backup — derives a symmetric key from a security phrase
3
+ * using Argon2id, then encrypts all crypto state with XSalsa20-Poly1305.
4
+ */
5
+ import { getSodium, randomBytes } from "./utils.js";
6
+ // Argon2id parameters — OWASP recommended for interactive hashing
7
+ const ARGON2_OPSLIMIT = 3; // crypto_pwhash_OPSLIMIT_MODERATE
8
+ const ARGON2_MEMLIMIT = 67108864; // 64 MB — safe for browsers (256MB can OOM on mobile)
9
+ const SALT_BYTES = 16; // crypto_pwhash_SALTBYTES
10
+ /**
11
+ * Derive a 32-byte symmetric key from a security phrase using Argon2id.
12
+ */
13
+ export function deriveBackupKey(securityPhrase, salt) {
14
+ const sodium = getSodium();
15
+ return sodium.crypto_pwhash(sodium.crypto_secretbox_KEYBYTES, // 32
16
+ securityPhrase, salt, ARGON2_OPSLIMIT, ARGON2_MEMLIMIT, sodium.crypto_pwhash_ALG_ARGON2ID13);
17
+ }
18
+ /**
19
+ * Encrypt a backup payload with a security phrase.
20
+ * Generates a fresh salt and nonce.
21
+ */
22
+ export function encryptBackup(plaintext, securityPhrase) {
23
+ const sodium = getSodium();
24
+ const salt = randomBytes(SALT_BYTES);
25
+ const key = deriveBackupKey(securityPhrase, salt);
26
+ const nonce = randomBytes(sodium.crypto_secretbox_NONCEBYTES); // 24 bytes
27
+ const encrypted = sodium.crypto_secretbox_easy(plaintext, nonce, key);
28
+ return { encrypted, nonce, salt };
29
+ }
30
+ /**
31
+ * Decrypt a backup payload using a security phrase, salt, and nonce.
32
+ * Throws if the phrase is wrong (authentication failure).
33
+ */
34
+ export function decryptBackup(encrypted, nonce, salt, securityPhrase) {
35
+ const sodium = getSodium();
36
+ const key = deriveBackupKey(securityPhrase, salt);
37
+ return sodium.crypto_secretbox_open_easy(encrypted, nonce, key);
38
+ }
39
+ /**
40
+ * Generate a cryptographically random recovery key as a human-readable
41
+ * string (groups of 4 base32 characters separated by dashes).
42
+ *
43
+ * Example: "ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ23-4567"
44
+ * 20 random bytes = 160 bits of entropy.
45
+ */
46
+ export function generateRecoveryKey() {
47
+ const bytes = randomBytes(20);
48
+ // Encode as base32 for human readability
49
+ const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
50
+ let bits = "";
51
+ for (const b of bytes) {
52
+ bits += b.toString(2).padStart(8, "0");
53
+ }
54
+ let encoded = "";
55
+ for (let i = 0; i + 5 <= bits.length; i += 5) {
56
+ encoded += base32Chars[parseInt(bits.slice(i, i + 5), 2)];
57
+ }
58
+ // Format into groups of 4
59
+ const groups = encoded.match(/.{1,4}/g) || [];
60
+ return groups.join("-");
61
+ }
62
+ //# sourceMappingURL=backup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/crypto/backup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAwB,WAAW,EAAE,MAAM,YAAY,CAAC;AAE1E,kEAAkE;AAClE,MAAM,eAAe,GAAG,CAAC,CAAC,CAAO,kCAAkC;AACnE,MAAM,eAAe,GAAG,QAAQ,CAAC,CAAC,sDAAsD;AACxF,MAAM,UAAU,GAAG,EAAE,CAAC,CAAY,0BAA0B;AA2D5D;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,cAAsB,EACtB,IAAgB;IAEhB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,aAAa,CACzB,MAAM,CAAC,yBAAyB,EAAE,KAAK;IACvC,cAAc,EACd,IAAI,EACJ,eAAe,EACf,eAAe,EACf,MAAM,CAAC,4BAA4B,CACpC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,SAAqB,EACrB,cAAsB;IAEtB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,eAAe,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC,CAAC,WAAW;IAC1E,MAAM,SAAS,GAAG,MAAM,CAAC,qBAAqB,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACtE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,SAAqB,EACrB,KAAiB,EACjB,IAAgB,EAChB,cAAsB;IAEtB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,eAAe,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC,0BAA0B,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,yCAAyC;IACzC,MAAM,WAAW,GAAG,kCAAkC,CAAC;IACvD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,0BAA0B;IAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAC9C,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Signal Protocol Double Ratchet implementation.
3
+ *
4
+ * Provides forward secrecy and break-in recovery for 1-on-1 DM sessions.
5
+ * After X3DH establishes a shared secret, this module handles all subsequent
6
+ * message encryption and decryption.
7
+ *
8
+ * References:
9
+ * - https://signal.org/docs/specifications/doubleratchet/
10
+ * - https://signal.org/docs/specifications/x3dh/
11
+ */
12
+ import { type DHKeyPair } from "./keys.js";
13
+ /** Message header sent in the clear (alongside ciphertext). */
14
+ export interface MessageHeader {
15
+ dhPublicKey: Uint8Array;
16
+ pn: number;
17
+ n: number;
18
+ }
19
+ /** An encrypted Double Ratchet message ready for the wire. */
20
+ export interface EncryptedMessage {
21
+ header: MessageHeader;
22
+ nonce: Uint8Array;
23
+ ciphertext: Uint8Array;
24
+ }
25
+ /** Serializable session state for persistence. */
26
+ export interface SessionState {
27
+ dhSend: {
28
+ publicKey: Uint8Array;
29
+ privateKey: Uint8Array;
30
+ };
31
+ dhRecv: Uint8Array | null;
32
+ rootKey: Uint8Array;
33
+ chainKeySend: Uint8Array | null;
34
+ chainKeyRecv: Uint8Array | null;
35
+ sendCount: number;
36
+ recvCount: number;
37
+ prevSendCount: number;
38
+ skippedKeys: Array<{
39
+ dhPub: Uint8Array;
40
+ n: number;
41
+ mk: Uint8Array;
42
+ }>;
43
+ }
44
+ export declare class DoubleRatchetSession {
45
+ private dhSend;
46
+ private dhRecv;
47
+ private rootKey;
48
+ private chainKeySend;
49
+ private chainKeyRecv;
50
+ private sendCount;
51
+ private recvCount;
52
+ private prevSendCount;
53
+ private skippedKeys;
54
+ private associatedData;
55
+ private constructor();
56
+ /**
57
+ * Initialize as the INITIATOR (Alice).
58
+ * Called after X3DH produces a shared key.
59
+ *
60
+ * @param sharedKey The SK from X3DH (32 bytes)
61
+ * @param associatedData AD = IK_A || IK_B (64 bytes)
62
+ * @param bobSignedPreKeyPub Bob's signed prekey public key (the one from X3DH)
63
+ */
64
+ static initAlice(sharedKey: Uint8Array, associatedData: Uint8Array, bobSignedPreKeyPub: Uint8Array): DoubleRatchetSession;
65
+ /**
66
+ * Initialize as the RESPONDER (Bob).
67
+ * Called after X3DH when Bob receives Alice's initial message.
68
+ *
69
+ * @param sharedKey The SK from X3DH (32 bytes)
70
+ * @param associatedData AD = IK_A || IK_B (64 bytes)
71
+ * @param bobSignedPreKeyPair Bob's signed prekey pair (used in X3DH)
72
+ */
73
+ static initBob(sharedKey: Uint8Array, associatedData: Uint8Array, bobSignedPreKeyPair: DHKeyPair): DoubleRatchetSession;
74
+ /**
75
+ * Encrypt a plaintext message.
76
+ */
77
+ encrypt(plaintext: Uint8Array): EncryptedMessage;
78
+ /**
79
+ * Decrypt a received message.
80
+ */
81
+ decrypt(message: EncryptedMessage): Uint8Array;
82
+ private dhRatchet;
83
+ private skipKeys;
84
+ private trySkippedKeys;
85
+ private aedEncrypt;
86
+ private aedDecrypt;
87
+ /**
88
+ * Export session state for persistent storage.
89
+ */
90
+ serialize(): SessionState;
91
+ /**
92
+ * Restore a session from serialized state.
93
+ */
94
+ static deserialize(state: SessionState, associatedData: Uint8Array): DoubleRatchetSession;
95
+ }
96
+ /**
97
+ * Serialize an encrypted message to bytes for transmission.
98
+ * Format: [dh_pub:32][pn:4 LE][n:4 LE][nonce:24][ciphertext:rest]
99
+ */
100
+ export declare function serializeMessage(msg: EncryptedMessage): Uint8Array;
101
+ /**
102
+ * Deserialize bytes back into an encrypted message.
103
+ */
104
+ export declare function deserializeMessage(buf: Uint8Array): EncryptedMessage;
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Signal Protocol Double Ratchet implementation.
3
+ *
4
+ * Provides forward secrecy and break-in recovery for 1-on-1 DM sessions.
5
+ * After X3DH establishes a shared secret, this module handles all subsequent
6
+ * message encryption and decryption.
7
+ *
8
+ * References:
9
+ * - https://signal.org/docs/specifications/doubleratchet/
10
+ * - https://signal.org/docs/specifications/x3dh/
11
+ */
12
+ import { dh, generateDHKeyPair } from "./keys.js";
13
+ import { getSodium, kdfRK, kdfCK, randomBytes } from "./utils.js";
14
+ // ─── Constants ─────────────────────────────────────────
15
+ const MAX_SKIP = 256;
16
+ const NONCE_LEN = 24; // XChaCha20-Poly1305
17
+ const TAG_LEN = 16; // Poly1305 auth tag
18
+ // ─── Session ───────────────────────────────────────────
19
+ export class DoubleRatchetSession {
20
+ dhSend;
21
+ dhRecv;
22
+ rootKey;
23
+ chainKeySend;
24
+ chainKeyRecv;
25
+ sendCount;
26
+ recvCount;
27
+ prevSendCount;
28
+ skippedKeys; // "hex(dhPub):n" -> messageKey
29
+ associatedData;
30
+ constructor(ad) {
31
+ this.dhSend = { publicKey: new Uint8Array(32), privateKey: new Uint8Array(32) };
32
+ this.dhRecv = null;
33
+ this.rootKey = new Uint8Array(32);
34
+ this.chainKeySend = null;
35
+ this.chainKeyRecv = null;
36
+ this.sendCount = 0;
37
+ this.recvCount = 0;
38
+ this.prevSendCount = 0;
39
+ this.skippedKeys = new Map();
40
+ this.associatedData = ad;
41
+ }
42
+ /**
43
+ * Initialize as the INITIATOR (Alice).
44
+ * Called after X3DH produces a shared key.
45
+ *
46
+ * @param sharedKey The SK from X3DH (32 bytes)
47
+ * @param associatedData AD = IK_A || IK_B (64 bytes)
48
+ * @param bobSignedPreKeyPub Bob's signed prekey public key (the one from X3DH)
49
+ */
50
+ static initAlice(sharedKey, associatedData, bobSignedPreKeyPub) {
51
+ const session = new DoubleRatchetSession(associatedData);
52
+ session.dhSend = generateDHKeyPair();
53
+ session.dhRecv = bobSignedPreKeyPub;
54
+ // First ratchet step: derive initial sending chain key
55
+ const [rk, cks] = kdfRK(sharedKey, dh(session.dhSend.privateKey, session.dhRecv));
56
+ session.rootKey = rk;
57
+ session.chainKeySend = cks;
58
+ return session;
59
+ }
60
+ /**
61
+ * Initialize as the RESPONDER (Bob).
62
+ * Called after X3DH when Bob receives Alice's initial message.
63
+ *
64
+ * @param sharedKey The SK from X3DH (32 bytes)
65
+ * @param associatedData AD = IK_A || IK_B (64 bytes)
66
+ * @param bobSignedPreKeyPair Bob's signed prekey pair (used in X3DH)
67
+ */
68
+ static initBob(sharedKey, associatedData, bobSignedPreKeyPair) {
69
+ const session = new DoubleRatchetSession(associatedData);
70
+ session.dhSend = bobSignedPreKeyPair;
71
+ session.rootKey = sharedKey;
72
+ // CKs and CKr are null — will be derived on first message
73
+ return session;
74
+ }
75
+ // ─── Encrypt ───────────────────────────────────────
76
+ /**
77
+ * Encrypt a plaintext message.
78
+ */
79
+ encrypt(plaintext) {
80
+ if (!this.chainKeySend) {
81
+ throw new Error("Sending chain not initialized");
82
+ }
83
+ const [newCK, mk] = kdfCK(this.chainKeySend);
84
+ this.chainKeySend = newCK;
85
+ const header = {
86
+ dhPublicKey: this.dhSend.publicKey,
87
+ pn: this.prevSendCount,
88
+ n: this.sendCount,
89
+ };
90
+ this.sendCount++;
91
+ const { nonce, ciphertext } = this.aedEncrypt(mk, plaintext, header);
92
+ return { header, nonce, ciphertext };
93
+ }
94
+ // ─── Decrypt ───────────────────────────────────────
95
+ /**
96
+ * Decrypt a received message.
97
+ */
98
+ decrypt(message) {
99
+ // Try skipped message keys first (out-of-order delivery)
100
+ const skippedResult = this.trySkippedKeys(message);
101
+ if (skippedResult)
102
+ return skippedResult;
103
+ // Check if we need a DH ratchet step (new DH key from sender)
104
+ const needsRatchet = !this.dhRecv || !uint8Eq(message.header.dhPublicKey, this.dhRecv);
105
+ if (needsRatchet) {
106
+ this.skipKeys(message.header.pn);
107
+ this.dhRatchet(message.header.dhPublicKey);
108
+ }
109
+ this.skipKeys(message.header.n);
110
+ if (!this.chainKeyRecv) {
111
+ throw new Error("Receiving chain not initialized");
112
+ }
113
+ const [newCK, mk] = kdfCK(this.chainKeyRecv);
114
+ this.chainKeyRecv = newCK;
115
+ this.recvCount++;
116
+ return this.aedDecrypt(mk, message.ciphertext, message.nonce, message.header);
117
+ }
118
+ // ─── DH Ratchet Step ──────────────────────────────
119
+ dhRatchet(newDhPub) {
120
+ this.prevSendCount = this.sendCount;
121
+ this.sendCount = 0;
122
+ this.recvCount = 0;
123
+ this.dhRecv = newDhPub;
124
+ // Derive new receiving chain
125
+ const [rk1, ckr] = kdfRK(this.rootKey, dh(this.dhSend.privateKey, this.dhRecv));
126
+ this.rootKey = rk1;
127
+ this.chainKeyRecv = ckr;
128
+ // Generate new DH keypair and derive new sending chain
129
+ this.dhSend = generateDHKeyPair();
130
+ const [rk2, cks] = kdfRK(this.rootKey, dh(this.dhSend.privateKey, this.dhRecv));
131
+ this.rootKey = rk2;
132
+ this.chainKeySend = cks;
133
+ }
134
+ // ─── Skipped Keys ─────────────────────────────────
135
+ skipKeys(until) {
136
+ if (!this.chainKeyRecv)
137
+ return;
138
+ if (until - this.recvCount > MAX_SKIP) {
139
+ throw new Error("Too many skipped messages");
140
+ }
141
+ while (this.recvCount < until) {
142
+ const [newCK, mk] = kdfCK(this.chainKeyRecv);
143
+ this.chainKeyRecv = newCK;
144
+ const key = skippedKeyId(this.dhRecv, this.recvCount);
145
+ this.skippedKeys.set(key, mk);
146
+ this.recvCount++;
147
+ }
148
+ }
149
+ trySkippedKeys(message) {
150
+ const key = skippedKeyId(message.header.dhPublicKey, message.header.n);
151
+ const mk = this.skippedKeys.get(key);
152
+ if (!mk)
153
+ return null;
154
+ this.skippedKeys.delete(key);
155
+ return this.aedDecrypt(mk, message.ciphertext, message.nonce, message.header);
156
+ }
157
+ // ─── AEAD Encryption ──────────────────────────────
158
+ aedEncrypt(mk, plaintext, header) {
159
+ const nonce = randomBytes(NONCE_LEN);
160
+ const ad = buildAD(this.associatedData, header);
161
+ const ciphertext = getSodium().crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, ad, null, // nsec (unused in this AEAD)
162
+ nonce, mk);
163
+ return { nonce, ciphertext };
164
+ }
165
+ aedDecrypt(mk, ciphertext, nonce, header) {
166
+ const ad = buildAD(this.associatedData, header);
167
+ return getSodium().crypto_aead_xchacha20poly1305_ietf_decrypt(null, // nsec
168
+ ciphertext, ad, nonce, mk);
169
+ }
170
+ // ─── Serialization ────────────────────────────────
171
+ /**
172
+ * Export session state for persistent storage.
173
+ */
174
+ serialize() {
175
+ return {
176
+ dhSend: { publicKey: this.dhSend.publicKey, privateKey: this.dhSend.privateKey },
177
+ dhRecv: this.dhRecv,
178
+ rootKey: this.rootKey,
179
+ chainKeySend: this.chainKeySend,
180
+ chainKeyRecv: this.chainKeyRecv,
181
+ sendCount: this.sendCount,
182
+ recvCount: this.recvCount,
183
+ prevSendCount: this.prevSendCount,
184
+ skippedKeys: Array.from(this.skippedKeys.entries()).map(([id, mk]) => {
185
+ const [hexPub, nStr] = id.split(":");
186
+ return { dhPub: hexToBytes(hexPub), n: parseInt(nStr, 10), mk };
187
+ }),
188
+ };
189
+ }
190
+ /**
191
+ * Restore a session from serialized state.
192
+ */
193
+ static deserialize(state, associatedData) {
194
+ const session = new DoubleRatchetSession(associatedData);
195
+ session.dhSend = state.dhSend;
196
+ session.dhRecv = state.dhRecv;
197
+ session.rootKey = state.rootKey;
198
+ session.chainKeySend = state.chainKeySend;
199
+ session.chainKeyRecv = state.chainKeyRecv;
200
+ session.sendCount = state.sendCount;
201
+ session.recvCount = state.recvCount;
202
+ session.prevSendCount = state.prevSendCount;
203
+ for (const { dhPub, n, mk } of state.skippedKeys) {
204
+ session.skippedKeys.set(skippedKeyId(dhPub, n), mk);
205
+ }
206
+ return session;
207
+ }
208
+ }
209
+ // ─── Wire Format ───────────────────────────────────────
210
+ /**
211
+ * Serialize an encrypted message to bytes for transmission.
212
+ * Format: [dh_pub:32][pn:4 LE][n:4 LE][nonce:24][ciphertext:rest]
213
+ */
214
+ export function serializeMessage(msg) {
215
+ const buf = new Uint8Array(32 + 4 + 4 + NONCE_LEN + msg.ciphertext.length);
216
+ const view = new DataView(buf.buffer);
217
+ buf.set(msg.header.dhPublicKey, 0);
218
+ view.setUint32(32, msg.header.pn, true);
219
+ view.setUint32(36, msg.header.n, true);
220
+ buf.set(msg.nonce, 40);
221
+ buf.set(msg.ciphertext, 40 + NONCE_LEN);
222
+ return buf;
223
+ }
224
+ /**
225
+ * Deserialize bytes back into an encrypted message.
226
+ */
227
+ export function deserializeMessage(buf) {
228
+ if (buf.length < 40 + NONCE_LEN + TAG_LEN) {
229
+ throw new Error("Message too short");
230
+ }
231
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
232
+ return {
233
+ header: {
234
+ dhPublicKey: buf.slice(0, 32),
235
+ pn: view.getUint32(32, true),
236
+ n: view.getUint32(36, true),
237
+ },
238
+ nonce: buf.slice(40, 40 + NONCE_LEN),
239
+ ciphertext: buf.slice(40 + NONCE_LEN),
240
+ };
241
+ }
242
+ // ─── Helpers ───────────────────────────────────────────
243
+ function buildAD(sessionAD, header) {
244
+ const ad = new Uint8Array(sessionAD.length + 32 + 4 + 4);
245
+ const view = new DataView(ad.buffer);
246
+ ad.set(sessionAD, 0);
247
+ ad.set(header.dhPublicKey, sessionAD.length);
248
+ view.setUint32(sessionAD.length + 32, header.pn, true);
249
+ view.setUint32(sessionAD.length + 36, header.n, true);
250
+ return ad;
251
+ }
252
+ function skippedKeyId(dhPub, n) {
253
+ return `${bytesToHex(dhPub)}:${n}`;
254
+ }
255
+ function uint8Eq(a, b) {
256
+ if (a.length !== b.length)
257
+ return false;
258
+ for (let i = 0; i < a.length; i++) {
259
+ if (a[i] !== b[i])
260
+ return false;
261
+ }
262
+ return true;
263
+ }
264
+ function bytesToHex(bytes) {
265
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
266
+ }
267
+ function hexToBytes(hex) {
268
+ const bytes = new Uint8Array(hex.length / 2);
269
+ for (let i = 0; i < bytes.length; i++) {
270
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
271
+ }
272
+ return bytes;
273
+ }
274
+ //# sourceMappingURL=double-ratchet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"double-ratchet.js","sourceRoot":"","sources":["../../src/crypto/double-ratchet.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface EncryptedFile {
2
+ encrypted: Uint8Array;
3
+ key: Uint8Array;
4
+ nonce: Uint8Array;
5
+ }
6
+ /**
7
+ * Encrypt a file using XSalsa20-Poly1305 (crypto_secretbox).
8
+ * Generates a random key and nonce.
9
+ */
10
+ export declare function encryptFile(plaintext: Uint8Array): EncryptedFile;
11
+ /**
12
+ * Decrypt a file encrypted with encryptFile.
13
+ */
14
+ export declare function decryptFile(encrypted: Uint8Array, key: Uint8Array, nonce: Uint8Array): Uint8Array;
@@ -0,0 +1,20 @@
1
+ import { getSodium, randomBytes } from "./utils.js";
2
+ /**
3
+ * Encrypt a file using XSalsa20-Poly1305 (crypto_secretbox).
4
+ * Generates a random key and nonce.
5
+ */
6
+ export function encryptFile(plaintext) {
7
+ const sodium = getSodium();
8
+ const key = randomBytes(sodium.crypto_secretbox_KEYBYTES);
9
+ const nonce = randomBytes(sodium.crypto_secretbox_NONCEBYTES);
10
+ const encrypted = sodium.crypto_secretbox_easy(plaintext, nonce, key);
11
+ return { encrypted, key, nonce };
12
+ }
13
+ /**
14
+ * Decrypt a file encrypted with encryptFile.
15
+ */
16
+ export function decryptFile(encrypted, key, nonce) {
17
+ const sodium = getSodium();
18
+ return sodium.crypto_secretbox_open_easy(encrypted, nonce, key);
19
+ }
20
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/crypto/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQpD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,SAAqB;IAC/C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,CAAC,yBAAyB,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,MAAM,CAAC,qBAAqB,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACtE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,SAAqB,EAAE,GAAe,EAAE,KAAiB;IACnF,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,0BAA0B,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { initSodium, getSodium, toBase64, fromBase64, randomBytes } from "./utils.js";
2
+ export { encryptFile, decryptFile, type EncryptedFile } from "./file.js";
3
+ export { type IdentityKeyPair, type DHKeyPair, type SignedPreKey, generateIdentityKeyPair, generateDHKeyPair, generateSignedPreKey, generateOneTimePreKeys, prepareRegistrationKeys, verifySignature, } from "./keys.js";
4
+ export { type X3DHResult, x3dhInitiate, x3dhRespond } from "./x3dh.js";
5
+ export { DoubleRatchetSession, type EncryptedMessage, type MessageHeader, type SessionState, serializeMessage, deserializeMessage, } from "./double-ratchet.js";
6
+ export { type SenderKeyState, type ReceivedSenderKey, type SenderKeyDistributionPayload, GROUP_MSG_TYPE, generateSenderKey, createSkdmPayload, parseSkdmPayload, encryptSkdm, decryptSkdm, senderKeyEncrypt, senderKeyDecrypt, } from "./sender-keys.js";
7
+ export { generateProfileKey, encryptProfile, decryptProfile, encryptProfileKeyFor, decryptProfileKey, encryptProfileToBase64, decryptProfileFromBase64, } from "./profile.js";
8
+ export { encryptBackup, decryptBackup, deriveBackupKey, generateRecoveryKey, type BackupEncryptResult, type BackupPayload, type SerializedSessionState, } from "./backup.js";
9
+ export { generatePassphrase } from "./passphrase.js";