block_io 1.2.1 → 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
2
  SHA256:
3
- metadata.gz: 64a968ba0f92fcd2d78097633d45b3c5af847bb4d485d1edad901dbbfff959d6
4
- data.tar.gz: 8924f4b86c12833f80bda1ef5f91be065d974f37556a4909703d46b635db0071
3
+ metadata.gz: eb3eee8a4c13ad41febe0e5f07271c0e9d8cab44cfc5540d898aa8802c89e334
4
+ data.tar.gz: 52c0524074d9ea5da705be0190b613c56e10b1f52f5bdd10bbac163a415ba2e5
5
5
  SHA512:
6
- metadata.gz: 658d6559024e0bfa246fee235c61327111fd624df4d0503ec1522df6d8cd029fb8e155885f6b9555efe5fd823d2f9d7bdf8847e4b09579057ae8c2c4bcc690ae
7
- data.tar.gz: ed86538d0e8811b768271b20c1dc370c9d00f9cdbce18fd75a9582e6a80e2a2562b6851c8f74e422b39aac3550ce6d8350635b9b9c7edc2915393e7226f5b3cd
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,5 +1,5 @@
1
1
  Gemfile.lock
2
- pkg
3
2
  vendor/
4
3
  .bundle/
5
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,15 @@ 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.2.1
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.
21
22
  *05/10/19*: Prevent inadvertent passing of PINs (user error).
22
23
  *06/25/18*: Remove support for Ruby < 1.9.3 (OpenSSL::Cipher::Cipher). Remove connection_pool dependency.
23
24
  *01/21/15*: Added ability to sweep coins from one address to another.
@@ -31,14 +32,19 @@ Or install it yourself as:
31
32
  It's super easy to get started. In your Ruby shell ($ irb), for example, do this:
32
33
 
33
34
  require 'block_io'
34
- BlockIo.set_options :api_key => 'API KEY', :pin => 'SECRET PIN', :version => 2
35
-
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
+
36
41
  And you're good to go:
37
42
 
38
- BlockIo.get_new_address
39
- BlockIo.get_my_addresses
43
+ blockio.get_new_address
44
+ blockio.get_my_addresses
40
45
 
41
- 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.
42
48
 
43
49
  ## Contributing
44
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,12 +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.8", '>= 2.8.0'
25
- spec.add_runtime_dependency "oj", "~> 3.3", '>= 3.3.5'
26
- spec.add_runtime_dependency "pbkdf2-ruby", '~> 0.2', '>= 0.2.1'
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"
27
29
  end
@@ -7,22 +7,20 @@
7
7
 
8
8
  require 'block_io'
9
9
 
10
- puts BlockIo.set_options :api_key => ENV['API_KEY'], :pin => ENV['PIN'], :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
11
13
 
12
- begin
13
- puts BlockIo.get_new_address(:label => 'testDest')
14
- rescue Exception => e
15
- # if this failed, we probably created testDest label before
16
- puts e.to_s
17
- end
14
+ # create the address if it doesn't exist
15
+ puts blockio.get_new_address(:label => 'testDest')
18
16
 
19
- puts BlockIo.withdraw_from_labels(:from_labels => 'default', :to_label => 'testDest', :amount => '2.5')
17
+ puts blockio.withdraw_from_labels(:from_labels => 'default', :to_label => 'testDest', :amount => '2.5')
20
18
 
21
- puts BlockIo.get_address_balance(:labels => 'default,testDest')
19
+ puts blockio.get_address_balance(:labels => 'default,testDest')
22
20
 
23
- puts BlockIo.get_transactions(:type => 'sent') # API v2 only
21
+ puts blockio.get_transactions(:type => 'sent')
24
22
 
25
- puts BlockIo.get_transactions(:type => 'received') # API v2 only
23
+ puts blockio.get_transactions(:type => 'received')
26
24
 
27
- puts BlockIo.get_current_price(:base_price => 'BTC')
25
+ puts blockio.get_current_price(:base_price => 'BTC')
28
26
 
@@ -3,30 +3,45 @@
3
3
  require 'block_io'
4
4
  require 'json'
5
5
 
6
- # please use the Dogecoin Testnet API key here
6
+ # please use the Litecoin Testnet API key here
7
7
  puts "*** Initialize BlockIo library: "
8
- 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
9
11
 
12
+ raise "Please use the LTCTEST network API Key here or modify this script for another network." unless blockio.network == "LTCTEST"
10
13
 
11
14
  # create 4 keys
12
- 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
13
24
 
14
25
  dtrust_address = nil
26
+ dtrust_address_label = "dTrust1_witness_v0"
15
27
 
16
28
  begin
17
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
18
31
 
19
- signers = ""
20
- keys.each { |key| signers += ',' if signers.length > 0; signers += key.public_key; }
32
+ signers = keys.map{|k| k.public_key}.join(',')
21
33
 
22
- 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")
23
35
 
24
36
  dtrust_address = response['data']['address']
37
+
38
+ raise response["data"]["error_message"] unless response["status"].eql?("success")
39
+
25
40
  rescue Exception => e
26
41
  # if this failed, we probably created the same label before. let's fetch the address then.
27
42
  puts e.to_s
28
43
 
29
- response = BlockIo.get_dtrust_address_by_label(:label => 'dTrust1')
44
+ response = blockio.get_dtrust_address_by_label(:label => dtrust_address_label)
30
45
 
31
46
  dtrust_address = response['data']['address']
32
47
  end
@@ -34,57 +49,40 @@ end
34
49
  puts "*** Our dTrust Address: #{dtrust_address}"
35
50
 
36
51
  # let's deposit some coins into this new address
37
- 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')
38
53
 
39
54
  puts "*** Withdrawal response:"
40
55
  puts JSON.pretty_generate(response)
41
56
 
42
57
 
43
58
  # fetch the dtrust address' balance
44
- puts "*** dTrust1 Balance:"
45
- 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))
46
61
 
47
- # withdraw a few coins from dtrust1 to the default label
48
- 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']
49
64
 
50
- 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"
51
66
 
52
- 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')
53
68
 
54
69
  puts JSON.pretty_generate(response)
55
70
 
56
71
  # let's sign for the public keys specified
72
+ signatures_added = BlockIo::Helper.signData(response["data"]["inputs"], keys)
57
73
 
58
- response['data']['inputs'].each do |input|
59
- # for each input
60
-
61
- data_to_sign = input['data_to_sign']
62
-
63
- input['signers'].each do |signer|
64
-
65
- # figure out if we have the public key that matches this signer
66
-
67
- keys.each do |key|
68
- # iterate over all keys till we've found the one that we need
69
-
70
- signer['signed_data'] = key.sign(data_to_sign) if key.public_key == signer['signer_public_key']
71
-
72
- end
73
-
74
- end
75
-
76
- end
74
+ puts "*** Signatures added? #{signatures_added}"
77
75
 
78
- puts "*** Our signed response: "
79
- puts JSON.pretty_generate(response['data']) #.to_json
76
+ puts "*** Our (signed) request:"
77
+ puts JSON.pretty_generate(response['data'])
80
78
 
81
79
  # let's final the withdrawal
82
80
  puts "*** Finalize withdrawal: "
83
- 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"]}))
84
82
 
85
83
  # get the sent transactions for this dTrust address
86
84
 
87
- puts "*** Get transactions sent by our dTrust1 address: "
85
+ puts "*** Get transactions sent by our dtrust_address_label address: "
88
86
 
89
- 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))
90
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')
@@ -6,19 +6,24 @@
6
6
 
7
7
  require "block_io"
8
8
 
9
- BlockIo.set_options :api_key => 'YOUR API KEY', :pin => 'PIN NOT NEEDED', :version => 2
9
+ blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :version => 2)
10
+ puts blockio.get_balance
11
+ puts blockio.network
10
12
 
11
- to_address = 'SWEEP COINS TO THIS ADDRESS'
13
+ to_address = ENV['TO_ADDRESS'] # sweep coins into this address
12
14
 
13
- from_address = 'SWEEP COINS FROM THIS ADDRESS'
14
- private_key = 'PRIVATE KEY FOR FROM_ADDRESS'
15
+ from_address = ENV['FROM_ADDRESS'] # sweep coins from this address
16
+ private_key = ENV['PRIVATE_KEY'] # private key for from_address
15
17
 
16
18
  begin
17
- response = BlockIo.sweep_from_address(:to_address => to_address, :private_key => private_key, :from_address => from_address)
18
-
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
+
19
23
  puts "Sweep Complete: #{response['data']['amount_sent']} #{response['data']['network']} swept from #{from_address} to #{to_address}."
20
24
  puts "Transaction ID: #{response['data']['txid']}"
21
25
  puts "Network Fee Incurred: #{response['data']['network_fee']} #{response['data']['network']}"
26
+
22
27
  rescue Exception => e
23
28
  puts "Sweep failed: #{e}"
24
29
  end
@@ -1,418 +1,22 @@
1
- require 'block_io/version'
2
- require 'httpclient'
3
- require 'oj'
4
- require 'ecdsa'
5
- require 'openssl'
6
- require 'digest'
7
- require 'pbkdf2'
8
- require 'securerandom'
9
- require 'base64'
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"
10
13
 
11
14
  module BlockIo
12
15
 
13
- @api_key = nil
14
- @base_url = nil
15
- @pin = nil
16
- @encryptionKey = nil
17
- @client = nil
18
- @conn_pool = nil
19
- @version = nil
20
-
21
- def self.set_options(args = {})
22
- # initialize BlockIo
23
- @api_key = args[:api_key]
24
- @pin = args[:pin]
25
-
26
- @encryptionKey = Helper.pinToAesKey(@pin) if !@pin.nil?
27
-
28
- hostname = args[:hostname] || "block.io"
29
- @base_url = "https://" << hostname << "/api/VERSION/API_CALL/?api_key="
30
-
31
- @client = HTTPClient.new
32
- @client.tcp_keepalive = true
33
- @client.ssl_config.ssl_version = :auto
34
-
35
- @version = args[:version] || 2 # default version is 2
36
-
37
- self.api_call(['get_balance',""])
38
- end
39
-
40
- def self.method_missing(m, *args, &block)
41
-
42
- method_name = m.to_s
43
-
44
- if ['withdraw', 'withdraw_from_address', 'withdraw_from_addresses', 'withdraw_from_user', 'withdraw_from_users', 'withdraw_from_label', 'withdraw_from_labels'].include?(m.to_s) then
45
- # need to withdraw from an address
46
- self.withdraw(args.first, m.to_s)
47
- elsif ['sweep_from_address'].include?(m.to_s) then
48
- # need to sweep from an address
49
- self.sweep(args.first, m.to_s)
50
- else
51
- params = get_params(args.first)
52
- self.api_call([method_name, params])
53
- end
54
-
55
- end
56
-
57
- def self.withdraw(args = {}, method_name = 'withdraw')
58
- # validate arguments for withdrawal of funds TODO
59
-
60
- raise Exception.new("PIN not set. Use BlockIo.set_options(:api_key=>'API KEY',:pin=>'SECRET PIN',:version=>'API VERSION')") if @pin.nil?
61
-
62
- # make sure pins don't get passed inadvertently
63
- args.delete(:pin)
64
- args.delete('pin')
65
-
66
- params = get_params(args)
67
-
68
- response = self.api_call([method_name, params])
69
-
70
- if response['data'].has_key?('reference_id') then
71
- # Block.io's asking us to provide some client-side signatures, let's get to it
72
-
73
- # extract the passphrase
74
- encrypted_passphrase = response['data']['encrypted_passphrase']['passphrase']
75
-
76
- # let's get our private key
77
- key = Helper.extractKey(encrypted_passphrase, @encryptionKey)
78
-
79
- 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']
80
-
81
- # let's sign all the inputs we can
82
- inputs = response['data']['inputs']
83
-
84
- Helper.signData(inputs, [key])
85
-
86
- # the response object is now signed, let's stringify it and finalize this withdrawal
87
- response = self.api_call(['sign_and_finalize_withdrawal',{:signature_data => Oj.dump(response['data'])}])
88
-
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
93
-
94
- return response
95
-
16
+ def self.version
17
+ BlockIo::VERSION
96
18
  end
97
-
98
- def self.sweep(args = {}, method_name = 'sweep_from_address')
99
- # sweep coins from a given address + key
100
-
101
- raise Exception.new("No private_key provided.") unless args.has_key?(:private_key)
102
-
103
- key = Key.from_wif(args[:private_key])
104
-
105
- args[:public_key] = key.public_key # so Block.io can match things up
106
- args.delete(:private_key) # the key must never leave this machine
107
-
108
- params = get_params(args)
109
-
110
- response = self.api_call([method_name, params])
111
-
112
- if response['data'].has_key?('reference_id') then
113
- # Block.io's asking us to provide some client-side signatures, let's get to it
114
-
115
- # let's sign all the inputs we can
116
- inputs = response['data']['inputs']
117
- Helper.signData(inputs, [key])
118
-
119
- # the response object is now signed, let's stringify it and finalize this withdrawal
120
- response = self.api_call(['sign_and_finalize_sweep',{:signature_data => Oj.dump(response['data'])}])
121
-
122
- # if we provided all the required signatures, this transaction went through
123
- # otherwise Block.io responded with data asking for more signatures
124
- # the latter will be the case for dTrust addresses
125
- end
126
-
127
- return response
128
-
129
- end
130
-
131
-
132
- private
133
19
 
134
- def self.api_call(endpoint)
135
-
136
- body = nil
137
-
138
- response = @client.post("#{@base_url.gsub('API_CALL',endpoint[0]).gsub('VERSION', 'v'+@version.to_s) + @api_key}", endpoint[1])
139
-
140
- begin
141
- body = Oj.load(response.body)
142
- raise Exception.new(body['data']['error_message']) if !body['status'].eql?('success')
143
- rescue
144
- raise Exception.new('Unknown error occurred. Please report this.')
145
- end
146
-
147
- body
148
- end
149
-
150
- private
151
-
152
- def self.get_params(args = {})
153
- # construct the parameter string
154
- params = ""
155
- args = {} if args.nil?
156
-
157
- args.each do |k,v|
158
- params += '&' if params.length > 0
159
- params += "#{k.to_s}=#{v.to_s}"
160
- end
161
-
162
- return params
163
- end
164
-
165
- public
166
-
167
- class Key
168
-
169
- def initialize(privkey = nil, compressed = true)
170
- # the privkey must be in hex if at all provided
171
-
172
- @group = ECDSA::Group::Secp256k1
173
- @private_key = privkey.to_i(16) || 1 + SecureRandom.random_number(group.order - 1)
174
- @public_key = @group.generator.multiply_by_scalar(@private_key)
175
- @compressed = compressed
176
-
177
- end
178
-
179
- def private_key
180
- # returns private key in hex form
181
- return @private_key.to_s(16)
182
- end
183
-
184
- def public_key
185
- # returns the compressed form of the public key to save network fees (shorter scripts)
186
-
187
- return ECDSA::Format::PointOctetString.encode(@public_key, compression: @compressed).unpack("H*")[0]
188
- end
189
-
190
- def sign(data)
191
- # signed the given hexadecimal string
192
-
193
- nonce = deterministicGenerateK([data].pack("H*"), @private_key) # RFC6979
194
-
195
- signature = ECDSA.sign(@group, @private_key, data.to_i(16), nonce)
196
-
197
- # BIP0062 -- use lower S values only
198
- r, s = signature.components
199
-
200
- over_two = @group.order >> 1 # half of what it was
201
- s = @group.order - s if (s > over_two)
202
-
203
- signature = ECDSA::Signature.new(r, s)
204
-
205
- # DER encode this, and return it in hex form
206
- return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*")[0]
207
- end
208
-
209
- def self.from_passphrase(passphrase)
210
- # create a private+public key pair from a given passphrase
211
- # think of this as your brain wallet. be very sure to use a sufficiently long passphrase
212
- # if you don't want a passphrase, just use Key.new and it will generate a random key for you
213
-
214
- raise Exception.new('Must provide passphrase at least 8 characters long.') if passphrase.nil? or passphrase.length < 8
215
-
216
- hashed_key = Helper.sha256([passphrase].pack("H*")) # must pass bytes to sha256
217
-
218
- return Key.new(hashed_key)
219
- end
220
-
221
- def self.from_wif(wif)
222
- # returns a new key extracted from the Wallet Import Format provided
223
- # TODO check against checksum
224
-
225
- hexkey = Helper.decode_base58(wif)
226
- actual_key = hexkey[2...66]
227
-
228
- compressed = hexkey[2..hexkey.length].length-8 > 64 and hexkey[2..hexkey.length][64...66] == '01'
229
-
230
- return Key.new(actual_key, compressed)
231
-
232
- end
233
-
234
- def isPositive(i)
235
- sig = "!+-"[i <=> 0]
236
-
237
- return sig.eql?("+")
238
- end
239
-
240
- def deterministicGenerateK(data, privkey, group = ECDSA::Group::Secp256k1)
241
- # returns a deterministic K -- RFC6979
242
-
243
- hash = data.bytes.to_a
244
-
245
- x = [privkey.to_s(16)].pack("H*").bytes.to_a
246
-
247
- k = []
248
- 32.times { k.insert(0, 0) }
249
-
250
- v = []
251
- 32.times { v.insert(0, 1) }
252
-
253
- # step D
254
- k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).concat(x).concat(hash).pack("C*")).bytes.to_a
255
-
256
- # step E
257
- v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
258
-
259
- # puts "E: " + v.pack("C*").unpack("H*")[0]
260
-
261
- # step F
262
- k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([1]).concat(x).concat(hash).pack("C*")).bytes.to_a
263
-
264
- # step G
265
- v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
266
-
267
- # step H2b (Step H1/H2a ignored)
268
- v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
269
-
270
- h2b = v.pack("C*").unpack("H*")[0]
271
- tNum = h2b.to_i(16)
272
-
273
- # step H3
274
- while (!isPositive(tNum) or tNum >= group.order) do
275
- # k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k)
276
- k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).pack("C*")).bytes.to_a
277
-
278
- # v = crypto.HmacSHA256(v, k)
279
- v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
280
-
281
- # T = BigInteger.fromBuffer(v)
282
- tNum = v.pack("C*").unpack("H*")[0].to_i(16)
283
- end
284
-
285
- return tNum
286
- end
287
-
288
- end
289
-
290
- module Helper
291
-
292
- def self.signData(inputs, keys)
293
- # sign the given data with the given keys
294
- # TODO loop is O(n^3), make it better
295
-
296
- raise Exception.new('Keys object must be an array of keys, without at least one key inside it.') unless keys.is_a?(Array) and keys.size >= 1
297
-
298
- i = 0
299
- while i < inputs.size do
300
- # iterate over all signers
301
- input = inputs[i]
302
-
303
- j = 0
304
- while j < input['signers'].size do
305
- # if our public key matches this signer's public key, sign the data
306
- signer = inputs[i]['signers'][j]
307
-
308
- k = 0
309
- while k < keys.size do
310
- # sign for each key provided, if we can
311
- key = keys[k]
312
- signer['signed_data'] = key.sign(input['data_to_sign']) if signer['signer_public_key'] == key.public_key
313
- k = k + 1
314
- end
315
-
316
- j = j + 1
317
- end
318
-
319
- i = i + 1
320
- end
321
-
322
- inputs
323
- end
324
-
325
- def self.extractKey(encrypted_data, b64_enc_key)
326
- # passphrase is in plain text
327
- # encrypted_data is in base64, as it was stored on Block.io
328
- # returns the private key extracted from the given encrypted data
329
-
330
- decrypted = self.decrypt(encrypted_data, b64_enc_key)
331
-
332
- return Key.from_passphrase(decrypted)
333
- end
334
-
335
- def self.sha256(value)
336
- # returns the hex of the hash of the given value
337
- hash = Digest::SHA2.new(256)
338
- hash << value
339
- hash.hexdigest # return hex
340
- end
341
-
342
- def self.pinToAesKey(secret_pin, iterations = 2048)
343
- # converts the pincode string to PBKDF2
344
- # returns a base64 version of PBKDF2 pincode
345
- salt = ""
346
-
347
- # pbkdf2-ruby gem uses SHA256 as the default hash function
348
- aes_key_bin = PBKDF2.new(:password => secret_pin, :salt => salt, :iterations => iterations/2, :key_length => 128/8).value
349
- aes_key_bin = PBKDF2.new(:password => aes_key_bin.unpack("H*")[0], :salt => salt, :iterations => iterations/2, :key_length => 256/8).value
350
-
351
- return Base64.strict_encode64(aes_key_bin) # the base64 encryption key
352
- end
353
-
354
- # Decrypts a block of data (encrypted_data) given an encryption key
355
- def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
356
-
357
- response = nil
358
-
359
- begin
360
- aes = OpenSSL::Cipher.new(cipher_type)
361
- aes.decrypt
362
- aes.key = Base64.strict_decode64(b64_enc_key)
363
- aes.iv = iv if iv != nil
364
- response = aes.update(Base64.strict_decode64(encrypted_data)) + aes.final
365
- rescue Exception => e
366
- # decryption failed, must be an invalid Secret PIN
367
- raise Exception.new('Invalid Secret PIN provided.')
368
- end
369
-
370
- return response
371
- end
372
-
373
- # Encrypts a block of data given an encryption key
374
- def self.encrypt(data, b64_enc_key, iv = nil, cipher_type = 'AES-256-ECB')
375
- aes = OpenSSL::Cipher.new(cipher_type)
376
- aes.encrypt
377
- aes.key = Base64.strict_decode64(b64_enc_key)
378
- aes.iv = iv if iv != nil
379
- Base64.strict_encode64(aes.update(data) + aes.final)
380
- end
20
+ end
381
21
 
382
- # courtesy bitcoin-ruby
383
-
384
- def self.int_to_base58(int_val, leading_zero_bytes=0)
385
- alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
386
- base58_val, base = '', alpha.size
387
- while int_val > 0
388
- int_val, remainder = int_val.divmod(base)
389
- base58_val = alpha[remainder] + base58_val
390
- end
391
- base58_val
392
- end
393
-
394
- def self.base58_to_int(base58_val)
395
- alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
396
- int_val, base = 0, alpha.size
397
- base58_val.reverse.each_char.with_index do |char,index|
398
- raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = alpha.index(char)
399
- int_val += char_index*(base**index)
400
- end
401
- int_val
402
- end
403
-
404
- def self.encode_base58(hex)
405
- leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
406
- ("1"*leading_zero_bytes) + Helper.int_to_base58( hex.to_i(16) )
407
- end
408
-
409
- def self.decode_base58(base58_val)
410
- s = Helper.base58_to_int(base58_val).to_s(16); s = (s.bytesize.odd? ? '0'+s : s)
411
- s = '' if s == '00'
412
- leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 : '').size
413
- s = ("00"*leading_zero_bytes) + s if leading_zero_bytes > 0
414
- s
415
- end
416
- end
417
22
 
418
- end