17
4
Given n
(the number of players), t
(the threshold value), and s
(the secret), output the n
secrets generated by Shamir's Secret Sharing algorithm.
The Algorithm
For the purposes of this challenge, the computations will be done in GF(251) (the finite field of size 251
, otherwise known as the integers mod 251). Ordinarily, the field would be chosen such that its size is a prime much greater than n
. To simplify the challenge, the field size will be constant. 251
has been chosen because it is the largest prime representable by an 8-bit unsigned integer.
- Generate
t-1
random integers in the (inclusive) range[0, 250]
. Label these a1 through at-1. - Construct a
t-1
th degree polynomial usings
as the constant value and the random integers from step 1 as the coefficients of the powers ofx
: f(x) = s + x*a1 + x2*a2 + ... + xt-1*at-1. - Output
(f(z) mod 251)
for eachz
in the (inclusive) range[1, n]
.
Reference Implementation
#!/usr/bin/env python
from __future__ import print_function
import random
import sys
# Shamir's Secret Sharing algorithm
# Input is taken on the command line, in the format "python shamir.py n t s"
n, t, s = [int(x) for x in sys.argv[1:4]]
if t > n:
print("Error: t must be less than or equal to n")
exit()
if n not in range(2, 251):
print("Error: n must be a positive integer less than 251")
exit()
if t not in range(2, 251):
print("Error: t must be a positive integer less than 251")
exit()
if s not in range(251):
print("Error: s must be a non-negative integer less than 251")
exit()
p = 251
a = [random.randrange(0, 251) for x in range(t-1)]
def f(x):
return s + sum(c*x**(i+1) for i,c in enumerate(a))
# Outputting the polynomial is for explanatory purposes only, and should not be included
# in the output for the challenge
print("f(x) = {0} + {1}".format(s, ' + '.join('{0}*x^{1}'.format(c, i+1) for i,c in enumerate(a))))
for z in range(1, n+1):
print(f(z) % p)
Verification
The following Stack Snippet can be used to verify outputs:
var validate = function() {
$('#nts-error').attr('hidden', parseInt($('#n').val()) >= parseInt($('#t').val()));
$('#check').prop('disabled', parseInt($('#n').val()) < parseInt($('#t').val()));
};
// the following function is taken from https://rosettacode.org/wiki/Combinations#JavaScript
var combinations = function(arr, k) {
var i,
subI,
ret = [],
sub,
next;
for (i = 0; i < arr.length; i++) {
if (k === 1) {
ret.push([arr[i]]);
} else {
sub = combinations(arr.slice(i + 1, arr.length), k - 1);
for (subI = 0; subI < sub.length; subI++) {
next = sub[subI];
next.unshift(arr[i]);
ret.push(next);
}
}
}
return ret;
};
// The following code is taken from https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing#Javascript_example with the modification that the prime is 251, not 257
var prime = 251;
/* Gives the decomposition of the gcd of a and b. Returns [x,y,z] such that x = gcd(a,b) and y*a + z*b = x */
function gcdD(a, b) {
if (b == 0) return [a, 1, 0];
else {
var n = Math.floor(a / b),
c = a % b,
r = gcdD(b, c);
return [r[0], r[2], r[1] - r[2] * n];
}
}
/* Gives the multiplicative inverse of k mod prime. In other words (k * modInverse(k)) % prime = 1 for all prime > k >= 1 */
function modInverse(k) {
k = k % prime;
var r = (k < 0) ? -gcdD(prime, -k)[2] : gcdD(prime, k)[2];
return (prime + r) % prime;
}
/* Join the shares into a number */
function join(shares) {
var accum, count, formula, startposition, nextposition, value, numerator, denominator;
for (formula = accum = 0; formula < shares.length; formula++) {
/* Multiply the numerator across the top and denominators across the bottom to do Lagrange's interpolation
* Result is x0(2), x1(4), x2(5) -> -4*-5 and (2-4=-2)(2-5=-3), etc for l0, l1, l2...
*/
for (count = 0, numerator = denominator = 1; count < shares.length; count++) {
if (formula == count) continue; // If not the same value
startposition = shares[formula][0];
nextposition = shares[count][0];
numerator = (numerator * -nextposition) % prime;
denominator = (denominator * (startposition - nextposition)) % prime;
}
value = shares[formula][1];
accum = (prime + accum + (value * numerator * modInverse(denominator))) % prime;
}
return accum;
}
$('#check').click(
function() {
var good = true;
var problem = [];
var n = parseInt($('#n').val());
var t = parseInt($('#t').val());
var s = parseInt($('#s').val());
combinations([...Array(n + 1).keys()].slice(1), t).forEach(
function(xArr) {
var xfArr = xArr.map(
function(x) {
return [x, parseInt($('#f' + x).val())];
}
);
var L = join(xfArr);
if (L !== s) {
good = false;
problem.push(xArr);
}
}
);
$('#valid').html(good ? "True" : ("False (problematic secret sets: {" + problem.map(
function(elem) {
return "[" + elem.join(', ') + "]";
}) + "})"));
}
);
$('#n').change(
function() {
var inputHTML = "";
for (var i = 1; i <= parseInt($('#n').val()); i++) {
inputHTML += 'f(' + i + '): <input type="number" min="0" max="250" value="0" id="f' + i + '"><br>\r\n';
}
$('#inputs').html(inputHTML);
validate();
}
);
$('#t').change(
function() {
validate();
}
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
n:
<input type="number" min="2" max="250" value="6" id="n">t:
<input type="number" min="2" max="250" value="3" id="t">s:
<input type="number" min="0" max="250" value="230" id="s">
<br>
<div id="nts-error" hidden="true">t must be less than or equal to n!</div>
<br>
<br>
<div id="inputs">
f(1):
<input type="number" min="0" max="250" value="35" id="f1">
<br>f(2):
<input type="number" min="0" max="250" value="95" id="f2">
<br>f(3):
<input type="number" min="0" max="250" value="159" id="f3">
<br>f(4):
<input type="number" min="0" max="250" value="227" id="f4">
<br>f(5):
<input type="number" min="0" max="250" value="48" id="f5">
<br>f(6):
<input type="number" min="0" max="250" value="124" id="f6">
</div>
<br>
<input type="submit" id="check" value="Verify">
<br>
<br>Valid:
<div id="valid"></div>
Rules
s
will be a non-negative integer less than251
, andn
andt
will be positive integers less than251
and greater than1
. Furthermore, you are guaranteed that the inputs are valid (meaningt <= n
).- Input and output can be in any reasonable, unambiguous, and consistent format.
- Random numbers are to be sampled from a uniform distribution - each possible value should have equal probability of being chosen.
1Do we have to output
z
andf(z)
? If I print an array off(z)
s in order,z
is implied by index.[[1, 5], [2, 2], [3, 9], [4, 14]]
does not contain more information than[5, 2, 9, 14]
. – orlp – 2016-07-07T15:44:01.4231Challenge for doing the inverse. – FryAmTheEggman – 2016-07-07T15:45:56.193
@orlp Fair point. – Mego – 2016-07-07T15:46:22.313
Any testcases ? – Leaky Nun – 2016-07-07T15:47:38.193
4@LeakyNun Since this question is tagged [tag:random], I think the verification snippet is much more valuable than test cases that will vary for each run. – FryAmTheEggman – 2016-07-07T15:50:57.440
@Mego I can't get your Stack Snippet to work; it is throwing a syntax error. – TheBikingViking – 2016-07-07T21:07:54.817
@Mego Just a heads up that your snippet is outputting
false
for every input and output except the default one. – R. Kap – 2016-07-08T01:21:53.277@R.Kap I've tested it with other values (
n = 6, t = 3, s = 42, [146, 210, 234, 218, 162, 66]
) and it works. Are you sure the problem isn't in your program? – Mego – 2016-07-08T02:58:55.887@Mego Yeah, I am sure, because, for instance, the input
– R. Kap – 2016-07-08T04:25:32.183n=5, t=4, s=7
produces a valid output[10,247,217,172,113]
based on the 3 integers[221,242,42]
as confirmed by my program and your program, but the snippet outputsfalse
for it.@Mego The same is true for the input
– R. Kap – 2016-07-08T04:33:31.713n=2 ,t=2, s=212
and the output[64,167]
based on the integer chosen[35]
again confirmed by your program.@R.Kap In your last example, the chosen integer is
103
. I agree that there is something funky going on with the verification snippet - I blame Javascript. I'll take a look at it and see if I can figure out what's going wrong. – Mego – 2016-07-08T04:55:18.257@Mego Yeah,
[35]
was I typo. I meant to say[103]
. – R. Kap – 2016-07-08T05:21:18.810