Loading Constant-Time Operations Guide...
Security Expert 15 min read

Constant-Time Operations

Understanding and implementing timing-attack resistant code. Learn how to protect against side-channel attacks through constant-time cryptographic operations.

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.

Critical Security Requirement:

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 Code Example (DO NOT USE)
// ❌ 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

Constant-Time Code Example
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

Constant-Time Operations with Forge EC
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

Timing Analysis Test
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

Constant-Time Authentication
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.

Constant-Time Checklist:
  • ✅ 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