// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package txscript

import (
	"bytes"
	"errors"
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"testing"

	"github.com/decred/dcrd/wire"
)

// testScriptFlags are the script flags which are used in the tests when
// executing transaction scripts to enforce additional checks.  Note these flags
// are different than what is required for the consensus rules in that they are
// more strict.
const testScriptFlags = ScriptDiscourageUpgradableNops |
	ScriptVerifyCleanStack |
	ScriptVerifyCheckLockTimeVerify |
	ScriptVerifyCheckSequenceVerify |
	ScriptVerifySHA256 |
	ScriptVerifyTreasury

// TestOpcodeDisabled tests the opcodeDisabled function manually because all
// disabled opcodes result in a script execution failure when executed normally,
// so the function is not called under normal circumstances.
func TestOpcodeDisabled(t *testing.T) {
	t.Parallel()
	tests := []byte{OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT, OP_INVERT,
		OP_AND, OP_OR, OP_2MUL, OP_2DIV, OP_MUL, OP_DIV, OP_MOD,
		OP_LSHIFT, OP_RSHIFT,
	}
	for _, opcodeVal := range tests {
		op := &opcodeArray[opcodeVal]
		err := opcodeDisabled(op, nil, nil)
		if !errors.Is(err, ErrDisabledOpcode) {
			t.Errorf("opcodeDisabled: unexpected error - got %v, "+
				"want %v", err, ErrDisabledOpcode)
			continue
		}
	}
}

// TestOpcodeDisasm tests the print function for all opcodes in both the oneline
// and full modes to ensure it provides the expected disassembly.
func TestOpcodeDisasm(t *testing.T) {
	t.Parallel()

	// First, test the oneline disassembly.

	// The expected strings for the data push opcodes are replaced in the
	// test loops below since they involve repeating bytes.  Also, the
	// OP_NOP# and OP_UNKNOWN# are replaced below too, since it's easier
	// than manually listing them here.
	oneBytes := []byte{0x01}
	oneStr := "01"
	expectedStrings := [256]string{0x00: "0", 0x4f: "-1",
		0x50: "OP_RESERVED", 0x61: "OP_NOP", 0x62: "OP_VER",
		0x63: "OP_IF", 0x64: "OP_NOTIF", 0x65: "OP_VERIF",
		0x66: "OP_VERNOTIF", 0x67: "OP_ELSE", 0x68: "OP_ENDIF",
		0x69: "OP_VERIFY", 0x6a: "OP_RETURN", 0x6b: "OP_TOALTSTACK",
		0x6c: "OP_FROMALTSTACK", 0x6d: "OP_2DROP", 0x6e: "OP_2DUP",
		0x6f: "OP_3DUP", 0x70: "OP_2OVER", 0x71: "OP_2ROT",
		0x72: "OP_2SWAP", 0x73: "OP_IFDUP", 0x74: "OP_DEPTH",
		0x75: "OP_DROP", 0x76: "OP_DUP", 0x77: "OP_NIP",
		0x78: "OP_OVER", 0x79: "OP_PICK", 0x7a: "OP_ROLL",
		0x7b: "OP_ROT", 0x7c: "OP_SWAP", 0x7d: "OP_TUCK",
		0x7e: "OP_CAT", 0x7f: "OP_SUBSTR", 0x80: "OP_LEFT",
		0x81: "OP_RIGHT", 0x82: "OP_SIZE", 0x83: "OP_INVERT",
		0x84: "OP_AND", 0x85: "OP_OR", 0x86: "OP_XOR",
		0x87: "OP_EQUAL", 0x88: "OP_EQUALVERIFY", 0x89: "OP_ROTR",
		0x8a: "OP_ROTL", 0x8b: "OP_1ADD", 0x8c: "OP_1SUB",
		0x8d: "OP_2MUL", 0x8e: "OP_2DIV", 0x8f: "OP_NEGATE",
		0x90: "OP_ABS", 0x91: "OP_NOT", 0x92: "OP_0NOTEQUAL",
		0x93: "OP_ADD", 0x94: "OP_SUB", 0x95: "OP_MUL", 0x96: "OP_DIV",
		0x97: "OP_MOD", 0x98: "OP_LSHIFT", 0x99: "OP_RSHIFT",
		0x9a: "OP_BOOLAND", 0x9b: "OP_BOOLOR", 0x9c: "OP_NUMEQUAL",
		0x9d: "OP_NUMEQUALVERIFY", 0x9e: "OP_NUMNOTEQUAL",
		0x9f: "OP_LESSTHAN", 0xa0: "OP_GREATERTHAN",
		0xa1: "OP_LESSTHANOREQUAL", 0xa2: "OP_GREATERTHANOREQUAL",
		0xa3: "OP_MIN", 0xa4: "OP_MAX", 0xa5: "OP_WITHIN",
		0xa6: "OP_RIPEMD160", 0xa7: "OP_SHA1", 0xa8: "OP_BLAKE256",
		0xa9: "OP_HASH160", 0xaa: "OP_HASH256", 0xab: "OP_CODESEPARATOR",
		0xac: "OP_CHECKSIG", 0xad: "OP_CHECKSIGVERIFY",
		0xae: "OP_CHECKMULTISIG", 0xaf: "OP_CHECKMULTISIGVERIFY",
		0xf9: "OP_INVALID249", 0xfa: "OP_SMALLINTEGER",
		0xfb: "OP_PUBKEYS", 0xfd: "OP_PUBKEYHASH", 0xfe: "OP_PUBKEY",
		0xff: "OP_INVALIDOPCODE", 0xba: "OP_SSTX", 0xbb: "OP_SSGEN",
		0xbc: "OP_SSRTX", 0xbd: "OP_SSTXCHANGE", 0xbe: "OP_CHECKSIGALT",
		0xbf: "OP_CHECKSIGALTVERIFY", 0xc0: "OP_SHA256",
		0xc1: "OP_TADD", 0xc2: "OP_TSPEND", 0xc3: "OP_TGEN",
	}
	for opcodeVal, expectedStr := range expectedStrings {
		var data []byte
		switch {
		// OP_DATA_1 through OP_DATA_65 display the pushed data.
		case opcodeVal >= 0x01 && opcodeVal < 0x4c:
			data = bytes.Repeat(oneBytes, opcodeVal)
			expectedStr = strings.Repeat(oneStr, opcodeVal)

		// OP_PUSHDATA1.
		case opcodeVal == 0x4c:
			data = bytes.Repeat(oneBytes, 1)
			expectedStr = strings.Repeat(oneStr, 1)

		// OP_PUSHDATA2.
		case opcodeVal == 0x4d:
			data = bytes.Repeat(oneBytes, 2)
			expectedStr = strings.Repeat(oneStr, 2)

		// OP_PUSHDATA4.
		case opcodeVal == 0x4e:
			data = bytes.Repeat(oneBytes, 3)
			expectedStr = strings.Repeat(oneStr, 3)

		// OP_1 through OP_16 display the numbers themselves.
		case opcodeVal >= 0x51 && opcodeVal <= 0x60:
			val := byte(opcodeVal - (0x51 - 1))
			data = []byte{val}
			expectedStr = strconv.Itoa(int(val))

		// OP_NOP1 through OP_NOP10.
		case opcodeVal >= 0xb0 && opcodeVal <= 0xb9:
			switch opcodeVal {
			case 0xb1:
				// OP_NOP2 is an alias of OP_CHECKLOCKTIMEVERIFY
				expectedStr = "OP_CHECKLOCKTIMEVERIFY"
			case 0xb2:
				// OP_NOP3 is an alias of OP_CHECKSEQUENCEVERIFY
				expectedStr = "OP_CHECKSEQUENCEVERIFY"
			default:
				val := byte(opcodeVal - (0xb0 - 1))
				expectedStr = "OP_NOP" + strconv.Itoa(int(val))
			}

		// OP_UNKNOWN#.
		case opcodeVal >= 0xc4 && opcodeVal <= 0xf8 || opcodeVal == 0xfc:
			expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
		}

		var buf strings.Builder
		disasmOpcode(&buf, &opcodeArray[opcodeVal], data, true)
		gotStr := buf.String()
		if gotStr != expectedStr {
			t.Errorf("pop.print (opcode %x): Unexpected disasm "+
				"string - got %v, want %v", opcodeVal, gotStr,
				expectedStr)
			continue
		}
	}

	// Now, replace the relevant fields and test the full disassembly.
	expectedStrings[0x00] = "OP_0"
	expectedStrings[0x4f] = "OP_1NEGATE"
	for opcodeVal, expectedStr := range expectedStrings {
		var data []byte
		switch {
		// OP_DATA_1 through OP_DATA_65 display the opcode followed by
		// the pushed data.
		case opcodeVal >= 0x01 && opcodeVal < 0x4c:
			data = bytes.Repeat(oneBytes, opcodeVal)
			expectedStr = fmt.Sprintf("OP_DATA_%d 0x%s", opcodeVal,
				strings.Repeat(oneStr, opcodeVal))

		// OP_PUSHDATA1.
		case opcodeVal == 0x4c:
			data = bytes.Repeat(oneBytes, 1)
			expectedStr = fmt.Sprintf("OP_PUSHDATA1 0x%02x 0x%s",
				len(data), strings.Repeat(oneStr, 1))

		// OP_PUSHDATA2.
		case opcodeVal == 0x4d:
			data = bytes.Repeat(oneBytes, 2)
			expectedStr = fmt.Sprintf("OP_PUSHDATA2 0x%04x 0x%s",
				len(data), strings.Repeat(oneStr, 2))

		// OP_PUSHDATA4.
		case opcodeVal == 0x4e:
			data = bytes.Repeat(oneBytes, 3)
			expectedStr = fmt.Sprintf("OP_PUSHDATA4 0x%08x 0x%s",
				len(data), strings.Repeat(oneStr, 3))

		// OP_1 through OP_16.
		case opcodeVal >= 0x51 && opcodeVal <= 0x60:
			val := byte(opcodeVal - (0x51 - 1))
			data = []byte{val}
			expectedStr = "OP_" + strconv.Itoa(int(val))

		// OP_NOP1 through OP_NOP10.
		case opcodeVal >= 0xb0 && opcodeVal <= 0xb9:
			switch opcodeVal {
			case 0xb1:
				// OP_NOP2 is an alias of OP_CHECKLOCKTIMEVERIFY
				expectedStr = "OP_CHECKLOCKTIMEVERIFY"
			case 0xb2:
				// OP_NOP3 is an alias of OP_CHECKSEQUENCEVERIFY
				expectedStr = "OP_CHECKSEQUENCEVERIFY"
			default:
				val := byte(opcodeVal - (0xb0 - 1))
				expectedStr = "OP_NOP" + strconv.Itoa(int(val))
			}

		// OP_UNKNOWN#.
		case opcodeVal >= 0xc4 && opcodeVal <= 0xf8 || opcodeVal == 0xfc:
			expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
		}

		var buf strings.Builder
		disasmOpcode(&buf, &opcodeArray[opcodeVal], data, false)
		gotStr := buf.String()
		if gotStr != expectedStr {
			t.Errorf("pop.print (opcode %x): Unexpected disasm "+
				"string - got %v, want %v", opcodeVal, gotStr,
				expectedStr)
			continue
		}
	}
}

func TestNewlyEnabledOpCodes(t *testing.T) {
	sigScriptMath := []byte{
		0x04,
		0xff, 0xff, 0xff, 0x7f,
		0x04,
		0xee, 0xee, 0xee, 0x6e,
	}
	sigScriptShift := []byte{
		0x04,
		0xff, 0xff, 0xff, 0x7f,
		0x53,
	}
	sigScriptRot := []byte{
		0x04,
		0x21, 0x12, 0x34, 0x56,
		0x53,
	}
	sigScriptInv := []byte{
		0x04,
		0xff, 0x00, 0xf0, 0x0f,
	}
	sigScriptLogic := []byte{
		0x04,
		0x21, 0x12, 0x34, 0x56,
		0x04,
		0x0f, 0xf0, 0x00, 0xff,
	}
	sigScriptCat := []byte{
		0x06,
		0x21, 0x12, 0x34, 0x56, 0x44, 0x55,
		0x06,
		0x0f, 0xf0, 0x00, 0xff, 0x88, 0x99,
	}
	lotsOf01s := bytes.Repeat([]byte{0x01}, 2050)
	builder := NewScriptBuilder()
	builder.AddData(lotsOf01s).AddData(lotsOf01s)
	sigScriptCatOverflow, _ := builder.Script()
	sigScriptSubstr := []byte{
		0x08,
		0x21, 0x12, 0x34, 0x56, 0x59, 0x32, 0x40, 0x21,
		0x56,
		0x52,
	}
	sigScriptLR := []byte{
		0x08,
		0x21, 0x12, 0x34, 0x56, 0x59, 0x32, 0x40, 0x21,
		0x54,
	}

	tests := []struct {
		name      string
		pkScript  []byte
		sigScript []byte
		expected  bool
	}{
		{
			name: "add",
			pkScript: []byte{
				0x93, // OP_ADD
				0x05, // Expected result push
				0xed, 0xee, 0xee, 0xee, 0x00,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptMath,
			expected:  true,
		},
		{
			name: "sub",
			pkScript: []byte{
				0x94, // OP_SUB
				0x04, // Expected result push
				0x11, 0x11, 0x11, 0x11,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptMath,
			expected:  true,
		},
		{
			name: "mul",
			pkScript: []byte{
				0x95, // OP_MUL
				0x04, // Expected result push
				0xee, 0xee, 0xee, 0xee,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptMath,
			expected:  true,
		},
		{
			name: "div",
			pkScript: []byte{
				0x96, // OP_DIV
				0x51, // Expected result push
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptMath,
			expected:  true,
		},
		{
			name: "mod",
			pkScript: []byte{
				0x97, // OP_MOD
				0x04, // Expected result push
				0x11, 0x11, 0x11, 0x11,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptMath,
			expected:  true,
		},
		{
			name: "lshift",
			pkScript: []byte{
				0x98, // OP_LSHIFT
				0x01, // Expected result push
				0x88,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptShift,
			expected:  true,
		},
		{
			name: "rshift",
			pkScript: []byte{
				0x99, // OP_RSHIFT
				0x04, // Expected result push
				0xff, 0xff, 0xff, 0x0f,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptShift,
			expected:  true,
		},
		{
			name: "rotr",
			pkScript: []byte{
				0x89, // OP_ROTR
				0x04, // Expected result push
				0x44, 0x82, 0xc6, 0x2a,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptRot,
			expected:  true,
		},
		{
			name: "rotl",
			pkScript: []byte{
				0x8a, // OP_ROTL
				0x04, // Expected result push
				0xf6, 0x6e, 0x5f, 0xce,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptRot,
			expected:  true,
		},
		{
			name: "inv",
			pkScript: []byte{
				0x83, // OP_INV
				0x04, // Expected result push
				0x00, 0x01, 0xf0, 0x8f,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptInv,
			expected:  true,
		},
		{
			name: "and",
			pkScript: []byte{
				0x84, // OP_AND
				0x03, // Expected result push
				0x21, 0x02, 0x34,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptLogic,
			expected:  true,
		},
		{
			name: "or",
			pkScript: []byte{
				0x85, // OP_OR
				0x04, // Expected result push
				0x0f, 0xe0, 0x00, 0xa9,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptLogic,
			expected:  true,
		},
		{
			name: "xor",
			pkScript: []byte{
				0x86, // OP_XOR
				0x04, // Expected result push
				0x30, 0xe2, 0x34, 0xa9,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptLogic,
			expected:  true,
		},
		{
			name: "cat",
			pkScript: []byte{
				0x7e, // OP_CAT
				0x0c, // Expected result push
				0x21, 0x12, 0x34, 0x56, 0x44, 0x55,
				0x0f, 0xf0, 0x00, 0xff, 0x88, 0x99,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptCat,
			expected:  true,
		},
		{
			name: "catoverflow",
			pkScript: []byte{
				0x7e, // OP_CAT
				0x0c, // Expected result push
				0x21, 0x12, 0x34, 0x56, 0x44, 0x55,
				0x0f, 0xf0, 0x00, 0xff, 0x88, 0x99,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptCatOverflow,
			expected:  false,
		},
		{
			name: "substr",
			pkScript: []byte{
				0x7f, // OP_SUBSTR
				0x04, // Expected result push
				0x34, 0x56, 0x59, 0x32,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptSubstr,
			expected:  true,
		},
		{
			name: "left",
			pkScript: []byte{
				0x80, // OP_LEFT
				0x04, // Expected result push
				0x21, 0x12, 0x34, 0x56,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptLR,
			expected:  true,
		},
		{
			name: "right",
			pkScript: []byte{
				0x81, // OP_RIGHT
				0x04, // Expected result push
				0x59, 0x32, 0x40, 0x21,
				0x87, // OP_EQUAL
			},
			sigScript: sigScriptLR,
			expected:  true,
		},
	}

	for _, test := range tests {
		msgTx := new(wire.MsgTx)
		msgTx.AddTxIn(&wire.TxIn{
			PreviousOutPoint: wire.OutPoint{},
			SignatureScript:  test.sigScript,
			Sequence:         0xFFFFFFFF,
		})
		msgTx.AddTxOut(&wire.TxOut{
			Value:    0x00FFFFFF00000000,
			PkScript: []byte{0x01},
		})
		engine, err := NewEngine(test.pkScript, msgTx, 0,
			testScriptFlags, 0, nil)
		if err != nil {
			t.Errorf("Bad script result for test %v because of error: %v",
				test.name, err.Error())
			continue
		}
		err = engine.Execute()
		if err != nil && test.expected {
			t.Errorf("Bad script exec for test %v because of error: %v",
				test.name, err.Error())
		}
	}
}

func randByteSliceSlice(i int, maxLen int, src int) [][]byte {
	r := rand.New(rand.NewSource(int64(src)))

	slices := make([][]byte, i)
	for j := 0; j < i; j++ {
		for {
			sz := r.Intn(maxLen) + 1

			sl := make([]byte, sz)
			for k := 0; k < sz; k++ {
				randByte := r.Intn(255)
				sl[k] = uint8(randByte)
			}

			// No duplicates allowed.
			if j > 0 &&
				(bytes.Equal(sl, slices[j-1])) {
				r.Seed(int64(j) + r.Int63n(12345))
				continue
			}

			slices[j] = sl
			break
		}
	}

	return slices
}

// TestForVMFailure feeds random scripts to the VMs to check and see if it
// crashes. Try increasing the number of iterations or the length of the
// byte string to sample a greater space.
func TestForVMFailure(t *testing.T) {
	numTests := 2
	bsLength := 11

	for i := 0; i < numTests; i++ {
		tests := randByteSliceSlice(65536, bsLength, i)

		for j := range tests {
			if j == 0 {
				continue
			}

			msgTx := new(wire.MsgTx)
			msgTx.AddTxIn(&wire.TxIn{
				PreviousOutPoint: wire.OutPoint{},
				SignatureScript:  tests[j-1],
				Sequence:         0xFFFFFFFF,
			})
			msgTx.AddTxOut(&wire.TxOut{
				Value:    0x00FFFFFF00000000,
				PkScript: []byte{0x01},
			})
			engine, err := NewEngine(tests[j], msgTx, 0,
				testScriptFlags, 0, nil)

			if err == nil {
				engine.Execute()
			}
		}
	}
}
