github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/wallit/signsend.go (about) 1 package wallit 2 3 import ( 4 "bytes" 5 "fmt" 6 "sort" 7 8 "github.com/mit-dci/lit/logging" 9 10 "github.com/mit-dci/lit/btcutil/chaincfg/chainhash" 11 "github.com/mit-dci/lit/btcutil/txscript" 12 "github.com/mit-dci/lit/btcutil/txsort" 13 "github.com/mit-dci/lit/consts" 14 "github.com/mit-dci/lit/lnutil" 15 "github.com/mit-dci/lit/portxo" 16 "github.com/mit-dci/lit/wire" 17 ) 18 19 // Build a tx, kindof like with SendCoins, but don't sign or broadcast. 20 // Segwit inputs only. Freeze the utxos used so the tx can be signed and broadcast 21 // later. Use only segwit utxos. Return the txid, and indexes of where the txouts 22 // in the argument slice ended up in the final tx. 23 // Bunch of redundancy with SendMany, maybe move that to a shared function... 24 //NOTE this does not support multiple txouts with identical pkscripts in one tx. 25 // The code would be trivial; it's not supported on purpose. Use unique pkscripts. 26 func (w *Wallit) MaybeSend(txos []*wire.TxOut, ow bool) ([]*wire.OutPoint, error) { 27 var err error 28 var totalSend int64 29 dustCutoff := consts.DustCutoff // below this amount, just give to miners 30 31 feePerByte := w.FeeRate 32 33 // make an initial txo copy so we can find where the outputs end up in final tx 34 35 initTxos := make([]*wire.TxOut, len(txos)) 36 37 // change output (if needed) 38 var changeOut *wire.TxOut 39 40 finalOutPoints := make([]*wire.OutPoint, len(txos)) 41 copy(initTxos, txos) 42 43 var outputByteSize int64 44 // check for negative...? 45 for _, txo := range txos { 46 totalSend += txo.Value 47 outputByteSize += 8 + int64(len(txo.PkScript)) 48 } 49 50 // start access to utxos 51 w.FreezeMutex.Lock() 52 defer w.FreezeMutex.Unlock() 53 54 // get inputs for this tx. Only segwit if needed 55 utxos, overshoot, err := 56 w.PickUtxos(totalSend, outputByteSize, feePerByte, ow) 57 if err != nil { 58 return nil, err 59 } 60 61 logging.Infof("MaybeSend has overshoot %d, %d inputs\n", overshoot, len(utxos)) 62 63 // changeOutSize is the extra vsize that a change output would add 64 changeOutFee := 30 * feePerByte 65 66 // add a change output if we have enough extra to do so 67 if overshoot > dustCutoff+changeOutFee { 68 changeOut, err = w.NewChangeOut(overshoot - changeOutFee) 69 if err != nil { 70 return nil, err 71 } 72 } 73 74 // build frozen tx for later broadcast 75 fTx := new(FrozenTx) 76 fTx.Ins = utxos 77 fTx.Outs = txos 78 fTx.ChangeOut = changeOut 79 80 if changeOut != nil { 81 txos = append(txos, changeOut) 82 } 83 84 // BuildDontSign gets the txid. Also sorts txin, txout slices in place 85 tx, err := w.BuildDontSign(utxos, txos) 86 if err != nil { 87 return nil, err 88 } 89 90 // after building, store the locktime and txid 91 fTx.Nlock = tx.LockTime 92 fTx.Txid = tx.TxHash() 93 94 for _, utxo := range utxos { 95 w.FreezeSet[utxo.Op] = fTx 96 } 97 98 // figure out where outputs ended up after adding the change output and sorting 99 for i, initTxo := range initTxos { 100 for j, finalTxo := range tx.TxOut { 101 // If pkscripts match, this is where it ended up. 102 // if you're sending different amounts to the same address, this 103 // might not work! Don't re-use addresses! 104 if bytes.Equal(initTxo.PkScript, finalTxo.PkScript) { 105 finalOutPoints[i] = wire.NewOutPoint(&fTx.Txid, uint32(j)) 106 } 107 } 108 } 109 110 return finalOutPoints, nil 111 } 112 113 // Sign and broadcast a tx previously built with MaybeSend. This clears the freeze 114 // on the utxos but they're not utxos anymore anyway. 115 func (w *Wallit) ReallySend(txid *chainhash.Hash) error { 116 logging.Infof("Reallysend %s\n", txid.String()) 117 // start frozen set access 118 w.FreezeMutex.Lock() 119 defer w.FreezeMutex.Unlock() 120 // get the transaction 121 frozenTx, err := w.FindFreezeTx(txid) 122 if err != nil { 123 return err 124 } 125 // delete inputs from frozen set (they're gone anyway, but just to clean it up) 126 for _, txin := range frozenTx.Ins { 127 logging.Infof("\t remove %s from frozen outpoints\n", txin.Op.String()) 128 delete(w.FreezeSet, txin.Op) 129 } 130 131 allOuts := frozenTx.Outs 132 133 if frozenTx.ChangeOut != nil { 134 allOuts = append(frozenTx.Outs, frozenTx.ChangeOut) 135 } 136 137 tx, err := w.BuildAndSign(frozenTx.Ins, allOuts, frozenTx.Nlock) 138 if err != nil { 139 return err 140 } 141 142 return w.NewOutgoingTx(tx) 143 } 144 145 // Cancel the hold on a tx previously built with MaybeSend. Clears freeze on 146 // utxos so they can be used somewhere else. 147 func (w *Wallit) NahDontSend(txid *chainhash.Hash) error { 148 logging.Infof("Nahdontsend %s\n", txid.String()) 149 // start frozen set access 150 w.FreezeMutex.Lock() 151 defer w.FreezeMutex.Unlock() 152 // get the transaction 153 frozenTx, err := w.FindFreezeTx(txid) 154 if err != nil { 155 return err 156 } 157 // go through all its inputs, and remove those outpoints from the frozen set 158 for _, txin := range frozenTx.Ins { 159 logging.Infof("\t remove %s from frozen outpoints\n", txin.Op.String()) 160 delete(w.FreezeSet, txin.Op) 161 } 162 return nil 163 } 164 165 // FindFreezeTx looks through the frozen map to find a tx. Error if it can't find it 166 func (w *Wallit) FindFreezeTx(txid *chainhash.Hash) (*FrozenTx, error) { 167 for op := range w.FreezeSet { 168 frozenTxid := w.FreezeSet[op].Txid 169 if frozenTxid.IsEqual(txid) { 170 return w.FreezeSet[op], nil 171 } 172 } 173 return nil, fmt.Errorf("couldn't find %s in frozen set", txid.String()) 174 } 175 176 // GrabAll makes first-party justice txs. 177 func (w *Wallit) GrabAll() error { 178 // no args, look through all utxos 179 utxos, err := w.GetAllUtxos() 180 if err != nil { 181 return err 182 } 183 184 // currently grabs only confirmed txs. 185 nothin := true 186 for _, u := range utxos { 187 if u.Seq == 1 && u.Height > 0 { // grabbable 188 logging.Infof("found %s to grab!\n", u.String()) 189 adr160, err := w.NewAdr160() 190 if err != nil { 191 return err 192 } 193 194 outScript := lnutil.DirectWPKHScriptFromPKH(adr160) 195 196 tx, err := w.SendOne(*u, outScript) 197 if err != nil { 198 return err 199 } 200 err = w.NewOutgoingTx(tx) 201 if err != nil { 202 return err 203 } 204 nothin = false 205 } 206 } 207 if nothin { 208 logging.Infof("Nothing to grab\n") 209 } 210 return nil 211 } 212 213 // Directly send out a tx. For things that plug in to the uspv wallet. 214 func (w *Wallit) DirectSendTx(tx *wire.MsgTx) error { 215 // don't ingest, just push out 216 return w.Hook.PushTx(tx) 217 } 218 219 // NewOutgoingTx runs a tx though the db first, then sends it out to the network. 220 func (w *Wallit) NewOutgoingTx(tx *wire.MsgTx) error { 221 _, err := w.Ingest(tx, 0) // our own tx; don't keep track of false positives 222 if err != nil { 223 return err 224 } 225 return w.Hook.PushTx(tx) 226 } 227 228 // PickUtxos Picks Utxos for spending. Tell it how much money you want. 229 // It returns a tx-sortable utxoslice, and the overshoot amount. Also errors. 230 // if "ow" is true, only gives witness utxos (for channel funding) 231 // The overshoot amount is *after* fees, so can be used directly for a 232 // change output. 233 func (w *Wallit) PickUtxos( 234 amtWanted, outputByteSize, feePerByte int64, 235 ow bool) (portxo.TxoSliceByBip69, int64, error) { 236 237 curHeight, err := w.GetDBSyncHeight() 238 if err != nil { 239 return nil, 0, err 240 } 241 242 var allUtxos portxo.TxoSliceByAmt 243 allUtxos, err = w.GetAllUtxos() 244 if err != nil { 245 return nil, 0, err 246 } 247 248 // remove frozen utxos from allUtxo slice. Iterate backwards / trailing delete 249 for i := len(allUtxos) - 1; i >= 0; i-- { 250 _, frozen := w.FreezeSet[allUtxos[i].Op] 251 if frozen { 252 // faster than append, and we're sorting a few lines later anyway 253 allUtxos[i] = allUtxos[len(allUtxos)-1] // redundant if at last index 254 allUtxos = allUtxos[:len(allUtxos)-1] // trim last element 255 } 256 } 257 258 // start with utxos sorted by value and pop off utxos which are greater 259 // than the send amount... as long as the next 2 are greater. 260 // simple / straightforward coin selection optimization, which tends to make 261 // 2 in 2 out 262 263 // smallest and unconfirmed last (because it's reversed) 264 sort.Sort(sort.Reverse(allUtxos)) 265 266 // guessing that txs won't be more than 10K here... 267 maxFeeGuess := feePerByte * consts.MaxTxCount 268 269 // first pass of removing candidate utxos; if the next one is bigger than 270 // we need, remove the top one. 271 for len(allUtxos) > 1 && 272 allUtxos[1].Value > amtWanted+maxFeeGuess && 273 allUtxos[1].Height > 100 && 274 !(ow && allUtxos[1].Mode&portxo.FlagTxoWitness == 0) { 275 allUtxos = allUtxos[1:] 276 } 277 278 // if we've got 2 or more confirmed utxos, and the next one is 279 // more than enough, pop off the first one. 280 // Note that there are probably all sorts of edge cases where this will 281 // result in not being able to send money when you should be able to. 282 // Thus the handwavey "maxFeeGuess" 283 for len(allUtxos) > 2 && 284 allUtxos[2].Height > 100 && // since sorted, don't need to check [1] 285 allUtxos[1].Mature(curHeight) && 286 allUtxos[2].Mature(curHeight) && 287 allUtxos[1].Value+allUtxos[2].Value > amtWanted+maxFeeGuess && 288 !(ow && allUtxos[2].Mode&portxo.FlagTxoWitness == 0) && 289 !(ow && allUtxos[1].Mode&portxo.FlagTxoWitness == 0) { 290 logging.Infof("remaining utxo list, in order:\n") 291 for _, u := range allUtxos { 292 logging.Infof("\t h: %d amt: %d\n", u.Height, u.Value) 293 } 294 allUtxos = allUtxos[1:] 295 } 296 297 // coin selection is super complex, and we can definitely do a lot better 298 // here! 299 // TODO: anyone who wants to: implement more advanced coin selection algo 300 301 // rSlice is the return slice of the utxos which are going into the tx 302 var rSlice portxo.TxoSliceByBip69 303 // add utxos until we've had enough 304 remaining := amtWanted // remaining is how much is needed on input side 305 for _, utxo := range allUtxos { 306 // skip unconfirmed. Or de-prioritize? Some option for this... 307 // if utxo.AtHeight == 0 { 308 // continue 309 // } 310 if !utxo.Mature(curHeight) { 311 continue // skip immature or unconfirmed time-locked sh outputs 312 } 313 if ow && utxo.Mode&portxo.FlagTxoWitness == 0 { 314 continue // skip non-witness 315 } 316 // why are 0-value outputs a thing..? 317 if utxo.Value < 1 { 318 continue 319 } 320 // yeah, lets add this utxo! 321 rSlice = append(rSlice, utxo) 322 remaining -= utxo.Value 323 // if remaining is positive, don't bother checking fee yet. 324 // if remaining is negative, calculate needed fee 325 if remaining <= 0 { 326 fee := EstFee(rSlice, outputByteSize, feePerByte) 327 // subtract fee from returned overshoot. 328 // (remaining is negative here) 329 remaining += fee 330 331 // done adding utxos if remaining below negative est fee 332 if remaining < -fee { 333 break 334 } 335 } 336 } 337 338 if remaining > 0 { 339 return nil, 0, fmt.Errorf("wanted %d but %d available.", 340 amtWanted, amtWanted-remaining) 341 } 342 343 sort.Sort(rSlice) // send sorted. This is probably redundant? 344 return rSlice, -remaining, nil 345 } 346 347 // SendOne is for the sweep function, and doesn't do change. 348 // Probably can get rid of this for real txs. 349 func (w *Wallit) SendOne(u portxo.PorTxo, outScript []byte) (*wire.MsgTx, error) { 350 351 w.FreezeMutex.Lock() 352 defer w.FreezeMutex.Unlock() 353 _, frozen := w.FreezeSet[u.Op] 354 if frozen { 355 return nil, fmt.Errorf("%s is frozen, can't spend", u.Op.String()) 356 } 357 358 curHeight, err := w.GetDBSyncHeight() 359 if err != nil { 360 return nil, err 361 } 362 363 if u.Seq > 1 && 364 (u.Height < 100 || u.Height+int32(u.Seq) > curHeight) { 365 // skip immature or unconfirmed time-locked sh outputs 366 return nil, fmt.Errorf("Can't spend, immature") 367 } 368 // fixed fee 369 fee := w.FeeRate * 200 370 371 sendAmt := u.Value - fee 372 373 // make user specified txout and add to tx 374 txout := wire.NewTxOut(sendAmt, outScript) 375 376 return w.BuildAndSign( 377 []*portxo.PorTxo{&u}, []*wire.TxOut{txout}, uint32(w.CurrentHeight())) 378 } 379 380 // Builds tx from inputs and outputs, returns tx. Sorts. Doesn't sign. 381 func (w *Wallit) BuildDontSign( 382 utxos []*portxo.PorTxo, txos []*wire.TxOut) (*wire.MsgTx, error) { 383 384 // make the tx 385 tx := wire.NewMsgTx() 386 // set version 2, for op_csv 387 tx.Version = 2 388 // set the time, the way core does. 389 tx.LockTime = uint32(w.CurrentHeight()) 390 391 // add all the txouts 392 for _, txo := range txos { 393 tx.AddTxOut(txo) 394 } 395 // add all the txins 396 for i, u := range utxos { 397 tx.AddTxIn(wire.NewTxIn(&u.Op, nil, nil)) 398 // set sequence field if it's in the portxo 399 if u.Seq > 1 { 400 tx.TxIn[i].Sequence = u.Seq 401 } 402 } 403 // sort in place before signing 404 txsort.InPlaceSort(tx) 405 return tx, nil 406 } 407 408 // SignMyInputs finds the inputs in a transaction that came from our own wallet, and signs them with our private keys. 409 // Will modify the transaction in place, but will ignore inputs that we can't sign and leave them unsigned. 410 func (w *Wallit) SignMyInputs(tx *wire.MsgTx) error { 411 412 // generate tx-wide hashCache for segwit stuff 413 // might not be needed (non-witness) but make it anyway 414 hCache := txscript.NewTxSigHashes(tx) 415 // make the stashes for signatures / witnesses 416 sigStash := make([][]byte, len(tx.TxIn)) 417 witStash := make([][][]byte, len(tx.TxIn)) 418 419 var allUtxos portxo.TxoSliceByAmt 420 allUtxos, err := w.GetAllUtxos() 421 if err != nil { 422 return err 423 } 424 425 for i := range tx.TxIn { 426 var utxo *portxo.PorTxo 427 for j := range allUtxos { 428 if allUtxos[j].Op.Hash.IsEqual(&tx.TxIn[i].PreviousOutPoint.Hash) && allUtxos[j].Op.Index == tx.TxIn[i].PreviousOutPoint.Index { 429 utxo = allUtxos[j] 430 break 431 } 432 } 433 434 if utxo == nil { 435 // Not my input, or at least i don't have it in my DB 436 continue 437 } 438 439 // get key 440 priv := w.PathPrivkey(utxo.KeyGen) 441 logging.Infof("signing with privkey pub %x\n", priv.PubKey().SerializeCompressed()) 442 443 if priv == nil { 444 return fmt.Errorf("SignMyInputs: nil privkey") 445 } 446 447 // sign into stash. 3 possibilities: legacy PKH, WPKH, WSH 448 if utxo.Mode == portxo.TxoP2PKHComp { // legacy PKH 449 sigStash[i], err = txscript.SignatureScript(tx, i, 450 utxo.PkScript, txscript.SigHashAll, priv, true) 451 if err != nil { 452 return err 453 } 454 } 455 if utxo.Mode == portxo.TxoP2WPKHComp { // witness PKH 456 witStash[i], err = txscript.WitnessScript(tx, hCache, i, 457 utxo.Value, utxo.PkScript, txscript.SigHashAll, priv, true) 458 if err != nil { 459 return err 460 } 461 } 462 if utxo.Mode == portxo.TxoP2WSHComp { // witness script hash 463 sig, err := txscript.RawTxInWitnessSignature(tx, hCache, i, 464 utxo.Value, utxo.PkScript, txscript.SigHashAll, priv) 465 if err != nil { 466 return err 467 } 468 // witness stack has the signature, items, then the previous full script 469 witStash[i] = make([][]byte, 2+len(utxo.PreSigStack)) 470 471 // sig comes first (pushed to stack last) 472 witStash[i][0] = sig 473 474 // after stack comes PostSigStack items 475 for j, element := range utxo.PreSigStack { 476 witStash[i][j+1] = element 477 } 478 479 // last stack item is the pkscript 480 witStash[i][len(witStash[i])-1] = utxo.PkScript 481 } 482 483 } 484 // swap sigs into sigScripts in txins 485 for i, txin := range tx.TxIn { 486 if sigStash[i] != nil { 487 txin.SignatureScript = sigStash[i] 488 } 489 if witStash[i] != nil { 490 txin.Witness = witStash[i] 491 txin.SignatureScript = nil 492 } 493 } 494 495 return nil 496 } 497 498 // BuildAndSign builds a tx from a slice of utxos and txOuts. 499 // It then signs all the inputs and returns the tx. Should 500 // pretty much always work for any inputs. 501 func (w *Wallit) BuildAndSign( 502 utxos []*portxo.PorTxo, txos []*wire.TxOut, nlt uint32) (*wire.MsgTx, error) { 503 504 if len(utxos) == 0 || len(txos) == 0 { 505 return nil, fmt.Errorf("BuildAndSign args no utxos or txos") 506 } 507 // sort input utxos first. 508 sort.Sort(portxo.TxoSliceByBip69(utxos)) 509 510 // make the tx 511 tx := wire.NewMsgTx() 512 513 // always make version 2 txs 514 tx.Version = 2 515 tx.LockTime = nlt 516 // add all the txouts, direct from the argument slice 517 for _, txo := range txos { 518 if txo == nil || txo.PkScript == nil || txo.Value == 0 { 519 return nil, fmt.Errorf("BuildAndSign arg invalid txo") 520 } 521 tx.AddTxOut(txo) 522 } 523 // add all the txins, first refenecing the prev outPoints 524 for i, u := range utxos { 525 tx.AddTxIn(wire.NewTxIn(&u.Op, nil, nil)) 526 // set sequence field if it's in the portxo 527 if u.Seq > 1 { 528 tx.TxIn[i].Sequence = u.Seq 529 } 530 } 531 // sort txouts in place before signing. txins are already sorted from above 532 txsort.InPlaceSort(tx) 533 534 w.SignMyInputs(tx) 535 536 logging.Infof("tx: %s", TxToString(tx)) 537 return tx, nil 538 } 539 540 // EstFee gives a fee estimate based on an input / output set and a sat/Byte target. 541 // It guesses the final tx size based on: 542 // Txouts: 8 bytes + pkscript length 543 // Total guess on the p2wsh one, see if that's accurate 544 func EstFee(txins []*portxo.PorTxo, outputByteSize, spB int64) int64 { 545 size := int64(40) // around 40 bytes for a change output and nlock time 546 size += outputByteSize // add the output size 547 548 // iterate through txins, guessing size based on mode 549 for _, txin := range txins { 550 if txin == nil { // silently ignore nil txins; somebody else's problem 551 continue 552 } 553 size += txin.EstSize() 554 } 555 556 logging.Infof("%d spB, est vsize %d, fee %d\n", spB, size, size*spB) 557 return size * spB 558 }