115 lines
3.7 KiB
Go
115 lines
3.7 KiB
Go
package lnwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"sort"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
)
|
|
|
|
// InPlaceCommitSort performs an in-place sort of a commitment transaction,
|
|
// given an unsorted transaction and a list of CLTV values for the HTLCs.
|
|
//
|
|
// The sort applied is a modified BIP69 sort, that uses the CLTV values of HTLCs
|
|
// as a tie breaker in case two HTLC outputs have an identical amount and
|
|
// pkscript. The pkscripts can be the same if they share the same payment hash,
|
|
// but since the CLTV is enforced via the nLockTime of the second-layer
|
|
// transactions, the script does not directly commit to them. Instead, the CLTVs
|
|
// must be supplied separately to act as a tie-breaker, otherwise we may produce
|
|
// invalid HTLC signatures if the receiver produces an alternative ordering
|
|
// during verification.
|
|
//
|
|
// NOTE: Commitment outputs should have a 0 CLTV corresponding to their index on
|
|
// the unsorted commitment transaction.
|
|
func InPlaceCommitSort(tx *wire.MsgTx, cltvs []uint32) {
|
|
if len(tx.TxOut) != len(cltvs) {
|
|
panic("output and cltv list size mismatch")
|
|
}
|
|
|
|
sort.Sort(sortableInputSlice(tx.TxIn))
|
|
sort.Sort(sortableCommitOutputSlice{tx.TxOut, cltvs})
|
|
}
|
|
|
|
// sortableInputSlice is a slice of transaction inputs that supports sorting via
|
|
// BIP69.
|
|
type sortableInputSlice []*wire.TxIn
|
|
|
|
// Len returns the length of the sortableInputSlice.
|
|
//
|
|
// NOTE: Part of the sort.Interface interface.
|
|
func (s sortableInputSlice) Len() int { return len(s) }
|
|
|
|
// Swap exchanges the position of inputs i and j.
|
|
//
|
|
// NOTE: Part of the sort.Interface interface.
|
|
func (s sortableInputSlice) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
// Less is the BIP69 input comparison function. The sort is first applied on
|
|
// input hash (reversed / rpc-style), then index. This logic is copied from
|
|
// btcutil/txsort.
|
|
//
|
|
// NOTE: Part of the sort.Interface interface.
|
|
func (s sortableInputSlice) Less(i, j int) bool {
|
|
// Input hashes are the same, so compare the index.
|
|
ihash := s[i].PreviousOutPoint.Hash
|
|
jhash := s[j].PreviousOutPoint.Hash
|
|
if ihash == jhash {
|
|
return s[i].PreviousOutPoint.Index < s[j].PreviousOutPoint.Index
|
|
}
|
|
|
|
// At this point, the hashes are not equal, so reverse them to
|
|
// big-endian and return the result of the comparison.
|
|
const hashSize = chainhash.HashSize
|
|
for b := 0; b < hashSize/2; b++ {
|
|
ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b]
|
|
jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b]
|
|
}
|
|
return bytes.Compare(ihash[:], jhash[:]) == -1
|
|
}
|
|
|
|
// sortableCommitOutputSlice is a slice of transaction outputs on a commitment
|
|
// transaction and the corresponding CLTV values of any HTLCs. Commitment
|
|
// outputs should have a CLTV of 0 and the same index in cltvs.
|
|
type sortableCommitOutputSlice struct {
|
|
txouts []*wire.TxOut
|
|
cltvs []uint32
|
|
}
|
|
|
|
// Len returns the length of the sortableCommitOutputSlice.
|
|
//
|
|
// NOTE: Part of the sort.Interface interface.
|
|
func (s sortableCommitOutputSlice) Len() int {
|
|
return len(s.txouts)
|
|
}
|
|
|
|
// Swap exchanges the position of outputs i and j, as well as their
|
|
// corresponding CLTV values.
|
|
//
|
|
// NOTE: Part of the sort.Interface interface.
|
|
func (s sortableCommitOutputSlice) Swap(i, j int) {
|
|
s.txouts[i], s.txouts[j] = s.txouts[j], s.txouts[i]
|
|
s.cltvs[i], s.cltvs[j] = s.cltvs[j], s.cltvs[i]
|
|
}
|
|
|
|
// Less is a modified BIP69 output comparison, that sorts based on value, then
|
|
// pkscript, then CLTV value.
|
|
//
|
|
// NOTE: Part of the sort.Interface interface.
|
|
func (s sortableCommitOutputSlice) Less(i, j int) bool {
|
|
outi, outj := s.txouts[i], s.txouts[j]
|
|
|
|
if outi.Value != outj.Value {
|
|
return outi.Value < outj.Value
|
|
}
|
|
|
|
pkScriptCmp := bytes.Compare(outi.PkScript, outj.PkScript)
|
|
if pkScriptCmp != 0 {
|
|
return pkScriptCmp < 0
|
|
}
|
|
|
|
return s.cltvs[i] < s.cltvs[j]
|
|
}
|