Skip to content

Commit 28db5bf

Browse files
committed
Implements Shamir and Feldman secret sharing.
1 parent f70c830 commit 28db5bf

File tree

3 files changed

+321
-0
lines changed

3 files changed

+321
-0
lines changed

math/polynomial/polynomial.go

+13
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,16 @@ func (p Polynomial) Evaluate(x group.Scalar) group.Scalar {
5356
return px
5457
}
5558

59+
// Coefficients returns a deep-copy of the polynomial's coefficients in
60+
// ascending order with respect to the degree.
61+
func (p Polynomial) Coefficients() []group.Scalar {
62+
c := make([]group.Scalar, len(p.c))
63+
for i := range p.c {
64+
c[i] = p.c[i].Copy()
65+
}
66+
return c
67+
}
68+
5669
// LagrangePolynomial stores a Lagrange polynomial over the set of scalars of a group.
5770
type LagrangePolynomial struct {
5871
// Internal representation is in Lagrange basis:

secretsharing/ss.go

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Package secretsharing provides methods to split secrets in 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 t+1 shares.
6+
//
7+
// The NewShamirSecretSharing function creates a Shamir secret sharing [1],
8+
// which relies on Lagrange polynomial interpolation.
9+
//
10+
// The NewFeldmanSecretSharing function creates a Feldman secret sharing [2],
11+
// which extends Shamir's by allowing to verify that a share was honestly
12+
// generated.
13+
//
14+
// References
15+
//
16+
// [1] https://dl.acm.org/doi/10.1145/359168.359176
17+
// [2] https://ieeexplore.ieee.org/document/4568297
18+
package secretsharing
19+
20+
import (
21+
"errors"
22+
"fmt"
23+
"io"
24+
25+
"github.com/cloudflare/circl/group"
26+
"github.com/cloudflare/circl/math/polynomial"
27+
)
28+
29+
// Share represents a share of a secret.
30+
type Share struct {
31+
ID uint // ID uniquely identifies a share in a secret sharing instance.
32+
Value group.Scalar // Value stores the share generated from a secret sharing instance.
33+
}
34+
35+
type ss struct {
36+
g group.Group
37+
t, n uint
38+
}
39+
40+
// NewShamirSecretSharing implements a (t,n) Shamir's secret sharing.
41+
// A (t,n) secret sharing allows to split a secret into n shares, such that the
42+
// secret can be only recovered from any subset of t+1 shares. Returns an error
43+
// if 0 <= t < n does not hold.
44+
func NewShamirSecretSharing(g group.Group, t, n uint) (ss, error) {
45+
if t >= n {
46+
return ss{}, errors.New("secretsharing: bad parameters")
47+
}
48+
return ss{g: g, t: t, n: n}, nil
49+
}
50+
51+
// Params returns the t and n parameters of the secret sharing.
52+
func (s ss) Params() (t, n uint) { return s.t, s.n }
53+
54+
func (s ss) polyFromSecret(rnd io.Reader, secret group.Scalar) (p polynomial.Polynomial) {
55+
c := make([]group.Scalar, s.t+1)
56+
for i := range c {
57+
c[i] = s.g.RandomScalar(rnd)
58+
}
59+
c[0].Set(secret)
60+
return polynomial.New(c)
61+
}
62+
63+
func (s ss) generateShares(poly polynomial.Polynomial) []Share {
64+
shares := make([]Share, s.n)
65+
x := s.g.NewScalar()
66+
for i := range shares {
67+
id := i + 1
68+
x.SetUint64(uint64(id))
69+
shares[i].ID = uint(id)
70+
shares[i].Value = poly.Evaluate(x)
71+
}
72+
73+
return shares
74+
}
75+
76+
// Shard splits the secret into n shares.
77+
func (s ss) Shard(rnd io.Reader, secret group.Scalar) []Share {
78+
return s.generateShares(s.polyFromSecret(rnd, secret))
79+
}
80+
81+
// Recover returns the secret provided more than t shares are given. Returns an
82+
// error if the number of shares is not above the threshold or goes beyond the
83+
// maximum number of shares.
84+
func (s ss) Recover(shares []Share) (group.Scalar, error) {
85+
if l := len(shares); l <= int(s.t) {
86+
return nil, fmt.Errorf("secretsharing: does not reach the threshold %v with %v shares", s.t, l)
87+
} else if l > int(s.n) {
88+
return nil, fmt.Errorf("secretsharing: %v shares above max number of shares %v", l, s.n)
89+
}
90+
91+
x := make([]group.Scalar, s.t+1)
92+
px := make([]group.Scalar, s.t+1)
93+
for i := range shares[:s.t+1] {
94+
x[i] = s.g.NewScalar().SetUint64(uint64(shares[i].ID))
95+
px[i] = shares[i].Value
96+
}
97+
98+
l := polynomial.NewLagrangePolynomial(x, px)
99+
zero := s.g.NewScalar()
100+
101+
return l.Evaluate(zero), nil
102+
}
103+
104+
type SharesCommitment = []group.Element
105+
106+
type vss struct{ s ss }
107+
108+
// NewFeldmanSecretSharing implements a (t,n) Feldman's verifiable secret
109+
// sharing. A (t,n) secret sharing allows to split a secret into n shares, such
110+
// that the secret can be only recovered from any subset of t+1 shares. This
111+
// method is verifiable because once the shares and the secret are committed
112+
// during sharding, one can later verify whether the share was generated
113+
// honestly. Returns an error if 0 < t <= n does not hold.
114+
func NewFeldmanSecretSharing(g group.Group, t, n uint) (vss, error) {
115+
s, err := NewShamirSecretSharing(g, t, n)
116+
return vss{s}, err
117+
}
118+
119+
// Params returns the t and n parameters of the secret sharing.
120+
func (v vss) Params() (t, n uint) { return v.s.Params() }
121+
122+
// Shard splits the secret into n shares, and also returns a commitment to both
123+
// the secret and the shares.
124+
func (v vss) Shard(rnd io.Reader, secret group.Scalar) ([]Share, SharesCommitment) {
125+
poly := v.s.polyFromSecret(rnd, secret)
126+
shares := v.s.generateShares(poly)
127+
coeffs := poly.Coefficients()
128+
shareComs := make(SharesCommitment, len(coeffs))
129+
for i := range coeffs {
130+
shareComs[i] = v.s.g.NewElement().MulGen(coeffs[i])
131+
}
132+
133+
return shares, shareComs
134+
}
135+
136+
// Verify returns true if a share was produced by sharding a secret. It uses
137+
// the share commitments generated by the Shard function to verify this
138+
// property.
139+
func (v vss) Verify(s Share, c SharesCommitment) bool {
140+
if len(c) != int(v.s.t+1) {
141+
return false
142+
}
143+
144+
lc := len(c) - 1
145+
sum := v.s.g.NewElement().Set(c[lc])
146+
x := v.s.g.NewScalar()
147+
for i := lc - 1; i >= 0; i-- {
148+
x.SetUint64(uint64(s.ID))
149+
sum.Mul(sum, x)
150+
sum.Add(sum, c[i])
151+
}
152+
polI := v.s.g.NewElement().MulGen(s.Value)
153+
return polI.IsEqual(sum)
154+
}
155+
156+
// Recover returns the secret provided more than t shares are given. Returns an
157+
// error if the number of shares is not above the threshold (t) or is larger
158+
// than the maximum number of shares (n).
159+
func (v vss) Recover(shares []Share) (group.Scalar, error) { return v.s.Recover(shares) }

secretsharing/ss_test.go

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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+
s, err := secretsharing.NewShamirSecretSharing(g, t, n)
18+
test.CheckNoErr(tt, err, "failed to create Shamir secret sharing")
19+
20+
want := g.RandomScalar(rand.Reader)
21+
shares := s.Shard(rand.Reader, want)
22+
test.CheckOk(len(shares) == int(n), "bad num shares", tt)
23+
24+
tt.Run("subsetSize", func(ttt *testing.T) {
25+
// Test any possible subset size.
26+
for k := 0; k <= int(n); k++ {
27+
got, err := s.Recover(shares[:k])
28+
if !(int(t) < k && k <= int(n)) {
29+
test.CheckIsErr(ttt, err, "should not recover secret")
30+
test.CheckOk(got == nil, "not nil secret", ttt)
31+
} else {
32+
test.CheckNoErr(ttt, err, "should recover secret")
33+
if !got.IsEqual(want) {
34+
test.ReportError(ttt, got, want, t, k, n)
35+
}
36+
}
37+
}
38+
})
39+
}
40+
41+
func TestVerifiableSecretSharing(tt *testing.T) {
42+
g := group.P256
43+
t := uint(3)
44+
n := uint(5)
45+
46+
vs, err := secretsharing.NewFeldmanSecretSharing(g, t, n)
47+
test.CheckNoErr(tt, err, "failed to create Feldman secret sharing")
48+
49+
want := g.RandomScalar(rand.Reader)
50+
shares, com := vs.Shard(rand.Reader, want)
51+
test.CheckOk(len(shares) == int(n), "bad num shares", tt)
52+
test.CheckOk(len(com) == int(t+1), "bad num commitments", tt)
53+
54+
tt.Run("verifyShares", func(ttt *testing.T) {
55+
for i := range shares {
56+
test.CheckOk(vs.Verify(shares[i], com) == true, "failed one share", ttt)
57+
}
58+
})
59+
60+
tt.Run("subsetSize", func(ttt *testing.T) {
61+
// Test any possible subset size.
62+
for k := 0; k <= int(n); k++ {
63+
got, err := vs.Recover(shares[:k])
64+
if k <= int(t) {
65+
test.CheckIsErr(ttt, err, "should not recover secret")
66+
test.CheckOk(got == nil, "not nil secret", ttt)
67+
} else {
68+
test.CheckNoErr(ttt, err, "should recover secret")
69+
if !got.IsEqual(want) {
70+
test.ReportError(ttt, got, want, t, k, n)
71+
}
72+
}
73+
}
74+
})
75+
76+
tt.Run("badShares", func(ttt *testing.T) {
77+
badShares := make([]secretsharing.Share, len(shares))
78+
for i := range shares {
79+
badShares[i].Value = shares[i].Value.Copy()
80+
badShares[i].Value.SetUint64(9)
81+
}
82+
83+
for i := range badShares {
84+
test.CheckOk(vs.Verify(badShares[i], com) == false, "verify must fail due to bad shares", ttt)
85+
}
86+
})
87+
88+
tt.Run("badCommitments", func(ttt *testing.T) {
89+
badCom := make(secretsharing.SharesCommitment, len(com))
90+
for i := range com {
91+
badCom[i] = com[i].Copy()
92+
badCom[i].Dbl(badCom[i])
93+
}
94+
95+
for i := range shares {
96+
test.CheckOk(vs.Verify(shares[i], badCom) == false, "verify must fail due to bad commitment", ttt)
97+
}
98+
})
99+
}
100+
101+
func BenchmarkSecretSharing(b *testing.B) {
102+
g := group.P256
103+
t := uint(3)
104+
n := uint(5)
105+
106+
s, _ := secretsharing.NewShamirSecretSharing(g, t, n)
107+
want := g.RandomScalar(rand.Reader)
108+
shares := s.Shard(rand.Reader, want)
109+
110+
b.Run("Shard", func(b *testing.B) {
111+
for i := 0; i < b.N; i++ {
112+
s.Shard(rand.Reader, want)
113+
}
114+
})
115+
116+
b.Run("Recover", func(b *testing.B) {
117+
for i := 0; i < b.N; i++ {
118+
_, _ = s.Recover(shares)
119+
}
120+
})
121+
}
122+
123+
func BenchmarkVerifiableSecretSharing(b *testing.B) {
124+
g := group.P256
125+
t := uint(3)
126+
n := uint(5)
127+
128+
vs, _ := secretsharing.NewFeldmanSecretSharing(g, t, n)
129+
want := g.RandomScalar(rand.Reader)
130+
shares, com := vs.Shard(rand.Reader, want)
131+
132+
b.Run("Shard", func(b *testing.B) {
133+
for i := 0; i < b.N; i++ {
134+
vs.Shard(rand.Reader, want)
135+
}
136+
})
137+
138+
b.Run("Recover", func(b *testing.B) {
139+
for i := 0; i < b.N; i++ {
140+
_, _ = vs.Recover(shares)
141+
}
142+
})
143+
144+
b.Run("Verify", func(b *testing.B) {
145+
for i := 0; i < b.N; i++ {
146+
vs.Verify(shares[0], com)
147+
}
148+
})
149+
}

0 commit comments

Comments
 (0)