Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 12 additions & 16 deletions src/limb/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ use super::{Limb, Word};
use crate::Encoding;

impl Encoding for Limb {
cpubits::cpubits! {
32 => { type Repr = [u8; 4]; }
64 => { type Repr = [u8; 8]; }
}
type Repr = [u8; Self::BYTES];

#[inline]
fn from_be_bytes(bytes: Self::Repr) -> Self {
Expand All @@ -32,32 +29,31 @@ impl Encoding for Limb {

#[cfg(feature = "alloc")]
impl Limb {
/// Decode limb from a big endian byte slice.
/// Decode limb from a big endian byte slice, which may be shorter than [`Limb::BYTES`].
///
/// # Panics
/// - if the slice is larger than [`Limb::Repr`].
pub(crate) fn from_be_slice(bytes: &[u8]) -> Self {
let offset = Self::BYTES
.checked_sub(bytes.len())
.expect("The given slice is larger than Limb::BYTES");

let mut repr = Self::ZERO.to_be_bytes();
let repr_len = repr.len();
assert!(
bytes.len() <= repr_len,
"The given slice is larger than the limb size"
);
repr[(repr_len - bytes.len())..].copy_from_slice(bytes);
repr[offset..].copy_from_slice(bytes);
Self::from_be_bytes(repr)
}

/// Decode limb from a little endian byte slice.
/// Decode limb from a little endian byte slice, which may be shorter than [`Limb::BYTES`].
///
/// # Panics
/// - if the slice is not the same size as [`Limb::Repr`].
pub(crate) fn from_le_slice(bytes: &[u8]) -> Self {
let mut repr = Self::ZERO.to_le_bytes();
let repr_len = repr.len();
assert!(
bytes.len() <= repr_len,
"The given slice is larger than the limb size"
bytes.len() <= Self::BYTES,
"The given slice is larger than Limb::BYTES"
);

let mut repr = Self::ZERO.to_be_bytes();
repr[..bytes.len()].copy_from_slice(bytes);
Self::from_le_bytes(repr)
}
Expand Down
71 changes: 47 additions & 24 deletions src/uint/boxed/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ impl BoxedUint {
/// The `bits_precision` argument represents the precision of the resulting integer, which is
/// fixed as this type is not arbitrary-precision.
///
/// The new [`BoxedUint`] will be created with `bits_precision`
/// rounded up to a multiple of [`Limb::BITS`].
/// The new [`BoxedUint`] will be created with `bits_precision` rounded up to a multiple of
/// [`Limb::BITS`].
///
/// # Errors
/// - Returns [`DecodeError::InputSize`] if the length of `bytes` is larger than
Expand All @@ -26,11 +26,7 @@ impl BoxedUint {
return Err(DecodeError::InputSize);
}

let mut ret = Self::zero_with_precision(bits_precision);

for (chunk, limb) in bytes.rchunks(Limb::BYTES).zip(ret.limbs.iter_mut()) {
*limb = Limb::from_be_slice(chunk);
}
let ret = Self::from_be_slice_truncated(bytes, bits_precision);

if bits_precision < ret.bits() {
return Err(DecodeError::Precision);
Expand All @@ -39,6 +35,27 @@ impl BoxedUint {
Ok(ret)
}

/// Create a new [`BoxedUint`] from the provided big endian bytes, zero padding if necessary,
/// and truncating to the least significant bytes in the event the given amount of data exceeds
/// `bits_precision`.
#[must_use]
pub fn from_be_slice_truncated(mut bytes: &[u8], bits_precision: u32) -> Self {
let bytes_precision = bitlen::to_bytes(bits_precision);

// TODO(tarcieri): mask bits in the most significant byte if necessary
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementing this will break the current DecodeError::Precision checks in from_be_slice/from_le_slice, so those need to be updated first.

I'll also note it's a bit weird those methods return DecodeError::InputSize if the input slice is too long, and DecodeError::Precision if it contains bits that exceed the given precision, but it shouldn't be too hard to preserve that behavior though it might need to be an eager check (those bits shouldn't be set in the first place and if they are it's an error, so we don't need to worry about constant-time).

if bytes.len() > bytes_precision {
bytes = &bytes[..bytes_precision];
}

let mut ret = Self::zero_with_precision(bits_precision);

for (chunk, limb) in bytes.rchunks(Limb::BYTES).zip(ret.limbs.iter_mut()) {
*limb = Limb::from_be_slice(chunk);
}

ret
}

/// Create a new [`BoxedUint`] from the provided big endian bytes, automatically selecting its
/// precision based on the size of the input.
///
Expand All @@ -47,12 +64,8 @@ impl BoxedUint {
///
/// When working with secret values, use [`BoxedUint::from_be_slice`].
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::missing_panics_doc)]
pub fn from_be_slice_vartime(bytes: &[u8]) -> Self {
let bits_precision = bitlen::from_bytes(bytes.len());

// TODO(tarcieri): avoid panic
Self::from_be_slice(bytes, bits_precision).expect("precision should be large enough")
Self::from_be_slice_truncated(bytes, bitlen::from_bytes(bytes.len()))
}

/// Create a new [`BoxedUint`] from the provided little endian bytes.
Expand All @@ -73,11 +86,7 @@ impl BoxedUint {
return Err(DecodeError::InputSize);
}

let mut ret = Self::zero_with_precision(bits_precision);

for (chunk, limb) in bytes.chunks(Limb::BYTES).zip(ret.limbs.iter_mut()) {
*limb = Limb::from_le_slice(chunk);
}
let ret = Self::from_le_slice_truncated(bytes, bits_precision);

if bits_precision < ret.bits() {
return Err(DecodeError::Precision);
Expand All @@ -86,6 +95,25 @@ impl BoxedUint {
Ok(ret)
}

/// Create a new [`BoxedUint`] from the provided little endian bytes, zero padding if necessary,
/// and truncating to the least significant bytes in the event the given amount of data exceeds
/// `bits_precision`.
#[must_use]
pub fn from_le_slice_truncated(mut bytes: &[u8], bits_precision: u32) -> Self {
let offset = bytes.len().saturating_sub(bitlen::to_bytes(bits_precision));

// TODO(tarcieri): mask bits in the most significant byte if necessary
bytes = &bytes[offset..];

let mut ret = Self::zero_with_precision(bits_precision);

for (chunk, limb) in bytes.chunks(Limb::BYTES).zip(ret.limbs.iter_mut()) {
*limb = Limb::from_le_slice(chunk);
}

ret
}

/// Create a new [`BoxedUint`] from the provided little endian bytes, automatically selecting
/// its precision based on the size of the input.
///
Expand All @@ -94,12 +122,8 @@ impl BoxedUint {
///
/// When working with secret values, use [`BoxedUint::from_le_slice`].
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::missing_panics_doc)]
pub fn from_le_slice_vartime(bytes: &[u8]) -> Self {
let bits_precision = bitlen::from_bytes(bytes.len());

// TODO(tarcieri): avoid panic
Self::from_le_slice(bytes, bits_precision).expect("precision should be large enough")
Self::from_le_slice_truncated(bytes, bitlen::from_bytes(bytes.len()))
}

/// Serialize this [`BoxedUint`] as big-endian.
Expand Down Expand Up @@ -163,9 +187,8 @@ impl BoxedUint {
/// # Panics
/// - if hex string is not the expected size
#[must_use]
#[allow(clippy::integer_division_remainder_used, reason = "public parameter")]
pub fn from_be_hex(hex: &str, bits_precision: u32) -> CtOption<Self> {
let nlimbs = (bits_precision / Limb::BITS) as usize;
let nlimbs = bitlen::to_limbs(bits_precision);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, seems like it currently cuts off the top limb if bits_precision isn't a multiple of Limb::BITS?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably pull this into its own PR and add some tests.

let bytes = hex.as_bytes();

assert_eq!(
Expand Down