round: rename Helper to Base — name the value, not the laziness

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 (278c071) 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.
This commit is contained in:
Hanzo AI
2026-06-01 00:26:33 -07:00
parent 80b59fb68d
commit 8c1ef0105d
35 changed files with 72 additions and 69 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ import "github.com/luxfi/threshold/pkg/party"
// Abort is an empty round containing a list of parties who misbehaved.
type Abort struct {
*Helper
*Base
Culprits []party.ID
Err error
}
@@ -13,9 +13,12 @@ import (
"github.com/luxfi/threshold/pkg/pool"
)
// Helper implements Session without Round, and can therefore be embedded in the first round of a protocol
// in order to satisfy the Session interface.
type Helper struct {
// 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
@@ -34,13 +37,13 @@ type Helper struct {
mtx sync.Mutex
}
// NewSession creates a new *Helper which can be embedded in the first Round,
// 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 simple counter which is incremented after execution, or a common random string.
// 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) (*Helper, error) {
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")
@@ -106,7 +109,7 @@ func NewSession(info Info, sessionID []byte, pl *pool.Pool, auxInfo ...hash.Writ
}
}
return &Helper{
return &Base{
info: info,
Pool: pl,
partyIDs: partyIDs,
@@ -117,7 +120,7 @@ func NewSession(info Info, sessionID []byte, pl *pool.Pool, auxInfo ...hash.Writ
}
// HashForID returns a clone of the hash.Hash for this session, initialized with the given id.
func (h *Helper) HashForID(id party.ID) *hash.Hash {
func (h *Base) HashForID(id party.ID) *hash.Hash {
h.mtx.Lock()
defer h.mtx.Unlock()
@@ -130,7 +133,7 @@ func (h *Helper) HashForID(id party.ID) *hash.Hash {
}
// UpdateHashState writes additional data to the hash state.
func (h *Helper) UpdateHashState(value hash.WriterToWithDomain) {
func (h *Base) UpdateHashState(value hash.WriterToWithDomain) {
h.mtx.Lock()
defer h.mtx.Unlock()
_ = h.hash.WriteAny(value)
@@ -138,7 +141,7 @@ func (h *Helper) UpdateHashState(value hash.WriterToWithDomain) {
// 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 *Helper) BroadcastMessage(out chan<- *Message, broadcastContent Content) error {
func (h *Base) BroadcastMessage(out chan<- *Message, broadcastContent Content) error {
msg := &Message{
From: h.info.SelfID,
Broadcast: true,
@@ -156,7 +159,7 @@ func (h *Helper) BroadcastMessage(out chan<- *Message, broadcastContent Content)
// 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 *Helper) SendMessage(out chan<- *Message, content Content, to party.ID) error {
func (h *Base) SendMessage(out chan<- *Message, content Content, to party.ID) error {
msg := &Message{
From: h.info.SelfID,
To: to,
@@ -171,7 +174,7 @@ func (h *Helper) SendMessage(out chan<- *Message, content Content, to party.ID)
}
// Hash returns copy of the hash function of this protocol execution.
func (h *Helper) Hash() *hash.Hash {
func (h *Base) Hash() *hash.Hash {
h.mtx.Lock()
defer h.mtx.Unlock()
return h.hash.Clone()
@@ -179,46 +182,46 @@ func (h *Helper) Hash() *hash.Hash {
// ResultRound returns a round that contains only the result of the protocol.
// This indicates to the used that the protocol is finished.
func (h *Helper) ResultRound(result interface{}) Session {
func (h *Base) ResultRound(result interface{}) Session {
return &Output{
Helper: h,
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 *Helper) AbortRound(err error, culprits ...party.ID) Session {
func (h *Base) AbortRound(err error, culprits ...party.ID) Session {
return &Abort{
Helper: h,
Base: h,
Culprits: culprits,
Err: err,
}
}
// ProtocolID is an identifier for this protocol.
func (h *Helper) ProtocolID() string { return h.info.ProtocolID }
func (h *Base) ProtocolID() string { return h.info.ProtocolID }
// FinalRoundNumber is the number of rounds before the output round.
func (h *Helper) FinalRoundNumber() Number { return h.info.FinalRoundNumber }
func (h *Base) FinalRoundNumber() Number { return h.info.FinalRoundNumber }
// SSID the unique identifier for this protocol execution.
func (h *Helper) SSID() []byte { return h.ssid }
func (h *Base) SSID() []byte { return h.ssid }
// SelfID is this party's ID.
func (h *Helper) SelfID() party.ID { return h.info.SelfID }
func (h *Base) SelfID() party.ID { return h.info.SelfID }
// PartyIDs is a sorted slice of participating parties in this protocol.
func (h *Helper) PartyIDs() party.IDSlice { return h.partyIDs }
func (h *Base) PartyIDs() party.IDSlice { return h.partyIDs }
// OtherPartyIDs returns a sorted list of parties that does not contain SelfID.
func (h *Helper) OtherPartyIDs() party.IDSlice { return h.otherPartyIDs }
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 *Helper) Threshold() int { return h.info.Threshold }
func (h *Base) Threshold() int { return h.info.Threshold }
// N returns the number of participants.
func (h *Helper) N() int { return len(h.info.PartyIDs) }
func (h *Base) N() int { return len(h.info.PartyIDs) }
// Group returns the curve used for this protocol.
func (h *Helper) Group() curve.Curve { return h.info.Group }
func (h *Base) Group() curve.Curve { return h.info.Group }
+1 -1
View File
@@ -2,7 +2,7 @@ package round
// Output is an empty round containing the output of the protocol.
type Output struct {
*Helper
*Base
Result interface{}
}
+3 -3
View File
@@ -18,7 +18,7 @@ const Rounds round.Number = 5
func Start(info round.Info, pl *pool.Pool, c *config.Config) protocol.StartFunc {
return func(sessionID []byte) (_ round.Session, err error) {
var helper *round.Helper
var helper *round.Base
if c == nil {
helper, err = round.NewSession(info, sessionID, pl)
} else {
@@ -41,7 +41,7 @@ func Start(info round.Info, pl *pool.Pool, c *config.Config) protocol.StartFunc
PublicSharesECDSA[id] = public.ECDSA
}
return &round1{
Helper: helper,
Base: helper,
PreviousSecretECDSA: c.ECDSA,
PreviousPublicSharesECDSA: PublicSharesECDSA,
PreviousChainKey: c.ChainKey,
@@ -54,7 +54,7 @@ func Start(info round.Info, pl *pool.Pool, c *config.Config) protocol.StartFunc
VSSConstant := sample.Scalar(rand.Reader, group)
VSSSecret := polynomial.NewPolynomial(group, helper.Threshold(), VSSConstant)
return &round1{
Helper: helper,
Base: helper,
VSSSecret: VSSSecret,
keyID: keyID,
}, nil
+1 -1
View File
@@ -18,7 +18,7 @@ import (
var _ round.Round = (*round1)(nil)
type round1 struct {
*round.Helper
*round.Base
// PreviousSecretECDSA = sk'ᵢ
// Contains the previous secret ECDSA key share which is being refreshed
+1 -1
View File
@@ -19,7 +19,7 @@ import (
var _ round.Round = (*presign1)(nil)
type presign1 struct {
*round.Helper
*round.Base
// Pool allows us to parallelize certain operations
Pool *pool.Pool
+1 -1
View File
@@ -149,7 +149,7 @@ func (r *presign7) Finalize(out chan<- *round.Message) (round.Session, error) {
}
rSign1 := &sign1{
Helper: r.Helper,
Base: r.Base,
PublicKey: r.PublicKey,
Message: r.Message,
PreSignature: preSignature,
+2 -2
View File
@@ -77,7 +77,7 @@ func StartPresign(c *config.Config, signers []party.ID, message []byte, pl *pool
}
return &presign1{
Helper: helper,
Base: helper,
Pool: pl,
SecretECDSA: SecretECDSA,
SecretElGamal: c.ElGamal,
@@ -137,7 +137,7 @@ func StartPresignOnline(c *config.Config, preSignature *ecdsa.PreSignature, mess
}
return &sign1{
Helper: helper,
Base: helper,
PublicKey: c.PublicPoint(),
Message: message,
PreSignature: preSignature,
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var _ round.Round = (*sign1)(nil)
type sign1 struct {
*round.Helper
*round.Base
// PublicKey = X
PublicKey curve.Point
// Message = m
+1 -1
View File
@@ -15,7 +15,7 @@ import (
var _ round.Round = (*round1)(nil)
type round1 struct {
*round.Helper
*round.Base
PublicKey curve.Point
+1 -1
View File
@@ -70,7 +70,7 @@ func StartSign(config *config.Config, signers []party.ID, message []byte, pl *po
}
return &round1{
Helper: helper,
Base: helper,
PublicKey: PublicKey,
SecretECDSA: SecretECDSA,
SecretPaillier: SecretPaillier,
+2 -2
View File
@@ -125,7 +125,7 @@ func StartKeygen(group curve.Curve, receiver bool, selfID, otherID party.ID, sec
if receiver {
return &round1R{
Helper: helper,
Base: helper,
refresh: refresh,
secretShare: secretShare,
publicShare: publicShare,
@@ -134,7 +134,7 @@ func StartKeygen(group curve.Curve, receiver bool, selfID, otherID party.ID, sec
}, nil
}
return &round1S{
Helper: helper,
Base: helper,
refresh: refresh,
secretShare: secretShare,
publicShare: publicShare,
+1 -1
View File
@@ -28,7 +28,7 @@ func (message1R) RoundNumber() round.Number { return 1 }
// round1R corresponds to the first round from the Receiver's perspective.
type round1R struct {
*round.Helper
*round.Base
// refresh indicates whether or not we should refresh
refresh bool
// public is an existing public key, if we're refresing
+1 -1
View File
@@ -30,7 +30,7 @@ func (message1S) RoundNumber() round.Number { return 2 }
// round1S corresponds to the second round from the Sender's perspective.
type round1S struct {
*round.Helper
*round.Base
// refresh indicates if we're refreshing, instead of generating a new key.
refresh bool
// public is an existing public key, when refreshing
+1 -1
View File
@@ -24,7 +24,7 @@ type message1R struct {
func (message1R) RoundNumber() round.Number { return 1 }
type round1R struct {
*round.Helper
*round.Base
hash []byte
config *keygen.ConfigReceiver
}
+1 -1
View File
@@ -27,7 +27,7 @@ func (message1S) RoundNumber() round.Number { return 2 }
// round1S is the first round from the Sender's perspective.
type round1S struct {
*round.Helper
*round.Base
config *keygen.ConfigSender
// The message hash to be signed.
hash []byte
+2 -2
View File
@@ -32,7 +32,7 @@ func StartSignReceiver(config *keygen.ConfigReceiver, selfID, otherID party.ID,
return nil, fmt.Errorf("keygen.StartKeygen: %w", err)
}
return &round1R{Helper: helper, config: config, hash: hash}, nil
return &round1R{Base: helper, config: config, hash: hash}, nil
}
}
@@ -58,6 +58,6 @@ func StartSignSender(config *keygen.ConfigSender, selfID, otherID party.ID, hash
return nil, fmt.Errorf("keygen.StartKeygen: %w", err)
}
return &round1S{Helper: helper, config: config, hash: hash}, nil
return &round1S{Base: helper, config: config, hash: hash}, nil
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ func StartXOR(selfID party.ID, partyIDs party.IDSlice) protocol.StartFunc {
return nil, fmt.Errorf("xor: %w", err)
}
r := &xor.Round1{
Helper: helper,
Base: helper,
}
return r, nil
}
+2 -2
View File
@@ -8,9 +8,9 @@ import (
"github.com/luxfi/threshold/pkg/party"
)
// Round1 can embed round.Helper which provides useful methods handling messages.
// Round1 can embed round.Base which provides useful methods handling messages.
type Round1 struct {
*round.Helper
*round.Base
}
// VerifyMessage in the first round does nothing since no messages are expected.
+1 -1
View File
@@ -60,7 +60,7 @@ func StartKeygenCommon(taproot bool, group curve.Curve, participants []party.ID,
}
return &round1{
Helper: helper,
Base: helper,
taproot: taproot,
threshold: threshold,
refresh: refresh,
+3 -3
View File
@@ -19,7 +19,7 @@ import (
//
// https://eprint.iacr.org/2020/852.pdf
type round1 struct {
*round.Helper
*round.Base
// taproot indicates whether or not to make taproot compatible keys.
//
// This means taking the necessary steps to ensure that the shared secret generates
@@ -98,7 +98,7 @@ func (r *round1) Finalize(out chan<- *round.Message) (round.Session, error) {
// Refresh: Don't create a proof.
var SigmaI *zksch.Proof
if !r.refresh {
SigmaI = zksch.NewProof(r.Helper.HashForID(r.SelfID()), aI0TimesG, aI0, nil)
SigmaI = zksch.NewProof(r.Base.HashForID(r.SelfID()), aI0TimesG, aI0, nil)
}
// 3. "Every participant Pᵢ computes a public comment Φᵢ = <ϕᵢ₀, ..., ϕᵢₜ>
@@ -125,7 +125,7 @@ func (r *round1) Finalize(out chan<- *round.Message) (round.Session, error) {
return r, fmt.Errorf("failed to sample ChainKey")
}
// Use session-based hash for commitments - with OUR ID
commitment, decommitment, err = r.Helper.HashForID(r.SelfID()).Commit(cI)
commitment, decommitment, err = r.Base.HashForID(r.SelfID()).Commit(cI)
if err != nil {
return r, fmt.Errorf("failed to commit to chain key")
}
+1 -1
View File
@@ -100,7 +100,7 @@ func (r *round2) StoreBroadcastMessage(msg round.Message) error {
return fmt.Errorf("party %s sent a non-zero constant while refreshing", from)
}
} else {
if !body.SigmaI.Verify(r.Helper.HashForID(from), body.PhiI.Constant(), nil) {
if !body.SigmaI.Verify(r.Base.HashForID(from), body.PhiI.Constant(), nil) {
return fmt.Errorf("failed to verify Schnorr proof for party %s", from)
}
}
+1 -1
View File
@@ -62,7 +62,7 @@ func (r *round3) StoreBroadcastMessage(msg round.Message) error {
// Use session-based hash for verification - using the SENDER's ID
// The Helper should be the same as the one used in round1 for commitment creation
if !r.Helper.HashForID(from).Decommit(commitment, body.Decommitment, body.CL) {
if !r.Base.HashForID(from).Decommit(commitment, body.Decommitment, body.CL) {
return fmt.Errorf("failed to verify chain key commitment from party %s (hash mismatch)", from)
}
r.ChainKeys.Store(from, body.CL)
+1 -1
View File
@@ -22,7 +22,7 @@ import (
// There are also differences corresponding to the lack of a signing authority,
// namely that these commitments are broadcast, instead of stored with the authority.
type round1 struct {
*round.Helper
*round.Base
// taproot indicates whether or not we need to generate Taproot / BIP-340 signatures.
//
// If so, we have a few slight tweaks to make around the evenness of points,
+1 -1
View File
@@ -54,7 +54,7 @@ func StartSignSR25519Common(taproot, sr25519 bool, signingContext []byte, result
return nil, fmt.Errorf("sign.StartSign: %w", err)
}
return &round1{
Helper: helper,
Base: helper,
taproot: taproot,
sr25519: sr25519,
signingContext: signingContext,
+1 -1
View File
@@ -28,7 +28,7 @@ func Start(selfID party.ID, participants []party.ID, threshold int, group curve.
}
return &round1{
Helper: helper,
Base: helper,
// sync.Map fields are zero-initialized and don't need explicit initialization
}, nil
}
+2 -2
View File
@@ -15,7 +15,7 @@ import (
// round1 generates polynomial and broadcasts commitments
type round1 struct {
*round.Helper
*round.Base
// Our polynomial for secret sharing
poly *polynomial.Polynomial
@@ -175,7 +175,7 @@ func (r *round1) Finalize(out chan<- *round.Message) (round.Session, error) {
// Create round2 with complete data
return &round2{
Helper: r.Helper,
Base: r.Base,
poly: r.poly,
commitments: commitments,
chainKeys: chainKeys,
+2 -2
View File
@@ -14,7 +14,7 @@ import (
// round2 receives commitments and sends shares
type round2 struct {
*round.Helper
*round.Base
// Our polynomial from round 1
poly *polynomial.Polynomial
@@ -166,7 +166,7 @@ func (r *round2) Finalize(out chan<- *round.Message) (round.Session, error) {
// We have all shares, advance to round3
return &round3{
Helper: r.Helper,
Base: r.Base,
commitments: r.commitments,
chainKeys: r.chainKeys,
shares: shares,
+1 -1
View File
@@ -13,7 +13,7 @@ import (
// round3 finalizes the keygen protocol
type round3 struct {
*round.Helper
*round.Base
// Data from previous rounds
commitments map[party.ID]map[party.ID]curve.Point
+1 -1
View File
@@ -73,7 +73,7 @@ func Start(oldConfig *config.Config, newParticipants []party.ID, newThreshold in
}
return &round1{
Helper: helper,
Base: helper,
oldConfig: oldConfig,
newParticipants: newParticipants,
newThreshold: newThreshold,
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// round1 initiates resharing by generating new polynomial shares
type round1 struct {
*round.Helper
*round.Base
oldConfig *config.Config
newParticipants []party.ID
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// round1 generates nonces for signing
type round1 struct {
*round.Helper
*round.Base
config *config.Config
signers []party.ID
+1 -1
View File
@@ -47,7 +47,7 @@ func Start(c *config.Config, signers []party.ID, messageHash []byte, pl *pool.Po
}
return &round1{
Helper: helper,
Base: helper,
config: c,
signers: signers,
messageHash: messageHash,
+2 -2
View File
@@ -56,7 +56,7 @@ func SignWithBlinding(c *config.Config, signers []party.ID, messageHash []byte,
// blindingRoundI implements Protocol I from the LSS paper
type blindingRoundI struct {
*round.Helper
*round.Base
config *config.Config
signers []party.ID
@@ -105,7 +105,7 @@ func startBlindingProtocolI(c *config.Config, signers []party.ID, messageHash []
return nil, err
}
r.Helper = helper
r.Base = helper
return r, nil
}