btcd/txscript/hashcache_test.go
Olaoluwa Osuntokun e781b66e2f
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.
2022-03-15 18:22:43 -07:00

199 lines
5.0 KiB
Go

// Copyright (c) 2017 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 (
"math/rand"
"testing"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
)
func init() {
rand.Seed(time.Now().Unix())
}
// genTestTx creates a random transaction for uses within test cases.
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{
PreviousOutPoint: wire.OutPoint{
Index: uint32(rand.Int31()),
},
Sequence: uint32(rand.Int31()),
}
_, err := rand.Read(randTxIn.PreviousOutPoint.Hash[:])
if err != nil {
return nil, nil, err
}
tx.TxIn = append(tx.TxIn, &randTxIn)
prevOuts.AddPrevOut(
randTxIn.PreviousOutPoint, &wire.TxOut{},
)
}
numTxouts := 1 + rand.Intn(11)
for i := 0; i < numTxouts; i++ {
randTxOut := wire.TxOut{
Value: rand.Int63(),
PkScript: make([]byte, rand.Intn(30)),
}
if _, err := rand.Read(randTxOut.PkScript); err != nil {
return nil, nil, err
}
tx.TxOut = append(tx.TxOut, &randTxOut)
}
return tx, prevOuts, nil
}
// TestHashCacheAddContainsHashes tests that after items have been added to the
// hash cache, the ContainsHashes method returns true for all the items
// inserted. Conversely, ContainsHashes should return false for any items
// _not_ in the hash cache.
func TestHashCacheAddContainsHashes(t *testing.T) {
t.Parallel()
cache := NewHashCache(10)
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], 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, prevOuts)
}
// Next, we'll ensure that each of the transactions inserted into the
// cache are properly located by the ContainsHashes method.
for _, tx := range txns {
txid := tx.TxHash()
if ok := cache.ContainsHashes(&txid); !ok {
t.Fatalf("txid %v not found in cache but should be: ",
txid)
}
}
randTx, _, err := genTestTx()
if err != nil {
t.Fatalf("unable to generate tx: %v", err)
}
// Finally, we'll assert that a transaction that wasn't added to the
// cache won't be reported as being present by the ContainsHashes
// method.
randTxid := randTx.TxHash()
if ok := cache.ContainsHashes(&randTxid); ok {
t.Fatalf("txid %v wasn't inserted into cache but was found",
randTxid)
}
}
// TestHashCacheAddGet tests that the sighashes for a particular transaction
// are properly retrieved by the GetSigHashes function.
func TestHashCacheAddGet(t *testing.T) {
t.Parallel()
cache := NewHashCache(10)
// To start, we'll generate a random transaction and compute the set of
// sighashes for the transaction.
randTx, prevOuts, err := genTestTx()
if err != nil {
t.Fatalf("unable to generate tx: %v", err)
}
sigHashes := NewTxSigHashes(randTx, prevOuts)
// Next, add the transaction to the hash cache.
cache.AddSigHashes(randTx, prevOuts)
// The transaction inserted into the cache above should be found.
txid := randTx.TxHash()
cacheHashes, ok := cache.GetSigHashes(&txid)
if !ok {
t.Fatalf("tx %v wasn't found in cache", txid)
}
// Finally, the sighashes retrieved should exactly match the sighash
// originally inserted into the cache.
if *sigHashes != *cacheHashes {
t.Fatalf("sighashes don't match: expected %v, got %v",
spew.Sdump(sigHashes), spew.Sdump(cacheHashes))
}
}
// TestHashCachePurge tests that items are able to be properly removed from the
// hash cache.
func TestHashCachePurge(t *testing.T) {
t.Parallel()
cache := NewHashCache(10)
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], 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, prevOuts)
}
// Once all the transactions have been inserted, we'll purge them from
// the hash cache.
for _, tx := range txns {
txid := tx.TxHash()
cache.PurgeSigHashes(&txid)
}
// At this point, none of the transactions inserted into the hash cache
// should be found within the cache.
for _, tx := range txns {
txid := tx.TxHash()
if ok := cache.ContainsHashes(&txid); ok {
t.Fatalf("tx %v found in cache but should have "+
"been purged: ", txid)
}
}
}