forked from btcsuite/btcd
-
Notifications
You must be signed in to change notification settings - Fork 1
/
hashcache.go
354 lines (302 loc) · 11.6 KB
/
hashcache.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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package txscript
import (
"bytes"
"encoding/binary"
"math"
"sync"
"github.com/dogecoinw/doged/chaincfg/chainhash"
"github.com/dogecoinw/doged/wire"
)
// calcHashPrevOuts calculates a single hash of all the previous outputs
// (txid:index) referenced within the passed transaction. This calculated hash
// can be re-used when validating all inputs spending segwit outputs, with a
// signature hash type of SigHashAll. This allows validation to re-use previous
// hashing computation, reducing the complexity of validating SigHashAll inputs
// from O(N^2) to O(N).
func calcHashPrevOuts(tx *wire.MsgTx) chainhash.Hash {
var b bytes.Buffer
for _, in := range tx.TxIn {
// First write out the 32-byte transaction ID one of whose
// outputs are being referenced by this input.
b.Write(in.PreviousOutPoint.Hash[:])
// Next, we'll encode the index of the referenced output as a
// little endian integer.
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], in.PreviousOutPoint.Index)
b.Write(buf[:])
}
return chainhash.HashH(b.Bytes())
}
// calcHashSequence computes an aggregated hash of each of the sequence numbers
// within the inputs of the passed transaction. This single hash can be re-used
// when validating all inputs spending segwit outputs, which include signatures
// using the SigHashAll sighash type. This allows validation to re-use previous
// hashing computation, reducing the complexity of validating SigHashAll inputs
// from O(N^2) to O(N).
func calcHashSequence(tx *wire.MsgTx) chainhash.Hash {
var b bytes.Buffer
for _, in := range tx.TxIn {
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], in.Sequence)
b.Write(buf[:])
}
return chainhash.HashH(b.Bytes())
}
// calcHashOutputs computes a hash digest of all outputs created by the
// transaction encoded using the wire format. This single hash can be re-used
// when validating all inputs spending witness programs, which include
// signatures using the SigHashAll sighash type. This allows computation to be
// cached, reducing the total hashing complexity from O(N^2) to O(N).
func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
var b bytes.Buffer
for _, out := range tx.TxOut {
wire.WriteTxOut(&b, 0, 0, out)
}
return chainhash.HashH(b.Bytes())
}
// PrevOutputFetcher is an interface used to supply the sighash cache with the
// previous output information needed to calculate the pre-computed sighash
// midstate for taproot transactions.
type PrevOutputFetcher interface {
// FetchPrevOutput attempts to fetch the previous output referenced by
// the passed outpoint. A nil value will be returned if the passed
// outpoint doesn't exist.
FetchPrevOutput(wire.OutPoint) *wire.TxOut
}
// CannedPrevOutputFetcher is an implementation of PrevOutputFetcher that only
// is able to return information for a single previous output.
type CannedPrevOutputFetcher struct {
pkScript []byte
amt int64
}
// NewCannedPrevOutputFetcher returns an instance of a CannedPrevOutputFetcher
// that can only return the TxOut defined by the passed script and amount.
func NewCannedPrevOutputFetcher(script []byte, amt int64) *CannedPrevOutputFetcher {
return &CannedPrevOutputFetcher{
pkScript: script,
amt: amt,
}
}
// FetchPrevOutput attempts to fetch the previous output referenced by the
// passed outpoint.
//
// NOTE: This is a part of the PrevOutputFetcher interface.
func (c *CannedPrevOutputFetcher) FetchPrevOutput(wire.OutPoint) *wire.TxOut {
return &wire.TxOut{
PkScript: c.pkScript,
Value: c.amt,
}
}
// A compile-time assertion to ensure that CannedPrevOutputFetcher matches the
// PrevOutputFetcher interface.
var _ PrevOutputFetcher = (*CannedPrevOutputFetcher)(nil)
// MultiPrevOutFetcher is a custom implementation of the PrevOutputFetcher
// backed by a key-value map of prevouts to outputs.
type MultiPrevOutFetcher struct {
prevOuts map[wire.OutPoint]*wire.TxOut
}
// NewMultiPrevOutFetcher returns an instance of a PrevOutputFetcher that's
// backed by an optional map which is used as an input source. The
func NewMultiPrevOutFetcher(prevOuts map[wire.OutPoint]*wire.TxOut) *MultiPrevOutFetcher {
if prevOuts == nil {
prevOuts = make(map[wire.OutPoint]*wire.TxOut)
}
return &MultiPrevOutFetcher{
prevOuts: prevOuts,
}
}
// FetchPrevOutput attempts to fetch the previous output referenced by the
// passed outpoint.
//
// NOTE: This is a part of the CannedPrevOutputFetcher interface.
func (m *MultiPrevOutFetcher) FetchPrevOutput(op wire.OutPoint) *wire.TxOut {
return m.prevOuts[op]
}
// AddPrevOut adds a new prev out, tx out pair to the backing map.
func (m *MultiPrevOutFetcher) AddPrevOut(op wire.OutPoint, txOut *wire.TxOut) {
m.prevOuts[op] = txOut
}
// Merge merges two instances of a MultiPrevOutFetcher into a single source.
func (m *MultiPrevOutFetcher) Merge(other *MultiPrevOutFetcher) {
for k, v := range other.prevOuts {
m.prevOuts[k] = v
}
}
// A compile-time assertion to ensure that MultiPrevOutFetcher matches the
// PrevOutputFetcher interface.
var _ PrevOutputFetcher = (*MultiPrevOutFetcher)(nil)
// calcHashInputAmounts computes a hash digest of the input amounts of all
// inputs referenced in the passed transaction. This hash pre computation is only
// used for validating taproot inputs.
func calcHashInputAmounts(tx *wire.MsgTx, inputFetcher PrevOutputFetcher) chainhash.Hash {
var b bytes.Buffer
for _, txIn := range tx.TxIn {
prevOut := inputFetcher.FetchPrevOutput(txIn.PreviousOutPoint)
_ = binary.Write(&b, binary.LittleEndian, prevOut.Value)
}
return chainhash.HashH(b.Bytes())
}
// calcHashInputAmts computes the hash digest of all the previous input scripts
// referenced by the passed transaction. This hash pre computation is only used
// for validating taproot inputs.
func calcHashInputScripts(tx *wire.MsgTx, inputFetcher PrevOutputFetcher) chainhash.Hash {
var b bytes.Buffer
for _, txIn := range tx.TxIn {
prevOut := inputFetcher.FetchPrevOutput(txIn.PreviousOutPoint)
_ = wire.WriteVarBytes(&b, 0, prevOut.PkScript)
}
return chainhash.HashH(b.Bytes())
}
// SegwitSigHashMidstate is the sighash midstate used in the base segwit
// sighash calculation as defined in BIP 143.
type SegwitSigHashMidstate struct {
HashPrevOutsV0 chainhash.Hash
HashSequenceV0 chainhash.Hash
HashOutputsV0 chainhash.Hash
}
// TaprootSigHashMidState is the sighash midstate used to compute taproot and
// tapscript signatures as defined in BIP 341.
type TaprootSigHashMidState struct {
HashPrevOutsV1 chainhash.Hash
HashSequenceV1 chainhash.Hash
HashOutputsV1 chainhash.Hash
HashInputScriptsV1 chainhash.Hash
HashInputAmountsV1 chainhash.Hash
}
// TxSigHashes houses the partial set of sighashes introduced within BIP0143.
// This partial set of sighashes may be re-used within each input across a
// transaction when validating all inputs. As a result, validation complexity
// for SigHashAll can be reduced by a polynomial factor.
type TxSigHashes struct {
SegwitSigHashMidstate
TaprootSigHashMidState
}
// NewTxSigHashes computes, and returns the cached sighashes of the given
// transaction.
func NewTxSigHashes(tx *wire.MsgTx,
inputFetcher PrevOutputFetcher) *TxSigHashes {
var (
sigHashes TxSigHashes
zeroHash chainhash.Hash
)
// Base segwit (witness version v0), and taproot (witness version v1)
// differ in how the set of pre-computed cached sighash midstate is
// computed. For taproot, the prevouts, sequence, and outputs are
// computed as normal, but a single sha256 hash invocation is used. In
// addition, the hashes of all the previous input amounts and scripts
// are included as well.
//
// Based on the above distinction, we'll run through all the referenced
// inputs to determine what we need to compute.
var hasV0Inputs, hasV1Inputs bool
for _, txIn := range tx.TxIn {
// If this is a coinbase input, then we know that we only need
// the v0 midstate (though it won't be used) in this instance.
outpoint := txIn.PreviousOutPoint
if outpoint.Index == math.MaxUint32 && outpoint.Hash == zeroHash {
hasV0Inputs = true
continue
}
prevOut := inputFetcher.FetchPrevOutput(outpoint)
// If this is spending a script that looks like a taproot output,
// then we'll need to pre-compute the extra taproot data.
if IsPayToTaproot(prevOut.PkScript) {
hasV1Inputs = true
} else {
// Otherwise, we'll assume we need the v0 sighash midstate.
hasV0Inputs = true
}
// If the transaction has _both_ v0 and v1 inputs, then we can stop
// here.
if hasV0Inputs && hasV1Inputs {
break
}
}
// Now that we know which cached midstate we need to calculate, we can
// go ahead and do so.
//
// First, we can calculate the information that both segwit v0 and v1
// need: the prevout, sequence and output hashes. For v1 the only
// difference is that this is a single instead of a double hash.
//
// Both v0 and v1 share this base data computed using a sha256 single
// hash.
sigHashes.HashPrevOutsV1 = calcHashPrevOuts(tx)
sigHashes.HashSequenceV1 = calcHashSequence(tx)
sigHashes.HashOutputsV1 = calcHashOutputs(tx)
// The v0 data is the same as the v1 (newer data) but it uses a double
// hash instead.
if hasV0Inputs {
sigHashes.HashPrevOutsV0 = chainhash.HashH(
sigHashes.HashPrevOutsV1[:],
)
sigHashes.HashSequenceV0 = chainhash.HashH(
sigHashes.HashSequenceV1[:],
)
sigHashes.HashOutputsV0 = chainhash.HashH(
sigHashes.HashOutputsV1[:],
)
}
// Finally, we'll compute the taproot specific data if needed.
if hasV1Inputs {
sigHashes.HashInputAmountsV1 = calcHashInputAmounts(
tx, inputFetcher,
)
sigHashes.HashInputScriptsV1 = calcHashInputScripts(
tx, inputFetcher,
)
}
return &sigHashes
}
// HashCache houses a set of partial sighashes keyed by txid. The set of partial
// sighashes are those introduced within BIP0143 by the new more efficient
// sighash digest calculation algorithm. Using this threadsafe shared cache,
// multiple goroutines can safely re-use the pre-computed partial sighashes
// speeding up validation time amongst all inputs found within a block.
type HashCache struct {
sigHashes map[chainhash.Hash]*TxSigHashes
sync.RWMutex
}
// NewHashCache returns a new instance of the HashCache given a maximum number
// of entries which may exist within it at anytime.
func NewHashCache(maxSize uint) *HashCache {
return &HashCache{
sigHashes: make(map[chainhash.Hash]*TxSigHashes, maxSize),
}
}
// AddSigHashes computes, then adds the partial sighashes for the passed
// transaction.
func (h *HashCache) AddSigHashes(tx *wire.MsgTx,
inputFetcher PrevOutputFetcher) {
h.Lock()
h.sigHashes[tx.TxHash()] = NewTxSigHashes(tx, inputFetcher)
h.Unlock()
}
// ContainsHashes returns true if the partial sighashes for the passed
// transaction currently exist within the HashCache, and false otherwise.
func (h *HashCache) ContainsHashes(txid *chainhash.Hash) bool {
h.RLock()
_, found := h.sigHashes[*txid]
h.RUnlock()
return found
}
// GetSigHashes possibly returns the previously cached partial sighashes for
// the passed transaction. This function also returns an additional boolean
// value indicating if the sighashes for the passed transaction were found to
// be present within the HashCache.
func (h *HashCache) GetSigHashes(txid *chainhash.Hash) (*TxSigHashes, bool) {
h.RLock()
item, found := h.sigHashes[*txid]
h.RUnlock()
return item, found
}
// PurgeSigHashes removes all partial sighashes from the HashCache belonging to
// the passed transaction.
func (h *HashCache) PurgeSigHashes(txid *chainhash.Hash) {
h.Lock()
delete(h.sigHashes, *txid)
h.Unlock()
}