block_io 3.0.5 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/block_io.gemspec +7 -8
- data/lib/block_io/client.rb +95 -59
- data/lib/block_io/extended_bitcoinrb.rb +6 -4
- data/lib/block_io/helper.rb +73 -64
- data/lib/block_io/key.rb +2 -2
- data/lib/block_io/version.rb +1 -1
- data/lib/block_io.rb +1 -2
- data/spec/client_misc_spec.rb +1 -8
- data/spec/client_spec.rb +1 -8
- data/spec/dtrust_spec.rb +3 -11
- data/spec/spec_helper.rb +2 -0
- data/spec/sweep_spec.rb +4 -12
- metadata +33 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 890b54dc8511344d1a39cf22e4379dfadc78d60395464a5bd3b5566fe19e775a
|
4
|
+
data.tar.gz: 0c286e1f3ac4d617034b18d621401eb06d243a18a9cef1a4fe19e39b388c72b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 358f8605db3bd1c6a1931384848d6533a3ecdc0dc41dce7a1b4e9799f6ae67d0819bb34a1d0bbbf8a83cc0d655bd8e5bb8bd573666480dbddf38540d7ba1e14a
|
7
|
+
data.tar.gz: 164c8c5e6689657b7535e689a83395edcd917eeade0aa03dbc3dffd03f2a0e732d0d00498a00d3fa879bc058c50ec4345007abe6c2eebe65eefce431f9c3bc61
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ This Ruby Gem is the official reference client for the Block.io's infrastructure
|
|
6
6
|
|
7
7
|
Add this line to your application's Gemfile:
|
8
8
|
|
9
|
-
gem
|
9
|
+
gem "block_io"
|
10
10
|
|
11
11
|
And then execute:
|
12
12
|
|
@@ -17,6 +17,7 @@ Or install it yourself as:
|
|
17
17
|
$ gem install block_io
|
18
18
|
|
19
19
|
## Changelog
|
20
|
+
*10/28/22*: Version 3.1.0 uses Typhoeus instead of httprb. If using proxy, :url must be provided instead of previous :hostname and :port.
|
20
21
|
*05/30/22*: Version 3.0.5 fixes Litecoin P2SH address version (properly now).
|
21
22
|
*12/26/21*: Version 3.0.4 drops support for EOL Ruby 2.4, 2.5. Supports Ruby 3.1.
|
22
23
|
*09/28/21*: Version 3.0.3 supports witness_v1 outputs (Bech32m).
|
data/block_io.gemspec
CHANGED
@@ -18,12 +18,11 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.required_ruby_version = '>= 2.6.0'
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_development_dependency "bundler", ">= 1.16", "< 3
|
22
|
-
spec.add_development_dependency "rake", "~> 13.0", "
|
23
|
-
spec.add_development_dependency "rspec", "~> 3.
|
24
|
-
spec.add_development_dependency "webmock", "~> 3.
|
25
|
-
spec.add_runtime_dependency "bitcoinrb", "
|
26
|
-
spec.add_runtime_dependency "
|
27
|
-
spec.add_runtime_dependency "oj", "~> 3.0", "< 4
|
28
|
-
spec.add_runtime_dependency "connection_pool", ">= 2.2", "< 3.0"
|
21
|
+
spec.add_development_dependency "bundler", ">= 1.16", "< 3"
|
22
|
+
spec.add_development_dependency "rake", "~> 13.0", "< 14"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0", "< 4"
|
24
|
+
spec.add_development_dependency "webmock", "~> 3.0", "< 4"
|
25
|
+
spec.add_runtime_dependency "bitcoinrb", ">= 1.2.1", "< 2"
|
26
|
+
spec.add_runtime_dependency "typhoeus", "~> 1.0", "< 2"
|
27
|
+
spec.add_runtime_dependency "oj", "~> 3.0", "< 4"
|
29
28
|
end
|
data/lib/block_io/client.rb
CHANGED
@@ -2,8 +2,14 @@ module BlockIo
|
|
2
2
|
|
3
3
|
class Client
|
4
4
|
|
5
|
-
attr_reader :api_key, :version, :network
|
5
|
+
attr_reader :api_key, :version, :network, :api_request_headers
|
6
6
|
|
7
|
+
USER_AGENT = 'gem:block_io:' << VERSION.to_s
|
8
|
+
CONTENT_TYPE = 'application/json; charset=UTF-8'
|
9
|
+
ACCEPT_TYPE = 'application/json'
|
10
|
+
KEEP_ALIVE = 'Keep-Alive'
|
11
|
+
TIMEOUT = 60
|
12
|
+
|
7
13
|
def initialize(args = {})
|
8
14
|
# api_key
|
9
15
|
# pin
|
@@ -13,21 +19,25 @@ module BlockIo
|
|
13
19
|
# pool_size
|
14
20
|
# keys
|
15
21
|
|
16
|
-
raise
|
22
|
+
raise 'Must provide an API Key.' unless args.key?(:api_key) and args[:api_key].to_s.size > 0
|
17
23
|
|
18
24
|
@api_key = args[:api_key]
|
19
25
|
@pin = args[:pin]
|
20
26
|
@version = args[:version] || 2
|
21
|
-
@hostname = args[:hostname] ||
|
22
|
-
@proxy = args[:proxy] || {}
|
27
|
+
@hostname = args[:hostname] || 'block.io'
|
23
28
|
@keys = {}
|
24
29
|
|
25
|
-
|
30
|
+
# prepare proxy settings if provided
|
31
|
+
proxy = args[:proxy] || {}
|
32
|
+
|
33
|
+
if proxy.keys.size > 0 then
|
34
|
+
raise Exception.new('Must specify hostname, port, username, password if using a proxy.') if [:url, :username, :password].any?{|x| !proxy.key?(x)}
|
35
|
+
@proxy = {:proxy => proxy[:url], :proxyuserpwd => "#{proxy[:username]}:#{proxy[:password]}"}.freeze
|
36
|
+
else
|
37
|
+
@proxy = {}
|
38
|
+
end
|
26
39
|
|
27
|
-
@
|
28
|
-
http = http.via(args.dig(:proxy, :hostname), args.dig(:proxy, :port), args.dig(:proxy, :username), args.dig(:proxy, :password)) if @proxy.key?(:hostname);
|
29
|
-
http = http.persistent("https://#{@hostname}");
|
30
|
-
http }
|
40
|
+
@api_request_headers = {'User-Agent' => USER_AGENT, 'Content-Type' => CONTENT_TYPE, 'Accept' => ACCEPT_TYPE, 'Expect' => '', 'Connection' => KEEP_ALIVE, 'Host' => @hostname}.freeze
|
31
41
|
|
32
42
|
# this will get populated after a successful API call
|
33
43
|
@network = nil
|
@@ -38,12 +48,12 @@ module BlockIo
|
|
38
48
|
|
39
49
|
method_name = m.to_s
|
40
50
|
|
41
|
-
raise Exception.new(
|
42
|
-
raise Exception.new(
|
43
|
-
raise Exception.new(
|
44
|
-
raise Exception.new(
|
51
|
+
raise Exception.new('Must provide arguments as a Hash.') unless args.size <= 1 and args.all?{|x| x.is_a?(Hash)}
|
52
|
+
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)}
|
53
|
+
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)
|
54
|
+
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)
|
45
55
|
|
46
|
-
if method_name.eql?(
|
56
|
+
if method_name.eql?('prepare_sweep_transaction') then
|
47
57
|
# we need to ensure @network is set before we allow this
|
48
58
|
# we need to send only the public key, not the given private key
|
49
59
|
# we're sweeping from an address
|
@@ -54,6 +64,13 @@ module BlockIo
|
|
54
64
|
|
55
65
|
end
|
56
66
|
|
67
|
+
def padded_f(d)
|
68
|
+
# returns a padded decimal to 8 decimal places
|
69
|
+
b = BigDecimal(d).to_s('F')
|
70
|
+
b << '0' * (8 - (b.size - b.index('.') - 1))
|
71
|
+
b
|
72
|
+
end
|
73
|
+
|
57
74
|
def summarize_prepared_transaction(data)
|
58
75
|
# takes the response from prepare_transaction/prepare_dtrust_transaction/prepare_sweep_transaction
|
59
76
|
# returns the network fee being paid, the blockio fee being paid, amounts being sent
|
@@ -64,10 +81,15 @@ module BlockIo
|
|
64
81
|
blockio_fees = [BigDecimal(0)]
|
65
82
|
change_amounts = [BigDecimal(0)]
|
66
83
|
|
67
|
-
|
68
|
-
|
84
|
+
i = 0
|
85
|
+
loop do
|
86
|
+
output = data['data']['outputs'][i]
|
87
|
+
break if output.nil?
|
88
|
+
i += 1
|
89
|
+
|
90
|
+
if output['output_category'].eql?('blockio-fee') then
|
69
91
|
blockio_fees << BigDecimal(output['output_value'])
|
70
|
-
elsif output['output_category']
|
92
|
+
elsif output['output_category'].eql?('change') then
|
71
93
|
change_amounts << BigDecimal(output['output_value'])
|
72
94
|
else
|
73
95
|
# user-specified
|
@@ -83,9 +105,9 @@ module BlockIo
|
|
83
105
|
|
84
106
|
{
|
85
107
|
'network' => data['data']['network'],
|
86
|
-
'network_fee' =>
|
87
|
-
|
88
|
-
|
108
|
+
'network_fee' => padded_f(network_fee),
|
109
|
+
'blockio_fee' => padded_f(blockio_fee),
|
110
|
+
'total_amount_to_send' => padded_f(output_sum)
|
89
111
|
}
|
90
112
|
|
91
113
|
end
|
@@ -97,33 +119,32 @@ module BlockIo
|
|
97
119
|
|
98
120
|
set_network(data['data']['network']) if data['data'].key?('network')
|
99
121
|
|
100
|
-
raise
|
101
|
-
raise
|
102
|
-
raise
|
122
|
+
raise 'Data must be contain one or more inputs' unless data['data']['inputs'].size > 0
|
123
|
+
raise 'Data must contain one or more outputs' unless data['data']['outputs'].size > 0
|
124
|
+
raise 'Data must contain information about addresses' unless data['data']['input_address_data'].size > 0 # TODO make stricter
|
103
125
|
|
104
126
|
private_keys = keys.map{|x| Key.from_private_key_hex(x)}
|
105
127
|
|
106
|
-
# TODO debug all of this
|
107
|
-
|
108
128
|
inputs = data['data']['inputs']
|
109
129
|
outputs = data['data']['outputs']
|
110
130
|
|
111
131
|
tx = Bitcoin::Tx.new
|
112
132
|
|
113
133
|
# populate the inputs
|
114
|
-
inputs.
|
115
|
-
|
134
|
+
tx.in << inputs.map do |input|
|
135
|
+
Bitcoin::TxIn.new(:out_point => Bitcoin::OutPoint.from_txid(input['previous_txid'], input['previous_output_index']))
|
116
136
|
end
|
117
|
-
|
137
|
+
tx.in.flatten!
|
138
|
+
|
118
139
|
# populate the outputs
|
119
|
-
outputs.
|
120
|
-
|
140
|
+
tx.out << outputs.map do |output|
|
141
|
+
Bitcoin::TxOut.new(:value => (BigDecimal(output['output_value']) * BigDecimal(100000000)).to_i, :script_pubkey => Bitcoin::Script.parse_from_addr(output['receiving_address']))
|
121
142
|
end
|
122
|
-
|
123
|
-
|
143
|
+
tx.out.flatten!
|
144
|
+
|
124
145
|
# some protection against misbehaving machines and/or code
|
125
|
-
raise Exception.new(
|
126
|
-
|
146
|
+
raise Exception.new('Expected unsigned transaction ID mismatch. Please report this error to support@block.io.') unless (data['data']['expected_unsigned_txid'].nil? or
|
147
|
+
data['data']['expected_unsigned_txid'].eql?(tx.txid))
|
127
148
|
|
128
149
|
# extract key
|
129
150
|
encrypted_key = data['data']['user_key']
|
@@ -131,11 +152,11 @@ module BlockIo
|
|
131
152
|
if !encrypted_key.nil? and !@keys.key?(encrypted_key['public_key']) then
|
132
153
|
# decrypt the key with PIN
|
133
154
|
|
134
|
-
raise Exception.new(
|
155
|
+
raise Exception.new('PIN not set and no keys provided. Cannot sign transaction.') unless !@pin.nil? or @keys.size > 0
|
135
156
|
|
136
157
|
key = Helper.dynamicExtractKey(encrypted_key, @pin)
|
137
158
|
|
138
|
-
raise Exception.new(
|
159
|
+
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
160
|
|
140
161
|
# store this key for later use
|
141
162
|
@keys[key.public_key_hex] = key
|
@@ -143,7 +164,7 @@ module BlockIo
|
|
143
164
|
end
|
144
165
|
|
145
166
|
# store the provided keys, if any, for later use
|
146
|
-
private_keys.
|
167
|
+
@keys.merge!(private_keys.map{|key| [key.public_key_hex, key]}.to_h)
|
147
168
|
|
148
169
|
signatures = []
|
149
170
|
|
@@ -152,20 +173,22 @@ module BlockIo
|
|
152
173
|
# Block.io will check to see if all signatures are present, or return an error otherwise saying insufficient signatures provided
|
153
174
|
|
154
175
|
i = 0
|
155
|
-
|
176
|
+
loop do
|
156
177
|
input = inputs[i]
|
178
|
+
break if input.nil?
|
157
179
|
|
158
|
-
input_address_data = data['data']['input_address_data'].detect{|d| d['address']
|
180
|
+
input_address_data = data['data']['input_address_data'].detect{|d| d['address'].eql?(input['spending_address'])}
|
159
181
|
sighash_for_input = Helper.getSigHashForInput(tx, i, input, input_address_data) # in bytes
|
160
182
|
|
161
|
-
input_address_data['public_keys'].
|
183
|
+
signatures << input_address_data['public_keys'].map do |signer_public_key|
|
162
184
|
# sign what we can and append signatures to the signatures object
|
163
|
-
|
164
185
|
next unless @keys.key?(signer_public_key)
|
165
186
|
|
166
|
-
|
167
|
-
|
168
|
-
|
187
|
+
{
|
188
|
+
'input_index' => i,
|
189
|
+
'public_key' => signer_public_key,
|
190
|
+
'signature' => @keys[signer_public_key].sign(sighash_for_input).unpack1('H*') # in hex
|
191
|
+
}
|
169
192
|
end
|
170
193
|
|
171
194
|
i += 1 # go to next input
|
@@ -173,6 +196,9 @@ module BlockIo
|
|
173
196
|
|
174
197
|
end
|
175
198
|
|
199
|
+
signatures.flatten!
|
200
|
+
signatures.compact!
|
201
|
+
|
176
202
|
# if we have everything we need for this transaction, just finalize the transaction
|
177
203
|
if Helper.allSignaturesPresent?(tx, inputs, signatures, data['data']['input_address_data']) then
|
178
204
|
Helper.finalizeTransaction(tx, inputs, signatures, data['data']['input_address_data'])
|
@@ -183,18 +209,18 @@ module BlockIo
|
|
183
209
|
@keys = {}
|
184
210
|
|
185
211
|
# the response for submitting the transaction
|
186
|
-
{
|
212
|
+
{'tx_type' => data['data']['tx_type'], 'tx_hex' => tx.to_hex, 'signatures' => (signatures.size.eql?(0) ? nil : signatures)}
|
187
213
|
|
188
214
|
end
|
189
215
|
|
190
216
|
private
|
191
217
|
|
192
|
-
def internal_prepare_sweep_transaction(args = {}, method_name =
|
218
|
+
def internal_prepare_sweep_transaction(args = {}, method_name = 'prepare_sweep_transaction')
|
193
219
|
|
194
220
|
# set the network first if not already known
|
195
|
-
api_call({:method_name =>
|
221
|
+
api_call({:method_name => 'get_balance', :params => {}}) if @network.nil?
|
196
222
|
|
197
|
-
raise Exception.new(
|
223
|
+
raise Exception.new('No private_key provided.') unless args.key?(:private_key) and (args[:private_key] || '').size > 0
|
198
224
|
|
199
225
|
# ensure the private key never goes to Block.io
|
200
226
|
key = Key.from_wif(args[:private_key])
|
@@ -215,26 +241,36 @@ module BlockIo
|
|
215
241
|
|
216
242
|
def api_call(args)
|
217
243
|
|
218
|
-
|
219
|
-
|
220
|
-
|
244
|
+
response = Typhoeus.post("https://#{@hostname}/api/v#{@version}/#{args[:method_name]}",
|
245
|
+
:body => Oj.dump(args[:params].merge({:api_key => @api_key}), :mode => :compat),
|
246
|
+
:headers => @api_request_headers,
|
247
|
+
:timeout => TIMEOUT,
|
248
|
+
**@proxy)
|
221
249
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
body = {
|
250
|
+
body = nil
|
251
|
+
|
252
|
+
if response.timed_out? then
|
253
|
+
body = {'status' => 'fail', 'data' => {'error_message' => 'Request timed out.'}}
|
254
|
+
elsif response.code.eql?(0) then
|
255
|
+
body = {'status' => 'fail', 'data' => {'error_message' => 'No response received.'}}
|
256
|
+
else
|
257
|
+
begin
|
258
|
+
body = Oj.safe_load(response.body.to_s)
|
259
|
+
rescue Exception => e
|
260
|
+
body = {'status' => 'fail', 'data' => {'error_message' => "Unknown error occurred. Please report this to support@block.io. Status #{response.code}."}}
|
261
|
+
end
|
226
262
|
end
|
227
|
-
|
228
|
-
if !body[
|
263
|
+
|
264
|
+
if !body['status'].eql?('success') then
|
229
265
|
# raise an exception on error for easy handling
|
230
266
|
# user can extract raw response using e.raw_data
|
231
|
-
e = APIException.new(
|
267
|
+
e = APIException.new(body['data']['error_message'].to_s)
|
232
268
|
e.set_raw_data(body)
|
233
269
|
raise e
|
234
270
|
end
|
235
271
|
|
272
|
+
# success
|
236
273
|
set_network(body['data']['network']) if body['data'].key?('network')
|
237
|
-
|
238
274
|
body
|
239
275
|
|
240
276
|
end
|
@@ -61,7 +61,7 @@ module Bitcoin
|
|
61
61
|
|
62
62
|
signature = ECDSA::Signature.new(r, s).to_der
|
63
63
|
|
64
|
-
#
|
64
|
+
# these lines lead to performance issues
|
65
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
66
|
# raise 'Creation of signature failed.' unless Bitcoin::Secp256k1::Ruby.verify_sig(data, signature, public_key)
|
67
67
|
|
@@ -77,7 +77,7 @@ module Bitcoin
|
|
77
77
|
# override so enforce compressed keys
|
78
78
|
|
79
79
|
raise "key_type must always be Bitcoin::KEY::TYPES[:compressed]" unless key_type == TYPES[:compressed]
|
80
|
-
puts
|
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
81
|
if key_type
|
82
82
|
@key_type = key_type
|
83
83
|
compressed = @key_type != TYPES[:uncompressed]
|
@@ -113,14 +113,16 @@ module Bech32
|
|
113
113
|
# override so we can parse non-Bitcoin Bech32 addresses
|
114
114
|
|
115
115
|
class SegwitAddr
|
116
|
+
|
117
|
+
VALID_HRPS = ['bc', 'ltc', 'tb', 'tltc', 'doge', 'tdge'].inject({}){|h,v| h[v] = true; h}
|
116
118
|
|
117
119
|
private
|
118
120
|
def parse_addr(addr)
|
119
121
|
@hrp, data, spec = Bech32.decode(addr)
|
120
|
-
raise 'Invalid address.' if hrp.nil? || data[0].nil? || !
|
122
|
+
raise 'Invalid address.' if hrp.nil? || data[0].nil? || !VALID_HRPS.key?(hrp) # HRP_MAINNET, HRP_TESTNET, HRP_REGTEST].include?(hrp)
|
121
123
|
@ver = data[0]
|
122
124
|
raise 'Invalid witness version' if @ver > 16
|
123
|
-
@prog = convert_bits(data[1..-1], 5, 8, false)
|
125
|
+
@prog = Bech32.convert_bits(data[1..-1], 5, 8, false)
|
124
126
|
raise 'Invalid witness program' if @prog.nil? || @prog.length < 2 || @prog.length > 40
|
125
127
|
raise 'Invalid witness program with version 0' if @ver == 0 && (@prog.length != 20 && @prog.length != 32)
|
126
128
|
raise 'Witness version and encoding spec do not match' if (@ver == 0 && spec != Bech32::Encoding::BECH32) || (@ver != 0 && spec != Bech32::Encoding::BECH32M)
|
data/lib/block_io/helper.rb
CHANGED
@@ -3,13 +3,13 @@ module BlockIo
|
|
3
3
|
class Helper
|
4
4
|
|
5
5
|
LEGACY_DECRYPTION_ALGORITHM = {
|
6
|
-
:pbkdf2_salt =>
|
6
|
+
:pbkdf2_salt => '',
|
7
7
|
:pbkdf2_iterations => 2048,
|
8
|
-
:pbkdf2_hash_function =>
|
8
|
+
:pbkdf2_hash_function => 'SHA256',
|
9
9
|
:pbkdf2_phase1_key_length => 16,
|
10
10
|
:pbkdf2_phase2_key_length => 32,
|
11
11
|
:aes_iv => nil,
|
12
|
-
:aes_cipher =>
|
12
|
+
:aes_cipher => 'AES-256-ECB',
|
13
13
|
:aes_auth_tag => nil,
|
14
14
|
:aes_auth_data => nil
|
15
15
|
}
|
@@ -19,20 +19,23 @@ module BlockIo
|
|
19
19
|
|
20
20
|
all_signatures_present = false
|
21
21
|
|
22
|
-
|
22
|
+
i = 0
|
23
|
+
loop do
|
23
24
|
# check if each input has its required signatures
|
25
|
+
input = inputs[i]
|
26
|
+
break if input.nil?
|
27
|
+
i += 1
|
24
28
|
|
25
29
|
spending_address = input['spending_address']
|
26
|
-
current_input_address_data = input_address_data.detect{|x| x['address']
|
30
|
+
current_input_address_data = input_address_data.detect{|x| x['address'].eql?(spending_address)}
|
27
31
|
required_signatures = current_input_address_data['required_signatures']
|
28
32
|
public_keys = current_input_address_data['public_keys']
|
29
33
|
|
30
|
-
signatures_present = signatures.map{|x| x if x['input_index']
|
34
|
+
signatures_present = signatures.map{|x| x if x['input_index'].eql?(input['input_index'])}.compact.inject({}){|h,v| h[v['public_key']] = v['signature']; h}
|
31
35
|
|
32
36
|
# break the loop if all signatures are not present for this input
|
33
|
-
all_signatures_present = (signatures_present.
|
34
|
-
break unless all_signatures_present
|
35
|
-
|
37
|
+
all_signatures_present = (signatures_present.size >= required_signatures)
|
38
|
+
break unless all_signatures_present
|
36
39
|
end
|
37
40
|
|
38
41
|
all_signatures_present
|
@@ -56,12 +59,16 @@ module BlockIo
|
|
56
59
|
|
57
60
|
def self.finalizeTransaction(tx, inputs, signatures, input_address_data)
|
58
61
|
# append signatures to the transaction and return its hexadecimal representation
|
59
|
-
|
60
|
-
|
62
|
+
|
63
|
+
i = 0
|
64
|
+
loop do
|
61
65
|
# for each input
|
66
|
+
input = inputs[i]
|
67
|
+
break if input.nil?
|
68
|
+
i += 1
|
62
69
|
|
63
|
-
signatures_present = signatures.map{|x| x if x['input_index']
|
64
|
-
address_data = input_address_data.detect{|x| x['address']
|
70
|
+
signatures_present = signatures.map{|x| x if x['input_index'].eql?(input['input_index'])}.compact.inject({}){|h,v| h[v['public_key']] = v['signature']; h}
|
71
|
+
address_data = input_address_data.detect{|x| x['address'].eql?(input['spending_address'])} # contains public keys (ordered) and the address type
|
65
72
|
input_index = input['input_index']
|
66
73
|
is_segwit = isSegwitAddressType?(address_data['address_type'])
|
67
74
|
script_stack = (is_segwit ? tx.in[input_index].script_witness.stack : tx.in[input_index].script_sig)
|
@@ -74,16 +81,16 @@ module BlockIo
|
|
74
81
|
current_signature = signatures_present[current_public_key]
|
75
82
|
|
76
83
|
# no blank push necessary for P2PKH, P2WPKH, P2WPKH-over-P2SH
|
77
|
-
script_stack << ([current_signature].pack(
|
78
|
-
script_stack << [current_public_key].pack(
|
84
|
+
script_stack << ([current_signature].pack('H*') + [Bitcoin::SIGHASH_TYPE[:all]].pack('C'))
|
85
|
+
script_stack << [current_public_key].pack('H*')
|
79
86
|
|
80
87
|
# P2WPKH-over-P2SH required script_sig still
|
81
88
|
tx.in[input_index].script_sig << (
|
82
89
|
Bitcoin::Script.to_p2wpkh(
|
83
90
|
Bitcoin::Key.new(:pubkey => current_public_key, :key_type => Bitcoin::Key::TYPES[:compressed]).hash160 # hash160 of the compressed pubkey
|
84
91
|
).to_payload
|
85
|
-
) if address_data['address_type']
|
86
|
-
|
92
|
+
) if address_data['address_type'].eql?('P2WPKH-over-P2SH')
|
93
|
+
|
87
94
|
elsif ['P2SH', 'WITNESS_V0', 'P2WSH-over-P2SH'].include?(address_data['address_type']) then
|
88
95
|
# P2SH will use script_sig as script_stack
|
89
96
|
# P2WSH or P2WSH-over-P2SH input will use script_witness.stack as script_stack
|
@@ -94,23 +101,27 @@ module BlockIo
|
|
94
101
|
|
95
102
|
signatures_added = 0
|
96
103
|
|
97
|
-
|
104
|
+
j = 0
|
105
|
+
loop do
|
106
|
+
public_key = address_data['public_keys'][j]
|
107
|
+
break if public_key.nil?
|
108
|
+
j += 1
|
98
109
|
next unless signatures_present.key?(public_key)
|
99
110
|
|
100
111
|
# append signatures, no sighash needed, in correct order of public keys
|
101
112
|
current_signature = signatures_present[public_key]
|
102
|
-
script_stack << ([current_signature].pack(
|
113
|
+
script_stack << ([current_signature].pack('H*') + [Bitcoin::SIGHASH_TYPE[:all]].pack('C'))
|
103
114
|
|
104
115
|
signatures_added += 1
|
105
116
|
|
106
117
|
# required signatures added? break loop and move on
|
107
|
-
break if signatures_added
|
118
|
+
break if signatures_added.eql?(address_data['required_signatures'])
|
108
119
|
end
|
109
120
|
|
110
121
|
script_stack << script.last.to_payload
|
111
122
|
|
112
123
|
# P2WSH-over-P2SH needs script_sig populated still
|
113
|
-
tx.in[input_index].script_sig << Bitcoin::Script.to_p2wsh(script.last).to_payload if address_data['address_type']
|
124
|
+
tx.in[input_index].script_sig << Bitcoin::Script.to_p2wsh(script.last).to_payload if address_data['address_type'].eql?('P2WSH-over-P2SH')
|
114
125
|
|
115
126
|
else
|
116
127
|
raise "Unrecognized input address: #{address_data['address_type']}"
|
@@ -125,35 +136,35 @@ module BlockIo
|
|
125
136
|
def self.getSigHashForInput(tx, input_index, input_data, input_address_data)
|
126
137
|
# returns the sighash for the given input in bytes
|
127
138
|
|
128
|
-
address_type = input_address_data[
|
129
|
-
input_value = (BigDecimal(input_data['input_value']) *
|
139
|
+
address_type = input_address_data['address_type']
|
140
|
+
input_value = (BigDecimal(input_data['input_value']) * 100_000_000).to_i # in sats
|
130
141
|
sighash = nil
|
131
142
|
|
132
|
-
if address_type
|
143
|
+
if address_type.eql?('P2SH') then
|
133
144
|
# P2SH addresses
|
134
145
|
|
135
|
-
script = Bitcoin::Script.to_p2sh_multisig_script(input_address_data[
|
146
|
+
script = Bitcoin::Script.to_p2sh_multisig_script(input_address_data['required_signatures'], input_address_data['public_keys'])
|
136
147
|
sighash = tx.sighash_for_input(input_index, script.last)
|
137
148
|
|
138
|
-
elsif address_type
|
149
|
+
elsif address_type.eql?('P2WSH-over-P2SH') or address_type.eql?('WITNESS_V0') then
|
139
150
|
# P2WSH-over-P2SH addresses
|
140
151
|
# WITNESS_V0 addresses
|
141
152
|
|
142
|
-
script = Bitcoin::Script.to_p2sh_multisig_script(input_address_data[
|
153
|
+
script = Bitcoin::Script.to_p2sh_multisig_script(input_address_data['required_signatures'], input_address_data['public_keys'])
|
143
154
|
sighash = tx.sighash_for_input(input_index, script.last, amount: input_value, sig_version: :witness_v0)
|
144
155
|
|
145
|
-
elsif address_type
|
156
|
+
elsif address_type.eql?('P2WPKH-over-P2SH') or address_type.eql?('P2WPKH') then
|
146
157
|
# P2WPKH-over-P2SH addresses
|
147
158
|
# P2WPKH addresses
|
148
159
|
|
149
|
-
pub_key = Bitcoin::Key.new(:pubkey => input_address_data['public_keys']
|
160
|
+
pub_key = Bitcoin::Key.new(:pubkey => input_address_data['public_keys'][0], :key_type => Bitcoin::Key::TYPES[:compressed]) # compressed
|
150
161
|
script = Bitcoin::Script.to_p2wpkh(pub_key.hash160)
|
151
162
|
sighash = tx.sighash_for_input(input_index, script, amount: input_value, sig_version: :witness_v0)
|
152
163
|
|
153
|
-
elsif address_type
|
164
|
+
elsif address_type.eql?('P2PKH') then
|
154
165
|
# P2PKH addresses
|
155
166
|
|
156
|
-
pub_key = Bitcoin::Key.new(:pubkey => input_address_data['public_keys']
|
167
|
+
pub_key = Bitcoin::Key.new(:pubkey => input_address_data['public_keys'][0], :key_type => Bitcoin::Key::TYPES[:compressed]) # compressed
|
157
168
|
script = Bitcoin::Script.to_p2pkh(pub_key.hash160)
|
158
169
|
sighash = tx.sighash_for_input(input_index, script)
|
159
170
|
|
@@ -168,7 +179,7 @@ module BlockIo
|
|
168
179
|
def self.getDecryptionAlgorithm(user_key_algorithm = nil)
|
169
180
|
# mainly used so existing unit tests do not break
|
170
181
|
|
171
|
-
algorithm = ({}).merge(LEGACY_DECRYPTION_ALGORITHM)
|
182
|
+
algorithm = ({}).merge!(LEGACY_DECRYPTION_ALGORITHM)
|
172
183
|
|
173
184
|
if !user_key_algorithm.nil? then
|
174
185
|
algorithm[:pbkdf2_salt] = user_key_algorithm['pbkdf2_salt']
|
@@ -216,14 +227,14 @@ module BlockIo
|
|
216
227
|
|
217
228
|
def self.sha256(value)
|
218
229
|
# returns the hex of the hash of the given value
|
219
|
-
OpenSSL::Digest::SHA256.digest(value).
|
230
|
+
OpenSSL::Digest::SHA256.digest(value).unpack1('H*')
|
220
231
|
end
|
221
232
|
|
222
|
-
def self.pinToAesKey(secret_pin, iterations = 2048, salt =
|
233
|
+
def self.pinToAesKey(secret_pin, iterations = 2048, salt = '', hash_function = 'SHA256', pbkdf2_phase1_key_length = 16, pbkdf2_phase2_key_length = 32)
|
223
234
|
# converts the pincode string to PBKDF2
|
224
235
|
# returns a base64 version of PBKDF2 pincode
|
225
236
|
|
226
|
-
raise Exception.new(
|
237
|
+
raise Exception.new('Unknown hash function specified. Are you using current version of this library?') unless hash_function.eql?('SHA256')
|
227
238
|
|
228
239
|
part1 = OpenSSL::PKCS5.pbkdf2_hmac(
|
229
240
|
secret_pin,
|
@@ -231,7 +242,7 @@ module BlockIo
|
|
231
242
|
iterations/2,
|
232
243
|
pbkdf2_phase1_key_length,
|
233
244
|
OpenSSL::Digest::SHA256.new
|
234
|
-
).
|
245
|
+
).unpack1('H*')
|
235
246
|
|
236
247
|
part2 = OpenSSL::PKCS5.pbkdf2_hmac(
|
237
248
|
part1,
|
@@ -241,80 +252,78 @@ module BlockIo
|
|
241
252
|
OpenSSL::Digest::SHA256.new
|
242
253
|
) # binary
|
243
254
|
|
244
|
-
[part2].pack(
|
255
|
+
[part2].pack('m0') # the base64 encryption key
|
245
256
|
|
246
257
|
end
|
247
258
|
|
248
259
|
# Decrypts a block of data (encrypted_data) given an encryption key
|
249
|
-
def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type =
|
260
|
+
def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB', auth_tag = nil, auth_data = nil)
|
250
261
|
|
251
|
-
raise Exception.new(
|
262
|
+
raise Exception.new('Auth tag must be 16 bytes exactly.') unless auth_tag.nil? or auth_tag.size.eql?(32)
|
252
263
|
|
253
264
|
response = nil
|
254
265
|
|
255
266
|
begin
|
256
267
|
aes = OpenSSL::Cipher.new(cipher_type.downcase)
|
257
268
|
aes.decrypt
|
258
|
-
aes.key = b64_enc_key.
|
259
|
-
aes.iv = [iv].pack(
|
260
|
-
aes.auth_tag = [auth_tag].pack(
|
261
|
-
aes.auth_data = [auth_data].pack(
|
262
|
-
response = aes.update(encrypted_data.
|
269
|
+
aes.key = b64_enc_key.unpack1('m0')
|
270
|
+
aes.iv = [iv].pack('H*') unless iv.nil?
|
271
|
+
aes.auth_tag = [auth_tag].pack('H*') unless auth_tag.nil?
|
272
|
+
aes.auth_data = [auth_data].pack('H*') unless auth_data.nil?
|
273
|
+
response = aes.update(encrypted_data.unpack1('m0')) << aes.final
|
263
274
|
rescue Exception => e
|
264
275
|
# decryption failed, must be an invalid Secret PIN
|
265
|
-
raise Exception.new(
|
276
|
+
raise Exception.new('Invalid Secret PIN provided.')
|
266
277
|
end
|
267
278
|
|
268
279
|
response
|
269
280
|
end
|
270
281
|
|
271
282
|
# Encrypts a block of data given an encryption key
|
272
|
-
def self.encrypt(data, b64_enc_key, iv = nil, cipher_type =
|
283
|
+
def self.encrypt(data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB', auth_data = nil)
|
273
284
|
aes = OpenSSL::Cipher.new(cipher_type.downcase)
|
274
285
|
aes.encrypt
|
275
|
-
aes.key = b64_enc_key.
|
276
|
-
aes.iv = [iv].pack(
|
277
|
-
aes.auth_data = [auth_data].pack(
|
278
|
-
result = [aes.update(data) << aes.final].pack(
|
279
|
-
auth_tag = (cipher_type.end_with?(
|
286
|
+
aes.key = b64_enc_key.unpack1('m0')
|
287
|
+
aes.iv = [iv].pack('H*') unless iv.nil?
|
288
|
+
aes.auth_data = [auth_data].pack('H*') unless auth_data.nil?
|
289
|
+
result = [aes.update(data) << aes.final].pack('m0')
|
290
|
+
auth_tag = (cipher_type.end_with?('-GCM') ? aes.auth_tag.unpack1('H*') : nil)
|
280
291
|
|
281
292
|
{:aes_auth_tag => auth_tag, :aes_cipher_text => result, :aes_iv => iv, :aes_cipher => cipher_type, :aes_auth_data => auth_data}
|
282
293
|
|
283
294
|
end
|
284
295
|
|
285
296
|
# courtesy bitcoin-ruby
|
286
|
-
|
297
|
+
BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
287
298
|
def self.int_to_base58(int_val, leading_zero_bytes=0)
|
288
|
-
|
289
|
-
base58_val, base = "", alpha.size
|
299
|
+
base58_val, base = '', BASE58_ALPHABET.size
|
290
300
|
while int_val > 0
|
291
301
|
int_val, remainder = int_val.divmod(base)
|
292
|
-
base58_val =
|
302
|
+
base58_val = '' << BASE58_ALPHABET[remainder] << base58_val
|
293
303
|
end
|
294
304
|
base58_val
|
295
305
|
end
|
296
306
|
|
297
307
|
def self.base58_to_int(base58_val)
|
298
|
-
|
299
|
-
int_val, base = 0, alpha.size
|
308
|
+
int_val, base = 0, BASE58_ALPHABET.size
|
300
309
|
base58_val.reverse.each_char.with_index do |char,index|
|
301
|
-
raise ArgumentError,
|
310
|
+
raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = BASE58_ALPHABET.index(char)
|
302
311
|
int_val += char_index*(base**index)
|
303
312
|
end
|
304
313
|
int_val
|
305
314
|
end
|
306
315
|
|
307
316
|
def self.encode_base58(hex)
|
308
|
-
leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 :
|
309
|
-
(
|
317
|
+
leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
|
318
|
+
('1'*leading_zero_bytes) << Helper.int_to_base58(hex.to_i(16))
|
310
319
|
end
|
311
320
|
|
312
321
|
def self.decode_base58(base58_val)
|
313
322
|
s = Helper.base58_to_int(base58_val).to_s(16)
|
314
|
-
s = (s.bytesize.odd? ? (
|
315
|
-
s =
|
316
|
-
leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 :
|
317
|
-
s = (
|
323
|
+
s = (s.bytesize.odd? ? ('0' << s) : s)
|
324
|
+
s = '' if s.eql?('00')
|
325
|
+
leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 : '').size
|
326
|
+
s = ('00'*leading_zero_bytes) << s if leading_zero_bytes > 0
|
318
327
|
s
|
319
328
|
end
|
320
329
|
end
|
data/lib/block_io/key.rb
CHANGED
@@ -18,9 +18,9 @@ module BlockIo
|
|
18
18
|
# create a private/public key pair from a given passphrase
|
19
19
|
# use a long, random passphrase. your security depends on the passphrase's entropy.
|
20
20
|
|
21
|
-
raise Exception.new(
|
21
|
+
raise Exception.new('Must provide passphrase at least 8 characters long.') if passphrase.nil? or passphrase.length < 8
|
22
22
|
|
23
|
-
hashed_key = Helper.sha256([passphrase].pack(
|
23
|
+
hashed_key = Helper.sha256([passphrase].pack('H*')) # must pass bytes to sha256
|
24
24
|
|
25
25
|
# modding is for backward compatibility with legacy bitcoinjs
|
26
26
|
BlockIo::Key.from_private_key_hex((hashed_key.to_i(16) % ECDSA::Group::Secp256k1.order).to_s(16))
|
data/lib/block_io/version.rb
CHANGED
data/lib/block_io.rb
CHANGED
data/spec/client_misc_spec.rb
CHANGED
@@ -5,19 +5,12 @@ describe "Client" do
|
|
5
5
|
before(:each) do
|
6
6
|
@api_key = "0000-0000-0000-0000"
|
7
7
|
@req_params = {:to_address => "QTLcyTFrH7T6kqUsi1VV2mJVXmX3AmwUNH", :amounts => "0.248"}
|
8
|
-
@headers = {
|
9
|
-
'Accept' => 'application/json',
|
10
|
-
'Connection' => 'Keep-Alive',
|
11
|
-
'Content-Type' => 'application/json; charset=UTF-8',
|
12
|
-
'Host' => 'block.io',
|
13
|
-
'User-Agent' => "gem:block_io:#{BlockIo::VERSION}"
|
14
|
-
}
|
15
8
|
|
16
9
|
@prepare_transaction_response = File.new("spec/test-cases/json/prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json").read
|
17
10
|
@stub1 = stub_request(:post, "https://block.io/api/v2/prepare_transaction").
|
18
11
|
with(
|
19
12
|
body: @req_params.merge({:api_key => @api_key}).to_json,
|
20
|
-
headers:
|
13
|
+
headers: SPEC_REQUEST_HEADERS).
|
21
14
|
to_return(status: 200, body: @prepare_transaction_response, headers: {})
|
22
15
|
|
23
16
|
@create_and_sign_transaction_response = File.new("spec/test-cases/json/create_and_sign_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json").read
|
data/spec/client_spec.rb
CHANGED
@@ -3,19 +3,12 @@ describe "Client.prepare_transaction" do
|
|
3
3
|
before(:each) do
|
4
4
|
@api_key = "0000-0000-0000-0000"
|
5
5
|
@req_params = {:to_address => "QTLcyTFrH7T6kqUsi1VV2mJVXmX3AmwUNH", :amounts => "0.248"}
|
6
|
-
@headers = {
|
7
|
-
'Accept' => 'application/json',
|
8
|
-
'Connection' => 'Keep-Alive',
|
9
|
-
'Content-Type' => 'application/json; charset=UTF-8',
|
10
|
-
'Host' => 'block.io',
|
11
|
-
'User-Agent' => "gem:block_io:#{BlockIo::VERSION}"
|
12
|
-
}
|
13
6
|
|
14
7
|
@prepare_transaction_response = File.new("spec/test-cases/json/prepare_transaction_response.json").read
|
15
8
|
@stub1 = stub_request(:post, "https://block.io/api/v2/prepare_transaction").
|
16
9
|
with(
|
17
10
|
body: @req_params.merge({:api_key => @api_key}).to_json,
|
18
|
-
headers:
|
11
|
+
headers: SPEC_REQUEST_HEADERS).
|
19
12
|
to_return(status: 200, body: @prepare_transaction_response, headers: {})
|
20
13
|
|
21
14
|
@create_and_sign_transaction_response = File.new("spec/test-cases/json/create_and_sign_transaction_response.json").read
|
data/spec/dtrust_spec.rb
CHANGED
@@ -8,14 +8,6 @@ describe "Client.prepare_dtrust_transaction" do
|
|
8
8
|
"6c1cefdfd9187b36b36c3698c1362642083dcc1941dc76d751481d3aa29ca65"]
|
9
9
|
@to_address = "QcnYiN3t3foHxHv7CnqXrmRoiMkADhapZw"
|
10
10
|
@amount = "0.00020000"
|
11
|
-
@headers = {
|
12
|
-
'Accept' => 'application/json',
|
13
|
-
'Connection' => 'Keep-Alive',
|
14
|
-
'Content-Type' => 'application/json; charset=UTF-8',
|
15
|
-
'Host' => 'block.io',
|
16
|
-
'User-Agent' => "gem:block_io:#{BlockIo::VERSION}"
|
17
|
-
}
|
18
|
-
|
19
11
|
end
|
20
12
|
|
21
13
|
context "p2sh" do
|
@@ -29,7 +21,7 @@ describe "Client.prepare_dtrust_transaction" do
|
|
29
21
|
@stub1 = stub_request(:post, "https://block.io/api/v2/prepare_dtrust_transaction").
|
30
22
|
with(
|
31
23
|
body: @req_params.merge({:api_key => @api_key}).to_json,
|
32
|
-
headers:
|
24
|
+
headers: SPEC_REQUEST_HEADERS).
|
33
25
|
to_return(status: 200, body: @prepare_dtrust_transaction_response_p2sh, headers: {})
|
34
26
|
|
35
27
|
@create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys = File.new("spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys.json").read
|
@@ -76,7 +68,7 @@ describe "Client.prepare_dtrust_transaction" do
|
|
76
68
|
@stub1 = stub_request(:post, "https://block.io/api/v2/prepare_dtrust_transaction").
|
77
69
|
with(
|
78
70
|
body: @req_params.merge({:api_key => @api_key}).to_json,
|
79
|
-
headers:
|
71
|
+
headers: SPEC_REQUEST_HEADERS).
|
80
72
|
to_return(status: 200, body: @prepare_dtrust_transaction_response_p2wsh_over_p2sh, headers: {})
|
81
73
|
|
82
74
|
@create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys = File.new("spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys.json").read
|
@@ -125,7 +117,7 @@ describe "Client.prepare_dtrust_transaction" do
|
|
125
117
|
@stub1 = stub_request(:post, "https://block.io/api/v2/prepare_dtrust_transaction").
|
126
118
|
with(
|
127
119
|
body: @req_params.merge({:api_key => @api_key}).to_json,
|
128
|
-
headers:
|
120
|
+
headers: SPEC_REQUEST_HEADERS).
|
129
121
|
to_return(status: 200, body: @prepare_dtrust_transaction_response_witness_v0, headers: {})
|
130
122
|
|
131
123
|
@create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys = File.new("spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys.json").read
|
data/spec/spec_helper.rb
CHANGED
data/spec/sweep_spec.rb
CHANGED
@@ -6,20 +6,12 @@ describe "Client.prepare_sweep_transaction" do
|
|
6
6
|
@req_params = {:to_address => "QTLcyTFrH7T6kqUsi1VV2mJVXmX3AmwUNH", :public_key => "021499295873879c280c31fff94845a6153142e76b84a29afc92128d482857ed93"}
|
7
7
|
@req_params_with_wif = {:to_address => "QTLcyTFrH7T6kqUsi1VV2mJVXmX3AmwUNH", :private_key => @wif}
|
8
8
|
|
9
|
-
@headers = {
|
10
|
-
'Accept' => 'application/json',
|
11
|
-
'Connection' => 'Keep-Alive',
|
12
|
-
'Content-Type' => 'application/json; charset=UTF-8',
|
13
|
-
'Host' => 'block.io',
|
14
|
-
'User-Agent' => "gem:block_io:#{BlockIo::VERSION}"
|
15
|
-
}
|
16
|
-
|
17
9
|
# since @network won't be set, the library will call get_balance first on prepare_sweep_transaction
|
18
10
|
@get_balance_response = File.new("spec/test-cases/json/get_balance_response.json").read
|
19
11
|
@stub_network = stub_request(:post, "https://block.io/api/v2/get_balance").
|
20
12
|
with(
|
21
13
|
body: {:api_key => @api_key}.to_json,
|
22
|
-
headers:
|
14
|
+
headers: SPEC_REQUEST_HEADERS).
|
23
15
|
to_return(status: 200, body: @get_balance_response, headers: {})
|
24
16
|
|
25
17
|
end
|
@@ -34,7 +26,7 @@ describe "Client.prepare_sweep_transaction" do
|
|
34
26
|
@stub1 = stub_request(:post, "https://block.io/api/v2/prepare_sweep_transaction").
|
35
27
|
with(
|
36
28
|
body: @req_params.merge({:api_key => @api_key}).to_json,
|
37
|
-
headers:
|
29
|
+
headers: SPEC_REQUEST_HEADERS).
|
38
30
|
to_return(status: 200, body: @prepare_sweep_transaction_response_p2pkh, headers: {})
|
39
31
|
|
40
32
|
@create_and_sign_transaction_response_sweep_p2pkh = File.new("spec/test-cases/json/create_and_sign_transaction_response_sweep_p2pkh.json").read
|
@@ -63,7 +55,7 @@ describe "Client.prepare_sweep_transaction" do
|
|
63
55
|
@stub1 = stub_request(:post, "https://block.io/api/v2/prepare_sweep_transaction").
|
64
56
|
with(
|
65
57
|
body: @req_params.merge({:api_key => @api_key}).to_json,
|
66
|
-
headers:
|
58
|
+
headers: SPEC_REQUEST_HEADERS).
|
67
59
|
to_return(status: 200, body: @prepare_sweep_transaction_response_p2wpkh_over_p2sh, headers: {})
|
68
60
|
|
69
61
|
@create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh = File.new("spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh.json").read
|
@@ -92,7 +84,7 @@ describe "Client.prepare_sweep_transaction" do
|
|
92
84
|
@stub1 = stub_request(:post, "https://block.io/api/v2/prepare_sweep_transaction").
|
93
85
|
with(
|
94
86
|
body: @req_params.merge({:api_key => @api_key}).to_json,
|
95
|
-
headers:
|
87
|
+
headers: SPEC_REQUEST_HEADERS).
|
96
88
|
to_return(status: 200, body: @prepare_sweep_transaction_response_p2wpkh, headers: {})
|
97
89
|
|
98
90
|
@create_and_sign_transaction_response_sweep_p2wpkh = File.new("spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh.json").read
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: block_io
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Atif Nazir
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: '1.16'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '3
|
22
|
+
version: '3'
|
23
23
|
type: :development
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +29,7 @@ dependencies:
|
|
29
29
|
version: '1.16'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '3
|
32
|
+
version: '3'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: rake
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -37,9 +37,9 @@ dependencies:
|
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '13.0'
|
40
|
-
- - "
|
40
|
+
- - "<"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '
|
42
|
+
version: '14'
|
43
43
|
type: :development
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -47,89 +47,89 @@ dependencies:
|
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
49
|
version: '13.0'
|
50
|
-
- - "
|
50
|
+
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version: '
|
52
|
+
version: '14'
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
name: rspec
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
57
|
- - "~>"
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: '3.
|
60
|
-
- - "
|
59
|
+
version: '3.0'
|
60
|
+
- - "<"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '
|
62
|
+
version: '4'
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: '3.
|
70
|
-
- - "
|
69
|
+
version: '3.0'
|
70
|
+
- - "<"
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version: '
|
72
|
+
version: '4'
|
73
73
|
- !ruby/object:Gem::Dependency
|
74
74
|
name: webmock
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
76
76
|
requirements:
|
77
77
|
- - "~>"
|
78
78
|
- !ruby/object:Gem::Version
|
79
|
-
version: '3.
|
79
|
+
version: '3.0'
|
80
80
|
- - "<"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '4
|
82
|
+
version: '4'
|
83
83
|
type: :development
|
84
84
|
prerelease: false
|
85
85
|
version_requirements: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '3.
|
89
|
+
version: '3.0'
|
90
90
|
- - "<"
|
91
91
|
- !ruby/object:Gem::Version
|
92
|
-
version: '4
|
92
|
+
version: '4'
|
93
93
|
- !ruby/object:Gem::Dependency
|
94
94
|
name: bitcoinrb
|
95
95
|
requirement: !ruby/object:Gem::Requirement
|
96
96
|
requirements:
|
97
|
-
- - "
|
97
|
+
- - ">="
|
98
98
|
- !ruby/object:Gem::Version
|
99
|
-
version:
|
99
|
+
version: 1.2.1
|
100
100
|
- - "<"
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version:
|
102
|
+
version: '2'
|
103
103
|
type: :runtime
|
104
104
|
prerelease: false
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
|
-
- - "
|
107
|
+
- - ">="
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
109
|
+
version: 1.2.1
|
110
110
|
- - "<"
|
111
111
|
- !ruby/object:Gem::Version
|
112
|
-
version:
|
112
|
+
version: '2'
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
|
-
name:
|
114
|
+
name: typhoeus
|
115
115
|
requirement: !ruby/object:Gem::Requirement
|
116
116
|
requirements:
|
117
|
-
- - "
|
117
|
+
- - "~>"
|
118
118
|
- !ruby/object:Gem::Version
|
119
|
-
version:
|
119
|
+
version: '1.0'
|
120
120
|
- - "<"
|
121
121
|
- !ruby/object:Gem::Version
|
122
|
-
version: '
|
122
|
+
version: '2'
|
123
123
|
type: :runtime
|
124
124
|
prerelease: false
|
125
125
|
version_requirements: !ruby/object:Gem::Requirement
|
126
126
|
requirements:
|
127
|
-
- - "
|
127
|
+
- - "~>"
|
128
128
|
- !ruby/object:Gem::Version
|
129
|
-
version:
|
129
|
+
version: '1.0'
|
130
130
|
- - "<"
|
131
131
|
- !ruby/object:Gem::Version
|
132
|
-
version: '
|
132
|
+
version: '2'
|
133
133
|
- !ruby/object:Gem::Dependency
|
134
134
|
name: oj
|
135
135
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,7 +139,7 @@ dependencies:
|
|
139
139
|
version: '3.0'
|
140
140
|
- - "<"
|
141
141
|
- !ruby/object:Gem::Version
|
142
|
-
version: '4
|
142
|
+
version: '4'
|
143
143
|
type: :runtime
|
144
144
|
prerelease: false
|
145
145
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -149,27 +149,7 @@ dependencies:
|
|
149
149
|
version: '3.0'
|
150
150
|
- - "<"
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version: '4
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: connection_pool
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '2.2'
|
160
|
-
- - "<"
|
161
|
-
- !ruby/object:Gem::Version
|
162
|
-
version: '3.0'
|
163
|
-
type: :runtime
|
164
|
-
prerelease: false
|
165
|
-
version_requirements: !ruby/object:Gem::Requirement
|
166
|
-
requirements:
|
167
|
-
- - ">="
|
168
|
-
- !ruby/object:Gem::Version
|
169
|
-
version: '2.2'
|
170
|
-
- - "<"
|
171
|
-
- !ruby/object:Gem::Version
|
172
|
-
version: '3.0'
|
152
|
+
version: '4'
|
173
153
|
description: This Ruby Gem is the official reference client for the Block.io payments
|
174
154
|
API. To use this, you will need the Dogecoin, Bitcoin, or Litecoin API key(s) from
|
175
155
|
Block.io. Go ahead, sign up :)
|