modality_utils/
hash_tax.rs

1use sha1::{Sha1, Digest};
2use sha2::{Sha256, Sha384, Sha512};
3use std::collections::HashMap;
4use std::error::Error;
5use num_bigint::BigUint;
6use num_bigint::ToBigUint;
7use num_traits::Num;
8
9const DEFAULT_MAX_TRIES: u128 = 100_000_000_000;
10const DEFAULT_HASH_FUNC_NAME: &str = "sha256";
11const DEFAULT_DIFFICULTY_COEFFICIENT: u128 = 0xffff;
12const DEFAULT_DIFFICULTY_EXPONENT: u128 = 0x1d;
13const DEFAULT_DIFFICULTY_BASE: u128 = 8;
14
15lazy_static::lazy_static! {
16    static ref HASH_FUNC_HEXADECIMAL_LENGTH: HashMap<&'static str, usize> = {
17        let mut map = HashMap::new();
18        map.insert("sha1", 40);
19        map.insert("sha256", 64);
20        map.insert("sha384", 96);
21        map.insert("sha512", 128);
22        map
23    };
24}
25
26#[allow(dead_code)]
27pub fn mine(
28    data: &str,
29    difficulty: u128,
30    max_tries: Option<u128>,
31    hash_func_name: Option<&str>,
32) -> Result<u128, Box<dyn Error>> {
33    let max_tries = max_tries.unwrap_or(DEFAULT_MAX_TRIES);
34    let hash_func_name = hash_func_name.unwrap_or(DEFAULT_HASH_FUNC_NAME);
35
36    let mut nonce = 0;
37    let mut try_count = 0;
38
39    while try_count < max_tries {
40        try_count += 1;
41        let hash = hash_with_nonce(data, nonce, hash_func_name)?;
42        if is_hash_acceptable(&hash, difficulty, hash_func_name) {
43            return Ok(nonce);
44        }
45        nonce += 1;
46    }
47
48    Err("maxTries reached, no nonce found".into())
49}
50
51#[allow(dead_code)]
52pub fn hash_with_nonce(data: &str, nonce: u128, hash_func_name: &str) -> Result<String, Box<dyn Error>> {
53    let hash = match hash_func_name {
54        "sha1" => {
55            let mut hasher = Sha1::new();
56            hasher.update(format!("{}{}", data, nonce));
57            format!("{:x}", hasher.finalize())
58        }
59        "sha256" => {
60            let mut hasher = Sha256::new();
61            hasher.update(format!("{}{}", data, nonce));
62            format!("{:x}", hasher.finalize())
63        }
64        "sha384" => {
65            let mut hasher = Sha384::new();
66            hasher.update(format!("{}{}", data, nonce));
67            format!("{:x}", hasher.finalize())
68        }
69        "sha512" => {
70            let mut hasher = Sha512::new();
71            hasher.update(format!("{}{}", data, nonce));
72            format!("{:x}", hasher.finalize())
73        }
74        _ => return Err(format!("Unsupported hash function: {}", hash_func_name).into()),
75    };
76
77    Ok(hash)
78}
79
80pub fn difficulty_to_target_hash(
81    difficulty: u128,
82    hash_func_name: &str,
83    coefficient: u128,
84    exponent: u128,
85    base: u128,
86) -> String {
87    let _hex_length = HASH_FUNC_HEXADECIMAL_LENGTH[hash_func_name];
88    let max_target = coefficient.to_biguint().unwrap() << (exponent * base);
89    let target_bignum = max_target / difficulty;
90    target_bignum.to_str_radix(16)
91}
92
93pub fn is_hash_acceptable(hash: &str, difficulty: u128, hash_func_name: &str) -> bool {
94    let target_hash = difficulty_to_target_hash(difficulty, hash_func_name, DEFAULT_DIFFICULTY_COEFFICIENT, DEFAULT_DIFFICULTY_EXPONENT, DEFAULT_DIFFICULTY_BASE);
95    let hash_big_int = BigUint::from_str_radix(hash, 16).unwrap();
96    let target_big_int = BigUint::from_str_radix(&target_hash, 16).unwrap();
97    hash_big_int < target_big_int
98}
99
100#[allow(dead_code)]
101pub fn validate_nonce(data: &str, nonce: u128, difficulty: u128, hash_func_name: &str) -> Result<bool, Box<dyn Error>> {
102    let hash = hash_with_nonce(data, nonce, hash_func_name)?;
103    Ok(is_hash_acceptable(&hash, difficulty, hash_func_name))
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn it_works() {
112        let data = String::from("data");
113        let nonce = mine(&data, 500, None, None).unwrap();
114        assert_eq!(nonce, 2401);
115    }
116}