@@ -89,27 +89,61 @@ comparison_primitives!(u32, le_u32, lt_u32, nz_u32, from_u32_lsb);
8989comparison_primitives ! ( u64 , le_u64, lt_u64, nz_u64, from_u64_lsb) ;
9090
9191macro_rules! borrowing_sub_primitives {
92- ( $ty: ty, $sub: ident, $from_lsb: ident) => {
92+ ( $ty: ty, $sub: ident, $sub_mask : ident , $sub_inner : ident , $ from_lsb: ident) => {
9393 /// Subtract `rhs` and an incoming borrow from `lhs`.
9494 ///
9595 /// Returns the wrapping difference and [`Choice::TRUE`] if the subtraction underflows.
9696 /// The incoming `borrow` subtracts one when true and zero when false.
9797 #[ inline]
9898 #[ must_use]
9999 pub const fn $sub( lhs: $ty, rhs: $ty, borrow: Choice ) -> ( $ty, Choice ) {
100- let difference = lhs. wrapping_sub( rhs) . wrapping_sub( borrow. to_u8( ) as $ty) ;
100+ let ( difference, borrow) = $sub_inner( lhs, rhs, borrow. to_u8( ) as $ty) ;
101+ ( difference, Choice :: $from_lsb( borrow) )
102+ }
103+
104+ /// Subtract `rhs` and an incoming borrow from `lhs`, using word-sized borrow masks.
105+ ///
106+ /// The incoming borrow is the most significant bit of `borrow`; all other bits are ignored.
107+ /// Returns the wrapping difference and an all-ones mask if the subtraction underflows,
108+ /// or zero otherwise.
109+ ///
110+ /// The outgoing mask can be passed directly to the next limb's subtraction, avoiding
111+ /// conversions to and from [`Choice`] at each step of a multi-limb subtraction.
112+ #[ inline]
113+ #[ must_use]
114+ pub const fn $sub_mask( lhs: $ty, rhs: $ty, borrow: $ty) -> ( $ty, $ty) {
115+ let ( difference, borrow) = $sub_inner( lhs, rhs, borrow >> ( <$ty>:: BITS - 1 ) ) ;
116+ ( difference, borrow. wrapping_neg( ) )
117+ }
118+
119+ // The incoming borrow must be zero or one; the outgoing borrow is also zero or one.
120+ #[ inline( always) ]
121+ const fn $sub_inner( lhs: $ty, rhs: $ty, borrow: $ty) -> ( $ty, $ty) {
122+ let difference = lhs. wrapping_sub( rhs) . wrapping_sub( borrow) ;
101123 // Keep all arithmetic in the operand type: widening can trigger incorrect
102124 // optimization on older compilers: https://github.com/rust-lang/rust/issues/149522
103- // Keep the full-width bit opaque before narrowing it to Choice, so a caller's
104- // masked add-back cannot be folded into a secret-dependent branch.
125+ // Keep the full-width bit opaque before forming a mask or narrowing it to Choice,
126+ // so a caller's masked add-back cannot be folded into a secret-dependent branch.
105127 let borrow = black_box( bitborrow!( lhs, rhs, difference, <$ty>:: BITS ) ) ;
106- ( difference, Choice :: $from_lsb ( borrow) )
128+ ( difference, borrow)
107129 }
108130 } ;
109131}
110132
111- borrowing_sub_primitives ! ( u32 , borrowing_sub_u32, from_u32_lsb) ;
112- borrowing_sub_primitives ! ( u64 , borrowing_sub_u64, from_u64_lsb) ;
133+ borrowing_sub_primitives ! (
134+ u32 ,
135+ borrowing_sub_u32,
136+ borrowing_sub_mask_u32,
137+ borrowing_sub_inner_u32,
138+ from_u32_lsb
139+ ) ;
140+ borrowing_sub_primitives ! (
141+ u64 ,
142+ borrowing_sub_u64,
143+ borrowing_sub_mask_u64,
144+ borrowing_sub_inner_u64,
145+ from_u64_lsb
146+ ) ;
113147
114148/// Count leading zero bits, returning 32 if `value` is zero.
115149#[ inline]
@@ -276,6 +310,12 @@ mod tests {
276310 assert ! ( borrowing_sub_u64( 0 , 0 , Choice :: TRUE ) . 1 . to_bool( ) ) ;
277311 assert ! ( !borrowing_sub_u32( 1 , 0 , Choice :: TRUE ) . 1 . to_bool( ) ) ;
278312 assert ! ( !borrowing_sub_u64( 1 , 0 , Choice :: TRUE ) . 1 . to_bool( ) ) ;
313+ assert ! ( borrowing_sub_mask_u32( 0 , 0 , u32 :: MAX ) . 0 == u32 :: MAX ) ;
314+ assert ! ( borrowing_sub_mask_u32( 0 , 0 , u32 :: MAX ) . 1 == u32 :: MAX ) ;
315+ assert ! ( borrowing_sub_mask_u64( 0 , 0 , u64 :: MAX ) . 0 == u64 :: MAX ) ;
316+ assert ! ( borrowing_sub_mask_u64( 0 , 0 , u64 :: MAX ) . 1 == u64 :: MAX ) ;
317+ assert ! ( borrowing_sub_mask_u32( 1 , 0 , u32 :: MAX ) . 1 == 0 ) ;
318+ assert ! ( borrowing_sub_mask_u64( 1 , 0 , u64 :: MAX ) . 1 == 0 ) ;
279319 assert ! ( leading_zeros_u32( 0 ) == 32 ) ;
280320 assert ! ( trailing_zeros_u32( 0 ) == 32 ) ;
281321 assert ! ( leading_zeros_u64( 0 ) == 64 ) ;
@@ -311,15 +351,17 @@ mod tests {
311351 }
312352
313353 macro_rules! subtraction_tests {
314- ( $test: ident, $ty: ty, $sub: ident) => {
354+ ( $test: ident, $ty: ty, $sub: ident, $sub_mask : ident ) => {
315355 #[ test]
316356 fn $test( ) {
317357 let half = 1 << ( <$ty>:: BITS >> 1 ) ;
318358 let high = 1 << ( <$ty>:: BITS - 1 ) ;
319359 let values = [ 0 , 1 , half - 1 , half, half + 1 , high - 1 , high, <$ty>:: MAX ] ;
320360 for lhs in values {
321361 for rhs in values {
322- for incoming in [ false , true ] {
362+ // Include noncanonical masks: only the most significant bit matters.
363+ for mask in [ 0 , 1 , high - 1 , high, <$ty>:: MAX - 1 , <$ty>:: MAX ] {
364+ let incoming = mask >> ( <$ty>:: BITS - 1 ) != 0 ;
323365 let ( expected, first_borrow) = lhs. overflowing_sub( rhs) ;
324366 let ( expected, second_borrow) =
325367 expected. overflowing_sub( <$ty>:: from( incoming) ) ;
@@ -329,15 +371,35 @@ mod tests {
329371 ( difference, borrow. to_bool( ) ) ,
330372 ( expected, first_borrow | second_borrow)
331373 ) ;
374+ assert_eq!(
375+ $sub_mask( lhs, rhs, mask) ,
376+ (
377+ expected,
378+ <$ty>:: from( first_borrow | second_borrow) . wrapping_neg( )
379+ )
380+ ) ;
332381 }
333382 }
334383 }
335384 }
336385 } ;
337386 }
338387
339- subtraction_tests ! ( sub_u32, u32 , borrowing_sub_u32) ;
340- subtraction_tests ! ( sub_u64, u64 , borrowing_sub_u64) ;
388+ subtraction_tests ! ( sub_u32, u32 , borrowing_sub_u32, borrowing_sub_mask_u32) ;
389+ subtraction_tests ! ( sub_u64, u64 , borrowing_sub_u64, borrowing_sub_mask_u64) ;
390+
391+ #[ test]
392+ fn sub_mask_propagates_borrow ( ) {
393+ let ( lo, borrow) = borrowing_sub_mask_u32 ( 0 , 1 , 0 ) ;
394+ let ( mid, borrow) = borrowing_sub_mask_u32 ( 0 , 0 , borrow) ;
395+ assert_eq ! ( ( lo, mid, borrow) , ( u32 :: MAX , u32 :: MAX , u32 :: MAX ) ) ;
396+ assert_eq ! ( borrowing_sub_mask_u32( 1 , 0 , borrow) , ( 0 , 0 ) ) ;
397+
398+ let ( lo, borrow) = borrowing_sub_mask_u64 ( 0 , 1 , 0 ) ;
399+ let ( mid, borrow) = borrowing_sub_mask_u64 ( 0 , 0 , borrow) ;
400+ assert_eq ! ( ( lo, mid, borrow) , ( u64 :: MAX , u64 :: MAX , u64 :: MAX ) ) ;
401+ assert_eq ! ( borrowing_sub_mask_u64( 1 , 0 , borrow) , ( 0 , 0 ) ) ;
402+ }
341403
342404 macro_rules! addition_tests {
343405 ( $test: ident, $ty: ty, $add: ident) => {
0 commit comments