tss 0.3.0 → 0.4.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 +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����
|