ed: Unvendor STROBE (#895)

* Fix bench build

* Remove vendored strobe impl and use strobe-rs
This commit is contained in:
Michael Rosenberg 2026-03-30 15:51:17 +02:00 committed by GitHub
parent bad078de58
commit fc23dd4a86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 42 additions and 293 deletions

67
Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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<SigningKey> = (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))

View file

@ -9,7 +9,6 @@
//! Batch signature verification.
mod strobe;
mod transcript;
use alloc::vec::Vec;

View file

@ -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);
}
}

View file

@ -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 {