Overview
Constant-time operations are crucial for preventing timing attacks in cryptographic implementations. Forge EC provides built-in constant-time operations and guidelines for maintaining timing-attack resistance in your applications.
All cryptographic operations involving secret data must be implemented in constant time to prevent timing attacks. This includes key operations, signature verification, and any computation that depends on private keys or sensitive data.
Timing Attacks
Timing attacks exploit variations in execution time to extract information about secret data. Even microsecond differences can leak sensitive information over many observations.
Attack Vectors
// ❌ VULNERABLE: Early return leaks timing information
fn vulnerable_verify(signature: &[u8], expected: &[u8]) -> bool {
if signature.len() != expected.len() {
return false; // Early return - timing leak!
}
for (a, b) in signature.iter().zip(expected.iter()) {
if a != b {
return false; // Early return - timing leak!
}
}
true
}
// ❌ VULNERABLE: Conditional operations leak timing
fn vulnerable_scalar_mult(scalar: &[u8], point: &Point) -> Point {
let mut result = Point::identity();
for bit in scalar.iter() {
result = result.double();
if *bit == 1 {
result = result.add(point); // Conditional operation - timing leak!
}
}
result
}
Constant-Time Principles
Constant-time implementations ensure that execution time is independent of secret data values.
Secure Implementation
use forge_ec::subtle::{ConstantTimeEq, Choice};
// ✅ SECURE: Constant-time comparison
fn secure_verify(signature: &[u8], expected: &[u8]) -> bool {
use forge_ec::subtle::ConstantTimeEq;
// Always check full length, no early returns
let len_match = signature.len() == expected.len();
// Pad shorter array to avoid timing leaks
let max_len = signature.len().max(expected.len());
let mut sig_padded = vec![0u8; max_len];
let mut exp_padded = vec![0u8; max_len];
sig_padded[..signature.len()].copy_from_slice(signature);
exp_padded[..expected.len()].copy_from_slice(expected);
// Constant-time comparison
let bytes_match = sig_padded.ct_eq(&exp_padded);
len_match && bytes_match.into()
}
// ✅ SECURE: Constant-time scalar multiplication
fn secure_scalar_mult(scalar: &[u8], point: &Point) -> Point {
let mut result = Point::identity();
let mut addend = *point;
for byte in scalar.iter() {
for bit_index in 0..8 {
let bit = (byte >> bit_index) & 1;
let choice = Choice::from(bit);
// Always perform both operations, select result
let add_result = result.add(&addend);
result = Point::conditional_select(&result, &add_result, choice);
addend = addend.double();
}
}
result
}
Implementation Guidelines
Best practices for implementing constant-time operations in Forge EC applications.
Using Forge EC's Constant-Time Primitives
use forge_ec::{PrivateKey, PublicKey, Signature, subtle::ConstantTimeEq};
fn secure_signature_verification() -> Result> {
let message = b"Important message";
let private_key = PrivateKey::new();
let public_key = private_key.public_key();
// Sign message
let signature = private_key.sign(message)?;
// ✅ SECURE: Constant-time verification
let is_valid = public_key.verify_constant_time(message, &signature)?;
// ✅ SECURE: Constant-time comparison of signatures
let expected_signature = private_key.sign(message)?;
let signatures_match = signature.ct_eq(&expected_signature).into();
Ok(is_valid && signatures_match)
}
Verification Techniques
Methods to verify that your implementations are truly constant-time.
Testing for Timing Leaks
use std::time::Instant;
use forge_ec::{PrivateKey, subtle::ConstantTimeEq};
#[cfg(test)]
mod timing_tests {
use super::*;
#[test]
fn test_constant_time_comparison() {
let key1 = PrivateKey::new();
let key2 = PrivateKey::new();
let mut times_equal = Vec::new();
let mut times_different = Vec::new();
// Test equal keys
for _ in 0..1000 {
let start = Instant::now();
let _ = key1.to_bytes().ct_eq(&key1.to_bytes());
times_equal.push(start.elapsed().as_nanos());
}
// Test different keys
for _ in 0..1000 {
let start = Instant::now();
let _ = key1.to_bytes().ct_eq(&key2.to_bytes());
times_different.push(start.elapsed().as_nanos());
}
// Statistical analysis
let avg_equal: f64 = times_equal.iter().sum::() as f64 / times_equal.len() as f64;
let avg_different: f64 = times_different.iter().sum::() as f64 / times_different.len() as f64;
// Times should be statistically similar
let difference_ratio = (avg_equal - avg_different).abs() / avg_equal.max(avg_different);
assert!(difference_ratio < 0.1, "Timing difference too large: {}", difference_ratio);
}
}
Complete Examples
Real-world examples of constant-time implementations.
Secure Authentication System
use forge_ec::{PrivateKey, PublicKey, Signature, subtle::ConstantTimeEq};
use std::collections::HashMap;
pub struct SecureAuthenticator {
users: HashMap,
}
impl SecureAuthenticator {
pub fn new() -> Self {
Self {
users: HashMap::new(),
}
}
pub fn register_user(&mut self, username: String, public_key: PublicKey) {
self.users.insert(username, public_key);
}
// ✅ SECURE: Constant-time authentication
pub fn authenticate(&self, username: &str, message: &[u8], signature: &Signature) -> bool {
// Always perform verification, even if user doesn't exist
let dummy_key = PublicKey::default();
let (key_to_use, user_exists) = match self.users.get(username) {
Some(key) => (key, true),
None => (&dummy_key, false),
};
// Always verify signature (constant-time)
let signature_valid = key_to_use
.verify_constant_time(message, signature)
.unwrap_or(false);
// Return result only if both user exists and signature is valid
user_exists && signature_valid
}
}
Best Practices
Essential guidelines for maintaining constant-time security.
- ✅ Use Forge EC's built-in constant-time operations
- ✅ Avoid conditional branches on secret data
- ✅ Use constant-time comparison functions
- ✅ Implement dummy operations for timing consistency
- ✅ Test for timing leaks with statistical analysis
- ✅ Clear sensitive data from memory securely
Common Pitfalls to Avoid
- Early returns: Never return early based on secret data
- Conditional operations: Avoid if/else branches on secrets
- Variable-time algorithms: Use constant-time alternatives
- Memory access patterns: Ensure consistent memory access
- Compiler optimizations: Be aware of optimization effects