mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +00:00
round.Helper was the canonical anti-pattern: a bare-noun
qualifier-name that told you nothing about what the type does. The
type is the embeddable Session implementation that protocol round
structs anchor on — its VALUE is "the base every round builds on".
Rename it to that.
type Helper struct { ... } → type Base struct { ... }
func (h *Helper) ... → func (h *Base) ...
func NewSession(...) (*Helper, error) → (*Base, error)
Output { *Helper } → Output { *Base }
Abort { *Helper } → Abort { *Base }
round.Helper → round.Base (18 files)
Helper: helper → Base: helper (struct literal init)
r.Helper.X / r.Helper → r.Base.X / r.Base (field access)
internal/round/helper.go → base.go
internal/round/helper_test.go → base_test.go
Forward-only — no type alias, no deprecation. The Go FAQ pointer-
receiver consistency landed in the prior commit (f7b1365) so the
method set is already aligned; this commit only renames the type.
Build + vet clean; round / CMP keygen smoke / FROST keygen all
PASS unchanged.
228 lines
6.7 KiB
Go
228 lines
6.7 KiB
Go
package round
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"sync"
|
|
|
|
"github.com/luxfi/threshold/internal/types"
|
|
"github.com/luxfi/threshold/pkg/hash"
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
"github.com/luxfi/threshold/pkg/party"
|
|
"github.com/luxfi/threshold/pkg/pool"
|
|
)
|
|
|
|
// Base is the round-package's implementation of Session minus the per-round
|
|
// Round methods (Finalize, VerifyMessage, StoreMessage, ...). Every protocol
|
|
// round embeds *Base as its first field so the round struct trivially
|
|
// satisfies Session through method promotion. The protocol-specific Round
|
|
// methods are then defined on the embedding round struct.
|
|
type Base struct {
|
|
info Info
|
|
|
|
// Pool allows us to parallelize certain operations
|
|
Pool *pool.Pool
|
|
|
|
// partyIDs is a sorted slice of Info.PartyIDs.
|
|
partyIDs party.IDSlice
|
|
// otherPartyIDs is the same as partyIDs without selfID
|
|
otherPartyIDs party.IDSlice
|
|
|
|
// ssid the unique identifier for this protocol execution
|
|
ssid []byte
|
|
|
|
hash *hash.Hash
|
|
|
|
mtx sync.Mutex
|
|
}
|
|
|
|
// NewSession creates a new *Base which can be embedded in the first Round,
|
|
// so that the full struct implements Session.
|
|
// `sessionID` is an optional byte slice that can be provided by the user.
|
|
// When used, it should be unique for each execution of the protocol.
|
|
// It could be a counter incremented after execution, or a common random string.
|
|
// `auxInfo` is a variable list of objects which should be included in the session's hash state.
|
|
func NewSession(info Info, sessionID []byte, pl *pool.Pool, auxInfo ...hash.WriterToWithDomain) (*Base, error) {
|
|
partyIDs := party.NewIDSlice(info.PartyIDs)
|
|
if !partyIDs.Valid() {
|
|
return nil, errors.New("session: partyIDs invalid")
|
|
}
|
|
|
|
// verify our ID is present
|
|
if !partyIDs.Contains(info.SelfID) {
|
|
return nil, errors.New("session: selfID not included in partyIDs")
|
|
}
|
|
|
|
// make sure the threshold is correct
|
|
if info.Threshold < 0 || info.Threshold > math.MaxUint32 {
|
|
return nil, fmt.Errorf("session: threshold %d is invalid", info.Threshold)
|
|
}
|
|
|
|
// the number of users satisfies the threshold
|
|
if n := len(partyIDs); n <= 0 || info.Threshold > n-1 {
|
|
return nil, fmt.Errorf("session: threshold %d is invalid for number of parties %d", info.Threshold, n)
|
|
}
|
|
|
|
var err error
|
|
h := hash.New()
|
|
|
|
if sessionID != nil {
|
|
if err = h.WriteAny(&hash.BytesWithDomain{
|
|
TheDomain: "Session ID",
|
|
Bytes: sessionID,
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("session: %w", err)
|
|
}
|
|
}
|
|
|
|
if err = h.WriteAny(&hash.BytesWithDomain{
|
|
TheDomain: "Protocol ID",
|
|
Bytes: []byte(info.ProtocolID),
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("session: %w", err)
|
|
}
|
|
|
|
if info.Group != nil {
|
|
if err = h.WriteAny(&hash.BytesWithDomain{
|
|
TheDomain: "Group Name",
|
|
Bytes: []byte(info.Group.Name()),
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("session: %w", err)
|
|
}
|
|
}
|
|
|
|
if err = h.WriteAny(partyIDs); err != nil {
|
|
return nil, fmt.Errorf("session: %w", err)
|
|
}
|
|
|
|
if err = h.WriteAny(types.ThresholdWrapper(info.Threshold)); err != nil {
|
|
return nil, fmt.Errorf("session: %w", err)
|
|
}
|
|
|
|
for _, a := range auxInfo {
|
|
if a == nil {
|
|
continue
|
|
}
|
|
if err = h.WriteAny(a); err != nil {
|
|
return nil, fmt.Errorf("session: %w", err)
|
|
}
|
|
}
|
|
|
|
return &Base{
|
|
info: info,
|
|
Pool: pl,
|
|
partyIDs: partyIDs,
|
|
otherPartyIDs: partyIDs.Remove(info.SelfID),
|
|
ssid: h.Clone().Sum(),
|
|
hash: h,
|
|
}, nil
|
|
}
|
|
|
|
// HashForID returns a clone of the hash.Hash for this session, initialized with the given id.
|
|
func (h *Base) HashForID(id party.ID) *hash.Hash {
|
|
h.mtx.Lock()
|
|
defer h.mtx.Unlock()
|
|
|
|
cloned := h.hash.Clone()
|
|
if id != "" {
|
|
_ = cloned.WriteAny(id)
|
|
}
|
|
|
|
return cloned
|
|
}
|
|
|
|
// UpdateHashState writes additional data to the hash state.
|
|
func (h *Base) UpdateHashState(value hash.WriterToWithDomain) {
|
|
h.mtx.Lock()
|
|
defer h.mtx.Unlock()
|
|
_ = h.hash.WriteAny(value)
|
|
}
|
|
|
|
// BroadcastMessage constructs a Message from the broadcast Content, and sets the header correctly.
|
|
// An error is returned if the message cannot be sent to the out channel.
|
|
func (h *Base) BroadcastMessage(out chan<- *Message, broadcastContent Content) error {
|
|
msg := &Message{
|
|
From: h.info.SelfID,
|
|
Broadcast: true,
|
|
Content: broadcastContent,
|
|
}
|
|
select {
|
|
case out <- msg:
|
|
return nil
|
|
default:
|
|
return ErrOutChanFull
|
|
}
|
|
}
|
|
|
|
// SendMessage is a convenience method for safely sending content to some party. If the message is
|
|
// intended for all participants (but does not require reliable broadcast), the `to` field may be empty ("").
|
|
// Returns an error if the message failed to send over out channel.
|
|
// `out` is expected to be a buffered channel with enough capacity to store all messages.
|
|
func (h *Base) SendMessage(out chan<- *Message, content Content, to party.ID) error {
|
|
msg := &Message{
|
|
From: h.info.SelfID,
|
|
To: to,
|
|
Content: content,
|
|
}
|
|
select {
|
|
case out <- msg:
|
|
return nil
|
|
default:
|
|
return ErrOutChanFull
|
|
}
|
|
}
|
|
|
|
// Hash returns copy of the hash function of this protocol execution.
|
|
func (h *Base) Hash() *hash.Hash {
|
|
h.mtx.Lock()
|
|
defer h.mtx.Unlock()
|
|
return h.hash.Clone()
|
|
}
|
|
|
|
// ResultRound returns a round that contains only the result of the protocol.
|
|
// This indicates to the used that the protocol is finished.
|
|
func (h *Base) ResultRound(result interface{}) Session {
|
|
return &Output{
|
|
Base: h,
|
|
Result: result,
|
|
}
|
|
}
|
|
|
|
// AbortRound returns a round that contains only the culprits that were able to be identified during
|
|
// a faulty execution of the protocol. The error returned by Round.Finalize() in this case should still be nil.
|
|
func (h *Base) AbortRound(err error, culprits ...party.ID) Session {
|
|
return &Abort{
|
|
Base: h,
|
|
Culprits: culprits,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
// ProtocolID is an identifier for this protocol.
|
|
func (h *Base) ProtocolID() string { return h.info.ProtocolID }
|
|
|
|
// FinalRoundNumber is the number of rounds before the output round.
|
|
func (h *Base) FinalRoundNumber() Number { return h.info.FinalRoundNumber }
|
|
|
|
// SSID the unique identifier for this protocol execution.
|
|
func (h *Base) SSID() []byte { return h.ssid }
|
|
|
|
// SelfID is this party's ID.
|
|
func (h *Base) SelfID() party.ID { return h.info.SelfID }
|
|
|
|
// PartyIDs is a sorted slice of participating parties in this protocol.
|
|
func (h *Base) PartyIDs() party.IDSlice { return h.partyIDs }
|
|
|
|
// OtherPartyIDs returns a sorted list of parties that does not contain SelfID.
|
|
func (h *Base) OtherPartyIDs() party.IDSlice { return h.otherPartyIDs }
|
|
|
|
// Threshold is the maximum number of parties that are assumed to be corrupted during the execution of this protocol.
|
|
func (h *Base) Threshold() int { return h.info.Threshold }
|
|
|
|
// N returns the number of participants.
|
|
func (h *Base) N() int { return len(h.info.PartyIDs) }
|
|
|
|
// Group returns the curve used for this protocol.
|
|
func (h *Base) Group() curve.Curve { return h.info.Group }
|