@freesignal/protocol 0.7.0 → 0.7.3

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,48 +33,57 @@ 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
+ request?: BootstrapRequest;
40
+ };
41
+ type SendEventData = {
42
+ session?: KeySession;
43
+ datagram: Datagram;
39
44
  };
40
45
  type MessageEventData = {
41
- header: DatagramHeader;
46
+ session: KeySession;
42
47
  payload: Uint8Array;
43
48
  };
44
49
  export declare class FreeSignalNode {
45
50
  protected readonly privateIdentityKey: PrivateIdentityKey;
46
51
  protected readonly sessions: SessionMap;
52
+ protected readonly users: LocalStorage<string, string>;
47
53
  protected readonly bundles: LocalStorage<string, KeyExchangeDataBundle>;
48
54
  protected readonly keyExchange: KeyExchange;
49
55
  protected readonly discovers: Set<string>;
50
56
  protected readonly bootstraps: LocalStorage<string, BootstrapRequest>;
51
- protected readonly emitter: EventEmitter<"message" | "send" | "handshaked" | "ping", NodeEventData>;
57
+ protected readonly emitter: EventEmitter<"message" | "send" | "handshake" | "ping" | "bootstrap", NodeEventData>;
52
58
  constructor(storage: Database<{
53
59
  sessions: LocalStorage<string, ExportedKeySession>;
60
+ users: LocalStorage<string, string>;
54
61
  keyExchange: LocalStorage<string, Crypto.KeyPair>;
55
62
  bundles: LocalStorage<string, KeyExchangeDataBundle>;
56
63
  bootstraps: LocalStorage<string, BootstrapRequest>;
57
64
  }>, privateIdentityKey?: PrivateIdentityKey);
65
+ protected messageHandler: EventCall<"message", NodeEventData>;
66
+ protected sendHandler: EventCall<"send", NodeEventData>;
67
+ protected handshakeHandler: EventCall<"handshake", NodeEventData>;
68
+ protected bootstrapHandler: EventCall<"bootstrap", NodeEventData>;
58
69
  onMessage: (data: MessageEventData) => void;
59
70
  onSend: (data: Uint8Array) => void;
60
- onHandshaked: (userId: UserId) => void;
71
+ onHandshake: (userId: UserId) => void;
72
+ onRequest: (request: BootstrapRequest) => void;
73
+ getRequest(userId: string): Promise<BootstrapRequest | undefined>;
61
74
  waitHandshaked(userId: UserId | string, timeout?: number): Promise<void>;
62
75
  get identityKey(): IdentityKey;
63
76
  get userId(): UserId;
64
- readonly requests: {
65
- onRequest: (request: BootstrapRequest) => void;
66
- getRequest: (userId: string) => Promise<BootstrapRequest | undefined>;
67
- };
68
- protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
77
+ protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<SendEventData>;
69
78
  sendHandshake(data: KeyExchangeData): Promise<void>;
70
- sendHandshake(receiverId: string | UserId): Promise<void>;
79
+ sendHandshake(session: KeySession): Promise<void>;
71
80
  sendData<T>(receiverId: string | UserId, data: T): Promise<void>;
72
- sendRelay(receiverId: string | UserId, data: Datagram): Promise<void>;
81
+ sendRelay(relayId: string | UserId, receiverId: string | UserId, data: Datagram): Promise<void>;
73
82
  sendPing(receiverId: string | UserId): Promise<void>;
74
83
  sendDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<void>;
75
84
  sendBootstrap(receiverId: string | UserId): Promise<void>;
76
- protected decrypt(datagram: Datagram): Promise<Uint8Array>;
77
- protected open(datagram: Datagram | Uint8Array): Promise<void>;
85
+ protected decrypt(datagram: EncryptedDatagram | Datagram | Uint8Array): Promise<MessageEventData>;
86
+ protected open(datagram: Datagram | EncryptedDatagram | Uint8Array): Promise<void>;
78
87
  }
79
88
  declare class SessionMap implements LocalStorage<string, KeySession> {
80
89
  readonly storage: LocalStorage<string, ExportedKeySession>;
package/dist/node.js CHANGED
@@ -82,29 +82,34 @@ 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({ 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
+ this.sendHandler = (data) => this.onSend(data.data.datagram.toBytes());
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
+ this.bootstrapHandler = (data) => { var _a; return this.onRequest((_a = data.data) === null || _a === void 0 ? void 0 : _a.request); };
85
89
  this.onMessage = () => { };
86
90
  this.onSend = () => { };
87
- this.onHandshaked = () => { };
88
- this.requests = {
89
- onRequest: () => { },
90
- getRequest: (userId) => {
91
- return this.bootstraps.get(userId);
92
- }
93
- };
91
+ this.onHandshake = () => { };
92
+ this.onRequest = () => { };
94
93
  this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
95
94
  this.sessions = new SessionMap(storage.sessions);
95
+ this.users = storage.users;
96
96
  this.keyExchange = new x3dh_1.KeyExchange(storage.keyExchange, this.privateIdentityKey);
97
97
  this.bundles = storage.bundles;
98
98
  this.bootstraps = storage.bootstraps;
99
- this.emitter.on('send', (data) => this.onSend(data.data.datagram.toBytes()));
100
- this.emitter.on('handshaked', (data) => this.onHandshaked(types_1.UserId.from(data.data.header.sender)));
99
+ this.emitter.on('message', this.messageHandler);
100
+ this.emitter.on('send', this.sendHandler);
101
+ this.emitter.on('handshake', this.handshakeHandler);
102
+ this.emitter.on('bootstrap', this.bootstrapHandler);
103
+ }
104
+ getRequest(userId) {
105
+ return this.bootstraps.get(userId);
101
106
  }
102
107
  waitHandshaked(userId, timeout) {
103
108
  return __awaiter(this, void 0, void 0, function* () {
104
- var _a;
109
+ var _a, _b;
105
110
  if (timeout)
106
111
  setTimeout(() => { throw new Error(); }, timeout);
107
- while (((_a = (yield this.emitter.wait('handshaked', 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())
108
113
  ;
109
114
  });
110
115
  }
@@ -116,55 +121,58 @@ class FreeSignalNode {
116
121
  }
117
122
  encrypt(receiverId, protocol, data) {
118
123
  return __awaiter(this, void 0, void 0, function* () {
119
- if (receiverId instanceof types_1.UserId)
120
- receiverId = receiverId.toString();
121
- 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);
122
128
  if (!session)
123
- throw new Error("Session not found for user: " + receiverId);
129
+ throw new Error("Session not found for sessionTag: " + sessionTag);
124
130
  const encrypted = (0, types_1.encryptData)(session, data);
125
- this.sessions.set(receiverId, session);
126
- 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, datagram: new types_1.EncryptedDatagram(protocol, session.sessionTag, encrypted).sign(this.privateIdentityKey.signatureKey) };
127
133
  });
128
134
  }
129
135
  sendHandshake(data) {
130
136
  return __awaiter(this, void 0, void 0, function* () {
131
- var _a;
132
- if (typeof data === 'string' || data instanceof types_1.UserId) {
137
+ if (data instanceof double_ratchet_1.KeySession) {
133
138
  //console.debug("Sending Handshake Ack");
134
- const userId = data.toString();
135
- const identityKey = (_a = (yield this.sessions.get(userId))) === null || _a === void 0 ? void 0 : _a.identityKey;
136
- if (!identityKey)
137
- throw new Error("Missing user");
138
- const datagram = yield this.encrypt(userId, types_1.Protocols.HANDSHAKE, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey));
139
- 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)));
140
143
  return;
141
144
  }
142
145
  //console.debug("Sending Handshake Syn");
143
146
  const { session, message } = yield this.keyExchange.digestData(data, (0, utils_1.encodeData)(yield this.keyExchange.generateBundle()));
144
- yield this.sessions.set(session.userId.toString(), session);
145
- 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);
146
- 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 });
147
151
  });
148
152
  }
149
153
  sendData(receiverId, data) {
150
154
  return __awaiter(this, void 0, void 0, function* () {
151
155
  //console.debug("Sending Data");
152
- const datagram = yield this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
153
- 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)));
154
157
  });
155
158
  }
156
- sendRelay(receiverId, data) {
159
+ sendRelay(relayId, receiverId, data) {
157
160
  return __awaiter(this, void 0, void 0, function* () {
158
161
  //console.debug("Sending Relay");
159
- const datagram = yield this.encrypt(receiverId, types_1.Protocols.RELAY, data.toBytes());
160
- 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())));
161
163
  });
162
164
  }
163
165
  sendPing(receiverId) {
164
166
  return __awaiter(this, void 0, void 0, function* () {
165
167
  //console.debug("Sending Ping");
166
- const datagram = new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.PING);
167
- 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 });
168
176
  });
169
177
  }
170
178
  sendDiscover(receiverId, discoverId) {
@@ -179,76 +187,88 @@ class FreeSignalNode {
179
187
  discoverId
180
188
  };
181
189
  this.discovers.add(receiverId);
182
- const datagram = yield this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(message));
183
- 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)));
184
191
  });
185
192
  }
186
193
  sendBootstrap(receiverId) {
187
194
  return __awaiter(this, void 0, void 0, function* () {
188
195
  //console.debug("Sending Bootstrap");
189
- const datagram = new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.BOOTSTRAP, (0, utils_1.encodeData)(yield this.keyExchange.generateData()));
190
- this.emitter.emit('send', { header: datagram.header, datagram });
196
+ if (yield this.sessions.has(receiverId.toString()))
197
+ throw new Error("Session exists");
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 });
191
200
  });
192
201
  }
193
202
  decrypt(datagram) {
194
203
  return __awaiter(this, void 0, void 0, function* () {
195
- var _a;
196
- const identityKey = (_a = (yield this.sessions.get(datagram.sender))) === null || _a === void 0 ? void 0 : _a.identityKey;
197
- if (!identityKey)
198
- throw new Error("User IdentityKey not found");
199
- if (!types_1.Datagram.verify(datagram, identityKey.signatureKey))
200
- throw new Error("Signature not verified");
201
- 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);
202
208
  if (!session)
203
- 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");
204
212
  if (!datagram.payload)
205
213
  throw new Error("Missing payload");
206
214
  const decrypted = (0, types_1.decryptData)(session, datagram.payload);
207
- this.sessions.set(datagram.sender, session);
208
- return decrypted;
215
+ this.sessions.set(datagram.sessionTag, session);
216
+ return { session, payload: decrypted };
209
217
  });
210
218
  }
211
219
  open(datagram) {
212
220
  return __awaiter(this, void 0, void 0, function* () {
213
- var _a;
214
221
  if (datagram instanceof Uint8Array)
215
222
  datagram = types_1.Datagram.from(datagram);
216
223
  switch (datagram.protocol) {
217
- case types_1.Protocols.HANDSHAKE:
218
- if (!datagram.payload)
224
+ case types_1.Protocols.HANDSHAKE: {
225
+ const encrypted = types_1.EncryptedDatagram.from(datagram);
226
+ if (!encrypted.payload)
219
227
  throw new Error("Missing payload");
220
- if (yield this.sessions.has(datagram.sender)) {
228
+ if (yield this.sessions.has(encrypted.sessionTag)) {
221
229
  //console.debug("Opening Handshake Ack");
222
- const payload = yield this.decrypt(datagram);
223
- const identityKey = (_a = (yield this.sessions.get(datagram.sender))) === null || _a === void 0 ? void 0 : _a.identityKey;
224
- if (!identityKey)
225
- throw new Error("Missing user");
226
- 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)))
227
235
  throw new Error("Error validating handshake data");
228
- this.emitter.emit('handshaked', { header: datagram.header });
236
+ this.emitter.emit('handshake', { session });
229
237
  return;
230
238
  }
231
239
  //console.debug("Opening Handshake Syn");
232
- const data = (0, utils_1.decodeData)(datagram.payload);
233
- 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))
234
242
  throw new Error("Signature not verified");
235
243
  const { session, associatedData } = yield this.keyExchange.digestMessage(data);
236
- 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);
237
246
  yield this.bundles.set(session.userId.toString(), (0, utils_1.decodeData)(associatedData));
238
- yield this.sendHandshake(session.userId);
239
- this.emitter.emit('handshaked', { header: datagram.header });
247
+ yield this.sendHandshake(session);
248
+ this.emitter.emit('handshake', { session });
240
249
  return;
250
+ }
241
251
  case types_1.Protocols.MESSAGE:
242
252
  //console.debug("Opening Message");
243
- this.emitter.emit('message', { header: datagram.header, payload: yield this.decrypt(datagram) });
253
+ this.emitter.emit('message', yield this.decrypt(datagram));
244
254
  return;
245
- case types_1.Protocols.RELAY:
255
+ case types_1.Protocols.RELAY: {
246
256
  //console.debug("Opening Relay");
247
- 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) });
248
266
  return;
249
- case types_1.Protocols.DISCOVER:
267
+ }
268
+ case types_1.Protocols.DISCOVER: {
250
269
  //console.debug("Opening Discover");
251
- 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);
252
272
  if (message.type === types_1.DiscoverType.REQUEST && message.discoverId && !(yield this.sessions.has(message.discoverId))) {
253
273
  let data;
254
274
  if (message.discoverId === this.userId.toString()) {
@@ -273,7 +293,7 @@ class FreeSignalNode {
273
293
  };
274
294
  }
275
295
  const response = { type: types_1.DiscoverType.RESPONSE, discoverId: message.discoverId, data };
276
- 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)));
277
297
  }
278
298
  else if (message.type === types_1.DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
279
299
  this.discovers.delete(message.discoverId);
@@ -281,24 +301,30 @@ class FreeSignalNode {
281
301
  yield this.sendHandshake(message.data);
282
302
  }
283
303
  return;
284
- case types_1.Protocols.BOOTSTRAP:
304
+ }
305
+ case types_1.Protocols.BOOTSTRAP: {
285
306
  //console.debug("Opening Bootstrap");
286
307
  if (!datagram.payload)
287
308
  throw new Error("Invalid Bootstrap");
288
309
  const keyExchangeData = (0, utils_1.decodeData)(datagram.payload);
289
- if (!(0, utils_1.compareBytes)(types_1.UserId.fromKey(keyExchangeData.identityKey).toBytes(), (0, utils_1.encodeBase64)(datagram.sender)))
290
- new Error("Malicious bootstrap request");
291
- const request = new BootstrapRequest(datagram.sender, keyExchangeData);
292
- 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;
293
314
  if (!request.data)
294
315
  throw new Error("Error sending handshake");
295
- this.sendHandshake(request.data);
316
+ this.sendHandshake((_a = event.data) === null || _a === void 0 ? void 0 : _a.data);
296
317
  };
297
- yield this.bootstraps.set(datagram.sender, request);
298
- this.requests.onRequest(request);
318
+ yield this.bootstraps.set(userId.toString(), request);
319
+ this.emitter.emit('bootstrap', { request });
299
320
  return;
321
+ }
300
322
  case types_1.Protocols.PING:
301
- 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 });
302
328
  return;
303
329
  default:
304
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,46 +1,45 @@
1
- {
2
- "name": "@freesignal/protocol",
3
- "version": "0.7.0",
4
- "description": "Signal Protocol implementation in javascript",
5
- "license": "GPL-3.0-or-later",
6
- "author": "Christian Braghette",
7
- "type": "commonjs",
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.3.0",
38
- "@freesignal/interfaces": "^0.2.0",
39
- "@freesignal/utils": "^1.4.1",
40
- "easyemitter.ts": "^1.0.3",
41
- "semaphore.ts": "^0.2.0"
42
- },
43
- "devDependencies": {
44
- "@types/node": "^24.2.1"
45
- }
46
- }
1
+ {
2
+ "name": "@freesignal/protocol",
3
+ "version": "0.7.3",
4
+ "description": "Signal Protocol implementation in javascript",
5
+ "license": "GPL-3.0-or-later",
6
+ "author": "Christian Braghette",
7
+ "type": "commonjs",
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.3.0",
38
+ "@freesignal/interfaces": "^0.2.0",
39
+ "@freesignal/utils": "^1.4.1",
40
+ "easyemitter.ts": "^1.0.3"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^24.2.1"
44
+ }
45
+ }