block_io 1.2.1 → 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
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