From 1950f10578e9ce760e739d036aefcb558ad40345 Mon Sep 17 00:00:00 2001 From: nullPointerEnjoyer Date: Sat, 17 Oct 2026 18:16:43 +0200 Subject: [PATCH 1/2] fix(crypto): keep vrf private key material out of debug output --- crypto/src/vrf/mod.rs | 33 ++++++++++++++++++++++++++++++-- crypto/src/vrf/schnorrkel/mod.rs | 25 +++++++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/crypto/src/vrf/mod.rs b/crypto/src/vrf/mod.rs index 4de87f2fcc..2c14efd440 100644 --- a/crypto/src/vrf/mod.rs +++ b/crypto/src/vrf/mod.rs @@ -55,17 +55,31 @@ pub enum VRFKeyKind { } #[must_use] -#[derive(Debug, PartialEq, Eq, Clone, Decode, Encode)] +#[derive(PartialEq, Eq, Clone, Decode, Encode)] pub struct VRFPrivateKey { key: VRFPrivateKeyHolder, } -#[derive(Debug, PartialEq, Eq, Clone, Decode, Encode)] +impl std::fmt::Debug for VRFPrivateKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Keep the key material out of the formatted output; this type can end up in + // log lines through derived Debug impls of larger structures. + f.write_str("VRFPrivateKey()") + } +} + +#[derive(PartialEq, Eq, Clone, Decode, Encode)] pub(crate) enum VRFPrivateKeyHolder { #[codec(index = 0)] Schnorrkel(SchnorrkelPrivateKey), } +impl std::fmt::Debug for VRFPrivateKeyHolder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("VRFPrivateKeyHolder()") + } +} + #[must_use] #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Decode, Encode)] pub struct VRFPublicKey { @@ -348,6 +362,21 @@ mod tests { use super::*; + #[test] + fn private_key_debug_hides_key_material() { + let mut rng = make_seedable_rng(Seed::from_entropy()); + let (sk, _pk) = VRFPrivateKey::new_from_rng(&mut rng, VRFKeyKind::Schnorrkel); + + let encoded_sk = sk.encode(); + let debug_output = format!("{sk:?}"); + + assert!(debug_output.contains("")); + // No byte of the encoded secret may appear anywhere in the formatted output. + for byte in &encoded_sk { + assert!(!debug_output.contains(&byte.to_string())); + } + } + #[rstest] #[trace] #[case(Seed::from_entropy())] diff --git a/crypto/src/vrf/schnorrkel/mod.rs b/crypto/src/vrf/schnorrkel/mod.rs index c7f13ba6ed..9483881b00 100644 --- a/crypto/src/vrf/schnorrkel/mod.rs +++ b/crypto/src/vrf/schnorrkel/mod.rs @@ -115,12 +115,20 @@ impl Decode for SchnorrkelPublicKey { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] #[must_use] pub struct SchnorrkelPrivateKey { key: schnorrkel::SecretKey, } +impl std::fmt::Debug for SchnorrkelPrivateKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Keep the key material out of the formatted output; this type can end up in + // log lines through derived Debug impls of larger structures. + f.write_str("SchnorrkelPrivateKey()") + } +} + impl SchnorrkelPrivateKey { pub fn new(rng: &mut R) -> (SchnorrkelPrivateKey, SchnorrkelPublicKey) { let sk = schnorrkel::SecretKey::generate_with(&mut Rng08Adapter(rng)); @@ -221,6 +229,21 @@ mod tests { use serialization::{DecodeAll, Encode}; use test_utils::random::{Seed, make_seedable_rng}; + #[test] + fn private_key_debug_hides_key_material() { + let mut rng = make_true_rng(); + let (sk, _pk) = SchnorrkelPrivateKey::new(&mut rng); + + let encoded_sk = sk.encode(); + let debug_output = format!("{sk:?}"); + + assert!(debug_output.contains("")); + // No byte of the encoded secret may appear anywhere in the formatted output. + for byte in &encoded_sk { + assert!(!debug_output.contains(&byte.to_string())); + } + } + #[test] fn key_serialization() { let mut rng = make_true_rng(); From 2ebfedfb74ecc11881443083f0aa4e5d018e232a Mon Sep 17 00:00:00 2001 From: nullPointerEnjoyer Date: Sat, 17 Oct 2026 18:22:10 +0200 Subject: [PATCH 2/2] test(crypto): pin the redacted vrf debug rendering exactly --- crypto/src/vrf/mod.rs | 9 +-------- crypto/src/vrf/schnorrkel/mod.rs | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/crypto/src/vrf/mod.rs b/crypto/src/vrf/mod.rs index 2c14efd440..dec529b652 100644 --- a/crypto/src/vrf/mod.rs +++ b/crypto/src/vrf/mod.rs @@ -367,14 +367,7 @@ mod tests { let mut rng = make_seedable_rng(Seed::from_entropy()); let (sk, _pk) = VRFPrivateKey::new_from_rng(&mut rng, VRFKeyKind::Schnorrkel); - let encoded_sk = sk.encode(); - let debug_output = format!("{sk:?}"); - - assert!(debug_output.contains("")); - // No byte of the encoded secret may appear anywhere in the formatted output. - for byte in &encoded_sk { - assert!(!debug_output.contains(&byte.to_string())); - } + assert_eq!(format!("{sk:?}"), "VRFPrivateKey()"); } #[rstest] diff --git a/crypto/src/vrf/schnorrkel/mod.rs b/crypto/src/vrf/schnorrkel/mod.rs index 9483881b00..8ae635c62b 100644 --- a/crypto/src/vrf/schnorrkel/mod.rs +++ b/crypto/src/vrf/schnorrkel/mod.rs @@ -234,14 +234,7 @@ mod tests { let mut rng = make_true_rng(); let (sk, _pk) = SchnorrkelPrivateKey::new(&mut rng); - let encoded_sk = sk.encode(); - let debug_output = format!("{sk:?}"); - - assert!(debug_output.contains("")); - // No byte of the encoded secret may appear anywhere in the formatted output. - for byte in &encoded_sk { - assert!(!debug_output.contains(&byte.to_string())); - } + assert_eq!(format!("{sk:?}"), "SchnorrkelPrivateKey()"); } #[test]