1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#![cfg_attr(feature = "doc-images",
cfg_attr(all(),
doc = ::embed_doc_image::embed_image!("two-degree-polynomial", "./images/two-points-in-two-degree-polynomial.png"),
doc = ::embed_doc_image::embed_image!("lagrange-polynomial", "./images/lagrange-polynomial.png"),
))]
#![cfg_attr(
    not(feature = "doc-images"),
    doc = "**Doc images not enabled**. Compile with feature `doc-images` and Rust version >= 1.54 \
           to enable."
)]
//! # Shamir Secret Sharing
//!
//! This system was explained in a paper by Adi Shamir in 1979: [How to Share a
//! Secret](https://web.mit.edu/6.857/OldStuff/Fall03/ref/Shamir-HowToShareASecret.pdf).
//!
//! Shamir Secret Sharing(SSS) is an algorithm for splitting a secret $S$ into a number of shares, $n$,
//! where at least $k$ shares are required to recover the original secret.
//! SSS also has a few nice properties:
//! 1. It has [information-theoretic security](https://en.wikipedia.org/wiki/Information-theoretic_security)
//!    which means that no matter how much computing power adversaries that may want to crack the system
//!    have, the system remains secure. This is due to a fundamental fact about the way secrets and
//!    shares are generated, explained below.
//! 2. Knowledge of any $k - 1$ shares gives no information to the information of the secret, $S$.
//! 3. It is extensible, which means the number of shares $n$ can be added or deleted without
//!    affecting the existing shares.
//! 4. It is dynamic, which means that if one of the shares is compromised, all of the given
//!    shares can be recalculated to generate a new polynomial without requiring a change of the
//!    secret.
//! 5. It is flexible, which means more shares can be given based on some condition, e.g. if one
//!    party should have twice as many shares as another party, that can be handled by the system.
//!
//! The first two principles are most important: no adversary can crack the system without having the $k$
//! required shares, and anybody without those $k$ shares gets no information about the secret,
//! $S$.
//!
//! This works by the following principle: Imagine you have a polynomial of degree $n$. You require
//! at least $n + 1$ points to uniquely define the polynomial. If you have $\leq n$ points, there
//! are infinitely many polynomials that could fit the given points, thus you have learned nothing
//! about the secret $S$ and are no more informed than a participant with no information.
//!
//! Imagine you are trying to recover a line (a polynomial with a degree of 1) from some number of
//! given points, $k$. If $k = 1$, there are infinitely many lines that pass through your given
//! point. Let's say our 1 point is the y-intercept, and it has a value of 3. How many lines have a
//! y-intercept of 3? Infinitely many, as all of these lines could possibly fit.
//!
//! $$ y = -\infty..\infty + 3 $$
//!
//! Thus you've learned nothing about the line, since you have to take infinitely many guesses to
//! define the line, which is the same as a participant with no information about the given point.
//!
//! This works for a polynomial of any degree. If you want to recover a polynomial with degree 2 (a
//! parabola), you'd need 3 points to recover it.
//!
//! ![Infinite number of polynomials of degree 2 with 2 points][two-degree-polynomial]
//!
//! From: <https://en.wikipedia.org/wiki/File:3_polynomials_of_degree_2_through_2_points.svg>
//!
//! If we have 3 points for our parabola, however, we can recover the unique polynomial. How we do
//! that is discussed in the next section.
//!
//! ## How it works
//!
//! Now that we know what we want to do (keep a secret value by encoding it as a polynomial), let's
//! go over how we do that.
//!
//! First, there should be some function that takes a secret value, and the number of shares
//! desired and returns a polynomial of the desired degree. In this implementation, we set the
//! secret as the y-intercept and each share is a given $x$ value.
//!
//! Second, we should have a function that decrypts the points. If the correct $k$ points are
//! passed to this function, the function returns the secret $S$ back. This is done with lagrange
//! polynomial interpolation. For any polynomial of degree $n$, the algorithm defines a polynomial
//! where $x$ is equal to 1 for the given point $k_i$, and 0 for all other points.
//!
//! Finally, all of the polynomials are multiplied together to recover the original polynomial (the
//! secret polynomial) and its secret value, the y-intercept can be easily found from it.
//!
//! In visual form:
//!
//! ![Lagrange Polynomial][lagrange-polynomial]
//!
//! From: <https://en.wikipedia.org/wiki/Lagrange_polynomial#/media/File:Lagrange_polynomial.svg>
//!
//! Note that the black line is the desired polynomial, and the rest are multiplied together to
//! recover the original polynomial and its y-intercept.
//!
//! In pseudocode that would look like the following:
//! ```txt
//! def interpolate(points: list[(x, y)]) -> number:
//!     let y = 0
//!     for (i, (x_0, y_0)) in enumerate(points):
//!         let l_i = 1
//!         for (j, (x_1, y_1)) in enumerate(points):
//!             if i != j:
//!                 l_i *= x_1 / (x_1 - x_0)
//!         y += l_i * y_0
//!     return y
//! ```
//!
//! And we get back the y-intercept.
//!
//! ## Pitfalls
//!
//! However, SSS also has a few pitfalls:
//!
//! 1. Given that it is information-theoretically secure, if one or more wrong values are provided,
//!    where the number of shares still is $\geq k$, an invalid secret will be returned by the
//!    decryption function.
//!
//! Ideally, we would rather the decryption function tell us which share was incorrect, but there
//! is no way for the function to do so as is. This makes the system attackable indirectly --
//! imagine that of the $k$ parties required, $k-1$ parties are honest and $1$ party is dishonest.
//! The dishonest party could then ask everyone to "reveal" their secret, and also reveal their own
//! phony secret. They would then have information of all the other $k-1$ shares required to
//! recover the secret, and could pair it with their required secret to recover the secret alone.
//! The other $k-1$ members would not be able to find out who was dishonest, since the decryption
//! algorithm gives a successful response but the wrong secret.
//!
use oorandom::Rand32;

use gf256::gf256;

#[cfg(feature = "doc-images")]
use embed_doc_image::embed_doc_image;

/// This function generates a random polynomial for Shamir's secret sharing.
/// It takes a secret and degree of polynomial to create (the amount of shares)
/// It sets the y-intercept to the secret passed in and then generates as many points as there are
/// shares, and returns the polynomial, with the secret as the first item.
/// Imagine you're trying to create a polynomial with a degree of 2:
/// In math notation that would look like this: $ ax^2 + bx + c $
/// Since the y-intercept will be the secret, we can set the secret to $c$.
/// Then, we generate two values, $a$ and $b$, which define the $x^2$ portion of the polynomial and
/// the $x$.
/// Imagine our $a$ is 7, our $b$ is 5 and our secret is 8. The polynomial would look like this:
/// $7x^2 + 5x + 8$.
/// In code, since we populate the values in reverse, that would be: `vec![8, 5, 7]`.
fn poly_random(rng: &mut Rand32, secret: gf256, degree: usize) -> Vec<gf256> {
    let mut f = vec![secret];
    for _ in 0..degree {
        let num = rng.rand_range(1..255) as u8;
        f.push(gf256::new(num));
    }
    f
}

/// This function takes a polynomial and evaluates it.
/// The polynomial is passed in in reverse order:
/// Normally, polynomials are written as $ax^2 + bx + c$, but this function takes the y-intercept
/// first, and then the unknowns, like $c + bx + ax^2$.
fn poly_eval(f: &[gf256], x: gf256) -> gf256 {
    let mut y = gf256::new(0);
    for c in f.iter().rev() {
        y = y * x + c;
    }
    y
}

/// This function performs polynomial interpolation. Given a set of points defined by x and y
/// coordinates, it returns the y-intercept of the unique polynomial that passes through all of the
/// points, with the degree $n - 1$, where $n$ is the number of points passed to the function.
fn poly_interpolate(xs: &[gf256], ys: &[gf256]) -> gf256 {
    assert!(xs.len() == ys.len());

    let mut y = gf256::new(0);
    for (i, (x0, y0)) in xs.iter().zip(ys).enumerate() {
        let mut li = gf256::new(1);
        for (j, (x1, _y1)) in xs.iter().zip(ys).enumerate() {
            if i != j {
                li *= x1 / (x1 - x0);
            }
        }

        y += li * y0;
    }

    y
}

/// This function generates a polynomial with the given secret, passed as bytes.
pub fn generate(secret: &[u8], n: usize, k: usize) -> Vec<Vec<u8>> {
    // we only support up to 255 shares
    assert!(
        n <= usize::try_from(255).unwrap_or(usize::MAX),
        "exceeded {} shares",
        255
    );
    let mut shares = vec![vec![]; n];
    let mut rng = Rand32::new(0);

    // we need to store the x coord somewhere, so just prepend the share with it
    for i in 0..n {
        shares[i].push(u8::try_from(i + 1).unwrap());
    }

    for x in secret {
        // generate a random polynomial for each byte
        let f = poly_random(&mut rng, gf256::new(*x), k - 1);

        // assign each share with a point at f(i)
        for i in 0..n {
            shares[i].push(poly_eval(&f, gf256::new(i as u8 + 1)).0);
        }
    }

    shares
}

/// This function attempts to reconstruct a secret from some amount of shares.
/// Given that this function doesn't know the number of shares required ($k$), it will try to fit a
/// polynomial in any case, thus providing an incorrect secret if there are fewer than $k$ shares
/// provided or at least one of the $k$ provided shares is incorrect.
pub fn reconstruct<S: AsRef<[u8]>>(shares: &[S]) -> Vec<u8> {
    assert!(
        shares
            .windows(2)
            .all(|ss| ss[0].as_ref().len() == ss[1].as_ref().len()),
        "mismatched share length"
    );

    let mut secret = vec![];
    let len = shares.first().map(|s| s.as_ref().len()).unwrap_or(0);
    if len == 0 {
        return secret;
    }

    // x is prepended to each share
    let xs = shares
        .iter()
        .map(|s| gf256::new(s.as_ref()[0]))
        .collect::<Vec<_>>();
    for i in 1..len {
        let ys = shares
            .iter()
            .map(|s| gf256::new(s.as_ref()[i]))
            .collect::<Vec<_>>();
        secret.push(poly_interpolate(&xs, &ys).0);
    }

    secret
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ex() {
        let shares = generate(b"secret secret secret!", 5, 4);

        // <4 can't reconstruct secret
        assert_ne!(reconstruct(&shares[..1]), b"secret secret secret!");
        assert_ne!(reconstruct(&shares[..2]), b"secret secret secret!");
        assert_ne!(reconstruct(&shares[..3]), b"secret secret secret!");

        // >=4 can reconstruct secret
        assert_eq!(reconstruct(&shares[..4]), b"secret secret secret!");
        assert_eq!(reconstruct(&shares[..5]), b"secret secret secret!");
    }
}