@freesignal/protocol 0.3.2 → 0.4.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.
@@ -16,8 +16,6 @@
16
16
  * You should have received a copy of the GNU General Public License
17
17
  * along with this program. If not, see <https://www.gnu.org/licenses/>
18
18
  */
19
- import { LocalStorage } from "@freesignal/interfaces";
20
- import { EncryptedData } from "./types";
21
19
  export interface ExportedKeySession {
22
20
  secretKey: string;
23
21
  rootKey?: string;
@@ -25,6 +23,14 @@ export interface ExportedKeySession {
25
23
  receivingChain?: ExportedKeyChain;
26
24
  previousKeys: [string, Uint8Array][];
27
25
  }
26
+ export interface EncryptionKeys {
27
+ readonly count: number;
28
+ readonly previous: number;
29
+ readonly publicKey: Uint8Array;
30
+ }
31
+ export interface PrivateEncryptionKeys extends EncryptionKeys {
32
+ readonly secretKey: Uint8Array;
33
+ }
28
34
  /**
29
35
  * Represents a secure Double Ratchet session.
30
36
  * Used for forward-secure encryption and decryption of messages.
@@ -33,36 +39,22 @@ export declare class KeySession {
33
39
  static readonly keyLength = 32;
34
40
  static readonly version = 1;
35
41
  static readonly info: string;
42
+ static readonly maxCount = 65536;
36
43
  readonly id: string;
37
- private readonly mutex;
38
- private readonly storage;
39
44
  private keyPair;
40
45
  private rootKey?;
41
46
  private sendingChain?;
42
47
  private receivingChain?;
43
48
  private previousKeys;
44
- constructor(storage: LocalStorage<string, ExportedKeySession>, opts?: {
49
+ constructor(opts?: {
45
50
  id?: string;
46
51
  secretKey?: Uint8Array;
47
52
  remoteKey?: Uint8Array;
48
53
  rootKey?: Uint8Array;
49
54
  });
50
55
  private getChain;
51
- private save;
52
- /**
53
- * Encrypts a message payload using the current sending chain.
54
- *
55
- * @param message - The message as a Uint8Array.
56
- * @returns An EncryptedPayload or undefined if encryption fails.
57
- */
58
- encrypt(message: Uint8Array): Promise<EncryptedData>;
59
- /**
60
- * Decrypts an encrypted message.
61
- *
62
- * @param payload - The received encrypted message.
63
- * @returns The decrypted message as a Uint8Array, or undefined if decryption fails.
64
- */
65
- decrypt(payload: Uint8Array | EncryptedData): Promise<Uint8Array>;
56
+ getSendingKey(): PrivateEncryptionKeys | undefined;
57
+ getReceivingKey(encryptionKeys: EncryptionKeys): Uint8Array | undefined;
66
58
  /**
67
59
  * Whether both the sending and receiving chains are initialized.
68
60
  */
@@ -81,7 +73,7 @@ export declare class KeySession {
81
73
  * @param json string returned by `export()` method.
82
74
  * @returns session with the state parsed.
83
75
  */
84
- static from(data: ExportedKeySession, storage: LocalStorage<string, ExportedKeySession>): KeySession;
76
+ static from(data: ExportedKeySession): KeySession;
85
77
  }
86
78
  interface ExportedKeyChain {
87
79
  publicKey: string;
@@ -90,32 +82,4 @@ interface ExportedKeyChain {
90
82
  count: number;
91
83
  previousCount: number;
92
84
  }
93
- export declare class EncryptedDataConstructor implements EncryptedData {
94
- static readonly secretKeyLength: number;
95
- static readonly publicKeyLength: number;
96
- static readonly keyLength: number;
97
- static readonly nonceLength: number;
98
- static readonly maxCount = 65536;
99
- static readonly countLength = 2;
100
- private raw;
101
- constructor(count: number | Uint8Array, previous: number | Uint8Array, publicKey: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, version?: number | Uint8Array);
102
- constructor(encrypted: Uint8Array | EncryptedData);
103
- get length(): number;
104
- get version(): number;
105
- get count(): number;
106
- get previous(): number;
107
- get publicKey(): Uint8Array;
108
- get nonce(): Uint8Array;
109
- get ciphertext(): Uint8Array;
110
- toBytes(): Uint8Array;
111
- toString(): string;
112
- toJSON(): {
113
- version: number;
114
- count: number;
115
- previous: number;
116
- publicKey: string;
117
- nonce: string;
118
- ciphertext: string;
119
- };
120
- }
121
85
  export {};
@@ -17,84 +17,20 @@
17
17
  * You should have received a copy of the GNU General Public License
18
18
  * along with this program. If not, see <https://www.gnu.org/licenses/>
19
19
  */
20
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
21
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
22
- return new (P || (P = Promise))(function (resolve, reject) {
23
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
24
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
25
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
26
- step((generator = generator.apply(thisArg, _arguments || [])).next());
27
- });
28
- };
29
- var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
30
- if (value !== null && value !== void 0) {
31
- if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
32
- var dispose, inner;
33
- if (async) {
34
- if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
35
- dispose = value[Symbol.asyncDispose];
36
- }
37
- if (dispose === void 0) {
38
- if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
39
- dispose = value[Symbol.dispose];
40
- if (async) inner = dispose;
41
- }
42
- if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
43
- if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
44
- env.stack.push({ value: value, dispose: dispose, async: async });
45
- }
46
- else if (async) {
47
- env.stack.push({ async: true });
48
- }
49
- return value;
50
- };
51
- var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
52
- return function (env) {
53
- function fail(e) {
54
- env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
55
- env.hasError = true;
56
- }
57
- var r, s = 0;
58
- function next() {
59
- while (r = env.stack.pop()) {
60
- try {
61
- if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
62
- if (r.dispose) {
63
- var result = r.dispose.call(r.value);
64
- if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
65
- }
66
- else s |= 1;
67
- }
68
- catch (e) {
69
- fail(e);
70
- }
71
- }
72
- if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
73
- if (env.hasError) throw env.error;
74
- }
75
- return next();
76
- };
77
- })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
78
- var e = new Error(message);
79
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
80
- });
81
20
  var __importDefault = (this && this.__importDefault) || function (mod) {
82
21
  return (mod && mod.__esModule) ? mod : { "default": mod };
83
22
  };
84
23
  Object.defineProperty(exports, "__esModule", { value: true });
85
- exports.EncryptedDataConstructor = exports.KeySession = void 0;
24
+ exports.KeySession = void 0;
86
25
  const crypto_1 = __importDefault(require("@freesignal/crypto"));
87
26
  const utils_1 = require("@freesignal/utils");
88
- const types_1 = require("./types");
89
- const semaphore_ts_1 = require("semaphore.ts");
90
27
  /**
91
28
  * Represents a secure Double Ratchet session.
92
29
  * Used for forward-secure encryption and decryption of messages.
93
30
  */
94
31
  class KeySession {
95
- constructor(storage, opts = {}) {
32
+ constructor(opts = {}) {
96
33
  var _a;
97
- this.mutex = { sending: new semaphore_ts_1.AsyncMutex(), receiving: new semaphore_ts_1.AsyncMutex() };
98
34
  this.previousKeys = new KeyMap();
99
35
  this.id = (_a = opts.id) !== null && _a !== void 0 ? _a : crypto_1.default.UUID.generate().toString();
100
36
  this.keyPair = crypto_1.default.ECDH.keyPair(opts.secretKey);
@@ -103,8 +39,6 @@ class KeySession {
103
39
  if (opts.remoteKey) {
104
40
  this.sendingChain = this.getChain(opts.remoteKey);
105
41
  }
106
- this.storage = storage;
107
- this.save();
108
42
  }
109
43
  getChain(remoteKey, previousCount) {
110
44
  const sharedKey = crypto_1.default.ECDH.scalarMult(this.keyPair.secretKey, remoteKey);
@@ -114,75 +48,38 @@ class KeySession {
114
48
  this.rootKey = hashkey.subarray(0, KeySession.keyLength);
115
49
  return new KeyChain(this.publicKey, remoteKey, hashkey.subarray(KeySession.keyLength), previousCount);
116
50
  }
117
- save() {
118
- return this.storage.set(this.id, this.toJSON());
119
- }
120
- /**
121
- * Encrypts a message payload using the current sending chain.
122
- *
123
- * @param message - The message as a Uint8Array.
124
- * @returns An EncryptedPayload or undefined if encryption fails.
125
- */
126
- encrypt(message) {
127
- return __awaiter(this, void 0, void 0, function* () {
128
- const env_1 = { stack: [], error: void 0, hasError: false };
129
- try {
130
- const lock = __addDisposableResource(env_1, yield this.mutex.sending.acquire(), false);
131
- if (!this.sendingChain)
132
- throw new Error("SendingChain not initialized");
133
- const key = this.sendingChain.getKey();
134
- const nonce = crypto_1.default.randomBytes(EncryptedDataConstructor.nonceLength);
135
- const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
136
- this.save();
137
- return new EncryptedDataConstructor(this.sendingChain.count, this.sendingChain.previousCount, this.keyPair.publicKey, nonce, ciphertext);
138
- }
139
- catch (e_1) {
140
- env_1.error = e_1;
141
- env_1.hasError = true;
142
- }
143
- finally {
144
- __disposeResources(env_1);
145
- }
146
- });
51
+ getSendingKey() {
52
+ if (!this.sendingChain)
53
+ return;
54
+ const secretKey = this.sendingChain.getKey();
55
+ return {
56
+ //version: KeySession.version,
57
+ count: this.sendingChain.count,
58
+ previous: this.sendingChain.previousCount,
59
+ publicKey: this.sendingChain.publicKey,
60
+ secretKey
61
+ };
147
62
  }
148
- /**
149
- * Decrypts an encrypted message.
150
- *
151
- * @param payload - The received encrypted message.
152
- * @returns The decrypted message as a Uint8Array, or undefined if decryption fails.
153
- */
154
- decrypt(payload) {
155
- return __awaiter(this, void 0, void 0, function* () {
156
- var _a, _b, _c;
157
- const encrypted = types_1.EncryptedData.from(payload);
158
- if (!this.previousKeys.has((0, utils_1.decodeBase64)(encrypted.publicKey) + encrypted.count.toString())) {
159
- const lock = yield this.mutex.receiving.acquire();
160
- if (!(0, utils_1.compareBytes)(encrypted.publicKey, (_b = (_a = this.receivingChain) === null || _a === void 0 ? void 0 : _a.remoteKey) !== null && _b !== void 0 ? _b : new Uint8Array())) {
161
- while (this.receivingChain && this.receivingChain.count < encrypted.previous) {
162
- const key = this.receivingChain.getKey();
163
- this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
164
- }
165
- this.receivingChain = this.getChain(encrypted.publicKey);
166
- this.keyPair = crypto_1.default.ECDH.keyPair();
167
- this.sendingChain = this.getChain(encrypted.publicKey, (_c = this.sendingChain) === null || _c === void 0 ? void 0 : _c.count);
168
- }
169
- if (!this.receivingChain)
170
- throw new Error("Error initializing receivingChain");
171
- while (this.receivingChain.count < encrypted.count) {
63
+ getReceivingKey(encryptionKeys) {
64
+ var _a, _b, _c;
65
+ if (!this.previousKeys.has((0, utils_1.decodeBase64)(encryptionKeys.publicKey) + encryptionKeys.count.toString())) {
66
+ if (!(0, utils_1.compareBytes)(encryptionKeys.publicKey, (_b = (_a = this.receivingChain) === null || _a === void 0 ? void 0 : _a.remoteKey) !== null && _b !== void 0 ? _b : new Uint8Array())) {
67
+ while (this.receivingChain && this.receivingChain.count < encryptionKeys.previous) {
172
68
  const key = this.receivingChain.getKey();
173
69
  this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
174
70
  }
175
- lock.release();
71
+ this.receivingChain = this.getChain(encryptionKeys.publicKey);
72
+ this.keyPair = crypto_1.default.ECDH.keyPair();
73
+ this.sendingChain = this.getChain(encryptionKeys.publicKey, (_c = this.sendingChain) === null || _c === void 0 ? void 0 : _c.count);
74
+ }
75
+ if (!this.receivingChain)
76
+ throw new Error("Error initializing receivingChain");
77
+ while (this.receivingChain.count < encryptionKeys.count) {
78
+ const key = this.receivingChain.getKey();
79
+ this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
176
80
  }
177
- const key = this.previousKeys.get((0, utils_1.decodeBase64)(encrypted.publicKey) + encrypted.count.toString());
178
- if (!key)
179
- throw new Error("Error calculating key");
180
- this.save();
181
- const cleartext = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key);
182
- if (!cleartext)
183
- throw new Error("Error decrypting ciphertext");
184
- return cleartext;
185
- });
81
+ }
82
+ return this.previousKeys.get((0, utils_1.decodeBase64)(encryptionKeys.publicKey) + encryptionKeys.count.toString());
186
83
  }
187
84
  /**
188
85
  * Whether both the sending and receiving chains are initialized.
@@ -211,13 +108,11 @@ class KeySession {
211
108
  * @param json string returned by `export()` method.
212
109
  * @returns session with the state parsed.
213
110
  */
214
- static from(data, storage) {
215
- const session = new KeySession(storage, { secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: data.rootKey ? (0, utils_1.encodeBase64)(data.rootKey) : undefined });
216
- //session._remoteKey = data.remoteKey ? encodeBase64(data.remoteKey) : undefined;
111
+ static from(data) {
112
+ const session = new KeySession({ secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: data.rootKey ? (0, utils_1.encodeBase64)(data.rootKey) : undefined });
217
113
  session.sendingChain = data.sendingChain ? KeyChain.from(data.sendingChain) : undefined;
218
114
  session.receivingChain = data.receivingChain ? KeyChain.from(data.receivingChain) : undefined;
219
115
  session.previousKeys = new KeyMap(data.previousKeys);
220
- session.save();
221
116
  return session;
222
117
  }
223
118
  }
@@ -225,6 +120,7 @@ exports.KeySession = KeySession;
225
120
  KeySession.keyLength = 32;
226
121
  KeySession.version = 1;
227
122
  KeySession.info = "/freesignal/double-ratchet/v0." + KeySession.version;
123
+ KeySession.maxCount = 65536;
228
124
  class KeyChain {
229
125
  constructor(publicKey, remoteKey, chainKey, previousCount = 0) {
230
126
  this.publicKey = publicKey;
@@ -234,7 +130,7 @@ class KeyChain {
234
130
  this._count = 0;
235
131
  }
236
132
  getKey() {
237
- if (++this._count >= EncryptedDataConstructor.maxCount)
133
+ if (++this._count >= KeySession.maxCount)
238
134
  throw new Error("SendingChain count too big");
239
135
  const hash = crypto_1.default.hkdf(this.chainKey, new Uint8Array(KeySession.keyLength).fill(0), KeySession.info, KeySession.keyLength * 2);
240
136
  this.chainKey = hash.subarray(0, KeySession.keyLength);
@@ -261,80 +157,6 @@ class KeyChain {
261
157
  return chain;
262
158
  }
263
159
  }
264
- class EncryptedDataConstructor {
265
- constructor(...arrays) {
266
- arrays = arrays.filter(value => value !== undefined);
267
- if (arrays[0] instanceof EncryptedDataConstructor) {
268
- this.raw = arrays[0].raw;
269
- return this;
270
- }
271
- if (typeof arrays[0] === 'number')
272
- arrays[0] = (0, utils_1.numberToBytes)(arrays[0], EncryptedDataConstructor.countLength);
273
- if (typeof arrays[1] === 'number')
274
- arrays[1] = (0, utils_1.numberToBytes)(arrays[1], EncryptedDataConstructor.countLength);
275
- if (arrays.length === 6) {
276
- arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToBytes)(arrays[5], 1) : arrays[5]);
277
- arrays.pop();
278
- }
279
- else if (arrays.length > 1) {
280
- arrays.unshift((0, utils_1.numberToBytes)(KeySession.version, 1));
281
- }
282
- this.raw = (0, utils_1.concatBytes)(...arrays);
283
- }
284
- get length() { return this.raw.length; }
285
- get version() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
286
- get count() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
287
- get previous() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
288
- get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
289
- get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
290
- get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
291
- toBytes() {
292
- return this.raw;
293
- }
294
- toString() {
295
- return (0, utils_1.decodeBase64)(this.raw);
296
- }
297
- toJSON() {
298
- return {
299
- version: this.version,
300
- count: this.count,
301
- previous: this.previous,
302
- publicKey: (0, utils_1.decodeBase64)(this.publicKey),
303
- nonce: (0, utils_1.decodeBase64)(this.nonce),
304
- ciphertext: (0, utils_1.decodeBase64)(this.ciphertext)
305
- };
306
- }
307
- }
308
- exports.EncryptedDataConstructor = EncryptedDataConstructor;
309
- EncryptedDataConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
310
- EncryptedDataConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
311
- EncryptedDataConstructor.keyLength = crypto_1.default.box.keyLength;
312
- EncryptedDataConstructor.nonceLength = crypto_1.default.box.nonceLength;
313
- EncryptedDataConstructor.maxCount = 65536; //32768;
314
- EncryptedDataConstructor.countLength = 2;
315
- class Offsets {
316
- static set(start, length) {
317
- class Offset {
318
- constructor(start, length) {
319
- this.start = start;
320
- this.length = length;
321
- if (typeof length === 'number')
322
- this.end = start + length;
323
- }
324
- get get() {
325
- return [this.start, this.length];
326
- }
327
- }
328
- return new Offset(start, length);
329
- }
330
- }
331
- Offsets.checksum = Offsets.set(0, 0);
332
- Offsets.version = Offsets.set(Offsets.checksum.end, 1);
333
- Offsets.count = Offsets.set(Offsets.version.end, EncryptedDataConstructor.countLength);
334
- Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.countLength);
335
- Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
336
- Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
337
- Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
338
160
  class KeyMap extends Map {
339
161
  get(key) {
340
162
  const out = super.get(key);
package/dist/node.d.ts CHANGED
@@ -1,3 +1,21 @@
1
+ /**
2
+ * FreeSignal Protocol
3
+ *
4
+ * Copyright (C) 2025 Christian Braghette
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program. If not, see <https://www.gnu.org/licenses/>
18
+ */
1
19
  import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
2
20
  import { Datagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
3
21
  import { KeyExchange } from "./x3dh";
package/dist/node.js CHANGED
@@ -1,4 +1,22 @@
1
1
  "use strict";
2
+ /**
3
+ * FreeSignal Protocol
4
+ *
5
+ * Copyright (C) 2025 Christian Braghette
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>
19
+ */
2
20
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
21
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
22
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -58,7 +76,7 @@ class FreeSignalNode {
58
76
  this.bootstraps = new Set();
59
77
  this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
60
78
  this.sessions = new SessionMap(storage.sessions);
61
- this.keyExchange = new x3dh_1.KeyExchange({ keys: storage.keyExchange, sessions: storage.sessions }, this.privateIdentityKey);
79
+ this.keyExchange = new x3dh_1.KeyExchange(storage.keyExchange, this.privateIdentityKey);
62
80
  this.users = storage.users;
63
81
  this.bundles = storage.bundles;
64
82
  }
@@ -78,7 +96,9 @@ class FreeSignalNode {
78
96
  const session = yield this.sessions.get(receiverId);
79
97
  if (!session)
80
98
  throw new Error("Session not found for user: " + receiverId);
81
- return new types_1.Datagram(this.userId.toString(), receiverId, protocol, yield session.encrypt(data)).sign(this.privateIdentityKey.signatureKey);
99
+ const encrypted = (0, types_1.encryptData)(session, data);
100
+ this.sessions.set(receiverId, session);
101
+ return new types_1.Datagram(this.userId.toString(), receiverId, protocol, encrypted).sign(this.privateIdentityKey.signatureKey);
82
102
  });
83
103
  }
84
104
  packHandshake(data) {
@@ -127,9 +147,8 @@ class FreeSignalNode {
127
147
  throw new Error("Session not found for user: " + datagram.sender);
128
148
  if (!datagram.payload)
129
149
  throw new Error("Missing payload");
130
- const decrypted = yield session.decrypt(datagram.payload);
131
- if (!decrypted)
132
- throw new Error("Decryption failed");
150
+ const decrypted = (0, types_1.decryptData)(session, datagram.payload);
151
+ this.sessions.set(datagram.sender, session);
133
152
  return decrypted;
134
153
  });
135
154
  }
@@ -220,7 +239,7 @@ class SessionMap {
220
239
  const sessionData = yield this.storage.get(key);
221
240
  if (!sessionData)
222
241
  return undefined;
223
- return double_ratchet_1.KeySession.from(sessionData, this.storage);
242
+ return double_ratchet_1.KeySession.from(sessionData);
224
243
  }
225
244
  return session;
226
245
  });
package/dist/types.d.ts CHANGED
@@ -17,6 +17,9 @@
17
17
  * along with this program. If not, see <https://www.gnu.org/licenses/>
18
18
  */
19
19
  import { LocalStorage, Encodable, KeyExchangeData } from "@freesignal/interfaces";
20
+ import { KeySession } from "./double-ratchet";
21
+ export declare function encryptData(session: KeySession, data: Uint8Array): EncryptedData;
22
+ export declare function decryptData(session: KeySession, encryptedData: Uint8Array): Uint8Array;
20
23
  export declare class UserId implements Encodable {
21
24
  private readonly array;
22
25
  private constructor();
@@ -167,14 +170,14 @@ export interface EncryptedData extends Encodable {
167
170
  ciphertext: string;
168
171
  };
169
172
  }
170
- export declare class EncryptedData {
173
+ export declare namespace EncryptedData {
171
174
  /**
172
175
  * Static factory method that constructs an `EncryptedPayload` from a raw Uint8Array.
173
176
  *
174
177
  * @param array - A previously serialized encrypted payload.
175
178
  * @returns An instance of `EncryptedPayload`.
176
179
  */
177
- static from(array: Uint8Array | EncryptedData): EncryptedData;
180
+ function from(array: Uint8Array | EncryptedData): EncryptedData;
178
181
  }
179
182
  export declare class AsyncMap<K, V> implements LocalStorage<K, V> {
180
183
  private readonly map;
package/dist/types.js CHANGED
@@ -31,9 +31,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
33
  exports.AsyncMap = exports.EncryptedData = exports.Datagram = exports.Protocols = exports.DiscoverType = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
34
+ exports.encryptData = encryptData;
35
+ exports.decryptData = decryptData;
34
36
  const utils_1 = require("@freesignal/utils");
35
37
  const crypto_1 = __importDefault(require("@freesignal/crypto"));
36
38
  const double_ratchet_1 = require("./double-ratchet");
39
+ function encryptData(session, data) {
40
+ const key = session.getSendingKey();
41
+ if (!key)
42
+ throw new Error("Error generating key");
43
+ const nonce = crypto_1.default.randomBytes(EncryptedDataConstructor.nonceLength);
44
+ const ciphertext = crypto_1.default.box.encrypt(data, nonce, key.secretKey);
45
+ return new EncryptedDataConstructor(key.count, key.previous, key.publicKey, nonce, ciphertext);
46
+ }
47
+ function decryptData(session, encryptedData) {
48
+ const encrypted = EncryptedData.from(encryptedData);
49
+ const key = session.getReceivingKey(encrypted);
50
+ if (!key)
51
+ throw new Error("Error calculating key");
52
+ const decrypted = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key);
53
+ if (!decrypted)
54
+ throw new Error("Error decrypting data");
55
+ return decrypted;
56
+ }
37
57
  class UserId {
38
58
  constructor(array) {
39
59
  this.array = array;
@@ -300,18 +320,91 @@ class Datagram {
300
320
  exports.Datagram = Datagram;
301
321
  Datagram.version = 1;
302
322
  Datagram.headerOffset = 26 + crypto_1.default.EdDSA.publicKeyLength * 2;
303
- class EncryptedData {
323
+ var EncryptedData;
324
+ (function (EncryptedData) {
304
325
  /**
305
326
  * Static factory method that constructs an `EncryptedPayload` from a raw Uint8Array.
306
327
  *
307
328
  * @param array - A previously serialized encrypted payload.
308
329
  * @returns An instance of `EncryptedPayload`.
309
330
  */
310
- static from(array) {
311
- return new double_ratchet_1.EncryptedDataConstructor(array);
331
+ function from(array) {
332
+ return new EncryptedDataConstructor(array);
333
+ }
334
+ EncryptedData.from = from;
335
+ })(EncryptedData || (exports.EncryptedData = EncryptedData = {}));
336
+ class EncryptedDataConstructor {
337
+ constructor(...arrays) {
338
+ arrays = arrays.filter(value => value !== undefined);
339
+ if (arrays[0] instanceof EncryptedDataConstructor) {
340
+ this.raw = arrays[0].raw;
341
+ return this;
342
+ }
343
+ if (typeof arrays[0] === 'number')
344
+ arrays[0] = (0, utils_1.numberToBytes)(arrays[0], EncryptedDataConstructor.countLength);
345
+ if (typeof arrays[1] === 'number')
346
+ arrays[1] = (0, utils_1.numberToBytes)(arrays[1], EncryptedDataConstructor.countLength);
347
+ if (arrays.length === 6) {
348
+ arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToBytes)(arrays[5], 1) : arrays[5]);
349
+ arrays.pop();
350
+ }
351
+ else if (arrays.length > 1) {
352
+ arrays.unshift((0, utils_1.numberToBytes)(double_ratchet_1.KeySession.version, 1));
353
+ }
354
+ this.raw = (0, utils_1.concatBytes)(...arrays);
355
+ }
356
+ get length() { return this.raw.length; }
357
+ get version() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
358
+ get count() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
359
+ get previous() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
360
+ get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
361
+ get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
362
+ get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
363
+ toBytes() {
364
+ return this.raw;
365
+ }
366
+ toString() {
367
+ return (0, utils_1.decodeBase64)(this.raw);
368
+ }
369
+ toJSON() {
370
+ return {
371
+ version: this.version,
372
+ count: this.count,
373
+ previous: this.previous,
374
+ publicKey: (0, utils_1.decodeBase64)(this.publicKey),
375
+ nonce: (0, utils_1.decodeBase64)(this.nonce),
376
+ ciphertext: (0, utils_1.decodeBase64)(this.ciphertext)
377
+ };
378
+ }
379
+ }
380
+ EncryptedDataConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
381
+ EncryptedDataConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
382
+ EncryptedDataConstructor.keyLength = crypto_1.default.box.keyLength;
383
+ EncryptedDataConstructor.nonceLength = crypto_1.default.box.nonceLength;
384
+ EncryptedDataConstructor.countLength = 2;
385
+ class Offsets {
386
+ static set(start, length) {
387
+ class Offset {
388
+ constructor(start, length) {
389
+ this.start = start;
390
+ this.length = length;
391
+ if (typeof length === 'number')
392
+ this.end = start + length;
393
+ }
394
+ get get() {
395
+ return [this.start, this.length];
396
+ }
397
+ }
398
+ return new Offset(start, length);
312
399
  }
313
400
  }
314
- exports.EncryptedData = EncryptedData;
401
+ Offsets.checksum = Offsets.set(0, 0);
402
+ Offsets.version = Offsets.set(Offsets.checksum.end, 1);
403
+ Offsets.count = Offsets.set(Offsets.version.end, EncryptedDataConstructor.countLength);
404
+ Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.countLength);
405
+ Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
406
+ Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
407
+ Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
315
408
  class AsyncMap {
316
409
  constructor(iterable) {
317
410
  this.map = new Map(iterable);
package/dist/x3dh.d.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  * along with this program. If not, see <https://www.gnu.org/licenses/>
18
18
  */
19
19
  import { KeyExchangeData, KeyExchangeDataBundle, KeyExchangeSynMessage, LocalStorage, Crypto } from "@freesignal/interfaces";
20
- import { ExportedKeySession, KeySession } from "./double-ratchet";
20
+ import { KeySession } from "./double-ratchet";
21
21
  import { IdentityKey, PrivateIdentityKey } from "./types";
22
22
  export interface ExportedKeyExchange {
23
23
  privateIdentityKey: PrivateIdentityKey;
@@ -29,11 +29,7 @@ export declare class KeyExchange {
29
29
  private static readonly maxOPK;
30
30
  private readonly privateIdentityKey;
31
31
  private readonly storage;
32
- private readonly sessions;
33
- constructor(storage: {
34
- keys: LocalStorage<string, Crypto.KeyPair>;
35
- sessions: LocalStorage<string, ExportedKeySession>;
36
- }, privateIdentityKey?: PrivateIdentityKey);
32
+ constructor(storage: LocalStorage<string, Crypto.KeyPair>, privateIdentityKey?: PrivateIdentityKey);
37
33
  get identityKey(): IdentityKey;
38
34
  private generateSPK;
39
35
  private generateOPK;
package/dist/x3dh.js CHANGED
@@ -38,8 +38,7 @@ const types_1 = require("./types");
38
38
  const _1 = require(".");
39
39
  class KeyExchange {
40
40
  constructor(storage, privateIdentityKey) {
41
- this.storage = storage.keys;
42
- this.sessions = storage.sessions;
41
+ this.storage = storage;
43
42
  this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
44
43
  }
45
44
  get identityKey() {
@@ -99,9 +98,9 @@ class KeyExchange {
99
98
  ...crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, signedPreKey),
100
99
  ...onetimePreKey ? crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
101
100
  ]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength);
102
- const session = new double_ratchet_1.KeySession(this.sessions, { remoteKey: identityKey.exchangeKey, rootKey });
103
- const cyphertext = yield session.encrypt((0, utils_1.concatBytes)(crypto_1.default.hash(this.identityKey.toBytes()), crypto_1.default.hash(identityKey.toBytes()), associatedData !== null && associatedData !== void 0 ? associatedData : new Uint8Array()));
104
- if (!cyphertext)
101
+ const session = new double_ratchet_1.KeySession({ remoteKey: identityKey.exchangeKey, rootKey });
102
+ const encrypted = (0, types_1.encryptData)(session, (0, utils_1.concatBytes)(crypto_1.default.hash(this.identityKey.toBytes()), crypto_1.default.hash(identityKey.toBytes()), associatedData !== null && associatedData !== void 0 ? associatedData : new Uint8Array()));
103
+ if (!encrypted)
105
104
  throw new Error("Decryption error");
106
105
  return {
107
106
  session,
@@ -111,7 +110,7 @@ class KeyExchange {
111
110
  ephemeralKey: (0, utils_1.decodeBase64)(ephemeralKey.publicKey),
112
111
  signedPreKeyHash: (0, utils_1.decodeBase64)(signedPreKeyHash),
113
112
  onetimePreKeyHash: (0, utils_1.decodeBase64)(onetimePreKeyHash),
114
- associatedData: (0, utils_1.decodeBase64)(cyphertext.toBytes())
113
+ associatedData: (0, utils_1.decodeBase64)(encrypted.toBytes())
115
114
  },
116
115
  identityKey
117
116
  };
@@ -134,16 +133,16 @@ class KeyExchange {
134
133
  ...crypto_1.default.ECDH.scalarMult(signedPreKey.secretKey, ephemeralKey),
135
134
  ...onetimePreKey ? crypto_1.default.ECDH.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
136
135
  ]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength);
137
- const session = new double_ratchet_1.KeySession(this.sessions, { secretKey: this.privateIdentityKey.exchangeKey, rootKey });
138
- const cleartext = yield session.decrypt((0, utils_1.encodeBase64)(message.associatedData));
139
- if (!cleartext)
136
+ const session = new double_ratchet_1.KeySession({ secretKey: this.privateIdentityKey.exchangeKey, rootKey });
137
+ const data = (0, types_1.decryptData)(session, (0, utils_1.encodeBase64)(message.associatedData));
138
+ if (!data)
140
139
  throw new Error("Error decrypting ACK message");
141
- if (!(0, utils_1.compareBytes)(cleartext.subarray(0, 64), (0, utils_1.concatBytes)(crypto_1.default.hash(identityKey.toBytes()), crypto_1.default.hash(this.identityKey.toBytes()))))
140
+ if (!(0, utils_1.compareBytes)(data.subarray(0, 64), (0, utils_1.concatBytes)(crypto_1.default.hash(identityKey.toBytes()), crypto_1.default.hash(this.identityKey.toBytes()))))
142
141
  throw new Error("Error verifing Associated Data");
143
142
  return {
144
143
  session,
145
144
  identityKey,
146
- associatedData: cleartext.subarray(64)
145
+ associatedData: data.subarray(64)
147
146
  };
148
147
  });
149
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freesignal/protocol",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Signal Protocol implementation in javascript",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "Christian Braghette",
@@ -8,22 +8,22 @@
8
8
  "exports": {
9
9
  ".": {
10
10
  "import": "./dist/index.js",
11
+ "require": "./dist/index.js",
11
12
  "types": "./dist/index.d.ts"
12
13
  },
13
14
  "./double-ratchet": {
14
15
  "import": "./dist/double-ratchet.js",
16
+ "require": "./dist/double-ratchet.js",
15
17
  "types": "./dist/double-ratchet.d.ts"
16
18
  },
17
19
  "./node": {
18
20
  "import": "./dist/node.js",
21
+ "require": "./dist/node.js",
19
22
  "types": "./dist/node.d.ts"
20
23
  },
21
- "./types": {
22
- "import": "./dist/types.js",
23
- "types": "./dist/types.d.ts"
24
- },
25
24
  "./x3dh": {
26
25
  "import": "./dist/x3dh.js",
26
+ "require": "./dist/x3dh.js",
27
27
  "types": "./dist/x3dh.d.ts"
28
28
  }
29
29
  },