github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/qln/buildtx.go (about)

     1  package qln
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	"github.com/mit-dci/lit/logging"
     8  
     9  	"github.com/mit-dci/lit/btcutil"
    10  	"github.com/mit-dci/lit/consts"
    11  	"github.com/mit-dci/lit/lnutil"
    12  
    13  	"github.com/mit-dci/lit/btcutil/txsort"
    14  	"github.com/mit-dci/lit/wire"
    15  )
    16  
    17  // GetStateIdxFromTx returns the state index from a commitment transaction.
    18  // No errors; returns 0 if there is no retrievable index.
    19  // Takes the xor input X which is derived from the 0th elkrems.
    20  func GetStateIdxFromTx(tx *wire.MsgTx, x uint64) uint64 {
    21  	// no tx, so no index
    22  	if tx == nil {
    23  		return 0
    24  	}
    25  	// more than 1 input, so not a close tx
    26  	if len(tx.TxIn) != 1 {
    27  		return 0
    28  	}
    29  	// mask need two high bytes of 0s
    30  	if x > 1<<48 {
    31  		return 0
    32  	}
    33  	// check that indicating high bytes are correct.  If not, return 0
    34  	if tx.TxIn[0].Sequence>>24 != 0xff || tx.LockTime>>24 != 0x21 {
    35  		//		logging.Infof("sequence byte %x, locktime byte %x\n",
    36  		//			tx.TxIn[0].Sequence>>24, tx.LockTime>>24 != 0x21)
    37  		return 0
    38  	}
    39  	// high 24 bits sequence, low 24 bits locktime
    40  	seqBits := uint64(tx.TxIn[0].Sequence & 0x00ffffff)
    41  	timeBits := uint64(tx.LockTime & 0x00ffffff)
    42  
    43  	return (seqBits<<24 | timeBits) ^ x
    44  }
    45  
    46  // SetStateIdxBits modifies the tx in place, setting the sequence and locktime
    47  // fields to indicate the given state index.
    48  func SetStateIdxBits(tx *wire.MsgTx, idx, x uint64) error {
    49  	if tx == nil {
    50  		return fmt.Errorf("SetStateIdxBits: nil tx")
    51  	}
    52  	if len(tx.TxIn) != 1 {
    53  		return fmt.Errorf("SetStateIdxBits: tx has %d inputs", len(tx.TxIn))
    54  	}
    55  	if idx >= 1<<48 {
    56  		return fmt.Errorf(
    57  			"SetStateIdxBits: index %d greater than max %d", idx, uint64(1<<48)-1)
    58  	}
    59  
    60  	idx = idx ^ x
    61  	// high 24 bits sequence, low 24 bits locktime
    62  	seqBits := uint32(idx >> 24)
    63  	timeBits := uint32(idx & 0x00ffffff)
    64  
    65  	tx.TxIn[0].Sequence = seqBits | seqMask
    66  	tx.LockTime = timeBits | timeMask
    67  
    68  	return nil
    69  }
    70  
    71  // SimpleCloseTx produces a close tx based on the current state.
    72  // The PKH addresses are my refund base with their r-elkrem point, and
    73  // their refund base with my r-elkrem point.  "Their" point means they have
    74  // the point but not the scalar.
    75  func (q *Qchan) SimpleCloseTx() (*wire.MsgTx, error) {
    76  	// sanity checks
    77  	if q == nil || q.State == nil {
    78  		return nil, fmt.Errorf("SimpleCloseTx: nil chan / state")
    79  	}
    80  
    81  	fee := q.State.Fee // symmetric fee
    82  
    83  	// make my output
    84  	myScript := lnutil.DirectWPKHScript(q.MyRefundPub)
    85  	var myAmt int64
    86  	var myOutput *wire.TxOut
    87  	if q.State.MyAmt != 0 {
    88  		myAmt = q.State.MyAmt - fee
    89  		myOutput = wire.NewTxOut(myAmt, myScript)
    90  	}
    91  	// make their output
    92  	theirScript := lnutil.DirectWPKHScript(q.TheirRefundPub)
    93  	var theirAmt int64
    94  	var theirOutput *wire.TxOut
    95  	if q.Value-q.State.MyAmt != 0 {
    96  		theirAmt = (q.Value - q.State.MyAmt) - fee
    97  		theirOutput = wire.NewTxOut(theirAmt, theirScript)
    98  	}
    99  
   100  	if myAmt == 0 && theirAmt == 0 {
   101  		return nil, fmt.Errorf("SimpleCloseTx: both outputs cannot be 0")
   102  	}
   103  
   104  	// check output amounts (should never fail)
   105  	if myAmt != 0 && myAmt < consts.MinOutput {
   106  		return nil, fmt.Errorf("SimpleCloseTx: my output amt %d too low", myAmt)
   107  	}
   108  	if theirAmt != 0 && theirAmt < consts.MinOutput {
   109  		return nil, fmt.Errorf("SimpleCloseTx: their output amt %d too low", theirAmt)
   110  	}
   111  
   112  	tx := wire.NewMsgTx()
   113  
   114  	// make tx with these outputs
   115  	if myAmt != 0 {
   116  		tx.AddTxOut(myOutput)
   117  	}
   118  	if theirAmt != 0 {
   119  		tx.AddTxOut(theirOutput)
   120  	}
   121  	// add channel outpoint as txin
   122  	tx.AddTxIn(wire.NewTxIn(&q.Op, nil, nil))
   123  	// sort and return
   124  	txsort.InPlaceSort(tx)
   125  	return tx, nil
   126  }
   127  
   128  // BuildStateTxs constructs and returns a state commitment tx and a list of HTLC
   129  // success/failure txs.  As simple as I can make it.
   130  // This func just makes the tx with data from State in ram, and HAKD key arg
   131  func (q *Qchan) BuildStateTxs(mine bool) (*wire.MsgTx, []*wire.MsgTx, []*wire.TxOut, error) {
   132  	if q == nil {
   133  		return nil, nil, nil, fmt.Errorf("BuildStateTx: nil chan")
   134  	}
   135  	// sanity checks
   136  	s := q.State // use it a lot, make shorthand variable
   137  	if s == nil {
   138  		return nil, nil, nil, fmt.Errorf("channel (%d,%d) has no state", q.KeyGen.Step[3], q.KeyGen.Step[4])
   139  	}
   140  
   141  	var fancyAmt, pkhAmt, theirAmt int64 // output amounts
   142  
   143  	revPub, timePub, pkhPub, err := q.GetKeysFromState(mine)
   144  	if err != nil {
   145  		return nil, nil, nil, err
   146  	}
   147  
   148  	var revPKH [20]byte
   149  	revPKHSlice := btcutil.Hash160(revPub[:])
   150  	copy(revPKH[:], revPKHSlice[:20])
   151  
   152  	fee := s.Fee // fixed fee for now
   153  
   154  	value := q.Value
   155  
   156  	if s.InProgHTLC != nil {
   157  		value -= s.InProgHTLC.Amt
   158  	}
   159  
   160  	if s.CollidingHTLC != nil {
   161  		value -= s.CollidingHTLC.Amt
   162  	}
   163  
   164  	for _, h := range s.HTLCs {
   165  		if !h.Cleared && !h.Clearing {
   166  			value -= h.Amt
   167  		}
   168  	}
   169  
   170  	theirAmt = value - s.MyAmt
   171  
   172  	logging.Infof("Value: %d, MyAmt: %d, TheirAmt: %d", value, s.MyAmt, theirAmt)
   173  
   174  	// the PKH clear refund also has elkrem points added to mask the PKH.
   175  	// this changes the txouts at each state to blind sorcerer better.
   176  	if mine { // build MY tx (to verify) (unless breaking)
   177  		// nonzero amts means build the output
   178  		if theirAmt > 0 {
   179  			pkhAmt = theirAmt - fee
   180  		}
   181  		if s.MyAmt > 0 {
   182  			fancyAmt = s.MyAmt - fee
   183  		}
   184  	} else { // build THEIR tx (to sign)
   185  		// nonzero amts means build the output
   186  		if theirAmt > 0 {
   187  			fancyAmt = theirAmt - fee
   188  		}
   189  		if s.MyAmt > 0 {
   190  			pkhAmt = s.MyAmt - fee
   191  		}
   192  	}
   193  
   194  	// check amounts.  Nonzero amounts below the minOutput is an error.
   195  	// Shouldn't happen and means some checks in push/pull went wrong.
   196  	if fancyAmt != 0 && fancyAmt < consts.MinOutput {
   197  		return nil, nil, nil, fmt.Errorf("SH amt %d too low", fancyAmt)
   198  	}
   199  	if pkhAmt != 0 && pkhAmt < consts.MinOutput {
   200  		return nil, nil, nil, fmt.Errorf("PKH amt %d too low", pkhAmt)
   201  	}
   202  
   203  	// now that everything is chosen, build fancy script and pkh script
   204  	fancyScript := lnutil.CommitScript(revPub, timePub, q.Delay)
   205  	pkhScript := lnutil.DirectWPKHScript(pkhPub) // p2wpkh-ify
   206  
   207  	logging.Infof("> made SH script, state %d\n", s.StateIdx)
   208  	logging.Infof("\t revPub %x timeout pub %x \n", revPub, timePub)
   209  	logging.Infof("\t script %x ", fancyScript)
   210  
   211  	fancyScript = lnutil.P2WSHify(fancyScript) // p2wsh-ify
   212  
   213  	logging.Infof("\t scripthash %x\n", fancyScript)
   214  
   215  	// create txouts by assigning amounts
   216  	outFancy := wire.NewTxOut(fancyAmt, fancyScript)
   217  	outPKH := wire.NewTxOut(pkhAmt, pkhScript)
   218  
   219  	logging.Infof("\tcombined refund %x, pkh %x, amt %d\n", pkhPub, outPKH.PkScript, pkhAmt)
   220  
   221  	var HTLCTxOuts []*wire.TxOut
   222  
   223  	// Generate new HTLC signatures
   224  	for _, h := range s.HTLCs {
   225  		if !h.Clearing && !h.Cleared {
   226  			HTLCOut, err := q.GenHTLCOut(h, mine)
   227  			if err != nil {
   228  				return nil, nil, nil, err
   229  			}
   230  			HTLCTxOuts = append(HTLCTxOuts, HTLCOut)
   231  		}
   232  	}
   233  
   234  	// There's an HTLC in progress
   235  	if s.InProgHTLC != nil {
   236  		HTLCOut, err := q.GenHTLCOut(*s.InProgHTLC, mine)
   237  		if err != nil {
   238  			return nil, nil, nil, err
   239  		}
   240  		HTLCTxOuts = append(HTLCTxOuts, HTLCOut)
   241  	}
   242  
   243  	// There's an colliding HTLC in progress
   244  	if s.CollidingHTLC != nil {
   245  		HTLCOut, err := q.GenHTLCOut(*s.CollidingHTLC, mine)
   246  		if err != nil {
   247  			return nil, nil, nil, err
   248  		}
   249  		HTLCTxOuts = append(HTLCTxOuts, HTLCOut)
   250  	}
   251  
   252  	// make a new tx
   253  	tx := wire.NewMsgTx()
   254  	// add txouts
   255  	if fancyAmt != 0 {
   256  		tx.AddTxOut(outFancy)
   257  	}
   258  	if pkhAmt != 0 {
   259  		tx.AddTxOut(outPKH)
   260  	}
   261  
   262  	// Add HTLC outputs
   263  	for _, out := range HTLCTxOuts {
   264  		tx.AddTxOut(out)
   265  	}
   266  
   267  	if len(tx.TxOut) < 1 {
   268  		return nil, nil, nil, fmt.Errorf("No outputs, all below minOutput")
   269  	}
   270  
   271  	// add unsigned txin
   272  	tx.AddTxIn(wire.NewTxIn(&q.Op, nil, nil))
   273  	// set index hints
   274  
   275  	// state 0 and 1 can't use mask?  Think they can now.
   276  	SetStateIdxBits(tx, s.StateIdx, q.GetChanHint(mine))
   277  
   278  	// sort outputs
   279  	txsort.InPlaceSort(tx)
   280  
   281  	txHash := tx.TxHash()
   282  
   283  	HTLCSpends := map[int]*wire.MsgTx{}
   284  
   285  	for j, h := range HTLCTxOuts {
   286  		amt := h.Value - fee
   287  		if amt < consts.MinOutput {
   288  			return nil, nil, nil, fmt.Errorf("HTLC amt %d too low (fee is %d)", amt, fee)
   289  		}
   290  
   291  		// But now they're sorted how do I know which outpoint to spend?
   292  		// We can iterate over our HTLC list, then compare pkScripts to find
   293  		// the right one
   294  		// Which index is this HTLC output in the tx?
   295  		var idx int
   296  		for i, out := range tx.TxOut {
   297  			if bytes.Compare(out.PkScript, h.PkScript) == 0 {
   298  				idx = i
   299  				break
   300  			}
   301  		}
   302  
   303  		spendHTLCScript := lnutil.CommitScript(revPub, timePub, q.Delay)
   304  
   305  		HTLCSpend := wire.NewMsgTx()
   306  
   307  		HTLCOp := wire.NewOutPoint(&txHash, uint32(idx))
   308  
   309  		in := wire.NewTxIn(HTLCOp, nil, nil)
   310  		in.Sequence = 0
   311  
   312  		HTLCSpend.AddTxIn(in)
   313  		HTLCSpend.AddTxOut(wire.NewTxOut(amt, lnutil.P2WSHify(spendHTLCScript)))
   314  
   315  		HTLCSpend.Version = 2
   316  
   317  		/*
   318  			!incoming & mine: my TX that they sign (HTLC-timeout)
   319  			!incoming & !mine: their TX that I sign (HTLC-success)
   320  			incoming & mine: my TX that they sign (HTLC-success)
   321  			incoming & !mine: their TX that I sign (HTLC-timeout)
   322  		*/
   323  
   324  		var success bool
   325  		var lt uint32
   326  
   327  		if j == len(s.HTLCs) {
   328  			success = s.InProgHTLC.Incoming == mine
   329  			lt = s.InProgHTLC.Locktime
   330  		} else if j == len(s.HTLCs)+1 {
   331  			success = s.CollidingHTLC.Incoming == mine
   332  			lt = s.CollidingHTLC.Locktime
   333  		} else {
   334  			success = s.HTLCs[j].Incoming == mine
   335  			lt = s.HTLCs[j].Locktime
   336  		}
   337  
   338  		if success {
   339  			// HTLC-success
   340  			HTLCSpend.LockTime = 0
   341  		} else {
   342  			// HTLC-failure
   343  			HTLCSpend.LockTime = lt
   344  		}
   345  
   346  		HTLCSpends[idx] = HTLCSpend
   347  	}
   348  
   349  	var HTLCSpendsArr []*wire.MsgTx
   350  
   351  	for i := 0; i < len(HTLCSpends)+2; i++ {
   352  		if s, ok := HTLCSpends[i]; ok {
   353  			HTLCSpendsArr = append(HTLCSpendsArr, s)
   354  		}
   355  	}
   356  
   357  	return tx, HTLCSpendsArr, HTLCTxOuts, nil
   358  }
   359  
   360  func (q *Qchan) GenHTLCScriptWithElkPointsAndRevPub(h HTLC, mine bool, theirElkPoint, myElkPoint, revPub [33]byte) ([]byte, error) {
   361  	var remotePub, localPub [33]byte
   362  
   363  	revPKHSlice := btcutil.Hash160(revPub[:])
   364  	var revPKH [20]byte
   365  	copy(revPKH[:], revPKHSlice[:20])
   366  
   367  	if mine { // Generating OUR tx that WE save
   368  		remotePub = lnutil.CombinePubs(h.TheirHTLCBase, theirElkPoint)
   369  		localPub = lnutil.CombinePubs(h.MyHTLCBase, myElkPoint)
   370  	} else { // Generating THEIR tx that THEY save
   371  		remotePub = lnutil.CombinePubs(h.MyHTLCBase, myElkPoint)
   372  		localPub = lnutil.CombinePubs(h.TheirHTLCBase, theirElkPoint)
   373  	}
   374  
   375  	var HTLCScript []byte
   376  
   377  	/*
   378  		incoming && mine = Receive
   379  		incoming && !mine = Offer
   380  		!incoming && mine = Offer
   381  		!incoming && !mine = Receive
   382  	*/
   383  	if h.Incoming != mine {
   384  		HTLCScript = lnutil.OfferHTLCScript(revPKH,
   385  			remotePub, h.RHash, localPub)
   386  	} else {
   387  		HTLCScript = lnutil.ReceiveHTLCScript(revPKH,
   388  			remotePub, h.RHash, localPub, h.Locktime)
   389  	}
   390  
   391  	logging.Infof("HTLC %d, script: %x, myBase: %x, theirBase: %x, Incoming: %t, Amt: %d, RHash: %x",
   392  		h.Idx, HTLCScript, h.MyHTLCBase, h.TheirHTLCBase, h.Incoming, h.Amt, h.RHash)
   393  
   394  	return HTLCScript, nil
   395  
   396  }
   397  
   398  func (q *Qchan) GenHTLCScript(h HTLC, mine bool) ([]byte, error) {
   399  
   400  	revPub, _, _, err := q.GetKeysFromState(mine)
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  
   405  	curElk, err := q.ElkPoint(false, q.State.StateIdx)
   406  	if err != nil {
   407  		return nil, err
   408  	}
   409  	return q.GenHTLCScriptWithElkPointsAndRevPub(h, mine, q.State.ElkPoint, curElk, revPub)
   410  }
   411  
   412  func (q *Qchan) GenHTLCOutWithElkPointsAndRevPub(h HTLC, mine bool, theirElkPoint, myElkPoint, revPub [33]byte) (*wire.TxOut, error) {
   413  	HTLCScript, err := q.GenHTLCScriptWithElkPointsAndRevPub(h, mine, theirElkPoint, myElkPoint, revPub)
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  
   418  	witScript := lnutil.P2WSHify(HTLCScript)
   419  
   420  	HTLCOut := wire.NewTxOut(h.Amt, witScript)
   421  
   422  	return HTLCOut, nil
   423  }
   424  
   425  func (q *Qchan) GenHTLCOut(h HTLC, mine bool) (*wire.TxOut, error) {
   426  	revPub, _, _, err := q.GetKeysFromState(mine)
   427  	if err != nil {
   428  		return nil, err
   429  	}
   430  
   431  	curElk, err := q.ElkPoint(false, q.State.StateIdx)
   432  	if err != nil {
   433  		return nil, err
   434  	}
   435  
   436  	return q.GenHTLCOutWithElkPointsAndRevPub(h, mine, q.State.ElkPoint, curElk, revPub)
   437  }
   438  
   439  // GetKeysFromState will inspect the channel state and return the revPub, timePub and pkhPub based on
   440  // whether we're building our own or the remote transaction.
   441  func (q *Qchan) GetKeysFromState(mine bool) (revPub, timePub, pkhPub [33]byte, err error) {
   442  
   443  	// the PKH clear refund also has elkrem points added to mask the PKH.
   444  	// this changes the txouts at each state to blind sorcerer better.
   445  	if mine { // build MY tx (to verify) (unless breaking)
   446  		var curElk [33]byte
   447  		// My tx that I store.  They get funds unencumbered. SH is mine eventually
   448  		// SH pubkeys are base points combined with the elk point we give them
   449  		// Create latest elkrem point (the one I create)
   450  		curElk, err = q.ElkPoint(false, q.State.StateIdx)
   451  		if err != nil {
   452  			return
   453  		}
   454  		revPub = lnutil.CombinePubs(q.TheirHAKDBase, curElk)
   455  		timePub = lnutil.AddPubsEZ(q.MyHAKDBase, curElk)
   456  
   457  		pkhPub = q.TheirRefundPub
   458  
   459  	} else { // build THEIR tx (to sign)
   460  		// Their tx that they store.  I get funds PKH.  SH is theirs eventually.
   461  		logging.Infof("using elkpoint %x\n", q.State.ElkPoint)
   462  		// SH pubkeys are our base points plus the received elk point
   463  		revPub = lnutil.CombinePubs(q.MyHAKDBase, q.State.ElkPoint)
   464  		timePub = lnutil.AddPubsEZ(q.TheirHAKDBase, q.State.ElkPoint)
   465  		// PKH output
   466  		pkhPub = q.MyRefundPub
   467  	}
   468  
   469  	return
   470  }
   471  
   472  // the scriptsig to put on a P2SH input.  Sigs need to be in order!
   473  func SpendMultiSigWitStack(pre, sigA, sigB []byte) [][]byte {
   474  
   475  	witStack := make([][]byte, 4)
   476  
   477  	witStack[0] = nil // it's not an OP_0 !!!! argh!
   478  	witStack[1] = sigA
   479  	witStack[2] = sigB
   480  	witStack[3] = pre
   481  
   482  	return witStack
   483  }