Skip to content

feat: stop encrypting DKG shares to self#1529

Open
hmzakhalid wants to merge 2 commits intomainfrom
feat/skip-self-dkg-shares
Open

feat: stop encrypting DKG shares to self#1529
hmzakhalid wants to merge 2 commits intomainfrom
feat/skip-self-dkg-shares

Conversation

@hmzakhalid
Copy link
Copy Markdown
Member

@hmzakhalid hmzakhalid commented May 3, 2026

During threshold key generation, every node was previously encrypting a share of its own secret to itself, sending it over the wire, then decrypting it back, a wasteful round-trip with no security benefit (a node already knows its own share). This PR removes the self-loop end-to-end.

Summary by CodeRabbit

Release Notes

  • Refactor
    • Optimized distributed key generation protocol with enhanced share decryption and verification efficiency.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 3, 2026

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

Project Deployment Actions Updated (UTC)
crisp Ready Ready Preview, Comment May 3, 2026 2:03am
enclave-docs Ready Ready Preview, Comment May 3, 2026 2:03am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

The PR implements a protocol shift in the DKG share decryption flow, transitioning from decrypting H parties' shares to decrypting only (H − 1) external parties' shares, with the node's own share handled separately via plaintext caching. The node now skips self-encryption during BFV share distribution, caches its own plaintext shares, excludes itself from share collection, and passes both its plaintext share data and the external ciphertexts (as optional slots) to proof-generation circuits.

Changes

DKG Share Decryption with Own-Party Separation

Layer / File(s) Summary
Request & Circuit Data Types
crates/events/src/enclave_event/compute_request/zk.rs, crates/zk-helpers/src/circuits/dkg/share_decryption/circuit.rs
DkgShareDecryptionProofRequest adds own_plaintext_idx and own_share_raw fields; ShareDecryptionCircuitData.honest_ciphertexts changes to Vec<Option<Vec<Ciphertext>>> (with None for own slot) and gains own_plaintext_share field.
Share Encryption with Skip Logic
crates/trbfv/src/shares/bfv_encrypted.rs
BfvEncryptedShares.shares becomes Vec<Option<BfvEncryptedShare>>; encrypt_all_extended adds skip_idx: Option<usize> parameter to skip self-encryption, storing None for skipped recipients and empty witness lists.
Share Collection Exclusion
crates/keyshare/src/threshold_share_collector.rs
ThresholdShareCollector::setup now takes own_party_id and excludes it from the todo set during initialization.
Own Share Caching & Self-Skip Integration
crates/keyshare/src/threshold_keyshare.rs
AggregatingDecryptionKey persists own_sk_share_raw and own_esi_shares_raw; during handle_shares_generated, plaintext share material is cached and BFV/ESM encryption skips self via skip_idx; C3a/C3b request generation excludes own recipient index.
Proof Collection & Expected Counts
crates/keyshare/src/threshold_keyshare.rs (continued)
handle_all_threshold_shares_collected derives expected C3 proof counts from cached own shares instead of collected on-wire shares; the honest set is expanded to include self during threshold checks.
Decryption Key Computation with Splicing
crates/keyshare/src/threshold_keyshare.rs (continued)
proceed_with_decryption_key_calculation deserializes cached own plaintext shares, computes own_plaintext_idx for sorted-party insertion, splices them into collected matrices, and builds C4 requests with own_plaintext_idx and own_share_raw.
Circuit Input Deserialization & Layout
crates/multithread/src/multithread.rs
handle_dkg_share_decryption_proof validates own_plaintext_idx, enforces honest_ciphertexts_raw contains (h-1) * l ciphertexts, deserializes external ciphertexts, splices them into an h-length optional vector with None at own_plaintext_idx, deserializes own_share_raw, and builds ShareDecryptionCircuitData with both.
Circuit Computation with Optional Slots
crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs
Inputs::compute conditionally validates own_plaintext_share shape and iterates over slots via match: decrypts Some(party_cts) ciphertexts versus reads coefficients directly from own_plaintext_share for None, maintaining reverse-before-commitment convention.
Sample Generation with Own Slot
crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs
generate_sample builds honest_ciphertexts as Vec<Option<Vec<Ciphertext>>>, designates an own slot, stores plaintext shares there when slot_idx == own_slot_idx, and pushes None for the own slot, Some(...) for external parties.
Proof Publication with Optional Slots
crates/zk-prover/src/actors/proof_request.rs
publish_threshold_share_with_proofs uses match share.extract_for_party(...) to handle None skipped slots with a trace! log instead of error; successful extractions continue to publish ThresholdShareCreated proofs.

Sequence Diagram

sequenceDiagram
    participant KeyMgr as Keyshare Manager
    participant BFV as BFV Encryption
    participant Collector as Share Collector
    participant Threshold as Threshold Keyshare
    participant Circuit as Circuit Computation
    participant Prover as ZK Prover

    KeyMgr->>BFV: Encrypt shares with skip_idx=own_party_id
    BFV->>BFV: For each recipient: if idx==skip_idx store None<br/>else encrypt to Some(ciphertext)
    BFV-->>KeyMgr: BfvEncryptedShares with Some/None slots

    KeyMgr->>Collector: Setup(own_party_id)<br/>Initialize todo excluding own_party_id
    Collector-->>KeyMgr: Collector ready (no self-collection)

    KeyMgr->>Threshold: Cache own_sk_share_raw<br/>and own_esi_shares_raw
    Threshold->>Threshold: Store in pending_own_dkg_shares

    KeyMgr->>Threshold: Transition to AggregatingDecryptionKey<br/>Consume pending_own_dkg_shares
    Threshold->>Threshold: Persist own share material<br/>Compute own_plaintext_idx in sorted order

    Threshold->>Threshold: Deserialize cached own shares<br/>Splice into collected plaintext matrices<br/>at own_plaintext_idx position

    Threshold->>Threshold: Build C4 requests:<br/>own_plaintext_idx + own_share_raw<br/>+ (H-1) external ciphertexts

    Threshold->>Circuit: ShareDecryptionCircuitData<br/>{ honest_ciphertexts: Vec<Option<...>>,<br/>own_plaintext_share, ... }

    Circuit->>Circuit: For each slot in honest_ciphertexts:<br/>if Some(cts) → decrypt<br/>if None → use own_plaintext_share directly

    Circuit-->>Prover: Computed commitments & shares

    Prover->>Prover: match extract_for_party():<br/>Some → publish proof<br/>None → trace skip (no error)
    Prover-->>KeyMgr: Proof published/skipped
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

This PR spans nine files with intricate changes across share encryption, circuit data structures, and proof-generation logic. The protocol shift requires understanding: (1) the mixed optional-slot representation in ciphertexts and how it flows end-to-end, (2) the caching and splicing of own plaintext shares at the correct sorted position, (3) the conditional logic in circuit computation that branches on Some/None slots, and (4) how proof requests and publication adapt to skipped own-party indices. While individual file changes follow a coherent pattern, the density of cross-file dependencies and the need to verify correctness of slot indexing, ordering, and serialization throughout the stack demands careful sequential review.

Possibly related PRs

Suggested reviewers

  • ctrlc03
  • 0xjei
  • cedoor

Poem

🐰 A rabbit hops through slots of shares,
Some encrypted, some plaintext pairs.
Self's no cipher—just stored inline,
(H−1) dance while we redesign!
Proofs now skip what's tucked away,
One sharp protocol finds its way. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: stop encrypting DKG shares to self' directly and precisely summarizes the main change: removing redundant self-encryption of DKG shares during threshold key generation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skip-self-dkg-shares

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@hmzakhalid hmzakhalid requested review from 0xjei, ctrlc03 and zahrajavar May 3, 2026 02:03
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/keyshare/src/threshold_keyshare.rs`:
- Around line 1179-1188: The code currently passes party_id as the slot index to
BfvEncryptedShares::encrypt_all_extended and to the C3 skip logic, which is
wrong when collected_encryption_keys is filtered or reordered; instead compute
the self slot by finding the position of the local party_id inside
collected_encryption_keys and use that index as the skip index. Concretely:
locate where collected_encryption_keys is available, find the index via a
position lookup of entries matching party_id (e.g.,
collected_encryption_keys.iter().position(|(id, _)| id == &party_id)), handle
the none case with an error, and pass that computed usize as the skip_idx to
encrypt_all_extended and any C3 skip checks rather than using party_id directly
(references: collected_encryption_keys, party_id,
BfvEncryptedShares::encrypt_all_extended, C3 skip logic).

In `@crates/multithread/src/multithread.rs`:
- Around line 1194-1210: After deserializing own_share_raw into
own_plaintext_share you must also validate each inner row length matches the BFV
coefficient count (the expected number of coefficients used for plaintext
polynomials) to avoid variable-length shares; iterate over own_plaintext_share
(use enumerate to get the row index), and for each row check row.len() ==
expected_coefficient_count (the BFV/poly coefficient count used elsewhere in the
module), returning Err(make_zk_error(&request, format!("own_plaintext_share[{}]
has {} coefficients, expected {}", i, row.len(), expected_coefficient_count)))
on mismatch instead of proceeding.

In `@crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs`:
- Around line 201-204: The loop currently calls
data.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap() which panics on
decryption failure; change it to propagate the error as a CircuitsErrors so the
method returns a proper ComputeRequestError instead of crashing. Replace the
unwrap usage in the loop that produces decrypted_pt (inside the function that
iterates mod_idx and computes share_coeffs) with error handling that maps the
try_decrypt Err into an appropriate CircuitsErrors variant (including context
like which mod_idx/party_cts failed) and returns it (using ? or map_err(...)?),
ensuring the rest of the function keeps its existing error type flow.

In `@crates/zk-prover/src/actors/proof_request.rs`:
- Around line 1244-1280: The match arm for
share.extract_for_party(positional_idx) currently treats every None as an
expected self-slot and just traces; change it to only skip (trace) when the None
corresponds to the sender’s own slot and treat any other None as an error:
inside the None branch compare the missing slot to the sender identity (use the
module’s sender/self positional identifier available in this actor—e.g., the
field that represents our own positional index or party id used elsewhere with
real_party_id/positional_idx), keep the trace and skip only for the own-slot
case, and for any other positional_idx log an error (include real_party_id and
positional_idx) so missing external recipient shares aren’t silently dropped
instead of being surfaced for upstream handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6201a406-25b2-497b-aeba-c561c80f41f3

📥 Commits

Reviewing files that changed from the base of the PR and between c7e9802 and 4afa3cc.

📒 Files selected for processing (9)
  • crates/events/src/enclave_event/compute_request/zk.rs
  • crates/keyshare/src/threshold_keyshare.rs
  • crates/keyshare/src/threshold_share_collector.rs
  • crates/multithread/src/multithread.rs
  • crates/trbfv/src/shares/bfv_encrypted.rs
  • crates/zk-helpers/src/circuits/dkg/share_decryption/circuit.rs
  • crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs
  • crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs
  • crates/zk-prover/src/actors/proof_request.rs

Comment on lines +1179 to 1188
// BFV-encrypt shares to all recipients except own slot (own share is bound via C2,
// consumed locally by C4). Returns per-row randomness for C3 proofs.
let mut rng = OsRng;
let (encrypted_sk_sss, sk_witnesses) = BfvEncryptedShares::encrypt_all_extended(
&decrypted_sk_sss,
&recipient_pks,
&params,
&mut rng,
Some(party_id as usize),
)?;
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Derive the self slot from collected_encryption_keys, not from party_id.

encrypt_all_extended(..., skip_idx) is slot-based, but this code passes party_id as usize and reuses the same assumption for the C3 skips. That only works if collected_encryption_keys is always dense and perfectly ordered by party ID. Once an earlier party is filtered out, we skip the wrong slot, still emit a self-share, and drop another recipient's share/proofs instead.

🛠 Proposed fix
+        let own_recipient_idx = collected_encryption_keys
+            .iter()
+            .position(|k| k.party_id == party_id)
+            .ok_or_else(|| anyhow!("Own party {} missing from collected_encryption_keys", party_id))?;
+
         // BFV-encrypt shares to all recipients except own slot (own share is bound via C2,
         // consumed locally by C4). Returns per-row randomness for C3 proofs.
         let mut rng = OsRng;
         let (encrypted_sk_sss, sk_witnesses) = BfvEncryptedShares::encrypt_all_extended(
             &decrypted_sk_sss,
             &recipient_pks,
             &params,
             &mut rng,
-            Some(party_id as usize),
+            Some(own_recipient_idx),
         )?;
@@
                 BfvEncryptedShares::encrypt_all_extended(
                     esi,
                     &recipient_pks,
                     &params,
                     &mut rng,
-                    Some(party_id as usize),
+                    Some(own_recipient_idx),
                 )
             })
@@
-        let own_idx = party_id as usize;
+        let own_idx = own_recipient_idx;

Also applies to: 1248-1252, 1280-1283

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/keyshare/src/threshold_keyshare.rs` around lines 1179 - 1188, The code
currently passes party_id as the slot index to
BfvEncryptedShares::encrypt_all_extended and to the C3 skip logic, which is
wrong when collected_encryption_keys is filtered or reordered; instead compute
the self slot by finding the position of the local party_id inside
collected_encryption_keys and use that index as the skip index. Concretely:
locate where collected_encryption_keys is available, find the index via a
position lookup of entries matching party_id (e.g.,
collected_encryption_keys.iter().position(|(id, _)| id == &party_id)), handle
the none case with an error, and pass that computed usize as the skip_idx to
encrypt_all_extended and any C3 skip checks rather than using party_id directly
(references: collected_encryption_keys, party_id,
BfvEncryptedShares::encrypt_all_extended, C3 skip logic).

Comment on lines +1194 to 1210
// Own-plaintext share rows: bincode `Vec<Vec<u64>>` shape [L][N].
let own_share_bytes = req
.own_share_raw
.access_raw(cipher)
.map_err(|e| make_zk_error(&request, format!("own_share decrypt: {}", e)))?;
let own_plaintext_share: Vec<Vec<u64>> = bincode::deserialize(&own_share_bytes)
.map_err(|e| make_zk_error(&request, format!("own_share deserialize: {}", e)))?;
if own_plaintext_share.len() != l {
return Err(make_zk_error(
&request,
format!(
"own_plaintext_share has {} moduli, expected {}",
own_plaintext_share.len(),
l
),
));
}
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate the inner N dimension of own_share_raw too.

This only checks the outer [L] shape. If any decoded row has the wrong coefficient count, the circuit gets variable-length plaintext shares and fails much later with a less actionable proof/witness error. Validate each row against the BFV degree here.

Suggested fix
     let own_plaintext_share: Vec<Vec<u64>> = bincode::deserialize(&own_share_bytes)
         .map_err(|e| make_zk_error(&request, format!("own_share deserialize: {}", e)))?;
     if own_plaintext_share.len() != l {
         return Err(make_zk_error(
             &request,
             format!(
                 "own_plaintext_share has {} moduli, expected {}",
                 own_plaintext_share.len(),
                 l
             ),
         ));
     }
+    let expected_degree = dkg_params.degree();
+    if let Some((row_idx, row)) = own_plaintext_share
+        .iter()
+        .enumerate()
+        .find(|(_, row)| row.len() != expected_degree)
+    {
+        return Err(make_zk_error(
+            &request,
+            format!(
+                "own_plaintext_share[{}] has {} coefficients, expected {}",
+                row_idx,
+                row.len(),
+                expected_degree
+            ),
+        ));
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/multithread/src/multithread.rs` around lines 1194 - 1210, After
deserializing own_share_raw into own_plaintext_share you must also validate each
inner row length matches the BFV coefficient count (the expected number of
coefficients used for plaintext polynomials) to avoid variable-length shares;
iterate over own_plaintext_share (use enumerate to get the row index), and for
each row check row.len() == expected_coefficient_count (the BFV/poly coefficient
count used elsewhere in the module), returning Err(make_zk_error(&request,
format!("own_plaintext_share[{}] has {} coefficients, expected {}", i,
row.len(), expected_coefficient_count))) on mismatch instead of proceeding.

Comment on lines +201 to +204
for mod_idx in 0..threshold_l {
let decrypted_pt =
data.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap();
let share_coeffs = decrypted_pt.value.deref().to_vec();
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate BFV decryption failures instead of panicking.

A bad ciphertext here will panic the prover worker via unwrap(), which skips the normal ComputeRequestError flow and turns invalid input into a task-level crash. This should return a CircuitsErrors like the rest of the method.

Suggested fix
                     for mod_idx in 0..threshold_l {
-                        let decrypted_pt =
-                            data.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap();
+                        let decrypted_pt = data.secret_key
+                            .try_decrypt(&party_cts[mod_idx])
+                            .map_err(|e| {
+                                CircuitsErrors::Other(format!(
+                                    "Failed to decrypt honest_ciphertexts[{}]: {:?}",
+                                    mod_idx, e
+                                ))
+                            })?;
                         let share_coeffs = decrypted_pt.value.deref().to_vec();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for mod_idx in 0..threshold_l {
let decrypted_pt =
data.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap();
let share_coeffs = decrypted_pt.value.deref().to_vec();
for mod_idx in 0..threshold_l {
let decrypted_pt = data.secret_key
.try_decrypt(&party_cts[mod_idx])
.map_err(|e| {
CircuitsErrors::Other(format!(
"Failed to decrypt honest_ciphertexts[{}]: {:?}",
mod_idx, e
))
})?;
let share_coeffs = decrypted_pt.value.deref().to_vec();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs` around
lines 201 - 204, The loop currently calls
data.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap() which panics on
decryption failure; change it to propagate the error as a CircuitsErrors so the
method returns a proper ComputeRequestError instead of crashing. Replace the
unwrap usage in the loop that produces decrypted_pt (inside the function that
iterates mod_idx and computes share_coeffs) with error handling that maps the
try_decrypt Err into an appropriate CircuitsErrors variant (including context
like which mod_idx/party_cts failed) and returns it (using ? or map_err(...)?),
ensuring the rest of the function keeps its existing error type flow.

Comment on lines +1244 to 1280
match share.extract_for_party(positional_idx) {
Some(party_share) => {
let c3a_proofs = signed_c3a_map
.get(&positional_idx)
.cloned()
.unwrap_or_default();
let c3b_proofs = signed_c3b_map
.get(&positional_idx)
.cloned()
.unwrap_or_default();

if let Err(err) = self.bus.publish(
ThresholdShareCreated {
e3_id: e3_id.clone(),
share: Arc::new(party_share),
target_party_id: real_party_id,
external: false,
signed_c2a_proof: Some(signed_c2a.clone()),
signed_c2b_proof: Some(signed_c2b.clone()),
signed_c3a_proofs: c3a_proofs,
signed_c3b_proofs: c3b_proofs,
},
ec.clone(),
) {
error!(
"Failed to publish ThresholdShareCreated for party {} (idx {}): {err}",
real_party_id, positional_idx
);
}
}
None => {
// Own slot is sparse (no self-encryption); nothing to publish.
trace!(
"Skipping ThresholdShareCreated for own slot (party {} idx {})",
real_party_id,
positional_idx
);
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't silently drop unexpected empty recipient slots.

extract_for_party() now returns None for both the intentional self slot and any other empty slot. This branch treats every None as expected, so a missing external recipient share/proof will only emit a trace and never get published, which can turn an upstream bug into a hard-to-debug collection timeout. Gate the skip on the sender’s own slot and keep logging an error for any other None.

Suggested fix
         for (positional_idx, &real_party_id) in pending.recipient_party_ids.iter().enumerate() {
             match share.extract_for_party(positional_idx) {
                 Some(party_share) => {
                     let c3a_proofs = signed_c3a_map
                         .get(&positional_idx)
                         .cloned()
                         .unwrap_or_default();
                     let c3b_proofs = signed_c3b_map
                         .get(&positional_idx)
                         .cloned()
                         .unwrap_or_default();

                     if let Err(err) = self.bus.publish(
                         ThresholdShareCreated {
                             e3_id: e3_id.clone(),
                             share: Arc::new(party_share),
                             target_party_id: real_party_id,
                             external: false,
                             signed_c2a_proof: Some(signed_c2a.clone()),
                             signed_c2b_proof: Some(signed_c2b.clone()),
                             signed_c3a_proofs: c3a_proofs,
                             signed_c3b_proofs: c3b_proofs,
                         },
                         ec.clone(),
                     ) {
                         error!(
                             "Failed to publish ThresholdShareCreated for party {} (idx {}): {err}",
                             real_party_id, positional_idx
                         );
                     }
                 }
-                None => {
-                    // Own slot is sparse (no self-encryption); nothing to publish.
-                    trace!(
-                        "Skipping ThresholdShareCreated for own slot (party {} idx {})",
-                        real_party_id,
-                        positional_idx
-                    );
-                }
+                None if real_party_id == party_id => {
+                    trace!(
+                        "Skipping ThresholdShareCreated for own slot (party {} idx {})",
+                        real_party_id,
+                        positional_idx
+                    );
+                }
+                None => {
+                    error!(
+                        "Missing ThresholdShare for non-self recipient {} (idx {})",
+                        real_party_id,
+                        positional_idx
+                    );
+                }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/zk-prover/src/actors/proof_request.rs` around lines 1244 - 1280, The
match arm for share.extract_for_party(positional_idx) currently treats every
None as an expected self-slot and just traces; change it to only skip (trace)
when the None corresponds to the sender’s own slot and treat any other None as
an error: inside the None branch compare the missing slot to the sender identity
(use the module’s sender/self positional identifier available in this
actor—e.g., the field that represents our own positional index or party id used
elsewhere with real_party_id/positional_idx), keep the trace and skip only for
the own-slot case, and for any other positional_idx log an error (include
real_party_id and positional_idx) so missing external recipient shares aren’t
silently dropped instead of being surfaced for upstream handling.

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