@freesignal/protocol 0.8.3 → 0.9.1

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.
@@ -21,8 +21,8 @@ export interface ExportedKeySession {
21
21
  identityKey: string;
22
22
  secretKey: string;
23
23
  rootKey: string;
24
- sendingChain?: ExportedKeyChain;
25
- receivingChain?: ExportedKeyChain;
24
+ sendingChain?: KeyChainState;
25
+ receivingChain?: KeyChainState;
26
26
  headerKey?: string;
27
27
  headerKeys: [string, Uint8Array][];
28
28
  previousKeys: [string, Uint8Array][];
@@ -87,7 +87,7 @@ export declare class KeySession {
87
87
  */
88
88
  static from(data: ExportedKeySession): KeySession;
89
89
  }
90
- interface ExportedKeyChain {
90
+ interface KeyChainState {
91
91
  publicKey: string;
92
92
  remoteKey: string;
93
93
  chainKey: string;
package/dist/index.d.ts CHANGED
@@ -16,10 +16,8 @@
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, Crypto, Database, KeyExchangeDataBundle } from "@freesignal/interfaces";
20
- import { ExportedKeySession } from "./double-ratchet.js";
21
19
  import { PrivateIdentityKey } from "./types.js";
22
- import { BootstrapRequest, FreeSignalNode } from "./node.js";
20
+ import { FreeSignalNode, FreeSignalNodeState } from "./node.js";
23
21
  /**
24
22
  * Generates identity key
25
23
  *
@@ -28,11 +26,5 @@ import { BootstrapRequest, FreeSignalNode } from "./node.js";
28
26
  */
29
27
  export declare function createIdentity(seed?: Uint8Array): PrivateIdentityKey;
30
28
  /** */
31
- export declare function createNode(storage: Database<{
32
- sessions: LocalStorage<string, ExportedKeySession>;
33
- users: LocalStorage<string, string>;
34
- keyExchange: LocalStorage<string, Crypto.KeyPair>;
35
- bundles: LocalStorage<string, KeyExchangeDataBundle>;
36
- bootstraps: LocalStorage<string, BootstrapRequest>;
37
- }>, privateIdentityKey?: PrivateIdentityKey): FreeSignalNode;
29
+ export declare function createNode(args?: Partial<FreeSignalNodeState>): FreeSignalNode;
38
30
  export * from "./types.js";
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ export function createIdentity(seed) {
35
35
  return PrivateIdentityKey.from(signatureKeyPair.secretKey, exchangeKeyPair.secretKey);
36
36
  }
37
37
  /** */
38
- export function createNode(storage, privateIdentityKey) {
39
- return new FreeSignalNode(storage, privateIdentityKey);
38
+ export function createNode(args) {
39
+ return new FreeSignalNode(args);
40
40
  }
41
41
  export * from "./types.js";
package/dist/node.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 { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
19
+ import { KeyExchangeDataBundle, KeyExchangeData, Crypto } from "@freesignal/interfaces";
20
20
  import { Datagram, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types.js";
21
21
  import { KeyExchange } from "./x3dh.js";
22
- import { ExportedKeySession, KeySession } from "./double-ratchet.js";
22
+ import { ExportedKeySession as KeySessionState, KeySession } from "./double-ratchet.js";
23
23
  import { EventEmitter, EventCallback } from "easyemitter.ts";
24
24
  export declare class BootstrapRequest extends EventEmitter<'change', BootstrapRequest> {
25
25
  #private;
@@ -50,22 +50,22 @@ export type MessageEventData = {
50
50
  session: KeySession;
51
51
  payload: Uint8Array;
52
52
  };
53
+ export type FreeSignalNodeState = {
54
+ privateIdentityKey: string;
55
+ sessions: Array<[string, KeySessionState]>;
56
+ users: Array<[string, string]>;
57
+ bundles: Array<[string, KeyExchangeDataBundle]>;
58
+ keyExchange: Array<[string, Crypto.KeyPair]>;
59
+ };
53
60
  export declare class FreeSignalNode {
54
61
  protected readonly privateIdentityKey: PrivateIdentityKey;
55
- protected readonly sessions: SessionMap;
56
- protected readonly users: LocalStorage<string, string>;
57
- protected readonly bundles: LocalStorage<string, KeyExchangeDataBundle>;
62
+ protected readonly sessions: Map<string, KeySession>;
63
+ protected readonly users: Map<string, string>;
64
+ protected readonly bundles: Map<string, KeyExchangeDataBundle>;
58
65
  protected readonly keyExchange: KeyExchange;
59
- protected readonly discovers: Set<string>;
60
- protected readonly bootstraps: LocalStorage<string, BootstrapRequest>;
61
- protected readonly emitter: EventEmitter<"message" | "send" | "handshake" | "bootstrap", NodeEventData>;
62
- constructor(storage: Database<{
63
- sessions: LocalStorage<string, ExportedKeySession>;
64
- users: LocalStorage<string, string>;
65
- keyExchange: LocalStorage<string, Crypto.KeyPair>;
66
- bundles: LocalStorage<string, KeyExchangeDataBundle>;
67
- bootstraps: LocalStorage<string, BootstrapRequest>;
68
- }>, privateIdentityKey?: PrivateIdentityKey);
66
+ protected readonly bootstraps: Map<string, BootstrapRequest>;
67
+ protected readonly emitter: EventEmitter<"error" | "debug" | "send" | "handshake" | "message" | "bootstrap", NodeEventData>;
68
+ constructor({ privateIdentityKey, sessions, users, bundles, keyExchange }?: Partial<FreeSignalNodeState>);
69
69
  protected messageHandler: EventCallback<NodeEventData, typeof this.emitter>;
70
70
  protected sendHandler: EventCallback<NodeEventData, typeof this.emitter>;
71
71
  protected handshakeHandler: EventCallback<NodeEventData, typeof this.emitter>;
@@ -74,7 +74,11 @@ export declare class FreeSignalNode {
74
74
  onSend: (data: Uint8Array) => void;
75
75
  onHandshake: (userId: UserId) => void;
76
76
  onRequest: (request: BootstrapRequest) => void;
77
- getRequest(userId: string): Promise<BootstrapRequest | undefined>;
77
+ onError: (error: any) => void;
78
+ onDebug: (...args: any[]) => void;
79
+ private error;
80
+ private debug;
81
+ getRequest(userId: string): BootstrapRequest | undefined;
78
82
  waitHandshaked(userId: UserId | string, timeout?: number): Promise<void>;
79
83
  get identityKey(): IdentityKey;
80
84
  get userId(): UserId;
@@ -84,23 +88,10 @@ export declare class FreeSignalNode {
84
88
  sendHandshake(userId: UserId | string): Promise<void>;
85
89
  sendData<T>(receiverId: string | UserId, data: T): Promise<void>;
86
90
  sendRelay(relayId: string | UserId, receiverId: string | UserId, data: Datagram): Promise<void>;
87
- sendDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<void>;
88
- packBootstrap(): Promise<Datagram>;
91
+ packBootstrap(): Datagram;
89
92
  sendBootstrap(receiverId: string | UserId): Promise<void>;
90
93
  protected decrypt(datagram: EncryptedDatagram | Datagram | Uint8Array): Promise<MessageEventData>;
91
- protected openHandshake(datagram: Datagram | EncryptedDatagram | Uint8Array): Promise<'syn' | 'ack'>;
94
+ protected openHandshake(datagram: Datagram | EncryptedDatagram | Uint8Array): Promise<"syn" | "ack">;
92
95
  protected open(datagram: Datagram | EncryptedDatagram | Uint8Array): Promise<void>;
96
+ toJSON(): FreeSignalNodeState;
93
97
  }
94
- declare class SessionMap implements LocalStorage<string, KeySession> {
95
- readonly storage: LocalStorage<string, ExportedKeySession>;
96
- readonly maxSize: number;
97
- private readonly cache;
98
- constructor(storage: LocalStorage<string, ExportedKeySession>, maxSize?: number);
99
- set(key: string, value: KeySession): Promise<void>;
100
- get(key: string): Promise<KeySession | undefined>;
101
- has(key: string): Promise<boolean>;
102
- delete(key: string): Promise<boolean>;
103
- clear(): Promise<void>;
104
- entries(): Promise<Iterable<[string, KeySession]>>;
105
- }
106
- export {};
package/dist/node.js CHANGED
@@ -37,7 +37,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
37
37
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
38
38
  };
39
39
  var _BootstrapRequest_status;
40
- import { Datagram, decryptData, DiscoverType, encryptData, EncryptedDatagram, IdentityKey, Protocols, UserId } from "./types.js";
40
+ import { Datagram, decryptData, encryptData, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types.js";
41
41
  import { KeyExchange } from "./x3dh.js";
42
42
  import { KeySession } from "./double-ratchet.js";
43
43
  import { createIdentity } from "./index.js";
@@ -71,8 +71,9 @@ export class BootstrapRequest extends EventEmitter {
71
71
  }
72
72
  _BootstrapRequest_status = new WeakMap();
73
73
  export class FreeSignalNode {
74
- constructor(storage, privateIdentityKey) {
75
- this.discovers = new Set();
74
+ constructor({ privateIdentityKey, sessions, users, bundles, keyExchange } = {}) {
75
+ var _a;
76
+ this.bootstraps = new Map();
76
77
  this.emitter = new EventEmitter();
77
78
  this.messageHandler = (data) => this.onMessage({ session: data.session, payload: data.payload });
78
79
  this.sendHandler = (data) => this.onSend(data.datagram.toBytes());
@@ -82,16 +83,25 @@ export class FreeSignalNode {
82
83
  this.onSend = () => { };
83
84
  this.onHandshake = () => { };
84
85
  this.onRequest = () => { };
85
- this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : createIdentity();
86
- this.sessions = new SessionMap(storage.sessions);
87
- this.users = storage.users;
88
- this.keyExchange = new KeyExchange(storage.keyExchange, this.privateIdentityKey);
89
- this.bundles = storage.bundles;
90
- this.bootstraps = storage.bootstraps;
86
+ this.onError = () => { };
87
+ this.onDebug = () => { };
88
+ this.privateIdentityKey = privateIdentityKey ? PrivateIdentityKey.from(privateIdentityKey) : createIdentity();
89
+ this.sessions = new Map((_a = sessions === null || sessions === void 0 ? void 0 : sessions.map(([key, value]) => [key, KeySession.from(value)])) !== null && _a !== void 0 ? _a : []);
90
+ this.users = new Map(users);
91
+ this.bundles = new Map(bundles);
92
+ this.keyExchange = new KeyExchange({ privateIdentityKey: this.privateIdentityKey, storage: keyExchange });
91
93
  this.emitter.on('message', this.messageHandler);
92
94
  this.emitter.on('send', this.sendHandler);
93
95
  this.emitter.on('handshake', this.handshakeHandler);
94
96
  this.emitter.on('bootstrap', this.bootstrapHandler);
97
+ this.emitter.on('error', (e) => this.onError(e.error));
98
+ this.emitter.on('debug', (e) => this.onDebug(e.debug));
99
+ }
100
+ error(error) {
101
+ this.emitter.emit('error', { error });
102
+ }
103
+ debug(...args) {
104
+ this.emitter.emit('debug', { debug: args });
95
105
  }
96
106
  getRequest(userId) {
97
107
  return this.bootstraps.get(userId);
@@ -114,37 +124,49 @@ export class FreeSignalNode {
114
124
  }
115
125
  encrypt(receiverId, protocol, data) {
116
126
  return __awaiter(this, void 0, void 0, function* () {
117
- const sessionTag = yield this.users.get(receiverId.toString());
118
- if (!sessionTag)
119
- throw new Error("User not found: " + receiverId);
120
- const session = yield this.sessions.get(sessionTag);
121
- if (!session)
122
- throw new Error("Session not found for sessionTag: " + sessionTag);
123
- const encrypted = encryptData(session, data);
124
- this.sessions.set(receiverId.toString(), session);
125
- return { session, userId: UserId.from(receiverId), datagram: new EncryptedDatagram(protocol, session.sessionTag, encrypted).sign(this.privateIdentityKey.signatureKey) };
127
+ try {
128
+ const sessionTag = this.users.get(receiverId.toString());
129
+ if (!sessionTag)
130
+ throw new Error("User not found: " + receiverId);
131
+ const session = this.sessions.get(sessionTag);
132
+ if (!session)
133
+ throw new Error("Session not found for sessionTag: " + sessionTag);
134
+ const encrypted = encryptData(session, data);
135
+ this.sessions.set(receiverId.toString(), session);
136
+ return { session, userId: UserId.from(receiverId), datagram: new EncryptedDatagram(protocol, session.sessionTag, encrypted).sign(this.privateIdentityKey.signatureKey) };
137
+ }
138
+ catch (error) {
139
+ this.error(error);
140
+ throw error;
141
+ }
126
142
  });
127
143
  }
128
144
  sendHandshake(data) {
129
145
  return __awaiter(this, void 0, void 0, function* () {
130
- if (data instanceof UserId || typeof data === 'string') {
131
- const session = yield this.sessions.get(data.toString());
132
- if (!session)
133
- throw new Error("Session not found for userId: " + data.toString());
134
- data = session;
146
+ try {
147
+ if (data instanceof UserId || typeof data === 'string') {
148
+ const session = this.sessions.get(data.toString());
149
+ if (!session)
150
+ throw new Error("Session not found for userId: " + data.toString());
151
+ data = session;
152
+ }
153
+ if (data instanceof KeySession) {
154
+ //console.debug("Sending Handshake Ack");
155
+ const session = this.sessions.get(data.sessionTag);
156
+ if (!session)
157
+ throw new Error("Session not found for sessionTag: " + data.sessionTag);
158
+ this.emitter.emit('send', yield this.encrypt(session.userId, Protocols.HANDSHAKE, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)));
159
+ return;
160
+ }
135
161
  }
136
- if (data instanceof KeySession) {
137
- //console.debug("Sending Handshake Ack");
138
- const session = yield this.sessions.get(data.sessionTag);
139
- if (!session)
140
- throw new Error("Session not found for sessionTag: " + data.sessionTag);
141
- this.emitter.emit('send', yield this.encrypt(session.userId, Protocols.HANDSHAKE, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)));
142
- return;
162
+ catch (error) {
163
+ this.error(error);
164
+ throw error;
143
165
  }
144
166
  //console.debug("Sending Handshake Syn");
145
- const { session, message } = yield this.keyExchange.digestData(data, encodeData(yield this.keyExchange.generateBundle()));
146
- yield this.sessions.set(session.sessionTag, session);
147
- yield this.users.set(session.userId.toString(), session.sessionTag);
167
+ const { session, message } = this.keyExchange.digestData(data, encodeData(this.keyExchange.generateBundle()));
168
+ this.sessions.set(session.sessionTag, session);
169
+ this.users.set(session.userId.toString(), session.sessionTag);
148
170
  const datagram = new Datagram(Protocols.HANDSHAKE, encodeData(message), session.sessionTag).sign(this.privateIdentityKey.signatureKey);
149
171
  this.emitter.emit('send', { session, datagram, userId: session.userId });
150
172
  });
@@ -171,219 +193,246 @@ export class FreeSignalNode {
171
193
  throw new Error("Session not found for sessionTag: " + sessionTag);
172
194
  const datagram = new Datagram(Protocols.PING, undefined, session.sessionTag);
173
195
  this.emitter.emit('send', { session, datagram, userId: session.userId });
174
- }*/
175
- sendDiscover(receiverId, discoverId) {
176
- return __awaiter(this, void 0, void 0, function* () {
177
- //console.debug("Sending Discover");
178
- if (receiverId instanceof UserId)
179
- receiverId = receiverId.toString();
180
- if (discoverId instanceof UserId)
181
- discoverId = discoverId.toString();
182
- const message = {
183
- type: DiscoverType.REQUEST,
184
- discoverId
185
- };
186
- this.discovers.add(receiverId);
187
- this.emitter.emit('send', yield this.encrypt(receiverId, Protocols.DISCOVER, encodeData(message)));
188
- });
189
196
  }
197
+
198
+ public async sendDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<void> {
199
+ //console.debug("Sending Discover");
200
+ if (receiverId instanceof UserId)
201
+ receiverId = receiverId.toString();
202
+ if (discoverId instanceof UserId)
203
+ discoverId = discoverId.toString();
204
+ const message: DiscoverMessage = {
205
+ type: DiscoverType.REQUEST,
206
+ discoverId
207
+ };
208
+ this.discovers.add(receiverId);
209
+ this.emitter.emit('send', await this.encrypt(receiverId, Protocols.DISCOVER, encodeData(message)));
210
+ }*/
190
211
  packBootstrap() {
191
- return __awaiter(this, void 0, void 0, function* () {
192
- return new Datagram(Protocols.BOOTSTRAP, encodeData(yield this.keyExchange.generateData()));
193
- });
212
+ return new Datagram(Protocols.BOOTSTRAP, encodeData(this.keyExchange.generateData()));
194
213
  }
195
214
  sendBootstrap(receiverId) {
196
215
  return __awaiter(this, void 0, void 0, function* () {
197
- //console.debug("Sending Bootstrap");
198
- if (yield this.sessions.has(receiverId.toString()))
199
- throw new Error("Session exists");
200
- const datagram = yield this.packBootstrap();
201
- this.emitter.emit('send', { datagram, userId: UserId.from(receiverId) });
216
+ try {
217
+ //console.debug("Sending Bootstrap");
218
+ if (this.sessions.has(receiverId.toString()))
219
+ throw new Error("Session exists");
220
+ const datagram = this.packBootstrap();
221
+ this.emitter.emit('send', { datagram, userId: UserId.from(receiverId) });
222
+ }
223
+ catch (error) {
224
+ this.error(error);
225
+ throw error;
226
+ }
202
227
  });
203
228
  }
204
229
  decrypt(datagram) {
205
230
  return __awaiter(this, void 0, void 0, function* () {
206
- datagram = EncryptedDatagram.from(datagram);
207
- if (!datagram.sessionTag)
208
- throw new Error("Datagram not encrypted");
209
- const session = yield this.sessions.get(datagram.sessionTag);
210
- if (!session)
211
- throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
212
- if (!datagram.verify(session.identityKey.signatureKey))
213
- throw new Error("Signature not verified");
214
- if (!datagram.payload)
215
- throw new Error("Missing payload");
216
- const decrypted = decryptData(session, datagram.payload);
217
- this.sessions.set(datagram.sessionTag, session);
218
- return { session, payload: decrypted };
231
+ try {
232
+ datagram = EncryptedDatagram.from(datagram);
233
+ if (!datagram.sessionTag)
234
+ throw new Error("Datagram not encrypted");
235
+ const session = this.sessions.get(datagram.sessionTag);
236
+ if (!session)
237
+ throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
238
+ if (!datagram.verify(session.identityKey.signatureKey))
239
+ throw new Error("Signature not verified");
240
+ if (!datagram.payload)
241
+ throw new Error("Missing payload");
242
+ const decrypted = decryptData(session, datagram.payload);
243
+ this.sessions.set(datagram.sessionTag, session);
244
+ return { session, payload: decrypted };
245
+ }
246
+ catch (error) {
247
+ this.error(error);
248
+ throw error;
249
+ }
219
250
  });
220
251
  }
221
252
  openHandshake(datagram) {
222
253
  return __awaiter(this, void 0, void 0, function* () {
223
- const encrypted = EncryptedDatagram.from(datagram);
224
- if (!encrypted.payload)
225
- throw new Error("Missing payload");
226
- if (yield this.sessions.has(encrypted.sessionTag)) {
227
- //console.debug("Opening Handshake Ack");
228
- const session = yield this.sessions.get(encrypted.sessionTag);
229
- const { payload } = yield this.decrypt(encrypted);
230
- if (!session)
231
- throw new Error("Session not found for sessionTag: " + encrypted.sessionTag);
232
- if (!compareBytes(payload, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)))
233
- throw new Error("Error validating handshake data");
234
- return 'ack';
254
+ try {
255
+ const encrypted = EncryptedDatagram.from(datagram);
256
+ if (!encrypted.payload)
257
+ throw new Error("Missing payload");
258
+ if (this.sessions.has(encrypted.sessionTag)) {
259
+ //console.debug("Opening Handshake Ack");
260
+ const session = this.sessions.get(encrypted.sessionTag);
261
+ const { payload } = yield this.decrypt(encrypted);
262
+ if (!session)
263
+ throw new Error("Session not found for sessionTag: " + encrypted.sessionTag);
264
+ if (!compareBytes(payload, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)))
265
+ throw new Error("Error validating handshake data");
266
+ return 'ack';
267
+ }
268
+ //console.debug("Opening Handshake Syn");
269
+ const data = decodeData(encrypted.payload);
270
+ if (!encrypted.verify(IdentityKey.from(data.identityKey).signatureKey))
271
+ throw new Error("Signature not verified");
272
+ const { session, associatedData } = this.keyExchange.digestMessage(data);
273
+ this.sessions.set(session.sessionTag, session);
274
+ this.users.set(session.userId.toString(), session.sessionTag);
275
+ this.bundles.set(session.userId.toString(), decodeData(associatedData));
276
+ return 'syn';
277
+ }
278
+ catch (error) {
279
+ this.error(error);
280
+ throw error;
235
281
  }
236
- //console.debug("Opening Handshake Syn");
237
- const data = decodeData(encrypted.payload);
238
- if (!encrypted.verify(IdentityKey.from(data.identityKey).signatureKey))
239
- throw new Error("Signature not verified");
240
- const { session, associatedData } = yield this.keyExchange.digestMessage(data);
241
- yield this.sessions.set(session.sessionTag, session);
242
- yield this.users.set(session.userId.toString(), session.sessionTag);
243
- yield this.bundles.set(session.userId.toString(), decodeData(associatedData));
244
- return 'syn';
245
282
  });
246
283
  }
247
284
  open(datagram) {
248
285
  return __awaiter(this, void 0, void 0, function* () {
249
- if (datagram instanceof Uint8Array)
250
- datagram = Datagram.from(datagram);
251
- switch (datagram.protocol) {
252
- case Protocols.HANDSHAKE: {
253
- const encrypted = EncryptedDatagram.from(datagram);
254
- if (!encrypted.payload)
255
- throw new Error("Missing payload");
256
- const handshakeState = yield this.openHandshake(datagram);
257
- const session = yield this.sessions.get(encrypted.sessionTag);
258
- if (!session)
259
- throw new Error("Session not found for sessionTag: " + encrypted.sessionTag);
260
- if (handshakeState === 'syn')
261
- yield this.sendHandshake(session);
262
- this.emitter.emit('handshake', { session });
263
- return;
264
- }
265
- case Protocols.MESSAGE:
266
- //console.debug("Opening Message");
267
- this.emitter.emit('message', yield this.decrypt(datagram));
268
- return;
269
- case Protocols.RELAY: {
270
- //console.debug("Opening Relay");
271
- const opened = yield this.decrypt(datagram);
272
- const userId = decodeBase64(opened.payload.subarray(0, UserId.keyLength));
273
- const sessionTag = yield this.users.get(userId);
274
- if (!sessionTag)
275
- throw new Error("Session not found for user: " + userId);
276
- const session = yield this.sessions.get(sessionTag);
277
- if (!session)
278
- throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
279
- this.emitter.emit('send', { session, datagram: Datagram.from(opened.payload.slice(UserId.keyLength)), userId: session.userId });
280
- return;
281
- }
282
- case Protocols.DISCOVER: {
283
- //console.debug("Opening Discover");
284
- const { session, payload } = yield this.decrypt(datagram);
285
- const message = decodeData(payload);
286
- if (message.type === DiscoverType.REQUEST && message.discoverId && !(yield this.sessions.has(message.discoverId))) {
287
- let data;
288
- if (message.discoverId === this.userId.toString()) {
289
- data = yield this.keyExchange.generateData();
290
- }
291
- else {
292
- const bundle = yield this.bundles.get(message.discoverId);
293
- if (!bundle)
294
- return;
295
- const { version, identityKey, signedPreKey, signature } = bundle;
296
- const onetimePreKey = bundle.onetimePreKeys.shift();
297
- if (!onetimePreKey) {
298
- yield this.bundles.delete(message.discoverId);
299
- return;
300
- }
301
- data = {
302
- version,
303
- identityKey,
304
- signedPreKey,
305
- signature,
306
- onetimePreKey
307
- };
308
- }
309
- const response = { type: DiscoverType.RESPONSE, discoverId: message.discoverId, data };
310
- this.emitter.emit('send', yield this.encrypt(session.userId, Protocols.DISCOVER, encodeData(response)));
286
+ try {
287
+ if (datagram instanceof Uint8Array)
288
+ datagram = Datagram.from(datagram);
289
+ switch (datagram.protocol) {
290
+ case Protocols.HANDSHAKE: {
291
+ const encrypted = EncryptedDatagram.from(datagram);
292
+ if (!encrypted.payload)
293
+ throw new Error("Missing payload");
294
+ const handshakeState = yield this.openHandshake(datagram);
295
+ const session = this.sessions.get(encrypted.sessionTag);
296
+ if (!session)
297
+ throw new Error("Session not found for sessionTag: " + encrypted.sessionTag);
298
+ if (handshakeState === 'syn')
299
+ this.sendHandshake(session);
300
+ this.emitter.emit('handshake', { session });
301
+ return;
311
302
  }
312
- else if (message.type === DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
313
- this.discovers.delete(message.discoverId);
314
- if (message.data)
315
- yield this.sendHandshake(message.data);
303
+ case Protocols.MESSAGE:
304
+ //console.debug("Opening Message");
305
+ this.emitter.emit('message', yield this.decrypt(datagram));
306
+ return;
307
+ case Protocols.RELAY: {
308
+ //console.debug("Opening Relay");
309
+ const opened = yield this.decrypt(datagram);
310
+ const userId = decodeBase64(opened.payload.subarray(0, UserId.keyLength));
311
+ const sessionTag = this.users.get(userId);
312
+ if (!sessionTag)
313
+ throw new Error("Session not found for user: " + userId);
314
+ const session = this.sessions.get(sessionTag);
315
+ if (!session)
316
+ throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
317
+ this.emitter.emit('send', { session, datagram: Datagram.from(opened.payload.slice(UserId.keyLength)), userId: session.userId });
318
+ return;
316
319
  }
317
- return;
318
- }
319
- case Protocols.BOOTSTRAP: {
320
- //console.debug("Opening Bootstrap");
321
- if (!datagram.payload)
322
- throw new Error("Invalid Bootstrap");
323
- const keyExchangeData = decodeData(datagram.payload);
324
- const userId = UserId.fromKey(keyExchangeData.identityKey);
325
- const request = new BootstrapRequest(userId, keyExchangeData);
326
- let sended = false;
327
- request.onChange = (request) => {
328
- if (request.status === 'accepted' && !sended) {
329
- sended = true;
330
- this.sendHandshake(request.data);
320
+ case Protocols.BOOTSTRAP: {
321
+ //console.debug("Opening Bootstrap");
322
+ if (!datagram.payload)
323
+ throw new Error("Invalid Bootstrap");
324
+ const keyExchangeData = decodeData(datagram.payload);
325
+ const userId = UserId.fromKey(keyExchangeData.identityKey);
326
+ const request = new BootstrapRequest(userId, keyExchangeData);
327
+ let sended = false;
328
+ request.onChange = (request) => {
329
+ if (request.status === 'accepted' && !sended) {
330
+ sended = true;
331
+ this.sendHandshake(request.data);
332
+ }
333
+ };
334
+ this.bootstraps.set(userId.toString(), request);
335
+ this.emitter.emit('bootstrap', { request });
336
+ return;
337
+ }
338
+ /*case Protocols.DISCOVER: {
339
+ //console.debug("Opening Discover");
340
+ const { session, payload } = await this.decrypt(datagram);
341
+ const message = decodeData<DiscoverMessage>(payload);
342
+ if (message.type === DiscoverType.REQUEST && message.discoverId && !(await this.sessions.has(message.discoverId))) {
343
+ let data: KeyExchangeData;
344
+ if (message.discoverId === this.userId.toString()) {
345
+ data = await this.keyExchange.generateData();
346
+ } else {
347
+ const bundle = await this.bundles.get(message.discoverId);
348
+ if (!bundle)
349
+ return;
350
+ const { version, identityKey, signedPreKey, signature } = bundle;
351
+ const onetimePreKey = bundle.onetimePreKeys.shift();
352
+ if (!onetimePreKey) {
353
+ await this.bundles.delete(message.discoverId);
354
+ return;
355
+ }
356
+ data = {
357
+ version,
358
+ identityKey,
359
+ signedPreKey,
360
+ signature,
361
+ onetimePreKey
362
+ };
363
+ }
364
+ const response: DiscoverMessage = { type: DiscoverType.RESPONSE, discoverId: message.discoverId, data };
365
+ this.emitter.emit('send', await this.encrypt(session.userId, Protocols.DISCOVER, encodeData(response)));
366
+ } else if (message.type === DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
367
+ this.discovers.delete(message.discoverId);
368
+ if (message.data)
369
+ await this.sendHandshake(message.data);
331
370
  }
332
- };
333
- yield this.bootstraps.set(userId.toString(), request);
334
- this.emitter.emit('bootstrap', { request });
335
- return;
371
+ return;
372
+ }
373
+
374
+ case Protocols.PING:
375
+ datagram = EncryptedDatagram.from(datagram);
376
+ const session = await this.sessions.get(datagram.sessionTag!);
377
+ if (!session)
378
+ throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
379
+ this.emitter.emit('ping', { session });
380
+ return;*/
381
+ default:
382
+ throw new Error("Invalid protocol");
336
383
  }
337
- /*case Protocols.PING:
338
- datagram = EncryptedDatagram.from(datagram);
339
- const session = await this.sessions.get(datagram.sessionTag!);
340
- if (!session)
341
- throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
342
- this.emitter.emit('ping', { session });
343
- return;*/
344
- default:
345
- throw new Error("Invalid protocol");
384
+ }
385
+ catch (error) {
386
+ this.error(error);
387
+ throw error;
346
388
  }
347
389
  });
348
390
  }
349
- }
350
- class SessionMap {
351
- constructor(storage, maxSize = 50) {
352
- this.storage = storage;
353
- this.maxSize = maxSize;
354
- this.cache = new Map();
391
+ toJSON() {
392
+ return {
393
+ privateIdentityKey: this.privateIdentityKey.toString(),
394
+ sessions: Array.from(this.sessions.entries()).map(([key, session]) => [key, session.toJSON()]),
395
+ users: Array.from(this.users.entries()),
396
+ bundles: Array.from(this.bundles),
397
+ keyExchange: this.keyExchange.toJSON().storage,
398
+ };
355
399
  }
356
- set(key, value) {
400
+ }
401
+ /*class SessionMap implements LocalStorage<string, KeySession> {
402
+ private readonly cache = new Map<string, KeySession>()
403
+
404
+ public constructor(public readonly storage: LocalStorage<string, KeySessionState>, public readonly maxSize = 50) { }
405
+
406
+ public set(key: string, value: KeySession): Promise<void> {
357
407
  this.cache.set(key, value);
358
408
  return this.storage.set(key, value.toJSON());
359
409
  }
360
- get(key) {
361
- return __awaiter(this, void 0, void 0, function* () {
362
- const session = this.cache.get(key);
363
- if (!session) {
364
- const sessionData = yield this.storage.get(key);
365
- if (!sessionData)
366
- return undefined;
367
- return KeySession.from(sessionData);
368
- }
369
- return session;
370
- });
410
+
411
+ public async get(key: string): Promise<KeySession | undefined> {
412
+ const session = this.cache.get(key);
413
+ if (!session) {
414
+ const sessionData = await this.storage.get(key);
415
+ if (!sessionData)
416
+ return undefined;
417
+ return KeySession.from(sessionData);
418
+ }
419
+ return session;
371
420
  }
372
- has(key) {
373
- return __awaiter(this, void 0, void 0, function* () {
374
- return this.cache.has(key) || (yield this.storage.has(key));
375
- });
421
+
422
+ public async has(key: string): Promise<boolean> {
423
+ return this.cache.has(key) || await this.storage.has(key);
376
424
  }
377
- delete(key) {
378
- return __awaiter(this, void 0, void 0, function* () {
379
- return this.cache.delete(key) || (yield this.storage.delete(key));
380
- });
425
+
426
+ public async delete(key: string): Promise<boolean> {
427
+ return this.cache.delete(key) || await this.storage.delete(key);
381
428
  }
382
- clear() {
429
+
430
+ public clear(): Promise<void> {
383
431
  this.cache.clear();
384
432
  return this.storage.clear();
385
433
  }
386
- entries() {
434
+
435
+ public entries(): Promise<Iterable<[string, KeySession]>> {
387
436
  throw new Error("Method not implemented.");
388
437
  }
389
- }
438
+ }*/
package/dist/types.d.ts CHANGED
@@ -16,7 +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 type { LocalStorage, Encodable, KeyExchangeData } from "@freesignal/interfaces";
19
+ import type { Encodable, KeyExchangeData } from "@freesignal/interfaces";
20
20
  import { EncryptionKeys, KeySession } from "./double-ratchet.js";
21
21
  export declare function encryptData(session: KeySession, data: Uint8Array): EncryptedData;
22
22
  export declare function decryptData(session: KeySession, encryptedData: Uint8Array): Uint8Array;
@@ -77,7 +77,6 @@ export declare enum Protocols {
77
77
  MESSAGE = "/freesignal/message",
78
78
  RELAY = "/freesignal/relay",
79
79
  HANDSHAKE = "/freesignal/handshake",
80
- DISCOVER = "/freesignal/discover",
81
80
  BOOTSTRAP = "/freesignal/bootstrap"
82
81
  }
83
82
  export declare namespace Protocols {
@@ -168,14 +167,4 @@ export declare class EncryptedData implements Encodable {
168
167
  };
169
168
  static from(data: Uint8Array | EncryptedData): EncryptedData;
170
169
  }
171
- export declare class AsyncMap<K, V> implements LocalStorage<K, V> {
172
- private readonly map;
173
- constructor(iterable?: Iterable<readonly [K, V]>);
174
- set(key: K, value: V): Promise<void>;
175
- get(key: K): Promise<V | undefined>;
176
- has(key: K): Promise<boolean>;
177
- delete(key: K): Promise<boolean>;
178
- clear(): Promise<void>;
179
- entries(): Promise<Iterable<[K, V]>>;
180
- }
181
170
  export {};
package/dist/types.js CHANGED
@@ -16,15 +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
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
20
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
21
- return new (P || (P = Promise))(function (resolve, reject) {
22
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
23
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
24
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
25
- step((generator = generator.apply(thisArg, _arguments || [])).next());
26
- });
27
- };
28
19
  import { concatBytes, decodeBase64, encodeBase64, bytesToNumber, numberToBytes, compareBytes, decodeBase64URL } from "@freesignal/utils";
29
20
  import crypto from "@freesignal/crypto";
30
21
  export function encryptData(session, data) {
@@ -196,10 +187,10 @@ export var Protocols;
196
187
  (function (Protocols) {
197
188
  Protocols["NULL"] = "";
198
189
  //PING = '/freesignal/ping',
190
+ //DISCOVER = '/freesignal/discover',
199
191
  Protocols["MESSAGE"] = "/freesignal/message";
200
192
  Protocols["RELAY"] = "/freesignal/relay";
201
193
  Protocols["HANDSHAKE"] = "/freesignal/handshake";
202
- Protocols["DISCOVER"] = "/freesignal/discover";
203
194
  Protocols["BOOTSTRAP"] = "/freesignal/bootstrap";
204
195
  })(Protocols || (Protocols = {}));
205
196
  (function (Protocols) {
@@ -386,39 +377,35 @@ export class EncryptedData {
386
377
  }
387
378
  EncryptedData.version = 1;
388
379
  EncryptedData.nonceLength = crypto.box.nonceLength;
389
- export class AsyncMap {
390
- constructor(iterable) {
391
- this.map = new Map(iterable);
392
- }
393
- set(key, value) {
394
- return __awaiter(this, void 0, void 0, function* () {
395
- this.map.set(key, value);
396
- return;
397
- });
398
- }
399
- get(key) {
400
- return __awaiter(this, void 0, void 0, function* () {
401
- return this.map.get(key);
402
- });
403
- }
404
- has(key) {
405
- return __awaiter(this, void 0, void 0, function* () {
406
- return this.map.has(key);
407
- });
408
- }
409
- delete(key) {
410
- return __awaiter(this, void 0, void 0, function* () {
411
- return this.map.delete(key);
412
- });
413
- }
414
- clear() {
415
- return __awaiter(this, void 0, void 0, function* () {
416
- return this.map.clear();
417
- });
418
- }
419
- entries() {
420
- return __awaiter(this, void 0, void 0, function* () {
421
- return this.map.entries();
422
- });
423
- }
424
- }
380
+ /*class AsyncMap<K, V> implements LocalStorage<K, V> {
381
+ private readonly map: Map<K, V>;
382
+
383
+ constructor(iterable?: Iterable<readonly [K, V]>) {
384
+ this.map = new Map<K, V>(iterable);
385
+ }
386
+
387
+ async set(key: K, value: V): Promise<void> {
388
+ this.map.set(key, value);
389
+ return;
390
+ }
391
+
392
+ async get(key: K): Promise<V | undefined> {
393
+ return this.map.get(key);
394
+ }
395
+
396
+ async has(key: K): Promise<boolean> {
397
+ return this.map.has(key);
398
+ }
399
+
400
+ async delete(key: K): Promise<boolean> {
401
+ return this.map.delete(key);
402
+ }
403
+
404
+ async clear(): Promise<void> {
405
+ return this.map.clear();
406
+ }
407
+
408
+ async entries(): Promise<Iterable<[K, V]>> {
409
+ return this.map.entries();
410
+ }
411
+ }*/
package/dist/x3dh.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 { KeyExchangeData, KeyExchangeDataBundle, KeyExchangeSynMessage, LocalStorage, Crypto } from "@freesignal/interfaces";
19
+ import { KeyExchangeData, KeyExchangeDataBundle, KeyExchangeSynMessage, Crypto } from "@freesignal/interfaces";
20
20
  import { KeySession } from "./double-ratchet.js";
21
21
  import { IdentityKey, PrivateIdentityKey } from "./types.js";
22
- export interface ExportedKeyExchange {
22
+ export interface KeyExchangeState {
23
23
  privateIdentityKey: PrivateIdentityKey;
24
24
  storage: Array<[string, Crypto.KeyPair]>;
25
25
  }
@@ -29,18 +29,19 @@ export declare class KeyExchange {
29
29
  private static readonly maxOPK;
30
30
  private readonly privateIdentityKey;
31
31
  private readonly storage;
32
- constructor(storage: LocalStorage<string, Crypto.KeyPair>, privateIdentityKey?: PrivateIdentityKey);
32
+ constructor({ storage, privateIdentityKey }?: Partial<KeyExchangeState>);
33
33
  get identityKey(): IdentityKey;
34
34
  private generateSPK;
35
35
  private generateOPK;
36
- generateBundle(length?: number): Promise<KeyExchangeDataBundle>;
37
- generateData(): Promise<KeyExchangeData>;
38
- digestData(message: KeyExchangeData, associatedData?: Uint8Array): Promise<{
36
+ generateBundle(length?: number): KeyExchangeDataBundle;
37
+ generateData(): KeyExchangeData;
38
+ digestData(message: KeyExchangeData, associatedData?: Uint8Array): {
39
39
  session: KeySession;
40
40
  message: KeyExchangeSynMessage;
41
- }>;
42
- digestMessage(message: KeyExchangeSynMessage): Promise<{
41
+ };
42
+ digestMessage(message: KeyExchangeSynMessage): {
43
43
  session: KeySession;
44
44
  associatedData: Uint8Array;
45
- }>;
45
+ };
46
+ toJSON(): KeyExchangeState;
46
47
  }
package/dist/x3dh.js CHANGED
@@ -16,23 +16,14 @@
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
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
20
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
21
- return new (P || (P = Promise))(function (resolve, reject) {
22
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
23
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
24
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
25
- step((generator = generator.apply(thisArg, _arguments || [])).next());
26
- });
27
- };
28
19
  import crypto from "@freesignal/crypto";
29
20
  import { KeySession } from "./double-ratchet.js";
30
21
  import { concatBytes, decodeBase64, encodeBase64, compareBytes } from "@freesignal/utils";
31
22
  import { decryptData, encryptData, IdentityKey } from "./types.js";
32
23
  import { createIdentity } from "./index.js";
33
24
  export class KeyExchange {
34
- constructor(storage, privateIdentityKey) {
35
- this.storage = storage;
25
+ constructor({ storage, privateIdentityKey } = {}) {
26
+ this.storage = new Map(storage);
36
27
  this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : createIdentity();
37
28
  }
38
29
  get identityKey() {
@@ -51,94 +42,92 @@ export class KeyExchange {
51
42
  return { onetimePreKey, onetimePreKeyHash };
52
43
  }
53
44
  generateBundle(length) {
54
- return __awaiter(this, void 0, void 0, function* () {
55
- const { signedPreKey, signedPreKeyHash } = this.generateSPK();
56
- const onetimePreKey = new Array(length !== null && length !== void 0 ? length : KeyExchange.maxOPK).fill(0).map(() => this.generateOPK(signedPreKeyHash).onetimePreKey);
57
- return {
58
- version: KeyExchange.version,
59
- identityKey: this.identityKey.toString(),
60
- signedPreKey: decodeBase64(signedPreKey.publicKey),
61
- signature: decodeBase64(crypto.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
62
- onetimePreKeys: onetimePreKey.map(opk => decodeBase64(opk.publicKey))
63
- };
64
- });
45
+ const { signedPreKey, signedPreKeyHash } = this.generateSPK();
46
+ const onetimePreKey = new Array(length !== null && length !== void 0 ? length : KeyExchange.maxOPK).fill(0).map(() => this.generateOPK(signedPreKeyHash).onetimePreKey);
47
+ return {
48
+ version: KeyExchange.version,
49
+ identityKey: this.identityKey.toString(),
50
+ signedPreKey: decodeBase64(signedPreKey.publicKey),
51
+ signature: decodeBase64(crypto.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
52
+ onetimePreKeys: onetimePreKey.map(opk => decodeBase64(opk.publicKey))
53
+ };
65
54
  }
66
55
  generateData() {
67
- return __awaiter(this, void 0, void 0, function* () {
68
- const { signedPreKey, signedPreKeyHash } = this.generateSPK();
69
- const { onetimePreKey } = this.generateOPK(signedPreKeyHash);
70
- return {
71
- version: KeyExchange.version,
72
- identityKey: this.identityKey.toString(),
73
- signedPreKey: decodeBase64(signedPreKey.publicKey),
74
- signature: decodeBase64(crypto.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
75
- onetimePreKey: decodeBase64(onetimePreKey.publicKey)
76
- };
77
- });
56
+ const { signedPreKey, signedPreKeyHash } = this.generateSPK();
57
+ const { onetimePreKey } = this.generateOPK(signedPreKeyHash);
58
+ return {
59
+ version: KeyExchange.version,
60
+ identityKey: this.identityKey.toString(),
61
+ signedPreKey: decodeBase64(signedPreKey.publicKey),
62
+ signature: decodeBase64(crypto.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
63
+ onetimePreKey: decodeBase64(onetimePreKey.publicKey)
64
+ };
78
65
  }
79
66
  digestData(message, associatedData) {
80
- return __awaiter(this, void 0, void 0, function* () {
81
- //console.debug("Digest Data")
82
- const ephemeralKey = crypto.ECDH.keyPair();
83
- const signedPreKey = encodeBase64(message.signedPreKey);
84
- const identityKey = IdentityKey.from(message.identityKey);
85
- if (!crypto.EdDSA.verify(encodeBase64(message.signature), crypto.hash(signedPreKey), identityKey.signatureKey))
86
- throw new Error("Signature verification failed");
87
- const onetimePreKey = message.onetimePreKey ? encodeBase64(message.onetimePreKey) : undefined;
88
- const signedPreKeyHash = crypto.hash(signedPreKey);
89
- const onetimePreKeyHash = onetimePreKey ? crypto.hash(onetimePreKey) : new Uint8Array();
90
- const derivedKey = crypto.hkdf(new Uint8Array([
91
- ...crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, signedPreKey),
92
- ...crypto.ECDH.scalarMult(ephemeralKey.secretKey, identityKey.exchangeKey),
93
- ...crypto.ECDH.scalarMult(ephemeralKey.secretKey, signedPreKey),
94
- ...onetimePreKey ? crypto.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
95
- ]), new Uint8Array(KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, KeySession.keyLength * 3);
96
- const session = new KeySession({ identityKey, remoteKey: identityKey.exchangeKey, rootKey: derivedKey.subarray(0, KeySession.keyLength), headerKey: derivedKey.subarray(KeySession.keyLength, KeySession.keyLength * 2), nextHeaderKey: derivedKey.subarray(KeySession.keyLength * 2) });
97
- const encrypted = encryptData(session, concatBytes(crypto.hash(this.identityKey.toBytes()), crypto.hash(identityKey.toBytes()), associatedData !== null && associatedData !== void 0 ? associatedData : new Uint8Array()));
98
- if (!encrypted)
99
- throw new Error("Decryption error");
100
- return {
101
- session,
102
- message: {
103
- version: KeyExchange.version,
104
- identityKey: this.identityKey.toString(),
105
- ephemeralKey: decodeBase64(ephemeralKey.publicKey),
106
- signedPreKeyHash: decodeBase64(signedPreKeyHash),
107
- onetimePreKeyHash: decodeBase64(onetimePreKeyHash),
108
- associatedData: decodeBase64(encrypted.toBytes())
109
- }
110
- };
111
- });
67
+ //console.debug("Digest Data")
68
+ const ephemeralKey = crypto.ECDH.keyPair();
69
+ const signedPreKey = encodeBase64(message.signedPreKey);
70
+ const identityKey = IdentityKey.from(message.identityKey);
71
+ if (!crypto.EdDSA.verify(encodeBase64(message.signature), crypto.hash(signedPreKey), identityKey.signatureKey))
72
+ throw new Error("Signature verification failed");
73
+ const onetimePreKey = message.onetimePreKey ? encodeBase64(message.onetimePreKey) : undefined;
74
+ const signedPreKeyHash = crypto.hash(signedPreKey);
75
+ const onetimePreKeyHash = onetimePreKey ? crypto.hash(onetimePreKey) : new Uint8Array();
76
+ const derivedKey = crypto.hkdf(new Uint8Array([
77
+ ...crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, signedPreKey),
78
+ ...crypto.ECDH.scalarMult(ephemeralKey.secretKey, identityKey.exchangeKey),
79
+ ...crypto.ECDH.scalarMult(ephemeralKey.secretKey, signedPreKey),
80
+ ...onetimePreKey ? crypto.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
81
+ ]), new Uint8Array(KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, KeySession.keyLength * 3);
82
+ const session = new KeySession({ identityKey, remoteKey: identityKey.exchangeKey, rootKey: derivedKey.subarray(0, KeySession.keyLength), headerKey: derivedKey.subarray(KeySession.keyLength, KeySession.keyLength * 2), nextHeaderKey: derivedKey.subarray(KeySession.keyLength * 2) });
83
+ const encrypted = encryptData(session, concatBytes(crypto.hash(this.identityKey.toBytes()), crypto.hash(identityKey.toBytes()), associatedData !== null && associatedData !== void 0 ? associatedData : new Uint8Array()));
84
+ if (!encrypted)
85
+ throw new Error("Decryption error");
86
+ return {
87
+ session,
88
+ message: {
89
+ version: KeyExchange.version,
90
+ identityKey: this.identityKey.toString(),
91
+ ephemeralKey: decodeBase64(ephemeralKey.publicKey),
92
+ signedPreKeyHash: decodeBase64(signedPreKeyHash),
93
+ onetimePreKeyHash: decodeBase64(onetimePreKeyHash),
94
+ associatedData: decodeBase64(encrypted.toBytes())
95
+ }
96
+ };
112
97
  }
113
98
  digestMessage(message) {
114
- return __awaiter(this, void 0, void 0, function* () {
115
- //console.debug("Digest Message")
116
- const signedPreKey = yield this.storage.get(message.signedPreKeyHash);
117
- const hash = message.signedPreKeyHash.concat(message.onetimePreKeyHash);
118
- const onetimePreKey = yield this.storage.get(hash);
119
- const identityKey = IdentityKey.from(message.identityKey);
120
- if (!signedPreKey || !onetimePreKey || !message.identityKey || !message.ephemeralKey)
121
- throw new Error("ACK message malformed");
122
- if (!this.storage.delete(hash))
123
- throw new Error("Bundle store deleting error");
124
- const ephemeralKey = encodeBase64(message.ephemeralKey);
125
- const derivedKey = crypto.hkdf(new Uint8Array([
126
- ...crypto.ECDH.scalarMult(signedPreKey.secretKey, identityKey.exchangeKey),
127
- ...crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, ephemeralKey),
128
- ...crypto.ECDH.scalarMult(signedPreKey.secretKey, ephemeralKey),
129
- ...onetimePreKey ? crypto.ECDH.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
130
- ]), new Uint8Array(KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, KeySession.keyLength * 3);
131
- const session = new KeySession({ identityKey, secretKey: this.privateIdentityKey.exchangeKey, rootKey: derivedKey.subarray(0, KeySession.keyLength), nextHeaderKey: derivedKey.subarray(KeySession.keyLength, KeySession.keyLength * 2), headerKey: derivedKey.subarray(KeySession.keyLength * 2) });
132
- const data = decryptData(session, encodeBase64(message.associatedData));
133
- if (!data)
134
- throw new Error("Error decrypting ACK message");
135
- if (!compareBytes(data.subarray(0, 64), concatBytes(crypto.hash(identityKey.toBytes()), crypto.hash(this.identityKey.toBytes()))))
136
- throw new Error("Error verifing Associated Data");
137
- return {
138
- session,
139
- associatedData: data.subarray(64)
140
- };
141
- });
99
+ //console.debug("Digest Message")
100
+ const signedPreKey = this.storage.get(message.signedPreKeyHash);
101
+ const hash = message.signedPreKeyHash.concat(message.onetimePreKeyHash);
102
+ const onetimePreKey = this.storage.get(hash);
103
+ const identityKey = IdentityKey.from(message.identityKey);
104
+ if (!signedPreKey || !onetimePreKey || !message.identityKey || !message.ephemeralKey)
105
+ throw new Error("ACK message malformed");
106
+ if (!this.storage.delete(hash))
107
+ throw new Error("Bundle store deleting error");
108
+ const ephemeralKey = encodeBase64(message.ephemeralKey);
109
+ const derivedKey = crypto.hkdf(new Uint8Array([
110
+ ...crypto.ECDH.scalarMult(signedPreKey.secretKey, identityKey.exchangeKey),
111
+ ...crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, ephemeralKey),
112
+ ...crypto.ECDH.scalarMult(signedPreKey.secretKey, ephemeralKey),
113
+ ...onetimePreKey ? crypto.ECDH.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
114
+ ]), new Uint8Array(KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, KeySession.keyLength * 3);
115
+ const session = new KeySession({ identityKey, secretKey: this.privateIdentityKey.exchangeKey, rootKey: derivedKey.subarray(0, KeySession.keyLength), nextHeaderKey: derivedKey.subarray(KeySession.keyLength, KeySession.keyLength * 2), headerKey: derivedKey.subarray(KeySession.keyLength * 2) });
116
+ const data = decryptData(session, encodeBase64(message.associatedData));
117
+ if (!data)
118
+ throw new Error("Error decrypting ACK message");
119
+ if (!compareBytes(data.subarray(0, 64), concatBytes(crypto.hash(identityKey.toBytes()), crypto.hash(this.identityKey.toBytes()))))
120
+ throw new Error("Error verifing Associated Data");
121
+ return {
122
+ session,
123
+ associatedData: data.subarray(64)
124
+ };
125
+ }
126
+ toJSON() {
127
+ return {
128
+ privateIdentityKey: this.privateIdentityKey,
129
+ storage: Array.from(this.storage.entries())
130
+ };
142
131
  }
143
132
  }
144
133
  KeyExchange.version = 1;
package/package.json CHANGED
@@ -1,45 +1,45 @@
1
- {
2
- "name": "@freesignal/protocol",
3
- "version": "0.8.3",
4
- "description": "Signal Protocol implementation in javascript",
5
- "license": "GPL-3.0-or-later",
6
- "author": "Christian Braghette",
7
- "type": "module",
8
- "exports": {
9
- ".": {
10
- "import": "./dist/index.js",
11
- "require": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
13
- },
14
- "./double-ratchet": {
15
- "import": "./dist/double-ratchet.js",
16
- "require": "./dist/double-ratchet.js",
17
- "types": "./dist/double-ratchet.d.ts"
18
- },
19
- "./node": {
20
- "import": "./dist/node.js",
21
- "require": "./dist/node.js",
22
- "types": "./dist/node.d.ts"
23
- },
24
- "./x3dh": {
25
- "import": "./dist/x3dh.js",
26
- "require": "./dist/x3dh.js",
27
- "types": "./dist/x3dh.d.ts"
28
- }
29
- },
30
- "main": "./dist/index.js",
31
- "scripts": {
32
- "pretest": "tsc",
33
- "test": "node ./dist/test.js",
34
- "prepare": "tsc"
35
- },
36
- "dependencies": {
37
- "@freesignal/crypto": "^0.4.2",
38
- "@freesignal/interfaces": "^0.3.0",
39
- "@freesignal/utils": "^1.5.1",
40
- "easyemitter.ts": "^1.1.0"
41
- },
42
- "devDependencies": {
43
- "@types/node": "^24.2.1"
44
- }
45
- }
1
+ {
2
+ "name": "@freesignal/protocol",
3
+ "version": "0.9.1",
4
+ "description": "Signal Protocol implementation in TypeScript",
5
+ "license": "GPL-3.0-or-later",
6
+ "author": "Christian Braghette",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./double-ratchet": {
15
+ "import": "./dist/double-ratchet.js",
16
+ "require": "./dist/double-ratchet.js",
17
+ "types": "./dist/double-ratchet.d.ts"
18
+ },
19
+ "./node": {
20
+ "import": "./dist/node.js",
21
+ "require": "./dist/node.js",
22
+ "types": "./dist/node.d.ts"
23
+ },
24
+ "./x3dh": {
25
+ "import": "./dist/x3dh.js",
26
+ "require": "./dist/x3dh.js",
27
+ "types": "./dist/x3dh.d.ts"
28
+ }
29
+ },
30
+ "main": "./dist/index.js",
31
+ "scripts": {
32
+ "pretest": "tsc",
33
+ "test": "node ./dist/test.js",
34
+ "prepare": "tsc"
35
+ },
36
+ "dependencies": {
37
+ "@freesignal/crypto": "^0.4.2",
38
+ "@freesignal/interfaces": "^0.3.0",
39
+ "@freesignal/utils": "^1.5.1",
40
+ "easyemitter.ts": "^1.1.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^24.2.1"
44
+ }
45
+ }