btcruby 1.0.9 → 1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,254 @@
1
+ module BTC
2
+ # (Based on CScriptNum)
3
+ # Numeric opcodes (OP_1ADD, etc) are restricted to operating on 4-byte integers.
4
+ # The semantics are subtle, though: operands must be in the range [-2^31 +1...2^31 -1],
5
+ # but results may overflow (and are valid as long as they are not used in a subsequent
6
+ # numeric operation). ScriptNumber enforces those semantics by storing results as
7
+ # an int64 and allowing out-of-range values to be returned as a vector of bytes but
8
+ # throwing an exception if arithmetic is done or the result is interpreted as an integer.
9
+ class ScriptNumberError < ArgumentError; end
10
+
11
+ class ScriptNumber
12
+ DEFAULT_MAX_SIZE = 4
13
+
14
+ INT64_MAX = 0x7fffffffffffffff
15
+ INT64_MIN = -INT64_MAX - 1
16
+
17
+ def initialize(integer: nil, boolean: nil, data: nil, hex: nil, require_minimal: true, max_size: DEFAULT_MAX_SIZE)
18
+ if integer
19
+ assert(integer >= INT64_MIN && integer <= INT64_MAX, "Integer must be within int64 range")
20
+ @integer = integer
21
+ elsif data || hex
22
+ data ||= BTC.from_hex(hex)
23
+ if data.bytesize > max_size
24
+ raise ScriptNumberError, "script number overflow (#{data.bytesize} > #{max_size})"
25
+ end
26
+ if require_minimal && data.bytesize > 0
27
+ # Check that the number is encoded with the minimum possible
28
+ # number of bytes.
29
+ #
30
+ # If the most-significant-byte - excluding the sign bit - is zero
31
+ # then we're not minimal. Note how this test also rejects the
32
+ # negative-zero encoding, 0x80.
33
+ if (data.bytes.last & 0x7f) == 0
34
+ # One exception: if there's more than one byte and the most
35
+ # significant bit of the second-most-significant-byte is set
36
+ # it would conflict with the sign bit. An example of this case
37
+ # is +-255, which encode to 0xff00 and 0xff80 respectively.
38
+ # (big-endian).
39
+ if data.bytesize <= 1 || (data.bytes[data.bytesize - 2] & 0x80) == 0
40
+ raise ScriptNumberError, "non-minimally encoded script number"
41
+ end
42
+ end
43
+ end
44
+ @integer = self.class.decode_integer(data)
45
+ elsif boolean == false || boolean == true
46
+ @integer = boolean ? 1 : 0
47
+ else
48
+ raise ArgumentError
49
+ end
50
+ end
51
+
52
+ # Operators
53
+
54
+ def ==(other); @integer == other.to_i; end
55
+ def !=(other); @integer != other.to_i; end
56
+ def <=(other); @integer <= other.to_i; end
57
+ def <(other); @integer < other.to_i; end
58
+ def >=(other); @integer >= other.to_i; end
59
+ def >(other); @integer > other.to_i; end
60
+
61
+ def +(other); self.class.new(integer: @integer + other.to_i); end
62
+ def -(other); self.class.new(integer: @integer - other.to_i); end
63
+
64
+ def +@
65
+ self
66
+ end
67
+
68
+ def -@
69
+ assert(@integer > INT64_MIN && @integer <= INT64_MAX, "Integer will not be within int64 range after negation")
70
+ self.class.new(integer: -@integer)
71
+ end
72
+
73
+
74
+
75
+ # Conversion Methods
76
+
77
+ def to_i
78
+ @integer
79
+ end
80
+
81
+ def to_s
82
+ @integer.to_s
83
+ end
84
+
85
+ def data
86
+ self.class.encode_integer(@integer)
87
+ end
88
+
89
+ def to_hex
90
+ BTC.to_hex(data)
91
+ end
92
+
93
+ def self.decode_integer(data)
94
+ return 0 if data.empty?
95
+
96
+ result = 0
97
+
98
+ bytes = data.bytes
99
+ bytes.each_with_index do |byte, i|
100
+ result |= bytes[i] << 8*i
101
+ end
102
+
103
+ # If the input vector's most significant byte is 0x80, remove it from
104
+ # the result's msb and return a negative.
105
+ if (bytes.last & 0x80) != 0
106
+ return -(result & ~(0x80 << (8 * (bytes.size - 1))));
107
+ end
108
+ return result
109
+ end
110
+
111
+ def self.encode_integer(value)
112
+ return "".b if value == 0
113
+
114
+ result = []
115
+ negative = value < 0
116
+ absvalue = negative ? -value : value
117
+
118
+ while absvalue != 0
119
+ result.push(absvalue & 0xff)
120
+ absvalue >>= 8
121
+ end
122
+
123
+ # - If the most significant byte is >= 0x80 and the value is positive, push a
124
+ # new zero-byte to make the significant byte < 0x80 again.
125
+ #
126
+ # - If the most significant byte is >= 0x80 and the value is negative, push a
127
+ # new 0x80 byte that will be popped off when converting to an integral.
128
+ #
129
+ # - If the most significant byte is < 0x80 and the value is negative, add
130
+ # 0x80 to it, since it will be subtracted and interpreted as a negative when
131
+ # converting to an integral.
132
+
133
+ if (result.last & 0x80) != 0
134
+ result.push(negative ? 0x80 : 0)
135
+ elsif negative
136
+ result[result.size - 1] = result.last | 0x80
137
+ end
138
+
139
+ BTC.data_from_bytes(result)
140
+ end
141
+
142
+ def assert(condition, message)
143
+ raise message if !condition
144
+ end
145
+
146
+ end
147
+ end
148
+
149
+ if $0 == __FILE__
150
+ require 'btcruby'
151
+
152
+ include BTC
153
+ def run_tests
154
+
155
+ # Decoding
156
+
157
+ [-1000000000000000,-10000,-100,-1,0,1,10,1000,100000000000000].each do |i|
158
+ should_equal(ScriptNumber.new(integer: i).to_i, i, "Must return integer as-is.")
159
+ end
160
+
161
+ should_equal(ScriptNumber.new(data: "").to_i, 0, "Must parse empty string as zero.")
162
+ should_equal(ScriptNumber.new(data: "\x01").to_i, 1, "Must parse 0x01 as 1.")
163
+ should_equal(ScriptNumber.new(data: "\xff").to_i, -127, "Must parse 0xff as -127.")
164
+ should_equal(ScriptNumber.new(data: "\xff\x00").to_i, 255, "Must parse 0xff00 as 255.")
165
+ should_equal(ScriptNumber.new(data: "\x81").to_i, -1, "Must parse 0x81 as -1.")
166
+ should_equal(ScriptNumber.new(data: "\x8f").to_i, -15, "Must parse 0x8f as -0x0f.")
167
+ should_equal(ScriptNumber.new(data: "\x00\x81").to_i, -256, "Must parse 0x0081 as -256.")
168
+ should_equal(ScriptNumber.new(data: "\xff\x80").to_i, -255, "Must decode -255.")
169
+
170
+ should_raise('non-minimally encoded script number') { ScriptNumber.new(data: "\x00") }
171
+ should_raise('non-minimally encoded script number') { ScriptNumber.new(data: "\x80") }
172
+ should_raise('non-minimally encoded script number') { ScriptNumber.new(data: "\x00\x80") }
173
+ should_raise('non-minimally encoded script number') { ScriptNumber.new(data: "\x01\x80") }
174
+ should_raise('non-minimally encoded script number') { ScriptNumber.new(data: "\x00\x00\x80") }
175
+ should_raise('non-minimally encoded script number') { ScriptNumber.new(data: "\x00\x10\x80") }
176
+ should_raise('non-minimally encoded script number') { ScriptNumber.new(data: "\x10\x00\x80") }
177
+ should_raise('script number overflow (3 > 2)') { ScriptNumber.new(data: "\x00\x00\x80", max_size: 2) }
178
+
179
+ # Encoding
180
+
181
+ should_equal(ScriptNumber.new(integer: 0).to_hex, "")
182
+ should_equal(ScriptNumber.new(integer: 1).to_hex, "01")
183
+ should_equal(ScriptNumber.new(integer: -1).to_hex, "81")
184
+ should_equal(ScriptNumber.new(integer: 255).to_hex, "ff00")
185
+ should_equal(ScriptNumber.new(integer: -255).to_hex, "ff80")
186
+ should_equal(ScriptNumber.new(integer: 0xffff).to_hex, "ffff00")
187
+ should_equal(ScriptNumber.new(integer: -0xffff).to_hex, "ffff80")
188
+
189
+ # Back and forth test
190
+
191
+ (-100000..10000).each do |i|
192
+ d = ScriptNumber.new(integer: i).data
193
+ #puts BTC.to_hex(d) if i % 16 == 0
194
+ i2 = ScriptNumber.new(data: d).to_i
195
+ should_equal(i, i2)
196
+ end
197
+
198
+ # Booleans
199
+
200
+ should_equal(ScriptNumber.new(boolean: true), 1)
201
+ should_equal(ScriptNumber.new(boolean: false), 0)
202
+
203
+ # Equality
204
+
205
+ should_equal(ScriptNumber.new(integer: 0) == 0, true)
206
+ should_equal(ScriptNumber.new(integer: 1) == 1, true)
207
+ should_equal(ScriptNumber.new(integer: -1) == -1, true)
208
+
209
+ should_equal(ScriptNumber.new(integer: 0) == ScriptNumber.new(integer: 0), true)
210
+ should_equal(ScriptNumber.new(integer: 1) == ScriptNumber.new(integer: 1), true)
211
+ should_equal(ScriptNumber.new(integer: -1) == ScriptNumber.new(integer: -1), true)
212
+
213
+ should_equal(ScriptNumber.new(integer: 0) != 0, false)
214
+ should_equal(ScriptNumber.new(integer: 1) != 1, false)
215
+ should_equal(ScriptNumber.new(integer: -1) != -1, false)
216
+
217
+ should_equal(ScriptNumber.new(integer: 0) != ScriptNumber.new(integer: 0), false)
218
+ should_equal(ScriptNumber.new(integer: 1) != ScriptNumber.new(integer: 1), false)
219
+ should_equal(ScriptNumber.new(integer: -1) != ScriptNumber.new(integer: -1), false)
220
+
221
+ # Arithmetic
222
+
223
+ sn = ScriptNumber.new(integer: 123)
224
+ sn -= 1
225
+ should_equal(sn, 122)
226
+
227
+ puts "All tests passed."
228
+ end
229
+
230
+ def should_equal(a, b, msg = 'Must equal')
231
+ a == b or raise "#{msg} Expected #{b.inspect}, received #{a.inspect}."
232
+ end
233
+
234
+ def should_raise(message)
235
+ raised = false
236
+ begin
237
+ yield
238
+ rescue => e
239
+ if e.message == message
240
+ raised = true
241
+ else
242
+ raise "Raised unexpected exception: #{e}"
243
+ end
244
+ end
245
+ if !raised
246
+ raise "Should have raised #{message.inspect}!"
247
+ end
248
+ end
249
+
250
+ run_tests
251
+ end
252
+
253
+
254
+
@@ -0,0 +1,14 @@
1
+ module BTC
2
+ # Protocol for signature checkers
3
+ module SignatureChecker
4
+ # Returns a boolean indicating if signature is valid for the given public key
5
+ def check_signature(script_signature: nil, public_key:nil, script:nil)
6
+ false
7
+ end
8
+ # Returns a boolean indicating if lock time is valid.
9
+ # lock_time is ScriptNumber instance.
10
+ def check_lock_time(lock_time)
11
+ false
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,53 @@
1
+ module BTC
2
+ class TestSignatureChecker
3
+ include SignatureChecker
4
+
5
+ def initialize(signature_hash: nil, timestamp: nil)
6
+ @signature_hash = signature_hash
7
+ @timestamp = timestamp
8
+ end
9
+
10
+ # for testing check_signature
11
+ def signature_hash
12
+ @signature_hash
13
+ end
14
+
15
+ # for testing check_lock_time
16
+ def timestamp
17
+ @timestamp
18
+ end
19
+
20
+ def check_signature(script_signature:nil, public_key:nil, script:nil)
21
+ # Signature must be long enough to contain a sighash byte.
22
+ return false if script_signature.size < 1
23
+
24
+ # Extract raw ECDSA signature by stripping off the last hashtype byte
25
+ ecdsa_sig = script_signature[0..-2]
26
+
27
+ key = BTC::Key.new(public_key: public_key)
28
+ result = key.verify_ecdsa_signature(ecdsa_sig, hash)
29
+ return result
30
+ end
31
+
32
+ def check_lock_time(lock_time)
33
+ # There are two times of nLockTime: lock-by-blockheight
34
+ # and lock-by-blocktime, distinguished by whether
35
+ # nLockTime < LOCKTIME_THRESHOLD.
36
+ #
37
+ # We want to compare apples to apples, so fail the script
38
+ # if the type of nLockTime being tested is not the same as
39
+ # the nLockTime in the transaction.
40
+ if !(
41
+ (timestamp < LOCKTIME_THRESHOLD && lock_time < LOCKTIME_THRESHOLD) ||
42
+ (timestamp >= LOCKTIME_THRESHOLD && lock_time >= LOCKTIME_THRESHOLD)
43
+ )
44
+ return false
45
+ end
46
+
47
+ # Now that we know we're comparing apples-to-apples, the
48
+ # comparison is a simple numeric one.
49
+ return timestamp >= lock_time
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,66 @@
1
+ module BTC
2
+ class TransactionSignatureChecker
3
+ include SignatureChecker
4
+
5
+ attr_accessor :transaction
6
+ attr_accessor :input_index
7
+ def initialize(transaction: nil, input_index: nil)
8
+ @transaction = transaction
9
+ @input_index = input_index
10
+ end
11
+
12
+ def check_signature(script_signature:nil, public_key:nil, script:nil)
13
+ # Signature must be long enough to contain a sighash byte.
14
+ return false if script_signature.size < 1
15
+
16
+ hashtype = script_signature[-1].bytes.first
17
+
18
+ # Extract raw ECDSA signature by stripping off the last hashtype byte
19
+ ecdsa_sig = script_signature[0..-2]
20
+
21
+ key = BTC::Key.new(public_key: public_key)
22
+ hash = @transaction.signature_hash(input_index: @input_index, output_script: script, hash_type: hashtype)
23
+ result = key.verify_ecdsa_signature(ecdsa_sig, hash)
24
+ return result
25
+ end
26
+
27
+ def check_lock_time(lock_time)
28
+ # There are two times of nLockTime: lock-by-blockheight
29
+ # and lock-by-blocktime, distinguished by whether
30
+ # nLockTime < LOCKTIME_THRESHOLD.
31
+ #
32
+ # We want to compare apples to apples, so fail the script
33
+ # if the type of nLockTime being tested is not the same as
34
+ # the nLockTime in the transaction.
35
+ if !(
36
+ (@transaction.lock_time < LOCKTIME_THRESHOLD && lock_time < LOCKTIME_THRESHOLD) ||
37
+ (@transaction.lock_time >= LOCKTIME_THRESHOLD && lock_time >= LOCKTIME_THRESHOLD)
38
+ )
39
+ return false
40
+ end
41
+
42
+ # Now that we know we're comparing apples-to-apples, the
43
+ # comparison is a simple numeric one.
44
+ if lock_time > @transaction.lock_time
45
+ return false
46
+ end
47
+
48
+ # Finally the nLockTime feature can be disabled and thus
49
+ # CHECKLOCKTIMEVERIFY bypassed if every txin has been
50
+ # finalized by setting nSequence to maxint. The
51
+ # transaction would be allowed into the blockchain, making
52
+ # the opcode ineffective.
53
+ #
54
+ # Testing if this vin is not final is sufficient to
55
+ # prevent this condition. Alternatively we could test all
56
+ # inputs, but testing just this input minimizes the data
57
+ # required to prove correct CHECKLOCKTIMEVERIFY execution.
58
+ if @transaction.inputs[@input_index].final?
59
+ return false
60
+ end
61
+
62
+ return true
63
+ end
64
+
65
+ end
66
+ end
@@ -78,7 +78,7 @@ module BTC
78
78
  @previous_hash = previous_hash || ZERO_HASH256
79
79
  @previous_hash = BTC.hash_from_id(previous_id) if previous_id
80
80
  @previous_index = previous_index || INVALID_INDEX
81
- @coinbase_data = coinbase_data
81
+ @coinbase_data = coinbase_data || "".b
82
82
  @signature_script = signature_script || BTC::Script.new
83
83
  @sequence = sequence || MAX_SEQUENCE
84
84
  end
@@ -1,3 +1,3 @@
1
1
  module BTC
2
- VERSION = "1.0.9".freeze
2
+ VERSION = "1.1".freeze
3
3
  end
@@ -184,12 +184,44 @@ module BTC
184
184
 
185
185
  intbuf + stringbuf
186
186
  end
187
-
188
-
187
+
188
+
189
+ # Reads varint length prefix, then calls the block appropriate number of times to read the items.
190
+ # Returns an array of items.
191
+ def read_array(data: nil, stream: nil, offset: 0)
192
+ count, len = read_varint(data: data, stream: stream, offset: offset)
193
+ return [nil, len] if !count
194
+ (0...count).map do |i|
195
+ yield
196
+ end
197
+ end
198
+
199
+ def encode_array(array, &block)
200
+ write_array(array, &block)
201
+ end
202
+
203
+ def write_array(array, data: nil, stream: nil, &block)
204
+ raise ArgumentError, "Array must be present" if !array
205
+ raise ArgumentError, "Parsing block must be present" if !block
206
+ intbuf = write_varint(array.size, data: data, stream: stream)
207
+ array.inject(intbuf) do |buf, e|
208
+ string = block.call(e)
209
+ stringbuf = BTC::Data.ensure_binary_encoding(string)
210
+ data << stringbuf if data
211
+ stream.write(stringbuf) if stream
212
+ buf << stringbuf
213
+ buf
214
+ end
215
+ end
216
+
217
+
218
+
219
+
220
+
189
221
  # LEB128 encoding used in Open Assets protocol
190
-
222
+
191
223
  # Decodes an unsigned integer encoded in LEB128.
192
- # Returns `[value, length]` where `value` is an integer decoded from LEB128 and `length`
224
+ # Returns `[value, length]` where `value` is an integer decoded from LEB128 and `length`
193
225
  # is a number of bytes read (includes length prefix and offset bytes).
194
226
  def read_uleb128(data: nil, stream: nil, offset: 0)
195
227
  if (data && stream) || (!data && !stream)
@@ -252,8 +284,8 @@ module BTC
252
284
  stream.write(buf) if stream
253
285
  buf
254
286
  end
255
-
256
-
287
+
288
+
257
289
 
258
290
  PACK_FORMAT_UINT8 = "C".freeze
259
291
  PACK_FORMAT_INT8 = "c".freeze
@@ -291,11 +323,11 @@ module BTC
291
323
  def read_int32le(data: nil, stream: nil, offset: 0)
292
324
  _read_fixint(name: :int32le, length: 4, pack_format: PACK_FORMAT_INT32LE, data: data, stream: stream, offset: offset)
293
325
  end
294
-
326
+
295
327
  def read_uint32be(data: nil, stream: nil, offset: 0) # used in BIP32
296
328
  _read_fixint(name: :uint32be, length: 4, pack_format: PACK_FORMAT_UINT32BE, data: data, stream: stream, offset: offset)
297
329
  end
298
-
330
+
299
331
  def read_int32be(data: nil, stream: nil, offset: 0)
300
332
  _read_fixint(name: :int32be, length: 4, pack_format: PACK_FORMAT_INT32BE, data: data, stream: stream, offset: offset)
301
333
  end