Skip to content

Commit

Permalink
Implements Shamir and Feldman secret sharing.
Browse files Browse the repository at this point in the history
  • Loading branch information
armfazh committed Jul 12, 2022
1 parent 5170e38 commit 44925f7
Show file tree
Hide file tree
Showing 4 changed files with 366 additions and 0 deletions.
73 changes: 73 additions & 0 deletions group/secretsharing/poly.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package secretsharing

import (
"errors"
"io"

"github.com/cloudflare/circl/group"
)

type polynomial struct {
deg uint
coeff []group.Scalar
}

func randomPolynomial(rnd io.Reader, g group.Group, deg uint) (p polynomial) {
p = polynomial{deg, make([]group.Scalar, deg+1)}

for i := 0; i <= int(deg); i++ {
p.coeff[i] = g.RandomScalar(rnd)
}
return
}

func (p polynomial) evaluate(x group.Scalar) group.Scalar {
px := p.coeff[p.deg].Copy()
for i := int(p.deg) - 1; i >= 0; i-- {
px.Mul(px, x)
px.Add(px, p.coeff[i])
}
return px
}

func LagrangeCoefficient(g group.Group, x []group.Scalar, index uint) group.Scalar {
if int(index) > len(x) {
panic("invalid parameter")
}

num := g.NewScalar()
num.SetUint64(1)
den := g.NewScalar()
den.SetUint64(1)
tmp := g.NewScalar()

for j := range x {
if j != int(index) {
num.Mul(num, x[j])
den.Mul(den, tmp.Sub(x[j], x[index]))
}
}

return num.Mul(num, tmp.Inv(den))
}

func LagrangeInterpolate(g group.Group, x, px []group.Scalar) (group.Scalar, error) {
if len(x) != len(px) {
return nil, errors.New("lagrange: bad input length")
}

zero := g.NewScalar()
for i := range x {
if x[i].IsEqual(zero) {
return nil, errors.New("lagrange: tried to evaluate on zero")
}
}

pol0 := g.NewScalar()
delta := g.NewScalar()
for i := range x {
pol0.Add(pol0, delta.Mul(px[i], LagrangeCoefficient(g, x, uint(i))))
}

return pol0, nil
}
60 changes: 60 additions & 0 deletions group/secretsharing/poly_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package secretsharing

import (
"testing"

"github.com/cloudflare/circl/group"
"github.com/cloudflare/circl/internal/test"
)

func TestPolyEval(t *testing.T) {
g := group.P256
p := polynomial{2, []group.Scalar{
g.NewScalar(),
g.NewScalar(),
g.NewScalar(),
}}
p.coeff[0].SetUint64(5)
p.coeff[1].SetUint64(5)
p.coeff[2].SetUint64(2)

x := g.NewScalar()
x.SetUint64(10)

got := p.evaluate(x)

want := g.NewScalar()
want.SetUint64(255)
if !got.IsEqual(want) {
test.ReportError(t, got, want)
}
}

func TestLagrange(t *testing.T) {
g := group.P256
p := polynomial{2, []group.Scalar{
g.NewScalar(),
g.NewScalar(),
g.NewScalar(),
}}
p.coeff[0].SetUint64(1234)
p.coeff[1].SetUint64(166)
p.coeff[2].SetUint64(94)

x := []group.Scalar{g.NewScalar(), g.NewScalar(), g.NewScalar()}
px := []group.Scalar{g.NewScalar(), g.NewScalar(), g.NewScalar()}
x[0].SetUint64(2)
px[0].SetUint64(1942)
x[1].SetUint64(4)
px[1].SetUint64(3402)
x[2].SetUint64(5)
px[2].SetUint64(4414)

got, err := LagrangeInterpolate(g, x, px)
test.CheckNoErr(t, err, "failed interpolation")
want := p.coeff[0]

if !got.IsEqual(want) {
test.ReportError(t, got, want)
}
}
114 changes: 114 additions & 0 deletions group/secretsharing/ss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package secretsharing

import (
"errors"
"fmt"
"io"

"github.com/cloudflare/circl/group"
)

type SecretShare struct {
ID uint
Share group.Scalar
}

type ShamirSS struct {
G group.Group
T, N uint
_ struct{}
}

func New(g group.Group, t, n uint) (*ShamirSS, error) {
if !(0 < t && t <= n) || g == nil {
return nil, errors.New("secretsharing: bad parameters")
}
return &ShamirSS{G: g, T: t, N: n}, nil
}

func (s ShamirSS) polyFromSecret(rnd io.Reader, secret group.Scalar) (p polynomial) {
p = randomPolynomial(rnd, s.G, s.T)
p.coeff[0] = secret.Copy()
return
}

func (s ShamirSS) generateShares(poly polynomial) []SecretShare {
shares := make([]SecretShare, s.N)
x := s.G.NewScalar()
for i := range shares {
id := uint(i + 1)
x.SetUint64(uint64(id))
shares[i].ID = id
shares[i].Share = poly.evaluate(x)
}

return shares
}

func (s ShamirSS) ShardSecret(rnd io.Reader, secret group.Scalar) []SecretShare {
return s.generateShares(s.polyFromSecret(rnd, secret))
}

func (s ShamirSS) RecoverSecret(shares []SecretShare) (group.Scalar, error) {
if l := len(shares); l <= int(s.T) {
return nil, fmt.Errorf("secretsharing: do not met threshold %v with %v shares", s.T, l)
} else if l > int(s.N) {
return nil, fmt.Errorf("secretsharing: %v shares above max number of shares %v", l, s.N)
}

x := make([]group.Scalar, len(shares))
px := make([]group.Scalar, len(shares))
for i := range shares {
x[i] = s.G.NewScalar()
x[i].SetUint64(uint64(shares[i].ID))
px[i] = shares[i].Share
}

return LagrangeInterpolate(s.G, x, px)
}

type Commitment = group.Element

type FeldmanSS struct {
s ShamirSS
_ struct{}
}

func NewVerifiable(g group.Group, t, n uint) (*FeldmanSS, error) {
if !(0 < t && t <= n) || g == nil {
return nil, errors.New("bad parameters")
}
return &FeldmanSS{s: ShamirSS{G: g, T: t, N: n}}, nil
}

func (f FeldmanSS) ShardSecret(rnd io.Reader, secret group.Scalar) ([]SecretShare, []Commitment) {
poly := f.s.polyFromSecret(rnd, secret)
shares := f.s.generateShares(poly)

vecComm := make([]Commitment, f.s.T+1)
for i, ki := range poly.coeff {
vecComm[i] = f.s.G.NewElement()
vecComm[i].MulGen(ki)
}

return shares, vecComm
}

func (s SecretShare) Verify(g group.Group, c []Commitment) bool {
polI := g.NewElement().MulGen(s.Share)

lc := len(c) - 1
sum := c[lc].Copy()
x := g.NewScalar()
for i := lc - 1; i >= 0; i-- {
x.SetUint64(uint64(s.ID))
sum.Mul(sum, x)
sum.Add(sum, c[i])
}

return polI.IsEqual(sum)
}

func (f FeldmanSS) RecoverSecret(shares []SecretShare) (group.Scalar, error) {
return f.s.RecoverSecret(shares)
}
119 changes: 119 additions & 0 deletions group/secretsharing/ss_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package secretsharing_test

import (
"crypto/rand"
"testing"

"github.com/cloudflare/circl/group"
"github.com/cloudflare/circl/group/secretsharing"
"github.com/cloudflare/circl/internal/test"
)

func TestShamirSS(tt *testing.T) {
g := group.P256
t := uint(3)
n := uint(5)

s, err := secretsharing.New(g, t, n)
test.CheckNoErr(tt, err, "failed to create ShamirSS")

want := g.RandomScalar(rand.Reader)
shares := s.ShardSecret(rand.Reader, want)
test.CheckOk(len(shares) == int(n), "bad num shares", tt)

// Test any possible subset size.
for k := 0; k < int(n); k++ {
got, err := s.RecoverSecret(shares[:k])
if k <= int(t) {
test.CheckIsErr(tt, err, "should not recover secret")
test.CheckOk(got == nil, "not nil secret", tt)
} else {
test.CheckNoErr(tt, err, "should recover secret")
if !got.IsEqual(want) {
test.ReportError(tt, got, want, t, k, n)
}
}
}
}

func TestFeldmanSS(tt *testing.T) {
g := group.P256
t := uint(3)
n := uint(5)

vs, err := secretsharing.NewVerifiable(g, t, n)
test.CheckNoErr(tt, err, "failed to create ShamirSS")

want := g.RandomScalar(rand.Reader)
shares, com := vs.ShardSecret(rand.Reader, want)
test.CheckOk(len(shares) == int(n), "bad num shares", tt)
test.CheckOk(len(com) == int(t+1), "bad num commitments", tt)

for i := range shares {
test.CheckOk(shares[i].Verify(g, com), "failed one share", tt)
}

// Test any possible subset size.
for k := 0; k < int(n); k++ {
got, err := vs.RecoverSecret(shares[:k])
if k <= int(t) {
test.CheckIsErr(tt, err, "should not recover secret")
test.CheckOk(got == nil, "not nil secret", tt)
} else {
test.CheckNoErr(tt, err, "should recover secret")
if !got.IsEqual(want) {
test.ReportError(tt, got, want, t, k, n)
}
}
}
}

func BenchmarkShamirSS(b *testing.B) {
g := group.P256
t := uint(3)
n := uint(5)

s, _ := secretsharing.New(g, t, n)
want := g.RandomScalar(rand.Reader)
shares := s.ShardSecret(rand.Reader, want)

b.Run("Shard", func(b *testing.B) {
for i := 0; i < b.N; i++ {
s.ShardSecret(rand.Reader, want)
}
})

b.Run("Recover", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = s.RecoverSecret(shares)
}
})
}

func BenchmarkFeldmanSS(b *testing.B) {
g := group.P256
t := uint(3)
n := uint(5)

s, _ := secretsharing.NewVerifiable(g, t, n)
want := g.RandomScalar(rand.Reader)
shares, com := s.ShardSecret(rand.Reader, want)

b.Run("Shard", func(b *testing.B) {
for i := 0; i < b.N; i++ {
s.ShardSecret(rand.Reader, want)
}
})

b.Run("Recover", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = s.RecoverSecret(shares)
}
})

b.Run("Verify", func(b *testing.B) {
for i := 0; i < b.N; i++ {
shares[0].Verify(g, com)
}
})
}

0 comments on commit 44925f7

Please sign in to comment.