secret_sharing 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7d1f9dc3ed57b5e31acda563d2a75bb39f17cda8
4
+ data.tar.gz: 38914666ac426bcdd7765ab4d04b9cd4209e48aa
5
+ SHA512:
6
+ metadata.gz: 05149dab9a3f6afceb1d52a72f15742bd4d5de9a06580d7abb8389d40b96a5de5af908c207e11cd5dc3c66f8c525fcf7c240a3882a1cf2a183327dd87e21b946
7
+ data.tar.gz: 1c6b21a99d780e527b9de6858a9f8bd9a3d3480904d8bc108b99d05dae446583157569be5d15f1da9c507600feb5748b92736aa1064bb2429a25409386003ac8
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
17
+ *.bundle
18
+ *.so
19
+ *.o
20
+ *.a
21
+ mkmf.log
22
+ :w
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ Style/AsciiComments:
2
+ Enabled: false # not to comment in another language than english but to be
3
+ # able to write tests for non ascii characters
4
+
5
+ Documentation:
6
+ Enabled: false # this is handled by yard and it is annoying since rubocop does
7
+ # not notice if a top level class documentation has already
8
+ # been created (https://github.com/bbatsov/rubocop/issues/691)
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.3
4
+ bundler_args: --without development
5
+ cache: bundler
data/.yardops ADDED
@@ -0,0 +1 @@
1
+ lib/**/*.rb --no-private --markup markdown - README.md LICENSE
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in secret_sharing.gemspec
4
+ gem 'bundler', '~> 1.6'
5
+ gem 'rake', group: [:development, :test]
6
+ gem 'rspec', group: [:development, :test]
7
+ gem 'yard', group: :development
8
+ gem 'rubocop', require: false, group: :development
9
+ gem 'simplecov', require: false, group: [:development, :test]
10
+ gem 'coveralls', require: false, group: [:development, :test]
data/Gemfile.lock ADDED
@@ -0,0 +1,68 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ ast (2.0.0)
5
+ astrolabe (1.3.0)
6
+ parser (>= 2.2.0.pre.3, < 3.0)
7
+ coveralls (0.7.1)
8
+ multi_json (~> 1.3)
9
+ rest-client
10
+ simplecov (>= 0.7)
11
+ term-ansicolor
12
+ thor
13
+ diff-lcs (1.2.5)
14
+ docile (1.1.5)
15
+ mime-types (2.4.2)
16
+ multi_json (1.10.1)
17
+ netrc (0.8.0)
18
+ parser (2.2.0.pre.5)
19
+ ast (>= 1.1, < 3.0)
20
+ slop (~> 3.4, >= 3.4.5)
21
+ powerpack (0.0.9)
22
+ rainbow (2.0.0)
23
+ rake (10.3.2)
24
+ rest-client (1.7.2)
25
+ mime-types (>= 1.16, < 3.0)
26
+ netrc (~> 0.7)
27
+ rspec (3.1.0)
28
+ rspec-core (~> 3.1.0)
29
+ rspec-expectations (~> 3.1.0)
30
+ rspec-mocks (~> 3.1.0)
31
+ rspec-core (3.1.7)
32
+ rspec-support (~> 3.1.0)
33
+ rspec-expectations (3.1.2)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.1.0)
36
+ rspec-mocks (3.1.3)
37
+ rspec-support (~> 3.1.0)
38
+ rspec-support (3.1.2)
39
+ rubocop (0.26.1)
40
+ astrolabe (~> 1.3)
41
+ parser (>= 2.2.0.pre.4, < 3.0)
42
+ powerpack (~> 0.0.6)
43
+ rainbow (>= 1.99.1, < 3.0)
44
+ ruby-progressbar (~> 1.4)
45
+ ruby-progressbar (1.6.0)
46
+ simplecov (0.9.1)
47
+ docile (~> 1.1.0)
48
+ multi_json (~> 1.0)
49
+ simplecov-html (~> 0.8.0)
50
+ simplecov-html (0.8.0)
51
+ slop (3.6.0)
52
+ term-ansicolor (1.3.0)
53
+ tins (~> 1.0)
54
+ thor (0.19.1)
55
+ tins (1.3.3)
56
+ yard (0.8.7.4)
57
+
58
+ PLATFORMS
59
+ ruby
60
+
61
+ DEPENDENCIES
62
+ bundler (~> 1.6)
63
+ coveralls
64
+ rake
65
+ rspec
66
+ rubocop
67
+ simplecov
68
+ yard
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 flower-pot
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ [![Build Status](https://travis-ci.org/duse-io/secret_sharing_ruby.svg?branch=master)](https://travis-ci.org/duse-io/secret_sharing_ruby)
2
+ [![Coverage Status](https://img.shields.io/coveralls/duse-io/secret_sharing_ruby.svg)](https://coveralls.io/r/duse-io/secret_sharing_ruby)
3
+ [![Code Climate](https://codeclimate.com/github/duse-io/secret_sharing_ruby/badges/gpa.svg)](https://codeclimate.com/github/duse-io/secret_sharing_ruby)
4
+
5
+ # SecretSharing
6
+
7
+ > **Warning:** This implementation has not been tested in production nor has it
8
+ > been examined by a security audit. All uses are your own responsibility.
9
+
10
+ A ruby implementation of shamir's secret sharing.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'secret_sharing'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install secret_sharing
25
+
26
+ ## Usage
27
+
28
+ require 'secret_sharing'
29
+ shares = SecretSharing.split_secret('secret', 2, 3) # => [...]
30
+ length = shares.length # => 3
31
+ secret = SecretSharing.recover_secret(shares[0..1]) # => 'my secret'
32
+
33
+ This implementation can also handle non-ascii characters, however, the charset
34
+ will be visible in the calculated shares. Thus splitting ascii only strings is
35
+ "more" secure. (but always remember, shamir's secret sharing alone is not
36
+ secure)
37
+
38
+ ## Compatible libraries
39
+
40
+ Since this implementation is special in some ways most [Shamir’s Secret
41
+ Sharing](http://de.wikipedia.org/wiki/Shamir%E2%80%99s_Secret_Sharing)
42
+ libraries are not compatible. The only library that is compatible as of now is
43
+ [duse-io/secret-sharing-dart](https://github.com/duse-io/secret-sharing-dart).
44
+
45
+ We do have [integration
46
+ tests](https://github.com/duse-io/lib-integration-tests) that make sure the
47
+ libraries work with each other.
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it ( https://github.com/duse-io/secret_sharing/fork )
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ begin
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
6
+ rescue LoadError
7
+ puts 'rspec tasks could not be loaded'
8
+ end
@@ -0,0 +1,55 @@
1
+ require 'secret_sharing/version'
2
+ require 'secret_sharing/point'
3
+ require 'secret_sharing/polynomial'
4
+ require 'secret_sharing/prime'
5
+ require 'secret_sharing/charset'
6
+ require 'secret_sharing/share'
7
+
8
+ # A ruby implementation of Shamir's Secret Sharing. This module is the only
9
+ # connection to be consumed by a user. Do not use any other class/module of
10
+ # this library unless you really know what you are doing.
11
+ module SecretSharing
12
+ # Split a secret using Shamir's Secret Sharing algorithm.
13
+ #
14
+ # Example
15
+ #
16
+ # SecretSharing.split_secret('secret', 2, 3)
17
+ # # => ["tcesr-1-4e16", "tcesr-2-1105", "tcesr-3-1d3f3"]
18
+ #
19
+ # @param secret_string [String] Secret to split.
20
+ # @param share_threshold [Integer] Number of shares to recover the secret.
21
+ # @param num_shares [Integer] Total number of shares to generate.
22
+ #
23
+ # @return [Array] Array of shares that can be used to recover the secret.
24
+ def split_secret(secret_string, share_threshold, num_shares)
25
+ charset = Charset.by_string secret_string
26
+ secret_int = charset.s_to_i(secret_string)
27
+ points = Polynomial.points_from_secret(secret_int,
28
+ share_threshold,
29
+ num_shares)
30
+ points.map do |point|
31
+ Share.new(charset, point).to_s
32
+ end
33
+ end
34
+
35
+ # Recover a secret with an array of string shares generated with
36
+ # {#split_secret}.
37
+ #
38
+ # Example
39
+ #
40
+ # SecretSharing.recover_secret(["tcesr-1-4e16", "tcesr-2-1105"])
41
+ # # => "secret"
42
+ #
43
+ # @param raw_shares [Array] Shares (array of strings) to recover the secret
44
+ #
45
+ # @return [String] Recovered secret in a string representation.
46
+ def recover_secret(raw_shares)
47
+ shares = raw_shares.map { |raw_share| Share.from_string raw_share }
48
+ points = shares.map(&:point)
49
+ secret_int = Polynomial.modular_lagrange_interpolation(points)
50
+ shares.first.charset.i_to_s(secret_int)
51
+ end
52
+
53
+ module_function :split_secret,
54
+ :recover_secret
55
+ end
@@ -0,0 +1,226 @@
1
+ require 'set'
2
+
3
+ module SecretSharing
4
+ # This module is used to create or retrieve a charset to use.
5
+ module Charset
6
+ # Decides which charset fits the provided string best and creates a custom
7
+ # charset if no predefined charset fits the usecase.
8
+ #
9
+ # Example
10
+ #
11
+ # SecretSharing::Charset.by_string 'test'
12
+ # # => #<SecretSharing::ASCIICharset @charset=[...]>
13
+ #
14
+ # Or with a custom charset
15
+ #
16
+ # SecretSharing::Charset.by_string 'testä'
17
+ # # => #<SecretSharing::DynamicCharset @charset=["s", "e", "ä", "t"]>
18
+ #
19
+ # @param string [String] The string to evaluate the charset for.
20
+ # @return A charset that has at least the methods #s_to_i and #i_to_s.
21
+ def by_string(string)
22
+ return ASCIICharset.new if ASCIICharset.new.subset?(string)
23
+ DynamicCharset.from_string string
24
+ end
25
+
26
+ # Retrieves a charset based on its string representation.
27
+ #
28
+ # Example
29
+ #
30
+ # SecretSharing::Charset.by_charset_string ''
31
+ # # => #<SecretSharing::ASCIICharset @charset=[...]>
32
+ #
33
+ # Or in case of a custom charset
34
+ #
35
+ # SecretSharing::Charset.by_charset_string 'tesä'
36
+ # # => #<SecretSharing::DynamicCharset @charset=["t", "e", "s", "ä"]>
37
+ #
38
+ # @param charset_string [String] The string to evaluate the charset for.
39
+ # @return A charset that has at least the methods #s_to_i and #i_to_s.
40
+ def by_charset_string(charset_string)
41
+ return ASCIICharset.new if charset_string.empty?
42
+ DynamicCharset.new(charset_string.chars)
43
+ end
44
+
45
+ module_function :by_string,
46
+ :by_charset_string
47
+ end
48
+
49
+ module Charset
50
+ # This objects of this class can represent a custom charset whenever the
51
+ # predefined charsets do not fit a situation.
52
+ class DynamicCharset
53
+ # A new instance of DynamicCharset. The constructor should only be used
54
+ # when you know what you are doing. Usually you only want to use this
55
+ # constructor when you recreate a charset and the order of the charset is
56
+ # important. The "null-byte" character is prepended to be the first
57
+ # character in the charset to avoid loosing the first character of the
58
+ # charset when it is also the first character in a string to convert.
59
+ #
60
+ # Example
61
+ #
62
+ # SecretSharing::Charset::DynamicCharset.new ['a', 'b', 'c']
63
+ # # => #<SecretSharing::Charset::DynamicCharset @charset=[...]>
64
+ #
65
+ # @param charset [Array] array of characters to use for the charset.
66
+ def initialize(charset)
67
+ @charset = charset.unshift("\u0000")
68
+ end
69
+
70
+ # Create a charset based on a string to encode.
71
+ #
72
+ # Example
73
+ #
74
+ # SecretSharing::Charset::DynamicCharset.from_string 'test'
75
+ # # => #<SecretSharing::Charset::DynamicCharset @charset=['e','t','s']>
76
+ #
77
+ # @param string [String] a string to encode with the charset to generate
78
+ # @return [DynamicCharset] shuffled charset to encode strings to ints
79
+ def self.from_string(string)
80
+ DynamicCharset.new string.chars.shuffle.uniq
81
+ end
82
+
83
+ # Calculate a string from an integer.
84
+ #
85
+ # Example
86
+ #
87
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
88
+ # charset.i_to_s 6
89
+ # # => "ab"
90
+ #
91
+ # @param x [Integer] integer to convert to string
92
+ # @return [String] converted string
93
+ def i_to_s(x)
94
+ unless x.is_a?(Integer) && x >= 0
95
+ fail ArgumentError, 'x must be a non-negative integer'
96
+ end
97
+
98
+ output = ''
99
+ while x > 0
100
+ x, codepoint = x.divmod(length)
101
+ output.prepend(codepoint_to_char(codepoint))
102
+ end
103
+ output
104
+ end
105
+
106
+ # Calculate an integer from a string.
107
+ #
108
+ # Example
109
+ #
110
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
111
+ # charset.s_to_i "ab"
112
+ # # => 6
113
+ #
114
+ # @param string [Integer] integer to convert to string
115
+ # @return [String] converted string
116
+ def s_to_i(string)
117
+ string.chars.reduce(0) do |output, char|
118
+ output * length + char_to_codepoint(char)
119
+ end
120
+ end
121
+
122
+ # Convert an integer into its string representation according to the
123
+ # charset. (only one character)
124
+ #
125
+ # Example
126
+ #
127
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
128
+ # charset.codepoint_to_char 1
129
+ # # => "a"
130
+ #
131
+ # @param codepoint [Integer] Codepoint to retrieve the character for
132
+ # @return [String] Retrieved character
133
+ def codepoint_to_char(codepoint)
134
+ char = @charset[codepoint]
135
+ return char unless char.nil?
136
+ fail ArgumentError, "Codepoint #{codepoint} does not exist in charset"
137
+ end
138
+
139
+ # Convert a single character into its integer representation according to
140
+ # the charset.
141
+ #
142
+ # Example
143
+ #
144
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
145
+ # charset.char_to_codepoint 'a'
146
+ # # => 1
147
+ #
148
+ # @param c [String] Character to retrieve its codepoint in the charset
149
+ # @return [Integer] Codepoint within the charset
150
+ def char_to_codepoint(c)
151
+ codepoint = @charset.rindex c
152
+ return codepoint unless codepoint.nil?
153
+ error_msg = "Character \"#{c}\" not part of the supported charset"
154
+ fail ArgumentError, error_msg
155
+ end
156
+
157
+ # Total length of the charset
158
+ #
159
+ # Example
160
+ #
161
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
162
+ # charset.length
163
+ # # => 4
164
+ def length
165
+ @charset.length
166
+ end
167
+
168
+ # Adds a string representation of the charset to the string, which can
169
+ # later be used to recreate the charset.
170
+ #
171
+ # Example
172
+ #
173
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
174
+ # charset.add_to_point('1-2')
175
+ # # => "abc-1-2"
176
+ #
177
+ # @param point_string [String] string to prepend to
178
+ # @return [String] string representation of charset-point_string
179
+ def add_to_point(point_string)
180
+ "#{@charset[1...length].join}-#{point_string}"
181
+ end
182
+
183
+ # Check if the provided string can be represented by the charset.
184
+ #
185
+ # Example
186
+ #
187
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
188
+ # charset.subset? 'd'
189
+ # # => false
190
+ # charset.subset? 'a'
191
+ # # => true
192
+ #
193
+ # @param string [String] Character to retrieve the for codepoint
194
+ # @return [TrueClass|FalseClass]
195
+ def subset?(string)
196
+ (Set.new(string.chars.uniq) - Set.new(@charset)).empty?
197
+ end
198
+ end
199
+
200
+ # Charset that can represent any string that only consists of ASCII
201
+ # characters.
202
+ class ASCIICharset < DynamicCharset
203
+ # An instance of an ASCIICharset is just a DynamicCharset with the ASCII
204
+ # characters.
205
+ def initialize
206
+ super((1..127).to_a.map(&:chr))
207
+ end
208
+
209
+ # Adds a string representation of the charset to the string (empty string
210
+ # in case of ASCII, since it is the default charset), which can later be
211
+ # used to recreate the charset.
212
+ #
213
+ # Example
214
+ #
215
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
216
+ # charset.add_to_point('1-2')
217
+ # # => "abc-1-2"
218
+ #
219
+ # @param point_string [String] string to prepend to
220
+ # @return [String] string representation of charset-point_string
221
+ def add_to_point(point_string)
222
+ point_string
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,63 @@
1
+ module SecretSharing
2
+ # This class is used to store a two dimensional point consisting of integers.
3
+ class Point
4
+ attr_accessor :x, :y
5
+
6
+ # Create a Point object.
7
+ #
8
+ # Examples
9
+ #
10
+ # Point.new(1, 2)
11
+ # # => #<Point: @x=1 @y=2>
12
+ #
13
+ # @param x [Integer] The x value of the point
14
+ # @param y [Integer] The y value of the point
15
+ def initialize(x, y)
16
+ @x = x
17
+ @y = y
18
+ end
19
+
20
+ # A nicer inspection of a point object than by default.
21
+ #
22
+ # @return [String] String representation of a Point
23
+ #
24
+ # Examples
25
+ #
26
+ # SecretSharing::Point.new(1, 2).inspect
27
+ # # => "#<Point: @x=1 @y=2>"
28
+ def inspect
29
+ "#<Point: @x=#{x} @y=#{y}>"
30
+ end
31
+
32
+ def to_s
33
+ "#{x}-#{y.to_s(16)}"
34
+ end
35
+
36
+ def self.from_string(point_string)
37
+ x_string, y_string = point_string.split '-'
38
+ Point.new x_string.to_i, y_string.to_i(16)
39
+ end
40
+
41
+ # An implementation similar to Array#transpose for Arrays of Points
42
+ #
43
+ # @return [Array] Two Arrays in an Array
44
+ #
45
+ # Examples
46
+ #
47
+ # point1 = SecretSharing::Point.new(1, 2)
48
+ # point2 = SecretSharing::Point.new(3, 4)
49
+ # points = [point1, point2]
50
+ # Point.transpose(points)
51
+ # # => [[1, 3], [2, 4]]
52
+ def self.transpose(points)
53
+ x_values = []
54
+ y_values = []
55
+
56
+ points.each do |point|
57
+ x_values << point.x
58
+ y_values << point.y
59
+ end
60
+ [x_values, y_values]
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,123 @@
1
+ require 'securerandom'
2
+
3
+ module SecretSharing
4
+ # The polynomial is used to represent the required random polynomials used in
5
+ # Shamir's Secret Sharing algorithm.
6
+ class Polynomial
7
+ # Create a new instance of a Polynomial with n coefficients, when having
8
+ # the polynomial in standard polynomial form.
9
+ #
10
+ # Example
11
+ #
12
+ # Polynomial.new [1, 2, 3]
13
+ # # => #<SecretSharing::Polynomial:0x0000000 @coefficients=[1, 2, 3]>
14
+ #
15
+ # @param coefficients [Array] an array of integers as the coefficients
16
+ def initialize(coefficients)
17
+ @coefficients = coefficients
18
+ end
19
+
20
+ # Generate points on the polynomial, that can be used to reconstruct the
21
+ # polynomial with.
22
+ #
23
+ # Example
24
+ #
25
+ # SecretSharing::Polynomial.new([1, 2, 3, 4]).points(3, 7)
26
+ # # => [#<Point: @x=1 @y=3>, #<Point: @x=2 @y=0>, #<Point: @x=3 @y=2>]
27
+ #
28
+ # @param num_points [Integer] number of points to generate
29
+ # @param prime [Integer] prime for calculation in finite field
30
+ # @return [Array] array of calculated points
31
+ def points(num_points, prime)
32
+ (1..num_points).map do |x|
33
+ y = @coefficients[0]
34
+ (1...@coefficients.length).each do |i|
35
+ exponentiation = x**i % prime
36
+ term = (@coefficients[i] * exponentiation) % prime
37
+ y = (y + term) % prime
38
+ end
39
+ Point.new(x, y)
40
+ end
41
+ end
42
+
43
+ # Generate a random polynomial with a specific degree, defined x=0 value
44
+ # and an upper limit for the coefficients of the polynomial.
45
+ #
46
+ # Example
47
+ #
48
+ # Polynomial.random(2, 3, 7)
49
+ # # => #<SecretSharing::Polynomial:0x0000000 @coefficients=[3, 0, 4]>
50
+ #
51
+ # @param degree [Integer] degree of the polynomial to generate
52
+ # @param intercept [Integer] the y value for x=0
53
+ # @param upper_bound [Integer] the highest value of a single coefficient
54
+ def self.random(degree, intercept, upper_bound)
55
+ fail ArgumentError, 'Degree must be a non-negative number' if degree < 0
56
+
57
+ coefficients = (0...degree).reduce([intercept]) do |accumulator, _i|
58
+ accumulator << SecureRandom.random_number(upper_bound)
59
+ end
60
+ Polynomial.new coefficients
61
+ end
62
+
63
+ # Generate points from a secret integer.
64
+ #
65
+ # Example
66
+ #
67
+ # SecretSharing::Polynomial.points_from_secret(123, 2, 3)
68
+ # # => [#<Point: @x=1 @y=109>, #<Point: @x=2 @y=95>, #<Point: @x=3 @y=81>]
69
+ #
70
+ # @param secret_int [Integer] the secret to divide into points
71
+ # @param point_threshold [Integer] number of points to reconstruct
72
+ # @param num_points [Integer] number of points to generate
73
+ # @return [Polynomial] the generated polynomial
74
+ def self.points_from_secret(secret_int, point_threshold, num_points)
75
+ prime = SecretSharing::Prime.large_enough_prime([secret_int, num_points])
76
+ fail ArgumentError, 'Secret is too long' if prime.nil?
77
+ fail ArgumentError, 'Threshold must be at least 2' if point_threshold < 2
78
+ fail ArgumentError, 'Threshold must be less than the total number of points' if point_threshold > num_points
79
+
80
+ polynomial = SecretSharing::Polynomial.random(point_threshold - 1,
81
+ secret_int,
82
+ prime)
83
+ polynomial.points(num_points, prime)
84
+ end
85
+
86
+ # Modular lagrange interpolation
87
+ def self.modular_lagrange_interpolation(points)
88
+ y_values = Point.transpose(points)[1]
89
+ prime = SecretSharing::Prime.large_enough_prime(y_values)
90
+ points.reduce(0) do |f_x, point|
91
+ numerator, denominator = lagrange_fraction(points, point, prime)
92
+ lagrange_polynomial = numerator * mod_inverse(denominator, prime)
93
+ (prime + f_x + (point.y * lagrange_polynomial)) % prime
94
+ end
95
+ end
96
+
97
+ # part of the lagrange interpolation
98
+ def self.lagrange_fraction(points, current, prime)
99
+ numerator, denominator = 1, 1
100
+ points.each do |point|
101
+ if point != current
102
+ numerator = (numerator * (0 - point.x)) % prime
103
+ denominator = (denominator * (current.x - point.x)) % prime
104
+ end
105
+ end
106
+ [numerator, denominator]
107
+ end
108
+
109
+ # inverse modulo
110
+ def self.mod_inverse(k, prime)
111
+ k = k % prime
112
+ r = egcd(prime, k.abs)[2]
113
+ (prime + r) % prime
114
+ end
115
+
116
+ # extended Euclidean algorithm
117
+ def self.egcd(a, b)
118
+ return [b, 0, 1] if a == 0
119
+ g, y, x = egcd(b % a, a)
120
+ [g, x - b.div(a) * y, y]
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,44 @@
1
+ module SecretSharing
2
+ # This module is used to generate/calculate/retrieve primes.
3
+ module Prime
4
+ # Retrieves the next largest prime for the largest number in batch
5
+ #
6
+ # Example
7
+ #
8
+ # Prime.large_enough_prime [1, 2, 3, 4]
9
+ # # => 7
10
+ #
11
+ # @param batch [Array] a list of integers
12
+ # @return [Integer] the next largest prime or nil if numbers too large
13
+ def large_enough_prime(batch)
14
+ standard_primes = mersenne_primes + [
15
+ # smallest 257, 321 and 385 bit primes
16
+ 2**256 + 297, 2**320 + 27, 2**384 + 231
17
+ ].sort
18
+
19
+ standard_primes.each do |prime|
20
+ greater_than_prime = Array.new(batch).select { |i| i if i > prime }
21
+ return prime if greater_than_prime.empty?
22
+ end
23
+ nil
24
+ end
25
+
26
+ private
27
+
28
+ # Calculates the first 15 mersenne primes
29
+ def mersenne_primes
30
+ mersenne_prime_exponents = [
31
+ 2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279
32
+ ]
33
+ mersenne_prime_exponents.map do |exp|
34
+ prime = 1
35
+ prime *= 2**exp
36
+ prime -= 1
37
+ prime
38
+ end
39
+ end
40
+
41
+ module_function :mersenne_primes,
42
+ :large_enough_prime
43
+ end
44
+ end
@@ -0,0 +1,74 @@
1
+ module SecretSharing
2
+ # A share is an object that encapsulates the properties of a share created by
3
+ # Shamir's Secret Sharing algorithm.
4
+ class Share
5
+ attr_reader :charset, :point
6
+
7
+ # Create a share object
8
+ #
9
+ # Example
10
+ #
11
+ # charset = SecretSharing::ASCIICharset.new
12
+ # point = SecretSharing::Point.new 1, 2
13
+ # SecretSharing::Share.new charset, point
14
+ # # => #<SecretSharing::Share:0x0000000 @charset=..., @point=...>
15
+ #
16
+ # @param charset [SecretSharing::Charset::DynamicCharset]
17
+ # @param point [SecretSharing::Point]
18
+ def initialize(charset, point)
19
+ @charset = charset
20
+ @point = point
21
+ end
22
+
23
+ # A string representation of a share.
24
+ #
25
+ # Example
26
+ #
27
+ # charset = SecretSharing::ASCIICharset.new
28
+ # point = SecretSharing::Point.new 1, 2
29
+ # SecretSharing::Share.new(charset, point).to_s
30
+ # # => "1-2"
31
+ #
32
+ def to_s
33
+ charset.add_to_point(point.to_s)
34
+ end
35
+
36
+ # Creates a share object from its string representation.
37
+ #
38
+ # Example
39
+ #
40
+ # SecretSharing::Share.from_string "1-2"
41
+ # # => #<SecretSharing::Share:0x0000000 @charset=..., @point=...>
42
+ #
43
+ def self.from_string(share_string)
44
+ charset_string, point_string = parse share_string
45
+ charset = Charset.by_charset_string charset_string
46
+ point = Point.from_string(point_string)
47
+ Share.new(charset, point)
48
+ end
49
+
50
+ # Parses the string representation of a share into charset and point
51
+ # coordinates out of a string representation of a share. Since the charset
52
+ # part can have a "-" in it it must be parsed backwards and not just
53
+ # splitted by "-".
54
+ #
55
+ # Example
56
+ #
57
+ # SecretSharing::Share.parse '1-2'
58
+ # # => ['', '1-2']
59
+ #
60
+ # @param share_string [String] a string representation of a share
61
+ # @return an array in form of [charset_string, point_string]
62
+ #
63
+ def self.parse(share_string)
64
+ charset_string, point_string = '', ''
65
+ number_of_dashes = 0
66
+ share_string.chars.reverse.each do |char|
67
+ charset_string.prepend(char) if number_of_dashes >= 2
68
+ number_of_dashes += 1 if char == '-'
69
+ point_string.prepend(char) if number_of_dashes <= 1
70
+ end
71
+ [charset_string, point_string]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,4 @@
1
+ module SecretSharing
2
+ # gem version
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,19 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'secret_sharing/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'secret_sharing'
8
+ spec.version = SecretSharing::VERSION
9
+ spec.authors = ['flower-pot']
10
+ spec.email = ['fbranczyk@gmail.com']
11
+ spec.summary = 'Ruby implementation of sharmir\'s secret sharing'
12
+ spec.description = 'Divide, share and reconstruct secrets.'
13
+ spec.homepage = 'https://github.com/duse-io/secret_sharing_ruby'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.test_files = `git ls-files spec`.split($INPUT_RECORD_SEPARATOR)
18
+ spec.require_paths = ['lib']
19
+ end
@@ -0,0 +1,36 @@
1
+ require 'securerandom'
2
+
3
+ describe SecretSharing::Charset do
4
+ context 'encode and decode' do
5
+ it 'should be able to decode randomly generated encoded strings' do
6
+ 50.times do
7
+ str = SecureRandom.base64(100)
8
+ charset = SecretSharing::Charset.by_charset_string(str)
9
+ encoded = charset.s_to_i(str)
10
+ decoded = charset.i_to_s(encoded)
11
+ expect(decoded).to eq(str)
12
+ end
13
+ end
14
+
15
+ it 'should throw errors when characters that are not in charset' do
16
+ encoder = SecretSharing::Charset.by_charset_string('test')
17
+ expect { encoder.codepoint_to_char(5) }.to raise_error(ArgumentError)
18
+ expect { encoder.char_to_codepoint('r') }.to raise_error(ArgumentError)
19
+ end
20
+
21
+ it 'should throw an error when trying to convert negative numbers' do
22
+ encoder = SecretSharing::Charset.by_charset_string('test')
23
+ expect { encoder.i_to_s(-1) }.to raise_error(ArgumentError)
24
+ end
25
+
26
+ it 'should throw an error when trying to convert something non-integer' do
27
+ encoder = SecretSharing::Charset.by_charset_string('test')
28
+ expect { encoder.i_to_s('error') }.to raise_error(ArgumentError)
29
+ end
30
+
31
+ it 'should return the correct charset for ""' do
32
+ charset = SecretSharing::Charset.by_charset_string('')
33
+ expect(charset).to be_a SecretSharing::Charset::ASCIICharset
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ describe SecretSharing::Point do
2
+ it 'should follow the ruby convention when inspecting' do
3
+ point = SecretSharing::Point.new(1, 2)
4
+ expect(point.inspect).to eq('#<Point: @x=1 @y=2>')
5
+ end
6
+
7
+ it 'should correctly stringify a point' do
8
+ expect(SecretSharing::Point.new(1, 2).to_s).to eq('1-2')
9
+ end
10
+
11
+ it 'should correctly create a point from a correctly formatted string' do
12
+ expect(SecretSharing::Point.new(1, 2).to_s).to eq('1-2')
13
+ end
14
+
15
+ it 'should be able to recreate a point from its string representation' do
16
+ point = SecretSharing::Point.new(1, 2)
17
+ string = point.to_s
18
+ reconstructed_point = SecretSharing::Point.from_string string
19
+ expect(reconstructed_point.x).to eq(point.x)
20
+ expect(reconstructed_point.y).to eq(point.y)
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ describe SecretSharing::Charset do
2
+ it 'should error when trying to generate a polynomial with negative degree' do
3
+ random_generator = -> { SecretSharing::Polynomial.random(-1, 1, 12) }
4
+ expect(&random_generator).to raise_error(ArgumentError)
5
+ end
6
+
7
+ it 'should error when the secret is to long' do
8
+ secret = 2**10_000
9
+ block = -> { SecretSharing::Polynomial.points_from_secret(secret, 2, 3) }
10
+ expect(&block).to raise_error(ArgumentError)
11
+ end
12
+
13
+ it 'should error when the point threshold is too small' do
14
+ block = -> { SecretSharing::Polynomial.points_from_secret(123, 1, 3) }
15
+ expect(&block).to raise_error(ArgumentError)
16
+ end
17
+
18
+ it 'should error when the point threshold is larger than total points' do
19
+ block = -> { SecretSharing::Polynomial.points_from_secret(123, 3, 2) }
20
+ expect(&block).to raise_error(ArgumentError)
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ describe SecretSharing::Prime do
2
+ context '#get_large_enough_prime' do
3
+ it 'should calc the correct prime' do
4
+ expect(SecretSharing::Prime.large_enough_prime([1, 2, 3, 4])).to eq(7)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ require 'securerandom'
2
+
3
+ describe SecretSharing do
4
+ it 'should encrypt and decrypt correctly' do
5
+ 50.times do
6
+ secret = SecureRandom.base64(130)
7
+ num_of_shares = 2 + SecureRandom.random_number(100)
8
+ shares = SecretSharing.split_secret(secret, 2, num_of_shares)
9
+ expect(SecretSharing.recover_secret(shares[0..1])).to eq(secret)
10
+ end
11
+ end
12
+
13
+ it 'should work with non ascii characters as well' do
14
+ secret = 'κόσμε'
15
+ num_of_shares = 2 + SecureRandom.random_number(100)
16
+ shares = SecretSharing.split_secret(secret, 2, num_of_shares)
17
+ expect(SecretSharing.recover_secret(shares[0..1])).to eq(secret)
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ describe SecretSharing::Share do
2
+ it 'correctly parses a string representation of a share' do
3
+ expect(SecretSharing::Share.parse('$$ASCII-1-2')).to eq(['$$ASCII', '1-2'])
4
+ end
5
+
6
+ it 'correctly parses a share when charset contains a "-"' do
7
+ expect(SecretSharing::Share.parse('tes--1-2')).to eq(['tes-', '1-2'])
8
+ end
9
+ end
@@ -0,0 +1,55 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider
11
+ # making a separate helper file that requires the additional dependencies and
12
+ # performs the additional setup, and require it from the spec files that
13
+ # actually need it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+
20
+ unless ENV['CI']
21
+ require 'simplecov'
22
+ SimpleCov.start
23
+ end
24
+
25
+ if ENV['CI']
26
+ require 'coveralls'
27
+ Coveralls.wear!
28
+ end
29
+
30
+ require 'secret_sharing'
31
+
32
+ RSpec.configure do |config|
33
+ # rspec-expectations config goes here. You can use an alternate
34
+ # assertion/expectation library such as wrong or the stdlib/minitest
35
+ # assertions if you prefer.
36
+ config.expect_with :rspec do |expectations|
37
+ # This option will default to `true` in RSpec 4. It makes the `description`
38
+ # and `failure_message` of custom matchers include text for helper methods
39
+ # defined using `chain`, e.g.:
40
+ # be_bigger_than(2).and_smaller_than(4).description
41
+ # # => "be bigger than 2 and smaller than 4"
42
+ # ...rather than:
43
+ # # => "be bigger than 2"
44
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
45
+ end
46
+
47
+ # rspec-mocks config goes here. You can use an alternate test double
48
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
49
+ config.mock_with :rspec do |mocks|
50
+ # Prevents you from mocking or stubbing a method that does not exist on
51
+ # a real object. This is generally recommended, and will default to
52
+ # `true` in RSpec 4.
53
+ mocks.verify_partial_doubles = true
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: secret_sharing
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - flower-pot
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Divide, share and reconstruct secrets.
14
+ email:
15
+ - fbranczyk@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".rubocop.yml"
23
+ - ".travis.yml"
24
+ - ".yardops"
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - LICENSE
28
+ - README.md
29
+ - Rakefile
30
+ - lib/secret_sharing.rb
31
+ - lib/secret_sharing/charset.rb
32
+ - lib/secret_sharing/point.rb
33
+ - lib/secret_sharing/polynomial.rb
34
+ - lib/secret_sharing/prime.rb
35
+ - lib/secret_sharing/share.rb
36
+ - lib/secret_sharing/version.rb
37
+ - secret_sharing.gemspec
38
+ - spec/charset_spec.rb
39
+ - spec/point_spec.rb
40
+ - spec/polynomial_spec.rb
41
+ - spec/prime_spec.rb
42
+ - spec/secret_sharing_spec.rb
43
+ - spec/share_spec.rb
44
+ - spec/spec_helper.rb
45
+ homepage: https://github.com/duse-io/secret_sharing_ruby
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 2.4.3
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Ruby implementation of sharmir's secret sharing
69
+ test_files:
70
+ - spec/charset_spec.rb
71
+ - spec/point_spec.rb
72
+ - spec/polynomial_spec.rb
73
+ - spec/prime_spec.rb
74
+ - spec/secret_sharing_spec.rb
75
+ - spec/share_spec.rb
76
+ - spec/spec_helper.rb
77
+ has_rdoc: