block_io 0.1.3 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -3
- data/block_io.gemspec +2 -1
- data/examples/basic.rb +22 -0
- data/examples/dtrust.rb +89 -0
- data/lib/block_io.rb +177 -136
- data/lib/block_io/version.rb +1 -1
- metadata +25 -4
- data/LICENSE.txt +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a5c7f9ebfcf7f608010b2f742ebe7e2c44035ab
|
4
|
+
data.tar.gz: 29253bde1cdcf14f17c7000165ea00571936fef7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d999194b92fe5b45ba67ebb21a422f4e1796ffaadfe5a6bb57c0735486e9150339cc0c8638f1577e68fe1392047ba3e8773abea859d7ab26fc12574ca5dc655e
|
7
|
+
data.tar.gz: 0332287b974c9bab30ed52f59031d0508b1e410be694c1bcb9b5363e459b1efbf74ebcd59443146f9f35bf8a7b38816926f68753532e26801340177afe9d3565
|
data/README.md
CHANGED
@@ -17,6 +17,8 @@ Or install it yourself as:
|
|
17
17
|
$ gem install block_io
|
18
18
|
|
19
19
|
## Changelog
|
20
|
+
|
21
|
+
*09/27/14*: Now supporting client-side signatures. API v2 recommended.
|
20
22
|
|
21
23
|
*07/01/14*: Forcing TLSv1 usage since Block.io does not support SSLv3 due to its vulnerable nature. Fixed:
|
22
24
|
HTTPClient.new.ssl_config.ssl_version = :TLSv1
|
@@ -27,18 +29,18 @@ Or install it yourself as:
|
|
27
29
|
It's super easy to get started. In your Ruby shell ($ irb), for example, do this:
|
28
30
|
|
29
31
|
require 'block_io'
|
30
|
-
BlockIo.set_options :api_key => 'API KEY', :pin => 'SECRET PIN'
|
32
|
+
BlockIo.set_options :api_key => 'API KEY', :pin => 'SECRET PIN', :version => 2
|
31
33
|
|
32
34
|
And you're good to go:
|
33
35
|
|
34
36
|
BlockIo.get_new_address
|
35
37
|
BlockIo.get_my_addresses
|
36
38
|
|
37
|
-
For more information, see https://block.io/api
|
39
|
+
For more information, see https://block.io/api/simple/ruby
|
38
40
|
|
39
41
|
## Contributing
|
40
42
|
|
41
|
-
1. Fork it ( https://github.com/
|
43
|
+
1. Fork it ( https://github.com/BlockIo/gem-block-io/fork )
|
42
44
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
43
45
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
44
46
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/block_io.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["a@block.io"]
|
11
11
|
spec.summary = %q{An easy to use Dogecoin, Bitcoin, Litecoin wallet API by Block.io. Sign up required at Block.io.}
|
12
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/ruby"
|
13
|
+
spec.homepage = "https://block.io/api/simple/ruby"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.6"
|
22
22
|
spec.add_development_dependency "rake", "~> 0"
|
23
|
+
spec.add_runtime_dependency "ecdsa", "~> 1.2", '>= 1.2.0'
|
23
24
|
spec.add_runtime_dependency "httpclient", "~> 2.4", '>= 2.4.0'
|
24
25
|
spec.add_runtime_dependency "json", "~> 1.8", '>= 1.8.1'
|
25
26
|
spec.add_runtime_dependency "connection_pool", "~> 2.0", '>= 2.0.0'
|
data/examples/basic.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# creates a new destination address, withdraws from the default label to it, gets sent transactions, and the current price
|
2
|
+
|
3
|
+
require 'block_io'
|
4
|
+
|
5
|
+
# please use the Dogecoin Testnet API key here
|
6
|
+
puts BlockIo.set_options :api_key => 'YOURDOGECOINTESTNETAPIKEY', :pin => 'YOURSECRETPIN', :version => 2
|
7
|
+
|
8
|
+
begin
|
9
|
+
puts BlockIo.get_new_address(:label => 'testDest')
|
10
|
+
rescue Exception => e
|
11
|
+
# if this failed, we probably created testDest label before
|
12
|
+
puts e.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
puts BlockIo.withdraw_from_labels(:from_labels => 'default', :to_label => 'testDest', :amount => '3.5')
|
16
|
+
|
17
|
+
puts BlockIo.get_address_by_label(:label => 'default')
|
18
|
+
|
19
|
+
puts BlockIo.get_transactions(:type => 'sent') # API v2 only
|
20
|
+
|
21
|
+
puts BlockIo.get_current_price(:base_price => 'BTC')
|
22
|
+
|
data/examples/dtrust.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# creates a new destination address, withdraws from the default label to it, gets sent transactions, and the current price
|
2
|
+
|
3
|
+
require 'block_io'
|
4
|
+
|
5
|
+
# please use the Dogecoin Testnet API key here
|
6
|
+
puts "*** Initialize BlockIo library: "
|
7
|
+
puts JSON.pretty_generate(BlockIo.set_options :api_key => 'YourDogecoinTestnetAPIKey', :pin => 'YourSecretPIN', :version => 2)
|
8
|
+
|
9
|
+
|
10
|
+
# create 4 keys
|
11
|
+
keys = [ BlockIo::Key.from_passphrase('alpha1alpha2alpha3alpha4'), BlockIo::Key.from_passphrase('alpha4alpha1alpha2alpha3'), BlockIo::Key.from_passphrase('alpha3alpha4alpha1alpha2'), BlockIo::Key.from_passphrase('alpha2alpha3alpha4alpha1') ]
|
12
|
+
|
13
|
+
dtrust_address = nil
|
14
|
+
|
15
|
+
begin
|
16
|
+
# let's create a new address with all 4 keys as signers, but only 3 signers required (i.e., 4 of 5 multisig, with 1 signature being Block.io)
|
17
|
+
|
18
|
+
signers = ""
|
19
|
+
keys.each { |key| signers += ',' if signers.length > 0; signers += key.public_key; }
|
20
|
+
|
21
|
+
response = BlockIo.get_new_dtrust_address(:label => 'dTrust1', :public_keys => signers, :required_signatures => 3)
|
22
|
+
|
23
|
+
dtrust_address = response['data']['address']
|
24
|
+
rescue Exception => e
|
25
|
+
# if this failed, we probably created the same label before. let's fetch the address then.
|
26
|
+
puts e.to_s
|
27
|
+
|
28
|
+
response = BlockIo.get_dtrust_address_by_label(:label => 'dTrust1')
|
29
|
+
|
30
|
+
dtrust_address = response['data']['address']
|
31
|
+
end
|
32
|
+
|
33
|
+
puts "*** Our dTrust Address: #{dtrust_address}"
|
34
|
+
|
35
|
+
# let's deposit some coins into this new address
|
36
|
+
response = BlockIo.withdraw_from_labels(:from_labels => 'default', :to_address => dtrust_address, :amount => '3.5')
|
37
|
+
|
38
|
+
puts "*** Withdrawal response:"
|
39
|
+
puts JSON.pretty_generate(response)
|
40
|
+
|
41
|
+
|
42
|
+
# fetch the dtrust address' balance
|
43
|
+
puts "*** dTrust1 Balance:"
|
44
|
+
puts JSON.pretty_generate(BlockIo.get_dtrust_address_balance(:label => 'dTrust1'))
|
45
|
+
|
46
|
+
# withdraw a few coins from dtrust1 to the default label
|
47
|
+
normal_address = BlockIo.get_address_by_label(:label => 'default')['data']['address']
|
48
|
+
|
49
|
+
puts "*** Withdrawing from dTrust1 to the 'default' label in normal multisig"
|
50
|
+
|
51
|
+
response = BlockIo.withdraw_from_dtrust_address(:from_labels => 'dTrust1', :to_addresses => normal_address, :amounts => '2.1')
|
52
|
+
|
53
|
+
puts JSON.pretty_generate(response)
|
54
|
+
|
55
|
+
# let's sign for the public keys specified
|
56
|
+
|
57
|
+
response['data']['inputs'].each do |input|
|
58
|
+
# for each input
|
59
|
+
|
60
|
+
data_to_sign = input['data_to_sign']
|
61
|
+
|
62
|
+
input['signers'].each do |signer|
|
63
|
+
|
64
|
+
# figure out if we have the public key that matches this signer
|
65
|
+
|
66
|
+
keys.each do |key|
|
67
|
+
# iterate over all keys till we've found the one that we need
|
68
|
+
|
69
|
+
signer['signed_data'] = key.sign(data_to_sign) if key.public_key == signer['signer_public_key']
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
puts "*** Our signed response: "
|
78
|
+
puts JSON.pretty_generate(response['data']) #.to_json
|
79
|
+
|
80
|
+
# let's final the withdrawal
|
81
|
+
puts "*** Finalize withdrawal: "
|
82
|
+
puts JSON.pretty_generate(BlockIo.sign_and_finalize_withdrawal(:signature_data => response['data'].to_json))
|
83
|
+
|
84
|
+
# get the sent transactions for this dTrust address
|
85
|
+
|
86
|
+
puts "*** Get transactions sent by our dTrust1 address: "
|
87
|
+
|
88
|
+
puts JSON.pretty_generate(BlockIo.get_dtrust_transactions(:type => 'sent', :labels => 'dTrust1'))
|
89
|
+
|
data/lib/block_io.rb
CHANGED
@@ -1,204 +1,245 @@
|
|
1
|
-
require
|
1
|
+
require 'block_io/version'
|
2
2
|
require 'httpclient'
|
3
3
|
require 'json'
|
4
4
|
require 'connection_pool'
|
5
|
+
require 'ecdsa'
|
6
|
+
require 'openssl'
|
7
|
+
require 'digest'
|
8
|
+
require 'securerandom'
|
9
|
+
require 'base64'
|
5
10
|
|
6
11
|
module BlockIo
|
7
12
|
|
8
13
|
@api_key = nil
|
9
|
-
@base_url = "https://block.io/api/
|
14
|
+
@base_url = "https://block.io/api/VERSION/API_CALL/?api_key="
|
10
15
|
@pin = nil
|
16
|
+
@encryptionKey = nil
|
11
17
|
@conn_pool = nil
|
18
|
+
@version = nil
|
12
19
|
|
13
20
|
def self.set_options(args = {})
|
14
21
|
# initialize BlockIo
|
15
22
|
@api_key = args[:api_key]
|
16
23
|
@pin = args[:pin]
|
17
|
-
@
|
24
|
+
@encryptionKey = Helper.pinToAesKey(@pin) if !@pin.nil?
|
18
25
|
|
26
|
+
@conn_pool = ConnectionPool.new(size: 5, timeout: 300) { HTTPClient.new }
|
27
|
+
|
28
|
+
@version = args[:version] || 2 # default version is 2
|
29
|
+
|
19
30
|
self.api_call(['get_balance',""])
|
20
31
|
end
|
21
32
|
|
22
|
-
def self.
|
23
|
-
# returns the offline vault's balance
|
24
|
-
endpoint = ["get_offline_vault_balance",""]
|
25
|
-
self.api_call(endpoint)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.get_offline_vault_address
|
29
|
-
# returns the offline vault's address and balance info
|
30
|
-
self.get_offline_vault_balance
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.get_balance
|
34
|
-
# returns the balances for your account tied to the API key
|
35
|
-
endpoint = ["get_balance", ""]
|
36
|
-
self.api_call(endpoint)
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.get_user_balance(args)
|
40
|
-
# returns the specified user's balance
|
41
|
-
|
42
|
-
user_id = args[:user_id]
|
33
|
+
def self.method_missing(m, *args, &block)
|
43
34
|
|
44
|
-
|
35
|
+
method_name = m.to_s
|
45
36
|
|
46
|
-
|
37
|
+
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
|
47
38
|
|
48
|
-
|
49
|
-
end
|
39
|
+
self.withdraw(args.first, m.to_s)
|
50
40
|
|
51
|
-
|
52
|
-
|
41
|
+
else
|
42
|
+
params = get_params(args.first)
|
43
|
+
self.api_call([method_name, params])
|
44
|
+
end
|
53
45
|
|
54
|
-
|
55
|
-
address_label = args[:address_label]
|
56
|
-
|
57
|
-
raise Exception.new("Must provide ONE of address or address_label") if (!address.nil? and !address_label.nil?) or (address.nil? and address_label.nil?)
|
58
|
-
|
59
|
-
endpoint = ['get_address_balance',"&address=#{address}"] unless address.nil?
|
60
|
-
endpoint = ['get_address_balance',"&address_label=#{address_label}"] unless address_label.nil?
|
46
|
+
end
|
61
47
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
def self.get_current_price(args = {})
|
66
|
-
# returns prices from different exchanges as an array of hashes
|
67
|
-
price_base = args[:price_base]
|
68
|
-
|
69
|
-
endpoint = ['get_current_price', '']
|
70
|
-
endpoint = ['get_current_price',"&price_base=#{price_base}"] unless price_base.nil? or price_base.to_s.length == 0
|
48
|
+
def self.withdraw(args = {}, method_name = 'withdraw')
|
49
|
+
# validate arguments for withdrawal of funds TODO
|
71
50
|
|
72
|
-
|
73
|
-
end
|
51
|
+
raise Exception.new("PIN not set. Use BlockIo.set_options(:api_key=>'API KEY',:pin=>'SECRET PIN',:version=>'API VERSION')") if @pin.nil?
|
74
52
|
|
75
|
-
|
76
|
-
# withdraws coins from the given user(s)
|
77
|
-
self.withdraw(args)
|
78
|
-
end
|
53
|
+
params = get_params(args)
|
79
54
|
|
80
|
-
|
81
|
-
# validate arguments for withdrawal of funds TODO
|
55
|
+
params += "&pin=#{@pin}" if @version == 1 # Block.io handles the Secret PIN in the legacy API (v1)
|
82
56
|
|
83
|
-
|
57
|
+
response = self.api_call([method_name, params])
|
58
|
+
|
59
|
+
if response['data'].has_key?('reference_id') then
|
60
|
+
# Block.io's asking us to provide some client-side signatures, let's get to it
|
84
61
|
|
85
|
-
|
86
|
-
|
87
|
-
to_user_id = args[:to_user_id]
|
88
|
-
payment_address = args[:payment_address]
|
89
|
-
from_user_ids = args[:from_user_ids] || args[:from_user_id]
|
62
|
+
# extract the passphrase
|
63
|
+
encrypted_passphrase = response['data']['encrypted_passphrase']['passphrase']
|
90
64
|
|
91
|
-
|
92
|
-
|
65
|
+
# let's get our private key
|
66
|
+
key = Helper.extractKey(encrypted_passphrase, @encryptionKey)
|
93
67
|
|
94
|
-
|
95
|
-
endpoint = ['withdraw',"&amount=#{amount}&to_user_id=#{to_user_id}&pin=#{@pin}"] unless to_user_id.nil?
|
96
|
-
endpoint = ['withdraw',"&amount=#{amount}&from_user_ids=#{from_user_ids}&pin=#{@pin}&payment_address=#{payment_address}"] unless from_user_ids.nil? or payment_address.nil?
|
97
|
-
endpoint = ['withdraw',"&amount=#{amount}&from_user_ids=#{from_user_ids}&to_user_id=#{to_user_id}&pin=#{@pin}"] unless to_user_id.nil? or from_user_ids.nil?
|
68
|
+
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']
|
98
69
|
|
99
|
-
|
100
|
-
|
70
|
+
# let's sign all the inputs we can
|
71
|
+
inputs = response['data']['inputs']
|
101
72
|
|
102
|
-
|
103
|
-
|
104
|
-
|
73
|
+
inputs.each do |input|
|
74
|
+
# iterate over all signers
|
75
|
+
|
76
|
+
input['signers'].each do |signer|
|
77
|
+
# if our public key matches this signer's public key, sign the data
|
105
78
|
|
106
|
-
|
107
|
-
endpoint = ["get_new_address","&address_label=#{address_label}"] unless address_label.nil?
|
79
|
+
signer['signed_data'] = key.sign(input['data_to_sign']) if signer['signer_public_key'] == key.public_key
|
108
80
|
|
109
|
-
|
110
|
-
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
111
84
|
|
112
|
-
|
113
|
-
# validate arguments for getting a new address
|
114
|
-
address_label = args[:address_label]
|
85
|
+
# the response object is now signed, let's stringify it and finalize this withdrawal
|
115
86
|
|
116
|
-
|
117
|
-
endpoint = ['create_user',"&address_label=#{address_label}"] unless address_label.nil?
|
87
|
+
response = self.api_call(['sign_and_finalize_withdrawal',{:signature_data => response['data'].to_json}])
|
118
88
|
|
119
|
-
|
120
|
-
|
89
|
+
# if we provided all the required signatures, this transaction went through
|
90
|
+
# otherwise Block.io responded with data asking for more signatures
|
91
|
+
# the latter will be the case for dTrust addresses
|
92
|
+
end
|
121
93
|
|
122
|
-
|
123
|
-
# returns all the addresses in your account tied to the API key
|
124
|
-
endpoint = ["get_my_addresses",""]
|
94
|
+
return response
|
125
95
|
|
126
|
-
self.api_call(endpoint)
|
127
96
|
end
|
128
97
|
|
129
|
-
def self.get_users(args = {})
|
130
|
-
# returns all the addresses in your account tied to the API key
|
131
|
-
endpoint = ['get_users',""]
|
132
|
-
|
133
|
-
self.api_call(endpoint)
|
134
|
-
end
|
135
98
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
user_id = args[:user_id]
|
140
|
-
address = args[:address]
|
99
|
+
private
|
100
|
+
|
101
|
+
def self.api_call(endpoint)
|
141
102
|
|
142
|
-
|
103
|
+
body = nil
|
143
104
|
|
144
|
-
|
145
|
-
|
146
|
-
endpoint = ['get_address_received',"&address_label=#{address_label}"] unless address_label.nil?
|
147
|
-
endpoint = ['get_address_received',"&address=#{address}"] unless address.nil?
|
105
|
+
@conn_pool.with do |hc|
|
106
|
+
# prevent initiation of HTTPClients every time we make this call, use a connection_pool
|
148
107
|
|
149
|
-
|
108
|
+
hc.ssl_config.ssl_version = :TLSv1
|
109
|
+
response = hc.post("#{@base_url.gsub('API_CALL',endpoint[0]).gsub('VERSION', 'v'+@version.to_s) + @api_key}", endpoint[1])
|
110
|
+
|
111
|
+
begin
|
112
|
+
body = JSON.parse(response.body)
|
113
|
+
raise Exception.new(body['data']['error_message']) if !body['status'].eql?('success')
|
114
|
+
rescue
|
115
|
+
raise Exception.new('Unknown error occurred. Please report this.')
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
body
|
150
120
|
end
|
151
121
|
|
152
|
-
|
153
|
-
# returns the user's received coins, confirmed and unconfirmed
|
154
|
-
|
155
|
-
user_id = args[:user_id]
|
122
|
+
private
|
156
123
|
|
157
|
-
|
124
|
+
def self.get_params(args)
|
125
|
+
# construct the parameter string
|
126
|
+
params = ""
|
127
|
+
|
128
|
+
args.each do |k,v|
|
129
|
+
params += '&' if params.length > 0
|
130
|
+
params += "#{k.to_s}=#{v.to_s}"
|
131
|
+
end
|
158
132
|
|
159
|
-
|
133
|
+
return params
|
160
134
|
end
|
161
135
|
|
162
|
-
|
163
|
-
# get address by label
|
164
|
-
|
165
|
-
address_label = args[:address_label]
|
166
|
-
|
167
|
-
raise Exception.new("Must provide address_label") if address_label.nil?
|
168
|
-
|
169
|
-
endpoint = ["get_address_by_label","&address_label=#{address_label}"]
|
136
|
+
public
|
170
137
|
|
171
|
-
|
172
|
-
end
|
173
|
-
|
174
|
-
def self.get_user_address(args = {})
|
175
|
-
# gets the user's address
|
138
|
+
class Key
|
176
139
|
|
177
|
-
|
140
|
+
def initialize(privkey = nil)
|
141
|
+
# the privkey must be in hex if at all provided
|
178
142
|
|
179
|
-
|
143
|
+
@group = ECDSA::Group::Secp256k1
|
144
|
+
@private_key = privkey.to_i(16) || 1 + SecureRandom.random_number(group.order - 1)
|
145
|
+
@public_key = @group.generator.multiply_by_scalar(@private_key)
|
180
146
|
|
181
|
-
|
147
|
+
end
|
148
|
+
|
149
|
+
def private_key
|
150
|
+
# returns private key in hex form
|
151
|
+
return @private_key.to_s(16)
|
152
|
+
end
|
153
|
+
|
154
|
+
def public_key
|
155
|
+
# returns the compressed form of the public key to save network fees (shorter scripts)
|
182
156
|
|
183
|
-
|
184
|
-
|
157
|
+
return ECDSA::Format::PointOctetString.encode(@public_key, compression: true).unpack("H*")[0]
|
158
|
+
end
|
159
|
+
|
160
|
+
def sign(data)
|
161
|
+
# signed the given hexadecimal string
|
185
162
|
|
186
|
-
|
163
|
+
nonce = 1 + SecureRandom.random_number(@group.order - 1) # nonce, can be made deterministic TODO
|
164
|
+
|
165
|
+
signature = ECDSA.sign(@group, @private_key, [data].pack("H*"), nonce)
|
187
166
|
|
188
|
-
|
167
|
+
# DER encode this, and return it in hex form
|
189
168
|
|
190
|
-
|
169
|
+
return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*")[0]
|
191
170
|
|
192
|
-
|
193
|
-
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.from_passphrase(passphrase)
|
174
|
+
# create a private+public key pair from a given passphrase
|
175
|
+
# think of this as your brain wallet. be very sure to use a sufficiently long passphrase
|
176
|
+
# if you don't want a passphrase, just use Key.new and it will generate a random key for you
|
177
|
+
|
178
|
+
raise Exception.new('Must provide passphrase at least 8 characters long.') if passphrase.nil? or passphrase.length < 8
|
179
|
+
|
180
|
+
hashed_key = Helper.sha256([passphrase].pack("H*")) # must pass bytes to sha256
|
194
181
|
|
195
|
-
|
196
|
-
|
197
|
-
|
182
|
+
return Key.new(hashed_key)
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
module Helper
|
188
|
+
|
189
|
+
def self.extractKey(encrypted_data, b64_enc_key)
|
190
|
+
# passphrase is in plain text
|
191
|
+
# encrypted_data is in base64, as it was stored on Block.io
|
192
|
+
# returns the private key extracted from the given encrypted data
|
198
193
|
|
194
|
+
decrypted = self.decrypt(encrypted_data, b64_enc_key)
|
195
|
+
|
196
|
+
return Key.from_passphrase(decrypted)
|
199
197
|
end
|
200
198
|
|
201
|
-
|
199
|
+
def self.sha256(value)
|
200
|
+
# returns the hex of the hash of the given value
|
201
|
+
hash = Digest::SHA2.new(256)
|
202
|
+
hash << value
|
203
|
+
hash.hexdigest # return hex
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.pinToAesKey(secret_pin, iterations = 2048)
|
207
|
+
# converts the pincode string to PBKDF2
|
208
|
+
# returns a base64 version of PBKDF2 pincode
|
209
|
+
salt = ""
|
210
|
+
aes_key_bin = OpenSSL::PKCS5.pbkdf2_hmac(secret_pin, salt, iterations/2, 16, OpenSSL::Digest::SHA256.new)
|
211
|
+
aes_key_bin = OpenSSL::PKCS5.pbkdf2_hmac(aes_key_bin.unpack("H*")[0], salt, iterations/2, 32, OpenSSL::Digest::SHA256.new)
|
212
|
+
|
213
|
+
return Base64.strict_encode64(aes_key_bin) # the base64 encryption key
|
214
|
+
end
|
215
|
+
|
216
|
+
# Decrypts a block of data (encrypted_data) given an encryption key
|
217
|
+
def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
|
218
|
+
|
219
|
+
response = nil
|
220
|
+
|
221
|
+
begin
|
222
|
+
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
|
223
|
+
aes.decrypt
|
224
|
+
aes.key = Base64.strict_decode64(b64_enc_key)
|
225
|
+
aes.iv = iv if iv != nil
|
226
|
+
response = aes.update(Base64.strict_decode64(encrypted_data)) + aes.final
|
227
|
+
rescue Exception => e
|
228
|
+
# decryption failed, must be an invalid Secret PIN
|
229
|
+
raise Exception.new('Invalid Secret PIN provided.')
|
230
|
+
end
|
231
|
+
|
232
|
+
return response
|
233
|
+
end
|
234
|
+
|
235
|
+
# Encrypts a block of data given an encryption key
|
236
|
+
def self.encrypt(data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
|
237
|
+
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
|
238
|
+
aes.encrypt
|
239
|
+
aes.key = Base64.strict_decode64(b64_enc_key)
|
240
|
+
aes.iv = iv if iv != nil
|
241
|
+
Base64.strict_encode64(aes.update(data) + aes.final)
|
242
|
+
end
|
202
243
|
end
|
203
244
|
|
204
245
|
end
|
data/lib/block_io/version.rb
CHANGED
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: 0.1
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Atif Nazir
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,26 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
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
|
41
61
|
- !ruby/object:Gem::Dependency
|
42
62
|
name: httpclient
|
43
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,13 +129,14 @@ extra_rdoc_files: []
|
|
109
129
|
files:
|
110
130
|
- Gemfile
|
111
131
|
- LICENSE
|
112
|
-
- LICENSE.txt
|
113
132
|
- README.md
|
114
133
|
- Rakefile
|
115
134
|
- block_io.gemspec
|
135
|
+
- examples/basic.rb
|
136
|
+
- examples/dtrust.rb
|
116
137
|
- lib/block_io.rb
|
117
138
|
- lib/block_io/version.rb
|
118
|
-
homepage: https://block.io/api/ruby
|
139
|
+
homepage: https://block.io/api/simple/ruby
|
119
140
|
licenses:
|
120
141
|
- MIT
|
121
142
|
metadata: {}
|
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2014 Atif Nazir
|
2
|
-
|
3
|
-
MIT License
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|