rgp 0.1.11

Enabling efficient E2EE for large-audience pub/sub.
Documentation

RGP

ci license crates.io docs.rs dependency status

Relatively Good Privacy

Usage

let (fingerprint, verifying_key) = rgp::signature::generate_fingerprint();

let (our_priv_key, our_pub_key) = rgp::generate_exchange_keys();
let mut pub_keys = vec![our_pub_key];

// 8mb
let content = vec![0u8; 8_000_000];

// 20,000 recipients
for _ in 0..20_000 {
    let (_, pub_key) = rgp::generate_exchange_keys();
    pub_keys.push(pub_key)
}

let mut encrypted_content = rgp::content::encrypt(
    fingerprint,
    content.clone(),
    &pub_keys
)
.unwrap();

rgp::content::extract_content_for_key_position(
    &mut encrypted_content,
    0
)
.unwrap();

let decrypted_content = rgp::content::decrypt(
    Some(&verifying_key),
    our_priv_key,
    &encrypted_content,
)
.unwrap();

assert_eq!(decrypted_content, content);

Disable Multi-threading

The "multi-thread" feature is enabled by default and utilizes the Rayon crate. It only impacts the content::encrypt function, but can be disabled by setting default-features to false.

# Cargo.toml

[dependencies]
rgp = { version = "x.x.x", default-features = false }

Process

  1. Generate one-time and ephemeral components
    • nonce
    • one-time content key
    • ephemeral private key
    • one-time public key
  2. Sign plaintext to generate content signature
  3. Encrypt plaintext and content signature with one-time content key
  4. Encrypt one-time content key for all recipients
    • Generate shared secret with recipient public key and ephemeral private key
    • Encrypt one-time content key with shared secret

Ciphersuite

  • Ed25519 for signatures
  • XChaCha20Poly1305 for content
  • X25519 for Diffie-Hellman shared secret generation
  • XChaCha20 for one-time content key encryption

Performance

For the 8mb example with 20,000 recipients, on my M1 MacBook Pro

Operation Time
encrypt (multi-thread) 97.186 ms
encrypt (single-thread) 764.00 ms
extract 328.30 µs
decrypt 44.729 ms

Doing the equivalent operation for just 1 recipient on 8mb is

Operation Time
encrypt (multi-thread) 61.212 ms
encrypt (single-thread) 61.314 ms

When benchmarked in isolation, the signing operation (internal to the encrypt function) and verifying operation (internal to the decrypt function), take 28.469 ms and 14.209 ms, respectively.

To check performance on your machine, run cargo bench (or cargo bench --no-default-features to disable multi-threading). You can also view the latest benches in the GitHub CI workflow under job/Benchmark or job/Benchmark (single threaded).

NOTE: in multi-threaded mode the content signing/encryption logic is done in a separate thread from the per-recipient content key encryption, and the content key encryption work is done in a Rayon par_chunks_mut for loop. There is likely an opportunity for further parallelization in the content encryption and signing step.

Encrypted Format

  • nonce = 24 bytes
  • one-time public key = 32 bytes
  • keys count (1-9 bytes)
    • int size = 2 bits (0 for u8+63 | 1 for u16+63 | 2 for u32+63 | 3 for u64+63)
    • count
      • numbers 0-63 = 6 bits
      • numbers >63 = 1-8 bytes (big endian int)
  • encrypted keys = pub_keys.len() * 32 bytes
  • encrypted content = content.len()
  • signature = 64 bytes (encrypted along with the content to preserve deniability)
  • Poly1305 MAC = 16 bytes

License

MIT

Security

THIS CODE HAS NOT BEEN AUDITED OR REVIEWED. USE AT YOUR OWN RISK.