mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
232 lines
8.0 KiB
Go
232 lines
8.0 KiB
Go
package input
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
)
|
|
|
|
const (
|
|
// MuSig2PartialSigSize is the size of a MuSig2 partial signature.
|
|
// Because a partial signature is just the s value, this corresponds to
|
|
// the length of a scalar.
|
|
MuSig2PartialSigSize = 32
|
|
)
|
|
|
|
// MuSig2SessionID is a type for a session ID that is just a hash of the MuSig2
|
|
// combined key and the local public nonces.
|
|
type MuSig2SessionID [sha256.Size]byte
|
|
|
|
// MuSig2Signer is an interface that declares all methods that a MuSig2
|
|
// compatible signer needs to implement.
|
|
type MuSig2Signer interface {
|
|
// MuSig2CreateSession creates a new MuSig2 signing session using the
|
|
// local key identified by the key locator. The complete list of all
|
|
// public keys of all signing parties must be provided, including the
|
|
// public key of the local signing key. If nonces of other parties are
|
|
// already known, they can be submitted as well to reduce the number of
|
|
// method calls necessary later on.
|
|
MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey,
|
|
*MuSig2Tweaks, [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo,
|
|
error)
|
|
|
|
// MuSig2RegisterNonces registers one or more public nonces of other
|
|
// signing participants for a session identified by its ID. This method
|
|
// returns true once we have all nonces for all other signing
|
|
// participants.
|
|
MuSig2RegisterNonces(MuSig2SessionID,
|
|
[][musig2.PubNonceSize]byte) (bool, error)
|
|
|
|
// MuSig2Sign creates a partial signature using the local signing key
|
|
// that was specified when the session was created. This can only be
|
|
// called when all public nonces of all participants are known and have
|
|
// been registered with the session. If this node isn't responsible for
|
|
// combining all the partial signatures, then the cleanup parameter
|
|
// should be set, indicating that the session can be removed from memory
|
|
// once the signature was produced.
|
|
MuSig2Sign(MuSig2SessionID, [sha256.Size]byte,
|
|
bool) (*musig2.PartialSignature, error)
|
|
|
|
// MuSig2CombineSig combines the given partial signature(s) with the
|
|
// local one, if it already exists. Once a partial signature of all
|
|
// participants is registered, the final signature will be combined and
|
|
// returned.
|
|
MuSig2CombineSig(MuSig2SessionID,
|
|
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error)
|
|
|
|
// MuSig2Cleanup removes a session from memory to free up resources.
|
|
MuSig2Cleanup(MuSig2SessionID) error
|
|
}
|
|
|
|
// MuSig2SessionInfo is a struct for keeping track of a signing session
|
|
// information in memory.
|
|
type MuSig2SessionInfo struct {
|
|
// SessionID is the wallet's internal unique ID of this session. The ID
|
|
// is the hash over the combined public key and the local public nonces.
|
|
SessionID [32]byte
|
|
|
|
// PublicNonce contains the public nonce of the local signer session.
|
|
PublicNonce [musig2.PubNonceSize]byte
|
|
|
|
// CombinedKey is the combined public key with all tweaks applied to it.
|
|
CombinedKey *btcec.PublicKey
|
|
|
|
// TaprootTweak indicates whether a taproot tweak (BIP-0086 or script
|
|
// path) was used. The TaprootInternalKey will only be set if this is
|
|
// set to true.
|
|
TaprootTweak bool
|
|
|
|
// TaprootInternalKey is the raw combined public key without any tweaks
|
|
// applied to it. This is only set if TaprootTweak is true.
|
|
TaprootInternalKey *btcec.PublicKey
|
|
|
|
// HaveAllNonces indicates whether this session already has all nonces
|
|
// of all other signing participants registered.
|
|
HaveAllNonces bool
|
|
|
|
// HaveAllSigs indicates whether this session already has all partial
|
|
// signatures of all other signing participants registered.
|
|
HaveAllSigs bool
|
|
}
|
|
|
|
// MuSig2Tweaks is a struct that contains all tweaks that can be applied to a
|
|
// MuSig2 combined public key.
|
|
type MuSig2Tweaks struct {
|
|
// GenericTweaks is a list of normal tweaks to apply to the combined
|
|
// public key (and to the private key when signing).
|
|
GenericTweaks []musig2.KeyTweakDesc
|
|
|
|
// TaprootBIP0086Tweak indicates that the final key should use the
|
|
// taproot tweak as defined in BIP 341, with the BIP 86 modification:
|
|
// outputKey = internalKey + h_tapTweak(internalKey)*G.
|
|
// In this case, the aggregated key before the tweak will be used as the
|
|
// internal key. If this is set to true then TaprootTweak will be
|
|
// ignored.
|
|
TaprootBIP0086Tweak bool
|
|
|
|
// TaprootTweak specifies that the final key should use the taproot
|
|
// tweak as defined in BIP 341:
|
|
// outputKey = internalKey + h_tapTweak(internalKey || scriptRoot).
|
|
// In this case, the aggregated key before the tweak will be used as the
|
|
// internal key. Will be ignored if TaprootBIP0086Tweak is set to true.
|
|
TaprootTweak []byte
|
|
}
|
|
|
|
// HasTaprootTweak returns true if either a taproot BIP0086 tweak or a taproot
|
|
// script root tweak is set.
|
|
func (t *MuSig2Tweaks) HasTaprootTweak() bool {
|
|
return t.TaprootBIP0086Tweak || len(t.TaprootTweak) > 0
|
|
}
|
|
|
|
// ToContextOptions converts the tweak descriptor to context options.
|
|
func (t *MuSig2Tweaks) ToContextOptions() []musig2.ContextOption {
|
|
var tweakOpts []musig2.ContextOption
|
|
if len(t.GenericTweaks) > 0 {
|
|
tweakOpts = append(tweakOpts, musig2.WithTweakedContext(
|
|
t.GenericTweaks...,
|
|
))
|
|
}
|
|
|
|
// The BIP0086 tweak and the taproot script tweak are mutually
|
|
// exclusive.
|
|
if t.TaprootBIP0086Tweak {
|
|
tweakOpts = append(tweakOpts, musig2.WithBip86TweakCtx())
|
|
} else if len(t.TaprootTweak) > 0 {
|
|
tweakOpts = append(tweakOpts, musig2.WithTaprootTweakCtx(
|
|
t.TaprootTweak,
|
|
))
|
|
}
|
|
|
|
return tweakOpts
|
|
}
|
|
|
|
// MuSig2CombineKeys combines the given set of public keys into a single
|
|
// combined MuSig2 combined public key, applying the given tweaks.
|
|
func MuSig2CombineKeys(allSignerPubKeys []*btcec.PublicKey,
|
|
tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
|
|
|
|
// Convert the tweak options into the appropriate MuSig2 API functional
|
|
// options.
|
|
var keyAggOpts []musig2.KeyAggOption
|
|
switch {
|
|
case tweaks.TaprootBIP0086Tweak:
|
|
keyAggOpts = append(keyAggOpts, musig2.WithBIP86KeyTweak())
|
|
case len(tweaks.TaprootTweak) > 0:
|
|
keyAggOpts = append(keyAggOpts, musig2.WithTaprootKeyTweak(
|
|
tweaks.TaprootTweak,
|
|
))
|
|
case len(tweaks.GenericTweaks) > 0:
|
|
keyAggOpts = append(keyAggOpts, musig2.WithKeyTweaks(
|
|
tweaks.GenericTweaks...,
|
|
))
|
|
}
|
|
|
|
// Then we'll use this information to compute the aggregated public key.
|
|
combinedKey, _, _, err := musig2.AggregateKeys(
|
|
allSignerPubKeys, true, keyAggOpts...,
|
|
)
|
|
return combinedKey, err
|
|
}
|
|
|
|
// NewMuSig2SessionID returns the unique ID of a MuSig2 session by using the
|
|
// combined key and the local public nonces and hashing that data.
|
|
func NewMuSig2SessionID(combinedKey *btcec.PublicKey,
|
|
publicNonces [musig2.PubNonceSize]byte) MuSig2SessionID {
|
|
|
|
// We hash the data to save some bytes in memory.
|
|
hash := sha256.New()
|
|
_, _ = hash.Write(combinedKey.SerializeCompressed())
|
|
_, _ = hash.Write(publicNonces[:])
|
|
|
|
id := MuSig2SessionID{}
|
|
copy(id[:], hash.Sum(nil))
|
|
return id
|
|
}
|
|
|
|
// SerializePartialSignature encodes the partial signature to a fixed size byte
|
|
// array.
|
|
func SerializePartialSignature(
|
|
sig *musig2.PartialSignature) ([MuSig2PartialSigSize]byte, error) {
|
|
|
|
var (
|
|
buf bytes.Buffer
|
|
result [MuSig2PartialSigSize]byte
|
|
)
|
|
if err := sig.Encode(&buf); err != nil {
|
|
return result, fmt.Errorf("error encoding partial signature: "+
|
|
"%v", err)
|
|
}
|
|
|
|
if buf.Len() != MuSig2PartialSigSize {
|
|
return result, fmt.Errorf("invalid partial signature length, "+
|
|
"got %d wanted %d", buf.Len(), MuSig2PartialSigSize)
|
|
}
|
|
|
|
copy(result[:], buf.Bytes())
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// DeserializePartialSignature decodes a partial signature from a byte slice.
|
|
func DeserializePartialSignature(scalarBytes []byte) (*musig2.PartialSignature,
|
|
error) {
|
|
|
|
if len(scalarBytes) != MuSig2PartialSigSize {
|
|
return nil, fmt.Errorf("invalid partial signature length, got "+
|
|
"%d wanted %d", len(scalarBytes), MuSig2PartialSigSize)
|
|
}
|
|
|
|
sig := &musig2.PartialSignature{}
|
|
if err := sig.Decode(bytes.NewReader(scalarBytes)); err != nil {
|
|
return nil, fmt.Errorf("error decoding partial signature: %v",
|
|
err)
|
|
}
|
|
|
|
return sig, nil
|
|
}
|