@freesignal/protocol 0.1.0 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/double-ratchet.js CHANGED
@@ -21,37 +21,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
21
21
  return (mod && mod.__esModule) ? mod : { "default": mod };
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.EncryptedPayload = exports.Session = void 0;
25
- exports.createSession = createSession;
24
+ exports.EncryptedData = exports.KeySession = void 0;
26
25
  const crypto_1 = __importDefault(require("./crypto"));
27
26
  const utils_1 = require("./utils");
28
- /**
29
- * Creates a new Double Ratchet session.
30
- *
31
- * @param opts.remoteKey The public key of the remote party.
32
- * @param opts.preSharedKey An optional pre-shared key to initialize the session.
33
- *
34
- * @returns A new Double Ratchet session.
35
- */
36
- function createSession(opts) {
37
- return new Session(opts);
38
- }
39
27
  /**
40
28
  * Represents a secure Double Ratchet session.
41
29
  * Used for forward-secure encryption and decryption of messages.
42
30
  */
43
- class Session {
31
+ class KeySession {
44
32
  constructor(opts = {}) {
45
- this.keyPair = crypto_1.default.ECDH.keyPair();
46
33
  this.sendingCount = 0;
47
34
  this.previousCount = 0;
48
35
  this.receivingCount = 0;
49
36
  this.previousKeys = new KeyMap();
50
- if (opts.preSharedKey)
51
- this.rootKey = opts.preSharedKey;
37
+ this.keyPair = crypto_1.default.ECDH.keyPair(opts.secretKey);
38
+ if (opts.rootKey)
39
+ this.rootKey = opts.rootKey;
52
40
  if (opts.remoteKey) {
53
41
  this._remoteKey = opts.remoteKey;
54
- this.sendingChain = this.dhRatchet();
42
+ this.sendingChain = this.ratchetKeys();
55
43
  }
56
44
  }
57
45
  /**
@@ -66,46 +54,43 @@ class Session {
66
54
  * The last known remote public key.
67
55
  */
68
56
  get remoteKey() { return this._remoteKey; }
69
- /**
70
- * Set remote public key.
71
- */
72
57
  setRemoteKey(key) {
73
58
  this._remoteKey = key;
74
- this.receivingChain = this.dhRatchet();
75
- if (this.receivingCount > (EncryptedPayloadConstructor.maxCount - Session.skipLimit * 2))
59
+ this.receivingChain = this.ratchetKeys();
60
+ if (this.receivingCount > (EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
76
61
  this.receivingCount = 0;
77
62
  this.previousCount = this.sendingCount;
78
63
  this.keyPair = crypto_1.default.ECDH.keyPair();
79
- this.sendingChain = this.dhRatchet();
80
- if (this.sendingCount > (EncryptedPayloadConstructor.maxCount - Session.skipLimit * 2))
64
+ this.sendingChain = this.ratchetKeys();
65
+ if (this.sendingCount > (EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
81
66
  this.sendingCount = 0;
82
67
  return this;
83
68
  }
84
- dhRatchet(info) {
69
+ ratchetKeys(info) {
85
70
  if (!this._remoteKey)
86
71
  throw new Error();
87
72
  const sharedKey = crypto_1.default.scalarMult(this.keyPair.secretKey, this._remoteKey);
88
73
  if (!this.rootKey)
89
74
  this.rootKey = crypto_1.default.hash(sharedKey);
90
- const hashkey = crypto_1.default.hkdf(sharedKey, this.rootKey, info, Session.keyLength * 2);
91
- this.rootKey = hashkey.slice(0, Session.keyLength);
92
- return hashkey.slice(Session.keyLength);
75
+ const hashkey = crypto_1.default.hkdf(sharedKey, this.rootKey, info, KeySession.keyLength * 2);
76
+ this.rootKey = hashkey.slice(0, KeySession.keyLength);
77
+ return hashkey.slice(KeySession.keyLength);
93
78
  }
94
79
  getSendingKey() {
95
80
  if (!this.sendingChain)
96
81
  throw new Error;
97
- const out = Session.symmetricRatchet(this.sendingChain);
98
- this.sendingChain = out[0];
82
+ const { chainKey, sharedKey } = KeySession.symmetricRatchet(this.sendingChain);
83
+ this.sendingChain = chainKey;
99
84
  this.sendingCount++;
100
- return out[1];
85
+ return sharedKey;
101
86
  }
102
87
  getReceivingKey() {
103
88
  if (!this.receivingChain)
104
89
  throw new Error();
105
- const out = Session.symmetricRatchet(this.receivingChain);
106
- this.receivingChain = out[0];
90
+ const { chainKey, sharedKey } = KeySession.symmetricRatchet(this.receivingChain);
91
+ this.receivingChain = chainKey;
107
92
  this.receivingCount++;
108
- return out[1];
93
+ return sharedKey;
109
94
  }
110
95
  /**
111
96
  * Encrypts a message payload using the current sending chain.
@@ -114,17 +99,12 @@ class Session {
114
99
  * @returns An EncryptedPayload or undefined if encryption fails.
115
100
  */
116
101
  encrypt(message) {
117
- try {
118
- const key = this.getSendingKey();
119
- if (this.sendingCount >= EncryptedPayloadConstructor.maxCount || this.previousCount >= EncryptedPayloadConstructor.maxCount)
120
- throw new Error();
121
- const nonce = crypto_1.default.randomBytes(EncryptedPayloadConstructor.nonceLength);
122
- const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
123
- return new EncryptedPayloadConstructor(this.sendingCount, this.previousCount, this.keyPair.publicKey, nonce, ciphertext);
124
- }
125
- catch (error) {
126
- return undefined;
127
- }
102
+ const key = this.getSendingKey();
103
+ if (this.sendingCount >= EncryptedDataConstructor.maxCount || this.previousCount >= EncryptedDataConstructor.maxCount)
104
+ throw new Error();
105
+ const nonce = crypto_1.default.randomBytes(EncryptedDataConstructor.nonceLength);
106
+ const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
107
+ return new EncryptedDataConstructor(this.sendingCount, this.previousCount, this.keyPair.publicKey, nonce, ciphertext);
128
108
  }
129
109
  /**
130
110
  * Decrypts an encrypted message.
@@ -134,47 +114,44 @@ class Session {
134
114
  */
135
115
  decrypt(payload) {
136
116
  var _a;
137
- try {
138
- const encrypted = EncryptedPayload.from(payload);
139
- const publicKey = encrypted.publicKey;
140
- if (!(0, utils_1.verifyUint8Array)(publicKey, this._remoteKey)) {
141
- while (this.receivingCount < encrypted.previous)
142
- this.previousKeys.set(this.receivingCount, this.getReceivingKey());
143
- this.setRemoteKey(publicKey);
144
- }
145
- let key;
146
- const count = encrypted.count;
147
- if (this.receivingCount < count) {
148
- let i = 0;
149
- while (this.receivingCount < count - 1 && i < Session.skipLimit) {
150
- this.previousKeys.set(this.receivingCount, this.getReceivingKey());
151
- }
152
- key = this.getReceivingKey();
153
- }
154
- else {
155
- key = this.previousKeys.get(count);
117
+ const encrypted = EncryptedData.from(payload);
118
+ const publicKey = encrypted.publicKey;
119
+ if (!(0, utils_1.verifyUint8Array)(publicKey, this._remoteKey)) {
120
+ while (this.receivingCount < encrypted.previous)
121
+ this.previousKeys.set(this.receivingCount, this.getReceivingKey());
122
+ this.setRemoteKey(publicKey);
123
+ }
124
+ let key;
125
+ const count = encrypted.count;
126
+ if (this.receivingCount < count) {
127
+ let i = 0;
128
+ while (this.receivingCount < count - 1 && i < KeySession.skipLimit) {
129
+ this.previousKeys.set(this.receivingCount, this.getReceivingKey());
156
130
  }
157
- if (!key)
158
- return undefined;
159
- return (_a = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key)) !== null && _a !== void 0 ? _a : undefined;
131
+ key = this.getReceivingKey();
160
132
  }
161
- catch (error) {
162
- return undefined;
133
+ else {
134
+ key = this.previousKeys.get(count);
163
135
  }
136
+ if (!key)
137
+ return undefined;
138
+ return (_a = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key)) !== null && _a !== void 0 ? _a : undefined;
164
139
  }
165
140
  /**
166
141
  * Export the state of the session;
167
142
  */
168
143
  export() {
169
- return JSON.stringify({
144
+ return {
145
+ secretKey: (0, utils_1.encodeBase64)((0, utils_1.concatUint8Array)(this.keyPair.secretKey)),
170
146
  remoteKey: (0, utils_1.encodeBase64)(this._remoteKey),
171
147
  rootKey: (0, utils_1.encodeBase64)(this.rootKey),
172
148
  sendingChain: (0, utils_1.encodeBase64)(this.sendingChain),
173
149
  receivingChain: (0, utils_1.encodeBase64)(this.receivingChain),
174
150
  sendingCount: this.sendingCount,
175
151
  receivingCount: this.receivingCount,
176
- //previousCount: this.previousCount
177
- });
152
+ previousCount: this.previousCount,
153
+ previousKeys: Array.from(this.previousKeys.entries())
154
+ };
178
155
  }
179
156
  /**
180
157
  * Import a state.
@@ -184,28 +161,34 @@ class Session {
184
161
  */
185
162
  static import(json) {
186
163
  const data = JSON.parse(json);
187
- const session = new Session({ remoteKey: (0, utils_1.decodeBase64)(data.remoteKey), preSharedKey: (0, utils_1.decodeBase64)(data.rootKey) });
164
+ const session = new KeySession({ secretKey: (0, utils_1.decodeBase64)(data.secretKey), rootKey: (0, utils_1.decodeBase64)(data.rootKey) });
165
+ session._remoteKey = (0, utils_1.decodeBase64)(data.remoteKey);
188
166
  session.sendingChain = (0, utils_1.decodeBase64)(data.sendingChain);
189
167
  session.receivingChain = (0, utils_1.decodeBase64)(data.receivingChain);
190
168
  session.sendingCount = data.sendingCount;
191
169
  session.receivingCount = data.receivingCount;
170
+ session.previousCount = data.previousCount;
171
+ session.previousKeys = new KeyMap(data.previousKeys);
192
172
  return session;
193
173
  }
194
174
  static symmetricRatchet(chain, salt, info) {
195
- const hash = crypto_1.default.hkdf(chain, salt !== null && salt !== void 0 ? salt : new Uint8Array(), info, Session.keyLength * 2);
196
- return [new Uint8Array(hash.buffer, 0, Session.keyLength), new Uint8Array(hash.buffer, Session.keyLength)];
175
+ const hash = crypto_1.default.hkdf(chain, salt !== null && salt !== void 0 ? salt : new Uint8Array(), info, KeySession.keyLength * 2);
176
+ return {
177
+ chainKey: new Uint8Array(hash.buffer, 0, KeySession.keyLength),
178
+ sharedKey: new Uint8Array(hash.buffer, KeySession.keyLength)
179
+ };
197
180
  }
198
181
  }
199
- exports.Session = Session;
200
- Session.skipLimit = 1000;
201
- Session.version = 1;
202
- Session.rootKeyLength = crypto_1.default.box.keyLength;
182
+ exports.KeySession = KeySession;
183
+ KeySession.skipLimit = 1000;
184
+ KeySession.version = 1;
185
+ KeySession.rootKeyLength = crypto_1.default.box.keyLength;
203
186
  /**
204
187
  * The fixed key length (in bytes) used throughout the Double Ratchet session.
205
188
  * Typically 32 bytes (256 bits) for symmetric keys.
206
189
  */
207
- Session.keyLength = 32;
208
- class EncryptedPayload {
190
+ KeySession.keyLength = 32;
191
+ class EncryptedData {
209
192
  /**
210
193
  * Static factory method that constructs an `EncryptedPayload` from a raw Uint8Array.
211
194
  *
@@ -213,24 +196,27 @@ class EncryptedPayload {
213
196
  * @returns An instance of `EncryptedPayload`.
214
197
  */
215
198
  static from(array) {
216
- return new EncryptedPayloadConstructor(array);
199
+ return new EncryptedDataConstructor(array);
217
200
  }
218
201
  }
219
- exports.EncryptedPayload = EncryptedPayload;
220
- class EncryptedPayloadConstructor {
202
+ exports.EncryptedData = EncryptedData;
203
+ class EncryptedDataConstructor {
221
204
  constructor(...arrays) {
222
- var _a;
223
205
  arrays = arrays.filter(value => value !== undefined);
224
- if (arrays[0] instanceof EncryptedPayloadConstructor) {
206
+ if (arrays[0] instanceof EncryptedDataConstructor) {
225
207
  this.raw = arrays[0].raw;
226
208
  return this;
227
209
  }
228
210
  if (typeof arrays[0] === 'number')
229
- arrays[0] = (0, utils_1.numberToUint8Array)(arrays[0], EncryptedPayloadConstructor.countLength);
211
+ arrays[0] = (0, utils_1.numberToUint8Array)(arrays[0], EncryptedDataConstructor.countLength);
230
212
  if (typeof arrays[1] === 'number')
231
- arrays[1] = (0, utils_1.numberToUint8Array)(arrays[1], EncryptedPayloadConstructor.countLength);
232
- if (arrays.length > 1) {
233
- arrays.unshift((_a = (typeof arrays[5] === 'number' ? (0, utils_1.numberToUint8Array)(arrays[5]) : arrays[5])) !== null && _a !== void 0 ? _a : (0, utils_1.numberToUint8Array)(Session.version));
213
+ arrays[1] = (0, utils_1.numberToUint8Array)(arrays[1], EncryptedDataConstructor.countLength);
214
+ if (arrays.length === 6) {
215
+ arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToUint8Array)(arrays[5]) : arrays[5]);
216
+ arrays.pop();
217
+ }
218
+ else if (arrays.length > 1) {
219
+ arrays.unshift((0, utils_1.numberToUint8Array)(KeySession.version));
234
220
  }
235
221
  this.raw = (0, utils_1.concatUint8Array)(...arrays);
236
222
  }
@@ -241,32 +227,6 @@ class EncryptedPayloadConstructor {
241
227
  get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
242
228
  get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
243
229
  get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
244
- /*public get signature() { return this.signed ? new Uint8Array(this.raw.buffer, this.raw.length - EncryptedPayloadConstructor.signatureLength) : undefined }
245
-
246
- public setSignature(signature: Uint8Array): this {
247
- this.raw = concatUint8Array(this.raw, signature);
248
- this.signed = true;
249
- return this;
250
- }
251
-
252
- public encodeUnsigned(): Uint8Array {
253
- return !this.signed ? this.raw : new Uint8Array(this.raw.buffer, 0, this.raw.length - EncryptedPayloadConstructor.signatureLength);
254
- }
255
-
256
- public encode(fixedLength?: number): Uint8Array {
257
- if (fixedLength) {
258
- var padStart = Math.floor(Math.random() * fixedLength - 2 - this.raw.length);
259
- var padEnd = Math.floor(padStart + 2 + this.raw.length);
260
- } else {
261
- padStart = Math.floor(Math.random() * (EncryptedPayloadConstructor.maxPadLength - EncryptedPayloadConstructor.minPadLength) + 1) + EncryptedPayloadConstructor.minPadLength;
262
- padEnd = Math.floor(Math.random() * (EncryptedPayloadConstructor.maxPadLength - EncryptedPayloadConstructor.minPadLength) + 1) + EncryptedPayloadConstructor.minPadLength;
263
- }
264
- return concatUint8Array(
265
- randomBytes(padStart - 1).map((value) => value !== 255 ? value : (value - 1)),
266
- new Uint8Array(1).fill(255), this.raw, new Uint8Array(1).fill(255),
267
- randomBytes(padEnd).map((value) => value !== 255 ? value : (value - 1)),
268
- );
269
- }*/
270
230
  encode() {
271
231
  return this.raw;
272
232
  }
@@ -277,26 +237,22 @@ class EncryptedPayloadConstructor {
277
237
  previous: this.previous,
278
238
  publicKey: (0, utils_1.encodeBase64)(this.publicKey),
279
239
  nonce: (0, utils_1.encodeBase64)(this.nonce),
280
- ciphertext: (0, utils_1.encodeUTF8)(this.ciphertext),
281
- //signature: encodeBase64(this.signature)
240
+ ciphertext: (0, utils_1.encodeBase64)(this.ciphertext)
282
241
  };
283
242
  }
284
243
  toString() {
285
- return (0, utils_1.encodeUTF8)(this.raw);
244
+ return (0, utils_1.encodeBase64)(this.raw);
286
245
  }
287
246
  toJSON() {
288
247
  return JSON.stringify(this.decode());
289
248
  }
290
249
  }
291
- //public static readonly signatureLength = crypto.EdDSA.signatureLength;
292
- EncryptedPayloadConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
293
- EncryptedPayloadConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
294
- EncryptedPayloadConstructor.keyLength = crypto_1.default.box.keyLength;
295
- EncryptedPayloadConstructor.nonceLength = crypto_1.default.box.nonceLength;
296
- //public static readonly minPadLength = 6;
297
- //public static readonly maxPadLength = 14;
298
- EncryptedPayloadConstructor.maxCount = 65536; //32768;
299
- EncryptedPayloadConstructor.countLength = 2;
250
+ EncryptedDataConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
251
+ EncryptedDataConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
252
+ EncryptedDataConstructor.keyLength = crypto_1.default.box.keyLength;
253
+ EncryptedDataConstructor.nonceLength = crypto_1.default.box.nonceLength;
254
+ EncryptedDataConstructor.maxCount = 65536; //32768;
255
+ EncryptedDataConstructor.countLength = 2;
300
256
  class Offsets {
301
257
  static set(start, length) {
302
258
  class Offset {
@@ -315,20 +271,12 @@ class Offsets {
315
271
  }
316
272
  Offsets.checksum = Offsets.set(0, 0);
317
273
  Offsets.version = Offsets.set(Offsets.checksum.end, 1);
318
- Offsets.count = Offsets.set(Offsets.version.end, EncryptedPayloadConstructor.countLength);
319
- Offsets.previous = Offsets.set(Offsets.count.end, EncryptedPayloadConstructor.countLength);
320
- Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedPayloadConstructor.publicKeyLength);
321
- Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedPayloadConstructor.nonceLength);
274
+ Offsets.count = Offsets.set(Offsets.version.end, EncryptedDataConstructor.countLength);
275
+ Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.countLength);
276
+ Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
277
+ Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
322
278
  Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
323
- class KeyMap {
324
- constructor(iterable) {
325
- return new KeyMapConstructor(iterable);
326
- }
327
- }
328
- class KeyMapConstructor extends Map {
329
- constructor(iterable) {
330
- super(iterable);
331
- }
279
+ class KeyMap extends Map {
332
280
  get(key) {
333
281
  const out = super.get(key);
334
282
  if (out && !super.delete(key))
package/index.d.ts ADDED
@@ -0,0 +1,46 @@
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
+ */
19
+ import crypto from "./crypto";
20
+ import { LocalStorage } from "./types";
21
+ import { KeySession } from "./double-ratchet";
22
+ import { KeyExchange } from "./x3dh";
23
+ /**
24
+ * Creates a new Double Ratchet session.
25
+ *
26
+ * @param opts.remoteKey The public key of the remote party.
27
+ * @param opts.preSharedKey An optional pre-shared key to initialize the session.
28
+ *
29
+ * @returns A new Double Ratchet session.
30
+ */
31
+ export declare function createKeySession(opts?: {
32
+ secretKey?: Uint8Array;
33
+ remoteKey?: Uint8Array;
34
+ rootKey?: Uint8Array;
35
+ }): KeySession;
36
+ /**
37
+ * Creates a new X3DH session.
38
+ *
39
+ * @param signKeyPair
40
+ * @param bundleStore
41
+ * @returns A new X3DH session.
42
+ */
43
+ export declare function createKeyExchange(signSecretKey: Uint8Array, boxSecretKey: Uint8Array, bundleStore?: LocalStorage<string, crypto.KeyPair>): KeyExchange;
44
+ export * from "./types";
45
+ export { Protocols, Datagram } from "./data";
46
+ export { EncryptedData } from "./double-ratchet";
package/index.js ADDED
@@ -0,0 +1,66 @@
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
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
32
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.EncryptedData = exports.Datagram = exports.Protocols = void 0;
36
+ exports.createKeySession = createKeySession;
37
+ exports.createKeyExchange = createKeyExchange;
38
+ const double_ratchet_1 = require("./double-ratchet");
39
+ const x3dh_1 = require("./x3dh");
40
+ /**
41
+ * Creates a new Double Ratchet session.
42
+ *
43
+ * @param opts.remoteKey The public key of the remote party.
44
+ * @param opts.preSharedKey An optional pre-shared key to initialize the session.
45
+ *
46
+ * @returns A new Double Ratchet session.
47
+ */
48
+ function createKeySession(opts) {
49
+ return new double_ratchet_1.KeySession(opts);
50
+ }
51
+ /**
52
+ * Creates a new X3DH session.
53
+ *
54
+ * @param signKeyPair
55
+ * @param bundleStore
56
+ * @returns A new X3DH session.
57
+ */
58
+ function createKeyExchange(signSecretKey, boxSecretKey, bundleStore) {
59
+ return new x3dh_1.KeyExchange(signSecretKey, boxSecretKey, bundleStore);
60
+ }
61
+ __exportStar(require("./types"), exports);
62
+ var data_1 = require("./data");
63
+ Object.defineProperty(exports, "Protocols", { enumerable: true, get: function () { return data_1.Protocols; } });
64
+ Object.defineProperty(exports, "Datagram", { enumerable: true, get: function () { return data_1.Datagram; } });
65
+ var double_ratchet_2 = require("./double-ratchet");
66
+ Object.defineProperty(exports, "EncryptedData", { enumerable: true, get: function () { return double_ratchet_2.EncryptedData; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freesignal/protocol",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "description": "Signal Protocol implementation in javascript",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "Christian Braghette",
@@ -12,6 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "base64-js": "^1.5.1",
15
+ "fflate": "^0.8.2",
15
16
  "js-sha3": "^0.9.3",
16
17
  "tweetnacl": "^1.0.3",
17
18
  "uuid": "^11.1.0"
package/test.js CHANGED
@@ -2,16 +2,30 @@
2
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
+ var _a, _b;
5
6
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const x3dh_1 = require("./x3dh");
7
+ const _1 = require(".");
7
8
  const crypto_1 = __importDefault(require("./crypto"));
9
+ const data_1 = require("./data");
8
10
  const utils_1 = require("./utils");
9
- const bob = new x3dh_1.X3DH(crypto_1.default.EdDSA.keyPair());
10
- const alice = new x3dh_1.X3DH(crypto_1.default.EdDSA.keyPair());
11
- const bobmessage = bob.generateSyn();
12
- const { rootKey, ackMessage: aliceack } = alice.digestSyn(bobmessage);
13
- if ((0, utils_1.verifyUint8Array)(rootKey, bob.digestAck(aliceack))) {
11
+ const bob = (0, _1.createKeyExchange)(crypto_1.default.EdDSA.keyPair().secretKey, crypto_1.default.ECDH.keyPair().secretKey);
12
+ const alice = (0, _1.createKeyExchange)(crypto_1.default.EdDSA.keyPair().secretKey, crypto_1.default.ECDH.keyPair().secretKey);
13
+ const bobmessage = bob.generateData();
14
+ const { session: alicesession, message: aliceack } = alice.digestData(bobmessage);
15
+ const { session: bobsession, cleartext } = (_a = bob.digestMessage(aliceack)) !== null && _a !== void 0 ? _a : {};
16
+ if (bobsession && cleartext) {
14
17
  console.log("Session established successfully between Alice and Bob.");
18
+ const datagram = data_1.Datagram.create(bob.signatureKey, alice.signatureKey, data_1.Protocols.MESSAGE, (_b = bobsession.encrypt((0, utils_1.decodeUTF8)("Hi Alice!"))) === null || _b === void 0 ? void 0 : _b.encode());
19
+ //console.log(datagram.payload);
20
+ const msg = datagram.encode();
21
+ console.log((0, utils_1.encodeUTF8)(alicesession.decrypt(data_1.Datagram.from(msg).payload)));
22
+ if (alicesession.handshaked && bobsession.handshaked)
23
+ console.log("Successfully handshaked");
24
+ else
25
+ console.log("Error during handshake");
26
+ const longmsg = data_1.Datagram.create(alice.signatureKey, bob.signatureKey, data_1.Protocols.MESSAGE, alicesession.encrypt(new Uint8Array(1000000).fill(33).map(val => val + Math.floor(Math.random() * 93))));
27
+ console.log(longmsg.encode().length);
28
+ console.log(longmsg.encode(false).length);
15
29
  }
16
30
  else
17
31
  console.log("Error");
package/types.d.ts ADDED
@@ -0,0 +1,61 @@
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
+ */
19
+ /** */
20
+ export interface Encodable {
21
+ encode(): Uint8Array;
22
+ toString(): string;
23
+ toJSON(): string;
24
+ }
25
+ export declare namespace Encodable {
26
+ function isEncodable(obj: any): boolean;
27
+ }
28
+ type LocalStorageIterator<T> = Iterable<T>;
29
+ export interface LocalStorage<K, T> {
30
+ set(key: K, value: T): this;
31
+ get(key: K): T | undefined;
32
+ has(key: K): boolean;
33
+ delete(key: K): boolean;
34
+ entries(): LocalStorageIterator<[K, T]>;
35
+ }
36
+ export interface KeyExchangeData {
37
+ readonly version: number;
38
+ readonly publicKey: string;
39
+ readonly identityKey: string;
40
+ readonly signedPreKey: string;
41
+ readonly signature: string;
42
+ readonly onetimePreKey: string;
43
+ }
44
+ export interface KeyExchangeSynMessage {
45
+ readonly version: number;
46
+ readonly publicKey: string;
47
+ readonly identityKey: string;
48
+ readonly ephemeralKey: string;
49
+ readonly signedPreKeyHash: string;
50
+ readonly onetimePreKeyHash: string;
51
+ readonly associatedData: string;
52
+ }
53
+ export interface KeyExchangeDataBundle {
54
+ readonly version: number;
55
+ readonly publicKey: string;
56
+ readonly identityKey: string;
57
+ readonly signedPreKey: string;
58
+ readonly signature: string;
59
+ readonly onetimePreKey: string[];
60
+ }
61
+ export {};
package/types.js ADDED
@@ -0,0 +1,29 @@
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
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.Encodable = void 0;
22
+ var Encodable;
23
+ (function (Encodable) {
24
+ const properties = ['encode', 'toString', 'toJSON'];
25
+ function isEncodable(obj) {
26
+ return !properties.some(prop => !obj[prop]);
27
+ }
28
+ Encodable.isEncodable = isEncodable;
29
+ })(Encodable || (exports.Encodable = Encodable = {}));
package/utils.js CHANGED
@@ -135,6 +135,7 @@ function verifyUint8Array(a, ...b) {
135
135
  * @returns A Uint8Array
136
136
  */
137
137
  function concatUint8Array(...arrays) {
138
+ //arrays = arrays.filter(array => array != undefined);
138
139
  const out = new Uint8Array(arrays.map(value => value.length).reduce((prev, curr) => prev + curr));
139
140
  let offset = 0;
140
141
  arrays.forEach(array => {