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,28 @@
1
+ module BTC
2
+ TransactionBuilder
3
+ class TransactionBuilder
4
+
5
+ # Interface for signing inputs used by transaction builder.
6
+ # As an alternative, you may provide `WIF` objects as `input_addresses` to have
7
+ # transaction builder sign simple P2PK and P2PKH inputs automatically.
8
+ module Signer
9
+
10
+ # Returns a signing BTC::Key instance to for a given BTC::TransactionOutput that is being spent
11
+ # and a corresponding BTC::Address.
12
+ # Used to sign inputs spending simple P2PK and P2PKH outputs.
13
+ # Other outputs must be signed via `signature_script_for_input_provider` or directly.
14
+ # If nil is returned, more generic method will be tried.
15
+ def signing_key_for_output(output: nil, address: nil)
16
+ nil
17
+ end
18
+
19
+ # Returns a BTC::Script instance that signs BTC::TransactionInput.
20
+ # Second argument is BTC::TransactionOutput that is being spent in that input.
21
+ # Returns a signature script (BTC::Script) or nil.
22
+ # If nil is returned, input is left unsigned.
23
+ def signature_script_for_input(input: nil, output: nil)
24
+ nil
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,520 @@
1
+ # Note: additional files are required in the bottom of the file.
2
+
3
+ module BTC
4
+ # TransactionBuilder allows to build arbitrary transactions using any source of
5
+ # unspent outputs and deferring signing to the user if needed.
6
+ # Features:
7
+ # - full spending feature vs. multi-output spending feature.
8
+ # - BIP44-friendly spending algorithm by default, but you can opt out.
9
+ # - flexible change/dust controls
10
+ # - flexible inputs API
11
+ # - flexible signing ability
12
+ # - support for compressed/uncompressed keys
13
+ # - support for pay-to-pubkey scripts
14
+
15
+ # TransactionBuilder composes and optionally sings a transaction.
16
+ class TransactionBuilder
17
+
18
+ # Network to validate provided addresses against.
19
+ # Default value is `Network.default`.
20
+ attr_accessor :network
21
+
22
+ # An array of BTC::TransactionOutput instances specifying how many coins to spend and how.
23
+ # If the array is empty, all unspent outputs are spent to the change address.
24
+ attr_accessor :outputs
25
+
26
+ # Change address (base58-encoded string or BTC::Address).
27
+ # Must be specified, but may not be used if change is too small.
28
+ attr_accessor :change_address
29
+
30
+ # Addresses from which to fetch the inputs.
31
+ # Could be base58-encoded address or BTC::Address instances.
32
+ # If any address is a WIF, the corresponding input will be automatically signed with SIGHASH_ALL.
33
+ # Otherwise, the signature_script in the input will be set to output script from unspent output.
34
+ attr_accessor :input_addresses
35
+
36
+ # Prepended unspent outputs with valid `index` and `transaction_hash` attributes.
37
+ # These will be included before some optional unspent outputs.
38
+ attr_accessor :prepended_unspent_outputs
39
+
40
+ # Actual available BTC::TransactionOutput's to spend.
41
+ # If not specified, builder will fetch and remember unspent outputs using `provider`.
42
+ # Only necessary inputs will be selected for spending.
43
+ # If TransactionOutput#confirmations attribute is not nil, outputs are sorted
44
+ # from oldest to newest, unless #keep_unspent_outputs_order is set to true.
45
+ attr_accessor :unspent_outputs
46
+
47
+ # Array of non-published transactions, which outputs can be consumed.
48
+ attr_accessor :parent_transactions
49
+
50
+ # Provider of the unspent outputs.
51
+ # If not specified, `unspent_outputs` must be provided.
52
+ attr_accessor :provider
53
+
54
+ # Signer for the inputs.
55
+ # If not provided, inputs will be left unsigned.
56
+ attr_accessor :signer
57
+
58
+ # Miner's fee per kilobyte (1000 bytes).
59
+ # Default is Transaction::DEFAULT_FEE_RATE
60
+ attr_accessor :fee_rate
61
+
62
+ # Minimum amount of change below which transaction is not composed.
63
+ # If change amount is non-zero and below this value, more unspent outputs are used.
64
+ # If change amount is zero, change output is not even created and this attribute is not used.
65
+ # Default value equals fee_rate.
66
+ attr_accessor :minimum_change
67
+
68
+ # Amount of change that can be forgone as a mining fee if there are no more
69
+ # unspent outputs available. If equals zero, no amount is allowed to be forgone.
70
+ # Default value equals minimum_change.
71
+ # This means builder will never fail with "insufficient funds" just because it could not
72
+ # find enough unspents for big enough change. In worst case it will forgo the change
73
+ # as a part of the mining fee. Set to 0 to avoid wasting a single satoshi.
74
+ attr_accessor :dust_change
75
+
76
+ # If true, does not sort unspent_outputs by confirmations number.
77
+ # Default is false, but order will be preserved if #confirmations attribute is nil.
78
+ attr_accessor :keep_unspent_outputs_order
79
+
80
+ # Introspection methods. Used by Provider during `build` process.
81
+
82
+ # Public addresses used on the inputs.
83
+ # Composed by converting all `WIF` instance in `input_addresses` to corresponding public addresses.
84
+ attr_reader :public_addresses
85
+
86
+ # A total amount in satoshis to be spent in all outputs (not including change output).
87
+ # If equals `nil`, all available unspent outputs for the public_address are expected to be returned
88
+ # ("swiping the keys").
89
+ attr_reader :outputs_amount
90
+
91
+ # A total size of all outputs in bytes (including change output).
92
+ attr_reader :outputs_size
93
+
94
+
95
+ # Implementation of the attributes declared above
96
+ # ===============================================
97
+
98
+ def network
99
+ @network ||= Network.default
100
+ end
101
+
102
+ def input_addresses=(addresses)
103
+ @input_addresses = addresses
104
+ # Normalize addresses to make them all BTC::Address instances
105
+ if @input_addresses
106
+ @input_addresses = @input_addresses.map do |addr|
107
+ BTC::Address.parse(addr).tap do |a|
108
+ if !a.is_a?(BTC::BitcoinPaymentAddress) && !a.is_a?(WIF)
109
+ raise ArgumentError, "Expected BitcoinPaymentAddress and WIF instances"
110
+ end
111
+ if a.network != self.network
112
+ raise ArgumentError, "Network mismatch. Expected input address for #{self.network.name}."
113
+ end
114
+ end
115
+ end
116
+ end
117
+ @unspent_outputs = nil
118
+ end
119
+
120
+ def unspent_outputs
121
+ # Lazily fetch unspent outputs using data provider block.
122
+ @unspent_outputs ||= self.internal_provider.unspent_outputs(self)
123
+ end
124
+
125
+ def provider=(provider)
126
+ @provider = provider
127
+ @unspent_outputs = nil
128
+ end
129
+
130
+ def internal_provider
131
+ @internal_provider ||= (self.provider || Provider.new{|txb| []})
132
+ end
133
+
134
+ def change_address=(change_address)
135
+ if change_address
136
+ addr = BTC::BitcoinPaymentAddress.parse(change_address)
137
+ if addr.network != self.network
138
+ raise ArgumentError, "Network mismatch. Expected change address for #{self.network.name}."
139
+ end
140
+ @change_address = addr
141
+ else
142
+ @change_address = nil
143
+ end
144
+ end
145
+
146
+ def fee_rate
147
+ @fee_rate ||= Transaction::DEFAULT_FEE_RATE
148
+ end
149
+
150
+ def minimum_change
151
+ @minimum_change || self.fee_rate
152
+ end
153
+
154
+ def dust_change
155
+ @dust_change || self.minimum_change
156
+ end
157
+
158
+ def public_addresses
159
+ (@input_addresses || []).map{|a| a.public_address }
160
+ end
161
+
162
+ def outputs_amount
163
+ if !self.outputs || self.outputs.size == 0
164
+ return nil
165
+ end
166
+ self.outputs.inject(0){|sum, o| sum + o.value }
167
+ end
168
+
169
+ def outputs_size
170
+ raise ArgumentError, "Change address must be specified" if !self.change_address
171
+ (self.outputs || []).inject(0){|sum, output| sum + output.data.bytesize } +
172
+ TransactionOutput.new(value:MAX_MONEY, script: self.change_address.script).data.bytesize
173
+ end
174
+
175
+ # Attempts to build a transaction
176
+ def build
177
+
178
+ if !self.change_address
179
+ raise MissingChangeAddressError
180
+ end
181
+
182
+ result = Result.new
183
+
184
+ add_input_for_utxo = proc do |utxo|
185
+ raise ArgumentError, "Unspent output must contain index" if !utxo.index
186
+ raise ArgumentError, "Unspent output must contain transaction_hash" if !utxo.transaction_hash
187
+
188
+ if !self.internal_provider.consumed_unspent_output?(utxo)
189
+ self.internal_provider.consume_unspent_output(utxo)
190
+
191
+ result.inputs_amount += utxo.value
192
+ txin = TransactionInput.new(
193
+ previous_hash: utxo.transaction_hash,
194
+ previous_index: utxo.index,
195
+ # put the output script here so the signer knows which key to use.
196
+ signature_script: utxo.script)
197
+ txin.transaction_output = utxo
198
+ result.transaction.add_input(txin)
199
+ else
200
+ # UTXO was already consumed possibly by another Tx Builder sharing the same provider.
201
+ end
202
+ end
203
+
204
+ # If no outputs are specified, spend all utxos to a change address, minus the mining fee.
205
+ if (self.outputs || []).size == 0
206
+ result.inputs_amount = 0
207
+
208
+ (self.prepended_unspent_outputs || []).each(&add_input_for_utxo)
209
+ (self.unspent_outputs || []).each(&add_input_for_utxo)
210
+
211
+ if result.transaction.inputs.size == 0
212
+ raise MissingUnspentOutputsError, "Missing unspent outputs"
213
+ end
214
+
215
+ # Value will be determined after computing the fee
216
+ change_output = TransactionOutput.new(value: 0, script: self.change_address.public_address.script)
217
+ result.transaction.add_output(change_output)
218
+
219
+ result.fee = compute_fee_for_transaction(result.transaction, self.fee_rate)
220
+ result.outputs_amount = result.inputs_amount - result.fee
221
+ result.change_amount = 0
222
+
223
+ # Check if inputs cover the fees
224
+ if result.outputs_amount < 0
225
+ raise InsufficientFundsError
226
+ end
227
+
228
+ # Warn if the output amount is relatively small.
229
+ if result.outputs_amount < result.fee
230
+ Diagnostics.current.add_message("BTC::TransactionBuilder: Warning! Spending all unspent outputs returns less than a mining fee. (#{result.outputs_amount} < #{result.fee})")
231
+ end
232
+
233
+ # Set the output value as needed
234
+ result.transaction.outputs[0].value = result.outputs_amount
235
+
236
+ # For each address that is a WIF, locate the matching input and sign it.
237
+ attempt_to_sign_transaction(result)
238
+
239
+ return result
240
+ end # if no specific outputs required
241
+
242
+ # We are having one or more outputs (e.g. normal payment)
243
+ # Need to find appropriate unspents and compose a transaction.
244
+
245
+ # Prepare all outputs.
246
+ # result.outputs_amount will also contain a fee after it's calculated.
247
+ (self.outputs || []).each do |txout|
248
+ result.outputs_amount += txout.value
249
+ result.transaction.add_output(txout)
250
+ end
251
+
252
+ # We'll determine final change value depending on inputs.
253
+ # Setting default to MAX_MONEY will protect against a bug when we fail to update the amount and
254
+ # spend unexpected amount on mining fees.
255
+ change_output = TransactionOutput.new(value: MAX_MONEY, script: self.change_address.public_address.script)
256
+ result.transaction.add_output(change_output)
257
+
258
+ mandatory_utxos = (self.prepended_unspent_outputs || []).to_a.dup
259
+
260
+ # We have specific outputs with specific amounts, so we need to select the best amount of coins.
261
+ # To play nice with BIP32/BIP44 change addresses (that need to monitor an increasing amount of addresses),
262
+ # we'll spend oldest outputs first and will add more and more newer outputs until we cover fees
263
+ # and change output is either zero or above the dust limit.
264
+ # If `confirmations` attribute is nil, order is preserved.
265
+ utxos = self.unspent_outputs || []
266
+ if self.keep_unspent_outputs_order
267
+ sorted_utxos = utxos.to_a.dup
268
+ else
269
+ sorted_utxos = utxos.to_a.sort_by{|txout| -(txout.confirmations || -1) } # oldest first
270
+ end
271
+
272
+ if self.parent_transactions
273
+ # Can repeat some outputs in mandatory_utxos or sorted_utxos,
274
+ # but double-spending will be prevented by provider.
275
+ self.parent_transactions.each do |parenttx|
276
+ sorted_utxos += parenttx.outputs
277
+ end
278
+ end
279
+
280
+ all_utxos = (sorted_utxos + mandatory_utxos)
281
+
282
+ while true
283
+ if (sorted_utxos.size + mandatory_utxos.size) == 0
284
+ raise InsufficientFundsError
285
+ end
286
+
287
+ utxo = nil
288
+ while (sorted_utxos.size + mandatory_utxos.size) > 0
289
+ utxo = if mandatory_utxos.size > 0
290
+ mandatory_utxos.shift
291
+ else
292
+ sorted_utxos.shift
293
+ end
294
+ if utxo.value > 0 &&
295
+ !utxo.script.op_return_script? &&
296
+ !self.internal_provider.consumed_unspent_output?(utxo)
297
+ self.internal_provider.consume_unspent_output(utxo)
298
+ break
299
+ else
300
+ #puts "CONSUMED UTXO, SKIPPING: #{utxo.transaction_id}:#{utxo.index} (#{utxo.value})"
301
+ # Continue reading utxos to find one that is not consumed yet.
302
+ utxo = nil
303
+ end
304
+ end
305
+
306
+ if !utxo
307
+ # puts "ALL UTXOS:"
308
+ # all_utxos.each do |utxo|
309
+ # puts "--> #{utxo.transaction_id}:#{utxo.index} (#{utxo.value})"
310
+ # end
311
+ raise InsufficientFundsError
312
+ end
313
+
314
+ result.inputs_amount += utxo.value
315
+
316
+ raise ArgumentError, "Transaction hash must be non-nil in unspent output" if !utxo.transaction_hash
317
+ raise ArgumentError, "Index must be non-nil in unspent output" if !utxo.index
318
+
319
+ txin = TransactionInput.new(previous_hash: utxo.transaction_hash,
320
+ previous_index: utxo.index,
321
+ # put the output script here so the signer knows which key to use.
322
+ signature_script: utxo.script)
323
+ txin.transaction_output = utxo
324
+ result.transaction.add_input(txin)
325
+
326
+ # Do not check the result before we included all the mandatory utxos.
327
+ if mandatory_utxos.size == 0
328
+ # Before computing the fee, quick check if we have enough inputs to cover the outputs.
329
+ # If not, go and add one more utxo before wasting time computing fees.
330
+ if result.inputs_amount >= result.outputs_amount
331
+ fee = compute_fee_for_transaction(result.transaction, self.fee_rate)
332
+
333
+ change = result.inputs_amount - result.outputs_amount - fee
334
+
335
+ if change >= self.minimum_change
336
+
337
+ # We have a big enough change, set missing values and return.
338
+
339
+ change_output.value = change
340
+ result.change_amount = change
341
+ result.outputs_amount += change
342
+ result.fee = fee
343
+ attempt_to_sign_transaction(result)
344
+ return result
345
+
346
+ elsif change > self.dust_change && change < self.minimum_change
347
+
348
+ # We have a shitty change: not small enough to forgo, not big enough to be useful.
349
+ # Try adding more utxos on the next cycle (or fail if no more utxos are available).
350
+
351
+ elsif change >= 0 && change <= self.dust_change
352
+ # This also includes the case when change is exactly zero satoshis.
353
+ # Remove the change output, keep existing outputs_amount, set fee and try to sign.
354
+ result.transaction.outputs = result.transaction.outputs[0, result.transaction.outputs.size - 1]
355
+ result.change_amount = 0
356
+ result.fee = fee
357
+ attempt_to_sign_transaction(result)
358
+ return result
359
+
360
+ else
361
+ # Change is negative, we need more funds for this transaction.
362
+ # Try adding more utxos on the next cycle.
363
+
364
+ end
365
+
366
+ end # if inputs_amount >= outputs_amount
367
+ end # if no more mandatory outputs to add
368
+ end # while true
369
+ end # build_transaction
370
+
371
+ private
372
+
373
+ # Helper to compute total fee for a given transaction.
374
+ # Simulates signatures to estimate final size.
375
+ def compute_fee_for_transaction(tx, fee_rate)
376
+ # Compute fees for this tx by composing a tx with properly sized dummy signatures.
377
+ simulated_tx = tx.dup
378
+ simulated_tx.inputs.each do |txin|
379
+ txout_script = txin.transaction_output.script
380
+ txin.signature_script = txout_script.simulated_signature_script(strict: false) || txout_script
381
+ end
382
+ return simulated_tx.compute_fee(fee_rate: fee_rate)
383
+ end
384
+
385
+ def attempt_to_sign_transaction(result)
386
+ # By default, all inputs are marked to be signed.
387
+ result.unsigned_input_indexes = (0...(result.transaction.inputs.size)).to_a
388
+
389
+ return if result.transaction.inputs.size == 0
390
+
391
+ fill_dict_with_key = proc do |dict, key|
392
+ ck = key.compressed_key
393
+ uck = key.uncompressed_key
394
+ p2pkh_compressed_script = PublicKeyAddress.new(public_key: ck.public_key).script
395
+ p2pkh_uncompressed_script = PublicKeyAddress.new(public_key: uck.public_key).script
396
+ p2pk_compressed_script = Script.new << ck.public_key << OP_CHECKSIG
397
+ p2pk_uncompressed_script = Script.new << uck.public_key << OP_CHECKSIG
398
+ dict[p2pkh_compressed_script.data] = ck
399
+ dict[p2pkh_uncompressed_script.data] = uck
400
+ dict[p2pk_compressed_script.data] = ck
401
+ dict[p2pk_uncompressed_script.data] = uck
402
+ end
403
+
404
+ # Arrange WIFs with script_data => Key
405
+ keys_for_script_data = (@input_addresses || []).find_all{|a|a.is_a?(WIF)}.inject({}) do |dict, wif|
406
+ # We support two kinds of scripts: p2pkh (modern style) and p2pk (old style)
407
+ # For each of these we support compressed and uncompressed pubkeys.
408
+ key = wif.key
409
+ fill_dict_with_key.call(dict, key)
410
+ dict
411
+ end
412
+
413
+ # We should group all inputs by their output script.
414
+ # For each group, try to find a key among WIFs, or ask the provider for a key.
415
+ # If key is not available or the script is not a standard one, ask signer for a raw signature_script.
416
+ grouped_inputs = result.transaction.inputs.group_by do |txin|
417
+ txin.signature_script.data
418
+ end
419
+
420
+ grouped_inputs.each do |script_data, inputs|
421
+ first_input = inputs.first
422
+ script = first_input.signature_script
423
+ key = nil # will be used only for standard p2pk, p2pkh scripts.
424
+ if script.p2pk? || script.p2pkh?
425
+ address = script.standard_address(network: self.network)
426
+ key = keys_for_script_data[script_data]
427
+ if !key && (key = self.signer ? self.signer.signing_key_for_output(output: first_input.transaction_output, address: address) : nil)
428
+ if !key.private_key
429
+ raise ArgumentError, "Provided BTC::Key must contain the private key"
430
+ end
431
+ fill_dict_with_key.call(keys_for_script_data, key) # so we get the properly compressed version for the current script.
432
+ key = keys_for_script_data[script_data]
433
+ # Note: if incorrect key was provided, we won't find a matching script data.
434
+ if !key
435
+ raise ArgumentError, "Key does not match the output"
436
+ end
437
+ end
438
+ end
439
+
440
+ # If we finally have a key, it's of correct compression and only for p2pk, p2pkh scripts.
441
+ if key
442
+ hashtype = SIGHASH_ALL
443
+ encoded_hashtype = WireFormat.encode_uint8(hashtype)
444
+
445
+ inputs.each do |txin|
446
+ output_script = txin.signature_script.dup
447
+ sighash = result.transaction.signature_hash(input_index: txin.index, output_script: output_script, hash_type: hashtype)
448
+ if script.p2pk?
449
+ txin.signature_script = Script.new << (key.ecdsa_signature(sighash) + encoded_hashtype)
450
+ result.unsigned_input_indexes.delete(txin.index)
451
+ elsif script.p2pkh?
452
+ txin.signature_script = Script.new << (key.ecdsa_signature(sighash) + encoded_hashtype) << key.public_key
453
+ result.unsigned_input_indexes.delete(txin.index)
454
+ else
455
+ BTC::Invariant(false, "All txins are expected to be of the same type")
456
+ # Unknown script, leave unsigned.
457
+ end
458
+ end
459
+ else # no keys - ask signer
460
+ inputs.each do |txin|
461
+ sigscript = self.signer ? self.signer.signature_script_for_input(input: txin, output: txin.transaction_output) : nil
462
+ if sigscript
463
+ txin.signature_script = sigscript
464
+ result.unsigned_input_indexes.delete(txin.index)
465
+ end
466
+ end
467
+ end
468
+ end
469
+ end # attempt_to_sign_transaction
470
+
471
+
472
+ end
473
+ end
474
+
475
+ require_relative 'transaction_builder/errors.rb'
476
+ require_relative 'transaction_builder/result.rb'
477
+ require_relative 'transaction_builder/provider.rb'
478
+ require_relative 'transaction_builder/signer.rb'
479
+
480
+ if $0 == __FILE__
481
+
482
+ # require_relative '../btcruby.rb'
483
+ # require 'pp'
484
+ # include BTC
485
+ #
486
+ # def mock_keys
487
+ # @mock_keys ||= [
488
+ # Key.random,
489
+ # Key.random
490
+ # ]
491
+ # end
492
+ #
493
+ # def mock_addresses
494
+ # mock_keys.map{|k| PublicKeyAddress.new(public_key: k.public_key) }
495
+ # end
496
+ #
497
+ # def mock_utxos
498
+ # scripts = mock_addresses.map{|a| a.script }
499
+ # (0...100).map do |i|
500
+ # TransactionOutput.new(value: 1000_00, script: scripts[i % scripts.size])
501
+ # end
502
+ # end
503
+ #
504
+ # @builder = TransactionBuilder.new
505
+ # @all_utxos = mock_utxos
506
+ # @builder.input_addresses = mock_addresses
507
+ # @builder.provider = TransactionBuilder::Provider.new do |txb|
508
+ # scripts = txb.public_addresses.map{|a| a.script }.uniq
509
+ # @all_utxos.find_all{|utxo| scripts.include?(utxo.script) }
510
+ # end
511
+ #
512
+ # @builder.change_address = Address.parse("1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")
513
+ #
514
+ # tx = @builder.transaction
515
+ #
516
+ # puts tx.inspect
517
+
518
+
519
+ end
520
+