Skip to main content

readable_hash/
lib.rs

1//! Hashes like `a7b9c3d4e5f6...` are hard to read, compare, and remember.
2//! This crate transforms hash bytes into pronounceable text, making them
3//! easier on human eyes.
4//!
5//! You might use this when verifying file integrity visually, or when you
6//! need consistent pseudonyms for names and addresses without caring much
7//! about cryptographic strength. It's also handy during debugging when you
8//! want to quickly tell hashes apart.
9//!
10//! This crate is not trying to be the most secure, fastest, or most
11//! entropy-efficient solution. The goal is simply readability.
12
13use std::hash::{DefaultHasher, Hasher};
14
15#[cfg(feature = "shake256")]
16use sha3::Shake256;
17#[cfg(feature = "shake256")]
18use sha3::digest::{ExtendableOutput, Update as XofUpdate, XofReader};
19
20pub mod english_word;
21
22// ============================================================================
23// Core Traits
24// ============================================================================
25
26/// Trait for reading bytes from a hash output.
27pub trait ByteReader {
28    /// Read bytes into the destination buffer. Returns bytes read.
29    fn read(&mut self, dest: &mut [u8]) -> usize;
30
31    /// Returns remaining bytes, or `None` if infinite.
32    fn remaining(&self) -> Option<usize>;
33}
34
35/// Trait for hashers that produce readable hashes.
36pub trait ReadableHasher: Default {
37    type Reader: ByteReader;
38
39    fn update(&mut self, data: &[u8]);
40    fn finalize(self) -> Self::Reader;
41}
42
43// ============================================================================
44// StdHasher (8 bytes output)
45// ============================================================================
46
47#[derive(Default)]
48pub struct StdHasher<H: Hasher + Default = DefaultHasher> {
49    hasher: H,
50}
51
52impl<H: Hasher + Default> ReadableHasher for StdHasher<H> {
53    type Reader = StdHasherReader;
54
55    fn update(&mut self, data: &[u8]) {
56        self.hasher.write(data);
57    }
58
59    fn finalize(self) -> Self::Reader {
60        StdHasherReader {
61            bytes: self.hasher.finish().to_le_bytes(),
62            position: 0,
63        }
64    }
65}
66
67pub struct StdHasherReader {
68    bytes: [u8; 8],
69    position: usize,
70}
71
72impl ByteReader for StdHasherReader {
73    fn read(&mut self, dest: &mut [u8]) -> usize {
74        let available = 8 - self.position;
75        let bytes_to_read = dest.len().min(available);
76        dest[..bytes_to_read]
77            .copy_from_slice(&self.bytes[self.position..self.position + bytes_to_read]);
78        self.position += bytes_to_read;
79        bytes_to_read
80    }
81
82    fn remaining(&self) -> Option<usize> {
83        Some(8 - self.position)
84    }
85}
86
87// ============================================================================
88// Shake256Hasher (infinite output)
89// ============================================================================
90
91#[cfg(feature = "shake256")]
92#[derive(Default)]
93pub struct Shake256Hasher {
94    hasher: Shake256,
95}
96
97#[cfg(feature = "shake256")]
98impl ReadableHasher for Shake256Hasher {
99    type Reader = Shake256Reader;
100
101    fn update(&mut self, data: &[u8]) {
102        XofUpdate::update(&mut self.hasher, data);
103    }
104
105    fn finalize(self) -> Self::Reader {
106        Shake256Reader {
107            reader: self.hasher.finalize_xof(),
108        }
109    }
110}
111
112#[cfg(feature = "shake256")]
113pub struct Shake256Reader {
114    reader: sha3::Shake256Reader,
115}
116
117#[cfg(feature = "shake256")]
118impl ByteReader for Shake256Reader {
119    fn read(&mut self, dest: &mut [u8]) -> usize {
120        XofReader::read(&mut self.reader, dest);
121        dest.len()
122    }
123
124    fn remaining(&self) -> Option<usize> {
125        None
126    }
127}
128
129// ============================================================================
130// SliceReader - ByteReader for byte slices
131// ============================================================================
132
133/// A ByteReader that wraps a byte slice.
134pub struct SliceReader<'a> {
135    data: &'a [u8],
136    position: usize,
137}
138
139impl<'a> SliceReader<'a> {
140    pub fn new(data: &'a [u8]) -> Self {
141        Self { data, position: 0 }
142    }
143}
144
145impl<'a> ByteReader for SliceReader<'a> {
146    fn read(&mut self, dest: &mut [u8]) -> usize {
147        let available = self.data.len() - self.position;
148        let bytes_to_read = dest.len().min(available);
149        dest[..bytes_to_read]
150            .copy_from_slice(&self.data[self.position..self.position + bytes_to_read]);
151        self.position += bytes_to_read;
152        bytes_to_read
153    }
154
155    fn remaining(&self) -> Option<usize> {
156        Some(self.data.len() - self.position)
157    }
158}
159
160// ============================================================================
161// Public API
162// ============================================================================
163
164/// Generate english-like word hash.
165///
166/// Reads bytes from the hasher and generates a single continuous word.
167/// For finite hashers, uses all available bytes.
168/// For infinite hashers, reads bytes proportional to input length (minimum 8).
169///
170/// # Examples
171/// ```
172/// use readable_hash::{english_word_hash, StdHasher};
173///
174/// assert_eq!(english_word_hash::<StdHasher, _>("I"), "waged");
175/// assert_eq!(english_word_hash::<StdHasher, _>("different"), "imaumates");
176/// assert_eq!(
177///     english_word_hash::<StdHasher, _>("pneumonoultramicroscopicsilicovolcanoconiosis"),
178///     "dummaricardemastria"
179/// );
180/// ```
181pub fn english_word_hash<H, T>(input: T) -> String
182where
183    H: ReadableHasher,
184    T: AsRef<[u8]>,
185{
186    let input_bytes = input.as_ref();
187    if input_bytes.is_empty() {
188        return String::new();
189    }
190    let input_len = input_bytes.len();
191
192    let mut hasher = H::default();
193    hasher.update(input_bytes);
194    let reader = hasher.finalize();
195
196    // For infinite readers, wrap with a length limiter
197    let bytes_limit = match reader.remaining() {
198        Some(_) => None,                // Finite: use all
199        None => Some(input_len.max(8)), // Infinite: limit to input length
200    };
201
202    let mut limited_reader = LimitedByteReader::new(reader, bytes_limit);
203    english_word::generate_word_with_target_len(&mut limited_reader, input_len)
204}
205
206/// A ByteReader wrapper that limits the number of bytes read.
207struct LimitedByteReader<R: ByteReader> {
208    inner: R,
209    remaining: Option<usize>,
210}
211
212impl<R: ByteReader> LimitedByteReader<R> {
213    fn new(inner: R, limit: Option<usize>) -> Self {
214        Self {
215            inner,
216            remaining: limit,
217        }
218    }
219}
220
221impl<R: ByteReader> ByteReader for LimitedByteReader<R> {
222    fn read(&mut self, dest: &mut [u8]) -> usize {
223        let max_read = match self.remaining {
224            Some(0) => return 0,
225            Some(remaining) => dest.len().min(remaining),
226            None => dest.len(),
227        };
228
229        let bytes_read = self.inner.read(&mut dest[..max_read]);
230        if let Some(ref mut remaining) = self.remaining {
231            *remaining = remaining.saturating_sub(bytes_read);
232        }
233        bytes_read
234    }
235
236    fn remaining(&self) -> Option<usize> {
237        match (self.remaining, self.inner.remaining()) {
238            (Some(limit), Some(inner_remaining)) => Some(limit.min(inner_remaining)),
239            (Some(limit), None) => Some(limit),
240            (None, inner_remaining) => inner_remaining,
241        }
242    }
243}