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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +9 -0
- data/README.md +23 -19
- data/lib/tss/cli_combine.rb +0 -2
- data/lib/tss/cli_common.rb +0 -2
- data/lib/tss/cli_split.rb +2 -4
- data/lib/tss/cli_version.rb +0 -2
- data/lib/tss/combiner.rb +57 -46
- data/lib/tss/custom_contracts.rb +45 -6
- data/lib/tss/hasher.rb +34 -37
- data/lib/tss/splitter.rb +28 -19
- data/lib/tss/tss.rb +10 -10
- data/lib/tss/util.rb +35 -42
- data/lib/tss/version.rb +1 -1
- data/tss.gemspec +1 -0
- metadata +16 -2
- metadata.gz.sig +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3e63f35e23f40623afd002c3cba4e7a3407dd19
|
4
|
+
data.tar.gz: 54fb3ead58b69e1118e13dcd72da08705bed4276
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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 `
|
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
|
423
|
-
`'
|
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 `
|
427
|
-
`
|
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 = '
|
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: '
|
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: '
|
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: '
|
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 `
|
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
|
-
|
569
|
-
|
570
|
-
a `
|
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 `'
|
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 `
|
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
|
674
|
-
|
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
|
|
data/lib/tss/cli_combine.rb
CHANGED
data/lib/tss/cli_common.rb
CHANGED
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 => '
|
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
|
58
|
+
$ tss split -t 3 -n 6 -i abc123 -h SHA256 -p 8 -f HUMAN
|
61
59
|
|
62
60
|
Enter your secret:
|
63
61
|
|
data/lib/tss/cli_version.rb
CHANGED
data/lib/tss/combiner.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
module TSS
|
2
|
-
#
|
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::
|
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
|
-
|
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::
|
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 == '
|
81
|
+
if select_by == 'FIRST'
|
74
82
|
@shares = shares.shift(threshold)
|
75
|
-
elsif select_by == '
|
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 == '
|
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]
|
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
|
126
|
-
# @param shares_bytes
|
127
|
-
# @return
|
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::
|
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
|
176
|
-
# @return
|
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
|
185
|
-
# @return
|
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
|
198
|
-
# @return
|
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
|
220
|
-
# @return
|
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
|
235
|
-
# @return
|
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
|
257
|
-
# @return
|
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
|
272
|
-
# @return
|
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
|
287
|
-
# @return
|
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
|
303
|
-
# @return
|
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
|
324
|
-
# @return
|
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
|
344
|
-
# @param threshold
|
345
|
-
# @param max_combinations
|
346
|
-
# @return
|
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 (#{
|
363
|
+
raise TSS::ArgumentError, "invalid options, too many combinations (#{combinations})"
|
353
364
|
else
|
354
365
|
return true
|
355
366
|
end
|
data/lib/tss/custom_contracts.rb
CHANGED
@@ -1,10 +1,23 @@
|
|
1
|
-
module
|
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.
|
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
|
9
|
-
SHA1
|
10
|
-
SHA256
|
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
|
15
|
-
# @return
|
16
|
-
Contract C::Int => C::Maybe[
|
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
|
27
|
-
# @return
|
28
|
-
Contract C::
|
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
|
30
|
+
HASHES[hash_key][:code]
|
31
31
|
end
|
32
32
|
|
33
33
|
# Lookup all valid hash codes, including NONE.
|
34
34
|
#
|
35
|
-
# @return
|
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
|
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
|
56
|
-
# @return
|
57
|
-
Contract C::
|
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
|
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 ==
|
63
|
+
# Returns '' if hash_key == 'NONE'
|
64
64
|
#
|
65
|
-
# @param hash_key
|
66
|
-
# @param str
|
67
|
-
# @return
|
68
|
-
Contract C::
|
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
|
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 ==
|
75
|
+
# Returns '' if hash_key == 'NONE'
|
77
76
|
#
|
78
|
-
# @param hash_key
|
79
|
-
# @param str
|
80
|
-
# @return
|
81
|
-
Contract C::
|
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
|
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 ==
|
87
|
+
# Returns [] if hash_key == 'NONE'
|
90
88
|
#
|
91
|
-
# @param hash_key
|
92
|
-
# @param str
|
93
|
-
# @return
|
94
|
-
Contract C::
|
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
|
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
|
-
#
|
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 =>
|
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, '
|
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
|
-
|
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 == '
|
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
|
129
|
-
# @param num_shares
|
130
|
-
# @return
|
131
|
-
# @raise [TSS::ArgumentError] if invalid
|
132
|
-
Contract
|
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
|
145
|
-
# @return
|
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
|
159
|
-
# @param hash_alg
|
160
|
-
# @param threshold
|
161
|
-
# @param share_len
|
162
|
-
# @return
|
163
|
-
|
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 ('
|
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
|
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 =>
|
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 ('
|
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
|
-
# '
|
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
|
-
# `
|
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
|
-
# `
|
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
|
-
# `
|
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
|
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::
|
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
|
221
|
-
# @return
|
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
|
230
|
-
# @return
|
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
|
239
|
-
# @return
|
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
|
250
|
-
# @return
|
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
|
259
|
-
# @return
|
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
|
268
|
-
# @return
|
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
|
277
|
-
# @param input_string
|
278
|
-
# @param pad_char
|
279
|
-
# @return
|
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
|
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.
|
296
|
-
# in as the second parameter so as not to leak info about the secret.
|
295
|
+
# via timing attacks.
|
297
296
|
#
|
298
|
-
#
|
299
|
-
#
|
300
|
-
#
|
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 =
|
306
|
-
|
309
|
+
l = ah.unpack('C*')
|
307
310
|
r, i = 0, -1
|
308
|
-
|
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
|
316
|
-
# @return
|
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
|
327
|
-
# @return
|
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
|
344
|
-
# @param r
|
345
|
-
# @return
|
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
data/tss.gemspec
CHANGED
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.
|
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-
|
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
|
-
|
2
|
-
|
1
|
+
H��P��
|
2
|
+
�jT�*�O��0��k"��~� ���{�9B�O��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����
|