mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 01:40:07 +01:00
txscript: implement BIP 341+342 segwit v1 taproot+tapscript
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.
This commit is contained in:
parent
6ecc72e5e6
commit
e781b66e2f
@ -10,9 +10,9 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
)
|
||||
|
||||
// txValidateItem holds a transaction along with which input to validate.
|
||||
@ -74,9 +74,11 @@ out:
|
||||
witness := txIn.Witness
|
||||
pkScript := utxo.PkScript()
|
||||
inputAmount := utxo.Amount()
|
||||
vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
|
||||
txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes,
|
||||
inputAmount)
|
||||
vm, err := txscript.NewEngine(
|
||||
pkScript, txVI.tx.MsgTx(), txVI.txInIndex,
|
||||
v.flags, v.sigCache, txVI.sigHashes,
|
||||
inputAmount, v.utxoView,
|
||||
)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("failed to parse input "+
|
||||
"%s:%d which references output %v - "+
|
||||
@ -201,7 +203,7 @@ func ValidateTransactionScripts(tx *btcutil.Tx, utxoView *UtxoViewpoint,
|
||||
// amongst all worker validation goroutines.
|
||||
if segwitActive && tx.MsgTx().HasWitness() &&
|
||||
!hashCache.ContainsHashes(tx.Hash()) {
|
||||
hashCache.AddSigHashes(tx.MsgTx())
|
||||
hashCache.AddSigHashes(tx.MsgTx(), utxoView)
|
||||
}
|
||||
|
||||
var cachedHashes *txscript.TxSigHashes
|
||||
@ -266,7 +268,7 @@ func checkBlockScripts(block *btcutil.Block, utxoView *UtxoViewpoint,
|
||||
if segwitActive && tx.HasWitness() && hashCache != nil &&
|
||||
!hashCache.ContainsHashes(hash) {
|
||||
|
||||
hashCache.AddSigHashes(tx.MsgTx())
|
||||
hashCache.AddSigHashes(tx.MsgTx(), utxoView)
|
||||
}
|
||||
|
||||
var cachedHashes *txscript.TxSigHashes
|
||||
@ -274,7 +276,9 @@ func checkBlockScripts(block *btcutil.Block, utxoView *UtxoViewpoint,
|
||||
if hashCache != nil {
|
||||
cachedHashes, _ = hashCache.GetSigHashes(hash)
|
||||
} else {
|
||||
cachedHashes = txscript.NewTxSigHashes(tx.MsgTx())
|
||||
cachedHashes = txscript.NewTxSigHashes(
|
||||
tx.MsgTx(), utxoView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,11 @@ package blockchain
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
)
|
||||
|
||||
// txoFlags is a bitmask defining additional information and state for a
|
||||
@ -159,6 +159,23 @@ func (view *UtxoViewpoint) LookupEntry(outpoint wire.OutPoint) *UtxoEntry {
|
||||
return view.entries[outpoint]
|
||||
}
|
||||
|
||||
// FetchPrevOutput fetches the previous output referenced by the passed
|
||||
// outpoint. This is identical to the LookupEntry method, but it returns a
|
||||
// wire.TxOut instead.
|
||||
//
|
||||
// NOTE: This is an implementation of the txscript.PrevOutputFetcher interface.
|
||||
func (view *UtxoViewpoint) FetchPrevOutput(op wire.OutPoint) *wire.TxOut {
|
||||
prevOut := view.entries[op]
|
||||
if prevOut == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &wire.TxOut{
|
||||
Value: prevOut.amount,
|
||||
PkScript: prevOut.PkScript(),
|
||||
}
|
||||
}
|
||||
|
||||
// addTxOut adds the specified output to the view if it is not provably
|
||||
// unspendable. When the view already has an entry for the output, it will be
|
||||
// marked unspent. All fields will be updated for existing entries since it's
|
||||
|
@ -27,12 +27,17 @@ var (
|
||||
// TagBIP0340Nonce is the BIP-0340 tag for nonces.
|
||||
TagBIP0340Nonce = []byte("BIP0340/nonce")
|
||||
|
||||
// TagTapSighash is the tag used by BIP 341 to generate the sighash
|
||||
// flags.
|
||||
TagTapSighash = []byte("TapSighash")
|
||||
|
||||
// precomputedTags is a map containing the SHA-256 hash of the BIP-0340
|
||||
// tags.
|
||||
precomputedTags = map[string]Hash{
|
||||
string(TagBIP0340Challenge): sha256.Sum256(TagBIP0340Challenge),
|
||||
string(TagBIP0340Aux): sha256.Sum256(TagBIP0340Aux),
|
||||
string(TagBIP0340Nonce): sha256.Sum256(TagBIP0340Nonce),
|
||||
string(TagTapSighash): sha256.Sum256(TagTapSighash),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -55,7 +55,8 @@ func BenchmarkCalcSigHash(b *testing.B) {
|
||||
// BenchmarkCalcWitnessSigHash benchmarks how long it takes to calculate the
|
||||
// witness signature hashes for all inputs of a transaction with many inputs.
|
||||
func BenchmarkCalcWitnessSigHash(b *testing.B) {
|
||||
sigHashes := NewTxSigHashes(&manyInputsBenchTx)
|
||||
prevOutFetcher := NewCannedPrevOutputFetcher(prevOutScript, 5)
|
||||
sigHashes := NewTxSigHashes(&manyInputsBenchTx, prevOutFetcher)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
@ -145,13 +145,14 @@ type Engine struct {
|
||||
// since transaction scripts are often executed more than once from various
|
||||
// contexts (e.g. new block templates, when transactions are first seen
|
||||
// prior to being mined, part of full block verification, etc).
|
||||
flags ScriptFlags
|
||||
tx wire.MsgTx
|
||||
txIdx int
|
||||
version uint16
|
||||
bip16 bool
|
||||
sigCache *SigCache
|
||||
hashCache *TxSigHashes
|
||||
flags ScriptFlags
|
||||
tx wire.MsgTx
|
||||
txIdx int
|
||||
version uint16
|
||||
bip16 bool
|
||||
sigCache *SigCache
|
||||
hashCache *TxSigHashes
|
||||
prevOutFetcher PrevOutputFetcher
|
||||
|
||||
// The following fields handle keeping track of the current execution state
|
||||
// of the engine.
|
||||
@ -1117,7 +1118,9 @@ func (vm *Engine) SetAltStack(data [][]byte) {
|
||||
// transaction, and input index. The flags modify the behavior of the script
|
||||
// engine according to the description provided by each flag.
|
||||
func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags,
|
||||
sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64) (*Engine, error) {
|
||||
sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64,
|
||||
prevOuts PrevOutputFetcher) (*Engine, error) {
|
||||
|
||||
const scriptVersion = 0
|
||||
|
||||
// The provided transaction input index must refer to a valid input.
|
||||
@ -1147,8 +1150,13 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
||||
// it possible to have a situation where P2SH would not be a soft fork
|
||||
// when it should be. The same goes for segwit which will pull in
|
||||
// additional scripts for execution from the witness stack.
|
||||
vm := Engine{flags: flags, sigCache: sigCache, hashCache: hashCache,
|
||||
inputAmount: inputAmount}
|
||||
vm := Engine{
|
||||
flags: flags,
|
||||
sigCache: sigCache,
|
||||
hashCache: hashCache,
|
||||
inputAmount: inputAmount,
|
||||
prevOutFetcher: prevOuts,
|
||||
}
|
||||
if vm.hasFlag(ScriptVerifyCleanStack) && (!vm.hasFlag(ScriptBip16) &&
|
||||
!vm.hasFlag(ScriptVerifyWitness)) {
|
||||
return nil, scriptError(ErrInvalidFlags,
|
||||
|
@ -55,7 +55,7 @@ func TestBadPC(t *testing.T) {
|
||||
pkScript := mustParseShortForm("NOP")
|
||||
|
||||
for _, test := range tests {
|
||||
vm, err := NewEngine(pkScript, tx, 0, 0, nil, nil, -1)
|
||||
vm, err := NewEngine(pkScript, tx, 0, 0, nil, nil, -1, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create script: %v", err)
|
||||
}
|
||||
@ -112,7 +112,7 @@ func TestCheckErrorCondition(t *testing.T) {
|
||||
pkScript := mustParseShortForm("NOP NOP NOP NOP NOP NOP NOP NOP NOP" +
|
||||
" NOP TRUE")
|
||||
|
||||
vm, err := NewEngine(pkScript, tx, 0, 0, nil, nil, 0)
|
||||
vm, err := NewEngine(pkScript, tx, 0, 0, nil, nil, 0, nil)
|
||||
if err != nil {
|
||||
t.Errorf("failed to create script: %v", err)
|
||||
}
|
||||
@ -188,7 +188,7 @@ func TestInvalidFlagCombinations(t *testing.T) {
|
||||
pkScript := []byte{OP_NOP}
|
||||
|
||||
for i, test := range tests {
|
||||
_, err := NewEngine(pkScript, tx, 0, test, nil, nil, -1)
|
||||
_, err := NewEngine(pkScript, tx, 0, test, nil, nil, -1, nil)
|
||||
if !IsErrorCode(err, ErrInvalidFlags) {
|
||||
t.Fatalf("TestInvalidFlagCombinations #%d unexpected "+
|
||||
"error: %v", i, err)
|
||||
|
@ -10,11 +10,11 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
)
|
||||
|
||||
// This example demonstrates creating a script which pays to a bitcoin address.
|
||||
@ -167,7 +167,7 @@ func ExampleSignTxOutput() {
|
||||
txscript.ScriptStrictMultiSig |
|
||||
txscript.ScriptDiscourageUpgradableNops
|
||||
vm, err := txscript.NewEngine(originTx.TxOut[0].PkScript, redeemTx, 0,
|
||||
flags, nil, nil, -1)
|
||||
flags, nil, nil, -1, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
|
@ -7,6 +7,7 @@ package txscript
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
@ -67,24 +68,230 @@ func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
|
||||
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 {
|
||||
HashPrevOuts chainhash.Hash
|
||||
HashSequence chainhash.Hash
|
||||
HashOutputs chainhash.Hash
|
||||
SegwitSigHashMidstate
|
||||
|
||||
TaprootSigHashMidState
|
||||
}
|
||||
|
||||
// NewTxSigHashes computes, and returns the cached sighashes of the given
|
||||
// transaction.
|
||||
func NewTxSigHashes(tx *wire.MsgTx) *TxSigHashes {
|
||||
return &TxSigHashes{
|
||||
HashPrevOuts: calcHashPrevOuts(tx),
|
||||
HashSequence: calcHashSequence(tx),
|
||||
HashOutputs: calcHashOutputs(tx),
|
||||
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
|
||||
@ -108,9 +315,11 @@ func NewHashCache(maxSize uint) *HashCache {
|
||||
|
||||
// AddSigHashes computes, then adds the partial sighashes for the passed
|
||||
// transaction.
|
||||
func (h *HashCache) AddSigHashes(tx *wire.MsgTx) {
|
||||
func (h *HashCache) AddSigHashes(tx *wire.MsgTx,
|
||||
inputFetcher PrevOutputFetcher) {
|
||||
|
||||
h.Lock()
|
||||
h.sigHashes[tx.TxHash()] = NewTxSigHashes(tx)
|
||||
h.sigHashes[tx.TxHash()] = NewTxSigHashes(tx, inputFetcher)
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,12 @@ func init() {
|
||||
}
|
||||
|
||||
// genTestTx creates a random transaction for uses within test cases.
|
||||
func genTestTx() (*wire.MsgTx, error) {
|
||||
func genTestTx() (*wire.MsgTx, *MultiPrevOutFetcher, error) {
|
||||
tx := wire.NewMsgTx(2)
|
||||
tx.Version = rand.Int31()
|
||||
|
||||
prevOuts := NewMultiPrevOutFetcher(nil)
|
||||
|
||||
numTxins := 1 + rand.Intn(11)
|
||||
for i := 0; i < numTxins; i++ {
|
||||
randTxIn := wire.TxIn{
|
||||
@ -32,10 +34,14 @@ func genTestTx() (*wire.MsgTx, error) {
|
||||
}
|
||||
_, err := rand.Read(randTxIn.PreviousOutPoint.Hash[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tx.TxIn = append(tx.TxIn, &randTxIn)
|
||||
|
||||
prevOuts.AddPrevOut(
|
||||
randTxIn.PreviousOutPoint, &wire.TxOut{},
|
||||
)
|
||||
}
|
||||
|
||||
numTxouts := 1 + rand.Intn(11)
|
||||
@ -45,12 +51,12 @@ func genTestTx() (*wire.MsgTx, error) {
|
||||
PkScript: make([]byte, rand.Intn(30)),
|
||||
}
|
||||
if _, err := rand.Read(randTxOut.PkScript); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
tx.TxOut = append(tx.TxOut, &randTxOut)
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
return tx, prevOuts, nil
|
||||
}
|
||||
|
||||
// TestHashCacheAddContainsHashes tests that after items have been added to the
|
||||
@ -62,23 +68,29 @@ func TestHashCacheAddContainsHashes(t *testing.T) {
|
||||
|
||||
cache := NewHashCache(10)
|
||||
|
||||
var err error
|
||||
var (
|
||||
err error
|
||||
randPrevOuts *MultiPrevOutFetcher
|
||||
)
|
||||
prevOuts := NewMultiPrevOutFetcher(nil)
|
||||
|
||||
// First, we'll generate 10 random transactions for use within our
|
||||
// tests.
|
||||
const numTxns = 10
|
||||
txns := make([]*wire.MsgTx, numTxns)
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txns[i], err = genTestTx()
|
||||
txns[i], randPrevOuts, err = genTestTx()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate test tx: %v", err)
|
||||
}
|
||||
|
||||
prevOuts.Merge(randPrevOuts)
|
||||
}
|
||||
|
||||
// With the transactions generated, we'll add each of them to the hash
|
||||
// cache.
|
||||
for _, tx := range txns {
|
||||
cache.AddSigHashes(tx)
|
||||
cache.AddSigHashes(tx, prevOuts)
|
||||
}
|
||||
|
||||
// Next, we'll ensure that each of the transactions inserted into the
|
||||
@ -91,7 +103,7 @@ func TestHashCacheAddContainsHashes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
randTx, err := genTestTx()
|
||||
randTx, _, err := genTestTx()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate tx: %v", err)
|
||||
}
|
||||
@ -115,14 +127,14 @@ func TestHashCacheAddGet(t *testing.T) {
|
||||
|
||||
// To start, we'll generate a random transaction and compute the set of
|
||||
// sighashes for the transaction.
|
||||
randTx, err := genTestTx()
|
||||
randTx, prevOuts, err := genTestTx()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate tx: %v", err)
|
||||
}
|
||||
sigHashes := NewTxSigHashes(randTx)
|
||||
sigHashes := NewTxSigHashes(randTx, prevOuts)
|
||||
|
||||
// Next, add the transaction to the hash cache.
|
||||
cache.AddSigHashes(randTx)
|
||||
cache.AddSigHashes(randTx, prevOuts)
|
||||
|
||||
// The transaction inserted into the cache above should be found.
|
||||
txid := randTx.TxHash()
|
||||
@ -146,19 +158,25 @@ func TestHashCachePurge(t *testing.T) {
|
||||
|
||||
cache := NewHashCache(10)
|
||||
|
||||
var err error
|
||||
var (
|
||||
err error
|
||||
randPrevOuts *MultiPrevOutFetcher
|
||||
)
|
||||
prevOuts := NewMultiPrevOutFetcher(nil)
|
||||
|
||||
// First we'll start by inserting numTxns transactions into the hash cache.
|
||||
const numTxns = 10
|
||||
txns := make([]*wire.MsgTx, numTxns)
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txns[i], err = genTestTx()
|
||||
txns[i], randPrevOuts, err = genTestTx()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate test tx: %v", err)
|
||||
}
|
||||
|
||||
prevOuts.Merge(randPrevOuts)
|
||||
}
|
||||
for _, tx := range txns {
|
||||
cache.AddSigHashes(tx)
|
||||
cache.AddSigHashes(tx, prevOuts)
|
||||
}
|
||||
|
||||
// Once all the transactions have been inserted, we'll purge them from
|
||||
|
@ -1914,7 +1914,7 @@ func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error {
|
||||
if vm.hashCache != nil {
|
||||
sigHashes = vm.hashCache
|
||||
} else {
|
||||
sigHashes = NewTxSigHashes(&vm.tx)
|
||||
sigHashes = NewTxSigHashes(&vm.tx, vm.prevOutFetcher)
|
||||
}
|
||||
|
||||
hash, err = calcWitnessSignatureHashRaw(subScript, sigHashes, hashType,
|
||||
@ -2185,7 +2185,7 @@ func opcodeCheckMultiSig(op *opcode, data []byte, vm *Engine) error {
|
||||
if vm.hashCache != nil {
|
||||
sigHashes = vm.hashCache
|
||||
} else {
|
||||
sigHashes = NewTxSigHashes(&vm.tx)
|
||||
sigHashes = NewTxSigHashes(&vm.tx, vm.prevOutFetcher)
|
||||
}
|
||||
|
||||
hash, err = calcWitnessSignatureHashRaw(script, sigHashes, hashType,
|
||||
|
@ -15,9 +15,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
)
|
||||
|
||||
// scriptTestName returns a descriptive test name for the given reference script
|
||||
@ -441,10 +441,14 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) {
|
||||
// Generate a transaction pair such that one spends from the
|
||||
// other and the provided signature and public key scripts are
|
||||
// used, then create a new engine to execute the scripts.
|
||||
tx := createSpendingTx(witness, scriptSig, scriptPubKey,
|
||||
int64(inputAmt))
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache, nil,
|
||||
int64(inputAmt))
|
||||
tx := createSpendingTx(
|
||||
witness, scriptSig, scriptPubKey, int64(inputAmt),
|
||||
)
|
||||
prevOuts := NewCannedPrevOutputFetcher(scriptPubKey, int64(inputAmt))
|
||||
vm, err := NewEngine(
|
||||
scriptPubKey, tx, 0, flags, sigCache, nil,
|
||||
int64(inputAmt), prevOuts,
|
||||
)
|
||||
if err == nil {
|
||||
err = vm.Execute()
|
||||
}
|
||||
@ -572,7 +576,7 @@ testloop:
|
||||
continue
|
||||
}
|
||||
|
||||
prevOuts := make(map[wire.OutPoint]scriptWithInputVal)
|
||||
prevOutFetcher := NewMultiPrevOutFetcher(nil)
|
||||
for j, iinput := range inputs {
|
||||
input, ok := iinput.([]interface{})
|
||||
if !ok {
|
||||
@ -633,16 +637,18 @@ testloop:
|
||||
}
|
||||
}
|
||||
|
||||
v := scriptWithInputVal{
|
||||
inputVal: int64(inputValue),
|
||||
pkScript: script,
|
||||
}
|
||||
prevOuts[*wire.NewOutPoint(prevhash, idx)] = v
|
||||
op := wire.NewOutPoint(prevhash, idx)
|
||||
prevOutFetcher.AddPrevOut(*op, &wire.TxOut{
|
||||
Value: int64(inputValue),
|
||||
PkScript: script,
|
||||
})
|
||||
}
|
||||
|
||||
for k, txin := range tx.MsgTx().TxIn {
|
||||
prevOut, ok := prevOuts[txin.PreviousOutPoint]
|
||||
if !ok {
|
||||
prevOut := prevOutFetcher.FetchPrevOutput(
|
||||
txin.PreviousOutPoint,
|
||||
)
|
||||
if prevOut == nil {
|
||||
t.Errorf("bad test (missing %dth input) %d:%v",
|
||||
k, i, test)
|
||||
continue testloop
|
||||
@ -650,8 +656,8 @@ testloop:
|
||||
// These are meant to fail, so as soon as the first
|
||||
// input fails the transaction has failed. (some of the
|
||||
// test txns have good inputs, too..
|
||||
vm, err := NewEngine(prevOut.pkScript, tx.MsgTx(), k,
|
||||
flags, nil, nil, prevOut.inputVal)
|
||||
vm, err := NewEngine(prevOut.PkScript, tx.MsgTx(), k,
|
||||
flags, nil, nil, prevOut.Value, prevOutFetcher)
|
||||
if err != nil {
|
||||
continue testloop
|
||||
}
|
||||
@ -727,7 +733,7 @@ testloop:
|
||||
continue
|
||||
}
|
||||
|
||||
prevOuts := make(map[wire.OutPoint]scriptWithInputVal)
|
||||
prevOutFetcher := NewMultiPrevOutFetcher(nil)
|
||||
for j, iinput := range inputs {
|
||||
input, ok := iinput.([]interface{})
|
||||
if !ok {
|
||||
@ -788,22 +794,24 @@ testloop:
|
||||
}
|
||||
}
|
||||
|
||||
v := scriptWithInputVal{
|
||||
inputVal: int64(inputValue),
|
||||
pkScript: script,
|
||||
}
|
||||
prevOuts[*wire.NewOutPoint(prevhash, idx)] = v
|
||||
op := wire.NewOutPoint(prevhash, idx)
|
||||
prevOutFetcher.AddPrevOut(*op, &wire.TxOut{
|
||||
Value: int64(inputValue),
|
||||
PkScript: script,
|
||||
})
|
||||
}
|
||||
|
||||
for k, txin := range tx.MsgTx().TxIn {
|
||||
prevOut, ok := prevOuts[txin.PreviousOutPoint]
|
||||
if !ok {
|
||||
prevOut := prevOutFetcher.FetchPrevOutput(
|
||||
txin.PreviousOutPoint,
|
||||
)
|
||||
if prevOut == nil {
|
||||
t.Errorf("bad test (missing %dth input) %d:%v",
|
||||
k, i, test)
|
||||
continue testloop
|
||||
}
|
||||
vm, err := NewEngine(prevOut.pkScript, tx.MsgTx(), k,
|
||||
flags, nil, nil, prevOut.inputVal)
|
||||
vm, err := NewEngine(prevOut.PkScript, tx.MsgTx(), k,
|
||||
flags, nil, nil, prevOut.Value, prevOutFetcher)
|
||||
if err != nil {
|
||||
t.Errorf("test (%d:%v:%d) failed to create "+
|
||||
"script: %v", i, test, k, err)
|
||||
|
@ -19,20 +19,17 @@ import (
|
||||
// This timestamp corresponds to Sun Apr 1 00:00:00 UTC 2012.
|
||||
var Bip16Activation = time.Unix(1333238400, 0)
|
||||
|
||||
// SigHashType represents hash type bits at the end of a signature.
|
||||
type SigHashType uint32
|
||||
|
||||
// Hash type bits from the end of a signature.
|
||||
const (
|
||||
SigHashOld SigHashType = 0x0
|
||||
SigHashAll SigHashType = 0x1
|
||||
SigHashNone SigHashType = 0x2
|
||||
SigHashSingle SigHashType = 0x3
|
||||
SigHashAnyOneCanPay SigHashType = 0x80
|
||||
// TaprootAnnexTag is the tag for an annex. This value is used to
|
||||
// identify the annex during tapscript spends. If there're at least two
|
||||
// elements in the taproot witness stack, and the first byte of the
|
||||
// last element matches this tag, then we'll extract this as a distinct
|
||||
// item.
|
||||
TaprootAnnexTag = 0x50
|
||||
|
||||
// sigHashMask defines the number of bits of the hash type which is used
|
||||
// to identify which outputs are signed.
|
||||
sigHashMask = 0x1f
|
||||
// TaprootLeafMask is the mask applied to the control block to extract
|
||||
// the leaf versions of the taproot script leaf being spent.
|
||||
TaprootLeafMask = 0xfe
|
||||
)
|
||||
|
||||
// These are the constants specified for maximums in individual scripts.
|
||||
|
@ -44,12 +44,12 @@ func TestSigCacheAddExists(t *testing.T) {
|
||||
}
|
||||
|
||||
// Add the triplet to the signature cache.
|
||||
sigCache.Add(*msg1, sig1, key1)
|
||||
sigCache.Add(*msg1, sig1.Serialize(), key1.SerializeCompressed())
|
||||
|
||||
// The previously added triplet should now be found within the sigcache.
|
||||
sig1Copy, _ := ecdsa.ParseSignature(sig1.Serialize())
|
||||
key1Copy, _ := btcec.ParsePubKey(key1.SerializeCompressed())
|
||||
if !sigCache.Exists(*msg1, sig1Copy, key1Copy) {
|
||||
if !sigCache.Exists(*msg1, sig1Copy.Serialize(), key1Copy.SerializeCompressed()) {
|
||||
t.Errorf("previously added item not found in signature cache")
|
||||
}
|
||||
}
|
||||
@ -69,7 +69,7 @@ func TestSigCacheAddEvictEntry(t *testing.T) {
|
||||
t.Fatalf("unable to generate random signature test data")
|
||||
}
|
||||
|
||||
sigCache.Add(*msg, sig, key)
|
||||
sigCache.Add(*msg, sig.Serialize(), key.SerializeCompressed())
|
||||
|
||||
sigCopy, err := ecdsa.ParseSignature(sig.Serialize())
|
||||
if err != nil {
|
||||
@ -79,7 +79,7 @@ func TestSigCacheAddEvictEntry(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to parse key: %v", err)
|
||||
}
|
||||
if !sigCache.Exists(*msg, sigCopy, keyCopy) {
|
||||
if !sigCache.Exists(*msg, sigCopy.Serialize(), keyCopy.SerializeCompressed()) {
|
||||
t.Errorf("previously added item not found in signature" +
|
||||
"cache")
|
||||
}
|
||||
@ -97,7 +97,7 @@ func TestSigCacheAddEvictEntry(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate random signature test data")
|
||||
}
|
||||
sigCache.Add(*msgNew, sigNew, keyNew)
|
||||
sigCache.Add(*msgNew, sigNew.Serialize(), keyNew.SerializeCompressed())
|
||||
|
||||
// The sigcache should still have sigCache entries.
|
||||
if uint(len(sigCache.validSigs)) != sigCacheSize {
|
||||
@ -108,7 +108,7 @@ func TestSigCacheAddEvictEntry(t *testing.T) {
|
||||
// The entry added above should be found within the sigcache.
|
||||
sigNewCopy, _ := ecdsa.ParseSignature(sigNew.Serialize())
|
||||
keyNewCopy, _ := btcec.ParsePubKey(keyNew.SerializeCompressed())
|
||||
if !sigCache.Exists(*msgNew, sigNewCopy, keyNewCopy) {
|
||||
if !sigCache.Exists(*msgNew, sigNewCopy.Serialize(), keyNewCopy.SerializeCompressed()) {
|
||||
t.Fatalf("previously added item not found in signature cache")
|
||||
}
|
||||
}
|
||||
@ -126,12 +126,12 @@ func TestSigCacheAddMaxEntriesZeroOrNegative(t *testing.T) {
|
||||
}
|
||||
|
||||
// Add the triplet to the signature cache.
|
||||
sigCache.Add(*msg1, sig1, key1)
|
||||
sigCache.Add(*msg1, sig1.Serialize(), key1.SerializeCompressed())
|
||||
|
||||
// The generated triplet should not be found.
|
||||
sig1Copy, _ := ecdsa.ParseSignature(sig1.Serialize())
|
||||
key1Copy, _ := btcec.ParsePubKey(key1.SerializeCompressed())
|
||||
if sigCache.Exists(*msg1, sig1Copy, key1Copy) {
|
||||
if sigCache.Exists(*msg1, sig1Copy.Serialize(), key1Copy.SerializeCompressed()) {
|
||||
t.Errorf("previously added signature found in sigcache, but" +
|
||||
"shouldn't have been")
|
||||
}
|
||||
|
@ -7,13 +7,39 @@ package txscript
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// SigHashType represents hash type bits at the end of a signature.
|
||||
type SigHashType uint32
|
||||
|
||||
// Hash type bits from the end of a signature.
|
||||
const (
|
||||
SigHashDefault SigHashType = 0x00
|
||||
SigHashOld SigHashType = 0x0
|
||||
SigHashAll SigHashType = 0x1
|
||||
SigHashNone SigHashType = 0x2
|
||||
SigHashSingle SigHashType = 0x3
|
||||
SigHashAnyOneCanPay SigHashType = 0x80
|
||||
|
||||
// sigHashMask defines the number of bits of the hash type which is used
|
||||
// to identify which outputs are signed.
|
||||
sigHashMask = 0x1f
|
||||
)
|
||||
|
||||
const (
|
||||
// blankCodeSepValue is the value of the code separator position in the
|
||||
// tapscript sighash when no code separator was found in the script.
|
||||
blankCodeSepValue = math.MaxUint32
|
||||
)
|
||||
|
||||
// shallowCopyTx creates a shallow copy of the transaction for use when
|
||||
// calculating the signature hash. It is used over the Copy method on the
|
||||
// transaction itself since that is a deep copy and therefore does more work and
|
||||
@ -188,7 +214,7 @@ func calcWitnessSignatureHashRaw(subScript []byte, sigHashes *TxSigHashes,
|
||||
// If anyone can pay isn't active, then we can use the cached
|
||||
// hashPrevOuts, otherwise we just write zeroes for the prev outs.
|
||||
if hashType&SigHashAnyOneCanPay == 0 {
|
||||
sigHash.Write(sigHashes.HashPrevOuts[:])
|
||||
sigHash.Write(sigHashes.HashPrevOutsV0[:])
|
||||
} else {
|
||||
sigHash.Write(zeroHash[:])
|
||||
}
|
||||
@ -199,7 +225,7 @@ func calcWitnessSignatureHashRaw(subScript []byte, sigHashes *TxSigHashes,
|
||||
if hashType&SigHashAnyOneCanPay == 0 &&
|
||||
hashType&sigHashMask != SigHashSingle &&
|
||||
hashType&sigHashMask != SigHashNone {
|
||||
sigHash.Write(sigHashes.HashSequence[:])
|
||||
sigHash.Write(sigHashes.HashSequenceV0[:])
|
||||
} else {
|
||||
sigHash.Write(zeroHash[:])
|
||||
}
|
||||
@ -245,7 +271,7 @@ func calcWitnessSignatureHashRaw(subScript []byte, sigHashes *TxSigHashes,
|
||||
// pre-image.
|
||||
if hashType&sigHashMask != SigHashSingle &&
|
||||
hashType&sigHashMask != SigHashNone {
|
||||
sigHash.Write(sigHashes.HashOutputs[:])
|
||||
sigHash.Write(sigHashes.HashOutputsV0[:])
|
||||
} else if hashType&sigHashMask == SigHashSingle && idx < len(tx.TxOut) {
|
||||
var b bytes.Buffer
|
||||
wire.WriteTxOut(&b, 0, 0, tx.TxOut[idx])
|
||||
@ -278,3 +304,325 @@ func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType
|
||||
|
||||
return calcWitnessSignatureHashRaw(script, sigHashes, hType, tx, idx, amt)
|
||||
}
|
||||
|
||||
// sigHashExtFlag represents the sig hash extension flag as defined in BIP 341.
|
||||
// Extensions to the base sighash algorithm will be appended to the base
|
||||
// sighash digest.
|
||||
type sigHashExtFlag uint8
|
||||
|
||||
const (
|
||||
// baseSigHashExtFlag is the base extension flag. This adds no changes
|
||||
// to the sighash digest message. This is used for segwit v1 spends,
|
||||
// a.k.a the tapscript keyspend path.
|
||||
baseSigHashExtFlag sigHashExtFlag = 0
|
||||
|
||||
// tapscriptSighashExtFlag is the extension flag defined by tapscript
|
||||
// base leaf version spend define din BIP 342. This augments the base
|
||||
// sighash by including the tapscript leaf hash, the key version, and
|
||||
// the code separator position.
|
||||
tapscriptSighashExtFlag sigHashExtFlag = 1
|
||||
)
|
||||
|
||||
// taprootSigHashOptions houses a set of functional options that may optionally
|
||||
// modify how the taproot/script sighash digest algorithm is implemented.
|
||||
type taprootSigHashOptions struct {
|
||||
// extFlag denotes the current message digest extension being used. For
|
||||
// top-level script spends use a value of zero, while each tapscript
|
||||
// version can define its own values as well.
|
||||
extFlag sigHashExtFlag
|
||||
|
||||
// annexHash is the sha256 hash of the annex with a compact size length
|
||||
// prefix: sha256(sizeOf(annex) || annex).
|
||||
annexHash []byte
|
||||
|
||||
// tapLeafHash is the hash of the tapscript leaf as defined in BIP 341.
|
||||
// This should be h_tapleaf(version || compactSizeOf(script) || script).
|
||||
tapLeafHash []byte
|
||||
|
||||
// keyVersion is the key version as defined in BIP 341. This is always
|
||||
// 0x00 for all currently defined leaf versions.
|
||||
keyVersion byte
|
||||
|
||||
// codeSepPos is the op code position of the last code separator. This
|
||||
// is used for the BIP 342 sighash message extension.
|
||||
codeSepPos uint32
|
||||
}
|
||||
|
||||
// writeDigestExtensions writes out the sighah mesage extensiosn defined by the
|
||||
// current active sigHashExtFlags.
|
||||
func (t *taprootSigHashOptions) writeDigestExtensions(w io.Writer) error {
|
||||
switch t.extFlag {
|
||||
// The base extension, used for tapscript keypath spends doesn't modify
|
||||
// the digest at all.
|
||||
case baseSigHashExtFlag:
|
||||
return nil
|
||||
|
||||
// The tapscript base leaf version extension adds the leaf hash, key
|
||||
// version, and code separator position to the final digest.
|
||||
case tapscriptSighashExtFlag:
|
||||
if _, err := w.Write(t.tapLeafHash); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{t.keyVersion}); err != nil {
|
||||
return err
|
||||
}
|
||||
err := binary.Write(w, binary.LittleEndian, t.codeSepPos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// defaultTaprootSighashOptions returns the set of default sighash options for
|
||||
// taproot execution.
|
||||
func defaultTaprootSighashOptions() *taprootSigHashOptions {
|
||||
return &taprootSigHashOptions{}
|
||||
}
|
||||
|
||||
// TaprootSigHashOption defines a set of functional param options that can be
|
||||
// used to modify the base sighash message with optional extensions.
|
||||
type TaprootSigHashOption func(*taprootSigHashOptions)
|
||||
|
||||
// WithAnnex is a functional option that allows the caller to specify the
|
||||
// existence of an annex in the final witness stack for the taproot/tapscript
|
||||
// spends.
|
||||
func WithAnnex(annex []byte) TaprootSigHashOption {
|
||||
return func(o *taprootSigHashOptions) {
|
||||
// It's just a bytes.Buffer which never returns an error on
|
||||
// write.
|
||||
var b bytes.Buffer
|
||||
_ = wire.WriteVarBytes(&b, 0, annex)
|
||||
|
||||
o.annexHash = chainhash.HashB(b.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// WithBaseTapscriptVersion is a functional option that specifies that the
|
||||
// sighash digest should include the extra information included as part of the
|
||||
// base tapscript version.
|
||||
func WithBaseTapscriptVersion(codeSepPos uint32,
|
||||
tapLeafHash []byte) TaprootSigHashOption {
|
||||
|
||||
return func(o *taprootSigHashOptions) {
|
||||
o.extFlag = tapscriptSighashExtFlag
|
||||
o.tapLeafHash = tapLeafHash
|
||||
o.keyVersion = 0
|
||||
o.codeSepPos = codeSepPos
|
||||
}
|
||||
}
|
||||
|
||||
// isValidTaprootSigHash returns true if the passed sighash is a valid taproot
|
||||
// sighash.
|
||||
func isValidTaprootSigHash(hashType SigHashType) bool {
|
||||
switch hashType {
|
||||
case SigHashDefault, SigHashAll, SigHashNone, SigHashSingle:
|
||||
fallthrough
|
||||
case 0x81, 0x82, 0x83:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// calcTaprootSignatureHashRaw computes the sighash as specified in BIP 143.
|
||||
// If an invalid sighash type is passed in, an error is returned.
|
||||
func calcTaprootSignatureHashRaw(sigHashes *TxSigHashes, hType SigHashType,
|
||||
tx *wire.MsgTx, idx int,
|
||||
prevOutFetcher PrevOutputFetcher,
|
||||
sigHashOpts ...TaprootSigHashOption) ([]byte, error) {
|
||||
|
||||
opts := defaultTaprootSighashOptions()
|
||||
for _, sigHashOpt := range sigHashOpts {
|
||||
sigHashOpt(opts)
|
||||
}
|
||||
|
||||
// If a valid sighash type isn't passed in, then we'll exit early.
|
||||
if !isValidTaprootSigHash(hType) {
|
||||
// TODO(roasbeef): use actual errr here
|
||||
return nil, fmt.Errorf("invalid taproot sighash type: %v", hType)
|
||||
}
|
||||
|
||||
// As a sanity check, ensure the passed input index for the transaction
|
||||
// is valid.
|
||||
if idx > len(tx.TxIn)-1 {
|
||||
return nil, fmt.Errorf("idx %d but %d txins", idx, len(tx.TxIn))
|
||||
}
|
||||
|
||||
// We'll utilize this buffer throughout to incrementally calculate
|
||||
// the signature hash for this transaction.
|
||||
var sigMsg bytes.Buffer
|
||||
|
||||
// The final sighash always has a value of 0x00 prepended to it, which
|
||||
// is called the sighash epoch.
|
||||
sigMsg.WriteByte(0x00)
|
||||
|
||||
// First, we write the hash type encoded as a single byte.
|
||||
if err := sigMsg.WriteByte(byte(hType)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next we'll write out the transaction specific data which binds the
|
||||
// outer context of the sighash.
|
||||
err := binary.Write(&sigMsg, binary.LittleEndian, tx.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Write(&sigMsg, binary.LittleEndian, tx.LockTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If sighash isn't anyone can pay, then we'll include all the
|
||||
// pre-computed midstate digests in the sighash.
|
||||
if hType&SigHashAnyOneCanPay != SigHashAnyOneCanPay {
|
||||
sigMsg.Write(sigHashes.HashPrevOutsV1[:])
|
||||
sigMsg.Write(sigHashes.HashInputAmountsV1[:])
|
||||
sigMsg.Write(sigHashes.HashInputScriptsV1[:])
|
||||
sigMsg.Write(sigHashes.HashSequenceV1[:])
|
||||
}
|
||||
|
||||
// If this is sighash all, or its taproot alias (sighash default),
|
||||
// then we'll also include the pre-computed digest of all the outputs
|
||||
// of the transaction.
|
||||
if hType&SigHashSingle != SigHashSingle &&
|
||||
hType&SigHashSingle != SigHashNone {
|
||||
|
||||
sigMsg.Write(sigHashes.HashOutputsV1[:])
|
||||
}
|
||||
|
||||
// Next, we'll write out the relevant information for this specific
|
||||
// input.
|
||||
//
|
||||
// The spend type is computed as the (ext_flag*2) + annex_present. We
|
||||
// use this to bind the extension flag (that BIP 342 uses), as well as
|
||||
// the annex if its present.
|
||||
input := tx.TxIn[idx]
|
||||
witnessHasAnnex := opts.annexHash != nil
|
||||
spendType := byte(opts.extFlag) * 2
|
||||
if witnessHasAnnex {
|
||||
spendType += 1
|
||||
}
|
||||
|
||||
if err := sigMsg.WriteByte(spendType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If anyone can pay is active, then we'll write out just the specific
|
||||
// information about this input, given we skipped writing all the
|
||||
// information of all the inputs above.
|
||||
if hType&SigHashAnyOneCanPay == SigHashAnyOneCanPay {
|
||||
// We'll start out with writing this input specific information by
|
||||
// first writing the entire previous output.
|
||||
err = wire.WriteOutPoint(&sigMsg, 0, 0, &input.PreviousOutPoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next, we'll write out the previous output (amt+script) being
|
||||
// spent itself.
|
||||
prevOut := prevOutFetcher.FetchPrevOutput(input.PreviousOutPoint)
|
||||
if err := wire.WriteTxOut(&sigMsg, 0, 0, prevOut); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, we'll write out the input sequence itself.
|
||||
err = binary.Write(&sigMsg, binary.LittleEndian, input.Sequence)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err := binary.Write(&sigMsg, binary.LittleEndian, uint32(idx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have the input specific information written, we'll
|
||||
// include the anex, if we have it.
|
||||
if witnessHasAnnex {
|
||||
sigMsg.Write(opts.annexHash)
|
||||
}
|
||||
|
||||
// Finally, if this is sighash single, then we'll write out the
|
||||
// information for this given output.
|
||||
if hType&sigHashMask == SigHashSingle {
|
||||
// If this output doesn't exist, then we'll return with an error
|
||||
// here as this is an invalid sighash type for this input.
|
||||
if idx >= len(tx.TxOut) {
|
||||
// TODO(roasbeef): real error here
|
||||
return nil, fmt.Errorf("invalid sighash type for input")
|
||||
}
|
||||
|
||||
// Now that we know this is a valid sighash input combination,
|
||||
// we'll write out the information specific to this input.
|
||||
// We'll write the wire serialization of the output and compute
|
||||
// the sha256 in a single step.
|
||||
shaWriter := sha256.New()
|
||||
txOut := tx.TxOut[idx]
|
||||
if err := wire.WriteTxOut(shaWriter, 0, 0, txOut); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With the digest obtained, we'll write this out into our
|
||||
// signature message.
|
||||
if _, err := sigMsg.Write(shaWriter.Sum(nil)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we've written out all the base information, we'll write any
|
||||
// message extensions (if they exist).
|
||||
if err := opts.writeDigestExtensions(&sigMsg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The final sighash is computed as: hash_TagSigHash(0x00 || sigMsg).
|
||||
// We wrote the 0x00 above so we don't need to append here and incur
|
||||
// extra allocations.
|
||||
sigHash := chainhash.TaggedHash(chainhash.TagTapSighash, sigMsg.Bytes())
|
||||
return sigHash[:], nil
|
||||
}
|
||||
|
||||
// CalcTaprootSignatureHash computes the sighash digest of a transaction's
|
||||
// taproot-spending input using the new sighash digest algorithm described in
|
||||
// BIP 341. As the new digest algoriths may require the digest to commit to the
|
||||
// entire prev output, a PrevOutputFetcher argument is required to obtain the
|
||||
// needed information. The TxSigHashes pre-computed sighash midstate MUST be
|
||||
// specified.
|
||||
func CalcTaprootSignatureHash(sigHashes *TxSigHashes, hType SigHashType,
|
||||
tx *wire.MsgTx, idx int,
|
||||
prevOutFetcher PrevOutputFetcher) ([]byte, error) {
|
||||
|
||||
return calcTaprootSignatureHashRaw(
|
||||
sigHashes, hType, tx, idx, prevOutFetcher,
|
||||
)
|
||||
}
|
||||
|
||||
// CalcTaprootSignatureHash is similar to CalcTaprootSignatureHash but for
|
||||
// _tapscript_ spends instead. A proper TapLeaf instance (the script leaf being
|
||||
// signed) must be passed in. The functional options can be used to specify an
|
||||
// annex if the signature was bound to that context.
|
||||
//
|
||||
// NOTE: This function is able to compute the sighash of scripts that contain a
|
||||
// code separator if the caller passes in an instance of
|
||||
// WithBaseTapscriptVersion with the valid position.
|
||||
func CalcTapscriptSignaturehash(sigHashes *TxSigHashes, hType SigHashType,
|
||||
tx *wire.MsgTx, idx int, prevOutFetcher PrevOutputFetcher,
|
||||
tapLeaf TapLeaf,
|
||||
sigHashOpts ...TaprootSigHashOption) ([]byte, error) {
|
||||
|
||||
tapLeafHash := tapLeaf.TapHash()
|
||||
|
||||
var opts []TaprootSigHashOption
|
||||
opts = append(
|
||||
opts, WithBaseTapscriptVersion(blankCodeSepValue, tapLeafHash[:]),
|
||||
)
|
||||
opts = append(opts, sigHashOpts...)
|
||||
|
||||
return calcTaprootSignatureHashRaw(
|
||||
sigHashes, hType, tx, idx, prevOutFetcher, opts...,
|
||||
)
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
)
|
||||
|
||||
type addressToKey struct {
|
||||
@ -56,7 +56,7 @@ func mkGetScript(scripts map[string][]byte) ScriptDB {
|
||||
func checkScripts(msg string, tx *wire.MsgTx, idx int, inputAmt int64, sigScript, pkScript []byte) error {
|
||||
tx.TxIn[idx].SignatureScript = sigScript
|
||||
vm, err := NewEngine(pkScript, tx, idx,
|
||||
ScriptBip16|ScriptVerifyDERSignatures, nil, nil, inputAmt)
|
||||
ScriptBip16|ScriptVerifyDERSignatures, nil, nil, inputAmt, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make script engine for %s: %v",
|
||||
msg, err)
|
||||
@ -1672,7 +1672,7 @@ nexttest:
|
||||
scriptFlags := ScriptBip16 | ScriptVerifyDERSignatures
|
||||
for j := range tx.TxIn {
|
||||
vm, err := NewEngine(sigScriptTests[i].
|
||||
inputs[j].txout.PkScript, tx, j, scriptFlags, nil, nil, 0)
|
||||
inputs[j].txout.PkScript, tx, j, scriptFlags, nil, nil, 0, nil)
|
||||
if err != nil {
|
||||
t.Errorf("cannot create script vm for test %v: %v",
|
||||
sigScriptTests[i].name, err)
|
||||
|
@ -466,13 +466,25 @@ func isWitnessTaprootScript(script []byte) bool {
|
||||
|
||||
// isAnnexedWitness returns true if the passed witness has a final push
|
||||
// that is a witness annex.
|
||||
func isAnnexedWitness(witness [][]byte) bool {
|
||||
const OP_ANNEX = 0x50
|
||||
func isAnnexedWitness(witness wire.TxWitness) bool {
|
||||
if len(witness) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
lastElement := witness[len(witness)-1]
|
||||
return len(lastElement) > 0 && lastElement[0] == OP_ANNEX
|
||||
return len(lastElement) > 0 && lastElement[0] == TaprootAnnexTag
|
||||
}
|
||||
|
||||
// extractAnnex attempts to extract the annex from the passed witness. If the
|
||||
// witness doesn't contain an annex, then an error is returned.
|
||||
func extractAnnex(witness [][]byte) ([]byte, error) {
|
||||
if !isAnnexedWitness(witness) {
|
||||
// TODO(roasbeef): make into actual type
|
||||
return nil, fmt.Errorf("no witness annex")
|
||||
}
|
||||
|
||||
lastElement := witness[len(witness)-1]
|
||||
return lastElement, nil
|
||||
}
|
||||
|
||||
// isNullDataScript returns whether or not the passed script is a standard
|
||||
|
Loading…
Reference in New Issue
Block a user