mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-13 11:09:23 +01:00
input+wallet: extract musig2 session management into new module
In this commit, we extract the musig2 session management into a new module. This allows us to re-use the session logic elsewhere in unit tests so we don't need to instantiate the entire wallet.
This commit is contained in:
parent
4733da5ccb
commit
9a65806c09
13 changed files with 402 additions and 371 deletions
|
@ -2112,7 +2112,7 @@ func createTestArbiter(t *testing.T, contractBreaches chan *ContractBreachEvent,
|
||||||
})
|
})
|
||||||
|
|
||||||
aliceKeyPriv, _ := btcec.PrivKeyFromBytes(channels.AlicesPrivKey)
|
aliceKeyPriv, _ := btcec.PrivKeyFromBytes(channels.AlicesPrivKey)
|
||||||
signer := &mock.SingleSigner{Privkey: aliceKeyPriv}
|
signer := input.NewMockSigner([]*btcec.PrivateKey{aliceKeyPriv}, nil)
|
||||||
|
|
||||||
// Assemble our test arbiter.
|
// Assemble our test arbiter.
|
||||||
notifier := mock.MakeMockSpendNotifier()
|
notifier := mock.MakeMockSpendNotifier()
|
||||||
|
@ -2339,8 +2339,10 @@ func createInitChannels(t *testing.T, revocationWindow int) (
|
||||||
Packager: channeldb.NewChannelPackager(shortChanID),
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceSigner := &mock.SingleSigner{Privkey: aliceKeyPriv}
|
aliceSigner := input.NewMockSigner(
|
||||||
bobSigner := &mock.SingleSigner{Privkey: bobKeyPriv}
|
[]*btcec.PrivateKey{aliceKeyPriv}, nil,
|
||||||
|
)
|
||||||
|
bobSigner := input.NewMockSigner([]*btcec.PrivateKey{bobKeyPriv}, nil)
|
||||||
|
|
||||||
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
||||||
channelAlice, err := lnwallet.NewLightningChannel(
|
channelAlice, err := lnwallet.NewLightningChannel(
|
||||||
|
|
|
@ -32,7 +32,6 @@ import (
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
"github.com/lightningnetwork/lnd/lntest/channels"
|
"github.com/lightningnetwork/lnd/lntest/channels"
|
||||||
"github.com/lightningnetwork/lnd/lntest/mock"
|
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
@ -337,8 +336,12 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte,
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceSigner := &mock.SingleSigner{Privkey: aliceKeyPriv}
|
aliceSigner := input.NewMockSigner(
|
||||||
bobSigner := &mock.SingleSigner{Privkey: bobKeyPriv}
|
[]*btcec.PrivateKey{aliceKeyPriv}, nil,
|
||||||
|
)
|
||||||
|
bobSigner := input.NewMockSigner(
|
||||||
|
[]*btcec.PrivateKey{bobKeyPriv}, nil,
|
||||||
|
)
|
||||||
|
|
||||||
alicePool := lnwallet.NewSigPool(runtime.NumCPU(), aliceSigner)
|
alicePool := lnwallet.NewSigPool(runtime.NumCPU(), aliceSigner)
|
||||||
channelAlice, err := lnwallet.NewLightningChannel(
|
channelAlice, err := lnwallet.NewLightningChannel(
|
||||||
|
|
|
@ -48,9 +48,13 @@ type MuSig2Signer interface {
|
||||||
// public key of the local signing key. If nonces of other parties are
|
// 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
|
// already known, they can be submitted as well to reduce the number of
|
||||||
// method calls necessary later on.
|
// method calls necessary later on.
|
||||||
|
//
|
||||||
|
// The set of sessionOpts are _optional_ and allow a caller to modify
|
||||||
|
// the generated sessions. As an example the local nonce might already
|
||||||
|
// be generated ahead of time.
|
||||||
MuSig2CreateSession(MuSig2Version, keychain.KeyLocator,
|
MuSig2CreateSession(MuSig2Version, keychain.KeyLocator,
|
||||||
[]*btcec.PublicKey, *MuSig2Tweaks,
|
[]*btcec.PublicKey, *MuSig2Tweaks, [][musig2.PubNonceSize]byte,
|
||||||
[][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, error)
|
...musig2.SessionOption) (*MuSig2SessionInfo, error)
|
||||||
|
|
||||||
// MuSig2RegisterNonces registers one or more public nonces of other
|
// MuSig2RegisterNonces registers one or more public nonces of other
|
||||||
// signing participants for a session identified by its ID. This method
|
// signing participants for a session identified by its ID. This method
|
||||||
|
@ -374,15 +378,20 @@ func combineKeysV040(allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
|
||||||
|
|
||||||
// MuSig2CreateContext creates a new MuSig2 signing context.
|
// MuSig2CreateContext creates a new MuSig2 signing context.
|
||||||
func MuSig2CreateContext(bipVersion MuSig2Version, privKey *btcec.PrivateKey,
|
func MuSig2CreateContext(bipVersion MuSig2Version, privKey *btcec.PrivateKey,
|
||||||
allSignerPubKeys []*btcec.PublicKey,
|
allSignerPubKeys []*btcec.PublicKey, tweaks *MuSig2Tweaks,
|
||||||
tweaks *MuSig2Tweaks) (MuSig2Context, MuSig2Session, error) {
|
sessionOpts ...musig2.SessionOption,
|
||||||
|
) (MuSig2Context, MuSig2Session, error) {
|
||||||
|
|
||||||
switch bipVersion {
|
switch bipVersion {
|
||||||
case MuSig2Version040:
|
case MuSig2Version040:
|
||||||
return createContextV040(privKey, allSignerPubKeys, tweaks)
|
return createContextV040(
|
||||||
|
privKey, allSignerPubKeys, tweaks, sessionOpts...,
|
||||||
|
)
|
||||||
|
|
||||||
case MuSig2Version100RC2:
|
case MuSig2Version100RC2:
|
||||||
return createContextV100RC2(privKey, allSignerPubKeys, tweaks)
|
return createContextV100RC2(
|
||||||
|
privKey, allSignerPubKeys, tweaks, sessionOpts...,
|
||||||
|
)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, nil, fmt.Errorf("unknown MuSig2 version: <%d>",
|
return nil, nil, fmt.Errorf("unknown MuSig2 version: <%d>",
|
||||||
|
@ -393,8 +402,9 @@ func MuSig2CreateContext(bipVersion MuSig2Version, privKey *btcec.PrivateKey,
|
||||||
// createContextV100RC2 implements the MuSig2CreateContext logic for the MuSig2
|
// createContextV100RC2 implements the MuSig2CreateContext logic for the MuSig2
|
||||||
// BIP draft version 1.0.0rc2.
|
// BIP draft version 1.0.0rc2.
|
||||||
func createContextV100RC2(privKey *btcec.PrivateKey,
|
func createContextV100RC2(privKey *btcec.PrivateKey,
|
||||||
allSignerPubKeys []*btcec.PublicKey,
|
allSignerPubKeys []*btcec.PublicKey, tweaks *MuSig2Tweaks,
|
||||||
tweaks *MuSig2Tweaks) (*musig2.Context, *musig2.Session, error) {
|
sessionOpts ...musig2.SessionOption,
|
||||||
|
) (*musig2.Context, *musig2.Session, error) {
|
||||||
|
|
||||||
// The context keeps track of all signing keys and our local key.
|
// The context keeps track of all signing keys and our local key.
|
||||||
allOpts := append(
|
allOpts := append(
|
||||||
|
@ -409,7 +419,7 @@ func createContextV100RC2(privKey *btcec.PrivateKey,
|
||||||
"context: %v", err)
|
"context: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
muSigSession, err := muSigContext.NewSession()
|
muSigSession, err := muSigContext.NewSession(sessionOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
|
return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
|
||||||
"session: %v", err)
|
"session: %v", err)
|
||||||
|
@ -421,9 +431,9 @@ func createContextV100RC2(privKey *btcec.PrivateKey,
|
||||||
// createContextV040 implements the MuSig2CreateContext logic for the MuSig2 BIP
|
// createContextV040 implements the MuSig2CreateContext logic for the MuSig2 BIP
|
||||||
// draft version 0.4.0.
|
// draft version 0.4.0.
|
||||||
func createContextV040(privKey *btcec.PrivateKey,
|
func createContextV040(privKey *btcec.PrivateKey,
|
||||||
allSignerPubKeys []*btcec.PublicKey,
|
allSignerPubKeys []*btcec.PublicKey, tweaks *MuSig2Tweaks,
|
||||||
tweaks *MuSig2Tweaks) (*musig2v040.Context, *musig2v040.Session,
|
sessionOpts ...musig2.SessionOption,
|
||||||
error) {
|
) (*musig2v040.Context, *musig2v040.Session, error) {
|
||||||
|
|
||||||
// The context keeps track of all signing keys and our local key.
|
// The context keeps track of all signing keys and our local key.
|
||||||
allOpts := append(
|
allOpts := append(
|
||||||
|
|
298
input/musig2_session_manager.go
Normal file
298
input/musig2_session_manager.go
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
package input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MuSig2State is a struct that holds on to the internal signing session state
|
||||||
|
// of a MuSig2 session.
|
||||||
|
type MuSig2State struct {
|
||||||
|
// MuSig2SessionInfo is the associated meta information of the signing
|
||||||
|
// session.
|
||||||
|
MuSig2SessionInfo
|
||||||
|
|
||||||
|
// context is the signing context responsible for keeping track of the
|
||||||
|
// public keys involved in the signing process.
|
||||||
|
context MuSig2Context
|
||||||
|
|
||||||
|
// session is the signing session responsible for keeping track of the
|
||||||
|
// nonces and partial signatures involved in the signing process.
|
||||||
|
session MuSig2Session
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivKeyFetcher is used to fetch a private key that matches a given key desc.
|
||||||
|
type PrivKeyFetcher func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error)
|
||||||
|
|
||||||
|
// MusigSessionMusigSessionManager houses the state needed to manage concurrent
|
||||||
|
// musig sessions. Each session is identified by a unique session ID which is
|
||||||
|
// used by callers to interact with a given session.
|
||||||
|
type MusigSessionManager struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
keyFetcher PrivKeyFetcher
|
||||||
|
|
||||||
|
musig2Sessions map[MuSig2SessionID]*MuSig2State
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMusigSessionManager creates a new musig manager given an abstract key
|
||||||
|
// fetcher.
|
||||||
|
func NewMusigSessionManager(keyFetcher PrivKeyFetcher) *MusigSessionManager {
|
||||||
|
return &MusigSessionManager{
|
||||||
|
keyFetcher: keyFetcher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// The set of sessionOpts are _optional_ and allow a caller to modify the
|
||||||
|
// generated sessions. As an example the local nonce might already be generated
|
||||||
|
// ahead of time.
|
||||||
|
func (m *MusigSessionManager) MuSig2CreateSession(bipVersion MuSig2Version,
|
||||||
|
keyLoc keychain.KeyLocator, allSignerPubKeys []*btcec.PublicKey,
|
||||||
|
tweaks *MuSig2Tweaks, otherSignerNonces [][musig2.PubNonceSize]byte,
|
||||||
|
sessionOpts ...musig2.SessionOption) (*MuSig2SessionInfo, error) {
|
||||||
|
|
||||||
|
// We need to derive the private key for signing. In the remote signing
|
||||||
|
// setup, this whole RPC call will be forwarded to the signing
|
||||||
|
// instance, which requires it to be stateful.
|
||||||
|
privKey, err := m.keyFetcher(&keychain.KeyDescriptor{
|
||||||
|
KeyLocator: keyLoc,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error deriving private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a signing context and session with the given private key and
|
||||||
|
// list of all known signer public keys.
|
||||||
|
musigContext, musigSession, err := MuSig2CreateContext(
|
||||||
|
bipVersion, privKey, allSignerPubKeys, tweaks,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating signing context: %w",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all nonces we might've learned so far.
|
||||||
|
haveAllNonces := false
|
||||||
|
for _, otherSignerNonce := range otherSignerNonces {
|
||||||
|
haveAllNonces, err = musigSession.RegisterPubNonce(
|
||||||
|
otherSignerNonce,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error registering other "+
|
||||||
|
"signer public nonce: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the new session.
|
||||||
|
combinedKey, err := musigContext.CombinedKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting combined key: %v", err)
|
||||||
|
}
|
||||||
|
session := &MuSig2State{
|
||||||
|
MuSig2SessionInfo: MuSig2SessionInfo{
|
||||||
|
SessionID: NewMuSig2SessionID(
|
||||||
|
combinedKey, musigSession.PublicNonce(),
|
||||||
|
),
|
||||||
|
Version: bipVersion,
|
||||||
|
PublicNonce: musigSession.PublicNonce(),
|
||||||
|
CombinedKey: combinedKey,
|
||||||
|
TaprootTweak: tweaks.HasTaprootTweak(),
|
||||||
|
HaveAllNonces: haveAllNonces,
|
||||||
|
},
|
||||||
|
context: musigContext,
|
||||||
|
session: musigSession,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The internal key is only calculated if we are using a taproot tweak
|
||||||
|
// and need to know it for a potential script spend.
|
||||||
|
if tweaks.HasTaprootTweak() {
|
||||||
|
internalKey, err := musigContext.TaprootInternalKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting internal key: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
session.TaprootInternalKey = internalKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we generate new nonces for every session, there is no way that
|
||||||
|
// a session with the same ID already exists. So even if we call the API
|
||||||
|
// twice with the same signers, we still get a new ID.
|
||||||
|
m.Lock()
|
||||||
|
m.musig2Sessions[session.SessionID] = session
|
||||||
|
m.Unlock()
|
||||||
|
|
||||||
|
return &session.MuSig2SessionInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (m *MusigSessionManager) MuSig2Sign(sessionID MuSig2SessionID,
|
||||||
|
msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
|
||||||
|
|
||||||
|
// We hold the lock during the whole operation, we don't want any
|
||||||
|
// interference with calls that might come through in parallel for the
|
||||||
|
// same session.
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
session, ok := m.musig2Sessions[sessionID]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("session with ID %x not found",
|
||||||
|
sessionID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can only sign once we have all other signer's nonces.
|
||||||
|
if !session.HaveAllNonces {
|
||||||
|
return nil, fmt.Errorf("only have %d of %d required nonces",
|
||||||
|
session.session.NumRegisteredNonces(),
|
||||||
|
len(session.context.SigningKeys()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our own partial signature with the local signing key.
|
||||||
|
partialSig, err := MuSig2Sign(session.session, msg, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error signing with local key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up our local state if requested.
|
||||||
|
if cleanUp {
|
||||||
|
delete(m.musig2Sessions, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return partialSig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (m *MusigSessionManager) MuSig2CombineSig(sessionID MuSig2SessionID,
|
||||||
|
partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
// We hold the lock during the whole operation, we don't want any
|
||||||
|
// interference with calls that might come through in parallel for the
|
||||||
|
// same session.
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
session, ok := m.musig2Sessions[sessionID]
|
||||||
|
if !ok {
|
||||||
|
return nil, false, fmt.Errorf("session with ID %x not found",
|
||||||
|
sessionID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we don't exceed the number of expected partial signatures
|
||||||
|
// as that would indicate something is wrong with the signing setup.
|
||||||
|
if session.HaveAllSigs {
|
||||||
|
return nil, true, fmt.Errorf("already have all partial" +
|
||||||
|
"signatures")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all sigs we got so far.
|
||||||
|
var (
|
||||||
|
finalSig *schnorr.Signature
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, otherPartialSig := range partialSigs {
|
||||||
|
session.HaveAllSigs, err = MuSig2CombineSig(
|
||||||
|
session.session, otherPartialSig,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("error combining "+
|
||||||
|
"partial signature: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have all partial signatures, we should be able to get the
|
||||||
|
// complete signature now. We also remove this session from memory since
|
||||||
|
// there is nothing more left to do.
|
||||||
|
if session.HaveAllSigs {
|
||||||
|
finalSig = session.session.FinalSig()
|
||||||
|
delete(m.musig2Sessions, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalSig, session.HaveAllSigs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuSig2Cleanup removes a session from memory to free up resources.
|
||||||
|
func (m *MusigSessionManager) MuSig2Cleanup(sessionID MuSig2SessionID) error {
|
||||||
|
// We hold the lock during the whole operation, we don't want any
|
||||||
|
// interference with calls that might come through in parallel for the
|
||||||
|
// same session.
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
_, ok := m.musig2Sessions[sessionID]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("session with ID %x not found", sessionID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(m.musig2Sessions, sessionID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (m *MusigSessionManager) MuSig2RegisterNonces(sessionID MuSig2SessionID,
|
||||||
|
otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) {
|
||||||
|
|
||||||
|
// We hold the lock during the whole operation, we don't want any
|
||||||
|
// interference with calls that might come through in parallel for the
|
||||||
|
// same session.
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
session, ok := m.musig2Sessions[sessionID]
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("session with ID %x not found",
|
||||||
|
sessionID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we don't exceed the number of expected nonces as that would
|
||||||
|
// indicate something is wrong with the signing setup.
|
||||||
|
if session.HaveAllNonces {
|
||||||
|
return true, fmt.Errorf("already have all nonces")
|
||||||
|
}
|
||||||
|
|
||||||
|
numSigners := len(session.context.SigningKeys())
|
||||||
|
remainingNonces := numSigners - session.session.NumRegisteredNonces()
|
||||||
|
if len(otherSignerNonces) > remainingNonces {
|
||||||
|
return false, fmt.Errorf("only %d other nonces remaining but "+
|
||||||
|
"trying to register %d more", remainingNonces,
|
||||||
|
len(otherSignerNonces))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all nonces we've learned so far.
|
||||||
|
var err error
|
||||||
|
for _, otherSignerNonce := range otherSignerNonces {
|
||||||
|
session.HaveAllNonces, err = session.session.RegisterPubNonce(
|
||||||
|
otherSignerNonce,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("error registering other "+
|
||||||
|
"signer public nonce: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.HaveAllNonces, nil
|
||||||
|
}
|
|
@ -2,14 +2,12 @@ package input
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
@ -51,6 +49,26 @@ var (
|
||||||
type MockSigner struct {
|
type MockSigner struct {
|
||||||
Privkeys []*btcec.PrivateKey
|
Privkeys []*btcec.PrivateKey
|
||||||
NetParams *chaincfg.Params
|
NetParams *chaincfg.Params
|
||||||
|
|
||||||
|
*MusigSessionManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockSigner returns a new instance of the MockSigner given a set of
|
||||||
|
// backing private keys.
|
||||||
|
func NewMockSigner(privKeys []*btcec.PrivateKey,
|
||||||
|
netParams *chaincfg.Params) *MockSigner {
|
||||||
|
|
||||||
|
signer := &MockSigner{
|
||||||
|
Privkeys: privKeys,
|
||||||
|
NetParams: netParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFetcher := func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
|
||||||
|
return signer.Privkeys[0], nil
|
||||||
|
}
|
||||||
|
signer.MusigSessionManager = NewMusigSessionManager(keyFetcher)
|
||||||
|
|
||||||
|
return signer
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignOutputRaw generates a signature for the passed transaction according to
|
// SignOutputRaw generates a signature for the passed transaction according to
|
||||||
|
@ -74,18 +92,14 @@ func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx,
|
||||||
|
|
||||||
// In case of a taproot output any signature is always a Schnorr
|
// In case of a taproot output any signature is always a Schnorr
|
||||||
// signature, based on the new tapscript sighash algorithm.
|
// signature, based on the new tapscript sighash algorithm.
|
||||||
//
|
|
||||||
// TODO(roasbeef): should conslidate with btcwallet/signer.go
|
|
||||||
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
|
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
|
||||||
sigHashes := txscript.NewTxSigHashes(
|
sigHashes := txscript.NewTxSigHashes(
|
||||||
tx, signDesc.PrevOutputFetcher,
|
tx, signDesc.PrevOutputFetcher,
|
||||||
)
|
)
|
||||||
|
|
||||||
witnessScript := signDesc.WitnessScript
|
|
||||||
|
|
||||||
// Are we spending a script path or the key path? The API is
|
// Are we spending a script path or the key path? The API is
|
||||||
// slightly different, so we need to account for that to get the
|
// slightly different, so we need to account for that to get
|
||||||
// raw signature.
|
// the raw signature.
|
||||||
var (
|
var (
|
||||||
rawSig []byte
|
rawSig []byte
|
||||||
err error
|
err error
|
||||||
|
@ -109,7 +123,7 @@ func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx,
|
||||||
case TaprootScriptSpendSignMethod:
|
case TaprootScriptSpendSignMethod:
|
||||||
leaf := txscript.TapLeaf{
|
leaf := txscript.TapLeaf{
|
||||||
LeafVersion: txscript.BaseLeafVersion,
|
LeafVersion: txscript.BaseLeafVersion,
|
||||||
Script: witnessScript,
|
Script: signDesc.WitnessScript,
|
||||||
}
|
}
|
||||||
rawSig, err = txscript.RawTxInTapscriptSignature(
|
rawSig, err = txscript.RawTxInTapscriptSignature(
|
||||||
tx, sigHashes, signDesc.InputIndex,
|
tx, sigHashes, signDesc.InputIndex,
|
||||||
|
@ -121,6 +135,10 @@ func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The signature returned above might have a sighash flag
|
||||||
|
// attached if a non-default type was used. We'll slice this
|
||||||
|
// off if it exists to ensure we can properly parse the raw
|
||||||
|
// signature.
|
||||||
sig, err := schnorr.ParseSignature(
|
sig, err := schnorr.ParseSignature(
|
||||||
rawSig[:schnorr.SignatureSize],
|
rawSig[:schnorr.SignatureSize],
|
||||||
)
|
)
|
||||||
|
@ -195,55 +213,6 @@ func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (m *MockSigner) MuSig2CreateSession(MuSig2Version, keychain.KeyLocator,
|
|
||||||
[]*btcec.PublicKey, *MuSig2Tweaks,
|
|
||||||
[][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, error) {
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (m *MockSigner) MuSig2RegisterNonces(MuSig2SessionID,
|
|
||||||
[][musig2.PubNonceSize]byte) (bool, error) {
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (m *MockSigner) MuSig2Sign(MuSig2SessionID,
|
|
||||||
[sha256.Size]byte, bool) (*musig2.PartialSignature, error) {
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (m *MockSigner) MuSig2CombineSig(MuSig2SessionID,
|
|
||||||
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error) {
|
|
||||||
|
|
||||||
return nil, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MuSig2Cleanup removes a session from memory to free up resources.
|
|
||||||
func (m *MockSigner) MuSig2Cleanup(MuSig2SessionID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findKey searches through all stored private keys and returns one
|
// findKey searches through all stored private keys and returns one
|
||||||
// corresponding to the hashed pubkey if it can be found. The public key may
|
// corresponding to the hashed pubkey if it can be found. The public key may
|
||||||
// either correspond directly to the private key or to the private key with a
|
// either correspond directly to the private key or to the private key with a
|
||||||
|
@ -252,13 +221,15 @@ func (m *MockSigner) findKey(needleHash160 []byte, singleTweak []byte,
|
||||||
doubleTweak *btcec.PrivateKey) *btcec.PrivateKey {
|
doubleTweak *btcec.PrivateKey) *btcec.PrivateKey {
|
||||||
|
|
||||||
for _, privkey := range m.Privkeys {
|
for _, privkey := range m.Privkeys {
|
||||||
// First check whether public key is directly derived from private key.
|
// First check whether public key is directly derived from
|
||||||
|
// private key.
|
||||||
hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed())
|
hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed())
|
||||||
if bytes.Equal(hash160, needleHash160) {
|
if bytes.Equal(hash160, needleHash160) {
|
||||||
return privkey
|
return privkey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise check if public key is derived from tweaked private key.
|
// Otherwise check if public key is derived from tweaked
|
||||||
|
// private key.
|
||||||
switch {
|
switch {
|
||||||
case singleTweak != nil:
|
case singleTweak != nil:
|
||||||
privkey = TweakPrivKey(privkey, singleTweak)
|
privkey = TweakPrivKey(privkey, singleTweak)
|
||||||
|
|
|
@ -57,7 +57,8 @@ func (d *DummySigner) ComputeInputScript(tx *wire.MsgTx,
|
||||||
// submitted as well to reduce the number of method calls necessary later on.
|
// submitted as well to reduce the number of method calls necessary later on.
|
||||||
func (d *DummySigner) MuSig2CreateSession(input.MuSig2Version,
|
func (d *DummySigner) MuSig2CreateSession(input.MuSig2Version,
|
||||||
keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
|
keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
|
||||||
[][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) {
|
[][musig2.PubNonceSize]byte,
|
||||||
|
...musig2.SessionOption) (*input.MuSig2SessionInfo, error) {
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -196,7 +197,8 @@ func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator,
|
||||||
// submitted as well to reduce the number of method calls necessary later on.
|
// submitted as well to reduce the number of method calls necessary later on.
|
||||||
func (s *SingleSigner) MuSig2CreateSession(input.MuSig2Version,
|
func (s *SingleSigner) MuSig2CreateSession(input.MuSig2Version,
|
||||||
keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
|
keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
|
||||||
[][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) {
|
[][musig2.PubNonceSize]byte,
|
||||||
|
...musig2.SessionOption) (*input.MuSig2SessionInfo, error) {
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,8 +104,7 @@ type BtcWallet struct {
|
||||||
|
|
||||||
blockCache *blockcache.BlockCache
|
blockCache *blockcache.BlockCache
|
||||||
|
|
||||||
musig2Sessions map[input.MuSig2SessionID]*muSig2State
|
*input.MusigSessionManager
|
||||||
musig2SessionsMtx sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compile time check to ensure that BtcWallet implements the
|
// A compile time check to ensure that BtcWallet implements the
|
||||||
|
@ -167,7 +166,7 @@ func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BtcWallet{
|
finalWallet := &BtcWallet{
|
||||||
cfg: &cfg,
|
cfg: &cfg,
|
||||||
wallet: wallet,
|
wallet: wallet,
|
||||||
db: wallet.Database(),
|
db: wallet.Database(),
|
||||||
|
@ -175,8 +174,13 @@ func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) {
|
||||||
netParams: cfg.NetParams,
|
netParams: cfg.NetParams,
|
||||||
chainKeyScope: chainKeyScope,
|
chainKeyScope: chainKeyScope,
|
||||||
blockCache: blockCache,
|
blockCache: blockCache,
|
||||||
musig2Sessions: make(map[input.MuSig2SessionID]*muSig2State),
|
}
|
||||||
}, nil
|
|
||||||
|
finalWallet.MusigSessionManager = input.NewMusigSessionManager(
|
||||||
|
finalWallet.fetchPrivKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
return finalWallet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loaderCfg holds optional wallet loader configuration.
|
// loaderCfg holds optional wallet loader configuration.
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package btcwallet
|
package btcwallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
@ -469,267 +467,6 @@ func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// muSig2State is a struct that holds on to the internal signing session state
|
|
||||||
// of a MuSig2 session.
|
|
||||||
type muSig2State struct {
|
|
||||||
// MuSig2SessionInfo is the associated meta information of the signing
|
|
||||||
// session.
|
|
||||||
input.MuSig2SessionInfo
|
|
||||||
|
|
||||||
// context is the signing context responsible for keeping track of the
|
|
||||||
// public keys involved in the signing process.
|
|
||||||
context input.MuSig2Context
|
|
||||||
|
|
||||||
// session is the signing session responsible for keeping track of the
|
|
||||||
// nonces and partial signatures involved in the signing process.
|
|
||||||
session input.MuSig2Session
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (b *BtcWallet) MuSig2CreateSession(bipVersion input.MuSig2Version,
|
|
||||||
keyLoc keychain.KeyLocator, allSignerPubKeys []*btcec.PublicKey,
|
|
||||||
tweaks *input.MuSig2Tweaks,
|
|
||||||
otherSignerNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo,
|
|
||||||
error) {
|
|
||||||
|
|
||||||
// We need to derive the private key for signing. In the remote signing
|
|
||||||
// setup, this whole RPC call will be forwarded to the signing
|
|
||||||
// instance, which requires it to be stateful.
|
|
||||||
privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{
|
|
||||||
KeyLocator: keyLoc,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error deriving private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a signing context and session with the given private key and
|
|
||||||
// list of all known signer public keys.
|
|
||||||
muSigContext, muSigSession, err := input.MuSig2CreateContext(
|
|
||||||
bipVersion, privKey, allSignerPubKeys, tweaks,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error creating signing context: %w",
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all nonces we might've learned so far.
|
|
||||||
haveAllNonces := false
|
|
||||||
for _, otherSignerNonce := range otherSignerNonces {
|
|
||||||
haveAllNonces, err = muSigSession.RegisterPubNonce(
|
|
||||||
otherSignerNonce,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error registering other "+
|
|
||||||
"signer public nonce: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the new session.
|
|
||||||
combinedKey, err := muSigContext.CombinedKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting combined key: %w", err)
|
|
||||||
}
|
|
||||||
session := &muSig2State{
|
|
||||||
MuSig2SessionInfo: input.MuSig2SessionInfo{
|
|
||||||
SessionID: input.NewMuSig2SessionID(
|
|
||||||
combinedKey, muSigSession.PublicNonce(),
|
|
||||||
),
|
|
||||||
Version: bipVersion,
|
|
||||||
PublicNonce: muSigSession.PublicNonce(),
|
|
||||||
CombinedKey: combinedKey,
|
|
||||||
TaprootTweak: tweaks.HasTaprootTweak(),
|
|
||||||
HaveAllNonces: haveAllNonces,
|
|
||||||
},
|
|
||||||
context: muSigContext,
|
|
||||||
session: muSigSession,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The internal key is only calculated if we are using a taproot tweak
|
|
||||||
// and need to know it for a potential script spend.
|
|
||||||
if tweaks.HasTaprootTweak() {
|
|
||||||
internalKey, err := muSigContext.TaprootInternalKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting internal key: %w",
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
session.TaprootInternalKey = internalKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since we generate new nonces for every session, there is no way that
|
|
||||||
// a session with the same ID already exists. So even if we call the API
|
|
||||||
// twice with the same signers, we still get a new ID.
|
|
||||||
b.musig2SessionsMtx.Lock()
|
|
||||||
b.musig2Sessions[session.SessionID] = session
|
|
||||||
b.musig2SessionsMtx.Unlock()
|
|
||||||
|
|
||||||
return &session.MuSig2SessionInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (b *BtcWallet) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
|
|
||||||
otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) {
|
|
||||||
|
|
||||||
// We hold the lock during the whole operation, we don't want any
|
|
||||||
// interference with calls that might come through in parallel for the
|
|
||||||
// same session.
|
|
||||||
b.musig2SessionsMtx.Lock()
|
|
||||||
defer b.musig2SessionsMtx.Unlock()
|
|
||||||
|
|
||||||
session, ok := b.musig2Sessions[sessionID]
|
|
||||||
if !ok {
|
|
||||||
return false, fmt.Errorf("session with ID %x not found",
|
|
||||||
sessionID[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we don't exceed the number of expected nonces as that would
|
|
||||||
// indicate something is wrong with the signing setup.
|
|
||||||
if session.HaveAllNonces {
|
|
||||||
return true, fmt.Errorf("already have all nonces")
|
|
||||||
}
|
|
||||||
|
|
||||||
numSigners := len(session.context.SigningKeys())
|
|
||||||
remainingNonces := numSigners - session.session.NumRegisteredNonces()
|
|
||||||
if len(otherSignerNonces) > remainingNonces {
|
|
||||||
return false, fmt.Errorf("only %d other nonces remaining but "+
|
|
||||||
"trying to register %d more", remainingNonces,
|
|
||||||
len(otherSignerNonces))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all nonces we've learned so far.
|
|
||||||
var err error
|
|
||||||
for _, otherSignerNonce := range otherSignerNonces {
|
|
||||||
session.HaveAllNonces, err = session.session.RegisterPubNonce(
|
|
||||||
otherSignerNonce,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("error registering other "+
|
|
||||||
"signer public nonce: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return session.HaveAllNonces, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (b *BtcWallet) MuSig2Sign(sessionID input.MuSig2SessionID,
|
|
||||||
msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
|
|
||||||
|
|
||||||
// We hold the lock during the whole operation, we don't want any
|
|
||||||
// interference with calls that might come through in parallel for the
|
|
||||||
// same session.
|
|
||||||
b.musig2SessionsMtx.Lock()
|
|
||||||
defer b.musig2SessionsMtx.Unlock()
|
|
||||||
|
|
||||||
session, ok := b.musig2Sessions[sessionID]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("session with ID %x not found",
|
|
||||||
sessionID[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can only sign once we have all other signer's nonces.
|
|
||||||
if !session.HaveAllNonces {
|
|
||||||
return nil, fmt.Errorf("only have %d of %d required nonces",
|
|
||||||
session.session.NumRegisteredNonces(),
|
|
||||||
len(session.context.SigningKeys()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create our own partial signature with the local signing key.
|
|
||||||
partialSig, err := input.MuSig2Sign(session.session, msg, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error signing with local key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up our local state if requested.
|
|
||||||
if cleanUp {
|
|
||||||
delete(b.musig2Sessions, sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return partialSig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (b *BtcWallet) MuSig2CombineSig(sessionID input.MuSig2SessionID,
|
|
||||||
partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
|
|
||||||
error) {
|
|
||||||
|
|
||||||
// We hold the lock during the whole operation, we don't want any
|
|
||||||
// interference with calls that might come through in parallel for the
|
|
||||||
// same session.
|
|
||||||
b.musig2SessionsMtx.Lock()
|
|
||||||
defer b.musig2SessionsMtx.Unlock()
|
|
||||||
|
|
||||||
session, ok := b.musig2Sessions[sessionID]
|
|
||||||
if !ok {
|
|
||||||
return nil, false, fmt.Errorf("session with ID %x not found",
|
|
||||||
sessionID[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we don't exceed the number of expected partial signatures
|
|
||||||
// as that would indicate something is wrong with the signing setup.
|
|
||||||
if session.HaveAllSigs {
|
|
||||||
return nil, true, fmt.Errorf("already have all partial" +
|
|
||||||
"signatures")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all sigs we got so far.
|
|
||||||
var (
|
|
||||||
finalSig *schnorr.Signature
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for _, otherPartialSig := range partialSigs {
|
|
||||||
session.HaveAllSigs, err = input.MuSig2CombineSig(
|
|
||||||
session.session, otherPartialSig,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, fmt.Errorf("error combining "+
|
|
||||||
"partial signature: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have all partial signatures, we should be able to get the
|
|
||||||
// complete signature now. We also remove this session from memory since
|
|
||||||
// there is nothing more left to do.
|
|
||||||
if session.HaveAllSigs {
|
|
||||||
finalSig = session.session.FinalSig()
|
|
||||||
delete(b.musig2Sessions, sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalSig, session.HaveAllSigs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MuSig2Cleanup removes a session from memory to free up resources.
|
|
||||||
func (b *BtcWallet) MuSig2Cleanup(sessionID input.MuSig2SessionID) error {
|
|
||||||
// We hold the lock during the whole operation, we don't want any
|
|
||||||
// interference with calls that might come through in parallel for the
|
|
||||||
// same session.
|
|
||||||
b.musig2SessionsMtx.Lock()
|
|
||||||
defer b.musig2SessionsMtx.Unlock()
|
|
||||||
|
|
||||||
_, ok := b.musig2Sessions[sessionID]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("session with ID %x not found", sessionID[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(b.musig2Sessions, sessionID)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// A compile time check to ensure that BtcWallet implements the Signer
|
// A compile time check to ensure that BtcWallet implements the Signer
|
||||||
// interface.
|
// interface.
|
||||||
var _ input.Signer = (*BtcWallet)(nil)
|
var _ input.Signer = (*BtcWallet)(nil)
|
||||||
|
|
|
@ -656,9 +656,8 @@ func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx,
|
||||||
// submitted as well to reduce the number of method calls necessary later on.
|
// submitted as well to reduce the number of method calls necessary later on.
|
||||||
func (r *RPCKeyRing) MuSig2CreateSession(bipVersion input.MuSig2Version,
|
func (r *RPCKeyRing) MuSig2CreateSession(bipVersion input.MuSig2Version,
|
||||||
keyLoc keychain.KeyLocator, pubKeys []*btcec.PublicKey,
|
keyLoc keychain.KeyLocator, pubKeys []*btcec.PublicKey,
|
||||||
tweaks *input.MuSig2Tweaks,
|
tweaks *input.MuSig2Tweaks, otherNonces [][musig2.PubNonceSize]byte,
|
||||||
otherNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo,
|
sessionOpts ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) {
|
||||||
error) {
|
|
||||||
|
|
||||||
apiVersion, err := signrpc.MarshalMuSig2Version(bipVersion)
|
apiVersion, err := signrpc.MarshalMuSig2Version(bipVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -343,8 +343,8 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
|
||||||
Packager: channeldb.NewChannelPackager(shortChanID),
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceSigner := &input.MockSigner{Privkeys: aliceKeys}
|
aliceSigner := input.NewMockSigner(aliceKeys, nil)
|
||||||
bobSigner := &input.MockSigner{Privkeys: bobKeys}
|
bobSigner := input.NewMockSigner(bobKeys, nil)
|
||||||
|
|
||||||
// TODO(roasbeef): make mock version of pre-image store
|
// TODO(roasbeef): make mock version of pre-image store
|
||||||
|
|
||||||
|
|
|
@ -587,9 +587,9 @@ func testSpendValidation(t *testing.T, tweakless bool) {
|
||||||
remoteCommitTweak := input.SingleTweakBytes(commitPoint, aliceKeyPub)
|
remoteCommitTweak := input.SingleTweakBytes(commitPoint, aliceKeyPub)
|
||||||
localCommitTweak := input.SingleTweakBytes(commitPoint, bobKeyPub)
|
localCommitTweak := input.SingleTweakBytes(commitPoint, bobKeyPub)
|
||||||
|
|
||||||
aliceSelfOutputSigner := &input.MockSigner{
|
aliceSelfOutputSigner := input.NewMockSigner(
|
||||||
Privkeys: []*btcec.PrivateKey{aliceKeyPriv},
|
[]*btcec.PrivateKey{aliceKeyPriv}, nil,
|
||||||
}
|
)
|
||||||
|
|
||||||
// Calculate the dust limit we'll use for the test.
|
// Calculate the dust limit we'll use for the test.
|
||||||
dustLimit := DustLimitForSize(input.UnknownWitnessSize)
|
dustLimit := DustLimitForSize(input.UnknownWitnessSize)
|
||||||
|
@ -679,7 +679,7 @@ func testSpendValidation(t *testing.T, tweakless bool) {
|
||||||
t.Fatalf("spend from delay output is invalid: %v", err)
|
t.Fatalf("spend from delay output is invalid: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
localSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}}
|
localSigner := input.NewMockSigner([]*btcec.PrivateKey{bobKeyPriv}, nil)
|
||||||
|
|
||||||
// Next, we'll test bob spending with the derived revocation key to
|
// Next, we'll test bob spending with the derived revocation key to
|
||||||
// simulate the scenario when Alice broadcasts this commitment
|
// simulate the scenario when Alice broadcasts this commitment
|
||||||
|
@ -994,15 +994,15 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mock signers that can sign for the keys that are used.
|
// Create mock signers that can sign for the keys that are used.
|
||||||
localSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{
|
localSigner := input.NewMockSigner([]*btcec.PrivateKey{
|
||||||
tc.localPaymentBasepointSecret, tc.localDelayedPaymentBasepointSecret,
|
tc.localPaymentBasepointSecret, tc.localDelayedPaymentBasepointSecret,
|
||||||
tc.localFundingPrivkey, localDummy1, localDummy2,
|
tc.localFundingPrivkey, localDummy1, localDummy2,
|
||||||
}}
|
}, nil)
|
||||||
|
|
||||||
remoteSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{
|
remoteSigner := input.NewMockSigner([]*btcec.PrivateKey{
|
||||||
tc.remoteFundingPrivkey, tc.remoteRevocationBasepointSecret,
|
tc.remoteFundingPrivkey, tc.remoteRevocationBasepointSecret,
|
||||||
tc.remotePaymentBasepointSecret, remoteDummy1, remoteDummy2,
|
tc.remotePaymentBasepointSecret, remoteDummy1, remoteDummy2,
|
||||||
}}
|
}, nil)
|
||||||
|
|
||||||
remotePool := NewSigPool(1, remoteSigner)
|
remotePool := NewSigPool(1, remoteSigner)
|
||||||
channelRemote, err := NewLightningChannel(
|
channelRemote, err := NewLightningChannel(
|
||||||
|
|
|
@ -276,8 +276,12 @@ func createTestPeer(t *testing.T, notifier chainntnfs.ChainNotifier,
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceSigner := &mock.SingleSigner{Privkey: aliceKeyPriv}
|
aliceSigner := input.NewMockSigner(
|
||||||
bobSigner := &mock.SingleSigner{Privkey: bobKeyPriv}
|
[]*btcec.PrivateKey{aliceKeyPriv}, nil,
|
||||||
|
)
|
||||||
|
bobSigner := input.NewMockSigner(
|
||||||
|
[]*btcec.PrivateKey{bobKeyPriv}, nil,
|
||||||
|
)
|
||||||
|
|
||||||
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
||||||
channelAlice, err := lnwallet.NewLightningChannel(
|
channelAlice, err := lnwallet.NewLightningChannel(
|
||||||
|
|
|
@ -71,7 +71,8 @@ func (s *MockSigner) ComputeInputScript(tx *wire.MsgTx,
|
||||||
// submitted as well to reduce the number of method calls necessary later on.
|
// submitted as well to reduce the number of method calls necessary later on.
|
||||||
func (s *MockSigner) MuSig2CreateSession(input.MuSig2Version,
|
func (s *MockSigner) MuSig2CreateSession(input.MuSig2Version,
|
||||||
keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
|
keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
|
||||||
[][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) {
|
[][musig2.PubNonceSize]byte,
|
||||||
|
...musig2.SessionOption) (*input.MuSig2SessionInfo, error) {
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue