txscript: Refactor engine to use raw scripts.

This refactors the script engine to store and step through raw scripts
by making using of the new zero-allocation script tokenizer as opposed
to the less efficient method of storing and stepping through parsed
opcodes.  It also improves several aspects while refactoring such as
optimizing the disassembly trace, showing all scripts in the trace in
the case of execution failure, and providing additional comments
describing the purpose of each field in the engine.

It should be noted that this is a step towards removing the parsed
opcode struct and associated supporting code altogether, however, in
order to ease the review process, this retains the struct and all
function signatures for opcode execution which make use of an individual
parsed opcode.  Those will be updated in future commits.

The following is an overview of the changes:

- Modify internal engine scripts slice to use raw scripts instead of
  parsed opcodes
- Introduce a tokenizer to the engine to track the current script
- Remove no longer needed script offset parameter from the engine since
  that is tracked by the tokenizer
- Add an opcode index counter for disassembly purposes to the engine
- Update check for valid program counter to only consider the script
  index
  - Update tests for bad program counter accordingly
- Rework the NewEngine function
  - Store the raw scripts
  - Setup the initial tokenizer
  - Explicitly check against version 0 instead of DefaultScriptVersion
    which would break consensus if changed
  - Check the scripts parse according to version 0 semantics to retain
    current consensus rules
  - Improve comments throughout
- Rework the Step function
  - Use the tokenizer and raw scripts
  - Create a parsed opcode on the fly for now to retain existing
    opcode execution function signatures
  - Improve comments throughout
- Update the Execute function
  - Explicitly check against version 0 instead of DefaultScriptVersion
    which would break consensus if changed
  - Improve the disassembly tracing in the case of error
- Update the CheckErrorCondition function
  - Modify clean stack error message to make sense in all cases
  - Improve the comments
- Update the DisasmPC and DisasmScript functions on the engine
  - Use the tokenizer
  - Optimize construction via the use of strings.Builder
- Modify the subScript function to return the raw script bytes since the
  parsed opcodes are no longer stored
- Update the various signature checking opcodes to use the raw opcode
  data removal and signature hash calculation functions since the
  subscript is now a raw script
  - opcodeCheckSig
  - opcodeCheckMultiSig
  - opcodeCheckSigAlt
This commit is contained in:
Dave Collins 2019-03-13 01:12:58 -05:00 committed by Roy Lee
parent 6198f45307
commit b871286f98
3 changed files with 266 additions and 146 deletions

View file

@ -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
scriptIdx int // changed afterwards. The entries of the signature cache are mutated
scriptOff int // during execution, however, the cache pointer itself is not changed.
lastCodeSep int //
dstack stack // data stack // flags specifies the additional flags which modify the execution behavior
astack stack // alt stack // of the engine.
//
// tx identifies the transaction that contains the input which in turn
// contains the signature script being executed.
//
// txIdx identifies the input index within the transaction that contains
// the signature script being executed.
//
// version specifies the version of the public key script to execute. Since
// signature scripts redeem public keys scripts, this means the same version
// also extends to signature scripts and redeem scripts in the case of
// pay-to-script-hash.
//
// bip16 specifies that the public key script is of a special form that
// indicates it is a BIP16 pay-to-script-hash and therefore the
// execution must be treated as such.
//
// sigCache caches the results of signature verifications. This is useful
// since transaction scripts are often executed more than once from various
// contexts (e.g. new block templates, when transactions are first seen
// prior to being mined, part of full block verification, etc).
flags ScriptFlags
tx wire.MsgTx tx wire.MsgTx
txIdx int txIdx int
condStack []int version uint16
numOps int bip16 bool
flags ScriptFlags
sigCache *SigCache sigCache *SigCache
hashCache *TxSigHashes hashCache *TxSigHashes
bip16 bool // treat execution as pay-to-script-hash
savedFirstStack [][]byte // stack from first script for bip16 scripts // The following fields handle keeping track of the current execution state
// of the engine.
//
// scripts houses the raw scripts that are executed by the engine. This
// includes the signature script as well as the public key script. It also
// includes the redeem script in the case of pay-to-script-hash.
//
// scriptIdx tracks the index into the scripts array for the current program
// counter.
//
// opcodeIdx tracks the number of the opcode within the current script for
// the current program counter. Note that it differs from the actual byte
// index into the script and is really only used for disassembly purposes.
//
// lastCodeSep specifies the position within the current script of the last
// OP_CODESEPARATOR.
//
// tokenizer provides the token stream of the current script being executed
// and doubles as state tracking for the program counter within the script.
//
// savedFirstStack keeps a copy of the stack from the first script when
// performing pay-to-script-hash execution.
//
// dstack is the primary data stack the various opcodes push and pop data
// to and from during execution.
//
// astack is the alternate data stack the various opcodes push and pop data
// to and from during execution.
//
// condStack tracks the conditional execution state with support for
// multiple nested conditional execution opcodes.
//
// numOps tracks the total number of non-push operations in a script and is
// primarily used to enforce maximum limits.
scripts [][]byte
scriptIdx int
opcodeIdx int
lastCodeSep int
tokenizer ScriptTokenizer
savedFirstStack [][]byte
dstack stack
astack stack
condStack []int
numOps int
witnessVersion int witnessVersion int
witnessProgram []byte witnessProgram []byte
inputAmount int64 inputAmount int64
@ -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

View file

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

View file

@ -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