modality_utils/
hash_tax.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use sha1::{Sha1, Digest};
use sha2::{Sha256, Sha384, Sha512};
use std::collections::HashMap;
use std::error::Error;
use num_bigint::BigUint;
use num_bigint::ToBigUint;
use num_traits::Num;

const DEFAULT_MAX_TRIES: u128 = 100_000_000_000;
const DEFAULT_HASH_FUNC_NAME: &str = "sha256";
const DEFAULT_DIFFICULTY_COEFFICIENT: u128 = 0xffff;
const DEFAULT_DIFFICULTY_EXPONENT: u128 = 0x1d;
const DEFAULT_DIFFICULTY_BASE: u128 = 8;

lazy_static::lazy_static! {
    static ref HASH_FUNC_HEXADECIMAL_LENGTH: HashMap<&'static str, usize> = {
        let mut map = HashMap::new();
        map.insert("sha1", 40);
        map.insert("sha256", 64);
        map.insert("sha384", 96);
        map.insert("sha512", 128);
        map
    };
}

#[allow(dead_code)]
pub fn mine(
    data: &str,
    difficulty: u128,
    max_tries: Option<u128>,
    hash_func_name: Option<&str>,
) -> Result<u128, Box<dyn Error>> {
    let max_tries = max_tries.unwrap_or(DEFAULT_MAX_TRIES);
    let hash_func_name = hash_func_name.unwrap_or(DEFAULT_HASH_FUNC_NAME);

    let mut nonce = 0;
    let mut try_count = 0;

    while try_count < max_tries {
        try_count += 1;
        let hash = hash_with_nonce(data, nonce, hash_func_name)?;
        if is_hash_acceptable(&hash, difficulty, hash_func_name) {
            return Ok(nonce);
        }
        nonce += 1;
    }

    Err("maxTries reached, no nonce found".into())
}

#[allow(dead_code)]
pub fn hash_with_nonce(data: &str, nonce: u128, hash_func_name: &str) -> Result<String, Box<dyn Error>> {
    let hash = match hash_func_name {
        "sha1" => {
            let mut hasher = Sha1::new();
            hasher.update(format!("{}{}", data, nonce));
            format!("{:x}", hasher.finalize())
        }
        "sha256" => {
            let mut hasher = Sha256::new();
            hasher.update(format!("{}{}", data, nonce));
            format!("{:x}", hasher.finalize())
        }
        "sha384" => {
            let mut hasher = Sha384::new();
            hasher.update(format!("{}{}", data, nonce));
            format!("{:x}", hasher.finalize())
        }
        "sha512" => {
            let mut hasher = Sha512::new();
            hasher.update(format!("{}{}", data, nonce));
            format!("{:x}", hasher.finalize())
        }
        _ => return Err(format!("Unsupported hash function: {}", hash_func_name).into()),
    };

    Ok(hash)
}

pub fn difficulty_to_target_hash(
    difficulty: u128,
    hash_func_name: &str,
    coefficient: u128,
    exponent: u128,
    base: u128,
) -> String {
    let _hex_length = HASH_FUNC_HEXADECIMAL_LENGTH[hash_func_name];
    let max_target = coefficient.to_biguint().unwrap() << (exponent * base);
    let target_bignum = max_target / difficulty;
    target_bignum.to_str_radix(16)
}

pub fn is_hash_acceptable(hash: &str, difficulty: u128, hash_func_name: &str) -> bool {
    let target_hash = difficulty_to_target_hash(difficulty, hash_func_name, DEFAULT_DIFFICULTY_COEFFICIENT, DEFAULT_DIFFICULTY_EXPONENT, DEFAULT_DIFFICULTY_BASE);
    let hash_big_int = BigUint::from_str_radix(hash, 16).unwrap();
    let target_big_int = BigUint::from_str_radix(&target_hash, 16).unwrap();
    hash_big_int < target_big_int
}

#[allow(dead_code)]
pub fn validate_nonce(data: &str, nonce: u128, difficulty: u128, hash_func_name: &str) -> Result<bool, Box<dyn Error>> {
    let hash = hash_with_nonce(data, nonce, hash_func_name)?;
    Ok(is_hash_acceptable(&hash, difficulty, hash_func_name))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let data = String::from("data");
        let nonce = mine(&data, 500, None, None).unwrap();
        assert_eq!(nonce, 2401);
    }
}