diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..2e07606 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] diff --git a/Cargo.toml b/Cargo.toml index 136789f..cdaa148 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,16 @@ description = "Post-quantum OPAQUE implementation using lattice-based cryptograp license = "MIT OR Apache-2.0" [dependencies] -pqcrypto-kyber = { version = "0.8", features = ["serialization"] } -pqcrypto-dilithium = { version = "0.5", features = ["serialization"] } -pqcrypto-traits = "0.3" +# Native backend (C FFI - faster but not WASM compatible) +pqcrypto-kyber = { version = "0.8", features = ["serialization"], optional = true } +pqcrypto-dilithium = { version = "0.5", features = ["serialization"], optional = true } +pqcrypto-traits = { version = "0.3", optional = true } + +# WASM backend (pure Rust - WASM compatible) +fips203 = { version = "0.4", default-features = false, features = ["ml-kem-768", "default-rng"], optional = true } +fips204 = { version = "0.4", default-features = false, features = ["ml-dsa-65", "default-rng"], optional = true } +getrandom_03 = { package = "getrandom", version = "0.3", features = ["wasm_js"], optional = true } +getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"], optional = true } sha2 = "0.10" sha3 = "0.10" @@ -42,7 +49,11 @@ name = "timing_verification" harness = false [features] -default = [] +default = ["native"] +# Native backend using pqcrypto (C FFI) - faster, not WASM compatible +native = ["dep:pqcrypto-kyber", "dep:pqcrypto-dilithium", "dep:pqcrypto-traits"] +# WASM backend using fips203/fips204 (pure Rust) - WASM compatible +wasm = ["dep:fips203", "dep:fips204", "dep:getrandom_03", "dep:getrandom_02"] server = ["dep:axum", "dep:tokio", "dep:tower-http"] debug-trace = [] diff --git a/pdfs/GitBookV2.pdf b/pdfs/GitBookV2.pdf new file mode 100644 index 0000000..52d40a3 Binary files /dev/null and b/pdfs/GitBookV2.pdf differ diff --git a/pdfs/access1_git.pdf b/pdfs/access1_git.pdf new file mode 100644 index 0000000..38d3bb2 Binary files /dev/null and b/pdfs/access1_git.pdf differ diff --git a/pdfs/advanced_git.pdf b/pdfs/advanced_git.pdf new file mode 100644 index 0000000..38d3bb2 Binary files /dev/null and b/pdfs/advanced_git.pdf differ diff --git a/pdfs/git_it_princeton.pdf b/pdfs/git_it_princeton.pdf new file mode 100644 index 0000000..b260be2 Binary files /dev/null and b/pdfs/git_it_princeton.pdf differ diff --git a/src/ake/dilithium.rs b/src/ake/dilithium.rs index bb71fd2..759a7c1 100644 --- a/src/ake/dilithium.rs +++ b/src/ake/dilithium.rs @@ -1,97 +1,217 @@ -use pqcrypto_dilithium::dilithium3; -use pqcrypto_traits::sign::{DetachedSignature, PublicKey, SecretKey}; -use zeroize::{Zeroize, ZeroizeOnDrop}; +#[cfg(feature = "native")] +mod native { + use pqcrypto_dilithium::dilithium3; + use pqcrypto_traits::sign::{DetachedSignature, PublicKey, SecretKey}; + use zeroize::{Zeroize, ZeroizeOnDrop}; -use crate::error::{OpaqueError, Result}; -use crate::types::{DILITHIUM_PK_LEN, DILITHIUM_SIG_LEN, DILITHIUM_SK_LEN}; + use crate::error::{OpaqueError, Result}; + use crate::types::{DILITHIUM_PK_LEN, DILITHIUM_SIG_LEN, DILITHIUM_SK_LEN}; -#[derive(Clone)] -pub struct DilithiumPublicKey(dilithium3::PublicKey); + #[derive(Clone)] + pub struct DilithiumPublicKey(pub(crate) dilithium3::PublicKey); -impl DilithiumPublicKey { - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != DILITHIUM_PK_LEN { - return Err(OpaqueError::InvalidKeyLength { - expected: DILITHIUM_PK_LEN, - got: bytes.len(), - }); + impl DilithiumPublicKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != DILITHIUM_PK_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: DILITHIUM_PK_LEN, + got: bytes.len(), + }); + } + dilithium3::PublicKey::from_bytes(bytes) + .map(Self) + .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium public key".into())) } - dilithium3::PublicKey::from_bytes(bytes) - .map(Self) - .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium public key".into())) - } - #[must_use] - pub fn as_bytes(&self) -> Vec { - self.0.as_bytes().to_vec() - } -} - -#[derive(Clone, Zeroize, ZeroizeOnDrop)] -pub struct DilithiumSecretKey { - #[zeroize(skip)] - inner: dilithium3::SecretKey, -} - -impl DilithiumSecretKey { - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != DILITHIUM_SK_LEN { - return Err(OpaqueError::InvalidKeyLength { - expected: DILITHIUM_SK_LEN, - got: bytes.len(), - }); + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.0.as_bytes().to_vec() } - dilithium3::SecretKey::from_bytes(bytes) - .map(|sk| Self { inner: sk }) - .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium secret key".into())) } - #[must_use] - pub fn as_bytes(&self) -> Vec { - self.inner.as_bytes().to_vec() + #[derive(Clone, Zeroize, ZeroizeOnDrop)] + pub struct DilithiumSecretKey { + #[zeroize(skip)] + pub(crate) inner: dilithium3::SecretKey, } -} -#[derive(Clone)] -pub struct DilithiumSignature(dilithium3::DetachedSignature); - -impl DilithiumSignature { - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != DILITHIUM_SIG_LEN { - return Err(OpaqueError::InvalidKeyLength { - expected: DILITHIUM_SIG_LEN, - got: bytes.len(), - }); + impl DilithiumSecretKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != DILITHIUM_SK_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: DILITHIUM_SK_LEN, + got: bytes.len(), + }); + } + dilithium3::SecretKey::from_bytes(bytes) + .map(|sk| Self { inner: sk }) + .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium secret key".into())) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.inner.as_bytes().to_vec() } - dilithium3::DetachedSignature::from_bytes(bytes) - .map(Self) - .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium signature".into())) } - #[must_use] - pub fn as_bytes(&self) -> Vec { - self.0.as_bytes().to_vec() + #[derive(Clone)] + pub struct DilithiumSignature(pub(crate) dilithium3::DetachedSignature); + + impl DilithiumSignature { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != DILITHIUM_SIG_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: DILITHIUM_SIG_LEN, + got: bytes.len(), + }); + } + dilithium3::DetachedSignature::from_bytes(bytes) + .map(Self) + .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium signature".into())) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.0.as_bytes().to_vec() + } + } + + pub fn generate_keypair() -> (DilithiumPublicKey, DilithiumSecretKey) { + let (pk, sk) = dilithium3::keypair(); + (DilithiumPublicKey(pk), DilithiumSecretKey { inner: sk }) + } + + pub fn sign(message: &[u8], sk: &DilithiumSecretKey) -> DilithiumSignature { + let sig = dilithium3::detached_sign(message, &sk.inner); + DilithiumSignature(sig) + } + + pub fn verify(message: &[u8], sig: &DilithiumSignature, pk: &DilithiumPublicKey) -> Result<()> { + dilithium3::verify_detached_signature(&sig.0, message, &pk.0) + .map_err(|_| OpaqueError::SignatureVerificationFailed) } } -pub fn generate_keypair() -> (DilithiumPublicKey, DilithiumSecretKey) { - let (pk, sk) = dilithium3::keypair(); - (DilithiumPublicKey(pk), DilithiumSecretKey { inner: sk }) +#[cfg(feature = "wasm")] +mod wasm { + use fips204::ml_dsa_65; + use fips204::traits::{SerDes, Signer, Verifier}; + use zeroize::{Zeroize, ZeroizeOnDrop}; + + use crate::error::{OpaqueError, Result}; + use crate::types::{DILITHIUM_PK_LEN, DILITHIUM_SIG_LEN, DILITHIUM_SK_LEN}; + + #[derive(Clone)] + pub struct DilithiumPublicKey(pub(crate) ml_dsa_65::PublicKey); + + impl DilithiumPublicKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != DILITHIUM_PK_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: DILITHIUM_PK_LEN, + got: bytes.len(), + }); + } + let arr: [u8; DILITHIUM_PK_LEN] = bytes + .try_into() + .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium public key".into()))?; + ml_dsa_65::PublicKey::try_from_bytes(arr) + .map(Self) + .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium public key".into())) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.0.clone().into_bytes().to_vec() + } + } + + #[derive(Clone, Zeroize, ZeroizeOnDrop)] + pub struct DilithiumSecretKey { + #[zeroize(skip)] + pub(crate) inner: ml_dsa_65::PrivateKey, + } + + impl DilithiumSecretKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != DILITHIUM_SK_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: DILITHIUM_SK_LEN, + got: bytes.len(), + }); + } + let arr: [u8; DILITHIUM_SK_LEN] = bytes + .try_into() + .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium secret key".into()))?; + ml_dsa_65::PrivateKey::try_from_bytes(arr) + .map(|sk| Self { inner: sk }) + .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium secret key".into())) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.inner.clone().into_bytes().to_vec() + } + } + + #[derive(Clone)] + pub struct DilithiumSignature(pub(crate) [u8; DILITHIUM_SIG_LEN]); + + impl DilithiumSignature { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != DILITHIUM_SIG_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: DILITHIUM_SIG_LEN, + got: bytes.len(), + }); + } + let arr: [u8; DILITHIUM_SIG_LEN] = bytes + .try_into() + .map_err(|_| OpaqueError::Deserialization("Invalid Dilithium signature".into()))?; + Ok(Self(arr)) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.0.to_vec() + } + } + + pub fn generate_keypair() -> (DilithiumPublicKey, DilithiumSecretKey) { + let (pk, sk) = ml_dsa_65::try_keygen().expect("keygen should not fail with good RNG"); + (DilithiumPublicKey(pk), DilithiumSecretKey { inner: sk }) + } + + pub fn sign(message: &[u8], sk: &DilithiumSecretKey) -> DilithiumSignature { + let sig = sk + .inner + .try_sign(message, &[]) + .expect("signing should not fail"); + DilithiumSignature(sig) + } + + pub fn verify(message: &[u8], sig: &DilithiumSignature, pk: &DilithiumPublicKey) -> Result<()> { + if pk.0.verify(message, &sig.0, &[]) { + Ok(()) + } else { + Err(OpaqueError::SignatureVerificationFailed) + } + } } -pub fn sign(message: &[u8], sk: &DilithiumSecretKey) -> DilithiumSignature { - let sig = dilithium3::detached_sign(message, &sk.inner); - DilithiumSignature(sig) -} +#[cfg(all(feature = "native", feature = "wasm"))] +compile_error!("Features 'native' and 'wasm' are mutually exclusive. Enable only one."); -pub fn verify(message: &[u8], sig: &DilithiumSignature, pk: &DilithiumPublicKey) -> Result<()> { - dilithium3::verify_detached_signature(&sig.0, message, &pk.0) - .map_err(|_| OpaqueError::SignatureVerificationFailed) -} +#[cfg(all(feature = "native", not(feature = "wasm")))] +pub use native::*; + +#[cfg(all(feature = "wasm", not(feature = "native")))] +pub use wasm::*; #[cfg(test)] mod tests { use super::*; + use crate::types::{DILITHIUM_PK_LEN, DILITHIUM_SIG_LEN, DILITHIUM_SK_LEN}; #[test] fn test_keypair_generation() { @@ -138,7 +258,7 @@ mod tests { let bytes = sig.as_bytes(); assert_eq!(bytes.len(), DILITHIUM_SIG_LEN); - let sig2 = DilithiumSignature::from_bytes(&bytes).unwrap(); + let sig2 = DilithiumSignature::from_bytes(&bytes).expect("deserialization should succeed"); assert_eq!(sig.as_bytes(), sig2.as_bytes()); } @@ -146,7 +266,7 @@ mod tests { fn test_public_key_serialization() { let (pk, _) = generate_keypair(); let bytes = pk.as_bytes(); - let pk2 = DilithiumPublicKey::from_bytes(&bytes).unwrap(); + let pk2 = DilithiumPublicKey::from_bytes(&bytes).expect("deserialization should succeed"); assert_eq!(pk.as_bytes(), pk2.as_bytes()); } diff --git a/src/ake/kyber.rs b/src/ake/kyber.rs index 0e99d8c..15db3fe 100644 --- a/src/ake/kyber.rs +++ b/src/ake/kyber.rs @@ -1,118 +1,258 @@ -use pqcrypto_kyber::kyber768; -use pqcrypto_traits::kem::{Ciphertext, PublicKey, SecretKey, SharedSecret}; -use zeroize::{Zeroize, ZeroizeOnDrop}; +#[cfg(feature = "native")] +mod native { + use pqcrypto_kyber::kyber768; + use pqcrypto_traits::kem::{Ciphertext, PublicKey, SecretKey, SharedSecret}; + use zeroize::{Zeroize, ZeroizeOnDrop}; -use crate::error::{OpaqueError, Result}; -use crate::types::{KYBER_CT_LEN, KYBER_PK_LEN, KYBER_SK_LEN, KYBER_SS_LEN}; + use crate::error::{OpaqueError, Result}; + use crate::types::{KYBER_CT_LEN, KYBER_PK_LEN, KYBER_SK_LEN, KYBER_SS_LEN}; -#[derive(Clone)] -pub struct KyberPublicKey(kyber768::PublicKey); + #[derive(Clone)] + pub struct KyberPublicKey(pub(crate) kyber768::PublicKey); -impl KyberPublicKey { - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != KYBER_PK_LEN { - return Err(OpaqueError::InvalidKeyLength { - expected: KYBER_PK_LEN, - got: bytes.len(), - }); + impl KyberPublicKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != KYBER_PK_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: KYBER_PK_LEN, + got: bytes.len(), + }); + } + kyber768::PublicKey::from_bytes(bytes) + .map(Self) + .map_err(|_| OpaqueError::Deserialization("Invalid Kyber public key".into())) } - kyber768::PublicKey::from_bytes(bytes) - .map(Self) - .map_err(|_| OpaqueError::Deserialization("Invalid Kyber public key".into())) - } - #[must_use] - pub fn as_bytes(&self) -> Vec { - self.0.as_bytes().to_vec() - } -} - -#[derive(Clone, Zeroize, ZeroizeOnDrop)] -pub struct KyberSecretKey { - #[zeroize(skip)] - inner: kyber768::SecretKey, -} - -impl KyberSecretKey { - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != KYBER_SK_LEN { - return Err(OpaqueError::InvalidKeyLength { - expected: KYBER_SK_LEN, - got: bytes.len(), - }); + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.0.as_bytes().to_vec() } - kyber768::SecretKey::from_bytes(bytes) - .map(|sk| Self { inner: sk }) - .map_err(|_| OpaqueError::Deserialization("Invalid Kyber secret key".into())) } - #[must_use] - pub fn as_bytes(&self) -> Vec { - self.inner.as_bytes().to_vec() + #[derive(Clone, Zeroize, ZeroizeOnDrop)] + pub struct KyberSecretKey { + #[zeroize(skip)] + pub(crate) inner: kyber768::SecretKey, } -} -#[derive(Clone)] -pub struct KyberCiphertext(kyber768::Ciphertext); - -impl KyberCiphertext { - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != KYBER_CT_LEN { - return Err(OpaqueError::InvalidKeyLength { - expected: KYBER_CT_LEN, - got: bytes.len(), - }); + impl KyberSecretKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != KYBER_SK_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: KYBER_SK_LEN, + got: bytes.len(), + }); + } + kyber768::SecretKey::from_bytes(bytes) + .map(|sk| Self { inner: sk }) + .map_err(|_| OpaqueError::Deserialization("Invalid Kyber secret key".into())) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.inner.as_bytes().to_vec() } - kyber768::Ciphertext::from_bytes(bytes) - .map(Self) - .map_err(|_| OpaqueError::Deserialization("Invalid Kyber ciphertext".into())) } - #[must_use] - pub fn as_bytes(&self) -> Vec { - self.0.as_bytes().to_vec() + #[derive(Clone)] + pub struct KyberCiphertext(pub(crate) kyber768::Ciphertext); + + impl KyberCiphertext { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != KYBER_CT_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: KYBER_CT_LEN, + got: bytes.len(), + }); + } + kyber768::Ciphertext::from_bytes(bytes) + .map(Self) + .map_err(|_| OpaqueError::Deserialization("Invalid Kyber ciphertext".into())) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.0.as_bytes().to_vec() + } + } + + #[derive(Clone, Zeroize, ZeroizeOnDrop)] + pub struct KyberSharedSecret { + #[zeroize(skip)] + pub(crate) inner: kyber768::SharedSecret, + } + + impl KyberSharedSecret { + #[must_use] + pub fn as_bytes(&self) -> &[u8] { + self.inner.as_bytes() + } + + #[must_use] + pub fn to_array(&self) -> [u8; KYBER_SS_LEN] { + let bytes = self.inner.as_bytes(); + let mut arr = [0u8; KYBER_SS_LEN]; + arr.copy_from_slice(bytes); + arr + } + } + + pub fn generate_keypair() -> (KyberPublicKey, KyberSecretKey) { + let (pk, sk) = kyber768::keypair(); + (KyberPublicKey(pk), KyberSecretKey { inner: sk }) + } + + pub fn encapsulate(pk: &KyberPublicKey) -> Result<(KyberSharedSecret, KyberCiphertext)> { + let (ss, ct) = kyber768::encapsulate(&pk.0); + Ok((KyberSharedSecret { inner: ss }, KyberCiphertext(ct))) + } + + pub fn decapsulate(ct: &KyberCiphertext, sk: &KyberSecretKey) -> Result { + let ss = kyber768::decapsulate(&ct.0, &sk.inner); + Ok(KyberSharedSecret { inner: ss }) } } -#[derive(Clone, Zeroize, ZeroizeOnDrop)] -pub struct KyberSharedSecret { - #[zeroize(skip)] - inner: kyber768::SharedSecret, -} +#[cfg(feature = "wasm")] +mod wasm { + use fips203::ml_kem_768; + use fips203::traits::{Decaps, Encaps, KeyGen, SerDes}; + use zeroize::{Zeroize, ZeroizeOnDrop}; -impl KyberSharedSecret { - #[must_use] - pub fn as_bytes(&self) -> &[u8] { - self.inner.as_bytes() + use crate::error::{OpaqueError, Result}; + use crate::types::{KYBER_CT_LEN, KYBER_PK_LEN, KYBER_SK_LEN, KYBER_SS_LEN}; + + #[derive(Clone)] + pub struct KyberPublicKey(pub(crate) ml_kem_768::EncapsKey); + + impl KyberPublicKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != KYBER_PK_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: KYBER_PK_LEN, + got: bytes.len(), + }); + } + let arr: [u8; KYBER_PK_LEN] = bytes + .try_into() + .map_err(|_| OpaqueError::Deserialization("Invalid Kyber public key".into()))?; + ml_kem_768::EncapsKey::try_from_bytes(arr) + .map(Self) + .map_err(|_| OpaqueError::Deserialization("Invalid Kyber public key".into())) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.0.clone().into_bytes().to_vec() + } } - #[must_use] - pub fn to_array(&self) -> [u8; KYBER_SS_LEN] { - let bytes = self.inner.as_bytes(); - let mut arr = [0u8; KYBER_SS_LEN]; - arr.copy_from_slice(bytes); - arr + #[derive(Clone, Zeroize, ZeroizeOnDrop)] + pub struct KyberSecretKey { + #[zeroize(skip)] + pub(crate) inner: ml_kem_768::DecapsKey, + } + + impl KyberSecretKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != KYBER_SK_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: KYBER_SK_LEN, + got: bytes.len(), + }); + } + let arr: [u8; KYBER_SK_LEN] = bytes + .try_into() + .map_err(|_| OpaqueError::Deserialization("Invalid Kyber secret key".into()))?; + ml_kem_768::DecapsKey::try_from_bytes(arr) + .map(|sk| Self { inner: sk }) + .map_err(|_| OpaqueError::Deserialization("Invalid Kyber secret key".into())) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.inner.clone().into_bytes().to_vec() + } + } + + #[derive(Clone)] + pub struct KyberCiphertext(pub(crate) ml_kem_768::CipherText); + + impl KyberCiphertext { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != KYBER_CT_LEN { + return Err(OpaqueError::InvalidKeyLength { + expected: KYBER_CT_LEN, + got: bytes.len(), + }); + } + let arr: [u8; KYBER_CT_LEN] = bytes + .try_into() + .map_err(|_| OpaqueError::Deserialization("Invalid Kyber ciphertext".into()))?; + ml_kem_768::CipherText::try_from_bytes(arr) + .map(Self) + .map_err(|_| OpaqueError::Deserialization("Invalid Kyber ciphertext".into())) + } + + #[must_use] + pub fn as_bytes(&self) -> Vec { + self.0.clone().into_bytes().to_vec() + } + } + + #[derive(Clone, Zeroize, ZeroizeOnDrop)] + pub struct KyberSharedSecret { + pub(crate) inner: [u8; KYBER_SS_LEN], + } + + impl KyberSharedSecret { + #[must_use] + pub fn as_bytes(&self) -> &[u8] { + &self.inner + } + + #[must_use] + pub fn to_array(&self) -> [u8; KYBER_SS_LEN] { + self.inner + } + } + + pub fn generate_keypair() -> (KyberPublicKey, KyberSecretKey) { + let (ek, dk) = ml_kem_768::KG::try_keygen().expect("keygen should not fail with good RNG"); + (KyberPublicKey(ek), KyberSecretKey { inner: dk }) + } + + pub fn encapsulate(pk: &KyberPublicKey) -> Result<(KyberSharedSecret, KyberCiphertext)> { + let (ssk, ct) = + pk.0.try_encaps() + .map_err(|_| OpaqueError::EncapsulationFailed)?; + let ss_bytes: [u8; KYBER_SS_LEN] = ssk.into_bytes().into(); + Ok((KyberSharedSecret { inner: ss_bytes }, KyberCiphertext(ct))) + } + + pub fn decapsulate(ct: &KyberCiphertext, sk: &KyberSecretKey) -> Result { + let ssk = sk + .inner + .try_decaps(&ct.0) + .map_err(|_| OpaqueError::DecapsulationFailed)?; + let ss_bytes: [u8; KYBER_SS_LEN] = ssk.into_bytes().into(); + Ok(KyberSharedSecret { inner: ss_bytes }) } } -pub fn generate_keypair() -> (KyberPublicKey, KyberSecretKey) { - let (pk, sk) = kyber768::keypair(); - (KyberPublicKey(pk), KyberSecretKey { inner: sk }) -} +#[cfg(all(feature = "native", feature = "wasm"))] +compile_error!("Features 'native' and 'wasm' are mutually exclusive. Enable only one."); -pub fn encapsulate(pk: &KyberPublicKey) -> Result<(KyberSharedSecret, KyberCiphertext)> { - let (ss, ct) = kyber768::encapsulate(&pk.0); - Ok((KyberSharedSecret { inner: ss }, KyberCiphertext(ct))) -} +#[cfg(all(feature = "native", not(feature = "wasm")))] +pub use native::*; -pub fn decapsulate(ct: &KyberCiphertext, sk: &KyberSecretKey) -> Result { - let ss = kyber768::decapsulate(&ct.0, &sk.inner); - Ok(KyberSharedSecret { inner: ss }) -} +#[cfg(all(feature = "wasm", not(feature = "native")))] +pub use wasm::*; #[cfg(test)] mod tests { use super::*; + use crate::types::{KYBER_CT_LEN, KYBER_PK_LEN, KYBER_SK_LEN, KYBER_SS_LEN}; #[test] fn test_keypair_generation() { @@ -125,8 +265,8 @@ mod tests { fn test_encapsulate_decapsulate() { let (pk, sk) = generate_keypair(); - let (ss1, ct) = encapsulate(&pk).unwrap(); - let ss2 = decapsulate(&ct, &sk).unwrap(); + let (ss1, ct) = encapsulate(&pk).expect("encapsulation should succeed"); + let ss2 = decapsulate(&ct, &sk).expect("decapsulation should succeed"); assert_eq!(ss1.as_bytes(), ss2.as_bytes()); assert_eq!(ss1.as_bytes().len(), KYBER_SS_LEN); @@ -136,7 +276,7 @@ mod tests { fn test_public_key_serialization() { let (pk, _) = generate_keypair(); let bytes = pk.as_bytes(); - let pk2 = KyberPublicKey::from_bytes(&bytes).unwrap(); + let pk2 = KyberPublicKey::from_bytes(&bytes).expect("deserialization should succeed"); assert_eq!(pk.as_bytes(), pk2.as_bytes()); } @@ -144,16 +284,16 @@ mod tests { fn test_secret_key_serialization() { let (_, sk) = generate_keypair(); let bytes = sk.as_bytes(); - let sk2 = KyberSecretKey::from_bytes(&bytes).unwrap(); + let sk2 = KyberSecretKey::from_bytes(&bytes).expect("deserialization should succeed"); assert_eq!(sk.as_bytes(), sk2.as_bytes()); } #[test] fn test_ciphertext_serialization() { let (pk, _) = generate_keypair(); - let (_, ct) = encapsulate(&pk).unwrap(); + let (_, ct) = encapsulate(&pk).expect("encapsulation should succeed"); let bytes = ct.as_bytes(); - let ct2 = KyberCiphertext::from_bytes(&bytes).unwrap(); + let ct2 = KyberCiphertext::from_bytes(&bytes).expect("deserialization should succeed"); assert_eq!(ct.as_bytes(), ct2.as_bytes()); }