btcruby 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.travis.yml +7 -0
  4. data/FAQ.md +7 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +18 -0
  7. data/HOWTO.md +17 -0
  8. data/LICENSE +19 -0
  9. data/README.md +59 -0
  10. data/Rakefile +6 -0
  11. data/TODO.txt +40 -0
  12. data/bin/console +19 -0
  13. data/btcruby.gemspec +20 -0
  14. data/documentation/address.md +73 -0
  15. data/documentation/base58.md +52 -0
  16. data/documentation/block.md +127 -0
  17. data/documentation/block_header.md +120 -0
  18. data/documentation/constants.md +88 -0
  19. data/documentation/data.md +54 -0
  20. data/documentation/diagnostics.md +90 -0
  21. data/documentation/extensions.md +76 -0
  22. data/documentation/hash_functions.md +58 -0
  23. data/documentation/hash_id.md +22 -0
  24. data/documentation/index.md +230 -0
  25. data/documentation/key.md +177 -0
  26. data/documentation/keychain.md +180 -0
  27. data/documentation/network.md +75 -0
  28. data/documentation/opcode.md +220 -0
  29. data/documentation/openssl.md +7 -0
  30. data/documentation/p2pkh.md +71 -0
  31. data/documentation/p2sh.md +64 -0
  32. data/documentation/proof_of_work.md +84 -0
  33. data/documentation/script.md +280 -0
  34. data/documentation/signature.md +71 -0
  35. data/documentation/transaction.md +213 -0
  36. data/documentation/transaction_builder.md +188 -0
  37. data/documentation/transaction_input.md +133 -0
  38. data/documentation/transaction_output.md +130 -0
  39. data/documentation/wif.md +72 -0
  40. data/documentation/wire_format.md +70 -0
  41. data/lib/btcruby/address.rb +296 -0
  42. data/lib/btcruby/base58.rb +108 -0
  43. data/lib/btcruby/big_number.rb +47 -0
  44. data/lib/btcruby/block.rb +170 -0
  45. data/lib/btcruby/block_header.rb +231 -0
  46. data/lib/btcruby/constants.rb +59 -0
  47. data/lib/btcruby/currency_formatter.rb +64 -0
  48. data/lib/btcruby/data.rb +98 -0
  49. data/lib/btcruby/diagnostics.rb +92 -0
  50. data/lib/btcruby/errors.rb +8 -0
  51. data/lib/btcruby/extensions.rb +65 -0
  52. data/lib/btcruby/hash_functions.rb +54 -0
  53. data/lib/btcruby/hash_id.rb +18 -0
  54. data/lib/btcruby/key.rb +517 -0
  55. data/lib/btcruby/keychain.rb +464 -0
  56. data/lib/btcruby/network.rb +73 -0
  57. data/lib/btcruby/opcode.rb +197 -0
  58. data/lib/btcruby/open_assets/asset.rb +35 -0
  59. data/lib/btcruby/open_assets/asset_address.rb +49 -0
  60. data/lib/btcruby/open_assets/asset_definition.rb +75 -0
  61. data/lib/btcruby/open_assets/asset_id.rb +24 -0
  62. data/lib/btcruby/open_assets/asset_marker.rb +94 -0
  63. data/lib/btcruby/open_assets/asset_processor.rb +377 -0
  64. data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
  65. data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
  66. data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
  67. data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
  68. data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
  69. data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
  70. data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
  71. data/lib/btcruby/open_assets.rb +26 -0
  72. data/lib/btcruby/openssl.rb +536 -0
  73. data/lib/btcruby/proof_of_work.rb +110 -0
  74. data/lib/btcruby/safety.rb +26 -0
  75. data/lib/btcruby/script.rb +733 -0
  76. data/lib/btcruby/signature_hashtype.rb +37 -0
  77. data/lib/btcruby/transaction.rb +511 -0
  78. data/lib/btcruby/transaction_builder/errors.rb +15 -0
  79. data/lib/btcruby/transaction_builder/provider.rb +54 -0
  80. data/lib/btcruby/transaction_builder/result.rb +73 -0
  81. data/lib/btcruby/transaction_builder/signer.rb +28 -0
  82. data/lib/btcruby/transaction_builder.rb +520 -0
  83. data/lib/btcruby/transaction_input.rb +298 -0
  84. data/lib/btcruby/transaction_outpoint.rb +30 -0
  85. data/lib/btcruby/transaction_output.rb +315 -0
  86. data/lib/btcruby/version.rb +3 -0
  87. data/lib/btcruby/wif.rb +118 -0
  88. data/lib/btcruby/wire_format.rb +362 -0
  89. data/lib/btcruby.rb +44 -2
  90. data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
  91. data/sample_code/creating_a_transaction_manually.rb +44 -0
  92. data/sample_code/generating_an_address.rb +20 -0
  93. data/sample_code/using_transaction_builder.rb +49 -0
  94. data/spec/address_spec.rb +206 -0
  95. data/spec/all.rb +6 -0
  96. data/spec/base58_spec.rb +83 -0
  97. data/spec/block_header_spec.rb +18 -0
  98. data/spec/block_spec.rb +18 -0
  99. data/spec/currency_formatter_spec.rb +46 -0
  100. data/spec/data_spec.rb +50 -0
  101. data/spec/diagnostics_spec.rb +41 -0
  102. data/spec/key_spec.rb +205 -0
  103. data/spec/keychain_spec.rb +261 -0
  104. data/spec/network_spec.rb +48 -0
  105. data/spec/open_assets/asset_address_spec.rb +33 -0
  106. data/spec/open_assets/asset_id_spec.rb +15 -0
  107. data/spec/open_assets/asset_marker_spec.rb +47 -0
  108. data/spec/open_assets/asset_processor_spec.rb +567 -0
  109. data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
  110. data/spec/open_assets/asset_transaction_spec.rb +70 -0
  111. data/spec/proof_of_work_spec.rb +53 -0
  112. data/spec/script_spec.rb +66 -0
  113. data/spec/spec_helper.rb +8 -0
  114. data/spec/transaction_builder_spec.rb +338 -0
  115. data/spec/transaction_spec.rb +162 -0
  116. data/spec/wire_format_spec.rb +283 -0
  117. metadata +141 -7
@@ -0,0 +1,733 @@
1
+ module BTC
2
+ class Script
3
+
4
+ # Serialized binary form of the script (payload)
5
+ attr_reader :data
6
+
7
+ # List of public keys if it is a multisig script.
8
+ # If it is not a multisig script, returns nil.
9
+ # See also #multisig_script?
10
+ attr_reader :multisig_public_keys
11
+
12
+ # Number of signatures required if it is a multisig script.
13
+ # If it is not a multisig script, returns nil.
14
+ # See also #multisig_script?
15
+ attr_reader :multisig_signatures_required
16
+
17
+ def initialize(hex: nil, # raw script data in hex
18
+ data: nil, # raw script data in binary
19
+ op_return: nil, # binary string for OP_RETURN script (or array of binary string)
20
+ public_keys: nil, signatures_required: nil # multisig
21
+ )
22
+ if data || hex
23
+ data ||= BTC::Data.data_from_hex(hex)
24
+ data = BTC::Data.ensure_binary_encoding(data)
25
+ @chunks = []
26
+ offset = 0
27
+ while offset < data.bytesize
28
+ chunk = Chunk.with_data(data, offset: offset)
29
+ if !chunk.canonical?
30
+ Diagnostics.current.add_message("BTC::Script: decoded non-canonical chunk at offset #{offset}: #{chunk.to_s}")
31
+ end
32
+ offset += chunk.size
33
+ @chunks << chunk
34
+ end
35
+ elsif op_return
36
+ @chunks = []
37
+ self << OP_RETURN << op_return
38
+ elsif public_keys || signatures_required
39
+ if !public_keys || public_keys.size < 1
40
+ raise ArgumentError, "Public keys must be an array of at least 1 pubkey"
41
+ end
42
+ if !signatures_required || signatures_required < 1
43
+ raise ArgumentError, "Number of required signatures must be greater than zero"
44
+ end
45
+ if signatures_required > public_keys.size
46
+ raise ArgumentError, "Number of required signatures must not exceed number of pubkeys."
47
+ end
48
+ if public_keys.size > 16
49
+ raise ArgumentError, "Maximum number of public keys exceeded (16)"
50
+ end
51
+
52
+ n_opcode = Opcode.opcode_for_small_integer(public_keys.size)
53
+ m_opcode = Opcode.opcode_for_small_integer(signatures_required)
54
+
55
+ @chunks = []
56
+ self << m_opcode << public_keys << n_opcode << OP_CHECKMULTISIG
57
+ else
58
+ # Empty script
59
+ @chunks = []
60
+ end
61
+ end
62
+
63
+ # Initializes a script with its binary representation.
64
+ # Returns nil if data is not a well-formed script.
65
+ def self.with_data(data)
66
+ raise ArgumentError, "Use Script.new(data: ...) instead"
67
+ end
68
+
69
+ # Initializes a script of form "OP_RETURN <data>"
70
+ def self.op_return_script(data)
71
+ raise ArgumentError, "Use Script.new(op_return:...) instead"
72
+ end
73
+
74
+ # Initializes a multisignature script "OP_<M> <pubkey1> ... <pubkeyN> OP_<N> OP_CHECKMULTISIG"
75
+ # N must be >= M, M and N should be from 1 to 16.
76
+ # If you need a more customized transaction with OP_CHECKMULTISIG, create it using other methods.
77
+ # `public_keys` is an array of binary strings.
78
+ def self.multisig(public_keys: [], signatures_required: 0)
79
+ self.new(public_keys: public_keys, signatures_required: signatures_required)
80
+ end
81
+
82
+
83
+ # Representation and conversion
84
+ # -----------------------------
85
+
86
+ # Returns true if this is a standard script
87
+ # As of September 2014, standard = public_key_hash_script? ||
88
+ # p2sh_script? ||
89
+ # standard_multisig_script? ||
90
+ # public_key_script? ||
91
+ # standard_op_return_script?
92
+ def standard?
93
+ public_key_hash_script? ||
94
+ script_hash_script? ||
95
+ standard_multisig_script? ||
96
+ public_key_script? ||
97
+ standard_op_return_script?
98
+ end
99
+
100
+ # Returns true if the script is a pay-to-pubkey script:
101
+ # "<pubkey> OP_CHECKSIG"
102
+ def public_key_script?
103
+ return false if @chunks.size != 2
104
+ return @chunks[0].pushdata? &&
105
+ @chunks[0].pushdata.size >= 33 &&
106
+ @chunks[1].opcode == OP_CHECKSIG
107
+ end
108
+
109
+ def p2pk?
110
+ return public_key_script?
111
+ end
112
+
113
+ # Returns a raw public key if this script is public_key_script?
114
+ def public_key
115
+ @chunks[0] && @chunks[0].pushdata
116
+ end
117
+
118
+ # Returns true if the script is a P2PKH script:
119
+ # "OP_DUP OP_HASH160 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG"
120
+ def public_key_hash_script?
121
+ return false if @chunks.size != 5
122
+ return @chunks[0].opcode == OP_DUP &&
123
+ @chunks[1].opcode == OP_HASH160 &&
124
+ @chunks[2].size == 21 &&
125
+ @chunks[3].opcode == OP_EQUALVERIFY &&
126
+ @chunks[4].opcode == OP_CHECKSIG
127
+ end
128
+
129
+ def p2pkh?
130
+ return public_key_hash_script?
131
+ end
132
+
133
+ # Returns public key hash if this script is public_key_hash_script?
134
+ def public_key_hash
135
+ @chunks[2] && @chunks[2].pushdata
136
+ end
137
+
138
+ # Returns true if the script is a P2SH script:
139
+ # "OP_HASH160 <20-byte hash> OP_EQUAL"
140
+ def script_hash_script?
141
+ return false if @chunks.size != 3
142
+ return @chunks[0].opcode == OP_HASH160 &&
143
+ @chunks[1].size == 21 &&
144
+ @chunks[2].opcode == OP_EQUAL
145
+ end
146
+
147
+ def p2sh?
148
+ return script_hash_script?
149
+ end
150
+
151
+ # Returns p2sh hash if this script is script_hash_script?
152
+ def script_hash
153
+ @chunks[1] && @chunks[1].pushdata
154
+ end
155
+
156
+ # Returns true if the script is "OP_<M> <pubkey1> ... <pubkeyN> OP_<N> OP_CHECKMULTISIG"
157
+ # where N is up to 15.
158
+ # Scripts with up to 15 signatures are considered standard and relayed quickly,
159
+ # but you are allowed to create more complex ones.
160
+ def standard_multisig_script?
161
+ return false if !multisig_script?
162
+
163
+ # Check chunks directly so we make sure OP_<N> are used, not pushdata.
164
+ # Bitcoin allows encoding multisig N and M parameters as pushdata
165
+ # (which will be interpreted as a little-endian bignum)
166
+ m_opcode = @chunks[0].opcode
167
+ n_opcode = @chunks[-2].opcode
168
+ if n_opcode >= OP_1 && n_opcode <= OP_15
169
+ if m_opcode >= OP_1 && m_opcode <= n_opcode
170
+ return true
171
+ end
172
+ end
173
+ return false
174
+ end
175
+
176
+ # Returns true if the script is in form "<M> <pubkey1> ... <pubkeyN> <N> OP_CHECKMULTISIG"
177
+ def multisig_script?
178
+ detect_multisig_if_needed
179
+ @is_multisig
180
+ end
181
+
182
+ # List of public keys if it is a multisig script.
183
+ # If it is not a multisig script, returns nil.
184
+ # See also #multisig_script?
185
+ def multisig_public_keys
186
+ detect_multisig_if_needed
187
+ @multisig_public_keys
188
+ end
189
+
190
+ # Number of signatures required if it is a multisig script.
191
+ # If it is not a multisig script, returns nil.
192
+ # See also #multisig_script?
193
+ def multisig_signatures_required
194
+ detect_multisig_if_needed
195
+ @multisig_signatures_required
196
+ end
197
+
198
+ # Returns true if this script is a 'OP_RETURN <data>' script and
199
+ # data size is within 40 bytes.
200
+ def standard_op_return_script?
201
+ retun false if !op_return_script? || @chunks.size != 2
202
+ @chunks[1].pushdata.bytesize <= 40
203
+ end
204
+
205
+ # Returns true if this script is of form 'OP_RETURN <data>'
206
+ def op_return_script?
207
+ return @chunks.size >= 2 &&
208
+ @chunks[0].opcode == OP_RETURN &&
209
+ @chunks[1..-1].all?{|c| c.pushdata? }
210
+ end
211
+
212
+ # Returns first data chunk if this script is 'OP_RETURN <data>'.
213
+ # Otherwise returns nil.
214
+ def op_return_data
215
+ return nil if !op_return_script?
216
+ return @chunks[1].pushdata
217
+ end
218
+
219
+ # Returns all data chunks if this script is 'OP_RETURN <data> [<data>...]'
220
+ # Most commonly returned array contains one binary string.
221
+ def op_return_items
222
+ return nil if !op_return_script?
223
+ return @chunks[1, @chunks.size-1].map{|c| c.pushdata}
224
+ end
225
+
226
+ # Returns `true` if this script may be a valid OpenAssets marker.
227
+ # Only checks the prefix and minimal length, does not validate the content.
228
+ # Use this method to quickly filter out non-asset transactions.
229
+ def open_assets_marker?
230
+ return false if !op_return_script?
231
+ data = op_return_data
232
+ return false if !data || data.bytesize < 6
233
+ if data[0, AssetMarker::PREFIX_V1.bytesize] == AssetMarker::PREFIX_V1
234
+ return true
235
+ end
236
+ false
237
+ end
238
+
239
+ # Returns pushdata if script starts with <pushdata> OP_DROP
240
+ # Returns nil if the script starts with some other opcodes or shorter than 2 opcodes.
241
+ def dropped_prefix_data
242
+ if @chunks.size >= 2 && @chunks[0].pushdata? && @chunks[1].opcode == OP_DROP
243
+ return @chunks[0].pushdata
244
+ end
245
+ nil
246
+ end
247
+
248
+ # If script starts with `<pushdata> OP_DROP`, these two opcodes are removed
249
+ # and a new script instance is returned.
250
+ def without_dropped_prefix_data
251
+ if dropped_prefix_data
252
+ return Script.new << @chunks[2..-1]
253
+ end
254
+ self
255
+ end
256
+
257
+ # Returns true if the script consists of push data operations only
258
+ # (including OP_<N>). Aka isPushOnly in BitcoinQT.
259
+ # Used in BIP16 (P2SH) implementation.
260
+ def data_only?
261
+ # Include PUSHDATA ops and OP_0..OP_16 literals.
262
+ @chunks.each do |chunk|
263
+ return false if chunk.opcode > OP_16
264
+ end
265
+ return true
266
+ end
267
+
268
+ # Serialized binary form of the script (payload)
269
+ def data
270
+ @chunks.inject("".b){|buf,c| buf << c.raw_data}
271
+ end
272
+
273
+ # Human-readable textual representation of the script
274
+ # (e.g. "OP_DUP OP_HASH160 5a73e920b7836c74f9e740a5bb885e8580557038 OP_EQUALVERIFY OP_CHECKSIG")
275
+ def to_s
276
+ @chunks.map{|c| c.to_s }.join(" ")
277
+ end
278
+
279
+ def to_hex
280
+ BTC::Data.hex_from_data(self.data)
281
+ end
282
+
283
+ # Returns an array of opcodes or pushdata strings.
284
+ # Integers are opcodes, strings are pushdata binary strings.
285
+ # OP_0 is treated as a zero-length pushdata.
286
+ def to_a
287
+ @chunks.map{|c| c.pushdata? ? c.pushdata : c.opcode }
288
+ end
289
+
290
+ # Complete copy of a script.
291
+ def dup
292
+ BTC::Script.new(data: self.data)
293
+ end
294
+
295
+ def ==(other)
296
+ return false if other == nil
297
+ self.data == other.data
298
+ end
299
+ alias_method :eql?, :==
300
+
301
+ def inspect
302
+ %{#<#{self.class.name} #{to_s.inspect} (#{self.data.bytesize} bytes)>}
303
+ end
304
+
305
+
306
+
307
+ # Conversion
308
+ # ----------
309
+
310
+
311
+ # Returns BTC::PublicKeyAddress or BTC::ScriptHashAddress if
312
+ # the script is a standard output script for these addresses.
313
+ # If the script is something different, returns nil.
314
+ def standard_address(network: nil)
315
+ if public_key_hash_script?
316
+ return BTC::PublicKeyAddress.new(hash: @chunks[2].pushdata, network: network)
317
+ elsif script_hash_script?
318
+ return BTC::ScriptHashAddress.new(hash: @chunks[1].pushdata, network: network)
319
+ elsif public_key_script?
320
+ return BTC::PublicKeyAddress.new(hash: BTC.hash160(@chunks[0].pushdata), network: network)
321
+ end
322
+ nil
323
+ end
324
+
325
+ # Wraps the recipient into an output P2SH script
326
+ # (OP_HASH160 <20-byte hash of the recipient> OP_EQUAL).
327
+ def p2sh_script
328
+ Script.new << OP_HASH160 << BTC.hash160(self.data) << OP_EQUAL
329
+ end
330
+
331
+
332
+ # Returns a dummy script matching this script on the input with
333
+ # the same size as an intended signature script.
334
+ # Only a few standard script types are supported.
335
+ # Set `strict` to false to allow imprecise guess for P2SH script.
336
+ # Returns nil if could not determine a matching script.
337
+ def simulated_signature_script(strict: true)
338
+ if public_key_hash_script?
339
+ # assuming non-compressed pubkeys to be conservative
340
+ return Script.new << Script.simulated_signature(hashtype: SIGHASH_ALL) << Script.simulated_uncompressed_pubkey
341
+
342
+ elsif public_key_script?
343
+ return Script.new << Script.simulated_signature(hashtype: SIGHASH_ALL)
344
+
345
+ elsif script_hash_script? && !strict
346
+ # This is a wild approximation, but works well if most p2sh scripts are 2-of-3 multisig scripts.
347
+ # If you have a very particular smart contract scheme you should not use TransactionBuilder which estimates fees this way.
348
+ return Script.new << OP_0 << [Script.simulated_signature(hashtype: SIGHASH_ALL)]*2 << Script.simulated_multisig_script(2,3).data
349
+
350
+ elsif multisig_script?
351
+ return Script.new << OP_0 << [Script.simulated_signature(hashtype: SIGHASH_ALL)]*self.multisig_signatures_required
352
+ else
353
+ return nil
354
+ end
355
+ end
356
+
357
+ # Returns a simulated signature with an optional hashtype byte attached
358
+ def self.simulated_signature(hashtype: nil)
359
+ "\x30" + "\xff"*71 + (hashtype ? WireFormat.encode_uint8(hashtype) : "")
360
+ end
361
+
362
+ # Returns a dummy uncompressed pubkey (65 bytes).
363
+ def self.simulated_uncompressed_pubkey
364
+ "\x04" + "\xff"*64
365
+ end
366
+
367
+ # Returns a dummy compressed pubkey (33 bytes).
368
+ def self.simulated_compressed_pubkey
369
+ "\x02" + "\xff"*32
370
+ end
371
+
372
+ # Returns a dummy script that simulates m-of-n multisig script
373
+ def self.simulated_multisig_script(m,n)
374
+ Script.new <<
375
+ Opcode.opcode_for_small_integer(m) <<
376
+ [simulated_uncompressed_pubkey]*n << # assuming non-compressed pubkeys to be conservative
377
+ Opcode.opcode_for_small_integer(n) <<
378
+ OP_CHECKMULTISIG
379
+ end
380
+
381
+
382
+ # Modification
383
+ # ------------
384
+
385
+ # Appends a non-pushdata opcode to the script.
386
+ def append_opcode(opcode)
387
+ raise ArgumentError, "Invalid opcode value." if opcode > 0xff || opcode < 0
388
+ if opcode > 0 && opcode <= OP_PUSHDATA4
389
+ raise ArgumentError, "Cannot add pushdata opcode without data"
390
+ end
391
+ @chunks << Chunk.new(opcode.chr)
392
+ return self
393
+ end
394
+
395
+ # Appends a pushdata opcode with the most compact encoding.
396
+ # Optional opcode may be equal to OP_PUSHDATA1, OP_PUSHDATA2, or OP_PUSHDATA4.
397
+ # ArgumentError is raised if opcode does not represent a given data length.
398
+ def append_pushdata(pushdata, opcode: nil)
399
+ raise ArgumentError, "No pushdata is given" if !pushdata
400
+ encoded_pushdata = self.class.encode_pushdata(pushdata, opcode: opcode)
401
+ if !encoded_pushdata
402
+ raise ArgumentError, "Cannot encode pushdata with opcode #{opcode}"
403
+ end
404
+ @chunks << Chunk.new(encoded_pushdata)
405
+ return self
406
+ end
407
+
408
+ # Removes all occurences of opcode. Typically it's OP_CODESEPARATOR.
409
+ def delete_opcode(opcode)
410
+ @chunks = @chunks.inject([]) do |list, chunk|
411
+ list << chunk if chunk.opcode != opcode
412
+ list
413
+ end
414
+ return self
415
+ end
416
+
417
+ # Removes all occurences of a given pushdata.
418
+ def delete_pushdata(pushdata)
419
+ @chunks = @chunks.inject([]) do |list, chunk|
420
+ list << chunk if chunk.pushdata != pushdata
421
+ list
422
+ end
423
+ return self
424
+ end
425
+
426
+ # Appends script to the current script.
427
+ def append_script(script)
428
+ raise ArgumentError, "No script is given" if !script
429
+ @chunks += script.chunks
430
+ return self
431
+ end
432
+
433
+ # Appends an opcode (Integer), pushdata (String) or Script and returns self.
434
+ # If Array is passed, this method is recursively called for each element in the array.
435
+ def <<(object)
436
+ if object.is_a?(BTC::Script)
437
+ append_script(object)
438
+ elsif object.is_a?(Integer)
439
+ append_opcode(object)
440
+ elsif object.is_a?(String)
441
+ append_pushdata(object.b)
442
+ elsif object.is_a?(Array)
443
+ object.each do |element|
444
+ self << element
445
+ end
446
+ elsif object.is_a?(Chunk)
447
+ if object.pushdata?
448
+ append_pushdata(object.pushdata)
449
+ else
450
+ append_opcode(object.opcode)
451
+ end
452
+ else
453
+ raise ArgumentError, "Operand must be an integer, a string a BTC::Script instance or an array of any of those."
454
+ end
455
+ return self
456
+ end
457
+
458
+ # Returns a new instance with concatenation of two scripts.
459
+ def +(other)
460
+ self.dup << other
461
+ end
462
+
463
+
464
+
465
+
466
+
467
+
468
+ # Private API
469
+ # -----------
470
+
471
+ attr_reader :chunks
472
+
473
+ # If opcode is nil, then the most compact encoding will be chosen.
474
+ # Returns nil if opcode can't be used for data, or data is nil or too big.
475
+ def self.encode_pushdata(pushdata, opcode: nil)
476
+ raise ArgumentError, "Pushdata is missing" if !pushdata
477
+ if pushdata.bytesize < OP_PUSHDATA1 && opcode == nil
478
+ return BTC::WireFormat.encode_uint8(pushdata.bytesize) + pushdata
479
+ elsif pushdata.bytesize < 0xff && (opcode == nil || opcode == OP_PUSHDATA1)
480
+ return BTC::WireFormat.encode_uint8(OP_PUSHDATA1) +
481
+ BTC::WireFormat.encode_uint8(pushdata.bytesize) +
482
+ pushdata
483
+ elsif pushdata.bytesize < 0xffff && (opcode == nil || opcode == OP_PUSHDATA2)
484
+ return BTC::WireFormat.encode_uint8(OP_PUSHDATA2) +
485
+ BTC::WireFormat.encode_uint16le(pushdata.bytesize) +
486
+ pushdata
487
+ elsif pushdata.bytesize < 0xffffffff && (opcode == nil || opcode == OP_PUSHDATA4)
488
+ return BTC::WireFormat.encode_uint8(OP_PUSHDATA4) +
489
+ BTC::WireFormat.encode_uint32le(pushdata.bytesize) +
490
+ pushdata
491
+ else
492
+ raise ArgumentError, "Invalid opcode or data is too big"
493
+ end
494
+ end
495
+
496
+ def detect_multisig_if_needed
497
+ return if @is_multisig != nil
498
+ @is_multisig = detect_multisig
499
+ end
500
+
501
+ def detect_multisig
502
+ # multisig script must have at least 4 ops ("OP_1 <pubkey> OP_1 OP_CHECKMULTISIG")
503
+ return false if @chunks.size < 4
504
+ return false if @chunks.last.opcode != OP_CHECKMULTISIG
505
+
506
+ m_chunk = @chunks[0]
507
+ n_chunk = @chunks[-2]
508
+ m_opcode = m_chunk.opcode
509
+ n_opcode = n_chunk.opcode
510
+
511
+ m = Opcode.small_integer_from_opcode(m_opcode)
512
+ n = Opcode.small_integer_from_opcode(n_opcode)
513
+
514
+ # If m or n is not OP_<int>, but a pushdata with little-endian bignum.
515
+ if !m
516
+ return false if !m_chunk.pushdata?
517
+ m = BTC::BigNumber.new(signed_little_endian: m_chunk.pushdata).integer
518
+ end
519
+
520
+ if !n
521
+ return false if !n_chunk.pushdata?
522
+ n = BTC::BigNumber.new(signed_little_endian: n_chunk.pushdata).integer
523
+ end
524
+
525
+ return false if m < 1 || n < 1 || m > n
526
+
527
+ # We must have correct number of pubkeys in the script. 3 extra ops: OP_<M>, OP_<N> and OP_CHECKMULTISIG
528
+ return false if @chunks.size != (3 + n)
529
+
530
+ pubkeys = []
531
+ @chunks[1, n].each do |chunk|
532
+ return false if !chunk.pushdata? || chunk.pushdata.bytesize == 0
533
+ pubkeys << chunk.pushdata
534
+ end
535
+
536
+ # Now we extracted all pubkeys and verified the numbers.
537
+ @multisig_public_keys = pubkeys
538
+ @multisig_signatures_required = m
539
+ return true
540
+ end
541
+
542
+ # Script::Chunk represents either an opcode or a pushdata command.
543
+ class Chunk
544
+ # Raw data for this chunk.
545
+ # 1 byte for regular opcode, 1 or more bytes for pushdata command.
546
+ # We do not call it 'data' to avoid confusion with `pushdata` (see below).
547
+ # The encoding is guaranteed to be binary.
548
+ attr_reader :raw_data
549
+
550
+ # Opcode for this chunk (first byte of the raw_data).
551
+ attr_reader :opcode
552
+
553
+ # If opcode is OP_PUSHDATA*, contains pure data being pushed.
554
+ # If opcode is OP_0, returns an empty string.
555
+ # If opcode is not pushdata, returns nil.
556
+ attr_reader :pushdata
557
+
558
+ # Length of raw_data in bytes.
559
+ attr_reader :size
560
+ alias :length :size
561
+
562
+ # Returns true if this is a non-pushdata (also not OP_0) opcode.
563
+ def opcode?
564
+ !pushdata?
565
+ end
566
+
567
+ # Returns true if this is a pushdata chunk (or OP_0 opcode).
568
+ def pushdata?
569
+ # Compact pushdata opcodes are "virtual", just length prefixes.
570
+ # Attention: OP_0 is also "pushdata" code that pushes empty data.
571
+ self.opcode <= OP_PUSHDATA4
572
+ end
573
+
574
+ # Returns true if this chunk is in canonical form (the most compact one).
575
+ # Returns false if it contains pushdata with too big length prefix.
576
+ # Example of non-canonical chunk: 75 bytes pushed with OP_PUSHDATA1 instead
577
+ # of simple 0x4b prefix.
578
+ def canonical?
579
+ opcode = self.opcode
580
+ if opcode < OP_PUSHDATA1
581
+ return true # most compact pushdata is always canonical.
582
+ elsif opcode == OP_PUSHDATA1
583
+ return (self.raw_data.bytesize - (1+1)) >= OP_PUSHDATA1
584
+ elsif opcode == OP_PUSHDATA2
585
+ return (self.raw_data.bytesize - (1+2)) > 0xff
586
+ elsif opcode == OP_PUSHDATA4
587
+ return (self.raw_data.bytesize - (1+4)) > 0xffff
588
+ else
589
+ return true # all other opcodes are canonical (just 1 byte code)
590
+ end
591
+ end
592
+
593
+ def opcode
594
+ # raises StopIteration if raw_data is empty,
595
+ # but we don't allow empty raw_data for chunks.
596
+ raw_data.each_byte.next
597
+ end
598
+
599
+ def pushdata
600
+ return nil if !pushdata?
601
+ opcode = self.opcode
602
+ offset = 1 # by default, opcode is just a length prefix.
603
+ if opcode == OP_PUSHDATA1
604
+ offset += 1
605
+ elsif opcode == OP_PUSHDATA2
606
+ offset += 2
607
+ elsif opcode == OP_PUSHDATA4
608
+ offset += 4
609
+ end
610
+ self.raw_data[offset, self.raw_data.size - offset]
611
+ end
612
+
613
+ def size
614
+ self.raw_data.bytesize
615
+ end
616
+
617
+ def to_s
618
+ opcode = self.opcode
619
+
620
+ if self.opcode?
621
+ return "OP_0" if opcode == OP_0
622
+ return "OP_1NEGATE" if opcode == OP_1NEGATE
623
+ if opcode >= OP_1 && opcode <= OP_16
624
+ return "OP_#{opcode + 1 - OP_1}"
625
+ end
626
+ return Opcode.name_for_opcode(opcode)
627
+ end
628
+
629
+ pushdata = self.pushdata
630
+ return "OP_0" if pushdata.bytesize == 0 # Empty data is encoded as OP_0.
631
+
632
+ string = ""
633
+
634
+ # If it's some weird readable string, show it as a readable string.
635
+ if data_is_ascii_printable?(pushdata) && (pushdata.bytesize < 20 || pushdata.bytesize > 65)
636
+ string = pushdata.encode(Encoding::ASCII)
637
+ # Escape escapes & single quote characters.
638
+ string.gsub!("\\", "\\\\")
639
+ string.gsub!("'", "\\'")
640
+ # Wrap in single quotes. Why not double? Because they are already used in JSON and we don't want to multiply the mess.
641
+ string = "'#{string}'"
642
+ else
643
+ string = BTC::Data.hex_from_data(pushdata)
644
+ # Shorter than 128-bit chunks are wrapped in square brackets to avoid ambiguity with big all-decimal numbers.
645
+ if (pushdata.bytesize < 16)
646
+ string = "[#{string}]"
647
+ end
648
+ end
649
+
650
+ # Pushdata with non-compact encoding will have explicit length prefix (1 for OP_PUSHDATA1, 2 for OP_PUSHDATA2 and 4 for OP_PUSHDATA4).
651
+ if !canonical?
652
+ prefix = 1
653
+ prefix = 2 if opcode == OP_PUSHDATA2
654
+ prefix = 4 if opcode == OP_PUSHDATA4
655
+ string = "#{prefix}:#{string}"
656
+ end
657
+
658
+ return string
659
+ end
660
+
661
+ # Parses the chunk with binary data. Assumes the encoding is binary.
662
+ def self.with_data(data, offset: 0)
663
+ raise ArgumentError, "Data is missing" if !data
664
+
665
+ opcode, _ = BTC::WireFormat.read_uint8(data: data, offset: offset)
666
+
667
+ raise ArgumentError, "Failed to read opcode of the script chunk" if !opcode
668
+
669
+ # push data opcode
670
+ if opcode <= OP_PUSHDATA4
671
+
672
+ length = data.bytesize
673
+
674
+ if opcode < OP_PUSHDATA1
675
+ pushdata_length = opcode
676
+ chunk_length = 1 + pushdata_length
677
+ if offset + chunk_length > length
678
+ raise ArgumentError, "PUSHDATA is longer than we have bytes available"
679
+ end
680
+ return self.new(data[offset, chunk_length])
681
+ elsif opcode == OP_PUSHDATA1
682
+ pushdata_length, _ = BTC::WireFormat.read_uint8(data: data, offset: offset + 1)
683
+ if !pushdata_length
684
+ raise ArgumentError, "Failed to read length for PUSHDATA1"
685
+ end
686
+ chunk_length = 1 + 1 + pushdata_length
687
+ if offset + chunk_length > length
688
+ raise ArgumentError, "PUSHDATA1 is longer than we have bytes available"
689
+ end
690
+ return self.new(data[offset, chunk_length])
691
+ elsif (opcode == OP_PUSHDATA2)
692
+ pushdata_length, _ = BTC::WireFormat.read_uint16le(data: data, offset: offset + 1)
693
+ if !pushdata_length
694
+ raise ArgumentError, "Failed to read length for PUSHDATA2"
695
+ end
696
+ chunk_length = 1 + 2 + pushdata_length
697
+ if offset + chunk_length > length
698
+ raise ArgumentError, "PUSHDATA2 is longer than we have bytes available"
699
+ end
700
+ return self.new(data[offset, chunk_length])
701
+ elsif (opcode == OP_PUSHDATA4)
702
+ pushdata_length, _ = BTC::WireFormat.read_uint32le(data: data, offset: offset + 1)
703
+ if !pushdata_length
704
+ raise ArgumentError, "Failed to read length for PUSHDATA4"
705
+ end
706
+ chunk_length = 1 + 4 + pushdata_length
707
+ if offset + chunk_length > length
708
+ raise ArgumentError, "PUSHDATA4 is longer than we have bytes available"
709
+ end
710
+ return self.new(data[offset, chunk_length])
711
+ end
712
+ else
713
+ # simple opcode - 1 byte
714
+ return self.new(data[offset, 1])
715
+ end
716
+ end
717
+
718
+ def initialize(raw_data)
719
+ @raw_data = raw_data
720
+ end
721
+
722
+ protected
723
+
724
+ def data_is_ascii_printable?(data)
725
+ data.each_byte do |byte|
726
+ return false if !(byte >= 0x20 && byte <= 0x7E)
727
+ end
728
+ return true
729
+ end
730
+
731
+ end # Chunk
732
+ end # Script
733
+ end # BTC