secretsharing 0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = "spec/*_spec.rb"
8
+ t.verbose = true
9
+ t.warning = false
10
+ end
11
+
12
+ task :default => 'test'
data/SIGNED.md ADDED
@@ -0,0 +1,99 @@
1
+ ##### Signed by https://keybase.io/grempe
2
+ ```
3
+ -----BEGIN PGP SIGNATURE-----
4
+
5
+ iQIcBAABCgAGBQJUtXiuAAoJEOcWanfPl9CRG6cP/3LrrQItO+olbfYvvp+bFa54
6
+ hiK3g6IGYCKAAlocHVCzxNnEczAqCa3/HASkh96RIs0LXznKcFAIjgs/uwZs/Ahh
7
+ WYxjI9iPEyJyWffFzDIvA//A2xJCxuzupLc3yqGCTGCsvEf7yVN5fcVe8svzq6ZG
8
+ k4BuNzPLONCuzXrtmd7W15F+6M5PD67Rcs+gcHNuZEZOsXnJ3bRsu8GS8LucsxOK
9
+ SKsSwLpgJ6vAt3Ef3LSI7SQVMeRRtvgKi2iii/zGwHqRPPoqilpoeLNLlhtTX5Ee
10
+ KjfuW9qCU3zEfUs36J0JNtsf1fe0UmdiVvuRtWyM8ONHLJFP4394EwwA/A2QEYil
11
+ EYOEd8XkIcIh6lAMAKCNP22gHoJ5ezzlQ/Fk8tMGPlG+C0JVpYzf7+kzlL7iEvG/
12
+ y1YC16Cob2whgoLSvt4W7DFADwPm0WRsrdr0QPi0saM60w0fc9+6s8n6YrQEF/rv
13
+ vrNbwR7nTT0Hgxqy7F0C6N9l6mS3UOuJvbM2h3QKKI/N7aDZbD4v/SYZjQTD5aXQ
14
+ ZVV8KJxykxdHTNlsaUJptO4QFNfnWv0gWc25nDLQ+Io6v3rtV4wYf7JgIRcZgJg9
15
+ L6A0B2H1+SXkin7kcxb3ixaIzk0nRTHzF5SKWxGum3Q6wc6WiNE4CcwxuH3Pq2zD
16
+ IIhFWwazaio135TTNoj4
17
+ =BtEN
18
+ -----END PGP SIGNATURE-----
19
+
20
+ ```
21
+
22
+ <!-- END SIGNATURES -->
23
+
24
+ ### Begin signed statement
25
+
26
+ #### Expect
27
+
28
+ ```
29
+ size exec file contents
30
+ ./
31
+ 96 .coco.yml 7c6c72d0ace59753d384d977006da83a0b7bf5333468047529f3e92b4b32b069
32
+ 168 .gitignore cbf96ae3fecba78c775adf62469ecb1161fabf7cd336d166f2b695c02ec105de
33
+ 296 .rubocop.yml 9e05e042f3c9b14bc52fc7b961cebe26cffdd72f36fea16266ef7622f315e669
34
+ 182 .travis.yml 79982d49c16dfef74ac82d239073627e81ab1b4cff7d97721aa1fc047ccc9007
35
+ 625 CHANGES 578a7ed1fbc9a0ccdbc148aeae0ce8b3b58cb3f78e23013d8db0aaf98ade49fa
36
+ 98 Gemfile 5184edc7cae42cd49ced95779a2035fa6b04edc686450034b7ed731ef2941037
37
+ 11357 LICENSE.txt c81e80664649784c5927f846ba071b04acbb6afaeeea9ee737c8a4a9c8a3bc89
38
+ 12398 README.md 7191bb1605c1628dc88ad14d4b846984ed6f4d24148b8c14e49fa74c59085158
39
+ 205 Rakefile 52e019b00c55641f894f914df53a40d993ccb90b20731338004269292f4c5d7f
40
+ bin/
41
+ 3151 x secretsharing 79efbec8ef0fc6f24417ea490defc6008ac6533ae96dabc0ab7f59c2b385d791
42
+ gemfiles/
43
+ 101 Gemfile.ci 7e196ea31483bcfd25f626d3ce5eff19ee8c330c44a3ca6b80a4bd8c8aece065
44
+ lib/
45
+ secretsharing/
46
+ shamir/
47
+ 3749 container.rb 37fd7db90e4f2f337db979c93b7022710d443815a0be8f71f3c642902c7cea80
48
+ 4954 secret.rb 34ed54e31a4f862ea5f7df2074b7b204dc5400548a47b7f0055fa7050b7ff1e3
49
+ 6017 share.rb a380d5adf64815e62ad4256596b8a05ede61a8c0e7ec1436348946edd3aaed7a
50
+ 3031 shamir.rb 5702d49cc0e97202e6e0867a0f6955366d407ec4b3fe2d5989bc2259d2776f9f
51
+ 706 version.rb 428fb8abe60cd3e195e87f1d5f438e03ce797de8ceb699ab18260b7a9a4d9848
52
+ 863 secretsharing.rb 73deaa58299b597e0540b905f1f713dff526060a9a24c6909cdca83ff23164c4
53
+ 1733 secretsharing.gemspec 00430997e55126061ec3729438a58ec01e07b07fa846467947f55cfde8285a86
54
+ spec/
55
+ 12643 shamir_container_spec.rb 39f3e0393cdf50c88fae9ba4302d32dc881fc2ce3b4b8adcd97cf1fb5df026f2
56
+ 7023 shamir_secret_spec.rb 67533deb4a929155197067106e4149f70bc49b626b08690e8e3611106885ef77
57
+ 2719 shamir_share_spec.rb 4d2132531310348308c72af976cdc489bb59ef4b77373d5a71dd95b15f3205db
58
+ 1032 shamir_spec.rb c5a7e030dad410917a7e52f46f0531a4df8d2f05019e9136a34750f97699b7e2
59
+ 997 spec_helper.rb 9b634a61716596562288b4c4325e43696af87b48abcd36b34250991aa010caf9
60
+ ```
61
+
62
+ #### Ignore
63
+
64
+ ```
65
+ /SIGNED.md
66
+ ```
67
+
68
+ #### Presets
69
+
70
+ ```
71
+ git # ignore .git and anything as described by .gitignore files
72
+ dropbox # ignore .dropbox-cache and other Dropbox-related files
73
+ kb # ignore anything as described by .kbignore files
74
+ ```
75
+
76
+ <!-- summarize version = 0.0.9 -->
77
+
78
+ ### End signed statement
79
+
80
+ <hr>
81
+
82
+ #### Notes
83
+
84
+ With keybase you can sign any directory's contents, whether it's a git repo,
85
+ source code distribution, or a personal documents folder. It aims to replace the drudgery of:
86
+
87
+ 1. comparing a zipped file to a detached statement
88
+ 2. downloading a public key
89
+ 3. confirming it is in fact the author's by reviewing public statements they've made, using it
90
+
91
+ All in one simple command:
92
+
93
+ ```bash
94
+ keybase dir verify
95
+ ```
96
+
97
+ There are lots of options, including assertions for automating your checks.
98
+
99
+ For more info, check out https://keybase.io/docs/command_line/code_signing
data/bin/secretsharing ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'highline/import'
4
+
5
+ begin
6
+ require 'secretsharing'
7
+ rescue LoadError
8
+ require 'rubygems'
9
+ require 'secretsharing'
10
+ end
11
+
12
+ include SecretSharing::Shamir
13
+
14
+ # FIXME : Need to be able to specify bitlength of random secret
15
+ # FIXME : Need tests
16
+ # FIXME : Allow specify a fixed secret that is a num or a String
17
+
18
+ # Build up a Hash of all of the options chosen
19
+ choices = {}
20
+
21
+ say("\nShamir's Secret Sharing\n")
22
+
23
+ say("\nWould you like to 'encode' a new secret as shares, or 'decode' one from existing shares?\n")
24
+ choose do |menu|
25
+ menu.prompt = 'Action? '
26
+
27
+ menu.choice(:encode) do
28
+ choices[:action] = :encode
29
+ end
30
+
31
+ menu.choice(:decode) do
32
+ choices[:action] = :decode
33
+ end
34
+ end
35
+
36
+ if choices[:action] == :encode
37
+
38
+ say("\nWould you like to create a 'random' secret, or will you provide a 'fixed' one?\n")
39
+ choose do |menu|
40
+ menu.prompt = 'Type? '
41
+
42
+ menu.choice(:random) do
43
+ choices[:secret_type] = :random
44
+ end
45
+
46
+ menu.choice(:fixed) do
47
+ choices[:secret_type] = :fixed
48
+ end
49
+ end
50
+
51
+ if choices[:secret_type] == :fixed
52
+ choices[:secret_password] = ask('Enter your numeric password: ', Integer) { |q| q.validate = /^[0-9]+$/ }
53
+ end
54
+
55
+ choices[:secret_n] = ask('How many total shares (n) do you want to distribute? ', Integer) { |q| q.in = 2..512 }
56
+ choices[:secret_k] = ask('How many of the total shares (k) are required to reveal the secret? ', Integer) { |q| q.in = 2..512 }
57
+
58
+ @c = SecretSharing::Shamir::Container.new(choices[:secret_n], choices[:secret_k])
59
+
60
+ if choices[:secret_type] == :fixed
61
+ @c.secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN.new(choices[:secret_password].to_s))
62
+ else
63
+ @c.secret = SecretSharing::Shamir::Secret.new
64
+ end
65
+
66
+ say("\n========================================\n")
67
+ say("Encoded Secret:\n\n")
68
+ say("(k) Value: #{choices[:secret_k]}\n")
69
+ say("(n) Value: #{choices[:secret_n]}\n")
70
+ say("\n")
71
+ say("Secret (Bignum): \n")
72
+ say(@c.secret.secret.to_s)
73
+ say("\n")
74
+ say("Secret (Base64 Compacted & URL Safe): \n")
75
+ say(@c.secret.to_s)
76
+ say("\n")
77
+ say("Secret has valid_hmac? \n")
78
+ say(@c.secret.valid_hmac?.to_s + "\n")
79
+ say("\n")
80
+ say("Shares:\n")
81
+ @c.shares.each { |s| say s.to_s }
82
+ say("\n========================================\n")
83
+
84
+ elsif choices[:action] == :decode
85
+ say("\n")
86
+ choices[:secret_k] = ask('How many of shares (k) are required to reveal this secret? ', Integer) { |q| q.in = 2..512 }
87
+
88
+ @c = SecretSharing::Shamir::Container.new(choices[:secret_k])
89
+
90
+ say("\n")
91
+ shares = ask("Enter the '#{choices[:secret_k]}' shares one at a time with a RETURN after each:", lambda { |ans| ans =~ /^-?\d+$/ ? Integer(ans) : ans }) do |q|
92
+ q.gather = choices[:secret_k]
93
+ end
94
+
95
+ shares.map { |s| @c << s }
96
+
97
+ say("\n")
98
+ if @c.secret?
99
+ say("\n========================================\n")
100
+ say("Decoded Secret:\n\n")
101
+ say("(k) Value: #{choices[:secret_k]}\n")
102
+ say("\n")
103
+ say("Secret (Bignum): \n")
104
+ say(@c.secret.secret.to_s)
105
+ say("\n")
106
+ say("Secret (Base64 Compacted & URL Safe): \n")
107
+ say(@c.secret.to_s)
108
+ say("\n")
109
+ say("\n========================================\n")
110
+ end
111
+ end
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'multi_json'
4
+ gem 'rake'
5
+ gem 'minitest'
6
+ gem 'highline'
7
+ gem 'mocha'
data/lib/secretsharing.rb CHANGED
@@ -1 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Copyright 2011-2015 Glenn Rempe
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'openssl'
18
+ require 'digest/sha1'
19
+ require 'base64'
20
+ require 'multi_json'
21
+
22
+ require 'secretsharing/version'
1
23
  require 'secretsharing/shamir'
24
+ require 'secretsharing/shamir/container'
25
+ require 'secretsharing/shamir/share'
26
+ require 'secretsharing/shamir/secret'
@@ -1,298 +1,96 @@
1
- require 'openssl'
2
- require 'digest/sha1'
3
- require 'base64'
1
+ # -*- encoding: utf-8 -*-
4
2
 
5
- module SecretSharing
6
- # The SecretSharing::Shamir class can be used to share random
7
- # secrets between n people, so that k < n people can recover the
8
- # secret, but k-1 people learn nothing (in an information-theoretical
9
- # sense) about the secret.
10
- #
11
- # For a theoretical background, see
12
- # http://www.cs.tau.ac.il/~bchor/Shamir.html or
13
- # http://en.wikipedia.org/wiki/Secret_sharing#Shamir.27s_scheme
14
- #
15
- # To share a secret, create a new SecretSharing::Shamir object and
16
- # then call the create_random_secret() method. The secret is now in
17
- # the secret attribute and the shares are an array in the shares attribute.
18
- #
19
- # Alternatively, you can call the set_fixed_secret() method with an
20
- # OpenSSL::BN object (or something that can be passed to OpenSSL::BN.new)
21
- # to set your own secret.
22
- #
23
- # To recover a secret, create a SecretSharing::Shamir object and
24
- # add the necessary shares to it using the '<<' method. Once enough
25
- # shares have been added, the secret can be recovered in the secret
26
- # attribute.
27
- class Shamir
28
- attr_reader :n, :k, :secret, :secret_bitlength, :shares
29
-
30
- DEFAULT_SECRET_BITLENGTH = 256
31
-
32
- # To create a new SecretSharing::Shamir object, you can
33
- # pass either just n, or k and n.
34
- #
35
- # For example:
36
- # s = SecretSharing::Shamir.new(5, 3)
37
- # to create an object for 3 out of 5 secret sharing.
38
- #
39
- # or
40
- # s = SecretSharing::Shamir.new(3)
41
- # for 3 out of 3 secret sharing.
42
- def initialize(n, k=n)
43
- if k > n then
44
- raise ArgumentError, 'k must be smaller or equal than n'
45
- end
46
- if k < 2 then
47
- raise ArgumentError, 'k must be greater or equal to two'
48
- end
49
- if n > 255 then
50
- raise ArgumentError, 'n must be smaller than 256'
51
- end
52
- @n = n
53
- @k = k
54
- @secret = nil
55
- @shares = []
56
- @received_shares = []
57
- end
58
-
59
- # Check whether the secret is set.
60
- def secret_set?
61
- ! @secret.nil?
62
- end
63
-
64
- # Create a random secret of a certain bitlength. Returns the
65
- # secret and stores it in the 'secret' attribute.
66
- def create_random_secret(bitlength = DEFAULT_SECRET_BITLENGTH)
67
- raise 'secret already set' if secret_set?
68
- raise 'max bitlength is 1024' if bitlength > 1024
69
- @secret = get_random_number(bitlength)
70
- @secret_bitlength = bitlength
71
- create_shares
72
- @secret
73
- end
74
-
75
- # Set the secret to a fixed OpenSSL::BN value. Stores it
76
- # in the 'secret' attribute, creates the corresponding shares and
77
- # returns the secret
78
- def set_fixed_secret(secret)
79
- raise 'secret already set' if secret_set?
80
- if secret.class != OpenSSL::BN then
81
- # create OpenSSL bignum
82
- secret = OpenSSL::BN.new(secret)
83
- end
84
- raise 'max bitlength is 1024' if secret.num_bits > 1024
85
- @secret = secret
86
- @secret_bitlength = secret.num_bits
87
- create_shares
88
- @secret
89
- end
90
-
91
- # The secret in a password representation (Base64-encoded)
92
- def secret_password
93
- if ! secret_set? then
94
- raise "Secret not (yet) set."
95
- end
96
- Base64.encode64([@secret.to_s(16)].pack('h*')).split("\n").join
97
- end
98
-
99
- # Add a secret share to the object. Accepts either a
100
- # SecretSharing::Shamir::Share instance or a string representing one.
101
- # Returns true if enough shares have been added to recover the secret,
102
- # false otherweise.
103
- def <<(share)
104
- # convert from string if needed
105
- if share.class != SecretSharing::Shamir::Share then
106
- if share.class == String then
107
- share = SecretSharing::Shamir::Share.from_string(share)
108
- else
109
- raise ArgumentError 'SecretSharing::Shamir::Share ' \
110
- + 'or String needed'
111
- end
112
- end
113
- if @received_shares.include? share then
114
- raise 'share has already been added'
115
- end
116
- if @received_shares.length == @k then
117
- raise 'we already have enough shares, no need to add more'
118
- end
119
- @received_shares << share
120
- if @received_shares.length == @k then
121
- recover_secret
122
- return true
123
- end
124
- false
125
- end
126
-
127
- # Computes the smallest prime of a given bitlength. Uses prime_fasttest
128
- # from the OpenSSL library with 20 attempts to be compatible to openssl
129
- # prime, which is used in the OpenXPKI::Crypto::Secret::Split library.
130
- def self.smallest_prime_of_bitlength(bitlength)
131
- # start with 2^bit_length + 1
132
- test_prime = OpenSSL::BN.new((2**bitlength + 1).to_s)
133
- prime_found = false
134
- while (! prime_found) do
135
- # prime_fasttest? 20 do be compatible to
136
- # openssl prime, which is used in
137
- # OpenXPKI::Crypto::Secret::Split
138
- prime_found = test_prime.prime_fasttest? 20
139
- test_prime += 2
140
- end
141
- test_prime
142
- end
143
-
144
- private
145
- # Creates a random number of a certain bitlength, optionally ensuring
146
- # the bitlength by setting the highest bit to 1.
147
- def get_random_number(bitlength, highest_bit_one = true)
148
- byte_length = (bitlength / 8.0).ceil
149
- rand_hex = OpenSSL::Random.random_bytes(byte_length).each_byte. \
150
- to_a.map { |a| "%02x" % a }.join('')
151
- rand = OpenSSL::BN.new(rand_hex, 16)
152
- begin
153
- rand.mask_bits!(bitlength)
154
- rescue OpenSSL::BNError
155
- # never mind if there was an error, this just means
156
- # rand was already smaller than 2^bitlength - 1
157
- end
158
- if highest_bit_one then
159
- rand.set_bit!(bitlength)
160
- end
161
- rand
162
- end
3
+ # Copyright 2011-2015 Glenn Rempe
163
4
 
164
- # Creates the shares by computing random coefficients for a polynomial
165
- # and then computing points on this polynomial.
166
- def create_shares
167
- @coefficients = []
168
- @coefficients[0] = @secret
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
169
8
 
170
- # round up to next nibble
171
- next_nibble_bitlength = @secret_bitlength + \
172
- (4 - (@secret_bitlength % 4))
173
- prime_bitlength = next_nibble_bitlength + 1
174
- @prime = self.class.smallest_prime_of_bitlength(prime_bitlength)
9
+ # http://www.apache.org/licenses/LICENSE-2.0
175
10
 
176
- # compute random coefficients
177
- (1..k-1).each do |x|
178
- @coefficients[x] = get_random_number(@secret_bitlength)
179
- end
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
180
16
 
181
- (1..n).each do |x|
182
- @shares[x-1] = construct_share(x, prime_bitlength)
183
- end
184
- end
185
-
186
- # Construct a share by evaluating the polynomial at x and creating
187
- # a SecretSharing::Shamir::Share object.
188
- def construct_share(x, bitlength)
189
- p_x = evaluate_polynomial_at(x)
190
- SecretSharing::Shamir::Share.new(x, p_x, @prime, bitlength)
191
- end
192
-
193
- # Evaluate the polynomial at x.
194
- def evaluate_polynomial_at(x)
195
- result = OpenSSL::BN.new('0')
196
- @coefficients.each_with_index do |coeff, i|
197
- result += coeff * OpenSSL::BN.new(x.to_s)**i
198
- result %= @prime
199
- end
200
- result
201
- end
202
-
203
- # Recover the secret by doing Lagrange interpolation.
204
- def recover_secret
205
- @secret = OpenSSL::BN.new('0')
206
- @received_shares.each do |share|
207
- l_x = l(share.x, @received_shares)
208
- summand = share.y * l_x
209
- summand %= share.prime
210
- @secret += summand
211
- @secret %= share.prime
212
- end
213
- @secret
214
- end
215
-
216
- # Part of the Lagrange interpolation.
217
- # This is l_j(0), i.e.
218
- # \prod_{x_j \neq x_i} \frac{-x_i}{x_j - x_i}
219
- # for more information compare Wikipedia:
220
- # http://en.wikipedia.org/wiki/Lagrange_form
221
- def l(x, shares)
222
- (shares.select { |s| s.x != x }.map do |s|
223
- minus_xi = OpenSSL::BN.new((-s.x).to_s)
224
- one_over_xj_minus_xi = OpenSSL::BN.new((x - s.x).to_s) \
225
- .mod_inverse(shares[0].prime)
226
- minus_xi.mod_mul(one_over_xj_minus_xi, shares[0].prime)
227
- end.inject { |p, f| p.mod_mul(f, shares[0].prime) })
228
- end
229
- end
230
-
231
- # A SecretSharing::Shamir::Share object represents a share in the
232
- # Shamir secret sharing scheme. The share consists of a point (x,y) on
233
- # a polynomial over Z/Zp, where p is a prime.
234
- class SecretSharing::Shamir::Share
235
- attr_reader :x, :y, :prime_bitlength, :prime
236
-
237
- FORMAT_VERSION = '0'
238
-
239
- # Create a new share with the given point, prime and prime bitlength.
240
- def initialize(x, y, prime, prime_bitlength)
241
- @x = x
242
- @y = y
243
- @prime = prime
244
- @prime_bitlength = prime_bitlength
245
- end
246
-
247
- # Create a new share from a string format representation. For
248
- # a discussion of the format, see the to_s() method.
249
- def self.from_string(string)
250
- version = string[0,1]
251
- if version != '0' then
252
- raise "invalid share format version #{version}."
253
- end
254
- x = string[1,2].hex
255
- prime_bitlength = 4 * string[-2,2].hex + 1
256
- p_x_str = string[3, string.length - 9]
257
- checksum = string[-6, 4]
258
- computed_checksum = Digest::SHA1.hexdigest(p_x_str)[0,4].upcase
259
- if checksum != computed_checksum then
260
- raise "invalid checksum. expected #{checksum}, " + \
261
- "got #{computed_checksum}"
262
- end
263
- prime = SecretSharing::Shamir. \
264
- smallest_prime_of_bitlength(prime_bitlength)
265
- self.new(x, OpenSSL::BN.new(p_x_str, 16), prime, prime_bitlength)
266
- end
267
-
268
- # A string representation of the share, that can for example be
269
- # distributed in printed form.
270
- # The string is an uppercase hexadecimal string of the following
271
- # format: ABBC*DDDDEEEE, where
272
- # * A (the first nibble) is the version number of the format, currently
273
- # fixed to 0.
274
- # * B (the next byte, two hex characters) is the x coordinate of the
275
- # point on the polynomial.
276
- # * C (the next variable length of bytes) is the y coordinate of the
277
- # point on the polynomial.
278
- # * D (the next two bytes, four hex characters) is the two highest
279
- # bytes of the SHA1 hash on the string representing the y coordinate,
280
- # it is used as a checksum to guard against typos
281
- # * E (the next two bytes, four hex characters) is the bitlength of the
282
- # prime number in nibbles.
283
- def to_s
284
- # bitlength in nibbles to save space
285
- prime_nibbles = (@prime_bitlength - 1) / 4
286
- p_x = ("%x" % @y).upcase
287
- FORMAT_VERSION + ("%02x" % @x).upcase \
288
- + p_x \
289
- + Digest::SHA1.hexdigest(p_x)[0,4].upcase \
290
- + ("%02x" % prime_nibbles).upcase
291
- end
292
-
293
- # Shares are equal if their string representation is the same.
294
- def ==(share)
295
- share.to_s == self.to_s
296
- end
297
- end
298
- end
17
+ module SecretSharing
18
+ # Module for common methods shared across Container, Secret, or Share
19
+ module Shamir
20
+ # FIXME : Needs focused tests
21
+ # Creates a random number of a certain bitlength, optionally ensuring
22
+ # the bitlength by setting the highest bit to 1.
23
+ def get_random_number(bitlength)
24
+ byte_length = (bitlength / 8.0).ceil
25
+ rand_hex = OpenSSL::Random.random_bytes(byte_length).each_byte.to_a.map { |a| sprintf('%02x', a) }.join('')
26
+ rand = OpenSSL::BN.new(rand_hex, 16)
27
+
28
+ begin
29
+ rand.mask_bits!(bitlength)
30
+ rescue OpenSSL::BNError
31
+ # never mind if there was an error, this just means
32
+ # rand was already smaller than 2^bitlength - 1
33
+ end
34
+
35
+ rand.set_bit!(bitlength)
36
+ rand
37
+ end
38
+
39
+ # FIXME : Needs focused tests
40
+
41
+ # Evaluate the polynomial at x.
42
+ def evaluate_polynomial_at(x, coefficients, prime)
43
+ result = OpenSSL::BN.new('0')
44
+
45
+ coefficients.each_with_index do |c, i|
46
+ result += c * OpenSSL::BN.new(x.to_s)**i
47
+ result %= prime
48
+ end
49
+
50
+ result
51
+ end
52
+
53
+ # FIXME : Needs focused tests
54
+
55
+ # Part of the Lagrange interpolation.
56
+ # This is l_j(0), i.e.
57
+ # \prod_{x_j \neq x_i} \frac{-x_i}{x_j - x_i}
58
+ # for more information compare Wikipedia:
59
+ # http://en.wikipedia.org/wiki/Lagrange_form
60
+ def lagrange(x, shares)
61
+ prime = shares.first.prime
62
+ other_shares = shares.reject { |s| s.x == x }
63
+
64
+ results = other_shares.map do |s|
65
+ minus_xi = OpenSSL::BN.new("#{-s.x}")
66
+ one_over_xj_minus_xi = OpenSSL::BN.new("#{x - s.x}").mod_inverse(prime)
67
+ minus_xi.mod_mul(one_over_xj_minus_xi, prime)
68
+ end
69
+
70
+ results.reduce { |a, e| a.mod_mul(e, prime) }
71
+ end
72
+
73
+ # FIXME : Needs focused tests
74
+
75
+ # Backported for Ruby 1.8.7, REE, JRuby, Rubinious
76
+ def usafe_decode64(str)
77
+ str = str.strip
78
+ return Base64.urlsafe_decode64(str) if Base64.respond_to?(:urlsafe_decode64)
79
+
80
+ if str.include?('\n')
81
+ fail(ArgumentError, 'invalid base64')
82
+ else
83
+ Base64.decode64(str)
84
+ end
85
+ end
86
+
87
+ # FIXME : Needs focused tests
88
+
89
+ # Backported for Ruby 1.8.7, REE, JRuby, Rubinious
90
+ def usafe_encode64(bin)
91
+ bin = bin.strip
92
+ return Base64.urlsafe_encode64(bin) if Base64.respond_to?(:urlsafe_encode64)
93
+ Base64.encode64(bin).tr("\n", '')
94
+ end
95
+ end # module Shamir
96
+ end # module SecretSharing