github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/qln/close.go (about) 1 package qln 2 3 import ( 4 "bytes" 5 "fmt" 6 "time" 7 8 "github.com/mit-dci/lit/btcutil/chaincfg/chainhash" 9 "github.com/mit-dci/lit/btcutil/txscript" 10 "github.com/mit-dci/lit/crypto/fastsha256" 11 "github.com/mit-dci/lit/crypto/koblitz" 12 "github.com/mit-dci/lit/lnutil" 13 "github.com/mit-dci/lit/logging" 14 "github.com/mit-dci/lit/portxo" 15 "github.com/mit-dci/lit/sig64" 16 "github.com/mit-dci/lit/wire" 17 ) 18 19 /* CloseChannel --- cooperative close 20 This is the simplified close which sends to the same outputs as a break tx, 21 just with no timeouts. 22 23 Users might want a more advanced close function which allows multiple outputs. 24 They can exchange txouts and sigs. That could be "fancyClose", but this is 25 just close, so only a signature is sent by the initiator, and the receiver 26 doesn't reply, as the channel is closed. 27 28 */ 29 30 // CoopClose requests a cooperative close of the channel 31 func (nd *LitNode) CoopClose(q *Qchan) error { 32 33 nd.RemoteMtx.Lock() 34 _, ok := nd.RemoteCons[q.Peer()] 35 nd.RemoteMtx.Unlock() 36 if !ok { 37 return fmt.Errorf("not connected to peer %d ", q.Peer()) 38 } 39 40 if q.CloseData.Closed { 41 return fmt.Errorf("can't close (%d,%d): already closed", 42 q.KeyGen.Step[3]&0x7fffffff, q.KeyGen.Step[4]&0x7fffffff) 43 } 44 45 for _, h := range q.State.HTLCs { 46 if !h.Cleared { 47 return fmt.Errorf("can't close (%d,%d): there are uncleared HTLCs", 48 q.KeyGen.Step[3]&0x7fffffff, q.KeyGen.Step[4]&0x7fffffff) 49 } 50 } 51 52 tx, err := q.SimpleCloseTx() 53 if err != nil { 54 return err 55 } 56 57 sig, err := nd.SignSimpleClose(q, tx) 58 if err != nil { 59 return err 60 } 61 62 // Save something, just so the UI marks it as closed, and 63 // we don't accept payments on this channel anymore. 64 65 // save channel state as closed. We know the txid... even though that 66 // txid may not actually happen. 67 nd.RemoteMtx.Lock() 68 q.LastUpdate = uint64(time.Now().UnixNano() / 1000) 69 q.CloseData.Closed = true 70 q.CloseData.CloseTxid = tx.TxHash() 71 nd.RemoteMtx.Unlock() 72 err = nd.SaveQchanUtxoData(q) 73 if err != nil { 74 return err 75 } 76 77 var signature [64]byte 78 copy(signature[:], sig[:]) 79 80 // Save something to db... TODO 81 // Should save something, just so the UI marks it as closed, and 82 // we don't accept payments on this channel anymore. 83 84 outMsg := lnutil.NewCloseReqMsg(q.Peer(), q.Op, signature) 85 nd.tmpSendLitMsg(outMsg) 86 87 return nil 88 } 89 90 // CloseReqHandler takes in a close request from a remote host, signs and 91 // responds with a close response. Obviously later there will be some judgment 92 // over what to do, but for now it just signs whatever it's requested to. 93 94 func (nd *LitNode) CloseReqHandler(msg lnutil.CloseReqMsg) { 95 opArr := lnutil.OutPointToBytes(msg.Outpoint) 96 97 // get channel 98 q, err := nd.GetQchan(opArr) 99 if err != nil { 100 logging.Errorf("CloseReqHandler GetQchan err %s", err.Error()) 101 return 102 } 103 104 if nd.SubWallet[q.Coin()] == nil { 105 logging.Errorf("Not connected to coin type %d\n", q.Coin()) 106 } 107 108 for _, h := range q.State.HTLCs { 109 if !h.Cleared { 110 logging.Errorf("can't close (%d,%d): there are uncleared HTLCs", 111 q.KeyGen.Step[3]&0x7fffffff, q.KeyGen.Step[4]&0x7fffffff) 112 return 113 } 114 } 115 116 // verify their sig? should do that before signing our side just to be safe 117 // TODO -- yeah we need to verify their sig 118 119 // build close tx 120 tx, err := q.SimpleCloseTx() 121 if err != nil { 122 logging.Errorf("CloseReqHandler SimpleCloseTx err %s", err.Error()) 123 return 124 } 125 126 hCache := txscript.NewTxSigHashes(tx) 127 128 pre, _, err := lnutil.FundTxScript(q.MyPub, q.TheirPub) 129 if err != nil { 130 logging.Errorf("CloseReqHandler Sig err %s", err.Error()) 131 return 132 } 133 134 parsed, err := txscript.ParseScript(pre) 135 if err != nil { 136 logging.Errorf("CloseReqHandler Sig err %s", err.Error()) 137 return 138 } 139 // always sighash all 140 hash := txscript.CalcWitnessSignatureHash( 141 parsed, hCache, txscript.SigHashAll, tx, 0, q.Value) 142 143 theirBigSig := sig64.SigDecompress(msg.Signature) 144 145 // sig is pre-truncated; last byte for sighashtype is always sighashAll 146 pSig, err := koblitz.ParseDERSignature(theirBigSig, koblitz.S256()) 147 if err != nil { 148 logging.Errorf("CloseReqHandler Sig err %s", err.Error()) 149 return 150 } 151 theirPubKey, err := koblitz.ParsePubKey(q.TheirPub[:], koblitz.S256()) 152 if err != nil { 153 logging.Errorf("CloseReqHandler Sig err %s", err.Error()) 154 return 155 } 156 157 worked := pSig.Verify(hash, theirPubKey) 158 if !worked { 159 logging.Errorf("CloseReqHandler Sig err invalid signature on close tx %s", err.Error()) 160 return 161 } 162 163 // sign close 164 mySig, err := nd.SignSimpleClose(q, tx) 165 if err != nil { 166 logging.Errorf("CloseReqHandler SignSimpleClose err %s", err.Error()) 167 return 168 } 169 170 myBigSig := sig64.SigDecompress(mySig) 171 172 // put the sighash all byte on the end of both signatures 173 myBigSig = append(myBigSig, byte(txscript.SigHashAll)) 174 theirBigSig = append(theirBigSig, byte(txscript.SigHashAll)) 175 176 pre, swap, err := lnutil.FundTxScript(q.MyPub, q.TheirPub) 177 if err != nil { 178 logging.Errorf("CloseReqHandler FundTxScript err %s", err.Error()) 179 return 180 } 181 182 // swap if needed 183 if swap { 184 tx.TxIn[0].Witness = SpendMultiSigWitStack(pre, theirBigSig, myBigSig) 185 } else { 186 tx.TxIn[0].Witness = SpendMultiSigWitStack(pre, myBigSig, theirBigSig) 187 } 188 logging.Info(lnutil.TxToString(tx)) 189 190 // save channel state to db as closed. 191 nd.RemoteMtx.Lock() 192 q.LastUpdate = uint64(time.Now().UnixNano() / 1000) 193 q.CloseData.Closed = true 194 q.CloseData.CloseTxid = tx.TxHash() 195 nd.RemoteMtx.Unlock() 196 err = nd.SaveQchanUtxoData(q) 197 if err != nil { 198 logging.Errorf("CloseReqHandler SaveQchanUtxoData err %s", err.Error()) 199 return 200 } 201 202 // broadcast 203 err = nd.SubWallet[q.Coin()].PushTx(tx) 204 if err != nil { 205 logging.Errorf("CloseReqHandler NewOutgoingTx err %s", err.Error()) 206 return 207 } 208 209 peerIdx := q.Peer() 210 peer := nd.PeerMan.GetPeerByIdx(int32(peerIdx)) 211 212 // Broadcast that we've closed a channel 213 closed := ChannelStateUpdateEvent{ 214 Action: "closed", 215 ChanIdx: q.Idx(), 216 State: q.State, 217 TheirPub: peer.GetPubkey(), 218 CoinType: q.Coin(), 219 } 220 221 if succeed, err := nd.Events.Publish(closed); err != nil { 222 logging.Errorf("ClosedHandler publish err %s", err) 223 return 224 } else if !succeed { 225 logging.Errorf("ClosedHandler publish did not succeed") 226 return 227 } 228 229 return 230 } 231 232 func (q *Qchan) GetHtlcTxosWithElkPointsAndRevPub(tx *wire.MsgTx, mine bool, theirElkPoint, myElkPoint, revPub [33]byte) ([]*wire.TxOut, []uint32, error) { 233 htlcOutsInTx := make([]*wire.TxOut, 0) 234 htlcOutIndexesInTx := make([]uint32, 0) 235 htlcOuts := make([]*wire.TxOut, 0) 236 for _, h := range q.State.HTLCs { 237 txOut, err := q.GenHTLCOutWithElkPointsAndRevPub(h, mine, theirElkPoint, myElkPoint, revPub) 238 if err != nil { 239 return nil, nil, err 240 } 241 htlcOuts = append(htlcOuts, txOut) 242 } 243 244 for i, out := range tx.TxOut { 245 htlcOut := false 246 for _, hOut := range htlcOuts { 247 if out.Value == hOut.Value && bytes.Equal(out.PkScript, hOut.PkScript) { 248 // This is an HTLC output 249 logging.Info("Found HTLC output at index %d", i) 250 htlcOut = true 251 break 252 } 253 } 254 if htlcOut { 255 htlcOutsInTx = append(htlcOutsInTx, out) 256 htlcOutIndexesInTx = append(htlcOutIndexesInTx, uint32(i)) 257 } 258 } 259 260 return htlcOutsInTx, htlcOutIndexesInTx, nil 261 } 262 263 func (q *Qchan) GetHtlcTxos(tx *wire.MsgTx, mine bool) ([]*wire.TxOut, []uint32, error) { 264 revPub, _, _, err := q.GetKeysFromState(mine) 265 if err != nil { 266 return nil, nil, err 267 } 268 269 curElk, err := q.ElkPoint(false, q.State.StateIdx) 270 if err != nil { 271 return nil, nil, err 272 } 273 274 return q.GetHtlcTxosWithElkPointsAndRevPub(tx, mine, q.State.ElkPoint, curElk, revPub) 275 } 276 277 // GetCloseTxos takes in a tx and sets the QcloseTXO fields based on the tx. 278 // It also returns the spendable (u)txos generated by the close. 279 // TODO way too long. Need to split up. 280 // TODO problem with collisions, and insufficiently high elk receiver...? 281 func (q *Qchan) GetCloseTxos(tx *wire.MsgTx) ([]portxo.PorTxo, error) { 282 if tx == nil { 283 return nil, fmt.Errorf("IngesGetCloseTxostCloseTx: nil tx") 284 } 285 txid := tx.TxHash() 286 // double check -- does this tx actually close the channel? 287 if !(len(tx.TxIn) == 1 && lnutil.OutPointsEqual(tx.TxIn[0].PreviousOutPoint, q.Op)) { 288 return nil, fmt.Errorf("tx %s doesn't spend channel outpoint %s", 289 txid.String(), q.Op.String()) 290 } 291 var shIdx, pkhIdx uint32 292 var pkhIsMine bool 293 cTxos := make([]portxo.PorTxo, 1) 294 myPKHPkSript := lnutil.DirectWPKHScript(q.MyRefundPub) 295 296 htlcOutsInTx, htlcOutIndexesInTx, err := q.GetHtlcTxos(tx, false) 297 if err != nil { 298 return nil, err 299 } 300 htlcOutsInOurTx, htlcOutIndexesInOurTx, err := q.GetHtlcTxos(tx, true) 301 if err != nil { 302 return nil, err 303 } 304 htlcOutsInTx = append(htlcOutsInTx, htlcOutsInOurTx...) 305 htlcOutIndexesInTx = append(htlcOutIndexesInTx, htlcOutIndexesInOurTx...) 306 307 shIdx = 999 // set high here to detect if there's no SH output 308 // Classify outputs. If output is an HTLC, do nothing, since there is a 309 // separate function for that 310 for i, out := range tx.TxOut { 311 if len(out.PkScript) == 34 { 312 htlcOut := false 313 for _, idx := range htlcOutIndexesInTx { 314 if uint32(i) == idx { 315 htlcOut = true 316 break 317 } 318 } 319 320 // There should be only one other script output other than HTLCs which 321 // is the closing script with timelock 322 if !htlcOut { 323 shIdx = uint32(i) 324 } 325 } else if bytes.Equal(myPKHPkSript, out.PkScript) { 326 pkhIdx = uint32(i) 327 pkhIsMine = true 328 } 329 } 330 331 // if pkh is mine, grab it. 332 if pkhIsMine { 333 logging.Info("got PKH output [%d] from channel close", pkhIdx) 334 var pkhTxo portxo.PorTxo // create new utxo and copy into it 335 336 pkhTxo.Op.Hash = txid 337 pkhTxo.Op.Index = pkhIdx 338 pkhTxo.Height = q.CloseData.CloseHeight 339 // keypath same, use different 340 pkhTxo.KeyGen = q.KeyGen 341 // same keygen as underlying channel, but use is refund 342 pkhTxo.KeyGen.Step[2] = UseChannelRefund 343 344 pkhTxo.Mode = portxo.TxoP2WPKHComp 345 pkhTxo.Value = tx.TxOut[pkhIdx].Value 346 // PKH, could omit this 347 pkhTxo.PkScript = tx.TxOut[pkhIdx].PkScript 348 cTxos[0] = pkhTxo 349 } 350 351 // get state hint based on pkh match. If pkh is mine, that's their TX & hint. 352 // if there's no PKH output for me, the TX is mine, so use my hint. 353 var comNum uint64 354 if pkhIsMine { 355 comNum = GetStateIdxFromTx(tx, q.GetChanHint(false)) 356 } else { 357 comNum = GetStateIdxFromTx(tx, q.GetChanHint(true)) 358 } 359 if comNum > q.State.StateIdx { // future state, uhoh. Crash for now. 360 logging.Info("indicated state %d but we know up to %d", 361 comNum, q.State.StateIdx) 362 return cTxos, nil 363 } 364 365 // if we didn't get the pkh, and the comNum is current, we get the SH output. 366 // also we probably closed ourselves. Regular timeout 367 if !pkhIsMine && shIdx < 999 && comNum != 0 && comNum == q.State.StateIdx { 368 theirElkPoint, err := q.ElkPoint(false, comNum) 369 if err != nil { 370 return nil, err 371 } 372 373 // build script to store in porTxo, make pubkeys 374 timeoutPub := lnutil.AddPubsEZ(q.MyHAKDBase, theirElkPoint) 375 revokePub := lnutil.CombinePubs(q.TheirHAKDBase, theirElkPoint) 376 377 script := lnutil.CommitScript(revokePub, timeoutPub, q.Delay) 378 // script check. redundant / just in case 379 genSH := fastsha256.Sum256(script) 380 if !bytes.Equal(genSH[:], tx.TxOut[shIdx].PkScript[2:34]) { 381 logging.Warnf("got different observed and generated SH scripts.\n") 382 logging.Warnf("in %s:%d, see %x\n", txid, shIdx, tx.TxOut[shIdx].PkScript) 383 logging.Warnf("generated %x \n", genSH) 384 logging.Warnf("revokable pub %x\ntimeout pub %x\n", revokePub, timeoutPub) 385 } 386 387 // create the ScriptHash, timeout portxo. 388 var shTxo portxo.PorTxo // create new utxo and copy into it 389 // use txidx's elkrem as it may not be most recent 390 elk, err := q.ElkSnd.AtIndex(comNum) 391 if err != nil { 392 return nil, err 393 } 394 // keypath is the same, except for use 395 shTxo.KeyGen = q.KeyGen 396 397 shTxo.Op.Hash = txid 398 shTxo.Op.Index = shIdx 399 shTxo.Height = q.CloseData.CloseHeight 400 401 shTxo.KeyGen.Step[2] = UseChannelHAKDBase 402 403 elkpoint := lnutil.ElkPointFromHash(elk) 404 addhash := chainhash.DoubleHashH(append(elkpoint[:], q.MyHAKDBase[:]...)) 405 406 shTxo.PrivKey = addhash 407 408 shTxo.Mode = portxo.TxoP2WSHComp 409 shTxo.Value = tx.TxOut[shIdx].Value 410 shTxo.Seq = uint32(q.Delay) 411 shTxo.PreSigStack = make([][]byte, 1) // revoke SH has one presig item 412 shTxo.PreSigStack[0] = nil // and that item is a nil (timeout) 413 414 shTxo.PkScript = script 415 cTxos[0] = shTxo 416 } 417 418 // if we got the pkh, and the comNum is too old, we can get the SH. Justice. 419 if pkhIsMine && comNum != 0 && comNum < q.State.StateIdx { 420 logging.Info("Executing Justice!") 421 422 // ---------- revoked SH is mine 423 // invalid previous state, can be grabbed! 424 // make MY elk points 425 myElkPoint, err := q.ElkPoint(true, comNum) 426 if err != nil { 427 return nil, err 428 } 429 430 theirElkPoint, err := q.ElkPoint(false, comNum) 431 if err != nil { 432 return nil, err 433 } 434 435 timeoutPub := lnutil.AddPubsEZ(q.TheirHAKDBase, myElkPoint) 436 revokePub := lnutil.CombinePubs(q.MyHAKDBase, myElkPoint) 437 script := lnutil.CommitScript(revokePub, timeoutPub, q.Delay) 438 439 htlcOutsInTx, htlcOutIndexesInTx, err := q.GetHtlcTxosWithElkPointsAndRevPub(tx, false, myElkPoint, theirElkPoint, revokePub) 440 if err != nil { 441 return nil, err 442 } 443 444 // Do this search again with the HTLC TXOs using the correct elkpoints 445 // There's probably a better solution for this. 446 447 shIdx = 999 // set high here to detect if there's no SH output 448 // Classify outputs. If output is an HTLC, do nothing, since there is a 449 // separate function for that 450 for i, out := range tx.TxOut { 451 if len(out.PkScript) == 34 { 452 htlcOut := false 453 for _, idx := range htlcOutIndexesInTx { 454 if uint32(i) == idx { 455 htlcOut = true 456 break 457 } 458 } 459 460 // There should be only one other script output other than HTLCs which 461 // is the closing script with timelock 462 if !htlcOut { 463 shIdx = uint32(i) 464 } 465 } 466 } 467 468 logging.Info("P2SH output from channel (non-HTLC output) is at %d", shIdx) 469 470 // script check 471 wshScript := lnutil.P2WSHify(script) 472 if !bytes.Equal(wshScript[:], tx.TxOut[shIdx].PkScript) { 473 logging.Warnf("got different observed and generated SH scripts.\n") 474 logging.Warnf("in %s:%d, see %x\n", txid, shIdx, tx.TxOut[shIdx].PkScript) 475 logging.Warnf("generated %x \n", wshScript) 476 logging.Warnf("revokable pub %x\ntimeout pub %x\n", revokePub, timeoutPub) 477 } 478 479 // myElkHashR added to HAKD private key 480 elk, err := q.ElkRcv.AtIndex(comNum) 481 if err != nil { 482 return nil, err 483 } 484 485 var shTxo portxo.PorTxo // create new utxo and copy into it 486 shTxo.KeyGen = q.KeyGen 487 shTxo.Op.Hash = txid 488 shTxo.Op.Index = shIdx 489 shTxo.Height = q.CloseData.CloseHeight 490 491 shTxo.KeyGen.Step[2] = UseChannelHAKDBase 492 493 shTxo.PrivKey = lnutil.ElkScalar(elk) 494 495 // just return the elkScalar and let 496 // something modify it before export due to the seq=1 flag. 497 498 shTxo.PkScript = script 499 shTxo.Value = tx.TxOut[shIdx].Value 500 shTxo.Mode = portxo.TxoP2WSHComp 501 shTxo.Seq = 1 // 1 means grab immediately 502 shTxo.PreSigStack = make([][]byte, 1) // timeout SH has one presig item 503 shTxo.PreSigStack[0] = []byte{0x01} // and that item is a 1 (justice) 504 cTxos = append(cTxos, shTxo) 505 506 logging.Info("There are %d HTLC Outs to do justice on in this transaction\n", len(htlcOutsInTx)) 507 // Also grab HTLCs. They are mine now too :) 508 for i, txo := range htlcOutsInTx { 509 logging.Info("Executing Justice on HTLC TXO!") 510 511 // script check 512 htlcScript, err := q.GenHTLCScriptWithElkPointsAndRevPub(q.State.HTLCs[i], false, theirElkPoint, myElkPoint, revokePub) 513 if err != nil { 514 return nil, err 515 } 516 wshHTLCScript := lnutil.P2WSHify(htlcScript) 517 518 if !bytes.Equal(wshHTLCScript[:], txo.PkScript) { 519 logging.Warnf("got different observed and generated HTLC scripts.\n") 520 logging.Warnf("in %s:%d, see %x\n", txid, htlcOutIndexesInTx[i], txo.PkScript) 521 logging.Warnf("generated %x \n", wshHTLCScript) 522 logging.Warnf("revokable pub %x\ntimeout pub %x\n", revokePub, timeoutPub) 523 } 524 525 var htlcTxo portxo.PorTxo // create new utxo and copy into it 526 htlcTxo.KeyGen = q.KeyGen 527 htlcTxo.Op.Hash = txid 528 htlcTxo.Op.Index = htlcOutIndexesInTx[i] 529 htlcTxo.Height = q.CloseData.CloseHeight 530 531 htlcTxo.KeyGen.Step[2] = UseChannelHAKDBase 532 533 htlcTxo.PrivKey = lnutil.ElkScalar(elk) 534 535 // just return the elkScalar and let 536 // something modify it before export due to the seq=1 flag. 537 538 htlcTxo.PkScript = script 539 htlcTxo.Value = txo.Value 540 htlcTxo.Mode = portxo.TxoP2WSHComp 541 htlcTxo.Seq = 1 // 1 means grab immediately 542 htlcTxo.PreSigStack = make([][]byte, 1) // timeout SH has one presig item 543 htlcTxo.PreSigStack[0] = []byte{0x01} // and that item is a 1 (justice) 544 cTxos = append(cTxos, htlcTxo) 545 } 546 } 547 logging.Info("Returning [%d] cTxos", len(cTxos)) 548 return cTxos, nil 549 }