Malformed Paillier Keys in THORChain’s TSS Stack

Reproducing a suspected GG20 signing-oracle path

security
A look at malformed Paillier key admission, signing-oracle behavior, and transport invariants in THORChain’s older TSS stack.
Author

banteg

Published

May 16, 2026

Threshold signatures are a cryptographic boundary: no single participant holds the private key, and a signature requires a threshold of participants. In production, that boundary also depends on key-generation proofs, message-routing invariants, replay protection, repair semantics, and abort-data handling.

On 15 May 2026, THORChain paused after a reported Asgard vault drain later traced to more than USD 11M across at least nine chains. Public incident updates centered on a newly churned validator and a possible GG20 weakness that may have leaked enough vault key material to sign as the vault.

We examined the deployed fork at the center of that theory: THORChain tss-lib tag v0.1.6 at commit 287e1e2, as used by thornode v3.18.0, plus the surrounding bifrost/tss/go-tss wrapper.

The core finding is not “a router bypass.” The end state is valid vault signatures over attacker-chosen payloads, which the rest of the system treats as ordinary vault-originated actions.

TL;DR

Our local evidence supports the malformed-Paillier key-material leakage model against the examined tss-lib v0.1.6 fork. The key-generation path accepts and persists peer Paillier material without the later MOD/FAC proof family that establishes a well-formed two-prime Paillier modulus. A malicious participant can therefore register a 2048-bit Paillier modulus that passes the legacy admission gate while containing attacker-known factors.

That persisted Paillier key is reused during signing in the MtA and MtAwc subprotocols. The highest-value path is MtAwc, where an honest party’s Lagrange-adjusted long-term signing share, \(w_i\), participates in a homomorphic Paillier computation. In an instrumented full-signing harness, candidate residue corrections produce a clean oracle shape: correct \(\gamma_i \bmod p\) and \(w_i \bmod p\) corrections complete signing, wrong \(\gamma_i\) guesses fail at the nonce-side Type 5 check, and wrong \(w_i\) guesses fail at the long-term-share Type 7 check.

The go-tss wrapper does not eliminate that oracle. A group-wide malformed-Paillier branch survives the real wrapper path and preserves the same success / Type 5 / Type 7 classification. We also found an independent wrapper-layer route-mismatch issue: an outer envelope can be treated as unicast by the THORChain reliable-broadcast layer while its inner TSS payload is treated as broadcast by tss-lib, bypassing the wrapper’s broadcast hash-confirmation gate in focused tests.

The evidence establishes, in the examined code, that:

  • tss-lib v0.1.6 admits malformed Paillier material in the known GG18/GG20 malicious-Paillier key-extraction class.
  • The malformed MtA and MtAwc transcripts are accepted by the real fork in pairwise tests.
  • The full signing protocol exposes the expected success / Type 5 / Type 7 oracle classes under instrumented residue corrections.
  • The go-tss wrapper preserves the oracle for a group-wide malformed branch.
  • Duplicate Round 5 overwrite and TSSControl repair can carry a forged body and preserve the oracle class.
  • The outer-unicast / inner-broadcast route mismatch is a real wrapper flaw in source-level and unit-level tests, with sibling bulk-shape, repair-binding, and blame-evidence issues.

It does not prove a complete, efficient online extractor against a live committee, and it does not establish incident attribution. Several target-isolation strategies failed in the harness. Vault-specific keygen, keysign, and blame artifacts are still required to connect these mechanics to a production incident.

Background: GG20, Paillier, and why this attack class exists

GG18/GG20-style threshold ECDSA uses Paillier encryption inside multiplication-to-addition, or MtA, subprotocols. One party encrypts a private value, another party homomorphically combines that ciphertext with its own private value, and the two parties receive additive shares of a product without directly revealing their inputs.

That design only works if each participant’s Paillier key is well formed and if the accompanying zero-knowledge proofs establish the properties the protocol assumes. During distributed key generation, parties publish Paillier public keys and proof parameters. Those values are then persisted and reused in later signing sessions.

In a GG20 signing session, each participant contributes a share of a nonce \(k_i\) and a Lagrange-adjusted long-term-share residue \(w_i\). The MtA path touches nonce-side values such as \(\gamma_i\); the with-check variant, MtAwc, touches \(w_i\). A leak in MtAwc reaches long-term signing-share material, not just a per-session nonce variable.

The malicious-Paillier attack class targets the assumption that the Paillier modulus \(N\) is well formed. A valid Paillier modulus should be the product of two large primes with the structure required by the protocol. If a malicious participant can get honest peers to persist an attacker-shaped \(N\) with known factorization structure, the homomorphic MtA response stops behaving like a one-way operation from the attacker’s point of view.

A simplified relation is:

\[ \operatorname{Dec}(c_B) = (k + N / p)\,b + \beta' \pmod N \]

Here \(p\) is an attacker-known factor of the malformed modulus, \(b\) is the honest peer’s private value for the MtA path under test, and \(\beta'\) is Bob’s secret Paillier mask. Malformed \(N\) creates residue-sensitive relations modulo attacker-chosen factors. Iterated across enough factors, those residues can be combined with CRT-style reconstruction.

This is not a new concern. SafeHeron published a working GG20 malicious-Paillier proof of concept in late 2023, including a Fiat-Shamir grinding technique for the range-proof branch. Fireblocks’ BitForge research documented a related, cheaper \(q^5\)-mask extraction variant earlier the same year. The known fixes are MOD and FAC proofs, no-small-factor guarantees, and session-bound proof context. Those fixes exist in later BNB Chain tss-lib releases. They are absent from THORChain’s examined v0.1.6 fork.

The deployed surface

The relevant path:

KEYGEN
  Round 2  → peer Paillier key (Pki) sent and stored
  Round 4  → legacy N-th-root proof verified
            (no MOD proof, no FAC proof, no small-factor floor above 1000)

SIGNING
  Round 1  → each signer broadcasts cA + range proof for its nonce encryption
  Round 2  → peers run BobMid and BobMidWC against the sender’s saved Paillier key
  Round 3  → parties broadcast delta_i, sigma_i, T_i, and T proof material
  Round 5  → parties broadcast PDL-with-slack proof bound to cA and R
  Round 6  → parties verify PDL and check the R-product consistency path (Type 5 abort)
  Round 7  → parties check the long-term-share consistency path (Type 7 abort)

The malformed-Paillier path enters at keygen and becomes useful during signing. At keygen time, a malicious node can register an attacker-shaped \(N\) and have it persisted in other nodes’ LocalPartySaveData. During signing, that same persisted \(N\) appears inside BobMid and BobMidWC whenever the malicious participant joins a signing committee.

Layer 1: keygen admits malformed Paillier moduli

THORChain tss-lib v0.1.6 checks Paillier keys with a 13-challenge N-th-root proof in crypto/paillier/paillier.go:229-276, after Round 4 of keygen. The verifier rejects factors below 1000 and confirms \(pf_i^N \bmod N = x_i \bmod N\) for each challenge. That is all it does. It does not prove that \(N\) is the product of exactly two primes, it does not require those primes to be safe or strong, and it does not constrain attacker-known factors above the legacy floor.

Our admission harness, gg20_paillier_admission_and_forge.py, builds a 2048-bit modulus that contains the exact small-factor set 1009, 1013, 1019, 1021, 1031, …, 1181, pads the modulus with larger cofactors so the total bit length matches the gate, and still passes the legacy proof.

The relevant recorded output is:

[2] Same persisted malicious N with CRT-sized small factors
    N bit length: 2048
    fixed CRT factors: 1009..1181
    CRT product bits: 263
    old N-th-root proof verifies for this same N: True

That single persisted \(N\) can carry the small-factor set used by the cost model. The attacker does not need a separate toy Paillier key for every residue.

For comparison, BNB tss-lib v3.0.0 adds MOD and FAC proofs in keygen and verifies them before the saved Paillier material is accepted.

Layer 2: MtA and MtAwc accept the forged ciphertext

Keygen admission matters if honest signing code accepts a malicious Alice ciphertext built around the malformed Paillier key.

Our pairwise harness, malicious_range_test.go, constructs Alice as a malicious participant holding a malformed 2048-bit \(N\) with a 16-bit factor \(p = 65537\). Alice sets \(c_A = \operatorname{Enc}(N / p)\) and forges the accompanying RangeProofAlice by grinding Fiat-Shamir challenges divisible by \(p\), following the same proof-shaping idea as SafeHeron’s public CheatProve technique.

The pairwise test records:

=== RUN   TestMaliciousPaillierRangeProofIsAcceptedByBobMid
    accepted malicious Paillier N with bitlen=2048 and factor p=65537
    forged Alice range proof after 8253 challenge attempts
    BobMid accepted cA=Enc(N/p); with Bob's internal betaPrime revealed,
        decrypted cB recovers victim share mod p = 4367
    BobMidWC also accepted the same forged cA/proof on the long-term-share path;
        decrypted cB recovers victim share mod p = 4367
--- PASS

Two details matter. First, the 8,253 challenge attempts are an offline proof grind, not an online cost paid during the live ceremony. Second, the same forged transcript is accepted by both BobMid and BobMidWC. The latter is the long-term-share path, which is what makes the \(w_i\) leak surface live rather than limiting the issue to a per-session nonce-side variable.

The test instruments Bob’s internal \(\beta'\) only to confirm the algebraic structure. A real malicious participant does not get that value, which leaves the full-signing oracle question.

Layer 3: the full-signing oracle

Live malicious Alice does not see Bob’s secret Paillier mask. She can observe whether a signing ceremony succeeds, aborts at Type 5 in Round 6, or aborts at Type 7 in Round 7. The question is whether those outcomes line up with residue guesses.

The full-signing harness, ecdsa/signing/malicious_oracle_test.go, drives a real 4-party threshold+1 signing session from the library’s own keygen fixtures: one malicious signer, three honest signers, and one live ECDSA signing run. It installs the malformed Paillier key on the malicious side, emits \(c_A = \operatorname{Enc}(k_\mathrm{base} + N / p)\) as the Round 1 ciphertext, and lets the malicious party adjust locally decrypted MtA responses by candidate residue vectors for the three honest peers’ \(\gamma_i \bmod p\) and \(w_i \bmod p\) values.

The matrix is clean:

Candidate state Full-signing outcome
Correct \(\gamma_i \bmod p\), correct \(w_i \bmod p\) Valid final ECDSA signature
Wrong \(\gamma_i \bmod p\), correct \(w_i \bmod p\) Type 5 / Round 6: \(g \ne \prod R_i\)
Correct \(\gamma_i \bmod p\), wrong \(w_i \bmod p\) Type 7 / Round 7: \(y \ne \prod S_j\)
Wrong \(\gamma_i \bmod p\), wrong \(w_i \bmod p\) Type 5 appears first

This is the oracle shape needed for CRT-style residue recovery: the abort classes are structurally separable and tied to different state components. Type 5 corresponds to nonce-side \(\gamma_i\) / \(k_i\) arithmetic. Type 7 corresponds to the long-term \(w_i\) path. Type 5 abort payloads carry KI, GammaI, AlphaIJ, and BetaJI rows that are enough to recompute the aborting party’s \(\delta_i\) equation for that failed session. Type 7 payloads carry raw \(\mu\) plaintext/randomness rows.

Two qualifiers matter. First, the with-check proof in MtAwc binds Bob’s coefficient \(x\) to the public EC point \(X = g^x\). In our BNB v3 differential, a malicious Bob could not turn raw-\(\mu\) exposure into an arbitrary-coefficient \(k_i\) leak by simply claiming a different \(x\). Type 7 helps confirm the long-term-share check path and helps forensic transcript review, but it is not a standalone arbitrary-coefficient extractor.

Second, the harness has more visibility than a production node by default. The final bridge harness confirms Type 5 and Type 7 visibility for the group-wide, duplicate Round 5, and TSSControl Round 5 cases from the malicious branch’s inbound protocol messages. Other classes, such as hash-confirmation failure, duplicate skip, repair request visibility, and timeout, remain weaker as online oracle signals.

Why broadcast consistency becomes the central question

A single pairwise leak is not enough to recover a threshold key. A practical path needs repeated useful oracle outcomes against honest participants, so transport semantics become critical.

Some candidate-dependent values in GG20 signing are logically broadcast. If every honest participant sees the same broadcast branch, the attacker is forced into a group-wide hypothesis. For realistic committee sizes, group-wide guessing across all relevant residues quickly becomes infeasible. A useful extractor wants isolation: make one target process a candidate branch, keep everyone else advancing on a compatible branch, and learn from the target’s success or abort class.

Reliable broadcast is supposed to block that. If Round 3 and Round 5 bodies are protected by a broadcast hash-confirmation layer, the attacker cannot simply send one broadcast value to a target and a different broadcast value to everyone else.

Cryptographic proofs are only as strong as the transport invariants that preserve their message semantics.

Layer 4: carrying the malformed branch through go-tss

The cryptographic mechanics live in tss-lib, but THORChain nodes run them through bifrost/tss/go-tss in thornode v3.18.0. The wrapper handles reliable broadcast, batching, missing-share repair through a TSSControl channel, and accepted-share replay protection.

Our keysign-Paillier bridge harness, route_mismatch_paillier_split_test.go, wires the full-signing oracle harness into a real TssCommon.ProcessOneMessage path. It establishes three things.

First, the group-wide bridge works. When malicious Round 1 and Round 5 messages are delivered through the wrapper to all peers, the ceremony produces the same success / Type 5 / Type 7 / Type 5 matrix as the lower-level signing harness. The wrapper does not destroy the oracle.

Second, duplicate Round 5 overwrite is a state-divergence primitive. The wrapper accepts an originally emitted non-malicious Round 5 and then a forged Paillier Round 5 for the same sender and message identifier in one outer-unicast / inner-broadcast bulk. The stored Round 5 hash becomes the forged branch, and later verification still produces the expected oracle class. This is possible because the examined tss-lib signing path treats replay protection as caller-owned while writing SignRound3Message and SignRound5Message directly into sender-indexed slots, and the wrapper’s accepted-share gate is not bound to the body hash in a way that prevents later overwritten state.

Third, TSSControl missing-share repair is a second carrier, but there are two distinct results. The wrapper-level tests show that normal ErrNotMajority repair can create a live ReqHash token through majority-hash agreement, and that a mismatched KEYSIGN3 or KEYSIGN5 body from an unrequested responder can re-enter processTSSMsg. The Paillier-connected bridge is narrower: in the 4-party fixture, a normal missing-share request asks for the forged Round 5 body hash, an unrequested malicious responder returns that forged Paillier Round 5 body, the wrapper stores it, and the same Type 5 / Type 7 oracle class is preserved.

The clean run records:

TestRouteMismatchPaillierOracleGroupBranchThroughGoTssWrapper
   wrong gamma + correct w -> group branch produced Type 5
   correct gamma + wrong w -> group branch produced Type 7

TestRouteMismatchPaillierDuplicateRound5OverwriteFeedsOracle
   wrong gamma + correct w -> duplicate Round5 overwrite stored forged body; Type 5
   correct gamma + wrong w -> duplicate Round5 overwrite stored forged body; Type 7

TestRouteMismatchPaillierTSSControlRound5RepairFeedsOracle
   wrong gamma + correct w -> repair stored forged Round5 from unrequested responder; Type 5
   correct gamma + wrong w -> repair stored forged Round5 from unrequested responder; Type 7

TestRouteMismatchPaillierMaliciousSignerLocalAbortVisibility
   Type 5 and Type 7 are classified from inbound protocol messages

This bridges the cryptographic mechanics into the wrapper for a group-wide malformed branch. It preserves the abort-class distinction, but does not prove target-specific extraction.

A separate wrapper finding: route-mismatch transport bypass

While instrumenting the wrapper, we found an independent transport issue worth treating on its own terms.

processTSSMsg in bifrost/tss/go-tss/common/tss.go decides whether to run reliable-broadcast hash confirmation by reading the outer WireMessage.Routing.IsBroadcast. Once a message reaches updateLocal, the parser consumes the inner BulkWireMsg.Routing.IsBroadcast to decide TSS semantics and passes that flag into tss-lib’s party.UpdateFromBytes.

The mismatch is:

outer WireMessage.Routing.IsBroadcast = false   # skips wrapper broadcast hash gate
inner BulkWireMsg.Routing.IsBroadcast = true    # tss-lib processes a broadcast

In the focused unit repro, an outer-unicast wrapper around an inner-broadcast TSS payload is accepted without the hash-confirmation step, then handed to tss-lib as a broadcast message. The sender signature covers the bulk bytes plus msgID, but not the outer routing fields, outer RoundInfo, wrapper MessageType, or wrapper MsgID. That creates a route-binding gap between the transport layer and the TSS state machine.

The same review surfaced three sibling issues.

The first is bulk-shape ambiguity. The receiver loops over the supplied []BulkWireMsg without enforcing len(bulk) == msgNum, without enforcing the expected MsgIdentifier set, without rejecting duplicate identifiers, and without requiring uniform route metadata across entries. Focused tests show that one-entry partial bulks and duplicate same-identifier branches can reach signing storage. The later batch-completion tests are important, though: processKeySign still waits for every expected identifier before emitting success, and two completed signatures in a three-message batch produce a task-level timeout with no partial TSSTaskDone. A per-identifier Type 5 blame reason can survive that timeout path, but this is still pre-oracle branch selection and accepted-then-overwritten state, not a demonstrated same-\(\gamma\) adaptive retry path or a 15x batch-probe multiplier.

The second is repair-path injection. TSSControl request tokens are naturally created by the ErrNotMajority broadcast-repair path. Once a token exists, response handling applies the embedded body if the token check passes, without fully binding the body hash, cache key, request type, or responder set to the outstanding request.

The third is blame-evidence nulling. A route-mismatched direct update can skip the RoundMgr canonical record, and an unsigned outer RoundInfo can store under the wrong cache key. The invalid-message blame path may identify a culprit while leaving the signed offending body and signature blank because no canonical RoundMgr entry is found.

Taken together, these are hardening issues even if the malformed-Paillier path is closed at the library boundary. The wrapper’s signed-bulk envelope is where these invariants should fail closed.

What did not work: the falsifiers

A useful security report should include the negative results. We tried to push the malformed-Paillier path into a target-specific split-view extractor, and several obvious strategies failed.

Target-only Round 1 / Round 3 split. The malicious party sends malformed Round 1 and a candidate-corrected Round 3 only to the target. The target stores them, but non-targets do not receive a malicious Round 3 branch they can act on. The ceremony stalls before malicious Round 5 emits, and no Type 5 / Type 7 signal reaches the malicious node.

Shared Round 3 / split Round 5. The malicious party adjusts only the target’s MtA/MtAwc material, sends the resulting Round 3 to everyone, and splits Round 5 by giving the target a forged malformed-\(c_A\) PDL proof while non-targets get the original Round 5 bytes. Non-targets reject the original Round 5 PDL proof before any target-local oracle appears.

Strict malicious self-forking. The malicious party runs main and shadow forks to cover target and non-target branches. Honest peers only answer the branch they actually received, so the malicious forks do not assemble compatible SignRound2 vectors. At least one non-target Round 3 fails before any target-local oracle appears.

Round 2 replay and cross-feeding. Replaying the target’s branch-bound Round 2 response into the shadow fork does not rescue the split. Cross-feeding every observed honest Round 2 response into both forks also fails; the mixed response set still breaks before a target-local oracle can be observed.

Same-bulk duplicate Round 1. Sending two malicious Round 1 branches to the target in one wrapper bulk causes the target to store one branch and emit exactly one Round 2 response back to the malicious sender. Duplicating Round 1 does not synthesize the two compatible honest response vectors needed to keep both forks advancing.

Post-Type-5 Round 5 retry. After a Type 5 abort, sending a different malicious Round 5 body for the same sender and message identifier does not change the stored branch. The wrapper treats the second body as a duplicate and returns without error. The duplicate path we reproduced is pre-oracle branch selection, not same-state adaptive retry after an oracle signal.

These negative results sharpen the claim. Route mismatch and duplicate-bulk delivery are real wrapper-layer weaknesses, and the group-wide malformed-Paillier oracle is preserved end-to-end. They still do not supply the target-specific split-view extractor assumed by the cheapest attack model. A future positive result would need to keep non-targets advancing on a compatible branch while the target-only branch resolves into a locally observable oracle class, or solve the fresh \(\gamma_i\) dimension another way.

Operator-facing detection: the small-factor fingerprint

Operators reviewing their own infrastructure need a cheap deterministic triage step before deeper transcript replay. We built gg20_paillier_factor_fingerprint.py for that purpose. For every saved peer Paillier \(N\) in a node’s keygen material, it reports signals such as:

  • n_bits, to confirm the modulus size.
  • crt_gcd, measuring overlap with the small-factor set used by the proof-grinding cost model.
  • small_factors, especially factors above the legacy <1000 filter such as 1009 and nearby primes.
  • bounded Pollard-rho factors, tagged probable-prime versus composite.
  • pairwise gcd results across every peer \(N\) from keygens involving the suspected node.
  • duplicate \(N\) values across vault keygens.
  • field_kind, so reviewers do not confuse malformed Paillier \(N\) with malformed NTilde or ambiguous serialized fields.

The strongest keygen-side signal would be repeated malformed Paillier structure associated with the same participant across multiple vault keygens. A single 64-bit factor is strong evidence of malformed admission, but it is not automatically the parameter used by the specific online proof-grind technique in our repro. The diagnostic question is more nuanced: does the persisted \(N\) contain very small proof-grindable factors just above the legacy floor, does it mix tiny and larger known factors, and does that shape repeat across keygens?

For signing transcripts, useful forensic indicators include:

  • logical broadcast TSS messages accepted without corresponding broadcast hash-confirmation messages;
  • outer IsBroadcast=false with inner IsBroadcast=true in reconstructed envelopes;
  • delivering peer identity that does not bind cleanly to outer or inner From fields;
  • TSSControl responses whose body hash, cache key, request type, responder set, or msgID do not match the outstanding request tuple;
  • bulk messages whose length differs from the configured msgNum, whose identifiers are missing or duplicated, or whose inner routing metadata is mixed;
  • duplicate KEYSIGN3 or KEYSIGN5 branches from the same sender and message identifier reaching signing storage;
  • cases where the first accepted payload hash differs from the later stored payload hash consumed by Round 5, Round 6, or Round 7;
  • repeated Type 5 or Type 7 abort classes visible to the suspected signer.

Raw TSS and blame transcripts should be treated as sensitive evidence. Type 5 abort payloads carry enough data to recompute failed \(\delta_i\) equations, and Type 7 payloads can expose raw \(\mu\) rows. Those artifacts should not be published before cryptographic review and redaction.

Remediation

If a malicious signer participated in vulnerable keygen, a code upgrade alone is not enough. The persisted Paillier material is the bug. A conservative response has to address the vault, keygen, library, wrapper, and forensic layers.

Vault-level remediation. Generate a fresh vault public key under hardened keygen, sweep funds, and retire the affected vault. Do not treat resharing the same private key as sufficient if the old key may already be reconstructable. Do not reopen signing on an affected vault key after only patching code.

Keygen-level remediation. Require modern Paillier modulus validation: MOD proofs, FAC proofs, no-small-factor guarantees, and session-bound proof context. Persisted proof parameters should be auditable, and historical keygen transcripts should be preserved for replay against hardened verification.

Library-level remediation. The signing storage boundary should be store-once by (sender, message type) for each signing session. Exact duplicate payloads can be idempotent, but a second different payload hash from the same sender and type should be rejected. Relying entirely on the wrapper for replay protection creates a fragile boundary.

Wrapper-level remediation. Fail closed before updateLocal, before worker spawn, and before any call into UpdateFromBytes. A hardened envelope should enforce:

WrappedMessage.MsgID == local signing session ID
delivering peer maps to outer Routing.From
signature covers msgID + MessageType + outer routing + RoundInfo + bulk bytes
outer route matches every inner route
broadcast and unicast semantics are not mixed inside one bulk
unicast To includes the local party and matches inner To
broadcast has no narrow To list
bulk contains no duplicate (round, sender, MsgIdentifier) keys
bulk contains exactly the expected MsgIdentifier set unless explicit partial mode exists
all entries parse to the expected round and message type
TSSControl response matches stored request tuple, body hash, cache key, type, and responder set

The bulk should be parsed and validated as a whole before any worker is enqueued. Per-entry validation followed by asynchronous worker-side state mutation is the shape that makes duplicate identifiers and accepted-then-overwritten state dangerous.

Forensic-level remediation. Preserve keygen, keysign, and blame artifacts before nodes rotate, wipe, or garbage-collect local state. Review saved peer Paillier moduli with small-factor fingerprinting, replay keygen transcripts against hardened MOD/FAC verification, and correlate suspicious keygen material with signing transcripts that show route inconsistency, repair-path injection, duplicate branches, or repeated abort-class signals.

What this means for threshold systems

Threshold cryptography also depends on transport and state semantics: who sent a message, whether it was broadcast or unicast, which session it belongs to, whether it has already been accepted, and whether repair traffic is bound to the exact missing message it claims to repair.

A malformed Paillier modulus is a cryptographic flaw. An outer/inner broadcast mismatch is a transport flaw. Duplicate branch overwrite is a state-management flaw. Weak repair binding is a recovery-path flaw. Together, they create the cross-layer surface that can turn a key-material leakage class into a signing-oracle investigation.

The actionable response is to harden keygen, rotate suspect vault keys, bind and validate every wrapper envelope, make signing storage store-once, preserve transcripts for review, and avoid publishing raw TSS artifacts until they have been redacted.

References

Disclosure note: The reproduction package used for this post contains synthetic harnesses and fixtures. Raw production keygen, keysign, and blame artifacts should not be published before cryptographic review and redaction.