diff --git a/txscript/engine.go b/txscript/engine.go index 422be1c5f..0f0000c50 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -100,6 +100,17 @@ func (vm *Engine) hasFlag(flag ScriptFlags) bool { return vm.flags&flag == flag } +// isBranchExecuting returns whether or not the current conditional branch is +// actively executing. For example, when the data stack has an OP_FALSE on it +// and an OP_IF is encountered, the branch is inactive until an OP_ELSE or +// OP_ENDIF is encountered. It properly handles nested conditionals. +func (vm *Engine) isBranchExecuting() bool { + if len(vm.condStack) == 0 { + return true + } + return vm.condStack[len(vm.condStack)-1] == OpCondTrue +} + // Execute will execute all script in the script engine and return either nil // for successful validation or an error if one occurred. func (vm *Engine) Execute() (err error) { @@ -195,7 +206,7 @@ func (vm *Engine) Step() (done bool, err error) { vm.scriptOff++ if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) { // Illegal to have an `if' that straddles two scripts. - if err == nil && len(vm.condStack) != 1 { + if err == nil && len(vm.condStack) != 0 { return false, ErrStackMissingEndif } @@ -531,7 +542,6 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags vm.tx = *tx vm.txIdx = txIdx - vm.condStack = []int{OpCondTrue} return &vm, nil } diff --git a/txscript/opcode.go b/txscript/opcode.go index 121677209..09fcfc8d8 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -745,13 +745,13 @@ func (pop *parsedOpcode) exec(vm *Engine) error { // If we are not a conditional opcode and we aren't executing, then // we are done now. - if vm.condStack[0] != OpCondTrue && !pop.conditional() { + if !vm.isBranchExecuting() && !pop.conditional() { return nil } // Ensure all executed data push opcodes use the minimal encoding when // the minimal data verification is set. - if vm.dstack.verifyMinimalData && vm.condStack[0] == OpCondTrue && + if vm.dstack.verifyMinimalData && vm.isBranchExecuting() && pop.opcode.value >= 0 && pop.opcode.value <= OP_PUSHDATA4 { if err := pop.checkMinimalDataPush(); err != nil { return err @@ -896,22 +896,19 @@ func opcodeNop(op *parsedOpcode, vm *Engine) error { func opcodeIf(op *parsedOpcode, vm *Engine) error { // opcodeIf will be executed even if it is on the non-execute side // of the conditional, this is so proper nesting is maintained - var condval int - if vm.condStack[0] == OpCondTrue { + condVal := OpCondFalse + if vm.isBranchExecuting() { ok, err := vm.dstack.PopBool() if err != nil { return err } if ok { - condval = OpCondTrue + condVal = OpCondTrue } } else { - condval = OpCondSkip + condVal = OpCondSkip } - cond := []int{condval} - // push condition to the 'head' of the slice - vm.condStack = append(cond, vm.condStack...) - // TODO(drahn) check if a maximum condtitional stack limit exists + vm.condStack = append(vm.condStack, condVal) return nil } @@ -920,37 +917,34 @@ func opcodeIf(op *parsedOpcode, vm *Engine) error { func opcodeNotIf(op *parsedOpcode, vm *Engine) error { // opcodeIf will be executed even if it is on the non-execute side // of the conditional, this is so proper nesting is maintained - var condval int - if vm.condStack[0] == OpCondTrue { + condVal := OpCondFalse + if vm.isBranchExecuting() { ok, err := vm.dstack.PopBool() if err != nil { return err } if !ok { - condval = OpCondTrue + condVal = OpCondTrue } } else { - condval = OpCondSkip + condVal = OpCondSkip } - cond := []int{condval} - // push condition to the 'head' of the slice - vm.condStack = append(cond, vm.condStack...) - // TODO(drahn) check if a maximum condtitional stack limit exists + vm.condStack = append(vm.condStack, condVal) return nil } // opcodeElse inverts conditional execution for other half of if/else/endif func opcodeElse(op *parsedOpcode, vm *Engine) error { - if len(vm.condStack) < 2 { - // intial true cannot be toggled, only pushed conditionals + if len(vm.condStack) == 0 { return ErrStackNoIf } - switch vm.condStack[0] { + conditionalIdx := len(vm.condStack) - 1 + switch vm.condStack[conditionalIdx] { case OpCondTrue: - vm.condStack[0] = OpCondFalse + vm.condStack[conditionalIdx] = OpCondFalse case OpCondFalse: - vm.condStack[0] = OpCondTrue + vm.condStack[conditionalIdx] = OpCondTrue case OpCondSkip: // value doesn't change in skip } @@ -960,14 +954,11 @@ func opcodeElse(op *parsedOpcode, vm *Engine) error { // opcodeEndif terminates a conditional block, removing the value from the // conditional execution stack. func opcodeEndif(op *parsedOpcode, vm *Engine) error { - if len(vm.condStack) < 2 { - // intial true cannot be popped, only pushed conditionals + if len(vm.condStack) == 0 { return ErrStackNoIf } - stk := make([]int, len(vm.condStack)-1, len(vm.condStack)-1) - copy(stk, vm.condStack[1:]) - vm.condStack = stk + vm.condStack = vm.condStack[:len(vm.condStack)-1] return nil }