block_io 3.0.5 → 3.1.0
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.
- 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 :)
|