mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +00:00
VerifyMessage/StoreMessage/MessageContent/Number/BroadcastContent on round structs were value receivers. Go FAQ: "if any method on T takes *T, all methods should take *T." Finalize already takes *T (it mutates round state) so the others should too. Aligns the method set, removes silent struct-copies on every dispatch, and prevents future drift where a value-receiver method accidentally observes pre-Finalize state. No behavior change — all current methods are no-ops or return constants. Touched: round.Abort, round.Output, all CMP keygen/presign/sign rounds, all Doerner keygen/sign rounds, all FROST keygen/sign rounds, xor example. Build + vet clean; CMP/FROST/Doerner -race tests PASS.
219 lines
6.3 KiB
Go
219 lines
6.3 KiB
Go
package presign
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/cronokirby/saferith"
|
|
"github.com/luxfi/threshold/internal/elgamal"
|
|
"github.com/luxfi/threshold/internal/round"
|
|
"github.com/luxfi/threshold/pkg/hash"
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
"github.com/luxfi/threshold/pkg/paillier"
|
|
"github.com/luxfi/threshold/pkg/party"
|
|
zkaffg "github.com/luxfi/threshold/pkg/zk/affg"
|
|
zkaffp "github.com/luxfi/threshold/pkg/zk/affp"
|
|
)
|
|
|
|
var _ round.Round = (*presign3)(nil)
|
|
|
|
type presign3 struct {
|
|
*presign2
|
|
// DeltaShareBeta[j] = βᵢⱼ
|
|
DeltaShareBeta map[party.ID]*saferith.Int
|
|
// ChiShareBeta[j] = β̂ᵢⱼ
|
|
ChiShareBeta map[party.ID]*saferith.Int
|
|
|
|
// DeltaCiphertext[j][k] = Dₖⱼ
|
|
DeltaCiphertext map[party.ID]map[party.ID]*paillier.Ciphertext
|
|
// ChiCiphertext[j][k] = D̂ₖⱼ
|
|
ChiCiphertext map[party.ID]map[party.ID]*paillier.Ciphertext
|
|
}
|
|
|
|
type broadcast3 struct {
|
|
round.NormalBroadcastContent
|
|
// DeltaCiphertext[k] = Dₖⱼ
|
|
DeltaCiphertext map[party.ID]*paillier.Ciphertext
|
|
// ChiCiphertext[k] = D̂ₖⱼ
|
|
ChiCiphertext map[party.ID]*paillier.Ciphertext
|
|
}
|
|
|
|
type message3 struct {
|
|
DeltaF *paillier.Ciphertext // DeltaF = Fᵢⱼ
|
|
DeltaProof *zkaffp.Proof
|
|
ChiF *paillier.Ciphertext // ChiF = F̂ᵢⱼ
|
|
ChiProof *zkaffg.Proof
|
|
}
|
|
|
|
// StoreBroadcastMessage implements round.BroadcastRound.
|
|
func (r *presign3) StoreBroadcastMessage(msg round.Message) error {
|
|
from := msg.From
|
|
body, ok := msg.Content.(*broadcast3)
|
|
if !ok || body == nil {
|
|
return round.ErrInvalidContent
|
|
}
|
|
|
|
if body.DeltaCiphertext == nil || body.ChiCiphertext == nil {
|
|
return round.ErrNilFields
|
|
}
|
|
|
|
for _, id := range r.PartyIDs() {
|
|
if id == from {
|
|
continue
|
|
}
|
|
DeltaCiphertext, ChiCiphertext := body.DeltaCiphertext[id], body.ChiCiphertext[id]
|
|
if !r.Paillier[id].ValidateCiphertexts(DeltaCiphertext, ChiCiphertext) {
|
|
return errors.New("received invalid ciphertext")
|
|
}
|
|
}
|
|
|
|
r.DeltaCiphertext[from] = body.DeltaCiphertext
|
|
r.ChiCiphertext[from] = body.ChiCiphertext
|
|
return nil
|
|
}
|
|
|
|
// VerifyMessage implements round.Round.
|
|
//
|
|
// - verify zkaffg, zkaffp.
|
|
func (r *presign3) VerifyMessage(msg round.Message) error {
|
|
from, to := msg.From, msg.To
|
|
body, ok := msg.Content.(*message3)
|
|
if !ok || body == nil {
|
|
return round.ErrInvalidContent
|
|
}
|
|
|
|
if !body.DeltaProof.Verify(r.Group(), r.HashForID(from), zkaffp.Public{
|
|
Kv: r.K[to],
|
|
Dv: r.DeltaCiphertext[from][to],
|
|
Fp: body.DeltaF,
|
|
Xp: r.G[from],
|
|
Prover: r.Paillier[from],
|
|
Verifier: r.Paillier[to],
|
|
Aux: r.Pedersen[to],
|
|
}) {
|
|
return errors.New("failed to validate affp proof for Delta MtA")
|
|
}
|
|
|
|
if !body.ChiProof.Verify(r.HashForID(from), zkaffg.Public{
|
|
Kv: r.K[to],
|
|
Dv: r.ChiCiphertext[from][to],
|
|
Fp: body.ChiF,
|
|
Xp: r.ECDSA[from],
|
|
Prover: r.Paillier[from],
|
|
Verifier: r.Paillier[to],
|
|
Aux: r.Pedersen[to],
|
|
}) {
|
|
return errors.New("failed to validate affg proof for Chi MtA")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StoreMessage implements round.Round.
|
|
func (*presign3) StoreMessage(round.Message) error { return nil }
|
|
|
|
// Finalize implements round.Round
|
|
//
|
|
// - Decrypt MtA shares,
|
|
// - save αᵢⱼ, α̂ᵢⱼ.
|
|
// - Γ = ∑ⱼ Γⱼ
|
|
// - Δᵢ = [kᵢ]Γ
|
|
// - δᵢ = γᵢ kᵢ + ∑ⱼ αᵢⱼ + βᵢⱼ
|
|
// - χᵢ = xᵢ kᵢ + ∑ⱼ α̂ᵢⱼ + β̂ᵢⱼ
|
|
// - Ẑⱼ, b̂ⱼ
|
|
func (r *presign3) Finalize(out chan<- *round.Message) (round.Session, error) {
|
|
// δᵢ = γᵢ kᵢ
|
|
KShareInt := curve.MakeInt(r.KShare)
|
|
DeltaShare := new(saferith.Int).Mul(r.GammaShare, KShareInt, -1)
|
|
|
|
DeltaSharesAlpha := make(map[party.ID]*saferith.Int, r.N())
|
|
ChiSharesAlpha := make(map[party.ID]*saferith.Int, r.N())
|
|
|
|
// χᵢ = xᵢ kᵢ
|
|
ChiShare := new(saferith.Int).Mul(curve.MakeInt(r.SecretECDSA), KShareInt, -1)
|
|
|
|
var (
|
|
culprits []party.ID
|
|
err error
|
|
)
|
|
for _, j := range r.OtherPartyIDs() {
|
|
// αᵢⱼ
|
|
DeltaSharesAlpha[j], err = r.SecretPaillier.Dec(r.DeltaCiphertext[j][r.SelfID()])
|
|
if err != nil {
|
|
culprits = append(culprits, j)
|
|
continue
|
|
}
|
|
// α̂ᵢⱼ
|
|
ChiSharesAlpha[j], err = r.SecretPaillier.Dec(r.ChiCiphertext[j][r.SelfID()])
|
|
if err != nil {
|
|
culprits = append(culprits, j)
|
|
continue
|
|
}
|
|
// δᵢ += αᵢⱼ + βᵢⱼ
|
|
DeltaShare.Add(DeltaShare, DeltaSharesAlpha[j], -1)
|
|
DeltaShare.Add(DeltaShare, r.DeltaShareBeta[j], -1)
|
|
|
|
// χᵢ += α̂ᵢⱼ + β̂ᵢⱼ
|
|
ChiShare.Add(ChiShare, ChiSharesAlpha[j], -1)
|
|
ChiShare.Add(ChiShare, r.ChiShareBeta[j], -1)
|
|
}
|
|
if culprits != nil {
|
|
return r.AbortRound(fmt.Errorf("failed to decrypt alpha shares for mta"), culprits...), nil
|
|
}
|
|
|
|
// ElGamalChi = Ẑⱼ = (b̂ⱼ⋅G, χᵢ+b̂ⱼ⋅Yᵢ)
|
|
// ElGamalChiNonce = b̂ⱼ
|
|
ElGamalChi, ElGamalChiNonce := elgamal.Encrypt(r.ElGamal[r.SelfID()], r.Group().NewScalar().SetNat(ChiShare.Mod(r.Group().Order())))
|
|
|
|
DeltaShareScalar := r.Group().NewScalar().SetNat(DeltaShare.Mod(r.Group().Order()))
|
|
|
|
msg := &broadcast4{
|
|
DeltaShare: DeltaShareScalar,
|
|
ElGamalChi: ElGamalChi,
|
|
}
|
|
if err = r.BroadcastMessage(out, msg); err != nil {
|
|
return r, err
|
|
}
|
|
|
|
return &presign4{
|
|
presign3: r,
|
|
DeltaShareAlpha: DeltaSharesAlpha,
|
|
ChiShareAlpha: ChiSharesAlpha,
|
|
ElGamalChiNonce: ElGamalChiNonce,
|
|
ElGamalChi: map[party.ID]*elgamal.Ciphertext{r.SelfID(): ElGamalChi},
|
|
DeltaShares: map[party.ID]curve.Scalar{r.SelfID(): DeltaShareScalar},
|
|
ChiShare: r.Group().NewScalar().SetNat(ChiShare.Mod(r.Group().Order())),
|
|
}, nil
|
|
}
|
|
|
|
// RoundNumber implements round.Content.
|
|
func (message3) RoundNumber() round.Number { return 3 }
|
|
|
|
// MessageContent implements round.Round.
|
|
func (r *presign3) MessageContent() round.Content {
|
|
return &message3{ChiProof: zkaffg.Empty(r.Group())}
|
|
}
|
|
|
|
// RoundNumber implements round.Content.
|
|
func (broadcast3) RoundNumber() round.Number { return 3 }
|
|
|
|
// BroadcastContent implements round.BroadcastRound.
|
|
func (*presign3) BroadcastContent() round.BroadcastContent { return &broadcast3{} }
|
|
|
|
// Number implements round.Round.
|
|
func (*presign3) Number() round.Number { return 3 }
|
|
|
|
// BroadcastData implements broadcast.Broadcaster.
|
|
func (m broadcast3) BroadcastData() []byte {
|
|
h := hash.New()
|
|
ids := make([]party.ID, 0, len(m.DeltaCiphertext))
|
|
for id := range m.DeltaCiphertext {
|
|
ids = append(ids, id)
|
|
}
|
|
sortedIDs := party.NewIDSlice(ids)
|
|
for _, id := range sortedIDs {
|
|
_ = h.WriteAny(id, m.DeltaCiphertext[id], m.ChiCiphertext[id])
|
|
}
|
|
return h.Sum()
|
|
}
|