Merge pull request #1769 from Roasbeef/txscript_zero_alloc_optimization_refactor
txscript: backport tokenizer from dcrd
This commit is contained in:
commit
7070d53e09
22 changed files with 2780 additions and 5027 deletions
|
@ -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)
|
||||
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
|
||||
|
||||
All official release tags are signed by Conformal so users can ensure the code
|
||||
|
|
537
txscript/bench_test.go
Normal file
537
txscript/bench_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
1
txscript/data/many_inputs_tx.hex
Normal file
1
txscript/data/many_inputs_tx.hex
Normal file
File diff suppressed because one or more lines are too long
|
@ -10,6 +10,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"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.
|
||||
type Engine struct {
|
||||
scripts [][]parsedOpcode
|
||||
scriptIdx int
|
||||
scriptOff int
|
||||
lastCodeSep int
|
||||
dstack stack // data stack
|
||||
astack stack // alt stack
|
||||
// The following fields are set when the engine is created and must not be
|
||||
// changed afterwards. The entries of the signature cache are mutated
|
||||
// during execution, however, the cache pointer itself is not changed.
|
||||
//
|
||||
// flags specifies the additional flags which modify the execution behavior
|
||||
// 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
|
||||
txIdx int
|
||||
condStack []int
|
||||
numOps int
|
||||
flags ScriptFlags
|
||||
version uint16
|
||||
bip16 bool
|
||||
sigCache *SigCache
|
||||
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
|
||||
witnessProgram []byte
|
||||
inputAmount int64
|
||||
|
@ -154,26 +218,144 @@ func (vm *Engine) isBranchExecuting() bool {
|
|||
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
|
||||
// whether or not it is hidden by conditionals, but some rules still must be
|
||||
// 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.
|
||||
if pop.isDisabled() {
|
||||
str := fmt.Sprintf("attempt to execute disabled opcode %s",
|
||||
pop.opcode.name)
|
||||
if isOpcodeDisabled(op.value) {
|
||||
str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name)
|
||||
return scriptError(ErrDisabledOpcode, str)
|
||||
}
|
||||
|
||||
// Always-illegal opcodes are fail on program counter.
|
||||
if pop.alwaysIllegal() {
|
||||
str := fmt.Sprintf("attempt to execute reserved opcode %s",
|
||||
pop.opcode.name)
|
||||
if isOpcodeAlwaysIllegal(op.value) {
|
||||
str := fmt.Sprintf("attempt to execute reserved opcode %s", op.name)
|
||||
return scriptError(ErrReservedOpcode, str)
|
||||
}
|
||||
|
||||
// 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++
|
||||
if vm.numOps > MaxOpsPerScript {
|
||||
str := fmt.Sprintf("exceeded max operation limit of %d",
|
||||
|
@ -181,67 +363,42 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error {
|
|||
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",
|
||||
len(pop.data), MaxScriptElementSize)
|
||||
len(data), MaxScriptElementSize)
|
||||
return scriptError(ErrElementTooBig, str)
|
||||
}
|
||||
|
||||
// Nothing left to do when this is not a conditional opcode and it is
|
||||
// not in an executing branch.
|
||||
if !vm.isBranchExecuting() && !pop.isConditional() {
|
||||
if !vm.isBranchExecuting() && !isOpcodeConditional(op.value) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure all executed data push opcodes use the minimal encoding when
|
||||
// the minimal data verification flag is set.
|
||||
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 pop.opcode.opfunc(pop, vm)
|
||||
return op.opfunc(op, data, vm)
|
||||
}
|
||||
|
||||
// disasm is a helper function to produce the output for DisasmPC and
|
||||
// DisasmScript. It produces the opcode prefixed by the program counter at the
|
||||
// provided position in the script. It does no error checking and leaves that
|
||||
// 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 {
|
||||
// checkValidPC returns an error if the current script position is not valid for
|
||||
// execution.
|
||||
func (vm *Engine) checkValidPC() error {
|
||||
if vm.scriptIdx >= len(vm.scripts) {
|
||||
str := fmt.Sprintf("past input scripts %v:%v %v:xxxx",
|
||||
vm.scriptIdx, vm.scriptOff, 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]))
|
||||
str := fmt.Sprintf("script index %d beyond total scripts %d",
|
||||
vm.scriptIdx, len(vm.scripts))
|
||||
return scriptError(ErrInvalidProgramCounter, str)
|
||||
}
|
||||
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
|
||||
// during the initialization of the Engine, and the program's version matches
|
||||
// the specified version.
|
||||
|
@ -269,7 +426,9 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pops, err := parseScript(pkScript)
|
||||
|
||||
const scriptVersion = 0
|
||||
err = checkScriptParses(vm.version, pkScript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -277,7 +436,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
|||
// Set the stack to the provided witness stack, then
|
||||
// append the pkScript generated above as the next
|
||||
// script to execute.
|
||||
vm.scripts = append(vm.scripts, pops)
|
||||
vm.scripts = append(vm.scripts, pkScript)
|
||||
vm.SetStack(witness)
|
||||
|
||||
case payToWitnessScriptHashDataSize: // P2WSH
|
||||
|
@ -307,10 +466,10 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
|||
"witness program hash mismatch")
|
||||
}
|
||||
|
||||
// With all the validity checks passed, parse the
|
||||
// script into individual op-codes so w can execute it
|
||||
// as the next script.
|
||||
pops, err := parseScript(witnessScript)
|
||||
// With all the validity checks passed, assert that the
|
||||
// script parses without failure.
|
||||
const scriptVersion = 0
|
||||
err := checkScriptParses(vm.version, witnessScript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -318,7 +477,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
|||
// The hash matched successfully, so use the witness as
|
||||
// the stack, and set the witnessScript to be the next
|
||||
// script executed.
|
||||
vm.scripts = append(vm.scripts, pops)
|
||||
vm.scripts = append(vm.scripts, witnessScript)
|
||||
vm.SetStack(witness[:len(witness)-1])
|
||||
|
||||
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
|
||||
// next to execute when Step() is called.
|
||||
// next to execute when Step is called.
|
||||
func (vm *Engine) DisasmPC() (string, error) {
|
||||
scriptIdx, scriptOff, err := vm.curPC()
|
||||
if err != nil {
|
||||
if err := vm.checkValidPC(); err != nil {
|
||||
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
|
||||
// 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) {
|
||||
if idx >= len(vm.scripts) {
|
||||
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)
|
||||
}
|
||||
|
||||
var disstr string
|
||||
for i := range vm.scripts[idx] {
|
||||
disstr = disstr + vm.disasm(idx, i) + "\n"
|
||||
var disbuf strings.Builder
|
||||
script := vm.scripts[idx]
|
||||
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
|
||||
// successful, leaving a a true boolean on the stack. An error otherwise,
|
||||
// including if the script has not finished.
|
||||
func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
||||
// Check execution is actually done. When pc is past the end of script
|
||||
// array there are no more scripts to run.
|
||||
// Check execution is actually done by ensuring the script index is after
|
||||
// the final script in the array script.
|
||||
if vm.scriptIdx < len(vm.scripts) {
|
||||
return scriptError(ErrScriptUnfinished,
|
||||
"error check when script unfinished")
|
||||
|
@ -404,11 +601,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
|||
"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) &&
|
||||
vm.dstack.Depth() != 1 {
|
||||
|
||||
str := fmt.Sprintf("stack contains %d unexpected items",
|
||||
vm.dstack.Depth()-1)
|
||||
str := fmt.Sprintf("stack must contain exactly one item (contains %d)",
|
||||
vm.dstack.Depth())
|
||||
return scriptError(ErrCleanStack, str)
|
||||
} else if vm.dstack.Depth() < 1 {
|
||||
return scriptError(ErrEmptyStack,
|
||||
|
@ -422,10 +622,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
|||
if !v {
|
||||
// Log interesting data.
|
||||
log.Tracef("%v", newLogClosure(func() string {
|
||||
dis0, _ := vm.DisasmScript(0)
|
||||
dis1, _ := vm.DisasmScript(1)
|
||||
return fmt.Sprintf("scripts failed: script0: %s\n"+
|
||||
"script1: %s", dis0, dis1)
|
||||
var buf strings.Builder
|
||||
buf.WriteString("scripts failed:\n")
|
||||
for i := range vm.scripts {
|
||||
dis, _ := vm.DisasmScript(i)
|
||||
buf.WriteString(fmt.Sprintf("script%d:\n", i))
|
||||
buf.WriteString(dis)
|
||||
}
|
||||
return buf.String()
|
||||
}))
|
||||
return scriptError(ErrEvalFalse,
|
||||
"false stack entry at end of script execution")
|
||||
|
@ -433,25 +637,38 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Step will execute the next instruction and move the program counter to the
|
||||
// next opcode in the script, or the next script if the current has ended. Step
|
||||
// will return true in the case that the last opcode was successfully executed.
|
||||
// Step executes the next instruction and moves the program counter to the next
|
||||
// opcode in the script, or the next script if the current has ended. Step will
|
||||
// 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
|
||||
// returned.
|
||||
func (vm *Engine) Step() (done bool, err error) {
|
||||
// Verify that it is pointing to a valid script address.
|
||||
err = vm.validPC()
|
||||
if err != nil {
|
||||
// Verify the engine is pointing to a valid program counter.
|
||||
if err := vm.checkValidPC(); err != nil {
|
||||
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
|
||||
// disabled opcodes, illegal opcodes, maximum allowed operations per
|
||||
// script, maximum script element sizes, and conditionals.
|
||||
err = vm.executeOpcode(opcode)
|
||||
// disabled opcodes, illegal opcodes, maximum allowed operations per script,
|
||||
// maximum script element sizes, and conditionals.
|
||||
err = vm.executeOpcode(vm.tokenizer.op, vm.tokenizer.Data())
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
@ -466,43 +683,53 @@ func (vm *Engine) Step() (done bool, err error) {
|
|||
}
|
||||
|
||||
// Prepare for next instruction.
|
||||
if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
|
||||
// Illegal to have an `if' that straddles two scripts.
|
||||
if err == nil && len(vm.condStack) != 0 {
|
||||
vm.opcodeIdx++
|
||||
if vm.tokenizer.Done() {
|
||||
// Illegal to have a conditional that straddles two scripts.
|
||||
if len(vm.condStack) != 0 {
|
||||
return false, scriptError(ErrUnbalancedConditional,
|
||||
"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.numOps = 0 // number of ops is per script.
|
||||
vm.scriptOff = 0
|
||||
if vm.scriptIdx == 0 && vm.bip16 {
|
||||
// The number of operations is per script.
|
||||
vm.numOps = 0
|
||||
|
||||
// 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.savedFirstStack = vm.GetStack()
|
||||
} else if vm.scriptIdx == 1 && vm.bip16 {
|
||||
|
||||
case vm.scriptIdx == 1 && vm.bip16:
|
||||
// Put us past the end for CheckErrorCondition()
|
||||
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)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Obtain the redeem script from the first stack and ensure it
|
||||
// parses.
|
||||
script := vm.savedFirstStack[len(vm.savedFirstStack)-1]
|
||||
pops, err := parseScript(script)
|
||||
if err != nil {
|
||||
if err := checkScriptParses(vm.version, script); err != nil {
|
||||
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
|
||||
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++
|
||||
|
||||
|
@ -510,30 +737,46 @@ func (vm *Engine) Step() (done bool, err error) {
|
|||
if err := vm.verifyWitnessProgram(witness); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
|
||||
default:
|
||||
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.lastCodeSep = 0
|
||||
if vm.scriptIdx >= len(vm.scripts) {
|
||||
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
|
||||
}
|
||||
|
||||
// Execute will execute all scripts in the script engine and return either nil
|
||||
// for successful validation or an error if one occurred.
|
||||
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
|
||||
for !done {
|
||||
log.Tracef("%v", newLogClosure(func() string {
|
||||
dis, err := vm.DisasmPC()
|
||||
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)
|
||||
}))
|
||||
|
@ -545,7 +788,7 @@ func (vm *Engine) Execute() (err error) {
|
|||
log.Tracef("%v", newLogClosure(func() string {
|
||||
var dstr, astr string
|
||||
|
||||
// if we're tracing, dump the stacks.
|
||||
// Log the non-empty stacks when tracing.
|
||||
if vm.dstack.Depth() != 0 {
|
||||
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.
|
||||
func (vm *Engine) subScript() []parsedOpcode {
|
||||
func (vm *Engine) subScript() []byte {
|
||||
return vm.scripts[vm.scriptIdx][vm.lastCodeSep:]
|
||||
}
|
||||
|
||||
|
@ -580,6 +823,27 @@ func (vm *Engine) checkHashTypeEncoding(hashType SigHashType) error {
|
|||
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
|
||||
// the strict encoding requirements if enabled.
|
||||
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.
|
||||
func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags,
|
||||
sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64) (*Engine, error) {
|
||||
const scriptVersion = 0
|
||||
|
||||
// The provided transaction input index must refer to a valid input.
|
||||
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
|
||||
|
||||
// When both the signature script and public key script are empty the
|
||||
// result is necessarily an error since the stack would end up being
|
||||
// empty which is equivalent to a false top element. Thus, just return
|
||||
// the relevant error now as an optimization.
|
||||
// When both the signature script and public key script are empty the result
|
||||
// is necessarily an error since the stack would end up being empty which is
|
||||
// equivalent to a false top element. Thus, just return the relevant error
|
||||
// now as an optimization.
|
||||
if len(scriptSig) == 0 && len(scriptPubKey) == 0 {
|
||||
return nil, scriptError(ErrEvalFalse,
|
||||
"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")
|
||||
}
|
||||
|
||||
// The engine stores the scripts in parsed form 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}
|
||||
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]) {
|
||||
// The signature script must only contain data pushes for PS2H which is
|
||||
// determined based on the form of the public key script.
|
||||
if vm.hasFlag(ScriptBip16) && isScriptHashScript(scriptPubKey) {
|
||||
// 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,
|
||||
"pay to script hash is not push only")
|
||||
}
|
||||
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) {
|
||||
vm.dstack.verifyMinimalData = true
|
||||
vm.astack.verifyMinimalData = true
|
||||
|
@ -952,7 +1222,7 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
|||
var witProgram []byte
|
||||
|
||||
switch {
|
||||
case isWitnessProgram(vm.scripts[1]):
|
||||
case IsWitnessProgram(vm.scripts[1]):
|
||||
// The scriptSig must be *empty* for all native witness
|
||||
// programs, otherwise we introduce malleability.
|
||||
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
|
||||
// reintroduce malleability.
|
||||
sigPops := vm.scripts[0]
|
||||
if len(sigPops) == 1 && canonicalPush(sigPops[0]) &&
|
||||
IsWitnessProgram(sigPops[0].data) {
|
||||
if len(sigPops) > 2 &&
|
||||
isCanonicalPush(sigPops[0], sigPops[1:]) &&
|
||||
IsWitnessProgram(sigPops[1:]) {
|
||||
|
||||
witProgram = sigPops[0].data
|
||||
witProgram = sigPops[1:]
|
||||
} else {
|
||||
errStr := "signature script for witness " +
|
||||
"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.txIdx = txIdx
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -11,16 +12,16 @@ import (
|
|||
"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.
|
||||
func TestBadPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
script, off int
|
||||
scriptIdx int
|
||||
}{
|
||||
{script: 2, off: 0},
|
||||
{script: 0, off: 2},
|
||||
{scriptIdx: 2},
|
||||
{scriptIdx: 3},
|
||||
}
|
||||
|
||||
// tx with almost empty scripts.
|
||||
|
@ -59,20 +60,20 @@ func TestBadPC(t *testing.T) {
|
|||
t.Errorf("Failed to create script: %v", err)
|
||||
}
|
||||
|
||||
// set to after all scripts
|
||||
vm.scriptIdx = test.script
|
||||
vm.scriptOff = test.off
|
||||
// Set to after all scripts.
|
||||
vm.scriptIdx = test.scriptIdx
|
||||
|
||||
// Ensure attempting to step fails.
|
||||
_, err = vm.Step()
|
||||
if err == nil {
|
||||
t.Errorf("Step with invalid pc (%v) succeeds!", test)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure attempting to disassemble the current program counter fails.
|
||||
_, err = vm.DisasmPC()
|
||||
if err == nil {
|
||||
t.Errorf("DisasmPC with invalid pc (%v) succeeds!",
|
||||
test)
|
||||
t.Errorf("DisasmPC with invalid pc (%v) succeeds!", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -47,6 +48,10 @@ const (
|
|||
// the provided data exceeds MaxDataCarrierSize.
|
||||
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.
|
||||
// ------------------------------------------
|
||||
|
@ -352,6 +357,7 @@ var errorCodeStrings = map[ErrorCode]string{
|
|||
ErrNotMultisigScript: "ErrNotMultisigScript",
|
||||
ErrTooManyRequiredSigs: "ErrTooManyRequiredSigs",
|
||||
ErrTooMuchNullData: "ErrTooMuchNullData",
|
||||
ErrUnsupportedScriptVersion: "ErrUnsupportedScriptVersion",
|
||||
ErrEarlyReturn: "ErrEarlyReturn",
|
||||
ErrEmptyStack: "ErrEmptyStack",
|
||||
ErrEvalFalse: "ErrEvalFalse",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -22,6 +23,7 @@ func TestErrorCodeStringer(t *testing.T) {
|
|||
{ErrUnsupportedAddress, "ErrUnsupportedAddress"},
|
||||
{ErrTooManyRequiredSigs, "ErrTooManyRequiredSigs"},
|
||||
{ErrTooMuchNullData, "ErrTooMuchNullData"},
|
||||
{ErrUnsupportedScriptVersion, "ErrUnsupportedScriptVersion"},
|
||||
{ErrNotMultisigScript, "ErrNotMultisigScript"},
|
||||
{ErrEarlyReturn, "ErrEarlyReturn"},
|
||||
{ErrEmptyStack, "ErrEmptyStack"},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (c) 2014-2016 The btcsuite developers
|
||||
// Copyright (c) 2015-2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -180,3 +181,34 @@ func ExampleSignTxOutput() {
|
|||
// Output:
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@ import (
|
|||
"bytes"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
|
||||
|
@ -27,7 +28,7 @@ type opcode struct {
|
|||
value byte
|
||||
name string
|
||||
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,
|
||||
|
@ -610,306 +611,52 @@ var opcodeOnelineRepls = map[string]string{
|
|||
"OP_16": "16",
|
||||
}
|
||||
|
||||
// parsedOpcode represents an opcode that has been parsed and includes any
|
||||
// potential data associated with it.
|
||||
type parsedOpcode struct {
|
||||
opcode *opcode
|
||||
data []byte
|
||||
}
|
||||
|
||||
// isDisabled 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 (pop *parsedOpcode) isDisabled() bool {
|
||||
switch pop.opcode.value {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// disasmOpcode writes a human-readable disassembly of the provided opcode and
|
||||
// data into the provided buffer. The compact flag indicates the disassembly
|
||||
// should print a more compact representation of data-carrying and small integer
|
||||
// opcodes. For example, OP_0 through OP_16 are replaced with the numeric value
|
||||
// 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.
|
||||
func disasmOpcode(buf *strings.Builder, op *opcode, data []byte, compact bool) {
|
||||
// Replace opcode which represent values (e.g. OP_0 through OP_16 and
|
||||
// OP_1NEGATE) with the raw value when performing a compact disassembly.
|
||||
opcodeName := op.name
|
||||
if compact {
|
||||
if replName, ok := opcodeOnelineRepls[opcodeName]; ok {
|
||||
opcodeName = replName
|
||||
}
|
||||
|
||||
// Nothing more to do for non-data push opcodes.
|
||||
if pop.opcode.length == 1 {
|
||||
return opcodeName
|
||||
// Either write the human-readable opcode or the parsed data in hex for
|
||||
// data-carrying opcodes.
|
||||
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.
|
||||
if pop.opcode.length == 1 {
|
||||
return opcodeName
|
||||
}
|
||||
buf.WriteString(opcodeName)
|
||||
|
||||
switch op.length {
|
||||
// Only write the opcode name for non-data push opcodes.
|
||||
case 1:
|
||||
return
|
||||
|
||||
// Add length for the OP_PUSHDATA# opcodes.
|
||||
retString := opcodeName
|
||||
switch pop.opcode.length {
|
||||
case -1:
|
||||
retString += fmt.Sprintf(" 0x%02x", len(pop.data))
|
||||
buf.WriteString(fmt.Sprintf(" 0x%02x", len(data)))
|
||||
case -2:
|
||||
retString += fmt.Sprintf(" 0x%04x", len(pop.data))
|
||||
buf.WriteString(fmt.Sprintf(" 0x%04x", len(data)))
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
buf.WriteString(fmt.Sprintf(" 0x%02x", data))
|
||||
}
|
||||
|
||||
// *******************************************
|
||||
|
@ -922,45 +669,42 @@ func (pop *parsedOpcode) bytes() ([]byte, error) {
|
|||
// opcodes before executing in an initial parse step, the consensus rules
|
||||
// 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).
|
||||
func opcodeDisabled(op *parsedOpcode, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute disabled opcode %s",
|
||||
op.opcode.name)
|
||||
func opcodeDisabled(op *opcode, data []byte, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name)
|
||||
return scriptError(ErrDisabledOpcode, str)
|
||||
}
|
||||
|
||||
// opcodeReserved is a common handler for all reserved opcodes. It returns an
|
||||
// appropriate error indicating the opcode is reserved.
|
||||
func opcodeReserved(op *parsedOpcode, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute reserved opcode %s",
|
||||
op.opcode.name)
|
||||
func opcodeReserved(op *opcode, data []byte, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute reserved opcode %s", op.name)
|
||||
return scriptError(ErrReservedOpcode, str)
|
||||
}
|
||||
|
||||
// opcodeInvalid is a common handler for all invalid opcodes. It returns an
|
||||
// appropriate error indicating the opcode is invalid.
|
||||
func opcodeInvalid(op *parsedOpcode, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute invalid opcode %s",
|
||||
op.opcode.name)
|
||||
func opcodeInvalid(op *opcode, data []byte, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute invalid opcode %s", op.name)
|
||||
return scriptError(ErrReservedOpcode, str)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
return nil
|
||||
}
|
||||
|
||||
// opcodePushData is a common handler for the vast majority of opcodes that push
|
||||
// raw data (bytes) to the data stack.
|
||||
func opcodePushData(op *parsedOpcode, vm *Engine) error {
|
||||
vm.dstack.PushByteArray(op.data)
|
||||
func opcodePushData(op *opcode, data []byte, vm *Engine) error {
|
||||
vm.dstack.PushByteArray(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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))
|
||||
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
|
||||
// pushes the numeric value the opcode represents (which will be from 1 to 16)
|
||||
// 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 difference.
|
||||
vm.dstack.PushInt(scriptNum((op.opcode.value - (OP_1 - 1))))
|
||||
vm.dstack.PushInt(scriptNum((op.value - (OP_1 - 1))))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// the flag to discourage use of NOPs is set for select opcodes.
|
||||
func opcodeNop(op *parsedOpcode, vm *Engine) error {
|
||||
switch op.opcode.value {
|
||||
func opcodeNop(op *opcode, data []byte, vm *Engine) error {
|
||||
switch op.value {
|
||||
case OP_NOP1, OP_NOP4, OP_NOP5,
|
||||
OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10:
|
||||
|
||||
if vm.hasFlag(ScriptDiscourageUpgradableNops) {
|
||||
str := fmt.Sprintf("OP_NOP%d reserved for soft-fork "+
|
||||
"upgrades", op.opcode.value-(OP_NOP1-1))
|
||||
str := fmt.Sprintf("%v reserved for soft-fork "+
|
||||
"upgrades", op.name)
|
||||
return scriptError(ErrDiscourageUpgradableNOPs, str)
|
||||
}
|
||||
}
|
||||
|
@ -1047,7 +792,7 @@ func popIfBool(vm *Engine) (bool, error) {
|
|||
//
|
||||
// Data stack transformation: [... bool] -> [...]
|
||||
// Conditional stack transformation: [...] -> [... OpCondValue]
|
||||
func opcodeIf(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeIf(op *opcode, data []byte, vm *Engine) error {
|
||||
condVal := OpCondFalse
|
||||
if vm.isBranchExecuting() {
|
||||
ok, err := popIfBool(vm)
|
||||
|
@ -1081,7 +826,7 @@ func opcodeIf(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Data stack transformation: [... bool] -> [...]
|
||||
// Conditional stack transformation: [...] -> [... OpCondValue]
|
||||
func opcodeNotIf(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeNotIf(op *opcode, data []byte, vm *Engine) error {
|
||||
condVal := OpCondFalse
|
||||
if vm.isBranchExecuting() {
|
||||
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.
|
||||
//
|
||||
// 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
//
|
||||
// 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// where the verification fails specifically due to the top item evaluating
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !verified {
|
||||
str := fmt.Sprintf("%s failed", op.opcode.name)
|
||||
str := fmt.Sprintf("%s failed", op.name)
|
||||
return scriptError(c, str)
|
||||
}
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
|
||||
// opcodeReturn returns an appropriate error since it is always an error to
|
||||
// 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")
|
||||
}
|
||||
|
||||
|
@ -1197,7 +942,7 @@ func verifyLockTime(txLockTime, threshold, lockTime int64) error {
|
|||
// validating if the transaction outputs are spendable yet. If flag
|
||||
// ScriptVerifyCheckLockTimeVerify is not set, the code continues as if OP_NOP2
|
||||
// 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
|
||||
// opcode as OP_NOP2 instead.
|
||||
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
|
||||
// ScriptVerifyCheckSequenceVerify is not set, the code continues as if OP_NOP3
|
||||
// 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
|
||||
// opcode as OP_NOP3 instead.
|
||||
if !vm.hasFlag(ScriptVerifyCheckSequenceVerify) {
|
||||
|
@ -1348,7 +1093,7 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2]
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1363,7 +1108,7 @@ func opcodeToAltStack(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 y3]
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1376,35 +1121,35 @@ func opcodeFromAltStack(op *parsedOpcode, vm *Engine) error {
|
|||
// opcode2Drop removes the top 2 items from the data stack.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// opcode2Dup duplicates the top 2 items on the data stack.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// opcode3Dup duplicates the top 3 items on the data stack.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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]
|
||||
func opcode2Over(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode2Over(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.OverN(2)
|
||||
}
|
||||
|
||||
// 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]
|
||||
func opcode2Rot(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode2Rot(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.RotN(2)
|
||||
}
|
||||
|
||||
|
@ -1412,7 +1157,7 @@ func opcode2Rot(op *parsedOpcode, vm *Engine) error {
|
|||
// before them.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
@ -1420,7 +1165,7 @@ func opcode2Swap(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Stack transformation (x1==0): [... 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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1440,7 +1185,7 @@ func opcodeIfDup(op *parsedOpcode, vm *Engine) error {
|
|||
// Stack transformation: [...] -> [... <num of items on the stack>]
|
||||
// Example with 2 items: [x1 x2] -> [x1 x2 2]
|
||||
// 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()))
|
||||
return nil
|
||||
}
|
||||
|
@ -1448,28 +1193,28 @@ func opcodeDepth(op *parsedOpcode, vm *Engine) error {
|
|||
// opcodeDrop removes the top item from the data stack.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// opcodeDup duplicates the top item on the data stack.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// opcodeNip removes the item before the top item on the data stack.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// opcodeOver duplicates the item before the top item on the data stack.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
@ -1479,7 +1224,7 @@ func opcodeOver(op *parsedOpcode, vm *Engine) error {
|
|||
// 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=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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1494,7 +1239,7 @@ func opcodePick(op *parsedOpcode, vm *Engine) error {
|
|||
// 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=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()
|
||||
if err != nil {
|
||||
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.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// opcodeSwap swaps the top two items on the stack.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
@ -1521,7 +1266,7 @@ func opcodeSwap(op *parsedOpcode, vm *Engine) error {
|
|||
// second-to-top item.
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
|
||||
|
@ -1529,7 +1274,7 @@ func opcodeTuck(op *parsedOpcode, vm *Engine) error {
|
|||
// stack.
|
||||
//
|
||||
// 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)
|
||||
if err != nil {
|
||||
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.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1564,8 +1309,8 @@ func opcodeEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// evaluates to true. An error is returned if it does not.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... bool] -> [...]
|
||||
func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error {
|
||||
err := opcodeEqual(op, vm)
|
||||
func opcodeEqualVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
err := opcodeEqual(op, data, vm)
|
||||
if err == nil {
|
||||
err = abstractVerify(op, vm, ErrEqualVerify)
|
||||
}
|
||||
|
@ -1576,7 +1321,7 @@ func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error {
|
|||
// it with its incremented value (plus 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1590,7 +1335,7 @@ func opcode1Add(op *parsedOpcode, vm *Engine) error {
|
|||
// it with its decremented value (minus 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1604,7 +1349,7 @@ func opcode1Sub(op *parsedOpcode, vm *Engine) error {
|
|||
// it with its negation.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1618,7 +1363,7 @@ func opcodeNegate(op *parsedOpcode, vm *Engine) error {
|
|||
// it with its absolute value.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
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 1] -> [... 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()
|
||||
if err != nil {
|
||||
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 1] -> [... 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1680,7 +1425,7 @@ func opcode0NotEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// them with their sum.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1700,7 +1445,7 @@ func opcodeAdd(op *parsedOpcode, vm *Engine) error {
|
|||
// entry.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
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): [... 0 7] -> [... 0]
|
||||
// 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()
|
||||
if err != nil {
|
||||
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): [... 0 7] -> [... 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()
|
||||
if err != nil {
|
||||
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 7] -> [... 0]
|
||||
func opcodeNumEqual(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeNumEqual(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1802,8 +1547,8 @@ func opcodeNumEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// to true. An error is returned if it does not.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... bool] -> [...]
|
||||
func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error {
|
||||
err := opcodeNumEqual(op, vm)
|
||||
func opcodeNumEqualVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
err := opcodeNumEqual(op, data, vm)
|
||||
if err == nil {
|
||||
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 7] -> [... 1]
|
||||
func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeNumNotEqual(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1840,7 +1585,7 @@ func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// otherwise a 0.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1865,7 +1610,7 @@ func opcodeLessThan(op *parsedOpcode, vm *Engine) error {
|
|||
// with a 1, otherwise a 0.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1889,7 +1634,7 @@ func opcodeGreaterThan(op *parsedOpcode, vm *Engine) error {
|
|||
// replaced with a 1, otherwise a 0.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1913,7 +1658,7 @@ func opcodeLessThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// item, they are replaced with a 1, otherwise a 0.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1937,7 +1682,7 @@ func opcodeGreaterThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// them with the minimum of the two.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1961,7 +1706,7 @@ func opcodeMin(op *parsedOpcode, vm *Engine) error {
|
|||
// them with the maximum of the two.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1989,7 +1734,7 @@ func opcodeMax(op *parsedOpcode, vm *Engine) error {
|
|||
// the third-to-top item is the value to test.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2023,7 +1768,7 @@ func calcHash(buf []byte, hasher hash.Hash) []byte {
|
|||
// replaces it with ripemd160(data).
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2037,7 +1782,7 @@ func opcodeRipemd160(op *parsedOpcode, vm *Engine) error {
|
|||
// with sha1(data).
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2052,7 +1797,7 @@ func opcodeSha1(op *parsedOpcode, vm *Engine) error {
|
|||
// it with sha256(data).
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2067,7 +1812,7 @@ func opcodeSha256(op *parsedOpcode, vm *Engine) error {
|
|||
// it with ripemd160(sha256(data)).
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2082,7 +1827,7 @@ func opcodeHash160(op *parsedOpcode, vm *Engine) error {
|
|||
// it with sha256(sha256(data)).
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2096,8 +1841,8 @@ func opcodeHash256(op *parsedOpcode, vm *Engine) error {
|
|||
// seen OP_CODESEPARATOR which is used during signature checking.
|
||||
//
|
||||
// This opcode does not change the contents of the data stack.
|
||||
func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error {
|
||||
vm.lastCodeSep = vm.scriptOff
|
||||
func opcodeCodeSeparator(op *opcode, data []byte, vm *Engine) error {
|
||||
vm.lastCodeSep = int(vm.tokenizer.ByteIndex())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2115,7 +1860,7 @@ func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error {
|
|||
// cryptographic methods against the provided public key.
|
||||
//
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2171,7 +1916,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
|||
sigHashes = NewTxSigHashes(&vm.tx)
|
||||
}
|
||||
|
||||
hash, err = calcWitnessSignatureHash(subScript, sigHashes, hashType,
|
||||
hash, err = calcWitnessSignatureHashRaw(subScript, sigHashes, hashType,
|
||||
&vm.tx, vm.txIdx, vm.inputAmount)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2230,9 +1975,9 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
|||
// The opcodeCheckSig function is invoked followed by opcodeVerify. See the
|
||||
// documentation for each of those opcodes for more details.
|
||||
//
|
||||
// Stack transformation: signature pubkey] -> [... bool] -> [...]
|
||||
func opcodeCheckSigVerify(op *parsedOpcode, vm *Engine) error {
|
||||
err := opcodeCheckSig(op, vm)
|
||||
// Stack transformation: [... signature pubkey] -> [... bool] -> [...]
|
||||
func opcodeCheckSigVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
err := opcodeCheckSig(op, data, vm)
|
||||
if err == nil {
|
||||
err = abstractVerify(op, vm, ErrCheckSigVerify)
|
||||
}
|
||||
|
@ -2267,7 +2012,7 @@ type parsedSigInfo struct {
|
|||
//
|
||||
// Stack transformation:
|
||||
// [... 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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2444,7 +2189,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
|||
sigHashes = NewTxSigHashes(&vm.tx)
|
||||
}
|
||||
|
||||
hash, err = calcWitnessSignatureHash(script, sigHashes, hashType,
|
||||
hash, err = calcWitnessSignatureHashRaw(script, sigHashes, hashType,
|
||||
&vm.tx, vm.txIdx, vm.inputAmount)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2493,8 +2238,8 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Stack transformation:
|
||||
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...]
|
||||
func opcodeCheckMultiSigVerify(op *parsedOpcode, vm *Engine) error {
|
||||
err := opcodeCheckMultiSig(op, vm)
|
||||
func opcodeCheckMultiSigVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
err := opcodeCheckMultiSig(op, data, vm)
|
||||
if err == nil {
|
||||
err = abstractVerify(op, vm, ErrCheckMultiSigVerify)
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ func TestOpcodeDisabled(t *testing.T) {
|
|||
OP_LSHIFT, OP_RSHIFT,
|
||||
}
|
||||
for _, opcodeVal := range tests {
|
||||
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: nil}
|
||||
err := opcodeDisabled(&pop, nil)
|
||||
op := &opcodeArray[opcodeVal]
|
||||
err := opcodeDisabled(op, nil, nil)
|
||||
if !IsErrorCode(err, ErrDisabledOpcode) {
|
||||
t.Errorf("opcodeDisabled: unexpected error - got %v, "+
|
||||
"want %v", err, ErrDisabledOpcode)
|
||||
|
@ -127,8 +127,9 @@ func TestOpcodeDisasm(t *testing.T) {
|
|||
expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
|
||||
}
|
||||
|
||||
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: data}
|
||||
gotStr := pop.print(true)
|
||||
var buf strings.Builder
|
||||
disasmOpcode(&buf, &opcodeArray[opcodeVal], data, true)
|
||||
gotStr := buf.String()
|
||||
if gotStr != expectedStr {
|
||||
t.Errorf("pop.print (opcode %x): Unexpected disasm "+
|
||||
"string - got %v, want %v", opcodeVal, gotStr,
|
||||
|
@ -193,8 +194,9 @@ func TestOpcodeDisasm(t *testing.T) {
|
|||
expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
|
||||
}
|
||||
|
||||
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: data}
|
||||
gotStr := pop.print(false)
|
||||
var buf strings.Builder
|
||||
disasmOpcode(&buf, &opcodeArray[opcodeVal], data, false)
|
||||
gotStr := buf.String()
|
||||
if gotStr != expectedStr {
|
||||
t.Errorf("pop.print (opcode %x): Unexpected disasm "+
|
||||
"string - got %v, want %v", opcodeVal, gotStr,
|
||||
|
|
|
@ -211,11 +211,12 @@ func computeNonWitnessPkScript(sigScript []byte) (PkScript, error) {
|
|||
// The redeem script will always be the last data push of the
|
||||
// signature script, so we'll parse the script into opcodes to
|
||||
// obtain it.
|
||||
parsedOpcodes, err := parseScript(sigScript)
|
||||
const scriptVersion = 0
|
||||
err := checkScriptParses(scriptVersion, sigScript)
|
||||
if err != nil {
|
||||
return PkScript{}, err
|
||||
}
|
||||
redeemScript := parsedOpcodes[len(parsedOpcodes)-1].data
|
||||
redeemScript := finalOpcodeData(scriptVersion, sigScript)
|
||||
|
||||
scriptHash := hash160(redeemScript)
|
||||
script, err := payToScriptHashScript(scriptHash)
|
||||
|
|
|
@ -836,6 +836,7 @@ func TestCalcSignatureHash(t *testing.T) {
|
|||
err)
|
||||
}
|
||||
|
||||
const scriptVersion = 0
|
||||
for i, test := range tests {
|
||||
if i == 0 {
|
||||
// Skip first line -- contains comments only.
|
||||
|
@ -855,16 +856,20 @@ func TestCalcSignatureHash(t *testing.T) {
|
|||
}
|
||||
|
||||
subScript, _ := hex.DecodeString(test[1].(string))
|
||||
parsedScript, err := parseScript(subScript)
|
||||
if err != nil {
|
||||
if err := checkScriptParses(scriptVersion, subScript); err != nil {
|
||||
t.Errorf("TestCalcSignatureHash failed test #%d: "+
|
||||
"Failed to parse sub-script: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
hashType := SigHashType(testVecF64ToUint32(test[3].(float64)))
|
||||
hash := calcSignatureHash(parsedScript, hashType, &tx,
|
||||
hash, err := CalcSignatureHash(subScript, hashType, &tx,
|
||||
int(test[2].(float64)))
|
||||
if err != nil {
|
||||
t.Errorf("TestCalcSignatureHash failed test #%d: "+
|
||||
"Failed to compute sighash: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
expectedHash, _ := chainhash.NewHashFromStr(test[4].(string))
|
||||
if !bytes.Equal(hash, expectedHash[:]) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -8,6 +9,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
@ -44,66 +46,48 @@ const (
|
|||
|
||||
// isSmallInt returns whether or not the opcode is considered a small integer,
|
||||
// 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) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
//
|
||||
// NOTE: This function is only valid for version 0 opcodes. Since the function
|
||||
// does not accept a script version, the results are undefined for other script
|
||||
// versions.
|
||||
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
|
||||
// transaction, false otherwise.
|
||||
func isScriptHash(pops []parsedOpcode) bool {
|
||||
return len(pops) == 3 &&
|
||||
pops[0].opcode.value == OP_HASH160 &&
|
||||
pops[1].opcode.value == OP_DATA_20 &&
|
||||
pops[2].opcode.value == OP_EQUAL
|
||||
// IsPayToPubKey returns true if the script is in the standard pay-to-pubkey
|
||||
// (P2PK) format, false otherwise.
|
||||
func IsPayToPubKey(script []byte) bool {
|
||||
return isPubKeyScript(script)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
pops, err := parseScript(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
|
||||
return isScriptHashScript(script)
|
||||
}
|
||||
|
||||
// IsPayToWitnessScriptHash returns true if the is in the standard
|
||||
// pay-to-witness-script-hash (P2WSH) format, false otherwise.
|
||||
func IsPayToWitnessScriptHash(script []byte) bool {
|
||||
pops, err := parseScript(script)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return isWitnessScriptHash(pops)
|
||||
return isWitnessScriptHashScript(script)
|
||||
}
|
||||
|
||||
// IsPayToWitnessPubKeyHash returns true if the is in the standard
|
||||
// pay-to-witness-pubkey-hash (P2WKH) format, false otherwise.
|
||||
func IsPayToWitnessPubKeyHash(script []byte) bool {
|
||||
pops, err := parseScript(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
|
||||
return isWitnessPubKeyHashScript(script)
|
||||
}
|
||||
|
||||
// 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
|
||||
// of pushed data.
|
||||
func IsWitnessProgram(script []byte) bool {
|
||||
// The length of the script must be between 4 and 42 bytes. The
|
||||
// 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)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return isWitnessProgram(pops)
|
||||
return isWitnessProgramScript(script)
|
||||
}
|
||||
|
||||
// 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)
|
||||
// IsNullData returns true if the passed script is a null data script, false
|
||||
// otherwise.
|
||||
func IsNullData(script []byte) bool {
|
||||
const scriptVersion = 0
|
||||
return isNullDataScript(scriptVersion, script)
|
||||
}
|
||||
|
||||
// ExtractWitnessProgramInfo attempts to extract the witness program version,
|
||||
// as well as the witness program itself from the passed script.
|
||||
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,
|
||||
// then we'll exit early as there isn't a valid version or program to
|
||||
// extract.
|
||||
if !isWitnessProgram(pops) {
|
||||
version, program, valid := extractWitnessProgramInfo(script)
|
||||
if !valid {
|
||||
return 0, nil, fmt.Errorf("script is not a witness program, " +
|
||||
"unable to extract version or witness program")
|
||||
}
|
||||
|
||||
witnessVersion := asSmallInt(pops[0].opcode)
|
||||
witnessProgram := pops[1].data
|
||||
|
||||
return witnessVersion, witnessProgram, nil
|
||||
return version, program, nil
|
||||
}
|
||||
|
||||
// isPushOnly returns true if the script only pushes data, false otherwise.
|
||||
func isPushOnly(pops []parsedOpcode) bool {
|
||||
// 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.
|
||||
// IsPushOnlyScript returns whether or not the passed script only pushes data
|
||||
// according to the consensus definition of pushing 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 {
|
||||
pops, err := parseScript(script)
|
||||
if err != nil {
|
||||
const scriptVersion = 0
|
||||
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 isPushOnly(pops)
|
||||
}
|
||||
|
||||
// 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
|
||||
return tokenizer.Err() == nil
|
||||
}
|
||||
|
||||
// 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]'
|
||||
// appended. In addition, the reason the script failed to parse is returned
|
||||
// if the caller wants more information about the failure.
|
||||
func DisasmString(buf []byte) (string, error) {
|
||||
var disbuf bytes.Buffer
|
||||
opcodes, err := parseScript(buf)
|
||||
for _, pop := range opcodes {
|
||||
disbuf.WriteString(pop.print(true))
|
||||
//
|
||||
// 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 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(' ')
|
||||
}
|
||||
if disbuf.Len() > 0 {
|
||||
disbuf.Truncate(disbuf.Len() - 1)
|
||||
}
|
||||
if err != nil {
|
||||
disbuf.WriteString("[error]")
|
||||
}
|
||||
return disbuf.String(), err
|
||||
return disbuf.String(), tokenizer.Err()
|
||||
}
|
||||
|
||||
// removeOpcode will remove any opcode matching ``opcode'' from the opcode
|
||||
// stream in pkscript
|
||||
func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode {
|
||||
retScript := make([]parsedOpcode, 0, len(pkscript))
|
||||
for _, pop := range pkscript {
|
||||
if pop.opcode.value != opcode {
|
||||
retScript = append(retScript, pop)
|
||||
// removeOpcodeRaw will return the script after removing any opcodes that match
|
||||
// `opcode`. If the opcode does not appear in script, the original script will
|
||||
// be returned unmodified. Otherwise, a new script will be allocated to contain
|
||||
// the filtered script. This metehod assumes that the script parses
|
||||
// successfully.
|
||||
//
|
||||
// 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 removeOpcodeRaw(script []byte, opcode byte) []byte {
|
||||
// Avoid work when possible.
|
||||
if len(script) == 0 {
|
||||
return script
|
||||
}
|
||||
|
||||
const scriptVersion = 0
|
||||
var result []byte
|
||||
var prevOffset int32
|
||||
|
||||
tokenizer := MakeScriptTokenizer(scriptVersion, script)
|
||||
for tokenizer.Next() {
|
||||
if tokenizer.Opcode() == opcode {
|
||||
if result == nil {
|
||||
result = make([]byte, 0, len(script))
|
||||
result = append(result, script[:prevOffset]...)
|
||||
}
|
||||
return retScript
|
||||
} else if result != nil {
|
||||
result = append(result, script[prevOffset:tokenizer.ByteIndex()]...)
|
||||
}
|
||||
prevOffset = tokenizer.ByteIndex()
|
||||
}
|
||||
if result == nil {
|
||||
return script
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// canonicalPush returns true if the object is either not a push instruction
|
||||
// or the push instruction contained wherein is matches the canonical form
|
||||
// or using the smallest instruction to do the job. False otherwise.
|
||||
func canonicalPush(pop parsedOpcode) bool {
|
||||
opcode := pop.opcode.value
|
||||
data := pop.data
|
||||
dataLen := len(pop.data)
|
||||
// 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 {
|
||||
return true
|
||||
}
|
||||
|
@ -329,17 +239,57 @@ func canonicalPush(pop parsedOpcode) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// removeOpcodeByData will return the script minus any opcodes that would push
|
||||
// the passed data to the stack.
|
||||
func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode {
|
||||
retScript := make([]parsedOpcode, 0, len(pkscript))
|
||||
for _, pop := range pkscript {
|
||||
if !canonicalPush(pop) || !bytes.Contains(pop.data, data) {
|
||||
retScript = append(retScript, pop)
|
||||
// removeOpcodeByData will return the script minus any opcodes that perform a
|
||||
// canonical push of data that contains the passed data to remove. This
|
||||
// function assumes it is provided a version 0 script as any future version of
|
||||
// script should avoid this functionality since it is unncessary due to the
|
||||
// signature scripts not being part of the witness-free transaction hash.
|
||||
//
|
||||
// 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
|
||||
|
@ -396,7 +346,7 @@ func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
|
|||
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
|
||||
// in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki.
|
||||
// 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
|
||||
// wallet if fed an invalid input amount, the real sighash will differ causing
|
||||
// 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) {
|
||||
|
||||
// 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)
|
||||
sigHash.Write(bIndex[:])
|
||||
|
||||
if isWitnessPubKeyHash(subScript) {
|
||||
if isWitnessPubKeyHashScript(scriptSig) {
|
||||
// The script code for a p2wkh is a length prefix varint for
|
||||
// the next 25 bytes, followed by a re-creation of the original
|
||||
// p2pkh pk script.
|
||||
|
@ -465,15 +415,14 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes,
|
|||
sigHash.Write([]byte{OP_DUP})
|
||||
sigHash.Write([]byte{OP_HASH160})
|
||||
sigHash.Write([]byte{OP_DATA_20})
|
||||
sigHash.Write(subScript[1].data)
|
||||
sigHash.Write(extractWitnessPubKeyHash(scriptSig))
|
||||
sigHash.Write([]byte{OP_EQUALVERIFY})
|
||||
sigHash.Write([]byte{OP_CHECKSIG})
|
||||
} else {
|
||||
// For p2wsh outputs, and future outputs, the script code is
|
||||
// the original script, with all code separators removed,
|
||||
// serialized with a var int length prefix.
|
||||
rawScript, _ := unparseScript(subScript)
|
||||
wire.WriteVarBytes(&sigHash, 0, rawScript)
|
||||
wire.WriteVarBytes(&sigHash, 0, scriptSig)
|
||||
}
|
||||
|
||||
// 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,
|
||||
tx *wire.MsgTx, idx int, amt int64) ([]byte, error) {
|
||||
|
||||
parsedScript, err := parseScript(script)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse output script: %v", err)
|
||||
const scriptVersion = 0
|
||||
if err := checkScriptParses(scriptVersion, script); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return calcWitnessSignatureHash(parsedScript, sigHashes, hType, tx, idx,
|
||||
amt)
|
||||
return calcWitnessSignatureHashRaw(script, sigHashes, hType, tx, idx, amt)
|
||||
}
|
||||
|
||||
// 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
|
||||
// engine instance, calculate the signature hash to be used for signing and
|
||||
// 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) {
|
||||
parsedScript, err := parseScript(script)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse output script: %v", err)
|
||||
const scriptVersion = 0
|
||||
if err := checkScriptParses(scriptVersion, script); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return calcSignatureHash(parsedScript, hashType, tx, idx), nil
|
||||
|
||||
return calcSignatureHash(script, hashType, tx, idx), nil
|
||||
}
|
||||
|
||||
// calcSignatureHash will, given a script and hash type for the current script
|
||||
// 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
|
||||
// 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.
|
||||
script = removeOpcode(script, OP_CODESEPARATOR)
|
||||
sigScript = removeOpcodeRaw(sigScript, OP_CODESEPARATOR)
|
||||
|
||||
// Make a shallow copy of the transaction, zeroing out the script for
|
||||
// all inputs that are not currently being processed.
|
||||
txCopy := shallowCopyTx(tx)
|
||||
for i := range txCopy.TxIn {
|
||||
if i == idx {
|
||||
// UnparseScript cannot fail here because removeOpcode
|
||||
// above only returns a valid script.
|
||||
sigScript, _ := unparseScript(script)
|
||||
txCopy.TxIn[idx].SignatureScript = sigScript
|
||||
} else {
|
||||
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
|
||||
// isSmallInt(), as an integer.
|
||||
func asSmallInt(op *opcode) int {
|
||||
if op.value == OP_0 {
|
||||
func asSmallInt(op byte) int {
|
||||
if op == OP_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
|
||||
// signature operations in the script provided by pops. If precise mode is
|
||||
// requested then we attempt to count the number of operations for a multisig
|
||||
// op. Otherwise we use the maximum.
|
||||
func getSigOpCount(pops []parsedOpcode, precise bool) int {
|
||||
nSigs := 0
|
||||
for i, pop := range pops {
|
||||
switch pop.opcode.value {
|
||||
case OP_CHECKSIG:
|
||||
fallthrough
|
||||
case OP_CHECKSIGVERIFY:
|
||||
nSigs++
|
||||
case OP_CHECKMULTISIG:
|
||||
fallthrough
|
||||
case OP_CHECKMULTISIGVERIFY:
|
||||
// If we are being precise then look for familiar
|
||||
// patterns for multisig, for now all we recognize is
|
||||
// OP_1 - OP_16 to signify the number of pubkeys.
|
||||
// Otherwise, we use the max of 20.
|
||||
if precise && i > 0 &&
|
||||
pops[i-1].opcode.value >= OP_1 &&
|
||||
pops[i-1].opcode.value <= OP_16 {
|
||||
nSigs += asSmallInt(pops[i-1].opcode)
|
||||
// countSigOpsV0 returns the number of signature operations in the provided
|
||||
// script up to the point of the first parse failure or the entire script when
|
||||
// there are no parse failures. The precise flag attempts to accurately count
|
||||
// the number of operations for a multisig operation versus using the maximum
|
||||
// allowed.
|
||||
//
|
||||
// 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 countSigOpsV0(script []byte, precise bool) int {
|
||||
const scriptVersion = 0
|
||||
|
||||
numSigOps := 0
|
||||
tokenizer := MakeScriptTokenizer(scriptVersion, script)
|
||||
prevOp := byte(OP_INVALIDOPCODE)
|
||||
for tokenizer.Next() {
|
||||
switch tokenizer.Opcode() {
|
||||
case OP_CHECKSIG, OP_CHECKSIGVERIFY:
|
||||
numSigOps++
|
||||
|
||||
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 {
|
||||
nSigs += MaxPubKeysPerMultiSig
|
||||
numSigOps += MaxPubKeysPerMultiSig
|
||||
}
|
||||
|
||||
default:
|
||||
// Not a sigop.
|
||||
}
|
||||
|
||||
prevOp = tokenizer.Opcode()
|
||||
}
|
||||
|
||||
return nSigs
|
||||
return numSigOps
|
||||
}
|
||||
|
||||
// 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.
|
||||
// If the script fails to parse, then the count up to the point of failure is
|
||||
// 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 {
|
||||
// Don't check error since parseScript returns the parsed-up-to-error
|
||||
// list of pops.
|
||||
pops, _ := parseScript(script)
|
||||
return getSigOpCount(pops, false)
|
||||
return countSigOpsV0(script, 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
|
||||
|
@ -720,44 +710,44 @@ func GetSigOpCount(script []byte) int {
|
|||
// 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
|
||||
// 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
|
||||
// list of pops.
|
||||
pops, _ := parseScript(scriptPubKey)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// The third parameter is DEPRECATED and is unused.
|
||||
func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, _ bool) int {
|
||||
const scriptVersion = 0
|
||||
|
||||
// Treat non P2SH transactions as normal.
|
||||
if !(bip16 && isScriptHash(pops)) {
|
||||
return getSigOpCount(pops, 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
|
||||
// Treat non P2SH transactions as normal. Note that signature operation
|
||||
// counting includes all operations up to the first parse failure.
|
||||
if !isScriptHashScript(scriptPubKey) {
|
||||
return countSigOpsV0(scriptPubKey, true)
|
||||
}
|
||||
|
||||
// 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
|
||||
// the case.
|
||||
if !isPushOnly(sigPops) || len(sigPops) == 0 {
|
||||
if len(scriptSig) == 0 || !IsPushOnlyScript(scriptSig) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// The P2SH script is the last item the signature script pushes to the
|
||||
// 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
|
||||
}
|
||||
|
||||
// Parse the P2SH script and don't check the error since parseScript
|
||||
// returns the parsed-up-to-error list of pops and the consensus rules
|
||||
// dictate signature operations are counted up to the first parse
|
||||
// failure.
|
||||
shPops, _ := parseScript(shScript)
|
||||
return getSigOpCount(shPops, true)
|
||||
// Return the more precise sigops count for the redeem script. Note that
|
||||
// signature operation counting includes all operations up to the first
|
||||
// parse failure.
|
||||
return countSigOpsV0(redeemScript, true)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// If this is a regular witness program, then we can proceed directly
|
||||
// to counting its signature operations without any further processing.
|
||||
if IsWitnessProgram(pkScript) {
|
||||
if isWitnessProgramScript(pkScript) {
|
||||
return getWitnessSigOps(pkScript, witness)
|
||||
}
|
||||
|
||||
// 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
|
||||
// datapush of a p2wsh witness program.
|
||||
sigPops, err := parseScript(sigScript)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
if IsPayToScriptHash(pkScript) && isPushOnly(sigPops) &&
|
||||
IsWitnessProgram(sigScript[1:]) {
|
||||
if isScriptHashScript(pkScript) && IsPushOnlyScript(sigScript) &&
|
||||
len(sigScript) > 0 && isWitnessProgramScript(sigScript[1:]) {
|
||||
return getWitnessSigOps(sigScript[1:], witness)
|
||||
}
|
||||
|
||||
|
@ -811,26 +797,41 @@ func getWitnessSigOps(pkScript []byte, witness wire.TxWitness) int {
|
|||
len(witness) > 0:
|
||||
|
||||
witnessScript := witness[len(witness)-1]
|
||||
pops, _ := parseScript(witnessScript)
|
||||
return getSigOpCount(pops, true)
|
||||
return countSigOpsV0(witnessScript, true)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// guaranteed to fail at execution. This allows inputs to be pruned instantly
|
||||
// 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 {
|
||||
// Not provably unspendable
|
||||
if len(pkScript) == 0 {
|
||||
return false
|
||||
}
|
||||
firstOpcode, err := checkScriptTemplateParseable(pkScript, &opcodeArray)
|
||||
if err != nil {
|
||||
// The script is unspendable if starts with OP_RETURN or is guaranteed
|
||||
// to fail at execution due to being larger than the max allowed script
|
||||
// size.
|
||||
switch {
|
||||
case len(pkScript) > 0 && pkScript[0] == OP_RETURN:
|
||||
return true
|
||||
case len(pkScript) > MaxScriptSize:
|
||||
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
|
@ -12,9 +12,21 @@ const (
|
|||
maxInt32 = 1<<31 - 1
|
||||
minInt32 = -1 << 31
|
||||
|
||||
// defaultScriptNumLen is the default number of bytes
|
||||
// data being interpreted as an integer may be.
|
||||
defaultScriptNumLen = 4
|
||||
// maxScriptNumLen is the maximum number of bytes data being interpreted
|
||||
// as an integer may be for the majority of op codes.
|
||||
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
|
||||
|
@ -178,7 +190,7 @@ func (n scriptNum) Int32() int32 {
|
|||
// before an ErrStackNumberTooBig is returned. This effectively limits the
|
||||
// range of allowed values.
|
||||
// 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.
|
||||
//
|
||||
// See the Bytes function documentation for example encodings.
|
||||
|
|
|
@ -104,35 +104,35 @@ func TestMakeScriptNum(t *testing.T) {
|
|||
err error
|
||||
}{
|
||||
// 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.
|
||||
// Should not error and return expected integral number.
|
||||
{nil, 0, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("01"), 1, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("81"), -1, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("7f"), 127, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("ff"), -127, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("8000"), 128, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("8080"), -128, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("8100"), 129, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("8180"), -129, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("0001"), 256, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("0081"), -256, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("ff7f"), 32767, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("ffff"), -32767, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("008000"), 32768, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("008080"), -32768, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("ffff00"), 65535, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("ffff80"), -65535, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("000008"), 524288, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("000088"), -524288, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("000070"), 7340032, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("0000f0"), -7340032, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("00008000"), 8388608, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("00008080"), -8388608, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("ffffff7f"), 2147483647, defaultScriptNumLen, true, nil},
|
||||
{hexToBytes("ffffffff"), -2147483647, defaultScriptNumLen, true, nil},
|
||||
{nil, 0, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("01"), 1, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("81"), -1, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("7f"), 127, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("ff"), -127, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("8000"), 128, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("8080"), -128, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("8100"), 129, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("8180"), -129, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("0001"), 256, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("0081"), -256, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("ff7f"), 32767, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("ffff"), -32767, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("008000"), 32768, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("008080"), -32768, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("ffff00"), 65535, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("ffff80"), -65535, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("000008"), 524288, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("000088"), -524288, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("000070"), 7340032, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("0000f0"), -7340032, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("00008000"), 8388608, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("00008080"), -8388608, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("ffffff7f"), 2147483647, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("ffffffff"), -2147483647, maxScriptNumLen, true, nil},
|
||||
{hexToBytes("ffffffff7f"), 549755813887, 5, true, nil},
|
||||
{hexToBytes("ffffffffff"), -549755813887, 5, 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
|
||||
// is interpreted as script numbers with the minimal encoding
|
||||
// flag set. Should error and return 0.
|
||||
{hexToBytes("0000008000"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000008080"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000009000"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000009080"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000000001"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000000081"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffffff7f"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffffffff"), 0, defaultScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000008000"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000008080"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000009000"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000009080"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffff00"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffff80"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000000001"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("0000000081"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffff00"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffff80"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffffff00"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffffff80"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffffff7f"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
{hexToBytes("ffffffffffffffff"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||
|
||||
// Non-minimally encoded, but otherwise valid values with
|
||||
// minimal encoding flag. Should error and return 0.
|
||||
{hexToBytes("00"), 0, defaultScriptNumLen, true, errMinimalData}, // 0
|
||||
{hexToBytes("0100"), 0, defaultScriptNumLen, true, errMinimalData}, // 1
|
||||
{hexToBytes("7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 127
|
||||
{hexToBytes("800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 128
|
||||
{hexToBytes("810000"), 0, defaultScriptNumLen, true, errMinimalData}, // 129
|
||||
{hexToBytes("000100"), 0, defaultScriptNumLen, true, errMinimalData}, // 256
|
||||
{hexToBytes("ff7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 32767
|
||||
{hexToBytes("00800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 32768
|
||||
{hexToBytes("ffff0000"), 0, defaultScriptNumLen, true, errMinimalData}, // 65535
|
||||
{hexToBytes("00000800"), 0, defaultScriptNumLen, true, errMinimalData}, // 524288
|
||||
{hexToBytes("00007000"), 0, defaultScriptNumLen, true, errMinimalData}, // 7340032
|
||||
{hexToBytes("00"), 0, maxScriptNumLen, true, errMinimalData}, // 0
|
||||
{hexToBytes("0100"), 0, maxScriptNumLen, true, errMinimalData}, // 1
|
||||
{hexToBytes("7f00"), 0, maxScriptNumLen, true, errMinimalData}, // 127
|
||||
{hexToBytes("800000"), 0, maxScriptNumLen, true, errMinimalData}, // 128
|
||||
{hexToBytes("810000"), 0, maxScriptNumLen, true, errMinimalData}, // 129
|
||||
{hexToBytes("000100"), 0, maxScriptNumLen, true, errMinimalData}, // 256
|
||||
{hexToBytes("ff7f00"), 0, maxScriptNumLen, true, errMinimalData}, // 32767
|
||||
{hexToBytes("00800000"), 0, maxScriptNumLen, true, errMinimalData}, // 32768
|
||||
{hexToBytes("ffff0000"), 0, maxScriptNumLen, true, errMinimalData}, // 65535
|
||||
{hexToBytes("00000800"), 0, maxScriptNumLen, true, errMinimalData}, // 524288
|
||||
{hexToBytes("00007000"), 0, maxScriptNumLen, true, errMinimalData}, // 7340032
|
||||
{hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520
|
||||
|
||||
// Non-minimally encoded, but otherwise valid values without
|
||||
// minimal encoding flag. Should not error and return expected
|
||||
// integral number.
|
||||
{hexToBytes("00"), 0, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("0100"), 1, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("7f00"), 127, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("800000"), 128, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("810000"), 129, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("000100"), 256, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("ff7f00"), 32767, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("00800000"), 32768, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("ffff0000"), 65535, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("00000800"), 524288, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("00007000"), 7340032, defaultScriptNumLen, false, nil},
|
||||
{hexToBytes("00"), 0, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("0100"), 1, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("7f00"), 127, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("800000"), 128, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("810000"), 129, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("000100"), 256, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("ff7f00"), 32767, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("00800000"), 32768, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("ffff0000"), 65535, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("00000800"), 524288, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("00007000"), 7340032, maxScriptNumLen, false, nil},
|
||||
{hexToBytes("0009000100"), 16779520, 5, false, nil},
|
||||
}
|
||||
|
||||
|
|
201
txscript/sign.go
201
txscript/sign.go
|
@ -22,12 +22,7 @@ func RawTxInWitnessSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int,
|
|||
amt int64, subScript []byte, hashType SigHashType,
|
||||
key *btcec.PrivateKey) ([]byte, error) {
|
||||
|
||||
parsedScript, err := parseScript(subScript)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse output script: %v", err)
|
||||
}
|
||||
|
||||
hash, err := calcWitnessSignatureHash(parsedScript, sigHashes, hashType, tx,
|
||||
hash, err := calcWitnessSignatureHashRaw(subScript, sigHashes, hashType, tx,
|
||||
idx, amt)
|
||||
if err != nil {
|
||||
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
|
||||
// that both provide signatures for pkScript in output idx of tx. addresses
|
||||
// and nRequired should be the results from extracting the addresses from
|
||||
// pkScript. Since this function is internal only we assume that the arguments
|
||||
// have come from other functions internally and thus are all consistent with
|
||||
// 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,
|
||||
nRequired int, pkScript, sigScript, prevScript []byte) []byte {
|
||||
|
||||
// This is an internal only function and we already parsed this script
|
||||
// as ok for multisig (this is how we got here), so if this fails then
|
||||
// all assumptions are broken and who knows which way is up?
|
||||
pkPops, _ := parseScript(pkScript)
|
||||
|
||||
sigPops, err := parseScript(sigScript)
|
||||
if err != nil || len(sigPops) == 0 {
|
||||
// Nothing to merge if either the new or previous signature scripts are
|
||||
// empty.
|
||||
if len(sigScript) == 0 {
|
||||
return prevScript
|
||||
}
|
||||
|
||||
prevPops, err := parseScript(prevScript)
|
||||
if err != nil || len(prevPops) == 0 {
|
||||
if len(prevScript) == 0 {
|
||||
return sigScript
|
||||
}
|
||||
|
||||
// Convenience function to avoid duplication.
|
||||
extractSigs := func(pops []parsedOpcode, sigs [][]byte) [][]byte {
|
||||
for _, pop := range pops {
|
||||
if len(pop.data) != 0 {
|
||||
sigs = append(sigs, pop.data)
|
||||
var possibleSigs [][]byte
|
||||
extractSigs := func(script []byte) error {
|
||||
const scriptVersion = 0
|
||||
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))
|
||||
possibleSigs = extractSigs(sigPops, possibleSigs)
|
||||
possibleSigs = extractSigs(prevPops, possibleSigs)
|
||||
// Attempt to extract signatures from the two scripts. Return the other
|
||||
// script that is intended to be merged in the case signature extraction
|
||||
// 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
|
||||
// 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
|
||||
// would make the transaction nonstandard and thus not
|
||||
// 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 {
|
||||
// All multisig addresses should be pubkey addresses
|
||||
|
@ -394,6 +329,81 @@ sigLoop:
|
|||
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
|
||||
// any user state required to get the private keys for an address.
|
||||
type KeyDB interface {
|
||||
|
@ -404,8 +414,7 @@ type KeyDB interface {
|
|||
type KeyClosure func(btcutil.Address) (*btcec.PrivateKey, bool, error)
|
||||
|
||||
// GetKey implements KeyDB by returning the result of calling the closure.
|
||||
func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey,
|
||||
bool, error) {
|
||||
func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey, bool, error) {
|
||||
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
|
||||
// will be merged in a type-dependent manner with the newly generated.
|
||||
// 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,
|
||||
pkScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB,
|
||||
previousScript []byte) ([]byte, error) {
|
||||
|
|
|
@ -86,7 +86,7 @@ func (s *stack) PopInt() (scriptNum, error) {
|
|||
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
|
||||
|
@ -123,7 +123,7 @@ func (s *stack) PeekInt(idx int32) (scriptNum, error) {
|
|||
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.
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -832,7 +832,7 @@ func TestCalcMultiSigStats(t *testing.T) {
|
|||
name: "short script",
|
||||
script: "0x046708afdb0fe5548271967f1a67130b7105cd6a828" +
|
||||
"e03909a67962e0ea1f61d",
|
||||
err: scriptError(ErrMalformedPush, ""),
|
||||
err: scriptError(ErrNotMultisigScript, ""),
|
||||
},
|
||||
{
|
||||
name: "stack underflow",
|
||||
|
@ -843,11 +843,7 @@ func TestCalcMultiSigStats(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "multisig script",
|
||||
script: "0 DATA_72 0x30450220106a3e4ef0b51b764a2887226" +
|
||||
"2ffef55846514dacbdcbbdd652c849d395b4384022100" +
|
||||
"e03ae554c3cbb40600d31dd46fc33f25e47bf8525b1fe" +
|
||||
"07282e3b6ecb5f3bb2801 CODESEPARATOR 1 DATA_33 " +
|
||||
"0x0232abdc893e7f0631364d7fd01cb33d24da45329a0" +
|
||||
script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da45329a0" +
|
||||
"0357b3a7886211ab414d55a 1 CHECKMULTISIG",
|
||||
err: nil,
|
||||
},
|
||||
|
|
186
txscript/tokenizer.go
Normal file
186
txscript/tokenizer.go
Normal 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
259
txscript/tokenizer_test.go
Normal 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")
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue