Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Int - Bit ops #697

Merged
merged 13 commits into from
Nov 12, 2024
28 changes: 27 additions & 1 deletion src/const_choice.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use subtle::{Choice, CtOption};

use crate::{modular::SafeGcdInverter, Limb, NonZero, Odd, Uint, WideWord, Word};
use crate::{modular::SafeGcdInverter, Int, Limb, NonZero, Odd, Uint, WideWord, Word};

/// A boolean value returned by constant-time `const fn`s.
// TODO: should be replaced by `subtle::Choice` or `CtOption`
Expand Down Expand Up @@ -391,6 +391,12 @@ impl<const LIMBS: usize> ConstCtOption<Uint<LIMBS>> {
assert!(self.is_some.is_true_vartime(), "{}", msg);
self.value
}

/// Returns the contained value, interpreting the underlying [`Uint`] value as an [`Int`].
#[inline]
pub const fn as_int(&self) -> ConstCtOption<Int<LIMBS>> {
ConstCtOption::new(Int::from_bits(self.value), self.is_some)
}
}

impl<const LIMBS: usize> ConstCtOption<(Uint<LIMBS>, Uint<LIMBS>)> {
Expand Down Expand Up @@ -435,6 +441,26 @@ impl<const LIMBS: usize> ConstCtOption<Odd<Uint<LIMBS>>> {
}
}

impl<const LIMBS: usize> ConstCtOption<Int<LIMBS>> {
/// This returns the underlying value if it is `Some` or the provided value otherwise.
#[inline]
pub const fn unwrap_or(self, def: Int<LIMBS>) -> Int<LIMBS> {
Int::select(&def, &self.value, self.is_some)
}

/// Returns the contained value, consuming the `self` value.
///
/// # Panics
///
/// Panics if the value is none with a custom panic message provided by
/// `msg`.
#[inline]
pub const fn expect(self, msg: &str) -> Int<LIMBS> {
assert!(self.is_some.is_true_vartime(), "{}", msg);
self.value
}
Comment on lines +458 to +461
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I copy-pasted this from ConstCtOption<Uint>.

In doing so, I did notice that, although it uses is_true_vartime, ConstCtOption<Uint>::expect was not annotated as being vartime. Moreover, it is used for Uint::shr and Uint::shl, among others, which are not vartime either. Is this OK because unwraps are vartime anyway, or do we need to do something about this @tarcieri ?

Copy link
Member

Choose a reason for hiding this comment

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

They're not really "vartime" in that if the condition is violated, it will crash the entire program, as opposed to completing successfully in a variable number of CPU cycles

}

impl ConstCtOption<NonZero<Limb>> {
/// Returns the contained value, consuming the `self` value.
///
Expand Down
6 changes: 6 additions & 0 deletions src/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ use crate::Encoding;
use crate::{Bounded, ConstChoice, ConstCtOption, Constants, Limb, NonZero, Odd, Uint, Word};

mod add;
mod bit_and;
mod bit_not;
mod bit_or;
mod bit_xor;
mod cmp;
mod div;
mod encoding;
mod from;
mod mul;
mod neg;
mod resize;
mod shl;
mod shr;
mod sign;
mod sub;
pub(crate) mod types;
Expand Down
140 changes: 140 additions & 0 deletions src/int/bit_and.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//! [`Int`] bitwise AND operations.

use core::ops::{BitAnd, BitAndAssign};

use crate::{ConstCtOption, Int, Limb, Uint, Wrapping};

impl<const LIMBS: usize> Int<LIMBS> {
/// Computes bitwise `a & b`.
#[inline(always)]
pub const fn bitand(&self, rhs: &Self) -> Self {
Self(Uint::bitand(&self.0, &rhs.0))
}

/// Perform bitwise `AND` between `self` and the given [`Limb`], performing the `AND` operation
/// on every limb of `self`.
pub const fn bitand_limb(&self, rhs: Limb) -> Self {
Self(Uint::bitand_limb(&self.0, rhs))
}

/// Perform wrapping bitwise `AND`.
///
/// There's no way wrapping could ever happen.
/// This function exists so that all operations are accounted for in the wrapping operations
pub const fn wrapping_and(&self, rhs: &Self) -> Self {
self.bitand(rhs)
}

/// Perform checked bitwise `AND`, returning a [`ConstCtOption`] which `is_some` always
pub const fn checked_and(&self, rhs: &Self) -> ConstCtOption<Self> {
ConstCtOption::some(self.bitand(rhs))
}
}

impl<const LIMBS: usize> BitAnd for Int<LIMBS> {
type Output = Self;

fn bitand(self, rhs: Self) -> Int<LIMBS> {
self.bitand(&rhs)
}
}

impl<const LIMBS: usize> BitAnd<&Int<LIMBS>> for Int<LIMBS> {
type Output = Int<LIMBS>;

#[allow(clippy::needless_borrow)]
fn bitand(self, rhs: &Int<LIMBS>) -> Int<LIMBS> {
(&self).bitand(rhs)
}
}

impl<const LIMBS: usize> BitAnd<Int<LIMBS>> for &Int<LIMBS> {
type Output = Int<LIMBS>;

fn bitand(self, rhs: Int<LIMBS>) -> Int<LIMBS> {
self.bitand(&rhs)
}
}

impl<const LIMBS: usize> BitAnd<&Int<LIMBS>> for &Int<LIMBS> {
type Output = Int<LIMBS>;

fn bitand(self, rhs: &Int<LIMBS>) -> Int<LIMBS> {
self.bitand(rhs)
}
}

impl<const LIMBS: usize> BitAndAssign for Int<LIMBS> {
#[allow(clippy::assign_op_pattern)]
fn bitand_assign(&mut self, other: Self) {
*self = *self & other;
}
}

impl<const LIMBS: usize> BitAndAssign<&Int<LIMBS>> for Int<LIMBS> {
#[allow(clippy::assign_op_pattern)]
fn bitand_assign(&mut self, other: &Self) {
*self = *self & other;
}
}

impl<const LIMBS: usize> BitAnd for Wrapping<Int<LIMBS>> {
type Output = Self;

fn bitand(self, rhs: Self) -> Wrapping<Int<LIMBS>> {
Wrapping(self.0.bitand(&rhs.0))
}
}

impl<const LIMBS: usize> BitAnd<&Wrapping<Int<LIMBS>>> for Wrapping<Int<LIMBS>> {
type Output = Wrapping<Int<LIMBS>>;

fn bitand(self, rhs: &Wrapping<Int<LIMBS>>) -> Wrapping<Int<LIMBS>> {
Wrapping(self.0.bitand(&rhs.0))
}
}

impl<const LIMBS: usize> BitAnd<Wrapping<Int<LIMBS>>> for &Wrapping<Int<LIMBS>> {
type Output = Wrapping<Int<LIMBS>>;

fn bitand(self, rhs: Wrapping<Int<LIMBS>>) -> Wrapping<Int<LIMBS>> {
Wrapping(self.0.bitand(&rhs.0))
}
}

impl<const LIMBS: usize> BitAnd<&Wrapping<Int<LIMBS>>> for &Wrapping<Int<LIMBS>> {
type Output = Wrapping<Int<LIMBS>>;

fn bitand(self, rhs: &Wrapping<Int<LIMBS>>) -> Wrapping<Int<LIMBS>> {
Wrapping(self.0.bitand(&rhs.0))
}
}

impl<const LIMBS: usize> BitAndAssign for Wrapping<Int<LIMBS>> {
#[allow(clippy::assign_op_pattern)]
fn bitand_assign(&mut self, other: Self) {
*self = *self & other;
}
}

impl<const LIMBS: usize> BitAndAssign<&Wrapping<Int<LIMBS>>> for Wrapping<Int<LIMBS>> {
#[allow(clippy::assign_op_pattern)]
fn bitand_assign(&mut self, other: &Self) {
*self = *self & other;
}
}

#[cfg(test)]
mod tests {
use crate::I128;

#[test]
fn checked_and_ok() {
assert_eq!(I128::ZERO.checked_and(&I128::ONE).unwrap(), I128::ZERO);
}

#[test]
fn overlapping_and_ok() {
assert_eq!(I128::MAX.wrapping_and(&I128::ONE), I128::ONE);
}
}
42 changes: 42 additions & 0 deletions src/int/bit_not.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! [`Int`] bitwise NOT operations.

use core::ops::Not;

use crate::{Uint, Wrapping};

use super::Int;

impl<const LIMBS: usize> Int<LIMBS> {
/// Computes bitwise `!a`.
#[inline(always)]
pub const fn not(&self) -> Self {
Self(Uint::not(&self.0))
}
}

impl<const LIMBS: usize> Not for Int<LIMBS> {
type Output = Self;

fn not(self) -> Self {
Self::not(&self)
}
}

impl<const LIMBS: usize> Not for Wrapping<Int<LIMBS>> {
type Output = Self;

fn not(self) -> <Self as Not>::Output {
Wrapping(self.0.not())
}
}

#[cfg(test)]
mod tests {
use crate::I128;

#[test]
fn bitnot_ok() {
assert_eq!(I128::ZERO.not(), I128::MINUS_ONE);
assert_eq!(I128::MAX.not(), I128::MIN);
}
}
132 changes: 132 additions & 0 deletions src/int/bit_or.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//! [`Int`] bitwise OR operations.

use core::ops::{BitOr, BitOrAssign};

use crate::{ConstCtOption, Uint, Wrapping};

use super::Int;

impl<const LIMBS: usize> Int<LIMBS> {
/// Computes bitwise `a & b`.
#[inline(always)]
pub const fn bitor(&self, rhs: &Self) -> Self {
Self(Uint::bitor(&self.0, &rhs.0))
}

/// Perform wrapping bitwise `OR`.
///
/// There's no way wrapping could ever happen.
/// This function exists so that all operations are accounted for in the wrapping operations
pub const fn wrapping_or(&self, rhs: &Self) -> Self {
self.bitor(rhs)
}

/// Perform checked bitwise `OR`, returning a [`ConstCtOption`] which `is_some` always
pub const fn checked_or(&self, rhs: &Self) -> ConstCtOption<Self> {
ConstCtOption::some(self.bitor(rhs))
}
}

impl<const LIMBS: usize> BitOr for Int<LIMBS> {
type Output = Self;

fn bitor(self, rhs: Self) -> Int<LIMBS> {
self.bitor(&rhs)
}
}

impl<const LIMBS: usize> BitOr<&Int<LIMBS>> for Int<LIMBS> {
type Output = Int<LIMBS>;

#[allow(clippy::needless_borrow)]
fn bitor(self, rhs: &Int<LIMBS>) -> Int<LIMBS> {
(&self).bitor(rhs)
}
}

impl<const LIMBS: usize> BitOr<Int<LIMBS>> for &Int<LIMBS> {
type Output = Int<LIMBS>;

fn bitor(self, rhs: Int<LIMBS>) -> Int<LIMBS> {
self.bitor(&rhs)
}
}

impl<const LIMBS: usize> BitOr<&Int<LIMBS>> for &Int<LIMBS> {
type Output = Int<LIMBS>;

fn bitor(self, rhs: &Int<LIMBS>) -> Int<LIMBS> {
self.bitor(rhs)
}
}

impl<const LIMBS: usize> BitOrAssign for Int<LIMBS> {
fn bitor_assign(&mut self, other: Self) {
*self = *self | other;
}
}

impl<const LIMBS: usize> BitOrAssign<&Int<LIMBS>> for Int<LIMBS> {
fn bitor_assign(&mut self, other: &Self) {
*self = *self | other;
}
}

impl<const LIMBS: usize> BitOr for Wrapping<Int<LIMBS>> {
type Output = Self;

fn bitor(self, rhs: Self) -> Wrapping<Int<LIMBS>> {
Wrapping(self.0.bitor(&rhs.0))
}
}

impl<const LIMBS: usize> BitOr<&Wrapping<Int<LIMBS>>> for Wrapping<Int<LIMBS>> {
type Output = Wrapping<Int<LIMBS>>;

fn bitor(self, rhs: &Wrapping<Int<LIMBS>>) -> Wrapping<Int<LIMBS>> {
Wrapping(self.0.bitor(&rhs.0))
}
}

impl<const LIMBS: usize> BitOr<Wrapping<Int<LIMBS>>> for &Wrapping<Int<LIMBS>> {
type Output = Wrapping<Int<LIMBS>>;

fn bitor(self, rhs: Wrapping<Int<LIMBS>>) -> Wrapping<Int<LIMBS>> {
Wrapping(self.0.bitor(&rhs.0))
}
}

impl<const LIMBS: usize> BitOr<&Wrapping<Int<LIMBS>>> for &Wrapping<Int<LIMBS>> {
type Output = Wrapping<Int<LIMBS>>;

fn bitor(self, rhs: &Wrapping<Int<LIMBS>>) -> Wrapping<Int<LIMBS>> {
Wrapping(self.0.bitor(&rhs.0))
}
}

impl<const LIMBS: usize> BitOrAssign for Wrapping<Int<LIMBS>> {
fn bitor_assign(&mut self, other: Self) {
*self = *self | other;
}
}

impl<const LIMBS: usize> BitOrAssign<&Wrapping<Int<LIMBS>>> for Wrapping<Int<LIMBS>> {
fn bitor_assign(&mut self, other: &Self) {
*self = *self | other;
}
}

#[cfg(test)]
mod tests {
use crate::I128;

#[test]
fn checked_or_ok() {
assert_eq!(I128::ZERO.checked_or(&I128::ONE).unwrap(), I128::ONE);
}

#[test]
fn overlapping_or_ok() {
assert_eq!(I128::MAX.wrapping_or(&I128::ONE), I128::MAX);
}
}
Loading