Class: BTC::ScriptNumber

Inherits:
Object
  • Object
show all
Defined in:
lib/btcruby/script/script_number.rb

Constant Summary collapse

DEFAULT_MAX_SIZE =
4
INT64_MAX =
0x7fffffffffffffff
INT64_MIN =
- 1

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(integer: nil, boolean: nil, data: nil, hex: nil, require_minimal: true, max_size: DEFAULT_MAX_SIZE) ⇒ ScriptNumber

Returns a new instance of ScriptNumber.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/btcruby/script/script_number.rb', line 17

def initialize(integer: nil, boolean: nil, data: nil, hex: nil, require_minimal: true, max_size: DEFAULT_MAX_SIZE)
  if integer
    assert(integer >= INT64_MIN && integer <= INT64_MAX, "Integer must be within int64 range")
    @integer = integer
  elsif boolean == false || boolean == true
    @integer = boolean ? 1 : 0
  elsif data || hex
    data ||= BTC.from_hex(hex)
    if data.bytesize > max_size
      raise ScriptNumberError, "script number overflow (#{data.bytesize} > #{max_size})"
    end
    if require_minimal && data.bytesize > 0
      # 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 (data.bytes.last & 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 data.bytesize <= 1 || (data.bytes[data.bytesize - 2] & 0x80) == 0
          raise ScriptNumberError, "non-minimally encoded script number"
        end
      end
    end
    @integer = self.class.decode_integer(data)
  else
    raise ArgumentError
  end
end

Class Method Details

.decode_integer(data) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/btcruby/script/script_number.rb', line 124

def self.decode_integer(data)
  return 0 if data.empty?

  result = 0

  bytes = data.bytes
  bytes.each_with_index do |byte, i|
    result |= bytes[i] << 8*i
  end

  # If the input vector's most significant byte is 0x80, remove it from
  # the result's msb and return a negative.
  if (bytes.last & 0x80) != 0
    return -(result & ~(0x80 << (8 * (bytes.size - 1))));
  end
  return result
end

.encode_integer(value) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/btcruby/script/script_number.rb', line 142

def self.encode_integer(value)
  return "".b if value == 0

  result = []
  negative = value < 0
  absvalue = negative ? -value : value

  while absvalue != 0
    result.push(absvalue & 0xff)
    absvalue >>= 8
  end

  # - If the most significant byte is >= 0x80 and the value is positive, push a
  # new zero-byte to make the significant byte < 0x80 again.
  #
  # - If the most significant byte is >= 0x80 and the value is negative, push a
  # new 0x80 byte that will be popped off when converting to an integral.
  #
  # - If the most significant byte is < 0x80 and the value is negative, add
  # 0x80 to it, since it will be subtracted and interpreted as a negative when
  # converting to an integral.

  if (result.last & 0x80) != 0
    result.push(negative ? 0x80 : 0)
  elsif negative
    result[result.size - 1] = result.last | 0x80
  end

  BTC.data_from_bytes(result)
end

Instance Method Details

#!=(other) ⇒ Object



55
# File 'lib/btcruby/script/script_number.rb', line 55

def !=(other); @integer != other.to_i; end

#*(other) ⇒ Object



73
74
75
76
# File 'lib/btcruby/script/script_number.rb', line 73

def *(other)
  # TODO: add asserts to check that result fits within int64 limits
  self.class.new(integer: @integer * other.to_i)
end

#+(other) ⇒ Object



61
# File 'lib/btcruby/script/script_number.rb', line 61

def +(other); self.class.new(integer: @integer + other.to_i); end

#+@Object



64
65
66
# File 'lib/btcruby/script/script_number.rb', line 64

def +@
  self
end

#-(other) ⇒ Object



62
# File 'lib/btcruby/script/script_number.rb', line 62

def -(other); self.class.new(integer: @integer - other.to_i); end

#-@Object



68
69
70
71
# File 'lib/btcruby/script/script_number.rb', line 68

def -@
  assert(@integer > INT64_MIN && @integer <= INT64_MAX, "Integer will not be within int64 range after negation")
  self.class.new(integer: -@integer)
end

#/(other) ⇒ Object



78
79
80
81
# File 'lib/btcruby/script/script_number.rb', line 78

def /(other)
  # TODO: add asserts to check that result fits within int64 limits
  self.class.new(integer: @integer / other.to_i)
end

#<(other) ⇒ Object



57
# File 'lib/btcruby/script/script_number.rb', line 57

def  <(other); @integer  < other.to_i; end

#<<(other) ⇒ Object



83
84
85
86
87
88
89
90
91
92
# File 'lib/btcruby/script/script_number.rb', line 83

def <<(other)
  # TODO: add asserts to check that result fits within int64 limits
  assert(other >= 0, "Shift amount must not be negative")
  a = @integer.abs
  r = a << other.to_i
  if a != @integer
    r = -r
  end
  self.class.new(integer: r)
end

#<=(other) ⇒ Object



56
# File 'lib/btcruby/script/script_number.rb', line 56

def <=(other); @integer <= other.to_i; end

#==(other) ⇒ Object

Operators



54
# File 'lib/btcruby/script/script_number.rb', line 54

def ==(other); @integer == other.to_i; end

#>(other) ⇒ Object



59
# File 'lib/btcruby/script/script_number.rb', line 59

def  >(other); @integer  > other.to_i; end

#>=(other) ⇒ Object



58
# File 'lib/btcruby/script/script_number.rb', line 58

def >=(other); @integer >= other.to_i; end

#>>(other) ⇒ Object



94
95
96
97
98
99
100
101
102
103
# File 'lib/btcruby/script/script_number.rb', line 94

def >>(other)
  # TODO: add asserts to check that result fits within int64 limits
  assert(other >= 0, "Shift amount must not be negative")
  a = @integer.abs
  r = a >> other.to_i
  if a != @integer
    r = -r
  end
  self.class.new(integer: r)
end

#assert(condition, message) ⇒ Object



173
174
175
# File 'lib/btcruby/script/script_number.rb', line 173

def assert(condition, message)
  raise message if !condition
end

#dataObject



116
117
118
# File 'lib/btcruby/script/script_number.rb', line 116

def data
  self.class.encode_integer(@integer)
end

#to_hexObject



120
121
122
# File 'lib/btcruby/script/script_number.rb', line 120

def to_hex
  BTC.to_hex(data)
end

#to_iObject

Conversion Methods



108
109
110
# File 'lib/btcruby/script/script_number.rb', line 108

def to_i
  @integer
end

#to_sObject



112
113
114
# File 'lib/btcruby/script/script_number.rb', line 112

def to_s
  @integer.to_s
end