@vex-chat/libvex 5.5.2 → 6.0.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.
@@ -1 +1 @@
1
- {"version":3,"file":"sqlSessionToCrypto.js","sourceRoot":"","sources":["../../src/utils/sqlSessionToCrypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,MAAM,UAAU,kBAAkB,CAAC,OAAmB;IAClD,OAAO;QACH,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC;QAClD,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;QAC9C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,MAAM,EAAE,OAAO,CAAC,MAAM;KACzB,CAAC;AACN,CAAC"}
1
+ {"version":3,"file":"sqlSessionToCrypto.js","sourceRoot":"","sources":["../../src/utils/sqlSessionToCrypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,MAAM,UAAU,kBAAkB,CAAC,OAAmB;IAClD,MAAM,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1D,OAAO;QACH,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;QACvD,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;QACvD,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;QACvD,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC;QAChD,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;QAC9C,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC;QAClD,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;QAC9C,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,WAAW;QACX,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC7B,CAAC;AACN,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW;IACjC,IAAI,CAAC;QACD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAChD,OAAO,EAAE,CAAC;QACd,CAAC;QACD,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1C,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACxB,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACf,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vex-chat/libvex",
3
- "version": "5.5.2",
3
+ "version": "6.0.0",
4
4
  "description": "Library for communicating with xchat server.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -93,8 +93,8 @@
93
93
  "msgpackr": "^1.11.9",
94
94
  "uuid": "^14.0.0",
95
95
  "zod": "^4.3.6",
96
- "@vex-chat/crypto": "^2.1.2",
97
- "@vex-chat/types": "^2.0.0"
96
+ "@vex-chat/crypto": "^3.0.0",
97
+ "@vex-chat/types": "^3.0.0"
98
98
  },
99
99
  "peerDependencies": {
100
100
  "better-sqlite3": ">=11.0.0"
package/src/Client.ts CHANGED
@@ -80,14 +80,22 @@ import { z } from "zod/v4";
80
80
  import { WebSocketAdapter } from "./transport/websocket.js";
81
81
  import {
82
82
  decodeFipsInitialExtraV1,
83
- decodeFipsSubsequentExtraV1,
84
83
  encodeFipsInitialExtraV1,
85
- encodeFipsSubsequentExtraV1,
86
84
  fipsP256AdFromIdentityPubs,
87
85
  fipsP256PreKeySignPayload,
88
86
  isFipsInitialExtraV1,
89
- isFipsSubsequentExtraV1,
90
87
  } from "./utils/fipsMailExtra.js";
88
+ import {
89
+ decodeRatchetHeader,
90
+ encodeRatchetHeader,
91
+ hasRemoteDhChanged,
92
+ initRatchetSession,
93
+ ratchetStepReceive,
94
+ ratchetStepSend,
95
+ sessionToSqlPatch,
96
+ takeReceiveMessageKey,
97
+ takeSendMessageKey,
98
+ } from "./utils/ratchet.js";
91
99
 
92
100
  function debugLibvexDm(
93
101
  msg: string,
@@ -1309,9 +1317,6 @@ export class Client {
1309
1317
  return [signKey, ephKey, ad, index];
1310
1318
  }
1311
1319
  case MailType.subsequent:
1312
- if (isFipsSubsequentExtraV1(extra)) {
1313
- return [decodeFipsSubsequentExtraV1(extra)];
1314
- }
1315
1320
  return [extra];
1316
1321
  default:
1317
1322
  return [];
@@ -2094,7 +2099,9 @@ export class Client {
2094
2099
  // discard the ephemeral keys
2095
2100
  await this.newEphemeralKeys();
2096
2101
 
2102
+ const ratchet = await initRatchetSession(SK, "initiator");
2097
2103
  const sessionEntry: SessionSQL = {
2104
+ ...ratchet,
2098
2105
  deviceID: device.deviceID,
2099
2106
  fingerprint: XUtils.encodeHex(AD),
2100
2107
  lastUsed: new Date().toISOString(),
@@ -3271,7 +3278,12 @@ export class Client {
3271
3278
  deviceEntry;
3272
3279
 
3273
3280
  // save session
3281
+ const ratchet = await initRatchetSession(
3282
+ SK,
3283
+ "receiver",
3284
+ );
3274
3285
  const newSession: SessionSQL = {
3286
+ ...ratchet,
3275
3287
  deviceID: mail.sender,
3276
3288
  fingerprint: XUtils.encodeHex(AD),
3277
3289
  lastUsed: new Date().toISOString(),
@@ -3305,19 +3317,12 @@ export class Client {
3305
3317
  }
3306
3318
  break;
3307
3319
  case MailType.subsequent: {
3308
- const extraBuf = new Uint8Array(mail.extra);
3309
- const publicKey = isFipsSubsequentExtraV1(extraBuf)
3310
- ? decodeFipsSubsequentExtraV1(extraBuf)
3311
- : Client.deserializeExtra(
3312
- mail.mailType,
3313
- extraBuf,
3314
- )[0];
3315
- if (!publicKey) {
3316
- throw new Error(
3317
- "Malformed subsequent mail extra: missing publicKey",
3318
- );
3319
- }
3320
- let session = await this.getSessionByPubkey(publicKey);
3320
+ const ratchetHeader = decodeRatchetHeader(
3321
+ new Uint8Array(mail.extra),
3322
+ );
3323
+ let session = await this.database.getSessionByDeviceID(
3324
+ mail.sender,
3325
+ );
3321
3326
  let retries = 0;
3322
3327
  while (!session) {
3323
3328
  if (retries >= 3) {
@@ -3325,14 +3330,32 @@ export class Client {
3325
3330
  }
3326
3331
  await sleep(100 * 2 ** retries);
3327
3332
  retries++;
3328
- session = await this.getSessionByPubkey(publicKey);
3333
+ session = await this.database.getSessionByDeviceID(
3334
+ mail.sender,
3335
+ );
3329
3336
  }
3330
3337
 
3331
3338
  if (!session) {
3332
3339
  void healSession();
3333
3340
  return;
3334
3341
  }
3335
- const HMAC = xHMAC(mail, session.SK);
3342
+
3343
+ if (
3344
+ hasRemoteDhChanged(session.DHr, ratchetHeader.dhPub)
3345
+ ) {
3346
+ await ratchetStepReceive(
3347
+ session,
3348
+ ratchetHeader.dhPub,
3349
+ ratchetHeader.pn,
3350
+ );
3351
+ }
3352
+
3353
+ const messageKey = takeReceiveMessageKey(
3354
+ session,
3355
+ ratchetHeader.dhPub,
3356
+ ratchetHeader.n,
3357
+ );
3358
+ const HMAC = xHMAC(mail, messageKey);
3336
3359
 
3337
3360
  if (!XUtils.bytesEqual(HMAC, header)) {
3338
3361
  void healSession();
@@ -3342,7 +3365,7 @@ export class Client {
3342
3365
  const decrypted = await xSecretboxOpenAsync(
3343
3366
  new Uint8Array(mail.cipher),
3344
3367
  new Uint8Array(mail.nonce),
3345
- session.SK,
3368
+ messageKey,
3346
3369
  );
3347
3370
 
3348
3371
  if (decrypted) {
@@ -3374,9 +3397,34 @@ export class Client {
3374
3397
  };
3375
3398
  this.emitter.emit("message", message);
3376
3399
 
3377
- void this.database.markSessionUsed(
3378
- session.sessionID,
3379
- );
3400
+ const sqlPatch = sessionToSqlPatch(session);
3401
+ const persisted: SessionSQL = {
3402
+ CKr: sqlPatch.CKr,
3403
+ CKs: sqlPatch.CKs,
3404
+ deviceID: mail.sender,
3405
+ DHr: sqlPatch.DHr,
3406
+ DHsPrivate: sqlPatch.DHsPrivate,
3407
+ DHsPublic: sqlPatch.DHsPublic,
3408
+ fingerprint: XUtils.encodeHex(
3409
+ session.fingerprint,
3410
+ ),
3411
+ lastUsed: new Date().toISOString(),
3412
+ mode: session.mode,
3413
+ Nr: sqlPatch.Nr,
3414
+ Ns: sqlPatch.Ns,
3415
+ PN: sqlPatch.PN,
3416
+ publicKey: XUtils.encodeHex(session.publicKey),
3417
+ RK: sqlPatch.RK,
3418
+ sessionID: session.sessionID,
3419
+ SK: XUtils.encodeHex(session.SK),
3420
+ skippedKeys: sqlPatch.skippedKeys,
3421
+ userID: session.userID,
3422
+ verified: session.verified,
3423
+ };
3424
+ await this.database.saveSession(persisted);
3425
+ this.sessionRecords[
3426
+ XUtils.encodeHex(session.publicKey)
3427
+ ] = session;
3380
3428
  } else {
3381
3429
  void healSession();
3382
3430
  this.emitter.emit("retryRequest", {
@@ -3718,12 +3766,19 @@ export class Client {
3718
3766
  });
3719
3767
  }
3720
3768
 
3769
+ if (!session.CKs) {
3770
+ await ratchetStepSend(session);
3771
+ }
3772
+ const { messageKey, n } = takeSendMessageKey(session);
3773
+ const ratchetHeader = {
3774
+ dhPub: session.DHsPublic,
3775
+ n,
3776
+ pn: session.PN,
3777
+ version: 1 as const,
3778
+ };
3721
3779
  const nonce = xMakeNonce();
3722
- const cipher = await xSecretboxAsync(msg, nonce, session.SK);
3723
- const extra =
3724
- this.cryptoProfile === "fips"
3725
- ? encodeFipsSubsequentExtraV1(session.publicKey)
3726
- : session.publicKey;
3780
+ const cipher = await xSecretboxAsync(msg, nonce, messageKey);
3781
+ const extra = encodeRatchetHeader(ratchetHeader);
3727
3782
 
3728
3783
  const mail: MailWS = {
3729
3784
  authorID: this.getUser().userID,
@@ -3747,7 +3802,7 @@ export class Client {
3747
3802
  type: "resource",
3748
3803
  };
3749
3804
 
3750
- const hmac = xHMAC(mail, session.SK);
3805
+ const hmac = xHMAC(mail, messageKey);
3751
3806
 
3752
3807
  const fwdOut = forward
3753
3808
  ? messageSchema.parse(msgpack.decode(msg))
@@ -3770,6 +3825,31 @@ export class Client {
3770
3825
  };
3771
3826
  this.emitter.emit("message", outMsg);
3772
3827
 
3828
+ const sqlPatch = sessionToSqlPatch(session);
3829
+ const persisted: SessionSQL = {
3830
+ CKr: sqlPatch.CKr,
3831
+ CKs: sqlPatch.CKs,
3832
+ deviceID: device.deviceID,
3833
+ DHr: sqlPatch.DHr,
3834
+ DHsPrivate: sqlPatch.DHsPrivate,
3835
+ DHsPublic: sqlPatch.DHsPublic,
3836
+ fingerprint: XUtils.encodeHex(session.fingerprint),
3837
+ lastUsed: new Date().toISOString(),
3838
+ mode: session.mode,
3839
+ Nr: sqlPatch.Nr,
3840
+ Ns: sqlPatch.Ns,
3841
+ PN: sqlPatch.PN,
3842
+ publicKey: XUtils.encodeHex(session.publicKey),
3843
+ RK: sqlPatch.RK,
3844
+ sessionID: session.sessionID,
3845
+ SK: XUtils.encodeHex(session.SK),
3846
+ skippedKeys: sqlPatch.skippedKeys,
3847
+ userID: session.userID,
3848
+ verified: session.verified,
3849
+ };
3850
+ await this.database.saveSession(persisted);
3851
+ this.sessionRecords[XUtils.encodeHex(session.publicKey)] = session;
3852
+
3773
3853
  await new Promise((res, rej) => {
3774
3854
  const callback = (packedMsg: Uint8Array) => {
3775
3855
  const [_header, receivedMsg] =
@@ -234,7 +234,12 @@ export class MemoryStorage extends EventEmitter implements Storage {
234
234
  }
235
235
 
236
236
  saveSession(session: SessionSQL): Promise<void> {
237
- if (!this.sessions.find((s) => s.SK === session.SK)) {
237
+ const idx = this.sessions.findIndex(
238
+ (s) => s.sessionID === session.sessionID,
239
+ );
240
+ if (idx >= 0) {
241
+ this.sessions[idx] = session;
242
+ } else {
238
243
  this.sessions.push(session);
239
244
  }
240
245
  return Promise.resolve();
@@ -261,14 +266,31 @@ export class MemoryStorage extends EventEmitter implements Storage {
261
266
  }
262
267
 
263
268
  private sqlToCrypto(s: SessionSQL): SessionCrypto {
269
+ let skippedKeys: Record<string, string> = {};
270
+ try {
271
+ skippedKeys = JSON.parse(s.skippedKeys) as Record<string, string>;
272
+ } catch {
273
+ skippedKeys = {};
274
+ }
264
275
  return {
276
+ CKr: s.CKr ? XUtils.decodeHex(s.CKr) : null,
277
+ CKs: s.CKs ? XUtils.decodeHex(s.CKs) : null,
278
+ DHr: s.DHr ? XUtils.decodeHex(s.DHr) : null,
279
+ DHsPrivate: XUtils.decodeHex(s.DHsPrivate),
280
+ DHsPublic: XUtils.decodeHex(s.DHsPublic),
265
281
  fingerprint: XUtils.decodeHex(s.fingerprint),
266
282
  lastUsed: s.lastUsed,
267
283
  mode: s.mode,
284
+ Nr: s.Nr,
285
+ Ns: s.Ns,
286
+ PN: s.PN,
268
287
  publicKey: XUtils.decodeHex(s.publicKey),
288
+ RK: XUtils.decodeHex(s.RK),
269
289
  sessionID: s.sessionID,
270
290
  SK: XUtils.decodeHex(s.SK),
291
+ skippedKeys,
271
292
  userID: s.userID,
293
+ verified: s.verified,
272
294
  };
273
295
  }
274
296
  }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
+ * Licensed under AGPL-3.0. See LICENSE for details.
4
+ * Commercial licenses available at vex.wtf
5
+ */
6
+
7
+ import { XUtils } from "@vex-chat/crypto";
8
+
9
+ import { describe, expect, it } from "vitest";
10
+
11
+ import {
12
+ decodeRatchetHeader,
13
+ encodeRatchetHeader,
14
+ hasRemoteDhChanged,
15
+ initRatchetSession,
16
+ ratchetStepReceive,
17
+ ratchetStepSend,
18
+ takeReceiveMessageKey,
19
+ takeSendMessageKey,
20
+ } from "../utils/ratchet.js";
21
+
22
+ describe("double ratchet helpers", () => {
23
+ it("derives matching message keys for first exchange and reply", async () => {
24
+ const sk = XUtils.decodeHex(
25
+ "1111111111111111111111111111111111111111111111111111111111111111",
26
+ );
27
+ const alice = await initRatchetSession(sk, "initiator");
28
+ const bob = await initRatchetSession(sk, "receiver");
29
+
30
+ const aliceState = {
31
+ CKr: alice.CKr ? XUtils.decodeHex(alice.CKr) : null,
32
+ CKs: alice.CKs ? XUtils.decodeHex(alice.CKs) : null,
33
+ DHr: alice.DHr ? XUtils.decodeHex(alice.DHr) : null,
34
+ DHsPrivate: XUtils.decodeHex(alice.DHsPrivate),
35
+ DHsPublic: XUtils.decodeHex(alice.DHsPublic),
36
+ Nr: alice.Nr,
37
+ Ns: alice.Ns,
38
+ PN: alice.PN,
39
+ RK: XUtils.decodeHex(alice.RK),
40
+ skippedKeys: {} as Record<string, string>,
41
+ };
42
+ const bobState = {
43
+ CKr: bob.CKr ? XUtils.decodeHex(bob.CKr) : null,
44
+ CKs: bob.CKs ? XUtils.decodeHex(bob.CKs) : null,
45
+ DHr: bob.DHr ? XUtils.decodeHex(bob.DHr) : null,
46
+ DHsPrivate: XUtils.decodeHex(bob.DHsPrivate),
47
+ DHsPublic: XUtils.decodeHex(bob.DHsPublic),
48
+ Nr: bob.Nr,
49
+ Ns: bob.Ns,
50
+ PN: bob.PN,
51
+ RK: XUtils.decodeHex(bob.RK),
52
+ skippedKeys: {} as Record<string, string>,
53
+ };
54
+
55
+ await ratchetStepSend(aliceState);
56
+ const a1 = takeSendMessageKey(aliceState);
57
+ const h1 = decodeRatchetHeader(
58
+ encodeRatchetHeader({
59
+ dhPub: aliceState.DHsPublic,
60
+ n: a1.n,
61
+ pn: aliceState.PN,
62
+ version: 1,
63
+ }),
64
+ );
65
+
66
+ expect(hasRemoteDhChanged(bobState.DHr, h1.dhPub)).toBe(true);
67
+ await ratchetStepReceive(bobState, h1.dhPub, h1.pn);
68
+ const b1 = takeReceiveMessageKey(bobState, h1.dhPub, h1.n);
69
+ expect(XUtils.bytesEqual(a1.messageKey, b1)).toBe(true);
70
+
71
+ await ratchetStepSend(bobState);
72
+ const bReply = takeSendMessageKey(bobState);
73
+ const h2 = decodeRatchetHeader(
74
+ encodeRatchetHeader({
75
+ dhPub: bobState.DHsPublic,
76
+ n: bReply.n,
77
+ pn: bobState.PN,
78
+ version: 1,
79
+ }),
80
+ );
81
+ await ratchetStepReceive(aliceState, h2.dhPub, h2.pn);
82
+ const aReply = takeReceiveMessageKey(aliceState, h2.dhPub, h2.n);
83
+ expect(XUtils.bytesEqual(aReply, bReply.messageKey)).toBe(true);
84
+ });
85
+
86
+ it("supports skipped keys for out-of-order messages", async () => {
87
+ const sk = XUtils.decodeHex(
88
+ "2222222222222222222222222222222222222222222222222222222222222222",
89
+ );
90
+ const initiator = await initRatchetSession(sk, "initiator");
91
+ const receiver = await initRatchetSession(sk, "receiver");
92
+
93
+ const s = {
94
+ CKr: initiator.CKr ? XUtils.decodeHex(initiator.CKr) : null,
95
+ CKs: initiator.CKs ? XUtils.decodeHex(initiator.CKs) : null,
96
+ DHr: initiator.DHr ? XUtils.decodeHex(initiator.DHr) : null,
97
+ DHsPrivate: XUtils.decodeHex(initiator.DHsPrivate),
98
+ DHsPublic: XUtils.decodeHex(initiator.DHsPublic),
99
+ Nr: initiator.Nr,
100
+ Ns: initiator.Ns,
101
+ PN: initiator.PN,
102
+ RK: XUtils.decodeHex(initiator.RK),
103
+ skippedKeys: {} as Record<string, string>,
104
+ };
105
+ const r = {
106
+ CKr: receiver.CKr ? XUtils.decodeHex(receiver.CKr) : null,
107
+ CKs: receiver.CKs ? XUtils.decodeHex(receiver.CKs) : null,
108
+ DHr: receiver.DHr ? XUtils.decodeHex(receiver.DHr) : null,
109
+ DHsPrivate: XUtils.decodeHex(receiver.DHsPrivate),
110
+ DHsPublic: XUtils.decodeHex(receiver.DHsPublic),
111
+ Nr: receiver.Nr,
112
+ Ns: receiver.Ns,
113
+ PN: receiver.PN,
114
+ RK: XUtils.decodeHex(receiver.RK),
115
+ skippedKeys: {} as Record<string, string>,
116
+ };
117
+
118
+ await ratchetStepSend(s);
119
+ const m0 = takeSendMessageKey(s);
120
+ const m1 = takeSendMessageKey(s);
121
+ const h0 = {
122
+ dhPub: s.DHsPublic,
123
+ n: m0.n,
124
+ pn: s.PN,
125
+ version: 1 as const,
126
+ };
127
+ const h1 = {
128
+ dhPub: s.DHsPublic,
129
+ n: m1.n,
130
+ pn: s.PN,
131
+ version: 1 as const,
132
+ };
133
+
134
+ await ratchetStepReceive(r, h1.dhPub, h1.pn);
135
+ const r1 = takeReceiveMessageKey(r, h1.dhPub, h1.n);
136
+ expect(XUtils.bytesEqual(r1, m1.messageKey)).toBe(true);
137
+
138
+ const r0 = takeReceiveMessageKey(r, h0.dhPub, h0.n);
139
+ expect(XUtils.bytesEqual(r0, m0.messageKey)).toBe(true);
140
+ });
141
+ });
@@ -88,13 +88,23 @@ interface PreKeysTable {
88
88
  userID: ColumnType<string, string | undefined, string>;
89
89
  }
90
90
  interface SessionsTable {
91
+ CKr: null | string;
92
+ CKs: null | string;
91
93
  deviceID: string;
94
+ DHr: null | string;
95
+ DHsPrivate: string;
96
+ DHsPublic: string;
92
97
  fingerprint: string;
93
98
  lastUsed: string;
94
99
  mode: string;
100
+ Nr: number;
101
+ Ns: number;
102
+ PN: number;
95
103
  publicKey: string;
104
+ RK: string;
96
105
  sessionID: string;
97
106
  SK: string;
107
+ skippedKeys: string;
98
108
  userID: string;
99
109
  verified: number;
100
110
  }