Merge pull request #1769 from Roasbeef/txscript_zero_alloc_optimization_refactor

txscript: backport tokenizer from dcrd
This commit is contained in:
Olaoluwa Osuntokun 2021-11-16 19:14:28 -08:00 committed by GitHub
commit 7070d53e09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 2780 additions and 5027 deletions

View file

@ -37,6 +37,10 @@ $ go get -u github.com/btcsuite/btcd/txscript
* [Manually Signing a Transaction Output](https://pkg.go.dev/github.com/btcsuite/btcd/txscript#example-SignTxOutput) * [Manually Signing a Transaction Output](https://pkg.go.dev/github.com/btcsuite/btcd/txscript#example-SignTxOutput)
Demonstrates manually creating and signing a redeem transaction. Demonstrates manually creating and signing a redeem transaction.
* [Counting Opcodes in Scripts](http://godoc.org/github.com/decred/dcrd/txscript#example-ScriptTokenizer)
Demonstrates creating a script tokenizer instance and using it to count the
number of opcodes a script contains.
## GPG Verification Key ## GPG Verification Key
All official release tags are signed by Conformal so users can ensure the code All official release tags are signed by Conformal so users can ensure the code

537
txscript/bench_test.go Normal file
View file

@ -0,0 +1,537 @@
// Copyright (c) 2018-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"
"fmt"
"io/ioutil"
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
)
var (
// manyInputsBenchTx is a transaction that contains a lot of inputs which is
// useful for benchmarking signature hash calculation.
manyInputsBenchTx wire.MsgTx
// A mock previous output script to use in the signing benchmark.
prevOutScript = hexToBytes("a914f5916158e3e2c4551c1796708db8367207ed13bb87")
)
func init() {
// tx 620f57c92cf05a7f7e7f7d28255d5f7089437bc48e34dcfebf7751d08b7fb8f5
txHex, err := ioutil.ReadFile("data/many_inputs_tx.hex")
if err != nil {
panic(fmt.Sprintf("unable to read benchmark tx file: %v", err))
}
txBytes := hexToBytes(string(txHex))
err = manyInputsBenchTx.Deserialize(bytes.NewReader(txBytes))
if err != nil {
panic(err)
}
}
// BenchmarkCalcSigHash benchmarks how long it takes to calculate the signature
// hashes for all inputs of a transaction with many inputs.
func BenchmarkCalcSigHash(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := 0; j < len(manyInputsBenchTx.TxIn); j++ {
_, err := CalcSignatureHash(prevOutScript, SigHashAll,
&manyInputsBenchTx, j)
if err != nil {
b.Fatalf("failed to calc signature hash: %v", err)
}
}
}
}
// BenchmarkCalcWitnessSigHash benchmarks how long it takes to calculate the
// witness signature hashes for all inputs of a transaction with many inputs.
func BenchmarkCalcWitnessSigHash(b *testing.B) {
sigHashes := NewTxSigHashes(&manyInputsBenchTx)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := 0; j < len(manyInputsBenchTx.TxIn); j++ {
_, err := CalcWitnessSigHash(
prevOutScript, sigHashes, SigHashAll,
&manyInputsBenchTx, j, 5,
)
if err != nil {
b.Fatalf("failed to calc signature hash: %v", err)
}
}
}
}
// genComplexScript returns a script comprised of half as many opcodes as the
// maximum allowed followed by as many max size data pushes fit without
// exceeding the max allowed script size.
func genComplexScript() ([]byte, error) {
var scriptLen int
builder := NewScriptBuilder()
for i := 0; i < MaxOpsPerScript/2; i++ {
builder.AddOp(OP_TRUE)
scriptLen++
}
maxData := bytes.Repeat([]byte{0x02}, MaxScriptElementSize)
for i := 0; i < (MaxScriptSize-scriptLen)/(MaxScriptElementSize+3); i++ {
builder.AddData(maxData)
}
return builder.Script()
}
// BenchmarkScriptParsing benchmarks how long it takes to parse a very large
// script.
func BenchmarkScriptParsing(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
const scriptVersion = 0
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
_ = tokenizer.Opcode()
_ = tokenizer.Data()
_ = tokenizer.ByteIndex()
}
if err := tokenizer.Err(); err != nil {
b.Fatalf("failed to parse script: %v", err)
}
}
}
// BenchmarkDisasmString benchmarks how long it takes to disassemble a very
// large script.
func BenchmarkDisasmString(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := DisasmString(script)
if err != nil {
b.Fatalf("failed to disasm script: %v", err)
}
}
}
// BenchmarkIsPubKeyScript benchmarks how long it takes to analyze a very large
// script to determine if it is a standard pay-to-pubkey script.
func BenchmarkIsPubKeyScript(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = IsPayToPubKey(script)
}
}
// BenchmarkIsPubKeyHashScript benchmarks how long it takes to analyze a very
// large script to determine if it is a standard pay-to-pubkey-hash script.
func BenchmarkIsPubKeyHashScript(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = IsPayToPubKeyHash(script)
}
}
// BenchmarkIsPayToScriptHash benchmarks how long it takes IsPayToScriptHash to
// analyze a very large script.
func BenchmarkIsPayToScriptHash(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = IsPayToScriptHash(script)
}
}
// BenchmarkIsMultisigScriptLarge benchmarks how long it takes IsMultisigScript
// to analyze a very large script.
func BenchmarkIsMultisigScriptLarge(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
isMultisig, err := IsMultisigScript(script)
if err != nil {
b.Fatalf("unexpected err: %v", err)
}
if isMultisig {
b.Fatalf("script should NOT be reported as mutisig script")
}
}
}
// BenchmarkIsMultisigScript benchmarks how long it takes IsMultisigScript to
// analyze a 1-of-2 multisig public key script.
func BenchmarkIsMultisigScript(b *testing.B) {
multisigShortForm := "1 " +
"DATA_33 " +
"0x030478aaaa2be30772f1e69e581610f1840b3cf2fe7228ee0281cd599e5746f81e " +
"DATA_33 " +
"0x0284f4d078b236a9ff91661f8ffbe012737cd3507566f30fd97d25f2b23539f3cd " +
"2 CHECKMULTISIG"
pkScript := mustParseShortForm(multisigShortForm)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
isMultisig, err := IsMultisigScript(pkScript)
if err != nil {
b.Fatalf("unexpected err: %v", err)
}
if !isMultisig {
b.Fatalf("script should be reported as a mutisig script")
}
}
}
// BenchmarkIsMultisigSigScript benchmarks how long it takes IsMultisigSigScript
// to analyze a very large script.
func BenchmarkIsMultisigSigScriptLarge(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if IsMultisigSigScript(script) {
b.Fatalf("script should NOT be reported as mutisig sig script")
}
}
}
// BenchmarkIsMultisigSigScript benchmarks how long it takes IsMultisigSigScript
// to analyze both a 1-of-2 multisig public key script (which should be false)
// and a signature script comprised of a pay-to-script-hash 1-of-2 multisig
// redeem script (which should be true).
func BenchmarkIsMultisigSigScript(b *testing.B) {
multisigShortForm := "1 " +
"DATA_33 " +
"0x030478aaaa2be30772f1e69e581610f1840b3cf2fe7228ee0281cd599e5746f81e " +
"DATA_33 " +
"0x0284f4d078b236a9ff91661f8ffbe012737cd3507566f30fd97d25f2b23539f3cd " +
"2 CHECKMULTISIG"
pkScript := mustParseShortForm(multisigShortForm)
sigHex := "0x304402205795c3ab6ba11331eeac757bf1fc9c34bef0c7e1a9c8bd5eebb8" +
"82f3b79c5838022001e0ab7b4c7662e4522dc5fa479e4b4133fa88c6a53d895dc1d5" +
"2eddc7bbcf2801 "
sigScript := mustParseShortForm("DATA_71 " + sigHex + "DATA_71 " +
multisigShortForm)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if IsMultisigSigScript(pkScript) {
b.Fatalf("script should NOT be reported as mutisig sig script")
}
if !IsMultisigSigScript(sigScript) {
b.Fatalf("script should be reported as a mutisig sig script")
}
}
}
// BenchmarkIsPushOnlyScript benchmarks how long it takes IsPushOnlyScript to
// analyze a very large script.
func BenchmarkIsPushOnlyScript(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = IsPushOnlyScript(script)
}
}
// BenchmarkIsWitnessPubKeyHash benchmarks how long it takes to analyze a very
// large script to determine if it is a standard witness pubkey hash script.
func BenchmarkIsWitnessPubKeyHash(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = IsPayToWitnessPubKeyHash(script)
}
}
// BenchmarkIsWitnessScriptHash benchmarks how long it takes to analyze a very
// large script to determine if it is a standard witness script hash script.
func BenchmarkIsWitnessScriptHash(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = IsPayToWitnessScriptHash(script)
}
}
// BenchmarkIsNullDataScript benchmarks how long it takes to analyze a very
// large script to determine if it is a standard nulldata script.
func BenchmarkIsNullDataScript(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = IsNullData(script)
}
}
// BenchmarkIsUnspendable benchmarks how long it takes IsUnspendable to analyze
// a very large script.
func BenchmarkIsUnspendable(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = IsUnspendable(script)
}
}
// BenchmarkGetSigOpCount benchmarks how long it takes to count the signature
// operations of a very large script.
func BenchmarkGetSigOpCount(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = GetSigOpCount(script)
}
}
// BenchmarkGetPreciseSigOpCount benchmarks how long it takes to count the
// signature operations of a very large script using the more precise counting
// method.
func BenchmarkGetPreciseSigOpCount(b *testing.B) {
redeemScript, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
// Create a fake pay-to-script-hash to pass the necessary checks and create
// the signature script accordingly by pushing the generated "redeem" script
// as the final data push so the benchmark will cover the p2sh path.
scriptHash := "0x0000000000000000000000000000000000000001"
pkScript := mustParseShortForm("HASH160 DATA_20 " + scriptHash + " EQUAL")
sigScript, err := NewScriptBuilder().AddFullData(redeemScript).Script()
if err != nil {
b.Fatalf("failed to create signature script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = GetPreciseSigOpCount(sigScript, pkScript, true)
}
}
// BenchmarkGetWitnessSigOpCount benchmarks how long it takes to count the
// witness signature operations of a very large script.
func BenchmarkGetWitnessSigOpCountP2WKH(b *testing.B) {
pkScript := mustParseShortForm("OP_0 DATA_20 0x0000000000000000000000000000000000000000")
redeemScript, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
witness := wire.TxWitness{
redeemScript,
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = GetWitnessSigOpCount(nil, pkScript, witness)
}
}
// BenchmarkGetWitnessSigOpCount benchmarks how long it takes to count the
// witness signature operations of a very large script.
func BenchmarkGetWitnessSigOpCountNested(b *testing.B) {
pkScript := mustParseShortForm("HASH160 DATA_20 0x0000000000000000000000000000000000000000 OP_EQUAL")
sigScript := mustParseShortForm("DATA_22 0x001600000000000000000000000000000000000000000000")
redeemScript, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
witness := wire.TxWitness{
redeemScript,
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = GetWitnessSigOpCount(sigScript, pkScript, witness)
}
}
// BenchmarkGetScriptClass benchmarks how long it takes GetScriptClass to
// analyze a very large script.
func BenchmarkGetScriptClass(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = GetScriptClass(script)
}
}
// BenchmarkPushedData benchmarks how long it takes to extract the pushed data
// from a very large script.
func BenchmarkPushedData(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := PushedData(script)
if err != nil {
b.Fatalf("unexpected err: %v", err)
}
}
}
// BenchmarkExtractAtomicSwapDataPushesLarge benchmarks how long it takes
// ExtractAtomicSwapDataPushes to analyze a very large script.
func BenchmarkExtractAtomicSwapDataPushesLarge(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
const scriptVersion = 0
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := ExtractAtomicSwapDataPushes(scriptVersion, script)
if err != nil {
b.Fatalf("unexpected err: %v", err)
}
}
}
// BenchmarkExtractAtomicSwapDataPushesLarge benchmarks how long it takes
// ExtractAtomicSwapDataPushes to analyze a standard atomic swap script.
func BenchmarkExtractAtomicSwapDataPushes(b *testing.B) {
secret := "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
recipient := "0000000000000000000000000000000000000001"
refund := "0000000000000000000000000000000000000002"
script := mustParseShortForm(fmt.Sprintf("IF SIZE 32 EQUALVERIFY SHA256 "+
"DATA_32 0x%s EQUALVERIFY DUP HASH160 DATA_20 0x%s ELSE 300000 "+
"CHECKLOCKTIMEVERIFY DROP DUP HASH160 DATA_20 0x%s ENDIF "+
"EQUALVERIFY CHECKSIG", secret, recipient, refund))
const scriptVersion = 0
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := ExtractAtomicSwapDataPushes(scriptVersion, script)
if err != nil {
b.Fatalf("unexpected err: %v", err)
}
}
}
// BenchmarkExtractPkScriptAddrsLarge benchmarks how long it takes to analyze
// and potentially extract addresses from a very large non-standard script.
func BenchmarkExtractPkScriptAddrsLarge(b *testing.B) {
script, err := genComplexScript()
if err != nil {
b.Fatalf("failed to create benchmark script: %v", err)
}
params := &chaincfg.MainNetParams
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _, _, err := ExtractPkScriptAddrs(script, params)
if err != nil {
b.Fatalf("unexpected err: %v", err)
}
}
}
// BenchmarkExtractPkScriptAddrs benchmarks how long it takes to analyze and
// potentially extract addresses from a typical script.
func BenchmarkExtractPkScriptAddrs(b *testing.B) {
script := mustParseShortForm("OP_DUP HASH160 " +
"DATA_20 0x0102030405060708090a0b0c0d0e0f1011121314 " +
"EQUAL")
params := &chaincfg.MainNetParams
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _, _, err := ExtractPkScriptAddrs(script, params)
if err != nil {
b.Fatalf("unexpected err: %v", err)
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -10,6 +10,7 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"math/big" "math/big"
"strings"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
@ -118,21 +119,84 @@ var halfOrder = new(big.Int).Rsh(btcec.S256().N, 1)
// Engine is the virtual machine that executes scripts. // Engine is the virtual machine that executes scripts.
type Engine struct { type Engine struct {
scripts [][]parsedOpcode // The following fields are set when the engine is created and must not be
scriptIdx int // changed afterwards. The entries of the signature cache are mutated
scriptOff int // during execution, however, the cache pointer itself is not changed.
lastCodeSep int //
dstack stack // data stack // flags specifies the additional flags which modify the execution behavior
astack stack // alt stack // of the engine.
//
// tx identifies the transaction that contains the input which in turn
// contains the signature script being executed.
//
// txIdx identifies the input index within the transaction that contains
// the signature script being executed.
//
// version specifies the version of the public key script to execute. Since
// signature scripts redeem public keys scripts, this means the same version
// also extends to signature scripts and redeem scripts in the case of
// pay-to-script-hash.
//
// bip16 specifies that the public key script is of a special form that
// indicates it is a BIP16 pay-to-script-hash and therefore the
// execution must be treated as such.
//
// sigCache caches the results of signature verifications. This is useful
// since transaction scripts are often executed more than once from various
// contexts (e.g. new block templates, when transactions are first seen
// prior to being mined, part of full block verification, etc).
flags ScriptFlags
tx wire.MsgTx tx wire.MsgTx
txIdx int txIdx int
condStack []int version uint16
numOps int bip16 bool
flags ScriptFlags
sigCache *SigCache sigCache *SigCache
hashCache *TxSigHashes hashCache *TxSigHashes
bip16 bool // treat execution as pay-to-script-hash
savedFirstStack [][]byte // stack from first script for bip16 scripts // The following fields handle keeping track of the current execution state
// of the engine.
//
// scripts houses the raw scripts that are executed by the engine. This
// includes the signature script as well as the public key script. It also
// includes the redeem script in the case of pay-to-script-hash.
//
// scriptIdx tracks the index into the scripts array for the current program
// counter.
//
// opcodeIdx tracks the number of the opcode within the current script for
// the current program counter. Note that it differs from the actual byte
// index into the script and is really only used for disassembly purposes.
//
// lastCodeSep specifies the position within the current script of the last
// OP_CODESEPARATOR.
//
// tokenizer provides the token stream of the current script being executed
// and doubles as state tracking for the program counter within the script.
//
// savedFirstStack keeps a copy of the stack from the first script when
// performing pay-to-script-hash execution.
//
// dstack is the primary data stack the various opcodes push and pop data
// to and from during execution.
//
// astack is the alternate data stack the various opcodes push and pop data
// to and from during execution.
//
// condStack tracks the conditional execution state with support for
// multiple nested conditional execution opcodes.
//
// numOps tracks the total number of non-push operations in a script and is
// primarily used to enforce maximum limits.
scripts [][]byte
scriptIdx int
opcodeIdx int
lastCodeSep int
tokenizer ScriptTokenizer
savedFirstStack [][]byte
dstack stack
astack stack
condStack []int
numOps int
witnessVersion int witnessVersion int
witnessProgram []byte witnessProgram []byte
inputAmount int64 inputAmount int64
@ -154,26 +218,144 @@ func (vm *Engine) isBranchExecuting() bool {
return vm.condStack[len(vm.condStack)-1] == OpCondTrue return vm.condStack[len(vm.condStack)-1] == OpCondTrue
} }
// isOpcodeDisabled returns whether or not the opcode is disabled and thus is
// always bad to see in the instruction stream (even if turned off by a
// conditional).
func isOpcodeDisabled(opcode byte) bool {
switch opcode {
case OP_CAT:
return true
case OP_SUBSTR:
return true
case OP_LEFT:
return true
case OP_RIGHT:
return true
case OP_INVERT:
return true
case OP_AND:
return true
case OP_OR:
return true
case OP_XOR:
return true
case OP_2MUL:
return true
case OP_2DIV:
return true
case OP_MUL:
return true
case OP_DIV:
return true
case OP_MOD:
return true
case OP_LSHIFT:
return true
case OP_RSHIFT:
return true
default:
return false
}
}
// isOpcodeAlwaysIllegal returns whether or not the opcode is always illegal
// when passed over by the program counter even if in a non-executed branch (it
// isn't a coincidence that they are conditionals).
func isOpcodeAlwaysIllegal(opcode byte) bool {
switch opcode {
case OP_VERIF:
return true
case OP_VERNOTIF:
return true
default:
return false
}
}
// isOpcodeConditional returns whether or not the opcode is a conditional opcode
// which changes the conditional execution stack when executed.
func isOpcodeConditional(opcode byte) bool {
switch opcode {
case OP_IF:
return true
case OP_NOTIF:
return true
case OP_ELSE:
return true
case OP_ENDIF:
return true
default:
return false
}
}
// checkMinimalDataPush returns whether or not the provided opcode is the
// smallest possible way to represent the given data. For example, the value 15
// could be pushed with OP_DATA_1 15 (among other variations); however, OP_15 is
// a single opcode that represents the same value and is only a single byte
// versus two bytes.
func checkMinimalDataPush(op *opcode, data []byte) error {
opcodeVal := op.value
dataLen := len(data)
switch {
case dataLen == 0 && opcodeVal != OP_0:
str := fmt.Sprintf("zero length data push is encoded with opcode %s "+
"instead of OP_0", op.name)
return scriptError(ErrMinimalData, str)
case dataLen == 1 && data[0] >= 1 && data[0] <= 16:
if opcodeVal != OP_1+data[0]-1 {
// Should have used OP_1 .. OP_16
str := fmt.Sprintf("data push of the value %d encoded with opcode "+
"%s instead of OP_%d", data[0], op.name, data[0])
return scriptError(ErrMinimalData, str)
}
case dataLen == 1 && data[0] == 0x81:
if opcodeVal != OP_1NEGATE {
str := fmt.Sprintf("data push of the value -1 encoded with opcode "+
"%s instead of OP_1NEGATE", op.name)
return scriptError(ErrMinimalData, str)
}
case dataLen <= 75:
if int(opcodeVal) != dataLen {
// Should have used a direct push
str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+
"instead of OP_DATA_%d", dataLen, op.name, dataLen)
return scriptError(ErrMinimalData, str)
}
case dataLen <= 255:
if opcodeVal != OP_PUSHDATA1 {
str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+
"instead of OP_PUSHDATA1", dataLen, op.name)
return scriptError(ErrMinimalData, str)
}
case dataLen <= 65535:
if opcodeVal != OP_PUSHDATA2 {
str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+
"instead of OP_PUSHDATA2", dataLen, op.name)
return scriptError(ErrMinimalData, str)
}
}
return nil
}
// executeOpcode peforms execution on the passed opcode. It takes into account // executeOpcode peforms execution on the passed opcode. It takes into account
// whether or not it is hidden by conditionals, but some rules still must be // whether or not it is hidden by conditionals, but some rules still must be
// tested in this case. // tested in this case.
func (vm *Engine) executeOpcode(pop *parsedOpcode) error { func (vm *Engine) executeOpcode(op *opcode, data []byte) error {
// Disabled opcodes are fail on program counter. // Disabled opcodes are fail on program counter.
if pop.isDisabled() { if isOpcodeDisabled(op.value) {
str := fmt.Sprintf("attempt to execute disabled opcode %s", str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name)
pop.opcode.name)
return scriptError(ErrDisabledOpcode, str) return scriptError(ErrDisabledOpcode, str)
} }
// Always-illegal opcodes are fail on program counter. // Always-illegal opcodes are fail on program counter.
if pop.alwaysIllegal() { if isOpcodeAlwaysIllegal(op.value) {
str := fmt.Sprintf("attempt to execute reserved opcode %s", str := fmt.Sprintf("attempt to execute reserved opcode %s", op.name)
pop.opcode.name)
return scriptError(ErrReservedOpcode, str) return scriptError(ErrReservedOpcode, str)
} }
// Note that this includes OP_RESERVED which counts as a push operation. // Note that this includes OP_RESERVED which counts as a push operation.
if pop.opcode.value > OP_16 { if op.value > OP_16 {
vm.numOps++ vm.numOps++
if vm.numOps > MaxOpsPerScript { if vm.numOps > MaxOpsPerScript {
str := fmt.Sprintf("exceeded max operation limit of %d", str := fmt.Sprintf("exceeded max operation limit of %d",
@ -181,67 +363,42 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error {
return scriptError(ErrTooManyOperations, str) return scriptError(ErrTooManyOperations, str)
} }
} else if len(pop.data) > MaxScriptElementSize { } else if len(data) > MaxScriptElementSize {
str := fmt.Sprintf("element size %d exceeds max allowed size %d", str := fmt.Sprintf("element size %d exceeds max allowed size %d",
len(pop.data), MaxScriptElementSize) len(data), MaxScriptElementSize)
return scriptError(ErrElementTooBig, str) return scriptError(ErrElementTooBig, str)
} }
// Nothing left to do when this is not a conditional opcode and it is // Nothing left to do when this is not a conditional opcode and it is
// not in an executing branch. // not in an executing branch.
if !vm.isBranchExecuting() && !pop.isConditional() { if !vm.isBranchExecuting() && !isOpcodeConditional(op.value) {
return nil return nil
} }
// Ensure all executed data push opcodes use the minimal encoding when // Ensure all executed data push opcodes use the minimal encoding when
// the minimal data verification flag is set. // the minimal data verification flag is set.
if vm.dstack.verifyMinimalData && vm.isBranchExecuting() && if vm.dstack.verifyMinimalData && vm.isBranchExecuting() &&
pop.opcode.value >= 0 && pop.opcode.value <= OP_PUSHDATA4 { op.value >= 0 && op.value <= OP_PUSHDATA4 {
if err := pop.checkMinimalDataPush(); err != nil { if err := checkMinimalDataPush(op, data); err != nil {
return err return err
} }
} }
return pop.opcode.opfunc(pop, vm) return op.opfunc(op, data, vm)
} }
// disasm is a helper function to produce the output for DisasmPC and // checkValidPC returns an error if the current script position is not valid for
// DisasmScript. It produces the opcode prefixed by the program counter at the // execution.
// provided position in the script. It does no error checking and leaves that func (vm *Engine) checkValidPC() error {
// to the caller to provide a valid offset.
func (vm *Engine) disasm(scriptIdx int, scriptOff int) string {
return fmt.Sprintf("%02x:%04x: %s", scriptIdx, scriptOff,
vm.scripts[scriptIdx][scriptOff].print(false))
}
// validPC returns an error if the current script position is valid for
// execution, nil otherwise.
func (vm *Engine) validPC() error {
if vm.scriptIdx >= len(vm.scripts) { if vm.scriptIdx >= len(vm.scripts) {
str := fmt.Sprintf("past input scripts %v:%v %v:xxxx", str := fmt.Sprintf("script index %d beyond total scripts %d",
vm.scriptIdx, vm.scriptOff, len(vm.scripts)) vm.scriptIdx, len(vm.scripts))
return scriptError(ErrInvalidProgramCounter, str)
}
if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
str := fmt.Sprintf("past input scripts %v:%v %v:%04d",
vm.scriptIdx, vm.scriptOff, vm.scriptIdx,
len(vm.scripts[vm.scriptIdx]))
return scriptError(ErrInvalidProgramCounter, str) return scriptError(ErrInvalidProgramCounter, str)
} }
return nil return nil
} }
// curPC returns either the current script and offset, or an error if the
// position isn't valid.
func (vm *Engine) curPC() (script int, off int, err error) {
err = vm.validPC()
if err != nil {
return 0, 0, err
}
return vm.scriptIdx, vm.scriptOff, nil
}
// isWitnessVersionActive returns true if a witness program was extracted // isWitnessVersionActive returns true if a witness program was extracted
// during the initialization of the Engine, and the program's version matches // during the initialization of the Engine, and the program's version matches
// the specified version. // the specified version.
@ -269,7 +426,9 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
if err != nil { if err != nil {
return err return err
} }
pops, err := parseScript(pkScript)
const scriptVersion = 0
err = checkScriptParses(vm.version, pkScript)
if err != nil { if err != nil {
return err return err
} }
@ -277,7 +436,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
// Set the stack to the provided witness stack, then // Set the stack to the provided witness stack, then
// append the pkScript generated above as the next // append the pkScript generated above as the next
// script to execute. // script to execute.
vm.scripts = append(vm.scripts, pops) vm.scripts = append(vm.scripts, pkScript)
vm.SetStack(witness) vm.SetStack(witness)
case payToWitnessScriptHashDataSize: // P2WSH case payToWitnessScriptHashDataSize: // P2WSH
@ -307,10 +466,10 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
"witness program hash mismatch") "witness program hash mismatch")
} }
// With all the validity checks passed, parse the // With all the validity checks passed, assert that the
// script into individual op-codes so w can execute it // script parses without failure.
// as the next script. const scriptVersion = 0
pops, err := parseScript(witnessScript) err := checkScriptParses(vm.version, witnessScript)
if err != nil { if err != nil {
return err return err
} }
@ -318,7 +477,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
// The hash matched successfully, so use the witness as // The hash matched successfully, so use the witness as
// the stack, and set the witnessScript to be the next // the stack, and set the witnessScript to be the next
// script executed. // script executed.
vm.scripts = append(vm.scripts, pops) vm.scripts = append(vm.scripts, witnessScript)
vm.SetStack(witness[:len(witness)-1]) vm.SetStack(witness[:len(witness)-1])
default: default:
@ -359,18 +518,50 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
} }
// DisasmPC returns the string for the disassembly of the opcode that will be // DisasmPC returns the string for the disassembly of the opcode that will be
// next to execute when Step() is called. // next to execute when Step is called.
func (vm *Engine) DisasmPC() (string, error) { func (vm *Engine) DisasmPC() (string, error) {
scriptIdx, scriptOff, err := vm.curPC() if err := vm.checkValidPC(); err != nil {
if err != nil {
return "", err return "", err
} }
return vm.disasm(scriptIdx, scriptOff), nil
// Create a copy of the current tokenizer and parse the next opcode in the
// copy to avoid mutating the current one.
peekTokenizer := vm.tokenizer
if !peekTokenizer.Next() {
// Note that due to the fact that all scripts are checked for parse
// failures before this code ever runs, there should never be an error
// here, but check again to be safe in case a refactor breaks that
// assumption or new script versions are introduced with different
// semantics.
if err := peekTokenizer.Err(); err != nil {
return "", err
}
// Note that this should be impossible to hit in practice because the
// only way it could happen would be for the final opcode of a script to
// already be parsed without the script index having been updated, which
// is not the case since stepping the script always increments the
// script index when parsing and executing the final opcode of a script.
//
// However, check again to be safe in case a refactor breaks that
// assumption or new script versions are introduced with different
// semantics.
str := fmt.Sprintf("program counter beyond script index %d (bytes %x)",
vm.scriptIdx, vm.scripts[vm.scriptIdx])
return "", scriptError(ErrInvalidProgramCounter, str)
}
var buf strings.Builder
disasmOpcode(&buf, peekTokenizer.op, peekTokenizer.Data(), false)
return fmt.Sprintf("%02x:%04x: %s", vm.scriptIdx, vm.opcodeIdx,
buf.String()), nil
} }
// DisasmScript returns the disassembly string for the script at the requested // DisasmScript returns the disassembly string for the script at the requested
// offset index. Index 0 is the signature script and 1 is the public key // offset index. Index 0 is the signature script and 1 is the public key
// script. // script. In the case of pay-to-script-hash, index 2 is the redeem script once
// the execution has progressed far enough to have successfully verified script
// hash and thus add the script to the scripts to execute.
func (vm *Engine) DisasmScript(idx int) (string, error) { func (vm *Engine) DisasmScript(idx int) (string, error) {
if idx >= len(vm.scripts) { if idx >= len(vm.scripts) {
str := fmt.Sprintf("script index %d >= total scripts %d", idx, str := fmt.Sprintf("script index %d >= total scripts %d", idx,
@ -378,19 +569,25 @@ func (vm *Engine) DisasmScript(idx int) (string, error) {
return "", scriptError(ErrInvalidIndex, str) return "", scriptError(ErrInvalidIndex, str)
} }
var disstr string var disbuf strings.Builder
for i := range vm.scripts[idx] { script := vm.scripts[idx]
disstr = disstr + vm.disasm(idx, i) + "\n" tokenizer := MakeScriptTokenizer(vm.version, script)
var opcodeIdx int
for tokenizer.Next() {
disbuf.WriteString(fmt.Sprintf("%02x:%04x: ", idx, opcodeIdx))
disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), false)
disbuf.WriteByte('\n')
opcodeIdx++
} }
return disstr, nil return disbuf.String(), tokenizer.Err()
} }
// CheckErrorCondition returns nil if the running script has ended and was // CheckErrorCondition returns nil if the running script has ended and was
// successful, leaving a a true boolean on the stack. An error otherwise, // successful, leaving a a true boolean on the stack. An error otherwise,
// including if the script has not finished. // including if the script has not finished.
func (vm *Engine) CheckErrorCondition(finalScript bool) error { func (vm *Engine) CheckErrorCondition(finalScript bool) error {
// Check execution is actually done. When pc is past the end of script // Check execution is actually done by ensuring the script index is after
// array there are no more scripts to run. // the final script in the array script.
if vm.scriptIdx < len(vm.scripts) { if vm.scriptIdx < len(vm.scripts) {
return scriptError(ErrScriptUnfinished, return scriptError(ErrScriptUnfinished,
"error check when script unfinished") "error check when script unfinished")
@ -404,11 +601,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
"have clean stack") "have clean stack")
} }
// The final script must end with exactly one data stack item when the
// verify clean stack flag is set. Otherwise, there must be at least one
// data stack item in order to interpret it as a boolean.
if finalScript && vm.hasFlag(ScriptVerifyCleanStack) && if finalScript && vm.hasFlag(ScriptVerifyCleanStack) &&
vm.dstack.Depth() != 1 { vm.dstack.Depth() != 1 {
str := fmt.Sprintf("stack contains %d unexpected items", str := fmt.Sprintf("stack must contain exactly one item (contains %d)",
vm.dstack.Depth()-1) vm.dstack.Depth())
return scriptError(ErrCleanStack, str) return scriptError(ErrCleanStack, str)
} else if vm.dstack.Depth() < 1 { } else if vm.dstack.Depth() < 1 {
return scriptError(ErrEmptyStack, return scriptError(ErrEmptyStack,
@ -422,10 +622,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
if !v { if !v {
// Log interesting data. // Log interesting data.
log.Tracef("%v", newLogClosure(func() string { log.Tracef("%v", newLogClosure(func() string {
dis0, _ := vm.DisasmScript(0) var buf strings.Builder
dis1, _ := vm.DisasmScript(1) buf.WriteString("scripts failed:\n")
return fmt.Sprintf("scripts failed: script0: %s\n"+ for i := range vm.scripts {
"script1: %s", dis0, dis1) dis, _ := vm.DisasmScript(i)
buf.WriteString(fmt.Sprintf("script%d:\n", i))
buf.WriteString(dis)
}
return buf.String()
})) }))
return scriptError(ErrEvalFalse, return scriptError(ErrEvalFalse,
"false stack entry at end of script execution") "false stack entry at end of script execution")
@ -433,25 +637,38 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
return nil return nil
} }
// Step will execute the next instruction and move the program counter to the // Step executes the next instruction and moves the program counter to the next
// next opcode in the script, or the next script if the current has ended. Step // opcode in the script, or the next script if the current has ended. Step will
// will return true in the case that the last opcode was successfully executed. // return true in the case that the last opcode was successfully executed.
// //
// The result of calling Step or any other method is undefined if an error is // The result of calling Step or any other method is undefined if an error is
// returned. // returned.
func (vm *Engine) Step() (done bool, err error) { func (vm *Engine) Step() (done bool, err error) {
// Verify that it is pointing to a valid script address. // Verify the engine is pointing to a valid program counter.
err = vm.validPC() if err := vm.checkValidPC(); err != nil {
if err != nil {
return true, err return true, err
} }
opcode := &vm.scripts[vm.scriptIdx][vm.scriptOff]
vm.scriptOff++ // Attempt to parse the next opcode from the current script.
if !vm.tokenizer.Next() {
// Note that due to the fact that all scripts are checked for parse
// failures before this code ever runs, there should never be an error
// here, but check again to be safe in case a refactor breaks that
// assumption or new script versions are introduced with different
// semantics.
if err := vm.tokenizer.Err(); err != nil {
return false, err
}
str := fmt.Sprintf("attempt to step beyond script index %d (bytes %x)",
vm.scriptIdx, vm.scripts[vm.scriptIdx])
return true, scriptError(ErrInvalidProgramCounter, str)
}
// Execute the opcode while taking into account several things such as // Execute the opcode while taking into account several things such as
// disabled opcodes, illegal opcodes, maximum allowed operations per // disabled opcodes, illegal opcodes, maximum allowed operations per script,
// script, maximum script element sizes, and conditionals. // maximum script element sizes, and conditionals.
err = vm.executeOpcode(opcode) err = vm.executeOpcode(vm.tokenizer.op, vm.tokenizer.Data())
if err != nil { if err != nil {
return true, err return true, err
} }
@ -466,43 +683,53 @@ func (vm *Engine) Step() (done bool, err error) {
} }
// Prepare for next instruction. // Prepare for next instruction.
if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) { vm.opcodeIdx++
// Illegal to have an `if' that straddles two scripts. if vm.tokenizer.Done() {
if err == nil && len(vm.condStack) != 0 { // Illegal to have a conditional that straddles two scripts.
if len(vm.condStack) != 0 {
return false, scriptError(ErrUnbalancedConditional, return false, scriptError(ErrUnbalancedConditional,
"end of script reached in conditional execution") "end of script reached in conditional execution")
} }
// Alt stack doesn't persist. // Alt stack doesn't persist between scripts.
_ = vm.astack.DropN(vm.astack.Depth()) _ = vm.astack.DropN(vm.astack.Depth())
vm.numOps = 0 // number of ops is per script. // The number of operations is per script.
vm.scriptOff = 0 vm.numOps = 0
if vm.scriptIdx == 0 && vm.bip16 {
// Reset the opcode index for the next script.
vm.opcodeIdx = 0
// Advance to the next script as needed.
switch {
case vm.scriptIdx == 0 && vm.bip16:
vm.scriptIdx++ vm.scriptIdx++
vm.savedFirstStack = vm.GetStack() vm.savedFirstStack = vm.GetStack()
} else if vm.scriptIdx == 1 && vm.bip16 {
case vm.scriptIdx == 1 && vm.bip16:
// Put us past the end for CheckErrorCondition() // Put us past the end for CheckErrorCondition()
vm.scriptIdx++ vm.scriptIdx++
// Check script ran successfully and pull the script
// out of the first stack and execute that. // Check script ran successfully.
err := vm.CheckErrorCondition(false) err := vm.CheckErrorCondition(false)
if err != nil { if err != nil {
return false, err return false, err
} }
// Obtain the redeem script from the first stack and ensure it
// parses.
script := vm.savedFirstStack[len(vm.savedFirstStack)-1] script := vm.savedFirstStack[len(vm.savedFirstStack)-1]
pops, err := parseScript(script) if err := checkScriptParses(vm.version, script); err != nil {
if err != nil {
return false, err return false, err
} }
vm.scripts = append(vm.scripts, pops) vm.scripts = append(vm.scripts, script)
// Set stack to be the stack from first script minus the // Set stack to be the stack from first script minus the redeem
// script itself // script itself
vm.SetStack(vm.savedFirstStack[:len(vm.savedFirstStack)-1]) vm.SetStack(vm.savedFirstStack[:len(vm.savedFirstStack)-1])
} else if (vm.scriptIdx == 1 && vm.witnessProgram != nil) ||
(vm.scriptIdx == 2 && vm.witnessProgram != nil && vm.bip16) { // Nested P2SH. case vm.scriptIdx == 1 && vm.witnessProgram != nil,
vm.scriptIdx == 2 && vm.witnessProgram != nil && vm.bip16: // np2sh
vm.scriptIdx++ vm.scriptIdx++
@ -510,30 +737,46 @@ func (vm *Engine) Step() (done bool, err error) {
if err := vm.verifyWitnessProgram(witness); err != nil { if err := vm.verifyWitnessProgram(witness); err != nil {
return false, err return false, err
} }
} else {
default:
vm.scriptIdx++ vm.scriptIdx++
} }
// there are zero length scripts in the wild
if vm.scriptIdx < len(vm.scripts) && vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) { // Skip empty scripts.
if vm.scriptIdx < len(vm.scripts) && len(vm.scripts[vm.scriptIdx]) == 0 {
vm.scriptIdx++ vm.scriptIdx++
} }
vm.lastCodeSep = 0 vm.lastCodeSep = 0
if vm.scriptIdx >= len(vm.scripts) { if vm.scriptIdx >= len(vm.scripts) {
return true, nil return true, nil
} }
// Finally, update the current tokenizer used to parse through scripts
// one opcode at a time to start from the beginning of the new script
// associated with the program counter.
vm.tokenizer = MakeScriptTokenizer(vm.version, vm.scripts[vm.scriptIdx])
} }
return false, nil return false, nil
} }
// Execute will execute all scripts in the script engine and return either nil // Execute will execute all scripts in the script engine and return either nil
// for successful validation or an error if one occurred. // for successful validation or an error if one occurred.
func (vm *Engine) Execute() (err error) { func (vm *Engine) Execute() (err error) {
// All script versions other than 0 currently execute without issue,
// making all outputs to them anyone can pay. In the future this
// will allow for the addition of new scripting languages.
if vm.version != 0 {
return nil
}
done := false done := false
for !done { for !done {
log.Tracef("%v", newLogClosure(func() string { log.Tracef("%v", newLogClosure(func() string {
dis, err := vm.DisasmPC() dis, err := vm.DisasmPC()
if err != nil { if err != nil {
return fmt.Sprintf("stepping (%v)", err) return fmt.Sprintf("stepping - failed to disasm pc: %v", err)
} }
return fmt.Sprintf("stepping %v", dis) return fmt.Sprintf("stepping %v", dis)
})) }))
@ -545,7 +788,7 @@ func (vm *Engine) Execute() (err error) {
log.Tracef("%v", newLogClosure(func() string { log.Tracef("%v", newLogClosure(func() string {
var dstr, astr string var dstr, astr string
// if we're tracing, dump the stacks. // Log the non-empty stacks when tracing.
if vm.dstack.Depth() != 0 { if vm.dstack.Depth() != 0 {
dstr = "Stack:\n" + vm.dstack.String() dstr = "Stack:\n" + vm.dstack.String()
} }
@ -561,7 +804,7 @@ func (vm *Engine) Execute() (err error) {
} }
// subScript returns the script since the last OP_CODESEPARATOR. // subScript returns the script since the last OP_CODESEPARATOR.
func (vm *Engine) subScript() []parsedOpcode { func (vm *Engine) subScript() []byte {
return vm.scripts[vm.scriptIdx][vm.lastCodeSep:] return vm.scripts[vm.scriptIdx][vm.lastCodeSep:]
} }
@ -580,6 +823,27 @@ func (vm *Engine) checkHashTypeEncoding(hashType SigHashType) error {
return nil return nil
} }
// isStrictPubKeyEncoding returns whether or not the passed public key adheres
// to the strict encoding requirements.
func isStrictPubKeyEncoding(pubKey []byte) bool {
if len(pubKey) == 33 && (pubKey[0] == 0x02 || pubKey[0] == 0x03) {
// Compressed
return true
}
if len(pubKey) == 65 {
switch pubKey[0] {
case 0x04:
// Uncompressed
return true
case 0x06, 0x07:
// Hybrid
return true
}
}
return false
}
// checkPubKeyEncoding returns whether or not the passed public key adheres to // checkPubKeyEncoding returns whether or not the passed public key adheres to
// the strict encoding requirements if enabled. // the strict encoding requirements if enabled.
func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error { func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
@ -854,6 +1118,7 @@ func (vm *Engine) SetAltStack(data [][]byte) {
// engine according to the description provided by each flag. // engine according to the description provided by each flag.
func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags, func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags,
sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64) (*Engine, error) { sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64) (*Engine, error) {
const scriptVersion = 0
// The provided transaction input index must refer to a valid input. // The provided transaction input index must refer to a valid input.
if txIdx < 0 || txIdx >= len(tx.TxIn) { if txIdx < 0 || txIdx >= len(tx.TxIn) {
@ -863,10 +1128,10 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
} }
scriptSig := tx.TxIn[txIdx].SignatureScript scriptSig := tx.TxIn[txIdx].SignatureScript
// When both the signature script and public key script are empty the // When both the signature script and public key script are empty the result
// result is necessarily an error since the stack would end up being // is necessarily an error since the stack would end up being empty which is
// empty which is equivalent to a false top element. Thus, just return // equivalent to a false top element. Thus, just return the relevant error
// the relevant error now as an optimization. // now as an optimization.
if len(scriptSig) == 0 && len(scriptPubKey) == 0 { if len(scriptSig) == 0 && len(scriptPubKey) == 0 {
return nil, scriptError(ErrEvalFalse, return nil, scriptError(ErrEvalFalse,
"false stack entry at end of script execution") "false stack entry at end of script execution")
@ -897,40 +1162,45 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
"signature script is not push only") "signature script is not push only")
} }
// The engine stores the scripts in parsed form using a slice. This // The signature script must only contain data pushes for PS2H which is
// allows multiple scripts to be executed in sequence. For example, // determined based on the form of the public key script.
// with a pay-to-script-hash transaction, there will be ultimately be if vm.hasFlag(ScriptBip16) && isScriptHashScript(scriptPubKey) {
// a third script to execute.
scripts := [][]byte{scriptSig, scriptPubKey}
vm.scripts = make([][]parsedOpcode, len(scripts))
for i, scr := range scripts {
if len(scr) > MaxScriptSize {
str := fmt.Sprintf("script size %d is larger than max "+
"allowed size %d", len(scr), MaxScriptSize)
return nil, scriptError(ErrScriptTooBig, str)
}
var err error
vm.scripts[i], err = parseScript(scr)
if err != nil {
return nil, err
}
}
// Advance the program counter to the public key script if the signature
// script is empty since there is nothing to execute for it in that
// case.
if len(scripts[0]) == 0 {
vm.scriptIdx++
}
if vm.hasFlag(ScriptBip16) && isScriptHash(vm.scripts[1]) {
// Only accept input scripts that push data for P2SH. // Only accept input scripts that push data for P2SH.
if !isPushOnly(vm.scripts[0]) { // Notice that the push only checks have already been done when
// the flag to verify signature scripts are push only is set
// above, so avoid checking again.
alreadyChecked := vm.hasFlag(ScriptVerifySigPushOnly)
if !alreadyChecked && !IsPushOnlyScript(scriptSig) {
return nil, scriptError(ErrNotPushOnly, return nil, scriptError(ErrNotPushOnly,
"pay to script hash is not push only") "pay to script hash is not push only")
} }
vm.bip16 = true vm.bip16 = true
} }
// The engine stores the scripts using a slice. This allows multiple
// scripts to be executed in sequence. For example, with a
// pay-to-script-hash transaction, there will be ultimately be a third
// script to execute.
scripts := [][]byte{scriptSig, scriptPubKey}
for _, scr := range scripts {
if len(scr) > MaxScriptSize {
str := fmt.Sprintf("script size %d is larger than max allowed "+
"size %d", len(scr), MaxScriptSize)
return nil, scriptError(ErrScriptTooBig, str)
}
const scriptVersion = 0
if err := checkScriptParses(scriptVersion, scr); err != nil {
return nil, err
}
}
vm.scripts = scripts
// Advance the program counter to the public key script if the signature
// script is empty since there is nothing to execute for it in that case.
if len(scriptSig) == 0 {
vm.scriptIdx++
}
if vm.hasFlag(ScriptVerifyMinimalData) { if vm.hasFlag(ScriptVerifyMinimalData) {
vm.dstack.verifyMinimalData = true vm.dstack.verifyMinimalData = true
vm.astack.verifyMinimalData = true vm.astack.verifyMinimalData = true
@ -952,7 +1222,7 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
var witProgram []byte var witProgram []byte
switch { switch {
case isWitnessProgram(vm.scripts[1]): case IsWitnessProgram(vm.scripts[1]):
// The scriptSig must be *empty* for all native witness // The scriptSig must be *empty* for all native witness
// programs, otherwise we introduce malleability. // programs, otherwise we introduce malleability.
if len(scriptSig) != 0 { if len(scriptSig) != 0 {
@ -967,10 +1237,11 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
// data push of the witness program, otherwise we // data push of the witness program, otherwise we
// reintroduce malleability. // reintroduce malleability.
sigPops := vm.scripts[0] sigPops := vm.scripts[0]
if len(sigPops) == 1 && canonicalPush(sigPops[0]) && if len(sigPops) > 2 &&
IsWitnessProgram(sigPops[0].data) { isCanonicalPush(sigPops[0], sigPops[1:]) &&
IsWitnessProgram(sigPops[1:]) {
witProgram = sigPops[0].data witProgram = sigPops[1:]
} else { } else {
errStr := "signature script for witness " + errStr := "signature script for witness " +
"nested p2sh is not canonical" "nested p2sh is not canonical"
@ -997,6 +1268,10 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
} }
// Setup the current tokenizer used to parse through the script one opcode
// at a time with the script associated with the program counter.
vm.tokenizer = MakeScriptTokenizer(scriptVersion, scripts[vm.scriptIdx])
vm.tx = *tx vm.tx = *tx
vm.txIdx = txIdx vm.txIdx = txIdx

View file

@ -1,4 +1,5 @@
// Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -11,16 +12,16 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
) )
// TestBadPC sets the pc to a deliberately bad result then confirms that Step() // TestBadPC sets the pc to a deliberately bad result then confirms that Step
// and Disasm fail correctly. // and Disasm fail correctly.
func TestBadPC(t *testing.T) { func TestBadPC(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {
script, off int scriptIdx int
}{ }{
{script: 2, off: 0}, {scriptIdx: 2},
{script: 0, off: 2}, {scriptIdx: 3},
} }
// tx with almost empty scripts. // tx with almost empty scripts.
@ -59,20 +60,20 @@ func TestBadPC(t *testing.T) {
t.Errorf("Failed to create script: %v", err) t.Errorf("Failed to create script: %v", err)
} }
// set to after all scripts // Set to after all scripts.
vm.scriptIdx = test.script vm.scriptIdx = test.scriptIdx
vm.scriptOff = test.off
// Ensure attempting to step fails.
_, err = vm.Step() _, err = vm.Step()
if err == nil { if err == nil {
t.Errorf("Step with invalid pc (%v) succeeds!", test) t.Errorf("Step with invalid pc (%v) succeeds!", test)
continue continue
} }
// Ensure attempting to disassemble the current program counter fails.
_, err = vm.DisasmPC() _, err = vm.DisasmPC()
if err == nil { if err == nil {
t.Errorf("DisasmPC with invalid pc (%v) succeeds!", t.Errorf("DisasmPC with invalid pc (%v) succeeds!", test)
test)
} }
} }
} }

View file

@ -1,4 +1,5 @@
// Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -47,6 +48,10 @@ const (
// the provided data exceeds MaxDataCarrierSize. // the provided data exceeds MaxDataCarrierSize.
ErrTooMuchNullData ErrTooMuchNullData
// ErrUnsupportedScriptVersion is returned when an unsupported script
// version is passed to a function which deals with script analysis.
ErrUnsupportedScriptVersion
// ------------------------------------------ // ------------------------------------------
// Failures related to final execution state. // Failures related to final execution state.
// ------------------------------------------ // ------------------------------------------
@ -352,6 +357,7 @@ var errorCodeStrings = map[ErrorCode]string{
ErrNotMultisigScript: "ErrNotMultisigScript", ErrNotMultisigScript: "ErrNotMultisigScript",
ErrTooManyRequiredSigs: "ErrTooManyRequiredSigs", ErrTooManyRequiredSigs: "ErrTooManyRequiredSigs",
ErrTooMuchNullData: "ErrTooMuchNullData", ErrTooMuchNullData: "ErrTooMuchNullData",
ErrUnsupportedScriptVersion: "ErrUnsupportedScriptVersion",
ErrEarlyReturn: "ErrEarlyReturn", ErrEarlyReturn: "ErrEarlyReturn",
ErrEmptyStack: "ErrEmptyStack", ErrEmptyStack: "ErrEmptyStack",
ErrEvalFalse: "ErrEvalFalse", ErrEvalFalse: "ErrEvalFalse",

View file

@ -1,4 +1,5 @@
// Copyright (c) 2017 The btcsuite developers // Copyright (c) 2017 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -22,6 +23,7 @@ func TestErrorCodeStringer(t *testing.T) {
{ErrUnsupportedAddress, "ErrUnsupportedAddress"}, {ErrUnsupportedAddress, "ErrUnsupportedAddress"},
{ErrTooManyRequiredSigs, "ErrTooManyRequiredSigs"}, {ErrTooManyRequiredSigs, "ErrTooManyRequiredSigs"},
{ErrTooMuchNullData, "ErrTooMuchNullData"}, {ErrTooMuchNullData, "ErrTooMuchNullData"},
{ErrUnsupportedScriptVersion, "ErrUnsupportedScriptVersion"},
{ErrNotMultisigScript, "ErrNotMultisigScript"}, {ErrNotMultisigScript, "ErrNotMultisigScript"},
{ErrEarlyReturn, "ErrEarlyReturn"}, {ErrEarlyReturn, "ErrEarlyReturn"},
{ErrEmptyStack, "ErrEmptyStack"}, {ErrEmptyStack, "ErrEmptyStack"},

View file

@ -1,4 +1,5 @@
// Copyright (c) 2014-2016 The btcsuite developers // Copyright (c) 2014-2016 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -180,3 +181,34 @@ func ExampleSignTxOutput() {
// Output: // Output:
// Transaction successfully signed // Transaction successfully signed
} }
// This example demonstrates creating a script tokenizer instance and using it
// to count the number of opcodes a script contains.
func ExampleScriptTokenizer() {
// Create a script to use in the example. Ordinarily this would come from
// some other source.
hash160 := btcutil.Hash160([]byte("example"))
script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).
AddOp(txscript.OP_HASH160).AddData(hash160).
AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG).Script()
if err != nil {
fmt.Printf("failed to build script: %v\n", err)
return
}
// Create a tokenizer to iterate the script and count the number of opcodes.
const scriptVersion = 0
var numOpcodes int
tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
numOpcodes++
}
if tokenizer.Err() != nil {
fmt.Printf("script failed to parse: %v\n", err)
} else {
fmt.Printf("script contains %d opcode(s)\n", numOpcodes)
}
// Output:
// script contains 5 opcode(s)
}

View file

@ -8,9 +8,10 @@ import (
"bytes" "bytes"
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/hex"
"fmt" "fmt"
"hash" "hash"
"strings"
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
@ -27,7 +28,7 @@ type opcode struct {
value byte value byte
name string name string
length int length int
opfunc func(*parsedOpcode, *Engine) error opfunc func(*opcode, []byte, *Engine) error
} }
// These constants are the values of the official opcodes used on the btc wiki, // These constants are the values of the official opcodes used on the btc wiki,
@ -610,306 +611,52 @@ var opcodeOnelineRepls = map[string]string{
"OP_16": "16", "OP_16": "16",
} }
// parsedOpcode represents an opcode that has been parsed and includes any // disasmOpcode writes a human-readable disassembly of the provided opcode and
// potential data associated with it. // data into the provided buffer. The compact flag indicates the disassembly
type parsedOpcode struct { // should print a more compact representation of data-carrying and small integer
opcode *opcode // opcodes. For example, OP_0 through OP_16 are replaced with the numeric value
data []byte // and data pushes are printed as only the hex representation of the data as
} // opposed to including the opcode that specifies the amount of data to push as
// well.
// isDisabled returns whether or not the opcode is disabled and thus is always func disasmOpcode(buf *strings.Builder, op *opcode, data []byte, compact bool) {
// bad to see in the instruction stream (even if turned off by a conditional). // Replace opcode which represent values (e.g. OP_0 through OP_16 and
func (pop *parsedOpcode) isDisabled() bool { // OP_1NEGATE) with the raw value when performing a compact disassembly.
switch pop.opcode.value { opcodeName := op.name
case OP_CAT: if compact {
return true
case OP_SUBSTR:
return true
case OP_LEFT:
return true
case OP_RIGHT:
return true
case OP_INVERT:
return true
case OP_AND:
return true
case OP_OR:
return true
case OP_XOR:
return true
case OP_2MUL:
return true
case OP_2DIV:
return true
case OP_MUL:
return true
case OP_DIV:
return true
case OP_MOD:
return true
case OP_LSHIFT:
return true
case OP_RSHIFT:
return true
default:
return false
}
}
// checkParseableInScript checks whether or not the current opcode is able to be
// parsed at a certain position in a script.
// This returns the position of the next opcode to be parsed in the script.
func (pop *parsedOpcode) checkParseableInScript(script []byte, scriptPos int) (int, error) {
// Parse data out of instruction.
switch {
// No additional data. Note that some of the opcodes, notably
// OP_1NEGATE, OP_0, and OP_[1-16] represent the data
// themselves.
case pop.opcode.length == 1:
scriptPos++
// Data pushes of specific lengths -- OP_DATA_[1-75].
case pop.opcode.length > 1:
if len(script[scriptPos:]) < pop.opcode.length {
str := fmt.Sprintf("opcode %s requires %d "+
"bytes, but script only has %d remaining",
pop.opcode.name, pop.opcode.length, len(script[scriptPos:]))
return 0, scriptError(ErrMalformedPush, str)
}
// Slice out the data.
pop.data = script[scriptPos+1 : scriptPos+pop.opcode.length]
scriptPos += pop.opcode.length
// Data pushes with parsed lengths -- OP_PUSHDATAP{1,2,4}.
case pop.opcode.length < 0:
var l uint
off := scriptPos + 1
if len(script[off:]) < -pop.opcode.length {
str := fmt.Sprintf("opcode %s requires %d "+
"bytes, but script only has %d remaining",
pop.opcode.name, -pop.opcode.length, len(script[off:]))
return 0, scriptError(ErrMalformedPush, str)
}
// Next -length bytes are little endian length of data.
switch pop.opcode.length {
case -1:
l = uint(script[off])
case -2:
l = ((uint(script[off+1]) << 8) |
uint(script[off]))
case -4:
l = ((uint(script[off+3]) << 24) |
(uint(script[off+2]) << 16) |
(uint(script[off+1]) << 8) |
uint(script[off]))
default:
str := fmt.Sprintf("invalid opcode length %d",
pop.opcode.length)
return 0, scriptError(ErrMalformedPush, str)
}
// Move offset to beginning of the data.
off += -pop.opcode.length
// Disallow entries that do not fit script or were
// sign extended.
if int(l) > len(script[off:]) || int(l) < 0 {
str := fmt.Sprintf("opcode %s pushes %d bytes, "+
"but script only has %d remaining",
pop.opcode.name, int(l), len(script[off:]))
return 0, scriptError(ErrMalformedPush, str)
}
pop.data = script[off : off+int(l)]
scriptPos += 1 - pop.opcode.length + int(l)
}
return scriptPos, nil
}
// alwaysIllegal returns whether or not the opcode is always illegal when passed
// over by the program counter even if in a non-executed branch (it isn't a
// coincidence that they are conditionals).
func (pop *parsedOpcode) alwaysIllegal() bool {
switch pop.opcode.value {
case OP_VERIF:
return true
case OP_VERNOTIF:
return true
default:
return false
}
}
// isConditional returns whether or not the opcode is a conditional opcode which
// changes the conditional execution stack when executed.
func (pop *parsedOpcode) isConditional() bool {
switch pop.opcode.value {
case OP_IF:
return true
case OP_NOTIF:
return true
case OP_ELSE:
return true
case OP_ENDIF:
return true
default:
return false
}
}
// checkMinimalDataPush returns whether or not the current data push uses the
// smallest possible opcode to represent it. For example, the value 15 could
// be pushed with OP_DATA_1 15 (among other variations); however, OP_15 is a
// single opcode that represents the same value and is only a single byte versus
// two bytes.
func (pop *parsedOpcode) checkMinimalDataPush() error {
data := pop.data
dataLen := len(data)
opcode := pop.opcode.value
if dataLen == 0 && opcode != OP_0 {
str := fmt.Sprintf("zero length data push is encoded with "+
"opcode %s instead of OP_0", pop.opcode.name)
return scriptError(ErrMinimalData, str)
} else if dataLen == 1 && data[0] >= 1 && data[0] <= 16 {
if opcode != OP_1+data[0]-1 {
// Should have used OP_1 .. OP_16
str := fmt.Sprintf("data push of the value %d encoded "+
"with opcode %s instead of OP_%d", data[0],
pop.opcode.name, data[0])
return scriptError(ErrMinimalData, str)
}
} else if dataLen == 1 && data[0] == 0x81 {
if opcode != OP_1NEGATE {
str := fmt.Sprintf("data push of the value -1 encoded "+
"with opcode %s instead of OP_1NEGATE",
pop.opcode.name)
return scriptError(ErrMinimalData, str)
}
} else if dataLen <= 75 {
if int(opcode) != dataLen {
// Should have used a direct push
str := fmt.Sprintf("data push of %d bytes encoded "+
"with opcode %s instead of OP_DATA_%d", dataLen,
pop.opcode.name, dataLen)
return scriptError(ErrMinimalData, str)
}
} else if dataLen <= 255 {
if opcode != OP_PUSHDATA1 {
str := fmt.Sprintf("data push of %d bytes encoded "+
"with opcode %s instead of OP_PUSHDATA1",
dataLen, pop.opcode.name)
return scriptError(ErrMinimalData, str)
}
} else if dataLen <= 65535 {
if opcode != OP_PUSHDATA2 {
str := fmt.Sprintf("data push of %d bytes encoded "+
"with opcode %s instead of OP_PUSHDATA2",
dataLen, pop.opcode.name)
return scriptError(ErrMinimalData, str)
}
}
return nil
}
// print returns a human-readable string representation of the opcode for use
// in script disassembly.
func (pop *parsedOpcode) print(oneline bool) string {
// The reference implementation one-line disassembly replaces opcodes
// which represent values (e.g. OP_0 through OP_16 and OP_1NEGATE)
// with the raw value. However, when not doing a one-line dissassembly,
// we prefer to show the actual opcode names. Thus, only replace the
// opcodes in question when the oneline flag is set.
opcodeName := pop.opcode.name
if oneline {
if replName, ok := opcodeOnelineRepls[opcodeName]; ok { if replName, ok := opcodeOnelineRepls[opcodeName]; ok {
opcodeName = replName opcodeName = replName
} }
// Nothing more to do for non-data push opcodes. // Either write the human-readable opcode or the parsed data in hex for
if pop.opcode.length == 1 { // data-carrying opcodes.
return opcodeName switch {
case op.length == 1:
buf.WriteString(opcodeName)
default:
buf.WriteString(hex.EncodeToString(data))
} }
return fmt.Sprintf("%x", pop.data) return
} }
// Nothing more to do for non-data push opcodes. buf.WriteString(opcodeName)
if pop.opcode.length == 1 {
return opcodeName switch op.length {
} // Only write the opcode name for non-data push opcodes.
case 1:
return
// Add length for the OP_PUSHDATA# opcodes. // Add length for the OP_PUSHDATA# opcodes.
retString := opcodeName
switch pop.opcode.length {
case -1: case -1:
retString += fmt.Sprintf(" 0x%02x", len(pop.data)) buf.WriteString(fmt.Sprintf(" 0x%02x", len(data)))
case -2: case -2:
retString += fmt.Sprintf(" 0x%04x", len(pop.data)) buf.WriteString(fmt.Sprintf(" 0x%04x", len(data)))
case -4: case -4:
retString += fmt.Sprintf(" 0x%08x", len(pop.data)) buf.WriteString(fmt.Sprintf(" 0x%08x", len(data)))
} }
return fmt.Sprintf("%s 0x%02x", retString, pop.data) buf.WriteString(fmt.Sprintf(" 0x%02x", data))
}
// bytes returns any data associated with the opcode encoded as it would be in
// a script. This is used for unparsing scripts from parsed opcodes.
func (pop *parsedOpcode) bytes() ([]byte, error) {
var retbytes []byte
if pop.opcode.length > 0 {
retbytes = make([]byte, 1, pop.opcode.length)
} else {
retbytes = make([]byte, 1, 1+len(pop.data)-
pop.opcode.length)
}
retbytes[0] = pop.opcode.value
if pop.opcode.length == 1 {
if len(pop.data) != 0 {
str := fmt.Sprintf("internal consistency error - "+
"parsed opcode %s has data length %d when %d "+
"was expected", pop.opcode.name, len(pop.data),
0)
return nil, scriptError(ErrInternal, str)
}
return retbytes, nil
}
nbytes := pop.opcode.length
if pop.opcode.length < 0 {
l := len(pop.data)
// tempting just to hardcode to avoid the complexity here.
switch pop.opcode.length {
case -1:
retbytes = append(retbytes, byte(l))
nbytes = int(retbytes[1]) + len(retbytes)
case -2:
retbytes = append(retbytes, byte(l&0xff),
byte(l>>8&0xff))
nbytes = int(binary.LittleEndian.Uint16(retbytes[1:])) +
len(retbytes)
case -4:
retbytes = append(retbytes, byte(l&0xff),
byte((l>>8)&0xff), byte((l>>16)&0xff),
byte((l>>24)&0xff))
nbytes = int(binary.LittleEndian.Uint32(retbytes[1:])) +
len(retbytes)
}
}
retbytes = append(retbytes, pop.data...)
if len(retbytes) != nbytes {
str := fmt.Sprintf("internal consistency error - "+
"parsed opcode %s has data length %d when %d was "+
"expected", pop.opcode.name, len(retbytes), nbytes)
return nil, scriptError(ErrInternal, str)
}
return retbytes, nil
} }
// ******************************************* // *******************************************
@ -922,45 +669,42 @@ func (pop *parsedOpcode) bytes() ([]byte, error) {
// opcodes before executing in an initial parse step, the consensus rules // opcodes before executing in an initial parse step, the consensus rules
// dictate the script doesn't fail until the program counter passes over a // dictate the script doesn't fail until the program counter passes over a
// disabled opcode (even when they appear in a branch that is not executed). // disabled opcode (even when they appear in a branch that is not executed).
func opcodeDisabled(op *parsedOpcode, vm *Engine) error { func opcodeDisabled(op *opcode, data []byte, vm *Engine) error {
str := fmt.Sprintf("attempt to execute disabled opcode %s", str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name)
op.opcode.name)
return scriptError(ErrDisabledOpcode, str) return scriptError(ErrDisabledOpcode, str)
} }
// opcodeReserved is a common handler for all reserved opcodes. It returns an // opcodeReserved is a common handler for all reserved opcodes. It returns an
// appropriate error indicating the opcode is reserved. // appropriate error indicating the opcode is reserved.
func opcodeReserved(op *parsedOpcode, vm *Engine) error { func opcodeReserved(op *opcode, data []byte, vm *Engine) error {
str := fmt.Sprintf("attempt to execute reserved opcode %s", str := fmt.Sprintf("attempt to execute reserved opcode %s", op.name)
op.opcode.name)
return scriptError(ErrReservedOpcode, str) return scriptError(ErrReservedOpcode, str)
} }
// opcodeInvalid is a common handler for all invalid opcodes. It returns an // opcodeInvalid is a common handler for all invalid opcodes. It returns an
// appropriate error indicating the opcode is invalid. // appropriate error indicating the opcode is invalid.
func opcodeInvalid(op *parsedOpcode, vm *Engine) error { func opcodeInvalid(op *opcode, data []byte, vm *Engine) error {
str := fmt.Sprintf("attempt to execute invalid opcode %s", str := fmt.Sprintf("attempt to execute invalid opcode %s", op.name)
op.opcode.name)
return scriptError(ErrReservedOpcode, str) return scriptError(ErrReservedOpcode, str)
} }
// opcodeFalse pushes an empty array to the data stack to represent false. Note // opcodeFalse pushes an empty array to the data stack to represent false. Note
// that 0, when encoded as a number according to the numeric encoding consensus // that 0, when encoded as a number according to the numeric encoding consensus
// rules, is an empty array. // rules, is an empty array.
func opcodeFalse(op *parsedOpcode, vm *Engine) error { func opcodeFalse(op *opcode, data []byte, vm *Engine) error {
vm.dstack.PushByteArray(nil) vm.dstack.PushByteArray(nil)
return nil return nil
} }
// opcodePushData is a common handler for the vast majority of opcodes that push // opcodePushData is a common handler for the vast majority of opcodes that push
// raw data (bytes) to the data stack. // raw data (bytes) to the data stack.
func opcodePushData(op *parsedOpcode, vm *Engine) error { func opcodePushData(op *opcode, data []byte, vm *Engine) error {
vm.dstack.PushByteArray(op.data) vm.dstack.PushByteArray(data)
return nil return nil
} }
// opcode1Negate pushes -1, encoded as a number, to the data stack. // opcode1Negate pushes -1, encoded as a number, to the data stack.
func opcode1Negate(op *parsedOpcode, vm *Engine) error { func opcode1Negate(op *opcode, data []byte, vm *Engine) error {
vm.dstack.PushInt(scriptNum(-1)) vm.dstack.PushInt(scriptNum(-1))
return nil return nil
} }
@ -968,23 +712,24 @@ func opcode1Negate(op *parsedOpcode, vm *Engine) error {
// opcodeN is a common handler for the small integer data push opcodes. It // opcodeN is a common handler for the small integer data push opcodes. It
// pushes the numeric value the opcode represents (which will be from 1 to 16) // pushes the numeric value the opcode represents (which will be from 1 to 16)
// onto the data stack. // onto the data stack.
func opcodeN(op *parsedOpcode, vm *Engine) error { func opcodeN(op *opcode, data []byte, vm *Engine) error {
// The opcodes are all defined consecutively, so the numeric value is // The opcodes are all defined consecutively, so the numeric value is
// the difference. // the difference.
vm.dstack.PushInt(scriptNum((op.opcode.value - (OP_1 - 1)))) vm.dstack.PushInt(scriptNum((op.value - (OP_1 - 1))))
return nil return nil
} }
// opcodeNop is a common handler for the NOP family of opcodes. As the name // opcodeNop is a common handler for the NOP family of opcodes. As the name
// implies it generally does nothing, however, it will return an error when // implies it generally does nothing, however, it will return an error when
// the flag to discourage use of NOPs is set for select opcodes. // the flag to discourage use of NOPs is set for select opcodes.
func opcodeNop(op *parsedOpcode, vm *Engine) error { func opcodeNop(op *opcode, data []byte, vm *Engine) error {
switch op.opcode.value { switch op.value {
case OP_NOP1, OP_NOP4, OP_NOP5, case OP_NOP1, OP_NOP4, OP_NOP5,
OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10: OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10:
if vm.hasFlag(ScriptDiscourageUpgradableNops) { if vm.hasFlag(ScriptDiscourageUpgradableNops) {
str := fmt.Sprintf("OP_NOP%d reserved for soft-fork "+ str := fmt.Sprintf("%v reserved for soft-fork "+
"upgrades", op.opcode.value-(OP_NOP1-1)) "upgrades", op.name)
return scriptError(ErrDiscourageUpgradableNOPs, str) return scriptError(ErrDiscourageUpgradableNOPs, str)
} }
} }
@ -1047,7 +792,7 @@ func popIfBool(vm *Engine) (bool, error) {
// //
// Data stack transformation: [... bool] -> [...] // Data stack transformation: [... bool] -> [...]
// Conditional stack transformation: [...] -> [... OpCondValue] // Conditional stack transformation: [...] -> [... OpCondValue]
func opcodeIf(op *parsedOpcode, vm *Engine) error { func opcodeIf(op *opcode, data []byte, vm *Engine) error {
condVal := OpCondFalse condVal := OpCondFalse
if vm.isBranchExecuting() { if vm.isBranchExecuting() {
ok, err := popIfBool(vm) ok, err := popIfBool(vm)
@ -1081,7 +826,7 @@ func opcodeIf(op *parsedOpcode, vm *Engine) error {
// //
// Data stack transformation: [... bool] -> [...] // Data stack transformation: [... bool] -> [...]
// Conditional stack transformation: [...] -> [... OpCondValue] // Conditional stack transformation: [...] -> [... OpCondValue]
func opcodeNotIf(op *parsedOpcode, vm *Engine) error { func opcodeNotIf(op *opcode, data []byte, vm *Engine) error {
condVal := OpCondFalse condVal := OpCondFalse
if vm.isBranchExecuting() { if vm.isBranchExecuting() {
ok, err := popIfBool(vm) ok, err := popIfBool(vm)
@ -1104,10 +849,10 @@ func opcodeNotIf(op *parsedOpcode, vm *Engine) error {
// An error is returned if there has not already been a matching OP_IF. // An error is returned if there has not already been a matching OP_IF.
// //
// Conditional stack transformation: [... OpCondValue] -> [... !OpCondValue] // Conditional stack transformation: [... OpCondValue] -> [... !OpCondValue]
func opcodeElse(op *parsedOpcode, vm *Engine) error { func opcodeElse(op *opcode, data []byte, vm *Engine) error {
if len(vm.condStack) == 0 { if len(vm.condStack) == 0 {
str := fmt.Sprintf("encountered opcode %s with no matching "+ str := fmt.Sprintf("encountered opcode %s with no matching "+
"opcode to begin conditional execution", op.opcode.name) "opcode to begin conditional execution", op.name)
return scriptError(ErrUnbalancedConditional, str) return scriptError(ErrUnbalancedConditional, str)
} }
@ -1130,10 +875,10 @@ func opcodeElse(op *parsedOpcode, vm *Engine) error {
// An error is returned if there has not already been a matching OP_IF. // An error is returned if there has not already been a matching OP_IF.
// //
// Conditional stack transformation: [... OpCondValue] -> [...] // Conditional stack transformation: [... OpCondValue] -> [...]
func opcodeEndif(op *parsedOpcode, vm *Engine) error { func opcodeEndif(op *opcode, data []byte, vm *Engine) error {
if len(vm.condStack) == 0 { if len(vm.condStack) == 0 {
str := fmt.Sprintf("encountered opcode %s with no matching "+ str := fmt.Sprintf("encountered opcode %s with no matching "+
"opcode to begin conditional execution", op.opcode.name) "opcode to begin conditional execution", op.name)
return scriptError(ErrUnbalancedConditional, str) return scriptError(ErrUnbalancedConditional, str)
} }
@ -1146,14 +891,14 @@ func opcodeEndif(op *parsedOpcode, vm *Engine) error {
// item on the stack or when that item evaluates to false. In the latter case // item on the stack or when that item evaluates to false. In the latter case
// where the verification fails specifically due to the top item evaluating // where the verification fails specifically due to the top item evaluating
// to false, the returned error will use the passed error code. // to false, the returned error will use the passed error code.
func abstractVerify(op *parsedOpcode, vm *Engine, c ErrorCode) error { func abstractVerify(op *opcode, vm *Engine, c ErrorCode) error {
verified, err := vm.dstack.PopBool() verified, err := vm.dstack.PopBool()
if err != nil { if err != nil {
return err return err
} }
if !verified { if !verified {
str := fmt.Sprintf("%s failed", op.opcode.name) str := fmt.Sprintf("%s failed", op.name)
return scriptError(c, str) return scriptError(c, str)
} }
return nil return nil
@ -1161,13 +906,13 @@ func abstractVerify(op *parsedOpcode, vm *Engine, c ErrorCode) error {
// opcodeVerify examines the top item on the data stack as a boolean value and // opcodeVerify examines the top item on the data stack as a boolean value and
// verifies it evaluates to true. An error is returned if it does not. // verifies it evaluates to true. An error is returned if it does not.
func opcodeVerify(op *parsedOpcode, vm *Engine) error { func opcodeVerify(op *opcode, data []byte, vm *Engine) error {
return abstractVerify(op, vm, ErrVerify) return abstractVerify(op, vm, ErrVerify)
} }
// opcodeReturn returns an appropriate error since it is always an error to // opcodeReturn returns an appropriate error since it is always an error to
// return early from a script. // return early from a script.
func opcodeReturn(op *parsedOpcode, vm *Engine) error { func opcodeReturn(op *opcode, data []byte, vm *Engine) error {
return scriptError(ErrEarlyReturn, "script returned early") return scriptError(ErrEarlyReturn, "script returned early")
} }
@ -1197,7 +942,7 @@ func verifyLockTime(txLockTime, threshold, lockTime int64) error {
// validating if the transaction outputs are spendable yet. If flag // validating if the transaction outputs are spendable yet. If flag
// ScriptVerifyCheckLockTimeVerify is not set, the code continues as if OP_NOP2 // ScriptVerifyCheckLockTimeVerify is not set, the code continues as if OP_NOP2
// were executed. // were executed.
func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error { func opcodeCheckLockTimeVerify(op *opcode, data []byte, vm *Engine) error {
// If the ScriptVerifyCheckLockTimeVerify script flag is not set, treat // If the ScriptVerifyCheckLockTimeVerify script flag is not set, treat
// opcode as OP_NOP2 instead. // opcode as OP_NOP2 instead.
if !vm.hasFlag(ScriptVerifyCheckLockTimeVerify) { if !vm.hasFlag(ScriptVerifyCheckLockTimeVerify) {
@ -1271,7 +1016,7 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
// validating if the transaction outputs are spendable yet. If flag // validating if the transaction outputs are spendable yet. If flag
// ScriptVerifyCheckSequenceVerify is not set, the code continues as if OP_NOP3 // ScriptVerifyCheckSequenceVerify is not set, the code continues as if OP_NOP3
// were executed. // were executed.
func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error { func opcodeCheckSequenceVerify(op *opcode, data []byte, vm *Engine) error {
// If the ScriptVerifyCheckSequenceVerify script flag is not set, treat // If the ScriptVerifyCheckSequenceVerify script flag is not set, treat
// opcode as OP_NOP3 instead. // opcode as OP_NOP3 instead.
if !vm.hasFlag(ScriptVerifyCheckSequenceVerify) { if !vm.hasFlag(ScriptVerifyCheckSequenceVerify) {
@ -1348,7 +1093,7 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
// //
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2] // Main data stack transformation: [... x1 x2 x3] -> [... x1 x2]
// Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2 y3 x3] // Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2 y3 x3]
func opcodeToAltStack(op *parsedOpcode, vm *Engine) error { func opcodeToAltStack(op *opcode, data []byte, vm *Engine) error {
so, err := vm.dstack.PopByteArray() so, err := vm.dstack.PopByteArray()
if err != nil { if err != nil {
return err return err
@ -1363,7 +1108,7 @@ func opcodeToAltStack(op *parsedOpcode, vm *Engine) error {
// //
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 y3] // Main data stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 y3]
// Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2] // Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2]
func opcodeFromAltStack(op *parsedOpcode, vm *Engine) error { func opcodeFromAltStack(op *opcode, data []byte, vm *Engine) error {
so, err := vm.astack.PopByteArray() so, err := vm.astack.PopByteArray()
if err != nil { if err != nil {
return err return err
@ -1376,35 +1121,35 @@ func opcodeFromAltStack(op *parsedOpcode, vm *Engine) error {
// opcode2Drop removes the top 2 items from the data stack. // opcode2Drop removes the top 2 items from the data stack.
// //
// Stack transformation: [... x1 x2 x3] -> [... x1] // Stack transformation: [... x1 x2 x3] -> [... x1]
func opcode2Drop(op *parsedOpcode, vm *Engine) error { func opcode2Drop(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.DropN(2) return vm.dstack.DropN(2)
} }
// opcode2Dup duplicates the top 2 items on the data stack. // opcode2Dup duplicates the top 2 items on the data stack.
// //
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2 x3] // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2 x3]
func opcode2Dup(op *parsedOpcode, vm *Engine) error { func opcode2Dup(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.DupN(2) return vm.dstack.DupN(2)
} }
// opcode3Dup duplicates the top 3 items on the data stack. // opcode3Dup duplicates the top 3 items on the data stack.
// //
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x1 x2 x3] // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x1 x2 x3]
func opcode3Dup(op *parsedOpcode, vm *Engine) error { func opcode3Dup(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.DupN(3) return vm.dstack.DupN(3)
} }
// opcode2Over duplicates the 2 items before the top 2 items on the data stack. // opcode2Over duplicates the 2 items before the top 2 items on the data stack.
// //
// Stack transformation: [... x1 x2 x3 x4] -> [... x1 x2 x3 x4 x1 x2] // Stack transformation: [... x1 x2 x3 x4] -> [... x1 x2 x3 x4 x1 x2]
func opcode2Over(op *parsedOpcode, vm *Engine) error { func opcode2Over(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.OverN(2) return vm.dstack.OverN(2)
} }
// opcode2Rot rotates the top 6 items on the data stack to the left twice. // opcode2Rot rotates the top 6 items on the data stack to the left twice.
// //
// Stack transformation: [... x1 x2 x3 x4 x5 x6] -> [... x3 x4 x5 x6 x1 x2] // Stack transformation: [... x1 x2 x3 x4 x5 x6] -> [... x3 x4 x5 x6 x1 x2]
func opcode2Rot(op *parsedOpcode, vm *Engine) error { func opcode2Rot(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.RotN(2) return vm.dstack.RotN(2)
} }
@ -1412,7 +1157,7 @@ func opcode2Rot(op *parsedOpcode, vm *Engine) error {
// before them. // before them.
// //
// Stack transformation: [... x1 x2 x3 x4] -> [... x3 x4 x1 x2] // Stack transformation: [... x1 x2 x3 x4] -> [... x3 x4 x1 x2]
func opcode2Swap(op *parsedOpcode, vm *Engine) error { func opcode2Swap(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.SwapN(2) return vm.dstack.SwapN(2)
} }
@ -1420,7 +1165,7 @@ func opcode2Swap(op *parsedOpcode, vm *Engine) error {
// //
// Stack transformation (x1==0): [... x1] -> [... x1] // Stack transformation (x1==0): [... x1] -> [... x1]
// Stack transformation (x1!=0): [... x1] -> [... x1 x1] // Stack transformation (x1!=0): [... x1] -> [... x1 x1]
func opcodeIfDup(op *parsedOpcode, vm *Engine) error { func opcodeIfDup(op *opcode, data []byte, vm *Engine) error {
so, err := vm.dstack.PeekByteArray(0) so, err := vm.dstack.PeekByteArray(0)
if err != nil { if err != nil {
return err return err
@ -1440,7 +1185,7 @@ func opcodeIfDup(op *parsedOpcode, vm *Engine) error {
// Stack transformation: [...] -> [... <num of items on the stack>] // Stack transformation: [...] -> [... <num of items on the stack>]
// Example with 2 items: [x1 x2] -> [x1 x2 2] // Example with 2 items: [x1 x2] -> [x1 x2 2]
// Example with 3 items: [x1 x2 x3] -> [x1 x2 x3 3] // Example with 3 items: [x1 x2 x3] -> [x1 x2 x3 3]
func opcodeDepth(op *parsedOpcode, vm *Engine) error { func opcodeDepth(op *opcode, data []byte, vm *Engine) error {
vm.dstack.PushInt(scriptNum(vm.dstack.Depth())) vm.dstack.PushInt(scriptNum(vm.dstack.Depth()))
return nil return nil
} }
@ -1448,28 +1193,28 @@ func opcodeDepth(op *parsedOpcode, vm *Engine) error {
// opcodeDrop removes the top item from the data stack. // opcodeDrop removes the top item from the data stack.
// //
// Stack transformation: [... x1 x2 x3] -> [... x1 x2] // Stack transformation: [... x1 x2 x3] -> [... x1 x2]
func opcodeDrop(op *parsedOpcode, vm *Engine) error { func opcodeDrop(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.DropN(1) return vm.dstack.DropN(1)
} }
// opcodeDup duplicates the top item on the data stack. // opcodeDup duplicates the top item on the data stack.
// //
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x3] // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x3]
func opcodeDup(op *parsedOpcode, vm *Engine) error { func opcodeDup(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.DupN(1) return vm.dstack.DupN(1)
} }
// opcodeNip removes the item before the top item on the data stack. // opcodeNip removes the item before the top item on the data stack.
// //
// Stack transformation: [... x1 x2 x3] -> [... x1 x3] // Stack transformation: [... x1 x2 x3] -> [... x1 x3]
func opcodeNip(op *parsedOpcode, vm *Engine) error { func opcodeNip(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.NipN(1) return vm.dstack.NipN(1)
} }
// opcodeOver duplicates the item before the top item on the data stack. // opcodeOver duplicates the item before the top item on the data stack.
// //
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2] // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2]
func opcodeOver(op *parsedOpcode, vm *Engine) error { func opcodeOver(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.OverN(1) return vm.dstack.OverN(1)
} }
@ -1479,7 +1224,7 @@ func opcodeOver(op *parsedOpcode, vm *Engine) error {
// Stack transformation: [xn ... x2 x1 x0 n] -> [xn ... x2 x1 x0 xn] // Stack transformation: [xn ... x2 x1 x0 n] -> [xn ... x2 x1 x0 xn]
// Example with n=1: [x2 x1 x0 1] -> [x2 x1 x0 x1] // Example with n=1: [x2 x1 x0 1] -> [x2 x1 x0 x1]
// Example with n=2: [x2 x1 x0 2] -> [x2 x1 x0 x2] // Example with n=2: [x2 x1 x0 2] -> [x2 x1 x0 x2]
func opcodePick(op *parsedOpcode, vm *Engine) error { func opcodePick(op *opcode, data []byte, vm *Engine) error {
val, err := vm.dstack.PopInt() val, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1494,7 +1239,7 @@ func opcodePick(op *parsedOpcode, vm *Engine) error {
// Stack transformation: [xn ... x2 x1 x0 n] -> [... x2 x1 x0 xn] // Stack transformation: [xn ... x2 x1 x0 n] -> [... x2 x1 x0 xn]
// Example with n=1: [x2 x1 x0 1] -> [x2 x0 x1] // Example with n=1: [x2 x1 x0 1] -> [x2 x0 x1]
// Example with n=2: [x2 x1 x0 2] -> [x1 x0 x2] // Example with n=2: [x2 x1 x0 2] -> [x1 x0 x2]
func opcodeRoll(op *parsedOpcode, vm *Engine) error { func opcodeRoll(op *opcode, data []byte, vm *Engine) error {
val, err := vm.dstack.PopInt() val, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1506,14 +1251,14 @@ func opcodeRoll(op *parsedOpcode, vm *Engine) error {
// opcodeRot rotates the top 3 items on the data stack to the left. // opcodeRot rotates the top 3 items on the data stack to the left.
// //
// Stack transformation: [... x1 x2 x3] -> [... x2 x3 x1] // Stack transformation: [... x1 x2 x3] -> [... x2 x3 x1]
func opcodeRot(op *parsedOpcode, vm *Engine) error { func opcodeRot(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.RotN(1) return vm.dstack.RotN(1)
} }
// opcodeSwap swaps the top two items on the stack. // opcodeSwap swaps the top two items on the stack.
// //
// Stack transformation: [... x1 x2] -> [... x2 x1] // Stack transformation: [... x1 x2] -> [... x2 x1]
func opcodeSwap(op *parsedOpcode, vm *Engine) error { func opcodeSwap(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.SwapN(1) return vm.dstack.SwapN(1)
} }
@ -1521,7 +1266,7 @@ func opcodeSwap(op *parsedOpcode, vm *Engine) error {
// second-to-top item. // second-to-top item.
// //
// Stack transformation: [... x1 x2] -> [... x2 x1 x2] // Stack transformation: [... x1 x2] -> [... x2 x1 x2]
func opcodeTuck(op *parsedOpcode, vm *Engine) error { func opcodeTuck(op *opcode, data []byte, vm *Engine) error {
return vm.dstack.Tuck() return vm.dstack.Tuck()
} }
@ -1529,7 +1274,7 @@ func opcodeTuck(op *parsedOpcode, vm *Engine) error {
// stack. // stack.
// //
// Stack transformation: [... x1] -> [... x1 len(x1)] // Stack transformation: [... x1] -> [... x1 len(x1)]
func opcodeSize(op *parsedOpcode, vm *Engine) error { func opcodeSize(op *opcode, data []byte, vm *Engine) error {
so, err := vm.dstack.PeekByteArray(0) so, err := vm.dstack.PeekByteArray(0)
if err != nil { if err != nil {
return err return err
@ -1543,7 +1288,7 @@ func opcodeSize(op *parsedOpcode, vm *Engine) error {
// bytes, and pushes the result, encoded as a boolean, back to the stack. // bytes, and pushes the result, encoded as a boolean, back to the stack.
// //
// Stack transformation: [... x1 x2] -> [... bool] // Stack transformation: [... x1 x2] -> [... bool]
func opcodeEqual(op *parsedOpcode, vm *Engine) error { func opcodeEqual(op *opcode, data []byte, vm *Engine) error {
a, err := vm.dstack.PopByteArray() a, err := vm.dstack.PopByteArray()
if err != nil { if err != nil {
return err return err
@ -1564,8 +1309,8 @@ func opcodeEqual(op *parsedOpcode, vm *Engine) error {
// evaluates to true. An error is returned if it does not. // evaluates to true. An error is returned if it does not.
// //
// Stack transformation: [... x1 x2] -> [... bool] -> [...] // Stack transformation: [... x1 x2] -> [... bool] -> [...]
func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error { func opcodeEqualVerify(op *opcode, data []byte, vm *Engine) error {
err := opcodeEqual(op, vm) err := opcodeEqual(op, data, vm)
if err == nil { if err == nil {
err = abstractVerify(op, vm, ErrEqualVerify) err = abstractVerify(op, vm, ErrEqualVerify)
} }
@ -1576,7 +1321,7 @@ func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error {
// it with its incremented value (plus 1). // it with its incremented value (plus 1).
// //
// Stack transformation: [... x1 x2] -> [... x1 x2+1] // Stack transformation: [... x1 x2] -> [... x1 x2+1]
func opcode1Add(op *parsedOpcode, vm *Engine) error { func opcode1Add(op *opcode, data []byte, vm *Engine) error {
m, err := vm.dstack.PopInt() m, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1590,7 +1335,7 @@ func opcode1Add(op *parsedOpcode, vm *Engine) error {
// it with its decremented value (minus 1). // it with its decremented value (minus 1).
// //
// Stack transformation: [... x1 x2] -> [... x1 x2-1] // Stack transformation: [... x1 x2] -> [... x1 x2-1]
func opcode1Sub(op *parsedOpcode, vm *Engine) error { func opcode1Sub(op *opcode, data []byte, vm *Engine) error {
m, err := vm.dstack.PopInt() m, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1604,7 +1349,7 @@ func opcode1Sub(op *parsedOpcode, vm *Engine) error {
// it with its negation. // it with its negation.
// //
// Stack transformation: [... x1 x2] -> [... x1 -x2] // Stack transformation: [... x1 x2] -> [... x1 -x2]
func opcodeNegate(op *parsedOpcode, vm *Engine) error { func opcodeNegate(op *opcode, data []byte, vm *Engine) error {
m, err := vm.dstack.PopInt() m, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1618,7 +1363,7 @@ func opcodeNegate(op *parsedOpcode, vm *Engine) error {
// it with its absolute value. // it with its absolute value.
// //
// Stack transformation: [... x1 x2] -> [... x1 abs(x2)] // Stack transformation: [... x1 x2] -> [... x1 abs(x2)]
func opcodeAbs(op *parsedOpcode, vm *Engine) error { func opcodeAbs(op *opcode, data []byte, vm *Engine) error {
m, err := vm.dstack.PopInt() m, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1643,7 +1388,7 @@ func opcodeAbs(op *parsedOpcode, vm *Engine) error {
// Stack transformation (x2==0): [... x1 0] -> [... x1 1] // Stack transformation (x2==0): [... x1 0] -> [... x1 1]
// Stack transformation (x2!=0): [... x1 1] -> [... x1 0] // Stack transformation (x2!=0): [... x1 1] -> [... x1 0]
// Stack transformation (x2!=0): [... x1 17] -> [... x1 0] // Stack transformation (x2!=0): [... x1 17] -> [... x1 0]
func opcodeNot(op *parsedOpcode, vm *Engine) error { func opcodeNot(op *opcode, data []byte, vm *Engine) error {
m, err := vm.dstack.PopInt() m, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1663,7 +1408,7 @@ func opcodeNot(op *parsedOpcode, vm *Engine) error {
// Stack transformation (x2==0): [... x1 0] -> [... x1 0] // Stack transformation (x2==0): [... x1 0] -> [... x1 0]
// Stack transformation (x2!=0): [... x1 1] -> [... x1 1] // Stack transformation (x2!=0): [... x1 1] -> [... x1 1]
// Stack transformation (x2!=0): [... x1 17] -> [... x1 1] // Stack transformation (x2!=0): [... x1 17] -> [... x1 1]
func opcode0NotEqual(op *parsedOpcode, vm *Engine) error { func opcode0NotEqual(op *opcode, data []byte, vm *Engine) error {
m, err := vm.dstack.PopInt() m, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1680,7 +1425,7 @@ func opcode0NotEqual(op *parsedOpcode, vm *Engine) error {
// them with their sum. // them with their sum.
// //
// Stack transformation: [... x1 x2] -> [... x1+x2] // Stack transformation: [... x1 x2] -> [... x1+x2]
func opcodeAdd(op *parsedOpcode, vm *Engine) error { func opcodeAdd(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1700,7 +1445,7 @@ func opcodeAdd(op *parsedOpcode, vm *Engine) error {
// entry. // entry.
// //
// Stack transformation: [... x1 x2] -> [... x1-x2] // Stack transformation: [... x1 x2] -> [... x1-x2]
func opcodeSub(op *parsedOpcode, vm *Engine) error { func opcodeSub(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1722,7 +1467,7 @@ func opcodeSub(op *parsedOpcode, vm *Engine) error {
// Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 0] // Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 0]
// Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 0] // Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 0]
// Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1] // Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1]
func opcodeBoolAnd(op *parsedOpcode, vm *Engine) error { func opcodeBoolAnd(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1749,7 +1494,7 @@ func opcodeBoolAnd(op *parsedOpcode, vm *Engine) error {
// Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 1] // Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 1]
// Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 1] // Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 1]
// Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1] // Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1]
func opcodeBoolOr(op *parsedOpcode, vm *Engine) error { func opcodeBoolOr(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1774,7 +1519,7 @@ func opcodeBoolOr(op *parsedOpcode, vm *Engine) error {
// //
// Stack transformation (x1==x2): [... 5 5] -> [... 1] // Stack transformation (x1==x2): [... 5 5] -> [... 1]
// Stack transformation (x1!=x2): [... 5 7] -> [... 0] // Stack transformation (x1!=x2): [... 5 7] -> [... 0]
func opcodeNumEqual(op *parsedOpcode, vm *Engine) error { func opcodeNumEqual(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1802,8 +1547,8 @@ func opcodeNumEqual(op *parsedOpcode, vm *Engine) error {
// to true. An error is returned if it does not. // to true. An error is returned if it does not.
// //
// Stack transformation: [... x1 x2] -> [... bool] -> [...] // Stack transformation: [... x1 x2] -> [... bool] -> [...]
func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error { func opcodeNumEqualVerify(op *opcode, data []byte, vm *Engine) error {
err := opcodeNumEqual(op, vm) err := opcodeNumEqual(op, data, vm)
if err == nil { if err == nil {
err = abstractVerify(op, vm, ErrNumEqualVerify) err = abstractVerify(op, vm, ErrNumEqualVerify)
} }
@ -1815,7 +1560,7 @@ func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error {
// //
// Stack transformation (x1==x2): [... 5 5] -> [... 0] // Stack transformation (x1==x2): [... 5 5] -> [... 0]
// Stack transformation (x1!=x2): [... 5 7] -> [... 1] // Stack transformation (x1!=x2): [... 5 7] -> [... 1]
func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error { func opcodeNumNotEqual(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1840,7 +1585,7 @@ func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error {
// otherwise a 0. // otherwise a 0.
// //
// Stack transformation: [... x1 x2] -> [... bool] // Stack transformation: [... x1 x2] -> [... bool]
func opcodeLessThan(op *parsedOpcode, vm *Engine) error { func opcodeLessThan(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1865,7 +1610,7 @@ func opcodeLessThan(op *parsedOpcode, vm *Engine) error {
// with a 1, otherwise a 0. // with a 1, otherwise a 0.
// //
// Stack transformation: [... x1 x2] -> [... bool] // Stack transformation: [... x1 x2] -> [... bool]
func opcodeGreaterThan(op *parsedOpcode, vm *Engine) error { func opcodeGreaterThan(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1889,7 +1634,7 @@ func opcodeGreaterThan(op *parsedOpcode, vm *Engine) error {
// replaced with a 1, otherwise a 0. // replaced with a 1, otherwise a 0.
// //
// Stack transformation: [... x1 x2] -> [... bool] // Stack transformation: [... x1 x2] -> [... bool]
func opcodeLessThanOrEqual(op *parsedOpcode, vm *Engine) error { func opcodeLessThanOrEqual(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1913,7 +1658,7 @@ func opcodeLessThanOrEqual(op *parsedOpcode, vm *Engine) error {
// item, they are replaced with a 1, otherwise a 0. // item, they are replaced with a 1, otherwise a 0.
// //
// Stack transformation: [... x1 x2] -> [... bool] // Stack transformation: [... x1 x2] -> [... bool]
func opcodeGreaterThanOrEqual(op *parsedOpcode, vm *Engine) error { func opcodeGreaterThanOrEqual(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1937,7 +1682,7 @@ func opcodeGreaterThanOrEqual(op *parsedOpcode, vm *Engine) error {
// them with the minimum of the two. // them with the minimum of the two.
// //
// Stack transformation: [... x1 x2] -> [... min(x1, x2)] // Stack transformation: [... x1 x2] -> [... min(x1, x2)]
func opcodeMin(op *parsedOpcode, vm *Engine) error { func opcodeMin(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1961,7 +1706,7 @@ func opcodeMin(op *parsedOpcode, vm *Engine) error {
// them with the maximum of the two. // them with the maximum of the two.
// //
// Stack transformation: [... x1 x2] -> [... max(x1, x2)] // Stack transformation: [... x1 x2] -> [... max(x1, x2)]
func opcodeMax(op *parsedOpcode, vm *Engine) error { func opcodeMax(op *opcode, data []byte, vm *Engine) error {
v0, err := vm.dstack.PopInt() v0, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -1989,7 +1734,7 @@ func opcodeMax(op *parsedOpcode, vm *Engine) error {
// the third-to-top item is the value to test. // the third-to-top item is the value to test.
// //
// Stack transformation: [... x1 min max] -> [... bool] // Stack transformation: [... x1 min max] -> [... bool]
func opcodeWithin(op *parsedOpcode, vm *Engine) error { func opcodeWithin(op *opcode, data []byte, vm *Engine) error {
maxVal, err := vm.dstack.PopInt() maxVal, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -2023,7 +1768,7 @@ func calcHash(buf []byte, hasher hash.Hash) []byte {
// replaces it with ripemd160(data). // replaces it with ripemd160(data).
// //
// Stack transformation: [... x1] -> [... ripemd160(x1)] // Stack transformation: [... x1] -> [... ripemd160(x1)]
func opcodeRipemd160(op *parsedOpcode, vm *Engine) error { func opcodeRipemd160(op *opcode, data []byte, vm *Engine) error {
buf, err := vm.dstack.PopByteArray() buf, err := vm.dstack.PopByteArray()
if err != nil { if err != nil {
return err return err
@ -2037,7 +1782,7 @@ func opcodeRipemd160(op *parsedOpcode, vm *Engine) error {
// with sha1(data). // with sha1(data).
// //
// Stack transformation: [... x1] -> [... sha1(x1)] // Stack transformation: [... x1] -> [... sha1(x1)]
func opcodeSha1(op *parsedOpcode, vm *Engine) error { func opcodeSha1(op *opcode, data []byte, vm *Engine) error {
buf, err := vm.dstack.PopByteArray() buf, err := vm.dstack.PopByteArray()
if err != nil { if err != nil {
return err return err
@ -2052,7 +1797,7 @@ func opcodeSha1(op *parsedOpcode, vm *Engine) error {
// it with sha256(data). // it with sha256(data).
// //
// Stack transformation: [... x1] -> [... sha256(x1)] // Stack transformation: [... x1] -> [... sha256(x1)]
func opcodeSha256(op *parsedOpcode, vm *Engine) error { func opcodeSha256(op *opcode, data []byte, vm *Engine) error {
buf, err := vm.dstack.PopByteArray() buf, err := vm.dstack.PopByteArray()
if err != nil { if err != nil {
return err return err
@ -2067,7 +1812,7 @@ func opcodeSha256(op *parsedOpcode, vm *Engine) error {
// it with ripemd160(sha256(data)). // it with ripemd160(sha256(data)).
// //
// Stack transformation: [... x1] -> [... ripemd160(sha256(x1))] // Stack transformation: [... x1] -> [... ripemd160(sha256(x1))]
func opcodeHash160(op *parsedOpcode, vm *Engine) error { func opcodeHash160(op *opcode, data []byte, vm *Engine) error {
buf, err := vm.dstack.PopByteArray() buf, err := vm.dstack.PopByteArray()
if err != nil { if err != nil {
return err return err
@ -2082,7 +1827,7 @@ func opcodeHash160(op *parsedOpcode, vm *Engine) error {
// it with sha256(sha256(data)). // it with sha256(sha256(data)).
// //
// Stack transformation: [... x1] -> [... sha256(sha256(x1))] // Stack transformation: [... x1] -> [... sha256(sha256(x1))]
func opcodeHash256(op *parsedOpcode, vm *Engine) error { func opcodeHash256(op *opcode, data []byte, vm *Engine) error {
buf, err := vm.dstack.PopByteArray() buf, err := vm.dstack.PopByteArray()
if err != nil { if err != nil {
return err return err
@ -2096,8 +1841,8 @@ func opcodeHash256(op *parsedOpcode, vm *Engine) error {
// seen OP_CODESEPARATOR which is used during signature checking. // seen OP_CODESEPARATOR which is used during signature checking.
// //
// This opcode does not change the contents of the data stack. // This opcode does not change the contents of the data stack.
func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error { func opcodeCodeSeparator(op *opcode, data []byte, vm *Engine) error {
vm.lastCodeSep = vm.scriptOff vm.lastCodeSep = int(vm.tokenizer.ByteIndex())
return nil return nil
} }
@ -2115,7 +1860,7 @@ func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error {
// cryptographic methods against the provided public key. // cryptographic methods against the provided public key.
// //
// Stack transformation: [... signature pubkey] -> [... bool] // Stack transformation: [... signature pubkey] -> [... bool]
func opcodeCheckSig(op *parsedOpcode, vm *Engine) error { func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error {
pkBytes, err := vm.dstack.PopByteArray() pkBytes, err := vm.dstack.PopByteArray()
if err != nil { if err != nil {
return err return err
@ -2171,7 +1916,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
sigHashes = NewTxSigHashes(&vm.tx) sigHashes = NewTxSigHashes(&vm.tx)
} }
hash, err = calcWitnessSignatureHash(subScript, sigHashes, hashType, hash, err = calcWitnessSignatureHashRaw(subScript, sigHashes, hashType,
&vm.tx, vm.txIdx, vm.inputAmount) &vm.tx, vm.txIdx, vm.inputAmount)
if err != nil { if err != nil {
return err return err
@ -2230,9 +1975,9 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
// The opcodeCheckSig function is invoked followed by opcodeVerify. See the // The opcodeCheckSig function is invoked followed by opcodeVerify. See the
// documentation for each of those opcodes for more details. // documentation for each of those opcodes for more details.
// //
// Stack transformation: signature pubkey] -> [... bool] -> [...] // Stack transformation: [... signature pubkey] -> [... bool] -> [...]
func opcodeCheckSigVerify(op *parsedOpcode, vm *Engine) error { func opcodeCheckSigVerify(op *opcode, data []byte, vm *Engine) error {
err := opcodeCheckSig(op, vm) err := opcodeCheckSig(op, data, vm)
if err == nil { if err == nil {
err = abstractVerify(op, vm, ErrCheckSigVerify) err = abstractVerify(op, vm, ErrCheckSigVerify)
} }
@ -2267,7 +2012,7 @@ type parsedSigInfo struct {
// //
// Stack transformation: // Stack transformation:
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] // [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool]
func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { func opcodeCheckMultiSig(op *opcode, data []byte, vm *Engine) error {
numKeys, err := vm.dstack.PopInt() numKeys, err := vm.dstack.PopInt()
if err != nil { if err != nil {
return err return err
@ -2444,7 +2189,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
sigHashes = NewTxSigHashes(&vm.tx) sigHashes = NewTxSigHashes(&vm.tx)
} }
hash, err = calcWitnessSignatureHash(script, sigHashes, hashType, hash, err = calcWitnessSignatureHashRaw(script, sigHashes, hashType,
&vm.tx, vm.txIdx, vm.inputAmount) &vm.tx, vm.txIdx, vm.inputAmount)
if err != nil { if err != nil {
return err return err
@ -2493,8 +2238,8 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
// //
// Stack transformation: // Stack transformation:
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...] // [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...]
func opcodeCheckMultiSigVerify(op *parsedOpcode, vm *Engine) error { func opcodeCheckMultiSigVerify(op *opcode, data []byte, vm *Engine) error {
err := opcodeCheckMultiSig(op, vm) err := opcodeCheckMultiSig(op, data, vm)
if err == nil { if err == nil {
err = abstractVerify(op, vm, ErrCheckMultiSigVerify) err = abstractVerify(op, vm, ErrCheckMultiSigVerify)
} }

View file

@ -23,8 +23,8 @@ func TestOpcodeDisabled(t *testing.T) {
OP_LSHIFT, OP_RSHIFT, OP_LSHIFT, OP_RSHIFT,
} }
for _, opcodeVal := range tests { for _, opcodeVal := range tests {
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: nil} op := &opcodeArray[opcodeVal]
err := opcodeDisabled(&pop, nil) err := opcodeDisabled(op, nil, nil)
if !IsErrorCode(err, ErrDisabledOpcode) { if !IsErrorCode(err, ErrDisabledOpcode) {
t.Errorf("opcodeDisabled: unexpected error - got %v, "+ t.Errorf("opcodeDisabled: unexpected error - got %v, "+
"want %v", err, ErrDisabledOpcode) "want %v", err, ErrDisabledOpcode)
@ -127,8 +127,9 @@ func TestOpcodeDisasm(t *testing.T) {
expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
} }
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: data} var buf strings.Builder
gotStr := pop.print(true) disasmOpcode(&buf, &opcodeArray[opcodeVal], data, true)
gotStr := buf.String()
if gotStr != expectedStr { if gotStr != expectedStr {
t.Errorf("pop.print (opcode %x): Unexpected disasm "+ t.Errorf("pop.print (opcode %x): Unexpected disasm "+
"string - got %v, want %v", opcodeVal, gotStr, "string - got %v, want %v", opcodeVal, gotStr,
@ -193,8 +194,9 @@ func TestOpcodeDisasm(t *testing.T) {
expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
} }
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: data} var buf strings.Builder
gotStr := pop.print(false) disasmOpcode(&buf, &opcodeArray[opcodeVal], data, false)
gotStr := buf.String()
if gotStr != expectedStr { if gotStr != expectedStr {
t.Errorf("pop.print (opcode %x): Unexpected disasm "+ t.Errorf("pop.print (opcode %x): Unexpected disasm "+
"string - got %v, want %v", opcodeVal, gotStr, "string - got %v, want %v", opcodeVal, gotStr,

View file

@ -211,11 +211,12 @@ func computeNonWitnessPkScript(sigScript []byte) (PkScript, error) {
// The redeem script will always be the last data push of the // The redeem script will always be the last data push of the
// signature script, so we'll parse the script into opcodes to // signature script, so we'll parse the script into opcodes to
// obtain it. // obtain it.
parsedOpcodes, err := parseScript(sigScript) const scriptVersion = 0
err := checkScriptParses(scriptVersion, sigScript)
if err != nil { if err != nil {
return PkScript{}, err return PkScript{}, err
} }
redeemScript := parsedOpcodes[len(parsedOpcodes)-1].data redeemScript := finalOpcodeData(scriptVersion, sigScript)
scriptHash := hash160(redeemScript) scriptHash := hash160(redeemScript)
script, err := payToScriptHashScript(scriptHash) script, err := payToScriptHashScript(scriptHash)

View file

@ -836,6 +836,7 @@ func TestCalcSignatureHash(t *testing.T) {
err) err)
} }
const scriptVersion = 0
for i, test := range tests { for i, test := range tests {
if i == 0 { if i == 0 {
// Skip first line -- contains comments only. // Skip first line -- contains comments only.
@ -855,16 +856,20 @@ func TestCalcSignatureHash(t *testing.T) {
} }
subScript, _ := hex.DecodeString(test[1].(string)) subScript, _ := hex.DecodeString(test[1].(string))
parsedScript, err := parseScript(subScript) if err := checkScriptParses(scriptVersion, subScript); err != nil {
if err != nil {
t.Errorf("TestCalcSignatureHash failed test #%d: "+ t.Errorf("TestCalcSignatureHash failed test #%d: "+
"Failed to parse sub-script: %v", i, err) "Failed to parse sub-script: %v", i, err)
continue continue
} }
hashType := SigHashType(testVecF64ToUint32(test[3].(float64))) hashType := SigHashType(testVecF64ToUint32(test[3].(float64)))
hash := calcSignatureHash(parsedScript, hashType, &tx, hash, err := CalcSignatureHash(subScript, hashType, &tx,
int(test[2].(float64))) 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)) expectedHash, _ := chainhash.NewHashFromStr(test[4].(string))
if !bytes.Equal(hash, expectedHash[:]) { if !bytes.Equal(hash, expectedHash[:]) {

View file

@ -1,4 +1,5 @@
// Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -8,6 +9,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
@ -44,66 +46,48 @@ const (
// isSmallInt returns whether or not the opcode is considered a small integer, // isSmallInt returns whether or not the opcode is considered a small integer,
// which is an OP_0, or OP_1 through OP_16. // which is an OP_0, or OP_1 through OP_16.
func isSmallInt(op *opcode) bool { //
if op.value == OP_0 || (op.value >= OP_1 && op.value <= OP_16) { // NOTE: This function is only valid for version 0 opcodes. Since the function
return true // does not accept a script version, the results are undefined for other script
} // versions.
return false func isSmallInt(op byte) bool {
return op == OP_0 || (op >= OP_1 && op <= OP_16)
} }
// isScriptHash returns true if the script passed is a pay-to-script-hash // IsPayToPubKey returns true if the script is in the standard pay-to-pubkey
// transaction, false otherwise. // (P2PK) format, false otherwise.
func isScriptHash(pops []parsedOpcode) bool { func IsPayToPubKey(script []byte) bool {
return len(pops) == 3 && return isPubKeyScript(script)
pops[0].opcode.value == OP_HASH160 && }
pops[1].opcode.value == OP_DATA_20 &&
pops[2].opcode.value == OP_EQUAL // IsPayToPubKeyHash returns true if the script is in the standard
// pay-to-pubkey-hash (P2PKH) format, false otherwise.
func IsPayToPubKeyHash(script []byte) bool {
return isPubKeyHashScript(script)
} }
// IsPayToScriptHash returns true if the script is in the standard // IsPayToScriptHash returns true if the script is in the standard
// pay-to-script-hash (P2SH) format, false otherwise. // pay-to-script-hash (P2SH) format, false otherwise.
//
// WARNING: This function always treats the passed script as version 0. Great
// care must be taken if introducing a new script version because it is used in
// consensus which, unfortunately as of the time of this writing, does not check
// script versions before determining if the script is a P2SH which means nodes
// on existing rules will analyze new version scripts as if they were version 0.
func IsPayToScriptHash(script []byte) bool { func IsPayToScriptHash(script []byte) bool {
pops, err := parseScript(script) return isScriptHashScript(script)
if err != nil {
return false
}
return isScriptHash(pops)
}
// isWitnessScriptHash returns true if the passed script is a
// pay-to-witness-script-hash transaction, false otherwise.
func isWitnessScriptHash(pops []parsedOpcode) bool {
return len(pops) == 2 &&
pops[0].opcode.value == OP_0 &&
pops[1].opcode.value == OP_DATA_32
} }
// IsPayToWitnessScriptHash returns true if the is in the standard // IsPayToWitnessScriptHash returns true if the is in the standard
// pay-to-witness-script-hash (P2WSH) format, false otherwise. // pay-to-witness-script-hash (P2WSH) format, false otherwise.
func IsPayToWitnessScriptHash(script []byte) bool { func IsPayToWitnessScriptHash(script []byte) bool {
pops, err := parseScript(script) return isWitnessScriptHashScript(script)
if err != nil {
return false
}
return isWitnessScriptHash(pops)
} }
// IsPayToWitnessPubKeyHash returns true if the is in the standard // IsPayToWitnessPubKeyHash returns true if the is in the standard
// pay-to-witness-pubkey-hash (P2WKH) format, false otherwise. // pay-to-witness-pubkey-hash (P2WKH) format, false otherwise.
func IsPayToWitnessPubKeyHash(script []byte) bool { func IsPayToWitnessPubKeyHash(script []byte) bool {
pops, err := parseScript(script) return isWitnessPubKeyHashScript(script)
if err != nil {
return false
}
return isWitnessPubKeyHash(pops)
}
// isWitnessPubKeyHash returns true if the passed script is a
// pay-to-witness-pubkey-hash, and false otherwise.
func isWitnessPubKeyHash(pops []parsedOpcode) bool {
return len(pops) == 2 &&
pops[0].opcode.value == OP_0 &&
pops[1].opcode.value == OP_DATA_20
} }
// IsWitnessProgram returns true if the passed script is a valid witness // IsWitnessProgram returns true if the passed script is a valid witness
@ -111,163 +95,52 @@ func isWitnessPubKeyHash(pops []parsedOpcode) bool {
// witness program must be a small integer (from 0-16), followed by 2-40 bytes // witness program must be a small integer (from 0-16), followed by 2-40 bytes
// of pushed data. // of pushed data.
func IsWitnessProgram(script []byte) bool { func IsWitnessProgram(script []byte) bool {
// The length of the script must be between 4 and 42 bytes. The return isWitnessProgramScript(script)
// smallest program is the witness version, followed by a data push of
// 2 bytes. The largest allowed witness program has a data push of
// 40-bytes.
if len(script) < 4 || len(script) > 42 {
return false
} }
pops, err := parseScript(script) // IsNullData returns true if the passed script is a null data script, false
if err != nil { // otherwise.
return false func IsNullData(script []byte) bool {
} const scriptVersion = 0
return isNullDataScript(scriptVersion, script)
return isWitnessProgram(pops)
}
// isWitnessProgram returns true if the passed script is a witness program, and
// false otherwise. A witness program MUST adhere to the following constraints:
// there must be exactly two pops (program version and the program itself), the
// first opcode MUST be a small integer (0-16), the push data MUST be
// canonical, and finally the size of the push data must be between 2 and 40
// bytes.
func isWitnessProgram(pops []parsedOpcode) bool {
return len(pops) == 2 &&
isSmallInt(pops[0].opcode) &&
canonicalPush(pops[1]) &&
(len(pops[1].data) >= 2 && len(pops[1].data) <= 40)
} }
// ExtractWitnessProgramInfo attempts to extract the witness program version, // ExtractWitnessProgramInfo attempts to extract the witness program version,
// as well as the witness program itself from the passed script. // as well as the witness program itself from the passed script.
func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) { func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) {
pops, err := parseScript(script)
if err != nil {
return 0, nil, err
}
// If at this point, the scripts doesn't resemble a witness program, // If at this point, the scripts doesn't resemble a witness program,
// then we'll exit early as there isn't a valid version or program to // then we'll exit early as there isn't a valid version or program to
// extract. // extract.
if !isWitnessProgram(pops) { version, program, valid := extractWitnessProgramInfo(script)
if !valid {
return 0, nil, fmt.Errorf("script is not a witness program, " + return 0, nil, fmt.Errorf("script is not a witness program, " +
"unable to extract version or witness program") "unable to extract version or witness program")
} }
witnessVersion := asSmallInt(pops[0].opcode) return version, program, nil
witnessProgram := pops[1].data
return witnessVersion, witnessProgram, nil
} }
// isPushOnly returns true if the script only pushes data, false otherwise. // IsPushOnlyScript returns whether or not the passed script only pushes data
func isPushOnly(pops []parsedOpcode) bool { // according to the consensus definition of pushing data.
// NOTE: This function does NOT verify opcodes directly since it is
// internal and is only called with parsed opcodes for scripts that did
// not have any parse errors. Thus, consensus is properly maintained.
for _, pop := range pops {
// All opcodes up to OP_16 are data push instructions.
// NOTE: This does consider OP_RESERVED to be a data push
// instruction, but execution of OP_RESERVED will fail anyways
// and matches the behavior required by consensus.
if pop.opcode.value > OP_16 {
return false
}
}
return true
}
// IsPushOnlyScript returns whether or not the passed script only pushes data.
// //
// False will be returned when the script does not parse. // WARNING: This function always treats the passed script as version 0. Great
// care must be taken if introducing a new script version because it is used in
// consensus which, unfortunately as of the time of this writing, does not check
// script versions before checking if it is a push only script which means nodes
// on existing rules will treat new version scripts as if they were version 0.
func IsPushOnlyScript(script []byte) bool { func IsPushOnlyScript(script []byte) bool {
pops, err := parseScript(script) const scriptVersion = 0
if err != nil { tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
// All opcodes up to OP_16 are data push instructions.
// NOTE: This does consider OP_RESERVED to be a data push instruction,
// but execution of OP_RESERVED will fail anyway and matches the
// behavior required by consensus.
if tokenizer.Opcode() > OP_16 {
return false return false
} }
return isPushOnly(pops)
} }
return tokenizer.Err() == nil
// parseScriptTemplate is the same as parseScript but allows the passing of the
// template list for testing purposes. When there are parse errors, it returns
// the list of parsed opcodes up to the point of failure along with the error.
func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode, error) {
retScript := make([]parsedOpcode, 0, len(script))
var err error
for i := 0; i < len(script); {
instr := script[i]
op := &opcodes[instr]
pop := parsedOpcode{opcode: op}
i, err = pop.checkParseableInScript(script, i)
if err != nil {
return retScript, err
}
retScript = append(retScript, pop)
}
return retScript, nil
}
// checkScriptTemplateParseable is the same as parseScriptTemplate but does not
// return the list of opcodes up until the point of failure so that this can be
// used in functions which do not necessarily have a need for the failed list of
// opcodes, such as IsUnspendable.
//
// This function returns a pointer to a byte. This byte is nil if the parsing
// has an error, or if the script length is zero. If the script length is not
// zero and parsing succeeds, then the first opcode parsed will be returned.
//
// Not returning the full opcode list up until failure also has the benefit of
// reducing GC pressure, as the list would get immediately thrown away.
func checkScriptTemplateParseable(script []byte, opcodes *[256]opcode) (*byte, error) {
var err error
// A script of length zero is an unspendable script but it is parseable.
var firstOpcode byte
var numParsedInstr uint = 0
for i := 0; i < len(script); {
instr := script[i]
op := &opcodes[instr]
pop := parsedOpcode{opcode: op}
i, err = pop.checkParseableInScript(script, i)
if err != nil {
return nil, err
}
// if this is a op_return then it is unspendable so we set the first
// parsed instruction in case it's an op_return
if numParsedInstr == 0 {
firstOpcode = pop.opcode.value
}
numParsedInstr++
}
return &firstOpcode, nil
}
// parseScript preparses the script in bytes into a list of parsedOpcodes while
// applying a number of sanity checks.
func parseScript(script []byte) ([]parsedOpcode, error) {
return parseScriptTemplate(script, &opcodeArray)
}
// unparseScript reversed the action of parseScript and returns the
// parsedOpcodes as a list of bytes
func unparseScript(pops []parsedOpcode) ([]byte, error) {
script := make([]byte, 0, len(pops))
for _, pop := range pops {
b, err := pop.bytes()
if err != nil {
return nil, err
}
script = append(script, b...)
}
return script, nil
} }
// DisasmString formats a disassembled script for one line printing. When the // DisasmString formats a disassembled script for one line printing. When the
@ -275,41 +148,78 @@ func unparseScript(pops []parsedOpcode) ([]byte, error) {
// script up to the point the failure occurred along with the string '[error]' // script up to the point the failure occurred along with the string '[error]'
// appended. In addition, the reason the script failed to parse is returned // appended. In addition, the reason the script failed to parse is returned
// if the caller wants more information about the failure. // if the caller wants more information about the failure.
func DisasmString(buf []byte) (string, error) { //
var disbuf bytes.Buffer // NOTE: This function is only valid for version 0 scripts. Since the function
opcodes, err := parseScript(buf) // does not accept a script version, the results are undefined for other script
for _, pop := range opcodes { // versions.
disbuf.WriteString(pop.print(true)) func DisasmString(script []byte) (string, error) {
const scriptVersion = 0
var disbuf strings.Builder
tokenizer := MakeScriptTokenizer(scriptVersion, script)
if tokenizer.Next() {
disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), true)
}
for tokenizer.Next() {
disbuf.WriteByte(' ')
disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), true)
}
if tokenizer.Err() != nil {
if tokenizer.ByteIndex() != 0 {
disbuf.WriteByte(' ') disbuf.WriteByte(' ')
} }
if disbuf.Len() > 0 {
disbuf.Truncate(disbuf.Len() - 1)
}
if err != nil {
disbuf.WriteString("[error]") disbuf.WriteString("[error]")
} }
return disbuf.String(), err return disbuf.String(), tokenizer.Err()
} }
// removeOpcode will remove any opcode matching ``opcode'' from the opcode // removeOpcodeRaw will return the script after removing any opcodes that match
// stream in pkscript // `opcode`. If the opcode does not appear in script, the original script will
func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode { // be returned unmodified. Otherwise, a new script will be allocated to contain
retScript := make([]parsedOpcode, 0, len(pkscript)) // the filtered script. This metehod assumes that the script parses
for _, pop := range pkscript { // successfully.
if pop.opcode.value != opcode { //
retScript = append(retScript, pop) // NOTE: This function is only valid for version 0 scripts. Since the function
} // does not accept a script version, the results are undefined for other script
} // versions.
return retScript func removeOpcodeRaw(script []byte, opcode byte) []byte {
// Avoid work when possible.
if len(script) == 0 {
return script
} }
// canonicalPush returns true if the object is either not a push instruction const scriptVersion = 0
// or the push instruction contained wherein is matches the canonical form var result []byte
// or using the smallest instruction to do the job. False otherwise. var prevOffset int32
func canonicalPush(pop parsedOpcode) bool {
opcode := pop.opcode.value tokenizer := MakeScriptTokenizer(scriptVersion, script)
data := pop.data for tokenizer.Next() {
dataLen := len(pop.data) if tokenizer.Opcode() == opcode {
if result == nil {
result = make([]byte, 0, len(script))
result = append(result, script[:prevOffset]...)
}
} else if result != nil {
result = append(result, script[prevOffset:tokenizer.ByteIndex()]...)
}
prevOffset = tokenizer.ByteIndex()
}
if result == nil {
return script
}
return result
}
// isCanonicalPush returns true if the opcode is either not a push instruction
// or the data associated with the push instruction uses the smallest
// instruction to do the job. False otherwise.
//
// For example, it is possible to push a value of 1 to the stack as "OP_1",
// "OP_DATA_1 0x01", "OP_PUSHDATA1 0x01 0x01", and others, however, the first
// only takes a single byte, while the rest take more. Only the first is
// considered canonical.
func isCanonicalPush(opcode byte, data []byte) bool {
dataLen := len(data)
if opcode > OP_16 { if opcode > OP_16 {
return true return true
} }
@ -329,17 +239,57 @@ func canonicalPush(pop parsedOpcode) bool {
return true return true
} }
// removeOpcodeByData will return the script minus any opcodes that would push // removeOpcodeByData will return the script minus any opcodes that perform a
// the passed data to the stack. // canonical push of data that contains the passed data to remove. This
func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode { // function assumes it is provided a version 0 script as any future version of
retScript := make([]parsedOpcode, 0, len(pkscript)) // script should avoid this functionality since it is unncessary due to the
for _, pop := range pkscript { // signature scripts not being part of the witness-free transaction hash.
if !canonicalPush(pop) || !bytes.Contains(pop.data, data) { //
retScript = append(retScript, pop) // WARNING: This will return the passed script unmodified unless a modification
// is necessary in which case the modified script is returned. This implies
// callers may NOT rely on being able to safely mutate either the passed or
// returned script without potentially modifying the same data.
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func removeOpcodeByData(script []byte, dataToRemove []byte) []byte {
// Avoid work when possible.
if len(script) == 0 || len(dataToRemove) == 0 {
return script
} }
}
return retScript
// Parse through the script looking for a canonical data push that contains
// the data to remove.
const scriptVersion = 0
var result []byte
var prevOffset int32
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
// In practice, the script will basically never actually contain the
// data since this function is only used during signature verification
// to remove the signature itself which would require some incredibly
// non-standard code to create.
//
// Thus, as an optimization, avoid allocating a new script unless there
// is actually a match that needs to be removed.
op, data := tokenizer.Opcode(), tokenizer.Data()
if isCanonicalPush(op, data) && bytes.Contains(data, dataToRemove) {
if result == nil {
fullPushLen := tokenizer.ByteIndex() - prevOffset
result = make([]byte, 0, int32(len(script))-fullPushLen)
result = append(result, script[0:prevOffset]...)
}
} else if result != nil {
result = append(result, script[prevOffset:tokenizer.ByteIndex()]...)
}
prevOffset = tokenizer.ByteIndex()
}
if result == nil {
result = script
}
return result
} }
// calcHashPrevOuts calculates a single hash of all the previous outputs // calcHashPrevOuts calculates a single hash of all the previous outputs
@ -396,7 +346,7 @@ func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
return chainhash.DoubleHashH(b.Bytes()) return chainhash.DoubleHashH(b.Bytes())
} }
// calcWitnessSignatureHash computes the sighash digest of a transaction's // calcWitnessSignatureHashRaw computes the sighash digest of a transaction's
// segwit input using the new, optimized digest calculation algorithm defined // segwit input using the new, optimized digest calculation algorithm defined
// in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki. // in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki.
// This function makes use of pre-calculated sighash fragments stored within // This function makes use of pre-calculated sighash fragments stored within
@ -407,7 +357,7 @@ func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
// being spent, in addition to the final transaction fee. In the case the // being spent, in addition to the final transaction fee. In the case the
// wallet if fed an invalid input amount, the real sighash will differ causing // wallet if fed an invalid input amount, the real sighash will differ causing
// the produced signature to be invalid. // the produced signature to be invalid.
func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes, func calcWitnessSignatureHashRaw(scriptSig []byte, sigHashes *TxSigHashes,
hashType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { hashType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) {
// As a sanity check, ensure the passed input index for the transaction // As a sanity check, ensure the passed input index for the transaction
@ -457,7 +407,7 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes,
binary.LittleEndian.PutUint32(bIndex[:], txIn.PreviousOutPoint.Index) binary.LittleEndian.PutUint32(bIndex[:], txIn.PreviousOutPoint.Index)
sigHash.Write(bIndex[:]) sigHash.Write(bIndex[:])
if isWitnessPubKeyHash(subScript) { if isWitnessPubKeyHashScript(scriptSig) {
// The script code for a p2wkh is a length prefix varint for // The script code for a p2wkh is a length prefix varint for
// the next 25 bytes, followed by a re-creation of the original // the next 25 bytes, followed by a re-creation of the original
// p2pkh pk script. // p2pkh pk script.
@ -465,15 +415,14 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes,
sigHash.Write([]byte{OP_DUP}) sigHash.Write([]byte{OP_DUP})
sigHash.Write([]byte{OP_HASH160}) sigHash.Write([]byte{OP_HASH160})
sigHash.Write([]byte{OP_DATA_20}) sigHash.Write([]byte{OP_DATA_20})
sigHash.Write(subScript[1].data) sigHash.Write(extractWitnessPubKeyHash(scriptSig))
sigHash.Write([]byte{OP_EQUALVERIFY}) sigHash.Write([]byte{OP_EQUALVERIFY})
sigHash.Write([]byte{OP_CHECKSIG}) sigHash.Write([]byte{OP_CHECKSIG})
} else { } else {
// For p2wsh outputs, and future outputs, the script code is // For p2wsh outputs, and future outputs, the script code is
// the original script, with all code separators removed, // the original script, with all code separators removed,
// serialized with a var int length prefix. // serialized with a var int length prefix.
rawScript, _ := unparseScript(subScript) wire.WriteVarBytes(&sigHash, 0, scriptSig)
wire.WriteVarBytes(&sigHash, 0, rawScript)
} }
// Next, add the input amount, and sequence number of the input being // Next, add the input amount, and sequence number of the input being
@ -517,13 +466,12 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes,
func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType, func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType,
tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { tx *wire.MsgTx, idx int, amt int64) ([]byte, error) {
parsedScript, err := parseScript(script) const scriptVersion = 0
if err != nil { if err := checkScriptParses(scriptVersion, script); err != nil {
return nil, fmt.Errorf("cannot parse output script: %v", err) return nil, err
} }
return calcWitnessSignatureHash(parsedScript, sigHashes, hType, tx, idx, return calcWitnessSignatureHashRaw(script, sigHashes, hType, tx, idx, amt)
amt)
} }
// shallowCopyTx creates a shallow copy of the transaction for use when // shallowCopyTx creates a shallow copy of the transaction for use when
@ -557,18 +505,22 @@ func shallowCopyTx(tx *wire.MsgTx) wire.MsgTx {
// CalcSignatureHash will, given a script and hash type for the current script // CalcSignatureHash will, given a script and hash type for the current script
// engine instance, calculate the signature hash to be used for signing and // engine instance, calculate the signature hash to be used for signing and
// verification. // verification.
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx int) ([]byte, error) { func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx int) ([]byte, error) {
parsedScript, err := parseScript(script) const scriptVersion = 0
if err != nil { if err := checkScriptParses(scriptVersion, script); err != nil {
return nil, fmt.Errorf("cannot parse output script: %v", err) return nil, err
}
return calcSignatureHash(parsedScript, hashType, tx, idx), nil
} }
// calcSignatureHash will, given a script and hash type for the current script return calcSignatureHash(script, hashType, tx, idx), nil
// engine instance, calculate the signature hash to be used for signing and }
// verification.
func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.MsgTx, idx int) []byte { // calcSignatureHash computes the signature hash for the specified input of the
// target transaction observing the desired signature hash type.
func calcSignatureHash(sigScript []byte, hashType SigHashType, tx *wire.MsgTx, idx int) []byte {
// The SigHashSingle signature type signs only the corresponding input // The SigHashSingle signature type signs only the corresponding input
// and output (the output with the same index number as the input). // and output (the output with the same index number as the input).
// //
@ -596,16 +548,13 @@ func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.Msg
} }
// Remove all instances of OP_CODESEPARATOR from the script. // Remove all instances of OP_CODESEPARATOR from the script.
script = removeOpcode(script, OP_CODESEPARATOR) sigScript = removeOpcodeRaw(sigScript, OP_CODESEPARATOR)
// Make a shallow copy of the transaction, zeroing out the script for // Make a shallow copy of the transaction, zeroing out the script for
// all inputs that are not currently being processed. // all inputs that are not currently being processed.
txCopy := shallowCopyTx(tx) txCopy := shallowCopyTx(tx)
for i := range txCopy.TxIn { for i := range txCopy.TxIn {
if i == idx { if i == idx {
// UnparseScript cannot fail here because removeOpcode
// above only returns a valid script.
sigScript, _ := unparseScript(script)
txCopy.TxIn[idx].SignatureScript = sigScript txCopy.TxIn[idx].SignatureScript = sigScript
} else { } else {
txCopy.TxIn[i].SignatureScript = nil txCopy.TxIn[i].SignatureScript = nil
@ -662,57 +611,98 @@ func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.Msg
// asSmallInt returns the passed opcode, which must be true according to // asSmallInt returns the passed opcode, which must be true according to
// isSmallInt(), as an integer. // isSmallInt(), as an integer.
func asSmallInt(op *opcode) int { func asSmallInt(op byte) int {
if op.value == OP_0 { if op == OP_0 {
return 0 return 0
} }
return int(op.value - (OP_1 - 1)) return int(op - (OP_1 - 1))
} }
// getSigOpCount is the implementation function for counting the number of // countSigOpsV0 returns the number of signature operations in the provided
// signature operations in the script provided by pops. If precise mode is // script up to the point of the first parse failure or the entire script when
// requested then we attempt to count the number of operations for a multisig // there are no parse failures. The precise flag attempts to accurately count
// op. Otherwise we use the maximum. // the number of operations for a multisig operation versus using the maximum
func getSigOpCount(pops []parsedOpcode, precise bool) int { // allowed.
nSigs := 0 //
for i, pop := range pops { // WARNING: This function always treats the passed script as version 0. Great
switch pop.opcode.value { // care must be taken if introducing a new script version because it is used in
case OP_CHECKSIG: // consensus which, unfortunately as of the time of this writing, does not check
fallthrough // script versions before counting their signature operations which means nodes
case OP_CHECKSIGVERIFY: // on existing rules will count new version scripts as if they were version 0.
nSigs++ func countSigOpsV0(script []byte, precise bool) int {
case OP_CHECKMULTISIG: const scriptVersion = 0
fallthrough
case OP_CHECKMULTISIGVERIFY: numSigOps := 0
// If we are being precise then look for familiar tokenizer := MakeScriptTokenizer(scriptVersion, script)
// patterns for multisig, for now all we recognize is prevOp := byte(OP_INVALIDOPCODE)
// OP_1 - OP_16 to signify the number of pubkeys. for tokenizer.Next() {
// Otherwise, we use the max of 20. switch tokenizer.Opcode() {
if precise && i > 0 && case OP_CHECKSIG, OP_CHECKSIGVERIFY:
pops[i-1].opcode.value >= OP_1 && numSigOps++
pops[i-1].opcode.value <= OP_16 {
nSigs += asSmallInt(pops[i-1].opcode) case OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY:
// Note that OP_0 is treated as the max number of sigops here in
// precise mode despite it being a valid small integer in order to
// highly discourage multisigs with zero pubkeys.
//
// Also, even though this is referred to as "precise" counting, it's
// not really precise at all due to the small int opcodes only
// covering 1 through 16 pubkeys, which means this will count any
// more than that value (e.g. 17, 18 19) as the maximum number of
// allowed pubkeys. This is, unfortunately, now part of
// the Bitcion consensus rules, due to historical
// reasons. This could be made more correct with a new
// script version, however, ideally all multisignaure
// operations in new script versions should move to
// aggregated schemes such as Schnorr instead.
if precise && prevOp >= OP_1 && prevOp <= OP_16 {
numSigOps += asSmallInt(prevOp)
} else { } else {
nSigs += MaxPubKeysPerMultiSig numSigOps += MaxPubKeysPerMultiSig
} }
default: default:
// Not a sigop. // Not a sigop.
} }
prevOp = tokenizer.Opcode()
} }
return nSigs return numSigOps
} }
// GetSigOpCount provides a quick count of the number of signature operations // GetSigOpCount provides a quick count of the number of signature operations
// in a script. a CHECKSIG operations counts for 1, and a CHECK_MULTISIG for 20. // in a script. a CHECKSIG operations counts for 1, and a CHECK_MULTISIG for 20.
// If the script fails to parse, then the count up to the point of failure is // If the script fails to parse, then the count up to the point of failure is
// returned. // returned.
//
// WARNING: This function always treats the passed script as version 0. Great
// care must be taken if introducing a new script version because it is used in
// consensus which, unfortunately as of the time of this writing, does not check
// script versions before counting their signature operations which means nodes
// on existing rules will count new version scripts as if they were version 0.
func GetSigOpCount(script []byte) int { func GetSigOpCount(script []byte) int {
// Don't check error since parseScript returns the parsed-up-to-error return countSigOpsV0(script, false)
// list of pops. }
pops, _ := parseScript(script)
return getSigOpCount(pops, false) // finalOpcodeData returns the data associated with the final opcode in the
// script. It will return nil if the script fails to parse.
func finalOpcodeData(scriptVersion uint16, script []byte) []byte {
// Avoid unnecessary work.
if len(script) == 0 {
return nil
}
var data []byte
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
data = tokenizer.Data()
}
if tokenizer.Err() != nil {
return nil
}
return data
} }
// GetPreciseSigOpCount returns the number of signature operations in // GetPreciseSigOpCount returns the number of signature operations in
@ -720,44 +710,44 @@ func GetSigOpCount(script []byte) int {
// Pay-To-Script-Hash script in order to find the precise number of signature // Pay-To-Script-Hash script in order to find the precise number of signature
// operations in the transaction. If the script fails to parse, then the count // operations in the transaction. If the script fails to parse, then the count
// up to the point of failure is returned. // up to the point of failure is returned.
func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int { //
// Don't check error since parseScript returns the parsed-up-to-error // WARNING: This function always treats the passed script as version 0. Great
// list of pops. // care must be taken if introducing a new script version because it is used in
pops, _ := parseScript(scriptPubKey) // consensus which, unfortunately as of the time of this writing, does not check
// script versions before counting their signature operations which means nodes
// on existing rules will count new version scripts as if they were version 0.
//
// The third parameter is DEPRECATED and is unused.
func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, _ bool) int {
const scriptVersion = 0
// Treat non P2SH transactions as normal. // Treat non P2SH transactions as normal. Note that signature operation
if !(bip16 && isScriptHash(pops)) { // counting includes all operations up to the first parse failure.
return getSigOpCount(pops, true) if !isScriptHashScript(scriptPubKey) {
} return countSigOpsV0(scriptPubKey, true)
// The public key script is a pay-to-script-hash, so parse the signature
// script to get the final item. Scripts that fail to fully parse count
// as 0 signature operations.
sigPops, err := parseScript(scriptSig)
if err != nil {
return 0
} }
// The signature script must only push data to the stack for P2SH to be // The signature script must only push data to the stack for P2SH to be
// a valid pair, so the signature operation count is 0 when that is not // a valid pair, so the signature operation count is 0 when that is not
// the case. // the case.
if !isPushOnly(sigPops) || len(sigPops) == 0 { if len(scriptSig) == 0 || !IsPushOnlyScript(scriptSig) {
return 0 return 0
} }
// The P2SH script is the last item the signature script pushes to the // The P2SH script is the last item the signature script pushes to the
// stack. When the script is empty, there are no signature operations. // stack. When the script is empty, there are no signature operations.
shScript := sigPops[len(sigPops)-1].data //
if len(shScript) == 0 { // Notice that signature scripts that fail to fully parse count as 0
// signature operations unlike public key and redeem scripts.
redeemScript := finalOpcodeData(scriptVersion, scriptSig)
if len(redeemScript) == 0 {
return 0 return 0
} }
// Parse the P2SH script and don't check the error since parseScript // Return the more precise sigops count for the redeem script. Note that
// returns the parsed-up-to-error list of pops and the consensus rules // signature operation counting includes all operations up to the first
// dictate signature operations are counted up to the first parse // parse failure.
// failure. return countSigOpsV0(redeemScript, true)
shPops, _ := parseScript(shScript)
return getSigOpCount(shPops, true)
} }
// GetWitnessSigOpCount returns the number of signature operations generated by // GetWitnessSigOpCount returns the number of signature operations generated by
@ -769,19 +759,15 @@ func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int {
func GetWitnessSigOpCount(sigScript, pkScript []byte, witness wire.TxWitness) int { func GetWitnessSigOpCount(sigScript, pkScript []byte, witness wire.TxWitness) int {
// If this is a regular witness program, then we can proceed directly // If this is a regular witness program, then we can proceed directly
// to counting its signature operations without any further processing. // to counting its signature operations without any further processing.
if IsWitnessProgram(pkScript) { if isWitnessProgramScript(pkScript) {
return getWitnessSigOps(pkScript, witness) return getWitnessSigOps(pkScript, witness)
} }
// Next, we'll check the sigScript to see if this is a nested p2sh // Next, we'll check the sigScript to see if this is a nested p2sh
// witness program. This is a case wherein the sigScript is actually a // witness program. This is a case wherein the sigScript is actually a
// datapush of a p2wsh witness program. // datapush of a p2wsh witness program.
sigPops, err := parseScript(sigScript) if isScriptHashScript(pkScript) && IsPushOnlyScript(sigScript) &&
if err != nil { len(sigScript) > 0 && isWitnessProgramScript(sigScript[1:]) {
return 0
}
if IsPayToScriptHash(pkScript) && isPushOnly(sigPops) &&
IsWitnessProgram(sigScript[1:]) {
return getWitnessSigOps(sigScript[1:], witness) return getWitnessSigOps(sigScript[1:], witness)
} }
@ -811,26 +797,41 @@ func getWitnessSigOps(pkScript []byte, witness wire.TxWitness) int {
len(witness) > 0: len(witness) > 0:
witnessScript := witness[len(witness)-1] witnessScript := witness[len(witness)-1]
pops, _ := parseScript(witnessScript) return countSigOpsV0(witnessScript, true)
return getSigOpCount(pops, true)
} }
} }
return 0 return 0
} }
// checkScriptParses returns an error if the provided script fails to parse.
func checkScriptParses(scriptVersion uint16, script []byte) error {
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
// Nothing to do.
}
return tokenizer.Err()
}
// IsUnspendable returns whether the passed public key script is unspendable, or // IsUnspendable returns whether the passed public key script is unspendable, or
// guaranteed to fail at execution. This allows inputs to be pruned instantly // guaranteed to fail at execution. This allows inputs to be pruned instantly
// when entering the UTXO set. // when entering the UTXO set.
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func IsUnspendable(pkScript []byte) bool { func IsUnspendable(pkScript []byte) bool {
// Not provably unspendable // The script is unspendable if starts with OP_RETURN or is guaranteed
if len(pkScript) == 0 { // to fail at execution due to being larger than the max allowed script
return false // size.
} switch {
firstOpcode, err := checkScriptTemplateParseable(pkScript, &opcodeArray) case len(pkScript) > 0 && pkScript[0] == OP_RETURN:
if err != nil { return true
case len(pkScript) > MaxScriptSize:
return true return true
} }
return firstOpcode != nil && *firstOpcode == OP_RETURN // The script is unspendable if it is guaranteed to fail at execution.
const scriptVersion = 0
return checkScriptParses(scriptVersion, pkScript) != nil
} }

File diff suppressed because it is too large Load diff

View file

@ -12,9 +12,21 @@ const (
maxInt32 = 1<<31 - 1 maxInt32 = 1<<31 - 1
minInt32 = -1 << 31 minInt32 = -1 << 31
// defaultScriptNumLen is the default number of bytes // maxScriptNumLen is the maximum number of bytes data being interpreted
// data being interpreted as an integer may be. // as an integer may be for the majority of op codes.
defaultScriptNumLen = 4 maxScriptNumLen = 4
// cltvMaxScriptNumLen is the maximum number of bytes data being interpreted
// as an integer may be for by-time and by-height locks as interpreted by
// CHECKLOCKTIMEVERIFY.
//
// The value comes from the fact that the current transaction locktime
// is a uint32 resulting in a maximum locktime of 2^32-1 (the year
// 2106). However, scriptNums are signed and therefore a standard
// 4-byte scriptNum would only support up to a maximum of 2^31-1 (the
// year 2038). Thus, a 5-byte scriptNum is needed since it will support
// up to 2^39-1 which allows dates beyond the current locktime limit.
cltvMaxScriptNumLen = 5
) )
// scriptNum represents a numeric value used in the scripting engine with // scriptNum represents a numeric value used in the scripting engine with
@ -178,7 +190,7 @@ func (n scriptNum) Int32() int32 {
// before an ErrStackNumberTooBig is returned. This effectively limits the // before an ErrStackNumberTooBig is returned. This effectively limits the
// range of allowed values. // range of allowed values.
// WARNING: Great care should be taken if passing a value larger than // WARNING: Great care should be taken if passing a value larger than
// defaultScriptNumLen, which could lead to addition and multiplication // maxScriptNumLen, which could lead to addition and multiplication
// overflows. // overflows.
// //
// See the Bytes function documentation for example encodings. // See the Bytes function documentation for example encodings.

View file

@ -104,35 +104,35 @@ func TestMakeScriptNum(t *testing.T) {
err error err error
}{ }{
// Minimal encoding must reject negative 0. // Minimal encoding must reject negative 0.
{hexToBytes("80"), 0, defaultScriptNumLen, true, errMinimalData}, {hexToBytes("80"), 0, maxScriptNumLen, true, errMinimalData},
// Minimally encoded valid values with minimal encoding flag. // Minimally encoded valid values with minimal encoding flag.
// Should not error and return expected integral number. // Should not error and return expected integral number.
{nil, 0, defaultScriptNumLen, true, nil}, {nil, 0, maxScriptNumLen, true, nil},
{hexToBytes("01"), 1, defaultScriptNumLen, true, nil}, {hexToBytes("01"), 1, maxScriptNumLen, true, nil},
{hexToBytes("81"), -1, defaultScriptNumLen, true, nil}, {hexToBytes("81"), -1, maxScriptNumLen, true, nil},
{hexToBytes("7f"), 127, defaultScriptNumLen, true, nil}, {hexToBytes("7f"), 127, maxScriptNumLen, true, nil},
{hexToBytes("ff"), -127, defaultScriptNumLen, true, nil}, {hexToBytes("ff"), -127, maxScriptNumLen, true, nil},
{hexToBytes("8000"), 128, defaultScriptNumLen, true, nil}, {hexToBytes("8000"), 128, maxScriptNumLen, true, nil},
{hexToBytes("8080"), -128, defaultScriptNumLen, true, nil}, {hexToBytes("8080"), -128, maxScriptNumLen, true, nil},
{hexToBytes("8100"), 129, defaultScriptNumLen, true, nil}, {hexToBytes("8100"), 129, maxScriptNumLen, true, nil},
{hexToBytes("8180"), -129, defaultScriptNumLen, true, nil}, {hexToBytes("8180"), -129, maxScriptNumLen, true, nil},
{hexToBytes("0001"), 256, defaultScriptNumLen, true, nil}, {hexToBytes("0001"), 256, maxScriptNumLen, true, nil},
{hexToBytes("0081"), -256, defaultScriptNumLen, true, nil}, {hexToBytes("0081"), -256, maxScriptNumLen, true, nil},
{hexToBytes("ff7f"), 32767, defaultScriptNumLen, true, nil}, {hexToBytes("ff7f"), 32767, maxScriptNumLen, true, nil},
{hexToBytes("ffff"), -32767, defaultScriptNumLen, true, nil}, {hexToBytes("ffff"), -32767, maxScriptNumLen, true, nil},
{hexToBytes("008000"), 32768, defaultScriptNumLen, true, nil}, {hexToBytes("008000"), 32768, maxScriptNumLen, true, nil},
{hexToBytes("008080"), -32768, defaultScriptNumLen, true, nil}, {hexToBytes("008080"), -32768, maxScriptNumLen, true, nil},
{hexToBytes("ffff00"), 65535, defaultScriptNumLen, true, nil}, {hexToBytes("ffff00"), 65535, maxScriptNumLen, true, nil},
{hexToBytes("ffff80"), -65535, defaultScriptNumLen, true, nil}, {hexToBytes("ffff80"), -65535, maxScriptNumLen, true, nil},
{hexToBytes("000008"), 524288, defaultScriptNumLen, true, nil}, {hexToBytes("000008"), 524288, maxScriptNumLen, true, nil},
{hexToBytes("000088"), -524288, defaultScriptNumLen, true, nil}, {hexToBytes("000088"), -524288, maxScriptNumLen, true, nil},
{hexToBytes("000070"), 7340032, defaultScriptNumLen, true, nil}, {hexToBytes("000070"), 7340032, maxScriptNumLen, true, nil},
{hexToBytes("0000f0"), -7340032, defaultScriptNumLen, true, nil}, {hexToBytes("0000f0"), -7340032, maxScriptNumLen, true, nil},
{hexToBytes("00008000"), 8388608, defaultScriptNumLen, true, nil}, {hexToBytes("00008000"), 8388608, maxScriptNumLen, true, nil},
{hexToBytes("00008080"), -8388608, defaultScriptNumLen, true, nil}, {hexToBytes("00008080"), -8388608, maxScriptNumLen, true, nil},
{hexToBytes("ffffff7f"), 2147483647, defaultScriptNumLen, true, nil}, {hexToBytes("ffffff7f"), 2147483647, maxScriptNumLen, true, nil},
{hexToBytes("ffffffff"), -2147483647, defaultScriptNumLen, true, nil}, {hexToBytes("ffffffff"), -2147483647, maxScriptNumLen, true, nil},
{hexToBytes("ffffffff7f"), 549755813887, 5, true, nil}, {hexToBytes("ffffffff7f"), 549755813887, 5, true, nil},
{hexToBytes("ffffffffff"), -549755813887, 5, true, nil}, {hexToBytes("ffffffffff"), -549755813887, 5, true, nil},
{hexToBytes("ffffffffffffff7f"), 9223372036854775807, 8, true, nil}, {hexToBytes("ffffffffffffff7f"), 9223372036854775807, 8, true, nil},
@ -145,50 +145,50 @@ func TestMakeScriptNum(t *testing.T) {
// Minimally encoded values that are out of range for data that // Minimally encoded values that are out of range for data that
// is interpreted as script numbers with the minimal encoding // is interpreted as script numbers with the minimal encoding
// flag set. Should error and return 0. // flag set. Should error and return 0.
{hexToBytes("0000008000"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("0000008000"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("0000008080"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("0000008080"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("0000009000"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("0000009000"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("0000009080"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("0000009080"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("ffffffff00"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("ffffffff80"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("0000000001"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("0000000001"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("0000000081"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("0000000081"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("ffffffffffff00"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("ffffffffffff80"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("ffffffffffffff00"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("ffffffffffffff80"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffffff7f"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("ffffffffffffff7f"), 0, maxScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffffffff"), 0, defaultScriptNumLen, true, errNumTooBig}, {hexToBytes("ffffffffffffffff"), 0, maxScriptNumLen, true, errNumTooBig},
// Non-minimally encoded, but otherwise valid values with // Non-minimally encoded, but otherwise valid values with
// minimal encoding flag. Should error and return 0. // minimal encoding flag. Should error and return 0.
{hexToBytes("00"), 0, defaultScriptNumLen, true, errMinimalData}, // 0 {hexToBytes("00"), 0, maxScriptNumLen, true, errMinimalData}, // 0
{hexToBytes("0100"), 0, defaultScriptNumLen, true, errMinimalData}, // 1 {hexToBytes("0100"), 0, maxScriptNumLen, true, errMinimalData}, // 1
{hexToBytes("7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 127 {hexToBytes("7f00"), 0, maxScriptNumLen, true, errMinimalData}, // 127
{hexToBytes("800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 128 {hexToBytes("800000"), 0, maxScriptNumLen, true, errMinimalData}, // 128
{hexToBytes("810000"), 0, defaultScriptNumLen, true, errMinimalData}, // 129 {hexToBytes("810000"), 0, maxScriptNumLen, true, errMinimalData}, // 129
{hexToBytes("000100"), 0, defaultScriptNumLen, true, errMinimalData}, // 256 {hexToBytes("000100"), 0, maxScriptNumLen, true, errMinimalData}, // 256
{hexToBytes("ff7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 32767 {hexToBytes("ff7f00"), 0, maxScriptNumLen, true, errMinimalData}, // 32767
{hexToBytes("00800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 32768 {hexToBytes("00800000"), 0, maxScriptNumLen, true, errMinimalData}, // 32768
{hexToBytes("ffff0000"), 0, defaultScriptNumLen, true, errMinimalData}, // 65535 {hexToBytes("ffff0000"), 0, maxScriptNumLen, true, errMinimalData}, // 65535
{hexToBytes("00000800"), 0, defaultScriptNumLen, true, errMinimalData}, // 524288 {hexToBytes("00000800"), 0, maxScriptNumLen, true, errMinimalData}, // 524288
{hexToBytes("00007000"), 0, defaultScriptNumLen, true, errMinimalData}, // 7340032 {hexToBytes("00007000"), 0, maxScriptNumLen, true, errMinimalData}, // 7340032
{hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520 {hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520
// Non-minimally encoded, but otherwise valid values without // Non-minimally encoded, but otherwise valid values without
// minimal encoding flag. Should not error and return expected // minimal encoding flag. Should not error and return expected
// integral number. // integral number.
{hexToBytes("00"), 0, defaultScriptNumLen, false, nil}, {hexToBytes("00"), 0, maxScriptNumLen, false, nil},
{hexToBytes("0100"), 1, defaultScriptNumLen, false, nil}, {hexToBytes("0100"), 1, maxScriptNumLen, false, nil},
{hexToBytes("7f00"), 127, defaultScriptNumLen, false, nil}, {hexToBytes("7f00"), 127, maxScriptNumLen, false, nil},
{hexToBytes("800000"), 128, defaultScriptNumLen, false, nil}, {hexToBytes("800000"), 128, maxScriptNumLen, false, nil},
{hexToBytes("810000"), 129, defaultScriptNumLen, false, nil}, {hexToBytes("810000"), 129, maxScriptNumLen, false, nil},
{hexToBytes("000100"), 256, defaultScriptNumLen, false, nil}, {hexToBytes("000100"), 256, maxScriptNumLen, false, nil},
{hexToBytes("ff7f00"), 32767, defaultScriptNumLen, false, nil}, {hexToBytes("ff7f00"), 32767, maxScriptNumLen, false, nil},
{hexToBytes("00800000"), 32768, defaultScriptNumLen, false, nil}, {hexToBytes("00800000"), 32768, maxScriptNumLen, false, nil},
{hexToBytes("ffff0000"), 65535, defaultScriptNumLen, false, nil}, {hexToBytes("ffff0000"), 65535, maxScriptNumLen, false, nil},
{hexToBytes("00000800"), 524288, defaultScriptNumLen, false, nil}, {hexToBytes("00000800"), 524288, maxScriptNumLen, false, nil},
{hexToBytes("00007000"), 7340032, defaultScriptNumLen, false, nil}, {hexToBytes("00007000"), 7340032, maxScriptNumLen, false, nil},
{hexToBytes("0009000100"), 16779520, 5, false, nil}, {hexToBytes("0009000100"), 16779520, 5, false, nil},
} }

View file

@ -22,12 +22,7 @@ func RawTxInWitnessSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int,
amt int64, subScript []byte, hashType SigHashType, amt int64, subScript []byte, hashType SigHashType,
key *btcec.PrivateKey) ([]byte, error) { key *btcec.PrivateKey) ([]byte, error) {
parsedScript, err := parseScript(subScript) hash, err := calcWitnessSignatureHashRaw(subScript, sigHashes, hashType, tx,
if err != nil {
return nil, fmt.Errorf("cannot parse output script: %v", err)
}
hash, err := calcWitnessSignatureHash(parsedScript, sigHashes, hashType, tx,
idx, amt) idx, amt)
if err != nil { if err != nil {
return nil, err return nil, err
@ -212,110 +207,50 @@ func sign(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int,
} }
} }
// mergeScripts merges sigScript and prevScript assuming they are both
// partial solutions for pkScript spending output idx of tx. class, addresses
// and nrequired are the result of extracting the addresses from pkscript.
// The return value is the best effort merging of the two scripts. Calling this
// function with addresses, class and nrequired that do not match pkScript is
// an error and results in undefined behaviour.
func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int,
pkScript []byte, class ScriptClass, addresses []btcutil.Address,
nRequired int, sigScript, prevScript []byte) []byte {
// TODO: the scripthash and multisig paths here are overly
// inefficient in that they will recompute already known data.
// some internal refactoring could probably make this avoid needless
// extra calculations.
switch class {
case ScriptHashTy:
// Remove the last push in the script and then recurse.
// this could be a lot less inefficient.
sigPops, err := parseScript(sigScript)
if err != nil || len(sigPops) == 0 {
return prevScript
}
prevPops, err := parseScript(prevScript)
if err != nil || len(prevPops) == 0 {
return sigScript
}
// assume that script in sigPops is the correct one, we just
// made it.
script := sigPops[len(sigPops)-1].data
// We already know this information somewhere up the stack.
class, addresses, nrequired, _ :=
ExtractPkScriptAddrs(script, chainParams)
// regenerate scripts.
sigScript, _ := unparseScript(sigPops)
prevScript, _ := unparseScript(prevPops)
// Merge
mergedScript := mergeScripts(chainParams, tx, idx, script,
class, addresses, nrequired, sigScript, prevScript)
// Reappend the script and return the result.
builder := NewScriptBuilder()
builder.AddOps(mergedScript)
builder.AddData(script)
finalScript, _ := builder.Script()
return finalScript
case MultiSigTy:
return mergeMultiSig(tx, idx, addresses, nRequired, pkScript,
sigScript, prevScript)
// It doesn't actually make sense to merge anything other than multiig
// and scripthash (because it could contain multisig). Everything else
// has either zero signature, can't be spent, or has a single signature
// which is either present or not. The other two cases are handled
// above. In the conflict case here we just assume the longest is
// correct (this matches behaviour of the reference implementation).
default:
if len(sigScript) > len(prevScript) {
return sigScript
}
return prevScript
}
}
// mergeMultiSig combines the two signature scripts sigScript and prevScript // mergeMultiSig combines the two signature scripts sigScript and prevScript
// that both provide signatures for pkScript in output idx of tx. addresses // that both provide signatures for pkScript in output idx of tx. addresses
// and nRequired should be the results from extracting the addresses from // and nRequired should be the results from extracting the addresses from
// pkScript. Since this function is internal only we assume that the arguments // pkScript. Since this function is internal only we assume that the arguments
// have come from other functions internally and thus are all consistent with // have come from other functions internally and thus are all consistent with
// each other, behaviour is undefined if this contract is broken. // each other, behaviour is undefined if this contract is broken.
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func mergeMultiSig(tx *wire.MsgTx, idx int, addresses []btcutil.Address, func mergeMultiSig(tx *wire.MsgTx, idx int, addresses []btcutil.Address,
nRequired int, pkScript, sigScript, prevScript []byte) []byte { nRequired int, pkScript, sigScript, prevScript []byte) []byte {
// This is an internal only function and we already parsed this script // Nothing to merge if either the new or previous signature scripts are
// as ok for multisig (this is how we got here), so if this fails then // empty.
// all assumptions are broken and who knows which way is up? if len(sigScript) == 0 {
pkPops, _ := parseScript(pkScript)
sigPops, err := parseScript(sigScript)
if err != nil || len(sigPops) == 0 {
return prevScript return prevScript
} }
if len(prevScript) == 0 {
prevPops, err := parseScript(prevScript)
if err != nil || len(prevPops) == 0 {
return sigScript return sigScript
} }
// Convenience function to avoid duplication. // Convenience function to avoid duplication.
extractSigs := func(pops []parsedOpcode, sigs [][]byte) [][]byte { var possibleSigs [][]byte
for _, pop := range pops { extractSigs := func(script []byte) error {
if len(pop.data) != 0 { const scriptVersion = 0
sigs = append(sigs, pop.data) tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
if data := tokenizer.Data(); len(data) != 0 {
possibleSigs = append(possibleSigs, data)
} }
} }
return sigs return tokenizer.Err()
} }
possibleSigs := make([][]byte, 0, len(sigPops)+len(prevPops)) // Attempt to extract signatures from the two scripts. Return the other
possibleSigs = extractSigs(sigPops, possibleSigs) // script that is intended to be merged in the case signature extraction
possibleSigs = extractSigs(prevPops, possibleSigs) // fails for some reason.
if err := extractSigs(sigScript); err != nil {
return prevScript
}
if err := extractSigs(prevScript); err != nil {
return sigScript
}
// Now we need to match the signatures to pubkeys, the only real way to // Now we need to match the signatures to pubkeys, the only real way to
// do that is to try to verify them all and match it to the pubkey // do that is to try to verify them all and match it to the pubkey
@ -345,7 +280,7 @@ sigLoop:
// however, assume no sigs etc are in the script since that // however, assume no sigs etc are in the script since that
// would make the transaction nonstandard and thus not // would make the transaction nonstandard and thus not
// MultiSigTy, so we just need to hash the full thing. // MultiSigTy, so we just need to hash the full thing.
hash := calcSignatureHash(pkPops, hashType, tx, idx) hash := calcSignatureHash(pkScript, hashType, tx, idx)
for _, addr := range addresses { for _, addr := range addresses {
// All multisig addresses should be pubkey addresses // All multisig addresses should be pubkey addresses
@ -394,6 +329,81 @@ sigLoop:
return script return script
} }
// mergeScripts merges sigScript and prevScript assuming they are both
// partial solutions for pkScript spending output idx of tx. class, addresses
// and nrequired are the result of extracting the addresses from pkscript.
// The return value is the best effort merging of the two scripts. Calling this
// function with addresses, class and nrequired that do not match pkScript is
// an error and results in undefined behaviour.
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int,
pkScript []byte, class ScriptClass, addresses []btcutil.Address,
nRequired int, sigScript, prevScript []byte) []byte {
// TODO(oga) the scripthash and multisig paths here are overly
// inefficient in that they will recompute already known data.
// some internal refactoring could probably make this avoid needless
// extra calculations.
const scriptVersion = 0
switch class {
case ScriptHashTy:
// Nothing to merge if either the new or previous signature
// scripts are empty or fail to parse.
if len(sigScript) == 0 ||
checkScriptParses(scriptVersion, sigScript) != nil {
return prevScript
}
if len(prevScript) == 0 ||
checkScriptParses(scriptVersion, prevScript) != nil {
return sigScript
}
// Remove the last push in the script and then recurse.
// this could be a lot less inefficient.
//
// Assume that final script is the correct one since it was just
// made and it is a pay-to-script-hash.
script := finalOpcodeData(scriptVersion, sigScript)
// We already know this information somewhere up the stack,
// therefore the error is ignored.
class, addresses, nrequired, _ :=
ExtractPkScriptAddrs(script, chainParams)
// Merge
mergedScript := mergeScripts(chainParams, tx, idx, script,
class, addresses, nrequired, sigScript, prevScript)
// Reappend the script and return the result.
builder := NewScriptBuilder()
builder.AddOps(mergedScript)
builder.AddData(script)
finalScript, _ := builder.Script()
return finalScript
case MultiSigTy:
return mergeMultiSig(tx, idx, addresses, nRequired, pkScript,
sigScript, prevScript)
// It doesn't actually make sense to merge anything other than multiig
// and scripthash (because it could contain multisig). Everything else
// has either zero signature, can't be spent, or has a single signature
// which is either present or not. The other two cases are handled
// above. In the conflict case here we just assume the longest is
// correct (this matches behaviour of the reference implementation).
default:
if len(sigScript) > len(prevScript) {
return sigScript
}
return prevScript
}
}
// KeyDB is an interface type provided to SignTxOutput, it encapsulates // KeyDB is an interface type provided to SignTxOutput, it encapsulates
// any user state required to get the private keys for an address. // any user state required to get the private keys for an address.
type KeyDB interface { type KeyDB interface {
@ -404,8 +414,7 @@ type KeyDB interface {
type KeyClosure func(btcutil.Address) (*btcec.PrivateKey, bool, error) type KeyClosure func(btcutil.Address) (*btcec.PrivateKey, bool, error)
// GetKey implements KeyDB by returning the result of calling the closure. // GetKey implements KeyDB by returning the result of calling the closure.
func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey, func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey, bool, error) {
bool, error) {
return kc(address) return kc(address)
} }
@ -430,6 +439,10 @@ func (sc ScriptClosure) GetScript(address btcutil.Address) ([]byte, error) {
// getScript. If previousScript is provided then the results in previousScript // getScript. If previousScript is provided then the results in previousScript
// will be merged in a type-dependent manner with the newly generated. // will be merged in a type-dependent manner with the newly generated.
// signature script. // signature script.
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func SignTxOutput(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, func SignTxOutput(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int,
pkScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB, pkScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB,
previousScript []byte) ([]byte, error) { previousScript []byte) ([]byte, error) {

View file

@ -86,7 +86,7 @@ func (s *stack) PopInt() (scriptNum, error) {
return 0, err return 0, err
} }
return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen) return makeScriptNum(so, s.verifyMinimalData, maxScriptNumLen)
} }
// PopBool pops the value off the top of the stack, converts it into a bool, and // PopBool pops the value off the top of the stack, converts it into a bool, and
@ -123,7 +123,7 @@ func (s *stack) PeekInt(idx int32) (scriptNum, error) {
return 0, err return 0, err
} }
return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen) return makeScriptNum(so, s.verifyMinimalData, maxScriptNumLen)
} }
// PeekBool returns the Nth item on the stack as a bool without removing it. // PeekBool returns the Nth item on the stack as a bool without removing it.

File diff suppressed because it is too large Load diff

View file

@ -832,7 +832,7 @@ func TestCalcMultiSigStats(t *testing.T) {
name: "short script", name: "short script",
script: "0x046708afdb0fe5548271967f1a67130b7105cd6a828" + script: "0x046708afdb0fe5548271967f1a67130b7105cd6a828" +
"e03909a67962e0ea1f61d", "e03909a67962e0ea1f61d",
err: scriptError(ErrMalformedPush, ""), err: scriptError(ErrNotMultisigScript, ""),
}, },
{ {
name: "stack underflow", name: "stack underflow",
@ -843,11 +843,7 @@ func TestCalcMultiSigStats(t *testing.T) {
}, },
{ {
name: "multisig script", name: "multisig script",
script: "0 DATA_72 0x30450220106a3e4ef0b51b764a2887226" + script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da45329a0" +
"2ffef55846514dacbdcbbdd652c849d395b4384022100" +
"e03ae554c3cbb40600d31dd46fc33f25e47bf8525b1fe" +
"07282e3b6ecb5f3bb2801 CODESEPARATOR 1 DATA_33 " +
"0x0232abdc893e7f0631364d7fd01cb33d24da45329a0" +
"0357b3a7886211ab414d55a 1 CHECKMULTISIG", "0357b3a7886211ab414d55a 1 CHECKMULTISIG",
err: nil, err: nil,
}, },

186
txscript/tokenizer.go Normal file
View file

@ -0,0 +1,186 @@
// Copyright (c) 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 (
"encoding/binary"
"fmt"
)
// opcodeArrayRef is used to break initialization cycles.
var opcodeArrayRef *[256]opcode
func init() {
opcodeArrayRef = &opcodeArray
}
// ScriptTokenizer provides a facility for easily and efficiently tokenizing
// transaction scripts without creating allocations. Each successive opcode is
// parsed with the Next function, which returns false when iteration is
// complete, either due to successfully tokenizing the entire script or
// encountering a parse error. In the case of failure, the Err function may be
// used to obtain the specific parse error.
//
// Upon successfully parsing an opcode, the opcode and data associated with it
// may be obtained via the Opcode and Data functions, respectively.
//
// The ByteIndex function may be used to obtain the tokenizer's current offset
// into the raw script.
type ScriptTokenizer struct {
script []byte
version uint16
offset int32
op *opcode
data []byte
err error
}
// Done returns true when either all opcodes have been exhausted or a parse
// failure was encountered and therefore the state has an associated error.
func (t *ScriptTokenizer) Done() bool {
return t.err != nil || t.offset >= int32(len(t.script))
}
// Next attempts to parse the next opcode and returns whether or not it was
// successful. It will not be successful if invoked when already at the end of
// the script, a parse failure is encountered, or an associated error already
// exists due to a previous parse failure.
//
// In the case of a true return, the parsed opcode and data can be obtained with
// the associated functions and the offset into the script will either point to
// the next opcode or the end of the script if the final opcode was parsed.
//
// In the case of a false return, the parsed opcode and data will be the last
// successfully parsed values (if any) and the offset into the script will
// either point to the failing opcode or the end of the script if the function
// was invoked when already at the end of the script.
//
// Invoking this function when already at the end of the script is not
// considered an error and will simply return false.
func (t *ScriptTokenizer) Next() bool {
if t.Done() {
return false
}
op := &opcodeArrayRef[t.script[t.offset]]
switch {
// No additional data. Note that some of the opcodes, notably OP_1NEGATE,
// OP_0, and OP_[1-16] represent the data themselves.
case op.length == 1:
t.offset++
t.op = op
t.data = nil
return true
// Data pushes of specific lengths -- OP_DATA_[1-75].
case op.length > 1:
script := t.script[t.offset:]
if len(script) < op.length {
str := fmt.Sprintf("opcode %s requires %d bytes, but script only "+
"has %d remaining", op.name, op.length, len(script))
t.err = scriptError(ErrMalformedPush, str)
return false
}
// Move the offset forward and set the opcode and data accordingly.
t.offset += int32(op.length)
t.op = op
t.data = script[1:op.length]
return true
// Data pushes with parsed lengths -- OP_PUSHDATA{1,2,4}.
case op.length < 0:
script := t.script[t.offset+1:]
if len(script) < -op.length {
str := fmt.Sprintf("opcode %s requires %d bytes, but script only "+
"has %d remaining", op.name, -op.length, len(script))
t.err = scriptError(ErrMalformedPush, str)
return false
}
// Next -length bytes are little endian length of data.
var dataLen int32
switch op.length {
case -1:
dataLen = int32(script[0])
case -2:
dataLen = int32(binary.LittleEndian.Uint16(script[:2]))
case -4:
dataLen = int32(binary.LittleEndian.Uint32(script[:4]))
default:
// In practice it should be impossible to hit this
// check as each op code is predefined, and only uses
// the specified lengths.
str := fmt.Sprintf("invalid opcode length %d", op.length)
t.err = scriptError(ErrMalformedPush, str)
return false
}
// Move to the beginning of the data.
script = script[-op.length:]
// Disallow entries that do not fit script or were sign extended.
if dataLen > int32(len(script)) || dataLen < 0 {
str := fmt.Sprintf("opcode %s pushes %d bytes, but script only "+
"has %d remaining", op.name, dataLen, len(script))
t.err = scriptError(ErrMalformedPush, str)
return false
}
// Move the offset forward and set the opcode and data accordingly.
t.offset += 1 + int32(-op.length) + dataLen
t.op = op
t.data = script[:dataLen]
return true
}
// The only remaining case is an opcode with length zero which is
// impossible.
panic("unreachable")
}
// Script returns the full script associated with the tokenizer.
func (t *ScriptTokenizer) Script() []byte {
return t.script
}
// ByteIndex returns the current offset into the full script that will be parsed
// next and therefore also implies everything before it has already been parsed.
func (t *ScriptTokenizer) ByteIndex() int32 {
return t.offset
}
// Opcode returns the current opcode associated with the tokenizer.
func (t *ScriptTokenizer) Opcode() byte {
return t.op.value
}
// Data returns the data associated with the most recently successfully parsed
// opcode.
func (t *ScriptTokenizer) Data() []byte {
return t.data
}
// Err returns any errors currently associated with the tokenizer. This will
// only be non-nil in the case a parsing error was encountered.
func (t *ScriptTokenizer) Err() error {
return t.err
}
// MakeScriptTokenizer returns a new instance of a script tokenizer. Passing
// an unsupported script version will result in the returned tokenizer
// immediately having an err set accordingly.
//
// See the docs for ScriptTokenizer for more details.
func MakeScriptTokenizer(scriptVersion uint16, script []byte) ScriptTokenizer {
// Only version 0 scripts are currently supported.
var err error
if scriptVersion != 0 {
str := fmt.Sprintf("script version %d is not supported", scriptVersion)
err = scriptError(ErrUnsupportedScriptVersion, str)
}
return ScriptTokenizer{version: scriptVersion, script: script, err: err}
}

259
txscript/tokenizer_test.go Normal file
View file

@ -0,0 +1,259 @@
// Copyright (c) 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"
"fmt"
"testing"
)
// TestScriptTokenizer ensures a wide variety of behavior provided by the script
// tokenizer performs as expected.
func TestScriptTokenizer(t *testing.T) {
t.Skip()
type expectedResult struct {
op byte // expected parsed opcode
data []byte // expected parsed data
index int32 // expected index into raw script after parsing token
}
type tokenizerTest struct {
name string // test description
script []byte // the script to tokenize
expected []expectedResult // the expected info after parsing each token
finalIdx int32 // the expected final byte index
err error // expected error
}
// Add both positive and negative tests for OP_DATA_1 through OP_DATA_75.
const numTestsHint = 100 // Make prealloc linter happy.
tests := make([]tokenizerTest, 0, numTestsHint)
for op := byte(OP_DATA_1); op < OP_DATA_75; op++ {
data := bytes.Repeat([]byte{0x01}, int(op))
tests = append(tests, tokenizerTest{
name: fmt.Sprintf("OP_DATA_%d", op),
script: append([]byte{op}, data...),
expected: []expectedResult{{op, data, 1 + int32(op)}},
finalIdx: 1 + int32(op),
err: nil,
})
// Create test that provides one less byte than the data push requires.
tests = append(tests, tokenizerTest{
name: fmt.Sprintf("short OP_DATA_%d", op),
script: append([]byte{op}, data[1:]...),
expected: nil,
finalIdx: 0,
err: scriptError(ErrMalformedPush, ""),
})
}
// Add both positive and negative tests for OP_PUSHDATA{1,2,4}.
data := mustParseShortForm("0x01{76}")
tests = append(tests, []tokenizerTest{{
name: "OP_PUSHDATA1",
script: mustParseShortForm("OP_PUSHDATA1 0x4c 0x01{76}"),
expected: []expectedResult{{OP_PUSHDATA1, data, 2 + int32(len(data))}},
finalIdx: 2 + int32(len(data)),
err: nil,
}, {
name: "OP_PUSHDATA1 no data length",
script: mustParseShortForm("OP_PUSHDATA1"),
expected: nil,
finalIdx: 0,
err: scriptError(ErrMalformedPush, ""),
}, {
name: "OP_PUSHDATA1 short data by 1 byte",
script: mustParseShortForm("OP_PUSHDATA1 0x4c 0x01{75}"),
expected: nil,
finalIdx: 0,
err: scriptError(ErrMalformedPush, ""),
}, {
name: "OP_PUSHDATA2",
script: mustParseShortForm("OP_PUSHDATA2 0x4c00 0x01{76}"),
expected: []expectedResult{{OP_PUSHDATA2, data, 3 + int32(len(data))}},
finalIdx: 3 + int32(len(data)),
err: nil,
}, {
name: "OP_PUSHDATA2 no data length",
script: mustParseShortForm("OP_PUSHDATA2"),
expected: nil,
finalIdx: 0,
err: scriptError(ErrMalformedPush, ""),
}, {
name: "OP_PUSHDATA2 short data by 1 byte",
script: mustParseShortForm("OP_PUSHDATA2 0x4c00 0x01{75}"),
expected: nil,
finalIdx: 0,
err: scriptError(ErrMalformedPush, ""),
}, {
name: "OP_PUSHDATA4",
script: mustParseShortForm("OP_PUSHDATA4 0x4c000000 0x01{76}"),
expected: []expectedResult{{OP_PUSHDATA4, data, 5 + int32(len(data))}},
finalIdx: 5 + int32(len(data)),
err: nil,
}, {
name: "OP_PUSHDATA4 no data length",
script: mustParseShortForm("OP_PUSHDATA4"),
expected: nil,
finalIdx: 0,
err: scriptError(ErrMalformedPush, ""),
}, {
name: "OP_PUSHDATA4 short data by 1 byte",
script: mustParseShortForm("OP_PUSHDATA4 0x4c000000 0x01{75}"),
expected: nil,
finalIdx: 0,
err: scriptError(ErrMalformedPush, ""),
}}...)
// Add tests for OP_0, and OP_1 through OP_16 (small integers/true/false).
opcodes := []byte{OP_0}
for op := byte(OP_1); op < OP_16; op++ {
opcodes = append(opcodes, op)
}
for _, op := range opcodes {
tests = append(tests, tokenizerTest{
name: fmt.Sprintf("OP_%d", op),
script: []byte{op},
expected: []expectedResult{{op, nil, 1}},
finalIdx: 1,
err: nil,
})
}
// Add various positive and negative tests for multi-opcode scripts.
tests = append(tests, []tokenizerTest{{
name: "pay-to-pubkey-hash",
script: mustParseShortForm("DUP HASH160 DATA_20 0x01{20} EQUAL CHECKSIG"),
expected: []expectedResult{
{OP_DUP, nil, 1}, {OP_HASH160, nil, 2},
{OP_DATA_20, mustParseShortForm("0x01{20}"), 23},
{OP_EQUAL, nil, 24}, {OP_CHECKSIG, nil, 25},
},
finalIdx: 25,
err: nil,
}, {
name: "almost pay-to-pubkey-hash (short data)",
script: mustParseShortForm("DUP HASH160 DATA_20 0x01{17} EQUAL CHECKSIG"),
expected: []expectedResult{
{OP_DUP, nil, 1}, {OP_HASH160, nil, 2},
},
finalIdx: 2,
err: scriptError(ErrMalformedPush, ""),
}, {
name: "almost pay-to-pubkey-hash (overlapped data)",
script: mustParseShortForm("DUP HASH160 DATA_20 0x01{19} EQUAL CHECKSIG"),
expected: []expectedResult{
{OP_DUP, nil, 1}, {OP_HASH160, nil, 2},
{OP_DATA_20, mustParseShortForm("0x01{19} EQUAL"), 23},
{OP_CHECKSIG, nil, 24},
},
finalIdx: 24,
err: nil,
}, {
name: "pay-to-script-hash",
script: mustParseShortForm("HASH160 DATA_20 0x01{20} EQUAL"),
expected: []expectedResult{
{OP_HASH160, nil, 1},
{OP_DATA_20, mustParseShortForm("0x01{20}"), 22},
{OP_EQUAL, nil, 23},
},
finalIdx: 23,
err: nil,
}, {
name: "almost pay-to-script-hash (short data)",
script: mustParseShortForm("HASH160 DATA_20 0x01{18} EQUAL"),
expected: []expectedResult{
{OP_HASH160, nil, 1},
},
finalIdx: 1,
err: scriptError(ErrMalformedPush, ""),
}, {
name: "almost pay-to-script-hash (overlapped data)",
script: mustParseShortForm("HASH160 DATA_20 0x01{19} EQUAL"),
expected: []expectedResult{
{OP_HASH160, nil, 1},
{OP_DATA_20, mustParseShortForm("0x01{19} EQUAL"), 22},
},
finalIdx: 22,
err: nil,
}}...)
const scriptVersion = 0
for _, test := range tests {
tokenizer := MakeScriptTokenizer(scriptVersion, test.script)
var opcodeNum int
for tokenizer.Next() {
// Ensure Next never returns true when there is an error set.
if err := tokenizer.Err(); err != nil {
t.Fatalf("%q: Next returned true when tokenizer has err: %v",
test.name, err)
}
// Ensure the test data expects a token to be parsed.
op := tokenizer.Opcode()
data := tokenizer.Data()
if opcodeNum >= len(test.expected) {
t.Fatalf("%q: unexpected token '%d' (data: '%x')", test.name,
op, data)
}
expected := &test.expected[opcodeNum]
// Ensure the opcode and data are the expected values.
if op != expected.op {
t.Fatalf("%q: unexpected opcode -- got %v, want %v", test.name,
op, expected.op)
}
if !bytes.Equal(data, expected.data) {
t.Fatalf("%q: unexpected data -- got %x, want %x", test.name,
data, expected.data)
}
tokenizerIdx := tokenizer.ByteIndex()
if tokenizerIdx != expected.index {
t.Fatalf("%q: unexpected byte index -- got %d, want %d",
test.name, tokenizerIdx, expected.index)
}
opcodeNum++
}
// Ensure the tokenizer claims it is done. This should be the case
// regardless of whether or not there was a parse error.
if !tokenizer.Done() {
t.Fatalf("%q: tokenizer claims it is not done", test.name)
}
// Ensure the error is as expected.
if test.err == nil && tokenizer.Err() != nil {
t.Fatalf("%q: unexpected tokenizer err -- got %v, want nil",
test.name, tokenizer.Err())
} else if test.err != nil {
if !IsErrorCode(tokenizer.Err(), test.err.(Error).ErrorCode) {
t.Fatalf("%q: unexpected tokenizer err -- got %v, want %v",
test.name, tokenizer.Err(), test.err.(Error).ErrorCode)
}
}
// Ensure the final index is the expected value.
tokenizerIdx := tokenizer.ByteIndex()
if tokenizerIdx != test.finalIdx {
t.Fatalf("%q: unexpected final byte index -- got %d, want %d",
test.name, tokenizerIdx, test.finalIdx)
}
}
}
// TestScriptTokenizerUnsupportedVersion ensures the tokenizer fails immediately
// with an unsupported script version.
func TestScriptTokenizerUnsupportedVersion(t *testing.T) {
const scriptVersion = 65535
tokenizer := MakeScriptTokenizer(scriptVersion, nil)
if !IsErrorCode(tokenizer.Err(), ErrUnsupportedScriptVersion) {
t.Fatalf("script tokenizer did not error with unsupported version")
}
}