use crate::errors::Result;
use alloc::vec::Vec;
use crypto_bigint::{BoxedUint, Choice, CtEq};
use zeroize::Zeroizing;
#[inline]
fn be_pad_into(bytes: &[u8], out: &mut [u8]) {
let excess = bytes.len().saturating_sub(out.len());
debug_assert!(
!bytes[..excess]
.iter()
.fold(Choice::FALSE, |overflow, byte| overflow | !byte.ct_eq(&0u8))
.to_bool(),
"integer exceeds output width"
);
out.fill(0);
let tail = &bytes[excess..];
let start = out.len() - tail.len();
out[start..].copy_from_slice(tail);
}
#[inline]
pub(crate) fn i2osp(input: BoxedUint, padded_len: usize) -> Vec<u8> {
let mut out = vec![0u8; padded_len];
be_pad_into(&input.to_be_bytes(), &mut out);
out
}
#[inline]
pub(crate) fn i2osp_zeroizing(input: BoxedUint, padded_len: usize) -> Zeroizing<Vec<u8>> {
let input = Zeroizing::new(input);
let bytes = Zeroizing::new(input.to_be_bytes());
let mut out = Zeroizing::new(vec![0u8; padded_len]);
be_pad_into(&bytes, &mut out);
out
}
#[inline]
#[allow(clippy::integer_division_remainder_used, reason = "debug")]
pub(crate) fn os2ip(input: &[u8], bits_precision: u32) -> Result<BoxedUint> {
debug_assert_eq!(bits_precision % crypto_bigint::Limb::BITS, 0);
Ok(BoxedUint::from_be_slice(input, bits_precision)?)
}
#[inline]
pub(crate) fn os2ip_zeroizing(
input: Zeroizing<Vec<u8>>,
bits_precision: u32,
) -> Result<Zeroizing<BoxedUint>> {
os2ip(&input, bits_precision).map(Zeroizing::new)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn be_pad() {
let x = BoxedUint::from(0x0102u64);
let p = i2osp(x.clone(), 16);
assert_eq!(&p[..14], &[0u8; 14]);
assert_eq!(&p[14..], &[1, 2]);
let p = i2osp_zeroizing(x.clone(), 3);
assert_eq!(&p[..], &[0, 1, 2]);
let p = i2osp(x.clone(), 8);
assert_eq!(&p[..], &[0, 0, 0, 0, 0, 0, 1, 2]);
let p = i2osp(BoxedUint::zero_with_precision(128), 4);
assert_eq!(&p[..], &[0u8; 4]);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "integer exceeds output width")]
fn be_pad_rejects_broken_precondition() {
i2osp_zeroizing(BoxedUint::from(0x0102u64), 1);
}
#[test]
fn os2ip_round_trip() {
let bits_precision = BoxedUint::zero_with_precision(2049).bits_precision();
for first_byte in [0, 1, 0xff] {
let mut input = vec![0xff; 257];
input[0] = first_byte;
let integer = os2ip(&input, bits_precision).unwrap();
assert_eq!(integer.bits_precision(), bits_precision);
assert_eq!(i2osp(integer, input.len()), input);
let integer = os2ip_zeroizing(Zeroizing::new(input.clone()), bits_precision).unwrap();
assert_eq!(integer.bits_precision(), bits_precision);
let output = i2osp_zeroizing((*integer).clone(), input.len());
assert_eq!(*output, input);
}
let zero = os2ip(&[], 128).unwrap();
assert_eq!(zero.bits_precision(), 128);
assert_eq!(i2osp(zero, 16), [0u8; 16]);
}
#[test]
fn os2ip_rejects_oversized_input() {
let input = vec![0; 17];
let expected = crate::errors::Error::Decode(crypto_bigint::DecodeError::InputSize);
assert_eq!(os2ip(&input, 128).unwrap_err(), expected);
assert_eq!(
os2ip_zeroizing(Zeroizing::new(input), 128).unwrap_err(),
expected
);
}
}