mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 18:00:11 +01:00
e781b66e2f
In this commit, we implement the new BIP 341+342 taproot sighash digest computation. The digest is similar, but re-orders some fragments and also starts to commit to the input values of all the transactions in the SIGHASH_ALL case. A new implicit sighash flag, SIGHASH_DEFAULT has been added that allows signatures to always be 64-bytes for the common case. The hashcache has been updated as well to store both the v0 and v1 mid state hashes. The v0 hashes are a double-sha of the contents, while the v1 hash is a single sha. As a result, if a transaction spends both v0 and v1 inputs, then we 're able to re-use all the intermediate hashes. As the sighash computation needs the input values and scripts, we create an abstraction: the PrevOutFetcher to give the caller flexibility w.r.t how this is done. We also create a `CannedPrevOutputFetcher` that holds the information in a map for a single input. A series of function options are also added to allow re-use of the same base sig hash calculation for both BIP 341 and 342.
355 lines
12 KiB
Go
355 lines
12 KiB
Go
// 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/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/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()
|
|
}
|