block_io 1.0.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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