tss 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4a6856a5740bb924091f7d3c11709a0ca5a85f8b
4
- data.tar.gz: 238fd08ac664b9518cfe5923261294808be59955
3
+ metadata.gz: e448b355558f4f493400bac55a51fd14a8c56bf3
4
+ data.tar.gz: 537024e64cf91c9394784d76d9df61347add2041
5
5
  SHA512:
6
- metadata.gz: d651a525d7b9c8869c720df9178b48b9c1502f5f2e3e1cdf160dcd68a9865f0534fa01bf6ef28831bcedb72a3bd886c536e74466f9d4a707f3d361e2da9bc411
7
- data.tar.gz: 8e1b706a13ef836f61ac5b94edae245e3e2578a80f736f13c146dbdab0efe43eed5d01f4a6983fa8fd8434909bf7fa1140f2ddce23d47fe375fa81bae1413c5a
6
+ metadata.gz: b69d52a5340fda8078e46e1ed95dc4c5a6e4ffbb54b5ac0295dce9fc2633decacf61a7616dff3c838772e3bb9e97be110d6a819a07fbf04d7f4ef0e8cfac9491
7
+ data.tar.gz: 5cc4fa4acc4e920dda21772432512da25b46bf9a16ac960496e0eb8cd0c0b2a3b5da979e4863e5571d0dcaa3962202ee9ec8bc3c98eb589b05ef8e8f5e653a0b
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.3.0 (9/24/2016)
4
+
5
+ * Breaking change, identifier cannot be an empty string
6
+ * Much greater coverage of functions with Contracts
7
+ * Related documentation updates
8
+
3
9
  ## v0.2.0 (9/23/2016)
4
10
 
5
11
  * clone the shares object passed to TSS.combine to prevent modification
data/README.md CHANGED
@@ -57,8 +57,8 @@ command line or Ruby, with the default `3 out of 5` secret sharing.
57
57
  ### Command Line Interface (CLI)
58
58
 
59
59
  ```sh
60
- $ echo 'secret unicode characters ½ ♥ 💩' | bundle exec bin/tss split -O /tmp/shares.txt
61
- $ bundle exec bin/tss combine -I /tmp/shares.txt
60
+ $ echo 'secret unicode characters ½ ♥ 💩' | tss split -O /tmp/shares.txt
61
+ $ tss combine -I /tmp/shares.txt
62
62
 
63
63
  RECOVERED SECRET METADATA
64
64
  *************************
@@ -253,7 +253,7 @@ cautioned that this method may leave command history which may contain your
253
253
  secret.
254
254
 
255
255
  ```text
256
- echo 'a secret' | bundle exec bin/tss split -O /tmp/shares.txt
256
+ echo 'a secret' | tss split -O /tmp/shares.txt
257
257
  ```
258
258
 
259
259
  **Example : `--input-file`**
@@ -263,7 +263,7 @@ cautioned that storing the secret on a filesystem may expose you to certain
263
263
  attacks since it can be hard to fully erase files once written.
264
264
 
265
265
  ```text
266
- $ bundle exec bin/tss split -I /tmp/secret.txt
266
+ $ tss split -I /tmp/secret.txt
267
267
  tss~v1~3f784d9d04dff796~3~M2Y3ODRkOWQwNGRmZjc5NgIDACsBvAVAo1dH3QrsYl0S30j2VVTu6dZLRe9EEk6zLtPTwZNYk-t1e4SAA41D
268
268
  tss~v1~3f784d9d04dff796~3~M2Y3ODRkOWQwNGRmZjc5NgIDACsCMfy6YlI-CQrd-l93Xtw8YqOWSP5ToolKTaZyO2fPYzceaaVmB30ycdVr
269
269
  tss~v1~3f784d9d04dff796~3~M2Y3ODRkOWQwNGRmZjc5NgIDACsD4IDasmAapmVFkuYPMvuavCtXiJgPZsaBHglGm4FU_U2rGZzfapRk-ZNN
@@ -279,7 +279,7 @@ in the command shell history or in a file on a filesystem that is hard to
279
279
  erase securely. It does not protect you from simple keylogger attacks though.
280
280
 
281
281
  ```text
282
- $ bundle exec bin/tss split
282
+ $ tss split
283
283
  Enter your secret, enter a dot (.) on a line by itself to finish :
284
284
  secret > the vault password is:
285
285
  secret > V0ulT!
@@ -306,7 +306,7 @@ Here are some simple examples of using each:
306
306
  **Example : `STDIN`**
307
307
 
308
308
  ```text
309
- $ echo 'a secret' | bundle exec bin/tss split | bundle exec bin/tss combine
309
+ $ echo 'a secret' | tss split | tss combine
310
310
 
311
311
  RECOVERED SECRET METADATA
312
312
  *************************
@@ -322,8 +322,8 @@ a secret
322
322
  **Example : `--input-file`**
323
323
 
324
324
  ```text
325
- $ echo 'a secret' | bundle exec bin/tss split -O /tmp/shares.txt
326
- $ bundle exec bin/tss combine -I /tmp/shares.txt
325
+ $ echo 'a secret' | tss split -O /tmp/shares.txt
326
+ $ tss combine -I /tmp/shares.txt
327
327
 
328
328
  RECOVERED SECRET METADATA
329
329
  *************************
@@ -351,7 +351,7 @@ tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoDsnUaZf94pMArKZL7jmavzk9Qa6
351
351
  tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoETCWwSGLHcutzGCLb1yOkH7i6MF7j2b0wr0ZsUh6LuBmx6FkN2MbTkTc=
352
352
  tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoFazXEwgGBilMwY7k7DtDR9qqG7mgigMJLmIVB70eAULfQJ89xbXbONF4=
353
353
 
354
- $ bundle exec bin/tss combine
354
+ $ tss combine
355
355
  Enter shares, one per line, and a dot (.) on a line by itself to finish :
356
356
  share> tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoBRjAH7wA0ncxJr3GliBqKxedf3bGaQH6AscuLqxtrKxxtxuC7A7a5Sqk=
357
357
  share> tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoClWVu75w-XHhoUgkbV5XaJ11stZCB5Z8HTArpv4QsIYDBpNO9DHQTbQ4=
@@ -402,14 +402,13 @@ between `1-255` inclusive. The `num_shares` value must be
402
402
  greater-than-or-equal-to the `threshold` value. If you don't pass in
403
403
  these options they will be set to `threshold = 3` and `num_shares = 5` by default.
404
404
 
405
- The `identifier` is a `0-16` Byte String that will be embedded in
405
+ The `identifier` is a `1-16` Byte String that will be embedded in
406
406
  each output share and should uniquely identify a secret. All shares output
407
407
  from the same secret splitting operation will have the same `identifier`. This
408
408
  value can be retrieved easily from a share header and should be assumed to be
409
409
  known to shareholders. Nothing that leaks information about the secret should
410
410
  be used as an `identifier`. If an `identifier` is not set, it will default
411
- to the output of `SecureRandom.hex(8)` which is 8 random hex bytes and
412
- 16 characters long.
411
+ to the output of `SecureRandom.hex(8)` which is 8 random hex bytes (16 characters).
413
412
 
414
413
  The `hash_alg` is a String that represents which cryptographic one-way
415
414
  hash function should be embedded in shares. The hash is used to verify
data/lib/tss/combiner.rb CHANGED
@@ -53,6 +53,7 @@ module TSS
53
53
  # The output string is returned (along with some metadata).
54
54
  #
55
55
  # 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})
56
57
  def combine
57
58
  # unwrap 'human' shares into binary shares
58
59
  if all_shares_appear_human?(shares)
@@ -287,9 +288,9 @@ module TSS
287
288
  Contract C::ArrayOf[String] => C::Bool
288
289
  def validate_all_shares(shares)
289
290
  if shares_have_valid_headers!(shares) &&
290
- shares_have_same_bytesize!(shares) &&
291
- shares_have_expected_length!(shares) &&
292
- shares_meet_threshold_min!(shares)
291
+ shares_have_same_bytesize!(shares) &&
292
+ shares_have_expected_length!(shares) &&
293
+ shares_meet_threshold_min!(shares)
293
294
  return true
294
295
  else
295
296
  return false
@@ -0,0 +1,66 @@
1
+ module TSS
2
+
3
+ # Custom Contracts
4
+ # See : https://egonschiele.github.io/contracts.ruby/
5
+ class SecretArg
6
+ def self.valid? val
7
+ val.present? && val.is_a?(String) &&
8
+ val.length.between?(1,65502) &&
9
+ ['UTF-8', 'US-ASCII'].include?(val.encoding.name) &&
10
+ val.slice(0) != "\u001F"
11
+ end
12
+
13
+ def self.to_s
14
+ 'must be a UTF-8 or US-ASCII String between 1 and 65,502 characters in length and must not begin with the padding char \u001F'
15
+ end
16
+ end
17
+
18
+ class ThresholdArg
19
+ def self.valid? val
20
+ val.present? &&
21
+ val.is_a?(Integer) &&
22
+ val.between?(1,255)
23
+ end
24
+
25
+ def self.to_s
26
+ 'must be an Integer between 1 and 255'
27
+ end
28
+ end
29
+
30
+ class NumSharesArg
31
+ def self.valid? val
32
+ val.present? &&
33
+ val.is_a?(Integer) &&
34
+ val.between?(1,255)
35
+ end
36
+
37
+ def self.to_s
38
+ 'must be an Integer between 1 and 255'
39
+ end
40
+ end
41
+
42
+ class IdentifierArg
43
+ def self.valid? val
44
+ val.present? &&
45
+ val.is_a?(String) &&
46
+ val.length.between?(1,16) &&
47
+ val =~ /^[a-zA-Z0-9\-\_\.]*$/i
48
+ end
49
+
50
+ def self.to_s
51
+ 'must be a String between 1 and 16 characters in length limited to [a-z, A-Z, -, _, .]'
52
+ end
53
+ end
54
+
55
+ class PadBlocksizeArg
56
+ def self.valid? val
57
+ val.present? &&
58
+ val.is_a?(Integer) &&
59
+ val.between?(0,255)
60
+ end
61
+
62
+ def self.to_s
63
+ 'must be an Integer between 0 and 255'
64
+ end
65
+ end
66
+ end
data/lib/tss/hasher.rb CHANGED
@@ -2,15 +2,18 @@ module TSS
2
2
  # Hasher is responsible for managing access to the various one-way hash
3
3
  # functions that can be used to validate a secret.
4
4
  class Hasher
5
+ include Contracts::Core
6
+ C = Contracts
7
+
5
8
  HASHES = { NONE: { code: 0, bytesize: 0, hasher: nil },
6
9
  SHA1: { code: 1, bytesize: 20, hasher: Digest::SHA1 },
7
- SHA256: { code: 2, bytesize: 32, hasher: Digest::SHA256 }
8
- }.freeze
10
+ SHA256: { code: 2, bytesize: 32, hasher: Digest::SHA256 }}.freeze
9
11
 
10
- # Lookup the Symbol key for a Hash with the code.
11
- #
12
- # @param code [Integer] the hash code to convert to a Symbol key
13
- # @return [Symbol] the hash key Symbol
12
+ # Lookup the Symbol key for a Hash with the code.
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
17
  def self.key_from_code(code)
15
18
  return nil unless Hasher.codes.include?(code)
16
19
  HASHES.each do |k, v|
@@ -22,6 +25,7 @@ module TSS
22
25
  #
23
26
  # @param hash_key [Symbol, String] the hash key to convert to an Integer code
24
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]
25
29
  def self.code(hash_key)
26
30
  HASHES[hash_key.upcase.to_sym][:code]
27
31
  end
@@ -29,6 +33,7 @@ module TSS
29
33
  # Lookup all valid hash codes, including NONE.
30
34
  #
31
35
  # @return [Array<Integer>] all hash codes including NONE
36
+ Contract C::None => C::ArrayOf[C::Int]
32
37
  def self.codes
33
38
  HASHES.map do |_k, v|
34
39
  v[:code]
@@ -38,6 +43,7 @@ module TSS
38
43
  # All valid hash codes that actually do hashing, excluding NONE.
39
44
  #
40
45
  # @return [Array<Integer>] all hash codes excluding NONE
46
+ Contract C::None => C::ArrayOf[C::Int]
41
47
  def self.codes_without_none
42
48
  HASHES.map do |_k, v|
43
49
  v[:code] if v[:code] > 0
@@ -48,8 +54,9 @@ module TSS
48
54
  #
49
55
  # @param hash_key [Symbol, String] the hash key to lookup
50
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
51
58
  def self.bytesize(hash_key)
52
- HASHES[hash_key.upcase.to_sym][:bytesize]
59
+ HASHES[hash_key.to_sym][:bytesize]
53
60
  end
54
61
 
55
62
  # Return a hexdigest hash for a String using hash_key hash algorithm.
@@ -58,6 +65,7 @@ module TSS
58
65
  # @param hash_key [Symbol, String] the hash key to use to hash a String
59
66
  # @param str [String] the String to hash
60
67
  # @return [String] the hex digest for str
68
+ Contract C::Or[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]], String => String
61
69
  def self.hex_string(hash_key, str)
62
70
  hash_key = hash_key.upcase.to_sym
63
71
  return '' if hash_key == :NONE
@@ -70,6 +78,7 @@ module TSS
70
78
  # @param hash_key [Symbol, String] the hash key to use to hash a String
71
79
  # @param str [String] the String to hash
72
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
73
82
  def self.byte_string(hash_key, str)
74
83
  hash_key = hash_key.upcase.to_sym
75
84
  return '' if hash_key == :NONE
@@ -82,6 +91,7 @@ module TSS
82
91
  # @param hash_key [Symbol, String] the hash key to use to hash a String
83
92
  # @param str [String] the String to hash
84
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]
85
95
  def self.byte_array(hash_key, str)
86
96
  hash_key = hash_key.upcase.to_sym
87
97
  return [] if hash_key == :NONE
data/lib/tss/splitter.rb CHANGED
@@ -8,26 +8,15 @@ module TSS
8
8
 
9
9
  attr_reader :secret, :threshold, :num_shares, :identifier, :hash_alg, :format, :pad_blocksize
10
10
 
11
- Contract ({ :secret => String, :threshold => C::Maybe[C::Int], :num_shares => C::Maybe[C::Int], :identifier => C::Maybe[String], :hash_alg => C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256']], :format => C::Maybe[C::Enum['binary', 'human']], :pad_blocksize => C::Maybe[C::Int] }) => C::Any
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
12
12
  def initialize(opts = {})
13
13
  @secret = opts.fetch(:secret)
14
- raise TSS::ArgumentError, 'Invalid secret length. Must be between 1 and 65502' unless @secret.size.between?(1,65502)
15
-
16
14
  @threshold = opts.fetch(:threshold, 3)
17
- raise TSS::ArgumentError, 'Invalid threshold size. Must be between 1 and 255' unless @threshold.between?(1,255)
18
-
19
15
  @num_shares = opts.fetch(:num_shares, 5)
20
- raise TSS::ArgumentError, 'Invalid num_shares size. Must be between 1 and 255' unless @num_shares.between?(1,255)
21
-
22
16
  @identifier = opts.fetch(:identifier, SecureRandom.hex(8))
23
- raise TSS::ArgumentError, 'Invalid identifier characters' unless @identifier =~ /^[a-zA-Z0-9\-\_\.]*$/i
24
- raise TSS::ArgumentError, 'Invalid identifier size. Must be between 0 and 16' unless @identifier.size.between?(0,16)
25
-
26
17
  @hash_alg = opts.fetch(:hash_alg, 'SHA256')
27
18
  @format = opts.fetch(:format, 'human')
28
-
29
19
  @pad_blocksize = opts.fetch(:pad_blocksize, 0)
30
- raise TSS::ArgumentError, 'Invalid pad_blocksize size. Must be between 0 and 255' unless @pad_blocksize.between?(0,255)
31
20
  end
32
21
 
33
22
  SHARE_HEADER_STRUCT = BinaryStruct.new([
@@ -60,9 +49,8 @@ module TSS
60
49
  # If the operation can not be completed successfully, then an error
61
50
  # code should be returned.
62
51
  #
52
+ Contract C::None => C::ArrayOf[String]
63
53
  def split
64
- secret_has_acceptable_encoding!(secret)
65
- secret_does_not_begin_with_padding_char!(secret)
66
54
  num_shares_not_less_than_threshold!(threshold, num_shares)
67
55
 
68
56
  # RTSS : Combine the secret with a hash digest before splitting. On recombine
@@ -129,35 +117,11 @@ module TSS
129
117
  human = ['tss', 'v1', identifier, threshold, Base64.urlsafe_encode64(binary)].join('~')
130
118
  format == 'binary' ? binary : human
131
119
  end
132
- end
133
-
134
- private
135
120
 
136
- # The secret must be encoded with UTF-8 of US-ASCII or it is invalid.
137
- #
138
- # @param secret [String] a secret String
139
- # @return [true] returns true if acceptable encoding
140
- # @raise [TSS::ArgumentError] if invalid
141
- def secret_has_acceptable_encoding!(secret)
142
- unless secret.encoding.name == 'UTF-8' || secret.encoding.name == 'US-ASCII'
143
- raise TSS::ArgumentError, "invalid secret, must be a UTF-8 or US-ASCII encoded String not '#{secret.encoding.name}'"
144
- else
145
- return true
146
- end
121
+ return shares
147
122
  end
148
123
 
149
- # The secret must not being with the padding character or it is invalid.
150
- #
151
- # @param secret [String] a secret String
152
- # @return [true] returns true if String does not begin with padding character
153
- # @raise [TSS::ArgumentError] if invalid
154
- def secret_does_not_begin_with_padding_char!(secret)
155
- if secret.slice(0) == "\u001F"
156
- raise TSS::ArgumentError, 'invalid secret, first byte of secret is the reserved left-pad character (\u001F)'
157
- else
158
- return true
159
- end
160
- end
124
+ private
161
125
 
162
126
  # The num_shares must be greater than or equal to the threshold or it is invalid.
163
127
  #
@@ -165,6 +129,7 @@ module TSS
165
129
  # @param num_shares [Integer] the num_shares value
166
130
  # @return [true] returns true if num_shares is >= threshold
167
131
  # @raise [TSS::ArgumentError] if invalid
132
+ Contract TSS::ThresholdArg, TSS::NumSharesArg => C::Bool
168
133
  def num_shares_not_less_than_threshold!(threshold, num_shares)
169
134
  if num_shares < threshold
170
135
  raise TSS::ArgumentError, "invalid num_shares, must be >= threshold (#{threshold})"
@@ -179,6 +144,7 @@ module TSS
179
144
  # @param secret_bytes [Array<Integer>] the Byte Array containing the secret
180
145
  # @return [true] returns true if num_shares is >= threshold
181
146
  # @raise [TSS::ArgumentError] if invalid
147
+ Contract C::ArrayOf[C::Int] => C::Bool
182
148
  def secret_bytes_is_smaller_than_max_size!(secret_bytes)
183
149
  if secret_bytes.size >= 65_535
184
150
  raise TSS::ArgumentError, 'invalid secret, combined padded secret and hash are too large'
@@ -194,6 +160,7 @@ module TSS
194
160
  # @param threshold [Integer] the threshold value
195
161
  # @param share_len [Integer] the length of the share in Bytes
196
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
197
164
  def share_header(identifier, hash_alg, threshold, share_len)
198
165
  SHARE_HEADER_STRUCT.encode(identifier: identifier,
199
166
  hash_id: Hasher.code(hash_alg),
data/lib/tss/tss.rb CHANGED
@@ -5,6 +5,7 @@ require 'binary_struct'
5
5
  require 'contracts'
6
6
  require 'tss/blank'
7
7
  require 'tss/version'
8
+ require 'tss/custom_contracts'
8
9
  require 'tss/util'
9
10
  require 'tss/hasher'
10
11
  require 'tss/splitter'
@@ -14,6 +15,9 @@ require 'tss/combiner'
14
15
  #
15
16
  # @author Glenn Rempe <glenn@rempe.us>
16
17
  module TSS
18
+ include Contracts::Core
19
+ C = Contracts
20
+
17
21
  # An unexpected error has occurred.
18
22
  class Error < StandardError; end
19
23
 
@@ -35,17 +39,17 @@ module TSS
35
39
  #
36
40
  # @param [Hash] opts the options to create a message with.
37
41
  # @option opts [String] :secret takes a String (UTF-8 or US-ASCII encoding) with a length between 1..65_534
38
- # @option opts [String] :threshold (3) The number of shares (M) that will be required to recombine the
42
+ # @option opts [Integer] :threshold (3) The number of shares (M) that will be required to recombine the
39
43
  # secret. Must be a value between 1..255 inclusive. Defaults to a threshold of 3 shares.
40
- # @option opts [String] :num_shares (5) The total number of shares (N) that will be created. Must be
44
+ # @option opts [Integer] :num_shares (5) The total number of shares (N) that will be created. Must be
41
45
  # a value between the `threshold` value (M) and 255 inclusive.
42
46
  # The upper limit is particular to the TSS algorithm used.
43
- # @option opts [String] :identifier (SecureRandom.hex(8)) A 0-16 bytes String limited to the characters 0-9, a-z, A-Z,
47
+ # @option opts [String] :identifier (SecureRandom.hex(8)) A 1-16 byte String limited to the characters 0-9, a-z, A-Z,
44
48
  # the dash (-), the underscore (_), and the period (.). The identifier will
45
49
  # be embedded in each the binary header of each share and should not reveal
46
50
  # anything about the secret.
47
51
  #
48
- # It defaults to the value of `SecureRandom.hex(8)`
52
+ # If the arg is nil it defaults to the value of `SecureRandom.hex(8)`
49
53
  # which returns a random 16 Byte string which represents a Base10 decimal
50
54
  # between 1 and 18446744073709552000.
51
55
  # @option opts [String] :hash_alg ('SHA256') The one-way hash algorithm that will be used to verify the
@@ -57,7 +61,7 @@ module TSS
57
61
  # who are recombining the shares to verify if they have in fact recovered
58
62
  # the correct secret.
59
63
  # @option opts [String] :format ('binary') the format of the String share output, 'binary' or 'human'
60
- # @option opts [String] :pad_blocksize (0) An integer representing the nearest multiple of Bytes
64
+ # @option opts [Integer] :pad_blocksize (0) An integer representing the nearest multiple of Bytes
61
65
  # to left pad the secret to. Defaults to not adding any padding (0). Padding
62
66
  # is done with the "\u001F" character (decimal 31 in a Byte Array).
63
67
  #
@@ -72,17 +76,10 @@ module TSS
72
76
  # the share is recombined, or instruct recipients to ignore it.
73
77
  #
74
78
  # @return [Array<String>] an Array of String shares
75
- # @raise [TSS::ArgumentError] if the options Types or Values are invalid
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]
76
81
  def self.split(opts)
77
- unless opts.is_a?(Hash) && opts.key?(:secret)
78
- raise TSS::ArgumentError, 'TSS.split takes a Hash of options with at least a :secret key'
79
- end
80
-
81
- begin
82
- TSS::Splitter.new(opts).split
83
- rescue ParamContractError => e
84
- raise TSS::ArgumentError, e.message
85
- end
82
+ TSS::Splitter.new(opts).split
86
83
  end
87
84
 
88
85
  # The reconstruction, or combining, operation reconstructs the secret from a
@@ -126,16 +123,9 @@ module TSS
126
123
  # @return [Hash] a Hash containing the ':secret' and other metadata
127
124
  # @raise [TSS::NoSecretError] if the secret cannot be re-created from the shares provided
128
125
  # @raise [TSS::InvalidSecretHashError] if the embedded hash of the secret does not match the hash of the recreated secret
129
- # @raise [TSS::ArgumentError] if the options Types or Values are invalid
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})
130
128
  def self.combine(opts)
131
- unless opts.is_a?(Hash) && opts.key?(:shares)
132
- raise TSS::ArgumentError, 'TSS.combine takes a Hash of options with at least a :shares key'
133
- end
134
-
135
- begin
136
- TSS::Combiner.new(opts).combine
137
- rescue ParamContractError => e
138
- raise TSS::ArgumentError, e.message
139
- end
129
+ TSS::Combiner.new(opts).combine
140
130
  end
141
131
  end
data/lib/tss/util.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  module TSS
2
2
  # Common utility, math, and conversion functions.
3
3
  module Util
4
+ include Contracts::Core
5
+ C = Contracts
6
+
4
7
  # The regex to match against human style shares
5
8
  HUMAN_SHARE_RE = /^tss~v1~*[a-zA-Z0-9\.\-\_]{0,16}~[0-9]{1,3}~([a-zA-Z0-9\-\_]+\={0,2})$/
6
9
 
@@ -216,6 +219,7 @@ module TSS
216
219
  #
217
220
  # @param str [String] a UTF-8 String to convert
218
221
  # @return [Array<Integer>] an Array of Integer Bytes
222
+ Contract String => C::ArrayOf[C::Int]
219
223
  def self.utf8_to_bytes(str)
220
224
  str.bytes.to_a
221
225
  end
@@ -224,6 +228,7 @@ module TSS
224
228
  #
225
229
  # @param bytes [Array<Integer>] an Array of Bytes to convert
226
230
  # @return [String] a UTF-8 String
231
+ Contract C::ArrayOf[C::Int] => String
227
232
  def self.bytes_to_utf8(bytes)
228
233
  bytes.pack('C*').force_encoding('utf-8')
229
234
  end
@@ -232,6 +237,7 @@ module TSS
232
237
  #
233
238
  # @param bytes [Array<Integer>] an Array of Bytes to convert
234
239
  # @return [String] a hex String
240
+ Contract C::ArrayOf[C::Int] => String
235
241
  def self.bytes_to_hex(bytes)
236
242
  hex = ''
237
243
  bytes.each { |b| hex += sprintf('%02x', b) }
@@ -242,6 +248,7 @@ module TSS
242
248
  #
243
249
  # @param str [String] a hex String to convert
244
250
  # @return [Array<Integer>] an Array of Integer Bytes
251
+ Contract String => C::ArrayOf[C::Int]
245
252
  def self.hex_to_bytes(str)
246
253
  [str].pack('H*').unpack('C*')
247
254
  end
@@ -250,6 +257,7 @@ module TSS
250
257
  #
251
258
  # @param hex [String] a hex String to convert
252
259
  # @return [String] a UTF-8 String
260
+ Contract String => String
253
261
  def self.hex_to_utf8(hex)
254
262
  bytes_to_utf8(hex_to_bytes(hex))
255
263
  end
@@ -258,6 +266,7 @@ module TSS
258
266
  #
259
267
  # @param str [String] a UTF-8 String to convert
260
268
  # @return [String] a hex String
269
+ Contract String => String
261
270
  def self.utf8_to_hex(str)
262
271
  bytes_to_hex(utf8_to_bytes(str))
263
272
  end
@@ -268,6 +277,7 @@ module TSS
268
277
  # @param input_string [String] the String to pad
269
278
  # @param pad_char [String] the String to pad with
270
279
  # @return [String] a padded String
280
+ Contract C::Int, String, String => String
271
281
  def self.left_pad(byte_multiple, input_string, pad_char = "\u001F")
272
282
  return input_string if byte_multiple == 0
273
283
  pad_length = byte_multiple - (input_string.length % byte_multiple)
@@ -288,6 +298,7 @@ module TSS
288
298
  # @param a [String] the private value
289
299
  # @param b [String] the user provided value
290
300
  # @return [true, false] whether the strings match or not
301
+ Contract String, String => C::Bool
291
302
  def self.secure_compare(a, b)
292
303
  return false unless a.bytesize == b.bytesize
293
304
 
@@ -303,6 +314,7 @@ module TSS
303
314
  #
304
315
  # @param share [String] a binary octet share
305
316
  # @return [Hash] header attributes
317
+ Contract String => Hash
306
318
  def self.extract_share_header(share)
307
319
  h = Splitter::SHARE_HEADER_STRUCT.decode(share)
308
320
  h[:identifier] = h[:identifier].delete("\x00")
@@ -313,6 +325,7 @@ module TSS
313
325
  #
314
326
  # @param n [Integer] the Integer to calculate for
315
327
  # @return [Integer] the factorial of n
328
+ Contract C::Int => C::Int
316
329
  def self.factorial(n)
317
330
  (1..n).reduce(:*) || 1
318
331
  end
@@ -330,6 +343,7 @@ module TSS
330
343
  # @param n [Integer] the total number of shares
331
344
  # @param r [Integer] the threshold number of shares
332
345
  # @return [Integer] the number of possible combinations
346
+ Contract C::Int, C::Int => C::Int
333
347
  def self.calc_combinations(n, r)
334
348
  factorial(n) / (factorial(r) * factorial(n - r))
335
349
  end
@@ -339,6 +353,7 @@ module TSS
339
353
  # @param n [Integer] an Integer to convert
340
354
  # @param delimiter [String] the String to delimit n in three Integer groups
341
355
  # @return [String] the object converted into a comma separated String.
356
+ Contract C::Int, String => String
342
357
  def self.int_commas(n, delimiter = ',')
343
358
  n.to_s.reverse.gsub(%r{([0-9]{3}(?=([0-9])))}, "\\1#{delimiter}").reverse
344
359
  end
data/lib/tss/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TSS
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  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.2.0
4
+ version: 0.3.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-23 00:00:00.000000000 Z
33
+ date: 2016-09-24 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: sysrandom
@@ -238,6 +238,7 @@ files:
238
238
  - lib/tss/cli_split.rb
239
239
  - lib/tss/cli_version.rb
240
240
  - lib/tss/combiner.rb
241
+ - lib/tss/custom_contracts.rb
241
242
  - lib/tss/hasher.rb
242
243
  - lib/tss/splitter.rb
243
244
  - lib/tss/tss.rb
metadata.gz.sig CHANGED
Binary file