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

View file

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

View file

@ -83,7 +83,7 @@ func (s *stack) PopInt() (scriptNum, error) {
return 0, err 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 // 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 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. // PeekBool returns the Nth item on the stack as a bool without removing it.