Strictly speaking, programming language standards don't guarantee timings, so the compiler could always insert branches in surprising ways. In some (admittedly rare) cases, they actually do. Some high-level languages like Python don't have constant time arithmetic at all. (On the other hand, having arbitrary precision integer by default makes prototyping very easy. SAGE is based on Python for a reason.)
The CPU on the other hand is more reliable: while every branch it takes will have variable timings, arithmetic timings are almost always independent from the content of the operands. There are two important exceptions to this:
- If your CPU doesn't have a barrel shifter, the timings of arithmetic shifts depends on by how many bits you shift. That's why modern cryptographic primitives now shift by constant amounts, unlike RC4.
- Some small CPUs have variable time multiplication. 64-bit multiplication is the worst, because you need compiler support to emulate it on 32-bit platforms and smaller.
On modern desktop/laptop/palmtop CPUs, all you care about are secret dependant branches and secret dependent indices in your generated binary code. And that is pretty easy to test with Valgrind. Replace your secret inputs by uninitialised blobs of data, see how it reacts: if Valgrind complains about a jump or indirection depending on uninitialised data, you may have a problem (you'll have at least one in the case of verifiers). If it doesn't complain however, you're good.
The CPU on the other hand is more reliable: while every branch it takes will have variable timings, arithmetic timings are almost always independent from the content of the operands. There are two important exceptions to this:
- If your CPU doesn't have a barrel shifter, the timings of arithmetic shifts depends on by how many bits you shift. That's why modern cryptographic primitives now shift by constant amounts, unlike RC4.
- Some small CPUs have variable time multiplication. 64-bit multiplication is the worst, because you need compiler support to emulate it on 32-bit platforms and smaller.
On modern desktop/laptop/palmtop CPUs, all you care about are secret dependant branches and secret dependent indices in your generated binary code. And that is pretty easy to test with Valgrind. Replace your secret inputs by uninitialised blobs of data, see how it reacts: if Valgrind complains about a jump or indirection depending on uninitialised data, you may have a problem (you'll have at least one in the case of verifiers). If it doesn't complain however, you're good.