tss 0.3.0 → 0.4.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
  SHA1:
3
- metadata.gz: e448b355558f4f493400bac55a51fd14a8c56bf3
4
- data.tar.gz: 537024e64cf91c9394784d76d9df61347add2041
3
+ metadata.gz: c3e63f35e23f40623afd002c3cba4e7a3407dd19
4
+ data.tar.gz: 54fb3ead58b69e1118e13dcd72da08705bed4276
5
5
  SHA512:
6
- metadata.gz: b69d52a5340fda8078e46e1ed95dc4c5a6e4ffbb54b5ac0295dce9fc2633decacf61a7616dff3c838772e3bb9e97be110d6a819a07fbf04d7f4ef0e8cfac9491
7
- data.tar.gz: 5cc4fa4acc4e920dda21772432512da25b46bf9a16ac960496e0eb8cd0c0b2a3b5da979e4863e5571d0dcaa3962202ee9ec8bc3c98eb589b05ef8e8f5e653a0b
6
+ metadata.gz: 1c1e218fbc97f5caee8f2975ac340313104807bd12a1d68b6e47a00ea3befea35c2cdc62785f428dbddd71289a55f3cbad6656438613a43029c41b52cf4b7d9b
7
+ data.tar.gz: 1a32eaaf782d3fe963451e2e1e1caff4c2fefe2e412b93ec47b8745eff6004773785fa9b9566a424ab15288b63cbc00957a547f59d7a1fa66e1c2420a423c936
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/.yardopts CHANGED
@@ -1 +1 @@
1
- --no-private lib/**/*.rb - README.md LICENSE.txt CODE_OF_CONDUCT.md
1
+ --plugin contracts -e lib/tss/custom_contracts.rb --no-private lib/**/*.rb - README.md LICENSE.txt CODE_OF_CONDUCT.md
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.4.0 (9/24/2016)
4
+
5
+ * Breaking change to force upcasing of some addition string args
6
+ * Use yard-contracts
7
+ * yardoc cleanups
8
+ * Remove int_commas utility method
9
+ * Hash w/ sha256 a,b strings in secure_compare
10
+ * Deeper Contracts integration
11
+
3
12
  ## v0.3.0 (9/24/2016)
4
13
 
5
14
  * Breaking change, identifier cannot be an empty string
data/README.md CHANGED
@@ -161,7 +161,7 @@ It may work on others as well.
161
161
  Add this line to your application's `Gemfile`:
162
162
 
163
163
  ```ruby
164
- gem 'tss', '~> 0.1'
164
+ gem 'tss', '~> 0.3'
165
165
  ```
166
166
 
167
167
  And then execute:
@@ -296,7 +296,7 @@ tss~v1~546604c0b5e9138b~3~NTQ2NjA0YzBiNWU5MTM4YgIDAD8FqmJGRCiXokksUc3E1-gQNuNf2l
296
296
  You can use the CLI to enter shares in order to recover a secret. Of course
297
297
  you will need at least the number of shares necessary as determined
298
298
  by the threshold when your shares were created. The `threshold` is visible
299
- as the third field in every `human` formatted share.
299
+ as the third field in every `HUMAN` formatted share.
300
300
 
301
301
  As with splitting a secret, there are also three methods of getting the shares
302
302
  into the CLI. `STDIN`, a path to a file containing shares, or interactively.
@@ -419,12 +419,12 @@ and then combined with it prior to secret splitting. This means that the hash
419
419
  is protected the same way as the secret. The algorithm used is
420
420
  `secret || hash(secret)`. You can use one of `NONE`, `SHA1`, or `SHA256`.
421
421
 
422
- The `format` arg takes a String Enum with either `'human'` (default) or
423
- `'binary'` values. This instructs the output of a split to either provide an
422
+ The `format` arg takes an uppercase String Enum with either `'HUMAN'` (default) or
423
+ `'BINARY'` values. This instructs the output of a split to either provide an
424
424
  array of binary octet strings (a standard RTSS format for interoperability), or
425
425
  a human friendly URL Safe Base 64 encoded version of that same binary output.
426
- The `human` format can be easily shared in a tweet, email, or even a URL. The
427
- `human` format is prefixed with `tss~VERSION~IDENTIFIER~THRESHOLD~` to make it
426
+ The `HUMAN` format can be easily shared in a tweet, email, or even a URL. The
427
+ `HUMAN` format is prefixed with `tss~VERSION~IDENTIFIER~THRESHOLD~` to make it
428
428
  easier to visually compare shares and see if they have matching identifiers and
429
429
  if you have enough shares to reach the threshold.
430
430
 
@@ -475,7 +475,7 @@ threshold = 3
475
475
  num_shares = 5
476
476
  identifier = SecureRandom.hex(8)
477
477
  hash_alg = 'SHA256'
478
- format = 'human'
478
+ format = 'HUMAN'
479
479
 
480
480
  s = TSS.split(secret: secret, threshold: threshold, num_shares: num_shares, identifier: identifier, hash_alg: 'SHA256', pad_blocksize: 16, format: format)
481
481
 
@@ -523,23 +523,23 @@ of those shares are selected for use in the operation. The method
523
523
  used to select the shares can be chosen with the `select_by:` argument
524
524
  which takes the following values as options:
525
525
 
526
- `select_by: 'first'` : If X shares are required by the threshold and more than X
526
+ `select_by: 'FIRST'` : If X shares are required by the threshold and more than X
527
527
  shares are provided, then the first X shares in the Array of shares provided
528
528
  will be used. All others will be discarded and the operation will fail if
529
529
  those selected shares cannot recreate the secret.
530
530
 
531
- `select_by: 'sample'` : If X shares are required by the threshold and more than X
531
+ `select_by: 'SAMPLE'` : If X shares are required by the threshold and more than X
532
532
  shares are provided, then X shares will be randomly selected from the Array
533
533
  of shares provided. All others will be discarded and the operation will
534
534
  fail if those selected shares cannot recreate the secret.
535
535
 
536
- `select_by: 'combinations'` : If X shares are required, and more than X shares are
536
+ `select_by: 'COMBINATIONS'` : If X shares are required, and more than X shares are
537
537
  provided, then all possible combinations of the threshold number of shares
538
538
  will be tried to see if the secret can be recreated.
539
539
 
540
540
  **Warning**
541
541
 
542
- This `combinations` flexibility comes with a cost. All combinations of
542
+ This `COMBINATIONS` flexibility comes with a cost. All combinations of
543
543
  `threshold` shares must be generated before processing. Due to the math
544
544
  associated with combinations it is possible that the system would try to
545
545
  generate a number of combinations that could never be generated or processed
@@ -565,9 +565,9 @@ A great short read on this is
565
565
 
566
566
  ### Exception Handling
567
567
 
568
- Initial validation of options is done when the `TSS.split` or `TSS.combine`
569
- methods are called. If the arguments passed are of the wrong Type or value
570
- a `TSS::ArgumentError` Exception will be raised.
568
+ Almost all methods in the program have strict contracts associated with them
569
+ that enforce argument presence, types, and value boundaries. This contract checking
570
+ is provided by the excellent Ruby [Contracts](https://egonschiele.github.io/contracts.ruby/) gem. If a contract violation occurs a `ParamContractError` Exception will be raised.
571
571
 
572
572
  The splitting and combining operations may also raise the following
573
573
  exception types:
@@ -583,7 +583,7 @@ All Exceptions should include hints as to what went wrong in the
583
583
  ### RTSS Binary
584
584
 
585
585
  TSS provides shares in a binary data format with the following fields, and
586
- by default this binary data is wrapped in a `'human'` text format:
586
+ by default this binary data is wrapped in a `'HUMAN'` text format:
587
587
 
588
588
  `Identifier`. This field contains 16 octets. It identifies the secret
589
589
  with which a share is associated. All of the shares associated
@@ -641,7 +641,7 @@ text wrapper around the RTSS binary data format is provided.
641
641
 
642
642
  Shares formatted this way can easily be shared via most any communication channel.
643
643
 
644
- The `human` data format is simply the same RTSS binary data, URL Safe Base64
644
+ The `HUMAN` data format is simply the same RTSS binary data, URL Safe Base64
645
645
  encoded, and prefixed with a String thet contains tilde `~` separated elements.
646
646
  The `~` is used as it is compatible with the URL Safe data and the allowed
647
647
  characters in the rest of the human format string.
@@ -666,12 +666,16 @@ and Combining involves at least `num_shares**secret_bytes` operations so
666
666
  larger values can quickly result in huge processing time. If you need to
667
667
  split large secrets into a large number of shares you should consider
668
668
  running those operations in a background worker process or thread for
669
- best performance.
669
+ best performance. For example a 64kb file split into 255 shares,
670
+ with a threshold of 255 (the max for all three settings), can take
671
+ 20 minutes to split and another 20 minutes to combine using a modern CPU.
670
672
 
671
673
  A reasonable set of values seems to be what I'll call the 'rule of 64'. If you
672
674
  keep the `secret <= 64 Bytes`, the `threshold <= 64`, and the `num_shares <= 64`
673
- you can do a round-trip split and combine operation in `~250ms` on a modern
674
- laptop. These should be very reasonable and secure max values for most use cases.
675
+ you can do a round-trip split and combine operation in `~250ms`. These should
676
+ be very reasonable and secure max values for most use cases, even as part of a
677
+ web request response cycle. Remember, its recommended to split encryption keys,
678
+ not plaintext.
675
679
 
676
680
  There are some simple benchmark tests to exercise things with `rake bench`.
677
681
 
@@ -1,7 +1,5 @@
1
1
  require 'thor'
2
2
 
3
- # Command Line Interface (CLI)
4
- # See also, `bin/tss` executable.
5
3
  module TSS
6
4
  class CLI < Thor
7
5
  include Thor::Actions
@@ -1,7 +1,5 @@
1
1
  require 'thor'
2
2
 
3
- # Command Line Interface (CLI)
4
- # See also, `bin/tss` executable.
5
3
  module TSS
6
4
  class CLI < Thor
7
5
 
data/lib/tss/cli_split.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  require 'thor'
2
2
 
3
- # Command Line Interface (CLI)
4
- # See also, `bin/tss` executable.
5
3
  module TSS
6
4
  class CLI < Thor
7
5
  include Thor::Actions
@@ -10,7 +8,7 @@ module TSS
10
8
  method_option :num_shares, :aliases => '-n', :banner => 'num_shares', :type => :numeric, :desc => '# of shares total that will be generated'
11
9
  method_option :identifier, :aliases => '-i', :banner => 'identifier', :type => :string, :desc => 'A unique identifier string, 0-16 Bytes, [a-zA-Z0-9.-_]'
12
10
  method_option :hash_alg, :aliases => '-h', :banner => 'hash_alg', :type => :string, :desc => 'A hash type for verification, NONE, SHA1, SHA256'
13
- method_option :format, :aliases => '-f', :banner => 'format', :type => :string, :default => 'human', :desc => 'Share output format, binary or human'
11
+ method_option :format, :aliases => '-f', :banner => 'format', :type => :string, :default => 'HUMAN', :desc => 'Share output format, BINARY or HUMAN'
14
12
  method_option :pad_blocksize, :aliases => '-p', :banner => 'pad_blocksize', :type => :numeric, :desc => 'Block size # secrets will be left-padded to, 0-255'
15
13
  method_option :input_file, :aliases => '-I', :banner => 'input_file', :type => :string, :desc => 'A filename to read the secret from'
16
14
  method_option :output_file, :aliases => '-O', :banner => 'output_file', :type => :string, :desc => 'A filename to write the shares to'
@@ -57,7 +55,7 @@ module TSS
57
55
 
58
56
  Example w/ options:
59
57
 
60
- $ tss split -t 3 -n 6 -i abc123 -h SHA256 -p 8 -f human
58
+ $ tss split -t 3 -n 6 -i abc123 -h SHA256 -p 8 -f HUMAN
61
59
 
62
60
  Enter your secret:
63
61
 
@@ -1,7 +1,5 @@
1
1
  require 'thor'
2
2
 
3
- # Command Line Interface (CLI)
4
- # See also, `bin/tss` executable.
5
3
  module TSS
6
4
  class CLI < Thor
7
5
  desc 'version', 'tss version'
data/lib/tss/combiner.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  module TSS
2
- # Combiner has responsibility for combining an Array of String shares back
2
+ # Warning, you probably don't want to use this directly. Instead
3
+ # see the TSS module.
4
+ #
5
+ # TSS::Combiner has responsibility for combining an Array of String shares back
3
6
  # into the original secret the shares were split from. It is also responsible
4
7
  # for doing extensive validation of user provided shares and ensuring
5
8
  # that any recovered secret matches the hash of the original secret.
@@ -11,15 +14,17 @@ module TSS
11
14
 
12
15
  attr_reader :shares, :select_by
13
16
 
14
- Contract ({ :shares => C::ArrayOf[String], :select_by => C::Maybe[C::Enum['first', 'sample', 'combinations']] }) => C::Any
17
+ Contract ({ :shares => C::ArrayOfShares, :select_by => C::Maybe[C::SelectByArg] }) => C::Any
15
18
  def initialize(opts = {})
16
19
  # clone the incoming shares so the object passed to this
17
20
  # function doesn't get modified.
18
21
  @shares = opts.fetch(:shares).clone
19
- raise TSS::ArgumentError, 'Invalid number of shares. Must be between 1 and 255' unless @shares.size.between?(1,255)
20
- @select_by = opts.fetch(:select_by, 'first')
22
+ @select_by = opts.fetch(:select_by, 'FIRST')
21
23
  end
22
24
 
25
+ # Warning, you probably don't want to use this directly. Instead
26
+ # see the TSS module.
27
+ #
23
28
  # To reconstruct a secret from a set of shares, the following
24
29
  # procedure, or any equivalent method, is used:
25
30
  #
@@ -52,8 +57,11 @@ module TSS
52
57
  #
53
58
  # The output string is returned (along with some metadata).
54
59
  #
60
+ #
61
+ # @return an Hash of combined secret attributes
62
+ # @raise [ParamContractError, TSS::ArgumentError] if the options Types or Values are invalid
55
63
  # rubocop:disable CyclomaticComplexity
56
- Contract C::None => ({ :hash => C::Maybe[String], :hash_alg => C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256']], :identifier => TSS::IdentifierArg, :process_time => C::Num, :secret => TSS::SecretArg, :threshold => TSS::ThresholdArg})
64
+ Contract C::None => ({ :hash => C::Maybe[String], :hash_alg => C::HashAlgArg, :identifier => C::IdentifierArg, :process_time => C::Num, :secret => C::SecretArg, :threshold => C::ThresholdArg})
57
65
  def combine
58
66
  # unwrap 'human' shares into binary shares
59
67
  if all_shares_appear_human?(shares)
@@ -70,9 +78,9 @@ module TSS
70
78
 
71
79
  # Select a subset of the shares provided using the chosen selection
72
80
  # method. If there are exactly the right amount of shares this is a no-op.
73
- if select_by == 'first'
81
+ if select_by == 'FIRST'
74
82
  @shares = shares.shift(threshold)
75
- elsif select_by == 'sample'
83
+ elsif select_by == 'SAMPLE'
76
84
  @shares = shares.sample(threshold)
77
85
  end
78
86
 
@@ -85,7 +93,7 @@ module TSS
85
93
 
86
94
  shares_bytes_have_valid_indexes!(shares_bytes)
87
95
 
88
- if select_by == 'combinations'
96
+ if select_by == 'COMBINATIONS'
89
97
  share_combinations_mode_allowed!(hash_id)
90
98
  share_combinations_out_of_bounds!(shares, threshold)
91
99
 
@@ -107,7 +115,7 @@ module TSS
107
115
  # Return a Hash with the secret and metadata
108
116
  {
109
117
  hash: secret[:hash],
110
- hash_alg: secret[:hash_alg].to_s,
118
+ hash_alg: secret[:hash_alg],
111
119
  identifier: identifier,
112
120
  process_time: ((Time.now - start_processing_time)*1000).round(2),
113
121
  secret: Util.bytes_to_utf8(secret[:secret]),
@@ -122,12 +130,12 @@ module TSS
122
130
  # and validate it against any one-way hash that was embedded in the shares
123
131
  # along with the secret.
124
132
  #
125
- # @param hash_id [Integer] the ID of the one-way hash function to test with
126
- # @param shares_bytes [Array<Array>] the shares as Byte Arrays to be evaluated
127
- # @return [Array<Integer>] returns the secret as an Array of Bytes if it was recovered from the shares and validated
133
+ # @param hash_id the ID of the one-way hash function to test with
134
+ # @param shares_bytes the shares as Byte Arrays to be evaluated
135
+ # @return returns the secret as an Array of Bytes if it was recovered from the shares and validated
128
136
  # @raise [TSS::NoSecretError] if the secret was not able to be recovered (with no hash)
129
137
  # @raise [TSS::InvalidSecretHashError] if the secret was able to be recovered but the hash test failed
130
- Contract C::Int, C::ArrayOf[C::ArrayOf[C::Num]] => ({ :secret => C::ArrayOf[C::Num], :hash => C::Maybe[String], :hash_alg => C::Enum[:NONE, :SHA1, :SHA256] })
138
+ Contract C::Int, C::ArrayOf[C::ArrayOf[C::Num]] => ({ :secret => C::ArrayOf[C::Num], :hash => C::Maybe[String], :hash_alg => C::HashAlgArg })
131
139
  def extract_secret_from_shares!(hash_id, shares_bytes)
132
140
  secret = []
133
141
 
@@ -172,8 +180,9 @@ module TSS
172
180
 
173
181
  # Strip off leading padding chars ("\u001F", decimal 31)
174
182
  #
175
- # @param secret [Array<Integer>] the secret to be stripped
176
- # @return [Array<Integer>,nil] returns the secret, stripped of the leading padding char
183
+ # @param secret the secret to be stripped
184
+ # @return returns the secret, stripped of the leading padding char
185
+ # @raise [ParamContractError] if secret appears invalid
177
186
  Contract C::ArrayOf[C::Num] => C::Maybe[Array]
178
187
  def strip_left_pad(secret)
179
188
  secret.shift while secret.first == 31
@@ -181,8 +190,9 @@ module TSS
181
190
 
182
191
  # Do all of the shares match the pattern expected of human style shares?
183
192
  #
184
- # @param shares [Array<String>] the shares to be evaluated
185
- # @return [true,false] returns true if all shares match the patterns, false if not
193
+ # @param shares the shares to be evaluated
194
+ # @return returns true if all shares match the patterns, false if not
195
+ # @raise [ParamContractError] if shares appear invalid
186
196
  Contract C::ArrayOf[String] => C::Bool
187
197
  def all_shares_appear_human?(shares)
188
198
  shares.all? do |s|
@@ -194,9 +204,9 @@ module TSS
194
204
 
195
205
  # Convert an Array of human style shares to binary style
196
206
  #
197
- # @param shares [Array<String>] the shares to be converted
198
- # @return [Array<String>] returns an Array of String shares in binary octet String format
199
- # @raise [TSS::ArgumentError] if shares appear invalid
207
+ # @param shares the shares to be converted
208
+ # @return returns an Array of String shares in binary octet String format
209
+ # @raise [ParamContractError, TSS::ArgumentError] if shares appear invalid
200
210
  Contract C::ArrayOf[String] => C::ArrayOf[String]
201
211
  def convert_shares_human_to_binary(shares)
202
212
  shares.map do |s|
@@ -216,9 +226,9 @@ module TSS
216
226
 
217
227
  # Do all shares have a common Byte size? They are invalid if not.
218
228
  #
219
- # @param shares [Array<String>] the shares to be evaluated
220
- # @return [true] returns true if all shares have the same Byte size
221
- # @raise [TSS::ArgumentError] if shares appear invalid
229
+ # @param shares the shares to be evaluated
230
+ # @return returns true if all shares have the same Byte size
231
+ # @raise [ParamContractError, TSS::ArgumentError] if shares appear invalid
222
232
  Contract C::ArrayOf[String] => C::Bool
223
233
  def shares_have_same_bytesize!(shares)
224
234
  shares.each do |s|
@@ -231,9 +241,9 @@ module TSS
231
241
 
232
242
  # Do all shares have a valid header and match each other? They are invalid if not.
233
243
  #
234
- # @param shares [Array<String>] the shares to be evaluated
235
- # @return [true] returns true if all shares have the same header
236
- # @raise [TSS::ArgumentError] if shares appear invalid
244
+ # @param shares the shares to be evaluated
245
+ # @return returns true if all shares have the same header
246
+ # @raise [ParamContractError, TSS::ArgumentError] if shares appear invalid
237
247
  Contract C::ArrayOf[String] => C::Bool
238
248
  def shares_have_valid_headers!(shares)
239
249
  fh = Util.extract_share_header(shares.first)
@@ -253,9 +263,9 @@ module TSS
253
263
 
254
264
  # Do all shares have a the expected length? They are invalid if not.
255
265
  #
256
- # @param shares [Array<String>] the shares to be evaluated
257
- # @return [true] returns true if all shares have the same header
258
- # @raise [TSS::ArgumentError] if shares appear invalid
266
+ # @param shares the shares to be evaluated
267
+ # @return returns true if all shares have the same header
268
+ # @raise [ParamContractError, TSS::ArgumentError] if shares appear invalid
259
269
  Contract C::ArrayOf[String] => C::Bool
260
270
  def shares_have_expected_length!(shares)
261
271
  shares.each do |s|
@@ -268,9 +278,9 @@ module TSS
268
278
 
269
279
  # Were enough shares provided to meet the threshold? They are invalid if not.
270
280
  #
271
- # @param shares [Array<String>] the shares to be evaluated
272
- # @return [true] returns true if there are enough shares
273
- # @raise [TSS::ArgumentError] if shares appear invalid
281
+ # @param shares the shares to be evaluated
282
+ # @return returns true if there are enough shares
283
+ # @raise [ParamContractError, TSS::ArgumentError] if shares appear invalid
274
284
  Contract C::ArrayOf[String] => C::Bool
275
285
  def shares_meet_threshold_min!(shares)
276
286
  fh = Util.extract_share_header(shares.first)
@@ -283,8 +293,9 @@ module TSS
283
293
 
284
294
  # Were enough shares provided to meet the threshold? They are invalid if not.
285
295
  #
286
- # @param shares [Array<String>] the shares to be evaluated
287
- # @return [true] returns true if all tests pass
296
+ # @param shares the shares to be evaluated
297
+ # @return returns true if all tests pass
298
+ # @raise [ParamContractError] if shares appear invalid
288
299
  Contract C::ArrayOf[String] => C::Bool
289
300
  def validate_all_shares(shares)
290
301
  if shares_have_valid_headers!(shares) &&
@@ -299,9 +310,9 @@ module TSS
299
310
 
300
311
  # Do all the shares have a valid first-byte index? They are invalid if not.
301
312
  #
302
- # @param shares_bytes [Array<Array>] the shares as Byte Arrays to be evaluated
303
- # @return [true] returns true if there are enough shares
304
- # @raise [TSS::ArgumentError] if shares appear invalid
313
+ # @param shares_bytes the shares as Byte Arrays to be evaluated
314
+ # @return returns true if there are enough shares
315
+ # @raise [ParamContractError, TSS::ArgumentError] if shares bytes appear invalid
305
316
  Contract C::ArrayOf[C::ArrayOf[C::Num]] => C::Bool
306
317
  def shares_bytes_have_valid_indexes!(shares_bytes)
307
318
  u = shares_bytes.map do |s|
@@ -320,9 +331,9 @@ module TSS
320
331
  # Is it valid to use combinations mode? Only when there is an embedded non-zero
321
332
  # hash_id Integer to test the results against. Invalid if not.
322
333
  #
323
- # @param hash_id [Integer] the shares as Byte Arrays to be evaluated
324
- # @return [true] returns true if OK to use combinations mode
325
- # @raise [TSS::ArgumentError] if hash_id represents a non hashing type
334
+ # @param hash_id the shares as Byte Arrays to be evaluated
335
+ # @return returns true if OK to use combinations mode
336
+ # @raise [ParamContractError, TSS::ArgumentError] if hash_id represents a non hashing type
326
337
  Contract C::Int => C::Bool
327
338
  def share_combinations_mode_allowed!(hash_id)
328
339
  unless Hasher.codes_without_none.include?(hash_id)
@@ -340,16 +351,16 @@ module TSS
340
351
  # e.g. 255 total shares, with threshold of 128, results in # combinations of:
341
352
  # 2884329411724603169044874178931143443870105850987581016304218283632259375395
342
353
  #
343
- # @param shares [Array<String>] the shares to be evaluated
344
- # @param threshold [Integer] the threshold value set in the shares
345
- # @param max_combinations [Integer] the max (1_000_000) number of combinations allowed
346
- # @return [true] returns true if a reasonable number of combinations
347
- # @raise [TSS::ArgumentError] if the number of possible combinations is unreasonably high
354
+ # @param shares the shares to be evaluated
355
+ # @param threshold the threshold value set in the shares
356
+ # @param max_combinations the max (1_000_000) number of combinations allowed
357
+ # @return returns true if a reasonable number of combinations
358
+ # @raise [ParamContractError, TSS::ArgumentError] if args are invalid or the number of possible combinations is unreasonably high
348
359
  Contract C::ArrayOf[String], C::Int, C::Int => C::Bool
349
360
  def share_combinations_out_of_bounds!(shares, threshold, max_combinations = 1_000_000)
350
361
  combinations = Util.calc_combinations(shares.size, threshold)
351
362
  if combinations > max_combinations
352
- raise TSS::ArgumentError, "invalid options, too many combinations (#{Util.int_commas(combinations)})"
363
+ raise TSS::ArgumentError, "invalid options, too many combinations (#{combinations})"
353
364
  else
354
365
  return true
355
366
  end
@@ -1,10 +1,23 @@
1
- module TSS
1
+ module Contracts
2
2
 
3
3
  # Custom Contracts
4
4
  # See : https://egonschiele.github.io/contracts.ruby/
5
+
6
+ class ArrayOfShares
7
+ def self.valid? val
8
+ val.is_a?(Array) &&
9
+ val.length.between?(1,255) &&
10
+ Contracts::ArrayOf[String].valid?(val)
11
+ end
12
+
13
+ def self.to_s
14
+ 'An Array of split secret shares'
15
+ end
16
+ end
17
+
5
18
  class SecretArg
6
19
  def self.valid? val
7
- val.present? && val.is_a?(String) &&
20
+ val.is_a?(String) &&
8
21
  val.length.between?(1,65502) &&
9
22
  ['UTF-8', 'US-ASCII'].include?(val.encoding.name) &&
10
23
  val.slice(0) != "\u001F"
@@ -17,7 +30,6 @@ module TSS
17
30
 
18
31
  class ThresholdArg
19
32
  def self.valid? val
20
- val.present? &&
21
33
  val.is_a?(Integer) &&
22
34
  val.between?(1,255)
23
35
  end
@@ -29,7 +41,6 @@ module TSS
29
41
 
30
42
  class NumSharesArg
31
43
  def self.valid? val
32
- val.present? &&
33
44
  val.is_a?(Integer) &&
34
45
  val.between?(1,255)
35
46
  end
@@ -41,7 +52,6 @@ module TSS
41
52
 
42
53
  class IdentifierArg
43
54
  def self.valid? val
44
- val.present? &&
45
55
  val.is_a?(String) &&
46
56
  val.length.between?(1,16) &&
47
57
  val =~ /^[a-zA-Z0-9\-\_\.]*$/i
@@ -52,9 +62,38 @@ module TSS
52
62
  end
53
63
  end
54
64
 
65
+ class HashAlgArg
66
+ def self.valid? val
67
+ Contracts::Enum['NONE', 'SHA1', 'SHA256'].valid?(val)
68
+ end
69
+
70
+ def self.to_s
71
+ 'must be a uppercase String specifying the hash algorithm to use [NONE, SHA1, SHA256].'
72
+ end
73
+ end
74
+
75
+ class FormatArg
76
+ def self.valid? val
77
+ Contracts::Enum['BINARY', 'HUMAN'].valid?(val)
78
+ end
79
+
80
+ def self.to_s
81
+ 'must be a uppercase String specifying the desired String share format [BINARY, HUMAN].'
82
+ end
83
+ end
84
+
85
+ class SelectByArg
86
+ def self.valid? val
87
+ Contracts::Enum['FIRST', 'SAMPLE', 'COMBINATIONS'].valid?(val)
88
+ end
89
+
90
+ def self.to_s
91
+ 'must be a uppercase String specifying the desired way to sample shares provided [FIRST, SAMPLE, COMBINATIONS].'
92
+ end
93
+ end
94
+
55
95
  class PadBlocksizeArg
56
96
  def self.valid? val
57
- val.present? &&
58
97
  val.is_a?(Integer) &&
59
98
  val.between?(0,255)
60
99
  end
data/lib/tss/hasher.rb CHANGED
@@ -5,15 +5,15 @@ module TSS
5
5
  include Contracts::Core
6
6
  C = Contracts
7
7
 
8
- HASHES = { NONE: { code: 0, bytesize: 0, hasher: nil },
9
- SHA1: { code: 1, bytesize: 20, hasher: Digest::SHA1 },
10
- SHA256: { code: 2, bytesize: 32, hasher: Digest::SHA256 }}.freeze
8
+ HASHES = { 'NONE' => { code: 0, bytesize: 0, hasher: nil },
9
+ 'SHA1' => { code: 1, bytesize: 20, hasher: Digest::SHA1 },
10
+ 'SHA256' => { code: 2, bytesize: 32, hasher: Digest::SHA256 }}.freeze
11
11
 
12
12
  # Lookup the Symbol key for a Hash with the code.
13
13
  #
14
- # @param code [Integer] the hash code to convert to a Symbol key
15
- # @return [Symbol,nil] the hash key Symbol or nil if not found
16
- Contract C::Int => C::Maybe[Symbol]
14
+ # @param code the hash code to convert to a Symbol key
15
+ # @return the hash key String or nil if not found
16
+ Contract C::Int => C::Maybe[C::HashAlgArg]
17
17
  def self.key_from_code(code)
18
18
  return nil unless Hasher.codes.include?(code)
19
19
  HASHES.each do |k, v|
@@ -23,16 +23,16 @@ module TSS
23
23
 
24
24
  # Lookup the hash code for the hash matching hash_key.
25
25
  #
26
- # @param hash_key [Symbol, String] the hash key to convert to an Integer code
27
- # @return [Integer] the hash key code
28
- Contract C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]] => C::Maybe[C::Int]
26
+ # @param hash_key the hash key to convert to an Integer code
27
+ # @return the hash key code
28
+ Contract C::HashAlgArg => C::Maybe[C::Int]
29
29
  def self.code(hash_key)
30
- HASHES[hash_key.upcase.to_sym][:code]
30
+ HASHES[hash_key][:code]
31
31
  end
32
32
 
33
33
  # Lookup all valid hash codes, including NONE.
34
34
  #
35
- # @return [Array<Integer>] all hash codes including NONE
35
+ # @return all hash codes including NONE
36
36
  Contract C::None => C::ArrayOf[C::Int]
37
37
  def self.codes
38
38
  HASHES.map do |_k, v|
@@ -42,7 +42,7 @@ module TSS
42
42
 
43
43
  # All valid hash codes that actually do hashing, excluding NONE.
44
44
  #
45
- # @return [Array<Integer>] all hash codes excluding NONE
45
+ # @return all hash codes excluding NONE
46
46
  Contract C::None => C::ArrayOf[C::Int]
47
47
  def self.codes_without_none
48
48
  HASHES.map do |_k, v|
@@ -52,49 +52,46 @@ module TSS
52
52
 
53
53
  # Lookup the size in Bytes for a specific hash_key.
54
54
  #
55
- # @param hash_key [Symbol, String] the hash key to lookup
56
- # @return [Integer] the size in Bytes for a specific hash_key
57
- Contract C::Or[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]] => C::Int
55
+ # @param hash_key the hash key to lookup
56
+ # @return the size in Bytes for a specific hash_key
57
+ Contract C::HashAlgArg => C::Int
58
58
  def self.bytesize(hash_key)
59
- HASHES[hash_key.to_sym][:bytesize]
59
+ HASHES[hash_key][:bytesize]
60
60
  end
61
61
 
62
62
  # Return a hexdigest hash for a String using hash_key hash algorithm.
63
- # Returns '' if hash_key == :NONE
63
+ # Returns '' if hash_key == 'NONE'
64
64
  #
65
- # @param hash_key [Symbol, String] the hash key to use to hash a String
66
- # @param str [String] the String to hash
67
- # @return [String] the hex digest for str
68
- Contract C::Or[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]], String => String
65
+ # @param hash_key the hash key to use to hash a String
66
+ # @param str the String to hash
67
+ # @return the hex digest for str
68
+ Contract C::HashAlgArg, String => String
69
69
  def self.hex_string(hash_key, str)
70
- hash_key = hash_key.upcase.to_sym
71
- return '' if hash_key == :NONE
70
+ return '' if hash_key == 'NONE'
72
71
  HASHES[hash_key][:hasher].send(:hexdigest, str)
73
72
  end
74
73
 
75
74
  # Return a Byte String hash for a String using hash_key hash algorithm.
76
- # Returns '' if hash_key == :NONE
75
+ # Returns '' if hash_key == 'NONE'
77
76
  #
78
- # @param hash_key [Symbol, String] the hash key to use to hash a String
79
- # @param str [String] the String to hash
80
- # @return [String] the Byte String digest for str
81
- Contract C::Or[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]], String => String
77
+ # @param hash_key the hash key to use to hash a String
78
+ # @param str the String to hash
79
+ # @return the Byte String digest for str
80
+ Contract C::HashAlgArg, String => String
82
81
  def self.byte_string(hash_key, str)
83
- hash_key = hash_key.upcase.to_sym
84
- return '' if hash_key == :NONE
82
+ return '' if hash_key == 'NONE'
85
83
  HASHES[hash_key][:hasher].send(:digest, str)
86
84
  end
87
85
 
88
86
  # Return a Byte Array hash for a String using hash_key hash algorithm.
89
- # Returns [] if hash_key == :NONE
87
+ # Returns [] if hash_key == 'NONE'
90
88
  #
91
- # @param hash_key [Symbol, String] the hash key to use to hash a String
92
- # @param str [String] the String to hash
93
- # @return [Array<Integer>] the Byte Array digest for str
94
- Contract C::Or[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]], String => C::ArrayOf[C::Int]
89
+ # @param hash_key the hash key to use to hash a String
90
+ # @param str the String to hash
91
+ # @return the Byte Array digest for str
92
+ Contract C::HashAlgArg, String => C::ArrayOf[C::Int]
95
93
  def self.byte_array(hash_key, str)
96
- hash_key = hash_key.upcase.to_sym
97
- return [] if hash_key == :NONE
94
+ return [] if hash_key == 'NONE'
98
95
  HASHES[hash_key][:hasher].send(:digest, str).unpack('C*')
99
96
  end
100
97
  end
data/lib/tss/splitter.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  module TSS
2
- # Splitter has responsibility for splitting a secret into an Array of String shares.
2
+ # Warning, you probably don't want to use this directly. Instead
3
+ # see the TSS module.
4
+ #
5
+ # TSS::Splitter has responsibility for splitting a secret into an Array of String shares.
3
6
  class Splitter
4
7
  include Contracts::Core
5
8
  include Util
@@ -8,14 +11,14 @@ module TSS
8
11
 
9
12
  attr_reader :secret, :threshold, :num_shares, :identifier, :hash_alg, :format, :pad_blocksize
10
13
 
11
- Contract ({ :secret => TSS::SecretArg, :threshold => C::Maybe[TSS::ThresholdArg], :num_shares => C::Maybe[TSS::NumSharesArg], :identifier => C::Maybe[TSS::IdentifierArg], :hash_alg => C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256']], :format => C::Maybe[C::Enum['binary', 'human']], :pad_blocksize => C::Maybe[TSS::PadBlocksizeArg] }) => C::Any
14
+ Contract ({ :secret => C::SecretArg, :threshold => C::Maybe[C::ThresholdArg], :num_shares => C::Maybe[C::NumSharesArg], :identifier => C::Maybe[C::IdentifierArg], :hash_alg => C::Maybe[C::HashAlgArg], :format => C::Maybe[C::FormatArg], :pad_blocksize => C::Maybe[C::PadBlocksizeArg] }) => C::Any
12
15
  def initialize(opts = {})
13
16
  @secret = opts.fetch(:secret)
14
17
  @threshold = opts.fetch(:threshold, 3)
15
18
  @num_shares = opts.fetch(:num_shares, 5)
16
19
  @identifier = opts.fetch(:identifier, SecureRandom.hex(8))
17
20
  @hash_alg = opts.fetch(:hash_alg, 'SHA256')
18
- @format = opts.fetch(:format, 'human')
21
+ @format = opts.fetch(:format, 'HUMAN')
19
22
  @pad_blocksize = opts.fetch(:pad_blocksize, 0)
20
23
  end
21
24
 
@@ -26,6 +29,9 @@ module TSS
26
29
  'n', :share_len
27
30
  ])
28
31
 
32
+ # Warning, you probably don't want to use this directly. Instead
33
+ # see the TSS module.
34
+ #
29
35
  # To split a secret into a set of shares, the following
30
36
  # procedure, or any equivalent method, is used:
31
37
  #
@@ -49,7 +55,9 @@ module TSS
49
55
  # If the operation can not be completed successfully, then an error
50
56
  # code should be returned.
51
57
  #
52
- Contract C::None => C::ArrayOf[String]
58
+ # @return an Array of formatted String shares
59
+ # @raise [ParamContractError, TSS::ArgumentError] if the options Types or Values are invalid
60
+ Contract C::None => C::ArrayOfShares
53
61
  def split
54
62
  num_shares_not_less_than_threshold!(threshold, num_shares)
55
63
 
@@ -115,7 +123,7 @@ module TSS
115
123
  binary = (header + s.pack('C*')).force_encoding('ASCII-8BIT')
116
124
  # join with URL safe '~'
117
125
  human = ['tss', 'v1', identifier, threshold, Base64.urlsafe_encode64(binary)].join('~')
118
- format == 'binary' ? binary : human
126
+ format == 'BINARY' ? binary : human
119
127
  end
120
128
 
121
129
  return shares
@@ -125,11 +133,11 @@ module TSS
125
133
 
126
134
  # The num_shares must be greater than or equal to the threshold or it is invalid.
127
135
  #
128
- # @param threshold [Integer] the threshold value
129
- # @param num_shares [Integer] the num_shares value
130
- # @return [true] returns true if num_shares is >= threshold
131
- # @raise [TSS::ArgumentError] if invalid
132
- Contract TSS::ThresholdArg, TSS::NumSharesArg => C::Bool
136
+ # @param threshold the threshold value
137
+ # @param num_shares the num_shares value
138
+ # @return returns true if num_shares is >= threshold
139
+ # @raise [ParamContractError, TSS::ArgumentError] if invalid
140
+ Contract C::ThresholdArg, C::NumSharesArg => C::Bool
133
141
  def num_shares_not_less_than_threshold!(threshold, num_shares)
134
142
  if num_shares < threshold
135
143
  raise TSS::ArgumentError, "invalid num_shares, must be >= threshold (#{threshold})"
@@ -141,9 +149,9 @@ module TSS
141
149
  # The total Byte size of the secret, including padding and hash, must be
142
150
  # less than the max allowed Byte size or it is invalid.
143
151
  #
144
- # @param secret_bytes [Array<Integer>] the Byte Array containing the secret
145
- # @return [true] returns true if num_shares is >= threshold
146
- # @raise [TSS::ArgumentError] if invalid
152
+ # @param secret_bytes the Byte Array containing the secret
153
+ # @return returns true if num_shares is >= threshold
154
+ # @raise [ParamContractError, TSS::ArgumentError] if invalid
147
155
  Contract C::ArrayOf[C::Int] => C::Bool
148
156
  def secret_bytes_is_smaller_than_max_size!(secret_bytes)
149
157
  if secret_bytes.size >= 65_535
@@ -155,12 +163,13 @@ module TSS
155
163
 
156
164
  # Construct a binary share header from its constituent parts.
157
165
  #
158
- # @param identifier [String] the unique identifier String
159
- # @param hash_alg [String] the hash algorithm String
160
- # @param threshold [Integer] the threshold value
161
- # @param share_len [Integer] the length of the share in Bytes
162
- # @return [String] returns an octet String of Bytes containing the binary header
163
- Contract TSS::IdentifierArg, C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256']], TSS::ThresholdArg, C::Int => String
166
+ # @param identifier the unique identifier String
167
+ # @param hash_alg the hash algorithm String
168
+ # @param threshold the threshold value
169
+ # @param share_len the length of the share in Bytes
170
+ # @return returns an octet String of Bytes containing the binary header
171
+ # @raise [ParamContractError] if invalid
172
+ Contract C::IdentifierArg, C::HashAlgArg, C::ThresholdArg, C::Int => String
164
173
  def share_header(identifier, hash_alg, threshold, share_len)
165
174
  SHARE_HEADER_STRUCT.encode(identifier: identifier,
166
175
  hash_id: Hasher.code(hash_alg),
data/lib/tss/tss.rb CHANGED
@@ -60,7 +60,7 @@ module TSS
60
60
  # to `SHA256`. The use of `NONE` is discouraged as it does not allow those
61
61
  # who are recombining the shares to verify if they have in fact recovered
62
62
  # the correct secret.
63
- # @option opts [String] :format ('binary') the format of the String share output, 'binary' or 'human'
63
+ # @option opts [String] :format ('BINARY') the format of the String share output, 'BINARY' or 'HUMAN'
64
64
  # @option opts [Integer] :pad_blocksize (0) An integer representing the nearest multiple of Bytes
65
65
  # to left pad the secret to. Defaults to not adding any padding (0). Padding
66
66
  # is done with the "\u001F" character (decimal 31 in a Byte Array).
@@ -75,9 +75,9 @@ module TSS
75
75
  # place. You would also need to manually remove the padding you added after
76
76
  # the share is recombined, or instruct recipients to ignore it.
77
77
  #
78
- # @return [Array<String>] an Array of String shares
78
+ # @return an Array of formatted String shares
79
79
  # @raise [ParamContractError, TSS::ArgumentError] if the options Types or Values are invalid
80
- Contract ({ :secret => TSS::SecretArg, :threshold => C::Maybe[TSS::ThresholdArg], :num_shares => C::Maybe[TSS::NumSharesArg], :identifier => C::Maybe[TSS::IdentifierArg], :hash_alg => C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256']], :format => C::Maybe[C::Enum['binary', 'human']], :pad_blocksize => C::Maybe[TSS::PadBlocksizeArg] }) => C::ArrayOf[String]
80
+ Contract ({ :secret => C::SecretArg, :threshold => C::Maybe[C::ThresholdArg], :num_shares => C::Maybe[C::NumSharesArg], :identifier => C::Maybe[C::IdentifierArg], :hash_alg => C::Maybe[C::HashAlgArg], :format => C::Maybe[C::FormatArg], :pad_blocksize => C::Maybe[C::PadBlocksizeArg] }) => C::ArrayOfShares
81
81
  def self.split(opts)
82
82
  TSS::Splitter.new(opts).split
83
83
  end
@@ -88,26 +88,26 @@ module TSS
88
88
  #
89
89
  # @param [Hash] opts the options to create a message with.
90
90
  # @option opts [Array<String>] :shares an Array of String shares to try to recombine into a secret
91
- # @option opts [String] :select_by ('first') the method to use for selecting
91
+ # @option opts [String] :select_by ('FIRST') the method to use for selecting
92
92
  # shares from the Array if more then threshold shares are provided. Can be
93
- # 'first', 'sample', or 'combinations'.
93
+ # upper case 'FIRST', 'SAMPLE', or 'COMBINATIONS'.
94
94
  #
95
95
  # If the number of shares provided as input to the secret
96
96
  # reconstruction operation is greater than the threshold M, then M
97
97
  # of those shares are selected for use in the operation. The method
98
98
  # used to select the shares can be chosen using the following values:
99
99
  #
100
- # `first` : If X shares are required by the threshold and more than X
100
+ # `FIRST` : If X shares are required by the threshold and more than X
101
101
  # shares are provided, then the first X shares in the Array of shares provided
102
102
  # will be used. All others will be discarded and the operation will fail if
103
103
  # those selected shares cannot recreate the secret.
104
104
  #
105
- # `sample` : If X shares are required by the threshold and more than X
105
+ # `SAMPLE` : If X shares are required by the threshold and more than X
106
106
  # shares are provided, then X shares will be randomly selected from the Array
107
107
  # of shares provided. All others will be discarded and the operation will
108
108
  # fail if those selected shares cannot recreate the secret.
109
109
  #
110
- # `combinations` : If X shares are required, and more than X shares are
110
+ # `COMBINATIONS` : If X shares are required, and more than X shares are
111
111
  # provided, then all possible combinations of the threshold number of shares
112
112
  # will be tried to see if the secret can be recreated.
113
113
  # This flexibility comes with a cost. All combinations of `threshold` shares
@@ -120,11 +120,11 @@ module TSS
120
120
  # of combinations will be too large then the an Exception will be raised before
121
121
  # processing has started.
122
122
  #
123
- # @return [Hash] a Hash containing the ':secret' and other metadata
123
+ # @return a Hash containing the now combined secret and other metadata
124
124
  # @raise [TSS::NoSecretError] if the secret cannot be re-created from the shares provided
125
125
  # @raise [TSS::InvalidSecretHashError] if the embedded hash of the secret does not match the hash of the recreated secret
126
126
  # @raise [ParamContractError, TSS::ArgumentError] if the options Types or Values are invalid
127
- Contract ({ :shares => C::ArrayOf[String], :select_by => C::Maybe[C::Enum['first', 'sample', 'combinations']] }) => ({ :hash => C::Maybe[String], :hash_alg => C::Enum['NONE', 'SHA1', 'SHA256'], :identifier => TSS::IdentifierArg, :process_time => C::Num, :secret => TSS::SecretArg, :threshold => TSS::ThresholdArg})
127
+ Contract ({ :shares => C::ArrayOfShares, :select_by => C::Maybe[C::SelectByArg] }) => ({ :hash => C::Maybe[String], :hash_alg => C::HashAlgArg, :identifier => C::IdentifierArg, :process_time => C::Num, :secret => C::SecretArg, :threshold => C::ThresholdArg})
128
128
  def self.combine(opts)
129
129
  TSS::Combiner.new(opts).combine
130
130
  end
data/lib/tss/util.rb CHANGED
@@ -217,8 +217,8 @@ module TSS
217
217
 
218
218
  # Convert a UTF-8 String to an Array of Bytes
219
219
  #
220
- # @param str [String] a UTF-8 String to convert
221
- # @return [Array<Integer>] an Array of Integer Bytes
220
+ # @param str a UTF-8 String to convert
221
+ # @return an Array of Integer Bytes
222
222
  Contract String => C::ArrayOf[C::Int]
223
223
  def self.utf8_to_bytes(str)
224
224
  str.bytes.to_a
@@ -226,8 +226,8 @@ module TSS
226
226
 
227
227
  # Convert an Array of Bytes to a UTF-8 String
228
228
  #
229
- # @param bytes [Array<Integer>] an Array of Bytes to convert
230
- # @return [String] a UTF-8 String
229
+ # @param bytes an Array of Bytes to convert
230
+ # @return a UTF-8 String
231
231
  Contract C::ArrayOf[C::Int] => String
232
232
  def self.bytes_to_utf8(bytes)
233
233
  bytes.pack('C*').force_encoding('utf-8')
@@ -235,8 +235,8 @@ module TSS
235
235
 
236
236
  # Convert an Array of Bytes to a hex String
237
237
  #
238
- # @param bytes [Array<Integer>] an Array of Bytes to convert
239
- # @return [String] a hex String
238
+ # @param bytes an Array of Bytes to convert
239
+ # @return a hex String
240
240
  Contract C::ArrayOf[C::Int] => String
241
241
  def self.bytes_to_hex(bytes)
242
242
  hex = ''
@@ -246,8 +246,8 @@ module TSS
246
246
 
247
247
  # Convert a hex String to an Array of Bytes
248
248
  #
249
- # @param str [String] a hex String to convert
250
- # @return [Array<Integer>] an Array of Integer Bytes
249
+ # @param str a hex String to convert
250
+ # @return an Array of Integer Bytes
251
251
  Contract String => C::ArrayOf[C::Int]
252
252
  def self.hex_to_bytes(str)
253
253
  [str].pack('H*').unpack('C*')
@@ -255,8 +255,8 @@ module TSS
255
255
 
256
256
  # Convert a hex String to a UTF-8 String
257
257
  #
258
- # @param hex [String] a hex String to convert
259
- # @return [String] a UTF-8 String
258
+ # @param hex a hex String to convert
259
+ # @return a UTF-8 String
260
260
  Contract String => String
261
261
  def self.hex_to_utf8(hex)
262
262
  bytes_to_utf8(hex_to_bytes(hex))
@@ -264,8 +264,8 @@ module TSS
264
264
 
265
265
  # Convert a UTF-8 String to a hex String
266
266
  #
267
- # @param str [String] a UTF-8 String to convert
268
- # @return [String] a hex String
267
+ # @param str a UTF-8 String to convert
268
+ # @return a hex String
269
269
  Contract String => String
270
270
  def self.utf8_to_hex(str)
271
271
  bytes_to_hex(utf8_to_bytes(str))
@@ -273,10 +273,10 @@ module TSS
273
273
 
274
274
  # Left pad a String with pad_char in multiples of byte_multiple
275
275
  #
276
- # @param byte_multiple [Integer] pad in blocks of this size
277
- # @param input_string [String] the String to pad
278
- # @param pad_char [String] the String to pad with
279
- # @return [String] a padded String
276
+ # @param byte_multiple pad in blocks of this size
277
+ # @param input_string the String to pad
278
+ # @param pad_char the String to pad with
279
+ # @return a padded String
280
280
  Contract C::Int, String, String => String
281
281
  def self.left_pad(byte_multiple, input_string, pad_char = "\u001F")
282
282
  return input_string if byte_multiple == 0
@@ -290,30 +290,33 @@ module TSS
290
290
  # https://github.com/rack/rack/blob/master/lib/rack/utils.rb
291
291
  #
292
292
  # NOTE: the values compared should be of fixed length, such as strings
293
- # that have already been processed by HMAC. This should not be used
293
+ # that have already been hashed. This should not be used
294
294
  # on variable length plaintext strings because it could leak length info
295
- # via timing attacks. The user provided value should always be passed
296
- # in as the second parameter so as not to leak info about the secret.
295
+ # via timing attacks.
297
296
  #
298
- # @param a [String] the private value
299
- # @param b [String] the user provided value
300
- # @return [true, false] whether the strings match or not
297
+ # The user provided value should always be passed in as the second
298
+ # parameter so as not to leak info about the secret.
299
+ #
300
+ # @param a the private value
301
+ # @param b the user provided value
302
+ # @return whether the strings match or not
301
303
  Contract String, String => C::Bool
302
304
  def self.secure_compare(a, b)
303
305
  return false unless a.bytesize == b.bytesize
306
+ ah = Digest::SHA256.hexdigest(a)
307
+ bh = Digest::SHA256.hexdigest(b)
304
308
 
305
- l = a.unpack('C*')
306
-
309
+ l = ah.unpack('C*')
307
310
  r, i = 0, -1
308
- b.each_byte { |v| r |= v ^ l[i+=1] }
311
+ bh.each_byte { |v| r |= v ^ l[i+=1] }
309
312
  r == 0
310
313
  end
311
314
 
312
315
  # Extract the header data from a binary share.
313
316
  # Extra "\x00" padding in the identifier will be removed.
314
317
  #
315
- # @param share [String] a binary octet share
316
- # @return [Hash] header attributes
318
+ # @param share a binary octet share
319
+ # @return header attributes
317
320
  Contract String => Hash
318
321
  def self.extract_share_header(share)
319
322
  h = Splitter::SHARE_HEADER_STRUCT.decode(share)
@@ -323,8 +326,8 @@ module TSS
323
326
 
324
327
  # Calculate the factorial for an Integer.
325
328
  #
326
- # @param n [Integer] the Integer to calculate for
327
- # @return [Integer] the factorial of n
329
+ # @param n the Integer to calculate for
330
+ # @return the factorial of n
328
331
  Contract C::Int => C::Int
329
332
  def self.factorial(n)
330
333
  (1..n).reduce(:*) || 1
@@ -340,22 +343,12 @@ module TSS
340
343
  # * http://chriscontinanza.com/2010/10/29/Array.html
341
344
  # * http://stackoverflow.com/questions/2434503/ruby-factorial-function
342
345
  #
343
- # @param n [Integer] the total number of shares
344
- # @param r [Integer] the threshold number of shares
345
- # @return [Integer] the number of possible combinations
346
+ # @param n the total number of shares
347
+ # @param r the threshold number of shares
348
+ # @return the number of possible combinations
346
349
  Contract C::Int, C::Int => C::Int
347
350
  def self.calc_combinations(n, r)
348
351
  factorial(n) / (factorial(r) * factorial(n - r))
349
352
  end
350
-
351
- # Converts an Integer into a delimiter separated String.
352
- #
353
- # @param n [Integer] an Integer to convert
354
- # @param delimiter [String] the String to delimit n in three Integer groups
355
- # @return [String] the object converted into a comma separated String.
356
- Contract C::Int, String => String
357
- def self.int_commas(n, delimiter = ',')
358
- n.to_s.reverse.gsub(%r{([0-9]{3}(?=([0-9])))}, "\\1#{delimiter}").reverse
359
- end
360
353
  end
361
354
  end
data/lib/tss/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TSS
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
data/tss.gemspec CHANGED
@@ -60,4 +60,5 @@ Gem::Specification.new do |spec|
60
60
  spec.add_development_dependency 'coveralls', '~> 0.8'
61
61
  spec.add_development_dependency 'coco', '~> 0.14'
62
62
  spec.add_development_dependency 'wwtd', '~> 1.3'
63
+ spec.add_development_dependency 'yard-contracts', '~> 0.1'
63
64
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Glenn Rempe
@@ -30,7 +30,7 @@ cert_chain:
30
30
  zieXiXZSAojfFx9g91fKdIrlPbInHU/BaCxXSLBwvOM0drE+c2ue9X8gB55XAhzX
31
31
  37oBiw==
32
32
  -----END CERTIFICATE-----
33
- date: 2016-09-24 00:00:00.000000000 Z
33
+ date: 2016-09-25 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: sysrandom
@@ -186,6 +186,20 @@ dependencies:
186
186
  - - "~>"
187
187
  - !ruby/object:Gem::Version
188
188
  version: '1.3'
189
+ - !ruby/object:Gem::Dependency
190
+ name: yard-contracts
191
+ requirement: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - "~>"
194
+ - !ruby/object:Gem::Version
195
+ version: '0.1'
196
+ type: :development
197
+ prerelease: false
198
+ version_requirements: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - "~>"
201
+ - !ruby/object:Gem::Version
202
+ version: '0.1'
189
203
  description: |2
190
204
  Threshold Secret Sharing (TSS) provides a way to generate N shares
191
205
  from a value, so that any M of those shares can be used to
metadata.gz.sig CHANGED
@@ -1,2 +1,4 @@
1
- /<���MeK+T���'��2r�|Z��� �4��rdm��KO8�Mz��/�b,�)>u
2
- hW_�<����hu;��s��܂��R��r1+b�����>t���s雝X'�~�y����l�K{3e�fs���\0q�J��I?c @�XIu��a:�뻭uR����Q��L�7m/���q���0;�'}]Ѵ�R��ij��W/I=��uCh�(��H���ԇ^�X[��~��´��h�:��E_���^).,l
1
+ H��P��
2
+ jT�*�O��0��k"��~� ���{9BO��pf_�ݤCKf���
3
+ ����f�#}�T�$߽h<����B,A�8�2H���
4
+ V���S}ͷ[�Q4���,V������/����t�"#I� ��3ދ��΢�,����7��p�/SqŸ�UƦI0�Q�%8y&;�j�F4ؐң�y@��,;>O��Z��٨3xmO\���cԟ'�3��ӓ8�jC�X�9���. �^0�����B��3�u����