coin-op 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -1
- data/lib/coin-op.rb +3 -4
- data/lib/coin-op/crypto.rb +91 -28
- data/lib/coin-op/version.rb +1 -1
- metadata +26 -27
- metadata.gz.sig +0 -0
- data/lib/coin-op/blockchain/blockr.rb +0 -162
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 763ee324d095d2518ed75c63c176c03bc3c2cf8e
|
4
|
+
data.tar.gz: b6eec07e082b37b2cadb269bd741713aad1be530
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 241a753e6a272af37c651c303bf96aa239b725b232a68381e9221e98577d05a21901f8a8ac4f1e01a02b556a2117c19ba907a7e0b480f3ac1f69b2b16b873cb5
|
7
|
+
data.tar.gz: 0c823fc19951647daa65312e0f5af73395594bb52461c5f09dc432a716b9789de484cc5168fb8159308e1d0bc104109506edb1d347b413753cb2d6760da56bc2
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
,��JH�?:~=\t�U] ��R0�\$ӡ^����P����s��L�bg��\��ٖ����S�i��{��!j��w?-Oe"QTsǍdtMT��U3L1�`�H����/b�l�C�Ӣ�����5U�%ym��z|~��68i��w��C2�A���[��t�Z�������M�ؾ�%���dr�m �ۥ�ĿH�
|
data/lib/coin-op.rb
CHANGED
@@ -4,8 +4,7 @@ module CoinOp
|
|
4
4
|
|
5
5
|
end
|
6
6
|
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require_relative
|
10
|
-
require_relative "coin-op/blockchain/blockr"
|
7
|
+
require_relative 'coin-op/encodings'
|
8
|
+
require_relative 'coin-op/crypto'
|
9
|
+
require_relative 'coin-op/bit'
|
11
10
|
|
data/lib/coin-op/crypto.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Ruby bindings for libsodium, a port of DJB's NaCl crypto library
|
2
|
-
require
|
3
|
-
require
|
2
|
+
require 'rbnacl'
|
3
|
+
require 'openssl'
|
4
|
+
|
4
5
|
|
5
6
|
module CoinOp
|
6
7
|
module Crypto
|
@@ -29,7 +30,12 @@ module CoinOp
|
|
29
30
|
include CoinOp::Encodings
|
30
31
|
|
31
32
|
# PBKDF2 work factor
|
32
|
-
ITERATIONS =
|
33
|
+
ITERATIONS = 90_000
|
34
|
+
ITERATIONS_WINDOW = 20_000
|
35
|
+
|
36
|
+
SALT_RANDOM_BYTES = 16
|
37
|
+
KEY_SIZE = 32
|
38
|
+
AES_CIPHER = 'AES-256-CBC'
|
33
39
|
|
34
40
|
# Given passphrase and plaintext as strings, returns a Hash
|
35
41
|
# containing the ciphertext and other values needed for later
|
@@ -43,10 +49,18 @@ module CoinOp
|
|
43
49
|
# :salt => salt, :nonce => nonce, :ciphertext => ciphertext
|
44
50
|
#
|
45
51
|
def self.decrypt(passphrase, hash)
|
46
|
-
salt, nonce, ciphertext =
|
47
|
-
hash.values_at(:salt, :nonce, :ciphertext).map {|s| decode_hex(s) }
|
48
|
-
|
49
|
-
|
52
|
+
salt, iv, nonce, ciphertext =
|
53
|
+
hash.values_at(:salt, :iv, :nonce, :ciphertext).map {|s| decode_hex(s) }
|
54
|
+
|
55
|
+
mode = nil
|
56
|
+
if iv.empty?
|
57
|
+
mode = :nacl
|
58
|
+
elsif nonce.empty?
|
59
|
+
mode = :aes
|
60
|
+
end
|
61
|
+
|
62
|
+
box = self.new(passphrase, mode, salt, hash[:iterations] || ITERATIONS)
|
63
|
+
box.decrypt(iv, nonce, ciphertext)
|
50
64
|
end
|
51
65
|
|
52
66
|
attr_reader :salt
|
@@ -54,33 +68,82 @@ module CoinOp
|
|
54
68
|
# Initialize with an existing salt and iterations to allow
|
55
69
|
# decryption. Otherwise, creates new values for these, meaning
|
56
70
|
# it creates an entirely new secret-box.
|
57
|
-
def initialize(passphrase, salt=
|
58
|
-
@salt = salt
|
59
|
-
@iterations = iterations || ITERATIONS
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
71
|
+
def initialize(passphrase, mode=:aes, salt=SecureRandom.random_bytes(SALT_RANDOM_BYTES), iterations=nil)
|
72
|
+
@salt = salt
|
73
|
+
@iterations = iterations || ITERATIONS + SecureRandom.random_number(ITERATIONS_WINDOW)
|
74
|
+
@mode = mode
|
75
|
+
|
76
|
+
if @mode == :aes
|
77
|
+
@key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(
|
78
|
+
passphrase,
|
79
|
+
@salt,
|
80
|
+
# TODO: decide on a very safe work factor
|
81
|
+
# https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet
|
82
|
+
#
|
83
|
+
@iterations, # number of iterations
|
84
|
+
KEY_SIZE * 2 # key length in bytes
|
85
|
+
)
|
86
|
+
|
87
|
+
@aes_key = @key[0, KEY_SIZE]
|
88
|
+
@hmac_key = @key[KEY_SIZE, KEY_SIZE]
|
89
|
+
@cipher = OpenSSL::Cipher.new(AES_CIPHER)
|
90
|
+
@cipher.padding = 0
|
91
|
+
elsif @mode == :nacl
|
92
|
+
@key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(
|
93
|
+
passphrase,
|
94
|
+
@salt,
|
95
|
+
# TODO: decide on a very safe work factor
|
96
|
+
# https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet
|
97
|
+
#
|
98
|
+
@iterations, # number of iterations
|
99
|
+
KEY_SIZE # key length in bytes
|
100
|
+
)
|
101
|
+
@box = RbNaCl::SecretBox.new(@key)
|
102
|
+
end
|
103
|
+
|
70
104
|
end
|
71
105
|
|
72
|
-
def encrypt(plaintext)
|
73
|
-
|
74
|
-
|
106
|
+
def encrypt(plaintext, iv=@cipher.random_iv)
|
107
|
+
@cipher.encrypt
|
108
|
+
@cipher.iv = iv
|
109
|
+
@cipher.key = @aes_key
|
110
|
+
encrypted = @cipher.update(plaintext)
|
111
|
+
encrypted << @cipher.final
|
112
|
+
digest = OpenSSL::Digest::SHA256.new
|
113
|
+
hmac_digest = OpenSSL::HMAC.digest(digest, @hmac_key, iv + encrypted)
|
114
|
+
ciphertext = encrypted + hmac_digest
|
75
115
|
{
|
76
|
-
:
|
77
|
-
:
|
78
|
-
:
|
79
|
-
:
|
116
|
+
iterations: @iterations,
|
117
|
+
salt: hex(@salt),
|
118
|
+
iv: hex(iv),
|
119
|
+
ciphertext: hex(ciphertext)
|
80
120
|
}
|
81
121
|
end
|
82
122
|
|
83
|
-
def decrypt(nonce, ciphertext)
|
123
|
+
def decrypt(iv, nonce, ciphertext)
|
124
|
+
if @mode == :aes
|
125
|
+
return decrypt_aes(iv, ciphertext)
|
126
|
+
elsif @mode == :nacl
|
127
|
+
return decrypt_nacl(nonce, ciphertext)
|
128
|
+
end
|
129
|
+
raise('Incompatible ciphertext')
|
130
|
+
end
|
131
|
+
|
132
|
+
def decrypt_aes(iv, ciphertext)
|
133
|
+
mac, ctext = ciphertext[-KEY_SIZE, KEY_SIZE], ciphertext[0...-KEY_SIZE]
|
134
|
+
digest = OpenSSL::Digest::SHA256.new
|
135
|
+
hmac_digest = OpenSSL::HMAC.digest(digest, @hmac_key, iv + ctext)
|
136
|
+
if hmac_digest != mac
|
137
|
+
raise('Invalid authentication code - this ciphertext may have been tampered with.')
|
138
|
+
end
|
139
|
+
@cipher.decrypt
|
140
|
+
@cipher.iv = iv
|
141
|
+
@cipher.key = @aes_key
|
142
|
+
decrypted = @cipher.update(ctext)
|
143
|
+
decrypted << @cipher.final
|
144
|
+
end
|
145
|
+
|
146
|
+
def decrypt_nacl(nonce, ciphertext)
|
84
147
|
@box.decrypt(nonce, ciphertext)
|
85
148
|
end
|
86
149
|
|
data/lib/coin-op/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coin-op
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew King
|
@@ -32,7 +32,7 @@ cert_chain:
|
|
32
32
|
tdc4VS7IlSRxlZ3dBOgiigy9GXpJ+7F831AqjxL39EPwdr7RguTNz+pi//RKaT/U
|
33
33
|
IlpVB+Xfk0vQdP7iYfjGxDzUf0FACMjsR95waJmadKW1Iy6STw2hwPhYIQz1Hu1A
|
34
34
|
-----END CERTIFICATE-----
|
35
|
-
date: 2015-
|
35
|
+
date: 2015-07-22 00:00:00.000000000 Z
|
36
36
|
dependencies:
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: bitcoin-ruby
|
@@ -62,20 +62,6 @@ dependencies:
|
|
62
62
|
- - "~>"
|
63
63
|
- !ruby/object:Gem::Version
|
64
64
|
version: '0.9'
|
65
|
-
- !ruby/object:Gem::Dependency
|
66
|
-
name: rbnacl-libsodium
|
67
|
-
requirement: !ruby/object:Gem::Requirement
|
68
|
-
requirements:
|
69
|
-
- - "~>"
|
70
|
-
- !ruby/object:Gem::Version
|
71
|
-
version: '1.0'
|
72
|
-
type: :runtime
|
73
|
-
prerelease: false
|
74
|
-
version_requirements: !ruby/object:Gem::Requirement
|
75
|
-
requirements:
|
76
|
-
- - "~>"
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
version: '1.0'
|
79
65
|
- !ruby/object:Gem::Dependency
|
80
66
|
name: hashie
|
81
67
|
requirement: !ruby/object:Gem::Requirement
|
@@ -91,19 +77,19 @@ dependencies:
|
|
91
77
|
- !ruby/object:Gem::Version
|
92
78
|
version: '2.0'
|
93
79
|
- !ruby/object:Gem::Dependency
|
94
|
-
name:
|
80
|
+
name: rbnacl-libsodium
|
95
81
|
requirement: !ruby/object:Gem::Requirement
|
96
82
|
requirements:
|
97
83
|
- - '='
|
98
84
|
- !ruby/object:Gem::Version
|
99
|
-
version: 0.
|
100
|
-
type: :
|
85
|
+
version: 1.0.3
|
86
|
+
type: :runtime
|
101
87
|
prerelease: false
|
102
88
|
version_requirements: !ruby/object:Gem::Requirement
|
103
89
|
requirements:
|
104
90
|
- - '='
|
105
91
|
- !ruby/object:Gem::Version
|
106
|
-
version: 0.
|
92
|
+
version: 1.0.3
|
107
93
|
- !ruby/object:Gem::Dependency
|
108
94
|
name: sequel
|
109
95
|
requirement: !ruby/object:Gem::Requirement
|
@@ -133,19 +119,33 @@ dependencies:
|
|
133
119
|
- !ruby/object:Gem::Version
|
134
120
|
version: '1.3'
|
135
121
|
- !ruby/object:Gem::Dependency
|
136
|
-
name:
|
122
|
+
name: rspec
|
137
123
|
requirement: !ruby/object:Gem::Requirement
|
138
124
|
requirements:
|
139
|
-
- - "
|
125
|
+
- - ">="
|
140
126
|
- !ruby/object:Gem::Version
|
141
|
-
version: '
|
127
|
+
version: '0'
|
142
128
|
type: :development
|
143
129
|
prerelease: false
|
144
130
|
version_requirements: !ruby/object:Gem::Requirement
|
145
131
|
requirements:
|
146
|
-
- - "
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: pry
|
137
|
+
requirement: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
type: :development
|
143
|
+
prerelease: false
|
144
|
+
version_requirements: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
147
|
- !ruby/object:Gem::Version
|
148
|
-
version: '
|
148
|
+
version: '0'
|
149
149
|
description: A pretty, simple to use interface for all of the cryptocurrency libraries
|
150
150
|
you love to use.
|
151
151
|
email:
|
@@ -168,7 +168,6 @@ files:
|
|
168
168
|
- lib/coin-op/bit/script.rb
|
169
169
|
- lib/coin-op/bit/spendable.rb
|
170
170
|
- lib/coin-op/bit/transaction.rb
|
171
|
-
- lib/coin-op/blockchain/blockr.rb
|
172
171
|
- lib/coin-op/blockchain/mockchain.rb
|
173
172
|
- lib/coin-op/crypto.rb
|
174
173
|
- lib/coin-op/encodings.rb
|
@@ -193,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
193
192
|
version: '0'
|
194
193
|
requirements: []
|
195
194
|
rubyforge_project:
|
196
|
-
rubygems_version: 2.
|
195
|
+
rubygems_version: 2.4.5
|
197
196
|
signing_key:
|
198
197
|
specification_version: 4
|
199
198
|
summary: Crypto currency classes in Ruby
|
metadata.gz.sig
CHANGED
Binary file
|
@@ -1,162 +0,0 @@
|
|
1
|
-
require "http"
|
2
|
-
require "json"
|
3
|
-
require 'enumerator'
|
4
|
-
|
5
|
-
require_relative "../bit"
|
6
|
-
|
7
|
-
module CoinOp
|
8
|
-
module Blockchain
|
9
|
-
|
10
|
-
# Blockr.io API documentation: http://blockr.io/documentation/api
|
11
|
-
class Blockr
|
12
|
-
include CoinOp::Encodings
|
13
|
-
|
14
|
-
def initialize(env=:test)
|
15
|
-
subdomain = (env.to_sym == :test) ? "tbtc" : "btc"
|
16
|
-
@base_url = "http://#{subdomain}.blockr.io/api/v1"
|
17
|
-
|
18
|
-
# Testing says 20 is the absolute max
|
19
|
-
@max_per_request = 20
|
20
|
-
|
21
|
-
@http = HTTP.with_headers(
|
22
|
-
"User-Agent" => "bv-blockchain-worker v0.1.0",
|
23
|
-
"Accept" => "application/json"
|
24
|
-
)
|
25
|
-
end
|
26
|
-
|
27
|
-
|
28
|
-
attr_accessor :max_per_request
|
29
|
-
|
30
|
-
|
31
|
-
def unspent(addresses, confirmations=6)
|
32
|
-
|
33
|
-
result = request(
|
34
|
-
:address, :unspent, addresses,
|
35
|
-
:confirmations => confirmations
|
36
|
-
)
|
37
|
-
|
38
|
-
outputs = []
|
39
|
-
result.each do |record|
|
40
|
-
record[:unspent].each do |output|
|
41
|
-
address = record[:address]
|
42
|
-
|
43
|
-
transaction_hex, index, value, script_hex =
|
44
|
-
output.values_at :tx, :n, :amount, :script
|
45
|
-
|
46
|
-
outputs << CoinOp::Bit::Output.new(
|
47
|
-
:transaction_hex => transaction_hex,
|
48
|
-
:index => index,
|
49
|
-
:value => bitcoins_to_satoshis(value),
|
50
|
-
:script => {:hex => script_hex},
|
51
|
-
:address => address
|
52
|
-
)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
outputs.sort_by {|output| -output.value }
|
57
|
-
end
|
58
|
-
|
59
|
-
|
60
|
-
def balance(addresses)
|
61
|
-
result = request(:address, :balance, addresses)
|
62
|
-
balances = {}
|
63
|
-
result.each do |record|
|
64
|
-
balances[record[:address]] = float_to_satoshis(record[:balance])
|
65
|
-
end
|
66
|
-
|
67
|
-
balances
|
68
|
-
end
|
69
|
-
|
70
|
-
|
71
|
-
def transactions(tx_ids)
|
72
|
-
results = request(:tx, :raw, tx_ids)
|
73
|
-
results.map do |record|
|
74
|
-
hex = record[:tx][:hex]
|
75
|
-
|
76
|
-
transaction = CoinOp::Bit::Transaction.hex(hex)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
|
81
|
-
def address_info(addresses, confirmations=6)
|
82
|
-
# Useful for testing transactions()
|
83
|
-
request(
|
84
|
-
:address, :info, addresses,
|
85
|
-
:confirmations => confirmations
|
86
|
-
)
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
def block_info(block_list)
|
91
|
-
request(:block, :info, block_list)
|
92
|
-
end
|
93
|
-
|
94
|
-
|
95
|
-
def block_txs(block_list)
|
96
|
-
request(:block, :txs, block_list)
|
97
|
-
end
|
98
|
-
|
99
|
-
|
100
|
-
# Helper methods
|
101
|
-
|
102
|
-
def bitcoins_to_satoshis(string)
|
103
|
-
string.gsub(".", "").to_i
|
104
|
-
end
|
105
|
-
|
106
|
-
def float_to_satoshis(float)
|
107
|
-
(float * 100_000_000).to_i
|
108
|
-
end
|
109
|
-
|
110
|
-
|
111
|
-
# Queries the Blockr Bitcoin from_type => to_type API with
|
112
|
-
# list, returning the results or throwing an exception on
|
113
|
-
# failure.
|
114
|
-
def request(from_type, to_type, args, query=nil)
|
115
|
-
|
116
|
-
unless args.is_a? Array
|
117
|
-
args = [args]
|
118
|
-
end
|
119
|
-
|
120
|
-
data = []
|
121
|
-
args.each_slice(@max_per_request) do |arg_slice|
|
122
|
-
# Permit calling with either an array or a scalar
|
123
|
-
slice_string = arg_slice.join(",")
|
124
|
-
url = "#{@base_url}/#{from_type}/#{to_type}/#{slice_string}"
|
125
|
-
|
126
|
-
# Construct query string if any params were passed.
|
127
|
-
if query
|
128
|
-
# TODO: validation. The value of the "confirmations" parameter
|
129
|
-
# must be an integer.
|
130
|
-
params = query.map { |name, value| "#{name}=#{value}" }.join("&")
|
131
|
-
url = "#{url}?#{params}"
|
132
|
-
end
|
133
|
-
|
134
|
-
response = @http.request "GET", url, :response => :object
|
135
|
-
# FIXME: rescue any JSON parsing exception and raise an
|
136
|
-
# exception explaining that it's blockr's fault.
|
137
|
-
begin
|
138
|
-
content = JSON.parse(response.body, :symbolize_names => true)
|
139
|
-
rescue JSON::ParserError => e
|
140
|
-
raise "Blockr returned invalid JSON: #{e}"
|
141
|
-
end
|
142
|
-
|
143
|
-
if content[:status] != "success"
|
144
|
-
raise "Blockr.io failure: #{content.to_json}"
|
145
|
-
end
|
146
|
-
|
147
|
-
slice_data = content[:data]
|
148
|
-
if content[:data].is_a? Array
|
149
|
-
data.concat slice_data
|
150
|
-
else
|
151
|
-
data << slice_data
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
data
|
156
|
-
end
|
157
|
-
|
158
|
-
end
|
159
|
-
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|