From 28db5bfc9dd6bffcb17b444c669adbecca5757c2 Mon Sep 17 00:00:00 2001 From: armfazh Date: Mon, 11 Jul 2022 23:45:54 -0700 Subject: [PATCH] Implements Shamir and Feldman secret sharing. --- math/polynomial/polynomial.go | 13 +++ secretsharing/ss.go | 159 ++++++++++++++++++++++++++++++++++ secretsharing/ss_test.go | 149 +++++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 secretsharing/ss.go create mode 100644 secretsharing/ss_test.go diff --git a/math/polynomial/polynomial.go b/math/polynomial/polynomial.go index bee812742..5dfbe4a89 100644 --- a/math/polynomial/polynomial.go +++ b/math/polynomial/polynomial.go @@ -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() { @@ -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 { @@ -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: diff --git a/secretsharing/ss.go b/secretsharing/ss.go new file mode 100644 index 000000000..64c5ac55d --- /dev/null +++ b/secretsharing/ss.go @@ -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) } diff --git a/secretsharing/ss_test.go b/secretsharing/ss_test.go new file mode 100644 index 000000000..1fd09c9c7 --- /dev/null +++ b/secretsharing/ss_test.go @@ -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) + } + }) +}