use indicatif::ProgressBar;
use rayon::prelude::*;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Mutex;
use crate::sha256_chain::{
generate_chain, Sha256ChainVariant, ALL_VARIANTS, DEFAULT_CHAIN_DEPTH,
};
use super::{AnalysisConfig, AnalysisResult, AnalysisStatus, Analyzer};
pub struct Sha256ChainAnalyzer {
variant: Option<Sha256ChainVariant>,
chain_depth: u32,
}
impl Sha256ChainAnalyzer {
pub fn new() -> Self {
Self {
variant: None,
chain_depth: DEFAULT_CHAIN_DEPTH,
}
}
pub fn with_variant(variant: Sha256ChainVariant) -> Self {
Self {
variant: Some(variant),
chain_depth: DEFAULT_CHAIN_DEPTH,
}
}
pub fn with_chain_depth(mut self, depth: u32) -> Self {
self.chain_depth = depth;
self
}
fn variants_to_test(&self) -> Vec<Sha256ChainVariant> {
match self.variant {
Some(v) => vec![v],
None => ALL_VARIANTS.to_vec(),
}
}
fn analyze_exact(
&self,
key: &[u8; 32],
progress: Option<&ProgressBar>,
) -> AnalysisResult {
let variants = self.variants_to_test();
let chain_depth = self.chain_depth;
let found = AtomicBool::new(false);
let found_seed = AtomicU32::new(0);
let found_variant = Mutex::new(Sha256ChainVariant::Iterated);
let found_index = AtomicU32::new(0);
let found_full_key = Mutex::new([0u8; 32]);
let total = u32::MAX as u64 + 1;
if let Some(pb) = progress {
pb.set_length(total);
pb.set_message("sha256_chain brute-force");
}
let chunk_size = 1_000_000u32;
let progress_interval = 100_000u32;
let chunks: Vec<u32> = (0..=(u32::MAX / chunk_size)).collect();
chunks.par_iter().for_each(|&chunk_idx| {
if found.load(Ordering::Acquire) {
return;
}
let start = chunk_idx.saturating_mul(chunk_size);
let end = start.saturating_add(chunk_size - 1);
let mut last_progress = start;
for seed in start..=end {
if found.load(Ordering::Acquire) {
if let Some(pb) = progress {
pb.inc((seed - last_progress) as u64);
}
return;
}
let seed_bytes = seed.to_be_bytes();
for variant in &variants {
let chain = generate_chain(&seed_bytes, *variant, chain_depth);
for (idx, chain_key) in chain.iter().enumerate() {
if chain_key == key {
found_seed.store(seed, Ordering::Release);
found.store(true, Ordering::Release);
found_index.store(idx as u32, Ordering::Release);
if let Ok(mut v) = found_variant.lock() {
*v = *variant;
}
if let Ok(mut fk) = found_full_key.lock() {
*fk = *chain_key;
}
if let Some(pb) = progress {
pb.inc((seed - last_progress) as u64);
}
return;
}
}
}
if let Some(pb) = progress {
if seed - last_progress >= progress_interval {
pb.inc((seed - last_progress) as u64);
last_progress = seed;
}
}
}
if let Some(pb) = progress {
pb.inc((end - last_progress + 1) as u64);
}
});
if let Some(pb) = progress {
pb.finish_and_clear();
}
if found.load(Ordering::Acquire) {
let seed = found_seed.load(Ordering::Acquire);
let index = found_index.load(Ordering::Acquire);
let variant = *found_variant.lock().unwrap();
AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::Confirmed,
details: Some(format!(
"seed={} (0x{:08x}), variant={}, chain_index={}",
seed, seed, variant.name(), index
)),
}
} else {
let variant_names: Vec<&str> = variants.iter().map(|v| v.name()).collect();
AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::NotFound,
details: Some(format!(
"checked {} seeds, variants=[{}], chain_depth={}",
total,
variant_names.join(", "),
chain_depth
)),
}
}
}
fn analyze_masked(
&self,
target: &[u8; 32],
mask_bits: u8,
progress: Option<&ProgressBar>,
) -> AnalysisResult {
let target_u64 = u64::from_be_bytes(target[24..32].try_into().unwrap());
let mask: u64 = if mask_bits >= 64 {
u64::MAX
} else {
(1u64 << mask_bits) - 1
};
let high_bit: u64 = if mask_bits == 0 {
0
} else if mask_bits >= 64 {
1u64 << 63
} else {
1u64 << (mask_bits - 1)
};
let variants = self.variants_to_test();
let chain_depth = self.chain_depth;
let found = AtomicBool::new(false);
let found_seed = AtomicU32::new(0);
let found_variant = Mutex::new(Sha256ChainVariant::Iterated);
let found_index = AtomicU32::new(0);
let found_full_key = Mutex::new([0u8; 32]);
let total = u32::MAX as u64 + 1;
if let Some(pb) = progress {
pb.set_length(total);
pb.set_message("sha256_chain masked brute-force");
}
let chunk_size = 1_000_000u32;
let progress_interval = 100_000u32;
let chunks: Vec<u32> = (0..=(u32::MAX / chunk_size)).collect();
chunks.par_iter().for_each(|&chunk_idx| {
if found.load(Ordering::Acquire) {
return;
}
let start = chunk_idx.saturating_mul(chunk_size);
let end = start.saturating_add(chunk_size - 1);
let mut last_progress = start;
for seed in start..=end {
if found.load(Ordering::Acquire) {
if let Some(pb) = progress {
pb.inc((seed - last_progress) as u64);
}
return;
}
let seed_bytes = seed.to_be_bytes();
for variant in &variants {
let chain = generate_chain(&seed_bytes, *variant, chain_depth);
for (idx, chain_key) in chain.iter().enumerate() {
let key_u64 = u64::from_be_bytes(chain_key[24..32].try_into().unwrap());
let masked = (key_u64 & mask) | high_bit;
if masked == target_u64 {
found_seed.store(seed, Ordering::Release);
found.store(true, Ordering::Release);
found_index.store(idx as u32, Ordering::Release);
if let Ok(mut v) = found_variant.lock() {
*v = *variant;
}
if let Ok(mut fk) = found_full_key.lock() {
*fk = *chain_key;
}
if let Some(pb) = progress {
pb.inc((seed - last_progress) as u64);
}
return;
}
}
}
if let Some(pb) = progress {
if seed - last_progress >= progress_interval {
pb.inc((seed - last_progress) as u64);
last_progress = seed;
}
}
}
if let Some(pb) = progress {
pb.inc((end - last_progress + 1) as u64);
}
});
if let Some(pb) = progress {
pb.finish_and_clear();
}
if found.load(Ordering::Acquire) {
let seed = found_seed.load(Ordering::Acquire);
let index = found_index.load(Ordering::Acquire);
let variant = *found_variant.lock().unwrap();
let full_key = *found_full_key.lock().unwrap();
AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::Confirmed,
details: Some(format!(
"seed={} (0x{:08x}), variant={}, chain_index={}, full_key={}, mask_bits={}",
seed, seed, variant.name(), index, hex::encode(full_key), mask_bits
)),
}
} else {
let variant_names: Vec<&str> = variants.iter().map(|v| v.name()).collect();
AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::NotFound,
details: Some(format!(
"checked {} seeds with {}-bit mask, variants=[{}], chain_depth={}",
total,
mask_bits,
variant_names.join(", "),
chain_depth
)),
}
}
}
fn analyze_cascading(
&self,
targets: &[(u8, u64)],
progress: Option<&ProgressBar>,
) -> AnalysisResult {
let variants = self.variants_to_test();
let found = AtomicBool::new(false);
let found_seed = AtomicU32::new(0);
let found_variant = Mutex::new(Sha256ChainVariant::Iterated);
let found_keys: Mutex<Vec<[u8; 32]>> = Mutex::new(Vec::new());
let total = u32::MAX as u64 + 1;
if let Some(pb) = progress {
pb.set_length(total);
pb.set_message("sha256_chain cascade brute-force");
}
let chunk_size = 1_000_000u32;
let progress_interval = 100_000u32;
let chunks: Vec<u32> = (0..=(u32::MAX / chunk_size)).collect();
chunks.par_iter().for_each(|&chunk_idx| {
if found.load(Ordering::Acquire) {
return;
}
let start = chunk_idx.saturating_mul(chunk_size);
let end = start.saturating_add(chunk_size - 1);
let mut last_progress = start;
for seed in start..=end {
if found.load(Ordering::Acquire) {
if let Some(pb) = progress {
pb.inc((seed - last_progress) as u64);
}
return;
}
let seed_bytes = seed.to_be_bytes();
for variant in &variants {
let chain = generate_chain(&seed_bytes, *variant, targets.len() as u32);
let mut all_matched = true;
for (idx, (bits, target)) in targets.iter().enumerate() {
if idx >= chain.len() {
all_matched = false;
break;
}
let key = &chain[idx];
let key_u64 = u64::from_be_bytes(key[24..32].try_into().unwrap());
let mask: u64 = if *bits >= 64 {
u64::MAX
} else {
(1u64 << bits) - 1
};
let high_bit: u64 = if *bits == 0 {
0
} else if *bits >= 64 {
1u64 << 63
} else {
1u64 << (bits - 1)
};
let masked = (key_u64 & mask) | high_bit;
if masked != *target {
all_matched = false;
break;
}
}
if all_matched {
found_seed.store(seed, Ordering::Release);
found.store(true, Ordering::Release);
if let Ok(mut v) = found_variant.lock() {
*v = *variant;
}
if let Ok(mut fk) = found_keys.lock() {
*fk = chain[..targets.len()].to_vec();
}
if let Some(pb) = progress {
pb.inc((seed - last_progress) as u64);
}
return;
}
}
if let Some(pb) = progress {
if seed - last_progress >= progress_interval {
pb.inc((seed - last_progress) as u64);
last_progress = seed;
}
}
}
if let Some(pb) = progress {
pb.inc((end - last_progress + 1) as u64);
}
});
if let Some(pb) = progress {
pb.finish_and_clear();
}
if found.load(Ordering::Acquire) {
let seed = found_seed.load(Ordering::Acquire);
let variant = *found_variant.lock().unwrap();
let keys = found_keys.lock().unwrap().clone();
let details = format_cascade_result(seed, variant, targets, &keys);
AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::Confirmed,
details: Some(details),
}
} else {
let variant_names: Vec<&str> = variants.iter().map(|v| v.name()).collect();
let target_desc: Vec<String> = targets
.iter()
.map(|(bits, target)| format!("P{}:0x{:x}", bits, target))
.collect();
AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::NotFound,
details: Some(format!(
"checked {} seeds, variants=[{}], cascade=[{}]",
total,
variant_names.join(", "),
target_desc.join(",")
)),
}
}
}
}
fn format_cascade_result(
seed: u32,
variant: Sha256ChainVariant,
targets: &[(u8, u64)],
keys: &[[u8; 32]],
) -> String {
let mut lines = vec![format!(
"seed={} (0x{:08x}), variant={}",
seed, seed, variant.name()
)];
for ((bits, target), key) in targets.iter().zip(keys.iter()) {
lines.push(format!(
" P{}: target=0x{:x}, full_key={}",
bits,
target,
hex::encode(key)
));
}
lines.join("\n")
}
impl Default for Sha256ChainAnalyzer {
fn default() -> Self {
Self::new()
}
}
impl Analyzer for Sha256ChainAnalyzer {
fn name(&self) -> &'static str {
"sha256_chain"
}
fn supports_mask(&self) -> bool {
true
}
fn is_brute_force(&self) -> bool {
true
}
fn analyze(
&self,
key: &[u8; 32],
config: &AnalysisConfig,
progress: Option<&ProgressBar>,
) -> AnalysisResult {
if let Some(ref targets) = config.cascade_targets {
return self.analyze_cascading(targets, progress);
}
match config.mask_bits {
Some(bits) => self.analyze_masked(key, bits, progress),
None => self.analyze_exact(key, progress),
}
}
#[cfg(feature = "gpu")]
fn supports_gpu(&self) -> bool {
true
}
#[cfg(feature = "gpu")]
fn analyze_gpu(
&self,
ctx: &crate::gpu::GpuContext,
key: &[u8; 32],
config: &AnalysisConfig,
progress: Option<&ProgressBar>,
) -> Result<AnalysisResult, crate::gpu::GpuError> {
use crate::gpu::GpuSha256ChainPipeline;
if config.mask_bits.is_some() && config.cascade_targets.is_none() {
return Err(crate::gpu::GpuError::Other(
"GPU masked analysis requires cascade filter, falling back to CPU".into(),
));
}
let pipeline = GpuSha256ChainPipeline::new(ctx)?;
let variants = self.variants_to_test();
if let Some(ref targets) = config.cascade_targets {
let total = u32::MAX as u64 + 1;
if let Some(pb) = progress {
pb.set_length(total);
pb.set_message("sha256_chain GPU cascade brute-force");
}
let result = pipeline.search_cascade(
targets,
&variants,
4_000_000,
|seeds_tested, found| {
if let Some(pb) = progress {
pb.set_position(seeds_tested);
}
found.is_none()
},
)?;
if let Some(pb) = progress {
pb.finish_and_clear();
}
if let Some(seed) = result.found_seed {
let variant = result.found_variant.unwrap();
let keys = generate_chain(&seed.to_be_bytes(), variant, targets.len() as u32);
let details = format_cascade_result(seed, variant, targets, &keys);
return Ok(AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::Confirmed,
details: Some(details),
});
} else {
let variant_names: Vec<&str> = variants.iter().map(|v| v.name()).collect();
let target_desc: Vec<String> = targets
.iter()
.map(|(bits, target)| format!("P{}:0x{:x}", bits, target))
.collect();
return Ok(AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::NotFound,
details: Some(format!(
"checked {} seeds (GPU), variants=[{}], cascade=[{}]",
result.seeds_tested,
variant_names.join(", "),
target_desc.join(",")
)),
});
}
}
let total = u32::MAX as u64 + 1;
if let Some(pb) = progress {
pb.set_length(total);
pb.set_message("sha256_chain GPU brute-force");
}
let result = pipeline.search_exact(
key,
&variants,
self.chain_depth,
4_000_000,
|seeds_tested, found| {
if let Some(pb) = progress {
pb.set_position(seeds_tested);
}
found.is_none()
},
)?;
if let Some(pb) = progress {
pb.finish_and_clear();
}
if let Some(seed) = result.found_seed {
let variant = result.found_variant.unwrap();
let chain_index = result.found_chain_index.unwrap();
Ok(AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::Confirmed,
details: Some(format!(
"seed={} (0x{:08x}), variant={}, chain_index={} (GPU)",
seed, seed, variant.name(), chain_index
)),
})
} else {
let variant_names: Vec<&str> = variants.iter().map(|v| v.name()).collect();
Ok(AnalysisResult {
analyzer: self.name(),
status: AnalysisStatus::NotFound,
details: Some(format!(
"checked {} seeds (GPU), variants=[{}], chain_depth={}",
result.seeds_tested,
variant_names.join(", "),
self.chain_depth
)),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn apply_mask(key: &[u8; 32], bits: u8) -> u64 {
let key_u64 = u64::from_be_bytes(key[24..32].try_into().unwrap());
let mask: u64 = if bits >= 64 {
u64::MAX
} else {
(1u64 << bits) - 1
};
let high_bit: u64 = 1u64 << (bits - 1);
(key_u64 & mask) | high_bit
}
fn generate_cascade_targets(
seed: u32,
variant: Sha256ChainVariant,
bit_widths: &[u8],
) -> Vec<(u8, u64)> {
let chain = generate_chain(&seed.to_be_bytes(), variant, bit_widths.len() as u32);
bit_widths
.iter()
.zip(chain.iter())
.map(|(&bits, key)| (bits, apply_mask(key, bits)))
.collect()
}
#[test]
fn test_analyzer_name() {
let analyzer = Sha256ChainAnalyzer::new();
assert_eq!(analyzer.name(), "sha256_chain");
}
#[test]
fn test_supports_mask() {
let analyzer = Sha256ChainAnalyzer::new();
assert!(analyzer.supports_mask());
}
#[test]
fn test_is_brute_force() {
let analyzer = Sha256ChainAnalyzer::new();
assert!(analyzer.is_brute_force());
}
#[test]
fn test_with_variant() {
let analyzer = Sha256ChainAnalyzer::with_variant(Sha256ChainVariant::Iterated);
assert_eq!(analyzer.variant, Some(Sha256ChainVariant::Iterated));
}
#[test]
fn test_with_chain_depth() {
let analyzer = Sha256ChainAnalyzer::new().with_chain_depth(20);
assert_eq!(analyzer.chain_depth, 20);
}
#[test]
fn test_variants_to_test_all() {
let analyzer = Sha256ChainAnalyzer::new();
let variants = analyzer.variants_to_test();
assert_eq!(variants.len(), 4);
}
#[test]
fn test_variants_to_test_specific() {
let analyzer = Sha256ChainAnalyzer::with_variant(Sha256ChainVariant::Iterated);
let variants = analyzer.variants_to_test();
assert_eq!(variants.len(), 1);
assert_eq!(variants[0], Sha256ChainVariant::Iterated);
}
#[test]
#[ignore]
fn test_find_known_seed_exact() {
let seed = 12345u32;
let variant = Sha256ChainVariant::Iterated;
let chain = generate_chain(&seed.to_be_bytes(), variant, 5);
let target_key = chain[2];
let analyzer = Sha256ChainAnalyzer::with_variant(variant).with_chain_depth(5);
let config = AnalysisConfig::default();
let result = analyzer.analyze(&target_key, &config, None);
assert_eq!(result.status, AnalysisStatus::Confirmed);
let details = result.details.unwrap();
assert!(details.contains("seed=12345"));
assert!(details.contains("chain_index=2"));
}
#[test]
#[ignore]
fn test_find_known_seed_masked() {
let seed = 42u32;
let variant = Sha256ChainVariant::Iterated;
let chain = generate_chain(&seed.to_be_bytes(), variant, 3);
let full_key = chain[0];
let mask_bits = 10u8;
let masked_target = apply_mask(&full_key, mask_bits);
let mut target_key = [0u8; 32];
target_key[24..32].copy_from_slice(&masked_target.to_be_bytes());
let analyzer = Sha256ChainAnalyzer::with_variant(variant).with_chain_depth(3);
let config = AnalysisConfig {
mask_bits: Some(mask_bits),
cascade_targets: None,
};
let result = analyzer.analyze(&target_key, &config, None);
assert_eq!(result.status, AnalysisStatus::Confirmed);
let details = result.details.unwrap();
assert!(details.contains("seed=42"));
}
#[test]
#[ignore]
fn test_cascade_finds_known_seed() {
let known_seed = 100u32;
let variant = Sha256ChainVariant::Iterated;
let targets = generate_cascade_targets(known_seed, variant, &[5, 10, 15]);
let config = AnalysisConfig {
mask_bits: None,
cascade_targets: Some(targets),
};
let analyzer = Sha256ChainAnalyzer::with_variant(variant);
let result = analyzer.analyze(&[0u8; 32], &config, None);
assert_eq!(result.status, AnalysisStatus::Confirmed);
let details = result.details.unwrap();
assert!(details.contains("seed=100"));
}
#[test]
#[ignore]
fn test_cascade_indexed_binary() {
let known_seed = 500u32;
let variant = Sha256ChainVariant::IndexedBinary { big_endian: true };
let targets = generate_cascade_targets(known_seed, variant, &[5, 10, 15]);
let config = AnalysisConfig {
mask_bits: None,
cascade_targets: Some(targets),
};
let analyzer = Sha256ChainAnalyzer::with_variant(variant);
let result = analyzer.analyze(&[0u8; 32], &config, None);
assert_eq!(result.status, AnalysisStatus::Confirmed);
let details = result.details.unwrap();
assert!(details.contains("seed=500"));
assert!(details.contains("indexed:be"));
}
#[test]
#[ignore]
fn test_cascade_counter_mode() {
let known_seed = 999u32;
let variant = Sha256ChainVariant::IndexedString;
let targets = generate_cascade_targets(known_seed, variant, &[5, 10, 15]);
let config = AnalysisConfig {
mask_bits: None,
cascade_targets: Some(targets),
};
let analyzer = Sha256ChainAnalyzer::with_variant(variant);
let result = analyzer.analyze(&[0u8; 32], &config, None);
assert_eq!(result.status, AnalysisStatus::Confirmed);
let details = result.details.unwrap();
assert!(details.contains("seed=999"));
assert!(details.contains("counter"));
}
#[test]
#[ignore]
fn test_cascade_seed_zero() {
let known_seed = 0u32;
let variant = Sha256ChainVariant::Iterated;
let targets = generate_cascade_targets(known_seed, variant, &[5, 10]);
let config = AnalysisConfig {
mask_bits: None,
cascade_targets: Some(targets),
};
let analyzer = Sha256ChainAnalyzer::with_variant(variant);
let result = analyzer.analyze(&[0u8; 32], &config, None);
assert_eq!(result.status, AnalysisStatus::Confirmed);
let details = result.details.unwrap();
assert!(details.contains("seed=0"));
}
#[test]
fn test_cascade_targets_generation() {
let seed = 123u32;
let variant = Sha256ChainVariant::Iterated;
let targets = generate_cascade_targets(seed, variant, &[5, 10, 15]);
assert_eq!(targets.len(), 3);
assert_eq!(targets[0].0, 5);
assert_eq!(targets[1].0, 10);
assert_eq!(targets[2].0, 15);
for (bits, target) in &targets {
let high_bit = 1u64 << (bits - 1);
assert!(target & high_bit != 0, "high bit must be set for P{}", bits);
assert!(*target < (1u64 << bits), "target must fit in {} bits", bits);
}
}
#[test]
fn test_apply_mask() {
let mut key = [0u8; 32];
key[31] = 0x15;
let masked = apply_mask(&key, 5);
assert_eq!(masked, 0x15);
let masked = apply_mask(&key, 10);
let expected = (0x15 & 0x3ff) | 0x200;
assert_eq!(masked, expected);
}
}