WIP: next hard fork #5
3 changed files with 266 additions and 146 deletions
|
@ -119,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
|
||||||
|
@ -327,44 +390,17 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error {
|
||||||
return pop.opcode.opfunc(pop, vm)
|
return pop.opcode.opfunc(pop, 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 {
|
|
||||||
var buf strings.Builder
|
|
||||||
pop := vm.scripts[scriptIdx][scriptOff]
|
|
||||||
disasmOpcode(&buf, pop.opcode, pop.data, false)
|
|
||||||
return fmt.Sprintf("%02x:%04x: %s", scriptIdx, scriptOff, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -392,7 +428,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
|
||||||
}
|
}
|
||||||
|
@ -400,7 +438,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
|
||||||
|
@ -430,10 +468,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
|
||||||
}
|
}
|
||||||
|
@ -441,7 +479,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:
|
||||||
|
@ -482,18 +520,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,
|
||||||
|
@ -501,19 +571,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")
|
||||||
|
@ -527,11 +603,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,
|
||||||
|
@ -545,10 +624,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")
|
||||||
|
@ -556,25 +639,39 @@ 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)
|
pop := parsedOpcode{opcode: vm.tokenizer.op, data: vm.tokenizer.Data()}
|
||||||
|
err = vm.executeOpcode(&pop)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
@ -589,43 +686,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++
|
||||||
|
|
||||||
|
@ -633,30 +740,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)
|
||||||
}))
|
}))
|
||||||
|
@ -668,7 +791,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()
|
||||||
}
|
}
|
||||||
|
@ -684,7 +807,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:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1008,10 +1131,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")
|
||||||
|
@ -1057,29 +1180,28 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
||||||
vm.bip16 = true
|
vm.bip16 = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// The engine stores the scripts in parsed form using a slice. This
|
// The engine stores the scripts using a slice. This allows multiple
|
||||||
// allows multiple scripts to be executed in sequence. For example,
|
// scripts to be executed in sequence. For example, with a
|
||||||
// with a pay-to-script-hash transaction, there will be ultimately be
|
// pay-to-script-hash transaction, there will be ultimately be a third
|
||||||
// a third script to execute.
|
// script to execute.
|
||||||
scripts := [][]byte{scriptSig, scriptPubKey}
|
scripts := [][]byte{scriptSig, scriptPubKey}
|
||||||
vm.scripts = make([][]parsedOpcode, len(scripts))
|
for _, scr := range scripts {
|
||||||
for i, scr := range scripts {
|
|
||||||
if len(scr) > MaxScriptSize {
|
if len(scr) > MaxScriptSize {
|
||||||
str := fmt.Sprintf("script size %d is larger than max "+
|
str := fmt.Sprintf("script size %d is larger than max allowed "+
|
||||||
"allowed size %d", len(scr), MaxScriptSize)
|
"size %d", len(scr), MaxScriptSize)
|
||||||
return nil, scriptError(ErrScriptTooBig, str)
|
return nil, scriptError(ErrScriptTooBig, str)
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
vm.scripts[i], err = parseScript(scr)
|
const scriptVersion = 0
|
||||||
if err != nil {
|
if err := checkScriptParses(scriptVersion, scr); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
vm.scripts = scripts
|
||||||
|
|
||||||
// Advance the program counter to the public key script if the signature
|
// 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
|
// script is empty since there is nothing to execute for it in that case.
|
||||||
// case.
|
if len(scriptSig) == 0 {
|
||||||
if len(scripts[0]) == 0 {
|
|
||||||
vm.scriptIdx++
|
vm.scriptIdx++
|
||||||
}
|
}
|
||||||
if vm.hasFlag(ScriptVerifyMinimalData) {
|
if vm.hasFlag(ScriptVerifyMinimalData) {
|
||||||
|
@ -1103,7 +1225,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 {
|
||||||
|
@ -1118,12 +1240,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 &&
|
if len(sigPops) > 2 &&
|
||||||
isCanonicalPush(sigPops[0].opcode.value,
|
isCanonicalPush(sigPops[0], sigPops[1:]) &&
|
||||||
sigPops[0].data) &&
|
IsWitnessProgram(sigPops[1:]) {
|
||||||
IsWitnessProgram(sigPops[0].data) {
|
|
||||||
|
|
||||||
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"
|
||||||
|
@ -1150,6 +1271,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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1981,7 +1981,7 @@ func opcodeHash256(op *parsedOpcode, vm *Engine) error {
|
||||||
//
|
//
|
||||||
// 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 *parsedOpcode, vm *Engine) error {
|
||||||
vm.lastCodeSep = vm.scriptOff
|
vm.lastCodeSep = int(vm.tokenizer.ByteIndex())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2055,7 +2055,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
|
||||||
|
@ -2063,12 +2063,9 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||||
} else {
|
} else {
|
||||||
// Remove the signature since there is no way for a signature
|
// Remove the signature since there is no way for a signature
|
||||||
// to sign itself.
|
// to sign itself.
|
||||||
subScript = removeOpcodeByData(subScript, fullSigBytes)
|
subScript = removeOpcodeByDataRaw(subScript, fullSigBytes)
|
||||||
|
|
||||||
hash, err = calcSignatureHash(subScript, hashType, &vm.tx, vm.txIdx)
|
hash = calcSignatureHashRaw(subScript, hashType, &vm.tx, vm.txIdx)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pubKey, err := btcec.ParsePubKey(pkBytes, btcec.S256())
|
pubKey, err := btcec.ParsePubKey(pkBytes, btcec.S256())
|
||||||
|
@ -2239,7 +2236,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||||
// no way for a signature to sign itself.
|
// no way for a signature to sign itself.
|
||||||
if !vm.isWitnessVersionActive(0) {
|
if !vm.isWitnessVersionActive(0) {
|
||||||
for _, sigInfo := range signatures {
|
for _, sigInfo := range signatures {
|
||||||
script = removeOpcodeByData(script, sigInfo.signature)
|
script = removeOpcodeByDataRaw(script, sigInfo.signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2331,16 +2328,13 @@ 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
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hash, err = calcSignatureHash(script, hashType, &vm.tx, vm.txIdx)
|
hash = calcSignatureHashRaw(script, hashType, &vm.tx, vm.txIdx)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var valid bool
|
var valid bool
|
||||||
|
|
Loading…
Reference in a new issue