import { chacha20poly1305 } from "@noble/ciphers/chacha"; import { x25519 } from "@noble/curves/ed25519"; import { hkdf } from "@noble/hashes/hkdf"; import { sha256 } from "@noble/hashes/sha256"; import { randomBytes } from "@noble/hashes/utils"; const HKDF_SALT = new TextEncoder().encode("backchannel-dm-salt-v1"); const HKDF_INFO = new TextEncoder().encode("backchannel-dm-key-v1"); const encoder = new TextEncoder(); const decoder = new TextDecoder(); export function bytesToBase64(input: Uint8Array): string { let bin = ""; for (const b of input) { bin += String.fromCharCode(b); } return btoa(bin); } export function base64ToBytes(input: string): Uint8Array { const bin = atob(input); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i += 1) { bytes[i] = bin.charCodeAt(i); } return bytes; } export function getOrCreateIdentityKey(): string { const current = localStorage.getItem("bc.identity_key"); if (current) { return current; } const key = bytesToBase64(randomBytes(32)); localStorage.setItem("bc.identity_key", key); return key; } export function createEphemeralKeypair(): { privateKey: Uint8Array; publicKeyB64: string } { const privateKey = x25519.utils.randomPrivateKey(); const publicKey = x25519.getPublicKey(privateKey); return { privateKey, publicKeyB64: bytesToBase64(publicKey) }; } export function deriveDmKey(privateKey: Uint8Array, theirPublicKeyB64: string): Uint8Array { const theirPublicKey = base64ToBytes(theirPublicKeyB64); const sharedSecret = x25519.getSharedSecret(privateKey, theirPublicKey); return hkdf(sha256, sharedSecret, HKDF_SALT, HKDF_INFO, 32); } export function encryptDm(key: Uint8Array, plaintext: string): { nonce: string; ciphertext: string } { const nonceBytes = randomBytes(12); const cipher = chacha20poly1305(key, nonceBytes); const ciphertext = cipher.encrypt(encoder.encode(plaintext)); return { nonce: bytesToBase64(nonceBytes), ciphertext: bytesToBase64(ciphertext), }; } export function decryptDm(key: Uint8Array, nonceB64: string, ciphertextB64: string): string { const nonceBytes = base64ToBytes(nonceB64); const ciphertext = base64ToBytes(ciphertextB64); const cipher = chacha20poly1305(key, nonceBytes); const plaintext = cipher.decrypt(ciphertext); return decoder.decode(plaintext); }