Skip to content

refactor: add share_decryption circuit [skip-line-limit]#1280

Merged
cedoor merged 14 commits into
mainfrom
refactor/share-decryption
Feb 10, 2026
Merged

refactor: add share_decryption circuit [skip-line-limit]#1280
cedoor merged 14 commits into
mainfrom
refactor/share-decryption

Conversation

@cedoor

@cedoor cedoor commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added threshold share decryption circuit with code generation, witness computation, and sample data generation support.
    • Expanded circuit registry with DKG and threshold circuit variants.
  • Improvements

    • Refined circuit naming conventions for clarity (e.g., dkg-share-encryption).
    • Optimized bit-width configurations for improved performance across security and insecure modes.
    • Enhanced committee handling with better error propagation in sample generation.

@cedoor cedoor requested a review from 0xjei February 9, 2026 17:02
@vercel

vercel Bot commented Feb 9, 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 10, 2026 10:40am
enclave-docs Ready Ready Preview, Comment Feb 10, 2026 10:40am

Request Review

@coderabbitai

coderabbitai Bot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR renames ciphertext and quotient polynomial parameters across threshold cryptography components (c_0→ct0, c_1→ct1, r_1→r1, r_2→r2), reduces bit-width configuration values by 1, introduces a new threshold share decryption circuit implementation with computation and codegen, updates DKG circuits to use the new committee structure, and refactors circuit aliases in the CLI.

Changes

Cohort / File(s) Summary
Parameter Renaming – Noir & Core
circuits/bin/threshold/share_decryption/src/main.nr, circuits/lib/src/core/threshold/share_decryption.nr
Renames ciphertext parameters c_0/c_1→ct0/ct1 and quotient polynomials r_1/r_2→r1/r2 across function signatures, struct definitions, and all internal usages.
Configuration Updates – Bit-widths
circuits/lib/src/configs/insecure/threshold.nr, circuits/lib/src/configs/secure/threshold.nr
Reduces SHARE_DECRYPTION bit-width constants by 1 (e.g., BIT_CT: 36→35, BIT_R1: 44→43) and simplifies section headers.
CLI & Circuit Registry
crates/zk-helpers/src/bin/zk_cli.rs
Introduces circuit aliases (DkgShareDecryptionCircuit, ThresholdShareDecryptionCircuit), updates registry dispatch logic, refactors sample generation to use committee collection, and adds threshold circuit instantiation.
New Threshold Share Decryption Circuit
crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs, ...codegen.rs, ...computation.rs, ...sample.rs, ...mod.rs, crates/zk-helpers/src/circuits/threshold/mod.rs
Implements complete threshold share decryption circuit module with public struct definitions, code generation (TOML/configs), multi-phase witness computation including bounds and bit-width derivation, and sample data generation.
DKG Share Computation & Encryption Updates
crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs, ...codegen.rs, ...computation.rs, crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs, ...circuit.rs, ...codegen.rs, crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs, ...codegen.rs
Updates sample generation to use CiphernodesCommittee instead of CiphernodesCommitteeSize, changes error handling to return Result with CircuitsErrors, and updates circuit naming (share-encryption → dkg-share-encryption).

Sequence Diagram(s)

sequenceDiagram
    participant CLI
    participant SampleGen as Sample Generation
    participant Computation
    participant Codegen
    
    CLI->>SampleGen: generate_sample(preset, committee)
    activate SampleGen
    SampleGen->>SampleGen: Create threshold params
    SampleGen->>SampleGen: Generate keys & encrypt
    SampleGen->>SampleGen: Simulate secret/error shares
    SampleGen->>SampleGen: Compute decryption share
    SampleGen-->>CLI: ShareDecryptionCircuitInput
    deactivate SampleGen
    
    CLI->>Computation: compute(preset, input)
    activate Computation
    Computation->>Computation: Compute bounds (r1, r2)
    Computation->>Computation: Compute bit-widths
    Computation->>Computation: Build witness polynomials
    Computation->>Computation: Per-modulus lifting & centering
    Computation->>Computation: Aggregate results
    Computation-->>CLI: ShareDecryptionComputationOutput
    deactivate Computation
    
    CLI->>Codegen: codegen(preset, input)
    activate Codegen
    Codegen->>Computation: Reuse computation phase
    Codegen->>Codegen: Generate TOML artifact
    Codegen->>Codegen: Generate configs.nr
    Codegen-->>CLI: Artifacts
    deactivate Codegen
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #1217: Introduces ParameterType::THRESHOLD and CiphernodesCommittee/CiphernodesCommitteeSize structure that this PR directly depends on for circuit parameterization.
  • PR #1231: Refactors polynomial types to CrtPolynomial and related conversion APIs that are used throughout the new threshold share decryption computation module.
  • PR #1261: Adds complementary threshold share_decryption circuit wiring and config constants that align with the renamed ct0/ct1 and r1/r2 identifiers in this PR.

Suggested reviewers

  • 0xjei

🐰 Hop, hop! The cipher's dressed in new attire,
ct0 and ct1, a naming to admire!
Thresholds now computed with polynomial care,
Decryption shares dancing through the codebase air!
🔐✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding a share_decryption circuit. This aligns with the substantial additions across multiple files, particularly the new threshold share decryption circuit implementation in crates/zk-helpers/src/circuits/threshold/share_decryption/.

✏️ 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/share-decryption

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: 5

🤖 Fix all issues with AI agents
In `@crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs`:
- Line 7: The module-level doc comment in codegen.rs is incorrect (copy-pasted):
it reads "Code generation for the public-key BFV circuit"; update that top doc
comment to "Code generation for the share decryption circuit" so the module
description matches the actual purpose of the file (file:
crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs, symbol:
module/codegen.rs doc comment).

In `@crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs`:
- Around line 224-227: The inline comments for the cyclotomic polynomial
creation are reversed: cyclo[0] is the constant (x^0) term and cyclo[n as usize]
is the x^N term. Update the comments on the lines that set cyclo[0] and cyclo[n
as usize] in the cyclo vector (in the block creating cyclotomic polynomial using
BigInt) so they correctly state "constant (x^0) term" for cyclo[0] and "x^N
term" for cyclo[n as usize].
- Around line 7-10: The crate-level and struct-level doc comments incorrectly
reference the "public key generation circuit" and `PkGenerationCircuit`; update
all such occurrences to say "share decryption circuit" and use the correct type
name `ShareDecryptionCircuit`, including the top module docstring and the docs
for `Configs`, `Bounds`, `Bits`, and `Witness` so their descriptions and links
point to `ShareDecryptionCircuit` instead of `PkGenerationCircuit`.

In `@crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs`:
- Around line 7-11: The module doc comment is copied from the
user-data-encryption circuit and incorrectly references "User data encryption
circuit", UserDataEncryptionCircuit, and UserDataEncryptionCircuitInput; update
the top-level module documentation to correctly describe this module as the
share decryption circuit and replace those references with
ShareDecryptionCircuit and ShareDecryptionCircuitInput (and any other mismatched
names) so the docs accurately describe the circuit provided in this file.

In `@crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs`:
- Around line 7-10: The module doc is incorrect/copy-pasted from
user-data-encryption; update the top-level doc comment to describe "share
decryption sample generation" and reference the actual generator method
ShareDecryptionCircuitInput::generate_sample (instead of the nonexistent Sample
type) and mention that it produces example inputs (e.g., random key share(s) and
ciphertexts) used for codegen and tests; ensure the wording and examples align
with the types in this module (ShareDecryptionCircuitInput) and remove any
mention of BFV key pair or "user data encryption circuit".
🧹 Nitpick comments (1)
circuits/lib/src/core/threshold/share_decryption.nr (1)

114-131: Stale doc references: c_0/c_1/r_1/r_2 in payload() docstring.

Lines 123–126 still reference the old field names (c_0, c_1, r_1, r_2) when describing the serialization order. These should be updated to ct0, ct1, r1, r2 to match the renamed struct fields.

The mathematical formula comments (e.g., line 34, line 243) are arguably fine since they describe the cryptographic equation rather than code identifiers.

Comment thread crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs Outdated
Comment thread crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs Outdated
Comment thread crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs Outdated
Comment thread crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs Outdated
Comment thread crates/zk-helpers/src/circuits/threshold/share_decryption/sample.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: 1

Caution

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

⚠️ Outside diff range comments (1)
circuits/lib/src/core/threshold/share_decryption.nr (1)

34-34: ⚠️ Potential issue | 🟡 Minor

Doc comments still reference the old c_0/c_1 and r_1/r_2 names.

Several documentation blocks were not updated to match the renamed fields:

  • Line 34: formula d = c_0 + c_1 * s + e + r_2 * (X^N + 1) + r_1 * q_i
  • Lines 123–126: payload serialization order references c_0, c_1, r_1, r_2
  • Line 187: r_1 and r_2
  • Line 223: c_0/c_1, r_1/r_2
  • Lines 243–251: c_0i, c_1i, r_1_i, r_2_i

These should be updated to ct0/ct1 and r1/r2 to stay consistent with the code.

Also applies to: 123-126, 187-187, 223-223, 240-251

🤖 Fix all issues with AI agents
In `@crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs`:
- Around line 176-185: The r1_bounds computation in the r1_bounds.push call does
not match its comment: the numerator currently subtracts qi_bound (… -
&qi_bound.clone()) but the comment (r_1j upper bound) expects an addition of Be;
verify the intended formula and either change the expression in the
r1_bounds.push to use addition (e.g., (&qi_bound * (&qi_bound * &n +
BigInt::from(3)) + &Be_or_qi_bound) / &qi_bigint) or update the comment to
reflect the subtraction version; ensure you reference the actual variables used
here (r1_bounds, qi_bound, qi_bigint, n, Be/Be_var) so the code and comment are
consistent.
🧹 Nitpick comments (1)
crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs (1)

188-197: to_u128().unwrap() will panic if any bound exceeds 128 bits.

For the current parameter sets this is likely safe, but it's a latent panic. Consider using try_into with a descriptive error via the CircuitsErrors path, or at minimum document the assumption.

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

Caution

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

⚠️ Outside diff range comments (4)
crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs (1)

134-160: ⚠️ Potential issue | 🟡 Minor

Pre-existing bug: test_generate_smudging_noise_sample uses DkgInputType::SecretKey instead of DkgInputType::SmudgingNoise.

Line 141 passes DkgInputType::SecretKey, so this test doesn't actually exercise the smudging noise path despite its name. This predates this PR but is worth fixing while you're touching this file.

Proposed fix
         let sample = ShareEncryptionCircuitInput::generate_sample(
             BfvPreset::InsecureThreshold512,
             committee.clone(),
-            DkgInputType::SecretKey,
+            DkgInputType::SmudgingNoise,
             sd.z,
             sd.lambda,
         );
circuits/lib/src/core/threshold/share_decryption.nr (1)

114-155: ⚠️ Potential issue | 🟡 Minor

Doc comments still reference old field names c_0/c_1 and r_1/r_2.

Lines 123–126, 187, and 223 still use the old naming (c_0, c_1, r_1, r_2) when describing the serialization order and range-check targets. These should be updated to ct0/ct1 and r1/r2 to match the renamed fields. The mathematical formula references (e.g., line 34, 243) are fine as-is since they follow cryptographic notation conventions.

crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs (1)

174-174: ⚠️ Potential issue | 🟠 Major

unwrap() on try_decrypt in non-test code will panic on decryption failure.

Witness::compute returns Result<Self, CircuitsErrors>, so the decryption error should be propagated instead of panicking.

Proposed fix
-                let decrypted_pt = input.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap();
+                let decrypted_pt = input.secret_key.try_decrypt(&party_cts[mod_idx]).map_err(|e| {
+                    CircuitsErrors::Other(format!(
+                        "Failed to decrypt ciphertext at mod_idx {}: {:?}",
+                        mod_idx, e
+                    ))
+                })?;
crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs (1)

78-111: ⚠️ Potential issue | 🟡 Minor

Duplicate .map_err() chains produce nested error messages.

Both the generate_smudging_error call (lines 80–91) and the generate_secret_shares_from_poly call (lines 100–111) chain two .map_err() transformations. The first .map_err() already converts the original library error into CircuitsErrors::Sample. The second .map_err() then wraps that CircuitsErrors::Sample into yet another CircuitsErrors::Sample, producing a nested message like "Failed to generate smudging error: Sample(\"Failed to generate smudging error: ...\")".

Remove the duplicate .map_err() on each call, keeping only one.

Proposed fix
                         let esi_coeffs = trbfv
                             .generate_smudging_error(sd.z as usize, sd.lambda as usize, &mut rng)
                             .map_err(|e| {
                                 CircuitsErrors::Sample(format!(
                                     "Failed to generate smudging error: {:?}",
                                     e
                                 ))
-                            })
-                            .map_err(|e| {
-                                CircuitsErrors::Sample(format!(
-                                    "Failed to generate smudging error: {:?}",
-                                    e
-                                ))
                             })?;
                         let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).map_err(|e| {
                             CircuitsErrors::Sample(format!(
                                 "Failed to convert error to poly: {:?}",
                                 e
                             ))
                         })?;
                         let esi_sss_u64 = share_manager
                             .generate_secret_shares_from_poly(esi_poly.clone(), &mut rng.clone())
                             .map_err(|e| {
                                 CircuitsErrors::Sample(format!(
                                     "Failed to generate error shares: {:?}",
                                     e
                                 ))
-                            })
-                            .map_err(|e| {
-                                CircuitsErrors::Sample(format!(
-                                    "Failed to generate error shares: {:?}",
-                                    e
-                                ))
                             })?;
🤖 Fix all issues with AI agents
In `@crates/zk-helpers/src/bin/zk_cli.rs`:
- Line 258: The local variable committee is a non-Copy CiphernodesCommittee
value created from CiphernodesCommitteeSize::Small.values() and is currently
moved into whichever single match arm executes; to make this robust for future
changes, avoid relying on the single-arm move by either cloning committee each
time you need to use it (call .clone() where committee is moved into a match
arm) or change the match to borrow committee (e.g., match on a reference) so you
reuse the same instance without moving; locate the committee binding and any
match that consumes it and update uses to committee.clone() or to borrow
(&committee) as appropriate to ensure multiple arms can safely access it.

In `@crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs`:
- Around line 310-335: The code computes pk_bit via
compute_pk_bit(&threshold_params) but then independently recomputes bit width
elsewhere; instead make the bits computed by Bits::compute authoritative: change
Witness::compute to accept the Bits instance (or add an overload) and use
bits.sk_bit when calling compute_aggregated_shares_commitment for
expected_sk_commitment and expected_e_sm_commitment (replace compute_pk_bit
usage), and add a unit test that constructs Bits::compute(...), calls
compute_pk_bit(...) and asserts bits.sk_bit == compute_pk_bit(...) to lock in
the equivalence if you still want the separate function.

In `@crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs`:
- Line 43: Replace the hardcoded num_ciphertexts = 10 with the preset-derived z
value so smudging uses the configured parameter: locate the variable in
sample.rs (where num_ciphertexts is set) and use the preset/search_defaults
value (e.g., sd.z or preset.search_defaults().z) instead, and update the call
site that passes num_ciphertexts so it now receives that z value.
🧹 Nitpick comments (7)
crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs (1)

249-249: Weak assertion after prefix rename — substring match gives a false sense of coverage.

"SHARE_ENCRYPTION_CONFIGS" is a substring of "DKG_SHARE_ENCRYPTION_CONFIGS", so this passes by coincidence. If the prefix were ever changed to something that doesn't contain the old substring, this test would silently break. Consider asserting the full prefixed name.

Proposed fix
-        assert!(artifacts.configs.contains("SHARE_ENCRYPTION_CONFIGS"));
+        assert!(artifacts.configs.contains("DKG_SHARE_ENCRYPTION_CONFIGS"));
crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs (2)

92-92: Redundant .take(num_parties).

party_secret_keys already has exactly num_parties elements (populated by the loop at lines 59–66). The .take() is a no-op.

Proposed fix
-        for party_sk in party_secret_keys.iter().take(num_parties) {
+        for party_sk in &party_secret_keys {

307-318: test_generate_template_repeatable doesn't verify repeatability — only structural equality.

The test name suggests deterministic output, but it only checks that the two independently-generated samples have the same number of limbs/components. Since both use OsRng/thread_rng, they produce different values each time. Consider renaming to test_generate_template_structural_consistency to avoid confusion.

crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs (2)

54-106: 22 positional format! args are fragile and hard to audit.

The template and argument list are correct today, but any insertion or reorder risks silent misalignment. Consider using named format arguments for clarity.

Example (partial)
format!(
    r#"...
pub global N: u32 = {n};
pub global L: u32 = {l};
pub global QIS: [Field; L] = [{qis}];

pub global {prefix}_BIT_CT: u32 = {bit_ct};
..."#,
    n = configs.n,
    l = configs.l,
    qis = qis_str,
    prefix = prefix,
    bit_ct = configs.bits.ct_bit,
    // ...
)

39-45: generate_toml takes Witness by value, while the DKG equivalent takes &Witness.

Minor inconsistency with the DKG codegen at crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs:42 which takes &Witness. Since Witness is only used to call to_json(), a reference would suffice and align the APIs.

crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs (2)

130-151: All non-r bit widths are set to r2_bit — intentional?

ct_bit, sk_bit, e_sm_bit, and d_bit are all assigned r2_bit. This uses r2's bound as a conservative ceiling. If these polynomials can have tighter bounds in practice (e.g., ct_bit via compute_pk_bit), circuit constraint counts could be reduced. If this is intentional as a simplification, a brief comment would help future readers.


101-123: Redundant build_pair_for_preset + Bounds::compute + Bits::compute calls when Configs is used alongside CircuitComputation::compute.

Configs::compute independently calls build_pair_for_preset, Bounds::compute, and Bits::compute. If Configs is ever used in the same flow as ShareDecryptionCircuit::compute (which also calls Bounds + Bits), these expensive operations run twice. Not a correctness issue, but worth noting for when the full pipeline is wired up — you may want Configs to accept pre-computed Bounds/Bits as input instead.

Comment thread crates/zk-helpers/src/bin/zk_cli.rs
Comment thread crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs Outdated
cedoor and others added 14 commits February 10, 2026 11:38
- cyclo[0] is constant (x^0) term, not x^N
- cyclo[n] is x^N term, not x^0

Co-authored-by: Cursor <cursoragent@cursor.com>
- Update comment to reflect subtraction formula (qi_bound * (qi_bound * n + 3) - qi_bound) / qi_bigint
- Document qi_bound, qi_bigint, n variables
- Remove outdated BS/Be reference that expected addition

Co-authored-by: Cursor <cursoragent@cursor.com>
- Replace hardcoded num_ciphertexts=10 with preset.search_defaults().z
- Add search_defaults() resolution with error handling for presets without defaults
- Smudging error generation now uses configured parameter per preset

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

@0xjei 0xjei 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.

utACK

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.

2 participants