Threshold Signatures
Threshold signatures enable a group to sign messages without ever reconstructing the private key, providing enhanced security over traditional multisig.
Overview
Threshold Signature Scheme (TSS) allows $t$ out of $n$ parties to collaboratively sign a message without reconstructing the private key.
Key Difference from Multisig:
- Multisig: Multiple signatures combined, visible on-chain
- Threshold Sig: Single signature indistinguishable from regular signature
Comparison: Multisig vs Threshold Signatures
| Feature | Multisig | Threshold Signature |
|---|---|---|
| On-chain appearance | Multiple signatures | Single signature |
| Privacy | Reveals number of signers | Looks like 1-of-1 |
| Transaction size | Large (multiple sigs) | Small (one sig) |
| Fees | Higher | Lower |
| Key reconstruction | Not applicable | Never reconstructed |
| Complexity | Low | High |
Key Properties
1. No Key Reconstruction
The private key is never assembled in one place:
- Generated in distributed manner (DKG)
- Signing uses key shares
- Secret remains distributed forever
2. Single Signature Output
1Regular signature: (r, s)
2Threshold signature: (r, s)
3→ Indistinguishable!
3. Threshold Flexibility
$(t, n)$ threshold:
- $t$ = minimum signers needed
- $n$ = total key share holders
- Example: $(2, 3)$ means any 2 out of 3 can sign
Distributed Key Generation (DKG)
Generate key shares without ever creating the full private key.
Protocol Flow
Simplified DKG Implementation
1from hashlib import sha256
2import secrets
3
4class DistributedKeyGeneration:
5 def __init__(self, threshold, num_parties, prime, generator):
6 self.threshold = threshold
7 self.num_parties = num_parties
8 self.prime = prime
9 self.G = generator
10
11 def generate_key_shares(self):
12 """
13 Simulate DKG for threshold ECDSA
14 In practice, this would involve secure multi-party computation
15 """
16 # Each party generates a secret and shares it using Shamir
17 party_secrets = []
18 all_shares = [[] for _ in range(self.num_parties)]
19
20 for party_id in range(self.num_parties):
21 # Generate secret for this party
22 secret = secrets.randbelow(self.prime)
23 party_secrets.append(secret)
24
25 # Generate shares of this secret
26 shares = self._shamir_share(secret, self.threshold, self.num_parties)
27
28 # Distribute shares
29 for i, share in enumerate(shares):
30 all_shares[i].append(share)
31
32 # Each party combines received shares to get their key share
33 key_shares = []
34 for party_shares in all_shares:
35 key_share = sum(share[1] for share in party_shares) % self.prime
36 key_shares.append(key_share)
37
38 # Compute public key (sum of all secrets · G)
39 total_secret = sum(party_secrets) % self.prime
40 public_key = self._scalar_mult(self.G, total_secret)
41
42 return key_shares, public_key
43
44 def _shamir_share(self, secret, threshold, num_shares):
45 """Generate Shamir shares"""
46 # Generate random polynomial coefficients
47 coeffs = [secret] + [secrets.randbelow(self.prime)
48 for _ in range(threshold - 1)]
49
50 # Evaluate polynomial at different points
51 shares = []
52 for x in range(1, num_shares + 1):
53 y = sum(coef * pow(x, i, self.prime)
54 for i, coef in enumerate(coeffs)) % self.prime
55 shares.append((x, y))
56
57 return shares
58
59 def _scalar_mult(self, point, scalar):
60 """Scalar multiplication on elliptic curve (simplified)"""
61 # In practice, use proper EC library
62 return (point[0] * scalar, point[1] * scalar)
Threshold ECDSA Signing
Most complex threshold signature scheme due to ECDSA's non-linear structure.
Signing Protocol
Simplified Implementation
1class ThresholdECDSA:
2 def __init__(self, curve_order, generator):
3 self.n = curve_order
4 self.G = generator
5
6 def sign_partial(self, message, key_share, nonce_share, R, r):
7 """
8 Generate partial signature
9
10 Args:
11 message: Message to sign
12 key_share: This party's share of private key
13 nonce_share: This party's share of nonce k
14 R: Combined nonce point
15 r: x-coordinate of R
16 """
17 # Hash message
18 h = int.from_bytes(sha256(message).digest(), 'big') % self.n
19
20 # Compute k^(-1) for this share
21 k_inv = self._mod_inverse(nonce_share, self.n)
22
23 # Partial signature: s_i = k_i^(-1) * (H(m) + r * x_i)
24 s_partial = (k_inv * (h + r * key_share)) % self.n
25
26 return s_partial
27
28 def combine_signatures(self, partial_sigs):
29 """Combine partial signatures into final signature"""
30 s = sum(partial_sigs) % self.n
31 return s
32
33 def verify(self, message, signature, public_key):
34 """Verify threshold signature (same as regular ECDSA)"""
35 r, s = signature
36 h = int.from_bytes(sha256(message).digest(), 'big') % self.n
37
38 # Compute u1 = H(m) * s^(-1), u2 = r * s^(-1)
39 s_inv = self._mod_inverse(s, self.n)
40 u1 = (h * s_inv) % self.n
41 u2 = (r * s_inv) % self.n
42
43 # Compute point: u1*G + u2*P
44 point = self._add_points(
45 self._scalar_mult(self.G, u1),
46 self._scalar_mult(public_key, u2)
47 )
48
49 # Verify: x-coordinate of point equals r
50 return point[0] % self.n == r
51
52 def _mod_inverse(self, a, m):
53 """Modular multiplicative inverse"""
54 def extended_gcd(a, b):
55 if a == 0:
56 return b, 0, 1
57 gcd, x1, y1 = extended_gcd(b % a, a)
58 return gcd, y1 - (b // a) * x1, x1
59
60 _, x, _ = extended_gcd(a % m, m)
61 return (x % m + m) % m
62
63 def _scalar_mult(self, point, scalar):
64 """Scalar multiplication (use proper EC library in production)"""
65 pass
66
67 def _add_points(self, p1, p2):
68 """Point addition (use proper EC library in production)"""
69 pass
Threshold Schnorr Signatures
Simpler than ECDSA due to Schnorr's linear structure.
Signing Protocol (Simplified)
1class ThresholdSchnorr:
2 def __init__(self, curve_order, generator):
3 self.n = curve_order
4 self.G = generator
5
6 def sign_partial(self, message, key_share, nonce_share, R_agg):
7 """
8 Generate partial Schnorr signature
9
10 Schnorr is linear: s = k + H(R,P,m)·x
11 Threshold: s_i = k_i + H(R,P,m)·x_i
12 """
13 # Compute challenge
14 challenge = self._compute_challenge(R_agg, message)
15
16 # Partial signature: s_i = k_i + c * x_i
17 s_partial = (nonce_share + challenge * key_share) % self.n
18
19 return s_partial
20
21 def combine_signatures(self, R_agg, partial_sigs):
22 """Combine partial signatures"""
23 s = sum(partial_sigs) % self.n
24 return (R_agg, s)
25
26 def verify(self, message, signature, public_key):
27 """Verify Schnorr signature"""
28 R, s = signature
29 c = self._compute_challenge(R, message)
30
31 # Verify: s·G = R + c·P
32 left = self._scalar_mult(self.G, s)
33 right = self._add_points(R, self._scalar_mult(public_key, c))
34
35 return left == right
36
37 def _compute_challenge(self, R, message):
38 """Compute Fiat-Shamir challenge"""
39 data = R.to_bytes() + message
40 return int.from_bytes(sha256(data).digest(), 'big') % self.n
BLS Threshold Signatures
Boneh-Lynn-Shacham (BLS) signatures have excellent aggregation properties.
Key Advantages
- Simple aggregation: Just add partial signatures
- Non-interactive: No rounds of communication
- Compact: Small signature size
Protocol
1class ThresholdBLS:
2 def __init__(self, pairing_group):
3 self.group = pairing_group
4
5 def sign_partial(self, message, key_share):
6 """
7 Generate partial BLS signature
8
9 BLS signature: σ = H(m)^x
10 Partial: σ_i = H(m)^(x_i)
11 """
12 # Hash message to curve point
13 H_m = self._hash_to_curve(message)
14
15 # Partial signature: σ_i = H(m)^(x_i)
16 sigma_partial = H_m ** key_share
17
18 return sigma_partial
19
20 def combine_signatures(self, partial_sigs, lagrange_coeffs):
21 """
22 Combine partial signatures using Lagrange interpolation
23
24 σ = ∏ σ_i^(λ_i)
25 where λ_i are Lagrange coefficients
26 """
27 sigma = self.group.identity()
28
29 for sigma_i, lambda_i in zip(partial_sigs, lagrange_coeffs):
30 sigma = sigma * (sigma_i ** lambda_i)
31
32 return sigma
33
34 def verify(self, message, signature, public_key):
35 """
36 Verify BLS signature using pairing
37
38 Check: e(σ, g) = e(H(m), P)
39 """
40 H_m = self._hash_to_curve(message)
41 g = self.group.generator()
42
43 # Pairing check
44 left = self.group.pair(signature, g)
45 right = self.group.pair(H_m, public_key)
46
47 return left == right
48
49 def _hash_to_curve(self, message):
50 """Hash message to curve point (use proper hash-to-curve)"""
51 pass
Security Considerations
1. Nonce Generation
Critical: Nonce reuse or predictable nonces break security!
1# ✅ GOOD: Deterministic nonce (RFC 6979)
2def generate_nonce_deterministic(private_key, message):
3 """Generate deterministic nonce (simplified RFC 6979)"""
4 h = sha256(private_key + message).digest()
5 k = int.from_bytes(h, 'big')
6 return k
7
8# ❌ BAD: Random nonce (can be manipulated in MPC)
9def generate_nonce_random():
10 return secrets.randbelow(curve_order)
2. Malicious Parties
Problem: Adversary can manipulate protocol to learn secrets
Solutions:
- Verifiable Secret Sharing (VSS): Prove shares are correct
- Zero-Knowledge Proofs: Prove correctness without revealing secrets
- Abort on inconsistency: Detect and stop malicious behavior
3. Communication Security
1✅ DO:
2- Use authenticated channels
3- Encrypt all communications
4- Verify party identities
5- Log all protocol messages
6
7❌ DON'T:
8- Send shares in plaintext
9- Skip authentication
10- Ignore protocol deviations
Use Cases
1. Cryptocurrency Custody
1Problem: Single private key is single point of failure
2
3Solution: (2, 3) threshold signature
4- 3 key shares distributed
5- Any 2 can sign transactions
6- Private key never exists!
7- On-chain: looks like regular transaction
2. Organizational Signing
1Company code signing key:
2- (3, 5) threshold among executives
3- No single person can sign
4- Quorum required for releases
5- Key never assembled
3. Decentralized Oracles
1Oracle network:
2- (t, n) threshold signature
3- t nodes must agree on data
4- Single signature on-chain
5- Gas efficient
4. Layer 2 Validators
1Rollup validators:
2- (2/3, n) threshold
3- 2/3 must sign state updates
4- Single signature to L1
5- Efficient verification
Comparison of Schemes
| Scheme | Rounds | Complexity | Aggregation | Use Case |
|---|---|---|---|---|
| Threshold ECDSA | 3-5 | Very High | Complex | Bitcoin, Ethereum (legacy) |
| Threshold Schnorr | 2-3 | Medium | Simple | Bitcoin (Taproot), modern chains |
| Threshold BLS | 1 | Low | Very Simple | Ethereum 2.0, Cosmos |
| Threshold EdDSA | 2 | Medium | Simple | Monero, general purpose |
Implementation Challenges
1. Round Complexity
Challenge: Network latency, failures Solution: Preprocessing, async protocols
2. Participant Availability
1Problem: Need t parties online simultaneously
2
3Solutions:
4- Preprocessing: Generate nonces offline
5- Async protocols: Parties contribute when available
6- Backup shares: Rotate participants
3. State Management
1class ThresholdSigningSession:
2 def __init__(self, session_id, threshold, participants):
3 self.session_id = session_id
4 self.threshold = threshold
5 self.participants = participants
6 self.round = 0
7 self.commitments = {}
8 self.shares = {}
9 self.partial_sigs = {}
10
11 def advance_round(self):
12 """Move to next round after validation"""
13 if len(self.get_current_round_data()) >= self.threshold:
14 self.round += 1
15 return True
16 return False
17
18 def get_current_round_data(self):
19 """Get data for current round"""
20 if self.round == 1:
21 return self.commitments
22 elif self.round == 2:
23 return self.shares
24 elif self.round == 3:
25 return self.partial_sigs
Best Practices
Setup Phase
1✅ Use trusted DKG ceremony or secure MPC
2✅ Verify all shares before accepting
3✅ Test signing with small amounts first
4✅ Document threshold and participants
5✅ Establish communication protocols
6✅ Plan for participant rotation
Operational Phase
1✅ Authenticate all participants
2✅ Encrypt all communications
3✅ Validate each round before proceeding
4✅ Log all protocol messages
5✅ Monitor for malicious behavior
6✅ Have abort procedures
Recovery Phase
1✅ Plan for participant unavailability
2✅ Have backup communication channels
3✅ Document recovery procedures
4✅ Test recovery regularly
Production Libraries
Threshold ECDSA
1- tss-lib (Binance): Go implementation
2- multi-party-ecdsa (ZenGo): Rust implementation
3- threshold-crypto: Rust BLS implementation
Example: Using tss-lib
1import (
2 "github.com/binance-chain/tss-lib/ecdsa/keygen"
3 "github.com/binance-chain/tss-lib/ecdsa/signing"
4)
5
6// Key generation
7parties := []*tss.PartyID{...}
8params := tss.NewParameters(curve, ctx, partyID, parties, threshold, len(parties))
9outCh := make(chan tss.Message, len(parties))
10endCh := make(chan keygen.LocalPartySaveData, 1)
11
12party := keygen.NewLocalParty(params, outCh, endCh)
13go func() {
14 if err := party.Start(); err != nil {
15 // Handle error
16 }
17}()
18
19// Signing
20msg := []byte("message to sign")
21signingParams := tss.NewParameters(curve, ctx, partyID, parties, threshold, len(parties))
22signingParty := signing.NewLocalParty(msg, signingParams, key, outCh, endCh)
Further Reading
- ECDSA Threshold Signatures (GG20)
- FROST: Flexible Round-Optimized Schnorr Threshold
- BLS Threshold Signatures
- TSS for Cryptocurrency Wallets
Related Snippets
- Asymmetric Encryption & Key Exchange
Asymmetric (public-key) cryptography with mathematical foundations, including … - Cryptographic Hash Functions
Cryptographic hash functions with mathematical properties and practical … - Digital Signatures
Digital signature algorithms with mathematical foundations. Mathematical … - Encrypt/Decrypt with Key Pairs
Encrypt and decrypt data using public/private key pairs and derive symmetric … - Generate Public/Private Key Pairs
Generate public/private key pairs on Linux for various cryptographic purposes. … - Hash and Sign Text with Key Pairs
Hash and digitally sign text using public/private key pairs. Hash Text (OpenSSL) … - Homomorphic Encryption Schemes
Homomorphic encryption allows computation on encrypted data without decryption, … - Key Derivation Functions
Key Derivation Functions (KDFs) for password hashing and key derivation. … - Key Sharding (Secret Sharing)
Key sharding splits a secret into multiple shares where a threshold of shares is … - Multi-Signature (Multisig) Schemes
Multi-signature schemes require multiple parties to sign a transaction or … - PGP Signature Operations
PGP/GPG signature operations for files, emails, and git commits. Generate GPG … - Setup PGP with Git (Auto-sign Commits)
Setup GPG/PGP to automatically sign Git commits and tags. Generate GPG Key for … - Symmetric Encryption
Symmetric encryption algorithms with mathematical foundations and practical …