// Copyright (c) 2013-2015 Conformal Systems LLC. // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package txscript import ( "encoding/hex" "encoding/json" "errors" "fmt" "io/ioutil" "strconv" "strings" "testing" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" ) // testName returns a descriptive test name for the given reference test data. func testName(test []string) (string, error) { var name string if len(test) < 3 || len(test) > 4 { return name, fmt.Errorf("invalid test length %d", len(test)) } if len(test) == 4 { name = fmt.Sprintf("test (%s)", test[3]) } else { name = fmt.Sprintf("test ([%s, %s, %s])", test[0], test[1], test[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:]) } // parseShortForm parses a string as as used in the Bitcoin Core reference tests // into the script it came from. func parseShortForm(script string) ([]byte, error) { ops := make(map[string]byte) // 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. for opcodeName, opcodeValue := range OpcodeByName { if opcodeValue < OP_NOP && opcodeValue != OP_RESERVED { continue } if strings.Contains(opcodeName, "OP_UNKNOWN") { continue } ops[opcodeName] = opcodeValue ops[strings.TrimPrefix(opcodeName, "OP_")] = opcodeValue } // do once, build map. // 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 { // naughty... 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 := ops[tok]; ok { builder.AddOp(opcode) } else { return nil, fmt.Errorf("bad token \"%s\"", 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 "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 "P2SH": flags |= ScriptBip16 case "SIGPUSHONLY": flags |= ScriptVerifySigPushOnly case "STRICTENC": flags |= ScriptVerifyStrictEncoding default: return flags, fmt.Errorf("invalid flag: %s", flag) } } return flags, nil } // createSpendTx generates a basic spending transaction given the passed // signature and public key scripts. func createSpendingTx(sigScript, pkScript []byte) *wire.MsgTx { coinbaseTx := wire.NewMsgTx() outPoint := wire.NewOutPoint(&wire.ShaHash{}, ^uint32(0)) txIn := wire.NewTxIn(outPoint, []byte{OP_0, OP_0}) txOut := wire.NewTxOut(0, pkScript) coinbaseTx.AddTxIn(txIn) coinbaseTx.AddTxOut(txOut) spendingTx := wire.NewMsgTx() coinbaseTxSha := coinbaseTx.TxSha() outPoint = wire.NewOutPoint(&coinbaseTxSha, 0) txIn = wire.NewTxIn(outPoint, sigScript) txOut = wire.NewTxOut(0, nil) spendingTx.AddTxIn(txIn) spendingTx.AddTxOut(txOut) return spendingTx } // TestScriptInvalidTests ensures all of the tests in script_invalid.json fail // as expected. func TestScriptInvalidTests(t *testing.T) { file, err := ioutil.ReadFile("data/script_invalid.json") if err != nil { t.Errorf("TestBitcoindInvalidTests: %v\n", err) return } var tests [][]string err = json.Unmarshal(file, &tests) if err != nil { t.Errorf("TestBitcoindInvalidTests couldn't Unmarshal: %v", err) return } for i, test := range tests { // Skip comments if len(test) == 1 { continue } name, err := testName(test) if err != nil { t.Errorf("TestBitcoindInvalidTests: invalid test #%d", i) continue } scriptSig, err := parseShortForm(test[0]) if err != nil { t.Errorf("%s: can't parse scriptSig; %v", name, err) continue } scriptPubKey, err := parseShortForm(test[1]) if err != nil { t.Errorf("%s: can't parse scriptPubkey; %v", name, err) continue } flags, err := parseScriptFlags(test[2]) if err != nil { t.Errorf("%s: %v", name, err) continue } tx := createSpendingTx(scriptSig, scriptPubKey) vm, err := NewEngine(scriptPubKey, tx, 0, flags) if err == nil { if err := vm.Execute(); err == nil { t.Errorf("%s test succeeded when it "+ "should have failed\n", name) } continue } } } // TestScriptValidTests ensures all of the tests in script_valid.json pass as // expected. func TestScriptValidTests(t *testing.T) { file, err := ioutil.ReadFile("data/script_valid.json") if err != nil { t.Errorf("TestBitcoinValidTests: %v\n", err) return } var tests [][]string err = json.Unmarshal(file, &tests) if err != nil { t.Errorf("TestBitcoindValidTests couldn't Unmarshal: %v", err) return } for i, test := range tests { // Skip comments if len(test) == 1 { continue } name, err := testName(test) if err != nil { t.Errorf("TestBitcoindValidTests: invalid test #%d", i) continue } scriptSig, err := parseShortForm(test[0]) if err != nil { t.Errorf("%s: can't parse scriptSig; %v", name, err) continue } scriptPubKey, err := parseShortForm(test[1]) if err != nil { t.Errorf("%s: can't parse scriptPubkey; %v", name, err) continue } flags, err := parseScriptFlags(test[2]) if err != nil { t.Errorf("%s: %v", name, err) continue } tx := createSpendingTx(scriptSig, scriptPubKey) vm, err := NewEngine(scriptPubKey, tx, 0, flags) if err != nil { t.Errorf("%s failed to create script: %v", name, err) continue } err = vm.Execute() if err != nil { t.Errorf("%s failed to execute: %v", name, err) continue } } } // 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.Errorf("TestBitcoindInvalidTests: %v\n", err) return } var tests [][]interface{} err = json.Unmarshal(file, &tests) if err != nil { t.Errorf("TestBitcoindInvalidTests couldn't Unmarshal: %v\n", err) return } // 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 } prevOuts := make(map[wire.OutPoint][]byte) 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 { 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 sha not string)"+ "%d: %v", j, i, test) continue testloop } prevhash, err := wire.NewShaHashFromStr(previoustx) if err != nil { t.Errorf("bad test (%dth input sha not sha %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 := uint32(idxf) // (floor(idxf) == 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 } prevOuts[*wire.NewOutPoint(prevhash, idx)] = script } for k, txin := range tx.MsgTx().TxIn { pkScript, ok := prevOuts[txin.PreviousOutPoint] if !ok { 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(pkScript, tx.MsgTx(), k, flags) 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.Errorf("TestBitcoindInvalidTests: %v\n", err) return } var tests [][]interface{} err = json.Unmarshal(file, &tests) if err != nil { t.Errorf("TestBitcoindInvalidTests couldn't Unmarshal: %v\n", err) return } // 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 } prevOuts := make(map[wire.OutPoint][]byte) 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 { 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 sha not string)"+ "%d: %v", j, i, test) continue } prevhash, err := wire.NewShaHashFromStr(previoustx) if err != nil { t.Errorf("bad test (%dth input sha not sha %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 := uint32(idxf) // (floor(idxf) == 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 } prevOuts[*wire.NewOutPoint(prevhash, idx)] = script } for k, txin := range tx.MsgTx().TxIn { pkScript, ok := prevOuts[txin.PreviousOutPoint] if !ok { t.Errorf("bad test (missing %dth input) %d:%v", k, i, test) continue testloop } vm, err := NewEngine(pkScript, tx.MsgTx(), k, flags) 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 } } } }