fix(dwallet-mpc): handle the deployed V1 network-DKG anchor on the v3 reconfiguration path (G1)#1798
Merged
Merged
Conversation
… 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>
ycscaly
approved these changes
Jul 3, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 readingnetwork_dkg_public_outputfrom both fullnodes: first byte0x00). 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 wasunreachable!(). With workspacepanic=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 tobwd_compat_reconfig::PublicInput::new_from_reconfiguration_outputwith the prior V2 output. No new type and no conversion —class_groups::dkg::PublicOutput's bcs layout is unchanged across theinkrypto → cryptography-private @ 32a27aabump.(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.rsinitial-DKG instantiation paths (×3) and the reconfig-output arms — errors.network_key_version(dwallet-mpc-centralized-party/lib.rs) returned viaunreachable!on real chain bytes — now correctly returnsOk(1)(a V1 anchor genuinely is version 1); the twoprotocol_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 asPublicOutputCore,.into()the class-groupsPublicOutput, re-serialize, wrapV1. Against the pre-fix code this panics at theunreachable!, 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 theVersionedNetworkDkgOutput::V1struct 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