#![no_std]
use digest::generic_array::GenericArray;
pub use digest::{Digest, ExtendableOutput, Reset, Update, VariableOutput, XofReader};
use num_bigint::BigUint;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
pub mod movingwindow;
#[cfg(feature = "std")]
use thiserror::Error;
#[derive(Clone, Debug, Default)]
pub struct FullDomainHash<H: Digest> {
output_size: usize,
inner_hash: H,
iv: u8,
current_suffix: u8,
read_buf_pos: usize,
read_buf: GenericArray<u8, H::OutputSize>,
}
#[cfg(feature = "std")]
#[derive(Debug, Error)]
pub enum Error {
#[error("fdh: Cannot find IV for a digest with the desired range")]
NoDigestWithin,
}
impl<H: Digest + Clone> FullDomainHash<H>
where
H::OutputSize: Clone,
{
fn reset(&mut self) {
self.current_suffix = self.iv;
self.read_buf_pos = 0;
self.read_buf = GenericArray::default();
self.inner_hash = H::new();
}
pub fn with_iv(output_size: usize, iv: u8) -> Self {
FullDomainHash {
output_size,
inner_hash: H::new(),
iv,
current_suffix: iv,
read_buf: GenericArray::default(),
read_buf_pos: 0,
}
}
fn fill_buffer(&mut self) {
if self.read_buf_pos == 0 {
let mut inner_hash = self.inner_hash.clone();
inner_hash.update([self.current_suffix]);
self.read_buf = inner_hash.finalize();
self.current_suffix = self.current_suffix.wrapping_add(1);
}
}
fn read_buf_pos_mod_add(&mut self, rhs: usize) {
if rhs > self.read_buf.len() {
panic!("fdh: Cannot increment buffer position a larger amount than the buffer itself. This is a bug, please report it at https://github.com/phayes/fdh-rs/issues");
}
if self.read_buf_pos + rhs > self.read_buf.len() - 1 {
self.read_buf_pos = rhs - (self.read_buf.len() - self.read_buf_pos);
} else {
self.read_buf_pos += rhs;
}
}
#[cfg(feature = "std")]
pub fn results_between(
self,
initial_iv: u8,
min: &BigUint,
max: &BigUint,
) -> Result<(std::vec::Vec<u8>, u8), Error> {
self.results_in_domain(initial_iv, |check| {
&BigUint::from_bytes_be(check) < max && &BigUint::from_bytes_be(check) > min
})
}
#[cfg(feature = "std")]
pub fn results_lt(
self,
initial_iv: u8,
max: &BigUint,
) -> Result<(std::vec::Vec<u8>, u8), Error> {
self.results_in_domain(initial_iv, |check| &BigUint::from_bytes_be(check) < max)
}
#[cfg(feature = "std")]
pub fn results_gt(
self,
initial_iv: u8,
min: &BigUint,
) -> Result<(std::vec::Vec<u8>, u8), Error> {
self.results_in_domain(initial_iv, |check| &BigUint::from_bytes_be(check) > min)
}
pub fn results_in_domain<C: Fn(&[u8]) -> bool>(
self,
initial_iv: u8,
value_in_domain: C,
) -> Result<(std::vec::Vec<u8>, u8), Error> {
let mut current_suffix = initial_iv;
loop {
let mut hasher = FullDomainHash {
output_size: self.output_size,
inner_hash: self.inner_hash.clone(),
iv: self.iv,
current_suffix,
read_buf: GenericArray::default(),
read_buf_pos: 0,
};
hasher.current_suffix = current_suffix;
let res = VariableOutput::finalize_boxed(hasher).into_vec();
if value_in_domain(&res) {
return Ok((res, current_suffix));
} else {
current_suffix = current_suffix.wrapping_add(1);
if current_suffix == initial_iv {
return Err(Error::NoDigestWithin);
}
}
}
}
}
#[cfg(feature = "std")]
impl<H: Digest + Clone> VariableOutput for FullDomainHash<H>
where
H::OutputSize: Clone,
{
fn new(output_size: usize) -> Result<Self, digest::InvalidOutputSize> {
Ok(FullDomainHash::with_iv(output_size, 0))
}
fn output_size(&self) -> usize {
self.output_size
}
fn finalize_variable(mut self, f: impl FnOnce(&[u8])) {
let num_inner = self.output_size / H::output_size();
let remainder = self.output_size % H::output_size();
let mut buf = std::vec::Vec::<u8>::with_capacity(self.output_size);
for _ in 0..num_inner {
self.fill_buffer();
buf.extend_from_slice(self.read_buf.as_slice());
}
if remainder != 0 {
self.fill_buffer();
buf.extend_from_slice(&self.read_buf.as_slice()[..remainder]);
}
f(buf.as_slice());
}
fn finalize_variable_reset(&mut self, f: impl FnOnce(&[u8])) {
let result = self.clone().finalize_variable(f);
self.reset();
result
}
}
impl<H: Digest> Update for FullDomainHash<H> {
fn update(&mut self, data: impl AsRef<[u8]>) {
self.inner_hash.update(data);
}
}
impl<H: Digest> Reset for FullDomainHash<H> {
fn reset(&mut self) {
self.inner_hash.reset();
}
}
#[cfg(feature = "std")]
impl<H: Digest> std::io::Write for FullDomainHash<H> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let len = buf.len();
self.update(buf);
Ok(len)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<H: Digest + Clone> ExtendableOutput for FullDomainHash<H>
where
H::OutputSize: Clone,
{
type Reader = Self;
fn finalize_xof(self) -> Self::Reader {
self
}
fn finalize_xof_reset(&mut self) -> Self::Reader {
let reader = self.clone();
self.reset();
reader
}
}
impl<H: Digest + Clone> digest::XofReader for FullDomainHash<H>
where
H::OutputSize: Clone,
{
fn read(&mut self, buffer: &mut [u8]) {
let dest_len = buffer.len();
let source_len = self.read_buf.len();
if source_len == dest_len {
self.fill_buffer();
buffer[..].copy_from_slice(&self.read_buf.as_slice()[..]);
} else {
let mut n = 0;
while n < dest_len {
self.fill_buffer();
let fill_amount = core::cmp::min(source_len - self.read_buf_pos, dest_len - n);
let read_slice =
&self.read_buf.as_slice()[self.read_buf_pos..(self.read_buf_pos + fill_amount)];
buffer[n..(n + fill_amount)].copy_from_slice(read_slice);
self.read_buf_pos_mod_add(fill_amount);
n += fill_amount;
}
}
}
}
#[cfg(test)]
mod tests {
use hex;
use sha1::Sha1;
use sha2::Sha256;
#[test]
#[cfg(feature = "std")]
fn sha256_std_test() {
use crate::{FullDomainHash, Update, VariableOutput};
let mut hasher = FullDomainHash::<Sha256>::new(256 / 8).unwrap();
hasher.update(b"ATTACK AT DAWN");
let result = hex::encode(hasher.finalize_boxed());
assert_eq!(
result,
"015d53c7925b4434f00286fe2f0eb28378a49300b159b896eb2356a7c4de95f1"
);
let mut hasher = FullDomainHash::<Sha256>::new(256 / 8).unwrap();
hasher.update(b"ATTACK AT DAWN");
hasher.reset();
hasher.update(b"ATTACK AT ");
hasher.update(b"DAWN");
let result = hex::encode(hasher.finalize_boxed());
assert_eq!(
result,
"015d53c7925b4434f00286fe2f0eb28378a49300b159b896eb2356a7c4de95f1"
);
let mut hasher = FullDomainHash::<Sha256>::new(128 / 8).unwrap();
hasher.update(b"ATTACK AT DAWN");
let result = hex::encode(hasher.finalize_boxed());
assert_eq!(result, "015d53c7925b4434f00286fe2f0eb283");
let mut hasher = FullDomainHash::<Sha256>::new(264 / 8).unwrap();
hasher.update(b"ATTACK AT DAWN");
let result = hex::encode(hasher.finalize_boxed());
assert_eq!(
result,
"015d53c7925b4434f00286fe2f0eb28378a49300b159b896eb2356a7c4de95f158"
);
let mut hasher = FullDomainHash::<Sha256>::new(1024 / 8).unwrap();
hasher.update(b"ATTACK AT DAWN");
let result = hex::encode(hasher.finalize_boxed());
assert_eq!(result, "015d53c7925b4434f00286fe2f0eb28378a49300b159b896eb2356a7c4de95f158617fec3b813f834cd86ab0dd26b971c46b7ede451b490279628a265edf0a10691095675808b47c0add4300b3181a31109cbc31a945d05562ceb6cca0fea834d9c456fe1abf34a5a775ed572ce571b1dcca03b984102e666e9ab876876fb3af");
let mut hasher = FullDomainHash::<Sha256>::with_iv(1024 / 8, 254);
hasher.update(b"ATTACK AT DAWN");
let result = hex::encode(hasher.finalize_boxed());
assert_eq!(result, "8b41c68cc83acfa422fb6a0c61c5c7a14eef381768d37375c78caf61d76e62b4a93a562946a7378fc3eca407eb44e81fef2be026e1ee340ba85a06f9b2e4fe84015d53c7925b4434f00286fe2f0eb28378a49300b159b896eb2356a7c4de95f158617fec3b813f834cd86ab0dd26b971c46b7ede451b490279628a265edf0a10");
}
#[test]
fn sha256_no_std_test() {
use crate::{ExtendableOutput, FullDomainHash, Update, XofReader};
let mut hasher = FullDomainHash::<Sha256>::default();
hasher.update(b"ATTACK AT DAWN");
let mut reader = hasher.finalize_xof();
let mut read_buf = <[u8; 8]>::default();
reader.read(&mut read_buf);
assert_eq!(read_buf, [0x01, 0x5d, 0x53, 0xc7, 0x92, 0x5b, 0x44, 0x34]);
reader.read(&mut read_buf);
assert_eq!(read_buf, [0xf0, 0x02, 0x86, 0xfe, 0x2f, 0x0e, 0xb2, 0x83]);
reader.read(&mut read_buf);
assert_eq!(read_buf, [0x78, 0xa4, 0x93, 0x00, 0xb1, 0x59, 0xb8, 0x96]);
reader.read(&mut read_buf);
assert_eq!(read_buf, [0xeb, 0x23, 0x56, 0xa7, 0xc4, 0xde, 0x95, 0xf1]);
reader.read(&mut read_buf);
assert_eq!(read_buf, [0x58, 0x61, 0x7f, 0xec, 0x3b, 0x81, 0x3f, 0x83]);
let mut hasher = FullDomainHash::<Sha256>::default();
hasher.update(b"ATTACK AT DAWN");
let mut reader = hasher.finalize_xof();
let mut read_buf = <[u8; 21]>::default();
reader.read(&mut read_buf);
assert_eq!(
read_buf,
[
0x01, 0x5d, 0x53, 0xc7, 0x92, 0x5b, 0x44, 0x34, 0xf0, 0x02, 0x86, 0xfe, 0x2f, 0x0e,
0xb2, 0x83, 0x78, 0xa4, 0x93, 0x00, 0xb1
]
);
reader.read(&mut read_buf);
assert_eq!(
read_buf,
[
0x59, 0xb8, 0x96, 0xeb, 0x23, 0x56, 0xa7, 0xc4, 0xde, 0x95, 0xf1, 0x58, 0x61, 0x7f,
0xec, 0x3b, 0x81, 0x3f, 0x83, 0x4c, 0xd8
]
);
let mut hasher = FullDomainHash::<Sha1>::default();
hasher.update(b"ATTACK AT DAWN");
let mut reader = hasher.finalize_xof();
let mut read_buf = <[u8; 21]>::default();
reader.read(&mut read_buf);
assert_eq!(
read_buf,
[
0x1a, 0xdf, 0xc3, 0x44, 0xb7, 0x5a, 0xb9, 0xa7, 0x7d, 0x70, 0x74, 0x5f, 0x4e, 0xbb,
0x5a, 0x97, 0x3c, 0x5d, 0x1f, 0x1d, 0x20
]
);
let mut hasher = FullDomainHash::<Sha1>::default();
hasher.update(b"ATTACK AT DAWN");
let mut reader = hasher.finalize_xof();
let mut read_buf = <[u8; 20]>::default();
reader.read(&mut read_buf);
assert_eq!(
read_buf,
[
0x1a, 0xdf, 0xc3, 0x44, 0xb7, 0x5a, 0xb9, 0xa7, 0x7d, 0x70, 0x74, 0x5f, 0x4e, 0xbb,
0x5a, 0x97, 0x3c, 0x5d, 0x1f, 0x1d
]
);
}
#[test]
#[cfg(feature = "std")]
fn test_results_within() {
use crate::{FullDomainHash, Update, VariableOutput};
use num_bigint::BigUint;
use num_traits::Num;
let min = BigUint::from_str_radix(
"51683095453715361952842063988888814558178328011011413557662527675023521115731",
10,
)
.unwrap();
let max = BigUint::from_str_radix(
"63372381656167118369940880608146415619543459354936568979731399163319071519847",
10,
)
.unwrap();
let mut hasher = FullDomainHash::<Sha256>::new(256 / 8).unwrap();
hasher.update(b"ATTACK AT DAWN");
let iv = 0;
let (result, iv) = hasher.results_between(iv, &min, &max).unwrap();
assert_eq!(iv, 20);
assert_eq!(
hex::encode(result),
"88d7143faf611e19119e4d861673e1a7d340686c00af1d8bcf06306bb5154b4d"
);
}
}