txscript: Change makeScriptNum to take a length argument

While current existing numeric opcodes are limited to 4 bytes, new
opcodes may need different limits.

This mimics Bitcoin Core commit 99088d60d8a7747c6d1a7fd5d8cd388be1b3e138
This commit is contained in:
David Hill 2015-06-26 12:20:05 -04:00
parent e4c053e504
commit ce22159fb2
3 changed files with 107 additions and 84 deletions

View file

@ -8,9 +8,9 @@ const (
maxInt32 = 1<<31 - 1
minInt32 = -1 << 31
// maxScriptNumLen is the maximum number of bytes data being interpreted
// as an integer may be.
maxScriptNumLen = 4
// defaultScriptNumLen is the default number of bytes
// data being interpreted as an integer may be.
defaultScriptNumLen = 4
)
// scriptNum represents a numeric value used in the scripting engine with
@ -36,9 +36,9 @@ const (
//
// Then, whenever data is interpreted as an integer, it is converted to this
// type by using the makeScriptNum function which will return an error if the
// number is out of range (or not minimally encoded depending on a flag). Since
// all numeric opcodes involve pulling data from the stack and interpreting it
// as an integer, it provides the required behavior.
// number is out of range or not minimally encoded depending on parameters.
// Since all numeric opcodes involve pulling data from the stack and
// interpreting it as an integer, it provides the required behavior.
type scriptNum int64
// checkMinimalDataEncoding returns whether or not the passed byte array adheres
@ -132,11 +132,12 @@ func (n scriptNum) Bytes() []byte {
// and the consensus rules dictate numbers which are directly cast to ints
// provide this behavior.
//
// In practice, the number should never really be out of range since it will
// have been created with makeScriptNum which rejects them, but in case
// something in the future ends up calling this function against the result
// of some arithmetic, which IS allowed to be out of range before being
// reinterpreted as an integer, this will provide the correct behavior.
// In practice, for most opcodes, the number should never be out of range since
// it will have been created with makeScriptNum using the defaultScriptLen
// value, which rejects them. In case something in the future ends up calling
// this function against the result of some arithmetic, which IS allowed to be
// out of range before being reinterpreted as an integer, this will provide the
// correct behavior.
func (n scriptNum) Int32() int32 {
if n > maxInt32 {
return maxInt32
@ -152,10 +153,13 @@ func (n scriptNum) Int32() int32 {
// makeScriptNum interprets the passed serialized bytes as an encoded integer
// and returns the result as a script number.
//
// Since the consensus rules dictate the serialized bytes interpreted as ints
// are only allowed to be in the range [-2^31 + 1, 2^31 - 1], an error will be
// returned when the provided bytes would result in a number outside of that
// range.
// Since the consensus rules dictate that serialized bytes interpreted as ints
// are only allowed to be in the range determined by a maximum number of bytes,
// on a per opcode basis, an error will be returned when the provided bytes
// would result in a number outside of that range. In particular, the range for
// the vast majority of opcodes dealing with numeric values are limited to 4
// bytes and therefore will pass that value to this function resulting in an
// allowed range of [-2^31 + 1, 2^31 - 1].
//
// The requireMinimal flag causes an error to be returned if additional checks
// on the encoding determine it is not represented with the smallest possible
@ -164,11 +168,18 @@ func (n scriptNum) Int32() int32 {
// [0x7f 0x00 0x00 ...], etc. All forms except [0x7f] will return an error with
// requireMinimal enabled.
//
// The scriptNumLen is the maximum number of bytes the encoded value can be
// before an ErrStackNumberTooBig is returned. This effectively limits the
// range of allowed values.
// WARNING: Great care should be taken if passing a value larger than
// defaultScriptNumLen, which could lead to addition and multiplication
// overflows.
//
// See the Bytes function documentation for example encodings.
func makeScriptNum(v []byte, requireMinimal bool) (scriptNum, error) {
// Interpreting data as an integer requires that it is not larger than
// a 32-bit integer.
if len(v) > maxScriptNumLen {
func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (scriptNum, error) {
// Interpreting data requires that it is not larger than
// the the passed scriptNumLen value.
if len(v) > scriptNumLen {
return 0, ErrStackNumberTooBig
}

View file

@ -94,90 +94,102 @@ func TestMakeScriptNum(t *testing.T) {
tests := []struct {
serialized []byte
num scriptNum
numLen int
minimalEncoding bool
err error
}{
// Minimal encoding must reject negative 0.
{hexToBytes("80"), 0, true, ErrStackMinimalData},
{hexToBytes("80"), 0, defaultScriptNumLen, true, ErrStackMinimalData},
// Minimally encoded valid values with minimal encoding flag.
// Should not error and return expected integral number.
{nil, 0, true, nil},
{hexToBytes("01"), 1, true, nil},
{hexToBytes("81"), -1, true, nil},
{hexToBytes("7f"), 127, true, nil},
{hexToBytes("ff"), -127, true, nil},
{hexToBytes("8000"), 128, true, nil},
{hexToBytes("8080"), -128, true, nil},
{hexToBytes("8100"), 129, true, nil},
{hexToBytes("8180"), -129, true, nil},
{hexToBytes("0001"), 256, true, nil},
{hexToBytes("0081"), -256, true, nil},
{hexToBytes("ff7f"), 32767, true, nil},
{hexToBytes("ffff"), -32767, true, nil},
{hexToBytes("008000"), 32768, true, nil},
{hexToBytes("008080"), -32768, true, nil},
{hexToBytes("ffff00"), 65535, true, nil},
{hexToBytes("ffff80"), -65535, true, nil},
{hexToBytes("000008"), 524288, true, nil},
{hexToBytes("000088"), -524288, true, nil},
{hexToBytes("000070"), 7340032, true, nil},
{hexToBytes("0000f0"), -7340032, true, nil},
{hexToBytes("00008000"), 8388608, true, nil},
{hexToBytes("00008080"), -8388608, true, nil},
{hexToBytes("ffffff7f"), 2147483647, true, nil},
{hexToBytes("ffffffff"), -2147483647, true, nil},
{nil, 0, defaultScriptNumLen, true, nil},
{hexToBytes("01"), 1, defaultScriptNumLen, true, nil},
{hexToBytes("81"), -1, defaultScriptNumLen, true, nil},
{hexToBytes("7f"), 127, defaultScriptNumLen, true, nil},
{hexToBytes("ff"), -127, defaultScriptNumLen, true, nil},
{hexToBytes("8000"), 128, defaultScriptNumLen, true, nil},
{hexToBytes("8080"), -128, defaultScriptNumLen, true, nil},
{hexToBytes("8100"), 129, defaultScriptNumLen, true, nil},
{hexToBytes("8180"), -129, defaultScriptNumLen, true, nil},
{hexToBytes("0001"), 256, defaultScriptNumLen, true, nil},
{hexToBytes("0081"), -256, defaultScriptNumLen, true, nil},
{hexToBytes("ff7f"), 32767, defaultScriptNumLen, true, nil},
{hexToBytes("ffff"), -32767, defaultScriptNumLen, true, nil},
{hexToBytes("008000"), 32768, defaultScriptNumLen, true, nil},
{hexToBytes("008080"), -32768, defaultScriptNumLen, true, nil},
{hexToBytes("ffff00"), 65535, defaultScriptNumLen, true, nil},
{hexToBytes("ffff80"), -65535, defaultScriptNumLen, true, nil},
{hexToBytes("000008"), 524288, defaultScriptNumLen, true, nil},
{hexToBytes("000088"), -524288, defaultScriptNumLen, true, nil},
{hexToBytes("000070"), 7340032, defaultScriptNumLen, true, nil},
{hexToBytes("0000f0"), -7340032, defaultScriptNumLen, true, nil},
{hexToBytes("00008000"), 8388608, defaultScriptNumLen, true, nil},
{hexToBytes("00008080"), -8388608, defaultScriptNumLen, true, nil},
{hexToBytes("ffffff7f"), 2147483647, defaultScriptNumLen, true, nil},
{hexToBytes("ffffffff"), -2147483647, defaultScriptNumLen, true, nil},
{hexToBytes("ffffffff7f"), 549755813887, 5, true, nil},
{hexToBytes("ffffffffff"), -549755813887, 5, true, nil},
{hexToBytes("ffffffffffffff7f"), 9223372036854775807, 8, true, nil},
{hexToBytes("ffffffffffffffff"), -9223372036854775807, 8, true, nil},
{hexToBytes("ffffffffffffffff7f"), -1, 9, true, nil},
{hexToBytes("ffffffffffffffffff"), 1, 9, true, nil},
{hexToBytes("ffffffffffffffffff7f"), -1, 10, true, nil},
{hexToBytes("ffffffffffffffffffff"), 1, 10, true, nil},
// Minimally encoded values that are out of range for data that
// is interpreted as script numbers with the minimal encoding
// flag set. Should error and return 0.
{hexToBytes("0000008000"), 0, true, ErrStackNumberTooBig},
{hexToBytes("0000008080"), 0, true, ErrStackNumberTooBig},
{hexToBytes("0000009000"), 0, true, ErrStackNumberTooBig},
{hexToBytes("0000009080"), 0, true, ErrStackNumberTooBig},
{hexToBytes("ffffffff00"), 0, true, ErrStackNumberTooBig},
{hexToBytes("ffffffff80"), 0, true, ErrStackNumberTooBig},
{hexToBytes("0000000001"), 0, true, ErrStackNumberTooBig},
{hexToBytes("0000000081"), 0, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffff00"), 0, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffff80"), 0, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff00"), 0, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff80"), 0, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff7f"), 0, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffffff"), 0, true, ErrStackNumberTooBig},
{hexToBytes("0000008000"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000008080"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000009000"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000009080"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffff00"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffff80"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000000001"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000000081"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffff00"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffff80"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff00"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff80"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff7f"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffffff"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
// Non-minimally encoded, but otherwise valid values with
// minimal encoding flag. Should error and return 0.
{hexToBytes("00"), 0, true, ErrStackMinimalData}, // 0
{hexToBytes("0100"), 0, true, ErrStackMinimalData}, // 1
{hexToBytes("7f00"), 0, true, ErrStackMinimalData}, // 127
{hexToBytes("800000"), 0, true, ErrStackMinimalData}, // 128
{hexToBytes("810000"), 0, true, ErrStackMinimalData}, // 129
{hexToBytes("000100"), 0, true, ErrStackMinimalData}, // 256
{hexToBytes("ff7f00"), 0, true, ErrStackMinimalData}, // 32767
{hexToBytes("00800000"), 0, true, ErrStackMinimalData}, // 32768
{hexToBytes("ffff0000"), 0, true, ErrStackMinimalData}, // 65535
{hexToBytes("00000800"), 0, true, ErrStackMinimalData}, // 524288
{hexToBytes("00007000"), 0, true, ErrStackMinimalData}, // 7340032
{hexToBytes("00"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 0
{hexToBytes("0100"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 1
{hexToBytes("7f00"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 127
{hexToBytes("800000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 128
{hexToBytes("810000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 129
{hexToBytes("000100"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 256
{hexToBytes("ff7f00"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 32767
{hexToBytes("00800000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 32768
{hexToBytes("ffff0000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 65535
{hexToBytes("00000800"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 524288
{hexToBytes("00007000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 7340032
{hexToBytes("0009000100"), 0, 5, true, ErrStackMinimalData}, // 16779520
// Non-minimally encoded, but otherwise valid values without
// minimal encoding flag. Should not error and return expected
// integral number.
{hexToBytes("00"), 0, false, nil},
{hexToBytes("0100"), 1, false, nil},
{hexToBytes("7f00"), 127, false, nil},
{hexToBytes("800000"), 128, false, nil},
{hexToBytes("810000"), 129, false, nil},
{hexToBytes("000100"), 256, false, nil},
{hexToBytes("ff7f00"), 32767, false, nil},
{hexToBytes("00800000"), 32768, false, nil},
{hexToBytes("ffff0000"), 65535, false, nil},
{hexToBytes("00000800"), 524288, false, nil},
{hexToBytes("00007000"), 7340032, false, nil},
{hexToBytes("00"), 0, defaultScriptNumLen, false, nil},
{hexToBytes("0100"), 1, defaultScriptNumLen, false, nil},
{hexToBytes("7f00"), 127, defaultScriptNumLen, false, nil},
{hexToBytes("800000"), 128, defaultScriptNumLen, false, nil},
{hexToBytes("810000"), 129, defaultScriptNumLen, false, nil},
{hexToBytes("000100"), 256, defaultScriptNumLen, false, nil},
{hexToBytes("ff7f00"), 32767, defaultScriptNumLen, false, nil},
{hexToBytes("00800000"), 32768, defaultScriptNumLen, false, nil},
{hexToBytes("ffff0000"), 65535, defaultScriptNumLen, false, nil},
{hexToBytes("00000800"), 524288, defaultScriptNumLen, false, nil},
{hexToBytes("00007000"), 7340032, defaultScriptNumLen, false, nil},
{hexToBytes("0009000100"), 16779520, 5, false, nil},
}
for _, test := range tests {
gotNum, err := makeScriptNum(test.serialized, test.minimalEncoding)
gotNum, err := makeScriptNum(test.serialized, test.minimalEncoding,
test.numLen)
if err != test.err {
t.Errorf("makeScriptNum: did not received expected "+
"error for %x - got %v, want %v",

View file

@ -83,7 +83,7 @@ func (s *stack) PopInt() (scriptNum, error) {
return 0, err
}
return makeScriptNum(so, s.verifyMinimalData)
return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen)
}
// PopBool pops the value off the top of the stack, converts it into a bool, and
@ -118,7 +118,7 @@ func (s *stack) PeekInt(idx int32) (scriptNum, error) {
return 0, err
}
return makeScriptNum(so, s.verifyMinimalData)
return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen)
}
// PeekBool returns the Nth item on the stack as a bool without removing it.