mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
sweepbatcher: add option WithCustomSignMuSig2
It is needed to provide a custom MuSig2 signer.
This commit is contained in:
parent
38e86a1b0f
commit
aa6d748647
3 changed files with 194 additions and 18 deletions
|
|
@ -137,6 +137,12 @@ type batchConfig struct {
|
|||
// the caller has to update it in the source of SweepInfo (interface
|
||||
// SweepFetcher) and re-add the sweep by calling AddSweep.
|
||||
noBumping bool
|
||||
|
||||
// customMuSig2Signer is a custom signer. If it is set, it is used to
|
||||
// create musig2 signatures instead of musig2SignSweep and signerClient.
|
||||
// Note that musig2SignSweep must be nil in this case, however signer
|
||||
// client must still be provided, as it is used for non-coop spendings.
|
||||
customMuSig2Signer SignMuSig2
|
||||
}
|
||||
|
||||
// rbfCache stores data related to our last fee bump.
|
||||
|
|
@ -503,9 +509,12 @@ func (b *batch) Run(ctx context.Context) error {
|
|||
close(b.finished)
|
||||
}()
|
||||
|
||||
if b.muSig2SignSweep == nil {
|
||||
if b.muSig2SignSweep == nil && b.cfg.customMuSig2Signer == nil {
|
||||
return fmt.Errorf("no musig2 signer available")
|
||||
}
|
||||
if b.muSig2SignSweep != nil && b.cfg.customMuSig2Signer != nil {
|
||||
return fmt.Errorf("both musig2 signers provided")
|
||||
}
|
||||
|
||||
blockChan, blockErrChan, err :=
|
||||
b.chainNotifier.RegisterBlockEpochNtfn(runCtx)
|
||||
|
|
@ -1008,6 +1017,36 @@ func (b *batch) musig2sign(ctx context.Context, inputIndex int, sweep sweep,
|
|||
return nil, fmt.Errorf("invalid htlc script version")
|
||||
}
|
||||
|
||||
var digest [32]byte
|
||||
copy(digest[:], sigHash)
|
||||
|
||||
// If a custom signer is installed, use it instead of b.signerClient
|
||||
// and b.muSig2SignSweep.
|
||||
if b.cfg.customMuSig2Signer != nil {
|
||||
// Produce a signature.
|
||||
finalSig, err := b.cfg.customMuSig2Signer(
|
||||
ctx, muSig2Version, sweep.swapHash,
|
||||
htlcScript.RootHash, digest,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("customMuSig2Signer failed: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
// To be sure that we're good, parse and validate that the
|
||||
// combined signature is indeed valid for the sig hash and the
|
||||
// internal pubkey.
|
||||
err = b.verifySchnorrSig(
|
||||
htlcScript.TaprootKey, sigHash, finalSig,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("verifySchnorrSig failed: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
return finalSig, nil
|
||||
}
|
||||
|
||||
// Now we're creating a local MuSig2 session using the receiver key's
|
||||
// key locator and the htlc's root hash.
|
||||
keyLocator := &sweep.htlcKeys.ClientScriptKeyLocator
|
||||
|
|
@ -1052,9 +1091,6 @@ func (b *batch) musig2sign(ctx context.Context, inputIndex int, sweep sweep,
|
|||
"nonces missing")
|
||||
}
|
||||
|
||||
var digest [32]byte
|
||||
copy(digest[:], sigHash)
|
||||
|
||||
// Since our MuSig2 session has all nonces, we can now create
|
||||
// the local partial signature by signing the sig hash.
|
||||
_, err = b.signerClient.MuSig2Sign(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
|
|
@ -140,6 +141,12 @@ type MuSig2SignSweep func(ctx context.Context,
|
|||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error)
|
||||
|
||||
// SignMuSig2 is a function that can be used to sign a sweep transaction in a
|
||||
// custom way.
|
||||
type SignMuSig2 func(ctx context.Context, muSig2Version input.MuSig2Version,
|
||||
swapHash lntypes.Hash, rootHash chainhash.Hash, sigHash [32]byte,
|
||||
) ([]byte, error)
|
||||
|
||||
// VerifySchnorrSig is a function that can be used to verify a schnorr
|
||||
// signature.
|
||||
type VerifySchnorrSig func(pubKey *btcec.PublicKey, hash, sig []byte) error
|
||||
|
|
@ -245,6 +252,12 @@ type Batcher struct {
|
|||
// the caller has to update it in the source of SweepInfo (interface
|
||||
// SweepFetcher) and re-add the sweep by calling AddSweep.
|
||||
noBumping bool
|
||||
|
||||
// customMuSig2Signer is a custom signer. If it is set, it is used to
|
||||
// create musig2 signatures instead of musig2SignSweep and signerClient.
|
||||
// Note that musig2SignSweep must be nil in this case, however signer
|
||||
// client must still be provided, as it is used for non-coop spendings.
|
||||
customMuSig2Signer SignMuSig2
|
||||
}
|
||||
|
||||
// BatcherConfig holds batcher configuration.
|
||||
|
|
@ -254,6 +267,12 @@ type BatcherConfig struct {
|
|||
// the caller has to update it in the source of SweepInfo (interface
|
||||
// SweepFetcher) and re-add the sweep by calling AddSweep.
|
||||
noBumping bool
|
||||
|
||||
// customMuSig2Signer is a custom signer. If it is set, it is used to
|
||||
// create musig2 signatures instead of musig2SignSweep and signerClient.
|
||||
// Note that musig2SignSweep must be nil in this case, however signer
|
||||
// client must still be provided, as it is used for non-coop spendings.
|
||||
customMuSig2Signer SignMuSig2
|
||||
}
|
||||
|
||||
// BatcherOption configures batcher behaviour.
|
||||
|
|
@ -269,6 +288,17 @@ func WithNoBumping() BatcherOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithCustomSignMuSig2 instructs sweepbatcher to use a custom function to
|
||||
// produce MuSig2 signatures. If it is set, it is used to create
|
||||
// musig2 signatures instead of musig2SignSweep and signerClient. Note
|
||||
// that musig2SignSweep must be nil in this case, however signerClient
|
||||
// must still be provided, as it is used for non-coop spendings.
|
||||
func WithCustomSignMuSig2(customMuSig2Signer SignMuSig2) BatcherOption {
|
||||
return func(cfg *BatcherConfig) {
|
||||
cfg.customMuSig2Signer = customMuSig2Signer
|
||||
}
|
||||
}
|
||||
|
||||
// NewBatcher creates a new Batcher instance.
|
||||
func NewBatcher(wallet lndclient.WalletKitClient,
|
||||
chainNotifier lndclient.ChainNotifierClient,
|
||||
|
|
@ -282,21 +312,27 @@ func NewBatcher(wallet lndclient.WalletKitClient,
|
|||
opt(&cfg)
|
||||
}
|
||||
|
||||
if cfg.customMuSig2Signer != nil && musig2ServerSigner != nil {
|
||||
panic("customMuSig2Signer must not be used with " +
|
||||
"musig2ServerSigner")
|
||||
}
|
||||
|
||||
return &Batcher{
|
||||
batches: make(map[int32]*batch),
|
||||
sweepReqs: make(chan SweepRequest),
|
||||
errChan: make(chan error, 1),
|
||||
quit: make(chan struct{}),
|
||||
initDone: make(chan struct{}),
|
||||
wallet: wallet,
|
||||
chainNotifier: chainNotifier,
|
||||
signerClient: signerClient,
|
||||
musig2ServerSign: musig2ServerSigner,
|
||||
VerifySchnorrSig: verifySchnorrSig,
|
||||
chainParams: chainparams,
|
||||
store: store,
|
||||
sweepStore: sweepStore,
|
||||
noBumping: cfg.noBumping,
|
||||
batches: make(map[int32]*batch),
|
||||
sweepReqs: make(chan SweepRequest),
|
||||
errChan: make(chan error, 1),
|
||||
quit: make(chan struct{}),
|
||||
initDone: make(chan struct{}),
|
||||
wallet: wallet,
|
||||
chainNotifier: chainNotifier,
|
||||
signerClient: signerClient,
|
||||
musig2ServerSign: musig2ServerSigner,
|
||||
VerifySchnorrSig: verifySchnorrSig,
|
||||
chainParams: chainparams,
|
||||
store: store,
|
||||
sweepStore: sweepStore,
|
||||
noBumping: cfg.noBumping,
|
||||
customMuSig2Signer: cfg.customMuSig2Signer,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -456,6 +492,7 @@ func (b *Batcher) spinUpBatch(ctx context.Context) (*batch, error) {
|
|||
cfg := batchConfig{
|
||||
maxTimeoutDistance: defaultMaxTimeoutDistance,
|
||||
noBumping: b.noBumping,
|
||||
customMuSig2Signer: b.customMuSig2Signer,
|
||||
}
|
||||
|
||||
switch b.chainParams {
|
||||
|
|
@ -574,6 +611,7 @@ func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error {
|
|||
cfg := batchConfig{
|
||||
maxTimeoutDistance: batch.cfg.maxTimeoutDistance,
|
||||
noBumping: b.noBumping,
|
||||
customMuSig2Signer: b.customMuSig2Signer,
|
||||
}
|
||||
|
||||
newBatch, err := NewBatchFromDB(cfg, batchKit)
|
||||
|
|
@ -637,6 +675,7 @@ func (b *Batcher) FetchUnconfirmedBatches(ctx context.Context) ([]*batch,
|
|||
bchCfg := batchConfig{
|
||||
maxTimeoutDistance: bch.MaxTimeoutDistance,
|
||||
noBumping: b.noBumping,
|
||||
customMuSig2Signer: b.customMuSig2Signer,
|
||||
}
|
||||
batch.cfg = &bchCfg
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
|
|
@ -68,6 +69,18 @@ func testMuSig2SignSweep(ctx context.Context,
|
|||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var customSignature = func() []byte {
|
||||
sig := [64]byte{10, 20, 30}
|
||||
return sig[:]
|
||||
}()
|
||||
|
||||
func testSignMuSig2func(ctx context.Context, muSig2Version input.MuSig2Version,
|
||||
swapHash lntypes.Hash, rootHash chainhash.Hash,
|
||||
sigHash [32]byte) ([]byte, error) {
|
||||
|
||||
return customSignature, nil
|
||||
}
|
||||
|
||||
var dummyNotifier = SpendNotifier{
|
||||
SpendChan: make(chan *SpendDetail, ntfnBufferSize),
|
||||
SpendErrChan: make(chan error, ntfnBufferSize),
|
||||
|
|
@ -1985,6 +1998,89 @@ func testSweepBatcherCloseDuringAdding(t *testing.T, store testStore,
|
|||
<-registrationChan
|
||||
}
|
||||
|
||||
// testCustomSignMuSig2 tests the operation with custom musig2 signer.
|
||||
func testCustomSignMuSig2(t *testing.T, store testStore,
|
||||
batcherStore testBatcherStore) {
|
||||
|
||||
defer test.Guard(t)()
|
||||
|
||||
lnd := test.NewMockLnd()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
sweepStore, err := NewSweepFetcherFromSwapStore(store, lnd.ChainParams)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use custom MuSig2 signer function.
|
||||
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
|
||||
nil, testVerifySchnorrSig, lnd.ChainParams, batcherStore,
|
||||
sweepStore, WithCustomSignMuSig2(testSignMuSig2func))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
var runErr error
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
runErr = batcher.Run(ctx)
|
||||
}()
|
||||
|
||||
// Wait for the batcher to be initialized.
|
||||
<-batcher.initDone
|
||||
|
||||
// Create a sweep request.
|
||||
sweepReq := SweepRequest{
|
||||
SwapHash: lntypes.Hash{1, 1, 1},
|
||||
Value: 111,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{1, 1},
|
||||
Index: 1,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111,
|
||||
AmountRequested: 111,
|
||||
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
|
||||
HtlcKeys: loopdb.HtlcKeys{
|
||||
SenderScriptKey: senderKey,
|
||||
ReceiverScriptKey: receiverKey,
|
||||
SenderInternalPubKey: senderKey,
|
||||
ReceiverInternalPubKey: receiverKey,
|
||||
},
|
||||
},
|
||||
|
||||
DestAddr: destAddr,
|
||||
SwapInvoice: swapInvoice,
|
||||
SweepConfTarget: 111,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq.SwapHash, swap)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Deliver sweep request to batcher.
|
||||
require.NoError(t, batcher.AddSweep(&sweepReq))
|
||||
|
||||
// Since a batch was created we check that it registered for its primary
|
||||
// sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
// Wait for tx to be published.
|
||||
tx := <-lnd.TxPublishChannel
|
||||
|
||||
// Check the signature.
|
||||
gotSig := tx.TxIn[0].Witness[0]
|
||||
require.Equal(t, customSignature, gotSig, "signatures don't match")
|
||||
|
||||
// Now make the batcher quit by canceling the context.
|
||||
cancel()
|
||||
wg.Wait()
|
||||
|
||||
checkBatcherError(t, runErr)
|
||||
}
|
||||
|
||||
// TestSweepBatcherBatchCreation tests that sweep requests enter the expected
|
||||
// batch based on their timeout distance.
|
||||
func TestSweepBatcherBatchCreation(t *testing.T) {
|
||||
|
|
@ -2070,6 +2166,11 @@ func TestSweepBatcherCloseDuringAdding(t *testing.T) {
|
|||
runTests(t, testSweepBatcherCloseDuringAdding)
|
||||
}
|
||||
|
||||
// TestCustomSignMuSig2 tests the operation with custom musig2 signer.
|
||||
func TestCustomSignMuSig2(t *testing.T) {
|
||||
runTests(t, testCustomSignMuSig2)
|
||||
}
|
||||
|
||||
// testBatcherStore is BatcherStore used in tests.
|
||||
type testBatcherStore interface {
|
||||
BatcherStore
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue