Files
zeekay b5ae22fa1d JVSS could not verify an honest share, so it could never reshare
The proof accompanying each share built its response as
NewScalar().Mul(challenge).Mul(share). NewScalar is the ZERO scalar, so that is
0·c·share = 0 and the response carried the nonce alone; the share never entered
it. VerifyAndCombine gates every share on VerifyShare, so an entirely honest
round ended as "invalid share from a to a". The package's four tests passed
throughout because none of them called either function.

Behind that sat a commitment that could not have been verified against in any
case. createCommitment committed to the polynomial's EVALUATIONS at 1..t while
evaluateCommitment read the same points back as COEFFICIENTS, and both halves
of the intended Pedersen pair were taken on the base point, so the blinding
polynomial added no hiding — a second generator was noted as missing and never
supplied.

polynomial.Exponent is that commitment, correct and already in this module:
F(X) = [f(X)]·G, one point per coefficient, verified as F(x) == [f(x)]·G. Using
it removes the hand-rolled commitment, the proof, and the challenge — 123 lines
— and needs no proof at all, since the exponent check IS the verification.

The tests now cover what the old ones stepped around: an honest share verifies,
a share does not verify as another party's or against another dealer's
commitment, and a full round completes.
2026-09-01 20:03:13 -07:00

85 lines
2.7 KiB
Go

package jvss
import (
"testing"
"github.com/luxfi/threshold/pkg/math/curve"
"github.com/luxfi/threshold/pkg/math/polynomial"
"github.com/luxfi/threshold/pkg/party"
)
// A verifiable sharing has to accept an honest share. The commitment is the
// dealer's polynomial in the exponent, so the check is F(x) == [f(x)]·G.
func TestAnHonestShareVerifies(t *testing.T) {
ids := []party.ID{"a", "b", "c"}
j := NewJVSS(curve.Secp256k1{}, 2, ids, "a")
shares, commitment, _, err := j.GenerateShares()
if err != nil {
t.Fatalf("generate: %v", err)
}
for _, id := range ids {
if !j.VerifyShare(shares[id], commitment, id) {
t.Errorf("party %s: an honest share was rejected", id)
}
}
}
// The negative controls, without which the test above passes against a
// VerifyShare that answers true unconditionally.
func TestVerifyShareRejectsWhatItShould(t *testing.T) {
group := curve.Secp256k1{}
ids := []party.ID{"a", "b", "c"}
j := NewJVSS(group, 2, ids, "a")
shares, commitment, _, err := j.GenerateShares()
if err != nil {
t.Fatalf("generate: %v", err)
}
if j.VerifyShare(shares["a"], commitment, "b") {
t.Error("a's share verified as b's")
}
// Another dealer's commitment describes another polynomial.
_, other, _, err := j.GenerateShares()
if err != nil {
t.Fatalf("generate: %v", err)
}
if j.VerifyShare(shares["a"], other, "a") {
t.Error("a share verified against a commitment it does not belong to")
}
if j.VerifyShare(&Share{Value: group.NewScalar()}, commitment, "a") {
t.Error("a zero share verified")
}
if j.VerifyShare(nil, commitment, "a") || j.VerifyShare(shares["a"], nil, "a") {
t.Error("a nil argument verified")
}
}
// The whole protocol has to complete. VerifyAndCombine gates every share on
// VerifyShare, so a sharing that cannot verify cannot reshare at all — the
// failure this package was in reached exactly here, as "invalid share from a
// to a" for a set of shares that were all honest.
func TestVerifyAndCombineCompletes(t *testing.T) {
ids := []party.ID{"a", "b", "c"}
j := NewJVSS(curve.Secp256k1{}, 2, ids, "a")
byDealer := make(map[party.ID]map[party.ID]*Share, len(ids))
commitments := make(map[party.ID]*polynomial.Exponent, len(ids))
for _, dealer := range ids {
shares, commitment, _, err := j.GenerateShares()
if err != nil {
t.Fatalf("dealer %s: %v", dealer, err)
}
byDealer[dealer] = shares
commitments[dealer] = commitment
}
if _, err := j.VerifyAndCombine(byDealer, commitments); err != nil {
t.Fatalf("an honest round did not complete: %v", err)
}
// And a corrupted share is still refused, by name.
byDealer["b"]["c"] = &Share{Value: curve.Secp256k1{}.NewScalar()}
if _, err := j.VerifyAndCombine(byDealer, commitments); err == nil {
t.Fatal("a corrupted share was combined")
}
}