Common MPC Pitfalls

Safeheron `multi-party-ecdsa-cpp` missing Paillier modulus validation

Safeheron’s multi-party-ecdsa-cpp ran GG18/GG20 key generation without checking the structure of each co-signer’s Paillier modulus $N$, so a non-biprime or smooth $N$ flowed through keygen and into the GG20 signing rounds unchecked. One example of vulnerable code is the Round 3 keygen verifier (pre-fix source):

1// FILE: src/multi-party-ecdsa/gg18/key_gen/round3.cpp
2// Safeheron/multi-party-ecdsa-cpp @ b75d125f (pre-fix, vulnerable)
3ok = bc_message_arr_[pos].pail_proof_.Verify(
4    sign_key.remote_parties_[pos].pail_pub_,
5    sign_key.remote_parties_[pos].index_,
6    bc_message_arr_[pos].dlog_proof_x_.pk_.x(),
7    bc_message_arr_[pos].dlog_proof_x_.pk_.y());

A malicious party could then publish $N = p_1 \cdots p_{16} \cdot q$ with each $p_i \approx 2^{16}$. During GG20 signing, the 16-factor structure opens parallel CRT channels and the small factors keep the MtA range-proof brute force at ~$2^{16}$ per channel. The victim’s encrypted share $x$ leaks $x \bmod p_i$ per session; CRT reconstructs the full share over 16 to ~$10^9$ sessions (Fireblocks technical report, POC).

Safeheron’s fix introduces two CGGMP21 proofs: PR #7 added a no-small-factor proof, and PR #10 replaced the share-binding pail_proof_ with the Paillier-Blum Modulus proof ($N = pq$ with $p \equiv q \equiv 3 \pmod 4$) verified directly against $N$.