From 90ae922adf7996165f6d6a1086476c9185badfc6 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Fri, 6 Jan 2023 17:16:17 +0100 Subject: [PATCH] loopin: generate and send internal key for MuSig2 swaps This commit changes how we create loopin swaps if the client activates the experimental MuSig2 features. When creating a new loopin swap the client will create (and store) a new key that will be used as the sender's internal key when constructing the HTLC. The client will send the public part to the server and will also receive (and store) the server's (receiver) internal public key. --- loopin.go | 45 +++++++++++++++++++++++++++++++++++++++++-- server_mock_test.go | 14 ++++++++++---- swap_server_client.go | 38 +++++++++++++++++++++--------------- 3 files changed, 76 insertions(+), 21 deletions(-) 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 }