Files
fips/benches/noise_aead.rs
Johnathan Corgan b6e2bcaa86 Correct the core's README and manifest comments
Text only; no code changes.

- The identity README said the root crate re-exports all of the module
  under fips::identity; it does not re-export secp or sha256, and
  re-exports ErasingKeypair only as pub(crate). It also said secp and
  sha256 are pub so the root crate can reach them, but no crate outside
  fips-core names either; they are pub and hidden.
- The test-hooks feature comment named only proto; the cache and noise
  modules have hooks too. The rand dev-dependency comment now also names
  TestEntropy.
- The root's sha2 comment now says that no root code hashes a secret
  and that fips-core enables the zeroize feature too, so it is on in
  every root build.
- The noise_aead benchmark's header described two shapes and now names
  the third, the hand-out row.
- The cache README's rule that an uncharged hint is refused rather than
  growing the cache past its cap now says it applies to hints only: a
  verified write is stored even when every entry is live-verified.
- Where the root's noise module, the core README and the noise README
  say the root meets AeadKey only as the hand-outs' return type, they
  now also name the compile-time tie that pins that type to ring.
2026-10-15 17:44:41 +00:00

159 lines
5.8 KiB
Rust

//! Seal and open on the Noise transport paths.
//!
//! Three shapes, the first two at a 64-byte and a 1400-byte payload behind a
//! 16-byte header used as associated data:
//!
//! - `session/*`: `NoiseSession::encrypt_with_aad` and
//! `decrypt_with_replay_check_and_aad`, the calls the node makes when it
//! seals or opens on its own task.
//! - `worker/*`: what the Unix encrypt and decrypt workers do per packet: a
//! key handed out by the session (`send_cipher_clone`) and a counter it
//! reserves, a seal in place on the wire buffer with the tag appended; or a
//! replay check, an open in place and an accept on a worker-owned window.
//! - `handout/send_cipher_clone`: the session's key hand-out alone, which the
//! encrypt path calls per packet.
//!
//! Only API that exists before and after the AEAD moved into `fips-core` is
//! used, so the same file runs against both trees for comparison.
use std::hint::black_box;
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use fips::Identity;
use fips::noise::{HandshakeState, NoiseSession};
use ring::aead::{Aad, Nonce};
const HEADER: usize = 16;
const SIZES: [usize; 2] = [64, 1400];
/// Packets sealed ahead for the open benchmarks; the replay window is reset
/// each time the ring wraps.
const RING: usize = 1024;
/// A completed initiator and responder session pair.
fn session_pair() -> (NoiseSession, NoiseSession) {
let a = Identity::generate();
let b = Identity::generate();
let mut i = HandshakeState::new_xk_initiator(a.keypair(), b.pubkey_full());
let mut r = HandshakeState::new_xk_responder(b.keypair());
i.set_local_epoch([1; 8]);
r.set_local_epoch([2; 8]);
let m1 = i.write_xk_message_1().unwrap();
r.read_xk_message_1(&m1).unwrap();
let m2 = r.write_xk_message_2().unwrap();
i.read_xk_message_2(&m2).unwrap();
let m3 = i.write_xk_message_3().unwrap();
r.read_xk_message_3(&m3).unwrap();
(i.into_session().unwrap(), r.into_session().unwrap())
}
fn nonce(counter: u64) -> Nonce {
let mut n = [0u8; 12];
n[4..].copy_from_slice(&counter.to_le_bytes());
Nonce::assume_unique_for_key(n)
}
fn session_paths(c: &mut Criterion) {
let mut g = c.benchmark_group("session");
for size in SIZES {
g.throughput(Throughput::Bytes(size as u64));
let header = [0x5au8; HEADER];
let payload = vec![0xa5u8; size];
let (mut tx, _) = session_pair();
g.bench_with_input(BenchmarkId::new("seal", size), &size, |bch, _| {
bch.iter(|| black_box(tx.encrypt_with_aad(black_box(&payload), &header).unwrap()))
});
let (mut tx, mut rx) = session_pair();
let sealed: Vec<Vec<u8>> = (0..RING)
.map(|_| tx.encrypt_with_aad(&payload, &header).unwrap())
.collect();
g.bench_with_input(BenchmarkId::new("open", size), &size, |bch, _| {
rx.reset_replay_window();
let mut k = 0usize;
bch.iter(|| {
if k == RING {
rx.reset_replay_window();
k = 0;
}
let out = rx
.decrypt_with_replay_check_and_aad(&sealed[k], k as u64, &header)
.unwrap();
k += 1;
black_box(out)
})
});
}
g.finish();
}
fn worker_paths(c: &mut Criterion) {
let mut g = c.benchmark_group("worker");
for size in SIZES {
g.throughput(Throughput::Bytes(size as u64));
let (mut tx, _) = session_pair();
let mut wire = Vec::with_capacity(HEADER + size + 16);
g.bench_with_input(BenchmarkId::new("seal", size), &size, |bch, _| {
bch.iter(|| {
let key = tx.send_cipher_clone().unwrap();
let counter = tx.take_send_counter().unwrap();
wire.clear();
wire.resize(HEADER + size, 0xa5);
let (aad, body) = wire.split_at_mut(HEADER);
let tag = key
.seal_in_place_separate_tag(nonce(counter), Aad::from(&*aad), body)
.unwrap();
wire.extend_from_slice(tag.as_ref());
black_box(&wire);
})
});
let (tx, rx) = session_pair();
let key = tx.send_cipher_clone().unwrap();
let header = [0x5au8; HEADER];
let sealed: Vec<Vec<u8>> = (0..RING as u64)
.map(|counter| {
let mut body = vec![0xa5u8; size];
let tag = key
.seal_in_place_separate_tag(nonce(counter), Aad::from(&header), &mut body)
.unwrap();
body.extend_from_slice(tag.as_ref());
body
})
.collect();
let rkey = rx.recv_cipher_clone().unwrap();
let mut window = rx.recv_replay_snapshot_owned();
let mut buf = Vec::with_capacity(size + 16);
g.bench_with_input(BenchmarkId::new("open", size), &size, |bch, _| {
window.reset();
let mut k = 0usize;
bch.iter(|| {
if k == RING {
window.reset();
k = 0;
}
let counter = k as u64;
assert!(window.check(counter));
buf.clear();
buf.extend_from_slice(&sealed[k]);
let plain = rkey
.open_in_place(nonce(counter), Aad::from(&header), &mut buf)
.unwrap();
black_box(plain);
window.accept(counter);
k += 1;
})
});
}
g.finish();
let (tx, _) = session_pair();
c.bench_function("handout/send_cipher_clone", |bch| {
bch.iter(|| black_box(tx.send_cipher_clone()))
});
}
criterion_group!(benches, session_paths, worker_paths);
criterion_main!(benches);