@unknownncat/swt-libsignal 1.0.5 → 1.0.7

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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/dist/crypto.d.ts +1 -1
  3. package/dist/crypto.js +69 -35
  4. package/dist/curve.d.ts +1 -1
  5. package/dist/fingerprint.js +1 -1
  6. package/dist/index.d.ts +19 -20
  7. package/dist/index.js +12 -13
  8. package/dist/job_queue.js +22 -18
  9. package/dist/key-helper.d.ts +1 -1
  10. package/dist/key-helper.js +1 -1
  11. package/dist/protobuf.d.ts +1 -1
  12. package/dist/protobuf.js +1 -1
  13. package/dist/{base_key_type.d.ts → ratchet-types.d.ts} +5 -0
  14. package/dist/{chain_type.js → ratchet-types.js} +4 -0
  15. package/dist/session/builder/index.d.ts +2 -2
  16. package/dist/session/builder/index.js +2 -2
  17. package/dist/session/builder/session-builder.d.ts +3 -3
  18. package/dist/session/builder/session-builder.js +36 -16
  19. package/dist/session/builder/types.d.ts +1 -1
  20. package/dist/session/cipher/encoding.d.ts +1 -1
  21. package/dist/session/cipher/index.d.ts +3 -3
  22. package/dist/session/cipher/index.js +2 -3
  23. package/dist/session/cipher/session-cipher.d.ts +2 -2
  24. package/dist/session/cipher/session-cipher.js +108 -59
  25. package/dist/session/cipher/types.d.ts +2 -2
  26. package/dist/session/index.d.ts +5 -3
  27. package/dist/session/index.js +5 -3
  28. package/dist/session/record/index.d.ts +3 -3
  29. package/dist/session/record/index.js +3 -3
  30. package/dist/session/record/session-entry.d.ts +1 -1
  31. package/dist/session/record/session-entry.js +2 -3
  32. package/dist/session/record/session-record.d.ts +2 -2
  33. package/dist/session/record/session-record.js +4 -4
  34. package/dist/session/record/types.d.ts +3 -3
  35. package/dist/session/storage/adapter.d.ts +21 -0
  36. package/dist/session/storage/adapter.js +57 -0
  37. package/dist/session/storage/in-memory.d.ts +13 -0
  38. package/dist/session/storage/in-memory.js +33 -0
  39. package/dist/session/storage/index.d.ts +19 -0
  40. package/dist/session/storage/index.js +29 -0
  41. package/dist/session/storage/migrations.d.ts +2 -0
  42. package/dist/session/storage/migrations.js +6 -0
  43. package/dist/session/storage/types.d.ts +18 -0
  44. package/dist/types/asymmetric.d.ts +41 -0
  45. package/dist/types/crypto.d.ts +69 -0
  46. package/dist/types/index.d.ts +2 -0
  47. package/package.json +12 -3
  48. package/dist/base_key_type.js +0 -4
  49. package/dist/chain_type.d.ts +0 -5
  50. package/dist/teste.js +0 -18
  51. /package/dist/{teste.d.ts → session/storage/types.js} +0 -0
@@ -1,14 +1,14 @@
1
- import { PROTOCOL_VERSION } from '../constants.js';
2
- import { WhisperMessageEncoder } from './encoding.js';
3
- import { assertUint8 } from '../utils.js';
4
- import { ChainType } from '../../chain_type.js';
5
- import { ProtocolAddress } from '../../protocol_address.js';
6
- import { SessionBuilder } from '../builder/session-builder.js';
7
- import { SessionRecord } from '../record/index.js';
8
- import { crypto } from '../../crypto.js';
9
- import { signalCrypto } from '../../curve.js';
10
- import { SessionError, UntrustedIdentityKeyError, MessageCounterError } from '../../signal-errors.js';
11
- import { enqueue } from '../../job_queue.js';
1
+ import { PROTOCOL_VERSION } from '../constants';
2
+ import { WhisperMessageEncoder } from './encoding';
3
+ import { assertUint8, toBase64 } from '../utils';
4
+ import { ChainType } from "../../ratchet-types";
5
+ import { ProtocolAddress } from '../../protocol_address';
6
+ import { SessionBuilder } from '../builder/session-builder';
7
+ import { SessionRecord } from '../record/index';
8
+ import { crypto } from '../../crypto';
9
+ import { signalCrypto } from '../../curve';
10
+ import { SessionError, UntrustedIdentityKeyError, MessageCounterError } from '../../signal-errors';
11
+ import { enqueue } from '../../job_queue';
12
12
  export class SessionCipher {
13
13
  addr;
14
14
  addrStr;
@@ -69,45 +69,54 @@ export class SessionCipher {
69
69
  if (!messageKey)
70
70
  throw new Error('Message key not generated');
71
71
  const keys = this.deriveSecrets(messageKey, new Uint8Array(32), new TextEncoder().encode('WhisperMessageKeys'));
72
- delete chain.messageKeys[chain.chainKey.counter];
73
72
  const [cipherKey, macKey, aadKey] = keys;
74
- if (!cipherKey || !macKey || !aadKey)
75
- throw new Error('Keys not derived');
76
- const encrypted = await crypto.encrypt(cipherKey, data, { aad: aadKey.subarray(0, 16) });
77
- const msg = {
78
- ephemeralKey: session.currentRatchet.ephemeralKeyPair.pubKey,
79
- counter: chain.chainKey.counter,
80
- previousCounter: session.currentRatchet.previousCounter,
81
- ciphertext: encrypted.ciphertext
82
- };
83
- const msgBuf = WhisperMessageEncoder.encodeWhisperMessage(msg);
84
- const macInput = new Uint8Array(msgBuf.byteLength + 67);
85
- macInput.set(ourIdentity.pubKey);
86
- macInput.set(remoteIdentityKey, 33);
87
- macInput[66] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
88
- macInput.set(msgBuf, 67);
89
- const mac = crypto.hmacSha256(macKey, macInput);
90
- const result = new Uint8Array(msgBuf.byteLength + 9);
91
- result[0] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
92
- result.set(msgBuf, 1);
93
- result.set(mac.subarray(0, 8), msgBuf.byteLength + 1);
94
- await this.storeRecord(record);
95
- if (session.pendingPreKey) {
96
- const preKeyMsg = {
97
- identityKey: ourIdentity.pubKey,
98
- registrationId: await this.storage.getOurRegistrationId(),
99
- baseKey: session.pendingPreKey.baseKey,
100
- signedPreKeyId: session.pendingPreKey.signedKeyId,
101
- preKeyId: session.pendingPreKey.preKeyId,
102
- message: result
73
+ if (!cipherKey || !macKey || !aadKey || cipherKey.length !== 32 || macKey.length !== 32 || aadKey.length !== 32) {
74
+ throw new Error('Invalid key derivation');
75
+ }
76
+ let result;
77
+ try {
78
+ const encrypted = await crypto.encrypt(cipherKey, data, { aad: aadKey.subarray(0, 16) });
79
+ const msg = {
80
+ ephemeralKey: session.currentRatchet.ephemeralKeyPair.pubKey,
81
+ counter: chain.chainKey.counter,
82
+ previousCounter: session.currentRatchet.previousCounter,
83
+ ciphertext: encrypted.ciphertext
103
84
  };
104
- const preKeyBuf = WhisperMessageEncoder.encodePreKeyWhisperMessage(preKeyMsg);
105
- const body = new Uint8Array(1 + preKeyBuf.byteLength);
85
+ const msgBuf = WhisperMessageEncoder.encodeWhisperMessage(msg);
86
+ const macInput = new Uint8Array(msgBuf.byteLength + 67);
87
+ macInput.set(ourIdentity.pubKey);
88
+ macInput.set(remoteIdentityKey, 33);
89
+ macInput[66] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
90
+ macInput.set(msgBuf, 67);
91
+ const mac = crypto.hmacSha256(macKey, macInput);
92
+ const body = new Uint8Array(msgBuf.byteLength + 9);
106
93
  body[0] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
107
- body.set(preKeyBuf, 1);
108
- return { type: 3, body, registrationId: session.registrationId };
94
+ body.set(msgBuf, 1);
95
+ body.set(mac.subarray(0, 8), msgBuf.byteLength + 1);
96
+ if (session.pendingPreKey) {
97
+ const preKeyMsg = {
98
+ identityKey: ourIdentity.pubKey,
99
+ registrationId: await this.storage.getOurRegistrationId(),
100
+ baseKey: session.pendingPreKey.baseKey,
101
+ signedPreKeyId: session.pendingPreKey.signedKeyId,
102
+ preKeyId: session.pendingPreKey.preKeyId,
103
+ message: body
104
+ };
105
+ const preKeyBuf = WhisperMessageEncoder.encodePreKeyWhisperMessage(preKeyMsg);
106
+ const finalBody = new Uint8Array(1 + preKeyBuf.byteLength);
107
+ finalBody[0] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
108
+ finalBody.set(preKeyBuf, 1);
109
+ result = { type: 3, body: finalBody, registrationId: session.registrationId };
110
+ }
111
+ else {
112
+ result = { type: 1, body, registrationId: session.registrationId };
113
+ }
109
114
  }
110
- return { type: 1, body: result, registrationId: session.registrationId };
115
+ finally {
116
+ delete chain.messageKeys[chain.chainKey.counter];
117
+ }
118
+ await this.storeRecord(record);
119
+ return result;
111
120
  });
112
121
  }
113
122
  async decryptWhisperMessage(data) {
@@ -136,9 +145,22 @@ export class SessionCipher {
136
145
  return this.queueJob(async () => {
137
146
  let record = await this.getRecord();
138
147
  const preKeyProto = WhisperMessageEncoder.decodePreKeyWhisperMessage(data.subarray(1));
148
+ if (!preKeyProto.identityKey || preKeyProto.identityKey.length === 0) {
149
+ throw new Error('Missing or empty identityKey in PreKeyWhisperMessage');
150
+ }
151
+ if (!preKeyProto.baseKey || preKeyProto.baseKey.length === 0) {
152
+ throw new Error('Missing or empty baseKey in PreKeyWhisperMessage');
153
+ }
154
+ if (!preKeyProto.message || preKeyProto.message.length === 0) {
155
+ throw new Error('Missing or empty message in PreKeyWhisperMessage');
156
+ }
157
+ if (preKeyProto.signedPreKeyId == null) {
158
+ throw new Error('Missing signedPreKeyId in PreKeyWhisperMessage');
159
+ }
160
+ if (preKeyProto.registrationId == null) {
161
+ throw new Error('Missing registrationId in PreKeyWhisperMessage');
162
+ }
139
163
  if (!record) {
140
- if (preKeyProto.registrationId == null)
141
- throw new Error('No registrationId');
142
164
  record = new SessionRecord();
143
165
  }
144
166
  const builder = new SessionBuilder(this.storage, this.addr);
@@ -175,19 +197,25 @@ export class SessionCipher {
175
197
  if (!sessions.length)
176
198
  throw new SessionError('No sessions available');
177
199
  const errors = [];
178
- for (const session of sessions) {
200
+ for (let i = 0; i < sessions.length; i++) {
201
+ const session = sessions[i];
202
+ const sessionKey = toBase64(session.indexInfo.baseKey);
179
203
  try {
180
204
  const plaintext = await this.doDecryptWhisperMessage(data, session);
181
205
  session.indexInfo.used = Date.now();
182
206
  return { session, plaintext };
183
207
  }
184
208
  catch (e) {
185
- errors.push(e);
209
+ errors.push({
210
+ sessionKey,
211
+ error: e instanceof Error ? e : new Error(String(e))
212
+ });
186
213
  }
187
214
  }
188
- console.error('Failed to decrypt with any session');
189
- errors.forEach(e => console.error(e));
190
- throw new SessionError('No matching sessions found for message');
215
+ const errorDetails = errors
216
+ .map(({ sessionKey, error }) => `[${sessionKey}]: ${error.message}`)
217
+ .join(' | ');
218
+ throw new SessionError(`No matching sessions found for message. Tried ${sessions.length} sessions. Errors: ${errorDetails}`, { cause: errors[0]?.error });
191
219
  }
192
220
  async doDecryptWhisperMessage(messageBuffer, session) {
193
221
  assertUint8(messageBuffer);
@@ -231,8 +259,13 @@ export class SessionCipher {
231
259
  fillMessageKeys(chain, targetCounter) {
232
260
  if (chain.chainKey.counter >= targetCounter)
233
261
  return;
234
- if (targetCounter - chain.chainKey.counter > 2000) {
235
- throw new SessionError('Over 2000 messages into the future!');
262
+ const maxFutureMessages = 2000;
263
+ const newMessages = targetCounter - chain.chainKey.counter;
264
+ if (newMessages > maxFutureMessages) {
265
+ throw new SessionError(`Over ${maxFutureMessages} messages into the future: ${newMessages} messages`);
266
+ }
267
+ if (targetCounter > Number.MAX_SAFE_INTEGER - 1000) {
268
+ throw new SessionError(`Counter would overflow: current=${chain.chainKey.counter}, target=${targetCounter}`);
236
269
  }
237
270
  if (chain.chainKey.key === undefined) {
238
271
  throw new SessionError('Chain closed');
@@ -283,18 +316,34 @@ export class SessionCipher {
283
316
  ratchet.rootKey = masterKeys[0];
284
317
  }
285
318
  deriveSecrets(input, salt, info, chunks = 3) {
286
- const hkdf = crypto.hkdf(input, salt, info, { length: chunks * 32 });
319
+ const expectedLength = chunks * 32;
320
+ const hkdf = crypto.hkdf(input, salt, info, { length: expectedLength });
321
+ if (hkdf.length !== expectedLength) {
322
+ throw new Error(`HKDF derivation failed: expected ${expectedLength} bytes, got ${hkdf.length}`);
323
+ }
287
324
  const result = [];
288
325
  for (let i = 0; i < chunks; i++) {
289
- result.push(hkdf.subarray(i * 32, (i + 1) * 32));
326
+ const key = hkdf.subarray(i * 32, (i + 1) * 32);
327
+ if (key.length !== 32) {
328
+ throw new Error(`Invalid key derivation at chunk ${i}: expected 32 bytes, got ${key.length}`);
329
+ }
330
+ result.push(key);
290
331
  }
291
332
  return result;
292
333
  }
293
334
  verifyMAC(macInput, key, mac, length) {
335
+ if (mac.length < length) {
336
+ throw new Error('MAC too short');
337
+ }
294
338
  const computed = crypto.hmacSha256(key, macInput);
339
+ let match = true;
295
340
  for (let i = 0; i < length; i++) {
296
- if (computed[i] !== mac[i])
297
- throw new Error('MAC verification failed');
341
+ if (computed[i] !== mac[i]) {
342
+ match = false;
343
+ }
344
+ }
345
+ if (!match) {
346
+ throw new Error('MAC verification failed');
298
347
  }
299
348
  }
300
349
  }
@@ -1,5 +1,5 @@
1
- import type { SessionEntry } from "../record/session-entry.js";
2
- import type { SessionRecord } from "../record/session-record.js";
1
+ import type { SessionEntry } from "../record/session-entry";
2
+ import type { SessionRecord } from "../record/session-record";
3
3
  export interface EncryptResult {
4
4
  type: number;
5
5
  body: Uint8Array;
@@ -1,3 +1,5 @@
1
- export * from "./cipher/index.js";
2
- export * from "./record/index.js";
3
- export * from "./builder/index.js";
1
+ export { WhisperMessageEncoder } from "./cipher/index";
2
+ export * from "./cipher/index";
3
+ export * from "./record/index";
4
+ export * from "./builder/index";
5
+ export * from "./storage/index";
@@ -1,3 +1,5 @@
1
- export * from "./cipher/index.js";
2
- export * from "./record/index.js";
3
- export * from "./builder/index.js";
1
+ export { WhisperMessageEncoder } from "./cipher/index";
2
+ export * from "./cipher/index";
3
+ export * from "./record/index";
4
+ export * from "./builder/index";
5
+ export * from "./storage/index";
@@ -1,3 +1,3 @@
1
- export * from './types.js';
2
- export * from './session-entry.js';
3
- export * from './session-record.js';
1
+ export * from './types';
2
+ export * from './session-entry';
3
+ export * from './session-record';
@@ -1,3 +1,3 @@
1
- export * from './types.js';
2
- export * from './session-entry.js';
3
- export * from './session-record.js';
1
+ export * from './types';
2
+ export * from './session-entry';
3
+ export * from './session-record';
@@ -1,4 +1,4 @@
1
- import type { ChainState, CurrentRatchet, IndexInfo, PendingPreKey, SerializedSessionEntry } from './types.js';
1
+ import type { ChainState, CurrentRatchet, IndexInfo, PendingPreKey, SerializedSessionEntry } from './types';
2
2
  export declare class SessionEntry {
3
3
  registrationId: number;
4
4
  currentRatchet: CurrentRatchet;
@@ -1,4 +1,4 @@
1
- import { assertUint8, toBase64, u8 } from '../utils.js';
1
+ import { assertUint8, toBase64, fromBase64, u8 } from '../utils';
2
2
  export class SessionEntry {
3
3
  registrationId;
4
4
  currentRatchet;
@@ -113,7 +113,7 @@ export class SessionEntry {
113
113
  return {
114
114
  baseKey: u8.decode(data.baseKey),
115
115
  signedKeyId: data.signedKeyId,
116
- preKeyId: data.preKeyId,
116
+ ...(data.preKeyId !== undefined && { preKeyId: data.preKeyId }),
117
117
  };
118
118
  }
119
119
  static deserialize(data) {
@@ -143,4 +143,3 @@ export class SessionEntry {
143
143
  return obj;
144
144
  }
145
145
  }
146
- import { fromBase64 } from '../utils.js';
@@ -1,5 +1,5 @@
1
- import type { SerializedSessionRecord } from './types.js';
2
- import { SessionEntry } from './session-entry.js';
1
+ import type { SerializedSessionRecord } from './types';
2
+ import { SessionEntry } from './session-entry';
3
3
  export declare class SessionRecord {
4
4
  sessions: Record<string, SessionEntry>;
5
5
  version: string;
@@ -1,7 +1,7 @@
1
- import { SessionEntry } from './session-entry.js';
2
- import { CLOSED_SESSIONS_MAX, SESSION_RECORD_VERSION } from '../constants.js';
3
- import { assertUint8, toBase64 } from '../utils.js';
4
- import { BaseKeyType } from '../../base_key_type.js';
1
+ import { SessionEntry } from './session-entry';
2
+ import { CLOSED_SESSIONS_MAX, SESSION_RECORD_VERSION } from '../constants';
3
+ import { assertUint8, toBase64 } from '../utils';
4
+ import { BaseKeyType } from "../../ratchet-types";
5
5
  export class SessionRecord {
6
6
  sessions = {};
7
7
  version = SESSION_RECORD_VERSION;
@@ -1,4 +1,4 @@
1
- import type { BaseKeyType } from "../../base_key_type.js";
1
+ import type { BaseKeyType, ChainType } from "../../ratchet-types";
2
2
  export interface PendingPreKey {
3
3
  baseKey: Uint8Array;
4
4
  signedKeyId: number;
@@ -10,7 +10,7 @@ export interface ChainKey {
10
10
  }
11
11
  export interface ChainState {
12
12
  chainKey: ChainKey;
13
- chainType: number;
13
+ chainType: ChainType;
14
14
  messageKeys: Record<string, Uint8Array>;
15
15
  }
16
16
  export interface CurrentRatchet {
@@ -35,7 +35,7 @@ export interface SerializedChainState {
35
35
  counter: number;
36
36
  key: string | undefined;
37
37
  };
38
- chainType: number;
38
+ chainType: ChainType;
39
39
  messageKeys: Record<string, string>;
40
40
  }
41
41
  export interface SerializedPendingPreKey {
@@ -0,0 +1,21 @@
1
+ import { StorageAdapter } from './types';
2
+ import { SessionRecord } from '../record/index';
3
+ export declare function createSessionStorage(adapter: StorageAdapter): {
4
+ isTrustedIdentity(addressName: string, identityKey: Uint8Array): Promise<boolean>;
5
+ loadSession(addressName: string): Promise<SessionRecord | undefined>;
6
+ storeSession(addressName: string, record: ReturnType<(typeof SessionRecord)["prototype"]["serialize"]>): Promise<void>;
7
+ getOurIdentity(): Promise<{
8
+ pubKey: Uint8Array<ArrayBufferLike>;
9
+ privKey: Uint8Array<ArrayBufferLike>;
10
+ }>;
11
+ loadPreKey(preKeyId: number): Promise<{
12
+ pubKey: Uint8Array<ArrayBufferLike>;
13
+ privKey: Uint8Array<ArrayBufferLike>;
14
+ } | undefined>;
15
+ loadSignedPreKey(signedPreKeyId: number): Promise<{
16
+ pubKey: Uint8Array<ArrayBufferLike>;
17
+ privKey: Uint8Array<ArrayBufferLike>;
18
+ } | undefined>;
19
+ removePreKey(preKeyId: number): Promise<void>;
20
+ getOurRegistrationId(): Promise<number>;
21
+ };
@@ -0,0 +1,57 @@
1
+ import { SessionRecord } from '../record/index';
2
+ import { toBase64, fromBase64 } from '../utils';
3
+ function sessionKey(addr) { return `session:${addr}`; }
4
+ function preKeyKey(id) { return `prekey:${id}`; }
5
+ function signedPreKeyKey(id) { return `signedprekey:${id}`; }
6
+ function identityKeyF(addr) { return `identity:${addr}`; }
7
+ const OUR_IDENTITY = 'our_identity';
8
+ const REG_ID = 'registration_id';
9
+ export function createSessionStorage(adapter) {
10
+ return {
11
+ async isTrustedIdentity(addressName, identityKey) {
12
+ const stored = await adapter.get(identityKeyF(addressName));
13
+ const encoded = toBase64(identityKey);
14
+ if (!stored) {
15
+ await adapter.set(identityKeyF(addressName), encoded);
16
+ return true;
17
+ }
18
+ return stored === encoded;
19
+ },
20
+ async loadSession(addressName) {
21
+ const data = await adapter.get(sessionKey(addressName));
22
+ if (!data)
23
+ return undefined;
24
+ return SessionRecord.deserialize(data);
25
+ },
26
+ async storeSession(addressName, record) {
27
+ await adapter.set(sessionKey(addressName), record);
28
+ },
29
+ async getOurIdentity() {
30
+ const data = await adapter.get(OUR_IDENTITY);
31
+ if (!data)
32
+ throw new Error('Our identity not found in storage');
33
+ return { pubKey: fromBase64(data.pubKey), privKey: fromBase64(data.privKey) };
34
+ },
35
+ async loadPreKey(preKeyId) {
36
+ const data = await adapter.get(preKeyKey(preKeyId));
37
+ if (!data)
38
+ return undefined;
39
+ return { pubKey: fromBase64(data.pubKey), privKey: fromBase64(data.privKey) };
40
+ },
41
+ async loadSignedPreKey(signedPreKeyId) {
42
+ const data = await adapter.get(signedPreKeyKey(signedPreKeyId));
43
+ if (!data)
44
+ return undefined;
45
+ return { pubKey: fromBase64(data.pubKey), privKey: fromBase64(data.privKey) };
46
+ },
47
+ async removePreKey(preKeyId) {
48
+ await adapter.delete(preKeyKey(preKeyId));
49
+ },
50
+ async getOurRegistrationId() {
51
+ const v = await adapter.get(REG_ID);
52
+ if (typeof v !== 'number')
53
+ throw new Error('registration id missing');
54
+ return v;
55
+ }
56
+ };
57
+ }
@@ -0,0 +1,13 @@
1
+ import { StorageAdapter, BatchOp } from './types';
2
+ export declare class InMemoryStorage implements StorageAdapter {
3
+ private store;
4
+ name: string;
5
+ constructor(initial?: Record<string, any>);
6
+ get<T = any>(key: string): Promise<T | undefined>;
7
+ set<T = any>(key: string, value: T): Promise<void>;
8
+ delete(key: string): Promise<void>;
9
+ batch(ops: BatchOp[]): Promise<void>;
10
+ clear(): Promise<void>;
11
+ close(): Promise<void>;
12
+ }
13
+ export declare function createInMemoryStorage(initial?: Record<string, any>): InMemoryStorage;
@@ -0,0 +1,33 @@
1
+ export class InMemoryStorage {
2
+ store = new Map();
3
+ name = 'in-memory';
4
+ constructor(initial) {
5
+ if (initial)
6
+ Object.entries(initial).forEach(([k, v]) => this.store.set(k, v));
7
+ }
8
+ async get(key) {
9
+ return this.store.has(key) ? this.store.get(key) : undefined;
10
+ }
11
+ async set(key, value) {
12
+ this.store.set(key, value);
13
+ }
14
+ async delete(key) {
15
+ this.store.delete(key);
16
+ }
17
+ async batch(ops) {
18
+ for (const op of ops) {
19
+ if (op.type === 'put')
20
+ this.store.set(op.key, op.value);
21
+ else
22
+ this.store.delete(op.key);
23
+ }
24
+ }
25
+ async clear() {
26
+ this.store.clear();
27
+ }
28
+ async close() {
29
+ }
30
+ }
31
+ export function createInMemoryStorage(initial) {
32
+ return new InMemoryStorage(initial);
33
+ }
@@ -0,0 +1,19 @@
1
+ export * from './types';
2
+ export * from './in-memory';
3
+ export * from './migrations';
4
+ export * from './adapter';
5
+ import { StorageAdapter } from './types';
6
+ export declare class StorageManager {
7
+ adapter: StorageAdapter;
8
+ constructor(adapter: StorageAdapter);
9
+ get<T = any>(key: string): Promise<T | undefined>;
10
+ set<T = any>(key: string, value: T): Promise<void>;
11
+ delete(key: string): Promise<void>;
12
+ batch(ops: Array<{
13
+ type: 'put' | 'del';
14
+ key: string;
15
+ value?: any;
16
+ }>): Promise<void>;
17
+ close(): Promise<void>;
18
+ }
19
+ export declare function createStorageManager(adapter: StorageAdapter): StorageManager;
@@ -0,0 +1,29 @@
1
+ export * from './types';
2
+ export * from './in-memory';
3
+ export * from './migrations';
4
+ export * from './adapter';
5
+ export class StorageManager {
6
+ adapter;
7
+ constructor(adapter) {
8
+ this.adapter = adapter;
9
+ }
10
+ get(key) {
11
+ return this.adapter.get(key);
12
+ }
13
+ set(key, value) {
14
+ return this.adapter.set(key, value);
15
+ }
16
+ delete(key) {
17
+ return this.adapter.delete(key);
18
+ }
19
+ batch(ops) {
20
+ return this.adapter.batch(ops);
21
+ }
22
+ async close() {
23
+ if (this.adapter.close)
24
+ return this.adapter.close();
25
+ }
26
+ }
27
+ export function createStorageManager(adapter) {
28
+ return new StorageManager(adapter);
29
+ }
@@ -0,0 +1,2 @@
1
+ import { StorageAdapter } from './types';
2
+ export declare function runMigrations(adapter: StorageAdapter, fromVersion: number, toVersion: number): Promise<void>;
@@ -0,0 +1,6 @@
1
+ export async function runMigrations(adapter, fromVersion, toVersion) {
2
+ if (adapter.migrate) {
3
+ await adapter.migrate(fromVersion, toVersion);
4
+ return;
5
+ }
6
+ }
@@ -0,0 +1,18 @@
1
+ export type BatchOp = {
2
+ type: 'put' | 'del';
3
+ key: string;
4
+ value?: any;
5
+ };
6
+ export interface StorageAdapter {
7
+ get<T = any>(key: string): Promise<T | undefined>;
8
+ set<T = any>(key: string, value: T): Promise<void>;
9
+ delete(key: string): Promise<void>;
10
+ batch(ops: BatchOp[]): Promise<void>;
11
+ clear?(): Promise<void>;
12
+ close?(): Promise<void>;
13
+ name?: string;
14
+ migrate?(fromVersion: number, toVersion: number): Promise<void>;
15
+ }
16
+ export interface StorageManagerOptions {
17
+ adapter: StorageAdapter;
18
+ }
@@ -0,0 +1,41 @@
1
+ export interface IdentityKeyPair {
2
+ readonly publicKey: Uint8Array // 32 bytes Ed25519
3
+ readonly privateKey: Uint8Array // 64 bytes Ed25519
4
+ }
5
+
6
+ export interface DHKeyPair {
7
+ readonly publicKey: Uint8Array // 32 bytes X25519
8
+ readonly privateKey: Uint8Array // 32 bytes X25519
9
+ }
10
+
11
+ export interface SignalAsymmetricAPI {
12
+ /* Identity (Ed25519) */
13
+ generateIdentityKeyPair(): Promise<IdentityKeyPair>
14
+ sign(
15
+ privateKey: Uint8Array,
16
+ message: Uint8Array
17
+ ): Uint8Array
18
+ verify(
19
+ publicKey: Uint8Array,
20
+ message: Uint8Array,
21
+ signature: Uint8Array
22
+ ): boolean
23
+
24
+ /* DH (X25519) */
25
+ generateDHKeyPair(): Promise<DHKeyPair>
26
+ calculateAgreement(
27
+ publicKey: Uint8Array,
28
+ privateKey: Uint8Array
29
+ ): Uint8Array
30
+
31
+ /* Conversion */
32
+ convertIdentityPublicToX25519(
33
+ edPublicKey: Uint8Array
34
+ ): Uint8Array
35
+
36
+ convertIdentityPrivateToX25519(
37
+ edPrivateKey: Uint8Array
38
+ ): Uint8Array
39
+ }
40
+
41
+ export const signalCrypto: SignalAsymmetricAPI