From ce22159fb239f7706253a40d9af24ee045296210 Mon Sep 17 00:00:00 2001 From: David Hill Date: Fri, 26 Jun 2015 12:20:05 -0400 Subject: [PATCH] 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 --- txscript/scriptnum.go | 49 ++++++++----- txscript/scriptnum_test.go | 138 ++++++++++++++++++++----------------- txscript/stack.go | 4 +- 3 files changed, 107 insertions(+), 84 deletions(-) diff --git a/txscript/scriptnum.go b/txscript/scriptnum.go index c901fe2c..9d11ea21 100644 --- a/txscript/scriptnum.go +++ b/txscript/scriptnum.go @@ -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 } diff --git a/txscript/scriptnum_test.go b/txscript/scriptnum_test.go index 5bdd4a3a..ee7cab56 100644 --- a/txscript/scriptnum_test.go +++ b/txscript/scriptnum_test.go @@ -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", diff --git a/txscript/stack.go b/txscript/stack.go index 664c000d..4e7f541f 100644 --- a/txscript/stack.go +++ b/txscript/stack.go @@ -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.