Skip to content

fix(service): deal from the finalize-time sealed share to prevent polynomial divergence#89

Merged
0xHansLee merged 2 commits into
mainfrom
dkg/fix-sealed-share-dealing
Jul 23, 2026
Merged

fix(service): deal from the finalize-time sealed share to prevent polynomial divergence#89
0xHansLee merged 2 commits into
mainfrom
dkg/fix-sealed-share-dealing

Conversation

@0xHansLee

Copy link
Copy Markdown
Collaborator

Problem

kyber's resharing DistKeyShare() interpolates the first oldThreshold members of the QUAL at call time — any subset recovers the same secret (GPK) but a different polynomial. The share was computed at two different instants: at finalize (QUAL still draining) the coeffs went on-chain and were sealed; at the next round's deal generation buildResharingPrevDKG recomputed DistKeyShare() on the live instance whose QUAL had since filled up, landing on a different polynomial. Every verifier then correctly complained → the round froze permanently.

Fix

  • Deal from the share sealed at finalization rather than recomputing. loadFromRoundShare resolves cache → sealed store → (fallback only) live recomputation with a Warn, and is used by both buildResharingPrevDKG and rebuildResharingPrevDKG. On-chain coeffs, the sealed share, dealing, and partial decryption now all originate from the one finalize-time computation.
  • Snapshot the finalize share after waitForFinalizationRegistrations, so DistKeyShare() interpolates over a fuller QUAL.

Tests

  • TestGetResharingPrevDKG_PrefersSealedFinalizeShare

issue: #86

…ynomial divergence

Resharing dealers recomputed their fromRound DistKeyShare live at deal
time, but kyber interpolates the first oldT QUAL members at call time, so
a QUAL that filled up after finalization yielded a share on a different
polynomial than the coeffs already submitted on-chain, and every verifier
complained. loadFromRoundShare now deals from the share sealed at
finalization (cache -> sealed store -> live recomputation fallback), and
FinalizeDKG snapshots that share only after the finalization-stage wait so
the QUAL is fuller before it is sealed.
@yingyangxu2026

Copy link
Copy Markdown
Contributor

Verified the deal-from-sealed behavior holds (across ~86 reshares the recompute-fallback warning never fired, and replacing the sealed share is what causes divergence — proving dealing sources from the sealed store, not a live recompute). One security issue and two smaller items surfaced:

The seal isn't bound to its storage slot. enclave/seal.go:12/:31 call SealWithUniqueKey(data, nil) / Unseal(sealed, nil) with a nil AAD; LoadDistKeyShare (store/dkg_store.go:98) derives the round purely from the file path, and MarshalDistKeyShare embeds no round — the only binding is the session nonce, which binds to a DB instance, not a round. So a host with write access to the state dir can copy/rollback a sealed dist_key_share from one round's directory into another's and it unseals and is accepted as that round's share (stale-share replay). Reproduced on a devnet: relocating round N's sealed share into round N+1's slot, then a cold-cache restart, made round N+1 deal the wrong share → divergence → complaints; a restart-only control with no file change stayed clean, confirming the relocated blob was accepted by the seal. This is the same class as the med3 "store key binding" finding from the PR #75 audit, previously discussed for the launcher/TDX path — it applies to the SGX kernel's enclave.SealToFile too, so both private_coeffs.sealed and dist_key_share.sealed are affected. Suggest binding round (and code commitment) into the seal AAD so a slot mismatch fails to unseal.

SealToFile uses os.WriteFile (enclave/seal.go:17) with no fsync or temp-file+rename, so a crash right after finalize can lose/truncate the sealed share and land the next reshare in the divergent recompute fallback. Worth an atomic write + fsync.

Minor: the cache-hit and sealed-hit paths (dist_key_gen.go:316-324) are silent — only the fallback warns — so an operator can't positively confirm which source a deal's share came from. A one-line log on the sealed-hit path would help.

@jinn-agent

jinn-agent Bot commented Jul 21, 2026

Copy link
Copy Markdown

The core fix is sound: loadFromRoundShare now prefers the share sealed at finalization, and snapshotting is moved after waitForFinalizationRegistrations so QUAL is fuller when DistKeyShare() is called. One correctness gap found in the fallback path.

Coverage note: review covers all three changed files in full.


Review iteration 1 · Commit a0897f5 · 2026-07-21T10:06:26Z

@0xHansLee
0xHansLee merged commit 548c93d into main Jul 23, 2026
13 checks passed
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.

2 participants