secretsharing 0.3 → 1.0.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.
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