forked from cloudflare/circl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Based on the work in the `secretSharing` branch from the original circl repository. https://github.com/cloudflare/circl/tree/secretSharing
- Loading branch information
Showing
5 changed files
with
358 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,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 |
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,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> | ||
} |
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,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) | ||
} |
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,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) | ||
} | ||
}) | ||
} |