mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +00:00
144 lines
4.3 KiB
Go
144 lines
4.3 KiB
Go
package thresholdd
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
"github.com/luxfi/threshold/pkg/party"
|
|
"github.com/luxfi/threshold/pkg/protocol"
|
|
"github.com/luxfi/threshold/protocols/frost"
|
|
frostkeygen "github.com/luxfi/threshold/protocols/frost/keygen"
|
|
frostsign "github.com/luxfi/threshold/protocols/frost/sign"
|
|
)
|
|
|
|
// frostScheme wires luxfi/threshold/protocols/frost (RFC 9591) into
|
|
// the dispatcher's scheme surface. secp256k1 group only — the
|
|
// registry's policyId is "SCHNORR-SECP256K1-FROST-RFC9591".
|
|
type frostScheme struct {
|
|
mu sync.Mutex
|
|
sessions map[string]*frostSession
|
|
}
|
|
|
|
type frostSession struct {
|
|
threshold int
|
|
configs map[party.ID]*frostkeygen.Config
|
|
partyIDs []party.ID
|
|
}
|
|
|
|
func newFrostScheme() *frostScheme {
|
|
return &frostScheme{sessions: make(map[string]*frostSession)}
|
|
}
|
|
|
|
func (s *frostScheme) Keygen(p keygenParams) (keygenResult, error) {
|
|
if err := validateKeygenParams(p); err != nil {
|
|
return keygenResult{}, err
|
|
}
|
|
group := curve.Secp256k1{}
|
|
ids := partyIDs(p.Participants)
|
|
sessionID := []byte(fmt.Sprintf("frost-keygen-%d", time.Now().UnixNano()))
|
|
results, err := runMultiparty(ids, sessionID, func(id party.ID) protocol.StartFunc {
|
|
// FROST threshold semantics: threshold parties can sign together,
|
|
// so the "t" passed into Keygen is threshold-1 (max-corruptions).
|
|
return frost.Keygen(group, id, ids, p.Threshold-1)
|
|
}, 2*time.Minute)
|
|
if err != nil {
|
|
return keygenResult{}, fmt.Errorf("frost keygen: %w", err)
|
|
}
|
|
|
|
configs := make(map[party.ID]*frostkeygen.Config, len(ids))
|
|
for _, id := range ids {
|
|
c, ok := results[id].(*frostkeygen.Config)
|
|
if !ok {
|
|
return keygenResult{}, fmt.Errorf("frost keygen: party %s produced %T", id, results[id])
|
|
}
|
|
configs[id] = c
|
|
}
|
|
pkBytes, err := configs[ids[0]].PublicKey.MarshalBinary()
|
|
if err != nil {
|
|
return keygenResult{}, fmt.Errorf("marshal pubkey: %w", err)
|
|
}
|
|
pkHex := hex.EncodeToString(pkBytes)
|
|
|
|
shares := make([]string, 0, len(ids))
|
|
for _, id := range ids {
|
|
sh, err := configs[id].PrivateShare.MarshalBinary()
|
|
if err != nil {
|
|
return keygenResult{}, fmt.Errorf("marshal share: %w", err)
|
|
}
|
|
shares = append(shares, hex.EncodeToString(sh))
|
|
}
|
|
|
|
s.mu.Lock()
|
|
s.sessions[pkHex] = &frostSession{
|
|
threshold: p.Threshold,
|
|
configs: configs,
|
|
partyIDs: ids,
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
return keygenResult{PublicKey: pkHex, Shares: shares}, nil
|
|
}
|
|
|
|
func (s *frostScheme) Sign(p signParams) (signResult, error) {
|
|
msg, err := hex.DecodeString(p.MessageHex)
|
|
if err != nil {
|
|
return signResult{}, fmt.Errorf("messageHex: %w", err)
|
|
}
|
|
s.mu.Lock()
|
|
sess, ok := s.sessions[p.PubKeyHex]
|
|
s.mu.Unlock()
|
|
if !ok {
|
|
return signResult{}, fmt.Errorf("frost sign: unknown pubKeyHex (keygen first)")
|
|
}
|
|
// FROST consumes a message hash (32 bytes).
|
|
hash := sha256.Sum256(msg)
|
|
|
|
signers := sess.partyIDs[:sess.threshold]
|
|
sessionID := []byte(fmt.Sprintf("frost-sign-%d", time.Now().UnixNano()))
|
|
results, err := runMultiparty(signers, sessionID, func(id party.ID) protocol.StartFunc {
|
|
return frost.Sign(sess.configs[id], signers, hash[:])
|
|
}, 2*time.Minute)
|
|
if err != nil {
|
|
return signResult{}, fmt.Errorf("frost sign: %w", err)
|
|
}
|
|
sigVal, ok := results[signers[0]].(frostsign.Signature)
|
|
if !ok {
|
|
return signResult{}, fmt.Errorf("frost sign: party %s produced %T", signers[0], results[signers[0]])
|
|
}
|
|
sigBytes, err := sigVal.MarshalBinary()
|
|
if err != nil {
|
|
return signResult{}, fmt.Errorf("marshal signature: %w", err)
|
|
}
|
|
return signResult{SignatureHex: hex.EncodeToString(sigBytes)}, nil
|
|
}
|
|
|
|
func (s *frostScheme) Verify(p verifyParams) (verifyResult, error) {
|
|
msg, err := hex.DecodeString(p.MessageHex)
|
|
if err != nil {
|
|
return verifyResult{}, fmt.Errorf("messageHex: %w", err)
|
|
}
|
|
sigBytes, err := hex.DecodeString(p.SignatureHex)
|
|
if err != nil {
|
|
return verifyResult{}, fmt.Errorf("signatureHex: %w", err)
|
|
}
|
|
pkBytes, err := hex.DecodeString(p.PubKeyHex)
|
|
if err != nil {
|
|
return verifyResult{}, fmt.Errorf("pubKeyHex: %w", err)
|
|
}
|
|
group := curve.Secp256k1{}
|
|
pub := group.NewPoint()
|
|
if err := pub.UnmarshalBinary(pkBytes); err != nil {
|
|
return verifyResult{OK: false}, nil
|
|
}
|
|
var sig frostsign.Signature
|
|
if err := sig.UnmarshalBinary(group, sigBytes); err != nil {
|
|
return verifyResult{OK: false}, nil
|
|
}
|
|
hash := sha256.Sum256(msg)
|
|
return verifyResult{OK: sig.Verify(pub, hash[:])}, nil
|
|
}
|