Skip to content

Commit

Permalink
Verifiable Secret Sharing
Browse files Browse the repository at this point in the history
Based on the work in the `secretSharing` branch from the original
circl repository.

https://github.com/cloudflare/circl/tree/secretSharing
  • Loading branch information
bcessa committed Jun 10, 2023
1 parent d312a7a commit d18cc37
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 0 deletions.
9 changes: 9 additions & 0 deletions math/polynomial/polynomial.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ func (l LagrangePolynomial) Evaluate(x group.Scalar) group.Scalar {
return px
}

// Coefficient returns a deep-copy of the n-th polynomial's coefficient.
// Note coefficients are sorted in ascending order with respect to the degree.
func (p Polynomial) Coefficient(n uint) group.Scalar {
if int(n) >= len(p.c) {
panic("polynomial: invalid index for coefficient")
}
return p.c[n].Copy()
}

// LagrangeBase returns the j-th Lagrange polynomial base evaluated at x.
// Thus, L_j(x) = \prod (x - x[i]) / (x[j] - x[i]) for 0 <= i < k, and i != j.
func LagrangeBase(jth uint, xi []group.Scalar, x group.Scalar) group.Scalar {
Expand Down
25 changes: 25 additions & 0 deletions vss/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Package vss provides methods to split secrets into 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 at least `t+1` different shares.
A Shamir secret sharing [1] relies on Lagrange polynomial interpolation.
A Feldman secret sharing [2] extends Shamir's by committing the secret,
which allows to verify that a share is part of the committed secret.
`New` returns a SecretSharing compatible with Shamir secret sharing.
The SecretSharing can be verifiable (compatible with Feldman secret
sharing) using the `CommitSecret` and `Verify` functions.
In this implementation, secret sharing is defined over the scalar field
of a prime order group.
References
[1] https://dl.acm.org/doi/10.1145/359168.359176/
[2] https://ieeexplore.ieee.org/document/4568297/
*/
package vss
56 changes: 56 additions & 0 deletions vss/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package vss

import (
"crypto/rand"
"fmt"

"go.bryk.io/circl/group"
)

func ExampleSecretSharing() {
g := group.P256
t := uint(2)
n := uint(5)

secret := g.RandomScalar(rand.Reader)
ss := New(rand.Reader, t, secret)
shares := ss.Share(n)

got, err := Recover(t, shares[:t])
fmt.Printf("Recover secret: %v\nError: %v\n", secret.IsEqual(got), err)

got, err = Recover(t, shares[:t+1])
fmt.Printf("Recover secret: %v\nError: %v\n", secret.IsEqual(got), err)
// Output:
// Recover secret: false
// Error: vss: number of shares (n=2) must be above the threshold (t=2)
// Recover secret: true
// Error: <nil>
}

func ExampleVerify() {
g := group.P256
t := uint(2)
n := uint(5)

secret := g.RandomScalar(rand.Reader)
ss := New(rand.Reader, t, secret)
shares := ss.Share(n)
verifiers := ss.CommitSecret()

for i := range shares {
ok := Verify(t, shares[i], verifiers)
fmt.Printf("Share %v is valid: %v\n", i, ok)
}

got, err := Recover(t, shares)
fmt.Printf("Recover secret: %v\nError: %v\n", secret.IsEqual(got), err)
// Output:
// Share 0 is valid: true
// Share 1 is valid: true
// Share 2 is valid: true
// Share 3 is valid: true
// Share 4 is valid: true
// Recover secret: true
// Error: <nil>
}
123 changes: 123 additions & 0 deletions vss/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package vss

import (
"fmt"
"io"

"go.bryk.io/circl/group"
"go.bryk.io/circl/math/polynomial"
)

// Share represents a share of a secret.
type Share struct {
// ID uniquely identifies a share in a secret sharing instance. ID is never zero.
ID group.Scalar
// Value stores the share generated by a secret sharing instance.
Value group.Scalar
}

// SecretCommitment is the set of commitments generated by splitting a secret.
type SecretCommitment = []group.Element

// SecretSharing provides a (t,n) Shamir's secret sharing. It allows splitting
// a secret into n shares, such that the secret can be only recovered from
// any subset of t+1 shares.
type SecretSharing struct {
g group.Group
t uint
poly polynomial.Polynomial
}

// New returns a SecretSharing providing a (t,n) Shamir's secret sharing.
// It allows splitting a secret into n shares, such that the secret is
// only recovered from any subset of at least t+1 shares.
func New(rnd io.Reader, t uint, secret group.Scalar) SecretSharing {
c := make([]group.Scalar, t+1)
c[0] = secret.Copy()
g := secret.Group()
for i := 1; i < len(c); i++ {
c[i] = g.RandomScalar(rnd)
}

return SecretSharing{g: g, t: t, poly: polynomial.New(c)}
}

// Share creates n shares with an ID monotonically increasing from 1 to n.
func (ss SecretSharing) Share(n uint) []Share {
shares := make([]Share, n)
id := ss.g.NewScalar()
for i := range shares {
shares[i] = ss.ShareWithID(id.SetUint64(uint64(i + 1)))
}

return shares
}

// ShareWithID creates one share of the secret using the ID as identifier.
// Notice that shares with the same ID are considered equal.
// Panics, if the ID is zero.
func (ss SecretSharing) ShareWithID(id group.Scalar) Share {
if id.IsZero() {
panic("secretsharing: id cannot be zero")
}

return Share{
ID: id.Copy(),
Value: ss.poly.Evaluate(id),
}
}

// CommitSecret creates a commitment to the secret for further verifying shares.
func (ss SecretSharing) CommitSecret() SecretCommitment {
c := make(SecretCommitment, ss.poly.Degree()+1)
for i := range c {
c[i] = ss.g.NewElement().MulGen(ss.poly.Coefficient(uint(i)))
}
return c
}

// Verify returns true if the share s was produced by sharing a secret with
// threshold t and commitment of the secret c.
func Verify(t uint, s Share, c SecretCommitment) bool {
if len(c) != int(t+1) {
return false
}
if s.ID.IsZero() {
return false
}

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

// Recover returns a secret provided more than t different shares are given.
// Returns an error if the number of shares is not above the threshold t.
// Panics if some shares are duplicated, i.e., shares must have different IDs.
func Recover(t uint, shares []Share) (secret group.Scalar, err error) {
if l := len(shares); l <= int(t) {
return nil, errThreshold(t, uint(l))
}

x := make([]group.Scalar, t+1)
px := make([]group.Scalar, t+1)
for i := range shares[:t+1] {
x[i] = shares[i].ID
px[i] = shares[i].Value
}

l := polynomial.NewLagrangePolynomial(x, px)
zero := shares[0].ID.Group().NewScalar()

return l.Evaluate(zero), nil
}

func errThreshold(t, n uint) error {
return fmt.Errorf("vss: number of shares (n=%v) must be above the threshold (t=%v)", n, t)
}
145 changes: 145 additions & 0 deletions vss/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package vss

import (
"crypto/rand"
"testing"

"go.bryk.io/circl/group"
"go.bryk.io/circl/internal/test"
)

func TestSecretSharing(tt *testing.T) {
g := group.P256
t := uint(2)
n := uint(5)

secret := g.RandomScalar(rand.Reader)
ss := New(rand.Reader, t, secret)
shares := ss.Share(n)
test.CheckOk(len(shares) == int(n), "bad num shares", tt)
verifiers := ss.CommitSecret()

tt.Run("subsetSize", func(ttt *testing.T) {
// Test any possible subset size.
for k := 0; k <= int(n); k++ {
got, err := Recover(t, 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")
want := secret
if !got.IsEqual(want) {
test.ReportError(ttt, got, want, t, k, n)
}
}
}
})

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

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

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

tt.Run("badCommitments", func(ttt *testing.T) {
badComs := make(SecretCommitment, len(verifiers))
for i := range verifiers {
badComs[i] = verifiers[i].Copy()
badComs[i].Dbl(badComs[i])
}

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

func TestShareWithID(tt *testing.T) {
g := group.P256
t := uint(2)
n := uint(5)
secret := g.RandomScalar(rand.Reader)
ss := New(rand.Reader, t, secret)

tt.Run("recoverOk", func(ttt *testing.T) {
// SecretSharing can create shares at will, not exactly n many.
shares := []Share{
ss.ShareWithID(g.RandomScalar(rand.Reader)),
ss.ShareWithID(g.RandomScalar(rand.Reader)),
ss.ShareWithID(g.RandomScalar(rand.Reader)),
}
got, err := Recover(t, shares)
test.CheckNoErr(tt, err, "failed to recover the secret")
want := secret
if !got.IsEqual(want) {
test.ReportError(tt, got, want, t, n)
}
})

tt.Run("duplicatedFail", func(ttt *testing.T) {
// Panics if trying to recover duplicated shares.
share := ss.ShareWithID(g.RandomScalar(rand.Reader))
sameShares := []Share{share, share, share}
err := test.CheckPanic(func() {
got, err := Recover(t, sameShares)
test.CheckIsErr(tt, err, "must fail to recover the secret")
test.CheckOk(got == nil, "must not recover", tt)
})
test.CheckOk(err == nil, "must panic", tt)
})
}

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

secret := g.RandomScalar(rand.Reader)
ss := New(rand.Reader, t, secret)
shares := ss.Share(n)
verifiers := ss.CommitSecret()

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

b.Run("Share", func(b *testing.B) {
for i := 0; i < b.N; i++ {
ss.Share(n)
}
})

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

b.Run("CommitSecret", func(b *testing.B) {
for i := 0; i < b.N; i++ {
ss.CommitSecret()
}
})

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

0 comments on commit d18cc37

Please sign in to comment.