From 95fba5ae618866864e1f1f60573adde3ed04b640 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Mon, 24 Jun 2024 14:11:11 +0200 Subject: [PATCH 1/5] kem: Defined an encapsulate_in_place method --- kem/src/lib.rs | 8 ++++++++ kem/tests/hpke.rs | 12 +++++++++++- kem/tests/saber.rs | 17 ++++++++++++++--- kem/tests/x3dh.rs | 10 ++++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/kem/src/lib.rs b/kem/src/lib.rs index 27dc0aa19..41e4a873a 100644 --- a/kem/src/lib.rs +++ b/kem/src/lib.rs @@ -21,6 +21,14 @@ pub trait Encapsulate { /// Encapsulates a fresh shared secret fn encapsulate(&self, rng: &mut impl CryptoRngCore) -> Result<(EK, SS), Self::Error>; + + /// Encapsulates a fresh shared secret, placing the encapsulated key into the given mut ref. + /// If this errors, the final value of `encapsulated_key` MUST equal its original value. + fn encapsulate_in_place( + &self, + rng: &mut impl CryptoRngCore, + encapsulated_key: &mut EK, + ) -> Result; } /// A value that can be used to decapsulate an encapsulated key. Often, this will just be a secret diff --git a/kem/tests/hpke.rs b/kem/tests/hpke.rs index 43ad61c9d..263499622 100644 --- a/kem/tests/hpke.rs +++ b/kem/tests/hpke.rs @@ -21,7 +21,17 @@ impl Encapsulate for PublicKey { &self, mut csprng: &mut impl CryptoRngCore, ) -> Result<(EncappedKey, SharedSecret), HpkeError> { - ::encap(&self.0, None, &mut csprng).map(|(ek, ss)| (ss, ek)) + ::encap(&self.0, None, &mut csprng).map(|(ss, ek)| (ek, ss)) + } + + fn encapsulate_in_place( + &self, + csprng: &mut impl CryptoRngCore, + encapsulated_key: &mut EncappedKey, + ) -> Result { + let (ek, ss) = self.encapsulate(csprng)?; + *encapsulated_key = ek; + Ok(ss) } } diff --git a/kem/tests/saber.rs b/kem/tests/saber.rs index 13e85ef6b..dcd2e798b 100644 --- a/kem/tests/saber.rs +++ b/kem/tests/saber.rs @@ -1,5 +1,7 @@ use kem::{Decapsulate, Encapsulate}; +use core::convert::Infallible; + use pqcrypto::kem::firesaber::{ decapsulate, encapsulate, keypair, Ciphertext as SaberEncappedKey, PublicKey, SecretKey, SharedSecret as SaberSharedSecret, @@ -12,16 +14,25 @@ struct SaberPublicKey(PublicKey); struct SaberPrivateKey(SecretKey); impl Encapsulate for SaberPublicKey { - // TODO: Encapsulation is infallible. Make this the never type once it's available - type Error = (); + type Error = Infallible; fn encapsulate( &self, _: &mut impl CryptoRngCore, - ) -> Result<(SaberEncappedKey, SaberSharedSecret), ()> { + ) -> Result<(SaberEncappedKey, SaberSharedSecret), Infallible> { let (ss, ek) = encapsulate(&self.0); Ok((ek, ss)) } + + fn encapsulate_in_place( + &self, + csprng: &mut impl CryptoRngCore, + encapsulated_key: &mut SaberEncappedKey, + ) -> Result { + let (ek, ss) = self.encapsulate(csprng)?; + *encapsulated_key = ek; + Ok(ss) + } } impl Decapsulate for SaberPrivateKey { diff --git a/kem/tests/x3dh.rs b/kem/tests/x3dh.rs index b39a5adf8..e343bafa0 100644 --- a/kem/tests/x3dh.rs +++ b/kem/tests/x3dh.rs @@ -68,6 +68,16 @@ impl Encapsulate for EncapContext { Ok((ek, shared_secret)) } + + fn encapsulate_in_place( + &self, + rng: &mut impl CryptoRngCore, + encapsulated_key: &mut EphemeralKey, + ) -> Result { + let (ek, ss) = self.encapsulate(rng)?; + *encapsulated_key = ek; + Ok(ss) + } } // Define an decapsulator. Since authenticated and unauthenticated encapped keys are represented by From 16cbfc1cad273a575c5db990e602fd5e74472636 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Mon, 24 Jun 2024 15:04:28 +0200 Subject: [PATCH 2/5] kem: Made Saber decap infallible too --- kem/tests/saber.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/kem/tests/saber.rs b/kem/tests/saber.rs index dcd2e798b..8c2b33574 100644 --- a/kem/tests/saber.rs +++ b/kem/tests/saber.rs @@ -36,10 +36,9 @@ impl Encapsulate for SaberPublicKey { } impl Decapsulate for SaberPrivateKey { - // TODO: Decapsulation is infallible. Make this the never type once it's available - type Error = (); + type Error = Infallible; - fn decapsulate(&self, ek: &SaberEncappedKey) -> Result { + fn decapsulate(&self, ek: &SaberEncappedKey) -> Result { Ok(decapsulate(ek, &self.0)) } } From 69da640c4443c4c569f081d113eb5bedf87d3240 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Tue, 25 Jun 2024 01:48:17 +0200 Subject: [PATCH 3/5] Added default impl for encapsulate_in_place; added encapsulate_in_place to one unit test --- kem/src/lib.rs | 8 +++++++- kem/tests/hpke.rs | 12 ++---------- kem/tests/saber.rs | 12 ++---------- kem/tests/x3dh.rs | 18 ++++++++---------- 4 files changed, 19 insertions(+), 31 deletions(-) diff --git a/kem/src/lib.rs b/kem/src/lib.rs index 41e4a873a..ec923df6a 100644 --- a/kem/src/lib.rs +++ b/kem/src/lib.rs @@ -28,7 +28,13 @@ pub trait Encapsulate { &self, rng: &mut impl CryptoRngCore, encapsulated_key: &mut EK, - ) -> Result; + ) -> Result { + // Provide a default encapsulate_in_place implementation. If an implementer is + // performance-conscious, they can override this. + let (ek, ss) = self.encapsulate(rng)?; + *encapsulated_key = ek; + Ok(ss) + } } /// A value that can be used to decapsulate an encapsulated key. Often, this will just be a secret diff --git a/kem/tests/hpke.rs b/kem/tests/hpke.rs index 263499622..9eb892f67 100644 --- a/kem/tests/hpke.rs +++ b/kem/tests/hpke.rs @@ -23,16 +23,6 @@ impl Encapsulate for PublicKey { ) -> Result<(EncappedKey, SharedSecret), HpkeError> { ::encap(&self.0, None, &mut csprng).map(|(ss, ek)| (ek, ss)) } - - fn encapsulate_in_place( - &self, - csprng: &mut impl CryptoRngCore, - encapsulated_key: &mut EncappedKey, - ) -> Result { - let (ek, ss) = self.encapsulate(csprng)?; - *encapsulated_key = ek; - Ok(ss) - } } impl Decapsulate for PrivateKey { @@ -60,4 +50,6 @@ fn test_hpke() { let (ek, ss1) = pk_recip.encapsulate(&mut rng).unwrap(); let ss2 = sk_recip.decapsulate(&ek).unwrap(); assert_eq!(ss1.0, ss2.0); + + // Can't use encapsulate_in_place for this crate, because EncappedKey has no constructor } diff --git a/kem/tests/saber.rs b/kem/tests/saber.rs index 8c2b33574..e45a0794e 100644 --- a/kem/tests/saber.rs +++ b/kem/tests/saber.rs @@ -23,16 +23,6 @@ impl Encapsulate for SaberPublicKey { let (ss, ek) = encapsulate(&self.0); Ok((ek, ss)) } - - fn encapsulate_in_place( - &self, - csprng: &mut impl CryptoRngCore, - encapsulated_key: &mut SaberEncappedKey, - ) -> Result { - let (ek, ss) = self.encapsulate(csprng)?; - *encapsulated_key = ek; - Ok(ss) - } } impl Decapsulate for SaberPrivateKey { @@ -60,4 +50,6 @@ fn test_saber() { let (ek, ss1) = pk_recip.encapsulate(&mut rng).unwrap(); let ss2 = sk_recip.decapsulate(&ek).unwrap(); assert_eq!(ss1.as_bytes(), ss2.as_bytes()); + + // Can't use encapsulate_in_place for this crate, because Ciphertext has no constructor } diff --git a/kem/tests/x3dh.rs b/kem/tests/x3dh.rs index e343bafa0..56092ca85 100644 --- a/kem/tests/x3dh.rs +++ b/kem/tests/x3dh.rs @@ -68,16 +68,6 @@ impl Encapsulate for EncapContext { Ok((ek, shared_secret)) } - - fn encapsulate_in_place( - &self, - rng: &mut impl CryptoRngCore, - encapsulated_key: &mut EphemeralKey, - ) -> Result { - let (ek, ss) = self.encapsulate(rng)?; - *encapsulated_key = ek; - Ok(ss) - } } // Define an decapsulator. Since authenticated and unauthenticated encapped keys are represented by @@ -120,4 +110,12 @@ fn test_x3dh() { let (encapped_key, ss1) = encap_context.encapsulate(&mut rng).unwrap(); let ss2 = decap_context.decapsulate(&encapped_key).unwrap(); assert_eq!(ss1, ss2); + + // Now do the same but with encapsulate_in_place + let mut encapped_key = EphemeralKey::default(); + let ss1 = encap_context + .encapsulate_in_place(&mut rng, &mut encapped_key) + .unwrap(); + let ss2 = decap_context.decapsulate(&encapped_key).unwrap(); + assert_eq!(ss1, ss2); } From c043312201ff1c25602526b1495369157176455f Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Tue, 25 Jun 2024 18:05:13 +0200 Subject: [PATCH 4/5] kem: Clarified docs on traits --- kem/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kem/src/lib.rs b/kem/src/lib.rs index ec923df6a..68d27f81d 100644 --- a/kem/src/lib.rs +++ b/kem/src/lib.rs @@ -12,9 +12,9 @@ use core::fmt::Debug; use rand_core::CryptoRngCore; -/// A value that can be encapsulated to. Often, this will just be a public key. However, it can -/// also be a bundle of public keys, or it can include a sender's private key for authenticated -/// encapsulation. +/// This trait implements encapsulation. Often, the implementer will just be a public key. However, +/// it can also be a bundle of public keys, or it can include a sender's private key for +/// authenticated encapsulation. pub trait Encapsulate { /// Encapsulation error type Error: Debug; @@ -37,9 +37,9 @@ pub trait Encapsulate { } } -/// A value that can be used to decapsulate an encapsulated key. Often, this will just be a secret -/// key. But, as with [`Encapsulate`], it can be a bundle of secret keys, or it can include a -/// sender's private key for authenticated encapsulation. +/// This trait implements decapsulation. Often, the implementer will just be a secret key. But, as +/// with [`Encapsulate`], it can be a bundle of secret keys, or it can include a sender's private +/// key for authenticated encapsulation. pub trait Decapsulate { /// Decapsulation error type Error: Debug; From 641f3ad9c9c025ce17f20cd40f60d4394ec04ea4 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Fri, 28 Jun 2024 12:27:54 +0200 Subject: [PATCH 5/5] Made EncapsulateInPlace its own trait --- kem/src/lib.rs | 16 +++++++++------- kem/tests/x3dh.rs | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/kem/src/lib.rs b/kem/src/lib.rs index 68d27f81d..0cbd043fe 100644 --- a/kem/src/lib.rs +++ b/kem/src/lib.rs @@ -21,6 +21,14 @@ pub trait Encapsulate { /// Encapsulates a fresh shared secret fn encapsulate(&self, rng: &mut impl CryptoRngCore) -> Result<(EK, SS), Self::Error>; +} + +/// This trait implements in-place encapsulation. Often, the implementer will just be a public key. +/// However, it can also be a bundle of public keys, or it can include a sender's private key for +/// authenticated encapsulation. +pub trait EncapsulateInPlace { + /// Encapsulation error + type Error: Debug; /// Encapsulates a fresh shared secret, placing the encapsulated key into the given mut ref. /// If this errors, the final value of `encapsulated_key` MUST equal its original value. @@ -28,13 +36,7 @@ pub trait Encapsulate { &self, rng: &mut impl CryptoRngCore, encapsulated_key: &mut EK, - ) -> Result { - // Provide a default encapsulate_in_place implementation. If an implementer is - // performance-conscious, they can override this. - let (ek, ss) = self.encapsulate(rng)?; - *encapsulated_key = ek; - Ok(ss) - } + ) -> Result; } /// This trait implements decapsulation. Often, the implementer will just be a secret key. But, as diff --git a/kem/tests/x3dh.rs b/kem/tests/x3dh.rs index 56092ca85..3b1e14d17 100644 --- a/kem/tests/x3dh.rs +++ b/kem/tests/x3dh.rs @@ -1,4 +1,4 @@ -use kem::{Decapsulate, Encapsulate}; +use kem::{Decapsulate, Encapsulate, EncapsulateInPlace}; use p256::ecdsa::Signature; use rand_core::CryptoRngCore; @@ -70,6 +70,22 @@ impl Encapsulate for EncapContext { } } +// Same thing but in-place +impl EncapsulateInPlace for EncapContext { + type Error = &'static str; + + // Do the dumb thing and just call encapsulate() and copy the result into the mut ref + fn encapsulate_in_place( + &self, + rng: &mut impl CryptoRngCore, + encapsulated_key: &mut EphemeralKey, + ) -> Result { + let (ek, ss) = self.encapsulate(rng)?; + *encapsulated_key = ek; + Ok(ss) + } +} + // Define an decapsulator. Since authenticated and unauthenticated encapped keys are represented by // the same type (which, outside of testing, should not be the case), this can do both auth'd and // unauth'd decapsulation.