<repo_name>smartcyberhacker/opscript<file_sep>/gui/code.go
package gui

import (
	"fmt"
	"regexp"
	"strings"
)

type codeLines []codeLine

type codeLine struct {
	isSeparator bool
	lineIdx     int
	text        string
}

func (cls codeLines) first() codeLine {
	if len(cls) == 0 {
		return codeLine{}
	}

	for _, l := range cls {
		if !l.isSeparator {
			return l
		}
	}

	return cls[0]
}

func (cls codeLines) last() codeLine {
	l := len(cls)

	if l == 0 {
		return codeLine{}
	}

	for i := range cls {
		l := cls[l-i-1]
		if !l.isSeparator {
			return l
		}
	}

	return cls[l-1]
}

func (cls codeLines) next(curLine int) codeLine {
	if len(cls) == 0 {
		return codeLine{}
	}

	for _, l := range cls {
		if !l.isSeparator && l.lineIdx > curLine {
			return l
		}
	}

	return cls[len(cls)-1]
}

func (cls codeLines) previous(curLine int) codeLine {
	l := len(cls)
	if l == 0 {
		return codeLine{}
	}

	for i := range cls {
		l := cls[l-i-1]

		if !l.isSeparator && l.lineIdx < curLine {
			return l
		}
	}

	return cls[0]
}

func formatDisasm(line string, indentation *int, indentationStep int) string {
	opData := regexp.MustCompile(`OP_DATA_\d+ `)
	line = opData.ReplaceAllString(line, "")

	parts := strings.SplitN(line, ":", 3)
	if len(parts) != 3 {
		return line
	}

	code := strings.TrimSpace(parts[2])

	// Decrease indentation for OP_ELSE and OP_ENDIF statements
	if strings.HasPrefix(code, "OP_ELSE") ||
		strings.HasPrefix(code, "OP_ENDIF") {
		*indentation -= indentationStep
	}

	line = fmt.Sprintf("  %s%s", strings.Repeat(" ", *indentation), code)

	// Increase indentation for all line inside OP_IF and OP_ELSE
	if strings.HasPrefix(code, "OP_IF") ||
		strings.HasPrefix(code, "OP_NOTIF") ||
		strings.HasPrefix(code, "OP_ELSE") {
		*indentation += indentationStep
	}

	return line
}

func isPubkeyScript(line string) bool {
	return strings.HasPrefix(line, "01:")
}

func isSignatureScript(line string) bool {
	return strings.HasPrefix(line, "00:")
}

func isWitnessScript(line string) bool {
	return strings.HasPrefix(line, "02:")
}

func isFirstScriptLine(line string) bool {
	return strings.Contains(line, ":0000: ")
}
<file_sep>/gui/gui.go
package gui

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/Jeiwan/opscript/debugger"
	"github.com/Jeiwan/opscript/spec"
	"github.com/jroimartin/gocui"
)

const (
	keyTilde   = '~'
	keyQ       = 'q'
	viewDebug  = "debug"
	viewScript = "script"
	viewSpec   = "spec"
	viewStack  = "stack"
)

// GUI ...
type GUI struct {
	codeLines codeLines
	cui       *gocui.Gui
	debugger  *debugger.Debugger
	spec      spec.Script
}

// New returns a new GUI.
func New(debugger *debugger.Debugger, spec spec.Script) (*GUI, error) {
	c, err := gocui.NewGui(gocui.OutputNormal)
	if err != nil {
		return nil, err
	}

	g := &GUI{
		codeLines: []codeLine{},
		cui:       c,
		debugger:  debugger,
		spec:      spec,
	}

	g.populateCodeLines()
	c.SetManagerFunc(g.layout)
	g.setKeybindings(c)

	return g, nil
}

// Stop ...
func (g GUI) Stop() {
	g.cui.Close()
}

// Start ...
func (g GUI) Start() error {
	if err := g.cui.MainLoop(); err != nil && err != gocui.ErrQuit {
		return err
	}

	return nil
}

func (g *GUI) layout(c *gocui.Gui) error {
	maxX, maxY := c.Size()

	if v, err := c.SetView(viewScript, 0, 0, int(0.5*float64(maxX))-1, maxY-1); err != nil {
		if err != gocui.ErrUnknownView {
			return fmt.Errorf("setView 'script': %+v", err)
		}

		v.Title = "Script"
		v.Highlight = true
		v.SelBgColor = gocui.ColorGreen
		v.SelFgColor = gocui.ColorBlack

		g.renderCodeLines(v)

		cx, _ := v.Cursor()
		v.SetCursor(cx, g.codeLines.first().lineIdx)
		c.SetCurrentView(viewScript)

	}

	if v, err := c.SetView(viewStack, int(0.5*float64(maxX)), 0, maxX-1, int(0.7*float64(maxY))-1); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Stack"

		if err := g.updateStack(); err != nil {
			return err
		}
	}

	if v, err := c.SetView(viewSpec, int(0.5*float64(maxX)), int(0.7*float64(maxY)), maxX-1, maxY-1); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Spec"
		v.Wrap = true

		if err := g.updateSpec(); err != nil {
			return err
		}
	}

	if v, err := c.SetView(viewDebug, int(0.7*float64(maxX)), int(0.4*float64(maxY)), maxX-1, int(0.6*float64(maxY))); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Debug"
		if _, err := g.cui.SetViewOnBottom(viewDebug); err != nil {
			return err
		}
	}

	return nil
}

func (g *GUI) populateCodeLines() {
	g.codeLines = nil
	curLine := 0

	var hasSigScript bool
	var hasPkScript bool

	indentation := 0
	indentationStep := 4

	for _, s := range g.debugger.Steps {
		if isFirstScriptLine(s.Disasm) {
			var line codeLine
			line.isSeparator = true
			line.lineIdx = curLine

			if isSignatureScript(s.Disasm) {
				hasSigScript = true
				line.text = "  Signature Script\n"
				curLine++

			} else if isPubkeyScript(s.Disasm) {
				hasPkScript = true
				line.text = "\n  Pubkey Script\n"
				curLine++
				if hasSigScript {
					curLine++
				}

			} else if isWitnessScript(s.Disasm) {
				line.text = "\n  Witness Script\n"
				curLine++
				if hasPkScript || hasSigScript {
					curLine++
				}
			}

			g.codeLines = append(g.codeLines, line)
		}

		var line codeLine
		line.lineIdx = curLine
		line.text = fmt.Sprintln(formatDisasm(s.Disasm, &indentation, indentationStep))

		g.codeLines = append(g.codeLines, line)
		curLine++
	}
}

func (g *GUI) renderCodeLines(v *gocui.View) {
	for _, cl := range g.codeLines {
		fmt.Fprint(v, cl.text)
	}
}

func (g GUI) setKeybindings(c *gocui.Gui) error {
	if err := c.SetKeybinding(viewScript, gocui.KeyArrowUp, gocui.ModNone, g.cursorUp); err != nil {
		return err
	}

	if err := c.SetKeybinding(viewScript, gocui.KeyArrowDown, gocui.ModNone, g.cursorDown); err != nil {
		return err
	}

	if err := c.SetKeybinding("", keyQ, gocui.ModNone, quit); err != nil {
		return err
	}

	if err := c.SetKeybinding("", keyTilde, gocui.ModNone, g.showDebugView); err != nil {
		return err
	}

	return nil
}

func (g GUI) updateDebug(line string) error {
	v, err := g.cui.View(viewDebug)
	if err != nil {
		return err
	}

	v.Autoscroll = true

	fmt.Fprint(v, line)
	return nil
}

func (g GUI) updateSpec() error {
	v, err := g.cui.View(viewSpec)
	if err != nil {
		return err
	}

	v.Clear()

	step := g.debugger.CurrentStep()
	opcodeRegex := regexp.MustCompile(`OP_[\w_]+`)
	opcode := opcodeRegex.FindString(step.Disasm)
	if opcode == "" || strings.HasPrefix(opcode, "OP_DATA") {
		return nil
	}

	spec := g.spec[opcode]
	if spec.Word == "" {
		fmt.Fprintf(v, " Missing spec for %s.", opcode)
		return nil
	}

	fmt.Fprintf(v, " %s (%s)\n\n", spec.Word, spec.Opcode)
	fmt.Fprintf(v, " Input:  %s\n", spec.Input)
	fmt.Fprintf(v, " Output: %s\n", spec.Output)
	fmt.Fprintf(v, " \n %s", spec.Short)

	return nil
}

func (g GUI) updateStack() error {
	v, err := g.cui.View(viewStack)
	if err != nil {
		return err
	}

	v.Clear()

	step := g.debugger.CurrentStep()
	for i := range step.Stack {
		fmt.Fprintf(v, " %x\n", step.Stack[len(step.Stack)-i-1])
	}

	return nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrQuit
}
<file_sep>/gui/code_test.go
package gui

import "testing"

func TestFormatDisasm(t *testing.T) {
	tests := []struct {
		name        string
		input       string
		indentation int
		expected    string
	}{
		{name: "data",
			input:       "00:0000: OP_DATA_3 deadbeef",
			indentation: 0,
			expected:    "  deadbeef"},

		{name: "op",
			input:       "00:0000: OP_1",
			indentation: 0,
			expected:    "  OP_1"},

		{name: "with indentation",
			input:       "00:0000: OP_1",
			indentation: 4,
			expected:    "      OP_1"},
	}

	for _, test := range tests {
		actual := formatDisasm(test.input, &test.indentation, 2)
		if actual != test.expected {
			t.Errorf("expected: %s, actual: %s", test.expected, actual)
		}
	}
}
<file_sep>/blockchain/node/node.go
package node

import (
	"fmt"

	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/rpcclient"
	"github.com/btcsuite/btcd/wire"
	"github.com/sirupsen/logrus"
)

// Node is a Bitcoin node.
type Node struct {
	host    string
	rpcUser string
	rpcPass string
}

// New returns a new Node.
func New(host, rpcUser, rpcPass string) *Node {
	return &Node{
		host:    host,
		rpcUser: rpcUser,
		rpcPass: rpcPass,
	}
}

// GetTransaction returns a transaction by its hash.
func (n Node) GetTransaction(txHash string) (*wire.MsgTx, error) {
	btcclient, err := rpcclient.New(&rpcclient.ConnConfig{
		HTTPPostMode: true,
		DisableTLS:   true,
		Host:         n.host,
		User:         n.rpcUser,
		Pass:         n.rpc<PASSWORD>,
	}, nil)
	if err != nil {
		logrus.Fatal(fmt.Errorf("new Bitcoin client: %+v", err))
	}
	defer btcclient.Shutdown()

	hash, err := chainhash.NewHashFromStr(txHash)
	if err != nil {
		return nil, fmt.Errorf("parse txid: %+v", err)
	}

	txResp, err := btcclient.GetRawTransaction(hash)
	if err != nil {
		return nil, fmt.Errorf("get raw transaction: %+v", err)
	}

	return txResp.MsgTx(), nil
}
<file_sep>/README.md
## OP_SCRIPT

A viewer and debugger of Bitcoin scripts. **Early development.**

![Screenshot](./screenshot.png)


## Features
1. Can fetch transactions from:
    1. A Bitcoin node (requires a full node with `txindex=1`). Default.
    1. [Blockstream.info](https://blockstream.info) JSON API. Use `--blockstream` flag.
1. Automatically finds related output.
1. Allows to navigate forward and backward.
1. Shows stack per line of code.
1. Shows opcodes information (hex code, input, output, and description).
1. Supports witness data (SegWit).
1. Uses [`btcd/txscript`](https://github.com/btcsuite/btcd/tree/master/txscript) under the hood.


## Usage

1. `go get github.com/Jeiwan/opscript`
1. `opscript help`
    ```shell
    Usage:
    opscript [flags] transactionHash:inputIndex
    opscript [command]

    Available Commands:
    buildspec
    help        Help about any command

    Flags:
        --blockstream        Use blockstream.info API to get transactions.
    -h, --help               help for opscript
        --node               Use Bitcoin node to get transactions (requires 'txindex=1'). (default true)
        --node-addr string   Bitcoin node address. (default "127.0.0.1:8332")
        --rpc-pass string    Bitcoin JSON-RPC password.
        --rpc-user string    Bitcoin JSON-RPC username.

    Use "opscript [command] --help" for more information about a command.
    ```

## Key bindings

* `q` – quit
* `↑`/`↓` – navigate between lines of code


## Examples
* Using Blockstream.info API:
    ```shell
    opscript --blockstream 70fde4687efab8dae09737f87e30042030288fec42fd9e12f34c435cdeb7812c
    ```
* Specifying input index:
    ```shell
    opscript --blockstream 70fde4687efab8dae09737f<PASSWORD>:0
    ```
* Using a Bitcoin node:
    ```shell
    opscript --rpc-user=woot --rpc-pass=woot <PASSWORD>
    ```
<file_sep>/cmd/buildspec.go
package cmd

import (
	"encoding/json"
	"io/ioutil"

	"github.com/Jeiwan/opscript/spec"
	"github.com/spf13/cobra"
)

func newBuildSpecCmd() *cobra.Command {
	const outFile = "spec.json"

	cmd := &cobra.Command{
		Use: "buildspec",
		RunE: func(cmd *cobra.Command, args []string) error {
			spec, err := spec.NewFromBitcoinWiki()
			if err != nil {
				return err
			}

			specJSON, err := json.MarshalIndent(spec, "", "    ")
			if err != nil {
				return err
			}

			return ioutil.WriteFile(outFile, specJSON, 0644)
		},
	}
	return cmd
}
<file_sep>/go.mod
module github.com/Jeiwan/opscript

go 1.12

require (
	github.com/PuerkitoBio/goquery v1.5.0
	github.com/btcsuite/btcd v0.20.1-beta
	github.com/jroimartin/gocui v0.4.0
	github.com/mattn/go-runewidth v0.0.6 // indirect
	github.com/nsf/termbox-go v0.0.0-20190817171036-93860e161317 // indirect
	github.com/sirupsen/logrus v1.4.2
	github.com/spf13/cobra v0.0.5
	github.com/stretchr/testify v1.3.0 // indirect
	golang.org/x/net v0.0.0-20190603091049-60506f45cf65
)
<file_sep>/debugger/step.go
package debugger

// Step ...
type Step struct {
	Disasm string
	Stack  [][]byte
}
<file_sep>/gui/keys.go
package gui

import (
	"github.com/jroimartin/gocui"
)

func (g *GUI) cursorDown(c *gocui.Gui, v *gocui.View) error {
	if v != nil {
		if len(g.codeLines) == 0 {
			return nil
		}

		ox, oy := v.Origin()
		_, maxY := v.Size()
		cx, cy := v.Cursor()
		nextLine := g.codeLines.next(cy + oy)

		maxY-- // one-line padding at the bottom

		if nextLine.lineIdx == cy+oy {
			return nil
		}

		if nextLine.lineIdx > maxY && cy == maxY {
			oy++
			if err := v.SetOrigin(ox, oy); err != nil {
				return err
			}
		}

		if err := v.SetCursor(cx, nextLine.lineIdx-oy); err != nil {
			if err := v.SetOrigin(ox, maxY); err != nil {
				return err
			}
		}

		g.debugger.Next()

		if err := g.updateStack(); err != nil {
			return err
		}

		if err := g.updateSpec(); err != nil {
			return err
		}
	}
	return nil
}

func (g *GUI) cursorUp(c *gocui.Gui, v *gocui.View) error {
	if v != nil {
		if len(g.codeLines) == 0 {
			return nil
		}

		ox, oy := v.Origin()
		cx, cy := v.Cursor()

		prevLine := g.codeLines.previous(oy + cy)
		firstLine := g.codeLines.first()

		if prevLine.lineIdx == 0 {
			if err := v.SetOrigin(cx, 0); err != nil {
				return err
			}
			if err := v.SetCursor(cx, firstLine.lineIdx); err != nil {
				return err
			}
			return nil
		}

		if prevLine.lineIdx < oy {
			if err := v.SetOrigin(cx, oy-(oy-prevLine.lineIdx)); err != nil {
				return err
			}
			prevLine.lineIdx = oy
		}

		if err := v.SetCursor(cx, prevLine.lineIdx-oy); err != nil {
			if err := v.SetOrigin(ox, firstLine.lineIdx); err != nil {
				return err
			}
		}

		g.debugger.Previous()

		if err := g.updateStack(); err != nil {
			return err
		}

		if err := g.updateSpec(); err != nil {
			return err
		}
	}
	return nil
}

func (g *GUI) showDebugView(c *gocui.Gui, v *gocui.View) error {
	if _, err := g.cui.SetViewOnTop(viewDebug); err != nil {
		return err
	}

	return nil
}
<file_sep>/cmd/opscript.go
package cmd

import (
	"encoding/hex"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/Jeiwan/opscript/blockchain/blockstream"
	"github.com/Jeiwan/opscript/blockchain/node"
	"github.com/Jeiwan/opscript/debugger"
	"github.com/Jeiwan/opscript/gui"
	"github.com/Jeiwan/opscript/spec"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

// Blockchain ...
type Blockchain interface {
	GetTransaction(txHash string) (*wire.MsgTx, error)
}

// New ...
func New(spec spec.Script) *cobra.Command {
	rootCmd := newRootCmd(spec)
	buildSpecCmd := newBuildSpecCmd()

	rootCmd.AddCommand(buildSpecCmd)

	return rootCmd
}

func newRootCmd(spec spec.Script) *cobra.Command {
	var useNode, useBlockstream bool
	var nodeAddr, rpcUser, rpcPass string

	cmd := &cobra.Command{
		Use:  "opscript [flags] transactionHash:inputIndex",
		Args: cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var bchain Blockchain

			switch {
			case useBlockstream:
				bchain = blockstream.New()
			case useNode:
				bchain = node.New(nodeAddr, rpcUser, rpcPass)
			}

			txHash, txInput, err := parseArgs(args)
			if err != nil {
				return err
			}

			tx, err := bchain.GetTransaction(txHash)
			if err != nil {
				return err
			}

			prevOut := tx.TxIn[txInput].PreviousOutPoint

			prevTx, err := bchain.GetTransaction(prevOut.Hash.String())
			if err != nil {
				logrus.Fatal(fmt.Errorf("get prev transaction: %+v", err))
			}

			en, err := newEngine(tx, prevTx.TxOut[prevOut.Index].PkScript, txInput)
			if err != nil {
				logrus.Fatal(fmt.Errorf("new engine: %+v", err))
			}

			d, err := debugger.New(en)
			if err != nil {
				logrus.Fatalln(err)
			}

			gui, err := gui.New(d, spec)
			if err != nil {
				logrus.Fatalln(err)
			}
			defer gui.Stop()

			if err := gui.Start(); err != nil {
				logrus.Fatalln(err)
			}
			return nil
		},
	}

	cmd.Flags().BoolVar(&useNode, "node", true, "Use Bitcoin node to get transactions (requires 'txindex=1').")
	cmd.Flags().BoolVar(&useBlockstream, "blockstream", false, "Use blockstream.info API to get transactions.")
	cmd.Flags().StringVar(&nodeAddr, "node-addr", "127.0.0.1:8332", "Bitcoin node address.")
	cmd.Flags().StringVar(&rpcUser, "rpc-user", "", "Bitcoin JSON-RPC username.")
	cmd.Flags().StringVar(&rpcPass, "rpc-pass", "", "Bitcoin JSON-RPC password.")

	return cmd
}

func newEngine(tx *wire.MsgTx, output []byte, inputIdx int) (*txscript.Engine, error) {
	e, err := txscript.NewEngine(
		output,
		tx,
		inputIdx,
		txscript.ScriptVerifyWitness+txscript.ScriptBip16+txscript.ScriptVerifyCleanStack+txscript.ScriptVerifyMinimalData,
		nil,
		nil,
		-1,
	)
	if err != nil {
		return nil, err
	}

	return e, nil
}

func parseArgs(args []string) (txHash string, input int, err error) {
	if len(args) != 1 {
		return "", -1, errors.New("wrong number of arguments")
	}

	parts := strings.Split(args[0], ":")
	if len(parts) == 2 {
		input, err = strconv.Atoi(parts[1])
		if err != nil {
			return "", -1, err
		}
	}

	if hashBytes, err := hex.DecodeString(parts[0]); err != nil || len(hashBytes) != 32 {
		return "", -1, errors.New("wrong transaction hash")
	}

	txHash = parts[0]

	return
}
<file_sep>/Makefile
default:
	cat Makefile

spec:
	go run *.go buildspec
	go-bindata -pkg internal -o internal/bindata.go spec.json

test:
	go test -v ./...<file_sep>/blockchain/blockstream/transaction.go
package blockstream

type transaction struct {
	Txid     string `json:"txid"`
	Version  int32  `json:"version"`
	Locktime int    `json:"locktime"`
	Size     int    `json:"size"`
	Weight   int    `json:"weight"`
	Fee      int    `json:"fee"`
	Vin      []vin  `json:"vin"`
	Vout     []vout `json:"vout"`
	Status   status `json:"status"`
}

type vin struct {
	Txid                  string   `json:"txid"`
	Vout                  uint32   `json:"vout"`
	IsCoinbase            bool     `json:"is_coinbase"`
	Scriptsig             string   `json:"scriptsig"`
	ScriptsigAsm          string   `json:"scriptsig_asm"`
	InnerRedeemscriptAsm  string   `json:"inner_redeemscript_asm"`
	InnerWitnessscriptAsm string   `json:"inner_witnessscript_asm"`
	Sequence              int      `json:"sequence"`
	Witness               []string `json:"witness"`
	Prevout               vout     `json:"prevout"`
}

type vout struct {
	Scriptpubkey        string `json:"scriptpubkey"`
	ScriptpubkeyAsm     string `json:"scriptpubkey_asm"`
	ScriptpubkeyType    string `json:"scriptpubkey_type"`
	ScriptpubkeyAddress string `json:"scriptpubkey_address"`
	Value               int64  `json:"value"`
}

type status struct {
	Confirmed   bool   `json:"confirmed"`
	BlockHeight int    `json:"block_height"`
	BlockHash   string `json:"block_hash"`
	BlockTime   int    `json:"block_time"`
}
<file_sep>/spec/spec.go
package spec

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/sirupsen/logrus"
)

// Opcode ...
type Opcode struct {
	Word    string `json:"word"`
	WordAlt string `json:"word_alt"`
	Opcode  string `json:"opcode"`
	Input   string `json:"input"`
	Output  string `json:"output"`
	Short   string `json:"short"`
}

// Script ...
type Script map[string]Opcode

// NewFromBitcoinWiki parses https://en.bitcoin.it/wiki/Script and extract Script specification.
func NewFromBitcoinWiki() (*Script, error) {
	const specURL = "https://en.bitcoin.it/wiki/Script"
	const totalTables = 10

	spec := make(Script)

	resp, err := http.Get(specURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}

		return nil, fmt.Errorf("spec page is not available: %d, %s", resp.StatusCode, b)
	}

	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		return nil, err
	}

	var scrapedTables uint8
	doc.Find("table.wikitable").Each(func(i int, s *goquery.Selection) {
		if scrapedTables >= totalTables {
			return
		}

		s.Find("tr").Each(func(j int, s *goquery.Selection) {
			cells := s.Find("td").Map(func(k int, s *goquery.Selection) string {
				return s.Text()
			})

			if len(cells) == 0 {
				return
			}

			ops := cellsToOps(cells)

			for _, op := range ops {
				spec[op.Word] = op
			}
		})

		scrapedTables++
	})

	return &spec, nil
}

func cellsToOps(cells []string) []Opcode {
	if len(cells) == 0 {
		return nil
	}

	for i := range cells {
		cells[i] = strings.TrimSpace(cells[i])
	}

	var input, output string
	word := cells[0]
	opcodeDec := cells[1]
	opcode := cells[2]
	if len(cells) == 6 {
		input = cells[3]
		output = cells[4]
	}
	short := cells[len(cells)-1]

	if !strings.HasPrefix(word, "OP_") {
		logrus.Infof("skipping %s\n", word)
		return nil
	}

	var ops []Opcode
	alts := strings.Split(word, ", ")
	rnge := strings.Split(word, "-")

	if len(alts) == 2 && len(rnge) == 2 {
		rnge = strings.Split(alts[1], "-")
		alts = []string{alts[0]}
		opcodeDec = strings.Split(opcodeDec, ", ")[1]
	}

	cleanWord := regexp.MustCompile(`OP_[\w_-]+`)

	if len(alts) == 1 {

		ops = append(ops, Opcode{
			Word:   cleanWord.FindString(alts[0]),
			Opcode: opcode,
			Input:  input,
			Output: output,
			Short:  short,
		})
	}

	if len(alts) == 2 {
		ops = append(ops, []Opcode{
			{
				Word:    cleanWord.FindString(alts[0]),
				WordAlt: cleanWord.FindString(alts[1]),
				Opcode:  opcode,
				Input:   input,
				Output:  output,
				Short:   short,
			},
			{
				Word:    cleanWord.FindString(alts[1]),
				WordAlt: cleanWord.FindString(alts[0]),
				Opcode:  opcode,
				Input:   input,
				Output:  output,
				Short:   short,
			},
		}...)
	}

	if len(rnge) == 2 {
		leftOp := rnge[0]
		rightOp := rnge[1]

		opNumber := regexp.MustCompile(`(OP_[A-Z]*)(\d+)`)

		leftMatches := opNumber.FindStringSubmatch(leftOp)
		rightMatches := opNumber.FindStringSubmatch(rightOp)
		if len(leftMatches) != 3 || len(rightMatches) != 3 {
			logrus.Errorf("skipping %s: invalid opcodes range %q, %q\n", word, leftMatches, rightMatches)
			return ops
		}

		leftN, err := strconv.Atoi(leftMatches[2])
		if err != nil {
			logrus.Errorf("skipping %s: %+v\n", word, err)
			return ops
		}

		rightN, err := strconv.Atoi(rightMatches[2])
		if err != nil {
			logrus.Errorf("skipping %s: %+v\n", word, err)
			return ops
		}

		opcodes := strings.Split(opcodeDec, "-")
		if len(opcodes) != 2 {
			logrus.Errorf("skipping %s: invalid opcode %+v\n", word, opcodeDec)
			return ops
		}

		opcodeLeft, err := strconv.Atoi(opcodes[0])
		if err != nil {
			logrus.Errorf("skipping %s: %+v\n", word, err)
			return ops
		}

		var outputLeft int

		isEmptyOutput := len(output) == 0

		if !isEmptyOutput {
			outputs := strings.Split(output, "-")
			if len(outputs) != 2 {
				logrus.Errorf("skipping %s: invalid output %+v\n", word, output)
				return ops
			}

			outputLeft, err = strconv.Atoi(outputs[0])
			if err != nil {
				logrus.Errorf("skipping %s: %+v\n", word, err)
				return ops
			}
		}

		for i := leftN; i <= rightN; i++ {
			op := Opcode{
				Word:   fmt.Sprintf("%s%d", leftMatches[1], i),
				Opcode: fmt.Sprintf("0x%x", opcodeLeft+i-leftN),
				Input:  input,
				Short:  short,
			}

			if !isEmptyOutput {
				op.Output = strconv.Itoa(outputLeft + i - leftN)
			}

			ops = append(ops, op)
		}
	}

	return ops
}
<file_sep>/blockchain/blockstream/blockstream.go
package blockstream

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/wire"
)

const baseURL = "https://blockstream.info/api/"

// Blockstream is a client for https://blockstream.info
type Blockstream struct {
}

// New returns a new Blockstream client.
func New() *Blockstream {
	return &Blockstream{}
}

// GetTransaction returns a transaction by its hash.
func (b Blockstream) GetTransaction(txHash string) (*wire.MsgTx, error) {
	url := fmt.Sprintf("%s/tx/%s", baseURL, txHash)
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		return nil, fmt.Errorf("failed to get a transaction: %s", b)
	}

	var tx transaction
	if err := json.NewDecoder(resp.Body).Decode(&tx); err != nil {
		return nil, err
	}

	msgTx := wire.NewMsgTx(tx.Version)
	msgTx.LockTime = uint32(tx.Locktime)

	for _, vin := range tx.Vin {
		voutHash, err := chainhash.NewHashFromStr(vin.Txid)
		if err != nil {
			return nil, err
		}

		sigScript, err := hex.DecodeString(vin.Scriptsig)
		if err != nil {
			return nil, err
		}

		var witness [][]byte
		for _, w := range vin.Witness {
			ws, err := hex.DecodeString(w)
			if err != nil {
				return nil, err
			}

			witness = append(witness, ws)
		}

		newInput := wire.NewTxIn(
			wire.NewOutPoint(voutHash, vin.Vout),
			sigScript,
			witness,
		)
		newInput.Sequence = uint32(vin.Sequence)

		msgTx.AddTxIn(newInput)
	}

	for _, vout := range tx.Vout {
		pkScript, err := hex.DecodeString(vout.Scriptpubkey)
		if err != nil {
			return nil, err
		}

		msgTx.AddTxOut(
			wire.NewTxOut(
				vout.Value,
				pkScript,
			),
		)
	}

	if msgTx.TxHash().String() != tx.Txid {
		return nil, fmt.Errorf("transaction hash doesn't match")
	}

	return msgTx, nil
}
<file_sep>/debugger/debugger.go
package debugger

import (
	"github.com/btcsuite/btcd/txscript"
	"github.com/sirupsen/logrus"
)

// Debugger ...
type Debugger struct {
	CurrentPos uint16
	Engine     *txscript.Engine
	Steps      []Step
}

// New ...
func New(en *txscript.Engine) (*Debugger, error) {
	var steps []Step

Loop:
	for {
		var step Step

		d, err := en.DisasmPC()
		if err != nil {
			return nil, err
		}
		logrus.Debugf("EXECUTING: %s", d)

		done, err := en.Step()
		if err != nil {
			return nil, err
		}

		step.Disasm = d
		step.Stack = en.GetStack()

		steps = append(steps, step)

		if done {
			logrus.Debugf("FINISHED")
			break Loop
		}
	}

	return &Debugger{
		CurrentPos: 0,
		Engine:     en,
		Steps:      steps,
	}, nil
}

// Next ...
func (d *Debugger) Next() bool {
	if int(d.CurrentPos+1) >= len(d.Steps) {
		return false
	}

	d.CurrentPos++

	return true
}

// Previous ...
func (d *Debugger) Previous() bool {
	if int(d.CurrentPos-1) < 0 {
		return false
	}

	d.CurrentPos--

	return true
}

// CurrentStep ...
func (d *Debugger) CurrentStep() *Step {
	return &d.Steps[d.CurrentPos]
}
<file_sep>/main.go
package main

import (
	"encoding/json"
	"os"

	"github.com/Jeiwan/opscript/cmd"
	"github.com/Jeiwan/opscript/internal"
	"github.com/Jeiwan/opscript/spec"
	"github.com/sirupsen/logrus"
)

func main() {
	if os.Getenv("DEBUG") != "" {
		logrus.SetLevel(logrus.DebugLevel)
	}

	specData, err := internal.Asset("spec.json")
	if err != nil {
		logrus.Fatalln(err)
	}

	scriptSpec := make(spec.Script)
	if err := json.Unmarshal(specData, &scriptSpec); err != nil {
		logrus.Fatalln(err)
	}

	if err := cmd.New(scriptSpec).Execute(); err != nil {
		os.Exit(1)
	}
}
