tss 0.2.0 → 0.3.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: 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