@freesignal/protocol 0.7.1 → 0.7.4

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.
@@ -45,6 +45,7 @@ export declare class KeySession {
45
45
  static readonly info: string;
46
46
  static readonly maxCount = 65536;
47
47
  readonly identityKey: IdentityKey;
48
+ readonly sessionTag: string;
48
49
  private keyPair;
49
50
  private rootKey;
50
51
  private sendingChain?;
@@ -34,8 +34,9 @@ class KeySession {
34
34
  this.headerKeys = new Map();
35
35
  this.previousKeys = new KeyMap();
36
36
  this.identityKey = identityKey;
37
- this.keyPair = crypto_1.default.ECDH.keyPair(secretKey);
38
37
  this.rootKey = rootKey;
38
+ this.sessionTag = (0, utils_1.decodeBase64)(crypto_1.default.hkdf(rootKey, new Uint8Array(32).fill(0), "/freesignal/session-authtag", 32));
39
+ this.keyPair = crypto_1.default.ECDH.keyPair(secretKey);
39
40
  if (headerKey)
40
41
  this.headerKey = headerKey;
41
42
  if (nextHeaderKey) {
package/dist/index.d.ts CHANGED
@@ -30,6 +30,7 @@ export declare function createIdentity(seed?: Uint8Array): PrivateIdentityKey;
30
30
  /** */
31
31
  export declare function createNode(storage: Database<{
32
32
  sessions: LocalStorage<string, ExportedKeySession>;
33
+ users: LocalStorage<string, string>;
33
34
  keyExchange: LocalStorage<string, Crypto.KeyPair>;
34
35
  bundles: LocalStorage<string, KeyExchangeDataBundle>;
35
36
  bootstraps: LocalStorage<string, BootstrapRequest>;
package/dist/node.d.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  * along with this program. If not, see <https://www.gnu.org/licenses/>
18
18
  */
19
19
  import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
20
- import { Datagram, DatagramHeader, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
20
+ import { Datagram, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
21
21
  import { KeyExchange } from "./x3dh";
22
22
  import { ExportedKeySession, KeySession } from "./double-ratchet";
23
23
  import EventEmitter, { EventCall } from "easyemitter.ts";
@@ -33,18 +33,25 @@ export declare class BootstrapRequest extends EventEmitter<'change', BootstrapRe
33
33
  deny(): void;
34
34
  }
35
35
  type NodeEventData = {
36
- header: DatagramHeader;
36
+ session?: KeySession;
37
37
  payload?: Uint8Array;
38
38
  datagram?: Datagram;
39
39
  request?: BootstrapRequest;
40
+ userId?: UserId;
41
+ };
42
+ type SendEventData = {
43
+ session?: KeySession;
44
+ datagram: Datagram;
45
+ userId: UserId;
40
46
  };
41
47
  type MessageEventData = {
42
- header: DatagramHeader;
48
+ session: KeySession;
43
49
  payload: Uint8Array;
44
50
  };
45
51
  export declare class FreeSignalNode {
46
52
  protected readonly privateIdentityKey: PrivateIdentityKey;
47
53
  protected readonly sessions: SessionMap;
54
+ protected readonly users: LocalStorage<string, string>;
48
55
  protected readonly bundles: LocalStorage<string, KeyExchangeDataBundle>;
49
56
  protected readonly keyExchange: KeyExchange;
50
57
  protected readonly discovers: Set<string>;
@@ -52,6 +59,7 @@ export declare class FreeSignalNode {
52
59
  protected readonly emitter: EventEmitter<"message" | "send" | "handshake" | "ping" | "bootstrap", NodeEventData>;
53
60
  constructor(storage: Database<{
54
61
  sessions: LocalStorage<string, ExportedKeySession>;
62
+ users: LocalStorage<string, string>;
55
63
  keyExchange: LocalStorage<string, Crypto.KeyPair>;
56
64
  bundles: LocalStorage<string, KeyExchangeDataBundle>;
57
65
  bootstraps: LocalStorage<string, BootstrapRequest>;
@@ -68,16 +76,16 @@ export declare class FreeSignalNode {
68
76
  waitHandshaked(userId: UserId | string, timeout?: number): Promise<void>;
69
77
  get identityKey(): IdentityKey;
70
78
  get userId(): UserId;
71
- protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
79
+ protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<SendEventData>;
72
80
  sendHandshake(data: KeyExchangeData): Promise<void>;
73
- sendHandshake(receiverId: string | UserId): Promise<void>;
81
+ sendHandshake(session: KeySession): Promise<void>;
74
82
  sendData<T>(receiverId: string | UserId, data: T): Promise<void>;
75
- sendRelay(receiverId: string | UserId, data: Datagram): Promise<void>;
83
+ sendRelay(relayId: string | UserId, receiverId: string | UserId, data: Datagram): Promise<void>;
76
84
  sendPing(receiverId: string | UserId): Promise<void>;
77
85
  sendDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<void>;
78
86
  sendBootstrap(receiverId: string | UserId): Promise<void>;
79
- protected decrypt(datagram: Datagram): Promise<Uint8Array>;
80
- protected open(datagram: Datagram | Uint8Array): Promise<void>;
87
+ protected decrypt(datagram: EncryptedDatagram | Datagram | Uint8Array): Promise<MessageEventData>;
88
+ protected open(datagram: Datagram | EncryptedDatagram | Uint8Array): Promise<void>;
81
89
  }
82
90
  declare class SessionMap implements LocalStorage<string, KeySession> {
83
91
  readonly storage: LocalStorage<string, ExportedKeySession>;
package/dist/node.js CHANGED
@@ -82,9 +82,9 @@ class FreeSignalNode {
82
82
  constructor(storage, privateIdentityKey) {
83
83
  this.discovers = new Set();
84
84
  this.emitter = new easyemitter_ts_1.default();
85
- this.messageHandler = (data) => { var _a, _b; return this.onMessage({ header: (_a = data.data) === null || _a === void 0 ? void 0 : _a.header, payload: (_b = data.data) === null || _b === void 0 ? void 0 : _b.payload }); };
85
+ this.messageHandler = (data) => { var _a, _b; return this.onMessage({ session: (_a = data.data) === null || _a === void 0 ? void 0 : _a.session, payload: (_b = data.data) === null || _b === void 0 ? void 0 : _b.payload }); };
86
86
  this.sendHandler = (data) => this.onSend(data.data.datagram.toBytes());
87
- this.handshakeHandler = (data) => this.onHandshake(types_1.UserId.from(data.data.header.sender));
87
+ this.handshakeHandler = (data) => { var _a, _b; return this.onHandshake(types_1.UserId.from((_b = (_a = data.data) === null || _a === void 0 ? void 0 : _a.session) === null || _b === void 0 ? void 0 : _b.userId)); };
88
88
  this.bootstrapHandler = (data) => { var _a; return this.onRequest((_a = data.data) === null || _a === void 0 ? void 0 : _a.request); };
89
89
  this.onMessage = () => { };
90
90
  this.onSend = () => { };
@@ -92,6 +92,7 @@ class FreeSignalNode {
92
92
  this.onRequest = () => { };
93
93
  this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
94
94
  this.sessions = new SessionMap(storage.sessions);
95
+ this.users = storage.users;
95
96
  this.keyExchange = new x3dh_1.KeyExchange(storage.keyExchange, this.privateIdentityKey);
96
97
  this.bundles = storage.bundles;
97
98
  this.bootstraps = storage.bootstraps;
@@ -105,10 +106,10 @@ class FreeSignalNode {
105
106
  }
106
107
  waitHandshaked(userId, timeout) {
107
108
  return __awaiter(this, void 0, void 0, function* () {
108
- var _a;
109
+ var _a, _b;
109
110
  if (timeout)
110
111
  setTimeout(() => { throw new Error(); }, timeout);
111
- while (((_a = (yield this.emitter.wait('handshake', timeout))) === null || _a === void 0 ? void 0 : _a.header.sender) !== userId.toString())
112
+ while (((_b = (_a = (yield this.emitter.wait('handshake', timeout))) === null || _a === void 0 ? void 0 : _a.session) === null || _b === void 0 ? void 0 : _b.userId.toString()) !== userId.toString())
112
113
  ;
113
114
  });
114
115
  }
@@ -120,55 +121,58 @@ class FreeSignalNode {
120
121
  }
121
122
  encrypt(receiverId, protocol, data) {
122
123
  return __awaiter(this, void 0, void 0, function* () {
123
- if (receiverId instanceof types_1.UserId)
124
- receiverId = receiverId.toString();
125
- const session = yield this.sessions.get(receiverId);
124
+ const sessionTag = yield this.users.get(receiverId.toString());
125
+ if (!sessionTag)
126
+ throw new Error("User not found: " + receiverId);
127
+ const session = yield this.sessions.get(sessionTag);
126
128
  if (!session)
127
- throw new Error("Session not found for user: " + receiverId);
129
+ throw new Error("Session not found for sessionTag: " + sessionTag);
128
130
  const encrypted = (0, types_1.encryptData)(session, data);
129
- this.sessions.set(receiverId, session);
130
- return new types_1.Datagram(this.userId.toString(), receiverId, protocol, encrypted).sign(this.privateIdentityKey.signatureKey);
131
+ this.sessions.set(receiverId.toString(), session);
132
+ return { session, userId: types_1.UserId.from(receiverId), datagram: new types_1.EncryptedDatagram(protocol, session.sessionTag, encrypted).sign(this.privateIdentityKey.signatureKey) };
131
133
  });
132
134
  }
133
135
  sendHandshake(data) {
134
136
  return __awaiter(this, void 0, void 0, function* () {
135
- var _a;
136
- if (typeof data === 'string' || data instanceof types_1.UserId) {
137
+ if (data instanceof double_ratchet_1.KeySession) {
137
138
  //console.debug("Sending Handshake Ack");
138
- const userId = data.toString();
139
- const identityKey = (_a = (yield this.sessions.get(userId))) === null || _a === void 0 ? void 0 : _a.identityKey;
140
- if (!identityKey)
141
- throw new Error("Missing user");
142
- const datagram = yield this.encrypt(userId, types_1.Protocols.HANDSHAKE, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey));
143
- this.emitter.emit('send', { header: datagram.header, datagram });
139
+ const session = yield this.sessions.get(data.sessionTag);
140
+ if (!session)
141
+ throw new Error("Session not found for user: " + data);
142
+ this.emitter.emit('send', yield this.encrypt(session.userId, types_1.Protocols.HANDSHAKE, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)));
144
143
  return;
145
144
  }
146
145
  //console.debug("Sending Handshake Syn");
147
146
  const { session, message } = yield this.keyExchange.digestData(data, (0, utils_1.encodeData)(yield this.keyExchange.generateBundle()));
148
- yield this.sessions.set(session.userId.toString(), session);
149
- const datagram = new types_1.Datagram(this.userId.toString(), types_1.UserId.fromKey(data.identityKey).toString(), types_1.Protocols.HANDSHAKE, (0, utils_1.encodeData)(message)).sign(this.privateIdentityKey.signatureKey);
150
- this.emitter.emit('send', { header: datagram.header, datagram });
147
+ yield this.sessions.set(session.sessionTag, session);
148
+ yield this.users.set(session.userId.toString(), session.sessionTag);
149
+ const datagram = new types_1.Datagram(types_1.Protocols.HANDSHAKE, (0, utils_1.encodeData)(message), session.sessionTag).sign(this.privateIdentityKey.signatureKey);
150
+ this.emitter.emit('send', { session, datagram, userId: session.userId });
151
151
  });
152
152
  }
153
153
  sendData(receiverId, data) {
154
154
  return __awaiter(this, void 0, void 0, function* () {
155
155
  //console.debug("Sending Data");
156
- const datagram = yield this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
157
- this.emitter.emit('send', { header: datagram.header, datagram });
156
+ this.emitter.emit('send', yield this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data)));
158
157
  });
159
158
  }
160
- sendRelay(receiverId, data) {
159
+ sendRelay(relayId, receiverId, data) {
161
160
  return __awaiter(this, void 0, void 0, function* () {
162
161
  //console.debug("Sending Relay");
163
- const datagram = yield this.encrypt(receiverId, types_1.Protocols.RELAY, data.toBytes());
164
- this.emitter.emit('send', { header: datagram.header, datagram });
162
+ this.emitter.emit('send', yield this.encrypt(relayId, types_1.Protocols.RELAY, (0, utils_1.concatBytes)(types_1.UserId.from(receiverId).toBytes(), data.toBytes())));
165
163
  });
166
164
  }
167
165
  sendPing(receiverId) {
168
166
  return __awaiter(this, void 0, void 0, function* () {
169
167
  //console.debug("Sending Ping");
170
- const datagram = new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.PING);
171
- this.emitter.emit('send', { header: datagram.header, datagram });
168
+ const sessionTag = yield this.users.get(receiverId.toString());
169
+ if (!sessionTag)
170
+ throw new Error("Session not found for user: " + receiverId);
171
+ const session = yield this.sessions.get(sessionTag);
172
+ if (!session)
173
+ throw new Error("Session not found for sessionTag: " + sessionTag);
174
+ const datagram = new types_1.Datagram(types_1.Protocols.PING, undefined, session.sessionTag);
175
+ this.emitter.emit('send', { session, datagram, userId: session.userId });
172
176
  });
173
177
  }
174
178
  sendDiscover(receiverId, discoverId) {
@@ -183,8 +187,7 @@ class FreeSignalNode {
183
187
  discoverId
184
188
  };
185
189
  this.discovers.add(receiverId);
186
- const datagram = yield this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(message));
187
- this.emitter.emit('send', { header: datagram.header, datagram });
190
+ this.emitter.emit('send', yield this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(message)));
188
191
  });
189
192
  }
190
193
  sendBootstrap(receiverId) {
@@ -192,69 +195,80 @@ class FreeSignalNode {
192
195
  //console.debug("Sending Bootstrap");
193
196
  if (yield this.sessions.has(receiverId.toString()))
194
197
  throw new Error("Session exists");
195
- const datagram = new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.BOOTSTRAP, (0, utils_1.encodeData)(yield this.keyExchange.generateData()));
196
- this.emitter.emit('send', { header: datagram.header, datagram });
198
+ const datagram = new types_1.Datagram(types_1.Protocols.BOOTSTRAP, (0, utils_1.encodeData)(yield this.keyExchange.generateData()));
199
+ this.emitter.emit('send', { datagram, userId: types_1.UserId.from(receiverId) });
197
200
  });
198
201
  }
199
202
  decrypt(datagram) {
200
203
  return __awaiter(this, void 0, void 0, function* () {
201
- var _a;
202
- const identityKey = (_a = (yield this.sessions.get(datagram.sender))) === null || _a === void 0 ? void 0 : _a.identityKey;
203
- if (!identityKey)
204
- throw new Error("User IdentityKey not found");
205
- if (!types_1.Datagram.verify(datagram, identityKey.signatureKey))
206
- throw new Error("Signature not verified");
207
- const session = yield this.sessions.get(datagram.sender);
204
+ datagram = types_1.EncryptedDatagram.from(datagram);
205
+ if (!datagram.sessionTag)
206
+ throw new Error("Datagram not encrypted");
207
+ const session = yield this.sessions.get(datagram.sessionTag);
208
208
  if (!session)
209
- throw new Error("Session not found for user: " + datagram.sender);
209
+ throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
210
+ if (!datagram.verify(session.identityKey.signatureKey))
211
+ throw new Error("Signature not verified");
210
212
  if (!datagram.payload)
211
213
  throw new Error("Missing payload");
212
214
  const decrypted = (0, types_1.decryptData)(session, datagram.payload);
213
- this.sessions.set(datagram.sender, session);
214
- return decrypted;
215
+ this.sessions.set(datagram.sessionTag, session);
216
+ return { session, payload: decrypted };
215
217
  });
216
218
  }
217
219
  open(datagram) {
218
220
  return __awaiter(this, void 0, void 0, function* () {
219
- var _a;
220
221
  if (datagram instanceof Uint8Array)
221
222
  datagram = types_1.Datagram.from(datagram);
222
223
  switch (datagram.protocol) {
223
- case types_1.Protocols.HANDSHAKE:
224
- if (!datagram.payload)
224
+ case types_1.Protocols.HANDSHAKE: {
225
+ const encrypted = types_1.EncryptedDatagram.from(datagram);
226
+ if (!encrypted.payload)
225
227
  throw new Error("Missing payload");
226
- if (yield this.sessions.has(datagram.sender)) {
228
+ if (yield this.sessions.has(encrypted.sessionTag)) {
227
229
  //console.debug("Opening Handshake Ack");
228
- const payload = yield this.decrypt(datagram);
229
- const identityKey = (_a = (yield this.sessions.get(datagram.sender))) === null || _a === void 0 ? void 0 : _a.identityKey;
230
- if (!identityKey)
231
- throw new Error("Missing user");
232
- if (!(0, utils_1.compareBytes)(payload, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey)))
230
+ const session = yield this.sessions.get(encrypted.sessionTag);
231
+ const { payload } = yield this.decrypt(encrypted);
232
+ if (!session)
233
+ throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
234
+ if (!(0, utils_1.compareBytes)(payload, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)))
233
235
  throw new Error("Error validating handshake data");
234
- this.emitter.emit('handshake', { header: datagram.header });
236
+ this.emitter.emit('handshake', { session });
235
237
  return;
236
238
  }
237
239
  //console.debug("Opening Handshake Syn");
238
- const data = (0, utils_1.decodeData)(datagram.payload);
239
- if (!types_1.Datagram.verify(datagram, types_1.IdentityKey.from(data.identityKey).signatureKey))
240
+ const data = (0, utils_1.decodeData)(encrypted.payload);
241
+ if (!datagram.verify(types_1.IdentityKey.from(data.identityKey).signatureKey))
240
242
  throw new Error("Signature not verified");
241
243
  const { session, associatedData } = yield this.keyExchange.digestMessage(data);
242
- yield this.sessions.set(session.userId.toString(), session);
244
+ yield this.sessions.set(session.sessionTag, session);
245
+ yield this.users.set(session.userId.toString(), session.sessionTag);
243
246
  yield this.bundles.set(session.userId.toString(), (0, utils_1.decodeData)(associatedData));
244
- yield this.sendHandshake(session.userId);
245
- this.emitter.emit('handshake', { header: datagram.header });
247
+ yield this.sendHandshake(session);
248
+ this.emitter.emit('handshake', { session });
246
249
  return;
250
+ }
247
251
  case types_1.Protocols.MESSAGE:
248
252
  //console.debug("Opening Message");
249
- this.emitter.emit('message', { header: datagram.header, payload: yield this.decrypt(datagram) });
253
+ this.emitter.emit('message', yield this.decrypt(datagram));
250
254
  return;
251
- case types_1.Protocols.RELAY:
255
+ case types_1.Protocols.RELAY: {
252
256
  //console.debug("Opening Relay");
253
- this.emitter.emit('send', { header: types_1.Datagram.from(yield this.decrypt(datagram)) });
257
+ const opened = yield this.decrypt(datagram);
258
+ const userId = (0, utils_1.decodeBase64)(opened.payload.subarray(0, types_1.UserId.keyLength));
259
+ const sessionTag = yield this.users.get(userId);
260
+ if (!sessionTag)
261
+ throw new Error("Session not found for user: " + userId);
262
+ const session = yield this.sessions.get(sessionTag);
263
+ if (!session)
264
+ throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
265
+ this.emitter.emit('send', { session, payload: opened.payload.slice(types_1.UserId.keyLength), userId: session.userId });
254
266
  return;
255
- case types_1.Protocols.DISCOVER:
267
+ }
268
+ case types_1.Protocols.DISCOVER: {
256
269
  //console.debug("Opening Discover");
257
- const message = (0, utils_1.decodeData)(yield this.decrypt(datagram));
270
+ const { session, payload } = yield this.decrypt(datagram);
271
+ const message = (0, utils_1.decodeData)(payload);
258
272
  if (message.type === types_1.DiscoverType.REQUEST && message.discoverId && !(yield this.sessions.has(message.discoverId))) {
259
273
  let data;
260
274
  if (message.discoverId === this.userId.toString()) {
@@ -279,7 +293,7 @@ class FreeSignalNode {
279
293
  };
280
294
  }
281
295
  const response = { type: types_1.DiscoverType.RESPONSE, discoverId: message.discoverId, data };
282
- this.emitter.emit('send', yield this.encrypt(datagram.sender, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(response)));
296
+ this.emitter.emit('send', yield this.encrypt(session.userId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(response)));
283
297
  }
284
298
  else if (message.type === types_1.DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
285
299
  this.discovers.delete(message.discoverId);
@@ -287,24 +301,30 @@ class FreeSignalNode {
287
301
  yield this.sendHandshake(message.data);
288
302
  }
289
303
  return;
290
- case types_1.Protocols.BOOTSTRAP:
304
+ }
305
+ case types_1.Protocols.BOOTSTRAP: {
291
306
  //console.debug("Opening Bootstrap");
292
307
  if (!datagram.payload)
293
308
  throw new Error("Invalid Bootstrap");
294
309
  const keyExchangeData = (0, utils_1.decodeData)(datagram.payload);
295
- if (!(0, utils_1.compareBytes)(types_1.UserId.fromKey(keyExchangeData.identityKey).toBytes(), (0, utils_1.encodeBase64)(datagram.sender)))
296
- new Error("Malicious bootstrap request");
297
- const request = new BootstrapRequest(datagram.sender, keyExchangeData);
298
- request.onChange = () => {
310
+ const userId = types_1.UserId.fromKey(keyExchangeData.identityKey);
311
+ const request = new BootstrapRequest(userId, keyExchangeData);
312
+ request.onChange = (event) => {
313
+ var _a;
299
314
  if (!request.data)
300
315
  throw new Error("Error sending handshake");
301
- this.sendHandshake(request.data);
316
+ this.sendHandshake((_a = event.data) === null || _a === void 0 ? void 0 : _a.data);
302
317
  };
303
- yield this.bootstraps.set(datagram.sender, request);
304
- this.emitter.emit('bootstrap', { header: datagram.header, request });
318
+ yield this.bootstraps.set(userId.toString(), request);
319
+ this.emitter.emit('bootstrap', { request });
305
320
  return;
321
+ }
306
322
  case types_1.Protocols.PING:
307
- this.emitter.emit('ping', { header: datagram.header });
323
+ datagram = types_1.EncryptedDatagram.from(datagram);
324
+ const session = yield this.sessions.get(datagram.sessionTag);
325
+ if (!session)
326
+ throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
327
+ this.emitter.emit('ping', { session });
308
328
  return;
309
329
  default:
310
330
  throw new Error("Invalid protocol");
package/dist/types.d.ts CHANGED
@@ -22,6 +22,7 @@ export declare function encryptData(session: KeySession, data: Uint8Array): Encr
22
22
  export declare function decryptData(session: KeySession, encryptedData: Uint8Array): Uint8Array;
23
23
  export declare class UserId implements Encodable {
24
24
  private readonly array;
25
+ static readonly keyLength = 32;
25
26
  private constructor();
26
27
  toString(): string;
27
28
  toJSON(): string;
@@ -86,56 +87,39 @@ export declare namespace Protocols {
86
87
  function decode(array: Uint8Array): Protocols;
87
88
  }
88
89
  interface DatagramJSON {
89
- id: string;
90
90
  version: number;
91
- sender: string;
92
- receiver: string;
91
+ sessionTag: string | undefined;
93
92
  protocol: Protocols;
94
- createdAt: number;
95
93
  payload: string | undefined;
96
94
  signature: string | undefined;
97
95
  }
98
96
  export interface SignedDatagram extends Datagram {
99
97
  signature: string;
100
98
  }
101
- export declare class DatagramHeader implements Encodable {
102
- static readonly headerLength: number;
103
- readonly id: string;
104
- readonly version: number;
105
- readonly sender: string;
106
- readonly receiver: string;
107
- readonly protocol: Protocols;
108
- readonly createdAt: number;
109
- private constructor();
110
- toBytes(): Uint8Array;
111
- static from(data: Uint8Array | string): DatagramHeader;
112
- }
113
- export declare class Datagram implements Encodable, DatagramHeader {
99
+ export declare class Datagram implements Encodable {
114
100
  static version: number;
115
- private _id;
101
+ static readonly headerLength = 34;
116
102
  private _version;
117
- readonly sender: string;
118
- readonly receiver: string;
103
+ readonly sessionTag?: string;
119
104
  readonly protocol: Protocols;
120
- private _createdAt;
121
- private _payload?;
105
+ readonly payload?: Uint8Array;
122
106
  private _signature?;
123
- constructor(sender: Uint8Array | string, receiver: Uint8Array | string, protocol: Protocols, payload?: Uint8Array | Encodable);
124
- get id(): string;
107
+ constructor(protocol: Protocols, payload?: Uint8Array | Encodable, sessionTag?: Uint8Array | string);
125
108
  get version(): number;
126
- get createdAt(): number;
127
- set payload(data: Uint8Array);
128
- get payload(): Uint8Array | undefined;
129
109
  get signature(): string | undefined;
130
110
  private get unsigned();
131
- get header(): DatagramHeader;
132
111
  toBytes(): Uint8Array;
133
112
  sign(secretKey: Uint8Array): SignedDatagram;
113
+ verify(publicKey: Uint8Array): boolean;
134
114
  toString(): string;
135
115
  toJSON(): DatagramJSON;
136
- static verify(datagram: Datagram, publicKey: Uint8Array): boolean;
137
116
  static from(data: Uint8Array | Datagram | string): Datagram;
138
117
  }
118
+ export declare class EncryptedDatagram extends Datagram {
119
+ readonly sessionTag: string;
120
+ constructor(protocol: Protocols, sessionTag: Uint8Array | string, payload?: Uint8Array | Encodable);
121
+ static from(data: Uint8Array | Datagram | string): EncryptedDatagram;
122
+ }
139
123
  export declare class EncryptionHeader implements EncryptionKeys, Encodable {
140
124
  readonly nonce: Uint8Array;
141
125
  static readonly keyLength: number;
package/dist/types.js CHANGED
@@ -30,7 +30,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
30
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.AsyncMap = exports.EncryptedData = exports.EncryptionHeader = exports.Datagram = exports.DatagramHeader = exports.Protocols = exports.DiscoverType = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
33
+ exports.AsyncMap = exports.EncryptedData = exports.EncryptionHeader = exports.EncryptedDatagram = exports.Datagram = exports.Protocols = exports.DiscoverType = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
34
34
  exports.encryptData = encryptData;
35
35
  exports.decryptData = decryptData;
36
36
  const utils_1 = require("@freesignal/utils");
@@ -90,7 +90,7 @@ class UserId {
90
90
  identityKey = (0, utils_1.encodeBase64)(identityKey);
91
91
  else if (identityKey instanceof IdentityKey)
92
92
  identityKey = (identityKey).toBytes();
93
- return new UserId(crypto_1.default.hkdf(identityKey, new Uint8Array(32).fill(0), "/freesignal/userid"));
93
+ return new UserId(crypto_1.default.hkdf(identityKey, new Uint8Array(32).fill(0), "/freesignal/userid", UserId.keyLength));
94
94
  }
95
95
  static from(userId) {
96
96
  if (typeof userId === 'string')
@@ -99,6 +99,7 @@ class UserId {
99
99
  }
100
100
  }
101
101
  exports.UserId = UserId;
102
+ UserId.keyLength = 32;
102
103
  class IdentityKey {
103
104
  constructor(identityKey) {
104
105
  if (identityKey instanceof IdentityKey) {
@@ -231,52 +232,16 @@ var Protocols;
231
232
  Protocols.decode = decode;
232
233
  })(Protocols || (exports.Protocols = Protocols = {}));
233
234
  ;
234
- class DatagramHeader {
235
- constructor(data) {
236
- this.version = data[0] & 127;
237
- this.protocol = Protocols.decode(data.subarray(1, 2));
238
- this.id = crypto_1.default.UUID.stringify(data.subarray(2, 18));
239
- this.createdAt = (0, utils_1.bytesToNumber)(data.subarray(18, 26));
240
- this.sender = (0, utils_1.decodeBase64)(data.subarray(26, 26 + crypto_1.default.EdDSA.publicKeyLength));
241
- this.receiver = (0, utils_1.decodeBase64)(data.subarray(26 + crypto_1.default.EdDSA.publicKeyLength, DatagramHeader.headerLength));
242
- }
243
- toBytes() {
244
- return (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(this.version, 1), Protocols.encode(this.protocol, 1), crypto_1.default.UUID.parse(this.id), (0, utils_1.numberToBytes)(this.createdAt, 8), (0, utils_1.encodeBase64)(this.sender), (0, utils_1.encodeBase64)(this.receiver));
245
- }
246
- static from(data) {
247
- if (typeof data === 'string')
248
- data = (0, utils_1.encodeBase64)(data);
249
- return new DatagramHeader(data);
250
- }
251
- }
252
- exports.DatagramHeader = DatagramHeader;
253
- DatagramHeader.headerLength = 26 + crypto_1.default.EdDSA.publicKeyLength * 2;
254
235
  class Datagram {
255
- constructor(sender, receiver, protocol, payload) {
256
- this._id = crypto_1.default.UUID.generate().toString();
236
+ constructor(protocol, payload, sessionTag) {
257
237
  this._version = Datagram.version;
258
- this.sender = typeof sender === 'string' ? sender : (0, utils_1.decodeBase64)(sender);
259
- this.receiver = typeof receiver === 'string' ? receiver : (0, utils_1.decodeBase64)(receiver);
238
+ this.sessionTag = sessionTag instanceof Uint8Array ? (0, utils_1.decodeBase64)(sessionTag) : sessionTag;
260
239
  this.protocol = protocol;
261
- this._createdAt = Date.now();
262
- this._payload = payload instanceof Uint8Array ? payload : payload === null || payload === void 0 ? void 0 : payload.toBytes();
263
- }
264
- get id() {
265
- return this._id;
240
+ this.payload = payload instanceof Uint8Array ? payload : payload === null || payload === void 0 ? void 0 : payload.toBytes();
266
241
  }
267
242
  get version() {
268
243
  return this._version;
269
244
  }
270
- get createdAt() {
271
- return this._createdAt;
272
- }
273
- set payload(data) {
274
- this._signature = undefined;
275
- this._payload = data;
276
- }
277
- get payload() {
278
- return this._payload;
279
- }
280
245
  get signature() {
281
246
  return this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined;
282
247
  }
@@ -285,60 +250,48 @@ class Datagram {
285
250
  data[0] &= 127;
286
251
  return data.subarray(0, data.length - (this._signature ? crypto_1.default.EdDSA.signatureLength : 0));
287
252
  }
288
- get header() {
289
- return DatagramHeader.from(this.toBytes().slice(0, DatagramHeader.headerLength));
290
- }
291
253
  toBytes() {
292
- var _a, _b, _c;
254
+ var _a, _b;
293
255
  return (0, utils_1.concatBytes)(new Uint8Array(1).fill(this.version | (this.signature ? 128 : 0)), //1
294
256
  Protocols.encode(this.protocol), //1
295
- (_a = crypto_1.default.UUID.parse(this.id)) !== null && _a !== void 0 ? _a : [], //16
296
- new Uint8Array((0, utils_1.numberToBytes)(this._createdAt, 8)), //8
297
- (0, utils_1.encodeBase64)(this.sender), //32
298
- (0, utils_1.encodeBase64)(this.receiver), //32
299
- (_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array(), (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
257
+ this.sessionTag ? (0, utils_1.encodeBase64)(this.sessionTag) : new Uint8Array(32).fill(0), //32
258
+ (_a = this.payload) !== null && _a !== void 0 ? _a : new Uint8Array(), (_b = this._signature) !== null && _b !== void 0 ? _b : new Uint8Array());
300
259
  }
301
260
  sign(secretKey) {
302
261
  this._signature = crypto_1.default.EdDSA.sign(this.unsigned, secretKey);
303
262
  return this;
304
263
  }
264
+ verify(publicKey) {
265
+ if (!this._signature)
266
+ throw new Error("Datagram not signed");
267
+ return crypto_1.default.EdDSA.verify(this.unsigned, this._signature, publicKey);
268
+ }
305
269
  toString() {
306
270
  return (0, utils_1.decodeBase64)(this.toBytes());
307
271
  }
308
272
  toJSON() {
309
273
  return {
310
- id: this.id,
311
274
  version: this.version,
312
- sender: this.sender,
313
- receiver: this.receiver,
275
+ sessionTag: this.sessionTag,
314
276
  protocol: this.protocol,
315
- createdAt: this.createdAt,
316
277
  payload: this.payload ? (0, utils_1.decodeBase64)(this.payload) : undefined,
317
278
  signature: this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined
318
279
  };
319
280
  }
320
- static verify(datagram, publicKey) {
321
- if (!datagram._signature)
322
- throw new Error("Datagram not signed");
323
- return crypto_1.default.EdDSA.verify(datagram.unsigned, datagram._signature, publicKey);
324
- }
325
281
  static from(data) {
326
282
  if (typeof data === 'string')
327
283
  data = (0, utils_1.encodeBase64)(data);
328
284
  if (data instanceof Uint8Array) {
329
- const datagram = new Datagram((0, utils_1.decodeBase64)(data.subarray(26, 26 + crypto_1.default.EdDSA.publicKeyLength)), (0, utils_1.decodeBase64)(data.subarray(26 + crypto_1.default.EdDSA.publicKeyLength, DatagramHeader.headerLength)), Protocols.decode(data.subarray(1, 2)), data.subarray(DatagramHeader.headerLength, data.length - (data[0] & 128 ? crypto_1.default.EdDSA.signatureLength : 0)));
285
+ const authTag = data.subarray(2, Datagram.headerLength);
286
+ const datagram = new Datagram(Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerLength, data.length - (data[0] & 128 ? crypto_1.default.EdDSA.signatureLength : 0)), (0, utils_1.compareBytes)(authTag, new Uint8Array(32).fill(0)) ? undefined : (0, utils_1.decodeBase64)(authTag));
330
287
  datagram._version = data[0] & 127;
331
- datagram._id = crypto_1.default.UUID.stringify(data.subarray(2, 18));
332
- datagram._createdAt = (0, utils_1.bytesToNumber)(data.subarray(18, 26));
333
288
  if (data[0] & 128)
334
289
  datagram._signature = data.subarray(data.length - crypto_1.default.EdDSA.signatureLength);
335
290
  return datagram;
336
291
  }
337
292
  else if (data instanceof Datagram) {
338
- const datagram = new Datagram(data.sender, data.receiver, data.protocol, data.payload);
339
- datagram._id = data.id;
293
+ const datagram = new Datagram(data.protocol, data.payload, data.sessionTag);
340
294
  datagram._version = data.version;
341
- datagram._createdAt = data._createdAt;
342
295
  datagram._signature = data._signature;
343
296
  return datagram;
344
297
  }
@@ -348,6 +301,19 @@ class Datagram {
348
301
  }
349
302
  exports.Datagram = Datagram;
350
303
  Datagram.version = 1;
304
+ Datagram.headerLength = 34;
305
+ class EncryptedDatagram extends Datagram {
306
+ constructor(protocol, sessionTag, payload) {
307
+ super(protocol, payload, sessionTag);
308
+ }
309
+ static from(data) {
310
+ const datagram = Datagram.from(data);
311
+ if (!datagram.sessionTag)
312
+ throw new Error("Datagram not encrypted");
313
+ return datagram;
314
+ }
315
+ }
316
+ exports.EncryptedDatagram = EncryptedDatagram;
351
317
  class EncryptionHeader {
352
318
  constructor(keys, nonce) {
353
319
  this.nonce = nonce;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freesignal/protocol",
3
- "version": "0.7.1",
3
+ "version": "0.7.4",
4
4
  "description": "Signal Protocol implementation in javascript",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "Christian Braghette",
@@ -37,8 +37,7 @@
37
37
  "@freesignal/crypto": "^0.3.0",
38
38
  "@freesignal/interfaces": "^0.2.0",
39
39
  "@freesignal/utils": "^1.4.1",
40
- "easyemitter.ts": "^1.0.3",
41
- "semaphore.ts": "^0.2.0"
40
+ "easyemitter.ts": "^1.0.3"
42
41
  },
43
42
  "devDependencies": {
44
43
  "@types/node": "^24.2.1"