Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions sv2/noise-sv2/BENCHES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Benchmarks

This document records the output of `cargo bench` for the `noise_sv2` crate.
The benchmarks measure the performance characteristics of the Noise protocol
implementation used by Stratum V2.

The results are intended to:
- Quantify the cost of the Noise handshake
- Measure post-handshake encryption and decryption overhead
- Serve as a reference point for future comparison and regression detection

All measurements are environment-dependent and should not be interpreted as
absolute performance guarantees.

## Benchmark Output

The following section contains the **raw output** of `cargo bench` captured
during a single run.

### Handshake Benchmarks

```text
Benchmarking handshake/step_0_initiator
Benchmarking handshake/step_0_initiator: Warming up for 3.0000 s
Benchmarking handshake/step_0_initiator: Collecting 100 samples in estimated 5.3787 s (56k iterations)
Benchmarking handshake/step_0_initiator: Analyzing
handshake/step_0_initiator
time: [18.560 µs 18.737 µs 18.981 µs]
change: [-50.218% -49.023% -47.697%] (p = 0.00 < 0.05)
Performance has improved.
Found 17 outliers among 100 measurements (17.00%)
11 (11.00%) high mild
6 (6.00%) high severe

Benchmarking handshake/step_1_responder
Benchmarking handshake/step_1_responder: Warming up for 3.0000 s
Benchmarking handshake/step_1_responder: Collecting 100 samples in estimated 7.4286 s (15k iterations)
Benchmarking handshake/step_1_responder: Analyzing
handshake/step_1_responder
time: [177.39 µs 178.07 µs 178.93 µs]
change: [-54.298% -53.548% -52.733%] (p = 0.00 < 0.05)
Performance has improved.
Found 10 outliers among 100 measurements (10.00%)
1 (1.00%) low mild
2 (2.00%) high mild
7 (7.00%) high severe

Benchmarking handshake/step_2_initiator
Benchmarking handshake/step_2_initiator: Warming up for 3.0000 s
Benchmarking handshake/step_2_initiator: Collecting 100 samples in estimated 6.1452 s (10k iterations)
Benchmarking handshake/step_2_initiator: Analyzing
handshake/step_2_initiator
time: [120.55 µs 120.91 µs 121.32 µs]
change: [-44.862% -43.680% -42.592%] (p = 0.00 < 0.05)
Performance has improved.
Found 10 outliers among 100 measurements (10.00%)
1 (1.00%) low mild
6 (6.00%) high mild
3 (3.00%) high severe

Benchmarking handshake/handshake
Benchmarking handshake/handshake: Warming up for 3.0000 s
Benchmarking handshake/handshake: Collecting 100 samples in estimated 6.1256 s (10k iterations)
Benchmarking handshake/handshake: Analyzing
handshake/handshake time: [316.58 µs 317.41 µs 318.34 µs]
change: [-43.167% -39.954% -36.598%] (p = 0.00 < 0.05)
Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
1 (1.00%) low mild
5 (5.00%) high mild
1 (1.00%) high severe
````

---

### Transport Roundtrip Benchmarks

```text
Gnuplot not found, using plotters backend

Benchmarking transport/roundtrip/64B
Benchmarking transport/roundtrip/64B: Warming up for 3.0000 s
Benchmarking transport/roundtrip/64B: Collecting 100 samples in estimated 5.0026 s (914k iterations)
Benchmarking transport/roundtrip/64B: Analyzing
transport/roundtrip/64B time: [5.1966 µs 5.2037 µs 5.2126 µs]
change: [-34.332% -31.654% -28.918%] (p = 0.00 < 0.05)
Performance has improved.
Found 15 outliers among 100 measurements (15.00%)
3 (3.00%) high mild
12 (12.00%) high severe

Benchmarking transport/roundtrip/256B
Benchmarking transport/roundtrip/256B: Warming up for 3.0000 s
Benchmarking transport/roundtrip/256B: Collecting 100 samples in estimated 5.0128 s (858k iterations)
Benchmarking transport/roundtrip/256B: Analyzing
transport/roundtrip/256B
time: [5.4840 µs 5.5004 µs 5.5190 µs]
change: [-37.044% -34.403% -31.675%] (p = 0.00 < 0.05)
Performance has improved.
Found 10 outliers among 100 measurements (10.00%)
4 (4.00%) high mild
6 (6.00%) high severe

Benchmarking transport/roundtrip/1024B
Benchmarking transport/roundtrip/1024B: Warming up for 3.0000 s
Benchmarking transport/roundtrip/1024B: Collecting 100 samples in estimated 5.0300 s (621k iterations)
Benchmarking transport/roundtrip/1024B: Analyzing
transport/roundtrip/1024B
time: [7.2587 µs 7.2827 µs 7.3112 µs]
change: [-32.654% -29.950% -27.189%] (p = 0.00 < 0.05)
Performance has improved.
Found 18 outliers among 100 measurements (18.00%)
7 (7.00%) high mild
11 (11.00%) high severe

Benchmarking transport/roundtrip/4096B
Benchmarking transport/roundtrip/4096B: Warming up for 3.0000 s
Benchmarking transport/roundtrip/4096B: Collecting 100 samples in estimated 5.0042 s (268k iterations)
Benchmarking transport/roundtrip/4096B: Analyzing
transport/roundtrip/4096B
time: [16.213 µs 16.388 µs 16.600 µs]
change: [-27.110% -23.533% -19.897%] (p = 0.00 < 0.05)
Performance has improved.
Found 13 outliers among 100 measurements (13.00%)
6 (6.00%) high mild
7 (7.00%) high severe
```

---

## Interpretation Notes

* Handshake cost is dominated by responder-side processing and key exchange.
* Post-handshake transport costs scale predictably with payload size and remain
within single-digit microseconds for small messages.

---

## Reproducing

Run benchmarks locally with:

```bash
cargo bench
```
11 changes: 11 additions & 0 deletions sv2/noise-sv2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ std = ["rand/std", "rand/std_rng", "rand_chacha/std", "secp256k1/rand-std"]
[dev-dependencies]
quickcheck = "1.0.3"
quickcheck_macros = "1"
criterion = { version = "0.3.0", features = ["html_reports"] }
rayon-core = "=1.12.1"
rand = {version = "0.8.5", default-features = false, features = ["std", "std_rng"] }

[profile.dev]
Expand All @@ -36,3 +38,12 @@ panic = "abort"

[package.metadata.docs.rs]
features = ["std"]

[[bench]]
name = "handshake"
harness = false


[[bench]]
name = "roundtrip"
harness = false
17 changes: 17 additions & 0 deletions sv2/noise-sv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,20 @@ This crate provides example on establishing a secure line:
1. **[Noise Handshake Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/noise-sv2/examples/handshake.rs)**:
Establish a secure line of communication between an Initiator and Responder via the Noise
protocol, allowing for the encryption and decryption of a secret message.

### Benches

This crate includes Criterion benchmarks under `benches/` covering:

* Noise handshake performance (`handshake.rs`)
* Encrypted message roundtrips after handshake (`roundtrip.rs`)

Benchmarks are intended for regression tracking and relative comparison, not absolute performance claims.

Example benchmark output and methodology are documented in [BENCHES.md](./BENCHES.md).

Run them with:

```bash
cargo bench
```
22 changes: 22 additions & 0 deletions sv2/noise-sv2/benches/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use rand::{rngs::StdRng, SeedableRng};
use secp256k1::{Keypair, Secp256k1};

pub fn rng() -> StdRng {
// Fixed seed for deterministic benchmark runs.
StdRng::seed_from_u64(0xdead_beef)
}

// Generates BIP340 compliant keypair
pub fn generate_key_with_rng<R: rand::Rng + ?Sized>(rng: &mut R) -> Keypair {
let secp = Secp256k1::new();
let (mut secret_key, public_key) = secp.generate_keypair(rng);
if public_key.x_only_public_key().1 == secp256k1::Parity::Odd {
secret_key = secret_key.negate();
}
Keypair::from_secret_key(&secp, &secret_key)
}

#[allow(warnings)]
pub fn payload(len: usize) -> Vec<u8> {
vec![0u8; len]
}
79 changes: 79 additions & 0 deletions sv2/noise-sv2/benches/handshake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use noise_sv2::{Initiator, Responder};

use crate::common::{generate_key_with_rng, rng};
mod common;

fn bench_nx_handshake(c: &mut Criterion) {
let mut group = c.benchmark_group("handshake");

group.bench_function("step_0_initiator", |b| {
b.iter_batched(
|| Initiator::new(None),
|mut initiator| {
let _msg_0 = initiator.step_0().unwrap();
},
BatchSize::SmallInput,
);
});

group.bench_function("step_1_responder", |b| {
b.iter_batched(
|| {
let mut rng = rng();
let responder_key = generate_key_with_rng(&mut rng);
let mut initiator = Initiator::new(None);
let responder = Responder::new(responder_key, 60);

let msg_0 = initiator.step_0().unwrap();
(responder, msg_0, rng)
},
|(mut responder, msg_0, mut rng)| {
let _ = responder.step_1_with_now_rng(msg_0, 10, &mut rng).unwrap();
},
BatchSize::SmallInput,
);
});

group.bench_function("step_2_initiator", |b| {
b.iter_batched(
|| {
let mut rng = rng();
let responder_key = generate_key_with_rng(&mut rng);
let mut initiator = Initiator::new(None);
let mut responder = Responder::new(responder_key, 60);

let msg_0 = initiator.step_0().unwrap();
let (msg_2, _) = responder.step_1_with_now_rng(msg_0, 10, &mut rng).unwrap();

(initiator, msg_2)
},
|(mut initiator, msg_2)| {
let _ = initiator.step_2_with_now(msg_2, 10).unwrap();
},
BatchSize::SmallInput,
);
});

group.bench_function("handshake", |b| {
b.iter_batched(
|| {
let mut rng = rng();
let responder_key = generate_key_with_rng(&mut rng);
let initiator = Initiator::new(None);
let responder = Responder::new(responder_key, 60);
(initiator, responder, rng)
},
|(mut initiator, mut responder, mut rng)| {
let msg_0 = initiator.step_0().unwrap();
let (msg_2, _) = responder.step_1_with_now_rng(msg_0, 10, &mut rng).unwrap();
let _ = initiator.step_2_with_now(msg_2, 10).unwrap();
},
BatchSize::SmallInput,
);
});
group.finish();
}

criterion_group!(handshake, bench_nx_handshake);
criterion_main!(handshake);
33 changes: 33 additions & 0 deletions sv2/noise-sv2/benches/roundtrip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use noise_sv2::{Initiator, Responder};

use crate::common::{generate_key_with_rng, payload, rng};
mod common;

fn bench_encryption_roundtrip(c: &mut Criterion) {
let responder_key = generate_key_with_rng(&mut rng());
let mut initiator = Initiator::new(None);
let mut responder = Responder::new(responder_key, 10);

let msg_0 = initiator.step_0().unwrap();
let (msg_2, mut responder_transport) = responder.step_1(msg_0).unwrap();
let mut initiator_transport = initiator.step_2(msg_2).unwrap();

for size in [64usize, 256, 1024, 4096] {
c.bench_function(&format!("transport/roundtrip/{size}B"), |b| {
b.iter_batched(
|| payload(size),
|mut msg| {
initiator_transport.encrypt(&mut msg).unwrap();
responder_transport.decrypt(&mut msg).unwrap();
responder_transport.encrypt(&mut msg).unwrap();
initiator_transport.decrypt(&mut msg).unwrap();
},
BatchSize::SmallInput,
)
});
}
}

criterion_group!(roundtrip, bench_encryption_roundtrip);
criterion_main!(roundtrip);
37 changes: 37 additions & 0 deletions sv2/noise-sv2/src/initiator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,40 @@ impl Drop for Initiator {
self.erase();
}
}

#[cfg(test)]
mod test {

use super::*;

#[test]
#[cfg_attr(miri, ignore)]
fn initiator_step0_produces_elligator_message() {
let mut initiator = Initiator::without_pk().unwrap();

let msg = initiator.step_0().expect("step_0 must succeed");

assert_eq!(msg.len(), ELLSWIFT_ENCODING_SIZE);
assert!(msg.iter().any(|b| *b != 0));
}

#[test]
#[cfg(feature = "std")]
#[cfg_attr(miri, ignore)]
fn initiator_rejects_tampered_handshake() {
use crate::Responder;

let authority_kp = Responder::generate_key();
let mut responder = Responder::new(authority_kp, 60);
let mut initiator = Initiator::without_pk().unwrap();

let msg0 = initiator.step_0().unwrap();
let (mut msg1, _) = responder.step_1(msg0).unwrap();

let idx = INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE - 5;
msg1[idx] ^= 0x01;

let res = initiator.step_2(msg1);
assert!(res.is_err());
}
}
Loading
Loading