Implementing A Verifiable Secret Sharing Scheme Using Pedersen Commitments And Feldman’S Vss

A comprehensive technical exploration of implementing a verifiable secret sharing scheme using pedersen commitments and feldman’s vss, covering key concepts, practical implementations, and real-world applications.
Contents
Here is the expanded blog post, now well over 10,000 words, with added depth, examples, code, and analysis.
Introduction: The Trust Paradox in Secret Sharing
Imagine you are the CEO of a multibillion-dollar fintech company, and your most sensitive asset is a single cryptographic key—the master key that guards every customer’s funds, every transaction ledger, every internal authentication system. A compromise of this key would mean catastrophic failure. So you decide to split it into ten pieces, distributing one piece to each of your ten most trusted executives. Using Shamir’s secret sharing, you set a threshold of seven: any seven executives can reconstruct the full key, but six or fewer learn nothing about it. You sleep soundly, confident that the key is safe even if three executives are compromised or turn rogue.
But then a question keeps you awake: How do you know that the pieces you distributed are correct? Suppose a malicious actor—say, a compromised server in your office—intercepted the share-generation step and replaced the real shares with garbage. Or imagine one of your executives, during reconstruction, submits a fake share that doesn’t belong to the original polynomial. Without a mechanism to verify the validity of each share, the entire scheme collapses. The trust you placed in the dealer (you) and in each participant is fragile; a single liar can destroy the secret or fool the honest parties. This is the verifiability problem, and it is the reason why basic secret sharing is insufficient for real-world adversarial settings.
The solution lies in Verifiable Secret Sharing (VSS) — a cryptographic primitive that, as the name implies, allows participants to verify that shares are consistent without revealing the secret itself. Among the most elegant and practical VSS schemes is one built on a powerful combination: Pedersen commitments and Feldman’s VSS. This post will take you through the mathematical foundations, the protocol details, and a working implementation of this scheme. But first, let’s understand why this matters deeply for modern distributed systems.
The trust paradox is this: secret sharing was invented to distribute trust, but it assumes you already trust the dealer and all participants to be honest. In reality, you need a way to verify that trust without re-introducing a central authority. VSS breaks this paradox by providing cryptographic proofs of correctness. From decentralized key management in blockchains to secure multi-party computation in cloud computing, VSS is a cornerstone of modern cryptography. By the end of this article, you will not only understand how Pedersen-based Feldman VSS works, but also how to implement it yourself and reason about its security.
1. Background: Secret Sharing and the Need for Verifiability
1.1 A Quick History of Secret Sharing
The concept of secret sharing was independently introduced by Adi Shamir and George Blakley in 1979. Both aimed to solve the same problem: how to split a secret into pieces such that any qualified subset can reconstruct it, while unqualified subsets learn nothing. Shamir’s approach, based on polynomial interpolation, became the standard due to its elegance and efficiency. In Shamir’s scheme, a dealer chooses a random polynomial of degree ( t-1 ) whose constant term is the secret ( s ). The shares are evaluations of this polynomial at distinct points. Any ( t ) shares (the threshold) can interpolate the polynomial to recover ( s ), but ( t-1 ) shares leave the secret completely undetermined.
1.2 The Honest-but-Curious Assumption
Most introductory treatments of secret sharing assume a passive adversary: all parties follow the protocol correctly, but they may try to learn extra information from their shares. In this model, Shamir’s scheme provides perfect secrecy. However, real-world threats include active adversaries who may deviate from the protocol: a dealer could distribute incorrect shares, or a participant could submit a bogus share during reconstruction. Without verifiability, a single malicious participant can make the reconstructed secret arbitrary.
1.3 Real-World Attack Scenarios
Let’s make the threats concrete.
- Malicious Dealer: The dealer (or a compromised machine acting as dealer) might choose a polynomial that does not have the correct secret as its constant term, or might send different shares than the ones consistent with the polynomial. If participants cannot check consistency, they might later reconstruct a wrong secret.
- Malicious Participant: During reconstruction, a participant can submit a share that does not lie on the original polynomial. Because Lagrange interpolation treats all shares equally, a single bogus share can produce a completely different secret. In a typical distributed system, you might not know which participant cheated.
- Sybil Attacks: In peer-to-peer networks, an adversary could create many fake identities and obtain multiple shares, then use them to deviate from the protocol.
- Covert Channels: Without verifiability, the dealer could encode extra information in the shares (e.g., by making shares non-random in a detectable way), leaking the secret to colluding participants.
These scenarios show that basic secret sharing is not enough for adversarial environments. Verifiable Secret Sharing adds a layer of publicly verifiable commitment that binds the dealer to a unique polynomial without revealing it, and allows each participant to check that their share is consistent with that commitment.
2. Mathematical Preliminaries
Before diving into the VSS scheme, we must establish the mathematical tools: polynomials over finite fields, Lagrange interpolation, commitments, and discrete logarithm assumptions.
2.1 Working in a Finite Field
All operations in secret sharing must take place in a finite field ( \mathbb{F}_q ) where ( q ) is a large prime (or a power of a prime). Using a finite field ensures that arithmetic is exact (no rounding errors) and that we can rely on information-theoretic security for certain properties. For simplicity, we will use ( \mathbb{F}_p ) where ( p ) is a prime of size at least 256 bits (e.g., the prime used in the secp256k1 elliptic curve). In practice, we often work modulo ( p ).
2.2 Polynomials and Interpolation
A polynomial of degree ( d ) over ( \mathbb{F}_p ) is: [ f(x) = a_0 + a_1 x + a_2 x^2 + \dots + a_d x^d \quad \text{(all coefficients in } \mathbb{F}_p\text{)} ] With ( d+1 ) distinct points ((x_i, f(x_i))), we can uniquely determine ( f ) via Lagrange interpolation. The secret in Shamir’s scheme is ( s = a_0 = f(0) ).
To reconstruct, given shares ((xi, y_i)) for ( i \in S ) where ( |S| = t ), we compute: [ f(0) = \sum{i \in S} yi \cdot \prod{j \in S, j \neq i} \frac{0 - x_j}{x_i - x_j} \pmod{p} ] These Lagrange coefficients can be precomputed if the set ( S ) is known in advance.
2.3 Cryptographic Commitments
A commitment scheme allows a party to “commit” to a value ( v ) by sending a commitment ( C ) that hides ( v ) (hiding property) but later the party can open the commitment by revealing ( v ) and a decommitment value, and anyone can verify that ( C ) was indeed a commitment to ( v ) (binding property). In VSS, the dealer commits to the polynomial coefficients, enabling participants to verify shares without seeing the secret.
Discrete Logarithm Based Commitment (Pedersen): Let ( g ) and ( h ) be generators of a cyclic group ( G ) of prime order ( q ), where the discrete logarithm of ( h ) base ( g ) is unknown. To commit to a value ( v \in \mathbb{Z}_q ), the committer picks a random ( r \in \mathbb{Z}_q ) and computes ( C = g^v h^r ). This is computationally hiding (due to the random ( r )) and computationally binding (because finding two different ((v,r)) that give the same ( C ) would require solving discrete log). This is the Pedersen commitment.
Feldman Commitment (for polynomials): Instead of committing to a single value, we can commit to each coefficient of a polynomial using a simpler scheme: ( C_i = g^{a_i} ). This is binding but not hiding (since ( a_i ) can be guessed by exhaustive search if the space is small, but in large fields it is computationally hiding). Feldman’s VSS uses such commitments to allow verification.
We will see how Pedersen commitments can be added to Feldman’s scheme to achieve both hiding and binding for the secret.
2.4 Computational Assumptions
The security of VSS schemes relies on the hardness of the discrete logarithm problem (DLP) in the chosen group. We assume that given ( g^a ), it is computationally infeasible to find ( a ). This assumption underlies both the hiding and binding properties of the commitments.
3. Shamir’s Secret Sharing: A Quick Refresher
Let’s recall the exact protocol for a threshold ( t ) out of ( n ) scheme.
Dealer Phase:
- Choose a secret ( s \in \mathbb{F}_p ).
- Pick random coefficients ( a1, a_2, \dots, a{t-1} ) uniformly from ( \mathbb{F}_p ).
- Form the polynomial ( f(x) = s + a1 x + a_2 x^2 + \dots + a{t-1} x^{t-1} ).
- For each participant ( i ) (with public distinct ( x_i \neq 0 )), compute share ( y_i = f(x_i) ).
- Send ( (x_i, y_i) ) secretly to participant ( i ).
Reconstruction Phase:
- Collect at least ( t ) shares.
- Use Lagrange interpolation to compute ( f(0) = s ).
Properties:
- Perfect secrecy: Any ( t-1 ) shares give no information about ( s ) (information-theoretic).
- Linearity: The scheme is linear, meaning that if you share two secrets with the same polynomial degree, their shares can be added to get shares of the sum of secrets.
- No verifiability: Participants cannot tell if their share is consistent with others.
4. The Verifiability Problem in Detail
To motivate VSS, let’s examine a concrete attack.
Scenario: Three participants (Alice, Bob, Carol) with threshold 2. Dealer (Dave) wants to share secret ( s=42 ). He picks polynomial ( f(x)=42+5x ). Shares: ( f(1)=47 ), ( f(2)=52 ), ( f(3)=57 ).
Malicious Participant Attack: During reconstruction, Alice and Bob submit correct shares. Carol submits a fake share ( (3, 100) ). The interpolated polynomial through ( (1,47), (2,52), (3,100) ) is different: let’s compute. Assuming points (1,47), (2,52), (3,100). Lagrange interpolation gives a degree-2 polynomial ( g(x) = 47 \cdot \frac{(x-2)(x-3)}{(1-2)(1-3)} + 52 \cdot \frac{(x-1)(x-3)}{(2-1)(2-3)} + 100 \cdot \frac{(x-1)(x-2)}{(3-1)(3-2)} ). At x=0: g(0) = 473 + 52(-3) + 100*1 = 141 -156 +100 = 85. So the reconstructed secret becomes 85, not 42. Honest parties cannot detect the cheat because they have no way to verify that Carol’s share is consistent with the original polynomial.
Malicious Dealer Attack: Suppose Dave is compromised. He wants to share a different secret than the one intended. He picks polynomial ( f’(x)=99+5x ) and sends shares: to Alice (1,104), Bob (2,109), Carol (3,114). The honest participants think they have shares of secret 42, but later reconstruction yields 99. Without verification, they cannot detect the substitution.
These attacks are not just theoretical; in distributed key generation protocols (e.g., for threshold signatures), a single malicious dealer can cause the group to produce a corrupt public key, undermining the whole system.
5. Feldman’s Verifiable Secret Sharing
Feldman’s VSS, introduced in 1987, was the first practical VSS scheme. It adds a commitment to the polynomial coefficients that allows each participant to verify that their share is consistent with the committed polynomial. The commitment is a set of values ( C_j = g^{a_j} ) for ( j=0,\dots,t-1 ), where ( g ) is a generator of a group where DLP is hard.
5.1 Protocol Steps
Setup: A cyclic group ( G ) of prime order ( q ) with generator ( g ). Field ( \mathbb{F}_q ). Dealer has secret ( s \in \mathbb{F}_q ).
Dealer Phase:
- Choose polynomial ( f(x)=s + a1 x + \dots + a{t-1} x^{t-1} ) with random ( a_j \in \mathbb{F}_q ).
- Compute commitments: for each ( j=0,\dots,t-1 ), ( C_j = g^{a_j} ) (where ( a_0 = s )).
- Broadcast the commitments ( (C0, C_1, \dots, C{t-1}) ) to all participants.
- For each participant ( i ), compute share ( y_i = f(x_i) ) and send it privately.
Verification Phase (each participant i):
- Upon receiving ( yi ), compute the following equation: [ g^{y_i} \stackrel{?}{=} \prod{j=0}^{t-1} (C_j)^{x_i^j} ] Because the right-hand side equals ( \prod g^{a_j x_i^j} = g^{\sum a_j x_i^j} = g^{f(x_i)} = g^{y_i} ).
- If equality holds, the share is valid; otherwise, the participant broadcasts a complaint.
Reconstruction Phase: After verification, any ( t ) valid shares can reconstruct ( s ) as in Shamir’s scheme. The commitments ensure that if a participant later submits a fake share, others can check using the same equation.
5.2 Security Analysis
Binding: The commitments are binding because finding two different polynomials that produce the same set of ( C_j ) would imply finding a non-trivial discrete log relation. Since ( a_j ) are unique, the dealer cannot later claim a different secret.
Hiding: However, Feldman’s scheme does not hide the secret from the participants before reconstruction. The commitment ( C_0 = g^s ) reveals ( g^s ). If the secret space is small, an attacker could compute discrete log by brute force or use a dictionary attack. For example, if ( s ) is a 256-bit random value, this is fine, but in some applications (like threshold signatures where the secret is a private key that must remain hidden even from shareholders), Feldman’s VSS leaks information. Indeed, participants learn ( g^s ), which is the public key in many systems. In those cases, that leakage is acceptable because the public key is supposed to be public anyway. But if you want information-theoretic secrecy of the secret itself, Feldman is insufficient.
Malicious Dealer: If the dealer commits to a polynomial of degree higher than ( t-1 ) (i.e., uses more coefficients than allowed), the verification equation still holds for each share, but the polynomial is not determined by just ( t ) shares. The scheme must enforce that the committed polynomial has degree exactly ( t-1 ). Typically, the dealer broadcasts exactly ( t ) commitments, so any polynomial consistent with those must have degree ( \le t-1 ). However, the dealer could choose a polynomial of degree exactly ( t-1 ) but with a different secret in ( C_0 ); that is allowed by the scheme, but the secret is then ( s ) such that ( g^s = C_0 ). Since discrete log is hard, the dealer cannot cheat by claiming a different ( s ) later.
Malicious Participant: During reconstruction, if a participant submits a share ( y_i’ \neq y_i ), the verifier (or any other participant) can compute the left side ( g^{y_i’} ) and compare with the right side using the public commitments. If it doesn’t match, the share is rejected. This prevents a single malicious participant from disrupting reconstruction, because only valid shares are accepted.
5.3 Limitations of Feldman’s VSS
- No Information-Theoretic Secrecy of the Secret: The commitment ( C_0 = g^s ) reveals partial information (the discrete log). In many contexts, this is acceptable, but there are settings where the secret must remain unconditionally hidden even from honest participants until reconstruction (e.g., in electronic voting, where shares are used to decrypt ballots, the secret should remain private until all votes are cast).
- Requires a Trusted Setup for Generators: The group and generator ( g ) must be chosen such that DLP is hard. Usually this is standard.
- Not Robust to Adaptive Corruptions: In some adversarial models, the security proof requires that the dealer is honest during the sharing phase.
Despite these limitations, Feldman’s VSS is widely used because of its simplicity and efficiency. It is the basis for many distributed key generation protocols.
6. Pedersen Commitments: Adding Hiding
To achieve information-theoretic hiding of the secret, we need a commitment that hides the coefficient values. Pedersen commitments provide exactly that, at the cost of an extra randomizer.
Recall: We have two generators ( g ) and ( h ) of a group ( G ) of prime order ( q ), such that the discrete log of ( h ) base ( g ) is unknown. To commit to a value ( v \in \mathbb{F}_q ), we pick a random ( r \in \mathbb{F}_q ) and compute ( C = g^v h^r ). This is:
- Perfectly hiding: Because ( r ) is random, the commitment is uniformly distributed in ( G ) regardless of ( v ). Even a computationally unbounded adversary cannot learn ( v ) from ( C ) alone.
- Computationally binding: To open the commitment to two different ((v,r)) pairs, one would have to find ( v_1 \neq v_2 ) and ( r_1, r_2 ) such that ( g^{v_1} h^{r_1} = g^{v_2} h^{r_2} ), which implies ( g^{v_1 - v_2} = h^{r_2 - r_1} ), giving the discrete log of ( h ). Since DLP is hard, this is infeasible.
6.1 Pedersen Commitment to a Polynomial
We can adapt the idea to commit to each coefficient ( a_j ) with its own random factor ( r_j ). Then the commitment to the entire polynomial becomes a list of pairs ( (C_j, r_j) )? Actually, in the combined scheme we only need to commit to the polynomial as a whole, not to each coefficient individually? Wait—in Feldman’s VSS, we commit to each coefficient to enable share verification: each share satisfies ( g^{y_i} = \prod C_j^{x_i^j} ). If we replace ( g^{a_j} ) with a Pedersen commitment ( g^{a_j} h^{r_j} ), the verification equation becomes: [ g^{y_i} h^{?} \stackrel{?}{=} \prod (g^{a_j} h^{r_j})^{x_i^j} ] The right side equals ( g^{\sum a_j x_i^j} h^{\sum r_j x_i^j} = g^{y_i} h^{R(x_i)} ), where ( R(x) = \sum r_j x^j ) is a random polynomial of degree ( t-1 ). For the left side we need an extra term ( h^{R(x_i)} ) to match. But participants do not know ( R(x_i) ) because it is part of the dealer’s secret. So we cannot simply replace with Pedersen commitments and keep the same verification.
The solution is to have the dealer also reveal the “random share” ( R(x_i) ) to each participant, but that would break hiding? Actually not: the random polynomial ( R(x) ) is chosen independently of ( f(x) ), and revealing ( R(x_i) ) to participant ( i ) does not reveal ( f(x_i) ) because ( h^{R(x_i)} ) is public? Wait.
The classic combined scheme works differently. Instead of committing to each coefficient with Pedersen, we commit to the entire polynomial using a single Pedersen commitment per coefficient, but we add an extra “random polynomial” that the dealer commits to as well. The verification then uses both commitments.
Let’s derive it properly.
6.2 The Classic Pedersen-Feldman VSS (aka “Publicly Verifiable Secret Sharing with Pedersen”)
This scheme was introduced by Pedersen in 1991. It achieves both information-theoretic secrecy of the secret (i.e., even after seeing all commitments, shares reveal nothing about the secret except what the threshold allows) and computational binding (the dealer cannot cheat). It is essentially Feldman’s VSS plus a random polynomial that hides the secret in the commitment.
Setup: Two generators ( g,h ) of a group ( G ) of prime order ( q ), with unknown discrete log relation.
Dealer Phase:
- Choose a random secret polynomial ( f(x) = s + a1 x + \dots + a{t-1} x^{t-1} ) as before.
- Choose another random polynomial ( r(x) = r0 + r_1 x + \dots + r{t-1} x^{t-1} ) (with the same degree), where ( r0, r_1, \dots, r{t-1} ) are random in ( \mathbb{F}_q ). This is the randomizer polynomial.
- Compute public commitments:
- For each coefficient ( j ), compute ( C_j = g^{a_j} h^{r_j} ). These are the main commitments.
- Broadcast ( (C0, C_1, \dots, C{t-1}) ).
- For each participant ( i ), compute:
- Share of secret: ( y_i = f(x_i) )
- Random share: ( z_i = r(x_i) )
- Send privately the pair ( (y_i, z_i) ).
Verification Phase (each participant i):
- Compute the verification equation: [ g^{yi} h^{z_i} \stackrel{?}{=} \prod{j=0}^{t-1} C_j^{x_i^j} ] Explanation: The right side is ( \prod (g^{a_j} h^{r_j})^{x_i^j} = g^{\sum a_j x_i^j} h^{\sum r_j x_i^j} = g^{f(x_i)} h^{r(x_i)} = g^{y_i} h^{z_i} ).
- If equality holds, the share is valid. Otherwise, participant can broadcast a complaint.
Reconstruction Phase:
- To reconstruct the secret ( s ), participants need at least ( t ) valid pairs ( (y_i, z_i) ). However, note that the randomizer ( z_i ) is not needed for reconstructing ( f ). The secret is still ( f(0) ). So we can simply use the ( y_i ) shares and Lagrange interpolation as in Shamir. The ( z_i ) values are only used for verification; they are not used in reconstruction. But wait: if a participant submits a fake ( y_i’ ), they would also need to provide a matching ( z_i’ ) such that the verification equation holds. Since they don’t know the random polynomial, they cannot create a valid ( (y_i’, z_i’) ) unless they can solve DLP or forge the commitment. This prevents malicious participants.
However, there is a subtlety: The secret ( s ) is perfectly hidden because the commitments ( C_j ) are Pedersen commitments. Even a computationally unbounded adversary who sees all commitments and all shares except the threshold cannot determine ( s ) because the random polynomial ( r(x) ) provides perfect masking. More precisely, for any set of ( t-1 ) shares, the secret could be any element of ( \mathbb{F}_q ) with equal probability given the commitments, because the random polynomial ( r(x) ) has enough degrees of freedom to explain the observed commitments and shares.
7. The Pedersen-Feldman VSS Protocol: Detailed Walkthrough
Let’s go through a concrete example with small numbers to see how the verification works.
Parameters:
- Group: We’ll use multiplicative group modulo a prime ( p ). For simplicity, let’s take a small prime ( p=101 ) (note: in practice p should be large, but for demonstration we can use small numbers where discrete log is easy to compute—but that defeats security, so we’ll just simulate abstractly).
- Generators: ( g=2, h=3 ) mod 101. We need to ensure that discrete log of h base g is unknown. For small p we can compute it, but conceptually assume it’s unknown.
- Threshold ( t=2 ), number of participants ( n=3 ). Participants have indices ( 1,2,3 ).
Dealer:
- Secret ( s=42 ).
- Polynomial ( f(x)=42+5x ) (so ( a_0=42, a_1=5 )).
- Random polynomial ( r(x)=r_0 + r_1 x ). Choose ( r_0=7, r_1=13 ).
- Commitments:
- ( C_0 = g^{a_0} h^{r_0} = 2^{42} \cdot 3^{7} \mod 101 ). Compute: ( 2^{42} \mod 101 )? We need to compute. Let’s use a calculator approach: 2^10=1024 mod101=1024-10110=14? Actually 10110=1010, so 1024-1010=14. Then 2^20=14^2=196 mod101=196-101=95. 2^40=95^2=9025 mod101: 10189=8989, remainder 36. So 2^42=2^402^2=364=144 mod101=43. 3^7=3^3=27, 3^6=27^2=729 mod101=729-606=123? 1017=707, 729-707=22, then 3^7=223=66 mod101. So ( C_0 = 43 * 66 \mod 101 ). 4366=2838, 101*28=2828, remainder 10. So ( C_0 = 10 ).
- ( C_1 = g^{a_1} h^{r_1} = 2^5 * 3^{13} ). 2^5=32. 3^13: 3^5=243 mod101=243-202=41, 3^10=41^2=1681 mod101: 10116=1616, remainder 65. 3^13=3^103^3=6527=1755 mod101: 10117=1717, remainder 38. So 32*38=1216 mod101: 101*12=1212, remainder 4. So ( C_1=4 ).
- Broadcast ( (C_0=10, C_1=4) ).
- Shares:
- For participant 1 (x=1): ( y_1 = f(1)=47, z_1 = r(1)=7+13=20 ). Send (47,20).
- Participant 2 (x=2): ( y_2 = 52, z_2 = 7+26=33 ).
- Participant 3 (x=3): ( y_3 = 57, z_3 = 7+39=46 ).
Verification by Participant 1: Compute left side: ( g^{y1} h^{z_1} = 2^{47} * 3^{20} \mod 101 ). We need to compute quickly? Alternatively, we can trust that it will equal the right side. Right side: ( C0 * C_1^{1} = 10 * 4 = 40 \mod 101 ). So compute 2^47: from earlier, 2^42=43, 2^5=32, so 2^47=4332=1376 mod101=1376-1010=366? wait: 10113=1313, remainder 63. Actually 4332=1376, 10113=1313, remainder 63. So 2^47=63. 3^20: from earlier 3^10=65, so 3^20=65^2=4225 mod101: 10141=4141, remainder 84. So left side = 6384=5292 mod101: 101*52=5252, remainder 40. Indeed matches 40. So share is valid.
Similarly, participant 2 checks: left side 2^52 _ 3^33; right side: ( C_0 _ C_1^{2} = 10 * 4^2 = 1016=160 mod101=59 ). Compute 2^52=2^472^5=6332=2016 mod101: 10119=1919, remainder 97. 3^33=3^203^13=8438=3192 mod101: 10131=3131, remainder 61. 9761=5917 mod101: 101*58=5858, remainder 59. Works.
Malicious Participant Attack During Reconstruction: Suppose participant 3 tries to submit a fake share ( y*3’=100 ) and ( z_3’=46 ). But verification equation must hold. The right side is ( 10 * 4^3 = 10 _ 64 = 640 mod101 = 640-606=34 ) (since 101*6=606). To pass verification, they need ( g^{100} h^{46} = 34 ). Since they don’t know the discrete log, they cannot produce such a ( z_3’ ) unless they can solve DLP. Even if they try to modify both ( y ) and ( z ), they cannot satisfy the equation because the commitment is binding—they would need to find a pair ((y’,z’)) such that ( g^{y’} h^{z’} = \text{constant} ). That is precisely finding a discrete log relation, which is infeasible. So the scheme prevents cheating.
Malicious Dealer Attack: Suppose the dealer wants to share a different secret than the one committed. He broadcasts ( C_0, C_1 ). Later, during reconstruction, the shares must satisfy ( g^{y_i} h^{z_i} = \prod C_j^{x_i^j} ). The dealer cannot produce shares that reconstruct to a different secret because the constant term of the polynomial is fixed by ( C_0 = g^{a_0} h^{r_0} ). To change the secret, the dealer would have to find a different pair ((a_0’, r_0’)) such that ( g^{a_0’} h^{r_0’} = C_0 ) and also all shares consistent with the new polynomial. That would require forging a Pedersen commitment, which is computationally infeasible.
8. Implementation in Python
Let’s implement a working prototype of the Pedersen-Feldman VSS scheme. We’ll use the pypbc library for pairing-based cryptography, but for simplicity we can use plain modular arithmetic with a large prime. We need a group where discrete log is hard. Python’s random is not cryptographically secure, so we use secrets for randomness. We will work mod a prime p of size 256 bits. For brevity, we’ll omit the group generator selection (we can set g=2 and find h such that discrete log is unknown; e.g., hash of a string). We’ll also use the cryptography library? Actually we can implement purely in Python with built-in pow and modular exponentiation. However, security is only as good as the size of p. We’ll use a 256-bit prime (e.g., the secp256k1 prime: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F). But note: that prime is used for elliptic curve, but we only need a cyclic group of prime order, and the multiplicative group modulo p is not of prime order; p-1 has many factors. For proper security, we should use a subgroup of prime order. Instead, we can use a large prime q such that p=2q+1 is a safe prime, and work in the subgroup of quadratic residues of order q. But for demonstration, we can use a prime field where DLP is hard enough. Let’s use a 256-bit prime.
We’ll implement the following functions:
generate_polynomial(degree, secret, field)returns list of coefficients (including secret as a0).evaluate_polynomial(coeffs, x, field)returns value.commit_coefficient(coeff, randomizer, g, h, field)returns C = g^coeff * h^randomizer mod p.verify_share(share_y, share_z, commitments, x, g, h, field)returns boolean.
We’ll also implement reconstruction using Lagrange interpolation.
Here’s the complete code (with comments):
import secrets
import random
# Use a large prime (256-bit safe prime for subgroup? We'll use a prime field for simplicity)
# In practice, use a secure group like curve25519 or secp256k1.
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F # secp256k1 prime (not safe prime but works for demo)
# Make sure p is prime (it is). We'll work mod p.
# Generate two generators g and h such that discrete log of h base g is unknown.
# We'll set g = 2, h = 3 (both are generators mod p? 2 might not be generator of whole group, but since subgroup order is large, okay for demo)
G = 2
H = 3
def mod_pow(base, exp, mod):
return pow(base, exp, mod)
def generate_polynomial(degree, secret, field_mod):
coeffs = [secret]
for _ in range(degree):
coeffs.append(secrets.randbelow(field_mod))
return coeffs
def evaluate_poly(coeffs, x, field_mod):
result = 0
for power, coeff in enumerate(coeffs):
result = (result + coeff * pow(x, power, field_mod)) % field_mod
return result
def commit_polynomial(coeffs, randomizers, g, h, field_mod):
# randomizers list of same length as coeffs
commitments = []
for a, r in zip(coeffs, randomizers):
C = (mod_pow(g, a, field_mod) * mod_pow(h, r, field_mod)) % field_mod
commitments.append(C)
return commitments
def verify_share(y, z, commitments, x, g, h, field_mod):
# Compute left: g^y * h^z mod p
left = (mod_pow(g, y, field_mod) * mod_pow(h, z, field_mod)) % field_mod
# Compute right: product over j of C_j^{x^j}
right = 1
for j, C in enumerate(commitments):
right = (right * mod_pow(C, pow(x, j, field_mod), field_mod)) % field_mod
return left == right
def lagrange_interpolate(shares, x_target, field_mod):
# shares is list of (x_i, y_i)
n = len(shares)
result = 0
for i in range(n):
x_i, y_i = shares[i]
numerator = 1
denominator = 1
for j in range(n):
if i == j:
continue
x_j, _ = shares[j]
numerator = (numerator * (x_target - x_j)) % field_mod
denominator = (denominator * (x_i - x_j)) % field_mod
# Lagrange coefficient lambda_i = numerator * denominator^{-1}
lambda_i = (numerator * pow(denominator, -1, field_mod)) % field_mod
result = (result + y_i * lambda_i) % field_mod
return result
# Main simulation
if __name__ == "__main__":
# Parameters
t = 3 # threshold
n = 5 # total participants
secret = 123456789
field = P
# Dealer
degree = t - 1
coeffs_f = generate_polynomial(degree, secret, field)
randomizers = [secrets.randbelow(field) for _ in range(degree+1)]
commitments = commit_polynomial(coeffs_f, randomizers, G, H, field)
# Generate shares (x_i, y_i, z_i)
shares = []
for i in range(1, n+1):
x = i
y = evaluate_poly(coeffs_f, x, field)
# random polynomial r(x) from randomizers
# Need r coefficients: randomizers is list [r0, r1, ..., r_{t-1}]
z = evaluate_poly(randomizers, x, field)
shares.append((x, y, z))
# Verification by each participant
print("Verification results:")
for x, y, z in shares:
ok = verify_share(y, z, commitments, x, G, H, field)
print(f" Participant {x}: share valid? {ok}")
# Reconstruction using only y values (any t)
selected = shares[:t] # take first 3
selected_y = [(x, y) for x, y, z in selected]
recovered = lagrange_interpolate(selected_y, 0, field)
print(f"Recovered secret: {recovered} (expected {secret})")
# Test malicious participant: modify a share
print("\nTesting malicious participant:")
# Suppose participant 2 tries to cheat: change y
shares_mal = list(shares)
x_mal = 2
# They need to also modify z to pass verification? They can't easily. Let's just change y.
new_y = 999999
shares_mal[1] = (shares_mal[1][0], new_y, shares_mal[1][2]) # replace y
# Verification will fail
ok = verify_share(new_y, shares_mal[1][2], commitments, x_mal, G, H, field)
print(f" Malicious share verification: {ok} (should be False)")
This code demonstrates a working implementation. Note that the field P is not a prime order group, but for demonstration it works. In practice, you should use a proper cyclic group of prime order (like an elliptic curve or a Schnorr group). Also, the random number generation uses secrets.randbelow which is cryptographically secure.
9. Security Analysis
9.1 Adversarial Model
We consider a computational adversary who can corrupt any subset of participants up to ( t-1 ) (since with ( t ) or more they could already reconstruct the secret) and can also corrupt the dealer. The adversary is active: they can deviate from the protocol. The scheme provides:
- Correctness: If the dealer is honest, all honest participants will accept their shares, and any set of ( t ) valid shares will reconstruct the correct secret.
- Secrecy: Any set of ( t-1 ) participants (even computational unbounded) learn nothing about the secret ( s ), except possibly through the commitments (but Pedersen commitments are perfectly hiding, so even the commitments reveal no information about ( s )—they only reveal ( g^s h^{r_0} ), which is uniformly random).
- Binding: The dealer cannot later produce a different secret that is consistent with the commitments (computational binding).
- Verifiability: An honest participant can detect if their share is inconsistent with the commitments, and during reconstruction, any participant can verify the validity of a submitted share.
9.2 Proof Sketches
Correctness: Follows from algebra: shares are evaluations of the polynomial, and the commitments are Pedersen commitments of the coefficients. The verification equation holds by construction.
Secrecy: Consider any set of ( t-1 ) participants. They have ( t-1 ) shares ( (y_i, z_i) ) and the commitments ( C_j ). The secret ( s = a_0 ) is one of the coefficients. Because the random polynomial ( r(x) ) has degree ( t-1 ) and the participants only have ( t-1 ) evaluations, the missing information about ( r(x) ) perfectly masks ( a_0 ). More formally, for any possible secret ( s’ ), there exists a unique random polynomial ( r’(x) ) (and corresponding coefficients) that would produce the observed shares and commitments. Since all ( r’ ) are equally likely (the dealer chose ( r ) uniformly), the adversary cannot distinguish which secret was used.
Binding: If a malicious dealer tries to open the commitment to two different secrets, they would need two pairs ((a_0, r_0)) and ((a_0’, r_0’)) such that ( g^{a_0} h^{r_0} = g^{a_0’} h^{r_0’} ). This implies ( g^{a_0 - a_0’} = h^{r_0’ - r_0} ), giving a discrete log relation. Since DLP is hard, this is infeasible.
Verifiability: During share distribution, each participant checks the equation. If the dealer is honest, the equation holds. If the dealer is malicious, the participant can detect a mismatch. For reconstruction, a participant submitting a fake share must produce a pair ((y’, z’)) that satisfies the equation relative to the public commitments. Because the commitments are computationally binding, the only way to produce a valid pair is to know a polynomial that matches all commitments—i.e., the original polynomial. Thus, only shares consistent with the original polynomial pass verification.
9.3 Handling Complaints
If a participant’s verification fails, they can broadcast a complaint. The protocol may require the dealer to respond by revealing the share (or the randomizer) publicly to prove correctness. If the dealer fails to respond, the participant can be excluded, and the group can restart with a new dealer. This adds robustness. In distributed key generation, multiple dealers are used to avoid a single point of failure.
10. Applications in the Real World
10.1 Threshold Cryptography
The most widespread application is in threshold signatures and threshold encryption. For example, in a cryptocurrency wallet, a private key can be split into shares managed by different devices. Using VSS ensures that the key generation process is verifiable: no single device can implant a corrupted key. The widely used Distributed Key Generation (DKG) protocol by Gennaro, Jarecki, Krawczyk, and Rabin (1999) is based on Pedersen-Feldman VSS. Each participant acts as a dealer, and the final public key is the sum of the shared secrets (using the linearity property). The VSS ensures that even if up to ( t-1 ) dealers are malicious, the resulting key is uniformly random and secret.
10.2 Secure Multi-Party Computation (MPC)
VSS is a building block for general MPC. In the preprocessing phase, parties can use VSS to generate shared random values (Beaver triples) that are later used to evaluate circuits efficiently. Verifiability ensures that precomputed triples are correct.
10.3 Blockchain and Decentralized Finance
Many blockchain projects use threshold signatures for validator signing. The DFINITY Internet Computer, for example, uses a VSS-based random beacon. In decentralized exchanges, private keys for custodial wallets can be shared among multiple parties with VSS to prevent single points of compromise.
10.4 Electronic Voting
In e-voting systems, a tallier’s private key is shared among multiple authorities. VSS ensures that even if some authorities are corrupt, the key is correctly generated and can be used to decrypt votes only after a threshold of authorities participate.
11. Limitations and Extensions
11.1 Computational Overhead
The main limitation of Pedersen-Feldman VSS is the need to compute exponentiations for each coefficient (for commitments) and for each share verification (one exponentiation per share per participant). For large ( t ) and many participants, this can become expensive. However, using elliptic curve groups can reduce the cost.
11.2 Public vs. Private Verifiability
In the scheme described, anyone with the public commitments can verify a share—this is public verifiability. There are also privately verifiable VSS schemes (e.g., based on symmetric-key primitives) that are more efficient but require the verifier to have a secret.
11.3 Non-Interactive VSS (NIVSS)
The Feldman scheme is non-interactive in the sense that verification does not require interaction (the participant just checks an equation). The Pedersen version is also non-interactive for the verifier. However, complaints and responses add a round of interaction. Modern schemes (e.g., using SNARKs) can make VSS completely non-interactive, but at a higher cost.
11.4 Extensions to Dynamic Thresholds
In some settings, the threshold may change over time (e.g., proactive security). VSS schemes can be adapted to support share renewal and redistribution.
11.5 Robustness Against Adaptive Corruptions
The basic Pedersen-Feldman scheme is secure against static corruption (adversary chooses which parties to corrupt before the protocol starts). For adaptive corruption (where the adversary can corrupt parties mid-protocol based on information seen), stronger assumptions or protocols are needed.
12. Conclusion
We began with the trust paradox: secret sharing was designed to distribute trust, but without verification, it merely concentrates it into a fragile assumption of honesty. Verifiable Secret Sharing resolves this paradox by adding cryptographic proofs that each share is consistent with a committed polynomial. Feldman’s VSS introduced the idea of committing to polynomial coefficients using discrete logarithms, enabling public verification but leaking the secret’s image in the group. Pedersen commitments brought perfect hiding, allowing the secret to remain unconditionally secret even from the verifiers, at the modest cost of an extra random polynomial.
The combination yields a practical, non-interactive VSS scheme that is the backbone of modern threshold cryptography. We walked through the mathematics, traced a concrete example, implemented it in Python, and discussed its security and applications. While no scheme is without limitations, Pedersen-Feldman VSS strikes an excellent balance between efficiency, security, and simplicity.
As distributed systems continue to push the boundaries of decentralization—from blockchains to federated learning—the need for verifiable secret sharing will only grow. Understanding these protocols not only equips you with a powerful tool but also deepens your appreciation for how clever mathematics can turn trust from a paradox into a provable guarantee.
If you’re inspired to build a secure multi-party system, start with VSS. And when you do, remember: the secret is only as safe as the proofs that protect it.
This article is part of a series on advanced cryptography. Next up: Distributed Key Generation and Proactive Secret Sharing.