@freesignal/protocol 0.2.5 → 0.2.6
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.
- package/api.d.ts +18 -5
- package/api.js +32 -58
- package/double-ratchet.js +4 -2
- package/index.js +0 -9
- package/package.json +2 -2
- package/types.d.ts +40 -0
- package/types.js +170 -16
- package/x3dh.js +2 -2
package/api.d.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
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
|
+
*/
|
|
1
19
|
import { Crypto, KeyExchangeData, KeyExchangeDataBundle, KeyExchangeSynMessage, LocalStorage } from "@freesignal/interfaces";
|
|
2
20
|
import { KeySession } from "./double-ratchet";
|
|
3
21
|
import { KeyExchange } from "./x3dh";
|
|
4
22
|
import { Datagram, IdentityKeys, EncryptedData, UserId } from "./types";
|
|
5
|
-
export declare const FREESIGNAL_MIME = "application/x-freesignal";
|
|
6
23
|
type DatagramId = string;
|
|
7
24
|
export declare class FreeSignalAPI {
|
|
8
25
|
protected readonly signKey: Crypto.KeyPair;
|
|
@@ -33,9 +50,5 @@ export declare class FreeSignalAPI {
|
|
|
33
50
|
identityKeys: IdentityKeys;
|
|
34
51
|
userId: UserId;
|
|
35
52
|
}>;
|
|
36
|
-
protected packIdList(datagramIds: DatagramId[]): Uint8Array;
|
|
37
|
-
protected unpackIdList(data: Uint8Array): DatagramId[];
|
|
38
|
-
protected packDatagrams(messages: Datagram[]): Uint8Array;
|
|
39
|
-
protected unpackDatagrams(data: Uint8Array): Datagram[];
|
|
40
53
|
}
|
|
41
54
|
export {};
|
package/api.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
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
|
+
*/
|
|
2
20
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
21
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
22
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -12,13 +30,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
30
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
31
|
};
|
|
14
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.FreeSignalAPI =
|
|
33
|
+
exports.FreeSignalAPI = void 0;
|
|
16
34
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
17
35
|
const x3dh_1 = require("./x3dh");
|
|
18
36
|
const utils_1 = require("@freesignal/utils");
|
|
19
37
|
const types_1 = require("./types");
|
|
20
|
-
const fflate_1 = __importDefault(require("fflate"));
|
|
21
|
-
exports.FREESIGNAL_MIME = "application/x-freesignal";
|
|
22
38
|
class FreeSignalAPI {
|
|
23
39
|
constructor(opts) {
|
|
24
40
|
const { secretSignKey, secretBoxKey, sessions, keyExchange, users } = opts;
|
|
@@ -70,9 +86,9 @@ class FreeSignalAPI {
|
|
|
70
86
|
const res = yield fetch(url, {
|
|
71
87
|
method: 'POST',
|
|
72
88
|
headers: {
|
|
73
|
-
'Content-Type':
|
|
89
|
+
'Content-Type': types_1.XFreeSignal.MIME
|
|
74
90
|
},
|
|
75
|
-
body: (
|
|
91
|
+
body: types_1.XFreeSignal.encodeBody('data', message)
|
|
76
92
|
});
|
|
77
93
|
return res.status === 200;
|
|
78
94
|
});
|
|
@@ -82,10 +98,10 @@ class FreeSignalAPI {
|
|
|
82
98
|
const res = yield fetch(url, {
|
|
83
99
|
method: 'PUT',
|
|
84
100
|
headers: {
|
|
85
|
-
'Content-Type':
|
|
101
|
+
'Content-Type': types_1.XFreeSignal.MIME,
|
|
86
102
|
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
87
103
|
},
|
|
88
|
-
body: (
|
|
104
|
+
body: types_1.XFreeSignal.encodeBody('data', bundle)
|
|
89
105
|
});
|
|
90
106
|
return res.status === 201;
|
|
91
107
|
});
|
|
@@ -109,35 +125,35 @@ class FreeSignalAPI {
|
|
|
109
125
|
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
110
126
|
}
|
|
111
127
|
});
|
|
112
|
-
return
|
|
128
|
+
return types_1.DataEncoder.from(yield this.decryptData(new Uint8Array(yield res.arrayBuffer()), types_1.UserId.getUserId(publicKey).toString())).data.map(array => types_1.Datagram.from(array));
|
|
113
129
|
});
|
|
114
130
|
}
|
|
115
131
|
postDatagrams(datagrams, publicKey, url) {
|
|
116
132
|
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
-
const data = yield this.encryptData(
|
|
133
|
+
const data = yield this.encryptData(new types_1.DataEncoder(datagrams.map(datagram => types_1.Datagram.from(datagram).encode())).encode(), types_1.UserId.getUserId(publicKey).toString());
|
|
118
134
|
const res = yield fetch(url, {
|
|
119
135
|
method: 'POST',
|
|
120
136
|
headers: {
|
|
121
|
-
'Content-Type':
|
|
137
|
+
'Content-Type': types_1.XFreeSignal.MIME,
|
|
122
138
|
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
123
139
|
},
|
|
124
|
-
body: data.encode()
|
|
140
|
+
body: types_1.XFreeSignal.encodeBody('data', data.encode())
|
|
125
141
|
});
|
|
126
|
-
return (0, utils_1.
|
|
142
|
+
return (0, utils_1.numberFromArray)(yield this.decryptData(new Uint8Array(yield res.arrayBuffer()), types_1.UserId.getUserId(publicKey).toString()));
|
|
127
143
|
});
|
|
128
144
|
}
|
|
129
145
|
deleteDatagrams(datagramIds, publicKey, url) {
|
|
130
146
|
return __awaiter(this, void 0, void 0, function* () {
|
|
131
|
-
const data = yield this.encryptData(
|
|
147
|
+
const data = yield this.encryptData(new types_1.DataEncoder(datagramIds.map(datagramId => crypto_1.default.UUID.parse(datagramId))).encode(), types_1.UserId.getUserId(publicKey).toString());
|
|
132
148
|
const res = yield fetch(url, {
|
|
133
149
|
method: 'DELETE',
|
|
134
150
|
headers: {
|
|
135
|
-
'Content-Type':
|
|
151
|
+
'Content-Type': types_1.XFreeSignal.MIME,
|
|
136
152
|
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
137
153
|
},
|
|
138
|
-
body: data.encode()
|
|
154
|
+
body: types_1.XFreeSignal.encodeBody('data', data.encode())
|
|
139
155
|
});
|
|
140
|
-
return (0, utils_1.
|
|
156
|
+
return (0, utils_1.numberFromArray)(yield this.decryptData(new Uint8Array(yield res.arrayBuffer()), types_1.UserId.getUserId(publicKey).toString()));
|
|
141
157
|
});
|
|
142
158
|
}
|
|
143
159
|
createToken(publicKey) {
|
|
@@ -160,47 +176,5 @@ class FreeSignalAPI {
|
|
|
160
176
|
throw new Error('Authorization header is required');
|
|
161
177
|
});
|
|
162
178
|
}
|
|
163
|
-
packIdList(datagramIds) {
|
|
164
|
-
return datagramIds.map(datagramId => crypto_1.default.UUID.parse(datagramId)).reduce((prev, curr) => new Uint8Array([...prev, ...curr]), new Uint8Array());
|
|
165
|
-
}
|
|
166
|
-
unpackIdList(data) {
|
|
167
|
-
const ids = [];
|
|
168
|
-
for (let i = 0; i < data.length; i += 16) {
|
|
169
|
-
ids.push(crypto_1.default.UUID.stringify(data.subarray(i, i + 16)));
|
|
170
|
-
}
|
|
171
|
-
return ids;
|
|
172
|
-
}
|
|
173
|
-
packDatagrams(messages) {
|
|
174
|
-
return fflate_1.default.deflateSync((0, utils_1.concatUint8Array)(...messages.flatMap(datagram => {
|
|
175
|
-
const encoded = types_1.Datagram.from(datagram).encode();
|
|
176
|
-
return [(0, utils_1.numberToUint8Array)(encoded.length, 8), encoded];
|
|
177
|
-
})));
|
|
178
|
-
}
|
|
179
|
-
unpackDatagrams(data) {
|
|
180
|
-
const messages = [];
|
|
181
|
-
let offset = 0;
|
|
182
|
-
data = fflate_1.default.inflateSync(data);
|
|
183
|
-
while (offset < data.length) {
|
|
184
|
-
const length = data.subarray(offset, offset + 8);
|
|
185
|
-
if (length.length < 8) {
|
|
186
|
-
throw new Error('Invalid message length');
|
|
187
|
-
}
|
|
188
|
-
const messageLength = (0, utils_1.numberFromUint8Array)(length);
|
|
189
|
-
offset += 8;
|
|
190
|
-
if (offset + messageLength > data.length) {
|
|
191
|
-
throw new Error('Invalid message length');
|
|
192
|
-
}
|
|
193
|
-
const messageData = data.subarray(offset, offset + messageLength);
|
|
194
|
-
offset += messageLength;
|
|
195
|
-
try {
|
|
196
|
-
const datagram = types_1.Datagram.from(messageData);
|
|
197
|
-
messages.push(datagram);
|
|
198
|
-
}
|
|
199
|
-
catch (error) {
|
|
200
|
-
throw new Error('Invalid datagram format');
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return messages;
|
|
204
|
-
}
|
|
205
179
|
}
|
|
206
180
|
exports.FreeSignalAPI = FreeSignalAPI;
|
package/double-ratchet.js
CHANGED
|
@@ -117,7 +117,9 @@ class KeySession {
|
|
|
117
117
|
var _a;
|
|
118
118
|
const encrypted = types_1.EncryptedData.from(payload);
|
|
119
119
|
const publicKey = encrypted.publicKey;
|
|
120
|
-
if (!
|
|
120
|
+
if (!this._remoteKey)
|
|
121
|
+
throw new Error("Missing remoteKey");
|
|
122
|
+
if (!(0, utils_1.verifyArrays)(publicKey, this._remoteKey)) {
|
|
121
123
|
while (this.receivingCount < encrypted.previous)
|
|
122
124
|
this.previousKeys.set(this.receivingCount, this.getReceivingKey());
|
|
123
125
|
this.setRemoteKey(publicKey);
|
|
@@ -143,7 +145,7 @@ class KeySession {
|
|
|
143
145
|
*/
|
|
144
146
|
toJSON() {
|
|
145
147
|
return {
|
|
146
|
-
secretKey: (0, utils_1.decodeBase64)((0, utils_1.
|
|
148
|
+
secretKey: (0, utils_1.decodeBase64)((0, utils_1.concatArrays)(this.keyPair.secretKey)),
|
|
147
149
|
remoteKey: (0, utils_1.decodeBase64)(this._remoteKey),
|
|
148
150
|
rootKey: (0, utils_1.decodeBase64)(this.rootKey),
|
|
149
151
|
sendingChain: (0, utils_1.decodeBase64)(this.sendingChain),
|
package/index.js
CHANGED
|
@@ -61,15 +61,6 @@ function createKeyExchange(signSecretKey, boxSecretKey, bundleStore) {
|
|
|
61
61
|
function createIdentityKeys(signSecretKey, boxSecretKey) {
|
|
62
62
|
return { sign: crypto_1.default.EdDSA.keyPair(signSecretKey), box: crypto_1.default.ECDH.keyPair(boxSecretKey) };
|
|
63
63
|
}
|
|
64
|
-
/*export function createAPI(opts: {
|
|
65
|
-
secretSignKey: Uint8Array;
|
|
66
|
-
secretBoxKey: Uint8Array;
|
|
67
|
-
sessions: LocalStorage<UserId, KeySession>;
|
|
68
|
-
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
69
|
-
users: LocalStorage<UserId, IdentityKeys>;
|
|
70
|
-
}): FreeSignalAPI {
|
|
71
|
-
return new FreeSignalAPI(opts);
|
|
72
|
-
}*/
|
|
73
64
|
var types_1 = require("./types");
|
|
74
65
|
Object.defineProperty(exports, "UserId", { enumerable: true, get: function () { return types_1.UserId; } });
|
|
75
66
|
Object.defineProperty(exports, "IdentityKeys", { enumerable: true, get: function () { return types_1.IdentityKeys; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freesignal/protocol",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Signal Protocol implementation in javascript",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Christian Braghette",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@freesignal/crypto": "^0.3.0",
|
|
15
15
|
"@freesignal/interfaces": "^0.1.1",
|
|
16
|
-
"@freesignal/utils": "^1.
|
|
16
|
+
"@freesignal/utils": "^1.2.0",
|
|
17
17
|
"base64-js": "^1.5.1",
|
|
18
18
|
"fflate": "^0.8.2",
|
|
19
19
|
"js-sha3": "^0.9.3",
|
package/types.d.ts
CHANGED
|
@@ -176,3 +176,43 @@ export declare class EncryptedDataConstructor implements EncryptedData {
|
|
|
176
176
|
toString(): string;
|
|
177
177
|
toJSON(): string;
|
|
178
178
|
}
|
|
179
|
+
declare enum DataType {
|
|
180
|
+
UKNOWN = -1,
|
|
181
|
+
RAW = 0,
|
|
182
|
+
NUMBER = 1,
|
|
183
|
+
STRING = 2,
|
|
184
|
+
ARRAY = 3,
|
|
185
|
+
OBJECT = 4
|
|
186
|
+
}
|
|
187
|
+
declare namespace DataType {
|
|
188
|
+
function getType(type: string): DataType;
|
|
189
|
+
function getName(type: DataType): string;
|
|
190
|
+
function from(data: any): DataType;
|
|
191
|
+
}
|
|
192
|
+
export declare class DataEncoder<T> implements Encodable {
|
|
193
|
+
readonly data: T;
|
|
194
|
+
readonly type: string;
|
|
195
|
+
constructor(data: T);
|
|
196
|
+
protected get _type(): DataType;
|
|
197
|
+
encode(): Uint8Array;
|
|
198
|
+
toString(): string;
|
|
199
|
+
toJSON(): string;
|
|
200
|
+
static from<T = any>(array: Uint8Array): DataEncoder<T>;
|
|
201
|
+
}
|
|
202
|
+
export declare namespace XFreeSignal {
|
|
203
|
+
export const MIME = "application/x-freesignal";
|
|
204
|
+
export const version = 1;
|
|
205
|
+
export function encodeBody(type: 'data' | 'error', data: any, compressed?: boolean): BodyInit;
|
|
206
|
+
export function decodeBody<T = any>(body: Uint8Array): Body<T>;
|
|
207
|
+
class Body<T> implements Encodable {
|
|
208
|
+
readonly type: 'data' | 'error';
|
|
209
|
+
readonly data: T;
|
|
210
|
+
constructor(type: 'data' | 'error', data: T);
|
|
211
|
+
encode(compressed?: boolean): Uint8Array;
|
|
212
|
+
toString(): string;
|
|
213
|
+
toJSON(): string;
|
|
214
|
+
static from<T = any>(array: Uint8Array): Body<T>;
|
|
215
|
+
}
|
|
216
|
+
export {};
|
|
217
|
+
}
|
|
218
|
+
export {};
|
package/types.js
CHANGED
|
@@ -21,10 +21,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.EncryptedDataConstructor = exports.EncryptedData = exports.Datagram = exports.Protocols = exports.IdentityKeys = exports.UserId = void 0;
|
|
24
|
+
exports.XFreeSignal = exports.DataEncoder = exports.EncryptedDataConstructor = exports.EncryptedData = exports.Datagram = exports.Protocols = exports.IdentityKeys = exports.UserId = void 0;
|
|
25
25
|
const utils_1 = require("@freesignal/utils");
|
|
26
26
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
27
27
|
const double_ratchet_1 = require("./double-ratchet");
|
|
28
|
+
const fflate_1 = __importDefault(require("fflate"));
|
|
28
29
|
var UserId;
|
|
29
30
|
(function (UserId) {
|
|
30
31
|
class UserIdConstructor {
|
|
@@ -68,7 +69,7 @@ var IdentityKeys;
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
encode() {
|
|
71
|
-
return (0, utils_1.
|
|
72
|
+
return (0, utils_1.concatArrays)((0, utils_1.encodeBase64)(this.publicKey), (0, utils_1.encodeBase64)(this.identityKey));
|
|
72
73
|
}
|
|
73
74
|
toString() {
|
|
74
75
|
throw (0, utils_1.decodeBase64)(this.encode());
|
|
@@ -107,11 +108,11 @@ var Protocols;
|
|
|
107
108
|
}
|
|
108
109
|
Protocols.toCode = toCode;
|
|
109
110
|
function encode(protocol, length) {
|
|
110
|
-
return (0, utils_1.
|
|
111
|
+
return (0, utils_1.numberToArray)(Protocols.toCode(protocol), length);
|
|
111
112
|
}
|
|
112
113
|
Protocols.encode = encode;
|
|
113
114
|
function decode(array) {
|
|
114
|
-
return Protocols.fromCode((0, utils_1.
|
|
115
|
+
return Protocols.fromCode((0, utils_1.numberFromArray)(array));
|
|
115
116
|
}
|
|
116
117
|
Protocols.decode = decode;
|
|
117
118
|
})(Protocols || (exports.Protocols = Protocols = {}));
|
|
@@ -125,7 +126,7 @@ var Datagram;
|
|
|
125
126
|
this.version = data[0] & 127;
|
|
126
127
|
this.protocol = Protocols.decode(data.subarray(1, 2));
|
|
127
128
|
this.id = crypto_1.default.UUID.stringify(data.subarray(2, 18));
|
|
128
|
-
this.createdAt = (0, utils_1.
|
|
129
|
+
this.createdAt = (0, utils_1.numberFromArray)(data.subarray(18, 26));
|
|
129
130
|
this.sender = (0, utils_1.decodeBase64)(data.subarray(26, 26 + crypto_1.default.EdDSA.publicKeyLength));
|
|
130
131
|
this.receiver = (0, utils_1.decodeBase64)(data.subarray(26 + crypto_1.default.EdDSA.publicKeyLength, DatagramConstructor.headerOffset));
|
|
131
132
|
if (data[0] & 128)
|
|
@@ -177,16 +178,16 @@ var Datagram;
|
|
|
177
178
|
}
|
|
178
179
|
encode() {
|
|
179
180
|
var _a, _b, _c;
|
|
180
|
-
const data = (0, utils_1.
|
|
181
|
+
const data = (0, utils_1.concatArrays)(new Uint8Array(1).fill(this.version | (this.secretKey ? 128 : 0)), //1
|
|
181
182
|
Protocols.encode(this.protocol), //1
|
|
182
183
|
(_a = crypto_1.default.UUID.parse(this.id)) !== null && _a !== void 0 ? _a : [], //16
|
|
183
|
-
(0, utils_1.
|
|
184
|
+
(0, utils_1.numberToArray)(this.createdAt, 8), //8
|
|
184
185
|
(0, utils_1.encodeBase64)(this.sender), //32
|
|
185
186
|
(0, utils_1.encodeBase64)(this.receiver), //32
|
|
186
187
|
(_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array());
|
|
187
188
|
if (this.secretKey)
|
|
188
189
|
this._signature = crypto_1.default.EdDSA.sign(data, this.secretKey);
|
|
189
|
-
return (0, utils_1.
|
|
190
|
+
return (0, utils_1.concatArrays)(data, (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
|
|
190
191
|
}
|
|
191
192
|
sign(secretKey) {
|
|
192
193
|
this.secretKey = secretKey;
|
|
@@ -238,22 +239,22 @@ class EncryptedDataConstructor {
|
|
|
238
239
|
return this;
|
|
239
240
|
}
|
|
240
241
|
if (typeof arrays[0] === 'number')
|
|
241
|
-
arrays[0] = (0, utils_1.
|
|
242
|
+
arrays[0] = (0, utils_1.numberToArray)(arrays[0], EncryptedDataConstructor.countLength);
|
|
242
243
|
if (typeof arrays[1] === 'number')
|
|
243
|
-
arrays[1] = (0, utils_1.
|
|
244
|
+
arrays[1] = (0, utils_1.numberToArray)(arrays[1], EncryptedDataConstructor.countLength);
|
|
244
245
|
if (arrays.length === 6) {
|
|
245
|
-
arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.
|
|
246
|
+
arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToArray)(arrays[5]) : arrays[5]);
|
|
246
247
|
arrays.pop();
|
|
247
248
|
}
|
|
248
249
|
else if (arrays.length > 1) {
|
|
249
|
-
arrays.unshift((0, utils_1.
|
|
250
|
+
arrays.unshift((0, utils_1.numberToArray)(double_ratchet_1.KeySession.version));
|
|
250
251
|
}
|
|
251
|
-
this.raw = (0, utils_1.
|
|
252
|
+
this.raw = (0, utils_1.concatArrays)(...arrays);
|
|
252
253
|
}
|
|
253
254
|
get length() { return this.raw.length; }
|
|
254
|
-
get version() { return (0, utils_1.
|
|
255
|
-
get count() { return (0, utils_1.
|
|
256
|
-
get previous() { return (0, utils_1.
|
|
255
|
+
get version() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
|
|
256
|
+
get count() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
|
|
257
|
+
get previous() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
|
|
257
258
|
get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
|
|
258
259
|
get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
|
|
259
260
|
get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
|
|
@@ -304,3 +305,156 @@ Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.count
|
|
|
304
305
|
Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
|
|
305
306
|
Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
|
|
306
307
|
Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
|
|
308
|
+
var DataType;
|
|
309
|
+
(function (DataType) {
|
|
310
|
+
DataType[DataType["UKNOWN"] = -1] = "UKNOWN";
|
|
311
|
+
DataType[DataType["RAW"] = 0] = "RAW";
|
|
312
|
+
DataType[DataType["NUMBER"] = 1] = "NUMBER";
|
|
313
|
+
DataType[DataType["STRING"] = 2] = "STRING";
|
|
314
|
+
DataType[DataType["ARRAY"] = 3] = "ARRAY";
|
|
315
|
+
DataType[DataType["OBJECT"] = 4] = "OBJECT";
|
|
316
|
+
})(DataType || (DataType = {}));
|
|
317
|
+
(function (DataType) {
|
|
318
|
+
function getType(type) {
|
|
319
|
+
return Object.values(DataType).indexOf(type.toLocaleUpperCase());
|
|
320
|
+
}
|
|
321
|
+
DataType.getType = getType;
|
|
322
|
+
function getName(type) {
|
|
323
|
+
return DataType[type].toLowerCase();
|
|
324
|
+
}
|
|
325
|
+
DataType.getName = getName;
|
|
326
|
+
function from(data) {
|
|
327
|
+
if (data instanceof Uint8Array)
|
|
328
|
+
return DataType.RAW;
|
|
329
|
+
return getType(typeof data);
|
|
330
|
+
}
|
|
331
|
+
DataType.from = from;
|
|
332
|
+
})(DataType || (DataType = {}));
|
|
333
|
+
class DataEncoder {
|
|
334
|
+
constructor(data) {
|
|
335
|
+
this.data = data;
|
|
336
|
+
this.type = DataType.getName(DataType.from(this.data));
|
|
337
|
+
}
|
|
338
|
+
get _type() {
|
|
339
|
+
return DataType.getType(this.type);
|
|
340
|
+
}
|
|
341
|
+
encode() {
|
|
342
|
+
let data;
|
|
343
|
+
switch (this._type) {
|
|
344
|
+
case DataType.RAW:
|
|
345
|
+
data = this.data;
|
|
346
|
+
break;
|
|
347
|
+
case DataType.NUMBER:
|
|
348
|
+
data = (0, utils_1.numberToArray)(this._type);
|
|
349
|
+
break;
|
|
350
|
+
case DataType.STRING:
|
|
351
|
+
data = (0, utils_1.encodeUTF8)(this.data);
|
|
352
|
+
break;
|
|
353
|
+
case DataType.ARRAY:
|
|
354
|
+
data = (0, utils_1.concatArrays)(...Array.from(this.data).flatMap(value => {
|
|
355
|
+
const data = new DataEncoder(value).encode();
|
|
356
|
+
return [(0, utils_1.numberToArray)(data.length, 8), data];
|
|
357
|
+
}));
|
|
358
|
+
break;
|
|
359
|
+
case DataType.OBJECT:
|
|
360
|
+
data = (0, utils_1.encodeJSON)(this.data);
|
|
361
|
+
break;
|
|
362
|
+
default:
|
|
363
|
+
throw new Error("Uknown type");
|
|
364
|
+
}
|
|
365
|
+
return (0, utils_1.concatArrays)((0, utils_1.numberToArray)(this._type), data);
|
|
366
|
+
}
|
|
367
|
+
toString() {
|
|
368
|
+
return "[Object XFreeSignalData]";
|
|
369
|
+
}
|
|
370
|
+
toJSON() {
|
|
371
|
+
return JSON.stringify(this.data);
|
|
372
|
+
}
|
|
373
|
+
static from(array) {
|
|
374
|
+
const type = array[0];
|
|
375
|
+
let rawData = array.subarray(1), data;
|
|
376
|
+
switch (type) {
|
|
377
|
+
case DataType.RAW:
|
|
378
|
+
data = rawData;
|
|
379
|
+
break;
|
|
380
|
+
case DataType.NUMBER:
|
|
381
|
+
data = (0, utils_1.numberFromArray)(rawData);
|
|
382
|
+
break;
|
|
383
|
+
case DataType.STRING:
|
|
384
|
+
data = (0, utils_1.decodeUTF8)(rawData);
|
|
385
|
+
break;
|
|
386
|
+
case DataType.ARRAY:
|
|
387
|
+
const arrayData = [];
|
|
388
|
+
let offset = 0;
|
|
389
|
+
while (offset < rawData.length) {
|
|
390
|
+
const length = rawData.subarray(offset, offset + 8);
|
|
391
|
+
if (length.length < 8)
|
|
392
|
+
throw new Error('Invalid data length');
|
|
393
|
+
const messageLength = (0, utils_1.numberFromArray)(length);
|
|
394
|
+
offset += 8;
|
|
395
|
+
if (offset + messageLength > rawData.length) {
|
|
396
|
+
throw new Error('Invalid data length');
|
|
397
|
+
}
|
|
398
|
+
arrayData.push(rawData.subarray(offset, offset + messageLength));
|
|
399
|
+
offset += messageLength;
|
|
400
|
+
}
|
|
401
|
+
data = arrayData;
|
|
402
|
+
break;
|
|
403
|
+
case DataType.OBJECT:
|
|
404
|
+
data = (0, utils_1.decodeJSON)(rawData);
|
|
405
|
+
break;
|
|
406
|
+
default:
|
|
407
|
+
throw new Error('Invalid data format');
|
|
408
|
+
}
|
|
409
|
+
return new DataEncoder(data);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
exports.DataEncoder = DataEncoder;
|
|
413
|
+
var XFreeSignal;
|
|
414
|
+
(function (XFreeSignal) {
|
|
415
|
+
XFreeSignal.MIME = "application/x-freesignal";
|
|
416
|
+
XFreeSignal.version = 1;
|
|
417
|
+
function encodeBody(type, data, compressed = false) {
|
|
418
|
+
return new Body(type, data).encode(compressed);
|
|
419
|
+
}
|
|
420
|
+
XFreeSignal.encodeBody = encodeBody;
|
|
421
|
+
function decodeBody(body) {
|
|
422
|
+
return Body.from(body);
|
|
423
|
+
}
|
|
424
|
+
XFreeSignal.decodeBody = decodeBody;
|
|
425
|
+
let BodyType;
|
|
426
|
+
(function (BodyType) {
|
|
427
|
+
BodyType[BodyType["DATA"] = 0] = "DATA";
|
|
428
|
+
BodyType[BodyType["ERROR"] = 1] = "ERROR";
|
|
429
|
+
})(BodyType || (BodyType = {}));
|
|
430
|
+
(function (BodyType) {
|
|
431
|
+
function getName(type) {
|
|
432
|
+
return BodyType[type].toLowerCase();
|
|
433
|
+
}
|
|
434
|
+
BodyType.getName = getName;
|
|
435
|
+
})(BodyType || (BodyType = {}));
|
|
436
|
+
class Body {
|
|
437
|
+
constructor(type, data) {
|
|
438
|
+
this.type = type;
|
|
439
|
+
this.data = data;
|
|
440
|
+
}
|
|
441
|
+
encode(compressed = false) {
|
|
442
|
+
const data = new DataEncoder(this.data).encode();
|
|
443
|
+
return (0, utils_1.concatArrays)((0, utils_1.numberToArray)(((this.type === 'data' ? BodyType.DATA : BodyType.ERROR) << 6)
|
|
444
|
+
+ (compressed ? 32 : 0)
|
|
445
|
+
+ XFreeSignal.version), compressed ? fflate_1.default.deflateSync(data) : data);
|
|
446
|
+
}
|
|
447
|
+
toString() {
|
|
448
|
+
return "[Object XFreeSignalBody]";
|
|
449
|
+
}
|
|
450
|
+
toJSON() {
|
|
451
|
+
return JSON.stringify({
|
|
452
|
+
type: this.type,
|
|
453
|
+
data: this.data
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
static from(array) {
|
|
457
|
+
return new Body(BodyType.getName((array[0] & 64) >> 6), DataEncoder.from((array[0] & 32) >> 5 === 1 ? fflate_1.default.inflateSync(array.subarray(1)) : array.subarray(1)).data);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
})(XFreeSignal || (exports.XFreeSignal = XFreeSignal = {}));
|
package/x3dh.js
CHANGED
|
@@ -94,7 +94,7 @@ class KeyExchange {
|
|
|
94
94
|
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
|
|
95
95
|
]), new Uint8Array(double_ratchet_1.KeySession.rootKeyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.rootKeyLength);
|
|
96
96
|
const session = new double_ratchet_1.KeySession({ remoteKey: identityKey, rootKey });
|
|
97
|
-
const cyphertext = session.encrypt((0, utils_1.
|
|
97
|
+
const cyphertext = session.encrypt((0, utils_1.concatArrays)(crypto_1.default.hash(this._identityKey.publicKey), crypto_1.default.hash(identityKey)));
|
|
98
98
|
if (!cyphertext)
|
|
99
99
|
throw new Error("Decryption error");
|
|
100
100
|
return {
|
|
@@ -135,7 +135,7 @@ class KeyExchange {
|
|
|
135
135
|
const cleartext = session.decrypt((0, utils_1.encodeBase64)(message.associatedData));
|
|
136
136
|
if (!cleartext)
|
|
137
137
|
throw new Error("Error decrypting ACK message");
|
|
138
|
-
if (!(0, utils_1.
|
|
138
|
+
if (!(0, utils_1.verifyArrays)(cleartext, (0, utils_1.concatArrays)(crypto_1.default.hash(identityKey), crypto_1.default.hash(this._identityKey.publicKey))))
|
|
139
139
|
throw new Error("Error verifing Associated Data");
|
|
140
140
|
return {
|
|
141
141
|
session,
|