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.
@@ -0,0 +1,93 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Copyright 2011-2015 Alexander Klink and 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
+ module SecretSharing
18
+ module Shamir
19
+ # The SecretSharing::Shamir::Container class can be used to share random
20
+ # secrets between n people, so that k < n people can recover the
21
+ # secret, but k-1 people learn nothing (in an information-theoretical
22
+ # sense) about the secret.
23
+ #
24
+ # For a theoretical background, see:
25
+ # http://www.cs.tau.ac.il/~bchor/Shamir.html
26
+ # http://en.wikipedia.org/wiki/Secret_sharing#Shamir.27s_scheme
27
+ #
28
+ class Container
29
+ include SecretSharing::Shamir
30
+ attr_reader :n, :k, :secret, :shares
31
+
32
+ MIN_SHARES = 2
33
+ MAX_SHARES = 512
34
+
35
+ # To create a new SecretSharing::Shamir::Container object, you can
36
+ # pass either just n, or n and k where:
37
+ #
38
+ # n = The total number of shares that will be created.
39
+ # k = The threshold number of the total shares needed to
40
+ # recreate the original secret. (Default = n)
41
+ #
42
+ # For example:
43
+ #
44
+ # # 3(k) out of 5(n) shares needed to recover secret
45
+ # s = SecretSharing::Shamir::Container.new(5, 3)
46
+ #
47
+ # # 3(k) out of 3(n) shares needed to recover secret
48
+ # s = SecretSharing::Shamir::Container.new(3)
49
+ #
50
+ def initialize(n, k = n)
51
+ @n = n.to_i
52
+ @k = k.to_i
53
+
54
+ fail ArgumentError, 'n must be an Integer' unless @n.is_a?(Integer)
55
+ fail ArgumentError, 'k must be an Integer' unless @k.is_a?(Integer)
56
+
57
+ fail ArgumentError, 'k must be <= n' unless @k <= @n
58
+ fail ArgumentError, 'k must be >= #{MIN_SHARES}' unless @k >= MIN_SHARES
59
+ fail ArgumentError, 'n must be <= #{MAX_SHARES}' unless @n <= MAX_SHARES
60
+
61
+ @secret = nil
62
+ @shares = []
63
+ end
64
+
65
+ def secret?
66
+ @secret.is_a?(SecretSharing::Shamir::Secret)
67
+ end
68
+
69
+ def secret=(sec)
70
+ fail ArgumentError, 'secret has already been set' if secret?
71
+ fail ArgumentError, 'secret must be a SecretSharing::Shamir::Secret instance' unless sec.is_a?(SecretSharing::Shamir::Secret)
72
+ @secret = sec
73
+ @shares = Share.create_shares(@k, @n, @secret)
74
+ true
75
+ end
76
+
77
+ # Add a secret share to the object. Accepts a
78
+ # SecretSharing::Shamir::Share instance.
79
+ # Returns secret as a SecretSharing::Shamir::Secret if enough valid shares have been added
80
+ # to recover the secret, and false otherwise. The secret can also be recovered
81
+ # later with SecretSharing::Shamir::Container#secret if enough valid shares were previously
82
+ # provided.
83
+ def <<(share)
84
+ # You can't add more shares than were originally generated with value of @n
85
+ fail ArgumentError, 'You have added more shares than allowed by the value of @n' if @shares.size >= @n
86
+
87
+ share = SecretSharing::Shamir::Share.new(:share => share) unless share.is_a?(SecretSharing::Shamir::Share)
88
+ @shares << share unless @shares.include?(share)
89
+ @secret = Share.recover_secret(@shares)
90
+ end
91
+ end # class Container
92
+ end # module Shamir
93
+ end # module SecretSharing
@@ -0,0 +1,123 @@
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
+ module SecretSharing
18
+ module Shamir
19
+ # A SecretSharing::Shamir::Secret object represents a Secret in the
20
+ # Shamir secret sharing scheme. Secrets can be passed in as an input
21
+ # argument when creating a new SecretSharing::Shamir::Container or
22
+ # can be the output from a Container that has successfully decoded shares.
23
+ # A new Secret take 0 or 1 args. Zero args means the Secret will be initialized
24
+ # with a random OpenSSL::BN object with the Secret::DEFAULT_BITLENGTH. If a
25
+ # single argument is passed it can be one of two object types, String or
26
+ # OpenSSL::BN. If a String it is expected to be a specially encoded String
27
+ # that was generated as the output of calling #to_s on another Secret object.
28
+ # If the object type is OpenSSL::BN it can represent a number up to 4096 num_bits
29
+ # in length as reported by OpenSSL::BN#num_bits.
30
+ #
31
+ # All secrets are internally represented as an OpenSSL::BN which can be retrieved
32
+ # in its raw form using #secret.
33
+ #
34
+ class Secret
35
+ include SecretSharing::Shamir
36
+
37
+ # FIXME : Is a MAX_BITLENGTH really needed? Can it be larger if so?
38
+
39
+ MAX_BITLENGTH = 4096
40
+
41
+ attr_accessor :secret, :bitlength, :hmac
42
+
43
+ # FIXME : allow instantiating a secret with any random number bitlength you choose.
44
+
45
+ def initialize(opts = {})
46
+ opts = {
47
+ :secret => get_random_number(256)
48
+ }.merge!(opts)
49
+
50
+ # override with options
51
+ opts.each_key do |k|
52
+ if self.respond_to?("#{k}=")
53
+ send("#{k}=", opts[k])
54
+ else
55
+ fail ArgumentError, "Argument '#{k}' is not allowed"
56
+ end
57
+ end
58
+
59
+ # FIXME : Do we really need the ability for a String arg to re-instantiate a Secret?
60
+ # FIXME : If its a String, shouldn't it be able to be an arbitrary String converted to/from OpenSSL::BN?
61
+
62
+ if opts[:secret].is_a?(String)
63
+ # Decode a Base64.urlsafe_encode64 String which contains a Base 36 encoded Bignum back into an OpenSSL::BN
64
+ # See : Secret#to_s for forward encoding method.
65
+ decoded_secret = usafe_decode64(opts[:secret])
66
+ fail ArgumentError, 'invalid base64 (returned nil or empty String)' if decoded_secret.empty?
67
+ @secret = OpenSSL::BN.new(decoded_secret.to_i(36).to_s)
68
+ end
69
+
70
+ @secret = opts[:secret] if @secret.nil?
71
+ fail ArgumentError, "Secret must be an OpenSSL::BN, not a '#{@secret.class}'" unless @secret.is_a?(OpenSSL::BN)
72
+ @bitlength = @secret.num_bits
73
+ fail ArgumentError, "Secret must have a bitlength less than or equal to #{MAX_BITLENGTH}" if @bitlength > MAX_BITLENGTH
74
+
75
+ generate_hmac
76
+ end
77
+
78
+ # Secrets are equal if the OpenSSL::BN in @secret is the same.
79
+ def ==(other)
80
+ other == @secret
81
+ end
82
+
83
+ # Set a new secret forces regeneration of the HMAC
84
+ def secret=(secret)
85
+ @secret = secret
86
+ generate_hmac
87
+ end
88
+
89
+ def secret?
90
+ @secret.is_a?(OpenSSL::BN)
91
+ end
92
+
93
+ def to_s
94
+ # Convert the OpenSSL::BN secret to an Bignum which has a #to_s(36) method
95
+ # Convert the Bignum to a Base 36 encoded String
96
+ # Wrap the Base 36 encoded String as a URL safe Base 64 encoded String
97
+ # Combined this should result in a relatively compact and portable String
98
+ usafe_encode64(@secret.to_i.to_s(36))
99
+ end
100
+
101
+ def valid_hmac?
102
+ return false if !@secret.is_a?(OpenSSL::BN) || @hmac.to_s.empty? || @secret.to_s.empty?
103
+
104
+ hmac_key = @secret.to_s
105
+ hmac_data = OpenSSL::Digest::SHA256.new(@secret.to_s).hexdigest
106
+
107
+ @hmac == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, hmac_key, hmac_data)
108
+ end
109
+
110
+ private
111
+
112
+ # The HMAC uses the raw secret itself as the HMAC key, and the SHA256 of the secret as the data.
113
+ # This allows later regeneration of the HMAC to confirm that the restored secret is in fact
114
+ # identical to what was originally split into shares.
115
+ def generate_hmac
116
+ return false if @secret.to_s.empty?
117
+ hmac_key = @secret.to_s
118
+ hmac_data = OpenSSL::Digest::SHA256.new(@secret.to_s).hexdigest
119
+ @hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, hmac_key, hmac_data)
120
+ end
121
+ end # class Secret
122
+ end # module Shamir
123
+ end # module SecretSharing
@@ -0,0 +1,156 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Copyright 2011-2015 Alexander Klink and 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
+ module SecretSharing
18
+ module Shamir
19
+ # A SecretSharing::Shamir::Share object represents a share in the
20
+ # Shamir secret sharing scheme. The share consists of a point (x,y) on
21
+ # a polynomial over Z/Zp, where p is a prime.
22
+ class Share
23
+ include SecretSharing::Shamir
24
+ extend SecretSharing::Shamir
25
+ attr_accessor :share, :version, :hmac, :k, :n, :x, :y, :prime, :prime_bitlength
26
+
27
+ def initialize(opts = {})
28
+ opts = {
29
+ :share => nil,
30
+ :version => 1,
31
+ :hmac => nil,
32
+ :k => nil,
33
+ :n => nil,
34
+ :x => nil,
35
+ :y => nil,
36
+ :prime => nil,
37
+ :prime_bitlength => nil
38
+ }.merge!(opts)
39
+
40
+ opts.each_key do |k|
41
+ if self.respond_to?("#{k}=")
42
+ send("#{k}=", opts[k])
43
+ else
44
+ fail ArgumentError, "Argument '#{k}' is not allowed"
45
+ end
46
+ end
47
+
48
+ # Decode and unpack a String share if provided
49
+ unpack_share(@share) unless @share.nil?
50
+
51
+ if @share.nil?
52
+ errors = [:version, :hmac, :k, :n, :x, :y, :prime, :prime_bitlength].map { |e| e if send("#{e}").nil? }.compact
53
+ fail ArgumentError, "#{errors.join(', ')} expected." unless errors.empty?
54
+ end
55
+ end
56
+
57
+ def ==(other)
58
+ other.to_s == to_s
59
+ end
60
+
61
+ # FIXME : Add an HMAC which 'signs' all of the attributes of the hash and which gets verified on re-hydration to make sure
62
+ # that none of the attributes changed?
63
+
64
+ def to_hash
65
+ [:version, :hmac, :k, :n, :x, :y, :prime, :prime_bitlength].reduce({}) do |h, element|
66
+ if [:hmac].include?(element)
67
+ h.merge(element => send(element))
68
+ else
69
+ # everything else is an Integer/Bignum
70
+ h.merge(element => send(element).to_i)
71
+ end
72
+ end
73
+ end
74
+
75
+ def to_json
76
+ MultiJson.dump(to_hash)
77
+ end
78
+
79
+ def to_s
80
+ usafe_encode64(to_json)
81
+ end
82
+
83
+ # Creates the shares by computing random coefficients for a polynomial
84
+ # and then computing points on this polynomial.
85
+ def self.create_shares(k, n, secret)
86
+ shares = []
87
+ coefficients = []
88
+ coefficients[0] = secret.secret
89
+
90
+ # round up to next nibble
91
+ next_nibble_bitlength = secret.bitlength + (4 - (secret.bitlength % 4))
92
+ prime_bitlength = next_nibble_bitlength + 1
93
+ prime = OpenSSL::BN.generate_prime(prime_bitlength)
94
+
95
+ # FIXME : Why does generate_prime always return 35879 for bitlength 1-15
96
+ # OpenSSL::BN::generate_prime(1).to_i
97
+ # => 35879
98
+ # Do we need to make sure that prime_bitlength is not shorter than 64 bits?
99
+ # See : https://www.mail-archive.com/openssl-dev@openssl.org/msg18835.html
100
+ # See : http://ardoino.com/2005/11/maths-openssl-primes-random/
101
+ # See : http://www.openssl.org/docs/apps/genrsa.html "Therefore the number of bits should not be less that 64."
102
+
103
+ # compute random coefficients
104
+ (1..k - 1).each { |x| coefficients[x] = get_random_number(secret.bitlength) }
105
+
106
+ (1..n).each do |x|
107
+ p_x = evaluate_polynomial_at(x, coefficients, prime)
108
+ new_share = new(:x => x, :y => p_x, :prime => prime, :prime_bitlength => prime_bitlength, :k => k, :n => n, :hmac => secret.hmac)
109
+ shares[x - 1] = new_share
110
+ end
111
+ shares
112
+ end
113
+
114
+ # Recover the secret by doing Lagrange interpolation.
115
+ def self.recover_secret(shares)
116
+ return false unless shares.length >= shares[0].k
117
+
118
+ # All Shares must have the same HMAC or they were derived from different Secrets
119
+ hmacs = shares.map(&:hmac).uniq
120
+ fail ArgumentError, 'Share mismatch. Not all Shares have a common HMAC.' unless hmacs.size == 1
121
+
122
+ secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN.new('0'))
123
+
124
+ shares.each do |share|
125
+ l_x = lagrange(share.x, shares)
126
+ summand = share.y * l_x
127
+ summand %= share.prime
128
+ secret.secret += summand
129
+ secret.secret %= share.prime
130
+ end
131
+
132
+ if secret && secret.is_a?(SecretSharing::Shamir::Secret) && secret.valid_hmac?
133
+ return secret
134
+ else
135
+ fail ArgumentError, 'Secret recovery failure. The generated Secret does not match the HMACs in the Shares provided.'
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def unpack_share(share)
142
+ decoded = usafe_decode64(share)
143
+ h = MultiJson.load(decoded, :symbolize_keys => true)
144
+
145
+ @version = h[:version].to_i unless h[:version].nil?
146
+ @hmac = h[:hmac] unless h[:hmac].nil?
147
+ @k = h[:k].to_i unless h[:k].nil?
148
+ @n = h[:n].to_i unless h[:n].nil?
149
+ @x = h[:x].to_i unless h[:x].nil?
150
+ @y = OpenSSL::BN.new(h[:y].to_s) unless h[:y].nil?
151
+ @prime = OpenSSL::BN.new(h[:prime].to_s) unless h[:prime].nil?
152
+ @prime_bitlength = h[:prime_bitlength].to_i unless h[:prime_bitlength].nil?
153
+ end
154
+ end # class Share
155
+ end # module Shamir
156
+ end # module SecretSharing
@@ -0,0 +1,20 @@
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
+ # Module for specifying the current version of the gem.
18
+ module SecretSharing
19
+ VERSION = '1.0.0'
20
+ end
@@ -0,0 +1,47 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'secretsharing/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "secretsharing"
9
+ s.version = SecretSharing::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ["Alexander Klink", "Glenn Rempe"]
12
+ s.email = ["glenn@rempe.us"]
13
+ s.homepage = "https://github.com/grempe/secretsharing"
14
+ s.summary = %q{A Ruby Gem to enable sharing secrets using Shamirs Secret Sharing.}
15
+ s.license = "APACHE 2.0"
16
+
17
+ s.has_rdoc = 'true'
18
+ s.extra_rdoc_files = ['README.md']
19
+
20
+ s.rubyforge_project = "secretsharing"
21
+
22
+ s.files = `git ls-files`.split($/)
23
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
25
+ s.require_paths = ["lib"]
26
+
27
+ s.add_dependency 'highline', '~> 1.6'
28
+ s.add_dependency 'multi_json', '~> 1.10'
29
+
30
+ s.add_development_dependency 'mocha'
31
+ s.add_development_dependency 'minitest'
32
+ s.add_development_dependency 'coco'
33
+ s.add_development_dependency 'rb-fsevent'
34
+ s.add_development_dependency 'rerun'
35
+ s.add_development_dependency 'rubocop'
36
+ s.add_development_dependency 'bundler', '~> 1.7'
37
+ s.add_development_dependency 'rake', '~> 10.4'
38
+
39
+ s.description =<<'XEOF'
40
+ Shamir's Secret Sharing is an algorithm in cryptography. It is a
41
+ form of secret sharing, where a secret is divided into parts,
42
+ giving each participant its own unique part, where some of the
43
+ parts or all of them are needed in order to reconstruct the
44
+ secret. Holders of a share gain no knowledge of the larger secret.
45
+ XEOF
46
+
47
+ end
@@ -0,0 +1,373 @@
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 File.expand_path('../spec_helper', __FILE__)
18
+
19
+ describe SecretSharing::Shamir::Container do
20
+
21
+ describe 'initialization' do
22
+
23
+ it 'will raise when instantiated with no args' do
24
+ lambda { SecretSharing::Shamir::Container.new }.must_raise(ArgumentError)
25
+ end
26
+
27
+ it 'will create shares with n and k equal when given one Integer arg' do
28
+ s1 = SecretSharing::Shamir::Container.new(5)
29
+ s1.n.must_equal(5)
30
+ s1.k.must_equal(5)
31
+ end
32
+
33
+ it 'will create shares with n and k set to their own values when given two Integer args' do
34
+ s1 = SecretSharing::Shamir::Container.new(5, 3)
35
+ s1.n.must_equal(5)
36
+ s1.k.must_equal(3)
37
+ end
38
+
39
+ it 'will create shares with n and k equal when given one Integer as String arg' do
40
+ s1 = SecretSharing::Shamir::Container.new('5')
41
+ s1.n.must_equal(5)
42
+ s1.k.must_equal(5)
43
+ end
44
+
45
+ it 'will raise an exception with n being a non-Integer String arg' do
46
+ lambda { SecretSharing::Shamir::Container.new('foo') }.must_raise(ArgumentError)
47
+ end
48
+
49
+ it 'will create shares with n and k set to their own values when given two Integer as String args' do
50
+ s1 = SecretSharing::Shamir::Container.new('5', '3')
51
+ s1.n.must_equal(5)
52
+ s1.k.must_equal(3)
53
+ end
54
+
55
+ it 'will return false when secret? is called after initialization with only n arg set' do
56
+ s1 = SecretSharing::Shamir::Container.new(5)
57
+ s1.secret?.must_equal(false)
58
+ end
59
+
60
+ it 'will return false when secret? is called after initialization with n and k arg set' do
61
+ s1 = SecretSharing::Shamir::Container.new(5, 3)
62
+ s1.secret?.must_equal(false)
63
+ end
64
+
65
+ it 'will return nil secret when called after initialization with only n arg set' do
66
+ s1 = SecretSharing::Shamir::Container.new(5)
67
+ s1.secret.must_be_nil
68
+ end
69
+
70
+ it 'will return nil secret when called after initialization with n and k arg set' do
71
+ s1 = SecretSharing::Shamir::Container.new(5, 3)
72
+ s1.secret.must_be_nil
73
+ end
74
+
75
+ it 'will raise if k > n' do
76
+ lambda { SecretSharing::Shamir::Container.new(5, 6) }.must_raise(ArgumentError)
77
+ end
78
+
79
+ it 'will raise if only n is provided and it is < 2' do
80
+ lambda { SecretSharing::Shamir::Container.new(1) }.must_raise(ArgumentError)
81
+ end
82
+
83
+ it 'will raise unless k >= 2' do
84
+ lambda { SecretSharing::Shamir::Container.new(1, 1) }.must_raise(ArgumentError)
85
+ end
86
+
87
+ it 'will initialize if both k and n are at max size of 512' do
88
+ s1 = SecretSharing::Shamir::Container.new(512, 512)
89
+ s1.n.must_equal(512)
90
+ s1.k.must_equal(512)
91
+ end
92
+
93
+ it 'will raise if n > 512' do
94
+ lambda { SecretSharing::Shamir::Container.new(513) }.must_raise(ArgumentError)
95
+ end
96
+
97
+ it 'must return the correct min shares constant value' do
98
+ SecretSharing::Shamir::Container::MIN_SHARES.must_equal(2)
99
+ end
100
+
101
+ it 'must return the correct max shares constant value' do
102
+ SecretSharing::Shamir::Container::MAX_SHARES.must_equal(512)
103
+ end
104
+
105
+ end # describe initialization
106
+
107
+ describe 'creating a container and setting a secret' do
108
+
109
+ before do
110
+ @num_shares = 5
111
+ @c = SecretSharing::Shamir::Container.new(@num_shares)
112
+ @secret_num = OpenSSL::BN.new('1234567890')
113
+ @c.secret = SecretSharing::Shamir::Secret.new(:secret => @secret_num)
114
+ end
115
+
116
+ it 'will return true from #secret?' do
117
+ @c.secret?.must_equal(true)
118
+ end
119
+
120
+ it 'will not return a nil #secret' do
121
+ @c.secret.wont_be_nil
122
+ end
123
+
124
+ it 'will not return a nil #shares' do
125
+ @c.shares.wont_be_nil
126
+ end
127
+
128
+ it 'will return an Array of #shares' do
129
+ @c.shares.must_be_instance_of(Array)
130
+ end
131
+
132
+ it 'will return an Array of #shares of the same length as initialized with' do
133
+ @c.shares.size.must_equal(@num_shares)
134
+ end
135
+
136
+ it 'will return an Array of #shares each of the correct class' do
137
+ @c.shares.each do |share|
138
+ share.must_be_instance_of(SecretSharing::Shamir::Share)
139
+ end
140
+ end
141
+
142
+ it 'must raise an exception if a secret is attempted to be set more than once' do
143
+ lambda { @c.secret = SecretSharing::Shamir::Secret.new }.must_raise(ArgumentError)
144
+ end
145
+
146
+ end # creating a container and setting a secret
147
+
148
+ describe 'generating shares when a secret is provided' do
149
+
150
+ it 'should generate unique shares for the min number of shares and a tiny secret' do
151
+ c1 = SecretSharing::Shamir::Container.new(2)
152
+ c1.secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN.new('123'))
153
+ shares = c1.shares
154
+ shares.size.must_equal(2)
155
+ uniq_shares = shares.uniq
156
+ shares.must_equal(uniq_shares)
157
+ end
158
+
159
+ it 'should generate unique shares for the max number of shares and a tiny secret' do
160
+ c1 = SecretSharing::Shamir::Container.new(512)
161
+ c1.secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN.new('123'))
162
+ shares = c1.shares
163
+ shares.size.must_equal(512)
164
+ uniq_shares = shares.uniq
165
+ shares.must_equal(uniq_shares)
166
+ end
167
+
168
+ it 'should generate unique shares for the min number of shares and a large secret' do
169
+ c1 = SecretSharing::Shamir::Container.new(2)
170
+ # FIXME : Major Perf Issue : If OpenSSL::BN::rand(512) is given with 4096 instead of 512 this takes FOREVER
171
+ c1.secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN::rand(512))
172
+ shares = c1.shares
173
+ shares.size.must_equal(2)
174
+ uniq_shares = shares.uniq
175
+ shares.must_equal(uniq_shares)
176
+ end
177
+
178
+ it 'should generate unique shares for the max number of shares and a large secret' do
179
+ c1 = SecretSharing::Shamir::Container.new(512)
180
+ # FIXME : Major Perf Issue : If OpenSSL::BN::rand(512) is given with 4096 instead of 512 this takes FOREVER
181
+ c1.secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN::rand(512))
182
+ shares = c1.shares
183
+ shares.size.must_equal(512)
184
+ uniq_shares = shares.uniq
185
+ shares.must_equal(uniq_shares)
186
+ end
187
+
188
+ end
189
+
190
+ describe 'recovering a secret from a container' do
191
+
192
+ before do
193
+ @c1 = SecretSharing::Shamir::Container.new(5) # creator
194
+ @c2 = SecretSharing::Shamir::Container.new(5) # recipient
195
+
196
+ @c3 = SecretSharing::Shamir::Container.new(5, 3) # creator
197
+ @c4 = SecretSharing::Shamir::Container.new(5, 3) # recipient
198
+
199
+ @bad = SecretSharing::Shamir::Container.new(5, 3) # a bad actor
200
+ end
201
+
202
+ describe 'with a mix of valid and invalid shares' do
203
+
204
+ before do
205
+ # set a secret on the 'creators'
206
+ @c1.secret = SecretSharing::Shamir::Secret.new
207
+ @c3.secret = SecretSharing::Shamir::Secret.new
208
+ @bad.secret = SecretSharing::Shamir::Secret.new
209
+ end
210
+
211
+ it 'should be able to recover secret when k equals n and all k valid shares are provided as Shamir::Share objects' do
212
+ @c2 << @c1.shares[0]
213
+ @c2 << @c1.shares[1]
214
+ @c2 << @c1.shares[2]
215
+ @c2 << @c1.shares[3]
216
+
217
+ # with the last remaining share missing
218
+ @c2.secret?.must_equal(false)
219
+
220
+ @c2 << @c1.shares[4]
221
+
222
+ # with the final share provided
223
+ @c2.secret?.must_equal(true)
224
+ @c2.secret.must_equal(@c1.secret)
225
+ end
226
+
227
+ it 'should raise an ArgumentError if n + 1 shares are provided (more than were originally generated)' do
228
+ @c2 << @c1.shares[0]
229
+ @c2 << @c1.shares[1]
230
+ @c2 << @c1.shares[2]
231
+ @c2 << @c1.shares[3]
232
+ @c2.secret?.must_equal(false)
233
+ @c2 << @c1.shares[4]
234
+ @c2.secret?.must_equal(true)
235
+
236
+ lambda { @c2 << @bad.shares[0] }.must_raise(ArgumentError)
237
+ end
238
+
239
+ it 'should raise an ArgumentError when k equals n and k-1 valid shares and 1 invalid share are provided as Shamir::Share objects' do
240
+ @c2 << @c1.shares[0]
241
+ @c2 << @c1.shares[1]
242
+ @c2 << @c1.shares[2]
243
+ @c2 << @c1.shares[3]
244
+
245
+ # with the last remaining share missing
246
+ @c2.secret?.must_equal(false)
247
+
248
+ # with a bad share
249
+ lambda { @c2 << @bad.shares[0] }.must_raise(ArgumentError)
250
+ end
251
+
252
+ it 'should raise an ArgumentError if the final @secret generated does not have a valid_hmac?' do
253
+ # mocha instance mock using handy 'any_instance'
254
+ SecretSharing::Shamir::Secret.any_instance.stubs(:valid_hmac?).returns(false)
255
+ @c2 << @c1.shares[0]
256
+ @c2 << @c1.shares[1]
257
+ @c2 << @c1.shares[2]
258
+ @c2 << @c1.shares[3]
259
+ lambda { @c2 << @c1.shares[4] }.must_raise(ArgumentError)
260
+ end
261
+
262
+ end
263
+
264
+ describe 'with valid shares resulting from a random secret' do
265
+
266
+ before do
267
+ extend SecretSharing::Shamir
268
+ # set a secret on both of the 'creators'
269
+ @c1.secret = SecretSharing::Shamir::Secret.new
270
+ @c3.secret = SecretSharing::Shamir::Secret.new
271
+ end
272
+
273
+ # This exposed a bug in the prime number generation; for instance 4*8
274
+ # passes.
275
+ it 'should be able to recover secret with a bitlength of 5*8' do
276
+ secret = SecretSharing::Shamir::Secret.new(:secret => get_random_number(5*8))
277
+ c = SecretSharing::Shamir::Container.new(4,2)
278
+ c.secret = secret
279
+
280
+ (0...4).to_a.permutation(2).collect{|e| e.sort}.uniq.each do |indexes|
281
+ c2 = SecretSharing::Shamir::Container.new(2)
282
+ indexes.each do |index|
283
+ c2 << c.shares[index]
284
+ end
285
+ c2.secret?.must_equal(true)
286
+ c2.secret.must_equal(c.secret)
287
+ end
288
+ end
289
+
290
+ it 'should be able to recover secret when k equals n and all k shares are provided as Shamir::Share objects' do
291
+ @c2 << @c1.shares[0]
292
+ @c2 << @c1.shares[1]
293
+ @c2 << @c1.shares[2]
294
+ @c2 << @c1.shares[3]
295
+
296
+ # with the last remaining share missing
297
+ @c2.secret?.must_equal(false)
298
+
299
+ @c2 << @c1.shares[4]
300
+
301
+ # with the final share provided
302
+ @c2.secret?.must_equal(true)
303
+ @c2.secret.must_equal(@c1.secret)
304
+ end
305
+
306
+ it 'should be able to recover secret when k equals n and all k shares are provided as Shamir::Share objects converted to Strings' do
307
+ @c2 << @c1.shares[0].to_s
308
+ @c2 << @c1.shares[1].to_s
309
+ @c2 << @c1.shares[2].to_s
310
+ @c2 << @c1.shares[3].to_s
311
+
312
+ # with the last remaining share missing
313
+ @c2.secret?.must_equal(false)
314
+
315
+ @c2 << @c1.shares[4].to_s
316
+
317
+ # with the final share provided
318
+ @c2.secret?.must_equal(true)
319
+ @c2.secret.must_equal(@c1.secret)
320
+ end
321
+
322
+ it 'should be able to recover secret when k < n and minimum k shares are provided as Shamir::Share objects' do
323
+ @c4 << @c3.shares[0]
324
+ @c4 << @c3.shares[1]
325
+
326
+ # with the last remaining share missing
327
+ @c4.secret?.must_equal(false)
328
+
329
+ @c4 << @c3.shares[2]
330
+
331
+ # with the final share provided
332
+ @c4.secret?.must_equal(true)
333
+ @c4.secret.must_equal(@c3.secret)
334
+ end
335
+
336
+ it 'should be able to recover secret when k < n and minimum k shares are provided as Shamir::Share objects converted to Strings' do
337
+ @c4 << @c3.shares[0].to_s
338
+ @c4 << @c3.shares[1].to_s
339
+
340
+ # with the last remaining share missing
341
+ @c4.secret?.must_equal(false)
342
+
343
+ @c4 << @c3.shares[2].to_s
344
+
345
+ # with the final share provided
346
+ @c4.secret?.must_equal(true)
347
+ @c4.secret.must_equal(@c3.secret)
348
+ end
349
+
350
+ it 'should be able to recover secret when k < n and more than minimum k shares are provided as Shamir::Share objects converted to Strings' do
351
+ @c4 << @c3.shares[0].to_s
352
+ @c4 << @c3.shares[1].to_s
353
+
354
+ # with the last remaining share missing
355
+ @c4.secret?.must_equal(false)
356
+
357
+ @c4 << @c3.shares[2].to_s
358
+
359
+ # with the final needed share provided
360
+ @c4.secret?.must_equal(true)
361
+ @c4.secret.must_equal(@c3.secret)
362
+
363
+ # with an extra valid share provided
364
+ @c4 << @c3.shares[3].to_s
365
+ @c4.secret?.must_equal(true)
366
+ @c4.secret.must_equal(@c3.secret)
367
+ end
368
+
369
+ end
370
+
371
+ end # recovering a secret from a container
372
+
373
+ end # describe SecretSharing::Shamir::Container