Skip to main content

vector_core/community/
cipher.rs

1//! Raw-key NIP-44 v2 sealing — the single symmetric-encryption primitive of the
2//! Community protocol. The channel key (message plane) and the server-root key
3//! (metadata plane) are both raw 32-byte `ConversationKey`s; ciphertext is
4//! base64'd for carriage in an event's string `content` field.
5
6use nostr_sdk::nips::nip44::v2::{decrypt_to_bytes, encrypt_to_bytes, ConversationKey};
7
8/// Encrypt `plaintext` under a raw 32-byte key, returning base64 for event content.
9pub fn seal(key: &[u8; 32], plaintext: &[u8]) -> Result<String, String> {
10    let ck = ConversationKey::new(*key);
11    let ciphertext = encrypt_to_bytes(&ck, plaintext).map_err(|e| e.to_string())?;
12    Ok(base64_simd::STANDARD.encode_to_string(&ciphertext))
13}
14
15/// Inverse of [`seal`]: base64-decode then NIP-44-decrypt under the raw key. A
16/// wrong key or tampered payload fails the MAC and returns `Err`.
17pub fn open(key: &[u8; 32], content_b64: &str) -> Result<Vec<u8>, String> {
18    let ciphertext = base64_simd::STANDARD
19        .decode_to_vec(content_b64.as_bytes())
20        .map_err(|e| e.to_string())?;
21    let ck = ConversationKey::new(*key);
22    decrypt_to_bytes(&ck, &ciphertext).map_err(|e| e.to_string())
23}
24
25#[cfg(test)]
26mod tests {
27    use super::*;
28
29    #[test]
30    fn round_trip() {
31        let key = [0x5au8; 32];
32        let sealed = seal(&key, b"hello community").unwrap();
33        assert_eq!(open(&key, &sealed).unwrap(), b"hello community");
34    }
35
36    #[test]
37    fn wrong_key_fails() {
38        let sealed = seal(&[1u8; 32], b"secret").unwrap();
39        assert!(open(&[2u8; 32], &sealed).is_err());
40    }
41
42    #[test]
43    fn distinct_ciphertext_per_call() {
44        let key = [9u8; 32];
45        assert_ne!(seal(&key, b"x").unwrap(), seal(&key, b"x").unwrap());
46    }
47}