use libcrux_hacl_rs::bignum::base::{self, bn_mont_ctx_u64};
use libcrux_hacl_rs::bignum::bignum64;
use crate::error::{Error, KeyFormatError};
use crate::low;
#[derive(Clone, Debug)]
pub(crate) struct RsaPublicKey {
pub(crate) n: RsaPosInt,
pub(crate) e: u64,
r2: [u64; MAX_PUBLIC_MODULUS_WORDS],
mu: u64,
}
impl low::Zeroable for RsaPublicKey {}
impl RsaPublicKey {
pub(crate) fn new(n: RsaPosInt, e: u64) -> Result<Self, Error> {
if n.is_even()
|| !(MIN_PUBLIC_MODULUS_BITS..=MAX_PUBLIC_MODULUS_BITS).contains(&n.len_bits())
{
return Err(KeyFormatError::RsaModulus.into());
}
if e.is_multiple_of(2) || e == 1 || e >> MAX_PUBLIC_EXPONENT_BITS != 0 {
return Err(KeyFormatError::RsaExponent.into());
}
let mu = base::mod_inv_uint64(n.as_words()[0]);
let r2 = montgomery_r2(&n, mu);
Ok(Self { n, e, r2, mu })
}
pub(crate) fn modulus_len_bytes(&self) -> usize {
self.n.len_bytes()
}
pub(crate) fn modulus_len_bits(&self) -> usize {
self.n.len_bits()
}
pub(crate) fn public_op(&self, c: RsaPosInt) -> Result<RsaPosInt, Error> {
let n = self.n.as_words();
let len = n.len() as u32;
let c = c.words_at_width(n.len())?;
if bignum64::lt_mask(len, c, n) != u64::MAX {
return Err(Error::WrongLength);
}
let k = [bn_mont_ctx_u64 {
len,
n: n.into(),
mu: self.mu,
r2: self.r2[..n.len()].into(),
}];
let mut m = [0u64; MAX_PUBLIC_MODULUS_WORDS];
let m = &mut m[..n.len()];
let e_bits = u64::BITS - self.e.leading_zeros();
bignum64::mod_exp_vartime_precomp(&k, c, e_bits, &[self.e], m);
RsaPosInt::from_words(m)
}
}
fn montgomery_r2(n: &RsaPosInt, mu: u64) -> [u64; MAX_PUBLIC_MODULUS_WORDS] {
let (bits, n) = (n.len_bits(), n.as_words());
let len = n.len();
let mut r2 = [0u64; MAX_PUBLIC_MODULUS_WORDS];
let mut x = [0u64; MAX_PUBLIC_MODULUS_WORDS];
let (r, x) = (&mut r2[..len], &mut x[..len]);
r[(bits - 1) / 64] = 1 << ((bits - 1) % 64);
for _ in bits - 1..64 * len + len {
x.copy_from_slice(r);
base::bn_add_mod_n_u64(len as u32, n, x, x, r);
}
for _ in 0..6 {
x.copy_from_slice(r);
base::bn_mont_sqr_u64(len as u32, n, mu, x, r);
}
r2
}
const MAX_PUBLIC_MODULUS_BITS: usize = 8192;
pub(crate) const MAX_PUBLIC_MODULUS_WORDS: usize = MAX_PUBLIC_MODULUS_BITS / 64;
pub(crate) const MAX_PUBLIC_MODULUS_BYTES: usize = MAX_PUBLIC_MODULUS_BITS / 8;
const MAX_PUBLIC_EXPONENT_BITS: u32 = 33;
const MIN_PUBLIC_MODULUS_BITS: usize = 1024;
type RsaPosInt = low::PosInt<MAX_PUBLIC_MODULUS_WORDS>;
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn montgomery_r2_matches_hacl() {
for bits in [1024usize, 1025, 2048, 2049, 3072, 4095, 8192] {
let len = bits.div_ceil(64);
let mut low = [0u64; MAX_PUBLIC_MODULUS_WORDS];
low[0] = 1;
low[(bits - 1) / 64] |= 1 << ((bits - 1) % 64);
let mut high = [0u64; MAX_PUBLIC_MODULUS_WORDS];
high[..len].fill(u64::MAX);
high[len - 1] >>= 64 * len - bits;
for n in [&low[..len], &high[..len]] {
let key = RsaPublicKey::new(RsaPosInt::from_words(n).unwrap(), 3).unwrap();
let want = bignum64::mont_ctx_init(len as u32, n);
assert_eq!(key.mu, want[0].mu, "{bits} bits");
assert_eq!(&key.r2[..len], &want[0].r2[..], "{bits} bits");
}
}
}
}