cloversplitter 0.2.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 +7 -0
- data/lib/cloversplitter.rb +230 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7f5dad685414f25162e92b3169f4780edf7934455fe9e6da0880576b3e08e8c0
|
4
|
+
data.tar.gz: 5b0e6c43be0738f1ac3fd7d613359c86fc8b61b8081efdc519ba111b261b52d4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d787ad4c93b31ac76d3b50a8f211bbe031891b3d3f4bdf994687524bbf3ed940652ad4325865031c195a292de711be017a4980c02caef8185281cb76ab6ce6de
|
7
|
+
data.tar.gz: e62ed85aff2468beea69a22d488902761557b16ab65fc953a0abe917f59f88e74d30c691365fd0dbada1be698e2a108eb3dff6f71189128f4bd0ccd382c1c167
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# Title: CLOVERSPLITTER
|
2
|
+
# Version: 0.2.1
|
3
|
+
#
|
4
|
+
# WARNING: Please be aware that this gem has not undergone any form of security evaluation. This gem is not recommended for usage under mission-critical circumstances and should not be relied upon to protect confidential or secret information. Users should assume that this gem is insecure until they can independently confirm otherwise.
|
5
|
+
#
|
6
|
+
# Copyright (C) 2020 Peter Bruce Funnell
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
13
|
+
# furnished to do so, subject to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be included in all
|
16
|
+
# copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
# SOFTWARE.
|
25
|
+
|
26
|
+
require "securerandom"
|
27
|
+
|
28
|
+
class CloverSplitter
|
29
|
+
def self.eval_at(poly, x, prime)
|
30
|
+
# Evaluates polynomial at x (used for share generation).
|
31
|
+
accum = 0
|
32
|
+
|
33
|
+
poly.reverse.each do |coeff|
|
34
|
+
accum = ((accum*x)+coeff) % prime
|
35
|
+
end
|
36
|
+
|
37
|
+
return accum
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.str_to_int(a)
|
41
|
+
# Turns an ASCII-8BIT string into an integer.
|
42
|
+
|
43
|
+
# Prepend \x80 to the string to prevent information loss occuring for strings that start with \x00.
|
44
|
+
a = "\x80".force_encoding("ASCII-8BIT")+a
|
45
|
+
|
46
|
+
# Decode string into a list of integers (with one integer representing each character).
|
47
|
+
b = a.each_byte.to_a
|
48
|
+
|
49
|
+
# Initialise c_b; this will be used to hold bytes (expressed in binary) for each character.
|
50
|
+
c_b = String.new()
|
51
|
+
|
52
|
+
# Loop through each value in b, appending the corresponding binary sequence for each character to c_b.
|
53
|
+
b.each do |i|
|
54
|
+
n = i.to_s(2)
|
55
|
+
while n.length < 8 do
|
56
|
+
n = "0"+n
|
57
|
+
end
|
58
|
+
c_b += n
|
59
|
+
end
|
60
|
+
|
61
|
+
# Convert c_b into an integer.
|
62
|
+
c_b.to_i(2)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.int_to_str(a)
|
66
|
+
# Turns an integer into a string.
|
67
|
+
|
68
|
+
# Convert the integer into a string of binary.
|
69
|
+
b = a.to_s(2)
|
70
|
+
|
71
|
+
# Add zeroes to the start of the string of binary until the length is a multiple of 8.
|
72
|
+
while b.length % 8 != 0 do
|
73
|
+
b = "0"+b
|
74
|
+
end
|
75
|
+
|
76
|
+
# Convert the string of binary into a list of character values.
|
77
|
+
c = []
|
78
|
+
c_a = b.chars.each_slice(8).to_a
|
79
|
+
c_a.each do |i|
|
80
|
+
s = String.new()
|
81
|
+
i.each do |j|
|
82
|
+
s << j
|
83
|
+
end
|
84
|
+
c << s.to_i(2)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Convert the list of character values back into a string.
|
88
|
+
d = String.new()
|
89
|
+
c.each do |i|
|
90
|
+
d << i.chr
|
91
|
+
end
|
92
|
+
|
93
|
+
# Ignore the first byte (which will always be \x80).
|
94
|
+
d = d[1, d.length-1]
|
95
|
+
|
96
|
+
return d
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.generate_shares(secret, minimum=3, shares=6, prime=((2**3217)-1))
|
100
|
+
# This function takes a secret string and spits out a list of shares.
|
101
|
+
# Changing the default values for minimum, shares and prime can cause errors or other problems.
|
102
|
+
# secret: This argument should be set to the secret string from which the user wishes to generate shares.
|
103
|
+
# minimum: The minimum number of shares required to recover the secret. [DEFAULT: 3]
|
104
|
+
# shares: The total number of shares to be generated. [DEFAULT: 6]
|
105
|
+
# prime: The prime number used for share generation. [DEFAULT: ((2**3217-1)) [NOTE: This is the 18th Mersenne Prime]]
|
106
|
+
|
107
|
+
# Convert the secret into ASCII-8BIT.
|
108
|
+
secret = secret.force_encoding("ASCII-8BIT")
|
109
|
+
|
110
|
+
# Convert the secret into an integer.
|
111
|
+
secret_int = self.str_to_int(secret)
|
112
|
+
|
113
|
+
# Ensure that the parameters make at least some sense.
|
114
|
+
if minimum > shares
|
115
|
+
raise("The total number of shares to be generated must equal or exceed the minimum number of shares required for recovery.")
|
116
|
+
end
|
117
|
+
|
118
|
+
# Create a list of coefficients for the polynomial, consisting of the secret integer and a series of randomly generated integers.
|
119
|
+
# The total length of the list should be the minimum number of shares required for recovery of the secret integer.
|
120
|
+
poly = [secret_int]+(Array.new(minimum-1) {(SecureRandom.rand(prime))})
|
121
|
+
|
122
|
+
# Evaluate n points on the polynomial, where n is the total number of shares to be generated.
|
123
|
+
points = []
|
124
|
+
(1..shares).each do |i|
|
125
|
+
points << [i, eval_at(poly, i, prime)]
|
126
|
+
end
|
127
|
+
|
128
|
+
# Attempt a test recovery of the secret from the set of all points.
|
129
|
+
check = self.recover_secret(points, prime)
|
130
|
+
|
131
|
+
# If the test recovery yielded the original secret, then the shares are valid and should be returned; if not, something went wrong.
|
132
|
+
if check == secret
|
133
|
+
return points
|
134
|
+
else
|
135
|
+
raise("Something went wrong during share generation. This could be due to the secret being too long or due to the minimum, shares and/or prime parameters being misconfigured.")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.egcd(a, b)
|
140
|
+
# Extended Euclidean algorithm.
|
141
|
+
x, y = 0, 1
|
142
|
+
last_x, last_y = 1, 0
|
143
|
+
|
144
|
+
while b != 0 do
|
145
|
+
quot = a/b
|
146
|
+
a, b = b, a % b
|
147
|
+
x, last_x = last_x-quot*x, x
|
148
|
+
y, last_y = last_y-quot*y, y
|
149
|
+
end
|
150
|
+
|
151
|
+
return last_x, last_y
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.divmod(num, den, p)
|
155
|
+
a, b = egcd(den, p)
|
156
|
+
|
157
|
+
return num*a
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.prod(vals)
|
161
|
+
# Calculate the product of a list of values.
|
162
|
+
accum = 1
|
163
|
+
|
164
|
+
vals.each do |v|
|
165
|
+
accum *= v
|
166
|
+
end
|
167
|
+
|
168
|
+
return accum
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.sum(vals)
|
172
|
+
# Calculate the sum of a list of values.
|
173
|
+
accum = 0
|
174
|
+
|
175
|
+
vals.each do |v|
|
176
|
+
accum += v
|
177
|
+
end
|
178
|
+
|
179
|
+
return accum
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.lagrange_interpolate(x, x_s, y_s, p)
|
183
|
+
k = x_s.length
|
184
|
+
|
185
|
+
if k != x_s.uniq.length
|
186
|
+
raise("An error occured during lagrange interpolation.")
|
187
|
+
end
|
188
|
+
|
189
|
+
nums = []
|
190
|
+
dens = []
|
191
|
+
(0..k-1).each do |i|
|
192
|
+
others = Array.new(x_s)
|
193
|
+
cur = others.delete_at(i)
|
194
|
+
nums_a = Array.new()
|
195
|
+
dens_a = Array.new()
|
196
|
+
others.each do |o|
|
197
|
+
nums_a << x-o
|
198
|
+
dens_a << cur-o
|
199
|
+
end
|
200
|
+
nums << prod(nums_a)
|
201
|
+
dens << prod(dens_a)
|
202
|
+
end
|
203
|
+
|
204
|
+
den = prod(dens)
|
205
|
+
num_a = Array.new()
|
206
|
+
(0..(k-1)).each do |i|
|
207
|
+
num_a << divmod(nums[i]*den*y_s[i] % p, dens[i], p)
|
208
|
+
end
|
209
|
+
|
210
|
+
num = sum(num_a)
|
211
|
+
|
212
|
+
return (divmod(num, den, p)+p) % p
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.recover_secret(shares, prime=((2**3217)-1))
|
216
|
+
# This function takes a list of shares and attempts to recover the secret string from those shares. If the number of shares is below the minimum number of shares required for recovery, the resulting string will be nonsensical and may scramble/gargle the console if printed. On a *nix system, the console can usually be reset with the "reset" command.
|
217
|
+
# shares: A list containing two or more shares.
|
218
|
+
# prime: The prime number that was used for share generation.
|
219
|
+
|
220
|
+
if shares.length < 2
|
221
|
+
raise("At least two shares are required in order to attempt secret recovery.")
|
222
|
+
end
|
223
|
+
|
224
|
+
x_s, y_s = shares.transpose
|
225
|
+
|
226
|
+
recovered_data = lagrange_interpolate(0, x_s, y_s, prime)
|
227
|
+
|
228
|
+
return self.int_to_str(recovered_data)
|
229
|
+
end
|
230
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloversplitter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Funnell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-06-09 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: 'A pure-Ruby implementation of Shamir''s Secret Sharing. WARNING: Please
|
14
|
+
be aware that this gem has not undergone any form of security evaluation. This gem
|
15
|
+
is not recommended for usage under mission-critical circumstances and should not
|
16
|
+
be relied upon to protect confidential or secret information. Users should assume
|
17
|
+
that this gem is insecure until they can independently confirm otherwise.'
|
18
|
+
email: hello@octetsplicer.com
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- lib/cloversplitter.rb
|
24
|
+
homepage: https://github.com/octetsplicer/CLOVERSPLITTER
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 2.5.5
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.7.6.2
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Shamir's Secret Sharing in Ruby
|
48
|
+
test_files: []
|