Skip to content

Commit

Permalink
Add challenge check PVSS (#520)
Browse files Browse the repository at this point in the history
* Expose error types
* Add dec challenge verification
* Move globalChallenge check to VerifyEncShare
* Use Horner method to compute commitments
* Change computeCommitments signature
* Remove unused error
  • Loading branch information
K1li4nL authored Jun 24, 2024
1 parent 60c866b commit 72a3fc0
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 36 deletions.
125 changes: 108 additions & 17 deletions share/pvss/pvss.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ package pvss

import (
"errors"
"fmt"

"go.dedis.ch/kyber/v4"
"go.dedis.ch/kyber/v4/proof/dleq"
Expand All @@ -31,10 +32,12 @@ type Suite interface {
}

// Some error definitions.
var errorTooFewShares = errors.New("not enough shares to recover secret")
var errorDifferentLengths = errors.New("inputs of different lengths")
var errorEncVerification = errors.New("verification of encrypted share failed")
var errorDecVerification = errors.New("verification of decrypted share failed")
var ErrTooFewShares = errors.New("not enough shares to recover secret")
var ErrDifferentLengths = errors.New("inputs of different lengths")
var ErrEncVerification = errors.New("verification of encrypted share failed")
var ErrDecVerification = errors.New("verification of decrypted share failed")
var ErrGlobalChallengeVerification = errors.New("failed to verify global challenge")
var ErrDecShareChallengeVerification = errors.New("failed to verify the share decryption challenge")

// PubVerShare is a public verifiable share.
type PubVerShare struct {
Expand Down Expand Up @@ -83,27 +86,99 @@ func EncShares(suite Suite, H kyber.Point, X []kyber.Point, secret kyber.Scalar,
return encShares, pubPoly, nil
}

func computeCommitments(suite Suite, n int, polyComs []kyber.Point) []kyber.Point {
coms := make([]kyber.Point, n)

// Compute Xi = C0 + iC1 + (i^2)C2 + ... + (i^(t-1))C_(t-1) for i in [1, ..., n]
// Using Horner's method: Xi = C0 + i(C1 + i(C2 + i(....)))
for i := 0; i < n; i++ {
ith := suite.Scalar().SetInt64(int64(i) + 1)
acc := suite.Point().Null()

// From j=t-1 to j = 1 since last C0 is not multiplied by ith
for j := len(polyComs) - 1; j > 0; j-- {
acc.Add(acc, polyComs[j])
acc.Mul(ith, acc)
}

acc.Add(acc, polyComs[0])
coms[i] = acc
}

return coms
}

func computeGlobalChallenge(suite Suite, n int, commit *share.PubPoly, encShares []*PubVerShare) (kyber.Scalar, error) {
_, polyComs := commit.Info()
coms := computeCommitments(suite, n, polyComs)

h := suite.Hash()
var err error
for _, com := range coms {
_, err = com.MarshalTo(h)
if err != nil {
return nil, err
}
}

for _, share := range encShares {
_, err = share.S.V.MarshalTo(h)
if err != nil {
return nil, err
}
}

for _, share := range encShares {
_, err = share.P.VG.MarshalTo(h)
if err != nil {
return nil, err
}
}

for _, share := range encShares {
_, err = share.P.VH.MarshalTo(h)
if err != nil {
return nil, err
}
}

cb := h.Sum(nil)
return suite.Scalar().Pick(suite.XOF(cb)), nil
}

// VerifyEncShare checks that the encrypted share sX satisfies
// log_{H}(sH) == log_{X}(sX) where sH is the public commitment computed by
// evaluating the public commitment polynomial at the encrypted share's index i.
func VerifyEncShare(suite Suite, H kyber.Point, X kyber.Point, sH kyber.Point, encShare *PubVerShare) error {
func VerifyEncShare(suite Suite, H kyber.Point, X kyber.Point, sH kyber.Point, expGlobalChallenge kyber.Scalar, encShare *PubVerShare) error {
if !encShare.P.C.Equal(expGlobalChallenge) {
return ErrGlobalChallengeVerification
}

if err := encShare.P.Verify(suite, H, X, sH, encShare.S.V); err != nil {
return errorEncVerification
return ErrEncVerification
}
return nil
}

// VerifyEncShareBatch provides the same functionality as VerifyEncShare but for
// slices of encrypted shares. The function returns the valid encrypted shares
// together with the corresponding public keys.
func VerifyEncShareBatch(suite Suite, H kyber.Point, X []kyber.Point, sH []kyber.Point, encShares []*PubVerShare) ([]kyber.Point, []*PubVerShare, error) {
func VerifyEncShareBatch(suite Suite, H kyber.Point, X []kyber.Point, sH []kyber.Point, commit *share.PubPoly, encShares []*PubVerShare) ([]kyber.Point, []*PubVerShare, error) {
if len(X) != len(sH) || len(sH) != len(encShares) {
return nil, nil, errorDifferentLengths
return nil, nil, ErrDifferentLengths
}
var K []kyber.Point // good public keys
var E []*PubVerShare // good encrypted shares

// Need to compute the global challenge and verify the encrypted shares
expGlobalChallenge, err := computeGlobalChallenge(suite, len(X), commit, encShares)
if err != nil {
return nil, nil, err
}

for i := 0; i < len(X); i++ {
if err := VerifyEncShare(suite, H, X[i], sH[i], encShares[i]); err == nil {

if err := VerifyEncShare(suite, H, X[i], sH[i], expGlobalChallenge, encShares[i]); err == nil {
K = append(K, X[i])
E = append(E, encShares[i])
}
Expand All @@ -114,10 +189,11 @@ func VerifyEncShareBatch(suite Suite, H kyber.Point, X []kyber.Point, sH []kyber
// DecShare first verifies the encrypted share against the encryption
// consistency proof and, if valid, decrypts it and creates a decryption
// consistency proof.
func DecShare(suite Suite, H kyber.Point, X kyber.Point, sH kyber.Point, x kyber.Scalar, encShare *PubVerShare) (*PubVerShare, error) {
if err := VerifyEncShare(suite, H, X, sH, encShare); err != nil {
func DecShare(suite Suite, H kyber.Point, X kyber.Point, sH kyber.Point, x, expGlobalChallenge kyber.Scalar, encShare *PubVerShare) (*PubVerShare, error) {
if err := VerifyEncShare(suite, H, X, sH, expGlobalChallenge, encShare); err != nil {
return nil, err
}

G := suite.Point().Base()
V := suite.Point().Mul(suite.Scalar().Inv(x), encShare.S.V) // decryption: x^{-1} * (xS)
ps := &share.PubShare{I: encShare.S.I, V: V}
Expand All @@ -131,15 +207,15 @@ func DecShare(suite Suite, H kyber.Point, X kyber.Point, sH kyber.Point, x kyber
// DecShareBatch provides the same functionality as DecShare but for slices of
// encrypted shares. The function returns the valid encrypted and decrypted
// shares as well as the corresponding public keys.
func DecShareBatch(suite Suite, H kyber.Point, X []kyber.Point, sH []kyber.Point, x kyber.Scalar, encShares []*PubVerShare) ([]kyber.Point, []*PubVerShare, []*PubVerShare, error) {
func DecShareBatch(suite Suite, H kyber.Point, X []kyber.Point, sH []kyber.Point, x kyber.Scalar, expGlobalChallenges []kyber.Scalar, encShares []*PubVerShare) ([]kyber.Point, []*PubVerShare, []*PubVerShare, error) {
if len(X) != len(sH) || len(sH) != len(encShares) {
return nil, nil, nil, errorDifferentLengths
return nil, nil, nil, ErrDifferentLengths
}
var K []kyber.Point // good public keys
var E []*PubVerShare // good encrypted shares
var D []*PubVerShare // good decrypted shares
for i := 0; i < len(encShares); i++ {
if ds, err := DecShare(suite, H, X[i], sH[i], x, encShares[i]); err == nil {
if ds, err := DecShare(suite, H, X[i], sH[i], x, expGlobalChallenges[i], encShares[i]); err == nil {
K = append(K, X[i])
E = append(E, encShares[i])
D = append(D, ds)
Expand All @@ -151,18 +227,33 @@ func DecShareBatch(suite Suite, H kyber.Point, X []kyber.Point, sH []kyber.Point
// VerifyDecShare checks that the decrypted share sG satisfies
// log_{G}(X) == log_{sG}(sX). Note that X = xG and sX = s(xG) = x(sG).
func VerifyDecShare(suite Suite, G kyber.Point, X kyber.Point, encShare *PubVerShare, decShare *PubVerShare) error {
// Compute challenge for the decShare
h := suite.Hash()
X.MarshalTo(h)
encShare.S.V.MarshalTo(h)
decShare.P.VG.MarshalTo(h)
decShare.P.VH.MarshalTo(h)
cb := h.Sum(nil)
expDecChallenge := suite.Scalar().Pick(suite.XOF(cb))

if !decShare.P.C.Equal(expDecChallenge) {
return ErrDecShareChallengeVerification
}

if err := decShare.P.Verify(suite, G, decShare.S.V, X, encShare.S.V); err != nil {
return errorDecVerification
return fmt.Errorf("didn't verify: %w", ErrDecVerification)
}

return nil
}

// VerifyDecShareBatch provides the same functionality as VerifyDecShare but for
// slices of decrypted shares. The function returns the the valid decrypted shares.
func VerifyDecShareBatch(suite Suite, G kyber.Point, X []kyber.Point, encShares []*PubVerShare, decShares []*PubVerShare) ([]*PubVerShare, error) {
if len(X) != len(encShares) || len(encShares) != len(decShares) {
return nil, errorDifferentLengths
return nil, fmt.Errorf("didn't verify: %w", ErrDifferentLengths)
}

var D []*PubVerShare // good decrypted shares
for i := 0; i < len(X); i++ {
if err := VerifyDecShare(suite, G, X[i], encShares[i], decShares[i]); err == nil {
Expand All @@ -180,7 +271,7 @@ func RecoverSecret(suite Suite, G kyber.Point, X []kyber.Point, encShares []*Pub
return nil, err
}
if len(D) < t {
return nil, errorTooFewShares
return nil, ErrTooFewShares
}
var shares []*share.PubShare
for _, s := range D {
Expand Down
96 changes: 77 additions & 19 deletions share/pvss/pvss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,53 @@ import (
"github.com/stretchr/testify/require"
"go.dedis.ch/kyber/v4"
"go.dedis.ch/kyber/v4/group/edwards25519"
"go.dedis.ch/kyber/v4/proof/dleq"
"go.dedis.ch/kyber/v4/share"
)

func TestComputePolyCommitments(test *testing.T) {
suite := edwards25519.NewBlakeSHA256Ed25519()
n := 20
t := 15
H := suite.Point().Pick(suite.XOF([]byte("H")))
secret := suite.Scalar().Pick(suite.RandomStream())
priPoly := share.NewPriPoly(suite, t, secret, suite.RandomStream())

x := make([]kyber.Scalar, n) // trustee private keys
X := make([]kyber.Point, n) // trustee public keys
for i := 0; i < n; i++ {
x[i] = suite.Scalar().Pick(suite.RandomStream())
X[i] = suite.Point().Mul(x[i], nil)
}

pubPoly := priPoly.Commit(H)
// Create secret set of shares
priShares := priPoly.Shares(n)

// Prepare data for encryption consistency proofs ...
indices := make([]uint32, n)
values := make([]kyber.Scalar, n)
HS := make([]kyber.Point, n)
for i := 0; i < n; i++ {
indices[i] = priShares[i].I
values[i] = priShares[i].V
HS[i] = H
}

_, expectedComm, _, err := dleq.NewDLEQProofBatch(suite, HS, X, values)
require.NoError(test, err)

_, com := pubPoly.Info()
actualComm := computeCommitments(suite, n, com)

require.Equal(test, n, len(expectedComm))
require.Equal(test, len(expectedComm), len(actualComm))

for i := 0; i < n; i++ {
require.Equal(test, expectedComm[i].String(), actualComm[i].String())
}
}

func TestPVSS(test *testing.T) {
suite := edwards25519.NewBlakeSHA256Ed25519()
G := suite.Point().Base()
Expand Down Expand Up @@ -38,8 +83,11 @@ func TestPVSS(test *testing.T) {
var E []*PubVerShare // good encrypted shares
var D []*PubVerShare // good decrypted shares

globalChallenge, err := computeGlobalChallenge(suite, n, pubPoly, encShares)
require.NoError(test, err)

for i := 0; i < n; i++ {
if ds, err := DecShare(suite, H, X[i], sH[i], x[i], encShares[i]); err == nil {
if ds, err := DecShare(suite, H, X[i], sH[i], x[i], globalChallenge, encShares[i]); err == nil {
K = append(K, X[i])
E = append(E, encShares[i])
D = append(D, ds)
Expand Down Expand Up @@ -72,10 +120,6 @@ func TestPVSSDelete(test *testing.T) {
encShares, pubPoly, err := EncShares(suite, H, X, secret, t)
require.Equal(test, err, nil)

// Corrupt some of the encrypted shares
encShares[0].S.V = suite.Point().Null()
encShares[5].S.V = suite.Point().Null()

// (2) Share decryption (trustees)
sH := make([]kyber.Point, n)
for i := 0; i < n; i++ {
Expand All @@ -86,16 +130,21 @@ func TestPVSSDelete(test *testing.T) {
var E []*PubVerShare // good encrypted shares
var D []*PubVerShare // good decrypted shares

globalChallenge, err := computeGlobalChallenge(suite, len(X), pubPoly, encShares)
require.NoError(test, err)

for i := 0; i < n; i++ {
if ds, err := DecShare(suite, H, X[i], sH[i], x[i], encShares[i]); err == nil {
if ds, err := DecShare(suite, H, X[i], sH[i], x[i], globalChallenge, encShares[i]); err == nil {
K = append(K, X[i])
E = append(E, encShares[i])
D = append(D, ds)
}
}

// Corrupt some of the decrypted shares
D[0].S.V = suite.Point().Null()
D[1].S.V = suite.Point().Null()
D[2].S.V = suite.Point().Null()

// (3) Check decrypted shares and recover secret if possible (dealer/3rd party)
recovered, err := RecoverSecret(suite, G, K, E, D, t, n)
Expand Down Expand Up @@ -123,10 +172,6 @@ func TestPVSSDeleteFail(test *testing.T) {
encShares, pubPoly, err := EncShares(suite, H, X, secret, t)
require.Equal(test, err, nil)

// Corrupt some of the encrypted shares
encShares[0].S.V = suite.Point().Null()
encShares[5].S.V = suite.Point().Null()

// (2) Share decryption (trustees)
sH := make([]kyber.Point, n)
for i := 0; i < n; i++ {
Expand All @@ -137,8 +182,11 @@ func TestPVSSDeleteFail(test *testing.T) {
var E []*PubVerShare // good encrypted shares
var D []*PubVerShare // good decrypted shares

globalChallenge, err := computeGlobalChallenge(suite, n, pubPoly, encShares)
require.NoError(test, err)

for i := 0; i < n; i++ {
if ds, err := DecShare(suite, H, X[i], sH[i], x[i], encShares[i]); err == nil {
if ds, err := DecShare(suite, H, X[i], sH[i], x[i], globalChallenge, encShares[i]); err == nil {
K = append(K, X[i])
E = append(E, encShares[i])
D = append(D, ds)
Expand All @@ -148,10 +196,12 @@ func TestPVSSDeleteFail(test *testing.T) {
// Corrupt enough decrypted shares to make the secret unrecoverable
D[0].S.V = suite.Point().Null()
D[1].S.V = suite.Point().Null()
D[2].S.V = suite.Point().Null()
D[3].S.V = suite.Point().Null()

// (3) Check decrypted shares and recover secret if possible (dealer/3rd party)
_, err = RecoverSecret(suite, G, K, E, D, t, n)
require.Equal(test, err, errorTooFewShares) // this test is supposed to fail
require.Equal(test, err, ErrTooFewShares) // this test is supposed to fail
}

func TestPVSSBatch(test *testing.T) {
Expand Down Expand Up @@ -190,13 +240,13 @@ func TestPVSSBatch(test *testing.T) {
}

// Batch verification
X0, E0, err := VerifyEncShareBatch(suite, H, X, sH0, e0)
X0, E0, err := VerifyEncShareBatch(suite, H, X, sH0, p0, e0)
require.Equal(test, err, nil)

X1, E1, err := VerifyEncShareBatch(suite, H, X, sH1, e1)
X1, E1, err := VerifyEncShareBatch(suite, H, X, sH1, p1, e1)
require.Equal(test, err, nil)

X2, E2, err := VerifyEncShareBatch(suite, H, X, sH2, e2)
X2, E2, err := VerifyEncShareBatch(suite, H, X, sH2, p2, e2)
require.Equal(test, err, nil)

// Reorder (some) poly evals, keys, and shares
Expand All @@ -215,17 +265,25 @@ func TestPVSSBatch(test *testing.T) {
Z2 := []*PubVerShare{E0[2], E1[2], E2[2]}
Z3 := []*PubVerShare{E0[3], E1[3], E2[3]}

globalChallenges := make([]kyber.Scalar, 3)
globalChallenges[0], err = computeGlobalChallenge(suite, n, p0, e0)
require.NoError(test, err)
globalChallenges[1], err = computeGlobalChallenge(suite, n, p1, e1)
require.NoError(test, err)
globalChallenges[2], err = computeGlobalChallenge(suite, n, p2, e2)
require.NoError(test, err)

// (2) Share batch decryption (trustees)
KD0, ED0, DD0, err := DecShareBatch(suite, H, Y0, P0, x[0], Z0)
KD0, ED0, DD0, err := DecShareBatch(suite, H, Y0, P0, x[0], globalChallenges, Z0)
require.Equal(test, err, nil)

KD1, ED1, DD1, err := DecShareBatch(suite, H, Y1, P1, x[1], Z1)
KD1, ED1, DD1, err := DecShareBatch(suite, H, Y1, P1, x[1], globalChallenges, Z1)
require.Equal(test, err, nil)

KD2, ED2, DD2, err := DecShareBatch(suite, H, Y2, P2, x[2], Z2)
KD2, ED2, DD2, err := DecShareBatch(suite, H, Y2, P2, x[2], globalChallenges, Z2)
require.Equal(test, err, nil)

KD3, ED3, DD3, err := DecShareBatch(suite, H, Y3, P3, x[3], Z3)
KD3, ED3, DD3, err := DecShareBatch(suite, H, Y3, P3, x[3], globalChallenges, Z3)
require.Equal(test, err, nil)

// Re-establish order
Expand Down

0 comments on commit 72a3fc0

Please sign in to comment.