| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| use num_bigint_dig::BigUint; |
| use num_traits::Num; |
| use std::cmp::Ordering; |
| use std::fmt; |
| use std::iter; |
| use thiserror::Error; |
| |
| use crate::util::parse_int::ParseInt; |
| |
| #[derive(Error, Debug, Clone, PartialEq, Eq)] |
| pub enum ParseBigIntError { |
| #[error("integer is too large")] |
| Overflow, |
| #[error("integer is too small")] |
| Underflow, |
| #[error(transparent)] |
| ParseBigIntError(#[from] num_bigint_dig::ParseBigIntError), |
| } |
| |
| /// A fixed-size unsigned big integer. |
| /// |
| /// This struct wraps a `BigUint` to facilitate defining new fixed-size unsigned integer types for |
| /// better type safety. |
| /// |
| /// An integer stored in this type is fixed-size in the sense that the minimum number of bits |
| /// required to represent it, i.e. its bit length, is at most `BIT_LEN`. This size can be specified |
| /// using the const parameters `BIT_LEN` and `EXACT_LEN` as follows: |
| /// - When `EXACT_LEN` is `false`, the bit length of the integer can be at most `BIT_LEN` bits, |
| /// e.g. SHA-256 digests (at most 256 bits) or RSA-3072 signatures (at most 3072 bits), |
| /// - When `EXACT_LEN` is `true`, the number of bits required to represent the integer must be |
| /// exactly `BIT_LEN` bits, e.g. RSA-3072 moduli (exactly 3072 bits). |
| /// Note that while the type encapsulates the size information, the actual check is performed at |
| /// runtime when an instance is created (see `check_len()`). |
| /// |
| /// This struct is not meant to be used directly, please see the `fixed_size_bigint` macro which |
| /// also generates the required boilerplate code for new types. |
| #[derive(Debug, Clone, Eq, PartialEq)] |
| pub(crate) struct FixedSizeBigInt<const BIT_LEN: usize, const EXACT_LEN: bool>(BigUint); |
| |
| impl<const BIT_LEN: usize, const EXACT_LEN: bool> FixedSizeBigInt<BIT_LEN, EXACT_LEN> { |
| const BYTE_LEN: usize = BIT_LEN.saturating_add(u8::BITS as usize - 1) / u8::BITS as usize; |
| |
| /// Checks the bit length of the `FixedSizeBigInt`. |
| /// |
| /// Bit length of a `FixedSizeBigInt` can be at most `BIT_LEN` if `EXACT_LEN` is `false`, must |
| /// be exactly `BIT_LEN` otherwise. |
| fn new_from_biguint(biguint: BigUint) -> Result<Self, ParseBigIntError> { |
| match (biguint.bits().cmp(&BIT_LEN), EXACT_LEN) { |
| (Ordering::Greater, _) => Err(ParseBigIntError::Overflow), |
| (Ordering::Equal, _) => Ok(Self(biguint)), |
| (Ordering::Less, true) => Err(ParseBigIntError::Underflow), |
| (Ordering::Less, false) => Ok(Self(biguint)), |
| } |
| } |
| |
| /// Creates a `FixedSizeBigInt` from little-endian bytes. |
| pub(crate) fn from_le_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, ParseBigIntError> { |
| Self::new_from_biguint(BigUint::from_bytes_le(bytes.as_ref())) |
| } |
| |
| /// Creates a `FixedSizeBigInt` from big-endian bytes. |
| pub(crate) fn from_be_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, ParseBigIntError> { |
| Self::new_from_biguint(BigUint::from_bytes_be(bytes.as_ref())) |
| } |
| |
| /// Returns the bit length. |
| /// |
| /// Bit length of `FixedSizeBigInt` is the minimum number of bits required to represent its |
| /// value. The underlying storage may be larger. |
| pub(crate) fn bit_len(&self) -> usize { |
| self.0.bits() |
| } |
| |
| /// Returns the byte representation in little-endian order. |
| pub(crate) fn to_le_bytes(&self) -> Vec<u8> { |
| let mut v = self.0.to_bytes_le(); |
| assert!(Self::BYTE_LEN >= v.len()); |
| // Append since `v` is little-endian. |
| v.resize(Self::BYTE_LEN, 0); |
| v |
| } |
| |
| /// Returns the byte representation in big-endian order. |
| pub(crate) fn to_be_bytes(&self) -> Vec<u8> { |
| let mut v = self.0.to_bytes_be(); |
| assert!(Self::BYTE_LEN >= v.len()); |
| // Prepend since `v` is big-endian. |
| v.splice(0..0, iter::repeat(0).take(Self::BYTE_LEN - v.len())); |
| v |
| } |
| |
| /// Returns the underlying `BigUint`. |
| pub(crate) fn as_biguint(&self) -> &BigUint { |
| &self.0 |
| } |
| } |
| |
| impl<const BIT_LEN: usize, const EXACT_LEN: bool> ParseInt for FixedSizeBigInt<BIT_LEN, EXACT_LEN> { |
| type FromStrRadixErr = ParseBigIntError; |
| |
| fn from_str_radix(src: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> { |
| Self::new_from_biguint( |
| BigUint::from_str_radix(src, radix).map_err(ParseBigIntError::ParseBigIntError)?, |
| ) |
| } |
| } |
| |
| impl<const BIT_LEN: usize, const EXACT_LEN: bool> fmt::Display |
| for FixedSizeBigInt<BIT_LEN, EXACT_LEN> |
| { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| fmt::Display::fmt( |
| &format_args!("{:#0width$x}", self.0, width = Self::BYTE_LEN * 2 + 2), |
| f, |
| ) |
| } |
| } |
| |
| /// Helper macro for the `fixed_size_bigint` macro. |
| macro_rules! fixed_size_bigint_impl { |
| ($struct_name:ident, $bit_len:expr, $exact_len:expr) => { |
| #[derive(serde::Serialize, Debug, Clone, Eq, PartialEq)] |
| #[serde(into = "String")] |
| pub struct $struct_name($crate::util::bigint::FixedSizeBigInt<$bit_len, $exact_len>); |
| |
| const _: () = { |
| use num_bigint_dig::BigUint; |
| use std::fmt; |
| use std::result::Result; |
| |
| use $crate::util::bigint::{FixedSizeBigInt, ParseBigIntError}; |
| use $crate::util::parse_int::ParseInt; |
| |
| impl $struct_name { |
| pub fn from_le_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, ParseBigIntError> { |
| Ok($struct_name( |
| FixedSizeBigInt::<$bit_len, $exact_len>::from_le_bytes(bytes)?, |
| )) |
| } |
| |
| pub fn from_be_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, ParseBigIntError> { |
| Ok($struct_name( |
| FixedSizeBigInt::<$bit_len, $exact_len>::from_be_bytes(bytes)?, |
| )) |
| } |
| |
| pub fn bit_len(&self) -> usize { |
| self.0.bit_len() |
| } |
| |
| pub fn to_le_bytes(&self) -> Vec<u8> { |
| self.0.to_le_bytes() |
| } |
| |
| pub fn to_be_bytes(&self) -> Vec<u8> { |
| self.0.to_be_bytes() |
| } |
| |
| pub fn as_biguint(&self) -> &BigUint { |
| self.0.as_biguint() |
| } |
| } |
| |
| impl ParseInt for $struct_name { |
| type FromStrRadixErr = ParseBigIntError; |
| |
| fn from_str_radix(src: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> { |
| Ok($struct_name( |
| FixedSizeBigInt::<$bit_len, $exact_len>::from_str_radix(src, radix)?, |
| )) |
| } |
| } |
| |
| impl fmt::Display for $struct_name { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| fmt::Display::fmt(&self.0, f) |
| } |
| } |
| |
| impl From<$struct_name> for String { |
| fn from(s: $struct_name) -> String { |
| s.0.to_string() |
| } |
| } |
| }; |
| }; |
| } |
| |
| pub(crate) use fixed_size_bigint_impl; |
| |
| /// Macro for defining a new fixed-size unsigned big integer type. |
| /// |
| /// Defines a new type that wraps a `FixedSizeBigInt`. This macro is intended to be used within this |
| /// crate to define types which can then be exported as needed: |
| /// |
| /// ``` |
| /// use crate::util::bigint::fixed_size_bigint; |
| /// |
| /// // Define a type for RSA-3072 moduli (exactly 3072 bits long): |
| /// fixed_size_bigint!(Rsa3072Modulus, 3072); |
| /// |
| /// // Define a type for SHA-256 digests (at most 256 bits long): |
| /// fixed_size_bigint!(Sha256Digest, at_most 256); |
| /// ``` |
| macro_rules! fixed_size_bigint { |
| ($struct_name:ident, $bit_len:expr) => { |
| $crate::util::bigint::fixed_size_bigint_impl!($struct_name, $bit_len, true); |
| }; |
| ($struct_name:ident, at_most $bit_len:expr) => { |
| $crate::util::bigint::fixed_size_bigint_impl!($struct_name, $bit_len, false); |
| }; |
| } |
| |
| pub(crate) use fixed_size_bigint; |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| fixed_size_bigint!(TestArray, at_most 16); |
| fixed_size_bigint!(TestArrayExact, 16); |
| |
| #[test] |
| fn test_from_to_le_bytes() { |
| fn check(slice: &[u8], data: &[u8]) { |
| assert_eq!(TestArray::from_le_bytes(slice).unwrap().to_le_bytes(), data); |
| } |
| check(&[], &[0, 0]); |
| check(&[1], &[1, 0]); |
| check(&[0, 1], &[0, 1]); |
| check(&[1, 0], &[1, 0]); |
| |
| assert!(TestArray::from_le_bytes([1, 2, 3]).is_err()); |
| } |
| |
| #[test] |
| fn test_from_to_le_bytes_exact_len() { |
| fn check(slice: &[u8], data: &[u8]) { |
| assert_eq!( |
| TestArrayExact::from_le_bytes(slice).unwrap().to_le_bytes(), |
| data |
| ); |
| } |
| check(&[0, 128], &[0, 128]); |
| check(&[255, 255, 0], &[255, 255]); |
| |
| assert!(TestArrayExact::from_le_bytes([1]).is_err()); |
| assert!(TestArrayExact::from_le_bytes([255, 127]).is_err()); |
| assert!(TestArrayExact::from_le_bytes([0, 0, 1]).is_err()); |
| } |
| |
| #[test] |
| fn test_from_to_be_bytes() { |
| fn check(slice: &[u8], data: &[u8]) { |
| assert_eq!(TestArray::from_be_bytes(slice).unwrap().to_be_bytes(), data); |
| } |
| check(&[1], &[0, 1]); |
| check(&[1, 0], &[1, 0]); |
| check(&[0, 1], &[0, 1]); |
| |
| assert!(TestArray::from_be_bytes([1, 2, 1]).is_err()); |
| } |
| |
| #[test] |
| fn test_from_to_be_bytes_exact_len() { |
| fn check(slice: &[u8], data: &[u8]) { |
| assert_eq!( |
| TestArrayExact::from_be_bytes(slice).unwrap().to_be_bytes(), |
| data |
| ); |
| } |
| check(&[128, 1], &[128, 1]); |
| check(&[0, 255, 255], &[255, 255]); |
| |
| assert!(TestArrayExact::from_be_bytes([1]).is_err()); |
| assert!(TestArrayExact::from_be_bytes([127, 1]).is_err()); |
| assert!(TestArrayExact::from_be_bytes([1, 0, 0]).is_err()); |
| } |
| |
| #[test] |
| fn test_bit_len() { |
| fn check(slice: &[u8], bit_len: usize) { |
| assert_eq!(TestArray::from_le_bytes(slice).unwrap().bit_len(), bit_len); |
| } |
| check(&[1], 1); |
| check(&[1, 0], 1); |
| check(&[255], 8); |
| check(&[0, 1], 9); |
| check(&[0, 128], 16); |
| } |
| |
| #[test] |
| fn test_from_str() { |
| assert_eq!(TestArray::from_str("0x01").unwrap().to_le_bytes(), [1, 0]); |
| assert_eq!( |
| TestArray::from_str("0x00201").unwrap().to_le_bytes(), |
| [1, 2] |
| ); |
| assert!(TestArray::from_str("0x030201").is_err()); |
| } |
| |
| #[test] |
| fn test_from_str_exact_len() { |
| assert_eq!( |
| TestArrayExact::from_str("0x08001").unwrap().to_le_bytes(), |
| [1, 128] |
| ); |
| |
| assert!(TestArrayExact::from_str("0x01").is_err()); |
| assert!(TestArrayExact::from_str("0x0201").is_err()); |
| assert!(TestArrayExact::from_str("0x030201").is_err()); |
| } |
| |
| #[test] |
| fn test_fmt() { |
| let exact = TestArrayExact::from_str("0xabcd").unwrap(); |
| assert_eq!(exact.to_string(), "0xabcd"); |
| |
| let at_most = TestArray::from_str("0xab").unwrap(); |
| assert_eq!(at_most.to_string(), "0x00ab"); |
| |
| let at_most_full = TestArray::from_str("0xabcd").unwrap(); |
| assert_eq!(at_most_full.to_string(), "0xabcd"); |
| } |
| } |