C comparison operators (strictly relational
< <= > >= and equality
== !=) yield 1 if the condition is satisfied and 0 if not. On some implementations (compilers) depending on the CPU and sometimes options, this may be implemented by code something like:
; int a = ..., b = ...;
; int x = a > b;
move a, r0
compare r0, b ; sometimes subtract with value ignored and only flags used
move 0, r1
move 1, r1
move r1, x
This executes a different sequence of instructions depending on whether the condition is true or false. Depending on the CPU, and if it has branch prediction/speculation as many (not all) do today depending on whether the conditional branch is predicted correctly which in turn depends on lots of other factors that are usually impossible to fully analyze, this may take different amounts of time which may be detectable (although typically damn small).
Some modern CPUs have conditional-move instructions, or even logicize-flag(s) instructions, that avoid this problem, but when writing source to be ported to unknown compiler versions and environments you can't be certain these will be present and (if so) used.
Actually you aren't guaranteed that even bitwise operations are constant time but in practice they're your best bet.
Similarly the logical negation operator
! in your code may be implemented by something like this which is not constant time:
move x, r0
test r0 ; sometimes included in the move
bzer lab1 ; (EDIT)
move 0, r1
move 1, r1
; use r1 as result
No Undefined Behavior:
Shifting by a count greater than or equal the width of the left operand is UB. Even when it comes from an
unsigned char as in the code you linked, the left operand of a shift is subject to the Integer Promotions of 22.214.171.124 (though NOT the Usual Arithmetic Conversions of 126.96.36.199) and so the left operand always has a width of at least 16 and they shift by 8 which is okay.
Also left shifting a signed type with a negative value or a value that overflows (i.e. at least one of the high N magnitude bits of the input is nonzero) is UB. They don't shift left.
Right shifting a signed type with a negative value is Implementation Defined: the sign bit may propagate or shift out. They do shift right values that are sometimes negative, but they immediately mask the result so that the bits which could be affected by this IB are not used.