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,37 @@
1
+ # Hash type determines how OP_CHECKSIG hashes the transaction to create or
2
+ # verify the signature in a transaction input.
3
+ # Depending on hash type, transaction is modified in some way before its hash is computed.
4
+ # Hash type is 1 byte appended to a signature in a transaction input.
5
+ module BTC
6
+ # First three types are mutually exclusive (tested using "type & 0x1F").
7
+
8
+ # Default. Signs all inputs and outputs.
9
+ # Other inputs have scripts and sequences zeroed out, current input has its script
10
+ # replaced by the previous transaction's output script (or, in case of P2SH,
11
+ # by the signatures and the redemption script).
12
+ # If (type & 0x1F) is not NONE or SINGLE, then this type is used.
13
+ SIGHASH_ALL = 1
14
+
15
+ # All outputs are removed. "I don't care where it goes as long as others pay".
16
+ # Note: this is not safe when used with ANYONECANPAY, because then anyone who relays the transaction
17
+ # can pick your input and use in his own transaction.
18
+ # It's also not safe if all inputs are SIGHASH_NONE as well (or it's the only input).
19
+ SIGHASH_NONE = 2
20
+
21
+ # Hash only the output with the same index as the current input.
22
+ # Preceding outputs are "nullified", other outputs are removed.
23
+ # Special case: if there is no matching output, hash is equal
24
+ # "0000000000000000000000000000000000000000000000000000000000000001" (32 bytes)
25
+ SIGHASH_SINGLE = 3
26
+
27
+ # This mask is used to determine one of the first types independently from ANYONECANPAY option:
28
+ # E.g. if (type & SIGHASH_OUTPUT_MASK == SIGHASH_NONE) { blank all outputs }
29
+ SIGHASH_OUTPUT_MASK = 0x1F
30
+
31
+ # Removes all inputs except for current txin before hashing.
32
+ # This allows to sign the transaction outputs without knowing who and how adds other inputs.
33
+ # E.g. a crowdfunding transaction with 100 BTC output can be signed independently by any number of people
34
+ # and will become valid only when someone combines all inputs in a single transaction to make it valid.
35
+ # Can be used together with any of the above types.
36
+ SIGHASH_ANYONECANPAY = 0x80
37
+ end
@@ -0,0 +1,511 @@
1
+ # Transaction represents a bitcoin transfer from one or more inputs to one or more outputs.
2
+ require 'stringio'
3
+ module BTC
4
+
5
+ class Transaction
6
+
7
+ CURRENT_VERSION = 1
8
+
9
+ DEFAULT_FEE_RATE = 10_000 # satoshis per 1000 bytes
10
+ DEFAULT_RELAY_FEE_RATE = 1000 # satoshis per 1000 bytes
11
+
12
+ # Core attributes.
13
+
14
+ # Version of the transaction. Default is CURRENT_VERSION.
15
+ attr_accessor :version
16
+
17
+ # List of TransactionInputs. See also #add_input and #remove_all_inputs.
18
+ attr_accessor :inputs
19
+
20
+ # List of TransactionOutputs. See also #add_output and #remove_all_outputs.
21
+ attr_accessor :outputs
22
+
23
+ # Lock time. Either a block height or a unix timestamp.
24
+ # Default is 0.
25
+ attr_accessor :lock_time
26
+
27
+
28
+ # Derived attributes.
29
+
30
+ # 32-byte transaction hash
31
+ attr_reader :transaction_hash
32
+
33
+ # Hexadecimal transaction ID with bytes reversed.
34
+ # Used by Chain.com, Blockchain.info, Blockr.io.
35
+ attr_reader :transaction_id
36
+
37
+ # Binary representation of the transaction in wire format (aka payload).
38
+ attr_reader :data
39
+
40
+ # Dictionary representation of transaction ready to be encoded in JSON, PropertyList etc.
41
+ attr_reader :dictionary
42
+
43
+
44
+ # Optional Attributes.
45
+ # These are not derived from tx data, but attached externally (e.g. via external APIs).
46
+
47
+ # Binary hash of the block at which transaction was included.
48
+ # If not confirmed or not available, equals nil.
49
+ attr_accessor :block_hash
50
+
51
+ # Hex-encoded block ID.
52
+ # If not confirmed or not available, equals nil.
53
+ attr_accessor :block_id
54
+
55
+ # Height of the block at which transaction was included.
56
+ # If not confirmed equals -1.
57
+ # Note: `block_height` might not be provided by some APIs while `confirmations` may be.
58
+ attr_accessor :block_height
59
+
60
+ # Time of the block at which tx was included (::Time instance or nil).
61
+ attr_accessor :block_time
62
+
63
+ # Number of confirmations for this transaction (depth in the blockchan).
64
+ # 0 stands for unconfirmed mempool transaction. Default is nil ("no info").
65
+ attr_accessor :confirmations
66
+
67
+ # If available, returns mining fee paid by this transaction.
68
+ # If set, `inputs_amount` is updated as (`outputs_amount` + `fee`).
69
+ # Default is nil.
70
+ attr_accessor :fee
71
+
72
+ # If available, returns total amount of all inputs.
73
+ # If set, `fee` is updated as (`inputs_amount` - `outputs_amount`).
74
+ # Default is nil.
75
+ attr_accessor :inputs_amount
76
+
77
+ # Total amount on all outputs (not including fees).
78
+ # Always available since outputs contain their amounts.
79
+ attr_reader :outputs_amount
80
+
81
+ # Initializes transaction with its attributes. Every attribute has a valid default value.
82
+ def initialize(hex: nil,
83
+ data: nil,
84
+ stream: nil,
85
+ dictionary: nil,
86
+ version: CURRENT_VERSION,
87
+ inputs: [],
88
+ outputs: [],
89
+ lock_time: 0,
90
+
91
+ # optional attributes
92
+ block_hash: nil,
93
+ block_id: nil,
94
+ block_height: nil,
95
+ block_time: nil,
96
+ confirmations: nil,
97
+ fee: nil,
98
+ inputs_amount: nil)
99
+
100
+ data = BTC::Data.data_from_hex(hex) if hex
101
+ stream = StringIO.new(data) if data
102
+ if stream
103
+ init_with_stream(stream)
104
+ elsif dictionary
105
+ init_with_dictionary(dictionary)
106
+ else
107
+ init_with_components(version: version, inputs: inputs, outputs: outputs, lock_time: lock_time)
108
+ end
109
+
110
+ @block_hash = block_hash
111
+ @block_hash = BTC.hash_from_id(block_id) if block_id
112
+ @block_height = block_height
113
+ @block_time = block_time
114
+ @confirmations = confirmations
115
+ @fee = fee
116
+ @inputs_amount = inputs_amount
117
+ end
118
+
119
+ def init_with_components(version: CURRENT_VERSION, inputs: [], outputs: [], lock_time: 0)
120
+ @version = version || CURRENT_VERSION
121
+ @inputs = inputs || []
122
+ @outputs = outputs || []
123
+ @lock_time = lock_time || 0
124
+ @inputs.each_with_index do |txin, i|
125
+ txin.transaction = self
126
+ txin.index = i
127
+ end
128
+ @outputs.each_with_index do |txout, i|
129
+ txout.transaction = self
130
+ txout.index = i
131
+ end
132
+ end
133
+
134
+ def init_with_stream(stream)
135
+ raise ArgumentError, "Stream is missing" if !stream
136
+ if stream.eof?
137
+ raise ArgumentError, "Can't parse transaction from stream because it is already closed."
138
+ end
139
+
140
+ if !(version = BTC::WireFormat.read_int32le(stream: stream).first)
141
+ raise ArgumentError, "Failed to read version prefix from the stream."
142
+ end
143
+
144
+ if !(inputs_count = BTC::WireFormat.read_varint(stream: stream).first)
145
+ raise ArgumentError, "Failed to read inputs count from the stream."
146
+ end
147
+
148
+ txins = (0...inputs_count).map do
149
+ TransactionInput.new(stream: stream)
150
+ end
151
+
152
+ if !(outputs_count = BTC::WireFormat.read_varint(stream: stream).first)
153
+ raise ArgumentError, "Failed to read outputs count from the stream."
154
+ end
155
+
156
+ txouts = (0...outputs_count).map do
157
+ TransactionOutput.new(stream: stream)
158
+ end
159
+
160
+ if !(lock_time = BTC::WireFormat.read_uint32le(stream: stream).first)
161
+ raise ArgumentError, "Failed to read lock_time from the stream."
162
+ end
163
+
164
+ init_with_components(version: version, inputs: txins, outputs: txouts, lock_time: lock_time)
165
+ end
166
+
167
+ def init_with_dictionary(dict)
168
+ version = dict["ver"] || CURRENT_VERSION
169
+ lock_time = dict["lock_time"] || 0
170
+
171
+ txins = dict["in"].map { |i| TransactionInput.new(dictionary: i) }
172
+ txouts = dict["out"].map {|o| TransactionOutput.new(dictionary: o) }
173
+
174
+ init_with_components(version: version, inputs: txins, outputs: txouts, lock_time: lock_time)
175
+ end
176
+
177
+ def self.with_data(data)
178
+ raise ArgumentError, "Use Transaction.new(data: ...) instead"
179
+ end
180
+
181
+ def self.with_stream(stream)
182
+ raise ArgumentError, "Use Transaction.new(stream: ...) instead"
183
+ end
184
+
185
+ def self.with_dictionary(dict)
186
+ raise ArgumentError, "Use Transaction.new(dictionary: ...)"
187
+ end
188
+
189
+ # Returns true if this transaction is a coinbase transaction.
190
+ def coinbase?
191
+ self.inputs.size == 1 && self.inputs[0].coinbase?
192
+ end
193
+
194
+ # Returns `true` if this transaction contains an Open Assets marker.
195
+ # Does not perform expensive validation.
196
+ # Use this method to quickly filter out non-asset transactions.
197
+ def open_assets_transaction?
198
+ self.outputs.any? {|txout| txout.script.open_assets_marker? }
199
+ end
200
+
201
+ def inputs=(inputs)
202
+ remove_all_inputs
203
+ @inputs = inputs || []
204
+ @inputs.each_with_index do |txin, i|
205
+ txin.transaction = self
206
+ txin.index = i
207
+ end
208
+ end
209
+
210
+ def outputs=(outputs)
211
+ remove_all_outputs
212
+ @outputs = outputs || []
213
+ @outputs.each_with_index do |txout, i|
214
+ txout.transaction = self
215
+ txout.index = i
216
+ end
217
+ end
218
+
219
+ # Adds another input to the transaction.
220
+ def add_input(txin)
221
+ raise ArgumentError, "Input is missing" if !txin
222
+ if !(txin.transaction == nil || txin.transaction == self)
223
+ raise ArgumentError, "Can't add an input to a transaction when it references another transaction" # sanity check
224
+ end
225
+ txin.transaction = self
226
+ txin.index = @inputs.size
227
+ @inputs << txin
228
+ self
229
+ end
230
+
231
+ def add_output(txout)
232
+ raise ArgumentError, "Output is missing" if !txout
233
+ if !(txout.transaction == nil || txout.transaction == self)
234
+ raise ArgumentError, "Can't add an output to a transaction when it references another transaction" # sanity check
235
+ end
236
+ txout.transaction = self
237
+ txout.index = @outputs.size
238
+ @outputs << txout
239
+ self
240
+ end
241
+
242
+ def remove_all_inputs
243
+ return if !@inputs
244
+ @inputs.each do |txin|
245
+ txin.transaction = nil
246
+ txin.index = nil
247
+ end
248
+ @inputs = []
249
+ self
250
+ end
251
+
252
+ def remove_all_outputs
253
+ return if !@outputs
254
+ @outputs.each do |txout|
255
+ txout.transaction = nil
256
+ txout.index = nil
257
+ end
258
+ @outputs = []
259
+ self
260
+ end
261
+
262
+ def transaction_hash
263
+ BTC.hash256(self.data)
264
+ end
265
+
266
+ def transaction_id
267
+ BTC.id_from_hash(self.transaction_hash)
268
+ end
269
+
270
+ def block_id
271
+ BTC.id_from_hash(self.block_hash)
272
+ end
273
+
274
+ def block_id=(block_id)
275
+ self.block_hash = BTC.hash_from_id(block_id)
276
+ end
277
+
278
+ # Amounts and fee
279
+
280
+ def fee=(fee)
281
+ @fee = fee
282
+ @inputs_amount = nil # will be computed from fee.
283
+ end
284
+
285
+ def fee
286
+ return @fee if @fee
287
+ if ia = self.inputs_amount
288
+ return (ia - self.outputs_amount)
289
+ end
290
+ return nil
291
+ end
292
+
293
+ def inputs_amount=(inputs_amount)
294
+ @inputs_amount = inputs_amount
295
+ @fee = nil # will be computed from inputs_amount. inputs_amount ? (inputs_amount - self.outputs_amount) : nil
296
+ end
297
+
298
+ def inputs_amount
299
+ return @inputs_amount if @inputs_amount
300
+ return (@fee + self.outputs_amount) if @fee
301
+ # Try to figure the total amount from amounts on inputs.
302
+ # If all of them are non-nil, we have a valid amount.
303
+ inputs.inject(0) do |total, input|
304
+ if total && (v = input.value)
305
+ total + input.value
306
+ else
307
+ return nil # quickly return nil
308
+ end
309
+ end
310
+ end
311
+
312
+ def outputs_amount
313
+ self.outputs.inject(0){|t,o| t + o.value}
314
+ end
315
+
316
+ def data
317
+ data = "".b
318
+ data << BTC::WireFormat.encode_int32le(self.version)
319
+ data << BTC::WireFormat.encode_varint(self.inputs.size)
320
+ self.inputs.each do |txin|
321
+ data << txin.data
322
+ end
323
+ data << BTC::WireFormat.encode_varint(self.outputs.size)
324
+ self.outputs.each do |txout|
325
+ data << txout.data
326
+ end
327
+ data << BTC::WireFormat.encode_uint32le(self.lock_time)
328
+ data
329
+ end
330
+
331
+ def dictionary
332
+ {
333
+ "hash" => self.transaction_id,
334
+ "ver" => self.version,
335
+ "vin_sz" => self.inputs.size,
336
+ "vout_sz" => self.outputs.size,
337
+ "lock_time" => self.lock_time,
338
+ "size" => self.data.bytesize,
339
+ "in" => self.inputs.map{|i| i.dictionary},
340
+ "out" => self.outputs.map{|o| o.dictionary}
341
+ }
342
+ end
343
+
344
+ # Hash for signing a transaction.
345
+ # You should specify an input index, output script of the previous transaction for that input,
346
+ # and an optional hash type (default is SIGHASH_ALL).
347
+ def signature_hash(input_index: nil, output_script: nil, hash_type: BTC::SIGHASH_ALL)
348
+
349
+ raise ArgumentError, "Should specify input_index in Transaction#signature_hash." if !input_index
350
+ raise ArgumentError, "Should specify output_script in Transaction#signature_hash." if !output_script
351
+ raise ArgumentError, "Should specify hash_type in Transaction#signature_hash." if !hash_type
352
+
353
+ # Create a temporary copy of the transaction to apply modifications to it.
354
+ tx = self.dup
355
+
356
+ # Note: BitcoinQT returns a 256-bit little-endian number 1 in such case,
357
+ # but it does not matter because it would crash before that in CScriptCheck::operator()().
358
+ # We normally won't enter this condition if script machine is instantiated
359
+ # with transaction and input index, but it's better to check anyway.
360
+ if (input_index >= tx.inputs.size)
361
+ raise ArgumentError, "Input index is out of bounds for transaction: #{input_index} >= #{tx.inputs.size}"
362
+ end
363
+
364
+ # In case concatenating two scripts ends up with two codeseparators,
365
+ # or an extra one at the end, this prevents all those possible incompatibilities.
366
+ # Note: this normally never happens because there is no use for OP_CODESEPARATOR.
367
+ # But we have to do that cleanup anyway to not break on rare transaction that use that for lulz.
368
+ # Also: we modify the same subscript which is used several times for multisig check,
369
+ # but that's what BitcoinQT does as well.
370
+ output_script.delete_opcode(BTC::OP_CODESEPARATOR)
371
+
372
+ # Blank out other inputs' signature scripts
373
+ # and replace our input script with a subscript (which is typically a full
374
+ # output script from the previous transaction).
375
+ tx.inputs.each do |txin|
376
+ txin.signature_script = BTC::Script.new
377
+ end
378
+ tx.inputs[input_index].signature_script = output_script
379
+
380
+ # Blank out some of the outputs depending on BTCSignatureHashType
381
+ # Default is SIGHASH_ALL - all inputs and outputs are signed.
382
+ if (hash_type & BTC::SIGHASH_OUTPUT_MASK) == BTC::SIGHASH_NONE
383
+ # Wildcard payee - we can pay anywhere.
384
+ tx.remove_all_outputs
385
+
386
+ # Blank out others' input sequence numbers to let others update transaction at will.
387
+ tx.inputs.each_with_index do |txin, i|
388
+ if i != input_index
389
+ tx.inputs[i].sequence = 0
390
+ end
391
+ end
392
+
393
+ # Single mode assumes we sign an output at the same index as an input.
394
+ # Outputs before the one we need are blanked out. All outputs after are simply removed.
395
+ elsif (hash_type & BTC::SIGHASH_OUTPUT_MASK) == BTC::SIGHASH_SINGLE
396
+
397
+ # Only lock-in the txout payee at same index as txin.
398
+ output_index = input_index;
399
+
400
+ # If output_index is out of bounds, BitcoinQT is returning a 256-bit little-endian 0x01 instead of failing with error.
401
+ # We should do the same to stay compatible.
402
+ if output_index >= tx.outputs.size
403
+ return "\x01" + "\x00"*31
404
+ end
405
+
406
+ # All outputs before the one we need are blanked out. All outputs after are simply removed.
407
+ # This is equivalent to replacing outputs with (i-1) empty outputs and a i-th original one.
408
+ my_output = tx.outputs[output_index]
409
+ tx.remove_all_outputs
410
+ (0...output_index).each do |i|
411
+ tx.add_output(BTC::TransactionOutput.new)
412
+ end
413
+ tx.add_output(my_output)
414
+
415
+ # Blank out others' input sequence numbers to let others update transaction at will.
416
+ tx.inputs.each_with_index do |txin, i|
417
+ if i != input_index
418
+ tx.inputs[i].sequence = 0
419
+ end
420
+ end
421
+ end # if hashtype is none or single
422
+
423
+ # Blank out other inputs completely. This is not recommended for open transactions.
424
+ if (hash_type & BTC::SIGHASH_ANYONECANPAY) != 0
425
+ input = tx.inputs[input_index]
426
+ tx.remove_all_outputs
427
+ tx.add_input(input)
428
+ end
429
+
430
+ # Important: we have to hash transaction together with its hash type.
431
+ # Hash type is appended as a little endian uint32 unlike 1-byte suffix of the signature.
432
+ data = tx.data + BTC::WireFormat.encode_uint32le(hash_type)
433
+ hash = BTC.hash256(data)
434
+
435
+ return hash
436
+ end
437
+
438
+ # Compute a fee for a transaction of a given size with a specified per-KB fee rate.
439
+ # By default uses built-in DEFAULT_FEE_RATE.
440
+ # Makes sure that whole number of fee_rate amounts are paid.
441
+ def self.compute_fee(size, fee_rate: DEFAULT_FEE_RATE)
442
+ return 0 if fee_rate <= 0
443
+ fee = fee_rate*size/1000 # according to Bitcoin Core as of March 15, 2015.
444
+ fee = fee_rate if fee == 0
445
+ fee
446
+ end
447
+
448
+ # Compute a fee for this transaction with a specified per-KB fee rate.
449
+ # By default uses built-in DEFAULT_FEE_RATE.
450
+ def compute_fee(fee_rate: DEFAULT_FEE_RATE)
451
+ self.class.compute_fee(self.data.bytesize, fee_rate: fee_rate)
452
+ end
453
+
454
+ # Returns dictionary representation of the transaction.
455
+ def to_h
456
+ self.dictionary
457
+ end
458
+
459
+ # Returns hex representation of the transaction.
460
+ def to_s
461
+ to_hex
462
+ end
463
+
464
+ # Returns hex representation of the transaction.
465
+ def to_hex
466
+ BTC::Data.hex_from_data(self.data)
467
+ end
468
+
469
+ # Makes a deep copy of a transaction (all inputs and outputs are copied too).
470
+ def dup
471
+ Transaction.new(version: @version,
472
+ inputs: (@inputs || []).map{|txin|txin.dup},
473
+ outputs: (@outputs || []).map{|txout|txout.dup},
474
+ lock_time: @lock_time,
475
+ block_hash: @block_hash,
476
+ block_height: @block_height,
477
+ block_time: @block_time,
478
+ confirmations: @confirmations,
479
+ fee: @fee,
480
+ inputs_amount: @inputs_amount)
481
+ end
482
+
483
+ def ==(other)
484
+ return false if other == nil
485
+ self.data == other.data
486
+ end
487
+ alias_method :eql?, :==
488
+
489
+ def inspect
490
+ %{#<#{self.class.name}:#{transaction_id}} +
491
+ %{ v#{version}} +
492
+ (lock_time > 0 ? %{ lock_time:#{lock_time} #{lock_time > LOCKTIME_THRESHOLD ? 'sec' : 'block'}} : "") +
493
+ %{ inputs:[#{inputs.map{|i|i.inspect(:light)}.join(", ")}]} +
494
+ %{ outputs:[#{outputs.map{|o|o.inspect(:light)}.join(", ")}]} +
495
+ %{>}
496
+ end
497
+
498
+ end
499
+ end
500
+
501
+ # if $0 == __FILE__
502
+ # require_relative "../btcruby.rb"
503
+ # require 'pp'
504
+ # include BTC
505
+ #
506
+ # tx = Transaction.new
507
+ # puts tx.inspect
508
+ # pp tx.to_h
509
+ # puts tx.to_s
510
+ #
511
+ # end
@@ -0,0 +1,15 @@
1
+ module BTC
2
+ TransactionBuilder
3
+ class TransactionBuilder
4
+ class Error < BTCError; end
5
+
6
+ # Change address is not specified
7
+ class MissingChangeAddressError < Error; end
8
+
9
+ # Unspent outputs are missing. Maybe because input_addresses are not specified.
10
+ class MissingUnspentOutputsError < Error; end
11
+
12
+ # Unspent outputs are not sufficient to build the transaction.
13
+ class InsufficientFundsError < Error; end
14
+ end
15
+ end
@@ -0,0 +1,54 @@
1
+ module BTC
2
+ TransactionBuilder
3
+ class TransactionBuilder
4
+
5
+ # Interface for providing unspent outputs to transaction builder.
6
+ # You can use it instead of Enumerable attribute `unspent_outputs` to customize which unspents to be used.
7
+ module Provider
8
+ # Returns an Enumerable object yielding unspent outputs for the given addresses.
9
+ # Each unspent output is a BTC::TransactionOutput instance with non-nil `transaction_hash` and `index` attributes.
10
+ # Additional information about outputs and fees may be used to optimize the set of unspents.
11
+ # If builder's `outputs_amount` is nil, all available unspent outputs are expected to be returned.
12
+ def unspent_outputs(transaction_builder)
13
+ []
14
+ end
15
+
16
+ # Allows provider to track unspents that are actually used by the transaction builder,
17
+ # so it can avoid providing the same outputs to another transaction builder.
18
+ def consume_unspent_output(output)
19
+ #puts "========> BTCRUBY: Provider #{self.object_id} consumes output #{btc_txbuilder_outpoint_id(output)}."
20
+ @btc_txbuilder_provider_consumed_unspent_outputs ||= {}
21
+ @btc_txbuilder_provider_consumed_unspent_outputs[btc_txbuilder_outpoint_id(output)] = 1
22
+ self
23
+ end
24
+
25
+ def consumed_unspent_output?(output)
26
+ #puts "========> BTCRUBY: Does Provider #{self.object_id} have output #{btc_txbuilder_outpoint_id(output)}?"
27
+ !!((@btc_txbuilder_provider_consumed_unspent_outputs ||= {})[btc_txbuilder_outpoint_id(output)])
28
+ end
29
+
30
+ def btc_txbuilder_outpoint_id(output)
31
+ raise ArgumentError, "Output must have txid" if !output.transaction_hash
32
+ raise ArgumentError, "Output must have txid" if !output.index
33
+ output.outpoint_id
34
+ end
35
+
36
+ # Creates a block-based provider:
37
+ # txbuilder.provider = TransactionBuilder::Provider.new {|builder| [...] }
38
+ def self.new(&block)
39
+ BlockProvider.new(&block)
40
+ end
41
+
42
+ class BlockProvider # private
43
+ include Provider
44
+ def initialize(&block)
45
+ @block = block
46
+ end
47
+ def unspent_outputs(txbuilder)
48
+ @block.call(txbuilder)
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,73 @@
1
+ module BTC
2
+ TransactionBuilder
3
+ class TransactionBuilder
4
+
5
+ # Result object containing transaction itself and various info about it.
6
+ # You get this object from `TransactionBuilder#build` method.
7
+ class Result
8
+
9
+ # BTC::Transaction instance. Each input is either signed (if WIF was used)
10
+ # or contains an unspent output's script as its signature_script.
11
+ # Unsigned inputs are marked using #unsigned_input_indexes attribute.
12
+ attr_reader :transaction
13
+
14
+ # List of input indexes that are not signed.
15
+ # Empty list means all indexes are signed.
16
+ attr_reader :unsigned_input_indexes
17
+
18
+ # Total fee for the composed transaction.
19
+ # Equals (inputs_amount - outputs_amount)
20
+ attr_reader :fee
21
+
22
+ # Total amount on the inputs.
23
+ attr_reader :inputs_amount
24
+
25
+ # Total amount on the outputs.
26
+ attr_reader :outputs_amount
27
+
28
+ # Amount in satoshis sent to a change address.
29
+ # change_amount = outputs_amount - sent_amount
30
+ attr_reader :change_amount
31
+
32
+ # Amount in satoshis sent to outputs specified by the user.
33
+ # sent_amount = outputs_amount - change_amount
34
+ attr_reader :sent_amount
35
+
36
+ def initialize
37
+ self.transaction = Transaction.new
38
+ self.transaction.inputs_amount = 0
39
+ self.unsigned_input_indexes = []
40
+ self.fee = 0
41
+ self.outputs_amount = 0
42
+ self.change_amount = 0
43
+ self.inputs_amount = 0
44
+ end
45
+
46
+ def inputs_amount=(amount)
47
+ @inputs_amount = amount
48
+ @transaction.inputs_amount = amount
49
+ end
50
+ end
51
+
52
+ # Internal-only setters.
53
+ class Result
54
+ attr_accessor :transaction
55
+ attr_accessor :unsigned_input_indexes
56
+ attr_accessor :fee
57
+ attr_accessor :inputs_amount
58
+ attr_accessor :outputs_amount
59
+ attr_accessor :change_amount
60
+ def sent_amount
61
+ outputs_amount - change_amount
62
+ end
63
+
64
+ def unsigned_input_indices
65
+ unsigned_input_indexes
66
+ end
67
+
68
+ def unsigned_input_indices=(arr)
69
+ self.unsigned_input_indexes = arr
70
+ end
71
+ end
72
+ end
73
+ end