#[cfg(test)]
mod tests {
    use crate::{
        core::circuits::{
            key_recovery::{
                utils::reed_solomon::{
                    KeyRecoveryDesc,
                    KeyRecoveryReedSolomonFinal,
                    KeyRecoveryReedSolomonInit,
                },
                MXE_KEYS_ENC_COUNT,
            },
            pre_compiled::constants::ARTIFACTS_DIR,
        },
        traits::{FromLeBytes, Random},
        utils::{
            crypto::{
                key::{
                    X25519PrivateKey,
                    X25519PublicKey,
                    AES_128_KEY_COUNT,
                    AES_192_KEY_COUNT,
                    AES_256_KEY_COUNT,
                    ED25519_SECRET_KEY_COUNT,
                    ED25519_SIGNING_KEY_HASH_PREFIX_COUNT,
                    ED25519_SIGNING_KEY_S_COUNT,
                    ED25519_VERIFYING_KEY_COUNT,
                    ELGAMAL_PUBKEY_COUNT,
                    ELGAMAL_SECRET_KEY_COUNT,
                    RESCUE_KEY_COUNT,
                    X25519_PRIVATE_KEY_COUNT,
                    X25519_PUBLIC_KEY_COUNT,
                },
                rescue_cipher::RescueCipher,
            },
            curve_point::CurvePoint,
            field::{BaseField, ScalarField},
        },
        AsyncMPCCircuit,
    };
    use core_utils::key_recovery::{MXE_KEY_RECOVERY_D, MXE_KEY_RECOVERY_K, MXE_KEY_RECOVERY_N};
    use num_bigint::BigUint;
    use num_traits::{FromBytes, ToBytes};
    use rand::{seq::SliceRandom, Rng};
    use std::fs;

    fn bytes_from_biguint(val: &BigUint) -> [u8; 32] {
        let mut bytes = [0u8; 32];
        let val_bytes = val.to_le_bytes();
        bytes[..val_bytes.len()].copy_from_slice(&val_bytes);
        bytes
    }

    // This test performs the following steps:
    // 1. Run the MXE keygen to get keys for the base MXE.
    // 2. Initialize n peers (x25519 keypairs) that will hold the Rescue key shares.
    // 3. Run the mxe_key_recovery_init circuit. This will threshold secret-share the Rescue base
    //    field key and encrypt the key shares under the respective peer pubkey. All other MXE keys
    //    (x25519 private key, Rescue scalar field key, AES keys, ed25519 secret key and ElGamal
    //    secret key) are encrypted with the Rescue MXE cipher and a SHA3-256 digest of the
    //    concatenation of all MXE keys is returned.
    // 4. Each peer performs a key exchange with the base MXE and decrypts their Rescue base field
    //    key shares.
    // 5. We choose a random number of corrupt peers. They generate random errors and add to their
    //    respective Rescue base field key shares. The number of corrupt peers is below the
    //    threshold, i.e., the key recovery algorithm should be successful.
    // 6. Run another MXE keygen, this time to get keys for the backup MXE. We will only use the
    //    x25519 keypair.
    // 7. Each peer performs a key exchange with the backup MXE and encrypts their Rescue base field
    //    key shares. Not tested here, but a missing ciphertext can be replaced by 0 and the peer
    //    will be considered corrupted.
    // 8. Run the mxe_key_recovery_finalize circuit. This will decrypt all the provided key shares
    //    and attempt to recover the Rescue base field key. Using the recovered Rescue key, it will
    //    decrypt the encrypted MXE keys (x25519 private key, Rescue scalar field key, AES keys,
    //    ed25519 secret key and ElGamal secret key) and compute a SHA3-256 digest of the
    //    concatenation of all MXE keys. The circuit also returns the public errors.
    // 9. Validate the key recovery. If the SHA3-256 digest of the recovered MXE keys does not match
    //    the digest of the base MXE keys then the key recovery failed. If they do match then the
    //    recovery algorithm was successful, and the errors indicate the corrupted peers. If the
    //    number of corrupted peers is below the threshold we accept the recovered keys. We don't
    //    have evidence that the MXE is compromised, but we don't have guarantees either. An
    //    adversary could still control sufficiently many peers to reconstruct the key, without the
    //    peers providing erroneous key shares.

    #[test]
    fn test_recovery() {
        let mut rng = &mut crate::utils::test_rng::get();
        // n is the actual number of peers in the key recovery cluster
        let mut n = MXE_KEY_RECOVERY_N;
        while rng.gen_bool(0.875) && n > 4 {
            n -= 1;
        }
        let desc = KeyRecoveryDesc::new(n);

        // ##### INIT #####

        // the base MXE keys
        // load the mxe_keygen circuit
        let circuit_keygen_serialized =
            fs::read(format!("{}/mxe_keygen/circuit.arcis", ARTIFACTS_DIR))
                .expect("Failed to read stored circuit");
        let circuit_keygen: AsyncMPCCircuit =
            bincode::deserialize(&circuit_keygen_serialized).expect("Deserialization failed");

        let base_mxe_keys = circuit_keygen.mock_eval_big_uint(Vec::new(), rng);
        let base_mxe_x25519_private_key_biguint = &base_mxe_keys[0];
        let base_mxe_x25519_private_key = X25519PrivateKey::new(
            ScalarField::from_le_bytes(bytes_from_biguint(base_mxe_x25519_private_key_biguint)),
            true,
        );
        let mut offset = X25519_PRIVATE_KEY_COUNT;
        let base_mxe_x25519_pubkey_biguint = &base_mxe_keys[offset];
        let base_mxe_x25519_pubkey = X25519PublicKey::new(
            CurvePoint::from_le_bytes(&bytes_from_biguint(base_mxe_x25519_pubkey_biguint)).unwrap(),
            true,
        );
        offset += X25519_PUBLIC_KEY_COUNT;
        let mxe_rescue_base_field_key_biguint =
            base_mxe_keys[offset..offset + RESCUE_KEY_COUNT].to_vec();
        offset += RESCUE_KEY_COUNT;
        let mxe_rescue_scalar_field_key_biguint =
            base_mxe_keys[offset..offset + RESCUE_KEY_COUNT].to_vec();
        offset += RESCUE_KEY_COUNT;
        let mxe_aes_128_key_biguint = base_mxe_keys[offset..offset + AES_128_KEY_COUNT].to_vec();
        offset += AES_128_KEY_COUNT;
        let mxe_aes_192_key_biguint = base_mxe_keys[offset..offset + AES_192_KEY_COUNT].to_vec();
        offset += AES_192_KEY_COUNT;
        let mxe_aes_256_key_biguint = base_mxe_keys[offset..offset + AES_256_KEY_COUNT].to_vec();
        offset += AES_256_KEY_COUNT;
        let mxe_ed25519_secret_key_biguint =
            base_mxe_keys[offset..offset + ED25519_SECRET_KEY_COUNT].to_vec();
        offset += ED25519_SECRET_KEY_COUNT;
        let mxe_ed25519_signing_key_s_biguint = &base_mxe_keys[offset];
        offset += ED25519_SIGNING_KEY_S_COUNT;
        let mxe_ed25519_signing_key_hash_prefix_biguint =
            base_mxe_keys[offset..offset + ED25519_SIGNING_KEY_HASH_PREFIX_COUNT].to_vec();
        offset += ED25519_SIGNING_KEY_HASH_PREFIX_COUNT;
        let mxe_ed25519_verifying_key_biguint =
            base_mxe_keys[offset..offset + ED25519_VERIFYING_KEY_COUNT].to_vec();
        offset += ED25519_VERIFYING_KEY_COUNT;
        let mxe_elgamal_secret_key_biguint = &base_mxe_keys[offset];
        offset += ELGAMAL_SECRET_KEY_COUNT;
        let mxe_elgamal_pubkey_biguint = &base_mxe_keys[offset];

        // the keypairs of all the peers in the recovery cluster
        // TODO: assert that the n first public keys are valid and distinct (otherwise
        // some peers would hold more than one secret-share) and that the remaining
        // MXE_KEY_RECOVERY_N - n public keys correspond to X25519PublicKey::default()
        let mut peer_x25519_keypairs = [(
            X25519PrivateKey::<ScalarField>::default(),
            X25519PublicKey::<CurvePoint>::default(),
        ); MXE_KEY_RECOVERY_N];
        for keypair in peer_x25519_keypairs.iter_mut().take(n) {
            let private_key = X25519PrivateKey::random();
            let pubkey = X25519PublicKey::new_from_private_key(private_key);
            *keypair = (private_key, pubkey);
        }

        // provided by the program
        let mut nonce_bytes = [0u8; 32];
        for byte in nonce_bytes.iter_mut().take(16) {
            *byte = rng.gen_range(0..=255);
        }
        let nonce = BaseField::from_le_bytes(nonce_bytes);
        // provided by the program
        // TODO: assert that n <= MXE_KEY_RECOVERY_N
        let n = BaseField::from(desc.n as u64);
        // provided by the program or computed by each node
        let g = KeyRecoveryReedSolomonInit::compute_generator_polynomial::<
            MXE_KEY_RECOVERY_D,
            BaseField,
        >(desc.d);

        // load the mxe_key_recovery_init circuit
        let circuit_init_serialized = fs::read(format!(
            "{}/mxe_key_recovery_init/circuit.arcis",
            ARTIFACTS_DIR
        ))
        .expect("Failed to read stored circuit");
        let circuit_init: AsyncMPCCircuit =
            bincode::deserialize(&circuit_init_serialized).expect("Deserialization failed");

        assert_eq!(
            circuit_init.output_indices().len(),
            MXE_KEY_RECOVERY_N * RESCUE_KEY_COUNT + MXE_KEYS_ENC_COUNT + 32 + 1 + 1
        );

        // prepare the inputs
        let mut inputs_init = peer_x25519_keypairs
            .iter()
            .map(|(_, pubkey)| BigUint::from_le_bytes(pubkey.inner().to_bytes().as_ref()))
            .collect::<Vec<BigUint>>();
        inputs_init.extend(vec![
            BigUint::from_le_bytes(&nonce.to_le_bytes()),
            BigUint::from_le_bytes(&n.to_le_bytes()),
        ]);
        inputs_init.extend(
            g.iter()
                .map(|coeff| BigUint::from_le_bytes(&coeff.to_le_bytes())),
        );
        inputs_init.push(BigUint::from_le_bytes(
            &base_mxe_x25519_private_key.inner().to_le_bytes(),
        ));
        inputs_init.extend(mxe_rescue_base_field_key_biguint.clone());
        inputs_init.extend(mxe_rescue_scalar_field_key_biguint.clone());
        inputs_init.extend(mxe_aes_128_key_biguint.clone());
        inputs_init.extend(mxe_aes_192_key_biguint.clone());
        inputs_init.extend(mxe_aes_256_key_biguint.clone());
        inputs_init.extend(mxe_ed25519_secret_key_biguint.clone());
        inputs_init.push(mxe_elgamal_secret_key_biguint.clone());

        // mock-eval the circuit, returns MXE_KEY_RECOVERY_N * RESCUE_KEY_COUNT elements
        // which we turn into MXE_KEY_RECOVERY_N vecs of RESCUE_KEY_COUNT elements,
        // along with the Rescue encrypted x25519 private key, Rescue scalar field key, AES keys,
        // ed25519 secret key and ElGamal secret key and the SHA3-256 digest of the
        // concatenation of all MXE keys
        let outputs_init = circuit_init.mock_eval_big_uint(inputs_init, rng);
        assert_eq!(
            outputs_init.len(),
            MXE_KEY_RECOVERY_N * RESCUE_KEY_COUNT + MXE_KEYS_ENC_COUNT + 32 + 1 + 1
        );
        let base_rescue_key_shares_enc = outputs_init[..MXE_KEY_RECOVERY_N * RESCUE_KEY_COUNT]
            .iter()
            .map(|val| BaseField::from_le_bytes(bytes_from_biguint(val)))
            .collect::<Vec<BaseField>>()
            .chunks(RESCUE_KEY_COUNT)
            .map(|chunk| chunk.to_vec())
            .collect::<Vec<Vec<BaseField>>>();
        let mut offset = MXE_KEY_RECOVERY_N * RESCUE_KEY_COUNT;
        let mxe_keys_enc_biguint = outputs_init[offset..offset + MXE_KEYS_ENC_COUNT].to_vec();
        offset += MXE_KEYS_ENC_COUNT;
        let mxe_keys_digest_biguint = outputs_init[offset..offset + 32].to_vec();
        offset += 32;
        let output_nonce = &outputs_init[offset];
        // Make sure the nonce output is the same as the one provided by the program
        assert_eq!(
            BaseField::from_le_bytes(bytes_from_biguint(output_nonce)),
            nonce
        );
        offset += 1;
        let output_n = &outputs_init[offset];
        // Make sure the n output is the same as the one provided by the program
        assert_eq!(BaseField::from_le_bytes(bytes_from_biguint(output_n)), n);

        // ##### KEY STORAGE #####

        // The peers perform a key exchange with the base MXE and decrypt their Rescue base field
        // key shares.
        let base_rescue_ciphers = peer_x25519_keypairs
            .iter()
            .map(|(private_key, _)| {
                RescueCipher::new_with_client_from_keys(*private_key, base_mxe_x25519_pubkey)
            })
            .collect::<Vec<RescueCipher<BaseField, BaseField>>>();

        let base_rescue_key_shares = base_rescue_ciphers
            .into_iter()
            .zip(base_rescue_key_shares_enc)
            .map(|(cipher, ciphertext)| cipher.decrypt(ciphertext, nonce))
            .collect::<Vec<Vec<BaseField>>>();

        // Assert that the MXE_KEY_RECOVERY_N - n last key shares are zero.
        // What we actually want to assert here is that the trailing encrypted (under the default
        // x25519 keypair) key shares are not valid key shares/do not leak any information.
        // If they were valid key shares, then anyone in the world would learn MXE_KEY_RECOVERY_N -
        // n key shares for free. Whether the outputs are 0, or encryptions of 0, or any other
        // meaningless constant doesn't actually matter - in practice they will get
        // discarded anyway.
        assert!(base_rescue_key_shares
            .iter()
            .skip(desc.n)
            .flatten()
            .all(|value| value.eq(&BaseField::from(0))));

        let base_rescue_key_shares = base_rescue_key_shares
            .into_iter()
            .take(desc.n)
            .collect::<Vec<Vec<BaseField>>>();

        // Simulate up to k - 1 corrupt peers. (The algorithm can correct up to (d-1)/2 erroneous
        // key shares. If n mod 3 = 0 then k = (d-1)/2. In this case, if one detects k erroneous
        // shares then, despite the fact of the key being recoverable, one should expect that
        // the MXE got compromised.)
        // We add random errors to the Rescue key shares of randomly chosen peers.
        let mut errors = vec![vec![BaseField::from(0); RESCUE_KEY_COUNT]; desc.n];
        let mut nu = desc.k - 1;
        while rng.gen_bool(0.5) && nu > 0 {
            nu -= 1;
        }
        // We also occasionally want to test the case where there are no errors.
        if rng.gen_bool(0.125) {
            nu = 0;
        }
        for error in errors.iter_mut().take(nu) {
            *error = (0..RESCUE_KEY_COUNT)
                .map(|_| BaseField::random())
                .collect::<Vec<BaseField>>();
        }
        errors.shuffle(&mut rng);

        // introduce errors
        let backup_rescue_key_shares = base_rescue_key_shares
            .iter()
            .zip(errors.iter())
            .map(|(shares, error)| {
                shares
                    .iter()
                    .zip(error)
                    .map(|(share, e)| *share + e)
                    .collect::<Vec<BaseField>>()
            })
            .collect::<Vec<Vec<BaseField>>>();

        // ##### FINALIZE #####

        // the backup MXE keys
        let backup_mxe_keys = circuit_keygen.mock_eval_big_uint(Vec::new(), rng);
        let backup_mxe_x25519_private_key = X25519PrivateKey::new(
            ScalarField::from_le_bytes(bytes_from_biguint(&backup_mxe_keys[0])),
            true,
        );
        let backup_mxe_x25519_pubkey = X25519PublicKey::new(
            CurvePoint::from_le_bytes(&bytes_from_biguint(&backup_mxe_keys[1])).unwrap(),
            true,
        );

        // The peers perform a key exchange with the backup MXE and encrypt their Rescue base field
        // key shares.
        let backup_rescue_ciphers = peer_x25519_keypairs
            .iter()
            .take(desc.n)
            .map(|(private_key, _)| {
                RescueCipher::new_with_client_from_keys(*private_key, backup_mxe_x25519_pubkey)
            })
            .collect::<Vec<RescueCipher<BaseField, BaseField>>>();

        let mut backup_rescue_key_shares_enc = backup_rescue_ciphers
            .into_iter()
            .zip(backup_rescue_key_shares)
            .map(|(cipher, plaintext)| cipher.encrypt(plaintext, nonce))
            .collect::<Vec<Vec<BaseField>>>();

        // pad with 0 ciphertexts
        backup_rescue_key_shares_enc.resize(
            MXE_KEY_RECOVERY_N,
            vec![BaseField::from(0); RESCUE_KEY_COUNT],
        );

        // provided by the program or computed by each node
        let (alpha_pows, scaled_polynomials) =
            KeyRecoveryReedSolomonFinal::compute_scaled_polynomials::<MXE_KEY_RECOVERY_K, BaseField>(
                desc.d, desc.k,
            );

        // assert that the K - k last elements of alpha_pows and scaled_polynomials are zero
        assert!(
            alpha_pows
                .iter()
                .skip(desc.k)
                .all(|value| value.eq(&BaseField::from(0)))
                && scaled_polynomials
                    .iter()
                    .skip(desc.k)
                    .all(|value| value.eq(&BaseField::from(0)))
        );

        // load the mxe_key_recovery_finalize circuit
        let circuit_finalize_serialized = fs::read(format!(
            "{}/mxe_key_recovery_finalize/circuit.arcis",
            ARTIFACTS_DIR
        ))
        .expect("Failed to read stored circuit");
        let circuit_finalize: AsyncMPCCircuit =
            bincode::deserialize(&circuit_finalize_serialized).expect("Deserialization failed");

        // prepare the inputs
        let mut inputs_finalize = backup_rescue_key_shares_enc
            .into_iter()
            .zip(peer_x25519_keypairs)
            .flat_map(|(shares, (_, pubkey))| {
                let mut vals = shares
                    .into_iter()
                    .map(|share| BigUint::from_le_bytes(&share.to_le_bytes()))
                    .collect::<Vec<BigUint>>();
                vals.push(BigUint::from_le_bytes(pubkey.inner().to_bytes().as_ref()));
                vals
            })
            .collect::<Vec<BigUint>>();
        inputs_finalize.extend(vec![
            BigUint::from_le_bytes(&nonce.to_le_bytes()),
            BigUint::from_le_bytes(&n.to_le_bytes()),
        ]);
        inputs_finalize.extend(
            alpha_pows
                .into_iter()
                .map(|val| BigUint::from_le_bytes(&val.to_le_bytes())),
        );
        inputs_finalize.extend(
            scaled_polynomials
                .into_iter()
                .map(|val| BigUint::from_le_bytes(&val.to_le_bytes())),
        );
        inputs_finalize.extend(mxe_keys_enc_biguint);
        inputs_finalize.push(BigUint::from_le_bytes(
            &backup_mxe_x25519_private_key.inner().to_le_bytes(),
        ));

        // mock-eval the circuit
        let outputs_finalize = circuit_finalize.mock_eval_big_uint(inputs_finalize, rng);
        let decrypted_mxe_x25519_private_key_biguint =
            outputs_finalize[..X25519_PRIVATE_KEY_COUNT].to_vec();
        let mut offset = X25519_PRIVATE_KEY_COUNT;
        let check_mxe_x25519_pubkey_biguint = &outputs_finalize[offset];
        offset += X25519_PUBLIC_KEY_COUNT;
        let recovered_mxe_rescue_base_field_key_biguint =
            outputs_finalize[offset..offset + RESCUE_KEY_COUNT].to_vec();
        offset += RESCUE_KEY_COUNT;
        let decrypted_mxe_rescue_scalar_field_key_biguint =
            outputs_finalize[offset..offset + RESCUE_KEY_COUNT].to_vec();
        offset += RESCUE_KEY_COUNT;
        let decrypted_mxe_aes_128_key_biguint =
            outputs_finalize[offset..offset + AES_128_KEY_COUNT].to_vec();
        offset += AES_128_KEY_COUNT;
        let decrypted_mxe_aes_192_key_biguint =
            outputs_finalize[offset..offset + AES_192_KEY_COUNT].to_vec();
        offset += AES_192_KEY_COUNT;
        let decrypted_mxe_aes_256_key_biguint =
            outputs_finalize[offset..offset + AES_256_KEY_COUNT].to_vec();
        offset += AES_256_KEY_COUNT;
        let decrypted_mxe_ed25519_secret_key_biguint =
            outputs_finalize[offset..offset + ED25519_SECRET_KEY_COUNT].to_vec();
        offset += ED25519_SECRET_KEY_COUNT;
        let decrypted_mxe_ed25519_signing_key_s_biguint =
            outputs_finalize[offset..offset + ED25519_SIGNING_KEY_S_COUNT].to_vec();
        offset += ED25519_SIGNING_KEY_S_COUNT;
        let decrypted_mxe_ed25519_signing_key_hash_prefix_biguint =
            outputs_finalize[offset..offset + ED25519_SIGNING_KEY_HASH_PREFIX_COUNT].to_vec();
        offset += ED25519_SIGNING_KEY_HASH_PREFIX_COUNT;
        let check_mxe_ed25519_verifying_key_biguint =
            outputs_finalize[offset..offset + ED25519_VERIFYING_KEY_COUNT].to_vec();
        offset += ED25519_VERIFYING_KEY_COUNT;
        let decrypted_mxe_elgamal_secret_key_biguint = &outputs_finalize[offset];
        offset += ELGAMAL_SECRET_KEY_COUNT;
        let check_mxe_elgamal_pubkey_biguint = &outputs_finalize[offset];
        offset += ELGAMAL_PUBKEY_COUNT;
        let check_mxe_keys_digest_biguint = outputs_finalize[offset..offset + 32].to_vec();
        offset += 32;
        let detected_errors = outputs_finalize[offset..]
            .chunks(RESCUE_KEY_COUNT)
            .map(|chunk| {
                chunk
                    .iter()
                    .map(|e| BaseField::from_le_bytes(bytes_from_biguint(e)))
                    .collect::<Vec<BaseField>>()
            })
            .take(desc.n)
            .collect::<Vec<Vec<BaseField>>>();

        // We check the secret-shared keys (this cannot be done in a real world scenario).
        assert_eq!(
            recovered_mxe_rescue_base_field_key_biguint,
            mxe_rescue_base_field_key_biguint
        );
        assert_eq!(
            &decrypted_mxe_x25519_private_key_biguint[0],
            base_mxe_x25519_private_key_biguint
        );
        assert_eq!(
            decrypted_mxe_rescue_scalar_field_key_biguint,
            mxe_rescue_scalar_field_key_biguint
        );
        assert_eq!(decrypted_mxe_aes_128_key_biguint, mxe_aes_128_key_biguint);
        assert_eq!(decrypted_mxe_aes_192_key_biguint, mxe_aes_192_key_biguint);
        assert_eq!(decrypted_mxe_aes_256_key_biguint, mxe_aes_256_key_biguint);
        assert_eq!(
            decrypted_mxe_ed25519_secret_key_biguint,
            mxe_ed25519_secret_key_biguint
        );
        assert_eq!(
            &decrypted_mxe_ed25519_signing_key_s_biguint[0],
            mxe_ed25519_signing_key_s_biguint
        );
        assert_eq!(
            decrypted_mxe_ed25519_signing_key_hash_prefix_biguint,
            mxe_ed25519_signing_key_hash_prefix_biguint
        );
        assert_eq!(
            decrypted_mxe_elgamal_secret_key_biguint,
            mxe_elgamal_secret_key_biguint
        );

        // Public checks that allow to assert that the key recovery was successful.
        // Must be done by the program.

        if check_mxe_keys_digest_biguint.ne(&mxe_keys_digest_biguint)
            || check_mxe_x25519_pubkey_biguint.ne(base_mxe_x25519_pubkey_biguint)
            || check_mxe_ed25519_verifying_key_biguint.ne(&mxe_ed25519_verifying_key_biguint)
            || check_mxe_elgamal_pubkey_biguint.ne(mxe_elgamal_pubkey_biguint)
        {
            // this panic deserves its name
            panic!(
                "\n##### Key recovery failed. We can expect that the MXE is compromised! #####\n"
            );
        } else {
            // Key recovery was successful (it might have been sabotaged by a certain number of
            // corrupt peers, but the recovery algorithm was still able to detect the peers and
            // correct the erroneous key shares).

            // Check that the errors detected are correct (this cannot be done in a real world
            // scenario).
            assert_eq!(detected_errors, errors);
            // Check error locations to identify the corrupt peers.
            // Must be done by the program.
            let corrupt_peers = detected_errors
                .into_iter()
                .enumerate()
                .filter(|(_, errors)| errors.iter().any(|error| error.ne(&BaseField::from(0))))
                .map(|(i, _)| i)
                .collect::<Vec<usize>>();
            assert_eq!(corrupt_peers.len(), nu);

            if corrupt_peers.len() < desc.k {
                // We don't have evidence that the MXE is compromised, but we don't have guarantees
                // either. An adversary could still control sufficiently many peers to reconstruct
                // the key, without the peers providing erroneous key shares.
                println!(
                    "\n##### Identified {} corrupt peers: {:?} #####\n",
                    corrupt_peers.len(),
                    corrupt_peers
                );
            } else {
                // Although the key recovery was successful (for each Rescue key there were at most
                // (d-1)/2 erroneous key shares), the overall number of corrupted peers is greater
                // than the threshold k-1 of the key recovery scheme, i.e., we can expect that an
                // adversary has recovered the Rescue key.
                // This siutation can occur if:
                //  - n mod 3 = 0 (so that (d-1)/2 = k) and there are exactly k corrupted peers.
                //  - A peer has provided an erroneous key share for the i-th key but a correct one
                //    for the j-th key, and per key there are no more than (d-1)/2 erroneous key
                //    shares. We can expect that this peer leaks all RESCUE_KEY_COUNT key shares.

                // If we're here then the program should declare the MXE as compromised.
                println!("\n##### Adversary controls k peers. We can expect that the MXE is compromised! #####\n");
            }
        }
    }
}
