@freesignal/protocol 0.2.9 → 0.3.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,70 +16,61 @@
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;
22
- remoteKey: string;
23
- rootKey: string;
24
- sendingChain: string;
25
- receivingChain: string;
26
- sendingCount: number;
27
- receivingCount: number;
28
- previousCount: number;
29
- previousKeys: [number, Uint8Array][];
23
+ rootKey?: string;
24
+ sendingChain?: ExportedKeyChain;
25
+ receivingChain?: ExportedKeyChain;
26
+ previousKeys: [string, Uint8Array][];
30
27
  }
31
28
  /**
32
29
  * Represents a secure Double Ratchet session.
33
30
  * Used for forward-secure encryption and decryption of messages.
34
31
  */
35
32
  export declare class KeySession {
36
- private static readonly skipLimit;
33
+ static readonly keyLength = 32;
37
34
  static readonly version = 1;
38
- static readonly rootKeyLength: number;
35
+ static readonly info: string;
36
+ readonly id: string;
37
+ private readonly mutex;
38
+ private readonly storage;
39
39
  private keyPair;
40
- private _remoteKey?;
41
40
  private rootKey?;
42
41
  private sendingChain?;
43
- private sendingCount;
44
- private previousCount;
45
42
  private receivingChain?;
46
- private receivingCount;
47
43
  private previousKeys;
48
- constructor(opts?: {
44
+ constructor(storage: LocalStorage<string, ExportedKeySession>, opts?: {
45
+ id?: string;
49
46
  secretKey?: Uint8Array;
50
47
  remoteKey?: Uint8Array;
51
48
  rootKey?: Uint8Array;
52
49
  });
53
- /**
54
- * Whether both the sending and receiving chains are initialized.
55
- */
56
- get handshaked(): boolean;
57
- /**
58
- * The public key of this session.
59
- */
60
- get publicKey(): Uint8Array;
61
- /**
62
- * The last known remote public key.
63
- */
64
- get remoteKey(): Uint8Array | undefined;
65
- private setRemoteKey;
66
- private ratchetKeys;
67
- private getSendingKey;
68
- private getReceivingKey;
50
+ private getChain;
51
+ private save;
69
52
  /**
70
53
  * Encrypts a message payload using the current sending chain.
71
54
  *
72
55
  * @param message - The message as a Uint8Array.
73
56
  * @returns An EncryptedPayload or undefined if encryption fails.
74
57
  */
75
- encrypt(message: Uint8Array): EncryptedData;
58
+ encrypt(message: Uint8Array): Promise<EncryptedData>;
76
59
  /**
77
60
  * Decrypts an encrypted message.
78
61
  *
79
62
  * @param payload - The received encrypted message.
80
63
  * @returns The decrypted message as a Uint8Array, or undefined if decryption fails.
81
64
  */
82
- decrypt(payload: Uint8Array | EncryptedData): Uint8Array | undefined;
65
+ decrypt(payload: Uint8Array | EncryptedData): Promise<Uint8Array>;
66
+ /**
67
+ * Whether both the sending and receiving chains are initialized.
68
+ */
69
+ get handshaked(): boolean;
70
+ /**
71
+ * The public key of this session.
72
+ */
73
+ get publicKey(): Uint8Array;
83
74
  /**
84
75
  * Export the state of the session;
85
76
  */
@@ -90,11 +81,41 @@ export declare class KeySession {
90
81
  * @param json string returned by `export()` method.
91
82
  * @returns session with the state parsed.
92
83
  */
93
- static from(data: ExportedKeySession): KeySession;
94
- /**
95
- * The fixed key length (in bytes) used throughout the Double Ratchet session.
96
- * Typically 32 bytes (256 bits) for symmetric keys.
97
- */
98
- static readonly keyLength = 32;
99
- private static symmetricRatchet;
84
+ static from(data: ExportedKeySession, storage: LocalStorage<string, ExportedKeySession>): KeySession;
85
+ }
86
+ interface ExportedKeyChain {
87
+ publicKey: string;
88
+ remoteKey: string;
89
+ chainKey: string;
90
+ count: number;
91
+ previousCount: number;
92
+ }
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
+ };
100
120
  }
121
+ export {};
package/double-ratchet.js CHANGED
@@ -17,81 +17,105 @@
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
+ });
20
81
  var __importDefault = (this && this.__importDefault) || function (mod) {
21
82
  return (mod && mod.__esModule) ? mod : { "default": mod };
22
83
  };
23
84
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.KeySession = void 0;
85
+ exports.EncryptedDataConstructor = exports.KeySession = void 0;
25
86
  const crypto_1 = __importDefault(require("@freesignal/crypto"));
26
87
  const utils_1 = require("@freesignal/utils");
27
88
  const types_1 = require("./types");
89
+ const semaphore_ts_1 = require("semaphore.ts");
28
90
  /**
29
91
  * Represents a secure Double Ratchet session.
30
92
  * Used for forward-secure encryption and decryption of messages.
31
93
  */
32
94
  class KeySession {
33
- constructor(opts = {}) {
34
- this.sendingCount = 0;
35
- this.previousCount = 0;
36
- this.receivingCount = 0;
95
+ constructor(storage, opts = {}) {
96
+ var _a;
97
+ this.mutex = new semaphore_ts_1.AsyncMutex();
37
98
  this.previousKeys = new KeyMap();
99
+ this.id = (_a = opts.id) !== null && _a !== void 0 ? _a : crypto_1.default.UUID.generate().toString();
38
100
  this.keyPair = crypto_1.default.ECDH.keyPair(opts.secretKey);
39
101
  if (opts.rootKey)
40
102
  this.rootKey = opts.rootKey;
41
103
  if (opts.remoteKey) {
42
- this._remoteKey = opts.remoteKey;
43
- this.sendingChain = this.ratchetKeys();
104
+ this.sendingChain = this.getChain(opts.remoteKey);
44
105
  }
106
+ this.storage = storage;
107
+ this.save();
45
108
  }
46
- /**
47
- * Whether both the sending and receiving chains are initialized.
48
- */
49
- get handshaked() { return this.sendingChain && this.receivingChain ? true : false; }
50
- /**
51
- * The public key of this session.
52
- */
53
- get publicKey() { return this.keyPair.publicKey; }
54
- /**
55
- * The last known remote public key.
56
- */
57
- get remoteKey() { return this._remoteKey; }
58
- setRemoteKey(key) {
59
- this._remoteKey = key;
60
- this.receivingChain = this.ratchetKeys();
61
- if (this.receivingCount > (types_1.EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
62
- this.receivingCount = 0;
63
- this.previousCount = this.sendingCount;
64
- this.keyPair = crypto_1.default.ECDH.keyPair();
65
- this.sendingChain = this.ratchetKeys();
66
- if (this.sendingCount > (types_1.EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
67
- this.sendingCount = 0;
68
- return this;
69
- }
70
- ratchetKeys(info) {
71
- if (!this._remoteKey)
72
- throw new Error();
73
- const sharedKey = crypto_1.default.ECDH.scalarMult(this.keyPair.secretKey, this._remoteKey);
109
+ getChain(remoteKey, previousCount) {
110
+ const sharedKey = crypto_1.default.ECDH.scalarMult(this.keyPair.secretKey, remoteKey);
74
111
  if (!this.rootKey)
75
112
  this.rootKey = crypto_1.default.hash(sharedKey);
76
- const hashkey = crypto_1.default.hkdf(sharedKey, this.rootKey, info, KeySession.keyLength * 2);
77
- this.rootKey = hashkey.slice(0, KeySession.keyLength);
78
- return hashkey.slice(KeySession.keyLength);
79
- }
80
- getSendingKey() {
81
- if (!this.sendingChain)
82
- throw new Error;
83
- const { chainKey, sharedKey } = KeySession.symmetricRatchet(this.sendingChain);
84
- this.sendingChain = chainKey;
85
- this.sendingCount++;
86
- return sharedKey;
87
- }
88
- getReceivingKey() {
89
- if (!this.receivingChain)
90
- throw new Error();
91
- const { chainKey, sharedKey } = KeySession.symmetricRatchet(this.receivingChain);
92
- this.receivingChain = chainKey;
93
- this.receivingCount++;
94
- return sharedKey;
113
+ const hashkey = crypto_1.default.hkdf(sharedKey, this.rootKey, KeySession.info, KeySession.keyLength * 2);
114
+ this.rootKey = hashkey.subarray(0, KeySession.keyLength);
115
+ return new KeyChain(this.publicKey, remoteKey, hashkey.subarray(KeySession.keyLength), previousCount);
116
+ }
117
+ save() {
118
+ return this.storage.set(this.id, this.toJSON());
95
119
  }
96
120
  /**
97
121
  * Encrypts a message payload using the current sending chain.
@@ -100,12 +124,26 @@ class KeySession {
100
124
  * @returns An EncryptedPayload or undefined if encryption fails.
101
125
  */
102
126
  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);
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.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
+ yield 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
+ });
109
147
  }
110
148
  /**
111
149
  * Decrypts an encrypted message.
@@ -114,45 +152,63 @@ class KeySession {
114
152
  * @returns The decrypted message as a Uint8Array, or undefined if decryption fails.
115
153
  */
116
154
  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());
125
- 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());
155
+ return __awaiter(this, void 0, void 0, function* () {
156
+ var _a, _b, _c;
157
+ const env_2 = { stack: [], error: void 0, hasError: false };
158
+ try {
159
+ const lock = __addDisposableResource(env_2, yield this.mutex.acquire(), false);
160
+ const encrypted = types_1.EncryptedData.from(payload);
161
+ if (!(0, utils_1.verifyArrays)(encrypted.publicKey, (_b = (_a = this.receivingChain) === null || _a === void 0 ? void 0 : _a.remoteKey) !== null && _b !== void 0 ? _b : new Uint8Array())) {
162
+ while (this.receivingChain && this.receivingChain.count < encrypted.previous) {
163
+ const key = this.receivingChain.getKey();
164
+ this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
165
+ }
166
+ this.receivingChain = this.getChain(encrypted.publicKey);
167
+ this.keyPair = crypto_1.default.ECDH.keyPair();
168
+ this.sendingChain = this.getChain(encrypted.publicKey, (_c = this.sendingChain) === null || _c === void 0 ? void 0 : _c.count);
169
+ }
170
+ if (!this.receivingChain)
171
+ throw new Error("Error initializing receivingChain");
172
+ while (this.receivingChain.count < encrypted.count) {
173
+ const key = this.receivingChain.getKey();
174
+ this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
175
+ }
176
+ const key = this.previousKeys.get((0, utils_1.decodeBase64)(encrypted.publicKey) + encrypted.count.toString());
177
+ if (!key)
178
+ throw new Error("Error calculating key");
179
+ yield this.save();
180
+ const cleartext = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key);
181
+ if (!cleartext)
182
+ throw new Error("Error decrypting ciphertext");
183
+ return cleartext;
133
184
  }
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;
185
+ catch (e_2) {
186
+ env_2.error = e_2;
187
+ env_2.hasError = true;
188
+ }
189
+ finally {
190
+ __disposeResources(env_2);
191
+ }
192
+ });
142
193
  }
194
+ /**
195
+ * Whether both the sending and receiving chains are initialized.
196
+ */
197
+ get handshaked() { return this.sendingChain && this.receivingChain ? true : false; }
198
+ /**
199
+ * The public key of this session.
200
+ */
201
+ get publicKey() { return this.keyPair.publicKey; }
143
202
  /**
144
203
  * Export the state of the session;
145
204
  */
146
205
  toJSON() {
206
+ var _a, _b;
147
207
  return {
148
- 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),
153
- sendingCount: this.sendingCount,
154
- receivingCount: this.receivingCount,
155
- previousCount: this.previousCount,
208
+ secretKey: (0, utils_1.decodeBase64)(this.keyPair.secretKey),
209
+ rootKey: this.rootKey ? (0, utils_1.decodeBase64)(this.rootKey) : undefined,
210
+ sendingChain: (_a = this.sendingChain) === null || _a === void 0 ? void 0 : _a.toJSON(),
211
+ receivingChain: (_b = this.receivingChain) === null || _b === void 0 ? void 0 : _b.toJSON(),
156
212
  previousKeys: Array.from(this.previousKeys.entries())
157
213
  };
158
214
  }
@@ -162,34 +218,130 @@ class KeySession {
162
218
  * @param json string returned by `export()` method.
163
219
  * @returns session with the state parsed.
164
220
  */
165
- static from(data) {
166
- const session = new KeySession({ secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: (0, utils_1.encodeBase64)(data.rootKey) });
167
- session._remoteKey = (0, utils_1.encodeBase64)(data.remoteKey);
168
- session.sendingChain = (0, utils_1.encodeBase64)(data.sendingChain);
169
- session.receivingChain = (0, utils_1.encodeBase64)(data.receivingChain);
170
- session.sendingCount = data.sendingCount;
171
- session.receivingCount = data.receivingCount;
172
- session.previousCount = data.previousCount;
221
+ static from(data, storage) {
222
+ const session = new KeySession(storage, { secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: data.rootKey ? (0, utils_1.encodeBase64)(data.rootKey) : undefined });
223
+ //session._remoteKey = data.remoteKey ? encodeBase64(data.remoteKey) : undefined;
224
+ session.sendingChain = data.sendingChain ? KeyChain.from(data.sendingChain) : undefined;
225
+ session.receivingChain = data.receivingChain ? KeyChain.from(data.receivingChain) : undefined;
173
226
  session.previousKeys = new KeyMap(data.previousKeys);
227
+ session.save();
174
228
  return session;
175
229
  }
176
- static symmetricRatchet(chain, salt, info) {
177
- const hash = crypto_1.default.hkdf(chain, salt !== null && salt !== void 0 ? salt : new Uint8Array(), info, KeySession.keyLength * 2);
230
+ }
231
+ exports.KeySession = KeySession;
232
+ KeySession.keyLength = 32;
233
+ KeySession.version = 1;
234
+ KeySession.info = "/freesignal/double-ratchet/v0." + KeySession.version;
235
+ class KeyChain {
236
+ constructor(publicKey, remoteKey, chainKey, previousCount = 0) {
237
+ this.publicKey = publicKey;
238
+ this.remoteKey = remoteKey;
239
+ this.chainKey = chainKey;
240
+ this.previousCount = previousCount;
241
+ this._count = 0;
242
+ }
243
+ getKey() {
244
+ if (++this._count >= EncryptedDataConstructor.maxCount)
245
+ throw new Error("SendingChain count too big");
246
+ const hash = crypto_1.default.hkdf(this.chainKey, new Uint8Array(KeySession.keyLength).fill(0), KeySession.info, KeySession.keyLength * 2);
247
+ this.chainKey = hash.subarray(0, KeySession.keyLength);
248
+ return hash.subarray(KeySession.keyLength);
249
+ }
250
+ toString() {
251
+ return "[object KeyChain]";
252
+ }
253
+ get count() {
254
+ return this._count;
255
+ }
256
+ toJSON() {
178
257
  return {
179
- chainKey: new Uint8Array(hash.buffer, 0, KeySession.keyLength),
180
- sharedKey: new Uint8Array(hash.buffer, KeySession.keyLength)
258
+ publicKey: (0, utils_1.decodeBase64)(this.publicKey),
259
+ remoteKey: (0, utils_1.decodeBase64)(this.remoteKey),
260
+ chainKey: (0, utils_1.decodeBase64)(this.chainKey),
261
+ count: this.count,
262
+ previousCount: this.previousCount
181
263
  };
182
264
  }
265
+ static from(obj) {
266
+ const chain = new KeyChain((0, utils_1.encodeBase64)(obj.publicKey), (0, utils_1.encodeBase64)(obj.remoteKey), (0, utils_1.encodeBase64)(obj.chainKey), obj.previousCount);
267
+ chain._count = obj.count;
268
+ return chain;
269
+ }
183
270
  }
184
- exports.KeySession = KeySession;
185
- KeySession.skipLimit = 1000;
186
- KeySession.version = 1;
187
- KeySession.rootKeyLength = crypto_1.default.box.keyLength;
188
- /**
189
- * The fixed key length (in bytes) used throughout the Double Ratchet session.
190
- * Typically 32 bytes (256 bits) for symmetric keys.
191
- */
192
- KeySession.keyLength = 32;
271
+ class EncryptedDataConstructor {
272
+ constructor(...arrays) {
273
+ arrays = arrays.filter(value => value !== undefined);
274
+ if (arrays[0] instanceof EncryptedDataConstructor) {
275
+ this.raw = arrays[0].raw;
276
+ return this;
277
+ }
278
+ if (typeof arrays[0] === 'number')
279
+ arrays[0] = (0, utils_1.numberToArray)(arrays[0], EncryptedDataConstructor.countLength);
280
+ if (typeof arrays[1] === 'number')
281
+ arrays[1] = (0, utils_1.numberToArray)(arrays[1], EncryptedDataConstructor.countLength);
282
+ if (arrays.length === 6) {
283
+ arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToArray)(arrays[5]) : arrays[5]);
284
+ arrays.pop();
285
+ }
286
+ else if (arrays.length > 1) {
287
+ arrays.unshift((0, utils_1.numberToArray)(KeySession.version));
288
+ }
289
+ this.raw = (0, utils_1.concatArrays)(...arrays);
290
+ }
291
+ get length() { return this.raw.length; }
292
+ get version() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
293
+ get count() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
294
+ get previous() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
295
+ get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
296
+ get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
297
+ get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
298
+ toBytes() {
299
+ return this.raw;
300
+ }
301
+ toString() {
302
+ return (0, utils_1.decodeBase64)(this.raw);
303
+ }
304
+ toJSON() {
305
+ return {
306
+ version: this.version,
307
+ count: this.count,
308
+ previous: this.previous,
309
+ publicKey: (0, utils_1.decodeBase64)(this.publicKey),
310
+ nonce: (0, utils_1.decodeBase64)(this.nonce),
311
+ ciphertext: (0, utils_1.decodeBase64)(this.ciphertext)
312
+ };
313
+ }
314
+ }
315
+ exports.EncryptedDataConstructor = EncryptedDataConstructor;
316
+ EncryptedDataConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
317
+ EncryptedDataConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
318
+ EncryptedDataConstructor.keyLength = crypto_1.default.box.keyLength;
319
+ EncryptedDataConstructor.nonceLength = crypto_1.default.box.nonceLength;
320
+ EncryptedDataConstructor.maxCount = 65536; //32768;
321
+ EncryptedDataConstructor.countLength = 2;
322
+ class Offsets {
323
+ static set(start, length) {
324
+ class Offset {
325
+ constructor(start, length) {
326
+ this.start = start;
327
+ this.length = length;
328
+ if (typeof length === 'number')
329
+ this.end = start + length;
330
+ }
331
+ get get() {
332
+ return [this.start, this.length];
333
+ }
334
+ }
335
+ return new Offset(start, length);
336
+ }
337
+ }
338
+ Offsets.checksum = Offsets.set(0, 0);
339
+ Offsets.version = Offsets.set(Offsets.checksum.end, 1);
340
+ Offsets.count = Offsets.set(Offsets.version.end, EncryptedDataConstructor.countLength);
341
+ Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.countLength);
342
+ Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
343
+ Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
344
+ Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
193
345
  class KeyMap extends Map {
194
346
  get(key) {
195
347
  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
- import { LocalStorage, Crypto } from "@freesignal/interfaces";
21
- import { KeySession } from "./double-ratchet";
22
- import { KeyExchange } from "./x3dh";
19
+ import { LocalStorage, Crypto, Database } from "@freesignal/interfaces";
20
+ import { ExportedKeySession } from "./double-ratchet";
21
+ import { IdentityKey, PrivateIdentityKey } from "./types";
22
+ import { FreeSignalNode } from "./node";
23
23
  /**
24
24
  * Creates a new Double Ratchet session for secure message exchange.
25
25
  *
@@ -29,29 +29,23 @@ 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?: {
33
- secretKey?: Uint8Array;
34
- remoteKey?: Uint8Array;
35
- rootKey?: Uint8Array;
36
- }): KeySession;
37
32
  /**
38
33
  * Creates a new X3DH (Extended Triple Diffie-Hellman) key exchange session.
39
34
  *
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.
35
+ * @param storage - Local storage for keys.
43
36
  * @returns A new instance of {@link KeyExchange}.
44
37
  */
45
- export declare function createKeyExchange(signSecretKey: Uint8Array, boxSecretKey: Uint8Array, bundleStore?: LocalStorage<string, crypto.KeyPair>): KeyExchange;
46
38
  /**
47
- * Generates identity key pairs for signing and encryption.
39
+ * Generates identity key
48
40
  *
49
- * @param signSecretKey - Optional secret key for EdDSA signing.
50
- * @param boxSecretKey - Optional secret key for ECDH encryption.
41
+ * @param seed - Seed to generate the key.
51
42
  * @returns An object containing readonly signing and box key pairs.
52
43
  */
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";
44
+ export declare function createIdentity(seed?: Uint8Array): PrivateIdentityKey;
45
+ /** */
46
+ export declare function createNode(storage: Database<{
47
+ sessions: LocalStorage<string, ExportedKeySession>;
48
+ keyExchange: LocalStorage<string, Crypto.KeyPair>;
49
+ users: LocalStorage<string, IdentityKey>;
50
+ }>, privateIdentityKey?: PrivateIdentityKey): FreeSignalNode;
51
+ export * from "./types";