From 44925f7aee25f0a8459c37a9dde909a6ee4c0cd4 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. --- group/secretsharing/poly.go | 73 +++++++++++++++++++ group/secretsharing/poly_test.go | 60 ++++++++++++++++ group/secretsharing/ss.go | 114 +++++++++++++++++++++++++++++ group/secretsharing/ss_test.go | 119 +++++++++++++++++++++++++++++++ 4 files changed, 366 insertions(+) create mode 100644 group/secretsharing/poly.go create mode 100644 group/secretsharing/poly_test.go create mode 100644 group/secretsharing/ss.go create mode 100644 group/secretsharing/ss_test.go diff --git a/group/secretsharing/poly.go b/group/secretsharing/poly.go new file mode 100644 index 00000000..235c852d --- /dev/null +++ b/group/secretsharing/poly.go @@ -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 +} diff --git a/group/secretsharing/poly_test.go b/group/secretsharing/poly_test.go new file mode 100644 index 00000000..945ebcaf --- /dev/null +++ b/group/secretsharing/poly_test.go @@ -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) + } +} diff --git a/group/secretsharing/ss.go b/group/secretsharing/ss.go new file mode 100644 index 00000000..de318a39 --- /dev/null +++ b/group/secretsharing/ss.go @@ -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) +} diff --git a/group/secretsharing/ss_test.go b/group/secretsharing/ss_test.go new file mode 100644 index 00000000..d995131f --- /dev/null +++ b/group/secretsharing/ss_test.go @@ -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) + } + }) +}