txscript: add verification of the post-segwit minimal if policy

This commit modifies the op-code execution for OP_IF and OP_NOTIF to
enforce the additional “minimal if” constraints which require the
top-stack item when the op codes are encountered to be either an empty
vector, or exactly [0x01].
This commit is contained in:
Olaoluwa Osuntokun 2017-04-26 16:27:44 -07:00 committed by Dave Collins
parent 65feec33e0
commit 9367aedfd7
2 changed files with 49 additions and 2 deletions

View file

@ -84,6 +84,10 @@ const (
// ScriptVerifyDiscourageUpgradeableWitnessProgram makes witness // ScriptVerifyDiscourageUpgradeableWitnessProgram makes witness
// program with versions 2-16 non-standard. // program with versions 2-16 non-standard.
ScriptVerifyDiscourageUpgradeableWitnessProgram ScriptVerifyDiscourageUpgradeableWitnessProgram
// ScriptVerifyMinimalIf makes a script with an OP_IF/OP_NOTIF whose
// operand is anything other than empty vector or [0x01] non-standard.
ScriptVerifyMinimalIf
) )
const ( const (

View file

@ -918,6 +918,47 @@ func opcodeNop(op *parsedOpcode, vm *Engine) error {
return nil return nil
} }
// popIfBool enforces the "minimal if" policy during script execution if the
// particular flag is set. If so, in order to eliminate an additional source
// of nuisance malleability, post-segwit for version 0 witness programs, we now
// require the following: for OP_IF and OP_NOT_IF, the top stack item MUST
// either be an empty byte slice, or [0x01]. Otherwise, the item at the top of
// the stack will be popped and interpreted as a boolean.
func popIfBool(vm *Engine) (bool, error) {
// When not in witness execution mode, not executing a v0 witness
// program, or the minimal if flag isn't set pop the top stack item as
// a normal bool.
if !vm.witness || !vm.hasFlag(ScriptVerifyMinimalIf) {
return vm.dstack.PopBool()
}
// At this point, a v0 witness program is being executed and the minimal
// if flag is set, so enforce additional constraints on the top stack
// item.
so, err := vm.dstack.PopByteArray()
if err != nil {
return false, err
}
// The top element MUST have a length of at least one.
if len(so) > 1 {
str := fmt.Sprintf("minimal if is active, top element MUST "+
"have a length of at least, instead length is %v",
len(so))
return false, scriptError(ErrMinimalIf, str)
}
// Additionally, if the length is one, then the value MUST be 0x01.
if len(so) == 1 && so[0] != 0x01 {
str := fmt.Sprintf("minimal if is active, top stack item MUST "+
"be an empty byte array or 0x01, is instead: %v",
so[0])
return false, scriptError(ErrMinimalIf, str)
}
return asBool(so), nil
}
// opcodeIf treats the top item on the data stack as a boolean and removes it. // opcodeIf treats the top item on the data stack as a boolean and removes it.
// //
// An appropriate entry is added to the conditional stack depending on whether // An appropriate entry is added to the conditional stack depending on whether
@ -936,10 +977,11 @@ func opcodeNop(op *parsedOpcode, vm *Engine) error {
func opcodeIf(op *parsedOpcode, vm *Engine) error { func opcodeIf(op *parsedOpcode, vm *Engine) error {
condVal := OpCondFalse condVal := OpCondFalse
if vm.isBranchExecuting() { if vm.isBranchExecuting() {
ok, err := vm.dstack.PopBool() ok, err := popIfBool(vm)
if err != nil { if err != nil {
return err return err
} }
if ok { if ok {
condVal = OpCondTrue condVal = OpCondTrue
} }
@ -969,10 +1011,11 @@ func opcodeIf(op *parsedOpcode, vm *Engine) error {
func opcodeNotIf(op *parsedOpcode, vm *Engine) error { func opcodeNotIf(op *parsedOpcode, vm *Engine) error {
condVal := OpCondFalse condVal := OpCondFalse
if vm.isBranchExecuting() { if vm.isBranchExecuting() {
ok, err := vm.dstack.PopBool() ok, err := popIfBool(vm)
if err != nil { if err != nil {
return err return err
} }
if !ok { if !ok {
condVal = OpCondTrue condVal = OpCondTrue
} }