PHP Classes

File: src/kem.rs

Recommend this page to a friend!
  Packages of Scott Arciszewski   ext-pqcrypto   src/kem.rs   Download  
File: src/kem.rs
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: ext-pqcrypto
PHP extension to encrypt data with FIPS algorithms
Author: By
Last change:
Date: 9 days ago
Size: 12,569 bytes
 

Contents

Class file image Download
use ext_php_rs::binary::Binary; use ext_php_rs::boxed::ZBox; use ext_php_rs::exception::PhpException; use ext_php_rs::prelude::*; use ext_php_rs::types::ZendHashTable; trait KemOps { fn generate() -> (Vec<u8>, Vec<u8>); fn keypair_from_seed(seed: &[u8]) -> Result<(Vec<u8>, Vec<u8>), String>; fn encapsulate(ek_bytes: &[u8]) -> Result<(Vec<u8>, Vec<u8>), String>; fn decapsulate(seed: &[u8], ct: &[u8]) -> Result<Vec<u8>, String>; fn seed_len() -> usize; fn ek_len() -> usize; } macro_rules! define_kem_variant { ( $dk:ident, $dk_name:literal, $ek:ident, $ek_name:literal, $algo:ident, $algo_name:literal, $ops:ty ) => { #[php_class] #[php(name = $dk_name)] #[derive(Clone)] pub struct $dk { seed: Vec<u8>, } #[php_impl] impl $dk { pub fn bytes(&self) -> Binary<u8> { Binary::new(self.seed.clone()) } pub fn decapsulate( &self, ciphertext: Binary<u8>, ) -> PhpResult<Binary<u8>> { <$ops>::decapsulate(&self.seed, &ciphertext) .map(Binary::new) .map_err(|e| PhpException::default(e)) } pub fn fromBytes(bytes: Binary<u8>) -> PhpResult<Self> { let expected = <$ops>::seed_len(); if bytes.len() != expected { return Err(PhpException::default(format!( "Invalid seed length: expected {}, got {}", expected, bytes.len() ))); } Ok(Self { seed: bytes.to_vec(), }) } } #[php_class] #[php(name = $ek_name)] #[derive(Clone)] pub struct $ek { bytes: Vec<u8>, } #[php_impl] impl $ek { pub fn bytes(&self) -> Binary<u8> { Binary::new(self.bytes.clone()) } pub fn encapsulate( &self, ) -> PhpResult<Vec<Binary<u8>>> { let (ss, ct) = <$ops>::encapsulate(&self.bytes) .map_err(|e| PhpException::default(e))?; Ok(vec![Binary::new(ss), Binary::new(ct)]) } pub fn fromBytes(bytes: Binary<u8>) -> PhpResult<Self> { let expected = <$ops>::ek_len(); if bytes.len() != expected { return Err(PhpException::default(format!( "Invalid key length: expected {}, got {}", expected, bytes.len() ))); } Ok(Self { bytes: bytes.to_vec(), }) } } #[php_class] #[php(name = $algo_name)] pub struct $algo; #[php_impl] impl $algo { pub fn generateKeypair( ) -> PhpResult<ZBox<ZendHashTable>> { let (seed, ek_bytes) = <$ops>::generate(); let dk = $dk { seed }; let ek = $ek { bytes: ek_bytes }; let mut ht = ZendHashTable::new(); ht.push(dk).map_err(|e| { PhpException::default(e.to_string()) })?; ht.push(ek).map_err(|e| { PhpException::default(e.to_string()) })?; Ok(ht) } pub fn keypairFromSeed( seed: Binary<u8>, ) -> PhpResult<ZBox<ZendHashTable>> { let (sk, ek_bytes) = <$ops>::keypair_from_seed(&seed) .map_err(|e| PhpException::default(e))?; let dk = $dk { seed: sk }; let ek = $ek { bytes: ek_bytes }; let mut ht = ZendHashTable::new(); ht.push(dk).map_err(|e| { PhpException::default(e.to_string()) })?; ht.push(ek).map_err(|e| { PhpException::default(e.to_string()) })?; Ok(ht) } } }; } mod mlkem_ops { use super::KemOps; use ml_kem::kem::{ Decapsulate, Encapsulate, Generate, KeyExport, KeyInit, }; macro_rules! impl_mlkem { ( $ops:ident, $dk_ty:ty, $ek_ty:ty, $seed_len:literal, $ek_len:literal ) => { pub struct $ops; impl KemOps for $ops { fn generate() -> (Vec<u8>, Vec<u8>) { let mut rng = rand::rng(); let dk = <$dk_ty>::generate_from_rng(&mut rng); let seed_bytes = dk.to_bytes(); let seed: &[u8] = &seed_bytes; let ek = dk.encapsulation_key(); let ek_bytes = ek.to_bytes(); let ek_slice: &[u8] = &ek_bytes; (seed.to_vec(), ek_slice.to_vec()) } fn keypair_from_seed( seed: &[u8], ) -> Result<(Vec<u8>, Vec<u8>), String> { let seed_key: &ml_kem::array::Array<u8, _> = seed.try_into().map_err(|_| { format!( "Invalid seed length: expected {}, got {}", $seed_len, seed.len() ) })?; let dk = <$dk_ty>::new(seed_key); let ek = dk.encapsulation_key(); let ek_bytes = ek.to_bytes(); let ek_s: &[u8] = &ek_bytes; Ok((seed.to_vec(), ek_s.to_vec())) } fn encapsulate( ek_bytes: &[u8], ) -> Result<(Vec<u8>, Vec<u8>), String> { let ek_key: &ml_kem::array::Array<u8, _> = ek_bytes.try_into().map_err(|_| { "Invalid encapsulation key length" .to_string() })?; let ek = <$ek_ty>::new(ek_key).map_err(|e| { format!("Invalid encapsulation key: {e}") })?; let mut rng = rand::rng(); let (ct, ss) = ek.encapsulate_with_rng(&mut rng); let ct_s: &[u8] = &ct; let ss_s: &[u8] = &ss; Ok((ss_s.to_vec(), ct_s.to_vec())) } fn decapsulate( seed: &[u8], ct: &[u8], ) -> Result<Vec<u8>, String> { let seed_key: &ml_kem::array::Array<u8, _> = seed.try_into().map_err(|_| { "Invalid seed length".to_string() })?; let dk = <$dk_ty>::new(seed_key); let ss = dk.decapsulate_slice(ct).map_err( |_| { "Invalid ciphertext length".to_string() }, )?; let ss_s: &[u8] = &ss; Ok(ss_s.to_vec()) } fn seed_len() -> usize { $seed_len } fn ek_len() -> usize { $ek_len } } }; } impl_mlkem!( MlKem512Ops, ml_kem::DecapsulationKey512, ml_kem::EncapsulationKey512, 64, 800 ); impl_mlkem!( MlKem768Ops, ml_kem::DecapsulationKey768, ml_kem::EncapsulationKey768, 64, 1184 ); impl_mlkem!( MlKem1024Ops, ml_kem::DecapsulationKey1024, ml_kem::EncapsulationKey1024, 64, 1568 ); } mod xwing_ops { use super::KemOps; use x_wing::kem::{Decapsulate, Decapsulator, Encapsulate, KeyExport, Kem}; use x_wing::{DecapsulationKey, EncapsulationKey, XWingKem}; pub struct XWingOps; impl KemOps for XWingOps { fn generate() -> (Vec<u8>, Vec<u8>) { let mut rng = rand::rng(); let (sk, pk) = XWingKem::generate_keypair_from_rng(&mut rng); let sk_bytes = sk.as_bytes().to_vec(); let pk_bytes = pk.to_bytes(); let pk_s: &[u8] = &pk_bytes; (sk_bytes, pk_s.to_vec()) } fn keypair_from_seed( seed: &[u8], ) -> Result<(Vec<u8>, Vec<u8>), String> { let sk_arr: [u8; 32] = seed .try_into() .map_err(|_| { format!( "Invalid seed length: expected 32, got {}", seed.len() ) })?; let dk = DecapsulationKey::from(sk_arr); let ek = dk.encapsulation_key(); let ek_bytes = ek.to_bytes(); let ek_s: &[u8] = &ek_bytes; Ok((seed.to_vec(), ek_s.to_vec())) } fn encapsulate( ek_bytes: &[u8], ) -> Result<(Vec<u8>, Vec<u8>), String> { let ek = EncapsulationKey::try_from(ek_bytes) .map_err(|e| { format!("Invalid encapsulation key: {e}") })?; let (ct, ss) = ek.encapsulate(); let ct_s: &[u8] = &ct; let ss_s: &[u8] = &ss; Ok((ss_s.to_vec(), ct_s.to_vec())) } fn decapsulate( seed: &[u8], ct: &[u8], ) -> Result<Vec<u8>, String> { let sk_arr: [u8; 32] = seed .try_into() .map_err(|_| "Invalid seed length".to_string())?; let dk = DecapsulationKey::from(sk_arr); let ss = dk .decapsulate_slice(ct) .map_err(|_| { "Invalid ciphertext length".to_string() })?; let ss_s: &[u8] = &ss; Ok(ss_s.to_vec()) } fn seed_len() -> usize { 32 } fn ek_len() -> usize { 1216 } } } define_kem_variant!( MlKem512Dk, "PQCrypto\\MLKem512\\DecapsulationKey", MlKem512Ek, "PQCrypto\\MLKem512\\EncapsulationKey", MlKem512Algo, "PQCrypto\\MLKem512", mlkem_ops::MlKem512Ops ); define_kem_variant!( MlKem768Dk, "PQCrypto\\MLKem768\\DecapsulationKey", MlKem768Ek, "PQCrypto\\MLKem768\\EncapsulationKey", MlKem768Algo, "PQCrypto\\MLKem768", mlkem_ops::MlKem768Ops ); define_kem_variant!( MlKem1024Dk, "PQCrypto\\MLKem1024\\DecapsulationKey", MlKem1024Ek, "PQCrypto\\MLKem1024\\EncapsulationKey", MlKem1024Algo, "PQCrypto\\MLKem1024", mlkem_ops::MlKem1024Ops ); define_kem_variant!( XWingDk, "PQCrypto\\XWing\\DecapsulationKey", XWingEk, "PQCrypto\\XWing\\EncapsulationKey", XWingAlgo, "PQCrypto\\XWing", xwing_ops::XWingOps ); #[cfg(test)] mod tests { use super::mlkem_ops::{MlKem512Ops, MlKem768Ops, MlKem1024Ops}; use super::xwing_ops::XWingOps; use super::KemOps; fn run_kem_test<T: KemOps>(seed_len: usize, ek_len: usize) { let (seed, ek) = T::generate(); assert_eq!(seed.len(), seed_len); assert_eq!(ek.len(), ek_len); let (ss, ct) = T::encapsulate(&ek).unwrap(); let rss = T::decapsulate(&seed, &ct).unwrap(); assert_eq!(ss, rss); assert!(T::decapsulate(b"short", &ct).is_err()); assert!(T::decapsulate(&seed, b"bad").is_err()); let (seed2, ek2) = T::keypair_from_seed(&seed).unwrap(); assert_eq!(seed, seed2); assert_eq!(ek, ek2); let (ss2, ct2) = T::encapsulate(&ek2).unwrap(); let rss2 = T::decapsulate(&seed2, &ct2).unwrap(); assert_eq!(ss2, rss2); assert!(T::keypair_from_seed(b"short").is_err()); } #[test] fn mlkem512() { run_kem_test::<MlKem512Ops>(64, 800); } #[test] fn mlkem768() { run_kem_test::<MlKem768Ops>(64, 1184); } #[test] fn mlkem1024() { run_kem_test::<MlKem1024Ops>(64, 1568); } #[test] fn xwing() { run_kem_test::<XWingOps>(32, 1216); } }