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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! An implementation of the Blakeout hash function.
//!
//! # Usage
//!
//! `Blakeout` can be used in the following way:
//!
//! ```rust
//! use crypto::digest::Digest;
//! use blakeout::Blakeout;
//!
//! // create a Blakeout object, it will hash your bytes for you
//! let mut hasher = Blakeout::default();
//!
//! // write input message
//! hasher.update(b"hello world");
//!
//! // read hash digest and consume hasher
//! let res = hasher.result_str();
//! assert_eq!(res, "6cc4bddb52416711be65e4b0201106fda4ceb0de48dfdce7e3a136e490d8586f");
//! ```

use digest::Digest;
use blake2::Blake2s;

const DEFAULT_HASH_SIZE: usize = 32;
const DEFAULT_HASH_COUNT: usize = 65536;

pub struct Blakeout {
    buffer: Vec<u8>,
    result: Vec<u8>,
    dirty: bool,
}

impl Default for Blakeout {
    fn default() -> Self {
        Blakeout::new()
    }
}

impl Blakeout {
    /// Creates new instance of Blakeout hasher
    pub fn new() -> Self {
        let mut buffer = Vec::new();
        buffer.resize(DEFAULT_HASH_SIZE * DEFAULT_HASH_COUNT, 0u8);
        Blakeout { buffer, result: Vec::new(), dirty: false }
    }

    /// Updates (hashes) supplied data
    pub fn update(&mut self, data: impl AsRef<[u8]>) {
        self.process_input(data.as_ref());
    }

    /// Resets current dirty state to start over
    pub fn reset(&mut self) {
        self.dirty = false;
    }

    /// Returns the size of result hash in bytes
    pub fn output_size() -> usize {
        DEFAULT_HASH_SIZE
    }

    /// Returns a slice of result hash, can be used multiple times
    pub fn result(&self) -> &[u8] {
        &self.result
    }

    /// Converts the result hash to a String and returns it
    pub fn result_str(&self) -> String {
        to_hex(&self.result)
    }

    fn process_input(&mut self, data: &[u8]) {
        let hash_size = DEFAULT_HASH_SIZE;
        let hash_count = self.buffer.len() / hash_size;
        let mut digest = Blake2s::default();

        if self.dirty {
            digest.update(&self.result);
        }
        // Preparing the scratchpad
        digest.update(data);
        Self::finalize_to(digest, &mut self.buffer.as_mut_slice()[0..hash_size]);
        let double_size = hash_size * 2;
        for x in (hash_size..hash_size * hash_count).step_by(hash_size) {
            let mut digest = Blake2s::default();
            let start = if x >= double_size { x - double_size } else { 0 };
            digest.update(&self.buffer[start..x]);
            Self::finalize_to(digest, &mut self.buffer.as_mut_slice()[x..(x + hash_size)]);
        }
        // Hashing whole buffer one way and another
        let mut digest = Blake2s::default();
        digest.update(&self.buffer);
        self.buffer.reverse();
        digest.update(&self.buffer);
        self.result.resize(DEFAULT_HASH_SIZE, 0u8);
        Self::finalize_to(digest, self.result.as_mut_slice());
        self.dirty = true;
    }

    fn finalize_to(digest: Blake2s, slice: &mut[u8]) {
        let buf = Digest::finalize(digest);
        slice.copy_from_slice(&buf[..]);
    }
}

/// Convert bytes array to HEX format
fn to_hex(buf: &[u8]) -> String {
    let mut result = String::new();
    for x in buf.iter() {
        result.push_str(&format!("{:01$x}", x, 2));
    }
    result
}

#[cfg(test)]
mod tests {
    use crate::{Blakeout, to_hex};
    const DATA: &[u8; 29] = b"Science is poetry of reality!";

    #[test]
    fn single_input() {
        let mut digest = Blakeout::default();
        digest.update(DATA);
        assert_eq!("4be892daff5d5432b43bf05c9d2ea4769daf2dd1ec482c23839ce5d6950e9e62", to_hex(&digest.result()));
    }

    #[test]
    fn double_input() {
        let mut digest = Blakeout::default();
        digest.update(DATA);
        digest.update(DATA);
        assert_eq!("a1b6cd16c9e718b876afb7bf4d61b64291a98a3dea0f20731da663b0358e68b9", to_hex(&digest.result()));
    }

    #[test]
    fn test_reset() {
        let mut digest = Blakeout::default();
        digest.update(DATA);
        let hash1 = digest.result_str();
        digest.reset();
        digest.update(DATA);
        let hash2 = digest.result_str();

        assert_eq!(hash1, hash2);
    }
}