Skip to content

fix(dwallet-mpc): handle the deployed V1 network-DKG anchor on the v3 reconfiguration path (G1)#1798

Merged
omersadika merged 1 commit into
devfrom
fix/v1-anchor-reconfiguration
Jul 4, 2026
Merged

fix(dwallet-mpc): handle the deployed V1 network-DKG anchor on the v3 reconfiguration path (G1)#1798
omersadika merged 1 commit into
devfrom
fix/v1-anchor-reconfiguration

Conversation

@omersadika

Copy link
Copy Markdown
Contributor

Release blocker G1 (finding G1 in the review): without this, a 1.2.0 binary wedges both mainnet and testnet — every validator panic-aborts at the first per-epoch reconfiguration, restarts, replays, and aborts again. Unrecoverable without an emergency binary.

The bug

The deployed mainnet/testnet network keys were DKG'd by a pre-1.1.8 binary, which wrote a V1-tagged anchor on chain: the raw class_groups::dkg::PublicOutput. Reconfiguration reshares the key every epoch but writes to a separate field — the anchor is never rewritten, so it is still V1 today (verified by reading network_dkg_public_output from both fullnodes: first byte 0x00). Their reconfiguration outputs are V2.

reconfiguration_bwd_compat_public_input (the protocol-v3 input builder) matches the anchor's tag first, before ever using the V2 reconfiguration output — and dev's V1 arm was unreachable!(). With workspace panic=abort, the deployed (V1 anchor, V2 reconfiguration output) shape kills every validator at the first reconfiguration. 1.1.8 has a working V1 arm here; it was deleted on the false premise that the deployed anchor is V2. Every existing test is green because a fresh DKG writes a V2/V3 anchor — the V1 shape only exists on the two real chains.

The fix

Reinstate the functional V1 arm at reconfiguration_bwd_compat_public_input, mirroring 1.1.8: for (V1 anchor, Some(V2 reconfiguration output)), decode the V1 inner bytes directly as the class-groups DKG output (no wrapper to project through, unlike the V2 arm's .into()) and feed it to bwd_compat_reconfig::PublicInput::new_from_reconfiguration_output with the prior V2 output. No new type and no conversion — class_groups::dkg::PublicOutput's bcs layout is unchanged across the inkrypto → cryptography-private @ 32a27aa bump. (V1, None) and (V1, V3) remain errors (as in 1.1.8).

The other V1(_) => unreachable!() sites are converted to proper errors rather than left as process-aborts (they are unreachable for deployed keys, but a panic on a decoded chain value is the wrong failure mode):

  • network_dkg.rs initial-DKG instantiation paths (×3) and the reconfig-output arms — errors.
  • The WASM SDK export network_key_version (dwallet-mpc-centralized-party/lib.rs) returned via unreachable! on real chain bytes — now correctly returns Ok(1) (a V1 anchor genuinely is version 1); the two protocol_public_parameters* WASM helpers return errors instead of aborting.

v4 scope: the main (v4) reconfiguration builder's V1 arm becomes a proper error, not full support — reconstructing the v4 input (and the V2→V3 anchor migration) from a V1 anchor is a tracked follow-up that must land before v4 activates on the deployed networks. v4 is not active anywhere, so this arm is unreachable in production today; the change only converts a latent panic into a clear error. The v3 path — the actual mainnet wedge — is fully fixed.

Test

test_v1_anchor_bwd_compat_reconfiguration (network_dkg_bwd_compat.rs) drives the exact deployed shape — a V1 anchor + V2 reconfiguration output at protocol v3 — through the reconfiguration builder and asserts it succeeds. The V1 anchor is built by the faithful projection the deployed bytes actually have: decode a v2 DKG output's inner as PublicOutputCore, .into() the class-groups PublicOutput, re-serialize, wrap V1. Against the pre-fix code this panics at the unreachable!, so the test genuinely discriminates the fix.

Docs

Corrected the false "deployed anchor is V2" premise in dev-docs/plans/network-dkg-anchor-v2-to-v3-migration.md, dev-docs/specs/handoff.md, and the VersionedNetworkDkgOutput::V1 struct doc — the deployed shape is now stated as "V1 anchor + V2 reconfiguration outputs at protocol v3", with the v4 one-time anchor migration restated from a V1 origin.

Last of the three release gates (with G2+G3 in #1797). Requires the full cluster suite + a v118 upgrade run green before merge.

🤖 Generated with Claude Code

… reconfiguration path (G1)

Mainnet/testnet network keys carry a V1-tagged DKG anchor on chain (raw
class_groups::dkg::PublicOutput, written by a pre-1.1.8 binary and never
rewritten — reconfiguration writes a separate field). Their
reconfiguration outputs are V2. reconfiguration_bwd_compat_public_input
matches the anchor tag first and dev had V1 => unreachable!(), so with
panic=abort every 1.2.0 validator would abort at the first reconfiguration
and crash-loop on replay: both networks wedged. (Deleted on the false
premise the deployed anchor is V2 — every test is green because a fresh
DKG writes V2/V3.)

Reinstate the functional V1 arm mirroring 1.1.8: for (V1 anchor, Some(V2
reconfig output)) decode the V1 inner bytes directly as the class-groups
DKG output (no wrapper projection, unlike the V2 arm's .into()) and feed
it to bwd_compat_reconfig::new_from_reconfiguration_output. No new type —
class_groups::dkg::PublicOutput's bcs layout is stable across the crypto
bump. (V1, None)/(V1, V3) stay errors, as in 1.1.8.

Convert the other V1 => unreachable!() sites (network_dkg instantiation
paths, reconfig-output arms, and the WASM network_key_version /
protocol_public_parameters exports) to proper errors instead of
process-aborts on decoded chain values; network_key_version now returns
Ok(1) (a V1 anchor genuinely is version 1). The two remaining
unreachable!() are provably-safe inner arms guarded by outer (V2 | V3)
bindings.

v4 scope: the main (v4) builder's V1 arm becomes a clear error, not full
support — v4 V1-anchor reconstruction + the V2->V3 migration for these
keys is a tracked follow-up that must land before v4 activates. v4 is not
active anywhere, so this arm is unreachable in production today.

Test: test_v1_anchor_bwd_compat_reconfiguration drives the deployed shape
(V1 anchor + V2 reconfig output at protocol v3) end to end and asserts
!rejected; fault-injected (site-D fix reverted -> panics at the
unreachable) then restored. Docs corrected: the false 'deployed anchor is
V2' premise in the migration plan, handoff.md, and the VersionedNetworkDkgOutput::V1
struct doc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@omersadika omersadika merged commit 988476f into dev Jul 4, 2026
10 checks passed
@omersadika omersadika deleted the fix/v1-anchor-reconfiguration branch July 4, 2026 18:30
omersadika added a commit that referenced this pull request Jul 9, 2026
… reconfiguration path (G1) (#1798)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants