pub mod constants;
mod r3;
mod rq;
mod zx;
use crate::error::{CryptoError, Result};
use crate::primitives::sha3 as native_sha3;
use rand::{CryptoRng, RngCore};
pub use constants::*;
pub type PublicKey = [u8; PK_SIZE];
pub type PrivateKey = [u8; SK_SIZE];
pub type Ciphertext = [u8; CT_SIZE];
pub type SharedSecret = [u8; K_SIZE];
pub struct NtruPrime;
impl NtruPrime {
pub fn generate_keypair_from_seed(seed: &[u8; 32]) -> (PublicKey, PrivateKey) {
use crate::internal::rng::ChaCha20Rng;
let mut rng = ChaCha20Rng::from_seed(*seed);
Self::generate_keypair(&mut rng)
}
pub fn generate_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> (PublicKey, PrivateKey) {
let mut g = [0i8; P];
let gr = loop {
zx::random::random_small(&mut g, rng);
let (mask, gr) = r3::reciprocal(g);
if mask == 0 {
break gr;
}
};
let mut f = [0i8; P];
zx::random::random_tsmall(&mut f, rng);
derive_keypair(f, g, gr)
}
pub fn encapsulate<R: RngCore + CryptoRng>(
pk: &PublicKey,
rng: &mut R,
) -> (Ciphertext, SharedSecret) {
let mut r = [0i8; P];
zx::random::random_tsmall(&mut r, rng);
create_ciphertext(r, *pk)
}
pub fn decapsulate(ciphertext: &Ciphertext, sk: &PrivateKey) -> Result<SharedSecret> {
let f = zx::encoding::decode(&sk[..191]);
let c = rq::encoding::decode_rounded(&ciphertext[32..]);
let mut t = [0i16; P];
rq::mult(&mut t, c, f);
let mut t3 = [0i8; P];
for i in 0..P {
t3[i] = r3::mod3::freeze(rq::modq::freeze(3 * t[i] as i32) as i32);
}
let gr = zx::encoding::decode(&sk[191..382]);
let mut r = [0i8; P];
r3::mult(&mut r, t3, gr);
let w = count_weight(&r);
let weight_check = w == W as i32;
let h = rq::encoding::decode(&sk[382..]);
let mut hr = [0i16; P];
rq::mult(&mut hr, h, r);
rq::round3(&mut hr);
let mut c_check = true;
for i in 0..P {
c_check &= (hr[i] - c[i]) == 0;
}
let s = native_sha3::sha3_512(&zx::encoding::encode(r));
let hash_check = s[..32] == ciphertext[..32];
if weight_check && c_check && hash_check {
let mut shared_secret = [0u8; K_SIZE];
shared_secret.copy_from_slice(&s[32..64]);
Ok(shared_secret)
} else {
Err(CryptoError::Decryption(
"NTRU Prime decapsulation failed".to_string(),
))
}
}
}
fn count_weight(r: &[i8; P]) -> i32 {
r.iter().map(|&x| x.abs() as i32).sum()
}
fn derive_keypair(f: [i8; P], g: [i8; P], gr: [i8; P]) -> (PublicKey, PrivateKey) {
let f3r = rq::reciprocal3(f);
let mut h = [0i16; P];
rq::mult(&mut h, f3r, g);
let pk = rq::encoding::encode(h);
let mut sk = [0u8; SK_SIZE];
sk[..191].copy_from_slice(&zx::encoding::encode(f));
sk[191..382].copy_from_slice(&zx::encoding::encode(gr));
sk[382..].copy_from_slice(&pk);
(pk, sk)
}
fn create_ciphertext(r: [i8; P], pk: PublicKey) -> (Ciphertext, SharedSecret) {
let h = rq::encoding::decode(&pk);
let mut c = [0i16; P];
rq::mult(&mut c, h, r);
rq::round3(&mut c);
let s = native_sha3::sha3_512(&zx::encoding::encode(r));
let mut shared_secret = [0u8; K_SIZE];
shared_secret.copy_from_slice(&s[32..64]);
let mut ciphertext = [0u8; CT_SIZE];
ciphertext[..32].copy_from_slice(&s[..32]);
ciphertext[32..].copy_from_slice(&rq::encoding::encode_rounded(c));
(ciphertext, shared_secret)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::internal::rng::ChaCha20Rng;
#[test]
fn test_ntru_prime_roundtrip() {
let mut rng = ChaCha20Rng::from_seed([42u8; 32]);
let (pk, sk) = NtruPrime::generate_keypair(&mut rng);
let (ct, ss1) = NtruPrime::encapsulate(&pk, &mut rng);
let ss2 = NtruPrime::decapsulate(&ct, &sk).expect("Decapsulation should succeed");
assert_eq!(ss1, ss2);
}
#[test]
fn test_deterministic_keypair() {
let seed = [99u8; 32];
let mut rng1 = ChaCha20Rng::from_seed(seed);
let (pk1, sk1) = NtruPrime::generate_keypair(&mut rng1);
let mut rng2 = ChaCha20Rng::from_seed(seed);
let (pk2, sk2) = NtruPrime::generate_keypair(&mut rng2);
assert_eq!(pk1, pk2, "Same seed should produce same public key");
assert_eq!(sk1, sk2, "Same seed should produce same private key");
}
#[test]
fn test_key_sizes() {
let mut rng = ChaCha20Rng::from_seed([1u8; 32]);
let (pk, sk) = NtruPrime::generate_keypair(&mut rng);
assert_eq!(pk.len(), PK_SIZE);
assert_eq!(sk.len(), SK_SIZE);
}
}