SHARAG256 0.1.1

A custom hashing algorithm with complex random number generation based on SHA-256 principles
Documentation
use std::f64::consts::PI;

// ─────────────────────────────────────────────────────────
// SHA-256 round constants (identical to Python's `k` list)
// ─────────────────────────────────────────────────────────
const K: [u32; 64] = [
    0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
    0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
    0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
    0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
    0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,
    0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
    0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
    0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
    0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
    0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
    0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,
    0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
    0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,
    0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
    0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
    0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2,
];

// ─────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────

#[inline]
fn right_rotate(x: u32, n: u32) -> u32 {
    x.rotate_right(n)
}

/// Integer factorial as f64  (n ≤ 10 in practice → no precision loss)
fn factorial(n: usize) -> f64 {
    (1..=n).fold(1.0_f64, |acc, i| acc * i as f64)
}

/// Lanczos approximation of the Gamma function.
/// Matches Python's math.gamma() to within a few ULP for positive integers.
fn gamma_fn(x: f64) -> f64 {
    const G: f64 = 7.0;
    const C: [f64; 9] = [
        0.99999999999980993,
        676.5203681218851,
        -1259.1392167224028,
        771.32342877765313,
        -176.61502916214059,
        12.507343278686905,
        -0.13857109526572012,
        9.9843695780195716e-6,
        1.5056327351493116e-7,
    ];

    if x < 0.5 {
        PI / ((PI * x).sin() * gamma_fn(1.0 - x))
    } else {
        let z = x - 1.0;
        let mut a = C[0];
        for i in 1..9 {
            a += C[i] / (z + i as f64);
        }
        let t = z + G + 0.5;
        (2.0 * PI).sqrt() * t.powf(z + 0.5) * (-t).exp() * a
    }
}

/// Mirrors Python's  bytes.decode('utf-8', errors='ignore'):
/// valid UTF-8 sequences are kept, invalid bytes are silently dropped.
fn decode_utf8_ignore(bytes: &[u8]) -> String {
    let mut result = String::with_capacity(bytes.len());
    let mut i = 0;
    while i < bytes.len() {
        match std::str::from_utf8(&bytes[i..]) {
            Ok(s) => {
                result.push_str(s);
                break;
            }
            Err(e) => {
                let up = e.valid_up_to();
                // SAFETY: from_utf8 guarantees bytes[i..i+up] is valid
                result.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[i..i + up]) });
                i += up;
                match e.error_len() {
                    Some(n) => i += n, // skip invalid byte(s)
                    None => break,
                }
            }
        }
    }
    result
}

// ─────────────────────────────────────────────────────────
// generate_complex_random_number3
// ─────────────────────────────────────────────────────────
fn generate_complex_random_number3(input_data: &[u8]) -> u64 {
    let decoded = decode_utf8_ignore(input_data);

    // Python:  if len(input_string) > 15: input_string = input_string[:15]
    // Python slicing is by Unicode code-point, so use .chars()
    let input_string: String = decoded.chars().take(15).collect();

    // Python:  ascii_values = [ord(char) for char in input_string]
    let code_points: Vec<u32> = input_string.chars().map(|c| c as u32).collect();

    if code_points.is_empty() {
        return 123456; // fixed seed for empty input
    }

    let length = code_points.len() as f64;
    let sum:    f64 = code_points.iter().map(|&x| x as f64).sum();
    let max_v:  f64 = *code_points.iter().max().unwrap() as f64;
    let min_v:  f64 = *code_points.iter().min().unwrap() as f64;
    let sum_sq: f64 = code_points.iter().map(|&x| (x as f64).powi(2)).sum();

    let result = sum.sin()
        + length.exp()
        + max_v.tan()
        + (length + 1e-10_f64).ln()         // math.log  == natural log
        + min_v.abs().sqrt()
        + factorial(length.min(10.0) as usize)
        + sum_sq
        - length.atan2(max_v)               // math.atan2(y, x)  → atan2(length, max)
        + length.cosh()
        + gamma_fn(length + 1.0);           // math.gamma(length + 1)

    (result.abs() as u64) % 1_000_000
}

// ─────────────────────────────────────────────────────────
// process_chunk
// ─────────────────────────────────────────────────────────
//
// NOTE ON THE TWO "PROCESSING STEPS" IN THE PYTHON:
//   The XOR-flip and the bit-shift both reassign the local `chunk` variable
//   AFTER w[0..16] has already been filled from the original chunk bytes.
//   The modified `chunk` is never read again, so those two steps are
//   pure dead code and have zero effect on the output.
//   They are intentionally omitted here for correctness AND speed.
//
fn process_chunk(chunk: &[u8], h_init: [u32; 8]) -> [u32; 8] {
    // --- message schedule ---
    let mut w = [0u32; 64];
    for i in 0..16 {
        w[i] = u32::from_be_bytes(chunk[i * 4..i * 4 + 4].try_into().unwrap());
    }
    for i in 16..64 {
        let s0 = right_rotate(w[i - 15], 7)
            ^ right_rotate(w[i - 15], 18)
            ^ (w[i - 15] >> 3);
        let s1 = right_rotate(w[i - 2], 17)
            ^ right_rotate(w[i - 2], 19)
            ^ (w[i - 2] >> 10);
        w[i] = w[i - 16]
            .wrapping_add(s0)
            .wrapping_add(w[i - 7])
            .wrapping_add(s1);
    }

    // --- compression ---
    let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = h_init;

    for i in 0..64 {
        let s1   = right_rotate(e, 6) ^ right_rotate(e, 11) ^ right_rotate(e, 25);
        let ch   = (e & f) ^ ((!e) & g);   // Python: (e & f) ^ (~e & g)  — ~u32 is bit-NOT
        let temp1 = h
            .wrapping_add(s1)
            .wrapping_add(ch)
            .wrapping_add(K[i])
            .wrapping_add(w[i]);
        let s0   = right_rotate(a, 2) ^ right_rotate(a, 13) ^ right_rotate(a, 22);
        let maj  = (a & b) ^ (a & c) ^ (b & c);
        let temp2 = s0.wrapping_add(maj);

        h = g; g = f; f = e;
        e = d.wrapping_add(temp1);
        d = c; c = b; b = a;
        a = temp1.wrapping_add(temp2);
    }

    [
        h_init[0].wrapping_add(a),
        h_init[1].wrapping_add(b),
        h_init[2].wrapping_add(c),
        h_init[3].wrapping_add(d),
        h_init[4].wrapping_add(e),
        h_init[5].wrapping_add(f),
        h_init[6].wrapping_add(g),
        h_init[7].wrapping_add(h),
    ]
}

// ─────────────────────────────────────────────────────────
// patentmethod  (public entry-point)
// ─────────────────────────────────────────────────────────
//
// IMPORTANT NOTE ON THREADING:
//   The Python code uses ThreadPoolExecutor but submits EVERY chunk with the
//   SAME initial h_values (not chained SHA-256 style).  It then overwrites
//   h0..h7 with each future's result in order, so the final hash is simply
//   the result of the LAST chunk processed with the original h_init.
//   This is fully deterministic and requires no actual parallelism to replicate.
//
pub fn patentmethod(message: &str) -> String {
    let h_init: [u32; 8] = [
        0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
        0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
    ];

    let mut msg: Vec<u8> = message.as_bytes().to_vec();
    let original_length_bits: u64 = (msg.len() as u64) * 8;

    // Step 1 – append 0x80 padding bit
    msg.push(0x80);

    // Step 2 – special case for empty original message
    if msg.len() == 1 {
        msg.extend_from_slice(&[0u8; 55]);
    }

    // Step 3 – derive & append 6-byte random salt
    let random_value = generate_complex_random_number3(&msg);
    let rv_be = random_value.to_be_bytes(); // 8 bytes big-endian
    msg.extend_from_slice(&rv_be[2..]);     // take last 6 bytes  ≡  .to_bytes(6, "big")

    // Step 4 – pad to len ≡ 56 (mod 64)
    while msg.len() % 64 != 56 {
        msg.push(0);
    }

    // Step 5 – append original bit-length (8 bytes big-endian)
    msg.extend_from_slice(&original_length_bits.to_be_bytes());

    // Step 6 – process chunks (each independently against h_init; last result wins)
    let mut final_h = h_init;
    for chunk in msg.chunks(64) {
        final_h = process_chunk(chunk, h_init);
    }

    // Step 7 – combine into 256-bit hex string
    // u128 is only 128 bits, so we can't shift h0 by 224 etc.
    // Instead, concatenate each 32-bit word's 8-char hex representation directly.
    let [h0, h1, h2, h3, h4, h5, h6, h7] = final_h;
    format!(
        "{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}",
        h0, h1, h2, h3, h4, h5, h6, h7
    )
}

// ─────────────────────────────────────────────────────────
// main – same test vectors you can run against the Python
// ─────────────────────────────────────────────────────────
fn main() {
    let cases = [
        "",
        "hello",
        "world",
        "test123",
        "anantehucnjldsnciejur380480-2=@#459mo-dlsgauranshi003003lavyabirthdayllllllllll",
    ];

    for msg in &cases {
        let hash = patentmethod(msg);
        println!("input : {:?}", msg);
        println!("hash  : {}", hash);
        println!();
    }
}