github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/watchtower/justice.go (about) 1 package watchtower 2 3 import ( 4 "bytes" 5 "fmt" 6 7 "github.com/mit-dci/lit/logging" 8 9 "github.com/boltdb/bolt" 10 "github.com/mit-dci/lit/btcutil/txscript" 11 "github.com/mit-dci/lit/elkrem" 12 "github.com/mit-dci/lit/lnutil" 13 "github.com/mit-dci/lit/sig64" 14 "github.com/mit-dci/lit/wire" 15 ) 16 17 // BuildJusticeTx takes the badTx and IdxSig found by IngestTx, and returns a 18 // Justice transaction moving funds with great vengeance & furious anger. 19 // Re-opens the DB which just was closed by IngestTx, but since this almost never 20 // happens, we need to end IngestTx as quickly as possible. 21 // Note that you should flag the channel for deletion after the JusticeTx is broadcast. 22 func (w *WatchTower) BuildJusticeTx( 23 cointype uint32, badTx *wire.MsgTx) (*wire.MsgTx, error) { 24 var err error 25 26 // wd and elkRcv are the two things we need to get out of the db 27 var wd lnutil.WatchDescMsg 28 var elkRcv *elkrem.ElkremReceiver 29 var iSig *IdxSig 30 31 // open DB and get static channel info 32 err = w.WatchDB.View(func(btx *bolt.Tx) error { 33 // get 34 // open the big bucket 35 txidbkt := btx.Bucket(BUCKETTxid) 36 if txidbkt == nil { 37 return fmt.Errorf("no txid bucket") 38 } 39 txid := badTx.TxHash() 40 idxSigBytes := txidbkt.Get(txid[:16]) 41 if idxSigBytes == nil { 42 return fmt.Errorf("couldn't get txid %x", idxSigBytes) 43 } 44 iSig, err = IdxSigFromBytes(idxSigBytes) 45 if err != nil { 46 return err 47 } 48 49 mapBucket := btx.Bucket(BUCKETPKHMap) 50 if mapBucket == nil { 51 return fmt.Errorf("no PKHmap bucket") 52 } 53 // figure out who this Justice belongs to 54 pkh := mapBucket.Get(lnutil.U32tB(iSig.PKHIdx)) 55 if pkh == nil { 56 return fmt.Errorf("No pkh found for index %d", iSig.PKHIdx) 57 } 58 59 channelBucket := btx.Bucket(BUCKETChandata) 60 if channelBucket == nil { 61 return fmt.Errorf("No channel bucket") 62 } 63 64 pkhBucket := channelBucket.Bucket(pkh) 65 if pkhBucket == nil { 66 return fmt.Errorf("No bucket for pkh %x", pkh) 67 } 68 69 static := pkhBucket.Get(KEYStatic) 70 if static == nil { 71 return fmt.Errorf("No static data for pkh %x", pkh) 72 } 73 // deserialize static watchDescriptor struct 74 var peerIdx uint32 75 peerIdx = 0 // should be replaced 76 wd2, err2 := lnutil.NewWatchDescMsgFromBytes(static, peerIdx) 77 if err2 != nil { 78 return err2 79 } else { 80 wd = wd2 81 } 82 83 // get the elkrem receiver 84 elkBytes := pkhBucket.Get(KEYElkRcv) 85 if elkBytes == nil { 86 return fmt.Errorf("No elkrem receiver for pkh %x", pkh) 87 } 88 // deserialize it 89 elkRcv, err = elkrem.ElkremReceiverFromBytes(elkBytes) 90 if err != nil { 91 return err 92 } 93 94 return nil 95 }) 96 if err != nil { 97 return nil, err 98 } 99 100 // done with DB, could do this in separate func? or leave here. 101 102 // get the elkrem we need. above check is redundant huh. 103 elkHash, err := elkRcv.AtIndex(iSig.StateIdx) 104 if err != nil { 105 return nil, err 106 } 107 108 elkPoint := lnutil.ElkPointFromHash(elkHash) 109 110 // build the script so we can match it with a txout 111 // to do so, generate Pubkeys for the script 112 113 // timeout key is the attacker's base point ez-added with the elk-point 114 TimeoutKey := lnutil.AddPubsEZ(wd.AdversaryBasePoint, elkPoint) 115 116 // revocable key is the customer's base point combined with same elk-point 117 Revkey := lnutil.CombinePubs(wd.CustomerBasePoint, elkPoint) 118 119 logging.Infof("tower build revpub %x \ntimeoutpub %x\n", Revkey, TimeoutKey) 120 // build script from the two combined pubkeys and the channel delay 121 script := lnutil.CommitScript(Revkey, TimeoutKey, wd.Delay) 122 123 // get P2WSH output script 124 shOutputScript := lnutil.P2WSHify(script) 125 logging.Infof("built script %x\npkscript %x\n", script, shOutputScript) 126 127 // try to match WSH with output from tx 128 txoutNum := 999 129 for i, out := range badTx.TxOut { 130 if bytes.Equal(shOutputScript, out.PkScript) { 131 txoutNum = i 132 break 133 } 134 } 135 // if txoutNum wasn't set, that means we couldn't find the right txout, 136 // so either we've generated the script incorrectly, or we've been led 137 // on a wild goose chase of some kind. If this happens for real (not in 138 // testing) then we should nuke the channel after this) 139 if txoutNum == 999 { 140 // TODO do something else here 141 return nil, fmt.Errorf("couldn't match generated script with detected txout") 142 } 143 144 justiceAmt := badTx.TxOut[txoutNum].Value - wd.Fee 145 justicePkScript := lnutil.DirectWPKHScriptFromPKH(wd.DestPKHScript) 146 // build the JusticeTX. First the output 147 justiceOut := wire.NewTxOut(justiceAmt, justicePkScript) 148 // now the input 149 badtxid := badTx.TxHash() 150 badOP := wire.NewOutPoint(&badtxid, uint32(txoutNum)) 151 justiceIn := wire.NewTxIn(badOP, nil, nil) 152 // expand the sig back to 71 bytes 153 bigSig := sig64.SigDecompress(iSig.Sig) 154 bigSig = append(bigSig, byte(txscript.SigHashAll)) // put sighash_all byte on at the end 155 156 justiceIn.Sequence = 1 // sequence 1 means grab immediately 157 justiceIn.Witness = make([][]byte, 3) // timeout SH has one presig item 158 justiceIn.Witness[0] = bigSig // expanded signature goes on bottom 159 justiceIn.Witness[1] = []byte{0x01} // above sig is a 1, for justice 160 justiceIn.Witness[2] = script // full script goes on at the top 161 162 // add in&out to the the final justiceTx 163 justiceTx := wire.NewMsgTx() 164 justiceTx.Version = 2 // shouldn't matter, but standardize 165 justiceTx.AddTxIn(justiceIn) 166 justiceTx.AddTxOut(justiceOut) 167 168 return justiceTx, nil 169 } 170 171 // don't use this? inline is OK... 172 func BuildIdxSig(who uint32, when uint64, sig [64]byte) IdxSig { 173 var x IdxSig 174 x.PKHIdx = who 175 x.StateIdx = when 176 x.Sig = sig 177 return x 178 }