mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
0e5ce71b33
We put the calls that don't use musig2 package specific types as parameters or return values behind an interface so we can easily call those directly in the RPC without needing to know the underlying implementation version. Some calls can't be used in the interface because they use the specific package version's types. These calls are implemented in helper functions in the input package instead that do the necessary type switches.
370 lines
13 KiB
Go
370 lines
13 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
|
|
}
|
|
|
|
// MuSig2Context is an interface that is an abstraction over the MuSig2 signing
|
|
// context. This interface does not contain all of the methods the underlying
|
|
// implementations have because those use package specific types which cannot
|
|
// easily be made compatible. Those calls (such as NewSession) are implemented
|
|
// in this package instead and do the necessary type switch (see
|
|
// MuSig2CreateContext).
|
|
type MuSig2Context interface {
|
|
// SigningKeys returns the set of keys used for signing.
|
|
SigningKeys() []*btcec.PublicKey
|
|
|
|
// CombinedKey returns the combined public key that will be used to
|
|
// generate multi-signatures against.
|
|
CombinedKey() (*btcec.PublicKey, error)
|
|
|
|
// TaprootInternalKey returns the internal taproot key, which is the
|
|
// aggregated key _before_ the tweak is applied. If a taproot tweak was
|
|
// specified, then CombinedKey() will return the fully tweaked output
|
|
// key, with this method returning the internal key. If a taproot tweak
|
|
// wasn't specified, then this method will return an error.
|
|
TaprootInternalKey() (*btcec.PublicKey, error)
|
|
}
|
|
|
|
// MuSig2Session is an interface that is an abstraction over the MuSig2 signing
|
|
// session. This interface does not contain all of the methods the underlying
|
|
// implementations have because those use package specific types which cannot
|
|
// easily be made compatible. Those calls (such as CombineSig or Sign) are
|
|
// implemented in this package instead and do the necessary type switch (see
|
|
// MuSig2CombineSig or MuSig2Sign).
|
|
type MuSig2Session interface {
|
|
// FinalSig returns the final combined multi-signature, if present.
|
|
FinalSig() *schnorr.Signature
|
|
|
|
// PublicNonce returns the public nonce for a signer. This should be
|
|
// sent to other parties before signing begins, so they can compute the
|
|
// aggregated public nonce.
|
|
PublicNonce() [musig2.PubNonceSize]byte
|
|
|
|
// NumRegisteredNonces returns the total number of nonces that have been
|
|
// registered so far.
|
|
NumRegisteredNonces() int
|
|
|
|
// RegisterPubNonce should be called for each public nonce from the set
|
|
// of signers. This method returns true once all the public nonces have
|
|
// been accounted for.
|
|
RegisterPubNonce(nonce [musig2.PubNonceSize]byte) (bool, 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
|
|
}
|
|
|
|
// MuSig2ParsePubKeys parses a list of raw public keys as the signing keys of a
|
|
// MuSig2 signing session.
|
|
func MuSig2ParsePubKeys(rawPubKeys [][]byte) ([]*btcec.PublicKey, error) {
|
|
allSignerPubKeys := make([]*btcec.PublicKey, len(rawPubKeys))
|
|
if len(rawPubKeys) < 2 {
|
|
return nil, fmt.Errorf("need at least two signing public keys")
|
|
}
|
|
|
|
for idx, pubKeyBytes := range rawPubKeys {
|
|
pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing signer public "+
|
|
"key %d: %v", idx, err)
|
|
}
|
|
allSignerPubKeys[idx] = pubKey
|
|
}
|
|
|
|
return allSignerPubKeys, nil
|
|
}
|
|
|
|
// 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, sortKeys bool,
|
|
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, sortKeys, keyAggOpts...,
|
|
)
|
|
return combinedKey, err
|
|
}
|
|
|
|
// MuSig2CreateContext creates a new MuSig2 signing context.
|
|
func MuSig2CreateContext(privKey *btcec.PrivateKey,
|
|
allSignerPubKeys []*btcec.PublicKey,
|
|
tweaks *MuSig2Tweaks) (*musig2.Context, *musig2.Session, error) {
|
|
|
|
// The context keeps track of all signing keys and our local key.
|
|
allOpts := append(
|
|
[]musig2.ContextOption{
|
|
musig2.WithKnownSigners(allSignerPubKeys),
|
|
},
|
|
tweaks.ToContextOptions()...,
|
|
)
|
|
muSigContext, err := musig2.NewContext(privKey, true, allOpts...)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
|
|
"context: %v", err)
|
|
}
|
|
|
|
muSigSession, err := muSigContext.NewSession()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
|
|
"session: %v", err)
|
|
}
|
|
|
|
return muSigContext, muSigSession, nil
|
|
}
|
|
|
|
// MuSig2Sign calls the Sign() method on the given versioned signing session and
|
|
// returns the result in the most recent version of the MuSig2 API.
|
|
func MuSig2Sign(session MuSig2Session, msg [32]byte,
|
|
withSortedKeys bool) (*musig2.PartialSignature, error) {
|
|
|
|
switch s := session.(type) {
|
|
case *musig2.Session:
|
|
var opts []musig2.SignOption
|
|
if withSortedKeys {
|
|
opts = append(opts, musig2.WithSortedKeys())
|
|
}
|
|
partialSig, err := s.Sign(msg, opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error signing with local key: "+
|
|
"%v", err)
|
|
}
|
|
|
|
return partialSig, nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("invalid session type <%T>", s)
|
|
}
|
|
}
|
|
|
|
// MuSig2CombineSig calls the CombineSig() method on the given versioned signing
|
|
// session and returns the result in the most recent version of the MuSig2 API.
|
|
func MuSig2CombineSig(session MuSig2Session,
|
|
otherPartialSig *musig2.PartialSignature) (bool, error) {
|
|
|
|
switch s := session.(type) {
|
|
case *musig2.Session:
|
|
haveAllSigs, err := s.CombineSig(otherPartialSig)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error combining partial "+
|
|
"signature: %v", err)
|
|
}
|
|
|
|
return haveAllSigs, nil
|
|
|
|
default:
|
|
return false, fmt.Errorf("invalid session type <%T>", s)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|