github.com/decred/dcrlnd@v0.7.6/watchtower/wtclient/backup_task.go (about)

     1  package wtclient
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/decred/dcrd/blockchain/standalone/v2"
     7  	"github.com/decred/dcrd/chaincfg/v3"
     8  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
     9  	"github.com/decred/dcrd/dcrutil/v4"
    10  	"github.com/decred/dcrd/dcrutil/v4/txsort"
    11  	"github.com/decred/dcrd/wire"
    12  	"github.com/decred/dcrlnd/channeldb"
    13  	"github.com/decred/dcrlnd/input"
    14  	"github.com/decred/dcrlnd/lnwallet"
    15  	"github.com/decred/dcrlnd/lnwire"
    16  	"github.com/decred/dcrlnd/watchtower/blob"
    17  	"github.com/decred/dcrlnd/watchtower/wtdb"
    18  )
    19  
    20  // backupTask is an internal struct for computing the justice transaction for a
    21  // particular revoked state. A backupTask functions as a scratch pad for storing
    22  // computing values of the transaction itself, such as the final split in
    23  // balance if the justice transaction will give a reward to the tower. The
    24  // backup task has three primary phases:
    25  //  1. Init: Determines which inputs from the breach transaction will be spent,
    26  //     and the total amount contained in the inputs.
    27  //  2. Bind: Asserts that the revoked state is eligible under a given session's
    28  //     parameters. Certain states may be ineligible due to fee rates, too little
    29  //     input amount, etc. Backup of these states can be deferred to a later time
    30  //     or session with more favorable parameters. If the session is bound
    31  //     successfully, the final session-dependent values to the justice
    32  //     transaction are solidified.
    33  //  3. Send: Once the task is bound, it will be queued to send to a specific
    34  //     tower corresponding to the session in which it was bound. The justice
    35  //     transaction will be assembled by examining the parameters left as a
    36  //     result of the binding. After the justice transaction is signed, the
    37  //     necessary components are stripped out and encrypted before being sent to
    38  //     the tower in a StateUpdate.
    39  type backupTask struct {
    40  	id         wtdb.BackupID
    41  	breachInfo *lnwallet.BreachRetribution
    42  	chanType   channeldb.ChannelType
    43  
    44  	// state-dependent variables
    45  
    46  	toLocalInput  input.Input
    47  	toRemoteInput input.Input
    48  	totalAmt      dcrutil.Amount
    49  	sweepPkScript []byte
    50  
    51  	// session-dependent variables
    52  
    53  	blobType blob.Type
    54  	outputs  []*wire.TxOut
    55  
    56  	chainParams *chaincfg.Params
    57  }
    58  
    59  // newBackupTask initializes a new backupTask and populates all state-dependent
    60  // variables.
    61  func newBackupTask(chanID *lnwire.ChannelID,
    62  	breachInfo *lnwallet.BreachRetribution,
    63  	sweepPkScript []byte, chanType channeldb.ChannelType,
    64  	chainParams *chaincfg.Params) *backupTask {
    65  
    66  	// Parse the non-dust outputs from the breach transaction,
    67  	// simultaneously computing the total amount contained in the inputs
    68  	// present. We can't compute the exact output values at this time
    69  	// since the task has not been assigned to a session, at which point
    70  	// parameters such as fee rate, number of outputs, and reward rate will
    71  	// be finalized.
    72  	var (
    73  		totalAmt      int64
    74  		toLocalInput  input.Input
    75  		toRemoteInput input.Input
    76  	)
    77  
    78  	// Add the sign descriptors and outputs corresponding to the to-local
    79  	// and to-remote outputs, respectively, if either input amount is
    80  	// non-dust. Note that the naming here seems reversed, but both are
    81  	// correct. For example, the to-remote output on the remote party's
    82  	// commitment is an output that pays to us. Hence the retribution refers
    83  	// to that output as local, though relative to their commitment, it is
    84  	// paying to-the-remote party (which is us).
    85  	if breachInfo.RemoteOutputSignDesc != nil {
    86  		toLocalInput = input.NewBaseInput(
    87  			&breachInfo.RemoteOutpoint,
    88  			input.CommitmentRevoke,
    89  			breachInfo.RemoteOutputSignDesc,
    90  			0,
    91  		)
    92  		totalAmt += breachInfo.RemoteOutputSignDesc.Output.Value
    93  	}
    94  	if breachInfo.LocalOutputSignDesc != nil {
    95  		var witnessType input.WitnessType
    96  		switch {
    97  		case chanType.HasAnchors():
    98  			witnessType = input.CommitmentToRemoteConfirmed
    99  		case chanType.IsTweakless():
   100  			witnessType = input.CommitSpendNoDelayTweakless
   101  		default:
   102  			witnessType = input.CommitmentNoDelay
   103  		}
   104  
   105  		// Anchor channels have a CSV-encumbered to-remote output. We'll
   106  		// construct a CSV input in that case and assign the proper CSV
   107  		// delay of 1, otherwise we fallback to the a regular P2WKH
   108  		// to-remote output for tweaked or tweakless channels.
   109  		if chanType.HasAnchors() {
   110  			toRemoteInput = input.NewCsvInput(
   111  				&breachInfo.LocalOutpoint,
   112  				witnessType,
   113  				breachInfo.LocalOutputSignDesc,
   114  				0, 1,
   115  			)
   116  		} else {
   117  			toRemoteInput = input.NewBaseInput(
   118  				&breachInfo.LocalOutpoint,
   119  				witnessType,
   120  				breachInfo.LocalOutputSignDesc,
   121  				0,
   122  			)
   123  		}
   124  
   125  		totalAmt += breachInfo.LocalOutputSignDesc.Output.Value
   126  	}
   127  
   128  	return &backupTask{
   129  		chainParams: chainParams,
   130  		id: wtdb.BackupID{
   131  			ChanID:       *chanID,
   132  			CommitHeight: breachInfo.RevokedStateNum,
   133  		},
   134  		breachInfo:    breachInfo,
   135  		chanType:      chanType,
   136  		toLocalInput:  toLocalInput,
   137  		toRemoteInput: toRemoteInput,
   138  		totalAmt:      dcrutil.Amount(totalAmt),
   139  		sweepPkScript: sweepPkScript,
   140  	}
   141  }
   142  
   143  // inputs returns all non-dust inputs that we will attempt to spend from.
   144  //
   145  // NOTE: Ordering of the inputs is not critical as we sort the transaction with
   146  // BIP69.
   147  func (t *backupTask) inputs() map[wire.OutPoint]input.Input {
   148  	inputs := make(map[wire.OutPoint]input.Input)
   149  	if t.toLocalInput != nil {
   150  		inputs[*t.toLocalInput.OutPoint()] = t.toLocalInput
   151  	}
   152  	if t.toRemoteInput != nil {
   153  		inputs[*t.toRemoteInput.OutPoint()] = t.toRemoteInput
   154  	}
   155  	return inputs
   156  }
   157  
   158  // bindSession determines if the backupTask is compatible with the passed
   159  // SessionInfo's policy. If no error is returned, the task has been bound to the
   160  // session and can be queued to upload to the tower. Otherwise, the bind failed
   161  // and should be rescheduled with a different session.
   162  func (t *backupTask) bindSession(session *wtdb.ClientSessionBody) error {
   163  	// First we'll begin by deriving a size estimate for the justice
   164  	// transaction. The final size can be different depending on whether
   165  	// the watchtower is taking a reward.
   166  	var sizeEstimate input.TxSizeEstimator
   167  
   168  	// Next, add the contribution from the inputs that are present on this
   169  	// breach transaction.
   170  	if t.toLocalInput != nil {
   171  		sizeEstimate.AddCustomInput(input.ToLocalPenaltySigScriptSize)
   172  	}
   173  	if t.toRemoteInput != nil {
   174  		// Legacy channels (both tweaked and non-tweaked) spend from
   175  		// P2PKH output. Anchor channels spend a to-remote confirmed
   176  		// P2SH  output.
   177  		if t.chanType.HasAnchors() {
   178  			sizeEstimate.AddCustomInput(input.ToRemoteConfirmedWitnessSize)
   179  		} else {
   180  			sizeEstimate.AddP2PKHInput()
   181  		}
   182  	}
   183  
   184  	// All justice transactions have a p2pkh output paying to the victim.
   185  	sizeEstimate.AddP2PKHOutput()
   186  
   187  	// If the justice transaction has a reward output, add the output's
   188  	// contribution to the size estimate.
   189  	if session.Policy.BlobType.Has(blob.FlagReward) {
   190  		sizeEstimate.AddP2PKHOutput()
   191  	}
   192  
   193  	if t.chanType.HasAnchors() != session.Policy.IsAnchorChannel() {
   194  		log.Criticalf("Invalid task (has_anchors=%t) for session "+
   195  			"(has_anchors=%t)", t.chanType.HasAnchors(),
   196  			session.Policy.IsAnchorChannel())
   197  	}
   198  
   199  	// Now, compute the output values depending on whether FlagReward is set
   200  	// in the current session's policy.
   201  	outputs, err := session.Policy.ComputeJusticeTxOuts(
   202  		t.totalAmt, sizeEstimate.Size(),
   203  		t.sweepPkScript, session.RewardPkScript,
   204  	)
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	t.blobType = session.Policy.BlobType
   210  	t.outputs = outputs
   211  
   212  	return nil
   213  }
   214  
   215  // craftSessionPayload is the final stage for a backupTask, and generates the
   216  // encrypted payload and breach hint that should be sent to the tower. This
   217  // method computes the final justice transaction using the bound
   218  // session-dependent variables, and signs the resulting transaction. The
   219  // required pieces from signatures, witness scripts, etc are then packaged into
   220  // a JusticeKit and encrypted using the breach transaction's key.
   221  func (t *backupTask) craftSessionPayload(
   222  	signer input.Signer) (blob.BreachHint, []byte, error) {
   223  
   224  	var hint blob.BreachHint
   225  
   226  	// First, copy over the sweep pkscript, the pubkeys used to derive the
   227  	// to-local script, and the remote CSV delay.
   228  	keyRing := t.breachInfo.KeyRing
   229  	justiceKit := &blob.JusticeKit{
   230  		BlobType:         t.blobType,
   231  		SweepAddress:     t.sweepPkScript,
   232  		RevocationPubKey: toBlobPubKey(keyRing.RevocationKey),
   233  		LocalDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
   234  		CSVDelay:         t.breachInfo.RemoteDelay,
   235  	}
   236  
   237  	// If this commitment has an output that pays to us, copy the to-remote
   238  	// pubkey into the justice kit. This serves as the indicator to the
   239  	// tower that we expect the breaching transaction to have a non-dust
   240  	// output to spend from.
   241  	if t.toRemoteInput != nil {
   242  		justiceKit.CommitToRemotePubKey = toBlobPubKey(
   243  			keyRing.ToRemoteKey,
   244  		)
   245  	}
   246  
   247  	// Now, begin construction of the justice transaction. We'll start with
   248  	// a version 2 transaction.
   249  	justiceTxn := wire.NewMsgTx()
   250  	justiceTxn.Version = 2
   251  
   252  	// Next, add the non-dust inputs that were derived from the breach
   253  	// information. This will either be contain both the to-local and
   254  	// to-remote outputs, or only be the to-local output.
   255  	inputs := t.inputs()
   256  	for prevOutPoint, input := range inputs {
   257  		justiceTxn.AddTxIn(&wire.TxIn{
   258  			PreviousOutPoint: prevOutPoint,
   259  			Sequence:         input.BlocksToMaturity(),
   260  		})
   261  	}
   262  
   263  	// Add the sweep output paying directly to the user and possibly a
   264  	// reward output, using the outputs computed when the task was bound.
   265  	justiceTxn.TxOut = t.outputs
   266  
   267  	// Sort the justice transaction according to BIP69.
   268  	txsort.InPlaceSort(justiceTxn)
   269  
   270  	// Check that the justice transaction meets basic validity requirements
   271  	// before attempting to attach the witnesses.
   272  	if err := standalone.CheckTransactionSanity(justiceTxn, uint64(t.chainParams.MaxTxSize)); err != nil {
   273  		return hint, nil, err
   274  	}
   275  
   276  	// Since the transaction inputs could have been reordered as a result
   277  	// of the BIP69 sort, create an index mapping each prevout to it's new
   278  	// index.
   279  	inputIndex := make(map[wire.OutPoint]int)
   280  	for i, txIn := range justiceTxn.TxIn {
   281  		inputIndex[txIn.PreviousOutPoint] = i
   282  	}
   283  
   284  	// Now, iterate through the list of inputs that were initially added to
   285  	// the transaction and store the computed witness within the justice
   286  	// kit.
   287  	for _, inp := range inputs {
   288  		// Lookup the input's new post-sort position.
   289  		i := inputIndex[*inp.OutPoint()]
   290  
   291  		// Construct the full witness required to spend this input.
   292  		inputScript, err := inp.CraftInputScript(
   293  			signer, justiceTxn, i,
   294  		)
   295  		if err != nil {
   296  			return hint, nil, err
   297  		}
   298  
   299  		// Parse the DER-encoded signature from the first position of
   300  		// the resulting witness. We trim an extra byte to remove the
   301  		// sighash flag.
   302  		witness := inputScript.Witness
   303  		rawSignature := witness[0][:len(witness[0])-1]
   304  
   305  		// Reencode the DER signature into a fixed-size 64 byte
   306  		// signature.
   307  		signature, err := lnwire.NewSigFromRawSignature(rawSignature)
   308  		if err != nil {
   309  			return hint, nil, err
   310  		}
   311  
   312  		// Finally, copy the serialized signature into the justice kit,
   313  		// using the input's witness type to select the appropriate
   314  		// field.
   315  		switch inp.WitnessType() {
   316  		case input.CommitmentRevoke:
   317  			copy(justiceKit.CommitToLocalSig[:], signature[:])
   318  
   319  		case input.CommitSpendNoDelayTweakless:
   320  			fallthrough
   321  		case input.CommitmentNoDelay:
   322  			fallthrough
   323  		case input.CommitmentToRemoteConfirmed:
   324  			copy(justiceKit.CommitToRemoteSig[:], signature[:])
   325  		default:
   326  			return hint, nil, fmt.Errorf("invalid witness type: %v",
   327  				inp.WitnessType())
   328  		}
   329  	}
   330  
   331  	breachTxID := t.breachInfo.BreachTransaction.TxHash()
   332  
   333  	// Compute the breach key as SHA256(txid).
   334  	hint, key := blob.NewBreachHintAndKeyFromHash(&breachTxID)
   335  
   336  	// Then, we'll encrypt the computed justice kit using the full breach
   337  	// transaction id, which will allow the tower to recover the contents
   338  	// after the transaction is seen in the chain or mempool.
   339  	encBlob, err := justiceKit.Encrypt(key)
   340  	if err != nil {
   341  		return hint, nil, err
   342  	}
   343  
   344  	return hint, encBlob, nil
   345  }
   346  
   347  // toBlobPubKey serializes the given pubkey into a blob.PubKey that can be set
   348  // as a field on a blob.JusticeKit.
   349  func toBlobPubKey(pubKey *secp256k1.PublicKey) blob.PubKey {
   350  	var blobPubKey blob.PubKey
   351  	copy(blobPubKey[:], pubKey.SerializeCompressed())
   352  	return blobPubKey
   353  }