Files
zeekay 2b80255415 security(thresholdd): remove trusted-dealer BLS scheme — H-2
The thresholdd daemon registered a "bls" scheme whose keygen ran a
TrustedDealer in-process (one node mints the whole group secret key) —
the same single-point-of-compromise trusted-dealer BLS custody removed
from mpc. BLS is consensus-only (native per-validator keys), never a
custodied threshold key. Remove the daemon's bls scheme so the dispatcher
can no longer mint a trusted-dealer BLS key.

- Delete pkg/thresholdd/bls.go (blsScheme / TrustedDealer keygen).
- server.go: drop s.schemes["bls"] + the bls keygen/sign/verify procs.
- zap_schema.go: drop ProcBLS{Keygen,Sign,Verify} opcodes.
- Tests: delete the bls-specific round-trip + benchmarks; rewire the
  auth-gate tests (which used bls only as a fast keygen driver) to frost;
  drop bls from the sign_ctx unsupported-scheme loop.
- Scrub stale bls mentions from doc comments.

NOT touched: protocols/bls (the library primitive) stays — luxfi/chains
(a non-owned repo) consumes bls.TrustedDealer/AggregateSignatures
directly. Deleting protocols/bls must be a coordinated change with the
chains owner; flagged, not forced here.

Verified: GOWORK=off go build ./... green; affected thresholdd tests
(auth-gate, ProcOpcode, ZapShares, FrostRoundTrip, sign_ctx unsupported)
pass without -short. Zero bls refs remain in thresholdd.
2026-06-26 18:46:02 -07:00

148 lines
3.3 KiB
Go

package thresholdd
import (
"context"
"fmt"
"io"
"sync"
"time"
log "github.com/luxfi/log"
"github.com/luxfi/metric"
"github.com/luxfi/threshold/internal/test"
"github.com/luxfi/threshold/pkg/party"
"github.com/luxfi/threshold/pkg/protocol"
)
// runMultiparty drives a synchronous in-process MPC round trip for every
// listed party and returns the final per-party result map.
//
// This is the production path the daemon uses for the protocols that
// actually need a round-based exchange (cmp / frost / doerner). Pulsar
// and corona have direct single-process keygen functions and skip
// this runner.
func runMultiparty(
partyIDs []party.ID,
sessionID []byte,
start func(party.ID) protocol.StartFunc,
timeout time.Duration,
) (map[party.ID]any, error) {
if timeout <= 0 {
timeout = 2 * time.Minute
}
cfg := &protocol.Config{
Workers: 4,
PriorityWorkers: 4,
BufferSize: 10000,
PriorityBuffer: 1000,
MessageTimeout: 30 * time.Second,
RoundTimeout: 60 * time.Second,
ProtocolTimeout: timeout,
}
// Use a writer-backed logger at FatalLevel so NewHandler accepts it
// (NoOpLogger.IsZero() == true and is rejected). Discarding output
// keeps the daemon silent under load.
logger := log.NewWriter(io.Discard).Level(log.FatalLevel)
network := test.NewNetwork(partyIDs)
handlers := make(map[party.ID]*protocol.Handler, len(partyIDs))
for _, id := range partyIDs {
h, err := protocol.NewHandler(
context.Background(),
logger,
metric.NewRegistry(),
start(id),
sessionID,
cfg,
)
if err != nil {
return nil, fmt.Errorf("create handler %s: %w", id, err)
}
handlers[id] = h
}
results := make(map[party.ID]any, len(partyIDs))
errs := make(map[party.ID]error, len(partyIDs))
var mu sync.Mutex
var wg sync.WaitGroup
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
for id, h := range handlers {
wg.Add(1)
go func(id party.ID, h *protocol.Handler) {
defer wg.Done()
// per-party done channel scopes the router goroutines so
// fast finishers don't race the global ctx cancel.
done := make(chan struct{})
defer close(done)
go func() {
for {
select {
case <-done:
return
case <-ctx.Done():
return
case msg := <-network.Next(id):
if msg != nil {
h.Accept(msg)
}
}
}
}()
go func() {
for {
select {
case <-done:
return
case <-ctx.Done():
return
case msg, ok := <-h.Listen():
if !ok {
return
}
if msg != nil {
network.Send(msg)
}
}
}
}()
res, err := h.WaitForResult()
mu.Lock()
results[id] = res
errs[id] = err
mu.Unlock()
}(id, h)
}
done := make(chan struct{})
go func() { wg.Wait(); close(done) }()
select {
case <-done:
case <-ctx.Done():
return nil, fmt.Errorf("protocol timeout after %s", timeout)
}
for id, err := range errs {
if err != nil {
return results, fmt.Errorf("party %s: %w", id, err)
}
}
return results, nil
}
// partyIDs returns deterministic sorted party.IDs for n parties.
func partyIDs(n int) []party.ID {
out := make([]party.ID, n)
base := ""
for i := 0; i < n; i++ {
if i%26 == 0 && i > 0 {
base += "a"
}
out[i] = party.ID(base + string('a'+rune(i%26)))
}
return out
}