Skip to content

refactor(math): dead-code & structural cleanup of the math crate#598

Open
diegokingston wants to merge 19 commits into
mainfrom
cleanup/math-dead-code
Open

refactor(math): dead-code & structural cleanup of the math crate#598
diegokingston wants to merge 19 commits into
mainfrom
cleanup/math-dead-code

Conversation

@diegokingston
Copy link
Copy Markdown
Collaborator

@diegokingston diegokingston commented May 20, 2026

Summary

Dead-code and structural cleanup of the math crate. Removes unused public
API, flattens the FFT module layout, and consolidates tests into one place.
No behavior changes — every removed item has zero production callers.

Changes

Dead code removal

  • Drop the unused Deserializable trait and the unreachable error variants
    it fed: DeserializationError, PairingError,
    ByteConversionError::{InvalidValue, PointNotInSubgroup, ValueNotCompressed},
    CreationError::InvalidDecString.
  • Remove the vestigial helpers module (next_power_of_two,
    resize_to_next_power_of_two) — unused.

Visibility tightening

  • Gate Polynomial::{truncate, reverse} and Polynomial::evaluate_fft_bit_reversed
    behind #[cfg(test)]. Every caller is test-only (truncate/reverse are
    reached only through the already-cfg(test) fast_division /
    invert_polynomial_mod; evaluate_fft_bit_reversed only from the FFT
    property tests), so this keeps them out of the production binary.

Structural

  • Flatten fft/cpu/* up to fft/* — the cpu layer had no gpu sibling.
  • Collapse the polynomial/ folder into a single polynomial.rs; delete the
    stale polynomial/README.md.
  • Consolidate scattered inline test modules into the dedicated tests/
    directory.

Folded in (commit 48762cf)

  • Delete reference_fft.rs (the reference radix-2 FFT) and its four
    comparison tests. It existed only to cross-check the production Bowers FFT,
    which is already verified directly against a naive O(n^2) DFT at the same
    sizes — the reference oracle was redundant.
  • Gate get_powers_of_primitive_root as test-only; production twiddle
    generation goes through bowers_fft::LayerTwiddles.
  • Collapse the evaluate_fft_cpu / interpolate_fft_cpu one-line wrappers
    into their sole callers; narrow coset_lde_full_into to pub(crate).
  • Relocate the inline test modules from bit_reversing.rs, goldilocks.rs
    and extensions_goldilocks.rs into tests/.

Footprint

25 files in crypto/math, +674 / -1113. The insertions are mostly the
polynomial/mod.rs content relocating into polynomial.rs.

Test plan

  • cargo test -p math — 211 pass
  • make lint (clippy --workspace --all-targets -D warnings) — clean

…r variants

`Deserializable` had a single impl (`Proof<T>`) whose only `deserialize`
call site was inside that impl itself — a recursion with no external
entry point. Production deserialization uses `bincode`/serde everywhere.
Delete the trait and the `Proof<T>` impl.

`merkle_tree/proof.rs` also carried a dead `Serializable` impl behind
`#[cfg(feature = "alloc")]` — a feature the `crypto` crate does not even
define, so the block never compiled. It referenced `math::traits::Serializable`,
which does not exist. Remove it.

Error cleanup (none constructed anywhere in the workspace):
- `DeserializationError` enum + `From<ByteConversionError>` impl — only
  reachable through the now-deleted `Deserializable`.
- `PairingError` enum — lambda_vm has no pairings.
- `ByteConversionError::{InvalidValue, PointNotInSubgroup, ValueNotCompressed}`
  — the latter two are elliptic-curve concepts; there is no EC here.
- `CreationError::InvalidDecString`.

Pure dead-code removal; `cargo build`/`cargo test -p math` unchanged
(216 tests pass).
`helpers::next_power_of_two(u64)` was bit-for-bit equivalent to stdlib
`u64::next_power_of_two()`. Its only caller, `resize_to_next_power_of_two`,
was used solely by the `fibonacci_rap` example — production trace sizing
already calls stdlib `.next_power_of_two()` directly everywhere.

Move `resize_to_next_power_of_two` into `examples/fibonacci_rap.rs` as a
local helper (rewritten on `usize` with the stdlib call, dropping the old
`try_into().unwrap()` TODO), and delete the `helpers` module.
`evaluate_fft_bit_reversed` was a non-gated `pub fn` but is only called
from the FFT property tests — no production caller in stark or prover.
Gate it `#[cfg(test)]` so it stops appearing in the production API
surface. Restore the gate (or remove it) when a real caller appears.
Both methods are called only from `fast_division` / `invert_polynomial_mod`,
which are themselves already `#[cfg(test)]`. No production caller in stark
or prover. Gate them `#[cfg(test)]` so they leave the production API surface.

`long_division_with_remainder` is left public — it is used by the
debug-checks path (`check_boundary_polys_divisibility`) and is a
legitimate general polynomial operation other code could want.
The `fft/cpu/` directory implied a `fft/gpu/` sibling that never existed —
GPU code lives in the separate `crypto/math-cuda` crate. The `cpu` nesting
was a vestigial holdover.

Move `bit_reversing`, `bowers_fft`, `roots_of_unity`, `fft`, and
`bowers_fft_tests` from `fft/cpu/` up to `fft/`; delete `fft/cpu/mod.rs`
and merge its declarations into `fft/mod.rs`. Update every
`fft::cpu::...` / `super::cpu::...` path across math, stark, prover, and
math-cuda to `fft::...`.

Pure module move — no logic change. 216 math tests pass; stark, prover,
math-cuda all build.
Two problems compounded:
1. `polynomial/` was a folder holding a single file (`mod.rs`) — the
   directory level served no purpose.
2. Two files were named `polynomial` (`polynomial/mod.rs` and
   `fft/polynomial.rs`), both defining `impl Polynomial<...>` blocks.

Merge `fft/polynomial.rs` (the FFT-specific `impl Polynomial` blocks plus
the `evaluate_fft_cpu` / `interpolate_fft_cpu` free functions) into
`polynomial/mod.rs`, then rename it to a top-level `crypto/math/src/polynomial.rs`
and delete the folder.

Result: one `polynomial.rs` holding the `Polynomial` type and all of its
methods (core ring ops + FFT). `fft/` is now purely the FFT *algorithm*
layer (Bowers, twiddles, bit-reversal, roots of unity).

Also delete the stale `polynomial/README.md` — inherited lambdaworks
docs referencing multilinear-polynomial files that don't exist in
lambda_vm.

Updated the one external path (`fft::polynomial::compose_fft` ->
`polynomial::compose_fft`). 216 math + 124 stark tests pass.
Three test conventions coexisted: the `tests/` directory, a sibling
`bowers_fft_tests.rs` next to source, and an inline `#[cfg(test)] mod`
in the polynomial module. Unify on the `tests/` directory.

- Move `fft/bowers_fft_tests.rs` -> `tests/bowers_fft_tests.rs`, register
  it in `tests/mod.rs`, drop the `mod bowers_fft_tests;` from `fft/mod.rs`.
- Move the inline coset-LDE `mod tests` out of `polynomial.rs` into a new
  `coset_lde_tests` module in `tests/fft_tests.rs`.
- Rename `fft/fft.rs` -> `fft/reference_fft.rs`: after the Step 6 flatten
  it collided with its own parent module name (`fft::fft`), which clippy
  rejects. The new name also says what it is — the reference radix-2 FFT
  used only to cross-check the production Bowers FFT. Gate the module
  `#[cfg(test)] pub(crate)` since its whole content is test-only.

216 math tests pass (same count — no tests lost in the move).
Comment thread crypto/math/src/polynomial.rs Outdated

/// Only used by the test-only `fast_division` / `invert_polynomial_mod`.
#[cfg(test)]
pub fn truncate(&self, k: usize) -> Self {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium – Breaking API change

truncate and reverse were previously unconditional pub fns. Gating them with #[cfg(test)] silently removes them from any non-test build. Any downstream crate (or integration test that doesn't run under cfg(test)) that calls poly.truncate(k) will fail to compile.

If the intent is to restrict visibility, use pub(crate) instead of the test gate — that keeps the symbol available in non-test builds while preventing external use:

Suggested change
pub fn truncate(&self, k: usize) -> Self {
pub(crate) fn truncate(&self, k: usize) -> Self {

Comment thread crypto/math/src/polynomial.rs Outdated

/// Only used by the test-only `fast_division`.
#[cfg(test)]
pub fn reverse(&self, d: usize) -> Self {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same concern as truncate above — #[cfg(test)] removes this from non-test builds, breaking any caller outside a test binary.

Suggested change
pub fn reverse(&self, d: usize) -> Self {
pub(crate) fn reverse(&self, d: usize) -> Self {

Comment thread crypto/math/src/polynomial.rs Outdated
/// Currently exercised only by the FFT property tests; gated to keep it out
/// of the production surface until a non-test caller needs it.
#[cfg(test)]
pub fn evaluate_fft_bit_reversed<F: IsFFTField + IsSubFieldOf<E>>(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium – Breaking API change

evaluate_fft_bit_reversed was previously an unconditional pub fn. Gating it with #[cfg(test)] removes it from non-test builds. The comment says it's "currently exercised only by FFT property tests," but it was part of the public API surface — the FRI commit phase is an obvious future caller. Use pub(crate) to restrict visibility without a test gate.

Comment thread crypto/math/src/polynomial.rs Outdated
E: Send + Sync,
{
let scaled = Polynomial::interpolate_fft::<F>(fft_evals)?;
Ok(scaled.scale(&offset.inv().unwrap()))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Low – Pre-existing panic (not introduced here, but now more visible)

offset.inv().unwrap() panics if offset == 0. The function returns Result<_, FFTError> so a zero offset could be turned into a proper error:

Suggested change
Ok(scaled.scale(&offset.inv().unwrap()))
Ok(scaled.scale(&offset.inv().map_err(|_| FFTError::InvalidInput)?))

(adjust to whatever the appropriate FFTError variant is)

@claude
Copy link
Copy Markdown

claude Bot commented May 20, 2026

Review: Cleanup/math dead code

Clean structural refactor — removes ~830 lines of dead code and flattens the fft::cpu layer to fft::. The overall direction is good.

Medium — Breaking API changes

Three previously-public methods are now gated by #[cfg(test)], which silently removes them from non-test builds. Any downstream crate that calls them will fail to compile without a helpful error:

Symbol File Line
Polynomial::truncate crypto/math/src/polynomial.rs 201
Polynomial::reverse crypto/math/src/polynomial.rs 211
Polynomial::evaluate_fft_bit_reversed crypto/math/src/polynomial.rs 971

If the goal is to hide these from external crates, pub(crate) is the right tool — it restricts visibility without disappearing from non-test builds. Inline suggestions attached.

Low — Pre-existing panic surfaced by the move

interpolate_offset_fft calls offset.inv().unwrap() (line 1034). The function already returns Result<_, FFTError>, so a zero offset could be propagated as an error instead of panicking. Not introduced by this PR, but this is a good moment to fix it since the code is being touched.

Removed public API (no objection, just noting)

The following public items are removed with no replacement:

  • DeserializationError, PairingError (from math::errors)
  • Deserializable trait (from math::traits)
  • ByteConversionError::InvalidValue / PointNotInSubgroup / ValueNotCompressed
  • CreationError::InvalidDecString
  • math::helpers::{next_power_of_two, resize_to_next_power_of_two}

The removed Proof::deserialize had a hardcoded chunks(8) regardless of T's actual size — removing it rather than fixing it is the right call.

No issues with the restructuring itself

The fft::cpu -> fft:: path flattening, the polynomial/mod.rs -> polynomial.rs consolidation, and the reference_fft renaming are all clean.

@github-actions
Copy link
Copy Markdown

Codex Code Review

No issues found in the PR diff for security, correctness, significant performance, or simplicity.

I attempted to run tests, but the sandbox could not resolve/fetch Cargo dependencies and also hit read-only Cargo/Rustup cache paths. Static review only.

diegokingston and others added 4 commits May 21, 2026 17:51
…tests

The reference radix-2 FFT existed only to cross-check the production Bowers
FFT. Bowers is already verified directly against a naive O(n^2) DFT at the
same sizes, so reference_fft.rs and its four comparison tests are dead
weight. gate get_powers_of_primitive_root as test-only (production twiddles
go through LayerTwiddles), collapse the evaluate_fft_cpu / interpolate_fft_cpu
wrappers into their sole callers, and narrow coset_lde_full_into to
pub(crate).

Move the inline test modules from bit_reversing.rs, goldilocks.rs and
extensions_goldilocks.rs into the dedicated tests/ directory.
@diegokingston diegokingston marked this pull request as ready for review May 22, 2026 13:16
@github-actions
Copy link
Copy Markdown

Codex Code Review

Findings

  1. Potential bug / API regression: roots_of_unity.rs now hides get_powers_of_primitive_root behind #[cfg(test)]. This was previously a public helper and supports non-coset configurations like Natural, BitReverse, and inverse variants. Any non-test downstream code importing it will now fail to compile, and the tests still pass because they compile with cfg(test). Keep it public unless this is an intentional breaking API removal.

Security

No Critical/High/Medium/Low security vulnerabilities found in the diff.

Verification

I attempted cargo check, but it could not run in this environment because rustup/cargo needed writable home cache paths and an unavailable git dependency.

@claude
Copy link
Copy Markdown

claude Bot commented May 22, 2026

Code Review: Cleanup/math dead code

Summary: This PR removes dead code and restructures the math crate's FFT module — flattening fft/cpu/ into fft/, merging fft/polynomial.rs into polynomial.rs, removing the legacy NR Radix-2 FFT implementation, and deleting unused traits (Deserializable, PairingError, DeserializationError). Changes are largely mechanical and improve clarity.


Medium — Reduced cross-validation coverage for a security-critical algorithm

The deleted fft/cpu/fft.rs contained the reference NR Radix-2 Cooley-Tukey FFT, and bowers_fft_tests.rs had three tests that proved Bowers FFT agreed with it:

  • test_bowers_vs_native_various_sizes (deterministic sweep)
  • test_bowers_vs_native_edge_cases (zeros / ones / alternating)
  • proptest_bowers_matches_native_fft (property-based)

These cross-checks were the strongest correctness guarantee for the FFT. Removing both the reference implementation and the cross-check tests simultaneously means Bowers FFT is now only validated against the naive O(n²) DFT — a different algorithm family, not an independent implementation of the same algorithm. For a ZK proving system, FFT correctness is directly tied to proof soundness. Consider keeping at least the proptest cross-check by extracting the reference NR FFT into a test-only helper instead of deleting it entirely.


Low — Panic on zero offset in public API (interpolate_offset_fft)

Ok(scaled.scale(&offset.inv().unwrap()))

Panics when offset == FieldElement::zero(). Pre-existing, but since this function is being touched in the merge, it is worth propagating the error instead. A zero coset offset is a degenerate input that should return Err, not panic.


Low — evaluate_fft_cpu / interpolate_fft_cpu removed without a deprecation note

These were pub module-level functions in fft/polynomial.rs. They disappear silently. Given this is an internal crate this is probably fine, but worth a CHANGELOG or semver note if any downstream crate consumed them directly.


Correctness note — Proof::deserialize removal is the right call

The deleted impl used a hardcoded chunks(8) for a generic T:

for elem in bytes[0..].chunks(8) {
    let node = T::deserialize(elem)?;

This would silently corrupt for any T whose serialized size ≠ 8 bytes. Removing it is correct; if proof deserialization is ever needed the element size must come from the type.

Comment thread crypto/math/src/polynomial.rs Outdated
E: Send + Sync,
{
let scaled = Polynomial::interpolate_fft::<F>(fft_evals)?;
Ok(scaled.scale(&offset.inv().unwrap()))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Low — panic on zero offset (public API)

offset.inv().unwrap() panics when offset == FieldElement::zero(). A zero coset offset is a degenerate input that should return Err. Consider propagating the error:

Suggested change
Ok(scaled.scale(&offset.inv().unwrap()))
Ok(scaled.scale(&offset.inv().map_err(|_| FFTError::InvalidOffset)?))

Or at minimum document the precondition with a debug_assert!(!offset.is_zero()).

@diegokingston diegokingston changed the title Cleanup/math dead code refactor(math): dead-code & structural cleanup of the math crate May 22, 2026
diegokingston and others added 6 commits May 22, 2026 11:19
…set panic

Addresses PR review feedback.

Reviewers flagged that `#[cfg(test)]`-gating previously-public methods is a
breaking change. Rather than keep them as `pub(crate)` dead code, the cluster
is removed outright — none of it has a production caller:

- Delete `Polynomial::{fast_division, invert_polynomial_mod,
  fast_fft_multiplication, truncate, reverse}` and `evaluate_fft_bit_reversed`,
  plus their 8 tests. The fast polynomial-division algorithm had no production
  use; it existed only to be tested.
- `get_powers_of_primitive_root` stays `#[cfg(test)]` — it is a genuine test
  helper used to validate the production FFT, not dead code.

Also fixes the pre-existing panic reviewers noted in `interpolate_offset_fft`:
`offset.inv().unwrap()` panicked on a zero coset offset. Add
`FFTError::InvalidCosetOffset` and propagate it instead.
Continues the dead-code audit of polynomial.rs. Every item removed here
has no production caller — each is a function reachable only from its own
tests, or a test-file algorithm that tests itself.

Removed from polynomial.rs:
- to_extension (zero callers anywhere)
- long_division_with_remainder, leading_coefficient
- ruffini_division_inplace
- interpolate_coset_eval_with_g_n_inv (base-field variant)

Removed the matching self-referential tests/helpers from
polynomial_tests.rs (division_works, division_by_zero_degree_polynomial_works,
test_xgcd, ruffini_inplace_* and the div_with_ref / xgcd / ruffini_division
helpers).

The 3 barycentric tests that exercised the base-field
interpolate_coset_eval_with_g_n_inv now call the production-used
interpolate_coset_eval_ext_with_g_n_inv (instantiated E=F), so they keep
covering the real code path.
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