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 26, 2022
1 parent f70c830 commit 28db5bf
Show file tree
Hide file tree
Showing 3 changed files with 321 additions and 0 deletions.
13 changes: 13 additions & 0 deletions math/polynomial/polynomial.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ func New(coeffs []group.Scalar) (p Polynomial) {
return
}

// Degree returns the degree of the polynomial. The zero polynomial has degree
// equal to -1.
func (p Polynomial) Degree() int {
i := len(p.c) - 1
for i > 0 && p.c[i].IsZero() {
Expand All @@ -41,6 +43,7 @@ func (p Polynomial) Degree() int {
return i
}

// Evaluate returns the evaluation of p on x.
func (p Polynomial) Evaluate(x group.Scalar) group.Scalar {
px := x.Group().NewScalar()
if l := len(p.c); l != 0 {
Expand All @@ -53,6 +56,16 @@ func (p Polynomial) Evaluate(x group.Scalar) group.Scalar {
return px
}

// Coefficients returns a deep-copy of the polynomial's coefficients in
// ascending order with respect to the degree.
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
159 changes: 159 additions & 0 deletions secretsharing/ss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Package secretsharing provides methods to split secrets in shares.
//
// Let n be the number of parties, and t the number of corrupted parties such
// that 0 <= t < n. A (t,n) secret sharing allows to split a secret into n
// shares, such that the secret can be recovered from any subset of t+1 shares.
//
// The NewShamirSecretSharing function creates a Shamir secret sharing [1],
// which relies on Lagrange polynomial interpolation.
//
// The NewFeldmanSecretSharing function creates a Feldman secret sharing [2],
// which extends Shamir's by allowing to verify that a share was honestly
// generated.
//
// 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 // ID uniquely identifies a share in a secret sharing instance.
Value group.Scalar // Value stores the share generated from a secret sharing instance.
}

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

// NewShamirSecretSharing implements a (t,n) Shamir's secret sharing.
// A (t,n) secret sharing allows to split a secret into n shares, such that the
// secret can be only recovered from any subset of t+1 shares. Returns an error
// if 0 <= t < n does not hold.
func NewShamirSecretSharing(g group.Group, t, n uint) (ss, error) {
if t >= n {
return ss{}, errors.New("secretsharing: bad parameters")
}
return ss{g: g, t: t, n: n}, nil
}

// Params returns the t and n parameters of the secret sharing.
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].Value = poly.Evaluate(x)
}

return shares
}

// Shard splits the secret into n shares.
func (s ss) Shard(rnd io.Reader, secret group.Scalar) []Share {
return s.generateShares(s.polyFromSecret(rnd, secret))
}

// Recover returns the secret provided more than t shares are given. Returns an
// error if the number of shares is not above the threshold or goes beyond the
// maximum number of shares.
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, s.t+1)
px := make([]group.Scalar, s.t+1)
for i := range shares[:s.t+1] {
x[i] = s.g.NewScalar().SetUint64(uint64(shares[i].ID))
px[i] = shares[i].Value
}

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

return l.Evaluate(zero), nil
}

type SharesCommitment = []group.Element

type vss struct{ s ss }

// NewFeldmanSecretSharing implements a (t,n) Feldman's verifiable secret
// sharing. A (t,n) secret sharing allows to split a secret into n shares, such
// that the secret can be only recovered from any subset of t+1 shares. This
// method is verifiable because once the shares and the secret are committed
// during sharding, one can later verify whether the share was generated
// honestly. Returns an error if 0 < t <= n does not hold.
func NewFeldmanSecretSharing(g group.Group, t, n uint) (vss, error) {
s, err := NewShamirSecretSharing(g, t, n)
return vss{s}, err
}

// Params returns the t and n parameters of the secret sharing.
func (v vss) Params() (t, n uint) { return v.s.Params() }

// Shard splits the secret into n shares, and also returns a commitment to both
// the secret and the shares.
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
}

// Verify returns true if a share was produced by sharding a secret. It uses
// the share commitments generated by the Shard function to verify this
// property.
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.Value)
return polI.IsEqual(sum)
}

// Recover returns the secret provided more than t shares are given. Returns an
// error if the number of shares is not above the threshold (t) or is larger
// than the maximum number of shares (n).
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(2)
n := uint(5)

s, err := secretsharing.NewShamirSecretSharing(g, t, n)
test.CheckNoErr(tt, err, "failed to create Shamir secret sharing")

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 !(int(t) < k && k <= int(n)) {
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.NewFeldmanSecretSharing(g, t, n)
test.CheckNoErr(tt, err, "failed to create Feldman secret sharing")

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].Value = shares[i].Value.Copy()
badShares[i].Value.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.NewShamirSecretSharing(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.NewFeldmanSecretSharing(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 28db5bf

Please sign in to comment.