diff --git a/loopin.go b/loopin.go index c329725..3bdff19 100644 --- a/loopin.go +++ b/loopin.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/mempool" @@ -19,6 +20,7 @@ import ( "github.com/lightninglabs/loop/swap" "github.com/lightningnetwork/lnd/chainntnfs" invpkg "github.com/lightningnetwork/lnd/invoices" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntypes" @@ -189,6 +191,23 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig, return nil, err } + // Default the HTLC internal key to our sender key. + senderInternalPubKey := senderKey + + // If this is a MuSig2 swap then we'll generate a brand new key pair + // and will use that as the internal key for the HTLC. + if loopdb.CurrentProtocolVersion() >= loopdb.ProtocolVersionMuSig2 { + secret, err := sharedSecretFromHash( + globalCtx, cfg.lnd.Signer, swapHash, + ) + if err != nil { + return nil, err + } + + _, pubKey := btcec.PrivKeyFromBytes(secret[:]) + copy(senderInternalPubKey[:], pubKey.SerializeCompressed()) + } + // Create a cancellable context that is used for monitoring the probe. probeWaitCtx, probeWaitCancel := context.WithCancel(globalCtx) @@ -204,8 +223,8 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig, // htlc. log.Infof("Initiating swap request at height %v", currentHeight) swapResp, err := cfg.server.NewLoopInSwap(globalCtx, swapHash, - request.Amount, senderKey, swapInvoice, probeInvoice, - request.LastHop, request.Initiator, + request.Amount, senderKey, senderInternalPubKey, swapInvoice, + probeInvoice, request.LastHop, request.Initiator, ) probeWaitCancel() if err != nil { @@ -254,6 +273,13 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig, }, } + // For MuSig2 swaps we store the proper internal keys that we generated + // and received from the server. + if loopdb.CurrentProtocolVersion() >= loopdb.ProtocolVersionMuSig2 { + contract.HtlcKeys.SenderInternalPubKey = senderInternalPubKey + contract.HtlcKeys.ReceiverInternalPubKey = swapResp.receiverInternalKey + } + swapKit := newSwapKit( swapHash, swap.TypeIn, cfg, &contract.SwapContract, @@ -1031,3 +1057,18 @@ func (s *loopInSwap) setState(state loopdb.SwapState) { s.lastUpdateTime = time.Now() s.state = state } + +// sharedSecretFromHash derives the shared secret from the swap hash using the +// swap.KeyFamily family and zero as index. +func sharedSecretFromHash(ctx context.Context, signer lndclient.SignerClient, + hash lntypes.Hash) ([32]byte, error) { + + _, hashPubKey := btcec.PrivKeyFromBytes(hash[:]) + + return signer.DeriveSharedKey( + ctx, hashPubKey, &keychain.KeyLocator{ + Family: keychain.KeyFamily(swap.KeyFamily), + Index: 0, + }, + ) +} diff --git a/server_mock_test.go b/server_mock_test.go index 28c42fe..9851a39 100644 --- a/server_mock_test.go +++ b/server_mock_test.go @@ -151,17 +151,22 @@ func getInvoice(hash lntypes.Hash, amt btcutil.Amount, memo string) (string, err } func (s *serverMock) NewLoopInSwap(_ context.Context, swapHash lntypes.Hash, - amount btcutil.Amount, _ [33]byte, swapInvoice, _ string, + amount btcutil.Amount, _, _ [33]byte, swapInvoice, _ string, _ *route.Vertex, _ string) (*newLoopInResponse, error) { _, receiverKey := test.CreateKey(101) + _, receiverInternalKey := test.CreateKey(102) if amount != s.expectedSwapAmt { return nil, errors.New("unexpected test swap amount") } - var receiverKeyArray [33]byte + var receiverKeyArray, receiverInternalKeyArray [33]byte copy(receiverKeyArray[:], receiverKey.SerializeCompressed()) + copy( + receiverInternalKeyArray[:], + receiverInternalKey.SerializeCompressed(), + ) s.swapInvoice = swapInvoice s.swapHash = swapHash @@ -175,8 +180,9 @@ func (s *serverMock) NewLoopInSwap(_ context.Context, swapHash lntypes.Hash, <-s.lnd.FailInvoiceChannel resp := &newLoopInResponse{ - expiry: s.height + testChargeOnChainCltvDelta, - receiverKey: receiverKeyArray, + expiry: s.height + testChargeOnChainCltvDelta, + receiverKey: receiverKeyArray, + receiverInternalKey: receiverInternalKeyArray, } return resp, nil diff --git a/swap_server_client.go b/swap_server_client.go index b2d23c5..e029943 100644 --- a/swap_server_client.go +++ b/swap_server_client.go @@ -94,10 +94,10 @@ type swapServerClient interface { preimage lntypes.Preimage) error NewLoopInSwap(ctx context.Context, - swapHash lntypes.Hash, amount btcutil.Amount, - senderKey [33]byte, swapInvoice, probeInvoice string, - lastHop *route.Vertex, initiator string) (*newLoopInResponse, - error) + swapHash lntypes.Hash, amount btcutil.Amount, senderScriptKey, + senderInternalKey [33]byte, swapInvoice, probeInvoice string, + lastHop *route.Vertex, initiator string) ( + *newLoopInResponse, error) // SubscribeLoopOutUpdates subscribes to loop out server state. SubscribeLoopOutUpdates(ctx context.Context, @@ -419,9 +419,9 @@ func (s *grpcSwapServerClient) PushLoopOutPreimage(ctx context.Context, } func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context, - swapHash lntypes.Hash, amount btcutil.Amount, senderKey [33]byte, - swapInvoice, probeInvoice string, lastHop *route.Vertex, - initiator string) (*newLoopInResponse, error) { + swapHash lntypes.Hash, amount btcutil.Amount, senderScriptKey, + senderInternalKey [33]byte, swapInvoice, probeInvoice string, + lastHop *route.Vertex, initiator string) (*newLoopInResponse, error) { rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout) defer rpcCancel() @@ -429,7 +429,7 @@ func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context, req := &looprpc.ServerLoopInRequest{ SwapHash: swapHash[:], Amt: uint64(amount), - SenderKey: senderKey[:], + SenderKey: senderScriptKey[:], SwapInvoice: swapInvoice, ProtocolVersion: loopdb.CurrentRPCProtocolVersion(), ProbeInvoice: probeInvoice, @@ -439,13 +439,19 @@ func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context, req.LastHop = lastHop[:] } + // Set the client's internal key if this is a MuSig2 swap. + if loopdb.CurrentProtocolVersion() >= loopdb.ProtocolVersionMuSig2 { + req.SenderInternalPubkey = senderInternalKey[:] + } + swapResp, err := s.server.NewLoopInSwap(rpcCtx, req) if err != nil { return nil, err } - var receiverKey [33]byte + var receiverKey, receiverInternalKey [33]byte copy(receiverKey[:], swapResp.ReceiverKey) + copy(receiverInternalKey[:], swapResp.ReceiverInternalPubkey) // Validate receiver key. _, err = btcec.ParsePubKey(receiverKey[:]) @@ -454,9 +460,10 @@ func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context, } return &newLoopInResponse{ - receiverKey: receiverKey, - expiry: swapResp.Expiry, - serverMessage: swapResp.ServerMessage, + receiverKey: receiverKey, + receiverInternalKey: receiverInternalKey, + expiry: swapResp.Expiry, + serverMessage: swapResp.ServerMessage, }, nil } @@ -873,7 +880,8 @@ type newLoopOutResponse struct { } type newLoopInResponse struct { - receiverKey [33]byte - expiry int32 - serverMessage string + receiverKey [33]byte + receiverInternalKey [33]byte + expiry int32 + serverMessage string }