github.com/decred/dcrlnd@v0.7.6/lnwallet/commit_sort.go (about)

     1  package lnwallet
     2  
     3  import (
     4  	"bytes"
     5  	"sort"
     6  
     7  	"github.com/decred/dcrd/chaincfg/chainhash"
     8  	"github.com/decred/dcrd/wire"
     9  )
    10  
    11  // InPlaceCommitSort performs an in-place sort of a commitment transaction,
    12  // given an unsorted transaction and a list of CLTV values for the HTLCs.
    13  //
    14  // The sort applied is a modified BIP69 sort, that uses the CLTV values of HTLCs
    15  // as a tie breaker in case two HTLC outputs have an identical amount and
    16  // pkscript. The pkscripts can be the same if they share the same payment hash,
    17  // but since the CLTV is enforced via the nLockTime of the second-layer
    18  // transactions, the script does not directly commit to them. Instead, the CLTVs
    19  // must be supplied separately to act as a tie-breaker, otherwise we may produce
    20  // invalid HTLC signatures if the receiver produces an alternative ordering
    21  // during verification.
    22  //
    23  // NOTE: Commitment outputs should have a 0 CLTV corresponding to their index on
    24  // the unsorted commitment transaction.
    25  func InPlaceCommitSort(tx *wire.MsgTx, cltvs []uint32) {
    26  	if len(tx.TxOut) != len(cltvs) {
    27  		panic("output and cltv list size mismatch")
    28  	}
    29  
    30  	sort.Sort(sortableInputSlice(tx.TxIn))
    31  	sort.Sort(sortableCommitOutputSlice{tx.TxOut, cltvs})
    32  }
    33  
    34  // sortableInputSlice is a slice of transaction inputs that supports sorting via
    35  // BIP69.
    36  type sortableInputSlice []*wire.TxIn
    37  
    38  // Len returns the length of the sortableInputSlice.
    39  //
    40  // NOTE: Part of the sort.Interface interface.
    41  func (s sortableInputSlice) Len() int { return len(s) }
    42  
    43  // Swap exchanges the position of inputs i and j.
    44  //
    45  // NOTE: Part of the sort.Interface interface.
    46  func (s sortableInputSlice) Swap(i, j int) {
    47  	s[i], s[j] = s[j], s[i]
    48  }
    49  
    50  // Less is the BIP69 input comparison function. The sort is first applied on
    51  // input hash (reversed / rpc-style), then index. This logic is copied from
    52  // dcrutil/txsort.
    53  //
    54  // NOTE: Part of the sort.Interface interface.
    55  func (s sortableInputSlice) Less(i, j int) bool {
    56  	// Input hashes are the same, so compare the index.
    57  	ihash := s[i].PreviousOutPoint.Hash
    58  	jhash := s[j].PreviousOutPoint.Hash
    59  	if ihash == jhash {
    60  		return s[i].PreviousOutPoint.Index < s[j].PreviousOutPoint.Index
    61  	}
    62  
    63  	// At this point, the hashes are not equal, so reverse them to
    64  	// big-endian and return the result of the comparison.
    65  	const hashSize = chainhash.HashSize
    66  	for b := 0; b < hashSize/2; b++ {
    67  		ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b]
    68  		jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b]
    69  	}
    70  	return bytes.Compare(ihash[:], jhash[:]) == -1
    71  }
    72  
    73  // sortableCommitOutputSlice is a slice of transaction outputs on a commitment
    74  // transaction and the corresponding CLTV values of any HTLCs. Commitment
    75  // outputs should have a CLTV of 0 and the same index in cltvs.
    76  type sortableCommitOutputSlice struct {
    77  	txouts []*wire.TxOut
    78  	cltvs  []uint32
    79  }
    80  
    81  // Len returns the length of the sortableCommitOutputSlice.
    82  //
    83  // NOTE: Part of the sort.Interface interface.
    84  func (s sortableCommitOutputSlice) Len() int {
    85  	return len(s.txouts)
    86  }
    87  
    88  // Swap exchanges the position of outputs i and j, as well as their
    89  // corresponding CLTV values.
    90  //
    91  // NOTE: Part of the sort.Interface interface.
    92  func (s sortableCommitOutputSlice) Swap(i, j int) {
    93  	s.txouts[i], s.txouts[j] = s.txouts[j], s.txouts[i]
    94  	s.cltvs[i], s.cltvs[j] = s.cltvs[j], s.cltvs[i]
    95  }
    96  
    97  // Less is a modified BIP69 output comparison, that sorts based on value, then
    98  // pkscript, then CLTV value.
    99  //
   100  // NOTE: Part of the sort.Interface interface.
   101  func (s sortableCommitOutputSlice) Less(i, j int) bool {
   102  	outi, outj := s.txouts[i], s.txouts[j]
   103  
   104  	if outi.Value != outj.Value {
   105  		return outi.Value < outj.Value
   106  	}
   107  
   108  	pkScriptCmp := bytes.Compare(outi.PkScript, outj.PkScript)
   109  	if pkScriptCmp != 0 {
   110  		return pkScriptCmp < 0
   111  	}
   112  
   113  	return s.cltvs[i] < s.cltvs[j]
   114  }