block_io 2.0.0 → 3.0.3

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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/{.appveyor.yml → .appveyor.yml-disabled} +2 -2
  3. data/.gitignore +1 -0
  4. data/.travis.yml +1 -1
  5. data/LICENSE +1 -1
  6. data/README.md +18 -14
  7. data/block_io.gemspec +7 -7
  8. data/examples/basic.rb +29 -5
  9. data/examples/dtrust.rb +43 -24
  10. data/examples/sweeper.rb +21 -16
  11. data/lib/block_io/api_exception.rb +11 -0
  12. data/lib/block_io/chainparams/BTC.yml +8 -0
  13. data/lib/block_io/chainparams/BTCTEST.yml +8 -0
  14. data/lib/block_io/chainparams/DOGE.yml +8 -0
  15. data/lib/block_io/chainparams/DOGETEST.yml +8 -0
  16. data/lib/block_io/chainparams/LTC.yml +8 -0
  17. data/lib/block_io/chainparams/LTCTEST.yml +8 -0
  18. data/lib/block_io/client.rb +145 -80
  19. data/lib/block_io/extended_bitcoinrb.rb +132 -0
  20. data/lib/block_io/helper.rb +211 -53
  21. data/lib/block_io/key.rb +11 -124
  22. data/lib/block_io/version.rb +1 -1
  23. data/lib/block_io.rb +3 -2
  24. data/spec/client_misc_spec.rb +76 -0
  25. data/spec/client_spec.rb +23 -178
  26. data/spec/dtrust_spec.rb +167 -0
  27. data/spec/helper_spec.rb +117 -7
  28. data/spec/key_spec.rb +50 -19
  29. data/spec/larger_transaction_spec.rb +371 -0
  30. data/spec/sweep_spec.rb +115 -0
  31. data/spec/test-cases/.gitignore +2 -0
  32. data/spec/test-cases/LICENSE +21 -0
  33. data/spec/test-cases/README.md +2 -0
  34. data/spec/test-cases/json/create_and_sign_transaction_response.json +61 -0
  35. data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1261 -0
  36. data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1266 -0
  37. data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1271 -0
  38. data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +3816 -0
  39. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_3of5_195inputs.json +2931 -0
  40. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_4of5_195inputs.json +5 -0
  41. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_251inputs.json +3771 -0
  42. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_252inputs.json +3786 -0
  43. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_253inputs.json +3801 -0
  44. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_251inputs.json +5 -0
  45. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_252inputs.json +5 -0
  46. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_253inputs.json +5 -0
  47. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_251inputs.json +3771 -0
  48. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_252inputs.json +3786 -0
  49. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_253inputs.json +3801 -0
  50. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_251inputs.json +5 -0
  51. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_252inputs.json +5 -0
  52. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_253inputs.json +5 -0
  53. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys.json +21 -0
  54. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_4_of_5_keys.json +5 -0
  55. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys.json +21 -0
  56. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_4_of_5_keys.json +5 -0
  57. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys.json +36 -0
  58. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_251outputs.json +591 -0
  59. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_252outputs.json +576 -0
  60. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_253outputs.json +531 -0
  61. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4_of_5_keys.json +5 -0
  62. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_251outputs.json +5 -0
  63. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_252outputs.json +5 -0
  64. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_253outputs.json +5 -0
  65. data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2pkh.json +5 -0
  66. data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh.json +5 -0
  67. data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh.json +5 -0
  68. data/spec/test-cases/json/create_and_sign_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +21 -0
  69. data/spec/test-cases/json/create_and_sign_transaction_response_witness_v1_output.json +11 -0
  70. data/spec/test-cases/json/get_balance_response.json +8 -0
  71. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_3of5_195inputs.json +1397 -0
  72. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_4of5_195inputs.json +1397 -0
  73. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_251inputs.json +1795 -0
  74. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_252inputs.json +1802 -0
  75. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_253inputs.json +1809 -0
  76. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_251inputs.json +1789 -0
  77. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_252inputs.json +1802 -0
  78. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_253inputs.json +1809 -0
  79. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_251inputs.json +1795 -0
  80. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_252inputs.json +1802 -0
  81. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_253inputs.json +1809 -0
  82. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_251inputs.json +1795 -0
  83. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_252inputs.json +1802 -0
  84. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_253inputs.json +1809 -0
  85. data/spec/test-cases/json/prepare_dtrust_transaction_response_p2sh.json +45 -0
  86. data/spec/test-cases/json/prepare_dtrust_transaction_response_p2wsh_over_p2sh.json +45 -0
  87. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0.json +52 -0
  88. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_251outputs.json +1805 -0
  89. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_252outputs.json +1804 -0
  90. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_253outputs.json +1789 -0
  91. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_251outputs.json +1805 -0
  92. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_252outputs.json +1804 -0
  93. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_253outputs.json +1789 -0
  94. data/spec/test-cases/json/prepare_sweep_transaction_response_p2pkh.json +35 -0
  95. data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh.json +35 -0
  96. data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh_over_p2sh.json +35 -0
  97. data/spec/test-cases/json/prepare_transaction_response.json +164 -0
  98. data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1796 -0
  99. data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1803 -0
  100. data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1810 -0
  101. data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +5367 -0
  102. data/spec/test-cases/json/prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +76 -0
  103. data/spec/test-cases/json/prepare_transaction_response_witness_v1_output.json +64 -0
  104. data/spec/test-cases/json/summarize_prepared_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +6 -0
  105. metadata +203 -57
  106. data/examples/max_withdrawal.rb +0 -29
  107. data/lib/block_io/constants.rb +0 -10
  108. data/spec/data/sign_and_finalize_dtrust_withdrawal_request.json +0 -1
  109. data/spec/data/sign_and_finalize_sweep_request.json +0 -1
  110. data/spec/data/sign_and_finalize_withdrawal_request.json +0 -4
  111. data/spec/data/sweep_from_address_response.json +0 -1
  112. data/spec/data/withdraw_from_dtrust_address_response.json +0 -1
  113. data/spec/data/withdraw_response.json +0 -1227
  114. data/spec/rfc6979_spec.rb +0 -59
  115. data/spec/withdraw_spec.rb +0 -90
@@ -16,20 +16,12 @@ module BlockIo
16
16
  raise "Must provide an API Key." unless args.key?(:api_key) and args[:api_key].to_s.size > 0
17
17
 
18
18
  @api_key = args[:api_key]
19
- @encryption_key = Helper.pinToAesKey(args[:pin] || "") if args.key?(:pin)
19
+ @pin = args[:pin]
20
20
  @version = args[:version] || 2
21
21
  @hostname = args[:hostname] || "block.io"
22
22
  @proxy = args[:proxy] || {}
23
- @keys = args[:keys] || []
24
- @use_low_r = args[:use_low_r]
25
- @raise_exception_on_error = args[:raise_exception_on_error] || false
23
+ @keys = {}
26
24
 
27
- raise Exception.new("Keys must be provided as an array.") unless @keys.is_a?(Array)
28
- raise Exception.new("Keys must be BlockIo::Key objects.") unless @keys.all?{|key| key.is_a?(BlockIo::Key)}
29
-
30
- # make a hash of the keys we've been given
31
- @keys = @keys.inject({}){|h,v| h[v.public_key] = v; h}
32
-
33
25
  raise Exception.new("Must specify hostname, port, username, password if using a proxy.") if @proxy.keys.size > 0 and [:hostname, :port, :username, :password].any?{|x| !@proxy.key?(x)}
34
26
 
35
27
  @conn = ConnectionPool.new(:size => args[:pool_size] || 5) { http = HTTP.headers(:accept => "application/json", :user_agent => "gem:block_io:#{VERSION}");
@@ -50,109 +42,176 @@ module BlockIo
50
42
  raise Exception.new("Parameter keys must be symbols. For instance: :label => 'default' instead of 'label' => 'default'") unless args[0].nil? or args[0].keys.all?{|x| x.is_a?(Symbol)}
51
43
  raise Exception.new("Cannot pass PINs to any calls. PINs can only be set when initiating this library.") if !args[0].nil? and args[0].key?(:pin)
52
44
  raise Exception.new("Do not specify API Keys here. Initiate a new BlockIo object instead if you need to use another API Key.") if !args[0].nil? and args[0].key?(:api_key)
53
-
54
- if BlockIo::WITHDRAW_METHODS.key?(method_name) then
55
- # it's a withdrawal call
56
- withdraw(args[0], method_name)
57
- elsif BlockIo::SWEEP_METHODS.key?(method_name) then
45
+
46
+ if method_name.eql?("prepare_sweep_transaction") then
47
+ # we need to ensure @network is set before we allow this
48
+ # we need to send only the public key, not the given private key
58
49
  # we're sweeping from an address
59
- sweep(args[0], method_name)
60
- elsif BlockIo::FINALIZE_SIGNATURE_METHODS.key?(method_name) then
61
- # we're finalize the transaction signatures
62
- finalize_signature(args[0], method_name)
50
+ internal_prepare_sweep_transaction(args[0], method_name)
63
51
  else
64
52
  api_call({:method_name => method_name, :params => args[0] || {}})
65
53
  end
66
54
 
67
55
  end
68
56
 
69
- private
70
-
71
- def withdraw(args = {}, method_name = "withdraw")
57
+ def summarize_prepared_transaction(data)
58
+ # takes the response from prepare_transaction/prepare_dtrust_transaction/prepare_sweep_transaction
59
+ # returns the network fee being paid, the blockio fee being paid, amounts being sent
72
60
 
73
- response = api_call({:method_name => method_name, :params => args})
61
+ input_sum = data['data']['inputs'].map{|input| BigDecimal(input['input_value'])}.inject(:+)
74
62
 
75
- if response["data"].key?("reference_id") then
76
- # Block.io's asking us to provide client-side signatures
63
+ output_values = [BigDecimal(0)]
64
+ blockio_fees = [BigDecimal(0)]
65
+ change_amounts = [BigDecimal(0)]
77
66
 
78
- encrypted_passphrase = response["data"]["encrypted_passphrase"]
67
+ data['data']['outputs'].each do |output|
68
+ if output['output_category'] == 'blockio-fee' then
69
+ blockio_fees << BigDecimal(output['output_value'])
70
+ elsif output['output_category'] == 'change' then
71
+ change_amounts << BigDecimal(output['output_value'])
72
+ else
73
+ # user-specified
74
+ output_values << BigDecimal(output['output_value'])
75
+ end
76
+ end
77
+
78
+ output_sum = output_values.inject(:+)
79
+ blockio_fee = blockio_fees.inject(:+)
80
+ change_amount = change_amounts.inject(:+)
81
+
82
+ network_fee = input_sum - output_sum - blockio_fee - change_amount
83
+
84
+ {
85
+ 'network' => data['data']['network'],
86
+ 'network_fee' => '%0.8f' % network_fee,
87
+ "blockio_fee" => '%0.8f' % blockio_fee,
88
+ "total_amount_to_send" => '%0.8f' % output_sum
89
+ }
90
+
91
+ end
92
+
93
+ def create_and_sign_transaction(data, keys = [])
94
+ # takes data from prepare_transaction, prepare_dtrust_transaction, prepare_sweep_transaction
95
+ # creates the transaction given the inputs and outputs from data
96
+ # signs the transaction using keys (if not provided, decrypts the key using the PIN)
97
+
98
+ set_network(data['data']['network']) if data['data'].key?('network')
79
99
 
80
- if !encrypted_passphrase.nil? and !@keys.key?(encrypted_passphrase["signer_public_key"]) then
81
- # encrypted passphrase was provided, and we do not have the signer's key, so let's extract it first
100
+ raise "Data must be contain one or more inputs" unless data['data']['inputs'].size > 0
101
+ raise "Data must contain one or more outputs" unless data['data']['outputs'].size > 0
102
+ raise "Data must contain information about addresses" unless data['data']['input_address_data'].size > 0 # TODO make stricter
82
103
 
83
- raise Exception.new("PIN not set and no keys provided. Cannot execute withdrawal requests.") unless @encryption_key or @keys.size > 0
104
+ private_keys = keys.map{|x| Key.from_private_key_hex(x)}
84
105
 
85
- key = Helper.extractKey(encrypted_passphrase["passphrase"], @encryption_key, @use_low_r)
86
- raise Exception.new("Public key mismatch for requested signer and ourselves. Invalid Secret PIN detected.") unless key.public_key.eql?(encrypted_passphrase["signer_public_key"])
106
+ # TODO debug all of this
107
+
108
+ inputs = data['data']['inputs']
109
+ outputs = data['data']['outputs']
87
110
 
88
- # store this key for later use
89
- @keys[key.public_key] = key
111
+ tx = Bitcoin::Tx.new
90
112
 
91
- end
113
+ # populate the inputs
114
+ inputs.each do |input|
115
+ tx.in << Bitcoin::TxIn.new(:out_point => Bitcoin::OutPoint.from_txid(input['previous_txid'], input['previous_output_index']))
116
+ end
92
117
 
93
- if @keys.size > 0 then
94
- # if we have at least one key available, try to send signatures back
95
- # if a dtrust withdrawal is used without any keys stored in the BlockIo::Client object, the output of this call will be the previous response from Block.io
96
-
97
- # we just need reference_id and inputs
98
- response["data"] = {"reference_id" => response["data"]["reference_id"], "inputs" => response["data"]["inputs"]}
99
-
100
- # let's sign all the inputs we can
101
- signatures_added = (@keys.size == 0 ? false : Helper.signData(response["data"]["inputs"], @keys))
102
-
103
- # the response object is now signed, let's stringify it and finalize this withdrawal
104
- response = finalize_signature({:signature_data => response["data"]}, "sign_and_finalize_withdrawal") if signatures_added
105
-
106
- # if we provided all the required signatures, this transaction went through
107
- # otherwise Block.io responded with data asking for more signatures and recorded the signature we provided above
108
- # the latter will be the case for dTrust addresses
109
- end
110
-
118
+ # populate the outputs
119
+ outputs.each do |output|
120
+ tx.out << Bitcoin::TxOut.new(:value => (BigDecimal(output['output_value']) * BigDecimal(100000000)).to_i, :script_pubkey => Bitcoin::Script.parse_from_addr(output['receiving_address']))
111
121
  end
112
122
 
113
- response
114
123
 
115
- end
124
+ # some protection against misbehaving machines and/or code
125
+ raise Exception.new("Expected unsigned transaction ID mismatch. Please report this error to support@block.io.") unless (data['data']['expected_unsigned_txid'].nil? or
126
+ data['data']['expected_unsigned_txid'] == tx.txid)
116
127
 
117
- def sweep(args = {}, method_name = "sweep_from_address")
118
- # sweep coins from a given address and key
128
+ # extract key
129
+ encrypted_key = data['data']['user_key']
119
130
 
120
- raise Exception.new("No private_key provided.") unless args.key?(:private_key) and (args[:private_key] || "").size > 0
131
+ if !encrypted_key.nil? and !@keys.key?(encrypted_key['public_key']) then
132
+ # decrypt the key with PIN
121
133
 
122
- key = Key.from_wif(args[:private_key], @use_low_r)
123
- sanitized_args = args.merge({:public_key => key.public_key})
124
- sanitized_args.delete(:private_key)
125
-
126
- response = api_call({:method_name => method_name, :params => sanitized_args})
127
-
128
- if response["data"].key?("reference_id") then
129
- # Block.io's asking us to provide client-side signatures
134
+ raise Exception.new("PIN not set and no keys provided. Cannot sign transaction.") unless !@pin.nil? or @keys.size > 0
130
135
 
131
- # we just need the reference_id and inputs
132
- response["data"] = {"reference_id" => response["data"]["reference_id"], "inputs" => response["data"]["inputs"]}
133
-
134
- # let's sign all the inputs we can
135
- signatures_added = Helper.signData(response["data"]["inputs"], [key])
136
+ key = Helper.dynamicExtractKey(encrypted_key, @pin)
136
137
 
137
- # the response object is now signed, let's stringify it and finalize this transaction
138
- response = finalize_signature({:signature_data => response["data"]}, "sign_and_finalize_sweep") if signatures_added
138
+ raise Exception.new("Public key mismatch for requested signer and ourselves. Invalid Secret PIN detected.") unless key.public_key_hex.eql?(encrypted_key["public_key"])
139
139
 
140
- # if we provided all the required signatures, this transaction went through
140
+ # store this key for later use
141
+ @keys[key.public_key_hex] = key
142
+
141
143
  end
142
144
 
143
- response
145
+ # store the provided keys, if any, for later use
146
+ private_keys.each{|key| @keys[key.public_key_hex] = key}
147
+
148
+ signatures = []
149
+
150
+ if @keys.size > 0 then
151
+ # try to sign whatever we can here and give the user the data back
152
+ # Block.io will check to see if all signatures are present, or return an error otherwise saying insufficient signatures provided
153
+
154
+ i = 0
155
+ while i < inputs.size do
156
+ input = inputs[i]
157
+
158
+ input_address_data = data['data']['input_address_data'].detect{|d| d['address'] == input['spending_address']}
159
+ sighash_for_input = Helper.getSigHashForInput(tx, i, input, input_address_data) # in bytes
160
+
161
+ input_address_data['public_keys'].each do |signer_public_key|
162
+ # sign what we can and append signatures to the signatures object
163
+
164
+ next unless @keys.key?(signer_public_key)
165
+
166
+ signature = @keys[signer_public_key].sign(sighash_for_input).unpack("H*")[0] # in hex
167
+ signatures << {"input_index" => i, "public_key" => signer_public_key, "signature" => signature}
168
+
169
+ end
170
+
171
+ i += 1 # go to next input
172
+ end
173
+
174
+ end
175
+
176
+ # if we have everything we need for this transaction, just finalize the transaction
177
+ if Helper.allSignaturesPresent?(tx, inputs, signatures, data['data']['input_address_data']) then
178
+ Helper.finalizeTransaction(tx, inputs, signatures, data['data']['input_address_data'])
179
+ signatures = [] # no signatures left to append
180
+ end
144
181
 
182
+ # reset keys
183
+ @keys = {}
184
+
185
+ # the response for submitting the transaction
186
+ {"tx_type" => data['data']['tx_type'], "tx_hex" => tx.to_hex, "signatures" => (signatures.size == 0 ? nil : signatures)}
187
+
145
188
  end
146
189
 
147
- def finalize_signature(args = {}, method_name = "sign_and_finalize_withdrawal")
190
+ private
191
+
192
+ def internal_prepare_sweep_transaction(args = {}, method_name = "prepare_sweep_transaction")
148
193
 
149
- raise Exception.new("Object must have reference_id and inputs keys.") unless args.key?(:signature_data) and args[:signature_data].key?("inputs") and args[:signature_data].key?("reference_id")
194
+ # set the network first if not already known
195
+ api_call({:method_name => "get_balance", :params => {}}) if @network.nil?
150
196
 
151
- signatures = {"reference_id" => args[:signature_data]["reference_id"], "inputs" => args[:signature_data]["inputs"]}
197
+ raise Exception.new("No private_key provided.") unless args.key?(:private_key) and (args[:private_key] || "").size > 0
152
198
 
153
- response = api_call({:method_name => method_name, :params => {:signature_data => Oj.dump(signatures)}})
199
+ # ensure the private key never goes to Block.io
200
+ key = Key.from_wif(args[:private_key])
201
+ sanitized_args = args.merge({:public_key => key.public_key_hex})
202
+ sanitized_args.delete(:private_key)
203
+
204
+ @keys[key.public_key_hex] = key # store this in our set of keys for later use
205
+
206
+ api_call({:method_name => method_name, :params => sanitized_args})
154
207
 
155
208
  end
209
+
210
+ def set_network(network)
211
+ # load the chain_params for this network
212
+ @network ||= network
213
+ Bitcoin.chain_params = @network unless @network.to_s.size == 0
214
+ end
156
215
 
157
216
  def api_call(args)
158
217
 
@@ -166,9 +225,15 @@ module BlockIo
166
225
  body = {"status" => "fail", "data" => {"error_message" => "Unknown error occurred. Please report this to support@block.io. Status #{response.code}."}}
167
226
  end
168
227
 
169
- raise Exception.new("#{body["data"]["error_message"]}") if !body["status"].eql?("success") and @raise_exception_on_error
228
+ if !body["status"].eql?("success") then
229
+ # raise an exception on error for easy handling
230
+ # user can extract raw response using e.raw_data
231
+ e = APIException.new("#{body["data"]["error_message"]}")
232
+ e.set_raw_data(body)
233
+ raise e
234
+ end
170
235
 
171
- @network ||= body["data"]["network"] if body["data"].key?("network")
236
+ set_network(body['data']['network']) if body['data'].key?('network')
172
237
 
173
238
  body
174
239
 
@@ -0,0 +1,132 @@
1
+ require 'yaml'
2
+
3
+ module Bitcoin
4
+
5
+ # set network chain params
6
+ def self.chain_params=(name)
7
+ raise "chain params for #{name} is not defined." unless %i(BTC DOGE LTC BTCTEST DOGETEST LTCTEST).include?(name.to_sym)
8
+ @current_chain = nil
9
+ @chain_param = name.to_sym
10
+ end
11
+
12
+ # current network chain params.
13
+ def self.chain_params
14
+ return @current_chain if @current_chain
15
+ return (@current_chain = Bitcoin::ChainParams.get(@chain_param.to_s))
16
+ end
17
+
18
+ class ChainParams
19
+ def self.get(network)
20
+ init(network)
21
+ end
22
+
23
+ def self.init(name)
24
+ i = YAML.load(File.open("#{__dir__}/chainparams/#{name}.yml"))
25
+ i.dust_relay_fee ||= Bitcoin::DUST_RELAY_TX_FEE
26
+ i
27
+ end
28
+ end
29
+
30
+ module Secp256k1
31
+
32
+ module Ruby
33
+
34
+ module_function
35
+
36
+ def sign_ecdsa(data, privkey, extra_entropy)
37
+ privkey = privkey.htb
38
+ private_key = ECDSA::Format::IntegerOctetString.decode(privkey)
39
+ extra_entropy ||= ''
40
+ nonce = RFC6979.generate_rfc6979_nonce(privkey + data, extra_entropy)
41
+
42
+ # port form ecdsa gem.
43
+ r_point = GROUP.new_point(nonce)
44
+
45
+ point_field = ECDSA::PrimeField.new(GROUP.order)
46
+ r = point_field.mod(r_point.x)
47
+ return nil if r.zero?
48
+
49
+ rec = r_point.y & 1
50
+
51
+ e = ECDSA.normalize_digest(data, GROUP.bit_length)
52
+ s = point_field.mod(point_field.inverse(nonce) * (e + r * private_key))
53
+
54
+ # covert to low-s
55
+ if s > (GROUP.order / 2)
56
+ s = GROUP.order - s
57
+ rec = r_point.y & 1
58
+ end
59
+
60
+ return nil if s.zero?
61
+
62
+ signature = ECDSA::Signature.new(r, s).to_der
63
+
64
+ # comment lines below lead to performance issues
65
+ # public_key = Bitcoin::Key.new(priv_key: privkey.bth, :key_type => Bitcoin::Key::TYPES[:compressed]).pubkey # get rid of the key_type warning
66
+ # raise 'Creation of signature failed.' unless Bitcoin::Secp256k1::Ruby.verify_sig(data, signature, public_key)
67
+
68
+ [signature, rec]
69
+ end
70
+
71
+ end
72
+ end
73
+
74
+ class Key
75
+
76
+ def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
77
+ # override so enforce compressed keys
78
+
79
+ raise "key_type must always be Bitcoin::KEY::TYPES[:compressed]" unless key_type == TYPES[:compressed]
80
+ puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
81
+ if key_type
82
+ @key_type = key_type
83
+ compressed = @key_type != TYPES[:uncompressed]
84
+ else
85
+ @key_type = compressed ? TYPES[:compressed] : TYPES[:uncompressed]
86
+ end
87
+ @secp256k1_module = Bitcoin.secp_impl
88
+ @priv_key = priv_key
89
+ if @priv_key
90
+ raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
91
+ end
92
+ if pubkey
93
+ @pubkey = pubkey
94
+ else
95
+ @pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
96
+ end
97
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
98
+ end
99
+
100
+ def public_key_hex
101
+ @pubkey
102
+ end
103
+
104
+ def private_key_hex
105
+ @priv_key
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+
112
+ module Bech32
113
+ # override so we can parse non-Bitcoin Bech32 addresses
114
+
115
+ class SegwitAddr
116
+
117
+ private
118
+ def parse_addr(addr)
119
+ @hrp, data, spec = Bech32.decode(addr)
120
+ raise 'Invalid address.' if hrp.nil? || data[0].nil? || !['bc', 'ltc', 'tb', 'tltc', 'doge', 'tdge'].include?(hrp) # HRP_MAINNET, HRP_TESTNET, HRP_REGTEST].include?(hrp)
121
+ @ver = data[0]
122
+ raise 'Invalid witness version' if @ver > 16
123
+ @prog = convert_bits(data[1..-1], 5, 8, false)
124
+ raise 'Invalid witness program' if @prog.nil? || @prog.length < 2 || @prog.length > 40
125
+ raise 'Invalid witness program with version 0' if @ver == 0 && (@prog.length != 20 && @prog.length != 32)
126
+ raise 'Witness version and encoding spec do not match' if (@ver == 0 && spec != Bech32::Encoding::BECH32) || (@ver != 0 && spec != Bech32::Encoding::BECH32M)
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+