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  }