ci_block_io 1.7.0 → 1.7.1
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/lib/ci_block_io.rb +48 -51
- data/lib/ci_block_io/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 93926bfd4df14d9634d0ea789c86fdd7737e485f129a7b966b52ea110ab96d46
|
4
|
+
data.tar.gz: b605c6dc58f4f2eb583691767f1ee2a7c97b8a920675787e4fcc6b46785d5480
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a492c73929e34c787b4cb7d4773f3024a0ffbbf0984a49bbccbf3e9c6739697ff70ea1cd04fe6cc39e419c45e2cc56d02c650dc076898cda9ed8187192caa03b
|
7
|
+
data.tar.gz: 26e60257ab22e318649b2f741807b591e73e41c4ce377ab6802f83510777f4a92e41bd5af9354e7f61c295b052f188b3279bba309b18685294ea8c9e545dfddd
|
data/lib/ci_block_io.rb
CHANGED
@@ -30,18 +30,18 @@ module CiBlockIo
|
|
30
30
|
# initialize BlockIo
|
31
31
|
@api_key = args[:api_key]
|
32
32
|
@pin = args[:pin]
|
33
|
-
|
33
|
+
|
34
34
|
@encryptionKey = Helper.pinToAesKey(@pin) if !@pin.nil?
|
35
35
|
|
36
36
|
hostname = args[:hostname] || "block.io"
|
37
37
|
@base_url = "https://" << hostname << "/api/VERSION/API_CALL/?api_key="
|
38
|
-
|
38
|
+
|
39
39
|
@client = HTTPClient.new
|
40
40
|
@client.tcp_keepalive = true
|
41
41
|
@client.ssl_config.ssl_version = :auto
|
42
|
-
|
42
|
+
|
43
43
|
@version = args[:version] || 2 # default version is 2
|
44
|
-
|
44
|
+
|
45
45
|
self.api_call(['get_balance',""])
|
46
46
|
end
|
47
47
|
|
@@ -59,7 +59,7 @@ module CiBlockIo
|
|
59
59
|
params = get_params(args.first)
|
60
60
|
self.api_call([method_name, params])
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
end
|
64
64
|
|
65
65
|
def self.withdraw(args = {}, method_name = 'withdraw')
|
@@ -72,7 +72,7 @@ module CiBlockIo
|
|
72
72
|
params << "&pin=" << @pin if @version == 1 # Block.io handles the Secret PIN in the legacy API (v1)
|
73
73
|
|
74
74
|
response = self.api_call([method_name, params])
|
75
|
-
|
75
|
+
|
76
76
|
if response['data'].has_key?('reference_id') then
|
77
77
|
# Block.io's asking us to provide some client-side signatures, let's get to it
|
78
78
|
|
@@ -114,7 +114,7 @@ module CiBlockIo
|
|
114
114
|
params = get_params(args)
|
115
115
|
|
116
116
|
response = self.api_call([method_name, params])
|
117
|
-
|
117
|
+
|
118
118
|
if response['data'].has_key?('reference_id') then
|
119
119
|
# Block.io's asking us to provide some client-side signatures, let's get to it
|
120
120
|
|
@@ -136,7 +136,7 @@ module CiBlockIo
|
|
136
136
|
|
137
137
|
|
138
138
|
private
|
139
|
-
|
139
|
+
|
140
140
|
def self.api_call(endpoint)
|
141
141
|
return @mock_response[endpoint[0]] if @mock_response.keys.include?(endpoint[0])
|
142
142
|
|
@@ -145,21 +145,19 @@ module CiBlockIo
|
|
145
145
|
return nil if base_url.nil? || @api_key.nil?
|
146
146
|
|
147
147
|
response = @client.post(base_url + @api_key, endpoint[1])
|
148
|
-
|
148
|
+
|
149
149
|
begin
|
150
150
|
body = Oj.load(response.body)
|
151
151
|
return unless body['status'].eql?('success')
|
152
152
|
case endpoint[0]
|
153
|
-
when 'withdraw'
|
153
|
+
when 'withdraw'
|
154
154
|
raise CiBlockIoWithdrawException.new(body['data']['error_message'])
|
155
155
|
else
|
156
156
|
raise Exception.new(body['data']['error_message'])
|
157
157
|
end
|
158
|
-
end
|
159
158
|
rescue
|
160
159
|
raise Exception.new('Unknown error occurred. Please report this.')
|
161
160
|
end
|
162
|
-
|
163
161
|
body
|
164
162
|
end
|
165
163
|
|
@@ -169,7 +167,7 @@ module CiBlockIo
|
|
169
167
|
# construct the parameter string
|
170
168
|
params = ""
|
171
169
|
args = {} if args.nil?
|
172
|
-
|
170
|
+
|
173
171
|
args.each do |k,v|
|
174
172
|
params += '&' if params.length > 0
|
175
173
|
params += "#{k.to_s}=#{v.to_s}"
|
@@ -191,23 +189,23 @@ module CiBlockIo
|
|
191
189
|
@compressed = compressed
|
192
190
|
|
193
191
|
end
|
194
|
-
|
192
|
+
|
195
193
|
def private_key
|
196
194
|
# returns private key in hex form
|
197
195
|
return @private_key.to_s(16)
|
198
196
|
end
|
199
|
-
|
197
|
+
|
200
198
|
def public_key
|
201
199
|
# returns the compressed form of the public key to save network fees (shorter scripts)
|
202
200
|
|
203
201
|
return ECDSA::Format::PointOctetString.encode(@public_key, compression: @compressed).unpack("H*")[0]
|
204
202
|
end
|
205
|
-
|
203
|
+
|
206
204
|
def sign(data)
|
207
205
|
# signed the given hexadecimal string
|
208
206
|
|
209
207
|
nonce = deterministicGenerateK([data].pack("H*"), @private_key) # RFC6979
|
210
|
-
|
208
|
+
|
211
209
|
signature = ECDSA.sign(@group, @private_key, data.to_i(16), nonce)
|
212
210
|
|
213
211
|
# BIP0062 -- use lower S values only
|
@@ -221,14 +219,14 @@ module CiBlockIo
|
|
221
219
|
# DER encode this, and return it in hex form
|
222
220
|
return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*")[0]
|
223
221
|
end
|
224
|
-
|
222
|
+
|
225
223
|
def self.from_passphrase(passphrase)
|
226
224
|
# create a private+public key pair from a given passphrase
|
227
225
|
# think of this as your brain wallet. be very sure to use a sufficiently long passphrase
|
228
226
|
# if you don't want a passphrase, just use Key.new and it will generate a random key for you
|
229
|
-
|
227
|
+
|
230
228
|
raise Exception.new('Must provide passphrase at least 8 characters long.') if passphrase.nil? or passphrase.length < 8
|
231
|
-
|
229
|
+
|
232
230
|
hashed_key = Helper.sha256([passphrase].pack("H*")) # must pass bytes to sha256
|
233
231
|
|
234
232
|
return Key.new(hashed_key)
|
@@ -246,65 +244,65 @@ module CiBlockIo
|
|
246
244
|
return Key.new(actual_key, compressed)
|
247
245
|
|
248
246
|
end
|
249
|
-
|
247
|
+
|
250
248
|
def isPositive(i)
|
251
249
|
sig = "!+-"[i <=> 0]
|
252
|
-
|
250
|
+
|
253
251
|
return sig.eql?("+")
|
254
252
|
end
|
255
|
-
|
253
|
+
|
256
254
|
def deterministicGenerateK(data, privkey, group = ECDSA::Group::Secp256k1)
|
257
255
|
# returns a deterministic K -- RFC6979
|
258
256
|
|
259
257
|
hash = data.bytes.to_a
|
260
258
|
|
261
259
|
x = [privkey.to_s(16)].pack("H*").bytes.to_a
|
262
|
-
|
260
|
+
|
263
261
|
k = []
|
264
262
|
32.times { k.insert(0, 0) }
|
265
|
-
|
263
|
+
|
266
264
|
v = []
|
267
265
|
32.times { v.insert(0, 1) }
|
268
|
-
|
266
|
+
|
269
267
|
# step D
|
270
268
|
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).concat(x).concat(hash).pack("C*")).bytes.to_a
|
271
|
-
|
269
|
+
|
272
270
|
# step E
|
273
271
|
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
274
|
-
|
272
|
+
|
275
273
|
# puts "E: " + v.pack("C*").unpack("H*")[0]
|
276
|
-
|
274
|
+
|
277
275
|
# step F
|
278
276
|
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([1]).concat(x).concat(hash).pack("C*")).bytes.to_a
|
279
|
-
|
277
|
+
|
280
278
|
# step G
|
281
279
|
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
282
|
-
|
280
|
+
|
283
281
|
# step H2b (Step H1/H2a ignored)
|
284
282
|
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
285
|
-
|
283
|
+
|
286
284
|
h2b = v.pack("C*").unpack("H*")[0]
|
287
285
|
tNum = h2b.to_i(16)
|
288
|
-
|
286
|
+
|
289
287
|
# step H3
|
290
288
|
while (!isPositive(tNum) or tNum >= group.order) do
|
291
289
|
# k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k)
|
292
290
|
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).pack("C*")).bytes.to_a
|
293
|
-
|
291
|
+
|
294
292
|
# v = crypto.HmacSHA256(v, k)
|
295
293
|
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
296
|
-
|
294
|
+
|
297
295
|
# T = BigInteger.fromBuffer(v)
|
298
296
|
tNum = v.pack("C*").unpack("H*")[0].to_i(16)
|
299
297
|
end
|
300
|
-
|
298
|
+
|
301
299
|
return tNum
|
302
300
|
end
|
303
301
|
|
304
302
|
end
|
305
|
-
|
303
|
+
|
306
304
|
module Helper
|
307
|
-
|
305
|
+
|
308
306
|
def self.signData(inputs, keys)
|
309
307
|
# sign the given data with the given keys
|
310
308
|
# TODO loop is O(n^3), make it better
|
@@ -320,7 +318,7 @@ module CiBlockIo
|
|
320
318
|
while j < input['signers'].size do
|
321
319
|
# if our public key matches this signer's public key, sign the data
|
322
320
|
signer = inputs[i]['signers'][j]
|
323
|
-
|
321
|
+
|
324
322
|
k = 0
|
325
323
|
while k < keys.size do
|
326
324
|
# sign for each key provided, if we can
|
@@ -342,19 +340,19 @@ module CiBlockIo
|
|
342
340
|
# passphrase is in plain text
|
343
341
|
# encrypted_data is in base64, as it was stored on Block.io
|
344
342
|
# returns the private key extracted from the given encrypted data
|
345
|
-
|
343
|
+
|
346
344
|
decrypted = self.decrypt(encrypted_data, b64_enc_key)
|
347
|
-
|
345
|
+
|
348
346
|
return Key.from_passphrase(decrypted)
|
349
347
|
end
|
350
|
-
|
348
|
+
|
351
349
|
def self.sha256(value)
|
352
350
|
# returns the hex of the hash of the given value
|
353
351
|
hash = Digest::SHA2.new(256)
|
354
352
|
hash << value
|
355
353
|
hash.hexdigest # return hex
|
356
354
|
end
|
357
|
-
|
355
|
+
|
358
356
|
def self.pinToAesKey(secret_pin, iterations = 2048)
|
359
357
|
# converts the pincode string to PBKDF2
|
360
358
|
# returns a base64 version of PBKDF2 pincode
|
@@ -366,10 +364,10 @@ module CiBlockIo
|
|
366
364
|
|
367
365
|
return Base64.strict_encode64(aes_key_bin) # the base64 encryption key
|
368
366
|
end
|
369
|
-
|
367
|
+
|
370
368
|
# Decrypts a block of data (encrypted_data) given an encryption key
|
371
369
|
def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
|
372
|
-
|
370
|
+
|
373
371
|
response = nil
|
374
372
|
|
375
373
|
begin
|
@@ -385,7 +383,7 @@ module CiBlockIo
|
|
385
383
|
|
386
384
|
return response
|
387
385
|
end
|
388
|
-
|
386
|
+
|
389
387
|
# Encrypts a block of data given an encryption key
|
390
388
|
def self.encrypt(data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
|
391
389
|
aes = OpenSSL::Cipher.new(cipher_type)
|
@@ -396,7 +394,7 @@ module CiBlockIo
|
|
396
394
|
end
|
397
395
|
|
398
396
|
# courtesy bitcoin-ruby
|
399
|
-
|
397
|
+
|
400
398
|
def self.int_to_base58(int_val, leading_zero_bytes=0)
|
401
399
|
alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
402
400
|
base58_val, base = '', alpha.size
|
@@ -406,7 +404,7 @@ module CiBlockIo
|
|
406
404
|
end
|
407
405
|
base58_val
|
408
406
|
end
|
409
|
-
|
407
|
+
|
410
408
|
def self.base58_to_int(base58_val)
|
411
409
|
alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
412
410
|
int_val, base = 0, alpha.size
|
@@ -416,12 +414,12 @@ module CiBlockIo
|
|
416
414
|
end
|
417
415
|
int_val
|
418
416
|
end
|
419
|
-
|
417
|
+
|
420
418
|
def self.encode_base58(hex)
|
421
419
|
leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
|
422
420
|
("1"*leading_zero_bytes) + Helper.int_to_base58( hex.to_i(16) )
|
423
421
|
end
|
424
|
-
|
422
|
+
|
425
423
|
def self.decode_base58(base58_val)
|
426
424
|
s = Helper.base58_to_int(base58_val).to_s(16); s = (s.bytesize.odd? ? '0'+s : s)
|
427
425
|
s = '' if s == '00'
|
@@ -430,5 +428,4 @@ module CiBlockIo
|
|
430
428
|
s
|
431
429
|
end
|
432
430
|
end
|
433
|
-
|
434
431
|
end
|
data/lib/ci_block_io/version.rb
CHANGED