Constant-Time Operations
Side-channel claim limit. Nothing on this page claims constant-time behaviour or side-channel resistance for any target: the side-channel harness covers nothing (finding A48) and no measurement exists. The Rust and C cores are designed for constant time and unmeasured; managed runtimes (JIT, GC, bounds checks) make no timing claim. Where this page names constant-time or side-channel techniques, in prose or in code, they are design intent or recommendations, not measured properties.
MetaMUI Crypto Primitives code uses constant-time techniques for secret-dependent code paths as design intent. Constant-time behaviour is unmeasured and not claimed for any target (A48): the Rust and C cores are designed for constant time, and managed runtimes (JIT, GC, bounds checks) make no timing claim.
What Are Timing Attacks?
Timing attacks exploit variations in execution time based on secret data:
- Different code paths for different key bits
- Early returns based on comparisons
- Cache timing differences
- Branch prediction attacks
Constant-Time Behaviour (not claimed)
Secret-Independent Execution
- Same number of operations regardless of secret values
- No branching based on secret data
- No array indexing with secret indices
- No early returns in comparisons
Implementation Techniques
Constant-Time Comparison
def constant_time_compare(a: bytes, b: bytes) -> bool:
"""Compare two byte arrays in constant time."""
if len(a) != len(b):
return False
result = 0
for x, y in zip(a, b):
result |= x ^ y
return result == 0
Constant-Time Selection
/// Select between two values without branching
fn ct_select(condition: bool, a: u32, b: u32) -> u32 {
let mask = (condition as u32).wrapping_neg();
(a & mask) | (b & !mask)
}
Constant-Time Modular Reduction
// Reduce without timing leaks
uint32_t ct_mod(uint64_t x, uint32_t p) {
uint32_t q = (uint32_t)(x / p);
uint32_t r = (uint32_t)(x - (uint64_t)q * p);
// Constant-time correction
uint32_t mask = (uint32_t)((r >= p) - 1);
return r - (p & ~mask);
}
Algorithm-Specific Implementations
Ed25519
- Montgomery ladder for scalar multiplication
- No secret-dependent array lookups
- Constant-time field operations (design intent; not claimed, unmeasured, A48)
AES
- Constant-time S-box implementation
- No table lookups with secret indices
- Bitsliced implementations where possible
ChaCha20
- Fixed number of rounds
- No secret-dependent operations
- Constant-time quarter-round function
Poly1305
- Constant-time polynomial evaluation
- No early exits in MAC verification
- Constant-time modular arithmetic
Post-Quantum Algorithms
- ML-KEM-768: Constant-time NTT (design intent; not claimed, unmeasured, A48)
- Dilithium: Constant-time rejection sampling (design intent; not claimed, unmeasured, A48)
- Falcon-512: Constant-time floating-point operations (design intent; not claimed, unmeasured, A48)
Verification Methods
Static Analysis
# Check for timing leaks with tools
cargo install cargo-timing
cargo timing check
Runtime Testing
def test_constant_time_operation():
"""Test that operation time doesn't depend on input."""
import time
times = []
for secret in generate_test_secrets():
start = time.perf_counter_ns()
operation(secret)
end = time.perf_counter_ns()
times.append(end - start)
# Statistical analysis of timing variance
assert variance(times) < threshold
Formal Verification
- Model checking for constant-time properties
- Symbolic execution to verify paths
- Automated proofs where applicable
Platform Considerations
Compiler Optimizations
// Prevent optimization that might break constant-time
void secure_compare(const uint8_t *a, const uint8_t *b, size_t len) {
volatile uint8_t result = 0;
for (size_t i = 0; i < len; i++) {
result |= a[i] ^ b[i];
}
return result == 0;
}
CPU Features
- Avoid variable-time instructions
- Be aware of CPU-specific timing variations
- Test on multiple architectures
Language-Specific Issues
Python
- Use
secrets.compare_digest()for comparisons - Avoid short-circuit evaluation
- Be careful with integer operations
JavaScript
- No native constant-time operations
- Use WASM for critical paths
- Avoid timing-dependent optimizations
Best Practices
- Never Branch on Secrets: Use masking instead of if statements
- Avoid Secret Indices: Don’t use secrets as array indices
- Complete All Operations: No early returns based on secrets
- Test Thoroughly: Verify constant-time properties
- Document Assumptions: Make timing assumptions explicit
Code Examples
Constant-Time Maximum
def ct_max(a: int, b: int) -> int:
"""Return maximum of a and b in constant time."""
# Create mask: -1 if a > b, 0 otherwise
diff = a - b
mask = -(diff >> 31) # Arithmetic shift
# Select without branching
return (a & mask) | (b & ~mask)
Constant-Time Conditional Copy
fn ct_copy(dest: &mut [u8], src: &[u8], condition: bool) {
let mask = (condition as u8).wrapping_neg();
for (d, s) in dest.iter_mut().zip(src.iter()) {
*d = (*d & !mask) | (*s & mask);
}
}
Testing Constant-Time Properties
import statistics
import time
def measure_timing_variance(operation, inputs):
"""Measure timing variance of an operation."""
timings = []
for inp in inputs:
# Warm up
for _ in range(100):
operation(inp)
# Measure
times = []
for _ in range(1000):
start = time.perf_counter_ns()
operation(inp)
end = time.perf_counter_ns()
times.append(end - start)
timings.append(statistics.median(times))
# Check variance is within acceptable bounds
cv = statistics.stdev(timings) / statistics.mean(timings)
assert cv < 0.01, f"High timing variance: {cv}"