github.com/decred/dcrlnd@v0.7.6/lnwallet/chanvalidate/validate.go (about) 1 package chanvalidate 2 3 import ( 4 "bytes" 5 "fmt" 6 7 "github.com/decred/dcrd/dcrutil/v4" 8 "github.com/decred/dcrd/txscript/v4" 9 "github.com/decred/dcrd/wire" 10 "github.com/decred/dcrlnd/input" 11 "github.com/decred/dcrlnd/lnwire" 12 ) 13 14 var ( 15 // ErrInvalidOutPoint is returned when the ChanLocator is unable to 16 // find the target outpoint. 17 ErrInvalidOutPoint = fmt.Errorf("output meant to create channel cannot " + 18 "be found") 19 20 // ErrWrongPkScript is returned when the alleged funding transaction is 21 // found to have an incorrect pkSript. 22 ErrWrongPkScript = fmt.Errorf("wrong pk script") 23 24 // ErrInvalidSize is returned when the alleged funding transaction 25 // output has the wrong size (channel capacity). 26 ErrInvalidSize = fmt.Errorf("channel has wrong size") 27 ) 28 29 // ErrScriptValidateError is returned when Script VM validation fails for an 30 // alleged channel output. 31 type ErrScriptValidateError struct { 32 err error 33 } 34 35 // Error returns a human readable string describing the error. 36 func (e *ErrScriptValidateError) Error() string { 37 return fmt.Sprintf("script validation failed: %v", e.err) 38 } 39 40 // Unwrap returns the underlying wrapped VM execution failure error. 41 func (e *ErrScriptValidateError) Unwrap() error { 42 return e.err 43 } 44 45 // ChanLocator abstracts away obtaining the output that created the channel, as 46 // well as validating its existence given the funding transaction. We need 47 // this as there are several ways (outpoint, short chan ID) to identify the 48 // output of a channel given the funding transaction. 49 type ChanLocator interface { 50 // Locate attempts to locate the funding output within the funding 51 // transaction. It also returns the final out point of the channel 52 // which uniquely identifies the output which creates the channel. If 53 // the target output cannot be found, or cannot exist on the funding 54 // transaction, then an error is to be returned. 55 Locate(*wire.MsgTx) (*wire.TxOut, *wire.OutPoint, error) 56 } 57 58 // OutPointChanLocator is an implementation of the ChanLocator that can be used 59 // when one already knows the expected chan point. 60 type OutPointChanLocator struct { 61 // ChanPoint is the expected chan point. 62 ChanPoint wire.OutPoint 63 } 64 65 // Locate attempts to locate the funding output within the passed funding 66 // transaction. 67 // 68 // NOTE: Part of the ChanLocator interface. 69 func (o *OutPointChanLocator) Locate(fundingTx *wire.MsgTx) ( 70 *wire.TxOut, *wire.OutPoint, error) { 71 72 // If the expected index is greater than the amount of output in the 73 // transaction, then we'll reject this channel as it's invalid. 74 if int(o.ChanPoint.Index) >= len(fundingTx.TxOut) { 75 return nil, nil, ErrInvalidOutPoint 76 } 77 78 // As an extra sanity check, we'll also ensure the txid hash matches. 79 fundingHash := fundingTx.TxHash() 80 if !bytes.Equal(fundingHash[:], o.ChanPoint.Hash[:]) { 81 return nil, nil, ErrInvalidOutPoint 82 } 83 84 return fundingTx.TxOut[o.ChanPoint.Index], &o.ChanPoint, nil 85 } 86 87 // ShortChanIDChanLocator is an implementation of the ChanLocator that can be 88 // used when one only knows the short channel ID of a channel. This should be 89 // used in contexts when one is verifying a 3rd party channel. 90 type ShortChanIDChanLocator struct { 91 // ID is the short channel ID of the target channel. 92 ID lnwire.ShortChannelID 93 } 94 95 // Locate attempts to locate the funding output within the passed funding 96 // transaction. 97 // 98 // NOTE: Part of the ChanLocator interface. 99 func (s *ShortChanIDChanLocator) Locate(fundingTx *wire.MsgTx) ( 100 *wire.TxOut, *wire.OutPoint, error) { 101 102 // If the expected index is greater than the amount of output in the 103 // transaction, then we'll reject this channel as it's invalid. 104 outputIndex := s.ID.TxPosition 105 if int(outputIndex) >= len(fundingTx.TxOut) { 106 return nil, nil, ErrInvalidOutPoint 107 } 108 109 chanPoint := wire.OutPoint{ 110 Hash: fundingTx.TxHash(), 111 Index: uint32(outputIndex), 112 } 113 114 return fundingTx.TxOut[outputIndex], &chanPoint, nil 115 } 116 117 // CommitmentContext is optional validation context that can be passed into the 118 // main Validate for self-owned channel. The information in this context allows 119 // us to fully verify out initial commitment spend based on the on-chain state 120 // of the funding output. 121 type CommitmentContext struct { 122 // Value is the known size of the channel. 123 Value dcrutil.Amount 124 125 // FullySignedCommitTx is the fully signed commitment transaction. This 126 // should include a valid witness. 127 FullySignedCommitTx *wire.MsgTx 128 } 129 130 // Context is the main validation contxet. For a given channel, all fields but 131 // the optional CommitCtx should be populated based on existing 132 // known-to-be-valid parameters. 133 type Context struct { 134 // Locator is a concrete implementation of the ChanLocator interface. 135 Locator ChanLocator 136 137 // MultiSigPkScript is the fully serialized witness script of the 138 // multi-sig output. This is the final witness program that should be 139 // found in the funding output. 140 MultiSigPkScript []byte 141 142 // FundingTx is channel funding transaction as found confirmed in the 143 // chain. 144 FundingTx *wire.MsgTx 145 146 // CommitCtx is an optional additional set of validation context 147 // required to validate a self-owned channel. If present, then a full 148 // Script VM validation will be performed. 149 CommitCtx *CommitmentContext 150 } 151 152 // Validate given the specified context, this function validates that the 153 // alleged channel is well formed, and spendable (if the optional CommitCtx is 154 // specified). If this method returns an error, then the alleged channel is 155 // invalid and should be abandoned immediately. 156 func Validate(ctx *Context) (*wire.OutPoint, error) { 157 // First, we'll attempt to locate the target outpoint in the funding 158 // transaction. If this returns an error, then we know that the 159 // outpoint doesn't actually exist, so we'll exit early. 160 fundingOutput, chanPoint, err := ctx.Locator.Locate( 161 ctx.FundingTx, 162 ) 163 if err != nil { 164 return nil, err 165 } 166 167 // The scripts should match up exactly, otherwise the channel is 168 // invalid. 169 fundingScript := fundingOutput.PkScript 170 if !bytes.Equal(ctx.MultiSigPkScript, fundingScript) { 171 return nil, ErrWrongPkScript 172 } 173 174 // If there's no commitment context, then we're done here as this is a 175 // 3rd party channel. 176 if ctx.CommitCtx == nil { 177 return chanPoint, nil 178 } 179 180 // Now that we know this is our channel, we'll verify the amount of the 181 // created output against our expected size of the channel. 182 fundingValue := fundingOutput.Value 183 if dcrutil.Amount(fundingValue) != ctx.CommitCtx.Value { 184 return nil, ErrInvalidSize 185 } 186 187 // If we reach this point, then all other checks have succeeded, so 188 // we'll now attempt a full Script VM execution to ensure that we're 189 // able to close the channel using this initial state. 190 vm, err := txscript.NewEngine( 191 ctx.MultiSigPkScript, ctx.CommitCtx.FullySignedCommitTx, 192 0, input.ScriptVerifyFlags, fundingOutput.Version, nil, 193 ) 194 if err != nil { 195 return nil, err 196 } 197 198 // Finally, we'll attempt to verify our full spend, if this fails then 199 // the channel is definitely invalid. 200 err = vm.Execute() 201 if err != nil { 202 return nil, &ErrScriptValidateError{err: err} 203 } 204 205 return chanPoint, nil 206 }