cryptoconditions_ruby 0.5.1
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 +7 -0
- data/.gitignore +10 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.rubocop.yml +26 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +201 -0
- data/README.md +22 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cryptoconditions_ruby.gemspec +34 -0
- data/lib/cryptoconditions_ruby/condition.rb +129 -0
- data/lib/cryptoconditions_ruby/crypto.rb +168 -0
- data/lib/cryptoconditions_ruby/exceptions.rb +8 -0
- data/lib/cryptoconditions_ruby/fulfillment.rb +138 -0
- data/lib/cryptoconditions_ruby/type_registry.rb +27 -0
- data/lib/cryptoconditions_ruby/types/base_sha_256_fulfillment.rb +15 -0
- data/lib/cryptoconditions_ruby/types/ed25519_fulfillment.rb +79 -0
- data/lib/cryptoconditions_ruby/types/inverted_threshold_sha_256_fulfillment.rb +15 -0
- data/lib/cryptoconditions_ruby/types/preimage_sha_256_fulfillment.rb +64 -0
- data/lib/cryptoconditions_ruby/types/threshold_sha_256_fulfillment.rb +343 -0
- data/lib/cryptoconditions_ruby/types/timeout_fulfillment.rb +46 -0
- data/lib/cryptoconditions_ruby/utils/base16.rb +28 -0
- data/lib/cryptoconditions_ruby/utils/base58.rb +53 -0
- data/lib/cryptoconditions_ruby/utils/byte_array.rb +16 -0
- data/lib/cryptoconditions_ruby/utils/bytes.rb +13 -0
- data/lib/cryptoconditions_ruby/utils/hasher.rb +27 -0
- data/lib/cryptoconditions_ruby/utils/hexlify.rb +13 -0
- data/lib/cryptoconditions_ruby/utils/predictor.rb +58 -0
- data/lib/cryptoconditions_ruby/utils/reader.rb +167 -0
- data/lib/cryptoconditions_ruby/utils/writer.rb +81 -0
- data/lib/cryptoconditions_ruby/version.rb +3 -0
- data/lib/cryptoconditions_ruby.rb +28 -0
- metadata +191 -0
@@ -0,0 +1,343 @@
|
|
1
|
+
require 'duplicate'
|
2
|
+
module CryptoconditionsRuby
|
3
|
+
module Types
|
4
|
+
CONDITION = 'condition'
|
5
|
+
FULFILLMENT = 'fulfillment'
|
6
|
+
|
7
|
+
class ThresholdSha256Fulfillment < BaseSha256Fulfillment
|
8
|
+
TYPE_ID = 2
|
9
|
+
FEATURE_BITMASK = 0x09
|
10
|
+
|
11
|
+
attr_accessor :bitmask, :threshold, :subconditions
|
12
|
+
private :bitmask
|
13
|
+
def initialize(threshold = nil)
|
14
|
+
if threshold && (!threshold.is_a?(Integer) || threshold < 1)
|
15
|
+
raise StandardError, "Threshold must be a integer greater than zero, was: #{threshold}"
|
16
|
+
end
|
17
|
+
self.threshold = threshold
|
18
|
+
self.subconditions = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_subcondition(subcondition, weight = 1)
|
22
|
+
if subcondition.is_a?(String)
|
23
|
+
subcondition = Condition.from_uri(subcondition)
|
24
|
+
end
|
25
|
+
unless subcondition.is_a?(Condition)
|
26
|
+
raise TypeError, 'Subconditions must be URIs or objects of type Condition'
|
27
|
+
end
|
28
|
+
unless weight.is_a?(Integer) || weight < 1
|
29
|
+
raise StandardError, "Invalid weight: #{weight}"
|
30
|
+
end
|
31
|
+
|
32
|
+
subconditions.push(
|
33
|
+
'type' => CONDITION,
|
34
|
+
'body' => subcondition,
|
35
|
+
'weight' => weight
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_subcondition_uri(subcondition_uri)
|
40
|
+
unless subcondition_uri.is_a?(String)
|
41
|
+
raise TypeError, "Subcondition must be provided as a URI string, was #{subcondition_uri}"
|
42
|
+
end
|
43
|
+
|
44
|
+
add_subcondition(Condition.from_uri(subcondition_uri))
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_subfulfillment(subfulfillment, weight = 1)
|
48
|
+
if subfulfillment.is_a?(String)
|
49
|
+
subfulfillment = Fulfillment.from_uri(subfulfillment)
|
50
|
+
end
|
51
|
+
unless subfulfillment.is_a?(Fulfillment)
|
52
|
+
raise TypeError, 'Subfulfillments must be URIs or objects of type Fulfillment'
|
53
|
+
end
|
54
|
+
if !weight.is_a?(Integer) || weight < 0
|
55
|
+
raise StandardError, "Invalid weight: #{weight}"
|
56
|
+
end
|
57
|
+
subconditions.push(
|
58
|
+
'type' => FULFILLMENT,
|
59
|
+
'body' => subfulfillment,
|
60
|
+
'weight' => weight
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_subfulfillment_uri(subfulfillment_uri)
|
65
|
+
unless subfulfillment_uri.is_a?(String)
|
66
|
+
raise TypeError, "Subfulfillment must be provided as a URI string, was: #{subfulfillment_uri}"
|
67
|
+
end
|
68
|
+
add_subfulfillment(Fulfillment.from_uri(subfulfillment_uri))
|
69
|
+
end
|
70
|
+
|
71
|
+
def bitmask
|
72
|
+
bitmask = super
|
73
|
+
subconditions.each do |cond|
|
74
|
+
bitmask |= cond['body'].bitmask
|
75
|
+
end
|
76
|
+
bitmask
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_subcondition_from_vk(vk)
|
80
|
+
subconditions.inject([]) do |store, c|
|
81
|
+
if c['body'].is_a?(Ed25519Fulfillment) && Utils::Base58.encode(c['body'].public_key.to_s) == vk
|
82
|
+
store.push(c)
|
83
|
+
elsif c['body'].is_a?(ThresholdSha256Fulfillment)
|
84
|
+
result = c['body'].get_subcondition_from_vk(vk)
|
85
|
+
store += result if result
|
86
|
+
end
|
87
|
+
store
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_hash_payload(hasher)
|
92
|
+
raise StandardError, 'Requires subconditions' if subconditions.empty?
|
93
|
+
|
94
|
+
_subconditions = subconditions.inject([]) do |store, c|
|
95
|
+
writer = Utils::Writer.new
|
96
|
+
writer.write_var_uint(c['weight'])
|
97
|
+
writer.write(
|
98
|
+
c['type'] == FULFILLMENT ? c['body'].condition_binary : c['body'].serialize_binary
|
99
|
+
)
|
100
|
+
store.push(writer.buffer)
|
101
|
+
end
|
102
|
+
sorted_subconditions = ThresholdSha256Fulfillment.sort_buffers(_subconditions)
|
103
|
+
|
104
|
+
hasher.write_uint32(threshold)
|
105
|
+
hasher.write_var_uint(sorted_subconditions.length)
|
106
|
+
sorted_subconditions.each do |cond|
|
107
|
+
hasher.write(cond)
|
108
|
+
end
|
109
|
+
hasher
|
110
|
+
end
|
111
|
+
|
112
|
+
def calculate_max_fulfillment_length
|
113
|
+
total_condition_len = 0
|
114
|
+
|
115
|
+
_subconditions = subconditions.map do |c|
|
116
|
+
condition_len = ThresholdSha256Fulfillment.predict_subcondition_length(c)
|
117
|
+
fulfillment_len = ThresholdSha256Fulfillment.predict_subfulfillment_length(c)
|
118
|
+
total_condition_len += condition_len
|
119
|
+
{
|
120
|
+
'weight' => c['weight'],
|
121
|
+
'size' => fulfillment_len - condition_len
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
_subconditions.sort_by! { |x| x['weight'].abs }
|
126
|
+
|
127
|
+
worst_case_fulfillments_length = total_condition_len + ThresholdSha256Fulfillment.calculate_worst_case_length(threshold, _subconditions)
|
128
|
+
|
129
|
+
if worst_case_fulfillments_length == -Float::INFINITY
|
130
|
+
raise StandardError, 'Insufficient subconditions/weights to meet the threshold'
|
131
|
+
end
|
132
|
+
|
133
|
+
# Calculate resulting total maximum fulfillment size
|
134
|
+
predictor = Utils::Predictor.new
|
135
|
+
predictor.write_uint32(threshold)
|
136
|
+
predictor.write_var_uint(subconditions.length)
|
137
|
+
subconditions.each do |c|
|
138
|
+
predictor.write_uint8(nil)
|
139
|
+
predictor.write_var_uint(c['weight']) unless c['weight'] == 1
|
140
|
+
end
|
141
|
+
|
142
|
+
predictor.skip(worst_case_fulfillments_length)
|
143
|
+
|
144
|
+
predictor.size
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.predict_subcondition_length(cond)
|
148
|
+
return cond['body'].condition_binary.length if cond['type'] == FULFILLMENT
|
149
|
+
|
150
|
+
cond['body'].serialize_binary.length
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.predict_subfulfillment_length(cond)
|
154
|
+
fulfillment_len = if cond['type'] == FULFILLMENT
|
155
|
+
cond['body'].condition.max_fulfillment_length
|
156
|
+
else
|
157
|
+
cond['body'].max_fulfillment_length
|
158
|
+
end
|
159
|
+
|
160
|
+
predictor = Utils::Predictor.new
|
161
|
+
predictor.write_uint16(nil)
|
162
|
+
predictor.write_var_octet_string('0' * fulfillment_len)
|
163
|
+
predictor.size
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.calculate_worst_case_length(threshold, subconditions, index = 0)
|
167
|
+
return 0 if threshold <= 0
|
168
|
+
if index < subconditions.length
|
169
|
+
next_condition = subconditions[index]
|
170
|
+
|
171
|
+
[
|
172
|
+
next_condition['size'] + ThresholdSha256Fulfillment.calculate_worst_case_length(
|
173
|
+
threshold - next_condition['weight'].abs,
|
174
|
+
subconditions,
|
175
|
+
index + 1
|
176
|
+
),
|
177
|
+
ThresholdSha256Fulfillment.calculate_worst_case_length(
|
178
|
+
threshold,
|
179
|
+
subconditions,
|
180
|
+
index + 1
|
181
|
+
)
|
182
|
+
].max
|
183
|
+
else
|
184
|
+
-Float::INFINITY
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def parse_payload(reader, *args)
|
189
|
+
raise TypeError, 'reader must be a Reader instance' unless reader.is_a?(Utils::Reader)
|
190
|
+
|
191
|
+
self.threshold = reader.read_var_uint
|
192
|
+
condition_count = reader.read_var_uint
|
193
|
+
|
194
|
+
condition_count.times do
|
195
|
+
weight = reader.read_var_uint
|
196
|
+
fulfillment = reader.read_var_octet_string
|
197
|
+
condition = reader.read_var_octet_string
|
198
|
+
if !fulfillment.empty? && !condition.empty?
|
199
|
+
raise TypeError, 'Subconditions may not provide both subcondition and fulfillment.'
|
200
|
+
elsif
|
201
|
+
if !fulfillment.empty?
|
202
|
+
add_subfulfillment(Fulfillment.from_binary(fulfillment), weight)
|
203
|
+
elsif !condition.empty?
|
204
|
+
add_subcondition(Condition.from_binary(condition), weight)
|
205
|
+
else
|
206
|
+
raise TypeError, 'Subconditions must provide either subcondition or fulfillment.'
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def write_payload(writer)
|
213
|
+
raise TypeError, 'writer must be a Writer instance' unless writer.is_a?(Utils::Writer)
|
214
|
+
|
215
|
+
subfulfillments = subconditions.each_with_index.map do |c, i|
|
216
|
+
next unless c['type'] == FULFILLMENT
|
217
|
+
|
218
|
+
subfulfillment = c.dup
|
219
|
+
subfulfillment.merge!(
|
220
|
+
'index' => i,
|
221
|
+
'size' => c['body'].serialize_binary.length,
|
222
|
+
'omit_size' => c['body'].condition_binary.length
|
223
|
+
)
|
224
|
+
end.compact
|
225
|
+
|
226
|
+
smallest_set = ThresholdSha256Fulfillment.calculate_smallest_valid_fulfillment_set(
|
227
|
+
threshold, subfulfillments
|
228
|
+
)['set']
|
229
|
+
|
230
|
+
optimized_subfulfillments = subconditions.each_with_index.map do |c, i|
|
231
|
+
if c['type'] == FULFILLMENT && !smallest_set.include?(i)
|
232
|
+
subfulfillment = c.dup
|
233
|
+
subfulfillment.update(
|
234
|
+
'type' => CONDITION,
|
235
|
+
'body' => c['body'].condition
|
236
|
+
)
|
237
|
+
else
|
238
|
+
c
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
serialized_subconditions = optimized_subfulfillments.map do |c|
|
243
|
+
writer_ = Utils::Writer.new
|
244
|
+
writer_.write_var_uint(c['weight'])
|
245
|
+
writer_.write_var_octet_string(c['type'] == FULFILLMENT ? c['body'].serialize_binary : '')
|
246
|
+
writer_.write_var_octet_string(c['type'] == CONDITION ? c['body'].serialize_binary : '')
|
247
|
+
writer_.buffer
|
248
|
+
end
|
249
|
+
|
250
|
+
sorted_subconditions = ThresholdSha256Fulfillment.sort_buffers(serialized_subconditions)
|
251
|
+
|
252
|
+
writer.write_var_uint(threshold)
|
253
|
+
writer.write_var_uint(sorted_subconditions.length)
|
254
|
+
sorted_subconditions.each { |c| writer.write(c) }
|
255
|
+
writer
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.calculate_smallest_valid_fulfillment_set(threshold, fulfillments, state = nil)
|
259
|
+
state ||= { 'index' => 0, 'size' => 0, 'set' => [] }
|
260
|
+
|
261
|
+
if threshold <= 0
|
262
|
+
{ 'size' => state['size'], 'set' => state['set'] }
|
263
|
+
elsif state['index'] < fulfillments.length
|
264
|
+
next_fulfillment = fulfillments[state['index']]
|
265
|
+
with_next = ThresholdSha256Fulfillment.calculate_smallest_valid_fulfillment_set(
|
266
|
+
threshold - next_fulfillment['weight'].abs,
|
267
|
+
fulfillments,
|
268
|
+
'size' => state['size'] + next_fulfillment['size'],
|
269
|
+
'index' => state['index'] + 1,
|
270
|
+
'set' => state['set'] + [next_fulfillment['index']]
|
271
|
+
)
|
272
|
+
|
273
|
+
without_next = ThresholdSha256Fulfillment.calculate_smallest_valid_fulfillment_set(
|
274
|
+
threshold,
|
275
|
+
fulfillments,
|
276
|
+
'size' => state['size'] + next_fulfillment['omit_size'],
|
277
|
+
'index' => state['index'] + 1,
|
278
|
+
'set' => state['set']
|
279
|
+
)
|
280
|
+
with_next['size'] < without_next['size'] ? with_next : without_next
|
281
|
+
else
|
282
|
+
{ 'size' => Float::INFINITY }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def self.sort_buffers(buffers)
|
287
|
+
Duplicate.duplicate(buffers).sort_by { |item| [item.length, item] }
|
288
|
+
end
|
289
|
+
|
290
|
+
def to_dict
|
291
|
+
subfulfillments = subconditions.map do |c|
|
292
|
+
subcondition = c['body'].to_dict
|
293
|
+
subcondition.merge!('weight' => c['weight'])
|
294
|
+
end
|
295
|
+
|
296
|
+
{
|
297
|
+
'type' => 'fulfillment',
|
298
|
+
'type_id' => type_id,
|
299
|
+
'bitmask' => bitmask,
|
300
|
+
'threshold' => threshold,
|
301
|
+
'subfulfillments' => subfulfillments
|
302
|
+
}
|
303
|
+
end
|
304
|
+
|
305
|
+
def parse_dict(data)
|
306
|
+
raise TypeError, 'reader must be a dict instance' unless data.is_a?(Hash)
|
307
|
+
self.threshold = data['threshold']
|
308
|
+
|
309
|
+
data['subfulfillments'].each do |subfulfillments|
|
310
|
+
weight = subfulfillments['weight']
|
311
|
+
if subfulfillments['type'] == FULFILLMENT
|
312
|
+
add_subfulfillment(Fulfillment.from_dict(subfulfillments), weight)
|
313
|
+
elsif subfulfillments['type'] == CONDITION
|
314
|
+
add_subcondition(Condition.from_dict(subfulfillments), weight)
|
315
|
+
else
|
316
|
+
raise TypeError, 'Subconditions must provide either subcondition or fulfillment.'
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def validate(message: nil, **_kwargs)
|
322
|
+
fulfillments = subconditions.select { |c| c['type'] == FULFILLMENT }
|
323
|
+
|
324
|
+
min_weight = Float::INFINITY
|
325
|
+
total_weight = 0
|
326
|
+
fulfillments.each do |fulfillment|
|
327
|
+
min_weight = [min_weight, fulfillment['weight'].abs].max
|
328
|
+
total_weight += min_weight
|
329
|
+
end
|
330
|
+
|
331
|
+
# Total weight must meet the threshold
|
332
|
+
return if total_weight < threshold
|
333
|
+
|
334
|
+
valid_decisions = fulfillments.map do |fulfillment|
|
335
|
+
if fulfillment['body'].validate(message: message, **_kwargs)
|
336
|
+
[true] * fulfillment['weight']
|
337
|
+
end
|
338
|
+
end.compact.flatten
|
339
|
+
valid_decisions.count >= threshold
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module CryptoconditionsRuby
|
2
|
+
TIMESTAMP_REGEX = /^\d{10}(\.\d+)?$/
|
3
|
+
|
4
|
+
module Types
|
5
|
+
class TimeoutFulfillment < PreimageSha256Fulfillment
|
6
|
+
TYPE_ID = 99
|
7
|
+
FEATURE_BITMASK = 0x09
|
8
|
+
REGEX = TIMESTAMP_REGEX
|
9
|
+
|
10
|
+
def self.timestamp(time)
|
11
|
+
format('%6f', time.to_f)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(expire_time = nil)
|
15
|
+
if expire_time.is_a?(String) && !expire_time.match(REGEX)
|
16
|
+
raise TypeError, "Expire time must be conform UTC unix time, was: #{expire_time}"
|
17
|
+
end
|
18
|
+
super if expire_time
|
19
|
+
end
|
20
|
+
|
21
|
+
def expire_time
|
22
|
+
preimage
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_dict
|
26
|
+
{
|
27
|
+
'type' => 'fulfillment',
|
28
|
+
'type_id' => TYPE_ID,
|
29
|
+
'bitmask' => bitmask,
|
30
|
+
'expire_time' => expire_time
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_dict(data)
|
35
|
+
self.preimage = data['expire_time']
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate(message: nil, now: nil, **_kwargs)
|
39
|
+
unless now || now.match(REGEX)
|
40
|
+
raise TypeError, "message must be of unix time format, was: #{message}"
|
41
|
+
end
|
42
|
+
now.to_f <= expire_time.to_f
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CryptoconditionsRuby
|
2
|
+
module Utils
|
3
|
+
class Base16
|
4
|
+
def self.encode(data)
|
5
|
+
ret = ''
|
6
|
+
data.each_char do |c|
|
7
|
+
ch = c.ord.to_s(16)
|
8
|
+
ch = '0' + ch if ch.size == 1
|
9
|
+
ret += ch
|
10
|
+
end
|
11
|
+
ret.upcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.decode(data)
|
15
|
+
chars = ''
|
16
|
+
ret = ''
|
17
|
+
data.each_char do |c|
|
18
|
+
chars += c
|
19
|
+
if chars.size == 2
|
20
|
+
ret += chars.to_i(16).chr
|
21
|
+
chars = ''
|
22
|
+
end
|
23
|
+
end
|
24
|
+
ret
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module CryptoconditionsRuby
|
2
|
+
module Utils
|
3
|
+
class Base58
|
4
|
+
CHARS = %w(123456789 ABCDEFGHJKLMNPQRSTUVWXYZ abcdefghijkmnopqrstuvwxyz).freeze
|
5
|
+
|
6
|
+
def self.decoding_table
|
7
|
+
@decoding_table ||= generate_decoding_table
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.generate_decoding_table
|
11
|
+
CHARS.join.split('').each_with_index.each_with_object({}) do |(c, i), store|
|
12
|
+
store[c] = i
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.encoding_table
|
17
|
+
@encoding_table ||= decoding_table.invert
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.encode(data)
|
21
|
+
cdata = data.unpack('C*')
|
22
|
+
vlong = cdata.inject(0) { |store, v| v + store * 256 }
|
23
|
+
result = ''
|
24
|
+
while vlong > 0
|
25
|
+
result += encoding_table[vlong % 58]
|
26
|
+
vlong /= 58
|
27
|
+
end
|
28
|
+
while cdata.first.zero?
|
29
|
+
result += '1'
|
30
|
+
cdata = cdata[1..-1]
|
31
|
+
end
|
32
|
+
result.reverse
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.decode(data_string)
|
36
|
+
vlong = data_string.each_byte.inject(0) do |store, b|
|
37
|
+
store = decoding_table[b.chr] + (58 * store)
|
38
|
+
store
|
39
|
+
end
|
40
|
+
result = ''
|
41
|
+
while vlong > 0
|
42
|
+
result += (vlong % 256).chr
|
43
|
+
vlong /= 256
|
44
|
+
end
|
45
|
+
while data_string[0] == '1'
|
46
|
+
result += "\x00"
|
47
|
+
data_string = data_string[1..-1]
|
48
|
+
end
|
49
|
+
result.reverse
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CryptoconditionsRuby
|
2
|
+
module Utils
|
3
|
+
class ByteArray
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(string_or_array)
|
7
|
+
@original = string_or_array
|
8
|
+
@collection = string_or_array.is_a?(Array) ? string_or_array : string_or_array.bytes
|
9
|
+
end
|
10
|
+
|
11
|
+
def each
|
12
|
+
@collection.each
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CryptoconditionsRuby::Utils::Bytes
|
2
|
+
attr_reader :bytes
|
3
|
+
private :bytes
|
4
|
+
def initialize(input)
|
5
|
+
@bytes = input.is_a?(Array) ? input : input.bytes
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_i(base)
|
9
|
+
bytes.reverse.each_with_index.inject(0) do |store, (byte, index)|
|
10
|
+
store += byte * base**(index * 2)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
class CryptoconditionsRuby::Utils::Hasher < CryptoconditionsRuby::Utils::Writer
|
4
|
+
attr_reader :digest_instance
|
5
|
+
private :digest_instance
|
6
|
+
|
7
|
+
def initialize(algorithm)
|
8
|
+
if algorithm == 'sha256'
|
9
|
+
@digest_instance = Digest::SHA256.new
|
10
|
+
else
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
super()
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(in_bytes)
|
17
|
+
digest_instance.update(in_bytes)
|
18
|
+
end
|
19
|
+
|
20
|
+
def digest
|
21
|
+
digest_instance.digest
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.length(algorithm)
|
25
|
+
new(algorithm).digest.length
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class CryptoconditionsRuby::Utils::Predictor
|
2
|
+
attr_accessor :size
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@size = 0
|
6
|
+
end
|
7
|
+
|
8
|
+
def write_uint(_value, length)
|
9
|
+
skip(length)
|
10
|
+
end
|
11
|
+
|
12
|
+
def write_var_uint(value)
|
13
|
+
return write_var_octet_string(value) if value.is_a?(String)
|
14
|
+
raise TypeError.new('UInt must be an integer') unless value.is_a?(Integer)
|
15
|
+
raise TypeError.new('UInt must be positive') unless value > 0
|
16
|
+
|
17
|
+
length_of_value = (sprintf('%02b', value).length / 8.0).ceil.to_i
|
18
|
+
buffer = (length_of_value - 1).times.map { 0 }.push(value).pack('C*')
|
19
|
+
write_var_octet_string(buffer)
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_octet_string(_value, length)
|
23
|
+
skip(length)
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_var_octet_string(value)
|
27
|
+
skip(1)
|
28
|
+
if value.length > 127
|
29
|
+
length_of_length = (sprintf('%02b', value.length).length / 8.0).ceil.to_i
|
30
|
+
skip(length_of_length)
|
31
|
+
end
|
32
|
+
skip(value.length)
|
33
|
+
end
|
34
|
+
|
35
|
+
def write(in_bytes)
|
36
|
+
self.size += in_bytes.length
|
37
|
+
end
|
38
|
+
|
39
|
+
def skip(byte_count)
|
40
|
+
self.size += byte_count
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_uint8(value)
|
44
|
+
self.write_uint(value, 1)
|
45
|
+
end
|
46
|
+
|
47
|
+
def write_uint16(value)
|
48
|
+
self.write_uint(value, 2)
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_uint32(value)
|
52
|
+
self.write_uint(value, 4)
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_uint64(value)
|
56
|
+
self.write_uint(value, 8)
|
57
|
+
end
|
58
|
+
end
|