mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 18:00:11 +01:00
79c314d503
In this commit, we add a total of 2760 taproot reference tests generated by the bitcoind functional tests located at: https://github.com/bitcoin/bitcoin/blob/master/test/functional/feature_taproot.py. The tests aren't deterministic (fresh private keys are generated), so we time we go to update the set of tests, we'll end up with fresh hashes (the file name is the sha1 of the raw json test) and tests.
1079 lines
29 KiB
Go
1079 lines
29 KiB
Go
// Copyright (c) 2013-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 (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
)
|
|
|
|
// scriptTestName returns a descriptive test name for the given reference script
|
|
// test data.
|
|
func scriptTestName(test []interface{}) (string, error) {
|
|
// Account for any optional leading witness data.
|
|
var witnessOffset int
|
|
if _, ok := test[0].([]interface{}); ok {
|
|
witnessOffset++
|
|
}
|
|
|
|
// In addition to the optional leading witness data, the test must
|
|
// consist of at least a signature script, public key script, flags,
|
|
// and expected error. Finally, it may optionally contain a comment.
|
|
if len(test) < witnessOffset+4 || len(test) > witnessOffset+5 {
|
|
return "", fmt.Errorf("invalid test length %d", len(test))
|
|
}
|
|
|
|
// Use the comment for the test name if one is specified, otherwise,
|
|
// construct the name based on the signature script, public key script,
|
|
// and flags.
|
|
var name string
|
|
if len(test) == witnessOffset+5 {
|
|
name = fmt.Sprintf("test (%s)", test[witnessOffset+4])
|
|
} else {
|
|
name = fmt.Sprintf("test ([%s, %s, %s])", test[witnessOffset],
|
|
test[witnessOffset+1], test[witnessOffset+2])
|
|
}
|
|
return name, nil
|
|
}
|
|
|
|
// parse hex string into a []byte.
|
|
func parseHex(tok string) ([]byte, error) {
|
|
if !strings.HasPrefix(tok, "0x") {
|
|
return nil, errors.New("not a hex number")
|
|
}
|
|
return hex.DecodeString(tok[2:])
|
|
}
|
|
|
|
// parseWitnessStack parses a json array of witness items encoded as hex into a
|
|
// slice of witness elements.
|
|
func parseWitnessStack(elements []interface{}) ([][]byte, error) {
|
|
witness := make([][]byte, len(elements))
|
|
for i, e := range elements {
|
|
witElement, err := hex.DecodeString(e.(string))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
witness[i] = witElement
|
|
}
|
|
|
|
return witness, nil
|
|
}
|
|
|
|
// shortFormOps holds a map of opcode names to values for use in short form
|
|
// parsing. It is declared here so it only needs to be created once.
|
|
var shortFormOps map[string]byte
|
|
|
|
// parseShortForm parses a string as as used in the Bitcoin Core reference tests
|
|
// into the script it came from.
|
|
//
|
|
// The format used for these tests is pretty simple if ad-hoc:
|
|
// - Opcodes other than the push opcodes and unknown are present as
|
|
// either OP_NAME or just NAME
|
|
// - Plain numbers are made into push operations
|
|
// - Numbers beginning with 0x are inserted into the []byte as-is (so
|
|
// 0x14 is OP_DATA_20)
|
|
// - Single quoted strings are pushed as data
|
|
// - Anything else is an error
|
|
func parseShortForm(script string) ([]byte, error) {
|
|
// Only create the short form opcode map once.
|
|
if shortFormOps == nil {
|
|
ops := make(map[string]byte)
|
|
for opcodeName, opcodeValue := range OpcodeByName {
|
|
if strings.Contains(opcodeName, "OP_UNKNOWN") {
|
|
continue
|
|
}
|
|
ops[opcodeName] = opcodeValue
|
|
|
|
// The opcodes named OP_# can't have the OP_ prefix
|
|
// stripped or they would conflict with the plain
|
|
// numbers. Also, since OP_FALSE and OP_TRUE are
|
|
// aliases for the OP_0, and OP_1, respectively, they
|
|
// have the same value, so detect those by name and
|
|
// allow them.
|
|
if (opcodeName == "OP_FALSE" || opcodeName == "OP_TRUE") ||
|
|
(opcodeValue != OP_0 && (opcodeValue < OP_1 ||
|
|
opcodeValue > OP_16)) {
|
|
|
|
ops[strings.TrimPrefix(opcodeName, "OP_")] = opcodeValue
|
|
}
|
|
}
|
|
shortFormOps = ops
|
|
}
|
|
|
|
// Split only does one separator so convert all \n and tab into space.
|
|
script = strings.Replace(script, "\n", " ", -1)
|
|
script = strings.Replace(script, "\t", " ", -1)
|
|
tokens := strings.Split(script, " ")
|
|
builder := NewScriptBuilder()
|
|
|
|
for _, tok := range tokens {
|
|
if len(tok) == 0 {
|
|
continue
|
|
}
|
|
// if parses as a plain number
|
|
if num, err := strconv.ParseInt(tok, 10, 64); err == nil {
|
|
builder.AddInt64(num)
|
|
continue
|
|
} else if bts, err := parseHex(tok); err == nil {
|
|
// Concatenate the bytes manually since the test code
|
|
// intentionally creates scripts that are too large and
|
|
// would cause the builder to error otherwise.
|
|
if builder.err == nil {
|
|
builder.script = append(builder.script, bts...)
|
|
}
|
|
} else if len(tok) >= 2 &&
|
|
tok[0] == '\'' && tok[len(tok)-1] == '\'' {
|
|
builder.AddFullData([]byte(tok[1 : len(tok)-1]))
|
|
} else if opcode, ok := shortFormOps[tok]; ok {
|
|
builder.AddOp(opcode)
|
|
} else {
|
|
return nil, fmt.Errorf("bad token %q", tok)
|
|
}
|
|
|
|
}
|
|
return builder.Script()
|
|
}
|
|
|
|
// parseScriptFlags parses the provided flags string from the format used in the
|
|
// reference tests into ScriptFlags suitable for use in the script engine.
|
|
func parseScriptFlags(flagStr string) (ScriptFlags, error) {
|
|
var flags ScriptFlags
|
|
|
|
sFlags := strings.Split(flagStr, ",")
|
|
for _, flag := range sFlags {
|
|
switch flag {
|
|
case "":
|
|
// Nothing.
|
|
case "CHECKLOCKTIMEVERIFY":
|
|
flags |= ScriptVerifyCheckLockTimeVerify
|
|
case "CHECKSEQUENCEVERIFY":
|
|
flags |= ScriptVerifyCheckSequenceVerify
|
|
case "CLEANSTACK":
|
|
flags |= ScriptVerifyCleanStack
|
|
case "DERSIG":
|
|
flags |= ScriptVerifyDERSignatures
|
|
case "DISCOURAGE_UPGRADABLE_NOPS":
|
|
flags |= ScriptDiscourageUpgradableNops
|
|
case "LOW_S":
|
|
flags |= ScriptVerifyLowS
|
|
case "MINIMALDATA":
|
|
flags |= ScriptVerifyMinimalData
|
|
case "NONE":
|
|
// Nothing.
|
|
case "NULLDUMMY":
|
|
flags |= ScriptStrictMultiSig
|
|
case "NULLFAIL":
|
|
flags |= ScriptVerifyNullFail
|
|
case "P2SH":
|
|
flags |= ScriptBip16
|
|
case "SIGPUSHONLY":
|
|
flags |= ScriptVerifySigPushOnly
|
|
case "STRICTENC":
|
|
flags |= ScriptVerifyStrictEncoding
|
|
case "WITNESS":
|
|
flags |= ScriptVerifyWitness
|
|
case "DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM":
|
|
flags |= ScriptVerifyDiscourageUpgradeableWitnessProgram
|
|
case "MINIMALIF":
|
|
flags |= ScriptVerifyMinimalIf
|
|
case "WITNESS_PUBKEYTYPE":
|
|
flags |= ScriptVerifyWitnessPubKeyType
|
|
case "TAPROOT":
|
|
flags |= ScriptVerifyTaproot
|
|
default:
|
|
return flags, fmt.Errorf("invalid flag: %s", flag)
|
|
}
|
|
}
|
|
return flags, nil
|
|
}
|
|
|
|
// parseExpectedResult parses the provided expected result string into allowed
|
|
// script error codes. An error is returned if the expected result string is
|
|
// not supported.
|
|
func parseExpectedResult(expected string) ([]ErrorCode, error) {
|
|
switch expected {
|
|
case "OK":
|
|
return nil, nil
|
|
case "UNKNOWN_ERROR":
|
|
return []ErrorCode{ErrNumberTooBig, ErrMinimalData}, nil
|
|
case "PUBKEYTYPE":
|
|
return []ErrorCode{ErrPubKeyType}, nil
|
|
case "SIG_DER":
|
|
return []ErrorCode{ErrSigTooShort, ErrSigTooLong,
|
|
ErrSigInvalidSeqID, ErrSigInvalidDataLen, ErrSigMissingSTypeID,
|
|
ErrSigMissingSLen, ErrSigInvalidSLen,
|
|
ErrSigInvalidRIntID, ErrSigZeroRLen, ErrSigNegativeR,
|
|
ErrSigTooMuchRPadding, ErrSigInvalidSIntID,
|
|
ErrSigZeroSLen, ErrSigNegativeS, ErrSigTooMuchSPadding,
|
|
ErrInvalidSigHashType}, nil
|
|
case "EVAL_FALSE":
|
|
return []ErrorCode{ErrEvalFalse, ErrEmptyStack}, nil
|
|
case "EQUALVERIFY":
|
|
return []ErrorCode{ErrEqualVerify}, nil
|
|
case "NULLFAIL":
|
|
return []ErrorCode{ErrNullFail}, nil
|
|
case "SIG_HIGH_S":
|
|
return []ErrorCode{ErrSigHighS}, nil
|
|
case "SIG_HASHTYPE":
|
|
return []ErrorCode{ErrInvalidSigHashType}, nil
|
|
case "SIG_NULLDUMMY":
|
|
return []ErrorCode{ErrSigNullDummy}, nil
|
|
case "SIG_PUSHONLY":
|
|
return []ErrorCode{ErrNotPushOnly}, nil
|
|
case "CLEANSTACK":
|
|
return []ErrorCode{ErrCleanStack}, nil
|
|
case "BAD_OPCODE":
|
|
return []ErrorCode{ErrReservedOpcode, ErrMalformedPush}, nil
|
|
case "UNBALANCED_CONDITIONAL":
|
|
return []ErrorCode{ErrUnbalancedConditional,
|
|
ErrInvalidStackOperation}, nil
|
|
case "OP_RETURN":
|
|
return []ErrorCode{ErrEarlyReturn}, nil
|
|
case "VERIFY":
|
|
return []ErrorCode{ErrVerify}, nil
|
|
case "INVALID_STACK_OPERATION", "INVALID_ALTSTACK_OPERATION":
|
|
return []ErrorCode{ErrInvalidStackOperation}, nil
|
|
case "DISABLED_OPCODE":
|
|
return []ErrorCode{ErrDisabledOpcode}, nil
|
|
case "DISCOURAGE_UPGRADABLE_NOPS":
|
|
return []ErrorCode{ErrDiscourageUpgradableNOPs}, nil
|
|
case "PUSH_SIZE":
|
|
return []ErrorCode{ErrElementTooBig}, nil
|
|
case "OP_COUNT":
|
|
return []ErrorCode{ErrTooManyOperations}, nil
|
|
case "STACK_SIZE":
|
|
return []ErrorCode{ErrStackOverflow}, nil
|
|
case "SCRIPT_SIZE":
|
|
return []ErrorCode{ErrScriptTooBig}, nil
|
|
case "PUBKEY_COUNT":
|
|
return []ErrorCode{ErrInvalidPubKeyCount}, nil
|
|
case "SIG_COUNT":
|
|
return []ErrorCode{ErrInvalidSignatureCount}, nil
|
|
case "MINIMALDATA":
|
|
return []ErrorCode{ErrMinimalData}, nil
|
|
case "NEGATIVE_LOCKTIME":
|
|
return []ErrorCode{ErrNegativeLockTime}, nil
|
|
case "UNSATISFIED_LOCKTIME":
|
|
return []ErrorCode{ErrUnsatisfiedLockTime}, nil
|
|
case "MINIMALIF":
|
|
return []ErrorCode{ErrMinimalIf}, nil
|
|
case "DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM":
|
|
return []ErrorCode{ErrDiscourageUpgradableWitnessProgram}, nil
|
|
case "WITNESS_PROGRAM_WRONG_LENGTH":
|
|
return []ErrorCode{ErrWitnessProgramWrongLength}, nil
|
|
case "WITNESS_PROGRAM_WITNESS_EMPTY":
|
|
return []ErrorCode{ErrWitnessProgramEmpty}, nil
|
|
case "WITNESS_PROGRAM_MISMATCH":
|
|
return []ErrorCode{ErrWitnessProgramMismatch}, nil
|
|
case "WITNESS_MALLEATED":
|
|
return []ErrorCode{ErrWitnessMalleated}, nil
|
|
case "WITNESS_MALLEATED_P2SH":
|
|
return []ErrorCode{ErrWitnessMalleatedP2SH}, nil
|
|
case "WITNESS_UNEXPECTED":
|
|
return []ErrorCode{ErrWitnessUnexpected}, nil
|
|
case "WITNESS_PUBKEYTYPE":
|
|
return []ErrorCode{ErrWitnessPubKeyType}, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unrecognized expected result in test data: %v",
|
|
expected)
|
|
}
|
|
|
|
// createSpendTx generates a basic spending transaction given the passed
|
|
// signature, witness and public key scripts.
|
|
func createSpendingTx(witness [][]byte, sigScript, pkScript []byte,
|
|
outputValue int64) *wire.MsgTx {
|
|
|
|
coinbaseTx := wire.NewMsgTx(wire.TxVersion)
|
|
|
|
outPoint := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0))
|
|
txIn := wire.NewTxIn(outPoint, []byte{OP_0, OP_0}, nil)
|
|
txOut := wire.NewTxOut(outputValue, pkScript)
|
|
coinbaseTx.AddTxIn(txIn)
|
|
coinbaseTx.AddTxOut(txOut)
|
|
|
|
spendingTx := wire.NewMsgTx(wire.TxVersion)
|
|
coinbaseTxSha := coinbaseTx.TxHash()
|
|
outPoint = wire.NewOutPoint(&coinbaseTxSha, 0)
|
|
txIn = wire.NewTxIn(outPoint, sigScript, witness)
|
|
txOut = wire.NewTxOut(outputValue, nil)
|
|
|
|
spendingTx.AddTxIn(txIn)
|
|
spendingTx.AddTxOut(txOut)
|
|
|
|
return spendingTx
|
|
}
|
|
|
|
// scriptWithInputVal wraps a target pkScript with the value of the output in
|
|
// which it is contained. The inputVal is necessary in order to properly
|
|
// validate inputs which spend nested, or native witness programs.
|
|
type scriptWithInputVal struct {
|
|
inputVal int64
|
|
pkScript []byte
|
|
}
|
|
|
|
// testScripts ensures all of the passed script tests execute with the expected
|
|
// results with or without using a signature cache, as specified by the
|
|
// parameter.
|
|
func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) {
|
|
// Create a signature cache to use only if requested.
|
|
var sigCache *SigCache
|
|
if useSigCache {
|
|
sigCache = NewSigCache(10)
|
|
}
|
|
|
|
for i, test := range tests {
|
|
// "Format is: [[wit..., amount]?, scriptSig, scriptPubKey,
|
|
// flags, expected_scripterror, ... comments]"
|
|
|
|
// Skip single line comments.
|
|
if len(test) == 1 {
|
|
continue
|
|
}
|
|
|
|
// Construct a name for the test based on the comment and test
|
|
// data.
|
|
name, err := scriptTestName(test)
|
|
if err != nil {
|
|
t.Errorf("TestScripts: invalid test #%d: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
var (
|
|
witness wire.TxWitness
|
|
inputAmt btcutil.Amount
|
|
)
|
|
|
|
// When the first field of the test data is a slice it contains
|
|
// witness data and everything else is offset by 1 as a result.
|
|
witnessOffset := 0
|
|
if witnessData, ok := test[0].([]interface{}); ok {
|
|
witnessOffset++
|
|
|
|
// If this is a witness test, then the final element
|
|
// within the slice is the input amount, so we ignore
|
|
// all but the last element in order to parse the
|
|
// witness stack.
|
|
strWitnesses := witnessData[:len(witnessData)-1]
|
|
witness, err = parseWitnessStack(strWitnesses)
|
|
if err != nil {
|
|
t.Errorf("%s: can't parse witness; %v", name, err)
|
|
continue
|
|
}
|
|
|
|
inputAmt, err = btcutil.NewAmount(witnessData[len(witnessData)-1].(float64))
|
|
if err != nil {
|
|
t.Errorf("%s: can't parse input amt: %v",
|
|
name, err)
|
|
continue
|
|
}
|
|
|
|
}
|
|
|
|
// Extract and parse the signature script from the test fields.
|
|
scriptSigStr, ok := test[witnessOffset].(string)
|
|
if !ok {
|
|
t.Errorf("%s: signature script is not a string", name)
|
|
continue
|
|
}
|
|
scriptSig, err := parseShortForm(scriptSigStr)
|
|
if err != nil {
|
|
t.Errorf("%s: can't parse signature script: %v", name,
|
|
err)
|
|
continue
|
|
}
|
|
|
|
// Extract and parse the public key script from the test fields.
|
|
scriptPubKeyStr, ok := test[witnessOffset+1].(string)
|
|
if !ok {
|
|
t.Errorf("%s: public key script is not a string", name)
|
|
continue
|
|
}
|
|
scriptPubKey, err := parseShortForm(scriptPubKeyStr)
|
|
if err != nil {
|
|
t.Errorf("%s: can't parse public key script: %v", name,
|
|
err)
|
|
continue
|
|
}
|
|
|
|
// Extract and parse the script flags from the test fields.
|
|
flagsStr, ok := test[witnessOffset+2].(string)
|
|
if !ok {
|
|
t.Errorf("%s: flags field is not a string", name)
|
|
continue
|
|
}
|
|
flags, err := parseScriptFlags(flagsStr)
|
|
if err != nil {
|
|
t.Errorf("%s: %v", name, err)
|
|
continue
|
|
}
|
|
|
|
// Extract and parse the expected result from the test fields.
|
|
//
|
|
// Convert the expected result string into the allowed script
|
|
// error codes. This is necessary because txscript is more
|
|
// fine grained with its errors than the reference test data, so
|
|
// some of the reference test data errors map to more than one
|
|
// possibility.
|
|
resultStr, ok := test[witnessOffset+3].(string)
|
|
if !ok {
|
|
t.Errorf("%s: result field is not a string", name)
|
|
continue
|
|
}
|
|
allowedErrorCodes, err := parseExpectedResult(resultStr)
|
|
if err != nil {
|
|
t.Errorf("%s: %v", name, err)
|
|
continue
|
|
}
|
|
|
|
// 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),
|
|
)
|
|
prevOuts := NewCannedPrevOutputFetcher(scriptPubKey, int64(inputAmt))
|
|
vm, err := NewEngine(
|
|
scriptPubKey, tx, 0, flags, sigCache, nil,
|
|
int64(inputAmt), prevOuts,
|
|
)
|
|
if err == nil {
|
|
err = vm.Execute()
|
|
}
|
|
|
|
// Ensure there were no errors when the expected result is OK.
|
|
if resultStr == "OK" {
|
|
if err != nil {
|
|
t.Errorf("%s failed to execute: %v", name, err)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// At this point an error was expected so ensure the result of
|
|
// the execution matches it.
|
|
success := false
|
|
for _, code := range allowedErrorCodes {
|
|
if IsErrorCode(err, code) {
|
|
success = true
|
|
break
|
|
}
|
|
}
|
|
if !success {
|
|
if serr, ok := err.(Error); ok {
|
|
t.Errorf("%s: want error codes %v, got %v", name,
|
|
allowedErrorCodes, serr.ErrorCode)
|
|
continue
|
|
}
|
|
t.Errorf("%s: want error codes %v, got err: %v (%T)",
|
|
name, allowedErrorCodes, err, err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestScripts ensures all of the tests in script_tests.json execute with the
|
|
// expected results as defined in the test data.
|
|
func TestScripts(t *testing.T) {
|
|
file, err := ioutil.ReadFile("data/script_tests.json")
|
|
if err != nil {
|
|
t.Fatalf("TestScripts: %v\n", err)
|
|
}
|
|
|
|
var tests [][]interface{}
|
|
err = json.Unmarshal(file, &tests)
|
|
if err != nil {
|
|
t.Fatalf("TestScripts couldn't Unmarshal: %v", err)
|
|
}
|
|
|
|
// Run all script tests with and without the signature cache.
|
|
testScripts(t, tests, true)
|
|
testScripts(t, tests, false)
|
|
}
|
|
|
|
// testVecF64ToUint32 properly handles conversion of float64s read from the JSON
|
|
// test data to unsigned 32-bit integers. This is necessary because some of the
|
|
// test data uses -1 as a shortcut to mean max uint32 and direct conversion of a
|
|
// negative float to an unsigned int is implementation dependent and therefore
|
|
// doesn't result in the expected value on all platforms. This function woks
|
|
// around that limitation by converting to a 32-bit signed integer first and
|
|
// then to a 32-bit unsigned integer which results in the expected behavior on
|
|
// all platforms.
|
|
func testVecF64ToUint32(f float64) uint32 {
|
|
return uint32(int32(f))
|
|
}
|
|
|
|
// TestTxInvalidTests ensures all of the tests in tx_invalid.json fail as
|
|
// expected.
|
|
func TestTxInvalidTests(t *testing.T) {
|
|
file, err := ioutil.ReadFile("data/tx_invalid.json")
|
|
if err != nil {
|
|
t.Fatalf("TestTxInvalidTests: %v\n", err)
|
|
}
|
|
|
|
var tests [][]interface{}
|
|
err = json.Unmarshal(file, &tests)
|
|
if err != nil {
|
|
t.Fatalf("TestTxInvalidTests couldn't Unmarshal: %v\n", err)
|
|
}
|
|
|
|
// form is either:
|
|
// ["this is a comment "]
|
|
// or:
|
|
// [[[previous hash, previous index, previous scriptPubKey]...,]
|
|
// serializedTransaction, verifyFlags]
|
|
testloop:
|
|
for i, test := range tests {
|
|
inputs, ok := test[0].([]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if len(test) != 3 {
|
|
t.Errorf("bad test (bad length) %d: %v", i, test)
|
|
continue
|
|
|
|
}
|
|
serializedhex, ok := test[1].(string)
|
|
if !ok {
|
|
t.Errorf("bad test (arg 2 not string) %d: %v", i, test)
|
|
continue
|
|
}
|
|
serializedTx, err := hex.DecodeString(serializedhex)
|
|
if err != nil {
|
|
t.Errorf("bad test (arg 2 not hex %v) %d: %v", err, i,
|
|
test)
|
|
continue
|
|
}
|
|
|
|
tx, err := btcutil.NewTxFromBytes(serializedTx)
|
|
if err != nil {
|
|
t.Errorf("bad test (arg 2 not msgtx %v) %d: %v", err,
|
|
i, test)
|
|
continue
|
|
}
|
|
|
|
verifyFlags, ok := test[2].(string)
|
|
if !ok {
|
|
t.Errorf("bad test (arg 3 not string) %d: %v", i, test)
|
|
continue
|
|
}
|
|
|
|
flags, err := parseScriptFlags(verifyFlags)
|
|
if err != nil {
|
|
t.Errorf("bad test %d: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
prevOutFetcher := NewMultiPrevOutFetcher(nil)
|
|
for j, iinput := range inputs {
|
|
input, ok := iinput.([]interface{})
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input not array)"+
|
|
"%d: %v", j, i, test)
|
|
continue testloop
|
|
}
|
|
|
|
if len(input) < 3 || len(input) > 4 {
|
|
t.Errorf("bad test (%dth input wrong length)"+
|
|
"%d: %v", j, i, test)
|
|
continue testloop
|
|
}
|
|
|
|
previoustx, ok := input[0].(string)
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input hash not string)"+
|
|
"%d: %v", j, i, test)
|
|
continue testloop
|
|
}
|
|
|
|
prevhash, err := chainhash.NewHashFromStr(previoustx)
|
|
if err != nil {
|
|
t.Errorf("bad test (%dth input hash not hash %v)"+
|
|
"%d: %v", j, err, i, test)
|
|
continue testloop
|
|
}
|
|
|
|
idxf, ok := input[1].(float64)
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input idx not number)"+
|
|
"%d: %v", j, i, test)
|
|
continue testloop
|
|
}
|
|
idx := testVecF64ToUint32(idxf)
|
|
|
|
oscript, ok := input[2].(string)
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input script not "+
|
|
"string) %d: %v", j, i, test)
|
|
continue testloop
|
|
}
|
|
|
|
script, err := parseShortForm(oscript)
|
|
if err != nil {
|
|
t.Errorf("bad test (%dth input script doesn't "+
|
|
"parse %v) %d: %v", j, err, i, test)
|
|
continue testloop
|
|
}
|
|
|
|
var inputValue float64
|
|
if len(input) == 4 {
|
|
inputValue, ok = input[3].(float64)
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input value not int) "+
|
|
"%d: %v", j, i, test)
|
|
continue
|
|
}
|
|
}
|
|
|
|
op := wire.NewOutPoint(prevhash, idx)
|
|
prevOutFetcher.AddPrevOut(*op, &wire.TxOut{
|
|
Value: int64(inputValue),
|
|
PkScript: script,
|
|
})
|
|
}
|
|
|
|
for k, txin := range tx.MsgTx().TxIn {
|
|
prevOut := prevOutFetcher.FetchPrevOutput(
|
|
txin.PreviousOutPoint,
|
|
)
|
|
if prevOut == nil {
|
|
t.Errorf("bad test (missing %dth input) %d:%v",
|
|
k, i, test)
|
|
continue 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.Value, prevOutFetcher)
|
|
if err != nil {
|
|
continue testloop
|
|
}
|
|
|
|
err = vm.Execute()
|
|
if err != nil {
|
|
continue testloop
|
|
}
|
|
|
|
}
|
|
t.Errorf("test (%d:%v) succeeded when should fail",
|
|
i, test)
|
|
}
|
|
}
|
|
|
|
// TestTxValidTests ensures all of the tests in tx_valid.json pass as expected.
|
|
func TestTxValidTests(t *testing.T) {
|
|
file, err := ioutil.ReadFile("data/tx_valid.json")
|
|
if err != nil {
|
|
t.Fatalf("TestTxValidTests: %v\n", err)
|
|
}
|
|
|
|
var tests [][]interface{}
|
|
err = json.Unmarshal(file, &tests)
|
|
if err != nil {
|
|
t.Fatalf("TestTxValidTests couldn't Unmarshal: %v\n", err)
|
|
}
|
|
|
|
// form is either:
|
|
// ["this is a comment "]
|
|
// or:
|
|
// [[[previous hash, previous index, previous scriptPubKey, input value]...,]
|
|
// serializedTransaction, verifyFlags]
|
|
testloop:
|
|
for i, test := range tests {
|
|
inputs, ok := test[0].([]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if len(test) != 3 {
|
|
t.Errorf("bad test (bad length) %d: %v", i, test)
|
|
continue
|
|
}
|
|
serializedhex, ok := test[1].(string)
|
|
if !ok {
|
|
t.Errorf("bad test (arg 2 not string) %d: %v", i, test)
|
|
continue
|
|
}
|
|
serializedTx, err := hex.DecodeString(serializedhex)
|
|
if err != nil {
|
|
t.Errorf("bad test (arg 2 not hex %v) %d: %v", err, i,
|
|
test)
|
|
continue
|
|
}
|
|
|
|
tx, err := btcutil.NewTxFromBytes(serializedTx)
|
|
if err != nil {
|
|
t.Errorf("bad test (arg 2 not msgtx %v) %d: %v", err,
|
|
i, test)
|
|
continue
|
|
}
|
|
|
|
verifyFlags, ok := test[2].(string)
|
|
if !ok {
|
|
t.Errorf("bad test (arg 3 not string) %d: %v", i, test)
|
|
continue
|
|
}
|
|
|
|
flags, err := parseScriptFlags(verifyFlags)
|
|
if err != nil {
|
|
t.Errorf("bad test %d: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
prevOutFetcher := NewMultiPrevOutFetcher(nil)
|
|
for j, iinput := range inputs {
|
|
input, ok := iinput.([]interface{})
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input not array)"+
|
|
"%d: %v", j, i, test)
|
|
continue
|
|
}
|
|
|
|
if len(input) < 3 || len(input) > 4 {
|
|
t.Errorf("bad test (%dth input wrong length)"+
|
|
"%d: %v", j, i, test)
|
|
continue
|
|
}
|
|
|
|
previoustx, ok := input[0].(string)
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input hash not string)"+
|
|
"%d: %v", j, i, test)
|
|
continue
|
|
}
|
|
|
|
prevhash, err := chainhash.NewHashFromStr(previoustx)
|
|
if err != nil {
|
|
t.Errorf("bad test (%dth input hash not hash %v)"+
|
|
"%d: %v", j, err, i, test)
|
|
continue
|
|
}
|
|
|
|
idxf, ok := input[1].(float64)
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input idx not number)"+
|
|
"%d: %v", j, i, test)
|
|
continue
|
|
}
|
|
idx := testVecF64ToUint32(idxf)
|
|
|
|
oscript, ok := input[2].(string)
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input script not "+
|
|
"string) %d: %v", j, i, test)
|
|
continue
|
|
}
|
|
|
|
script, err := parseShortForm(oscript)
|
|
if err != nil {
|
|
t.Errorf("bad test (%dth input script doesn't "+
|
|
"parse %v) %d: %v", j, err, i, test)
|
|
continue
|
|
}
|
|
|
|
var inputValue float64
|
|
if len(input) == 4 {
|
|
inputValue, ok = input[3].(float64)
|
|
if !ok {
|
|
t.Errorf("bad test (%dth input value not int) "+
|
|
"%d: %v", j, i, test)
|
|
continue
|
|
}
|
|
}
|
|
|
|
op := wire.NewOutPoint(prevhash, idx)
|
|
prevOutFetcher.AddPrevOut(*op, &wire.TxOut{
|
|
Value: int64(inputValue),
|
|
PkScript: script,
|
|
})
|
|
}
|
|
|
|
for k, txin := range tx.MsgTx().TxIn {
|
|
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.Value, prevOutFetcher)
|
|
if err != nil {
|
|
t.Errorf("test (%d:%v:%d) failed to create "+
|
|
"script: %v", i, test, k, err)
|
|
continue
|
|
}
|
|
|
|
err = vm.Execute()
|
|
if err != nil {
|
|
t.Errorf("test (%d:%v:%d) failed to execute: "+
|
|
"%v", i, test, k, err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCalcSignatureHash runs the Bitcoin Core signature hash calculation tests
|
|
// in sighash.json.
|
|
// https://github.com/bitcoin/bitcoin/blob/master/src/test/data/sighash.json
|
|
func TestCalcSignatureHash(t *testing.T) {
|
|
file, err := ioutil.ReadFile("data/sighash.json")
|
|
if err != nil {
|
|
t.Fatalf("TestCalcSignatureHash: %v\n", err)
|
|
}
|
|
|
|
var tests [][]interface{}
|
|
err = json.Unmarshal(file, &tests)
|
|
if err != nil {
|
|
t.Fatalf("TestCalcSignatureHash couldn't Unmarshal: %v\n",
|
|
err)
|
|
}
|
|
|
|
const scriptVersion = 0
|
|
for i, test := range tests {
|
|
if i == 0 {
|
|
// Skip first line -- contains comments only.
|
|
continue
|
|
}
|
|
if len(test) != 5 {
|
|
t.Fatalf("TestCalcSignatureHash: Test #%d has "+
|
|
"wrong length.", i)
|
|
}
|
|
var tx wire.MsgTx
|
|
rawTx, _ := hex.DecodeString(test[0].(string))
|
|
err := tx.Deserialize(bytes.NewReader(rawTx))
|
|
if err != nil {
|
|
t.Errorf("TestCalcSignatureHash failed test #%d: "+
|
|
"Failed to parse transaction: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
subScript, _ := hex.DecodeString(test[1].(string))
|
|
if err := checkScriptParses(scriptVersion, subScript); err != nil {
|
|
t.Errorf("TestCalcSignatureHash failed test #%d: "+
|
|
"Failed to parse sub-script: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
hashType := SigHashType(testVecF64ToUint32(test[3].(float64)))
|
|
hash, err := CalcSignatureHash(subScript, hashType, &tx,
|
|
int(test[2].(float64)))
|
|
if err != nil {
|
|
t.Errorf("TestCalcSignatureHash failed test #%d: "+
|
|
"Failed to compute sighash: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
expectedHash, _ := chainhash.NewHashFromStr(test[4].(string))
|
|
if !bytes.Equal(hash, expectedHash[:]) {
|
|
t.Errorf("TestCalcSignatureHash failed test #%d: "+
|
|
"Signature hash mismatch.", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
type inputWitness struct {
|
|
ScriptSig string `json:"scriptSig"`
|
|
Witness []string `json:"witness"`
|
|
}
|
|
|
|
type taprootJsonTest struct {
|
|
Tx string `json:"tx"`
|
|
Prevouts []string `json:"prevouts"`
|
|
Index int `json:"index"`
|
|
Flags string `json:"flags"`
|
|
|
|
Comment string `json:"comment"`
|
|
|
|
Success *inputWitness `json:"success"`
|
|
|
|
Failure *inputWitness `json:"failure"`
|
|
}
|
|
|
|
func executeTaprootRefTest(t *testing.T, testCase taprootJsonTest) {
|
|
t.Helper()
|
|
|
|
txHex, err := hex.DecodeString(testCase.Tx)
|
|
if err != nil {
|
|
t.Fatalf("unable to decode hex: %v", err)
|
|
}
|
|
tx, err := btcutil.NewTxFromBytes(txHex)
|
|
if err != nil {
|
|
t.Fatalf("unable to decode hex: %v", err)
|
|
}
|
|
|
|
var prevOut wire.TxOut
|
|
|
|
prevOutFetcher := NewMultiPrevOutFetcher(nil)
|
|
for i, prevOutString := range testCase.Prevouts {
|
|
prevOutBytes, err := hex.DecodeString(prevOutString)
|
|
if err != nil {
|
|
t.Fatalf("unable to decode hex: %v", err)
|
|
}
|
|
|
|
var txOut wire.TxOut
|
|
err = wire.ReadTxOut(
|
|
bytes.NewReader(prevOutBytes), 0, 0, &txOut,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to read utxo: %v", err)
|
|
}
|
|
|
|
prevOutFetcher.AddPrevOut(
|
|
tx.MsgTx().TxIn[i].PreviousOutPoint, &txOut,
|
|
)
|
|
|
|
if i == testCase.Index {
|
|
prevOut = txOut
|
|
}
|
|
}
|
|
|
|
flags, err := parseScriptFlags(testCase.Flags)
|
|
if err != nil {
|
|
t.Fatalf("unable to parse flags: %v", err)
|
|
}
|
|
|
|
makeVM := func() *Engine {
|
|
hashCache := NewTxSigHashes(tx.MsgTx(), prevOutFetcher)
|
|
|
|
vm, err := NewEngine(
|
|
prevOut.PkScript, tx.MsgTx(), testCase.Index,
|
|
flags, nil, hashCache, prevOut.Value, prevOutFetcher,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create vm: %v", err)
|
|
}
|
|
|
|
return vm
|
|
}
|
|
|
|
if testCase.Success != nil {
|
|
tx.MsgTx().TxIn[testCase.Index].SignatureScript, err = hex.DecodeString(
|
|
testCase.Success.ScriptSig,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to parse sig script: %v", err)
|
|
}
|
|
|
|
var witness [][]byte
|
|
for _, witnessStr := range testCase.Success.Witness {
|
|
witElem, err := hex.DecodeString(witnessStr)
|
|
if err != nil {
|
|
t.Fatalf("unable to parse witness stack: %v", err)
|
|
}
|
|
|
|
witness = append(witness, witElem)
|
|
}
|
|
|
|
tx.MsgTx().TxIn[testCase.Index].Witness = witness
|
|
|
|
vm := makeVM()
|
|
|
|
err = vm.Execute()
|
|
if err != nil {
|
|
t.Fatalf("test (%v) failed to execute: "+
|
|
"%v", testCase.Comment, err)
|
|
}
|
|
}
|
|
|
|
if testCase.Failure != nil {
|
|
tx.MsgTx().TxIn[testCase.Index].SignatureScript, err = hex.DecodeString(
|
|
testCase.Failure.ScriptSig,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to parse sig script: %v", err)
|
|
}
|
|
|
|
var witness [][]byte
|
|
for _, witnessStr := range testCase.Failure.Witness {
|
|
witElem, err := hex.DecodeString(witnessStr)
|
|
if err != nil {
|
|
t.Fatalf("unable to parse witness stack: %v", err)
|
|
}
|
|
|
|
witness = append(witness, witElem)
|
|
}
|
|
|
|
tx.MsgTx().TxIn[testCase.Index].Witness = witness
|
|
|
|
vm := makeVM()
|
|
|
|
err = vm.Execute()
|
|
if err == nil {
|
|
t.Fatalf("test (%v) succeeded, should fail: "+
|
|
"%v", testCase.Comment, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestTaprootReferenceTests test that we're able to properly validate (success
|
|
// and failure paths for each test) the set of functional generative tests
|
|
// created by the bitcoind project for taproot at:
|
|
// https://github.com/bitcoin/bitcoin/blob/master/test/functional/feature_taproot.py.
|
|
func TestTaprootReferenceTests(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
filePath := "data/taproot-ref"
|
|
|
|
testFunc := func(path string, info fs.FileInfo, walkErr error) error {
|
|
if walkErr != nil {
|
|
return walkErr
|
|
}
|
|
|
|
if info.IsDir() {
|
|
t.Logf("skipping dir: %v", info.Name())
|
|
return nil
|
|
}
|
|
|
|
testJson, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read file: %v", err)
|
|
}
|
|
|
|
// All the JSON files have a trailing comma and a new line
|
|
// character, so we'll remove that here before attempting to
|
|
// parse it.
|
|
testJson = bytes.TrimSuffix(testJson, []byte(",\n"))
|
|
|
|
var testCase taprootJsonTest
|
|
if err := json.Unmarshal(testJson, &testCase); err != nil {
|
|
return fmt.Errorf("unable to decode json: %v", err)
|
|
}
|
|
|
|
testName := fmt.Sprintf(
|
|
"%v:%v", testCase.Comment, filepath.Base(path),
|
|
)
|
|
_ = t.Run(testName, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
executeTaprootRefTest(t, testCase)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
err := filepath.Walk(filePath, testFunc)
|
|
if err != nil {
|
|
t.Fatalf("unable to execute taproot test vectors: %v", err)
|
|
}
|
|
}
|