@haven-chat-org/core 1.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.
- package/LICENSE +661 -0
- package/README.md +71 -0
- package/dist/crypto/backup.d.ts +87 -0
- package/dist/crypto/backup.js +62 -0
- package/dist/crypto/backup.js.map +1 -0
- package/dist/crypto/double-ratchet.d.ts +104 -0
- package/dist/crypto/double-ratchet.js +274 -0
- package/dist/crypto/double-ratchet.js.map +1 -0
- package/dist/crypto/file.d.ts +14 -0
- package/dist/crypto/file.js +20 -0
- package/dist/crypto/file.js.map +1 -0
- package/dist/crypto/index.d.ts +9 -0
- package/dist/crypto/index.js +10 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/keys.d.ts +61 -0
- package/dist/crypto/keys.js +79 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/crypto/passphrase.d.ts +10 -0
- package/dist/crypto/passphrase.js +142 -0
- package/dist/crypto/passphrase.js.map +1 -0
- package/dist/crypto/profile.d.ts +31 -0
- package/dist/crypto/profile.js +73 -0
- package/dist/crypto/profile.js.map +1 -0
- package/dist/crypto/sender-keys.d.ts +76 -0
- package/dist/crypto/sender-keys.js +170 -0
- package/dist/crypto/sender-keys.js.map +1 -0
- package/dist/crypto/sender-keys.test.d.ts +1 -0
- package/dist/crypto/sender-keys.test.js +272 -0
- package/dist/crypto/sender-keys.test.js.map +1 -0
- package/dist/crypto/utils.d.ts +41 -0
- package/dist/crypto/utils.js +102 -0
- package/dist/crypto/utils.js.map +1 -0
- package/dist/crypto/x3dh.d.ts +45 -0
- package/dist/crypto/x3dh.js +106 -0
- package/dist/crypto/x3dh.js.map +1 -0
- package/dist/export/__tests__/archive.test.d.ts +1 -0
- package/dist/export/__tests__/archive.test.js +276 -0
- package/dist/export/__tests__/archive.test.js.map +1 -0
- package/dist/export/archive.d.ts +38 -0
- package/dist/export/archive.js +107 -0
- package/dist/export/archive.js.map +1 -0
- package/dist/export/index.d.ts +4 -0
- package/dist/export/index.js +4 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/reader.d.ts +27 -0
- package/dist/export/reader.js +101 -0
- package/dist/export/reader.js.map +1 -0
- package/dist/export/signing.d.ts +15 -0
- package/dist/export/signing.js +44 -0
- package/dist/export/signing.js.map +1 -0
- package/dist/export/types.d.ts +128 -0
- package/dist/export/types.js +3 -0
- package/dist/export/types.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/net/api.d.ts +200 -0
- package/dist/net/api.js +715 -0
- package/dist/net/api.js.map +1 -0
- package/dist/net/api.test.d.ts +1 -0
- package/dist/net/api.test.js +884 -0
- package/dist/net/api.test.js.map +1 -0
- package/dist/net/index.d.ts +2 -0
- package/dist/net/index.js +3 -0
- package/dist/net/index.js.map +1 -0
- package/dist/net/ws.d.ts +71 -0
- package/dist/net/ws.js +257 -0
- package/dist/net/ws.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/memory.d.ts +24 -0
- package/dist/store/memory.js +50 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/types.d.ts +23 -0
- package/dist/store/types.js +2 -0
- package/dist/store/types.js.map +1 -0
- package/dist/types.d.ts +850 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# haven-core
|
|
2
|
+
|
|
3
|
+
Shared TypeScript library providing cryptographic primitives, API client, and type definitions used by the web frontend (and any future clients).
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── types.ts # All shared TypeScript interfaces and types
|
|
10
|
+
├── index.ts # Public API re-exports
|
|
11
|
+
├── crypto/
|
|
12
|
+
│ ├── utils.ts # libsodium init, base64 helpers, key generation
|
|
13
|
+
│ ├── x3dh.ts # X3DH key agreement (initiator + responder)
|
|
14
|
+
│ ├── double-ratchet.ts # Double Ratchet session (DM encryption)
|
|
15
|
+
│ ├── sender-keys.ts # Sender Keys protocol (group channel encryption)
|
|
16
|
+
│ ├── file-crypto.ts # XChaCha20-Poly1305 file encryption
|
|
17
|
+
│ └── store.ts # MemoryStore for key material (in-memory only)
|
|
18
|
+
├── net/
|
|
19
|
+
│ ├── api.ts # HavenApi — type-safe REST client with JWT management
|
|
20
|
+
│ └── ws.ts # HavenWs — WebSocket client with auto-reconnect
|
|
21
|
+
└── store/
|
|
22
|
+
└── memory-store.ts # In-memory key/session storage
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## E2EE Model
|
|
26
|
+
|
|
27
|
+
### DMs (X3DH + Double Ratchet)
|
|
28
|
+
1. Initiator fetches recipient's key bundle (identity key + signed prekey + one-time prekey)
|
|
29
|
+
2. X3DH key agreement produces a shared secret
|
|
30
|
+
3. Double Ratchet session provides forward secrecy and break-in recovery
|
|
31
|
+
4. Wire format: `[0x01 or 0x02][encrypted payload]`
|
|
32
|
+
|
|
33
|
+
### Server Channels (Sender Keys)
|
|
34
|
+
1. Each user generates a sender key per channel
|
|
35
|
+
2. Sender Key Distribution Messages (SKDMs) are encrypted to each member's identity key via `crypto_box_seal`
|
|
36
|
+
3. Wire format: `[0x03][distributionId:16][chainIndex:4 LE][nonce:24][ciphertext+tag]`
|
|
37
|
+
|
|
38
|
+
### Files
|
|
39
|
+
- XChaCha20-Poly1305 client-side encryption
|
|
40
|
+
- Key and nonce embedded in the message payload (encrypted along with the message)
|
|
41
|
+
- File sizes are padded to prevent type inference
|
|
42
|
+
|
|
43
|
+
## Building
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install
|
|
47
|
+
npm run build # Compiles to dist/ via tsc
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Important**: The web frontend imports from `dist/`, not `src/`. You must rebuild haven-core after any changes for the frontend to pick them up.
|
|
51
|
+
|
|
52
|
+
## Testing
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx vitest run # 78 tests
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API Client
|
|
59
|
+
|
|
60
|
+
The `HavenApi` class handles JWT token management, automatic PoW solving for registration, and typed request/response for every endpoint:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { HavenApi } from '@haven/core';
|
|
64
|
+
|
|
65
|
+
const api = new HavenApi({ baseUrl: 'https://chat.example.com' });
|
|
66
|
+
await api.register({ username, password, ...keys });
|
|
67
|
+
await api.login({ username, password });
|
|
68
|
+
const servers = await api.listServers();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Covers all Haven API areas: auth, servers, channels, messages, sender keys, roles, friends, invites, voice (join/leave/participants/mute/deafen), attachments (upload/download), link previews, admin, and user management.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypted key backup — derives a symmetric key from a security phrase
|
|
3
|
+
* using Argon2id, then encrypts all crypto state with XSalsa20-Poly1305.
|
|
4
|
+
*/
|
|
5
|
+
export interface BackupEncryptResult {
|
|
6
|
+
encrypted: Uint8Array;
|
|
7
|
+
nonce: Uint8Array;
|
|
8
|
+
salt: Uint8Array;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The plaintext JSON structure that gets encrypted into the backup blob.
|
|
12
|
+
*/
|
|
13
|
+
export interface BackupPayload {
|
|
14
|
+
version: 1;
|
|
15
|
+
identity: {
|
|
16
|
+
publicKey: string;
|
|
17
|
+
privateKey: string;
|
|
18
|
+
};
|
|
19
|
+
signedPreKey: {
|
|
20
|
+
publicKey: string;
|
|
21
|
+
privateKey: string;
|
|
22
|
+
signature: string;
|
|
23
|
+
};
|
|
24
|
+
sessions: Record<string, {
|
|
25
|
+
state: SerializedSessionState;
|
|
26
|
+
ad: string;
|
|
27
|
+
}>;
|
|
28
|
+
mySenderKeys: Record<string, {
|
|
29
|
+
distributionId: string;
|
|
30
|
+
chainKey: string;
|
|
31
|
+
chainIndex: number;
|
|
32
|
+
}>;
|
|
33
|
+
receivedSenderKeys: Record<string, {
|
|
34
|
+
fromUserId: string;
|
|
35
|
+
key: {
|
|
36
|
+
distributionId: string;
|
|
37
|
+
chainKey: string;
|
|
38
|
+
chainIndex: number;
|
|
39
|
+
};
|
|
40
|
+
}>;
|
|
41
|
+
distributedChannels: string[];
|
|
42
|
+
channelPeerMap: Record<string, string>;
|
|
43
|
+
timestamp: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* SessionState with Uint8Arrays serialized to base64 strings for JSON safety.
|
|
47
|
+
*/
|
|
48
|
+
export interface SerializedSessionState {
|
|
49
|
+
dhSend: {
|
|
50
|
+
publicKey: string;
|
|
51
|
+
privateKey: string;
|
|
52
|
+
};
|
|
53
|
+
dhRecv: string | null;
|
|
54
|
+
rootKey: string;
|
|
55
|
+
chainKeySend: string | null;
|
|
56
|
+
chainKeyRecv: string | null;
|
|
57
|
+
sendCount: number;
|
|
58
|
+
recvCount: number;
|
|
59
|
+
prevSendCount: number;
|
|
60
|
+
skippedKeys: Array<{
|
|
61
|
+
dhPub: string;
|
|
62
|
+
n: number;
|
|
63
|
+
mk: string;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Derive a 32-byte symmetric key from a security phrase using Argon2id.
|
|
68
|
+
*/
|
|
69
|
+
export declare function deriveBackupKey(securityPhrase: string, salt: Uint8Array): Uint8Array;
|
|
70
|
+
/**
|
|
71
|
+
* Encrypt a backup payload with a security phrase.
|
|
72
|
+
* Generates a fresh salt and nonce.
|
|
73
|
+
*/
|
|
74
|
+
export declare function encryptBackup(plaintext: Uint8Array, securityPhrase: string): BackupEncryptResult;
|
|
75
|
+
/**
|
|
76
|
+
* Decrypt a backup payload using a security phrase, salt, and nonce.
|
|
77
|
+
* Throws if the phrase is wrong (authentication failure).
|
|
78
|
+
*/
|
|
79
|
+
export declare function decryptBackup(encrypted: Uint8Array, nonce: Uint8Array, salt: Uint8Array, securityPhrase: string): Uint8Array;
|
|
80
|
+
/**
|
|
81
|
+
* Generate a cryptographically random recovery key as a human-readable
|
|
82
|
+
* string (groups of 4 base32 characters separated by dashes).
|
|
83
|
+
*
|
|
84
|
+
* Example: "ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ23-4567"
|
|
85
|
+
* 20 random bytes = 160 bits of entropy.
|
|
86
|
+
*/
|
|
87
|
+
export declare function generateRecoveryKey(): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypted key backup — derives a symmetric key from a security phrase
|
|
3
|
+
* using Argon2id, then encrypts all crypto state with XSalsa20-Poly1305.
|
|
4
|
+
*/
|
|
5
|
+
import { getSodium, randomBytes } from "./utils.js";
|
|
6
|
+
// Argon2id parameters — OWASP recommended for interactive hashing
|
|
7
|
+
const ARGON2_OPSLIMIT = 3; // crypto_pwhash_OPSLIMIT_MODERATE
|
|
8
|
+
const ARGON2_MEMLIMIT = 67108864; // 64 MB — safe for browsers (256MB can OOM on mobile)
|
|
9
|
+
const SALT_BYTES = 16; // crypto_pwhash_SALTBYTES
|
|
10
|
+
/**
|
|
11
|
+
* Derive a 32-byte symmetric key from a security phrase using Argon2id.
|
|
12
|
+
*/
|
|
13
|
+
export function deriveBackupKey(securityPhrase, salt) {
|
|
14
|
+
const sodium = getSodium();
|
|
15
|
+
return sodium.crypto_pwhash(sodium.crypto_secretbox_KEYBYTES, // 32
|
|
16
|
+
securityPhrase, salt, ARGON2_OPSLIMIT, ARGON2_MEMLIMIT, sodium.crypto_pwhash_ALG_ARGON2ID13);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Encrypt a backup payload with a security phrase.
|
|
20
|
+
* Generates a fresh salt and nonce.
|
|
21
|
+
*/
|
|
22
|
+
export function encryptBackup(plaintext, securityPhrase) {
|
|
23
|
+
const sodium = getSodium();
|
|
24
|
+
const salt = randomBytes(SALT_BYTES);
|
|
25
|
+
const key = deriveBackupKey(securityPhrase, salt);
|
|
26
|
+
const nonce = randomBytes(sodium.crypto_secretbox_NONCEBYTES); // 24 bytes
|
|
27
|
+
const encrypted = sodium.crypto_secretbox_easy(plaintext, nonce, key);
|
|
28
|
+
return { encrypted, nonce, salt };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Decrypt a backup payload using a security phrase, salt, and nonce.
|
|
32
|
+
* Throws if the phrase is wrong (authentication failure).
|
|
33
|
+
*/
|
|
34
|
+
export function decryptBackup(encrypted, nonce, salt, securityPhrase) {
|
|
35
|
+
const sodium = getSodium();
|
|
36
|
+
const key = deriveBackupKey(securityPhrase, salt);
|
|
37
|
+
return sodium.crypto_secretbox_open_easy(encrypted, nonce, key);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Generate a cryptographically random recovery key as a human-readable
|
|
41
|
+
* string (groups of 4 base32 characters separated by dashes).
|
|
42
|
+
*
|
|
43
|
+
* Example: "ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ23-4567"
|
|
44
|
+
* 20 random bytes = 160 bits of entropy.
|
|
45
|
+
*/
|
|
46
|
+
export function generateRecoveryKey() {
|
|
47
|
+
const bytes = randomBytes(20);
|
|
48
|
+
// Encode as base32 for human readability
|
|
49
|
+
const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
50
|
+
let bits = "";
|
|
51
|
+
for (const b of bytes) {
|
|
52
|
+
bits += b.toString(2).padStart(8, "0");
|
|
53
|
+
}
|
|
54
|
+
let encoded = "";
|
|
55
|
+
for (let i = 0; i + 5 <= bits.length; i += 5) {
|
|
56
|
+
encoded += base32Chars[parseInt(bits.slice(i, i + 5), 2)];
|
|
57
|
+
}
|
|
58
|
+
// Format into groups of 4
|
|
59
|
+
const groups = encoded.match(/.{1,4}/g) || [];
|
|
60
|
+
return groups.join("-");
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/crypto/backup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAwB,WAAW,EAAE,MAAM,YAAY,CAAC;AAE1E,kEAAkE;AAClE,MAAM,eAAe,GAAG,CAAC,CAAC,CAAO,kCAAkC;AACnE,MAAM,eAAe,GAAG,QAAQ,CAAC,CAAC,sDAAsD;AACxF,MAAM,UAAU,GAAG,EAAE,CAAC,CAAY,0BAA0B;AA2D5D;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,cAAsB,EACtB,IAAgB;IAEhB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,aAAa,CACzB,MAAM,CAAC,yBAAyB,EAAE,KAAK;IACvC,cAAc,EACd,IAAI,EACJ,eAAe,EACf,eAAe,EACf,MAAM,CAAC,4BAA4B,CACpC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,SAAqB,EACrB,cAAsB;IAEtB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,eAAe,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC,CAAC,WAAW;IAC1E,MAAM,SAAS,GAAG,MAAM,CAAC,qBAAqB,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACtE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,SAAqB,EACrB,KAAiB,EACjB,IAAgB,EAChB,cAAsB;IAEtB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,eAAe,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC,0BAA0B,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,yCAAyC;IACzC,MAAM,WAAW,GAAG,kCAAkC,CAAC;IACvD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,0BAA0B;IAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAC9C,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signal Protocol Double Ratchet implementation.
|
|
3
|
+
*
|
|
4
|
+
* Provides forward secrecy and break-in recovery for 1-on-1 DM sessions.
|
|
5
|
+
* After X3DH establishes a shared secret, this module handles all subsequent
|
|
6
|
+
* message encryption and decryption.
|
|
7
|
+
*
|
|
8
|
+
* References:
|
|
9
|
+
* - https://signal.org/docs/specifications/doubleratchet/
|
|
10
|
+
* - https://signal.org/docs/specifications/x3dh/
|
|
11
|
+
*/
|
|
12
|
+
import { type DHKeyPair } from "./keys.js";
|
|
13
|
+
/** Message header sent in the clear (alongside ciphertext). */
|
|
14
|
+
export interface MessageHeader {
|
|
15
|
+
dhPublicKey: Uint8Array;
|
|
16
|
+
pn: number;
|
|
17
|
+
n: number;
|
|
18
|
+
}
|
|
19
|
+
/** An encrypted Double Ratchet message ready for the wire. */
|
|
20
|
+
export interface EncryptedMessage {
|
|
21
|
+
header: MessageHeader;
|
|
22
|
+
nonce: Uint8Array;
|
|
23
|
+
ciphertext: Uint8Array;
|
|
24
|
+
}
|
|
25
|
+
/** Serializable session state for persistence. */
|
|
26
|
+
export interface SessionState {
|
|
27
|
+
dhSend: {
|
|
28
|
+
publicKey: Uint8Array;
|
|
29
|
+
privateKey: Uint8Array;
|
|
30
|
+
};
|
|
31
|
+
dhRecv: Uint8Array | null;
|
|
32
|
+
rootKey: Uint8Array;
|
|
33
|
+
chainKeySend: Uint8Array | null;
|
|
34
|
+
chainKeyRecv: Uint8Array | null;
|
|
35
|
+
sendCount: number;
|
|
36
|
+
recvCount: number;
|
|
37
|
+
prevSendCount: number;
|
|
38
|
+
skippedKeys: Array<{
|
|
39
|
+
dhPub: Uint8Array;
|
|
40
|
+
n: number;
|
|
41
|
+
mk: Uint8Array;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
export declare class DoubleRatchetSession {
|
|
45
|
+
private dhSend;
|
|
46
|
+
private dhRecv;
|
|
47
|
+
private rootKey;
|
|
48
|
+
private chainKeySend;
|
|
49
|
+
private chainKeyRecv;
|
|
50
|
+
private sendCount;
|
|
51
|
+
private recvCount;
|
|
52
|
+
private prevSendCount;
|
|
53
|
+
private skippedKeys;
|
|
54
|
+
private associatedData;
|
|
55
|
+
private constructor();
|
|
56
|
+
/**
|
|
57
|
+
* Initialize as the INITIATOR (Alice).
|
|
58
|
+
* Called after X3DH produces a shared key.
|
|
59
|
+
*
|
|
60
|
+
* @param sharedKey The SK from X3DH (32 bytes)
|
|
61
|
+
* @param associatedData AD = IK_A || IK_B (64 bytes)
|
|
62
|
+
* @param bobSignedPreKeyPub Bob's signed prekey public key (the one from X3DH)
|
|
63
|
+
*/
|
|
64
|
+
static initAlice(sharedKey: Uint8Array, associatedData: Uint8Array, bobSignedPreKeyPub: Uint8Array): DoubleRatchetSession;
|
|
65
|
+
/**
|
|
66
|
+
* Initialize as the RESPONDER (Bob).
|
|
67
|
+
* Called after X3DH when Bob receives Alice's initial message.
|
|
68
|
+
*
|
|
69
|
+
* @param sharedKey The SK from X3DH (32 bytes)
|
|
70
|
+
* @param associatedData AD = IK_A || IK_B (64 bytes)
|
|
71
|
+
* @param bobSignedPreKeyPair Bob's signed prekey pair (used in X3DH)
|
|
72
|
+
*/
|
|
73
|
+
static initBob(sharedKey: Uint8Array, associatedData: Uint8Array, bobSignedPreKeyPair: DHKeyPair): DoubleRatchetSession;
|
|
74
|
+
/**
|
|
75
|
+
* Encrypt a plaintext message.
|
|
76
|
+
*/
|
|
77
|
+
encrypt(plaintext: Uint8Array): EncryptedMessage;
|
|
78
|
+
/**
|
|
79
|
+
* Decrypt a received message.
|
|
80
|
+
*/
|
|
81
|
+
decrypt(message: EncryptedMessage): Uint8Array;
|
|
82
|
+
private dhRatchet;
|
|
83
|
+
private skipKeys;
|
|
84
|
+
private trySkippedKeys;
|
|
85
|
+
private aedEncrypt;
|
|
86
|
+
private aedDecrypt;
|
|
87
|
+
/**
|
|
88
|
+
* Export session state for persistent storage.
|
|
89
|
+
*/
|
|
90
|
+
serialize(): SessionState;
|
|
91
|
+
/**
|
|
92
|
+
* Restore a session from serialized state.
|
|
93
|
+
*/
|
|
94
|
+
static deserialize(state: SessionState, associatedData: Uint8Array): DoubleRatchetSession;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Serialize an encrypted message to bytes for transmission.
|
|
98
|
+
* Format: [dh_pub:32][pn:4 LE][n:4 LE][nonce:24][ciphertext:rest]
|
|
99
|
+
*/
|
|
100
|
+
export declare function serializeMessage(msg: EncryptedMessage): Uint8Array;
|
|
101
|
+
/**
|
|
102
|
+
* Deserialize bytes back into an encrypted message.
|
|
103
|
+
*/
|
|
104
|
+
export declare function deserializeMessage(buf: Uint8Array): EncryptedMessage;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signal Protocol Double Ratchet implementation.
|
|
3
|
+
*
|
|
4
|
+
* Provides forward secrecy and break-in recovery for 1-on-1 DM sessions.
|
|
5
|
+
* After X3DH establishes a shared secret, this module handles all subsequent
|
|
6
|
+
* message encryption and decryption.
|
|
7
|
+
*
|
|
8
|
+
* References:
|
|
9
|
+
* - https://signal.org/docs/specifications/doubleratchet/
|
|
10
|
+
* - https://signal.org/docs/specifications/x3dh/
|
|
11
|
+
*/
|
|
12
|
+
import { dh, generateDHKeyPair } from "./keys.js";
|
|
13
|
+
import { getSodium, kdfRK, kdfCK, randomBytes } from "./utils.js";
|
|
14
|
+
// ─── Constants ─────────────────────────────────────────
|
|
15
|
+
const MAX_SKIP = 256;
|
|
16
|
+
const NONCE_LEN = 24; // XChaCha20-Poly1305
|
|
17
|
+
const TAG_LEN = 16; // Poly1305 auth tag
|
|
18
|
+
// ─── Session ───────────────────────────────────────────
|
|
19
|
+
export class DoubleRatchetSession {
|
|
20
|
+
dhSend;
|
|
21
|
+
dhRecv;
|
|
22
|
+
rootKey;
|
|
23
|
+
chainKeySend;
|
|
24
|
+
chainKeyRecv;
|
|
25
|
+
sendCount;
|
|
26
|
+
recvCount;
|
|
27
|
+
prevSendCount;
|
|
28
|
+
skippedKeys; // "hex(dhPub):n" -> messageKey
|
|
29
|
+
associatedData;
|
|
30
|
+
constructor(ad) {
|
|
31
|
+
this.dhSend = { publicKey: new Uint8Array(32), privateKey: new Uint8Array(32) };
|
|
32
|
+
this.dhRecv = null;
|
|
33
|
+
this.rootKey = new Uint8Array(32);
|
|
34
|
+
this.chainKeySend = null;
|
|
35
|
+
this.chainKeyRecv = null;
|
|
36
|
+
this.sendCount = 0;
|
|
37
|
+
this.recvCount = 0;
|
|
38
|
+
this.prevSendCount = 0;
|
|
39
|
+
this.skippedKeys = new Map();
|
|
40
|
+
this.associatedData = ad;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Initialize as the INITIATOR (Alice).
|
|
44
|
+
* Called after X3DH produces a shared key.
|
|
45
|
+
*
|
|
46
|
+
* @param sharedKey The SK from X3DH (32 bytes)
|
|
47
|
+
* @param associatedData AD = IK_A || IK_B (64 bytes)
|
|
48
|
+
* @param bobSignedPreKeyPub Bob's signed prekey public key (the one from X3DH)
|
|
49
|
+
*/
|
|
50
|
+
static initAlice(sharedKey, associatedData, bobSignedPreKeyPub) {
|
|
51
|
+
const session = new DoubleRatchetSession(associatedData);
|
|
52
|
+
session.dhSend = generateDHKeyPair();
|
|
53
|
+
session.dhRecv = bobSignedPreKeyPub;
|
|
54
|
+
// First ratchet step: derive initial sending chain key
|
|
55
|
+
const [rk, cks] = kdfRK(sharedKey, dh(session.dhSend.privateKey, session.dhRecv));
|
|
56
|
+
session.rootKey = rk;
|
|
57
|
+
session.chainKeySend = cks;
|
|
58
|
+
return session;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Initialize as the RESPONDER (Bob).
|
|
62
|
+
* Called after X3DH when Bob receives Alice's initial message.
|
|
63
|
+
*
|
|
64
|
+
* @param sharedKey The SK from X3DH (32 bytes)
|
|
65
|
+
* @param associatedData AD = IK_A || IK_B (64 bytes)
|
|
66
|
+
* @param bobSignedPreKeyPair Bob's signed prekey pair (used in X3DH)
|
|
67
|
+
*/
|
|
68
|
+
static initBob(sharedKey, associatedData, bobSignedPreKeyPair) {
|
|
69
|
+
const session = new DoubleRatchetSession(associatedData);
|
|
70
|
+
session.dhSend = bobSignedPreKeyPair;
|
|
71
|
+
session.rootKey = sharedKey;
|
|
72
|
+
// CKs and CKr are null — will be derived on first message
|
|
73
|
+
return session;
|
|
74
|
+
}
|
|
75
|
+
// ─── Encrypt ───────────────────────────────────────
|
|
76
|
+
/**
|
|
77
|
+
* Encrypt a plaintext message.
|
|
78
|
+
*/
|
|
79
|
+
encrypt(plaintext) {
|
|
80
|
+
if (!this.chainKeySend) {
|
|
81
|
+
throw new Error("Sending chain not initialized");
|
|
82
|
+
}
|
|
83
|
+
const [newCK, mk] = kdfCK(this.chainKeySend);
|
|
84
|
+
this.chainKeySend = newCK;
|
|
85
|
+
const header = {
|
|
86
|
+
dhPublicKey: this.dhSend.publicKey,
|
|
87
|
+
pn: this.prevSendCount,
|
|
88
|
+
n: this.sendCount,
|
|
89
|
+
};
|
|
90
|
+
this.sendCount++;
|
|
91
|
+
const { nonce, ciphertext } = this.aedEncrypt(mk, plaintext, header);
|
|
92
|
+
return { header, nonce, ciphertext };
|
|
93
|
+
}
|
|
94
|
+
// ─── Decrypt ───────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Decrypt a received message.
|
|
97
|
+
*/
|
|
98
|
+
decrypt(message) {
|
|
99
|
+
// Try skipped message keys first (out-of-order delivery)
|
|
100
|
+
const skippedResult = this.trySkippedKeys(message);
|
|
101
|
+
if (skippedResult)
|
|
102
|
+
return skippedResult;
|
|
103
|
+
// Check if we need a DH ratchet step (new DH key from sender)
|
|
104
|
+
const needsRatchet = !this.dhRecv || !uint8Eq(message.header.dhPublicKey, this.dhRecv);
|
|
105
|
+
if (needsRatchet) {
|
|
106
|
+
this.skipKeys(message.header.pn);
|
|
107
|
+
this.dhRatchet(message.header.dhPublicKey);
|
|
108
|
+
}
|
|
109
|
+
this.skipKeys(message.header.n);
|
|
110
|
+
if (!this.chainKeyRecv) {
|
|
111
|
+
throw new Error("Receiving chain not initialized");
|
|
112
|
+
}
|
|
113
|
+
const [newCK, mk] = kdfCK(this.chainKeyRecv);
|
|
114
|
+
this.chainKeyRecv = newCK;
|
|
115
|
+
this.recvCount++;
|
|
116
|
+
return this.aedDecrypt(mk, message.ciphertext, message.nonce, message.header);
|
|
117
|
+
}
|
|
118
|
+
// ─── DH Ratchet Step ──────────────────────────────
|
|
119
|
+
dhRatchet(newDhPub) {
|
|
120
|
+
this.prevSendCount = this.sendCount;
|
|
121
|
+
this.sendCount = 0;
|
|
122
|
+
this.recvCount = 0;
|
|
123
|
+
this.dhRecv = newDhPub;
|
|
124
|
+
// Derive new receiving chain
|
|
125
|
+
const [rk1, ckr] = kdfRK(this.rootKey, dh(this.dhSend.privateKey, this.dhRecv));
|
|
126
|
+
this.rootKey = rk1;
|
|
127
|
+
this.chainKeyRecv = ckr;
|
|
128
|
+
// Generate new DH keypair and derive new sending chain
|
|
129
|
+
this.dhSend = generateDHKeyPair();
|
|
130
|
+
const [rk2, cks] = kdfRK(this.rootKey, dh(this.dhSend.privateKey, this.dhRecv));
|
|
131
|
+
this.rootKey = rk2;
|
|
132
|
+
this.chainKeySend = cks;
|
|
133
|
+
}
|
|
134
|
+
// ─── Skipped Keys ─────────────────────────────────
|
|
135
|
+
skipKeys(until) {
|
|
136
|
+
if (!this.chainKeyRecv)
|
|
137
|
+
return;
|
|
138
|
+
if (until - this.recvCount > MAX_SKIP) {
|
|
139
|
+
throw new Error("Too many skipped messages");
|
|
140
|
+
}
|
|
141
|
+
while (this.recvCount < until) {
|
|
142
|
+
const [newCK, mk] = kdfCK(this.chainKeyRecv);
|
|
143
|
+
this.chainKeyRecv = newCK;
|
|
144
|
+
const key = skippedKeyId(this.dhRecv, this.recvCount);
|
|
145
|
+
this.skippedKeys.set(key, mk);
|
|
146
|
+
this.recvCount++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
trySkippedKeys(message) {
|
|
150
|
+
const key = skippedKeyId(message.header.dhPublicKey, message.header.n);
|
|
151
|
+
const mk = this.skippedKeys.get(key);
|
|
152
|
+
if (!mk)
|
|
153
|
+
return null;
|
|
154
|
+
this.skippedKeys.delete(key);
|
|
155
|
+
return this.aedDecrypt(mk, message.ciphertext, message.nonce, message.header);
|
|
156
|
+
}
|
|
157
|
+
// ─── AEAD Encryption ──────────────────────────────
|
|
158
|
+
aedEncrypt(mk, plaintext, header) {
|
|
159
|
+
const nonce = randomBytes(NONCE_LEN);
|
|
160
|
+
const ad = buildAD(this.associatedData, header);
|
|
161
|
+
const ciphertext = getSodium().crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, ad, null, // nsec (unused in this AEAD)
|
|
162
|
+
nonce, mk);
|
|
163
|
+
return { nonce, ciphertext };
|
|
164
|
+
}
|
|
165
|
+
aedDecrypt(mk, ciphertext, nonce, header) {
|
|
166
|
+
const ad = buildAD(this.associatedData, header);
|
|
167
|
+
return getSodium().crypto_aead_xchacha20poly1305_ietf_decrypt(null, // nsec
|
|
168
|
+
ciphertext, ad, nonce, mk);
|
|
169
|
+
}
|
|
170
|
+
// ─── Serialization ────────────────────────────────
|
|
171
|
+
/**
|
|
172
|
+
* Export session state for persistent storage.
|
|
173
|
+
*/
|
|
174
|
+
serialize() {
|
|
175
|
+
return {
|
|
176
|
+
dhSend: { publicKey: this.dhSend.publicKey, privateKey: this.dhSend.privateKey },
|
|
177
|
+
dhRecv: this.dhRecv,
|
|
178
|
+
rootKey: this.rootKey,
|
|
179
|
+
chainKeySend: this.chainKeySend,
|
|
180
|
+
chainKeyRecv: this.chainKeyRecv,
|
|
181
|
+
sendCount: this.sendCount,
|
|
182
|
+
recvCount: this.recvCount,
|
|
183
|
+
prevSendCount: this.prevSendCount,
|
|
184
|
+
skippedKeys: Array.from(this.skippedKeys.entries()).map(([id, mk]) => {
|
|
185
|
+
const [hexPub, nStr] = id.split(":");
|
|
186
|
+
return { dhPub: hexToBytes(hexPub), n: parseInt(nStr, 10), mk };
|
|
187
|
+
}),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Restore a session from serialized state.
|
|
192
|
+
*/
|
|
193
|
+
static deserialize(state, associatedData) {
|
|
194
|
+
const session = new DoubleRatchetSession(associatedData);
|
|
195
|
+
session.dhSend = state.dhSend;
|
|
196
|
+
session.dhRecv = state.dhRecv;
|
|
197
|
+
session.rootKey = state.rootKey;
|
|
198
|
+
session.chainKeySend = state.chainKeySend;
|
|
199
|
+
session.chainKeyRecv = state.chainKeyRecv;
|
|
200
|
+
session.sendCount = state.sendCount;
|
|
201
|
+
session.recvCount = state.recvCount;
|
|
202
|
+
session.prevSendCount = state.prevSendCount;
|
|
203
|
+
for (const { dhPub, n, mk } of state.skippedKeys) {
|
|
204
|
+
session.skippedKeys.set(skippedKeyId(dhPub, n), mk);
|
|
205
|
+
}
|
|
206
|
+
return session;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// ─── Wire Format ───────────────────────────────────────
|
|
210
|
+
/**
|
|
211
|
+
* Serialize an encrypted message to bytes for transmission.
|
|
212
|
+
* Format: [dh_pub:32][pn:4 LE][n:4 LE][nonce:24][ciphertext:rest]
|
|
213
|
+
*/
|
|
214
|
+
export function serializeMessage(msg) {
|
|
215
|
+
const buf = new Uint8Array(32 + 4 + 4 + NONCE_LEN + msg.ciphertext.length);
|
|
216
|
+
const view = new DataView(buf.buffer);
|
|
217
|
+
buf.set(msg.header.dhPublicKey, 0);
|
|
218
|
+
view.setUint32(32, msg.header.pn, true);
|
|
219
|
+
view.setUint32(36, msg.header.n, true);
|
|
220
|
+
buf.set(msg.nonce, 40);
|
|
221
|
+
buf.set(msg.ciphertext, 40 + NONCE_LEN);
|
|
222
|
+
return buf;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Deserialize bytes back into an encrypted message.
|
|
226
|
+
*/
|
|
227
|
+
export function deserializeMessage(buf) {
|
|
228
|
+
if (buf.length < 40 + NONCE_LEN + TAG_LEN) {
|
|
229
|
+
throw new Error("Message too short");
|
|
230
|
+
}
|
|
231
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
232
|
+
return {
|
|
233
|
+
header: {
|
|
234
|
+
dhPublicKey: buf.slice(0, 32),
|
|
235
|
+
pn: view.getUint32(32, true),
|
|
236
|
+
n: view.getUint32(36, true),
|
|
237
|
+
},
|
|
238
|
+
nonce: buf.slice(40, 40 + NONCE_LEN),
|
|
239
|
+
ciphertext: buf.slice(40 + NONCE_LEN),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
// ─── Helpers ───────────────────────────────────────────
|
|
243
|
+
function buildAD(sessionAD, header) {
|
|
244
|
+
const ad = new Uint8Array(sessionAD.length + 32 + 4 + 4);
|
|
245
|
+
const view = new DataView(ad.buffer);
|
|
246
|
+
ad.set(sessionAD, 0);
|
|
247
|
+
ad.set(header.dhPublicKey, sessionAD.length);
|
|
248
|
+
view.setUint32(sessionAD.length + 32, header.pn, true);
|
|
249
|
+
view.setUint32(sessionAD.length + 36, header.n, true);
|
|
250
|
+
return ad;
|
|
251
|
+
}
|
|
252
|
+
function skippedKeyId(dhPub, n) {
|
|
253
|
+
return `${bytesToHex(dhPub)}:${n}`;
|
|
254
|
+
}
|
|
255
|
+
function uint8Eq(a, b) {
|
|
256
|
+
if (a.length !== b.length)
|
|
257
|
+
return false;
|
|
258
|
+
for (let i = 0; i < a.length; i++) {
|
|
259
|
+
if (a[i] !== b[i])
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
function bytesToHex(bytes) {
|
|
265
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
266
|
+
}
|
|
267
|
+
function hexToBytes(hex) {
|
|
268
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
269
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
270
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
271
|
+
}
|
|
272
|
+
return bytes;
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=double-ratchet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"double-ratchet.js","sourceRoot":"","sources":["../../src/crypto/double-ratchet.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface EncryptedFile {
|
|
2
|
+
encrypted: Uint8Array;
|
|
3
|
+
key: Uint8Array;
|
|
4
|
+
nonce: Uint8Array;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Encrypt a file using XSalsa20-Poly1305 (crypto_secretbox).
|
|
8
|
+
* Generates a random key and nonce.
|
|
9
|
+
*/
|
|
10
|
+
export declare function encryptFile(plaintext: Uint8Array): EncryptedFile;
|
|
11
|
+
/**
|
|
12
|
+
* Decrypt a file encrypted with encryptFile.
|
|
13
|
+
*/
|
|
14
|
+
export declare function decryptFile(encrypted: Uint8Array, key: Uint8Array, nonce: Uint8Array): Uint8Array;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getSodium, randomBytes } from "./utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Encrypt a file using XSalsa20-Poly1305 (crypto_secretbox).
|
|
4
|
+
* Generates a random key and nonce.
|
|
5
|
+
*/
|
|
6
|
+
export function encryptFile(plaintext) {
|
|
7
|
+
const sodium = getSodium();
|
|
8
|
+
const key = randomBytes(sodium.crypto_secretbox_KEYBYTES);
|
|
9
|
+
const nonce = randomBytes(sodium.crypto_secretbox_NONCEBYTES);
|
|
10
|
+
const encrypted = sodium.crypto_secretbox_easy(plaintext, nonce, key);
|
|
11
|
+
return { encrypted, key, nonce };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Decrypt a file encrypted with encryptFile.
|
|
15
|
+
*/
|
|
16
|
+
export function decryptFile(encrypted, key, nonce) {
|
|
17
|
+
const sodium = getSodium();
|
|
18
|
+
return sodium.crypto_secretbox_open_easy(encrypted, nonce, key);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/crypto/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQpD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,SAAqB;IAC/C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,CAAC,yBAAyB,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,MAAM,CAAC,qBAAqB,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACtE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,SAAqB,EAAE,GAAe,EAAE,KAAiB;IACnF,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,0BAA0B,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { initSodium, getSodium, toBase64, fromBase64, randomBytes } from "./utils.js";
|
|
2
|
+
export { encryptFile, decryptFile, type EncryptedFile } from "./file.js";
|
|
3
|
+
export { type IdentityKeyPair, type DHKeyPair, type SignedPreKey, generateIdentityKeyPair, generateDHKeyPair, generateSignedPreKey, generateOneTimePreKeys, prepareRegistrationKeys, verifySignature, } from "./keys.js";
|
|
4
|
+
export { type X3DHResult, x3dhInitiate, x3dhRespond } from "./x3dh.js";
|
|
5
|
+
export { DoubleRatchetSession, type EncryptedMessage, type MessageHeader, type SessionState, serializeMessage, deserializeMessage, } from "./double-ratchet.js";
|
|
6
|
+
export { type SenderKeyState, type ReceivedSenderKey, type SenderKeyDistributionPayload, GROUP_MSG_TYPE, generateSenderKey, createSkdmPayload, parseSkdmPayload, encryptSkdm, decryptSkdm, senderKeyEncrypt, senderKeyDecrypt, } from "./sender-keys.js";
|
|
7
|
+
export { generateProfileKey, encryptProfile, decryptProfile, encryptProfileKeyFor, decryptProfileKey, encryptProfileToBase64, decryptProfileFromBase64, } from "./profile.js";
|
|
8
|
+
export { encryptBackup, decryptBackup, deriveBackupKey, generateRecoveryKey, type BackupEncryptResult, type BackupPayload, type SerializedSessionState, } from "./backup.js";
|
|
9
|
+
export { generatePassphrase } from "./passphrase.js";
|