From fc23dd4a8660353233d54a8bd1244b920ab5ebdc Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Mon, 30 Mar 2026 15:51:17 +0200 Subject: [PATCH] ed: Unvendor STROBE (#895) * Fix bench build * Remove vendored strobe impl and use strobe-rs --- Cargo.lock | 67 +++--- ed25519-dalek/Cargo.toml | 4 +- ed25519-dalek/benches/ed25519_benchmarks.rs | 13 +- ed25519-dalek/src/batch.rs | 1 - ed25519-dalek/src/batch/strobe.rs | 240 -------------------- ed25519-dalek/src/batch/transcript.rs | 10 +- 6 files changed, 42 insertions(+), 293 deletions(-) delete mode 100644 ed25519-dalek/src/batch/strobe.rs diff --git a/Cargo.lock b/Cargo.lock index 836fc69..8d126f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,12 +76,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.10.0" @@ -144,12 +138,12 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" -version = "0.10.0-rc.10" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c536927023d1c432e6e23a25ef45f6756094eac2ab460db5fb17a772acdfd312" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures", "rand_core 0.10.0", ] @@ -217,15 +211,6 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - [[package]] name = "cpufeatures" version = "0.3.0" @@ -326,7 +311,7 @@ version = "5.0.0-pre.6" dependencies = [ "bincode", "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures", "criterion", "curve25519-dalek-derive", "digest", @@ -612,7 +597,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures", ] [[package]] @@ -769,7 +754,7 @@ checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.10.0", + "bitflags", "num-traits", "rand", "rand_chacha", @@ -953,7 +938,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1052,7 +1037,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures", "digest", ] @@ -1087,14 +1072,15 @@ dependencies = [ [[package]] name = "strobe-rs" -version = "0.5.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9baee171ea889cfe5333700f0948777b483db8dc805c5c519ffe92e7714783" +checksum = "83561175f0a962dea437885589480d216d8741debf853e0d36edd526344fd273" dependencies = [ - "bitflags 1.3.2", + "bitflags", "byteorder", + "keccak", "subtle", - "tiny-keccak", + "zeroize", ] [[package]] @@ -1144,15 +1130,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -1336,7 +1313,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags", "hashbrown 0.15.5", "indexmap", "semver", @@ -1440,7 +1417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags", "indexmap", "log", "serde", @@ -1517,6 +1494,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] [[package]] name = "zmij" diff --git a/ed25519-dalek/Cargo.toml b/ed25519-dalek/Cargo.toml index 311235f..6495c6e 100644 --- a/ed25519-dalek/Cargo.toml +++ b/ed25519-dalek/Cargo.toml @@ -41,6 +41,7 @@ keccak = { version = "0.2", default-features = false, optional = true } rand_core = { version = "0.10", default-features = false, optional = true } serde = { version = "1.0", default-features = false, optional = true } zeroize = { version = "1.5", default-features = false, optional = true } +strobe-rs = { version = "0.13", optional = true } [dev-dependencies] curve25519-dalek = { version = "=5.0.0-pre.6", default-features = false, features = [ @@ -60,7 +61,6 @@ serde_json = "1.0" criterion = { version = "0.5", features = ["html_reports"] } hex-literal = "1" serde = { version = "1.0", features = ["derive"] } -strobe-rs = "0.5" toml = { version = "0.9" } [[bench]] @@ -78,7 +78,7 @@ alloc = [ "zeroize?/alloc", ] -batch = ["alloc", "dep:keccak", "rand_core"] +batch = ["alloc", "dep:keccak", "rand_core", "strobe-rs"] fast = ["curve25519-dalek/precomputed-tables"] digest = ["signature/digest"] # Exposes the hazmat module diff --git a/ed25519-dalek/benches/ed25519_benchmarks.rs b/ed25519-dalek/benches/ed25519_benchmarks.rs index eda1715..d3d3af9 100644 --- a/ed25519-dalek/benches/ed25519_benchmarks.rs +++ b/ed25519-dalek/benches/ed25519_benchmarks.rs @@ -14,11 +14,10 @@ mod ed25519_benches { use ed25519_dalek::Signature; use ed25519_dalek::Signer; use ed25519_dalek::SigningKey; - use rand_core::prelude::ThreadRng; - use rand_core::rng; + use getrandom::{SysRng, rand_core::UnwrapErr}; fn sign(c: &mut Criterion) { - let mut csprng: ThreadRng = rng(); + let mut csprng = UnwrapErr(SysRng); let keypair: SigningKey = SigningKey::generate(&mut csprng); let msg: &[u8] = b""; @@ -26,7 +25,7 @@ mod ed25519_benches { } fn verify(c: &mut Criterion) { - let mut csprng: ThreadRng = rng(); + let mut csprng = UnwrapErr(SysRng); let keypair: SigningKey = SigningKey::generate(&mut csprng); let msg: &[u8] = b""; let sig: Signature = keypair.sign(msg); @@ -37,7 +36,7 @@ mod ed25519_benches { } fn verify_strict(c: &mut Criterion) { - let mut csprng: ThreadRng = rng(); + let mut csprng = UnwrapErr(SysRng); let keypair: SigningKey = SigningKey::generate(&mut csprng); let msg: &[u8] = b""; let sig: Signature = keypair.sign(msg); @@ -58,7 +57,7 @@ mod ed25519_benches { for size in BATCH_SIZES { let name = format!("size={size}"); group.bench_function(name, |b| { - let mut csprng: ThreadRng = rng(); + let mut csprng = UnwrapErr(SysRng); let keypairs: Vec = (0..size) .map(|_| SigningKey::generate(&mut csprng)) .collect(); @@ -78,7 +77,7 @@ mod ed25519_benches { fn verify_batch_signatures(_: &mut Criterion) {} fn key_generation(c: &mut Criterion) { - let mut csprng: ThreadRng = rng(); + let mut csprng = UnwrapErr(SysRng); c.bench_function("Ed25519 keypair generation", move |b| { b.iter(|| SigningKey::generate(&mut csprng)) diff --git a/ed25519-dalek/src/batch.rs b/ed25519-dalek/src/batch.rs index d77ad67..92180ee 100644 --- a/ed25519-dalek/src/batch.rs +++ b/ed25519-dalek/src/batch.rs @@ -9,7 +9,6 @@ //! Batch signature verification. -mod strobe; mod transcript; use alloc::vec::Vec; diff --git a/ed25519-dalek/src/batch/strobe.rs b/ed25519-dalek/src/batch/strobe.rs deleted file mode 100644 index b21b729..0000000 --- a/ed25519-dalek/src/batch/strobe.rs +++ /dev/null @@ -1,240 +0,0 @@ -//! Minimal implementation of (parts of) Strobe. - -use core::ops::{Deref, DerefMut}; -use keccak::Keccak; - -#[cfg(feature = "zeroize")] -use zeroize::Zeroize; - -/// Strobe R value; security level 128 is hardcoded -const STROBE_R: u8 = 166; - -const FLAG_I: u8 = 1; -const FLAG_A: u8 = 1 << 1; -const FLAG_C: u8 = 1 << 2; -const FLAG_T: u8 = 1 << 3; -const FLAG_M: u8 = 1 << 4; -const FLAG_K: u8 = 1 << 5; - -fn transmute_state(st: &mut AlignedKeccakState) -> &mut [u64; 25] { - unsafe { &mut *(st as *mut AlignedKeccakState as *mut [u64; 25]) } -} - -/// This is a wrapper around 200-byte buffer that's always 8-byte aligned -/// to make pointers to it safely convertible to pointers to [u64; 25] -/// (since u64 words must be 8-byte aligned) -#[derive(Clone)] -#[repr(align(8))] -struct AlignedKeccakState([u8; 200]); - -#[cfg(feature = "zeroize")] -impl Drop for AlignedKeccakState { - fn drop(&mut self) { - self.0.zeroize(); - } -} - -/// A Strobe context for the 128-bit security level. -/// -/// Only `meta-AD`, `AD`, `KEY`, and `PRF` operations are supported. -#[derive(Clone)] -pub struct Strobe128 { - state: AlignedKeccakState, - pos: u8, - pos_begin: u8, - cur_flags: u8, -} - -impl ::core::fmt::Debug for Strobe128 { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - // Ensure that the Strobe state isn't accidentally logged - write!(f, "Strobe128: STATE OMITTED") - } -} - -impl Strobe128 { - pub fn new(protocol_label: &[u8]) -> Strobe128 { - let initial_state = { - let mut st = AlignedKeccakState([0u8; 200]); - st[0..6].copy_from_slice(&[1, STROBE_R + 2, 1, 0, 1, 96]); - st[6..18].copy_from_slice(b"STROBEv1.0.2"); - Keccak::new().with_f1600(|f1600| f1600(transmute_state(&mut st))); - - st - }; - - let mut strobe = Strobe128 { - state: initial_state, - pos: 0, - pos_begin: 0, - cur_flags: 0, - }; - - strobe.meta_ad(protocol_label, false); - - strobe - } - - pub fn meta_ad(&mut self, data: &[u8], more: bool) { - self.begin_op(FLAG_M | FLAG_A, more); - self.absorb(data); - } - - pub fn ad(&mut self, data: &[u8], more: bool) { - self.begin_op(FLAG_A, more); - self.absorb(data); - } - - pub fn prf(&mut self, data: &mut [u8], more: bool) { - self.begin_op(FLAG_I | FLAG_A | FLAG_C, more); - self.squeeze(data); - } - - pub fn key(&mut self, data: &[u8], more: bool) { - self.begin_op(FLAG_A | FLAG_C, more); - self.overwrite(data); - } -} - -impl Strobe128 { - fn run_f(&mut self) { - self.state[self.pos as usize] ^= self.pos_begin; - self.state[(self.pos + 1) as usize] ^= 0x04; - self.state[(STROBE_R + 1) as usize] ^= 0x80; - Keccak::new().with_f1600(|f1600| f1600(transmute_state(&mut self.state))); - self.pos = 0; - self.pos_begin = 0; - } - - fn absorb(&mut self, data: &[u8]) { - for byte in data { - self.state[self.pos as usize] ^= byte; - self.pos += 1; - if self.pos == STROBE_R { - self.run_f(); - } - } - } - - fn overwrite(&mut self, data: &[u8]) { - for byte in data { - self.state[self.pos as usize] = *byte; - self.pos += 1; - if self.pos == STROBE_R { - self.run_f(); - } - } - } - - fn squeeze(&mut self, data: &mut [u8]) { - for byte in data { - *byte = self.state[self.pos as usize]; - self.state[self.pos as usize] = 0; - self.pos += 1; - if self.pos == STROBE_R { - self.run_f(); - } - } - } - - fn begin_op(&mut self, flags: u8, more: bool) { - // Check if we're continuing an operation - if more { - assert_eq!( - self.cur_flags, flags, - "You tried to continue op {:#b} but changed flags to {:#b}", - self.cur_flags, flags, - ); - return; - } - - // Skip adjusting direction information (we just use AD, PRF) - assert_eq!( - flags & FLAG_T, - 0u8, - "You used the T flag, which this implementation doesn't support" - ); - - let old_begin = self.pos_begin; - self.pos_begin = self.pos + 1; - self.cur_flags = flags; - - self.absorb(&[old_begin, flags]); - - // Force running F if C or K is set - let force_f = 0 != (flags & (FLAG_C | FLAG_K)); - - if force_f && self.pos != 0 { - self.run_f(); - } - } -} - -impl Deref for AlignedKeccakState { - type Target = [u8; 200]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for AlignedKeccakState { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -#[cfg(test)] -mod tests { - use strobe_rs::{self, SecParam}; - - #[test] - fn test_conformance() { - let mut s1 = super::Strobe128::new(b"Conformance Test Protocol"); - let mut s2 = strobe_rs::Strobe::new(b"Conformance Test Protocol", SecParam::B128); - - // meta-AD(b"msg"); AD(msg) - - let msg = [99u8; 1024]; - - s1.meta_ad(b"ms", false); - s1.meta_ad(b"g", true); - s1.ad(&msg, false); - - s2.meta_ad(b"ms", false); - s2.meta_ad(b"g", true); - s2.ad(&msg, false); - - // meta-AD(b"prf"); PRF() - - let mut prf1 = [0u8; 32]; - s1.meta_ad(b"prf", false); - s1.prf(&mut prf1, false); - - let mut prf2 = [0u8; 32]; - s2.meta_ad(b"prf", false); - s2.prf(&mut prf2, false); - - assert_eq!(prf1, prf2); - - // meta-AD(b"key"); KEY(prf output) - - s1.meta_ad(b"key", false); - s1.key(&prf1, false); - - s2.meta_ad(b"key", false); - s2.key(&prf2, false); - - // meta-AD(b"prf"); PRF() - - let mut prf1 = [0u8; 32]; - s1.meta_ad(b"prf", false); - s1.prf(&mut prf1, false); - - let mut prf2 = [0u8; 32]; - s2.meta_ad(b"prf", false); - s2.prf(&mut prf2, false); - - assert_eq!(prf1, prf2); - } -} diff --git a/ed25519-dalek/src/batch/transcript.rs b/ed25519-dalek/src/batch/transcript.rs index ea19e61..3c2b2c5 100644 --- a/ed25519-dalek/src/batch/transcript.rs +++ b/ed25519-dalek/src/batch/transcript.rs @@ -1,6 +1,6 @@ use super::MERLIN_PROTOCOL_LABEL; -use super::strobe::Strobe128; use core::convert::Infallible; +use strobe_rs::{SecParam, Strobe}; fn encode_usize_as_u32(x: usize) -> [u8; 4] { u32::try_from(x).expect("usize too large").to_le_bytes() @@ -37,7 +37,7 @@ fn encode_usize_as_u32(x: usize) -> [u8; 4] { /// Merlin](https://merlin.cool/use/index.html) section. #[derive(Clone)] pub struct Transcript { - strobe: Strobe128, + strobe: Strobe, } impl Transcript { @@ -53,7 +53,7 @@ impl Transcript { /// the Merlin website for more details on why. pub fn new(label: &'static [u8]) -> Transcript { let mut transcript = Transcript { - strobe: Strobe128::new(MERLIN_PROTOCOL_LABEL), + strobe: Strobe::new(MERLIN_PROTOCOL_LABEL, SecParam::B128), }; transcript.append_message(b"dom-sep", label); @@ -129,7 +129,7 @@ impl Transcript { /// [rekey_with_witness_bytes]: TranscriptRngBuilder::rekey_with_witness_bytes /// [finalize]: TranscriptRngBuilder::finalize pub struct TranscriptRngBuilder { - strobe: Strobe128, + strobe: Strobe, } impl TranscriptRngBuilder { @@ -183,7 +183,7 @@ impl TranscriptRngBuilder { /// Randomness](https://merlin.cool/transcript/rng.html) section of /// the Merlin website. pub struct TranscriptRng { - strobe: Strobe128, + strobe: Strobe, } impl rand_core::TryRng for TranscriptRng {