coin-op 0.4.3 → 0.4.4
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 +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
|
-
|