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 }