Skip to content

Commit bb3e3e4

Browse files
committed
Implements Shamir and Feldman secret sharing.
1 parent ccccbca commit bb3e3e4

File tree

3 files changed

+329
-0
lines changed

3 files changed

+329
-0
lines changed

math/polynomial/polynomial.go

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

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

secretsharing/ss.go

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