Skip to content

refactor: polynomial conversions [skip-line-limit]#1231

Merged
0xjei merged 20 commits into
mainfrom
refactor/polynomial-conversions
Feb 2, 2026
Merged

refactor: polynomial conversions [skip-line-limit]#1231
0xjei merged 20 commits into
mainfrom
refactor/polynomial-conversions

Conversation

@cedoor

@cedoor cedoor commented Jan 29, 2026

Copy link
Copy Markdown
Contributor

Closes #1203

Summary by CodeRabbit

  • New Features

    • Added a CRT-based polynomial type and new in-place polynomial operations (ordering, reduction, centering, and constructors).
  • Refactor

    • Commitment and packing APIs now accept CRT/polynomial wrappers; ciphertext-addition flow and related inputs/serialization updated.
    • Public commitment method renamed to computeCiphertextCommitment (including JS/WASM SDK updates).
    • Benchmarks adjusted to avoid mutating originals.
  • Chores

    • Dependency and feature declaration tweaks; docs and tests updated to match new APIs.

@vercel

vercel Bot commented Jan 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
crisp Ready Ready Preview, Comment Feb 2, 2026 3:43pm
enclave-docs Ready Ready Preview, Comment Feb 2, 2026 3:43pm

Request Review

@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@cedoor has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 4 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Adds a CRT-based polynomial type (CrtPolynomial) and migrates many APIs from raw Vec<BigInt> to CrtPolynomial/Polynomial; refactors polynomial utilities to in-place reduce/center operations; updates benchmarks, serialization, WASM bindings, SDK naming, and workspace dependency declarations.

Changes

Cohort / File(s) Summary
Core polynomial infra
crates/polynomial/src/crt_polynomial.rs, crates/polynomial/src/lib.rs, crates/polynomial/src/polynomial.rs, crates/polynomial/src/utils.rs, crates/polynomial/Cargo.toml, crates/polynomial/benches/polynomial.rs, crates/polynomial/README.md
Add CrtPolynomial + CrtPolynomialError; expose module and re-exports; add/from_bigint_vectors/from_fhe_polynomial; replace legacy reduce_and_center helpers with reduce/center in-place APIs; add reverse, from_u64_vector; update benches, README, and Cargo features/dev-deps.
ZK helpers (commitments & packing)
crates/zk-helpers/src/commitments.rs, crates/zk-helpers/src/packing.rs
Change public APIs to accept &CrtPolynomial/&Polynomial instead of nested Vec<BigInt>; use .limbs and from_ref for payload flattening; update tests and docs accordingly.
BFV client
crates/bfv-client/Cargo.toml, crates/bfv-client/src/client.rs
Add e3-polynomial workspace dep; convert bigint vectors to CrtPolynomial via from_bigint_vectors before computing commitments.
CRISP inputs & examples
examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs, examples/CRISP/crates/zk-inputs/src/lib.rs, examples/CRISP/crates/zk-inputs/src/serialization.rs, examples/CRISP/crates/zk-inputs/Cargo.toml
Replace many Vec<Vec<BigInt>> fields with CrtPolynomial; change CiphertextAdditionInputs API and internal quotient computation; update input generation and serialization to use limb.coefficients(); remove num-integer dep.
WASM binding & SDK
examples/CRISP/crates/zk-inputs-wasm/Cargo.toml, examples/CRISP/crates/zk-inputs-wasm/src/lib.rs, examples/CRISP/packages/crisp-sdk/src/vote.ts, examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
Add workspace e3-polynomial dep to WASM crate; rename wasm binding computeCommitmentcomputeCiphertextCommitment; WASM now builds CrtPolynomial via from_bigint_vectors; SDK and tests renamed accordingly.
Serialization & tests
examples/CRISP/crates/zk-inputs/src/serialization.rs, tests in examples and zk-helpers
Serialization now iterates limbs and uses limb.coefficients(); tests updated to construct CrtPolynomial/Polynomial wrappers and expect multiple moduli per field.
Workspace manifests & deps
Cargo.toml, crates/polynomial/Cargo.toml, crates/bfv-client/Cargo.toml, examples/CRISP/crates/zk-inputs-wasm/Cargo.toml
Adjust workspace dependency declarations (fhe-math git URL normalized; num-bigint switched to inline table form); add e3-polynomial workspace deps and adjust features/dev-deps.
PVSS & minor scripts
crates/pvss/src/circuits/pk_bfv/computation.rs, circuits/bin/threshold/Nargo.toml, scripts/lint-circuits.sh
Replace reduce_and_center_* usage with center_* where appropriate; update Nargo workspace members; lint script: adjust directories, remove per-package Prover.toml creation and add skips for missing dirs.
Examples WASM rename
examples/CRISP/crates/zk-inputs-wasm/src/lib.rs, examples/CRISP/packages/crisp-sdk/src/vote.ts
Rename exported function to computeCiphertextCommitment and update call sites.

Sequence Diagram(s)

sequenceDiagram
  participant SDK as SDK / Frontend
  participant WASM as zk-inputs-wasm
  participant Generator as ZK Generator
  participant Commit as zk-helpers::commitments
  participant Poly as e3_polynomial::CrtPolynomial

  SDK->>WASM: computeCiphertextCommitment(ct0is, ct1is)
  WASM->>Poly: CrtPolynomial::from_bigint_vectors(ct0is)
  WASM->>Poly: CrtPolynomial::from_bigint_vectors(ct1is)
  WASM->>Generator: generator.compute_commitment(&ct0, &ct1)
  Generator->>Commit: compute_ciphertext_commitment(&ct0, &ct1, bit_ct)
  Commit-->>Generator: BigInt (commitment)
  Generator-->>WASM: commitment
  WASM-->>SDK: return commitment
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

crisp

Suggested reviewers

  • 0xjei
  • ryardley
  • hmzakhalid

Poem

🐰 I hopped through limbs and coefficients bright,

Turned tangled Vecs to CRTs late at night.
Reverse, reduce, center — all neat in a row,
Commitments now tidy, my whiskers aglow.
🥕 Hooray for safer math — a rabbit’s small show!

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR does not implement the explicit type-safe polynomial representation pattern (AscendingPoly, DescendingPoly) from issue #1203. Instead, it introduces CrtPolynomial for CRT-based representations and refactors existing polynomial APIs, but does not address the core requirement of explicit coefficient-order encoding types. Implement the proposed AscendingPoly and DescendingPoly types with explicit From implementations and conversion utilities as specified in issue #1203 to properly prevent coefficient ordering bugs.
Out of Scope Changes check ❓ Inconclusive The PR includes many changes beyond the core polynomial conversion refactoring: dependency updates (fhe-math URL, num-bigint format), polynomial struct API changes (reverse, center, reduce methods), utils refactoring, CRT polynomial introduction, and benchmark modifications. It is unclear which changes are intentional refactoring byproducts versus scope creep. Clarify whether changes to polynomial.rs API (removing ascending/descending helpers, adding in-place methods), utils.rs (replacing reduce_and_center with separate utilities), benchmarks, and CrtPolynomial are all necessary for addressing issue #1203 or if they represent additional scope.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: polynomial conversions' clearly and concisely describes the main change, which is refactoring polynomial conversions across the codebase.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/polynomial-conversions

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.

@vercel vercel Bot temporarily deployed to Preview – enclave-docs January 30, 2026 09:21 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp January 30, 2026 09:21 Inactive
@cedoor cedoor force-pushed the refactor/polynomial-conversions branch from 4a9b9e4 to d7b0a38 Compare January 30, 2026 10:05
@cedoor cedoor force-pushed the refactor/polynomial-conversions branch from 9a4c65f to 5dc1462 Compare January 30, 2026 20:17
@cedoor cedoor changed the title refactor: polynomial conversions refactor: polynomial conversions [skip-line-limit] Jan 30, 2026
@cedoor cedoor marked this pull request as ready for review January 30, 2026 20:17
@cedoor cedoor requested a review from 0xjei January 30, 2026 20:17

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/polynomial/Cargo.toml (1)

12-20: ⚠️ Potential issue | 🟡 Minor

Remove unused ndarray dependency from Cargo.toml.

Verification shows fhe_math and bincode are properly used, but ndarray has no usage in the crate and should be removed to avoid dependency bloat.

🤖 Fix all issues with AI agents
In `@Cargo.toml`:
- Line 157: Remove the unused "rand" feature from the num-bigint dependency
entry in Cargo.toml by deleting the features = ["rand"] clause so the dependency
becomes num-bigint = { version = "=0.4.6" }; verify no code uses num-bigint's
RandBigInt APIs and that existing random logic continues using the rand crate
directly (e.g., rand::thread_rng(), ChaCha20Rng); then run cargo build/cargo
update to ensure the workspace dependency graph updates and no member (like
crates/compute-provider) inherits the unnecessary feature.

In `@examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs`:
- Around line 143-145: The code currently discards the remainder returned from
diff.div(&qi) (bound to _remainder) when computing (q_poly, _remainder); change
this to capture the remainder, verify it is zero (or all-zero polynomial) and
return an error (or panic/eyre) including index i if it is non-zero so the
division is exact; update the call site around diff.div(&qi) and the surrounding
logic that relies on q_poly to handle the validation failure.
🧹 Nitpick comments (4)
crates/polynomial/src/utils.rs (1)

29-60: Consider guarding or documenting modulus != 0 in reduce.
% 0 will panic; since this is now public, an explicit precondition helps.

🔧 Optional guard
 pub fn reduce(x: &BigInt, modulus: &BigInt) -> BigInt {
+    debug_assert!(!modulus.is_zero(), "modulus must be non-zero");
     let mut r = x % modulus;
     if r < BigInt::zero() {
         r += modulus;
     }
     r
 }
crates/polynomial/src/crt_polynomial.rs (2)

64-66: Incomplete documentation section.

The doc comment has an empty "Coefficient order" section header with no content following it. Either add documentation explaining the coefficient order semantics or remove the empty section.

📝 Suggested fix
-    /// # Coefficient order
-    ///
-
+    /// # Coefficient order
+    ///
+    /// The resulting polynomial retains the coefficient order from fhe-math's PowerBasis
+    /// representation (ascending degree: c_0, c_1, …, c_n). Call `reverse()` if descending
+    /// order is needed for Horner evaluation.

177-186: Documentation references non-existent method.

The doc comment references from_limb_coefficients as the inverse operation, but the actual constructor is from_bigint_vectors.

📝 Suggested fix
     /// Returns limb coefficient vectors (one `Vec<BigInt>` per modulus).
     ///
     /// Use when you need a raw CRT representation for serialization, hashing,
-    /// or APIs that expect `&[Vec<BigInt>]`. The inverse of [`from_limb_coefficients`](Self::from_limb_coefficients).
+    /// or APIs that expect `&[Vec<BigInt>]`. The inverse of [`from_bigint_vectors`](Self::from_bigint_vectors).
     pub fn to_limb_coefficients(&self) -> Vec<Vec<BigInt>> {
examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs (1)

119-120: Stale documentation parameter.

The doc comment documents a parameter n (polynomial degree) that doesn't exist in the actual function signature.

📝 Suggested fix
     /// * `sum` - CRT polynomial of the sum ciphertext
     /// * `a` - CRT polynomial of the first ciphertext
     /// * `b` - CRT polynomial of the second ciphertext
-    /// * `n` - polynomial degree (number of coefficients per limb)
     /// * `moduli` - moduli for each CRT limb

Comment thread Cargo.toml Outdated
Comment thread examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs Outdated

@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: 2

🤖 Fix all issues with AI agents
In `@crates/polynomial/Cargo.toml`:
- Around line 10-16: Update the polynomial crate's Cargo.toml to remove unused
dependency bloat: in the num-bigint dependency entry drop the unused "rand" and
"serde" features (leave num-bigint in workspace but only with needed features),
and remove the ndarray dependency entirely; keep fhe-math as-is since
crt_polynomial.rs uses it. Ensure the resulting Cargo.toml no longer lists
ndarray and that num-bigint's features only include those actually used by the
polynomial crate.

In `@crates/polynomial/src/crt_polynomial.rs`:
- Around line 55-77: Update the from_fhe_polynomial docstring to specify that
coefficients().outer_iter() yields coefficients in ascending degree order (c_0,
c_1, ...), that if p is in NTT it is converted to PowerBasis (as already done
via p.change_representation(Representation::PowerBasis)), and callers who expect
descending-degree ordering must apply .reverse() to the resulting limbs (see the
ciphertext_addition.rs example). Also mention that the returned Self { limbs }
preserves this ascending ordering so downstream code (e.g.,
Polynomial::from_u64_vector) should treat limb 0 as the constant term.
🧹 Nitpick comments (1)
crates/polynomial/src/crt_polynomial.rs (1)

155-170: Consider adding a safe accessor to avoid panics in public API.

Optional enhancement: add get_limb returning Option<&Polynomial> for callers that prefer bounds-checked access.

💡 Possible addition
 impl CrtPolynomial {
+    pub fn get_limb(&self, i: usize) -> Option<&Polynomial> {
+        self.limbs.get(i)
+    }
 }

Comment thread crates/polynomial/Cargo.toml
Comment thread crates/polynomial/src/crt_polynomial.rs
cedoor and others added 6 commits February 2, 2026 16:02
- Reword comment for first-in-slot votes and prev_ct_commitment
- Add IMPORTANT prefix so the requirement is not missed

Co-authored-by: Cursor <cursoragent@cursor.com>
- Capture remainder from diff.div(&qi) instead of discarding it
- Verify remainder is zero (all-zero polynomial) after division
- Return eyre error including modulus index i when remainder is non-zero

Co-authored-by: Cursor <cursoragent@cursor.com>
…ding_coefficients

- Drop from_ascending_coefficients and to_ascending_coefficients; use new() + reverse() instead
- Point new() doc to reverse() for ordering
- Remove tests and benchmark that relied on removed APIs
- Update README to mention reverse() for ordering

Co-authored-by: Cursor <cursoragent@cursor.com>

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/polynomial/src/polynomial.rs (1)

371-400: ⚠️ Potential issue | 🟡 Minor

Docs claim in-place mutation but methods return new polynomials.
scalar_mul and reduce_by_cyclotomic do not mutate self; the comments are misleading.

📝 Suggested doc fix
-    /// Mutates the polynomial in place.
+    /// Returns a new polynomial with each coefficient scaled.
@@
-    /// Mutates the polynomial in place.
+    /// Returns the reduced polynomial (does not mutate `self`).
🤖 Fix all issues with AI agents
In `@examples/CRISP/crates/zk-inputs/src/lib.rs`:
- Around line 127-143: CiphertextAdditionInputs::compute is being called with
bit_pk (public-key bounds) but it expects bit_ct (ciphertext bounds) used for
packing; this leads to wrong commitment calculations via
compute_ciphertext_commitment. Fix by computing the correct ciphertext bit width
(bit_ct: u32) from the ciphertext bounds and pass that bit_ct into
CiphertextAdditionInputs::compute (replace bit_pk with bit_ct) at all call sites
where CiphertextAdditionInputs::compute is invoked, and ensure any subsequent
calls to compute_ciphertext_commitment also use the same bit_ct value.
🧹 Nitpick comments (5)
crates/polynomial/src/utils.rs (2)

54-67: reduce_and_center duplicates centering logic from center.

The centering logic (lines 58-64) is identical to the center function (lines 28-34). Consider refactoring to reuse center:

♻️ Proposed refactor to eliminate duplication
 pub fn reduce_and_center(x: &BigInt, modulus: &BigInt, half_modulus: &BigInt) -> BigInt {
-    let mut r = reduce(x, modulus);
-
-    // Adjust the remainder if it is greater than half_modulus.
-    if (modulus % BigInt::from(2)) == BigInt::from(1) {
-        if r > *half_modulus {
-            r -= modulus;
-        }
-    } else if r >= *half_modulus {
-        r -= modulus;
-    }
-
-    r
+    let r = reduce(x, modulus);
+    center(&r, modulus)
 }

Note: The half_modulus parameter becomes unused and could be removed in a follow-up if callers are updated.


118-130: Unnecessary clone in reduce_in_ring.

Line 123 clones coefficients only to immediately wrap it in a Polynomial. Since reduce_by_cyclotomic returns a new Polynomial, the clone could be avoided by constructing directly:

♻️ Proposed refactor to avoid clone
 pub fn reduce_in_ring(
     coefficients: &mut Vec<BigInt>,
     cyclo: &[BigInt],
     modulus: &BigInt,
 ) -> Result<(), PolynomialError> {
-    let coeffs = coefficients.clone();
-    let poly = Polynomial::new(coeffs);
+    let poly = Polynomial::new(std::mem::take(coefficients));
     let reduced = poly.reduce_by_cyclotomic(cyclo)?;
-    *coefficients = reduced.coefficients;
+    *coefficients = reduced.into_coefficients(); // or similar accessor
     reduce_coefficients_mut(coefficients, modulus);
     center_coefficients_mut(coefficients, modulus);
     Ok(())
 }

This requires an into_coefficients() method on Polynomial. Alternatively, keep the current approach if the clone overhead is negligible for typical polynomial sizes.

crates/polynomial/src/crt_polynomial.rs (2)

146-150: Consider removing unnecessary borrow in reduce_uniform.

Line 148 uses &modulus but modulus is already a reference. The extra & is harmless but unnecessary.

♻️ Proposed fix
     pub fn reduce_uniform(&mut self, modulus: &BigInt) {
         for limb in &mut self.limbs {
-            limb.reduce(&modulus);
+            limb.reduce(modulus);
         }
     }

166-168: limb() panics on out-of-bounds; consider returning Option or documenting panics prominently.

The method panics if i >= self.limbs.len(). While this is documented, a safer alternative would be to return Option<&Polynomial> or provide a separate get() method. However, if panic semantics are intentional for internal use, the current documentation is acceptable.

examples/CRISP/crates/zk-inputs-wasm/src/lib.rs (1)

184-188: Validate limb/degree shape before building CrtPolynomial.
This avoids silently producing commitments from malformed JS input.

🧩 Suggested input validation
+        let params = self.generator.get_bfv_params();
+        let expected_limbs = params.moduli().len();
+        let expected_degree = params.degree();
+
+        if ct0is_vec.len() != expected_limbs || ct1is_vec.len() != expected_limbs {
+            return Err(JsValue::from_str("ct0is/ct1is limb count mismatch"));
+        }
+        if ct0is_vec.iter().any(|limb| limb.len() != expected_degree)
+            || ct1is_vec.iter().any(|limb| limb.len() != expected_degree)
+        {
+            return Err(JsValue::from_str(
+                "ct0is/ct1is coefficient length mismatch",
+            ));
+        }

Comment thread examples/CRISP/crates/zk-inputs/src/lib.rs
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.

make a type-safe polynomial representations to prevent coefficient ordering bugs

2 participants