@freesignal/protocol 0.2.8 → 0.2.11

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,6 +16,7 @@
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";
19
20
  import { EncryptedData } from "./types";
20
21
  export interface ExportedKeySession {
21
22
  secretKey: string;
@@ -36,6 +37,7 @@ export declare class KeySession {
36
37
  private static readonly skipLimit;
37
38
  static readonly version = 1;
38
39
  static readonly rootKeyLength: number;
40
+ readonly id: string;
39
41
  private keyPair;
40
42
  private _remoteKey?;
41
43
  private rootKey?;
@@ -45,7 +47,9 @@ export declare class KeySession {
45
47
  private receivingChain?;
46
48
  private receivingCount;
47
49
  private previousKeys;
48
- constructor(opts?: {
50
+ private readonly storage;
51
+ constructor(storage: LocalStorage<string, ExportedKeySession>, opts?: {
52
+ id?: string;
49
53
  secretKey?: Uint8Array;
50
54
  remoteKey?: Uint8Array;
51
55
  rootKey?: Uint8Array;
@@ -66,20 +70,21 @@ export declare class KeySession {
66
70
  private ratchetKeys;
67
71
  private getSendingKey;
68
72
  private getReceivingKey;
73
+ private save;
69
74
  /**
70
75
  * Encrypts a message payload using the current sending chain.
71
76
  *
72
77
  * @param message - The message as a Uint8Array.
73
78
  * @returns An EncryptedPayload or undefined if encryption fails.
74
79
  */
75
- encrypt(message: Uint8Array): EncryptedData;
80
+ encrypt(message: Uint8Array): Promise<EncryptedData>;
76
81
  /**
77
82
  * Decrypts an encrypted message.
78
83
  *
79
84
  * @param payload - The received encrypted message.
80
85
  * @returns The decrypted message as a Uint8Array, or undefined if decryption fails.
81
86
  */
82
- decrypt(payload: Uint8Array | EncryptedData): Uint8Array | undefined;
87
+ decrypt(payload: Uint8Array | EncryptedData): Promise<Uint8Array | undefined>;
83
88
  /**
84
89
  * Export the state of the session;
85
90
  */
@@ -90,7 +95,7 @@ export declare class KeySession {
90
95
  * @param json string returned by `export()` method.
91
96
  * @returns session with the state parsed.
92
97
  */
93
- static from(data: ExportedKeySession): KeySession;
98
+ static from(data: ExportedKeySession, storage: LocalStorage<string, ExportedKeySession>): KeySession;
94
99
  /**
95
100
  * The fixed key length (in bytes) used throughout the Double Ratchet session.
96
101
  * Typically 32 bytes (256 bits) for symmetric keys.
@@ -98,3 +103,31 @@ export declare class KeySession {
98
103
  static readonly keyLength = 32;
99
104
  private static symmetricRatchet;
100
105
  }
106
+ export declare class EncryptedDataConstructor implements EncryptedData {
107
+ static readonly secretKeyLength: number;
108
+ static readonly publicKeyLength: number;
109
+ static readonly keyLength: number;
110
+ static readonly nonceLength: number;
111
+ static readonly maxCount = 65536;
112
+ static readonly countLength = 2;
113
+ private raw;
114
+ constructor(count: number | Uint8Array, previous: number | Uint8Array, publicKey: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, version?: number | Uint8Array);
115
+ constructor(encrypted: Uint8Array | EncryptedData);
116
+ get length(): number;
117
+ get version(): number;
118
+ get count(): number;
119
+ get previous(): number;
120
+ get publicKey(): Uint8Array;
121
+ get nonce(): Uint8Array;
122
+ get ciphertext(): Uint8Array;
123
+ toBytes(): Uint8Array;
124
+ toString(): string;
125
+ toJSON(): {
126
+ version: number;
127
+ count: number;
128
+ previous: number;
129
+ publicKey: string;
130
+ nonce: string;
131
+ ciphertext: string;
132
+ };
133
+ }
package/double-ratchet.js CHANGED
@@ -17,11 +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
+ };
20
29
  var __importDefault = (this && this.__importDefault) || function (mod) {
21
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
22
31
  };
23
32
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.KeySession = void 0;
33
+ exports.EncryptedDataConstructor = exports.KeySession = void 0;
25
34
  const crypto_1 = __importDefault(require("@freesignal/crypto"));
26
35
  const utils_1 = require("@freesignal/utils");
27
36
  const types_1 = require("./types");
@@ -30,11 +39,13 @@ const types_1 = require("./types");
30
39
  * Used for forward-secure encryption and decryption of messages.
31
40
  */
32
41
  class KeySession {
33
- constructor(opts = {}) {
42
+ constructor(storage, opts = {}) {
43
+ var _a;
34
44
  this.sendingCount = 0;
35
45
  this.previousCount = 0;
36
46
  this.receivingCount = 0;
37
47
  this.previousKeys = new KeyMap();
48
+ this.id = (_a = opts.id) !== null && _a !== void 0 ? _a : crypto_1.default.UUID.generate().toString();
38
49
  this.keyPair = crypto_1.default.ECDH.keyPair(opts.secretKey);
39
50
  if (opts.rootKey)
40
51
  this.rootKey = opts.rootKey;
@@ -42,6 +53,8 @@ class KeySession {
42
53
  this._remoteKey = opts.remoteKey;
43
54
  this.sendingChain = this.ratchetKeys();
44
55
  }
56
+ this.storage = storage;
57
+ this.storage.set(this.id, this.toJSON());
45
58
  }
46
59
  /**
47
60
  * Whether both the sending and receiving chains are initialized.
@@ -58,12 +71,12 @@ class KeySession {
58
71
  setRemoteKey(key) {
59
72
  this._remoteKey = key;
60
73
  this.receivingChain = this.ratchetKeys();
61
- if (this.receivingCount > (types_1.EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
74
+ if (this.receivingCount > (EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
62
75
  this.receivingCount = 0;
63
76
  this.previousCount = this.sendingCount;
64
77
  this.keyPair = crypto_1.default.ECDH.keyPair();
65
78
  this.sendingChain = this.ratchetKeys();
66
- if (this.sendingCount > (types_1.EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
79
+ if (this.sendingCount > (EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
67
80
  this.sendingCount = 0;
68
81
  return this;
69
82
  }
@@ -93,6 +106,9 @@ class KeySession {
93
106
  this.receivingCount++;
94
107
  return sharedKey;
95
108
  }
109
+ save() {
110
+ return this.storage.set(this.id, this.toJSON());
111
+ }
96
112
  /**
97
113
  * Encrypts a message payload using the current sending chain.
98
114
  *
@@ -100,12 +116,15 @@ class KeySession {
100
116
  * @returns An EncryptedPayload or undefined if encryption fails.
101
117
  */
102
118
  encrypt(message) {
103
- const key = this.getSendingKey();
104
- if (this.sendingCount >= types_1.EncryptedDataConstructor.maxCount || this.previousCount >= types_1.EncryptedDataConstructor.maxCount)
105
- throw new Error();
106
- const nonce = crypto_1.default.randomBytes(types_1.EncryptedDataConstructor.nonceLength);
107
- const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
108
- return new types_1.EncryptedDataConstructor(this.sendingCount, this.previousCount, this.keyPair.publicKey, nonce, ciphertext);
119
+ return __awaiter(this, void 0, void 0, function* () {
120
+ const key = this.getSendingKey();
121
+ if (this.sendingCount >= EncryptedDataConstructor.maxCount || this.previousCount >= EncryptedDataConstructor.maxCount)
122
+ throw new Error();
123
+ const nonce = crypto_1.default.randomBytes(EncryptedDataConstructor.nonceLength);
124
+ const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
125
+ yield this.save();
126
+ return new EncryptedDataConstructor(this.sendingCount, this.previousCount, this.keyPair.publicKey, nonce, ciphertext);
127
+ });
109
128
  }
110
129
  /**
111
130
  * Decrypts an encrypted message.
@@ -114,42 +133,42 @@ class KeySession {
114
133
  * @returns The decrypted message as a Uint8Array, or undefined if decryption fails.
115
134
  */
116
135
  decrypt(payload) {
117
- var _a;
118
- const encrypted = types_1.EncryptedData.from(payload);
119
- const publicKey = encrypted.publicKey;
120
- if (!this._remoteKey)
121
- throw new Error("Missing remoteKey");
122
- if (!(0, utils_1.verifyArrays)(publicKey, this._remoteKey)) {
123
- while (this.receivingCount < encrypted.previous)
124
- this.previousKeys.set(this.receivingCount, this.getReceivingKey());
136
+ return __awaiter(this, void 0, void 0, function* () {
137
+ const encrypted = types_1.EncryptedData.from(payload);
138
+ const publicKey = encrypted.publicKey;
139
+ if (this._remoteKey && !(0, utils_1.verifyArrays)(publicKey, this._remoteKey))
140
+ while (this.receivingCount < encrypted.previous)
141
+ this.previousKeys.set(this.receivingCount, this.getReceivingKey());
125
142
  this.setRemoteKey(publicKey);
126
- }
127
- let key;
128
- const count = encrypted.count;
129
- if (this.receivingCount < count) {
130
- let i = 0;
131
- while (this.receivingCount < count - 1 && i < KeySession.skipLimit) {
132
- this.previousKeys.set(this.receivingCount, this.getReceivingKey());
143
+ let key;
144
+ const count = encrypted.count;
145
+ if (this.receivingCount < count) {
146
+ let i = 0;
147
+ while (this.receivingCount < count - 1 && i < KeySession.skipLimit) {
148
+ this.previousKeys.set(this.receivingCount, this.getReceivingKey());
149
+ }
150
+ key = this.getReceivingKey();
133
151
  }
134
- key = this.getReceivingKey();
135
- }
136
- else {
137
- key = this.previousKeys.get(count);
138
- }
139
- if (!key)
140
- return undefined;
141
- return (_a = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key)) !== null && _a !== void 0 ? _a : undefined;
152
+ else {
153
+ key = this.previousKeys.get(count);
154
+ }
155
+ if (!key)
156
+ return undefined;
157
+ yield this.save();
158
+ return crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key);
159
+ });
142
160
  }
143
161
  /**
144
162
  * Export the state of the session;
145
163
  */
146
164
  toJSON() {
165
+ var _a, _b, _c, _d;
147
166
  return {
148
167
  secretKey: (0, utils_1.decodeBase64)((0, utils_1.concatArrays)(this.keyPair.secretKey)),
149
- remoteKey: (0, utils_1.decodeBase64)(this._remoteKey),
150
- rootKey: (0, utils_1.decodeBase64)(this.rootKey),
151
- sendingChain: (0, utils_1.decodeBase64)(this.sendingChain),
152
- receivingChain: (0, utils_1.decodeBase64)(this.receivingChain),
168
+ remoteKey: (0, utils_1.decodeBase64)((_a = this._remoteKey) !== null && _a !== void 0 ? _a : new Uint8Array()),
169
+ rootKey: (0, utils_1.decodeBase64)((_b = this.rootKey) !== null && _b !== void 0 ? _b : new Uint8Array()),
170
+ sendingChain: (0, utils_1.decodeBase64)((_c = this.sendingChain) !== null && _c !== void 0 ? _c : new Uint8Array()),
171
+ receivingChain: (0, utils_1.decodeBase64)((_d = this.receivingChain) !== null && _d !== void 0 ? _d : new Uint8Array()),
153
172
  sendingCount: this.sendingCount,
154
173
  receivingCount: this.receivingCount,
155
174
  previousCount: this.previousCount,
@@ -162,8 +181,8 @@ class KeySession {
162
181
  * @param json string returned by `export()` method.
163
182
  * @returns session with the state parsed.
164
183
  */
165
- static from(data) {
166
- const session = new KeySession({ secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: (0, utils_1.encodeBase64)(data.rootKey) });
184
+ static from(data, storage) {
185
+ const session = new KeySession(storage, { secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: (0, utils_1.encodeBase64)(data.rootKey) });
167
186
  session._remoteKey = (0, utils_1.encodeBase64)(data.remoteKey);
168
187
  session.sendingChain = (0, utils_1.encodeBase64)(data.sendingChain);
169
188
  session.receivingChain = (0, utils_1.encodeBase64)(data.receivingChain);
@@ -171,6 +190,7 @@ class KeySession {
171
190
  session.receivingCount = data.receivingCount;
172
191
  session.previousCount = data.previousCount;
173
192
  session.previousKeys = new KeyMap(data.previousKeys);
193
+ session.save();
174
194
  return session;
175
195
  }
176
196
  static symmetricRatchet(chain, salt, info) {
@@ -190,6 +210,80 @@ KeySession.rootKeyLength = crypto_1.default.box.keyLength;
190
210
  * Typically 32 bytes (256 bits) for symmetric keys.
191
211
  */
192
212
  KeySession.keyLength = 32;
213
+ class EncryptedDataConstructor {
214
+ constructor(...arrays) {
215
+ arrays = arrays.filter(value => value !== undefined);
216
+ if (arrays[0] instanceof EncryptedDataConstructor) {
217
+ this.raw = arrays[0].raw;
218
+ return this;
219
+ }
220
+ if (typeof arrays[0] === 'number')
221
+ arrays[0] = (0, utils_1.numberToArray)(arrays[0], EncryptedDataConstructor.countLength);
222
+ if (typeof arrays[1] === 'number')
223
+ arrays[1] = (0, utils_1.numberToArray)(arrays[1], EncryptedDataConstructor.countLength);
224
+ if (arrays.length === 6) {
225
+ arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToArray)(arrays[5]) : arrays[5]);
226
+ arrays.pop();
227
+ }
228
+ else if (arrays.length > 1) {
229
+ arrays.unshift((0, utils_1.numberToArray)(KeySession.version));
230
+ }
231
+ this.raw = (0, utils_1.concatArrays)(...arrays);
232
+ }
233
+ get length() { return this.raw.length; }
234
+ get version() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
235
+ get count() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
236
+ get previous() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
237
+ get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
238
+ get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
239
+ get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
240
+ toBytes() {
241
+ return this.raw;
242
+ }
243
+ toString() {
244
+ return (0, utils_1.decodeBase64)(this.raw);
245
+ }
246
+ toJSON() {
247
+ return {
248
+ version: this.version,
249
+ count: this.count,
250
+ previous: this.previous,
251
+ publicKey: (0, utils_1.decodeBase64)(this.publicKey),
252
+ nonce: (0, utils_1.decodeBase64)(this.nonce),
253
+ ciphertext: (0, utils_1.decodeBase64)(this.ciphertext)
254
+ };
255
+ }
256
+ }
257
+ exports.EncryptedDataConstructor = EncryptedDataConstructor;
258
+ EncryptedDataConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
259
+ EncryptedDataConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
260
+ EncryptedDataConstructor.keyLength = crypto_1.default.box.keyLength;
261
+ EncryptedDataConstructor.nonceLength = crypto_1.default.box.nonceLength;
262
+ EncryptedDataConstructor.maxCount = 65536; //32768;
263
+ EncryptedDataConstructor.countLength = 2;
264
+ class Offsets {
265
+ static set(start, length) {
266
+ class Offset {
267
+ constructor(start, length) {
268
+ this.start = start;
269
+ this.length = length;
270
+ if (typeof length === 'number')
271
+ this.end = start + length;
272
+ }
273
+ get get() {
274
+ return [this.start, this.length];
275
+ }
276
+ }
277
+ return new Offset(start, length);
278
+ }
279
+ }
280
+ Offsets.checksum = Offsets.set(0, 0);
281
+ Offsets.version = Offsets.set(Offsets.checksum.end, 1);
282
+ Offsets.count = Offsets.set(Offsets.version.end, EncryptedDataConstructor.countLength);
283
+ Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.countLength);
284
+ Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
285
+ Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
286
+ Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
193
287
  class KeyMap extends Map {
194
288
  get(key) {
195
289
  const out = super.get(key);
package/index.d.ts CHANGED
@@ -16,10 +16,10 @@
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 crypto from "@freesignal/crypto";
20
19
  import { LocalStorage, Crypto } from "@freesignal/interfaces";
21
- import { KeySession } from "./double-ratchet";
20
+ import { ExportedKeySession, KeySession } from "./double-ratchet";
22
21
  import { KeyExchange } from "./x3dh";
22
+ import { PrivateIdentityKey } from "./types";
23
23
  /**
24
24
  * Creates a new Double Ratchet session for secure message exchange.
25
25
  *
@@ -29,7 +29,7 @@ import { KeyExchange } from "./x3dh";
29
29
  * @param opts.rootKey - An optional root key to initialize the session.
30
30
  * @returns A new instance of {@link KeySession}.
31
31
  */
32
- export declare function createKeySession(opts?: {
32
+ export declare function createKeySession(storage: LocalStorage<string, ExportedKeySession>, opts?: {
33
33
  secretKey?: Uint8Array;
34
34
  remoteKey?: Uint8Array;
35
35
  rootKey?: Uint8Array;
@@ -37,21 +37,18 @@ export declare function createKeySession(opts?: {
37
37
  /**
38
38
  * Creates a new X3DH (Extended Triple Diffie-Hellman) key exchange session.
39
39
  *
40
- * @param signSecretKey - The EdDSA signing secret key as a Uint8Array.
41
- * @param boxSecretKey - The ECDH box secret key as a Uint8Array.
42
- * @param bundleStore - Optional local storage for key bundles.
40
+ * @param storage - Local storage for keys.
43
41
  * @returns A new instance of {@link KeyExchange}.
44
42
  */
45
- export declare function createKeyExchange(signSecretKey: Uint8Array, boxSecretKey: Uint8Array, bundleStore?: LocalStorage<string, crypto.KeyPair>): KeyExchange;
43
+ export declare function createKeyExchange(storage: {
44
+ keys: LocalStorage<string, Crypto.KeyPair>;
45
+ sessions: LocalStorage<string, ExportedKeySession>;
46
+ }, privateIdentityKey?: PrivateIdentityKey): KeyExchange;
46
47
  /**
47
- * Generates identity key pairs for signing and encryption.
48
+ * Generates identity key
48
49
  *
49
- * @param signSecretKey - Optional secret key for EdDSA signing.
50
- * @param boxSecretKey - Optional secret key for ECDH encryption.
50
+ * @param seed - Seed to generate the key.
51
51
  * @returns An object containing readonly signing and box key pairs.
52
52
  */
53
- export declare function createIdentityKeys(signSecretKey?: Uint8Array, boxSecretKey?: Uint8Array): {
54
- readonly sign: Crypto.KeyPair;
55
- readonly box: Crypto.KeyPair;
56
- };
57
- export { UserId, IdentityKeys, Protocols, Datagram, EncryptedData } from "./types";
53
+ export declare function createIdentity(seed?: Uint8Array): PrivateIdentityKey;
54
+ export * from "./types";
package/index.js CHANGED
@@ -17,17 +17,31 @@
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 __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
+ };
20
34
  var __importDefault = (this && this.__importDefault) || function (mod) {
21
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
22
36
  };
23
37
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.EncryptedData = exports.Datagram = exports.Protocols = exports.IdentityKeys = exports.UserId = void 0;
25
38
  exports.createKeySession = createKeySession;
26
39
  exports.createKeyExchange = createKeyExchange;
27
- exports.createIdentityKeys = createIdentityKeys;
40
+ exports.createIdentity = createIdentity;
28
41
  const crypto_1 = __importDefault(require("@freesignal/crypto"));
29
42
  const double_ratchet_1 = require("./double-ratchet");
30
43
  const x3dh_1 = require("./x3dh");
44
+ const types_1 = require("./types");
31
45
  /**
32
46
  * Creates a new Double Ratchet session for secure message exchange.
33
47
  *
@@ -37,33 +51,30 @@ const x3dh_1 = require("./x3dh");
37
51
  * @param opts.rootKey - An optional root key to initialize the session.
38
52
  * @returns A new instance of {@link KeySession}.
39
53
  */
40
- function createKeySession(opts) {
41
- return new double_ratchet_1.KeySession(opts);
54
+ function createKeySession(storage, opts) {
55
+ return new double_ratchet_1.KeySession(storage, opts);
42
56
  }
43
57
  /**
44
58
  * Creates a new X3DH (Extended Triple Diffie-Hellman) key exchange session.
45
59
  *
46
- * @param signSecretKey - The EdDSA signing secret key as a Uint8Array.
47
- * @param boxSecretKey - The ECDH box secret key as a Uint8Array.
48
- * @param bundleStore - Optional local storage for key bundles.
60
+ * @param storage - Local storage for keys.
49
61
  * @returns A new instance of {@link KeyExchange}.
50
62
  */
51
- function createKeyExchange(signSecretKey, boxSecretKey, bundleStore) {
52
- return new x3dh_1.KeyExchange(signSecretKey, boxSecretKey, bundleStore);
63
+ function createKeyExchange(storage, privateIdentityKey) {
64
+ return new x3dh_1.KeyExchange(storage, privateIdentityKey);
53
65
  }
54
66
  /**
55
- * Generates identity key pairs for signing and encryption.
67
+ * Generates identity key
56
68
  *
57
- * @param signSecretKey - Optional secret key for EdDSA signing.
58
- * @param boxSecretKey - Optional secret key for ECDH encryption.
69
+ * @param seed - Seed to generate the key.
59
70
  * @returns An object containing readonly signing and box key pairs.
60
71
  */
61
- function createIdentityKeys(signSecretKey, boxSecretKey) {
62
- return { sign: crypto_1.default.EdDSA.keyPair(signSecretKey), box: crypto_1.default.ECDH.keyPair(boxSecretKey) };
72
+ function createIdentity(seed) {
73
+ seed !== null && seed !== void 0 ? seed : (seed = crypto_1.default.randomBytes(crypto_1.default.EdDSA.seedLength));
74
+ const signatureSeed = crypto_1.default.hkdf(seed, new Uint8Array(crypto_1.default.EdDSA.seedLength).fill(0), "identity-ed25519", crypto_1.default.EdDSA.seedLength);
75
+ const exchangeSeed = crypto_1.default.hkdf(seed, new Uint8Array(crypto_1.default.ECDH.secretKeyLength).fill(0), "identity-x25519", crypto_1.default.ECDH.secretKeyLength);
76
+ const signatureKeyPair = crypto_1.default.EdDSA.keyPairFromSeed(signatureSeed);
77
+ const exchangeKeyPair = crypto_1.default.ECDH.keyPair(exchangeSeed);
78
+ return types_1.PrivateIdentityKey.from(signatureKeyPair.secretKey, exchangeKeyPair.secretKey);
63
79
  }
64
- var types_1 = require("./types");
65
- Object.defineProperty(exports, "UserId", { enumerable: true, get: function () { return types_1.UserId; } });
66
- Object.defineProperty(exports, "IdentityKeys", { enumerable: true, get: function () { return types_1.IdentityKeys; } });
67
- Object.defineProperty(exports, "Protocols", { enumerable: true, get: function () { return types_1.Protocols; } });
68
- Object.defineProperty(exports, "Datagram", { enumerable: true, get: function () { return types_1.Datagram; } });
69
- Object.defineProperty(exports, "EncryptedData", { enumerable: true, get: function () { return types_1.EncryptedData; } });
80
+ __exportStar(require("./types"), exports);
package/node.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
2
+ import { Datagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
3
+ import { KeyExchange } from "./x3dh";
4
+ import { ExportedKeySession, KeySession } from "./double-ratchet";
5
+ export declare class FreeSignalNode {
6
+ protected readonly privateIdentityKey: PrivateIdentityKey;
7
+ protected readonly sessions: Map<string, KeySession>;
8
+ protected readonly sessionsData: LocalStorage<string, ExportedKeySession>;
9
+ protected readonly users: LocalStorage<string, IdentityKey>;
10
+ protected readonly keyExchange: KeyExchange;
11
+ constructor(storage: Database<{
12
+ sessions: LocalStorage<string, ExportedKeySession>;
13
+ keyExchange: LocalStorage<string, Crypto.KeyPair>;
14
+ users: LocalStorage<string, IdentityKey>;
15
+ }>, privateIdentityKey?: PrivateIdentityKey);
16
+ get userId(): UserId;
17
+ get identityKey(): IdentityKey;
18
+ generateKeyData(): Promise<KeyExchangeData>;
19
+ generateKeyBundle(length?: number): Promise<KeyExchangeDataBundle>;
20
+ encrypt(receiverId: string, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
21
+ sendHandshake(data: KeyExchangeData): Promise<Datagram>;
22
+ sendData<T>(receiverId: string, data: T): Promise<Datagram>;
23
+ sendRelay(receiverId: string, data: Datagram): Promise<Datagram>;
24
+ sendDiscover(receiverId: string, discoverId: string): Promise<Datagram>;
25
+ decrypt(datagram: Datagram): Promise<Uint8Array>;
26
+ receive<T extends Uint8Array | UserId | Datagram | UserId | void>(datagram: Datagram | Uint8Array): Promise<T>;
27
+ }
package/node.js ADDED
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.FreeSignalNode = void 0;
13
+ const types_1 = require("./types");
14
+ const x3dh_1 = require("./x3dh");
15
+ const _1 = require(".");
16
+ const utils_1 = require("@freesignal/utils");
17
+ class FreeSignalNode {
18
+ constructor(storage, privateIdentityKey) {
19
+ this.sessions = new Map();
20
+ this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
21
+ this.sessionsData = storage.sessions;
22
+ this.keyExchange = new x3dh_1.KeyExchange({ keys: storage.keyExchange, sessions: storage.sessions }, this.privateIdentityKey);
23
+ this.users = storage.users;
24
+ /*Array.from(this.sessionsData.entries()).forEach(([userId, sessionData]) => {
25
+ this.sessions.set(userId, KeySession.from(sessionData, this.sessionsData));
26
+ });*/
27
+ }
28
+ get userId() {
29
+ return types_1.UserId.fromKey(this.privateIdentityKey.identityKey);
30
+ }
31
+ get identityKey() {
32
+ return this.privateIdentityKey.identityKey;
33
+ }
34
+ generateKeyData() {
35
+ return this.keyExchange.generateData();
36
+ }
37
+ ;
38
+ generateKeyBundle(length) {
39
+ return this.keyExchange.generateBundle(length);
40
+ }
41
+ ;
42
+ encrypt(receiverId, protocol, data) {
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ const session = this.sessions.get(receiverId);
45
+ if (!session)
46
+ throw new Error("Session not found for user: " + receiverId);
47
+ return new types_1.Datagram(this.userId.toString(), receiverId, protocol, yield session.encrypt(data));
48
+ });
49
+ }
50
+ sendHandshake(data) {
51
+ return __awaiter(this, void 0, void 0, function* () {
52
+ const { session, message, identityKey } = yield this.keyExchange.digestData(data);
53
+ const remoteId = types_1.UserId.fromKey(identityKey);
54
+ this.sessions.set(remoteId.toString(), session);
55
+ return new types_1.Datagram(this.userId.toString(), types_1.UserId.fromKey(data.identityKey).toString(), types_1.Protocols.HANDSHAKE, (0, utils_1.encodeData)(message));
56
+ });
57
+ }
58
+ sendData(receiverId, data) {
59
+ return this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
60
+ }
61
+ sendRelay(receiverId, data) {
62
+ return this.encrypt(receiverId, types_1.Protocols.RELAY, (0, utils_1.encodeData)(data));
63
+ }
64
+ sendDiscover(receiverId, discoverId) {
65
+ return this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(discoverId));
66
+ }
67
+ decrypt(datagram) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ const userId = datagram.sender;
70
+ const session = this.sessions.get(userId);
71
+ if (!session)
72
+ throw new Error("Session not found for user: " + userId);
73
+ if (!datagram.payload)
74
+ throw new Error("Missing payload");
75
+ const decrypted = yield session.decrypt(datagram.payload);
76
+ if (!decrypted)
77
+ throw new Error("Decryption failed");
78
+ return decrypted;
79
+ });
80
+ }
81
+ receive(datagram) {
82
+ return __awaiter(this, void 0, void 0, function* () {
83
+ if (datagram instanceof Uint8Array)
84
+ datagram = types_1.Datagram.from(datagram);
85
+ switch (datagram.protocol) {
86
+ case types_1.Protocols.HANDSHAKE:
87
+ if (!datagram.payload)
88
+ throw new Error("Missing payload");
89
+ const data = (0, utils_1.decodeData)(datagram.payload);
90
+ const { session, identityKey } = yield this.keyExchange.digestMessage(data);
91
+ this.sessions.set(types_1.UserId.fromKey(identityKey).toString(), session);
92
+ return;
93
+ case types_1.Protocols.MESSAGE:
94
+ return yield this.decrypt(datagram);
95
+ case types_1.Protocols.RELAY:
96
+ return (0, utils_1.decodeData)(yield this.decrypt(datagram));
97
+ case types_1.Protocols.DISCOVER:
98
+ return types_1.UserId.from((0, utils_1.decodeData)(yield this.decrypt(datagram)));
99
+ default:
100
+ throw new Error("Invalid protocol");
101
+ }
102
+ });
103
+ }
104
+ }
105
+ exports.FreeSignalNode = FreeSignalNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freesignal/protocol",
3
- "version": "0.2.8",
3
+ "version": "0.2.11",
4
4
  "description": "Signal Protocol implementation in javascript",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "Christian Braghette",
@@ -12,13 +12,8 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@freesignal/crypto": "^0.3.0",
15
- "@freesignal/interfaces": "^0.1.1",
16
- "@freesignal/utils": "^1.2.0",
17
- "base64-js": "^1.5.1",
18
- "fflate": "^0.8.2",
19
- "js-sha3": "^0.9.3",
20
- "tweetnacl": "^1.0.3",
21
- "uuid": "^11.1.0"
15
+ "@freesignal/interfaces": "^0.2.0",
16
+ "@freesignal/utils": "^1.3.0"
22
17
  },
23
18
  "devDependencies": {
24
19
  "@types/node": "^24.2.1"