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.
194 lines
6.5 KiB
Go
194 lines
6.5 KiB
Go
package keygen
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/luxfi/threshold/internal/round"
|
|
"github.com/luxfi/threshold/internal/types"
|
|
"github.com/luxfi/threshold/pkg/hash"
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
"github.com/luxfi/threshold/pkg/math/polynomial"
|
|
"github.com/luxfi/threshold/pkg/math/sample"
|
|
"github.com/luxfi/threshold/pkg/party"
|
|
zksch "github.com/luxfi/threshold/pkg/zk/sch"
|
|
)
|
|
|
|
// This round corresponds with the steps 1-4 of Round 1, Figure 1 in the Frost paper:
|
|
//
|
|
// https://eprint.iacr.org/2020/852.pdf
|
|
type round1 struct {
|
|
*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
|
|
// a public key with even y coordinate.
|
|
//
|
|
// We also end up returning a different result, to accommodate this fact.
|
|
taproot bool
|
|
// threshold is the integer t which defines the maximum number of corruptions tolerated for this session.
|
|
//
|
|
// Alternatively, the degree of the polynomial used to share the secret.
|
|
//
|
|
// Alternatively, t + 1 participants are needed to make a signature.
|
|
threshold int
|
|
// refresh indicates whether or not we're doing a refresh instead of a key-generation.
|
|
refresh bool
|
|
// These fields are set to accommodate both key-generation, in which case they'll
|
|
// take on identity values, and refresh, in which case their values are meaningful.
|
|
// These values should be modifiable.
|
|
|
|
// privateShare is our previous private share when refreshing, and 0 otherwise.
|
|
privateShare curve.Scalar
|
|
// verificationShares should hold the previous verification shares when refreshing, and identity points otherwise.
|
|
verificationShares map[party.ID]curve.Point
|
|
// publicKey should be the previous public key when refreshing, and 0 otherwise.
|
|
publicKey curve.Point
|
|
}
|
|
|
|
// VerifyMessage implements round.Round.
|
|
//
|
|
// Since this is the start of the protocol, we aren't expecting to have received
|
|
// any messages yet, so we do nothing.
|
|
func (r *round1) VerifyMessage(round.Message) error { return nil }
|
|
|
|
// StoreMessage implements round.Round.
|
|
func (r *round1) StoreMessage(round.Message) error { return nil }
|
|
|
|
// Finalize implements round.Round.
|
|
//
|
|
// The overall goal of this round is to generate a secret value, create a polynomial
|
|
// sharing of that value, and then send commitments to these values.
|
|
func (r *round1) Finalize(out chan<- *round.Message) (round.Session, error) {
|
|
group := r.Group()
|
|
// These steps come from Figure 1, Round 1 of the Frost paper.
|
|
|
|
// 1. "Every participant P_i samples t + 1 random values (aᵢ₀, ..., aᵢₜ)) <-$ Z/(q)
|
|
// and uses these values as coefficients to define a degree t polynomial
|
|
// fᵢ(x) = ∑ⱼ₌₀ᵗ⁻¹ aᵢⱼ xʲ"
|
|
//
|
|
// Note: I've adjusted the thresholds in this quote to reflect our convention
|
|
// that t + 1 participants are needed to create a signature.
|
|
|
|
// Refresh: Instead of creating a new secret, instead use 0, so that our result doesn't change.
|
|
aI0 := group.NewScalar()
|
|
aI0TimesG := group.NewPoint()
|
|
if !r.refresh {
|
|
aI0 = sample.Scalar(rand.Reader, r.Group())
|
|
aI0TimesG = aI0.ActOnBase()
|
|
}
|
|
fI := polynomial.NewPolynomial(r.Group(), r.threshold, aI0)
|
|
|
|
// 2. "Every Pᵢ computes a proof of knowledge to the corresponding secret aᵢ₀
|
|
// by calculating σᵢ = (Rᵢ, μᵢ), such that:
|
|
//
|
|
// k <-$ Z/(q)
|
|
// Rᵢ = k * G
|
|
// cᵢ = H(i, ctx, aᵢ₀ • G, Rᵢ)
|
|
// μᵢ = k + aᵢ₀ cᵢ
|
|
//
|
|
// with ctx being a context string to prevent replay attacks"
|
|
|
|
// We essentially follow this, although the order of hashing ends up being slightly
|
|
// different.
|
|
// At this point, we've already hashed context inside of helper, so we just
|
|
// add in our own ID, and then we're good to go.
|
|
|
|
// Refresh: Don't create a proof.
|
|
var SigmaI *zksch.Proof
|
|
if !r.refresh {
|
|
SigmaI = zksch.NewProof(r.Base.HashForID(r.SelfID()), aI0TimesG, aI0, nil)
|
|
}
|
|
|
|
// 3. "Every participant Pᵢ computes a public comment Φᵢ = <ϕᵢ₀, ..., ϕᵢₜ>
|
|
// where ϕᵢⱼ = aᵢⱼ * G."
|
|
//
|
|
// Note: I've once again adjusted the threshold indices, I've also taken
|
|
// the liberty of renaming "Cᵢ" to "Φᵢ" so that we can later do Phi_i[j]
|
|
// for each individual commitment.
|
|
|
|
// This method conveniently calculates all of that for us
|
|
// PhiI = Φᵢ
|
|
PhiI := polynomial.NewPolynomialExponent(fI)
|
|
|
|
// cI is our contribution to the chaining key
|
|
// During refresh, we don't need to regenerate the chain key
|
|
var cI types.RID
|
|
var commitment hash.Commitment
|
|
var decommitment hash.Decommitment
|
|
var err error
|
|
|
|
if !r.refresh {
|
|
cI, err = types.NewRID(rand.Reader)
|
|
if err != nil {
|
|
return r, fmt.Errorf("failed to sample ChainKey")
|
|
}
|
|
// Use session-based hash for commitments - with OUR ID
|
|
commitment, decommitment, err = r.Base.HashForID(r.SelfID()).Commit(cI)
|
|
if err != nil {
|
|
return r, fmt.Errorf("failed to commit to chain key")
|
|
}
|
|
// Debug: Log commitment details (commented out for production)
|
|
// fmt.Printf("[ROUND1] Party %s created commitment: %x (len=%d) for chainkey: %x\n", r.SelfID(), commitment, len(commitment), cI)
|
|
} else {
|
|
// During refresh, use empty values for chain key
|
|
cI = types.EmptyRID()
|
|
// Use nil for refresh - they will be properly handled
|
|
commitment = nil
|
|
decommitment = nil
|
|
}
|
|
|
|
// 4. "Every Pᵢ broadcasts Φᵢ, σᵢ to all other participants
|
|
err = r.BroadcastMessage(out, &broadcast2{
|
|
PhiI: PhiI,
|
|
SigmaI: SigmaI,
|
|
Commitment: commitment,
|
|
})
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
|
|
// Make a defensive copy of the commitment to avoid potential issues with shared slices
|
|
var commitmentCopy []byte
|
|
if commitment != nil {
|
|
commitmentCopy = make([]byte, len(commitment))
|
|
copy(commitmentCopy, commitment)
|
|
}
|
|
|
|
// Initialize sync.Maps with initial values
|
|
phi := &sync.Map{}
|
|
phi.Store(r.SelfID(), PhiI)
|
|
|
|
chainKeys := &sync.Map{}
|
|
chainKeys.Store(r.SelfID(), cI)
|
|
|
|
chainKeyCommitments := &sync.Map{}
|
|
|
|
nextRound := &round2{
|
|
round1: r,
|
|
fI: fI,
|
|
ChainKeyDecommitment: decommitment,
|
|
}
|
|
// Copy values from the maps instead of copying the maps themselves
|
|
phi.Range(func(key, value interface{}) bool {
|
|
nextRound.Phi.Store(key, value)
|
|
return true
|
|
})
|
|
chainKeys.Range(func(key, value interface{}) bool {
|
|
nextRound.ChainKeys.Store(key, value)
|
|
return true
|
|
})
|
|
chainKeyCommitments.Range(func(key, value interface{}) bool {
|
|
nextRound.ChainKeyCommitments.Store(key, value)
|
|
return true
|
|
})
|
|
return nextRound, nil
|
|
}
|
|
|
|
// MessageContent implements round.Round.
|
|
func (*round1) MessageContent() round.Content { return nil }
|
|
|
|
// Number implements round.Round.
|
|
func (*round1) Number() round.Number { return 1 }
|