secret_sharing 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/.rspec +1 -0
- data/.rubocop.yml +0 -4
- data/.travis.yml +10 -2
- data/README.md +1 -0
- data/Rakefile +3 -0
- data/lib/secret_sharing.rb +5 -6
- data/lib/secret_sharing/charset.rb +102 -117
- data/lib/secret_sharing/point.rb +4 -13
- data/lib/secret_sharing/polynomial.rb +18 -6
- data/lib/secret_sharing/prime.rb +21 -19
- data/lib/secret_sharing/share.rb +6 -3
- data/lib/secret_sharing/version.rb +1 -1
- data/spec/charset_spec.rb +101 -19
- data/spec/point_spec.rb +29 -4
- data/spec/polynomial_spec.rb +106 -14
- data/spec/prime_spec.rb +4 -4
- data/spec/secret_sharing_spec.rb +29 -1
- data/spec/share_spec.rb +39 -9
- data/spec/spec_helper.rb +6 -0
- metadata +10 -11
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MTU5NTYxM2RmYzRmOTdlMzVlYzczNTg1ODc2NDBmYTcwOGNiNWY2Yg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NjA3MGFiOGNiYzlhOGQ0MWIxYjNkM2VlNDVkMGZiYmQ2MzgyZTgzZA==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NGM2YjkzZDQyMGYyNGU5NzhhMGUxZjAxMTVmZTg4MGJjNTRkNTI1NGJmYzkw
|
10
|
+
NzAwN2Y4NGQ3MmJkZWNlN2NiNjhkM2FkYTExZDFlNTNjMDM4MDYyNDdjNWNi
|
11
|
+
MmYyOGUwNTU1ZDE0MTAzZmU4NzUzZGViMmM2OTRiYTUxN2M4NTQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NzRmYjIwMzk2MWJmYTUxY2YxMjIzYzUyOWMwMDVhZGIwZGI5NTIxOTlhNDdl
|
14
|
+
OWNjYzczNzJkMmJhOTBjYzk2Yjc3ZWVmMmFmNjI4ZmI3ZWZhM2M0Y2RiNjIz
|
15
|
+
MDBkYTU0NGE3NGU3MTg1MmQyYmE4ZmE5MmZmMzI1MjQzNjI3ZWQ=
|
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,7 +1,3 @@
|
|
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
1
|
Documentation:
|
6
2
|
Enabled: false # this is handled by yard and it is annoying since rubocop does
|
7
3
|
# not notice if a top level class documentation has already
|
data/.travis.yml
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
|
4
|
-
bundler_args: --without development
|
3
|
+
- 2.2.2
|
4
|
+
bundler_args: "--without development"
|
5
5
|
cache: bundler
|
6
6
|
sudo: false
|
7
|
+
deploy:
|
8
|
+
provider: rubygems
|
9
|
+
api_key:
|
10
|
+
secure: aNkBOgCW7ZCsDHp/tSv1C2yxu1FVh9XoClyGNc6IXrvdJ/OouvuLucNXxazD53YnbaRqM5QxyJbl2wMNHrlp7aakjSmmPcHcYGWpZU0AzCaIvzCdoLPxL5GMR0K1CIdyL7KDTDv5ntmEA4iINACYcm20v0EBhY02Tbt1DI/nc9c=
|
11
|
+
gem: secret_sharing
|
12
|
+
on:
|
13
|
+
tags: true
|
14
|
+
repo: duse-io/secret_sharing_ruby
|
data/README.md
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/secret_sharing.svg)](http://badge.fury.io/rb/secret_sharing)
|
1
2
|
[![Build Status](https://travis-ci.org/duse-io/secret_sharing_ruby.svg?branch=master)](https://travis-ci.org/duse-io/secret_sharing_ruby)
|
2
3
|
[![Coverage Status](https://coveralls.io/repos/duse-io/secret_sharing_ruby/badge.svg?branch=master)](https://coveralls.io/r/duse-io/secret_sharing_ruby?branch=master)
|
3
4
|
[![Code Climate](https://codeclimate.com/github/duse-io/secret_sharing_ruby/badges/gpa.svg)](https://codeclimate.com/github/duse-io/secret_sharing_ruby)
|
data/Rakefile
CHANGED
data/lib/secret_sharing.rb
CHANGED
@@ -9,6 +9,8 @@ require 'secret_sharing/share'
|
|
9
9
|
# connection to be consumed by a user. Do not use any other class/module of
|
10
10
|
# this library unless you really know what you are doing.
|
11
11
|
module SecretSharing
|
12
|
+
extend self
|
13
|
+
|
12
14
|
# Split a secret using Shamir's Secret Sharing algorithm.
|
13
15
|
#
|
14
16
|
# Example
|
@@ -22,13 +24,13 @@ module SecretSharing
|
|
22
24
|
#
|
23
25
|
# @return [Array] Array of shares that can be used to recover the secret.
|
24
26
|
def split_secret(secret_string, share_threshold, num_shares)
|
25
|
-
secret_int = Charset::ASCIICharset.
|
27
|
+
secret_int = Charset::ASCIICharset.s_to_i(secret_string)
|
26
28
|
points = Polynomial.points_from_secret(secret_int,
|
27
29
|
share_threshold,
|
28
30
|
num_shares)
|
29
31
|
|
30
32
|
points.map do |point|
|
31
|
-
Share.new(point).to_s
|
33
|
+
Share.new(point).to_s(Math.log10(points.length).floor + 1)
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
@@ -47,9 +49,6 @@ module SecretSharing
|
|
47
49
|
shares = raw_shares.map { |raw_share| Share.from_string raw_share }
|
48
50
|
points = shares.map(&:point)
|
49
51
|
secret_int = Polynomial.modular_lagrange_interpolation(points)
|
50
|
-
Charset::ASCIICharset.
|
52
|
+
Charset::ASCIICharset.i_to_s(secret_int)
|
51
53
|
end
|
52
|
-
|
53
|
-
module_function :split_secret,
|
54
|
-
:recover_secret
|
55
54
|
end
|
@@ -1,138 +1,123 @@
|
|
1
1
|
require 'set'
|
2
2
|
|
3
3
|
module SecretSharing
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
class
|
8
|
-
# A new instance of DynamicCharset. The constructor should only be used
|
9
|
-
# when you know what you are doing. Usually you only want to use this
|
10
|
-
# constructor when you recreate a charset and the order of the charset is
|
11
|
-
# important. The "null-byte" character is prepended to be the first
|
12
|
-
# character in the charset to avoid loosing the first character of the
|
13
|
-
# charset when it is also the first character in a string to convert.
|
14
|
-
#
|
15
|
-
# Example
|
16
|
-
#
|
17
|
-
# SecretSharing::Charset::DynamicCharset.new ['a', 'b', 'c']
|
18
|
-
# # => #<SecretSharing::Charset::DynamicCharset @charset=[...]>
|
19
|
-
#
|
20
|
-
# @param charset [Array] array of characters to use for the charset.
|
21
|
-
def initialize(charset)
|
22
|
-
@charset = charset.unshift("\u0000")
|
23
|
-
end
|
4
|
+
# Charset can represent any charset which does not have the null byte
|
5
|
+
class Charset
|
6
|
+
class NotInCharset < ArgumentError; end
|
7
|
+
class NotPositiveInteger < ArgumentError; end
|
24
8
|
|
25
|
-
|
26
|
-
|
27
|
-
# Example
|
28
|
-
#
|
29
|
-
# charset = SecretSharing::Charset.by_charset_string 'abc'
|
30
|
-
# charset.i_to_s 6
|
31
|
-
# # => "ab"
|
32
|
-
#
|
33
|
-
# @param x [Integer] integer to convert to string
|
34
|
-
# @return [String] converted string
|
35
|
-
def i_to_s(x)
|
36
|
-
unless x.is_a?(Integer) && x >= 0
|
37
|
-
fail ArgumentError, 'x must be a non-negative integer'
|
38
|
-
end
|
9
|
+
# @return [Array] internal representation of the charset
|
10
|
+
attr_reader :charset
|
39
11
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
12
|
+
# The "null-byte" character is prepended to be the first character in the
|
13
|
+
# charset to avoid loosing the first character of the charset when it is
|
14
|
+
# also the first character in a string to convert.
|
15
|
+
#
|
16
|
+
# Example
|
17
|
+
#
|
18
|
+
# SecretSharing::Charset::DynamicCharset.new ['a', 'b', 'c'] # =>
|
19
|
+
# #<SecretSharing::Charset::DynamicCharset @charset=[...]>
|
20
|
+
#
|
21
|
+
# @param charset [Array] array of characters to use for the charset.
|
22
|
+
def initialize(charset)
|
23
|
+
@charset = ["\u0000"] + charset
|
24
|
+
@charset.freeze
|
25
|
+
end
|
47
26
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
27
|
+
# Calculate a string from an integer.
|
28
|
+
#
|
29
|
+
# Example
|
30
|
+
#
|
31
|
+
# charset = SecretSharing::Charset.by_charset_string 'abc'
|
32
|
+
# charset.i_to_s 6
|
33
|
+
# # => "ab"
|
34
|
+
#
|
35
|
+
# @param input [Integer] integer to convert to string
|
36
|
+
# @return [String] converted string
|
37
|
+
def i_to_s(input)
|
38
|
+
if !input.is_a?(Integer) || input < 0
|
39
|
+
fail NotPositiveInteger, 'input must be a non-negative integer'
|
62
40
|
end
|
63
41
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
#
|
69
|
-
# charset = SecretSharing::Charset.by_charset_string 'abc'
|
70
|
-
# charset.codepoint_to_char 1
|
71
|
-
# # => "a"
|
72
|
-
#
|
73
|
-
# @param codepoint [Integer] Codepoint to retrieve the character for
|
74
|
-
# @return [String] Retrieved character
|
75
|
-
def codepoint_to_char(codepoint)
|
76
|
-
char = @charset[codepoint]
|
77
|
-
return char unless char.nil?
|
78
|
-
fail ArgumentError, "Codepoint #{codepoint} does not exist in charset"
|
42
|
+
output = ''
|
43
|
+
while input > 0
|
44
|
+
input, codepoint = input.divmod(charset.length)
|
45
|
+
output.prepend(codepoint_to_char(codepoint))
|
79
46
|
end
|
47
|
+
output
|
48
|
+
end
|
80
49
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
return codepoint unless codepoint.nil?
|
95
|
-
error_msg = "Character \"#{c}\" not part of the supported charset"
|
96
|
-
fail ArgumentError, error_msg
|
50
|
+
# Calculate an integer from a string.
|
51
|
+
#
|
52
|
+
# Example
|
53
|
+
#
|
54
|
+
# charset = SecretSharing::Charset.by_charset_string 'abc'
|
55
|
+
# charset.s_to_i "ab"
|
56
|
+
# # => 6
|
57
|
+
#
|
58
|
+
# @param string [Integer] integer to convert to string
|
59
|
+
# @return [String] converted string
|
60
|
+
def s_to_i(string)
|
61
|
+
string.chars.reduce(0) do |output, char|
|
62
|
+
output * charset.length + char_to_codepoint(char)
|
97
63
|
end
|
64
|
+
end
|
98
65
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
66
|
+
# Convert an integer into its string representation according to the
|
67
|
+
# charset. (only one character)
|
68
|
+
#
|
69
|
+
# Example
|
70
|
+
#
|
71
|
+
# charset = SecretSharing::Charset.by_charset_string 'abc'
|
72
|
+
# charset.codepoint_to_char 1
|
73
|
+
# # => "a"
|
74
|
+
#
|
75
|
+
# @param codepoint [Integer] Codepoint to retrieve the character for
|
76
|
+
# @return [String] Retrieved character
|
77
|
+
def codepoint_to_char(codepoint)
|
78
|
+
if charset.at(codepoint).nil?
|
79
|
+
fail NotInCharset, "Codepoint #{codepoint} does not exist in charset"
|
108
80
|
end
|
81
|
+
charset.at(codepoint)
|
82
|
+
end
|
109
83
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
84
|
+
# Convert a single character into its integer representation according to
|
85
|
+
# the charset.
|
86
|
+
#
|
87
|
+
# Example
|
88
|
+
#
|
89
|
+
# charset = SecretSharing::Charset.by_charset_string 'abc'
|
90
|
+
# charset.char_to_codepoint 'a'
|
91
|
+
# # => 1
|
92
|
+
#
|
93
|
+
# @param c [String] Character to retrieve its codepoint in the charset
|
94
|
+
# @return [Integer] Codepoint within the charset
|
95
|
+
def char_to_codepoint(c)
|
96
|
+
codepoint = charset.index c
|
97
|
+
if codepoint.nil?
|
98
|
+
fail NotInCharset, "Char \"#{c}\" not part of the supported charset"
|
124
99
|
end
|
100
|
+
codepoint
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check if the provided string can be represented by the charset.
|
104
|
+
#
|
105
|
+
# Example
|
106
|
+
#
|
107
|
+
# charset = SecretSharing::Charset.by_charset_string 'abc'
|
108
|
+
# charset.subset? 'd'
|
109
|
+
# # => false
|
110
|
+
# charset.subset? 'a'
|
111
|
+
# # => true
|
112
|
+
#
|
113
|
+
# @param string [String] Character to retrieve the for codepoint
|
114
|
+
# @return [TrueClass|FalseClass]
|
115
|
+
def subset?(string)
|
116
|
+
(string.chars - charset).empty?
|
125
117
|
end
|
126
118
|
|
127
119
|
# Charset that can represent any string that only consists of ASCII
|
128
120
|
# characters.
|
129
|
-
|
130
|
-
# An instance of an ASCIICharset is just a DynamicCharset with the ASCII
|
131
|
-
# characters.
|
132
|
-
def initialize
|
133
|
-
super((1..127).to_a.map(&:chr))
|
134
|
-
end
|
135
|
-
end
|
121
|
+
ASCIICharset = new((1..127).to_a.map(&:chr))
|
136
122
|
end
|
137
123
|
end
|
138
|
-
|
data/lib/secret_sharing/point.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
module SecretSharing
|
2
2
|
# This class is used to store a two dimensional point consisting of integers.
|
3
3
|
class Point
|
4
|
-
|
4
|
+
# @return [Integer] x value the point was instantiated with
|
5
|
+
attr_reader :x
|
6
|
+
# @return [Integer] y value the point was instantiated with
|
7
|
+
attr_reader :y
|
5
8
|
|
6
9
|
# Create a Point object.
|
7
10
|
#
|
@@ -17,18 +20,6 @@ module SecretSharing
|
|
17
20
|
@y = y
|
18
21
|
end
|
19
22
|
|
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
23
|
# An implementation similar to Array#transpose for Arrays of Points
|
33
24
|
#
|
34
25
|
# @return [Array] Two Arrays in an Array
|
@@ -4,17 +4,27 @@ module SecretSharing
|
|
4
4
|
# The polynomial is used to represent the required random polynomials used in
|
5
5
|
# Shamir's Secret Sharing algorithm.
|
6
6
|
class Polynomial
|
7
|
+
attr_reader :coefficients
|
8
|
+
|
7
9
|
# Create a new instance of a Polynomial with n coefficients, when having
|
8
10
|
# the polynomial in standard polynomial form.
|
9
11
|
#
|
10
12
|
# Example
|
11
13
|
#
|
14
|
+
# For the polynomial f(x) = a0 + a1 * x + a2 * x^2 + ... + an * x^n the
|
15
|
+
# coefficients are [a0, a1, a2, ..., an]
|
16
|
+
#
|
12
17
|
# Polynomial.new [1, 2, 3]
|
13
18
|
# # => #<SecretSharing::Polynomial:0x0000000 @coefficients=[1, 2, 3]>
|
14
19
|
#
|
15
20
|
# @param coefficients [Array] an array of integers as the coefficients
|
16
21
|
def initialize(coefficients)
|
22
|
+
coefficients.each do |c|
|
23
|
+
not_an_int = 'One or more coefficents are not integers'
|
24
|
+
fail ArgumentError, not_an_int unless c.is_a?(Integer)
|
25
|
+
end
|
17
26
|
@coefficients = coefficients
|
27
|
+
@coefficients.freeze
|
18
28
|
end
|
19
29
|
|
20
30
|
# Generate points on the polynomial, that can be used to reconstruct the
|
@@ -29,8 +39,9 @@ module SecretSharing
|
|
29
39
|
# @param prime [Integer] prime for calculation in finite field
|
30
40
|
# @return [Array] array of calculated points
|
31
41
|
def points(num_points, prime)
|
42
|
+
intercept = @coefficients[0] # the first coefficient is the intercept
|
32
43
|
(1..num_points).map do |x|
|
33
|
-
y =
|
44
|
+
y = intercept
|
34
45
|
(1...@coefficients.length).each do |i|
|
35
46
|
exponentiation = x**i % prime
|
36
47
|
term = (@coefficients[i] * exponentiation) % prime
|
@@ -54,10 +65,10 @@ module SecretSharing
|
|
54
65
|
def self.random(degree, intercept, upper_bound)
|
55
66
|
fail ArgumentError, 'Degree must be a non-negative number' if degree < 0
|
56
67
|
|
57
|
-
coefficients = (0...degree).reduce([intercept]) do |accumulator
|
68
|
+
coefficients = (0...degree).reduce([intercept]) do |accumulator|
|
58
69
|
accumulator << SecureRandom.random_number(upper_bound)
|
59
70
|
end
|
60
|
-
|
71
|
+
new coefficients
|
61
72
|
end
|
62
73
|
|
63
74
|
# Generate points from a secret integer.
|
@@ -72,8 +83,7 @@ module SecretSharing
|
|
72
83
|
# @param num_points [Integer] number of points to generate
|
73
84
|
# @return [Polynomial] the generated polynomial
|
74
85
|
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?
|
86
|
+
prime = SecretSharing::Prime.large_enough_prime([secret_int, num_points].max)
|
77
87
|
fail ArgumentError, 'Threshold must be at least 2' if point_threshold < 2
|
78
88
|
fail ArgumentError, 'Threshold must be less than the total number of points' if point_threshold > num_points
|
79
89
|
|
@@ -81,12 +91,14 @@ module SecretSharing
|
|
81
91
|
secret_int,
|
82
92
|
prime)
|
83
93
|
polynomial.points(num_points, prime)
|
94
|
+
rescue Prime::CannotFindLargeEnoughPrime
|
95
|
+
raise ArgumentError, 'Secret is too long'
|
84
96
|
end
|
85
97
|
|
86
98
|
# Modular lagrange interpolation
|
87
99
|
def self.modular_lagrange_interpolation(points)
|
88
100
|
y_values = Point.transpose(points)[1]
|
89
|
-
prime = SecretSharing::Prime.large_enough_prime(y_values)
|
101
|
+
prime = SecretSharing::Prime.large_enough_prime(y_values.max)
|
90
102
|
points.reduce(0) do |f_x, point|
|
91
103
|
numerator, denominator = lagrange_fraction(points, point, prime)
|
92
104
|
lagrange_polynomial = numerator * mod_inverse(denominator, prime)
|
data/lib/secret_sharing/prime.rb
CHANGED
@@ -1,44 +1,46 @@
|
|
1
1
|
module SecretSharing
|
2
2
|
# This module is used to generate/calculate/retrieve primes.
|
3
3
|
module Prime
|
4
|
+
class CannotFindLargeEnoughPrime < StandardError; end
|
5
|
+
|
6
|
+
extend self
|
7
|
+
|
4
8
|
# Retrieves the next largest prime for the largest number in batch
|
5
9
|
#
|
6
10
|
# Example
|
7
11
|
#
|
8
|
-
# Prime.large_enough_prime
|
12
|
+
# Prime.large_enough_prime 4
|
9
13
|
# # => 7
|
10
14
|
#
|
11
|
-
# @param
|
12
|
-
# @return [Integer] the next largest prime
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
2**256 + 297, 2**320 + 27, 2**384 + 231
|
17
|
-
].sort
|
18
|
-
|
15
|
+
# @param input [Integer] the integer to find the next largest prime for
|
16
|
+
# @return [Integer] the next largest prime
|
17
|
+
# @raise [CannotFindLargeEnoughPrime] raised when input is too large and
|
18
|
+
# no large enough prime can be found
|
19
|
+
def large_enough_prime(input)
|
19
20
|
standard_primes.each do |prime|
|
20
|
-
|
21
|
-
return prime if greater_than_prime.empty?
|
21
|
+
return prime if prime > input
|
22
22
|
end
|
23
|
-
|
23
|
+
fail CannotFindLargeEnoughPrime, 'Input too large'
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
+
# Merges the mersenne primes with the smallst 257, 321, and 385 bit primes
|
29
|
+
def standard_primes
|
30
|
+
mersenne_primes + [
|
31
|
+
# smallest 257, 321 and 385 bit primes
|
32
|
+
2**256 + 297, 2**320 + 27, 2**384 + 231
|
33
|
+
].sort
|
34
|
+
end
|
35
|
+
|
28
36
|
# Calculates the first 15 mersenne primes
|
29
37
|
def mersenne_primes
|
30
38
|
mersenne_prime_exponents = [
|
31
39
|
2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279
|
32
40
|
]
|
33
41
|
mersenne_prime_exponents.map do |exp|
|
34
|
-
|
35
|
-
prime *= 2**exp
|
36
|
-
prime -= 1
|
37
|
-
prime
|
42
|
+
(2**exp) - 1
|
38
43
|
end
|
39
44
|
end
|
40
|
-
|
41
|
-
module_function :mersenne_primes,
|
42
|
-
:large_enough_prime
|
43
45
|
end
|
44
46
|
end
|
data/lib/secret_sharing/share.rb
CHANGED
@@ -2,6 +2,7 @@ module SecretSharing
|
|
2
2
|
# A share is an object that encapsulates the properties of a share created by
|
3
3
|
# Shamir's Secret Sharing algorithm.
|
4
4
|
class Share
|
5
|
+
# @return [SecretSharing::Point] Point the share was instantiated with
|
5
6
|
attr_reader :point
|
6
7
|
|
7
8
|
# Create a share object
|
@@ -25,8 +26,8 @@ module SecretSharing
|
|
25
26
|
# SecretSharing::Share.new(point).to_s
|
26
27
|
# # => "1-2"
|
27
28
|
#
|
28
|
-
def to_s
|
29
|
-
"#{point.x}-#{point.y.to_s(16)}"
|
29
|
+
def to_s(x_length)
|
30
|
+
"#{point.x.to_s.rjust(x_length, '0')}-#{point.y.to_s(16)}"
|
30
31
|
end
|
31
32
|
|
32
33
|
# Creates a share object from its string representation.
|
@@ -36,10 +37,12 @@ module SecretSharing
|
|
36
37
|
# SecretSharing::Share.from_string "1-2"
|
37
38
|
# # => #<SecretSharing::Share:0x0000000 @point=...>
|
38
39
|
#
|
40
|
+
# @param share_string [String]
|
41
|
+
# @return [SecretSharing::Share] parsed share
|
39
42
|
def self.from_string(share_string)
|
40
43
|
x_string, y_string = share_string.split '-'
|
41
44
|
point = Point.new x_string.to_i, y_string.to_i(16)
|
42
|
-
|
45
|
+
new(point)
|
43
46
|
end
|
44
47
|
end
|
45
48
|
end
|
data/spec/charset_spec.rb
CHANGED
@@ -1,31 +1,113 @@
|
|
1
1
|
require 'securerandom'
|
2
2
|
|
3
3
|
describe SecretSharing::Charset do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
subject(:charset) { SecretSharing::Charset.new %w(a b c) }
|
5
|
+
|
6
|
+
describe '#initialize' do
|
7
|
+
it 'adds a non printable character in front of the charset' do
|
8
|
+
expect(charset.charset).to eq ["\u0000", 'a', 'b', 'c']
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'makes the internal representation of the charset immutable' do
|
12
|
+
expect { charset.charset << 'x' }.to raise_error(RuntimeError)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#i_to_s' do
|
17
|
+
it 'converts 0 to empty string' do
|
18
|
+
expect(charset.i_to_s(0)).to eq ''
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'converts 1 to "a"' do
|
22
|
+
expect(charset.i_to_s(1)).to eq 'a'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'converts 6 to "ab"' do
|
26
|
+
expect(charset.i_to_s(6)).to eq 'ab'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'throws an error when trying to convert negative numbers' do
|
30
|
+
expect { charset.i_to_s(-1) }.to raise_error(
|
31
|
+
SecretSharing::Charset::NotPositiveInteger,
|
32
|
+
'input must be a non-negative integer'
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'throws an error when trying to convert something non-integer' do
|
37
|
+
expect { charset.i_to_s('error') }.to raise_error(
|
38
|
+
SecretSharing::Charset::NotPositiveInteger,
|
39
|
+
'input must be a non-negative integer'
|
40
|
+
)
|
13
41
|
end
|
42
|
+
end
|
14
43
|
|
15
|
-
|
16
|
-
|
17
|
-
expect
|
18
|
-
expect { charset.char_to_codepoint('ä') }.to raise_error(ArgumentError)
|
44
|
+
describe '#s_to_i' do
|
45
|
+
it 'converts "a" to 1' do
|
46
|
+
expect(charset.s_to_i('a')).to eq 1
|
19
47
|
end
|
20
48
|
|
21
|
-
it '
|
22
|
-
charset
|
23
|
-
expect { charset.i_to_s(-1) }.to raise_error(ArgumentError)
|
49
|
+
it 'converts "ab" to 6' do
|
50
|
+
expect(charset.s_to_i('ab')).to eq 6
|
24
51
|
end
|
52
|
+
end
|
25
53
|
|
26
|
-
|
27
|
-
|
28
|
-
expect
|
54
|
+
describe '#codepoint_to_char' do
|
55
|
+
it 'returns "b" for 2' do
|
56
|
+
expect(charset.codepoint_to_char(2)).to eq 'b'
|
29
57
|
end
|
58
|
+
|
59
|
+
it 'throws an error on an integer outside of the charset' do
|
60
|
+
expect { charset.codepoint_to_char(4) }.to raise_error(
|
61
|
+
SecretSharing::Charset::NotInCharset,
|
62
|
+
'Codepoint 4 does not exist in charset'
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#char_to_codepoint' do
|
68
|
+
it 'returns 2 for "b"' do
|
69
|
+
expect(charset.char_to_codepoint('b')).to eq 2
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'throws an error on a character that is not in the charset' do
|
73
|
+
expect { charset.char_to_codepoint('d') }.to raise_error(
|
74
|
+
SecretSharing::Charset::NotInCharset,
|
75
|
+
"Char \"d\" not part of the supported charset"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#subset?' do
|
81
|
+
context 'the input is a subset' do
|
82
|
+
it 'returns true' do
|
83
|
+
expect(charset.subset?('ab')).to be true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'the input is not a subset' do
|
88
|
+
it 'returns false' do
|
89
|
+
expect(charset.subset?('abcdef')).to be false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'the input does not only contain unique characters' do
|
94
|
+
it 'is still a subset' do
|
95
|
+
expect(charset.subset?('aabc')).to be true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'SecretSharing::Charset::ASCIICharset' do
|
102
|
+
it 'is a 128 character long charset' do
|
103
|
+
expect(SecretSharing::Charset::ASCIICharset.charset.length).to eq 128
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'supports all printable characters in ASCII' do
|
107
|
+
all_chars = " !\"#$%&'()*+,-./0123456789:;<=>?@" \
|
108
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`" \
|
109
|
+
"abcdefghijklmnopqrstuvwxyz{|}~\n"
|
110
|
+
int = SecretSharing::Charset::ASCIICharset.s_to_i all_chars
|
111
|
+
expect(SecretSharing::Charset::ASCIICharset.i_to_s(int)).to eq all_chars
|
30
112
|
end
|
31
113
|
end
|
data/spec/point_spec.rb
CHANGED
@@ -1,6 +1,31 @@
|
|
1
|
-
describe SecretSharing::Point do
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
RSpec.describe SecretSharing::Point do
|
2
|
+
describe '#initialize' do
|
3
|
+
it 'sets the coordinates correctly' do
|
4
|
+
point = SecretSharing::Point.new(1, 2)
|
5
|
+
expect(point.x).to be 1
|
6
|
+
expect(point.y).to be 2
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.transpose' do
|
11
|
+
it 'returns an array consisting of two further arrays' do
|
12
|
+
result = SecretSharing::Point.transpose []
|
13
|
+
expect(result).to be_an Array
|
14
|
+
expect(result.length).to be 2
|
15
|
+
expect(result.first).to be_an Array
|
16
|
+
expect(result.last).to be_an Array
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'puts all x values in the first and all y values in the second array' do
|
20
|
+
result = SecretSharing::Point.transpose([
|
21
|
+
SecretSharing::Point.new(1, 2),
|
22
|
+
SecretSharing::Point.new(3, 4),
|
23
|
+
SecretSharing::Point.new(5, 6)
|
24
|
+
])
|
25
|
+
expect(result).to eq([
|
26
|
+
[1, 3, 5],
|
27
|
+
[2, 4, 6]
|
28
|
+
])
|
29
|
+
end
|
5
30
|
end
|
6
31
|
end
|
data/spec/polynomial_spec.rb
CHANGED
@@ -1,22 +1,114 @@
|
|
1
|
-
describe SecretSharing::
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
RSpec.describe SecretSharing::Polynomial do
|
2
|
+
describe '#initialize' do
|
3
|
+
it 'does not error when all coefficients are integers' do
|
4
|
+
SecretSharing::Polynomial.new [1, 2, 3]
|
5
|
+
end
|
6
|
+
|
7
|
+
it 'makes the coefficients immutable' do
|
8
|
+
polynomial = SecretSharing::Polynomial.new [1, 2, 3]
|
9
|
+
expect(polynomial.coefficients).to eq [1, 2, 3]
|
10
|
+
expect { polynomial.coefficients << 4 }.to raise_error(RuntimeError)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'raises an error when a coefficient is not an integer' do
|
14
|
+
expect { SecretSharing::Polynomial.new(['']) }.to raise_error(
|
15
|
+
ArgumentError,
|
16
|
+
'One or more coefficents are not integers'
|
17
|
+
)
|
18
|
+
end
|
5
19
|
end
|
6
20
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
21
|
+
describe '#points' do
|
22
|
+
context '5 points for f(x)=x^4 + 2' do
|
23
|
+
subject(:polynomial) { SecretSharing::Polynomial.new([2, 0, 0, 0, 1]) }
|
24
|
+
|
25
|
+
it 'generates 5 points' do
|
26
|
+
expect(polynomial.points(5, 3).length).to eq 5
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'generates points (1, 0), (2, 0) and (3, 2) for f(x)=x^4 + 2' do
|
30
|
+
points = polynomial.points(5, 7)
|
31
|
+
expect(points[0].x).to eq 1
|
32
|
+
expect(points[0].y).to eq 3
|
33
|
+
expect(points[1].x).to eq 2
|
34
|
+
expect(points[1].y).to eq 4
|
35
|
+
expect(points[2].x).to eq 3
|
36
|
+
expect(points[2].y).to eq 6
|
37
|
+
expect(points[3].x).to eq 4
|
38
|
+
expect(points[3].y).to eq 6
|
39
|
+
expect(points[4].x).to eq 5
|
40
|
+
expect(points[4].y).to eq 4
|
41
|
+
end
|
42
|
+
end
|
11
43
|
end
|
12
44
|
|
13
|
-
|
14
|
-
|
15
|
-
|
45
|
+
describe '.random' do
|
46
|
+
it 'errors when trying to generate a polynomial with negative degree' do
|
47
|
+
random_generator = -> { SecretSharing::Polynomial.random(-1, 1, 12) }
|
48
|
+
expect(&random_generator).to raise_error(
|
49
|
+
ArgumentError,
|
50
|
+
'Degree must be a non-negative number'
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'can create polynomials of degree 0' do
|
55
|
+
polynomial = SecretSharing::Polynomial.random(0, 5, 10)
|
56
|
+
expect(polynomial.coefficients).to eq [5]
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'randomly generates coefficients' do
|
60
|
+
allow(SecureRandom).to receive(:random_number).and_return(3, 5, 2)
|
61
|
+
polynomial = SecretSharing::Polynomial.random(3, 5, 10)
|
62
|
+
expect(polynomial.coefficients).to eq [5, 3, 5 ,2]
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'uses the intercept as the first coefficient' do
|
66
|
+
polynomial = SecretSharing::Polynomial.random(3, 5, 10)
|
67
|
+
expect(polynomial.coefficients.first).to eq 5
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'creates a polynomial with degree as number of coefficients' do
|
71
|
+
polynomial = SecretSharing::Polynomial.random(3, 3, 10)
|
72
|
+
expect(polynomial.coefficients.length).to eq 4
|
73
|
+
end
|
16
74
|
end
|
17
75
|
|
18
|
-
|
19
|
-
|
20
|
-
|
76
|
+
describe '.points_from_secret' do
|
77
|
+
it 'generates points according to the num_points parameter' do
|
78
|
+
points = SecretSharing::Polynomial.points_from_secret(10, 2, 4)
|
79
|
+
expect(points.length).to eq 4
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'requires threshold points to reconstruct' do
|
83
|
+
points = SecretSharing::Polynomial.points_from_secret(234234, 2, 3)
|
84
|
+
points = points[0, 2]
|
85
|
+
secret = SecretSharing::Polynomial.modular_lagrange_interpolation(points)
|
86
|
+
expect(secret).to eq 234234
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'errors when the secret is to long' do
|
90
|
+
secret = 2**10_000
|
91
|
+
block = -> { SecretSharing::Polynomial.points_from_secret(secret, 2, 3) }
|
92
|
+
expect(&block).to raise_error(
|
93
|
+
ArgumentError,
|
94
|
+
'Secret is too long'
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'errors when the point threshold is too small' do
|
99
|
+
block = -> { SecretSharing::Polynomial.points_from_secret(123, 1, 3) }
|
100
|
+
expect(&block).to raise_error(
|
101
|
+
ArgumentError,
|
102
|
+
'Threshold must be at least 2'
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'errors when the point threshold is larger than total points' do
|
107
|
+
block = -> { SecretSharing::Polynomial.points_from_secret(123, 3, 2) }
|
108
|
+
expect(&block).to raise_error(
|
109
|
+
ArgumentError,
|
110
|
+
'Threshold must be less than the total number of points'
|
111
|
+
)
|
112
|
+
end
|
21
113
|
end
|
22
114
|
end
|
data/spec/prime_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
describe SecretSharing::Prime do
|
2
|
-
|
3
|
-
it '
|
4
|
-
expect(SecretSharing::Prime.large_enough_prime(
|
1
|
+
RSpec.describe SecretSharing::Prime do
|
2
|
+
describe '#get_large_enough_prime' do
|
3
|
+
it 'calc the correct prime' do
|
4
|
+
expect(SecretSharing::Prime.large_enough_prime(4)).to eq(7)
|
5
5
|
end
|
6
6
|
end
|
7
7
|
end
|
data/spec/secret_sharing_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'securerandom'
|
2
2
|
|
3
|
-
describe SecretSharing do
|
3
|
+
RSpec.describe SecretSharing do
|
4
4
|
it 'should encrypt and decrypt correctly' do
|
5
5
|
50.times do
|
6
6
|
secret = SecureRandom.base64(130)
|
@@ -9,4 +9,32 @@ describe SecretSharing do
|
|
9
9
|
expect(SecretSharing.recover_secret(shares[0..1])).to eq(secret)
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
context 'threshold 2 out of 4 shares' do
|
14
|
+
subject(:shares) { SecretSharing.split_secret('secret', 2, 4) }
|
15
|
+
|
16
|
+
it 'generates 4 shares' do
|
17
|
+
expect(shares.length). to eq 4
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'cannot reconstruct with only one share' do
|
21
|
+
expect(SecretSharing.recover_secret(shares[0, 1])).not_to eq 'secret'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can reconstruct the secret with any combination of 2 shares' do
|
25
|
+
shares.permutation(2).each do |share_combination|
|
26
|
+
expect(SecretSharing.recover_secret(share_combination)).to eq 'secret'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can reconstruct the secret with any combination of 3 shares' do
|
31
|
+
shares.permutation(3).each do |share_combination|
|
32
|
+
expect(SecretSharing.recover_secret(share_combination)).to eq 'secret'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'can reconstruct the secret with all shares' do
|
37
|
+
expect(SecretSharing.recover_secret(shares)).to eq 'secret'
|
38
|
+
end
|
39
|
+
end
|
12
40
|
end
|
data/spec/share_spec.rb
CHANGED
@@ -1,13 +1,43 @@
|
|
1
|
-
describe SecretSharing::Share do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
RSpec.describe SecretSharing::Share do
|
2
|
+
describe '.from_string' do
|
3
|
+
it 'creates an instance from a string' do
|
4
|
+
share = SecretSharing::Share.from_string('1-2')
|
5
|
+
point = share.point
|
6
|
+
expect(point.x).to be 1
|
7
|
+
expect(point.y).to be 2
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'correctly converts the y value from hex to integer' do
|
11
|
+
share = SecretSharing::Share.from_string('1-1b')
|
12
|
+
point = share.point
|
13
|
+
expect(point.x).to be 1
|
14
|
+
expect(point.y).to be 27
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'correctly converts paddings' do
|
18
|
+
share = SecretSharing::Share.from_string('01-1b')
|
19
|
+
point = share.point
|
20
|
+
expect(point.x).to be 1
|
21
|
+
expect(point.y).to be 27
|
22
|
+
end
|
7
23
|
end
|
8
24
|
|
9
|
-
|
10
|
-
|
11
|
-
|
25
|
+
describe '#to_s' do
|
26
|
+
it 'stringifies the share by concatenating the x and y value' do
|
27
|
+
point = SecretSharing::Point.new(1, 2)
|
28
|
+
expect(SecretSharing::Share.new(point).to_s(1)).to eq('1-2')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'converts the y value to hex' do
|
32
|
+
point = SecretSharing::Point.new(1, 27)
|
33
|
+
expect(SecretSharing::Share.new(point).to_s(1)).to eq('1-1b')
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'a x point length is specified' do
|
37
|
+
it 'adds 0 padding so all shares are equal length' do
|
38
|
+
point = SecretSharing::Point.new(1, 27)
|
39
|
+
expect(SecretSharing::Share.new(point).to_s(2)).to eq('01-1b')
|
40
|
+
end
|
41
|
+
end
|
12
42
|
end
|
13
43
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -27,6 +27,8 @@ if ENV['CI']
|
|
27
27
|
Coveralls.wear!
|
28
28
|
end
|
29
29
|
|
30
|
+
require 'timeout'
|
31
|
+
|
30
32
|
require 'secret_sharing'
|
31
33
|
|
32
34
|
RSpec.configure do |config|
|
@@ -52,4 +54,8 @@ RSpec.configure do |config|
|
|
52
54
|
# `true` in RSpec 4.
|
53
55
|
mocks.verify_partial_doubles = true
|
54
56
|
end
|
57
|
+
|
58
|
+
config.around :each do |block|
|
59
|
+
Timeout.timeout(1, &block)
|
60
|
+
end
|
55
61
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secret_sharing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- flower-pot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-04-23 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Divide, share and reconstruct secrets.
|
14
14
|
email:
|
@@ -17,11 +17,11 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
-
-
|
21
|
-
-
|
22
|
-
-
|
23
|
-
-
|
24
|
-
-
|
20
|
+
- .gitignore
|
21
|
+
- .rspec
|
22
|
+
- .rubocop.yml
|
23
|
+
- .travis.yml
|
24
|
+
- .yardops
|
25
25
|
- Gemfile
|
26
26
|
- LICENSE
|
27
27
|
- README.md
|
@@ -51,17 +51,17 @@ require_paths:
|
|
51
51
|
- lib
|
52
52
|
required_ruby_version: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
|
-
- -
|
54
|
+
- - ! '>='
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: '0'
|
57
57
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
requirements: []
|
63
63
|
rubyforge_project:
|
64
|
-
rubygems_version: 2.4.
|
64
|
+
rubygems_version: 2.4.5
|
65
65
|
signing_key:
|
66
66
|
specification_version: 4
|
67
67
|
summary: Ruby implementation of sharmir's secret sharing
|
@@ -73,4 +73,3 @@ test_files:
|
|
73
73
|
- spec/secret_sharing_spec.rb
|
74
74
|
- spec/share_spec.rb
|
75
75
|
- spec/spec_helper.rb
|
76
|
-
has_rdoc:
|