github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/qln/dualfund.go (about) 1 package qln 2 3 import ( 4 "fmt" 5 "github.com/mit-dci/lit/btcutil/txsort" 6 "github.com/mit-dci/lit/consts" 7 "github.com/mit-dci/lit/crypto/koblitz" 8 "github.com/mit-dci/lit/elkrem" 9 "github.com/mit-dci/lit/lnutil" 10 "github.com/mit-dci/lit/logging" 11 "github.com/mit-dci/lit/portxo" 12 "github.com/mit-dci/lit/wire" 13 ) 14 15 const ( 16 DUALFUND_DECLINE_REASON_USER = 0x01 // User manually declined 17 DUALFUND_DECLINE_REASON_INSUFFICIENT_BALANCE = 0x02 // Not enough balance to accept the request 18 DUALFUND_DECLINE_REASON_UNSUPPORTED_COIN = 0x03 // We don't have that coin type, so we declined automatically 19 DUALFUND_DECLINE_REASON_ALREADY_PENDING_REQUEST = 0x04 // Current design only supports single dualfund request at a time. So decline when we have one in progress. 20 21 ) 22 23 /* Dual funding process. 24 25 Allows a peer to initiate funding with its counterparty, requesting that party 26 to also commit funds into the channel. 27 28 Parameters: 29 30 Peer : The peer index to dual fund with 31 MyAmount : The amount i am funding myself 32 TheirAmount : The amount we request the peer to fund 33 34 Initial send and data seems unnecessary here, since both parties are funding, 35 neither is sending the other side money (the initial state will be equal 36 to the amounts funded) 37 38 The process that's followed: 39 40 A -> B Request funding UTXOs and change address for TheirAmount 41 42 B -> A Respond with UTXOs and change address for TheirAmount 43 (or) B -> A Respond with a decline 44 45 A -> B Respond with UTXOs and change address for MyAmount 46 47 A and B Compose the funding TX 48 49 Then (regular funding messages): 50 A -> B Requests channel point and refund pubkey, sending along its own 51 52 A channel point (33) (channel pubkey for now) 53 A refund (33) 54 55 B -> A Replies with channel point and refund pubkey 56 57 B channel point (32) (channel pubkey for now) 58 B refund (33) 59 60 A -> B Sends channel Description: 61 --- 62 outpoint (36) 63 capacity (8) 64 initial push (8) 65 B's HAKD pub #1 (33) 66 signature (~70) 67 --- 68 69 B -> A Acknowledges Channel: 70 --- 71 A's HAKD pub #1 (33) 72 signature (~70) 73 --- 74 75 Then (dual funding specific, exchange of signatures for the funding TX): 76 77 A -> B Requests signatures for the funding TX, sending along its own 78 79 B -> A Sends signatures for the funding TX 80 81 A Publishes funding TX 82 83 === time passes, fund tx gets in a block === 84 85 A -> B SigProof 86 SPV proof of the outpoint (block height, tree depth, tx index, hashes) 87 signature (~70) 88 89 */ 90 91 // DualFundChannel requests a peer to do dual funding. The remote peer can decline. 92 93 func (nd *LitNode) DualFundChannel( 94 peerIdx, cointype uint32, ourAmount int64, theirAmount int64) (*DualFundingResult, error) { 95 96 nullFundingResult := new(DualFundingResult) 97 nullFundingResult.Error = true 98 99 wal, ok := nd.SubWallet[cointype] 100 if !ok { 101 return nullFundingResult, fmt.Errorf("No wallet of type %d connected", cointype) 102 } 103 104 changeAddr, err := wal.NewAdr() 105 if err != nil { 106 return nullFundingResult, err 107 } 108 109 nd.InProgDual.mtx.Lock() 110 // defer nd.InProg.mtx.Lock() 111 if nd.InProgDual.PeerIdx != 0 { 112 nd.InProgDual.mtx.Unlock() 113 return nullFundingResult, fmt.Errorf("dual fund with peer %d not done yet", nd.InProgDual.PeerIdx) 114 } 115 116 if theirAmount <= 0 || ourAmount <= 0 { 117 nd.InProgDual.mtx.Unlock() 118 return nullFundingResult, fmt.Errorf("dual funding requires both parties to commit funds") 119 } 120 121 if theirAmount+ourAmount < consts.MinChanCapacity { // limit for now 122 nd.InProgDual.mtx.Unlock() 123 return nullFundingResult, fmt.Errorf("Min channel capacity 1M sat") 124 } 125 126 // TODO - would be convenient if it auto connected to the peer huh 127 if !nd.ConnectedToPeer(peerIdx) { 128 nd.InProgDual.mtx.Unlock() 129 return nullFundingResult, fmt.Errorf("Not connected to peer %d. Do that yourself.", peerIdx) 130 } 131 132 cIdx, err := nd.NextChannelIdx() 133 if err != nil { 134 nd.InProgDual.mtx.Unlock() 135 return nullFundingResult, err 136 } 137 138 // Find UTXOs to use 139 utxos, _, err := wal.PickUtxos(ourAmount, consts.DualFundFee, wal.Fee(), true) //TODO Fee calculation 140 if err != nil { 141 return nil, err 142 } 143 144 ourInputs := make([]lnutil.DualFundingInput, len(utxos)) 145 for i := 0; i < len(utxos); i++ { 146 ourInputs[i] = lnutil.DualFundingInput{Outpoint: utxos[i].Op, Value: utxos[i].Value} 147 } 148 149 var kg portxo.KeyGen 150 kg.Depth = 5 151 kg.Step[0] = 44 | 1<<31 152 kg.Step[1] = cointype | 1<<31 153 kg.Step[2] = UseChannelFund 154 kg.Step[3] = peerIdx | 1<<31 155 kg.Step[4] = cIdx | 1<<31 156 157 myChanPub, _ := nd.GetUsePub(kg, UseChannelFund) 158 myRefundPub, _ := nd.GetUsePub(kg, UseChannelRefund) 159 myHAKDbase, err := nd.GetUsePub(kg, UseChannelHAKDBase) 160 if err != nil { 161 return nil, err 162 } 163 164 nd.InProgDual.ChanIdx = cIdx 165 nd.InProgDual.PeerIdx = peerIdx 166 nd.InProgDual.CoinType = cointype 167 nd.InProgDual.OurAmount = ourAmount 168 nd.InProgDual.TheirAmount = theirAmount 169 nd.InProgDual.OurChangeAddress = changeAddr 170 nd.InProgDual.InitiatedByUs = true 171 nd.InProgDual.OurPub = myChanPub 172 nd.InProgDual.OurRefundPub = myRefundPub 173 nd.InProgDual.OurHAKDBase = myHAKDbase 174 nd.InProgDual.OurInputs = ourInputs 175 nd.InProgDual.mtx.Unlock() // switch to defer 176 177 outMsg := lnutil.NewDualFundingReqMsg(peerIdx, cointype, ourAmount, theirAmount, myChanPub, myRefundPub, myHAKDbase, changeAddr, ourInputs) 178 179 nd.tmpSendLitMsg(outMsg) 180 181 // wait until it's done! 182 result := <-nd.InProgDual.done 183 184 return result, nil 185 } 186 187 // Declines the in progress (received) dual funding request 188 func (nd *LitNode) DualFundDecline(reason uint8) { 189 outMsg := lnutil.NewDualFundingDeclMsg(nd.InProgDual.PeerIdx, reason) 190 nd.tmpSendLitMsg(outMsg) 191 nd.InProgDual.mtx.Lock() 192 nd.InProgDual.Clear() 193 nd.InProgDual.mtx.Unlock() 194 return 195 } 196 197 func (nd *LitNode) DualFundAccept() *DualFundingResult { 198 wal, ok := nd.SubWallet[nd.InProgDual.CoinType] 199 if !ok { 200 logging.Errorf("DualFundingReqHandler err no wallet for type %d", nd.InProgDual.CoinType) 201 return nil 202 } 203 204 changeAddr, err := wal.NewAdr() 205 if err != nil { 206 logging.Errorf("Error creating change address: %s", err.Error()) 207 return nil 208 } 209 210 // Find UTXOs to use 211 utxos, _, err := wal.PickUtxos(nd.InProgDual.OurAmount, 500, wal.Fee(), true) //TODO Fee calculation 212 if err != nil { 213 logging.Errorf("Error fetching UTXOs to use for funding: %s", err.Error()) 214 return nil 215 } 216 217 ourInputs := make([]lnutil.DualFundingInput, len(utxos)) 218 for i := 0; i < len(utxos); i++ { 219 ourInputs[i] = lnutil.DualFundingInput{Outpoint: utxos[i].Op, Value: utxos[i].Value} 220 } 221 222 var kg portxo.KeyGen 223 kg.Depth = 5 224 kg.Step[0] = 44 | 1<<31 225 kg.Step[1] = nd.InProgDual.CoinType | 1<<31 226 kg.Step[2] = UseChannelFund 227 kg.Step[3] = nd.InProgDual.PeerIdx | 1<<31 228 kg.Step[4] = nd.InProgDual.ChanIdx | 1<<31 229 230 myChanPub, _ := nd.GetUsePub(kg, UseChannelFund) 231 myRefundPub, _ := nd.GetUsePub(kg, UseChannelRefund) 232 myHAKDbase, err := nd.GetUsePub(kg, UseChannelHAKDBase) 233 if err != nil { 234 logging.Errorf("Error fetching UTXOs to use for funding: %s", err.Error()) 235 return nil 236 } 237 238 var keyGen portxo.KeyGen 239 keyGen.Depth = 5 240 keyGen.Step[0] = 44 | 1<<31 241 keyGen.Step[1] = nd.InProgDual.CoinType | 1<<31 242 keyGen.Step[2] = UseHTLCBase 243 keyGen.Step[3] = 0 | 1<<31 244 keyGen.Step[4] = nd.InProgDual.ChanIdx | 1<<31 245 246 MyNextHTLCBase, err := nd.GetUsePub(keyGen, UseHTLCBase) 247 if err != nil { 248 logging.Errorf("error generating NextHTLCBase %v", err) 249 return nil 250 } 251 252 keyGen.Step[3] = 1 | 1<<31 253 MyN2HTLCBase, err := nd.GetUsePub(keyGen, UseHTLCBase) 254 if err != nil { 255 logging.Errorf("error generating N2HTLCBase %v", err) 256 return nil 257 } 258 259 nd.InProgDual.mtx.Lock() 260 nd.InProgDual.OurChangeAddress = changeAddr 261 nd.InProgDual.OurInputs = ourInputs 262 nd.InProgDual.OurPub = myChanPub 263 nd.InProgDual.OurRefundPub = myRefundPub 264 nd.InProgDual.OurHAKDBase = myHAKDbase 265 nd.InProgDual.OurNextHTLCBase = MyNextHTLCBase 266 nd.InProgDual.OurN2HTLCBase = MyN2HTLCBase 267 nd.InProgDual.mtx.Unlock() 268 269 outMsg := lnutil.NewDualFundingAcceptMsg(nd.InProgDual.PeerIdx, nd.InProgDual.CoinType, myChanPub, myRefundPub, myHAKDbase, changeAddr, ourInputs, MyNextHTLCBase, MyN2HTLCBase) 270 271 nd.tmpSendLitMsg(outMsg) 272 273 // wait until it's done! 274 result := <-nd.InProgDual.done 275 276 return result 277 } 278 279 // RECIPIENT 280 // DualFundingReqHandler gets a request with the funding data of the remote peer, along with the 281 // amount of funding requested to return data for. 282 func (nd *LitNode) DualFundingReqHandler(msg lnutil.DualFundingReqMsg) { 283 284 cIdx, err := nd.NextChannelIdx() 285 if err != nil { 286 logging.Errorf("DualFundingReqHandler err %s", err.Error()) 287 return 288 } 289 290 wal, ok := nd.SubWallet[msg.CoinType] 291 if !ok { 292 logging.Errorf("DualFundingReqHandler err no wallet for type %d", msg.CoinType) 293 logging.Warnf("Auto declining dual fund request. We don't handle coin type %d\n", msg.CoinType) 294 nd.DualFundDecline(DUALFUND_DECLINE_REASON_UNSUPPORTED_COIN) 295 return 296 } 297 298 nd.InProgDual.mtx.Lock() 299 if nd.InProgDual.PeerIdx != 0 { 300 logging.Warnf("DualFundingReqHandler already have a pending request. Declining.") 301 nd.DualFundDecline(DUALFUND_DECLINE_REASON_ALREADY_PENDING_REQUEST) 302 nd.InProgDual.mtx.Unlock() 303 return 304 } 305 306 nd.InProgDual.ChanIdx = cIdx 307 nd.InProgDual.PeerIdx = msg.Peer() 308 nd.InProgDual.CoinType = msg.CoinType 309 nd.InProgDual.OurAmount = msg.TheirAmount 310 nd.InProgDual.TheirInputs = msg.OurInputs 311 nd.InProgDual.TheirAmount = msg.OurAmount 312 nd.InProgDual.TheirChangeAddress = msg.OurChangeAddressPKH 313 nd.InProgDual.TheirPub = msg.OurPub 314 nd.InProgDual.TheirRefundPub = msg.OurRefundPub 315 nd.InProgDual.TheirHAKDBase = msg.OurHAKDBase 316 nd.InProgDual.InitiatedByUs = false 317 nd.InProgDual.mtx.Unlock() 318 319 // Check if we have the requested amount, otherwise auto-decline 320 var allPorTxos portxo.TxoSliceByAmt 321 allPorTxos, err = wal.UtxoDump() 322 if err != nil { 323 logging.Errorf("DualFundingReqHandler err %s", err.Error()) 324 return 325 } 326 327 nowHeight := wal.CurrentHeight() 328 spendable := allPorTxos.SumWitness(nowHeight) 329 if msg.TheirAmount > spendable-50000 { 330 logging.Warnf("Auto declining dual fund request. Requested %d, but we only have %d for funding\n", msg.TheirAmount, spendable-50000) 331 nd.DualFundDecline(DUALFUND_DECLINE_REASON_INSUFFICIENT_BALANCE) 332 return 333 } 334 335 return 336 } 337 338 // RECIPIENT 339 // DualFundingDeclHandler gets a message where the remote party is declining the 340 // request to dualfund. 341 func (nd *LitNode) DualFundingDeclHandler(msg lnutil.DualFundingDeclMsg) { 342 343 result := new(DualFundingResult) 344 345 result.DeclineReason = msg.Reason 346 347 nd.InProgDual.mtx.Lock() 348 nd.InProgDual.done <- result 349 nd.InProgDual.Clear() 350 nd.InProgDual.mtx.Unlock() 351 352 return 353 } 354 355 // DualFundingAcceptHandler gets a message where the remote party is accepting the 356 // request to dualfund. 357 func (nd *LitNode) DualFundingAcceptHandler(msg lnutil.DualFundingAcceptMsg) { 358 359 nd.InProgDual.mtx.Lock() 360 nd.InProgDual.TheirInputs = msg.OurInputs 361 nd.InProgDual.TheirChangeAddress = msg.OurChangeAddressPKH 362 nd.InProgDual.TheirPub = msg.OurPub 363 nd.InProgDual.TheirRefundPub = msg.OurRefundPub 364 nd.InProgDual.TheirHAKDBase = msg.OurHAKDBase 365 366 // make channel (not in db) just for keys / elk 367 q := new(Qchan) 368 369 q.Height = -1 370 371 q.Value = nd.InProg.Amt 372 373 q.KeyGen.Depth = 5 374 q.KeyGen.Step[0] = 44 | 1<<31 375 q.KeyGen.Step[1] = nd.InProgDual.CoinType | 1<<31 376 q.KeyGen.Step[2] = UseChannelFund 377 q.KeyGen.Step[3] = nd.InProgDual.PeerIdx | 1<<31 378 q.KeyGen.Step[4] = nd.InProgDual.ChanIdx | 1<<31 379 380 q.MyPub, _ = nd.GetUsePub(q.KeyGen, UseChannelFund) 381 q.MyRefundPub, _ = nd.GetUsePub(q.KeyGen, UseChannelRefund) 382 q.MyHAKDBase, _ = nd.GetUsePub(q.KeyGen, UseChannelHAKDBase) 383 384 // chop up incoming message, save points to channel struct 385 copy(q.TheirPub[:], nd.InProgDual.TheirPub[:]) 386 copy(q.TheirRefundPub[:], nd.InProgDual.TheirRefundPub[:]) 387 copy(q.TheirHAKDBase[:], nd.InProgDual.TheirHAKDBase[:]) 388 389 // make sure their pubkeys are real pubkeys 390 _, err := koblitz.ParsePubKey(q.TheirPub[:], koblitz.S256()) 391 if err != nil { 392 nd.InProgDual.mtx.Unlock() 393 logging.Errorf("PubRespHandler TheirPub err %s", err.Error()) 394 return 395 } 396 _, err = koblitz.ParsePubKey(q.TheirRefundPub[:], koblitz.S256()) 397 if err != nil { 398 nd.InProgDual.mtx.Unlock() 399 logging.Errorf("PubRespHandler TheirRefundPub err %s", err.Error()) 400 return 401 } 402 _, err = koblitz.ParsePubKey(q.TheirHAKDBase[:], koblitz.S256()) 403 if err != nil { 404 nd.InProgDual.mtx.Unlock() 405 logging.Errorf("PubRespHandler TheirHAKDBase err %s", err.Error()) 406 return 407 } 408 409 _, err = koblitz.ParsePubKey(msg.OurNextHTLCBase[:], koblitz.S256()) 410 if err != nil { 411 nd.InProgDual.mtx.Unlock() 412 logging.Errorf("PubRespHandler NextHTLCBase err %s", err.Error()) 413 return 414 } 415 _, err = koblitz.ParsePubKey(msg.OurN2HTLCBase[:], koblitz.S256()) 416 if err != nil { 417 nd.InProgDual.mtx.Unlock() 418 logging.Errorf("PubRespHandler N2HTLCBase err %s", err.Error()) 419 return 420 } 421 422 nd.InProgDual.TheirNextHTLCBase = msg.OurNextHTLCBase 423 nd.InProgDual.TheirN2HTLCBase = msg.OurN2HTLCBase 424 425 // derive elkrem sender root from HD keychain 426 elkRoot, _ := nd.GetElkremRoot(q.KeyGen) 427 q.ElkSnd = elkrem.NewElkremSender(elkRoot) 428 429 // Build the funding transaction 430 tx, _ := nd.BuildDualFundingTransaction() 431 432 outPoint := wire.OutPoint{Hash: tx.TxHash(), Index: 0} 433 nd.InProgDual.OutPoint = &outPoint 434 q.Op = *nd.InProgDual.OutPoint 435 436 // create initial state for elkrem points 437 q.State = new(StatCom) 438 q.State.StateIdx = 0 439 q.State.MyAmt = nd.InProgDual.OurAmount 440 // get fee from sub wallet. Later should make fee per channel and update state 441 // based on size 442 q.State.Fee = nd.SubWallet[q.Coin()].Fee() * consts.QcStateFee 443 q.Value = nd.InProgDual.OurAmount + nd.InProgDual.TheirAmount 444 445 q.State.NextHTLCBase = msg.OurNextHTLCBase 446 q.State.N2HTLCBase = msg.OurN2HTLCBase 447 q.State.MyNextHTLCBase = nd.InProgDual.OurNextHTLCBase 448 q.State.MyN2HTLCBase = nd.InProgDual.OurN2HTLCBase 449 450 // save channel to db 451 err = nd.SaveQChan(q) 452 if err != nil { 453 logging.Errorf("PointRespHandler SaveQchanState err %s", err.Error()) 454 return 455 } 456 457 // when funding a channel, give them the first *3* elkpoints. 458 elkPointZero, err := q.ElkPoint(false, 0) 459 if err != nil { 460 logging.Errorf("PointRespHandler ElkpointZero err %s", err.Error()) 461 return 462 } 463 elkPointOne, err := q.ElkPoint(false, 1) 464 if err != nil { 465 logging.Errorf("PointRespHandler ElkpointOne err %s", err.Error()) 466 return 467 } 468 469 elkPointTwo, err := q.N2ElkPointForThem() 470 if err != nil { 471 logging.Errorf("PointRespHandler ElkpointTwo err %s", err.Error()) 472 return 473 } 474 475 outMsg := lnutil.NewChanDescMsg( 476 nd.InProgDual.PeerIdx, *nd.InProgDual.OutPoint, q.MyPub, q.MyRefundPub, q.MyHAKDBase, nd.InProgDual.OurNextHTLCBase, 477 nd.InProgDual.OurN2HTLCBase, 478 nd.InProgDual.CoinType, nd.InProgDual.OurAmount+nd.InProgDual.TheirAmount, nd.InProgDual.TheirAmount, 479 elkPointZero, elkPointOne, elkPointTwo, q.State.Data) 480 481 nd.InProgDual.mtx.Unlock() 482 nd.tmpSendLitMsg(outMsg) 483 484 return 485 } 486 487 func (nd *LitNode) BuildDualFundingTransaction() (*wire.MsgTx, error) { 488 // make the tx 489 tx := wire.NewMsgTx() 490 491 w, ok := nd.SubWallet[nd.InProgDual.CoinType] 492 if !ok { 493 err := fmt.Errorf("BuildDualFundingTransaction err no wallet for type %d", nd.InProgDual.CoinType) 494 return tx, err 495 } 496 497 // set version 2, for op_csv 498 tx.Version = 2 499 // set the time, the way core does. 500 tx.LockTime = uint32(w.CurrentHeight()) 501 502 // add all the txins 503 var ourInputTotal int64 504 var theirInputTotal int64 505 506 for _, u := range nd.InProgDual.OurInputs { 507 tx.AddTxIn(wire.NewTxIn(&u.Outpoint, nil, nil)) 508 ourInputTotal += u.Value 509 } 510 for _, u := range nd.InProgDual.TheirInputs { 511 tx.AddTxIn(wire.NewTxIn(&u.Outpoint, nil, nil)) 512 theirInputTotal += u.Value 513 } 514 515 var initiatorPub [33]byte 516 var counterPartyPub [33]byte 517 if nd.InProgDual.InitiatedByUs { 518 initiatorPub = nd.InProgDual.OurPub 519 counterPartyPub = nd.InProgDual.TheirPub 520 } else { 521 initiatorPub = nd.InProgDual.TheirPub 522 counterPartyPub = nd.InProgDual.OurPub 523 } 524 525 // get txo for channel 526 txo, err := lnutil.FundTxOut(initiatorPub, counterPartyPub, nd.InProgDual.OurAmount+nd.InProgDual.TheirAmount) 527 if err != nil { 528 return tx, err 529 } 530 tx.AddTxOut(txo) 531 532 // add the change outputs 533 var initiatorChange int64 534 var counterPartyChange int64 535 var initiatorChangeAddress [20]byte 536 var counterPartyChangeAddress [20]byte 537 538 if nd.InProgDual.InitiatedByUs { 539 initiatorChangeAddress = nd.InProgDual.OurChangeAddress 540 initiatorChange = ourInputTotal - nd.InProgDual.OurAmount - consts.DualFundFee 541 counterPartyChangeAddress = nd.InProgDual.TheirChangeAddress 542 counterPartyChange = theirInputTotal - nd.InProgDual.TheirAmount - consts.DualFundFee 543 544 } else { 545 initiatorChangeAddress = nd.InProgDual.TheirChangeAddress 546 initiatorChange = theirInputTotal - nd.InProgDual.TheirAmount - consts.DualFundFee 547 counterPartyChangeAddress = nd.InProgDual.OurChangeAddress 548 counterPartyChange = ourInputTotal - nd.InProgDual.OurAmount - consts.DualFundFee 549 } 550 551 changeScriptInitiator := lnutil.DirectWPKHScriptFromPKH(initiatorChangeAddress) 552 tx.AddTxOut(wire.NewTxOut(initiatorChange, changeScriptInitiator)) 553 554 changeScriptCounterParty := lnutil.DirectWPKHScriptFromPKH(counterPartyChangeAddress) 555 tx.AddTxOut(wire.NewTxOut(counterPartyChange, changeScriptCounterParty)) 556 557 txsort.InPlaceSort(tx) 558 559 return tx, nil 560 } 561 562 // RECIPIENT 563 // QChanDescHandler takes in a description of a channel output. It then 564 // saves it to the local db, and returns a channel acknowledgement 565 func (nd *LitNode) DualFundChanDescHandler(msg lnutil.ChanDescMsg) error { 566 567 logging.Infof("DualFundChanDescHandler\n") 568 569 wal, ok := nd.SubWallet[msg.CoinType] 570 if !ok { 571 return fmt.Errorf("DualFundChanDescHandler err no wallet for type %d", msg.CoinType) 572 } 573 574 // deserialize desc 575 op := msg.Outpoint 576 opArr := lnutil.OutPointToBytes(op) 577 amt := msg.Capacity 578 579 cIdx, err := nd.NextChannelIdx() 580 if err != nil { 581 return fmt.Errorf("DualFundChanDescHandler err %s", err.Error()) 582 } 583 584 qc := new(Qchan) 585 586 qc.Height = -1 587 qc.KeyGen.Depth = 5 588 qc.KeyGen.Step[0] = 44 | 1<<31 589 qc.KeyGen.Step[1] = msg.CoinType | 1<<31 590 qc.KeyGen.Step[2] = UseChannelFund 591 qc.KeyGen.Step[3] = msg.Peer() | 1<<31 592 qc.KeyGen.Step[4] = cIdx | 1<<31 593 qc.Value = amt 594 qc.Mode = portxo.TxoP2WSHComp 595 qc.Op = op 596 597 qc.TheirPub = msg.PubKey 598 qc.TheirRefundPub = msg.RefundPub 599 qc.TheirHAKDBase = msg.HAKDbase 600 qc.MyPub, _ = nd.GetUsePub(qc.KeyGen, UseChannelFund) 601 qc.MyRefundPub, _ = nd.GetUsePub(qc.KeyGen, UseChannelRefund) 602 qc.MyHAKDBase, _ = nd.GetUsePub(qc.KeyGen, UseChannelHAKDBase) 603 604 _, err = koblitz.ParsePubKey(msg.NextHTLCBase[:], koblitz.S256()) 605 if err != nil { 606 return fmt.Errorf("QChanDescHandler NextHTLCBase err %s", err.Error()) 607 } 608 _, err = koblitz.ParsePubKey(msg.N2HTLCBase[:], koblitz.S256()) 609 if err != nil { 610 return fmt.Errorf("QChanDescHandler N2HTLCBase err %s", err.Error()) 611 } 612 613 // it should go into the next bucket and get the right key index. 614 // but we can't actually check that. 615 // qc, err := nd.SaveFundTx( 616 // op, amt, peerArr, theirPub, theirRefundPub, theirHAKDbase) 617 // if err != nil { 618 // logging.Errorf("QChanDescHandler SaveFundTx err %s", err.Error()) 619 // return 620 // } 621 logging.Infof("got multisig output %s amt %d\n", op.String(), amt) 622 623 // create initial state 624 qc.State = new(StatCom) 625 // similar to SIGREV in pushpull 626 627 // TODO assumes both parties use same fee 628 qc.State.Fee = wal.Fee() * consts.QcStateFee 629 qc.State.MyAmt = msg.InitPayment 630 631 qc.State.Data = msg.Data 632 633 qc.State.StateIdx = 0 634 // use new ElkPoint for signing 635 qc.State.ElkPoint = msg.ElkZero 636 qc.State.NextElkPoint = msg.ElkOne 637 qc.State.N2ElkPoint = msg.ElkTwo 638 639 qc.State.MyNextHTLCBase = nd.InProgDual.OurNextHTLCBase 640 qc.State.MyN2HTLCBase = nd.InProgDual.OurN2HTLCBase 641 642 qc.State.NextHTLCBase = msg.NextHTLCBase 643 qc.State.N2HTLCBase = msg.N2HTLCBase 644 645 // save new channel to db 646 err = nd.SaveQChan(qc) 647 if err != nil { 648 return fmt.Errorf("DualFundChanDescHandler err %s", err.Error()) 649 } 650 651 // load ... the thing I just saved. why? 652 qc, err = nd.GetQchan(opArr) 653 if err != nil { 654 return fmt.Errorf("DualFundChanDescHandler GetQchan err %s", err.Error()) 655 } 656 657 // when funding a channel, give them the first *2* elkpoints. 658 theirElkPointZero, err := qc.ElkPoint(false, 0) 659 if err != nil { 660 return fmt.Errorf("DualFundChanDescHandler err %s", err.Error()) 661 } 662 theirElkPointOne, err := qc.ElkPoint(false, 1) 663 if err != nil { 664 return fmt.Errorf("DualFundChanDescHandler err %s", err.Error()) 665 } 666 667 theirElkPointTwo, err := qc.N2ElkPointForThem() 668 if err != nil { 669 return fmt.Errorf("DualFundChanDescHandler err %s", err.Error()) 670 } 671 672 sig, _, err := nd.SignState(qc) 673 if err != nil { 674 return fmt.Errorf("DualFundChanDescHandler SignState err %s", err.Error()) 675 } 676 677 logging.Infof("Acking channel...\n") 678 679 fundingTx, err := nd.BuildDualFundingTransaction() 680 if err != nil { 681 return fmt.Errorf("DualFundChanDescHandler BuildDualFundingTransaction err %s", err.Error()) 682 } 683 684 wal.SignMyInputs(fundingTx) 685 686 outMsg := lnutil.NewDualFundingChanAckMsg( 687 msg.Peer(), op, 688 theirElkPointZero, theirElkPointOne, theirElkPointTwo, 689 sig, fundingTx) 690 691 nd.tmpSendLitMsg(outMsg) 692 693 return nil 694 } 695 696 // FUNDER 697 // QChanAckHandler takes in an acknowledgement multisig description. 698 // when a multisig outpoint is ackd, that causes the funder to sign and broadcast. 699 func (nd *LitNode) DualFundChanAckHandler(msg lnutil.DualFundingChanAckMsg, peer *RemotePeer) { 700 opArr := lnutil.OutPointToBytes(msg.Outpoint) 701 sig := msg.Signature 702 703 // load channel to save their refund address 704 qc, err := nd.GetQchan(opArr) 705 if err != nil { 706 logging.Errorf("DualFundChanAckHandler GetQchan err %s", err.Error()) 707 return 708 } 709 710 // err = qc.IngestElkrem(revElk) 711 // if err != nil { // this can't happen because it's the first elk... remove? 712 // logging.Errorf("QChanAckHandler IngestElkrem err %s", err.Error()) 713 // return 714 // } 715 qc.State.ElkPoint = msg.ElkZero 716 qc.State.NextElkPoint = msg.ElkOne 717 qc.State.N2ElkPoint = msg.ElkTwo 718 719 err = qc.VerifySigs(sig, nil) 720 if err != nil { 721 logging.Errorf("DualFundChanAckHandler VerifySig err %s", err.Error()) 722 return 723 } 724 725 // verify worked; Save state 1 to DB 726 err = nd.SaveQchanState(qc) 727 if err != nil { 728 logging.Errorf("DualFundChanAckHandler SaveQchanState err %s", err.Error()) 729 return 730 } 731 732 // Make sure everything works & is saved, then clear InProg. 733 734 // sign their com tx to send 735 sig, _, err = nd.SignState(qc) 736 if err != nil { 737 logging.Errorf("DualFundChanAckHandler SignState err %s", err.Error()) 738 return 739 } 740 741 // OK to fund. 742 tx, err := nd.BuildDualFundingTransaction() 743 if err != nil { 744 logging.Errorf("DualFundChanAckHandler BuildDualFundingTransaction err %s", err.Error()) 745 return 746 } 747 err = nd.SubWallet[qc.Coin()].SignMyInputs(tx) 748 if err != nil { 749 logging.Errorf("DualFundChanAckHandler SignMyInputs err %s", err.Error()) 750 return 751 } 752 753 // Add signatures from peer 754 for i := range msg.SignedFundingTx.TxIn { 755 if (msg.SignedFundingTx.TxIn[i].Witness != nil || msg.SignedFundingTx.TxIn[i].SignatureScript != nil) && tx.TxIn[i].Witness == nil && tx.TxIn[i].SignatureScript == nil { 756 tx.TxIn[i].Witness = msg.SignedFundingTx.TxIn[i].Witness 757 tx.TxIn[i].SignatureScript = msg.SignedFundingTx.TxIn[i].SignatureScript 758 } 759 } 760 761 nd.SubWallet[qc.Coin()].DirectSendTx(tx) 762 763 err = nd.SubWallet[qc.Coin()].WatchThis(qc.Op) 764 if err != nil { 765 logging.Errorf("DualFundChanAckHandler WatchThis err %s", err.Error()) 766 return 767 } 768 769 logging.Infof("Registering refund address in the wallet\n") 770 // tell base wallet about watcher refund address in case that happens 771 // TODO this is weird & ugly... maybe have an export keypath func? 772 nullTxo := new(portxo.PorTxo) 773 nullTxo.Value = 0 // redundant, but explicitly show that this is just for adr 774 nullTxo.KeyGen = qc.KeyGen 775 nullTxo.KeyGen.Step[2] = UseChannelWatchRefund 776 nd.SubWallet[qc.Coin()].ExportUtxo(nullTxo) 777 778 // channel creation is ~complete, clear InProg. 779 // We may be asked to re-send the sig-proof 780 result := new(DualFundingResult) 781 782 result.Accepted = true 783 result.ChannelId = qc.KeyGen.Step[4] & 0x7fffffff 784 785 logging.Infof("Built result with channel ID %d, committing...\n", result.ChannelId) 786 787 nd.InProgDual.mtx.Lock() 788 nd.InProgDual.done <- result 789 nd.InProgDual.Clear() 790 nd.InProgDual.mtx.Unlock() 791 792 peer.QCs[qc.Idx()] = qc 793 peer.OpMap[opArr] = qc.Idx() 794 795 // sig proof should be sent later once there are confirmations. 796 // it'll have an spv proof of the fund tx. 797 // but for now just send the sig. 798 logging.Infof("Sending sigproof\n") 799 800 outMsg := lnutil.NewSigProofMsg(msg.Peer(), msg.Outpoint, sig) 801 802 nd.tmpSendLitMsg(outMsg) 803 804 return 805 } 806 807 // RECIPIENT 808 // SigProofHandler saves the signature the recipient stores. 809 // In some cases you don't need this message. 810 func (nd *LitNode) DualFundSigProofHandler(msg lnutil.SigProofMsg, peer *RemotePeer) { 811 812 op := msg.Outpoint 813 opArr := lnutil.OutPointToBytes(op) 814 815 qc, err := nd.GetQchan(opArr) 816 if err != nil { 817 logging.Errorf("DualFundSigProofHandler err %s", err.Error()) 818 return 819 } 820 821 wal, ok := nd.SubWallet[qc.Coin()] 822 if !ok { 823 logging.Errorf("Not connected to coin type %d\n", qc.Coin()) 824 return 825 } 826 827 err = qc.VerifySigs(msg.Signature, nil) 828 if err != nil { 829 logging.Errorf("DualFundSigProofHandler err %s", err.Error()) 830 return 831 } 832 833 // sig OK, save 834 err = nd.SaveQchanState(qc) 835 if err != nil { 836 logging.Errorf("DualFundSigProofHandler err %s", err.Error()) 837 return 838 } 839 840 err = wal.WatchThis(op) 841 842 if err != nil { 843 logging.Errorf("DualFundSigProofHandler err %s", err.Error()) 844 return 845 } 846 847 // tell base wallet about watcher refund address in case that happens 848 nullTxo := new(portxo.PorTxo) 849 nullTxo.Value = 0 // redundant, but explicitly show that this is just for adr 850 nullTxo.KeyGen = qc.KeyGen 851 nullTxo.KeyGen.Step[2] = UseChannelWatchRefund 852 wal.ExportUtxo(nullTxo) 853 854 peer.QCs[qc.Idx()] = qc 855 peer.OpMap[opArr] = qc.Idx() 856 857 // sig OK; in terms of UI here's where you can say "payment received" 858 // "channel online" etc 859 860 result := new(DualFundingResult) 861 862 result.Accepted = true 863 result.ChannelId = qc.KeyGen.Step[4] & 0x7fffffff 864 nd.InProgDual.mtx.Lock() 865 nd.InProgDual.done <- result 866 nd.InProgDual.Clear() 867 nd.InProgDual.mtx.Unlock() 868 869 return 870 }