block_io 1.2.1 → 3.0.2
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
- data/.appveyor.yml-disabled +26 -0
- data/.gitignore +2 -1
- data/.rspec +1 -0
- data/.travis.yml +14 -0
- data/LICENSE +1 -1
- data/README.md +24 -15
- data/block_io.gemspec +9 -7
- data/examples/basic.rb +32 -10
- data/examples/dtrust.rb +59 -42
- data/examples/proxy.rb +36 -0
- data/examples/sweeper.rb +24 -14
- data/lib/block_io.rb +16 -411
- data/lib/block_io/api_exception.rb +11 -0
- data/lib/block_io/chainparams/BTC.yml +8 -0
- data/lib/block_io/chainparams/BTCTEST.yml +8 -0
- data/lib/block_io/chainparams/DOGE.yml +8 -0
- data/lib/block_io/chainparams/DOGETEST.yml +8 -0
- data/lib/block_io/chainparams/LTC.yml +8 -0
- data/lib/block_io/chainparams/LTCTEST.yml +8 -0
- data/lib/block_io/client.rb +244 -0
- data/lib/block_io/extended_bitcoinrb.rb +127 -0
- data/lib/block_io/helper.rb +322 -0
- data/lib/block_io/key.rb +38 -0
- data/lib/block_io/version.rb +1 -1
- data/spec/client_misc_spec.rb +76 -0
- data/spec/client_spec.rb +68 -0
- data/spec/dtrust_spec.rb +167 -0
- data/spec/helper_spec.rb +154 -0
- data/spec/key_spec.rb +92 -0
- data/spec/larger_transaction_spec.rb +351 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/sweep_spec.rb +115 -0
- data/spec/test-cases/.gitignore +2 -0
- data/spec/test-cases/LICENSE +21 -0
- data/spec/test-cases/README.md +2 -0
- data/spec/test-cases/json/create_and_sign_transaction_response.json +61 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1261 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1266 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1271 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +3816 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_3of5_195inputs.json +2931 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_4of5_195inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_251inputs.json +3771 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_252inputs.json +3786 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_253inputs.json +3801 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_251inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_252inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_253inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_251inputs.json +3771 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_252inputs.json +3786 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_253inputs.json +3801 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_251inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_252inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_253inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys.json +21 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_4_of_5_keys.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys.json +21 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_4_of_5_keys.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys.json +36 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_251outputs.json +591 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_252outputs.json +576 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_253outputs.json +531 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4_of_5_keys.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_251outputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_252outputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_253outputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2pkh.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +21 -0
- data/spec/test-cases/json/get_balance_response.json +8 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_3of5_195inputs.json +1397 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_4of5_195inputs.json +1397 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_251inputs.json +1795 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_251inputs.json +1789 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_251inputs.json +1795 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_251inputs.json +1795 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_p2sh.json +45 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_p2wsh_over_p2sh.json +45 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0.json +52 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_251outputs.json +1805 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_252outputs.json +1804 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_253outputs.json +1789 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_251outputs.json +1805 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_252outputs.json +1804 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_253outputs.json +1789 -0
- data/spec/test-cases/json/prepare_sweep_transaction_response_p2pkh.json +35 -0
- data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh.json +35 -0
- data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh_over_p2sh.json +35 -0
- data/spec/test-cases/json/prepare_transaction_response.json +164 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1796 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1803 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1810 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +5367 -0
- data/spec/test-cases/json/prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +76 -0
- data/spec/test-cases/json/summarize_prepared_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +6 -0
- metadata +270 -44
- data/examples/change.rb +0 -117
data/lib/block_io.rb
CHANGED
|
@@ -1,418 +1,23 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
require "http"
|
|
2
|
+
require "oj"
|
|
3
|
+
require "bitcoin"
|
|
4
|
+
require "openssl"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
require "connection_pool"
|
|
7
|
+
|
|
8
|
+
require_relative "block_io/version"
|
|
9
|
+
require_relative "block_io/helper"
|
|
10
|
+
require_relative "block_io/key"
|
|
11
|
+
require_relative "block_io/client"
|
|
12
|
+
require_relative "block_io/api_exception"
|
|
13
|
+
require_relative "block_io/extended_bitcoinrb"
|
|
10
14
|
|
|
11
15
|
module BlockIo
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
+
def self.version
|
|
18
|
+
BlockIo::VERSION
|
|
96
19
|
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
20
|
|
|
134
|
-
|
|
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
|
|
21
|
+
end
|
|
381
22
|
|
|
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
23
|
|
|
418
|
-
end
|