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.
|
||||
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
|
||||
scriptOff int
|
||||
opcodeIdx int
|
||||
lastCodeSep int
|
||||
dstack stack // data stack
|
||||
astack stack // alt stack
|
||||
tx wire.MsgTx
|
||||
txIdx int
|
||||
tokenizer ScriptTokenizer
|
||||
savedFirstStack [][]byte
|
||||
dstack stack
|
||||
astack stack
|
||||
condStack []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
|
||||
witnessProgram []byte
|
||||
inputAmount int64
|
||||
|
@ -327,44 +390,17 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error {
|
|||
return pop.opcode.opfunc(pop, vm)
|
||||
}
|
||||
|
||||
// disasm is a helper function to produce the output for DisasmPC and
|
||||
// DisasmScript. It produces the opcode prefixed by the program counter at the
|
||||
// provided position in the script. It does no error checking and leaves that
|
||||
// to the caller to provide a valid offset.
|
||||
func (vm *Engine) disasm(scriptIdx int, scriptOff int) string {
|
||||
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 {
|
||||
// checkValidPC returns an error if the current script position is not valid for
|
||||
// execution.
|
||||
func (vm *Engine) checkValidPC() error {
|
||||
if vm.scriptIdx >= len(vm.scripts) {
|
||||
str := fmt.Sprintf("past input scripts %v:%v %v:xxxx",
|
||||
vm.scriptIdx, vm.scriptOff, len(vm.scripts))
|
||||
return scriptError(ErrInvalidProgramCounter, str)
|
||||
}
|
||||
if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
|
||||
str := fmt.Sprintf("past input scripts %v:%v %v:%04d",
|
||||
vm.scriptIdx, vm.scriptOff, vm.scriptIdx,
|
||||
len(vm.scripts[vm.scriptIdx]))
|
||||
str := fmt.Sprintf("script index %d beyond total scripts %d",
|
||||
vm.scriptIdx, len(vm.scripts))
|
||||
return scriptError(ErrInvalidProgramCounter, str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// curPC returns either the current script and offset, or an error if the
|
||||
// position isn't valid.
|
||||
func (vm *Engine) curPC() (script int, off int, err error) {
|
||||
err = vm.validPC()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return vm.scriptIdx, vm.scriptOff, nil
|
||||
}
|
||||
|
||||
// isWitnessVersionActive returns true if a witness program was extracted
|
||||
// during the initialization of the Engine, and the program's version matches
|
||||
// the specified version.
|
||||
|
@ -392,7 +428,9 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pops, err := parseScript(pkScript)
|
||||
|
||||
const scriptVersion = 0
|
||||
err = checkScriptParses(vm.version, pkScript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -400,7 +438,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
|||
// Set the stack to the provided witness stack, then
|
||||
// append the pkScript generated above as the next
|
||||
// script to execute.
|
||||
vm.scripts = append(vm.scripts, pops)
|
||||
vm.scripts = append(vm.scripts, pkScript)
|
||||
vm.SetStack(witness)
|
||||
|
||||
case payToWitnessScriptHashDataSize: // P2WSH
|
||||
|
@ -430,10 +468,10 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
|||
"witness program hash mismatch")
|
||||
}
|
||||
|
||||
// With all the validity checks passed, parse the
|
||||
// script into individual op-codes so w can execute it
|
||||
// as the next script.
|
||||
pops, err := parseScript(witnessScript)
|
||||
// With all the validity checks passed, assert that the
|
||||
// script parses without failure.
|
||||
const scriptVersion = 0
|
||||
err := checkScriptParses(vm.version, witnessScript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -441,7 +479,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
|||
// The hash matched successfully, so use the witness as
|
||||
// the stack, and set the witnessScript to be the next
|
||||
// script executed.
|
||||
vm.scripts = append(vm.scripts, pops)
|
||||
vm.scripts = append(vm.scripts, witnessScript)
|
||||
vm.SetStack(witness[:len(witness)-1])
|
||||
|
||||
default:
|
||||
|
@ -482,18 +520,50 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
|||
}
|
||||
|
||||
// DisasmPC returns the string for the disassembly of the opcode that will be
|
||||
// next to execute when Step() is called.
|
||||
// next to execute when Step is called.
|
||||
func (vm *Engine) DisasmPC() (string, error) {
|
||||
scriptIdx, scriptOff, err := vm.curPC()
|
||||
if err != nil {
|
||||
if err := vm.checkValidPC(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return vm.disasm(scriptIdx, scriptOff), nil
|
||||
|
||||
// Create a copy of the current tokenizer and parse the next opcode in the
|
||||
// copy to avoid mutating the current one.
|
||||
peekTokenizer := vm.tokenizer
|
||||
if !peekTokenizer.Next() {
|
||||
// Note that due to the fact that all scripts are checked for parse
|
||||
// failures before this code ever runs, there should never be an error
|
||||
// here, but check again to be safe in case a refactor breaks that
|
||||
// assumption or new script versions are introduced with different
|
||||
// semantics.
|
||||
if err := peekTokenizer.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Note that this should be impossible to hit in practice because the
|
||||
// only way it could happen would be for the final opcode of a script to
|
||||
// already be parsed without the script index having been updated, which
|
||||
// is not the case since stepping the script always increments the
|
||||
// script index when parsing and executing the final opcode of a script.
|
||||
//
|
||||
// However, check again to be safe in case a refactor breaks that
|
||||
// assumption or new script versions are introduced with different
|
||||
// semantics.
|
||||
str := fmt.Sprintf("program counter beyond script index %d (bytes %x)",
|
||||
vm.scriptIdx, vm.scripts[vm.scriptIdx])
|
||||
return "", scriptError(ErrInvalidProgramCounter, str)
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
disasmOpcode(&buf, peekTokenizer.op, peekTokenizer.Data(), false)
|
||||
return fmt.Sprintf("%02x:%04x: %s", vm.scriptIdx, vm.opcodeIdx,
|
||||
buf.String()), nil
|
||||
}
|
||||
|
||||
// DisasmScript returns the disassembly string for the script at the requested
|
||||
// offset index. Index 0 is the signature script and 1 is the public key
|
||||
// script.
|
||||
// script. In the case of pay-to-script-hash, index 2 is the redeem script once
|
||||
// the execution has progressed far enough to have successfully verified script
|
||||
// hash and thus add the script to the scripts to execute.
|
||||
func (vm *Engine) DisasmScript(idx int) (string, error) {
|
||||
if idx >= len(vm.scripts) {
|
||||
str := fmt.Sprintf("script index %d >= total scripts %d", idx,
|
||||
|
@ -501,19 +571,25 @@ func (vm *Engine) DisasmScript(idx int) (string, error) {
|
|||
return "", scriptError(ErrInvalidIndex, str)
|
||||
}
|
||||
|
||||
var disstr string
|
||||
for i := range vm.scripts[idx] {
|
||||
disstr = disstr + vm.disasm(idx, i) + "\n"
|
||||
var disbuf strings.Builder
|
||||
script := vm.scripts[idx]
|
||||
tokenizer := MakeScriptTokenizer(vm.version, script)
|
||||
var opcodeIdx int
|
||||
for tokenizer.Next() {
|
||||
disbuf.WriteString(fmt.Sprintf("%02x:%04x: ", idx, opcodeIdx))
|
||||
disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), false)
|
||||
disbuf.WriteByte('\n')
|
||||
opcodeIdx++
|
||||
}
|
||||
return disstr, nil
|
||||
return disbuf.String(), tokenizer.Err()
|
||||
}
|
||||
|
||||
// CheckErrorCondition returns nil if the running script has ended and was
|
||||
// successful, leaving a a true boolean on the stack. An error otherwise,
|
||||
// including if the script has not finished.
|
||||
func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
||||
// Check execution is actually done. When pc is past the end of script
|
||||
// array there are no more scripts to run.
|
||||
// Check execution is actually done by ensuring the script index is after
|
||||
// the final script in the array script.
|
||||
if vm.scriptIdx < len(vm.scripts) {
|
||||
return scriptError(ErrScriptUnfinished,
|
||||
"error check when script unfinished")
|
||||
|
@ -527,11 +603,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
|||
"have clean stack")
|
||||
}
|
||||
|
||||
// The final script must end with exactly one data stack item when the
|
||||
// verify clean stack flag is set. Otherwise, there must be at least one
|
||||
// data stack item in order to interpret it as a boolean.
|
||||
if finalScript && vm.hasFlag(ScriptVerifyCleanStack) &&
|
||||
vm.dstack.Depth() != 1 {
|
||||
|
||||
str := fmt.Sprintf("stack contains %d unexpected items",
|
||||
vm.dstack.Depth()-1)
|
||||
str := fmt.Sprintf("stack must contain exactly one item (contains %d)",
|
||||
vm.dstack.Depth())
|
||||
return scriptError(ErrCleanStack, str)
|
||||
} else if vm.dstack.Depth() < 1 {
|
||||
return scriptError(ErrEmptyStack,
|
||||
|
@ -545,10 +624,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
|||
if !v {
|
||||
// Log interesting data.
|
||||
log.Tracef("%v", newLogClosure(func() string {
|
||||
dis0, _ := vm.DisasmScript(0)
|
||||
dis1, _ := vm.DisasmScript(1)
|
||||
return fmt.Sprintf("scripts failed: script0: %s\n"+
|
||||
"script1: %s", dis0, dis1)
|
||||
var buf strings.Builder
|
||||
buf.WriteString("scripts failed:\n")
|
||||
for i := range vm.scripts {
|
||||
dis, _ := vm.DisasmScript(i)
|
||||
buf.WriteString(fmt.Sprintf("script%d:\n", i))
|
||||
buf.WriteString(dis)
|
||||
}
|
||||
return buf.String()
|
||||
}))
|
||||
return scriptError(ErrEvalFalse,
|
||||
"false stack entry at end of script execution")
|
||||
|
@ -556,25 +639,39 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Step will execute the next instruction and move the program counter to the
|
||||
// next opcode in the script, or the next script if the current has ended. Step
|
||||
// will return true in the case that the last opcode was successfully executed.
|
||||
// Step executes the next instruction and moves the program counter to the next
|
||||
// opcode in the script, or the next script if the current has ended. Step will
|
||||
// return true in the case that the last opcode was successfully executed.
|
||||
//
|
||||
// The result of calling Step or any other method is undefined if an error is
|
||||
// returned.
|
||||
func (vm *Engine) Step() (done bool, err error) {
|
||||
// Verify that it is pointing to a valid script address.
|
||||
err = vm.validPC()
|
||||
if err != nil {
|
||||
// Verify the engine is pointing to a valid program counter.
|
||||
if err := vm.checkValidPC(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
opcode := &vm.scripts[vm.scriptIdx][vm.scriptOff]
|
||||
vm.scriptOff++
|
||||
|
||||
// Attempt to parse the next opcode from the current script.
|
||||
if !vm.tokenizer.Next() {
|
||||
// Note that due to the fact that all scripts are checked for parse
|
||||
// failures before this code ever runs, there should never be an error
|
||||
// here, but check again to be safe in case a refactor breaks that
|
||||
// assumption or new script versions are introduced with different
|
||||
// semantics.
|
||||
if err := vm.tokenizer.Err(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("attempt to step beyond script index %d (bytes %x)",
|
||||
vm.scriptIdx, vm.scripts[vm.scriptIdx])
|
||||
return true, scriptError(ErrInvalidProgramCounter, str)
|
||||
}
|
||||
|
||||
// Execute the opcode while taking into account several things such as
|
||||
// disabled opcodes, illegal opcodes, maximum allowed operations per
|
||||
// script, maximum script element sizes, and conditionals.
|
||||
err = vm.executeOpcode(opcode)
|
||||
// disabled opcodes, illegal opcodes, maximum allowed operations per script,
|
||||
// maximum script element sizes, and conditionals.
|
||||
pop := parsedOpcode{opcode: vm.tokenizer.op, data: vm.tokenizer.Data()}
|
||||
err = vm.executeOpcode(&pop)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
@ -589,43 +686,53 @@ func (vm *Engine) Step() (done bool, err error) {
|
|||
}
|
||||
|
||||
// Prepare for next instruction.
|
||||
if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
|
||||
// Illegal to have an `if' that straddles two scripts.
|
||||
if err == nil && len(vm.condStack) != 0 {
|
||||
vm.opcodeIdx++
|
||||
if vm.tokenizer.Done() {
|
||||
// Illegal to have a conditional that straddles two scripts.
|
||||
if len(vm.condStack) != 0 {
|
||||
return false, scriptError(ErrUnbalancedConditional,
|
||||
"end of script reached in conditional execution")
|
||||
}
|
||||
|
||||
// Alt stack doesn't persist.
|
||||
// Alt stack doesn't persist between scripts.
|
||||
_ = vm.astack.DropN(vm.astack.Depth())
|
||||
|
||||
vm.numOps = 0 // number of ops is per script.
|
||||
vm.scriptOff = 0
|
||||
if vm.scriptIdx == 0 && vm.bip16 {
|
||||
// The number of operations is per script.
|
||||
vm.numOps = 0
|
||||
|
||||
// Reset the opcode index for the next script.
|
||||
vm.opcodeIdx = 0
|
||||
|
||||
// Advance to the next script as needed.
|
||||
switch {
|
||||
case vm.scriptIdx == 0 && vm.bip16:
|
||||
vm.scriptIdx++
|
||||
vm.savedFirstStack = vm.GetStack()
|
||||
} else if vm.scriptIdx == 1 && vm.bip16 {
|
||||
|
||||
case vm.scriptIdx == 1 && vm.bip16:
|
||||
// Put us past the end for CheckErrorCondition()
|
||||
vm.scriptIdx++
|
||||
// Check script ran successfully and pull the script
|
||||
// out of the first stack and execute that.
|
||||
|
||||
// Check script ran successfully.
|
||||
err := vm.CheckErrorCondition(false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Obtain the redeem script from the first stack and ensure it
|
||||
// parses.
|
||||
script := vm.savedFirstStack[len(vm.savedFirstStack)-1]
|
||||
pops, err := parseScript(script)
|
||||
if err != nil {
|
||||
if err := checkScriptParses(vm.version, script); err != nil {
|
||||
return false, err
|
||||
}
|
||||
vm.scripts = append(vm.scripts, pops)
|
||||
vm.scripts = append(vm.scripts, script)
|
||||
|
||||
// Set stack to be the stack from first script minus the
|
||||
// Set stack to be the stack from first script minus the redeem
|
||||
// script itself
|
||||
vm.SetStack(vm.savedFirstStack[:len(vm.savedFirstStack)-1])
|
||||
} else if (vm.scriptIdx == 1 && vm.witnessProgram != nil) ||
|
||||
(vm.scriptIdx == 2 && vm.witnessProgram != nil && vm.bip16) { // Nested P2SH.
|
||||
|
||||
case vm.scriptIdx == 1 && vm.witnessProgram != nil,
|
||||
vm.scriptIdx == 2 && vm.witnessProgram != nil && vm.bip16: // np2sh
|
||||
|
||||
vm.scriptIdx++
|
||||
|
||||
|
@ -633,30 +740,46 @@ func (vm *Engine) Step() (done bool, err error) {
|
|||
if err := vm.verifyWitnessProgram(witness); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
|
||||
default:
|
||||
vm.scriptIdx++
|
||||
}
|
||||
// there are zero length scripts in the wild
|
||||
if vm.scriptIdx < len(vm.scripts) && vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
|
||||
|
||||
// Skip empty scripts.
|
||||
if vm.scriptIdx < len(vm.scripts) && len(vm.scripts[vm.scriptIdx]) == 0 {
|
||||
vm.scriptIdx++
|
||||
}
|
||||
|
||||
vm.lastCodeSep = 0
|
||||
if vm.scriptIdx >= len(vm.scripts) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Finally, update the current tokenizer used to parse through scripts
|
||||
// one opcode at a time to start from the beginning of the new script
|
||||
// associated with the program counter.
|
||||
vm.tokenizer = MakeScriptTokenizer(vm.version, vm.scripts[vm.scriptIdx])
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Execute will execute all scripts in the script engine and return either nil
|
||||
// for successful validation or an error if one occurred.
|
||||
func (vm *Engine) Execute() (err error) {
|
||||
// All script versions other than 0 currently execute without issue,
|
||||
// making all outputs to them anyone can pay. In the future this
|
||||
// will allow for the addition of new scripting languages.
|
||||
if vm.version != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
done := false
|
||||
for !done {
|
||||
log.Tracef("%v", newLogClosure(func() string {
|
||||
dis, err := vm.DisasmPC()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("stepping (%v)", err)
|
||||
return fmt.Sprintf("stepping - failed to disasm pc: %v", err)
|
||||
}
|
||||
return fmt.Sprintf("stepping %v", dis)
|
||||
}))
|
||||
|
@ -668,7 +791,7 @@ func (vm *Engine) Execute() (err error) {
|
|||
log.Tracef("%v", newLogClosure(func() string {
|
||||
var dstr, astr string
|
||||
|
||||
// if we're tracing, dump the stacks.
|
||||
// Log the non-empty stacks when tracing.
|
||||
if vm.dstack.Depth() != 0 {
|
||||
dstr = "Stack:\n" + vm.dstack.String()
|
||||
}
|
||||
|
@ -684,7 +807,7 @@ func (vm *Engine) Execute() (err error) {
|
|||
}
|
||||
|
||||
// subScript returns the script since the last OP_CODESEPARATOR.
|
||||
func (vm *Engine) subScript() []parsedOpcode {
|
||||
func (vm *Engine) subScript() []byte {
|
||||
return vm.scripts[vm.scriptIdx][vm.lastCodeSep:]
|
||||
}
|
||||
|
||||
|
@ -1008,10 +1131,10 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
|||
}
|
||||
scriptSig := tx.TxIn[txIdx].SignatureScript
|
||||
|
||||
// When both the signature script and public key script are empty the
|
||||
// result is necessarily an error since the stack would end up being
|
||||
// empty which is equivalent to a false top element. Thus, just return
|
||||
// the relevant error now as an optimization.
|
||||
// When both the signature script and public key script are empty the result
|
||||
// is necessarily an error since the stack would end up being empty which is
|
||||
// equivalent to a false top element. Thus, just return the relevant error
|
||||
// now as an optimization.
|
||||
if len(scriptSig) == 0 && len(scriptPubKey) == 0 {
|
||||
return nil, scriptError(ErrEvalFalse,
|
||||
"false stack entry at end of script execution")
|
||||
|
@ -1057,29 +1180,28 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
|||
vm.bip16 = true
|
||||
}
|
||||
|
||||
// The engine stores the scripts in parsed form using a slice. This
|
||||
// allows multiple scripts to be executed in sequence. For example,
|
||||
// with a pay-to-script-hash transaction, there will be ultimately be
|
||||
// a third script to execute.
|
||||
// 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}
|
||||
vm.scripts = make([][]parsedOpcode, len(scripts))
|
||||
for i, scr := range scripts {
|
||||
for _, scr := range scripts {
|
||||
if len(scr) > MaxScriptSize {
|
||||
str := fmt.Sprintf("script size %d is larger than max "+
|
||||
"allowed size %d", 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 {
|
||||
|
||||
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(scripts[0]) == 0 {
|
||||
// script is empty since there is nothing to execute for it in that case.
|
||||
if len(scriptSig) == 0 {
|
||||
vm.scriptIdx++
|
||||
}
|
||||
if vm.hasFlag(ScriptVerifyMinimalData) {
|
||||
|
@ -1103,7 +1225,7 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
|||
var witProgram []byte
|
||||
|
||||
switch {
|
||||
case isWitnessProgram(vm.scripts[1]):
|
||||
case IsWitnessProgram(vm.scripts[1]):
|
||||
// The scriptSig must be *empty* for all native witness
|
||||
// programs, otherwise we introduce malleability.
|
||||
if len(scriptSig) != 0 {
|
||||
|
@ -1118,12 +1240,11 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
|||
// data push of the witness program, otherwise we
|
||||
// reintroduce malleability.
|
||||
sigPops := vm.scripts[0]
|
||||
if len(sigPops) == 1 &&
|
||||
isCanonicalPush(sigPops[0].opcode.value,
|
||||
sigPops[0].data) &&
|
||||
IsWitnessProgram(sigPops[0].data) {
|
||||
if len(sigPops) > 2 &&
|
||||
isCanonicalPush(sigPops[0], sigPops[1:]) &&
|
||||
IsWitnessProgram(sigPops[1:]) {
|
||||
|
||||
witProgram = sigPops[0].data
|
||||
witProgram = sigPops[1:]
|
||||
} else {
|
||||
errStr := "signature script for witness " +
|
||||
"nested p2sh is not canonical"
|
||||
|
@ -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.txIdx = txIdx
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -11,16 +12,16 @@ import (
|
|||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// TestBadPC sets the pc to a deliberately bad result then confirms that Step()
|
||||
// TestBadPC sets the pc to a deliberately bad result then confirms that Step
|
||||
// and Disasm fail correctly.
|
||||
func TestBadPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
script, off int
|
||||
scriptIdx int
|
||||
}{
|
||||
{script: 2, off: 0},
|
||||
{script: 0, off: 2},
|
||||
{scriptIdx: 2},
|
||||
{scriptIdx: 3},
|
||||
}
|
||||
|
||||
// tx with almost empty scripts.
|
||||
|
@ -59,20 +60,20 @@ func TestBadPC(t *testing.T) {
|
|||
t.Errorf("Failed to create script: %v", err)
|
||||
}
|
||||
|
||||
// set to after all scripts
|
||||
vm.scriptIdx = test.script
|
||||
vm.scriptOff = test.off
|
||||
// Set to after all scripts.
|
||||
vm.scriptIdx = test.scriptIdx
|
||||
|
||||
// Ensure attempting to step fails.
|
||||
_, err = vm.Step()
|
||||
if err == nil {
|
||||
t.Errorf("Step with invalid pc (%v) succeeds!", test)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure attempting to disassemble the current program counter fails.
|
||||
_, err = vm.DisasmPC()
|
||||
if err == nil {
|
||||
t.Errorf("DisasmPC with invalid pc (%v) succeeds!",
|
||||
test)
|
||||
t.Errorf("DisasmPC with invalid pc (%v) succeeds!", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1981,7 +1981,7 @@ func opcodeHash256(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// This opcode does not change the contents of the data stack.
|
||||
func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error {
|
||||
vm.lastCodeSep = vm.scriptOff
|
||||
vm.lastCodeSep = int(vm.tokenizer.ByteIndex())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2055,7 +2055,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
|||
sigHashes = NewTxSigHashes(&vm.tx)
|
||||
}
|
||||
|
||||
hash, err = calcWitnessSignatureHash(subScript, sigHashes, hashType,
|
||||
hash, err = calcWitnessSignatureHashRaw(subScript, sigHashes, hashType,
|
||||
&vm.tx, vm.txIdx, vm.inputAmount)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2063,12 +2063,9 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
|||
} else {
|
||||
// Remove the signature since there is no way for a signature
|
||||
// to sign itself.
|
||||
subScript = removeOpcodeByData(subScript, fullSigBytes)
|
||||
subScript = removeOpcodeByDataRaw(subScript, fullSigBytes)
|
||||
|
||||
hash, err = calcSignatureHash(subScript, hashType, &vm.tx, vm.txIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash = calcSignatureHashRaw(subScript, hashType, &vm.tx, vm.txIdx)
|
||||
}
|
||||
|
||||
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.
|
||||
if !vm.isWitnessVersionActive(0) {
|
||||
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)
|
||||
}
|
||||
|
||||
hash, err = calcWitnessSignatureHash(script, sigHashes, hashType,
|
||||
hash, err = calcWitnessSignatureHashRaw(script, sigHashes, hashType,
|
||||
&vm.tx, vm.txIdx, vm.inputAmount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
hash, err = calcSignatureHash(script, hashType, &vm.tx, vm.txIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash = calcSignatureHashRaw(script, hashType, &vm.tx, vm.txIdx)
|
||||
}
|
||||
|
||||
var valid bool
|
||||
|
|
Loading…
Reference in a new issue