rozbb / saber-rs

A pure-Rust implementation of the Saber key encapsulation mechanism (KEM)
Other
4 stars 1 forks source link

Ciphertext constructor requires copying #6

Open rozbb opened 2 weeks ago

rozbb commented 2 weeks ago

Currently, SaberCiphertext can only be constructed from from_bytes, which requires a [u8; Self::LEN]. This requires a ciphertext to be copied before it is decapsulated. This is entirely unnecessary, as it could just as easily be a slice.

One solution is to define a SaberCiphertextRef<'a> which contains a &'a [u8; Self::LEN]. So encap returns SaberCiphertext and decap takes a SaberCiphertextRef. And we can impl AsRef<SaberCiphertextRef> for SaberCiphertext to make conversion straightforward.

rozbb commented 1 week ago

Ok here is a different idea. Just make pub type XSaberCiphertext = [u8; XSABER_CIPHERTEXT_LEN]. This gets exactly the ref semnatics we want, and lets us avoid inventing 2 newtypes.

Below is what the new API looks like. Notice that this also gives us a natural way to do an encapsulate_in_place, whereas with a custom ciphertext type we'd need to have a CiphertextMutRef to hold that.

cc @thomwiggers @pinkforest

use saber_kem::{
    kem_traits::{Decapsulate, Encapsulate},
    lightsaber::{
        LightsaberCiphertext, LightsaberPublicKey, LightsaberSecretKey, LIGHTSABER_CIPHERTEXT_LEN,
    },
};

fn main() {
    let mut rng = rand::thread_rng();

    // Generate a keypair
    let sk = LightsaberSecretKey::generate(&mut rng);
    let pk = sk.public_key();

    // Serialize the secret key, maybe to save on disk
    let mut sk_bytes = [0u8; LightsaberSecretKey::SERIALIZED_LEN];
    sk.to_bytes(&mut sk_bytes);
    let slice_containing_sk = sk_bytes.as_slice();

    // Deserialize the secret key
    // The API only accepts fixed-len slices, so we have to cast it first
    let sk_arr = slice_containing_sk[..LightsaberSecretKey::SERIALIZED_LEN]
        .try_into()
        .unwrap();
    let sk = LightsaberSecretKey::from_bytes(sk_arr);

    // Also serialize and deserialize the public key
    let mut pk_bytes = [0u8; LightsaberPublicKey::SERIALIZED_LEN];
    pk.to_bytes(&mut pk_bytes);
    let slice_containing_pk = pk_bytes.as_slice();
    // The API only accepts fixed-len slices, so we have to cast it first
    let pk_arr = slice_containing_pk[..LightsaberPublicKey::SERIALIZED_LEN]
        .try_into()
        .unwrap();
    let pk = LightsaberPublicKey::from_bytes(pk_arr);

    // Encapsulate a shared secret, ss1, to pk
    let (ct, ss1) = pk.encapsulate(&mut rng).unwrap();
    // Alternatively, if you have a buffer and want to avoid an extra allocation, encapsulate in
    // place:
    let mut ct = [0u8; LIGHTSABER_CIPHERTEXT_LEN];
    let ss1 = pk.encapsulate_in_place(&mut rng, &mut ct).unwrap();
    // The ciphertext is just bytes, so serializing is straightforward
    let ct_bytes = ct.as_ref();

    // Deserializing is also straightforward
    assert_eq!(ct_bytes.len(), LIGHTSABER_CIPHERTEXT_LEN);
    let receiver_ct: &LightsaberCiphertext = ct_bytes.try_into().unwrap();

    // Use the secret key to decapsulate the ciphertext
    let ss2 = sk.decapsulate(receiver_ct).unwrap();

    // Check the shared secrets are equal. NOTE is not a constant-time check (ie not secure). We
    // only do this for testing purposes.
    assert_eq!(ss1.as_bytes(), ss2.as_bytes());

    println!("KEM ran successfully");
}