#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
use core::fmt;
use zeroize::Zeroizing;
mod range;
mod shuffle;
pub use range::{gen_range, gen_range_inclusive, RandInt};
pub use shuffle::shuffle;
const KDF_CONTEXT: &str = "arkhe-rand stream";
const XOF_BLOCK_LEN: usize = 64;
pub struct RngSource {
#[allow(dead_code)]
seed: Zeroizing<[u8; 32]>,
xof: blake3::OutputReader,
block: Zeroizing<[u8; XOF_BLOCK_LEN]>,
pos: usize,
}
impl RngSource {
pub fn from_seed(seed: &[u8; 32]) -> Self {
let mut hasher = blake3::Hasher::new_derive_key(KDF_CONTEXT);
hasher.update(seed.as_slice());
let xof = hasher.finalize_xof();
Self {
seed: Zeroizing::new(*seed),
xof,
block: Zeroizing::new([0u8; XOF_BLOCK_LEN]),
pos: XOF_BLOCK_LEN,
}
}
#[cfg(feature = "os-entropy")]
pub fn from_os_entropy() -> Result<Self, RngError> {
let mut seed = Zeroizing::new([0u8; 32]);
getrandom::getrandom(seed.as_mut_slice()).map_err(RngError::OsEntropyUnavailable)?;
Ok(Self::from_seed(&seed))
}
pub fn split(&mut self) -> Self {
let mut child = Zeroizing::new([0u8; 32]);
self.fill_bytes(child.as_mut_slice());
Self::from_seed(&child)
}
pub fn fill_bytes(&mut self, buf: &mut [u8]) {
let mut filled = 0;
while filled < buf.len() {
if self.pos == XOF_BLOCK_LEN {
if buf.len() - filled >= XOF_BLOCK_LEN {
self.xof.fill(&mut buf[filled..]);
return;
}
self.xof.fill(self.block.as_mut_slice());
self.pos = 0;
}
let n = (buf.len() - filled).min(XOF_BLOCK_LEN - self.pos);
buf[filled..filled + n].copy_from_slice(&self.block[self.pos..self.pos + n]);
self.pos += n;
filled += n;
}
}
}
impl Drop for RngSource {
fn drop(&mut self) {
let zero_seed = [0u8; 32];
let mut sentinel = blake3::Hasher::new_derive_key(KDF_CONTEXT);
sentinel.update(&zero_seed);
self.xof = sentinel.finalize_xof();
}
}
impl fmt::Debug for RngSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RngSource").finish_non_exhaustive()
}
}
#[cfg(feature = "os-entropy")]
#[non_exhaustive]
#[derive(Debug)]
pub enum RngError {
OsEntropyUnavailable(getrandom::Error),
}
#[cfg(feature = "os-entropy")]
impl fmt::Display for RngError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OsEntropyUnavailable(e) => write!(f, "OS entropy unavailable: {e}"),
}
}
}