Skip to content

Commit 44925f7

Browse files
committed
Implements Shamir and Feldman secret sharing.
1 parent 5170e38 commit 44925f7

File tree

4 files changed

+366
-0
lines changed

4 files changed

+366
-0
lines changed

group/secretsharing/poly.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package secretsharing
2+
3+
import (
4+
"errors"
5+
"io"
6+
7+
"github.com/cloudflare/circl/group"
8+
)
9+
10+
type polynomial struct {
11+
deg uint
12+
coeff []group.Scalar
13+
}
14+
15+
func randomPolynomial(rnd io.Reader, g group.Group, deg uint) (p polynomial) {
16+
p = polynomial{deg, make([]group.Scalar, deg+1)}
17+
18+
for i := 0; i <= int(deg); i++ {
19+
p.coeff[i] = g.RandomScalar(rnd)
20+
}
21+
return
22+
}
23+
24+
func (p polynomial) evaluate(x group.Scalar) group.Scalar {
25+
px := p.coeff[p.deg].Copy()
26+
for i := int(p.deg) - 1; i >= 0; i-- {
27+
px.Mul(px, x)
28+
px.Add(px, p.coeff[i])
29+
}
30+
return px
31+
}
32+
33+
func LagrangeCoefficient(g group.Group, x []group.Scalar, index uint) group.Scalar {
34+
if int(index) > len(x) {
35+
panic("invalid parameter")
36+
}
37+
38+
num := g.NewScalar()
39+
num.SetUint64(1)
40+
den := g.NewScalar()
41+
den.SetUint64(1)
42+
tmp := g.NewScalar()
43+
44+
for j := range x {
45+
if j != int(index) {
46+
num.Mul(num, x[j])
47+
den.Mul(den, tmp.Sub(x[j], x[index]))
48+
}
49+
}
50+
51+
return num.Mul(num, tmp.Inv(den))
52+
}
53+
54+
func LagrangeInterpolate(g group.Group, x, px []group.Scalar) (group.Scalar, error) {
55+
if len(x) != len(px) {
56+
return nil, errors.New("lagrange: bad input length")
57+
}
58+
59+
zero := g.NewScalar()
60+
for i := range x {
61+
if x[i].IsEqual(zero) {
62+
return nil, errors.New("lagrange: tried to evaluate on zero")
63+
}
64+
}
65+
66+
pol0 := g.NewScalar()
67+
delta := g.NewScalar()
68+
for i := range x {
69+
pol0.Add(pol0, delta.Mul(px[i], LagrangeCoefficient(g, x, uint(i))))
70+
}
71+
72+
return pol0, nil
73+
}

group/secretsharing/poly_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package secretsharing
2+
3+
import (
4+
"testing"
5+
6+
"github.com/cloudflare/circl/group"
7+
"github.com/cloudflare/circl/internal/test"
8+
)
9+
10+
func TestPolyEval(t *testing.T) {
11+
g := group.P256
12+
p := polynomial{2, []group.Scalar{
13+
g.NewScalar(),
14+
g.NewScalar(),
15+
g.NewScalar(),
16+
}}
17+
p.coeff[0].SetUint64(5)
18+
p.coeff[1].SetUint64(5)
19+
p.coeff[2].SetUint64(2)
20+
21+
x := g.NewScalar()
22+
x.SetUint64(10)
23+
24+
got := p.evaluate(x)
25+
26+
want := g.NewScalar()
27+
want.SetUint64(255)
28+
if !got.IsEqual(want) {
29+
test.ReportError(t, got, want)
30+
}
31+
}
32+
33+
func TestLagrange(t *testing.T) {
34+
g := group.P256
35+
p := polynomial{2, []group.Scalar{
36+
g.NewScalar(),
37+
g.NewScalar(),
38+
g.NewScalar(),
39+
}}
40+
p.coeff[0].SetUint64(1234)
41+
p.coeff[1].SetUint64(166)
42+
p.coeff[2].SetUint64(94)
43+
44+
x := []group.Scalar{g.NewScalar(), g.NewScalar(), g.NewScalar()}
45+
px := []group.Scalar{g.NewScalar(), g.NewScalar(), g.NewScalar()}
46+
x[0].SetUint64(2)
47+
px[0].SetUint64(1942)
48+
x[1].SetUint64(4)
49+
px[1].SetUint64(3402)
50+
x[2].SetUint64(5)
51+
px[2].SetUint64(4414)
52+
53+
got, err := LagrangeInterpolate(g, x, px)
54+
test.CheckNoErr(t, err, "failed interpolation")
55+
want := p.coeff[0]
56+
57+
if !got.IsEqual(want) {
58+
test.ReportError(t, got, want)
59+
}
60+
}

group/secretsharing/ss.go

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package secretsharing
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
8+
"github.com/cloudflare/circl/group"
9+
)
10+
11+
type SecretShare struct {
12+
ID uint
13+
Share group.Scalar
14+
}
15+
16+
type ShamirSS struct {
17+
G group.Group
18+
T, N uint
19+
_ struct{}
20+
}
21+
22+
func New(g group.Group, t, n uint) (*ShamirSS, error) {
23+
if !(0 < t && t <= n) || g == nil {
24+
return nil, errors.New("secretsharing: bad parameters")
25+
}
26+
return &ShamirSS{G: g, T: t, N: n}, nil
27+
}
28+
29+
func (s ShamirSS) polyFromSecret(rnd io.Reader, secret group.Scalar) (p polynomial) {
30+
p = randomPolynomial(rnd, s.G, s.T)
31+
p.coeff[0] = secret.Copy()
32+
return
33+
}
34+
35+
func (s ShamirSS) generateShares(poly polynomial) []SecretShare {
36+
shares := make([]SecretShare, s.N)
37+
x := s.G.NewScalar()
38+
for i := range shares {
39+
id := uint(i + 1)
40+
x.SetUint64(uint64(id))
41+
shares[i].ID = id
42+
shares[i].Share = poly.evaluate(x)
43+
}
44+
45+
return shares
46+
}
47+
48+
func (s ShamirSS) ShardSecret(rnd io.Reader, secret group.Scalar) []SecretShare {
49+
return s.generateShares(s.polyFromSecret(rnd, secret))
50+
}
51+
52+
func (s ShamirSS) RecoverSecret(shares []SecretShare) (group.Scalar, error) {
53+
if l := len(shares); l <= int(s.T) {
54+
return nil, fmt.Errorf("secretsharing: do not met threshold %v with %v shares", s.T, l)
55+
} else if l > int(s.N) {
56+
return nil, fmt.Errorf("secretsharing: %v shares above max number of shares %v", l, s.N)
57+
}
58+
59+
x := make([]group.Scalar, len(shares))
60+
px := make([]group.Scalar, len(shares))
61+
for i := range shares {
62+
x[i] = s.G.NewScalar()
63+
x[i].SetUint64(uint64(shares[i].ID))
64+
px[i] = shares[i].Share
65+
}
66+
67+
return LagrangeInterpolate(s.G, x, px)
68+
}
69+
70+
type Commitment = group.Element
71+
72+
type FeldmanSS struct {
73+
s ShamirSS
74+
_ struct{}
75+
}
76+
77+
func NewVerifiable(g group.Group, t, n uint) (*FeldmanSS, error) {
78+
if !(0 < t && t <= n) || g == nil {
79+
return nil, errors.New("bad parameters")
80+
}
81+
return &FeldmanSS{s: ShamirSS{G: g, T: t, N: n}}, nil
82+
}
83+
84+
func (f FeldmanSS) ShardSecret(rnd io.Reader, secret group.Scalar) ([]SecretShare, []Commitment) {
85+
poly := f.s.polyFromSecret(rnd, secret)
86+
shares := f.s.generateShares(poly)
87+
88+
vecComm := make([]Commitment, f.s.T+1)
89+
for i, ki := range poly.coeff {
90+
vecComm[i] = f.s.G.NewElement()
91+
vecComm[i].MulGen(ki)
92+
}
93+
94+
return shares, vecComm
95+
}
96+
97+
func (s SecretShare) Verify(g group.Group, c []Commitment) bool {
98+
polI := g.NewElement().MulGen(s.Share)
99+
100+
lc := len(c) - 1
101+
sum := c[lc].Copy()
102+
x := g.NewScalar()
103+
for i := lc - 1; i >= 0; i-- {
104+
x.SetUint64(uint64(s.ID))
105+
sum.Mul(sum, x)
106+
sum.Add(sum, c[i])
107+
}
108+
109+
return polI.IsEqual(sum)
110+
}
111+
112+
func (f FeldmanSS) RecoverSecret(shares []SecretShare) (group.Scalar, error) {
113+
return f.s.RecoverSecret(shares)
114+
}

group/secretsharing/ss_test.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package secretsharing_test
2+
3+
import (
4+
"crypto/rand"
5+
"testing"
6+
7+
"github.com/cloudflare/circl/group"
8+
"github.com/cloudflare/circl/group/secretsharing"
9+
"github.com/cloudflare/circl/internal/test"
10+
)
11+
12+
func TestShamirSS(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.ShardSecret(rand.Reader, want)
22+
test.CheckOk(len(shares) == int(n), "bad num shares", tt)
23+
24+
// Test any possible subset size.
25+
for k := 0; k < int(n); k++ {
26+
got, err := s.RecoverSecret(shares[:k])
27+
if k <= int(t) {
28+
test.CheckIsErr(tt, err, "should not recover secret")
29+
test.CheckOk(got == nil, "not nil secret", tt)
30+
} else {
31+
test.CheckNoErr(tt, err, "should recover secret")
32+
if !got.IsEqual(want) {
33+
test.ReportError(tt, got, want, t, k, n)
34+
}
35+
}
36+
}
37+
}
38+
39+
func TestFeldmanSS(tt *testing.T) {
40+
g := group.P256
41+
t := uint(3)
42+
n := uint(5)
43+
44+
vs, err := secretsharing.NewVerifiable(g, t, n)
45+
test.CheckNoErr(tt, err, "failed to create ShamirSS")
46+
47+
want := g.RandomScalar(rand.Reader)
48+
shares, com := vs.ShardSecret(rand.Reader, want)
49+
test.CheckOk(len(shares) == int(n), "bad num shares", tt)
50+
test.CheckOk(len(com) == int(t+1), "bad num commitments", tt)
51+
52+
for i := range shares {
53+
test.CheckOk(shares[i].Verify(g, com), "failed one share", tt)
54+
}
55+
56+
// Test any possible subset size.
57+
for k := 0; k < int(n); k++ {
58+
got, err := vs.RecoverSecret(shares[:k])
59+
if k <= int(t) {
60+
test.CheckIsErr(tt, err, "should not recover secret")
61+
test.CheckOk(got == nil, "not nil secret", tt)
62+
} else {
63+
test.CheckNoErr(tt, err, "should recover secret")
64+
if !got.IsEqual(want) {
65+
test.ReportError(tt, got, want, t, k, n)
66+
}
67+
}
68+
}
69+
}
70+
71+
func BenchmarkShamirSS(b *testing.B) {
72+
g := group.P256
73+
t := uint(3)
74+
n := uint(5)
75+
76+
s, _ := secretsharing.New(g, t, n)
77+
want := g.RandomScalar(rand.Reader)
78+
shares := s.ShardSecret(rand.Reader, want)
79+
80+
b.Run("Shard", func(b *testing.B) {
81+
for i := 0; i < b.N; i++ {
82+
s.ShardSecret(rand.Reader, want)
83+
}
84+
})
85+
86+
b.Run("Recover", func(b *testing.B) {
87+
for i := 0; i < b.N; i++ {
88+
_, _ = s.RecoverSecret(shares)
89+
}
90+
})
91+
}
92+
93+
func BenchmarkFeldmanSS(b *testing.B) {
94+
g := group.P256
95+
t := uint(3)
96+
n := uint(5)
97+
98+
s, _ := secretsharing.NewVerifiable(g, t, n)
99+
want := g.RandomScalar(rand.Reader)
100+
shares, com := s.ShardSecret(rand.Reader, want)
101+
102+
b.Run("Shard", func(b *testing.B) {
103+
for i := 0; i < b.N; i++ {
104+
s.ShardSecret(rand.Reader, want)
105+
}
106+
})
107+
108+
b.Run("Recover", func(b *testing.B) {
109+
for i := 0; i < b.N; i++ {
110+
_, _ = s.RecoverSecret(shares)
111+
}
112+
})
113+
114+
b.Run("Verify", func(b *testing.B) {
115+
for i := 0; i < b.N; i++ {
116+
shares[0].Verify(g, com)
117+
}
118+
})
119+
}

0 commit comments

Comments
 (0)