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-1random integers in the (inclusive) range[0, 250]. Label these a1 through at-1. - Construct a
t-1th degree polynomial usingsas 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 eachzin 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
swill be a non-negative integer less than251, andnandtwill be positive integers less than251and 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
zandf(z)? If I print an array off(z)s in order,zis 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
falsefor 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=7produces 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 outputsfalsefor it.@Mego The same is true for the input
– R. Kap – 2016-07-08T04:33:31.713n=2 ,t=2, s=212and 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