bidirectional_hash 0.1.0

A simple implementation of a bidirectional hash function using a Feistel network.
Documentation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Input {
    I32(i32),
    U32(u32),
    I64(i64),
    U64(u64),
    I128(i128),
    U128(u128),
}

impl From<i32> for Input {
    fn from(i: i32) -> Self {
        Input::I32(i)
    }
}

impl From<u32> for Input {
    fn from(u: u32) -> Self {
        Input::U32(u)
    }
}

impl From<i64> for Input {
    fn from(i: i64) -> Self {
        Input::I64(i)
    }
}

impl From<u64> for Input {
    fn from(u: u64) -> Self {
        Input::U64(u)
    }
}

impl From<i128> for Input {
    fn from(i: i128) -> Self {
        Input::I128(i)
    }
}

impl From<u128> for Input {
    fn from(u: u128) -> Self {
        Input::U128(u)
    }
}

pub fn hash(input: Input) -> Input {
    match input {
        Input::I32(i) => Input::I32(internal::hash(i)),
        Input::U32(u) => Input::U32(internal::hash(u as i32) as u32),
        Input::I64(i) => {
            Input::I64(hash_64(i as u64) as i64)
        },
        Input::U64(u) => {
            Input::U64(hash_64(u))
        },
        Input::I128(i) => {
            let out1 = hash_64(i as u64);
            let out2 = hash_64((i >> 64) as u64);

            Input::I128(((out2 as i128) << 64) | (out1 as i128 & 0xFFFFFFFFFFFFFFFF))
        },
        Input::U128(u) => {
            let out1 = hash_64(u as u64);
            let out2 = hash_64((u >> 64) as u64);

            Input::U128(((out2 as u128) << 64) | (out1 as u128 & 0xFFFFFFFFFFFFFFFF))
        },
    }
}

fn hash_64(u: u64) -> u64 {
    let out1 = internal::hash(u as i32);
    let out2 = internal::hash((u >> 32) as i32);
    ((out2 as u64) << 32) | (out1 as u64 & 0xFFFFFFFF)
}

mod internal {
    // https://wiki.postgresql.org/wiki/Pseudo_encrypt
    pub(crate) fn hash(input: i32) -> i32 {
        let mut l1 = (input >> 16) & 65535;
        let mut r1 = input & 65535;
        let mut i = 0;
        while i < 3 {
            let l2 = r1;
            let r2 = l1 ^ ((((1366 * r1 + 150889) % 714025) as f64 / 714025.0 * 32767.0) as i32);
            l1 = l2;
            r1 = r2;
            i += 1;
        }
        (r1 << 16) + l1
    }
}

#[cfg(test)]
macro_rules! create_tests {
    ($type:ty) => {
        // Convert type to a string to use in function names
        paste::item! {
            #[test]
            fn [<it_works_$type>]() {
                let input: $type = rand::random();
                let input = input.into();
                let result = hash(input);
                assert_ne!(result, 0.into());
                assert_ne!(input, result);
            }

            #[test]
            fn [<bidirectional_$type>]() {
                let input: $type = rand::random();
                let input = input.into();
                let result = hash(input);
                assert_ne!(result, 0.into());
                assert_ne!(input, result);
                assert_eq!(hash(result), input);
            }
        }
    };
}

#[cfg(test)]
mod tests {
    #![allow(unused_imports)] // required for macro
    use rand::random;
    use super::*;

    create_tests!(i32);
    create_tests!(u32);
    create_tests!(i64);
    create_tests!(u64);
    create_tests!(i128);
    create_tests!(u128);
}