# \[H16\] DKG Round-1 rejects all honest packages unless exactly threshold(N)+1 registered Round-0 — deterministic bridge halt for N≥7

**URL:** https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123
**Category:** Resolved
**Tags:** exodus-pallet
**Created:** [October 3, 2026, 12:26am UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123 "2026-10-03T00:26:55Z")
**Posts on this page:** 11
**Page:** 1

<div class="post-metadata">

### Author: ![Chaplin](https://ghostforum.org/letter_avatar_proxy/v4/letter/c/74df32/32.png) [@Chaplin](https://ghostforum.org/u/Chaplin)
#### Post date: [October 3, 2026, 12:26am UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/1 "2026-10-03T00:26:55Z")

</div>

## Summary

Two gates in the `pallets/exodus` DKG Round-1 flow disagree on how many VSS commitments a  
valid Round-1 package must carry, and honest output satisfies both **only on a knife’s edge** :

- `validate_round1_package_size` (`pallets/exodus/src/lib.rs:2728-2740`, tx-pool gate) pins  
the package byte-length to `round1_package_size(byzantium_threshold(N))` — exactly  
`threshold(N)` coefficients, where `N` = total authorities.
- `register_round1_package` (`lib.rs:992-999`, on-chain execution) demands  
`coefficients.len() == R - 1`, where `R` = authorities that registered Round-0.
- The offchain worker (`dkg_run_round0` → `dkg_part1`, `lib.rs:3746-3786`) always builds  
packages with `min_signers = get_byzantium_quorum_threshold(N)` coefficients.

So a ceremony survives Round-1 **iff exactly `threshold(N)+1` authorities registered Round-0**.  
Consequences: with **N ≥ 7 and full participation (the normal case), every honest Round-1  
package is rejected** — the DKG can never complete and the bridge threshold key can never be  
established/rotated. With N = 4–6 the DKG works only with 100% Round-0 participation (zero  
fault tolerance — one offline/censored authority kills the ceremony despite the BFT quorum  
being met). With N = 3 it can never succeed at all. Failed ceremonies hit `restart_dkg()` and  
livelock, and **all threshold signing is rejected while any DKG is pending**  
(`lib.rs:3084, 3136, 3184, 3221`) — so a stuck (rotation) DKG permanently halts bridge-outs,  
rotations and governance signing, not just the rotation. The pallet’s test suite uses exactly  
4 authorities with full participation (`mock.rs:44`), the one configuration where the two  
gates agree, which is why this survived.

## Severity

**High — CVSS 3.1: 7.5** — `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`

No attacker needed for the N ≥ 7 case (deterministic on the happy path); for N ≤ 6 a single  
authority kept out of the Round-0 window (offline, censored, or DoS’d) forces a full restart.  
Availability impact is total: the bridge cannot start (initial DKG) or permanently halts on  
the first authority-set rotation, because signing stays blocked across the livelock.

## Affected code

```rust
// lib.rs:2728-2740 — pool gate: package length pinned to threshold(N) coefficients
let authorities = QualificationAuthorities::<T>::get(&network_curve);
let expected_count = get_byzantium_quorum_threshold(authorities.len());
let expected_package_len = round1_package_size(
    expected_count,
    network_curve.element_bytes_len(),
    network_curve.signature_bytes_len(),
    network_curve.header_bytes_len(),
);
if round1.package.len() != expected_package_len {
    return Err(InvalidTransaction::BadProof);
}

```

```rust
// lib.rs:992-999 — execution gate: demands R-1 coefficients
state.count_ones::<AuthIndex>().checked_sub(1)
    .map(|needed_commitment_count| {
        estimated_commitment_count
            .eq(&needed_commitment_count)
            .then(|| ())
    })
    .ok_or(Error::<T>::Round1PackageInvalidLength)?;

```

```rust
// lib.rs:3746-3750 — OCW always builds threshold(N)-coefficient packages
let max_signers = QualificationAuthorities::<T>::decode_len(&network_curve)...;
let min_signers = get_byzantium_quorum_threshold(max_signers);
// dkg_part1(authority_index, max_signers, min_signers, &mut rng)
// → frost dkg::part1 creates exactly min_signers coefficients

```

```rust
// lib.rs:3084 (representative) — signing halted while DKG pending
if QualificationDkgState::<T>::get(&network_curve).is_dkg_pending() {
    return Err(InvalidTransaction::BadProof);
}
// same guard in validate_exodus_nonce_commitment (3136),
// validate_exodus_group_commitment (3184), and the OCW driver (3221)

```

where `get_byzantium_quorum_threshold(n) = n - (n-1)/3` (`pallets/helpers/src/lib.rs:37-46`).

## Steps to reproduce

Code-path analysis only (no toolchain needed — the condition is deterministic arithmetic):

1. `git clone https://git.ghostchain.io/ghostchain/ghost-node && git rev-parse HEAD` →  
`0162da4604d9931c16d12a0cec40dd85951368c5`
2. Take N = 7: `threshold(7) = 7 - 6/3 = 5`. Every honest Round-1 package carries 5  
coefficients (and the pool gate’s byte-length check enforces exactly that).
3. All 7 authorities register Round-0 → phase advances (7 ≥ 5).
4. Execution gate requires `5 == 7 - 1 = 6` → false → `Err(Round1PackageInvalidLength)` for  
**every** package, honest or otherwise.
5. `prepare_round2_storage` intersects qualification with the empty Round-1 set →  
`restart_dkg()` → new `dkg_index`, back to Vacant. The next ceremony fails identically.

Participation table (`R` = Round-0 registrants; need `R - 1 == threshold(N)`, `threshold(N) ≤ R ≤ N`):

| N | threshold(N) | R required | outcome |
| --- | --- | --- | --- |
| 3 | 3 | 4 | impossible — DKG never succeeds |
| 4–6 | N-1 | N | only with 100% Round-0 turnout |
| 7 | 5 | 6 | only with _exactly_ 6/7; full turnout always fails |
| 8 | 6 | 7 | only with _exactly_ 7/8; full turnout always fails |
| 10 | 7 | 8 | only with _exactly_ 8/10; full turnout always fails |

Variant: N = 4, one authority offline during the Round-0 window → R = 3 (meets the 3/4 BFT  
quorum, phase advances) → execution requires `3 == 2` → false → total collapse and restart,  
even though the BFT threshold was satisfied.

## Impact

- **Initial DKG:** with N ≥ 7 the bridge threshold key can never be created (chain starts,  
bridge never activates); with N ≤ 6 any single Round-0 absence kills the ceremony.
- **Rotation DKG (validator-set change):** the failed ceremony livelocks with  
`is_dkg_pending() == true`, and every signing gate rejects while pending — so a routine  
rotation becomes a **permanent, total bridge halt** (bridge-outs, rotations, governance).
- **Griefing:** for N ≤ 6, censoring/DoS-ing one authority’s Round-0 transaction during the  
round window is enough to force a restart; each failed ceremony burns a full DKG  
round-period with signing halted throughout.
- Fail-closed (no fund theft), but complete and potentially permanent loss of bridge  
availability — the exact property the BFT threshold was designed to protect.

## Remediation

Pick one threshold and enforce it in a single place:

- If the intent is the BFT threshold over the full set: change the execution check to  
`estimated == get_byzantium_quorum_threshold(QualificationAuthorities::decode_len())`,  
matching the pool gate and the OCW.
- If the intent is adaptive `R - 1`: make the OCW derive `min_signers` from the observed  
Round-0 count and relax the pool byte-length check accordingly (it currently pins  
`threshold(N)`).
- Add DKG tests for N ∈ {3, 7, 10} and for partial Round-0 participation — the current suite  
only covers N = 4 full-participation, exactly the blind spot.
- Consider bounding total DKG-pending time (fallback) so a failed ceremony cannot halt  
threshold signing indefinitely.

---

<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 3, 2026, 7:05pm UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/2 "2026-10-03T19:05:50Z")

</div>

Thank you for your report! This is actually my favorite one so far, because it works on multiple layers.

At first glance, it makes perfect sense - two different checks, two different length requirements. But after spending a couple of hours investigating why nothing fails when I bump the participant count up to 20 during local tests, the realization was shocking.

It’s a bug inside a bug. If you look carefully at the check, the error path is only triggered when `checked_sub` itself fails - that is, only when `count_ones == 0`. The actual coefficient-count comparison is never enforced, because the expression has the wrong shape:

```auto
state.count_ones::<AuthIndex>().checked_sub(1)
    .map(|needed_commitment_count| {
        estimated_commitment_count
            .eq(&needed_commitment_count)
            .then(|| ())
    })
    .ok_or(Error::<T>::Round1PackageInvalidLength)?;

```

[Code could be found here](https://git.ghostchain.io/ghostchain/ghost-node/src/commit/0162da4604d9931c16d12a0cec40dd85951368c5/pallets/exodus/src/lib.rs#L991)

`.map(...)` returns `Option<Option<()>>`, and `.ok_or(...)?` only inspects the **outer** `Option`. The inner `Option<()>` produced by `.then(|| ())` - the result of the coefficient-count comparison - is silently discarded. So the gate is effectively dead code. It never rejects a package for having the wrong coefficient count. It only fires when `count_ones == 0`.

On the one hand, this is definitely a real defect - the intended invariant (`estimated_commitment_count == count_ones - 1`) is not enforced. On the other hand, because of the way it’s written, the bug has no runtime impact today: honest and dishonest packages alike pass through. And that is the reason why it is not affecting the tests.

Any thoughts on this? Maybe I’m wrong here?

---

<div class="post-metadata">

### Author: ![Chaplin](https://ghostforum.org/letter_avatar_proxy/v4/letter/c/74df32/32.png) [@Chaplin](https://ghostforum.org/u/Chaplin)
#### Post date: [October 3, 2026, 7:53pm UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/3 "2026-10-03T19:53:15Z")

</div>

* * *

You’re not wrong — you’re right, and I verified it independently. Nice catch.

I compiled the exact expression shape in isolation:

```rust
count_ones.checked_sub(1)
    .map(|needed| estimated.eq(&needed).then(|| ()))
    .ok_or(Error::Round1PackageInvalidLength)?;

```

`checked_sub` yields `Option<u32>`, `.map()` lifts it to `Option<Option<()>>`, and `.ok_or()?` only inspects the **outer** `Option` — the `?` unwraps to an `Option<()>` that is discarded as a statement. The error fires if and only if `count_ones == 0`; the coefficient-count comparison is silently dropped in every other case. My test confirms: mismatch → `Ok(())`, match → `Ok(())`, zero registrants → `Err`.

So the headline claim in my report is wrong: honest Round-1 packages are never rejected by this gate, and there is no deterministic DKG halt. I retract the High severity on H16.

* * *

But re-auditing from the corrected understanding turned up something real — a different bug behind the same dead check. The gate’s _intent_ was to enforce a coefficient count, and because it enforces nothing, this passes:

**Trailing-byte smuggling → persistent DKG liveness failure (Medium)**

1. FROST’s `Package::deserialize` calls `postcard::from_bytes` directly (`frost-core-2.2.0/src/serialization.rs:244`), which does **not** require the input to be fully consumed. I verified empirically: 8 clean bytes vs 108 padded bytes both deserialize to the same 3 coefficients.
2. A malicious registered validator M runs `dkg_part1` locally with `min_signers = K < threshold(N)` — producing a _valid_ package with a _valid_ Schnorr proof of knowledge over K commitments — then appends trailing padding so the total length equals `round1_package_size(threshold(N))`.
3. Gate-by-gate: pool byte-length (lib.rs:2734) passes by construction; dispatchable byte-length (lib.rs:968) passes; M’s own signature over their own bytes passes; round-0 hash binding passes (M registered `blake2_256(padded_bytes)` themselves); `dkg_verify_proof_of_knowledge` passes (deserializes fine, PoK valid over K coefficients); the dead check passes trivially.
4. In `dkg_run_round2`, every honest validator calls `dkg_part2` with `min_signers = threshold(N)` in their secret. FROST’s `part2` enforces `package.commitment.0.len() == secret.min_signers` (`frost-core-2.2.0/src/keys/dkg.rs:509`) → `Err(IncorrectNumberOfCommitments)` on **every** honest validator. The `?` at lib.rs:3981 aborts the OCW round — nobody submits round-2. `Round2Packages` stays empty → `restart_dkg()`, and the next ceremony is attacked identically: **one unsigned transaction per ceremony for a persistent halt**.
5. While any DKG is pending, `is_dkg_pending()` gates reject all threshold signing (lib.rs:3084, 3136, 3184, 3221) — bridge-outs, rotations, and governance signing all stop. No round-1 complaint path exists (`register_round3_complaints` is round-3-only), and FROST fails closed (error, not a corrupt key), so this is liveness-only.

Why worse than simple non-participation: an offline validator is excluded and the DKG proceeds without them. Here M **stays qualified** while poisoning the computation for everyone — converting a tolerated fault into a systemic liveness failure.

**Severity: Medium.** Requires a malicious/compromised registered validator (insider, not permissionless); liveness-only; the anomaly is visible on-chain via the event’s `coefficients_count`; governance removal of the validator is a (slow) recovery path.

**Fix (do NOT just “activate” the dead check — its `R - 1` invariant is wrong for honest packages and would introduce the halt I originally, incorrectly, claimed):** reject trailing bytes at parse time (e.g. enforce no remainder after `postcard` deserialization, or assert `package.len() == round1_package_size(parsed_count)` after `dkg_verify_proof_of_knowledge`); and/or make `dkg_run_round2` skip rather than abort on `IncorrectNumberOfCommitments`, so one bad package degrades to exclusion instead of global failure.

Happy to be corrected further if I’m still missing something.

* * *

## _Verification: standalone Rust tests of the combinator shape and of postcard trailing-byte behavior; source review of lib.rs @ 0162da4 (round-1 gates, dkg\_run\_round2, restart path) and frost-core-2.2.0 (serialization.rs, keys/dkg.rs)._

---

<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 5, 2026, 9:18am UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/4 "2026-10-05T09:18:55Z")

</div>

I believe this dead check could be removed. First of all, because Round #0 was added later (the rounds weren’t created in sequence - a fun fact), and during `register_round1_package` this legacy check remained. Round #1 was originally the first one, and during it the active participants were activated. After Round #0 was introduced, a bit of a mess came into play.

In any case, sizes are already checked in `validate_round1_package_size`, so it should be fine to run the length check only once and then run the Proof of Knowledge on the package.

Could you describe your latest attack vector in more detail? I understand that a malicious validator might be able to abuse postcard, but how could they bypass the length check, the hash equality check, and the proof of knowledge all at the same time?

---

<div class="post-metadata">

### Author: ![Chaplin](https://ghostforum.org/letter_avatar_proxy/v4/letter/c/74df32/32.png) [@Chaplin](https://ghostforum.org/u/Chaplin)
#### Post date: [October 5, 2026, 11:25am UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/5 "2026-10-05T11:25:37Z")

</div>

Thanks for the question. I have a concrete reproducer for the trailing-byte case. The key point is that the attacker does not need to bypass the three checks independently. The checks operate on different representations of the same submitted package.

For example, with `N = 7`:

- The normal Round-0 threshold is `5`.
- An honest Round-1 package serializes to `236` bytes and contains `5` coefficients.
- A malicious package can contain only `2` coefficients and serialize to `137` bytes.
- The attacker then appends arbitrary trailing bytes until the submitted package is exactly `236` bytes.

The checks behave as follows:

1. **Length check**

`validate_round1_package_size` checks the length of the submitted byte slice. Since the padded package is `236` bytes, it matches the expected size and passes the check.

1. **Hash equality check**

The attacker registers `blake2_256(padded_bytes)` during Round 0. In Round 1, `lib.rs:980` hashes the raw submitted bytes again. Since the submitted bytes are the same padded byte sequence, the registered hash and the Round-1 hash match.

1. **Proof of Knowledge**

`frost-core` deserializes the padded byte slice into a `round1::Package`. In the reproducer, the decoded package still contains only `2` coefficients because the trailing bytes are ignored by `postcard::from_bytes`. The PoK then verifies against that decoded commitment. This behavior is documented for Postcard 1.1.3. ([from\_bytes in postcard - Rust](https://docs.rs/postcard/latest/postcard/fn.from_bytes.html))

1. **Round-2 failure**

When the decoded package reaches FROST `dkg::part2`, it reports `min_signers = 2`, while the local secret package expects `5`. `frost-core` compares those values and returns `IncorrectNumberOfCommitments`. ([dkg.rs - source](https://docs.rs/frost-core/latest/src/frost_core/keys/dkg.rs.html))

The reproducer reaches this exact sequence:

`236B padded → length check PASS → deserialize to 2 coefficients → PoK PASS → hash equality PASS → part2 → IncorrectNumberOfCommitments`.

So the issue is not that the attacker breaks the length check, Blake2, and the PoK at the same time. The issue is that the checks authenticate different representations:

- the length and hash checks use the raw byte representation;
- the later validation derives the commitment count from the deserialized representation.

As a counterfactual, if the hash were computed over the canonical serialization after deserialization, the padded 236-byte representation would not have the same hash as the canonical 137-byte package. The reproducer demonstrates this distinction as well.

That is the trailing-byte attack vector I was referring to.

---

<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 5, 2026, 1:07pm UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/6 "2026-10-05T13:07:39Z")

</div>

Brilliant finding!

I checked on my own and you are absolutely correct - `postcard v1.1.3` is there as a silent dependency of `frost-core`. And you’re 100% right that we don’t need to drop the result of `dkg_verify_proof_of_knowledge`; we need to check it once more against the BFT threshold over all possible validators - the same set that was used to generate the package.

But since `postcard` will behave the same way for all the other DKG rounds and the FROST signing process, are they vulnerable to the same type of attack? For example, an attacker could bypass the length check for Round #2 - but that doesn’t make sense for them, because the `justification` process would then be triggered against them. Still, maybe you see the same vector for other rounds or for the signing process?

---

<div class="post-metadata">

### Author: ![Chaplin](https://ghostforum.org/letter_avatar_proxy/v4/letter/c/74df32/32.png) [@Chaplin](https://ghostforum.org/u/Chaplin)
#### Post date: [October 5, 2026, 2:04pm UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/7 "2026-10-05T14:04:36Z")

</div>

Thanks. I dug deeper into the other DKG rounds and the signing path.

I agree with your intuition for the standard Round-2 case. If the package successfully deserializes but is otherwise invalid, the complaint/justification path can identify the sender and the attacker can be removed. I also don’t think the Round-1 trailing-byte construction generalizes cleanly to the other rounds.

I did find a different Round-2 vector that avoids the justification path entirely.

The difference is a SCALE decode failure at the individual recipient-slot level.

A malicious registered validator can construct its Round-2 bundle with a valid outer structure, including the recipient bitmap and the expected total blob length, while placing malformed SCALE bytes in the recipient slots. The on-chain validation checks the outer blob shape and the authority signature, but does not decode each `EncryptionData` slot at that point.

Later, `gather_incoming_offchain_packages` decodes the individual slots using `filter_map(... decode(...).ok())`. If decoding a slot fails, that entry is silently dropped instead of returning an error. Since the complaint/decryption path only iterates over the successfully decoded map, the attacker is no longer present in that validator’s input and no complaint is generated against them.

I updated the reproducer so that all recipient slots are malformed, rather than only one victim slot. It now shows the same result for every honest validator: the attacker’s entry is absent from the gathered map, so no complaint can be filed.

The next issue is the Round-5 invariant. The Round-1 map still contains the attacker, while the decrypted Round-2 map does not. `frost-core 2.2.0` then returns `IncorrectNumberOfPackages` from `dkg::part3` when the two maps have different cardinalities. The reproducer gets exactly that result for all three honest validators, while the complete Round-2 map passes as a positive control.

So these are two different cases:

`decryptable-but-invalid package`  
→ complaint  
→ justification  
→ attacker can be removed

versus:

`SCALE-invalid slot`  
→ silently dropped during gathering  
→ attacker never reaches the complaint path  
→ no justification  
→ attacker remains in the Round-1 state  
→ Round 5 returns `IncorrectNumberOfPackages`

That is why I don’t think the justification safety net applies to this construction.

One caveat: I have runtime-proven the decode failure, the silent drop, the attacker’s absence from the complaint input, and the `part3 -> IncorrectNumberOfPackages` chain. I have not yet executed the actual pallet extrinsic in a mock runtime or run the complete off-chain-worker flow through `restart_dkg()`. Those parts are source-verified only, so I don’t want to overstate the current evidence.

My current conclusion is that the original trailing-byte issue is Round-1-specific, but Round 2 has a separate malformed-slot liveness vector that bypasses the justification mechanism for a different reason.

---

<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 5, 2026, 2:56pm UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/8 "2026-10-05T14:56:17Z")

</div>

One more brilliant catch here!

I looked over the code just now. The function that actually collects the complaints is `ecdh_decrypt_round2_packages`, but it blindly iterates over whatever is inside `round2_packages` - the output of `gather_incoming_offchain_packages` - while silently omitting errors during the loop.

From what I understand, it was done that way for another part of the code, [which is here](https://git.ghostchain.io/ghostchain/ghost-node/src/commit/0162da4604d9931c16d12a0cec40dd85951368c5/pallets/exodus/src/lib.rs#L4510).

In my opinion, the initial idea for a fix would be to swap what it iterates over during `ecdh_decrypt_round2_packages` - in other words, iterate over `round1_packages` and then extract the `round2_package` by index. But that’s just off the top of my head.

Please continue your research! This is definitely one of the best threads we have right now. Moreover, for me personally this is an elevation in any case, because of the initial statement that a **malformed validator** is trying to break the protocol.

---

<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:45pm UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/9 "2026-10-10T15:45:54Z")

</div>

Hey @Chaplin - it looks like the dev team has [fixed the all highlighted issues](https://git.ghostchain.io/ghostchain/ghost-node/commit/653bf0848609c62f0a02ecf5903b212d99bb7375) you reported. Could you confirm whether it’s resolved on your end now, or continue the thread if it still persists?

---

<div class="post-metadata">

### Author: ![Chaplin](https://ghostforum.org/letter_avatar_proxy/v4/letter/c/74df32/32.png) [@Chaplin](https://ghostforum.org/u/Chaplin)
#### Post date: [October 10, 2026, 11:59pm UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/10 "2026-10-10T23:59:04Z")

</div>

Hey VoidDragon — confirmed resolved on my end. I pulled commit `653bf0848` and verified both vectors:

1. **Trailing-byte (Round-1):** the dead `Option<Option<()>>` check is now `ensure!(estimated == byzantium_threshold(full authority set), Round1PackageInvalidLength)`. Importantly it’s _not_ the `R-1` invariant we discussed avoiding — padded packages now fail at registration, and honest packages (which always carry exactly `threshold(N)` coefficients) can never be rejected by it.
2. **Malformed slots (Round-2):** `gather_incoming_offchain_packages` is gone; `ecdh_decrypt_round2_packages` now iterates `round1_packages` and marks decode failures in `accused_indexes`, so a malformed sender lands on the complaint path instead of being silently dropped.

I also compiled `ghost-exodus` at that commit and ran the pallet suite: the new `test_dkg_robustness_against_malformed_scale_slots` passes and the full suite is 28/28 green — no regression on the honest paths.

Thanks for the quick turnaround on the fix. Happy to re-run my standalone reproducers against this commit too if you’d like an extra confirmation.

One more thing while I have you: could you explain how the payout side of this program works? Is there a fixed scale per severity (e.g. what a Medium pays), who makes the severity/payout call, and roughly what the timeline looks like from fix-confirmation to payout?

---

<div class="post-metadata">

### Author: ![Colossus](https://ghostforum.org/user_avatar/ghostforum.org/colossus/32/10_2.png) [@Colossus](https://ghostforum.org/u/Colossus)
#### Post date: [October 11, 2026, 8:25pm UTC](https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123/11 "2026-10-11T20:25:06Z")

</div>

Hey @Chaplin - glad to hear the fix is confirmed on your end, and thanks for the detailed re-verification against `653bf0848`. That extra standalone reproducer run would definitely be appreciated if you have time.

On the payout side:

- **Severity/payout recommendation** is made by the team after the fix is implemented - they assess impact and recommend a severity level.
- **Final compensation is decided by a community vote** - specifically by **GHOST Whales** , who govern the project and vote on GHOST Whales Improvement Proposals (GWIPs).
- There isn’t a publicly fixed scale (e.g. “Medium = $X”) that I can point you to; the recommendation + vote is the mechanism.
- **Timeline:** from fix-confirmation → team severity recommendation → GHOST Whales vote → payout. Exact duration can vary, but the gating step is the community vote.

If you want to participate in that governance process yourself, the entry path is here: [How to Become a GHOST Whale](https://blog.ghostchain.io/become-ghost-whale/)
