@freesignal/protocol 0.3.0 → 0.3.2

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.
@@ -94,7 +94,7 @@ const semaphore_ts_1 = require("semaphore.ts");
94
94
  class KeySession {
95
95
  constructor(storage, opts = {}) {
96
96
  var _a;
97
- this.mutex = new semaphore_ts_1.AsyncMutex();
97
+ this.mutex = { sending: new semaphore_ts_1.AsyncMutex(), receiving: new semaphore_ts_1.AsyncMutex() };
98
98
  this.previousKeys = new KeyMap();
99
99
  this.id = (_a = opts.id) !== null && _a !== void 0 ? _a : crypto_1.default.UUID.generate().toString();
100
100
  this.keyPair = crypto_1.default.ECDH.keyPair(opts.secretKey);
@@ -127,13 +127,13 @@ class KeySession {
127
127
  return __awaiter(this, void 0, void 0, function* () {
128
128
  const env_1 = { stack: [], error: void 0, hasError: false };
129
129
  try {
130
- const lock = __addDisposableResource(env_1, yield this.mutex.acquire(), false);
130
+ const lock = __addDisposableResource(env_1, yield this.mutex.sending.acquire(), false);
131
131
  if (!this.sendingChain)
132
132
  throw new Error("SendingChain not initialized");
133
133
  const key = this.sendingChain.getKey();
134
134
  const nonce = crypto_1.default.randomBytes(EncryptedDataConstructor.nonceLength);
135
135
  const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
136
- yield this.save();
136
+ this.save();
137
137
  return new EncryptedDataConstructor(this.sendingChain.count, this.sendingChain.previousCount, this.keyPair.publicKey, nonce, ciphertext);
138
138
  }
139
139
  catch (e_1) {
@@ -154,11 +154,10 @@ class KeySession {
154
154
  decrypt(payload) {
155
155
  return __awaiter(this, void 0, void 0, function* () {
156
156
  var _a, _b, _c;
157
- const env_2 = { stack: [], error: void 0, hasError: false };
158
- try {
159
- const lock = __addDisposableResource(env_2, yield this.mutex.acquire(), false);
160
- const encrypted = types_1.EncryptedData.from(payload);
161
- if (!(0, utils_1.verifyArrays)(encrypted.publicKey, (_b = (_a = this.receivingChain) === null || _a === void 0 ? void 0 : _a.remoteKey) !== null && _b !== void 0 ? _b : new Uint8Array())) {
157
+ const encrypted = types_1.EncryptedData.from(payload);
158
+ if (!this.previousKeys.has((0, utils_1.decodeBase64)(encrypted.publicKey) + encrypted.count.toString())) {
159
+ const lock = yield this.mutex.receiving.acquire();
160
+ if (!(0, utils_1.compareBytes)(encrypted.publicKey, (_b = (_a = this.receivingChain) === null || _a === void 0 ? void 0 : _a.remoteKey) !== null && _b !== void 0 ? _b : new Uint8Array())) {
162
161
  while (this.receivingChain && this.receivingChain.count < encrypted.previous) {
163
162
  const key = this.receivingChain.getKey();
164
163
  this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
@@ -173,22 +172,16 @@ class KeySession {
173
172
  const key = this.receivingChain.getKey();
174
173
  this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
175
174
  }
176
- const key = this.previousKeys.get((0, utils_1.decodeBase64)(encrypted.publicKey) + encrypted.count.toString());
177
- if (!key)
178
- throw new Error("Error calculating key");
179
- yield this.save();
180
- const cleartext = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key);
181
- if (!cleartext)
182
- throw new Error("Error decrypting ciphertext");
183
- return cleartext;
184
- }
185
- catch (e_2) {
186
- env_2.error = e_2;
187
- env_2.hasError = true;
188
- }
189
- finally {
190
- __disposeResources(env_2);
175
+ lock.release();
191
176
  }
177
+ const key = this.previousKeys.get((0, utils_1.decodeBase64)(encrypted.publicKey) + encrypted.count.toString());
178
+ if (!key)
179
+ throw new Error("Error calculating key");
180
+ this.save();
181
+ const cleartext = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key);
182
+ if (!cleartext)
183
+ throw new Error("Error decrypting ciphertext");
184
+ return cleartext;
192
185
  });
193
186
  }
194
187
  /**
@@ -276,22 +269,22 @@ class EncryptedDataConstructor {
276
269
  return this;
277
270
  }
278
271
  if (typeof arrays[0] === 'number')
279
- arrays[0] = (0, utils_1.numberToArray)(arrays[0], EncryptedDataConstructor.countLength);
272
+ arrays[0] = (0, utils_1.numberToBytes)(arrays[0], EncryptedDataConstructor.countLength);
280
273
  if (typeof arrays[1] === 'number')
281
- arrays[1] = (0, utils_1.numberToArray)(arrays[1], EncryptedDataConstructor.countLength);
274
+ arrays[1] = (0, utils_1.numberToBytes)(arrays[1], EncryptedDataConstructor.countLength);
282
275
  if (arrays.length === 6) {
283
- arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToArray)(arrays[5]) : arrays[5]);
276
+ arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToBytes)(arrays[5], 1) : arrays[5]);
284
277
  arrays.pop();
285
278
  }
286
279
  else if (arrays.length > 1) {
287
- arrays.unshift((0, utils_1.numberToArray)(KeySession.version));
280
+ arrays.unshift((0, utils_1.numberToBytes)(KeySession.version, 1));
288
281
  }
289
- this.raw = (0, utils_1.concatArrays)(...arrays);
282
+ this.raw = (0, utils_1.concatBytes)(...arrays);
290
283
  }
291
284
  get length() { return this.raw.length; }
292
- get version() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
293
- get count() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
294
- get previous() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
285
+ get version() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
286
+ get count() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
287
+ get previous() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
295
288
  get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
296
289
  get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
297
290
  get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
@@ -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 { LocalStorage, Crypto, Database } from "@freesignal/interfaces";
19
+ import { LocalStorage, Crypto, Database, KeyExchangeDataBundle } from "@freesignal/interfaces";
20
20
  import { ExportedKeySession } from "./double-ratchet";
21
21
  import { IdentityKey, PrivateIdentityKey } from "./types";
22
22
  import { FreeSignalNode } from "./node";
@@ -47,5 +47,6 @@ export declare function createNode(storage: Database<{
47
47
  sessions: LocalStorage<string, ExportedKeySession>;
48
48
  keyExchange: LocalStorage<string, Crypto.KeyPair>;
49
49
  users: LocalStorage<string, IdentityKey>;
50
+ bundles: LocalStorage<string, KeyExchangeDataBundle>;
50
51
  }>, privateIdentityKey?: PrivateIdentityKey): FreeSignalNode;
51
52
  export * from "./types";
package/dist/node.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
2
+ import { Datagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
3
+ import { KeyExchange } from "./x3dh";
4
+ import { ExportedKeySession, KeySession } from "./double-ratchet";
5
+ declare class BootstrapRequest {
6
+ #private;
7
+ readonly senderId: UserId | string;
8
+ readonly data: KeyExchangeData;
9
+ private readonly acceptFn;
10
+ constructor(senderId: UserId | string, data: KeyExchangeData, acceptFn: (data: KeyExchangeData) => Promise<Datagram>);
11
+ get status(): "pending" | "accepted" | "denied";
12
+ accept(): Promise<Datagram | undefined>;
13
+ deny(): void;
14
+ }
15
+ type OpenFnReturns = Uint8Array | UserId | Datagram | UserId | KeyExchangeData | undefined | void;
16
+ export declare class FreeSignalNode {
17
+ protected readonly privateIdentityKey: PrivateIdentityKey;
18
+ protected readonly sessions: SessionMap;
19
+ protected readonly users: LocalStorage<string, IdentityKey>;
20
+ protected readonly bundles: LocalStorage<string, KeyExchangeDataBundle>;
21
+ protected readonly keyExchange: KeyExchange;
22
+ protected readonly discovers: Set<string>;
23
+ protected readonly bootstraps: Set<BootstrapRequest>;
24
+ constructor(storage: Database<{
25
+ sessions: LocalStorage<string, ExportedKeySession>;
26
+ keyExchange: LocalStorage<string, Crypto.KeyPair>;
27
+ users: LocalStorage<string, IdentityKey>;
28
+ bundles: LocalStorage<string, KeyExchangeDataBundle>;
29
+ }>, privateIdentityKey?: PrivateIdentityKey);
30
+ get identityKey(): IdentityKey;
31
+ get userId(): UserId;
32
+ get requests(): BootstrapRequest[];
33
+ protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
34
+ packHandshake(data: KeyExchangeData): Promise<Datagram>;
35
+ packData<T>(receiverId: string | UserId, data: T): Promise<Datagram>;
36
+ packRelay(receiverId: string | UserId, data: Datagram): Promise<Datagram>;
37
+ packDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<Datagram>;
38
+ packBootstrap(receiverId: string | UserId): Promise<Datagram>;
39
+ protected decrypt(datagram: Datagram): Promise<Uint8Array>;
40
+ open<T extends OpenFnReturns>(datagram: Datagram | Uint8Array): Promise<T>;
41
+ }
42
+ declare class SessionMap implements LocalStorage<string, KeySession> {
43
+ readonly storage: LocalStorage<string, ExportedKeySession>;
44
+ readonly maxSize: number;
45
+ private readonly cache;
46
+ constructor(storage: LocalStorage<string, ExportedKeySession>, maxSize?: number);
47
+ set(key: string, value: KeySession): Promise<void>;
48
+ get(key: string): Promise<KeySession | undefined>;
49
+ has(key: string): Promise<boolean>;
50
+ delete(key: string): Promise<boolean>;
51
+ clear(): Promise<void>;
52
+ }
53
+ export {};
package/dist/node.js ADDED
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
12
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
13
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
14
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
15
+ };
16
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
17
+ if (kind === "m") throw new TypeError("Private method is not writable");
18
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
19
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
20
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
21
+ };
22
+ var _BootstrapRequest_status;
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.FreeSignalNode = void 0;
25
+ const types_1 = require("./types");
26
+ const x3dh_1 = require("./x3dh");
27
+ const double_ratchet_1 = require("./double-ratchet");
28
+ const _1 = require(".");
29
+ const utils_1 = require("@freesignal/utils");
30
+ class BootstrapRequest {
31
+ constructor(senderId, data, acceptFn) {
32
+ this.senderId = senderId;
33
+ this.data = data;
34
+ this.acceptFn = acceptFn;
35
+ _BootstrapRequest_status.set(this, 'pending');
36
+ }
37
+ get status() {
38
+ return __classPrivateFieldGet(this, _BootstrapRequest_status, "f");
39
+ }
40
+ accept() {
41
+ return __awaiter(this, void 0, void 0, function* () {
42
+ if (this.status === 'pending')
43
+ __classPrivateFieldSet(this, _BootstrapRequest_status, 'accepted', "f");
44
+ if (__classPrivateFieldGet(this, _BootstrapRequest_status, "f") === 'accepted')
45
+ return yield this.acceptFn(this.data);
46
+ });
47
+ }
48
+ deny() {
49
+ if (this.status === 'pending')
50
+ __classPrivateFieldSet(this, _BootstrapRequest_status, 'denied', "f");
51
+ return;
52
+ }
53
+ }
54
+ _BootstrapRequest_status = new WeakMap();
55
+ class FreeSignalNode {
56
+ constructor(storage, privateIdentityKey) {
57
+ this.discovers = new Set();
58
+ this.bootstraps = new Set();
59
+ this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
60
+ this.sessions = new SessionMap(storage.sessions);
61
+ this.keyExchange = new x3dh_1.KeyExchange({ keys: storage.keyExchange, sessions: storage.sessions }, this.privateIdentityKey);
62
+ this.users = storage.users;
63
+ this.bundles = storage.bundles;
64
+ }
65
+ get identityKey() {
66
+ return this.privateIdentityKey.identityKey;
67
+ }
68
+ get userId() {
69
+ return types_1.UserId.fromKey(this.identityKey);
70
+ }
71
+ get requests() {
72
+ return Array.from(this.bootstraps.values());
73
+ }
74
+ encrypt(receiverId, protocol, data) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ if (receiverId instanceof types_1.UserId)
77
+ receiverId = receiverId.toString();
78
+ const session = yield this.sessions.get(receiverId);
79
+ if (!session)
80
+ throw new Error("Session not found for user: " + receiverId);
81
+ return new types_1.Datagram(this.userId.toString(), receiverId, protocol, yield session.encrypt(data)).sign(this.privateIdentityKey.signatureKey);
82
+ });
83
+ }
84
+ packHandshake(data) {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ const { session, message, identityKey } = yield this.keyExchange.digestData(data, (0, utils_1.encodeData)(yield this.keyExchange.generateBundle()));
87
+ const remoteId = types_1.UserId.fromKey(identityKey);
88
+ yield this.users.set(remoteId.toString(), identityKey);
89
+ yield this.sessions.set(remoteId.toString(), session);
90
+ return 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);
91
+ });
92
+ }
93
+ packData(receiverId, data) {
94
+ return this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
95
+ }
96
+ packRelay(receiverId, data) {
97
+ return this.encrypt(receiverId, types_1.Protocols.RELAY, (0, utils_1.encodeData)(data));
98
+ }
99
+ packDiscover(receiverId, discoverId) {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ if (receiverId instanceof types_1.UserId)
102
+ receiverId = receiverId.toString();
103
+ if (discoverId instanceof types_1.UserId)
104
+ discoverId = discoverId.toString();
105
+ const message = {
106
+ type: types_1.DiscoverType.REQUEST,
107
+ discoverId
108
+ };
109
+ this.discovers.add(receiverId);
110
+ return this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(message));
111
+ });
112
+ }
113
+ packBootstrap(receiverId) {
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ return new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.BOOTSTRAP, (0, utils_1.encodeData)(yield this.keyExchange.generateData()));
116
+ });
117
+ }
118
+ decrypt(datagram) {
119
+ return __awaiter(this, void 0, void 0, function* () {
120
+ const signatureKey = yield this.users.get(datagram.sender);
121
+ if (!signatureKey)
122
+ throw new Error("User IdentityKey not found");
123
+ if (!types_1.Datagram.verify(datagram, signatureKey.signatureKey))
124
+ throw new Error("Signature not verified");
125
+ const session = yield this.sessions.get(datagram.sender);
126
+ if (!session)
127
+ throw new Error("Session not found for user: " + datagram.sender);
128
+ if (!datagram.payload)
129
+ throw new Error("Missing payload");
130
+ const decrypted = yield session.decrypt(datagram.payload);
131
+ if (!decrypted)
132
+ throw new Error("Decryption failed");
133
+ return decrypted;
134
+ });
135
+ }
136
+ open(datagram) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ if (datagram instanceof Uint8Array)
139
+ datagram = types_1.Datagram.from(datagram);
140
+ switch (datagram.protocol) {
141
+ case types_1.Protocols.HANDSHAKE:
142
+ if (!datagram.payload)
143
+ throw new Error("Missing payload");
144
+ const data = (0, utils_1.decodeData)(datagram.payload);
145
+ if (!types_1.Datagram.verify(datagram, types_1.IdentityKey.from(data.identityKey).signatureKey))
146
+ throw new Error("Signature not verified");
147
+ const { session, identityKey, associatedData } = yield this.keyExchange.digestMessage(data);
148
+ const userId = types_1.UserId.fromKey(identityKey);
149
+ yield this.users.set(userId.toString(), identityKey);
150
+ yield this.sessions.set(userId.toString(), session);
151
+ yield this.bundles.set(userId.toString(), (0, utils_1.decodeData)(associatedData));
152
+ return;
153
+ case types_1.Protocols.MESSAGE:
154
+ return (0, utils_1.decodeData)(yield this.decrypt(datagram));
155
+ case types_1.Protocols.RELAY:
156
+ return (0, utils_1.decodeData)(yield this.decrypt(datagram));
157
+ case types_1.Protocols.DISCOVER:
158
+ const message = (0, utils_1.decodeData)(yield this.decrypt(datagram));
159
+ if (message.type === types_1.DiscoverType.REQUEST && message.discoverId && !(yield this.users.has(message.discoverId))) {
160
+ let data;
161
+ if (message.discoverId === this.userId.toString()) {
162
+ data = yield this.keyExchange.generateData();
163
+ }
164
+ else {
165
+ const bundle = yield this.bundles.get(message.discoverId);
166
+ if (!bundle)
167
+ return;
168
+ const { version, identityKey, signedPreKey, signature } = bundle;
169
+ const onetimePreKey = bundle.onetimePreKeys.shift();
170
+ if (!onetimePreKey) {
171
+ yield this.bundles.delete(message.discoverId);
172
+ return;
173
+ }
174
+ data = {
175
+ version,
176
+ identityKey,
177
+ signedPreKey,
178
+ signature,
179
+ onetimePreKey
180
+ };
181
+ }
182
+ const response = { type: types_1.DiscoverType.RESPONSE, discoverId: message.discoverId, data };
183
+ return yield this.encrypt(datagram.sender, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(response));
184
+ }
185
+ else if (message.type === types_1.DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
186
+ this.discovers.delete(message.discoverId);
187
+ return message.data;
188
+ }
189
+ return;
190
+ case types_1.Protocols.BOOTSTRAP:
191
+ if (datagram.payload) {
192
+ const data = (0, utils_1.decodeData)(datagram.payload);
193
+ if (!(0, utils_1.compareBytes)(types_1.UserId.fromKey(data.identityKey).toBytes(), (0, utils_1.encodeBase64)(datagram.sender)))
194
+ return;
195
+ this.bootstraps.add(new BootstrapRequest(datagram.sender, data, (data) => this.packHandshake(data)));
196
+ }
197
+ ;
198
+ return;
199
+ default:
200
+ throw new Error("Invalid protocol");
201
+ }
202
+ });
203
+ }
204
+ }
205
+ exports.FreeSignalNode = FreeSignalNode;
206
+ class SessionMap {
207
+ constructor(storage, maxSize = 50) {
208
+ this.storage = storage;
209
+ this.maxSize = maxSize;
210
+ this.cache = new Map();
211
+ }
212
+ set(key, value) {
213
+ this.cache.set(key, value);
214
+ return this.storage.set(key, value.toJSON());
215
+ }
216
+ get(key) {
217
+ return __awaiter(this, void 0, void 0, function* () {
218
+ const session = this.cache.get(key);
219
+ if (!session) {
220
+ const sessionData = yield this.storage.get(key);
221
+ if (!sessionData)
222
+ return undefined;
223
+ return double_ratchet_1.KeySession.from(sessionData, this.storage);
224
+ }
225
+ return session;
226
+ });
227
+ }
228
+ has(key) {
229
+ return __awaiter(this, void 0, void 0, function* () {
230
+ return this.cache.has(key) || (yield this.storage.has(key));
231
+ });
232
+ }
233
+ delete(key) {
234
+ return __awaiter(this, void 0, void 0, function* () {
235
+ return this.cache.delete(key) || (yield this.storage.delete(key));
236
+ });
237
+ }
238
+ clear() {
239
+ this.cache.clear();
240
+ return this.storage.clear();
241
+ }
242
+ }
package/dist/test.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/test.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const utils_1 = require("@freesignal/utils");
16
+ const _1 = require(".");
17
+ const crypto_1 = __importDefault(require("@freesignal/crypto"));
18
+ console.log("FreeSignal protocol test");
19
+ const bob = (0, _1.createNode)({ keyExchange: new _1.AsyncMap(), sessions: new _1.AsyncMap(), users: new _1.AsyncMap(), bundles: new _1.AsyncMap() });
20
+ const alice = (0, _1.createNode)({ keyExchange: new _1.AsyncMap(), sessions: new _1.AsyncMap(), users: new _1.AsyncMap(), bundles: new _1.AsyncMap() });
21
+ setImmediate(() => __awaiter(void 0, void 0, void 0, function* () {
22
+ const bobBootstrap = yield bob.packBootstrap(alice.userId);
23
+ yield alice.open(bobBootstrap);
24
+ const bootstraps = yield Promise.all(alice.requests.map(request => request.accept()));
25
+ const aliceHandshake = bootstraps.filter(value => (value === null || value === void 0 ? void 0 : value.receiver) === bob.userId.toString())[0];
26
+ if (!aliceHandshake)
27
+ throw new Error("Bootstrap Failed");
28
+ yield bob.open(aliceHandshake);
29
+ const first = (yield bob.packData(alice.userId, "Hi Alice!")).toBytes();
30
+ console.log("Bob: ", yield alice.open(first));
31
+ const second = yield alice.packData(bob.userId, "Hi Bob!");
32
+ console.log("Alice: ", yield bob.open(second));
33
+ const third = yield Promise.all(["How are you?", "How are this days?", "For me it's a good time"].map(msg => bob.packData(alice.userId, msg)));
34
+ third.forEach((data) => __awaiter(void 0, void 0, void 0, function* () {
35
+ console.log("Bob: ", yield alice.open(data));
36
+ }));
37
+ const fourth = yield alice.packData(bob.userId, "Not so bad my man");
38
+ console.log("Alice: ", yield bob.open(fourth));
39
+ const testone = yield Promise.all(Array(400).fill(0).map(() => alice.packData(bob.userId, (0, utils_1.decodeBase64)(crypto_1.default.randomBytes(64)))));
40
+ console.log((yield bob.open(testone[350])));
41
+ }));
@@ -16,13 +16,13 @@
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, Encodable } from "@freesignal/interfaces";
20
- export declare class UserId {
19
+ import { LocalStorage, Encodable, KeyExchangeData } from "@freesignal/interfaces";
20
+ export declare class UserId implements Encodable {
21
21
  private readonly array;
22
22
  private constructor();
23
23
  toString(): string;
24
24
  toJSON(): string;
25
- toUint8Array(): Uint8Array;
25
+ toBytes(): Uint8Array;
26
26
  static fromKey(identityKey: string | Uint8Array | IdentityKey): UserId;
27
27
  static from(userId: string | Uint8Array | UserId): UserId;
28
28
  }
@@ -51,12 +51,22 @@ export declare namespace PrivateIdentityKey {
51
51
  function from(identityKey: PrivateIdentityKey | Uint8Array | string): PrivateIdentityKey;
52
52
  function from(signatureKey: Uint8Array | string, exchangeKey: Uint8Array | string): PrivateIdentityKey;
53
53
  }
54
+ export declare enum DiscoverType {
55
+ REQUEST = 0,
56
+ RESPONSE = 1
57
+ }
58
+ export interface DiscoverMessage {
59
+ type: DiscoverType;
60
+ discoverId: string;
61
+ data?: KeyExchangeData;
62
+ }
54
63
  export declare enum Protocols {
55
64
  NULL = "",
56
65
  MESSAGE = "/freesignal/message",
57
66
  RELAY = "/freesignal/relay",
58
67
  HANDSHAKE = "/freesignal/handshake",
59
- DISCOVER = "/freesignal/discover"
68
+ DISCOVER = "/freesignal/discover",
69
+ BOOTSTRAP = "/freesignal/bootstrap"
60
70
  }
61
71
  export declare namespace Protocols {
62
72
  function isProtocol(protocol: any): boolean;
@@ -65,6 +75,19 @@ export declare namespace Protocols {
65
75
  function encode(protocol: Protocols, length?: number): Uint8Array;
66
76
  function decode(array: Uint8Array): Protocols;
67
77
  }
78
+ interface DatagramJSON {
79
+ id: string;
80
+ version: number;
81
+ sender: string;
82
+ receiver: string;
83
+ protocol: Protocols;
84
+ createdAt: number;
85
+ payload: string | undefined;
86
+ signature: string | undefined;
87
+ }
88
+ export interface SignedDatagram extends Datagram {
89
+ signature: string;
90
+ }
68
91
  export declare class Datagram implements Encodable {
69
92
  static version: number;
70
93
  private _id;
@@ -75,20 +98,20 @@ export declare class Datagram implements Encodable {
75
98
  private _createdAt;
76
99
  private _payload?;
77
100
  private _signature?;
78
- private secretKey?;
79
101
  private static headerOffset;
80
102
  constructor(sender: Uint8Array | string, receiver: Uint8Array | string, protocol: Protocols, payload?: Uint8Array | Encodable);
81
103
  get id(): string;
82
104
  get version(): number;
83
105
  get createdAt(): number;
84
- get signed(): boolean;
85
- get signature(): string | undefined;
86
106
  set payload(data: Uint8Array);
87
107
  get payload(): Uint8Array | undefined;
108
+ get signature(): string | undefined;
109
+ private get unsigned();
88
110
  toBytes(): Uint8Array;
89
- sign(secretKey: Uint8Array): this;
111
+ sign(secretKey: Uint8Array): SignedDatagram;
90
112
  toString(): string;
91
- toJSON(): string;
113
+ toJSON(): DatagramJSON;
114
+ static verify(datagram: Datagram, publicKey: Uint8Array): boolean;
92
115
  static from(data: Uint8Array | Datagram | string): Datagram;
93
116
  }
94
117
  /**
@@ -163,3 +186,4 @@ export declare class AsyncMap<K, V> implements LocalStorage<K, V> {
163
186
  clear(): Promise<void>;
164
187
  entries(): ArrayIterator<[K, V]>;
165
188
  }
189
+ export {};
@@ -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.Datagram = exports.Protocols = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
33
+ exports.AsyncMap = exports.EncryptedData = exports.Datagram = exports.Protocols = exports.DiscoverType = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
34
34
  const utils_1 = require("@freesignal/utils");
35
35
  const crypto_1 = __importDefault(require("@freesignal/crypto"));
36
36
  const double_ratchet_1 = require("./double-ratchet");
@@ -45,7 +45,7 @@ class UserId {
45
45
  toJSON() {
46
46
  return this.toString();
47
47
  }
48
- toUint8Array() {
48
+ toBytes() {
49
49
  return this.array;
50
50
  }
51
51
  static fromKey(identityKey) {
@@ -88,7 +88,7 @@ var IdentityKey;
88
88
  return UserId.fromKey(this.toBytes()).toString();
89
89
  }
90
90
  toBytes() {
91
- return (0, utils_1.concatArrays)((0, utils_1.numberToArray)(this.info), this.signatureKey, this.exchangeKey);
91
+ return (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(this.info, 1), this.signatureKey, this.exchangeKey);
92
92
  }
93
93
  toString() {
94
94
  return (0, utils_1.decodeBase64)(this.toBytes());
@@ -110,7 +110,7 @@ var IdentityKey;
110
110
  else
111
111
  return key;
112
112
  });
113
- return new IdentityKeyConstructor(keys.length === 2 ? (0, utils_1.concatArrays)((0, utils_1.numberToArray)(info + IdentityKey.version), ...keys) : keys[0]);
113
+ return new IdentityKeyConstructor(keys.length === 2 ? (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(info + IdentityKey.version, 1), ...keys) : keys[0]);
114
114
  }
115
115
  IdentityKey.from = from;
116
116
  })(IdentityKey || (exports.IdentityKey = IdentityKey = {}));
@@ -121,8 +121,8 @@ var PrivateIdentityKey;
121
121
  PrivateIdentityKey.version = 1;
122
122
  class PrivateIdentityKeyConstructor {
123
123
  constructor(privateIdentityKey) {
124
- this.info = info + PrivateIdentityKey.version;
125
124
  if (privateIdentityKey instanceof PrivateIdentityKeyConstructor) {
125
+ this.info = privateIdentityKey.info;
126
126
  this.signatureKey = privateIdentityKey.signatureKey;
127
127
  this.exchangeKey = privateIdentityKey.exchangeKey;
128
128
  this.identityKey = privateIdentityKey.identityKey;
@@ -132,6 +132,7 @@ var PrivateIdentityKey;
132
132
  privateIdentityKey = (0, utils_1.encodeBase64)(privateIdentityKey);
133
133
  if (!isIdentityKeys(privateIdentityKey))
134
134
  throw new Error("Invalid key length");
135
+ this.info = privateIdentityKey[0];
135
136
  this.signatureKey = privateIdentityKey.subarray(1, crypto_1.default.EdDSA.secretKeyLength + 1);
136
137
  this.exchangeKey = privateIdentityKey.subarray(crypto_1.default.EdDSA.secretKeyLength + 1, PrivateIdentityKey.keyLength);
137
138
  this.identityKey = IdentityKey.from(crypto_1.default.EdDSA.keyPair(this.signatureKey).publicKey, crypto_1.default.ECDH.keyPair(this.exchangeKey).publicKey);
@@ -141,7 +142,7 @@ var PrivateIdentityKey;
141
142
  return UserId.fromKey(this.identityKey.toBytes()).toString();
142
143
  }
143
144
  toBytes() {
144
- return (0, utils_1.concatArrays)((0, utils_1.numberToArray)(this.info), this.signatureKey, this.exchangeKey);
145
+ return (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(this.info, 1), this.signatureKey, this.exchangeKey);
145
146
  }
146
147
  toString() {
147
148
  return (0, utils_1.decodeBase64)(this.toBytes());
@@ -163,10 +164,15 @@ var PrivateIdentityKey;
163
164
  else
164
165
  return key;
165
166
  });
166
- return new PrivateIdentityKeyConstructor(keys.length === 2 ? (0, utils_1.concatArrays)((0, utils_1.numberToArray)(info + PrivateIdentityKey.version), ...keys) : keys[0]);
167
+ return new PrivateIdentityKeyConstructor(keys.length === 2 ? (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(info + PrivateIdentityKey.version, 1), ...keys) : keys[0]);
167
168
  }
168
169
  PrivateIdentityKey.from = from;
169
170
  })(PrivateIdentityKey || (exports.PrivateIdentityKey = PrivateIdentityKey = {}));
171
+ var DiscoverType;
172
+ (function (DiscoverType) {
173
+ DiscoverType[DiscoverType["REQUEST"] = 0] = "REQUEST";
174
+ DiscoverType[DiscoverType["RESPONSE"] = 1] = "RESPONSE";
175
+ })(DiscoverType || (exports.DiscoverType = DiscoverType = {}));
170
176
  var Protocols;
171
177
  (function (Protocols) {
172
178
  Protocols["NULL"] = "";
@@ -174,6 +180,7 @@ var Protocols;
174
180
  Protocols["RELAY"] = "/freesignal/relay";
175
181
  Protocols["HANDSHAKE"] = "/freesignal/handshake";
176
182
  Protocols["DISCOVER"] = "/freesignal/discover";
183
+ Protocols["BOOTSTRAP"] = "/freesignal/bootstrap";
177
184
  })(Protocols || (exports.Protocols = Protocols = {}));
178
185
  (function (Protocols) {
179
186
  function isProtocol(protocol) {
@@ -188,15 +195,16 @@ var Protocols;
188
195
  return Object.values(Protocols).indexOf(protocol);
189
196
  }
190
197
  Protocols.toCode = toCode;
191
- function encode(protocol, length) {
192
- return (0, utils_1.numberToArray)(Protocols.toCode(protocol), length);
198
+ function encode(protocol, length = 1) {
199
+ return (0, utils_1.numberToBytes)(Protocols.toCode(protocol), length);
193
200
  }
194
201
  Protocols.encode = encode;
195
202
  function decode(array) {
196
- return Protocols.fromCode((0, utils_1.numberFromArray)(array));
203
+ return Protocols.fromCode((0, utils_1.bytesToNumber)(array));
197
204
  }
198
205
  Protocols.decode = decode;
199
206
  })(Protocols || (exports.Protocols = Protocols = {}));
207
+ ;
200
208
  class Datagram {
201
209
  constructor(sender, receiver, protocol, payload) {
202
210
  this._id = crypto_1.default.UUID.generate().toString();
@@ -216,16 +224,6 @@ class Datagram {
216
224
  get createdAt() {
217
225
  return this._createdAt;
218
226
  }
219
- get signed() {
220
- return !!this._signature || !!this.secretKey;
221
- }
222
- get signature() {
223
- if (this.signed) {
224
- if (!this._signature)
225
- this.toBytes();
226
- return (0, utils_1.decodeBase64)(this._signature);
227
- }
228
- }
229
227
  set payload(data) {
230
228
  this._signature = undefined;
231
229
  this._payload = data;
@@ -233,37 +231,56 @@ class Datagram {
233
231
  get payload() {
234
232
  return this._payload;
235
233
  }
234
+ get signature() {
235
+ return this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined;
236
+ }
237
+ get unsigned() {
238
+ const data = this.toBytes();
239
+ data[0] &= 127;
240
+ return data.subarray(0, data.length - (this._signature ? crypto_1.default.EdDSA.signatureLength : 0));
241
+ }
236
242
  toBytes() {
237
243
  var _a, _b, _c;
238
- const data = (0, utils_1.concatArrays)(new Uint8Array(1).fill(this.version | (this.secretKey ? 128 : 0)), //1
244
+ return (0, utils_1.concatBytes)(new Uint8Array(1).fill(this.version | (this.signature ? 128 : 0)), //1
239
245
  Protocols.encode(this.protocol), //1
240
246
  (_a = crypto_1.default.UUID.parse(this.id)) !== null && _a !== void 0 ? _a : [], //16
241
- (0, utils_1.numberToArray)(this.createdAt, 8), //8
247
+ new Uint8Array((0, utils_1.numberToBytes)(this._createdAt, 8)), //8
242
248
  (0, utils_1.encodeBase64)(this.sender), //32
243
249
  (0, utils_1.encodeBase64)(this.receiver), //32
244
- (_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array());
245
- if (this.secretKey)
246
- this._signature = crypto_1.default.EdDSA.sign(data, this.secretKey);
247
- return (0, utils_1.concatArrays)(data, (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
250
+ (_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array(), (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
248
251
  }
249
252
  sign(secretKey) {
250
- this.secretKey = secretKey;
253
+ this._signature = crypto_1.default.EdDSA.sign(this.unsigned, secretKey);
251
254
  return this;
252
255
  }
253
256
  toString() {
254
257
  return (0, utils_1.decodeBase64)(this.toBytes());
255
258
  }
256
259
  toJSON() {
257
- return this.toString();
260
+ return {
261
+ id: this.id,
262
+ version: this.version,
263
+ sender: this.sender,
264
+ receiver: this.receiver,
265
+ protocol: this.protocol,
266
+ createdAt: this.createdAt,
267
+ payload: this.payload ? (0, utils_1.decodeBase64)(this.payload) : undefined,
268
+ signature: this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined
269
+ };
270
+ }
271
+ static verify(datagram, publicKey) {
272
+ if (!datagram._signature)
273
+ throw new Error("Datagram not signed");
274
+ return crypto_1.default.EdDSA.verify(datagram.unsigned, datagram._signature, publicKey);
258
275
  }
259
276
  static from(data) {
260
277
  if (typeof data === 'string')
261
278
  data = (0, utils_1.encodeBase64)(data);
262
279
  if (data instanceof Uint8Array) {
263
- 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, Datagram.headerOffset)), Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerOffset, data.length));
280
+ 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, Datagram.headerOffset)), Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerOffset, data.length - (data[0] & 128 ? crypto_1.default.EdDSA.signatureLength : 0)));
264
281
  datagram._version = data[0] & 127;
265
282
  datagram._id = crypto_1.default.UUID.stringify(data.subarray(2, 18));
266
- datagram._createdAt = (0, utils_1.numberFromArray)(data.subarray(18, 26));
283
+ datagram._createdAt = (0, utils_1.bytesToNumber)(data.subarray(18, 26));
267
284
  if (data[0] & 128)
268
285
  datagram._signature = data.subarray(data.length - crypto_1.default.EdDSA.signatureLength);
269
286
  return datagram;
@@ -272,8 +289,8 @@ class Datagram {
272
289
  const datagram = new Datagram(data.sender, data.receiver, data.protocol, data.payload);
273
290
  datagram._id = datagram.id;
274
291
  datagram._version = datagram.version;
275
- datagram._createdAt = datagram.createdAt;
276
- datagram._signature = datagram.signature ? (0, utils_1.encodeBase64)(datagram.signature) : undefined;
292
+ datagram._createdAt = datagram._createdAt;
293
+ datagram._signature = datagram._signature;
277
294
  return datagram;
278
295
  }
279
296
  else
@@ -39,7 +39,7 @@ export declare class KeyExchange {
39
39
  private generateOPK;
40
40
  generateBundle(length?: number): Promise<KeyExchangeDataBundle>;
41
41
  generateData(): Promise<KeyExchangeData>;
42
- digestData(message: KeyExchangeData): Promise<{
42
+ digestData(message: KeyExchangeData, associatedData?: Uint8Array): Promise<{
43
43
  session: KeySession;
44
44
  message: KeyExchangeSynMessage;
45
45
  identityKey: IdentityKey;
@@ -47,5 +47,6 @@ export declare class KeyExchange {
47
47
  digestMessage(message: KeyExchangeSynMessage): Promise<{
48
48
  session: KeySession;
49
49
  identityKey: IdentityKey;
50
+ associatedData: Uint8Array;
50
51
  }>;
51
52
  }
@@ -66,7 +66,7 @@ class KeyExchange {
66
66
  identityKey: this.identityKey.toString(),
67
67
  signedPreKey: (0, utils_1.decodeBase64)(signedPreKey.publicKey),
68
68
  signature: (0, utils_1.decodeBase64)(crypto_1.default.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
69
- onetimePreKey: onetimePreKey.map(opk => (0, utils_1.decodeBase64)(opk.publicKey))
69
+ onetimePreKeys: onetimePreKey.map(opk => (0, utils_1.decodeBase64)(opk.publicKey))
70
70
  };
71
71
  });
72
72
  }
@@ -83,7 +83,7 @@ class KeyExchange {
83
83
  };
84
84
  });
85
85
  }
86
- digestData(message) {
86
+ digestData(message, associatedData) {
87
87
  return __awaiter(this, void 0, void 0, function* () {
88
88
  const ephemeralKey = crypto_1.default.ECDH.keyPair();
89
89
  const signedPreKey = (0, utils_1.encodeBase64)(message.signedPreKey);
@@ -100,7 +100,7 @@ class KeyExchange {
100
100
  ...onetimePreKey ? crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
101
101
  ]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength);
102
102
  const session = new double_ratchet_1.KeySession(this.sessions, { remoteKey: identityKey.exchangeKey, rootKey });
103
- const cyphertext = yield session.encrypt((0, utils_1.concatArrays)(crypto_1.default.hash(this.identityKey.toBytes()), crypto_1.default.hash(identityKey.toBytes())));
103
+ const cyphertext = yield session.encrypt((0, utils_1.concatBytes)(crypto_1.default.hash(this.identityKey.toBytes()), crypto_1.default.hash(identityKey.toBytes()), associatedData !== null && associatedData !== void 0 ? associatedData : new Uint8Array()));
104
104
  if (!cyphertext)
105
105
  throw new Error("Decryption error");
106
106
  return {
@@ -138,11 +138,12 @@ class KeyExchange {
138
138
  const cleartext = yield session.decrypt((0, utils_1.encodeBase64)(message.associatedData));
139
139
  if (!cleartext)
140
140
  throw new Error("Error decrypting ACK message");
141
- if (!(0, utils_1.verifyArrays)(cleartext, (0, utils_1.concatArrays)(crypto_1.default.hash(identityKey.toBytes()), crypto_1.default.hash(this.identityKey.toBytes()))))
141
+ if (!(0, utils_1.compareBytes)(cleartext.subarray(0, 64), (0, utils_1.concatBytes)(crypto_1.default.hash(identityKey.toBytes()), crypto_1.default.hash(this.identityKey.toBytes()))))
142
142
  throw new Error("Error verifing Associated Data");
143
143
  return {
144
144
  session,
145
- identityKey
145
+ identityKey,
146
+ associatedData: cleartext.subarray(64)
146
147
  };
147
148
  });
148
149
  }
package/package.json CHANGED
@@ -1,22 +1,45 @@
1
1
  {
2
2
  "name": "@freesignal/protocol",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Signal Protocol implementation in javascript",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "Christian Braghette",
7
7
  "type": "commonjs",
8
- "main": "index.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./double-ratchet": {
14
+ "import": "./dist/double-ratchet.js",
15
+ "types": "./dist/double-ratchet.d.ts"
16
+ },
17
+ "./node": {
18
+ "import": "./dist/node.js",
19
+ "types": "./dist/node.d.ts"
20
+ },
21
+ "./types": {
22
+ "import": "./dist/types.js",
23
+ "types": "./dist/types.d.ts"
24
+ },
25
+ "./x3dh": {
26
+ "import": "./dist/x3dh.js",
27
+ "types": "./dist/x3dh.d.ts"
28
+ }
29
+ },
30
+ "main": "./dist/index.js",
9
31
  "scripts": {
10
- "test": "tsc && node ./build/test.js",
11
- "build": "tsc && node ./build/build.js"
32
+ "pretest": "tsc",
33
+ "test": "node ./dist/test.js",
34
+ "prepare": "tsc"
12
35
  },
13
36
  "dependencies": {
14
37
  "@freesignal/crypto": "^0.3.0",
15
38
  "@freesignal/interfaces": "^0.2.0",
16
- "@freesignal/utils": "^1.3.0",
39
+ "@freesignal/utils": "^1.4.1",
17
40
  "semaphore.ts": "^0.2.0"
18
41
  },
19
42
  "devDependencies": {
20
43
  "@types/node": "^24.2.1"
21
44
  }
22
- }
45
+ }
package/node.d.ts DELETED
@@ -1,38 +0,0 @@
1
- import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
2
- import { Datagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
3
- import { KeyExchange } from "./x3dh";
4
- import { ExportedKeySession, KeySession } from "./double-ratchet";
5
- export declare class FreeSignalNode {
6
- protected readonly privateIdentityKey: PrivateIdentityKey;
7
- protected readonly sessions: SessionMap;
8
- protected readonly users: LocalStorage<string, IdentityKey>;
9
- protected readonly keyExchange: KeyExchange;
10
- constructor(storage: Database<{
11
- sessions: LocalStorage<string, ExportedKeySession>;
12
- keyExchange: LocalStorage<string, Crypto.KeyPair>;
13
- users: LocalStorage<string, IdentityKey>;
14
- }>, privateIdentityKey?: PrivateIdentityKey);
15
- get userId(): UserId;
16
- get identityKey(): IdentityKey;
17
- generateKeyData(): Promise<KeyExchangeData>;
18
- generateKeyBundle(length?: number): Promise<KeyExchangeDataBundle>;
19
- encrypt(receiverId: string, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
20
- sendHandshake(data: KeyExchangeData): Promise<Datagram>;
21
- sendData<T>(receiverId: string, data: T): Promise<Datagram>;
22
- sendRelay(receiverId: string, data: Datagram): Promise<Datagram>;
23
- sendDiscover(receiverId: string, discoverId: string): Promise<Datagram>;
24
- decrypt(datagram: Datagram): Promise<Uint8Array>;
25
- receive<T extends Uint8Array | UserId | Datagram | UserId | void>(datagram: Datagram | Uint8Array): Promise<T>;
26
- }
27
- declare class SessionMap implements LocalStorage<string, KeySession> {
28
- readonly storage: LocalStorage<string, ExportedKeySession>;
29
- readonly maxSize: number;
30
- private readonly cache;
31
- constructor(storage: LocalStorage<string, ExportedKeySession>, maxSize?: number);
32
- set(key: string, value: KeySession): Promise<void>;
33
- get(key: string): Promise<KeySession | undefined>;
34
- has(key: string): Promise<boolean>;
35
- delete(key: string): Promise<boolean>;
36
- clear(): Promise<void>;
37
- }
38
- export {};
package/node.js DELETED
@@ -1,139 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.FreeSignalNode = void 0;
13
- const types_1 = require("./types");
14
- const x3dh_1 = require("./x3dh");
15
- const double_ratchet_1 = require("./double-ratchet");
16
- const _1 = require(".");
17
- const utils_1 = require("@freesignal/utils");
18
- class FreeSignalNode {
19
- constructor(storage, privateIdentityKey) {
20
- this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
21
- this.sessions = new SessionMap(storage.sessions);
22
- this.keyExchange = new x3dh_1.KeyExchange({ keys: storage.keyExchange, sessions: storage.sessions }, this.privateIdentityKey);
23
- this.users = storage.users;
24
- }
25
- get userId() {
26
- return types_1.UserId.fromKey(this.privateIdentityKey.identityKey);
27
- }
28
- get identityKey() {
29
- return this.privateIdentityKey.identityKey;
30
- }
31
- generateKeyData() {
32
- return this.keyExchange.generateData();
33
- }
34
- ;
35
- generateKeyBundle(length) {
36
- return this.keyExchange.generateBundle(length);
37
- }
38
- ;
39
- encrypt(receiverId, protocol, data) {
40
- return __awaiter(this, void 0, void 0, function* () {
41
- const session = yield this.sessions.get(receiverId);
42
- if (!session)
43
- throw new Error("Session not found for user: " + receiverId);
44
- return new types_1.Datagram(this.userId.toString(), receiverId, protocol, yield session.encrypt(data));
45
- });
46
- }
47
- sendHandshake(data) {
48
- return __awaiter(this, void 0, void 0, function* () {
49
- const { session, message, identityKey } = yield this.keyExchange.digestData(data);
50
- const remoteId = types_1.UserId.fromKey(identityKey);
51
- this.sessions.set(remoteId.toString(), session);
52
- return new types_1.Datagram(this.userId.toString(), types_1.UserId.fromKey(data.identityKey).toString(), types_1.Protocols.HANDSHAKE, (0, utils_1.encodeData)(message));
53
- });
54
- }
55
- sendData(receiverId, data) {
56
- return this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
57
- }
58
- sendRelay(receiverId, data) {
59
- return this.encrypt(receiverId, types_1.Protocols.RELAY, (0, utils_1.encodeData)(data));
60
- }
61
- sendDiscover(receiverId, discoverId) {
62
- return this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(discoverId));
63
- }
64
- decrypt(datagram) {
65
- return __awaiter(this, void 0, void 0, function* () {
66
- const userId = datagram.sender;
67
- const session = yield this.sessions.get(userId);
68
- if (!session)
69
- throw new Error("Session not found for user: " + userId);
70
- if (!datagram.payload)
71
- throw new Error("Missing payload");
72
- const decrypted = yield session.decrypt(datagram.payload);
73
- if (!decrypted)
74
- throw new Error("Decryption failed");
75
- return decrypted;
76
- });
77
- }
78
- receive(datagram) {
79
- return __awaiter(this, void 0, void 0, function* () {
80
- if (datagram instanceof Uint8Array)
81
- datagram = types_1.Datagram.from(datagram);
82
- switch (datagram.protocol) {
83
- case types_1.Protocols.HANDSHAKE:
84
- if (!datagram.payload)
85
- throw new Error("Missing payload");
86
- const data = (0, utils_1.decodeData)(datagram.payload);
87
- const { session, identityKey } = yield this.keyExchange.digestMessage(data);
88
- this.sessions.set(types_1.UserId.fromKey(identityKey).toString(), session);
89
- return;
90
- case types_1.Protocols.MESSAGE:
91
- return yield this.decrypt(datagram);
92
- case types_1.Protocols.RELAY:
93
- return (0, utils_1.decodeData)(yield this.decrypt(datagram));
94
- case types_1.Protocols.DISCOVER:
95
- return types_1.UserId.from((0, utils_1.decodeData)(yield this.decrypt(datagram)));
96
- default:
97
- throw new Error("Invalid protocol");
98
- }
99
- });
100
- }
101
- }
102
- exports.FreeSignalNode = FreeSignalNode;
103
- class SessionMap {
104
- constructor(storage, maxSize = 50) {
105
- this.storage = storage;
106
- this.maxSize = maxSize;
107
- this.cache = new Map();
108
- }
109
- set(key, value) {
110
- this.cache.set(key, value);
111
- return this.storage.set(key, value.toJSON());
112
- }
113
- get(key) {
114
- return __awaiter(this, void 0, void 0, function* () {
115
- const session = this.cache.get(key);
116
- if (!session) {
117
- const sessionData = yield this.storage.get(key);
118
- if (!sessionData)
119
- return undefined;
120
- return double_ratchet_1.KeySession.from(sessionData, this.storage);
121
- }
122
- return session;
123
- });
124
- }
125
- has(key) {
126
- return __awaiter(this, void 0, void 0, function* () {
127
- return this.cache.has(key) || (yield this.storage.has(key));
128
- });
129
- }
130
- delete(key) {
131
- return __awaiter(this, void 0, void 0, function* () {
132
- return this.cache.delete(key) || (yield this.storage.delete(key));
133
- });
134
- }
135
- clear() {
136
- this.cache.clear();
137
- return this.storage.clear();
138
- }
139
- }
package/test.js DELETED
@@ -1,28 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- const utils_1 = require("@freesignal/utils");
13
- const _1 = require(".");
14
- const bob = (0, _1.createNode)({ keyExchange: new _1.AsyncMap(), sessions: new _1.AsyncMap(), users: new _1.AsyncMap() });
15
- const alice = (0, _1.createNode)({ keyExchange: new _1.AsyncMap(), sessions: new _1.AsyncMap(), users: new _1.AsyncMap() });
16
- setImmediate(() => __awaiter(void 0, void 0, void 0, function* () {
17
- const aliceHandshake = yield alice.sendHandshake(yield bob.generateKeyData());
18
- yield bob.receive(aliceHandshake);
19
- console.log("Session established successfully between Alice and Bob.");
20
- const first = (yield bob.sendData(alice.userId.toString(), (0, utils_1.encodeData)("Hi Alice!"))).toBytes();
21
- console.log("Bob: ", (0, utils_1.decodeData)(yield alice.receive(first)));
22
- const second = yield alice.sendData(bob.userId.toString(), (0, utils_1.encodeData)("Hi Bob!"));
23
- console.log("Alice: ", (0, utils_1.decodeData)(yield bob.receive(second)));
24
- const third = yield Promise.all(["How are you?", "How are this days?", "For me it's a good time"].map(msg => bob.sendData(alice.userId.toString(), (0, utils_1.encodeData)(msg))));
25
- third.forEach((value) => __awaiter(void 0, void 0, void 0, function* () {
26
- console.log("Bob: ", (0, utils_1.decodeData)(yield alice.receive(value)));
27
- }));
28
- }));
File without changes