use crate::internal::bit_repr::BitRepr;
use crate::internal::curve::CurvePoints;
use crate::internal::ed25519::{
    Ed25519Signature, Ed25519Signing, PublicSigningKey, SigningKeypair,
};
use crate::internal::field::ExtensionField;
use crate::internal::field::Field;
use crate::internal::fp::fr_256::Fr256;
use crate::internal::fp::fr_480::Fr480;
use crate::internal::fp12elem::Fp12Elem;
use crate::internal::hashable::{Hashable, Hashable32, Hashable60};
use crate::internal::homogeneouspoint::{HomogeneousPoint, PointErr, TwistedHPoint};
use crate::internal::pairing::Pairing;
use crate::internal::pairing::PairingConfig;
use crate::internal::sha256::Sha256Hashing;
use crate::nonemptyvec::NonEmptyVec;
use clear_on_drop::clear::Clear;
use gridiron::digits::constant_bool::ConstantBool;
use gridiron::digits::constant_time_primitives::ConstantSwap;
use gridiron::fp_256;
use gridiron::fp_256::Fp256;
use gridiron::fp_480;
use gridiron::fp_480::Fp480;
use log::error;
use num_traits::{One, Zero};
use quick_error::quick_error;
use std::ops::{Add, Mul, Neg, Sub};
#[macro_use]
pub mod macros;
pub mod bit_repr;
pub mod bytedecoder;
pub mod curve;
pub mod ed25519;
pub mod field;
pub mod fp;
pub mod fp12elem;
pub mod fp2elem;
pub mod fp6elem;
pub mod hashable;
pub mod homogeneouspoint;
pub mod memlock;
pub mod pairing;
pub mod rand_bytes;
pub mod schnorr;
pub mod sha256;

use crate::api;
use crate::api_480;
use std::sync::{Mutex, MutexGuard};

pub type ByteVector = Vec<u8>;
pub type ErrorOr<T> = Result<T, InternalError>;

quick_error! {
    #[derive(Debug, PartialEq, Eq)]
    pub enum InternalError {
        AuthHashMatchFailed {}
        InvalidEncryptedMessageSignature {}
        PointInvalid(err: PointErr) {
            source(err)
            from()
        }
        CorruptReencryptionKey {}
    }
}

#[derive(Clone, Copy, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct PublicKey<T: Field> {
    pub value: HomogeneousPoint<T>,
}

impl<T: Field + From<u32> + Hashable> PublicKey<T> {
    pub fn new(point: HomogeneousPoint<T>) -> PublicKey<T> {
        PublicKey { value: point }
    }

    pub fn from_x_y(x: T, y: T) -> ErrorOr<PublicKey<T>> {
        Ok(HomogeneousPoint::from_x_y((x, y)).map(|value| PublicKey { value })?)
    }
}

impl PublicKey<fp_256::Monty> {
    pub fn to_byte_vectors_32(&self) -> Option<([u8; 32], [u8; 32])> {
        self.value
            .normalize()
            .map(|(x, y)| (x.to_bytes_32(), y.to_bytes_32()))
    }
}

impl PublicKey<fp_480::Monty> {
    pub fn to_byte_vectors_60(&self) -> Option<([u8; 60], [u8; 60])> {
        self.value
            .normalize()
            .map(|(x, y)| (x.to_bytes_60(), y.to_bytes_60()))
    }
}

impl<T: Field + Hashable> Hashable for PublicKey<T> {
    fn to_bytes(&self) -> ByteVector {
        self.value.to_bytes()
    }
}

#[derive(Copy, Clone, Debug, Default)]
#[cfg_attr(test, derive(PartialEq))]
pub struct PrivateKey<T> {
    pub value: T,
}
impl From<api::PrivateKey> for PrivateKey<fp_256::Monty> {
    fn from(api_pk: api::PrivateKey) -> Self {
        PrivateKey {
            value: Fp256::from(api_pk.to_bytes_32()).to_monty(),
        }
    }
}

impl<'a> From<&'a api::PrivateKey> for PrivateKey<fp_256::Monty> {
    fn from(api_pk: &'a api::PrivateKey) -> Self {
        PrivateKey {
            value: Fp256::from(api_pk.to_bytes_32()).to_monty(),
        }
    }
}

impl PrivateKey<fp_256::Monty> {
    pub fn from_fp256(fp256: fp_256::Monty) -> PrivateKey<fp_256::Monty> {
        PrivateKey { value: fp256 }
    }
}

impl From<api_480::PrivateKey> for PrivateKey<fp_480::Monty> {
    fn from(api_pk: api_480::PrivateKey) -> Self {
        PrivateKey {
            value: Fp480::from(api_pk.to_bytes_60()).to_monty(),
        }
    }
}

impl<'a> From<&'a api_480::PrivateKey> for PrivateKey<fp_480::Monty> {
    fn from(api_pk: &'a api_480::PrivateKey) -> Self {
        PrivateKey {
            value: Fp480::from(api_pk.to_bytes_60()).to_monty(),
        }
    }
}

impl PrivateKey<fp_480::Monty> {
    pub fn from_fp480(fp480: fp_480::Monty) -> PrivateKey<fp_480::Monty> {
        PrivateKey { value: fp480 }
    }
}

impl<T: Add<Output = T>> Add for PrivateKey<T> {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            value: self.value + other.value,
        }
    }
}

impl<T: Sub<Output = T>> Sub for PrivateKey<T> {
    type Output = Self;

    fn sub(self, other: Self) -> Self {
        Self {
            value: self.value - other.value,
        }
    }
}

impl<T> BitRepr for PrivateKey<T>
where
    T: BitRepr + Copy,
{
    fn to_bits(&self) -> Vec<ConstantBool<u32>> {
        self.value.to_bits()
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct SignedValue<T> {
    pub public_signing_key: PublicSigningKey,
    pub signature: Ed25519Signature,
    pub payload: T,
}

impl<T: Hashable> Hashable for SignedValue<T> {
    fn to_bytes(&self) -> ByteVector {
        (&self.public_signing_key, &self.payload).to_bytes()
    }
}
/// A value included in an encrypted message that can be used when the message is decrypted
/// to ensure that you got the same value out as the one that was originally encrypted.
/// It is a hash of the plaintext.
///
/// PartialEq/Eq are not constant time, but AuthHash is not secret and we need equality for checking hashes.
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct AuthHash {
    pub bytes: [u8; 32],
}

impl Hashable for AuthHash {
    fn to_bytes(&self) -> ByteVector {
        self.bytes.to_vec()
    }
}

impl AuthHash {
    pub fn create<A: Hashable, F: Sha256Hashing>(hash_func: &F, a: &A) -> AuthHash {
        AuthHash {
            bytes: hash_func.hash(a),
        }
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub enum EncryptedValue<T: Field + Hashable> {
    EncryptedOnce(EncryptedOnceValue<T>),
    Reencrypted(ReencryptedValue<T>),
}

/// A value that has been transformed at least once - this is comprised of the initial encrypted message
/// followed by a set of reencryption blocks, one that is added for each reencryption hop.
/// The number of reencryption hops is equal to the length of the encryptionBlocks Vector.
///
/// ephemeralPublicKey - public key of the private key that was used to encrypt
/// encryptedMessage - the encrypted value.
/// authHash - Authentication hash for the plaintext.
/// encryptionBlocks - A vector of blocks which describes how to transform the encrypted data to be decrypted by another party.
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct ReencryptedValue<T: Field> {
    pub ephemeral_public_key: PublicKey<T>,
    pub encrypted_message: Fp12Elem<T>,
    pub auth_hash: AuthHash,
    pub encryption_blocks: NonEmptyVec<ReencryptionBlock<T>>,
}

impl<FP: Field + Hashable> ReencryptedValue<FP> {
    fn from_encrypted_once(
        encrypted_value: &EncryptedOnceValue<FP>,
        re_blocks: NonEmptyVec<ReencryptionBlock<FP>>,
    ) -> ReencryptedValue<FP> {
        ReencryptedValue {
            ephemeral_public_key: encrypted_value.ephemeral_public_key,
            encrypted_message: encrypted_value.encrypted_message,
            auth_hash: encrypted_value.auth_hash,
            encryption_blocks: re_blocks,
        }
    }

    fn with_new_re_blocks(
        &self,
        new_blocks: NonEmptyVec<ReencryptionBlock<FP>>,
    ) -> ReencryptedValue<FP> {
        ReencryptedValue {
            ephemeral_public_key: self.ephemeral_public_key,
            encrypted_message: self.encrypted_message,
            auth_hash: self.auth_hash,
            encryption_blocks: new_blocks,
        }
    }
}

#[derive(Clone, Copy, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct ReencryptionBlock<T: Field> {
    pub public_key: PublicKey<T>,
    pub encrypted_temp_key: Fp12Elem<T>,
    pub rand_re_public_key: PublicKey<T>,
    pub encrypted_rand_re_temp_key: Fp12Elem<T>,
}

impl<FP: Field + Hashable> ReencryptionBlock<FP> {
    fn with_temp_key(
        &self,
        encrypted_temp_key: Fp12Elem<FP>,
        encrypted_rand_re_temp_key: Fp12Elem<FP>,
    ) -> ReencryptionBlock<FP> {
        ReencryptionBlock {
            public_key: self.public_key,
            encrypted_temp_key,
            rand_re_public_key: self.rand_re_public_key,
            encrypted_rand_re_temp_key,
        }
    }
}

impl<FP: Field + Hashable> Hashable for ReencryptionBlock<FP> {
    fn to_bytes(&self) -> Vec<u8> {
        [
            &self.public_key.to_bytes()[..],
            &self.encrypted_temp_key.to_bytes()[..],
            &self.rand_re_public_key.to_bytes()[..],
            &self.encrypted_rand_re_temp_key.to_bytes()[..],
        ]
        .concat()
    }
}
/// A value which has been encrypted, but not transformed.
/// `ephemeral_public_key`  - public key of the private key that was used to encrypt
/// `encrypted_message`     - the encrypted value.
/// `auth_hash`             - Authentication hash for the plaintext.
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct EncryptedOnceValue<T: Field> {
    pub ephemeral_public_key: PublicKey<T>,
    pub encrypted_message: Fp12Elem<T>,
    pub auth_hash: AuthHash,
}

impl<T: Field + Hashable> Hashable for EncryptedValue<T> {
    fn to_bytes(&self) -> ByteVector {
        match self {
            EncryptedValue::EncryptedOnce(EncryptedOnceValue {
                ephemeral_public_key,
                encrypted_message,
                auth_hash,
            }) => vec![
                ephemeral_public_key.to_bytes(),
                encrypted_message.to_bytes(),
                auth_hash.to_bytes(),
            ]
            .to_bytes(),
            EncryptedValue::Reencrypted(ReencryptedValue {
                ephemeral_public_key,
                encrypted_message,
                auth_hash,
                encryption_blocks,
            }) => vec![
                ephemeral_public_key.to_bytes(),
                encrypted_message.to_bytes(),
                auth_hash.to_bytes(),
                encryption_blocks.to_bytes(),
            ]
            .to_bytes(),
        }
    }
}

pub trait Square {
    fn square(&self) -> Self;
}

impl Square for Fp256 {
    fn square(&self) -> Self {
        *self * *self
    }
}

impl Square for fp_256::Monty {
    fn square(&self) -> Self {
        *self * *self
    }
}

impl Square for Fr256 {
    fn square(&self) -> Self {
        *self * *self
    }
}

impl Square for gridiron::fp_480::Monty {
    fn square(&self) -> Self {
        *self * *self
    }
}

impl Square for Fr480 {
    fn square(&self) -> Self {
        *self * *self
    }
}

///Sum t n times.
///This is not constant time, it reveals the n, but if the t has a constant time add, doesn't reveal the t.
fn sum_n<T: Add<Output = T> + Copy + Zero + PartialEq>(t: T, n: u32) -> T {
    if n == 0 {
        Zero::zero()
    } else {
        sum_n_loop(t, n - 1, t)
    }
}

fn sum_n_loop<T: Add<Output = T> + Copy>(t: T, k: u32, extra: T) -> T {
    if k == 1 {
        t + extra
    } else {
        let x = if (k & 1) == 1 { t + extra } else { extra };
        sum_n_loop(t + t, k >> 1, x)
    }
}

/// This is not constant time, it reveals the n, but if the t has a constant time square and multiply, doesn't reveal the t.
fn pow_for_square<T: One + Mul<T, Output = T> + Copy + Square>(t: T, exp: u32) -> T {
    if exp == 0 {
        T::one()
    } else {
        let mut mut_exp = exp;
        let mut y = T::one();
        let mut x = t;
        while mut_exp > 1 {
            if mut_exp & 1 == 1 {
                y = x * y;
            }
            x = x.square();
            mut_exp >>= 1;
        }
        y * x
    }
}

pub fn array_concat_32<T: Copy + Zero>(one: &[T; 32], two: &[T; 32]) -> [T; 64] {
    let mut result: [T; 64] = [T::zero(); 64];
    result[0..32].copy_from_slice(&one[..]);
    result[32..64].copy_from_slice(&two[..]);
    result
}

pub fn array_concat_60<T: Copy + Zero>(one: &[T; 60], two: &[T; 60]) -> [T; 120] {
    let mut result: [T; 120] = [T::zero(); 120];
    result[0..60].copy_from_slice(&one[..]);
    result[60..120].copy_from_slice(&two[..]);
    result
}

pub fn array_split_64<T: Copy + Zero>(array: &[T; 64]) -> ([T; 32], [T; 32]) {
    let mut one = [T::zero(); 32];
    let mut two = [T::zero(); 32];
    one.copy_from_slice(&array[0..32]);
    two.copy_from_slice(&array[32..64]);
    (one, two)
}

pub fn array_split_120<T: Copy + Zero>(array: &[T; 120]) -> ([T; 60], [T; 60]) {
    let mut one = [T::zero(); 60];
    let mut two = [T::zero(); 60];
    one.copy_from_slice(&array[0..60]);
    two.copy_from_slice(&array[60..120]);
    (one, two)
}
/// Generate one of the rth roots of unity (an element of G_T) given an FP12Elem.
/// Useful for calling `encrypt`
pub fn gen_rth_root<T>(pairing: &Pairing<T>, fp12_elem: Fp12Elem<T>) -> Fp12Elem<T>
where
    T: ExtensionField + PairingConfig,
{
    pairing.final_exp(fp12_elem)
}

///Generate a public key using the private key and generator point.
pub fn public_keygen<T>(private_key: PrivateKey<T>, generator: HomogeneousPoint<T>) -> PublicKey<T>
where
    T: Field + BitRepr + ConstantSwap,
{
    PublicKey {
        value: generator * private_key.value,
    }
}

/// Signs some value of type `T` (and the publicSigningKey) with privateSigningKey.
///
/// # Arguments
/// `payload` - value to sign; must be a type with an associated Hashable instance
/// `signing_keypair` - the Ed25519 keypair that is used to compute the signature
/// `ed25519` - Implementation of `Ed25519Signing` trait
///
/// # Return
/// SignedValue<T> - contains the value `payload`, the public signingkey, and the computed signature
fn sign_value<T, F: Ed25519Signing>(
    payload: T,
    signing_keypair: &SigningKeypair,
    ed25519: &F,
) -> SignedValue<T>
where
    T: Hashable + Clone,
{
    let public_signing_key = signing_keypair.public_key();
    let signature = ed25519.sign(&(&public_signing_key, &payload), signing_keypair);
    SignedValue {
        public_signing_key,
        signature,
        payload,
    }
}

/// Encrypt plaintext to publicKey. This public key encryption is not meant to encrypt arbitrary
/// data; instead, you should generate a random plaintext value (an element of G_T), apply a
/// SHA256 hash to it to generate a 32-bit number, and use that as a key for a symmetric algorithm
/// like AES256-GCM to encrypt the data. Then use this method to encrypt the plaintext.
///
/// Note that the encrypting privateKey is ephemeral.
///
/// The result will have the `public_signing_key` embedded and be signed by the `private_signing_key`.
/// It also includes a authentication hash that the decrypter can use to confirm that the final
/// result after decryption matches the value that was encrypted.
///
/// # Arguments
/// `to_public_key`         - the public key to encrypt to
/// `plaintext`             - the value to encrypt - must be an element of G_T
/// `encrypting_key`        - a random private key value chosen just for this plaintext
/// `signing_keypair`       - the public portion of the encrypter's signing key pair
/// `pairing`               - Optimal Ate Pairing
/// `curve_points`          - IronCore's curve
/// `hash`                  - Sha256Hashing instance
/// `sign`                  - Ed25519Signing instance
///
/// # Return
/// SignedValue[EncryptedValue] - the plaintext encrypted to the specified public key,
/// along with the authHash, public signing key, and signature
pub fn encrypt<T: Clone, F: Sha256Hashing, G: Ed25519Signing>(
    to_public_key: PublicKey<T>,
    plaintext: Fp12Elem<T>, // @clintfred can this be any fp12? or an rth root?
    encrypting_key: PrivateKey<T>,
    signing_keypair: &SigningKeypair,
    pairing: &Pairing<T>,
    curve_points: &CurvePoints<T>,
    hash: &F,
    sign: &G,
) -> ErrorOr<SignedValue<EncryptedValue<T>>>
where
    T: ExtensionField + PairingConfig + BitRepr + Hashable + ConstantSwap,
{
    let ephem_pub_key = PublicKey {
        value: curve_points.generator * encrypting_key,
    };
    let encrypted_message =
        pairing.pair(to_public_key.value * encrypting_key, curve_points.g1)? * plaintext;
    let auth_hash = AuthHash::create(hash, &(&ephem_pub_key, &plaintext));
    Ok(sign_value(
        EncryptedValue::EncryptedOnce(EncryptedOnceValue {
            ephemeral_public_key: ephem_pub_key,
            encrypted_message,
            auth_hash,
        }),
        signing_keypair,
        sign,
    ))
}

/// Decrypt the signed_encrypted_value, verifying that the embedded public signing key matches the
/// signing private key and that the plaintext hash matches the included `auth_hash`. This method
/// handles both "encrypted once" and "reencrypted" messages.
///
/// # Arguments
/// `privateKey`                - private key of the recipient of the message
/// `signed_encrypted_value`    - the output of encrypt() or reencrypt()
/// `pairing`                   - Optimal Ate Pairing
/// `curve_points`              - IronCore's curve
/// `hash`                      - Sha256Hashing instance
/// `signing`                   - Ed25519Signing instance
///
/// # Return
/// ErrorOr[FP12Elem] the decrypted value, which is an element of G_T, or an error (which might be
/// caused by an authHash comparision failure or a signature validation failure)
pub fn decrypt<T, H: Sha256Hashing, G: Ed25519Signing>(
    private_key: PrivateKey<T>,
    signed_encrypted_value: SignedValue<EncryptedValue<T>>,
    pairing: &Pairing<T>,
    curve_points: &CurvePoints<T>,
    hash: &H,
    signing: &G,
) -> ErrorOr<Fp12Elem<T>>
where
    T: ExtensionField
        + PairingConfig
        + BitRepr
        + Hashable
        + From<[u8; 64]>
        + Default
        + ConstantSwap,
{
    verify_signed_value(signed_encrypted_value, signing).map_or(
        Result::Err(InternalError::InvalidEncryptedMessageSignature),
        |good_encrypted_value| match good_encrypted_value {
            EncryptedValue::EncryptedOnce(encrypted_once_value) => {
                let unverified_plaintext = decrypt_encrypted_once(
                    private_key,
                    &encrypted_once_value,
                    pairing,
                    curve_points,
                )?;
                compute_and_compare_auth_hash(
                    encrypted_once_value.auth_hash,
                    encrypted_once_value.ephemeral_public_key,
                    unverified_plaintext,
                    hash,
                )
            }
            EncryptedValue::Reencrypted(re_value) => {
                let unverified_plaintext =
                    decrypt_reencrypted_value(private_key, &re_value, curve_points, pairing, hash)?;
                compute_and_compare_auth_hash(
                    re_value.auth_hash,
                    re_value.ephemeral_public_key,
                    unverified_plaintext,
                    hash,
                )
            }
        },
    )
}

fn compute_and_compare_auth_hash<FP, H: Sha256Hashing>(
    candidate_auth_hash: AuthHash,
    public_key: PublicKey<FP>,
    unverified_plaintext: Fp12Elem<FP>,
    hash: &H,
) -> ErrorOr<Fp12Elem<FP>>
where
    FP: ExtensionField + PairingConfig + BitRepr + Hashable,
{
    let computed_auth_hash = AuthHash::create(hash, &(&public_key, &unverified_plaintext));

    if candidate_auth_hash != computed_auth_hash {
        Result::Err(InternalError::AuthHashMatchFailed)
    } else {
        Result::Ok(unverified_plaintext)
    }
}

/// Decrypt an `EncryptedOnceValue` using private_key.
///
/// # Arguments
/// `private_key`       - private_key matching the publicKey that was used in encrypt.
/// `encrypted_value`   - encrypted_value which needs to be decrypted.
/// `pairing`           - Optimal Ate Pairing
/// `curve_points`      - IronCore's curve
///
/// # Return
/// decrypted value as an FP12 element
fn decrypt_encrypted_once<T>(
    private_key: PrivateKey<T>,
    encrypted_value: &EncryptedOnceValue<T>,
    pairing: &Pairing<T>,
    curve_points: &CurvePoints<T>,
) -> ErrorOr<Fp12Elem<T>>
where
    T: ExtensionField + PairingConfig + BitRepr + ConstantSwap,
{
    let g1 = curve_points.g1;
    let EncryptedOnceValue {
        ephemeral_public_key,
        encrypted_message,
        ..
    } = encrypted_value; //works for now since their is only one case for this enum

    //This is because:
    // m*pair(P,Q)*pair(P,-Q) = m*pair(P,Q)*pair(P,Q)^(-1) = m
    Ok(*encrypted_message * pairing.pair(-(ephemeral_public_key.value * private_key.value), g1)?)
}
/// Decrypt a reencryptedValue using the provided privateKey.
///
/// # Arguments
/// `private_key`       - private key that the ReencryptedValue is destined for.
/// `reencrypted_value` - reencrypted value to decrypt.
///
/// # Return
///  decrypted value as FP12 element
fn decrypt_reencrypted_value<FP, H>(
    private_key: PrivateKey<FP>,
    reencrypted_value: &ReencryptedValue<FP>,
    curve_points: &CurvePoints<FP>,
    pairing: &Pairing<FP>,
    sha256: &H,
) -> ErrorOr<Fp12Elem<FP>>
where
    FP: Hashable
        + ExtensionField
        + PairingConfig
        + BitRepr
        + From<[u8; 64]>
        + Default
        + ConstantSwap,
    H: Sha256Hashing,
{
    let re_blocks = &reencrypted_value.encryption_blocks;
    // algorithm specifies that we should operate on the last element of the reencryption blocks
    let re_blocks_last = re_blocks.last();
    let ReencryptionBlock {
        public_key: re_pub_key_last,
        encrypted_temp_key: encrypted_k_last,
        rand_re_public_key: rand_re_pub_key_last,
        encrypted_rand_re_temp_key: enc_rand_re_k_last,
    } = re_blocks_last;

    let sec_to_last_k = KValue(
        *encrypted_k_last
            * pairing.pair(-re_pub_key_last.value * private_key.value, curve_points.g1)?,
    );
    let sec_to_last_rand_re_k = KValue(
        *enc_rand_re_k_last
            * pairing.pair(
                -rand_re_pub_key_last.value * private_key.value,
                curve_points.g1,
            )?,
    );
    //We're going through the list backwards because we unravel the reencryption blocks from last to first, the last one is special so it's done first.
    let (first_k, first_rand_re_k) = re_blocks.to_vec().iter().rev().skip(1).try_fold(
        (sec_to_last_k, sec_to_last_rand_re_k),
        |(curr_k, curr_rand_re_k), curr_re_block| -> Result<_, PointErr> {
            let ReencryptionBlock {
                public_key: next_re_pub_key,
                encrypted_temp_key: next_enc_k,
                rand_re_public_key: next_rand_re_pub_key,
                encrypted_rand_re_temp_key: next_enc_rand_re_k,
            } = curr_re_block;
            let curr_k_hash = hash2(curr_k, curve_points, sha256);
            let new_k = KValue(*next_enc_k * pairing.pair(-next_re_pub_key.value, curr_k_hash)?);
            let new_rand_re_k = KValue(
                *next_enc_rand_re_k
                    * pairing.pair(
                        -next_rand_re_pub_key.value,
                        hash2(curr_rand_re_k, curve_points, sha256) + curr_k_hash,
                    )?,
            );
            Ok((new_k, new_rand_re_k))
        },
    )?;
    Ok(reencrypted_value.encrypted_message
        * pairing.pair(
            reencrypted_value.ephemeral_public_key.value.neg(),
            hash2(first_k, curve_points, sha256) + hash2(first_rand_re_k, curve_points, sha256),
        )?)
}

/// Verifies the Ed25519 signature on a signed value.
///
/// # Arguments
/// `signed_value`  - encrypted value with the public signing key and signature
/// `sign`          - Ed25519Signing signing
///
/// # Return
/// Some around the payload if the signature was valid, or None otherwise
///
fn verify_signed_value<T: Hashable + Clone, G: Ed25519Signing>(
    signed_value: SignedValue<T>,
    sign: &G,
) -> Option<T> {
    if sign.verify(
        &signed_value,
        &signed_value.signature,
        &signed_value.public_signing_key,
    ) {
        Some(signed_value.payload)
    } else {
        None
    }
}

/// Generate a reencryption key which allows the private key of `to_public_key` to decrypt a message
/// from a (different) from_public_key.
/// The result will be signed using the signingKey.
///
/// # Arguments
/// `from_private_key`          - The privateKey matching the from_public_key
/// `to_public_key`             - the public key to transform to
/// `reencryption_private_key`  - a random private key
/// `new_k`                     - a random FP12 element
/// `signing_keypair`           - Ed25519 keypair to use to sign reencryption key
/// `curve_points`              - IronCore's curve
/// `sha256`                    - Sha256 instance
/// `ed25519`                   - Ed25519Signing instance
///
/// # Return
///  reencryption key, along with an Ed25519 public signing key and Ed25519 signature
/// @clintfred are there some equations or papers we can reference here?
///
pub fn generate_reencryption_key<FP, H, S>(
    from_private_key: PrivateKey<FP>,
    to_public_key: PublicKey<FP>,
    reencryption_private_key: PrivateKey<FP>,
    new_k: KValue<FP>,
    signing_keypair: &SigningKeypair,
    curve_points: &CurvePoints<FP>,
    pairing: &Pairing<FP>,
    sha256: &H,
    ed25519: &S,
) -> ErrorOr<SignedValue<ReencryptionKey<FP>>>
where
    FP: ExtensionField
        + PairingConfig
        + BitRepr
        + Hashable
        + From<[u8; 64]>
        + Default
        + ConstantSwap,
    H: Sha256Hashing,
    S: Ed25519Signing,
{
    let g1 = curve_points.g1;

    let re_public_key = public_keygen(reencryption_private_key, curve_points.generator);
    let p = to_public_key.value * reencryption_private_key.value;
    let encrypted_k = pairing.pair(p, g1)? * new_k.0;
    let hashed_k = hash2(new_k, curve_points, sha256) + (g1.neg() * from_private_key);
    let reencryption_key = ReencryptionKey {
        re_public_key,
        to_public_key,
        encrypted_k,
        hashed_k,
    };

    Ok(sign_value(reencryption_key, signing_keypair, ed25519))
}

/// Fp12Elem that is private and is used in the transform/decrypt algorithms
// we don't derive Copy or Clone here on purpose. KValue is a sensitive value.
pub struct KValue<FP: Clear + Default>(pub(crate) Fp12Elem<FP>);

/// Before KValue is dropped, we want to clear the Fp12, to reduce the value's exposure to snooping.
impl<FP: Default> Drop for KValue<FP> {
    fn drop(&mut self) {
        self.0.clear()
    }
}

impl<FP> Hashable for KValue<FP>
where
    FP: Hashable + Default + Clear + Copy,
{
    fn to_bytes(&self) -> Vec<u8> {
        self.0.to_bytes()
    }
}

impl From<api::Plaintext> for KValue<fp_256::Monty> {
    fn from(pt: api::Plaintext) -> Self {
        KValue(*pt.internal_fp12())
    }
}
impl From<api_480::Plaintext> for KValue<fp_480::Monty> {
    fn from(pt: api_480::Plaintext) -> Self {
        KValue(*pt.internal_fp12())
    }
}

/// Arbitrary hash function to hash an integer into points base field subgroup of the elliptic curve
///
/// # Arguments
/// `k_value`       - Fp12 element to use in the hash
/// `curve_points`  - IronCore's curve
/// `sha256`        - Sha256 implementation
///
fn hash2<FP, H>(
    k_value: KValue<FP>,
    curve_points: &CurvePoints<FP>,
    sha256: &H,
) -> TwistedHPoint<FP>
where
    FP: Hashable + From<[u8; 64]> + BitRepr + Default + ExtensionField + ConstantSwap,
    H: Sha256Hashing,
{
    let hash_element = curve_points.hash_element;
    //Produce a 512 bit byte vector, which ensures we have a big enough value for 480 and Fp
    //We use a constant value combined with the entire fp12 element so we don't leak information about the fp12 structure.
    let bytes = array_concat_32(
        &sha256.hash(&(&0u8, &k_value)),
        &sha256.hash(&(&1u8, &k_value)),
    );
    let fp = FP::from(bytes);
    hash_element * fp
}

/// A reencryption key allows a message encrypted to one public key (the key of the delegator)
/// to be transformed as if it was encrypted to another public key (the key of hte delegatee),
/// so it can be decrypted using the delegatee's private key.
///
/// `re_public_key` - ephemeral key that encrypted the `encrypted_k` value; unique to this ReencryptionKey
/// `to_public_key` - public key of the delegatee (user/device)
/// `encrypted_k`   - random value K, encrypted to the delegatee, that is used to unroll
///                   successive levels of multi-hop transform encryption
/// `hashed_k`      - a combination of the hash of K and the secret key of the delegator,
///                   used to recover `K` from `encrypted_k`
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ReencryptionKey<FP: ExtensionField> {
    pub re_public_key: PublicKey<FP>,
    pub to_public_key: PublicKey<FP>,
    pub encrypted_k: Fp12Elem<FP>,
    pub hashed_k: TwistedHPoint<FP>,
}

impl<FP: Hashable + ExtensionField> Hashable for ReencryptionKey<FP> {
    fn to_bytes(&self) -> ByteVector {
        (&self.re_public_key, &self.to_public_key, &self.encrypted_k).to_bytes()
    }
}

impl<FP: BitRepr + ExtensionField + ConstantSwap> ReencryptionKey<FP> {
    ///Augment this ReencryptionKey with a priv_key. This is useful if the ReencryptionKey was from an unaugmented
    ///private key.
    pub fn augment(
        &self,
        priv_key: &PrivateKey<FP>,
        g1: &TwistedHPoint<FP>,
    ) -> ReencryptionKey<FP> {
        //Note that because priv_key is an Fp negating it would not work, we have to negate the point or the result of
        //the multiplication.
        let new_hashed_k = -(*g1 * priv_key.value) + self.hashed_k;
        ReencryptionKey {
            hashed_k: new_hashed_k,
            ..*self
        }
    }
}

/// Reencrypt an EncryptedValue to a new user. This can be the output of either encrypt() or reencrypt().
/// Will fail if the transformKey signature fails to verify.
///
/// # Arguments
/// `signed_reencryption_key` - A signed version of the reencryption key,which allows a transform from a delegater to a delegatee
/// `signed_encrypted_value`  - A signed version of the encrypted value, which is encrypted to the delegating user.
/// `rand_re_priv_key`            - A new random private key, which will be used to encrypt the rand_re_k.
/// `rand_re_k`                 - A new random integer which is used to ensure that the reencryption block cannot be reused.
/// `private_signing_key`     - The ED25519 private key to sign the reencryption block.
/// `public_signing_key`      - The ED25519 public key matching the private_signing_key.
///
/// # Return
/// Ok(ReencryptedValue) - if the value could be successfully reencrypted
/// - Err(InvalidEncryptedMessageSignature|ReencryptionKeyIsCorrupt) - if the signatures weren't valid.
pub fn reencrypt<FP, S, H>(
    signed_reencryption_key: SignedValue<ReencryptionKey<FP>>,
    signed_encrypted_value: SignedValue<EncryptedValue<FP>>,
    rand_re_priv_key: PrivateKey<FP>,
    rand_re_k: KValue<FP>,
    signing_keypair: &SigningKeypair,
    ed25519: &S,
    sha256: &H,
    curve_points: &CurvePoints<FP>,
    pairing: &Pairing<FP>,
) -> ErrorOr<SignedValue<EncryptedValue<FP>>>
where
    FP: Hashable
        + ExtensionField
        + PairingConfig
        + BitRepr
        + From<[u8; 64]>
        + Default
        + ConstantSwap,
    H: Sha256Hashing,
    S: Ed25519Signing,
{
    match (
        verify_signed_value(signed_encrypted_value, ed25519),
        verify_signed_value(signed_reencryption_key, ed25519),
    ) {
        (Some(EncryptedValue::EncryptedOnce(encrypted_val)), Some(re_key)) => Ok(sign_value(
            EncryptedValue::Reencrypted(reencrypt_encrypted_once(
                re_key,
                encrypted_val,
                rand_re_priv_key,
                rand_re_k,
                curve_points,
                pairing,
                sha256,
            )?),
            signing_keypair,
            ed25519,
        )),

        (Some(EncryptedValue::Reencrypted(encrypted_val)), Some(re_key)) => Ok(sign_value(
            EncryptedValue::Reencrypted(reencrypt_reencrypted_value(
                re_key,
                &encrypted_val,
                rand_re_priv_key,
                rand_re_k,
                curve_points,
                pairing,
                sha256,
            )?),
            signing_keypair,
            ed25519,
        )),
        (None, _) => Err(InternalError::InvalidEncryptedMessageSignature),
        (_, None) => Err(InternalError::CorruptReencryptionKey),
    }
}

/**
 * Reencrypt an EncryptedValue to a new key.
 * `reencryption_key` - The reencryption key, which allows a transform from a delegating user to another user
 * `encrypted_value`  - The encrypted value, which is encrypted to the delegating user.
 * `rand_re_priv_key` - A new random private key, which will be used to encrypt the rand_re_temp_key.
 * `rand_re_temp_key` - A new random integer which is used to ensure that the reencryption block cannot be reused.
 * @return - ReencryptedValue as FP12 element
 */
fn reencrypt_encrypted_once<FP, H>(
    ReencryptionKey {
        re_public_key,
        to_public_key,
        encrypted_k,
        hashed_k,
    }: ReencryptionKey<FP>,
    EncryptedOnceValue {
        ephemeral_public_key,
        encrypted_message,
        auth_hash,
    }: EncryptedOnceValue<FP>,
    rand_re_priv_key: PrivateKey<FP>,
    rand_re_temp_key: KValue<FP>,
    curve_points: &CurvePoints<FP>,
    pairing: &Pairing<FP>,
    sha256: &H,
) -> ErrorOr<ReencryptedValue<FP>>
where
    FP: Hashable
        + ExtensionField
        + PairingConfig
        + BitRepr
        + From<[u8; 64]>
        + Default
        + ConstantSwap,
    H: Sha256Hashing,
{
    // encrypt and product auth hashes for the rand_re_temp_key
    let rand_re_public_key = public_keygen(rand_re_priv_key, curve_points.generator);

    let encrypted_rand_re_temp_key =
        pairing.pair(to_public_key.value * rand_re_priv_key, curve_points.g1)? * rand_re_temp_key.0;
    // Because this is the first reencryption, modify the encrypted_message using rand_re_temp_key
    // Note that this can be decrypted using the reencryption key
    let encrypted_msg_prime = pairing.pair(
        ephemeral_public_key.value,
        hashed_k + hash2(rand_re_temp_key, curve_points, sha256),
    )? * encrypted_message;
    let new_encypted_data = EncryptedOnceValue {
        ephemeral_public_key,
        encrypted_message: encrypted_msg_prime,
        auth_hash,
    };
    let reencryption_block = ReencryptionBlock {
        public_key: re_public_key,
        encrypted_temp_key: encrypted_k,
        rand_re_public_key,
        encrypted_rand_re_temp_key,
    };
    Ok(ReencryptedValue::from_encrypted_once(
        &new_encypted_data,
        NonEmptyVec::new_first(reencryption_block),
    ))
}

/**
 * Reencrypt a value which was already Reencrypted to yet another person. This is hops 3 through N and can be chained indefinitely.
 * reencryption_key - The key which allows the transform from the current last reencyption block to the reencryption_key.toPublicKey
 * reencrypted_value - Reencrypted value which is going to be transformed
 * rand_re_priv_key - A new random private key, which will be used to encrypt the rand_re_temp_key.
 * rand_re_temp_key - A new random integer which is used to ensure that the reencryption block cannot be reused.
 */
fn reencrypt_reencrypted_value<FP, H>(
    ReencryptionKey {
        re_public_key,
        to_public_key,
        encrypted_k,
        hashed_k,
    }: ReencryptionKey<FP>,
    reencrypted_value: &ReencryptedValue<FP>,
    rand_re_priv_key: PrivateKey<FP>,
    rand_re_temp_key: KValue<FP>,
    curve_points: &CurvePoints<FP>,
    pairing: &Pairing<FP>,
    sha256: &H,
) -> ErrorOr<ReencryptedValue<FP>>
where
    FP: Hashable
        + ExtensionField
        + PairingConfig
        + BitRepr
        + From<[u8; 64]>
        + Default
        + ConstantSwap,
    H: Sha256Hashing,
{
    let re_blocks = reencrypted_value.encryption_blocks.clone();
    // algorithm specifies that we should operate on the last element of the reencryption blocks
    let re_blocks_last = re_blocks.last();
    let ReencryptionBlock {
        public_key: re_pub_key_last,
        encrypted_temp_key: encrypted_k_last,
        rand_re_public_key: rand_re_pub_key_last,
        encrypted_rand_re_temp_key: enc_rand_re_k_last,
    } = re_blocks_last;
    let encrypted_k_prime_last =
        *encrypted_k_last * pairing.pair(re_pub_key_last.value, hashed_k)?; // re-encrypted K
    let rand_re_pub_key = public_keygen(rand_re_priv_key, curve_points.generator);
    let enc_rand_re_temp_key =
        pairing.pair(to_public_key.value * rand_re_priv_key, curve_points.g1)? * rand_re_temp_key.0;
    // Modify the enc_rand_re_temp_key of the last block with the new random reencryption K
    let rand_re_k_last_prime = *enc_rand_re_k_last
        * pairing.pair(
            rand_re_pub_key_last.value,
            hash2(rand_re_temp_key, curve_points, sha256) + hashed_k,
        )?;
    let re_block_last_prime =
        re_blocks_last.with_temp_key(encrypted_k_prime_last, rand_re_k_last_prime);
    let new_re_block = ReencryptionBlock {
        public_key: re_public_key,
        encrypted_temp_key: encrypted_k,
        rand_re_public_key: rand_re_pub_key,
        encrypted_rand_re_temp_key: enc_rand_re_temp_key,
    };
    // Because we modified the last block, replace it and append the new block as well
    let new_len = re_blocks.len() - 1;
    let new_blocks_vec = NonEmptyVec::new_last(
        &re_blocks.to_vec()[..new_len],
        NonEmptyVec::new(re_block_last_prime, vec![new_re_block]),
    );
    Ok(reencrypted_value.with_new_re_blocks(new_blocks_vec))
}

/// Acquire mutex in a blocking fashion. If the Mutex is or becomes poisoned, write out an error
/// message and panic.
///
/// The lock is released when the returned MutexGuard falls out of scope.
///
/// # Usage:
/// single statement (mut)
/// `let result = take_lock(&t).deref_mut().call_method_on_t();`
///
/// mutli-statement (mut)
///
/// ```ignore
/// let t = T {};
/// let result = {
///     let g = &mut *take_lock(&t);
///     g.call_method_on_t()
/// }; // lock released here
/// ```
///
pub(crate) fn take_lock<T>(m: &Mutex<T>) -> MutexGuard<T> {
    m.lock().unwrap_or_else(|e| {
        let error = format!("Error when acquiring lock: {}", e);
        error!("{}", error);
        panic!("{}", error);
    })
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::api::test::DummyRandomBytes;
    use crate::internal::ed25519::Ed25519;
    use crate::internal::fp::fp256_unsafe_from;
    use crate::internal::fp12elem::test::arb_fp12;
    use crate::internal::homogeneouspoint::test::arb_homogeneous_256;
    use crate::internal::sha256::Sha256;
    use crate::internal::sum_n;
    use num_traits::Pow;
    use proptest::arbitrary::any;
    use proptest::prelude::*;
    prop_compose! {
        pub fn arb_fp256()(seed in any::<u32>()) -> fp_256::Monty {
            if seed == 0 {
                fp_256::Monty::zero()
            } else if seed == 1 {
                fp_256::Monty::one()
            } else {
                fp_256::Monty::from(seed).pow(seed).pow(seed)
            }
        }
    }
    prop_compose! {
        pub fn arb_fp480()(seed in any::<u32>()) -> fp_480::Monty {
            if seed == 0 {
                fp_480::Monty::zero()
            } else if seed == 1 {
                fp_480::Monty::one()
            } else {
                fp_480::Monty::from(seed).pow(seed).pow(seed)
            }

        }
    }

    struct Mocks;

    impl Ed25519Signing for Mocks {
        fn sign<T: Hashable>(&self, _t: &T, _signing_keypair: &SigningKeypair) -> Ed25519Signature {
            Ed25519Signature::new([0; 64])
        }

        fn verify<T: Hashable>(
            &self,
            _t: &T,
            _signature: &Ed25519Signature,
            _public_key: &PublicSigningKey,
        ) -> bool {
            true
        }
    }

    impl Sha256Hashing for Mocks {
        fn hash<T: Hashable>(&self, _t: &T) -> [u8; 32] {
            [0; 32]
        }
    }

    struct AlwaysFailVerifyEd25519Signing;

    impl Ed25519Signing for AlwaysFailVerifyEd25519Signing {
        fn sign<T: Hashable>(&self, _t: &T, _signing_keypair: &SigningKeypair) -> Ed25519Signature {
            Ed25519Signature::new([0; 64])
        }

        fn verify<T: Hashable>(
            &self,
            _t: &T,
            _signature: &Ed25519Signature,
            _public_key: &PublicSigningKey,
        ) -> bool {
            false
        }
    }

    //copied from API as it's private there
    fn gen_random_fp12<R: rand_bytes::RandomBytesGen>(
        random_bytes: &mut R,
    ) -> Fp12Elem<fp_256::Monty> {
        // generate 12 random Fp values
        gen_rth_root(
            &pairing::Pairing::new(),
            Fp12Elem::create_from_t(
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
                Fp256::from(random_bytes.random_bytes_32()),
            )
            .map(&|fp| fp.to_monty()),
        )
    }

    #[test]
    fn array_concat_32_array_split_roundtrip() {
        let one = [1u8; 32];
        let two = [2u8; 32];
        let concat = array_concat_32(&one, &two);
        let (res1, res2) = array_split_64(&concat);
        assert_eq!(one, res1);
        assert_eq!(two, res2);
    }

    #[test]
    fn pow_for_square_works() {
        let v = Fp256::from(10u8);
        let result = pow_for_square(v, 2);
        assert_eq!(result, v.pow(2));
        let result2 = pow_for_square(v, 5);
        assert_eq!(result2, v.pow(5));
    }
    #[test]
    fn fp480_pow_for_square_works() {
        let v = fp_480::Monty::from(10u8);
        let result = pow_for_square(v, 2);
        assert_eq!(result, v.pow(2));
        let result2 = pow_for_square(v, 5);
        assert_eq!(result2, v.pow(5));
    }
    #[test]
        #[rustfmt::skip]
        fn create_transform_key_known_value() {
            let pairing = pairing::Pairing::new();
            let ref curve_points = *curve::FP_256_CURVE_POINTS;
            let ref sha256 = sha256::Sha256;
            let ref ed25519 = api::test::DummyEd25519;
            let signing_keypair = ed25519::test::good_signing_keypair();

            //37777967648492203239675772600961898148040325589588086812374811831221462604944
            let private_key = PrivateKey {
                value: fp256_unsafe_from("5385926b9f6135086d1912901e5a433ffcebc19a30fadbd0ee8cee26ba719c90").to_monty()
            };

            //22002131259228303741090495322318969764532178674829148099822698556219881568451
            let re_private_key = PrivateKey {
                value: fp256_unsafe_from("30a4c3d5f31a096db20eed892919e542427341d7aec1e1494275831bbca638c3").to_monty()
            };

            //56377452267431283559088187378398270325210563762492926393848580098576649271541
            let parsed_pub_key_x = fp256_unsafe_from("7ca481d71abbae43395152eb7baa230d60543d43e2e8f89a18d182ecf8c3b8f5").to_monty();
            //46643694276241842996939080253335644316475473619096522181405937227991761798154
            let parsed_pub_key_y = fp256_unsafe_from("671f653900901fc3688542e5939ba6c064a7768f34fe45492a49e1f6d4d7c40a").to_monty();
            let public_key = PublicKey::from_x_y(parsed_pub_key_x, parsed_pub_key_y).unwrap();

            let salt = KValue(Fp12Elem::create_from_t(
                //20621517740542501009268492188240231175004875885443969425948886451683622135253
                fp256_unsafe_from("2d975d8c65b577810297bc5b7193691a6892cefacbee2544fb16f67ba7c825d5"),
                //34374877744619883729582518521480375735530540362125629015072222432427068254516
                fp256_unsafe_from("4bff7dc7983fb830ec19f39e78268d8191d96ec9974ac41ef8100acca66e6934"),
                //3061516916225902041514148805993070634368655849312514173666756917317148753791
                fp256_unsafe_from("6c4c1d5c2d00bbfc5eac19626b1967ce3ca5a60bce0122626d0662f53463f7f"),
                //36462333850830053304472867079357777410712443208968594405185610332940263631144
                fp256_unsafe_from("509cf319e11149b9f03c5c442efecb02c3fd98c4034490c505c6983f4f862928"),
                //61512103449194136219283269928242996434577060883392017268158197606945715641345
                fp256_unsafe_from("87fe9de48e020614a01af0a1ae62a44b47342ead7d99af4c6a0f1eec6cbfdc01"),
                //6400685679296646713554926627062187315936943674688629293503755450503276487519
                fp256_unsafe_from("e26a8e2e7136d4027e2ff75226d1c456767129d1232fa257503f6cf34cb635f"),
                //53751186939356616119935218564341196608994152768328518524478036628068165341835
                fp256_unsafe_from("76d617fc05a8f85acff827a14f3ffdc99be13ac7bb0b823d30d8752bf205d28b"),
                //24086990466602794093787211540995552936111869178774386613517233502609109093865
                fp256_unsafe_from("3540c0e3e718e5cd7d1153d3d392e246a251d8f4a5d7490b4fa18d369a270de9"),
                //61396452992397102589850224464045014903468298857108669606429537125544948220026
                fp256_unsafe_from("87bd2932b2a9e20f9b51b8b1adfeda2b434985710e73bb335d5b568eb8c1407a"),
                //15909384434160564083979503677021998800821775569159782381560100961841901513229
                fp256_unsafe_from("232c6479f7e8768b2e85a025cb9e7162315ff7ebeef6bd73ef896bb14748aa0d"),
                //60608834117224548548490931258722195552088501100182383267798941700183023164589
                fp256_unsafe_from("85ff626aef9f9cef0310f93a9541ef9ce2207eb9b57077db4572531a879c1cad"),
                //17433339776741835027827317970122814431745024562995872600925458287403992082321
                fp256_unsafe_from("268aebaf44e6ae76c70f48aed806180ced89dfc17f962de209f2a3437b4fe791")
            ) .map(&|fp| fp.to_monty()));

            let re_key = generate_reencryption_key(
                private_key,
                public_key,
                re_private_key,
                salt,
                &signing_keypair,
                curve_points,
                &pairing,
                sha256,
                ed25519).unwrap().payload;


            let good_encrypted_k = Fp12Elem::create_from_t(
                //12468319166808038973695145957420708896894989833062445136258751614714728860674
                fp256_unsafe_from("1b90d18d6c2e5cc05034aad9fb05ac729f16faa377dd7aa566cd26541426cc02"),
                //2128854359484213588380497318350091194644767868675298191307887815867151037785
                fp256_unsafe_from("4b4e38cd756a3a0b8e0c4410b6a13d83b6687e03e07370249818392c3ca3559"),
                //21445177129013608048665933252873170350264030103628078278539253145170500354896
                fp256_unsafe_from("2f698a45185e09ed53f5ed58717ed285f0320a1578b4b896a5e9790e1b938350"),
                //56038156109742185788555899045964131128124373881320309662221006159550412032591
                fp256_unsafe_from("7be478ed278532693e88fd07aacc59aee1e3f810680ee60f990c8046de5d0a4f"),
                //26914485966895326452176682212429959307113556759541870452048350761624956307220
                fp256_unsafe_from("3b810f28e8f4bbea9abad6e971c87354a13b82b20c5c6831cf30f3f68b9c0714"),
                //47982890198790824855446467783137770650698480322416616491158632550503307113855
                fp256_unsafe_from("6a155a7a87f8ddcc593e88c60a92d0968777d94c0022c02be73f7f8108b5757f"),
                //10711644709861300386519729424210641312562914145500366805337572652137411248028
                fp256_unsafe_from("17ae93786aff5ee3066e8e63625fdc9cfb59c0d97b7fe35f9ef768fdb407ff9c"),
                //43347748542964759695743884414110499509686690671474974704725984636415455585090
                fp256_unsafe_from("5fd5f4cb23e1ea76cb3fc5acc8a3811a9098da9da23eff2a29e4ecb98458e342"),
                //17596819370787728602670144220164446940512614719945259504347007792221644397210
                fp256_unsafe_from("26e772626d3693c4e90f383cb4e33703d77f3bc0b053f621b6ca2c3235ac1a9a"),
                //38746694397885185117442963327077624722941852366420178940757070667288257609651
                fp256_unsafe_from("55a9da13cb5ed5cdc48eb709e4bb040a4cb7189793bda322df17e977b609bfb3"),
                //38002592948627699884055358711985526413232379309204607956998925141882284407414
                fp256_unsafe_from("5404b493123a5b087cb6cc363116a9fc393c27dac3efa3df778ec974b8bdfa76"),
                //49941783284737700171954796486309114156831309184207470337019703077683277143423
                fp256_unsafe_from("6e6a0c315c47fa2990268bc2010eeda17b78cc32e7ab6f093bf1aa2234491d7f")
            ) .map(&|fp| fp.to_monty());

            let good_hashed_k = TwistedHPoint {
                x: fp2elem::Fp2Elem {
                    //22422077836615563303859289302360681993513791649150629779433386726239734002618
                    elem1: fp256_unsafe_from("319272423a484aae2063912bbbf6a61f58ae4eaa9b0aca8dd493a050a6aef3ba"),
                    //52027607726191528801608200848909742835559726958532705354275625739433988283036
                    elem2: fp256_unsafe_from("73069519d5de19f839fbd1c6ff3a7bece381de22395cfe51c5636aafd77f5a9c")
                },
                y: fp2elem::Fp2Elem {
                    //4645247538810084127206561896955679361089846324400619601984418530338530410972
                    elem1: fp256_unsafe_from("a451def6c985a08374ed5f96426b861125c3e418ebe4f2f587fdf86f4d441dc"),
                    //34485967566945102067940948289591403523736094597232197502117656471251591927196
                    elem2: fp256_unsafe_from("4c3e5dae1e009f89877a44eea4e491285b6198941bbcc17545f3ffc07fd8959c")
                },
                z: fp2elem::Fp2Elem {
                    //2258730050599105467979057492418988601962605970075660368624801947296357678159
                    elem1: fp256_unsafe_from("4fe6559f6d8e64c53addb0ac095bea18ff0cdf9cf84e93dd8e4bc7cc5bbfc4f"),
                    //18358874287282838450918898019272385250887285769392485811015731318886010089831
                    elem2: fp256_unsafe_from("2896c12e42c82648d4e553e82b32abe95618f0e5ad7f8ba14d6180b78ae67167")
                }
            } .map(&|fp| fp.to_monty());

            assert_eq!(good_encrypted_k, re_key.encrypted_k);
            assert_eq!(good_hashed_k, re_key.hashed_k)
        }

    #[test]
    fn reencrypt_roundtrip_with_known_keys() {
        let pt_fp12 = Fp12Elem::create_from_t(
            Fp256::from(1u8),
            Fp256::from(2u8),
            Fp256::from(3u8),
            Fp256::from(4u8),
            Fp256::from(5u8),
            Fp256::from(6u8),
            Fp256::from(7u8),
            Fp256::from(8u8),
            Fp256::from(9u8),
            Fp256::from(10u8),
            Fp256::from(11u8),
            Fp256::from(12u8),
        )
        .map(&|fp| fp.to_monty());
        let salt_fp12 = Fp12Elem::create_from_t(
            Fp256::from(11u8),
            Fp256::from(12u8),
            Fp256::from(13u8),
            Fp256::from(14u8),
            Fp256::from(15u8),
            Fp256::from(16u8),
            Fp256::from(17u8),
            Fp256::from(18u8),
            Fp256::from(19u8),
            Fp256::from(110u8),
            Fp256::from(111u8),
            Fp256::from(112u8),
        )
        .map(&|fp| fp.to_monty());
        let rand_re_k_fp12 = Fp12Elem::create_from_t(
            Fp256::from(21u8),
            Fp256::from(22u8),
            Fp256::from(23u8),
            Fp256::from(24u8),
            Fp256::from(25u8),
            Fp256::from(26u8),
            Fp256::from(27u8),
            Fp256::from(28u8),
            Fp256::from(29u8),
            Fp256::from(210u8),
            Fp256::from(211u8),
            Fp256::from(212u8),
        )
        .map(&|fp| fp.to_monty());

        let pairing = pairing::Pairing::new();
        let ref curve_points = *curve::FP_256_CURVE_POINTS;
        let ref sha256 = sha256::Sha256;
        let ref ed25519 = api::test::DummyEd25519;
        let salt = KValue(gen_rth_root(&pairing, salt_fp12));
        let signing_keypair = ed25519::test::good_signing_keypair();

        let re_private = PrivateKey::from_fp256(
            //22002131259228303741090495322318969764532178674829148099822698556219881568451
            fp256_unsafe_from("30a4c3d5f31a096db20eed892919e542427341d7aec1e1494275831bbca638c3")
                .to_monty(),
        );
        let ephem_priv_key = PrivateKey::from_fp256(
            //24550233719269254106556478663938123459765238883583743938937070753673053032673
            fp256_unsafe_from("3646f09b1f8ec8c696326e7095a16635d29a5e6d5df5b8c9cd4d15a1ff2550e1")
                .to_monty(),
        );
        let priv_key = PrivateKey::from_fp256(
            //43966559432365357341903140497410248873099149633601160471165130153973144042658
            fp256_unsafe_from("613430d6b5ffee80cb971c85f2ea779d2dd0c020dcdd31a93c46e56c5b2f3ca2")
                .to_monty(),
        );
        let pub_key = public_keygen(priv_key, curve_points.generator);
        let plaintext = pt_fp12;
        let encrypt_result = encrypt(
            pub_key,
            plaintext,
            ephem_priv_key,
            &signing_keypair,
            &pairing,
            curve_points,
            sha256,
            ed25519,
        )
        .unwrap();
        let rand_re_priv_key = PrivateKey::from_fp256(
            //17561965855055966875289582496525889116201409974621952158489640859240156546764
            fp256_unsafe_from("26d3b86dad678314ca9532ff4046e372802d175cd5e1ad63aacdcc968552c6cc")
                .to_monty(),
        );
        let rand_re_k = KValue(gen_rth_root(&pairing, rand_re_k_fp12));
        let re_key = generate_reencryption_key(
            priv_key,
            pub_key,
            re_private,
            salt,
            &signing_keypair,
            curve_points,
            &pairing,
            sha256,
            ed25519,
        )
        .unwrap();

        let reencrypted_value = reencrypt(
            re_key,
            encrypt_result,
            rand_re_priv_key,
            rand_re_k,
            &signing_keypair,
            ed25519,
            sha256,
            curve_points,
            &pairing,
        )
        .unwrap();

        let decrypted_value = decrypt(
            priv_key,
            reencrypted_value,
            &pairing,
            curve_points,
            sha256,
            ed25519,
        )
        .unwrap();
        assert_eq!(decrypted_value, plaintext)
    }

    #[test]
    fn fail_when_verify_fails() {
        let pairing = pairing::Pairing::new();
        let ref curve_points = *curve::FP_256_CURVE_POINTS;
        let ref sha256 = sha256::Sha256;
        let salt1 = gen_rth_root(&pairing, gen_random_fp12(&mut DummyRandomBytes));
        let signing_keypair = ed25519::test::good_signing_keypair();
        let priv_key = PrivateKey::from_fp256(
            //43966559432365357341903140497410248873099149633601160471165130153973144042658
            fp256_unsafe_from("613430d6b5ffee80cb971c85f2ea779d2dd0c020dcdd31a93c46e56c5b2f3ca2")
                .to_monty(),
        );
        let ephem_priv_key = PrivateKey::from_fp256(
            //88866559432365357341903140497410248873099149633601160471165130153973144042888
            fp256_unsafe_from("c478b0b05e9d5cc4a7aaa3e5f991f6f452fd26a72f5415a93c46e56c5b2f3d88")
                .to_monty(),
        );
        let pub_key = public_keygen(priv_key, curve_points.generator);
        let encrypted_value = encrypt(
            pub_key,
            salt1,
            ephem_priv_key,
            &signing_keypair,
            &pairing,
            curve_points,
            sha256,
            &AlwaysFailVerifyEd25519Signing,
        )
        .unwrap();
        let decrypt_result = decrypt(
            priv_key,
            encrypted_value,
            &pairing,
            curve_points,
            sha256,
            &AlwaysFailVerifyEd25519Signing,
        );
        if let Err(InternalError::InvalidEncryptedMessageSignature) = decrypt_result {
            //pass
        } else {
            assert!(false, "Error should have been returned")
        }
    }

    #[test]
    fn fail_when_auth_hash_does_not_match_wrong_priv_key() {
        let pairing = pairing::Pairing::new();
        let ref curve_points = *curve::FP_256_CURVE_POINTS;
        let ref sha256 = sha256::Sha256;
        let salt1 = gen_rth_root(&pairing, gen_random_fp12(&mut DummyRandomBytes));
        let signing_keypair = ed25519::test::good_signing_keypair();
        let priv_key = PrivateKey::from_fp256(
            //43966559432365357341903140497410248873099149633601160471165130153973144042658
            fp256_unsafe_from("613430d6b5ffee80cb971c85f2ea779d2dd0c020dcdd31a93c46e56c5b2f3ca2")
                .to_monty(),
        );
        let ephem_priv_key = PrivateKey::from_fp256(
            //88866559432365357341903140497410248873099149633601160471165130153973144042888
            fp256_unsafe_from("c478b0b05e9d5cc4a7aaa3e5f991f6f452fd26a72f5415a93c46e56c5b2f3d88")
                .to_monty(),
        );
        let pub_key = public_keygen(priv_key, curve_points.generator);
        let encrypted_value = encrypt(
            pub_key,
            salt1,
            ephem_priv_key,
            &signing_keypair,
            &pairing,
            curve_points,
            sha256,
            &Mocks,
        )
        .unwrap();

        let diff_priv_key = PrivateKey::from_fp256(fp_256::Monty::from(42u8));
        let decrypt_result = decrypt(
            diff_priv_key,
            encrypted_value,
            &pairing,
            curve_points,
            sha256,
            &Mocks,
        );

        if let Err(InternalError::AuthHashMatchFailed) = decrypt_result {
            //pass
        } else {
            assert!(false, "Auth hash check should fail")
        }
    }
    #[test]
    fn reencrypt_2nd_level_roundtrip_with_known_keys_and_auth_hash_checks() {
        let pt_fp12 = Fp12Elem::create_from_t(
            Fp256::from(1u32),
            Fp256::from(2u32),
            Fp256::from(3u32),
            Fp256::from(4u32),
            Fp256::from(5u32),
            Fp256::from(6u32),
            Fp256::from(7u32),
            Fp256::from(8u32),
            Fp256::from(9u32),
            Fp256::from(10u32),
            Fp256::from(11u32),
            Fp256::from(12u32),
        )
        .map(&|fp| fp.to_monty());
        let salt_1_fp12 = Fp12Elem::create_from_t(
            Fp256::from(11u32),
            Fp256::from(12u32),
            Fp256::from(13u32),
            Fp256::from(14u32),
            Fp256::from(15u32),
            Fp256::from(16u32),
            Fp256::from(17u32),
            Fp256::from(18u32),
            Fp256::from(19u32),
            Fp256::from(110u32),
            Fp256::from(111u32),
            Fp256::from(112u32),
        )
        .map(&|fp| fp.to_monty());
        let rand_re_k_1_fp12 = Fp12Elem::create_from_t(
            Fp256::from(21u32),
            Fp256::from(22u32),
            Fp256::from(23u32),
            Fp256::from(24u32),
            Fp256::from(25u32),
            Fp256::from(26u32),
            Fp256::from(27u32),
            Fp256::from(28u32),
            Fp256::from(29u32),
            Fp256::from(210u32),
            Fp256::from(211u32),
            Fp256::from(212u32),
        )
        .map(&|fp| fp.to_monty());
        let salt_2_fp12 = Fp12Elem::create_from_t(
            Fp256::from(31u32),
            Fp256::from(32u32),
            Fp256::from(33u32),
            Fp256::from(34u32),
            Fp256::from(35u32),
            Fp256::from(36u32),
            Fp256::from(37u32),
            Fp256::from(38u32),
            Fp256::from(39u32),
            Fp256::from(310u32),
            Fp256::from(311u32),
            Fp256::from(312u32),
        )
        .map(&|fp| fp.to_monty());
        let rand_re_k_2_fp12 = Fp12Elem::create_from_t(
            Fp256::from(41u32),
            Fp256::from(42u32),
            Fp256::from(43u32),
            Fp256::from(44u32),
            Fp256::from(45u32),
            Fp256::from(46u32),
            Fp256::from(47u32),
            Fp256::from(48u32),
            Fp256::from(49u32),
            Fp256::from(410u32),
            Fp256::from(411u32),
            Fp256::from(412u32),
        )
        .map(&|fp| fp.to_monty());

        let pairing = pairing::Pairing::new();
        let ref curve_points = *curve::FP_256_CURVE_POINTS;
        let ref sha256 = sha256::Sha256;
        let ref ed25519 = api::test::DummyEd25519;
        let salt1 = KValue(gen_rth_root(&pairing, salt_1_fp12));
        let signing_keypair = ed25519::test::good_signing_keypair();

        let re_private = PrivateKey::from_fp256(
            //22002131259228303741090495322318969764532178674829148099822698556219881568451
            fp256_unsafe_from("30a4c3d5f31a096db20eed892919e542427341d7aec1e1494275831bbca638c3")
                .to_monty(),
        );
        let ephem_priv_key = PrivateKey::from_fp256(
            //24550233719269254106556478663938123459765238883583743938937070753673053032673
            fp256_unsafe_from("3646f09b1f8ec8c696326e7095a16635d29a5e6d5df5b8c9cd4d15a1ff2550e1")
                .to_monty(),
        );
        let priv_key = PrivateKey::from_fp256(
            //43966559432365357341903140497410248873099149633601160471165130153973144042658
            fp256_unsafe_from("613430d6b5ffee80cb971c85f2ea779d2dd0c020dcdd31a93c46e56c5b2f3ca2")
                .to_monty(),
        );
        let pub_key = public_keygen(priv_key, curve_points.generator);
        let priv_key2 = PrivateKey::from_fp256(
            //22266559432365357341903140497410248873090149633601160471165130153973144042608
            fp256_unsafe_from("313a6d10030318ffa481b32fa104b4a77d6ad640a87bade5ee9e4ddc5b2f3c70")
                .to_monty(),
        );
        let pub_key2 = public_keygen(priv_key2, curve_points.generator);
        let priv_key3 = PrivateKey::from_fp256(
            //33333359432365357341903140497410248873090149633601160471165130153973144042608
            fp256_unsafe_from("49b2034a4bc9614d95bdac29251fb567b2ed2b41b0d25be5ee9e4ddc5b2f3c70")
                .to_monty(),
        );
        let pub_key3 = public_keygen(priv_key3, curve_points.generator);

        let plaintext = gen_rth_root(&pairing, pt_fp12);

        // First level encryption
        let encrypt_result = encrypt(
            pub_key,
            plaintext,
            ephem_priv_key,
            &signing_keypair,
            &pairing,
            curve_points,
            sha256,
            ed25519,
        )
        .unwrap();
        let rand_re_priv_key = PrivateKey::from_fp256(
            //17561965855055966875289582496525889116201409974621952158489640859240156546764
            fp256_unsafe_from("26d3b86dad678314ca9532ff4046e372802d175cd5e1ad63aacdcc968552c6cc")
                .to_monty(),
        );
        let rand_re_k = KValue(gen_rth_root(&pairing, rand_re_k_1_fp12));
        let re_key = generate_reencryption_key(
            priv_key,
            pub_key2,
            re_private,
            salt1,
            &signing_keypair,
            curve_points,
            &pairing,
            sha256,
            ed25519,
        )
        .unwrap();

        //first level of REencryption
        let reencrypted_value = reencrypt(
            re_key,
            encrypt_result,
            rand_re_priv_key,
            rand_re_k,
            &signing_keypair,
            ed25519,
            sha256,
            curve_points,
            &pairing,
        )
        .unwrap();

        // the fun has just begun! Do a second level of reencryption
        let rand_re_priv_key_2 = PrivateKey::from_fp256(
            //1756196585505596687528958249652588911620140997462195215848000000000
            fp256_unsafe_from("0000000010ad13d179057a034b800241c7ebb038b4be347ec9bde3e352291000")
                .to_monty(),
        );
        let re_priv_2 = PrivateKey::from_fp256(
            //22002131259228303741090495322318969763333178674829148099822698556219881568451
            fp256_unsafe_from("30a4c3d5f31a096db20eed892919e53ebc6c41c1ec39b2a68347872bbca638c3")
                .to_monty(),
        );
        let rand_re_k_2 = KValue(gen_rth_root(&pairing, rand_re_k_2_fp12));
        let salt2 = KValue(gen_rth_root(&pairing, salt_2_fp12));
        let reencryption_key_2 = generate_reencryption_key(
            priv_key2,
            pub_key3,
            re_priv_2,
            salt2,
            &signing_keypair,
            curve_points,
            &pairing,
            sha256,
            ed25519,
        )
        .unwrap();

        let reencrypted_value_2 = reencrypt(
            reencryption_key_2,
            reencrypted_value,
            rand_re_priv_key_2,
            rand_re_k_2,
            &signing_keypair,
            ed25519,
            sha256,
            curve_points,
            &pairing,
        )
        .unwrap();

        if let EncryptedValue::Reencrypted(v) = reencrypted_value_2.payload.clone() {
            assert_eq!(2, v.encryption_blocks.len())
        } else {
            assert!(false, "This should be a reencrypted value")
        }
        //Assert that the to_bytes is a known value which matches recrypt scala.
        let known_to_bytes_value = hex::decode("8538d52016446c7e8f4fa28e333cef771e4c17389682a8515a9e2fbb88caf3f94af54e76c9f0163854b907c19b71a1970877884286173dc8842a80dd46aa9e615ac6a2157793862320033c4024ab86e06417d199811499127bb8b2eb8d69ec833be81a2b786f2fedc61737bb8864d55786547cd09e2f9b640c760355aa86ef685528d7fae7c42389a9a0260b3d20585ff33bf92bc08d2d1a91783a021f169fca1862fb0e038af94b4de96e8eb882437e3a9bd5d8920f2d7feb991efd09f47fb35768b0e0d8d0c3512551ca92bfd0539d7178a3bc6eb0e21d467fc1e2fcf6ad542e1dc66cc03e6b846012bf8f85eaabfc596e3de6044fc0561723431b7f6a5bd8577945d6b3870cecc29b89522b63acea10956d188a5eb74d22686e9248e85c403aca90a727f1f0cefe6e07b9ce6976aa91441a8f52f9acc1df61bf63af6ea5ef1661815f56697f1d5da1028a25917bc0ae8c9d844edc39c955fbe9bb73c5745f288e8f6cef6bf2bc6971bb2f18f227415a6f6fb9112f8a787840da96a5a0a4452fc6dc1ca38196e9ae4420171d71d3a9cdc3bca78047ec363678abcffbc51b743db43e23027edddeba64bcd3074955fce91f00210a910fa6b337afbfbd5f981399a5fe86e2c2dbdfd528730f26e70aadeed4103aab96196911da5900508cf6c571bdc397623d278d8a82224111e7441509d72faba0d8fd8994d74674025c20e34194d9ca607479e476bba584c87e53b33b70bc8b4a200b13eab6f00bcfc4a3ea2ceeee59f1a59c17f8e4934b137e7c2bdaa3a131bf276424a4340e34b57fbc7708ef779ced7c8d3915f4f982d8bd11246b7ed989c19c9826b4ea61ba9cce06320eff550399ee97908b0ba353d5ced125c5edb679a31c1604bd848f77c94d619348cb07a089a743ecbf56cd5e6fb77b719068bcbf4ef57ff8c5f791a4ea807c863f63ff7cbdc105d5b92d666f1fc05a0dad33597da211b5864e9b15969b3bdfd57d5efaad7a15f75d3d4d055c9920d7a2be72d257104ae75721368a645920f43e38b84350522b8d2c589d661ec296d9f9ef6bda55e43760924698d22d16f7cbd51c565ef386e92a600f3414dfe7efd4bdeec09bff1c3bbcf3673b6b0bcd9b74fc183c2079da10c4466af7b39d4cd963611536b67e9b992aec8cc5432f6b38ae20665e8da1bf3474675b4711e91e4ce9d15765ef9b30a67ee68a2dc2cfce6e84415fe38b3acb03a01afb815c232ded3089f258aec45850a672eb18d18e90ea55ee1626528543178f59238b848631640e04b9d55758dc6b32ea92ce00248a7dd5617a0490ffe984617ed44e0748e5fb99f1ada2d26c36b1f9dcf34b2c5247eeefda0c0ed47500da3c806cfcc7035bb1abe6b63dbcd4c1bdbcfe6b369d46117cd6be33b7cc44d2085344dde3ba832a3394ed03d0de5676dac700449765c29f92d3230141e2782d140790878dd447d25faa9da250764e37a5cec2651959dba1a290f3519663b71c8de3618f4b0efbd98dea89b719c3b4513994949acd3bbc3de9c18f8df4a0d1baed760f1bc501d288a58acd18b97bb12746ed5e497366edc75050684a1a7a190a095345a821a1dc167fbbe2a6ba87fe44608c8cbfad83644896bc22477f8e620884815a4eb36a0e2378adcd0c74aa7e941cf166b17554ec66c9265258278bc1b6cf4e2a7ca76f3769bd7a22e10303901abf8c1b19e12c182f7ebc985f68c9376646bce71d39347fd04184ea9bc0eb23dbc11c1f4a2672a6101bc64b549354d40195e682b2ce53f616672170117e23ab1e1eaf141553fd6f759bc8c061c483fb5b287c65a82d6c239b63c73e1f67686acc1d776c72158833e80581f8440fd15ecd376435d8412cc20f965e90e87af8406807b1484d1d8da5c45eb09f3963ebb3de7c120f0542bfb2024adca8956a8da32f03112f91f3bf8c5302a597388f252631820de477856e29e6397c006ee0e28c990ddc3964760eab0d148c5509e606038ef59941322cd535bb6c8e9a8d673213917e6ba1a5119cd7b642303b8c3ad06651f60cc6f8ee75d2a08f561fdec73237471eb0a20f15ac73cf2d7d8476a01bd5fa63b550242fe1bbc1192567a9a80005a974b034f8df8891d926e0dd31c9e575dfce569a7a5d777a67bb9c453d20b62828dc29ed150cff4a9b168c7a8a4989d1b3aa06d8a879a0440f860d4e0b25a8e8c2e30a348d9a151e7a7ceeaf4fa6bc2617e561a83e31cc4bb7a870a413f4d37ead4496eb7e27b07b012b28eb67151af8b26cb2e6c2edfda62d114c67697e53d17911292a398f2087f80744a3030029995e7e7759356374fc431b8cdf4a09544f5699bd429930c6b6152dc2bb07e062c74422e1439293abcc5825fac6ddf3f33d9bf6eb5bd259538aea49758b704cccf440f512680a4bfdb46c90a0b6381d99d390f2c2721970c939b0cce41449fa8eb23f5b941d3aaa6e2cbe94ddb645c16eaf0f3d068d6da459925c9bcfb377cc269bf995d768095d225046792f991edff225ad955f2b6cd5543cac46751c58b7abc78e498b949eec0da842a877f6872c79b96f3220cccc1e3f7adcbf787162dc7841f58943fd97a01945537b294bc285dde3ff6a543875517b0403f1888e0b21d0253b74dbcf8518a89bbeef68d82035a66ac9bc5a2161665295f0fec47f533f124ba35597bf0dded2d67a77688db1e5d8dfd7a38cb8a8efa5ec68fdb6cf04b9254fd2aafa2b0e55089da484507a92d666d0309406cdcc72e0232d9c57138cc52c1ddf36bd51a1078b946496f3559bfc6dd1692ce4a41736dc49c09519f70edff330cd253b7b2797fd89941c330fc07251153f489fa93183c6c7563a792c80c9c52e26f1f63e335776216e534bef47d4771430e9ec2e54fd667385a2619a5f47d42b0f9c025d6df3ee2c81af97e9dcfb17a50b4454d53827ee484296635d30bba9ec1a2537f2084fa4a0fcd4c4a60ba75c8194487c0b95ef8ade74f738fc1f4f97deaca40502105598bc6069e621e297869be84cf6c3b8c3ddb57e27002d10104d20ca31de39336753953faf07e0e0a7124d3fd3b0814e750ac6942b1bdf752440f186a6c0d201dfebf9a97ee62647a817a0dfac3eecc9288d39d567b1d40b11a4265ffdd470565fca9a418d3cb4d62e7964c19e1c3f2984a1d5eb5c0cea2d952b3af8f75014000438f1be0c23e1a151e4eca1018bc7b14bc4df3784f578").unwrap();
        assert_eq!(reencrypted_value_2.payload.to_bytes(), known_to_bytes_value);

        let decrypted_value = decrypt(
            priv_key3,
            reencrypted_value_2.clone(),
            &pairing,
            curve_points,
            sha256,
            ed25519,
        )
        .unwrap();
        assert_eq!(decrypted_value, plaintext);

        //finally, show that a invalid private key will force an auth hash failure
        let invalid_priv_key = PrivateKey::from_fp256(fp_256::Monty::from(42u8));
        let decrypt_auth_failure = decrypt(
            invalid_priv_key,
            reencrypted_value_2,
            &pairing,
            curve_points,
            sha256,
            ed25519,
        );
        if let Err(InternalError::AuthHashMatchFailed) = decrypt_auth_failure {
            // pass
        } else {
            assert!(false, "Private key should have caused an auth hash failure")
        }
    }

    fn good_signing_keys() -> SigningKeypair {
        ed25519::test::good_signing_keypair()
    }

    proptest! {
        #[test]
        fn sign_verify_roundtrip(fp256 in arb_fp256()) {
            let priv_signing_key = good_signing_keys();
            let signed_value = sign_value(fp256, &priv_signing_key, &Ed25519);
            let verified = verify_signed_value(signed_value, &Ed25519);
            prop_assert!(verified.is_some())
        }
        #[test]
        fn encrypt_decrypt_roundtrip(priv_key in arb_priv_key(), plaintext in arb_fp12().prop_filter("", |a| !(*a == Fp12Elem::<fp_256::Monty>::zero()))) {
            let pub_key = public_keygen(priv_key, curve::FP_256_CURVE_POINTS.generator);
            let ephem_secret_key = PrivateKey::from_fp256(fp_256::Monty::from(42u8));
            let priv_signing_key = good_signing_keys();
            let pairing = pairing::Pairing::new();
            let curve_points = &*curve::FP_256_CURVE_POINTS;
            let encrypt_result = encrypt(
                pub_key,
                plaintext,
                ephem_secret_key,
                &priv_signing_key,
                &pairing,
                curve_points,
                &Sha256,
                &Ed25519
            ).unwrap();
            let decrypt_result = decrypt(
                priv_key,
                encrypt_result,
                &pairing,
                curve_points,
                &Sha256,
                &Ed25519
            ).unwrap();
            assert_eq!(plaintext, decrypt_result);
        }

        #[test]
        fn sum_n_is_times(x in any::<i32>(), n in any::<i32>()) {
            //all the casts are to ensure we don't overflow.
            let computed_result = if n < 0 {
                -sum_n(x as i64, n.abs() as u32)
            } else{
                sum_n(x as i64, n as u32)
            };
            prop_assert_eq!(computed_result, (x as i64) * (n as i64));
        }

        #[test]
        fn generate_reencryption_key_sig_not_over_hashedk(
            signed_re_key in arb_reencryption_key(),
            fp in arb_fp256()
            ) {

            let cpoints = &*curve::FP_256_CURVE_POINTS;
            let new_hashed_k = cpoints.g1 * fp;
            let re_key: ReencryptionKey<fp_256::Monty> = signed_re_key.payload.clone();

            // clone the ReencryptionKey, replacing the hashed_k with the arb one
            let re_key_copy = ReencryptionKey {
                re_public_key: re_key.re_public_key,
                to_public_key: re_key.to_public_key,
                encrypted_k: re_key.encrypted_k,
                hashed_k: new_hashed_k // replaced hashed_k!

            };

            prop_assert_eq!(signed_re_key.payload.to_bytes(), re_key_copy.to_bytes())
        }
    }

    prop_compose! {
        pub fn arb_pub_key()(ref hpoint in arb_homogeneous_256().prop_filter("", |a| !(*a == Zero::zero()))) -> PublicKey<fp_256::Monty> {
            PublicKey { value: (*hpoint) }
        }
    }

    prop_compose! {
        pub fn arb_priv_key()(fp256 in arb_fp256().prop_filter("", |a| !(*a == Zero::zero()))) -> PrivateKey<fp_256::Monty> {
            PrivateKey { value: fp256 }
        }
    }

    prop_compose! {
        fn arb_reencryption_key()
        (ref reencryption_private_key in arb_priv_key(),
         ref new_k in arb_fp12(),
         to_public_key in arb_pub_key(),
         from_private_key in arb_priv_key())
         -> SignedValue<ReencryptionKey<fp_256::Monty>> {
            let pairing = pairing::Pairing::new();
            let curve_points = &*curve::FP_256_CURVE_POINTS;

            generate_reencryption_key(
                from_private_key,
                to_public_key,
                *reencryption_private_key,
                KValue(*new_k),
                &ed25519::test::good_signing_keypair(),
                &curve_points,
                &pairing,
                &Mocks,
                &Mocks
            ).unwrap()
        }
    }
}
