From 710bd5646e55a520054c4620e507b8867b864e64 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:55 -0500 Subject: [PATCH] txscript: Make min push accept raw opcode and data. This converts the checkMinimalDataPush function defined on a parsed opcode to a standalone function which accepts an opcode and data slice instead in order to make it more flexible for raw script analysis. It also updates all callers accordingly. --- txscript/engine.go | 51 +++++++++++++++++++++++++++++++++++++++++- txscript/opcode.go | 55 ---------------------------------------------- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index ddb26de5..a2dfad4c 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -225,6 +225,55 @@ func isOpcodeConditional(opcode byte) bool { } } +// checkMinimalDataPush returns whether or not the provided opcode is the +// smallest possible way to represent the given data. For example, the value 15 +// could be pushed with OP_DATA_1 15 (among other variations); however, OP_15 is +// a single opcode that represents the same value and is only a single byte +// versus two bytes. +func checkMinimalDataPush(op *opcode, data []byte) error { + opcodeVal := op.value + dataLen := len(data) + switch { + case dataLen == 0 && opcodeVal != OP_0: + str := fmt.Sprintf("zero length data push is encoded with opcode %s "+ + "instead of OP_0", op.name) + return scriptError(ErrMinimalData, str) + case dataLen == 1 && data[0] >= 1 && data[0] <= 16: + if opcodeVal != OP_1+data[0]-1 { + // Should have used OP_1 .. OP_16 + str := fmt.Sprintf("data push of the value %d encoded with opcode "+ + "%s instead of OP_%d", data[0], op.name, data[0]) + return scriptError(ErrMinimalData, str) + } + case dataLen == 1 && data[0] == 0x81: + if opcodeVal != OP_1NEGATE { + str := fmt.Sprintf("data push of the value -1 encoded with opcode "+ + "%s instead of OP_1NEGATE", op.name) + return scriptError(ErrMinimalData, str) + } + case dataLen <= 75: + if int(opcodeVal) != dataLen { + // Should have used a direct push + str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+ + "instead of OP_DATA_%d", dataLen, op.name, dataLen) + return scriptError(ErrMinimalData, str) + } + case dataLen <= 255: + if opcodeVal != OP_PUSHDATA1 { + str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+ + "instead of OP_PUSHDATA1", dataLen, op.name) + return scriptError(ErrMinimalData, str) + } + case dataLen <= 65535: + if opcodeVal != OP_PUSHDATA2 { + str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+ + "instead of OP_PUSHDATA2", dataLen, op.name) + return scriptError(ErrMinimalData, str) + } + } + return nil +} + // executeOpcode peforms execution on the passed opcode. It takes into account // whether or not it is hidden by conditionals, but some rules still must be // tested in this case. @@ -269,7 +318,7 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error { if vm.dstack.verifyMinimalData && vm.isBranchExecuting() && pop.opcode.value >= 0 && pop.opcode.value <= OP_PUSHDATA4 { - if err := pop.checkMinimalDataPush(); err != nil { + if err := checkMinimalDataPush(pop.opcode, pop.data); err != nil { return err } } diff --git a/txscript/opcode.go b/txscript/opcode.go index 7705f59b..95b47580 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -692,61 +692,6 @@ func (pop *parsedOpcode) checkParseableInScript(script []byte, scriptPos int) (i return scriptPos, nil } -// checkMinimalDataPush returns whether or not the current data push uses the -// smallest possible opcode to represent it. For example, the value 15 could -// be pushed with OP_DATA_1 15 (among other variations); however, OP_15 is a -// single opcode that represents the same value and is only a single byte versus -// two bytes. -func (pop *parsedOpcode) checkMinimalDataPush() error { - data := pop.data - dataLen := len(data) - opcode := pop.opcode.value - - if dataLen == 0 && opcode != OP_0 { - str := fmt.Sprintf("zero length data push is encoded with "+ - "opcode %s instead of OP_0", pop.opcode.name) - return scriptError(ErrMinimalData, str) - } else if dataLen == 1 && data[0] >= 1 && data[0] <= 16 { - if opcode != OP_1+data[0]-1 { - // Should have used OP_1 .. OP_16 - str := fmt.Sprintf("data push of the value %d encoded "+ - "with opcode %s instead of OP_%d", data[0], - pop.opcode.name, data[0]) - return scriptError(ErrMinimalData, str) - } - } else if dataLen == 1 && data[0] == 0x81 { - if opcode != OP_1NEGATE { - str := fmt.Sprintf("data push of the value -1 encoded "+ - "with opcode %s instead of OP_1NEGATE", - pop.opcode.name) - return scriptError(ErrMinimalData, str) - } - } else if dataLen <= 75 { - if int(opcode) != dataLen { - // Should have used a direct push - str := fmt.Sprintf("data push of %d bytes encoded "+ - "with opcode %s instead of OP_DATA_%d", dataLen, - pop.opcode.name, dataLen) - return scriptError(ErrMinimalData, str) - } - } else if dataLen <= 255 { - if opcode != OP_PUSHDATA1 { - str := fmt.Sprintf("data push of %d bytes encoded "+ - "with opcode %s instead of OP_PUSHDATA1", - dataLen, pop.opcode.name) - return scriptError(ErrMinimalData, str) - } - } else if dataLen <= 65535 { - if opcode != OP_PUSHDATA2 { - str := fmt.Sprintf("data push of %d bytes encoded "+ - "with opcode %s instead of OP_PUSHDATA2", - dataLen, pop.opcode.name) - return scriptError(ErrMinimalData, str) - } - } - return nil -} - // disasmOpcode writes a human-readable disassembly of the provided opcode and // data into the provided buffer. The compact flag indicates the disassembly // should print a more compact representation of data-carrying and small integer