Skip to content

Commit 9c3d94b

Browse files
committed
Implements Shamir and Feldman secret sharing.
1 parent c56c51d commit 9c3d94b

File tree

4 files changed

+360
-0
lines changed

4 files changed

+360
-0
lines changed

math/polynomial/polynomial.go

+12
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ func New(coeffs []group.Scalar) (p Polynomial) {
3333
return
3434
}
3535

36+
// Degree returns the degree of the polynomial. The zero polynomial has degree
37+
// equal to -1.
3638
func (p Polynomial) Degree() int {
3739
i := len(p.c) - 1
3840
for i > 0 && p.c[i].IsZero() {
@@ -41,6 +43,7 @@ func (p Polynomial) Degree() int {
4143
return i
4244
}
4345

46+
// Evaluate returns the evaluation of p on x.
4447
func (p Polynomial) Evaluate(x group.Scalar) group.Scalar {
4548
px := x.Group().NewScalar()
4649
if l := len(p.c); l != 0 {
@@ -53,6 +56,15 @@ func (p Polynomial) Evaluate(x group.Scalar) group.Scalar {
5356
return px
5457
}
5558

59+
// Coefficient returns a deep-copy of the n-th polynomial's coefficient.
60+
// Note coefficients are sorted in ascending order with respect to the degree.
61+
func (p Polynomial) Coefficient(n uint) group.Scalar {
62+
if int(n) >= len(p.c) {
63+
panic("polynomial: invalid index for coefficient")
64+
}
65+
return p.c[n].Copy()
66+
}
67+
5668
// LagrangePolynomial stores a Lagrange polynomial over the set of scalars of a group.
5769
type LagrangePolynomial struct {
5870
// Internal representation is in Lagrange basis:

secretsharing/example_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package secretsharing_test
2+
3+
import (
4+
"crypto/rand"
5+
"fmt"
6+
7+
"github.com/cloudflare/circl/group"
8+
"github.com/cloudflare/circl/secretsharing"
9+
)
10+
11+
func ExampleSecretSharing() {
12+
g := group.P256
13+
t := uint(2)
14+
n := uint(5)
15+
16+
secret := g.RandomScalar(rand.Reader)
17+
ss := secretsharing.New(rand.Reader, t, secret)
18+
shares := ss.Share(n)
19+
20+
got, err := secretsharing.Recover(t, shares[:t])
21+
fmt.Printf("Recover secret: %v\nError: %v\n", secret.IsEqual(got), err)
22+
23+
got, err = secretsharing.Recover(t, shares[:t+1])
24+
fmt.Printf("Recover secret: %v\nError: %v\n", secret.IsEqual(got), err)
25+
// Output:
26+
// Recover secret: false
27+
// Error: secretsharing: number of shares (n=2) must be above the threshold (t=2)
28+
// Recover secret: true
29+
// Error: <nil>
30+
}
31+
32+
func ExampleVerify() {
33+
g := group.P256
34+
t := uint(2)
35+
n := uint(5)
36+
37+
secret := g.RandomScalar(rand.Reader)
38+
ss := secretsharing.New(rand.Reader, t, secret)
39+
shares := ss.Share(n)
40+
coms := ss.CommitSecret()
41+
42+
for i := range shares {
43+
ok := secretsharing.Verify(t, shares[i], coms)
44+
fmt.Printf("Share %v is valid: %v\n", i, ok)
45+
}
46+
47+
got, err := secretsharing.Recover(t, shares)
48+
fmt.Printf("Recover secret: %v\nError: %v\n", secret.IsEqual(got), err)
49+
// Output:
50+
// Share 0 is valid: true
51+
// Share 1 is valid: true
52+
// Share 2 is valid: true
53+
// Share 3 is valid: true
54+
// Share 4 is valid: true
55+
// Recover secret: true
56+
// Error: <nil>
57+
}

secretsharing/ss.go

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Package secretsharing provides methods to split secrets into shares.
2+
//
3+
// Let n be the number of parties, and t the number of corrupted parties such
4+
// that 0 <= t < n. A (t,n) secret sharing allows to split a secret into n
5+
// shares, such that the secret can be recovered from any subset of at least t+1
6+
// different shares.
7+
//
8+
// A Shamir secret sharing [1] relies on Lagrange polynomial interpolation.
9+
// A Feldman secret sharing [2] extends Shamir's by committing the secret,
10+
// which allows to verify that a share is part of the committed secret.
11+
//
12+
// New returns a SecretSharing compatible with Shamir secret sharing.
13+
// The SecretSharing can be verifiable (compatible with Feldman secret sharing)
14+
// using the CommitSecret and Verify functions.
15+
//
16+
// In this implementation, secret sharing is defined over the scalar field of
17+
// a prime order group.
18+
//
19+
// References
20+
//
21+
// [1] Shamir, How to share a secret. https://dl.acm.org/doi/10.1145/359168.359176/
22+
// [2] Feldman, A practical scheme for non-interactive verifiable secret sharing. https://ieeexplore.ieee.org/document/4568297/
23+
package secretsharing
24+
25+
import (
26+
"fmt"
27+
"io"
28+
29+
"github.com/cloudflare/circl/group"
30+
"github.com/cloudflare/circl/math/polynomial"
31+
)
32+
33+
// Share represents a share of a secret.
34+
type Share struct {
35+
// ID uniquely identifies a share in a secret sharing instance. ID is never zero.
36+
ID group.Scalar
37+
// Value stores the share generated by a secret sharing instance.
38+
Value group.Scalar
39+
}
40+
41+
// SecretCommitment is the set of commitments generated by splitting a secret.
42+
type SecretCommitment = []group.Element
43+
44+
// SecretSharing provides a (t,n) Shamir's secret sharing. It allows splitting
45+
// a secret into n shares, such that the secret can be only recovered from
46+
// any subset of t+1 shares.
47+
type SecretSharing struct {
48+
g group.Group
49+
t uint
50+
poly polynomial.Polynomial
51+
}
52+
53+
// New returns a SecretSharing providing a (t,n) Shamir's secret sharing.
54+
// It allows splitting a secret into n shares, such that the secret is
55+
// only recovered from any subset of at least t+1 shares.
56+
func New(rnd io.Reader, t uint, secret group.Scalar) SecretSharing {
57+
c := make([]group.Scalar, t+1)
58+
c[0] = secret.Copy()
59+
g := secret.Group()
60+
for i := 1; i < len(c); i++ {
61+
c[i] = g.RandomScalar(rnd)
62+
}
63+
64+
return SecretSharing{g: g, t: t, poly: polynomial.New(c)}
65+
}
66+
67+
// Share creates n shares with an ID monotonically increasing from 1 to n.
68+
func (ss SecretSharing) Share(n uint) []Share {
69+
shares := make([]Share, n)
70+
id := ss.g.NewScalar()
71+
for i := range shares {
72+
shares[i] = ss.ShareWithID(id.SetUint64(uint64(i + 1)))
73+
}
74+
75+
return shares
76+
}
77+
78+
// ShareWithID creates one share of the secret using the ID as identifier.
79+
// Notice that shares with the same ID are considered equal.
80+
// Panics, if the ID is zero.
81+
func (ss SecretSharing) ShareWithID(id group.Scalar) Share {
82+
if id.IsZero() {
83+
panic("secretsharing: id cannot be zero")
84+
}
85+
86+
return Share{
87+
ID: id.Copy(),
88+
Value: ss.poly.Evaluate(id),
89+
}
90+
}
91+
92+
// CommitSecret creates a commitment to the secret for further verifying shares.
93+
func (ss SecretSharing) CommitSecret() SecretCommitment {
94+
c := make(SecretCommitment, ss.poly.Degree()+1)
95+
for i := range c {
96+
c[i] = ss.g.NewElement().MulGen(ss.poly.Coefficient(uint(i)))
97+
}
98+
return c
99+
}
100+
101+
// Verify returns true if the share s was produced by sharing a secret with
102+
// threshold t and commitment of the secret c.
103+
func Verify(t uint, s Share, c SecretCommitment) bool {
104+
if len(c) != int(t+1) {
105+
return false
106+
}
107+
if s.ID.IsZero() {
108+
return false
109+
}
110+
111+
g := s.ID.Group()
112+
lc := len(c) - 1
113+
sum := g.NewElement().Set(c[lc])
114+
for i := lc - 1; i >= 0; i-- {
115+
sum.Mul(sum, s.ID)
116+
sum.Add(sum, c[i])
117+
}
118+
polI := g.NewElement().MulGen(s.Value)
119+
return polI.IsEqual(sum)
120+
}
121+
122+
// Recover returns a secret provided more than t different shares are given.
123+
// Returns an error if the number of shares is not above the threshold t.
124+
// Panics if some shares are duplicated, i.e., shares must have different IDs.
125+
func Recover(t uint, shares []Share) (secret group.Scalar, err error) {
126+
if l := len(shares); l <= int(t) {
127+
return nil, errThreshold(t, uint(l))
128+
}
129+
130+
x := make([]group.Scalar, t+1)
131+
px := make([]group.Scalar, t+1)
132+
for i := range shares[:t+1] {
133+
x[i] = shares[i].ID
134+
px[i] = shares[i].Value
135+
}
136+
137+
l := polynomial.NewLagrangePolynomial(x, px)
138+
zero := shares[0].ID.Group().NewScalar()
139+
140+
return l.Evaluate(zero), nil
141+
}
142+
143+
func errThreshold(t, n uint) error {
144+
return fmt.Errorf("secretsharing: number of shares (n=%v) must be above the threshold (t=%v)", n, t)
145+
}

secretsharing/ss_test.go

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package secretsharing_test
2+
3+
import (
4+
"crypto/rand"
5+
"testing"
6+
7+
"github.com/cloudflare/circl/group"
8+
"github.com/cloudflare/circl/internal/test"
9+
"github.com/cloudflare/circl/secretsharing"
10+
)
11+
12+
func TestSecretSharing(tt *testing.T) {
13+
g := group.P256
14+
t := uint(2)
15+
n := uint(5)
16+
17+
secret := g.RandomScalar(rand.Reader)
18+
ss := secretsharing.New(rand.Reader, t, secret)
19+
shares := ss.Share(n)
20+
test.CheckOk(len(shares) == int(n), "bad num shares", tt)
21+
coms := ss.CommitSecret()
22+
23+
tt.Run("subsetSize", func(ttt *testing.T) {
24+
// Test any possible subset size.
25+
for k := 0; k <= int(n); k++ {
26+
got, err := secretsharing.Recover(t, shares[:k])
27+
if !(int(t) < k && k <= int(n)) {
28+
test.CheckIsErr(ttt, err, "should not recover secret")
29+
test.CheckOk(got == nil, "not nil secret", ttt)
30+
} else {
31+
test.CheckNoErr(ttt, err, "should recover secret")
32+
want := secret
33+
if !got.IsEqual(want) {
34+
test.ReportError(ttt, got, want, t, k, n)
35+
}
36+
}
37+
}
38+
})
39+
40+
tt.Run("verifyShares", func(ttt *testing.T) {
41+
for i := range shares {
42+
test.CheckOk(secretsharing.Verify(t, shares[i], coms) == true, "failed one share", ttt)
43+
}
44+
})
45+
46+
tt.Run("badShares", func(ttt *testing.T) {
47+
badShares := make([]secretsharing.Share, len(shares))
48+
for i := range shares {
49+
badShares[i].ID = shares[i].ID.Copy()
50+
badShares[i].Value = shares[i].Value.Copy()
51+
badShares[i].Value.SetUint64(9)
52+
}
53+
54+
for i := range badShares {
55+
test.CheckOk(secretsharing.Verify(t, badShares[i], coms) == false, "verify must fail due to bad shares", ttt)
56+
}
57+
})
58+
59+
tt.Run("badCommitments", func(ttt *testing.T) {
60+
badComs := make(secretsharing.SecretCommitment, len(coms))
61+
for i := range coms {
62+
badComs[i] = coms[i].Copy()
63+
badComs[i].Dbl(badComs[i])
64+
}
65+
66+
for i := range shares {
67+
test.CheckOk(secretsharing.Verify(t, shares[i], badComs) == false, "verify must fail due to bad commitment", ttt)
68+
}
69+
})
70+
}
71+
72+
func TestShareWithID(tt *testing.T) {
73+
g := group.P256
74+
t := uint(2)
75+
n := uint(5)
76+
secret := g.RandomScalar(rand.Reader)
77+
ss := secretsharing.New(rand.Reader, t, secret)
78+
79+
tt.Run("recoverOk", func(ttt *testing.T) {
80+
// SecretSharing can create shares at will, not exactly n many.
81+
shares := []secretsharing.Share{
82+
ss.ShareWithID(g.RandomScalar(rand.Reader)),
83+
ss.ShareWithID(g.RandomScalar(rand.Reader)),
84+
ss.ShareWithID(g.RandomScalar(rand.Reader)),
85+
}
86+
got, err := secretsharing.Recover(t, shares)
87+
test.CheckNoErr(tt, err, "failed to recover the secret")
88+
want := secret
89+
if !got.IsEqual(want) {
90+
test.ReportError(tt, got, want, t, n)
91+
}
92+
})
93+
94+
tt.Run("duplicatedFail", func(ttt *testing.T) {
95+
// Panics if trying to recover duplicated shares.
96+
share := ss.ShareWithID(g.RandomScalar(rand.Reader))
97+
sameShares := []secretsharing.Share{share, share, share}
98+
err := test.CheckPanic(func() {
99+
got, err := secretsharing.Recover(t, sameShares)
100+
test.CheckIsErr(tt, err, "must fail to recover the secret")
101+
test.CheckOk(got == nil, "must not recover", tt)
102+
})
103+
test.CheckOk(err == nil, "must panic", tt)
104+
})
105+
}
106+
107+
func BenchmarkSecretSharing(b *testing.B) {
108+
g := group.P256
109+
t := uint(3)
110+
n := uint(5)
111+
112+
secret := g.RandomScalar(rand.Reader)
113+
ss := secretsharing.New(rand.Reader, t, secret)
114+
shares := ss.Share(n)
115+
coms := ss.CommitSecret()
116+
117+
b.Run("New", func(b *testing.B) {
118+
for i := 0; i < b.N; i++ {
119+
secretsharing.New(rand.Reader, t, secret)
120+
}
121+
})
122+
123+
b.Run("Share", func(b *testing.B) {
124+
for i := 0; i < b.N; i++ {
125+
ss.Share(n)
126+
}
127+
})
128+
129+
b.Run("Recover", func(b *testing.B) {
130+
for i := 0; i < b.N; i++ {
131+
_, _ = secretsharing.Recover(t, shares)
132+
}
133+
})
134+
135+
b.Run("CommitSecret", func(b *testing.B) {
136+
for i := 0; i < b.N; i++ {
137+
ss.CommitSecret()
138+
}
139+
})
140+
141+
b.Run("Verify", func(b *testing.B) {
142+
for i := 0; i < b.N; i++ {
143+
secretsharing.Verify(t, shares[0], coms)
144+
}
145+
})
146+
}

0 commit comments

Comments
 (0)