// Copyright (c) 2016 rust-threshold-secret-sharing developers
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

//! Montgomery modular multiplication field.

use super::Field;

/// MontgomeryField32 Value (wraps an u32 for type-safety).
#[derive(Copy,Clone,Debug)]
pub struct Value(u32);

/// Implementation of Field with Montgomery modular multiplication.
///
/// See https://en.wikipedia.org/wiki/Montgomery_modular_multiplication
/// for general description of the scheme, or
/// http://www.hackersdelight.org/MontgomeryMultiplication.pdf for
/// implementation notes.
///
/// This implementation assumes R=2^32. In other terms, the modulus must be
/// in the u32 range. All values will be positive, in the 0..modulus range,
/// and represented by a u32.
pub struct MontgomeryField32 {
    pub n: u32, // the prime
    pub n_quote: u32,
    pub r_inv: u32, // r = 2^32
    pub r_cube: u32, // r^3 is used by inv()
}

impl MontgomeryField32 {
    pub fn new(prime: u32) -> MontgomeryField32 {
        let r = 1u64 << 32;
        let tmp = ::numtheory::mod_inverse(r as i64, prime as i64);
        let r_inv = if tmp < 0 {
            (tmp + prime as i64) as u32
        } else {
            tmp as u32
        };
        let tmp = ::numtheory::mod_inverse(prime as i64, r as i64);
        let n_quote = if tmp > 0 {
            (r as i64 - tmp) as u32
        } else {
            (r as i64 - tmp) as u32
        };
        let r_cube = ::numtheory::mod_pow(r as i64 % prime as i64, 3u32, prime as i64);
        MontgomeryField32 {
            n: prime,
            r_inv: r_inv,
            n_quote: n_quote,
            r_cube: r_cube as u32,
        }
    }

    fn redc(&self, a: u64) -> Value {
        let m: u64 = (a as u32).wrapping_mul(self.n_quote) as u64;
        let t: u32 = ((a + m * (self.n as u64)) >> 32) as u32;
        Value(if t >= (self.n) { t - self.n } else { t })
    }
}

impl Field for MontgomeryField32 {
    type U = Value;

    fn modulus(&self) -> u64 {
        self.n as u64
    }

    fn add(&self, a: Self::U, b: Self::U) -> Self::U {
        let sum = a.0 as u64 + b.0 as u64;
        if sum > self.n as u64 {
            Value((sum - self.n as u64) as u32)
        } else {
            Value(sum as u32)
        }
    }

    fn sub(&self, a: Self::U, b: Self::U) -> Self::U {
        if a.0 > b.0 {
            Value(a.0 - b.0)
        } else {
            Value((a.0 as u64 + self.n as u64 - b.0 as u64) as u32)
        }
    }

    fn mul(&self, a: Self::U, b: Self::U) -> Self::U {
        self.redc((a.0 as u64).wrapping_mul(b.0 as u64))
    }

    fn inv(&self, a: Self::U) -> Self::U {
        let ar_modn_inv = ::numtheory::mod_inverse(a.0 as i64, self.n as i64);
        self.redc((ar_modn_inv as u64).wrapping_mul(self.r_cube as u64))
    }

    fn new(prime: u64) -> MontgomeryField32 {
        MontgomeryField32::new(prime as u32)
    }

    fn from_u64(&self, a: u64) -> Self::U {
        Value(((a << 32) % self.n as u64) as u32)
    }

    fn to_u64(&self, a: Self::U) -> u64 {
        a.0 as u64 * self.r_inv as u64 % self.n as u64
    }
}

#[cfg(test)]
all_fields_test!(MontgomeryField32);
