feat(crypto): sender-key per-conversation multi-device E2EE
Release desktop app / build (, windows-latest) (push) Has been cancelled
Release desktop app / build (--target universal-apple-darwin --bundles app,updater, macos-14) (push) Has been cancelled

This commit is contained in:
2026-04-19 19:27:24 +02:00
parent e57f81c9c3
commit 75618637e2
11 changed files with 703 additions and 142 deletions
+1
View File
@@ -1,3 +1,4 @@
export * from './backend.js';
export * from './box.js';
export * from './keys.js';
export * from './sessionKeys.js';
+58
View File
@@ -0,0 +1,58 @@
import { getCryptoBackend } from './backend.js';
import { decryptFrom, encryptFor, type EncryptedEnvelope } from './box.js';
// Sender-Key (Signal-style) helpers — one symmetric XSalsa20-Poly1305 key per
// conversation, wrapped with `crypto_box` for each recipient device's pubkey.
//
// Flow:
// - generateConvKey() produces 32 random bytes
// - wrapConvKeyForRecipient() encrypts the conv-key with sender's private key
// and recipient's pubkey -> stored in `conversation_keys` table
// - unwrapConvKey() reverses it on the receiving side
// - encryptWithConvKey()/decryptWithConvKey() do the message-payload work
export function generateConvKey(): Uint8Array {
const backend = getCryptoBackend();
return backend.randomBytes(backend.secretboxKeyLength);
}
export async function wrapConvKeyForRecipient(
convKey: Uint8Array,
recipientPublicKey: Uint8Array,
senderPrivateKey: Uint8Array,
): Promise<EncryptedEnvelope> {
return encryptFor(convKey, recipientPublicKey, senderPrivateKey);
}
export async function unwrapConvKey(
encryptedKey: Uint8Array,
nonce: Uint8Array,
senderPublicKey: Uint8Array,
recipientPrivateKey: Uint8Array,
): Promise<Uint8Array> {
return decryptFrom(encryptedKey, nonce, senderPublicKey, recipientPrivateKey);
}
export interface ConvCipher {
ciphertext: Uint8Array;
nonce: Uint8Array;
}
export function encryptWithConvKey(
plaintext: Uint8Array,
convKey: Uint8Array,
): ConvCipher {
const backend = getCryptoBackend();
const nonce = backend.randomBytes(backend.secretboxNonceLength);
const ciphertext = backend.secretbox(plaintext, nonce, convKey);
return { ciphertext, nonce };
}
export function decryptWithConvKey(
ciphertext: Uint8Array,
nonce: Uint8Array,
convKey: Uint8Array,
): Uint8Array {
const backend = getCryptoBackend();
return backend.secretboxOpen(ciphertext, nonce, convKey);
}