From c3b35dbd699f4114c013417f5e83eb0df7f96ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Sat, 30 Oct 2021 13:39:08 +0100 Subject: [PATCH 01/14] move max num length to config library --- bscript/interpreter/config.go | 10 ++ bscript/interpreter/operations.go | 6 +- bscript/interpreter/scriptnum.go | 4 - bscript/interpreter/scriptnum_test.go | 126 +++++++++++++------------- bscript/interpreter/stack.go | 12 ++- bscript/interpreter/stack_test.go | 2 +- bscript/interpreter/thread.go | 6 +- 7 files changed, 89 insertions(+), 77 deletions(-) diff --git a/bscript/interpreter/config.go b/bscript/interpreter/config.go index 881b6c77..f07ce57b 100644 --- a/bscript/interpreter/config.go +++ b/bscript/interpreter/config.go @@ -7,6 +7,7 @@ type config interface { MaxStackSize() int MaxScriptSize() int MaxScriptElementSize() int + MaxScriptNumberLength() int MaxPubKeysPerMultiSig() int } @@ -16,6 +17,7 @@ const ( MaxStackSizeBeforeGenesis = 1000 MaxScriptSizeBeforeGenesis = 10000 MaxScriptElementSizeBeforeGenesis = 520 + MaxScriptNumberLengthBeforeGenesis = 4 MaxPubKeysPerMultiSigBeforeGenesis = 20 ) @@ -46,6 +48,14 @@ func (b *beforeGenesisConfig) MaxScriptElementSize() int { return MaxScriptElementSizeBeforeGenesis } +func (a *afterGenesisConfig) MaxScriptNumberLength() int { + return 750 * 1000 // 750 * 1Kb +} + +func (b *beforeGenesisConfig) MaxScriptNumberLength() int { + return MaxScriptNumberLengthBeforeGenesis +} + func (a *afterGenesisConfig) MaxOps() int { return math.MaxInt32 } diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index fb19d90d..07088d81 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -1008,7 +1008,7 @@ func opcodeNum2bin(op *ParsedOp, t *thread) error { size := int(n.Int32()) if size > t.cfg.MaxScriptElementSize() { - return errs.NewError(errs.ErrNumberTooBig, "n is larger than the max of %d", defaultScriptNumLen) + return errs.NewError(errs.ErrNumberTooBig, "n is larger than the max of %d", t.cfg.MaxScriptElementSize()) } // encode a as a script num so that we we take the bytes it @@ -1058,8 +1058,8 @@ func opcodeBin2num(op *ParsedOp, t *thread) error { if err != nil { return err } - if len(n.Bytes()) > defaultScriptNumLen { - return errs.NewError(errs.ErrNumberTooBig, "script numbers are limited to %d bytes", defaultScriptNumLen) + if len(n.Bytes()) > t.cfg.MaxScriptNumberLength() { + return errs.NewError(errs.ErrNumberTooBig, "script numbers are limited to %d bytes", t.cfg.MaxScriptNumberLength()) } t.dstack.PushInt(n) diff --git a/bscript/interpreter/scriptnum.go b/bscript/interpreter/scriptnum.go index f9c19b25..8cbc59e8 100644 --- a/bscript/interpreter/scriptnum.go +++ b/bscript/interpreter/scriptnum.go @@ -11,10 +11,6 @@ import ( const ( maxInt32 = 1<<31 - 1 minInt32 = -1 << 31 - - // 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 diff --git a/bscript/interpreter/scriptnum_test.go b/bscript/interpreter/scriptnum_test.go index ffff91ab..2e17d120 100644 --- a/bscript/interpreter/scriptnum_test.go +++ b/bscript/interpreter/scriptnum_test.go @@ -109,35 +109,35 @@ func TestMakeScriptNum(t *testing.T) { err error }{ // Minimal encoding must reject negative 0. - {hexToBytes("80"), 0, defaultScriptNumLen, true, errMinimalData}, + {hexToBytes("80"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // Minimally encoded valid values with minimal encoding flag. // Should not error and return expected integral number. - {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}, + {nil, 0, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("01"), 1, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("81"), -1, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("7f"), 127, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("ff"), -127, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("8000"), 128, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("8080"), -128, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("8100"), 129, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("8180"), -129, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("0001"), 256, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("0081"), -256, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("ff7f"), 32767, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("ffff"), -32767, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("008000"), 32768, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("008080"), -32768, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("ffff00"), 65535, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("ffff80"), -65535, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("000008"), 524288, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("000088"), -524288, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("000070"), 7340032, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("0000f0"), -7340032, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("00008000"), 8388608, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("00008080"), -8388608, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("ffffff7f"), 2147483647, MaxScriptNumberLengthBeforeGenesis, true, nil}, + {hexToBytes("ffffffff"), -2147483647, MaxScriptNumberLengthBeforeGenesis, true, nil}, {hexToBytes("ffffffff7f"), 549755813887, 5, true, nil}, {hexToBytes("ffffffffff"), -549755813887, 5, true, nil}, {hexToBytes("ffffffffffffff7f"), 9223372036854775807, 8, true, nil}, @@ -150,50 +150,50 @@ func TestMakeScriptNum(t *testing.T) { // 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, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000008080"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000009000"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000009080"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000000001"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000000081"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffff7f"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffffff"), 0, defaultScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000008000"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("0000008080"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("0000009000"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("0000009080"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("ffffffff00"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("ffffffff80"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("0000000001"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("0000000081"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("ffffffffffff00"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("ffffffffffff80"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("ffffffffffffff00"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("ffffffffffffff80"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("ffffffffffffff7f"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, + {hexToBytes("ffffffffffffffff"), 0, MaxScriptNumberLengthBeforeGenesis, true, errNumTooBig}, // Non-minimally encoded, but otherwise valid values with // minimal encoding flag. Should error and return 0. - {hexToBytes("00"), 0, defaultScriptNumLen, true, errMinimalData}, // 0 - {hexToBytes("0100"), 0, defaultScriptNumLen, true, errMinimalData}, // 1 - {hexToBytes("7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 127 - {hexToBytes("800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 128 - {hexToBytes("810000"), 0, defaultScriptNumLen, true, errMinimalData}, // 129 - {hexToBytes("000100"), 0, defaultScriptNumLen, true, errMinimalData}, // 256 - {hexToBytes("ff7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 32767 - {hexToBytes("00800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 32768 - {hexToBytes("ffff0000"), 0, defaultScriptNumLen, true, errMinimalData}, // 65535 - {hexToBytes("00000800"), 0, defaultScriptNumLen, true, errMinimalData}, // 524288 - {hexToBytes("00007000"), 0, defaultScriptNumLen, true, errMinimalData}, // 7340032 - {hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520 + {hexToBytes("00"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 0 + {hexToBytes("0100"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 1 + {hexToBytes("7f00"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 127 + {hexToBytes("800000"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 128 + {hexToBytes("810000"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 129 + {hexToBytes("000100"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 256 + {hexToBytes("ff7f00"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 32767 + {hexToBytes("00800000"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 32768 + {hexToBytes("ffff0000"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 65535 + {hexToBytes("00000800"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 524288 + {hexToBytes("00007000"), 0, MaxScriptNumberLengthBeforeGenesis, true, errMinimalData}, // 7340032 + {hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520 // Non-minimally encoded, but otherwise valid values without // minimal encoding flag. Should not error and return expected // integral number. - {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("00"), 0, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("0100"), 1, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("7f00"), 127, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("800000"), 128, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("810000"), 129, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("000100"), 256, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("ff7f00"), 32767, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("00800000"), 32768, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("ffff0000"), 65535, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("00000800"), 524288, MaxScriptNumberLengthBeforeGenesis, false, nil}, + {hexToBytes("00007000"), 7340032, MaxScriptNumberLengthBeforeGenesis, false, nil}, {hexToBytes("0009000100"), 16779520, 5, false, nil}, } diff --git a/bscript/interpreter/stack.go b/bscript/interpreter/stack.go index 3586a43e..7bb3bb5e 100644 --- a/bscript/interpreter/stack.go +++ b/bscript/interpreter/stack.go @@ -38,9 +38,17 @@ func fromBool(v bool) []byte { // stack. type stack struct { stk [][]byte + maxNumLength int verifyMinimalData bool } +func newStack(cfg config, verifyMinimalData bool) stack { + return stack{ + maxNumLength: cfg.MaxScriptNumberLength(), + verifyMinimalData: verifyMinimalData, + } +} + // Depth returns the number of items on the stack. func (s *stack) Depth() int32 { return int32(len(s.stk)) @@ -87,7 +95,7 @@ func (s *stack) PopInt() (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen) + return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength) } // PopBool pops the value off the top of the stack, converts it into a bool, and @@ -122,7 +130,7 @@ func (s *stack) PeekInt(idx int32) (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen) + return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength) } // PeekBool returns the Nth item on the stack as a bool without removing it. diff --git a/bscript/interpreter/stack_test.go b/bscript/interpreter/stack_test.go index c1e7ba59..58253408 100644 --- a/bscript/interpreter/stack_test.go +++ b/bscript/interpreter/stack_test.go @@ -905,7 +905,7 @@ func TestStack(t *testing.T) { for _, test := range tests { // Setup the initial stack state and perform the test operation. - s := stack{} + s := newStack(&beforeGenesisConfig{}, false) for i := range test.before { s.PushByteArray(test.before[i]) } diff --git a/bscript/interpreter/thread.go b/bscript/interpreter/thread.go index 20c78189..a3876646 100644 --- a/bscript/interpreter/thread.go +++ b/bscript/interpreter/thread.go @@ -429,10 +429,8 @@ func (t *thread) apply(opts *execOpts) error { t.bip16 = true } - if t.hasFlag(scriptflag.VerifyMinimalData) { - t.dstack.verifyMinimalData = true - t.astack.verifyMinimalData = true - } + t.dstack = newStack(t.cfg, t.hasFlag(scriptflag.VerifyMinimalData)) + t.astack = newStack(t.cfg, t.hasFlag(scriptflag.VerifyMinimalData)) if t.tx != nil { t.tx.InputIdx(t.inputIdx).PreviousTxScript = t.prevOutput.LockingScript From 69eeb067c9ade651732861f81c6ac62dc002facd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 9 Nov 2021 14:06:57 +0000 Subject: [PATCH 02/14] getting there --- bscript/interpreter/config.go | 9 + bscript/interpreter/data/script_tests.json | 4 +- bscript/interpreter/number.go | 187 +++++++++++++++++++++ bscript/interpreter/operations.go | 133 ++++++++------- bscript/interpreter/reference_test.go | 3 + bscript/interpreter/scriptnum.go | 61 ++++++- bscript/interpreter/scriptnum_test.go | 2 +- bscript/interpreter/stack.go | 19 ++- bscript/interpreter/thread.go | 15 ++ examples/test/test.go | 37 ++++ 10 files changed, 399 insertions(+), 71 deletions(-) create mode 100644 bscript/interpreter/number.go create mode 100644 examples/test/test.go diff --git a/bscript/interpreter/config.go b/bscript/interpreter/config.go index f07ce57b..1b12189b 100644 --- a/bscript/interpreter/config.go +++ b/bscript/interpreter/config.go @@ -3,6 +3,7 @@ package interpreter import "math" type config interface { + AfterGenesis() bool MaxOps() int MaxStackSize() int MaxScriptSize() int @@ -24,6 +25,14 @@ const ( type beforeGenesisConfig struct{} type afterGenesisConfig struct{} +func (a *afterGenesisConfig) AfterGenesis() bool { + return true +} + +func (b *beforeGenesisConfig) AfterGenesis() bool { + return false +} + func (a *afterGenesisConfig) MaxStackSize() int { return math.MaxInt32 } diff --git a/bscript/interpreter/data/script_tests.json b/bscript/interpreter/data/script_tests.json index 2ec54bab..6284ece8 100644 --- a/bscript/interpreter/data/script_tests.json +++ b/bscript/interpreter/data/script_tests.json @@ -1167,8 +1167,8 @@ ["-1 -1","DIV 1 EQUAL","P2SH,STRICTENC","OK"], ["28 21","DIV 1 EQUAL","P2SH,STRICTENC","OK","Round towards zero"], ["12 -7","DIV -1 EQUAL","P2SH,STRICTENC","OK","Round towards zero"], -["-32 29","DIV -1 EQUAL","P2SH,STRICTENC","OK","Round towards zero"], -["-42 -27","DIV 1 EQUAL","P2SH,STRICTENC","OK","Round towards zero"], +["-32 29","DIV -1 EQUAL","P2SH,STRICTENC","OK","Round towards zero 2"], +["-42 -27","DIV 1 EQUAL","P2SH,STRICTENC","OK","Round towards zero 3"], ["0 123","DIV 0 EQUAL","P2SH,STRICTENC","OK"], ["511 0","DIV","P2SH,STRICTENC","DIV_BY_ZERO","DIV, divide by zero"], ["1 1","DIV DEPTH 1 EQUAL","P2SH,STRICTENC","OK","Stack depth correct"], diff --git a/bscript/interpreter/number.go b/bscript/interpreter/number.go new file mode 100644 index 00000000..4a4da7d0 --- /dev/null +++ b/bscript/interpreter/number.go @@ -0,0 +1,187 @@ +package interpreter + +import ( + "math" + "math/big" + + "github.com/libsv/go-bt/v2/bscript/interpreter/errs" +) + +type Number struct { + val *big.Int + afterGenesis bool +} + +var zero = big.NewInt(0) +var one = big.NewInt(1) + +func NewNumber(bb []byte, scriptNumLen int, requireMinimal, afterGenesis bool) (*Number, error) { + if len(bb) > scriptNumLen { + return nil, errs.NewError( + errs.ErrNumberTooBig, + "numeric value encoded as %x is %d bytes which exceeds the max allowed of %d", + bb, len(bb), scriptNumLen, + ) + } + if requireMinimal { + if err := checkMinimalDataEncoding(bb); err != nil { + return nil, err + } + } + if len(bb) == 0 { + return &Number{ + afterGenesis: afterGenesis, + val: big.NewInt(0), + }, nil + } + v := new(big.Int) + for i, b := range bb { + v.Or(v, new(big.Int).Lsh(new(big.Int).SetBytes([]byte{b}), uint(8*i))) + } + + if bb[len(bb)-1]&0x80 != 0 { + shift := big.NewInt(int64(0x80)) + shift.Not(shift.Lsh(shift, uint(8*(len(bb)-1)))) + v.And(v, shift).Neg(v) + } + return &Number{ + val: v, + afterGenesis: afterGenesis, + }, nil +} + +func (n *Number) Add(o *Number) *Number { + result := n.val.Add(n.val, o.val) + *n.val = *result + return n +} + +func (n *Number) Sub(o *Number) *Number { + result := n.val.Sub(n.val, o.val) + *n.val = *result + return n +} + +func (n *Number) Mul(o *Number) *Number { + result := n.val.Mul(n.val, o.val) + *n.val = *result + return n +} + +func (n *Number) Div(o *Number) *Number { + result := n.val.Quo(n.val, o.val) + *n.val = *result + return n +} + +func (n *Number) Mod(o *Number) *Number { + result := n.val.Rem(n.val, o.val) + *n.val = *result + return n +} + +func (n *Number) LessThanInt(i int64) bool { + return n.LessThan(&Number{val: big.NewInt(i)}) +} + +func (n *Number) LessThan(o *Number) bool { + return n.val.Cmp(o.val) == -1 +} + +func (n *Number) LessThanOrEqual(o *Number) bool { + return n.val.Cmp(o.val) < 1 +} + +func (n *Number) GreaterThanInt(i int64) bool { + return n.GreaterThan(&Number{val: big.NewInt(i)}) +} + +func (n *Number) GreaterThan(o *Number) bool { + return n.val.Cmp(o.val) == 1 +} + +func (n *Number) GreaterThanOrEqual(o *Number) bool { + return n.val.Cmp(o.val) > -1 +} + +func (n *Number) EqualInt(i int64) bool { + return n.Equal(&Number{val: big.NewInt(i)}) +} + +func (n *Number) Equal(o *Number) bool { + return n.val.Cmp(o.val) == 0 +} + +func (n *Number) IsZero() bool { + return n.val.Cmp(zero) == 0 +} + +func (n *Number) Incr() *Number { + result := n.val.Add(n.val, one) + *n.val = *result + return n +} + +func (n *Number) Decr() *Number { + result := n.val.Sub(n.val, one) + *n.val = *result + return n +} + +func (n *Number) Neg() *Number { + result := n.val.Neg(n.val) + *n.val = *result + return n +} + +func (n *Number) Abs() *Number { + result := n.val.Abs(n.val) + *n.val = *result + return n +} + +func (n *Number) Bytes() []byte { + if n.val.Int64() == 0 { + return []byte{} + } + isNegative := n.val.Cmp(big.NewInt(0)) == -1 + if isNegative { + n.val.Neg(n.val) + } + + var bb []byte + if !n.afterGenesis { + v := n.val.Int64() + if v > math.MaxInt32 { + bb = big.NewInt(int64(math.MaxInt32)).Bytes() + } else if v < math.MinInt32 { + bb = big.NewInt(int64(math.MinInt32)).Bytes() + } + } + if bb == nil { + bb = n.val.Bytes() + } + + //tmp := make([]byte, len(bb), len(bb)+1) + //for i := len(bb) - 1; i >= 0; i-- { + // tmp[i] = bb[i] + //} + tmp := make([]byte, 0, len(bb)+1) + cpy := new(big.Int).SetBytes(n.val.Bytes()) + for cpy.Cmp(zero) == 1 { + tmp = append(tmp, byte(cpy.Int64()&0xff)) + cpy.Rsh(cpy, 8) + } + + if tmp[len(tmp)-1]&0x80 != 0 { + extraByte := byte(0x00) + if isNegative { + extraByte = 0x80 + } + tmp = append(tmp, extraByte) + } else if isNegative { + tmp[len(tmp)-1] |= 0x80 + } + + return tmp +} diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index 07088d81..56eda7c1 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -638,7 +638,7 @@ func opcodeCheckLockTimeVerify(op *ParsedOp, t *thread) error { if err != nil { return err } - lockTime, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5) + lockTime, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5, t.afterGenesis) if err != nil { return err } @@ -708,7 +708,7 @@ func opcodeCheckSequenceVerify(op *ParsedOp, t *thread) error { if err != nil { return err } - stackSequence, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5) + stackSequence, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5, t.afterGenesis) if err != nil { return err } @@ -996,7 +996,8 @@ func opcodeSplit(op *ParsedOp, t *thread) error { // // Stack transformation: a b bscript.OpNUM2BIN -> x func opcodeNum2bin(op *ParsedOp, t *thread) error { - n, err := t.dstack.PopInt() + //n, err := t.dstack.PopInt() + n, err := t.dstack.PopNumber() if err != nil { return err } @@ -1006,23 +1007,24 @@ func opcodeNum2bin(op *ParsedOp, t *thread) error { return err } - size := int(n.Int32()) - if size > t.cfg.MaxScriptElementSize() { + //size := int(n.Int32()) + if n.GreaterThanInt(int64(t.cfg.MaxScriptElementSize())) { + //if size > t.cfg.MaxScriptElementSize() { return errs.NewError(errs.ErrNumberTooBig, "n is larger than the max of %d", t.cfg.MaxScriptElementSize()) } // encode a as a script num so that we we take the bytes it // will be minimally encoded. - sn, err := makeScriptNum(a, false, len(a)) + sn, err := makeScriptNum(a, false, len(a), t.afterGenesis) if err != nil { return err } b := sn.Bytes() - if len(b) > size { + if n.LessThanInt(int64(len(b))) { return errs.NewError(errs.ErrNumberTooSmall, "cannot fit it into n sized array") } - if len(b) == size { + if n.EqualInt(int64(len(b))) { t.dstack.PushByteArray(b) return nil } @@ -1033,7 +1035,8 @@ func opcodeNum2bin(op *ParsedOp, t *thread) error { b[len(b)-1] &= 0x7f } - for len(b) < size-1 { + for n.GreaterThanInt(int64(len(b) + 1)) { + //for len(b) < size-1 { b = append(b, 0x00) } @@ -1054,15 +1057,19 @@ func opcodeBin2num(op *ParsedOp, t *thread) error { return err } - n, err := makeScriptNum(a, false, len(a)) - if err != nil { - return err - } - if len(n.Bytes()) > t.cfg.MaxScriptNumberLength() { + //n, err := makeScriptNum(a, false, len(a)) + //if err != nil { + // return err + //} + + //t.dstack.PushInt(n) + + b := minimallyEncode(a) + if len(b) > t.cfg.MaxScriptNumberLength() { return errs.NewError(errs.ErrNumberTooBig, "script numbers are limited to %d bytes", t.cfg.MaxScriptNumberLength()) } - t.dstack.PushInt(n) + t.dstack.PushByteArray(b) return nil } @@ -1216,12 +1223,12 @@ func opcodeEqualVerify(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1 x2+1] func opcode1Add(op *ParsedOp, t *thread) error { - m, err := t.dstack.PopInt() + m, err := t.dstack.PopNumber() if err != nil { return err } - t.dstack.PushInt(m + 1) + t.dstack.PushNumber(m.Incr()) return nil } @@ -1230,12 +1237,12 @@ func opcode1Add(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1 x2-1] func opcode1Sub(op *ParsedOp, t *thread) error { - m, err := t.dstack.PopInt() + m, err := t.dstack.PopNumber() if err != nil { return err } - t.dstack.PushInt(m - 1) + t.dstack.PushNumber(m.Decr()) return nil } @@ -1244,12 +1251,12 @@ func opcode1Sub(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1 -x2] func opcodeNegate(op *ParsedOp, t *thread) error { - m, err := t.dstack.PopInt() + m, err := t.dstack.PopNumber() if err != nil { return err } - t.dstack.PushInt(-m) + t.dstack.PushNumber(m.Neg()) return nil } @@ -1258,16 +1265,12 @@ func opcodeNegate(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1 abs(x2)] func opcodeAbs(op *ParsedOp, t *thread) error { - m, err := t.dstack.PopInt() + m, err := t.dstack.PopNumber() if err != nil { return err } - if m < 0 { - m = -m - } - - t.dstack.PushInt(m) + t.dstack.PushNumber(m.Abs()) return nil } @@ -1323,17 +1326,17 @@ func opcode0NotEqual(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1+x2] func opcodeAdd(op *ParsedOp, t *thread) error { - v0, err := t.dstack.PopInt() + v0, err := t.dstack.PopNumber() if err != nil { return err } - v1, err := t.dstack.PopInt() + v1, err := t.dstack.PopNumber() if err != nil { return err } - t.dstack.PushInt(v0 + v1) + t.dstack.PushNumber(v0.Add(v1)) return nil } @@ -1343,17 +1346,17 @@ func opcodeAdd(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1-x2] func opcodeSub(op *ParsedOp, t *thread) error { - v0, err := t.dstack.PopInt() + v0, err := t.dstack.PopNumber() if err != nil { return err } - v1, err := t.dstack.PopInt() + v1, err := t.dstack.PopNumber() if err != nil { return err } - t.dstack.PushInt(v1 - v0) + t.dstack.PushNumber(v1.Sub(v0)) return nil } @@ -1361,19 +1364,17 @@ func opcodeSub(op *ParsedOp, t *thread) error { // them with the result of subtracting the top entry from the second-to-top // entry. func opcodeMul(op *ParsedOp, t *thread) error { - n1, err := t.dstack.PopInt() + n1, err := t.dstack.PopNumber() if err != nil { return err } - n2, err := t.dstack.PopInt() + n2, err := t.dstack.PopNumber() if err != nil { return err } - n3 := n1.Int64() * n2.Int64() - - t.dstack.PushInt(scriptNum(n3)) + t.dstack.PushNumber(n1.Mul(n2)) return nil } @@ -1382,21 +1383,21 @@ func opcodeMul(op *ParsedOp, t *thread) error { // // Stack transformation: a b bscript.OpDIV -> out func opcodeDiv(op *ParsedOp, t *thread) error { - b, err := t.dstack.PopInt() + b, err := t.dstack.PopNumber() if err != nil { return err } - a, err := t.dstack.PopInt() + a, err := t.dstack.PopNumber() if err != nil { return err } - if b == 0 { + if b.IsZero() { return errs.NewError(errs.ErrDivideByZero, "divide by zero") } - t.dstack.PushInt(a / b) + t.dstack.PushNumber(a.Div(b)) return nil } @@ -1405,21 +1406,21 @@ func opcodeDiv(op *ParsedOp, t *thread) error { // // Stack transformation: a b bscript.OpMOD -> out func opcodeMod(op *ParsedOp, t *thread) error { - b, err := t.dstack.PopInt() + b, err := t.dstack.PopNumber() if err != nil { return err } - a, err := t.dstack.PopInt() + a, err := t.dstack.PopNumber() if err != nil { return err } - if b == 0 { + if b.IsZero() { return errs.NewError(errs.ErrDivideByZero, "mod by zero") } - t.dstack.PushInt(a % b) + t.dstack.PushNumber(a.Mod(b)) return nil } @@ -1533,18 +1534,20 @@ func opcodeBoolOr(op *ParsedOp, t *thread) error { // Stack transformation (x1==x2): [... 5 5] -> [... 1] // Stack transformation (x1!=x2): [... 5 7] -> [... 0] func opcodeNumEqual(op *ParsedOp, t *thread) error { - v0, err := t.dstack.PopInt() + //v0, err := t.dstack.PopInt() + v0, err := t.dstack.PopNumber() if err != nil { return err } - v1, err := t.dstack.PopInt() + //v1, err := t.dstack.PopInt() + v1, err := t.dstack.PopNumber() if err != nil { return err } var n scriptNum - if v0 == v1 { + if v0.Equal(v1) { n = 1 } @@ -1574,18 +1577,20 @@ func opcodeNumEqualVerify(op *ParsedOp, t *thread) error { // Stack transformation (x1==x2): [... 5 5] -> [... 0] // Stack transformation (x1!=x2): [... 5 7] -> [... 1] func opcodeNumNotEqual(op *ParsedOp, t *thread) error { - v0, err := t.dstack.PopInt() + //v0, err := t.dstack.PopInt() + v0, err := t.dstack.PopNumber() if err != nil { return err } - v1, err := t.dstack.PopInt() + //v1, err := t.dstack.PopInt() + v1, err := t.dstack.PopNumber() if err != nil { return err } var n scriptNum - if v0 != v1 { + if !v0.Equal(v1) { n = 1 } @@ -1599,18 +1604,18 @@ func opcodeNumNotEqual(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... bool] func opcodeLessThan(op *ParsedOp, t *thread) error { - v0, err := t.dstack.PopInt() + v0, err := t.dstack.PopNumber() if err != nil { return err } - v1, err := t.dstack.PopInt() + v1, err := t.dstack.PopNumber() if err != nil { return err } var n scriptNum - if v1 < v0 { + if v1.LessThan(v0) { n = 1 } @@ -1624,18 +1629,18 @@ func opcodeLessThan(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... bool] func opcodeGreaterThan(op *ParsedOp, t *thread) error { - v0, err := t.dstack.PopInt() + v0, err := t.dstack.PopNumber() if err != nil { return err } - v1, err := t.dstack.PopInt() + v1, err := t.dstack.PopNumber() if err != nil { return err } var n scriptNum - if v1 > v0 { + if v1.GreaterThan(v0) { n = 1 } @@ -1649,18 +1654,18 @@ func opcodeGreaterThan(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... bool] func opcodeLessThanOrEqual(op *ParsedOp, t *thread) error { - v0, err := t.dstack.PopInt() + v0, err := t.dstack.PopNumber() if err != nil { return err } - v1, err := t.dstack.PopInt() + v1, err := t.dstack.PopNumber() if err != nil { return err } var n scriptNum - if v1 <= v0 { + if v1.LessThanOrEqual(v0) { n = 1 } @@ -1674,18 +1679,18 @@ func opcodeLessThanOrEqual(op *ParsedOp, t *thread) error { // // Stack transformation: [... x1 x2] -> [... bool] func opcodeGreaterThanOrEqual(op *ParsedOp, t *thread) error { - v0, err := t.dstack.PopInt() + v0, err := t.dstack.PopNumber() if err != nil { return err } - v1, err := t.dstack.PopInt() + v1, err := t.dstack.PopNumber() if err != nil { return err } var n scriptNum - if v1 >= v0 { + if v1.GreaterThanOrEqual(v0) { n = 1 } diff --git a/bscript/interpreter/reference_test.go b/bscript/interpreter/reference_test.go index b4a206ac..601b0afa 100644 --- a/bscript/interpreter/reference_test.go +++ b/bscript/interpreter/reference_test.go @@ -438,6 +438,9 @@ func TestScripts(t *testing.T) { // Ensure there were no errors when the expected result is OK. if resultStr == "OK" { + if name == "test (Round towards zero 2)" { + fmt.Println("found ls", scriptPubKey.String(), "uls", scriptSig.String()) + } if err != nil { t.Errorf("%s failed to execute: %v", name, err) } diff --git a/bscript/interpreter/scriptnum.go b/bscript/interpreter/scriptnum.go index 8cbc59e8..73a32ca4 100644 --- a/bscript/interpreter/scriptnum.go +++ b/bscript/interpreter/scriptnum.go @@ -11,8 +11,15 @@ import ( const ( maxInt32 = 1<<31 - 1 minInt32 = -1 << 31 + + maxInt64 = 1<<63 - 1 + minInt64 = -1 << 63 ) +type scriptNumber interface { + Bytes() []byte +} + // scriptNum represents a numeric value used in the scripting engine with // special handling to deal with the subtle semantics required by consensus. // @@ -151,7 +158,13 @@ func (n scriptNum) Int32() int32 { } func (n scriptNum) Int64() int64 { - return int64(n.Int32()) + if n > maxInt64 { + return maxInt64 + } + if n < minInt64 { + return minInt64 + } + return int64(n) } // makeScriptNum interprets the passed serialised bytes as an encoded integer @@ -180,7 +193,7 @@ func (n scriptNum) Int64() int64 { // overflows. // // See the Bytes function documentation for example encodings. -func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (scriptNum, error) { +func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int, bigInt bool) (scriptNum, error) { // Interpreting data requires that it is not larger than // the passed scriptNumLen value. if len(v) > scriptNumLen { @@ -203,6 +216,16 @@ func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (scriptNum, return 0, nil } + //if bigInt { + // tmp := make([]byte, len(v)) + // copy(tmp, v) + // for i := 0; i < len(tmp)/2; i++ { + // tmp[i], tmp[len(tmp)-i-1] = tmp[len(tmp)-i-1], tmp[i] + // } + // b := new(big.Int).SetBytes(tmp) + // return scriptNum(b.Int64()), nil + //} + // Decode from little endian. var result int64 for i, val := range v { @@ -222,3 +245,37 @@ func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (scriptNum, return scriptNum(result), nil } + +func minimallyEncode(data []byte) []byte { + if len(data) == 0 { + return data + } + + last := data[len(data)-1] + if last&0x7f != 0 { + return data + } + + if len(data) == 1 { + return []byte{} + } + + if data[len(data)-2]&0x80 != 0 { + return data + } + + for i := len(data) - 1; i > 0; i-- { + if data[i-1] != 0 { + if data[i-1]&0x80 != 0 { + data[i] = last + i++ + } else { + data[i-1] |= last + } + + return data[:i] + } + } + + return []byte{} +} diff --git a/bscript/interpreter/scriptnum_test.go b/bscript/interpreter/scriptnum_test.go index 2e17d120..fe90c6e6 100644 --- a/bscript/interpreter/scriptnum_test.go +++ b/bscript/interpreter/scriptnum_test.go @@ -201,7 +201,7 @@ func TestMakeScriptNum(t *testing.T) { // Ensure the error code is of the expected type and the error // code matches the value specified in the test instance. gotNum, err := makeScriptNum(test.serialised, test.minimalEncoding, - test.numLen) + test.numLen, false) if e := tstCheckScriptError(err, test.err); e != nil { t.Errorf("makeScriptNum(%#x): %v", test.serialised, e) continue diff --git a/bscript/interpreter/stack.go b/bscript/interpreter/stack.go index 7bb3bb5e..27797ebd 100644 --- a/bscript/interpreter/stack.go +++ b/bscript/interpreter/stack.go @@ -39,12 +39,14 @@ func fromBool(v bool) []byte { type stack struct { stk [][]byte maxNumLength int + afterGenesis bool verifyMinimalData bool } func newStack(cfg config, verifyMinimalData bool) stack { return stack{ maxNumLength: cfg.MaxScriptNumberLength(), + afterGenesis: cfg.AfterGenesis(), verifyMinimalData: verifyMinimalData, } } @@ -69,6 +71,10 @@ func (s *stack) PushInt(val scriptNum) { s.PushByteArray(val.Bytes()) } +func (s *stack) PushNumber(n *Number) { + s.PushByteArray(n.Bytes()) +} + // PushBool converts the provided boolean to a suitable byte array then pushes // it onto the top of the stack. // @@ -95,7 +101,16 @@ func (s *stack) PopInt() (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength) + return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength, s.afterGenesis) +} + +func (s *stack) PopNumber() (*Number, error) { + so, err := s.PopByteArray() + if err != nil { + return nil, err + } + + return NewNumber(so, s.maxNumLength, s.verifyMinimalData, s.afterGenesis) } // PopBool pops the value off the top of the stack, converts it into a bool, and @@ -130,7 +145,7 @@ func (s *stack) PeekInt(idx int32) (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength) + return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength, s.afterGenesis) } // PeekBool returns the Nth item on the stack as a bool without removing it. diff --git a/bscript/interpreter/thread.go b/bscript/interpreter/thread.go index a3876646..014197dc 100644 --- a/bscript/interpreter/thread.go +++ b/bscript/interpreter/thread.go @@ -1,6 +1,8 @@ package interpreter import ( + "encoding/hex" + "fmt" "math/big" "github.com/libsv/go-bk/bec" @@ -251,6 +253,19 @@ func (t *thread) Step() (bool, error) { opcode := t.scripts[t.scriptIdx][t.scriptOff] t.scriptOff++ + //if 720 <= t.numOps && t.numOps <= 725 { + // fmt.Println(t.numOps, opcode.Name()) + //} + + if len(t.dstack.stk) > 0 { + fmt.Println() + fmt.Println("---", "OPERATION", t.numOps, "---") + //for _, s := range t.dstack.stk { + // fmt.Println(hex.EncodeToString(s)) + //} + fmt.Println(hex.EncodeToString(t.dstack.stk[len(t.dstack.stk)-1])) + } + // Execute the opcode while taking into account several things such as // disabled opcodes, illegal opcodes, maximum allowed operations per // script, maximum script element sizes, and conditionals. diff --git a/examples/test/test.go b/examples/test/test.go new file mode 100644 index 00000000..8d12dec8 --- /dev/null +++ b/examples/test/test.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + + "github.com/libsv/go-bt/v2/bscript" + "github.com/libsv/go-bt/v2/bscript/interpreter" + "github.com/libsv/go-bt/v2/bscript/interpreter/scriptflag" +) + +func main() { + e := interpreter.NewEngine() + + ls, err := bscript.NewFromHexString("964f87") + //ls, err := bscript.NewFromASM("OP_2 OP_2 OP_SUB OP_4 OP_ADD OP_EQUAL") + if err != nil { + fmt.Println(err) + } + uls, _ := bscript.NewFromHexString("01a0011d") + //uls, err := bscript.NewFromASM("OP_2") + if err != nil { + fmt.Println(err) + } + + asm, _ := uls.ToASM() + asm2, _ := ls.ToASM() + fmt.Println(asm, asm2) + + if err := e.Execute( + interpreter.WithScripts( + ls, uls, + ), + interpreter.WithFlags(scriptflag.VerifyStrictEncoding), + ); err != nil { + fmt.Println(err) + } +} From 794f5ef5ac0ae035f8670c711c336a74ae509bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Sun, 14 Nov 2021 19:49:42 +0000 Subject: [PATCH 03/14] its working lol --- bscript/interpreter/operations.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index b22d1cc2..7a73c71a 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -1007,7 +1007,6 @@ func opcodeNum2bin(op *ParsedOpcode, t *thread) error { return err } - //size := int(n.Int32()) if n.GreaterThanInt(int64(t.cfg.MaxScriptElementSize())) { //if size > t.cfg.MaxScriptElementSize() { return errs.NewError(errs.ErrNumberTooBig, "n is larger than the max of %d", t.cfg.MaxScriptElementSize()) @@ -1015,7 +1014,7 @@ func opcodeNum2bin(op *ParsedOpcode, t *thread) error { // encode a as a script num so that we we take the bytes it // will be minimally encoded. - sn, err := makeScriptNum(a, false, len(a), t.afterGenesis) + sn, err := NewNumber(a, len(a), false, t.afterGenesis) if err != nil { return err } @@ -1031,12 +1030,11 @@ func opcodeNum2bin(op *ParsedOpcode, t *thread) error { signbit := byte(0x00) if len(b) > 0 { - signbit = b[0] & 0x80 + signbit = b[len(b)-1] & 0x80 b[len(b)-1] &= 0x7f } for n.GreaterThanInt(int64(len(b) + 1)) { - //for len(b) < size-1 { b = append(b, 0x00) } From a4ff6c21374d784181edfdec06ab9807fc15f1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Sun, 14 Nov 2021 19:54:28 +0000 Subject: [PATCH 04/14] cleaned up pr --- bscript/interpreter/operations.go | 15 ++--------- bscript/interpreter/reference_test.go | 3 --- bscript/interpreter/scriptnum.go | 12 +-------- bscript/interpreter/scriptnum_test.go | 2 +- bscript/interpreter/stack.go | 4 +-- examples/test/test.go | 37 --------------------------- 6 files changed, 6 insertions(+), 67 deletions(-) delete mode 100644 examples/test/test.go diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index 7a73c71a..084aede4 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -638,7 +638,7 @@ func opcodeCheckLockTimeVerify(op *ParsedOpcode, t *thread) error { if err != nil { return err } - lockTime, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5, t.afterGenesis) + lockTime, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5) if err != nil { return err } @@ -708,7 +708,7 @@ func opcodeCheckSequenceVerify(op *ParsedOpcode, t *thread) error { if err != nil { return err } - stackSequence, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5, t.afterGenesis) + stackSequence, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5) if err != nil { return err } @@ -1055,13 +1055,6 @@ func opcodeBin2num(op *ParsedOpcode, t *thread) error { return err } - //n, err := makeScriptNum(a, false, len(a)) - //if err != nil { - // return err - //} - - //t.dstack.PushInt(n) - b := minimallyEncode(a) if len(b) > t.cfg.MaxScriptNumberLength() { return errs.NewError(errs.ErrNumberTooBig, "script numbers are limited to %d bytes", t.cfg.MaxScriptNumberLength()) @@ -1532,13 +1525,11 @@ func opcodeBoolOr(op *ParsedOpcode, t *thread) error { // Stack transformation (x1==x2): [... 5 5] -> [... 1] // Stack transformation (x1!=x2): [... 5 7] -> [... 0] func opcodeNumEqual(op *ParsedOpcode, t *thread) error { - //v0, err := t.dstack.PopInt() v0, err := t.dstack.PopNumber() if err != nil { return err } - //v1, err := t.dstack.PopInt() v1, err := t.dstack.PopNumber() if err != nil { return err @@ -1575,13 +1566,11 @@ func opcodeNumEqualVerify(op *ParsedOpcode, t *thread) error { // Stack transformation (x1==x2): [... 5 5] -> [... 0] // Stack transformation (x1!=x2): [... 5 7] -> [... 1] func opcodeNumNotEqual(op *ParsedOpcode, t *thread) error { - //v0, err := t.dstack.PopInt() v0, err := t.dstack.PopNumber() if err != nil { return err } - //v1, err := t.dstack.PopInt() v1, err := t.dstack.PopNumber() if err != nil { return err diff --git a/bscript/interpreter/reference_test.go b/bscript/interpreter/reference_test.go index 601b0afa..b4a206ac 100644 --- a/bscript/interpreter/reference_test.go +++ b/bscript/interpreter/reference_test.go @@ -438,9 +438,6 @@ func TestScripts(t *testing.T) { // Ensure there were no errors when the expected result is OK. if resultStr == "OK" { - if name == "test (Round towards zero 2)" { - fmt.Println("found ls", scriptPubKey.String(), "uls", scriptSig.String()) - } if err != nil { t.Errorf("%s failed to execute: %v", name, err) } diff --git a/bscript/interpreter/scriptnum.go b/bscript/interpreter/scriptnum.go index 73a32ca4..336fbaf8 100644 --- a/bscript/interpreter/scriptnum.go +++ b/bscript/interpreter/scriptnum.go @@ -193,7 +193,7 @@ func (n scriptNum) Int64() int64 { // overflows. // // See the Bytes function documentation for example encodings. -func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int, bigInt bool) (scriptNum, error) { +func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (scriptNum, error) { // Interpreting data requires that it is not larger than // the passed scriptNumLen value. if len(v) > scriptNumLen { @@ -216,16 +216,6 @@ func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int, bigInt bool) return 0, nil } - //if bigInt { - // tmp := make([]byte, len(v)) - // copy(tmp, v) - // for i := 0; i < len(tmp)/2; i++ { - // tmp[i], tmp[len(tmp)-i-1] = tmp[len(tmp)-i-1], tmp[i] - // } - // b := new(big.Int).SetBytes(tmp) - // return scriptNum(b.Int64()), nil - //} - // Decode from little endian. var result int64 for i, val := range v { diff --git a/bscript/interpreter/scriptnum_test.go b/bscript/interpreter/scriptnum_test.go index fe90c6e6..2e17d120 100644 --- a/bscript/interpreter/scriptnum_test.go +++ b/bscript/interpreter/scriptnum_test.go @@ -201,7 +201,7 @@ func TestMakeScriptNum(t *testing.T) { // Ensure the error code is of the expected type and the error // code matches the value specified in the test instance. gotNum, err := makeScriptNum(test.serialised, test.minimalEncoding, - test.numLen, false) + test.numLen) if e := tstCheckScriptError(err, test.err); e != nil { t.Errorf("makeScriptNum(%#x): %v", test.serialised, e) continue diff --git a/bscript/interpreter/stack.go b/bscript/interpreter/stack.go index 80869790..3c1f660b 100644 --- a/bscript/interpreter/stack.go +++ b/bscript/interpreter/stack.go @@ -113,7 +113,7 @@ func (s *stack) PopInt() (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength, s.afterGenesis) + return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength) } func (s *stack) PopNumber() (*Number, error) { @@ -157,7 +157,7 @@ func (s *stack) PeekInt(idx int32) (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength, s.afterGenesis) + return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength) } // PeekBool returns the Nth item on the stack as a bool without removing it. diff --git a/examples/test/test.go b/examples/test/test.go deleted file mode 100644 index 8d12dec8..00000000 --- a/examples/test/test.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/libsv/go-bt/v2/bscript" - "github.com/libsv/go-bt/v2/bscript/interpreter" - "github.com/libsv/go-bt/v2/bscript/interpreter/scriptflag" -) - -func main() { - e := interpreter.NewEngine() - - ls, err := bscript.NewFromHexString("964f87") - //ls, err := bscript.NewFromASM("OP_2 OP_2 OP_SUB OP_4 OP_ADD OP_EQUAL") - if err != nil { - fmt.Println(err) - } - uls, _ := bscript.NewFromHexString("01a0011d") - //uls, err := bscript.NewFromASM("OP_2") - if err != nil { - fmt.Println(err) - } - - asm, _ := uls.ToASM() - asm2, _ := ls.ToASM() - fmt.Println(asm, asm2) - - if err := e.Execute( - interpreter.WithScripts( - ls, uls, - ), - interpreter.WithFlags(scriptflag.VerifyStrictEncoding), - ); err != nil { - fmt.Println(err) - } -} From 2a701c2411105f46fa848d73b924f8484d16c469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Sun, 14 Nov 2021 19:55:05 +0000 Subject: [PATCH 05/14] more cleaning --- bscript/interpreter/data/script_tests.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bscript/interpreter/data/script_tests.json b/bscript/interpreter/data/script_tests.json index 6284ece8..2ec54bab 100644 --- a/bscript/interpreter/data/script_tests.json +++ b/bscript/interpreter/data/script_tests.json @@ -1167,8 +1167,8 @@ ["-1 -1","DIV 1 EQUAL","P2SH,STRICTENC","OK"], ["28 21","DIV 1 EQUAL","P2SH,STRICTENC","OK","Round towards zero"], ["12 -7","DIV -1 EQUAL","P2SH,STRICTENC","OK","Round towards zero"], -["-32 29","DIV -1 EQUAL","P2SH,STRICTENC","OK","Round towards zero 2"], -["-42 -27","DIV 1 EQUAL","P2SH,STRICTENC","OK","Round towards zero 3"], +["-32 29","DIV -1 EQUAL","P2SH,STRICTENC","OK","Round towards zero"], +["-42 -27","DIV 1 EQUAL","P2SH,STRICTENC","OK","Round towards zero"], ["0 123","DIV 0 EQUAL","P2SH,STRICTENC","OK"], ["511 0","DIV","P2SH,STRICTENC","DIV_BY_ZERO","DIV, divide by zero"], ["1 1","DIV DEPTH 1 EQUAL","P2SH,STRICTENC","OK","Stack depth correct"], From f3fd00a1c98e70132320eca1049af729fc14bafd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Sun, 14 Nov 2021 20:15:07 +0000 Subject: [PATCH 06/14] fixed linter --- bscript/interpreter/number.go | 30 ++++++++++++++++++++++-------- bscript/interpreter/operations.go | 2 -- bscript/interpreter/scriptnum.go | 4 ---- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/bscript/interpreter/number.go b/bscript/interpreter/number.go index 4a4da7d0..792f9c50 100644 --- a/bscript/interpreter/number.go +++ b/bscript/interpreter/number.go @@ -7,6 +7,7 @@ import ( "github.com/libsv/go-bt/v2/bscript/interpreter/errs" ) +// Number a number larger than int64. type Number struct { val *big.Int afterGenesis bool @@ -15,6 +16,7 @@ type Number struct { var zero = big.NewInt(0) var one = big.NewInt(1) +// NewNumber returns a number. func NewNumber(bb []byte, scriptNumLen int, requireMinimal, afterGenesis bool) (*Number, error) { if len(bb) > scriptNumLen { return nil, errs.NewError( @@ -50,104 +52,120 @@ func NewNumber(bb []byte, scriptNumLen int, requireMinimal, afterGenesis bool) ( }, nil } +// Add adds the receiver and the number, sets the result over the receiver and returns. func (n *Number) Add(o *Number) *Number { result := n.val.Add(n.val, o.val) *n.val = *result return n } +// Sub subtracts the number from the receiver, sets the result over the receiver and returns. func (n *Number) Sub(o *Number) *Number { result := n.val.Sub(n.val, o.val) *n.val = *result return n } +// Mul multiplies the receiver by the number, sets the result over the receiver and returns. func (n *Number) Mul(o *Number) *Number { result := n.val.Mul(n.val, o.val) *n.val = *result return n } +// Div divides the receiver by the number, sets the result over the receiver and returns. func (n *Number) Div(o *Number) *Number { result := n.val.Quo(n.val, o.val) *n.val = *result return n } +// Mod divides the receiver by the number, sets the remainder over the receiver and returns. func (n *Number) Mod(o *Number) *Number { result := n.val.Rem(n.val, o.val) *n.val = *result return n } +// LessThanInt returns true if the receiver is smaller than the integer passed. func (n *Number) LessThanInt(i int64) bool { return n.LessThan(&Number{val: big.NewInt(i)}) } +// LessThan returns true if the receiver is smaller than the number passed. func (n *Number) LessThan(o *Number) bool { return n.val.Cmp(o.val) == -1 } +// LessThanOrEqual returns ture if the receiver is smaller or equal to the number passed. func (n *Number) LessThanOrEqual(o *Number) bool { return n.val.Cmp(o.val) < 1 } +// GreaterThanInt returns true if the receiver is larger than the integer passed. func (n *Number) GreaterThanInt(i int64) bool { return n.GreaterThan(&Number{val: big.NewInt(i)}) } +// GreaterThan returns true if the receiver is larger than the number passed. func (n *Number) GreaterThan(o *Number) bool { return n.val.Cmp(o.val) == 1 } +// GreaterThanOrEqual returns true if the receiver is larger or equal to the number passed. func (n *Number) GreaterThanOrEqual(o *Number) bool { return n.val.Cmp(o.val) > -1 } +// EqualInt returns true if the receiver is equal to the integer passed. func (n *Number) EqualInt(i int64) bool { return n.Equal(&Number{val: big.NewInt(i)}) } +// Equal returns true if the receiver is equal to the number passed. func (n *Number) Equal(o *Number) bool { return n.val.Cmp(o.val) == 0 } +// IsZero return strue if hte receiver equals zero. func (n *Number) IsZero() bool { return n.val.Cmp(zero) == 0 } +// Incr increment the receiver by one. func (n *Number) Incr() *Number { result := n.val.Add(n.val, one) *n.val = *result return n } +// Decr decrement the receiver by one. func (n *Number) Decr() *Number { result := n.val.Sub(n.val, one) *n.val = *result return n } +// Neg sets the receiver to the negative of the receiver. func (n *Number) Neg() *Number { result := n.val.Neg(n.val) *n.val = *result return n } +// Abs sets the receiver to the absolute value of hte receiver. func (n *Number) Abs() *Number { result := n.val.Abs(n.val) *n.val = *result return n } +// Bytes return the receiver in Bytes form. func (n *Number) Bytes() []byte { if n.val.Int64() == 0 { return []byte{} } - isNegative := n.val.Cmp(big.NewInt(0)) == -1 - if isNegative { - n.val.Neg(n.val) - } + isNegative := n.val.Cmp(zero) == -1 var bb []byte if !n.afterGenesis { @@ -162,10 +180,6 @@ func (n *Number) Bytes() []byte { bb = n.val.Bytes() } - //tmp := make([]byte, len(bb), len(bb)+1) - //for i := len(bb) - 1; i >= 0; i-- { - // tmp[i] = bb[i] - //} tmp := make([]byte, 0, len(bb)+1) cpy := new(big.Int).SetBytes(n.val.Bytes()) for cpy.Cmp(zero) == 1 { diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index 084aede4..e8aa026b 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -996,7 +996,6 @@ func opcodeSplit(op *ParsedOpcode, t *thread) error { // // Stack transformation: a b bscript.OpNUM2BIN -> x func opcodeNum2bin(op *ParsedOpcode, t *thread) error { - //n, err := t.dstack.PopInt() n, err := t.dstack.PopNumber() if err != nil { return err @@ -1008,7 +1007,6 @@ func opcodeNum2bin(op *ParsedOpcode, t *thread) error { } if n.GreaterThanInt(int64(t.cfg.MaxScriptElementSize())) { - //if size > t.cfg.MaxScriptElementSize() { return errs.NewError(errs.ErrNumberTooBig, "n is larger than the max of %d", t.cfg.MaxScriptElementSize()) } diff --git a/bscript/interpreter/scriptnum.go b/bscript/interpreter/scriptnum.go index 336fbaf8..596c8898 100644 --- a/bscript/interpreter/scriptnum.go +++ b/bscript/interpreter/scriptnum.go @@ -16,10 +16,6 @@ const ( minInt64 = -1 << 63 ) -type scriptNumber interface { - Bytes() []byte -} - // scriptNum represents a numeric value used in the scripting engine with // special handling to deal with the subtle semantics required by consensus. // From 323706b9623a8c6760faecca079dc3727e39f4b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Sun, 14 Nov 2021 21:07:02 +0000 Subject: [PATCH 07/14] began scriptnum removal --- bscript/interpreter/number.go | 18 +++ bscript/interpreter/operations.go | 183 +++++++++++++++----------- bscript/interpreter/scriptnum_test.go | 10 +- bscript/interpreter/stack.go | 21 +-- bscript/interpreter/stack_test.go | 56 ++++---- 5 files changed, 163 insertions(+), 125 deletions(-) diff --git a/bscript/interpreter/number.go b/bscript/interpreter/number.go index 792f9c50..d5333529 100644 --- a/bscript/interpreter/number.go +++ b/bscript/interpreter/number.go @@ -160,6 +160,24 @@ func (n *Number) Abs() *Number { return n } +func (n *Number) Int() int { + return int(n.val.Int64()) +} + +func (n *Number) Int32() int32 { + return int32(n.val.Int64()) +} + +func (n *Number) Int64() int64 { + return n.val.Int64() +} + +func (n *Number) Set(i int64) *Number { + val := big.NewInt(i) + *n.val = *val + return n +} + // Bytes return the receiver in Bytes form. func (n *Number) Bytes() []byte { if n.val.Int64() == 0 { diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index e8aa026b..0235cfcc 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" //nolint:gosec // OP_SHA1 support requires this "crypto/sha256" "hash" + "math/big" "github.com/libsv/go-bk/bec" "github.com/libsv/go-bk/crypto" @@ -638,7 +639,7 @@ func opcodeCheckLockTimeVerify(op *ParsedOpcode, t *thread) error { if err != nil { return err } - lockTime, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5) + lockTime, err := NewNumber(so, 5, t.dstack.verifyMinimalData, t.cfg.AfterGenesis()) if err != nil { return err } @@ -646,15 +647,15 @@ func opcodeCheckLockTimeVerify(op *ParsedOpcode, t *thread) error { // In the rare event that the argument needs to be < 0 due to some // arithmetic being done first, you can always use // 0 bscript.OpMAX bscript.OpCHECKLOCKTIMEVERIFY. - if lockTime < 0 { - return errs.NewError(errs.ErrNegativeLockTime, "negative lock time: %d", lockTime) + if lockTime.LessThanInt(0) { + return errs.NewError(errs.ErrNegativeLockTime, "negative lock time: %d", lockTime.val.Int64()) } // The lock time field of a transaction is either a block height at // which the transaction is finalised or a timestamp depending on if the // value is before the interpreter.LockTimeThreshold. When it is under the // threshold it is a block height. - if err = verifyLockTime(int64(t.tx.LockTime), LockTimeThreshold, int64(lockTime)); err != nil { + if err = verifyLockTime(int64(t.tx.LockTime), LockTimeThreshold, lockTime.val.Int64()); err != nil { return err } @@ -708,7 +709,7 @@ func opcodeCheckSequenceVerify(op *ParsedOpcode, t *thread) error { if err != nil { return err } - stackSequence, err := makeScriptNum(so, t.dstack.verifyMinimalData, 5) + stackSequence, err := NewNumber(so, 5, t.dstack.verifyMinimalData, t.cfg.AfterGenesis()) if err != nil { return err } @@ -716,11 +717,11 @@ func opcodeCheckSequenceVerify(op *ParsedOpcode, t *thread) error { // In the rare event that the argument needs to be < 0 due to some // arithmetic being done first, you can always use // 0 bscript.OpMAX bscript.OpCHECKSEQUENCEVERIFY. - if stackSequence < 0 { - return errs.NewError(errs.ErrNegativeLockTime, "negative sequence: %d", stackSequence) + if stackSequence.LessThanInt(0) { + return errs.NewError(errs.ErrNegativeLockTime, "negative sequence: %d", stackSequence.val.Int64()) } - sequence := int64(stackSequence) + sequence := stackSequence.val.Int64() // To provide for future soft-fork extensibility, if the // operand has the disabled lock-time flag set, @@ -978,12 +979,12 @@ func opcodeSplit(op *ParsedOpcode, t *thread) error { if n.Int32() > int32(len(c)) { return errs.NewError(errs.ErrNumberTooBig, "n is larger than length of array") } - if n < 0 { + if n.LessThanInt(0) { return errs.NewError(errs.ErrNumberTooSmall, "n is negative") } - a := c[:n] - b := c[n:] + a := c[:n.Int()] + b := c[n.Int():] t.dstack.PushByteArray(a) t.dstack.PushByteArray(b) @@ -996,7 +997,7 @@ func opcodeSplit(op *ParsedOpcode, t *thread) error { // // Stack transformation: a b bscript.OpNUM2BIN -> x func opcodeNum2bin(op *ParsedOpcode, t *thread) error { - n, err := t.dstack.PopNumber() + n, err := t.dstack.PopInt() if err != nil { return err } @@ -1212,7 +1213,7 @@ func opcodeEqualVerify(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1 x2+1] func opcode1Add(op *ParsedOpcode, t *thread) error { - m, err := t.dstack.PopNumber() + m, err := t.dstack.PopInt() if err != nil { return err } @@ -1226,7 +1227,7 @@ func opcode1Add(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1 x2-1] func opcode1Sub(op *ParsedOpcode, t *thread) error { - m, err := t.dstack.PopNumber() + m, err := t.dstack.PopInt() if err != nil { return err } @@ -1240,7 +1241,7 @@ func opcode1Sub(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1 -x2] func opcodeNegate(op *ParsedOpcode, t *thread) error { - m, err := t.dstack.PopNumber() + m, err := t.dstack.PopInt() if err != nil { return err } @@ -1254,7 +1255,7 @@ func opcodeNegate(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1 abs(x2)] func opcodeAbs(op *ParsedOpcode, t *thread) error { - m, err := t.dstack.PopNumber() + m, err := t.dstack.PopInt() if err != nil { return err } @@ -1281,12 +1282,15 @@ func opcodeNot(op *ParsedOpcode, t *thread) error { return err } - var n scriptNum - if m == 0 { + var n int64 + if m.IsZero() { n = 1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -1302,11 +1306,11 @@ func opcode0NotEqual(op *ParsedOpcode, t *thread) error { return err } - if m != 0 { - m = 1 + if !m.IsZero() { + m.Set(1) } - t.dstack.PushInt(m) + t.dstack.PushNumber(m) return nil } @@ -1315,12 +1319,12 @@ func opcode0NotEqual(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1+x2] func opcodeAdd(op *ParsedOpcode, t *thread) error { - v0, err := t.dstack.PopNumber() + v0, err := t.dstack.PopInt() if err != nil { return err } - v1, err := t.dstack.PopNumber() + v1, err := t.dstack.PopInt() if err != nil { return err } @@ -1335,12 +1339,12 @@ func opcodeAdd(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... x1-x2] func opcodeSub(op *ParsedOpcode, t *thread) error { - v0, err := t.dstack.PopNumber() + v0, err := t.dstack.PopInt() if err != nil { return err } - v1, err := t.dstack.PopNumber() + v1, err := t.dstack.PopInt() if err != nil { return err } @@ -1353,12 +1357,12 @@ func opcodeSub(op *ParsedOpcode, t *thread) error { // them with the result of subtracting the top entry from the second-to-top // entry. func opcodeMul(op *ParsedOpcode, t *thread) error { - n1, err := t.dstack.PopNumber() + n1, err := t.dstack.PopInt() if err != nil { return err } - n2, err := t.dstack.PopNumber() + n2, err := t.dstack.PopInt() if err != nil { return err } @@ -1372,12 +1376,12 @@ func opcodeMul(op *ParsedOpcode, t *thread) error { // // Stack transformation: a b bscript.OpDIV -> out func opcodeDiv(op *ParsedOpcode, t *thread) error { - b, err := t.dstack.PopNumber() + b, err := t.dstack.PopInt() if err != nil { return err } - a, err := t.dstack.PopNumber() + a, err := t.dstack.PopInt() if err != nil { return err } @@ -1395,12 +1399,12 @@ func opcodeDiv(op *ParsedOpcode, t *thread) error { // // Stack transformation: a b bscript.OpMOD -> out func opcodeMod(op *ParsedOpcode, t *thread) error { - b, err := t.dstack.PopNumber() + b, err := t.dstack.PopInt() if err != nil { return err } - a, err := t.dstack.PopNumber() + a, err := t.dstack.PopInt() if err != nil { return err } @@ -1414,12 +1418,13 @@ func opcodeMod(op *ParsedOpcode, t *thread) error { } func opcodeLShift(op *ParsedOpcode, t *thread) error { - n, err := t.dstack.PopInt() + num, err := t.dstack.PopInt() if err != nil { return err } + n := num.Int() - if n.Int32() < 0 { + if n < 0 { return errs.NewError(errs.ErrNumberTooSmall, "n less than 0") } @@ -1439,12 +1444,13 @@ func opcodeLShift(op *ParsedOpcode, t *thread) error { } func opcodeRShift(op *ParsedOpcode, t *thread) error { - n, err := t.dstack.PopInt() + num, err := t.dstack.PopInt() if err != nil { return err } + n := num.Int() - if n.Int32() < 0 { + if n < 0 { return errs.NewError(errs.ErrNumberTooSmall, "n less than 0") } @@ -1481,12 +1487,15 @@ func opcodeBoolAnd(op *ParsedOpcode, t *thread) error { return err } - var n scriptNum - if v0 != 0 && v1 != 0 { + var n int64 + if !v0.IsZero() && !v1.IsZero() { n = 1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -1508,12 +1517,15 @@ func opcodeBoolOr(op *ParsedOpcode, t *thread) error { return err } - var n scriptNum - if v0 != 0 || v1 != 0 { + var n int64 + if !v0.IsZero() || !v1.IsZero() { n = 1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -1523,22 +1535,25 @@ func opcodeBoolOr(op *ParsedOpcode, t *thread) error { // Stack transformation (x1==x2): [... 5 5] -> [... 1] // Stack transformation (x1!=x2): [... 5 7] -> [... 0] func opcodeNumEqual(op *ParsedOpcode, t *thread) error { - v0, err := t.dstack.PopNumber() + v0, err := t.dstack.PopInt() if err != nil { return err } - v1, err := t.dstack.PopNumber() + v1, err := t.dstack.PopInt() if err != nil { return err } - var n scriptNum + var n int64 if v0.Equal(v1) { n = 1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -1564,22 +1579,25 @@ func opcodeNumEqualVerify(op *ParsedOpcode, t *thread) error { // Stack transformation (x1==x2): [... 5 5] -> [... 0] // Stack transformation (x1!=x2): [... 5 7] -> [... 1] func opcodeNumNotEqual(op *ParsedOpcode, t *thread) error { - v0, err := t.dstack.PopNumber() + v0, err := t.dstack.PopInt() if err != nil { return err } - v1, err := t.dstack.PopNumber() + v1, err := t.dstack.PopInt() if err != nil { return err } - var n scriptNum + var n int64 if !v0.Equal(v1) { n = 1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -1589,22 +1607,25 @@ func opcodeNumNotEqual(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... bool] func opcodeLessThan(op *ParsedOpcode, t *thread) error { - v0, err := t.dstack.PopNumber() + v0, err := t.dstack.PopInt() if err != nil { return err } - v1, err := t.dstack.PopNumber() + v1, err := t.dstack.PopInt() if err != nil { return err } - var n scriptNum + var n int64 if v1.LessThan(v0) { n = 1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -1614,22 +1635,25 @@ func opcodeLessThan(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... bool] func opcodeGreaterThan(op *ParsedOpcode, t *thread) error { - v0, err := t.dstack.PopNumber() + v0, err := t.dstack.PopInt() if err != nil { return err } - v1, err := t.dstack.PopNumber() + v1, err := t.dstack.PopInt() if err != nil { return err } - var n scriptNum + var n int64 if v1.GreaterThan(v0) { n = 1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -1639,22 +1663,25 @@ func opcodeGreaterThan(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... bool] func opcodeLessThanOrEqual(op *ParsedOpcode, t *thread) error { - v0, err := t.dstack.PopNumber() + v0, err := t.dstack.PopInt() if err != nil { return err } - v1, err := t.dstack.PopNumber() + v1, err := t.dstack.PopInt() if err != nil { return err } - var n scriptNum + var n int64 if v1.LessThanOrEqual(v0) { n = 1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -1664,22 +1691,25 @@ func opcodeLessThanOrEqual(op *ParsedOpcode, t *thread) error { // // Stack transformation: [... x1 x2] -> [... bool] func opcodeGreaterThanOrEqual(op *ParsedOpcode, t *thread) error { - v0, err := t.dstack.PopNumber() + v0, err := t.dstack.PopInt() if err != nil { return err } - v1, err := t.dstack.PopNumber() + v1, err := t.dstack.PopInt() if err != nil { return err } - var n scriptNum + var n int64 if v1.GreaterThanOrEqual(v0) { n = 1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -1699,11 +1729,11 @@ func opcodeMin(op *ParsedOpcode, t *thread) error { } n := v0 - if v1 < v0 { + if v1.LessThan(v0) { n = v1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(n) return nil } @@ -1723,11 +1753,11 @@ func opcodeMax(op *ParsedOpcode, t *thread) error { } n := v0 - if v1 > v0 { + if v1.GreaterThan(v0) { n = v1 } - t.dstack.PushInt(n) + t.dstack.PushNumber(n) return nil } @@ -1755,12 +1785,15 @@ func opcodeWithin(op *ParsedOpcode, t *thread) error { return err } - var n int - if minVal <= x && x < maxVal { + var n int64 + if minVal.LessThanOrEqual(x) && x.LessThan(maxVal) { n = 1 } - t.dstack.PushInt(scriptNum(n)) + t.dstack.PushNumber(&Number{ + val: big.NewInt(n), + afterGenesis: t.cfg.AfterGenesis(), + }) return nil } @@ -2009,7 +2042,7 @@ func opcodeCheckMultiSig(op *ParsedOpcode, t *thread) error { return err } - numPubKeys := int(numKeys.Int32()) + numPubKeys := numKeys.Int() if numPubKeys < 0 { return errs.NewError(errs.ErrInvalidPubKeyCount, "number of pubkeys %d is negative", numPubKeys) } @@ -2039,7 +2072,7 @@ func opcodeCheckMultiSig(op *ParsedOpcode, t *thread) error { return err } - numSignatures := int(numSigs.Int32()) + numSignatures := numSigs.Int() if numSignatures < 0 { return errs.NewError(errs.ErrInvalidSignatureCount, "number of signatures %d is negative", numSignatures) } diff --git a/bscript/interpreter/scriptnum_test.go b/bscript/interpreter/scriptnum_test.go index 2e17d120..f91b6247 100644 --- a/bscript/interpreter/scriptnum_test.go +++ b/bscript/interpreter/scriptnum_test.go @@ -200,17 +200,17 @@ func TestMakeScriptNum(t *testing.T) { for _, test := range tests { // Ensure the error code is of the expected type and the error // code matches the value specified in the test instance. - gotNum, err := makeScriptNum(test.serialised, test.minimalEncoding, - test.numLen) + //gotNum, err := makeScriptNum(test.serialised, test.minimalEncoding, test.numLen) + //gotNum, err := NewNumber(test.serialised, test.numLen, test.minimalEncoding, true) + gotNum, err := makeScriptNum(test.serialised, test.minimalEncoding, test.numLen) if e := tstCheckScriptError(err, test.err); e != nil { t.Errorf("makeScriptNum(%#x): %v", test.serialised, e) continue } if gotNum != test.num { - t.Errorf("makeScriptNum(%#x): did not get expected "+ - "number - got %d, want %d", test.serialised, - gotNum, test.num) + t.Errorf("makeScriptNum(%#x): did not get expected number - got %d, want %d", + test.serialised, gotNum, test.num) continue } } diff --git a/bscript/interpreter/stack.go b/bscript/interpreter/stack.go index 3c1f660b..513708a1 100644 --- a/bscript/interpreter/stack.go +++ b/bscript/interpreter/stack.go @@ -102,21 +102,12 @@ func (s *stack) PopByteArray() ([]byte, error) { return data, nil } -// PopInt pops the value off the top of the stack, converts it into a script -// num, and returns it. The act of converting to a script num enforces the +// PopInt pops the value off the top of the stack, converts it into a Number, +// and returns it. The act of converting to a script num enforces the // consensus rules imposed on data interpreted as numbers. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2] -func (s *stack) PopInt() (scriptNum, error) { - so, err := s.PopByteArray() - if err != nil { - return 0, err - } - - return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength) -} - -func (s *stack) PopNumber() (*Number, error) { +func (s *stack) PopInt() (*Number, error) { so, err := s.PopByteArray() if err != nil { return nil, err @@ -151,13 +142,13 @@ func (s *stack) PeekByteArray(idx int32) ([]byte, error) { // PeekInt returns the Nth item on the stack as a script num without removing // it. The act of converting to a script num enforces the consensus rules // imposed on data interpreted as numbers. -func (s *stack) PeekInt(idx int32) (scriptNum, error) { +func (s *stack) PeekInt(idx int32) (*Number, error) { so, err := s.PeekByteArray(idx) if err != nil { - return 0, err + return nil, err } - return makeScriptNum(so, s.verifyMinimalData, s.maxNumLength) + return NewNumber(so, s.maxNumLength, s.verifyMinimalData, s.afterGenesis) } // PeekBool returns the Nth item on the stack as a bool without removing it. diff --git a/bscript/interpreter/stack_test.go b/bscript/interpreter/stack_test.go index 58253408..d347e472 100644 --- a/bscript/interpreter/stack_test.go +++ b/bscript/interpreter/stack_test.go @@ -185,15 +185,15 @@ func TestStack(t *testing.T) { nil, }, { - "popInt 0", + "popNumber 0", [][]byte{{0x0}}, func(s *stack) error { v, err := s.PopInt() if err != nil { return err } - if v != 0 { - return errors.New("0 != 0 on popInt") + if v.Int() != 0 { + return errors.New("0 != 0 on popNumber") } return nil }, @@ -201,15 +201,15 @@ func TestStack(t *testing.T) { nil, }, { - "popInt -0", + "popNumber -0", [][]byte{{0x80}}, func(s *stack) error { v, err := s.PopInt() if err != nil { return err } - if v != 0 { - return errors.New("-0 != 0 on popInt") + if v.Int() != 0 { + return errors.New("-0 != 0 on popNumber") } return nil }, @@ -217,15 +217,15 @@ func TestStack(t *testing.T) { nil, }, { - "popInt 1", + "popNumber 1", [][]byte{{0x01}}, func(s *stack) error { v, err := s.PopInt() if err != nil { return err } - if v != 1 { - return errors.New("1 != 1 on popInt") + if v.Int() != 1 { + return errors.New("1 != 1 on popNumber") } return nil }, @@ -233,16 +233,15 @@ func TestStack(t *testing.T) { nil, }, { - "popInt 1 leading 0", + "popNumber 1 leading 0", [][]byte{{0x01, 0x00, 0x00, 0x00}}, func(s *stack) error { v, err := s.PopInt() if err != nil { return err } - if v != 1 { - fmt.Printf("%v != %v\n", v, 1) - return errors.New("1 != 1 on popInt") + if v.Int() != 1 { + return errors.New("1 != 1 on popNumber") } return nil }, @@ -250,15 +249,15 @@ func TestStack(t *testing.T) { nil, }, { - "popInt -1", + "popNumber -1", [][]byte{{0x81}}, func(s *stack) error { v, err := s.PopInt() if err != nil { return err } - if v != -1 { - return errors.New("-1 != -1 on popInt") + if v.Int() != -1 { + return errors.New("-1 != -1 on popNumber") } return nil }, @@ -266,16 +265,15 @@ func TestStack(t *testing.T) { nil, }, { - "popInt -1 leading 0", + "popNumber -1 leading 0", [][]byte{{0x01, 0x00, 0x00, 0x80}}, func(s *stack) error { v, err := s.PopInt() if err != nil { return err } - if v != -1 { - fmt.Printf("%v != %v\n", v, -1) - return errors.New("-1 != -1 on popInt") + if v.Int() != -1 { + return errors.New("-1 != -1 on popNumber") } return nil }, @@ -284,16 +282,15 @@ func TestStack(t *testing.T) { }, // Triggers the multibyte case in asInt { - "popInt -513", + "popNumber -513", [][]byte{{0x1, 0x82}}, func(s *stack) error { v, err := s.PopInt() if err != nil { return err } - if v != -513 { - fmt.Printf("%v != %v\n", v, -513) - return errors.New("1 != 1 on popInt") + if v.Int() != -513 { + return errors.New("-513 != -513 on popNumber") } return nil }, @@ -309,8 +306,7 @@ func TestStack(t *testing.T) { if err != nil { return err } - if v != -1 { - fmt.Printf("%v != %v\n", v, -1) + if v.Int() != -1 { return errors.New("-1 != -1 on popInt") } return nil @@ -844,7 +840,7 @@ func TestStack(t *testing.T) { if err != nil { return err } - if val != 1 { + if val.Int() != 1 { return errors.New("invalid result") } return nil @@ -862,7 +858,7 @@ func TestStack(t *testing.T) { if err != nil { return err } - if val != 0 { + if val.Int() != 0 { return errors.New("invalid result") } return nil @@ -871,7 +867,7 @@ func TestStack(t *testing.T) { [][]byte{{0}}, }, { - "pop int", + "pop number", nil, func(s *stack) error { s.PushInt(scriptNum(1)) @@ -881,7 +877,7 @@ func TestStack(t *testing.T) { if err != nil { return err } - if val != 1 { + if val.Int() != 1 { return errors.New("invalid result") } return nil From bd8200c6263122d606347c37f8af2913c610b63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Mon, 15 Nov 2021 19:12:43 +0000 Subject: [PATCH 08/14] rewrite scriptnumber entirely --- bscript/interpreter/number.go | 272 +++++++++++++++--- .../{scriptnum_test.go => number_test.go} | 88 +++++- bscript/interpreter/operations.go | 67 +++-- bscript/interpreter/reference_test.go | 4 +- bscript/interpreter/scriptnum.go | 267 ----------------- bscript/interpreter/stack.go | 16 +- bscript/interpreter/stack_test.go | 17 +- 7 files changed, 367 insertions(+), 364 deletions(-) rename bscript/interpreter/{scriptnum_test.go => number_test.go} (82%) delete mode 100644 bscript/interpreter/scriptnum.go diff --git a/bscript/interpreter/number.go b/bscript/interpreter/number.go index d5333529..772ad7ec 100644 --- a/bscript/interpreter/number.go +++ b/bscript/interpreter/number.go @@ -7,8 +7,33 @@ import ( "github.com/libsv/go-bt/v2/bscript/interpreter/errs" ) -// Number a number larger than int64. -type Number struct { +// scriptNumber represents a numeric value used in the scripting engine with +// special handling to deal with the subtle semantics required by consensus. +// +// All numbers are stored on the data and alternate stacks encoded as little +// endian with a sign bit. All numeric opcodes such as OP_ADD, OP_SUB, +// and OP_MUL, are only allowed to operate on 4-byte integers in the range +// [-2^31 + 1, 2^31 - 1], however the results of numeric operations may overflow +// and remain valid so long as they are not used as inputs to other numeric +// operations or otherwise interpreted as an integer. +// +// For example, it is possible for OP_ADD to have 2^31 - 1 for its two operands +// resulting 2^32 - 2, which overflows, but is still pushed to the stack as the +// result of the addition. That value can then be used as input to OP_VERIFY +// which will succeed because the data is being interpreted as a boolean. +// However, if that same value were to be used as input to another numeric +// opcode, such as OP_SUB, it must fail. +// +// This type handles the aforementioned requirements by storing all numeric +// operation results as an int64 to handle overflow and provides the Bytes +// method to get the serialised representation (including values that overflow). +// +// Then, whenever data is interpreted as an integer, it is converted to this +// type by using the NewNumber function which will return an error if the +// 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 behaviour. +type scriptNumber struct { val *big.Int afterGenesis bool } @@ -16,174 +41,277 @@ type Number struct { var zero = big.NewInt(0) var one = big.NewInt(1) -// NewNumber returns a number. -func NewNumber(bb []byte, scriptNumLen int, requireMinimal, afterGenesis bool) (*Number, error) { +// makeScriptNumber interprets the passed serialised bytes as an encoded integer +// and returns the result as a Number. +// +// Since the consensus rules dictate that serialised bytes interpreted as integers +// 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 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 +// number of bytes or is the negative 0 encoding, [0x80]. For example, consider +// the number 127. It could be encoded as [0x7f], [0x7f 0x00], +// [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 errs.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 makeScriptNumber(bb []byte, scriptNumLen int, requireMinimal, afterGenesis bool) (*scriptNumber, error) { + // Interpreting data requires that it is not larger than the passed scriptNumLen value. if len(bb) > scriptNumLen { - return nil, errs.NewError( + return &scriptNumber{val: big.NewInt(0), afterGenesis: false}, errs.NewError( errs.ErrNumberTooBig, "numeric value encoded as %x is %d bytes which exceeds the max allowed of %d", bb, len(bb), scriptNumLen, ) } + + // Enforce minimal encoded if requested. if requireMinimal { if err := checkMinimalDataEncoding(bb); err != nil { - return nil, err + return &scriptNumber{ + val: big.NewInt(0), + afterGenesis: false, + }, err } } + + // Zero is encoded as an empty byte slice. if len(bb) == 0 { - return &Number{ + return &scriptNumber{ afterGenesis: afterGenesis, val: big.NewInt(0), }, nil } + + // Decode from little endian. v := new(big.Int) for i, b := range bb { v.Or(v, new(big.Int).Lsh(new(big.Int).SetBytes([]byte{b}), uint(8*i))) } + // When the most significant byte of the input bytes has the sign bit + // set, the result is negative. So, remove the sign bit from the result + // and make it negative. if bb[len(bb)-1]&0x80 != 0 { + // The maximum length of bb has already been determined to be 4 + // above, so uint8 is enough to cover the max possible shift + // value of 24. shift := big.NewInt(int64(0x80)) shift.Not(shift.Lsh(shift, uint(8*(len(bb)-1)))) v.And(v, shift).Neg(v) } - return &Number{ + return &scriptNumber{ val: v, afterGenesis: afterGenesis, }, nil } // Add adds the receiver and the number, sets the result over the receiver and returns. -func (n *Number) Add(o *Number) *Number { +func (n *scriptNumber) Add(o *scriptNumber) *scriptNumber { result := n.val.Add(n.val, o.val) *n.val = *result return n } // Sub subtracts the number from the receiver, sets the result over the receiver and returns. -func (n *Number) Sub(o *Number) *Number { +func (n *scriptNumber) Sub(o *scriptNumber) *scriptNumber { result := n.val.Sub(n.val, o.val) *n.val = *result return n } // Mul multiplies the receiver by the number, sets the result over the receiver and returns. -func (n *Number) Mul(o *Number) *Number { +func (n *scriptNumber) Mul(o *scriptNumber) *scriptNumber { result := n.val.Mul(n.val, o.val) *n.val = *result return n } // Div divides the receiver by the number, sets the result over the receiver and returns. -func (n *Number) Div(o *Number) *Number { +func (n *scriptNumber) Div(o *scriptNumber) *scriptNumber { result := n.val.Quo(n.val, o.val) *n.val = *result return n } // Mod divides the receiver by the number, sets the remainder over the receiver and returns. -func (n *Number) Mod(o *Number) *Number { +func (n *scriptNumber) Mod(o *scriptNumber) *scriptNumber { result := n.val.Rem(n.val, o.val) *n.val = *result return n } // LessThanInt returns true if the receiver is smaller than the integer passed. -func (n *Number) LessThanInt(i int64) bool { - return n.LessThan(&Number{val: big.NewInt(i)}) +func (n *scriptNumber) LessThanInt(i int64) bool { + return n.LessThan(&scriptNumber{val: big.NewInt(i)}) } // LessThan returns true if the receiver is smaller than the number passed. -func (n *Number) LessThan(o *Number) bool { +func (n *scriptNumber) LessThan(o *scriptNumber) bool { return n.val.Cmp(o.val) == -1 } // LessThanOrEqual returns ture if the receiver is smaller or equal to the number passed. -func (n *Number) LessThanOrEqual(o *Number) bool { +func (n *scriptNumber) LessThanOrEqual(o *scriptNumber) bool { return n.val.Cmp(o.val) < 1 } // GreaterThanInt returns true if the receiver is larger than the integer passed. -func (n *Number) GreaterThanInt(i int64) bool { - return n.GreaterThan(&Number{val: big.NewInt(i)}) +func (n *scriptNumber) GreaterThanInt(i int64) bool { + return n.GreaterThan(&scriptNumber{val: big.NewInt(i)}) } // GreaterThan returns true if the receiver is larger than the number passed. -func (n *Number) GreaterThan(o *Number) bool { +func (n *scriptNumber) GreaterThan(o *scriptNumber) bool { return n.val.Cmp(o.val) == 1 } // GreaterThanOrEqual returns true if the receiver is larger or equal to the number passed. -func (n *Number) GreaterThanOrEqual(o *Number) bool { +func (n *scriptNumber) GreaterThanOrEqual(o *scriptNumber) bool { return n.val.Cmp(o.val) > -1 } // EqualInt returns true if the receiver is equal to the integer passed. -func (n *Number) EqualInt(i int64) bool { - return n.Equal(&Number{val: big.NewInt(i)}) +func (n *scriptNumber) EqualInt(i int64) bool { + return n.Equal(&scriptNumber{val: big.NewInt(i)}) } // Equal returns true if the receiver is equal to the number passed. -func (n *Number) Equal(o *Number) bool { +func (n *scriptNumber) Equal(o *scriptNumber) bool { return n.val.Cmp(o.val) == 0 } // IsZero return strue if hte receiver equals zero. -func (n *Number) IsZero() bool { +func (n *scriptNumber) IsZero() bool { return n.val.Cmp(zero) == 0 } // Incr increment the receiver by one. -func (n *Number) Incr() *Number { +func (n *scriptNumber) Incr() *scriptNumber { result := n.val.Add(n.val, one) *n.val = *result return n } // Decr decrement the receiver by one. -func (n *Number) Decr() *Number { +func (n *scriptNumber) Decr() *scriptNumber { result := n.val.Sub(n.val, one) *n.val = *result return n } // Neg sets the receiver to the negative of the receiver. -func (n *Number) Neg() *Number { +func (n *scriptNumber) Neg() *scriptNumber { result := n.val.Neg(n.val) *n.val = *result return n } // Abs sets the receiver to the absolute value of hte receiver. -func (n *Number) Abs() *Number { +func (n *scriptNumber) Abs() *scriptNumber { result := n.val.Abs(n.val) *n.val = *result return n } -func (n *Number) Int() int { +// Int returns the receivers value as an int. +func (n *scriptNumber) Int() int { return int(n.val.Int64()) } -func (n *Number) Int32() int32 { - return int32(n.val.Int64()) +// Int32 returns the Number clamped to a valid int32. That is to say +// when the script number is higher than the max allowed int32, the max int32 +// value is returned and vice versa for the minimum value. Note that this +// behaviour is different from a simple int32 cast because that truncates +// and the consensus rules dictate numbers which are directly cast to integers +// provide this behaviour. +// +// 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 behaviour. +func (n *scriptNumber) Int32() int32 { + v := n.val.Int64() + if v > math.MaxInt32 { + return math.MaxInt32 + } + if v < math.MinInt32 { + return math.MinInt32 + } + return int32(v) } -func (n *Number) Int64() int64 { +// Int64 returns the Number clamped to a valid int64. That is to say +// when the script number is higher than the max allowed int64, the max int64 +// value is returned and vice versa for the minimum value. Note that this +// behaviour is different from a simple int64 cast because that truncates +// and the consensus rules dictate numbers which are directly cast to integers +// provide this behaviour. +// +// 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 behaviour. +func (n *scriptNumber) Int64() int64 { + if n.GreaterThanInt(math.MaxInt64) { + return math.MaxInt64 + } + if n.LessThanInt(math.MinInt64) { + return math.MinInt64 + } return n.val.Int64() } -func (n *Number) Set(i int64) *Number { +// Set the value of the receiver. +func (n *scriptNumber) Set(i int64) *scriptNumber { val := big.NewInt(i) *n.val = *val return n } -// Bytes return the receiver in Bytes form. -func (n *Number) Bytes() []byte { - if n.val.Int64() == 0 { +// Bytes returns the number serialised as a little endian with a sign bit. +// +// Example encodings: +// 127 -> [0x7f] +// -127 -> [0xff] +// 128 -> [0x80 0x00] +// -128 -> [0x80 0x80] +// 129 -> [0x81 0x00] +// -129 -> [0x81 0x80] +// 256 -> [0x00 0x01] +// -256 -> [0x00 0x81] +// 32767 -> [0xff 0x7f] +// -32767 -> [0xff 0xff] +// 32768 -> [0x00 0x80 0x00] +// -32768 -> [0x00 0x80 0x80] +func (n *scriptNumber) Bytes() []byte { + // Zero encodes as an empty byte slice. + if n.IsZero() { return []byte{} } + + // Take the absolute value and keep track of whether it was originally + // negative. isNegative := n.val.Cmp(zero) == -1 + if isNegative { + n.Neg() + } var bb []byte if !n.afterGenesis { @@ -198,6 +326,8 @@ func (n *Number) Bytes() []byte { bb = n.val.Bytes() } + // Encode to little endian. The maximum number of encoded bytes is 9 + // (8 bytes for max int64 plus a potential byte for sign extension). tmp := make([]byte, 0, len(bb)+1) cpy := new(big.Int).SetBytes(n.val.Bytes()) for cpy.Cmp(zero) == 1 { @@ -205,6 +335,13 @@ func (n *Number) Bytes() []byte { cpy.Rsh(cpy, 8) } + // When the most significant byte already has the high bit set, an + // additional high byte is required to indicate whether the number is + // negative or positive. The additional byte is removed when converting + // back to an integral and its high bit is used to denote the sign. + // + // Otherwise, when the most significant byte does not already have the + // high bit set, use it to indicate the value is negative, if needed. if tmp[len(tmp)-1]&0x80 != 0 { extraByte := byte(0x00) if isNegative { @@ -217,3 +354,64 @@ func (n *Number) Bytes() []byte { return tmp } + +func minimallyEncode(data []byte) []byte { + if len(data) == 0 { + return data + } + + last := data[len(data)-1] + if last&0x7f != 0 { + return data + } + + if len(data) == 1 { + return []byte{} + } + + if data[len(data)-2]&0x80 != 0 { + return data + } + + for i := len(data) - 1; i > 0; i-- { + if data[i-1] != 0 { + if data[i-1]&0x80 != 0 { + data[i] = last + i++ + } else { + data[i-1] |= last + } + + return data[:i] + } + } + + return []byte{} +} + +// checkMinimalDataEncoding returns whether the passed byte array adheres +// to the minimal encoding requirements. +func checkMinimalDataEncoding(v []byte) error { + if len(v) == 0 { + return nil + } + + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, [0x80]. + if v[len(v)-1]&0x7f == 0 { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if len(v) == 1 || v[len(v)-2]&0x80 == 0 { + return errs.NewError(errs.ErrMinimalData, "numeric value encoded as %x is not minimally encoded", v) + } + } + + return nil +} diff --git a/bscript/interpreter/scriptnum_test.go b/bscript/interpreter/number_test.go similarity index 82% rename from bscript/interpreter/scriptnum_test.go rename to bscript/interpreter/number_test.go index f91b6247..4e395bb8 100644 --- a/bscript/interpreter/scriptnum_test.go +++ b/bscript/interpreter/number_test.go @@ -7,6 +7,8 @@ package interpreter import ( "bytes" "encoding/hex" + "math" + "math/big" "testing" "github.com/libsv/go-bt/v2" @@ -33,7 +35,7 @@ func TestScriptNumBytes(t *testing.T) { t.Parallel() tests := []struct { - num scriptNum + num int64 serialised []byte }{ {0, nil}, @@ -81,7 +83,8 @@ func TestScriptNumBytes(t *testing.T) { } for _, test := range tests { - gotBytes := test.num.Bytes() + n := &scriptNumber{val: big.NewInt(test.num)} + gotBytes := n.Bytes() if !bytes.Equal(gotBytes, test.serialised) { t.Errorf("Bytes: did not get expected bytes for %d - "+ "got %x, want %x", test.num, gotBytes, @@ -103,7 +106,7 @@ func TestMakeScriptNum(t *testing.T) { tests := []struct { serialised []byte - num scriptNum + num int numLen int minimalEncoding bool err error @@ -197,20 +200,20 @@ func TestMakeScriptNum(t *testing.T) { {hexToBytes("0009000100"), 16779520, 5, false, nil}, } + var i int for _, test := range tests { + i++ // Ensure the error code is of the expected type and the error // code matches the value specified in the test instance. - //gotNum, err := makeScriptNum(test.serialised, test.minimalEncoding, test.numLen) - //gotNum, err := NewNumber(test.serialised, test.numLen, test.minimalEncoding, true) - gotNum, err := makeScriptNum(test.serialised, test.minimalEncoding, test.numLen) + gotNum, err := makeScriptNumber(test.serialised, test.numLen, test.minimalEncoding, true) if e := tstCheckScriptError(err, test.err); e != nil { t.Errorf("makeScriptNum(%#x): %v", test.serialised, e) continue } - if gotNum != test.num { + if gotNum.Int() != test.num { t.Errorf("makeScriptNum(%#x): did not get expected number - got %d, want %d", - test.serialised, gotNum, test.num) + test.serialised, gotNum.Int64(), test.num) continue } } @@ -222,7 +225,7 @@ func TestScriptNumInt32(t *testing.T) { t.Parallel() tests := []struct { - in scriptNum + in int64 want int32 }{ // Values inside the valid int32 range are just the values @@ -268,10 +271,71 @@ func TestScriptNumInt32(t *testing.T) { } for _, test := range tests { - got := test.in.Int32() + n := &scriptNumber{val: big.NewInt(test.in)} + got := n.Int32() if got != test.want { - t.Errorf("Int32: did not get expected value for %d - "+ - "got %d, want %d", test.in, got, test.want) + t.Errorf("Int32: did not get expected value for %d - got %d, want %d", test.in, got, test.want) + continue + } + } +} + +func TestScriptNumInt64(t *testing.T) { + t.Parallel() + + tests := []struct { + in *big.Int + want int64 + }{ + // Values inside the valid int64 range are just the values + // themselves cast to an int64. + {big.NewInt(0), 0}, + {big.NewInt(1), 1}, + {big.NewInt(-1), -1}, + {big.NewInt(127), 127}, + {big.NewInt(-127), -127}, + {big.NewInt(128), 128}, + {big.NewInt(-128), -128}, + {big.NewInt(129), 129}, + {big.NewInt(-129), -129}, + {big.NewInt(256), 256}, + {big.NewInt(-256), -256}, + {big.NewInt(32767), 32767}, + {big.NewInt(-32767), -32767}, + {big.NewInt(32768), 32768}, + {big.NewInt(-32768), -32768}, + {big.NewInt(65535), 65535}, + {big.NewInt(-65535), -65535}, + {big.NewInt(524288), 524288}, + {big.NewInt(-524288), -524288}, + {big.NewInt(7340032), 7340032}, + {big.NewInt(-7340032), -7340032}, + {big.NewInt(8388608), 8388608}, + {big.NewInt(-8388608), -8388608}, + {big.NewInt(2147483647), 2147483647}, + {big.NewInt(-2147483647), -2147483647}, + {big.NewInt(-2147483648), -2147483648}, + {big.NewInt(2147483648), 2147483648}, + {big.NewInt(-2147483649), -2147483649}, + {big.NewInt(1152921504606846975), 1152921504606846975}, + {big.NewInt(-1152921504606846975), -1152921504606846975}, + {big.NewInt(2305843009213693951), 2305843009213693951}, + {big.NewInt(-2305843009213693951), -2305843009213693951}, + {big.NewInt(4611686018427387903), 4611686018427387903}, + {big.NewInt(-4611686018427387903), -4611686018427387903}, + {big.NewInt(9223372036854775807), 9223372036854775807}, + {big.NewInt(-9223372036854775808), -9223372036854775808}, + + // Values outside of the valid int64 range are limited to int64. + {new(big.Int).Add(big.NewInt(9223372036854775807), big.NewInt(5)), math.MaxInt64}, + {new(big.Int).Sub(big.NewInt(-9223372036854775808), big.NewInt(600)), math.MinInt64}, + } + + for _, test := range tests { + n := &scriptNumber{val: test.in} + got := n.Int64() + if got != test.want { + t.Errorf("Int32: did not get expected value for %d - got %d, want %d", test.in, got, test.want) continue } } diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index 0235cfcc..54c7567b 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -366,7 +366,10 @@ func opcodePushData(op *ParsedOpcode, t *thread) error { // opcode1Negate pushes -1, encoded as a number, to the data stack. func opcode1Negate(op *ParsedOpcode, t *thread) error { - t.dstack.PushInt(-1) + t.dstack.PushInt(&scriptNumber{ + val: big.NewInt(-1), + afterGenesis: t.afterGenesis, + }) return nil } @@ -376,7 +379,7 @@ func opcode1Negate(op *ParsedOpcode, t *thread) error { func opcodeN(op *ParsedOpcode, t *thread) error { // The opcodes are all defined consecutively, so the numeric value is // the difference. - t.dstack.PushInt(scriptNum((op.op.val - (bscript.Op1 - 1)))) + t.dstack.PushByteArray([]byte{(op.op.val - (bscript.Op1 - 1))}) return nil } @@ -639,7 +642,7 @@ func opcodeCheckLockTimeVerify(op *ParsedOpcode, t *thread) error { if err != nil { return err } - lockTime, err := NewNumber(so, 5, t.dstack.verifyMinimalData, t.cfg.AfterGenesis()) + lockTime, err := makeScriptNumber(so, 5, t.dstack.verifyMinimalData, t.cfg.AfterGenesis()) if err != nil { return err } @@ -709,7 +712,7 @@ func opcodeCheckSequenceVerify(op *ParsedOpcode, t *thread) error { if err != nil { return err } - stackSequence, err := NewNumber(so, 5, t.dstack.verifyMinimalData, t.cfg.AfterGenesis()) + stackSequence, err := makeScriptNumber(so, 5, t.dstack.verifyMinimalData, t.cfg.AfterGenesis()) if err != nil { return err } @@ -852,7 +855,10 @@ func opcodeIfDup(op *ParsedOpcode, t *thread) error { // Example with 2 items: [x1 x2] -> [x1 x2 2] // Example with 3 items: [x1 x2 x3] -> [x1 x2 x3 3] func opcodeDepth(op *ParsedOpcode, t *thread) error { - t.dstack.PushInt(scriptNum(t.dstack.Depth())) + t.dstack.PushInt(&scriptNumber{ + val: big.NewInt(int64(t.dstack.Depth())), + afterGenesis: t.afterGenesis, + }) return nil } @@ -1013,7 +1019,7 @@ func opcodeNum2bin(op *ParsedOpcode, t *thread) error { // encode a as a script num so that we we take the bytes it // will be minimally encoded. - sn, err := NewNumber(a, len(a), false, t.afterGenesis) + sn, err := makeScriptNumber(a, len(a), false, t.afterGenesis) if err != nil { return err } @@ -1073,7 +1079,10 @@ func opcodeSize(op *ParsedOpcode, t *thread) error { return err } - t.dstack.PushInt(scriptNum(len(so))) + t.dstack.PushInt(&scriptNumber{ + val: big.NewInt(int64(len(so))), + afterGenesis: t.afterGenesis, + }) return nil } @@ -1218,7 +1227,7 @@ func opcode1Add(op *ParsedOpcode, t *thread) error { return err } - t.dstack.PushNumber(m.Incr()) + t.dstack.PushInt(m.Incr()) return nil } @@ -1232,7 +1241,7 @@ func opcode1Sub(op *ParsedOpcode, t *thread) error { return err } - t.dstack.PushNumber(m.Decr()) + t.dstack.PushInt(m.Decr()) return nil } @@ -1246,7 +1255,7 @@ func opcodeNegate(op *ParsedOpcode, t *thread) error { return err } - t.dstack.PushNumber(m.Neg()) + t.dstack.PushInt(m.Neg()) return nil } @@ -1260,7 +1269,7 @@ func opcodeAbs(op *ParsedOpcode, t *thread) error { return err } - t.dstack.PushNumber(m.Abs()) + t.dstack.PushInt(m.Abs()) return nil } @@ -1287,7 +1296,7 @@ func opcodeNot(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) @@ -1310,7 +1319,7 @@ func opcode0NotEqual(op *ParsedOpcode, t *thread) error { m.Set(1) } - t.dstack.PushNumber(m) + t.dstack.PushInt(m) return nil } @@ -1329,7 +1338,7 @@ func opcodeAdd(op *ParsedOpcode, t *thread) error { return err } - t.dstack.PushNumber(v0.Add(v1)) + t.dstack.PushInt(v0.Add(v1)) return nil } @@ -1349,7 +1358,7 @@ func opcodeSub(op *ParsedOpcode, t *thread) error { return err } - t.dstack.PushNumber(v1.Sub(v0)) + t.dstack.PushInt(v1.Sub(v0)) return nil } @@ -1367,7 +1376,7 @@ func opcodeMul(op *ParsedOpcode, t *thread) error { return err } - t.dstack.PushNumber(n1.Mul(n2)) + t.dstack.PushInt(n1.Mul(n2)) return nil } @@ -1390,7 +1399,7 @@ func opcodeDiv(op *ParsedOpcode, t *thread) error { return errs.NewError(errs.ErrDivideByZero, "divide by zero") } - t.dstack.PushNumber(a.Div(b)) + t.dstack.PushInt(a.Div(b)) return nil } @@ -1413,7 +1422,7 @@ func opcodeMod(op *ParsedOpcode, t *thread) error { return errs.NewError(errs.ErrDivideByZero, "mod by zero") } - t.dstack.PushNumber(a.Mod(b)) + t.dstack.PushInt(a.Mod(b)) return nil } @@ -1492,7 +1501,7 @@ func opcodeBoolAnd(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) @@ -1522,7 +1531,7 @@ func opcodeBoolOr(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) @@ -1550,7 +1559,7 @@ func opcodeNumEqual(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) @@ -1594,7 +1603,7 @@ func opcodeNumNotEqual(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) @@ -1622,7 +1631,7 @@ func opcodeLessThan(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) @@ -1650,7 +1659,7 @@ func opcodeGreaterThan(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) @@ -1678,7 +1687,7 @@ func opcodeLessThanOrEqual(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) @@ -1706,7 +1715,7 @@ func opcodeGreaterThanOrEqual(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) @@ -1733,7 +1742,7 @@ func opcodeMin(op *ParsedOpcode, t *thread) error { n = v1 } - t.dstack.PushNumber(n) + t.dstack.PushInt(n) return nil } @@ -1757,7 +1766,7 @@ func opcodeMax(op *ParsedOpcode, t *thread) error { n = v1 } - t.dstack.PushNumber(n) + t.dstack.PushInt(n) return nil } @@ -1790,7 +1799,7 @@ func opcodeWithin(op *ParsedOpcode, t *thread) error { n = 1 } - t.dstack.PushNumber(&Number{ + t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), afterGenesis: t.cfg.AfterGenesis(), }) diff --git a/bscript/interpreter/reference_test.go b/bscript/interpreter/reference_test.go index b4a206ac..59267169 100644 --- a/bscript/interpreter/reference_test.go +++ b/bscript/interpreter/reference_test.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io/ioutil" + "math/big" "strconv" "strings" "testing" @@ -92,7 +93,8 @@ func parseShortForm(script string) (*bscript.Script, error) { } else if num == -1 || (1 <= num && num <= 16) { scr.AppendOpCode((bscript.Op1 - 1) + byte(num)) } else { - scr.AppendPushData(scriptNum(num).Bytes()) + n := &scriptNumber{val: big.NewInt(num)} + scr.AppendPushData(n.Bytes()) } continue } else if bts, err := parseHex(tok); err == nil { diff --git a/bscript/interpreter/scriptnum.go b/bscript/interpreter/scriptnum.go deleted file mode 100644 index 596c8898..00000000 --- a/bscript/interpreter/scriptnum.go +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) 2015-2017 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package interpreter - -import ( - "github.com/libsv/go-bt/v2/bscript/interpreter/errs" -) - -const ( - maxInt32 = 1<<31 - 1 - minInt32 = -1 << 31 - - maxInt64 = 1<<63 - 1 - minInt64 = -1 << 63 -) - -// scriptNum represents a numeric value used in the scripting engine with -// special handling to deal with the subtle semantics required by consensus. -// -// All numbers are stored on the data and alternate stacks encoded as little -// endian with a sign bit. All numeric opcodes such as OP_ADD, OP_SUB, -// and OP_MUL, are only allowed to operate on 4-byte integers in the range -// [-2^31 + 1, 2^31 - 1], however the results of numeric operations may overflow -// and remain valid so long as they are not used as inputs to other numeric -// operations or otherwise interpreted as an integer. -// -// For example, it is possible for OP_ADD to have 2^31 - 1 for its two operands -// resulting 2^32 - 2, which overflows, but is still pushed to the stack as the -// result of the addition. That value can then be used as input to OP_VERIFY -// which will succeed because the data is being interpreted as a boolean. -// However, if that same value were to be used as input to another numeric -// opcode, such as OP_SUB, it must fail. -// -// This type handles the aforementioned requirements by storing all numeric -// operation results as an int64 to handle overflow and provides the Bytes -// method to get the serialised representation (including values that overflow). -// -// 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 parameters. -// Since all numeric opcodes involve pulling data from the stack and -// interpreting it as an integer, it provides the required behaviour. -type scriptNum int64 - -// checkMinimalDataEncoding returns whether the passed byte array adheres -// to the minimal encoding requirements. -func checkMinimalDataEncoding(v []byte) error { - if len(v) == 0 { - return nil - } - - // Check that the number is encoded with the minimum possible - // number of bytes. - // - // If the most-significant-byte - excluding the sign bit - is zero - // then we're not minimal. Note how this test also rejects the - // negative-zero encoding, [0x80]. - if v[len(v)-1]&0x7f == 0 { - // One exception: if there's more than one byte and the most - // significant bit of the second-most-significant-byte is set - // it would conflict with the sign bit. An example of this case - // is +-255, which encode to 0xff00 and 0xff80 respectively. - // (big-endian). - if len(v) == 1 || v[len(v)-2]&0x80 == 0 { - return errs.NewError(errs.ErrMinimalData, "numeric value encoded as %x is not minimally encoded", v) - } - } - - return nil -} - -// Bytes returns the number serialised as a little endian with a sign bit. -// -// Example encodings: -// 127 -> [0x7f] -// -127 -> [0xff] -// 128 -> [0x80 0x00] -// -128 -> [0x80 0x80] -// 129 -> [0x81 0x00] -// -129 -> [0x81 0x80] -// 256 -> [0x00 0x01] -// -256 -> [0x00 0x81] -// 32767 -> [0xff 0x7f] -// -32767 -> [0xff 0xff] -// 32768 -> [0x00 0x80 0x00] -// -32768 -> [0x00 0x80 0x80] -func (n scriptNum) Bytes() []byte { - // Zero encodes as an empty byte slice. - if n == 0 { - return nil - } - - // Take the absolute value and keep track of whether it was originally - // negative. - isNegative := n < 0 - if isNegative { - n = -n - } - - // Encode to little endian. The maximum number of encoded bytes is 9 - // (8 bytes for max int64 plus a potential byte for sign extension). - result := make([]byte, 0, 9) - for n > 0 { - result = append(result, byte(n&0xff)) - n >>= 8 - } - - // When the most significant byte already has the high bit set, an - // additional high byte is required to indicate whether the number is - // negative or positive. The additional byte is removed when converting - // back to an integral and its high bit is used to denote the sign. - // - // Otherwise, when the most significant byte does not already have the - // high bit set, use it to indicate the value is negative, if needed. - if result[len(result)-1]&0x80 != 0 { - extraByte := byte(0x00) - if isNegative { - extraByte = 0x80 - } - result = append(result, extraByte) - - } else if isNegative { - result[len(result)-1] |= 0x80 - } - - return result -} - -// Int32 returns the script number clamped to a valid int32. That is to say -// when the script number is higher than the max allowed int32, the max int32 -// value is returned and vice versa for the minimum value. Note that this -// behaviour is different from a simple int32 cast because that truncates -// and the consensus rules dictate numbers which are directly cast to integers -// provide this behaviour. -// -// 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 behaviour. -func (n scriptNum) Int32() int32 { - if n > maxInt32 { - return maxInt32 - } - - if n < minInt32 { - return minInt32 - } - - return int32(n) -} - -func (n scriptNum) Int64() int64 { - if n > maxInt64 { - return maxInt64 - } - if n < minInt64 { - return minInt64 - } - return int64(n) -} - -// makeScriptNum interprets the passed serialised bytes as an encoded integer -// and returns the result as a script number. -// -// Since the consensus rules dictate that serialised bytes interpreted as integers -// 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 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 -// number of bytes or is the negative 0 encoding, [0x80]. For example, consider -// the number 127. It could be encoded as [0x7f], [0x7f 0x00], -// [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 errs.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, scriptNumLen int) (scriptNum, error) { - // Interpreting data requires that it is not larger than - // the passed scriptNumLen value. - if len(v) > scriptNumLen { - return 0, errs.NewError( - errs.ErrNumberTooBig, - "numeric value encoded as %x is %d bytes which exceeds the max allowed of %d", - v, len(v), scriptNumLen, - ) - } - - // Enforce minimal encoded if requested. - if requireMinimal { - if err := checkMinimalDataEncoding(v); err != nil { - return 0, err - } - } - - // Zero is encoded as an empty byte slice. - if len(v) == 0 { - return 0, nil - } - - // Decode from little endian. - var result int64 - for i, val := range v { - result |= int64(val) << uint8(8*i) - } - - // When the most significant byte of the input bytes has the sign bit - // set, the result is negative. So, remove the sign bit from the result - // and make it negative. - if v[len(v)-1]&0x80 != 0 { - // The maximum length of v has already been determined to be 4 - // above, so uint8 is enough to cover the max possible shift - // value of 24. - result &= ^(int64(0x80) << uint8(8*(len(v)-1))) - return scriptNum(-result), nil - } - - return scriptNum(result), nil -} - -func minimallyEncode(data []byte) []byte { - if len(data) == 0 { - return data - } - - last := data[len(data)-1] - if last&0x7f != 0 { - return data - } - - if len(data) == 1 { - return []byte{} - } - - if data[len(data)-2]&0x80 != 0 { - return data - } - - for i := len(data) - 1; i > 0; i-- { - if data[i-1] != 0 { - if data[i-1]&0x80 != 0 { - data[i] = last - i++ - } else { - data[i-1] |= last - } - - return data[:i] - } - } - - return []byte{} -} diff --git a/bscript/interpreter/stack.go b/bscript/interpreter/stack.go index 513708a1..3cbca350 100644 --- a/bscript/interpreter/stack.go +++ b/bscript/interpreter/stack.go @@ -69,15 +69,11 @@ func (s *stack) PushByteArray(so []byte) { s.stk = append(s.stk, so) } -// PushInt converts the provided scriptNum to a suitable byte array then pushes +// PushInt converts the provided Number to a suitable byte array then pushes // it onto the top of the stack. // // Stack transformation: [... x1 x2] -> [... x1 x2 int] -func (s *stack) PushInt(val scriptNum) { - s.PushByteArray(val.Bytes()) -} - -func (s *stack) PushNumber(n *Number) { +func (s *stack) PushInt(n *scriptNumber) { s.PushByteArray(n.Bytes()) } @@ -107,13 +103,13 @@ func (s *stack) PopByteArray() ([]byte, error) { // consensus rules imposed on data interpreted as numbers. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2] -func (s *stack) PopInt() (*Number, error) { +func (s *stack) PopInt() (*scriptNumber, error) { so, err := s.PopByteArray() if err != nil { return nil, err } - return NewNumber(so, s.maxNumLength, s.verifyMinimalData, s.afterGenesis) + return makeScriptNumber(so, s.maxNumLength, s.verifyMinimalData, s.afterGenesis) } // PopBool pops the value off the top of the stack, converts it into a bool, and @@ -142,13 +138,13 @@ func (s *stack) PeekByteArray(idx int32) ([]byte, error) { // PeekInt returns the Nth item on the stack as a script num without removing // it. The act of converting to a script num enforces the consensus rules // imposed on data interpreted as numbers. -func (s *stack) PeekInt(idx int32) (*Number, error) { +func (s *stack) PeekInt(idx int32) (*scriptNumber, error) { so, err := s.PeekByteArray(idx) if err != nil { return nil, err } - return NewNumber(so, s.maxNumLength, s.verifyMinimalData, s.afterGenesis) + return makeScriptNumber(so, s.maxNumLength, s.verifyMinimalData, s.afterGenesis) } // PeekBool returns the Nth item on the stack as a bool without removing it. diff --git a/bscript/interpreter/stack_test.go b/bscript/interpreter/stack_test.go index d347e472..08ed0124 100644 --- a/bscript/interpreter/stack_test.go +++ b/bscript/interpreter/stack_test.go @@ -8,6 +8,7 @@ import ( "bytes" "errors" "fmt" + "math/big" "reflect" "testing" @@ -318,7 +319,7 @@ func TestStack(t *testing.T) { "PushInt 0", nil, func(s *stack) error { - s.PushInt(scriptNum(0)) + s.PushInt(&scriptNumber{val: big.NewInt(0)}) return nil }, nil, @@ -328,7 +329,7 @@ func TestStack(t *testing.T) { "PushInt 1", nil, func(s *stack) error { - s.PushInt(scriptNum(1)) + s.PushInt(&scriptNumber{val: big.NewInt(1)}) return nil }, nil, @@ -338,7 +339,7 @@ func TestStack(t *testing.T) { "PushInt -1", nil, func(s *stack) error { - s.PushInt(scriptNum(-1)) + s.PushInt(&scriptNumber{val: big.NewInt(-1)}) return nil }, nil, @@ -348,7 +349,7 @@ func TestStack(t *testing.T) { "PushInt two bytes", nil, func(s *stack) error { - s.PushInt(scriptNum(256)) + s.PushInt(&scriptNumber{val: big.NewInt(256)}) return nil }, nil, @@ -360,7 +361,7 @@ func TestStack(t *testing.T) { nil, func(s *stack) error { // this will have the highbit set - s.PushInt(scriptNum(128)) + s.PushInt(&scriptNumber{val: big.NewInt(128)}) return nil }, nil, @@ -482,7 +483,7 @@ func TestStack(t *testing.T) { "PushInt PopBool", nil, func(s *stack) error { - s.PushInt(scriptNum(1)) + s.PushInt(&scriptNumber{val: big.NewInt(1)}) val, err := s.PopBool() if err != nil { return err @@ -500,7 +501,7 @@ func TestStack(t *testing.T) { "PushInt PopBool 2", nil, func(s *stack) error { - s.PushInt(scriptNum(0)) + s.PushInt(&scriptNumber{val: big.NewInt(0)}) val, err := s.PopBool() if err != nil { return err @@ -870,7 +871,7 @@ func TestStack(t *testing.T) { "pop number", nil, func(s *stack) error { - s.PushInt(scriptNum(1)) + s.PushInt(&scriptNumber{val: big.NewInt(1)}) // Peek int is otherwise pretty well tested, // just check it works. val, err := s.PopInt() From 4b3a2aee72e6e93a379114233056cab4c9374d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Mon, 15 Nov 2021 19:16:58 +0000 Subject: [PATCH 09/14] use afterGenesis flag --- bscript/interpreter/operations.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index 54c7567b..a2864be5 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -642,7 +642,7 @@ func opcodeCheckLockTimeVerify(op *ParsedOpcode, t *thread) error { if err != nil { return err } - lockTime, err := makeScriptNumber(so, 5, t.dstack.verifyMinimalData, t.cfg.AfterGenesis()) + lockTime, err := makeScriptNumber(so, 5, t.dstack.verifyMinimalData, t.afterGenesis) if err != nil { return err } @@ -712,7 +712,7 @@ func opcodeCheckSequenceVerify(op *ParsedOpcode, t *thread) error { if err != nil { return err } - stackSequence, err := makeScriptNumber(so, 5, t.dstack.verifyMinimalData, t.cfg.AfterGenesis()) + stackSequence, err := makeScriptNumber(so, 5, t.dstack.verifyMinimalData, t.afterGenesis) if err != nil { return err } @@ -1298,7 +1298,7 @@ func opcodeNot(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } @@ -1503,7 +1503,7 @@ func opcodeBoolAnd(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } @@ -1533,7 +1533,7 @@ func opcodeBoolOr(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } @@ -1561,7 +1561,7 @@ func opcodeNumEqual(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } @@ -1605,7 +1605,7 @@ func opcodeNumNotEqual(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } @@ -1633,7 +1633,7 @@ func opcodeLessThan(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } @@ -1661,7 +1661,7 @@ func opcodeGreaterThan(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } @@ -1689,7 +1689,7 @@ func opcodeLessThanOrEqual(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } @@ -1717,7 +1717,7 @@ func opcodeGreaterThanOrEqual(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } @@ -1801,7 +1801,7 @@ func opcodeWithin(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(&scriptNumber{ val: big.NewInt(n), - afterGenesis: t.cfg.AfterGenesis(), + afterGenesis: t.afterGenesis, }) return nil } From e6ad6904479fa58223ad8db6eab3ca0ba19aa3ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Mon, 15 Nov 2021 19:19:56 +0000 Subject: [PATCH 10/14] more cleanups --- bscript/interpreter/number_test.go | 2 +- bscript/interpreter/stack.go | 4 ++-- bscript/interpreter/stack_test.go | 30 +++++++++++++++--------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/bscript/interpreter/number_test.go b/bscript/interpreter/number_test.go index 4e395bb8..e636c638 100644 --- a/bscript/interpreter/number_test.go +++ b/bscript/interpreter/number_test.go @@ -335,7 +335,7 @@ func TestScriptNumInt64(t *testing.T) { n := &scriptNumber{val: test.in} got := n.Int64() if got != test.want { - t.Errorf("Int32: did not get expected value for %d - got %d, want %d", test.in, got, test.want) + t.Errorf("Int64: did not get expected value for %d - got %d, want %d", test.in, got, test.want) continue } } diff --git a/bscript/interpreter/stack.go b/bscript/interpreter/stack.go index 3cbca350..7560c41f 100644 --- a/bscript/interpreter/stack.go +++ b/bscript/interpreter/stack.go @@ -69,7 +69,7 @@ func (s *stack) PushByteArray(so []byte) { s.stk = append(s.stk, so) } -// PushInt converts the provided Number to a suitable byte array then pushes +// PushInt converts the provided scriptNumber to a suitable byte array then pushes // it onto the top of the stack. // // Stack transformation: [... x1 x2] -> [... x1 x2 int] @@ -98,7 +98,7 @@ func (s *stack) PopByteArray() ([]byte, error) { return data, nil } -// PopInt pops the value off the top of the stack, converts it into a Number, +// PopInt pops the value off the top of the stack, converts it into a scriptNumber, // and returns it. The act of converting to a script num enforces the // consensus rules imposed on data interpreted as numbers. // diff --git a/bscript/interpreter/stack_test.go b/bscript/interpreter/stack_test.go index 08ed0124..81d11060 100644 --- a/bscript/interpreter/stack_test.go +++ b/bscript/interpreter/stack_test.go @@ -186,7 +186,7 @@ func TestStack(t *testing.T) { nil, }, { - "popNumber 0", + "popInt 0", [][]byte{{0x0}}, func(s *stack) error { v, err := s.PopInt() @@ -194,7 +194,7 @@ func TestStack(t *testing.T) { return err } if v.Int() != 0 { - return errors.New("0 != 0 on popNumber") + return errors.New("0 != 0 on popInt") } return nil }, @@ -202,7 +202,7 @@ func TestStack(t *testing.T) { nil, }, { - "popNumber -0", + "popInt -0", [][]byte{{0x80}}, func(s *stack) error { v, err := s.PopInt() @@ -210,7 +210,7 @@ func TestStack(t *testing.T) { return err } if v.Int() != 0 { - return errors.New("-0 != 0 on popNumber") + return errors.New("-0 != 0 on popInt") } return nil }, @@ -218,7 +218,7 @@ func TestStack(t *testing.T) { nil, }, { - "popNumber 1", + "popInt 1", [][]byte{{0x01}}, func(s *stack) error { v, err := s.PopInt() @@ -226,7 +226,7 @@ func TestStack(t *testing.T) { return err } if v.Int() != 1 { - return errors.New("1 != 1 on popNumber") + return errors.New("1 != 1 on popInt") } return nil }, @@ -234,7 +234,7 @@ func TestStack(t *testing.T) { nil, }, { - "popNumber 1 leading 0", + "popInt 1 leading 0", [][]byte{{0x01, 0x00, 0x00, 0x00}}, func(s *stack) error { v, err := s.PopInt() @@ -242,7 +242,7 @@ func TestStack(t *testing.T) { return err } if v.Int() != 1 { - return errors.New("1 != 1 on popNumber") + return errors.New("1 != 1 on popInt") } return nil }, @@ -250,7 +250,7 @@ func TestStack(t *testing.T) { nil, }, { - "popNumber -1", + "popInt -1", [][]byte{{0x81}}, func(s *stack) error { v, err := s.PopInt() @@ -258,7 +258,7 @@ func TestStack(t *testing.T) { return err } if v.Int() != -1 { - return errors.New("-1 != -1 on popNumber") + return errors.New("-1 != -1 on popInt") } return nil }, @@ -266,7 +266,7 @@ func TestStack(t *testing.T) { nil, }, { - "popNumber -1 leading 0", + "popInt -1 leading 0", [][]byte{{0x01, 0x00, 0x00, 0x80}}, func(s *stack) error { v, err := s.PopInt() @@ -274,7 +274,7 @@ func TestStack(t *testing.T) { return err } if v.Int() != -1 { - return errors.New("-1 != -1 on popNumber") + return errors.New("-1 != -1 on popInt") } return nil }, @@ -283,7 +283,7 @@ func TestStack(t *testing.T) { }, // Triggers the multibyte case in asInt { - "popNumber -513", + "popInt -513", [][]byte{{0x1, 0x82}}, func(s *stack) error { v, err := s.PopInt() @@ -291,7 +291,7 @@ func TestStack(t *testing.T) { return err } if v.Int() != -513 { - return errors.New("-513 != -513 on popNumber") + return errors.New("-513 != -513 on popInt") } return nil }, @@ -868,7 +868,7 @@ func TestStack(t *testing.T) { [][]byte{{0}}, }, { - "pop number", + "pop int", nil, func(s *stack) error { s.PushInt(&scriptNumber{val: big.NewInt(1)}) From 124bc82f1bf1b09c929e814a594419b9c7ef2842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Mon, 15 Nov 2021 19:21:47 +0000 Subject: [PATCH 11/14] purged all refs to makeScriptNum --- bscript/interpreter/number.go | 4 ++-- bscript/interpreter/number_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bscript/interpreter/number.go b/bscript/interpreter/number.go index 772ad7ec..62a4bd92 100644 --- a/bscript/interpreter/number.go +++ b/bscript/interpreter/number.go @@ -239,7 +239,7 @@ func (n *scriptNumber) Int() int { // provide this behaviour. // // In practice, for most opcodes, the number should never be out of range since -// it will have been created with makeScriptNum using the defaultScriptLen +// it will have been created with makeScriptNumber 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 @@ -263,7 +263,7 @@ func (n *scriptNumber) Int32() int32 { // provide this behaviour. // // In practice, for most opcodes, the number should never be out of range since -// it will have been created with makeScriptNum using the defaultScriptLen +// it will have been created with makeScriptNumber 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 diff --git a/bscript/interpreter/number_test.go b/bscript/interpreter/number_test.go index e636c638..551e55bf 100644 --- a/bscript/interpreter/number_test.go +++ b/bscript/interpreter/number_test.go @@ -207,12 +207,12 @@ func TestMakeScriptNum(t *testing.T) { // code matches the value specified in the test instance. gotNum, err := makeScriptNumber(test.serialised, test.numLen, test.minimalEncoding, true) if e := tstCheckScriptError(err, test.err); e != nil { - t.Errorf("makeScriptNum(%#x): %v", test.serialised, e) + t.Errorf("makeScriptNumber(%#x): %v", test.serialised, e) continue } if gotNum.Int() != test.num { - t.Errorf("makeScriptNum(%#x): did not get expected number - got %d, want %d", + t.Errorf("makeScriptNumber(%#x): did not get expected number - got %d, want %d", test.serialised, gotNum.Int64(), test.num) continue } From 35c55eebaa95d9e6876fe29b4eba121a168a70d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Mon, 15 Nov 2021 19:26:12 +0000 Subject: [PATCH 12/14] even more cleanups --- bscript/interpreter/number_test.go | 19 ++++++------------- bscript/interpreter/operations.go | 8 ++++---- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/bscript/interpreter/number_test.go b/bscript/interpreter/number_test.go index 551e55bf..6b4b2375 100644 --- a/bscript/interpreter/number_test.go +++ b/bscript/interpreter/number_test.go @@ -84,11 +84,8 @@ func TestScriptNumBytes(t *testing.T) { for _, test := range tests { n := &scriptNumber{val: big.NewInt(test.num)} - gotBytes := n.Bytes() - if !bytes.Equal(gotBytes, test.serialised) { - t.Errorf("Bytes: did not get expected bytes for %d - "+ - "got %x, want %x", test.num, gotBytes, - test.serialised) + if !bytes.Equal(n.Bytes(), test.serialised) { + t.Errorf("Bytes: did not get expected bytes for %d - got %x, want %x", test.num, n.Bytes(), test.serialised) continue } } @@ -200,9 +197,7 @@ func TestMakeScriptNum(t *testing.T) { {hexToBytes("0009000100"), 16779520, 5, false, nil}, } - var i int for _, test := range tests { - i++ // Ensure the error code is of the expected type and the error // code matches the value specified in the test instance. gotNum, err := makeScriptNumber(test.serialised, test.numLen, test.minimalEncoding, true) @@ -272,9 +267,8 @@ func TestScriptNumInt32(t *testing.T) { for _, test := range tests { n := &scriptNumber{val: big.NewInt(test.in)} - got := n.Int32() - if got != test.want { - t.Errorf("Int32: did not get expected value for %d - got %d, want %d", test.in, got, test.want) + if n.Int32() != test.want { + t.Errorf("Int32: did not get expected value for %d - got %d, want %d", test.in, n.Int32(), test.want) continue } } @@ -333,9 +327,8 @@ func TestScriptNumInt64(t *testing.T) { for _, test := range tests { n := &scriptNumber{val: test.in} - got := n.Int64() - if got != test.want { - t.Errorf("Int64: did not get expected value for %d - got %d, want %d", test.in, got, test.want) + if n.Int64() != test.want { + t.Errorf("Int64: did not get expected value for %d - got %d, want %d", test.in, n.Int64(), test.want) continue } } diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index a2864be5..487e25ce 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -651,14 +651,14 @@ func opcodeCheckLockTimeVerify(op *ParsedOpcode, t *thread) error { // arithmetic being done first, you can always use // 0 bscript.OpMAX bscript.OpCHECKLOCKTIMEVERIFY. if lockTime.LessThanInt(0) { - return errs.NewError(errs.ErrNegativeLockTime, "negative lock time: %d", lockTime.val.Int64()) + return errs.NewError(errs.ErrNegativeLockTime, "negative lock time: %d", lockTime.Int64()) } // The lock time field of a transaction is either a block height at // which the transaction is finalised or a timestamp depending on if the // value is before the interpreter.LockTimeThreshold. When it is under the // threshold it is a block height. - if err = verifyLockTime(int64(t.tx.LockTime), LockTimeThreshold, lockTime.val.Int64()); err != nil { + if err = verifyLockTime(int64(t.tx.LockTime), LockTimeThreshold, lockTime.Int64()); err != nil { return err } @@ -721,10 +721,10 @@ func opcodeCheckSequenceVerify(op *ParsedOpcode, t *thread) error { // arithmetic being done first, you can always use // 0 bscript.OpMAX bscript.OpCHECKSEQUENCEVERIFY. if stackSequence.LessThanInt(0) { - return errs.NewError(errs.ErrNegativeLockTime, "negative sequence: %d", stackSequence.val.Int64()) + return errs.NewError(errs.ErrNegativeLockTime, "negative sequence: %d", stackSequence.Int64()) } - sequence := stackSequence.val.Int64() + sequence := stackSequence.Int64() // To provide for future soft-fork extensibility, if the // operand has the disabled lock-time flag set, From 65222177aea249095339d71e01835f37c9b14258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Mon, 15 Nov 2021 20:59:01 +0000 Subject: [PATCH 13/14] shortened some code --- bscript/interpreter/number.go | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/bscript/interpreter/number.go b/bscript/interpreter/number.go index 62a4bd92..8e817851 100644 --- a/bscript/interpreter/number.go +++ b/bscript/interpreter/number.go @@ -120,36 +120,31 @@ func makeScriptNumber(bb []byte, scriptNumLen int, requireMinimal, afterGenesis // Add adds the receiver and the number, sets the result over the receiver and returns. func (n *scriptNumber) Add(o *scriptNumber) *scriptNumber { - result := n.val.Add(n.val, o.val) - *n.val = *result + *n.val = *new(big.Int).Add(n.val, o.val) return n } // Sub subtracts the number from the receiver, sets the result over the receiver and returns. func (n *scriptNumber) Sub(o *scriptNumber) *scriptNumber { - result := n.val.Sub(n.val, o.val) - *n.val = *result + *n.val = *new(big.Int).Sub(n.val, o.val) return n } // Mul multiplies the receiver by the number, sets the result over the receiver and returns. func (n *scriptNumber) Mul(o *scriptNumber) *scriptNumber { - result := n.val.Mul(n.val, o.val) - *n.val = *result + *n.val = *new(big.Int).Mul(n.val, o.val) return n } // Div divides the receiver by the number, sets the result over the receiver and returns. func (n *scriptNumber) Div(o *scriptNumber) *scriptNumber { - result := n.val.Quo(n.val, o.val) - *n.val = *result + *n.val = *new(big.Int).Quo(n.val, o.val) return n } // Mod divides the receiver by the number, sets the remainder over the receiver and returns. func (n *scriptNumber) Mod(o *scriptNumber) *scriptNumber { - result := n.val.Rem(n.val, o.val) - *n.val = *result + *n.val = *new(big.Int).Rem(n.val, o.val) return n } @@ -200,29 +195,25 @@ func (n *scriptNumber) IsZero() bool { // Incr increment the receiver by one. func (n *scriptNumber) Incr() *scriptNumber { - result := n.val.Add(n.val, one) - *n.val = *result + *n.val = *new(big.Int).Add(n.val, one) return n } // Decr decrement the receiver by one. func (n *scriptNumber) Decr() *scriptNumber { - result := n.val.Sub(n.val, one) - *n.val = *result + *n.val = *new(big.Int).Sub(n.val, one) return n } // Neg sets the receiver to the negative of the receiver. func (n *scriptNumber) Neg() *scriptNumber { - result := n.val.Neg(n.val) - *n.val = *result + *n.val = *new(big.Int).Neg(n.val) return n } // Abs sets the receiver to the absolute value of hte receiver. func (n *scriptNumber) Abs() *scriptNumber { - result := n.val.Abs(n.val) - *n.val = *result + *n.val = *new(big.Int).Abs(n.val) return n } @@ -280,8 +271,7 @@ func (n *scriptNumber) Int64() int64 { // Set the value of the receiver. func (n *scriptNumber) Set(i int64) *scriptNumber { - val := big.NewInt(i) - *n.val = *val + *n.val = *new(big.Int).SetInt64(i) return n } From 7a9c1d17822129d3bb81999dbdf688deb29a6ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Mon, 15 Nov 2021 21:10:54 +0000 Subject: [PATCH 14/14] made the shifts on big.Int clearer with comments --- bscript/interpreter/number.go | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/bscript/interpreter/number.go b/bscript/interpreter/number.go index 8e817851..a651c2d9 100644 --- a/bscript/interpreter/number.go +++ b/bscript/interpreter/number.go @@ -96,6 +96,12 @@ func makeScriptNumber(bb []byte, scriptNumLen int, requireMinimal, afterGenesis } // Decode from little endian. + // + // The following is the equivalent of: + // var v int64 + // for i, b := range bb { + // v |= int64(b) << uint8(8*i) + // } v := new(big.Int) for i, b := range bb { v.Or(v, new(big.Int).Lsh(new(big.Int).SetBytes([]byte{b}), uint(8*i))) @@ -104,6 +110,12 @@ func makeScriptNumber(bb []byte, scriptNumLen int, requireMinimal, afterGenesis // When the most significant byte of the input bytes has the sign bit // set, the result is negative. So, remove the sign bit from the result // and make it negative. + // + // The following is the equivalent of: + // if bb[len(bb)-1]&0x80 != 0 { + // v &= ^(int64(0x80) << uint8(8*(len(bb)-1))) + // return -v, nil + // } if bb[len(bb)-1]&0x80 != 0 { // The maximum length of bb has already been determined to be 4 // above, so uint8 is enough to cover the max possible shift @@ -316,12 +328,19 @@ func (n *scriptNumber) Bytes() []byte { bb = n.val.Bytes() } - // Encode to little endian. The maximum number of encoded bytes is 9 + // Encode to little endian. The maximum number of encoded bytes is len(bb)+1 // (8 bytes for max int64 plus a potential byte for sign extension). - tmp := make([]byte, 0, len(bb)+1) + // + // The following is the equivalent of: + // result := make([]byte, 0, len(bb)+1) + // for n > 0 { + // result = append(result, byte(n&0xff)) + // n >>= 8 + // } + result := make([]byte, 0, len(bb)+1) cpy := new(big.Int).SetBytes(n.val.Bytes()) for cpy.Cmp(zero) == 1 { - tmp = append(tmp, byte(cpy.Int64()&0xff)) + result = append(result, byte(cpy.Int64()&0xff)) cpy.Rsh(cpy, 8) } @@ -332,17 +351,17 @@ func (n *scriptNumber) Bytes() []byte { // // Otherwise, when the most significant byte does not already have the // high bit set, use it to indicate the value is negative, if needed. - if tmp[len(tmp)-1]&0x80 != 0 { + if result[len(result)-1]&0x80 != 0 { extraByte := byte(0x00) if isNegative { extraByte = 0x80 } - tmp = append(tmp, extraByte) + result = append(result, extraByte) } else if isNegative { - tmp[len(tmp)-1] |= 0x80 + result[len(result)-1] |= 0x80 } - return tmp + return result } func minimallyEncode(data []byte) []byte {