@freesignal/protocol 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,8 +16,6 @@
16
16
  * You should have received a copy of the GNU General Public License
17
17
  * along with this program. If not, see <https://www.gnu.org/licenses/>
18
18
  */
19
- import { LocalStorage } from "@freesignal/interfaces";
20
- import { EncryptedData } from "./types";
21
19
  export interface ExportedKeySession {
22
20
  secretKey: string;
23
21
  rootKey?: string;
@@ -25,6 +23,14 @@ export interface ExportedKeySession {
25
23
  receivingChain?: ExportedKeyChain;
26
24
  previousKeys: [string, Uint8Array][];
27
25
  }
26
+ export interface EncryptionKeys {
27
+ readonly count: number;
28
+ readonly previous: number;
29
+ readonly publicKey: Uint8Array;
30
+ }
31
+ export interface PrivateEncryptionKeys extends EncryptionKeys {
32
+ readonly secretKey: Uint8Array;
33
+ }
28
34
  /**
29
35
  * Represents a secure Double Ratchet session.
30
36
  * Used for forward-secure encryption and decryption of messages.
@@ -33,36 +39,22 @@ export declare class KeySession {
33
39
  static readonly keyLength = 32;
34
40
  static readonly version = 1;
35
41
  static readonly info: string;
42
+ static readonly maxCount = 65536;
36
43
  readonly id: string;
37
- private readonly mutex;
38
- private readonly storage;
39
44
  private keyPair;
40
45
  private rootKey?;
41
46
  private sendingChain?;
42
47
  private receivingChain?;
43
48
  private previousKeys;
44
- constructor(storage: LocalStorage<string, ExportedKeySession>, opts?: {
49
+ constructor(opts?: {
45
50
  id?: string;
46
51
  secretKey?: Uint8Array;
47
52
  remoteKey?: Uint8Array;
48
53
  rootKey?: Uint8Array;
49
54
  });
50
55
  private getChain;
51
- private save;
52
- /**
53
- * Encrypts a message payload using the current sending chain.
54
- *
55
- * @param message - The message as a Uint8Array.
56
- * @returns An EncryptedPayload or undefined if encryption fails.
57
- */
58
- encrypt(message: Uint8Array): Promise<EncryptedData>;
59
- /**
60
- * Decrypts an encrypted message.
61
- *
62
- * @param payload - The received encrypted message.
63
- * @returns The decrypted message as a Uint8Array, or undefined if decryption fails.
64
- */
65
- decrypt(payload: Uint8Array | EncryptedData): Promise<Uint8Array>;
56
+ getSendingKey(): PrivateEncryptionKeys | undefined;
57
+ getReceivingKey(encryptionKeys: EncryptionKeys): Uint8Array | undefined;
66
58
  /**
67
59
  * Whether both the sending and receiving chains are initialized.
68
60
  */
@@ -81,7 +73,7 @@ export declare class KeySession {
81
73
  * @param json string returned by `export()` method.
82
74
  * @returns session with the state parsed.
83
75
  */
84
- static from(data: ExportedKeySession, storage: LocalStorage<string, ExportedKeySession>): KeySession;
76
+ static from(data: ExportedKeySession): KeySession;
85
77
  }
86
78
  interface ExportedKeyChain {
87
79
  publicKey: string;
@@ -90,32 +82,4 @@ interface ExportedKeyChain {
90
82
  count: number;
91
83
  previousCount: number;
92
84
  }
93
- export declare class EncryptedDataConstructor implements EncryptedData {
94
- static readonly secretKeyLength: number;
95
- static readonly publicKeyLength: number;
96
- static readonly keyLength: number;
97
- static readonly nonceLength: number;
98
- static readonly maxCount = 65536;
99
- static readonly countLength = 2;
100
- private raw;
101
- constructor(count: number | Uint8Array, previous: number | Uint8Array, publicKey: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, version?: number | Uint8Array);
102
- constructor(encrypted: Uint8Array | EncryptedData);
103
- get length(): number;
104
- get version(): number;
105
- get count(): number;
106
- get previous(): number;
107
- get publicKey(): Uint8Array;
108
- get nonce(): Uint8Array;
109
- get ciphertext(): Uint8Array;
110
- toBytes(): Uint8Array;
111
- toString(): string;
112
- toJSON(): {
113
- version: number;
114
- count: number;
115
- previous: number;
116
- publicKey: string;
117
- nonce: string;
118
- ciphertext: string;
119
- };
120
- }
121
85
  export {};
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ /**
3
+ * FreeSignal Protocol
4
+ *
5
+ * Copyright (C) 2025 Christian Braghette
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>
19
+ */
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.KeySession = void 0;
25
+ const crypto_1 = __importDefault(require("@freesignal/crypto"));
26
+ const utils_1 = require("@freesignal/utils");
27
+ /**
28
+ * Represents a secure Double Ratchet session.
29
+ * Used for forward-secure encryption and decryption of messages.
30
+ */
31
+ class KeySession {
32
+ constructor(opts = {}) {
33
+ var _a;
34
+ this.previousKeys = new KeyMap();
35
+ this.id = (_a = opts.id) !== null && _a !== void 0 ? _a : crypto_1.default.UUID.generate().toString();
36
+ this.keyPair = crypto_1.default.ECDH.keyPair(opts.secretKey);
37
+ if (opts.rootKey)
38
+ this.rootKey = opts.rootKey;
39
+ if (opts.remoteKey) {
40
+ this.sendingChain = this.getChain(opts.remoteKey);
41
+ }
42
+ }
43
+ getChain(remoteKey, previousCount) {
44
+ const sharedKey = crypto_1.default.ECDH.scalarMult(this.keyPair.secretKey, remoteKey);
45
+ if (!this.rootKey)
46
+ this.rootKey = crypto_1.default.hash(sharedKey);
47
+ const hashkey = crypto_1.default.hkdf(sharedKey, this.rootKey, KeySession.info, KeySession.keyLength * 2);
48
+ this.rootKey = hashkey.subarray(0, KeySession.keyLength);
49
+ return new KeyChain(this.publicKey, remoteKey, hashkey.subarray(KeySession.keyLength), previousCount);
50
+ }
51
+ getSendingKey() {
52
+ if (!this.sendingChain)
53
+ return;
54
+ const secretKey = this.sendingChain.getKey();
55
+ return {
56
+ //version: KeySession.version,
57
+ count: this.sendingChain.count,
58
+ previous: this.sendingChain.previousCount,
59
+ publicKey: this.sendingChain.publicKey,
60
+ secretKey
61
+ };
62
+ }
63
+ getReceivingKey(encryptionKeys) {
64
+ var _a, _b, _c;
65
+ if (!this.previousKeys.has((0, utils_1.decodeBase64)(encryptionKeys.publicKey) + encryptionKeys.count.toString())) {
66
+ if (!(0, utils_1.compareBytes)(encryptionKeys.publicKey, (_b = (_a = this.receivingChain) === null || _a === void 0 ? void 0 : _a.remoteKey) !== null && _b !== void 0 ? _b : new Uint8Array())) {
67
+ while (this.receivingChain && this.receivingChain.count < encryptionKeys.previous) {
68
+ const key = this.receivingChain.getKey();
69
+ this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
70
+ }
71
+ this.receivingChain = this.getChain(encryptionKeys.publicKey);
72
+ this.keyPair = crypto_1.default.ECDH.keyPair();
73
+ this.sendingChain = this.getChain(encryptionKeys.publicKey, (_c = this.sendingChain) === null || _c === void 0 ? void 0 : _c.count);
74
+ }
75
+ if (!this.receivingChain)
76
+ throw new Error("Error initializing receivingChain");
77
+ while (this.receivingChain.count < encryptionKeys.count) {
78
+ const key = this.receivingChain.getKey();
79
+ this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
80
+ }
81
+ }
82
+ return this.previousKeys.get((0, utils_1.decodeBase64)(encryptionKeys.publicKey) + encryptionKeys.count.toString());
83
+ }
84
+ /**
85
+ * Whether both the sending and receiving chains are initialized.
86
+ */
87
+ get handshaked() { return this.sendingChain && this.receivingChain ? true : false; }
88
+ /**
89
+ * The public key of this session.
90
+ */
91
+ get publicKey() { return this.keyPair.publicKey; }
92
+ /**
93
+ * Export the state of the session;
94
+ */
95
+ toJSON() {
96
+ var _a, _b;
97
+ return {
98
+ secretKey: (0, utils_1.decodeBase64)(this.keyPair.secretKey),
99
+ rootKey: this.rootKey ? (0, utils_1.decodeBase64)(this.rootKey) : undefined,
100
+ sendingChain: (_a = this.sendingChain) === null || _a === void 0 ? void 0 : _a.toJSON(),
101
+ receivingChain: (_b = this.receivingChain) === null || _b === void 0 ? void 0 : _b.toJSON(),
102
+ previousKeys: Array.from(this.previousKeys.entries())
103
+ };
104
+ }
105
+ /**
106
+ * Import a state.
107
+ *
108
+ * @param json string returned by `export()` method.
109
+ * @returns session with the state parsed.
110
+ */
111
+ static from(data) {
112
+ const session = new KeySession({ secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: data.rootKey ? (0, utils_1.encodeBase64)(data.rootKey) : undefined });
113
+ session.sendingChain = data.sendingChain ? KeyChain.from(data.sendingChain) : undefined;
114
+ session.receivingChain = data.receivingChain ? KeyChain.from(data.receivingChain) : undefined;
115
+ session.previousKeys = new KeyMap(data.previousKeys);
116
+ return session;
117
+ }
118
+ }
119
+ exports.KeySession = KeySession;
120
+ KeySession.keyLength = 32;
121
+ KeySession.version = 1;
122
+ KeySession.info = "/freesignal/double-ratchet/v0." + KeySession.version;
123
+ KeySession.maxCount = 65536;
124
+ class KeyChain {
125
+ constructor(publicKey, remoteKey, chainKey, previousCount = 0) {
126
+ this.publicKey = publicKey;
127
+ this.remoteKey = remoteKey;
128
+ this.chainKey = chainKey;
129
+ this.previousCount = previousCount;
130
+ this._count = 0;
131
+ }
132
+ getKey() {
133
+ if (++this._count >= KeySession.maxCount)
134
+ throw new Error("SendingChain count too big");
135
+ const hash = crypto_1.default.hkdf(this.chainKey, new Uint8Array(KeySession.keyLength).fill(0), KeySession.info, KeySession.keyLength * 2);
136
+ this.chainKey = hash.subarray(0, KeySession.keyLength);
137
+ return hash.subarray(KeySession.keyLength);
138
+ }
139
+ toString() {
140
+ return "[object KeyChain]";
141
+ }
142
+ get count() {
143
+ return this._count;
144
+ }
145
+ toJSON() {
146
+ return {
147
+ publicKey: (0, utils_1.decodeBase64)(this.publicKey),
148
+ remoteKey: (0, utils_1.decodeBase64)(this.remoteKey),
149
+ chainKey: (0, utils_1.decodeBase64)(this.chainKey),
150
+ count: this.count,
151
+ previousCount: this.previousCount
152
+ };
153
+ }
154
+ static from(obj) {
155
+ const chain = new KeyChain((0, utils_1.encodeBase64)(obj.publicKey), (0, utils_1.encodeBase64)(obj.remoteKey), (0, utils_1.encodeBase64)(obj.chainKey), obj.previousCount);
156
+ chain._count = obj.count;
157
+ return chain;
158
+ }
159
+ }
160
+ class KeyMap extends Map {
161
+ get(key) {
162
+ const out = super.get(key);
163
+ if (out && !super.delete(key))
164
+ throw new Error();
165
+ return out;
166
+ }
167
+ }
@@ -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,71 @@
1
+ /**
2
+ * FreeSignal Protocol
3
+ *
4
+ * Copyright (C) 2025 Christian Braghette
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program. If not, see <https://www.gnu.org/licenses/>
18
+ */
19
+ import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
20
+ import { Datagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
21
+ import { KeyExchange } from "./x3dh";
22
+ import { ExportedKeySession, KeySession } from "./double-ratchet";
23
+ declare class BootstrapRequest {
24
+ #private;
25
+ readonly senderId: UserId | string;
26
+ readonly data: KeyExchangeData;
27
+ private readonly acceptFn;
28
+ constructor(senderId: UserId | string, data: KeyExchangeData, acceptFn: (data: KeyExchangeData) => Promise<Datagram>);
29
+ get status(): "pending" | "accepted" | "denied";
30
+ accept(): Promise<Datagram | undefined>;
31
+ deny(): void;
32
+ }
33
+ type OpenFnReturns = Uint8Array | UserId | Datagram | UserId | KeyExchangeData | undefined | void;
34
+ export declare class FreeSignalNode {
35
+ protected readonly privateIdentityKey: PrivateIdentityKey;
36
+ protected readonly sessions: SessionMap;
37
+ protected readonly users: LocalStorage<string, IdentityKey>;
38
+ protected readonly bundles: LocalStorage<string, KeyExchangeDataBundle>;
39
+ protected readonly keyExchange: KeyExchange;
40
+ protected readonly discovers: Set<string>;
41
+ protected readonly bootstraps: Set<BootstrapRequest>;
42
+ constructor(storage: Database<{
43
+ sessions: LocalStorage<string, ExportedKeySession>;
44
+ keyExchange: LocalStorage<string, Crypto.KeyPair>;
45
+ users: LocalStorage<string, IdentityKey>;
46
+ bundles: LocalStorage<string, KeyExchangeDataBundle>;
47
+ }>, privateIdentityKey?: PrivateIdentityKey);
48
+ get identityKey(): IdentityKey;
49
+ get userId(): UserId;
50
+ get requests(): BootstrapRequest[];
51
+ protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
52
+ packHandshake(data: KeyExchangeData): Promise<Datagram>;
53
+ packData<T>(receiverId: string | UserId, data: T): Promise<Datagram>;
54
+ packRelay(receiverId: string | UserId, data: Datagram): Promise<Datagram>;
55
+ packDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<Datagram>;
56
+ packBootstrap(receiverId: string | UserId): Promise<Datagram>;
57
+ protected decrypt(datagram: Datagram): Promise<Uint8Array>;
58
+ open<T extends OpenFnReturns>(datagram: Datagram | Uint8Array): Promise<T>;
59
+ }
60
+ declare class SessionMap implements LocalStorage<string, KeySession> {
61
+ readonly storage: LocalStorage<string, ExportedKeySession>;
62
+ readonly maxSize: number;
63
+ private readonly cache;
64
+ constructor(storage: LocalStorage<string, ExportedKeySession>, maxSize?: number);
65
+ set(key: string, value: KeySession): Promise<void>;
66
+ get(key: string): Promise<KeySession | undefined>;
67
+ has(key: string): Promise<boolean>;
68
+ delete(key: string): Promise<boolean>;
69
+ clear(): Promise<void>;
70
+ }
71
+ export {};
package/dist/node.js ADDED
@@ -0,0 +1,261 @@
1
+ "use strict";
2
+ /**
3
+ * FreeSignal Protocol
4
+ *
5
+ * Copyright (C) 2025 Christian Braghette
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>
19
+ */
20
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
21
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
22
+ return new (P || (P = Promise))(function (resolve, reject) {
23
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
24
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
25
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
26
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
27
+ });
28
+ };
29
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
30
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
31
+ 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");
32
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
33
+ };
34
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
35
+ if (kind === "m") throw new TypeError("Private method is not writable");
36
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
37
+ 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");
38
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
39
+ };
40
+ var _BootstrapRequest_status;
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.FreeSignalNode = void 0;
43
+ const types_1 = require("./types");
44
+ const x3dh_1 = require("./x3dh");
45
+ const double_ratchet_1 = require("./double-ratchet");
46
+ const _1 = require(".");
47
+ const utils_1 = require("@freesignal/utils");
48
+ class BootstrapRequest {
49
+ constructor(senderId, data, acceptFn) {
50
+ this.senderId = senderId;
51
+ this.data = data;
52
+ this.acceptFn = acceptFn;
53
+ _BootstrapRequest_status.set(this, 'pending');
54
+ }
55
+ get status() {
56
+ return __classPrivateFieldGet(this, _BootstrapRequest_status, "f");
57
+ }
58
+ accept() {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ if (this.status === 'pending')
61
+ __classPrivateFieldSet(this, _BootstrapRequest_status, 'accepted', "f");
62
+ if (__classPrivateFieldGet(this, _BootstrapRequest_status, "f") === 'accepted')
63
+ return yield this.acceptFn(this.data);
64
+ });
65
+ }
66
+ deny() {
67
+ if (this.status === 'pending')
68
+ __classPrivateFieldSet(this, _BootstrapRequest_status, 'denied', "f");
69
+ return;
70
+ }
71
+ }
72
+ _BootstrapRequest_status = new WeakMap();
73
+ class FreeSignalNode {
74
+ constructor(storage, privateIdentityKey) {
75
+ this.discovers = new Set();
76
+ this.bootstraps = new Set();
77
+ this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
78
+ this.sessions = new SessionMap(storage.sessions);
79
+ this.keyExchange = new x3dh_1.KeyExchange(storage.keyExchange, this.privateIdentityKey);
80
+ this.users = storage.users;
81
+ this.bundles = storage.bundles;
82
+ }
83
+ get identityKey() {
84
+ return this.privateIdentityKey.identityKey;
85
+ }
86
+ get userId() {
87
+ return types_1.UserId.fromKey(this.identityKey);
88
+ }
89
+ get requests() {
90
+ return Array.from(this.bootstraps.values());
91
+ }
92
+ encrypt(receiverId, protocol, data) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ if (receiverId instanceof types_1.UserId)
95
+ receiverId = receiverId.toString();
96
+ const session = yield this.sessions.get(receiverId);
97
+ if (!session)
98
+ throw new Error("Session not found for user: " + receiverId);
99
+ const encrypted = (0, types_1.encryptData)(session, data);
100
+ this.sessions.set(receiverId, session);
101
+ return new types_1.Datagram(this.userId.toString(), receiverId, protocol, encrypted).sign(this.privateIdentityKey.signatureKey);
102
+ });
103
+ }
104
+ packHandshake(data) {
105
+ return __awaiter(this, void 0, void 0, function* () {
106
+ const { session, message, identityKey } = yield this.keyExchange.digestData(data, (0, utils_1.encodeData)(yield this.keyExchange.generateBundle()));
107
+ const remoteId = types_1.UserId.fromKey(identityKey);
108
+ yield this.users.set(remoteId.toString(), identityKey);
109
+ yield this.sessions.set(remoteId.toString(), session);
110
+ 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);
111
+ });
112
+ }
113
+ packData(receiverId, data) {
114
+ return this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
115
+ }
116
+ packRelay(receiverId, data) {
117
+ return this.encrypt(receiverId, types_1.Protocols.RELAY, (0, utils_1.encodeData)(data));
118
+ }
119
+ packDiscover(receiverId, discoverId) {
120
+ return __awaiter(this, void 0, void 0, function* () {
121
+ if (receiverId instanceof types_1.UserId)
122
+ receiverId = receiverId.toString();
123
+ if (discoverId instanceof types_1.UserId)
124
+ discoverId = discoverId.toString();
125
+ const message = {
126
+ type: types_1.DiscoverType.REQUEST,
127
+ discoverId
128
+ };
129
+ this.discovers.add(receiverId);
130
+ return this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(message));
131
+ });
132
+ }
133
+ packBootstrap(receiverId) {
134
+ return __awaiter(this, void 0, void 0, function* () {
135
+ return new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.BOOTSTRAP, (0, utils_1.encodeData)(yield this.keyExchange.generateData()));
136
+ });
137
+ }
138
+ decrypt(datagram) {
139
+ return __awaiter(this, void 0, void 0, function* () {
140
+ const signatureKey = yield this.users.get(datagram.sender);
141
+ if (!signatureKey)
142
+ throw new Error("User IdentityKey not found");
143
+ if (!types_1.Datagram.verify(datagram, signatureKey.signatureKey))
144
+ throw new Error("Signature not verified");
145
+ const session = yield this.sessions.get(datagram.sender);
146
+ if (!session)
147
+ throw new Error("Session not found for user: " + datagram.sender);
148
+ if (!datagram.payload)
149
+ throw new Error("Missing payload");
150
+ const decrypted = (0, types_1.decryptData)(session, datagram.payload);
151
+ this.sessions.set(datagram.sender, session);
152
+ return decrypted;
153
+ });
154
+ }
155
+ open(datagram) {
156
+ return __awaiter(this, void 0, void 0, function* () {
157
+ if (datagram instanceof Uint8Array)
158
+ datagram = types_1.Datagram.from(datagram);
159
+ switch (datagram.protocol) {
160
+ case types_1.Protocols.HANDSHAKE:
161
+ if (!datagram.payload)
162
+ throw new Error("Missing payload");
163
+ const data = (0, utils_1.decodeData)(datagram.payload);
164
+ if (!types_1.Datagram.verify(datagram, types_1.IdentityKey.from(data.identityKey).signatureKey))
165
+ throw new Error("Signature not verified");
166
+ const { session, identityKey, associatedData } = yield this.keyExchange.digestMessage(data);
167
+ const userId = types_1.UserId.fromKey(identityKey);
168
+ yield this.users.set(userId.toString(), identityKey);
169
+ yield this.sessions.set(userId.toString(), session);
170
+ yield this.bundles.set(userId.toString(), (0, utils_1.decodeData)(associatedData));
171
+ return;
172
+ case types_1.Protocols.MESSAGE:
173
+ return (0, utils_1.decodeData)(yield this.decrypt(datagram));
174
+ case types_1.Protocols.RELAY:
175
+ return (0, utils_1.decodeData)(yield this.decrypt(datagram));
176
+ case types_1.Protocols.DISCOVER:
177
+ const message = (0, utils_1.decodeData)(yield this.decrypt(datagram));
178
+ if (message.type === types_1.DiscoverType.REQUEST && message.discoverId && !(yield this.users.has(message.discoverId))) {
179
+ let data;
180
+ if (message.discoverId === this.userId.toString()) {
181
+ data = yield this.keyExchange.generateData();
182
+ }
183
+ else {
184
+ const bundle = yield this.bundles.get(message.discoverId);
185
+ if (!bundle)
186
+ return;
187
+ const { version, identityKey, signedPreKey, signature } = bundle;
188
+ const onetimePreKey = bundle.onetimePreKeys.shift();
189
+ if (!onetimePreKey) {
190
+ yield this.bundles.delete(message.discoverId);
191
+ return;
192
+ }
193
+ data = {
194
+ version,
195
+ identityKey,
196
+ signedPreKey,
197
+ signature,
198
+ onetimePreKey
199
+ };
200
+ }
201
+ const response = { type: types_1.DiscoverType.RESPONSE, discoverId: message.discoverId, data };
202
+ return yield this.encrypt(datagram.sender, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(response));
203
+ }
204
+ else if (message.type === types_1.DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
205
+ this.discovers.delete(message.discoverId);
206
+ return message.data;
207
+ }
208
+ return;
209
+ case types_1.Protocols.BOOTSTRAP:
210
+ if (datagram.payload) {
211
+ const data = (0, utils_1.decodeData)(datagram.payload);
212
+ if (!(0, utils_1.compareBytes)(types_1.UserId.fromKey(data.identityKey).toBytes(), (0, utils_1.encodeBase64)(datagram.sender)))
213
+ return;
214
+ this.bootstraps.add(new BootstrapRequest(datagram.sender, data, (data) => this.packHandshake(data)));
215
+ }
216
+ ;
217
+ return;
218
+ default:
219
+ throw new Error("Invalid protocol");
220
+ }
221
+ });
222
+ }
223
+ }
224
+ exports.FreeSignalNode = FreeSignalNode;
225
+ class SessionMap {
226
+ constructor(storage, maxSize = 50) {
227
+ this.storage = storage;
228
+ this.maxSize = maxSize;
229
+ this.cache = new Map();
230
+ }
231
+ set(key, value) {
232
+ this.cache.set(key, value);
233
+ return this.storage.set(key, value.toJSON());
234
+ }
235
+ get(key) {
236
+ return __awaiter(this, void 0, void 0, function* () {
237
+ const session = this.cache.get(key);
238
+ if (!session) {
239
+ const sessionData = yield this.storage.get(key);
240
+ if (!sessionData)
241
+ return undefined;
242
+ return double_ratchet_1.KeySession.from(sessionData);
243
+ }
244
+ return session;
245
+ });
246
+ }
247
+ has(key) {
248
+ return __awaiter(this, void 0, void 0, function* () {
249
+ return this.cache.has(key) || (yield this.storage.has(key));
250
+ });
251
+ }
252
+ delete(key) {
253
+ return __awaiter(this, void 0, void 0, function* () {
254
+ return this.cache.delete(key) || (yield this.storage.delete(key));
255
+ });
256
+ }
257
+ clear() {
258
+ this.cache.clear();
259
+ return this.storage.clear();
260
+ }
261
+ }
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
+ }));