Files
2026-02-19 13:51:07 +08:00

70 lines
2.3 KiB
TypeScript

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);
}