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 }