Files
zeekay eeaa2a1c07 fix: resolve go vet and formatting issues
- Fix go vet error in lss_bench_test.go: replace b.Fatal with panic in goroutine
- Fix go vet error in lss_test.go: remove t.Skip call from goroutine
- Apply gofmt to all files to fix formatting issues

All go vet errors and formatting issues have been resolved.
2025-08-05 17:59:18 -05:00

213 lines
7.6 KiB
Go

package keygen
import (
"errors"
"fmt"
"github.com/luxfi/threshold/internal/bip32"
"github.com/luxfi/threshold/internal/params"
"github.com/luxfi/threshold/pkg/math/curve"
"github.com/luxfi/threshold/pkg/party"
"github.com/luxfi/threshold/pkg/taproot"
)
// Config contains all the information produced after key generation, from the perspective
// of a single participant.
//
// When unmarshalling, EmptyResult needs to be called to set the group, before
// calling cbor.Unmarshal, or equivalent methods.
type Config struct {
// ID is the identifier for this participant.
ID party.ID
// Threshold is the number of accepted corruptions while still being able to sign.
Threshold int
// PrivateShare is the fraction of the secret key owned by this participant.
PrivateShare curve.Scalar
// PublicKey is the shared public key for this consortium of signers.
//
// This key can be used to verify signatures produced by the consortium.
PublicKey curve.Point
// ChainKey is the additional randomness we've agreed upon.
//
// This is only ever useful if you do BIP-32 key derivation, or something similar.
ChainKey []byte
// VerificationShares is a map between parties and a commitment to their private share.
//
// This will later be used to verify the integrity of the signing protocol.
VerificationShares *party.PointMap
}
// EmptyConfig creates an empty Result with a specific group.
//
// This needs to be called before unmarshalling, instead of just using new(Result).
// This is to allow points and scalars to be correctly unmarshalled.
func EmptyConfig(group curve.Curve) *Config {
return &Config{
PrivateShare: group.NewScalar(),
PublicKey: group.NewPoint(),
VerificationShares: party.EmptyPointMap(group),
}
}
// Curve returns the Elliptic Curve Group associated with this result.
func (r *Config) Curve() curve.Curve {
return r.PublicKey.Curve()
}
// Derive performs an arbitrary derivation of a related key, by adding a scalar.
//
// This can support methods like BIP32, but is more general.
//
// Optionally, a new chain key can be passed as well.
func (r *Config) Derive(adjust curve.Scalar, newChainKey []byte) (*Config, error) {
if len(newChainKey) <= 0 {
newChainKey = r.ChainKey
}
if len(newChainKey) != params.SecBytes {
return nil, fmt.Errorf("expecte %d bytes for chain key, found %d", params.SecBytes, len(newChainKey))
}
adjustG := adjust.ActOnBase()
verificationShares := make(map[party.ID]curve.Point, len(r.VerificationShares.Points))
for k, v := range r.VerificationShares.Points {
verificationShares[k] = v.Add(adjustG)
}
return &Config{
ID: r.ID,
Threshold: r.Threshold,
PrivateShare: r.PrivateShare.Curve().NewScalar().Set(r.PrivateShare).Add(adjust),
PublicKey: r.PublicKey.Add(adjustG),
ChainKey: newChainKey,
VerificationShares: party.NewPointMap(verificationShares),
}, nil
}
// DeriveChild adjusts the shares to represent the derived public key at a certain index.
//
// # This will panic if the group is not curve.Secp256k1
//
// This derivation works according to BIP-32, see:
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
func (r *Config) DeriveChild(i uint32) (*Config, error) {
publicKey, ok := r.PublicKey.(*curve.Secp256k1Point)
if !ok {
return nil, errors.New("DeriveChild called on non secp256k1 curve")
}
scalar, newChainKey, err := bip32.DeriveScalar(publicKey, r.ChainKey, i)
if err != nil {
return nil, err
}
return r.Derive(scalar, newChainKey)
}
// TaprootConfig is like result, but for Taproot / BIP-340 keys.
//
// The main difference is that our public key is an actual taproot public key.
type TaprootConfig struct {
// ID is the identifier for this participant.
ID party.ID
// Threshold is the number of accepted corruptions while still being able to sign.
Threshold int
// PrivateShare is the fraction of the secret key owned by this participant.
PrivateShare *curve.Secp256k1Scalar
// PublicKey is the shared public key for this consortium of signers.
//
// This key can be used to verify signatures produced by the consortium.
PublicKey taproot.PublicKey
// ChainKey is the additional randomness we've agreed upon.
//
// This is only ever useful if you do BIP-32 key derivation, or something similar.
ChainKey []byte
// VerificationShares is a map between parties and a commitment to their private share.
//
// This will later be used to verify the integrity of the signing protocol.
VerificationShares map[party.ID]*curve.Secp256k1Point
}
// Clone creates a deep clone of this struct, and all the values contained inside
func (r *TaprootConfig) Clone() *TaprootConfig {
publicKeyCopy := make([]byte, len(r.PublicKey))
copy(publicKeyCopy, r.PublicKey)
chainKeyCopy := make([]byte, len(r.ChainKey))
copy(chainKeyCopy, r.ChainKey)
verificationSharesCopy := make(map[party.ID]*curve.Secp256k1Point)
for k, v := range r.VerificationShares {
verificationSharesCopy[k] = v
}
return &TaprootConfig{
ID: r.ID,
Threshold: r.Threshold,
PrivateShare: curve.Secp256k1{}.NewScalar().Set(r.PrivateShare).(*curve.Secp256k1Scalar),
PublicKey: publicKeyCopy,
ChainKey: chainKeyCopy,
VerificationShares: verificationSharesCopy,
}
}
// Derive performs an arbitrary derivation of a related key, by adding a scalar.
//
// This can support methods like BIP32, but is more general.
//
// Optionally, a new chain key can be passed as well.
func (r *TaprootConfig) Derive(adjust *curve.Secp256k1Scalar, newChainKey []byte) (*TaprootConfig, error) {
if len(newChainKey) <= 0 {
newChainKey = r.ChainKey
}
if len(newChainKey) != params.SecBytes {
return nil, fmt.Errorf("expecte %d bytes for chain key, found %d", params.SecBytes, len(newChainKey))
}
adjustG := adjust.ActOnBase()
verificationShares := make(map[party.ID]*curve.Secp256k1Point, len(r.VerificationShares))
for k, v := range r.VerificationShares {
verificationShares[k] = v.Add(adjustG).(*curve.Secp256k1Point)
}
privateShare := curve.Secp256k1{}.NewScalar().Set(r.PrivateShare).Add(adjust)
publicKey, err := curve.Secp256k1{}.LiftX(r.PublicKey)
if err != nil {
return nil, err
}
publicKey = publicKey.Add(adjustG).(*curve.Secp256k1Point)
// If our public key is odd, we need to negate our secret key, and everything
// that entails. This means negating each secret share, and the corresponding
// verification shares.
if !publicKey.HasEvenY() {
privateShare.Negate()
for k, v := range verificationShares {
verificationShares[k] = v.Negate().(*curve.Secp256k1Point)
}
}
return &TaprootConfig{
ID: r.ID,
Threshold: r.Threshold,
PrivateShare: privateShare.(*curve.Secp256k1Scalar),
PublicKey: publicKey.XBytes(),
ChainKey: newChainKey,
VerificationShares: verificationShares,
}, nil
}
// DeriveChild adjusts the shares to represent the derived public key at a certain index.
//
// This derivation works according to BIP-32, see:
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
//
// Note that to do this derivation, we interpret the Taproot key as an "old"
// ECDSA key, with the y coordinate byte set to 0x02. We also only look at the x
// coordinate of the derived public key, making sure that the corresponding secret
// key matches the version of this point with an even y coordinate.
func (r *TaprootConfig) DeriveChild(i uint32) (*TaprootConfig, error) {
publicKey, err := curve.Secp256k1{}.LiftX(r.PublicKey)
if err != nil {
return nil, err
}
scalar, newChainKey, err := bip32.DeriveScalar(publicKey, r.ChainKey, i)
if err != nil {
return nil, err
}
return r.Derive(scalar, newChainKey)
}