Skip to content

fix: constant time - #54

Merged
Tranduy1dol merged 1 commit into
mainfrom
fix/constant-time
Jan 8, 2026
Merged

fix: constant time#54
Tranduy1dol merged 1 commit into
mainfrom
fix/constant-time

Conversation

@Tranduy1dol

@Tranduy1dol Tranduy1dol commented Jan 8, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Added secure conditional selection method for field operations.
    • Introduced new constant to core big integer type.
    • New benchmark suite for performance profiling constant-time operations.
  • Tests

    • Added extensive test coverage for constant-time arithmetic operations including division, modular exponentiation, and bit computations.
  • Chores

    • Updated development dependencies to newer versions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements constant-time cryptographic operations across U1024 and FieldElement types, introduces comprehensive benchmarks and test coverage for constant-time behavior, updates multiple dependencies, refactors RNG patterns in examples, and removes obsolete code.

Changes

Cohort / File(s) Summary
Dependency and Configuration Updates
Cargo.toml
Bumped rand (0.8→0.9), criterion (0.5→0.8), proptest (1.4→1.9); added new benchmark target constant_time_bench with harness = false.
Core Constant-Time Implementation
src/big_int/u1024.rs
Added public ONE constant; refactored bits(), div_rem, mod_pow, and mod_mul to use constant-time patterns with conditional_select and masked operations instead of explicit branching; replaced dynamic loops with fixed-position bit-selection logic.
Field Arithmetic Constant-Time
src/field/element.rs
Added public conditional_select() method for Montgomery-represented elements; refactored pow() exponentiation to use constant-time selection via conditional_select instead of direct conditional branches.
Constant-Time Benchmarks
benches/constant_time_bench.rs
New 236-line benchmark file covering conditional_select, bits(), mod_pow_hamming_weight, div_rem, and timing-leak detection with reduced sample sizes for expensive operations.
Constant-Time Test Suite
tests/constant_time_test.rs
New comprehensive 460-line test suite validating conditional_select, bits(), mod_pow, div_rem across edge cases, Hamming weights, boundary conditions, and correctness invariants.
Example RNG Refactoring
examples/generate_params.rs
Replaced RandBigInt trait with custom gen_biguint() and gen_biguint_range() helpers; switched from thread_rng() to rand::rng(); added bit-width masking for precision handling.
Polynomial Derivative Optimization
src/poly/univariate.rs
Refactored derivative computation to use scalar multiplication instead of accumulated loops; changed capacity from len() - 1 to len().saturating_sub(1).
Code Cleanup
lumen-math-macros/src/lib.rs, tests/derive_test.rs, tests/digest_test.rs, tests/field_element_test.rs
Converted for loops to for_each closures; removed commented verification blocks and explanatory comments.
Removed Example
examples/poly_display.rs
Deleted obsolete polynomial display example.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 whiskers twitch
Branches vanish, constants bloom so bright,
Timing safe at last through cryptographic night,
U1024's heartbeat, FieldElement's masked dance,
Every limb moves true—no leaks shall chance! 🔐⏰

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix: constant time' is vague and uses a non-descriptive term 'constant time' without clarifying what specific issue is being fixed or what constant-time improvements are being made. Use a more specific title that describes the actual changes, such as 'refactor: implement constant-time arithmetic operations' or 'fix: add constant-time conditional select and mod_pow implementations'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
examples/generate_params.rs (2)

96-109: Masking logic is correct, but consider input validation.

The bit-masking logic correctly ensures the generated BigUint has exactly bits bits. The mask (1u8 << extra_bits) - 1 applied to the most significant byte is correct for little-endian byte order.

However, consider adding validation for edge cases, such as when bits = 0, which would cause unexpected behavior.

🛡️ Suggested input validation
 fn gen_biguint<R: Rng>(rng: &mut R, bits: usize) -> BigUint {
+    if bits == 0 {
+        return BigUint::from(0u32);
+    }
     let bytes_needed = (bits + 7).div_ceil(8);
     let mut bytes = vec![0u8; bytes_needed];
     rng.fill(&mut bytes[..]);

111-127: Rejection sampling is not constant-time.

While the rejection sampling approach is mathematically correct for generating uniform random values in the range [low, high), it is not constant-time because the number of loop iterations varies depending on the random values generated.

Given that this PR focuses on constant-time cryptographic operations, this might be acceptable for example code used in parameter generation (not runtime cryptographic operations). However, be aware that this could leak timing information about the range size or random values in side-channel sensitive contexts.

Additionally, consider adding input validation for the case where low >= high.

🛡️ Suggested input validation
 fn gen_biguint_range<R: Rng>(rng: &mut R, low: &BigUint, high: &BigUint) -> BigUint {
     use num_traits::Zero;
 
+    assert!(low < high, "low must be less than high");
     let range = high - low;
-    if range.is_zero() {
-        return low.clone();
-    }
src/poly/univariate.rs (1)

119-124: Consider performance implications of the new approach.

The refactored derivative implementation is mathematically correct and more explicit. The saturating_sub(1) on Line 119 adds defensive robustness, though it's redundant given the early return on Line 115-117.

The new scalar multiplication approach (Lines 121-122) creates a FieldElement for each index, which may have overhead compared to accumulation-based approaches. For polynomials with many terms, consider profiling to ensure this doesn't introduce performance regressions.

benches/constant_time_bench.rs (1)

93-95: Consider formatting the long hex string for readability.

The 256-character hex modulus on Line 94 could be split across multiple lines for improved readability.

♻️ Proposed formatting
     let modulus = U1024::from_hex(
-        "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB43",
+        "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\
+           FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB43",
     );
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e385879 and ff5017f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • Cargo.toml
  • benches/constant_time_bench.rs
  • examples/generate_params.rs
  • examples/poly_display.rs
  • lumen-math-macros/src/lib.rs
  • src/big_int/u1024.rs
  • src/field/element.rs
  • src/poly/univariate.rs
  • tests/constant_time_test.rs
  • tests/derive_test.rs
  • tests/digest_test.rs
  • tests/field_element_test.rs
💤 Files with no reviewable changes (4)
  • tests/field_element_test.rs
  • tests/derive_test.rs
  • examples/poly_display.rs
  • tests/digest_test.rs
🧰 Additional context used
🧬 Code graph analysis (6)
benches/constant_time_bench.rs (2)
src/big_int/u1024.rs (4)
  • conditional_select (999-1020)
  • zero (973-975)
  • from_u64 (403-407)
  • from_hex (322-343)
src/field/element.rs (2)
  • conditional_select (190-192)
  • zero (126-131)
src/field/element.rs (3)
src/big_int/u1024.rs (1)
  • conditional_select (999-1020)
src/traits.rs (1)
  • conditional_select (25-25)
src/big_int/backend/avx2.rs (1)
  • conditional_select (56-78)
src/poly/univariate.rs (2)
src/field/element.rs (1)
  • new (107-115)
src/big_int/u1024.rs (1)
  • from_u64 (403-407)
src/big_int/u1024.rs (3)
src/field/element.rs (2)
  • one (134-136)
  • conditional_select (190-192)
src/traits.rs (2)
  • one (22-22)
  • conditional_select (25-25)
src/big_int/backend/avx2.rs (1)
  • conditional_select (56-78)
examples/generate_params.rs (1)
src/big_int/u1024.rs (1)
  • bits (284-297)
tests/constant_time_test.rs (2)
src/big_int/u1024.rs (4)
  • from_u64 (403-407)
  • conditional_select (999-1020)
  • bits (284-297)
  • from_hex (322-343)
src/field/element.rs (1)
  • conditional_select (190-192)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test and Coverage
🔇 Additional comments (24)
examples/generate_params.rs (1)

11-11: No action needed. The rand::rng() API is correct for rand 0.9 (the version specified in Cargo.toml). This function replaced the deprecated rand::thread_rng() as of rand 0.9.0 and is the recommended approach.

src/poly/univariate.rs (1)

13-13: LGTM!

The addition of U1024 to the imports is necessary for the updated derivative implementation.

lumen-math-macros/src/lib.rs (1)

144-146: LGTM!

The conversion from imperative for loops to functional-style for_each is a clean stylistic improvement that maintains the same behavior while making the code more idiomatic.

Also applies to: 155-157, 170-172, 180-182

Cargo.toml (2)

36-38: LGTM!

The new constant_time_bench benchmark target is correctly configured with harness = false, which is appropriate for Criterion-based benchmarks.


8-8: Verify compatibility of dependency updates and confirm code compiles with new versions.

The dependency bumps include significant breaking changes:

  • rand 0.8 → 0.9: Major API changes including method renames (genrandom, gen_rangerandom_range, thread_rng()rng()), trait renames (from_rngtry_from_rng, from_entropyfrom_os_rng), feature renames (serde1serde, getrandomos_rng), and MSRV bump to 1.63.0
  • criterion 0.5 → 0.8 (minor version bumps)
  • proptest 1.4 → 1.9 (minor version bumps)

Direct usage of affected rand APIs appears minimal in the codebase. However, num-bigint 0.4.6 depends on rand via its features = ["rand"], which requires verification that the version is compatible. Run the full test suite to confirm all functionality works correctly with these updated versions.

src/field/element.rs (2)

190-192: LGTM!

The new conditional_select function correctly provides a constant-time selection primitive by delegating to U1024::conditional_select. This is a key building block for constant-time cryptographic operations.


202-206: Excellent constant-time refactoring.

The refactored pow method eliminates timing side-channels by:

  1. Always computing the product (Line 203)
  2. Using conditional_select to choose the result without branching (Line 205)
  3. Using square() for explicit squaring (Line 206)

This approach trades a small performance cost (computing unused multiplications) for security, which is the correct tradeoff for cryptographic code.

benches/constant_time_bench.rs (7)

1-11: LGTM!

The file header provides clear documentation of the benchmark's purpose and usage, with helpful guidelines for interpreting constant-time results (timing differences within ~5% noise margins).


23-47: LGTM!

The bench_conditional_select function provides excellent coverage:

  • Fixed true and false cases establish baseline timing
  • The alternating case defeats branch prediction, which is critical for detecting timing variations

53-85: LGTM!

The bench_bits function tests a comprehensive range of bit patterns (zero, small, medium, large, sparse high-limb) to verify constant-time behavior of bit-length operations across different input distributions.


91-142: Excellent coverage of constant-time exponentiation.

The bench_mod_pow_hamming_weight function is critical for verifying constant-time behavior in modular exponentiation. By testing exponents with Hamming weights of 1, 32, 64, and 128, it can detect timing leaks that depend on the number of set bits—a common vulnerability in non-constant-time implementations.

The sample_size(50) is appropriate for balancing statistical significance with benchmark duration for this expensive operation.


148-178: LGTM!

The bench_div_rem function appropriately tests division with varying dividend sizes (small, medium, large) against a fixed divisor. The sample_size(50) is suitable for this moderately expensive operation.


188-226: Excellent timing leak detection design.

The bench_timing_leak_detection function is specifically crafted to expose timing side-channels by comparing:

  1. Sparse vs. dense exponents in modular exponentiation
  2. true vs. false in conditional_select

The sample_size(100) provides good statistical power for detecting timing differences. This is a critical test for validating the constant-time implementation.


228-236: LGTM!

The benchmark group configuration correctly includes all test functions and uses the standard Criterion setup.

tests/constant_time_test.rs (5)

13-113: Comprehensive test coverage for conditional_select.

The tests cover the essential cases: true/false selection, zero values, max values, identical inputs, and per-limb verification. The assertions correctly match the implementation semantics where choice=true returns the first argument.


119-178: Thorough edge-case coverage for bits().

Tests correctly verify boundary conditions including zero, single-bit values, multi-limb placements, and full 1024-bit maximum. The expected values align with the constant-time implementation logic.


184-269: Good coverage of mod_pow including mathematical properties.

The test suite validates basic operations, edge cases (zero/one exponent, modulus one), Fermat's Little Theorem verification, and panic behavior. This provides confidence in both correctness and robustness.


275-342: Solid test coverage for constant-time div_rem.

The tests verify key invariants (q * b + r = a) and cover important edge cases. The panic test correctly expects "Division by zero".


349-459: Valuable property-based tests for constant-time guarantees.

The Hamming weight tests are particularly important for cryptographic implementations—they verify that results remain correct regardless of bit patterns, which is essential for timing-attack resistance. The invariant checks across multiple values provide additional confidence.

src/big_int/u1024.rs (5)

145-146: Clean addition of ONE constant.

The constant correctly represents the value 1 with 16 limbs, consistent with the existing one() method. This enables more efficient compile-time usage.


802-819: Correct constant-time binary long division.

The implementation properly uses:

  1. Fixed 1024 iterations (constant time regardless of operand sizes)
  2. Mask-based conditional_select for both remainder and quotient updates
  3. Borrow check from borrowing_sub to determine subtraction feasibility

The algorithm correctly implements the binary division invariant where remainder accumulates bits and conditionally subtracts the divisor.


928-944: Proper constant-time modular exponentiation.

The implementation correctly:

  1. Always computes the multiplication (result.mod_mul(&base, ...))
  2. Uses conditional_select to conditionally update result
  3. Always squares base regardless of the exponent bit

This ensures uniform execution time across all exponent values, preventing timing attacks on the secret exponent.


1010-1018: Solid constant-time conditional selection with black_box.

The implementation correctly uses:

  • Mask derivation via wrapping_sub (avoiding branches)
  • core::hint::black_box to prevent compiler optimizations that might reintroduce data-dependent branches
  • Bitwise operations that execute in constant time

The semantics are consistent with the AVX2 implementation in src/big_int/backend/avx2.rs.


284-297: Well-implemented constant-time bit-length computation.

The mask-based conditional update ensures no data-dependent branching. The iteration order (low-to-high) correctly allows higher limb results to overwrite lower ones.

Note on leading_zeros(): This maps to LLVM's ctlz intrinsic, which on x86_64 compiles to a single LZCNT instruction (constant-time) only when the target supports it (modern CPUs: Intel Haswell+, AMD Bulldozer+) or when compiled with -C target-feature=+lzcnt. Without these conditions, LLVM falls back to BSR plus a zero-check, which is not constant-time. If constant-time behavior is required across all x86_64 targets, ensure compilation flags are set accordingly or use a custom constant-time leading-zeros implementation.

@codecov

codecov Bot commented Jan 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.76923% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/big_int/u1024.rs 70.58% 5 Missing ⚠️
Files with missing lines Coverage Δ
lumen-math-macros/src/lib.rs 33.33% <ø> (ø)
src/field/element.rs 89.24% <100.00%> (+0.35%) ⬆️
src/poly/univariate.rs 75.22% <100.00%> (-0.23%) ⬇️
src/big_int/u1024.rs 84.53% <70.58%> (+0.02%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Tranduy1dol
Tranduy1dol merged commit 82b9be1 into main Jan 8, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant