ci_block_io 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +48 -0
- data/Rakefile +2 -0
- data/ci_block_io.gemspec +27 -0
- data/lib/ci_block_io/version.rb +3 -0
- data/lib/ci_block_io.rb +418 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: df3f290034d9d589c52a407a53d22bf8b47377f75eca1735efa619e2540c2fcb
|
4
|
+
data.tar.gz: 2a2828ce15a32faa36e97d64b5c429af77e2783fd5f54755ff1059c788dd0aba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 95b1149b0f041f2af71b1c51384979e4649b28737c78bdcefb2d7c359b475baa0ab7525da497c92a2cf0ce51624ae35726dd09145aab857b94659206aab00a84
|
7
|
+
data.tar.gz: 393160136ac00104b62cf9f4c2c978fcac43dd610de8b4c7433b5ef822c95600cecb4fe123c00084342b2e1d3a8665e9668b8b3417393e1d0de1b0d5439773d4
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 BlockIo
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# BlockIo
|
2
|
+
|
3
|
+
This Ruby Gem is the official reference client for the Block.io payments API. To use this, you will need the Dogecoin, Bitcoin, or Litecoin API key(s) from <a href="https://block.io" target="_blank">Block.io</a>. Go ahead, sign up :)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'block_io'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install block_io -v=1.2.0
|
18
|
+
|
19
|
+
## Changelog
|
20
|
+
|
21
|
+
*06/25/18*: Remove support for Ruby < 1.9.3 (OpenSSL::Cipher::Cipher). Remove connection_pool dependency.
|
22
|
+
*01/21/15*: Added ability to sweep coins from one address to another.
|
23
|
+
*11/04/14*: Fix issue with nil parameters in an API call.
|
24
|
+
*11/03/14*: Reduce dependence on OpenSSL. PBKDF2 function is now Ruby-based. Should work well with Heroku's libraries.
|
25
|
+
*10/18/14*: Now using deterministic signatures (RFC6979), and BIP62 to hinder transaction malleability.
|
26
|
+
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
It's super easy to get started. In your Ruby shell ($ irb), for example, do this:
|
31
|
+
|
32
|
+
require 'block_io'
|
33
|
+
BlockIo.set_options :api_key => 'API KEY', :pin => 'SECRET PIN', :version => 2
|
34
|
+
|
35
|
+
And you're good to go:
|
36
|
+
|
37
|
+
BlockIo.get_new_address
|
38
|
+
BlockIo.get_my_addresses
|
39
|
+
|
40
|
+
For more information, see https://block.io/api/simple/ruby
|
41
|
+
|
42
|
+
## Contributing
|
43
|
+
|
44
|
+
1. Fork it ( https://github.com/BlockIo/gem-block-io/fork )
|
45
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
46
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
47
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
48
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/ci_block_io.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ci_block_io/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ci_block_io"
|
8
|
+
spec.version = CiBlockIo::VERSION
|
9
|
+
spec.authors = ["Atsuhiro Tsuruta feat. Atif Nazir"]
|
10
|
+
spec.email = ["a.tsuruta@1-box.co.jp"]
|
11
|
+
spec.summary = %q{An easy to use Dogecoin, Bitcoin, Litecoin wallet API by Block.io. Sign up required at Block.io.}
|
12
|
+
spec.description = %q{This Ruby Gem is the official reference client for the Block.io payments API. To use this, you will need the Dogecoin, Bitcoin, or Litecoin API key(s) from Block.io. Go ahead, sign up :)}
|
13
|
+
spec.homepage = "https://block.io/api/simple/ruby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake", "~> 0"
|
23
|
+
spec.add_runtime_dependency "ecdsa", "~> 1.2", '>= 1.2.0'
|
24
|
+
spec.add_runtime_dependency "httpclient", "~> 2.8", '>= 2.8.0'
|
25
|
+
spec.add_runtime_dependency "oj", "~> 3.3", '>= 3.3.5'
|
26
|
+
spec.add_runtime_dependency "pbkdf2-ruby", '~> 0.2', '>= 0.2.1'
|
27
|
+
end
|
data/lib/ci_block_io.rb
ADDED
@@ -0,0 +1,418 @@
|
|
1
|
+
require 'ci_block_io/version'
|
2
|
+
require 'httpclient'
|
3
|
+
require 'oj'
|
4
|
+
require 'ecdsa'
|
5
|
+
require 'openssl'
|
6
|
+
require 'digest'
|
7
|
+
require 'pbkdf2'
|
8
|
+
require 'securerandom'
|
9
|
+
require 'base64'
|
10
|
+
|
11
|
+
module CiBlockIo
|
12
|
+
|
13
|
+
@api_key = nil
|
14
|
+
@base_url = nil
|
15
|
+
@pin = nil
|
16
|
+
@encryptionKey = nil
|
17
|
+
@client = nil
|
18
|
+
@conn_pool = nil
|
19
|
+
@version = nil
|
20
|
+
|
21
|
+
def self.set_options(args = {})
|
22
|
+
# initialize BlockIo
|
23
|
+
@api_key = args[:api_key]
|
24
|
+
@pin = args[:pin]
|
25
|
+
|
26
|
+
@encryptionKey = Helper.pinToAesKey(@pin) if !@pin.nil?
|
27
|
+
|
28
|
+
hostname = args[:hostname] || "block.io"
|
29
|
+
@base_url = "https://" << hostname << "/api/VERSION/API_CALL/?api_key="
|
30
|
+
|
31
|
+
@client = HTTPClient.new
|
32
|
+
@client.tcp_keepalive = true
|
33
|
+
@client.ssl_config.ssl_version = :auto
|
34
|
+
|
35
|
+
@version = args[:version] || 2 # default version is 2
|
36
|
+
|
37
|
+
self.api_call(['get_balance',""])
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.method_missing(m, *args, &block)
|
41
|
+
|
42
|
+
method_name = m.to_s
|
43
|
+
|
44
|
+
if ['withdraw', 'withdraw_from_address', 'withdraw_from_addresses', 'withdraw_from_user', 'withdraw_from_users', 'withdraw_from_label', 'withdraw_from_labels'].include?(m.to_s) then
|
45
|
+
# need to withdraw from an address
|
46
|
+
self.withdraw(args.first, m.to_s)
|
47
|
+
elsif ['sweep_from_address'].include?(m.to_s) then
|
48
|
+
# need to sweep from an address
|
49
|
+
self.sweep(args.first, m.to_s)
|
50
|
+
else
|
51
|
+
params = get_params(args.first)
|
52
|
+
self.api_call([method_name, params])
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.withdraw(args = {}, method_name = 'withdraw')
|
58
|
+
# validate arguments for withdrawal of funds TODO
|
59
|
+
|
60
|
+
raise Exception.new("PIN not set. Use BlockIo.set_options(:api_key=>'API KEY',:pin=>'SECRET PIN',:version=>'API VERSION')") if @pin.nil?
|
61
|
+
|
62
|
+
params = get_params(args)
|
63
|
+
|
64
|
+
params << "&pin=" << @pin if @version == 1 # Block.io handles the Secret PIN in the legacy API (v1)
|
65
|
+
|
66
|
+
response = self.api_call([method_name, params])
|
67
|
+
|
68
|
+
if response['data'].has_key?('reference_id') then
|
69
|
+
# Block.io's asking us to provide some client-side signatures, let's get to it
|
70
|
+
|
71
|
+
# extract the passphrase
|
72
|
+
encrypted_passphrase = response['data']['encrypted_passphrase']['passphrase']
|
73
|
+
|
74
|
+
# let's get our private key
|
75
|
+
key = Helper.extractKey(encrypted_passphrase, @encryptionKey)
|
76
|
+
|
77
|
+
raise Exception.new('Public key mismatch for requested signer and ourselves. Invalid Secret PIN detected.') if key.public_key != response['data']['encrypted_passphrase']['signer_public_key']
|
78
|
+
|
79
|
+
# let's sign all the inputs we can
|
80
|
+
inputs = response['data']['inputs']
|
81
|
+
|
82
|
+
Helper.signData(inputs, [key])
|
83
|
+
|
84
|
+
# the response object is now signed, let's stringify it and finalize this withdrawal
|
85
|
+
response = self.api_call(['sign_and_finalize_withdrawal',{:signature_data => Oj.dump(response['data'])}])
|
86
|
+
|
87
|
+
# if we provided all the required signatures, this transaction went through
|
88
|
+
# otherwise Block.io responded with data asking for more signatures
|
89
|
+
# the latter will be the case for dTrust addresses
|
90
|
+
end
|
91
|
+
|
92
|
+
return response
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.sweep(args = {}, method_name = 'sweep_from_address')
|
97
|
+
# sweep coins from a given address + key
|
98
|
+
|
99
|
+
raise Exception.new("No private_key provided.") unless args.has_key?(:private_key)
|
100
|
+
|
101
|
+
key = Key.from_wif(args[:private_key])
|
102
|
+
|
103
|
+
args[:public_key] = key.public_key # so Block.io can match things up
|
104
|
+
args.delete(:private_key) # the key must never leave this machine
|
105
|
+
|
106
|
+
params = get_params(args)
|
107
|
+
|
108
|
+
response = self.api_call([method_name, params])
|
109
|
+
|
110
|
+
if response['data'].has_key?('reference_id') then
|
111
|
+
# Block.io's asking us to provide some client-side signatures, let's get to it
|
112
|
+
|
113
|
+
# let's sign all the inputs we can
|
114
|
+
inputs = response['data']['inputs']
|
115
|
+
Helper.signData(inputs, [key])
|
116
|
+
|
117
|
+
# the response object is now signed, let's stringify it and finalize this withdrawal
|
118
|
+
response = self.api_call(['sign_and_finalize_sweep',{:signature_data => Oj.dump(response['data'])}])
|
119
|
+
|
120
|
+
# if we provided all the required signatures, this transaction went through
|
121
|
+
# otherwise Block.io responded with data asking for more signatures
|
122
|
+
# the latter will be the case for dTrust addresses
|
123
|
+
end
|
124
|
+
|
125
|
+
return response
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def self.api_call(endpoint)
|
133
|
+
|
134
|
+
body = nil
|
135
|
+
base_url = @base_url.gsub('API_CALL',endpoint[0]).gsub('VERSION', 'v'+@version.to_s)
|
136
|
+
return nil if base_url.blank?
|
137
|
+
|
138
|
+
response = @client.post("#{base_url + @api_key}", endpoint[1])
|
139
|
+
|
140
|
+
begin
|
141
|
+
body = Oj.load(response.body)
|
142
|
+
raise Exception.new(body['data']['error_message']) if !body['status'].eql?('success')
|
143
|
+
rescue
|
144
|
+
raise Exception.new('Unknown error occurred. Please report this.')
|
145
|
+
end
|
146
|
+
|
147
|
+
body
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def self.get_params(args = {})
|
153
|
+
# construct the parameter string
|
154
|
+
params = ""
|
155
|
+
args = {} if args.nil?
|
156
|
+
|
157
|
+
args.each do |k,v|
|
158
|
+
params += '&' if params.length > 0
|
159
|
+
params += "#{k.to_s}=#{v.to_s}"
|
160
|
+
end
|
161
|
+
|
162
|
+
return params
|
163
|
+
end
|
164
|
+
|
165
|
+
public
|
166
|
+
|
167
|
+
class Key
|
168
|
+
|
169
|
+
def initialize(privkey = nil, compressed = true)
|
170
|
+
# the privkey must be in hex if at all provided
|
171
|
+
|
172
|
+
@group = ECDSA::Group::Secp256k1
|
173
|
+
@private_key = privkey.to_i(16) || 1 + SecureRandom.random_number(group.order - 1)
|
174
|
+
@public_key = @group.generator.multiply_by_scalar(@private_key)
|
175
|
+
@compressed = compressed
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
def private_key
|
180
|
+
# returns private key in hex form
|
181
|
+
return @private_key.to_s(16)
|
182
|
+
end
|
183
|
+
|
184
|
+
def public_key
|
185
|
+
# returns the compressed form of the public key to save network fees (shorter scripts)
|
186
|
+
|
187
|
+
return ECDSA::Format::PointOctetString.encode(@public_key, compression: @compressed).unpack("H*")[0]
|
188
|
+
end
|
189
|
+
|
190
|
+
def sign(data)
|
191
|
+
# signed the given hexadecimal string
|
192
|
+
|
193
|
+
nonce = deterministicGenerateK([data].pack("H*"), @private_key) # RFC6979
|
194
|
+
|
195
|
+
signature = ECDSA.sign(@group, @private_key, data.to_i(16), nonce)
|
196
|
+
|
197
|
+
# BIP0062 -- use lower S values only
|
198
|
+
r, s = signature.components
|
199
|
+
|
200
|
+
over_two = @group.order >> 1 # half of what it was
|
201
|
+
s = @group.order - s if (s > over_two)
|
202
|
+
|
203
|
+
signature = ECDSA::Signature.new(r, s)
|
204
|
+
|
205
|
+
# DER encode this, and return it in hex form
|
206
|
+
return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*")[0]
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.from_passphrase(passphrase)
|
210
|
+
# create a private+public key pair from a given passphrase
|
211
|
+
# think of this as your brain wallet. be very sure to use a sufficiently long passphrase
|
212
|
+
# if you don't want a passphrase, just use Key.new and it will generate a random key for you
|
213
|
+
|
214
|
+
raise Exception.new('Must provide passphrase at least 8 characters long.') if passphrase.nil? or passphrase.length < 8
|
215
|
+
|
216
|
+
hashed_key = Helper.sha256([passphrase].pack("H*")) # must pass bytes to sha256
|
217
|
+
|
218
|
+
return Key.new(hashed_key)
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.from_wif(wif)
|
222
|
+
# returns a new key extracted from the Wallet Import Format provided
|
223
|
+
# TODO check against checksum
|
224
|
+
|
225
|
+
hexkey = Helper.decode_base58(wif)
|
226
|
+
actual_key = hexkey[2...66]
|
227
|
+
|
228
|
+
compressed = hexkey[2..hexkey.length].length-8 > 64 and hexkey[2..hexkey.length][64...66] == '01'
|
229
|
+
|
230
|
+
return Key.new(actual_key, compressed)
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
def isPositive(i)
|
235
|
+
sig = "!+-"[i <=> 0]
|
236
|
+
|
237
|
+
return sig.eql?("+")
|
238
|
+
end
|
239
|
+
|
240
|
+
def deterministicGenerateK(data, privkey, group = ECDSA::Group::Secp256k1)
|
241
|
+
# returns a deterministic K -- RFC6979
|
242
|
+
|
243
|
+
hash = data.bytes.to_a
|
244
|
+
|
245
|
+
x = [privkey.to_s(16)].pack("H*").bytes.to_a
|
246
|
+
|
247
|
+
k = []
|
248
|
+
32.times { k.insert(0, 0) }
|
249
|
+
|
250
|
+
v = []
|
251
|
+
32.times { v.insert(0, 1) }
|
252
|
+
|
253
|
+
# step D
|
254
|
+
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).concat(x).concat(hash).pack("C*")).bytes.to_a
|
255
|
+
|
256
|
+
# step E
|
257
|
+
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
258
|
+
|
259
|
+
# puts "E: " + v.pack("C*").unpack("H*")[0]
|
260
|
+
|
261
|
+
# step F
|
262
|
+
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([1]).concat(x).concat(hash).pack("C*")).bytes.to_a
|
263
|
+
|
264
|
+
# step G
|
265
|
+
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
266
|
+
|
267
|
+
# step H2b (Step H1/H2a ignored)
|
268
|
+
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
269
|
+
|
270
|
+
h2b = v.pack("C*").unpack("H*")[0]
|
271
|
+
tNum = h2b.to_i(16)
|
272
|
+
|
273
|
+
# step H3
|
274
|
+
while (!isPositive(tNum) or tNum >= group.order) do
|
275
|
+
# k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k)
|
276
|
+
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).pack("C*")).bytes.to_a
|
277
|
+
|
278
|
+
# v = crypto.HmacSHA256(v, k)
|
279
|
+
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
280
|
+
|
281
|
+
# T = BigInteger.fromBuffer(v)
|
282
|
+
tNum = v.pack("C*").unpack("H*")[0].to_i(16)
|
283
|
+
end
|
284
|
+
|
285
|
+
return tNum
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
module Helper
|
291
|
+
|
292
|
+
def self.signData(inputs, keys)
|
293
|
+
# sign the given data with the given keys
|
294
|
+
# TODO loop is O(n^3), make it better
|
295
|
+
|
296
|
+
raise Exception.new('Keys object must be an array of keys, without at least one key inside it.') unless keys.is_a?(Array) and keys.size >= 1
|
297
|
+
|
298
|
+
i = 0
|
299
|
+
while i < inputs.size do
|
300
|
+
# iterate over all signers
|
301
|
+
input = inputs[i]
|
302
|
+
|
303
|
+
j = 0
|
304
|
+
while j < input['signers'].size do
|
305
|
+
# if our public key matches this signer's public key, sign the data
|
306
|
+
signer = inputs[i]['signers'][j]
|
307
|
+
|
308
|
+
k = 0
|
309
|
+
while k < keys.size do
|
310
|
+
# sign for each key provided, if we can
|
311
|
+
key = keys[k]
|
312
|
+
signer['signed_data'] = key.sign(input['data_to_sign']) if signer['signer_public_key'] == key.public_key
|
313
|
+
k = k + 1
|
314
|
+
end
|
315
|
+
|
316
|
+
j = j + 1
|
317
|
+
end
|
318
|
+
|
319
|
+
i = i + 1
|
320
|
+
end
|
321
|
+
|
322
|
+
inputs
|
323
|
+
end
|
324
|
+
|
325
|
+
def self.extractKey(encrypted_data, b64_enc_key)
|
326
|
+
# passphrase is in plain text
|
327
|
+
# encrypted_data is in base64, as it was stored on Block.io
|
328
|
+
# returns the private key extracted from the given encrypted data
|
329
|
+
|
330
|
+
decrypted = self.decrypt(encrypted_data, b64_enc_key)
|
331
|
+
|
332
|
+
return Key.from_passphrase(decrypted)
|
333
|
+
end
|
334
|
+
|
335
|
+
def self.sha256(value)
|
336
|
+
# returns the hex of the hash of the given value
|
337
|
+
hash = Digest::SHA2.new(256)
|
338
|
+
hash << value
|
339
|
+
hash.hexdigest # return hex
|
340
|
+
end
|
341
|
+
|
342
|
+
def self.pinToAesKey(secret_pin, iterations = 2048)
|
343
|
+
# converts the pincode string to PBKDF2
|
344
|
+
# returns a base64 version of PBKDF2 pincode
|
345
|
+
salt = ""
|
346
|
+
|
347
|
+
# pbkdf2-ruby gem uses SHA256 as the default hash function
|
348
|
+
aes_key_bin = PBKDF2.new(:password => secret_pin, :salt => salt, :iterations => iterations/2, :key_length => 128/8).value
|
349
|
+
aes_key_bin = PBKDF2.new(:password => aes_key_bin.unpack("H*")[0], :salt => salt, :iterations => iterations/2, :key_length => 256/8).value
|
350
|
+
|
351
|
+
return Base64.strict_encode64(aes_key_bin) # the base64 encryption key
|
352
|
+
end
|
353
|
+
|
354
|
+
# Decrypts a block of data (encrypted_data) given an encryption key
|
355
|
+
def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
|
356
|
+
|
357
|
+
response = nil
|
358
|
+
|
359
|
+
begin
|
360
|
+
aes = OpenSSL::Cipher.new(cipher_type)
|
361
|
+
aes.decrypt
|
362
|
+
aes.key = Base64.strict_decode64(b64_enc_key)
|
363
|
+
aes.iv = iv if iv != nil
|
364
|
+
response = aes.update(Base64.strict_decode64(encrypted_data)) + aes.final
|
365
|
+
rescue Exception => e
|
366
|
+
# decryption failed, must be an invalid Secret PIN
|
367
|
+
raise Exception.new('Invalid Secret PIN provided.')
|
368
|
+
end
|
369
|
+
|
370
|
+
return response
|
371
|
+
end
|
372
|
+
|
373
|
+
# Encrypts a block of data given an encryption key
|
374
|
+
def self.encrypt(data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
|
375
|
+
aes = OpenSSL::Cipher.new(cipher_type)
|
376
|
+
aes.encrypt
|
377
|
+
aes.key = Base64.strict_decode64(b64_enc_key)
|
378
|
+
aes.iv = iv if iv != nil
|
379
|
+
Base64.strict_encode64(aes.update(data) + aes.final)
|
380
|
+
end
|
381
|
+
|
382
|
+
# courtesy bitcoin-ruby
|
383
|
+
|
384
|
+
def self.int_to_base58(int_val, leading_zero_bytes=0)
|
385
|
+
alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
386
|
+
base58_val, base = '', alpha.size
|
387
|
+
while int_val > 0
|
388
|
+
int_val, remainder = int_val.divmod(base)
|
389
|
+
base58_val = alpha[remainder] + base58_val
|
390
|
+
end
|
391
|
+
base58_val
|
392
|
+
end
|
393
|
+
|
394
|
+
def self.base58_to_int(base58_val)
|
395
|
+
alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
396
|
+
int_val, base = 0, alpha.size
|
397
|
+
base58_val.reverse.each_char.with_index do |char,index|
|
398
|
+
raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = alpha.index(char)
|
399
|
+
int_val += char_index*(base**index)
|
400
|
+
end
|
401
|
+
int_val
|
402
|
+
end
|
403
|
+
|
404
|
+
def self.encode_base58(hex)
|
405
|
+
leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
|
406
|
+
("1"*leading_zero_bytes) + Helper.int_to_base58( hex.to_i(16) )
|
407
|
+
end
|
408
|
+
|
409
|
+
def self.decode_base58(base58_val)
|
410
|
+
s = Helper.base58_to_int(base58_val).to_s(16); s = (s.bytesize.odd? ? '0'+s : s)
|
411
|
+
s = '' if s == '00'
|
412
|
+
leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 : '').size
|
413
|
+
s = ("00"*leading_zero_bytes) + s if leading_zero_bytes > 0
|
414
|
+
s
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ci_block_io
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Atsuhiro Tsuruta feat. Atif Nazir
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ecdsa
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.2'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 1.2.0
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '1.2'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.2.0
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: httpclient
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.8'
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 2.8.0
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '2.8'
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 2.8.0
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: oj
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '3.3'
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 3.3.5
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '3.3'
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 3.3.5
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: pbkdf2-ruby
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - "~>"
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0.2'
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.2.1
|
111
|
+
type: :runtime
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.2'
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 0.2.1
|
121
|
+
description: This Ruby Gem is the official reference client for the Block.io payments
|
122
|
+
API. To use this, you will need the Dogecoin, Bitcoin, or Litecoin API key(s) from
|
123
|
+
Block.io. Go ahead, sign up :)
|
124
|
+
email:
|
125
|
+
- a.tsuruta@1-box.co.jp
|
126
|
+
executables: []
|
127
|
+
extensions: []
|
128
|
+
extra_rdoc_files: []
|
129
|
+
files:
|
130
|
+
- ".gitignore"
|
131
|
+
- Gemfile
|
132
|
+
- LICENSE
|
133
|
+
- README.md
|
134
|
+
- Rakefile
|
135
|
+
- ci_block_io.gemspec
|
136
|
+
- lib/ci_block_io.rb
|
137
|
+
- lib/ci_block_io/version.rb
|
138
|
+
homepage: https://block.io/api/simple/ruby
|
139
|
+
licenses:
|
140
|
+
- MIT
|
141
|
+
metadata: {}
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 2.7.3
|
159
|
+
signing_key:
|
160
|
+
specification_version: 4
|
161
|
+
summary: An easy to use Dogecoin, Bitcoin, Litecoin wallet API by Block.io. Sign up
|
162
|
+
required at Block.io.
|
163
|
+
test_files: []
|