Skip to content

Commit

Permalink
adds single-threaded mode
Browse files Browse the repository at this point in the history
  • Loading branch information
seanwatters committed Feb 8, 2024
1 parent d6c3c77 commit 8095042
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 16 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
run: cargo test --verbose
- name: Benchmark
run: cargo bench --verbose
- name: Benchmark (single thread)
run: cargo bench --no-default-features --verbose

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@ license = "MIT"
name = "rgp"
readme = "README.md"
repository = "https://github.com/seanwatters/RGP"
version = "0.1.5"
version = "0.1.6"

[dependencies]
chacha20 = "0.9.1"
chacha20poly1305 = "0.10.1"
ed25519-dalek = { version = "2.0.0", features = ["rand_core"] }
rand_core = "0.6.4"
rayon = "1.8.1"
rayon = { version = "1.8.1", optional = true }
x25519-dalek = { version = "2.0.0", features = ["static_secrets"] }

[dev-dependencies]
criterion = { version = "0.5.1", features = ["html_reports"] }

[features]
default = ["multi-thread"]
multi-thread = ["dep:rayon"]

[[bench]]
name = "lib"
harness = false
35 changes: 24 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ let decrypted_content = rgp::content::decrypt(
assert_eq!(decrypted_content, content);
```

### Disable Multi-threading

The `"multi-thread"` feature is enabled by default and utilizes the [Rayon](https://crates.io/crates/rayon) crate. It only impacts the `content::encrypt` function, but can be disabled by setting `default-features` to `false`.

```toml
# Cargo.toml

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

## Process

1. Generate one-time and ephemeral components
Expand All @@ -72,24 +83,26 @@ assert_eq!(decrypted_content, content);

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

| Operation | Time |
| --------------- | --------- |
| multi_encrypt | 101.76 ms |
| extract | 486.00 µs |
| decrypt | 44.729 ms |
| Operation | Time |
| ----------------------- | --------- |
| encrypt (multi-thread) | 101.76 ms |
| encrypt (single-thread) | 766.31 ms |
| extract | 486.00 µs |
| decrypt | 44.729 ms |

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

| Operation | Time |
| --------- | --------- |
| encrypt | 61.537 ms |
| decrypt | 44.729 ms |
| Operation | Time |
| ----------------------- | --------- |
| encrypt (multi-thread) | 61.537 ms |
| encrypt (single-thread) | 63.758 ms |
| decrypt | 44.729 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`. You can also view the latest benches in the GitHub CI [workflow](https://github.com//seanwatters/RGP/actions/workflows/ci.yml) under job/Benchmark.
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](https://github.com//seanwatters/RGP/actions/workflows/ci.yml) under job/Benchmark or job/Benchmark (single threaded).

**NOTE:** 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` loop, so the number of threads does have an impact on performance.
**NOTE:** 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` loop, so the number of threads does have an impact on performance.

## Encrypted Format

Expand Down
43 changes: 41 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ pub fn generate_exchange_keys() -> ([u8; 32], [u8; 32]) {
/// assert_eq!(decrypted_content, content);
/// ```
pub mod content {
#[cfg(feature = "multi-thread")]
use rayon::prelude::*;
#[cfg(feature = "multi-thread")]
use std::thread;

use chacha20::{cipher::StreamCipher, XChaCha20 as ChaCha};
Expand Down Expand Up @@ -156,20 +158,36 @@ pub mod content {
out.extend(&keys_header);

// sign/encrypt content

#[cfg(feature = "multi-thread")]
let sign_and_encrypt_handle = thread::spawn(move || {
let signature = super::signature::sign(&fingerprint, &content);
content.extend(signature);

let content_cipher = ChaChaAEAD::new(&content_key);
match content_cipher.encrypt(&nonce, content.as_ref()) {
Ok(encrypted_content) => return Ok(encrypted_content),
Err(_) => return Err("failed to encrypt content"),
Ok(encrypted_content) => Ok(encrypted_content),
Err(_) => Err("failed to encrypt content"),
}
});

#[cfg(not(feature = "multi-thread"))]
let encrypted_content = {
let signature = super::signature::sign(&fingerprint, &content);
content.extend(signature);

let content_cipher = ChaChaAEAD::new(&content_key);
match content_cipher.encrypt(&nonce, content.as_ref()) {
Ok(encrypted_content) => encrypted_content,
Err(_) => return Err("failed to encrypt content"),
}
};

let mut encrypted_keys = vec![0u8; KEY_LEN * pub_key_count];

// encrypt keys

#[cfg(feature = "multi-thread")]
encrypted_keys
.par_chunks_mut(KEY_LEN)
.enumerate()
Expand All @@ -189,8 +207,29 @@ pub mod content {
chunk[0..KEY_LEN].copy_from_slice(&buffer);
});

#[cfg(not(feature = "multi-thread"))]
encrypted_keys
.chunks_mut(KEY_LEN)
.enumerate()
.for_each(|(i, chunk)| {
let shared_secret = e_priv_key
.diffie_hellman(&PublicKey::from(pub_keys[i]))
.to_bytes();

let mut key_cipher = {
use chacha20::cipher::KeyIvInit;
ChaCha::new(&shared_secret.into(), &nonce)
};

let mut buffer = content_key.to_vec();
key_cipher.apply_keystream(&mut buffer);

chunk[0..KEY_LEN].copy_from_slice(&buffer);
});

out.extend(encrypted_keys);

#[cfg(feature = "multi-thread")]
let encrypted_content = sign_and_encrypt_handle.join().unwrap()?;
out.extend(encrypted_content);

Expand Down

0 comments on commit 8095042

Please sign in to comment.