From d5ea2c0afc5d01e063aa8de3fb5e527af1bd17e9 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 29 Dec 2025 10:07:16 -0700 Subject: [PATCH] ctutils: add `Choice::from_u8*` and `from_u16*` Adds a set of `Choice` constructors following the same pattern we already implement for `u32`, `u64`, and `u128`. Interestingly, this gives us an alternative to `Choice::new` which doesn't have to deal with out-of-range values: `Choice::from_u8_lsb`. With that possibility noted, this also documents `Choice::new` may be deprecated in the future. Ideally it could be removed before final `crypto-bigint` releases. Regarding `u16`, I actually ran into a real-world case where this would've been helpful inside `crypto-bigint`. --- ctutils/src/choice.rs | 163 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 3 deletions(-) diff --git a/ctutils/src/choice.rs b/ctutils/src/choice.rs index 7a1f31ec..76ec96e0 100644 --- a/ctutils/src/choice.rs +++ b/ctutils/src/choice.rs @@ -47,6 +47,17 @@ impl Choice { /// Create a new [`Choice`] from the given `u8` value, which MUST be either `0` or `1`. /// + ///
+ /// Potential panics and other misbehavior! + /// + /// This constructor panics in debug builds if the given value is not 0 or 1. + /// + /// It mainly exists as a convenience for migrating code which previously used `subtle`, but + /// we may deprecate it in future releases. + /// + /// Consider using a more specific non-panicking constructor, like [`Choice::from_u8_lsb`]. + ///
+ /// /// # Panics /// - in `debug` builds, panics if the value is anything other than `0` or `1`. // TODO(tarcieri): deprecate this in favor of non-panicking constructors? @@ -160,12 +171,80 @@ impl Choice { // `const fn` constructor methods // + // i64 + /// Returns the truthy value if `x == y`, and the falsy value otherwise. #[inline] pub const fn from_i64_eq(x: i64, y: i64) -> Self { Self::from_u64_nz(x as u64 ^ y as u64).not() } + // u8 + + /// Returns the truthy value if `x == y`, and the falsy value otherwise. + #[inline] + pub const fn from_u8_eq(x: u8, y: u8) -> Self { + Self::from_u8_nz(x ^ y).not() + } + + /// Returns the truthy value if `x <= y` and the falsy value otherwise. + #[inline] + pub const fn from_u8_le(x: u8, y: u8) -> Self { + Self::from_u8_lsb(bitle!(x, y, u8::BITS)) + } + + /// Initialize from the least significant bit of a `u8`. + #[inline] + pub const fn from_u8_lsb(value: u8) -> Self { + Self(value & 0x1) + } + + /// Returns the truthy value if `x < y`, and the falsy value otherwise. + #[inline] + pub const fn from_u8_lt(x: u8, y: u8) -> Self { + Self::from_u8_lsb(bitlt!(x, y, u8::BITS)) + } + + /// Returns the truthy value if `value != 0`, and the falsy value otherwise. + #[inline] + pub const fn from_u8_nz(value: u8) -> Self { + Self::from_u8_lsb(bitnz!(value, u8::BITS)) + } + + // u16 + + /// Returns the truthy value if `x == y`, and the falsy value otherwise. + #[inline] + pub const fn from_u16_eq(x: u16, y: u16) -> Self { + Self::from_u16_nz(x ^ y).not() + } + + /// Returns the truthy value if `x <= y` and the falsy value otherwise. + #[inline] + pub const fn from_u16_le(x: u16, y: u16) -> Self { + Self::from_u16_lsb(bitle!(x, y, u16::BITS)) + } + + /// Initialize from the least significant bit of a `u16`. + #[inline] + pub const fn from_u16_lsb(value: u16) -> Self { + Self((value & 0x1) as u8) + } + + /// Returns the truthy value if `x < y`, and the falsy value otherwise. + #[inline] + pub const fn from_u16_lt(x: u16, y: u16) -> Self { + Self::from_u16_lsb(bitlt!(x, y, u16::BITS)) + } + + /// Returns the truthy value if `value != 0`, and the falsy value otherwise. + #[inline] + pub const fn from_u16_nz(value: u16) -> Self { + Self::from_u16_lsb(bitnz!(value, u16::BITS)) + } + + // u32 + /// Returns the truthy value if `x == y`, and the falsy value otherwise. #[inline] pub const fn from_u32_eq(x: u32, y: u32) -> Self { @@ -181,7 +260,7 @@ impl Choice { /// Initialize from the least significant bit of a `u32`. #[inline] pub const fn from_u32_lsb(value: u32) -> Self { - Self::new((value & 0x1) as u8) + Self((value & 0x1) as u8) } /// Returns the truthy value if `x < y`, and the falsy value otherwise. @@ -196,6 +275,8 @@ impl Choice { Self::from_u32_lsb(bitnz!(value, u32::BITS)) } + // u64 + /// Returns the truthy value if `x == y`, and the falsy value otherwise. #[inline] pub const fn from_u64_eq(x: u64, y: u64) -> Self { @@ -211,7 +292,7 @@ impl Choice { /// Initialize from the least significant bit of a `u64`. #[inline] pub const fn from_u64_lsb(value: u64) -> Self { - Self::new((value & 0x1) as u8) + Self((value & 0x1) as u8) } /// Returns the truthy value if `x < y`, and the falsy value otherwise. @@ -226,6 +307,8 @@ impl Choice { Self::from_u64_lsb(bitnz!(value, u64::BITS)) } + // u128 + /// Returns the truthy value if `x == y`, and the falsy value otherwise. #[inline] pub const fn from_u128_eq(x: u128, y: u128) -> Self { @@ -241,7 +324,7 @@ impl Choice { /// Initialize from the least significant bit of a `u128`. #[inline] pub const fn from_u128_lsb(value: u128) -> Self { - Self::new((value & 1) as u8) + Self((value & 1) as u8) } /// Returns the truthy value if `x < y`, and the falsy value otherwise. @@ -539,6 +622,80 @@ mod tests { assert_eq!(Choice::from_i64_eq(1, 1), Choice::TRUE); } + #[test] + fn from_u8_eq() { + assert_eq!(Choice::from_u8_eq(0, 1), Choice::FALSE); + assert_eq!(Choice::from_u8_eq(1, 1), Choice::TRUE); + } + + #[test] + fn from_u8_le() { + assert_eq!(Choice::from_u8_le(0, 0), Choice::TRUE); + assert_eq!(Choice::from_u8_le(1, 0), Choice::FALSE); + assert_eq!(Choice::from_u8_le(1, 1), Choice::TRUE); + assert_eq!(Choice::from_u8_le(1, 2), Choice::TRUE); + } + + #[test] + fn from_u8_lsb() { + assert_eq!(Choice::from_u8_lsb(0), Choice::FALSE); + assert_eq!(Choice::from_u8_lsb(1), Choice::TRUE); + assert_eq!(Choice::from_u8_lsb(2), Choice::FALSE); + assert_eq!(Choice::from_u8_lsb(3), Choice::TRUE); + } + + #[test] + fn from_u8_lt() { + assert_eq!(Choice::from_u8_lt(0, 0), Choice::FALSE); + assert_eq!(Choice::from_u8_lt(1, 0), Choice::FALSE); + assert_eq!(Choice::from_u8_lt(1, 1), Choice::FALSE); + assert_eq!(Choice::from_u8_lt(1, 2), Choice::TRUE); + } + + #[test] + fn from_u8_nz() { + assert_eq!(Choice::from_u8_nz(0), Choice::FALSE); + assert_eq!(Choice::from_u8_nz(1), Choice::TRUE); + assert_eq!(Choice::from_u8_nz(2), Choice::TRUE); + } + + #[test] + fn from_u16_eq() { + assert_eq!(Choice::from_u16_eq(0, 1), Choice::FALSE); + assert_eq!(Choice::from_u16_eq(1, 1), Choice::TRUE); + } + + #[test] + fn from_u16_le() { + assert_eq!(Choice::from_u16_le(0, 0), Choice::TRUE); + assert_eq!(Choice::from_u16_le(1, 0), Choice::FALSE); + assert_eq!(Choice::from_u16_le(1, 1), Choice::TRUE); + assert_eq!(Choice::from_u16_le(1, 2), Choice::TRUE); + } + + #[test] + fn from_u16_lsb() { + assert_eq!(Choice::from_u16_lsb(0), Choice::FALSE); + assert_eq!(Choice::from_u16_lsb(1), Choice::TRUE); + assert_eq!(Choice::from_u16_lsb(2), Choice::FALSE); + assert_eq!(Choice::from_u16_lsb(3), Choice::TRUE); + } + + #[test] + fn from_u16_lt() { + assert_eq!(Choice::from_u16_lt(0, 0), Choice::FALSE); + assert_eq!(Choice::from_u16_lt(1, 0), Choice::FALSE); + assert_eq!(Choice::from_u16_lt(1, 1), Choice::FALSE); + assert_eq!(Choice::from_u16_lt(1, 2), Choice::TRUE); + } + + #[test] + fn from_u16_nz() { + assert_eq!(Choice::from_u16_nz(0), Choice::FALSE); + assert_eq!(Choice::from_u16_nz(1), Choice::TRUE); + assert_eq!(Choice::from_u16_nz(2), Choice::TRUE); + } + #[test] fn from_u32_eq() { assert_eq!(Choice::from_u32_eq(0, 1), Choice::FALSE);