Multi-Party Computation

Threshold ECDSA
from Scratch

How multiple parties can jointly sign messages without any single party knowing the private key — and why that's much harder than it sounds.

Based on: Canetti, Gennaro, Goldfeder, Makriyannis, Peled — CCS'20 / 2024 UC Non-Interactive, Proactive, Distributed ECDSA with Identifiable Aborts

ECDSA in three minutes

A digital signature scheme lets you prove authorship of a message using a mathematical key pair. ECDSA — the Elliptic Curve Digital Signature Algorithm — underpins Bitcoin, Ethereum, TLS, and most of the modern security stack.

Key generation

Pick a special point g on an elliptic curve. g will be publicly known. Your private key is a random scalar x. Your public key is computed by "adding g to itself x times":

// Key pair
x ← random scalar in 𝔽_q // secret key — NEVER share
X = g^x // public key — share freely
// Hard problem: given X and g, find x. (Discrete log on EC)

Signing

m = H(msg) // hash the message
γ ← random nonce (fresh every time!)
Γ = g
r = x-coordinate of Γ // first signature component
σ = γ⁻¹ · (m + r·x) mod q // second component
Output: (r, σ)

Verification

// Anyone with public key X can verify
R' = g^(m·σ⁻¹) · X^(r·σ⁻¹)
Accept if x-coordinate of R' == r

The nonce is sacred. Using the same γ twice, or leaking it even partially, allows full recovery of the private key x. The PlayStation 3 private key was broken exactly this way in 2010.

Why ECDSA is "threshold-unfriendly"

The bottleneck is the term γ⁻¹ · (m + r·x). It involves the product of two secret values — the nonce and the key. Multiplying things that are secretly distributed across parties requires heavy machinery. Compare with Schnorr signatures, where the analogous computation is just γ + c·x — linear, trivially distributable.

The threshold problem

A threshold signature distributes the private key x across n parties so that no single party holds it. Any attempt to sign requires all parties to cooperate — yet at the end, only a standard ECDSA signature emerges, verifiable by anyone.

Party P₁
secret: x₁
public: X₁ = g^x₁
Party P₂
secret: x₂
public: X₂ = g^x₂
Party P₃
secret: x₃
public: X₃ = g^x₃
Party Pₙ
secret: xₙ
public: Xₙ = g^xₙ
// Nobody knows x, but collectively: x = x₁ + x₂ + ... + xₙ // additive sharing
X = X₁ · X₂ · ... · Xₙ // public key — everyone computes this

The core difficulty

To compute σ = γ⁻¹ · (m + r·x) without reconstructing either γ or x, parties need to evaluate the product of their shares. This is an inherently nonlinear operation on secret data.

The MtA problem. Party Pᵢ holds aᵢ, party Pⱼ holds bⱼ. They want additive shares of aᵢ · bⱼ — without either learning the other's value. This is the "Multiplicative-to-Additive" share conversion, and it's the cryptographic heart of the protocol.

The signing protocol

The protocol runs in three rounds of communication. Each round, parties broadcast messages to all others. Click through the steps below.

Sample local nonce shares

Each party independently samples two local random values and encrypts them using their own Paillier key:

// We want to compute γ · k and x · k
// where k = k₁ + k₂ + ... + kₙ
// where γ = γ₁ + γ₂ + ... + γₙ
// where x = x₁ + x₂ + ... + xₙ
// Party Pᵢ samples:
kᵢ, γᵢ ← random in 𝔽_q
// Paillier-encrypt under own key (everyone can encrypt, only Pᵢ can decrypt)
Kᵢ = enc_i(kᵢ) // commitment to kᵢ Gᵢ = enc_i(γᵢ) // commitment to γᵢ
// Broadcast to all parties → broadcast (Kᵢ, Gᵢ)

The Paillier ciphertexts act as commitments — binding and hiding. Parties are committing to their nonce shares before seeing others' values, preventing cheating.

Multiplicative-to-Additive (MtA) conversion

This is where the magic happens. For every pair (Pᵢ , Pⱼ), we need additive shares of the cross-products γⱼ · kᵢ and xⱼ · kᵢ — without either party learning the other's secrets.

// Pⱼ receives Kᵢ = enc_i(kᵢ) from round 1. kᵢ is only known to Pᵢ
// Pⱼ wants to split γⱼ·kᵢ into: αᵢ,ⱼ (for Pᵢ) + βⱼ,ᵢ (for Pⱼ)
βⱼ,ᵢ ← random // Pⱼ picks this value. It is Pⱼ's additive mask
// Paillier homomorphism: compute enc_i(γⱼ·kᵢ - βⱼ,ᵢ) WITHOUT knowing kᵢ
Dᵢ,ⱼ = γⱼ Kᵢ enc_i(−βⱼ,ᵢ) // Pⱼ sends Dᵢ,ⱼ to Pᵢ
// Pᵢ decrypts to get their share αᵢ,ⱼ = dec_i(Dᵢ,ⱼ) = γⱼ·kᵢ − βⱼ,ᵢ
// Note: αᵢ,ⱼ (in Pᵢ) + βⱼ,ᵢ (in Pⱼ) = γⱼ·kᵢ
// no single party saw the product γⱼ·kᵢ
// γⱼ·kᵢ exists only implicitily as αᵢ,ⱼ + βⱼ,ᵢ split across two parties

The same procedure runs in parallel for xⱼ · kᵢ, giving shares α̂ᵢ,ⱼ and β̂ⱼ,ᵢ. Also, each party broadcasts Γᵢ = g^γᵢ.

Key innovation of this paper. Rather than just sending Paillier ciphertexts, parties also attach zero-knowledge proofs that the values inside the ciphertexts are well-formed and in range. This prevents malicious deviations without adding extra rounds.

Aggregate local shares

Each party combines all the cross-product shares they received to compute local shares of γ·k and x·k:

// Pᵢ decrypts their incoming ciphertexts αᵢ,ⱼ = dec_i(Dᵢ,ⱼ)
// Pᵢ's local share of γ·k δᵢ = γᵢ·kᵢ + Σ_(j≠i) (αᵢ,ⱼ + βᵢ,ⱼ)
// Note that Σᵢ δᵢ = Σᵢ γᵢ·kᵢ + Σᵢ Σ(j≠i) (αᵢ,ⱼ + βᵢ,ⱼ)
//                 = Σᵢ γᵢ·kᵢ + Σ(i≠j) γⱼ·kᵢ
//                 = (Σᵢ γᵢ) · (Σᵢ kᵢ)
//                 = γ · k
// Pᵢ's local share of x·k χᵢ = xᵢ·kᵢ + Σ_(j≠i) (α̂ᵢ,ⱼ + β̂ᵢ,ⱼ)
// Note that Σᵢ χᵢ = x · k
// r = x-coordinate of Γ
// where Γ = Γ₁ · Γ₂ · ... · Γₙ = g^γ₁ · g^γ₂ + ... + g^γₙ = g^γ
// Broadcast δᵢ and σ̂ᵢ = kᵢ·m + r·χᵢ → broadcast (δᵢ, σ̂ᵢ)
Reconstruct the signature

Once all shares are collected, any party can reconstruct the signature:

// Sum the δ-shares
δ = Σ_i δᵢ = γ·k // Sum the σ̂-shares and divide
σ = (Σ_i σ̂ᵢ) · δ⁻¹ = (k·m + r·k·x) / (γ·k) = γ⁻¹·(m + r·x) ✓ valid ECDSA signature

The output (r, σ) is a perfectly standard ECDSA signature. Any third party can verify it with the standard ECDSA algorithm — they don't need to know it was produced by multiple parties.

Why secrecy holds

Throughout the protocol, what does each party actually see?

// Round 1 transcript contains:
Kᵢ = enc_i(kᵢ) ← Paillier encrypted, hides kᵢ
Gᵢ = enc_i(γᵢ) ← Paillier encrypted, hides γᵢ
// Round 2 transcript contains:
Dᵢ,ⱼ ← enc of cross-products, hides everything
Γᵢ = g^γᵢ ← hides γᵢ by discrete log hardness
// Round 3 leaks only:
(δᵢ, σ̂ᵢ) ← equivalent to revealing (r, σ) which is public anyway

The key insight: information about the secret key x only enters the output in round 3, and by that point, it's indistinguishable from the signature itself — which is already public.

The Paillier homomorphism

The entire MtA protocol rests on one special property of Paillier encryption: you can perform arithmetic on encrypted values without decrypting them. This is called homomorphic encryption.

Paillier encryption

// Public key: N (product of two large primes p, q)
enc(m; ρ) = (1 + N)^m · ρ^N mod N² // ρ is random
dec(c) = m // using knowledge of p, q

The homomorphic property

Addition:
enc(a) ⊕ enc(b)
enc(a + b)
Scalar mult:
k ⊙ enc(a)
enc(k · a)
MtA trick:
γ ⊙ enc_i(k) ⊕ enc_i(−β)
enc_i(γ·k − β)

The scalar multiplication uses only the public key of Pᵢ — Pⱼ can compute enc_i(γⱼ · kᵢ − βⱼ,ᵢ) without ever knowing kᵢ. Only Pᵢ can decrypt the result.

Why NIZKs are essential

Paillier encryption is hiding but not automatically binding to any specific range. A malicious party could encrypt a value far outside the expected range and corrupt the protocol. The paper accompanies every Paillier operation with a Non-Interactive Zero-Knowledge proof (NIZK) that:

// Party Pᵢ proves (without revealing kᵢ):
1. Kᵢ is a valid Paillier encryption Π_enc
2. The plaintext kᵢ lies in range [−2^ℓ, 2^ℓ] range proof
3. Dᵢ,ⱼ was computed honestly from Kⱼ and γᵢ Π_aff-g

The key insight. Previous protocols (like GG18) verified correctness after the computation, requiring 6 extra rounds. This protocol embeds ZK proofs inline — achieving the same security in zero extra rounds.

Protocol properties

What sets this protocol apart from earlier threshold ECDSA work is the combination of properties achieved simultaneously.

Non-interactive signing

Rounds 1–2 run before the message is known ("presigning"). Once the message arrives, each party computes one field element and sends it. No interaction required.

Proactive security

Parties periodically run a key refresh. Security holds epoch-by-epoch: even if every party is compromised at some point, signatures remain safe as long as one party is honest per epoch.

Identifiable abort

If signature generation fails — due to a malicious party — the honest parties can identify and expel the culprit. Essential for real-world deployments.

UC composability

Full UC security: the protocol remains secure when deployed as a component inside a larger system, with concurrent sessions and an adaptive adversary.

n−1
Dishonest majority

Secure against n−1 corrupted parties out of n. The strongest possible threshold — only one honest party is needed.

Standard output

The output is a normal ECDSA signature. Verifiers need no special software — Bitcoin nodes, TLS clients, everyone just runs standard ECDSA verification.