Encrypted, ephemeral, private memos on atproto
1import { XWing } from "@noble/post-quantum/hybrid.js";
2import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
3import { sha3_512 } from "@noble/hashes/sha3.js";
4import type { EncryptedPayload } from "./types.ts";
5
6export function decryptText(
7 secretKey: Uint8Array,
8 payload: EncryptedPayload,
9): string {
10 const cipherText = Uint8Array.fromBase64(payload.cipherText);
11 const nonce = Uint8Array.fromBase64(payload.nonce);
12 const content = Uint8Array.fromBase64(payload.content);
13 const sharedSecret = XWing.decapsulate(cipherText, secretKey);
14 const cipher = xchacha20poly1305(sharedSecret, nonce);
15 const decrypted = cipher.decrypt(content);
16 const hash = sha3_512(decrypted);
17
18 if (decrypted.byteLength !== payload.length) {
19 throw new Error(
20 `content lengths do not match: got ${decrypted.byteLength}, expected ${payload.length}`,
21 );
22 } else if (hash.toBase64() !== payload.hash) {
23 throw new Error(
24 `hashes do not match: got ${hash.toBase64()}, expected ${payload.hash}`,
25 );
26 }
27
28 return new TextDecoder().decode(decrypted);
29}