From 8d7a272fddae97732f9b836966b48ceaa3d6936b Mon Sep 17 00:00:00 2001 From: Bjorn Olav Jalborg Date: Wed, 31 Jul 2019 10:46:51 +0200 Subject: [PATCH] loop+loopout: validate hash of swap invoice This commit fixes a possible exploit by the loop server, where - in a loop out - the server could claim money off-chain, without publishing an on-chain swap htlc. The server could do this by responding with a regular invoice, whose hash is different than the hash in the NewLoopOutSwap request. To prevent the exploit, we validate that the hash of the swap invoice is equal to the hash the client generated. --- loopout.go | 17 +++++++++++------ swap/fees.go | 18 +++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/loopout.go b/loopout.go index 64c17f1..d02724d 100644 --- a/loopout.go +++ b/loopout.go @@ -86,12 +86,12 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig, return nil, fmt.Errorf("cannot initiate swap: %v", err) } - err = validateLoopOutContract(cfg.lnd, currentHeight, request, swapResp) + err = validateLoopOutContract(cfg.lnd, currentHeight, request, swapHash, swapResp) if err != nil { return nil, err } - // Instantie a struct that contains all required data to start the swap. + // Instantiate a struct that contains all required data to start the swap. initiationTime := time.Now() contract := loopdb.LoopOutContract{ @@ -660,21 +660,26 @@ func (s *loopOutSwap) sweep(ctx context.Context, // validateLoopOutContract validates the contract parameters against our // request. func validateLoopOutContract(lnd *lndclient.LndServices, - height int32, - request *OutRequest, + height int32, request *OutRequest, swapHash lntypes.Hash, response *newLoopOutResponse) error { // Check invoice amounts. chainParams := lnd.ChainParams - swapInvoiceAmt, err := swap.GetInvoiceAmt( + swapInvoiceHash, swapInvoiceAmt, err := swap.DecodeInvoice( chainParams, response.swapInvoice, ) if err != nil { return err } - prepayInvoiceAmt, err := swap.GetInvoiceAmt( + if swapInvoiceHash != swapHash { + return fmt.Errorf( + "cannot initiate swap, swap invoice hash %v not equal generated swap hash %v", + swapInvoiceHash, swapHash) + } + + _, prepayInvoiceAmt, err := swap.DecodeInvoice( chainParams, response.prepayInvoice, ) if err != nil { diff --git a/swap/fees.go b/swap/fees.go index b104f9a..c190926 100644 --- a/swap/fees.go +++ b/swap/fees.go @@ -5,6 +5,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/zpay32" ) @@ -26,21 +27,24 @@ func FeeRateAsPercentage(feeRate int64) float64 { return float64(feeRate) / (FeeRateTotalParts / 100) } -// GetInvoiceAmt gets the invoice amount. It requires an amount to be -// specified. -func GetInvoiceAmt(params *chaincfg.Params, - payReq string) (btcutil.Amount, error) { +// DecodeInvoice gets the hash and the amount of an invoice. +// It requires an amount to be specified. +func DecodeInvoice(params *chaincfg.Params, + payReq string) (lntypes.Hash, btcutil.Amount, error) { swapPayReq, err := zpay32.Decode( payReq, params, ) if err != nil { - return 0, err + return lntypes.Hash{}, 0, err } if swapPayReq.MilliSat == nil { - return 0, errors.New("no amount in invoice") + return lntypes.Hash{}, 0, errors.New("no amount in invoice") } - return swapPayReq.MilliSat.ToSatoshis(), nil + var hash lntypes.Hash + copy(hash[:], swapPayReq.PaymentHash[:]) + + return hash, swapPayReq.MilliSat.ToSatoshis(), nil }