github.com/decred/dcrlnd@v0.7.6/watchtower/lookout/justice_descriptor.go (about)

     1  package lookout
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/decred/dcrd/blockchain/standalone/v2"
     8  	"github.com/decred/dcrd/chaincfg/v3"
     9  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    10  	"github.com/decred/dcrd/dcrutil/v4"
    11  	"github.com/decred/dcrd/dcrutil/v4/txsort"
    12  	"github.com/decred/dcrd/txscript/v4"
    13  	"github.com/decred/dcrd/wire"
    14  	"github.com/decred/dcrlnd/input"
    15  	"github.com/decred/dcrlnd/watchtower/blob"
    16  	"github.com/decred/dcrlnd/watchtower/wtdb"
    17  )
    18  
    19  var (
    20  	// ErrOutputNotFound signals that the breached output could not be found
    21  	// on the commitment transaction.
    22  	ErrOutputNotFound = errors.New("unable to find output on commit tx")
    23  
    24  	// ErrUnknownSweepAddrType signals that client provided an output that
    25  	// was not p2wkh or p2wsh.
    26  	ErrUnknownSweepAddrType = errors.New("sweep addr is not p2wkh or p2wsh")
    27  )
    28  
    29  // JusticeDescriptor contains the information required to sweep a breached
    30  // channel on behalf of a victim. It supports the ability to create the justice
    31  // transaction that sweeps the commitments and recover a cut of the channel for
    32  // the watcher's eternal vigilance.
    33  type JusticeDescriptor struct {
    34  	// BreachedCommitTx is the commitment transaction that caused the breach
    35  	// to be detected.
    36  	BreachedCommitTx *wire.MsgTx
    37  
    38  	// SessionInfo contains the contract with the watchtower client and
    39  	// the prenegotiated terms they agreed to.
    40  	SessionInfo *wtdb.SessionInfo
    41  
    42  	// JusticeKit contains the decrypted blob and information required to
    43  	// construct the transaction scripts and witnesses.
    44  	JusticeKit *blob.JusticeKit
    45  
    46  	// NetParams contains a reference to the chain parameters where the
    47  	// transactions will be broadcast.
    48  	NetParams *chaincfg.Params
    49  }
    50  
    51  // breachedInput contains the required information to construct and spend
    52  // breached outputs on a commitment transaction.
    53  type breachedInput struct {
    54  	txOut    *wire.TxOut
    55  	outPoint wire.OutPoint
    56  	witness  [][]byte
    57  	sequence uint32
    58  }
    59  
    60  // commitToLocalInput extracts the information required to spend the commit
    61  // to-local output.
    62  func (p *JusticeDescriptor) commitToLocalInput() (*breachedInput, error) {
    63  	// Retrieve the to-local witness script from the justice kit.
    64  	toLocalScript, err := p.JusticeKit.CommitToLocalWitnessScript()
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	// Compute the witness script hash, which will be used to locate the
    70  	// input on the breaching commitment transaction.
    71  	toLocalWitnessHash, err := input.ScriptHashPkScript(toLocalScript)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	// Locate the to-local output on the breaching commitment transaction.
    77  	toLocalIndex, toLocalTxOut, err := findTxOutByPkScript(
    78  		p.BreachedCommitTx, toLocalWitnessHash,
    79  	)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	// Construct the to-local outpoint that will be spent in the justice
    85  	// transaction.
    86  	toLocalOutPoint := wire.OutPoint{
    87  		Hash:  p.BreachedCommitTx.TxHash(),
    88  		Index: toLocalIndex,
    89  	}
    90  
    91  	// Retrieve to-local witness stack, which primarily includes a signature
    92  	// under the revocation pubkey.
    93  	witnessStack, err := p.JusticeKit.CommitToLocalRevokeWitnessStack()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	return &breachedInput{
    99  		txOut:    toLocalTxOut,
   100  		outPoint: toLocalOutPoint,
   101  		witness:  buildWitness(witnessStack, toLocalScript),
   102  	}, nil
   103  }
   104  
   105  // commitToRemoteInput extracts the information required to spend the commit
   106  // to-remote output.
   107  func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) {
   108  	// Retrieve the to-remote redeem script from the justice kit.
   109  	toRemoteRedeemScript, err := p.JusticeKit.CommitToRemoteWitnessScript()
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	var (
   115  		toRemotePkScript []byte
   116  		toRemoteSequence uint32
   117  	)
   118  	if p.JusticeKit.BlobType.IsAnchorChannel() {
   119  		// The P2SH PKScript can be directly computed from the redeem
   120  		// script.
   121  		toRemotePkScript, err = input.ScriptHashPkScript(
   122  			toRemoteRedeemScript,
   123  		)
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  
   128  		toRemoteSequence = 1
   129  	} else {
   130  		// Since the to-remote witness script should just be a regular p2pkh
   131  		// output, we'll parse it to retrieve the public key.
   132  		toRemotePubKey, err := secp256k1.ParsePubKey(toRemoteRedeemScript)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  
   137  		// Compute the PKScript hash from the to-remote pubkey, which will
   138  		// be used to locate the input on the breach commitment transaction.
   139  		toRemotePkScript, err = input.CommitScriptUnencumbered(
   140  			toRemotePubKey,
   141  		)
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  	}
   146  
   147  	// Locate the to-remote output on the breaching commitment transaction.
   148  	toRemoteIndex, toRemoteTxOut, err := findTxOutByPkScript(
   149  		p.BreachedCommitTx, toRemotePkScript,
   150  	)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	// Construct the to-remote outpoint which will be spent in the justice
   156  	// transaction.
   157  	toRemoteOutPoint := wire.OutPoint{
   158  		Hash:  p.BreachedCommitTx.TxHash(),
   159  		Index: toRemoteIndex,
   160  	}
   161  
   162  	// Retrieve the to-remote witness stack, which is just a signature under
   163  	// the to-remote pubkey.
   164  	witnessStack, err := p.JusticeKit.CommitToRemoteWitnessStack()
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	return &breachedInput{
   170  		txOut:    toRemoteTxOut,
   171  		outPoint: toRemoteOutPoint,
   172  		witness:  buildWitness(witnessStack, toRemoteRedeemScript),
   173  		sequence: toRemoteSequence,
   174  	}, nil
   175  }
   176  
   177  // assembleJusticeTxn accepts the breached inputs recovered from state update
   178  // and attempts to construct the justice transaction that sweeps the victims
   179  // funds to their wallet and claims the watchtower's reward.
   180  func (p *JusticeDescriptor) assembleJusticeTxn(txSize int64,
   181  	inputs ...*breachedInput) (*wire.MsgTx, error) {
   182  
   183  	justiceTxn := wire.NewMsgTx()
   184  	justiceTxn.Version = 2
   185  
   186  	// First, construct add the breached inputs to our justice transaction
   187  	// and compute the total amount that will be swept.
   188  	var totalAmt dcrutil.Amount
   189  	for _, input := range inputs {
   190  		totalAmt += dcrutil.Amount(input.txOut.Value)
   191  		justiceTxn.AddTxIn(&wire.TxIn{
   192  			PreviousOutPoint: input.outPoint,
   193  			ValueIn:          input.txOut.Value,
   194  			Sequence:         input.sequence,
   195  		})
   196  	}
   197  
   198  	// Using the session's policy, compute the outputs that should be added
   199  	// to the justice transaction. In the case of an altruist sweep, there
   200  	// will be a single output paying back to the victim. Otherwise for a
   201  	// reward sweep, there will be two outputs, one of which pays back to
   202  	// the victim while the other gives a cut to the tower.
   203  	outputs, err := p.SessionInfo.Policy.ComputeJusticeTxOuts(
   204  		totalAmt, txSize, p.JusticeKit.SweepAddress[:],
   205  		p.SessionInfo.RewardAddress,
   206  	)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  
   211  	// Attach the computed txouts to the justice transaction.
   212  	justiceTxn.TxOut = outputs
   213  
   214  	// Apply a BIP69 sort to the resulting transaction.
   215  	txsort.InPlaceSort(justiceTxn)
   216  
   217  	if err := standalone.CheckTransactionSanity(justiceTxn, uint64(p.NetParams.MaxTxSize)); err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	// Since the transaction inputs could have been reordered as a result
   222  	// of the BIP69 sort, create an index mapping each prevout to it's new
   223  	// index.
   224  	inputIndex := make(map[wire.OutPoint]int)
   225  	for i, txIn := range justiceTxn.TxIn {
   226  		inputIndex[txIn.PreviousOutPoint] = i
   227  	}
   228  
   229  	// Attach each of the provided witnesses to the transaction.
   230  	for inidx, inp := range inputs {
   231  		// Lookup the input's new post-sort position.
   232  		i := inputIndex[inp.outPoint]
   233  
   234  		justiceTxn.TxIn[i].SignatureScript, err = input.WitnessStackToSigScript(
   235  			inp.witness,
   236  		)
   237  		if err != nil {
   238  			return nil, err
   239  		}
   240  
   241  		// Validate the reconstructed witnesses to ensure they are valid
   242  		// for the breached inputs.
   243  		vm, err := txscript.NewEngine(
   244  			inp.txOut.PkScript, justiceTxn, i,
   245  			input.ScriptVerifyFlags,
   246  			inp.txOut.Version, nil,
   247  		)
   248  		if err != nil {
   249  			return nil, err
   250  		}
   251  		if err := vm.Execute(); err != nil {
   252  			return nil, fmt.Errorf("validation error on input %d of justice tx: %v", inidx, err)
   253  		}
   254  	}
   255  
   256  	return justiceTxn, nil
   257  }
   258  
   259  // CreateJusticeTxn computes the justice transaction that sweeps a breaching
   260  // commitment transaction. The justice transaction is constructed by assembling
   261  // the witnesses using data provided by the client in a prior state update.
   262  //
   263  // NOTE: An older version of ToLocalPenaltyWitnessSize underestimated the size
   264  // of the witness by one byte, which could cause the signature(s) to break if
   265  // the tower is reconstructing with the newer constant because the output values
   266  // might differ. This method retains that original behavior to not invalidate
   267  // historical signatures.
   268  func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) {
   269  	var (
   270  		sweepInputs  = make([]*breachedInput, 0, 2)
   271  		sizeEstimate input.TxSizeEstimator
   272  	)
   273  
   274  	// Add the sweep address's contribution, depending on whether it is a
   275  	// p2pkh or p2sh output.
   276  	switch int64(len(p.JusticeKit.SweepAddress)) {
   277  	case input.P2PKHPkScriptSize:
   278  		sizeEstimate.AddP2PKHOutput()
   279  
   280  	case input.P2SHPkScriptSize:
   281  		sizeEstimate.AddP2SHOutput()
   282  
   283  	default:
   284  		return nil, ErrUnknownSweepAddrType
   285  	}
   286  
   287  	// Add our reward address to the weight estimate if the policy's blob
   288  	// type specifies a reward output.
   289  	if p.SessionInfo.Policy.BlobType.Has(blob.FlagReward) {
   290  		sizeEstimate.AddP2PKHOutput()
   291  	}
   292  
   293  	// Assemble the breached to-local output from the justice descriptor and
   294  	// add it to our size estimate.
   295  	toLocalInput, err := p.commitToLocalInput()
   296  	if err != nil {
   297  		return nil, err
   298  	}
   299  	sizeEstimate.AddCustomInput(input.ToLocalPenaltySigScriptSize)
   300  	sweepInputs = append(sweepInputs, toLocalInput)
   301  
   302  	// If the justice kit specifies that we have to sweep the to-remote
   303  	// output, we'll also try to assemble the output and add it to size
   304  	// estimate if successful.
   305  	if p.JusticeKit.HasCommitToRemoteOutput() {
   306  		toRemoteInput, err := p.commitToRemoteInput()
   307  		if err != nil {
   308  			return nil, err
   309  		}
   310  		sweepInputs = append(sweepInputs, toRemoteInput)
   311  
   312  		if p.JusticeKit.BlobType.IsAnchorChannel() {
   313  			sizeEstimate.AddCustomInput(input.ToRemoteConfirmedWitnessSize)
   314  		} else {
   315  			sizeEstimate.AddP2PKHInput()
   316  		}
   317  	}
   318  
   319  	// TODO(conner): sweep htlc outputs
   320  
   321  	txSize := sizeEstimate.Size()
   322  
   323  	return p.assembleJusticeTxn(txSize, sweepInputs...)
   324  }
   325  
   326  // findTxOutByPkScript searches the given transaction for an output whose
   327  // pkscript matches the query. If one is found, the TxOut is returned along with
   328  // the index.
   329  //
   330  // NOTE: The search stops after the first match is found.
   331  func findTxOutByPkScript(txn *wire.MsgTx,
   332  	pkScript []byte) (uint32, *wire.TxOut, error) {
   333  
   334  	found, index := input.FindScriptOutputIndex(txn, pkScript)
   335  	if !found {
   336  		return 0, nil, ErrOutputNotFound
   337  	}
   338  
   339  	return index, txn.TxOut[index], nil
   340  }
   341  
   342  // buildWitness appends the witness script to a given witness stack.
   343  func buildWitness(witnessStack [][]byte, witnessScript []byte) [][]byte {
   344  	witness := make([][]byte, len(witnessStack)+1)
   345  	lastIdx := copy(witness, witnessStack)
   346  	witness[lastIdx] = witnessScript
   347  
   348  	return witness
   349  }