Browse Source

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.
pull/70/head
Bjorn Olav Jalborg 1 year ago
parent
commit
8d7a272fdd
2 changed files with 22 additions and 13 deletions
  1. +11
    -6
      loopout.go
  2. +11
    -7
      swap/fees.go

+ 11
- 6
loopout.go View File

@ -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 {

+ 11
- 7
swap/fees.go View File

@ -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
}

Loading…
Cancel
Save