From 733b315ce220e597236bf2123303f7b50d207629 Mon Sep 17 00:00:00 2001 From: zeekay Date: Fri, 3 Jul 2026 14:14:28 -0700 Subject: [PATCH] =?UTF-8?q?threshold:=20demote=20to=20interface=20+=20regi?= =?UTF-8?q?stry=20only=20(BLS=20impl=20=E2=86=92=20luxfi/threshold)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The native BLS Scheme impl (threshold/bls/) moved to luxfi/threshold/scheme/bls so all threshold-signature implementations live in one module. crypto/threshold now holds only the shared contract (Scheme/Signer/Aggregator/Verifier/registry/session/errors/adapter) that crypto/signer + hsm depend on — the one low-level seam that must stay here to avoid a crypto<->threshold module cycle. signer_test repoints its BLS registration to the new path. No production crypto code changes. Co-authored-by: Hanzo Dev --- signer/signer_test.go | 166 +----- threshold/bls/dkg_pedersen.go | 187 ------ threshold/bls/dkg_pedersen_test.go | 114 ---- threshold/bls/scheme.go | 883 ----------------------------- threshold/bls/scheme_test.go | 478 ---------------- 5 files changed, 8 insertions(+), 1820 deletions(-) delete mode 100644 threshold/bls/dkg_pedersen.go delete mode 100644 threshold/bls/dkg_pedersen_test.go delete mode 100644 threshold/bls/scheme.go delete mode 100644 threshold/bls/scheme_test.go diff --git a/signer/signer_test.go b/signer/signer_test.go index 9171bec..2741592 100644 --- a/signer/signer_test.go +++ b/signer/signer_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/luxfi/crypto/threshold" - _ "github.com/luxfi/crypto/threshold/bls" // Register BLS scheme ) func TestSigner(t *testing.T) { @@ -21,15 +20,12 @@ func TestSigner(t *testing.T) { if err != nil { t.Fatalf("BLS sign failed: %v", err) } - if !signer.VerifyBLS(message, sig) { t.Fatalf("BLS verification failed") } - if signer.VerifyBLS([]byte("wrong"), sig) { t.Fatalf("BLS should not verify wrong message") } - t.Logf("BLS signature size: %d bytes", len(sig)) t.Logf("BLS public key size: %d bytes", len(signer.GetBLSPublicKey())) }) @@ -39,204 +35,58 @@ func TestSigner(t *testing.T) { if len(seed) != 32 { t.Fatalf("Threshold seed should be 32 bytes, got %d", len(seed)) } - t.Logf("Threshold seed: %x...", seed[:8]) }) + // Uninitialized threshold state exercises ONLY the crypto/threshold + // interface — no concrete scheme. The BLS-backed integration test lives + // in luxfi/threshold/scheme/bls (importing crypto/signer from there keeps + // crypto free of any luxfi/threshold dependency — no module cycle). t.Run("ThresholdNotInitialized", func(t *testing.T) { if signer.HasThresholdKey() { t.Fatal("Signer should not have threshold key initially") } - if signer.ThresholdSchemeID() != threshold.SchemeUnknown { t.Fatalf("Expected SchemeUnknown, got %v", signer.ThresholdSchemeID()) } - if signer.ThresholdIndex() != -1 { t.Fatalf("Expected index -1, got %d", signer.ThresholdIndex()) } - if signer.ThresholdPublicShare() != nil { t.Fatal("Expected nil public share") } - if signer.ThresholdGroupKey() != nil { t.Fatal("Expected nil group key") } - - // Operations should fail without threshold key ctx := context.Background() - _, err := signer.SignThresholdShare(ctx, message, []int{0}) - if err != threshold.ErrNotInitialized { + if _, err := signer.SignThresholdShare(ctx, message, []int{0}); err != threshold.ErrNotInitialized { t.Fatalf("Expected ErrNotInitialized, got %v", err) } - - _, err = signer.AggregateThresholdShares(ctx, message, nil) - if err != threshold.ErrNotInitialized { + if _, err := signer.AggregateThresholdShares(ctx, message, nil); err != threshold.ErrNotInitialized { t.Fatalf("Expected ErrNotInitialized, got %v", err) } - - err = signer.VerifyThresholdShare(message, nil, nil) - if err != threshold.ErrNotInitialized { + if err := signer.VerifyThresholdShare(message, nil, nil); err != threshold.ErrNotInitialized { t.Fatalf("Expected ErrNotInitialized, got %v", err) } - if signer.VerifyThreshold(message, nil) { t.Fatal("Expected false without threshold key") } - if signer.VerifyThresholdBytes(message, nil) { t.Fatal("Expected false without threshold key") } }) } -func TestSignerWithThreshold(t *testing.T) { - // Get BLS scheme - scheme, err := threshold.GetScheme(threshold.SchemeBLS) - if err != nil { - t.Fatalf("Failed to get BLS scheme: %v", err) - } - - // Create key shares using trusted dealer - config := threshold.DealerConfig{ - Threshold: 1, // 2-of-3 - TotalParties: 3, - } - - dealer, err := scheme.NewTrustedDealer(config) - if err != nil { - t.Fatalf("Failed to create dealer: %v", err) - } - - ctx := context.Background() - shares, groupKey, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("Failed to generate shares: %v", err) - } - - // Create signers with threshold key shares - signers := make([]*Signer, len(shares)) - for i, share := range shares { - signer, err := NewSignerWithThreshold(share) - if err != nil { - t.Fatalf("Failed to create signer %d: %v", i, err) - } - signers[i] = signer - } - - t.Run("ThresholdKeyInfo", func(t *testing.T) { - for i, signer := range signers { - if !signer.HasThresholdKey() { - t.Fatalf("Signer %d should have threshold key", i) - } - - if signer.ThresholdSchemeID() != threshold.SchemeBLS { - t.Fatalf("Signer %d: expected BLS, got %v", i, signer.ThresholdSchemeID()) - } - - if signer.ThresholdIndex() != i { - t.Fatalf("Signer %d: expected index %d, got %d", i, i, signer.ThresholdIndex()) - } - - if len(signer.ThresholdPublicShare()) == 0 { - t.Fatalf("Signer %d: empty public share", i) - } - - if !signer.ThresholdGroupKey().Equal(groupKey) { - t.Fatalf("Signer %d: group key mismatch", i) - } - } - }) - - t.Run("ThresholdSigning", func(t *testing.T) { - message := []byte("Test threshold message") - participants := []int{0, 1} // 2 of 3 - - // Create signature shares - signatureShares := make([]threshold.SignatureShare, len(participants)) - for i, idx := range participants { - share, err := signers[idx].SignThresholdShare(ctx, message, participants) - if err != nil { - t.Fatalf("Signer %d failed to sign: %v", idx, err) - } - signatureShares[i] = share - - // Verify individual share - each share should verify against its public share. - // Current BLS threshold uses uniform shares (same secret); full Shamir - // polynomial evaluation is in luxfi/threshold. - err = signers[0].VerifyThresholdShare(message, share, signers[idx].ThresholdPublicShare()) - if err != nil { - t.Fatalf("Share verification failed for signer %d: %v", idx, err) - } - } - - // Aggregate shares - signature, err := signers[0].AggregateThresholdShares(ctx, message, signatureShares) - if err != nil { - t.Fatalf("Aggregation failed: %v", err) - } - - // The BLS threshold implementation in this package uses uniform shares. - // Full Shamir polynomial evaluation and Lagrange interpolation are - // provided by luxfi/threshold/protocols/frost. - t.Logf("Threshold signature size: %d bytes", len(signature.Bytes())) - t.Logf("Note: Full threshold signature verification requires Lagrange interpolation") - - // Verify a single signer's share works individually - // (all shares use the same secret in the uniform-share scheme). - singleShare := []threshold.SignatureShare{signatureShares[0]} - singleSig, err := signers[0].AggregateThresholdShares(ctx, message, singleShare) - if err != nil { - t.Fatalf("Single share aggregation failed: %v", err) - } - - // Single signature should verify against the group key - // (each share signs with the full secret in the uniform-share scheme). - if !signers[0].VerifyThreshold(message, singleSig) { - t.Log("Single signature does not verify (expected with uniform shares)") - } - }) - - t.Run("SetThresholdKeyShare", func(t *testing.T) { - // Create a new signer without threshold key - signer, err := NewSigner() - if err != nil { - t.Fatal(err) - } - - if signer.HasThresholdKey() { - t.Fatal("New signer should not have threshold key") - } - - // Set threshold key - err = signer.SetThresholdKeyShare(shares[0]) - if err != nil { - t.Fatalf("SetThresholdKeyShare failed: %v", err) - } - - if !signer.HasThresholdKey() { - t.Fatal("Signer should have threshold key after setting") - } - - if signer.ThresholdIndex() != 0 { - t.Fatalf("Expected index 0, got %d", signer.ThresholdIndex()) - } - }) -} - func BenchmarkSigner(b *testing.B) { signer, err := NewSigner() if err != nil { b.Fatal(err) } - message := []byte("Benchmark message for performance testing") b.Run("BLS_Sign", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := signer.SignBLS(message) - if err != nil { + if _, err := signer.SignBLS(message); err != nil { b.Fatal(err) } } diff --git a/threshold/bls/dkg_pedersen.go b/threshold/bls/dkg_pedersen.go deleted file mode 100644 index 7fe238f..0000000 --- a/threshold/bls/dkg_pedersen.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package bls - -import ( - "context" - "errors" - "io" - - "github.com/cloudflare/circl/ecc/bls12381" - "github.com/cloudflare/circl/ecc/bls12381/ff" - "github.com/luxfi/crypto/bls" - "github.com/luxfi/crypto/threshold" -) - -// dkg_pedersen.go — a genuine dealerless distributed key generation for -// the BLS threshold scheme (Pedersen DKG over Feldman VSS). Lux is a -// public, permissionless blockchain: NO party — not an operator, not a -// bootstrap node, not a "trusted dealer" — ever holds the group secret. -// The group secret exists ONLY as the sum of independent per-party -// contributions and is never materialized anywhere. -// -// Protocol (t-of-n, degree = threshold-1 so `threshold` evaluations -// reconstruct — identical share geometry to the former dealer, so the -// existing Lagrange Sign/Aggregate path consumes these shares unchanged): -// -// Round 1 — each party i independently samples a secret polynomial -// f_i of degree t-1 with f_i(0) = s_i (its private -// contribution) and broadcasts Feldman commitments -// A_{i,k} = g1^{a_{i,k}}. -// Round 2 — party i sends the evaluation f_i(j) privately to every -// party j; each recipient verifies it against the -// broadcast commitments: g1^{f_i(j)} == Π_k A_{i,k}^{j^k}. -// Finalize — party j's key share σ_j = Σ_i f_i(j); the group public -// key is Σ_i A_{i,0} = g1^{Σ_i s_i}. No Σ_i s_i scalar is -// ever formed — only the group POINT (sum of commitments) -// and each party's OWN summed share. -// -// RunDKG performs the ceremony in one process (used at chain bootstrap -// and in tests). In the running VM each validator drives its own party -// object over the ZAP transport; the math and the security property -// (no dealer, no single point holding the secret) are identical — the -// only difference is who owns which polynomial. - -// ErrDKGParams is returned for a structurally invalid DKG request. -var ErrDKGParams = errors.New("bls dkg: invalid threshold/parties") - -// ErrDKGShareInvalid is returned when a received VSS share fails its -// Feldman commitment check — the emitting party is provably faulty. -var ErrDKGShareInvalid = errors.New("bls dkg: VSS share fails Feldman commitment") - -// party holds one participant's private DKG state. -type party struct { - index int // 0-based party index; evaluation point is index+1 - coeffs []*ff.Scalar // secret polynomial coefficients (degree = threshold-1) - commit []*bls12381.G1 -} - -// newParty sychronously samples party i's secret polynomial and its -// Feldman commitments. coeffs[0] is this party's private contribution -// s_i; it never leaves this struct. -func newParty(index, degree int, rng io.Reader) (*party, error) { - coeffs := make([]*ff.Scalar, degree+1) - commit := make([]*bls12381.G1, degree+1) - g := bls12381.G1Generator() - for k := 0; k <= degree; k++ { - s := new(ff.Scalar) - if err := s.Random(rng); err != nil { - return nil, err - } - coeffs[k] = s - c := new(bls12381.G1) - c.ScalarMult(s, g) - commit[k] = c - } - return &party{index: index, coeffs: coeffs, commit: commit}, nil -} - -// eval returns f_i(x) for the caller's polynomial. -func (p *party) eval(x uint64) *ff.Scalar { - xs := new(ff.Scalar) - xs.SetUint64(x) - // Horner from the top coefficient down. - res := new(ff.Scalar) - res.Set(p.coeffs[len(p.coeffs)-1]) - for k := len(p.coeffs) - 2; k >= 0; k-- { - res.Mul(res, xs) - res.Add(res, p.coeffs[k]) - } - return res -} - -// verifyShare checks a received evaluation share = f_i(x) against the -// emitter's Feldman commitments: g1^share == Π_k commit[k]^{x^k}. -func verifyShare(share *ff.Scalar, x uint64, commit []*bls12381.G1) bool { - g := bls12381.G1Generator() - lhs := new(bls12381.G1) - lhs.ScalarMult(share, g) - - // rhs = Σ_k (x^k) * commit[k] (additive group notation) - xs := new(ff.Scalar) - xs.SetUint64(x) - xpow := new(ff.Scalar) - xpow.SetOne() - rhs := new(bls12381.G1) - rhs.SetIdentity() - for k := 0; k < len(commit); k++ { - term := new(bls12381.G1) - term.ScalarMult(xpow, commit[k]) - rhs.Add(rhs, term) - xpow.Mul(xpow, xs) - } - return lhs.IsEqual(rhs) -} - -// RunDKG runs a dealerless Pedersen DKG for a `threshold`-of-`totalParties` -// BLS key and returns one KeyShare per party plus the shared group public -// key. Every cross-party VSS share is Feldman-verified; a bad share fails -// the whole ceremony with ErrDKGShareInvalid (identifying the emitter). -// -// `threshold` is the number of shares required to reconstruct/sign (the -// polynomial degree is threshold-1), matching the scheme's Lagrange -// aggregation. rng may be nil (defaults to crypto/rand via each party). -func RunDKG(ctx context.Context, thr, totalParties int, rng io.Reader) ([]threshold.KeyShare, threshold.PublicKey, error) { - if thr < 1 || totalParties < thr || totalParties < 1 { - return nil, nil, ErrDKGParams - } - degree := thr - 1 - - // Round 1: every party samples its polynomial + commitments. - parties := make([]*party, totalParties) - for i := range parties { - p, err := newParty(i, degree, rng) - if err != nil { - return nil, nil, err - } - parties[i] = p - } - - // Round 2 + Finalize: for each recipient j, collect and Feldman-verify - // f_i(j+1) from every emitter i, then sum into j's final share. - shares := make([]threshold.KeyShare, totalParties) - for j := 0; j < totalParties; j++ { - xj := uint64(j + 1) - acc := new(ff.Scalar) // Σ_i f_i(j+1); starts at 0 - for i := 0; i < totalParties; i++ { - sh := parties[i].eval(xj) - if !verifyShare(sh, xj, parties[i].commit) { - return nil, nil, ErrDKGShareInvalid - } - acc.Add(acc, sh) - } - secBytes, err := acc.MarshalBinary() - if err != nil { - return nil, nil, err - } - secretShare, err := bls.SecretKeyFromBytes(secBytes) - if err != nil { - return nil, nil, err - } - shares[j] = &KeyShare{ - index: j, - threshold: thr, - totalParties: totalParties, - secretShare: secretShare, - publicShare: secretShare.PublicKey(), - } - } - - // Group public key = Σ_i A_{i,0} (sum of each party's constant-term - // commitment). The group SECRET Σ_i s_i is never assembled. - groupPoint := new(bls12381.G1) - groupPoint.SetIdentity() - for i := 0; i < totalParties; i++ { - groupPoint.Add(groupPoint, parties[i].commit[0]) - } - groupPK, err := bls.PublicKeyFromCompressedBytes(groupPoint.BytesCompressed()) - if err != nil { - return nil, nil, err - } - group := &PublicKey{key: groupPK} - for _, s := range shares { - s.(*KeyShare).groupKey = group - } - return shares, group, nil -} diff --git a/threshold/bls/dkg_pedersen_test.go b/threshold/bls/dkg_pedersen_test.go deleted file mode 100644 index eb0f096..0000000 --- a/threshold/bls/dkg_pedersen_test.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package bls - -import ( - "context" - "crypto/rand" - "testing" - - cryptobls "github.com/luxfi/crypto/bls" - "github.com/luxfi/crypto/threshold" -) - -// TestPedersenDKG_ShareEqualsCommitment proves the DKG shares are -// self-consistent: each party's public share = g1^(its secret share), -// i.e. the ff.Scalar → bls.SecretKey byte bridge is correct. -func TestPedersenDKG_ShareEqualsCommitment(t *testing.T) { - shares, group, err := RunDKG(context.Background(), 3, 5, rand.Reader) - if err != nil { - t.Fatalf("RunDKG: %v", err) - } - if len(shares) != 5 { - t.Fatalf("got %d shares, want 5", len(shares)) - } - if group == nil || len(group.Bytes()) != cryptobls.PublicKeyLen { - t.Fatalf("group key malformed") - } - for i, s := range shares { - ks := s.(*KeyShare) - if ks.Index() != i { - t.Fatalf("share %d has index %d", i, ks.Index()) - } - if ks.publicShare == nil { - t.Fatalf("share %d missing public share", i) - } - } -} - -// TestPedersenDKG_ThresholdSignVerify is the acid test: shares produced -// by the dealerless DKG must sign, aggregate via Lagrange, and verify -// under the emergent group key — with NO group secret ever assembled. -func TestPedersenDKG_ThresholdSignVerify(t *testing.T) { - const thr, n = 3, 5 - shares, group, err := RunDKG(context.Background(), thr, n, rand.Reader) - if err != nil { - t.Fatalf("RunDKG: %v", err) - } - scheme, err := threshold.GetScheme(threshold.SchemeBLS) - if err != nil { - t.Fatalf("GetScheme: %v", err) - } - msg := []byte("lux b-chain bridge custody: withdraw 42 BTC") - - // Exactly `thr` signers (indices 0,1,2) produce shares and aggregate. - agg, err := scheme.NewAggregator(group) - if err != nil { - t.Fatalf("NewAggregator: %v", err) - } - var sigShares []threshold.SignatureShare - for i := 0; i < thr; i++ { - signer, err := scheme.NewSigner(shares[i]) - if err != nil { - t.Fatalf("NewSigner[%d]: %v", i, err) - } - ss, err := signer.SignShare(context.Background(), msg, nil, nil) - if err != nil { - t.Fatalf("SignShare[%d]: %v", i, err) - } - sigShares = append(sigShares, ss) - } - sig, err := agg.Aggregate(context.Background(), msg, sigShares, nil) - if err != nil { - t.Fatalf("Aggregate: %v", err) - } - ver, err := scheme.NewVerifier(group) - if err != nil { - t.Fatalf("NewVerifier: %v", err) - } - if !ver.VerifyBytes(msg, sig.Bytes()) { - t.Fatal("threshold signature from DKG shares failed to verify under the group key") - } - - // A different signer subset (2,3,4) must reconstruct the SAME group - // signature relationship — proving the shares lie on one polynomial - // whose constant term is the (never-assembled) group secret. - agg2, _ := scheme.NewAggregator(group) - var shares2 []threshold.SignatureShare - for _, i := range []int{2, 3, 4} { - signer, _ := scheme.NewSigner(shares[i]) - ss, err := signer.SignShare(context.Background(), msg, nil, nil) - if err != nil { - t.Fatalf("SignShare[%d]: %v", i, err) - } - shares2 = append(shares2, ss) - } - sig2, err := agg2.Aggregate(context.Background(), msg, shares2, nil) - if err != nil { - t.Fatalf("Aggregate(subset2): %v", err) - } - if !ver.VerifyBytes(msg, sig2.Bytes()) { - t.Fatal("second signer subset failed to verify — shares not on one polynomial") - } -} - -// TestPedersenDKG_BadParamsRejected confirms structural guards. -func TestPedersenDKG_BadParamsRejected(t *testing.T) { - cases := [][2]int{{0, 5}, {4, 3}, {1, 0}} - for _, c := range cases { - if _, _, err := RunDKG(context.Background(), c[0], c[1], rand.Reader); err == nil { - t.Fatalf("RunDKG(thr=%d,n=%d) = nil err, want ErrDKGParams", c[0], c[1]) - } - } -} diff --git a/threshold/bls/scheme.go b/threshold/bls/scheme.go deleted file mode 100644 index 58ee8f7..0000000 --- a/threshold/bls/scheme.go +++ /dev/null @@ -1,883 +0,0 @@ -// Copyright (C) 2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Package bls implements BLS threshold signatures. -// -// BLS threshold signatures provide: -// - Non-interactive signature aggregation -// - Constant-size signatures regardless of threshold -// - Efficient verification -// -// This implementation uses Shamir's Secret Sharing for key distribution -// and Lagrange interpolation for signature combination. -package bls - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/binary" - "errors" - "fmt" - "io" - - "github.com/cloudflare/circl/ecc/bls12381" - "github.com/cloudflare/circl/ecc/bls12381/ff" - "github.com/luxfi/crypto/bls" - "github.com/luxfi/crypto/threshold" -) - -func init() { - threshold.RegisterScheme(&Scheme{}) -} - -// Constants for BLS threshold scheme. -const ( - // KeyShareSize is the serialized size of a BLS key share. - // 32 bytes secret + 48 bytes public share + 48 bytes group key + 4 bytes metadata - KeyShareSize = 132 - - // SignatureShareSize is the serialized size of a signature share. - // 96 bytes signature + 4 bytes index - SignatureShareSize = 100 - - // SignatureSize is the serialized size of the final signature. - SignatureSize = 96 - - // PublicKeySize is the serialized size of the group public key. - PublicKeySize = 48 -) - -// Scheme implements the BLS threshold signature scheme. -type Scheme struct{} - -// ID returns the scheme identifier. -func (s *Scheme) ID() threshold.SchemeID { - return threshold.SchemeBLS -} - -// Name returns the human-readable name. -func (s *Scheme) Name() string { - return "BLS Threshold" -} - -// KeyShareSize returns the serialized key share size. -func (s *Scheme) KeyShareSize() int { - return KeyShareSize -} - -// SignatureShareSize returns the serialized signature share size. -func (s *Scheme) SignatureShareSize() int { - return SignatureShareSize -} - -// SignatureSize returns the serialized final signature size. -func (s *Scheme) SignatureSize() int { - return SignatureSize -} - -// PublicKeySize returns the serialized public key size. -func (s *Scheme) PublicKeySize() int { - return PublicKeySize -} - -// NewDKG creates a new distributed key generation instance. -// BLS DKG is not yet implemented here. Use threshold/protocols/frost for production DKG. -func (s *Scheme) NewDKG(config threshold.DKGConfig) (threshold.DKG, error) { - return nil, fmt.Errorf("BLS DKG not yet implemented: use threshold/protocols/frost for production DKG") -} - -// NewTrustedDealer creates a trusted dealer for centralized key generation. -func (s *Scheme) NewTrustedDealer(config threshold.DealerConfig) (threshold.TrustedDealer, error) { - if err := config.Validate(); err != nil { - return nil, err - } - return &TrustedDealer{ - config: config, - }, nil -} - -// NewSigner creates a signer from a key share. -func (s *Scheme) NewSigner(share threshold.KeyShare) (threshold.Signer, error) { - ks, ok := share.(*KeyShare) - if !ok { - return nil, threshold.ErrInvalidKeyShare - } - return &Signer{ - keyShare: ks, - }, nil -} - -// NewAggregator creates a signature aggregator for the given group key. -func (s *Scheme) NewAggregator(groupKey threshold.PublicKey) (threshold.Aggregator, error) { - pk, ok := groupKey.(*PublicKey) - if !ok { - return nil, threshold.ErrInvalidPublicKey - } - return &Aggregator{ - groupKey: pk, - }, nil -} - -// NewVerifier creates a signature verifier for the given group key. -func (s *Scheme) NewVerifier(groupKey threshold.PublicKey) (threshold.Verifier, error) { - pk, ok := groupKey.(*PublicKey) - if !ok { - return nil, threshold.ErrInvalidPublicKey - } - return &Verifier{ - groupKey: pk, - }, nil -} - -// ParseKeyShare deserializes a key share from bytes. -func (s *Scheme) ParseKeyShare(data []byte) (threshold.KeyShare, error) { - if len(data) < KeyShareSize { - return nil, threshold.ErrDataTooShort - } - return parseKeyShare(data) -} - -// ParsePublicKey deserializes a group public key from bytes. -func (s *Scheme) ParsePublicKey(data []byte) (threshold.PublicKey, error) { - if len(data) != PublicKeySize { - return nil, threshold.ErrInvalidPublicKey - } - - pk, err := bls.PublicKeyFromCompressedBytes(data) - if err != nil { - return nil, threshold.ErrInvalidPublicKey - } - - return &PublicKey{ - key: pk, - }, nil -} - -// ParseSignatureShare deserializes a signature share from bytes. -func (s *Scheme) ParseSignatureShare(data []byte) (threshold.SignatureShare, error) { - if len(data) < SignatureShareSize { - return nil, threshold.ErrDataTooShort - } - return parseSignatureShare(data) -} - -// ParseSignature deserializes a final signature from bytes. -func (s *Scheme) ParseSignature(data []byte) (threshold.Signature, error) { - if len(data) != SignatureSize { - return nil, threshold.ErrInvalidSignature - } - - sig, err := bls.SignatureFromBytes(data) - if err != nil { - return nil, threshold.ErrInvalidSignature - } - - return &Signature{ - sig: sig, - }, nil -} - -// PublicKey represents a BLS threshold group public key. -type PublicKey struct { - key *bls.PublicKey -} - -// Bytes serializes the public key. -func (pk *PublicKey) Bytes() []byte { - return bls.PublicKeyToCompressedBytes(pk.key) -} - -// Equal returns true if the public keys are identical. -func (pk *PublicKey) Equal(other threshold.PublicKey) bool { - otherPK, ok := other.(*PublicKey) - if !ok { - return false - } - return bytes.Equal(pk.Bytes(), otherPK.Bytes()) -} - -// SchemeID returns the scheme identifier. -func (pk *PublicKey) SchemeID() threshold.SchemeID { - return threshold.SchemeBLS -} - -// KeyShare represents a party's BLS key share. -type KeyShare struct { - index int - threshold int - totalParties int - secretShare *bls.SecretKey - publicShare *bls.PublicKey - groupKey *PublicKey -} - -// Index returns the party index. -func (ks *KeyShare) Index() int { - return ks.index -} - -// GroupKey returns the group public key. -func (ks *KeyShare) GroupKey() threshold.PublicKey { - return ks.groupKey -} - -// PublicShare returns this party's public key share. -func (ks *KeyShare) PublicShare() []byte { - return bls.PublicKeyToCompressedBytes(ks.publicShare) -} - -// Threshold returns the signing threshold. -func (ks *KeyShare) Threshold() int { - return ks.threshold -} - -// TotalParties returns the total number of parties. -func (ks *KeyShare) TotalParties() int { - return ks.totalParties -} - -// Bytes serializes the key share. -func (ks *KeyShare) Bytes() []byte { - buf := make([]byte, KeyShareSize) - - // Secret share (32 bytes) - copy(buf[0:32], bls.SecretKeyToBytes(ks.secretShare)) - - // Public share (48 bytes) - copy(buf[32:80], bls.PublicKeyToCompressedBytes(ks.publicShare)) - - // Group key (48 bytes) - copy(buf[80:128], ks.groupKey.Bytes()) - - // Metadata (4 bytes: index, threshold, total) - buf[128] = byte(ks.index) - buf[129] = byte(ks.threshold) - buf[130] = byte(ks.totalParties >> 8) - buf[131] = byte(ks.totalParties) - - return buf -} - -// SchemeID returns the scheme identifier. -func (ks *KeyShare) SchemeID() threshold.SchemeID { - return threshold.SchemeBLS -} - -// parseKeyShare deserializes a key share. -func parseKeyShare(data []byte) (*KeyShare, error) { - if len(data) < KeyShareSize { - return nil, threshold.ErrDataTooShort - } - - // Secret share - sk, err := bls.SecretKeyFromBytes(data[0:32]) - if err != nil { - return nil, threshold.ErrInvalidKeyShare - } - - // Public share - pk, err := bls.PublicKeyFromCompressedBytes(data[32:80]) - if err != nil { - return nil, threshold.ErrInvalidKeyShare - } - - // Group key - gk, err := bls.PublicKeyFromCompressedBytes(data[80:128]) - if err != nil { - return nil, threshold.ErrInvalidKeyShare - } - - return &KeyShare{ - index: int(data[128]), - threshold: int(data[129]), - totalParties: int(data[130])<<8 | int(data[131]), - secretShare: sk, - publicShare: pk, - groupKey: &PublicKey{key: gk}, - }, nil -} - -// SignatureShare represents a BLS signature share. -type SignatureShare struct { - index int - sig *bls.Signature -} - -// Index returns the party index. -func (ss *SignatureShare) Index() int { - return ss.index -} - -// Bytes serializes the signature share. -func (ss *SignatureShare) Bytes() []byte { - buf := make([]byte, SignatureShareSize) - binary.BigEndian.PutUint32(buf[0:4], uint32(ss.index)) - copy(buf[4:], bls.SignatureToBytes(ss.sig)) - return buf -} - -// SchemeID returns the scheme identifier. -func (ss *SignatureShare) SchemeID() threshold.SchemeID { - return threshold.SchemeBLS -} - -// parseSignatureShare deserializes a signature share. -func parseSignatureShare(data []byte) (*SignatureShare, error) { - if len(data) < SignatureShareSize { - return nil, threshold.ErrDataTooShort - } - - index := int(binary.BigEndian.Uint32(data[0:4])) - sig, err := bls.SignatureFromBytes(data[4:100]) - if err != nil { - return nil, threshold.ErrInvalidSignatureShare - } - - return &SignatureShare{ - index: index, - sig: sig, - }, nil -} - -// Signature represents a final BLS threshold signature. -type Signature struct { - sig *bls.Signature -} - -// Bytes serializes the signature. -func (s *Signature) Bytes() []byte { - return bls.SignatureToBytes(s.sig) -} - -// SchemeID returns the scheme identifier. -func (s *Signature) SchemeID() threshold.SchemeID { - return threshold.SchemeBLS -} - -// TrustedDealer generates key shares using a trusted dealer. -type TrustedDealer struct { - config threshold.DealerConfig -} - -// GenerateShares creates all key shares and the group public key. -func (d *TrustedDealer) GenerateShares(ctx context.Context) ([]threshold.KeyShare, threshold.PublicKey, error) { - rng := d.config.Rand - if rng == nil { - rng = rand.Reader - } - - // Generate master secret key - masterSK, err := bls.NewSecretKey() - if err != nil { - return nil, nil, err - } - - // Compute group public key - groupPK := masterSK.PublicKey() - - // Generate polynomial coefficients for Shamir secret sharing - // f(x) = a_0 + a_1*x + a_2*x^2 + ... + a_{t-1}*x^{t-1} - // where a_0 = masterSK - // For t-of-n threshold, we need degree t-1 (so t points uniquely determine it) - coefficients := make([]*bls.SecretKey, d.config.Threshold) - coefficients[0] = masterSK - - for i := 1; i < d.config.Threshold; i++ { - coef, err := bls.NewSecretKey() - if err != nil { - return nil, nil, err - } - coefficients[i] = coef - } - - // Generate shares by evaluating polynomial at points 1, 2, ..., n - shares := make([]threshold.KeyShare, d.config.TotalParties) - for i := 0; i < d.config.TotalParties; i++ { - // Evaluate polynomial at x = i+1 - shareSecret := evaluatePolynomial(coefficients, i+1) - - // Compute public share - sharePK := shareSecret.PublicKey() - - shares[i] = &KeyShare{ - index: i, - threshold: d.config.Threshold, - totalParties: d.config.TotalParties, - secretShare: shareSecret, - publicShare: sharePK, - groupKey: &PublicKey{key: groupPK}, - } - } - - return shares, &PublicKey{key: groupPK}, nil -} - -// evaluatePolynomial evaluates a polynomial at a given point using BLS12-381 scalar field arithmetic. -// f(x) = a_0 + a_1*x + a_2*x^2 + ... + a_t*x^t using Horner's method. -func evaluatePolynomial(coefficients []*bls.SecretKey, x int) *bls.SecretKey { - if len(coefficients) == 0 { - return nil - } - - // Convert coefficients to scalars - scalars := make([]*ff.Scalar, len(coefficients)) - for i, coef := range coefficients { - scalars[i] = new(ff.Scalar) - scalars[i].SetBytes(bls.SecretKeyToBytes(coef)) - } - - // Convert x to scalar - xScalar := new(ff.Scalar) - xScalar.SetUint64(uint64(x)) - - // Evaluate using Horner's method: f(x) = a_n + x*(a_{n-1} + x*(a_{n-2} + ...)) - result := new(ff.Scalar) - result.Set(scalars[len(scalars)-1]) - - for i := len(scalars) - 2; i >= 0; i-- { - // result = result * x + a_i - result.Mul(result, xScalar) - result.Add(result, scalars[i]) - } - - // Convert back to SecretKey - resultBytes, _ := result.MarshalBinary() - sk, err := bls.SecretKeyFromBytes(resultBytes) - if err != nil { - // Fallback to constant term if conversion fails - return coefficients[0] - } - return sk -} - -// computeLagrangeCoefficients computes Lagrange coefficients at x=0 for the given indices. -// λ_j(0) = Π_{i≠j} (0 - x_i) / (x_j - x_i) = Π_{i≠j} x_i / (x_i - x_j) -func computeLagrangeCoefficients(indices []int) []*ff.Scalar { - n := len(indices) - coeffs := make([]*ff.Scalar, n) - - for j := 0; j < n; j++ { - xj := new(ff.Scalar) - xj.SetUint64(uint64(indices[j])) - - // numerator = Π_{i≠j} x_i - numerator := new(ff.Scalar) - numerator.SetOne() - - // denominator = Π_{i≠j} (x_j - x_i) - denominator := new(ff.Scalar) - denominator.SetOne() - - for i := 0; i < n; i++ { - if i == j { - continue - } - xi := new(ff.Scalar) - xi.SetUint64(uint64(indices[i])) - - // numerator *= x_i - numerator.Mul(numerator, xi) - - // diff = x_j - x_i - diff := new(ff.Scalar) - diff.Set(xj) - diff.Sub(diff, xi) - - // denominator *= diff - denominator.Mul(denominator, diff) - } - - // λ_j = numerator / denominator = numerator * denominator^(-1) - coeffs[j] = new(ff.Scalar) - coeffs[j].Inv(denominator) - coeffs[j].Mul(coeffs[j], numerator) - } - - return coeffs -} - -// isScalarOne checks if a scalar equals 1. -func isScalarOne(s *ff.Scalar) bool { - one := new(ff.Scalar) - one.SetOne() - return s.IsEqual(one) == 1 -} - -// aggregateWithLagrange aggregates signature shares with Lagrange coefficients. -// Uses circl's G2 for scalar multiplication on signature points. -func aggregateWithLagrange(shares []*SignatureShare, coeffs []*ff.Scalar) (*Signature, error) { - if len(shares) != len(coeffs) { - return nil, errors.New("shares and coefficients length mismatch") - } - - // Use circl's G2 for scalar multiplication - // circl bls12381 uses G2 for signatures - var result bls12381G2 - result.SetIdentity() - - for i, share := range shares { - // Get signature bytes and parse as G2 point - sigBytes := bls.SignatureToBytes(share.sig) - - var sigPoint bls12381G2 - if err := sigPoint.SetBytes(sigBytes); err != nil { - return nil, err - } - - // Multiply signature by Lagrange coefficient - scaled := sigPoint.ScalarMult(coeffs[i]) - - // Add to result - result.Add(&result, scaled) - } - - // Convert back to bls.Signature - resultBytes := result.BytesCompressed() - sig, err := bls.SignatureFromBytes(resultBytes) - if err != nil { - return nil, err - } - - return &Signature{sig: sig}, nil -} - -// bls12381G2 wraps circl's G2 for signature point operations. -type bls12381G2 struct { - point bls12381.G2 -} - -func (g *bls12381G2) SetIdentity() { - g.point.SetIdentity() -} - -func (g *bls12381G2) SetBytes(data []byte) error { - return g.point.SetBytes(data) -} - -func (g *bls12381G2) Add(a, b *bls12381G2) { - g.point.Add(&a.point, &b.point) -} - -func (g *bls12381G2) ScalarMult(scalar *ff.Scalar) *bls12381G2 { - result := new(bls12381G2) - result.point.ScalarMult(scalar, &g.point) - return result -} - -func (g *bls12381G2) BytesCompressed() []byte { - return g.point.BytesCompressed() -} - -// DKG implements distributed key generation for BLS threshold. -type DKG struct { - config threshold.DKGConfig - round int - secretShare *bls.SecretKey - publicShare *bls.PublicKey - groupKey *PublicKey - complete bool -} - -// Round1 generates the first round DKG message. -func (d *DKG) Round1(ctx context.Context) (threshold.DKGMessage, error) { - if d.round != 0 { - return nil, threshold.ErrInvalidRound - } - - rng := d.config.Rand - if rng == nil { - rng = rand.Reader - } - - // Generate secret share - sk, err := bls.NewSecretKey() - if err != nil { - return nil, err - } - d.secretShare = sk - d.publicShare = sk.PublicKey() - - // Create commitment message - msg := &DKGMessage{ - round: 1, - fromParty: d.config.PartyIndex, - data: bls.PublicKeyToCompressedBytes(d.publicShare), - } - - d.round = 1 - return msg, nil -} - -// Round2 processes Round1 messages and generates Round2 messages. -func (d *DKG) Round2(ctx context.Context, round1Messages map[int]threshold.DKGMessage) (threshold.DKGMessage, error) { - if d.round != 1 { - return nil, threshold.ErrInvalidRound - } - - // Verify we have messages from all parties - if len(round1Messages) < d.config.TotalParties-1 { - return nil, threshold.ErrMissingMessage - } - - // In a full implementation, we would: - // 1. Verify commitments - // 2. Generate Feldman VSS shares - // 3. Create encrypted shares for each party - - msg := &DKGMessage{ - round: 2, - fromParty: d.config.PartyIndex, - data: bls.PublicKeyToCompressedBytes(d.publicShare), - } - - d.round = 2 - return msg, nil -} - -// Round3 processes Round2 messages and generates the final key share. -func (d *DKG) Round3(ctx context.Context, round2Messages map[int]threshold.DKGMessage) (threshold.KeyShare, error) { - if d.round != 2 { - return nil, threshold.ErrInvalidRound - } - - // In a full implementation, we would: - // 1. Verify received shares - // 2. Combine shares to get our secret share - // 3. Compute group public key from commitments - - // For now, create a key share with our generated secret - keyShare := &KeyShare{ - index: d.config.PartyIndex, - threshold: d.config.Threshold, - totalParties: d.config.TotalParties, - secretShare: d.secretShare, - publicShare: d.publicShare, - groupKey: &PublicKey{key: d.publicShare}, // Simplified - should be aggregated - } - - d.complete = true - return keyShare, nil -} - -// NumRounds returns the number of DKG rounds. -func (d *DKG) NumRounds() int { - return 3 -} - -// GroupKey returns the group public key after DKG completes. -func (d *DKG) GroupKey() threshold.PublicKey { - if !d.complete { - return nil - } - return d.groupKey -} - -// DKGMessage represents a DKG protocol message. -type DKGMessage struct { - round int - fromParty int - data []byte -} - -// Round returns the round number. -func (m *DKGMessage) Round() int { - return m.round -} - -// FromParty returns the sender's party index. -func (m *DKGMessage) FromParty() int { - return m.fromParty -} - -// Bytes serializes the message. -func (m *DKGMessage) Bytes() []byte { - buf := make([]byte, 8+len(m.data)) - binary.BigEndian.PutUint32(buf[0:4], uint32(m.round)) - binary.BigEndian.PutUint32(buf[4:8], uint32(m.fromParty)) - copy(buf[8:], m.data) - return buf -} - -// Signer creates BLS signature shares. -type Signer struct { - keyShare *KeyShare -} - -// Index returns the party index. -func (s *Signer) Index() int { - return s.keyShare.index -} - -// PublicShare returns this party's public key share. -func (s *Signer) PublicShare() []byte { - return s.keyShare.PublicShare() -} - -// NonceGen generates a nonce commitment. -// BLS threshold doesn't require nonces for aggregation. -func (s *Signer) NonceGen(ctx context.Context) (threshold.NonceCommitment, threshold.NonceState, error) { - // BLS is non-interactive, no nonce needed - return nil, nil, nil -} - -// SignShare creates a signature share for the given message. -func (s *Signer) SignShare(ctx context.Context, message []byte, signers []int, nonce threshold.NonceState) (threshold.SignatureShare, error) { - // Sign with the secret share - sig, err := s.keyShare.secretShare.Sign(message) - if err != nil { - return nil, err - } - - return &SignatureShare{ - index: s.keyShare.index, - sig: sig, - }, nil -} - -// KeyShare returns the underlying key share. -func (s *Signer) KeyShare() threshold.KeyShare { - return s.keyShare -} - -// Aggregator combines BLS signature shares. -type Aggregator struct { - groupKey *PublicKey -} - -// Aggregate combines signature shares into a final signature using Lagrange interpolation. -// For t-of-n threshold signatures, this computes the group signature by multiplying -// each signature share by its Lagrange coefficient and aggregating. -func (a *Aggregator) Aggregate(ctx context.Context, message []byte, shares []threshold.SignatureShare, commitments []threshold.NonceCommitment) (threshold.Signature, error) { - if len(shares) == 0 { - return nil, threshold.ErrInsufficientShares - } - - // Get participant indices for Lagrange interpolation - indices := make([]int, len(shares)) - sigShares := make([]*SignatureShare, len(shares)) - for i, share := range shares { - ss, ok := share.(*SignatureShare) - if !ok { - return nil, threshold.ErrInvalidSignatureShare - } - sigShares[i] = ss - indices[i] = ss.index + 1 // Lagrange uses 1-indexed points - } - - // Compute Lagrange coefficients at x=0 for each participant - lagrangeCoeffs := computeLagrangeCoefficients(indices) - - // For each signature share, multiply by its Lagrange coefficient - // In BLS, "multiplication" of signature by scalar is done by exponentiating - // the signature point by the scalar. However, blst doesn't expose this directly. - // - // For BLS threshold with Lagrange interpolation, we use the property that: - // σ = Σ λ_i * σ_i (where λ_i are Lagrange coefficients) - // - // Since we can't directly scale G2 points with blst's public API, - // we use the aggregate-then-verify approach that works when all signers - // are honest and using proper Shamir shares. - // - // For a proper implementation with scalar multiplication on G2, you would need - // to use lower-level blst or circl APIs. - blsSigs := make([]*bls.Signature, len(shares)) - for i := range sigShares { - blsSigs[i] = sigShares[i].sig - } - - // If lagrange coefficients are all 1 (n-of-n case), simple aggregation works - allOnes := true - for _, coef := range lagrangeCoeffs { - if !isScalarOne(coef) { - allOnes = false - break - } - } - - if allOnes { - // n-of-n case: simple aggregation - aggSig, err := bls.AggregateSignatures(blsSigs) - if err != nil { - return nil, err - } - return &Signature{sig: aggSig}, nil - } - - // For t-of-n (t < n), we need weighted aggregation - // Use circl's G2 for scalar multiplication - return aggregateWithLagrange(sigShares, lagrangeCoeffs) -} - -// VerifyShare verifies a single signature share. -func (a *Aggregator) VerifyShare(message []byte, share threshold.SignatureShare, publicShare []byte) error { - ss, ok := share.(*SignatureShare) - if !ok { - return threshold.ErrInvalidSignatureShare - } - - pk, err := bls.PublicKeyFromCompressedBytes(publicShare) - if err != nil { - return threshold.ErrInvalidPublicKey - } - - if !bls.Verify(pk, ss.sig, message) { - return threshold.ErrSignatureVerificationFailed - } - - return nil -} - -// GroupKey returns the group public key. -func (a *Aggregator) GroupKey() threshold.PublicKey { - return a.groupKey -} - -// Verifier verifies BLS threshold signatures. -type Verifier struct { - groupKey *PublicKey -} - -// Verify checks if a signature is valid. -func (v *Verifier) Verify(message []byte, signature threshold.Signature) bool { - sig, ok := signature.(*Signature) - if !ok { - return false - } - - return bls.Verify(v.groupKey.key, sig.sig, message) -} - -// VerifyBytes verifies a serialized signature. -func (v *Verifier) VerifyBytes(message, signature []byte) bool { - sig, err := bls.SignatureFromBytes(signature) - if err != nil { - return false - } - - return bls.Verify(v.groupKey.key, sig, message) -} - -// GroupKey returns the group public key. -func (v *Verifier) GroupKey() threshold.PublicKey { - return v.groupKey -} - -// Ensure interfaces are implemented -var ( - _ threshold.Scheme = (*Scheme)(nil) - _ threshold.PublicKey = (*PublicKey)(nil) - _ threshold.KeyShare = (*KeyShare)(nil) - _ threshold.SignatureShare = (*SignatureShare)(nil) - _ threshold.Signature = (*Signature)(nil) - _ threshold.TrustedDealer = (*TrustedDealer)(nil) - _ threshold.DKG = (*DKG)(nil) - _ threshold.DKGMessage = (*DKGMessage)(nil) - _ threshold.Signer = (*Signer)(nil) - _ threshold.Aggregator = (*Aggregator)(nil) - _ threshold.Verifier = (*Verifier)(nil) -) - -// Compilation check for unused variables -var _ io.Reader = rand.Reader -var _ = errors.New diff --git a/threshold/bls/scheme_test.go b/threshold/bls/scheme_test.go deleted file mode 100644 index 435f83b..0000000 --- a/threshold/bls/scheme_test.go +++ /dev/null @@ -1,478 +0,0 @@ -// Copyright (C) 2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package bls - -import ( - "context" - "testing" - - "github.com/luxfi/crypto/threshold" -) - -func TestSchemeRegistration(t *testing.T) { - // Verify scheme is registered via init() - if !threshold.HasScheme(threshold.SchemeBLS) { - t.Fatal("BLS scheme not registered") - } - - scheme, err := threshold.GetScheme(threshold.SchemeBLS) - if err != nil { - t.Fatalf("GetScheme failed: %v", err) - } - - if scheme.ID() != threshold.SchemeBLS { - t.Errorf("scheme ID = %v, want %v", scheme.ID(), threshold.SchemeBLS) - } - - if scheme.Name() != "BLS Threshold" { - t.Errorf("scheme Name = %v, want %v", scheme.Name(), "BLS Threshold") - } -} - -func TestSchemeConstants(t *testing.T) { - scheme := &Scheme{} - - if scheme.KeyShareSize() != KeyShareSize { - t.Errorf("KeyShareSize = %d, want %d", scheme.KeyShareSize(), KeyShareSize) - } - - if scheme.SignatureShareSize() != SignatureShareSize { - t.Errorf("SignatureShareSize = %d, want %d", scheme.SignatureShareSize(), SignatureShareSize) - } - - if scheme.SignatureSize() != SignatureSize { - t.Errorf("SignatureSize = %d, want %d", scheme.SignatureSize(), SignatureSize) - } - - if scheme.PublicKeySize() != PublicKeySize { - t.Errorf("PublicKeySize = %d, want %d", scheme.PublicKeySize(), PublicKeySize) - } -} - -func TestTrustedDealerKeyGeneration(t *testing.T) { - scheme := &Scheme{} - - config := threshold.DealerConfig{ - Threshold: 2, // 3-of-5 threshold - TotalParties: 5, - } - - dealer, err := scheme.NewTrustedDealer(config) - if err != nil { - t.Fatalf("NewTrustedDealer failed: %v", err) - } - - ctx := context.Background() - shares, groupKey, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("GenerateShares failed: %v", err) - } - - // Verify correct number of shares - if len(shares) != config.TotalParties { - t.Errorf("got %d shares, want %d", len(shares), config.TotalParties) - } - - // Verify each share - for i, share := range shares { - if share.Index() != i { - t.Errorf("share %d has index %d", i, share.Index()) - } - - if share.Threshold() != config.Threshold { - t.Errorf("share %d threshold = %d, want %d", i, share.Threshold(), config.Threshold) - } - - if share.TotalParties() != config.TotalParties { - t.Errorf("share %d totalParties = %d, want %d", i, share.TotalParties(), config.TotalParties) - } - - if share.SchemeID() != threshold.SchemeBLS { - t.Errorf("share %d schemeID = %v, want %v", i, share.SchemeID(), threshold.SchemeBLS) - } - - // Verify group key matches - if !share.GroupKey().Equal(groupKey) { - t.Errorf("share %d group key mismatch", i) - } - } - - // Verify group key - if groupKey == nil { - t.Fatal("groupKey is nil") - } - - if groupKey.SchemeID() != threshold.SchemeBLS { - t.Errorf("groupKey schemeID = %v, want %v", groupKey.SchemeID(), threshold.SchemeBLS) - } -} - -func TestKeyShareSerialization(t *testing.T) { - scheme := &Scheme{} - - config := threshold.DealerConfig{ - Threshold: 1, - TotalParties: 3, - } - - dealer, err := scheme.NewTrustedDealer(config) - if err != nil { - t.Fatalf("NewTrustedDealer failed: %v", err) - } - - ctx := context.Background() - shares, _, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("GenerateShares failed: %v", err) - } - - // Test serialization/deserialization - for i, share := range shares { - data := share.Bytes() - if len(data) != KeyShareSize { - t.Errorf("share %d bytes length = %d, want %d", i, len(data), KeyShareSize) - } - - parsed, err := scheme.ParseKeyShare(data) - if err != nil { - t.Errorf("ParseKeyShare failed for share %d: %v", i, err) - continue - } - - if parsed.Index() != share.Index() { - t.Errorf("parsed index = %d, want %d", parsed.Index(), share.Index()) - } - - if parsed.Threshold() != share.Threshold() { - t.Errorf("parsed threshold = %d, want %d", parsed.Threshold(), share.Threshold()) - } - } -} - -func TestSignerCreation(t *testing.T) { - scheme := &Scheme{} - - config := threshold.DealerConfig{ - Threshold: 1, - TotalParties: 3, - } - - dealer, err := scheme.NewTrustedDealer(config) - if err != nil { - t.Fatalf("NewTrustedDealer failed: %v", err) - } - - ctx := context.Background() - shares, _, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("GenerateShares failed: %v", err) - } - - // Create signers from shares - for i, share := range shares { - signer, err := scheme.NewSigner(share) - if err != nil { - t.Errorf("NewSigner failed for share %d: %v", i, err) - continue - } - - if signer.Index() != i { - t.Errorf("signer index = %d, want %d", signer.Index(), i) - } - - // Verify public share is not empty - pubShare := signer.PublicShare() - if len(pubShare) == 0 { - t.Errorf("signer %d has empty public share", i) - } - } -} - -func TestSigningAndAggregation(t *testing.T) { - scheme := &Scheme{} - - config := threshold.DealerConfig{ - Threshold: 1, // 2-of-3 threshold - TotalParties: 3, - } - - dealer, err := scheme.NewTrustedDealer(config) - if err != nil { - t.Fatalf("NewTrustedDealer failed: %v", err) - } - - ctx := context.Background() - shares, groupKey, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("GenerateShares failed: %v", err) - } - - // Create signers - signers := make([]threshold.Signer, len(shares)) - for i, share := range shares { - signer, err := scheme.NewSigner(share) - if err != nil { - t.Fatalf("NewSigner failed: %v", err) - } - signers[i] = signer - } - - // Create aggregator - aggregator, err := scheme.NewAggregator(groupKey) - if err != nil { - t.Fatalf("NewAggregator failed: %v", err) - } - - // Sign a message with first 2 signers (threshold+1) - message := []byte("test message for threshold signing") - participantIndices := []int{0, 1} - - signatureShares := make([]threshold.SignatureShare, len(participantIndices)) - for i, idx := range participantIndices { - share, err := signers[idx].SignShare(ctx, message, participantIndices, nil) - if err != nil { - t.Fatalf("SignShare failed for signer %d: %v", idx, err) - } - signatureShares[i] = share - } - - // Verify individual shares - for i, idx := range participantIndices { - err := aggregator.VerifyShare(message, signatureShares[i], signers[idx].PublicShare()) - if err != nil { - t.Errorf("VerifyShare failed for signer %d: %v", idx, err) - } - } - - // Aggregate shares - signature, err := aggregator.Aggregate(ctx, message, signatureShares, nil) - if err != nil { - t.Fatalf("Aggregate failed: %v", err) - } - - // Verify signature is not nil - if signature == nil { - t.Fatal("signature is nil") - } - - // Verify scheme ID - if signature.SchemeID() != threshold.SchemeBLS { - t.Errorf("signature schemeID = %v, want %v", signature.SchemeID(), threshold.SchemeBLS) - } -} - -func TestVerifier(t *testing.T) { - scheme := &Scheme{} - - config := threshold.DealerConfig{ - Threshold: 1, - TotalParties: 3, - } - - dealer, err := scheme.NewTrustedDealer(config) - if err != nil { - t.Fatalf("NewTrustedDealer failed: %v", err) - } - - ctx := context.Background() - shares, groupKey, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("GenerateShares failed: %v", err) - } - - // Create verifier - verifier, err := scheme.NewVerifier(groupKey) - if err != nil { - t.Fatalf("NewVerifier failed: %v", err) - } - - // Verify group key matches - if !verifier.GroupKey().Equal(groupKey) { - t.Error("verifier group key mismatch") - } - - // Create a signer and sign - signer, err := scheme.NewSigner(shares[0]) - if err != nil { - t.Fatalf("NewSigner failed: %v", err) - } - - message := []byte("test message") - share, err := signer.SignShare(ctx, message, []int{0}, nil) - if err != nil { - t.Fatalf("SignShare failed: %v", err) - } - - // Create aggregator and aggregate (single share for test) - aggregator, err := scheme.NewAggregator(groupKey) - if err != nil { - t.Fatalf("NewAggregator failed: %v", err) - } - - sig, err := aggregator.Aggregate(ctx, message, []threshold.SignatureShare{share}, nil) - if err != nil { - t.Fatalf("Aggregate failed: %v", err) - } - - // Verify the signature - if !verifier.Verify(message, sig) { - t.Error("Verify returned false, expected true") - } - - // Verify with bytes - if !verifier.VerifyBytes(message, sig.Bytes()) { - t.Error("VerifyBytes returned false, expected true") - } - - // Verify wrong message fails - if verifier.Verify([]byte("wrong message"), sig) { - t.Error("Verify returned true for wrong message, expected false") - } -} - -func TestDKGConfig(t *testing.T) { - scheme := &Scheme{} - - // NewDKG is intentionally unimplemented — it must return an error - // directing callers to threshold/protocols/frost for production DKG. - config := threshold.DKGConfig{ - Threshold: 1, - TotalParties: 3, - PartyIndex: 0, - } - - _, err := scheme.NewDKG(config) - if err == nil { - t.Fatal("NewDKG should return an error (not implemented)") - } -} - -func TestSignatureShareSerialization(t *testing.T) { - scheme := &Scheme{} - - config := threshold.DealerConfig{ - Threshold: 1, - TotalParties: 3, - } - - dealer, err := scheme.NewTrustedDealer(config) - if err != nil { - t.Fatalf("NewTrustedDealer failed: %v", err) - } - - ctx := context.Background() - shares, _, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("GenerateShares failed: %v", err) - } - - signer, err := scheme.NewSigner(shares[0]) - if err != nil { - t.Fatalf("NewSigner failed: %v", err) - } - - message := []byte("test message") - sigShare, err := signer.SignShare(ctx, message, []int{0}, nil) - if err != nil { - t.Fatalf("SignShare failed: %v", err) - } - - // Serialize - data := sigShare.Bytes() - if len(data) != SignatureShareSize { - t.Errorf("signature share bytes length = %d, want %d", len(data), SignatureShareSize) - } - - // Deserialize - parsed, err := scheme.ParseSignatureShare(data) - if err != nil { - t.Fatalf("ParseSignatureShare failed: %v", err) - } - - if parsed.Index() != sigShare.Index() { - t.Errorf("parsed index = %d, want %d", parsed.Index(), sigShare.Index()) - } - - if parsed.SchemeID() != threshold.SchemeBLS { - t.Errorf("parsed schemeID = %v, want %v", parsed.SchemeID(), threshold.SchemeBLS) - } -} - -func TestPublicKeySerialization(t *testing.T) { - scheme := &Scheme{} - - config := threshold.DealerConfig{ - Threshold: 1, - TotalParties: 3, - } - - dealer, err := scheme.NewTrustedDealer(config) - if err != nil { - t.Fatalf("NewTrustedDealer failed: %v", err) - } - - ctx := context.Background() - _, groupKey, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("GenerateShares failed: %v", err) - } - - // Serialize - data := groupKey.Bytes() - if len(data) != PublicKeySize { - t.Errorf("public key bytes length = %d, want %d", len(data), PublicKeySize) - } - - // Deserialize - parsed, err := scheme.ParsePublicKey(data) - if err != nil { - t.Fatalf("ParsePublicKey failed: %v", err) - } - - if !parsed.Equal(groupKey) { - t.Error("parsed public key does not equal original") - } - - if parsed.SchemeID() != threshold.SchemeBLS { - t.Errorf("parsed schemeID = %v, want %v", parsed.SchemeID(), threshold.SchemeBLS) - } -} - -func TestPublicKeyEquality(t *testing.T) { - scheme := &Scheme{} - - config := threshold.DealerConfig{ - Threshold: 1, - TotalParties: 3, - } - - dealer, err := scheme.NewTrustedDealer(config) - if err != nil { - t.Fatalf("NewTrustedDealer failed: %v", err) - } - - ctx := context.Background() - - // Generate two different group keys - _, groupKey1, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("GenerateShares 1 failed: %v", err) - } - - _, groupKey2, err := dealer.GenerateShares(ctx) - if err != nil { - t.Fatalf("GenerateShares 2 failed: %v", err) - } - - // Same key should be equal to itself - if !groupKey1.Equal(groupKey1) { - t.Error("group key should equal itself") - } - - // Different keys should not be equal (with very high probability) - // Note: There's an astronomically small chance this could fail randomly - if groupKey1.Equal(groupKey2) { - t.Error("different group keys should not be equal") - } -}