Skip to content

Commit 0122e3d

Browse files
committed
Implements Shamir and Feldman secret sharing.
1 parent 39ba87a commit 0122e3d

File tree

3 files changed

+328
-0
lines changed

3 files changed

+328
-0
lines changed

math/polynomial/polynomial.go

+8
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ func (p Polynomial) Evaluate(x group.Scalar) group.Scalar {
5151
return px
5252
}
5353

54+
func (p Polynomial) Coefficients() []group.Scalar {
55+
c := make([]group.Scalar, len(p.c))
56+
for i := range p.c {
57+
c[i] = p.c[i].Copy()
58+
}
59+
return c
60+
}
61+
5462
// LagrangePolynomial stores a Lagrange polynomial over the set of scalars of a group.
5563
type LagrangePolynomial struct {
5664
// Internal representation is in Lagrange basis:

secretsharing/ss.go

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