-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements Shamir and Feldman secret sharing.
- Loading branch information
Showing
3 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} |