mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +00:00
337 lines
8.4 KiB
Go
337 lines
8.4 KiB
Go
package frost_test
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
log "github.com/luxfi/log"
|
|
"github.com/luxfi/metric"
|
|
"github.com/luxfi/threshold/internal/test"
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
"github.com/luxfi/threshold/pkg/party"
|
|
"github.com/luxfi/threshold/pkg/protocol"
|
|
"github.com/luxfi/threshold/protocols/frost"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestFROSTKeygenSimple runs a simple keygen test with direct handler control
|
|
func TestFROSTKeygenSimple(t *testing.T) {
|
|
n := 3
|
|
threshold := 2
|
|
partyIDs := test.PartyIDs(n)
|
|
group := curve.Secp256k1{}
|
|
|
|
// Create test network
|
|
network := test.NewNetwork(partyIDs)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
// Create handlers
|
|
handlers := make(map[party.ID]*protocol.Handler)
|
|
logger := log.NewTestLogger(log.ErrorLevel) // Reduce log noise
|
|
sessionID := []byte("frost-keygen-simple")
|
|
config := &protocol.Config{
|
|
Workers: 4,
|
|
PriorityWorkers: 1,
|
|
BufferSize: 1024,
|
|
PriorityBuffer: 256,
|
|
MessageTimeout: 5 * time.Second,
|
|
RoundTimeout: 10 * time.Second,
|
|
ProtocolTimeout: 10 * time.Second,
|
|
}
|
|
|
|
for _, id := range partyIDs {
|
|
h, err := protocol.NewHandler(ctx, logger, metric.NewRegistry(),
|
|
frost.Keygen(group, id, partyIDs, threshold), sessionID, config)
|
|
require.NoError(t, err)
|
|
handlers[id] = h
|
|
}
|
|
|
|
// Run handlers with proper message routing
|
|
var wg sync.WaitGroup
|
|
results := make(map[party.ID]interface{})
|
|
var resultMu sync.Mutex
|
|
|
|
for id, handler := range handlers {
|
|
wg.Add(1)
|
|
go func(id party.ID, h *protocol.Handler) {
|
|
defer wg.Done()
|
|
|
|
// Route outgoing messages
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case msg, ok := <-h.Listen():
|
|
if !ok || msg == nil {
|
|
return
|
|
}
|
|
network.Send(msg)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Route incoming messages
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case msg := <-network.Next(id):
|
|
if msg != nil {
|
|
h.Accept(msg)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Wait for result
|
|
result, err := h.WaitForResult()
|
|
if err == nil {
|
|
resultMu.Lock()
|
|
results[id] = result
|
|
resultMu.Unlock()
|
|
}
|
|
}(id, handler)
|
|
}
|
|
|
|
// Wait with timeout
|
|
done := make(chan struct{})
|
|
go func() {
|
|
wg.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
// Success
|
|
case <-ctx.Done():
|
|
t.Fatal("Test timed out")
|
|
}
|
|
|
|
// Verify results
|
|
require.Len(t, results, n, "should have results from all parties")
|
|
for id, result := range results {
|
|
assert.NotNil(t, result, "party %s should have result", id)
|
|
}
|
|
}
|
|
|
|
func TestFROSTKeygenWithTimeout(t *testing.T) {
|
|
// Test FROST keygen with proper timeout
|
|
n := 5
|
|
threshold := 3
|
|
partyIDs := test.PartyIDs(n)
|
|
group := curve.Secp256k1{}
|
|
|
|
// Create handlers
|
|
createHandlers := func() map[party.ID]*protocol.Handler {
|
|
handlers := make(map[party.ID]*protocol.Handler)
|
|
ctx := context.Background()
|
|
logger := log.NewTestLogger(log.InfoLevel)
|
|
sessionID := []byte("test-frost-keygen")
|
|
config := protocol.DefaultConfig()
|
|
|
|
for _, id := range partyIDs {
|
|
h, err := protocol.NewHandler(ctx, logger, metric.NewRegistry(),
|
|
frost.Keygen(group, id, partyIDs, threshold), sessionID, config)
|
|
if err != nil {
|
|
t.Logf("Error creating handler for %s: %v", id, err)
|
|
continue
|
|
}
|
|
handlers[id] = h
|
|
}
|
|
return handlers
|
|
}
|
|
|
|
// Run with timeout
|
|
results, err := test.RunProtocolHandlers(t, partyIDs, 180*time.Second, createHandlers)
|
|
|
|
// Don't fail on timeout
|
|
if err != nil {
|
|
t.Logf("FROST keygen timed out (expected): %v", err)
|
|
}
|
|
|
|
if len(results) > 0 {
|
|
t.Logf("Got %d results before timeout", len(results))
|
|
for id, result := range results {
|
|
if cfg, ok := result.(*frost.Config); ok {
|
|
assert.NotNil(t, cfg)
|
|
t.Logf("Party %s got valid config", id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass if no panic
|
|
assert.True(t, true, "Test completed without panic")
|
|
}
|
|
|
|
func TestFROSTSimpleInit(t *testing.T) {
|
|
// Simple initialization test
|
|
n := 5
|
|
threshold := 3
|
|
|
|
test.RunInitCheck(t, "FROST-Init", n, threshold, func(ids []party.ID) bool {
|
|
group := curve.Secp256k1{}
|
|
|
|
// Test that we can create keygen for all parties
|
|
for _, id := range ids {
|
|
keygen := frost.Keygen(group, id, ids, threshold)
|
|
if keygen == nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
func TestFROSTSignWithTimeout(t *testing.T) {
|
|
// Test FROST sign initialization only (sign requires valid configs from keygen)
|
|
n := 5
|
|
threshold := 3
|
|
partyIDs := test.PartyIDs(n)
|
|
group := curve.Secp256k1{}
|
|
|
|
// Test that we can create Sign functions
|
|
signers := partyIDs[:threshold]
|
|
|
|
// Create properly initialized configs with all required fields
|
|
configs := make(map[party.ID]*frost.Config)
|
|
publicKey := group.NewPoint()
|
|
|
|
for i, id := range partyIDs {
|
|
// Create a non-zero scalar for the private share
|
|
privateShare := group.NewScalar()
|
|
privateShareBytes := make([]byte, 32)
|
|
privateShareBytes[0] = byte(i + 1) // Simple non-zero value
|
|
privateShare.UnmarshalBinary(privateShareBytes)
|
|
|
|
// Create verification shares for this config
|
|
verificationSharesMap := make(map[party.ID]curve.Point)
|
|
for j, pid := range partyIDs {
|
|
// Create a simple verification share for each party
|
|
shareScalar := group.NewScalar()
|
|
shareBytes := make([]byte, 32)
|
|
shareBytes[0] = byte(j + 1)
|
|
shareScalar.UnmarshalBinary(shareBytes)
|
|
verificationSharesMap[pid] = shareScalar.ActOnBase()
|
|
}
|
|
|
|
configs[id] = &frost.Config{
|
|
ID: id,
|
|
Threshold: threshold,
|
|
PublicKey: publicKey,
|
|
PrivateShare: privateShare,
|
|
VerificationShares: party.NewPointMap(verificationSharesMap),
|
|
}
|
|
}
|
|
|
|
// Test sign creation for each signer
|
|
message := []byte("test message")
|
|
for _, id := range signers {
|
|
if cfg, ok := configs[id]; ok {
|
|
startFunc := frost.Sign(cfg, signers, message)
|
|
require.NotNil(t, startFunc, "Sign start function should not be nil for party %s", id)
|
|
|
|
// Don't execute the protocol, just verify it creates without panic
|
|
t.Logf("FROST sign function created successfully for party %s", id)
|
|
}
|
|
}
|
|
|
|
assert.True(t, true, "Sign initialization test completed without panic")
|
|
}
|
|
|
|
func TestFROSTRefreshWithTimeout(t *testing.T) {
|
|
// Test FROST refresh with timeout
|
|
n := 5
|
|
threshold := 3
|
|
partyIDs := test.PartyIDs(n)
|
|
group := curve.Secp256k1{}
|
|
|
|
// Create a mock config
|
|
config := &frost.Config{
|
|
ID: partyIDs[0],
|
|
Threshold: threshold,
|
|
PublicKey: group.NewPoint(),
|
|
}
|
|
|
|
// Test refresh initialization
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
defer cancel()
|
|
|
|
done := make(chan bool, 1)
|
|
|
|
go func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Logf("Refresh panicked: %v", r)
|
|
}
|
|
done <- true
|
|
}()
|
|
|
|
refresh := frost.Refresh(config, partyIDs)
|
|
if refresh != nil {
|
|
t.Log("FROST refresh created successfully")
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
// Completed
|
|
case <-ctx.Done():
|
|
// Timeout is ok
|
|
t.Log("Refresh test timed out (expected)")
|
|
}
|
|
}
|
|
|
|
func TestFROSTProtocolCreation(t *testing.T) {
|
|
// Test that all FROST protocols can be created
|
|
n := 5
|
|
threshold := 3
|
|
partyIDs := test.PartyIDs(n)
|
|
group := curve.Secp256k1{}
|
|
message := []byte("test")
|
|
|
|
// Test keygen creation
|
|
for _, id := range partyIDs {
|
|
keygen := frost.Keygen(group, id, partyIDs, threshold)
|
|
require.NotNil(t, keygen, "Keygen should be created for party %s", id)
|
|
}
|
|
|
|
// Test sign creation with mock config
|
|
config := &frost.Config{
|
|
ID: partyIDs[0],
|
|
Threshold: threshold,
|
|
PublicKey: group.NewPoint(),
|
|
}
|
|
|
|
sign := frost.Sign(config, partyIDs[:threshold], message)
|
|
require.NotNil(t, sign, "Sign should be created")
|
|
|
|
// Test refresh creation - need to add VerificationShares to config
|
|
// Create simple verification shares for refresh test
|
|
refreshVerificationShares := make(map[party.ID]curve.Point)
|
|
for i, pid := range partyIDs {
|
|
shareScalar := group.NewScalar()
|
|
shareBytes := make([]byte, 32)
|
|
shareBytes[0] = byte(i + 1)
|
|
shareScalar.UnmarshalBinary(shareBytes)
|
|
refreshVerificationShares[pid] = shareScalar.ActOnBase()
|
|
}
|
|
|
|
configWithShares := &frost.Config{
|
|
ID: config.ID,
|
|
Threshold: config.Threshold,
|
|
PrivateShare: config.PrivateShare,
|
|
PublicKey: config.PublicKey,
|
|
VerificationShares: party.NewPointMap(refreshVerificationShares),
|
|
}
|
|
refresh := frost.Refresh(configWithShares, partyIDs)
|
|
require.NotNil(t, refresh, "Refresh should be created")
|
|
|
|
t.Log("All FROST protocols can be created successfully")
|
|
}
|