@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
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Signal-style Sender Keys protocol for group E2EE.
3
+ *
4
+ * Each member generates a sender key per channel. Messages are encrypted O(1)
5
+ * using a symmetric chain ratchet. Distribution uses crypto_box_seal to each
6
+ * recipient's X25519 identity key.
7
+ *
8
+ * Wire format (type 0x03):
9
+ * [0x03][distributionId:16][chainIndex:4 LE][nonce:24][ciphertext+tag]
10
+ * Overhead: 45 bytes + ciphertext
11
+ *
12
+ * References:
13
+ * - https://signal.org/docs/specifications/group-v2/
14
+ */
15
+ import { getSodium, kdfCK, randomBytes } from "./utils.js";
16
+ import { ed25519PkToX25519, ed25519SkToX25519 } from "./keys.js";
17
+ // ─── Constants ─────────────────────────────────────────
18
+ const NONCE_LEN = 24; // XChaCha20-Poly1305
19
+ const UUID_LEN = 16; // UUID as bytes
20
+ const CHAIN_IDX_LEN = 4; // uint32 LE
21
+ const MAX_SKIP = 256;
22
+ export const GROUP_MSG_TYPE = 0x03;
23
+ // ─── Sender Key Generation ─────────────────────────────
24
+ /**
25
+ * Generate a fresh sender key for a channel.
26
+ * Called when first joining a channel or after a re-key event.
27
+ */
28
+ export function generateSenderKey() {
29
+ return {
30
+ distributionId: randomBytes(UUID_LEN),
31
+ chainKey: randomBytes(32),
32
+ chainIndex: 0,
33
+ };
34
+ }
35
+ // ─── SKDM Payload Serialization ────────────────────────
36
+ /**
37
+ * Serialize an SKDM payload for distribution.
38
+ * Format: [distributionId:16][chainIndex:4 LE][chainKey:32] = 52 bytes
39
+ */
40
+ export function createSkdmPayload(state) {
41
+ const buf = new Uint8Array(16 + 4 + 32);
42
+ const view = new DataView(buf.buffer);
43
+ buf.set(state.distributionId, 0);
44
+ view.setUint32(16, state.chainIndex, true);
45
+ buf.set(state.chainKey, 20);
46
+ return buf;
47
+ }
48
+ /**
49
+ * Parse an SKDM payload back into structured data.
50
+ */
51
+ export function parseSkdmPayload(buf) {
52
+ if (buf.length < 52)
53
+ throw new Error("SKDM payload too short");
54
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
55
+ return {
56
+ distributionId: buf.slice(0, 16),
57
+ chainIndex: view.getUint32(16, true),
58
+ chainKey: buf.slice(20, 52),
59
+ };
60
+ }
61
+ // ─── SKDM Encryption / Decryption ─────────────────────
62
+ /**
63
+ * Encrypt an SKDM for a single recipient using crypto_box_seal.
64
+ * The recipient's Ed25519 identity key is converted to X25519 for sealing.
65
+ */
66
+ export function encryptSkdm(skdmPayload, recipientIdentityKeyEd25519) {
67
+ const sodium = getSodium();
68
+ const recipientX25519 = ed25519PkToX25519(recipientIdentityKeyEd25519);
69
+ return sodium.crypto_box_seal(skdmPayload, recipientX25519);
70
+ }
71
+ /**
72
+ * Decrypt an SKDM received for us.
73
+ */
74
+ export function decryptSkdm(encrypted, ourIdentityKeyPair) {
75
+ const sodium = getSodium();
76
+ const ourX25519Pk = ed25519PkToX25519(ourIdentityKeyPair.publicKey);
77
+ const ourX25519Sk = ed25519SkToX25519(ourIdentityKeyPair.privateKey);
78
+ return sodium.crypto_box_seal_open(encrypted, ourX25519Pk, ourX25519Sk);
79
+ }
80
+ // ─── Group Message Encryption ──────────────────────────
81
+ /**
82
+ * Encrypt a message using the sender's key for a channel.
83
+ * Ratchets the chain forward. Returns the full wire-format bytes.
84
+ *
85
+ * Wire format:
86
+ * [0x03][distributionId:16][chainIndex:4 LE][nonce:24][ciphertext+tag]
87
+ *
88
+ * @param senderKey The sender's key state (mutated: chain ratchets forward)
89
+ * @param plaintext The message plaintext bytes
90
+ */
91
+ export function senderKeyEncrypt(senderKey, plaintext) {
92
+ const sodium = getSodium();
93
+ // Ratchet the chain
94
+ const [newChainKey, messageKey] = kdfCK(senderKey.chainKey);
95
+ const currentIndex = senderKey.chainIndex;
96
+ // Update sender state
97
+ senderKey.chainKey = newChainKey;
98
+ senderKey.chainIndex = currentIndex + 1;
99
+ // Encrypt with AAD bound to the distribution ID
100
+ const nonce = randomBytes(NONCE_LEN);
101
+ const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, senderKey.distributionId, // AAD
102
+ null, nonce, messageKey);
103
+ // Build wire format
104
+ const headerLen = 1 + UUID_LEN + CHAIN_IDX_LEN + NONCE_LEN;
105
+ const buf = new Uint8Array(headerLen + ciphertext.length);
106
+ const view = new DataView(buf.buffer);
107
+ buf[0] = GROUP_MSG_TYPE;
108
+ buf.set(senderKey.distributionId, 1);
109
+ view.setUint32(1 + UUID_LEN, currentIndex, true);
110
+ buf.set(nonce, 1 + UUID_LEN + CHAIN_IDX_LEN);
111
+ buf.set(ciphertext, headerLen);
112
+ return buf;
113
+ }
114
+ /**
115
+ * Decrypt a group message using a received sender key.
116
+ * Ratchets the received key forward to match the message's chain index.
117
+ *
118
+ * @param wireBytes Full wire-format bytes (including type byte 0x03)
119
+ * @param receivedKey The sender's key (mutated: ratchets forward)
120
+ */
121
+ export function senderKeyDecrypt(wireBytes, receivedKey) {
122
+ const sodium = getSodium();
123
+ if (wireBytes[0] !== GROUP_MSG_TYPE) {
124
+ throw new Error(`Expected group message type 0x03, got 0x${wireBytes[0].toString(16)}`);
125
+ }
126
+ const view = new DataView(wireBytes.buffer, wireBytes.byteOffset, wireBytes.byteLength);
127
+ const distributionId = wireBytes.slice(1, 1 + UUID_LEN);
128
+ const chainIndex = view.getUint32(1 + UUID_LEN, true);
129
+ const nonce = wireBytes.slice(1 + UUID_LEN + CHAIN_IDX_LEN, 1 + UUID_LEN + CHAIN_IDX_LEN + NONCE_LEN);
130
+ const ciphertext = wireBytes.slice(1 + UUID_LEN + CHAIN_IDX_LEN + NONCE_LEN);
131
+ // Verify distribution ID matches
132
+ if (!uint8Eq(distributionId, receivedKey.distributionId)) {
133
+ throw new Error("Distribution ID mismatch");
134
+ }
135
+ // Advance the chain to the required index
136
+ const stepsNeeded = chainIndex - receivedKey.chainIndex;
137
+ if (stepsNeeded < 0) {
138
+ throw new Error("Message chain index is before our current state (already consumed)");
139
+ }
140
+ if (stepsNeeded > MAX_SKIP) {
141
+ throw new Error(`Too many skipped messages: ${stepsNeeded}`);
142
+ }
143
+ // Ratchet forward to derive the correct message key
144
+ let currentChainKey = receivedKey.chainKey;
145
+ let messageKey = new Uint8Array(32);
146
+ for (let i = 0; i <= stepsNeeded; i++) {
147
+ const [newCK, mk] = kdfCK(currentChainKey);
148
+ if (i === stepsNeeded) {
149
+ messageKey = mk;
150
+ // Update received key to one past the message we just decrypted
151
+ receivedKey.chainKey = newCK;
152
+ receivedKey.chainIndex = chainIndex + 1;
153
+ }
154
+ currentChainKey = newCK;
155
+ }
156
+ // Decrypt
157
+ return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ciphertext, distributionId, // AAD
158
+ nonce, messageKey);
159
+ }
160
+ // ─── Helpers ───────────────────────────────────────────
161
+ function uint8Eq(a, b) {
162
+ if (a.length !== b.length)
163
+ return false;
164
+ for (let i = 0; i < a.length; i++) {
165
+ if (a[i] !== b[i])
166
+ return false;
167
+ }
168
+ return true;
169
+ }
170
+ //# sourceMappingURL=sender-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sender-keys.js","sourceRoot":"","sources":["../../src/crypto/sender-keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,272 @@
1
+ import { describe, it, expect, beforeAll } from "vitest";
2
+ import { initSodium, randomBytes } from "./utils.js";
3
+ import { generateIdentityKeyPair } from "./keys.js";
4
+ import { generateSenderKey, createSkdmPayload, parseSkdmPayload, encryptSkdm, decryptSkdm, senderKeyEncrypt, senderKeyDecrypt, GROUP_MSG_TYPE, } from "./sender-keys.js";
5
+ beforeAll(async () => {
6
+ await initSodium();
7
+ });
8
+ // ─── Helper ────────────────────────────────────────────
9
+ /** Deep-clone a SenderKeyState so mutations don't cross-contaminate. */
10
+ function cloneSenderKey(sk) {
11
+ return {
12
+ distributionId: new Uint8Array(sk.distributionId),
13
+ chainKey: new Uint8Array(sk.chainKey),
14
+ chainIndex: sk.chainIndex,
15
+ };
16
+ }
17
+ const plaintext = () => new TextEncoder().encode("hello group");
18
+ // ─── generateSenderKey ─────────────────────────────────
19
+ describe("generateSenderKey", () => {
20
+ it("returns a valid sender key with 16-byte distributionId, 32-byte chainKey, and index 0", () => {
21
+ const sk = generateSenderKey();
22
+ expect(sk.distributionId).toBeInstanceOf(Uint8Array);
23
+ expect(sk.distributionId.length).toBe(16);
24
+ expect(sk.chainKey).toBeInstanceOf(Uint8Array);
25
+ expect(sk.chainKey.length).toBe(32);
26
+ expect(sk.chainIndex).toBe(0);
27
+ });
28
+ it("generates unique keys on each call", () => {
29
+ const a = generateSenderKey();
30
+ const b = generateSenderKey();
31
+ expect(a.distributionId).not.toEqual(b.distributionId);
32
+ expect(a.chainKey).not.toEqual(b.chainKey);
33
+ });
34
+ });
35
+ // ─── SKDM payload serialization ────────────────────────
36
+ describe("SKDM payload round-trip", () => {
37
+ it("createSkdmPayload → parseSkdmPayload preserves all fields", () => {
38
+ const sk = generateSenderKey();
39
+ const buf = createSkdmPayload(sk);
40
+ expect(buf.length).toBe(52);
41
+ const parsed = parseSkdmPayload(buf);
42
+ expect(parsed.distributionId).toEqual(sk.distributionId);
43
+ expect(parsed.chainKey).toEqual(sk.chainKey);
44
+ expect(parsed.chainIndex).toBe(sk.chainIndex);
45
+ });
46
+ it("preserves non-zero chainIndex", () => {
47
+ const sk = generateSenderKey();
48
+ sk.chainIndex = 42;
49
+ const parsed = parseSkdmPayload(createSkdmPayload(sk));
50
+ expect(parsed.chainIndex).toBe(42);
51
+ });
52
+ it("rejects payloads shorter than 52 bytes", () => {
53
+ expect(() => parseSkdmPayload(new Uint8Array(51))).toThrow("too short");
54
+ });
55
+ });
56
+ // ─── SKDM encryption / decryption ──────────────────────
57
+ describe("SKDM encrypt / decrypt", () => {
58
+ it("round-trips through crypto_box_seal", () => {
59
+ const identity = generateIdentityKeyPair();
60
+ const sk = generateSenderKey();
61
+ const payload = createSkdmPayload(sk);
62
+ const encrypted = encryptSkdm(payload, identity.publicKey);
63
+ expect(encrypted.length).toBeGreaterThan(payload.length); // seal overhead
64
+ const decrypted = decryptSkdm(encrypted, identity);
65
+ const parsed = parseSkdmPayload(decrypted);
66
+ expect(parsed.distributionId).toEqual(sk.distributionId);
67
+ expect(parsed.chainKey).toEqual(sk.chainKey);
68
+ expect(parsed.chainIndex).toBe(sk.chainIndex);
69
+ });
70
+ it("fails to decrypt with a different identity key", () => {
71
+ const sender = generateIdentityKeyPair();
72
+ const stranger = generateIdentityKeyPair();
73
+ const sk = generateSenderKey();
74
+ const encrypted = encryptSkdm(createSkdmPayload(sk), sender.publicKey);
75
+ expect(() => decryptSkdm(encrypted, stranger)).toThrow();
76
+ });
77
+ });
78
+ // ─── Basic encrypt / decrypt ───────────────────────────
79
+ describe("senderKeyEncrypt / senderKeyDecrypt", () => {
80
+ it("encrypts and decrypts a single message", () => {
81
+ const sk = generateSenderKey();
82
+ const received = cloneSenderKey(sk);
83
+ const msg = plaintext();
84
+ const wire = senderKeyEncrypt(sk, msg);
85
+ expect(wire[0]).toBe(GROUP_MSG_TYPE);
86
+ const decrypted = senderKeyDecrypt(wire, received);
87
+ expect(decrypted).toEqual(msg);
88
+ });
89
+ it("encrypts and decrypts multiple sequential messages", () => {
90
+ const sk = generateSenderKey();
91
+ const received = cloneSenderKey(sk);
92
+ for (let i = 0; i < 10; i++) {
93
+ const msg = new TextEncoder().encode(`message ${i}`);
94
+ const wire = senderKeyEncrypt(sk, msg);
95
+ const dec = senderKeyDecrypt(wire, received);
96
+ expect(new TextDecoder().decode(dec)).toBe(`message ${i}`);
97
+ }
98
+ // After 10 messages, both sides should be at chainIndex 10
99
+ expect(sk.chainIndex).toBe(10);
100
+ expect(received.chainIndex).toBe(10);
101
+ });
102
+ it("advances chainIndex and chainKey on the sender after encryption", () => {
103
+ const sk = generateSenderKey();
104
+ const origChainKey = new Uint8Array(sk.chainKey);
105
+ expect(sk.chainIndex).toBe(0);
106
+ senderKeyEncrypt(sk, plaintext());
107
+ expect(sk.chainIndex).toBe(1);
108
+ expect(sk.chainKey).not.toEqual(origChainKey);
109
+ });
110
+ it("rejects a message with wrong distribution ID", () => {
111
+ const sk = generateSenderKey();
112
+ const wire = senderKeyEncrypt(sk, plaintext());
113
+ // Create a received key with a different distribution ID
114
+ const wrongReceived = {
115
+ distributionId: randomBytes(16),
116
+ chainKey: randomBytes(32),
117
+ chainIndex: 0,
118
+ };
119
+ expect(() => senderKeyDecrypt(wire, wrongReceived)).toThrow("Distribution ID mismatch");
120
+ });
121
+ it("rejects a message with wrong type byte", () => {
122
+ const sk = generateSenderKey();
123
+ const wire = senderKeyEncrypt(sk, plaintext());
124
+ wire[0] = 0x01; // DM type, not group
125
+ const received = cloneSenderKey(sk);
126
+ received.chainIndex = 0;
127
+ expect(() => senderKeyDecrypt(wire, received)).toThrow("Expected group message type");
128
+ });
129
+ });
130
+ // ─── Self-decryption (the critical bug fix) ────────────
131
+ describe("self-decryption: sender decrypts own messages via cloned key", () => {
132
+ it("decrypts own message using a clone made at generation time", () => {
133
+ const sk = generateSenderKey();
134
+ // Clone at generation time (chainIndex 0) — this is what the client does
135
+ const selfCopy = cloneSenderKey(sk);
136
+ const msg = new TextEncoder().encode("my own message");
137
+ const wire = senderKeyEncrypt(sk, msg);
138
+ // The self-copy should be able to decrypt by ratcheting forward
139
+ const decrypted = senderKeyDecrypt(wire, selfCopy);
140
+ expect(decrypted).toEqual(msg);
141
+ });
142
+ it("decrypts multiple own messages using the same clone", () => {
143
+ const sk = generateSenderKey();
144
+ const selfCopy = cloneSenderKey(sk);
145
+ const messages = ["first", "second", "third", "fourth", "fifth"];
146
+ const wires = messages.map((m) => senderKeyEncrypt(sk, new TextEncoder().encode(m)));
147
+ // Decrypt all in order using the self-copy
148
+ for (let i = 0; i < wires.length; i++) {
149
+ const dec = senderKeyDecrypt(wires[i], selfCopy);
150
+ expect(new TextDecoder().decode(dec)).toBe(messages[i]);
151
+ }
152
+ });
153
+ it("clone is independent — encrypting does not mutate the clone", () => {
154
+ const sk = generateSenderKey();
155
+ const selfCopy = cloneSenderKey(sk);
156
+ const origCloneChainKey = new Uint8Array(selfCopy.chainKey);
157
+ const origCloneIndex = selfCopy.chainIndex;
158
+ // Encrypt 5 messages — this should only mutate sk, not selfCopy
159
+ for (let i = 0; i < 5; i++) {
160
+ senderKeyEncrypt(sk, plaintext());
161
+ }
162
+ // Clone should be untouched
163
+ expect(selfCopy.chainKey).toEqual(origCloneChainKey);
164
+ expect(selfCopy.chainIndex).toBe(origCloneIndex);
165
+ });
166
+ it("self-copy can catch up after many skipped encryptions", () => {
167
+ const sk = generateSenderKey();
168
+ const selfCopy = cloneSenderKey(sk);
169
+ // Encrypt 100 messages but only try to decrypt the last one
170
+ let lastWire = new Uint8Array(0);
171
+ for (let i = 0; i < 100; i++) {
172
+ lastWire = senderKeyEncrypt(sk, new TextEncoder().encode(`msg-${i}`));
173
+ }
174
+ // Self-copy is at index 0, message is at index 99 — needs to ratchet 100 steps
175
+ const dec = senderKeyDecrypt(lastWire, selfCopy);
176
+ expect(new TextDecoder().decode(dec)).toBe("msg-99");
177
+ // Self-copy should now be at index 100
178
+ expect(selfCopy.chainIndex).toBe(100);
179
+ });
180
+ });
181
+ // ─── Chain ratcheting edge cases ───────────────────────
182
+ describe("chain ratcheting", () => {
183
+ it("skipped messages: can decrypt message N without decrypting 0..N-1 first", () => {
184
+ const sk = generateSenderKey();
185
+ const received = cloneSenderKey(sk);
186
+ // Encrypt 5 messages, keep only the 5th (index 4)
187
+ let fifthWire = new Uint8Array(0);
188
+ for (let i = 0; i < 5; i++) {
189
+ const wire = senderKeyEncrypt(sk, new TextEncoder().encode(`msg-${i}`));
190
+ if (i === 4)
191
+ fifthWire = wire;
192
+ }
193
+ // Decrypt only the 5th message — receiver ratchets from 0 to 4
194
+ const dec = senderKeyDecrypt(fifthWire, received);
195
+ expect(new TextDecoder().decode(dec)).toBe("msg-4");
196
+ expect(received.chainIndex).toBe(5);
197
+ });
198
+ it("rejects messages with chain index before current state", () => {
199
+ const sk = generateSenderKey();
200
+ const received = cloneSenderKey(sk);
201
+ // Encrypt 3 messages
202
+ const wires = [0, 1, 2].map((i) => senderKeyEncrypt(sk, new TextEncoder().encode(`msg-${i}`)));
203
+ // Decrypt message at index 2, advancing received to index 3
204
+ senderKeyDecrypt(wires[2], received);
205
+ expect(received.chainIndex).toBe(3);
206
+ // Trying to decrypt message at index 0 should fail (already consumed)
207
+ expect(() => senderKeyDecrypt(wires[0], received)).toThrow("already consumed");
208
+ });
209
+ it("rejects messages that skip more than MAX_SKIP (256)", () => {
210
+ const sk = generateSenderKey();
211
+ const received = cloneSenderKey(sk);
212
+ // Encrypt 258 messages, try to decrypt only the last one
213
+ let lastWire = new Uint8Array(0);
214
+ for (let i = 0; i < 258; i++) {
215
+ lastWire = senderKeyEncrypt(sk, new TextEncoder().encode(`msg-${i}`));
216
+ }
217
+ // Receiver is at index 0, message at index 257 — skip of 258 > MAX_SKIP (256)
218
+ expect(() => senderKeyDecrypt(lastWire, received)).toThrow("Too many skipped");
219
+ });
220
+ it("exactly MAX_SKIP (256) skipped messages is allowed", () => {
221
+ const sk = generateSenderKey();
222
+ const received = cloneSenderKey(sk);
223
+ // Encrypt 257 messages (indices 0-256), decrypt only the last (index 256)
224
+ // Skip = 256 - 0 = 256, which equals MAX_SKIP and should succeed
225
+ let lastWire = new Uint8Array(0);
226
+ for (let i = 0; i < 257; i++) {
227
+ lastWire = senderKeyEncrypt(sk, new TextEncoder().encode(`msg-${i}`));
228
+ }
229
+ const dec = senderKeyDecrypt(lastWire, received);
230
+ expect(new TextDecoder().decode(dec)).toBe("msg-256");
231
+ });
232
+ });
233
+ // ─── Wire format integrity ─────────────────────────────
234
+ describe("wire format", () => {
235
+ it("first byte is GROUP_MSG_TYPE (0x03)", () => {
236
+ const sk = generateSenderKey();
237
+ const wire = senderKeyEncrypt(sk, plaintext());
238
+ expect(wire[0]).toBe(0x03);
239
+ });
240
+ it("embeds the distribution ID at bytes 1-16", () => {
241
+ const sk = generateSenderKey();
242
+ const distId = new Uint8Array(sk.distributionId);
243
+ const wire = senderKeyEncrypt(sk, plaintext());
244
+ expect(wire.slice(1, 17)).toEqual(distId);
245
+ });
246
+ it("embeds the chain index as uint32 LE at bytes 17-20", () => {
247
+ const sk = generateSenderKey();
248
+ // Encrypt 3 messages so index is 2 for the third
249
+ senderKeyEncrypt(sk, plaintext());
250
+ senderKeyEncrypt(sk, plaintext());
251
+ const wire = senderKeyEncrypt(sk, plaintext());
252
+ const view = new DataView(wire.buffer, wire.byteOffset, wire.byteLength);
253
+ expect(view.getUint32(17, true)).toBe(2);
254
+ });
255
+ it("tampered ciphertext fails decryption", () => {
256
+ const sk = generateSenderKey();
257
+ const received = cloneSenderKey(sk);
258
+ const wire = senderKeyEncrypt(sk, plaintext());
259
+ // Flip a byte in the ciphertext area (after the 45-byte header)
260
+ wire[50] ^= 0xff;
261
+ expect(() => senderKeyDecrypt(wire, received)).toThrow();
262
+ });
263
+ it("tampered nonce fails decryption", () => {
264
+ const sk = generateSenderKey();
265
+ const received = cloneSenderKey(sk);
266
+ const wire = senderKeyEncrypt(sk, plaintext());
267
+ // Flip a byte in the nonce area (bytes 21-44)
268
+ wire[25] ^= 0xff;
269
+ expect(() => senderKeyDecrypt(wire, received)).toThrow();
270
+ });
271
+ });
272
+ //# sourceMappingURL=sender-keys.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sender-keys.test.js","sourceRoot":"","sources":["../../src/crypto/sender-keys.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Initialize the libsodium runtime. Must be called once before any crypto operations.
3
+ */
4
+ export declare function initSodium(): Promise<void>;
5
+ /**
6
+ * Get the initialized sodium instance. Throws if initSodium() hasn't been called.
7
+ */
8
+ export declare function getSodium(): any;
9
+ export declare function toBase64(bytes: Uint8Array): string;
10
+ export declare function fromBase64(b64: string): Uint8Array;
11
+ export declare function toHex(bytes: Uint8Array): string;
12
+ export declare function fromHex(hex: string): Uint8Array;
13
+ export declare function randomBytes(n: number): Uint8Array;
14
+ /**
15
+ * HMAC-SHA256 using libsodium's crypto_auth_hmacsha256.
16
+ * Key must be exactly 32 bytes.
17
+ */
18
+ export declare function hmacSha256(key: Uint8Array, data: Uint8Array): Uint8Array;
19
+ /**
20
+ * HKDF-Extract: PRK = HMAC-SHA256(salt, ikm)
21
+ */
22
+ export declare function hkdfExtract(salt: Uint8Array, ikm: Uint8Array): Uint8Array;
23
+ /**
24
+ * HKDF-Expand: derive `length` bytes from PRK + info.
25
+ * length must be <= 255 * 32 = 8160 bytes.
26
+ */
27
+ export declare function hkdfExpand(prk: Uint8Array, info: Uint8Array, length: number): Uint8Array;
28
+ /**
29
+ * Full HKDF: Extract then Expand.
30
+ */
31
+ export declare function hkdf(salt: Uint8Array, ikm: Uint8Array, info: Uint8Array, length: number): Uint8Array;
32
+ /**
33
+ * KDF_RK: Root key ratchet step.
34
+ * Returns [newRootKey, chainKey] (32 bytes each).
35
+ */
36
+ export declare function kdfRK(rootKey: Uint8Array, dhOutput: Uint8Array): [Uint8Array, Uint8Array];
37
+ /**
38
+ * KDF_CK: Chain key ratchet step.
39
+ * Returns [newChainKey, messageKey].
40
+ */
41
+ export declare function kdfCK(chainKey: Uint8Array): [Uint8Array, Uint8Array];
@@ -0,0 +1,102 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ let _sodium = null;
3
+ /**
4
+ * Initialize the libsodium runtime. Must be called once before any crypto operations.
5
+ */
6
+ export async function initSodium() {
7
+ if (_sodium)
8
+ return;
9
+ const mod = await import("libsodium-wrappers-sumo");
10
+ await mod.default.ready;
11
+ _sodium = mod.default;
12
+ }
13
+ /**
14
+ * Get the initialized sodium instance. Throws if initSodium() hasn't been called.
15
+ */
16
+ export function getSodium() {
17
+ if (!_sodium)
18
+ throw new Error("Call initSodium() before using crypto functions");
19
+ return _sodium;
20
+ }
21
+ // ─── Encoding ──────────────────────────────────────────
22
+ export function toBase64(bytes) {
23
+ const s = getSodium();
24
+ return s.to_base64(bytes, s.base64_variants.ORIGINAL);
25
+ }
26
+ export function fromBase64(b64) {
27
+ const s = getSodium();
28
+ return s.from_base64(b64, s.base64_variants.ORIGINAL);
29
+ }
30
+ export function toHex(bytes) {
31
+ return getSodium().to_hex(bytes);
32
+ }
33
+ export function fromHex(hex) {
34
+ return getSodium().from_hex(hex);
35
+ }
36
+ export function randomBytes(n) {
37
+ return getSodium().randombytes_buf(n);
38
+ }
39
+ // ─── HKDF-SHA256 ───────────────────────────────────────
40
+ // Used by both X3DH and the Double Ratchet.
41
+ // Implements RFC 5869 using HMAC-SHA256.
42
+ /**
43
+ * HMAC-SHA256 using libsodium's crypto_auth_hmacsha256.
44
+ * Key must be exactly 32 bytes.
45
+ */
46
+ export function hmacSha256(key, data) {
47
+ return getSodium().crypto_auth_hmacsha256(data, key);
48
+ }
49
+ /**
50
+ * HKDF-Extract: PRK = HMAC-SHA256(salt, ikm)
51
+ */
52
+ export function hkdfExtract(salt, ikm) {
53
+ return hmacSha256(salt, ikm);
54
+ }
55
+ /**
56
+ * HKDF-Expand: derive `length` bytes from PRK + info.
57
+ * length must be <= 255 * 32 = 8160 bytes.
58
+ */
59
+ export function hkdfExpand(prk, info, length) {
60
+ const hashLen = 32;
61
+ const n = Math.ceil(length / hashLen);
62
+ const output = new Uint8Array(n * hashLen);
63
+ let prev = new Uint8Array(0);
64
+ for (let i = 1; i <= n; i++) {
65
+ const input = new Uint8Array(prev.length + info.length + 1);
66
+ input.set(prev, 0);
67
+ input.set(info, prev.length);
68
+ input[prev.length + info.length] = i;
69
+ prev = new Uint8Array(hmacSha256(prk, input));
70
+ output.set(prev, (i - 1) * hashLen);
71
+ }
72
+ return new Uint8Array(output.buffer, 0, length);
73
+ }
74
+ /**
75
+ * Full HKDF: Extract then Expand.
76
+ */
77
+ export function hkdf(salt, ikm, info, length) {
78
+ const prk = hkdfExtract(salt, ikm);
79
+ return hkdfExpand(prk, info, length);
80
+ }
81
+ // ─── Ratchet KDFs (Signal spec) ────────────────────────
82
+ const RATCHET_INFO = new TextEncoder().encode("haven_ratchet");
83
+ const CHAIN_MSG_KEY = new Uint8Array([0x01]);
84
+ const CHAIN_NEXT_KEY = new Uint8Array([0x02]);
85
+ /**
86
+ * KDF_RK: Root key ratchet step.
87
+ * Returns [newRootKey, chainKey] (32 bytes each).
88
+ */
89
+ export function kdfRK(rootKey, dhOutput) {
90
+ const derived = hkdf(rootKey, dhOutput, RATCHET_INFO, 64);
91
+ return [derived.slice(0, 32), derived.slice(32, 64)];
92
+ }
93
+ /**
94
+ * KDF_CK: Chain key ratchet step.
95
+ * Returns [newChainKey, messageKey].
96
+ */
97
+ export function kdfCK(chainKey) {
98
+ const newChainKey = hmacSha256(chainKey, CHAIN_NEXT_KEY);
99
+ const messageKey = hmacSha256(chainKey, CHAIN_MSG_KEY);
100
+ return [newChainKey, messageKey];
101
+ }
102
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/crypto/utils.ts"],"names":[],"mappings":"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"}