# \[pallets/exodus\] Round-5 to 6 bitmap intersection desyncs the proof gate for dense validator sets; DKG restart loop on ordinary dropouts

**URL:** https://ghostforum.org/t/pallets-exodus-round-5-to-6-bitmap-intersection-desyncs-the-proof-gate-for-dense-validator-sets-dkg-restart-loop-on-ordinary-dropouts/168
**Category:** Resolved
**Tags:** exodus-pallet
**Created:** [October 7, 2026, 3:13am UTC](https://ghostforum.org/t/pallets-exodus-round-5-to-6-bitmap-intersection-desyncs-the-proof-gate-for-dense-validator-sets-dkg-restart-loop-on-ordinary-dropouts/168 "2026-10-07T03:13:50Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![Rusdiwireng](https://ghostforum.org/letter_avatar_proxy/v4/letter/r/9de0a6/32.png) [@Rusdiwireng](https://ghostforum.org/u/Rusdiwireng)
#### Post date: [October 7, 2026, 3:13am UTC](https://ghostforum.org/t/pallets-exodus-round-5-to-6-bitmap-intersection-desyncs-the-proof-gate-for-dense-validator-sets-dkg-restart-loop-on-ordinary-dropouts/168/1 "2026-10-07T03:13:51Z")

</div>

**Component:** `ghost-node` @ `0162da46`, `pallets/exodus/src/lib.rs` (round-5 to 6 transition, around line 2526; proof gate at lines 3039-3045).

**Description:** After the Merkle trees are built, the round-5 to 6 transition intersects the qualification bitmap with the largest agreeing consensus subset. The proof gate is then sized by `count_ones()` on that intersected bitmap (`estimated_proof_count = state.count_ones::<usize>().next_power_of_two().trailing_zeros()`, compared against `dkg_package.bytes_len()`). Any dropout that crosses a power-of-two boundary, for example 9 authorities dropping to 8 when one misses round 5, desyncs the gate from the already-built tree. The ceremony bricks into a restart loop. Crucially, this needs no sparsity: it fires on fully dense validator sets with an ordinary single dropout.

**Distinction from Sands #93:** that report covered only sparse production indexes (for example {0,150,300}). This variant reproduces on dense sets, which is the normal operating case, and was never covered. Not a re-report.

**Impact:** Deterministic DKG liveness break and restart loop on ordinary validator sets whenever one dropout crosses a power-of-two boundary. No fund impact; the threshold-signing pipeline cannot complete.

**Reproduction:** Executed against the actual repo primitives (temporary cargo test, passed, then reverted to keep the tree clean).

**Severity: High.** Reason: deterministic liveness kill of the threshold-signing pipeline, no attacker needed, triggered by routine operational conditions.

**Recommendation:** Size the gate by `highest_bit()` (the tree’s actual height input) instead of `count_ones()`; one line fixes both this variant and #93.

**Verification:** VERIFIED (executed PoC against repo primitives).

---

<div class="post-metadata">

### Author: ![VoidDragon](https://ghostforum.org/user_avatar/ghostforum.org/voiddragon/32/16_2.png) [@VoidDragon](https://ghostforum.org/u/VoidDragon)
#### Post date: [October 7, 2026, 10:54am UTC](https://ghostforum.org/t/pallets-exodus-round-5-to-6-bitmap-intersection-desyncs-the-proof-gate-for-dense-validator-sets-dkg-restart-loop-on-ordinary-dropouts/168/2 "2026-10-07T10:54:52Z")

</div>

Thank you for your report!

Actually, I can see how it can happen:

1. Round 5 is generated based on `N` participants.
2. For some reason, one of them voted for a malicious hash - let’s say `0x000..000`.
3. The participants bitmap used by the intersect function doesn’t contain all participants.
4. During Round 6, honest participants will try to push a proof for `N` participants, while there are only `N-1`.

Am I reading your report correctly? Just double-checking that I haven’t missed something.

In my opinion, using `highest_bit` could lead to more mistakes down the road. For example, if `T-1` excluded participants are at the head of the bitmap, they’ll still be included in the computation, which isn’t accurate. Available options:

1. Omit this check completely.
2. Verify it against the latest `VerifyingKeyConsensus` participants (which should be the valid set).

Both options have pros and cons. For example, the second one will reject all participants who voted for another hash, which actually looks like it makes sense. Any ideas on this?

---

<div class="post-metadata">

### Author: ![Rusdiwireng](https://ghostforum.org/letter_avatar_proxy/v4/letter/r/9de0a6/32.png) [@Rusdiwireng](https://ghostforum.org/u/Rusdiwireng)
#### Post date: [October 8, 2026, 5:46am UTC](https://ghostforum.org/t/pallets-exodus-round-5-to-6-bitmap-intersection-desyncs-the-proof-gate-for-dense-validator-sets-dkg-restart-loop-on-ordinary-dropouts/168/3 "2026-10-08T05:46:53Z")

</div>

Yes, your reading is correct, with one addition: no malicious hash is needed. A plain dropout works too. If one authority is simply offline for round 5 (missed its submission, no malice), the intersected bitmap still shrinks from N to N-1, and if that crosses a power-of-two boundary the honest proofs built for N are rejected. Your malicious-vote scenario is a special case of the same desync; the mildest trigger is one node being down.

On highest\_bit: agreed, your objection stands. Exclusions at the head of the bitmap would inflate the size estimate, so it is not a correct fix. Withdrawing that recommendation.

Of your two options, I prefer the second, understood as cryptographic verification of the submitted proof against the Merkle root in the latest VerifyingKeyConsensus, replacing the length pre-check rather than supplementing it. That eliminates the whole desync class (both the sparse-index variant and this dense one), because a proof either verifies against the committed root or it does not; no size estimate can drift.

Omitting the check entirely (option 1) also fixes liveness, but it widens the DoS surface: the length gate currently runs before signature verification in validate\_unsigned, so removing it lets anyone submit arbitrary-length proofs that burn verification resources before failing. A middle path keeps a cheap constant sanity bound (proof length within the maximum tree depth) as spam protection while the consensus-root verification does the real gating. That keeps the DoS cost roughly where it is today and removes the drift.

One note on your option 2 as stated: rejecting participants who voted for another hash is indeed the sensible behavior here, since the intersected set is by construction the set that agreed. That matches what the gate was trying to approximate.

---

<div class="post-metadata">

### Author: ![VoidDragon](https://ghostforum.org/user_avatar/ghostforum.org/voiddragon/32/16_2.png) [@VoidDragon](https://ghostforum.org/u/VoidDragon)
#### Post date: [October 10, 2026, 3:42pm UTC](https://ghostforum.org/t/pallets-exodus-round-5-to-6-bitmap-intersection-desyncs-the-proof-gate-for-dense-validator-sets-dkg-restart-loop-on-ordinary-dropouts/168/4 "2026-10-10T15:42:35Z")

</div>

Hey @Rusdiwireng - it looks like the dev team has [fixed the issue here](https://git.ghostchain.io/ghostchain/ghost-node/commit/d488bf9a434e9a070a2dc576163c39ca174c3f54) you reported and [then fixed it even more better](https://git.ghostchain.io/ghostchain/ghost-node/commit/1dc61fb17d97e2afcd445fda95b84fff1797b9fd). Could you confirm whether it’s resolved on your end now, or continue the thread if it still persists?

At a high level, all honest participants should now reach the same set of members - namely, those they consider to have passed all prior rounds successfully. That set is then part of the hash used as the key for voting.

---

<div class="post-metadata">

### Author: ![Rusdiwireng](https://ghostforum.org/letter_avatar_proxy/v4/letter/r/9de0a6/32.png) [@Rusdiwireng](https://ghostforum.org/u/Rusdiwireng)
#### Post date: [October 11, 2026, 8:15am UTC](https://ghostforum.org/t/pallets-exodus-round-5-to-6-bitmap-intersection-desyncs-the-proof-gate-for-dense-validator-sets-dkg-restart-loop-on-ordinary-dropouts/168/5 "2026-10-11T08:15:44Z")

</div>

Read d488bf9 and 1dc61fb. Round5 packages now carry the key\_members bitmask, it feeds into the voting hash, and Round5 validation rejects non-qualified members, so the proof-size gate operates on the agreed member set instead of per-node intersects. That closes the desync root cause, including the plain-dropout case I added. This matches the direction I supported in my last reply. Confirmed resolved on my end.
