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)
|
* [Manually Signing a Transaction Output](https://pkg.go.dev/github.com/btcsuite/btcd/txscript#example-SignTxOutput)
|
||||||
Demonstrates manually creating and signing a redeem transaction.
|
Demonstrates manually creating and signing a redeem transaction.
|
||||||
|
|
||||||
|
* [Counting Opcodes in Scripts](http://godoc.org/github.com/decred/dcrd/txscript#example-ScriptTokenizer)
|
||||||
|
Demonstrates creating a script tokenizer instance and using it to count the
|
||||||
|
number of opcodes a script contains.
|
||||||
|
|
||||||
## GPG Verification Key
|
## GPG Verification Key
|
||||||
|
|
||||||
All official release tags are signed by Conformal so users can ensure the code
|
All official release tags are signed by Conformal so users can ensure the code
|
||||||
|
|
537
txscript/bench_test.go
Normal file
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"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
@ -118,21 +119,84 @@ var halfOrder = new(big.Int).Rsh(btcec.S256().N, 1)
|
||||||
|
|
||||||
// Engine is the virtual machine that executes scripts.
|
// Engine is the virtual machine that executes scripts.
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
scripts [][]parsedOpcode
|
// The following fields are set when the engine is created and must not be
|
||||||
|
// 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
|
||||||
|
version uint16
|
||||||
|
bip16 bool
|
||||||
|
sigCache *SigCache
|
||||||
|
hashCache *TxSigHashes
|
||||||
|
|
||||||
|
// 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
|
scriptIdx int
|
||||||
scriptOff int
|
opcodeIdx int
|
||||||
lastCodeSep int
|
lastCodeSep int
|
||||||
dstack stack // data stack
|
tokenizer ScriptTokenizer
|
||||||
astack stack // alt stack
|
savedFirstStack [][]byte
|
||||||
tx wire.MsgTx
|
dstack stack
|
||||||
txIdx int
|
astack stack
|
||||||
condStack []int
|
condStack []int
|
||||||
numOps int
|
numOps int
|
||||||
flags ScriptFlags
|
|
||||||
sigCache *SigCache
|
|
||||||
hashCache *TxSigHashes
|
|
||||||
bip16 bool // treat execution as pay-to-script-hash
|
|
||||||
savedFirstStack [][]byte // stack from first script for bip16 scripts
|
|
||||||
witnessVersion int
|
witnessVersion int
|
||||||
witnessProgram []byte
|
witnessProgram []byte
|
||||||
inputAmount int64
|
inputAmount int64
|
||||||
|
@ -154,26 +218,144 @@ func (vm *Engine) isBranchExecuting() bool {
|
||||||
return vm.condStack[len(vm.condStack)-1] == OpCondTrue
|
return vm.condStack[len(vm.condStack)-1] == OpCondTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isOpcodeDisabled returns whether or not the opcode is disabled and thus is
|
||||||
|
// always bad to see in the instruction stream (even if turned off by a
|
||||||
|
// conditional).
|
||||||
|
func isOpcodeDisabled(opcode byte) bool {
|
||||||
|
switch opcode {
|
||||||
|
case OP_CAT:
|
||||||
|
return true
|
||||||
|
case OP_SUBSTR:
|
||||||
|
return true
|
||||||
|
case OP_LEFT:
|
||||||
|
return true
|
||||||
|
case OP_RIGHT:
|
||||||
|
return true
|
||||||
|
case OP_INVERT:
|
||||||
|
return true
|
||||||
|
case OP_AND:
|
||||||
|
return true
|
||||||
|
case OP_OR:
|
||||||
|
return true
|
||||||
|
case OP_XOR:
|
||||||
|
return true
|
||||||
|
case OP_2MUL:
|
||||||
|
return true
|
||||||
|
case OP_2DIV:
|
||||||
|
return true
|
||||||
|
case OP_MUL:
|
||||||
|
return true
|
||||||
|
case OP_DIV:
|
||||||
|
return true
|
||||||
|
case OP_MOD:
|
||||||
|
return true
|
||||||
|
case OP_LSHIFT:
|
||||||
|
return true
|
||||||
|
case OP_RSHIFT:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isOpcodeAlwaysIllegal returns whether or not the opcode is always illegal
|
||||||
|
// when passed over by the program counter even if in a non-executed branch (it
|
||||||
|
// isn't a coincidence that they are conditionals).
|
||||||
|
func isOpcodeAlwaysIllegal(opcode byte) bool {
|
||||||
|
switch opcode {
|
||||||
|
case OP_VERIF:
|
||||||
|
return true
|
||||||
|
case OP_VERNOTIF:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isOpcodeConditional returns whether or not the opcode is a conditional opcode
|
||||||
|
// which changes the conditional execution stack when executed.
|
||||||
|
func isOpcodeConditional(opcode byte) bool {
|
||||||
|
switch opcode {
|
||||||
|
case OP_IF:
|
||||||
|
return true
|
||||||
|
case OP_NOTIF:
|
||||||
|
return true
|
||||||
|
case OP_ELSE:
|
||||||
|
return true
|
||||||
|
case OP_ENDIF:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkMinimalDataPush returns whether or not the provided opcode is the
|
||||||
|
// smallest possible way to represent the given data. For example, the value 15
|
||||||
|
// could be pushed with OP_DATA_1 15 (among other variations); however, OP_15 is
|
||||||
|
// a single opcode that represents the same value and is only a single byte
|
||||||
|
// versus two bytes.
|
||||||
|
func checkMinimalDataPush(op *opcode, data []byte) error {
|
||||||
|
opcodeVal := op.value
|
||||||
|
dataLen := len(data)
|
||||||
|
switch {
|
||||||
|
case dataLen == 0 && opcodeVal != OP_0:
|
||||||
|
str := fmt.Sprintf("zero length data push is encoded with opcode %s "+
|
||||||
|
"instead of OP_0", op.name)
|
||||||
|
return scriptError(ErrMinimalData, str)
|
||||||
|
case dataLen == 1 && data[0] >= 1 && data[0] <= 16:
|
||||||
|
if opcodeVal != OP_1+data[0]-1 {
|
||||||
|
// Should have used OP_1 .. OP_16
|
||||||
|
str := fmt.Sprintf("data push of the value %d encoded with opcode "+
|
||||||
|
"%s instead of OP_%d", data[0], op.name, data[0])
|
||||||
|
return scriptError(ErrMinimalData, str)
|
||||||
|
}
|
||||||
|
case dataLen == 1 && data[0] == 0x81:
|
||||||
|
if opcodeVal != OP_1NEGATE {
|
||||||
|
str := fmt.Sprintf("data push of the value -1 encoded with opcode "+
|
||||||
|
"%s instead of OP_1NEGATE", op.name)
|
||||||
|
return scriptError(ErrMinimalData, str)
|
||||||
|
}
|
||||||
|
case dataLen <= 75:
|
||||||
|
if int(opcodeVal) != dataLen {
|
||||||
|
// Should have used a direct push
|
||||||
|
str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+
|
||||||
|
"instead of OP_DATA_%d", dataLen, op.name, dataLen)
|
||||||
|
return scriptError(ErrMinimalData, str)
|
||||||
|
}
|
||||||
|
case dataLen <= 255:
|
||||||
|
if opcodeVal != OP_PUSHDATA1 {
|
||||||
|
str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+
|
||||||
|
"instead of OP_PUSHDATA1", dataLen, op.name)
|
||||||
|
return scriptError(ErrMinimalData, str)
|
||||||
|
}
|
||||||
|
case dataLen <= 65535:
|
||||||
|
if opcodeVal != OP_PUSHDATA2 {
|
||||||
|
str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+
|
||||||
|
"instead of OP_PUSHDATA2", dataLen, op.name)
|
||||||
|
return scriptError(ErrMinimalData, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// executeOpcode peforms execution on the passed opcode. It takes into account
|
// executeOpcode peforms execution on the passed opcode. It takes into account
|
||||||
// whether or not it is hidden by conditionals, but some rules still must be
|
// whether or not it is hidden by conditionals, but some rules still must be
|
||||||
// tested in this case.
|
// tested in this case.
|
||||||
func (vm *Engine) executeOpcode(pop *parsedOpcode) error {
|
func (vm *Engine) executeOpcode(op *opcode, data []byte) error {
|
||||||
// Disabled opcodes are fail on program counter.
|
// Disabled opcodes are fail on program counter.
|
||||||
if pop.isDisabled() {
|
if isOpcodeDisabled(op.value) {
|
||||||
str := fmt.Sprintf("attempt to execute disabled opcode %s",
|
str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name)
|
||||||
pop.opcode.name)
|
|
||||||
return scriptError(ErrDisabledOpcode, str)
|
return scriptError(ErrDisabledOpcode, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always-illegal opcodes are fail on program counter.
|
// Always-illegal opcodes are fail on program counter.
|
||||||
if pop.alwaysIllegal() {
|
if isOpcodeAlwaysIllegal(op.value) {
|
||||||
str := fmt.Sprintf("attempt to execute reserved opcode %s",
|
str := fmt.Sprintf("attempt to execute reserved opcode %s", op.name)
|
||||||
pop.opcode.name)
|
|
||||||
return scriptError(ErrReservedOpcode, str)
|
return scriptError(ErrReservedOpcode, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that this includes OP_RESERVED which counts as a push operation.
|
// Note that this includes OP_RESERVED which counts as a push operation.
|
||||||
if pop.opcode.value > OP_16 {
|
if op.value > OP_16 {
|
||||||
vm.numOps++
|
vm.numOps++
|
||||||
if vm.numOps > MaxOpsPerScript {
|
if vm.numOps > MaxOpsPerScript {
|
||||||
str := fmt.Sprintf("exceeded max operation limit of %d",
|
str := fmt.Sprintf("exceeded max operation limit of %d",
|
||||||
|
@ -181,67 +363,42 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error {
|
||||||
return scriptError(ErrTooManyOperations, str)
|
return scriptError(ErrTooManyOperations, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if len(pop.data) > MaxScriptElementSize {
|
} else if len(data) > MaxScriptElementSize {
|
||||||
str := fmt.Sprintf("element size %d exceeds max allowed size %d",
|
str := fmt.Sprintf("element size %d exceeds max allowed size %d",
|
||||||
len(pop.data), MaxScriptElementSize)
|
len(data), MaxScriptElementSize)
|
||||||
return scriptError(ErrElementTooBig, str)
|
return scriptError(ErrElementTooBig, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing left to do when this is not a conditional opcode and it is
|
// Nothing left to do when this is not a conditional opcode and it is
|
||||||
// not in an executing branch.
|
// not in an executing branch.
|
||||||
if !vm.isBranchExecuting() && !pop.isConditional() {
|
if !vm.isBranchExecuting() && !isOpcodeConditional(op.value) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure all executed data push opcodes use the minimal encoding when
|
// Ensure all executed data push opcodes use the minimal encoding when
|
||||||
// the minimal data verification flag is set.
|
// the minimal data verification flag is set.
|
||||||
if vm.dstack.verifyMinimalData && vm.isBranchExecuting() &&
|
if vm.dstack.verifyMinimalData && vm.isBranchExecuting() &&
|
||||||
pop.opcode.value >= 0 && pop.opcode.value <= OP_PUSHDATA4 {
|
op.value >= 0 && op.value <= OP_PUSHDATA4 {
|
||||||
|
|
||||||
if err := pop.checkMinimalDataPush(); err != nil {
|
if err := checkMinimalDataPush(op, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pop.opcode.opfunc(pop, vm)
|
return op.opfunc(op, data, vm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// disasm is a helper function to produce the output for DisasmPC and
|
// checkValidPC returns an error if the current script position is not valid for
|
||||||
// DisasmScript. It produces the opcode prefixed by the program counter at the
|
// execution.
|
||||||
// provided position in the script. It does no error checking and leaves that
|
func (vm *Engine) checkValidPC() error {
|
||||||
// to the caller to provide a valid offset.
|
|
||||||
func (vm *Engine) disasm(scriptIdx int, scriptOff int) string {
|
|
||||||
return fmt.Sprintf("%02x:%04x: %s", scriptIdx, scriptOff,
|
|
||||||
vm.scripts[scriptIdx][scriptOff].print(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
// validPC returns an error if the current script position is valid for
|
|
||||||
// execution, nil otherwise.
|
|
||||||
func (vm *Engine) validPC() error {
|
|
||||||
if vm.scriptIdx >= len(vm.scripts) {
|
if vm.scriptIdx >= len(vm.scripts) {
|
||||||
str := fmt.Sprintf("past input scripts %v:%v %v:xxxx",
|
str := fmt.Sprintf("script index %d beyond total scripts %d",
|
||||||
vm.scriptIdx, vm.scriptOff, len(vm.scripts))
|
vm.scriptIdx, len(vm.scripts))
|
||||||
return scriptError(ErrInvalidProgramCounter, str)
|
|
||||||
}
|
|
||||||
if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
|
|
||||||
str := fmt.Sprintf("past input scripts %v:%v %v:%04d",
|
|
||||||
vm.scriptIdx, vm.scriptOff, vm.scriptIdx,
|
|
||||||
len(vm.scripts[vm.scriptIdx]))
|
|
||||||
return scriptError(ErrInvalidProgramCounter, str)
|
return scriptError(ErrInvalidProgramCounter, str)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// curPC returns either the current script and offset, or an error if the
|
|
||||||
// position isn't valid.
|
|
||||||
func (vm *Engine) curPC() (script int, off int, err error) {
|
|
||||||
err = vm.validPC()
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
return vm.scriptIdx, vm.scriptOff, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isWitnessVersionActive returns true if a witness program was extracted
|
// isWitnessVersionActive returns true if a witness program was extracted
|
||||||
// during the initialization of the Engine, and the program's version matches
|
// during the initialization of the Engine, and the program's version matches
|
||||||
// the specified version.
|
// the specified version.
|
||||||
|
@ -269,7 +426,9 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pops, err := parseScript(pkScript)
|
|
||||||
|
const scriptVersion = 0
|
||||||
|
err = checkScriptParses(vm.version, pkScript)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -277,7 +436,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
||||||
// Set the stack to the provided witness stack, then
|
// Set the stack to the provided witness stack, then
|
||||||
// append the pkScript generated above as the next
|
// append the pkScript generated above as the next
|
||||||
// script to execute.
|
// script to execute.
|
||||||
vm.scripts = append(vm.scripts, pops)
|
vm.scripts = append(vm.scripts, pkScript)
|
||||||
vm.SetStack(witness)
|
vm.SetStack(witness)
|
||||||
|
|
||||||
case payToWitnessScriptHashDataSize: // P2WSH
|
case payToWitnessScriptHashDataSize: // P2WSH
|
||||||
|
@ -307,10 +466,10 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
||||||
"witness program hash mismatch")
|
"witness program hash mismatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
// With all the validity checks passed, parse the
|
// With all the validity checks passed, assert that the
|
||||||
// script into individual op-codes so w can execute it
|
// script parses without failure.
|
||||||
// as the next script.
|
const scriptVersion = 0
|
||||||
pops, err := parseScript(witnessScript)
|
err := checkScriptParses(vm.version, witnessScript)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -318,7 +477,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
||||||
// The hash matched successfully, so use the witness as
|
// The hash matched successfully, so use the witness as
|
||||||
// the stack, and set the witnessScript to be the next
|
// the stack, and set the witnessScript to be the next
|
||||||
// script executed.
|
// script executed.
|
||||||
vm.scripts = append(vm.scripts, pops)
|
vm.scripts = append(vm.scripts, witnessScript)
|
||||||
vm.SetStack(witness[:len(witness)-1])
|
vm.SetStack(witness[:len(witness)-1])
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -359,18 +518,50 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisasmPC returns the string for the disassembly of the opcode that will be
|
// DisasmPC returns the string for the disassembly of the opcode that will be
|
||||||
// next to execute when Step() is called.
|
// next to execute when Step is called.
|
||||||
func (vm *Engine) DisasmPC() (string, error) {
|
func (vm *Engine) DisasmPC() (string, error) {
|
||||||
scriptIdx, scriptOff, err := vm.curPC()
|
if err := vm.checkValidPC(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return vm.disasm(scriptIdx, scriptOff), nil
|
|
||||||
|
// Create a copy of the current tokenizer and parse the next opcode in the
|
||||||
|
// copy to avoid mutating the current one.
|
||||||
|
peekTokenizer := vm.tokenizer
|
||||||
|
if !peekTokenizer.Next() {
|
||||||
|
// Note that due to the fact that all scripts are checked for parse
|
||||||
|
// failures before this code ever runs, there should never be an error
|
||||||
|
// here, but check again to be safe in case a refactor breaks that
|
||||||
|
// assumption or new script versions are introduced with different
|
||||||
|
// semantics.
|
||||||
|
if err := peekTokenizer.Err(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this should be impossible to hit in practice because the
|
||||||
|
// only way it could happen would be for the final opcode of a script to
|
||||||
|
// already be parsed without the script index having been updated, which
|
||||||
|
// is not the case since stepping the script always increments the
|
||||||
|
// script index when parsing and executing the final opcode of a script.
|
||||||
|
//
|
||||||
|
// However, check again to be safe in case a refactor breaks that
|
||||||
|
// assumption or new script versions are introduced with different
|
||||||
|
// semantics.
|
||||||
|
str := fmt.Sprintf("program counter beyond script index %d (bytes %x)",
|
||||||
|
vm.scriptIdx, vm.scripts[vm.scriptIdx])
|
||||||
|
return "", scriptError(ErrInvalidProgramCounter, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
disasmOpcode(&buf, peekTokenizer.op, peekTokenizer.Data(), false)
|
||||||
|
return fmt.Sprintf("%02x:%04x: %s", vm.scriptIdx, vm.opcodeIdx,
|
||||||
|
buf.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisasmScript returns the disassembly string for the script at the requested
|
// DisasmScript returns the disassembly string for the script at the requested
|
||||||
// offset index. Index 0 is the signature script and 1 is the public key
|
// offset index. Index 0 is the signature script and 1 is the public key
|
||||||
// script.
|
// script. In the case of pay-to-script-hash, index 2 is the redeem script once
|
||||||
|
// the execution has progressed far enough to have successfully verified script
|
||||||
|
// hash and thus add the script to the scripts to execute.
|
||||||
func (vm *Engine) DisasmScript(idx int) (string, error) {
|
func (vm *Engine) DisasmScript(idx int) (string, error) {
|
||||||
if idx >= len(vm.scripts) {
|
if idx >= len(vm.scripts) {
|
||||||
str := fmt.Sprintf("script index %d >= total scripts %d", idx,
|
str := fmt.Sprintf("script index %d >= total scripts %d", idx,
|
||||||
|
@ -378,19 +569,25 @@ func (vm *Engine) DisasmScript(idx int) (string, error) {
|
||||||
return "", scriptError(ErrInvalidIndex, str)
|
return "", scriptError(ErrInvalidIndex, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
var disstr string
|
var disbuf strings.Builder
|
||||||
for i := range vm.scripts[idx] {
|
script := vm.scripts[idx]
|
||||||
disstr = disstr + vm.disasm(idx, i) + "\n"
|
tokenizer := MakeScriptTokenizer(vm.version, script)
|
||||||
|
var opcodeIdx int
|
||||||
|
for tokenizer.Next() {
|
||||||
|
disbuf.WriteString(fmt.Sprintf("%02x:%04x: ", idx, opcodeIdx))
|
||||||
|
disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), false)
|
||||||
|
disbuf.WriteByte('\n')
|
||||||
|
opcodeIdx++
|
||||||
}
|
}
|
||||||
return disstr, nil
|
return disbuf.String(), tokenizer.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckErrorCondition returns nil if the running script has ended and was
|
// CheckErrorCondition returns nil if the running script has ended and was
|
||||||
// successful, leaving a a true boolean on the stack. An error otherwise,
|
// successful, leaving a a true boolean on the stack. An error otherwise,
|
||||||
// including if the script has not finished.
|
// including if the script has not finished.
|
||||||
func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
||||||
// Check execution is actually done. When pc is past the end of script
|
// Check execution is actually done by ensuring the script index is after
|
||||||
// array there are no more scripts to run.
|
// the final script in the array script.
|
||||||
if vm.scriptIdx < len(vm.scripts) {
|
if vm.scriptIdx < len(vm.scripts) {
|
||||||
return scriptError(ErrScriptUnfinished,
|
return scriptError(ErrScriptUnfinished,
|
||||||
"error check when script unfinished")
|
"error check when script unfinished")
|
||||||
|
@ -404,11 +601,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
||||||
"have clean stack")
|
"have clean stack")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The final script must end with exactly one data stack item when the
|
||||||
|
// verify clean stack flag is set. Otherwise, there must be at least one
|
||||||
|
// data stack item in order to interpret it as a boolean.
|
||||||
if finalScript && vm.hasFlag(ScriptVerifyCleanStack) &&
|
if finalScript && vm.hasFlag(ScriptVerifyCleanStack) &&
|
||||||
vm.dstack.Depth() != 1 {
|
vm.dstack.Depth() != 1 {
|
||||||
|
|
||||||
str := fmt.Sprintf("stack contains %d unexpected items",
|
str := fmt.Sprintf("stack must contain exactly one item (contains %d)",
|
||||||
vm.dstack.Depth()-1)
|
vm.dstack.Depth())
|
||||||
return scriptError(ErrCleanStack, str)
|
return scriptError(ErrCleanStack, str)
|
||||||
} else if vm.dstack.Depth() < 1 {
|
} else if vm.dstack.Depth() < 1 {
|
||||||
return scriptError(ErrEmptyStack,
|
return scriptError(ErrEmptyStack,
|
||||||
|
@ -422,10 +622,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
||||||
if !v {
|
if !v {
|
||||||
// Log interesting data.
|
// Log interesting data.
|
||||||
log.Tracef("%v", newLogClosure(func() string {
|
log.Tracef("%v", newLogClosure(func() string {
|
||||||
dis0, _ := vm.DisasmScript(0)
|
var buf strings.Builder
|
||||||
dis1, _ := vm.DisasmScript(1)
|
buf.WriteString("scripts failed:\n")
|
||||||
return fmt.Sprintf("scripts failed: script0: %s\n"+
|
for i := range vm.scripts {
|
||||||
"script1: %s", dis0, dis1)
|
dis, _ := vm.DisasmScript(i)
|
||||||
|
buf.WriteString(fmt.Sprintf("script%d:\n", i))
|
||||||
|
buf.WriteString(dis)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
}))
|
}))
|
||||||
return scriptError(ErrEvalFalse,
|
return scriptError(ErrEvalFalse,
|
||||||
"false stack entry at end of script execution")
|
"false stack entry at end of script execution")
|
||||||
|
@ -433,25 +637,38 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step will execute the next instruction and move the program counter to the
|
// Step executes the next instruction and moves the program counter to the next
|
||||||
// next opcode in the script, or the next script if the current has ended. Step
|
// opcode in the script, or the next script if the current has ended. Step will
|
||||||
// will return true in the case that the last opcode was successfully executed.
|
// return true in the case that the last opcode was successfully executed.
|
||||||
//
|
//
|
||||||
// The result of calling Step or any other method is undefined if an error is
|
// The result of calling Step or any other method is undefined if an error is
|
||||||
// returned.
|
// returned.
|
||||||
func (vm *Engine) Step() (done bool, err error) {
|
func (vm *Engine) Step() (done bool, err error) {
|
||||||
// Verify that it is pointing to a valid script address.
|
// Verify the engine is pointing to a valid program counter.
|
||||||
err = vm.validPC()
|
if err := vm.checkValidPC(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
opcode := &vm.scripts[vm.scriptIdx][vm.scriptOff]
|
|
||||||
vm.scriptOff++
|
// Attempt to parse the next opcode from the current script.
|
||||||
|
if !vm.tokenizer.Next() {
|
||||||
|
// Note that due to the fact that all scripts are checked for parse
|
||||||
|
// failures before this code ever runs, there should never be an error
|
||||||
|
// here, but check again to be safe in case a refactor breaks that
|
||||||
|
// assumption or new script versions are introduced with different
|
||||||
|
// semantics.
|
||||||
|
if err := vm.tokenizer.Err(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
str := fmt.Sprintf("attempt to step beyond script index %d (bytes %x)",
|
||||||
|
vm.scriptIdx, vm.scripts[vm.scriptIdx])
|
||||||
|
return true, scriptError(ErrInvalidProgramCounter, str)
|
||||||
|
}
|
||||||
|
|
||||||
// Execute the opcode while taking into account several things such as
|
// Execute the opcode while taking into account several things such as
|
||||||
// disabled opcodes, illegal opcodes, maximum allowed operations per
|
// disabled opcodes, illegal opcodes, maximum allowed operations per script,
|
||||||
// script, maximum script element sizes, and conditionals.
|
// maximum script element sizes, and conditionals.
|
||||||
err = vm.executeOpcode(opcode)
|
err = vm.executeOpcode(vm.tokenizer.op, vm.tokenizer.Data())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
@ -466,43 +683,53 @@ func (vm *Engine) Step() (done bool, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare for next instruction.
|
// Prepare for next instruction.
|
||||||
if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
|
vm.opcodeIdx++
|
||||||
// Illegal to have an `if' that straddles two scripts.
|
if vm.tokenizer.Done() {
|
||||||
if err == nil && len(vm.condStack) != 0 {
|
// Illegal to have a conditional that straddles two scripts.
|
||||||
|
if len(vm.condStack) != 0 {
|
||||||
return false, scriptError(ErrUnbalancedConditional,
|
return false, scriptError(ErrUnbalancedConditional,
|
||||||
"end of script reached in conditional execution")
|
"end of script reached in conditional execution")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alt stack doesn't persist.
|
// Alt stack doesn't persist between scripts.
|
||||||
_ = vm.astack.DropN(vm.astack.Depth())
|
_ = vm.astack.DropN(vm.astack.Depth())
|
||||||
|
|
||||||
vm.numOps = 0 // number of ops is per script.
|
// The number of operations is per script.
|
||||||
vm.scriptOff = 0
|
vm.numOps = 0
|
||||||
if vm.scriptIdx == 0 && vm.bip16 {
|
|
||||||
|
// Reset the opcode index for the next script.
|
||||||
|
vm.opcodeIdx = 0
|
||||||
|
|
||||||
|
// Advance to the next script as needed.
|
||||||
|
switch {
|
||||||
|
case vm.scriptIdx == 0 && vm.bip16:
|
||||||
vm.scriptIdx++
|
vm.scriptIdx++
|
||||||
vm.savedFirstStack = vm.GetStack()
|
vm.savedFirstStack = vm.GetStack()
|
||||||
} else if vm.scriptIdx == 1 && vm.bip16 {
|
|
||||||
|
case vm.scriptIdx == 1 && vm.bip16:
|
||||||
// Put us past the end for CheckErrorCondition()
|
// Put us past the end for CheckErrorCondition()
|
||||||
vm.scriptIdx++
|
vm.scriptIdx++
|
||||||
// Check script ran successfully and pull the script
|
|
||||||
// out of the first stack and execute that.
|
// Check script ran successfully.
|
||||||
err := vm.CheckErrorCondition(false)
|
err := vm.CheckErrorCondition(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Obtain the redeem script from the first stack and ensure it
|
||||||
|
// parses.
|
||||||
script := vm.savedFirstStack[len(vm.savedFirstStack)-1]
|
script := vm.savedFirstStack[len(vm.savedFirstStack)-1]
|
||||||
pops, err := parseScript(script)
|
if err := checkScriptParses(vm.version, script); err != nil {
|
||||||
if err != nil {
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
vm.scripts = append(vm.scripts, pops)
|
vm.scripts = append(vm.scripts, script)
|
||||||
|
|
||||||
// Set stack to be the stack from first script minus the
|
// Set stack to be the stack from first script minus the redeem
|
||||||
// script itself
|
// script itself
|
||||||
vm.SetStack(vm.savedFirstStack[:len(vm.savedFirstStack)-1])
|
vm.SetStack(vm.savedFirstStack[:len(vm.savedFirstStack)-1])
|
||||||
} else if (vm.scriptIdx == 1 && vm.witnessProgram != nil) ||
|
|
||||||
(vm.scriptIdx == 2 && vm.witnessProgram != nil && vm.bip16) { // Nested P2SH.
|
case vm.scriptIdx == 1 && vm.witnessProgram != nil,
|
||||||
|
vm.scriptIdx == 2 && vm.witnessProgram != nil && vm.bip16: // np2sh
|
||||||
|
|
||||||
vm.scriptIdx++
|
vm.scriptIdx++
|
||||||
|
|
||||||
|
@ -510,30 +737,46 @@ func (vm *Engine) Step() (done bool, err error) {
|
||||||
if err := vm.verifyWitnessProgram(witness); err != nil {
|
if err := vm.verifyWitnessProgram(witness); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
default:
|
||||||
vm.scriptIdx++
|
vm.scriptIdx++
|
||||||
}
|
}
|
||||||
// there are zero length scripts in the wild
|
|
||||||
if vm.scriptIdx < len(vm.scripts) && vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
|
// Skip empty scripts.
|
||||||
|
if vm.scriptIdx < len(vm.scripts) && len(vm.scripts[vm.scriptIdx]) == 0 {
|
||||||
vm.scriptIdx++
|
vm.scriptIdx++
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.lastCodeSep = 0
|
vm.lastCodeSep = 0
|
||||||
if vm.scriptIdx >= len(vm.scripts) {
|
if vm.scriptIdx >= len(vm.scripts) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally, update the current tokenizer used to parse through scripts
|
||||||
|
// one opcode at a time to start from the beginning of the new script
|
||||||
|
// associated with the program counter.
|
||||||
|
vm.tokenizer = MakeScriptTokenizer(vm.version, vm.scripts[vm.scriptIdx])
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute will execute all scripts in the script engine and return either nil
|
// Execute will execute all scripts in the script engine and return either nil
|
||||||
// for successful validation or an error if one occurred.
|
// for successful validation or an error if one occurred.
|
||||||
func (vm *Engine) Execute() (err error) {
|
func (vm *Engine) Execute() (err error) {
|
||||||
|
// All script versions other than 0 currently execute without issue,
|
||||||
|
// making all outputs to them anyone can pay. In the future this
|
||||||
|
// will allow for the addition of new scripting languages.
|
||||||
|
if vm.version != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
done := false
|
done := false
|
||||||
for !done {
|
for !done {
|
||||||
log.Tracef("%v", newLogClosure(func() string {
|
log.Tracef("%v", newLogClosure(func() string {
|
||||||
dis, err := vm.DisasmPC()
|
dis, err := vm.DisasmPC()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("stepping (%v)", err)
|
return fmt.Sprintf("stepping - failed to disasm pc: %v", err)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("stepping %v", dis)
|
return fmt.Sprintf("stepping %v", dis)
|
||||||
}))
|
}))
|
||||||
|
@ -545,7 +788,7 @@ func (vm *Engine) Execute() (err error) {
|
||||||
log.Tracef("%v", newLogClosure(func() string {
|
log.Tracef("%v", newLogClosure(func() string {
|
||||||
var dstr, astr string
|
var dstr, astr string
|
||||||
|
|
||||||
// if we're tracing, dump the stacks.
|
// Log the non-empty stacks when tracing.
|
||||||
if vm.dstack.Depth() != 0 {
|
if vm.dstack.Depth() != 0 {
|
||||||
dstr = "Stack:\n" + vm.dstack.String()
|
dstr = "Stack:\n" + vm.dstack.String()
|
||||||
}
|
}
|
||||||
|
@ -561,7 +804,7 @@ func (vm *Engine) Execute() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// subScript returns the script since the last OP_CODESEPARATOR.
|
// subScript returns the script since the last OP_CODESEPARATOR.
|
||||||
func (vm *Engine) subScript() []parsedOpcode {
|
func (vm *Engine) subScript() []byte {
|
||||||
return vm.scripts[vm.scriptIdx][vm.lastCodeSep:]
|
return vm.scripts[vm.scriptIdx][vm.lastCodeSep:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -580,6 +823,27 @@ func (vm *Engine) checkHashTypeEncoding(hashType SigHashType) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isStrictPubKeyEncoding returns whether or not the passed public key adheres
|
||||||
|
// to the strict encoding requirements.
|
||||||
|
func isStrictPubKeyEncoding(pubKey []byte) bool {
|
||||||
|
if len(pubKey) == 33 && (pubKey[0] == 0x02 || pubKey[0] == 0x03) {
|
||||||
|
// Compressed
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(pubKey) == 65 {
|
||||||
|
switch pubKey[0] {
|
||||||
|
case 0x04:
|
||||||
|
// Uncompressed
|
||||||
|
return true
|
||||||
|
|
||||||
|
case 0x06, 0x07:
|
||||||
|
// Hybrid
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// checkPubKeyEncoding returns whether or not the passed public key adheres to
|
// checkPubKeyEncoding returns whether or not the passed public key adheres to
|
||||||
// the strict encoding requirements if enabled.
|
// the strict encoding requirements if enabled.
|
||||||
func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
|
func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
|
||||||
|
@ -854,6 +1118,7 @@ func (vm *Engine) SetAltStack(data [][]byte) {
|
||||||
// engine according to the description provided by each flag.
|
// engine according to the description provided by each flag.
|
||||||
func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags,
|
func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags,
|
||||||
sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64) (*Engine, error) {
|
sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64) (*Engine, error) {
|
||||||
|
const scriptVersion = 0
|
||||||
|
|
||||||
// The provided transaction input index must refer to a valid input.
|
// The provided transaction input index must refer to a valid input.
|
||||||
if txIdx < 0 || txIdx >= len(tx.TxIn) {
|
if txIdx < 0 || txIdx >= len(tx.TxIn) {
|
||||||
|
@ -863,10 +1128,10 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
||||||
}
|
}
|
||||||
scriptSig := tx.TxIn[txIdx].SignatureScript
|
scriptSig := tx.TxIn[txIdx].SignatureScript
|
||||||
|
|
||||||
// When both the signature script and public key script are empty the
|
// When both the signature script and public key script are empty the result
|
||||||
// result is necessarily an error since the stack would end up being
|
// is necessarily an error since the stack would end up being empty which is
|
||||||
// empty which is equivalent to a false top element. Thus, just return
|
// equivalent to a false top element. Thus, just return the relevant error
|
||||||
// the relevant error now as an optimization.
|
// now as an optimization.
|
||||||
if len(scriptSig) == 0 && len(scriptPubKey) == 0 {
|
if len(scriptSig) == 0 && len(scriptPubKey) == 0 {
|
||||||
return nil, scriptError(ErrEvalFalse,
|
return nil, scriptError(ErrEvalFalse,
|
||||||
"false stack entry at end of script execution")
|
"false stack entry at end of script execution")
|
||||||
|
@ -897,40 +1162,45 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
||||||
"signature script is not push only")
|
"signature script is not push only")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The engine stores the scripts in parsed form using a slice. This
|
// The signature script must only contain data pushes for PS2H which is
|
||||||
// allows multiple scripts to be executed in sequence. For example,
|
// determined based on the form of the public key script.
|
||||||
// with a pay-to-script-hash transaction, there will be ultimately be
|
if vm.hasFlag(ScriptBip16) && isScriptHashScript(scriptPubKey) {
|
||||||
// a third script to execute.
|
|
||||||
scripts := [][]byte{scriptSig, scriptPubKey}
|
|
||||||
vm.scripts = make([][]parsedOpcode, len(scripts))
|
|
||||||
for i, scr := range scripts {
|
|
||||||
if len(scr) > MaxScriptSize {
|
|
||||||
str := fmt.Sprintf("script size %d is larger than max "+
|
|
||||||
"allowed size %d", len(scr), MaxScriptSize)
|
|
||||||
return nil, scriptError(ErrScriptTooBig, str)
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
vm.scripts[i], err = parseScript(scr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance the program counter to the public key script if the signature
|
|
||||||
// script is empty since there is nothing to execute for it in that
|
|
||||||
// case.
|
|
||||||
if len(scripts[0]) == 0 {
|
|
||||||
vm.scriptIdx++
|
|
||||||
}
|
|
||||||
|
|
||||||
if vm.hasFlag(ScriptBip16) && isScriptHash(vm.scripts[1]) {
|
|
||||||
// Only accept input scripts that push data for P2SH.
|
// Only accept input scripts that push data for P2SH.
|
||||||
if !isPushOnly(vm.scripts[0]) {
|
// Notice that the push only checks have already been done when
|
||||||
|
// the flag to verify signature scripts are push only is set
|
||||||
|
// above, so avoid checking again.
|
||||||
|
alreadyChecked := vm.hasFlag(ScriptVerifySigPushOnly)
|
||||||
|
if !alreadyChecked && !IsPushOnlyScript(scriptSig) {
|
||||||
return nil, scriptError(ErrNotPushOnly,
|
return nil, scriptError(ErrNotPushOnly,
|
||||||
"pay to script hash is not push only")
|
"pay to script hash is not push only")
|
||||||
}
|
}
|
||||||
vm.bip16 = true
|
vm.bip16 = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The engine stores the scripts using a slice. This allows multiple
|
||||||
|
// scripts to be executed in sequence. For example, with a
|
||||||
|
// pay-to-script-hash transaction, there will be ultimately be a third
|
||||||
|
// script to execute.
|
||||||
|
scripts := [][]byte{scriptSig, scriptPubKey}
|
||||||
|
for _, scr := range scripts {
|
||||||
|
if len(scr) > MaxScriptSize {
|
||||||
|
str := fmt.Sprintf("script size %d is larger than max allowed "+
|
||||||
|
"size %d", len(scr), MaxScriptSize)
|
||||||
|
return nil, scriptError(ErrScriptTooBig, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptVersion = 0
|
||||||
|
if err := checkScriptParses(scriptVersion, scr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vm.scripts = scripts
|
||||||
|
|
||||||
|
// Advance the program counter to the public key script if the signature
|
||||||
|
// script is empty since there is nothing to execute for it in that case.
|
||||||
|
if len(scriptSig) == 0 {
|
||||||
|
vm.scriptIdx++
|
||||||
|
}
|
||||||
if vm.hasFlag(ScriptVerifyMinimalData) {
|
if vm.hasFlag(ScriptVerifyMinimalData) {
|
||||||
vm.dstack.verifyMinimalData = true
|
vm.dstack.verifyMinimalData = true
|
||||||
vm.astack.verifyMinimalData = true
|
vm.astack.verifyMinimalData = true
|
||||||
|
@ -952,7 +1222,7 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
||||||
var witProgram []byte
|
var witProgram []byte
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case isWitnessProgram(vm.scripts[1]):
|
case IsWitnessProgram(vm.scripts[1]):
|
||||||
// The scriptSig must be *empty* for all native witness
|
// The scriptSig must be *empty* for all native witness
|
||||||
// programs, otherwise we introduce malleability.
|
// programs, otherwise we introduce malleability.
|
||||||
if len(scriptSig) != 0 {
|
if len(scriptSig) != 0 {
|
||||||
|
@ -967,10 +1237,11 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
||||||
// data push of the witness program, otherwise we
|
// data push of the witness program, otherwise we
|
||||||
// reintroduce malleability.
|
// reintroduce malleability.
|
||||||
sigPops := vm.scripts[0]
|
sigPops := vm.scripts[0]
|
||||||
if len(sigPops) == 1 && canonicalPush(sigPops[0]) &&
|
if len(sigPops) > 2 &&
|
||||||
IsWitnessProgram(sigPops[0].data) {
|
isCanonicalPush(sigPops[0], sigPops[1:]) &&
|
||||||
|
IsWitnessProgram(sigPops[1:]) {
|
||||||
|
|
||||||
witProgram = sigPops[0].data
|
witProgram = sigPops[1:]
|
||||||
} else {
|
} else {
|
||||||
errStr := "signature script for witness " +
|
errStr := "signature script for witness " +
|
||||||
"nested p2sh is not canonical"
|
"nested p2sh is not canonical"
|
||||||
|
@ -997,6 +1268,10 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup the current tokenizer used to parse through the script one opcode
|
||||||
|
// at a time with the script associated with the program counter.
|
||||||
|
vm.tokenizer = MakeScriptTokenizer(scriptVersion, scripts[vm.scriptIdx])
|
||||||
|
|
||||||
vm.tx = *tx
|
vm.tx = *tx
|
||||||
vm.txIdx = txIdx
|
vm.txIdx = txIdx
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright (c) 2013-2017 The btcsuite developers
|
// Copyright (c) 2013-2017 The btcsuite developers
|
||||||
|
// Copyright (c) 2015-2019 The Decred developers
|
||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -11,16 +12,16 @@ import (
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestBadPC sets the pc to a deliberately bad result then confirms that Step()
|
// TestBadPC sets the pc to a deliberately bad result then confirms that Step
|
||||||
// and Disasm fail correctly.
|
// and Disasm fail correctly.
|
||||||
func TestBadPC(t *testing.T) {
|
func TestBadPC(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
script, off int
|
scriptIdx int
|
||||||
}{
|
}{
|
||||||
{script: 2, off: 0},
|
{scriptIdx: 2},
|
||||||
{script: 0, off: 2},
|
{scriptIdx: 3},
|
||||||
}
|
}
|
||||||
|
|
||||||
// tx with almost empty scripts.
|
// tx with almost empty scripts.
|
||||||
|
@ -59,20 +60,20 @@ func TestBadPC(t *testing.T) {
|
||||||
t.Errorf("Failed to create script: %v", err)
|
t.Errorf("Failed to create script: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set to after all scripts
|
// Set to after all scripts.
|
||||||
vm.scriptIdx = test.script
|
vm.scriptIdx = test.scriptIdx
|
||||||
vm.scriptOff = test.off
|
|
||||||
|
|
||||||
|
// Ensure attempting to step fails.
|
||||||
_, err = vm.Step()
|
_, err = vm.Step()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Step with invalid pc (%v) succeeds!", test)
|
t.Errorf("Step with invalid pc (%v) succeeds!", test)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure attempting to disassemble the current program counter fails.
|
||||||
_, err = vm.DisasmPC()
|
_, err = vm.DisasmPC()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("DisasmPC with invalid pc (%v) succeeds!",
|
t.Errorf("DisasmPC with invalid pc (%v) succeeds!", test)
|
||||||
test)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright (c) 2013-2017 The btcsuite developers
|
// Copyright (c) 2013-2017 The btcsuite developers
|
||||||
|
// Copyright (c) 2015-2019 The Decred developers
|
||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -47,6 +48,10 @@ const (
|
||||||
// the provided data exceeds MaxDataCarrierSize.
|
// the provided data exceeds MaxDataCarrierSize.
|
||||||
ErrTooMuchNullData
|
ErrTooMuchNullData
|
||||||
|
|
||||||
|
// ErrUnsupportedScriptVersion is returned when an unsupported script
|
||||||
|
// version is passed to a function which deals with script analysis.
|
||||||
|
ErrUnsupportedScriptVersion
|
||||||
|
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// Failures related to final execution state.
|
// Failures related to final execution state.
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
|
@ -352,6 +357,7 @@ var errorCodeStrings = map[ErrorCode]string{
|
||||||
ErrNotMultisigScript: "ErrNotMultisigScript",
|
ErrNotMultisigScript: "ErrNotMultisigScript",
|
||||||
ErrTooManyRequiredSigs: "ErrTooManyRequiredSigs",
|
ErrTooManyRequiredSigs: "ErrTooManyRequiredSigs",
|
||||||
ErrTooMuchNullData: "ErrTooMuchNullData",
|
ErrTooMuchNullData: "ErrTooMuchNullData",
|
||||||
|
ErrUnsupportedScriptVersion: "ErrUnsupportedScriptVersion",
|
||||||
ErrEarlyReturn: "ErrEarlyReturn",
|
ErrEarlyReturn: "ErrEarlyReturn",
|
||||||
ErrEmptyStack: "ErrEmptyStack",
|
ErrEmptyStack: "ErrEmptyStack",
|
||||||
ErrEvalFalse: "ErrEvalFalse",
|
ErrEvalFalse: "ErrEvalFalse",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright (c) 2017 The btcsuite developers
|
// Copyright (c) 2017 The btcsuite developers
|
||||||
|
// Copyright (c) 2015-2019 The Decred developers
|
||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ func TestErrorCodeStringer(t *testing.T) {
|
||||||
{ErrUnsupportedAddress, "ErrUnsupportedAddress"},
|
{ErrUnsupportedAddress, "ErrUnsupportedAddress"},
|
||||||
{ErrTooManyRequiredSigs, "ErrTooManyRequiredSigs"},
|
{ErrTooManyRequiredSigs, "ErrTooManyRequiredSigs"},
|
||||||
{ErrTooMuchNullData, "ErrTooMuchNullData"},
|
{ErrTooMuchNullData, "ErrTooMuchNullData"},
|
||||||
|
{ErrUnsupportedScriptVersion, "ErrUnsupportedScriptVersion"},
|
||||||
{ErrNotMultisigScript, "ErrNotMultisigScript"},
|
{ErrNotMultisigScript, "ErrNotMultisigScript"},
|
||||||
{ErrEarlyReturn, "ErrEarlyReturn"},
|
{ErrEarlyReturn, "ErrEarlyReturn"},
|
||||||
{ErrEmptyStack, "ErrEmptyStack"},
|
{ErrEmptyStack, "ErrEmptyStack"},
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright (c) 2014-2016 The btcsuite developers
|
// Copyright (c) 2014-2016 The btcsuite developers
|
||||||
|
// Copyright (c) 2015-2019 The Decred developers
|
||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -180,3 +181,34 @@ func ExampleSignTxOutput() {
|
||||||
// Output:
|
// Output:
|
||||||
// Transaction successfully signed
|
// Transaction successfully signed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This example demonstrates creating a script tokenizer instance and using it
|
||||||
|
// to count the number of opcodes a script contains.
|
||||||
|
func ExampleScriptTokenizer() {
|
||||||
|
// Create a script to use in the example. Ordinarily this would come from
|
||||||
|
// some other source.
|
||||||
|
hash160 := btcutil.Hash160([]byte("example"))
|
||||||
|
script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).
|
||||||
|
AddOp(txscript.OP_HASH160).AddData(hash160).
|
||||||
|
AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG).Script()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to build script: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a tokenizer to iterate the script and count the number of opcodes.
|
||||||
|
const scriptVersion = 0
|
||||||
|
var numOpcodes int
|
||||||
|
tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script)
|
||||||
|
for tokenizer.Next() {
|
||||||
|
numOpcodes++
|
||||||
|
}
|
||||||
|
if tokenizer.Err() != nil {
|
||||||
|
fmt.Printf("script failed to parse: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("script contains %d opcode(s)\n", numOpcodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// script contains 5 opcode(s)
|
||||||
|
}
|
||||||
|
|
|
@ -8,9 +8,10 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ type opcode struct {
|
||||||
value byte
|
value byte
|
||||||
name string
|
name string
|
||||||
length int
|
length int
|
||||||
opfunc func(*parsedOpcode, *Engine) error
|
opfunc func(*opcode, []byte, *Engine) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// These constants are the values of the official opcodes used on the btc wiki,
|
// These constants are the values of the official opcodes used on the btc wiki,
|
||||||
|
@ -610,306 +611,52 @@ var opcodeOnelineRepls = map[string]string{
|
||||||
"OP_16": "16",
|
"OP_16": "16",
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsedOpcode represents an opcode that has been parsed and includes any
|
// disasmOpcode writes a human-readable disassembly of the provided opcode and
|
||||||
// potential data associated with it.
|
// data into the provided buffer. The compact flag indicates the disassembly
|
||||||
type parsedOpcode struct {
|
// should print a more compact representation of data-carrying and small integer
|
||||||
opcode *opcode
|
// opcodes. For example, OP_0 through OP_16 are replaced with the numeric value
|
||||||
data []byte
|
// and data pushes are printed as only the hex representation of the data as
|
||||||
}
|
// opposed to including the opcode that specifies the amount of data to push as
|
||||||
|
// well.
|
||||||
// isDisabled returns whether or not the opcode is disabled and thus is always
|
func disasmOpcode(buf *strings.Builder, op *opcode, data []byte, compact bool) {
|
||||||
// bad to see in the instruction stream (even if turned off by a conditional).
|
// Replace opcode which represent values (e.g. OP_0 through OP_16 and
|
||||||
func (pop *parsedOpcode) isDisabled() bool {
|
// OP_1NEGATE) with the raw value when performing a compact disassembly.
|
||||||
switch pop.opcode.value {
|
opcodeName := op.name
|
||||||
case OP_CAT:
|
if compact {
|
||||||
return true
|
|
||||||
case OP_SUBSTR:
|
|
||||||
return true
|
|
||||||
case OP_LEFT:
|
|
||||||
return true
|
|
||||||
case OP_RIGHT:
|
|
||||||
return true
|
|
||||||
case OP_INVERT:
|
|
||||||
return true
|
|
||||||
case OP_AND:
|
|
||||||
return true
|
|
||||||
case OP_OR:
|
|
||||||
return true
|
|
||||||
case OP_XOR:
|
|
||||||
return true
|
|
||||||
case OP_2MUL:
|
|
||||||
return true
|
|
||||||
case OP_2DIV:
|
|
||||||
return true
|
|
||||||
case OP_MUL:
|
|
||||||
return true
|
|
||||||
case OP_DIV:
|
|
||||||
return true
|
|
||||||
case OP_MOD:
|
|
||||||
return true
|
|
||||||
case OP_LSHIFT:
|
|
||||||
return true
|
|
||||||
case OP_RSHIFT:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkParseableInScript checks whether or not the current opcode is able to be
|
|
||||||
// parsed at a certain position in a script.
|
|
||||||
// This returns the position of the next opcode to be parsed in the script.
|
|
||||||
func (pop *parsedOpcode) checkParseableInScript(script []byte, scriptPos int) (int, error) {
|
|
||||||
// Parse data out of instruction.
|
|
||||||
switch {
|
|
||||||
// No additional data. Note that some of the opcodes, notably
|
|
||||||
// OP_1NEGATE, OP_0, and OP_[1-16] represent the data
|
|
||||||
// themselves.
|
|
||||||
case pop.opcode.length == 1:
|
|
||||||
scriptPos++
|
|
||||||
|
|
||||||
// Data pushes of specific lengths -- OP_DATA_[1-75].
|
|
||||||
case pop.opcode.length > 1:
|
|
||||||
if len(script[scriptPos:]) < pop.opcode.length {
|
|
||||||
str := fmt.Sprintf("opcode %s requires %d "+
|
|
||||||
"bytes, but script only has %d remaining",
|
|
||||||
pop.opcode.name, pop.opcode.length, len(script[scriptPos:]))
|
|
||||||
return 0, scriptError(ErrMalformedPush, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slice out the data.
|
|
||||||
pop.data = script[scriptPos+1 : scriptPos+pop.opcode.length]
|
|
||||||
scriptPos += pop.opcode.length
|
|
||||||
|
|
||||||
// Data pushes with parsed lengths -- OP_PUSHDATAP{1,2,4}.
|
|
||||||
case pop.opcode.length < 0:
|
|
||||||
var l uint
|
|
||||||
off := scriptPos + 1
|
|
||||||
|
|
||||||
if len(script[off:]) < -pop.opcode.length {
|
|
||||||
str := fmt.Sprintf("opcode %s requires %d "+
|
|
||||||
"bytes, but script only has %d remaining",
|
|
||||||
pop.opcode.name, -pop.opcode.length, len(script[off:]))
|
|
||||||
return 0, scriptError(ErrMalformedPush, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next -length bytes are little endian length of data.
|
|
||||||
switch pop.opcode.length {
|
|
||||||
case -1:
|
|
||||||
l = uint(script[off])
|
|
||||||
case -2:
|
|
||||||
l = ((uint(script[off+1]) << 8) |
|
|
||||||
uint(script[off]))
|
|
||||||
case -4:
|
|
||||||
l = ((uint(script[off+3]) << 24) |
|
|
||||||
(uint(script[off+2]) << 16) |
|
|
||||||
(uint(script[off+1]) << 8) |
|
|
||||||
uint(script[off]))
|
|
||||||
default:
|
|
||||||
str := fmt.Sprintf("invalid opcode length %d",
|
|
||||||
pop.opcode.length)
|
|
||||||
return 0, scriptError(ErrMalformedPush, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move offset to beginning of the data.
|
|
||||||
off += -pop.opcode.length
|
|
||||||
|
|
||||||
// Disallow entries that do not fit script or were
|
|
||||||
// sign extended.
|
|
||||||
if int(l) > len(script[off:]) || int(l) < 0 {
|
|
||||||
str := fmt.Sprintf("opcode %s pushes %d bytes, "+
|
|
||||||
"but script only has %d remaining",
|
|
||||||
pop.opcode.name, int(l), len(script[off:]))
|
|
||||||
return 0, scriptError(ErrMalformedPush, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
pop.data = script[off : off+int(l)]
|
|
||||||
scriptPos += 1 - pop.opcode.length + int(l)
|
|
||||||
}
|
|
||||||
return scriptPos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// alwaysIllegal returns whether or not the opcode is always illegal when passed
|
|
||||||
// over by the program counter even if in a non-executed branch (it isn't a
|
|
||||||
// coincidence that they are conditionals).
|
|
||||||
func (pop *parsedOpcode) alwaysIllegal() bool {
|
|
||||||
switch pop.opcode.value {
|
|
||||||
case OP_VERIF:
|
|
||||||
return true
|
|
||||||
case OP_VERNOTIF:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isConditional returns whether or not the opcode is a conditional opcode which
|
|
||||||
// changes the conditional execution stack when executed.
|
|
||||||
func (pop *parsedOpcode) isConditional() bool {
|
|
||||||
switch pop.opcode.value {
|
|
||||||
case OP_IF:
|
|
||||||
return true
|
|
||||||
case OP_NOTIF:
|
|
||||||
return true
|
|
||||||
case OP_ELSE:
|
|
||||||
return true
|
|
||||||
case OP_ENDIF:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkMinimalDataPush returns whether or not the current data push uses the
|
|
||||||
// smallest possible opcode to represent it. For example, the value 15 could
|
|
||||||
// be pushed with OP_DATA_1 15 (among other variations); however, OP_15 is a
|
|
||||||
// single opcode that represents the same value and is only a single byte versus
|
|
||||||
// two bytes.
|
|
||||||
func (pop *parsedOpcode) checkMinimalDataPush() error {
|
|
||||||
data := pop.data
|
|
||||||
dataLen := len(data)
|
|
||||||
opcode := pop.opcode.value
|
|
||||||
|
|
||||||
if dataLen == 0 && opcode != OP_0 {
|
|
||||||
str := fmt.Sprintf("zero length data push is encoded with "+
|
|
||||||
"opcode %s instead of OP_0", pop.opcode.name)
|
|
||||||
return scriptError(ErrMinimalData, str)
|
|
||||||
} else if dataLen == 1 && data[0] >= 1 && data[0] <= 16 {
|
|
||||||
if opcode != OP_1+data[0]-1 {
|
|
||||||
// Should have used OP_1 .. OP_16
|
|
||||||
str := fmt.Sprintf("data push of the value %d encoded "+
|
|
||||||
"with opcode %s instead of OP_%d", data[0],
|
|
||||||
pop.opcode.name, data[0])
|
|
||||||
return scriptError(ErrMinimalData, str)
|
|
||||||
}
|
|
||||||
} else if dataLen == 1 && data[0] == 0x81 {
|
|
||||||
if opcode != OP_1NEGATE {
|
|
||||||
str := fmt.Sprintf("data push of the value -1 encoded "+
|
|
||||||
"with opcode %s instead of OP_1NEGATE",
|
|
||||||
pop.opcode.name)
|
|
||||||
return scriptError(ErrMinimalData, str)
|
|
||||||
}
|
|
||||||
} else if dataLen <= 75 {
|
|
||||||
if int(opcode) != dataLen {
|
|
||||||
// Should have used a direct push
|
|
||||||
str := fmt.Sprintf("data push of %d bytes encoded "+
|
|
||||||
"with opcode %s instead of OP_DATA_%d", dataLen,
|
|
||||||
pop.opcode.name, dataLen)
|
|
||||||
return scriptError(ErrMinimalData, str)
|
|
||||||
}
|
|
||||||
} else if dataLen <= 255 {
|
|
||||||
if opcode != OP_PUSHDATA1 {
|
|
||||||
str := fmt.Sprintf("data push of %d bytes encoded "+
|
|
||||||
"with opcode %s instead of OP_PUSHDATA1",
|
|
||||||
dataLen, pop.opcode.name)
|
|
||||||
return scriptError(ErrMinimalData, str)
|
|
||||||
}
|
|
||||||
} else if dataLen <= 65535 {
|
|
||||||
if opcode != OP_PUSHDATA2 {
|
|
||||||
str := fmt.Sprintf("data push of %d bytes encoded "+
|
|
||||||
"with opcode %s instead of OP_PUSHDATA2",
|
|
||||||
dataLen, pop.opcode.name)
|
|
||||||
return scriptError(ErrMinimalData, str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// print returns a human-readable string representation of the opcode for use
|
|
||||||
// in script disassembly.
|
|
||||||
func (pop *parsedOpcode) print(oneline bool) string {
|
|
||||||
// The reference implementation one-line disassembly replaces opcodes
|
|
||||||
// which represent values (e.g. OP_0 through OP_16 and OP_1NEGATE)
|
|
||||||
// with the raw value. However, when not doing a one-line dissassembly,
|
|
||||||
// we prefer to show the actual opcode names. Thus, only replace the
|
|
||||||
// opcodes in question when the oneline flag is set.
|
|
||||||
opcodeName := pop.opcode.name
|
|
||||||
if oneline {
|
|
||||||
if replName, ok := opcodeOnelineRepls[opcodeName]; ok {
|
if replName, ok := opcodeOnelineRepls[opcodeName]; ok {
|
||||||
opcodeName = replName
|
opcodeName = replName
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing more to do for non-data push opcodes.
|
// Either write the human-readable opcode or the parsed data in hex for
|
||||||
if pop.opcode.length == 1 {
|
// data-carrying opcodes.
|
||||||
return opcodeName
|
switch {
|
||||||
|
case op.length == 1:
|
||||||
|
buf.WriteString(opcodeName)
|
||||||
|
|
||||||
|
default:
|
||||||
|
buf.WriteString(hex.EncodeToString(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%x", pop.data)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing more to do for non-data push opcodes.
|
buf.WriteString(opcodeName)
|
||||||
if pop.opcode.length == 1 {
|
|
||||||
return opcodeName
|
switch op.length {
|
||||||
}
|
// Only write the opcode name for non-data push opcodes.
|
||||||
|
case 1:
|
||||||
|
return
|
||||||
|
|
||||||
// Add length for the OP_PUSHDATA# opcodes.
|
// Add length for the OP_PUSHDATA# opcodes.
|
||||||
retString := opcodeName
|
|
||||||
switch pop.opcode.length {
|
|
||||||
case -1:
|
case -1:
|
||||||
retString += fmt.Sprintf(" 0x%02x", len(pop.data))
|
buf.WriteString(fmt.Sprintf(" 0x%02x", len(data)))
|
||||||
case -2:
|
case -2:
|
||||||
retString += fmt.Sprintf(" 0x%04x", len(pop.data))
|
buf.WriteString(fmt.Sprintf(" 0x%04x", len(data)))
|
||||||
case -4:
|
case -4:
|
||||||
retString += fmt.Sprintf(" 0x%08x", len(pop.data))
|
buf.WriteString(fmt.Sprintf(" 0x%08x", len(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s 0x%02x", retString, pop.data)
|
buf.WriteString(fmt.Sprintf(" 0x%02x", data))
|
||||||
}
|
|
||||||
|
|
||||||
// bytes returns any data associated with the opcode encoded as it would be in
|
|
||||||
// a script. This is used for unparsing scripts from parsed opcodes.
|
|
||||||
func (pop *parsedOpcode) bytes() ([]byte, error) {
|
|
||||||
var retbytes []byte
|
|
||||||
if pop.opcode.length > 0 {
|
|
||||||
retbytes = make([]byte, 1, pop.opcode.length)
|
|
||||||
} else {
|
|
||||||
retbytes = make([]byte, 1, 1+len(pop.data)-
|
|
||||||
pop.opcode.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
retbytes[0] = pop.opcode.value
|
|
||||||
if pop.opcode.length == 1 {
|
|
||||||
if len(pop.data) != 0 {
|
|
||||||
str := fmt.Sprintf("internal consistency error - "+
|
|
||||||
"parsed opcode %s has data length %d when %d "+
|
|
||||||
"was expected", pop.opcode.name, len(pop.data),
|
|
||||||
0)
|
|
||||||
return nil, scriptError(ErrInternal, str)
|
|
||||||
}
|
|
||||||
return retbytes, nil
|
|
||||||
}
|
|
||||||
nbytes := pop.opcode.length
|
|
||||||
if pop.opcode.length < 0 {
|
|
||||||
l := len(pop.data)
|
|
||||||
// tempting just to hardcode to avoid the complexity here.
|
|
||||||
switch pop.opcode.length {
|
|
||||||
case -1:
|
|
||||||
retbytes = append(retbytes, byte(l))
|
|
||||||
nbytes = int(retbytes[1]) + len(retbytes)
|
|
||||||
case -2:
|
|
||||||
retbytes = append(retbytes, byte(l&0xff),
|
|
||||||
byte(l>>8&0xff))
|
|
||||||
nbytes = int(binary.LittleEndian.Uint16(retbytes[1:])) +
|
|
||||||
len(retbytes)
|
|
||||||
case -4:
|
|
||||||
retbytes = append(retbytes, byte(l&0xff),
|
|
||||||
byte((l>>8)&0xff), byte((l>>16)&0xff),
|
|
||||||
byte((l>>24)&0xff))
|
|
||||||
nbytes = int(binary.LittleEndian.Uint32(retbytes[1:])) +
|
|
||||||
len(retbytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retbytes = append(retbytes, pop.data...)
|
|
||||||
|
|
||||||
if len(retbytes) != nbytes {
|
|
||||||
str := fmt.Sprintf("internal consistency error - "+
|
|
||||||
"parsed opcode %s has data length %d when %d was "+
|
|
||||||
"expected", pop.opcode.name, len(retbytes), nbytes)
|
|
||||||
return nil, scriptError(ErrInternal, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
return retbytes, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// *******************************************
|
// *******************************************
|
||||||
|
@ -922,45 +669,42 @@ func (pop *parsedOpcode) bytes() ([]byte, error) {
|
||||||
// opcodes before executing in an initial parse step, the consensus rules
|
// opcodes before executing in an initial parse step, the consensus rules
|
||||||
// dictate the script doesn't fail until the program counter passes over a
|
// dictate the script doesn't fail until the program counter passes over a
|
||||||
// disabled opcode (even when they appear in a branch that is not executed).
|
// disabled opcode (even when they appear in a branch that is not executed).
|
||||||
func opcodeDisabled(op *parsedOpcode, vm *Engine) error {
|
func opcodeDisabled(op *opcode, data []byte, vm *Engine) error {
|
||||||
str := fmt.Sprintf("attempt to execute disabled opcode %s",
|
str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name)
|
||||||
op.opcode.name)
|
|
||||||
return scriptError(ErrDisabledOpcode, str)
|
return scriptError(ErrDisabledOpcode, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeReserved is a common handler for all reserved opcodes. It returns an
|
// opcodeReserved is a common handler for all reserved opcodes. It returns an
|
||||||
// appropriate error indicating the opcode is reserved.
|
// appropriate error indicating the opcode is reserved.
|
||||||
func opcodeReserved(op *parsedOpcode, vm *Engine) error {
|
func opcodeReserved(op *opcode, data []byte, vm *Engine) error {
|
||||||
str := fmt.Sprintf("attempt to execute reserved opcode %s",
|
str := fmt.Sprintf("attempt to execute reserved opcode %s", op.name)
|
||||||
op.opcode.name)
|
|
||||||
return scriptError(ErrReservedOpcode, str)
|
return scriptError(ErrReservedOpcode, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeInvalid is a common handler for all invalid opcodes. It returns an
|
// opcodeInvalid is a common handler for all invalid opcodes. It returns an
|
||||||
// appropriate error indicating the opcode is invalid.
|
// appropriate error indicating the opcode is invalid.
|
||||||
func opcodeInvalid(op *parsedOpcode, vm *Engine) error {
|
func opcodeInvalid(op *opcode, data []byte, vm *Engine) error {
|
||||||
str := fmt.Sprintf("attempt to execute invalid opcode %s",
|
str := fmt.Sprintf("attempt to execute invalid opcode %s", op.name)
|
||||||
op.opcode.name)
|
|
||||||
return scriptError(ErrReservedOpcode, str)
|
return scriptError(ErrReservedOpcode, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeFalse pushes an empty array to the data stack to represent false. Note
|
// opcodeFalse pushes an empty array to the data stack to represent false. Note
|
||||||
// that 0, when encoded as a number according to the numeric encoding consensus
|
// that 0, when encoded as a number according to the numeric encoding consensus
|
||||||
// rules, is an empty array.
|
// rules, is an empty array.
|
||||||
func opcodeFalse(op *parsedOpcode, vm *Engine) error {
|
func opcodeFalse(op *opcode, data []byte, vm *Engine) error {
|
||||||
vm.dstack.PushByteArray(nil)
|
vm.dstack.PushByteArray(nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodePushData is a common handler for the vast majority of opcodes that push
|
// opcodePushData is a common handler for the vast majority of opcodes that push
|
||||||
// raw data (bytes) to the data stack.
|
// raw data (bytes) to the data stack.
|
||||||
func opcodePushData(op *parsedOpcode, vm *Engine) error {
|
func opcodePushData(op *opcode, data []byte, vm *Engine) error {
|
||||||
vm.dstack.PushByteArray(op.data)
|
vm.dstack.PushByteArray(data)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcode1Negate pushes -1, encoded as a number, to the data stack.
|
// opcode1Negate pushes -1, encoded as a number, to the data stack.
|
||||||
func opcode1Negate(op *parsedOpcode, vm *Engine) error {
|
func opcode1Negate(op *opcode, data []byte, vm *Engine) error {
|
||||||
vm.dstack.PushInt(scriptNum(-1))
|
vm.dstack.PushInt(scriptNum(-1))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -968,23 +712,24 @@ func opcode1Negate(op *parsedOpcode, vm *Engine) error {
|
||||||
// opcodeN is a common handler for the small integer data push opcodes. It
|
// opcodeN is a common handler for the small integer data push opcodes. It
|
||||||
// pushes the numeric value the opcode represents (which will be from 1 to 16)
|
// pushes the numeric value the opcode represents (which will be from 1 to 16)
|
||||||
// onto the data stack.
|
// onto the data stack.
|
||||||
func opcodeN(op *parsedOpcode, vm *Engine) error {
|
func opcodeN(op *opcode, data []byte, vm *Engine) error {
|
||||||
// The opcodes are all defined consecutively, so the numeric value is
|
// The opcodes are all defined consecutively, so the numeric value is
|
||||||
// the difference.
|
// the difference.
|
||||||
vm.dstack.PushInt(scriptNum((op.opcode.value - (OP_1 - 1))))
|
vm.dstack.PushInt(scriptNum((op.value - (OP_1 - 1))))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeNop is a common handler for the NOP family of opcodes. As the name
|
// opcodeNop is a common handler for the NOP family of opcodes. As the name
|
||||||
// implies it generally does nothing, however, it will return an error when
|
// implies it generally does nothing, however, it will return an error when
|
||||||
// the flag to discourage use of NOPs is set for select opcodes.
|
// the flag to discourage use of NOPs is set for select opcodes.
|
||||||
func opcodeNop(op *parsedOpcode, vm *Engine) error {
|
func opcodeNop(op *opcode, data []byte, vm *Engine) error {
|
||||||
switch op.opcode.value {
|
switch op.value {
|
||||||
case OP_NOP1, OP_NOP4, OP_NOP5,
|
case OP_NOP1, OP_NOP4, OP_NOP5,
|
||||||
OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10:
|
OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10:
|
||||||
|
|
||||||
if vm.hasFlag(ScriptDiscourageUpgradableNops) {
|
if vm.hasFlag(ScriptDiscourageUpgradableNops) {
|
||||||
str := fmt.Sprintf("OP_NOP%d reserved for soft-fork "+
|
str := fmt.Sprintf("%v reserved for soft-fork "+
|
||||||
"upgrades", op.opcode.value-(OP_NOP1-1))
|
"upgrades", op.name)
|
||||||
return scriptError(ErrDiscourageUpgradableNOPs, str)
|
return scriptError(ErrDiscourageUpgradableNOPs, str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1047,7 +792,7 @@ func popIfBool(vm *Engine) (bool, error) {
|
||||||
//
|
//
|
||||||
// Data stack transformation: [... bool] -> [...]
|
// Data stack transformation: [... bool] -> [...]
|
||||||
// Conditional stack transformation: [...] -> [... OpCondValue]
|
// Conditional stack transformation: [...] -> [... OpCondValue]
|
||||||
func opcodeIf(op *parsedOpcode, vm *Engine) error {
|
func opcodeIf(op *opcode, data []byte, vm *Engine) error {
|
||||||
condVal := OpCondFalse
|
condVal := OpCondFalse
|
||||||
if vm.isBranchExecuting() {
|
if vm.isBranchExecuting() {
|
||||||
ok, err := popIfBool(vm)
|
ok, err := popIfBool(vm)
|
||||||
|
@ -1081,7 +826,7 @@ func opcodeIf(op *parsedOpcode, vm *Engine) error {
|
||||||
//
|
//
|
||||||
// Data stack transformation: [... bool] -> [...]
|
// Data stack transformation: [... bool] -> [...]
|
||||||
// Conditional stack transformation: [...] -> [... OpCondValue]
|
// Conditional stack transformation: [...] -> [... OpCondValue]
|
||||||
func opcodeNotIf(op *parsedOpcode, vm *Engine) error {
|
func opcodeNotIf(op *opcode, data []byte, vm *Engine) error {
|
||||||
condVal := OpCondFalse
|
condVal := OpCondFalse
|
||||||
if vm.isBranchExecuting() {
|
if vm.isBranchExecuting() {
|
||||||
ok, err := popIfBool(vm)
|
ok, err := popIfBool(vm)
|
||||||
|
@ -1104,10 +849,10 @@ func opcodeNotIf(op *parsedOpcode, vm *Engine) error {
|
||||||
// An error is returned if there has not already been a matching OP_IF.
|
// An error is returned if there has not already been a matching OP_IF.
|
||||||
//
|
//
|
||||||
// Conditional stack transformation: [... OpCondValue] -> [... !OpCondValue]
|
// Conditional stack transformation: [... OpCondValue] -> [... !OpCondValue]
|
||||||
func opcodeElse(op *parsedOpcode, vm *Engine) error {
|
func opcodeElse(op *opcode, data []byte, vm *Engine) error {
|
||||||
if len(vm.condStack) == 0 {
|
if len(vm.condStack) == 0 {
|
||||||
str := fmt.Sprintf("encountered opcode %s with no matching "+
|
str := fmt.Sprintf("encountered opcode %s with no matching "+
|
||||||
"opcode to begin conditional execution", op.opcode.name)
|
"opcode to begin conditional execution", op.name)
|
||||||
return scriptError(ErrUnbalancedConditional, str)
|
return scriptError(ErrUnbalancedConditional, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1130,10 +875,10 @@ func opcodeElse(op *parsedOpcode, vm *Engine) error {
|
||||||
// An error is returned if there has not already been a matching OP_IF.
|
// An error is returned if there has not already been a matching OP_IF.
|
||||||
//
|
//
|
||||||
// Conditional stack transformation: [... OpCondValue] -> [...]
|
// Conditional stack transformation: [... OpCondValue] -> [...]
|
||||||
func opcodeEndif(op *parsedOpcode, vm *Engine) error {
|
func opcodeEndif(op *opcode, data []byte, vm *Engine) error {
|
||||||
if len(vm.condStack) == 0 {
|
if len(vm.condStack) == 0 {
|
||||||
str := fmt.Sprintf("encountered opcode %s with no matching "+
|
str := fmt.Sprintf("encountered opcode %s with no matching "+
|
||||||
"opcode to begin conditional execution", op.opcode.name)
|
"opcode to begin conditional execution", op.name)
|
||||||
return scriptError(ErrUnbalancedConditional, str)
|
return scriptError(ErrUnbalancedConditional, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1146,14 +891,14 @@ func opcodeEndif(op *parsedOpcode, vm *Engine) error {
|
||||||
// item on the stack or when that item evaluates to false. In the latter case
|
// item on the stack or when that item evaluates to false. In the latter case
|
||||||
// where the verification fails specifically due to the top item evaluating
|
// where the verification fails specifically due to the top item evaluating
|
||||||
// to false, the returned error will use the passed error code.
|
// to false, the returned error will use the passed error code.
|
||||||
func abstractVerify(op *parsedOpcode, vm *Engine, c ErrorCode) error {
|
func abstractVerify(op *opcode, vm *Engine, c ErrorCode) error {
|
||||||
verified, err := vm.dstack.PopBool()
|
verified, err := vm.dstack.PopBool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !verified {
|
if !verified {
|
||||||
str := fmt.Sprintf("%s failed", op.opcode.name)
|
str := fmt.Sprintf("%s failed", op.name)
|
||||||
return scriptError(c, str)
|
return scriptError(c, str)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1161,13 +906,13 @@ func abstractVerify(op *parsedOpcode, vm *Engine, c ErrorCode) error {
|
||||||
|
|
||||||
// opcodeVerify examines the top item on the data stack as a boolean value and
|
// opcodeVerify examines the top item on the data stack as a boolean value and
|
||||||
// verifies it evaluates to true. An error is returned if it does not.
|
// verifies it evaluates to true. An error is returned if it does not.
|
||||||
func opcodeVerify(op *parsedOpcode, vm *Engine) error {
|
func opcodeVerify(op *opcode, data []byte, vm *Engine) error {
|
||||||
return abstractVerify(op, vm, ErrVerify)
|
return abstractVerify(op, vm, ErrVerify)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeReturn returns an appropriate error since it is always an error to
|
// opcodeReturn returns an appropriate error since it is always an error to
|
||||||
// return early from a script.
|
// return early from a script.
|
||||||
func opcodeReturn(op *parsedOpcode, vm *Engine) error {
|
func opcodeReturn(op *opcode, data []byte, vm *Engine) error {
|
||||||
return scriptError(ErrEarlyReturn, "script returned early")
|
return scriptError(ErrEarlyReturn, "script returned early")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1197,7 +942,7 @@ func verifyLockTime(txLockTime, threshold, lockTime int64) error {
|
||||||
// validating if the transaction outputs are spendable yet. If flag
|
// validating if the transaction outputs are spendable yet. If flag
|
||||||
// ScriptVerifyCheckLockTimeVerify is not set, the code continues as if OP_NOP2
|
// ScriptVerifyCheckLockTimeVerify is not set, the code continues as if OP_NOP2
|
||||||
// were executed.
|
// were executed.
|
||||||
func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
|
func opcodeCheckLockTimeVerify(op *opcode, data []byte, vm *Engine) error {
|
||||||
// If the ScriptVerifyCheckLockTimeVerify script flag is not set, treat
|
// If the ScriptVerifyCheckLockTimeVerify script flag is not set, treat
|
||||||
// opcode as OP_NOP2 instead.
|
// opcode as OP_NOP2 instead.
|
||||||
if !vm.hasFlag(ScriptVerifyCheckLockTimeVerify) {
|
if !vm.hasFlag(ScriptVerifyCheckLockTimeVerify) {
|
||||||
|
@ -1271,7 +1016,7 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
|
||||||
// validating if the transaction outputs are spendable yet. If flag
|
// validating if the transaction outputs are spendable yet. If flag
|
||||||
// ScriptVerifyCheckSequenceVerify is not set, the code continues as if OP_NOP3
|
// ScriptVerifyCheckSequenceVerify is not set, the code continues as if OP_NOP3
|
||||||
// were executed.
|
// were executed.
|
||||||
func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
func opcodeCheckSequenceVerify(op *opcode, data []byte, vm *Engine) error {
|
||||||
// If the ScriptVerifyCheckSequenceVerify script flag is not set, treat
|
// If the ScriptVerifyCheckSequenceVerify script flag is not set, treat
|
||||||
// opcode as OP_NOP3 instead.
|
// opcode as OP_NOP3 instead.
|
||||||
if !vm.hasFlag(ScriptVerifyCheckSequenceVerify) {
|
if !vm.hasFlag(ScriptVerifyCheckSequenceVerify) {
|
||||||
|
@ -1348,7 +1093,7 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
||||||
//
|
//
|
||||||
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2]
|
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2]
|
||||||
// Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2 y3 x3]
|
// Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2 y3 x3]
|
||||||
func opcodeToAltStack(op *parsedOpcode, vm *Engine) error {
|
func opcodeToAltStack(op *opcode, data []byte, vm *Engine) error {
|
||||||
so, err := vm.dstack.PopByteArray()
|
so, err := vm.dstack.PopByteArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1363,7 +1108,7 @@ func opcodeToAltStack(op *parsedOpcode, vm *Engine) error {
|
||||||
//
|
//
|
||||||
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 y3]
|
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 y3]
|
||||||
// Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2]
|
// Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2]
|
||||||
func opcodeFromAltStack(op *parsedOpcode, vm *Engine) error {
|
func opcodeFromAltStack(op *opcode, data []byte, vm *Engine) error {
|
||||||
so, err := vm.astack.PopByteArray()
|
so, err := vm.astack.PopByteArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1376,35 +1121,35 @@ func opcodeFromAltStack(op *parsedOpcode, vm *Engine) error {
|
||||||
// opcode2Drop removes the top 2 items from the data stack.
|
// opcode2Drop removes the top 2 items from the data stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3] -> [... x1]
|
// Stack transformation: [... x1 x2 x3] -> [... x1]
|
||||||
func opcode2Drop(op *parsedOpcode, vm *Engine) error {
|
func opcode2Drop(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.DropN(2)
|
return vm.dstack.DropN(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcode2Dup duplicates the top 2 items on the data stack.
|
// opcode2Dup duplicates the top 2 items on the data stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2 x3]
|
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2 x3]
|
||||||
func opcode2Dup(op *parsedOpcode, vm *Engine) error {
|
func opcode2Dup(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.DupN(2)
|
return vm.dstack.DupN(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcode3Dup duplicates the top 3 items on the data stack.
|
// opcode3Dup duplicates the top 3 items on the data stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x1 x2 x3]
|
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x1 x2 x3]
|
||||||
func opcode3Dup(op *parsedOpcode, vm *Engine) error {
|
func opcode3Dup(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.DupN(3)
|
return vm.dstack.DupN(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcode2Over duplicates the 2 items before the top 2 items on the data stack.
|
// opcode2Over duplicates the 2 items before the top 2 items on the data stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3 x4] -> [... x1 x2 x3 x4 x1 x2]
|
// Stack transformation: [... x1 x2 x3 x4] -> [... x1 x2 x3 x4 x1 x2]
|
||||||
func opcode2Over(op *parsedOpcode, vm *Engine) error {
|
func opcode2Over(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.OverN(2)
|
return vm.dstack.OverN(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcode2Rot rotates the top 6 items on the data stack to the left twice.
|
// opcode2Rot rotates the top 6 items on the data stack to the left twice.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3 x4 x5 x6] -> [... x3 x4 x5 x6 x1 x2]
|
// Stack transformation: [... x1 x2 x3 x4 x5 x6] -> [... x3 x4 x5 x6 x1 x2]
|
||||||
func opcode2Rot(op *parsedOpcode, vm *Engine) error {
|
func opcode2Rot(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.RotN(2)
|
return vm.dstack.RotN(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1412,7 +1157,7 @@ func opcode2Rot(op *parsedOpcode, vm *Engine) error {
|
||||||
// before them.
|
// before them.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3 x4] -> [... x3 x4 x1 x2]
|
// Stack transformation: [... x1 x2 x3 x4] -> [... x3 x4 x1 x2]
|
||||||
func opcode2Swap(op *parsedOpcode, vm *Engine) error {
|
func opcode2Swap(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.SwapN(2)
|
return vm.dstack.SwapN(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1420,7 +1165,7 @@ func opcode2Swap(op *parsedOpcode, vm *Engine) error {
|
||||||
//
|
//
|
||||||
// Stack transformation (x1==0): [... x1] -> [... x1]
|
// Stack transformation (x1==0): [... x1] -> [... x1]
|
||||||
// Stack transformation (x1!=0): [... x1] -> [... x1 x1]
|
// Stack transformation (x1!=0): [... x1] -> [... x1 x1]
|
||||||
func opcodeIfDup(op *parsedOpcode, vm *Engine) error {
|
func opcodeIfDup(op *opcode, data []byte, vm *Engine) error {
|
||||||
so, err := vm.dstack.PeekByteArray(0)
|
so, err := vm.dstack.PeekByteArray(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1440,7 +1185,7 @@ func opcodeIfDup(op *parsedOpcode, vm *Engine) error {
|
||||||
// Stack transformation: [...] -> [... <num of items on the stack>]
|
// Stack transformation: [...] -> [... <num of items on the stack>]
|
||||||
// Example with 2 items: [x1 x2] -> [x1 x2 2]
|
// Example with 2 items: [x1 x2] -> [x1 x2 2]
|
||||||
// Example with 3 items: [x1 x2 x3] -> [x1 x2 x3 3]
|
// Example with 3 items: [x1 x2 x3] -> [x1 x2 x3 3]
|
||||||
func opcodeDepth(op *parsedOpcode, vm *Engine) error {
|
func opcodeDepth(op *opcode, data []byte, vm *Engine) error {
|
||||||
vm.dstack.PushInt(scriptNum(vm.dstack.Depth()))
|
vm.dstack.PushInt(scriptNum(vm.dstack.Depth()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1448,28 +1193,28 @@ func opcodeDepth(op *parsedOpcode, vm *Engine) error {
|
||||||
// opcodeDrop removes the top item from the data stack.
|
// opcodeDrop removes the top item from the data stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2]
|
// Stack transformation: [... x1 x2 x3] -> [... x1 x2]
|
||||||
func opcodeDrop(op *parsedOpcode, vm *Engine) error {
|
func opcodeDrop(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.DropN(1)
|
return vm.dstack.DropN(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeDup duplicates the top item on the data stack.
|
// opcodeDup duplicates the top item on the data stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x3]
|
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x3]
|
||||||
func opcodeDup(op *parsedOpcode, vm *Engine) error {
|
func opcodeDup(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.DupN(1)
|
return vm.dstack.DupN(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeNip removes the item before the top item on the data stack.
|
// opcodeNip removes the item before the top item on the data stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x3]
|
// Stack transformation: [... x1 x2 x3] -> [... x1 x3]
|
||||||
func opcodeNip(op *parsedOpcode, vm *Engine) error {
|
func opcodeNip(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.NipN(1)
|
return vm.dstack.NipN(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeOver duplicates the item before the top item on the data stack.
|
// opcodeOver duplicates the item before the top item on the data stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2]
|
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2]
|
||||||
func opcodeOver(op *parsedOpcode, vm *Engine) error {
|
func opcodeOver(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.OverN(1)
|
return vm.dstack.OverN(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1479,7 +1224,7 @@ func opcodeOver(op *parsedOpcode, vm *Engine) error {
|
||||||
// Stack transformation: [xn ... x2 x1 x0 n] -> [xn ... x2 x1 x0 xn]
|
// Stack transformation: [xn ... x2 x1 x0 n] -> [xn ... x2 x1 x0 xn]
|
||||||
// Example with n=1: [x2 x1 x0 1] -> [x2 x1 x0 x1]
|
// Example with n=1: [x2 x1 x0 1] -> [x2 x1 x0 x1]
|
||||||
// Example with n=2: [x2 x1 x0 2] -> [x2 x1 x0 x2]
|
// Example with n=2: [x2 x1 x0 2] -> [x2 x1 x0 x2]
|
||||||
func opcodePick(op *parsedOpcode, vm *Engine) error {
|
func opcodePick(op *opcode, data []byte, vm *Engine) error {
|
||||||
val, err := vm.dstack.PopInt()
|
val, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1494,7 +1239,7 @@ func opcodePick(op *parsedOpcode, vm *Engine) error {
|
||||||
// Stack transformation: [xn ... x2 x1 x0 n] -> [... x2 x1 x0 xn]
|
// Stack transformation: [xn ... x2 x1 x0 n] -> [... x2 x1 x0 xn]
|
||||||
// Example with n=1: [x2 x1 x0 1] -> [x2 x0 x1]
|
// Example with n=1: [x2 x1 x0 1] -> [x2 x0 x1]
|
||||||
// Example with n=2: [x2 x1 x0 2] -> [x1 x0 x2]
|
// Example with n=2: [x2 x1 x0 2] -> [x1 x0 x2]
|
||||||
func opcodeRoll(op *parsedOpcode, vm *Engine) error {
|
func opcodeRoll(op *opcode, data []byte, vm *Engine) error {
|
||||||
val, err := vm.dstack.PopInt()
|
val, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1506,14 +1251,14 @@ func opcodeRoll(op *parsedOpcode, vm *Engine) error {
|
||||||
// opcodeRot rotates the top 3 items on the data stack to the left.
|
// opcodeRot rotates the top 3 items on the data stack to the left.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2 x3] -> [... x2 x3 x1]
|
// Stack transformation: [... x1 x2 x3] -> [... x2 x3 x1]
|
||||||
func opcodeRot(op *parsedOpcode, vm *Engine) error {
|
func opcodeRot(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.RotN(1)
|
return vm.dstack.RotN(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeSwap swaps the top two items on the stack.
|
// opcodeSwap swaps the top two items on the stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... x2 x1]
|
// Stack transformation: [... x1 x2] -> [... x2 x1]
|
||||||
func opcodeSwap(op *parsedOpcode, vm *Engine) error {
|
func opcodeSwap(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.SwapN(1)
|
return vm.dstack.SwapN(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1521,7 +1266,7 @@ func opcodeSwap(op *parsedOpcode, vm *Engine) error {
|
||||||
// second-to-top item.
|
// second-to-top item.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... x2 x1 x2]
|
// Stack transformation: [... x1 x2] -> [... x2 x1 x2]
|
||||||
func opcodeTuck(op *parsedOpcode, vm *Engine) error {
|
func opcodeTuck(op *opcode, data []byte, vm *Engine) error {
|
||||||
return vm.dstack.Tuck()
|
return vm.dstack.Tuck()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1529,7 +1274,7 @@ func opcodeTuck(op *parsedOpcode, vm *Engine) error {
|
||||||
// stack.
|
// stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1] -> [... x1 len(x1)]
|
// Stack transformation: [... x1] -> [... x1 len(x1)]
|
||||||
func opcodeSize(op *parsedOpcode, vm *Engine) error {
|
func opcodeSize(op *opcode, data []byte, vm *Engine) error {
|
||||||
so, err := vm.dstack.PeekByteArray(0)
|
so, err := vm.dstack.PeekByteArray(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1543,7 +1288,7 @@ func opcodeSize(op *parsedOpcode, vm *Engine) error {
|
||||||
// bytes, and pushes the result, encoded as a boolean, back to the stack.
|
// bytes, and pushes the result, encoded as a boolean, back to the stack.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... bool]
|
// Stack transformation: [... x1 x2] -> [... bool]
|
||||||
func opcodeEqual(op *parsedOpcode, vm *Engine) error {
|
func opcodeEqual(op *opcode, data []byte, vm *Engine) error {
|
||||||
a, err := vm.dstack.PopByteArray()
|
a, err := vm.dstack.PopByteArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1564,8 +1309,8 @@ func opcodeEqual(op *parsedOpcode, vm *Engine) error {
|
||||||
// evaluates to true. An error is returned if it does not.
|
// evaluates to true. An error is returned if it does not.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... bool] -> [...]
|
// Stack transformation: [... x1 x2] -> [... bool] -> [...]
|
||||||
func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error {
|
func opcodeEqualVerify(op *opcode, data []byte, vm *Engine) error {
|
||||||
err := opcodeEqual(op, vm)
|
err := opcodeEqual(op, data, vm)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = abstractVerify(op, vm, ErrEqualVerify)
|
err = abstractVerify(op, vm, ErrEqualVerify)
|
||||||
}
|
}
|
||||||
|
@ -1576,7 +1321,7 @@ func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error {
|
||||||
// it with its incremented value (plus 1).
|
// it with its incremented value (plus 1).
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... x1 x2+1]
|
// Stack transformation: [... x1 x2] -> [... x1 x2+1]
|
||||||
func opcode1Add(op *parsedOpcode, vm *Engine) error {
|
func opcode1Add(op *opcode, data []byte, vm *Engine) error {
|
||||||
m, err := vm.dstack.PopInt()
|
m, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1590,7 +1335,7 @@ func opcode1Add(op *parsedOpcode, vm *Engine) error {
|
||||||
// it with its decremented value (minus 1).
|
// it with its decremented value (minus 1).
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... x1 x2-1]
|
// Stack transformation: [... x1 x2] -> [... x1 x2-1]
|
||||||
func opcode1Sub(op *parsedOpcode, vm *Engine) error {
|
func opcode1Sub(op *opcode, data []byte, vm *Engine) error {
|
||||||
m, err := vm.dstack.PopInt()
|
m, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1604,7 +1349,7 @@ func opcode1Sub(op *parsedOpcode, vm *Engine) error {
|
||||||
// it with its negation.
|
// it with its negation.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... x1 -x2]
|
// Stack transformation: [... x1 x2] -> [... x1 -x2]
|
||||||
func opcodeNegate(op *parsedOpcode, vm *Engine) error {
|
func opcodeNegate(op *opcode, data []byte, vm *Engine) error {
|
||||||
m, err := vm.dstack.PopInt()
|
m, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1618,7 +1363,7 @@ func opcodeNegate(op *parsedOpcode, vm *Engine) error {
|
||||||
// it with its absolute value.
|
// it with its absolute value.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... x1 abs(x2)]
|
// Stack transformation: [... x1 x2] -> [... x1 abs(x2)]
|
||||||
func opcodeAbs(op *parsedOpcode, vm *Engine) error {
|
func opcodeAbs(op *opcode, data []byte, vm *Engine) error {
|
||||||
m, err := vm.dstack.PopInt()
|
m, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1643,7 +1388,7 @@ func opcodeAbs(op *parsedOpcode, vm *Engine) error {
|
||||||
// Stack transformation (x2==0): [... x1 0] -> [... x1 1]
|
// Stack transformation (x2==0): [... x1 0] -> [... x1 1]
|
||||||
// Stack transformation (x2!=0): [... x1 1] -> [... x1 0]
|
// Stack transformation (x2!=0): [... x1 1] -> [... x1 0]
|
||||||
// Stack transformation (x2!=0): [... x1 17] -> [... x1 0]
|
// Stack transformation (x2!=0): [... x1 17] -> [... x1 0]
|
||||||
func opcodeNot(op *parsedOpcode, vm *Engine) error {
|
func opcodeNot(op *opcode, data []byte, vm *Engine) error {
|
||||||
m, err := vm.dstack.PopInt()
|
m, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1663,7 +1408,7 @@ func opcodeNot(op *parsedOpcode, vm *Engine) error {
|
||||||
// Stack transformation (x2==0): [... x1 0] -> [... x1 0]
|
// Stack transformation (x2==0): [... x1 0] -> [... x1 0]
|
||||||
// Stack transformation (x2!=0): [... x1 1] -> [... x1 1]
|
// Stack transformation (x2!=0): [... x1 1] -> [... x1 1]
|
||||||
// Stack transformation (x2!=0): [... x1 17] -> [... x1 1]
|
// Stack transformation (x2!=0): [... x1 17] -> [... x1 1]
|
||||||
func opcode0NotEqual(op *parsedOpcode, vm *Engine) error {
|
func opcode0NotEqual(op *opcode, data []byte, vm *Engine) error {
|
||||||
m, err := vm.dstack.PopInt()
|
m, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1680,7 +1425,7 @@ func opcode0NotEqual(op *parsedOpcode, vm *Engine) error {
|
||||||
// them with their sum.
|
// them with their sum.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... x1+x2]
|
// Stack transformation: [... x1 x2] -> [... x1+x2]
|
||||||
func opcodeAdd(op *parsedOpcode, vm *Engine) error {
|
func opcodeAdd(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1700,7 +1445,7 @@ func opcodeAdd(op *parsedOpcode, vm *Engine) error {
|
||||||
// entry.
|
// entry.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... x1-x2]
|
// Stack transformation: [... x1 x2] -> [... x1-x2]
|
||||||
func opcodeSub(op *parsedOpcode, vm *Engine) error {
|
func opcodeSub(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1722,7 +1467,7 @@ func opcodeSub(op *parsedOpcode, vm *Engine) error {
|
||||||
// Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 0]
|
// Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 0]
|
||||||
// Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 0]
|
// Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 0]
|
||||||
// Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1]
|
// Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1]
|
||||||
func opcodeBoolAnd(op *parsedOpcode, vm *Engine) error {
|
func opcodeBoolAnd(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1749,7 +1494,7 @@ func opcodeBoolAnd(op *parsedOpcode, vm *Engine) error {
|
||||||
// Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 1]
|
// Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 1]
|
||||||
// Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 1]
|
// Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 1]
|
||||||
// Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1]
|
// Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1]
|
||||||
func opcodeBoolOr(op *parsedOpcode, vm *Engine) error {
|
func opcodeBoolOr(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1774,7 +1519,7 @@ func opcodeBoolOr(op *parsedOpcode, vm *Engine) error {
|
||||||
//
|
//
|
||||||
// Stack transformation (x1==x2): [... 5 5] -> [... 1]
|
// Stack transformation (x1==x2): [... 5 5] -> [... 1]
|
||||||
// Stack transformation (x1!=x2): [... 5 7] -> [... 0]
|
// Stack transformation (x1!=x2): [... 5 7] -> [... 0]
|
||||||
func opcodeNumEqual(op *parsedOpcode, vm *Engine) error {
|
func opcodeNumEqual(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1802,8 +1547,8 @@ func opcodeNumEqual(op *parsedOpcode, vm *Engine) error {
|
||||||
// to true. An error is returned if it does not.
|
// to true. An error is returned if it does not.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... bool] -> [...]
|
// Stack transformation: [... x1 x2] -> [... bool] -> [...]
|
||||||
func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error {
|
func opcodeNumEqualVerify(op *opcode, data []byte, vm *Engine) error {
|
||||||
err := opcodeNumEqual(op, vm)
|
err := opcodeNumEqual(op, data, vm)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = abstractVerify(op, vm, ErrNumEqualVerify)
|
err = abstractVerify(op, vm, ErrNumEqualVerify)
|
||||||
}
|
}
|
||||||
|
@ -1815,7 +1560,7 @@ func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error {
|
||||||
//
|
//
|
||||||
// Stack transformation (x1==x2): [... 5 5] -> [... 0]
|
// Stack transformation (x1==x2): [... 5 5] -> [... 0]
|
||||||
// Stack transformation (x1!=x2): [... 5 7] -> [... 1]
|
// Stack transformation (x1!=x2): [... 5 7] -> [... 1]
|
||||||
func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error {
|
func opcodeNumNotEqual(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1840,7 +1585,7 @@ func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error {
|
||||||
// otherwise a 0.
|
// otherwise a 0.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... bool]
|
// Stack transformation: [... x1 x2] -> [... bool]
|
||||||
func opcodeLessThan(op *parsedOpcode, vm *Engine) error {
|
func opcodeLessThan(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1865,7 +1610,7 @@ func opcodeLessThan(op *parsedOpcode, vm *Engine) error {
|
||||||
// with a 1, otherwise a 0.
|
// with a 1, otherwise a 0.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... bool]
|
// Stack transformation: [... x1 x2] -> [... bool]
|
||||||
func opcodeGreaterThan(op *parsedOpcode, vm *Engine) error {
|
func opcodeGreaterThan(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1889,7 +1634,7 @@ func opcodeGreaterThan(op *parsedOpcode, vm *Engine) error {
|
||||||
// replaced with a 1, otherwise a 0.
|
// replaced with a 1, otherwise a 0.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... bool]
|
// Stack transformation: [... x1 x2] -> [... bool]
|
||||||
func opcodeLessThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
func opcodeLessThanOrEqual(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1913,7 +1658,7 @@ func opcodeLessThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
||||||
// item, they are replaced with a 1, otherwise a 0.
|
// item, they are replaced with a 1, otherwise a 0.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... bool]
|
// Stack transformation: [... x1 x2] -> [... bool]
|
||||||
func opcodeGreaterThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
func opcodeGreaterThanOrEqual(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1937,7 +1682,7 @@ func opcodeGreaterThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
||||||
// them with the minimum of the two.
|
// them with the minimum of the two.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... min(x1, x2)]
|
// Stack transformation: [... x1 x2] -> [... min(x1, x2)]
|
||||||
func opcodeMin(op *parsedOpcode, vm *Engine) error {
|
func opcodeMin(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1961,7 +1706,7 @@ func opcodeMin(op *parsedOpcode, vm *Engine) error {
|
||||||
// them with the maximum of the two.
|
// them with the maximum of the two.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 x2] -> [... max(x1, x2)]
|
// Stack transformation: [... x1 x2] -> [... max(x1, x2)]
|
||||||
func opcodeMax(op *parsedOpcode, vm *Engine) error {
|
func opcodeMax(op *opcode, data []byte, vm *Engine) error {
|
||||||
v0, err := vm.dstack.PopInt()
|
v0, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1989,7 +1734,7 @@ func opcodeMax(op *parsedOpcode, vm *Engine) error {
|
||||||
// the third-to-top item is the value to test.
|
// the third-to-top item is the value to test.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1 min max] -> [... bool]
|
// Stack transformation: [... x1 min max] -> [... bool]
|
||||||
func opcodeWithin(op *parsedOpcode, vm *Engine) error {
|
func opcodeWithin(op *opcode, data []byte, vm *Engine) error {
|
||||||
maxVal, err := vm.dstack.PopInt()
|
maxVal, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2023,7 +1768,7 @@ func calcHash(buf []byte, hasher hash.Hash) []byte {
|
||||||
// replaces it with ripemd160(data).
|
// replaces it with ripemd160(data).
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1] -> [... ripemd160(x1)]
|
// Stack transformation: [... x1] -> [... ripemd160(x1)]
|
||||||
func opcodeRipemd160(op *parsedOpcode, vm *Engine) error {
|
func opcodeRipemd160(op *opcode, data []byte, vm *Engine) error {
|
||||||
buf, err := vm.dstack.PopByteArray()
|
buf, err := vm.dstack.PopByteArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2037,7 +1782,7 @@ func opcodeRipemd160(op *parsedOpcode, vm *Engine) error {
|
||||||
// with sha1(data).
|
// with sha1(data).
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1] -> [... sha1(x1)]
|
// Stack transformation: [... x1] -> [... sha1(x1)]
|
||||||
func opcodeSha1(op *parsedOpcode, vm *Engine) error {
|
func opcodeSha1(op *opcode, data []byte, vm *Engine) error {
|
||||||
buf, err := vm.dstack.PopByteArray()
|
buf, err := vm.dstack.PopByteArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2052,7 +1797,7 @@ func opcodeSha1(op *parsedOpcode, vm *Engine) error {
|
||||||
// it with sha256(data).
|
// it with sha256(data).
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1] -> [... sha256(x1)]
|
// Stack transformation: [... x1] -> [... sha256(x1)]
|
||||||
func opcodeSha256(op *parsedOpcode, vm *Engine) error {
|
func opcodeSha256(op *opcode, data []byte, vm *Engine) error {
|
||||||
buf, err := vm.dstack.PopByteArray()
|
buf, err := vm.dstack.PopByteArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2067,7 +1812,7 @@ func opcodeSha256(op *parsedOpcode, vm *Engine) error {
|
||||||
// it with ripemd160(sha256(data)).
|
// it with ripemd160(sha256(data)).
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1] -> [... ripemd160(sha256(x1))]
|
// Stack transformation: [... x1] -> [... ripemd160(sha256(x1))]
|
||||||
func opcodeHash160(op *parsedOpcode, vm *Engine) error {
|
func opcodeHash160(op *opcode, data []byte, vm *Engine) error {
|
||||||
buf, err := vm.dstack.PopByteArray()
|
buf, err := vm.dstack.PopByteArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2082,7 +1827,7 @@ func opcodeHash160(op *parsedOpcode, vm *Engine) error {
|
||||||
// it with sha256(sha256(data)).
|
// it with sha256(sha256(data)).
|
||||||
//
|
//
|
||||||
// Stack transformation: [... x1] -> [... sha256(sha256(x1))]
|
// Stack transformation: [... x1] -> [... sha256(sha256(x1))]
|
||||||
func opcodeHash256(op *parsedOpcode, vm *Engine) error {
|
func opcodeHash256(op *opcode, data []byte, vm *Engine) error {
|
||||||
buf, err := vm.dstack.PopByteArray()
|
buf, err := vm.dstack.PopByteArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2096,8 +1841,8 @@ func opcodeHash256(op *parsedOpcode, vm *Engine) error {
|
||||||
// seen OP_CODESEPARATOR which is used during signature checking.
|
// seen OP_CODESEPARATOR which is used during signature checking.
|
||||||
//
|
//
|
||||||
// This opcode does not change the contents of the data stack.
|
// This opcode does not change the contents of the data stack.
|
||||||
func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error {
|
func opcodeCodeSeparator(op *opcode, data []byte, vm *Engine) error {
|
||||||
vm.lastCodeSep = vm.scriptOff
|
vm.lastCodeSep = int(vm.tokenizer.ByteIndex())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2115,7 +1860,7 @@ func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error {
|
||||||
// cryptographic methods against the provided public key.
|
// cryptographic methods against the provided public key.
|
||||||
//
|
//
|
||||||
// Stack transformation: [... signature pubkey] -> [... bool]
|
// Stack transformation: [... signature pubkey] -> [... bool]
|
||||||
func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error {
|
||||||
pkBytes, err := vm.dstack.PopByteArray()
|
pkBytes, err := vm.dstack.PopByteArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2171,7 +1916,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||||
sigHashes = NewTxSigHashes(&vm.tx)
|
sigHashes = NewTxSigHashes(&vm.tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err = calcWitnessSignatureHash(subScript, sigHashes, hashType,
|
hash, err = calcWitnessSignatureHashRaw(subScript, sigHashes, hashType,
|
||||||
&vm.tx, vm.txIdx, vm.inputAmount)
|
&vm.tx, vm.txIdx, vm.inputAmount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2230,9 +1975,9 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||||
// The opcodeCheckSig function is invoked followed by opcodeVerify. See the
|
// The opcodeCheckSig function is invoked followed by opcodeVerify. See the
|
||||||
// documentation for each of those opcodes for more details.
|
// documentation for each of those opcodes for more details.
|
||||||
//
|
//
|
||||||
// Stack transformation: signature pubkey] -> [... bool] -> [...]
|
// Stack transformation: [... signature pubkey] -> [... bool] -> [...]
|
||||||
func opcodeCheckSigVerify(op *parsedOpcode, vm *Engine) error {
|
func opcodeCheckSigVerify(op *opcode, data []byte, vm *Engine) error {
|
||||||
err := opcodeCheckSig(op, vm)
|
err := opcodeCheckSig(op, data, vm)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = abstractVerify(op, vm, ErrCheckSigVerify)
|
err = abstractVerify(op, vm, ErrCheckSigVerify)
|
||||||
}
|
}
|
||||||
|
@ -2267,7 +2012,7 @@ type parsedSigInfo struct {
|
||||||
//
|
//
|
||||||
// Stack transformation:
|
// Stack transformation:
|
||||||
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool]
|
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool]
|
||||||
func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
func opcodeCheckMultiSig(op *opcode, data []byte, vm *Engine) error {
|
||||||
numKeys, err := vm.dstack.PopInt()
|
numKeys, err := vm.dstack.PopInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2444,7 +2189,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||||
sigHashes = NewTxSigHashes(&vm.tx)
|
sigHashes = NewTxSigHashes(&vm.tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err = calcWitnessSignatureHash(script, sigHashes, hashType,
|
hash, err = calcWitnessSignatureHashRaw(script, sigHashes, hashType,
|
||||||
&vm.tx, vm.txIdx, vm.inputAmount)
|
&vm.tx, vm.txIdx, vm.inputAmount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2493,8 +2238,8 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||||
//
|
//
|
||||||
// Stack transformation:
|
// Stack transformation:
|
||||||
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...]
|
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...]
|
||||||
func opcodeCheckMultiSigVerify(op *parsedOpcode, vm *Engine) error {
|
func opcodeCheckMultiSigVerify(op *opcode, data []byte, vm *Engine) error {
|
||||||
err := opcodeCheckMultiSig(op, vm)
|
err := opcodeCheckMultiSig(op, data, vm)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = abstractVerify(op, vm, ErrCheckMultiSigVerify)
|
err = abstractVerify(op, vm, ErrCheckMultiSigVerify)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ func TestOpcodeDisabled(t *testing.T) {
|
||||||
OP_LSHIFT, OP_RSHIFT,
|
OP_LSHIFT, OP_RSHIFT,
|
||||||
}
|
}
|
||||||
for _, opcodeVal := range tests {
|
for _, opcodeVal := range tests {
|
||||||
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: nil}
|
op := &opcodeArray[opcodeVal]
|
||||||
err := opcodeDisabled(&pop, nil)
|
err := opcodeDisabled(op, nil, nil)
|
||||||
if !IsErrorCode(err, ErrDisabledOpcode) {
|
if !IsErrorCode(err, ErrDisabledOpcode) {
|
||||||
t.Errorf("opcodeDisabled: unexpected error - got %v, "+
|
t.Errorf("opcodeDisabled: unexpected error - got %v, "+
|
||||||
"want %v", err, ErrDisabledOpcode)
|
"want %v", err, ErrDisabledOpcode)
|
||||||
|
@ -127,8 +127,9 @@ func TestOpcodeDisasm(t *testing.T) {
|
||||||
expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
|
expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: data}
|
var buf strings.Builder
|
||||||
gotStr := pop.print(true)
|
disasmOpcode(&buf, &opcodeArray[opcodeVal], data, true)
|
||||||
|
gotStr := buf.String()
|
||||||
if gotStr != expectedStr {
|
if gotStr != expectedStr {
|
||||||
t.Errorf("pop.print (opcode %x): Unexpected disasm "+
|
t.Errorf("pop.print (opcode %x): Unexpected disasm "+
|
||||||
"string - got %v, want %v", opcodeVal, gotStr,
|
"string - got %v, want %v", opcodeVal, gotStr,
|
||||||
|
@ -193,8 +194,9 @@ func TestOpcodeDisasm(t *testing.T) {
|
||||||
expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
|
expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: data}
|
var buf strings.Builder
|
||||||
gotStr := pop.print(false)
|
disasmOpcode(&buf, &opcodeArray[opcodeVal], data, false)
|
||||||
|
gotStr := buf.String()
|
||||||
if gotStr != expectedStr {
|
if gotStr != expectedStr {
|
||||||
t.Errorf("pop.print (opcode %x): Unexpected disasm "+
|
t.Errorf("pop.print (opcode %x): Unexpected disasm "+
|
||||||
"string - got %v, want %v", opcodeVal, gotStr,
|
"string - got %v, want %v", opcodeVal, gotStr,
|
||||||
|
|
|
@ -211,11 +211,12 @@ func computeNonWitnessPkScript(sigScript []byte) (PkScript, error) {
|
||||||
// The redeem script will always be the last data push of the
|
// The redeem script will always be the last data push of the
|
||||||
// signature script, so we'll parse the script into opcodes to
|
// signature script, so we'll parse the script into opcodes to
|
||||||
// obtain it.
|
// obtain it.
|
||||||
parsedOpcodes, err := parseScript(sigScript)
|
const scriptVersion = 0
|
||||||
|
err := checkScriptParses(scriptVersion, sigScript)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PkScript{}, err
|
return PkScript{}, err
|
||||||
}
|
}
|
||||||
redeemScript := parsedOpcodes[len(parsedOpcodes)-1].data
|
redeemScript := finalOpcodeData(scriptVersion, sigScript)
|
||||||
|
|
||||||
scriptHash := hash160(redeemScript)
|
scriptHash := hash160(redeemScript)
|
||||||
script, err := payToScriptHashScript(scriptHash)
|
script, err := payToScriptHashScript(scriptHash)
|
||||||
|
|
|
@ -836,6 +836,7 @@ func TestCalcSignatureHash(t *testing.T) {
|
||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scriptVersion = 0
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
// Skip first line -- contains comments only.
|
// Skip first line -- contains comments only.
|
||||||
|
@ -855,16 +856,20 @@ func TestCalcSignatureHash(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
subScript, _ := hex.DecodeString(test[1].(string))
|
subScript, _ := hex.DecodeString(test[1].(string))
|
||||||
parsedScript, err := parseScript(subScript)
|
if err := checkScriptParses(scriptVersion, subScript); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestCalcSignatureHash failed test #%d: "+
|
t.Errorf("TestCalcSignatureHash failed test #%d: "+
|
||||||
"Failed to parse sub-script: %v", i, err)
|
"Failed to parse sub-script: %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
hashType := SigHashType(testVecF64ToUint32(test[3].(float64)))
|
hashType := SigHashType(testVecF64ToUint32(test[3].(float64)))
|
||||||
hash := calcSignatureHash(parsedScript, hashType, &tx,
|
hash, err := CalcSignatureHash(subScript, hashType, &tx,
|
||||||
int(test[2].(float64)))
|
int(test[2].(float64)))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TestCalcSignatureHash failed test #%d: "+
|
||||||
|
"Failed to compute sighash: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
expectedHash, _ := chainhash.NewHashFromStr(test[4].(string))
|
expectedHash, _ := chainhash.NewHashFromStr(test[4].(string))
|
||||||
if !bytes.Equal(hash, expectedHash[:]) {
|
if !bytes.Equal(hash, expectedHash[:]) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright (c) 2013-2017 The btcsuite developers
|
// Copyright (c) 2013-2017 The btcsuite developers
|
||||||
|
// Copyright (c) 2015-2019 The Decred developers
|
||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
@ -44,66 +46,48 @@ const (
|
||||||
|
|
||||||
// isSmallInt returns whether or not the opcode is considered a small integer,
|
// isSmallInt returns whether or not the opcode is considered a small integer,
|
||||||
// which is an OP_0, or OP_1 through OP_16.
|
// which is an OP_0, or OP_1 through OP_16.
|
||||||
func isSmallInt(op *opcode) bool {
|
//
|
||||||
if op.value == OP_0 || (op.value >= OP_1 && op.value <= OP_16) {
|
// NOTE: This function is only valid for version 0 opcodes. Since the function
|
||||||
return true
|
// does not accept a script version, the results are undefined for other script
|
||||||
}
|
// versions.
|
||||||
return false
|
func isSmallInt(op byte) bool {
|
||||||
|
return op == OP_0 || (op >= OP_1 && op <= OP_16)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isScriptHash returns true if the script passed is a pay-to-script-hash
|
// IsPayToPubKey returns true if the script is in the standard pay-to-pubkey
|
||||||
// transaction, false otherwise.
|
// (P2PK) format, false otherwise.
|
||||||
func isScriptHash(pops []parsedOpcode) bool {
|
func IsPayToPubKey(script []byte) bool {
|
||||||
return len(pops) == 3 &&
|
return isPubKeyScript(script)
|
||||||
pops[0].opcode.value == OP_HASH160 &&
|
}
|
||||||
pops[1].opcode.value == OP_DATA_20 &&
|
|
||||||
pops[2].opcode.value == OP_EQUAL
|
// IsPayToPubKeyHash returns true if the script is in the standard
|
||||||
|
// pay-to-pubkey-hash (P2PKH) format, false otherwise.
|
||||||
|
func IsPayToPubKeyHash(script []byte) bool {
|
||||||
|
return isPubKeyHashScript(script)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPayToScriptHash returns true if the script is in the standard
|
// IsPayToScriptHash returns true if the script is in the standard
|
||||||
// pay-to-script-hash (P2SH) format, false otherwise.
|
// pay-to-script-hash (P2SH) format, false otherwise.
|
||||||
|
//
|
||||||
|
// WARNING: This function always treats the passed script as version 0. Great
|
||||||
|
// care must be taken if introducing a new script version because it is used in
|
||||||
|
// consensus which, unfortunately as of the time of this writing, does not check
|
||||||
|
// script versions before determining if the script is a P2SH which means nodes
|
||||||
|
// on existing rules will analyze new version scripts as if they were version 0.
|
||||||
func IsPayToScriptHash(script []byte) bool {
|
func IsPayToScriptHash(script []byte) bool {
|
||||||
pops, err := parseScript(script)
|
return isScriptHashScript(script)
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isScriptHash(pops)
|
|
||||||
}
|
|
||||||
|
|
||||||
// isWitnessScriptHash returns true if the passed script is a
|
|
||||||
// pay-to-witness-script-hash transaction, false otherwise.
|
|
||||||
func isWitnessScriptHash(pops []parsedOpcode) bool {
|
|
||||||
return len(pops) == 2 &&
|
|
||||||
pops[0].opcode.value == OP_0 &&
|
|
||||||
pops[1].opcode.value == OP_DATA_32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPayToWitnessScriptHash returns true if the is in the standard
|
// IsPayToWitnessScriptHash returns true if the is in the standard
|
||||||
// pay-to-witness-script-hash (P2WSH) format, false otherwise.
|
// pay-to-witness-script-hash (P2WSH) format, false otherwise.
|
||||||
func IsPayToWitnessScriptHash(script []byte) bool {
|
func IsPayToWitnessScriptHash(script []byte) bool {
|
||||||
pops, err := parseScript(script)
|
return isWitnessScriptHashScript(script)
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isWitnessScriptHash(pops)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPayToWitnessPubKeyHash returns true if the is in the standard
|
// IsPayToWitnessPubKeyHash returns true if the is in the standard
|
||||||
// pay-to-witness-pubkey-hash (P2WKH) format, false otherwise.
|
// pay-to-witness-pubkey-hash (P2WKH) format, false otherwise.
|
||||||
func IsPayToWitnessPubKeyHash(script []byte) bool {
|
func IsPayToWitnessPubKeyHash(script []byte) bool {
|
||||||
pops, err := parseScript(script)
|
return isWitnessPubKeyHashScript(script)
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isWitnessPubKeyHash(pops)
|
|
||||||
}
|
|
||||||
|
|
||||||
// isWitnessPubKeyHash returns true if the passed script is a
|
|
||||||
// pay-to-witness-pubkey-hash, and false otherwise.
|
|
||||||
func isWitnessPubKeyHash(pops []parsedOpcode) bool {
|
|
||||||
return len(pops) == 2 &&
|
|
||||||
pops[0].opcode.value == OP_0 &&
|
|
||||||
pops[1].opcode.value == OP_DATA_20
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWitnessProgram returns true if the passed script is a valid witness
|
// IsWitnessProgram returns true if the passed script is a valid witness
|
||||||
|
@ -111,163 +95,52 @@ func isWitnessPubKeyHash(pops []parsedOpcode) bool {
|
||||||
// witness program must be a small integer (from 0-16), followed by 2-40 bytes
|
// witness program must be a small integer (from 0-16), followed by 2-40 bytes
|
||||||
// of pushed data.
|
// of pushed data.
|
||||||
func IsWitnessProgram(script []byte) bool {
|
func IsWitnessProgram(script []byte) bool {
|
||||||
// The length of the script must be between 4 and 42 bytes. The
|
return isWitnessProgramScript(script)
|
||||||
// smallest program is the witness version, followed by a data push of
|
|
||||||
// 2 bytes. The largest allowed witness program has a data push of
|
|
||||||
// 40-bytes.
|
|
||||||
if len(script) < 4 || len(script) > 42 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
pops, err := parseScript(script)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return isWitnessProgram(pops)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isWitnessProgram returns true if the passed script is a witness program, and
|
// IsNullData returns true if the passed script is a null data script, false
|
||||||
// false otherwise. A witness program MUST adhere to the following constraints:
|
// otherwise.
|
||||||
// there must be exactly two pops (program version and the program itself), the
|
func IsNullData(script []byte) bool {
|
||||||
// first opcode MUST be a small integer (0-16), the push data MUST be
|
const scriptVersion = 0
|
||||||
// canonical, and finally the size of the push data must be between 2 and 40
|
return isNullDataScript(scriptVersion, script)
|
||||||
// bytes.
|
|
||||||
func isWitnessProgram(pops []parsedOpcode) bool {
|
|
||||||
return len(pops) == 2 &&
|
|
||||||
isSmallInt(pops[0].opcode) &&
|
|
||||||
canonicalPush(pops[1]) &&
|
|
||||||
(len(pops[1].data) >= 2 && len(pops[1].data) <= 40)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractWitnessProgramInfo attempts to extract the witness program version,
|
// ExtractWitnessProgramInfo attempts to extract the witness program version,
|
||||||
// as well as the witness program itself from the passed script.
|
// as well as the witness program itself from the passed script.
|
||||||
func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) {
|
func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) {
|
||||||
pops, err := parseScript(script)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If at this point, the scripts doesn't resemble a witness program,
|
// If at this point, the scripts doesn't resemble a witness program,
|
||||||
// then we'll exit early as there isn't a valid version or program to
|
// then we'll exit early as there isn't a valid version or program to
|
||||||
// extract.
|
// extract.
|
||||||
if !isWitnessProgram(pops) {
|
version, program, valid := extractWitnessProgramInfo(script)
|
||||||
|
if !valid {
|
||||||
return 0, nil, fmt.Errorf("script is not a witness program, " +
|
return 0, nil, fmt.Errorf("script is not a witness program, " +
|
||||||
"unable to extract version or witness program")
|
"unable to extract version or witness program")
|
||||||
}
|
}
|
||||||
|
|
||||||
witnessVersion := asSmallInt(pops[0].opcode)
|
return version, program, nil
|
||||||
witnessProgram := pops[1].data
|
|
||||||
|
|
||||||
return witnessVersion, witnessProgram, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isPushOnly returns true if the script only pushes data, false otherwise.
|
// IsPushOnlyScript returns whether or not the passed script only pushes data
|
||||||
func isPushOnly(pops []parsedOpcode) bool {
|
// according to the consensus definition of pushing data.
|
||||||
// NOTE: This function does NOT verify opcodes directly since it is
|
//
|
||||||
// internal and is only called with parsed opcodes for scripts that did
|
// WARNING: This function always treats the passed script as version 0. Great
|
||||||
// not have any parse errors. Thus, consensus is properly maintained.
|
// 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
|
||||||
for _, pop := range pops {
|
// 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 {
|
||||||
|
const scriptVersion = 0
|
||||||
|
tokenizer := MakeScriptTokenizer(scriptVersion, script)
|
||||||
|
for tokenizer.Next() {
|
||||||
// All opcodes up to OP_16 are data push instructions.
|
// All opcodes up to OP_16 are data push instructions.
|
||||||
// NOTE: This does consider OP_RESERVED to be a data push
|
// NOTE: This does consider OP_RESERVED to be a data push instruction,
|
||||||
// instruction, but execution of OP_RESERVED will fail anyways
|
// but execution of OP_RESERVED will fail anyway and matches the
|
||||||
// and matches the behavior required by consensus.
|
// behavior required by consensus.
|
||||||
if pop.opcode.value > OP_16 {
|
if tokenizer.Opcode() > OP_16 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return tokenizer.Err() == nil
|
||||||
}
|
|
||||||
|
|
||||||
// IsPushOnlyScript returns whether or not the passed script only pushes data.
|
|
||||||
//
|
|
||||||
// False will be returned when the script does not parse.
|
|
||||||
func IsPushOnlyScript(script []byte) bool {
|
|
||||||
pops, err := parseScript(script)
|
|
||||||
if err != nil {
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisasmString formats a disassembled script for one line printing. When the
|
// DisasmString formats a disassembled script for one line printing. When the
|
||||||
|
@ -275,41 +148,78 @@ func unparseScript(pops []parsedOpcode) ([]byte, error) {
|
||||||
// script up to the point the failure occurred along with the string '[error]'
|
// script up to the point the failure occurred along with the string '[error]'
|
||||||
// appended. In addition, the reason the script failed to parse is returned
|
// appended. In addition, the reason the script failed to parse is returned
|
||||||
// if the caller wants more information about the failure.
|
// if the caller wants more information about the failure.
|
||||||
func DisasmString(buf []byte) (string, error) {
|
//
|
||||||
var disbuf bytes.Buffer
|
// NOTE: This function is only valid for version 0 scripts. Since the function
|
||||||
opcodes, err := parseScript(buf)
|
// does not accept a script version, the results are undefined for other script
|
||||||
for _, pop := range opcodes {
|
// versions.
|
||||||
disbuf.WriteString(pop.print(true))
|
func DisasmString(script []byte) (string, error) {
|
||||||
|
const scriptVersion = 0
|
||||||
|
|
||||||
|
var disbuf strings.Builder
|
||||||
|
tokenizer := MakeScriptTokenizer(scriptVersion, script)
|
||||||
|
if tokenizer.Next() {
|
||||||
|
disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), true)
|
||||||
|
}
|
||||||
|
for tokenizer.Next() {
|
||||||
disbuf.WriteByte(' ')
|
disbuf.WriteByte(' ')
|
||||||
|
disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), true)
|
||||||
}
|
}
|
||||||
if disbuf.Len() > 0 {
|
if tokenizer.Err() != nil {
|
||||||
disbuf.Truncate(disbuf.Len() - 1)
|
if tokenizer.ByteIndex() != 0 {
|
||||||
}
|
disbuf.WriteByte(' ')
|
||||||
if err != nil {
|
}
|
||||||
disbuf.WriteString("[error]")
|
disbuf.WriteString("[error]")
|
||||||
}
|
}
|
||||||
return disbuf.String(), err
|
return disbuf.String(), tokenizer.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeOpcode will remove any opcode matching ``opcode'' from the opcode
|
// removeOpcodeRaw will return the script after removing any opcodes that match
|
||||||
// stream in pkscript
|
// `opcode`. If the opcode does not appear in script, the original script will
|
||||||
func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode {
|
// be returned unmodified. Otherwise, a new script will be allocated to contain
|
||||||
retScript := make([]parsedOpcode, 0, len(pkscript))
|
// the filtered script. This metehod assumes that the script parses
|
||||||
for _, pop := range pkscript {
|
// successfully.
|
||||||
if pop.opcode.value != opcode {
|
//
|
||||||
retScript = append(retScript, pop)
|
// NOTE: This function is only valid for version 0 scripts. Since the function
|
||||||
}
|
// does not accept a script version, the results are undefined for other script
|
||||||
|
// versions.
|
||||||
|
func removeOpcodeRaw(script []byte, opcode byte) []byte {
|
||||||
|
// Avoid work when possible.
|
||||||
|
if len(script) == 0 {
|
||||||
|
return script
|
||||||
}
|
}
|
||||||
return retScript
|
|
||||||
|
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]...)
|
||||||
|
}
|
||||||
|
} 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
|
// isCanonicalPush returns true if the opcode is either not a push instruction
|
||||||
// or the push instruction contained wherein is matches the canonical form
|
// or the data associated with the push instruction uses the smallest
|
||||||
// or using the smallest instruction to do the job. False otherwise.
|
// instruction to do the job. False otherwise.
|
||||||
func canonicalPush(pop parsedOpcode) bool {
|
//
|
||||||
opcode := pop.opcode.value
|
// For example, it is possible to push a value of 1 to the stack as "OP_1",
|
||||||
data := pop.data
|
// "OP_DATA_1 0x01", "OP_PUSHDATA1 0x01 0x01", and others, however, the first
|
||||||
dataLen := len(pop.data)
|
// only takes a single byte, while the rest take more. Only the first is
|
||||||
|
// considered canonical.
|
||||||
|
func isCanonicalPush(opcode byte, data []byte) bool {
|
||||||
|
dataLen := len(data)
|
||||||
if opcode > OP_16 {
|
if opcode > OP_16 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -329,17 +239,57 @@ func canonicalPush(pop parsedOpcode) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeOpcodeByData will return the script minus any opcodes that would push
|
// removeOpcodeByData will return the script minus any opcodes that perform a
|
||||||
// the passed data to the stack.
|
// canonical push of data that contains the passed data to remove. This
|
||||||
func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode {
|
// function assumes it is provided a version 0 script as any future version of
|
||||||
retScript := make([]parsedOpcode, 0, len(pkscript))
|
// script should avoid this functionality since it is unncessary due to the
|
||||||
for _, pop := range pkscript {
|
// signature scripts not being part of the witness-free transaction hash.
|
||||||
if !canonicalPush(pop) || !bytes.Contains(pop.data, data) {
|
//
|
||||||
retScript = append(retScript, pop)
|
// WARNING: This will return the passed script unmodified unless a modification
|
||||||
}
|
// is necessary in which case the modified script is returned. This implies
|
||||||
|
// callers may NOT rely on being able to safely mutate either the passed or
|
||||||
|
// returned script without potentially modifying the same data.
|
||||||
|
//
|
||||||
|
// NOTE: This function is only valid for version 0 scripts. Since the function
|
||||||
|
// does not accept a script version, the results are undefined for other script
|
||||||
|
// versions.
|
||||||
|
func removeOpcodeByData(script []byte, dataToRemove []byte) []byte {
|
||||||
|
// Avoid work when possible.
|
||||||
|
if len(script) == 0 || len(dataToRemove) == 0 {
|
||||||
|
return script
|
||||||
}
|
}
|
||||||
return retScript
|
|
||||||
|
|
||||||
|
// Parse through the script looking for a canonical data push that contains
|
||||||
|
// the data to remove.
|
||||||
|
const scriptVersion = 0
|
||||||
|
var result []byte
|
||||||
|
var prevOffset int32
|
||||||
|
tokenizer := MakeScriptTokenizer(scriptVersion, script)
|
||||||
|
for tokenizer.Next() {
|
||||||
|
// In practice, the script will basically never actually contain the
|
||||||
|
// data since this function is only used during signature verification
|
||||||
|
// to remove the signature itself which would require some incredibly
|
||||||
|
// non-standard code to create.
|
||||||
|
//
|
||||||
|
// Thus, as an optimization, avoid allocating a new script unless there
|
||||||
|
// is actually a match that needs to be removed.
|
||||||
|
op, data := tokenizer.Opcode(), tokenizer.Data()
|
||||||
|
if isCanonicalPush(op, data) && bytes.Contains(data, dataToRemove) {
|
||||||
|
if result == nil {
|
||||||
|
fullPushLen := tokenizer.ByteIndex() - prevOffset
|
||||||
|
result = make([]byte, 0, int32(len(script))-fullPushLen)
|
||||||
|
result = append(result, script[0:prevOffset]...)
|
||||||
|
}
|
||||||
|
} else if result != nil {
|
||||||
|
result = append(result, script[prevOffset:tokenizer.ByteIndex()]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
prevOffset = tokenizer.ByteIndex()
|
||||||
|
}
|
||||||
|
if result == nil {
|
||||||
|
result = script
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcHashPrevOuts calculates a single hash of all the previous outputs
|
// calcHashPrevOuts calculates a single hash of all the previous outputs
|
||||||
|
@ -396,7 +346,7 @@ func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
|
||||||
return chainhash.DoubleHashH(b.Bytes())
|
return chainhash.DoubleHashH(b.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcWitnessSignatureHash computes the sighash digest of a transaction's
|
// calcWitnessSignatureHashRaw computes the sighash digest of a transaction's
|
||||||
// segwit input using the new, optimized digest calculation algorithm defined
|
// segwit input using the new, optimized digest calculation algorithm defined
|
||||||
// in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki.
|
// in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki.
|
||||||
// This function makes use of pre-calculated sighash fragments stored within
|
// This function makes use of pre-calculated sighash fragments stored within
|
||||||
|
@ -407,7 +357,7 @@ func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
|
||||||
// being spent, in addition to the final transaction fee. In the case the
|
// being spent, in addition to the final transaction fee. In the case the
|
||||||
// wallet if fed an invalid input amount, the real sighash will differ causing
|
// wallet if fed an invalid input amount, the real sighash will differ causing
|
||||||
// the produced signature to be invalid.
|
// the produced signature to be invalid.
|
||||||
func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes,
|
func calcWitnessSignatureHashRaw(scriptSig []byte, sigHashes *TxSigHashes,
|
||||||
hashType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) {
|
hashType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) {
|
||||||
|
|
||||||
// As a sanity check, ensure the passed input index for the transaction
|
// As a sanity check, ensure the passed input index for the transaction
|
||||||
|
@ -457,7 +407,7 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes,
|
||||||
binary.LittleEndian.PutUint32(bIndex[:], txIn.PreviousOutPoint.Index)
|
binary.LittleEndian.PutUint32(bIndex[:], txIn.PreviousOutPoint.Index)
|
||||||
sigHash.Write(bIndex[:])
|
sigHash.Write(bIndex[:])
|
||||||
|
|
||||||
if isWitnessPubKeyHash(subScript) {
|
if isWitnessPubKeyHashScript(scriptSig) {
|
||||||
// The script code for a p2wkh is a length prefix varint for
|
// The script code for a p2wkh is a length prefix varint for
|
||||||
// the next 25 bytes, followed by a re-creation of the original
|
// the next 25 bytes, followed by a re-creation of the original
|
||||||
// p2pkh pk script.
|
// p2pkh pk script.
|
||||||
|
@ -465,15 +415,14 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes,
|
||||||
sigHash.Write([]byte{OP_DUP})
|
sigHash.Write([]byte{OP_DUP})
|
||||||
sigHash.Write([]byte{OP_HASH160})
|
sigHash.Write([]byte{OP_HASH160})
|
||||||
sigHash.Write([]byte{OP_DATA_20})
|
sigHash.Write([]byte{OP_DATA_20})
|
||||||
sigHash.Write(subScript[1].data)
|
sigHash.Write(extractWitnessPubKeyHash(scriptSig))
|
||||||
sigHash.Write([]byte{OP_EQUALVERIFY})
|
sigHash.Write([]byte{OP_EQUALVERIFY})
|
||||||
sigHash.Write([]byte{OP_CHECKSIG})
|
sigHash.Write([]byte{OP_CHECKSIG})
|
||||||
} else {
|
} else {
|
||||||
// For p2wsh outputs, and future outputs, the script code is
|
// For p2wsh outputs, and future outputs, the script code is
|
||||||
// the original script, with all code separators removed,
|
// the original script, with all code separators removed,
|
||||||
// serialized with a var int length prefix.
|
// serialized with a var int length prefix.
|
||||||
rawScript, _ := unparseScript(subScript)
|
wire.WriteVarBytes(&sigHash, 0, scriptSig)
|
||||||
wire.WriteVarBytes(&sigHash, 0, rawScript)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, add the input amount, and sequence number of the input being
|
// Next, add the input amount, and sequence number of the input being
|
||||||
|
@ -517,13 +466,12 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes,
|
||||||
func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType,
|
func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType,
|
||||||
tx *wire.MsgTx, idx int, amt int64) ([]byte, error) {
|
tx *wire.MsgTx, idx int, amt int64) ([]byte, error) {
|
||||||
|
|
||||||
parsedScript, err := parseScript(script)
|
const scriptVersion = 0
|
||||||
if err != nil {
|
if err := checkScriptParses(scriptVersion, script); err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse output script: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return calcWitnessSignatureHash(parsedScript, sigHashes, hType, tx, idx,
|
return calcWitnessSignatureHashRaw(script, sigHashes, hType, tx, idx, amt)
|
||||||
amt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// shallowCopyTx creates a shallow copy of the transaction for use when
|
// shallowCopyTx creates a shallow copy of the transaction for use when
|
||||||
|
@ -557,18 +505,22 @@ func shallowCopyTx(tx *wire.MsgTx) wire.MsgTx {
|
||||||
// CalcSignatureHash will, given a script and hash type for the current script
|
// CalcSignatureHash will, given a script and hash type for the current script
|
||||||
// engine instance, calculate the signature hash to be used for signing and
|
// engine instance, calculate the signature hash to be used for signing and
|
||||||
// verification.
|
// verification.
|
||||||
|
//
|
||||||
|
// NOTE: This function is only valid for version 0 scripts. Since the function
|
||||||
|
// does not accept a script version, the results are undefined for other script
|
||||||
|
// versions.
|
||||||
func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx int) ([]byte, error) {
|
func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx int) ([]byte, error) {
|
||||||
parsedScript, err := parseScript(script)
|
const scriptVersion = 0
|
||||||
if err != nil {
|
if err := checkScriptParses(scriptVersion, script); err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse output script: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return calcSignatureHash(parsedScript, hashType, tx, idx), nil
|
|
||||||
|
return calcSignatureHash(script, hashType, tx, idx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcSignatureHash will, given a script and hash type for the current script
|
// calcSignatureHash computes the signature hash for the specified input of the
|
||||||
// engine instance, calculate the signature hash to be used for signing and
|
// target transaction observing the desired signature hash type.
|
||||||
// verification.
|
func calcSignatureHash(sigScript []byte, hashType SigHashType, tx *wire.MsgTx, idx int) []byte {
|
||||||
func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.MsgTx, idx int) []byte {
|
|
||||||
// The SigHashSingle signature type signs only the corresponding input
|
// The SigHashSingle signature type signs only the corresponding input
|
||||||
// and output (the output with the same index number as the input).
|
// and output (the output with the same index number as the input).
|
||||||
//
|
//
|
||||||
|
@ -596,16 +548,13 @@ func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.Msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all instances of OP_CODESEPARATOR from the script.
|
// Remove all instances of OP_CODESEPARATOR from the script.
|
||||||
script = removeOpcode(script, OP_CODESEPARATOR)
|
sigScript = removeOpcodeRaw(sigScript, OP_CODESEPARATOR)
|
||||||
|
|
||||||
// Make a shallow copy of the transaction, zeroing out the script for
|
// Make a shallow copy of the transaction, zeroing out the script for
|
||||||
// all inputs that are not currently being processed.
|
// all inputs that are not currently being processed.
|
||||||
txCopy := shallowCopyTx(tx)
|
txCopy := shallowCopyTx(tx)
|
||||||
for i := range txCopy.TxIn {
|
for i := range txCopy.TxIn {
|
||||||
if i == idx {
|
if i == idx {
|
||||||
// UnparseScript cannot fail here because removeOpcode
|
|
||||||
// above only returns a valid script.
|
|
||||||
sigScript, _ := unparseScript(script)
|
|
||||||
txCopy.TxIn[idx].SignatureScript = sigScript
|
txCopy.TxIn[idx].SignatureScript = sigScript
|
||||||
} else {
|
} else {
|
||||||
txCopy.TxIn[i].SignatureScript = nil
|
txCopy.TxIn[i].SignatureScript = nil
|
||||||
|
@ -662,57 +611,98 @@ func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.Msg
|
||||||
|
|
||||||
// asSmallInt returns the passed opcode, which must be true according to
|
// asSmallInt returns the passed opcode, which must be true according to
|
||||||
// isSmallInt(), as an integer.
|
// isSmallInt(), as an integer.
|
||||||
func asSmallInt(op *opcode) int {
|
func asSmallInt(op byte) int {
|
||||||
if op.value == OP_0 {
|
if op == OP_0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return int(op.value - (OP_1 - 1))
|
return int(op - (OP_1 - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSigOpCount is the implementation function for counting the number of
|
// countSigOpsV0 returns the number of signature operations in the provided
|
||||||
// signature operations in the script provided by pops. If precise mode is
|
// script up to the point of the first parse failure or the entire script when
|
||||||
// requested then we attempt to count the number of operations for a multisig
|
// there are no parse failures. The precise flag attempts to accurately count
|
||||||
// op. Otherwise we use the maximum.
|
// the number of operations for a multisig operation versus using the maximum
|
||||||
func getSigOpCount(pops []parsedOpcode, precise bool) int {
|
// allowed.
|
||||||
nSigs := 0
|
//
|
||||||
for i, pop := range pops {
|
// WARNING: This function always treats the passed script as version 0. Great
|
||||||
switch pop.opcode.value {
|
// care must be taken if introducing a new script version because it is used in
|
||||||
case OP_CHECKSIG:
|
// consensus which, unfortunately as of the time of this writing, does not check
|
||||||
fallthrough
|
// script versions before counting their signature operations which means nodes
|
||||||
case OP_CHECKSIGVERIFY:
|
// on existing rules will count new version scripts as if they were version 0.
|
||||||
nSigs++
|
func countSigOpsV0(script []byte, precise bool) int {
|
||||||
case OP_CHECKMULTISIG:
|
const scriptVersion = 0
|
||||||
fallthrough
|
|
||||||
case OP_CHECKMULTISIGVERIFY:
|
numSigOps := 0
|
||||||
// If we are being precise then look for familiar
|
tokenizer := MakeScriptTokenizer(scriptVersion, script)
|
||||||
// patterns for multisig, for now all we recognize is
|
prevOp := byte(OP_INVALIDOPCODE)
|
||||||
// OP_1 - OP_16 to signify the number of pubkeys.
|
for tokenizer.Next() {
|
||||||
// Otherwise, we use the max of 20.
|
switch tokenizer.Opcode() {
|
||||||
if precise && i > 0 &&
|
case OP_CHECKSIG, OP_CHECKSIGVERIFY:
|
||||||
pops[i-1].opcode.value >= OP_1 &&
|
numSigOps++
|
||||||
pops[i-1].opcode.value <= OP_16 {
|
|
||||||
nSigs += asSmallInt(pops[i-1].opcode)
|
case OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY:
|
||||||
|
// Note that OP_0 is treated as the max number of sigops here in
|
||||||
|
// precise mode despite it being a valid small integer in order to
|
||||||
|
// highly discourage multisigs with zero pubkeys.
|
||||||
|
//
|
||||||
|
// Also, even though this is referred to as "precise" counting, it's
|
||||||
|
// not really precise at all due to the small int opcodes only
|
||||||
|
// covering 1 through 16 pubkeys, which means this will count any
|
||||||
|
// more than that value (e.g. 17, 18 19) as the maximum number of
|
||||||
|
// allowed pubkeys. This is, unfortunately, now part of
|
||||||
|
// the Bitcion consensus rules, due to historical
|
||||||
|
// reasons. This could be made more correct with a new
|
||||||
|
// script version, however, ideally all multisignaure
|
||||||
|
// operations in new script versions should move to
|
||||||
|
// aggregated schemes such as Schnorr instead.
|
||||||
|
if precise && prevOp >= OP_1 && prevOp <= OP_16 {
|
||||||
|
numSigOps += asSmallInt(prevOp)
|
||||||
} else {
|
} else {
|
||||||
nSigs += MaxPubKeysPerMultiSig
|
numSigOps += MaxPubKeysPerMultiSig
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Not a sigop.
|
// Not a sigop.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevOp = tokenizer.Opcode()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nSigs
|
return numSigOps
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSigOpCount provides a quick count of the number of signature operations
|
// GetSigOpCount provides a quick count of the number of signature operations
|
||||||
// in a script. a CHECKSIG operations counts for 1, and a CHECK_MULTISIG for 20.
|
// in a script. a CHECKSIG operations counts for 1, and a CHECK_MULTISIG for 20.
|
||||||
// If the script fails to parse, then the count up to the point of failure is
|
// If the script fails to parse, then the count up to the point of failure is
|
||||||
// returned.
|
// returned.
|
||||||
|
//
|
||||||
|
// WARNING: This function always treats the passed script as version 0. Great
|
||||||
|
// care must be taken if introducing a new script version because it is used in
|
||||||
|
// consensus which, unfortunately as of the time of this writing, does not check
|
||||||
|
// script versions before counting their signature operations which means nodes
|
||||||
|
// on existing rules will count new version scripts as if they were version 0.
|
||||||
func GetSigOpCount(script []byte) int {
|
func GetSigOpCount(script []byte) int {
|
||||||
// Don't check error since parseScript returns the parsed-up-to-error
|
return countSigOpsV0(script, false)
|
||||||
// list of pops.
|
}
|
||||||
pops, _ := parseScript(script)
|
|
||||||
return getSigOpCount(pops, false)
|
// finalOpcodeData returns the data associated with the final opcode in the
|
||||||
|
// script. It will return nil if the script fails to parse.
|
||||||
|
func finalOpcodeData(scriptVersion uint16, script []byte) []byte {
|
||||||
|
// Avoid unnecessary work.
|
||||||
|
if len(script) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
tokenizer := MakeScriptTokenizer(scriptVersion, script)
|
||||||
|
for tokenizer.Next() {
|
||||||
|
data = tokenizer.Data()
|
||||||
|
}
|
||||||
|
if tokenizer.Err() != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPreciseSigOpCount returns the number of signature operations in
|
// GetPreciseSigOpCount returns the number of signature operations in
|
||||||
|
@ -720,44 +710,44 @@ func GetSigOpCount(script []byte) int {
|
||||||
// Pay-To-Script-Hash script in order to find the precise number of signature
|
// Pay-To-Script-Hash script in order to find the precise number of signature
|
||||||
// operations in the transaction. If the script fails to parse, then the count
|
// operations in the transaction. If the script fails to parse, then the count
|
||||||
// up to the point of failure is returned.
|
// up to the point of failure is returned.
|
||||||
func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int {
|
//
|
||||||
// Don't check error since parseScript returns the parsed-up-to-error
|
// WARNING: This function always treats the passed script as version 0. Great
|
||||||
// list of pops.
|
// care must be taken if introducing a new script version because it is used in
|
||||||
pops, _ := parseScript(scriptPubKey)
|
// consensus which, unfortunately as of the time of this writing, does not check
|
||||||
|
// script versions before counting their signature operations which means nodes
|
||||||
|
// on existing rules will count new version scripts as if they were version 0.
|
||||||
|
//
|
||||||
|
// The third parameter is DEPRECATED and is unused.
|
||||||
|
func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, _ bool) int {
|
||||||
|
const scriptVersion = 0
|
||||||
|
|
||||||
// Treat non P2SH transactions as normal.
|
// Treat non P2SH transactions as normal. Note that signature operation
|
||||||
if !(bip16 && isScriptHash(pops)) {
|
// counting includes all operations up to the first parse failure.
|
||||||
return getSigOpCount(pops, true)
|
if !isScriptHashScript(scriptPubKey) {
|
||||||
}
|
return countSigOpsV0(scriptPubKey, true)
|
||||||
|
|
||||||
// The public key script is a pay-to-script-hash, so parse the signature
|
|
||||||
// script to get the final item. Scripts that fail to fully parse count
|
|
||||||
// as 0 signature operations.
|
|
||||||
sigPops, err := parseScript(scriptSig)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The signature script must only push data to the stack for P2SH to be
|
// The signature script must only push data to the stack for P2SH to be
|
||||||
// a valid pair, so the signature operation count is 0 when that is not
|
// a valid pair, so the signature operation count is 0 when that is not
|
||||||
// the case.
|
// the case.
|
||||||
if !isPushOnly(sigPops) || len(sigPops) == 0 {
|
if len(scriptSig) == 0 || !IsPushOnlyScript(scriptSig) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// The P2SH script is the last item the signature script pushes to the
|
// The P2SH script is the last item the signature script pushes to the
|
||||||
// stack. When the script is empty, there are no signature operations.
|
// stack. When the script is empty, there are no signature operations.
|
||||||
shScript := sigPops[len(sigPops)-1].data
|
//
|
||||||
if len(shScript) == 0 {
|
// Notice that signature scripts that fail to fully parse count as 0
|
||||||
|
// signature operations unlike public key and redeem scripts.
|
||||||
|
redeemScript := finalOpcodeData(scriptVersion, scriptSig)
|
||||||
|
if len(redeemScript) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the P2SH script and don't check the error since parseScript
|
// Return the more precise sigops count for the redeem script. Note that
|
||||||
// returns the parsed-up-to-error list of pops and the consensus rules
|
// signature operation counting includes all operations up to the first
|
||||||
// dictate signature operations are counted up to the first parse
|
// parse failure.
|
||||||
// failure.
|
return countSigOpsV0(redeemScript, true)
|
||||||
shPops, _ := parseScript(shScript)
|
|
||||||
return getSigOpCount(shPops, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWitnessSigOpCount returns the number of signature operations generated by
|
// GetWitnessSigOpCount returns the number of signature operations generated by
|
||||||
|
@ -769,19 +759,15 @@ func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int {
|
||||||
func GetWitnessSigOpCount(sigScript, pkScript []byte, witness wire.TxWitness) int {
|
func GetWitnessSigOpCount(sigScript, pkScript []byte, witness wire.TxWitness) int {
|
||||||
// If this is a regular witness program, then we can proceed directly
|
// If this is a regular witness program, then we can proceed directly
|
||||||
// to counting its signature operations without any further processing.
|
// to counting its signature operations without any further processing.
|
||||||
if IsWitnessProgram(pkScript) {
|
if isWitnessProgramScript(pkScript) {
|
||||||
return getWitnessSigOps(pkScript, witness)
|
return getWitnessSigOps(pkScript, witness)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, we'll check the sigScript to see if this is a nested p2sh
|
// Next, we'll check the sigScript to see if this is a nested p2sh
|
||||||
// witness program. This is a case wherein the sigScript is actually a
|
// witness program. This is a case wherein the sigScript is actually a
|
||||||
// datapush of a p2wsh witness program.
|
// datapush of a p2wsh witness program.
|
||||||
sigPops, err := parseScript(sigScript)
|
if isScriptHashScript(pkScript) && IsPushOnlyScript(sigScript) &&
|
||||||
if err != nil {
|
len(sigScript) > 0 && isWitnessProgramScript(sigScript[1:]) {
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if IsPayToScriptHash(pkScript) && isPushOnly(sigPops) &&
|
|
||||||
IsWitnessProgram(sigScript[1:]) {
|
|
||||||
return getWitnessSigOps(sigScript[1:], witness)
|
return getWitnessSigOps(sigScript[1:], witness)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -811,26 +797,41 @@ func getWitnessSigOps(pkScript []byte, witness wire.TxWitness) int {
|
||||||
len(witness) > 0:
|
len(witness) > 0:
|
||||||
|
|
||||||
witnessScript := witness[len(witness)-1]
|
witnessScript := witness[len(witness)-1]
|
||||||
pops, _ := parseScript(witnessScript)
|
return countSigOpsV0(witnessScript, true)
|
||||||
return getSigOpCount(pops, true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkScriptParses returns an error if the provided script fails to parse.
|
||||||
|
func checkScriptParses(scriptVersion uint16, script []byte) error {
|
||||||
|
tokenizer := MakeScriptTokenizer(scriptVersion, script)
|
||||||
|
for tokenizer.Next() {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
return tokenizer.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// IsUnspendable returns whether the passed public key script is unspendable, or
|
// IsUnspendable returns whether the passed public key script is unspendable, or
|
||||||
// guaranteed to fail at execution. This allows inputs to be pruned instantly
|
// guaranteed to fail at execution. This allows inputs to be pruned instantly
|
||||||
// when entering the UTXO set.
|
// when entering the UTXO set.
|
||||||
|
//
|
||||||
|
// NOTE: This function is only valid for version 0 scripts. Since the function
|
||||||
|
// does not accept a script version, the results are undefined for other script
|
||||||
|
// versions.
|
||||||
func IsUnspendable(pkScript []byte) bool {
|
func IsUnspendable(pkScript []byte) bool {
|
||||||
// Not provably unspendable
|
// The script is unspendable if starts with OP_RETURN or is guaranteed
|
||||||
if len(pkScript) == 0 {
|
// to fail at execution due to being larger than the max allowed script
|
||||||
return false
|
// size.
|
||||||
}
|
switch {
|
||||||
firstOpcode, err := checkScriptTemplateParseable(pkScript, &opcodeArray)
|
case len(pkScript) > 0 && pkScript[0] == OP_RETURN:
|
||||||
if err != nil {
|
return true
|
||||||
|
case len(pkScript) > MaxScriptSize:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return firstOpcode != nil && *firstOpcode == OP_RETURN
|
// The script is unspendable if it is guaranteed to fail at execution.
|
||||||
|
const scriptVersion = 0
|
||||||
|
return checkScriptParses(scriptVersion, pkScript) != nil
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,9 +12,21 @@ const (
|
||||||
maxInt32 = 1<<31 - 1
|
maxInt32 = 1<<31 - 1
|
||||||
minInt32 = -1 << 31
|
minInt32 = -1 << 31
|
||||||
|
|
||||||
// defaultScriptNumLen is the default number of bytes
|
// maxScriptNumLen is the maximum number of bytes data being interpreted
|
||||||
// data being interpreted as an integer may be.
|
// as an integer may be for the majority of op codes.
|
||||||
defaultScriptNumLen = 4
|
maxScriptNumLen = 4
|
||||||
|
|
||||||
|
// cltvMaxScriptNumLen is the maximum number of bytes data being interpreted
|
||||||
|
// as an integer may be for by-time and by-height locks as interpreted by
|
||||||
|
// CHECKLOCKTIMEVERIFY.
|
||||||
|
//
|
||||||
|
// The value comes from the fact that the current transaction locktime
|
||||||
|
// is a uint32 resulting in a maximum locktime of 2^32-1 (the year
|
||||||
|
// 2106). However, scriptNums are signed and therefore a standard
|
||||||
|
// 4-byte scriptNum would only support up to a maximum of 2^31-1 (the
|
||||||
|
// year 2038). Thus, a 5-byte scriptNum is needed since it will support
|
||||||
|
// up to 2^39-1 which allows dates beyond the current locktime limit.
|
||||||
|
cltvMaxScriptNumLen = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
// scriptNum represents a numeric value used in the scripting engine with
|
// scriptNum represents a numeric value used in the scripting engine with
|
||||||
|
@ -178,7 +190,7 @@ func (n scriptNum) Int32() int32 {
|
||||||
// before an ErrStackNumberTooBig is returned. This effectively limits the
|
// before an ErrStackNumberTooBig is returned. This effectively limits the
|
||||||
// range of allowed values.
|
// range of allowed values.
|
||||||
// WARNING: Great care should be taken if passing a value larger than
|
// WARNING: Great care should be taken if passing a value larger than
|
||||||
// defaultScriptNumLen, which could lead to addition and multiplication
|
// maxScriptNumLen, which could lead to addition and multiplication
|
||||||
// overflows.
|
// overflows.
|
||||||
//
|
//
|
||||||
// See the Bytes function documentation for example encodings.
|
// See the Bytes function documentation for example encodings.
|
||||||
|
|
|
@ -104,35 +104,35 @@ func TestMakeScriptNum(t *testing.T) {
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
// Minimal encoding must reject negative 0.
|
// Minimal encoding must reject negative 0.
|
||||||
{hexToBytes("80"), 0, defaultScriptNumLen, true, errMinimalData},
|
{hexToBytes("80"), 0, maxScriptNumLen, true, errMinimalData},
|
||||||
|
|
||||||
// Minimally encoded valid values with minimal encoding flag.
|
// Minimally encoded valid values with minimal encoding flag.
|
||||||
// Should not error and return expected integral number.
|
// Should not error and return expected integral number.
|
||||||
{nil, 0, defaultScriptNumLen, true, nil},
|
{nil, 0, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("01"), 1, defaultScriptNumLen, true, nil},
|
{hexToBytes("01"), 1, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("81"), -1, defaultScriptNumLen, true, nil},
|
{hexToBytes("81"), -1, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("7f"), 127, defaultScriptNumLen, true, nil},
|
{hexToBytes("7f"), 127, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("ff"), -127, defaultScriptNumLen, true, nil},
|
{hexToBytes("ff"), -127, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("8000"), 128, defaultScriptNumLen, true, nil},
|
{hexToBytes("8000"), 128, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("8080"), -128, defaultScriptNumLen, true, nil},
|
{hexToBytes("8080"), -128, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("8100"), 129, defaultScriptNumLen, true, nil},
|
{hexToBytes("8100"), 129, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("8180"), -129, defaultScriptNumLen, true, nil},
|
{hexToBytes("8180"), -129, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("0001"), 256, defaultScriptNumLen, true, nil},
|
{hexToBytes("0001"), 256, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("0081"), -256, defaultScriptNumLen, true, nil},
|
{hexToBytes("0081"), -256, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("ff7f"), 32767, defaultScriptNumLen, true, nil},
|
{hexToBytes("ff7f"), 32767, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("ffff"), -32767, defaultScriptNumLen, true, nil},
|
{hexToBytes("ffff"), -32767, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("008000"), 32768, defaultScriptNumLen, true, nil},
|
{hexToBytes("008000"), 32768, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("008080"), -32768, defaultScriptNumLen, true, nil},
|
{hexToBytes("008080"), -32768, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("ffff00"), 65535, defaultScriptNumLen, true, nil},
|
{hexToBytes("ffff00"), 65535, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("ffff80"), -65535, defaultScriptNumLen, true, nil},
|
{hexToBytes("ffff80"), -65535, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("000008"), 524288, defaultScriptNumLen, true, nil},
|
{hexToBytes("000008"), 524288, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("000088"), -524288, defaultScriptNumLen, true, nil},
|
{hexToBytes("000088"), -524288, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("000070"), 7340032, defaultScriptNumLen, true, nil},
|
{hexToBytes("000070"), 7340032, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("0000f0"), -7340032, defaultScriptNumLen, true, nil},
|
{hexToBytes("0000f0"), -7340032, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("00008000"), 8388608, defaultScriptNumLen, true, nil},
|
{hexToBytes("00008000"), 8388608, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("00008080"), -8388608, defaultScriptNumLen, true, nil},
|
{hexToBytes("00008080"), -8388608, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("ffffff7f"), 2147483647, defaultScriptNumLen, true, nil},
|
{hexToBytes("ffffff7f"), 2147483647, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("ffffffff"), -2147483647, defaultScriptNumLen, true, nil},
|
{hexToBytes("ffffffff"), -2147483647, maxScriptNumLen, true, nil},
|
||||||
{hexToBytes("ffffffff7f"), 549755813887, 5, true, nil},
|
{hexToBytes("ffffffff7f"), 549755813887, 5, true, nil},
|
||||||
{hexToBytes("ffffffffff"), -549755813887, 5, true, nil},
|
{hexToBytes("ffffffffff"), -549755813887, 5, true, nil},
|
||||||
{hexToBytes("ffffffffffffff7f"), 9223372036854775807, 8, true, nil},
|
{hexToBytes("ffffffffffffff7f"), 9223372036854775807, 8, true, nil},
|
||||||
|
@ -145,50 +145,50 @@ func TestMakeScriptNum(t *testing.T) {
|
||||||
// Minimally encoded values that are out of range for data that
|
// Minimally encoded values that are out of range for data that
|
||||||
// is interpreted as script numbers with the minimal encoding
|
// is interpreted as script numbers with the minimal encoding
|
||||||
// flag set. Should error and return 0.
|
// flag set. Should error and return 0.
|
||||||
{hexToBytes("0000008000"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("0000008000"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("0000008080"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("0000008080"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("0000009000"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("0000009000"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("0000009080"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("0000009080"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("ffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("ffffffff00"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("ffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("ffffffff80"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("0000000001"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("0000000001"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("0000000081"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("0000000081"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("ffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("ffffffffffff00"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("ffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("ffffffffffff80"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("ffffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("ffffffffffffff00"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("ffffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("ffffffffffffff80"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("ffffffffffffff7f"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("ffffffffffffff7f"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
{hexToBytes("ffffffffffffffff"), 0, defaultScriptNumLen, true, errNumTooBig},
|
{hexToBytes("ffffffffffffffff"), 0, maxScriptNumLen, true, errNumTooBig},
|
||||||
|
|
||||||
// Non-minimally encoded, but otherwise valid values with
|
// Non-minimally encoded, but otherwise valid values with
|
||||||
// minimal encoding flag. Should error and return 0.
|
// minimal encoding flag. Should error and return 0.
|
||||||
{hexToBytes("00"), 0, defaultScriptNumLen, true, errMinimalData}, // 0
|
{hexToBytes("00"), 0, maxScriptNumLen, true, errMinimalData}, // 0
|
||||||
{hexToBytes("0100"), 0, defaultScriptNumLen, true, errMinimalData}, // 1
|
{hexToBytes("0100"), 0, maxScriptNumLen, true, errMinimalData}, // 1
|
||||||
{hexToBytes("7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 127
|
{hexToBytes("7f00"), 0, maxScriptNumLen, true, errMinimalData}, // 127
|
||||||
{hexToBytes("800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 128
|
{hexToBytes("800000"), 0, maxScriptNumLen, true, errMinimalData}, // 128
|
||||||
{hexToBytes("810000"), 0, defaultScriptNumLen, true, errMinimalData}, // 129
|
{hexToBytes("810000"), 0, maxScriptNumLen, true, errMinimalData}, // 129
|
||||||
{hexToBytes("000100"), 0, defaultScriptNumLen, true, errMinimalData}, // 256
|
{hexToBytes("000100"), 0, maxScriptNumLen, true, errMinimalData}, // 256
|
||||||
{hexToBytes("ff7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 32767
|
{hexToBytes("ff7f00"), 0, maxScriptNumLen, true, errMinimalData}, // 32767
|
||||||
{hexToBytes("00800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 32768
|
{hexToBytes("00800000"), 0, maxScriptNumLen, true, errMinimalData}, // 32768
|
||||||
{hexToBytes("ffff0000"), 0, defaultScriptNumLen, true, errMinimalData}, // 65535
|
{hexToBytes("ffff0000"), 0, maxScriptNumLen, true, errMinimalData}, // 65535
|
||||||
{hexToBytes("00000800"), 0, defaultScriptNumLen, true, errMinimalData}, // 524288
|
{hexToBytes("00000800"), 0, maxScriptNumLen, true, errMinimalData}, // 524288
|
||||||
{hexToBytes("00007000"), 0, defaultScriptNumLen, true, errMinimalData}, // 7340032
|
{hexToBytes("00007000"), 0, maxScriptNumLen, true, errMinimalData}, // 7340032
|
||||||
{hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520
|
{hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520
|
||||||
|
|
||||||
// Non-minimally encoded, but otherwise valid values without
|
// Non-minimally encoded, but otherwise valid values without
|
||||||
// minimal encoding flag. Should not error and return expected
|
// minimal encoding flag. Should not error and return expected
|
||||||
// integral number.
|
// integral number.
|
||||||
{hexToBytes("00"), 0, defaultScriptNumLen, false, nil},
|
{hexToBytes("00"), 0, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("0100"), 1, defaultScriptNumLen, false, nil},
|
{hexToBytes("0100"), 1, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("7f00"), 127, defaultScriptNumLen, false, nil},
|
{hexToBytes("7f00"), 127, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("800000"), 128, defaultScriptNumLen, false, nil},
|
{hexToBytes("800000"), 128, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("810000"), 129, defaultScriptNumLen, false, nil},
|
{hexToBytes("810000"), 129, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("000100"), 256, defaultScriptNumLen, false, nil},
|
{hexToBytes("000100"), 256, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("ff7f00"), 32767, defaultScriptNumLen, false, nil},
|
{hexToBytes("ff7f00"), 32767, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("00800000"), 32768, defaultScriptNumLen, false, nil},
|
{hexToBytes("00800000"), 32768, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("ffff0000"), 65535, defaultScriptNumLen, false, nil},
|
{hexToBytes("ffff0000"), 65535, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("00000800"), 524288, defaultScriptNumLen, false, nil},
|
{hexToBytes("00000800"), 524288, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("00007000"), 7340032, defaultScriptNumLen, false, nil},
|
{hexToBytes("00007000"), 7340032, maxScriptNumLen, false, nil},
|
||||||
{hexToBytes("0009000100"), 16779520, 5, false, nil},
|
{hexToBytes("0009000100"), 16779520, 5, false, nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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,
|
amt int64, subScript []byte, hashType SigHashType,
|
||||||
key *btcec.PrivateKey) ([]byte, error) {
|
key *btcec.PrivateKey) ([]byte, error) {
|
||||||
|
|
||||||
parsedScript, err := parseScript(subScript)
|
hash, err := calcWitnessSignatureHashRaw(subScript, sigHashes, hashType, tx,
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot parse output script: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, err := calcWitnessSignatureHash(parsedScript, sigHashes, hashType, tx,
|
|
||||||
idx, amt)
|
idx, amt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -212,110 +207,50 @@ func sign(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeScripts merges sigScript and prevScript assuming they are both
|
|
||||||
// partial solutions for pkScript spending output idx of tx. class, addresses
|
|
||||||
// and nrequired are the result of extracting the addresses from pkscript.
|
|
||||||
// The return value is the best effort merging of the two scripts. Calling this
|
|
||||||
// function with addresses, class and nrequired that do not match pkScript is
|
|
||||||
// an error and results in undefined behaviour.
|
|
||||||
func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int,
|
|
||||||
pkScript []byte, class ScriptClass, addresses []btcutil.Address,
|
|
||||||
nRequired int, sigScript, prevScript []byte) []byte {
|
|
||||||
|
|
||||||
// TODO: the scripthash and multisig paths here are overly
|
|
||||||
// inefficient in that they will recompute already known data.
|
|
||||||
// some internal refactoring could probably make this avoid needless
|
|
||||||
// extra calculations.
|
|
||||||
switch class {
|
|
||||||
case ScriptHashTy:
|
|
||||||
// Remove the last push in the script and then recurse.
|
|
||||||
// this could be a lot less inefficient.
|
|
||||||
sigPops, err := parseScript(sigScript)
|
|
||||||
if err != nil || len(sigPops) == 0 {
|
|
||||||
return prevScript
|
|
||||||
}
|
|
||||||
prevPops, err := parseScript(prevScript)
|
|
||||||
if err != nil || len(prevPops) == 0 {
|
|
||||||
return sigScript
|
|
||||||
}
|
|
||||||
|
|
||||||
// assume that script in sigPops is the correct one, we just
|
|
||||||
// made it.
|
|
||||||
script := sigPops[len(sigPops)-1].data
|
|
||||||
|
|
||||||
// We already know this information somewhere up the stack.
|
|
||||||
class, addresses, nrequired, _ :=
|
|
||||||
ExtractPkScriptAddrs(script, chainParams)
|
|
||||||
|
|
||||||
// regenerate scripts.
|
|
||||||
sigScript, _ := unparseScript(sigPops)
|
|
||||||
prevScript, _ := unparseScript(prevPops)
|
|
||||||
|
|
||||||
// Merge
|
|
||||||
mergedScript := mergeScripts(chainParams, tx, idx, script,
|
|
||||||
class, addresses, nrequired, sigScript, prevScript)
|
|
||||||
|
|
||||||
// Reappend the script and return the result.
|
|
||||||
builder := NewScriptBuilder()
|
|
||||||
builder.AddOps(mergedScript)
|
|
||||||
builder.AddData(script)
|
|
||||||
finalScript, _ := builder.Script()
|
|
||||||
return finalScript
|
|
||||||
case MultiSigTy:
|
|
||||||
return mergeMultiSig(tx, idx, addresses, nRequired, pkScript,
|
|
||||||
sigScript, prevScript)
|
|
||||||
|
|
||||||
// It doesn't actually make sense to merge anything other than multiig
|
|
||||||
// and scripthash (because it could contain multisig). Everything else
|
|
||||||
// has either zero signature, can't be spent, or has a single signature
|
|
||||||
// which is either present or not. The other two cases are handled
|
|
||||||
// above. In the conflict case here we just assume the longest is
|
|
||||||
// correct (this matches behaviour of the reference implementation).
|
|
||||||
default:
|
|
||||||
if len(sigScript) > len(prevScript) {
|
|
||||||
return sigScript
|
|
||||||
}
|
|
||||||
return prevScript
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeMultiSig combines the two signature scripts sigScript and prevScript
|
// mergeMultiSig combines the two signature scripts sigScript and prevScript
|
||||||
// that both provide signatures for pkScript in output idx of tx. addresses
|
// that both provide signatures for pkScript in output idx of tx. addresses
|
||||||
// and nRequired should be the results from extracting the addresses from
|
// and nRequired should be the results from extracting the addresses from
|
||||||
// pkScript. Since this function is internal only we assume that the arguments
|
// pkScript. Since this function is internal only we assume that the arguments
|
||||||
// have come from other functions internally and thus are all consistent with
|
// have come from other functions internally and thus are all consistent with
|
||||||
// each other, behaviour is undefined if this contract is broken.
|
// each other, behaviour is undefined if this contract is broken.
|
||||||
|
//
|
||||||
|
// NOTE: This function is only valid for version 0 scripts. Since the function
|
||||||
|
// does not accept a script version, the results are undefined for other script
|
||||||
|
// versions.
|
||||||
func mergeMultiSig(tx *wire.MsgTx, idx int, addresses []btcutil.Address,
|
func mergeMultiSig(tx *wire.MsgTx, idx int, addresses []btcutil.Address,
|
||||||
nRequired int, pkScript, sigScript, prevScript []byte) []byte {
|
nRequired int, pkScript, sigScript, prevScript []byte) []byte {
|
||||||
|
|
||||||
// This is an internal only function and we already parsed this script
|
// Nothing to merge if either the new or previous signature scripts are
|
||||||
// as ok for multisig (this is how we got here), so if this fails then
|
// empty.
|
||||||
// all assumptions are broken and who knows which way is up?
|
if len(sigScript) == 0 {
|
||||||
pkPops, _ := parseScript(pkScript)
|
|
||||||
|
|
||||||
sigPops, err := parseScript(sigScript)
|
|
||||||
if err != nil || len(sigPops) == 0 {
|
|
||||||
return prevScript
|
return prevScript
|
||||||
}
|
}
|
||||||
|
if len(prevScript) == 0 {
|
||||||
prevPops, err := parseScript(prevScript)
|
|
||||||
if err != nil || len(prevPops) == 0 {
|
|
||||||
return sigScript
|
return sigScript
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience function to avoid duplication.
|
// Convenience function to avoid duplication.
|
||||||
extractSigs := func(pops []parsedOpcode, sigs [][]byte) [][]byte {
|
var possibleSigs [][]byte
|
||||||
for _, pop := range pops {
|
extractSigs := func(script []byte) error {
|
||||||
if len(pop.data) != 0 {
|
const scriptVersion = 0
|
||||||
sigs = append(sigs, pop.data)
|
tokenizer := MakeScriptTokenizer(scriptVersion, script)
|
||||||
|
for tokenizer.Next() {
|
||||||
|
if data := tokenizer.Data(); len(data) != 0 {
|
||||||
|
possibleSigs = append(possibleSigs, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sigs
|
return tokenizer.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
possibleSigs := make([][]byte, 0, len(sigPops)+len(prevPops))
|
// Attempt to extract signatures from the two scripts. Return the other
|
||||||
possibleSigs = extractSigs(sigPops, possibleSigs)
|
// script that is intended to be merged in the case signature extraction
|
||||||
possibleSigs = extractSigs(prevPops, possibleSigs)
|
// fails for some reason.
|
||||||
|
if err := extractSigs(sigScript); err != nil {
|
||||||
|
return prevScript
|
||||||
|
}
|
||||||
|
if err := extractSigs(prevScript); err != nil {
|
||||||
|
return sigScript
|
||||||
|
}
|
||||||
|
|
||||||
// Now we need to match the signatures to pubkeys, the only real way to
|
// Now we need to match the signatures to pubkeys, the only real way to
|
||||||
// do that is to try to verify them all and match it to the pubkey
|
// do that is to try to verify them all and match it to the pubkey
|
||||||
|
@ -345,7 +280,7 @@ sigLoop:
|
||||||
// however, assume no sigs etc are in the script since that
|
// however, assume no sigs etc are in the script since that
|
||||||
// would make the transaction nonstandard and thus not
|
// would make the transaction nonstandard and thus not
|
||||||
// MultiSigTy, so we just need to hash the full thing.
|
// MultiSigTy, so we just need to hash the full thing.
|
||||||
hash := calcSignatureHash(pkPops, hashType, tx, idx)
|
hash := calcSignatureHash(pkScript, hashType, tx, idx)
|
||||||
|
|
||||||
for _, addr := range addresses {
|
for _, addr := range addresses {
|
||||||
// All multisig addresses should be pubkey addresses
|
// All multisig addresses should be pubkey addresses
|
||||||
|
@ -394,6 +329,81 @@ sigLoop:
|
||||||
return script
|
return script
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeScripts merges sigScript and prevScript assuming they are both
|
||||||
|
// partial solutions for pkScript spending output idx of tx. class, addresses
|
||||||
|
// and nrequired are the result of extracting the addresses from pkscript.
|
||||||
|
// The return value is the best effort merging of the two scripts. Calling this
|
||||||
|
// function with addresses, class and nrequired that do not match pkScript is
|
||||||
|
// an error and results in undefined behaviour.
|
||||||
|
//
|
||||||
|
// NOTE: This function is only valid for version 0 scripts. Since the function
|
||||||
|
// does not accept a script version, the results are undefined for other script
|
||||||
|
// versions.
|
||||||
|
func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int,
|
||||||
|
pkScript []byte, class ScriptClass, addresses []btcutil.Address,
|
||||||
|
nRequired int, sigScript, prevScript []byte) []byte {
|
||||||
|
|
||||||
|
// TODO(oga) the scripthash and multisig paths here are overly
|
||||||
|
// inefficient in that they will recompute already known data.
|
||||||
|
// some internal refactoring could probably make this avoid needless
|
||||||
|
// extra calculations.
|
||||||
|
const scriptVersion = 0
|
||||||
|
switch class {
|
||||||
|
case ScriptHashTy:
|
||||||
|
// Nothing to merge if either the new or previous signature
|
||||||
|
// scripts are empty or fail to parse.
|
||||||
|
if len(sigScript) == 0 ||
|
||||||
|
checkScriptParses(scriptVersion, sigScript) != nil {
|
||||||
|
|
||||||
|
return prevScript
|
||||||
|
}
|
||||||
|
if len(prevScript) == 0 ||
|
||||||
|
checkScriptParses(scriptVersion, prevScript) != nil {
|
||||||
|
|
||||||
|
return sigScript
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the last push in the script and then recurse.
|
||||||
|
// this could be a lot less inefficient.
|
||||||
|
//
|
||||||
|
// Assume that final script is the correct one since it was just
|
||||||
|
// made and it is a pay-to-script-hash.
|
||||||
|
script := finalOpcodeData(scriptVersion, sigScript)
|
||||||
|
|
||||||
|
// We already know this information somewhere up the stack,
|
||||||
|
// therefore the error is ignored.
|
||||||
|
class, addresses, nrequired, _ :=
|
||||||
|
ExtractPkScriptAddrs(script, chainParams)
|
||||||
|
|
||||||
|
// Merge
|
||||||
|
mergedScript := mergeScripts(chainParams, tx, idx, script,
|
||||||
|
class, addresses, nrequired, sigScript, prevScript)
|
||||||
|
|
||||||
|
// Reappend the script and return the result.
|
||||||
|
builder := NewScriptBuilder()
|
||||||
|
builder.AddOps(mergedScript)
|
||||||
|
builder.AddData(script)
|
||||||
|
finalScript, _ := builder.Script()
|
||||||
|
return finalScript
|
||||||
|
|
||||||
|
case MultiSigTy:
|
||||||
|
return mergeMultiSig(tx, idx, addresses, nRequired, pkScript,
|
||||||
|
sigScript, prevScript)
|
||||||
|
|
||||||
|
// It doesn't actually make sense to merge anything other than multiig
|
||||||
|
// and scripthash (because it could contain multisig). Everything else
|
||||||
|
// has either zero signature, can't be spent, or has a single signature
|
||||||
|
// which is either present or not. The other two cases are handled
|
||||||
|
// above. In the conflict case here we just assume the longest is
|
||||||
|
// correct (this matches behaviour of the reference implementation).
|
||||||
|
default:
|
||||||
|
if len(sigScript) > len(prevScript) {
|
||||||
|
return sigScript
|
||||||
|
}
|
||||||
|
return prevScript
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// KeyDB is an interface type provided to SignTxOutput, it encapsulates
|
// KeyDB is an interface type provided to SignTxOutput, it encapsulates
|
||||||
// any user state required to get the private keys for an address.
|
// any user state required to get the private keys for an address.
|
||||||
type KeyDB interface {
|
type KeyDB interface {
|
||||||
|
@ -404,8 +414,7 @@ type KeyDB interface {
|
||||||
type KeyClosure func(btcutil.Address) (*btcec.PrivateKey, bool, error)
|
type KeyClosure func(btcutil.Address) (*btcec.PrivateKey, bool, error)
|
||||||
|
|
||||||
// GetKey implements KeyDB by returning the result of calling the closure.
|
// GetKey implements KeyDB by returning the result of calling the closure.
|
||||||
func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey,
|
func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey, bool, error) {
|
||||||
bool, error) {
|
|
||||||
return kc(address)
|
return kc(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,6 +439,10 @@ func (sc ScriptClosure) GetScript(address btcutil.Address) ([]byte, error) {
|
||||||
// getScript. If previousScript is provided then the results in previousScript
|
// getScript. If previousScript is provided then the results in previousScript
|
||||||
// will be merged in a type-dependent manner with the newly generated.
|
// will be merged in a type-dependent manner with the newly generated.
|
||||||
// signature script.
|
// signature script.
|
||||||
|
//
|
||||||
|
// NOTE: This function is only valid for version 0 scripts. Since the function
|
||||||
|
// does not accept a script version, the results are undefined for other script
|
||||||
|
// versions.
|
||||||
func SignTxOutput(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int,
|
func SignTxOutput(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int,
|
||||||
pkScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB,
|
pkScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB,
|
||||||
previousScript []byte) ([]byte, error) {
|
previousScript []byte) ([]byte, error) {
|
||||||
|
|
|
@ -86,7 +86,7 @@ func (s *stack) PopInt() (scriptNum, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen)
|
return makeScriptNum(so, s.verifyMinimalData, maxScriptNumLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PopBool pops the value off the top of the stack, converts it into a bool, and
|
// PopBool pops the value off the top of the stack, converts it into a bool, and
|
||||||
|
@ -123,7 +123,7 @@ func (s *stack) PeekInt(idx int32) (scriptNum, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen)
|
return makeScriptNum(so, s.verifyMinimalData, maxScriptNumLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeekBool returns the Nth item on the stack as a bool without removing it.
|
// PeekBool returns the Nth item on the stack as a bool without removing it.
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -832,7 +832,7 @@ func TestCalcMultiSigStats(t *testing.T) {
|
||||||
name: "short script",
|
name: "short script",
|
||||||
script: "0x046708afdb0fe5548271967f1a67130b7105cd6a828" +
|
script: "0x046708afdb0fe5548271967f1a67130b7105cd6a828" +
|
||||||
"e03909a67962e0ea1f61d",
|
"e03909a67962e0ea1f61d",
|
||||||
err: scriptError(ErrMalformedPush, ""),
|
err: scriptError(ErrNotMultisigScript, ""),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "stack underflow",
|
name: "stack underflow",
|
||||||
|
@ -843,11 +843,7 @@ func TestCalcMultiSigStats(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multisig script",
|
name: "multisig script",
|
||||||
script: "0 DATA_72 0x30450220106a3e4ef0b51b764a2887226" +
|
script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da45329a0" +
|
||||||
"2ffef55846514dacbdcbbdd652c849d395b4384022100" +
|
|
||||||
"e03ae554c3cbb40600d31dd46fc33f25e47bf8525b1fe" +
|
|
||||||
"07282e3b6ecb5f3bb2801 CODESEPARATOR 1 DATA_33 " +
|
|
||||||
"0x0232abdc893e7f0631364d7fd01cb33d24da45329a0" +
|
|
||||||
"0357b3a7886211ab414d55a 1 CHECKMULTISIG",
|
"0357b3a7886211ab414d55a 1 CHECKMULTISIG",
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
|
|
186
txscript/tokenizer.go
Normal file
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