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 Aug 1, 2022
1 parent dc30325 commit 99716d8
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 0 deletions.
8 changes: 8 additions & 0 deletions math/polynomial/polynomial.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ func (p Polynomial) Evaluate(x group.Scalar) group.Scalar {
return px
}

func (p Polynomial) Coefficients() []group.Scalar {
c := make([]group.Scalar, len(p.c))
for i := range p.c {
c[i] = p.c[i].Copy()
}
return c
}

// LagrangePolynomial stores a Lagrange polynomial over the set of scalars of a group.
type LagrangePolynomial struct {
// Internal representation is in Lagrange basis:
Expand Down
171 changes: 171 additions & 0 deletions secretsharing/ss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// Package secretsharing provides methods to split secrets in shares.
//
// A (t,n) secret sharing allows to split a secret into n shares, such that the
// secret can be only recovered given more than t shares.
//
// The New function creates a Shamir secret sharing [1], which relies on
// Lagrange polynomial interpolation.
//
// The NewVerifiable function creates a Feldman secret sharing [2], which
// extends Shamir's by allowing to verify that a share corresponds to the
// secret.
//
// References
// [1] https://dl.acm.org/doi/10.1145/359168.359176
// [2] https://ieeexplore.ieee.org/document/4568297
package secretsharing

import (
"errors"
"fmt"
"io"

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

// Share represents a share of a secret.
type Share struct {
ID uint
Share group.Scalar
}

// SecretSharing implements a (t,n) Shamir's secret sharing.
type SecretSharing interface {
// Params returns the t and n parameters of the secret sharing.
Params() (t, n uint)
// Shard splits the secret into n shares.
Shard(rnd io.Reader, secret group.Scalar) []Share
// Recover returns the secret provided more than t shares are given.
Recover(shares []Share) (secret group.Scalar, err error)
}

type ss struct {
g group.Group
t, n uint
}

// New returns a struct implementing SecretSharing interface. A (t,n) secret
// sharing allows to split a secret into n shares, such that the secret can be
// only recovered given more than t shares. It panics if 0 < t <= n does not
// hold.
func New(g group.Group, t, n uint) (ss, error) {
if !(0 < t && t <= n) {
return ss{}, errors.New("secretsharing: bad parameters")
}
s := ss{g: g, t: t, n: n}
var _ SecretSharing = s // checking at compile-time
return s, nil
}

func (s ss) Params() (t, n uint) { return s.t, s.n }

func (s ss) polyFromSecret(rnd io.Reader, secret group.Scalar) (p polynomial.Polynomial) {
c := make([]group.Scalar, s.t+1)
for i := range c {
c[i] = s.g.RandomScalar(rnd)
}
c[0].Set(secret)
return polynomial.New(c)
}

func (s ss) generateShares(poly polynomial.Polynomial) []Share {
shares := make([]Share, s.n)
x := s.g.NewScalar()
for i := range shares {
id := i + 1
x.SetUint64(uint64(id))
shares[i].ID = uint(id)
shares[i].Share = poly.Evaluate(x)
}

return shares
}

func (s ss) Shard(rnd io.Reader, secret group.Scalar) []Share {
return s.generateShares(s.polyFromSecret(rnd, secret))
}

func (s ss) Recover(shares []Share) (group.Scalar, error) {
if l := len(shares); l <= int(s.t) {
return nil, fmt.Errorf("secretsharing: does not reach the 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
}

l := polynomial.NewLagrangePolynomial(x, px)
zero := s.g.NewScalar()

return l.Evaluate(zero), nil
}

type SharesCommitment = []group.Element

type vss struct{ s ss }

// SecretSharing implements a (t,n) Feldman's secret sharing.
type VerifiableSecretSharing interface {
// Params returns the t and n parameters of the secret sharing.
Params() (t, n uint)
// Shard splits the secret into n shares, and a commitment of the secret
// and the shares.
Shard(rnd io.Reader, secret group.Scalar) ([]Share, SharesCommitment)
// Recover returns the secret provided more than t shares are given.
Recover(shares []Share) (secret group.Scalar, err error)
// Verify returns true if the share corresponds to a committed secret using
// the commitment produced by Shard.
Verify(share Share, coms SharesCommitment) bool
}

// New returns a struct implementing VerifiableSecretSharing interface. A (t,n)
// secret sharing allows to split a secret into n shares, such that the secret
// can be only recovered given more than t shares. It is possible to verify
// whether a share corresponds to a secret. It panics if 0 < t <= n does not
// hold.
func NewVerifiable(g group.Group, t, n uint) (vss, error) {
s, err := New(g, t, n)
v := vss{s}
var _ VerifiableSecretSharing = v // checking at compile-time
return v, err
}

func (v vss) Params() (t, n uint) { return v.s.Params() }

func (v vss) Shard(rnd io.Reader, secret group.Scalar) ([]Share, SharesCommitment) {
poly := v.s.polyFromSecret(rnd, secret)
shares := v.s.generateShares(poly)
coeffs := poly.Coefficients()
shareComs := make(SharesCommitment, len(coeffs))
for i := range coeffs {
shareComs[i] = v.s.g.NewElement().MulGen(coeffs[i])
}

return shares, shareComs
}

func (v vss) Verify(s Share, c SharesCommitment) bool {
if len(c) != int(v.s.t+1) {
return false
}

lc := len(c) - 1
sum := v.s.g.NewElement().Set(c[lc])
x := v.s.g.NewScalar()
for i := lc - 1; i >= 0; i-- {
x.SetUint64(uint64(s.ID))
sum.Mul(sum, x)
sum.Add(sum, c[i])
}
polI := v.s.g.NewElement().MulGen(s.Share)
return polI.IsEqual(sum)
}

func (v vss) Recover(shares []Share) (group.Scalar, error) { return v.s.Recover(shares) }
149 changes: 149 additions & 0 deletions secretsharing/ss_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package secretsharing_test

import (
"crypto/rand"
"testing"

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

func TestSecretSharing(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.Shard(rand.Reader, want)
test.CheckOk(len(shares) == int(n), "bad num shares", tt)

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

func TestVerifiableSecretSharing(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.Shard(rand.Reader, want)
test.CheckOk(len(shares) == int(n), "bad num shares", tt)
test.CheckOk(len(com) == int(t+1), "bad num commitments", tt)

tt.Run("verifyShares", func(ttt *testing.T) {
for i := range shares {
test.CheckOk(vs.Verify(shares[i], com) == true, "failed one share", ttt)
}
})

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

tt.Run("badShares", func(ttt *testing.T) {
badShares := make([]secretsharing.Share, len(shares))
for i := range shares {
badShares[i].Share = shares[i].Share.Copy()
badShares[i].Share.SetUint64(9)
}

for i := range badShares {
test.CheckOk(vs.Verify(badShares[i], com) == false, "verify must fail due to bad shares", ttt)
}
})

tt.Run("badCommitments", func(ttt *testing.T) {
badCom := make(secretsharing.SharesCommitment, len(com))
for i := range com {
badCom[i] = com[i].Copy()
badCom[i].Dbl(badCom[i])
}

for i := range shares {
test.CheckOk(vs.Verify(shares[i], badCom) == false, "verify must fail due to bad commitment", ttt)
}
})
}

func BenchmarkSecretSharing(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.Shard(rand.Reader, want)

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

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

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

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

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

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

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

0 comments on commit 99716d8

Please sign in to comment.