use rand::Rng;
use sha2::{Digest, Sha256, Sha512};
const DEFAULT_DIFFICULTY: usize = 4;
#[derive(Clone,Debug,Default)]
pub enum HashAlgorithm {
#[default]
Sha256,
Sha512
}
#[derive(Debug, Clone)]
pub struct Challenge {
pub hash_algorithm: HashAlgorithm,
pub puzzle: String,
pub difficulty: usize,
}
#[derive(Debug, Clone)]
pub struct Solution {
pub nonce: u64,
}
pub fn generate_challenge( difficulty: Option<usize>, hash_algorithm: Option<HashAlgorithm>) -> Challenge {
let hash_algorithm = hash_algorithm.unwrap_or_default();
let difficulty = difficulty.unwrap_or(DEFAULT_DIFFICULTY);
let puzzle = generate_random_string(16);
Challenge {
hash_algorithm,
puzzle,
difficulty,
}
}
pub fn verify_solution(challenge: &Challenge, solution: &Solution) -> bool {
let hash_hex = compute_hash(&challenge.hash_algorithm, &challenge.puzzle, solution.nonce);
hash_hex.starts_with(&"0".repeat(challenge.difficulty))
}
fn generate_random_string(len: usize) -> String {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let mut rng = rand::thread_rng();
(0..len)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
}
fn compute_hash(algorithm: &HashAlgorithm, puzzle: &str, nonce: u64) -> String {
match algorithm {
HashAlgorithm::Sha256 => {
let mut hasher = Sha256::new();
hasher.update(puzzle.as_bytes());
hasher.update(nonce.to_string().as_bytes());
let result = hasher.finalize();
hex::encode(result)
}
HashAlgorithm::Sha512 => {
let mut hasher = Sha512::new();
hasher.update(puzzle.as_bytes());
hasher.update(nonce.to_string().as_bytes());
let result = hasher.finalize();
hex::encode(result)
}
}
}
pub fn solve_challenge(challenge: &Challenge) -> Solution {
let mut nonce = 0;
loop {
let hash_hex = compute_hash(&challenge.hash_algorithm, &challenge.puzzle, nonce);
if hash_hex.starts_with(&"0".repeat(challenge.difficulty)) {
return Solution { nonce };
}
nonce += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_generates_challenge() {
let challenge = generate_challenge(Some(5), None);
assert_eq!(challenge.difficulty, 5);
assert_eq!(challenge.puzzle.len(), 16);
}
#[test]
fn it_uses_default_difficulty() {
let challenge = generate_challenge(None, None);
assert_eq!(challenge.difficulty, DEFAULT_DIFFICULTY);
}
#[test]
fn it_verifies_a_correct_solution() {
let challenge = generate_challenge(Some(4), None);
let solution = solve_challenge(&challenge);
assert!(verify_solution(&challenge, &solution));
}
#[test]
fn it_rejects_an_incorrect_solution() {
let challenge = generate_challenge(Some(4), None);
let incorrect_solution = Solution { nonce: 12345 };
let mut hasher = Sha256::new();
hasher.update(challenge.puzzle.as_bytes());
hasher.update(incorrect_solution.nonce.to_string().as_bytes());
let result = hasher.finalize();
let hash_hex = hex::encode(result);
if !hash_hex.starts_with(&"0".repeat(challenge.difficulty)) {
assert!(!verify_solution(&challenge, &incorrect_solution));
} else {
println!("Warning: Random incorrect nonce happened to be correct.");
}
}
}