use core::ops::{Deref, DerefMut};
use crate::Error;
use crate::low::generic::zeroise::Zeroable;
use crate::low::{ct, zeroise_value};
#[derive(Clone, Debug)]
pub(crate) struct PosInt<const N: usize> {
words: [u64; N],
used: usize,
}
impl<const N: usize> PosInt<N> {
pub(crate) fn from_words(words: &[u64]) -> Result<Self, Error> {
let mut r = Self {
words: [0; N],
used: words.len(),
};
r.words
.get_mut(..words.len())
.ok_or(Error::WrongLength)?
.copy_from_slice(words);
Ok(r)
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
let start = bytes.iter().position(|b| *b != 0).unwrap_or(bytes.len());
let bytes = &bytes[start..];
if bytes.len() > N * 8 {
return Err(Error::WrongLength);
}
let mut r = Self {
words: [0; N],
used: bytes.len().div_ceil(8),
};
for (word, chunk) in r.words.iter_mut().zip(bytes.rchunks(8)) {
let mut be = [0u8; 8];
be[8 - chunk.len()..].copy_from_slice(chunk);
*word = u64::from_be_bytes(be);
}
Ok(r)
}
pub(crate) fn to_bytes<'a>(&self, out: &'a mut [u8]) -> Result<&'a [u8], Error> {
let out = out.get_mut(..self.used * 8).ok_or(Error::WrongLength)?;
for (chunk, word) in out.rchunks_exact_mut(8).zip(self.as_words()) {
chunk.copy_from_slice(&word.to_be_bytes());
}
Ok(out)
}
pub(crate) fn to_bytes_asn1<'a>(&self, out: &'a mut [u8]) -> Result<&'a [u8], Error> {
let out = out.get_mut(..self.used * 8 + 1).ok_or(Error::WrongLength)?;
out[0] = 0x00;
self.to_bytes(&mut out[1..])?;
Ok(out)
}
pub(crate) fn len_bytes(&self) -> usize {
self.len_bits().div_ceil(8)
}
pub(crate) fn len_bits(&self) -> usize {
let top = self.as_words().iter().rposition(|w| *w != 0);
top.map_or(0, |i| i * 64 + 64 - self.words[i].leading_zeros() as usize)
}
pub(crate) fn is_even(&self) -> bool {
self.words[0] & 1 == 0
}
pub(crate) fn as_words(&self) -> &[u64] {
&self.words[..self.used]
}
pub(crate) fn words_at_width(&self, len: usize) -> Result<&[u64], Error> {
match self.words.get(len..self.used.max(len)) {
Some(above) if above.iter().all(|w| *w == 0) => Ok(&self.words[..len]),
_ => Err(Error::WrongLength),
}
}
}
impl<const N: usize> Zeroable for PosInt<N> {}
pub(crate) struct SecretPosInt<const N: usize>(PosInt<N>);
impl<const N: usize> From<PosInt<N>> for SecretPosInt<N> {
fn from(pi: PosInt<N>) -> Self {
Self(PosInt {
used: pi.used,
words: ct::into_secret(pi.words),
})
}
}
impl<const N: usize> Deref for SecretPosInt<N> {
type Target = PosInt<N>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const N: usize> DerefMut for SecretPosInt<N> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<const N: usize> Drop for SecretPosInt<N> {
fn drop(&mut self) {
zeroise_value(&mut self.0);
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn bytes_round_trip() {
let x = PosInt::<2>::from_bytes(b"\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09").unwrap();
assert_eq!(x.as_words(), &[0x0203040506070809, 0x01]);
assert_eq!(x.len_bytes(), 9);
assert_eq!(x.len_bits(), 65);
let mut out = [0u8; 17];
assert_eq!(
x.to_bytes(&mut out).unwrap(),
b"\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09"
);
assert_eq!(x.to_bytes_asn1(&mut out).unwrap()[..2], [0, 0]);
assert_eq!(x.to_bytes(&mut [0u8; 15]), Err(Error::WrongLength));
}
#[test]
fn zero() {
let x = PosInt::<2>::from_bytes(b"\x00\x00").unwrap();
assert!(x.as_words().is_empty());
assert_eq!(x.len_bytes(), 0);
assert_eq!(x.len_bits(), 0);
}
#[test]
fn too_long() {
assert_eq!(
PosInt::<1>::from_bytes(&[1; 9]).unwrap_err(),
Error::WrongLength
);
assert!(PosInt::<1>::from_bytes(b"\x00\x01\x02\x03\x04\x05\x06\x07\x08").is_ok());
}
#[test]
fn widths() {
let x = PosInt::<4>::from_words(&[1, 2, 0]).unwrap();
assert_eq!(x.words_at_width(4).unwrap(), &[1, 2, 0, 0]);
assert_eq!(x.words_at_width(2).unwrap(), &[1, 2]);
assert_eq!(x.words_at_width(1), Err(Error::WrongLength));
}
}