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