Skip to main content

arcis_compiler/core/circuits/key_recovery/
utils.rs

1pub mod reed_solomon {
2    use crate::{
3        core::{
4            actually_used_field::ActuallyUsedField,
5            circuits::boolean::boolean_value::Boolean,
6            expressions::field_expr::FieldExpr,
7            global_value::value::FieldValue,
8        },
9        traits::{GreaterEqual, GreaterThan, Invert, Random, Reveal},
10        utils::field::BaseField,
11    };
12    use num_traits::ToPrimitive;
13    use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
14
15    #[derive(Debug)]
16    #[allow(dead_code)]
17    pub struct KeyRecoveryDesc {
18        pub n: usize,
19        pub k: usize,
20        pub d: usize,
21    }
22
23    impl KeyRecoveryDesc {
24        #[allow(dead_code)]
25        pub fn new(n: usize) -> Self {
26            let k = (n - 1) / 3 + 1;
27            Self { n, k, d: n - k + 1 }
28        }
29    }
30
31    // The notation is taken from [here](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction#The_BCH_view:_The_codeword_as_a_sequence_of_coefficients)
32    // and [here](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction#Peterson%E2%80%93Gorenstein%E2%80%93Zierler_decoder).
33    // N stands for an upper bound on the block length n, K for an upper bound
34    // on the message length k, and D = N - K + 1 for the distance.
35    // If t is the threshold of malicious nodes we want to support we set k = t + 1.
36    #[derive(Clone, Debug, Default)]
37    pub struct KeyRecoveryReedSolomonInit;
38
39    #[allow(dead_code)]
40    impl KeyRecoveryReedSolomonInit {
41        // Compute the coefficients of the generator polynomial
42        // g(x) = (x - alpha) * (x - alpha^2) * .. * (x - alpha^(d - 1))
43        // and store them in an array of size D.
44        // This is computed by each node.
45        pub fn compute_generator_polynomial<const D: usize, F: ActuallyUsedField>(
46            d: usize,
47        ) -> [F; D] {
48            let mut g = [F::from(0); D];
49            let mut alpha_pow_j = F::MULTIPLICATIVE_GENERATOR;
50            g[0] = -alpha_pow_j;
51            g[1] = F::from(1);
52            for _ in 0..d - 2 {
53                alpha_pow_j *= F::MULTIPLICATIVE_GENERATOR;
54                let mut tmp = g;
55                tmp.rotate_right(1);
56                for i in 0..D {
57                    g[i] *= -alpha_pow_j;
58                    g[i] += tmp[i];
59                }
60            }
61
62            g
63        }
64
65        /// Encodes the value val using Reed-Solomon encoding.
66        /// The message polynomial is any degree-k-1 polynomial
67        /// whose sum of coefficients equals val (you can see this
68        /// as k-secret-sharing val and then define a polynomial with
69        /// coefficients the secret-shares). The message polynomial is
70        /// then encoded by multiplying with the generator polynomial g.
71        // Here, D is the number of coefficients of the generator polynomial
72        // (i.e., it is of degree D - 1) and N is the length of the code.
73        // The message is thus of length K = N - D + 1.
74        // Typical values: N = 300, D = 201 and K = 100.
75        // This guarantees that up to 99 corrupt peers cannot recover the message,
76        // nor can they sabotage the recovery and would be identified if they tried.
77        pub fn encode<
78            const N: usize,
79            const D: usize,
80            B: Boolean,
81            T: Random
82                + Mul<T, Output = T>
83                + Sub<T, Output = T>
84                + AddAssign<T>
85                + SubAssign<T>
86                + GreaterEqual<T, Output = B>
87                + From<B>
88                + From<usize>
89                + Copy,
90        >(
91            val: T,
92            g: [T; D],
93            k: T,
94        ) -> [T; N] {
95            // Secret-share val and view the secret-shares as the coefficients of
96            // the message polynomial p(x). The first k coefficients are non-zero,
97            // the remaining ones are 0.
98            let mut p = vec![val];
99            for i in 0..N - D {
100                let r = T::from(T::from(i).lt(k - T::from(1))) * T::random();
101                p[0] -= r;
102                p.push(r);
103            }
104
105            // Perform the polynomial multiplication between
106            // g(x) = g[0] + g[1] * x + .. + g[D - 1] * x^(D - 1) and
107            // p(x) = p[0] + p[1] * x + .. + p[N - D] * x^(N - D),
108            // which yields s(x) of degree N - 1.
109            // Note: the actual degrees of g, p and s might be less (for p the degree is k - 1).
110            let mut s = [T::from(0); N];
111            for i in 0..N {
112                for j in 0..=i {
113                    if i - j < D && j < N - D + 1 {
114                        s[i] += g[i - j] * p[j];
115                    }
116                }
117            }
118
119            s
120        }
121    }
122
123    #[derive(Clone, Debug, Default)]
124    pub struct KeyRecoveryReedSolomonFinal;
125
126    #[allow(dead_code)]
127    impl KeyRecoveryReedSolomonFinal {
128        /// Given the N coefficients of the polynomial r, compute d - 1 syndromes
129        /// r(alpha), r(alpha^2), .., r(alpha^(d - 1)).
130        pub fn compute_syndromes<
131            const N: usize,
132            const DMINUSONE: usize,
133            F: ActuallyUsedField,
134            B: Boolean,
135            T: Mul<T, Output = T>
136                + AddAssign<T>
137                + MulAssign<T>
138                + GreaterEqual<T, Output = B>
139                + From<B>
140                + From<F>
141                + From<usize>
142                + Reveal
143                + Copy,
144        >(
145            r: [T; N],
146            d_minus_one: T,
147        ) -> [T; DMINUSONE] {
148            // Can be done more efficiently..
149            let mut syndromes = [r[0]; DMINUSONE];
150            let mut alpha_pow_j = F::MULTIPLICATIVE_GENERATOR;
151            for (j, syndrome) in syndromes.iter_mut().enumerate() {
152                let mut pow = alpha_pow_j;
153                for r_i in r.iter().skip(1) {
154                    *syndrome += *r_i * T::from(pow);
155                    pow *= alpha_pow_j;
156                }
157                // Set syndrome to 0 for j >= d - 1.
158                *syndrome *= T::from(T::from(j).lt(d_minus_one));
159                alpha_pow_j *= F::MULTIPLICATIVE_GENERATOR;
160            }
161
162            syndromes.map(|syndrome| syndrome.reveal())
163        }
164
165        /// Compute the errors from plaintext syndromes, using Berlekamp-Massey, Chien search and
166        /// Forney's algorithm.
167        pub fn compute_errors<const N: usize, const DMINUSONE: usize>(
168            d_minus_one: FieldValue<BaseField>,
169            syndromes: [FieldValue<BaseField>; DMINUSONE],
170        ) -> [FieldValue<BaseField>; N] {
171            let mut errors = [FieldValue::from(0); N];
172            for (i, e) in errors.iter_mut().enumerate() {
173                *e = FieldValue::new(FieldExpr::KeyRecoveryComputeErrors(
174                    d_minus_one,
175                    syndromes.to_vec(),
176                    i,
177                ));
178            }
179            errors
180        }
181
182        /// Compute the errors from plaintext syndromes, using Berlekamp-Massey, Chien search and
183        /// Forney's algorithm.
184        #[allow(non_snake_case)]
185        pub fn compute_errors_field<const N: usize, F: ActuallyUsedField>(
186            d_minus_one: F,
187            syndromes: Vec<F>,
188        ) -> [F; N] {
189            let d_minus_one = d_minus_one
190                .to_unsigned_number()
191                .to_usize()
192                .expect("d_minus_one way too large");
193            let syndromes = syndromes.into_iter().take(d_minus_one).collect::<Vec<F>>();
194
195            // Compute the error locator polynomial Lambda using the Berlekamp-Massey algorithm.
196            // Notation-wise we follow [this](https://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm#Pseudocode).
197            let mut C = vec![F::from(1)];
198            let mut B = vec![F::from(1)];
199            let mut L = 0usize;
200            let mut m = 1usize;
201            let mut b = F::from(1);
202            for n in 0..syndromes.len() {
203                let mut d = syndromes[n];
204                for i in 1..=L {
205                    d += C[i] * syndromes[n - i];
206                }
207
208                if d.eq(&F::from(0)) {
209                    m += 1;
210                } else if 2 * L <= n {
211                    let T = C.clone();
212                    C.resize((m + B.len()).max(C.len()), F::from(0));
213                    // on plaintext values we simply set is_expected_non_zero = true
214                    let db_inv = d * b.invert(true);
215                    for i in 0..B.len() {
216                        C[m + i] -= db_inv * B[i];
217                    }
218                    L = n + 1 - L;
219                    B = T;
220                    b = d;
221                    m = 1usize;
222                } else {
223                    C.resize((m + B.len()).max(C.len()), F::from(0));
224                    // on plaintext values we simply set is_expected_non_zero = true
225                    let db_inv = d * b.invert(true);
226                    for i in 0..B.len() {
227                        C[m + i] -= db_inv * B[i];
228                    }
229                    m += 1;
230                }
231            }
232            // L now corresponds to the number of errors and C corresponds to Lambda.
233
234            // We perform the [Chien search](https://en.wikipedia.org/wiki/Chien_search)
235            // to find the roots of the reversed Lambda.
236            let mut alpha_pows = vec![F::from(1)];
237            for i in 0..L {
238                alpha_pows.push(alpha_pows[i] * F::MULTIPLICATIVE_GENERATOR);
239            }
240            let mut lambdas = C.iter().copied().rev().collect::<Vec<F>>();
241            let mut error_locations = vec![lambdas
242                .iter()
243                .copied()
244                .reduce(|a, b| a + b)
245                .unwrap()
246                .eq(&F::from(0))];
247            for _ in 0..N - 1 {
248                lambdas = lambdas
249                    .iter()
250                    .zip(alpha_pows.clone())
251                    .map(|(lambda, pow)| *lambda * pow)
252                    .collect::<Vec<F>>();
253                error_locations.push(
254                    lambdas
255                        .iter()
256                        .copied()
257                        .reduce(|a, b| a + b)
258                        .unwrap()
259                        .eq(&F::from(0)),
260                );
261            }
262
263            // [Forney's algorithm](https://en.wikipedia.org/wiki/Forney_algorithm).
264            let Lambda_prime = C
265                .iter()
266                .enumerate()
267                .skip(1)
268                .map(|(i, lambda)| F::from(i as u64) * lambda)
269                .collect::<Vec<F>>();
270            let mut Omega = Vec::new();
271            for i in 0..L {
272                let mut omega_i = F::from(0);
273                for j in 0..=i {
274                    if i - j < syndromes.len() && j < L + 1 {
275                        omega_i += syndromes[i - j] * C[j];
276                    }
277                }
278                Omega.push(omega_i);
279            }
280
281            fn eval_poly<F: ActuallyUsedField>(poly: Vec<F>, x: F) -> F {
282                let mut res = poly[0];
283                let mut pow = F::from(1);
284                for c in poly.into_iter().skip(1) {
285                    pow *= x;
286                    res += c * pow;
287                }
288                res
289            }
290
291            let mut errors = [F::from(0); N];
292            let mut pow = F::from(1);
293            for (i, e) in error_locations.into_iter().enumerate() {
294                if e {
295                    let X_inv = pow.invert(true);
296                    let num = eval_poly(Omega.clone(), X_inv);
297                    let denom = eval_poly(Lambda_prime.clone(), X_inv);
298                    // on plaintext values we simply set is_expected_non_zero = true
299                    errors[i] = -num * denom.invert(true);
300                }
301                pow *= F::MULTIPLICATIVE_GENERATOR;
302            }
303
304            errors
305        }
306
307        // We know that the generator polynomial g does not vanish at the consecutive
308        // powers alpha^d, .., alpha^(d + k - 1). Observe that for i = 0, .., k - 1, we have
309        // s(alpha^(d + i)) = p(alpha^(d + i)) * g(alpha^(d + i)).
310        // It suffices to evaluate s(alpha^(d + i)), for i = 0, .., k - 1, to know the value
311        // of p at k distinct points, more precisely,
312        // p(alpha^(d + i)) = s(alpha^(d + i)) / g(alpha^(d + i)), for i = 0, .., k - 1.
313        // Since we are interested in p(1) we compute the Lagrange polynomials evaluated at 1,
314        // l_{alpha^(d + i)}(1), for i = 0, .., k - 1, and divide them by g(alpha^(d + i)).
315        // This will be computed by each node.
316        pub fn compute_scaled_polynomials<const K: usize, F: ActuallyUsedField>(
317            d: usize,
318            k: usize,
319        ) -> ([F; K], [F; K]) {
320            let alpha_pows = (0..d + k - 1)
321                .scan(F::from(1), |acc, _| {
322                    *acc *= F::MULTIPLICATIVE_GENERATOR;
323                    Some(*acc)
324                })
325                .collect::<Vec<F>>();
326            // Lagrange polynomials at 1 multiplied by the inverse of g(alpha^(d + i)).
327            let mut scaled_polynomials = alpha_pows
328                .iter()
329                .copied()
330                .skip(d - 1)
331                .map(|x| {
332                    // numerator of Lagrange polynomial
333                    let num = alpha_pows
334                        .iter()
335                        .copied()
336                        .skip(d - 1)
337                        .filter(|val| *val != x)
338                        .map(|val| F::from(1) - val)
339                        .reduce(|a, b| a * b)
340                        .unwrap();
341                    // denominator of Lagrange polynomial
342                    let denom = alpha_pows
343                        .iter()
344                        .copied()
345                        .skip(d - 1)
346                        .filter(|val| *val != x)
347                        .map(|val| x - val)
348                        .reduce(|a, b| a * b)
349                        .unwrap();
350                    let poly = num * denom.invert(true);
351                    let g_at_alpha_pow = alpha_pows
352                        .iter()
353                        .copied()
354                        .take(d - 1)
355                        .map(|z| x - z)
356                        .reduce(|a, b| a * b)
357                        .unwrap();
358                    poly * g_at_alpha_pow.invert(true)
359                })
360                .collect::<Vec<F>>();
361
362            // We remove the first d - 1 powers of alpha and
363            // fill up alpha_pows and scaled_polynomials with zeros.
364            let mut alpha_pows = alpha_pows.iter().copied().skip(d - 1).collect::<Vec<F>>();
365            alpha_pows.resize(K, F::from(0));
366            scaled_polynomials.resize(K, F::from(0));
367
368            (
369                alpha_pows.try_into().unwrap_or_else(|v: Vec<F>| {
370                    panic!("Expected a Vec of length {} (found {})", K, v.len())
371                }),
372                scaled_polynomials.try_into().unwrap_or_else(|v: Vec<F>| {
373                    panic!("Expected a Vec of length {} (found {})", K, v.len())
374                }),
375            )
376        }
377
378        /// Once the error polynomial e is computed, subtract it from r.
379        pub fn subtract_errors<const N: usize, T: Sub<T, Output = T>>(
380            r: [T; N],
381            e: [T; N],
382        ) -> [T; N] {
383            r.into_iter()
384                .zip(e)
385                .map(|(coeff_r, coeff_e)| coeff_r - coeff_e)
386                .collect::<Vec<T>>()
387                .try_into()
388                .unwrap_or_else(|v: Vec<T>| {
389                    panic!("Expected a Vec of length {} (found {})", N, v.len())
390                })
391        }
392
393        /// Recover the polynomial p from the corrected codeword s and evaluate at 1
394        /// to obtain val (equivalently, sum the coefficients of p).
395        pub fn decode<
396            const N: usize,
397            const K: usize,
398            B: Boolean,
399            T: Mul<T, Output = T>
400                + MulAssign<T>
401                + Add<T, Output = T>
402                + AddAssign<T>
403                + GreaterThan<T, Output = B>
404                + From<usize>
405                + Copy,
406        >(
407            s: [T; N],
408            alpha_pows: [T; K],
409            scaled_polynomials: [T; K],
410        ) -> T {
411            // compute s(alpha^d), .., s(alpha^(d + k - 1))
412            let s_at_alpha_pows = alpha_pows
413                .into_iter()
414                .map(|alpha_pow_j| {
415                    let mut pow = alpha_pow_j;
416                    let mut s_at_alpha_pow = s[0];
417                    for s_i in s.iter().skip(1) {
418                        s_at_alpha_pow += *s_i * pow;
419                        pow *= alpha_pow_j;
420                    }
421                    s_at_alpha_pow
422                })
423                .collect::<Vec<T>>();
424
425            // scaled_polynomials are of the form l_{alpha^(d + i)}(1) / g(alpha^(d + i)),
426            // for i = 0, .., k - 1. Multiplying the ith s_at_alpha_pow by the ith scaled_polynomial
427            // yields s(alpha^(d + i)) * l_{alpha^(d + i)}(1) / g(alpha^(d + i))
428            // = p(alpha^(d + i)) * l_{alpha^(d + i)}(1).
429            // Summing all the above yields p(1).
430            scaled_polynomials
431                .into_iter()
432                .zip(s_at_alpha_pows)
433                .map(|(poly, s_at_alpha_pow)| poly * s_at_alpha_pow)
434                .reduce(|a, b| a + b)
435                .unwrap()
436        }
437    }
438}
439
440pub mod shamir {
441    use crate::{
442        core::{
443            actually_used_field::ActuallyUsedField,
444            bounds::FieldBounds,
445            circuits::{
446                boolean::boolean_value::{Boolean, BooleanValue},
447                traits::arithmetic_circuit::ArithmeticCircuit,
448            },
449            expressions::expr::EvalFailure,
450            global_value::value::FieldValue,
451        },
452        traits::{GreaterThan, Invert, Random},
453        utils::number::Number,
454    };
455    use rand::{seq::SliceRandom, thread_rng};
456    use std::ops::{Add, AddAssign, Mul, MulAssign};
457
458    #[derive(Clone, Debug, Default)]
459    pub struct KeyRecoveryShamirInit;
460
461    impl KeyRecoveryShamirInit {
462        /// Shamir secret-shares the value val as the constant term of a polynomial
463        /// of degree deg and evaluates at the points 1, .., n (inclusive).
464        /// The remaining N - n secret-shares are set to 0.
465        pub fn shamir_secret_share<
466            const N: usize,
467            B: Boolean,
468            T: Random
469                + Mul<T, Output = T>
470                + MulAssign<T>
471                + AddAssign<T>
472                + GreaterThan<T, Output = B>
473                + From<B>
474                + From<usize>
475                + Copy,
476        >(
477            val: T,
478            deg: T,
479            n: T,
480        ) -> [T; N] {
481            let mut coeffs = vec![val];
482            coeffs.extend((0..N - 1).map(|_| T::random()));
483            (1..N + 1)
484                .map(|i| {
485                    let mut res = T::from(0);
486                    // it is important to set pow = 0 as soon as i > n
487                    // otherwise, whoever gets to decrypt sufficiently many of
488                    // these secret-shares will be able to recover the key
489                    let mut pow = T::from(T::from(i).le(n));
490                    coeffs.iter().enumerate().for_each(|(j, c)| {
491                        res += pow * *c * T::from(T::from(j).le(deg));
492                        pow *= T::from(i);
493                    });
494                    res
495                })
496                .collect::<Vec<T>>()
497                .try_into()
498                .unwrap_or_else(|v: Vec<T>| {
499                    panic!("Expected a Vec of length {} (found {})", N, v.len())
500                })
501        }
502    }
503
504    #[derive(Clone, Debug, Default)]
505    pub struct KeyRecoveryShamirFinal;
506
507    impl KeyRecoveryShamirFinal {
508        pub fn lagrange_interpolate_at_zero<
509            const N: usize,
510            T: Mul<T, Output = T> + Add<T, Output = T> + From<usize> + Copy,
511        >(
512            inps: [(T, T); N],
513        ) -> T {
514            inps.into_iter()
515                .fold(T::from(0), |acc, (poly, y)| acc + y * poly)
516        }
517    }
518
519    #[derive(Clone, Debug)]
520    #[allow(dead_code)]
521    struct TestKeyRecovery {
522        deg: usize,
523        n: usize,
524    }
525
526    impl TestKeyRecovery {
527        #[allow(dead_code)]
528        pub fn new(deg: usize, n: usize) -> Self {
529            Self { deg, n }
530        }
531    }
532
533    const N: usize = 100;
534
535    impl<F: ActuallyUsedField> ArithmeticCircuit<F> for TestKeyRecovery {
536        fn eval(&self, x: Vec<F>) -> Result<Vec<F>, EvalFailure> {
537            if x.len() != 1 {
538                panic!("expected input of length 1 (found {})", x.len());
539            }
540            if self.n > N {
541                return EvalFailure::err_ub("n too large");
542            }
543            Ok(x)
544        }
545
546        fn bounds(&self, _bounds: Vec<FieldBounds<F>>) -> Vec<FieldBounds<F>> {
547            vec![FieldBounds::All]
548        }
549
550        fn run(&self, vals: Vec<FieldValue<F>>) -> Vec<FieldValue<F>>
551        where
552            F: ActuallyUsedField,
553        {
554            // computed by the source MXE M (we omit the encryption)
555            let all_secret_shares =
556                KeyRecoveryShamirInit::shamir_secret_share::<N, BooleanValue, FieldValue<F>>(
557                    vals[0],
558                    FieldValue::<F>::from(self.deg),
559                    FieldValue::<F>::from(self.n),
560                );
561
562            // computed by the program/nodes of M and put on chain
563            let mut secret_shares = all_secret_shares
564                .into_iter()
565                .take(self.n)
566                .enumerate()
567                .map(|(i, secret_share)| (i + 1, secret_share))
568                .collect::<Vec<(usize, FieldValue<F>)>>();
569
570            // simulates a random subset of deg + 1 shares for the recovery
571            // these are authenticated via zkps and put on chain
572            let mut rng = thread_rng();
573            secret_shares.shuffle(&mut rng);
574            let secret_shares = secret_shares
575                .into_iter()
576                .take(self.deg + 1)
577                .collect::<Vec<(usize, FieldValue<F>)>>();
578
579            // computed by the program/nodes of the target MXE M'
580            let (xs, mut ys): (Vec<usize>, Vec<FieldValue<F>>) =
581                secret_shares.iter().copied().unzip();
582            let mut lagrange_basis_polynomials = xs
583                .iter()
584                .copied()
585                .map(|x| {
586                    let num = xs
587                        .iter()
588                        .copied()
589                        .filter(|val| *val != x)
590                        .map(|val| -Number::from(val))
591                        .reduce(|a, b| a * b)
592                        .unwrap();
593                    let denom = xs
594                        .iter()
595                        .copied()
596                        .filter(|val| *val != x)
597                        .map(|val| Number::from(x) - Number::from(val))
598                        .reduce(|a, b| a * b)
599                        .unwrap();
600                    FieldValue::from(F::from(num) * F::from(denom).invert(true))
601                })
602                .collect::<Vec<FieldValue<F>>>();
603            lagrange_basis_polynomials.extend(std::iter::repeat_n(
604                FieldValue::<F>::from(0),
605                N - (self.deg + 1),
606            ));
607            ys.extend(std::iter::repeat_n(
608                FieldValue::<F>::from(0),
609                N - (self.deg + 1),
610            ));
611
612            // computed by M' (we omit the decryption)
613            vec![KeyRecoveryShamirFinal::lagrange_interpolate_at_zero::<
614                N,
615                FieldValue<F>,
616            >(
617                lagrange_basis_polynomials
618                    .into_iter()
619                    .zip(ys)
620                    .collect::<Vec<(FieldValue<F>, FieldValue<F>)>>()
621                    .try_into()
622                    .unwrap_or_else(|v: Vec<(FieldValue<F>, FieldValue<F>)>| {
623                        panic!("Expected a Vec of length {} (found {})", N, v.len())
624                    }),
625            )]
626        }
627    }
628
629    #[cfg(test)]
630    mod tests {
631        use super::*;
632        use crate::{
633            core::circuits::traits::arithmetic_circuit::tests::TestedArithmeticCircuit,
634            utils::field::BaseField,
635        };
636        use rand::Rng;
637
638        impl TestedArithmeticCircuit<BaseField> for TestKeyRecovery {
639            fn gen_desc<R: Rng + ?Sized>(rng: &mut R) -> Self {
640                let mut deg = 3;
641                while rng.gen_bool(0.75) {
642                    deg += 1;
643                }
644                let mut n = deg + 1;
645                while rng.gen_bool(0.75) {
646                    n += 1;
647                }
648                Self::new(deg as usize, n as usize)
649            }
650
651            fn gen_n_inputs<R: Rng + ?Sized>(&self, _rng: &mut R) -> usize {
652                1
653            }
654        }
655
656        #[test]
657        fn tested_recovery() {
658            TestKeyRecovery::test(2, 1)
659        }
660    }
661}