block_io 1.0.5 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0940811390b9b507fed0e47b2a04769206f6942c
4
- data.tar.gz: ec335dcad5f5a0b8cb70524ac3a619e5e70094c5
2
+ SHA256:
3
+ metadata.gz: eb3eee8a4c13ad41febe0e5f07271c0e9d8cab44cfc5540d898aa8802c89e334
4
+ data.tar.gz: 52c0524074d9ea5da705be0190b613c56e10b1f52f5bdd10bbac163a415ba2e5
5
5
  SHA512:
6
- metadata.gz: 27f8d7f35c2d73438f0faf47e1d57b8d866dfeefde4a9080c1618eb0dd623b205caed1e345782dae052ad2039f470c0ec1a7476bfea832e67429e9916f3f8749
7
- data.tar.gz: e0b5606867ac6a6618732c3ca2c05fb48822a68ee00de2728a2bf283d6514b963d53609a23ee43809adba88b5384c9ba62b26cc5bd740f0937ed6ccf2734b231
6
+ metadata.gz: 8b85df794d052257b15add0e7a298b6b3482f04eaa67db0d4fe8b944898fd8a9e00451a2f4f6b351f867a8e33db7694421b2a419102941e41b9543a0a5ba2048
7
+ data.tar.gz: b58bb5628274ac2609a6103e68ba0fe5b829705231ee4264126521703907b83e46da97b6ebc9a8698c5d3ff39c11685011fcd4aa6e3d287fdb5ead9e0553054d
@@ -0,0 +1,26 @@
1
+ version: '{build}'
2
+
3
+ skip_tags: true
4
+
5
+ environment:
6
+ matrix:
7
+ - ruby_version: "23"
8
+ - ruby_version: "23-x64"
9
+ - ruby_version: "24"
10
+ - ruby_version: "24-x64"
11
+ - ruby_version: "25"
12
+ - ruby_version: "25-x64"
13
+ - ruby_version: "26"
14
+ - ruby_version: "26-x64"
15
+ - ruby_version: "27"
16
+ - ruby_version: "27-x64"
17
+
18
+ install:
19
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
20
+ - gem install bundler --no-document -v 2.1.4
21
+ - bundle install --retry=3
22
+
23
+ test_script:
24
+ - bundle exec rspec
25
+
26
+ build: off
data/.gitignore CHANGED
@@ -1,2 +1,5 @@
1
1
  Gemfile.lock
2
- pkg
2
+ vendor/
3
+ .bundle/
4
+ *.gem
5
+ *~
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ os:
3
+ - linux
4
+ rvm:
5
+ - 2.3
6
+ - 2.4
7
+ - 2.5
8
+ - 2.6
9
+ - 2.7
10
+ bundler_args: --jobs=2
11
+ script:
12
+ - bundle exec rspec
13
+ cache: bundler
14
+
data/README.md CHANGED
@@ -10,14 +10,19 @@ Add this line to your application's Gemfile:
10
10
 
11
11
  And then execute:
12
12
 
13
- $ bundle
13
+ $ bundle install
14
14
 
15
15
  Or install it yourself as:
16
16
 
17
- $ gem install block_io -v=1.0.4
17
+ $ gem install block_io -v=2.0.0
18
18
 
19
19
  ## Changelog
20
20
 
21
+ *07/02/20*: BREAKING CHANGES. Version 2.0.0. Remove support for Ruby < 2.3.0. Behavior and interfaces have changed. By upgrading you'll need to revise your code and tests.
22
+ *05/10/19*: Prevent inadvertent passing of PINs (user error).
23
+ *06/25/18*: Remove support for Ruby < 1.9.3 (OpenSSL::Cipher::Cipher). Remove connection_pool dependency.
24
+ *01/21/15*: Added ability to sweep coins from one address to another.
25
+ *11/04/14*: Fix issue with nil parameters in an API call.
21
26
  *11/03/14*: Reduce dependence on OpenSSL. PBKDF2 function is now Ruby-based. Should work well with Heroku's libraries.
22
27
  *10/18/14*: Now using deterministic signatures (RFC6979), and BIP62 to hinder transaction malleability.
23
28
 
@@ -27,14 +32,19 @@ Or install it yourself as:
27
32
  It's super easy to get started. In your Ruby shell ($ irb), for example, do this:
28
33
 
29
34
  require 'block_io'
30
- BlockIo.set_options :api_key => 'API KEY', :pin => 'SECRET PIN', :version => 2
31
-
35
+ blockio = BlockIo::Client.new(:api_key => "API KEY", :pin => "SECRET PIN")
36
+
37
+ If you do not have your PIN, or just wish to use your private key backup directly, do this:
38
+
39
+ blockio = BlockIo::Client.new(:api_key => "API KEY", :keys => [BlockIo::Key.from_wif("PRIVATE KEY BACKUP")])
40
+
32
41
  And you're good to go:
33
42
 
34
- BlockIo.get_new_address
35
- BlockIo.get_my_addresses
43
+ blockio.get_new_address
44
+ blockio.get_my_addresses
36
45
 
37
- For more information, see https://block.io/api/simple/ruby
46
+ For other initialization options/parameters, see `lib/block_io/client.rb`.
47
+ For more information, see https://block.io/api/simple/ruby.
38
48
 
39
49
  ## Contributing
40
50
 
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'block_io/version'
@@ -16,13 +15,15 @@ Gem::Specification.new do |spec|
16
15
  spec.files = `git ls-files -z`.split("\x0")
17
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.required_ruby_version = '>= 2.3.0'
19
19
  spec.require_paths = ["lib"]
20
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.4", '>= 2.4.0'
25
- spec.add_runtime_dependency "json", "~> 1.8", '>= 1.8.1'
26
- spec.add_runtime_dependency "connection_pool", "~> 2.0", '>= 2.0.0'
27
- spec.add_runtime_dependency "pbkdf2-ruby", '~> 0.2', '>= 0.2.1'
21
+ spec.add_development_dependency "bundler", ">= 1.16", "< 3.0"
22
+ spec.add_development_dependency "rake", "~> 12.3", ">= 12.3.3"
23
+ spec.add_development_dependency "rspec", "~> 3.6", ">= 3.6"
24
+ spec.add_development_dependency "webmock", "~> 3.8", "< 4.0"
25
+ spec.add_runtime_dependency "ecdsa", "~> 1.2.0", ">= 1.2.0"
26
+ spec.add_runtime_dependency "http", "~> 4.4.1", ">= 4.4.1"
27
+ spec.add_runtime_dependency "oj", "~> 3.10.6", ">= 3.10"
28
+ spec.add_runtime_dependency "connection_pool", "~> 2.2.3", ">= 2.2"
28
29
  end
@@ -1,22 +1,26 @@
1
- # creates a new destination address, withdraws from the default label to it, gets sent transactions, and the current price
1
+ # creates a new destination address, withdraws from the default label to it, gets updated address balances, gets sent/received transactions, and the current price
2
+ #
3
+ # basic example: $ API_KEY=TESTNET_API_KEY PIN=YOUR_SECRET_PIN ruby basic.rb
4
+ # bundler example: $ API_KEY=TESTNET_API_KEY PIN=YOUR_SECRET_PIN bundle exec ruby basic.rb
5
+ #
6
+ # adjust amount below if not using the Dogecoin Testnet
2
7
 
3
8
  require 'block_io'
4
9
 
5
- # please use the Dogecoin Testnet API key here
6
- puts BlockIo.set_options :api_key => 'YOURDOGECOINTESTNETAPIKEY', :pin => 'YOURSECRETPIN', :version => 2
10
+ blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :pin => ENV['PIN'], :version => 2)
11
+ puts blockio.get_balance
12
+ puts blockio.network
7
13
 
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
+ # create the address if it doesn't exist
15
+ puts blockio.get_new_address(:label => 'testDest')
14
16
 
15
- puts BlockIo.withdraw_from_labels(:from_labels => 'default', :to_label => 'testDest', :amount => '3.5')
17
+ puts blockio.withdraw_from_labels(:from_labels => 'default', :to_label => 'testDest', :amount => '2.5')
16
18
 
17
- puts BlockIo.get_address_by_label(:label => 'default')
19
+ puts blockio.get_address_balance(:labels => 'default,testDest')
18
20
 
19
- puts BlockIo.get_transactions(:type => 'sent') # API v2 only
21
+ puts blockio.get_transactions(:type => 'sent')
20
22
 
21
- puts BlockIo.get_current_price(:base_price => 'BTC')
23
+ puts blockio.get_transactions(:type => 'received')
24
+
25
+ puts blockio.get_current_price(:base_price => 'BTC')
22
26
 
@@ -1,31 +1,47 @@
1
1
  # creates a new destination address, withdraws from the default label to it, gets sent transactions, and the current price
2
2
 
3
3
  require 'block_io'
4
+ require 'json'
4
5
 
5
- # please use the Dogecoin Testnet API key here
6
+ # please use the Litecoin Testnet API key here
6
7
  puts "*** Initialize BlockIo library: "
7
- puts JSON.pretty_generate(BlockIo.set_options :api_key => 'YourDogecoinTestnetAPIKey', :pin => 'YourSecretPIN', :version => 2)
8
+ blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :pin => ENV['PIN'], :version => 2)
9
+ puts blockio.get_dtrust_balance
10
+ puts blockio.network
8
11
 
12
+ raise "Please use the LTCTEST network API Key here or modify this script for another network." unless blockio.network == "LTCTEST"
9
13
 
10
14
  # 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') ]
15
+ # you will generate your own private keys, for instance: key = BlockIo::Key.new. Just note down key.public_key and key.private_key somewhere safe before you use your keys to generate dTrust addresses.
16
+ # if you already have hex private keys, load the keys with BlockIo::Key.new(private_key_hex). Ensure the key's .public_key matches what you expect.
17
+ # WARNING: The keys below are just for demonstration, DO NOT use them on mainnets, DO NOT use insecurely generated keys
18
+ keys = [
19
+ BlockIo::Key.new("b515fd806a662e061b488e78e5d0c2ff46df80083a79818e166300666385c0a2"), # alpha1alpha2alpha3alpha4
20
+ BlockIo::Key.new("1584b821c62ecdc554e185222591720d6fe651ed1b820d83f92cdc45c5e21f"), # alpha2alpha3alpha4alpha1
21
+ BlockIo::Key.new("2f9090b8aa4ddb32c3b0b8371db1b50e19084c720c30db1d6bb9fcd3a0f78e61"), # alpha3alpha4alpha1alpha2
22
+ BlockIo::Key.new("6c1cefdfd9187b36b36c3698c1362642083dcc1941dc76d751481d3aa29ca65") # alpha4alpha1alpha2alpha3
23
+ ].freeze
12
24
 
13
25
  dtrust_address = nil
26
+ dtrust_address_label = "dTrust1_witness_v0"
14
27
 
15
28
  begin
16
29
  # 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)
30
+ # you will need all 4 of your keys to use your address without interacting with Block.io
17
31
 
18
- signers = ""
19
- keys.each { |key| signers += ',' if signers.length > 0; signers += key.public_key; }
32
+ signers = keys.map{|k| k.public_key}.join(',')
20
33
 
21
- response = BlockIo.get_new_dtrust_address(:label => 'dTrust1', :public_keys => signers, :required_signatures => 3)
34
+ response = blockio.get_new_dtrust_address(:label => dtrust_address_label, :public_keys => signers, :required_signatures => 3, :address_type => "witness_v0")
22
35
 
23
36
  dtrust_address = response['data']['address']
37
+
38
+ raise response["data"]["error_message"] unless response["status"].eql?("success")
39
+
24
40
  rescue Exception => e
25
41
  # if this failed, we probably created the same label before. let's fetch the address then.
26
42
  puts e.to_s
27
43
 
28
- response = BlockIo.get_dtrust_address_by_label(:label => 'dTrust1')
44
+ response = blockio.get_dtrust_address_by_label(:label => dtrust_address_label)
29
45
 
30
46
  dtrust_address = response['data']['address']
31
47
  end
@@ -33,57 +49,40 @@ end
33
49
  puts "*** Our dTrust Address: #{dtrust_address}"
34
50
 
35
51
  # 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')
52
+ response = blockio.withdraw_from_labels(:from_labels => 'default', :to_address => dtrust_address, :amount => '0.001')
37
53
 
38
54
  puts "*** Withdrawal response:"
39
55
  puts JSON.pretty_generate(response)
40
56
 
41
57
 
42
58
  # fetch the dtrust address' balance
43
- puts "*** dTrust1 Balance:"
44
- puts JSON.pretty_generate(BlockIo.get_dtrust_address_balance(:label => 'dTrust1'))
59
+ puts "*** dtrust_address_label Balance:"
60
+ puts JSON.pretty_generate(blockio.get_dtrust_address_balance(:label => dtrust_address_label))
45
61
 
46
- # withdraw a few coins from dtrust1 to the default label
47
- normal_address = BlockIo.get_address_by_label(:label => 'default')['data']['address']
62
+ # withdraw a few coins from dtrust_address_label to the default label
63
+ normal_address = blockio.get_address_by_label(:label => 'default')['data']['address']
48
64
 
49
- puts "*** Withdrawing from dTrust1 to the 'default' label in normal multisig"
65
+ puts "*** Withdrawing from dtrust_address_label to the 'default' label in normal multisig"
50
66
 
51
- response = BlockIo.withdraw_from_dtrust_address(:from_labels => 'dTrust1', :to_addresses => normal_address, :amounts => '2.1')
67
+ response = blockio.withdraw_from_dtrust_address(:from_labels => dtrust_address_label, :to_addresses => normal_address, :amounts => '0.0009')
52
68
 
53
69
  puts JSON.pretty_generate(response)
54
70
 
55
71
  # let's sign for the public keys specified
72
+ signatures_added = BlockIo::Helper.signData(response["data"]["inputs"], keys)
56
73
 
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
74
+ puts "*** Signatures added? #{signatures_added}"
76
75
 
77
- puts "*** Our signed response: "
78
- puts JSON.pretty_generate(response['data']) #.to_json
76
+ puts "*** Our (signed) request:"
77
+ puts JSON.pretty_generate(response['data'])
79
78
 
80
79
  # let's final the withdrawal
81
80
  puts "*** Finalize withdrawal: "
82
- puts JSON.pretty_generate(BlockIo.sign_and_finalize_withdrawal(:signature_data => response['data'].to_json))
81
+ puts JSON.pretty_generate(blockio.sign_and_finalize_withdrawal({:signature_data => response["data"]}))
83
82
 
84
83
  # get the sent transactions for this dTrust address
85
84
 
86
- puts "*** Get transactions sent by our dTrust1 address: "
85
+ puts "*** Get transactions sent by our dtrust_address_label address: "
87
86
 
88
- puts JSON.pretty_generate(BlockIo.get_dtrust_transactions(:type => 'sent', :labels => 'dTrust1'))
87
+ puts JSON.pretty_generate(blockio.get_dtrust_transactions(:type => 'sent', :labels => dtrust_address_label))
89
88
 
@@ -0,0 +1,29 @@
1
+ # withdraws maximum balance to a given destination in a single transaction
2
+ # you will need to repeat this if a single transaction does not suffice for withdrawing the entire balance
3
+ # for error handling, please check response['data']['status'] != 'success'
4
+
5
+ require 'block_io'
6
+
7
+ blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :pin => ENV['PIN'], :version => 2)
8
+ puts blockio.get_balance
9
+ puts blockio.network
10
+
11
+ TO_ADDRESS = ENV['TO_ADDRESS']
12
+
13
+ raise "must specify a TO_ADDRESS" unless TO_ADDRESS.to_s.size > 0
14
+
15
+ total_balance = blockio.get_balance['data']['available_balance']
16
+
17
+ puts " -- total balance: #{total_balance} #{blockio.network}"
18
+
19
+ while true do
20
+ response = blockio.withdraw(:to_address => TO_ADDRESS, :amount => total_balance)
21
+ maximum_withdrawable_balance = response['data']['max_withdrawal_available']
22
+ break if BigDecimal(maximum_withdrawable_balance).zero?
23
+ puts blockio.withdraw(:to_address => TO_ADDRESS, :amount => maximum_withdrawable_balance)
24
+ end
25
+
26
+ final_balance = blockio.get_balance['data']['available_balance']
27
+
28
+ puts " -- final balance: #{final_balance} #{blockio.network}"
29
+
@@ -0,0 +1,36 @@
1
+ # creates a new destination address, withdraws from the default label to it,
2
+ # gets updated address balances, gets sent/received transactions, and the
3
+ # current price
4
+ # ... through an https proxy (like Squid)
5
+ #
6
+ # Unauthenticated:
7
+ # $ PROXY_HOST=localhost PROXY_PORT=3128 API_KEY=TESTNET_API_KEY PIN=YOUR_SECRET_PIN ruby proxy.rb
8
+ #
9
+ # Authenticated:
10
+ # $ PROXY_HOST=localhost PROXY_PORT=3128 PROXY_USER=user PROXY_PASS=pass API_KEY=TESTNET_API_KEY PIN=YOUR_SECRET_PIN ruby proxy.rb
11
+ #
12
+ # adjust amount below if not using the Dogecoin Testnet
13
+
14
+ require '../lib/block_io'
15
+
16
+ blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :pin => ENV['PIN'], :version => 2, :proxy => {
17
+ :hostname => ENV['PROXY_HOST'],
18
+ :port => ENV['PROXY_PORT'],
19
+ :username => ENV['PROXY_USER'],
20
+ :password => ENV['PROXY_PASS']
21
+ })
22
+ puts blockio.get_balance
23
+ puts blockio.network
24
+
25
+ # create the address if it doesn't exist
26
+ puts blockio.get_new_address(:label => 'testDest')
27
+
28
+ puts blockio.withdraw_from_labels(:from_labels => 'default', :to_label => 'testDest', :amount => '2.5')
29
+
30
+ puts blockio.get_address_balance(:labels => 'default,testDest')
31
+
32
+ puts blockio.get_transactions(:type => 'sent')
33
+
34
+ puts blockio.get_transactions(:type => 'received')
35
+
36
+ puts blockio.get_current_price(:base_price => 'BTC')
@@ -0,0 +1,29 @@
1
+ # Example script for sweeping all coins from a given address
2
+ # Must use the API Key for the Network the address belongs to
3
+ # Must also provide the Private Key to the sweep address in Wallet Import Format (WIF)
4
+ #
5
+ # Contact support@block.io if you have any issues
6
+
7
+ require "block_io"
8
+
9
+ blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :version => 2)
10
+ puts blockio.get_balance
11
+ puts blockio.network
12
+
13
+ to_address = ENV['TO_ADDRESS'] # sweep coins into this address
14
+
15
+ from_address = ENV['FROM_ADDRESS'] # sweep coins from this address
16
+ private_key = ENV['PRIVATE_KEY'] # private key for from_address
17
+
18
+ begin
19
+ response = blockio.sweep_from_address(:to_address => to_address, :private_key => private_key, :from_address => from_address)
20
+
21
+ raise response["data"]["error_message"] unless response["status"].eql?("success")
22
+
23
+ puts "Sweep Complete: #{response['data']['amount_sent']} #{response['data']['network']} swept from #{from_address} to #{to_address}."
24
+ puts "Transaction ID: #{response['data']['txid']}"
25
+ puts "Network Fee Incurred: #{response['data']['network_fee']} #{response['data']['network']}"
26
+
27
+ rescue Exception => e
28
+ puts "Sweep failed: #{e}"
29
+ end
@@ -1,309 +1,22 @@
1
- require 'block_io/version'
2
- require 'httpclient'
3
- require 'json'
4
- require 'connection_pool'
5
- require 'ecdsa'
6
- require 'openssl'
7
- require 'digest'
8
- require 'pbkdf2'
9
- require 'securerandom'
10
- require 'base64'
1
+ require "http"
2
+ require "oj"
3
+ require "ecdsa"
4
+ require "openssl"
5
+ require "securerandom"
6
+ require "connection_pool"
7
+
8
+ require_relative "block_io/version"
9
+ require_relative "block_io/constants"
10
+ require_relative "block_io/helper"
11
+ require_relative "block_io/key"
12
+ require_relative "block_io/client"
11
13
 
12
14
  module BlockIo
13
15
 
14
- @api_key = nil
15
- @base_url = "https://block.io/api/VERSION/API_CALL/?api_key="
16
- @pin = nil
17
- @encryptionKey = 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
- @encryptionKey = Helper.pinToAesKey(@pin) if !@pin.nil?
26
-
27
- @conn_pool = ConnectionPool.new(size: 5, timeout: 300) { HTTPClient.new }
28
-
29
- @version = args[:version] || 2 # default version is 2
30
-
31
- self.api_call(['get_balance',""])
32
- end
33
-
34
- def self.method_missing(m, *args, &block)
35
-
36
- method_name = m.to_s
37
-
38
- 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
39
-
40
- self.withdraw(args.first, m.to_s)
41
-
42
- else
43
- params = get_params(args.first)
44
- self.api_call([method_name, params])
45
- end
46
-
47
- end
48
-
49
- def self.withdraw(args = {}, method_name = 'withdraw')
50
- # validate arguments for withdrawal of funds TODO
51
-
52
- raise Exception.new("PIN not set. Use BlockIo.set_options(:api_key=>'API KEY',:pin=>'SECRET PIN',:version=>'API VERSION')") if @pin.nil?
53
-
54
- params = get_params(args)
55
-
56
- params += "&pin=#{@pin}" if @version == 1 # Block.io handles the Secret PIN in the legacy API (v1)
57
-
58
- response = self.api_call([method_name, params])
59
-
60
- if response['data'].has_key?('reference_id') then
61
- # Block.io's asking us to provide some client-side signatures, let's get to it
62
-
63
- # extract the passphrase
64
- encrypted_passphrase = response['data']['encrypted_passphrase']['passphrase']
65
-
66
- # let's get our private key
67
- key = Helper.extractKey(encrypted_passphrase, @encryptionKey)
68
-
69
- 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']
70
-
71
- # let's sign all the inputs we can
72
- inputs = response['data']['inputs']
73
-
74
- inputs.each do |input|
75
- # iterate over all signers
76
-
77
- input['signers'].each do |signer|
78
- # if our public key matches this signer's public key, sign the data
79
-
80
- signer['signed_data'] = key.sign(input['data_to_sign']) if signer['signer_public_key'] == key.public_key
81
-
82
- end
83
-
84
- end
85
-
86
- # the response object is now signed, let's stringify it and finalize this withdrawal
87
-
88
- response = self.api_call(['sign_and_finalize_withdrawal',{:signature_data => response['data'].to_json}])
89
-
90
- # if we provided all the required signatures, this transaction went through
91
- # otherwise Block.io responded with data asking for more signatures
92
- # the latter will be the case for dTrust addresses
93
- end
94
-
95
- return response
96
-
97
- end
98
-
99
-
100
- private
101
-
102
- def self.api_call(endpoint)
103
-
104
- body = nil
105
-
106
- @conn_pool.with do |hc|
107
- # prevent initiation of HTTPClients every time we make this call, use a connection_pool
108
-
109
- hc.ssl_config.ssl_version = :TLSv1
110
- response = hc.post("#{@base_url.gsub('API_CALL',endpoint[0]).gsub('VERSION', 'v'+@version.to_s) + @api_key}", endpoint[1])
111
-
112
- begin
113
- body = JSON.parse(response.body)
114
- raise Exception.new(body['data']['error_message']) if !body['status'].eql?('success')
115
- rescue
116
- raise Exception.new('Unknown error occurred. Please report this.')
117
- end
118
- end
119
-
120
- body
121
- end
122
-
123
- private
124
-
125
- def self.get_params(args = {})
126
- # construct the parameter string
127
- params = ""
128
- args = {} if args.nil?
129
-
130
- args.each do |k,v|
131
- params += '&' if params.length > 0
132
- params += "#{k.to_s}=#{v.to_s}"
133
- end
134
-
135
- return params
136
- end
137
-
138
- public
139
-
140
- class Key
141
-
142
- def initialize(privkey = nil)
143
- # the privkey must be in hex if at all provided
144
-
145
- @group = ECDSA::Group::Secp256k1
146
- @private_key = privkey.to_i(16) || 1 + SecureRandom.random_number(group.order - 1)
147
- @public_key = @group.generator.multiply_by_scalar(@private_key)
148
-
149
- end
150
-
151
- def private_key
152
- # returns private key in hex form
153
- return @private_key.to_s(16)
154
- end
155
-
156
- def public_key
157
- # returns the compressed form of the public key to save network fees (shorter scripts)
158
-
159
- return ECDSA::Format::PointOctetString.encode(@public_key, compression: true).unpack("H*")[0]
160
- end
161
-
162
- def sign(data)
163
- # signed the given hexadecimal string
164
-
165
- nonce = deterministicGenerateK([data].pack("H*"), @private_key) # RFC6979
166
-
167
- signature = ECDSA.sign(@group, @private_key, data.to_i(16), nonce)
168
-
169
- # BIP0062 -- use lower S values only
170
- r, s = signature.components
171
-
172
- over_two = @group.order >> 1 # half of what it was
173
- s = @group.order - s if (s > over_two)
174
-
175
- signature = ECDSA::Signature.new(r, s)
176
-
177
- # DER encode this, and return it in hex form
178
- return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*")[0]
179
- end
180
-
181
- def self.from_passphrase(passphrase)
182
- # create a private+public key pair from a given passphrase
183
- # think of this as your brain wallet. be very sure to use a sufficiently long passphrase
184
- # if you don't want a passphrase, just use Key.new and it will generate a random key for you
185
-
186
- raise Exception.new('Must provide passphrase at least 8 characters long.') if passphrase.nil? or passphrase.length < 8
187
-
188
- hashed_key = Helper.sha256([passphrase].pack("H*")) # must pass bytes to sha256
189
-
190
- return Key.new(hashed_key)
191
- end
192
-
193
- def isPositive(i)
194
- sig = "!+-"[i <=> 0]
195
-
196
- return sig.eql?("+")
197
- end
198
-
199
- def deterministicGenerateK(data, privkey, group = ECDSA::Group::Secp256k1)
200
- # returns a deterministic K -- RFC6979
201
-
202
- hash = data.bytes.to_a
203
-
204
- x = [privkey.to_s(16)].pack("H*").bytes.to_a
205
-
206
- k = []
207
- 32.times { k.insert(0, 0) }
208
-
209
- v = []
210
- 32.times { v.insert(0, 1) }
211
-
212
- # step D
213
- k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).concat(x).concat(hash).pack("C*")).bytes.to_a
214
-
215
- # step E
216
- v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
217
-
218
- # puts "E: " + v.pack("C*").unpack("H*")[0]
219
-
220
- # step F
221
- k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([1]).concat(x).concat(hash).pack("C*")).bytes.to_a
222
-
223
- # step G
224
- v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
225
-
226
- # step H2b (Step H1/H2a ignored)
227
- v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
228
-
229
- h2b = v.pack("C*").unpack("H*")[0]
230
- tNum = h2b.to_i(16)
231
-
232
- # step H3
233
- while (!isPositive(tNum) or tNum >= group.order) do
234
- # k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k)
235
- k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).pack("C*")).bytes.to_a
236
-
237
- # v = crypto.HmacSHA256(v, k)
238
- v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
239
-
240
- # T = BigInteger.fromBuffer(v)
241
- tNum = v.pack("C*").unpack("H*")[0].to_i(16)
242
- end
243
-
244
- return tNum
245
- end
246
-
16
+ def self.version
17
+ BlockIo::VERSION
247
18
  end
248
19
 
249
- module Helper
250
-
251
- def self.extractKey(encrypted_data, b64_enc_key)
252
- # passphrase is in plain text
253
- # encrypted_data is in base64, as it was stored on Block.io
254
- # returns the private key extracted from the given encrypted data
255
-
256
- decrypted = self.decrypt(encrypted_data, b64_enc_key)
257
-
258
- return Key.from_passphrase(decrypted)
259
- end
260
-
261
- def self.sha256(value)
262
- # returns the hex of the hash of the given value
263
- hash = Digest::SHA2.new(256)
264
- hash << value
265
- hash.hexdigest # return hex
266
- end
267
-
268
- def self.pinToAesKey(secret_pin, iterations = 2048)
269
- # converts the pincode string to PBKDF2
270
- # returns a base64 version of PBKDF2 pincode
271
- salt = ""
272
-
273
- # pbkdf2-ruby gem uses SHA256 as the default hash function
274
- aes_key_bin = PBKDF2.new(:password => secret_pin, :salt => salt, :iterations => iterations/2, :key_length => 128/8).value
275
- aes_key_bin = PBKDF2.new(:password => aes_key_bin.unpack("H*")[0], :salt => salt, :iterations => iterations/2, :key_length => 256/8).value
276
-
277
- return Base64.strict_encode64(aes_key_bin) # the base64 encryption key
278
- end
279
-
280
- # Decrypts a block of data (encrypted_data) given an encryption key
281
- def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
282
-
283
- response = nil
284
-
285
- begin
286
- aes = OpenSSL::Cipher::Cipher.new(cipher_type)
287
- aes.decrypt
288
- aes.key = Base64.strict_decode64(b64_enc_key)
289
- aes.iv = iv if iv != nil
290
- response = aes.update(Base64.strict_decode64(encrypted_data)) + aes.final
291
- rescue Exception => e
292
- # decryption failed, must be an invalid Secret PIN
293
- raise Exception.new('Invalid Secret PIN provided.')
294
- end
20
+ end
295
21
 
296
- return response
297
- end
298
-
299
- # Encrypts a block of data given an encryption key
300
- def self.encrypt(data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
301
- aes = OpenSSL::Cipher::Cipher.new(cipher_type)
302
- aes.encrypt
303
- aes.key = Base64.strict_decode64(b64_enc_key)
304
- aes.iv = iv if iv != nil
305
- Base64.strict_encode64(aes.update(data) + aes.final)
306
- end
307
- end
308
22
 
309
- end