Skip to content

Commit ecfb115

Browse files
committed
Redact seed debug output
Avoid exposing mnemonic phrases, passphrases, or raw entropy through Debug formatting on core and UniFFI seed types. Add regression coverage so future changes do not accidentally reintroduce secret-bearing debug output.
1 parent c8f6c92 commit ecfb115

2 files changed

Lines changed: 80 additions & 2 deletions

File tree

orange-sdk/src/ffi/orange/config.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::collections::HashMap;
2+
use std::fmt;
23
use std::ops::Deref;
34
use std::path::PathBuf;
45
use std::str::FromStr;
@@ -22,7 +23,7 @@ use crate::logging::LoggerType;
2223
use crate::trusted_wallet::ExtraConfig as OrangeExtraConfig;
2324
use crate::{impl_from_core_type, impl_into_core_type};
2425

25-
#[derive(Debug, Clone, uniffi::Enum)]
26+
#[derive(Clone, uniffi::Enum)]
2627
pub enum Seed {
2728
/// A BIP 39 mnemonic seed.
2829
MnemonicSeed {
@@ -35,6 +36,19 @@ pub enum Seed {
3536
Seed64(Vec<u8>),
3637
}
3738

39+
impl fmt::Debug for Seed {
40+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41+
match self {
42+
Seed::MnemonicSeed { .. } => f
43+
.debug_struct("MnemonicSeed")
44+
.field("mnemonic", &"<redacted>")
45+
.field("passphrase", &"<redacted>")
46+
.finish(),
47+
Seed::Seed64(_) => f.debug_tuple("Seed64").field(&"<redacted>").finish(),
48+
}
49+
}
50+
}
51+
3852
impl TryInto<OrangeSeed> for Seed {
3953
type Error = ConfigError;
4054
fn try_into(self) -> Result<OrangeSeed, Self::Error> {
@@ -301,3 +315,29 @@ impl TryFrom<WalletConfig> for OrangeWalletConfig {
301315
})
302316
}
303317
}
318+
319+
#[cfg(test)]
320+
mod tests {
321+
use super::*;
322+
use crate::ffi::ldk_node::Mnemonic;
323+
324+
#[test]
325+
fn ffi_seed_debug_redacts_seed64_bytes() {
326+
let seed = Seed::Seed64(vec![42; 64]);
327+
328+
assert_eq!(format!("{seed:?}"), "Seed64(\"<redacted>\")");
329+
}
330+
331+
#[test]
332+
fn ffi_seed_debug_redacts_mnemonic_and_passphrase() {
333+
let mnemonic = Arc::new(
334+
Mnemonic::from_entropy(vec![0; 16]).expect("static test entropy should be valid"),
335+
);
336+
let seed = Seed::MnemonicSeed { mnemonic, passphrase: Some("test passphrase".to_owned()) };
337+
let debug = format!("{seed:?}");
338+
339+
assert_eq!(debug, "MnemonicSeed { mnemonic: \"<redacted>\", passphrase: \"<redacted>\" }");
340+
assert!(!debug.contains("abandon"));
341+
assert!(!debug.contains("test passphrase"));
342+
}
343+
}

orange-sdk/src/lib.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ pub struct Wallet {
143143
}
144144

145145
/// Represents the seed used for wallet generation.
146-
#[derive(Debug, Clone)]
146+
#[derive(Clone)]
147147
pub enum Seed {
148148
/// A BIP 39 mnemonic seed.
149149
Mnemonic {
@@ -156,6 +156,19 @@ pub enum Seed {
156156
Seed64([u8; 64]),
157157
}
158158

159+
impl Debug for Seed {
160+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161+
match self {
162+
Seed::Mnemonic { .. } => f
163+
.debug_struct("Mnemonic")
164+
.field("mnemonic", &"<redacted>")
165+
.field("passphrase", &"<redacted>")
166+
.finish(),
167+
Seed::Seed64(_) => f.debug_tuple("Seed64").field(&"<redacted>").finish(),
168+
}
169+
}
170+
}
171+
159172
impl Seed {
160173
pub(crate) fn to_node_entropy(&self) -> NodeEntropy {
161174
match self {
@@ -1489,3 +1502,28 @@ impl Wallet {
14891502
Ok(())
14901503
}
14911504
}
1505+
1506+
#[cfg(test)]
1507+
mod tests {
1508+
use super::*;
1509+
use ldk_node::bip39::Mnemonic;
1510+
1511+
#[test]
1512+
fn seed_debug_redacts_seed64_bytes() {
1513+
let seed = Seed::Seed64([42; 64]);
1514+
1515+
assert_eq!(format!("{seed:?}"), "Seed64(\"<redacted>\")");
1516+
}
1517+
1518+
#[test]
1519+
fn seed_debug_redacts_mnemonic_and_passphrase() {
1520+
let mnemonic =
1521+
Mnemonic::from_entropy(&[0; 16]).expect("static test entropy should be valid");
1522+
let seed = Seed::Mnemonic { mnemonic, passphrase: Some("test passphrase".to_owned()) };
1523+
let debug = format!("{seed:?}");
1524+
1525+
assert_eq!(debug, "Mnemonic { mnemonic: \"<redacted>\", passphrase: \"<redacted>\" }");
1526+
assert!(!debug.contains("abandon"));
1527+
assert!(!debug.contains("test passphrase"));
1528+
}
1529+
}

0 commit comments

Comments
 (0)