Encrypted, ephemeral, private memos on atproto
at main 29 lines 1.1 kB view raw
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}