block_io 2.0.0 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+